@yuneta/gobj-ui 5.2.0 → 5.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "5.2.0",
3
+ "version": "5.3.0",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -16,6 +16,7 @@
16
16
  --json-null: #475ed0;
17
17
  --json-key: var(--bulma-text-strong, #1a1a1a);
18
18
  --json-row-hover: rgba(0, 0, 0, 0.05);
19
+ --json-guide: #b5b5b5;
19
20
  }
20
21
 
21
22
  :root[data-theme="dark"] .C_YUI_JSON,
@@ -26,6 +27,7 @@
26
27
  --json-null: #90a4f4;
27
28
  --json-key: var(--bulma-text-strong, #e6e6e6);
28
29
  --json-row-hover: rgba(255, 255, 255, 0.06);
30
+ --json-guide: #5a5a5a;
29
31
  }
30
32
 
31
33
  @media (prefers-color-scheme: dark) {
@@ -36,6 +38,7 @@
36
38
  --json-null: #90a4f4;
37
39
  --json-key: var(--bulma-text-strong, #e6e6e6);
38
40
  --json-row-hover: rgba(255, 255, 255, 0.06);
41
+ --json-guide: #5a5a5a;
39
42
  }
40
43
  }
41
44
 
@@ -55,10 +58,27 @@
55
58
  align-items: flex-start;
56
59
  white-space: pre;
57
60
  padding-right: 0.5rem;
58
- }
59
-
61
+ /* Depth guides. The rows are SIBLINGS with growing padding, not
62
+ * nested boxes, so there is no element to hang a border-left on:
63
+ * the guides are painted as a repeating gradient across the row's
64
+ * own indentation, one line per ancestor level. The tile is
65
+ * parked at the first level's column and its WIDTH is set per row
66
+ * (row_indent), so a row only ever draws the levels above it.
67
+ * Four characters, like every other indentation. */
68
+ background-image: repeating-linear-gradient(
69
+ to right,
70
+ var(--json-guide) 0 1px,
71
+ transparent 1px 4ch
72
+ );
73
+ background-position: 0.4ch 0;
74
+ background-repeat: no-repeat;
75
+ }
76
+
77
+ /* background-COLOR, never the `background` shorthand: the shorthand
78
+ * resets background-image and the depth guides vanish under the
79
+ * cursor, on the one row the reader is looking at. */
60
80
  .C_YUI_JSON .JSON_ROW:hover {
61
- background: var(--json-row-hover);
81
+ background-color: var(--json-row-hover);
62
82
  }
63
83
 
64
84
  .C_YUI_JSON .JSON_ROW_BODY {
package/src/c_yui_json.js CHANGED
@@ -548,11 +548,25 @@ function leaf_row(key, value, depth)
548
548
  }
549
549
 
550
550
  /************************************************************
551
- * Depth indentation (inline style keeps it CSS-framework free)
551
+ * Depth indentation (inline style keeps it CSS-framework free).
552
+ *
553
+ * FOUR characters per level, house rule: structure is read as
554
+ * indentation, and it has to be the same four everywhere — this
555
+ * viewer, the raw dump behind it, the site map's tree. `ch` is
556
+ * the character width of the row's own font, so the rows line up
557
+ * with the monospace text they carry instead of drifting from it
558
+ * at some zoom level.
552
559
  ************************************************************/
560
+ const INDENT_CH = 4;
561
+ const INDENT_PAD = 0.4;
562
+
553
563
  function row_indent(depth)
554
564
  {
555
- return 'padding-left:' + (0.4 + depth * 1.1) + 'em;';
565
+ /* `background-size` bounds the depth guides painted by the CSS to
566
+ * this row's own indentation: one vertical line per ancestor
567
+ * level, none across the content. */
568
+ return 'padding-left:' + (INDENT_PAD + depth * INDENT_CH) + 'ch;' +
569
+ 'background-size:' + (depth * INDENT_CH) + 'ch 100%;';
556
570
  }
557
571
 
558
572
  /************************************************************
@@ -760,7 +774,7 @@ function ac_copy_all(gobj, event, kw, src)
760
774
  if(priv.root === null || priv.root === undefined) {
761
775
  return 0;
762
776
  }
763
- let text = JSON.stringify(priv.root, null, 2);
777
+ let text = JSON.stringify(priv.root, null, 4);
764
778
  if(navigator.clipboard && navigator.clipboard.writeText) {
765
779
  navigator.clipboard.writeText(text).catch(function(e) {
766
780
  log_error(`${GCLASS_NAME}: clipboard write failed: ${e}`);
package/src/c_yui_nav.js CHANGED
@@ -15,6 +15,13 @@
15
15
  * "cards" — grid of tappable cards (section-index landing;
16
16
  * mounted as a stage view by C_YUI_SHELL when a
17
17
  * primary item declares submenu.index)
18
+ * "breadcrumb" — the PATH as one line ("Cards › Energy › South
19
+ * hall › Meter 3"), each crumb a link to that
20
+ * level. Unlike every other layout its items
21
+ * are not a node's children but the trail down
22
+ * to where the user is: the second way to show
23
+ * depth, for when stacking one strip per level
24
+ * costs more vertical room than it is worth.
18
25
  * "backbar" — single "← <section>" link back to the section
19
26
  * index; the mobile replacement of the tab strip
20
27
  * for submenu.index sections (the shell collapses
@@ -76,7 +83,7 @@ const GCLASS_NAME = "C_YUI_NAV";
76
83
 
77
84
  const SUPPORTED_LAYOUTS = [
78
85
  "vertical", "icon-bar", "tabs", "drawer", "submenu", "accordion", "cards",
79
- "backbar"
86
+ "backbar", "breadcrumb"
80
87
  ];
81
88
 
82
89
  /* Decorative items have no `route` and never participate in
@@ -220,6 +227,7 @@ function build_ui(gobj)
220
227
  case "accordion": $container = render_accordion(gobj, items); break;
221
228
  case "cards": $container = render_cards(gobj, items); break;
222
229
  case "backbar": $container = render_backbar(gobj); break;
230
+ case "breadcrumb": $container = render_breadcrumb(gobj, items); break;
223
231
  default: $container = render_vertical(gobj, items);
224
232
  }
225
233
  $container.setAttribute("data-nav-zone", zone);
@@ -254,6 +262,13 @@ function build_ui(gobj)
254
262
  wire_clicks(gobj, $container);
255
263
 
256
264
  gobj_write_attr(gobj, "$container", $container);
265
+
266
+ /* A nav can be CREATED already knowing which item is active (a
267
+ * C_YUI_NODE builds its chrome for the child it is about to show).
268
+ * Applying the highlight only on rebuild lost it for every such
269
+ * nav — the shell never noticed because it always follows creation
270
+ * with an EV_ROUTE_CHANGED. */
271
+ apply_active_route(gobj);
257
272
  }
258
273
 
259
274
  /************************************************************
@@ -286,8 +301,6 @@ function rebuild(gobj)
286
301
  if($old && $old.parentNode) {
287
302
  $old.parentNode.removeChild($old);
288
303
  }
289
-
290
- apply_active_route(gobj);
291
304
  }
292
305
 
293
306
  /************************************************************
@@ -432,6 +445,53 @@ function render_cards(gobj, items)
432
445
  * collapses the whole secondary zone (tabs and cards never
433
446
  * coexist), so the bar needs no self-hiding logic.
434
447
  ************************************************************/
448
+ /************************************************************
449
+ * breadcrumb: the trail to here, one line.
450
+ *
451
+ * Bulma's own markup (nav.breadcrumb > ul > li, separators drawn by
452
+ * CSS), with the SAME data-route/data-item-id contract as every
453
+ * other layout — so the delegated click handler and the active
454
+ * highlight work unchanged, and the last crumb marks itself through
455
+ * `active_route` like a tab does.
456
+ ************************************************************/
457
+ function render_breadcrumb(gobj, items)
458
+ {
459
+ let show_label = gobj_read_attr(gobj, "show_label");
460
+ let lis = [];
461
+
462
+ for(let it of (items || [])) {
463
+ if(is_decorative(it)) {
464
+ continue;
465
+ }
466
+ let children = [];
467
+ if(it.icon) {
468
+ children.push(["span", {class: "icon is-small"},
469
+ ["i", {class: it.icon, "aria-hidden": "true"}]]);
470
+ }
471
+ if(show_label !== false) {
472
+ let label = it.name || it.id || "";
473
+ children.push(["span", {class: "yui-nav-label", i18n: label}, label]);
474
+ }
475
+ let a_attrs = {
476
+ class: "yui-nav-item yui-nav-crumb",
477
+ href: it.route ? "#" + it.route : "#",
478
+ "data-item-id": it.id,
479
+ "data-route": it.route || "",
480
+ "data-disabled": it.disabled ? "1" : "0"
481
+ };
482
+ if(it.name) {
483
+ a_attrs["aria-label"] = it.name;
484
+ a_attrs["data-i18n-aria-label"] = it.name;
485
+ }
486
+ lis.push(["li", {}, [["a", a_attrs, children]]]);
487
+ }
488
+
489
+ return createElement2(
490
+ ["nav", {class: "breadcrumb is-small", "aria-label": "breadcrumbs"},
491
+ [["ul", {}, lis]]]
492
+ );
493
+ }
494
+
435
495
  function render_backbar(gobj)
436
496
  {
437
497
  let back_route = gobj_read_attr(gobj, "back_route") || "";
@@ -0,0 +1,77 @@
1
+ /***********************************************************************
2
+ * c_yui_node.css
3
+ *
4
+ * Layout of C_YUI_NODE — deliberately thin: the projections are
5
+ * C_YUI_NAV renders and bring their own styling (cards, tabs,
6
+ * backbar…), so all this file owns is the vertical stack
7
+ * chrome / body and the nesting that lets a deep branch scroll.
8
+ *
9
+ * No colours beyond Bulma variables, and no transitions (house
10
+ * rule): a level change appears instantly.
11
+ *
12
+ * Copyright (c) 2026, ArtGins.
13
+ * All Rights Reserved.
14
+ ***********************************************************************/
15
+
16
+ .NODE_VIEW {
17
+ display: flex;
18
+ flex-direction: column;
19
+ min-height: 0;
20
+ height: 100%;
21
+ width: 100%;
22
+ }
23
+
24
+ /*--- chrome: the parent's projection while a child is showing ---*/
25
+ .NODE_CHROME {
26
+ flex: 0 0 auto;
27
+ }
28
+ .NODE_CHROME:empty {
29
+ display: none;
30
+ }
31
+
32
+ /*--- body: content, index projection, or the active child ---*/
33
+ .NODE_BODY {
34
+ display: flex;
35
+ flex-direction: column;
36
+ flex: 1 1 auto;
37
+ min-height: 0;
38
+ }
39
+
40
+ .NODE_CONTENT {
41
+ flex: 0 0 auto;
42
+ }
43
+
44
+ /* The index projection (a card grid, a list…) is the page when this
45
+ * node is the tip: it takes the remaining height and scrolls. */
46
+ .NODE_INDEX {
47
+ flex: 1 1 auto;
48
+ min-height: 0;
49
+ overflow-y: auto;
50
+ }
51
+
52
+ /* A node whose index projection targets a ZONE (the root, painting
53
+ * the rail) leaves this empty — an empty flex child would still eat
54
+ * the body's height. */
55
+ .NODE_INDEX:empty {
56
+ display: none;
57
+ }
58
+
59
+ /* A node with BOTH content and children: the content keeps its
60
+ * natural height and the projection sits under it. */
61
+ .NODE_CONTENT + .NODE_INDEX {
62
+ border-top: 1px solid var(--bulma-border-weak, #dbdbdb);
63
+ }
64
+
65
+ /* The active child's own NODE_VIEW nests here — min-height:0 all the
66
+ * way down or an inner overflow-y never gets a bounded height. */
67
+ .NODE_CHILD {
68
+ display: flex;
69
+ flex-direction: column;
70
+ flex: 1 1 auto;
71
+ min-height: 0;
72
+ }
73
+
74
+ .NODE_EMPTY {
75
+ padding: 1rem;
76
+ color: var(--bulma-text-weak, #7a7a7a);
77
+ }