@yuneta/gobj-ui 7.20.0 → 7.21.0

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.
@@ -10272,6 +10272,7 @@ function create_gclass$11(gclass_name) {
10272
10272
  * Register GClass
10273
10273
  ***************************************************************/
10274
10274
  function register_c_yui_json_graph() {
10275
+ if (gclass_find_by_name(GCLASS_NAME$11, false)) return 0;
10275
10276
  return create_gclass$11(GCLASS_NAME$11);
10276
10277
  }
10277
10278
  /************************************************************
@@ -10472,19 +10473,32 @@ function json_text_dump(root, max_chars) {
10472
10473
  * Only expanded containers are materialised in the DOM, so the tree
10473
10474
  * stays bounded no matter how large the source document is.
10474
10475
  *
10475
- * TWO VIEWS over the same working document, chosen by the `view_mode`
10476
- * attr ("tree" | "text") and switched from the toolbar: the lazy tree
10477
- * above, and the raw `JSON.stringify(…, 4)` dump of what is currently
10478
- * loaded. The text view is what you reach for to read a document as
10479
- * it is written, to select a slab of it, or to search it with the
10480
- * browser's own Ctrl+F; it shows the `__collapsed__` sentinels
10481
- * verbatim, because that is honestly what the client holds. The
10476
+ * THREE VIEWS over the same working document, chosen by the
10477
+ * `view_mode` attr ("tree" | "text" | "graph") and switched from the
10478
+ * toolbar. They answer three different questions:
10479
+ *
10480
+ * tree where is this value, and what is around it the lazy
10481
+ * view above, the only one that can drill.
10482
+ * text what does this document SAY, verbatim for reading it
10483
+ * as written, selecting a slab of it, or searching it with
10484
+ * the browser's own Ctrl+F.
10485
+ * graph what SHAPE is this — nesting as a hierarchy, drawn by a
10486
+ * hosted C_YUI_JSON_GRAPH child (AntV/G6).
10487
+ *
10488
+ * Neither text nor graph is lazy: both show what the client currently
10489
+ * holds, `__collapsed__` sentinels included, because that is honestly
10490
+ * what it has. Drill in the tree and they grow with it. The
10482
10491
  * tree-only controls (search, expand/collapse) hide with the tree.
10483
10492
  *
10493
+ * The graph child is built on FIRST entry into graph mode and not
10494
+ * before: G6 measures its container, so a graph created behind
10495
+ * `is-hidden` comes up 0x0, and a viewer nobody switches to graph
10496
+ * should not pay for a canvas.
10497
+ *
10484
10498
  * DOM is self-describing (UPPER_SNAKE logical classes): JSON_VIEWER /
10485
- * JSON_TOOLBAR / JSON_SEARCH / JSON_VIEW_MODE / JSON_TREE / JSON_TEXT /
10486
- * JSON_TEXT_BODY / JSON_ROW / JSON_KEY / JSON_VALUE / JSON_SUMMARY /
10487
- * JSON_COLLAPSED / JSON_TIME.
10499
+ * JSON_TOOLBAR / JSON_SEARCH / JSON_VIEW_SWITCH / JSON_VIEW_MODE /
10500
+ * JSON_TREE / JSON_TEXT / JSON_TEXT_BODY / JSON_GRAPH / JSON_ROW /
10501
+ * JSON_KEY / JSON_VALUE / JSON_SUMMARY / JSON_COLLAPSED / JSON_TIME.
10488
10502
  *
10489
10503
  * Copyright (c) 2026, ArtGins.
10490
10504
  * All Rights Reserved.
@@ -10495,6 +10509,23 @@ function json_text_dump(root, max_chars) {
10495
10509
  var GCLASS_NAME$10 = "C_YUI_JSON";
10496
10510
  var MAX_RENDER_ROWS = 5e3;
10497
10511
  var MAX_TEXT_CHARS = 2e6;
10512
+ var VIEWS = [
10513
+ {
10514
+ mode: "tree",
10515
+ icon: "yi-sitemap",
10516
+ key: "tree view"
10517
+ },
10518
+ {
10519
+ mode: "text",
10520
+ icon: "yi-code",
10521
+ key: "text view"
10522
+ },
10523
+ {
10524
+ mode: "graph",
10525
+ icon: "yi-hexagon-nodes",
10526
+ key: "graph view"
10527
+ }
10528
+ ];
10498
10529
  /***************************************************************
10499
10530
  * Data
10500
10531
  ***************************************************************/
@@ -10502,7 +10533,7 @@ var attrs_table$10 = [
10502
10533
  SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
10503
10534
  SDATA(data_type_t.DTP_STRING, "title", 0, "", "Optional header title (i18n key)"),
10504
10535
  SDATA(data_type_t.DTP_JSON, "json_data", 0, null, "Initial JSON to render (usually already collapsed)"),
10505
- SDATA(data_type_t.DTP_STRING, "view_mode", 0, "tree", "View: 'tree' (lazy tree) or 'text' (raw JSON dump)"),
10536
+ SDATA(data_type_t.DTP_STRING, "view_mode", 0, "tree", "View: 'tree' (lazy tree), 'text' (raw dump) or 'graph'"),
10506
10537
  SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "HTMLElement root, mounted by the parent"),
10507
10538
  SDATA_END()
10508
10539
  ];
@@ -10515,11 +10546,13 @@ var PRIVATE_DATA$10 = {
10515
10546
  $tree: null,
10516
10547
  $text: null,
10517
10548
  $text_body: null,
10549
+ $graph: null,
10550
+ graph_gobj: null,
10518
10551
  $search: null,
10519
10552
  $search_ctl: null,
10520
10553
  $expand_btn: null,
10521
10554
  $collapse_btn: null,
10522
- $mode_btn: null
10555
+ $mode_btns: null
10523
10556
  };
10524
10557
  var __gclass__$10 = null;
10525
10558
  /******************************
@@ -10556,6 +10589,7 @@ function mt_stop$10(gobj) {}
10556
10589
  * Framework Method: Destroy
10557
10590
  ***************************************************************/
10558
10591
  function mt_destroy$10(gobj) {
10592
+ teardown_graph_child(gobj);
10559
10593
  destroy_ui$5(gobj);
10560
10594
  }
10561
10595
  /***************************
@@ -10594,6 +10628,14 @@ function build_ui$9(gobj) {
10594
10628
  style: "flex:1 1 auto; min-height:0; overflow:auto;"
10595
10629
  },
10596
10630
  []
10631
+ ],
10632
+ [
10633
+ "div",
10634
+ {
10635
+ class: "JSON_GRAPH is-flex-grow-1 is-hidden",
10636
+ style: "flex:1 1 auto; overflow:hidden;"
10637
+ },
10638
+ []
10597
10639
  ]
10598
10640
  ]
10599
10641
  ]);
@@ -10604,7 +10646,12 @@ function build_ui$9(gobj) {
10604
10646
  priv.$search_ctl = $container.querySelector(".JSON_SEARCH_CONTROL");
10605
10647
  priv.$expand_btn = $container.querySelector(".EV_EXPAND_ALL");
10606
10648
  priv.$collapse_btn = $container.querySelector(".EV_COLLAPSE_ALL");
10607
- priv.$mode_btn = $container.querySelector(".JSON_VIEW_MODE");
10649
+ priv.$graph = $container.querySelector(".JSON_GRAPH");
10650
+ priv.$mode_btns = /* @__PURE__ */ new Map();
10651
+ VIEWS.forEach(function(v) {
10652
+ let $btn = $container.querySelector(".JSON_VIEW_MODE_" + v.mode.toUpperCase());
10653
+ if ($btn) priv.$mode_btns.set(v.mode, $btn);
10654
+ });
10608
10655
  refresh_language($container, t);
10609
10656
  }
10610
10657
  /************************************************************
@@ -10620,11 +10667,12 @@ function destroy_ui$5(gobj) {
10620
10667
  priv.$tree = null;
10621
10668
  priv.$text = null;
10622
10669
  priv.$text_body = null;
10670
+ priv.$graph = null;
10623
10671
  priv.$search = null;
10624
10672
  priv.$search_ctl = null;
10625
10673
  priv.$expand_btn = null;
10626
10674
  priv.$collapse_btn = null;
10627
- priv.$mode_btn = null;
10675
+ priv.$mode_btns = null;
10628
10676
  }
10629
10677
  /************************************************************
10630
10678
  * Toolbar: search + tree/text switch + expand-all /
@@ -10636,7 +10684,7 @@ function make_toolbar$2(gobj) {
10636
10684
  if (title) left_items.push([
10637
10685
  "span",
10638
10686
  {
10639
- class: "JSON_TITLE is-flex is-align-items-center px-2",
10687
+ class: "JSON_TITLE is-flex is-align-items-center px-2 is-hidden-mobile",
10640
10688
  style: "font-weight:600;",
10641
10689
  "data-i18n": title
10642
10690
  },
@@ -10669,7 +10717,7 @@ function make_toolbar$2(gobj) {
10669
10717
  attach_clear($search_control, $search_input);
10670
10718
  left_items.push($search_control);
10671
10719
  let right_items = [
10672
- view_mode_button(gobj),
10720
+ view_mode_switch(gobj),
10673
10721
  icon_button(gobj, "yi-chevron-right", "EV_EXPAND_ALL", "expand loaded"),
10674
10722
  icon_button(gobj, "yi-chevron-right", "EV_COLLAPSE_ALL", "collapse all"),
10675
10723
  icon_button(gobj, "yi-copy", "EV_COPY_ALL", "copy json")
@@ -10726,74 +10774,174 @@ function icon_button(gobj, icon, event_name, label_key) {
10726
10774
  ];
10727
10775
  }
10728
10776
  /************************************************************
10729
- * The tree/text switch.
10777
+ * The view switch: one button per view, the current one marked.
10730
10778
  *
10731
- * It shows the view it takes you TO the `code` glyph while the
10732
- * tree is on screen, the `sitemap` glyph while the text is and
10733
- * sends EV_SET_VIEW_MODE with no mode, which the action reads as
10734
- * "toggle". apply_view_mode() keeps the icon and its i18n keys in
10735
- * step with the attr.
10779
+ * Three views do not fit a toggle. A button that CYCLES cannot be
10780
+ * aimed reaching the graph from the tree would mean passing
10781
+ * through the text and rebuilding it on the way so each view gets
10782
+ * its own button and says where it goes. apply_view_mode() moves
10783
+ * the `is-active` mark.
10736
10784
  ************************************************************/
10737
- function view_mode_button(gobj) {
10785
+ function view_mode_switch(gobj) {
10738
10786
  return [
10739
- "button",
10740
- {
10741
- class: "button JSON_VIEW_MODE",
10742
- style: "width:2.5em;",
10743
- title: t("text view"),
10744
- "data-i18n-title": "text view",
10745
- "aria-label": t("text view"),
10746
- "data-i18n-aria-label": "text view"
10747
- },
10748
- [[
10749
- "span",
10750
- { class: "icon" },
10751
- [["i", { class: "yi-code" }]]
10752
- ]],
10753
- { click: function(evt) {
10754
- evt.stopPropagation();
10755
- gobj_send_event(gobj, "EV_SET_VIEW_MODE", {}, gobj);
10756
- } }
10787
+ "div",
10788
+ { class: "buttons has-addons is-flex-wrap-nowrap mb-0 JSON_VIEW_SWITCH" },
10789
+ VIEWS.map(function(v) {
10790
+ let label = view_label(v.mode);
10791
+ return [
10792
+ "button",
10793
+ {
10794
+ class: `button JSON_VIEW_MODE JSON_VIEW_MODE_${v.mode.toUpperCase()}`,
10795
+ type: "button",
10796
+ style: "width:2.5em;",
10797
+ title: label,
10798
+ "data-i18n-title": v.key,
10799
+ "aria-label": label,
10800
+ "data-i18n-aria-label": v.key
10801
+ },
10802
+ [[
10803
+ "span",
10804
+ { class: "icon" },
10805
+ [["i", { class: v.icon }]]
10806
+ ]],
10807
+ { click: function(evt) {
10808
+ evt.stopPropagation();
10809
+ gobj_send_event(gobj, "EV_SET_VIEW_MODE", { mode: v.mode }, gobj);
10810
+ } }
10811
+ ];
10812
+ })
10757
10813
  ];
10758
10814
  }
10759
10815
  /************************************************************
10760
- * The current view, normalised. Anything but "text" is the tree.
10816
+ * The label of a view, with every key SPELLED OUT inside t().
10817
+ *
10818
+ * Never t(VIEWS[i].key): the apps' validate-locales scans for
10819
+ * t("literal"), so a key reached through a variable is a key it
10820
+ * cannot demand — and i18next answers an undefined key with the key
10821
+ * ITSELF, which renders in lower-case English and never changes
10822
+ * language. That shipped once already (7.20.0, `tree view`); this
10823
+ * table would have hidden all three.
10824
+ ************************************************************/
10825
+ function view_label(mode) {
10826
+ switch (mode) {
10827
+ case "text": return t("text view");
10828
+ case "graph": return t("graph view");
10829
+ default: return t("tree view");
10830
+ }
10831
+ }
10832
+ /************************************************************
10833
+ * The current view, normalised. Anything unknown is the tree.
10761
10834
  ************************************************************/
10762
10835
  function current_view_mode(gobj) {
10763
- return gobj_read_str_attr(gobj, "view_mode") === "text" ? "text" : "tree";
10836
+ let mode = gobj_read_str_attr(gobj, "view_mode");
10837
+ return VIEWS.some((v) => v.mode === mode) ? mode : "tree";
10764
10838
  }
10765
10839
  /************************************************************
10766
10840
  * Show the chrome of the current view and hide the other's.
10767
10841
  ************************************************************/
10768
10842
  function apply_view_mode(gobj) {
10769
10843
  let priv = gobj.priv;
10770
- let text_mode = current_view_mode(gobj) === "text";
10771
- if (priv.$tree) priv.$tree.classList.toggle("is-hidden", text_mode);
10772
- if (priv.$text) priv.$text.classList.toggle("is-hidden", !text_mode);
10844
+ let mode = current_view_mode(gobj);
10845
+ let bodies = {
10846
+ tree: priv.$tree,
10847
+ text: priv.$text,
10848
+ graph: priv.$graph
10849
+ };
10850
+ for (let [m, $el] of Object.entries(bodies)) if ($el) $el.classList.toggle("is-hidden", m !== mode);
10773
10851
  [
10774
10852
  priv.$search_ctl,
10775
10853
  priv.$expand_btn,
10776
10854
  priv.$collapse_btn
10777
10855
  ].forEach(function($el) {
10778
- if ($el) $el.classList.toggle("is-hidden", text_mode);
10856
+ if ($el) $el.classList.toggle("is-hidden", mode !== "tree");
10779
10857
  });
10780
- if (priv.$mode_btn) {
10781
- let icon = text_mode ? "yi-sitemap" : "yi-code";
10782
- let key = text_mode ? "tree view" : "text view";
10783
- let $i = priv.$mode_btn.querySelector("i");
10784
- if ($i) $i.className = icon;
10785
- priv.$mode_btn.setAttribute("data-i18n-title", key);
10786
- priv.$mode_btn.setAttribute("data-i18n-aria-label", key);
10787
- priv.$mode_btn.setAttribute("title", t(key));
10788
- priv.$mode_btn.setAttribute("aria-label", t(key));
10858
+ if (priv.$mode_btns) for (let [m, $btn] of priv.$mode_btns) {
10859
+ $btn.classList.toggle("is-active", m === mode);
10860
+ $btn.setAttribute("aria-pressed", m === mode ? "true" : "false");
10789
10861
  }
10790
10862
  }
10791
10863
  /************************************************************
10792
10864
  * Render whichever view is current.
10793
10865
  ************************************************************/
10794
10866
  function render_view(gobj) {
10795
- if (current_view_mode(gobj) === "text") render_text(gobj);
10796
- else render_tree(gobj);
10867
+ switch (current_view_mode(gobj)) {
10868
+ case "text":
10869
+ render_text(gobj);
10870
+ break;
10871
+ case "graph":
10872
+ render_graph(gobj);
10873
+ break;
10874
+ default: render_tree(gobj);
10875
+ }
10876
+ }
10877
+ /************************************************************
10878
+ * Render the graph view.
10879
+ *
10880
+ * The C_YUI_JSON_GRAPH child is built HERE, on first entry, and
10881
+ * never in build_ui: G6 sizes itself from its container, and a
10882
+ * container behind `is-hidden` measures 0x0. By the time this
10883
+ * runs apply_view_mode() has already revealed the body.
10884
+ ************************************************************/
10885
+ function render_graph(gobj) {
10886
+ let priv = gobj.priv;
10887
+ let $graph = priv.$graph;
10888
+ if (!$graph) return;
10889
+ if (priv.root === null || priv.root === void 0) {
10890
+ teardown_graph_child(gobj);
10891
+ $graph.textContent = "";
10892
+ $graph.appendChild(createElement2([
10893
+ "div",
10894
+ {
10895
+ class: "JSON_EMPTY has-text-grey p-3",
10896
+ "data-i18n": "no data"
10897
+ },
10898
+ "no data"
10899
+ ]));
10900
+ refresh_language($graph, t);
10901
+ return;
10902
+ }
10903
+ if (!priv.graph_gobj) {
10904
+ $graph.textContent = "";
10905
+ if (!build_graph_child(gobj)) return;
10906
+ }
10907
+ gobj_send_event(priv.graph_gobj, "EV_LOAD_DATA", {
10908
+ data: priv.root,
10909
+ path: gobj_read_str_attr(gobj, "title") || ""
10910
+ }, gobj);
10911
+ gobj_send_event(priv.graph_gobj, "EV_RESIZE", {}, gobj);
10912
+ }
10913
+ /************************************************************
10914
+ * Build and mount the hosted graph child. Returns true on
10915
+ * success; every failure path logs.
10916
+ ************************************************************/
10917
+ function build_graph_child(gobj) {
10918
+ let priv = gobj.priv;
10919
+ let graph = gobj_create_pure_child("graph_" + clean_name(gobj_name(gobj)), "C_YUI_JSON_GRAPH", {}, gobj);
10920
+ if (!graph) {
10921
+ log_error(`${gobj_short_name(gobj)}: cannot create the graph viewer`);
10922
+ return false;
10923
+ }
10924
+ let $box = gobj_read_attr(graph, "$container");
10925
+ if (!$box) {
10926
+ log_error(`${gobj_short_name(gobj)}: the graph viewer built no $container`);
10927
+ gobj_destroy(graph);
10928
+ return false;
10929
+ }
10930
+ priv.$graph.appendChild($box);
10931
+ priv.graph_gobj = graph;
10932
+ gobj_start(graph);
10933
+ return true;
10934
+ }
10935
+ /************************************************************
10936
+ * Destroy the hosted graph child, if any.
10937
+ ************************************************************/
10938
+ function teardown_graph_child(gobj) {
10939
+ let priv = gobj.priv;
10940
+ if (priv.graph_gobj) {
10941
+ if (gobj_is_running(priv.graph_gobj)) gobj_stop(priv.graph_gobj);
10942
+ gobj_destroy(priv.graph_gobj);
10943
+ priv.graph_gobj = null;
10944
+ }
10797
10945
  }
10798
10946
  /************************************************************
10799
10947
  * Render the text view: the whole loaded document, verbatim.
@@ -11284,23 +11432,36 @@ function ac_copy_all(gobj, event, kw, src) {
11284
11432
  return 0;
11285
11433
  }
11286
11434
  /************************************************************
11287
- * EV_SET_VIEW_MODE { mode } — "tree" | "text".
11435
+ * EV_SET_VIEW_MODE { mode } — "tree" | "text" | "graph".
11288
11436
  *
11289
- * With no mode it toggles, which is what the toolbar button sends.
11437
+ * With no mode it ADVANCES to the next view in VIEWS order, which
11438
+ * is what the two-view toggle did when the list was two long. The
11439
+ * toolbar buttons always name their mode.
11290
11440
  ************************************************************/
11291
11441
  function ac_set_view_mode(gobj, event, kw, src) {
11292
11442
  let mode = kw.mode;
11293
- if (mode === void 0 || mode === null || mode === "") mode = current_view_mode(gobj) === "text" ? "tree" : "text";
11294
- else if (mode !== "tree" && mode !== "text") {
11443
+ if (mode === void 0 || mode === null || mode === "") mode = VIEWS[(VIEWS.findIndex((v) => v.mode === current_view_mode(gobj)) + 1) % VIEWS.length].mode;
11444
+ else if (!VIEWS.some((v) => v.mode === mode)) {
11295
11445
  log_error(`${GCLASS_NAME$10}: unknown view_mode '${mode}'`);
11296
11446
  return -1;
11297
11447
  }
11448
+ if (mode === current_view_mode(gobj)) return 0;
11298
11449
  gobj_write_attr(gobj, "view_mode", mode);
11299
11450
  apply_view_mode(gobj);
11300
11451
  render_view(gobj);
11301
11452
  return 0;
11302
11453
  }
11303
11454
  /************************************************************
11455
+ * EV_JSON_ITEM_CLICKED — from the hosted graph child.
11456
+ *
11457
+ * Republished under this gclass's own name so the host has ONE
11458
+ * contract and never has to know the child exists.
11459
+ ************************************************************/
11460
+ function ac_json_item_clicked(gobj, event, kw, src) {
11461
+ gobj_publish_event(gobj, "EV_JSON_ITEM_CLICKED", kw);
11462
+ return 0;
11463
+ }
11464
+ /************************************************************
11304
11465
  * EV_LANGUAGE_CHANGED — re-translate chrome + re-render
11305
11466
  ************************************************************/
11306
11467
  function ac_language_changed$3(gobj, event, kw, src) {
@@ -11388,6 +11549,11 @@ function create_gclass$10(gclass_name) {
11388
11549
  ac_set_json,
11389
11550
  null
11390
11551
  ],
11552
+ [
11553
+ "EV_JSON_ITEM_CLICKED",
11554
+ ac_json_item_clicked,
11555
+ null
11556
+ ],
11391
11557
  [
11392
11558
  "EV_SUBTREE_LOADED",
11393
11559
  ac_subtree_loaded,
@@ -11465,6 +11631,7 @@ function create_gclass$10(gclass_name) {
11465
11631
  ["EV_COLLAPSE_ALL", 0],
11466
11632
  ["EV_COPY_ALL", 0],
11467
11633
  ["EV_SET_VIEW_MODE", 0],
11634
+ ["EV_JSON_ITEM_CLICKED", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],
11468
11635
  ["EV_LANGUAGE_CHANGED", 0],
11469
11636
  ["EV_REFRESH", 0],
11470
11637
  ["EV_SHOW", 0],
@@ -11480,6 +11647,7 @@ function create_gclass$10(gclass_name) {
11480
11647
  ***************************************************************/
11481
11648
  function register_c_yui_json() {
11482
11649
  if (gclass_find_by_name(GCLASS_NAME$10, false)) return 0;
11650
+ if (!gclass_find_by_name("C_YUI_JSON_GRAPH", false)) register_c_yui_json_graph();
11483
11651
  return create_gclass$10(GCLASS_NAME$10);
11484
11652
  }
11485
11653
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.20.0",
3
+ "version": "7.21.0",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -1,7 +1,7 @@
1
1
  /***********************************************************************
2
2
  * c_yui_json.css
3
3
  *
4
- * Styling for C_YUI_JSON (lazy JSON tree viewer + raw text view).
4
+ * Styling for C_YUI_JSON (lazy tree + raw text + hosted graph).
5
5
  * Monospace tree, type-coloured leaves, dark-aware.
6
6
  * No transitions/animations by design (lib-yui convention).
7
7
  *
@@ -46,6 +46,31 @@
46
46
  border-bottom: 1px solid var(--bulma-border-weak, #e0e0e0);
47
47
  }
48
48
 
49
+ /*
50
+ * Who YIELDS when the toolbar runs out of room. The search box does:
51
+ * it grows to `max-width` when there is space and shrinks to nothing
52
+ * when there is not, while the buttons keep their size. A narrow
53
+ * search box still accepts what you type; a button pushed past the
54
+ * edge can only be reached by scrolling the toolbar, and the view
55
+ * switch is the last control that should be hiding.
56
+ *
57
+ * Measured at 390px: without this the search took 294 of 320 visible
58
+ * pixels and every button sat outside.
59
+ */
60
+ .C_YUI_JSON .yui-horizontal-toolbar-section.left {
61
+ flex: 1 1 auto;
62
+ min-width: 0;
63
+ }
64
+
65
+ .C_YUI_JSON .JSON_SEARCH_CONTROL {
66
+ flex: 1 1 auto;
67
+ min-width: 0;
68
+ }
69
+
70
+ .C_YUI_JSON .yui-horizontal-toolbar-section.right {
71
+ flex: 0 0 auto;
72
+ }
73
+
49
74
  .C_YUI_JSON .JSON_TREE {
50
75
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
51
76
  font-size: 1em;
@@ -188,6 +213,58 @@
188
213
  user-select: text;
189
214
  }
190
215
 
216
+ /*
217
+ * Graph view: the hosted C_YUI_JSON_GRAPH fills the body. No
218
+ * overflow here — G6 owns its viewport and pans inside it; a scroller
219
+ * on this box would compete with the canvas for the same drag.
220
+ */
221
+ /*
222
+ * The graph body carries a DEFINITE height, and that is the whole
223
+ * trick. The tree and the text push their own height; a canvas
224
+ * pushes NOTHING, so in a host that does not constrain the viewer the
225
+ * chain of `height:100%` down to G6's container resolved against auto
226
+ * and the graph came up 1061x2 — a hairline.
227
+ *
228
+ * It has to be `height`, not `min-height`: a percentage height does
229
+ * not resolve against a box whose size comes from a minimum, so
230
+ * `min-height` here left the container at 4px (its two borders).
231
+ * Measured both ways in the browser.
232
+ *
233
+ * `flex: 1 1 auto` keeps this a FLOOR and not a cage: in a host that
234
+ * does constrain the viewer — a modal, a C_YUI_WINDOW — the item
235
+ * grows past it and the graph follows the host.
236
+ */
237
+ .C_YUI_JSON .JSON_GRAPH {
238
+ height: 24rem;
239
+ }
240
+
241
+ .C_YUI_JSON .JSON_GRAPH > .C_YUI_JSON_GRAPH {
242
+ height: 100%;
243
+ }
244
+
245
+ /*
246
+ * The view switch. Bulma's `.buttons` gives every child a
247
+ * margin-bottom for the rows it expects to wrap; this strip never
248
+ * wraps, so that margin is only a gap that pushes the toolbar taller.
249
+ */
250
+ .C_YUI_JSON .JSON_VIEW_SWITCH {
251
+ margin-bottom: 0;
252
+ }
253
+
254
+ .C_YUI_JSON .JSON_VIEW_SWITCH .button {
255
+ margin-bottom: 0;
256
+ }
257
+
258
+ /* Bulma's `.button.is-active` only borrows the :active border, which on a
259
+ * three-way switch is not enough to say WHICH view you are looking at.
260
+ * Fill it instead — the mark has to survive a glance. */
261
+ .C_YUI_JSON .JSON_VIEW_MODE.is-active {
262
+ background-color: var(--bulma-link, #3273dc);
263
+ border-color: var(--bulma-link, #3273dc);
264
+ color: var(--bulma-link-invert, #fff);
265
+ z-index: 1;
266
+ }
267
+
191
268
  /*
192
269
  * Disable any transition a host stylesheet might inject.
193
270
  */