@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.
@@ -5,7 +5,7 @@ import { createJSONEditor } from "vanilla-jsoneditor";
5
5
  import { TabulatorFull } from "tabulator-tables";
6
6
  import * as maplibregl from "maplibre-gl";
7
7
  import uPlot from "uplot";
8
- import { BaseLayout, CanvasEvent, Circle, CommonEvent, DragCanvas, EdgeEvent, ExtensionCategory, Graph, HistoryEvent, NodeEvent, Toolbar, register } from "@antv/g6";
8
+ import { BaseLayout, CanvasEvent, Circle, CommonEvent, DragCanvas, EdgeEvent, ExtensionCategory, Graph, HistoryEvent, NodeEvent, Toolbar, ZoomCanvas, register } from "@antv/g6";
9
9
  //#region \0rolldown/runtime.js
10
10
  var __create = Object.create;
11
11
  var __defProp = Object.defineProperty;
@@ -9499,6 +9499,275 @@ function yui_graph_refresh_item(gobj, wide) {
9499
9499
  return camera_button(gobj, "yi-arrows-rotate", "EV_REFRESH", "refresh", wide);
9500
9500
  }
9501
9501
  //#endregion
9502
+ //#region src/g6_touch_gestures.js
9503
+ /***********************************************************************
9504
+ * g6_touch_gestures.js
9505
+ *
9506
+ * The two gestures a G6 graph needs on a touch screen and
9507
+ * does not have: PINCH to zoom, and LONG PRESS to open the
9508
+ * context menu.
9509
+ *
9510
+ * Why this exists
9511
+ * ----------------
9512
+ * G6 v5 draws a graph that reads fine on a phone and cannot
9513
+ * be OPERATED on one. Two gaps, both structural:
9514
+ *
9515
+ * 1. `zoom-canvas` binds the WHEEL and nothing else
9516
+ * (`trigger: []`). There is no wheel on a phone, so the
9517
+ * only zoom is the toolbar's +/- buttons. G6 does ship a
9518
+ * pinch recogniser, and `zoom-canvas` will use it -- with
9519
+ * `trigger: ['pinch']`, which its `bindEvents` treats as
9520
+ * an ALTERNATIVE to the wheel (`if pinch ... else wheel`),
9521
+ * so opting in costs the mouse. Worse, its `PinchHandler`
9522
+ * keeps its instance and its callback list in STATICS: on
9523
+ * a page with two graphs the second one registers its
9524
+ * callback against the FIRST one's emitter, so pinching
9525
+ * graph A zooms both and pinching graph B does nothing.
9526
+ * We recognise the gesture ourselves, per graph.
9527
+ *
9528
+ * 2. The context menu is bound to `contextmenu`, and G6 does
9529
+ * not take that from the DOM: its `BehaviorController`
9530
+ * SYNTHESISES it from `pointerdown` with `button === 2`.
9531
+ * A long press therefore never reaches the plugin on ANY
9532
+ * platform, whatever the browser does with the gesture.
9533
+ * So we synthesise it the same way G6 does, from the same
9534
+ * forwarded event object -- which is why the plugin's
9535
+ * `getItems(e)` receives exactly the shape it expects.
9536
+ *
9537
+ * Both are built on ONE per-graph record of the fingers
9538
+ * on the glass, read from the NATIVE touch events -- see
9539
+ * `touch_state_of` for why the pointer stream, which looks
9540
+ * like the right source, is not. That record is also what
9541
+ * tells `drag-canvas` to stop panning while two fingers are
9542
+ * down (`is_pinching`), the guard G6's own drag-canvas has
9543
+ * and our replacement dropped.
9544
+ *
9545
+ * Copyright (c) 2026, ArtGins.
9546
+ * All Rights Reserved.
9547
+ ***********************************************************************/
9548
+ /***************************************************************
9549
+ * Constants
9550
+ ***************************************************************/
9551
+ var LONG_PRESS_MS = 500;
9552
+ var LONG_PRESS_SLOP = 10;
9553
+ /***************************************************************
9554
+ * Per-graph record of the fingers on the glass
9555
+ ***************************************************************/
9556
+ var __touch_state__ = /* @__PURE__ */ new WeakMap();
9557
+ /************************************************************
9558
+ * The touch record of one graph, installing its listeners
9559
+ * the first time it is asked for.
9560
+ *
9561
+ * Read from the NATIVE touch events, not from G6's
9562
+ * forwarded pointer stream.
9563
+ *
9564
+ * The pointer stream looks like the right source and is
9565
+ * not: @antv/g re-issues pointer ids in the middle of a
9566
+ * two-finger gesture (measured: a pinch that started on
9567
+ * ids 2 and 3 finished on id 1), so anything that keys a
9568
+ * Map on `pointerId` loses one of the two fingers halfway
9569
+ * and reads the gesture as a fraction of what it was.
9570
+ * G6's own `PinchHandler` is built exactly that way.
9571
+ *
9572
+ * `event.touches` needs no bookkeeping at all: it IS the
9573
+ * list of fingers currently down, re-stated on every event.
9574
+ ************************************************************/
9575
+ function touch_state_of(graph) {
9576
+ let state = __touch_state__.get(graph);
9577
+ if (state) return state;
9578
+ state = {
9579
+ count: 0,
9580
+ container: null
9581
+ };
9582
+ __touch_state__.set(graph, state);
9583
+ let container = graph.getCanvas?.()?.getContainer?.();
9584
+ if (!container) return state;
9585
+ state.container = container;
9586
+ let read = (event) => {
9587
+ state.count = event.touches ? event.touches.length : 0;
9588
+ };
9589
+ container.addEventListener("touchstart", read, { passive: true });
9590
+ container.addEventListener("touchmove", read, { passive: true });
9591
+ container.addEventListener("touchend", read, { passive: true });
9592
+ container.addEventListener("touchcancel", read, { passive: true });
9593
+ return state;
9594
+ }
9595
+ /************************************************************
9596
+ * Is a two-finger gesture in progress on this graph?
9597
+ *
9598
+ * Read by our `drag-canvas` replacement: panning and zooming
9599
+ * the same drag at the same time fights the user for the
9600
+ * camera.
9601
+ ************************************************************/
9602
+ function is_pinching(graph) {
9603
+ if (!graph) return false;
9604
+ let state = __touch_state__.get(graph);
9605
+ if (!state) return false;
9606
+ return state.count >= 2;
9607
+ }
9608
+ /***************************
9609
+ * Pinch to zoom
9610
+ ***************************/
9611
+ /************************************************************
9612
+ * `zoom-canvas` that also answers the pinch.
9613
+ *
9614
+ * The base class keeps the wheel; the pinch is added on top
9615
+ * instead of replacing it, so one build serves the mouse and
9616
+ * the finger. The scale is ABSOLUTE against the zoom the
9617
+ * gesture started at -- G6's own pinch feeds `zoom()` an
9618
+ * increment per frame, which drifts away from the fingers
9619
+ * over a long gesture.
9620
+ ************************************************************/
9621
+ var ZoomCanvasPinch = class extends ZoomCanvas {
9622
+ constructor(context, options) {
9623
+ super(context, options);
9624
+ this._pinch_dist0 = 0;
9625
+ this._pinch_zoom0 = 1;
9626
+ this._on_touch_move = null;
9627
+ let { trigger } = this.options;
9628
+ if (Array.isArray(trigger) && trigger.includes(CommonEvent.PINCH)) return;
9629
+ let graph = this.context.graph;
9630
+ let state = touch_state_of(graph);
9631
+ if (!state.container) return;
9632
+ this._on_touch_move = (event) => {
9633
+ let touches = event.touches;
9634
+ if (!touches || touches.length !== 2) {
9635
+ this._pinch_dist0 = 0;
9636
+ return;
9637
+ }
9638
+ let dx = touches[0].clientX - touches[1].clientX;
9639
+ let dy = touches[0].clientY - touches[1].clientY;
9640
+ let dist = Math.sqrt(dx * dx + dy * dy);
9641
+ if (dist <= 0) return;
9642
+ if (this._pinch_dist0 === 0) {
9643
+ this._pinch_dist0 = dist;
9644
+ this._pinch_zoom0 = graph.getZoom();
9645
+ return;
9646
+ }
9647
+ this._pinch_to(dist / this._pinch_dist0, touches, event);
9648
+ };
9649
+ state.container.addEventListener("touchmove", this._on_touch_move, { passive: true });
9650
+ }
9651
+ /************************************************************
9652
+ * Zoom to `scale` times the zoom the pinch started at,
9653
+ * about the midpoint between the two fingers.
9654
+ *
9655
+ * ABSOLUTE against the start of the gesture, where G6's
9656
+ * own pinch feeds `zoom()` an increment per frame: an
9657
+ * increment drifts away from the fingers over a long
9658
+ * gesture, and cannot come back when they close again.
9659
+ ************************************************************/
9660
+ _pinch_to(scale, touches, event) {
9661
+ if (!this.validate(event)) return;
9662
+ let graph = this.context.graph;
9663
+ let state = touch_state_of(graph);
9664
+ let origin = null;
9665
+ let rect = state.container?.getBoundingClientRect();
9666
+ if (rect) origin = [(touches[0].clientX + touches[1].clientX) / 2 - rect.left, (touches[0].clientY + touches[1].clientY) / 2 - rect.top];
9667
+ graph.zoomTo(this._pinch_zoom0 * scale, false, origin);
9668
+ this.options.onFinish?.();
9669
+ }
9670
+ destroy() {
9671
+ if (this._on_touch_move) {
9672
+ __touch_state__.get(this.context.graph)?.container?.removeEventListener("touchmove", this._on_touch_move);
9673
+ this._on_touch_move = null;
9674
+ }
9675
+ super.destroy();
9676
+ }
9677
+ };
9678
+ /************************************************************
9679
+ * Register once, over the built-in `zoom-canvas`, so every
9680
+ * graph (tree, json, treedb, editor) pinches without
9681
+ * touching its behaviors list.
9682
+ ************************************************************/
9683
+ var __zoom_canvas_patched__ = false;
9684
+ function ensure_pinch_zoom_patch() {
9685
+ if (__zoom_canvas_patched__) return;
9686
+ register(ExtensionCategory.BEHAVIOR, "zoom-canvas", ZoomCanvasPinch);
9687
+ __zoom_canvas_patched__ = true;
9688
+ }
9689
+ /***************************
9690
+ * Long press
9691
+ ***************************/
9692
+ /************************************************************
9693
+ * Make a long press open the context menu.
9694
+ *
9695
+ * Returns the function that uninstalls it.
9696
+ *
9697
+ * The menu the press opens is whatever the `contextmenu`
9698
+ * plugin builds: we re-emit G6's own forwarded event under
9699
+ * the name the plugin listens for, so `getItems(e)` and
9700
+ * `enable(e)` see the same object a right-click would give
9701
+ * them -- same `target`, same `targetType`, same `client`.
9702
+ ************************************************************/
9703
+ function install_long_press_contextmenu(graph) {
9704
+ if (!graph) return () => {};
9705
+ let state = touch_state_of(graph);
9706
+ let pending = null;
9707
+ let cancel = () => {
9708
+ if (pending) {
9709
+ clearTimeout(pending.timer);
9710
+ pending = null;
9711
+ }
9712
+ };
9713
+ let fire = (event) => {
9714
+ pending = null;
9715
+ let swallow = (evt) => {
9716
+ evt.stopPropagation();
9717
+ document.removeEventListener("click", swallow, true);
9718
+ };
9719
+ document.addEventListener("click", swallow, true);
9720
+ setTimeout(() => {
9721
+ document.removeEventListener("click", swallow, true);
9722
+ }, 1e3);
9723
+ let container = graph.getCanvas?.()?.getContainer?.();
9724
+ if (container) container.addEventListener("contextmenu", (evt) => {
9725
+ evt.preventDefault();
9726
+ }, {
9727
+ once: true,
9728
+ capture: true
9729
+ });
9730
+ graph.emit(`${event.targetType}:${CommonEvent.CONTEXT_MENU}`, event);
9731
+ graph.emit(CommonEvent.CONTEXT_MENU, event);
9732
+ };
9733
+ let on_down = (event) => {
9734
+ cancel();
9735
+ if (event.pointerType !== "touch") return;
9736
+ if (state.count > 1) return;
9737
+ let x = event.client?.x;
9738
+ let y = event.client?.y;
9739
+ if (x === void 0 || y === void 0) return;
9740
+ let frozen = Object.assign({}, event);
9741
+ pending = {
9742
+ x,
9743
+ y,
9744
+ timer: setTimeout(() => {
9745
+ fire(frozen);
9746
+ }, LONG_PRESS_MS)
9747
+ };
9748
+ };
9749
+ let on_move = (event) => {
9750
+ if (!pending) return;
9751
+ if (state.count > 1) {
9752
+ cancel();
9753
+ return;
9754
+ }
9755
+ let x = event.client?.x;
9756
+ let y = event.client?.y;
9757
+ if (x === void 0 || y === void 0) return;
9758
+ if (Math.abs(x - pending.x) > LONG_PRESS_SLOP || Math.abs(y - pending.y) > LONG_PRESS_SLOP) cancel();
9759
+ };
9760
+ graph.on(CommonEvent.POINTER_DOWN, on_down);
9761
+ graph.on(CommonEvent.POINTER_MOVE, on_move);
9762
+ graph.on(CommonEvent.POINTER_UP, cancel);
9763
+ return () => {
9764
+ cancel();
9765
+ graph.off(CommonEvent.POINTER_DOWN, on_down);
9766
+ graph.off(CommonEvent.POINTER_MOVE, on_move);
9767
+ graph.off(CommonEvent.POINTER_UP, cancel);
9768
+ };
9769
+ }
9770
+ //#endregion
9502
9771
  //#region src/g6_drag_canvas_touch.js
9503
9772
  /***********************************************************************
9504
9773
  * g6_drag_canvas_touch.js
@@ -9543,6 +9812,12 @@ function yui_graph_refresh_item(gobj, wide) {
9543
9812
  * every graph (tree, json, treedb, editor) is fixed without
9544
9813
  * touching each consumer's behaviors list.
9545
9814
  *
9815
+ * It also has to keep the ONE guard the base class has and
9816
+ * this replacement dropped: G6's `onDrag` refuses to pan
9817
+ * while a pinch is in progress. See `is_pinching` in
9818
+ * `g6_touch_gestures.js`, which is where the pinch that
9819
+ * matters is now recognised.
9820
+ *
9546
9821
  * Copyright (c) 2026, ArtGins.
9547
9822
  * All Rights Reserved.
9548
9823
  ***********************************************************************/
@@ -9558,6 +9833,10 @@ var DragCanvasTouch = class extends DragCanvas {
9558
9833
  this._lastVP = null;
9559
9834
  return;
9560
9835
  }
9836
+ if (is_pinching(this.context.graph)) {
9837
+ this._lastVP = null;
9838
+ return;
9839
+ }
9561
9840
  let vp = event.viewport;
9562
9841
  if (!vp || typeof vp.x !== "number" || typeof vp.y !== "number") return;
9563
9842
  if (this._lastVP === null) {
@@ -9787,6 +10066,7 @@ function mt_create$11(gobj) {
9787
10066
  priv.theme_observer = yui_watch_theme(gobj);
9788
10067
  build_ui$10(gobj);
9789
10068
  ensure_drag_canvas_patch();
10069
+ ensure_pinch_zoom_patch();
9790
10070
  if (!__layout_registered__$1) {
9791
10071
  register(ExtensionCategory.LAYOUT, "json-tree-layout", JsonTreeLayout);
9792
10072
  __layout_registered__$1 = true;
@@ -9907,24 +10187,17 @@ function destroy_ui$6(gobj) {
9907
10187
  function make_toolbar$3(gobj) {
9908
10188
  let priv = gobj.priv;
9909
10189
  let toolbar_wide = gobj_read_attr(gobj, "wide");
9910
- let left_items = yui_graph_fold_items(gobj, toolbar_wide).concat([[
9911
- "div",
10190
+ let $find_input = createElement2([
10191
+ "input",
9912
10192
  {
9913
- class: "JSON_GRAPH_FIND control has-icons-left",
9914
- style: "margin-right:.5rem; max-width:12rem; min-width:7rem;"
9915
- },
9916
- [["input", {
9917
10193
  class: "JSON_GRAPH_FIND_INPUT input",
9918
10194
  type: "text",
9919
10195
  placeholder: t("search"),
9920
10196
  "data-i18n-placeholder": "search",
9921
10197
  "aria-label": t("search"),
9922
10198
  "data-i18n-aria-label": "search"
9923
- }], [
9924
- "span",
9925
- { class: "icon is-left" },
9926
- [["i", { class: "yi-magnifying-glass" }]]
9927
- ]],
10199
+ },
10200
+ [],
9928
10201
  { input: (evt) => {
9929
10202
  evt.stopPropagation();
9930
10203
  let text = evt.target.value.trim();
@@ -9934,7 +10207,21 @@ function make_toolbar$3(gobj) {
9934
10207
  gobj_send_event(gobj, "EV_FIND_NODES", { text }, gobj);
9935
10208
  }, 250);
9936
10209
  } }
9937
- ], [
10210
+ ]);
10211
+ let $find_control = createElement2([
10212
+ "div",
10213
+ {
10214
+ class: "JSON_GRAPH_FIND control has-icons-left",
10215
+ style: "margin-right:.5rem; max-width:12rem; min-width:7rem;"
10216
+ },
10217
+ [$find_input, [
10218
+ "span",
10219
+ { class: "icon is-left" },
10220
+ [["i", { class: "yi-magnifying-glass" }]]
10221
+ ]]
10222
+ ]);
10223
+ attach_clear($find_control, $find_input);
10224
+ let left_items = yui_graph_fold_items(gobj, toolbar_wide).concat([$find_control, [
9938
10225
  "div",
9939
10226
  {
9940
10227
  class: "JSON_GRAPH_FIND_RESULT is-hidden",
@@ -11235,14 +11522,10 @@ function make_toolbar$2(gobj) {
11235
11522
  [["i", { class: "yi-magnifying-glass" }]]
11236
11523
  ]]
11237
11524
  ]);
11525
+ 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"));
11238
11526
  attach_clear($search_control, $search_input);
11239
11527
  left_items.push($search_control);
11240
- let right_items = [
11241
- view_mode_switch(gobj),
11242
- icon_button(gobj, "yi-chevron-right", "EV_EXPAND_ALL", "expand loaded"),
11243
- icon_button(gobj, "yi-chevron-right", "EV_COLLAPSE_ALL", "collapse all"),
11244
- icon_button(gobj, "yi-copy", "EV_COPY_ALL", "copy json")
11245
- ];
11528
+ let right_items = [view_mode_switch(gobj), icon_button(gobj, "yi-copy", "EV_COPY_ALL", "copy json")];
11246
11529
  const $toolbar = yui_toolbar({}, [
11247
11530
  [
11248
11531
  "div",
@@ -12412,6 +12695,7 @@ function mt_create$9(gobj) {
12412
12695
  priv.theme_observer = yui_watch_theme(gobj);
12413
12696
  build_ui$8(gobj);
12414
12697
  ensure_drag_canvas_patch();
12698
+ ensure_pinch_zoom_patch();
12415
12699
  if (!__layout_registered__) {
12416
12700
  register(ExtensionCategory.LAYOUT, "gobj-tree-v", GobjTreeVLayout);
12417
12701
  register(ExtensionCategory.LAYOUT, "gobj-tree-h", GobjTreeHLayout);
@@ -47182,6 +47466,20 @@ var ICONS = `<svg xmlns="http://www.w3.org/2000/svg">
47182
47466
  <symbol id="g6-icon-plus" viewBox="0 0 512 512">
47183
47467
  <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"/>
47184
47468
  </symbol>
47469
+ <!-- ellipsis-vertical (unfold the floating toolbar).
47470
+ The strip it stands for is a vertical stack, and a vertical
47471
+ ellipsis is what "there is more of this here" is drawn as
47472
+ everywhere else. -->
47473
+ <symbol id="g6-icon-toolbar-show" viewBox="0 0 128 512">
47474
+ <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"/>
47475
+ </symbol>
47476
+ <!-- xmark (fold the floating toolbar away).
47477
+ Not a chevron: the two toolbars sit on OPPOSITE edges, so a
47478
+ direction would point the wrong way on one of them. What the
47479
+ button does is close an overlay, and that is an x. -->
47480
+ <symbol id="g6-icon-toolbar-hide" viewBox="0 0 384 512">
47481
+ <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"/>
47482
+ </symbol>
47185
47483
  <!-- floppy-disk (save) -->
47186
47484
  <symbol id="g6-icon-save" viewBox="0 0 448 512">
47187
47485
  <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"/>
@@ -47254,6 +47552,7 @@ var YuiToolbar = class extends Toolbar {
47254
47552
  };
47255
47553
  register(ExtensionCategory.PLUGIN, "yui-toolbar", YuiToolbar);
47256
47554
  ensure_drag_canvas_patch();
47555
+ ensure_pinch_zoom_patch();
47257
47556
  /***************************************************************
47258
47557
  * Constants
47259
47558
  ***************************************************************/
@@ -47373,6 +47672,9 @@ var PRIVATE_DATA$4 = {
47373
47672
  _focus_topic: null,
47374
47673
  _focus_ids: [],
47375
47674
  _on_pointerdown_focus: null,
47675
+ _uninstall_long_press: null,
47676
+ toolbar_collapsed: true,
47677
+ _toolbars_could_collapse: null,
47376
47678
  _on_focusout_restore: null,
47377
47679
  _selected_paint_ids: [],
47378
47680
  _pending_focus_topic: null,
@@ -47458,6 +47760,10 @@ function mt_destroy$4(gobj) {
47458
47760
  cancelAnimationFrame(priv._resize_raf);
47459
47761
  priv._resize_raf = 0;
47460
47762
  }
47763
+ if (priv._uninstall_long_press) {
47764
+ priv._uninstall_long_press();
47765
+ priv._uninstall_long_press = null;
47766
+ }
47461
47767
  if (priv.graph) {
47462
47768
  priv.graph.destroy();
47463
47769
  priv.graph = null;
@@ -47672,6 +47978,8 @@ function configure_plugins(gobj) {
47672
47978
  return e.targetType === "node" || e.targetType === "edge";
47673
47979
  }
47674
47980
  });
47981
+ if (priv._uninstall_long_press) priv._uninstall_long_press();
47982
+ priv._uninstall_long_press = install_long_press_contextmenu(priv.graph);
47675
47983
  if (gobj_read_bool_attr(gobj, "with_toolbar")) {
47676
47984
  configure_toolbar(gobj);
47677
47985
  configure_toolbar_edit(gobj);
@@ -47701,6 +48009,55 @@ function update_toolbar(gobj) {
47701
48009
  * flipping now; the fallbacks are the old forced-light values, so
47702
48010
  * a host without Bulma gets exactly what it had.
47703
48011
  ************************************************************/
48012
+ /************************************************************
48013
+ * Is the canvas too narrow to give both floating toolbars a
48014
+ * strip of it?
48015
+ *
48016
+ * They are drawn INSIDE the canvas, one on each edge. On a
48017
+ * desktop that is a corner; on a 356px-wide phone canvas the
48018
+ * two of them take a third of the drawing area and stand on
48019
+ * top of the nodes -- and a node under a toolbar cannot be
48020
+ * read, tapped or dragged out from under it.
48021
+ *
48022
+ * Measured on the CONTAINER, not on the window: the same
48023
+ * graph is a full page in one app and a card in a column in
48024
+ * another, and it is the box it actually got that decides.
48025
+ ************************************************************/
48026
+ var TOOLBAR_COLLAPSE_WIDTH = 480;
48027
+ function toolbars_can_collapse(gobj) {
48028
+ let $container = gobj.priv.$container;
48029
+ if (!$container) return false;
48030
+ let width = $container.getBoundingClientRect().width;
48031
+ if (!width) return false;
48032
+ return width < TOOLBAR_COLLAPSE_WIDTH;
48033
+ }
48034
+ /************************************************************
48035
+ * Are they folded RIGHT NOW? Wide enough and the question
48036
+ * does not arise -- the desktop toolbar never grows a button
48037
+ * it does not need.
48038
+ ************************************************************/
48039
+ function toolbars_collapsed(gobj) {
48040
+ if (!toolbars_can_collapse(gobj)) return false;
48041
+ return gobj.priv.toolbar_collapsed !== false;
48042
+ }
48043
+ /************************************************************
48044
+ * The one item a folded toolbar shows, and the one an
48045
+ * unfolded narrow toolbar adds to fold itself again.
48046
+ ************************************************************/
48047
+ function toolbar_fold_item(gobj) {
48048
+ if (toolbars_collapsed(gobj)) return {
48049
+ id: "g6-icon-toolbar-show",
48050
+ value: "toolbar-toggle",
48051
+ className: "EV_TOGGLE_TOOLBARS",
48052
+ title: t("show toolbar")
48053
+ };
48054
+ return {
48055
+ id: "g6-icon-toolbar-hide",
48056
+ value: "toolbar-toggle",
48057
+ className: "EV_TOGGLE_TOOLBARS",
48058
+ title: t("hide toolbar")
48059
+ };
48060
+ }
47704
48061
  function toolbar_style(extra) {
47705
48062
  return Object.assign({
47706
48063
  backgroundColor: "var(--bulma-scheme-main, #f5f5f5)",
@@ -47758,6 +48115,7 @@ function configure_toolbar(gobj) {
47758
48115
  marginLeft: "12px"
47759
48116
  }),
47760
48117
  getItems: () => {
48118
+ if (toolbars_collapsed(gobj)) return [toolbar_fold_item(gobj)];
47761
48119
  let items = [
47762
48120
  {
47763
48121
  id: "g6-icon-zoom-in",
@@ -47813,6 +48171,10 @@ function configure_toolbar(gobj) {
47813
48171
  title: t("enter full screen")
47814
48172
  });
47815
48173
  }
48174
+ if (toolbars_can_collapse(gobj)) {
48175
+ items.push({ separator: true });
48176
+ items.push(toolbar_fold_item(gobj));
48177
+ }
47816
48178
  return items;
47817
48179
  },
47818
48180
  onClick: (value) => {
@@ -47838,7 +48200,10 @@ function configure_toolbar(gobj) {
47838
48200
  case "request-fullscreen":
47839
48201
  gobj_send_event(gobj, "EV_REQUEST_FULLSCREEN", {}, gobj);
47840
48202
  break;
47841
- case "exit-fullscreen": gobj_send_event(gobj, "EV_EXIT_FULLSCREEN", {}, gobj);
48203
+ case "exit-fullscreen":
48204
+ gobj_send_event(gobj, "EV_EXIT_FULLSCREEN", {}, gobj);
48205
+ break;
48206
+ case "toolbar-toggle": gobj_send_event(gobj, "EV_TOGGLE_TOOLBARS", {}, gobj);
47842
48207
  }
47843
48208
  }
47844
48209
  });
@@ -47848,7 +48213,7 @@ function configure_toolbar(gobj) {
47848
48213
  * Contains edit-mode buttons: Undo, Redo, Save
47849
48214
  ************************************************************/
47850
48215
  function configure_toolbar_edit(gobj) {
47851
- if (!gobj.priv.edit_mode) {
48216
+ if (!gobj.priv.edit_mode || toolbars_collapsed(gobj)) {
47852
48217
  graph_remove_plugin(gobj, "toolbar-edit");
47853
48218
  return;
47854
48219
  }
@@ -47956,6 +48321,7 @@ function configure_behaviour(gobj) {
47956
48321
  break;
47957
48322
  case "operation":
47958
48323
  priv.edit_mode = false;
48324
+ behaviors = ["drag-canvas", "zoom-canvas"];
47959
48325
  break;
47960
48326
  default: log_error(`operation mode unknown: ${operation_mode}`);
47961
48327
  }
@@ -48918,6 +49284,14 @@ var ManualLayout = class extends BaseLayout {
48918
49284
  ************************************************************/
48919
49285
  var RESIZE_HANDLE_SIZE = 8;
48920
49286
  var RESIZE_MIN_VP = 20;
49287
+ var RESIZE_HANDLE_VISUAL_COARSE = 14;
49288
+ var RESIZE_HANDLE_HIT_COARSE = 44;
49289
+ function resize_handle_visual() {
49290
+ return coarse_pointer() ? RESIZE_HANDLE_VISUAL_COARSE : RESIZE_HANDLE_SIZE;
49291
+ }
49292
+ function resize_handle_hit() {
49293
+ return coarse_pointer() ? RESIZE_HANDLE_HIT_COARSE : RESIZE_HANDLE_SIZE;
49294
+ }
48921
49295
  /************************************************************
48922
49296
  * SVG icons for floating action buttons (28×28 circles).
48923
49297
  * Each value is a raw SVG string sized 18×18 in a 24×24 viewBox.
@@ -48930,7 +49304,50 @@ var SVG_ICONS = {
48930
49304
  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>"
48931
49305
  };
48932
49306
  /************************************************************
48933
- * Create a 28×28 floating circular icon button.
49307
+ * Is the primary pointer a FINGER?
49308
+ *
49309
+ * Everything this gclass draws over the canvas is sized from
49310
+ * this answer. A mouse is a pixel and can hit an 8px square;
49311
+ * a fingertip covers about 40, and the overlay it lands on
49312
+ * decides between "open the properties" and "delete the node".
49313
+ *
49314
+ * Asked per call, not cached: a tablet with a keyboard dock
49315
+ * changes the answer without reloading the page.
49316
+ ************************************************************/
49317
+ function coarse_pointer() {
49318
+ if (typeof window.matchMedia !== "function") return false;
49319
+ try {
49320
+ return window.matchMedia("(pointer: coarse)").matches === true;
49321
+ } catch (e) {
49322
+ return false;
49323
+ }
49324
+ }
49325
+ /************************************************************
49326
+ * The size of the round floating buttons (properties,
49327
+ * delete, link), and the GAP between two of them.
49328
+ *
49329
+ * The gap matters as much as the size: `node properties` and
49330
+ * `delete node` are drawn one under the other, and at 28px
49331
+ * with 4px between them a fingertip covers both -- with the
49332
+ * destructive one underneath.
49333
+ ************************************************************/
49334
+ function floating_icon_size() {
49335
+ return coarse_pointer() ? 44 : 28;
49336
+ }
49337
+ function floating_icon_gap() {
49338
+ return coarse_pointer() ? 12 : 4;
49339
+ }
49340
+ function floating_icon_dy() {
49341
+ return -floating_icon_size() / 2;
49342
+ }
49343
+ function floating_icon_step() {
49344
+ return floating_icon_size() + floating_icon_gap();
49345
+ }
49346
+ function floating_popover_dx() {
49347
+ return 4 + floating_icon_size() + 4;
49348
+ }
49349
+ /************************************************************
49350
+ * Create a floating circular icon button.
48934
49351
  * @param {string} svgKey - key into SVG_ICONS
48935
49352
  * @param {string} color - border & text color (e.g. '#1890ff')
48936
49353
  * @param {number} left - CSS left in px
@@ -48940,10 +49357,18 @@ var SVG_ICONS = {
48940
49357
  * @returns {HTMLElement}
48941
49358
  ************************************************************/
48942
49359
  function create_floating_icon(svgKey, color, left, top, title, onClick) {
49360
+ const size = floating_icon_size();
48943
49361
  const el = document.createElement("div");
48944
49362
  el.title = title;
49363
+ el.setAttribute("aria-label", title);
48945
49364
  el.innerHTML = SVG_ICONS[svgKey] || "";
48946
- 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 + ";";
49365
+ 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 + ";";
49366
+ const $svg = el.querySelector("svg");
49367
+ if ($svg) {
49368
+ const glyph = Math.round(size * .5);
49369
+ $svg.setAttribute("width", String(glyph));
49370
+ $svg.setAttribute("height", String(glyph));
49371
+ }
48947
49372
  el.addEventListener("click", (e) => {
48948
49373
  e.stopPropagation();
48949
49374
  onClick();
@@ -48989,7 +49414,7 @@ function show_dual_icons(gobj, x, y, gear_title, gear_click, trash_title, trash_
48989
49414
  let priv = gobj.priv;
48990
49415
  const icon_el = create_floating_icon("gear", "#1890ff", x, y, gear_title, gear_click);
48991
49416
  priv.$container.appendChild(icon_el);
48992
- const delete_el = create_floating_icon("trash", "#ff4d4f", x, y + 32, trash_title, trash_click);
49417
+ const delete_el = create_floating_icon("trash", "#ff4d4f", x, y + floating_icon_step(), trash_title, trash_click);
48993
49418
  priv.$container.appendChild(delete_el);
48994
49419
  return {
48995
49420
  icon_el,
@@ -49007,6 +49432,21 @@ function create_popover_base(left, top, className, borderColor, minWidth) {
49007
49432
  popover.addEventListener("pointerdown", (e) => e.stopPropagation());
49008
49433
  return popover;
49009
49434
  }
49435
+ /************************************************************
49436
+ * Extra CSS every control inside a popover carries on a
49437
+ * touch screen.
49438
+ *
49439
+ * A popover is where the graph is TYPED into -- a colour, a
49440
+ * radius, an id -- and its controls were sized by their
49441
+ * padding alone, which lands them at 25-30px. A finger needs
49442
+ * 44, and the number is not ours: it is the floor both Apple
49443
+ * and Google publish, and the one every native control on the
49444
+ * device already meets.
49445
+ ************************************************************/
49446
+ function touch_control_css() {
49447
+ if (!coarse_pointer()) return "";
49448
+ return "min-height:44px;font-size:16px;";
49449
+ }
49010
49450
  function create_form_label(parent, text) {
49011
49451
  let label = document.createElement("label");
49012
49452
  label.textContent = t(text);
@@ -49018,7 +49458,7 @@ function create_form_color_input(parent, value, onInput) {
49018
49458
  let input = document.createElement("input");
49019
49459
  input.type = "color";
49020
49460
  input.value = value;
49021
- 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;";
49461
+ 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();
49022
49462
  if (onInput) input.addEventListener("input", onInput);
49023
49463
  parent.appendChild(input);
49024
49464
  return input;
@@ -49029,14 +49469,14 @@ function create_form_number_input(parent, value, min, max, onInput) {
49029
49469
  input.min = String(min);
49030
49470
  input.max = String(max);
49031
49471
  input.value = value;
49032
- 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;";
49472
+ 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();
49033
49473
  if (onInput) input.addEventListener("input", onInput);
49034
49474
  parent.appendChild(input);
49035
49475
  return input;
49036
49476
  }
49037
49477
  function create_form_select(parent, options, marginBottom) {
49038
49478
  let select = document.createElement("select");
49039
- 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") + ";";
49479
+ 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();
49040
49480
  for (let opt of options) {
49041
49481
  let o = document.createElement("option");
49042
49482
  o.value = opt.value;
@@ -49053,7 +49493,7 @@ function create_form_button_row(parent, buttons) {
49053
49493
  for (let { text, style, onClick } of buttons) {
49054
49494
  let btn = document.createElement("button");
49055
49495
  btn.textContent = t(text);
49056
- btn.style.cssText = style;
49496
+ btn.style.cssText = style + touch_control_css();
49057
49497
  btn.addEventListener("click", (e) => {
49058
49498
  e.stopPropagation();
49059
49499
  onClick();
@@ -49291,7 +49731,7 @@ function show_resize_handles(gobj) {
49291
49731
  container.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10;";
49292
49732
  const selRect = document.createElement("div");
49293
49733
  container.appendChild(selRect);
49294
- const handle_defs = [
49734
+ let handle_defs = [
49295
49735
  {
49296
49736
  cursor: "nw-resize",
49297
49737
  mx: -1,
@@ -49333,10 +49773,16 @@ function show_resize_handles(gobj) {
49333
49773
  my: 1
49334
49774
  }
49335
49775
  ];
49776
+ const hit = resize_handle_hit();
49777
+ const visual = resize_handle_visual();
49778
+ if (coarse_pointer()) handle_defs = handle_defs.filter((def) => def.mx !== 0 && def.my !== 0);
49336
49779
  const handles = [];
49337
49780
  for (const def of handle_defs) {
49338
49781
  const el = document.createElement("div");
49339
- 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;";
49782
+ 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;";
49783
+ const mark = document.createElement("div");
49784
+ mark.style.cssText = "width:" + visual + "px;height:" + visual + "px;background:#fff;border:1px solid #1890ff;pointer-events:none;box-sizing:border-box;";
49785
+ el.appendChild(mark);
49340
49786
  el.addEventListener("pointerdown", (e) => {
49341
49787
  start_node_resize(gobj, e, def.mx, def.my);
49342
49788
  });
@@ -49376,52 +49822,18 @@ function update_resize_handles_position(gobj) {
49376
49822
  }
49377
49823
  function apply_handles_to_rect(gobj, rect) {
49378
49824
  let priv = gobj.priv;
49379
- const HALF = RESIZE_HANDLE_SIZE / 2;
49825
+ const HALF = resize_handle_hit() / 2;
49380
49826
  const left = rect.left;
49381
49827
  const top = rect.top;
49382
49828
  const w = rect.right - rect.left;
49383
49829
  const h = rect.bottom - rect.top;
49384
49830
  const selRect = priv._resize_sel_rect;
49385
49831
  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;";
49386
- const positions = [
49387
- {
49388
- x: left,
49389
- y: top
49390
- },
49391
- {
49392
- x: left + w / 2,
49393
- y: top
49394
- },
49395
- {
49396
- x: left + w,
49397
- y: top
49398
- },
49399
- {
49400
- x: left,
49401
- y: top + h / 2
49402
- },
49403
- {
49404
- x: left + w,
49405
- y: top + h / 2
49406
- },
49407
- {
49408
- x: left,
49409
- y: top + h
49410
- },
49411
- {
49412
- x: left + w / 2,
49413
- y: top + h
49414
- },
49415
- {
49416
- x: left + w,
49417
- y: top + h
49418
- }
49419
- ];
49420
- for (let i = 0; i < priv._resize_handles.length; i++) {
49421
- const handle = priv._resize_handles[i];
49422
- const p = positions[i];
49423
- handle.el.style.left = p.x - HALF + "px";
49424
- handle.el.style.top = p.y - HALF + "px";
49832
+ for (const handle of priv._resize_handles) {
49833
+ const x = left + (handle.mx + 1) / 2 * w;
49834
+ const y = top + (handle.my + 1) / 2 * h;
49835
+ handle.el.style.left = x - HALF + "px";
49836
+ handle.el.style.top = y - HALF + "px";
49425
49837
  }
49426
49838
  }
49427
49839
  function start_node_resize(gobj, e, mx, my) {
@@ -49497,6 +49909,14 @@ function start_node_resize(gobj, e, mx, my) {
49497
49909
  * Port selection and individual port resizing
49498
49910
  ************************************************************/
49499
49911
  var PORT_HANDLE_SIZE = 8;
49912
+ var PORT_HANDLE_VISUAL_COARSE = 12;
49913
+ var PORT_HANDLE_HIT_COARSE = 32;
49914
+ function port_handle_visual() {
49915
+ return coarse_pointer() ? PORT_HANDLE_VISUAL_COARSE : PORT_HANDLE_SIZE;
49916
+ }
49917
+ function port_handle_hit() {
49918
+ return coarse_pointer() ? PORT_HANDLE_HIT_COARSE : PORT_HANDLE_SIZE;
49919
+ }
49500
49920
  /************************************************************
49501
49921
  * Compute the canvas (world) position of a port on a node.
49502
49922
  * Returns {x, y} in world coordinates.
@@ -49529,6 +49949,26 @@ function get_port_radius(gobj, node_id, port_key) {
49529
49949
  return style.portR || 6;
49530
49950
  }
49531
49951
  /************************************************************
49952
+ * How much wider than the port itself its hit area is,
49953
+ * measured on the SCREEN and converted to world units.
49954
+ *
49955
+ * It used to be a flat `+4` in WORLD units, which is a
49956
+ * different target on every zoom: at the 50% a phone lands on
49957
+ * after fit, a 6-unit port plus 4 is a 5px radius -- one
49958
+ * fifth of a fingertip, and picking a port is the first step
49959
+ * of every link.
49960
+ ************************************************************/
49961
+ function port_hit_slop_world(graph) {
49962
+ let slop = coarse_pointer() ? 22 : 6;
49963
+ let zoom = 1;
49964
+ try {
49965
+ zoom = graph.getZoom() || 1;
49966
+ } catch (e) {
49967
+ zoom = 1;
49968
+ }
49969
+ return slop / zoom;
49970
+ }
49971
+ /************************************************************
49532
49972
  * Detect if a click in canvas coordinates hits a port.
49533
49973
  * Returns the port key string, or null if no port hit.
49534
49974
  ************************************************************/
@@ -49541,6 +49981,7 @@ function detect_port_click(gobj, node_id, canvasX, canvasY) {
49541
49981
  const defaultR = style.portR || 6;
49542
49982
  let best_key = null;
49543
49983
  let best_dist = Infinity;
49984
+ let slop = port_hit_slop_world(graph);
49544
49985
  for (let i = 0; i < ports.length; i++) {
49545
49986
  let pl = ports[i].placement || [.5, .5];
49546
49987
  let px = pos[0] + (pl[0] - .5) * w;
@@ -49549,7 +49990,7 @@ function detect_port_click(gobj, node_id, canvasX, canvasY) {
49549
49990
  let dx = canvasX - px;
49550
49991
  let dy = canvasY - py;
49551
49992
  let dist = Math.sqrt(dx * dx + dy * dy);
49552
- if (dist <= r + 4 && dist < best_dist) {
49993
+ if (dist <= r + slop && dist < best_dist) {
49553
49994
  best_dist = dist;
49554
49995
  best_key = ports[i].key;
49555
49996
  }
@@ -49597,7 +50038,7 @@ function show_link_icon_if_fkey(gobj) {
49597
50038
  let canvasPos = get_port_canvas_position(gobj, priv._selected_node_id, priv._selected_port_key);
49598
50039
  if (!canvasPos) return;
49599
50040
  let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
49600
- let icon = create_floating_icon("link", "#52c41a", vp[0] + 18, vp[1] - 14, t("link to hook"), () => {
50041
+ let icon = create_floating_icon("link", "#52c41a", vp[0] + floating_icon_size() / 2 + 4, vp[1] + floating_icon_dy(), t("link to hook"), () => {
49601
50042
  if (!priv._linking_mode) enter_linking_mode(gobj);
49602
50043
  });
49603
50044
  icon.addEventListener("pointerdown", (e) => {
@@ -49622,8 +50063,8 @@ function update_link_icon_position(gobj) {
49622
50063
  return;
49623
50064
  }
49624
50065
  let vp = graph.getViewportByCanvas([canvasPos.x, canvasPos.y]);
49625
- priv._link_icon_el.style.left = vp[0] + 18 + "px";
49626
- priv._link_icon_el.style.top = vp[1] - 14 + "px";
50066
+ priv._link_icon_el.style.left = vp[0] + floating_icon_size() / 2 + 4 + "px";
50067
+ priv._link_icon_el.style.top = vp[1] + floating_icon_dy() + "px";
49627
50068
  } catch (e) {
49628
50069
  hide_link_icon(gobj);
49629
50070
  }
@@ -49891,10 +50332,15 @@ function show_port_resize_handles(gobj) {
49891
50332
  dy: 0
49892
50333
  }
49893
50334
  ];
50335
+ const hit = port_handle_hit();
50336
+ const visual = port_handle_visual();
49894
50337
  const handles = [];
49895
50338
  for (const def of handle_defs) {
49896
50339
  const el = document.createElement("div");
49897
- 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;";
50340
+ 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;";
50341
+ const mark = document.createElement("div");
50342
+ 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;";
50343
+ el.appendChild(mark);
49898
50344
  el.addEventListener("pointerdown", (e) => {
49899
50345
  start_port_resize(gobj, e);
49900
50346
  });
@@ -49948,7 +50394,7 @@ function update_port_resize_handles_position(gobj) {
49948
50394
  ************************************************************/
49949
50395
  function apply_port_handles(gobj, cx, cy, vpR) {
49950
50396
  let priv = gobj.priv;
49951
- const HALF = PORT_HANDLE_SIZE / 2;
50397
+ const HALF = port_handle_hit() / 2;
49952
50398
  const ring = priv._port_ring;
49953
50399
  if (ring) {
49954
50400
  const d = vpR * 2;
@@ -50093,7 +50539,7 @@ function show_edge_icon(gobj) {
50093
50539
  if (!priv._selected_edge_id) return;
50094
50540
  let mid = get_edge_viewport_midpoint(gobj, priv._selected_edge_id);
50095
50541
  if (!mid) return;
50096
- let icons = show_dual_icons(gobj, mid.x + 4, mid.y - 14, t("edge properties"), () => toggle_edge_popover(gobj), t("unlink"), () => request_unlink_edge(gobj));
50542
+ let icons = show_dual_icons(gobj, mid.x + 4, mid.y + floating_icon_dy(), t("edge properties"), () => toggle_edge_popover(gobj), t("unlink"), () => request_unlink_edge(gobj));
50097
50543
  priv._edge_icon_el = icons.icon_el;
50098
50544
  priv._edge_delete_el = icons.delete_el;
50099
50545
  }
@@ -50111,14 +50557,14 @@ function update_edge_icon_position(gobj) {
50111
50557
  return;
50112
50558
  }
50113
50559
  priv._edge_icon_el.style.left = mid.x + 4 + "px";
50114
- priv._edge_icon_el.style.top = mid.y - 14 + "px";
50560
+ priv._edge_icon_el.style.top = mid.y + floating_icon_dy() + "px";
50115
50561
  if (priv._edge_delete_el) {
50116
50562
  priv._edge_delete_el.style.left = mid.x + 4 + "px";
50117
- priv._edge_delete_el.style.top = mid.y + 18 + "px";
50563
+ priv._edge_delete_el.style.top = mid.y + floating_icon_dy() + floating_icon_step() + "px";
50118
50564
  }
50119
50565
  if (priv._edge_popover_el) {
50120
- priv._edge_popover_el.style.left = mid.x + 36 + "px";
50121
- priv._edge_popover_el.style.top = mid.y - 14 + "px";
50566
+ priv._edge_popover_el.style.left = mid.x + floating_popover_dx() + "px";
50567
+ priv._edge_popover_el.style.top = mid.y + floating_icon_dy() + "px";
50122
50568
  clamp_popover_position(gobj, priv._edge_popover_el);
50123
50569
  }
50124
50570
  } catch (e) {
@@ -50156,7 +50602,7 @@ function show_edge_popover(gobj) {
50156
50602
  });
50157
50603
  let mid = get_edge_viewport_midpoint(gobj, edge_id);
50158
50604
  if (!mid) return;
50159
- const popover = create_popover_base(mid.x + 36, mid.y - 14, "g6-edge-popover", "#d9d9d9", 180);
50605
+ const popover = create_popover_base(mid.x + floating_popover_dx(), mid.y + floating_icon_dy(), "g6-edge-popover", "#d9d9d9", 180);
50160
50606
  function preview_edge() {
50161
50607
  let lw = parseInt(lwInput.value) || 2;
50162
50608
  let color = colorInput.value;
@@ -50243,7 +50689,7 @@ function show_node_icon(gobj) {
50243
50689
  if (!priv._selected_node_id || priv._selected_port_key) return;
50244
50690
  let rect = get_node_viewport_rect(gobj, priv._selected_node_id);
50245
50691
  if (!rect) return;
50246
- let icons = show_dual_icons(gobj, rect.right + 4, rect.top - 14, t("node properties"), () => toggle_node_popover(gobj), t("delete node"), () => request_delete_node(gobj));
50692
+ let icons = show_dual_icons(gobj, rect.right + 4, rect.top + floating_icon_dy(), t("node properties"), () => toggle_node_popover(gobj), t("delete node"), () => request_delete_node(gobj));
50247
50693
  priv._node_icon_el = icons.icon_el;
50248
50694
  priv._node_delete_el = icons.delete_el;
50249
50695
  }
@@ -50265,14 +50711,14 @@ function update_node_icon_position(gobj) {
50265
50711
  return;
50266
50712
  }
50267
50713
  priv._node_icon_el.style.left = rect.right + 4 + "px";
50268
- priv._node_icon_el.style.top = rect.top - 14 + "px";
50714
+ priv._node_icon_el.style.top = rect.top + floating_icon_dy() + "px";
50269
50715
  if (priv._node_delete_el) {
50270
50716
  priv._node_delete_el.style.left = rect.right + 4 + "px";
50271
- priv._node_delete_el.style.top = rect.top + 18 + "px";
50717
+ priv._node_delete_el.style.top = rect.top + floating_icon_dy() + floating_icon_step() + "px";
50272
50718
  }
50273
50719
  if (priv._node_popover_el) {
50274
- priv._node_popover_el.style.left = rect.right + 36 + "px";
50275
- priv._node_popover_el.style.top = rect.top - 14 + "px";
50720
+ priv._node_popover_el.style.left = rect.right + floating_popover_dx() + "px";
50721
+ priv._node_popover_el.style.top = rect.top + floating_icon_dy() + "px";
50276
50722
  clamp_popover_position(gobj, priv._node_popover_el);
50277
50723
  }
50278
50724
  } catch (e) {
@@ -50305,7 +50751,7 @@ function show_node_popover(gobj) {
50305
50751
  let origLW = currentLW;
50306
50752
  let rect = get_node_viewport_rect(gobj, node_id);
50307
50753
  if (!rect) return;
50308
- const popover = create_popover_base(rect.right + 36, rect.top - 14, "g6-node-popover", "#d9d9d9", 180);
50754
+ const popover = create_popover_base(rect.right + floating_popover_dx(), rect.top + floating_icon_dy(), "g6-node-popover", "#d9d9d9", 180);
50309
50755
  let node_graph_type = nodeData.data && nodeData.data.desc ? nodeData.data.desc.node_treedb_type : null;
50310
50756
  function preview_node() {
50311
50757
  let fill = fillInput.value;
@@ -50936,7 +51382,7 @@ function show_create_popover(gobj) {
50936
51382
  topicLabel.textContent = t("topic") + ":";
50937
51383
  popover.appendChild(topicLabel);
50938
51384
  let topicSelect = document.createElement("select");
50939
- topicSelect.style.cssText = "width:100%;padding:6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;margin-bottom:8px;";
51385
+ topicSelect.style.cssText = "width:100%;padding:6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;margin-bottom:8px;" + touch_control_css();
50940
51386
  for (let i = 0; i < topics.length; i++) {
50941
51387
  let opt = document.createElement("option");
50942
51388
  opt.value = topics[i].topic_name;
@@ -50969,7 +51415,7 @@ function show_create_popover(gobj) {
50969
51415
  let idInput = document.createElement("input");
50970
51416
  idInput.type = "text";
50971
51417
  idInput.placeholder = t("node id");
50972
- idInput.style.cssText = "width:100%;padding:6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;margin-bottom:10px;box-sizing:border-box;";
51418
+ 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();
50973
51419
  popover.appendChild(idInput);
50974
51420
  let errorEl = document.createElement("div");
50975
51421
  errorEl.style.cssText = "color:#ff4d4f;font-size:12px;margin-bottom:8px;display:none;";
@@ -51659,6 +52105,11 @@ function ac_resize(gobj, event, kw, src) {
51659
52105
  if (h < 0) h = 80;
51660
52106
  graph_resize(gobj, rect.width, h);
51661
52107
  }
52108
+ let can_collapse = toolbars_can_collapse(gobj);
52109
+ if (can_collapse !== priv._toolbars_could_collapse) {
52110
+ priv._toolbars_could_collapse = can_collapse;
52111
+ update_toolbar(gobj);
52112
+ }
51662
52113
  return 0;
51663
52114
  }
51664
52115
  /************************************************************
@@ -51697,6 +52148,19 @@ function ac_zoom_reset(gobj, event, kw, src) {
51697
52148
  gobj.priv.graph.zoomTo(1);
51698
52149
  return 0;
51699
52150
  }
52151
+ /************************************************************
52152
+ * Fold / unfold the floating toolbars.
52153
+ *
52154
+ * One flag for BOTH: they are one wall over the canvas, and
52155
+ * the button that unfolds them has to be the same button
52156
+ * whichever edge the user reaches for.
52157
+ ************************************************************/
52158
+ function ac_toggle_toolbars(gobj, event, kw, src) {
52159
+ let priv = gobj.priv;
52160
+ priv.toolbar_collapsed = !toolbars_collapsed(gobj);
52161
+ update_toolbar(gobj);
52162
+ return 0;
52163
+ }
51700
52164
  function ac_auto_fit(gobj, event, kw, src) {
51701
52165
  graph_fitview(gobj);
51702
52166
  return 0;
@@ -52081,6 +52545,11 @@ function create_gclass$4(gclass_name) {
52081
52545
  ac_zoom_reset,
52082
52546
  null
52083
52547
  ],
52548
+ [
52549
+ "EV_TOGGLE_TOOLBARS",
52550
+ ac_toggle_toolbars,
52551
+ null
52552
+ ],
52084
52553
  [
52085
52554
  "EV_AUTO_FIT",
52086
52555
  ac_auto_fit,
@@ -52184,6 +52653,7 @@ function create_gclass$4(gclass_name) {
52184
52653
  ["EV_ZOOM_IN", 0],
52185
52654
  ["EV_ZOOM_OUT", 0],
52186
52655
  ["EV_ZOOM_RESET", 0],
52656
+ ["EV_TOGGLE_TOOLBARS", 0],
52187
52657
  ["EV_AUTO_FIT", 0],
52188
52658
  ["EV_ZOOM_SELECTION", 0],
52189
52659
  ["EV_FOCUS_TOPIC", 0],
@@ -52587,24 +53057,17 @@ function make_toolbar(gobj) {
52587
53057
  } }
52588
53058
  ]
52589
53059
  ];
52590
- let center_items = [[
52591
- "div",
53060
+ let $find_input = createElement2([
53061
+ "input",
52592
53062
  {
52593
- class: "GRAPH_FIND control has-icons-left",
52594
- style: "margin-right:.5rem; max-width:12rem; min-width:7rem;"
52595
- },
52596
- [["input", {
52597
53063
  class: "GRAPH_FIND_INPUT input",
52598
53064
  type: "text",
52599
53065
  placeholder: t("search"),
52600
53066
  "data-i18n-placeholder": "search",
52601
53067
  "aria-label": t("search"),
52602
53068
  "data-i18n-aria-label": "search"
52603
- }], [
52604
- "span",
52605
- { class: "icon is-left" },
52606
- [["i", { class: "yi-magnifying-glass" }]]
52607
- ]],
53069
+ },
53070
+ [],
52608
53071
  { input: (evt) => {
52609
53072
  evt.stopPropagation();
52610
53073
  let text = evt.target.value.trim();
@@ -52614,22 +53077,20 @@ function make_toolbar(gobj) {
52614
53077
  gobj_send_event(gobj, "EV_FIND_NODES", { text }, gobj);
52615
53078
  }, 250);
52616
53079
  } }
52617
- ], [
53080
+ ]);
53081
+ let $find_control = createElement2([
52618
53082
  "div",
52619
53083
  {
52620
- class: "GRAPH_FIND_RESULT is-hidden",
52621
- style: "display:flex; align-items:center; gap:.3rem; margin-right:.5rem; font-size:.85rem;"
53084
+ class: "GRAPH_FIND control has-icons-left",
53085
+ style: "margin-right:.5rem; max-width:12rem; min-width:7rem;"
52622
53086
  },
52623
- [[
52624
- "span",
52625
- { class: "GRAPH_FIND_COUNT" },
52626
- ""
52627
- ], [
53087
+ [$find_input, [
52628
53088
  "span",
52629
- { i18n: "matches" },
52630
- "matches"
53089
+ { class: "icon is-left" },
53090
+ [["i", { class: "yi-magnifying-glass" }]]
52631
53091
  ]]
52632
- ]];
53092
+ ]);
53093
+ attach_clear($find_control, $find_input);
52633
53094
  const $toolbar_header = yui_toolbar({}, [
52634
53095
  [
52635
53096
  "div",
@@ -52639,7 +53100,22 @@ function make_toolbar(gobj) {
52639
53100
  [
52640
53101
  "div",
52641
53102
  { class: "yui-horizontal-toolbar-section center" },
52642
- center_items
53103
+ [$find_control, [
53104
+ "div",
53105
+ {
53106
+ class: "GRAPH_FIND_RESULT is-hidden",
53107
+ style: "display:flex; align-items:center; gap:.3rem; margin-right:.5rem; font-size:.85rem;"
53108
+ },
53109
+ [[
53110
+ "span",
53111
+ { class: "GRAPH_FIND_COUNT" },
53112
+ ""
53113
+ ], [
53114
+ "span",
53115
+ { i18n: "matches" },
53116
+ "matches"
53117
+ ]]
53118
+ ]]
52643
53119
  ],
52644
53120
  [
52645
53121
  "div",