@yuneta/gobj-ui 7.23.25 → 7.23.27

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.
@@ -9710,23 +9710,41 @@ function yui_graph_update_anchor($container, state) {
9710
9710
  * an error worth a log line per notch, they are all "there is
9711
9711
  * nothing to centre right now".
9712
9712
  ************************************************************/
9713
+ var __centring__ = /* @__PURE__ */ new WeakMap();
9713
9714
  async function yui_graph_center_on(graph, node_id) {
9714
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
+ });
9715
9725
  try {
9716
9726
  if (typeof graph.getElementData === "function" && !graph.getElementData(node_id)) return false;
9717
9727
  if (typeof graph.getElementPosition !== "function" || typeof graph.getViewportByCanvas !== "function" || typeof graph.getCanvasCenter !== "function" || typeof graph.translateTo !== "function") return false;
9718
9728
  for (let i = 0; i < 3; i++) {
9729
+ let z = graph.getZoom() || 1;
9719
9730
  let at = graph.getViewportByCanvas(graph.getElementPosition(node_id));
9720
9731
  let want = graph.getCanvasCenter();
9721
9732
  let dx = want[0] - at[0];
9722
9733
  let dy = want[1] - at[1];
9723
9734
  if (Math.abs(dx) < 1 && Math.abs(dy) < 1) break;
9724
- let pos = graph.getPosition();
9725
- await graph.translateTo([pos[0] + dx, pos[1] + dy]);
9735
+ let n = graph.getElementPosition(node_id);
9736
+ await graph.translateTo([(want[0] - n[0]) * z, (want[1] - n[1]) * z]);
9726
9737
  }
9727
9738
  return true;
9728
9739
  } catch (e) {
9729
9740
  return false;
9741
+ } finally {
9742
+ let pending = __centring__.get(graph);
9743
+ __centring__.set(graph, {
9744
+ running: false,
9745
+ again: null
9746
+ });
9747
+ if (pending && pending.again) yui_graph_center_on(graph, pending.again);
9730
9748
  }
9731
9749
  }
9732
9750
  /************************************************************
@@ -10573,7 +10591,8 @@ var PRIVATE_DATA$11 = {
10573
10591
  anchor_id: "",
10574
10592
  anchor_path: "",
10575
10593
  anchor_state: "off",
10576
- last_zoom: 1
10594
+ last_zoom: 1,
10595
+ center_posted: false
10577
10596
  };
10578
10597
  var __gclass__$11 = null;
10579
10598
  var __layout_registered__$1 = false;
@@ -10941,11 +10960,7 @@ function build_graph$3(gobj) {
10941
10960
  }
10942
10961
  },
10943
10962
  layout: layout_cfg.g6_layout,
10944
- behaviors: [
10945
- "drag-canvas",
10946
- "zoom-canvas",
10947
- "drag-element"
10948
- ]
10963
+ behaviors: BEHAVIORS_READING$1.slice()
10949
10964
  });
10950
10965
  if (priv.theme) graph.setTheme(priv.theme);
10951
10966
  graph.on(_antv_g6.NodeEvent.CLICK, (evt) => {
@@ -10961,7 +10976,10 @@ function build_graph$3(gobj) {
10961
10976
  }
10962
10977
  if (z === priv.last_zoom) return;
10963
10978
  priv.last_zoom = z;
10964
- if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
10979
+ if (priv.anchor_state === "on" && priv.anchor_id && !priv.center_posted) {
10980
+ priv.center_posted = true;
10981
+ (0, _yuneta_gobj_js.gobj_post_event)(gobj, "EV_CENTER_ANCHOR", {}, gobj);
10982
+ }
10965
10983
  });
10966
10984
  graph.on(_antv_g6.CanvasEvent.CLICK, (evt) => {});
10967
10985
  if (typeof ResizeObserver !== "undefined") {
@@ -11052,6 +11070,16 @@ function build_cell_html(key, value, type, dark, matched, fold) {
11052
11070
  * forever. `+3` and `−` need no language.
11053
11071
  ************************************************************/
11054
11072
  var FOLD_SPACER = `<span style="width:24px; flex:0 0 auto;"></span>`;
11073
+ var ZOOM_CANVAS$1 = {
11074
+ type: "zoom-canvas",
11075
+ animation: false
11076
+ };
11077
+ var BEHAVIORS_READING$1 = [
11078
+ "drag-canvas",
11079
+ ZOOM_CANVAS$1,
11080
+ "drag-element"
11081
+ ];
11082
+ var BEHAVIORS_PICKING$1 = ["drag-canvas", ZOOM_CANVAS$1];
11055
11083
  var CARD_HEADER_H$1 = 24;
11056
11084
  var CARD_ROW_H$1 = 22;
11057
11085
  function fold_toggle_html(path, foldable, folded, branches, colors) {
@@ -11188,7 +11216,7 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
11188
11216
  let min_width = Math.max(180, label.length * 9);
11189
11217
  let card_height = CARD_HEADER_H$1 + lines.length * CARD_ROW_H$1;
11190
11218
  let node_html = `
11191
- <div style="
11219
+ <div class="JSON_CARD" data-json-path="${(0, _yuneta_gobj_js.escapeHtml)(path)}" style="
11192
11220
  min-width: ${min_width}px;
11193
11221
  background: ${colors.bg};
11194
11222
  border: ${border_width}px ${is_dict ? "dashed" : "solid"} ${border_color};
@@ -11292,6 +11320,7 @@ function load_json(gobj) {
11292
11320
  });
11293
11321
  graph.render().then(() => {
11294
11322
  reanchor$1(gobj);
11323
+ paint_anchor_mark(gobj);
11295
11324
  if (preserve_view) {
11296
11325
  yui_graph_update_zoom((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.graph);
11297
11326
  return;
@@ -11613,10 +11642,49 @@ function reanchor$1(gobj) {
11613
11642
  yui_graph_update_anchor((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.anchor_state);
11614
11643
  }
11615
11644
  /************************************************************
11645
+ * Reading, or picking the node to anchor.
11646
+ *
11647
+ * Two visible consequences and one invisible: the cursor says
11648
+ * a click is expected, the mount says which mode it is in for
11649
+ * anyone reading the DOM, and node dragging stands aside so a
11650
+ * click that drifts is still a click.
11651
+ ************************************************************/
11652
+ function set_picking_mode$1(gobj, picking) {
11653
+ let graph = gobj.priv.graph;
11654
+ if (graph && typeof graph.setBehaviors === "function") graph.setBehaviors((picking ? BEHAVIORS_PICKING$1 : BEHAVIORS_READING$1).slice());
11655
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
11656
+ if ($container) {
11657
+ let $mount = $container.querySelector(".json-graph-container");
11658
+ if ($mount) $mount.classList.toggle("JSON_GRAPH_PICKING", !!picking);
11659
+ }
11660
+ }
11661
+ /************************************************************
11662
+ * Mark the card the camera is holding.
11663
+ *
11664
+ * A class on the card's own element and not a G6 node state:
11665
+ * G6 paints no state style on an html node, so `setElementState`
11666
+ * would select it correctly and show nothing.
11667
+ *
11668
+ * Re-applied after every rebuild, because the cards are new DOM
11669
+ * each time and the mark would otherwise survive only until the
11670
+ * next fold.
11671
+ ************************************************************/
11672
+ function paint_anchor_mark(gobj) {
11673
+ let priv = gobj.priv;
11674
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
11675
+ if (!$container) return;
11676
+ for (let $card of $container.querySelectorAll(".JSON_CARD_ANCHORED")) $card.classList.remove("JSON_CARD_ANCHORED");
11677
+ if (priv.anchor_state !== "on" || !priv.anchor_path) return;
11678
+ let $it = $container.querySelector(".JSON_CARD[data-json-path=\"" + CSS.escape(priv.anchor_path) + "\"]");
11679
+ if ($it) $it.classList.add("JSON_CARD_ANCHORED");
11680
+ }
11681
+ /************************************************************
11616
11682
  * Centre on the anchor, one cycle after it was chosen.
11617
11683
  ************************************************************/
11618
11684
  function ac_center_anchor$2(gobj, event, kw, src) {
11619
11685
  let priv = gobj.priv;
11686
+ priv.center_posted = false;
11687
+ paint_anchor_mark(gobj);
11620
11688
  if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
11621
11689
  return 0;
11622
11690
  }
@@ -11637,6 +11705,8 @@ function ac_toggle_anchor$2(gobj, event, kw, src) {
11637
11705
  priv.anchor_path = "";
11638
11706
  }
11639
11707
  yui_graph_update_anchor((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.anchor_state);
11708
+ paint_anchor_mark(gobj);
11709
+ set_picking_mode$1(gobj, priv.anchor_state === "arming");
11640
11710
  return 0;
11641
11711
  }
11642
11712
  /************************************************************
@@ -11703,6 +11773,7 @@ function ac_node_click$4(gobj, event, kw, src) {
11703
11773
  priv.anchor_path = nodedata && nodedata.data ? nodedata.data.path : "";
11704
11774
  priv.anchor_state = "on";
11705
11775
  yui_graph_update_anchor((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.anchor_state);
11776
+ set_picking_mode$1(gobj, false);
11706
11777
  (0, _yuneta_gobj_js.gobj_post_event)(gobj, "EV_CENTER_ANCHOR", {}, gobj);
11707
11778
  return 0;
11708
11779
  }
@@ -13169,6 +13240,16 @@ var attrs_table$9 = [
13169
13240
  (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."),
13170
13241
  (0, _yuneta_gobj_js.SDATA_END)()
13171
13242
  ];
13243
+ var ZOOM_CANVAS = {
13244
+ type: "zoom-canvas",
13245
+ animation: false
13246
+ };
13247
+ var BEHAVIORS_READING = [
13248
+ "drag-canvas",
13249
+ ZOOM_CANVAS,
13250
+ "drag-element"
13251
+ ];
13252
+ var BEHAVIORS_PICKING = ["drag-canvas", ZOOM_CANVAS];
13172
13253
  var PRIVATE_DATA$9 = {
13173
13254
  canvas_id: "",
13174
13255
  graph: null,
@@ -13458,7 +13539,7 @@ function build_ui$8(gobj) {
13458
13539
  {
13459
13540
  id: priv.canvas_id,
13460
13541
  class: "gobj-tree-container",
13461
- style: "height:100%; min-height:0; border: 1px solid var(--bulma-border-weak); border-radius:0.2rem; touch-action:none; -webkit-user-select:none; user-select:none;"
13542
+ style: "height:100%; min-height:0; border: 1px solid var(--bulma-border-weak); border-radius:0.2rem;"
13462
13543
  },
13463
13544
  []
13464
13545
  ]]
@@ -13650,11 +13731,7 @@ function build_graph$2(gobj) {
13650
13731
  }
13651
13732
  },
13652
13733
  layout: layout_cfg.g6_layout,
13653
- behaviors: [
13654
- "drag-canvas",
13655
- "zoom-canvas",
13656
- "drag-element"
13657
- ]
13734
+ behaviors: BEHAVIORS_READING.slice()
13658
13735
  });
13659
13736
  if (priv.theme) graph.setTheme(priv.theme);
13660
13737
  graph.on("aftertransform", () => {
@@ -14526,6 +14603,16 @@ function ac_center_anchor$1(gobj, event, kw, src) {
14526
14603
  return 0;
14527
14604
  }
14528
14605
  /************************************************************
14606
+ * Reading, or picking the node to anchor.
14607
+ ************************************************************/
14608
+ function set_picking_mode(gobj, picking) {
14609
+ let graph = gobj.priv.graph;
14610
+ if (graph && typeof graph.setBehaviors === "function") graph.setBehaviors((picking ? BEHAVIORS_PICKING : BEHAVIORS_READING).slice());
14611
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
14612
+ let $mount = $container ? $container.querySelector(".gobj-tree-container") : null;
14613
+ if ($mount) $mount.classList.toggle("GOBJ_TREE_PICKING", !!picking);
14614
+ }
14615
+ /************************************************************
14529
14616
  * Arm the anchor, or let it go.
14530
14617
  *
14531
14618
  * Three states and one button, so a press ADVANCES: with no
@@ -14542,6 +14629,7 @@ function ac_toggle_anchor$1(gobj, event, kw, src) {
14542
14629
  priv.anchor_name = "";
14543
14630
  }
14544
14631
  yui_graph_update_anchor((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.anchor_state);
14632
+ set_picking_mode(gobj, priv.anchor_state === "arming");
14545
14633
  return 0;
14546
14634
  }
14547
14635
  /************************************************************
@@ -14610,6 +14698,7 @@ function ac_node_click$3(gobj, event, kw, src) {
14610
14698
  priv.anchor_name = node_data.full_name || "";
14611
14699
  priv.anchor_state = "on";
14612
14700
  yui_graph_update_anchor((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.anchor_state);
14701
+ set_picking_mode(gobj, false);
14613
14702
  (0, _yuneta_gobj_js.gobj_post_event)(gobj, "EV_CENTER_ANCHOR", {}, gobj);
14614
14703
  return 0;
14615
14704
  }
@@ -37605,7 +37694,7 @@ Text.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(DisplayObject.PARS
37605
37694
  * * register property @see https://developer.mozilla.org/en-US/docs/Web/API/CSS/RegisterProperty
37606
37695
  * * CSS Layout API
37607
37696
  */
37608
- var CSS = {
37697
+ var CSS$1 = {
37609
37698
  /**
37610
37699
  * <number>
37611
37700
  * @see https://drafts.csswg.org/css-values-4/#number-value
@@ -47654,7 +47743,7 @@ var e = 1e-4;
47654
47743
  syntax: PropertySyntax.ANGLE
47655
47744
  }
47656
47745
  ].forEach(function(property) {
47657
- CSS.registerProperty(property);
47746
+ CSS$1.registerProperty(property);
47658
47747
  });
47659
47748
  var polarToCartesian = function polarToCartesian(centerX, centerY, radius, angleInRadian) {
47660
47749
  return {
@@ -9705,23 +9705,41 @@ function yui_graph_update_anchor($container, state) {
9705
9705
  * an error worth a log line per notch, they are all "there is
9706
9706
  * nothing to centre right now".
9707
9707
  ************************************************************/
9708
+ var __centring__ = /* @__PURE__ */ new WeakMap();
9708
9709
  async function yui_graph_center_on(graph, node_id) {
9709
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
+ });
9710
9720
  try {
9711
9721
  if (typeof graph.getElementData === "function" && !graph.getElementData(node_id)) return false;
9712
9722
  if (typeof graph.getElementPosition !== "function" || typeof graph.getViewportByCanvas !== "function" || typeof graph.getCanvasCenter !== "function" || typeof graph.translateTo !== "function") return false;
9713
9723
  for (let i = 0; i < 3; i++) {
9724
+ let z = graph.getZoom() || 1;
9714
9725
  let at = graph.getViewportByCanvas(graph.getElementPosition(node_id));
9715
9726
  let want = graph.getCanvasCenter();
9716
9727
  let dx = want[0] - at[0];
9717
9728
  let dy = want[1] - at[1];
9718
9729
  if (Math.abs(dx) < 1 && Math.abs(dy) < 1) break;
9719
- let pos = graph.getPosition();
9720
- await graph.translateTo([pos[0] + dx, pos[1] + dy]);
9730
+ let n = graph.getElementPosition(node_id);
9731
+ await graph.translateTo([(want[0] - n[0]) * z, (want[1] - n[1]) * z]);
9721
9732
  }
9722
9733
  return true;
9723
9734
  } catch (e) {
9724
9735
  return false;
9736
+ } finally {
9737
+ let pending = __centring__.get(graph);
9738
+ __centring__.set(graph, {
9739
+ running: false,
9740
+ again: null
9741
+ });
9742
+ if (pending && pending.again) yui_graph_center_on(graph, pending.again);
9725
9743
  }
9726
9744
  }
9727
9745
  /************************************************************
@@ -10568,7 +10586,8 @@ var PRIVATE_DATA$11 = {
10568
10586
  anchor_id: "",
10569
10587
  anchor_path: "",
10570
10588
  anchor_state: "off",
10571
- last_zoom: 1
10589
+ last_zoom: 1,
10590
+ center_posted: false
10572
10591
  };
10573
10592
  var __gclass__$11 = null;
10574
10593
  var __layout_registered__$1 = false;
@@ -10936,11 +10955,7 @@ function build_graph$3(gobj) {
10936
10955
  }
10937
10956
  },
10938
10957
  layout: layout_cfg.g6_layout,
10939
- behaviors: [
10940
- "drag-canvas",
10941
- "zoom-canvas",
10942
- "drag-element"
10943
- ]
10958
+ behaviors: BEHAVIORS_READING$1.slice()
10944
10959
  });
10945
10960
  if (priv.theme) graph.setTheme(priv.theme);
10946
10961
  graph.on(NodeEvent.CLICK, (evt) => {
@@ -10956,7 +10971,10 @@ function build_graph$3(gobj) {
10956
10971
  }
10957
10972
  if (z === priv.last_zoom) return;
10958
10973
  priv.last_zoom = z;
10959
- if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
10974
+ if (priv.anchor_state === "on" && priv.anchor_id && !priv.center_posted) {
10975
+ priv.center_posted = true;
10976
+ gobj_post_event(gobj, "EV_CENTER_ANCHOR", {}, gobj);
10977
+ }
10960
10978
  });
10961
10979
  graph.on(CanvasEvent.CLICK, (evt) => {});
10962
10980
  if (typeof ResizeObserver !== "undefined") {
@@ -11047,6 +11065,16 @@ function build_cell_html(key, value, type, dark, matched, fold) {
11047
11065
  * forever. `+3` and `−` need no language.
11048
11066
  ************************************************************/
11049
11067
  var FOLD_SPACER = `<span style="width:24px; flex:0 0 auto;"></span>`;
11068
+ var ZOOM_CANVAS$1 = {
11069
+ type: "zoom-canvas",
11070
+ animation: false
11071
+ };
11072
+ var BEHAVIORS_READING$1 = [
11073
+ "drag-canvas",
11074
+ ZOOM_CANVAS$1,
11075
+ "drag-element"
11076
+ ];
11077
+ var BEHAVIORS_PICKING$1 = ["drag-canvas", ZOOM_CANVAS$1];
11050
11078
  var CARD_HEADER_H$1 = 24;
11051
11079
  var CARD_ROW_H$1 = 22;
11052
11080
  function fold_toggle_html(path, foldable, folded, branches, colors) {
@@ -11183,7 +11211,7 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
11183
11211
  let min_width = Math.max(180, label.length * 9);
11184
11212
  let card_height = CARD_HEADER_H$1 + lines.length * CARD_ROW_H$1;
11185
11213
  let node_html = `
11186
- <div style="
11214
+ <div class="JSON_CARD" data-json-path="${escapeHtml(path)}" style="
11187
11215
  min-width: ${min_width}px;
11188
11216
  background: ${colors.bg};
11189
11217
  border: ${border_width}px ${is_dict ? "dashed" : "solid"} ${border_color};
@@ -11287,6 +11315,7 @@ function load_json(gobj) {
11287
11315
  });
11288
11316
  graph.render().then(() => {
11289
11317
  reanchor$1(gobj);
11318
+ paint_anchor_mark(gobj);
11290
11319
  if (preserve_view) {
11291
11320
  yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
11292
11321
  return;
@@ -11608,10 +11637,49 @@ function reanchor$1(gobj) {
11608
11637
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
11609
11638
  }
11610
11639
  /************************************************************
11640
+ * Reading, or picking the node to anchor.
11641
+ *
11642
+ * Two visible consequences and one invisible: the cursor says
11643
+ * a click is expected, the mount says which mode it is in for
11644
+ * anyone reading the DOM, and node dragging stands aside so a
11645
+ * click that drifts is still a click.
11646
+ ************************************************************/
11647
+ function set_picking_mode$1(gobj, picking) {
11648
+ let graph = gobj.priv.graph;
11649
+ if (graph && typeof graph.setBehaviors === "function") graph.setBehaviors((picking ? BEHAVIORS_PICKING$1 : BEHAVIORS_READING$1).slice());
11650
+ let $container = gobj_read_attr(gobj, "$container");
11651
+ if ($container) {
11652
+ let $mount = $container.querySelector(".json-graph-container");
11653
+ if ($mount) $mount.classList.toggle("JSON_GRAPH_PICKING", !!picking);
11654
+ }
11655
+ }
11656
+ /************************************************************
11657
+ * Mark the card the camera is holding.
11658
+ *
11659
+ * A class on the card's own element and not a G6 node state:
11660
+ * G6 paints no state style on an html node, so `setElementState`
11661
+ * would select it correctly and show nothing.
11662
+ *
11663
+ * Re-applied after every rebuild, because the cards are new DOM
11664
+ * each time and the mark would otherwise survive only until the
11665
+ * next fold.
11666
+ ************************************************************/
11667
+ function paint_anchor_mark(gobj) {
11668
+ let priv = gobj.priv;
11669
+ let $container = gobj_read_attr(gobj, "$container");
11670
+ if (!$container) return;
11671
+ for (let $card of $container.querySelectorAll(".JSON_CARD_ANCHORED")) $card.classList.remove("JSON_CARD_ANCHORED");
11672
+ if (priv.anchor_state !== "on" || !priv.anchor_path) return;
11673
+ let $it = $container.querySelector(".JSON_CARD[data-json-path=\"" + CSS.escape(priv.anchor_path) + "\"]");
11674
+ if ($it) $it.classList.add("JSON_CARD_ANCHORED");
11675
+ }
11676
+ /************************************************************
11611
11677
  * Centre on the anchor, one cycle after it was chosen.
11612
11678
  ************************************************************/
11613
11679
  function ac_center_anchor$2(gobj, event, kw, src) {
11614
11680
  let priv = gobj.priv;
11681
+ priv.center_posted = false;
11682
+ paint_anchor_mark(gobj);
11615
11683
  if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
11616
11684
  return 0;
11617
11685
  }
@@ -11632,6 +11700,8 @@ function ac_toggle_anchor$2(gobj, event, kw, src) {
11632
11700
  priv.anchor_path = "";
11633
11701
  }
11634
11702
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
11703
+ paint_anchor_mark(gobj);
11704
+ set_picking_mode$1(gobj, priv.anchor_state === "arming");
11635
11705
  return 0;
11636
11706
  }
11637
11707
  /************************************************************
@@ -11698,6 +11768,7 @@ function ac_node_click$4(gobj, event, kw, src) {
11698
11768
  priv.anchor_path = nodedata && nodedata.data ? nodedata.data.path : "";
11699
11769
  priv.anchor_state = "on";
11700
11770
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
11771
+ set_picking_mode$1(gobj, false);
11701
11772
  gobj_post_event(gobj, "EV_CENTER_ANCHOR", {}, gobj);
11702
11773
  return 0;
11703
11774
  }
@@ -13164,6 +13235,16 @@ var attrs_table$9 = [
13164
13235
  SDATA(data_type_t.DTP_INTEGER, "collapse_threshold", 0, 10, "Auto-collapse a node whose direct children exceed this number. 0 disables auto-collapse."),
13165
13236
  SDATA_END()
13166
13237
  ];
13238
+ var ZOOM_CANVAS = {
13239
+ type: "zoom-canvas",
13240
+ animation: false
13241
+ };
13242
+ var BEHAVIORS_READING = [
13243
+ "drag-canvas",
13244
+ ZOOM_CANVAS,
13245
+ "drag-element"
13246
+ ];
13247
+ var BEHAVIORS_PICKING = ["drag-canvas", ZOOM_CANVAS];
13167
13248
  var PRIVATE_DATA$9 = {
13168
13249
  canvas_id: "",
13169
13250
  graph: null,
@@ -13453,7 +13534,7 @@ function build_ui$8(gobj) {
13453
13534
  {
13454
13535
  id: priv.canvas_id,
13455
13536
  class: "gobj-tree-container",
13456
- style: "height:100%; min-height:0; border: 1px solid var(--bulma-border-weak); border-radius:0.2rem; touch-action:none; -webkit-user-select:none; user-select:none;"
13537
+ style: "height:100%; min-height:0; border: 1px solid var(--bulma-border-weak); border-radius:0.2rem;"
13457
13538
  },
13458
13539
  []
13459
13540
  ]]
@@ -13645,11 +13726,7 @@ function build_graph$2(gobj) {
13645
13726
  }
13646
13727
  },
13647
13728
  layout: layout_cfg.g6_layout,
13648
- behaviors: [
13649
- "drag-canvas",
13650
- "zoom-canvas",
13651
- "drag-element"
13652
- ]
13729
+ behaviors: BEHAVIORS_READING.slice()
13653
13730
  });
13654
13731
  if (priv.theme) graph.setTheme(priv.theme);
13655
13732
  graph.on("aftertransform", () => {
@@ -14521,6 +14598,16 @@ function ac_center_anchor$1(gobj, event, kw, src) {
14521
14598
  return 0;
14522
14599
  }
14523
14600
  /************************************************************
14601
+ * Reading, or picking the node to anchor.
14602
+ ************************************************************/
14603
+ function set_picking_mode(gobj, picking) {
14604
+ let graph = gobj.priv.graph;
14605
+ if (graph && typeof graph.setBehaviors === "function") graph.setBehaviors((picking ? BEHAVIORS_PICKING : BEHAVIORS_READING).slice());
14606
+ let $container = gobj_read_attr(gobj, "$container");
14607
+ let $mount = $container ? $container.querySelector(".gobj-tree-container") : null;
14608
+ if ($mount) $mount.classList.toggle("GOBJ_TREE_PICKING", !!picking);
14609
+ }
14610
+ /************************************************************
14524
14611
  * Arm the anchor, or let it go.
14525
14612
  *
14526
14613
  * Three states and one button, so a press ADVANCES: with no
@@ -14537,6 +14624,7 @@ function ac_toggle_anchor$1(gobj, event, kw, src) {
14537
14624
  priv.anchor_name = "";
14538
14625
  }
14539
14626
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
14627
+ set_picking_mode(gobj, priv.anchor_state === "arming");
14540
14628
  return 0;
14541
14629
  }
14542
14630
  /************************************************************
@@ -14605,6 +14693,7 @@ function ac_node_click$3(gobj, event, kw, src) {
14605
14693
  priv.anchor_name = node_data.full_name || "";
14606
14694
  priv.anchor_state = "on";
14607
14695
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
14696
+ set_picking_mode(gobj, false);
14608
14697
  gobj_post_event(gobj, "EV_CENTER_ANCHOR", {}, gobj);
14609
14698
  return 0;
14610
14699
  }
@@ -37600,7 +37689,7 @@ Text.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(DisplayObject.PARS
37600
37689
  * * register property @see https://developer.mozilla.org/en-US/docs/Web/API/CSS/RegisterProperty
37601
37690
  * * CSS Layout API
37602
37691
  */
37603
- var CSS = {
37692
+ var CSS$1 = {
37604
37693
  /**
37605
37694
  * <number>
37606
37695
  * @see https://drafts.csswg.org/css-values-4/#number-value
@@ -47649,7 +47738,7 @@ var e = 1e-4;
47649
47738
  syntax: PropertySyntax.ANGLE
47650
47739
  }
47651
47740
  ].forEach(function(property) {
47652
- CSS.registerProperty(property);
47741
+ CSS$1.registerProperty(property);
47653
47742
  });
47654
47743
  var polarToCartesian = function polarToCartesian(centerX, centerY, radius, angleInRadian) {
47655
47744
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.25",
3
+ "version": "7.23.27",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -90,6 +90,36 @@ SDATA(data_type_t.DTP_INTEGER, "collapse_threshold", 0, 10, "Auto-collapse a no
90
90
  SDATA_END()
91
91
  ];
92
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
+ /*
105
+ * `zoom-canvas` with NO animation, and that is two rules at once.
106
+ *
107
+ * The house rule first: nothing in this GUI slides or fades, and G6's
108
+ * 200ms zoom easing is a transition nobody chose -- it comes with the
109
+ * behavior's defaults.
110
+ *
111
+ * And it is what makes an anchor possible on the WHEEL. `aftertransform`
112
+ * fires while that easing is still running, so the re-centring measured
113
+ * a camera that was still moving: measured, its passes went -272, then
114
+ * +411 the other side, then +183 -- oscillating, because between two
115
+ * reads the easing had moved the ground. With the zoom instantaneous
116
+ * the camera is still by the time anything measures it.
117
+ */
118
+ const ZOOM_CANVAS = {type: 'zoom-canvas', animation: false};
119
+
120
+ const BEHAVIORS_READING = ['drag-canvas', ZOOM_CANVAS, 'drag-element'];
121
+ const BEHAVIORS_PICKING = ['drag-canvas', ZOOM_CANVAS];
122
+
93
123
  let PRIVATE_DATA = {
94
124
  canvas_id: "",
95
125
  graph: null,
@@ -408,13 +438,7 @@ function build_ui(gobj)
408
438
  ['div', {class: 'C_YUI_GOBJ_TREE_JS', style: 'height:100%; display:flex; flex-direction:column; position:relative;'}, [
409
439
  ['div', {class: 'is-flex-grow-0 is-flex toolbar_yui_gobj_tree'}, $toolbar],
410
440
  ['div', {class: 'is-flex-grow-1', style: 'height:100%; min-height:0; overflow:hidden;'}, [
411
- /* `touch-action` and `user-select` inline because this gclass loads no
412
- * stylesheet of its own: G6 puts `touch-action: none` on its canvas and
413
- * nothing on the html nodes over it, so without this a finger dragging a
414
- * card makes a page scroll and stops dead after ~20px; and a card is
415
- * text, so a mouse drag would start a selection. Same two rules
416
- * `lib_graph.css` carries for the treedb graph. */
417
- ['div', {id: priv.canvas_id, class: 'gobj-tree-container', style: 'height:100%; min-height:0; border: 1px solid var(--bulma-border-weak); border-radius:0.2rem; touch-action:none; -webkit-user-select:none; user-select:none;'}, [
441
+ ['div', {id: priv.canvas_id, class: 'gobj-tree-container', style: 'height:100%; min-height:0; border: 1px solid var(--bulma-border-weak); border-radius:0.2rem;'}, [
418
442
  ]]
419
443
  ]]
420
444
  ]]
@@ -654,17 +678,7 @@ function build_graph(gobj)
654
678
 
655
679
  layout: layout_cfg.g6_layout,
656
680
 
657
- behaviors: [
658
- 'drag-canvas',
659
- 'zoom-canvas',
660
- /* Move a card by dragging it. The position is not kept --
661
- * this tree is rebuilt from the live gobj tree on every
662
- * refresh -- and it is still worth having: pulling two
663
- * cards apart to read the lines between them is the whole
664
- * use of it. `enable` REPLACES a behavior's default rather
665
- * than adding to it, so it is not passed. */
666
- 'drag-element',
667
- ],
681
+ behaviors: BEHAVIORS_READING.slice(),
668
682
  });
669
683
 
670
684
  if(priv.theme) {
@@ -2014,6 +2028,24 @@ function ac_center_anchor(gobj, event, kw, src)
2014
2028
  return 0;
2015
2029
  }
2016
2030
 
2031
+ /************************************************************
2032
+ * Reading, or picking the node to anchor.
2033
+ ************************************************************/
2034
+ function set_picking_mode(gobj, picking)
2035
+ {
2036
+ let graph = gobj.priv.graph;
2037
+
2038
+ if(graph && typeof graph.setBehaviors === "function") {
2039
+ graph.setBehaviors((picking? BEHAVIORS_PICKING: BEHAVIORS_READING).slice());
2040
+ }
2041
+
2042
+ let $container = gobj_read_attr(gobj, "$container");
2043
+ let $mount = $container? $container.querySelector('.gobj-tree-container'): null;
2044
+ if($mount) {
2045
+ $mount.classList.toggle('GOBJ_TREE_PICKING', !!picking);
2046
+ }
2047
+ }
2048
+
2017
2049
  /************************************************************
2018
2050
  * Arm the anchor, or let it go.
2019
2051
  *
@@ -2035,6 +2067,7 @@ function ac_toggle_anchor(gobj, event, kw, src)
2035
2067
  }
2036
2068
 
2037
2069
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
2070
+ set_picking_mode(gobj, priv.anchor_state === "arming");
2038
2071
  return 0;
2039
2072
  }
2040
2073
 
@@ -2139,6 +2172,7 @@ function ac_node_click(gobj, event, kw, src)
2139
2172
  priv.anchor_name = node_data.full_name || "";
2140
2173
  priv.anchor_state = "on";
2141
2174
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
2175
+ set_picking_mode(gobj, false);
2142
2176
 
2143
2177
  /* POSTED, not made here: a camera move issued inside G6's click
2144
2178
  * dispatch is swallowed -- see the note in yui_graph_center_on.
@@ -293,33 +293,6 @@
293
293
  z-index: 1;
294
294
  }
295
295
 
296
- /*
297
- * The graph mount, once its cards can be DRAGGED.
298
- *
299
- * Two problems, both of them the browser's rather than G6's, and both
300
- * already paid for once in the treedb graph (`lib_graph.css`):
301
- *
302
- * G6 puts `touch-action: none` on its CANVAS and NOTHING on the html
303
- * nodes, which are ordinary DIVs layered over it. So a finger landing
304
- * on a card made a page scroll: two `pointermove`s through, then the
305
- * browser decided the gesture was a scroll and killed the stream with
306
- * `pointercancel` -- the card followed for about 20px and stopped dead
307
- * while the page slid.
308
- *
309
- * And a card is text: dragging one starts a text selection, and the
310
- * wash stays until the next click somewhere else. Nothing on this
311
- * canvas is prose.
312
- *
313
- * On the CONTAINER and not the cards: every layer G6 stacks in here is
314
- * a DIV over a canvas that already refuses the browser's gestures, and
315
- * they have to agree.
316
- */
317
- .C_YUI_JSON .json-graph-container {
318
- touch-action: none;
319
- -webkit-user-select: none;
320
- user-select: none;
321
- }
322
-
323
296
  /*
324
297
  * Disable any transition a host stylesheet might inject.
325
298
  */
@@ -205,6 +205,7 @@ let PRIVATE_DATA = {
205
205
  anchor_path: "", // its path, so a rebuild can find it again
206
206
  anchor_state: "off", // "off" | "arming" | "on"
207
207
  last_zoom: 1, // tells a wheel zoom from a drag in aftertransform
208
+ center_posted: false, // one re-centring in flight, never a queue of them
208
209
  };
209
210
 
210
211
  let __gclass__ = null;
@@ -692,24 +693,7 @@ function build_graph(gobj)
692
693
 
693
694
  layout: layout_cfg.g6_layout,
694
695
 
695
- behaviors: [
696
- 'drag-canvas',
697
- 'zoom-canvas',
698
- /*
699
- * Move a card by dragging it. The position is NOT kept:
700
- * the graph is rebuilt from the document on every refresh,
701
- * fold and layout change, and there is nothing here to
702
- * save it to -- this is a viewer over a value somebody
703
- * handed it, not a document of its own. Deliberate: being
704
- * able to pull two cards apart to read the lines between
705
- * them is worth having even when it lasts one session.
706
- *
707
- * No `enable`: a behavior's `enable` REPLACES the default,
708
- * it does not add to it, and the default here is exactly
709
- * what is wanted.
710
- */
711
- 'drag-element',
712
- ],
696
+ behaviors: BEHAVIORS_READING.slice(),
713
697
  });
714
698
 
715
699
  if(priv.theme) {
@@ -748,8 +732,22 @@ function build_graph(gobj)
748
732
  return;
749
733
  }
750
734
  priv.last_zoom = z;
751
- if(priv.anchor_state === "on" && priv.anchor_id) {
752
- yui_graph_center_on(priv.graph, priv.anchor_id);
735
+
736
+ /*
737
+ * POSTED, like the pick. This runs inside G6's own transform
738
+ * emit, and a camera move made there behaves the way one made
739
+ * inside its click dispatch does: measured, the centring runs,
740
+ * its three passes each fire back here, and the card still ends
741
+ * up hundreds of pixels off -- while the same call from a zoom
742
+ * BUTTON, which lands after the zoom promise resolved, centres
743
+ * exactly. One turn later is out of that stack.
744
+ *
745
+ * At most one in flight: a wheel gesture is a burst of notches,
746
+ * and the queue is not a work queue.
747
+ */
748
+ if(priv.anchor_state === "on" && priv.anchor_id && !priv.center_posted) {
749
+ priv.center_posted = true;
750
+ gobj_post_event(gobj, "EV_CENTER_ANCHOR", {}, gobj);
753
751
  }
754
752
  });
755
753
 
@@ -899,6 +897,39 @@ function build_cell_html(key, value, type, dark, matched, fold)
899
897
  ************************************************************/
900
898
  const FOLD_SPACER = `<span style="width:24px; flex:0 0 auto;"></span>`;
901
899
 
900
+ /*
901
+ * What the graph listens for while you are READING it, and while you
902
+ * are PICKING the node to anchor.
903
+ *
904
+ * The difference is `drag-element`, and dropping it is not a detail:
905
+ * with it on, a click that drifts two pixels -- which is every click a
906
+ * hand makes -- becomes a drag, G6 fires no `node:click`, and the pick
907
+ * silently does not happen. Measured: an instant click anchors, the
908
+ * same click with 2px of movement does not.
909
+ *
910
+ * Swapped with `setBehaviors()` and not with a behavior's `enable`,
911
+ * which REPLACES the behavior's own default test rather than adding to
912
+ * it -- the trap that made a card drag pan the canvas in 7.23.10.
913
+ */
914
+ /*
915
+ * `zoom-canvas` with NO animation, and that is two rules at once.
916
+ *
917
+ * The house rule first: nothing in this GUI slides or fades, and G6's
918
+ * 200ms zoom easing is a transition nobody chose -- it comes with the
919
+ * behavior's defaults.
920
+ *
921
+ * And it is what makes an anchor possible on the WHEEL. `aftertransform`
922
+ * fires while that easing is still running, so the re-centring measured
923
+ * a camera that was still moving: measured, its passes went -272, then
924
+ * +411 the other side, then +183 -- oscillating, because between two
925
+ * reads the easing had moved the ground. With the zoom instantaneous
926
+ * the camera is still by the time anything measures it.
927
+ */
928
+ const ZOOM_CANVAS = {type: 'zoom-canvas', animation: false};
929
+
930
+ const BEHAVIORS_READING = ['drag-canvas', ZOOM_CANVAS, 'drag-element'];
931
+ const BEHAVIORS_PICKING = ['drag-canvas', ZOOM_CANVAS];
932
+
902
933
  /*
903
934
  * The card's geometry, in ONE place.
904
935
  *
@@ -1152,8 +1183,21 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
1152
1183
  let min_width = Math.max(180, label.length * 9);
1153
1184
  let card_height = CARD_HEADER_H + lines.length * CARD_ROW_H;
1154
1185
 
1186
+ /*
1187
+ * The card carries its NAME and its PLACE in the document.
1188
+ *
1189
+ * `JSON_CARD` because a gclass that builds DOM has to leave a tree
1190
+ * somebody can read in the Inspector, and until now every card here
1191
+ * was an anonymous `<div>` among the anonymous `<div>`s G6 stacks.
1192
+ *
1193
+ * `data-json-path` because the anchor has to be able to find the
1194
+ * card it is holding: G6 paints no state style on an html node, so
1195
+ * the mark cannot be a node state and has to be a class on this
1196
+ * element -- and the path is what survives a rebuild, where the
1197
+ * node id does not.
1198
+ */
1155
1199
  let node_html = `
1156
- <div style="
1200
+ <div class="JSON_CARD" data-json-path="${escapeHtml(path)}" style="
1157
1201
  min-width: ${min_width}px;
1158
1202
  background: ${colors.bg};
1159
1203
  border: ${border_width}px ${is_dict ? 'dashed' : 'solid'} ${border_color};
@@ -1348,6 +1392,7 @@ function load_json(gobj)
1348
1392
  /* Ids are generated per build: point the anchor at its path
1349
1393
  * again before anybody asks the camera to go there. */
1350
1394
  reanchor(gobj);
1395
+ paint_anchor_mark(gobj);
1351
1396
 
1352
1397
  if(preserve_view) {
1353
1398
  yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
@@ -1820,6 +1865,65 @@ function reanchor(gobj)
1820
1865
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
1821
1866
  }
1822
1867
 
1868
+ /************************************************************
1869
+ * Reading, or picking the node to anchor.
1870
+ *
1871
+ * Two visible consequences and one invisible: the cursor says
1872
+ * a click is expected, the mount says which mode it is in for
1873
+ * anyone reading the DOM, and node dragging stands aside so a
1874
+ * click that drifts is still a click.
1875
+ ************************************************************/
1876
+ function set_picking_mode(gobj, picking)
1877
+ {
1878
+ let priv = gobj.priv;
1879
+ let graph = priv.graph;
1880
+
1881
+ if(graph && typeof graph.setBehaviors === "function") {
1882
+ graph.setBehaviors((picking? BEHAVIORS_PICKING: BEHAVIORS_READING).slice());
1883
+ }
1884
+
1885
+ let $container = gobj_read_attr(gobj, "$container");
1886
+ if($container) {
1887
+ let $mount = $container.querySelector('.json-graph-container');
1888
+ if($mount) {
1889
+ $mount.classList.toggle('JSON_GRAPH_PICKING', !!picking);
1890
+ }
1891
+ }
1892
+ }
1893
+
1894
+ /************************************************************
1895
+ * Mark the card the camera is holding.
1896
+ *
1897
+ * A class on the card's own element and not a G6 node state:
1898
+ * G6 paints no state style on an html node, so `setElementState`
1899
+ * would select it correctly and show nothing.
1900
+ *
1901
+ * Re-applied after every rebuild, because the cards are new DOM
1902
+ * each time and the mark would otherwise survive only until the
1903
+ * next fold.
1904
+ ************************************************************/
1905
+ function paint_anchor_mark(gobj)
1906
+ {
1907
+ let priv = gobj.priv;
1908
+ let $container = gobj_read_attr(gobj, "$container");
1909
+
1910
+ if(!$container) {
1911
+ return;
1912
+ }
1913
+ for(let $card of $container.querySelectorAll('.JSON_CARD_ANCHORED')) {
1914
+ $card.classList.remove('JSON_CARD_ANCHORED');
1915
+ }
1916
+ if(priv.anchor_state !== "on" || !priv.anchor_path) {
1917
+ return;
1918
+ }
1919
+ let $it = $container.querySelector(
1920
+ '.JSON_CARD[data-json-path="' + CSS.escape(priv.anchor_path) + '"]'
1921
+ );
1922
+ if($it) {
1923
+ $it.classList.add('JSON_CARD_ANCHORED');
1924
+ }
1925
+ }
1926
+
1823
1927
  /************************************************************
1824
1928
  * Centre on the anchor, one cycle after it was chosen.
1825
1929
  ************************************************************/
@@ -1827,6 +1931,8 @@ function ac_center_anchor(gobj, event, kw, src)
1827
1931
  {
1828
1932
  let priv = gobj.priv;
1829
1933
 
1934
+ priv.center_posted = false;
1935
+ paint_anchor_mark(gobj);
1830
1936
  if(priv.anchor_state === "on" && priv.anchor_id) {
1831
1937
  yui_graph_center_on(priv.graph, priv.anchor_id);
1832
1938
  }
@@ -1854,6 +1960,8 @@ function ac_toggle_anchor(gobj, event, kw, src)
1854
1960
  }
1855
1961
 
1856
1962
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
1963
+ paint_anchor_mark(gobj);
1964
+ set_picking_mode(gobj, priv.anchor_state === "arming");
1857
1965
  return 0;
1858
1966
  }
1859
1967
 
@@ -1945,6 +2053,7 @@ function ac_node_click(gobj, event, kw, src)
1945
2053
  priv.anchor_path = (nodedata && nodedata.data)? nodedata.data.path: "";
1946
2054
  priv.anchor_state = "on";
1947
2055
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
2056
+ set_picking_mode(gobj, false);
1948
2057
 
1949
2058
  /*
1950
2059
  * The camera move is POSTED, not made here.
package/src/lib_graph.css CHANGED
@@ -256,6 +256,73 @@
256
256
  * pressed button hands it back to Bulma's rule and the toggle appears
257
257
  * to switch off under the pointer.
258
258
  */
259
+ /*
260
+ * THE OTHER TWO GRAPHS' MOUNTS.
261
+ *
262
+ * They live here, unscoped, and that is the point: these rules first
263
+ * shipped inside `c_yui_json.css` under a `.C_YUI_JSON` ancestor, and
264
+ * a host that mounts `C_YUI_JSON_GRAPH` DIRECTLY -- which the demo
265
+ * does, and any host wanting only the graph -- has no such ancestor.
266
+ * Every one of them silently did nothing there: no crosshair while
267
+ * picking, no mark on the anchored card, and no `touch-action`, so a
268
+ * finger dragging a card made a page scroll. A gclass's stylesheet
269
+ * cannot assume the gclass that usually hosts it.
270
+ *
271
+ * `lib_graph.css` is reached through `lib_graph.js`, which the camera
272
+ * module imports and every one of these graphs uses, so it is loaded
273
+ * wherever a graph is.
274
+ */
275
+
276
+ /*
277
+ * Picking the node to anchor: the pointer says a click is expected,
278
+ * which is otherwise announced only by a button at the far end of a
279
+ * toolbar. `!important` because G6 writes `cursor` onto its own
280
+ * elements from its style props, and a mode has to win over a default.
281
+ */
282
+ .json-graph-container.JSON_GRAPH_PICKING,
283
+ .json-graph-container.JSON_GRAPH_PICKING *,
284
+ .gobj-tree-container.GOBJ_TREE_PICKING,
285
+ .gobj-tree-container.GOBJ_TREE_PICKING * {
286
+ cursor: crosshair !important;
287
+ }
288
+
289
+ /*
290
+ * The card the camera is holding.
291
+ *
292
+ * It has to say so ON THE CARD: the toolbar knows an anchor is set,
293
+ * and knowing WHICH node it is was left to the reader remembering
294
+ * what they clicked -- not a mark, a memory test.
295
+ *
296
+ * An outline and not a fill: the card's own colours already mean
297
+ * things (a dashed border is a dict, a solid one a list, the header
298
+ * carries the group's hue), so repainting any of them would say two
299
+ * things down one channel. `outline` draws OUTSIDE the box, so it
300
+ * cannot move a single row.
301
+ *
302
+ * Amber, the same "this one, right now" the toolbar's arming state
303
+ * uses, and not one of the palette's action colours -- it is not an
304
+ * action.
305
+ */
306
+ .JSON_CARD_ANCHORED {
307
+ outline: 3px solid #fa8c16;
308
+ outline-offset: 2px;
309
+ }
310
+
311
+ /*
312
+ * Once the cards can be DRAGGED. Two problems, both the browser's:
313
+ * G6 puts `touch-action: none` on its CANVAS and nothing on the html
314
+ * nodes, which are ordinary DIVs layered over it, so a finger landing
315
+ * on a card made a page scroll -- two `pointermove`s, then
316
+ * `pointercancel`, the card dead after ~20px while the page slid. And
317
+ * a card is text, so a mouse drag started a selection.
318
+ */
319
+ .json-graph-container,
320
+ .gobj-tree-container {
321
+ touch-action: none;
322
+ -webkit-user-select: none;
323
+ user-select: none;
324
+ }
325
+
259
326
  .button.pressed_state,
260
327
  .button.pressed_state:hover {
261
328
  background-color: var(--bulma-text, #2e333d);
@@ -233,11 +233,35 @@ export function yui_graph_update_anchor($container, state)
233
233
  * an error worth a log line per notch, they are all "there is
234
234
  * nothing to centre right now".
235
235
  ************************************************************/
236
+ const __centring__ = new WeakMap();
237
+
236
238
  export async function yui_graph_center_on(graph, node_id)
237
239
  {
238
240
  if(!graph || !node_id) {
239
241
  return false;
240
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
+
241
265
  try {
242
266
  if(typeof graph.getElementData === "function" && !graph.getElementData(node_id)) {
243
267
  return false;
@@ -250,7 +274,7 @@ export async function yui_graph_center_on(graph, node_id)
250
274
  }
251
275
 
252
276
  /*
253
- * MEASURE, MOVE, MEASURE AGAIN -- up to three times.
277
+ * MOVE BY THE CLOSED FORM, then check -- up to three times.
254
278
  *
255
279
  * Not the arithmetic anybody would write first, and the reason
256
280
  * is worth keeping. The obvious call is `graph.focusElement()`,
@@ -263,18 +287,26 @@ export async function yui_graph_center_on(graph, node_id)
263
287
  * one shot lands short or long by a factor nobody should have
264
288
  * to name.
265
289
  *
266
- * Probing for that factor gives noise: the camera settles
267
- * asynchronously, so a position read straight after a
268
- * translate is a value halfway through. AWAITING each move and
269
- * measuring again needs no factor at all -- the direction is
270
- * right, so the residual falls to nothing in two or three
271
- * passes, and a graph whose maths change under us keeps
272
- * working.
290
+ * Guessing at that factor does not work: stepping by the
291
+ * measured pixel gap OSCILLATES -- measured, its passes went
292
+ * -272, then +411 the other side, then +183, with the zoom
293
+ * stable throughout, so it is not an animation racing the
294
+ * reads. The factor is read off G6's own
295
+ * `getTranslateOptions()` instead: an absolute translate puts
296
+ * the camera at `canvasCentre - T/zoom`, and the camera is
297
+ * what lands on the viewport centre, so
298
+ *
299
+ * T = (canvasCentre - nodeWorldPosition) * zoom
273
300
  *
274
- * The 1px floor is what stops it converging forever on the
275
- * rounding.
301
+ * which lands exactly, in one pass, at any zoom.
302
+ *
303
+ * The loop stays as a guard: it costs one measurement when the
304
+ * first move was right, and it is what would catch this going
305
+ * wrong again rather than shipping a graph that drifts. The
306
+ * 1px floor stops it chasing the rounding.
276
307
  */
277
308
  for(let i = 0; i < 3; i++) {
309
+ let z = graph.getZoom() || 1;
278
310
  let at = graph.getViewportByCanvas(graph.getElementPosition(node_id));
279
311
  let want = graph.getCanvasCenter();
280
312
  let dx = want[0] - at[0];
@@ -283,12 +315,22 @@ export async function yui_graph_center_on(graph, node_id)
283
315
  if(Math.abs(dx) < 1 && Math.abs(dy) < 1) {
284
316
  break;
285
317
  }
286
- let pos = graph.getPosition();
287
- await graph.translateTo([pos[0] + dx, pos[1] + dy]);
318
+ /* Closed form, read off G6's own `getTranslateOptions()`:
319
+ * an ABSOLUTE translate puts the camera at
320
+ * `canvasCentre - T/zoom`, and the camera is what lands on
321
+ * the viewport centre. So T = (centre - node) * zoom. */
322
+ let n = graph.getElementPosition(node_id);
323
+ await graph.translateTo([(want[0] - n[0]) * z, (want[1] - n[1]) * z]);
288
324
  }
289
325
  return true;
290
326
  } catch(e) {
291
327
  return false; /* the element is gone, or the graph is between renders */
328
+ } finally {
329
+ let pending = __centring__.get(graph);
330
+ __centring__.set(graph, {running: false, again: null});
331
+ if(pending && pending.again) {
332
+ yui_graph_center_on(graph, pending.again);
333
+ }
292
334
  }
293
335
  }
294
336