@yuneta/gobj-ui 7.23.2 → 7.23.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -448,6 +448,17 @@ stays. Note the close contract: `C_YUI_WINDOW.close_window()` calls `on_close`
448
448
  and THEN destroys itself, so a host must drop its reference rather than destroy
449
449
  the window too.
450
450
 
451
+ Its camera comes from **`yui_graph_camera.js`** (`7.23.4`), the one place every
452
+ DOM-toolbar graph asks for one — this gclass and the gobj tree both, so a
453
+ cloned toolbar cannot drift apart again. The vocabulary is the treedb graph's
454
+ (`c_g6_nodes_tree.js`, which draws its toolbar as a G6 plugin over an SVG
455
+ sprite and so cannot share the code, only the decisions): zoom in / zoom out, a
456
+ live **zoom readout**, `yi-fit` for fit — the same corner brackets the sprite
457
+ draws for `g6-icon-fit` — and the WRITTEN `1:1` for actual size, which is
458
+ written in every editor that offers it and never drawn. Fold is the chevron
459
+ pair, from the lazy tree viewer's own toolbar. The readout hangs off G6's
460
+ `aftertransform`, the one hook that also covers the wheel.
461
+
451
462
  The graph carries **its own** toolbar, a **layout picker**
452
463
  (`EV_CHANGE_LAYOUT {layout}`: `vertical tree`, `dagre top-down`,
453
464
  `dagre left-right` — the layout and the edge type move together), and since
@@ -9350,6 +9350,160 @@ function yui_toolbar(attrs = {}, items = []) {
9350
9350
  return $toolbar;
9351
9351
  }
9352
9352
  //#endregion
9353
+ //#region src/yui_graph_camera.js
9354
+ /***********************************************************************
9355
+ * yui_graph_camera.js
9356
+ *
9357
+ * The camera and fold controls a G6 graph puts in a DOM toolbar,
9358
+ * built in ONE place.
9359
+ *
9360
+ * This module exists because the same action was drawn three
9361
+ * different ways across sibling views — actual size as a bare
9362
+ * magnifier here and as `1:1` there, fit as `arrows-to-eye` here and
9363
+ * as corner brackets there, fold as chevrons here and as an eye
9364
+ * there. A user sees two of these graphs side by side in the same
9365
+ * console, so a copied-and-drifted toolbar is not a detail.
9366
+ *
9367
+ * The VOCABULARY is not invented here. It is the one
9368
+ * `c_g6_nodes_tree.js` settled on for the treedb graph, which draws
9369
+ * its toolbar as a G6 plugin over an SVG sprite and therefore cannot
9370
+ * share this code — only its decisions:
9371
+ *
9372
+ * zoom in / zoom out magnifiers
9373
+ * zoom level a READOUT, because without it "1:1" is
9374
+ * a jump to a value nobody was told
9375
+ * fit corner brackets (`yi-fit`, the same
9376
+ * path as the sprite's `g6-icon-fit`)
9377
+ * actual size the WRITTEN `1:1` — the action is
9378
+ * `zoomTo(1)`, and actual size is written
9379
+ * in every editor that offers it, never
9380
+ * drawn, because there is no glyph for it
9381
+ *
9382
+ * Fold is the one the treedb graph does not have, so its vocabulary
9383
+ * comes from the other side of the family: the lazy tree viewer's own
9384
+ * toolbar (`c_yui_json.js`) uses chevrons — open pointing down, closed
9385
+ * pointing right — which is also the direction the per-node handles
9386
+ * speak. An eye means show/hide, which is a different idea.
9387
+ *
9388
+ * Copyright (c) 2026, ArtGins.
9389
+ * All Rights Reserved.
9390
+ ***********************************************************************/
9391
+ /************************************************************
9392
+ * One icon button that fires `event_name`.
9393
+ ************************************************************/
9394
+ function camera_button(gobj, icon, event_name, label_key, wide, extra_style) {
9395
+ return [
9396
+ "button",
9397
+ {
9398
+ class: `button ${event_name}`,
9399
+ type: "button",
9400
+ style: {
9401
+ height: wide,
9402
+ width: "2.5em"
9403
+ },
9404
+ title: (0, i18next.t)(label_key),
9405
+ "data-i18n-title": label_key,
9406
+ "aria-label": (0, i18next.t)(label_key),
9407
+ "data-i18n-aria-label": label_key
9408
+ },
9409
+ ["i", {
9410
+ style: "font-size:1.5em; color:inherit;" + (extra_style || ""),
9411
+ class: icon
9412
+ }],
9413
+ { click: (evt) => {
9414
+ evt.stopPropagation();
9415
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, event_name, { evt }, gobj);
9416
+ } }
9417
+ ];
9418
+ }
9419
+ /************************************************************
9420
+ * The current zoom as a percentage.
9421
+ ************************************************************/
9422
+ function yui_graph_zoom_text(graph) {
9423
+ if (!graph) return "100%";
9424
+ try {
9425
+ return Math.round(graph.getZoom() * 100) + "%";
9426
+ } catch (e) {
9427
+ return "100%";
9428
+ }
9429
+ }
9430
+ /************************************************************
9431
+ * Repaint the readout of a container that carries one.
9432
+ *
9433
+ * Call it from G6's `aftertransform`, which is the ONE hook
9434
+ * that also covers the WHEEL — a camera change that passes
9435
+ * through no action, so repainting from each zoom action
9436
+ * leaves the number lying after every notch.
9437
+ ************************************************************/
9438
+ function yui_graph_update_zoom($container, graph) {
9439
+ if (!$container) return;
9440
+ let $readout = $container.querySelector(".GRAPH_ZOOM_LEVEL");
9441
+ if (!$readout) return;
9442
+ $readout.textContent = yui_graph_zoom_text(graph);
9443
+ }
9444
+ /************************************************************
9445
+ * The camera cluster: zoom in, zoom out, readout, fit,
9446
+ * actual size. Returns createElement2 specs, in order.
9447
+ *
9448
+ * `wide` is the host toolbar's button height.
9449
+ ************************************************************/
9450
+ function yui_graph_camera_items(gobj, graph, wide) {
9451
+ return [
9452
+ camera_button(gobj, "yi-magnifying-glass-plus", "EV_ZOOM_IN", "zoom in", wide),
9453
+ camera_button(gobj, "yi-magnifying-glass-minus", "EV_ZOOM_OUT", "zoom out", wide),
9454
+ [
9455
+ "span",
9456
+ {
9457
+ class: "GRAPH_ZOOM_LEVEL is-flex is-align-items-center px-2 has-text-grey",
9458
+ style: "font-size:.85rem; min-width:3.5em; justify-content:center;",
9459
+ title: (0, i18next.t)("zoom level"),
9460
+ "data-i18n-title": "zoom level"
9461
+ },
9462
+ yui_graph_zoom_text(graph)
9463
+ ],
9464
+ camera_button(gobj, "yi-fit", "EV_CENTER", "auto fit", wide),
9465
+ [
9466
+ "button",
9467
+ {
9468
+ class: "button EV_ZOOM_RESET",
9469
+ type: "button",
9470
+ style: {
9471
+ height: wide,
9472
+ width: "2.5em"
9473
+ },
9474
+ title: (0, i18next.t)("actual size"),
9475
+ "data-i18n-title": "actual size",
9476
+ "aria-label": (0, i18next.t)("actual size"),
9477
+ "data-i18n-aria-label": "actual size"
9478
+ },
9479
+ [
9480
+ "span",
9481
+ { style: "font-weight:700;" },
9482
+ "1:1"
9483
+ ],
9484
+ { click: (evt) => {
9485
+ evt.stopPropagation();
9486
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_ZOOM_RESET", { evt }, gobj);
9487
+ } }
9488
+ ]
9489
+ ];
9490
+ }
9491
+ /************************************************************
9492
+ * The fold pair: expand all, collapse all. Chevrons, the
9493
+ * open one rotated down — the same pair the lazy tree
9494
+ * viewer's toolbar uses, and the same direction the
9495
+ * per-node handles speak.
9496
+ ************************************************************/
9497
+ function yui_graph_fold_items(gobj, wide) {
9498
+ return [camera_button(gobj, "yi-chevron-right", "EV_EXPAND_ALL", "expand all", wide, " transform: rotate(90deg);"), camera_button(gobj, "yi-chevron-right", "EV_COLLAPSE_ALL", "collapse all", wide)];
9499
+ }
9500
+ /************************************************************
9501
+ * A refresh button, for the graphs that offer one.
9502
+ ************************************************************/
9503
+ function yui_graph_refresh_item(gobj, wide) {
9504
+ return camera_button(gobj, "yi-arrows-rotate", "EV_REFRESH", "refresh", wide);
9505
+ }
9506
+ //#endregion
9353
9507
  //#region src/g6_drag_canvas_touch.js
9354
9508
  /***********************************************************************
9355
9509
  * g6_drag_canvas_touch.js
@@ -9753,36 +9907,6 @@ function destroy_ui$6(gobj) {
9753
9907
  }
9754
9908
  }
9755
9909
  /************************************************************
9756
- * One fold button. `open` rotates the chevron to the open
9757
- * state, mirroring the per-node toggle of the lazy tree.
9758
- ************************************************************/
9759
- function graph_fold_button(gobj, event_name, label_key, open) {
9760
- let icon_style = "font-size:1.5em; color:inherit;" + (open ? " transform: rotate(90deg);" : "");
9761
- return [
9762
- "button",
9763
- {
9764
- class: `button ${event_name}`,
9765
- type: "button",
9766
- style: {
9767
- height: (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "wide"),
9768
- width: "2.5em"
9769
- },
9770
- title: (0, i18next.t)(label_key),
9771
- "data-i18n-title": label_key,
9772
- "aria-label": (0, i18next.t)(label_key),
9773
- "data-i18n-aria-label": label_key
9774
- },
9775
- ["i", {
9776
- style: icon_style,
9777
- class: "yi-chevron-right"
9778
- }],
9779
- { click: (evt) => {
9780
- evt.stopPropagation();
9781
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, event_name, {}, gobj);
9782
- } }
9783
- ];
9784
- }
9785
- /************************************************************
9786
9910
  * Toolbar
9787
9911
  ************************************************************/
9788
9912
  function make_toolbar$3(gobj) {
@@ -9832,62 +9956,8 @@ function make_toolbar$3(gobj) {
9832
9956
  ]]
9833
9957
  ]];
9834
9958
  let center_items = [];
9835
- for (let item of [
9836
- [
9837
- "yi-magnifying-glass-plus",
9838
- "EV_ZOOM_IN",
9839
- false,
9840
- "i"
9841
- ],
9842
- [
9843
- "yi-magnifying-glass",
9844
- "EV_ZOOM_RESET",
9845
- false,
9846
- "i"
9847
- ],
9848
- [
9849
- "yi-magnifying-glass-minus",
9850
- "EV_ZOOM_OUT",
9851
- false,
9852
- "i"
9853
- ],
9854
- [
9855
- "yi-arrows-to-eye",
9856
- "EV_CENTER",
9857
- false,
9858
- "i"
9859
- ],
9860
- [
9861
- "yi-arrows-rotate",
9862
- "EV_REFRESH",
9863
- false,
9864
- "i"
9865
- ]
9866
- ]) {
9867
- let icon_name = item[0];
9868
- let event_name = item[1];
9869
- let disabled = item[2];
9870
- let button = {
9871
- class: `button ${event_name}`,
9872
- style: {
9873
- height: toolbar_wide,
9874
- width: "2.5em"
9875
- }
9876
- };
9877
- if (disabled) button.disabled = true;
9878
- center_items.push([
9879
- "button",
9880
- button,
9881
- ["i", {
9882
- style: "font-size:1.5em; color:inherit;",
9883
- class: icon_name
9884
- }],
9885
- { click: (evt) => {
9886
- evt.stopPropagation();
9887
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, event_name, { evt }, gobj);
9888
- } }
9889
- ]);
9890
- }
9959
+ center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide);
9960
+ center_items.push(yui_graph_refresh_item(gobj, toolbar_wide));
9891
9961
  let current_layout = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "layout") || DEFAULT_LAYOUT;
9892
9962
  let options = Object.keys(LAYOUTS$1).map(function(key) {
9893
9963
  let attrs = {
@@ -9901,29 +9971,25 @@ function make_toolbar$3(gobj) {
9901
9971
  layout_label(key)
9902
9972
  ];
9903
9973
  });
9904
- let right_items = [
9905
- (0, _yuneta_gobj_js.createElement2)([
9906
- "select",
9907
- {
9908
- class: "JSON_GRAPH_LAYOUT select",
9909
- style: {
9910
- height: toolbar_wide,
9911
- "margin-left": "0.5em"
9912
- },
9913
- title: (0, i18next.t)("layout"),
9914
- "data-i18n-title": "layout",
9915
- "aria-label": (0, i18next.t)("layout"),
9916
- "data-i18n-aria-label": "layout"
9974
+ let right_items = [(0, _yuneta_gobj_js.createElement2)([
9975
+ "select",
9976
+ {
9977
+ class: "JSON_GRAPH_LAYOUT select",
9978
+ style: {
9979
+ height: toolbar_wide,
9980
+ "margin-left": "0.5em"
9917
9981
  },
9918
- options,
9919
- { change: (evt) => {
9920
- evt.stopPropagation();
9921
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CHANGE_LAYOUT", { layout: evt.target.value }, gobj);
9922
- } }
9923
- ]),
9924
- graph_fold_button(gobj, "EV_EXPAND_ALL", "expand all", true),
9925
- graph_fold_button(gobj, "EV_COLLAPSE_ALL", "collapse all", false)
9926
- ];
9982
+ title: (0, i18next.t)("layout"),
9983
+ "data-i18n-title": "layout",
9984
+ "aria-label": (0, i18next.t)("layout"),
9985
+ "data-i18n-aria-label": "layout"
9986
+ },
9987
+ options,
9988
+ { change: (evt) => {
9989
+ evt.stopPropagation();
9990
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CHANGE_LAYOUT", { layout: evt.target.value }, gobj);
9991
+ } }
9992
+ ])].concat(yui_graph_fold_items(gobj, toolbar_wide));
9927
9993
  const $toolbar = yui_toolbar({}, [
9928
9994
  [
9929
9995
  "div",
@@ -9971,6 +10037,9 @@ function build_graph$3(gobj) {
9971
10037
  graph.on(_antv_g6.NodeEvent.CLICK, (evt) => {
9972
10038
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_NODE_CLICK", { evt }, gobj);
9973
10039
  });
10040
+ graph.on("aftertransform", () => {
10041
+ yui_graph_update_zoom((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.graph);
10042
+ });
9974
10043
  graph.on(_antv_g6.CanvasEvent.CLICK, (evt) => {});
9975
10044
  if (typeof ResizeObserver !== "undefined") {
9976
10045
  let $canvas = document.getElementById(priv.canvas_id);
@@ -10230,6 +10299,7 @@ function load_json(gobj) {
10230
10299
  });
10231
10300
  graph.render().then(() => {
10232
10301
  if (!preserve_view) graph.fitView();
10302
+ yui_graph_update_zoom((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.graph);
10233
10303
  });
10234
10304
  }
10235
10305
  }
@@ -12539,74 +12609,8 @@ function make_toolbar$1(gobj) {
12539
12609
  let left_items = [];
12540
12610
  let center_items = [];
12541
12611
  let right_items = [];
12542
- for (let item of [
12543
- [
12544
- "yi-magnifying-glass-plus",
12545
- "EV_ZOOM_IN",
12546
- false,
12547
- "i"
12548
- ],
12549
- [
12550
- "yi-magnifying-glass",
12551
- "EV_ZOOM_RESET",
12552
- false,
12553
- "i"
12554
- ],
12555
- [
12556
- "yi-magnifying-glass-minus",
12557
- "EV_ZOOM_OUT",
12558
- false,
12559
- "i"
12560
- ],
12561
- [
12562
- "yi-arrows-to-eye",
12563
- "EV_CENTER",
12564
- false,
12565
- "i"
12566
- ],
12567
- [
12568
- "yi-eye",
12569
- "EV_EXPAND_ALL",
12570
- false,
12571
- "i"
12572
- ],
12573
- [
12574
- "yi-eye-slash",
12575
- "EV_COLLAPSE_ALL",
12576
- false,
12577
- "i"
12578
- ],
12579
- [
12580
- "yi-arrows-rotate",
12581
- "EV_REFRESH",
12582
- false,
12583
- "i"
12584
- ]
12585
- ]) {
12586
- let icon_name = item[0];
12587
- let event_name = item[1];
12588
- let disabled = item[2];
12589
- let button = {
12590
- class: `button ${event_name}`,
12591
- style: {
12592
- height: toolbar_wide,
12593
- width: "2.5em"
12594
- }
12595
- };
12596
- if (disabled) button.disabled = true;
12597
- center_items.push([
12598
- "button",
12599
- button,
12600
- ["i", {
12601
- style: "font-size:1.5em; color:inherit;",
12602
- class: icon_name
12603
- }],
12604
- { click: (evt) => {
12605
- evt.stopPropagation();
12606
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, event_name, { evt }, gobj);
12607
- } }
12608
- ]);
12609
- }
12612
+ center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide).concat(yui_graph_fold_items(gobj, toolbar_wide));
12613
+ center_items.push(yui_graph_refresh_item(gobj, toolbar_wide));
12610
12614
  let options = [];
12611
12615
  for (let key of Object.keys(LAYOUTS)) {
12612
12616
  let opt_attrs = { value: key };
@@ -12679,6 +12683,9 @@ function build_graph$2(gobj) {
12679
12683
  behaviors: ["drag-canvas", "zoom-canvas"]
12680
12684
  });
12681
12685
  if (priv.theme) graph.setTheme(priv.theme);
12686
+ graph.on("aftertransform", () => {
12687
+ yui_graph_update_zoom((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container"), priv.graph);
12688
+ });
12682
12689
  graph.on(_antv_g6.NodeEvent.CLICK, (evt) => {
12683
12690
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_NODE_CLICK", { evt }, gobj);
12684
12691
  });
@@ -9345,6 +9345,160 @@ function yui_toolbar(attrs = {}, items = []) {
9345
9345
  return $toolbar;
9346
9346
  }
9347
9347
  //#endregion
9348
+ //#region src/yui_graph_camera.js
9349
+ /***********************************************************************
9350
+ * yui_graph_camera.js
9351
+ *
9352
+ * The camera and fold controls a G6 graph puts in a DOM toolbar,
9353
+ * built in ONE place.
9354
+ *
9355
+ * This module exists because the same action was drawn three
9356
+ * different ways across sibling views — actual size as a bare
9357
+ * magnifier here and as `1:1` there, fit as `arrows-to-eye` here and
9358
+ * as corner brackets there, fold as chevrons here and as an eye
9359
+ * there. A user sees two of these graphs side by side in the same
9360
+ * console, so a copied-and-drifted toolbar is not a detail.
9361
+ *
9362
+ * The VOCABULARY is not invented here. It is the one
9363
+ * `c_g6_nodes_tree.js` settled on for the treedb graph, which draws
9364
+ * its toolbar as a G6 plugin over an SVG sprite and therefore cannot
9365
+ * share this code — only its decisions:
9366
+ *
9367
+ * zoom in / zoom out magnifiers
9368
+ * zoom level a READOUT, because without it "1:1" is
9369
+ * a jump to a value nobody was told
9370
+ * fit corner brackets (`yi-fit`, the same
9371
+ * path as the sprite's `g6-icon-fit`)
9372
+ * actual size the WRITTEN `1:1` — the action is
9373
+ * `zoomTo(1)`, and actual size is written
9374
+ * in every editor that offers it, never
9375
+ * drawn, because there is no glyph for it
9376
+ *
9377
+ * Fold is the one the treedb graph does not have, so its vocabulary
9378
+ * comes from the other side of the family: the lazy tree viewer's own
9379
+ * toolbar (`c_yui_json.js`) uses chevrons — open pointing down, closed
9380
+ * pointing right — which is also the direction the per-node handles
9381
+ * speak. An eye means show/hide, which is a different idea.
9382
+ *
9383
+ * Copyright (c) 2026, ArtGins.
9384
+ * All Rights Reserved.
9385
+ ***********************************************************************/
9386
+ /************************************************************
9387
+ * One icon button that fires `event_name`.
9388
+ ************************************************************/
9389
+ function camera_button(gobj, icon, event_name, label_key, wide, extra_style) {
9390
+ return [
9391
+ "button",
9392
+ {
9393
+ class: `button ${event_name}`,
9394
+ type: "button",
9395
+ style: {
9396
+ height: wide,
9397
+ width: "2.5em"
9398
+ },
9399
+ title: t(label_key),
9400
+ "data-i18n-title": label_key,
9401
+ "aria-label": t(label_key),
9402
+ "data-i18n-aria-label": label_key
9403
+ },
9404
+ ["i", {
9405
+ style: "font-size:1.5em; color:inherit;" + (extra_style || ""),
9406
+ class: icon
9407
+ }],
9408
+ { click: (evt) => {
9409
+ evt.stopPropagation();
9410
+ gobj_send_event(gobj, event_name, { evt }, gobj);
9411
+ } }
9412
+ ];
9413
+ }
9414
+ /************************************************************
9415
+ * The current zoom as a percentage.
9416
+ ************************************************************/
9417
+ function yui_graph_zoom_text(graph) {
9418
+ if (!graph) return "100%";
9419
+ try {
9420
+ return Math.round(graph.getZoom() * 100) + "%";
9421
+ } catch (e) {
9422
+ return "100%";
9423
+ }
9424
+ }
9425
+ /************************************************************
9426
+ * Repaint the readout of a container that carries one.
9427
+ *
9428
+ * Call it from G6's `aftertransform`, which is the ONE hook
9429
+ * that also covers the WHEEL — a camera change that passes
9430
+ * through no action, so repainting from each zoom action
9431
+ * leaves the number lying after every notch.
9432
+ ************************************************************/
9433
+ function yui_graph_update_zoom($container, graph) {
9434
+ if (!$container) return;
9435
+ let $readout = $container.querySelector(".GRAPH_ZOOM_LEVEL");
9436
+ if (!$readout) return;
9437
+ $readout.textContent = yui_graph_zoom_text(graph);
9438
+ }
9439
+ /************************************************************
9440
+ * The camera cluster: zoom in, zoom out, readout, fit,
9441
+ * actual size. Returns createElement2 specs, in order.
9442
+ *
9443
+ * `wide` is the host toolbar's button height.
9444
+ ************************************************************/
9445
+ function yui_graph_camera_items(gobj, graph, wide) {
9446
+ return [
9447
+ camera_button(gobj, "yi-magnifying-glass-plus", "EV_ZOOM_IN", "zoom in", wide),
9448
+ camera_button(gobj, "yi-magnifying-glass-minus", "EV_ZOOM_OUT", "zoom out", wide),
9449
+ [
9450
+ "span",
9451
+ {
9452
+ class: "GRAPH_ZOOM_LEVEL is-flex is-align-items-center px-2 has-text-grey",
9453
+ style: "font-size:.85rem; min-width:3.5em; justify-content:center;",
9454
+ title: t("zoom level"),
9455
+ "data-i18n-title": "zoom level"
9456
+ },
9457
+ yui_graph_zoom_text(graph)
9458
+ ],
9459
+ camera_button(gobj, "yi-fit", "EV_CENTER", "auto fit", wide),
9460
+ [
9461
+ "button",
9462
+ {
9463
+ class: "button EV_ZOOM_RESET",
9464
+ type: "button",
9465
+ style: {
9466
+ height: wide,
9467
+ width: "2.5em"
9468
+ },
9469
+ title: t("actual size"),
9470
+ "data-i18n-title": "actual size",
9471
+ "aria-label": t("actual size"),
9472
+ "data-i18n-aria-label": "actual size"
9473
+ },
9474
+ [
9475
+ "span",
9476
+ { style: "font-weight:700;" },
9477
+ "1:1"
9478
+ ],
9479
+ { click: (evt) => {
9480
+ evt.stopPropagation();
9481
+ gobj_send_event(gobj, "EV_ZOOM_RESET", { evt }, gobj);
9482
+ } }
9483
+ ]
9484
+ ];
9485
+ }
9486
+ /************************************************************
9487
+ * The fold pair: expand all, collapse all. Chevrons, the
9488
+ * open one rotated down — the same pair the lazy tree
9489
+ * viewer's toolbar uses, and the same direction the
9490
+ * per-node handles speak.
9491
+ ************************************************************/
9492
+ function yui_graph_fold_items(gobj, wide) {
9493
+ return [camera_button(gobj, "yi-chevron-right", "EV_EXPAND_ALL", "expand all", wide, " transform: rotate(90deg);"), camera_button(gobj, "yi-chevron-right", "EV_COLLAPSE_ALL", "collapse all", wide)];
9494
+ }
9495
+ /************************************************************
9496
+ * A refresh button, for the graphs that offer one.
9497
+ ************************************************************/
9498
+ function yui_graph_refresh_item(gobj, wide) {
9499
+ return camera_button(gobj, "yi-arrows-rotate", "EV_REFRESH", "refresh", wide);
9500
+ }
9501
+ //#endregion
9348
9502
  //#region src/g6_drag_canvas_touch.js
9349
9503
  /***********************************************************************
9350
9504
  * g6_drag_canvas_touch.js
@@ -9748,36 +9902,6 @@ function destroy_ui$6(gobj) {
9748
9902
  }
9749
9903
  }
9750
9904
  /************************************************************
9751
- * One fold button. `open` rotates the chevron to the open
9752
- * state, mirroring the per-node toggle of the lazy tree.
9753
- ************************************************************/
9754
- function graph_fold_button(gobj, event_name, label_key, open) {
9755
- let icon_style = "font-size:1.5em; color:inherit;" + (open ? " transform: rotate(90deg);" : "");
9756
- return [
9757
- "button",
9758
- {
9759
- class: `button ${event_name}`,
9760
- type: "button",
9761
- style: {
9762
- height: gobj_read_attr(gobj, "wide"),
9763
- width: "2.5em"
9764
- },
9765
- title: t(label_key),
9766
- "data-i18n-title": label_key,
9767
- "aria-label": t(label_key),
9768
- "data-i18n-aria-label": label_key
9769
- },
9770
- ["i", {
9771
- style: icon_style,
9772
- class: "yi-chevron-right"
9773
- }],
9774
- { click: (evt) => {
9775
- evt.stopPropagation();
9776
- gobj_send_event(gobj, event_name, {}, gobj);
9777
- } }
9778
- ];
9779
- }
9780
- /************************************************************
9781
9905
  * Toolbar
9782
9906
  ************************************************************/
9783
9907
  function make_toolbar$3(gobj) {
@@ -9827,62 +9951,8 @@ function make_toolbar$3(gobj) {
9827
9951
  ]]
9828
9952
  ]];
9829
9953
  let center_items = [];
9830
- for (let item of [
9831
- [
9832
- "yi-magnifying-glass-plus",
9833
- "EV_ZOOM_IN",
9834
- false,
9835
- "i"
9836
- ],
9837
- [
9838
- "yi-magnifying-glass",
9839
- "EV_ZOOM_RESET",
9840
- false,
9841
- "i"
9842
- ],
9843
- [
9844
- "yi-magnifying-glass-minus",
9845
- "EV_ZOOM_OUT",
9846
- false,
9847
- "i"
9848
- ],
9849
- [
9850
- "yi-arrows-to-eye",
9851
- "EV_CENTER",
9852
- false,
9853
- "i"
9854
- ],
9855
- [
9856
- "yi-arrows-rotate",
9857
- "EV_REFRESH",
9858
- false,
9859
- "i"
9860
- ]
9861
- ]) {
9862
- let icon_name = item[0];
9863
- let event_name = item[1];
9864
- let disabled = item[2];
9865
- let button = {
9866
- class: `button ${event_name}`,
9867
- style: {
9868
- height: toolbar_wide,
9869
- width: "2.5em"
9870
- }
9871
- };
9872
- if (disabled) button.disabled = true;
9873
- center_items.push([
9874
- "button",
9875
- button,
9876
- ["i", {
9877
- style: "font-size:1.5em; color:inherit;",
9878
- class: icon_name
9879
- }],
9880
- { click: (evt) => {
9881
- evt.stopPropagation();
9882
- gobj_send_event(gobj, event_name, { evt }, gobj);
9883
- } }
9884
- ]);
9885
- }
9954
+ center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide);
9955
+ center_items.push(yui_graph_refresh_item(gobj, toolbar_wide));
9886
9956
  let current_layout = gobj_read_str_attr(gobj, "layout") || DEFAULT_LAYOUT;
9887
9957
  let options = Object.keys(LAYOUTS$1).map(function(key) {
9888
9958
  let attrs = {
@@ -9896,29 +9966,25 @@ function make_toolbar$3(gobj) {
9896
9966
  layout_label(key)
9897
9967
  ];
9898
9968
  });
9899
- let right_items = [
9900
- createElement2([
9901
- "select",
9902
- {
9903
- class: "JSON_GRAPH_LAYOUT select",
9904
- style: {
9905
- height: toolbar_wide,
9906
- "margin-left": "0.5em"
9907
- },
9908
- title: t("layout"),
9909
- "data-i18n-title": "layout",
9910
- "aria-label": t("layout"),
9911
- "data-i18n-aria-label": "layout"
9969
+ let right_items = [createElement2([
9970
+ "select",
9971
+ {
9972
+ class: "JSON_GRAPH_LAYOUT select",
9973
+ style: {
9974
+ height: toolbar_wide,
9975
+ "margin-left": "0.5em"
9912
9976
  },
9913
- options,
9914
- { change: (evt) => {
9915
- evt.stopPropagation();
9916
- gobj_send_event(gobj, "EV_CHANGE_LAYOUT", { layout: evt.target.value }, gobj);
9917
- } }
9918
- ]),
9919
- graph_fold_button(gobj, "EV_EXPAND_ALL", "expand all", true),
9920
- graph_fold_button(gobj, "EV_COLLAPSE_ALL", "collapse all", false)
9921
- ];
9977
+ title: t("layout"),
9978
+ "data-i18n-title": "layout",
9979
+ "aria-label": t("layout"),
9980
+ "data-i18n-aria-label": "layout"
9981
+ },
9982
+ options,
9983
+ { change: (evt) => {
9984
+ evt.stopPropagation();
9985
+ gobj_send_event(gobj, "EV_CHANGE_LAYOUT", { layout: evt.target.value }, gobj);
9986
+ } }
9987
+ ])].concat(yui_graph_fold_items(gobj, toolbar_wide));
9922
9988
  const $toolbar = yui_toolbar({}, [
9923
9989
  [
9924
9990
  "div",
@@ -9966,6 +10032,9 @@ function build_graph$3(gobj) {
9966
10032
  graph.on(NodeEvent.CLICK, (evt) => {
9967
10033
  gobj_send_event(gobj, "EV_NODE_CLICK", { evt }, gobj);
9968
10034
  });
10035
+ graph.on("aftertransform", () => {
10036
+ yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
10037
+ });
9969
10038
  graph.on(CanvasEvent.CLICK, (evt) => {});
9970
10039
  if (typeof ResizeObserver !== "undefined") {
9971
10040
  let $canvas = document.getElementById(priv.canvas_id);
@@ -10225,6 +10294,7 @@ function load_json(gobj) {
10225
10294
  });
10226
10295
  graph.render().then(() => {
10227
10296
  if (!preserve_view) graph.fitView();
10297
+ yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
10228
10298
  });
10229
10299
  }
10230
10300
  }
@@ -12534,74 +12604,8 @@ function make_toolbar$1(gobj) {
12534
12604
  let left_items = [];
12535
12605
  let center_items = [];
12536
12606
  let right_items = [];
12537
- for (let item of [
12538
- [
12539
- "yi-magnifying-glass-plus",
12540
- "EV_ZOOM_IN",
12541
- false,
12542
- "i"
12543
- ],
12544
- [
12545
- "yi-magnifying-glass",
12546
- "EV_ZOOM_RESET",
12547
- false,
12548
- "i"
12549
- ],
12550
- [
12551
- "yi-magnifying-glass-minus",
12552
- "EV_ZOOM_OUT",
12553
- false,
12554
- "i"
12555
- ],
12556
- [
12557
- "yi-arrows-to-eye",
12558
- "EV_CENTER",
12559
- false,
12560
- "i"
12561
- ],
12562
- [
12563
- "yi-eye",
12564
- "EV_EXPAND_ALL",
12565
- false,
12566
- "i"
12567
- ],
12568
- [
12569
- "yi-eye-slash",
12570
- "EV_COLLAPSE_ALL",
12571
- false,
12572
- "i"
12573
- ],
12574
- [
12575
- "yi-arrows-rotate",
12576
- "EV_REFRESH",
12577
- false,
12578
- "i"
12579
- ]
12580
- ]) {
12581
- let icon_name = item[0];
12582
- let event_name = item[1];
12583
- let disabled = item[2];
12584
- let button = {
12585
- class: `button ${event_name}`,
12586
- style: {
12587
- height: toolbar_wide,
12588
- width: "2.5em"
12589
- }
12590
- };
12591
- if (disabled) button.disabled = true;
12592
- center_items.push([
12593
- "button",
12594
- button,
12595
- ["i", {
12596
- style: "font-size:1.5em; color:inherit;",
12597
- class: icon_name
12598
- }],
12599
- { click: (evt) => {
12600
- evt.stopPropagation();
12601
- gobj_send_event(gobj, event_name, { evt }, gobj);
12602
- } }
12603
- ]);
12604
- }
12607
+ center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide).concat(yui_graph_fold_items(gobj, toolbar_wide));
12608
+ center_items.push(yui_graph_refresh_item(gobj, toolbar_wide));
12605
12609
  let options = [];
12606
12610
  for (let key of Object.keys(LAYOUTS)) {
12607
12611
  let opt_attrs = { value: key };
@@ -12674,6 +12678,9 @@ function build_graph$2(gobj) {
12674
12678
  behaviors: ["drag-canvas", "zoom-canvas"]
12675
12679
  });
12676
12680
  if (priv.theme) graph.setTheme(priv.theme);
12681
+ graph.on("aftertransform", () => {
12682
+ yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
12683
+ });
12677
12684
  graph.on(NodeEvent.CLICK, (evt) => {
12678
12685
  gobj_send_event(gobj, "EV_NODE_CLICK", { evt }, gobj);
12679
12686
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.2",
3
+ "version": "7.23.4",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -40,6 +40,12 @@ import {
40
40
  } from "@yuneta/gobj-js";
41
41
 
42
42
  import {yui_toolbar} from "./yui_toolbar.js";
43
+ import {
44
+ yui_graph_camera_items,
45
+ yui_graph_fold_items,
46
+ yui_graph_refresh_item,
47
+ yui_graph_update_zoom,
48
+ } from "./yui_graph_camera.js";
43
49
 
44
50
  import {t} from "i18next";
45
51
 
@@ -535,40 +541,18 @@ function make_toolbar(gobj)
535
541
  let center_items = [];
536
542
  let right_items = [];
537
543
 
538
- let c_icons = [
539
- ["yi-magnifying-glass-plus", "EV_ZOOM_IN", false, 'i'],
540
- ["yi-magnifying-glass", "EV_ZOOM_RESET", false, 'i'],
541
- ["yi-magnifying-glass-minus", "EV_ZOOM_OUT", false, 'i'],
542
- ["yi-arrows-to-eye", "EV_CENTER", false, 'i'],
543
- ["yi-eye", "EV_EXPAND_ALL", false, 'i'],
544
- ["yi-eye-slash", "EV_COLLAPSE_ALL", false, 'i'],
545
- ["yi-arrows-rotate", "EV_REFRESH", false, 'i'],
546
- ];
547
-
548
- for(let item of c_icons) {
549
- let icon_name = item[0];
550
- let event_name = item[1];
551
- let disabled = item[2];
552
-
553
- let button = {
554
- class: `button ${event_name}`,
555
- style: {height: toolbar_wide, width: '2.5em'}
556
- };
557
- if(disabled) {
558
- button.disabled = true;
559
- }
560
- center_items.push(
561
- ['button', button,
562
- ['i', {style: 'font-size:1.5em; color:inherit;', class: icon_name}],
563
- {
564
- click: (evt) => {
565
- evt.stopPropagation();
566
- gobj_send_event(gobj, event_name, {evt: evt}, gobj);
567
- }
568
- }
569
- ]
570
- );
571
- }
544
+ /*
545
+ * The camera and the fold pair come from yui_graph_camera.js.
546
+ *
547
+ * This toolbar used to draw two of these its own way — a bare
548
+ * magnifier for actual size, `arrows-to-eye` for fit — and fold as
549
+ * an eye, which means show/hide and not open/closed. The same
550
+ * console shows this graph and the JSON one side by side, so the
551
+ * drawings have to be the same drawings.
552
+ */
553
+ center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide)
554
+ .concat(yui_graph_fold_items(gobj, toolbar_wide));
555
+ center_items.push(yui_graph_refresh_item(gobj, toolbar_wide));
572
556
 
573
557
  /*
574
558
  * Layout selector
@@ -651,6 +635,12 @@ function build_graph(gobj)
651
635
  graph.setTheme(priv.theme);
652
636
  }
653
637
 
638
+ /* The readout follows ANY camera change, the wheel included — a
639
+ * wheel notch passes through no action of ours. */
640
+ graph.on('aftertransform', () => {
641
+ yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
642
+ });
643
+
654
644
  graph.on(NodeEvent.CLICK, (evt) => {
655
645
  gobj_send_event(gobj, "EV_NODE_CLICK", {evt: evt}, gobj);
656
646
  });
@@ -47,6 +47,12 @@ import {
47
47
  } from "@yuneta/gobj-js";
48
48
 
49
49
  import {yui_toolbar} from "./yui_toolbar.js";
50
+ import {
51
+ yui_graph_camera_items,
52
+ yui_graph_fold_items,
53
+ yui_graph_refresh_item,
54
+ yui_graph_update_zoom,
55
+ } from "./yui_graph_camera.js";
50
56
 
51
57
  import {t} from "i18next";
52
58
 
@@ -454,30 +460,6 @@ function destroy_ui(gobj)
454
460
  }
455
461
  }
456
462
 
457
- /************************************************************
458
- * One fold button. `open` rotates the chevron to the open
459
- * state, mirroring the per-node toggle of the lazy tree.
460
- ************************************************************/
461
- function graph_fold_button(gobj, event_name, label_key, open)
462
- {
463
- let icon_style = 'font-size:1.5em; color:inherit;' +
464
- (open? ' transform: rotate(90deg);': '');
465
-
466
- return ['button', {class: `button ${event_name}`,
467
- type: 'button',
468
- style: {height: gobj_read_attr(gobj, "wide"), width: '2.5em'},
469
- title: t(label_key), 'data-i18n-title': label_key,
470
- 'aria-label': t(label_key), 'data-i18n-aria-label': label_key},
471
- ['i', {style: icon_style, class: 'yi-chevron-right'}],
472
- {
473
- click: (evt) => {
474
- evt.stopPropagation();
475
- gobj_send_event(gobj, event_name, {}, gobj);
476
- }
477
- }
478
- ];
479
- }
480
-
481
463
  /************************************************************
482
464
  * Toolbar
483
465
  ************************************************************/
@@ -544,38 +526,13 @@ function make_toolbar(gobj)
544
526
  ];
545
527
  let center_items = [];
546
528
 
547
- let c_icons = [
548
- ["yi-magnifying-glass-plus", "EV_ZOOM_IN", false, 'i'],
549
- ["yi-magnifying-glass", "EV_ZOOM_RESET", false, 'i'],
550
- ["yi-magnifying-glass-minus","EV_ZOOM_OUT", false, 'i'],
551
- ["yi-arrows-to-eye", "EV_CENTER", false, 'i'],
552
- ["yi-arrows-rotate", "EV_REFRESH", false, 'i'],
553
- ];
554
-
555
- for(let item of c_icons) {
556
- let icon_name = item[0];
557
- let event_name = item[1];
558
- let disabled = item[2];
559
-
560
- let button = {
561
- class: `button ${event_name}`,
562
- style: {height: toolbar_wide, width: '2.5em'}
563
- };
564
- if(disabled) {
565
- button.disabled = true;
566
- }
567
- center_items.push(
568
- ['button', button,
569
- ['i', {style: 'font-size:1.5em; color:inherit;', class: icon_name}],
570
- {
571
- click: (evt) => {
572
- evt.stopPropagation();
573
- gobj_send_event(gobj, event_name, {evt: evt}, gobj);
574
- }
575
- }
576
- ]
577
- );
578
- }
529
+ /*
530
+ * The camera and the fold pair come from yui_graph_camera.js: the
531
+ * same drawings the gobj tree uses, because the two graphs sit in
532
+ * the same console and a copied toolbar drifts.
533
+ */
534
+ center_items = yui_graph_camera_items(gobj, priv.graph, toolbar_wide);
535
+ center_items.push(yui_graph_refresh_item(gobj, toolbar_wide));
579
536
 
580
537
  /*
581
538
  * Right: fold the tree.
@@ -616,11 +573,8 @@ function make_toolbar(gobj)
616
573
  }]
617
574
  );
618
575
 
619
- let right_items = [
620
- $layout_select,
621
- graph_fold_button(gobj, "EV_EXPAND_ALL", "expand all", true),
622
- graph_fold_button(gobj, "EV_COLLAPSE_ALL", "collapse all", false),
623
- ];
576
+ let right_items = [$layout_select].concat(
577
+ yui_graph_fold_items(gobj, toolbar_wide));
624
578
 
625
579
  const $toolbar = yui_toolbar({}, [
626
580
  ['div', {class: 'yui-horizontal-toolbar-section left'}, left_items],
@@ -677,6 +631,15 @@ function build_graph(gobj)
677
631
  gobj_send_event(gobj, "EV_NODE_CLICK", {evt: evt}, gobj);
678
632
  });
679
633
 
634
+ /* ONE hook for the readout, the same one the treedb graph uses:
635
+ * G6 fires it on any camera change, and the WHEEL is a camera
636
+ * change that passes through no action of ours — patching the text
637
+ * node from each zoom action would have left the readout lying
638
+ * after every notch. */
639
+ graph.on('aftertransform', () => {
640
+ yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
641
+ });
642
+
680
643
  graph.on(CanvasEvent.CLICK, (evt) => {
681
644
  // Canvas click
682
645
  });
@@ -1071,6 +1034,7 @@ function load_json(gobj)
1071
1034
  if(!preserve_view) {
1072
1035
  graph.fitView();
1073
1036
  }
1037
+ yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
1074
1038
  });
1075
1039
  }
1076
1040
  }
@@ -0,0 +1,150 @@
1
+ /***********************************************************************
2
+ * yui_graph_camera.js
3
+ *
4
+ * The camera and fold controls a G6 graph puts in a DOM toolbar,
5
+ * built in ONE place.
6
+ *
7
+ * This module exists because the same action was drawn three
8
+ * different ways across sibling views — actual size as a bare
9
+ * magnifier here and as `1:1` there, fit as `arrows-to-eye` here and
10
+ * as corner brackets there, fold as chevrons here and as an eye
11
+ * there. A user sees two of these graphs side by side in the same
12
+ * console, so a copied-and-drifted toolbar is not a detail.
13
+ *
14
+ * The VOCABULARY is not invented here. It is the one
15
+ * `c_g6_nodes_tree.js` settled on for the treedb graph, which draws
16
+ * its toolbar as a G6 plugin over an SVG sprite and therefore cannot
17
+ * share this code — only its decisions:
18
+ *
19
+ * zoom in / zoom out magnifiers
20
+ * zoom level a READOUT, because without it "1:1" is
21
+ * a jump to a value nobody was told
22
+ * fit corner brackets (`yi-fit`, the same
23
+ * path as the sprite's `g6-icon-fit`)
24
+ * actual size the WRITTEN `1:1` — the action is
25
+ * `zoomTo(1)`, and actual size is written
26
+ * in every editor that offers it, never
27
+ * drawn, because there is no glyph for it
28
+ *
29
+ * Fold is the one the treedb graph does not have, so its vocabulary
30
+ * comes from the other side of the family: the lazy tree viewer's own
31
+ * toolbar (`c_yui_json.js`) uses chevrons — open pointing down, closed
32
+ * pointing right — which is also the direction the per-node handles
33
+ * speak. An eye means show/hide, which is a different idea.
34
+ *
35
+ * Copyright (c) 2026, ArtGins.
36
+ * All Rights Reserved.
37
+ ***********************************************************************/
38
+ import {createElement2, gobj_send_event} from "@yuneta/gobj-js";
39
+
40
+ import {t} from "i18next";
41
+
42
+
43
+ /************************************************************
44
+ * One icon button that fires `event_name`.
45
+ ************************************************************/
46
+ function camera_button(gobj, icon, event_name, label_key, wide, extra_style)
47
+ {
48
+ return ['button', {class: `button ${event_name}`, type: 'button',
49
+ style: {height: wide, width: '2.5em'},
50
+ title: t(label_key), 'data-i18n-title': label_key,
51
+ 'aria-label': t(label_key), 'data-i18n-aria-label': label_key},
52
+ ['i', {style: 'font-size:1.5em; color:inherit;' + (extra_style || ''),
53
+ class: icon}],
54
+ {
55
+ click: (evt) => {
56
+ evt.stopPropagation();
57
+ gobj_send_event(gobj, event_name, {evt: evt}, gobj);
58
+ }
59
+ }
60
+ ];
61
+ }
62
+
63
+ /************************************************************
64
+ * The current zoom as a percentage.
65
+ ************************************************************/
66
+ export function yui_graph_zoom_text(graph)
67
+ {
68
+ if(!graph) {
69
+ return "100%";
70
+ }
71
+ try {
72
+ return Math.round(graph.getZoom() * 100) + "%";
73
+ } catch(e) {
74
+ return "100%";
75
+ }
76
+ }
77
+
78
+ /************************************************************
79
+ * Repaint the readout of a container that carries one.
80
+ *
81
+ * Call it from G6's `aftertransform`, which is the ONE hook
82
+ * that also covers the WHEEL — a camera change that passes
83
+ * through no action, so repainting from each zoom action
84
+ * leaves the number lying after every notch.
85
+ ************************************************************/
86
+ export function yui_graph_update_zoom($container, graph)
87
+ {
88
+ if(!$container) {
89
+ return;
90
+ }
91
+ let $readout = $container.querySelector('.GRAPH_ZOOM_LEVEL');
92
+ if(!$readout) {
93
+ return; /* a toolbar without a readout: nobody to report to */
94
+ }
95
+ $readout.textContent = yui_graph_zoom_text(graph);
96
+ }
97
+
98
+ /************************************************************
99
+ * The camera cluster: zoom in, zoom out, readout, fit,
100
+ * actual size. Returns createElement2 specs, in order.
101
+ *
102
+ * `wide` is the host toolbar's button height.
103
+ ************************************************************/
104
+ export function yui_graph_camera_items(gobj, graph, wide)
105
+ {
106
+ return [
107
+ camera_button(gobj, "yi-magnifying-glass-plus", "EV_ZOOM_IN", "zoom in", wide),
108
+ camera_button(gobj, "yi-magnifying-glass-minus", "EV_ZOOM_OUT", "zoom out", wide),
109
+ ['span', {class: 'GRAPH_ZOOM_LEVEL is-flex is-align-items-center px-2 has-text-grey',
110
+ style: 'font-size:.85rem; min-width:3.5em; justify-content:center;',
111
+ title: t('zoom level'), 'data-i18n-title': 'zoom level'},
112
+ yui_graph_zoom_text(graph)],
113
+ camera_button(gobj, "yi-fit", "EV_CENTER", "auto fit", wide),
114
+ /* Written, not drawn — see the header. */
115
+ ['button', {class: 'button EV_ZOOM_RESET', type: 'button',
116
+ style: {height: wide, width: '2.5em'},
117
+ title: t('actual size'), 'data-i18n-title': 'actual size',
118
+ 'aria-label': t('actual size'), 'data-i18n-aria-label': 'actual size'},
119
+ ['span', {style: 'font-weight:700;'}, '1:1'],
120
+ {
121
+ click: (evt) => {
122
+ evt.stopPropagation();
123
+ gobj_send_event(gobj, "EV_ZOOM_RESET", {evt: evt}, gobj);
124
+ }
125
+ }]
126
+ ];
127
+ }
128
+
129
+ /************************************************************
130
+ * The fold pair: expand all, collapse all. Chevrons, the
131
+ * open one rotated down — the same pair the lazy tree
132
+ * viewer's toolbar uses, and the same direction the
133
+ * per-node handles speak.
134
+ ************************************************************/
135
+ export function yui_graph_fold_items(gobj, wide)
136
+ {
137
+ return [
138
+ camera_button(gobj, "yi-chevron-right", "EV_EXPAND_ALL", "expand all", wide,
139
+ ' transform: rotate(90deg);'),
140
+ camera_button(gobj, "yi-chevron-right", "EV_COLLAPSE_ALL", "collapse all", wide),
141
+ ];
142
+ }
143
+
144
+ /************************************************************
145
+ * A refresh button, for the graphs that offer one.
146
+ ************************************************************/
147
+ export function yui_graph_refresh_item(gobj, wide)
148
+ {
149
+ return camera_button(gobj, "yi-arrows-rotate", "EV_REFRESH", "refresh", wide);
150
+ }
package/src/yui_icons.css CHANGED
@@ -376,3 +376,13 @@
376
376
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M208 80c0-26.5 21.5-48 48-48l64 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-8 0 0 40 152 0c30.9 0 56 25.1 56 56l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-4.4-3.6-8-8-8l-152 0 0 40 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-40-152 0c-4.4 0-8 3.6-8 8l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-30.9 25.1-56 56-56l152 0 0-40-8 0c-26.5 0-48-21.5-48-48l0-64z'/%3E%3C/svg%3E");
377
377
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512'%3E%3Cpath d='M208 80c0-26.5 21.5-48 48-48l64 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-8 0 0 40 152 0c30.9 0 56 25.1 56 56l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-4.4-3.6-8-8-8l-152 0 0 40 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-40-152 0c-4.4 0-8 3.6-8 8l0 32 8 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-64 0c-26.5 0-48-21.5-48-48l0-64c0-26.5 21.5-48 48-48l8 0 0-32c0-30.9 25.1-56 56-56l152 0 0-40-8 0c-26.5 0-48-21.5-48-48l0-64z'/%3E%3C/svg%3E");
378
378
  }
379
+
380
+ /* The camera's "fit" bracket, the SAME glyph the treedb graph's SVG
381
+ * sprite uses for the same action (`g6-icon-fit` in lib_icons.js).
382
+ * One action, one drawing: the JSON graph used to say it with
383
+ * `yi-arrows-to-eye`, which is a different picture for the same
384
+ * button in a sibling view. */
385
+ .yi-fit::before {
386
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M32 32C14.3 32 0 46.3 0 64l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64 64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 32zM64 352c0-17.7-14.3-32-32-32S0 334.3 0 352l0 96c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0 0-64zM320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32l-96 0zM448 352c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 64-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-96z'/%3E%3C/svg%3E");
387
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M32 32C14.3 32 0 46.3 0 64l0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-64 64 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 32zM64 352c0-17.7-14.3-32-32-32S0 334.3 0 352l0 96c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-64 0 0-64zM320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0 0 64c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96c0-17.7-14.3-32-32-32l-96 0zM448 352c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 64-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-96z'/%3E%3C/svg%3E");
388
+ }