@yuneta/gobj-ui 7.23.9 → 7.23.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -964,12 +964,28 @@ gone wrong:
964
964
  work — so a marquee shows up in the `machine` trace like every other action.
965
965
  Shift+click is not G6's `click-select` at all: this gclass already owns
966
966
  `EV_NODE_CLICK`, and adding a second selection owner outside the FSM is how
967
- the two end up disagreeing.
968
-
969
- Panning gives way while **Shift** is held (`drag-canvas` takes an `enable`
970
- predicate), or the canvas would pan under the rubber band — G6 binds
971
- `drag-canvas` straight to the drag events, and its own docs warn that the two
972
- gestures cannot both be a plain drag.
967
+ the two end up disagreeing. The band comes from `g6_brush_select_owned.js`,
968
+ a `brush-select` under our own id with ONE half removed: the built-in also
969
+ rewrites the `selected` state of every node and every edge on each canvas
970
+ click, behind the gclass and outside the history pause with which it sets
971
+ and clears the selection — a recorded command whose before and after are
972
+ identical, which is a lit Save on a graph nobody has touched (`7.23.10`).
973
+
974
+ Panning gives way while **Shift** is held AND while the pointer is on a card
975
+ (`drag-canvas` takes an `enable` predicate) — the first because the canvas
976
+ would otherwise pan under the rubber band, the second because it would
977
+ otherwise pan under the node being dragged. That second half is G6's own
978
+ default, and an `enable` REPLACES the default rather than adding to it: while
979
+ it was missing, a drag moved the card at twice the pointer and slid the whole
980
+ graph underneath it (`7.23.10`).
981
+
982
+ **Undo, Redo and Save are the history plugin, and it follows the MODE.** It is
983
+ installed on entering edition and removed on leaving it — including when
984
+ edition is reached through the mode selector on a graph whose data is already
985
+ loaded, which is the ordinary way in. Before `7.23.10` it was installed at the
986
+ arrival of the last topic of the load and only if the graph was in edition
987
+ right then, so that ordinary way left the graph with dead Undo/Redo buttons
988
+ and a `history_pause()` nothing answered, while Save still lit on its own.
973
989
 
974
990
  **A delete says what it takes, whether it is one or twenty.** The Delete key
975
991
  asks the same question the per-node delete icon asks, built by the same
@@ -47507,6 +47507,58 @@ function inject_svg_icons() {
47507
47507
  document.body.appendChild(div);
47508
47508
  }
47509
47509
  //#endregion
47510
+ //#region src/g6_brush_select_owned.js
47511
+ /***********************************************************************
47512
+ * g6_brush_select_owned.js
47513
+ *
47514
+ * The rubber band, for a graph whose FSM owns the selection.
47515
+ *
47516
+ * Why this exists
47517
+ * ----------------
47518
+ * G6 v5 `brush-select` does two things: it draws the band and
47519
+ * it keeps the `selected` element state. The second one is
47520
+ * not ours to give away -- `C_G6_NODES_TREE` owns the
47521
+ * selection, sets it in `set_selection()` and clears it in
47522
+ * `deselect_node()`, both with the history recorder paused,
47523
+ * because a selection is not an edit.
47524
+ *
47525
+ * The built-in disagrees on one path: on EVERY canvas click
47526
+ * it calls `clearElementsStates()`, which writes the state of
47527
+ * every node and every edge of the graph -- whether or not
47528
+ * any of them carried it -- outside our pause. G6's history
47529
+ * then records the redraw as a command whose `original` and
47530
+ * `current` are identical: an undo entry that undoes nothing,
47531
+ * and a Save button lit on a graph nobody has touched. One
47532
+ * click on the background was enough.
47533
+ *
47534
+ * So this subclass drops that half and keeps the band. The
47535
+ * selection still clears on a canvas click -- through
47536
+ * `EV_CANVAS_CLICK`, where the gclass has always cleared it.
47537
+ *
47538
+ * Registered under its OWN id: the built-in `brush-select` is
47539
+ * right for a graph that does not own its selection, and no
47540
+ * other consumer of this library asked for this.
47541
+ *
47542
+ * Copyright (c) 2026, ArtGins.
47543
+ * All Rights Reserved.
47544
+ ***********************************************************************/
47545
+ /************************************************************
47546
+ * Same band, no state bookkeeping behind the gclass's back
47547
+ ************************************************************/
47548
+ var BrushSelectOwned = class extends _antv_g6.BrushSelect {
47549
+ clearElementsStates() {}
47550
+ };
47551
+ /************************************************************
47552
+ * Register once, under our own id
47553
+ ************************************************************/
47554
+ var BRUSH_SELECT_OWNED = "yui-brush-select";
47555
+ var __brush_select_registered__ = false;
47556
+ function ensure_brush_select_owned() {
47557
+ if (__brush_select_registered__) return;
47558
+ (0, _antv_g6.register)(_antv_g6.ExtensionCategory.BEHAVIOR, BRUSH_SELECT_OWNED, BrushSelectOwned);
47559
+ __brush_select_registered__ = true;
47560
+ }
47561
+ //#endregion
47510
47562
  //#region src/c_g6_nodes_tree.js
47511
47563
  /***********************************************************************
47512
47564
  * c_g6_nodes_tree.js
@@ -47565,6 +47617,7 @@ var YuiToolbar = class extends _antv_g6.Toolbar {
47565
47617
  };
47566
47618
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.PLUGIN, "yui-toolbar", YuiToolbar);
47567
47619
  ensure_drag_canvas_patch();
47620
+ ensure_brush_select_owned();
47568
47621
  ensure_pinch_zoom_patch();
47569
47622
  /***************************************************************
47570
47623
  * Constants
@@ -48315,12 +48368,16 @@ function configure_behaviour(gobj) {
48315
48368
  {
48316
48369
  type: "drag-canvas",
48317
48370
  key: "drag-canvas",
48318
- enable: (event) => !event.shiftKey
48371
+ enable: (event) => {
48372
+ if (event.shiftKey) return false;
48373
+ if ("targetType" in event) return event.targetType === "canvas";
48374
+ return true;
48375
+ }
48319
48376
  },
48320
48377
  "zoom-canvas",
48321
48378
  "drag-element",
48322
48379
  {
48323
- type: "brush-select",
48380
+ type: BRUSH_SELECT_OWNED,
48324
48381
  key: "brush-select",
48325
48382
  trigger: ["shift"],
48326
48383
  enableElements: ["node"],
@@ -48904,6 +48961,30 @@ function history_resume(gobj) {
48904
48961
  gobj.priv._history_paused = false;
48905
48962
  }
48906
48963
  /************************************************************
48964
+ * The history plugin exists while the graph is in EDITION,
48965
+ * and only there.
48966
+ *
48967
+ * It used to be installed at one MOMENT -- the arrival of the
48968
+ * last topic of the load -- and only if the graph happened to
48969
+ * be in edition right then. Entering edition afterwards, which
48970
+ * is the ordinary way in (the graph opens in reading and the
48971
+ * mode selector is next to it), left no history at all: Undo
48972
+ * and Redo were dead buttons that never even lit, `history_
48973
+ * pause()` had no `beforeAddCommand` to answer, and the Save
48974
+ * button was told the graph was dirty by `mark_graph_dirty()`
48975
+ * alone -- which is why a move offered Save without Undo.
48976
+ *
48977
+ * So it follows the MODE, and every place that changes the
48978
+ * mode calls this.
48979
+ ************************************************************/
48980
+ function sync_history_plugin(gobj) {
48981
+ let priv = gobj.priv;
48982
+ if (!priv.graph || !priv.graph_rendered) return;
48983
+ if (priv.edit_mode) graph_add_plugin(gobj, "history");
48984
+ else graph_remove_plugin(gobj, "history");
48985
+ update_history_buttons(gobj);
48986
+ }
48987
+ /************************************************************
48907
48988
  *
48908
48989
  ************************************************************/
48909
48990
  function update_history_buttons(gobj) {
@@ -49194,6 +49275,7 @@ function graph_add_plugin(gobj, plugin_key, options) {
49194
49275
  let priv = gobj.priv;
49195
49276
  let graph = priv.graph;
49196
49277
  let plugin = graph_get_plugin(gobj, plugin_key);
49278
+ let existed = !!plugin;
49197
49279
  if (!plugin) {
49198
49280
  let plugin_def = {
49199
49281
  key: plugin_key,
@@ -49204,7 +49286,7 @@ function graph_add_plugin(gobj, plugin_key, options) {
49204
49286
  }
49205
49287
  plugin = graph_get_plugin(gobj, plugin_key);
49206
49288
  switch (plugin_key) {
49207
- case "history": if (plugin) {
49289
+ case "history": if (plugin && !existed) {
49208
49290
  plugin.on(_antv_g6.HistoryEvent.CHANGE, () => {
49209
49291
  update_history_buttons(gobj);
49210
49292
  });
@@ -51935,8 +52017,7 @@ function ac_load_data(gobj, event, kw, src) {
51935
52017
  if (auto_laid) graph_fit_readable(gobj).catch((e) => {
51936
52018
  (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: cannot fit the graph: ${e}`);
51937
52019
  });
51938
- if (priv.edit_mode) graph_add_plugin(gobj, "history");
51939
- else graph_remove_plugin(gobj, "history");
52020
+ sync_history_plugin(gobj);
51940
52021
  if (priv._pending_focus_topic !== null) {
51941
52022
  let ft = priv._pending_focus_topic;
51942
52023
  priv._pending_focus_topic = null;
@@ -52238,6 +52319,7 @@ function ac_set_operation_mode$1(gobj, event, kw, src) {
52238
52319
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "operation_mode", kw.operation_mode);
52239
52320
  configure_behaviour(gobj);
52240
52321
  update_toolbar(gobj);
52322
+ sync_history_plugin(gobj);
52241
52323
  return 0;
52242
52324
  }
52243
52325
  /************************************************************
@@ -5,7 +5,7 @@ import { createJSONEditor } from "vanilla-jsoneditor";
5
5
  import { TabulatorFull } from "tabulator-tables";
6
6
  import * as maplibregl from "maplibre-gl";
7
7
  import uPlot from "uplot";
8
- import { BaseLayout, CanvasEvent, Circle, CommonEvent, DragCanvas, EdgeEvent, ExtensionCategory, Graph, HistoryEvent, NodeEvent, Toolbar, ZoomCanvas, register } from "@antv/g6";
8
+ import { BaseLayout, BrushSelect, CanvasEvent, Circle, CommonEvent, DragCanvas, EdgeEvent, ExtensionCategory, Graph, HistoryEvent, NodeEvent, Toolbar, ZoomCanvas, register } from "@antv/g6";
9
9
  //#region \0rolldown/runtime.js
10
10
  var __create = Object.create;
11
11
  var __defProp = Object.defineProperty;
@@ -47494,6 +47494,58 @@ function inject_svg_icons() {
47494
47494
  document.body.appendChild(div);
47495
47495
  }
47496
47496
  //#endregion
47497
+ //#region src/g6_brush_select_owned.js
47498
+ /***********************************************************************
47499
+ * g6_brush_select_owned.js
47500
+ *
47501
+ * The rubber band, for a graph whose FSM owns the selection.
47502
+ *
47503
+ * Why this exists
47504
+ * ----------------
47505
+ * G6 v5 `brush-select` does two things: it draws the band and
47506
+ * it keeps the `selected` element state. The second one is
47507
+ * not ours to give away -- `C_G6_NODES_TREE` owns the
47508
+ * selection, sets it in `set_selection()` and clears it in
47509
+ * `deselect_node()`, both with the history recorder paused,
47510
+ * because a selection is not an edit.
47511
+ *
47512
+ * The built-in disagrees on one path: on EVERY canvas click
47513
+ * it calls `clearElementsStates()`, which writes the state of
47514
+ * every node and every edge of the graph -- whether or not
47515
+ * any of them carried it -- outside our pause. G6's history
47516
+ * then records the redraw as a command whose `original` and
47517
+ * `current` are identical: an undo entry that undoes nothing,
47518
+ * and a Save button lit on a graph nobody has touched. One
47519
+ * click on the background was enough.
47520
+ *
47521
+ * So this subclass drops that half and keeps the band. The
47522
+ * selection still clears on a canvas click -- through
47523
+ * `EV_CANVAS_CLICK`, where the gclass has always cleared it.
47524
+ *
47525
+ * Registered under its OWN id: the built-in `brush-select` is
47526
+ * right for a graph that does not own its selection, and no
47527
+ * other consumer of this library asked for this.
47528
+ *
47529
+ * Copyright (c) 2026, ArtGins.
47530
+ * All Rights Reserved.
47531
+ ***********************************************************************/
47532
+ /************************************************************
47533
+ * Same band, no state bookkeeping behind the gclass's back
47534
+ ************************************************************/
47535
+ var BrushSelectOwned = class extends BrushSelect {
47536
+ clearElementsStates() {}
47537
+ };
47538
+ /************************************************************
47539
+ * Register once, under our own id
47540
+ ************************************************************/
47541
+ var BRUSH_SELECT_OWNED = "yui-brush-select";
47542
+ var __brush_select_registered__ = false;
47543
+ function ensure_brush_select_owned() {
47544
+ if (__brush_select_registered__) return;
47545
+ register(ExtensionCategory.BEHAVIOR, BRUSH_SELECT_OWNED, BrushSelectOwned);
47546
+ __brush_select_registered__ = true;
47547
+ }
47548
+ //#endregion
47497
47549
  //#region src/c_g6_nodes_tree.js
47498
47550
  /***********************************************************************
47499
47551
  * c_g6_nodes_tree.js
@@ -47552,6 +47604,7 @@ var YuiToolbar = class extends Toolbar {
47552
47604
  };
47553
47605
  register(ExtensionCategory.PLUGIN, "yui-toolbar", YuiToolbar);
47554
47606
  ensure_drag_canvas_patch();
47607
+ ensure_brush_select_owned();
47555
47608
  ensure_pinch_zoom_patch();
47556
47609
  /***************************************************************
47557
47610
  * Constants
@@ -48302,12 +48355,16 @@ function configure_behaviour(gobj) {
48302
48355
  {
48303
48356
  type: "drag-canvas",
48304
48357
  key: "drag-canvas",
48305
- enable: (event) => !event.shiftKey
48358
+ enable: (event) => {
48359
+ if (event.shiftKey) return false;
48360
+ if ("targetType" in event) return event.targetType === "canvas";
48361
+ return true;
48362
+ }
48306
48363
  },
48307
48364
  "zoom-canvas",
48308
48365
  "drag-element",
48309
48366
  {
48310
- type: "brush-select",
48367
+ type: BRUSH_SELECT_OWNED,
48311
48368
  key: "brush-select",
48312
48369
  trigger: ["shift"],
48313
48370
  enableElements: ["node"],
@@ -48891,6 +48948,30 @@ function history_resume(gobj) {
48891
48948
  gobj.priv._history_paused = false;
48892
48949
  }
48893
48950
  /************************************************************
48951
+ * The history plugin exists while the graph is in EDITION,
48952
+ * and only there.
48953
+ *
48954
+ * It used to be installed at one MOMENT -- the arrival of the
48955
+ * last topic of the load -- and only if the graph happened to
48956
+ * be in edition right then. Entering edition afterwards, which
48957
+ * is the ordinary way in (the graph opens in reading and the
48958
+ * mode selector is next to it), left no history at all: Undo
48959
+ * and Redo were dead buttons that never even lit, `history_
48960
+ * pause()` had no `beforeAddCommand` to answer, and the Save
48961
+ * button was told the graph was dirty by `mark_graph_dirty()`
48962
+ * alone -- which is why a move offered Save without Undo.
48963
+ *
48964
+ * So it follows the MODE, and every place that changes the
48965
+ * mode calls this.
48966
+ ************************************************************/
48967
+ function sync_history_plugin(gobj) {
48968
+ let priv = gobj.priv;
48969
+ if (!priv.graph || !priv.graph_rendered) return;
48970
+ if (priv.edit_mode) graph_add_plugin(gobj, "history");
48971
+ else graph_remove_plugin(gobj, "history");
48972
+ update_history_buttons(gobj);
48973
+ }
48974
+ /************************************************************
48894
48975
  *
48895
48976
  ************************************************************/
48896
48977
  function update_history_buttons(gobj) {
@@ -49181,6 +49262,7 @@ function graph_add_plugin(gobj, plugin_key, options) {
49181
49262
  let priv = gobj.priv;
49182
49263
  let graph = priv.graph;
49183
49264
  let plugin = graph_get_plugin(gobj, plugin_key);
49265
+ let existed = !!plugin;
49184
49266
  if (!plugin) {
49185
49267
  let plugin_def = {
49186
49268
  key: plugin_key,
@@ -49191,7 +49273,7 @@ function graph_add_plugin(gobj, plugin_key, options) {
49191
49273
  }
49192
49274
  plugin = graph_get_plugin(gobj, plugin_key);
49193
49275
  switch (plugin_key) {
49194
- case "history": if (plugin) {
49276
+ case "history": if (plugin && !existed) {
49195
49277
  plugin.on(HistoryEvent.CHANGE, () => {
49196
49278
  update_history_buttons(gobj);
49197
49279
  });
@@ -51922,8 +52004,7 @@ function ac_load_data(gobj, event, kw, src) {
51922
52004
  if (auto_laid) graph_fit_readable(gobj).catch((e) => {
51923
52005
  log_error(`${gobj_short_name(gobj)}: cannot fit the graph: ${e}`);
51924
52006
  });
51925
- if (priv.edit_mode) graph_add_plugin(gobj, "history");
51926
- else graph_remove_plugin(gobj, "history");
52007
+ sync_history_plugin(gobj);
51927
52008
  if (priv._pending_focus_topic !== null) {
51928
52009
  let ft = priv._pending_focus_topic;
51929
52010
  priv._pending_focus_topic = null;
@@ -52225,6 +52306,7 @@ function ac_set_operation_mode$1(gobj, event, kw, src) {
52225
52306
  gobj_write_attr(gobj, "operation_mode", kw.operation_mode);
52226
52307
  configure_behaviour(gobj);
52227
52308
  update_toolbar(gobj);
52309
+ sync_history_plugin(gobj);
52228
52310
  return 0;
52229
52311
  }
52230
52312
  /************************************************************
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.9",
3
+ "version": "7.23.10",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -103,6 +103,10 @@ import i18next, {t} from "i18next";
103
103
 
104
104
  import {inject_svg_icons} from "./lib_icons.js";
105
105
  import {ensure_drag_canvas_patch} from "./g6_drag_canvas_touch.js";
106
+ import {
107
+ ensure_brush_select_owned,
108
+ BRUSH_SELECT_OWNED,
109
+ } from "./g6_brush_select_owned.js";
106
110
  import {
107
111
  ensure_pinch_zoom_patch,
108
112
  install_long_press_contextmenu,
@@ -176,6 +180,7 @@ class YuiToolbar extends Toolbar
176
180
  }
177
181
  register(ExtensionCategory.PLUGIN, 'yui-toolbar', YuiToolbar);
178
182
  ensure_drag_canvas_patch();
183
+ ensure_brush_select_owned();
179
184
  ensure_pinch_zoom_patch();
180
185
 
181
186
  /***************************************************************
@@ -1410,11 +1415,28 @@ function configure_behaviour(gobj)
1410
1415
  /* Panning gives way while Shift is held: that is the
1411
1416
  * marquee's gesture, and G6 binds drag-canvas straight
1412
1417
  * to the drag events, so without this the canvas pans
1413
- * under the rubber band. */
1418
+ * under the rubber band.
1419
+ *
1420
+ * And it gives way to a NODE drag, which is what G6's
1421
+ * own default `enable` says and what this one has to
1422
+ * repeat: `enable` REPLACES it, it does not add to it.
1423
+ * Dropping the `targetType` half made every drag of a
1424
+ * card pan the canvas as well as move the card -- the
1425
+ * card ran at twice the pointer and the whole graph
1426
+ * slid underneath it, on a move whose RESULT was
1427
+ * right, because a pan writes nothing. */
1414
1428
  {
1415
1429
  type: "drag-canvas",
1416
1430
  key: "drag-canvas",
1417
- enable: (event) => !event.shiftKey,
1431
+ enable: (event) => {
1432
+ if(event.shiftKey) {
1433
+ return false;
1434
+ }
1435
+ if('targetType' in event) {
1436
+ return event.targetType === 'canvas';
1437
+ }
1438
+ return true; /* a key, not a pointer */
1439
+ },
1418
1440
  },
1419
1441
  "zoom-canvas",
1420
1442
  /* Moves EVERY node in the `selected` state, not just
@@ -1429,7 +1451,12 @@ function configure_behaviour(gobj)
1429
1451
  * ids travel with the event and the action does not
1430
1452
  * have to race it. */
1431
1453
  {
1432
- type: "brush-select",
1454
+ /* Ours: the built-in also rewrites the state of
1455
+ * EVERY element on a canvas click, behind the
1456
+ * gclass's back and outside its history pause --
1457
+ * a command that changed nothing, and a Save
1458
+ * button lit on a graph nobody had touched. */
1459
+ type: BRUSH_SELECT_OWNED,
1433
1460
  key: "brush-select",
1434
1461
  trigger: ["shift"],
1435
1462
  enableElements: ["node"],
@@ -2370,6 +2397,42 @@ function history_resume(gobj)
2370
2397
  gobj.priv._history_paused = false;
2371
2398
  }
2372
2399
 
2400
+ /************************************************************
2401
+ * The history plugin exists while the graph is in EDITION,
2402
+ * and only there.
2403
+ *
2404
+ * It used to be installed at one MOMENT -- the arrival of the
2405
+ * last topic of the load -- and only if the graph happened to
2406
+ * be in edition right then. Entering edition afterwards, which
2407
+ * is the ordinary way in (the graph opens in reading and the
2408
+ * mode selector is next to it), left no history at all: Undo
2409
+ * and Redo were dead buttons that never even lit, `history_
2410
+ * pause()` had no `beforeAddCommand` to answer, and the Save
2411
+ * button was told the graph was dirty by `mark_graph_dirty()`
2412
+ * alone -- which is why a move offered Save without Undo.
2413
+ *
2414
+ * So it follows the MODE, and every place that changes the
2415
+ * mode calls this.
2416
+ ************************************************************/
2417
+ function sync_history_plugin(gobj)
2418
+ {
2419
+ let priv = gobj.priv;
2420
+
2421
+ if(!priv.graph || !priv.graph_rendered) {
2422
+ /* Nothing to hang a plugin on yet: the load path calls
2423
+ * this again once the graph is drawn. */
2424
+ return;
2425
+ }
2426
+
2427
+ if(priv.edit_mode) {
2428
+ graph_add_plugin(gobj, "history");
2429
+ } else {
2430
+ graph_remove_plugin(gobj, "history");
2431
+ }
2432
+
2433
+ update_history_buttons(gobj);
2434
+ }
2435
+
2373
2436
  /************************************************************
2374
2437
  *
2375
2438
  ************************************************************/
@@ -2775,6 +2838,7 @@ function graph_add_plugin(gobj, plugin_key, options)
2775
2838
  let graph = priv.graph;
2776
2839
 
2777
2840
  let plugin = graph_get_plugin(gobj, plugin_key);
2841
+ let existed = !!plugin;
2778
2842
  if(!plugin) {
2779
2843
  let plugin_def = {
2780
2844
  key: plugin_key,
@@ -2790,7 +2854,11 @@ function graph_add_plugin(gobj, plugin_key, options)
2790
2854
 
2791
2855
  switch(plugin_key) {
2792
2856
  case "history":
2793
- if(plugin) {
2857
+ /* Wired once per INSTANCE: this is called again on every
2858
+ * load and on every entry into edition, and a second
2859
+ * listener on the same emitter is a second repaint of the
2860
+ * same two buttons. */
2861
+ if(plugin && !existed) {
2794
2862
  plugin.on(HistoryEvent.CHANGE, () => {
2795
2863
  update_history_buttons(gobj);
2796
2864
  });
@@ -3563,10 +3631,10 @@ function toggle_in_selection(gobj, node_id)
3563
3631
  {
3564
3632
  let priv = gobj.priv;
3565
3633
 
3566
- /* The union of the two views of the same thing. A brush clears
3567
- * G6's state on pointerdown without telling anybody, so reading
3568
- * only the state could drop a card that is on screen wearing a
3569
- * ring. */
3634
+ /* The union of the two views of the same thing: either can hold
3635
+ * an id the other lost -- G6's state is what a drag moves, the
3636
+ * painted set is what the reader can see -- and a card on screen
3637
+ * wearing a ring belongs to the selection whichever view has it. */
3570
3638
  let current = new Set(selected_node_ids(gobj));
3571
3639
  for(let id of (priv._selected_paint_ids || [])) {
3572
3640
  current.add(id);
@@ -7000,11 +7068,7 @@ function ac_load_data(gobj, event, kw, src)
7000
7068
  log_error(`${gobj_short_name(gobj)}: cannot fit the graph: ${e}`);
7001
7069
  });
7002
7070
  }
7003
- if(priv.edit_mode) {
7004
- graph_add_plugin(gobj, "history");
7005
- } else {
7006
- graph_remove_plugin(gobj, "history");
7007
- }
7071
+ sync_history_plugin(gobj);
7008
7072
  /* Nodes exist and are positioned now: apply a focus
7009
7073
  * requested before the data was ready (deep link). */
7010
7074
  if(priv._pending_focus_topic !== null) {
@@ -7462,6 +7526,10 @@ function ac_set_operation_mode(gobj, event, kw, src)
7462
7526
  gobj_write_attr(gobj, "operation_mode", kw.operation_mode);
7463
7527
  configure_behaviour(gobj);
7464
7528
  update_toolbar(gobj);
7529
+
7530
+ /* `configure_behaviour` is what decides `edit_mode`, and the
7531
+ * history plugin belongs to that decision. */
7532
+ sync_history_plugin(gobj);
7465
7533
  return 0;
7466
7534
  }
7467
7535
 
@@ -0,0 +1,69 @@
1
+ /***********************************************************************
2
+ * g6_brush_select_owned.js
3
+ *
4
+ * The rubber band, for a graph whose FSM owns the selection.
5
+ *
6
+ * Why this exists
7
+ * ----------------
8
+ * G6 v5 `brush-select` does two things: it draws the band and
9
+ * it keeps the `selected` element state. The second one is
10
+ * not ours to give away -- `C_G6_NODES_TREE` owns the
11
+ * selection, sets it in `set_selection()` and clears it in
12
+ * `deselect_node()`, both with the history recorder paused,
13
+ * because a selection is not an edit.
14
+ *
15
+ * The built-in disagrees on one path: on EVERY canvas click
16
+ * it calls `clearElementsStates()`, which writes the state of
17
+ * every node and every edge of the graph -- whether or not
18
+ * any of them carried it -- outside our pause. G6's history
19
+ * then records the redraw as a command whose `original` and
20
+ * `current` are identical: an undo entry that undoes nothing,
21
+ * and a Save button lit on a graph nobody has touched. One
22
+ * click on the background was enough.
23
+ *
24
+ * So this subclass drops that half and keeps the band. The
25
+ * selection still clears on a canvas click -- through
26
+ * `EV_CANVAS_CLICK`, where the gclass has always cleared it.
27
+ *
28
+ * Registered under its OWN id: the built-in `brush-select` is
29
+ * right for a graph that does not own its selection, and no
30
+ * other consumer of this library asked for this.
31
+ *
32
+ * Copyright (c) 2026, ArtGins.
33
+ * All Rights Reserved.
34
+ ***********************************************************************/
35
+ import {
36
+ BrushSelect,
37
+ ExtensionCategory,
38
+ register,
39
+ } from '@antv/g6';
40
+
41
+ /************************************************************
42
+ * Same band, no state bookkeeping behind the gclass's back
43
+ ************************************************************/
44
+ class BrushSelectOwned extends BrushSelect {
45
+ clearElementsStates() {
46
+ /*
47
+ * Deliberately empty. The gclass clears the selection from
48
+ * its own EV_CANVAS_CLICK action, with history paused.
49
+ */
50
+ }
51
+ }
52
+
53
+ /************************************************************
54
+ * Register once, under our own id
55
+ ************************************************************/
56
+ const BRUSH_SELECT_OWNED = 'yui-brush-select';
57
+
58
+ let __brush_select_registered__ = false;
59
+
60
+ function ensure_brush_select_owned()
61
+ {
62
+ if(__brush_select_registered__) {
63
+ return;
64
+ }
65
+ register(ExtensionCategory.BEHAVIOR, BRUSH_SELECT_OWNED, BrushSelectOwned);
66
+ __brush_select_registered__ = true;
67
+ }
68
+
69
+ export { ensure_brush_select_owned, BRUSH_SELECT_OWNED };