@yuneta/gobj-ui 7.23.8 → 7.23.9

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.
@@ -9504,6 +9504,275 @@ function yui_graph_refresh_item(gobj, wide) {
9504
9504
  return camera_button(gobj, "yi-arrows-rotate", "EV_REFRESH", "refresh", wide);
9505
9505
  }
9506
9506
  //#endregion
9507
+ //#region src/g6_touch_gestures.js
9508
+ /***********************************************************************
9509
+ * g6_touch_gestures.js
9510
+ *
9511
+ * The two gestures a G6 graph needs on a touch screen and
9512
+ * does not have: PINCH to zoom, and LONG PRESS to open the
9513
+ * context menu.
9514
+ *
9515
+ * Why this exists
9516
+ * ----------------
9517
+ * G6 v5 draws a graph that reads fine on a phone and cannot
9518
+ * be OPERATED on one. Two gaps, both structural:
9519
+ *
9520
+ * 1. `zoom-canvas` binds the WHEEL and nothing else
9521
+ * (`trigger: []`). There is no wheel on a phone, so the
9522
+ * only zoom is the toolbar's +/- buttons. G6 does ship a
9523
+ * pinch recogniser, and `zoom-canvas` will use it -- with
9524
+ * `trigger: ['pinch']`, which its `bindEvents` treats as
9525
+ * an ALTERNATIVE to the wheel (`if pinch ... else wheel`),
9526
+ * so opting in costs the mouse. Worse, its `PinchHandler`
9527
+ * keeps its instance and its callback list in STATICS: on
9528
+ * a page with two graphs the second one registers its
9529
+ * callback against the FIRST one's emitter, so pinching
9530
+ * graph A zooms both and pinching graph B does nothing.
9531
+ * We recognise the gesture ourselves, per graph.
9532
+ *
9533
+ * 2. The context menu is bound to `contextmenu`, and G6 does
9534
+ * not take that from the DOM: its `BehaviorController`
9535
+ * SYNTHESISES it from `pointerdown` with `button === 2`.
9536
+ * A long press therefore never reaches the plugin on ANY
9537
+ * platform, whatever the browser does with the gesture.
9538
+ * So we synthesise it the same way G6 does, from the same
9539
+ * forwarded event object -- which is why the plugin's
9540
+ * `getItems(e)` receives exactly the shape it expects.
9541
+ *
9542
+ * Both are built on ONE per-graph record of the fingers
9543
+ * on the glass, read from the NATIVE touch events -- see
9544
+ * `touch_state_of` for why the pointer stream, which looks
9545
+ * like the right source, is not. That record is also what
9546
+ * tells `drag-canvas` to stop panning while two fingers are
9547
+ * down (`is_pinching`), the guard G6's own drag-canvas has
9548
+ * and our replacement dropped.
9549
+ *
9550
+ * Copyright (c) 2026, ArtGins.
9551
+ * All Rights Reserved.
9552
+ ***********************************************************************/
9553
+ /***************************************************************
9554
+ * Constants
9555
+ ***************************************************************/
9556
+ var LONG_PRESS_MS = 500;
9557
+ var LONG_PRESS_SLOP = 10;
9558
+ /***************************************************************
9559
+ * Per-graph record of the fingers on the glass
9560
+ ***************************************************************/
9561
+ var __touch_state__ = /* @__PURE__ */ new WeakMap();
9562
+ /************************************************************
9563
+ * The touch record of one graph, installing its listeners
9564
+ * the first time it is asked for.
9565
+ *
9566
+ * Read from the NATIVE touch events, not from G6's
9567
+ * forwarded pointer stream.
9568
+ *
9569
+ * The pointer stream looks like the right source and is
9570
+ * not: @antv/g re-issues pointer ids in the middle of a
9571
+ * two-finger gesture (measured: a pinch that started on
9572
+ * ids 2 and 3 finished on id 1), so anything that keys a
9573
+ * Map on `pointerId` loses one of the two fingers halfway
9574
+ * and reads the gesture as a fraction of what it was.
9575
+ * G6's own `PinchHandler` is built exactly that way.
9576
+ *
9577
+ * `event.touches` needs no bookkeeping at all: it IS the
9578
+ * list of fingers currently down, re-stated on every event.
9579
+ ************************************************************/
9580
+ function touch_state_of(graph) {
9581
+ let state = __touch_state__.get(graph);
9582
+ if (state) return state;
9583
+ state = {
9584
+ count: 0,
9585
+ container: null
9586
+ };
9587
+ __touch_state__.set(graph, state);
9588
+ let container = graph.getCanvas?.()?.getContainer?.();
9589
+ if (!container) return state;
9590
+ state.container = container;
9591
+ let read = (event) => {
9592
+ state.count = event.touches ? event.touches.length : 0;
9593
+ };
9594
+ container.addEventListener("touchstart", read, { passive: true });
9595
+ container.addEventListener("touchmove", read, { passive: true });
9596
+ container.addEventListener("touchend", read, { passive: true });
9597
+ container.addEventListener("touchcancel", read, { passive: true });
9598
+ return state;
9599
+ }
9600
+ /************************************************************
9601
+ * Is a two-finger gesture in progress on this graph?
9602
+ *
9603
+ * Read by our `drag-canvas` replacement: panning and zooming
9604
+ * the same drag at the same time fights the user for the
9605
+ * camera.
9606
+ ************************************************************/
9607
+ function is_pinching(graph) {
9608
+ if (!graph) return false;
9609
+ let state = __touch_state__.get(graph);
9610
+ if (!state) return false;
9611
+ return state.count >= 2;
9612
+ }
9613
+ /***************************
9614
+ * Pinch to zoom
9615
+ ***************************/
9616
+ /************************************************************
9617
+ * `zoom-canvas` that also answers the pinch.
9618
+ *
9619
+ * The base class keeps the wheel; the pinch is added on top
9620
+ * instead of replacing it, so one build serves the mouse and
9621
+ * the finger. The scale is ABSOLUTE against the zoom the
9622
+ * gesture started at -- G6's own pinch feeds `zoom()` an
9623
+ * increment per frame, which drifts away from the fingers
9624
+ * over a long gesture.
9625
+ ************************************************************/
9626
+ var ZoomCanvasPinch = class extends _antv_g6.ZoomCanvas {
9627
+ constructor(context, options) {
9628
+ super(context, options);
9629
+ this._pinch_dist0 = 0;
9630
+ this._pinch_zoom0 = 1;
9631
+ this._on_touch_move = null;
9632
+ let { trigger } = this.options;
9633
+ if (Array.isArray(trigger) && trigger.includes(_antv_g6.CommonEvent.PINCH)) return;
9634
+ let graph = this.context.graph;
9635
+ let state = touch_state_of(graph);
9636
+ if (!state.container) return;
9637
+ this._on_touch_move = (event) => {
9638
+ let touches = event.touches;
9639
+ if (!touches || touches.length !== 2) {
9640
+ this._pinch_dist0 = 0;
9641
+ return;
9642
+ }
9643
+ let dx = touches[0].clientX - touches[1].clientX;
9644
+ let dy = touches[0].clientY - touches[1].clientY;
9645
+ let dist = Math.sqrt(dx * dx + dy * dy);
9646
+ if (dist <= 0) return;
9647
+ if (this._pinch_dist0 === 0) {
9648
+ this._pinch_dist0 = dist;
9649
+ this._pinch_zoom0 = graph.getZoom();
9650
+ return;
9651
+ }
9652
+ this._pinch_to(dist / this._pinch_dist0, touches, event);
9653
+ };
9654
+ state.container.addEventListener("touchmove", this._on_touch_move, { passive: true });
9655
+ }
9656
+ /************************************************************
9657
+ * Zoom to `scale` times the zoom the pinch started at,
9658
+ * about the midpoint between the two fingers.
9659
+ *
9660
+ * ABSOLUTE against the start of the gesture, where G6's
9661
+ * own pinch feeds `zoom()` an increment per frame: an
9662
+ * increment drifts away from the fingers over a long
9663
+ * gesture, and cannot come back when they close again.
9664
+ ************************************************************/
9665
+ _pinch_to(scale, touches, event) {
9666
+ if (!this.validate(event)) return;
9667
+ let graph = this.context.graph;
9668
+ let state = touch_state_of(graph);
9669
+ let origin = null;
9670
+ let rect = state.container?.getBoundingClientRect();
9671
+ if (rect) origin = [(touches[0].clientX + touches[1].clientX) / 2 - rect.left, (touches[0].clientY + touches[1].clientY) / 2 - rect.top];
9672
+ graph.zoomTo(this._pinch_zoom0 * scale, false, origin);
9673
+ this.options.onFinish?.();
9674
+ }
9675
+ destroy() {
9676
+ if (this._on_touch_move) {
9677
+ __touch_state__.get(this.context.graph)?.container?.removeEventListener("touchmove", this._on_touch_move);
9678
+ this._on_touch_move = null;
9679
+ }
9680
+ super.destroy();
9681
+ }
9682
+ };
9683
+ /************************************************************
9684
+ * Register once, over the built-in `zoom-canvas`, so every
9685
+ * graph (tree, json, treedb, editor) pinches without
9686
+ * touching its behaviors list.
9687
+ ************************************************************/
9688
+ var __zoom_canvas_patched__ = false;
9689
+ function ensure_pinch_zoom_patch() {
9690
+ if (__zoom_canvas_patched__) return;
9691
+ (0, _antv_g6.register)(_antv_g6.ExtensionCategory.BEHAVIOR, "zoom-canvas", ZoomCanvasPinch);
9692
+ __zoom_canvas_patched__ = true;
9693
+ }
9694
+ /***************************
9695
+ * Long press
9696
+ ***************************/
9697
+ /************************************************************
9698
+ * Make a long press open the context menu.
9699
+ *
9700
+ * Returns the function that uninstalls it.
9701
+ *
9702
+ * The menu the press opens is whatever the `contextmenu`
9703
+ * plugin builds: we re-emit G6's own forwarded event under
9704
+ * the name the plugin listens for, so `getItems(e)` and
9705
+ * `enable(e)` see the same object a right-click would give
9706
+ * them -- same `target`, same `targetType`, same `client`.
9707
+ ************************************************************/
9708
+ function install_long_press_contextmenu(graph) {
9709
+ if (!graph) return () => {};
9710
+ let state = touch_state_of(graph);
9711
+ let pending = null;
9712
+ let cancel = () => {
9713
+ if (pending) {
9714
+ clearTimeout(pending.timer);
9715
+ pending = null;
9716
+ }
9717
+ };
9718
+ let fire = (event) => {
9719
+ pending = null;
9720
+ let swallow = (evt) => {
9721
+ evt.stopPropagation();
9722
+ document.removeEventListener("click", swallow, true);
9723
+ };
9724
+ document.addEventListener("click", swallow, true);
9725
+ setTimeout(() => {
9726
+ document.removeEventListener("click", swallow, true);
9727
+ }, 1e3);
9728
+ let container = graph.getCanvas?.()?.getContainer?.();
9729
+ if (container) container.addEventListener("contextmenu", (evt) => {
9730
+ evt.preventDefault();
9731
+ }, {
9732
+ once: true,
9733
+ capture: true
9734
+ });
9735
+ graph.emit(`${event.targetType}:${_antv_g6.CommonEvent.CONTEXT_MENU}`, event);
9736
+ graph.emit(_antv_g6.CommonEvent.CONTEXT_MENU, event);
9737
+ };
9738
+ let on_down = (event) => {
9739
+ cancel();
9740
+ if (event.pointerType !== "touch") return;
9741
+ if (state.count > 1) return;
9742
+ let x = event.client?.x;
9743
+ let y = event.client?.y;
9744
+ if (x === void 0 || y === void 0) return;
9745
+ let frozen = Object.assign({}, event);
9746
+ pending = {
9747
+ x,
9748
+ y,
9749
+ timer: setTimeout(() => {
9750
+ fire(frozen);
9751
+ }, LONG_PRESS_MS)
9752
+ };
9753
+ };
9754
+ let on_move = (event) => {
9755
+ if (!pending) return;
9756
+ if (state.count > 1) {
9757
+ cancel();
9758
+ return;
9759
+ }
9760
+ let x = event.client?.x;
9761
+ let y = event.client?.y;
9762
+ if (x === void 0 || y === void 0) return;
9763
+ if (Math.abs(x - pending.x) > LONG_PRESS_SLOP || Math.abs(y - pending.y) > LONG_PRESS_SLOP) cancel();
9764
+ };
9765
+ graph.on(_antv_g6.CommonEvent.POINTER_DOWN, on_down);
9766
+ graph.on(_antv_g6.CommonEvent.POINTER_MOVE, on_move);
9767
+ graph.on(_antv_g6.CommonEvent.POINTER_UP, cancel);
9768
+ return () => {
9769
+ cancel();
9770
+ graph.off(_antv_g6.CommonEvent.POINTER_DOWN, on_down);
9771
+ graph.off(_antv_g6.CommonEvent.POINTER_MOVE, on_move);
9772
+ graph.off(_antv_g6.CommonEvent.POINTER_UP, cancel);
9773
+ };
9774
+ }
9775
+ //#endregion
9507
9776
  //#region src/g6_drag_canvas_touch.js
9508
9777
  /***********************************************************************
9509
9778
  * g6_drag_canvas_touch.js
@@ -9548,6 +9817,12 @@ function yui_graph_refresh_item(gobj, wide) {
9548
9817
  * every graph (tree, json, treedb, editor) is fixed without
9549
9818
  * touching each consumer's behaviors list.
9550
9819
  *
9820
+ * It also has to keep the ONE guard the base class has and
9821
+ * this replacement dropped: G6's `onDrag` refuses to pan
9822
+ * while a pinch is in progress. See `is_pinching` in
9823
+ * `g6_touch_gestures.js`, which is where the pinch that
9824
+ * matters is now recognised.
9825
+ *
9551
9826
  * Copyright (c) 2026, ArtGins.
9552
9827
  * All Rights Reserved.
9553
9828
  ***********************************************************************/
@@ -9563,6 +9838,10 @@ var DragCanvasTouch = class extends _antv_g6.DragCanvas {
9563
9838
  this._lastVP = null;
9564
9839
  return;
9565
9840
  }
9841
+ if (is_pinching(this.context.graph)) {
9842
+ this._lastVP = null;
9843
+ return;
9844
+ }
9566
9845
  let vp = event.viewport;
9567
9846
  if (!vp || typeof vp.x !== "number" || typeof vp.y !== "number") return;
9568
9847
  if (this._lastVP === null) {
@@ -9792,6 +10071,7 @@ function mt_create$11(gobj) {
9792
10071
  priv.theme_observer = yui_watch_theme(gobj);
9793
10072
  build_ui$10(gobj);
9794
10073
  ensure_drag_canvas_patch();
10074
+ ensure_pinch_zoom_patch();
9795
10075
  if (!__layout_registered__$1) {
9796
10076
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "json-tree-layout", JsonTreeLayout);
9797
10077
  __layout_registered__$1 = true;
@@ -12420,6 +12700,7 @@ function mt_create$9(gobj) {
12420
12700
  priv.theme_observer = yui_watch_theme(gobj);
12421
12701
  build_ui$8(gobj);
12422
12702
  ensure_drag_canvas_patch();
12703
+ ensure_pinch_zoom_patch();
12423
12704
  if (!__layout_registered__) {
12424
12705
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "gobj-tree-v", GobjTreeVLayout);
12425
12706
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "gobj-tree-h", GobjTreeHLayout);
@@ -47198,6 +47479,20 @@ var ICONS = `<svg xmlns="http://www.w3.org/2000/svg">
47198
47479
  <symbol id="g6-icon-plus" viewBox="0 0 512 512">
47199
47480
  <path fill="currentColor" d="M316 32 L316 196 L480 196 L480 316 L316 316 L316 480 L196 480 L196 316 L32 316 L32 196 L196 196 L196 32 Z"/>
47200
47481
  </symbol>
47482
+ <!-- ellipsis-vertical (unfold the floating toolbar).
47483
+ The strip it stands for is a vertical stack, and a vertical
47484
+ ellipsis is what "there is more of this here" is drawn as
47485
+ everywhere else. -->
47486
+ <symbol id="g6-icon-toolbar-show" viewBox="0 0 128 512">
47487
+ <path fill="currentColor" d="M64 360a56 56 0 1 0 0 112 56 56 0 1 0 0-112zm0-160a56 56 0 1 0 0 112 56 56 0 1 0 0-112zM120 96A56 56 0 1 0 8 96a56 56 0 1 0 112 0z"/>
47488
+ </symbol>
47489
+ <!-- xmark (fold the floating toolbar away).
47490
+ Not a chevron: the two toolbars sit on OPPOSITE edges, so a
47491
+ direction would point the wrong way on one of them. What the
47492
+ button does is close an overlay, and that is an x. -->
47493
+ <symbol id="g6-icon-toolbar-hide" viewBox="0 0 384 512">
47494
+ <path fill="currentColor" d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/>
47495
+ </symbol>
47201
47496
  <!-- floppy-disk (save) -->
47202
47497
  <symbol id="g6-icon-save" viewBox="0 0 448 512">
47203
47498
  <path fill="currentColor" d="M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-242.7c0-17-6.7-33.3-18.7-45.3L352 50.7C340 38.7 323.7 32 306.7 32L64 32zm32 96c0-17.7 14.3-32 32-32l160 0c17.7 0 32 14.3 32 32l0 64c0 17.7-14.3 32-32 32l-160 0c-17.7 0-32-14.3-32-32l0-64zM224 288a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/>
@@ -47270,6 +47565,7 @@ var YuiToolbar = class extends _antv_g6.Toolbar {
47270
47565
  };
47271
47566
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.PLUGIN, "yui-toolbar", YuiToolbar);
47272
47567
  ensure_drag_canvas_patch();
47568
+ ensure_pinch_zoom_patch();
47273
47569
  /***************************************************************
47274
47570
  * Constants
47275
47571
  ***************************************************************/
@@ -47389,6 +47685,9 @@ var PRIVATE_DATA$4 = {
47389
47685
  _focus_topic: null,
47390
47686
  _focus_ids: [],
47391
47687
  _on_pointerdown_focus: null,
47688
+ _uninstall_long_press: null,
47689
+ toolbar_collapsed: true,
47690
+ _toolbars_could_collapse: null,
47392
47691
  _on_focusout_restore: null,
47393
47692
  _selected_paint_ids: [],
47394
47693
  _pending_focus_topic: null,
@@ -47474,6 +47773,10 @@ function mt_destroy$4(gobj) {
47474
47773
  cancelAnimationFrame(priv._resize_raf);
47475
47774
  priv._resize_raf = 0;
47476
47775
  }
47776
+ if (priv._uninstall_long_press) {
47777
+ priv._uninstall_long_press();
47778
+ priv._uninstall_long_press = null;
47779
+ }
47477
47780
  if (priv.graph) {
47478
47781
  priv.graph.destroy();
47479
47782
  priv.graph = null;
@@ -47688,6 +47991,8 @@ function configure_plugins(gobj) {
47688
47991
  return e.targetType === "node" || e.targetType === "edge";
47689
47992
  }
47690
47993
  });
47994
+ if (priv._uninstall_long_press) priv._uninstall_long_press();
47995
+ priv._uninstall_long_press = install_long_press_contextmenu(priv.graph);
47691
47996
  if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_toolbar")) {
47692
47997
  configure_toolbar(gobj);
47693
47998
  configure_toolbar_edit(gobj);
@@ -47717,6 +48022,55 @@ function update_toolbar(gobj) {
47717
48022
  * flipping now; the fallbacks are the old forced-light values, so
47718
48023
  * a host without Bulma gets exactly what it had.
47719
48024
  ************************************************************/
48025
+ /************************************************************
48026
+ * Is the canvas too narrow to give both floating toolbars a
48027
+ * strip of it?
48028
+ *
48029
+ * They are drawn INSIDE the canvas, one on each edge. On a
48030
+ * desktop that is a corner; on a 356px-wide phone canvas the
48031
+ * two of them take a third of the drawing area and stand on
48032
+ * top of the nodes -- and a node under a toolbar cannot be
48033
+ * read, tapped or dragged out from under it.
48034
+ *
48035
+ * Measured on the CONTAINER, not on the window: the same
48036
+ * graph is a full page in one app and a card in a column in
48037
+ * another, and it is the box it actually got that decides.
48038
+ ************************************************************/
48039
+ var TOOLBAR_COLLAPSE_WIDTH = 480;
48040
+ function toolbars_can_collapse(gobj) {
48041
+ let $container = gobj.priv.$container;
48042
+ if (!$container) return false;
48043
+ let width = $container.getBoundingClientRect().width;
48044
+ if (!width) return false;
48045
+ return width < TOOLBAR_COLLAPSE_WIDTH;
48046
+ }
48047
+ /************************************************************
48048
+ * Are they folded RIGHT NOW? Wide enough and the question
48049
+ * does not arise -- the desktop toolbar never grows a button
48050
+ * it does not need.
48051
+ ************************************************************/
48052
+ function toolbars_collapsed(gobj) {
48053
+ if (!toolbars_can_collapse(gobj)) return false;
48054
+ return gobj.priv.toolbar_collapsed !== false;
48055
+ }
48056
+ /************************************************************
48057
+ * The one item a folded toolbar shows, and the one an
48058
+ * unfolded narrow toolbar adds to fold itself again.
48059
+ ************************************************************/
48060
+ function toolbar_fold_item(gobj) {
48061
+ if (toolbars_collapsed(gobj)) return {
48062
+ id: "g6-icon-toolbar-show",
48063
+ value: "toolbar-toggle",
48064
+ className: "EV_TOGGLE_TOOLBARS",
48065
+ title: (0, i18next.t)("show toolbar")
48066
+ };
48067
+ return {
48068
+ id: "g6-icon-toolbar-hide",
48069
+ value: "toolbar-toggle",
48070
+ className: "EV_TOGGLE_TOOLBARS",
48071
+ title: (0, i18next.t)("hide toolbar")
48072
+ };
48073
+ }
47720
48074
  function toolbar_style(extra) {
47721
48075
  return Object.assign({
47722
48076
  backgroundColor: "var(--bulma-scheme-main, #f5f5f5)",
@@ -47774,6 +48128,7 @@ function configure_toolbar(gobj) {
47774
48128
  marginLeft: "12px"
47775
48129
  }),
47776
48130
  getItems: () => {
48131
+ if (toolbars_collapsed(gobj)) return [toolbar_fold_item(gobj)];
47777
48132
  let items = [
47778
48133
  {
47779
48134
  id: "g6-icon-zoom-in",
@@ -47829,6 +48184,10 @@ function configure_toolbar(gobj) {
47829
48184
  title: (0, i18next.t)("enter full screen")
47830
48185
  });
47831
48186
  }
48187
+ if (toolbars_can_collapse(gobj)) {
48188
+ items.push({ separator: true });
48189
+ items.push(toolbar_fold_item(gobj));
48190
+ }
47832
48191
  return items;
47833
48192
  },
47834
48193
  onClick: (value) => {
@@ -47854,7 +48213,10 @@ function configure_toolbar(gobj) {
47854
48213
  case "request-fullscreen":
47855
48214
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REQUEST_FULLSCREEN", {}, gobj);
47856
48215
  break;
47857
- case "exit-fullscreen": (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_EXIT_FULLSCREEN", {}, gobj);
48216
+ case "exit-fullscreen":
48217
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_EXIT_FULLSCREEN", {}, gobj);
48218
+ break;
48219
+ case "toolbar-toggle": (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_TOGGLE_TOOLBARS", {}, gobj);
47858
48220
  }
47859
48221
  }
47860
48222
  });
@@ -47864,7 +48226,7 @@ function configure_toolbar(gobj) {
47864
48226
  * Contains edit-mode buttons: Undo, Redo, Save
47865
48227
  ************************************************************/
47866
48228
  function configure_toolbar_edit(gobj) {
47867
- if (!gobj.priv.edit_mode) {
48229
+ if (!gobj.priv.edit_mode || toolbars_collapsed(gobj)) {
47868
48230
  graph_remove_plugin(gobj, "toolbar-edit");
47869
48231
  return;
47870
48232
  }
@@ -47972,6 +48334,7 @@ function configure_behaviour(gobj) {
47972
48334
  break;
47973
48335
  case "operation":
47974
48336
  priv.edit_mode = false;
48337
+ behaviors = ["drag-canvas", "zoom-canvas"];
47975
48338
  break;
47976
48339
  default: (0, _yuneta_gobj_js.log_error)(`operation mode unknown: ${operation_mode}`);
47977
48340
  }
@@ -48934,6 +49297,14 @@ var ManualLayout = class extends _antv_g6.BaseLayout {
48934
49297
  ************************************************************/
48935
49298
  var RESIZE_HANDLE_SIZE = 8;
48936
49299
  var RESIZE_MIN_VP = 20;
49300
+ var RESIZE_HANDLE_VISUAL_COARSE = 14;
49301
+ var RESIZE_HANDLE_HIT_COARSE = 44;
49302
+ function resize_handle_visual() {
49303
+ return coarse_pointer() ? RESIZE_HANDLE_VISUAL_COARSE : RESIZE_HANDLE_SIZE;
49304
+ }
49305
+ function resize_handle_hit() {
49306
+ return coarse_pointer() ? RESIZE_HANDLE_HIT_COARSE : RESIZE_HANDLE_SIZE;
49307
+ }
48937
49308
  /************************************************************
48938
49309
  * SVG icons for floating action buttons (28×28 circles).
48939
49310
  * Each value is a raw SVG string sized 18×18 in a 24×24 viewBox.
@@ -48946,7 +49317,50 @@ var SVG_ICONS = {
48946
49317
  plus: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"/><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/></svg>"
48947
49318
  };
48948
49319
  /************************************************************
48949
- * Create a 28×28 floating circular icon button.
49320
+ * Is the primary pointer a FINGER?
49321
+ *
49322
+ * Everything this gclass draws over the canvas is sized from
49323
+ * this answer. A mouse is a pixel and can hit an 8px square;
49324
+ * a fingertip covers about 40, and the overlay it lands on
49325
+ * decides between "open the properties" and "delete the node".
49326
+ *
49327
+ * Asked per call, not cached: a tablet with a keyboard dock
49328
+ * changes the answer without reloading the page.
49329
+ ************************************************************/
49330
+ function coarse_pointer() {
49331
+ if (typeof window.matchMedia !== "function") return false;
49332
+ try {
49333
+ return window.matchMedia("(pointer: coarse)").matches === true;
49334
+ } catch (e) {
49335
+ return false;
49336
+ }
49337
+ }
49338
+ /************************************************************
49339
+ * The size of the round floating buttons (properties,
49340
+ * delete, link), and the GAP between two of them.
49341
+ *
49342
+ * The gap matters as much as the size: `node properties` and
49343
+ * `delete node` are drawn one under the other, and at 28px
49344
+ * with 4px between them a fingertip covers both -- with the
49345
+ * destructive one underneath.
49346
+ ************************************************************/
49347
+ function floating_icon_size() {
49348
+ return coarse_pointer() ? 44 : 28;
49349
+ }
49350
+ function floating_icon_gap() {
49351
+ return coarse_pointer() ? 12 : 4;
49352
+ }
49353
+ function floating_icon_dy() {
49354
+ return -floating_icon_size() / 2;
49355
+ }
49356
+ function floating_icon_step() {
49357
+ return floating_icon_size() + floating_icon_gap();
49358
+ }
49359
+ function floating_popover_dx() {
49360
+ return 4 + floating_icon_size() + 4;
49361
+ }
49362
+ /************************************************************
49363
+ * Create a floating circular icon button.
48950
49364
  * @param {string} svgKey - key into SVG_ICONS
48951
49365
  * @param {string} color - border & text color (e.g. '#1890ff')
48952
49366
  * @param {number} left - CSS left in px
@@ -48956,10 +49370,18 @@ var SVG_ICONS = {
48956
49370
  * @returns {HTMLElement}
48957
49371
  ************************************************************/
48958
49372
  function create_floating_icon(svgKey, color, left, top, title, onClick) {
49373
+ const size = floating_icon_size();
48959
49374
  const el = document.createElement("div");
48960
49375
  el.title = title;
49376
+ el.setAttribute("aria-label", title);
48961
49377
  el.innerHTML = SVG_ICONS[svgKey] || "";
48962
- el.style.cssText = "position:absolute;left:" + left + "px;top:" + top + "px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;background:#fff;border:1px solid " + color + ";border-radius:50%;cursor:pointer;pointer-events:all;z-index:11;box-shadow:0 2px 6px rgba(0,0,0,0.15);color:" + color + ";";
49378
+ el.style.cssText = "position:absolute;left:" + left + "px;top:" + top + "px;width:" + size + "px;height:" + size + "px;display:flex;align-items:center;justify-content:center;background:#fff;border:1px solid " + color + ";border-radius:50%;cursor:pointer;pointer-events:all;z-index:11;touch-action:none;box-shadow:0 2px 6px rgba(0,0,0,0.15);color:" + color + ";";
49379
+ const $svg = el.querySelector("svg");
49380
+ if ($svg) {
49381
+ const glyph = Math.round(size * .5);
49382
+ $svg.setAttribute("width", String(glyph));
49383
+ $svg.setAttribute("height", String(glyph));
49384
+ }
48963
49385
  el.addEventListener("click", (e) => {
48964
49386
  e.stopPropagation();
48965
49387
  onClick();
@@ -49005,7 +49427,7 @@ function show_dual_icons(gobj, x, y, gear_title, gear_click, trash_title, trash_
49005
49427
  let priv = gobj.priv;
49006
49428
  const icon_el = create_floating_icon("gear", "#1890ff", x, y, gear_title, gear_click);
49007
49429
  priv.$container.appendChild(icon_el);
49008
- const delete_el = create_floating_icon("trash", "#ff4d4f", x, y + 32, trash_title, trash_click);
49430
+ const delete_el = create_floating_icon("trash", "#ff4d4f", x, y + floating_icon_step(), trash_title, trash_click);
49009
49431
  priv.$container.appendChild(delete_el);
49010
49432
  return {
49011
49433
  icon_el,
@@ -49023,6 +49445,21 @@ function create_popover_base(left, top, className, borderColor, minWidth) {
49023
49445
  popover.addEventListener("pointerdown", (e) => e.stopPropagation());
49024
49446
  return popover;
49025
49447
  }
49448
+ /************************************************************
49449
+ * Extra CSS every control inside a popover carries on a
49450
+ * touch screen.
49451
+ *
49452
+ * A popover is where the graph is TYPED into -- a colour, a
49453
+ * radius, an id -- and its controls were sized by their
49454
+ * padding alone, which lands them at 25-30px. A finger needs
49455
+ * 44, and the number is not ours: it is the floor both Apple
49456
+ * and Google publish, and the one every native control on the
49457
+ * device already meets.
49458
+ ************************************************************/
49459
+ function touch_control_css() {
49460
+ if (!coarse_pointer()) return "";
49461
+ return "min-height:44px;font-size:16px;";
49462
+ }
49026
49463
  function create_form_label(parent, text) {
49027
49464
  let label = document.createElement("label");
49028
49465
  label.textContent = (0, i18next.t)(text);
@@ -49034,7 +49471,7 @@ function create_form_color_input(parent, value, onInput) {
49034
49471
  let input = document.createElement("input");
49035
49472
  input.type = "color";
49036
49473
  input.value = value;
49037
- input.style.cssText = "width:100%;height:30px;padding:0;border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;cursor:pointer;margin-bottom:10px;";
49474
+ input.style.cssText = "width:100%;height:30px;padding:0;border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;cursor:pointer;margin-bottom:10px;" + touch_control_css();
49038
49475
  if (onInput) input.addEventListener("input", onInput);
49039
49476
  parent.appendChild(input);
49040
49477
  return input;
@@ -49045,14 +49482,14 @@ function create_form_number_input(parent, value, min, max, onInput) {
49045
49482
  input.min = String(min);
49046
49483
  input.max = String(max);
49047
49484
  input.value = value;
49048
- input.style.cssText = "width:100%;padding:4px 6px;background:var(--bulma-scheme-main-bis, #fff);color:var(--bulma-text-strong, #333);border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;box-sizing:border-box;margin-bottom:10px;";
49485
+ input.style.cssText = "width:100%;padding:4px 6px;background:var(--bulma-scheme-main-bis, #fff);color:var(--bulma-text-strong, #333);border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;box-sizing:border-box;margin-bottom:10px;" + touch_control_css();
49049
49486
  if (onInput) input.addEventListener("input", onInput);
49050
49487
  parent.appendChild(input);
49051
49488
  return input;
49052
49489
  }
49053
49490
  function create_form_select(parent, options, marginBottom) {
49054
49491
  let select = document.createElement("select");
49055
- select.style.cssText = "width:100%;padding:4px 6px;background:var(--bulma-scheme-main-bis, #fff);color:var(--bulma-text-strong, #333);border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;box-sizing:border-box;margin-bottom:" + (marginBottom || "12px") + ";";
49492
+ select.style.cssText = "width:100%;padding:4px 6px;background:var(--bulma-scheme-main-bis, #fff);color:var(--bulma-text-strong, #333);border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;box-sizing:border-box;margin-bottom:" + (marginBottom || "12px") + ";" + touch_control_css();
49056
49493
  for (let opt of options) {
49057
49494
  let o = document.createElement("option");
49058
49495
  o.value = opt.value;
@@ -49069,7 +49506,7 @@ function create_form_button_row(parent, buttons) {
49069
49506
  for (let { text, style, onClick } of buttons) {
49070
49507
  let btn = document.createElement("button");
49071
49508
  btn.textContent = (0, i18next.t)(text);
49072
- btn.style.cssText = style;
49509
+ btn.style.cssText = style + touch_control_css();
49073
49510
  btn.addEventListener("click", (e) => {
49074
49511
  e.stopPropagation();
49075
49512
  onClick();
@@ -49307,7 +49744,7 @@ function show_resize_handles(gobj) {
49307
49744
  container.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10;";
49308
49745
  const selRect = document.createElement("div");
49309
49746
  container.appendChild(selRect);
49310
- const handle_defs = [
49747
+ let handle_defs = [
49311
49748
  {
49312
49749
  cursor: "nw-resize",
49313
49750
  mx: -1,
@@ -49349,10 +49786,16 @@ function show_resize_handles(gobj) {
49349
49786
  my: 1
49350
49787
  }
49351
49788
  ];
49789
+ const hit = resize_handle_hit();
49790
+ const visual = resize_handle_visual();
49791
+ if (coarse_pointer()) handle_defs = handle_defs.filter((def) => def.mx !== 0 && def.my !== 0);
49352
49792
  const handles = [];
49353
49793
  for (const def of handle_defs) {
49354
49794
  const el = document.createElement("div");
49355
- el.style.cssText = "position:absolute;width:8px;height:8px;background:#fff;border:1px solid #1890ff;cursor:" + def.cursor + ";pointer-events:all;box-sizing:border-box;";
49795
+ el.style.cssText = "position:absolute;width:" + hit + "px;height:" + hit + "px;display:flex;align-items:center;justify-content:center;cursor:" + def.cursor + ";pointer-events:all;touch-action:none;box-sizing:border-box;";
49796
+ const mark = document.createElement("div");
49797
+ mark.style.cssText = "width:" + visual + "px;height:" + visual + "px;background:#fff;border:1px solid #1890ff;pointer-events:none;box-sizing:border-box;";
49798
+ el.appendChild(mark);
49356
49799
  el.addEventListener("pointerdown", (e) => {
49357
49800
  start_node_resize(gobj, e, def.mx, def.my);
49358
49801
  });
@@ -49392,52 +49835,18 @@ function update_resize_handles_position(gobj) {
49392
49835
  }
49393
49836
  function apply_handles_to_rect(gobj, rect) {
49394
49837
  let priv = gobj.priv;
49395
- const HALF = RESIZE_HANDLE_SIZE / 2;
49838
+ const HALF = resize_handle_hit() / 2;
49396
49839
  const left = rect.left;
49397
49840
  const top = rect.top;
49398
49841
  const w = rect.right - rect.left;
49399
49842
  const h = rect.bottom - rect.top;
49400
49843
  const selRect = priv._resize_sel_rect;
49401
49844
  if (selRect) selRect.style.cssText = "position:absolute;left:" + left + "px;top:" + top + "px;width:" + w + "px;height:" + h + "px;border:1px dashed #1890ff;pointer-events:none;box-sizing:border-box;";
49402
- const positions = [
49403
- {
49404
- x: left,
49405
- y: top
49406
- },
49407
- {
49408
- x: left + w / 2,
49409
- y: top
49410
- },
49411
- {
49412
- x: left + w,
49413
- y: top
49414
- },
49415
- {
49416
- x: left,
49417
- y: top + h / 2
49418
- },
49419
- {
49420
- x: left + w,
49421
- y: top + h / 2
49422
- },
49423
- {
49424
- x: left,
49425
- y: top + h
49426
- },
49427
- {
49428
- x: left + w / 2,
49429
- y: top + h
49430
- },
49431
- {
49432
- x: left + w,
49433
- y: top + h
49434
- }
49435
- ];
49436
- for (let i = 0; i < priv._resize_handles.length; i++) {
49437
- const handle = priv._resize_handles[i];
49438
- const p = positions[i];
49439
- handle.el.style.left = p.x - HALF + "px";
49440
- handle.el.style.top = p.y - HALF + "px";
49845
+ for (const handle of priv._resize_handles) {
49846
+ const x = left + (handle.mx + 1) / 2 * w;
49847
+ const y = top + (handle.my + 1) / 2 * h;
49848
+ handle.el.style.left = x - HALF + "px";
49849
+ handle.el.style.top = y - HALF + "px";
49441
49850
  }
49442
49851
  }
49443
49852
  function start_node_resize(gobj, e, mx, my) {
@@ -49513,6 +49922,14 @@ function start_node_resize(gobj, e, mx, my) {
49513
49922
  * Port selection and individual port resizing
49514
49923
  ************************************************************/
49515
49924
  var PORT_HANDLE_SIZE = 8;
49925
+ var PORT_HANDLE_VISUAL_COARSE = 12;
49926
+ var PORT_HANDLE_HIT_COARSE = 32;
49927
+ function port_handle_visual() {
49928
+ return coarse_pointer() ? PORT_HANDLE_VISUAL_COARSE : PORT_HANDLE_SIZE;
49929
+ }
49930
+ function port_handle_hit() {
49931
+ return coarse_pointer() ? PORT_HANDLE_HIT_COARSE : PORT_HANDLE_SIZE;
49932
+ }
49516
49933
  /************************************************************
49517
49934
  * Compute the canvas (world) position of a port on a node.
49518
49935
  * Returns {x, y} in world coordinates.
@@ -49545,6 +49962,26 @@ function get_port_radius(gobj, node_id, port_key) {
49545
49962
  return style.portR || 6;
49546
49963
  }
49547
49964
  /************************************************************
49965
+ * How much wider than the port itself its hit area is,
49966
+ * measured on the SCREEN and converted to world units.
49967
+ *
49968
+ * It used to be a flat `+4` in WORLD units, which is a
49969
+ * different target on every zoom: at the 50% a phone lands on
49970
+ * after fit, a 6-unit port plus 4 is a 5px radius -- one
49971
+ * fifth of a fingertip, and picking a port is the first step
49972
+ * of every link.
49973
+ ************************************************************/
49974
+ function port_hit_slop_world(graph) {
49975
+ let slop = coarse_pointer() ? 22 : 6;
49976
+ let zoom = 1;
49977
+ try {
49978
+ zoom = graph.getZoom() || 1;
49979
+ } catch (e) {
49980
+ zoom = 1;
49981
+ }
49982
+ return slop / zoom;
49983
+ }
49984
+ /************************************************************
49548
49985
  * Detect if a click in canvas coordinates hits a port.
49549
49986
  * Returns the port key string, or null if no port hit.
49550
49987
  ************************************************************/
@@ -49557,6 +49994,7 @@ function detect_port_click(gobj, node_id, canvasX, canvasY) {
49557
49994
  const defaultR = style.portR || 6;
49558
49995
  let best_key = null;
49559
49996
  let best_dist = Infinity;
49997
+ let slop = port_hit_slop_world(graph);
49560
49998
  for (let i = 0; i < ports.length; i++) {
49561
49999
  let pl = ports[i].placement || [.5, .5];
49562
50000
  let px = pos[0] + (pl[0] - .5) * w;
@@ -49565,7 +50003,7 @@ function detect_port_click(gobj, node_id, canvasX, canvasY) {
49565
50003
  let dx = canvasX - px;
49566
50004
  let dy = canvasY - py;
49567
50005
  let dist = Math.sqrt(dx * dx + dy * dy);
49568
- if (dist <= r + 4 && dist < best_dist) {
50006
+ if (dist <= r + slop && dist < best_dist) {
49569
50007
  best_dist = dist;
49570
50008
  best_key = ports[i].key;
49571
50009
  }
@@ -49613,7 +50051,7 @@ function show_link_icon_if_fkey(gobj) {
49613
50051
  let canvasPos = get_port_canvas_position(gobj, priv._selected_node_id, priv._selected_port_key);
49614
50052
  if (!canvasPos) return;
49615
50053
  let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
49616
- let icon = create_floating_icon("link", "#52c41a", vp[0] + 18, vp[1] - 14, (0, i18next.t)("link to hook"), () => {
50054
+ let icon = create_floating_icon("link", "#52c41a", vp[0] + floating_icon_size() / 2 + 4, vp[1] + floating_icon_dy(), (0, i18next.t)("link to hook"), () => {
49617
50055
  if (!priv._linking_mode) enter_linking_mode(gobj);
49618
50056
  });
49619
50057
  icon.addEventListener("pointerdown", (e) => {
@@ -49638,8 +50076,8 @@ function update_link_icon_position(gobj) {
49638
50076
  return;
49639
50077
  }
49640
50078
  let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
49641
- priv._link_icon_el.style.left = vp[0] + 18 + "px";
49642
- priv._link_icon_el.style.top = vp[1] - 14 + "px";
50079
+ priv._link_icon_el.style.left = vp[0] + floating_icon_size() / 2 + 4 + "px";
50080
+ priv._link_icon_el.style.top = vp[1] + floating_icon_dy() + "px";
49643
50081
  } catch (e) {
49644
50082
  hide_link_icon(gobj);
49645
50083
  }
@@ -49907,10 +50345,15 @@ function show_port_resize_handles(gobj) {
49907
50345
  dy: 0
49908
50346
  }
49909
50347
  ];
50348
+ const hit = port_handle_hit();
50349
+ const visual = port_handle_visual();
49910
50350
  const handles = [];
49911
50351
  for (const def of handle_defs) {
49912
50352
  const el = document.createElement("div");
49913
- el.style.cssText = "position:absolute;width:8px;height:8px;background:#fff;border:1px solid #fa8c16;border-radius:50%;cursor:" + def.cursor + ";pointer-events:all;box-sizing:border-box;";
50353
+ el.style.cssText = "position:absolute;width:" + hit + "px;height:" + hit + "px;display:flex;align-items:center;justify-content:center;cursor:" + def.cursor + ";pointer-events:all;touch-action:none;box-sizing:border-box;";
50354
+ const mark = document.createElement("div");
50355
+ mark.style.cssText = "width:" + visual + "px;height:" + visual + "px;background:#fff;border:1px solid #fa8c16;border-radius:50%;pointer-events:none;box-sizing:border-box;";
50356
+ el.appendChild(mark);
49914
50357
  el.addEventListener("pointerdown", (e) => {
49915
50358
  start_port_resize(gobj, e);
49916
50359
  });
@@ -49964,7 +50407,7 @@ function update_port_resize_handles_position(gobj) {
49964
50407
  ************************************************************/
49965
50408
  function apply_port_handles(gobj, cx, cy, vpR) {
49966
50409
  let priv = gobj.priv;
49967
- const HALF = PORT_HANDLE_SIZE / 2;
50410
+ const HALF = port_handle_hit() / 2;
49968
50411
  const ring = priv._port_ring;
49969
50412
  if (ring) {
49970
50413
  const d = vpR * 2;
@@ -50109,7 +50552,7 @@ function show_edge_icon(gobj) {
50109
50552
  if (!priv._selected_edge_id) return;
50110
50553
  let mid = get_edge_viewport_midpoint(gobj, priv._selected_edge_id);
50111
50554
  if (!mid) return;
50112
- let icons = show_dual_icons(gobj, mid.x + 4, mid.y - 14, (0, i18next.t)("edge properties"), () => toggle_edge_popover(gobj), (0, i18next.t)("unlink"), () => request_unlink_edge(gobj));
50555
+ let icons = show_dual_icons(gobj, mid.x + 4, mid.y + floating_icon_dy(), (0, i18next.t)("edge properties"), () => toggle_edge_popover(gobj), (0, i18next.t)("unlink"), () => request_unlink_edge(gobj));
50113
50556
  priv._edge_icon_el = icons.icon_el;
50114
50557
  priv._edge_delete_el = icons.delete_el;
50115
50558
  }
@@ -50127,14 +50570,14 @@ function update_edge_icon_position(gobj) {
50127
50570
  return;
50128
50571
  }
50129
50572
  priv._edge_icon_el.style.left = mid.x + 4 + "px";
50130
- priv._edge_icon_el.style.top = mid.y - 14 + "px";
50573
+ priv._edge_icon_el.style.top = mid.y + floating_icon_dy() + "px";
50131
50574
  if (priv._edge_delete_el) {
50132
50575
  priv._edge_delete_el.style.left = mid.x + 4 + "px";
50133
- priv._edge_delete_el.style.top = mid.y + 18 + "px";
50576
+ priv._edge_delete_el.style.top = mid.y + floating_icon_dy() + floating_icon_step() + "px";
50134
50577
  }
50135
50578
  if (priv._edge_popover_el) {
50136
- priv._edge_popover_el.style.left = mid.x + 36 + "px";
50137
- priv._edge_popover_el.style.top = mid.y - 14 + "px";
50579
+ priv._edge_popover_el.style.left = mid.x + floating_popover_dx() + "px";
50580
+ priv._edge_popover_el.style.top = mid.y + floating_icon_dy() + "px";
50138
50581
  clamp_popover_position(gobj, priv._edge_popover_el);
50139
50582
  }
50140
50583
  } catch (e) {
@@ -50172,7 +50615,7 @@ function show_edge_popover(gobj) {
50172
50615
  });
50173
50616
  let mid = get_edge_viewport_midpoint(gobj, edge_id);
50174
50617
  if (!mid) return;
50175
- const popover = create_popover_base(mid.x + 36, mid.y - 14, "g6-edge-popover", "#d9d9d9", 180);
50618
+ const popover = create_popover_base(mid.x + floating_popover_dx(), mid.y + floating_icon_dy(), "g6-edge-popover", "#d9d9d9", 180);
50176
50619
  function preview_edge() {
50177
50620
  let lw = parseInt(lwInput.value) || 2;
50178
50621
  let color = colorInput.value;
@@ -50259,7 +50702,7 @@ function show_node_icon(gobj) {
50259
50702
  if (!priv._selected_node_id || priv._selected_port_key) return;
50260
50703
  let rect = get_node_viewport_rect(gobj, priv._selected_node_id);
50261
50704
  if (!rect) return;
50262
- let icons = show_dual_icons(gobj, rect.right + 4, rect.top - 14, (0, i18next.t)("node properties"), () => toggle_node_popover(gobj), (0, i18next.t)("delete node"), () => request_delete_node(gobj));
50705
+ let icons = show_dual_icons(gobj, rect.right + 4, rect.top + floating_icon_dy(), (0, i18next.t)("node properties"), () => toggle_node_popover(gobj), (0, i18next.t)("delete node"), () => request_delete_node(gobj));
50263
50706
  priv._node_icon_el = icons.icon_el;
50264
50707
  priv._node_delete_el = icons.delete_el;
50265
50708
  }
@@ -50281,14 +50724,14 @@ function update_node_icon_position(gobj) {
50281
50724
  return;
50282
50725
  }
50283
50726
  priv._node_icon_el.style.left = rect.right + 4 + "px";
50284
- priv._node_icon_el.style.top = rect.top - 14 + "px";
50727
+ priv._node_icon_el.style.top = rect.top + floating_icon_dy() + "px";
50285
50728
  if (priv._node_delete_el) {
50286
50729
  priv._node_delete_el.style.left = rect.right + 4 + "px";
50287
- priv._node_delete_el.style.top = rect.top + 18 + "px";
50730
+ priv._node_delete_el.style.top = rect.top + floating_icon_dy() + floating_icon_step() + "px";
50288
50731
  }
50289
50732
  if (priv._node_popover_el) {
50290
- priv._node_popover_el.style.left = rect.right + 36 + "px";
50291
- priv._node_popover_el.style.top = rect.top - 14 + "px";
50733
+ priv._node_popover_el.style.left = rect.right + floating_popover_dx() + "px";
50734
+ priv._node_popover_el.style.top = rect.top + floating_icon_dy() + "px";
50292
50735
  clamp_popover_position(gobj, priv._node_popover_el);
50293
50736
  }
50294
50737
  } catch (e) {
@@ -50321,7 +50764,7 @@ function show_node_popover(gobj) {
50321
50764
  let origLW = currentLW;
50322
50765
  let rect = get_node_viewport_rect(gobj, node_id);
50323
50766
  if (!rect) return;
50324
- const popover = create_popover_base(rect.right + 36, rect.top - 14, "g6-node-popover", "#d9d9d9", 180);
50767
+ const popover = create_popover_base(rect.right + floating_popover_dx(), rect.top + floating_icon_dy(), "g6-node-popover", "#d9d9d9", 180);
50325
50768
  let node_graph_type = nodeData.data && nodeData.data.desc ? nodeData.data.desc.node_treedb_type : null;
50326
50769
  function preview_node() {
50327
50770
  let fill = fillInput.value;
@@ -50952,7 +51395,7 @@ function show_create_popover(gobj) {
50952
51395
  topicLabel.textContent = (0, i18next.t)("topic") + ":";
50953
51396
  popover.appendChild(topicLabel);
50954
51397
  let topicSelect = document.createElement("select");
50955
- topicSelect.style.cssText = "width:100%;padding:6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;margin-bottom:8px;";
51398
+ topicSelect.style.cssText = "width:100%;padding:6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;margin-bottom:8px;" + touch_control_css();
50956
51399
  for (let i = 0; i < topics.length; i++) {
50957
51400
  let opt = document.createElement("option");
50958
51401
  opt.value = topics[i].topic_name;
@@ -50985,7 +51428,7 @@ function show_create_popover(gobj) {
50985
51428
  let idInput = document.createElement("input");
50986
51429
  idInput.type = "text";
50987
51430
  idInput.placeholder = (0, i18next.t)("node id");
50988
- idInput.style.cssText = "width:100%;padding:6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;margin-bottom:10px;box-sizing:border-box;";
51431
+ idInput.style.cssText = "width:100%;padding:6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;margin-bottom:10px;box-sizing:border-box;" + touch_control_css();
50989
51432
  popover.appendChild(idInput);
50990
51433
  let errorEl = document.createElement("div");
50991
51434
  errorEl.style.cssText = "color:#ff4d4f;font-size:12px;margin-bottom:8px;display:none;";
@@ -51675,6 +52118,11 @@ function ac_resize(gobj, event, kw, src) {
51675
52118
  if (h < 0) h = 80;
51676
52119
  graph_resize(gobj, rect.width, h);
51677
52120
  }
52121
+ let can_collapse = toolbars_can_collapse(gobj);
52122
+ if (can_collapse !== priv._toolbars_could_collapse) {
52123
+ priv._toolbars_could_collapse = can_collapse;
52124
+ update_toolbar(gobj);
52125
+ }
51678
52126
  return 0;
51679
52127
  }
51680
52128
  /************************************************************
@@ -51713,6 +52161,19 @@ function ac_zoom_reset(gobj, event, kw, src) {
51713
52161
  gobj.priv.graph.zoomTo(1);
51714
52162
  return 0;
51715
52163
  }
52164
+ /************************************************************
52165
+ * Fold / unfold the floating toolbars.
52166
+ *
52167
+ * One flag for BOTH: they are one wall over the canvas, and
52168
+ * the button that unfolds them has to be the same button
52169
+ * whichever edge the user reaches for.
52170
+ ************************************************************/
52171
+ function ac_toggle_toolbars(gobj, event, kw, src) {
52172
+ let priv = gobj.priv;
52173
+ priv.toolbar_collapsed = !toolbars_collapsed(gobj);
52174
+ update_toolbar(gobj);
52175
+ return 0;
52176
+ }
51716
52177
  function ac_auto_fit(gobj, event, kw, src) {
51717
52178
  graph_fitview(gobj);
51718
52179
  return 0;
@@ -52097,6 +52558,11 @@ function create_gclass$4(gclass_name) {
52097
52558
  ac_zoom_reset,
52098
52559
  null
52099
52560
  ],
52561
+ [
52562
+ "EV_TOGGLE_TOOLBARS",
52563
+ ac_toggle_toolbars,
52564
+ null
52565
+ ],
52100
52566
  [
52101
52567
  "EV_AUTO_FIT",
52102
52568
  ac_auto_fit,
@@ -52200,6 +52666,7 @@ function create_gclass$4(gclass_name) {
52200
52666
  ["EV_ZOOM_IN", 0],
52201
52667
  ["EV_ZOOM_OUT", 0],
52202
52668
  ["EV_ZOOM_RESET", 0],
52669
+ ["EV_TOGGLE_TOOLBARS", 0],
52203
52670
  ["EV_AUTO_FIT", 0],
52204
52671
  ["EV_ZOOM_SELECTION", 0],
52205
52672
  ["EV_FOCUS_TOPIC", 0],