@yuneta/gobj-ui 7.23.28 → 7.23.29

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.
@@ -9706,6 +9706,42 @@ function yui_graph_update_anchor($container, state) {
9706
9706
  * nothing to centre right now".
9707
9707
  ************************************************************/
9708
9708
  var __centring__ = /* @__PURE__ */ new WeakMap();
9709
+ /************************************************************
9710
+ * Put `node_id` at a GIVEN point of the viewport.
9711
+ *
9712
+ * What "the graph does not jump when I fold something" is
9713
+ * made of: read where the card is, rebuild the layout, put
9714
+ * that card back where it was. Everything that did not move
9715
+ * in the layout stays put with it.
9716
+ ************************************************************/
9717
+ async function yui_graph_place_at(graph, node_id, viewport_xy) {
9718
+ if (!graph || !node_id || !viewport_xy) return false;
9719
+ try {
9720
+ if (typeof graph.getElementPosition !== "function" || typeof graph.getCanvasCenter !== "function" || typeof graph.translateTo !== "function") return false;
9721
+ let z = graph.getZoom() || 1;
9722
+ let c = graph.getCanvasCenter();
9723
+ let n = graph.getElementPosition(node_id);
9724
+ await graph.translateTo([(c[0] - n[0]) * z + (viewport_xy[0] - c[0]), (c[1] - n[1]) * z + (viewport_xy[1] - c[1])]);
9725
+ return true;
9726
+ } catch (e) {
9727
+ return false;
9728
+ }
9729
+ }
9730
+ /************************************************************
9731
+ * Where `node_id` is right now, in viewport pixels.
9732
+ *
9733
+ * The pair of the call above: read it before a rebuild, hand
9734
+ * it back after, and the node has not moved on screen even
9735
+ * though the layout under it changed.
9736
+ ************************************************************/
9737
+ function yui_graph_viewport_of(graph, node_id) {
9738
+ if (!graph || !node_id) return null;
9739
+ try {
9740
+ return graph.getViewportByCanvas(graph.getElementPosition(node_id));
9741
+ } catch (e) {
9742
+ return null;
9743
+ }
9744
+ }
9709
9745
  async function yui_graph_center_on(graph, node_id) {
9710
9746
  if (!graph || !node_id) return false;
9711
9747
  let st = __centring__.get(graph);
@@ -10576,6 +10612,7 @@ var PRIVATE_DATA$11 = {
10576
10612
  _resize_raf: 0,
10577
10613
  search: "",
10578
10614
  collapsed: null,
10615
+ reserved: null,
10579
10616
  match_count: 0,
10580
10617
  find_timer: null,
10581
10618
  fold_listener: null,
@@ -10587,7 +10624,8 @@ var PRIVATE_DATA$11 = {
10587
10624
  anchor_path: "",
10588
10625
  anchor_state: "off",
10589
10626
  last_zoom: 1,
10590
- center_posted: false
10627
+ center_posted: false,
10628
+ pending_keep: null
10591
10629
  };
10592
10630
  var __gclass__$11 = null;
10593
10631
  var __layout_registered__$1 = false;
@@ -10697,6 +10735,7 @@ function type_color(type, dark) {
10697
10735
  function mt_create$11(gobj) {
10698
10736
  let priv = gobj.priv;
10699
10737
  priv.collapsed = /* @__PURE__ */ new Set();
10738
+ priv.reserved = /* @__PURE__ */ new Map();
10700
10739
  priv.search = "";
10701
10740
  priv.canvas_id = "json-canvas-" + clean_name(gobj_name(gobj));
10702
10741
  gobj_write_str_attr(gobj, "canvas_id", priv.canvas_id);
@@ -10796,7 +10835,11 @@ function build_ui$10(gobj) {
10796
10835
  evt.stopPropagation();
10797
10836
  evt.preventDefault();
10798
10837
  if (evt.type !== "click") return;
10799
- gobj_send_event(gobj, "EV_TOGGLE_FOLD", { path: $handle.getAttribute("data-fold-path") }, gobj);
10838
+ let $card = $handle.closest ? $handle.closest(".JSON_CARD") : null;
10839
+ gobj_send_event(gobj, "EV_TOGGLE_FOLD", {
10840
+ path: $handle.getAttribute("data-fold-path"),
10841
+ keep: $card ? $card.getAttribute("data-json-path") : ""
10842
+ }, gobj);
10800
10843
  };
10801
10844
  for (let type of FOLD_SWALLOWED_EVENTS) $mount.addEventListener(type, priv.fold_listener, true);
10802
10845
  } else log_error(`${gobj_short_name(gobj)}: no canvas mount, fold handles are dead`);
@@ -11077,6 +11120,8 @@ var BEHAVIORS_READING$1 = [
11077
11120
  var BEHAVIORS_PICKING$1 = ["drag-canvas", ZOOM_CANVAS$1];
11078
11121
  var CARD_HEADER_H$1 = 24;
11079
11122
  var CARD_ROW_H$1 = 22;
11123
+ var H_GAP = 60;
11124
+ var V_GAP = 50;
11080
11125
  function fold_toggle_html(path, foldable, folded, branches, colors) {
11081
11126
  if (!foldable) return FOLD_SPACER;
11082
11127
  let label = folded ? "+" + branches : "−";
@@ -11198,7 +11243,10 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
11198
11243
  lines.push(build_cell_html(key, value, type, dark, matched, fold));
11199
11244
  }
11200
11245
  if (parent_id && is_pure_collection(kw)) {
11201
- if (priv.collapsed.has(path)) return parent_id;
11246
+ if (priv.collapsed.has(path)) {
11247
+ push_fold_phantom(gobj, path, parent_id, parent_port, nodes, edges);
11248
+ return parent_id;
11249
+ }
11202
11250
  for (let pending of pending_complex) build_json_nodes(gobj, add_segment(path, pending.key), pending.value, nodes, edges, parent_id, parent_port);
11203
11251
  return parent_id;
11204
11252
  }
@@ -11284,7 +11332,10 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id, parent_port)
11284
11332
  if (parent_port) edge.style.sourcePort = parent_port;
11285
11333
  edges.push(edge);
11286
11334
  }
11287
- if (folded) return group_id;
11335
+ if (folded) {
11336
+ push_fold_phantom(gobj, path, group_id, "", nodes, edges);
11337
+ return group_id;
11338
+ }
11288
11339
  for (let pending of pending_complex) build_json_nodes(gobj, add_segment(path, pending.key), pending.value, nodes, edges, group_id, pending.port);
11289
11340
  return group_id;
11290
11341
  }
@@ -11315,8 +11366,16 @@ function load_json(gobj) {
11315
11366
  });
11316
11367
  graph.render().then(() => {
11317
11368
  reanchor$1(gobj);
11318
- paint_anchor_mark(gobj);
11369
+ paint_anchor_mark$1(gobj);
11319
11370
  if (preserve_view) {
11371
+ let keep = priv.pending_keep;
11372
+ priv.pending_keep = null;
11373
+ if (keep) {
11374
+ for (let n of nodes) if (n.data && n.data.path === keep.path) {
11375
+ yui_graph_place_at(graph, n.id, [keep.x, keep.y]);
11376
+ break;
11377
+ }
11378
+ }
11320
11379
  yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
11321
11380
  return;
11322
11381
  }
@@ -11368,6 +11427,108 @@ function update_find_result(gobj) {
11368
11427
  priv.$find_result.classList.remove("is-hidden");
11369
11428
  }
11370
11429
  /************************************************************
11430
+ * The invisible child a fold leaves behind.
11431
+ *
11432
+ * It is the whole of "nothing moves when I fold": the layout
11433
+ * is a plain tree that centres a parent over its children, so
11434
+ * the only way to keep the survivors still is to leave the
11435
+ * space where it was, in the order it was, as a CHILD. A width
11436
+ * reserved as a number would still have to be placed, and
11437
+ * appending it moves every sibling by half of it.
11438
+ *
11439
+ * A `rect` and not an `html` node: nothing is drawn, and a
11440
+ * DOM element per fold would be a card nobody can see but the
11441
+ * Inspector would show.
11442
+ ************************************************************/
11443
+ function push_fold_phantom(gobj, path, parent_id, parent_port, nodes, edges) {
11444
+ let width = gobj.priv.reserved.get(path) || 0;
11445
+ if (!(width > 0) || !parent_id) return;
11446
+ let id = gen_node_id$1(gobj);
11447
+ nodes.push({
11448
+ id,
11449
+ type: "rect",
11450
+ data: {
11451
+ path,
11452
+ phantom: true
11453
+ },
11454
+ style: {
11455
+ size: [width, 1],
11456
+ fill: "transparent",
11457
+ stroke: "transparent",
11458
+ opacity: 0,
11459
+ pointerEvents: "none"
11460
+ }
11461
+ });
11462
+ edges.push({
11463
+ source: parent_id,
11464
+ target: id,
11465
+ style: {
11466
+ opacity: 0,
11467
+ endArrow: false,
11468
+ sourcePort: parent_port || void 0
11469
+ }
11470
+ });
11471
+ }
11472
+ /************************************************************
11473
+ * The width the children a fold is about to hide are taking
11474
+ * RIGHT NOW, measured with the layout's own numbers.
11475
+ *
11476
+ * `card_path` is the card that will lose them; `under` limits
11477
+ * it to the ones hanging from one key of that card, which is
11478
+ * the case of a container that has no card of its own -- its
11479
+ * children hang from the parent, so its fold is a hole in the
11480
+ * PARENT.
11481
+ ************************************************************/
11482
+ function group_width_of(gobj, card_path, under) {
11483
+ let priv = gobj.priv;
11484
+ if (!priv.graph) return 0;
11485
+ try {
11486
+ let by_id = {};
11487
+ let card_id = "";
11488
+ for (let node of priv.graph.getNodeData()) {
11489
+ if (!node || !node.data) continue;
11490
+ by_id[node.id] = node;
11491
+ if (node.data.path === card_path) card_id = node.id;
11492
+ }
11493
+ if (!card_id) return 0;
11494
+ let prefix = under ? under + "`" : "";
11495
+ let sum = 0;
11496
+ let n = 0;
11497
+ for (let edge of priv.graph.getEdgeData()) {
11498
+ if (!edge || edge.source !== card_id) continue;
11499
+ let kid = by_id[edge.target];
11500
+ if (!kid) continue;
11501
+ if (prefix && String(kid.data.path).indexOf(prefix) !== 0) continue;
11502
+ sum += kid.data.subtree_width || 0;
11503
+ n++;
11504
+ }
11505
+ return n ? sum + H_GAP * (n - 1) : 0;
11506
+ } catch (e) {
11507
+ return 0;
11508
+ }
11509
+ }
11510
+ /************************************************************
11511
+ * The screen spot of the card at `path`, to be given back
11512
+ * after the rebuild. Null when it has no card of its own --
11513
+ * a pure collection -- and then nothing anchors and the
11514
+ * camera simply stays where it was.
11515
+ ************************************************************/
11516
+ function keep_of(gobj, path) {
11517
+ let priv = gobj.priv;
11518
+ if (empty_string(path) || !priv.graph) return null;
11519
+ try {
11520
+ for (let node of priv.graph.getNodeData()) if (node && node.data && node.data.path === path) {
11521
+ let vp = yui_graph_viewport_of(priv.graph, node.id);
11522
+ return vp ? {
11523
+ path,
11524
+ x: vp[0],
11525
+ y: vp[1]
11526
+ } : null;
11527
+ }
11528
+ } catch (e) {}
11529
+ return null;
11530
+ }
11531
+ /************************************************************
11371
11532
  * Clear and reload.
11372
11533
  * {preserve_view: true}
11373
11534
  * keep the current zoom and translate (no fitView) —
@@ -11409,8 +11570,6 @@ var JsonTreeLayout = class extends BaseLayout {
11409
11570
  h
11410
11571
  };
11411
11572
  }
11412
- const H_GAP = 60;
11413
- const V_GAP = 50;
11414
11573
  let subtree_widths = {};
11415
11574
  function calc_subtree_width(node_id) {
11416
11575
  let kids = children_map[node_id] || [];
@@ -11427,6 +11586,7 @@ var JsonTreeLayout = class extends BaseLayout {
11427
11586
  return subtree_widths[node_id];
11428
11587
  }
11429
11588
  for (let root of roots) calc_subtree_width(root.id);
11589
+ for (let node of nodes) if (node.data) node.data.subtree_width = subtree_widths[node.id];
11430
11590
  let positions = {};
11431
11591
  function position_node(node_id, x, y) {
11432
11592
  positions[node_id] = {
@@ -11474,9 +11634,16 @@ var JsonTreeLayout = class extends BaseLayout {
11474
11634
  /************************************************************
11475
11635
  *
11476
11636
  ************************************************************/
11637
+ function priv_reset_folds(gobj) {
11638
+ gobj.priv.collapsed.clear();
11639
+ gobj.priv.reserved.clear();
11640
+ }
11477
11641
  function ac_load_data$1(gobj, event, kw, src) {
11478
11642
  if (kw.path !== void 0) gobj_write_str_attr(gobj, "path", kw.path);
11479
- if (kw.data !== void 0) gobj_write_attr(gobj, "json_data", kw.data);
11643
+ if (kw.data !== void 0) {
11644
+ gobj_write_attr(gobj, "json_data", kw.data);
11645
+ priv_reset_folds(gobj);
11646
+ }
11480
11647
  refresh_json(gobj);
11481
11648
  return 0;
11482
11649
  }
@@ -11531,8 +11698,16 @@ function ac_toggle_fold(gobj, event, kw, src) {
11531
11698
  log_error(`${gobj_short_name(gobj)}: EV_TOGGLE_FOLD with no path`);
11532
11699
  return -1;
11533
11700
  }
11534
- if (priv.collapsed.has(path)) priv.collapsed.delete(path);
11535
- else priv.collapsed.add(path);
11701
+ let card = kw && kw.keep || path;
11702
+ if (priv.collapsed.has(path)) {
11703
+ priv.collapsed.delete(path);
11704
+ priv.reserved.delete(path);
11705
+ } else {
11706
+ let w = group_width_of(gobj, card, card === path ? "" : path);
11707
+ priv.collapsed.add(path);
11708
+ if (w > 0) priv.reserved.set(path, w);
11709
+ }
11710
+ priv.pending_keep = keep_of(gobj, kw && kw.keep || path);
11536
11711
  refresh_json(gobj, { preserve_view: true });
11537
11712
  update_find_result(gobj);
11538
11713
  return 0;
@@ -11559,6 +11734,7 @@ function ac_expand_all$2(gobj, event, kw, src) {
11559
11734
  let priv = gobj.priv;
11560
11735
  if (priv.collapsed.size === 0) return 0;
11561
11736
  priv.collapsed.clear();
11737
+ priv.reserved.clear();
11562
11738
  refresh_json(gobj);
11563
11739
  update_find_result(gobj);
11564
11740
  return 0;
@@ -11575,6 +11751,7 @@ function ac_collapse_all$2(gobj, event, kw, src) {
11575
11751
  if (empty_string(path)) path = "`";
11576
11752
  collect_foldable_paths(json_data, path, paths, true);
11577
11753
  priv.collapsed = new Set(paths);
11754
+ priv.reserved.clear();
11578
11755
  refresh_json(gobj);
11579
11756
  update_find_result(gobj);
11580
11757
  return 0;
@@ -11664,7 +11841,7 @@ function set_picking_mode$1(gobj, picking) {
11664
11841
  * each time and the mark would otherwise survive only until the
11665
11842
  * next fold.
11666
11843
  ************************************************************/
11667
- function paint_anchor_mark(gobj) {
11844
+ function paint_anchor_mark$1(gobj) {
11668
11845
  let priv = gobj.priv;
11669
11846
  let $container = gobj_read_attr(gobj, "$container");
11670
11847
  if (!$container) return;
@@ -11679,7 +11856,7 @@ function paint_anchor_mark(gobj) {
11679
11856
  function ac_center_anchor$2(gobj, event, kw, src) {
11680
11857
  let priv = gobj.priv;
11681
11858
  priv.center_posted = false;
11682
- paint_anchor_mark(gobj);
11859
+ paint_anchor_mark$1(gobj);
11683
11860
  if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
11684
11861
  return 0;
11685
11862
  }
@@ -11700,7 +11877,7 @@ function ac_toggle_anchor$2(gobj, event, kw, src) {
11700
11877
  priv.anchor_path = "";
11701
11878
  }
11702
11879
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
11703
- paint_anchor_mark(gobj);
11880
+ paint_anchor_mark$1(gobj);
11704
11881
  set_picking_mode$1(gobj, priv.anchor_state === "arming");
11705
11882
  return 0;
11706
11883
  }
@@ -13245,6 +13422,71 @@ var BEHAVIORS_READING = [
13245
13422
  "drag-element"
13246
13423
  ];
13247
13424
  var BEHAVIORS_PICKING = ["drag-canvas", ZOOM_CANVAS];
13425
+ var VIEW_STORE_PREFIX = "yui_gobj_tree_view:";
13426
+ function view_store_key(gobj) {
13427
+ return VIEW_STORE_PREFIX + gobj_name(gobj);
13428
+ }
13429
+ function restore_view_shape(gobj) {
13430
+ let priv = gobj.priv;
13431
+ let state = load_view_state(gobj);
13432
+ if (!state) return;
13433
+ if (state.layout && LAYOUTS[state.layout]) {
13434
+ gobj_write_str_attr(gobj, "layout", state.layout);
13435
+ if (priv.$layout_select) priv.$layout_select.value = state.layout;
13436
+ }
13437
+ if (state.collapsed && typeof state.collapsed === "object") priv.collapse_state = Object.assign({}, state.collapsed);
13438
+ }
13439
+ function restore_view_state(gobj) {
13440
+ let priv = gobj.priv;
13441
+ let graph = priv.graph;
13442
+ let state = load_view_state(gobj);
13443
+ if (!graph || !state) return false;
13444
+ let done = false;
13445
+ priv.restoring = true;
13446
+ try {
13447
+ if (typeof state.zoom === "number" && state.zoom > 0) {
13448
+ graph.zoomTo(state.zoom);
13449
+ priv.last_zoom = state.zoom;
13450
+ done = true;
13451
+ }
13452
+ if (Array.isArray(state.position) && state.position.length >= 2) {
13453
+ graph.translateTo([state.position[0], state.position[1]]);
13454
+ done = true;
13455
+ }
13456
+ if (state.anchor) {
13457
+ priv.anchor_name = state.anchor;
13458
+ priv.anchor_state = "on";
13459
+ reanchor(gobj);
13460
+ yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
13461
+ paint_anchor_mark(gobj);
13462
+ }
13463
+ } catch (e) {
13464
+ log_error(`${gobj_short_name(gobj)}: cannot restore the saved view: ${e}`);
13465
+ } finally {
13466
+ priv.restoring = false;
13467
+ }
13468
+ yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
13469
+ return done;
13470
+ }
13471
+ function save_view_state(gobj) {
13472
+ let priv = gobj.priv;
13473
+ let graph = priv.graph;
13474
+ if (!graph || priv.restoring) return;
13475
+ let state = {
13476
+ layout: gobj_read_str_attr(gobj, "layout"),
13477
+ collapsed: priv.collapse_state || {},
13478
+ anchor: priv.anchor_state === "on" ? priv.anchor_name : ""
13479
+ };
13480
+ try {
13481
+ state.zoom = graph.getZoom();
13482
+ state.position = graph.getPosition();
13483
+ } catch (e) {}
13484
+ kw_set_local_storage_value(view_store_key(gobj), state);
13485
+ }
13486
+ function load_view_state(gobj) {
13487
+ let state = kw_get_local_storage_value(view_store_key(gobj), null, false);
13488
+ return state && typeof state === "object" ? state : null;
13489
+ }
13248
13490
  var PRIVATE_DATA$9 = {
13249
13491
  canvas_id: "",
13250
13492
  graph: null,
@@ -13262,6 +13504,8 @@ var PRIVATE_DATA$9 = {
13262
13504
  anchor_id: "",
13263
13505
  anchor_name: "",
13264
13506
  anchor_state: "off",
13507
+ restoring: false,
13508
+ restored: false,
13265
13509
  last_zoom: 1
13266
13510
  };
13267
13511
  var __gclass__$9 = null;
@@ -13468,6 +13712,7 @@ function mt_create$9(gobj) {
13468
13712
  * Framework Method: Start
13469
13713
  ***************************************************************/
13470
13714
  function mt_start$9(gobj) {
13715
+ restore_view_shape(gobj);
13471
13716
  build_graph$2(gobj);
13472
13717
  load_tree(gobj);
13473
13718
  }
@@ -13605,8 +13850,12 @@ function build_popover(gobj, $container) {
13605
13850
  $header.appendChild($title);
13606
13851
  let $close = document.createElement("button");
13607
13852
  $close.type = "button";
13853
+ $close.className = "GOBJ_TREE_POPOVER_CLOSE";
13608
13854
  $close.textContent = "×";
13609
13855
  $close.title = t("close");
13856
+ $close.setAttribute("data-i18n-title", "close");
13857
+ $close.setAttribute("aria-label", t("close"));
13858
+ $close.setAttribute("data-i18n-aria-label", "close");
13610
13859
  $close.style.cssText = `
13611
13860
  background: transparent;
13612
13861
  border: none;
@@ -13740,6 +13989,7 @@ function build_graph$2(gobj) {
13740
13989
  if (z === priv.last_zoom) return;
13741
13990
  priv.last_zoom = z;
13742
13991
  if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
13992
+ save_view_state(gobj);
13743
13993
  });
13744
13994
  graph.on(NodeEvent.CLICK, (evt) => {
13745
13995
  gobj_send_event(gobj, "EV_NODE_CLICK", { evt }, gobj);
@@ -13889,7 +14139,7 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
13889
14139
  if (compact) {
13890
14140
  let compact_label = escapeHtml(instance_name ? gclass_name + "^" + instance_name : gclass_name);
13891
14141
  node_html = `
13892
- <div style="
14142
+ <div class="GOBJ_CARD" data-gobj-name="${escapeHtml(full_name)}" style="
13893
14143
  width: ${width}px;
13894
14144
  height: ${height}px;
13895
14145
  background: ${cs.bg};
@@ -13926,7 +14176,7 @@ function build_gobj_nodes(gobj, target_gobj, nodes, edges, parent_id, is_root, c
13926
14176
  }
13927
14177
  let badges_html = badges.map((b) => `<span style="font-size:10px; padding:0 5px; margin-right:4px; background:${cs.tagbg}; color:${cs.border}; border:1px solid ${cs.border}; border-radius:6px; white-space:nowrap;">${b}</span>`).join("");
13928
14178
  node_html = `
13929
- <div style="
14179
+ <div class="GOBJ_CARD" data-gobj-name="${escapeHtml(full_name)}" style="
13930
14180
  width: ${width}px;
13931
14181
  height: ${height}px;
13932
14182
  background: ${cs.bg};
@@ -14034,11 +14284,16 @@ function load_tree(gobj) {
14034
14284
  });
14035
14285
  graph.render().then(() => {
14036
14286
  reanchor(gobj);
14287
+ paint_anchor_mark(gobj);
14037
14288
  if (anchor) {
14038
14289
  restore_view_anchored(gobj, anchor);
14039
14290
  return;
14040
14291
  }
14041
14292
  if (preserve_view) return;
14293
+ if (!priv.restored) {
14294
+ priv.restored = true;
14295
+ if (restore_view_state(gobj)) return;
14296
+ }
14042
14297
  Promise.resolve(graph.zoomTo(1)).then(() => {
14043
14298
  if (!yui_graph_center_on(graph, priv.anchor_id)) yui_graph_center_on(graph, root_id);
14044
14299
  priv.last_zoom = 1;
@@ -14469,6 +14724,7 @@ var GobjLanesHLayout = class extends BaseLayout {
14469
14724
  *
14470
14725
  ************************************************************/
14471
14726
  function ac_refresh$3(gobj, event, kw, src) {
14727
+ save_view_state(gobj);
14472
14728
  refresh_tree(gobj);
14473
14729
  return 0;
14474
14730
  }
@@ -14496,6 +14752,8 @@ function ac_change_layout(gobj, event, kw, src) {
14496
14752
  gobj_write_str_attr(gobj, "layout", new_key);
14497
14753
  if (priv.$layout_select && priv.$layout_select.value !== new_key) priv.$layout_select.value = new_key;
14498
14754
  apply_layout(gobj);
14755
+ save_view_state(gobj);
14756
+ save_view_state(gobj);
14499
14757
  refresh_tree(gobj);
14500
14758
  return 0;
14501
14759
  }
@@ -14515,6 +14773,7 @@ function ac_toggle_collapse(gobj, event, kw, src) {
14515
14773
  priv.collapse_state[node_data.full_name] = "expanded";
14516
14774
  for (let child_info of node_data.direct_children || []) if (child_info.has_grandchildren) priv.collapse_state[child_info.full_name] = "collapsed";
14517
14775
  } else priv.collapse_state[node_data.full_name] = "collapsed";
14776
+ save_view_state(gobj);
14518
14777
  refresh_tree(gobj, { anchor });
14519
14778
  return 0;
14520
14779
  }
@@ -14594,10 +14853,31 @@ function reanchor(gobj) {
14594
14853
  ************************************************************/
14595
14854
  function ac_center_anchor$1(gobj, event, kw, src) {
14596
14855
  let priv = gobj.priv;
14856
+ paint_anchor_mark(gobj);
14857
+ save_view_state(gobj);
14597
14858
  if (priv.anchor_state === "on" && priv.anchor_id) yui_graph_center_on(priv.graph, priv.anchor_id);
14598
14859
  return 0;
14599
14860
  }
14600
14861
  /************************************************************
14862
+ * Mark the card the camera is holding.
14863
+ *
14864
+ * A class on the card's own element and not a G6 node state:
14865
+ * G6 paints no state style on an html node.
14866
+ *
14867
+ * Re-applied after every rebuild, because the cards are new
14868
+ * DOM each time and the mark would otherwise last until the
14869
+ * next expand.
14870
+ ************************************************************/
14871
+ function paint_anchor_mark(gobj) {
14872
+ let priv = gobj.priv;
14873
+ let $container = gobj_read_attr(gobj, "$container");
14874
+ if (!$container) return;
14875
+ for (let $card of $container.querySelectorAll(".GOBJ_CARD_ANCHORED")) $card.classList.remove("GOBJ_CARD_ANCHORED");
14876
+ if (priv.anchor_state !== "on" || !priv.anchor_name) return;
14877
+ let $it = $container.querySelector(".GOBJ_CARD[data-gobj-name=\"" + CSS.escape(priv.anchor_name) + "\"]");
14878
+ if ($it) $it.classList.add("GOBJ_CARD_ANCHORED");
14879
+ }
14880
+ /************************************************************
14601
14881
  * Reading, or picking the node to anchor.
14602
14882
  ************************************************************/
14603
14883
  function set_picking_mode(gobj, picking) {
@@ -14625,6 +14905,8 @@ function ac_toggle_anchor$1(gobj, event, kw, src) {
14625
14905
  }
14626
14906
  yui_graph_update_anchor(gobj_read_attr(gobj, "$container"), priv.anchor_state);
14627
14907
  set_picking_mode(gobj, priv.anchor_state === "arming");
14908
+ paint_anchor_mark(gobj);
14909
+ save_view_state(gobj);
14628
14910
  return 0;
14629
14911
  }
14630
14912
  /************************************************************
@@ -14708,6 +14990,7 @@ function ac_node_click$3(gobj, event, kw, src) {
14708
14990
  function ac_show$6(gobj, event, kw, src) {
14709
14991
  let priv = gobj.priv;
14710
14992
  let graph = priv.graph;
14993
+ save_view_state(gobj);
14711
14994
  refresh_tree(gobj);
14712
14995
  if (graph) {
14713
14996
  let $canvas = document.getElementById(priv.canvas_id);
@@ -49026,7 +49309,7 @@ function configure_toolbar(gobj) {
49026
49309
  {
49027
49310
  id: "g6-icon-anchor",
49028
49311
  value: "anchor",
49029
- className: "EV_TOGGLE_ANCHOR" + (priv.anchor_state !== "off" ? " pressed_state" : "") + (priv.anchor_state === "arming" ? " color_pending_state" : ""),
49312
+ className: "EV_TOGGLE_ANCHOR" + (priv.anchor_state === "on" ? " pressed_state" : "") + (priv.anchor_state === "arming" ? " color_pending_state" : ""),
49030
49313
  title: anchor_title(priv.anchor_state)
49031
49314
  }
49032
49315
  ];
@@ -51935,15 +52218,15 @@ function refresh_minimap(gobj) {
51935
52218
  * same three-way choice — it lived inline in the theme refresh and is
51936
52219
  * shared now. Returns null for a node that carries no desc.
51937
52220
  ************************************************************/
51938
- function node_innerHTML_of(nd, theme, highlight, selected) {
52221
+ function node_innerHTML_of(nd, theme, highlight, selected, anchored) {
51939
52222
  if (!nd || !nd.data || !nd.data.desc) return null;
51940
52223
  let desc = nd.data.desc;
51941
52224
  let record = nd.data.record || {};
51942
52225
  let label = node_label(desc, record);
51943
52226
  switch (desc.node_treedb_type) {
51944
- case "child": return build_chip_innerHTML(desc.color, theme, record.icon, label, record.id, highlight, selected);
51945
- case "extended": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, true, record.id, highlight, selected);
51946
- case "hierarchical": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, false, record.id, highlight, selected);
52227
+ case "child": return build_chip_innerHTML(desc.color, theme, record.icon, label, record.id, highlight, selected, anchored);
52228
+ case "extended": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, true, record.id, highlight, selected, anchored);
52229
+ case "hierarchical": return build_node_innerHTML(desc.color, theme, record.icon, label, desc.topic_name, false, record.id, highlight, selected, anchored);
51947
52230
  }
51948
52231
  return null;
51949
52232
  }
@@ -51964,7 +52247,7 @@ function repaint_cards(gobj, ids) {
51964
52247
  let selected = new Set(priv._selected_paint_ids || []);
51965
52248
  let updates = [];
51966
52249
  for (let id of ids) {
51967
- let html = node_innerHTML_of(graph.getNodeData(id), priv.theme, focus.has(id), selected.has(id));
52250
+ let html = node_innerHTML_of(graph.getNodeData(id), priv.theme, focus.has(id), selected.has(id), priv.anchor_state === "on" && id === priv.anchor_id);
51968
52251
  if (html !== null) updates.push({
51969
52252
  id,
51970
52253
  style: { innerHTML: html }
@@ -52038,6 +52321,23 @@ function refresh_default_edges_theme(gobj, theme) {
52038
52321
  * it, which is also the order that reads correctly when only one
52039
52322
  * of them is on.
52040
52323
  ************************************************************/
52324
+ /************************************************************
52325
+ * The mark of the card the CAMERA is holding.
52326
+ *
52327
+ * An outline and not another ring, and DASHED: the two rings
52328
+ * already mean things here -- amber is a find match or a
52329
+ * focused topic, blue is the selection -- and a third solid
52330
+ * ring would be a third thing to learn in the same channel.
52331
+ * A dash is a different channel, so it reads beside either of
52332
+ * them, and it says "provisional" the way a camera state is.
52333
+ *
52334
+ * The same dash the other two viewers draw: this is one
52335
+ * feature with one control, and it should not be three
52336
+ * drawings.
52337
+ ************************************************************/
52338
+ function anchor_outline(anchored) {
52339
+ return anchored ? "outline: 3px dashed #fa8c16; outline-offset: 2px;" : "";
52340
+ }
52041
52341
  function ring_shadow(highlight, selected, base_shadow) {
52042
52342
  let rings = [];
52043
52343
  if (highlight) rings.push(`0 0 0 4px ${HIGHLIGHT_HALO}`);
@@ -52052,7 +52352,7 @@ function ring_shadow(highlight, selected, base_shadow) {
52052
52352
  * but lighter (1px border, no shadow, single line). The name is
52053
52353
  * always legible (ellipsis + native title tooltip on overflow).
52054
52354
  ************************************************************/
52055
- function build_chip_innerHTML(color, theme, icon, label, key, highlight, selected) {
52355
+ function build_chip_innerHTML(color, theme, icon, label, key, highlight, selected, anchored) {
52056
52356
  let title = key || label;
52057
52357
  let dark = theme === "dark";
52058
52358
  let bg = dark ? `color-mix(in srgb, ${color} 30%, #2c3542)` : `color-mix(in srgb, ${color} 10%, ${dark ? "#1b2230" : "#ffffff"})`;
@@ -52065,13 +52365,14 @@ function build_chip_innerHTML(color, theme, icon, label, key, highlight, selecte
52065
52365
  margin-right: 6px; flex: 0 0 auto;
52066
52366
  "/>`;
52067
52367
  return `
52068
- <div title="${escapeHtml(title)}" style="
52368
+ <div class="TREEDB_CARD" title="${escapeHtml(title)}" style="
52069
52369
  box-sizing: border-box;
52070
52370
  width: 100%;
52071
52371
  height: 100%;
52072
52372
  background: ${bg};
52073
52373
  border: ${highlight ? "3px" : "1px"} solid ${border};
52074
52374
  ${ring_shadow(highlight, selected, "")}
52375
+ ${anchor_outline(anchored)}
52075
52376
  border-radius: 8px;
52076
52377
  color: ${text_color};
52077
52378
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
@@ -52097,7 +52398,7 @@ function build_chip_innerHTML(color, theme, icon, label, key, highlight, selecte
52097
52398
  * colour is kept (per-topic differentiation) but softened via
52098
52399
  * color-mix instead of a harsh saturated fill. Theme-aware.
52099
52400
  ************************************************************/
52100
- function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key, highlight, selected) {
52401
+ function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key, highlight, selected, anchored) {
52101
52402
  let title = key || label;
52102
52403
  let dark = theme === "dark";
52103
52404
  let surface = dark ? "#1b2230" : "#ffffff";
@@ -52132,7 +52433,7 @@ function build_node_innerHTML(color, theme, icon, label, topic_name, structural,
52132
52433
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
52133
52434
  ">${escapeHtml(topic_name)}</div>`;
52134
52435
  return `
52135
- <div title="${escapeHtml(title)}" style="
52436
+ <div class="TREEDB_CARD" title="${escapeHtml(title)}" style="
52136
52437
  box-sizing: border-box;
52137
52438
  width: 100%;
52138
52439
  height: 100%;
@@ -52140,6 +52441,7 @@ function build_node_innerHTML(color, theme, icon, label, topic_name, structural,
52140
52441
  border: ${highlight ? "3px" : "1.5px"} ${border_style} ${border};
52141
52442
  border-radius: 10px;
52142
52443
  ${ring_shadow(highlight, selected, shadow)}
52444
+ ${anchor_outline(anchored)}
52143
52445
  color: ${title_color};
52144
52446
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
52145
52447
  display: flex;
@@ -53123,12 +53425,14 @@ function after_camera(gobj, moved) {
53123
53425
  ************************************************************/
53124
53426
  function ac_toggle_anchor(gobj, event, kw, src) {
53125
53427
  let priv = gobj.priv;
53428
+ let was = priv.anchor_id;
53126
53429
  if (priv.anchor_state === "off") priv.anchor_state = "arming";
53127
53430
  else {
53128
53431
  priv.anchor_state = "off";
53129
53432
  priv.anchor_id = "";
53130
53433
  }
53131
53434
  update_toolbar(gobj);
53435
+ if (was) repaint_cards(gobj, /* @__PURE__ */ new Set([was]));
53132
53436
  return 0;
53133
53437
  }
53134
53438
  /************************************************************
@@ -53306,6 +53610,7 @@ function ac_node_click$2(gobj, event, kw, src) {
53306
53610
  priv.anchor_id = node_id;
53307
53611
  priv.anchor_state = "on";
53308
53612
  update_toolbar(gobj);
53613
+ repaint_cards(gobj, /* @__PURE__ */ new Set([node_id]));
53309
53614
  gobj_post_event(gobj, "EV_CENTER_ANCHOR", {}, gobj);
53310
53615
  return 0;
53311
53616
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.28",
3
+ "version": "7.23.29",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",