@yuneta/gobj-ui 7.23.7 → 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;
@@ -9912,24 +10192,17 @@ function destroy_ui$6(gobj) {
9912
10192
  function make_toolbar$3(gobj) {
9913
10193
  let priv = gobj.priv;
9914
10194
  let toolbar_wide = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "wide");
9915
- let left_items = yui_graph_fold_items(gobj, toolbar_wide).concat([[
9916
- "div",
10195
+ let $find_input = (0, _yuneta_gobj_js.createElement2)([
10196
+ "input",
9917
10197
  {
9918
- class: "JSON_GRAPH_FIND control has-icons-left",
9919
- style: "margin-right:.5rem; max-width:12rem; min-width:7rem;"
9920
- },
9921
- [["input", {
9922
10198
  class: "JSON_GRAPH_FIND_INPUT input",
9923
10199
  type: "text",
9924
10200
  placeholder: (0, i18next.t)("search"),
9925
10201
  "data-i18n-placeholder": "search",
9926
10202
  "aria-label": (0, i18next.t)("search"),
9927
10203
  "data-i18n-aria-label": "search"
9928
- }], [
9929
- "span",
9930
- { class: "icon is-left" },
9931
- [["i", { class: "yi-magnifying-glass" }]]
9932
- ]],
10204
+ },
10205
+ [],
9933
10206
  { input: (evt) => {
9934
10207
  evt.stopPropagation();
9935
10208
  let text = evt.target.value.trim();
@@ -9939,7 +10212,21 @@ function make_toolbar$3(gobj) {
9939
10212
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_FIND_NODES", { text }, gobj);
9940
10213
  }, 250);
9941
10214
  } }
9942
- ], [
10215
+ ]);
10216
+ let $find_control = (0, _yuneta_gobj_js.createElement2)([
10217
+ "div",
10218
+ {
10219
+ class: "JSON_GRAPH_FIND control has-icons-left",
10220
+ style: "margin-right:.5rem; max-width:12rem; min-width:7rem;"
10221
+ },
10222
+ [$find_input, [
10223
+ "span",
10224
+ { class: "icon is-left" },
10225
+ [["i", { class: "yi-magnifying-glass" }]]
10226
+ ]]
10227
+ ]);
10228
+ attach_clear($find_control, $find_input);
10229
+ let left_items = yui_graph_fold_items(gobj, toolbar_wide).concat([$find_control, [
9943
10230
  "div",
9944
10231
  {
9945
10232
  class: "JSON_GRAPH_FIND_RESULT is-hidden",
@@ -11240,14 +11527,10 @@ function make_toolbar$2(gobj) {
11240
11527
  [["i", { class: "yi-magnifying-glass" }]]
11241
11528
  ]]
11242
11529
  ]);
11530
+ left_items.push(icon_button(gobj, "yi-chevron-right", "EV_EXPAND_ALL", "expand loaded"), icon_button(gobj, "yi-chevron-right", "EV_COLLAPSE_ALL", "collapse all"));
11243
11531
  attach_clear($search_control, $search_input);
11244
11532
  left_items.push($search_control);
11245
- let right_items = [
11246
- view_mode_switch(gobj),
11247
- icon_button(gobj, "yi-chevron-right", "EV_EXPAND_ALL", "expand loaded"),
11248
- icon_button(gobj, "yi-chevron-right", "EV_COLLAPSE_ALL", "collapse all"),
11249
- icon_button(gobj, "yi-copy", "EV_COPY_ALL", "copy json")
11250
- ];
11533
+ let right_items = [view_mode_switch(gobj), icon_button(gobj, "yi-copy", "EV_COPY_ALL", "copy json")];
11251
11534
  const $toolbar = yui_toolbar({}, [
11252
11535
  [
11253
11536
  "div",
@@ -12417,6 +12700,7 @@ function mt_create$9(gobj) {
12417
12700
  priv.theme_observer = yui_watch_theme(gobj);
12418
12701
  build_ui$8(gobj);
12419
12702
  ensure_drag_canvas_patch();
12703
+ ensure_pinch_zoom_patch();
12420
12704
  if (!__layout_registered__) {
12421
12705
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "gobj-tree-v", GobjTreeVLayout);
12422
12706
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "gobj-tree-h", GobjTreeHLayout);
@@ -47195,6 +47479,20 @@ var ICONS = `<svg xmlns="http://www.w3.org/2000/svg">
47195
47479
  <symbol id="g6-icon-plus" viewBox="0 0 512 512">
47196
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"/>
47197
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>
47198
47496
  <!-- floppy-disk (save) -->
47199
47497
  <symbol id="g6-icon-save" viewBox="0 0 448 512">
47200
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"/>
@@ -47267,6 +47565,7 @@ var YuiToolbar = class extends _antv_g6.Toolbar {
47267
47565
  };
47268
47566
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.PLUGIN, "yui-toolbar", YuiToolbar);
47269
47567
  ensure_drag_canvas_patch();
47568
+ ensure_pinch_zoom_patch();
47270
47569
  /***************************************************************
47271
47570
  * Constants
47272
47571
  ***************************************************************/
@@ -47386,6 +47685,9 @@ var PRIVATE_DATA$4 = {
47386
47685
  _focus_topic: null,
47387
47686
  _focus_ids: [],
47388
47687
  _on_pointerdown_focus: null,
47688
+ _uninstall_long_press: null,
47689
+ toolbar_collapsed: true,
47690
+ _toolbars_could_collapse: null,
47389
47691
  _on_focusout_restore: null,
47390
47692
  _selected_paint_ids: [],
47391
47693
  _pending_focus_topic: null,
@@ -47471,6 +47773,10 @@ function mt_destroy$4(gobj) {
47471
47773
  cancelAnimationFrame(priv._resize_raf);
47472
47774
  priv._resize_raf = 0;
47473
47775
  }
47776
+ if (priv._uninstall_long_press) {
47777
+ priv._uninstall_long_press();
47778
+ priv._uninstall_long_press = null;
47779
+ }
47474
47780
  if (priv.graph) {
47475
47781
  priv.graph.destroy();
47476
47782
  priv.graph = null;
@@ -47685,6 +47991,8 @@ function configure_plugins(gobj) {
47685
47991
  return e.targetType === "node" || e.targetType === "edge";
47686
47992
  }
47687
47993
  });
47994
+ if (priv._uninstall_long_press) priv._uninstall_long_press();
47995
+ priv._uninstall_long_press = install_long_press_contextmenu(priv.graph);
47688
47996
  if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_toolbar")) {
47689
47997
  configure_toolbar(gobj);
47690
47998
  configure_toolbar_edit(gobj);
@@ -47714,6 +48022,55 @@ function update_toolbar(gobj) {
47714
48022
  * flipping now; the fallbacks are the old forced-light values, so
47715
48023
  * a host without Bulma gets exactly what it had.
47716
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
+ }
47717
48074
  function toolbar_style(extra) {
47718
48075
  return Object.assign({
47719
48076
  backgroundColor: "var(--bulma-scheme-main, #f5f5f5)",
@@ -47771,6 +48128,7 @@ function configure_toolbar(gobj) {
47771
48128
  marginLeft: "12px"
47772
48129
  }),
47773
48130
  getItems: () => {
48131
+ if (toolbars_collapsed(gobj)) return [toolbar_fold_item(gobj)];
47774
48132
  let items = [
47775
48133
  {
47776
48134
  id: "g6-icon-zoom-in",
@@ -47826,6 +48184,10 @@ function configure_toolbar(gobj) {
47826
48184
  title: (0, i18next.t)("enter full screen")
47827
48185
  });
47828
48186
  }
48187
+ if (toolbars_can_collapse(gobj)) {
48188
+ items.push({ separator: true });
48189
+ items.push(toolbar_fold_item(gobj));
48190
+ }
47829
48191
  return items;
47830
48192
  },
47831
48193
  onClick: (value) => {
@@ -47851,7 +48213,10 @@ function configure_toolbar(gobj) {
47851
48213
  case "request-fullscreen":
47852
48214
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_REQUEST_FULLSCREEN", {}, gobj);
47853
48215
  break;
47854
- 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);
47855
48220
  }
47856
48221
  }
47857
48222
  });
@@ -47861,7 +48226,7 @@ function configure_toolbar(gobj) {
47861
48226
  * Contains edit-mode buttons: Undo, Redo, Save
47862
48227
  ************************************************************/
47863
48228
  function configure_toolbar_edit(gobj) {
47864
- if (!gobj.priv.edit_mode) {
48229
+ if (!gobj.priv.edit_mode || toolbars_collapsed(gobj)) {
47865
48230
  graph_remove_plugin(gobj, "toolbar-edit");
47866
48231
  return;
47867
48232
  }
@@ -47969,6 +48334,7 @@ function configure_behaviour(gobj) {
47969
48334
  break;
47970
48335
  case "operation":
47971
48336
  priv.edit_mode = false;
48337
+ behaviors = ["drag-canvas", "zoom-canvas"];
47972
48338
  break;
47973
48339
  default: (0, _yuneta_gobj_js.log_error)(`operation mode unknown: ${operation_mode}`);
47974
48340
  }
@@ -48931,6 +49297,14 @@ var ManualLayout = class extends _antv_g6.BaseLayout {
48931
49297
  ************************************************************/
48932
49298
  var RESIZE_HANDLE_SIZE = 8;
48933
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
+ }
48934
49308
  /************************************************************
48935
49309
  * SVG icons for floating action buttons (28×28 circles).
48936
49310
  * Each value is a raw SVG string sized 18×18 in a 24×24 viewBox.
@@ -48943,7 +49317,50 @@ var SVG_ICONS = {
48943
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>"
48944
49318
  };
48945
49319
  /************************************************************
48946
- * 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.
48947
49364
  * @param {string} svgKey - key into SVG_ICONS
48948
49365
  * @param {string} color - border & text color (e.g. '#1890ff')
48949
49366
  * @param {number} left - CSS left in px
@@ -48953,10 +49370,18 @@ var SVG_ICONS = {
48953
49370
  * @returns {HTMLElement}
48954
49371
  ************************************************************/
48955
49372
  function create_floating_icon(svgKey, color, left, top, title, onClick) {
49373
+ const size = floating_icon_size();
48956
49374
  const el = document.createElement("div");
48957
49375
  el.title = title;
49376
+ el.setAttribute("aria-label", title);
48958
49377
  el.innerHTML = SVG_ICONS[svgKey] || "";
48959
- 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
+ }
48960
49385
  el.addEventListener("click", (e) => {
48961
49386
  e.stopPropagation();
48962
49387
  onClick();
@@ -49002,7 +49427,7 @@ function show_dual_icons(gobj, x, y, gear_title, gear_click, trash_title, trash_
49002
49427
  let priv = gobj.priv;
49003
49428
  const icon_el = create_floating_icon("gear", "#1890ff", x, y, gear_title, gear_click);
49004
49429
  priv.$container.appendChild(icon_el);
49005
- 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);
49006
49431
  priv.$container.appendChild(delete_el);
49007
49432
  return {
49008
49433
  icon_el,
@@ -49020,6 +49445,21 @@ function create_popover_base(left, top, className, borderColor, minWidth) {
49020
49445
  popover.addEventListener("pointerdown", (e) => e.stopPropagation());
49021
49446
  return popover;
49022
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
+ }
49023
49463
  function create_form_label(parent, text) {
49024
49464
  let label = document.createElement("label");
49025
49465
  label.textContent = (0, i18next.t)(text);
@@ -49031,7 +49471,7 @@ function create_form_color_input(parent, value, onInput) {
49031
49471
  let input = document.createElement("input");
49032
49472
  input.type = "color";
49033
49473
  input.value = value;
49034
- 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();
49035
49475
  if (onInput) input.addEventListener("input", onInput);
49036
49476
  parent.appendChild(input);
49037
49477
  return input;
@@ -49042,14 +49482,14 @@ function create_form_number_input(parent, value, min, max, onInput) {
49042
49482
  input.min = String(min);
49043
49483
  input.max = String(max);
49044
49484
  input.value = value;
49045
- 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();
49046
49486
  if (onInput) input.addEventListener("input", onInput);
49047
49487
  parent.appendChild(input);
49048
49488
  return input;
49049
49489
  }
49050
49490
  function create_form_select(parent, options, marginBottom) {
49051
49491
  let select = document.createElement("select");
49052
- 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();
49053
49493
  for (let opt of options) {
49054
49494
  let o = document.createElement("option");
49055
49495
  o.value = opt.value;
@@ -49066,7 +49506,7 @@ function create_form_button_row(parent, buttons) {
49066
49506
  for (let { text, style, onClick } of buttons) {
49067
49507
  let btn = document.createElement("button");
49068
49508
  btn.textContent = (0, i18next.t)(text);
49069
- btn.style.cssText = style;
49509
+ btn.style.cssText = style + touch_control_css();
49070
49510
  btn.addEventListener("click", (e) => {
49071
49511
  e.stopPropagation();
49072
49512
  onClick();
@@ -49304,7 +49744,7 @@ function show_resize_handles(gobj) {
49304
49744
  container.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10;";
49305
49745
  const selRect = document.createElement("div");
49306
49746
  container.appendChild(selRect);
49307
- const handle_defs = [
49747
+ let handle_defs = [
49308
49748
  {
49309
49749
  cursor: "nw-resize",
49310
49750
  mx: -1,
@@ -49346,10 +49786,16 @@ function show_resize_handles(gobj) {
49346
49786
  my: 1
49347
49787
  }
49348
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);
49349
49792
  const handles = [];
49350
49793
  for (const def of handle_defs) {
49351
49794
  const el = document.createElement("div");
49352
- 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);
49353
49799
  el.addEventListener("pointerdown", (e) => {
49354
49800
  start_node_resize(gobj, e, def.mx, def.my);
49355
49801
  });
@@ -49389,52 +49835,18 @@ function update_resize_handles_position(gobj) {
49389
49835
  }
49390
49836
  function apply_handles_to_rect(gobj, rect) {
49391
49837
  let priv = gobj.priv;
49392
- const HALF = RESIZE_HANDLE_SIZE / 2;
49838
+ const HALF = resize_handle_hit() / 2;
49393
49839
  const left = rect.left;
49394
49840
  const top = rect.top;
49395
49841
  const w = rect.right - rect.left;
49396
49842
  const h = rect.bottom - rect.top;
49397
49843
  const selRect = priv._resize_sel_rect;
49398
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;";
49399
- const positions = [
49400
- {
49401
- x: left,
49402
- y: top
49403
- },
49404
- {
49405
- x: left + w / 2,
49406
- y: top
49407
- },
49408
- {
49409
- x: left + w,
49410
- y: top
49411
- },
49412
- {
49413
- x: left,
49414
- y: top + h / 2
49415
- },
49416
- {
49417
- x: left + w,
49418
- y: top + h / 2
49419
- },
49420
- {
49421
- x: left,
49422
- y: top + h
49423
- },
49424
- {
49425
- x: left + w / 2,
49426
- y: top + h
49427
- },
49428
- {
49429
- x: left + w,
49430
- y: top + h
49431
- }
49432
- ];
49433
- for (let i = 0; i < priv._resize_handles.length; i++) {
49434
- const handle = priv._resize_handles[i];
49435
- const p = positions[i];
49436
- handle.el.style.left = p.x - HALF + "px";
49437
- 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";
49438
49850
  }
49439
49851
  }
49440
49852
  function start_node_resize(gobj, e, mx, my) {
@@ -49510,6 +49922,14 @@ function start_node_resize(gobj, e, mx, my) {
49510
49922
  * Port selection and individual port resizing
49511
49923
  ************************************************************/
49512
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
+ }
49513
49933
  /************************************************************
49514
49934
  * Compute the canvas (world) position of a port on a node.
49515
49935
  * Returns {x, y} in world coordinates.
@@ -49542,6 +49962,26 @@ function get_port_radius(gobj, node_id, port_key) {
49542
49962
  return style.portR || 6;
49543
49963
  }
49544
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
+ /************************************************************
49545
49985
  * Detect if a click in canvas coordinates hits a port.
49546
49986
  * Returns the port key string, or null if no port hit.
49547
49987
  ************************************************************/
@@ -49554,6 +49994,7 @@ function detect_port_click(gobj, node_id, canvasX, canvasY) {
49554
49994
  const defaultR = style.portR || 6;
49555
49995
  let best_key = null;
49556
49996
  let best_dist = Infinity;
49997
+ let slop = port_hit_slop_world(graph);
49557
49998
  for (let i = 0; i < ports.length; i++) {
49558
49999
  let pl = ports[i].placement || [.5, .5];
49559
50000
  let px = pos[0] + (pl[0] - .5) * w;
@@ -49562,7 +50003,7 @@ function detect_port_click(gobj, node_id, canvasX, canvasY) {
49562
50003
  let dx = canvasX - px;
49563
50004
  let dy = canvasY - py;
49564
50005
  let dist = Math.sqrt(dx * dx + dy * dy);
49565
- if (dist <= r + 4 && dist < best_dist) {
50006
+ if (dist <= r + slop && dist < best_dist) {
49566
50007
  best_dist = dist;
49567
50008
  best_key = ports[i].key;
49568
50009
  }
@@ -49610,7 +50051,7 @@ function show_link_icon_if_fkey(gobj) {
49610
50051
  let canvasPos = get_port_canvas_position(gobj, priv._selected_node_id, priv._selected_port_key);
49611
50052
  if (!canvasPos) return;
49612
50053
  let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
49613
- 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"), () => {
49614
50055
  if (!priv._linking_mode) enter_linking_mode(gobj);
49615
50056
  });
49616
50057
  icon.addEventListener("pointerdown", (e) => {
@@ -49635,8 +50076,8 @@ function update_link_icon_position(gobj) {
49635
50076
  return;
49636
50077
  }
49637
50078
  let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
49638
- priv._link_icon_el.style.left = vp[0] + 18 + "px";
49639
- 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";
49640
50081
  } catch (e) {
49641
50082
  hide_link_icon(gobj);
49642
50083
  }
@@ -49904,10 +50345,15 @@ function show_port_resize_handles(gobj) {
49904
50345
  dy: 0
49905
50346
  }
49906
50347
  ];
50348
+ const hit = port_handle_hit();
50349
+ const visual = port_handle_visual();
49907
50350
  const handles = [];
49908
50351
  for (const def of handle_defs) {
49909
50352
  const el = document.createElement("div");
49910
- 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);
49911
50357
  el.addEventListener("pointerdown", (e) => {
49912
50358
  start_port_resize(gobj, e);
49913
50359
  });
@@ -49961,7 +50407,7 @@ function update_port_resize_handles_position(gobj) {
49961
50407
  ************************************************************/
49962
50408
  function apply_port_handles(gobj, cx, cy, vpR) {
49963
50409
  let priv = gobj.priv;
49964
- const HALF = PORT_HANDLE_SIZE / 2;
50410
+ const HALF = port_handle_hit() / 2;
49965
50411
  const ring = priv._port_ring;
49966
50412
  if (ring) {
49967
50413
  const d = vpR * 2;
@@ -50106,7 +50552,7 @@ function show_edge_icon(gobj) {
50106
50552
  if (!priv._selected_edge_id) return;
50107
50553
  let mid = get_edge_viewport_midpoint(gobj, priv._selected_edge_id);
50108
50554
  if (!mid) return;
50109
- 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));
50110
50556
  priv._edge_icon_el = icons.icon_el;
50111
50557
  priv._edge_delete_el = icons.delete_el;
50112
50558
  }
@@ -50124,14 +50570,14 @@ function update_edge_icon_position(gobj) {
50124
50570
  return;
50125
50571
  }
50126
50572
  priv._edge_icon_el.style.left = mid.x + 4 + "px";
50127
- priv._edge_icon_el.style.top = mid.y - 14 + "px";
50573
+ priv._edge_icon_el.style.top = mid.y + floating_icon_dy() + "px";
50128
50574
  if (priv._edge_delete_el) {
50129
50575
  priv._edge_delete_el.style.left = mid.x + 4 + "px";
50130
- 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";
50131
50577
  }
50132
50578
  if (priv._edge_popover_el) {
50133
- priv._edge_popover_el.style.left = mid.x + 36 + "px";
50134
- 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";
50135
50581
  clamp_popover_position(gobj, priv._edge_popover_el);
50136
50582
  }
50137
50583
  } catch (e) {
@@ -50169,7 +50615,7 @@ function show_edge_popover(gobj) {
50169
50615
  });
50170
50616
  let mid = get_edge_viewport_midpoint(gobj, edge_id);
50171
50617
  if (!mid) return;
50172
- 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);
50173
50619
  function preview_edge() {
50174
50620
  let lw = parseInt(lwInput.value) || 2;
50175
50621
  let color = colorInput.value;
@@ -50256,7 +50702,7 @@ function show_node_icon(gobj) {
50256
50702
  if (!priv._selected_node_id || priv._selected_port_key) return;
50257
50703
  let rect = get_node_viewport_rect(gobj, priv._selected_node_id);
50258
50704
  if (!rect) return;
50259
- 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));
50260
50706
  priv._node_icon_el = icons.icon_el;
50261
50707
  priv._node_delete_el = icons.delete_el;
50262
50708
  }
@@ -50278,14 +50724,14 @@ function update_node_icon_position(gobj) {
50278
50724
  return;
50279
50725
  }
50280
50726
  priv._node_icon_el.style.left = rect.right + 4 + "px";
50281
- priv._node_icon_el.style.top = rect.top - 14 + "px";
50727
+ priv._node_icon_el.style.top = rect.top + floating_icon_dy() + "px";
50282
50728
  if (priv._node_delete_el) {
50283
50729
  priv._node_delete_el.style.left = rect.right + 4 + "px";
50284
- 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";
50285
50731
  }
50286
50732
  if (priv._node_popover_el) {
50287
- priv._node_popover_el.style.left = rect.right + 36 + "px";
50288
- 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";
50289
50735
  clamp_popover_position(gobj, priv._node_popover_el);
50290
50736
  }
50291
50737
  } catch (e) {
@@ -50318,7 +50764,7 @@ function show_node_popover(gobj) {
50318
50764
  let origLW = currentLW;
50319
50765
  let rect = get_node_viewport_rect(gobj, node_id);
50320
50766
  if (!rect) return;
50321
- 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);
50322
50768
  let node_graph_type = nodeData.data && nodeData.data.desc ? nodeData.data.desc.node_treedb_type : null;
50323
50769
  function preview_node() {
50324
50770
  let fill = fillInput.value;
@@ -50949,7 +51395,7 @@ function show_create_popover(gobj) {
50949
51395
  topicLabel.textContent = (0, i18next.t)("topic") + ":";
50950
51396
  popover.appendChild(topicLabel);
50951
51397
  let topicSelect = document.createElement("select");
50952
- 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();
50953
51399
  for (let i = 0; i < topics.length; i++) {
50954
51400
  let opt = document.createElement("option");
50955
51401
  opt.value = topics[i].topic_name;
@@ -50982,7 +51428,7 @@ function show_create_popover(gobj) {
50982
51428
  let idInput = document.createElement("input");
50983
51429
  idInput.type = "text";
50984
51430
  idInput.placeholder = (0, i18next.t)("node id");
50985
- 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();
50986
51432
  popover.appendChild(idInput);
50987
51433
  let errorEl = document.createElement("div");
50988
51434
  errorEl.style.cssText = "color:#ff4d4f;font-size:12px;margin-bottom:8px;display:none;";
@@ -51672,6 +52118,11 @@ function ac_resize(gobj, event, kw, src) {
51672
52118
  if (h < 0) h = 80;
51673
52119
  graph_resize(gobj, rect.width, h);
51674
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
+ }
51675
52126
  return 0;
51676
52127
  }
51677
52128
  /************************************************************
@@ -51710,6 +52161,19 @@ function ac_zoom_reset(gobj, event, kw, src) {
51710
52161
  gobj.priv.graph.zoomTo(1);
51711
52162
  return 0;
51712
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
+ }
51713
52177
  function ac_auto_fit(gobj, event, kw, src) {
51714
52178
  graph_fitview(gobj);
51715
52179
  return 0;
@@ -52094,6 +52558,11 @@ function create_gclass$4(gclass_name) {
52094
52558
  ac_zoom_reset,
52095
52559
  null
52096
52560
  ],
52561
+ [
52562
+ "EV_TOGGLE_TOOLBARS",
52563
+ ac_toggle_toolbars,
52564
+ null
52565
+ ],
52097
52566
  [
52098
52567
  "EV_AUTO_FIT",
52099
52568
  ac_auto_fit,
@@ -52197,6 +52666,7 @@ function create_gclass$4(gclass_name) {
52197
52666
  ["EV_ZOOM_IN", 0],
52198
52667
  ["EV_ZOOM_OUT", 0],
52199
52668
  ["EV_ZOOM_RESET", 0],
52669
+ ["EV_TOGGLE_TOOLBARS", 0],
52200
52670
  ["EV_AUTO_FIT", 0],
52201
52671
  ["EV_ZOOM_SELECTION", 0],
52202
52672
  ["EV_FOCUS_TOPIC", 0],
@@ -52600,24 +53070,17 @@ function make_toolbar(gobj) {
52600
53070
  } }
52601
53071
  ]
52602
53072
  ];
52603
- let center_items = [[
52604
- "div",
53073
+ let $find_input = (0, _yuneta_gobj_js.createElement2)([
53074
+ "input",
52605
53075
  {
52606
- class: "GRAPH_FIND control has-icons-left",
52607
- style: "margin-right:.5rem; max-width:12rem; min-width:7rem;"
52608
- },
52609
- [["input", {
52610
53076
  class: "GRAPH_FIND_INPUT input",
52611
53077
  type: "text",
52612
53078
  placeholder: (0, i18next.t)("search"),
52613
53079
  "data-i18n-placeholder": "search",
52614
53080
  "aria-label": (0, i18next.t)("search"),
52615
53081
  "data-i18n-aria-label": "search"
52616
- }], [
52617
- "span",
52618
- { class: "icon is-left" },
52619
- [["i", { class: "yi-magnifying-glass" }]]
52620
- ]],
53082
+ },
53083
+ [],
52621
53084
  { input: (evt) => {
52622
53085
  evt.stopPropagation();
52623
53086
  let text = evt.target.value.trim();
@@ -52627,22 +53090,20 @@ function make_toolbar(gobj) {
52627
53090
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_FIND_NODES", { text }, gobj);
52628
53091
  }, 250);
52629
53092
  } }
52630
- ], [
53093
+ ]);
53094
+ let $find_control = (0, _yuneta_gobj_js.createElement2)([
52631
53095
  "div",
52632
53096
  {
52633
- class: "GRAPH_FIND_RESULT is-hidden",
52634
- style: "display:flex; align-items:center; gap:.3rem; margin-right:.5rem; font-size:.85rem;"
53097
+ class: "GRAPH_FIND control has-icons-left",
53098
+ style: "margin-right:.5rem; max-width:12rem; min-width:7rem;"
52635
53099
  },
52636
- [[
52637
- "span",
52638
- { class: "GRAPH_FIND_COUNT" },
52639
- ""
52640
- ], [
53100
+ [$find_input, [
52641
53101
  "span",
52642
- { i18n: "matches" },
52643
- "matches"
53102
+ { class: "icon is-left" },
53103
+ [["i", { class: "yi-magnifying-glass" }]]
52644
53104
  ]]
52645
- ]];
53105
+ ]);
53106
+ attach_clear($find_control, $find_input);
52646
53107
  const $toolbar_header = yui_toolbar({}, [
52647
53108
  [
52648
53109
  "div",
@@ -52652,7 +53113,22 @@ function make_toolbar(gobj) {
52652
53113
  [
52653
53114
  "div",
52654
53115
  { class: "yui-horizontal-toolbar-section center" },
52655
- center_items
53116
+ [$find_control, [
53117
+ "div",
53118
+ {
53119
+ class: "GRAPH_FIND_RESULT is-hidden",
53120
+ style: "display:flex; align-items:center; gap:.3rem; margin-right:.5rem; font-size:.85rem;"
53121
+ },
53122
+ [[
53123
+ "span",
53124
+ { class: "GRAPH_FIND_COUNT" },
53125
+ ""
53126
+ ], [
53127
+ "span",
53128
+ { i18n: "matches" },
53129
+ "matches"
53130
+ ]]
53131
+ ]]
52656
53132
  ],
52657
53133
  [
52658
53134
  "div",