@yuneta/gobj-ui 7.26.34 → 7.26.35

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.
Files changed (43) hide show
  1. package/README.md +223 -53
  2. package/dist/gobj-ui.cjs.js +1035 -141
  3. package/dist/gobj-ui.es.js +1035 -141
  4. package/package.json +2 -2
  5. package/scripts/validate-locales.mjs +672 -96
  6. package/src/c_yui_form.js +186 -9
  7. package/src/c_yui_gclass.js +2 -0
  8. package/src/c_yui_gobj_tree_js.js +2 -0
  9. package/src/c_yui_json.js +2 -0
  10. package/src/c_yui_json_graph.js +2 -0
  11. package/src/c_yui_nav.js +23 -5
  12. package/src/c_yui_shell.css +16 -0
  13. package/src/c_yui_shell.js +282 -26
  14. package/src/c_yui_treedb_graph.js +26 -8
  15. package/src/c_yui_treedb_topic_with_form.js +40 -18
  16. package/src/c_yui_treedb_topics.js +34 -11
  17. package/src/c_yui_window.js +132 -32
  18. package/src/c_yui_window_manager.js +156 -44
  19. package/src/form_json_editor_names.js +448 -0
  20. package/src/form_json_editor_names.wiring.test.js +595 -0
  21. package/src/form_label_i18n.wiring.test.js +169 -0
  22. package/src/i18n_key_functions.js +14 -6
  23. package/src/i18n_key_functions.test.js +124 -2
  24. package/src/lib_maplibre.js +26 -2
  25. package/src/lib_maplibre_relocalize.test.js +78 -0
  26. package/src/named_controls.wiring.test.js +468 -0
  27. package/src/nav_cards_helpers.js +9 -1
  28. package/src/nav_item_badge.wiring.test.js +182 -1
  29. package/src/shell_route_map.js +25 -8
  30. package/src/table_editor_name.wiring.test.js +201 -0
  31. package/src/treedb_column_title.wiring.test.js +151 -0
  32. package/src/treedb_json_drill.wiring.test.js +41 -1
  33. package/src/window_layer.wiring.test.js +308 -43
  34. package/src/window_show.wiring.test.js +6 -2
  35. package/src/window_stacking.wiring.test.js +334 -0
  36. package/src/yui_badge.js +58 -13
  37. package/src/yui_badge.test.js +40 -3
  38. package/src/yui_dev.js +2 -1
  39. package/src/yui_file_field.js +2 -0
  40. package/src/yui_frontend_view.js +2 -1
  41. package/src/yui_gclass_view.js +9 -6
  42. package/src/yui_tabulator_i18n.js +42 -0
  43. package/src/yui_tabulator_i18n.test.js +48 -1
@@ -545,14 +545,26 @@ function text_list_item_formatter(label) {
545
545
  * ALWAYS RENDERED, hidden when empty, so the runtime setters have a
546
546
  * node to write into and never rebuild the control that holds it.
547
547
  *
548
- * role="status" and NOT aria-hidden, deliberately. The control
549
- * carries an explicit aria-label, and an explicit label REPLACES
550
- * the element's content for a screen reader -- so a badge inside
551
- * it would be silent. As its own live region it is both read and
552
- * announced when the count changes, which is the whole point of a
553
- * badge that means "something needs you". For the same reason a
554
- * write that changes nothing touches no DOM: rewriting the same
555
- * number would have it announced again on every tick.
548
+ * PART OF THE CONTROL'S NAME (name_control_with_badge()). The
549
+ * control carries an explicit aria-label, and an explicit label
550
+ * REPLACES the element's content for a screen reader -- so a badge
551
+ * inside it is silent, and inside a <button> (the toolbar, an
552
+ * accordion head) its children are not even in the tree. The
553
+ * control is labelled by ITSELF (its aria-label, which
554
+ * refresh_language() re-translates) and by the badge: the name is
555
+ * "Alarms 3" while there is a count, "Alarms" while there is none,
556
+ * with nothing composed in javascript that a language switch would
557
+ * leave behind.
558
+ *
559
+ * NOT a live region any more. Up to 7.26.34 the badge was its own
560
+ * role="status", and that said "3" with no word of what has three;
561
+ * hidden while empty, it was not in the tree when the first count
562
+ * arrived, so 0 -> 1 was most likely never said; and inside a
563
+ * <button> it was not there at all. The change is said by the
564
+ * shell, in one live region of its own that is always there, with
565
+ * the name of the item: "Alarms, 3" (yui_shell_set_nav_item_badge,
566
+ * yui_shell_set_toolbar_item_badge). A write that changes nothing
567
+ * still touches no DOM.
556
568
  *
557
569
  * A TONE, optional: one of BADGE_TONES, drawn as Bulma's
558
570
  * `is-<tone>` on the badge. It says HOW BAD the count is -- the
@@ -578,6 +590,7 @@ var BADGE_TONES = Object.freeze([
578
590
  "primary",
579
591
  "link"
580
592
  ]);
593
+ var __badge_seq__ = 0;
581
594
  /************************************************************
582
595
  * The text a badge shows for `value`, "" when it shows none.
583
596
  ************************************************************/
@@ -611,10 +624,7 @@ function badge_tone(tone, who) {
611
624
  function badge_node(value, tone, classes, who) {
612
625
  let text = badge_text(value);
613
626
  let known = badge_tone(tone, who);
614
- let attrs = {
615
- class: known ? `${classes} is-${known}` : classes,
616
- role: "status"
617
- };
627
+ let attrs = { class: known ? `${classes} is-${known}` : classes };
618
628
  if (!text) attrs.hidden = "hidden";
619
629
  return [
620
630
  "span",
@@ -623,6 +633,31 @@ function badge_node(value, tone, classes, who) {
623
633
  ];
624
634
  }
625
635
  /************************************************************
636
+ * Make the count part of the NAME of the control that carries
637
+ * the badge: its `aria-labelledby` points at the control itself
638
+ * and at the badge.
639
+ *
640
+ * The control keeps its `aria-label` and its
641
+ * data-i18n-aria-label: a self-reference reads the aria-label,
642
+ * so the name is the translated label followed by the badge's
643
+ * text -- "Alarms 3" -- or the label alone while the badge is
644
+ * empty. A painted count is in the name at once, and a language
645
+ * switch re-translates the label as it always did.
646
+ *
647
+ * Both are createElement2() descriptions, before anything is
648
+ * built: `control_attrs` (the attrs of the <a> or <button>, which
649
+ * must carry its aria-label) gets an `id` when it has none, and
650
+ * the `aria-labelledby`; `badge` (what badge_node() answered)
651
+ * gets its `id`.
652
+ ************************************************************/
653
+ function name_control_with_badge(control_attrs, badge) {
654
+ let seq = ++__badge_seq__;
655
+ if (!control_attrs.id) control_attrs.id = `yui-badged-${seq}`;
656
+ let badge_attrs = badge[1];
657
+ badge_attrs.id = `yui-badge-${seq}`;
658
+ control_attrs["aria-labelledby"] = `${control_attrs.id} ${badge_attrs.id}`;
659
+ }
660
+ /************************************************************
626
661
  * Write a badge that is already in the DOM, as badge_node()
627
662
  * draws it. `tone` is one badge_tone() answered: the setter
628
663
  * checks it once for all the badges it paints.
@@ -1019,6 +1054,8 @@ function secondary_nav_renders(item, layout) {
1019
1054
  *
1020
1055
  * Parses a JSON config to build:
1021
1056
  * - Layers (z-stacked): base, overlay, popup, modal, notification, loading
1057
+ * (the overlay holds the WINDOW layer, where the windows live,
1058
+ * under the drawer: yui_shell_window_layer())
1022
1059
  * - Zones (inside base layer): top, top-sub, left, center, right,
1023
1060
  * bottom-sub, bottom
1024
1061
  * - Menus (primary + submenus) rendered via C_YUI_NAV — one nav
@@ -1046,6 +1083,8 @@ function secondary_nav_renders(item, layout) {
1046
1083
  ***************************************************************/
1047
1084
  var GCLASS_NAME$22 = "C_YUI_SHELL";
1048
1085
  var __last_shell__ = null;
1086
+ var __shell_watchers__ = [];
1087
+ var __window_z__ = 10;
1049
1088
  var ZONE_IDS = [
1050
1089
  "top",
1051
1090
  "top-sub",
@@ -1080,10 +1119,12 @@ var attrs_table$22 = [
1080
1119
  var PRIVATE_DATA$22 = {
1081
1120
  zones: {},
1082
1121
  layers: {},
1122
+ window_layer: null,
1083
1123
  stages: {},
1084
1124
  navs: [],
1085
1125
  item_index: {},
1086
1126
  nav_badges: {},
1127
+ badge_status: null,
1087
1128
  dynamic_items: {},
1088
1129
  section_pos: {},
1089
1130
  route_mru: [],
@@ -1124,6 +1165,7 @@ function mt_create$22(gobj) {
1124
1165
  if (subscriber) (0, _yuneta_gobj_js.gobj_subscribe_event)(gobj, null, {}, subscriber);
1125
1166
  __last_shell__ = gobj;
1126
1167
  build_ui$19(gobj);
1168
+ tell_shell_watchers(gobj);
1127
1169
  }
1128
1170
  /***************************************************************
1129
1171
  * Framework Method: Start
@@ -1307,10 +1349,11 @@ function mt_stop$20(gobj) {
1307
1349
  ***************************************************************/
1308
1350
  function mt_destroy$22(gobj) {
1309
1351
  close_toolbar_dropdown(gobj);
1352
+ if (__last_shell__ === gobj) __last_shell__ = null;
1310
1353
  let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
1311
1354
  if ($container && $container.parentNode) $container.parentNode.removeChild($container);
1355
+ tell_shell_watchers(gobj);
1312
1356
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", null);
1313
- if (__last_shell__ === gobj) __last_shell__ = null;
1314
1357
  }
1315
1358
  /***************************
1316
1359
  * Local Methods
@@ -1433,6 +1476,17 @@ function build_ui$19(gobj) {
1433
1476
  $container.appendChild($layer);
1434
1477
  priv.layers[id] = $layer;
1435
1478
  }
1479
+ let $windows = (0, _yuneta_gobj_js.createElement2)(["div", { class: "SHELL_WINDOW_LAYER yui-window-layer" }]);
1480
+ priv.layers.overlay.appendChild($windows);
1481
+ priv.window_layer = $windows;
1482
+ let $status = (0, _yuneta_gobj_js.createElement2)(["div", {
1483
+ class: "SHELL_BADGE_STATUS is-sr-only",
1484
+ role: "status",
1485
+ "aria-live": "polite",
1486
+ "aria-atomic": "true"
1487
+ }]);
1488
+ $container.appendChild($status);
1489
+ priv.badge_status = $status;
1436
1490
  let $base = priv.layers.base;
1437
1491
  $base.classList.add("yui-base-grid");
1438
1492
  let zones_cfg = shell_cfg.zones || {};
@@ -2026,14 +2080,18 @@ function toolbar_link_attrs(it) {
2026
2080
  ************************************************************/
2027
2081
  function build_toolbar_action_item(gobj, it) {
2028
2082
  let children = [];
2029
- if (!(0, _yuneta_gobj_js.empty_string)(it.icon)) children.push([
2030
- "span",
2031
- { class: "icon yui-toolbar-icon" },
2032
- [["i", {
2033
- class: it.icon,
2034
- "aria-hidden": "true"
2035
- }], badge_node(it.badge, it.badge_tone, "TOOLBAR_ITEM_BADGE yui-badge yui-toolbar-badge", `C_YUI_SHELL: toolbar item '${it.id || "?"}'`)]
2036
- ]);
2083
+ let badge = null;
2084
+ if (!(0, _yuneta_gobj_js.empty_string)(it.icon)) {
2085
+ badge = badge_node(it.badge, it.badge_tone, "TOOLBAR_ITEM_BADGE yui-badge yui-toolbar-badge", `C_YUI_SHELL: toolbar item '${it.id || "?"}'`);
2086
+ children.push([
2087
+ "span",
2088
+ { class: "icon yui-toolbar-icon" },
2089
+ [["i", {
2090
+ class: it.icon,
2091
+ "aria-hidden": "true"
2092
+ }], badge]
2093
+ ]);
2094
+ }
2037
2095
  if (!(0, _yuneta_gobj_js.empty_string)(it.name)) children.push([
2038
2096
  "span",
2039
2097
  {
@@ -2060,6 +2118,7 @@ function build_toolbar_action_item(gobj, it) {
2060
2118
  btn_attrs.title = tip;
2061
2119
  btn_attrs["data-i18n-title"] = tip;
2062
2120
  }
2121
+ if (badge && !(0, _yuneta_gobj_js.empty_string)(aria_key)) name_control_with_badge(btn_attrs, badge);
2063
2122
  let link = toolbar_link_attrs(it);
2064
2123
  if (link) {
2065
2124
  delete btn_attrs.type;
@@ -2844,23 +2903,32 @@ function update_secondary_nav_visibility(gobj, entry) {
2844
2903
  }
2845
2904
  }
2846
2905
  /************************************************************
2847
- * Does a menu of this shell declare an item `item_id`: an item
2848
- * of a menu of app_config.json, of one of their submenus, or
2849
- * one given to yui_shell_set_submenu() since.
2906
+ * The item `item_id` a menu of this shell declares, null when
2907
+ * none does: an item of a menu of app_config.json, of one of
2908
+ * their submenus, or one given to yui_shell_set_submenu() since
2909
+ * -- which comes first, because it replaced what the submenu
2910
+ * declared and is what the navs draw.
2850
2911
  ************************************************************/
2851
- function nav_item_declared(gobj, item_id) {
2912
+ function nav_item_of(gobj, item_id) {
2852
2913
  let priv = gobj.priv;
2853
2914
  let config = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "config") || {};
2854
2915
  let menus = (0, _yuneta_gobj_js.is_object)(config.menu) ? config.menu : {};
2855
- let holds = (items) => (0, _yuneta_gobj_js.is_array)(items) && items.some((it) => it && it.id === item_id);
2916
+ let find = (items) => (0, _yuneta_gobj_js.is_array)(items) && items.find((it) => it && it.id === item_id) || null;
2917
+ for (let parent_item_id in priv.dynamic_items) {
2918
+ let it = find(priv.dynamic_items[parent_item_id]);
2919
+ if (it) return it;
2920
+ }
2856
2921
  for (let menu_id in menus) {
2857
2922
  let items = menus[menu_id] && menus[menu_id].items;
2858
2923
  if (!(0, _yuneta_gobj_js.is_array)(items)) continue;
2859
- if (holds(items)) return true;
2860
- for (let it of items) if (it && it.submenu && holds(it.submenu.items)) return true;
2924
+ let it = find(items);
2925
+ if (it) return it;
2926
+ for (let parent of items) {
2927
+ let sub = parent && parent.submenu && find(parent.submenu.items);
2928
+ if (sub) return sub;
2929
+ }
2861
2930
  }
2862
- for (let parent_item_id in priv.dynamic_items) if (holds(priv.dynamic_items[parent_item_id])) return true;
2863
- return false;
2931
+ return null;
2864
2932
  }
2865
2933
  /************************************************************
2866
2934
  * Every C_YUI_NAV of this shell: the navs of its menus, and the
@@ -2878,6 +2946,61 @@ function shell_navs(gobj) {
2878
2946
  }
2879
2947
  return navs;
2880
2948
  }
2949
+ /************************************************************
2950
+ * Say a badge that changed, with the name of what it counts:
2951
+ * "Alarms, 3", in the shell's live region (build_ui()).
2952
+ *
2953
+ * The badge itself is no live region (yui_badge.js): its number
2954
+ * alone said nothing, hidden while empty it missed the first
2955
+ * count, and inside a <button> it was not there at all. The
2956
+ * setters call this only when the text a reader would see
2957
+ * changes and is not empty: a count going away is not news, and
2958
+ * the same count again is not either.
2959
+ *
2960
+ * `name_key` is the key of the control's name; the phrase is a
2961
+ * key of its own, so a language orders and punctuates it its
2962
+ * way. Said once: a live region needs no re-translation.
2963
+ ************************************************************/
2964
+ function announce_badge(gobj, name_key, text) {
2965
+ let priv = gobj.priv;
2966
+ let $status = priv.badge_status;
2967
+ if (!$status) {
2968
+ (0, _yuneta_gobj_js.log_error)(`C_YUI_SHELL: no live region to say badge '${name_key}': ${text}`);
2969
+ return;
2970
+ }
2971
+ let message;
2972
+ if (typeof priv.translator === "function") {
2973
+ const tr = priv.translator;
2974
+ message = tr("{{name}}, {{n}}", {
2975
+ name: tr(name_key),
2976
+ n: text
2977
+ });
2978
+ } else message = `${name_key}, ${text}`;
2979
+ $status.textContent = message;
2980
+ }
2981
+ /************************************************************
2982
+ * A shell was built (mt_create) or is being destroyed
2983
+ * (mt_destroy): tell every gobj that keeps DOM in a shell's
2984
+ * layers without being its child (yui_shell_watch_changes()).
2985
+ * Each one decides in its own action what it means for it --
2986
+ * the trace shows EV_SHELL_CHANGED arriving, from a `!!` shell
2987
+ * when it is the one going.
2988
+ *
2989
+ * An event, not a call into the window's module: the shell does
2990
+ * not import its watchers, and the move is work, so it crosses
2991
+ * their FSM. A watcher that is being destroyed is one whose
2992
+ * mt_destroy did not unwatch: it is dropped, and said.
2993
+ ************************************************************/
2994
+ function tell_shell_watchers(gobj) {
2995
+ for (let watcher of __shell_watchers__.slice()) {
2996
+ if ((0, _yuneta_gobj_js.gobj_is_destroying)(watcher)) {
2997
+ (0, _yuneta_gobj_js.log_error)(`C_YUI_SHELL: a destroyed gobj still watches the shell (${(0, _yuneta_gobj_js.gobj_short_name)(watcher)}): it must call yui_shell_unwatch_changes() in its mt_destroy`);
2998
+ yui_shell_unwatch_changes(watcher);
2999
+ continue;
3000
+ }
3001
+ (0, _yuneta_gobj_js.gobj_send_event)(watcher, "EV_SHELL_CHANGED", {}, gobj);
3002
+ }
3003
+ }
2881
3004
  /***************************
2882
3005
  * Actions
2883
3006
  ***************************/
@@ -3147,6 +3270,81 @@ function yui_shell_of(gobj) {
3147
3270
  return __last_shell__;
3148
3271
  }
3149
3272
  /************************************************************
3273
+ * The DOM where a C_YUI_WINDOW lives when its host names no
3274
+ * `$parent`: the WINDOW layer, a stacking context of its own
3275
+ * inside the overlay layer -- above the content, below the
3276
+ * drawer, the toolbar's dropdowns, the dialogs and the toasts,
3277
+ * whatever z-index a window takes there. Null with no shell (the
3278
+ * window then lives in document.body).
3279
+ ************************************************************/
3280
+ function yui_shell_window_layer(shell_gobj) {
3281
+ if (!shell_gobj || !shell_gobj.priv) return null;
3282
+ let priv = shell_gobj.priv;
3283
+ return priv.window_layer || priv.layers && priv.layers.overlay || null;
3284
+ }
3285
+ /************************************************************
3286
+ * Put a window on top of the others: the next number of ONE
3287
+ * counter, the same for a window of the dock and for a floating
3288
+ * one.
3289
+ *
3290
+ * Up to 7.26.34 there were two orders: the dock raised its windows
3291
+ * to a counter of its own (11, 12, ...), and a window without a
3292
+ * dock sat at 3 and was raised by being moved to the end of its
3293
+ * parent -- so a window opened LATER without a dock (a treedb's
3294
+ * raw json) opened UNDER any docked one, and no click could bring
3295
+ * it up. They share the window layer, and this one number orders
3296
+ * them all. It grows for ever, and it can: nothing else lives in
3297
+ * the window layer's stacking context.
3298
+ *
3299
+ * A number, not a move in the DOM: moving an element takes its
3300
+ * focus, its scroll positions and the pointer press that started
3301
+ * on it.
3302
+ ************************************************************/
3303
+ function yui_shell_raise_window($el) {
3304
+ if (!$el || !$el.style) {
3305
+ (0, _yuneta_gobj_js.log_error)("C_YUI_SHELL: yui_shell_raise_window() needs an element");
3306
+ return;
3307
+ }
3308
+ __window_z__ += 1;
3309
+ $el.style.zIndex = String(__window_z__);
3310
+ }
3311
+ /************************************************************
3312
+ * Ask to be told (EV_SHELL_CHANGED, an INPUT event the gclass of
3313
+ * `gobj` must take in its FSM) when a shell is built or
3314
+ * destroyed.
3315
+ *
3316
+ * For a gobj that keeps DOM in a shell's layers without being
3317
+ * its child -- a window parented to the app, the dock. A shell's
3318
+ * children are destroyed with it; such a gobj is not, and its
3319
+ * place goes with the shell: a logout destroys the shell, the
3320
+ * login builds another. Told, it takes its place again in the
3321
+ * action.
3322
+ *
3323
+ * Idempotent. Pair it with yui_shell_unwatch_changes() in
3324
+ * mt_destroy, or the next shell sends the event to a destroyed
3325
+ * gobj.
3326
+ *
3327
+ * function mt_create(gobj) {
3328
+ * ...
3329
+ * yui_shell_watch_changes(gobj);
3330
+ * }
3331
+ * function mt_destroy(gobj) {
3332
+ * yui_shell_unwatch_changes(gobj);
3333
+ * ...
3334
+ * }
3335
+ ************************************************************/
3336
+ function yui_shell_watch_changes(gobj) {
3337
+ if (!(0, _yuneta_gobj_js.is_gobj)(gobj)) {
3338
+ (0, _yuneta_gobj_js.log_error)("C_YUI_SHELL: yui_shell_watch_changes() needs a gobj");
3339
+ return;
3340
+ }
3341
+ if (__shell_watchers__.indexOf(gobj) < 0) __shell_watchers__.push(gobj);
3342
+ }
3343
+ function yui_shell_unwatch_changes(gobj) {
3344
+ let idx = __shell_watchers__.indexOf(gobj);
3345
+ if (idx >= 0) __shell_watchers__.splice(idx, 1);
3346
+ }
3347
+ /************************************************************
3150
3348
  * Programmatic navigation.
3151
3349
  *
3152
3350
  * Default — PUSH: the user MOVED somewhere new, so change the URL via
@@ -3402,7 +3600,13 @@ function yui_shell_set_toolbar_item_badge(shell_gobj, item_id, value, tone) {
3402
3600
  if (!$container) return;
3403
3601
  let $badge = $container.querySelector(`[data-toolbar-item-id="${item_id}"] .yui-toolbar-badge`);
3404
3602
  if (!$badge) return;
3603
+ let before = $badge.hasAttribute("hidden") ? "" : $badge.textContent;
3405
3604
  paint_badge($badge, value, tone);
3605
+ let now = badge_text(value);
3606
+ if (now && now !== before) {
3607
+ let $item = $badge.closest("[data-toolbar-item-id]");
3608
+ announce_badge(shell_gobj, $item && $item.getAttribute("data-i18n-aria-label") || item_id, now);
3609
+ }
3406
3610
  }
3407
3611
  /************************************************************
3408
3612
  * Set (or clear) the badge of a MENU item — the twin of
@@ -3436,8 +3640,11 @@ function yui_shell_set_nav_item_badge(shell_gobj, item_id, value, tone) {
3436
3640
  tone = badge_tone(tone, `C_YUI_SHELL: yui_shell_set_nav_item_badge('${item_id}')`);
3437
3641
  if (!shell_gobj || !(0, _yuneta_gobj_js.is_gobj)(shell_gobj) || (0, _yuneta_gobj_js.gobj_is_destroying)(shell_gobj) || (0, _yuneta_gobj_js.empty_string)(item_id)) return;
3438
3642
  let priv = shell_gobj.priv;
3439
- if (!priv || !nav_item_declared(shell_gobj, item_id)) return;
3643
+ let item = priv ? nav_item_of(shell_gobj, item_id) : null;
3644
+ if (!item) return;
3440
3645
  let badge = value === void 0 ? null : value;
3646
+ let was = priv.nav_badges[item_id];
3647
+ let before = badge_text(was ? was.badge : item.badge);
3441
3648
  priv.nav_badges[item_id] = {
3442
3649
  badge,
3443
3650
  badge_tone: tone
@@ -3447,6 +3654,8 @@ function yui_shell_set_nav_item_badge(shell_gobj, item_id, value, tone) {
3447
3654
  badge,
3448
3655
  badge_tone: tone
3449
3656
  }, shell_gobj);
3657
+ let now = badge_text(badge);
3658
+ if (now && now !== before) announce_badge(shell_gobj, item.name || item_id, now);
3450
3659
  }
3451
3660
  /************************************************************
3452
3661
  * Programmatic close of any open toolbar dropdown. Useful for
@@ -4050,7 +4259,7 @@ var MOBILE_MAX = 768;
4050
4259
  ***************************************************************/
4051
4260
  var attrs_table$21 = [
4052
4261
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
4053
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$parent", 0, null, "$container will be appended to $parent if not null, else to the overlay layer of the shell (document.body when there is none)"),
4262
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$parent", 0, null, "$container will be appended to $parent if not null, else to the window layer of the shell (document.body while there is none), following the shell when it is destroyed or another is built"),
4054
4263
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "x", 0, "300", "X position"),
4055
4264
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "y", 0, "100", "Y position"),
4056
4265
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "width", 0, "700", "Width of the window"),
@@ -4086,7 +4295,11 @@ var attrs_table$21 = [
4086
4295
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "win_resize_handler", 0, null, "Internal: native window 'resize' listener"),
4087
4296
  (0, _yuneta_gobj_js.SDATA_END)()
4088
4297
  ];
4089
- var PRIVATE_DATA$21 = { prevSize: 0 };
4298
+ var PRIVATE_DATA$21 = {
4299
+ prevSize: 0,
4300
+ placed_by_shell: false,
4301
+ back_shell: null
4302
+ };
4090
4303
  var __gclass__$21 = null;
4091
4304
  /******************************
4092
4305
  * Framework Methods
@@ -4101,6 +4314,7 @@ function mt_create$21(gobj) {
4101
4314
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "window_id", window_id);
4102
4315
  let manager = resolve_manager(gobj);
4103
4316
  build_ui$18(gobj);
4317
+ yui_shell_watch_changes(gobj);
4104
4318
  if (manager) {
4105
4319
  let $c = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
4106
4320
  if ($c) $c.addEventListener("pointerdown", function() {
@@ -4116,7 +4330,16 @@ function mt_create$21(gobj) {
4116
4330
  title_prefix: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "title_prefix") || "",
4117
4331
  icon: (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "icon") || ""
4118
4332
  }, gobj);
4119
- } else if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "back_dismissable")) register_back_overlay(gobj);
4333
+ } else {
4334
+ let $c = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
4335
+ if ($c) {
4336
+ yui_shell_raise_window($c);
4337
+ $c.addEventListener("pointerdown", function() {
4338
+ if (!(0, _yuneta_gobj_js.gobj_is_destroying)(gobj)) (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SHOW", {}, gobj);
4339
+ }, true);
4340
+ }
4341
+ if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "back_dismissable")) register_back_overlay(gobj);
4342
+ }
4120
4343
  let on_win_resize = function() {
4121
4344
  handleResize(gobj);
4122
4345
  };
@@ -4127,13 +4350,15 @@ function mt_create$21(gobj) {
4127
4350
  * Framework Method: Destroy
4128
4351
  ***************************************************************/
4129
4352
  function mt_destroy$21(gobj) {
4353
+ let priv = gobj.priv;
4354
+ yui_shell_unwatch_changes(gobj);
4130
4355
  let manager = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "manager");
4131
4356
  if (manager) (0, _yuneta_gobj_js.gobj_send_event)(manager, "EV_UNREGISTER_WINDOW", { window: (0, _yuneta_gobj_js.gobj_full_name)(gobj) }, gobj);
4132
4357
  let back_overlay = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "back_overlay");
4133
4358
  if (back_overlay) {
4134
- let shell = yui_shell_of(gobj);
4135
- if (shell) yui_shell_overlay_dismissed(shell, back_overlay);
4359
+ if (priv.back_shell) yui_shell_overlay_dismissed(priv.back_shell, back_overlay);
4136
4360
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "back_overlay", null);
4361
+ priv.back_shell = null;
4137
4362
  }
4138
4363
  let on_win_resize = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "win_resize_handler");
4139
4364
  if (on_win_resize) {
@@ -4545,7 +4770,8 @@ function build_ui$18(gobj) {
4545
4770
  }
4546
4771
  (0, _yuneta_gobj_js.refresh_language)($container, i18next.t);
4547
4772
  let $parent = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$parent");
4548
- if (!$parent) $parent = default_parent(gobj);
4773
+ gobj.priv.placed_by_shell = !$parent;
4774
+ if (!$parent) $parent = window_place(gobj);
4549
4775
  $parent.appendChild($container);
4550
4776
  if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "openMaximized")) max$2(gobj);
4551
4777
  else if (!mobile) {
@@ -4587,11 +4813,13 @@ function register_back_overlay(gobj) {
4587
4813
  };
4588
4814
  let overlay = yui_shell_register_overlay(shell, function() {
4589
4815
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "back_overlay", null);
4816
+ gobj.priv.back_shell = null;
4590
4817
  const closing = close_window(gobj);
4591
4818
  if (closing.state === "vetoed") rearm();
4592
4819
  else if (closing.state === "asking") closing.answer.then(rearm);
4593
4820
  }, { keep_on_navigate: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "keep_on_navigate") });
4594
4821
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "back_overlay", overlay);
4822
+ gobj.priv.back_shell = overlay ? shell : null;
4595
4823
  }
4596
4824
  /************************************************************
4597
4825
  * Ask to close the window. Answers what happened:
@@ -4633,7 +4861,8 @@ function close_window(gobj) {
4633
4861
  }
4634
4862
  /************************************************************
4635
4863
  * Where a window lives when its host names no `$parent`: the
4636
- * OVERLAY layer of the shell -- above the content, below the
4864
+ * WINDOW layer of its shell (yui_shell_window_layer()), inside
4865
+ * the overlay layer -- above the content, below the drawer, the
4637
4866
  * toolbar's dropdowns, the dialogs and the toasts.
4638
4867
  *
4639
4868
  * It used to be document.body, and that put every window above
@@ -4646,14 +4875,17 @@ function close_window(gobj) {
4646
4875
  * toolbar, any dialog opened with a window on screen, the same.
4647
4876
  *
4648
4877
  * Inside the layer the dock's z-index still orders the windows
4649
- * among themselves. No shell (a page with windows and no shell):
4650
- * the body, as before.
4878
+ * among themselves, and no number it gives them reaches the
4879
+ * drawer (7.26.31 put them in the overlay layer itself, beside
4880
+ * it). No shell (a page with windows and no shell, or the time
4881
+ * between a logout and the next login): the body.
4882
+ *
4883
+ * The place is the shell's, and a window parented to the app
4884
+ * outlives it: ac_shell_changed() moves it when a shell goes or
4885
+ * comes.
4651
4886
  ************************************************************/
4652
- function default_parent(gobj) {
4653
- let shell = yui_shell_of(gobj);
4654
- let layers = shell && shell.priv && shell.priv.layers;
4655
- if (layers && layers.overlay) return layers.overlay;
4656
- return document.body;
4887
+ function window_place(gobj) {
4888
+ return yui_shell_window_layer(yui_shell_of(gobj)) || document.body;
4657
4889
  }
4658
4890
  /************************************************************
4659
4891
  *
@@ -4925,8 +5157,7 @@ function ac_show$15(gobj, event, kw, src) {
4925
5157
  return -1;
4926
5158
  }
4927
5159
  $container.style.removeProperty("display");
4928
- let $parent = $container.parentNode;
4929
- if ($parent && $parent.lastChild !== $container) $parent.appendChild($container);
5160
+ yui_shell_raise_window($container);
4930
5161
  return 0;
4931
5162
  }
4932
5163
  /************************************************************
@@ -4943,6 +5174,49 @@ function ac_close(gobj, event, kw, src) {
4943
5174
  close_window(gobj);
4944
5175
  return 0;
4945
5176
  }
5177
+ /************************************************************
5178
+ * EV_SHELL_CHANGED -- a shell was built, or is being destroyed
5179
+ * (`src`, then destroying): the place this window lives in may
5180
+ * have gone with it, or be in the new one
5181
+ * (yui_shell_watch_changes()).
5182
+ *
5183
+ * - A window whose host named no `$parent` takes the window place
5184
+ * again: the window layer of the shell that governs it NOW,
5185
+ * document.body while there is none. So the developer window
5186
+ * outlives a logout in the body, and the login's shell takes it
5187
+ * in -- above its content, under its dialogs. From 7.26.31 to
5188
+ * 7.26.34 it stayed in the destroyed shell's layer: alive,
5189
+ * detached, invisible.
5190
+ * - A window its host placed stays where it was put, unless that
5191
+ * element was inside the shell being destroyed: then no place
5192
+ * is left, and it takes the window place from now on.
5193
+ * - Its Back entry, when the shell being destroyed held it, went
5194
+ * with that shell's history and is forgotten. The next shell
5195
+ * does not get one: a history entry pushed while a shell is
5196
+ * still being built would sit under its first route. Back no
5197
+ * longer closes such a window; its ✕ does, as before 7.26.31.
5198
+ ************************************************************/
5199
+ function ac_shell_changed$1(gobj, event, kw, src) {
5200
+ let priv = gobj.priv;
5201
+ let going = (0, _yuneta_gobj_js.gobj_is_destroying)(src);
5202
+ if (going && priv.back_shell === src) {
5203
+ (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "back_overlay", null);
5204
+ priv.back_shell = null;
5205
+ }
5206
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
5207
+ if (!$container) {
5208
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: EV_SHELL_CHANGED without a container`);
5209
+ return -1;
5210
+ }
5211
+ if (!priv.placed_by_shell) {
5212
+ let $shell = going ? (0, _yuneta_gobj_js.gobj_read_attr)(src, "$container") : null;
5213
+ if (!$shell || !$shell.contains($container)) return 0;
5214
+ priv.placed_by_shell = true;
5215
+ }
5216
+ let $place = window_place(gobj);
5217
+ if ($container.parentNode !== $place) $place.appendChild($container);
5218
+ return 0;
5219
+ }
4946
5220
  /***************************
4947
5221
  * FSM
4948
5222
  ***************************/
@@ -4978,6 +5252,11 @@ function create_gclass$21(gclass_name) {
4978
5252
  "EV_CLOSE_WINDOW",
4979
5253
  ac_close,
4980
5254
  null
5255
+ ],
5256
+ [
5257
+ "EV_SHELL_CHANGED",
5258
+ ac_shell_changed$1,
5259
+ null
4981
5260
  ]
4982
5261
  ]]];
4983
5262
  const event_types = [
@@ -4987,7 +5266,8 @@ function create_gclass$21(gclass_name) {
4987
5266
  ["EV_REFRESH", 0],
4988
5267
  ["EV_SHOW", 0],
4989
5268
  ["EV_HIDE", 0],
4990
- ["EV_CLOSE_WINDOW", 0]
5269
+ ["EV_CLOSE_WINDOW", 0],
5270
+ ["EV_SHELL_CHANGED", 0]
4991
5271
  ];
4992
5272
  __gclass__$21 = (0, _yuneta_gobj_js.gclass_create)(gclass_name, event_types, states, gmt$21, 0, attrs_table$21, PRIVATE_DATA$21, 0, 0, 0, 0);
4993
5273
  if (!__gclass__$21) return -1;
@@ -5038,6 +5318,7 @@ function register_c_yui_window() {
5038
5318
  var GCLASS_NAME$20 = "C_YUI_WINDOW_MANAGER";
5039
5319
  var DOCK_Z = 1e6;
5040
5320
  var WC_X = "<svg viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"M4.5 4.5 L11.5 11.5 M11.5 4.5 L4.5 11.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg>";
5321
+ var __chip_seq__ = 0;
5041
5322
  /***************************************************************
5042
5323
  * Data
5043
5324
  ***************************************************************/
@@ -5047,13 +5328,12 @@ var attrs_table$20 = [
5047
5328
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "dock_corner", 0, "bottom-left", "Floating placement: top-left | top-right | bottom-left | bottom-right"),
5048
5329
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "inline_selector", 0, "", "CSS selector of the container the dock mounts into when inline (e.g. a shell zone)"),
5049
5330
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "responsive_query", 0, "(max-width: 768px)", "Media query that selects inline placement when dock_mode is 'responsive'"),
5050
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$parent", 0, null, "Where the floating dock mounts (default document.body)"),
5331
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$parent", 0, null, "Where the floating dock mounts (default: the popup layer of the shell, document.body while there is none)"),
5051
5332
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$container", 0, null, "Internal: the dock element"),
5052
5333
  (0, _yuneta_gobj_js.SDATA_END)()
5053
5334
  ];
5054
5335
  var PRIVATE_DATA$20 = {
5055
5336
  windows: null,
5056
- z: 0,
5057
5337
  mql: null,
5058
5338
  mql_handler: null
5059
5339
  };
@@ -5067,7 +5347,6 @@ var __gclass__$20 = null;
5067
5347
  function mt_create$20(gobj) {
5068
5348
  let priv = gobj.priv;
5069
5349
  priv.windows = [];
5070
- priv.z = 10;
5071
5350
  if (((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "dock_mode") || "floating") === "responsive") {
5072
5351
  let q = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "responsive_query") || "(max-width: 768px)";
5073
5352
  priv.mql = window.matchMedia(q);
@@ -5077,6 +5356,7 @@ function mt_create$20(gobj) {
5077
5356
  priv.mql.addEventListener("change", priv.mql_handler);
5078
5357
  }
5079
5358
  build_dock(gobj);
5359
+ yui_shell_watch_changes(gobj);
5080
5360
  const subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
5081
5361
  if (subscriber) (0, _yuneta_gobj_js.gobj_subscribe_event)(gobj, null, {}, subscriber);
5082
5362
  }
@@ -5085,6 +5365,7 @@ function mt_create$20(gobj) {
5085
5365
  ***************************************************************/
5086
5366
  function mt_destroy$20(gobj) {
5087
5367
  let priv = gobj.priv;
5368
+ yui_shell_unwatch_changes(gobj);
5088
5369
  if (priv.mql && priv.mql_handler) {
5089
5370
  priv.mql.removeEventListener("change", priv.mql_handler);
5090
5371
  priv.mql = null;
@@ -5134,10 +5415,18 @@ function ensure_dock_style() {
5134
5415
  }
5135
5416
  .yui-dock-chip {
5136
5417
  display: inline-flex; align-items: center; gap: 7px; font: inherit; font-size: 12px;
5137
- padding: 4px 5px 4px 11px; border: 1px solid var(--bulma-border); border-radius: 7px;
5138
- background: var(--bulma-scheme-main); color: var(--bulma-text); cursor: pointer; white-space: nowrap;
5418
+ padding: 0 5px 0 0; border: 1px solid var(--bulma-border); border-radius: 7px;
5419
+ background: var(--bulma-scheme-main); color: var(--bulma-text); white-space: nowrap;
5139
5420
  }
5140
5421
  .yui-dock-chip:hover { border-color: var(--bulma-text-weak); }
5422
+ /* The chip is the look; its toggle a button that adds none, and
5423
+ * takes the pill's padding, so the whole label is what a press lands
5424
+ * on and the chip reads as one pill with its ✕ at the end. */
5425
+ .yui-dock-chip-toggle {
5426
+ display: inline-flex; align-items: center; gap: 7px;
5427
+ padding: 4px 0 4px 11px; margin: 0; border: 0; border-radius: 7px 0 0 7px;
5428
+ background: transparent; font: inherit; color: inherit; cursor: pointer; white-space: nowrap;
5429
+ }
5141
5430
  /* The BORDER says active, not the text: blue ink on a blue tint is
5142
5431
  * the same hue on itself -- 4.49:1 on the light chip, under the 4.5
5143
5432
  * its 12px label needs. The label takes the text colour (11:1). */
@@ -5227,10 +5516,33 @@ function apply_placement(gobj) {
5227
5516
  "bottom-left",
5228
5517
  "bottom-right"
5229
5518
  ]) $dock.classList.toggle(`yui-dock--${c}`, !inline && c === corner);
5230
- let target = inline ? $inline : (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$parent") || document.body;
5519
+ let target = inline ? $inline : (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$parent") || dock_place(gobj);
5231
5520
  if ($dock.parentNode !== target) target.appendChild($dock);
5232
5521
  }
5233
5522
  /************************************************************
5523
+ * Where the FLOATING dock lives when the app names no
5524
+ * `$parent`: the popup layer of the shell, at the top of it.
5525
+ *
5526
+ * It was document.body at z-index 1000000, and that put it
5527
+ * above the WHOLE shell -- `.yui-shell` is a stacking context
5528
+ * of its own, so its dialogs (modal layer, 99) and toasts (120)
5529
+ * counted only inside it: the dock stayed clickable over a
5530
+ * dialog's backdrop (a window could be closed from its chip
5531
+ * while its own "all changes will be lost" question was open),
5532
+ * and it covered a toast where they met.
5533
+ *
5534
+ * The popup layer, not the window layer: the dock has to stay
5535
+ * above EVERY window, and windows live in two layers -- the
5536
+ * window layer (no `$parent`) and the popup layer, where the
5537
+ * site map and the JSON windows of the treedb views put theirs.
5538
+ * Above both; below the dialogs, the toasts and the loading
5539
+ * cover. No shell (before the first, between a logout and the
5540
+ * next login): the body.
5541
+ ************************************************************/
5542
+ function dock_place(gobj) {
5543
+ return yui_shell_popup_layer(yui_shell_of(gobj)) || document.body;
5544
+ }
5545
+ /************************************************************
5234
5546
  * Destroy the dock element.
5235
5547
  ************************************************************/
5236
5548
  function destroy_dock(gobj) {
@@ -5307,7 +5619,6 @@ function set_active(gobj, entry) {
5307
5619
  * Bring a window to front and mark it active/visible.
5308
5620
  ************************************************************/
5309
5621
  function focus_entry(gobj, entry) {
5310
- let priv = gobj.priv;
5311
5622
  let $win = (0, _yuneta_gobj_js.gobj_read_attr)(entry.gobj, "$container");
5312
5623
  if ($win) {
5313
5624
  if (entry.minimized) {
@@ -5315,8 +5626,7 @@ function focus_entry(gobj, entry) {
5315
5626
  entry.minimized = false;
5316
5627
  if (entry.$chip) entry.$chip.classList.remove("is-min");
5317
5628
  }
5318
- priv.z += 1;
5319
- $win.style.zIndex = priv.z;
5629
+ yui_shell_raise_window($win);
5320
5630
  }
5321
5631
  set_active(gobj, entry);
5322
5632
  }
@@ -5383,57 +5693,57 @@ function ac_register_window(gobj, event, kw, src) {
5383
5693
  { class: "yui-dock-label-kind" },
5384
5694
  text_node(title)
5385
5695
  ]);
5696
+ let chip_seq = ++__chip_seq__;
5697
+ let label_ids = label_items.map(function(item, i) {
5698
+ item[1].id = `yui-dock-chip-${chip_seq}-${i}`;
5699
+ return item[1].id;
5700
+ });
5386
5701
  let entry = {
5387
5702
  gobj: win,
5388
5703
  $chip: null,
5389
5704
  minimized: false
5390
5705
  };
5706
+ let window_name = (0, _yuneta_gobj_js.gobj_full_name)(win);
5391
5707
  let $chip = (0, _yuneta_gobj_js.createElement2)([
5392
5708
  "div",
5393
- Object.assign({
5394
- class: "yui-dock-chip",
5395
- role: "button",
5396
- tabindex: "0",
5397
- title
5398
- }, !prefix && title_key ? { "data-i18n-title": title_key } : {}),
5399
- [
5400
- chip_lead(kw.icon),
5401
- [
5709
+ { class: "DOCK_CHIP yui-dock-chip" },
5710
+ [[
5711
+ "button",
5712
+ Object.assign({
5713
+ class: "DOCK_CHIP_TOGGLE yui-dock-chip-toggle",
5714
+ type: "button",
5715
+ title,
5716
+ "aria-label": title,
5717
+ "aria-labelledby": label_ids.join(" ")
5718
+ }, !prefix && title_key ? {
5719
+ "data-i18n-title": title_key,
5720
+ "data-i18n-aria-label": title_key
5721
+ } : {}),
5722
+ [chip_lead(kw.icon), [
5402
5723
  "span",
5403
5724
  { class: "yui-dock-label" },
5404
5725
  label_items
5405
- ],
5406
- [
5407
- "button",
5408
- {
5409
- class: "yui-dock-close",
5410
- type: "button",
5411
- title: (0, i18next.t)("close"),
5412
- "data-i18n-title": "close",
5413
- "aria-label": (0, i18next.t)("close"),
5414
- "data-i18n-aria-label": "close"
5415
- },
5416
- (0, _yuneta_gobj_js.html_markup)(WC_X),
5417
- { click: (evt) => {
5418
- evt.stopPropagation();
5419
- (0, _yuneta_gobj_js.gobj_send_event)(entry.gobj, "EV_CLOSE_WINDOW", {}, gobj);
5420
- } }
5421
- ]
5422
- ],
5423
- {
5424
- click: (evt) => {
5726
+ ]],
5727
+ { click: (evt) => {
5425
5728
  evt.stopPropagation();
5426
- on_chip_click(gobj, entry);
5729
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_TOGGLE_WINDOW", { window: window_name }, gobj);
5730
+ } }
5731
+ ], [
5732
+ "button",
5733
+ {
5734
+ class: "DOCK_CHIP_CLOSE yui-dock-close",
5735
+ type: "button",
5736
+ title: (0, i18next.t)("close"),
5737
+ "data-i18n-title": "close",
5738
+ "aria-label": (0, i18next.t)("close"),
5739
+ "data-i18n-aria-label": "close"
5427
5740
  },
5428
- keydown: (evt) => {
5429
- if (evt.target !== evt.currentTarget) return;
5430
- if (evt.key === "Enter" || evt.key === " ") {
5431
- if (evt.key === " ") evt.preventDefault();
5432
- evt.stopPropagation();
5433
- on_chip_click(gobj, entry);
5434
- }
5435
- }
5436
- }
5741
+ (0, _yuneta_gobj_js.html_markup)(WC_X),
5742
+ { click: (evt) => {
5743
+ evt.stopPropagation();
5744
+ (0, _yuneta_gobj_js.gobj_send_event)(entry.gobj, "EV_CLOSE_WINDOW", {}, gobj);
5745
+ } }
5746
+ ]]
5437
5747
  ]);
5438
5748
  entry.$chip = $chip;
5439
5749
  priv.windows.push(entry);
@@ -5477,6 +5787,41 @@ function ac_focus_window(gobj, event, kw, src) {
5477
5787
  if (entry) focus_entry(gobj, entry);
5478
5788
  return 0;
5479
5789
  }
5790
+ /************************************************************
5791
+ * EV_TOGGLE_WINDOW { window } -- the toggle of a chip of THIS
5792
+ * dock was pressed: bring a minimized or covered window to the
5793
+ * front, send the one in front to the dock. Only the dock's own
5794
+ * chip sends it (src is the dock): the events a window sends
5795
+ * about itself come from the window, and this one from the chip
5796
+ * that names it.
5797
+ ************************************************************/
5798
+ function ac_toggle_window(gobj, event, kw, src) {
5799
+ let priv = gobj.priv;
5800
+ if (src !== gobj) {
5801
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} comes from the dock's own chip, not from ${(0, _yuneta_gobj_js.is_gobj)(src) ? (0, _yuneta_gobj_js.gobj_full_name)(src) : String(src)}`);
5802
+ return -1;
5803
+ }
5804
+ let name = kw && kw.window;
5805
+ let entry = priv.windows.find((e) => (0, _yuneta_gobj_js.gobj_full_name)(e.gobj) === name);
5806
+ if (!entry) {
5807
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event}: no window '${name}' in the dock`);
5808
+ return -1;
5809
+ }
5810
+ on_chip_click(gobj, entry);
5811
+ return 0;
5812
+ }
5813
+ /************************************************************
5814
+ * EV_SHELL_CHANGED -- a shell was built, or is being destroyed
5815
+ * (yui_shell_watch_changes()). The dock's place -- the shell's
5816
+ * popup layer, or its zone when inline -- may have gone with
5817
+ * it, or be in the new one: place it again. Up to 7.26.34 an
5818
+ * inline dock stayed in the destroyed shell's zone after a
5819
+ * logout, detached, until a window registered.
5820
+ ************************************************************/
5821
+ function ac_shell_changed(gobj, event, kw, src) {
5822
+ apply_placement(gobj);
5823
+ return 0;
5824
+ }
5480
5825
  /***************************
5481
5826
  * FSM
5482
5827
  ***************************/
@@ -5496,7 +5841,9 @@ function create_gclass$20(gclass_name) {
5496
5841
  ["EV_REGISTER_WINDOW", 0],
5497
5842
  ["EV_UNREGISTER_WINDOW", 0],
5498
5843
  ["EV_MINIMIZE_WINDOW", 0],
5499
- ["EV_FOCUS_WINDOW", 0]
5844
+ ["EV_FOCUS_WINDOW", 0],
5845
+ ["EV_TOGGLE_WINDOW", 0],
5846
+ ["EV_SHELL_CHANGED", 0]
5500
5847
  ], [["ST_IDLE", [
5501
5848
  [
5502
5849
  "EV_REGISTER_WINDOW",
@@ -5517,6 +5864,16 @@ function create_gclass$20(gclass_name) {
5517
5864
  "EV_FOCUS_WINDOW",
5518
5865
  ac_focus_window,
5519
5866
  null
5867
+ ],
5868
+ [
5869
+ "EV_TOGGLE_WINDOW",
5870
+ ac_toggle_window,
5871
+ null
5872
+ ],
5873
+ [
5874
+ "EV_SHELL_CHANGED",
5875
+ ac_shell_changed,
5876
+ null
5520
5877
  ]
5521
5878
  ]]], gmt$20, 0, attrs_table$20, PRIVATE_DATA$20, 0, 0, 0, 0);
5522
5879
  if (!__gclass__$20) return -1;
@@ -6796,6 +7153,41 @@ function yui_tabulator_name_row_selects(table, t) {
6796
7153
  }
6797
7154
  } catch (e) {}
6798
7155
  }
7156
+ /***************************************************************
7157
+ * Name the EDITOR of an editable column: the box Tabulator draws
7158
+ * in a cell while it is edited (input, number, textarea, list,
7159
+ * tickCross, date...).
7160
+ *
7161
+ * Tabulator gives it nothing -- no label, no aria-label, no title
7162
+ * -- so a reader lands in an anonymous edit box. Its name is the
7163
+ * column's: a cell is edited under its column's title. Asked at
7164
+ * the MOMENT of editing: Tabulator takes `editorParams` as a
7165
+ * function of the cell, and every built-in editor writes the
7166
+ * `elementAttributes` it answers onto the element it draws. So
7167
+ * the name is in the language of that moment, with nothing to
7168
+ * re-apply after a language change or a rebuild of the columns
7169
+ * -- the twin of the header filters above, which have to be
7170
+ * renamed after every render.
7171
+ *
7172
+ * `name_of(cell)` answers the column's title, TRANSLATED (the
7173
+ * view's own label: a cascade, a schema header); "" names
7174
+ * nothing. What `editorParams` the column had is kept, its own
7175
+ * `elementAttributes` too; a name it set itself wins.
7176
+ ***************************************************************/
7177
+ function yui_tabulator_name_editor(column_def, name_of) {
7178
+ if (!column_def || !column_def.editor || typeof name_of !== "function") return;
7179
+ let base = column_def.editorParams;
7180
+ column_def.editorParams = function(cell) {
7181
+ let params = typeof base === "function" ? base(cell) : base || {};
7182
+ let name = name_of(cell);
7183
+ if (!name) return params;
7184
+ let attrs = Object.assign({
7185
+ "aria-label": name,
7186
+ title: name
7187
+ }, params.elementAttributes || {});
7188
+ return Object.assign({}, params, { elementAttributes: attrs });
7189
+ };
7190
+ }
6799
7191
  function yui_tabulator_relocalize(table, t) {
6800
7192
  if (!table) return;
6801
7193
  try {
@@ -7316,6 +7708,7 @@ function yui_file_control(gobj, { name, value, readonly, accept, on_pick }) {
7316
7708
  {
7317
7709
  type: "button",
7318
7710
  class: "FILE_PICK button is-small",
7711
+ title: (0, i18next.t)("choose a file"),
7319
7712
  "data-i18n-title": "choose a file",
7320
7713
  "aria-label": (0, i18next.t)("choose a file"),
7321
7714
  "data-i18n-aria-label": "choose a file"
@@ -7334,6 +7727,7 @@ function yui_file_control(gobj, { name, value, readonly, accept, on_pick }) {
7334
7727
  {
7335
7728
  type: "button",
7336
7729
  class: "FILE_CLEAR button is-small",
7730
+ title: (0, i18next.t)("remove the file"),
7337
7731
  "data-i18n-title": "remove the file",
7338
7732
  "aria-label": (0, i18next.t)("remove the file"),
7339
7733
  "data-i18n-aria-label": "remove the file"
@@ -8682,6 +9076,293 @@ function json_editor_value(content) {
8682
9076
  return JSON.stringify(content.json);
8683
9077
  }
8684
9078
  //#endregion
9079
+ //#region src/form_json_editor_names.js
9080
+ /***********************************************************************
9081
+ * form_json_editor_names.js
9082
+ *
9083
+ * The NAMES of the buttons vanilla-jsoneditor draws in its menu
9084
+ * (a json field of C_YUI_FORM): in the app's language, and with an
9085
+ * aria-label.
9086
+ *
9087
+ * The editor draws them itself, in English, with a `title` and
9088
+ * nothing else: "Format JSON: add proper indentation and new lines
9089
+ * (Ctrl+I)", "Undo (Ctrl+Z)", and three mode buttons whose text is
9090
+ * "text", "tree" and "table" (REV-CLOUD V23.5.6). Two hooks reach
9091
+ * them, and both are needed:
9092
+ *
9093
+ * - json_editor_menu_items(), the editor's `onRenderMenu`: the
9094
+ * items it is about to draw, with each title translated (its
9095
+ * shortcut kept) and the mode buttons' text too. Svelte draws
9096
+ * from these, so a button born in a re-render is born named.
9097
+ * - name_json_editor_menu(), after the render, from the form's
9098
+ * FSM: the aria-label of each button, with its key, so
9099
+ * refresh_language() re-translates it, and the state of the
9100
+ * mode buttons (aria-pressed). A menu item has no field for
9101
+ * either.
9102
+ *
9103
+ * A button is known by the CLASS the editor gives it (`jse-format`,
9104
+ * `jse-undo`...), never by its English title: a class is what the
9105
+ * editor's own stylesheet relies on. A button not listed here keeps
9106
+ * the editor's words.
9107
+ *
9108
+ * Its CONTEXT MENU (`onRenderContextMenu`,
9109
+ * json_editor_context_menu_items()) has no class on its items, so
9110
+ * an item is known by what it IS and where it IS: the icon the
9111
+ * editor draws it with (a FontAwesome name: `paste`, `scissors`),
9112
+ * the column of the menu it stands in, its place in a dropdown
9113
+ * (the main button, the first or the second of its list), the
9114
+ * mode of the editor (tree or table) -- never by its words. Its
9115
+ * text and title go back translated; after the render the same
9116
+ * pass names its buttons, and the arrow of each dropdown, which
9117
+ * the editor draws with no name at all.
9118
+ *
9119
+ * Not reached, because the editor gives them no hook: its search
9120
+ * box, its repair messages, the tip at the foot of the context
9121
+ * menu, and its modals (sort, transform). They stay in its
9122
+ * English.
9123
+ *
9124
+ * Copyright (c) 2026, ArtGins.
9125
+ * All Rights Reserved.
9126
+ ***********************************************************************/
9127
+ /***************************************************************
9128
+ * Constants
9129
+ ***************************************************************/
9130
+ var JSON_EDITOR_BUTTON_KEYS = {
9131
+ "jse-expand-all": "expand all",
9132
+ "jse-collapse-all": "collapse all",
9133
+ "jse-search": "search",
9134
+ "jse-copy": "copy",
9135
+ "jse-undo": "undo",
9136
+ "jse-redo": "redo",
9137
+ "jse-cancel": "cancel",
9138
+ "jse-format": "format json",
9139
+ "jse-compact": "compact json",
9140
+ "jse-sort": "sort",
9141
+ "jse-transform": "transform",
9142
+ "jse-contextmenu": "open context menu"
9143
+ };
9144
+ var JSON_EDITOR_MODE_NAME_KEYS = {
9145
+ text: "text view",
9146
+ tree: "tree view",
9147
+ table: "table view"
9148
+ };
9149
+ var JSON_EDITOR_MODE_TEXT_KEYS = {
9150
+ text: "text mode",
9151
+ tree: "tree mode",
9152
+ table: "table mode"
9153
+ };
9154
+ var MODE_CLASS = "yui-jse-mode-";
9155
+ var SHORTCUT = /\((?:Ctrl|Cmd|⌘)[^()]*\)\s*$/;
9156
+ var CONTEXT_BUTTON_KEYS = {
9157
+ "pen": "edit key",
9158
+ "paste": "paste",
9159
+ "clone": "duplicate",
9160
+ "crop-simple": "extract",
9161
+ "arrow-down-short-wide": "sort",
9162
+ "filter": "transform",
9163
+ "trash-can": "remove",
9164
+ "square-caret-up": "insert before",
9165
+ "square-caret-down": "insert after"
9166
+ };
9167
+ var CONTEXT_ROW_BUTTON_KEYS = {
9168
+ "pen": "edit row",
9169
+ "clone": "duplicate row",
9170
+ "trash-can": "remove row"
9171
+ };
9172
+ var CONTEXT_DROPDOWN_MAIN_KEYS = {
9173
+ "pen": "edit value",
9174
+ "scissors": "cut",
9175
+ "copy": "copy"
9176
+ };
9177
+ var CONTEXT_DROPDOWN_FIRST_KEYS = {
9178
+ "pen": "edit value",
9179
+ "scissors": "cut formatted",
9180
+ "copy": "copy formatted"
9181
+ };
9182
+ var CONTEXT_DROPDOWN_SECOND_KEYS = {
9183
+ "pen": "enforce string",
9184
+ "scissors": "cut compacted",
9185
+ "copy": "copy compacted"
9186
+ };
9187
+ var CONTEXT_TARGET_KEYS = {
9188
+ "0": "structure",
9189
+ "1": "object",
9190
+ "2": "array",
9191
+ "3": "value"
9192
+ };
9193
+ var TARGET_ICONS = ["arrow-right-arrow-left", "plus"];
9194
+ var CONTEXT_LABEL_KEYS = {
9195
+ convert: "convert to",
9196
+ insert: "insert",
9197
+ cell: "table cell",
9198
+ row: "table row"
9199
+ };
9200
+ /***************************
9201
+ * Local Methods
9202
+ ***************************/
9203
+ function has_key(table, name) {
9204
+ return Object.prototype.hasOwnProperty.call(table, name);
9205
+ }
9206
+ function button_class(classes) {
9207
+ return (Array.isArray(classes) ? classes : String(classes || "").split(/\s+/)).find((c) => has_key(JSON_EDITOR_BUTTON_KEYS, c)) || "";
9208
+ }
9209
+ function mode_of_item(item) {
9210
+ if (String(item.className || "").split(/\s+/).indexOf("jse-group-button") < 0) return "";
9211
+ return has_key(JSON_EDITOR_MODE_NAME_KEYS, item.text) ? item.text : "";
9212
+ }
9213
+ function icon_of(item) {
9214
+ return item && item.icon && typeof item.icon.iconName === "string" ? item.icon.iconName : "";
9215
+ }
9216
+ function named_item(item, name) {
9217
+ let m = SHORTCUT.exec(String(item.title || ""));
9218
+ return Object.assign({}, item, {
9219
+ text: name,
9220
+ title: m ? `${name} ${m[0].trim()}` : name
9221
+ });
9222
+ }
9223
+ function context_dropdown(item) {
9224
+ let icon = icon_of(item.main);
9225
+ if (!has_key(CONTEXT_DROPDOWN_MAIN_KEYS, icon)) return item;
9226
+ let list = Array.isArray(item.items) ? item.items : [];
9227
+ return Object.assign({}, item, {
9228
+ main: named_item(item.main, (0, i18next.t)(CONTEXT_DROPDOWN_MAIN_KEYS[icon])),
9229
+ items: list.map(function(it, i) {
9230
+ if (!it || it.type !== "button") return it;
9231
+ if (i === 0) return named_item(it, (0, i18next.t)(CONTEXT_DROPDOWN_FIRST_KEYS[icon]));
9232
+ if (i === 1) return named_item(it, (0, i18next.t)(CONTEXT_DROPDOWN_SECOND_KEYS[icon]));
9233
+ return it;
9234
+ })
9235
+ });
9236
+ }
9237
+ function context_button(item, row_column) {
9238
+ let icon = icon_of(item);
9239
+ if (row_column && has_key(CONTEXT_ROW_BUTTON_KEYS, icon)) return named_item(item, (0, i18next.t)(CONTEXT_ROW_BUTTON_KEYS[icon]));
9240
+ if (has_key(CONTEXT_BUTTON_KEYS, icon)) return named_item(item, (0, i18next.t)(CONTEXT_BUTTON_KEYS[icon]));
9241
+ return item;
9242
+ }
9243
+ function context_column(item, table, index) {
9244
+ let list = Array.isArray(item.items) ? item.items : [];
9245
+ let buttons = list.filter((it) => it && it.type === "button");
9246
+ let targets = !table && buttons.length > 0 && buttons.every((it) => TARGET_ICONS.indexOf(icon_of(it)) >= 0);
9247
+ let row_column = table && index === 1;
9248
+ let label_kind = "";
9249
+ if (table) label_kind = index === 0 ? "cell" : index === 1 ? "row" : "";
9250
+ else if (targets) label_kind = icon_of(buttons[0]) === "plus" ? "insert" : "convert";
9251
+ let target = 0;
9252
+ let insert_row = 0;
9253
+ return Object.assign({}, item, { items: list.map(function(it) {
9254
+ if (!it || typeof it !== "object") return it;
9255
+ if (it.type === "label") return label_kind ? Object.assign({}, it, { text: (0, i18next.t)(CONTEXT_LABEL_KEYS[label_kind]) }) : it;
9256
+ if (it.type === "dropdown-button") return context_dropdown(it);
9257
+ if (it.type !== "button") return it;
9258
+ if (targets) return named_item(it, (0, i18next.t)(CONTEXT_TARGET_KEYS[String(target++)]));
9259
+ if (row_column && icon_of(it) === "plus") return named_item(it, (0, i18next.t)(insert_row++ === 0 ? CONTEXT_BUTTON_KEYS["square-caret-up"] : CONTEXT_BUTTON_KEYS["square-caret-down"]));
9260
+ return context_button(it, row_column);
9261
+ }) });
9262
+ }
9263
+ function context_item(item, table) {
9264
+ if (!item || typeof item !== "object") return item;
9265
+ if (item.type === "row") {
9266
+ let column = 0;
9267
+ let list = Array.isArray(item.items) ? item.items : [];
9268
+ return Object.assign({}, item, { items: list.map(function(it) {
9269
+ if (it && it.type === "column") return context_column(it, table, column++);
9270
+ return context_item(it, table);
9271
+ }) });
9272
+ }
9273
+ if (item.type === "dropdown-button") return context_dropdown(item);
9274
+ if (item.type === "button") return context_button(item, false);
9275
+ return item;
9276
+ }
9277
+ function mode_of_button($b) {
9278
+ for (let c of $b.classList) if (c.indexOf(MODE_CLASS) === 0) {
9279
+ let mode = c.slice(13);
9280
+ if (has_key(JSON_EDITOR_MODE_NAME_KEYS, mode)) return mode;
9281
+ }
9282
+ return "";
9283
+ }
9284
+ /***************************
9285
+ * Public
9286
+ ***************************/
9287
+ /************************************************************
9288
+ * The editor's `onRenderMenu`: the items it is about to draw,
9289
+ * translated. A title keeps the editor's shortcut after the
9290
+ * translated name ("Deshacer (Ctrl+Z)"); a mode button gets its
9291
+ * mode's name as title and its short text translated, and a class
9292
+ * that says its mode. Anything else is returned as it came.
9293
+ *
9294
+ * Pure: what it answers is data for the editor to draw.
9295
+ ************************************************************/
9296
+ function json_editor_menu_items(items) {
9297
+ if (!Array.isArray(items)) return items;
9298
+ return items.map(function(item) {
9299
+ if (!item || item.type !== "button") return item;
9300
+ let mode = mode_of_item(item);
9301
+ if (mode) return Object.assign({}, item, {
9302
+ className: `${item.className} ${MODE_CLASS}${mode}`,
9303
+ title: (0, i18next.t)(JSON_EDITOR_MODE_NAME_KEYS[mode]),
9304
+ text: (0, i18next.t)(JSON_EDITOR_MODE_TEXT_KEYS[mode])
9305
+ });
9306
+ let cls = button_class(item.className);
9307
+ if (!cls) return item;
9308
+ let m = SHORTCUT.exec(String(item.title || ""));
9309
+ let name = (0, i18next.t)(JSON_EDITOR_BUTTON_KEYS[cls]);
9310
+ return Object.assign({}, item, { title: m ? `${name} ${m[0].trim()}` : name });
9311
+ });
9312
+ }
9313
+ /************************************************************
9314
+ * The editor's `onRenderContextMenu`: the items it is about to
9315
+ * draw, translated -- each known by its icon and its place, never
9316
+ * by its words (context_item()). Anything the module does not
9317
+ * know comes back as it came: a newer editor that moves an item
9318
+ * leaves it in English, it does not mislabel it.
9319
+ *
9320
+ * Pure: what it answers is data for the editor to draw.
9321
+ ************************************************************/
9322
+ function json_editor_context_menu_items(items, context) {
9323
+ if (!Array.isArray(items)) return items;
9324
+ let table = !!(context && context.mode === "table");
9325
+ return items.map((item) => context_item(item, table));
9326
+ }
9327
+ /************************************************************
9328
+ * Name the buttons of a rendered editor's menu: an aria-label
9329
+ * with its key (data-i18n-aria-label, so a language change
9330
+ * reaches it), and aria-pressed on the mode buttons -- the
9331
+ * selected one is the mode on screen (`jse-selected`). Run
9332
+ * after every render of the menu: a re-render may draw new
9333
+ * buttons, which carry none of it.
9334
+ ************************************************************/
9335
+ function name_json_editor_menu($editor) {
9336
+ if (!$editor || typeof $editor.querySelectorAll !== "function") return 0;
9337
+ let named = 0;
9338
+ for (let $b of $editor.querySelectorAll(".jse-menu button")) {
9339
+ let mode = mode_of_button($b);
9340
+ const key = mode ? JSON_EDITOR_MODE_NAME_KEYS[mode] : JSON_EDITOR_BUTTON_KEYS[button_class([...$b.classList])];
9341
+ if (!key) continue;
9342
+ $b.setAttribute("aria-label", (0, i18next.t)(key));
9343
+ $b.setAttribute("data-i18n-aria-label", key);
9344
+ if (mode) $b.setAttribute("aria-pressed", $b.classList.contains("jse-selected") ? "true" : "false");
9345
+ named++;
9346
+ }
9347
+ const MORE = "more options";
9348
+ for (let $b of $editor.querySelectorAll("div.jse-contextmenu button")) {
9349
+ if ($b.getAttribute("data-type") === "jse-open-dropdown") {
9350
+ $b.setAttribute("title", (0, i18next.t)(MORE));
9351
+ $b.setAttribute("data-i18n-title", MORE);
9352
+ $b.setAttribute("aria-label", (0, i18next.t)(MORE));
9353
+ $b.setAttribute("data-i18n-aria-label", MORE);
9354
+ named++;
9355
+ continue;
9356
+ }
9357
+ let text = ($b.textContent || "").trim();
9358
+ if (text) {
9359
+ $b.setAttribute("aria-label", text);
9360
+ named++;
9361
+ }
9362
+ }
9363
+ return named;
9364
+ }
9365
+ //#endregion
8685
9366
  //#region src/c_yui_form.js
8686
9367
  /***********************************************************************
8687
9368
  * c_yui_form.js
@@ -8731,7 +9412,9 @@ var attrs_table$17 = [
8731
9412
  var PRIVATE_DATA$17 = {
8732
9413
  rearm_timer: null,
8733
9414
  tables: null,
8734
- table_seq: 0
9415
+ table_seq: 0,
9416
+ json_names_posted: false,
9417
+ lang_shell: null
8735
9418
  };
8736
9419
  var __gclass__$17 = null;
8737
9420
  /***************************
@@ -8762,13 +9445,23 @@ function mt_destroy$17(gobj) {
8762
9445
  * Framework Method start
8763
9446
  ************************************************************/
8764
9447
  function mt_start$17(gobj) {
9448
+ let priv = gobj.priv;
9449
+ let shell = yui_shell_of(gobj);
9450
+ if (shell) {
9451
+ (0, _yuneta_gobj_js.gobj_subscribe_event)(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
9452
+ priv.lang_shell = shell;
9453
+ }
8765
9454
  let record = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "record");
8766
9455
  if (record) (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_LOAD_RECORD", record, gobj);
8767
9456
  }
8768
9457
  /************************************************************
8769
9458
  * Framework Method stop
8770
9459
  ************************************************************/
8771
- function mt_stop$17(gobj) {}
9460
+ function mt_stop$17(gobj) {
9461
+ let priv = gobj.priv;
9462
+ if (priv.lang_shell && !(0, _yuneta_gobj_js.gobj_is_destroying)(priv.lang_shell)) (0, _yuneta_gobj_js.gobj_unsubscribe_event)(priv.lang_shell, "EV_LANGUAGE_CHANGED", {}, gobj);
9463
+ priv.lang_shell = null;
9464
+ }
8772
9465
  /***************************
8773
9466
  * Local Methods
8774
9467
  ***************************/
@@ -9224,6 +9917,80 @@ function name_form_control$1($field, conf) {
9224
9917
  $clear.setAttribute("data-i18n-aria-label", CLEAR_KEY);
9225
9918
  }
9226
9919
  }
9920
+ /************************************************************
9921
+ * The ✕ of ONE selected option of a Tom Select: its
9922
+ * remove_button plugin calls this for every option it draws.
9923
+ *
9924
+ * The plugin's own was a div titled "Remove" in English -- never
9925
+ * translated, no aria-label -- with tabindex -1, and the form
9926
+ * turns Backspace deletion off (no_backspace_delete): a keyboard
9927
+ * could not take one option out at all. Named here through t()
9928
+ * with its key (so a language change reaches it), and pressed by
9929
+ * Enter or Space as the clear button is: the key clicks it, which
9930
+ * runs the plugin's own handler.
9931
+ ************************************************************/
9932
+ function remove_option_button() {
9933
+ const KEY = "remove this option";
9934
+ let $b = (0, _yuneta_gobj_js.createElement2)([
9935
+ "div",
9936
+ {
9937
+ class: "remove",
9938
+ role: "button",
9939
+ tabindex: "0",
9940
+ title: (0, i18next.t)(KEY),
9941
+ "data-i18n-title": KEY,
9942
+ "aria-label": (0, i18next.t)(KEY),
9943
+ "data-i18n-aria-label": KEY
9944
+ },
9945
+ "×"
9946
+ ]);
9947
+ $b.addEventListener("keydown", function(evt) {
9948
+ if (evt.key === "Enter" || evt.key === " ") {
9949
+ evt.preventDefault();
9950
+ evt.stopPropagation();
9951
+ $b.click();
9952
+ }
9953
+ });
9954
+ return $b;
9955
+ }
9956
+ /************************************************************
9957
+ * The `onRenderMenu` of a json editor of this form.
9958
+ *
9959
+ * The editor calls it with the items it is about to draw: they go
9960
+ * back translated (json_editor_menu_items(), data for the editor
9961
+ * to draw). What no item can carry -- an aria-label, the state of
9962
+ * the mode buttons -- is written on the drawn buttons, after the
9963
+ * render, by the form's FSM (EV_NAME_JSON_EDITORS): the callback
9964
+ * only posts the event, once until it is handled. Posted, not
9965
+ * sent: when it runs the editor has not drawn the menu yet.
9966
+ *
9967
+ * A new function each call: handing an editor a new one is what
9968
+ * makes it draw its menu again (ac_language_changed).
9969
+ ************************************************************/
9970
+ function json_editor_render_menu(gobj) {
9971
+ return function(items) {
9972
+ ask_json_editor_names(gobj);
9973
+ return json_editor_menu_items(items);
9974
+ };
9975
+ }
9976
+ /************************************************************
9977
+ * The `onRenderContextMenu` of a json editor of this form: the
9978
+ * same, for the menu it opens on a right click or Ctrl+Q. It is
9979
+ * built each time it opens, in the language of that moment.
9980
+ ************************************************************/
9981
+ function json_editor_render_context_menu(gobj) {
9982
+ return function(items, context) {
9983
+ ask_json_editor_names(gobj);
9984
+ return json_editor_context_menu_items(items, context);
9985
+ };
9986
+ }
9987
+ function ask_json_editor_names(gobj) {
9988
+ let priv = gobj.priv;
9989
+ if (!priv.json_names_posted && !(0, _yuneta_gobj_js.gobj_is_destroying)(gobj)) {
9990
+ priv.json_names_posted = true;
9991
+ (0, _yuneta_gobj_js.gobj_post_event)(gobj, "EV_NAME_JSON_EDITORS", {}, gobj);
9992
+ }
9993
+ }
9227
9994
  function build_form(gobj) {
9228
9995
  let form_id = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "form_id");
9229
9996
  let $form = (0, _yuneta_gobj_js.createElement2)(["form", {
@@ -9255,7 +10022,7 @@ function build_html_form(gobj, $form, prefix, template) {
9255
10022
  let label_keys = topic_name ? [topic_name + "." + field_desc.name, field_desc.name] : [field_desc.name];
9256
10023
  const NO_I18N = "\0";
9257
10024
  html_field_conf.label = (0, i18next.t)(label_keys, { defaultValue: field_desc.header });
9258
- html_field_conf.label_i18n = (0, i18next.t)(field_desc.name, { defaultValue: NO_I18N }) !== NO_I18N ? field_desc.name : null;
10025
+ html_field_conf.label_i18n = label_keys.find((k) => (0, i18next.t)(k, { defaultValue: NO_I18N }) !== NO_I18N) || null;
9259
10026
  html_field_conf.placeholder = field_desc.placeholder ? (0, i18next.t)(field_desc.placeholder) : "";
9260
10027
  let placeholders = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "placeholders");
9261
10028
  if (placeholders[key]) html_field_conf.placeholder = placeholders[key];
@@ -9961,7 +10728,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
9961
10728
  "clear_button": { "title": (0, i18next.t)("remove all selected options") },
9962
10729
  "no_active_items": {},
9963
10730
  "no_backspace_delete": {},
9964
- "remove_button": {}
10731
+ "remove_button": { html: remove_option_button }
9965
10732
  }
9966
10733
  });
9967
10734
  $extend.tom_select.on("change", function() {
@@ -10030,7 +10797,9 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
10030
10797
  },
10031
10798
  timestampTag: function({ field, value, path }) {
10032
10799
  return is_time_field(field);
10033
- }
10800
+ },
10801
+ onRenderMenu: json_editor_render_menu(gobj),
10802
+ onRenderContextMenu: json_editor_render_context_menu(gobj)
10034
10803
  }
10035
10804
  });
10036
10805
  break;
@@ -10562,6 +11331,9 @@ function template2columns(gobj, columns, template, sub_elements) {
10562
11331
  }
10563
11332
  if (column) {
10564
11333
  if (readonly) delete column.editor;
11334
+ yui_tabulator_name_editor(column, function() {
11335
+ return (0, i18next.t)(field_desc.header || field_desc.name);
11336
+ });
10565
11337
  columns.push(column);
10566
11338
  }
10567
11339
  });
@@ -11322,6 +12094,39 @@ function ac_hide$11(gobj, event, kw, src) {
11322
12094
  function ac_timeout(gobj, event, kw, src) {
11323
12095
  return 0;
11324
12096
  }
12097
+ /************************************************************
12098
+ * EV_NAME_JSON_EDITORS {} -- a menu of a json editor of this
12099
+ * form was drawn, its menu or its context menu (posted by
12100
+ * ask_json_editor_names): name its buttons. Every editor of the
12101
+ * form: naming is idempotent, and a kw is plain json, which an
12102
+ * editor is not.
12103
+ ************************************************************/
12104
+ function ac_name_json_editors(gobj, event, kw, src) {
12105
+ let priv = gobj.priv;
12106
+ priv.json_names_posted = false;
12107
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
12108
+ if (!$container) {
12109
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: ${event} without a container`);
12110
+ return -1;
12111
+ }
12112
+ for (let $editor of $container.querySelectorAll(".jsoneditor")) name_json_editor_menu($editor);
12113
+ return 0;
12114
+ }
12115
+ /************************************************************
12116
+ * EV_LANGUAGE_CHANGED {} -- from the shell. A json editor draws
12117
+ * its menu once: its titles and the text of its mode buttons stay
12118
+ * in the old language until it draws it again, so each is told to
12119
+ * (a new onRenderMenu), and the render names it again.
12120
+ ************************************************************/
12121
+ function ac_language_changed$10(gobj, event, kw, src) {
12122
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
12123
+ if (!$container) return 0;
12124
+ for (let $editor of $container.querySelectorAll(".jsoneditor")) {
12125
+ let editor = $editor.jsoneditor;
12126
+ if (editor && typeof editor.updateProps === "function") editor.updateProps({ onRenderMenu: json_editor_render_menu(gobj) });
12127
+ }
12128
+ return 0;
12129
+ }
11325
12130
  /***************************
11326
12131
  * FSM
11327
12132
  ***************************/
@@ -11471,6 +12276,16 @@ function create_gclass$17(gclass_name) {
11471
12276
  "EV_TIMEOUT",
11472
12277
  ac_timeout,
11473
12278
  null
12279
+ ],
12280
+ [
12281
+ "EV_NAME_JSON_EDITORS",
12282
+ ac_name_json_editors,
12283
+ null
12284
+ ],
12285
+ [
12286
+ "EV_LANGUAGE_CHANGED",
12287
+ ac_language_changed$10,
12288
+ null
11474
12289
  ]
11475
12290
  ]]];
11476
12291
  const event_types = [
@@ -11489,7 +12304,9 @@ function create_gclass$17(gclass_name) {
11489
12304
  ["EV_REFRESH", 0],
11490
12305
  ["EV_SHOW", 0],
11491
12306
  ["EV_HIDE", 0],
11492
- ["EV_TIMEOUT", 0]
12307
+ ["EV_TIMEOUT", 0],
12308
+ ["EV_NAME_JSON_EDITORS", 0],
12309
+ ["EV_LANGUAGE_CHANGED", 0]
11493
12310
  ];
11494
12311
  __gclass__$17 = (0, _yuneta_gobj_js.gclass_create)(gclass_name, event_types, states, gmt$17, 0, attrs_table$17, PRIVATE_DATA$17, 0, 0, 0, 0);
11495
12312
  if (!__gclass__$17) return -1;
@@ -11509,7 +12326,7 @@ function register_c_yui_form() {
11509
12326
  *
11510
12327
  * Utilities for maplibre
11511
12328
  *
11512
- * Copyright (c) 2025, ArtGins.
12329
+ * Copyright (c) 2025-2026, ArtGins.
11513
12330
  * All Rights Reserved.
11514
12331
  ***********************************************************************/
11515
12332
  var EditControl = class {
@@ -11705,9 +12522,14 @@ function label_ctrl($root, selector, text) {
11705
12522
  $el.setAttribute("aria-label", text);
11706
12523
  }
11707
12524
  }
12525
+ function relabel_written($root, selector, was, now) {
12526
+ if (!$root || !now) return;
12527
+ for (const $el of $root.querySelectorAll(selector)) if ($el.getAttribute("aria-label") === was) $el.setAttribute("aria-label", now);
12528
+ }
11708
12529
  function yui_maplibre_relocalize(map, t) {
11709
12530
  if (!map || typeof map.getContainer !== "function") return;
11710
12531
  const strings = yui_maplibre_locale(t);
12532
+ const was = Object.assign({}, map._locale);
11711
12533
  try {
11712
12534
  map._locale = Object.assign({}, map._locale, strings);
11713
12535
  } catch (e) {}
@@ -11719,6 +12541,8 @@ function yui_maplibre_relocalize(map, t) {
11719
12541
  label_ctrl($c, ".maplibregl-ctrl-attrib-button", strings["AttributionControl.ToggleAttribution"]);
11720
12542
  label_ctrl($c, ".maplibregl-ctrl-fullscreen", strings["FullscreenControl.Enter"]);
11721
12543
  label_ctrl($c, ".maplibregl-popup-close-button", strings["Popup.Close"]);
12544
+ relabel_written($c, ".maplibregl-canvas", was["Map.Title"], strings["Map.Title"]);
12545
+ relabel_written($c, ".maplibregl-marker", was["Marker.Title"], strings["Marker.Title"]);
11722
12546
  const mac = typeof navigator !== "undefined" && (navigator.userAgent || "").includes("Mac");
11723
12547
  const $desktop = $c.querySelector(".maplibregl-desktop-message");
11724
12548
  if ($desktop) $desktop.textContent = mac ? strings["CooperativeGesturesHandler.MacHelpText"] : strings["CooperativeGesturesHandler.WindowsHelpText"];
@@ -14467,6 +15291,8 @@ function make_toolbar$5(gobj) {
14467
15291
  type: "text",
14468
15292
  placeholder: (0, i18next.t)("search"),
14469
15293
  "data-i18n-placeholder": "search",
15294
+ title: (0, i18next.t)("search"),
15295
+ "data-i18n-title": "search",
14470
15296
  "aria-label": (0, i18next.t)("search"),
14471
15297
  "data-i18n-aria-label": "search"
14472
15298
  },
@@ -16007,6 +16833,8 @@ function make_toolbar$4(gobj) {
16007
16833
  type: "text",
16008
16834
  placeholder: (0, i18next.t)("search"),
16009
16835
  "data-i18n-placeholder": "search",
16836
+ title: (0, i18next.t)("search"),
16837
+ "data-i18n-title": "search",
16010
16838
  "aria-label": (0, i18next.t)("search"),
16011
16839
  "data-i18n-aria-label": "search"
16012
16840
  },
@@ -19280,6 +20108,8 @@ function make_toolbar$2(gobj) {
19280
20108
  type: "text",
19281
20109
  placeholder: (0, i18next.t)("search"),
19282
20110
  "data-i18n-placeholder": "search",
20111
+ title: (0, i18next.t)("search"),
20112
+ "data-i18n-title": "search",
19283
20113
  "aria-label": (0, i18next.t)("search"),
19284
20114
  "data-i18n-aria-label": "search"
19285
20115
  },
@@ -21100,7 +21930,7 @@ function open_gclass_view(host, gclass_name, opts) {
21100
21930
  (0, _yuneta_gobj_js.gobj_start)(jv);
21101
21931
  return handle;
21102
21932
  }
21103
- let $win_parent = window_layer_of(host, shell);
21933
+ let $win_parent = window_layer_of(host);
21104
21934
  handle.win = (0, _yuneta_gobj_js.gobj_create_service)(`gclass-win-${(0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(host))}`, "C_YUI_WINDOW", {
21105
21935
  $parent: $win_parent,
21106
21936
  subscriber: null,
@@ -21168,14 +21998,19 @@ function close_gclass_view(handle) {
21168
21998
  * window on `body` and mounted in the shell's popup layer
21169
21999
  * lands BEHIND its opener whatever number it is given, and
21170
22000
  * the button reads as one that did nothing.
22001
+ *
22002
+ * Opened from no window: null -- the window layer of the
22003
+ * shell, where every window lives (the legacy '#top-layer'
22004
+ * when a page has one). It was the shell's popup layer: above
22005
+ * the drawer, in the way of the toolbar's dropdowns.
21171
22006
  ************************************************************/
21172
- function window_layer_of(host, shell) {
22007
+ function window_layer_of(host) {
21173
22008
  let $c = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(host, "$container");
21174
22009
  if ($c && typeof $c.closest === "function") {
21175
22010
  let $win = $c.closest(".C_YUI_WINDOW");
21176
22011
  if ($win && $win.parentNode) return $win.parentNode;
21177
22012
  }
21178
- return typeof document !== "undefined" && document.getElementById("top-layer") || shell && yui_shell_popup_layer(shell) || null;
22013
+ return typeof document !== "undefined" && document.getElementById("top-layer") || null;
21179
22014
  }
21180
22015
  /************************************************************
21181
22016
  * A window is not a sheet: below this width there is no room
@@ -21844,6 +22679,8 @@ function make_find_items(gobj) {
21844
22679
  type: "text",
21845
22680
  placeholder: (0, i18next.t)("search"),
21846
22681
  "data-i18n-placeholder": "search",
22682
+ title: (0, i18next.t)("search"),
22683
+ "data-i18n-title": "search",
21847
22684
  "aria-label": (0, i18next.t)("search"),
21848
22685
  "data-i18n-aria-label": "search"
21849
22686
  },
@@ -23629,7 +24466,7 @@ function card_descriptor(it, show_label, badge_of) {
23629
24466
  "aria-label": label || it.id
23630
24467
  };
23631
24468
  if (label) a_attrs["data-i18n-aria-label"] = label;
23632
- let tip = it.tooltip || it.aria_label;
24469
+ let tip = it.tooltip || it.aria_label || label;
23633
24470
  if (tip) {
23634
24471
  a_attrs.title = tip;
23635
24472
  a_attrs["data-i18n-title"] = tip;
@@ -23638,6 +24475,7 @@ function card_descriptor(it, show_label, badge_of) {
23638
24475
  a_attrs["aria-disabled"] = "true";
23639
24476
  a_attrs["tabindex"] = "-1";
23640
24477
  }
24478
+ if (badge) name_control_with_badge(a_attrs, badge);
23641
24479
  return [
23642
24480
  "a",
23643
24481
  a_attrs,
@@ -24040,6 +24878,7 @@ function render_tabs(gobj, items) {
24040
24878
  a_attrs.title = tip;
24041
24879
  a_attrs["data-i18n-title"] = tip;
24042
24880
  }
24881
+ name_control_with_badge(a_attrs, badge_el);
24043
24882
  let li_class = (0, _yuneta_gobj_js.empty_string)(it.class) ? "" : String(it.class);
24044
24883
  lis.push([
24045
24884
  "li",
@@ -24119,11 +24958,14 @@ function render_breadcrumb(gobj, items) {
24119
24958
  href: it.route ? "#" + it.route : "#",
24120
24959
  "data-item-id": it.id,
24121
24960
  "data-route": it.route || "",
24122
- "data-disabled": it.disabled ? "1" : "0"
24961
+ "data-disabled": it.disabled ? "1" : "0",
24962
+ "aria-label": it.name || it.id
24123
24963
  };
24124
- if (it.name) {
24125
- a_attrs["aria-label"] = it.name;
24126
- a_attrs["data-i18n-aria-label"] = it.name;
24964
+ if (it.name) a_attrs["data-i18n-aria-label"] = it.name;
24965
+ let tip = it.tooltip || it.aria_label || it.name;
24966
+ if (tip) {
24967
+ a_attrs.title = tip;
24968
+ a_attrs["data-i18n-title"] = tip;
24127
24969
  }
24128
24970
  lis.push([
24129
24971
  "li",
@@ -24256,17 +25098,19 @@ function render_accordion(gobj, items) {
24256
25098
  "aria-expanded": "false",
24257
25099
  "aria-controls": body_id
24258
25100
  };
25101
+ let head_badge = item_badge_node(gobj, it, false);
24259
25102
  if (!(0, _yuneta_gobj_js.empty_string)(head_text)) {
24260
25103
  head_attrs.i18n = head_text;
24261
25104
  head_attrs.title = head_text;
24262
25105
  head_attrs["data-i18n-title"] = head_text;
24263
25106
  head_attrs["aria-label"] = head_text;
24264
25107
  head_attrs["data-i18n-aria-label"] = head_text;
25108
+ name_control_with_badge(head_attrs, head_badge);
24265
25109
  }
24266
25110
  let $hdr = (0, _yuneta_gobj_js.createElement2)([
24267
25111
  "button",
24268
25112
  head_attrs,
24269
- [text_node(head_text), item_badge_node(gobj, it, false)]
25113
+ [text_node(head_text), head_badge]
24270
25114
  ]);
24271
25115
  $container.appendChild($hdr);
24272
25116
  let $ul = (0, _yuneta_gobj_js.createElement2)(["ul", {
@@ -24355,6 +25199,7 @@ function item_li(gobj, it, opts) {
24355
25199
  a_attrs["aria-disabled"] = "true";
24356
25200
  a_attrs["tabindex"] = "-1";
24357
25201
  }
25202
+ name_control_with_badge(a_attrs, badge_el);
24358
25203
  return [
24359
25204
  "li",
24360
25205
  {},
@@ -24401,6 +25246,7 @@ function item_iconbar(gobj, it, opts) {
24401
25246
  a_attrs.title = tip;
24402
25247
  a_attrs["data-i18n-title"] = tip;
24403
25248
  }
25249
+ name_control_with_badge(a_attrs, badge_el);
24404
25250
  return [
24405
25251
  "div",
24406
25252
  { class: "level-item" },
@@ -24658,7 +25504,10 @@ function render_node(node) {
24658
25504
  if (node.icon && /^yi-[a-z0-9-]+$/.test(node.icon)) row.push([
24659
25505
  "span",
24660
25506
  { class: "icon ROUTEMAP_ICON" },
24661
- [["i", { class: node.icon }]]
25507
+ [["i", {
25508
+ class: node.icon,
25509
+ "aria-hidden": "true"
25510
+ }]]
24662
25511
  ]);
24663
25512
  row.push([
24664
25513
  "span",
@@ -24707,16 +25556,23 @@ function render_node(node) {
24707
25556
  ]);
24708
25557
  let row_class = (node.current ? " ROUTEMAP_CURRENT" : "") + (node.ref ? " ROUTEMAP_IS_REF" : "");
24709
25558
  let $row;
24710
- if (node.route) $row = (0, _yuneta_gobj_js.createElement2)([
24711
- "a",
24712
- {
25559
+ if (node.route) {
25560
+ let a_attrs = {
24713
25561
  class: "ROUTEMAP_LINK ROUTEMAP_ROW" + row_class,
24714
25562
  href: "#" + node.route,
24715
25563
  title: node.route
24716
- },
24717
- row
24718
- ]);
24719
- else $row = (0, _yuneta_gobj_js.createElement2)([
25564
+ };
25565
+ if (node.label) {
25566
+ a_attrs["aria-label"] = node.label;
25567
+ a_attrs["data-i18n-aria-label"] = node.label;
25568
+ }
25569
+ if (node.current) a_attrs["aria-current"] = "page";
25570
+ $row = (0, _yuneta_gobj_js.createElement2)([
25571
+ "a",
25572
+ a_attrs,
25573
+ row
25574
+ ]);
25575
+ } else $row = (0, _yuneta_gobj_js.createElement2)([
24720
25576
  "span",
24721
25577
  { class: "ROUTEMAP_ROW ROUTEMAP_STRUCT" + row_class },
24722
25578
  row
@@ -25000,7 +25856,7 @@ function yui_shell_show_route_map(shell, opts) {
25000
25856
  if ((0, _yuneta_gobj_js.gclass_find_by_name)("C_YUI_WINDOW")) {
25001
25857
  let win_ref = { gobj: null };
25002
25858
  let $body = build_body(shell, t);
25003
- let $parent = yui_shell_popup_layer(shell) || typeof document !== "undefined" && document.getElementById("top-layer") || null;
25859
+ let $parent = typeof document !== "undefined" && document.getElementById("top-layer") || null;
25004
25860
  let win = (0, _yuneta_gobj_js.gobj_create_service)(WIN_NAME$2, "C_YUI_WINDOW", {
25005
25861
  $parent,
25006
25862
  subscriber: null,
@@ -26074,7 +26930,13 @@ function add_tab(gobj, gobj2, id, text, icon) {
26074
26930
  let $item = (0, _yuneta_gobj_js.createElement2)([
26075
26931
  "li",
26076
26932
  { class: "" },
26077
- [["a", { href: id }]]
26933
+ [["a", {
26934
+ href: id,
26935
+ title: (0, i18next.t)(text),
26936
+ "data-i18n-title": text,
26937
+ "aria-label": (0, i18next.t)(text),
26938
+ "data-i18n-aria-label": text
26939
+ }]]
26078
26940
  ]);
26079
26941
  $tabs.appendChild($item);
26080
26942
  $item.gobj = gobj2;
@@ -26082,7 +26944,10 @@ function add_tab(gobj, gobj2, id, text, icon) {
26082
26944
  if (icon) $a.appendChild((0, _yuneta_gobj_js.createElement2)([
26083
26945
  "span",
26084
26946
  { class: "icon is-small is-hidden-mobile" },
26085
- ["i", { class: `${icon}` }]
26947
+ ["i", {
26948
+ class: `${icon}`,
26949
+ "aria-hidden": "true"
26950
+ }]
26086
26951
  ]));
26087
26952
  $a.appendChild((0, _yuneta_gobj_js.createElement2)([
26088
26953
  "span",
@@ -26271,7 +27136,9 @@ function add_topic_card(gobj, id, text, icon, desc) {
26271
27136
  class: "yui-nav-item yui-nav-card TREEDB_TOPIC_CARD",
26272
27137
  href: "#",
26273
27138
  "data-topic-href": id,
26274
- "aria-label": text,
27139
+ title: (0, i18next.t)(text),
27140
+ "data-i18n-title": text,
27141
+ "aria-label": (0, i18next.t)(text),
26275
27142
  "data-i18n-aria-label": text
26276
27143
  },
26277
27144
  [[
@@ -27115,7 +27982,7 @@ function open_json_viewer$1(gobj, opts) {
27115
27982
  }
27116
27983
  });
27117
27984
  } else {
27118
- let $win_parent = shell && yui_shell_popup_layer(shell) || typeof document !== "undefined" && document.getElementById("top-layer") || null;
27985
+ let $win_parent = typeof document !== "undefined" && document.getElementById("top-layer") || null;
27119
27986
  priv.json_win = (0, _yuneta_gobj_js.gobj_create_service)(`treedb-topics-jsonwin-${(0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj))}`, "C_YUI_WINDOW", {
27120
27987
  $parent: $win_parent,
27121
27988
  subscriber: null,
@@ -27242,7 +28109,9 @@ function request_print_tranger$1(gobj, path) {
27242
28109
  }, gobj);
27243
28110
  if (ret) {
27244
28111
  (0, _yuneta_gobj_js.log_error)(ret);
27245
- answer_print_tranger_refused$1(gobj, path, { error: refusal_text$1(ret) || "print-tranger failed" });
28112
+ const refusal = refusal_text$1(ret);
28113
+ if (refusal) answer_print_tranger_refused$1(gobj, path, { error: refusal });
28114
+ else answer_print_tranger_refused$1(gobj, path, { i18n: "print-tranger failed" });
27246
28115
  return;
27247
28116
  }
27248
28117
  if (path) priv.drills_in_flight.add(path);
@@ -27310,9 +28179,13 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
27310
28179
  }
27311
28180
  } else gobj.priv.drills_in_flight.clear();
27312
28181
  if (result < 0) {
27313
- if (path) (0, _yuneta_gobj_js.gobj_send_event)(jv, "EV_SUBTREE_ERROR", {
28182
+ if (path && comment) (0, _yuneta_gobj_js.gobj_send_event)(jv, "EV_SUBTREE_ERROR", {
27314
28183
  path,
27315
- error: comment || "print-tranger failed"
28184
+ error: comment
28185
+ }, gobj);
28186
+ else if (path) (0, _yuneta_gobj_js.gobj_send_event)(jv, "EV_SUBTREE_ERROR", {
28187
+ path,
28188
+ i18n: "print-tranger failed"
27316
28189
  }, gobj);
27317
28190
  else yui_shell_show_error(yui_shell_of(gobj), comment || "print-tranger failed", { t: i18next.t });
27318
28191
  return 0;
@@ -29097,7 +29970,7 @@ function present_json_popup(gobj, $box, opts) {
29097
29970
  win: null
29098
29971
  };
29099
29972
  let win = (0, _yuneta_gobj_js.gobj_create_service)(opts.name, "C_YUI_WINDOW", {
29100
- $parent: popup_mount_layer(gobj),
29973
+ $parent: popup_mount_layer(),
29101
29974
  subscriber: null,
29102
29975
  modal: false,
29103
29976
  showMax: true,
@@ -29151,10 +30024,17 @@ function retire_popup_window(win) {
29151
30024
  (0, _yuneta_gobj_js.log_warning)(`${GCLASS_NAME$5}: json window already gone: ${e}`);
29152
30025
  }
29153
30026
  }
29154
- function popup_mount_layer(gobj) {
29155
- let $layer = yui_shell_popup_layer(yui_shell_of(gobj));
29156
- if ($layer) return $layer;
29157
- return document.getElementById("popup-layer") || document.body;
30027
+ /************************************************************
30028
+ * Where a json window of this view lives: null, the WINDOW
30029
+ * layer of the shell, where every window lives -- below the
30030
+ * shell's dialogs, so a confirm asked over it paints above it
30031
+ * (the legacy C_YUI_MAIN '#popup-layer' when a page has one).
30032
+ * It was the shell's popup layer: above the drawer, in the way
30033
+ * of the toolbar's dropdowns, and in an order of its own no
30034
+ * other window could rise above.
30035
+ ************************************************************/
30036
+ function popup_mount_layer() {
30037
+ return document.getElementById("popup-layer") || null;
29158
30038
  }
29159
30039
  /******************************
29160
30040
  * Framework Methods
@@ -29769,7 +30649,10 @@ function col_title_formatter(gobj, col) {
29769
30649
  let text = col_label(col, topic_name);
29770
30650
  let attrs = {};
29771
30651
  const NO_I18N = "\0";
29772
- if ((0, i18next.t)(col.id, { defaultValue: NO_I18N }) !== NO_I18N) attrs.i18n = col.id;
30652
+ let key = null;
30653
+ if (topic_name && (0, i18next.t)(topic_name + "." + col.id, { defaultValue: NO_I18N }) !== NO_I18N) key = topic_name + "." + col.id;
30654
+ else if ((0, i18next.t)(col.id, { defaultValue: NO_I18N }) !== NO_I18N) key = col.id;
30655
+ if (key) attrs.i18n = key;
29773
30656
  return (0, _yuneta_gobj_js.createElement2)([
29774
30657
  "span",
29775
30658
  attrs,
@@ -29863,6 +30746,9 @@ function create_tabulator(gobj) {
29863
30746
  colDef.editable = function() {
29864
30747
  return (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "editable") && !(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly");
29865
30748
  };
30749
+ yui_tabulator_name_editor(colDef, function() {
30750
+ return col_label(col, (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name"));
30751
+ });
29866
30752
  }
29867
30753
  const FILTERABLE = [
29868
30754
  "string",
@@ -71221,7 +72107,7 @@ function open_json_viewer(gobj) {
71221
72107
  });
71222
72108
  (0, _yuneta_gobj_js.gobj_start)(jv);
71223
72109
  } else {
71224
- let $win_parent = shell && yui_shell_popup_layer(shell) || typeof document !== "undefined" && document.getElementById("top-layer") || null;
72110
+ let $win_parent = typeof document !== "undefined" && document.getElementById("top-layer") || null;
71225
72111
  priv.json_win = (0, _yuneta_gobj_js.gobj_create_service)(`treedb-jsonwin-${(0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj))}`, "C_YUI_WINDOW", {
71226
72112
  $parent: $win_parent,
71227
72113
  subscriber: null,
@@ -71335,7 +72221,9 @@ function request_print_tranger(gobj, path) {
71335
72221
  }, gobj);
71336
72222
  if (ret) {
71337
72223
  (0, _yuneta_gobj_js.log_error)(ret);
71338
- answer_print_tranger_refused(gobj, path, { error: refusal_text(ret) || "print-tranger failed" });
72224
+ const refusal = refusal_text(ret);
72225
+ if (refusal) answer_print_tranger_refused(gobj, path, { error: refusal });
72226
+ else answer_print_tranger_refused(gobj, path, { i18n: "print-tranger failed" });
71339
72227
  return;
71340
72228
  }
71341
72229
  if (path) priv.drills_in_flight.add(path);
@@ -71740,9 +72628,13 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
71740
72628
  }
71741
72629
  } else priv.drills_in_flight.clear();
71742
72630
  if (result < 0) {
71743
- if (path) (0, _yuneta_gobj_js.gobj_send_event)(jv, "EV_SUBTREE_ERROR", {
72631
+ if (path && comment) (0, _yuneta_gobj_js.gobj_send_event)(jv, "EV_SUBTREE_ERROR", {
72632
+ path,
72633
+ error: comment
72634
+ }, gobj);
72635
+ else if (path) (0, _yuneta_gobj_js.gobj_send_event)(jv, "EV_SUBTREE_ERROR", {
71744
72636
  path,
71745
- error: comment || "print-tranger failed"
72637
+ i18n: "print-tranger failed"
71746
72638
  }, gobj);
71747
72639
  else yui_shell_show_error(yui_shell_of(gobj), comment || "print-tranger failed", { t: i18next.t });
71748
72640
  return 0;
@@ -83334,7 +84226,8 @@ function apply_dev_traces() {
83334
84226
  * Shell-agnostic: the legacy C_YUI_MAIN shell has a
83335
84227
  * '#top-layer' stacking element; the new C_YUI_SHELL does not.
83336
84228
  * We pass that element when present, otherwise null — C_YUI_WINDOW
83337
- * falls back to document.body by contract.
84229
+ * then takes the window layer of the shell (document.body while
84230
+ * there is none), and follows the shell across a logout.
83338
84231
  ************************************************************/
83339
84232
  function setup_dev(self, show) {
83340
84233
  if (show) {
@@ -83424,7 +84317,8 @@ var WIN_NAME$1 = "Frontend-View-Window";
83424
84317
  * Shell-agnostic: the legacy C_YUI_MAIN shell has a
83425
84318
  * '#top-layer' stacking element; the new C_YUI_SHELL does not.
83426
84319
  * We pass that element when present, otherwise null — C_YUI_WINDOW
83427
- * falls back to document.body by contract.
84320
+ * then takes the window layer of the shell (document.body while
84321
+ * there is none), and follows the shell across a logout.
83428
84322
  *
83429
84323
  * Returns the window gobj, or null.
83430
84324
  ************************************************************/