@yuneta/gobj-ui 7.23.12 → 7.23.14

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.
@@ -9498,6 +9498,29 @@ function yui_graph_fold_items(gobj, wide) {
9498
9498
  function yui_graph_refresh_item(gobj, wide) {
9499
9499
  return camera_button(gobj, "yi-arrows-rotate", "EV_REFRESH", "refresh", wide);
9500
9500
  }
9501
+ /***************************************************************
9502
+ * Arbitration
9503
+ ***************************************************************/
9504
+ /************************************************************
9505
+ * Did the finger travel far enough to mean a DRAG?
9506
+ *
9507
+ * `press` is what the pointerdown recorded: {x, y, t0}.
9508
+ ************************************************************/
9509
+ function press_moved(press, x, y) {
9510
+ if (!press) return false;
9511
+ return Math.abs(x - press.x) > 10 || Math.abs(y - press.y) > 10;
9512
+ }
9513
+ /************************************************************
9514
+ * What the press meant, asked when the finger comes up.
9515
+ *
9516
+ * Answers "none" when there was no press to arbitrate.
9517
+ ************************************************************/
9518
+ function classify_press(press, x, y, t) {
9519
+ if (!press) return "none";
9520
+ if (press_moved(press, x, y)) return "drag";
9521
+ if (t - press.t0 >= 500) return "long";
9522
+ return "tap";
9523
+ }
9501
9524
  //#endregion
9502
9525
  //#region src/g6_touch_gestures.js
9503
9526
  /***********************************************************************
@@ -9534,6 +9557,21 @@ function yui_graph_refresh_item(gobj, wide) {
9534
9557
  * forwarded event object -- which is why the plugin's
9535
9558
  * `getItems(e)` receives exactly the shape it expects.
9536
9559
  *
9560
+ * ONE finger has to serve three commands -- move the
9561
+ * node, act on it, open its menu -- so the press is
9562
+ * arbitrated, and arbitrated at the RELEASE:
9563
+ *
9564
+ * moved -> drag
9565
+ * still, let go fast -> the element's own action
9566
+ * still, held >= 500ms -> the context menu
9567
+ *
9568
+ * A menu opened on a TIMER instead cannot be arbitrated
9569
+ * at all: it opens while `drag-element` is already
9570
+ * carrying the node, so the same press means both things
9571
+ * and the user gets a menu over a card running away
9572
+ * underneath it. `classify_press` is where the three
9573
+ * live, and it is pure.
9574
+ *
9537
9575
  * Both are built on ONE per-graph record of the fingers
9538
9576
  * on the glass, read from the NATIVE touch events -- see
9539
9577
  * `touch_state_of` for why the pointer stream, which looks
@@ -9546,11 +9584,6 @@ function yui_graph_refresh_item(gobj, wide) {
9546
9584
  * All Rights Reserved.
9547
9585
  ***********************************************************************/
9548
9586
  /***************************************************************
9549
- * Constants
9550
- ***************************************************************/
9551
- var LONG_PRESS_MS = 500;
9552
- var LONG_PRESS_SLOP = 10;
9553
- /***************************************************************
9554
9587
  * Per-graph record of the fingers on the glass
9555
9588
  ***************************************************************/
9556
9589
  var __touch_state__ = /* @__PURE__ */ new WeakMap();
@@ -9577,7 +9610,9 @@ function touch_state_of(graph) {
9577
9610
  if (state) return state;
9578
9611
  state = {
9579
9612
  count: 0,
9580
- container: null
9613
+ multi: false,
9614
+ container: null,
9615
+ long_press_click: false
9581
9616
  };
9582
9617
  __touch_state__.set(graph, state);
9583
9618
  let container = graph.getCanvas?.()?.getContainer?.();
@@ -9585,11 +9620,16 @@ function touch_state_of(graph) {
9585
9620
  state.container = container;
9586
9621
  let read = (event) => {
9587
9622
  state.count = event.touches ? event.touches.length : 0;
9623
+ if (state.count >= 2) state.multi = true;
9624
+ else if (state.count === 0) state.multi = false;
9588
9625
  };
9589
9626
  container.addEventListener("touchstart", read, { passive: true });
9590
9627
  container.addEventListener("touchmove", read, { passive: true });
9591
9628
  container.addEventListener("touchend", read, { passive: true });
9592
9629
  container.addEventListener("touchcancel", read, { passive: true });
9630
+ container.addEventListener("contextmenu", (event) => {
9631
+ if (state.count > 0) event.preventDefault();
9632
+ }, { capture: true });
9593
9633
  return state;
9594
9634
  }
9595
9635
  /************************************************************
@@ -9690,6 +9730,24 @@ function ensure_pinch_zoom_patch() {
9690
9730
  * Long press
9691
9731
  ***************************/
9692
9732
  /************************************************************
9733
+ * Is the click being dispatched right now the tail of a long
9734
+ * press? Answers true ONCE, to the first asker.
9735
+ *
9736
+ * @antv/g does not take `click` from the DOM either: it makes
9737
+ * one out of the pointerdown/pointerup pair itself, inside
9738
+ * the same `onPointerUp` that just fed us the release. So
9739
+ * the press that opened the menu goes on to click whatever it
9740
+ * opened the menu ON -- selecting the node, or worse, dropping
9741
+ * a selection of several -- unless the handler asks.
9742
+ ************************************************************/
9743
+ function consume_long_press_click(graph) {
9744
+ if (!graph) return false;
9745
+ let state = __touch_state__.get(graph);
9746
+ if (!state || !state.long_press_click) return false;
9747
+ state.long_press_click = false;
9748
+ return true;
9749
+ }
9750
+ /************************************************************
9693
9751
  * Make a long press open the context menu.
9694
9752
  *
9695
9753
  * Returns the function that uninstalls it.
@@ -9699,19 +9757,26 @@ function ensure_pinch_zoom_patch() {
9699
9757
  * the name the plugin listens for, so `getItems(e)` and
9700
9758
  * `enable(e)` see the same object a right-click would give
9701
9759
  * them -- same `target`, same `targetType`, same `client`.
9760
+ *
9761
+ * `options.enable(event)` decides, at the DOWN, whether this
9762
+ * press is even a candidate. Pass the plugin's own predicate:
9763
+ * a press on the empty canvas would otherwise arm a menu the
9764
+ * plugin then refuses to show, and swallow the click that
9765
+ * would have cleared the selection.
9702
9766
  ************************************************************/
9703
- function install_long_press_contextmenu(graph) {
9767
+ function install_long_press_contextmenu(graph, options) {
9704
9768
  if (!graph) return () => {};
9769
+ let enable = options?.enable;
9705
9770
  let state = touch_state_of(graph);
9706
9771
  let pending = null;
9707
- let cancel = () => {
9708
- if (pending) {
9709
- clearTimeout(pending.timer);
9710
- pending = null;
9711
- }
9772
+ let disarm = () => {
9773
+ pending = null;
9712
9774
  };
9713
9775
  let fire = (event) => {
9714
- pending = null;
9776
+ state.long_press_click = true;
9777
+ setTimeout(() => {
9778
+ state.long_press_click = false;
9779
+ }, 0);
9715
9780
  let swallow = (evt) => {
9716
9781
  evt.stopPropagation();
9717
9782
  document.removeEventListener("click", swallow, true);
@@ -9720,51 +9785,60 @@ function install_long_press_contextmenu(graph) {
9720
9785
  setTimeout(() => {
9721
9786
  document.removeEventListener("click", swallow, true);
9722
9787
  }, 1e3);
9723
- let container = graph.getCanvas?.()?.getContainer?.();
9724
- if (container) container.addEventListener("contextmenu", (evt) => {
9725
- evt.preventDefault();
9726
- }, {
9727
- once: true,
9728
- capture: true
9729
- });
9730
9788
  graph.emit(`${event.targetType}:${CommonEvent.CONTEXT_MENU}`, event);
9731
9789
  graph.emit(CommonEvent.CONTEXT_MENU, event);
9732
9790
  };
9733
9791
  let on_down = (event) => {
9734
- cancel();
9792
+ disarm();
9735
9793
  if (event.pointerType !== "touch") return;
9736
- if (state.count > 1) return;
9794
+ if (state.count > 1 || state.multi) return;
9795
+ if (enable && !enable(event)) return;
9737
9796
  let x = event.client?.x;
9738
9797
  let y = event.client?.y;
9739
9798
  if (x === void 0 || y === void 0) return;
9740
- let frozen = Object.assign({}, event);
9741
9799
  pending = {
9742
9800
  x,
9743
9801
  y,
9744
- timer: setTimeout(() => {
9745
- fire(frozen);
9746
- }, LONG_PRESS_MS)
9802
+ t0: Date.now(),
9803
+ event: Object.assign({}, event)
9747
9804
  };
9748
9805
  };
9749
9806
  let on_move = (event) => {
9750
9807
  if (!pending) return;
9751
- if (state.count > 1) {
9752
- cancel();
9808
+ if (state.count > 1 || state.multi) {
9809
+ disarm();
9753
9810
  return;
9754
9811
  }
9755
9812
  let x = event.client?.x;
9756
9813
  let y = event.client?.y;
9757
9814
  if (x === void 0 || y === void 0) return;
9758
- if (Math.abs(x - pending.x) > LONG_PRESS_SLOP || Math.abs(y - pending.y) > LONG_PRESS_SLOP) cancel();
9815
+ if (press_moved(pending, x, y)) disarm();
9759
9816
  };
9817
+ let on_up = (event) => {
9818
+ let press = pending;
9819
+ pending = null;
9820
+ if (!press) return;
9821
+ if (state.multi) return;
9822
+ let x = event.client?.x;
9823
+ let y = event.client?.y;
9824
+ if (x === void 0) x = press.x;
9825
+ if (y === void 0) y = press.y;
9826
+ if (classify_press(press, x, y, Date.now()) !== "long") return;
9827
+ fire(press.event);
9828
+ };
9829
+ let on_touch_cancel = () => {
9830
+ disarm();
9831
+ };
9832
+ state.container?.addEventListener("touchcancel", on_touch_cancel);
9760
9833
  graph.on(CommonEvent.POINTER_DOWN, on_down);
9761
9834
  graph.on(CommonEvent.POINTER_MOVE, on_move);
9762
- graph.on(CommonEvent.POINTER_UP, cancel);
9835
+ graph.on(CommonEvent.POINTER_UP, on_up);
9763
9836
  return () => {
9764
- cancel();
9837
+ disarm();
9838
+ state.container?.removeEventListener("touchcancel", on_touch_cancel);
9765
9839
  graph.off(CommonEvent.POINTER_DOWN, on_down);
9766
9840
  graph.off(CommonEvent.POINTER_MOVE, on_move);
9767
- graph.off(CommonEvent.POINTER_UP, cancel);
9841
+ graph.off(CommonEvent.POINTER_UP, on_up);
9768
9842
  };
9769
9843
  }
9770
9844
  //#endregion
@@ -47964,6 +48038,7 @@ function configure_events(gobj) {
47964
48038
  let priv = gobj.priv;
47965
48039
  let graph = priv.graph;
47966
48040
  graph.on(CanvasEvent.CLICK, (evt) => {
48041
+ if (consume_long_press_click(graph)) return;
47967
48042
  gobj_send_event(gobj, "EV_CANVAS_CLICK", { evt }, gobj);
47968
48043
  });
47969
48044
  graph.on(NodeEvent.DRAG, (evt) => {
@@ -47977,12 +48052,14 @@ function configure_events(gobj) {
47977
48052
  gobj_send_event(gobj, "EV_NODE_DRAG_END", { evt }, gobj);
47978
48053
  });
47979
48054
  graph.on(NodeEvent.CLICK, (evt) => {
48055
+ if (consume_long_press_click(graph)) return;
47980
48056
  gobj_send_event(gobj, "EV_NODE_CLICK", { evt }, gobj);
47981
48057
  });
47982
48058
  graph.on(NodeEvent.CONTEXT_MENU, (evt) => {
47983
48059
  gobj_send_event(gobj, "EV_NODE_CONTEXT_MENU", { evt }, gobj);
47984
48060
  });
47985
48061
  graph.on(EdgeEvent.CLICK, (evt) => {
48062
+ if (consume_long_press_click(graph)) return;
47986
48063
  gobj_send_event(gobj, "EV_EDGE_CLICK", { evt }, gobj);
47987
48064
  });
47988
48065
  graph.on("aftertransform", () => {
@@ -48046,6 +48123,9 @@ function configure_plugins(gobj) {
48046
48123
  update_toolbar(gobj);
48047
48124
  }
48048
48125
  });
48126
+ let menu_enable = (e) => {
48127
+ return e.targetType === "node" || e.targetType === "edge";
48128
+ };
48049
48129
  graph_add_plugin(gobj, "contextmenu", {
48050
48130
  trigger: "contextmenu",
48051
48131
  onClick: (value) => {
@@ -48054,12 +48134,10 @@ function configure_plugins(gobj) {
48054
48134
  getItems: (e) => {
48055
48135
  return build_context_menu_items(gobj, e);
48056
48136
  },
48057
- enable: (e) => {
48058
- return e.targetType === "node" || e.targetType === "edge";
48059
- }
48137
+ enable: menu_enable
48060
48138
  });
48061
48139
  if (priv._uninstall_long_press) priv._uninstall_long_press();
48062
- priv._uninstall_long_press = install_long_press_contextmenu(priv.graph);
48140
+ priv._uninstall_long_press = install_long_press_contextmenu(priv.graph, { enable: menu_enable });
48063
48141
  if (gobj_read_bool_attr(gobj, "with_toolbar")) {
48064
48142
  configure_toolbar(gobj);
48065
48143
  configure_toolbar_edit(gobj);
@@ -53123,6 +53201,40 @@ function destroy_ui$1(gobj) {
53123
53201
  }
53124
53202
  }
53125
53203
  /************************************************************
53204
+ * The label of an option of the two selects of the toolbar.
53205
+ *
53206
+ * Both of them used to render their RAW names -- `reading`,
53207
+ * `edition`, `dagre`, `manual` -- in every language, because
53208
+ * neither went through `t()` at all. Not a missing key: a
53209
+ * missing call.
53210
+ *
53211
+ * Written as one literal per case, and deliberately not as
53212
+ * `t(name)`: a consumer's `validate-locales.mjs` reads
53213
+ * LITERALS, so a variable key is invisible to it -- the app
53214
+ * ships without the entry and nothing says so, because
53215
+ * i18next answers an unknown key with the key itself, which
53216
+ * is exactly the English word that was there before. The
53217
+ * same reason the keys are listed in the README.
53218
+ *
53219
+ * The default is what a host that adds a layout of its own
53220
+ * gets: the name it chose, which is what all of them got
53221
+ * until now.
53222
+ ************************************************************/
53223
+ function option_label(name) {
53224
+ switch (name) {
53225
+ case "reading": return t("reading");
53226
+ case "operation": return t("operation");
53227
+ case "writing": return t("writing");
53228
+ case "edition": return t("edition");
53229
+ case "manual": return t("manual");
53230
+ case "dagre": return t("dagre");
53231
+ case "antv-dagre": return t("antv-dagre");
53232
+ case "d3-force": return t("d3-force");
53233
+ case "force-atlas2": return t("force-atlas2");
53234
+ default: return name;
53235
+ }
53236
+ }
53237
+ /************************************************************
53126
53238
  *
53127
53239
  ************************************************************/
53128
53240
  function make_toolbar(gobj) {
@@ -53165,8 +53277,11 @@ function make_toolbar(gobj) {
53165
53277
  { class: "GRAPH_MODE_SELECT" },
53166
53278
  modes.map((item) => [
53167
53279
  "option",
53168
- {},
53169
- item
53280
+ {
53281
+ value: item,
53282
+ "data-i18n": item
53283
+ },
53284
+ option_label(item)
53170
53285
  ])
53171
53286
  ]],
53172
53287
  { change: (evt) => {
@@ -53505,7 +53620,8 @@ function populate_nodes_tree_options(gobj) {
53505
53620
  for (let name of layout_names) {
53506
53621
  let option = document.createElement("option");
53507
53622
  option.value = name;
53508
- option.textContent = name;
53623
+ option.setAttribute("data-i18n", name);
53624
+ option.textContent = option_label(name);
53509
53625
  $layout_select.appendChild(option);
53510
53626
  }
53511
53627
  let current_layout = gobj_read_str_attr(priv.gobj_nodes_tree, "layout");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.12",
3
+ "version": "7.23.14",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -111,6 +111,7 @@ import {
111
111
  import {
112
112
  ensure_pinch_zoom_patch,
113
113
  install_long_press_contextmenu,
114
+ consume_long_press_click,
114
115
  } from "./g6_touch_gestures.js";
115
116
  import {yui_theme_now, yui_watch_theme} from "./yui_theme.js";
116
117
 
@@ -784,7 +785,14 @@ function configure_events(gobj)
784
785
  let priv = gobj.priv;
785
786
  let graph = priv.graph;
786
787
 
788
+ /* @antv/g makes a `click` out of every pointerdown/pointerup
789
+ * pair, this one included -- so the press that just opened the
790
+ * context menu goes on to click what it opened the menu ON. One
791
+ * press, one meaning: the press was already spent. */
787
792
  graph.on(CanvasEvent.CLICK, (evt) => {
793
+ if(consume_long_press_click(graph)) {
794
+ return;
795
+ }
788
796
  gobj_send_event(gobj, "EV_CANVAS_CLICK", {evt: evt}, gobj);
789
797
  });
790
798
 
@@ -801,6 +809,9 @@ function configure_events(gobj)
801
809
  });
802
810
 
803
811
  graph.on(NodeEvent.CLICK, (evt) => {
812
+ if(consume_long_press_click(graph)) {
813
+ return;
814
+ }
804
815
  gobj_send_event(gobj, "EV_NODE_CLICK", {evt: evt}, gobj);
805
816
  });
806
817
 
@@ -809,6 +820,9 @@ function configure_events(gobj)
809
820
  });
810
821
 
811
822
  graph.on(EdgeEvent.CLICK, (evt) => {
823
+ if(consume_long_press_click(graph)) {
824
+ return;
825
+ }
812
826
  gobj_send_event(gobj, "EV_EDGE_CLICK", {evt: evt}, gobj);
813
827
  });
814
828
 
@@ -948,6 +962,13 @@ function configure_plugins(gobj)
948
962
  );
949
963
  }
950
964
 
965
+ /* What the menu is FOR: a node or an edge. Shared with the long
966
+ * press below, so the gesture arms itself on exactly what the
967
+ * menu would agree to open on. */
968
+ let menu_enable = (e) => {
969
+ return e.targetType === 'node' || e.targetType === 'edge';
970
+ };
971
+
951
972
  graph_add_plugin(
952
973
  gobj,
953
974
  'contextmenu',
@@ -959,9 +980,7 @@ function configure_plugins(gobj)
959
980
  getItems: (e) => {
960
981
  return build_context_menu_items(gobj, e);
961
982
  },
962
- enable: (e) => {
963
- return e.targetType === 'node' || e.targetType === 'edge';
964
- },
983
+ enable: menu_enable,
965
984
  }
966
985
  );
967
986
 
@@ -976,7 +995,9 @@ function configure_plugins(gobj)
976
995
  if(priv._uninstall_long_press) {
977
996
  priv._uninstall_long_press();
978
997
  }
979
- priv._uninstall_long_press = install_long_press_contextmenu(priv.graph);
998
+ priv._uninstall_long_press = install_long_press_contextmenu(
999
+ priv.graph, {enable: menu_enable}
1000
+ );
980
1001
 
981
1002
  if(gobj_read_bool_attr(gobj, "with_toolbar")) {
982
1003
  configure_toolbar(gobj);
@@ -452,6 +452,56 @@ function destroy_ui(gobj)
452
452
  }
453
453
  }
454
454
 
455
+ /************************************************************
456
+ * The label of an option of the two selects of the toolbar.
457
+ *
458
+ * Both of them used to render their RAW names -- `reading`,
459
+ * `edition`, `dagre`, `manual` -- in every language, because
460
+ * neither went through `t()` at all. Not a missing key: a
461
+ * missing call.
462
+ *
463
+ * Written as one literal per case, and deliberately not as
464
+ * `t(name)`: a consumer's `validate-locales.mjs` reads
465
+ * LITERALS, so a variable key is invisible to it -- the app
466
+ * ships without the entry and nothing says so, because
467
+ * i18next answers an unknown key with the key itself, which
468
+ * is exactly the English word that was there before. The
469
+ * same reason the keys are listed in the README.
470
+ *
471
+ * The default is what a host that adds a layout of its own
472
+ * gets: the name it chose, which is what all of them got
473
+ * until now.
474
+ ************************************************************/
475
+ function option_label(name)
476
+ {
477
+ switch(name) {
478
+ /* Operation modes (`operation_modes` attr). */
479
+ case "reading":
480
+ return t("reading");
481
+ case "operation":
482
+ return t("operation");
483
+ case "writing":
484
+ return t("writing");
485
+ case "edition":
486
+ return t("edition");
487
+
488
+ /* Layouts (`layout_names` of the G6 child). */
489
+ case "manual":
490
+ return t("manual");
491
+ case "dagre":
492
+ return t("dagre");
493
+ case "antv-dagre":
494
+ return t("antv-dagre");
495
+ case "d3-force":
496
+ return t("d3-force");
497
+ case "force-atlas2":
498
+ return t("force-atlas2");
499
+
500
+ default:
501
+ return name;
502
+ }
503
+ }
504
+
455
505
  /************************************************************
456
506
  *
457
507
  ************************************************************/
@@ -468,7 +518,14 @@ function make_toolbar(gobj)
468
518
  * one that draws the create / delete / link affordances. */
469
519
  modes = modes.filter(mode => mode !== "edition");
470
520
  }
471
- let mode_options = modes.map(item => ['option', {}, item]);
521
+ /* `value` EXPLICIT, and that is not decoration: an <option>
522
+ * with no value attribute answers with its own TEXT, so the
523
+ * moment the label is translated `evt.target.value` becomes
524
+ * "Edición" and the mode the FSM is told to enter is a word
525
+ * no `switch` of this gclass knows. */
526
+ let mode_options = modes.map(item =>
527
+ ['option', {value: item, 'data-i18n': item}, option_label(item)]
528
+ );
472
529
 
473
530
  /*
474
531
  * Left: layout and mode selectors
@@ -876,7 +933,12 @@ function populate_nodes_tree_options(gobj)
876
933
  for(let name of layout_names) {
877
934
  let option = document.createElement('option');
878
935
  option.value = name;
879
- option.textContent = name;
936
+ /* The key travels in the attribute so a language change
937
+ * re-translates it: `refresh_language()` only touches a
938
+ * node that carries its own key, and this select is built
939
+ * once, after the child answers with its layouts. */
940
+ option.setAttribute('data-i18n', name);
941
+ option.textContent = option_label(name);
880
942
  $layout_select.appendChild(option);
881
943
  }
882
944
  // Restore persisted layout selection