@yuneta/gobj-ui 7.26.34 → 7.26.36

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 +1109 -143
  3. package/dist/gobj-ui.es.js +1109 -143
  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 +250 -46
  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 +348 -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
@@ -540,14 +540,26 @@ function text_list_item_formatter(label) {
540
540
  * ALWAYS RENDERED, hidden when empty, so the runtime setters have a
541
541
  * node to write into and never rebuild the control that holds it.
542
542
  *
543
- * role="status" and NOT aria-hidden, deliberately. The control
544
- * carries an explicit aria-label, and an explicit label REPLACES
545
- * the element's content for a screen reader -- so a badge inside
546
- * it would be silent. As its own live region it is both read and
547
- * announced when the count changes, which is the whole point of a
548
- * badge that means "something needs you". For the same reason a
549
- * write that changes nothing touches no DOM: rewriting the same
550
- * number would have it announced again on every tick.
543
+ * PART OF THE CONTROL'S NAME (name_control_with_badge()). The
544
+ * control carries an explicit aria-label, and an explicit label
545
+ * REPLACES the element's content for a screen reader -- so a badge
546
+ * inside it is silent, and inside a <button> (the toolbar, an
547
+ * accordion head) its children are not even in the tree. The
548
+ * control is labelled by ITSELF (its aria-label, which
549
+ * refresh_language() re-translates) and by the badge: the name is
550
+ * "Alarms 3" while there is a count, "Alarms" while there is none,
551
+ * with nothing composed in javascript that a language switch would
552
+ * leave behind.
553
+ *
554
+ * NOT a live region any more. Up to 7.26.34 the badge was its own
555
+ * role="status", and that said "3" with no word of what has three;
556
+ * hidden while empty, it was not in the tree when the first count
557
+ * arrived, so 0 -> 1 was most likely never said; and inside a
558
+ * <button> it was not there at all. The change is said by the
559
+ * shell, in one live region of its own that is always there, with
560
+ * the name of the item: "Alarms, 3" (yui_shell_set_nav_item_badge,
561
+ * yui_shell_set_toolbar_item_badge). A write that changes nothing
562
+ * still touches no DOM.
551
563
  *
552
564
  * A TONE, optional: one of BADGE_TONES, drawn as Bulma's
553
565
  * `is-<tone>` on the badge. It says HOW BAD the count is -- the
@@ -573,6 +585,7 @@ var BADGE_TONES = Object.freeze([
573
585
  "primary",
574
586
  "link"
575
587
  ]);
588
+ var __badge_seq__ = 0;
576
589
  /************************************************************
577
590
  * The text a badge shows for `value`, "" when it shows none.
578
591
  ************************************************************/
@@ -606,10 +619,7 @@ function badge_tone(tone, who) {
606
619
  function badge_node(value, tone, classes, who) {
607
620
  let text = badge_text(value);
608
621
  let known = badge_tone(tone, who);
609
- let attrs = {
610
- class: known ? `${classes} is-${known}` : classes,
611
- role: "status"
612
- };
622
+ let attrs = { class: known ? `${classes} is-${known}` : classes };
613
623
  if (!text) attrs.hidden = "hidden";
614
624
  return [
615
625
  "span",
@@ -618,6 +628,31 @@ function badge_node(value, tone, classes, who) {
618
628
  ];
619
629
  }
620
630
  /************************************************************
631
+ * Make the count part of the NAME of the control that carries
632
+ * the badge: its `aria-labelledby` points at the control itself
633
+ * and at the badge.
634
+ *
635
+ * The control keeps its `aria-label` and its
636
+ * data-i18n-aria-label: a self-reference reads the aria-label,
637
+ * so the name is the translated label followed by the badge's
638
+ * text -- "Alarms 3" -- or the label alone while the badge is
639
+ * empty. A painted count is in the name at once, and a language
640
+ * switch re-translates the label as it always did.
641
+ *
642
+ * Both are createElement2() descriptions, before anything is
643
+ * built: `control_attrs` (the attrs of the <a> or <button>, which
644
+ * must carry its aria-label) gets an `id` when it has none, and
645
+ * the `aria-labelledby`; `badge` (what badge_node() answered)
646
+ * gets its `id`.
647
+ ************************************************************/
648
+ function name_control_with_badge(control_attrs, badge) {
649
+ let seq = ++__badge_seq__;
650
+ if (!control_attrs.id) control_attrs.id = `yui-badged-${seq}`;
651
+ let badge_attrs = badge[1];
652
+ badge_attrs.id = `yui-badge-${seq}`;
653
+ control_attrs["aria-labelledby"] = `${control_attrs.id} ${badge_attrs.id}`;
654
+ }
655
+ /************************************************************
621
656
  * Write a badge that is already in the DOM, as badge_node()
622
657
  * draws it. `tone` is one badge_tone() answered: the setter
623
658
  * checks it once for all the badges it paints.
@@ -1014,6 +1049,8 @@ function secondary_nav_renders(item, layout) {
1014
1049
  *
1015
1050
  * Parses a JSON config to build:
1016
1051
  * - Layers (z-stacked): base, overlay, popup, modal, notification, loading
1052
+ * (the overlay holds the WINDOW layer, where the windows live,
1053
+ * under the drawer: yui_shell_window_layer())
1017
1054
  * - Zones (inside base layer): top, top-sub, left, center, right,
1018
1055
  * bottom-sub, bottom
1019
1056
  * - Menus (primary + submenus) rendered via C_YUI_NAV — one nav
@@ -1041,6 +1078,8 @@ function secondary_nav_renders(item, layout) {
1041
1078
  ***************************************************************/
1042
1079
  var GCLASS_NAME$22 = "C_YUI_SHELL";
1043
1080
  var __last_shell__ = null;
1081
+ var __shell_watchers__ = [];
1082
+ var __window_z__ = 10;
1044
1083
  var ZONE_IDS = [
1045
1084
  "top",
1046
1085
  "top-sub",
@@ -1075,10 +1114,12 @@ var attrs_table$22 = [
1075
1114
  var PRIVATE_DATA$22 = {
1076
1115
  zones: {},
1077
1116
  layers: {},
1117
+ window_layer: null,
1078
1118
  stages: {},
1079
1119
  navs: [],
1080
1120
  item_index: {},
1081
1121
  nav_badges: {},
1122
+ badge_status: null,
1082
1123
  dynamic_items: {},
1083
1124
  section_pos: {},
1084
1125
  route_mru: [],
@@ -1119,6 +1160,7 @@ function mt_create$22(gobj) {
1119
1160
  if (subscriber) gobj_subscribe_event(gobj, null, {}, subscriber);
1120
1161
  __last_shell__ = gobj;
1121
1162
  build_ui$19(gobj);
1163
+ tell_shell_watchers(gobj);
1122
1164
  }
1123
1165
  /***************************************************************
1124
1166
  * Framework Method: Start
@@ -1302,10 +1344,11 @@ function mt_stop$20(gobj) {
1302
1344
  ***************************************************************/
1303
1345
  function mt_destroy$22(gobj) {
1304
1346
  close_toolbar_dropdown(gobj);
1347
+ if (__last_shell__ === gobj) __last_shell__ = null;
1305
1348
  let $container = gobj_read_attr(gobj, "$container");
1306
1349
  if ($container && $container.parentNode) $container.parentNode.removeChild($container);
1350
+ tell_shell_watchers(gobj);
1307
1351
  gobj_write_attr(gobj, "$container", null);
1308
- if (__last_shell__ === gobj) __last_shell__ = null;
1309
1352
  }
1310
1353
  /***************************
1311
1354
  * Local Methods
@@ -1428,6 +1471,17 @@ function build_ui$19(gobj) {
1428
1471
  $container.appendChild($layer);
1429
1472
  priv.layers[id] = $layer;
1430
1473
  }
1474
+ let $windows = createElement2(["div", { class: "SHELL_WINDOW_LAYER yui-window-layer" }]);
1475
+ priv.layers.overlay.appendChild($windows);
1476
+ priv.window_layer = $windows;
1477
+ let $status = createElement2(["div", {
1478
+ class: "SHELL_BADGE_STATUS is-sr-only",
1479
+ role: "status",
1480
+ "aria-live": "polite",
1481
+ "aria-atomic": "true"
1482
+ }]);
1483
+ $container.appendChild($status);
1484
+ priv.badge_status = $status;
1431
1485
  let $base = priv.layers.base;
1432
1486
  $base.classList.add("yui-base-grid");
1433
1487
  let zones_cfg = shell_cfg.zones || {};
@@ -2021,14 +2075,18 @@ function toolbar_link_attrs(it) {
2021
2075
  ************************************************************/
2022
2076
  function build_toolbar_action_item(gobj, it) {
2023
2077
  let children = [];
2024
- if (!empty_string(it.icon)) children.push([
2025
- "span",
2026
- { class: "icon yui-toolbar-icon" },
2027
- [["i", {
2028
- class: it.icon,
2029
- "aria-hidden": "true"
2030
- }], badge_node(it.badge, it.badge_tone, "TOOLBAR_ITEM_BADGE yui-badge yui-toolbar-badge", `C_YUI_SHELL: toolbar item '${it.id || "?"}'`)]
2031
- ]);
2078
+ let badge = null;
2079
+ if (!empty_string(it.icon)) {
2080
+ badge = badge_node(it.badge, it.badge_tone, "TOOLBAR_ITEM_BADGE yui-badge yui-toolbar-badge", `C_YUI_SHELL: toolbar item '${it.id || "?"}'`);
2081
+ children.push([
2082
+ "span",
2083
+ { class: "icon yui-toolbar-icon" },
2084
+ [["i", {
2085
+ class: it.icon,
2086
+ "aria-hidden": "true"
2087
+ }], badge]
2088
+ ]);
2089
+ }
2032
2090
  if (!empty_string(it.name)) children.push([
2033
2091
  "span",
2034
2092
  {
@@ -2055,6 +2113,7 @@ function build_toolbar_action_item(gobj, it) {
2055
2113
  btn_attrs.title = tip;
2056
2114
  btn_attrs["data-i18n-title"] = tip;
2057
2115
  }
2116
+ if (badge && !empty_string(aria_key)) name_control_with_badge(btn_attrs, badge);
2058
2117
  let link = toolbar_link_attrs(it);
2059
2118
  if (link) {
2060
2119
  delete btn_attrs.type;
@@ -2839,23 +2898,32 @@ function update_secondary_nav_visibility(gobj, entry) {
2839
2898
  }
2840
2899
  }
2841
2900
  /************************************************************
2842
- * Does a menu of this shell declare an item `item_id`: an item
2843
- * of a menu of app_config.json, of one of their submenus, or
2844
- * one given to yui_shell_set_submenu() since.
2901
+ * The item `item_id` a menu of this shell declares, null when
2902
+ * none does: an item of a menu of app_config.json, of one of
2903
+ * their submenus, or one given to yui_shell_set_submenu() since
2904
+ * -- which comes first, because it replaced what the submenu
2905
+ * declared and is what the navs draw.
2845
2906
  ************************************************************/
2846
- function nav_item_declared(gobj, item_id) {
2907
+ function nav_item_of(gobj, item_id) {
2847
2908
  let priv = gobj.priv;
2848
2909
  let config = gobj_read_attr(gobj, "config") || {};
2849
2910
  let menus = is_object(config.menu) ? config.menu : {};
2850
- let holds = (items) => is_array(items) && items.some((it) => it && it.id === item_id);
2911
+ let find = (items) => is_array(items) && items.find((it) => it && it.id === item_id) || null;
2912
+ for (let parent_item_id in priv.dynamic_items) {
2913
+ let it = find(priv.dynamic_items[parent_item_id]);
2914
+ if (it) return it;
2915
+ }
2851
2916
  for (let menu_id in menus) {
2852
2917
  let items = menus[menu_id] && menus[menu_id].items;
2853
2918
  if (!is_array(items)) continue;
2854
- if (holds(items)) return true;
2855
- for (let it of items) if (it && it.submenu && holds(it.submenu.items)) return true;
2919
+ let it = find(items);
2920
+ if (it) return it;
2921
+ for (let parent of items) {
2922
+ let sub = parent && parent.submenu && find(parent.submenu.items);
2923
+ if (sub) return sub;
2924
+ }
2856
2925
  }
2857
- for (let parent_item_id in priv.dynamic_items) if (holds(priv.dynamic_items[parent_item_id])) return true;
2858
- return false;
2926
+ return null;
2859
2927
  }
2860
2928
  /************************************************************
2861
2929
  * Every C_YUI_NAV of this shell: the navs of its menus, and the
@@ -2873,6 +2941,61 @@ function shell_navs(gobj) {
2873
2941
  }
2874
2942
  return navs;
2875
2943
  }
2944
+ /************************************************************
2945
+ * Say a badge that changed, with the name of what it counts:
2946
+ * "Alarms, 3", in the shell's live region (build_ui()).
2947
+ *
2948
+ * The badge itself is no live region (yui_badge.js): its number
2949
+ * alone said nothing, hidden while empty it missed the first
2950
+ * count, and inside a <button> it was not there at all. The
2951
+ * setters call this only when the text a reader would see
2952
+ * changes and is not empty: a count going away is not news, and
2953
+ * the same count again is not either.
2954
+ *
2955
+ * `name_key` is the key of the control's name; the phrase is a
2956
+ * key of its own, so a language orders and punctuates it its
2957
+ * way. Said once: a live region needs no re-translation.
2958
+ ************************************************************/
2959
+ function announce_badge(gobj, name_key, text) {
2960
+ let priv = gobj.priv;
2961
+ let $status = priv.badge_status;
2962
+ if (!$status) {
2963
+ log_error(`C_YUI_SHELL: no live region to say badge '${name_key}': ${text}`);
2964
+ return;
2965
+ }
2966
+ let message;
2967
+ if (typeof priv.translator === "function") {
2968
+ const tr = priv.translator;
2969
+ message = tr("{{name}}, {{n}}", {
2970
+ name: tr(name_key),
2971
+ n: text
2972
+ });
2973
+ } else message = `${name_key}, ${text}`;
2974
+ $status.textContent = message;
2975
+ }
2976
+ /************************************************************
2977
+ * A shell was built (mt_create) or is being destroyed
2978
+ * (mt_destroy): tell every gobj that keeps DOM in a shell's
2979
+ * layers without being its child (yui_shell_watch_changes()).
2980
+ * Each one decides in its own action what it means for it --
2981
+ * the trace shows EV_SHELL_CHANGED arriving, from a `!!` shell
2982
+ * when it is the one going.
2983
+ *
2984
+ * An event, not a call into the window's module: the shell does
2985
+ * not import its watchers, and the move is work, so it crosses
2986
+ * their FSM. A watcher that is being destroyed is one whose
2987
+ * mt_destroy did not unwatch: it is dropped, and said.
2988
+ ************************************************************/
2989
+ function tell_shell_watchers(gobj) {
2990
+ for (let watcher of __shell_watchers__.slice()) {
2991
+ if (gobj_is_destroying(watcher)) {
2992
+ log_error(`C_YUI_SHELL: a destroyed gobj still watches the shell (${gobj_short_name(watcher)}): it must call yui_shell_unwatch_changes() in its mt_destroy`);
2993
+ yui_shell_unwatch_changes(watcher);
2994
+ continue;
2995
+ }
2996
+ gobj_send_event(watcher, "EV_SHELL_CHANGED", {}, gobj);
2997
+ }
2998
+ }
2876
2999
  /***************************
2877
3000
  * Actions
2878
3001
  ***************************/
@@ -3142,6 +3265,81 @@ function yui_shell_of(gobj) {
3142
3265
  return __last_shell__;
3143
3266
  }
3144
3267
  /************************************************************
3268
+ * The DOM where a C_YUI_WINDOW lives when its host names no
3269
+ * `$parent`: the WINDOW layer, a stacking context of its own
3270
+ * inside the overlay layer -- above the content, below the
3271
+ * drawer, the toolbar's dropdowns, the dialogs and the toasts,
3272
+ * whatever z-index a window takes there. Null with no shell (the
3273
+ * window then lives in document.body).
3274
+ ************************************************************/
3275
+ function yui_shell_window_layer(shell_gobj) {
3276
+ if (!shell_gobj || !shell_gobj.priv) return null;
3277
+ let priv = shell_gobj.priv;
3278
+ return priv.window_layer || priv.layers && priv.layers.overlay || null;
3279
+ }
3280
+ /************************************************************
3281
+ * Put a window on top of the others: the next number of ONE
3282
+ * counter, the same for a window of the dock and for a floating
3283
+ * one.
3284
+ *
3285
+ * Up to 7.26.34 there were two orders: the dock raised its windows
3286
+ * to a counter of its own (11, 12, ...), and a window without a
3287
+ * dock sat at 3 and was raised by being moved to the end of its
3288
+ * parent -- so a window opened LATER without a dock (a treedb's
3289
+ * raw json) opened UNDER any docked one, and no click could bring
3290
+ * it up. They share the window layer, and this one number orders
3291
+ * them all. It grows for ever, and it can: nothing else lives in
3292
+ * the window layer's stacking context.
3293
+ *
3294
+ * A number, not a move in the DOM: moving an element takes its
3295
+ * focus, its scroll positions and the pointer press that started
3296
+ * on it.
3297
+ ************************************************************/
3298
+ function yui_shell_raise_window($el) {
3299
+ if (!$el || !$el.style) {
3300
+ log_error("C_YUI_SHELL: yui_shell_raise_window() needs an element");
3301
+ return;
3302
+ }
3303
+ __window_z__ += 1;
3304
+ $el.style.zIndex = String(__window_z__);
3305
+ }
3306
+ /************************************************************
3307
+ * Ask to be told (EV_SHELL_CHANGED, an INPUT event the gclass of
3308
+ * `gobj` must take in its FSM) when a shell is built or
3309
+ * destroyed.
3310
+ *
3311
+ * For a gobj that keeps DOM in a shell's layers without being
3312
+ * its child -- a window parented to the app, the dock. A shell's
3313
+ * children are destroyed with it; such a gobj is not, and its
3314
+ * place goes with the shell: a logout destroys the shell, the
3315
+ * login builds another. Told, it takes its place again in the
3316
+ * action.
3317
+ *
3318
+ * Idempotent. Pair it with yui_shell_unwatch_changes() in
3319
+ * mt_destroy, or the next shell sends the event to a destroyed
3320
+ * gobj.
3321
+ *
3322
+ * function mt_create(gobj) {
3323
+ * ...
3324
+ * yui_shell_watch_changes(gobj);
3325
+ * }
3326
+ * function mt_destroy(gobj) {
3327
+ * yui_shell_unwatch_changes(gobj);
3328
+ * ...
3329
+ * }
3330
+ ************************************************************/
3331
+ function yui_shell_watch_changes(gobj) {
3332
+ if (!is_gobj(gobj)) {
3333
+ log_error("C_YUI_SHELL: yui_shell_watch_changes() needs a gobj");
3334
+ return;
3335
+ }
3336
+ if (__shell_watchers__.indexOf(gobj) < 0) __shell_watchers__.push(gobj);
3337
+ }
3338
+ function yui_shell_unwatch_changes(gobj) {
3339
+ let idx = __shell_watchers__.indexOf(gobj);
3340
+ if (idx >= 0) __shell_watchers__.splice(idx, 1);
3341
+ }
3342
+ /************************************************************
3145
3343
  * Programmatic navigation.
3146
3344
  *
3147
3345
  * Default — PUSH: the user MOVED somewhere new, so change the URL via
@@ -3397,7 +3595,13 @@ function yui_shell_set_toolbar_item_badge(shell_gobj, item_id, value, tone) {
3397
3595
  if (!$container) return;
3398
3596
  let $badge = $container.querySelector(`[data-toolbar-item-id="${item_id}"] .yui-toolbar-badge`);
3399
3597
  if (!$badge) return;
3598
+ let before = $badge.hasAttribute("hidden") ? "" : $badge.textContent;
3400
3599
  paint_badge($badge, value, tone);
3600
+ let now = badge_text(value);
3601
+ if (now && now !== before) {
3602
+ let $item = $badge.closest("[data-toolbar-item-id]");
3603
+ announce_badge(shell_gobj, $item && $item.getAttribute("data-i18n-aria-label") || item_id, now);
3604
+ }
3401
3605
  }
3402
3606
  /************************************************************
3403
3607
  * Set (or clear) the badge of a MENU item — the twin of
@@ -3431,8 +3635,11 @@ function yui_shell_set_nav_item_badge(shell_gobj, item_id, value, tone) {
3431
3635
  tone = badge_tone(tone, `C_YUI_SHELL: yui_shell_set_nav_item_badge('${item_id}')`);
3432
3636
  if (!shell_gobj || !is_gobj(shell_gobj) || gobj_is_destroying(shell_gobj) || empty_string(item_id)) return;
3433
3637
  let priv = shell_gobj.priv;
3434
- if (!priv || !nav_item_declared(shell_gobj, item_id)) return;
3638
+ let item = priv ? nav_item_of(shell_gobj, item_id) : null;
3639
+ if (!item) return;
3435
3640
  let badge = value === void 0 ? null : value;
3641
+ let was = priv.nav_badges[item_id];
3642
+ let before = badge_text(was ? was.badge : item.badge);
3436
3643
  priv.nav_badges[item_id] = {
3437
3644
  badge,
3438
3645
  badge_tone: tone
@@ -3442,6 +3649,8 @@ function yui_shell_set_nav_item_badge(shell_gobj, item_id, value, tone) {
3442
3649
  badge,
3443
3650
  badge_tone: tone
3444
3651
  }, shell_gobj);
3652
+ let now = badge_text(badge);
3653
+ if (now && now !== before) announce_badge(shell_gobj, item.name || item_id, now);
3445
3654
  }
3446
3655
  /************************************************************
3447
3656
  * Programmatic close of any open toolbar dropdown. Useful for
@@ -4045,7 +4254,7 @@ var MOBILE_MAX = 768;
4045
4254
  ***************************************************************/
4046
4255
  var attrs_table$21 = [
4047
4256
  SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
4048
- SDATA(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)"),
4257
+ SDATA(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"),
4049
4258
  SDATA(data_type_t.DTP_INTEGER, "x", 0, "300", "X position"),
4050
4259
  SDATA(data_type_t.DTP_INTEGER, "y", 0, "100", "Y position"),
4051
4260
  SDATA(data_type_t.DTP_INTEGER, "width", 0, "700", "Width of the window"),
@@ -4081,7 +4290,11 @@ var attrs_table$21 = [
4081
4290
  SDATA(data_type_t.DTP_POINTER, "win_resize_handler", 0, null, "Internal: native window 'resize' listener"),
4082
4291
  SDATA_END()
4083
4292
  ];
4084
- var PRIVATE_DATA$21 = { prevSize: 0 };
4293
+ var PRIVATE_DATA$21 = {
4294
+ prevSize: 0,
4295
+ placed_by_shell: false,
4296
+ back_shell: null
4297
+ };
4085
4298
  var __gclass__$21 = null;
4086
4299
  /******************************
4087
4300
  * Framework Methods
@@ -4096,6 +4309,7 @@ function mt_create$21(gobj) {
4096
4309
  gobj_write_attr(gobj, "window_id", window_id);
4097
4310
  let manager = resolve_manager(gobj);
4098
4311
  build_ui$18(gobj);
4312
+ yui_shell_watch_changes(gobj);
4099
4313
  if (manager) {
4100
4314
  let $c = gobj_read_attr(gobj, "$container");
4101
4315
  if ($c) $c.addEventListener("pointerdown", function() {
@@ -4111,7 +4325,16 @@ function mt_create$21(gobj) {
4111
4325
  title_prefix: gobj_read_str_attr(gobj, "title_prefix") || "",
4112
4326
  icon: gobj_read_attr(gobj, "icon") || ""
4113
4327
  }, gobj);
4114
- } else if (gobj_read_bool_attr(gobj, "back_dismissable")) register_back_overlay(gobj);
4328
+ } else {
4329
+ let $c = gobj_read_attr(gobj, "$container");
4330
+ if ($c) {
4331
+ yui_shell_raise_window($c);
4332
+ $c.addEventListener("pointerdown", function() {
4333
+ if (!gobj_is_destroying(gobj)) gobj_send_event(gobj, "EV_SHOW", {}, gobj);
4334
+ }, true);
4335
+ }
4336
+ if (gobj_read_bool_attr(gobj, "back_dismissable")) register_back_overlay(gobj);
4337
+ }
4115
4338
  let on_win_resize = function() {
4116
4339
  handleResize(gobj);
4117
4340
  };
@@ -4122,13 +4345,15 @@ function mt_create$21(gobj) {
4122
4345
  * Framework Method: Destroy
4123
4346
  ***************************************************************/
4124
4347
  function mt_destroy$21(gobj) {
4348
+ let priv = gobj.priv;
4349
+ yui_shell_unwatch_changes(gobj);
4125
4350
  let manager = gobj_read_pointer_attr(gobj, "manager");
4126
4351
  if (manager) gobj_send_event(manager, "EV_UNREGISTER_WINDOW", { window: gobj_full_name(gobj) }, gobj);
4127
4352
  let back_overlay = gobj_read_attr(gobj, "back_overlay");
4128
4353
  if (back_overlay) {
4129
- let shell = yui_shell_of(gobj);
4130
- if (shell) yui_shell_overlay_dismissed(shell, back_overlay);
4354
+ if (priv.back_shell) yui_shell_overlay_dismissed(priv.back_shell, back_overlay);
4131
4355
  gobj_write_attr(gobj, "back_overlay", null);
4356
+ priv.back_shell = null;
4132
4357
  }
4133
4358
  let on_win_resize = gobj_read_attr(gobj, "win_resize_handler");
4134
4359
  if (on_win_resize) {
@@ -4540,7 +4765,8 @@ function build_ui$18(gobj) {
4540
4765
  }
4541
4766
  refresh_language($container, t);
4542
4767
  let $parent = gobj_read_attr(gobj, "$parent");
4543
- if (!$parent) $parent = default_parent(gobj);
4768
+ gobj.priv.placed_by_shell = !$parent;
4769
+ if (!$parent) $parent = window_place(gobj);
4544
4770
  $parent.appendChild($container);
4545
4771
  if (gobj_read_bool_attr(gobj, "openMaximized")) max$2(gobj);
4546
4772
  else if (!mobile) {
@@ -4582,11 +4808,13 @@ function register_back_overlay(gobj) {
4582
4808
  };
4583
4809
  let overlay = yui_shell_register_overlay(shell, function() {
4584
4810
  gobj_write_attr(gobj, "back_overlay", null);
4811
+ gobj.priv.back_shell = null;
4585
4812
  const closing = close_window(gobj);
4586
4813
  if (closing.state === "vetoed") rearm();
4587
4814
  else if (closing.state === "asking") closing.answer.then(rearm);
4588
4815
  }, { keep_on_navigate: gobj_read_bool_attr(gobj, "keep_on_navigate") });
4589
4816
  gobj_write_attr(gobj, "back_overlay", overlay);
4817
+ gobj.priv.back_shell = overlay ? shell : null;
4590
4818
  }
4591
4819
  /************************************************************
4592
4820
  * Ask to close the window. Answers what happened:
@@ -4628,7 +4856,8 @@ function close_window(gobj) {
4628
4856
  }
4629
4857
  /************************************************************
4630
4858
  * Where a window lives when its host names no `$parent`: the
4631
- * OVERLAY layer of the shell -- above the content, below the
4859
+ * WINDOW layer of its shell (yui_shell_window_layer()), inside
4860
+ * the overlay layer -- above the content, below the drawer, the
4632
4861
  * toolbar's dropdowns, the dialogs and the toasts.
4633
4862
  *
4634
4863
  * It used to be document.body, and that put every window above
@@ -4641,14 +4870,17 @@ function close_window(gobj) {
4641
4870
  * toolbar, any dialog opened with a window on screen, the same.
4642
4871
  *
4643
4872
  * Inside the layer the dock's z-index still orders the windows
4644
- * among themselves. No shell (a page with windows and no shell):
4645
- * the body, as before.
4873
+ * among themselves, and no number it gives them reaches the
4874
+ * drawer (7.26.31 put them in the overlay layer itself, beside
4875
+ * it). No shell (a page with windows and no shell, or the time
4876
+ * between a logout and the next login): the body.
4877
+ *
4878
+ * The place is the shell's, and a window parented to the app
4879
+ * outlives it: ac_shell_changed() moves it when a shell goes or
4880
+ * comes.
4646
4881
  ************************************************************/
4647
- function default_parent(gobj) {
4648
- let shell = yui_shell_of(gobj);
4649
- let layers = shell && shell.priv && shell.priv.layers;
4650
- if (layers && layers.overlay) return layers.overlay;
4651
- return document.body;
4882
+ function window_place(gobj) {
4883
+ return yui_shell_window_layer(yui_shell_of(gobj)) || document.body;
4652
4884
  }
4653
4885
  /************************************************************
4654
4886
  *
@@ -4920,8 +5152,7 @@ function ac_show$15(gobj, event, kw, src) {
4920
5152
  return -1;
4921
5153
  }
4922
5154
  $container.style.removeProperty("display");
4923
- let $parent = $container.parentNode;
4924
- if ($parent && $parent.lastChild !== $container) $parent.appendChild($container);
5155
+ yui_shell_raise_window($container);
4925
5156
  return 0;
4926
5157
  }
4927
5158
  /************************************************************
@@ -4938,6 +5169,49 @@ function ac_close(gobj, event, kw, src) {
4938
5169
  close_window(gobj);
4939
5170
  return 0;
4940
5171
  }
5172
+ /************************************************************
5173
+ * EV_SHELL_CHANGED -- a shell was built, or is being destroyed
5174
+ * (`src`, then destroying): the place this window lives in may
5175
+ * have gone with it, or be in the new one
5176
+ * (yui_shell_watch_changes()).
5177
+ *
5178
+ * - A window whose host named no `$parent` takes the window place
5179
+ * again: the window layer of the shell that governs it NOW,
5180
+ * document.body while there is none. So the developer window
5181
+ * outlives a logout in the body, and the login's shell takes it
5182
+ * in -- above its content, under its dialogs. From 7.26.31 to
5183
+ * 7.26.34 it stayed in the destroyed shell's layer: alive,
5184
+ * detached, invisible.
5185
+ * - A window its host placed stays where it was put, unless that
5186
+ * element was inside the shell being destroyed: then no place
5187
+ * is left, and it takes the window place from now on.
5188
+ * - Its Back entry, when the shell being destroyed held it, went
5189
+ * with that shell's history and is forgotten. The next shell
5190
+ * does not get one: a history entry pushed while a shell is
5191
+ * still being built would sit under its first route. Back no
5192
+ * longer closes such a window; its ✕ does, as before 7.26.31.
5193
+ ************************************************************/
5194
+ function ac_shell_changed$1(gobj, event, kw, src) {
5195
+ let priv = gobj.priv;
5196
+ let going = gobj_is_destroying(src);
5197
+ if (going && priv.back_shell === src) {
5198
+ gobj_write_attr(gobj, "back_overlay", null);
5199
+ priv.back_shell = null;
5200
+ }
5201
+ let $container = gobj_read_attr(gobj, "$container");
5202
+ if (!$container) {
5203
+ log_error(`${gobj_short_name(gobj)}: EV_SHELL_CHANGED without a container`);
5204
+ return -1;
5205
+ }
5206
+ if (!priv.placed_by_shell) {
5207
+ let $shell = going ? gobj_read_attr(src, "$container") : null;
5208
+ if (!$shell || !$shell.contains($container)) return 0;
5209
+ priv.placed_by_shell = true;
5210
+ }
5211
+ let $place = window_place(gobj);
5212
+ if ($container.parentNode !== $place) $place.appendChild($container);
5213
+ return 0;
5214
+ }
4941
5215
  /***************************
4942
5216
  * FSM
4943
5217
  ***************************/
@@ -4973,6 +5247,11 @@ function create_gclass$21(gclass_name) {
4973
5247
  "EV_CLOSE_WINDOW",
4974
5248
  ac_close,
4975
5249
  null
5250
+ ],
5251
+ [
5252
+ "EV_SHELL_CHANGED",
5253
+ ac_shell_changed$1,
5254
+ null
4976
5255
  ]
4977
5256
  ]]];
4978
5257
  const event_types = [
@@ -4982,7 +5261,8 @@ function create_gclass$21(gclass_name) {
4982
5261
  ["EV_REFRESH", 0],
4983
5262
  ["EV_SHOW", 0],
4984
5263
  ["EV_HIDE", 0],
4985
- ["EV_CLOSE_WINDOW", 0]
5264
+ ["EV_CLOSE_WINDOW", 0],
5265
+ ["EV_SHELL_CHANGED", 0]
4986
5266
  ];
4987
5267
  __gclass__$21 = gclass_create(gclass_name, event_types, states, gmt$21, 0, attrs_table$21, PRIVATE_DATA$21, 0, 0, 0, 0);
4988
5268
  if (!__gclass__$21) return -1;
@@ -5033,6 +5313,7 @@ function register_c_yui_window() {
5033
5313
  var GCLASS_NAME$20 = "C_YUI_WINDOW_MANAGER";
5034
5314
  var DOCK_Z = 1e6;
5035
5315
  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>";
5316
+ var __chip_seq__ = 0;
5036
5317
  /***************************************************************
5037
5318
  * Data
5038
5319
  ***************************************************************/
@@ -5042,15 +5323,15 @@ var attrs_table$20 = [
5042
5323
  SDATA(data_type_t.DTP_STRING, "dock_corner", 0, "bottom-left", "Floating placement: top-left | top-right | bottom-left | bottom-right"),
5043
5324
  SDATA(data_type_t.DTP_STRING, "inline_selector", 0, "", "CSS selector of the container the dock mounts into when inline (e.g. a shell zone)"),
5044
5325
  SDATA(data_type_t.DTP_STRING, "responsive_query", 0, "(max-width: 768px)", "Media query that selects inline placement when dock_mode is 'responsive'"),
5045
- SDATA(data_type_t.DTP_POINTER, "$parent", 0, null, "Where the floating dock mounts (default document.body)"),
5326
+ SDATA(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)"),
5046
5327
  SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Internal: the dock element"),
5047
5328
  SDATA_END()
5048
5329
  ];
5049
5330
  var PRIVATE_DATA$20 = {
5050
5331
  windows: null,
5051
- z: 0,
5052
5332
  mql: null,
5053
- mql_handler: null
5333
+ mql_handler: null,
5334
+ lang_shell: null
5054
5335
  };
5055
5336
  var __gclass__$20 = null;
5056
5337
  /******************************
@@ -5062,7 +5343,6 @@ var __gclass__$20 = null;
5062
5343
  function mt_create$20(gobj) {
5063
5344
  let priv = gobj.priv;
5064
5345
  priv.windows = [];
5065
- priv.z = 10;
5066
5346
  if ((gobj_read_attr(gobj, "dock_mode") || "floating") === "responsive") {
5067
5347
  let q = gobj_read_attr(gobj, "responsive_query") || "(max-width: 768px)";
5068
5348
  priv.mql = window.matchMedia(q);
@@ -5072,6 +5352,8 @@ function mt_create$20(gobj) {
5072
5352
  priv.mql.addEventListener("change", priv.mql_handler);
5073
5353
  }
5074
5354
  build_dock(gobj);
5355
+ yui_shell_watch_changes(gobj);
5356
+ follow_language(gobj);
5075
5357
  const subscriber = gobj_read_pointer_attr(gobj, "subscriber");
5076
5358
  if (subscriber) gobj_subscribe_event(gobj, null, {}, subscriber);
5077
5359
  }
@@ -5080,6 +5362,11 @@ function mt_create$20(gobj) {
5080
5362
  ***************************************************************/
5081
5363
  function mt_destroy$20(gobj) {
5082
5364
  let priv = gobj.priv;
5365
+ yui_shell_unwatch_changes(gobj);
5366
+ if (priv.lang_shell) {
5367
+ if (!gobj_is_destroying(priv.lang_shell)) gobj_unsubscribe_event(priv.lang_shell, "EV_LANGUAGE_CHANGED", {}, gobj);
5368
+ priv.lang_shell = null;
5369
+ }
5083
5370
  if (priv.mql && priv.mql_handler) {
5084
5371
  priv.mql.removeEventListener("change", priv.mql_handler);
5085
5372
  priv.mql = null;
@@ -5091,6 +5378,37 @@ function mt_destroy$20(gobj) {
5091
5378
  * Local Methods
5092
5379
  ***************************/
5093
5380
  /************************************************************
5381
+ * Hear the language of the shell that holds the dock -- the
5382
+ * dock outlives a shell, so it follows them: the dying one's
5383
+ * subscription goes with it, the next one is heard. The
5384
+ * toggle of a chip whose window's title has a DATA half has a
5385
+ * composed title, which no key carries: it is written again in
5386
+ * the new language (ac_language_changed).
5387
+ ************************************************************/
5388
+ function follow_language(gobj) {
5389
+ let priv = gobj.priv;
5390
+ let shell = yui_shell_of(gobj);
5391
+ if (shell && gobj_is_destroying(shell)) shell = null;
5392
+ if (shell === priv.lang_shell) return;
5393
+ if (priv.lang_shell && !gobj_is_destroying(priv.lang_shell)) gobj_unsubscribe_event(priv.lang_shell, "EV_LANGUAGE_CHANGED", {}, gobj);
5394
+ priv.lang_shell = null;
5395
+ if (shell) {
5396
+ gobj_subscribe_event(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
5397
+ priv.lang_shell = shell;
5398
+ }
5399
+ }
5400
+ /************************************************************
5401
+ * The toggle's title and aria-label, in the current language:
5402
+ * the same join as C_YUI_WINDOW's composed_title(), DATA half
5403
+ * first. The title the window registered with when it gave no
5404
+ * halves (a legacy caller).
5405
+ ************************************************************/
5406
+ function chip_title(entry) {
5407
+ let kind = entry.title_key ? t(entry.title_key) : "";
5408
+ if (entry.title_prefix && kind) return `${entry.title_prefix} · ${kind}`;
5409
+ return entry.title_prefix || kind || entry.title;
5410
+ }
5411
+ /************************************************************
5094
5412
  * Inject the dock stylesheet once (theme-aware).
5095
5413
  ************************************************************/
5096
5414
  function ensure_dock_style() {
@@ -5129,10 +5447,18 @@ function ensure_dock_style() {
5129
5447
  }
5130
5448
  .yui-dock-chip {
5131
5449
  display: inline-flex; align-items: center; gap: 7px; font: inherit; font-size: 12px;
5132
- padding: 4px 5px 4px 11px; border: 1px solid var(--bulma-border); border-radius: 7px;
5133
- background: var(--bulma-scheme-main); color: var(--bulma-text); cursor: pointer; white-space: nowrap;
5450
+ padding: 0 5px 0 0; border: 1px solid var(--bulma-border); border-radius: 7px;
5451
+ background: var(--bulma-scheme-main); color: var(--bulma-text); white-space: nowrap;
5134
5452
  }
5135
5453
  .yui-dock-chip:hover { border-color: var(--bulma-text-weak); }
5454
+ /* The chip is the look; its toggle a button that adds none, and
5455
+ * takes the pill's padding, so the whole label is what a press lands
5456
+ * on and the chip reads as one pill with its ✕ at the end. */
5457
+ .yui-dock-chip-toggle {
5458
+ display: inline-flex; align-items: center; gap: 7px;
5459
+ padding: 4px 0 4px 11px; margin: 0; border: 0; border-radius: 7px 0 0 7px;
5460
+ background: transparent; font: inherit; color: inherit; cursor: pointer; white-space: nowrap;
5461
+ }
5136
5462
  /* The BORDER says active, not the text: blue ink on a blue tint is
5137
5463
  * the same hue on itself -- 4.49:1 on the light chip, under the 4.5
5138
5464
  * its 12px label needs. The label takes the text colour (11:1). */
@@ -5222,10 +5548,33 @@ function apply_placement(gobj) {
5222
5548
  "bottom-left",
5223
5549
  "bottom-right"
5224
5550
  ]) $dock.classList.toggle(`yui-dock--${c}`, !inline && c === corner);
5225
- let target = inline ? $inline : gobj_read_attr(gobj, "$parent") || document.body;
5551
+ let target = inline ? $inline : gobj_read_attr(gobj, "$parent") || dock_place(gobj);
5226
5552
  if ($dock.parentNode !== target) target.appendChild($dock);
5227
5553
  }
5228
5554
  /************************************************************
5555
+ * Where the FLOATING dock lives when the app names no
5556
+ * `$parent`: the popup layer of the shell, at the top of it.
5557
+ *
5558
+ * It was document.body at z-index 1000000, and that put it
5559
+ * above the WHOLE shell -- `.yui-shell` is a stacking context
5560
+ * of its own, so its dialogs (modal layer, 99) and toasts (120)
5561
+ * counted only inside it: the dock stayed clickable over a
5562
+ * dialog's backdrop (a window could be closed from its chip
5563
+ * while its own "all changes will be lost" question was open),
5564
+ * and it covered a toast where they met.
5565
+ *
5566
+ * The popup layer, not the window layer: the dock has to stay
5567
+ * above EVERY window, and windows live in two layers -- the
5568
+ * window layer (no `$parent`) and the popup layer, where the
5569
+ * site map and the JSON windows of the treedb views put theirs.
5570
+ * Above both; below the dialogs, the toasts and the loading
5571
+ * cover. No shell (before the first, between a logout and the
5572
+ * next login): the body.
5573
+ ************************************************************/
5574
+ function dock_place(gobj) {
5575
+ return yui_shell_popup_layer(yui_shell_of(gobj)) || document.body;
5576
+ }
5577
+ /************************************************************
5229
5578
  * Destroy the dock element.
5230
5579
  ************************************************************/
5231
5580
  function destroy_dock(gobj) {
@@ -5302,7 +5651,6 @@ function set_active(gobj, entry) {
5302
5651
  * Bring a window to front and mark it active/visible.
5303
5652
  ************************************************************/
5304
5653
  function focus_entry(gobj, entry) {
5305
- let priv = gobj.priv;
5306
5654
  let $win = gobj_read_attr(entry.gobj, "$container");
5307
5655
  if ($win) {
5308
5656
  if (entry.minimized) {
@@ -5310,8 +5658,7 @@ function focus_entry(gobj, entry) {
5310
5658
  entry.minimized = false;
5311
5659
  if (entry.$chip) entry.$chip.classList.remove("is-min");
5312
5660
  }
5313
- priv.z += 1;
5314
- $win.style.zIndex = priv.z;
5661
+ yui_shell_raise_window($win);
5315
5662
  }
5316
5663
  set_active(gobj, entry);
5317
5664
  }
@@ -5378,57 +5725,60 @@ function ac_register_window(gobj, event, kw, src) {
5378
5725
  { class: "yui-dock-label-kind" },
5379
5726
  text_node(title)
5380
5727
  ]);
5728
+ let chip_seq = ++__chip_seq__;
5729
+ let label_ids = label_items.map(function(item, i) {
5730
+ item[1].id = `yui-dock-chip-${chip_seq}-${i}`;
5731
+ return item[1].id;
5732
+ });
5381
5733
  let entry = {
5382
5734
  gobj: win,
5383
5735
  $chip: null,
5384
- minimized: false
5736
+ minimized: false,
5737
+ title,
5738
+ title_key,
5739
+ title_prefix: prefix
5385
5740
  };
5741
+ let window_name = gobj_full_name(win);
5386
5742
  let $chip = createElement2([
5387
5743
  "div",
5388
- Object.assign({
5389
- class: "yui-dock-chip",
5390
- role: "button",
5391
- tabindex: "0",
5392
- title
5393
- }, !prefix && title_key ? { "data-i18n-title": title_key } : {}),
5394
- [
5395
- chip_lead(kw.icon),
5396
- [
5744
+ { class: "DOCK_CHIP yui-dock-chip" },
5745
+ [[
5746
+ "button",
5747
+ Object.assign({
5748
+ class: "DOCK_CHIP_TOGGLE yui-dock-chip-toggle",
5749
+ type: "button",
5750
+ title,
5751
+ "aria-label": title,
5752
+ "aria-labelledby": label_ids.join(" ")
5753
+ }, !prefix && title_key ? {
5754
+ "data-i18n-title": title_key,
5755
+ "data-i18n-aria-label": title_key
5756
+ } : {}),
5757
+ [chip_lead(kw.icon), [
5397
5758
  "span",
5398
5759
  { class: "yui-dock-label" },
5399
5760
  label_items
5400
- ],
5401
- [
5402
- "button",
5403
- {
5404
- class: "yui-dock-close",
5405
- type: "button",
5406
- title: t("close"),
5407
- "data-i18n-title": "close",
5408
- "aria-label": t("close"),
5409
- "data-i18n-aria-label": "close"
5410
- },
5411
- html_markup(WC_X),
5412
- { click: (evt) => {
5413
- evt.stopPropagation();
5414
- gobj_send_event(entry.gobj, "EV_CLOSE_WINDOW", {}, gobj);
5415
- } }
5416
- ]
5417
- ],
5418
- {
5419
- click: (evt) => {
5761
+ ]],
5762
+ { click: (evt) => {
5420
5763
  evt.stopPropagation();
5421
- on_chip_click(gobj, entry);
5764
+ gobj_send_event(gobj, "EV_TOGGLE_WINDOW", { window: window_name }, gobj);
5765
+ } }
5766
+ ], [
5767
+ "button",
5768
+ {
5769
+ class: "DOCK_CHIP_CLOSE yui-dock-close",
5770
+ type: "button",
5771
+ title: t("close"),
5772
+ "data-i18n-title": "close",
5773
+ "aria-label": t("close"),
5774
+ "data-i18n-aria-label": "close"
5422
5775
  },
5423
- keydown: (evt) => {
5424
- if (evt.target !== evt.currentTarget) return;
5425
- if (evt.key === "Enter" || evt.key === " ") {
5426
- if (evt.key === " ") evt.preventDefault();
5427
- evt.stopPropagation();
5428
- on_chip_click(gobj, entry);
5429
- }
5430
- }
5431
- }
5776
+ html_markup(WC_X),
5777
+ { click: (evt) => {
5778
+ evt.stopPropagation();
5779
+ gobj_send_event(entry.gobj, "EV_CLOSE_WINDOW", {}, gobj);
5780
+ } }
5781
+ ]]
5432
5782
  ]);
5433
5783
  entry.$chip = $chip;
5434
5784
  priv.windows.push(entry);
@@ -5472,6 +5822,67 @@ function ac_focus_window(gobj, event, kw, src) {
5472
5822
  if (entry) focus_entry(gobj, entry);
5473
5823
  return 0;
5474
5824
  }
5825
+ /************************************************************
5826
+ * EV_TOGGLE_WINDOW { window } -- the toggle of a chip of THIS
5827
+ * dock was pressed: bring a minimized or covered window to the
5828
+ * front, send the one in front to the dock. Only the dock's own
5829
+ * chip sends it (src is the dock): the events a window sends
5830
+ * about itself come from the window, and this one from the chip
5831
+ * that names it.
5832
+ ************************************************************/
5833
+ function ac_toggle_window(gobj, event, kw, src) {
5834
+ let priv = gobj.priv;
5835
+ if (src !== gobj) {
5836
+ log_error(`${gobj_short_name(gobj)}: ${event} comes from the dock's own chip, not from ${is_gobj(src) ? gobj_full_name(src) : String(src)}`);
5837
+ return -1;
5838
+ }
5839
+ let name = kw && kw.window;
5840
+ let entry = priv.windows.find((e) => gobj_full_name(e.gobj) === name);
5841
+ if (!entry) {
5842
+ log_error(`${gobj_short_name(gobj)}: ${event}: no window '${name}' in the dock`);
5843
+ return -1;
5844
+ }
5845
+ on_chip_click(gobj, entry);
5846
+ return 0;
5847
+ }
5848
+ /************************************************************
5849
+ * EV_SHELL_CHANGED -- a shell was built, or is being destroyed
5850
+ * (yui_shell_watch_changes()). The dock's place -- the shell's
5851
+ * popup layer, or its zone when inline -- may have gone with
5852
+ * it, or be in the new one: place it again. Up to 7.26.34 an
5853
+ * inline dock stayed in the destroyed shell's zone after a
5854
+ * logout, detached, until a window registered. Its language
5855
+ * is the new shell's too.
5856
+ ************************************************************/
5857
+ function ac_shell_changed(gobj, event, kw, src) {
5858
+ apply_placement(gobj);
5859
+ follow_language(gobj);
5860
+ return 0;
5861
+ }
5862
+ /************************************************************
5863
+ * EV_LANGUAGE_CHANGED -- from the shell that holds the dock.
5864
+ * A chip's label re-translates by its key (refresh_language()
5865
+ * reaches its kind half); the toggle's title and aria-label
5866
+ * of a window with a DATA half are COMPOSED, so no key carries
5867
+ * them: written again here. Up to 7.26.35 they stayed in the
5868
+ * language the window was opened in (a reader took the
5869
+ * aria-labelledby, which changed; the tooltip did not).
5870
+ ************************************************************/
5871
+ function ac_language_changed$11(gobj, event, kw, src) {
5872
+ let priv = gobj.priv;
5873
+ for (let entry of priv.windows) {
5874
+ if (!entry.title_prefix || !entry.$chip) continue;
5875
+ let $toggle = entry.$chip.querySelector(".DOCK_CHIP_TOGGLE");
5876
+ if (!$toggle) {
5877
+ log_error(`${gobj_short_name(gobj)}: a chip without its toggle`);
5878
+ continue;
5879
+ }
5880
+ let title = chip_title(entry);
5881
+ $toggle.setAttribute("title", title);
5882
+ $toggle.setAttribute("aria-label", title);
5883
+ }
5884
+ return 0;
5885
+ }
5475
5886
  /***************************
5476
5887
  * FSM
5477
5888
  ***************************/
@@ -5491,7 +5902,10 @@ function create_gclass$20(gclass_name) {
5491
5902
  ["EV_REGISTER_WINDOW", 0],
5492
5903
  ["EV_UNREGISTER_WINDOW", 0],
5493
5904
  ["EV_MINIMIZE_WINDOW", 0],
5494
- ["EV_FOCUS_WINDOW", 0]
5905
+ ["EV_FOCUS_WINDOW", 0],
5906
+ ["EV_TOGGLE_WINDOW", 0],
5907
+ ["EV_SHELL_CHANGED", 0],
5908
+ ["EV_LANGUAGE_CHANGED", 0]
5495
5909
  ], [["ST_IDLE", [
5496
5910
  [
5497
5911
  "EV_REGISTER_WINDOW",
@@ -5512,6 +5926,21 @@ function create_gclass$20(gclass_name) {
5512
5926
  "EV_FOCUS_WINDOW",
5513
5927
  ac_focus_window,
5514
5928
  null
5929
+ ],
5930
+ [
5931
+ "EV_TOGGLE_WINDOW",
5932
+ ac_toggle_window,
5933
+ null
5934
+ ],
5935
+ [
5936
+ "EV_SHELL_CHANGED",
5937
+ ac_shell_changed,
5938
+ null
5939
+ ],
5940
+ [
5941
+ "EV_LANGUAGE_CHANGED",
5942
+ ac_language_changed$11,
5943
+ null
5515
5944
  ]
5516
5945
  ]]], gmt$20, 0, attrs_table$20, PRIVATE_DATA$20, 0, 0, 0, 0);
5517
5946
  if (!__gclass__$20) return -1;
@@ -6791,6 +7220,41 @@ function yui_tabulator_name_row_selects(table, t) {
6791
7220
  }
6792
7221
  } catch (e) {}
6793
7222
  }
7223
+ /***************************************************************
7224
+ * Name the EDITOR of an editable column: the box Tabulator draws
7225
+ * in a cell while it is edited (input, number, textarea, list,
7226
+ * tickCross, date...).
7227
+ *
7228
+ * Tabulator gives it nothing -- no label, no aria-label, no title
7229
+ * -- so a reader lands in an anonymous edit box. Its name is the
7230
+ * column's: a cell is edited under its column's title. Asked at
7231
+ * the MOMENT of editing: Tabulator takes `editorParams` as a
7232
+ * function of the cell, and every built-in editor writes the
7233
+ * `elementAttributes` it answers onto the element it draws. So
7234
+ * the name is in the language of that moment, with nothing to
7235
+ * re-apply after a language change or a rebuild of the columns
7236
+ * -- the twin of the header filters above, which have to be
7237
+ * renamed after every render.
7238
+ *
7239
+ * `name_of(cell)` answers the column's title, TRANSLATED (the
7240
+ * view's own label: a cascade, a schema header); "" names
7241
+ * nothing. What `editorParams` the column had is kept, its own
7242
+ * `elementAttributes` too; a name it set itself wins.
7243
+ ***************************************************************/
7244
+ function yui_tabulator_name_editor(column_def, name_of) {
7245
+ if (!column_def || !column_def.editor || typeof name_of !== "function") return;
7246
+ let base = column_def.editorParams;
7247
+ column_def.editorParams = function(cell) {
7248
+ let params = typeof base === "function" ? base(cell) : base || {};
7249
+ let name = name_of(cell);
7250
+ if (!name) return params;
7251
+ let attrs = Object.assign({
7252
+ "aria-label": name,
7253
+ title: name
7254
+ }, params.elementAttributes || {});
7255
+ return Object.assign({}, params, { elementAttributes: attrs });
7256
+ };
7257
+ }
6794
7258
  function yui_tabulator_relocalize(table, t) {
6795
7259
  if (!table) return;
6796
7260
  try {
@@ -7311,6 +7775,7 @@ function yui_file_control(gobj, { name, value, readonly, accept, on_pick }) {
7311
7775
  {
7312
7776
  type: "button",
7313
7777
  class: "FILE_PICK button is-small",
7778
+ title: t("choose a file"),
7314
7779
  "data-i18n-title": "choose a file",
7315
7780
  "aria-label": t("choose a file"),
7316
7781
  "data-i18n-aria-label": "choose a file"
@@ -7329,6 +7794,7 @@ function yui_file_control(gobj, { name, value, readonly, accept, on_pick }) {
7329
7794
  {
7330
7795
  type: "button",
7331
7796
  class: "FILE_CLEAR button is-small",
7797
+ title: t("remove the file"),
7332
7798
  "data-i18n-title": "remove the file",
7333
7799
  "aria-label": t("remove the file"),
7334
7800
  "data-i18n-aria-label": "remove the file"
@@ -8677,6 +9143,293 @@ function json_editor_value(content) {
8677
9143
  return JSON.stringify(content.json);
8678
9144
  }
8679
9145
  //#endregion
9146
+ //#region src/form_json_editor_names.js
9147
+ /***********************************************************************
9148
+ * form_json_editor_names.js
9149
+ *
9150
+ * The NAMES of the buttons vanilla-jsoneditor draws in its menu
9151
+ * (a json field of C_YUI_FORM): in the app's language, and with an
9152
+ * aria-label.
9153
+ *
9154
+ * The editor draws them itself, in English, with a `title` and
9155
+ * nothing else: "Format JSON: add proper indentation and new lines
9156
+ * (Ctrl+I)", "Undo (Ctrl+Z)", and three mode buttons whose text is
9157
+ * "text", "tree" and "table" (REV-CLOUD V23.5.6). Two hooks reach
9158
+ * them, and both are needed:
9159
+ *
9160
+ * - json_editor_menu_items(), the editor's `onRenderMenu`: the
9161
+ * items it is about to draw, with each title translated (its
9162
+ * shortcut kept) and the mode buttons' text too. Svelte draws
9163
+ * from these, so a button born in a re-render is born named.
9164
+ * - name_json_editor_menu(), after the render, from the form's
9165
+ * FSM: the aria-label of each button, with its key, so
9166
+ * refresh_language() re-translates it, and the state of the
9167
+ * mode buttons (aria-pressed). A menu item has no field for
9168
+ * either.
9169
+ *
9170
+ * A button is known by the CLASS the editor gives it (`jse-format`,
9171
+ * `jse-undo`...), never by its English title: a class is what the
9172
+ * editor's own stylesheet relies on. A button not listed here keeps
9173
+ * the editor's words.
9174
+ *
9175
+ * Its CONTEXT MENU (`onRenderContextMenu`,
9176
+ * json_editor_context_menu_items()) has no class on its items, so
9177
+ * an item is known by what it IS and where it IS: the icon the
9178
+ * editor draws it with (a FontAwesome name: `paste`, `scissors`),
9179
+ * the column of the menu it stands in, its place in a dropdown
9180
+ * (the main button, the first or the second of its list), the
9181
+ * mode of the editor (tree or table) -- never by its words. Its
9182
+ * text and title go back translated; after the render the same
9183
+ * pass names its buttons, and the arrow of each dropdown, which
9184
+ * the editor draws with no name at all.
9185
+ *
9186
+ * Not reached, because the editor gives them no hook: its search
9187
+ * box, its repair messages, the tip at the foot of the context
9188
+ * menu, and its modals (sort, transform). They stay in its
9189
+ * English.
9190
+ *
9191
+ * Copyright (c) 2026, ArtGins.
9192
+ * All Rights Reserved.
9193
+ ***********************************************************************/
9194
+ /***************************************************************
9195
+ * Constants
9196
+ ***************************************************************/
9197
+ var JSON_EDITOR_BUTTON_KEYS = {
9198
+ "jse-expand-all": "expand all",
9199
+ "jse-collapse-all": "collapse all",
9200
+ "jse-search": "search",
9201
+ "jse-copy": "copy",
9202
+ "jse-undo": "undo",
9203
+ "jse-redo": "redo",
9204
+ "jse-cancel": "cancel",
9205
+ "jse-format": "format json",
9206
+ "jse-compact": "compact json",
9207
+ "jse-sort": "sort",
9208
+ "jse-transform": "transform",
9209
+ "jse-contextmenu": "open context menu"
9210
+ };
9211
+ var JSON_EDITOR_MODE_NAME_KEYS = {
9212
+ text: "text view",
9213
+ tree: "tree view",
9214
+ table: "table view"
9215
+ };
9216
+ var JSON_EDITOR_MODE_TEXT_KEYS = {
9217
+ text: "text mode",
9218
+ tree: "tree mode",
9219
+ table: "table mode"
9220
+ };
9221
+ var MODE_CLASS = "yui-jse-mode-";
9222
+ var SHORTCUT = /\((?:Ctrl|Cmd|⌘)[^()]*\)\s*$/;
9223
+ var CONTEXT_BUTTON_KEYS = {
9224
+ "pen": "edit key",
9225
+ "paste": "paste",
9226
+ "clone": "duplicate",
9227
+ "crop-simple": "extract",
9228
+ "arrow-down-short-wide": "sort",
9229
+ "filter": "transform",
9230
+ "trash-can": "remove",
9231
+ "square-caret-up": "insert before",
9232
+ "square-caret-down": "insert after"
9233
+ };
9234
+ var CONTEXT_ROW_BUTTON_KEYS = {
9235
+ "pen": "edit row",
9236
+ "clone": "duplicate row",
9237
+ "trash-can": "remove row"
9238
+ };
9239
+ var CONTEXT_DROPDOWN_MAIN_KEYS = {
9240
+ "pen": "edit value",
9241
+ "scissors": "cut",
9242
+ "copy": "copy"
9243
+ };
9244
+ var CONTEXT_DROPDOWN_FIRST_KEYS = {
9245
+ "pen": "edit value",
9246
+ "scissors": "cut formatted",
9247
+ "copy": "copy formatted"
9248
+ };
9249
+ var CONTEXT_DROPDOWN_SECOND_KEYS = {
9250
+ "pen": "enforce string",
9251
+ "scissors": "cut compacted",
9252
+ "copy": "copy compacted"
9253
+ };
9254
+ var CONTEXT_TARGET_KEYS = {
9255
+ "0": "structure",
9256
+ "1": "object",
9257
+ "2": "array",
9258
+ "3": "value"
9259
+ };
9260
+ var TARGET_ICONS = ["arrow-right-arrow-left", "plus"];
9261
+ var CONTEXT_LABEL_KEYS = {
9262
+ convert: "convert to",
9263
+ insert: "insert",
9264
+ cell: "table cell",
9265
+ row: "table row"
9266
+ };
9267
+ /***************************
9268
+ * Local Methods
9269
+ ***************************/
9270
+ function has_key(table, name) {
9271
+ return Object.prototype.hasOwnProperty.call(table, name);
9272
+ }
9273
+ function button_class(classes) {
9274
+ return (Array.isArray(classes) ? classes : String(classes || "").split(/\s+/)).find((c) => has_key(JSON_EDITOR_BUTTON_KEYS, c)) || "";
9275
+ }
9276
+ function mode_of_item(item) {
9277
+ if (String(item.className || "").split(/\s+/).indexOf("jse-group-button") < 0) return "";
9278
+ return has_key(JSON_EDITOR_MODE_NAME_KEYS, item.text) ? item.text : "";
9279
+ }
9280
+ function icon_of(item) {
9281
+ return item && item.icon && typeof item.icon.iconName === "string" ? item.icon.iconName : "";
9282
+ }
9283
+ function named_item(item, name) {
9284
+ let m = SHORTCUT.exec(String(item.title || ""));
9285
+ return Object.assign({}, item, {
9286
+ text: name,
9287
+ title: m ? `${name} ${m[0].trim()}` : name
9288
+ });
9289
+ }
9290
+ function context_dropdown(item) {
9291
+ let icon = icon_of(item.main);
9292
+ if (!has_key(CONTEXT_DROPDOWN_MAIN_KEYS, icon)) return item;
9293
+ let list = Array.isArray(item.items) ? item.items : [];
9294
+ return Object.assign({}, item, {
9295
+ main: named_item(item.main, t(CONTEXT_DROPDOWN_MAIN_KEYS[icon])),
9296
+ items: list.map(function(it, i) {
9297
+ if (!it || it.type !== "button") return it;
9298
+ if (i === 0) return named_item(it, t(CONTEXT_DROPDOWN_FIRST_KEYS[icon]));
9299
+ if (i === 1) return named_item(it, t(CONTEXT_DROPDOWN_SECOND_KEYS[icon]));
9300
+ return it;
9301
+ })
9302
+ });
9303
+ }
9304
+ function context_button(item, row_column) {
9305
+ let icon = icon_of(item);
9306
+ if (row_column && has_key(CONTEXT_ROW_BUTTON_KEYS, icon)) return named_item(item, t(CONTEXT_ROW_BUTTON_KEYS[icon]));
9307
+ if (has_key(CONTEXT_BUTTON_KEYS, icon)) return named_item(item, t(CONTEXT_BUTTON_KEYS[icon]));
9308
+ return item;
9309
+ }
9310
+ function context_column(item, table, index) {
9311
+ let list = Array.isArray(item.items) ? item.items : [];
9312
+ let buttons = list.filter((it) => it && it.type === "button");
9313
+ let targets = !table && buttons.length > 0 && buttons.every((it) => TARGET_ICONS.indexOf(icon_of(it)) >= 0);
9314
+ let row_column = table && index === 1;
9315
+ let label_kind = "";
9316
+ if (table) label_kind = index === 0 ? "cell" : index === 1 ? "row" : "";
9317
+ else if (targets) label_kind = icon_of(buttons[0]) === "plus" ? "insert" : "convert";
9318
+ let target = 0;
9319
+ let insert_row = 0;
9320
+ return Object.assign({}, item, { items: list.map(function(it) {
9321
+ if (!it || typeof it !== "object") return it;
9322
+ if (it.type === "label") return label_kind ? Object.assign({}, it, { text: t(CONTEXT_LABEL_KEYS[label_kind]) }) : it;
9323
+ if (it.type === "dropdown-button") return context_dropdown(it);
9324
+ if (it.type !== "button") return it;
9325
+ if (targets) return named_item(it, t(CONTEXT_TARGET_KEYS[String(target++)]));
9326
+ if (row_column && icon_of(it) === "plus") return named_item(it, t(insert_row++ === 0 ? CONTEXT_BUTTON_KEYS["square-caret-up"] : CONTEXT_BUTTON_KEYS["square-caret-down"]));
9327
+ return context_button(it, row_column);
9328
+ }) });
9329
+ }
9330
+ function context_item(item, table) {
9331
+ if (!item || typeof item !== "object") return item;
9332
+ if (item.type === "row") {
9333
+ let column = 0;
9334
+ let list = Array.isArray(item.items) ? item.items : [];
9335
+ return Object.assign({}, item, { items: list.map(function(it) {
9336
+ if (it && it.type === "column") return context_column(it, table, column++);
9337
+ return context_item(it, table);
9338
+ }) });
9339
+ }
9340
+ if (item.type === "dropdown-button") return context_dropdown(item);
9341
+ if (item.type === "button") return context_button(item, false);
9342
+ return item;
9343
+ }
9344
+ function mode_of_button($b) {
9345
+ for (let c of $b.classList) if (c.indexOf(MODE_CLASS) === 0) {
9346
+ let mode = c.slice(13);
9347
+ if (has_key(JSON_EDITOR_MODE_NAME_KEYS, mode)) return mode;
9348
+ }
9349
+ return "";
9350
+ }
9351
+ /***************************
9352
+ * Public
9353
+ ***************************/
9354
+ /************************************************************
9355
+ * The editor's `onRenderMenu`: the items it is about to draw,
9356
+ * translated. A title keeps the editor's shortcut after the
9357
+ * translated name ("Deshacer (Ctrl+Z)"); a mode button gets its
9358
+ * mode's name as title and its short text translated, and a class
9359
+ * that says its mode. Anything else is returned as it came.
9360
+ *
9361
+ * Pure: what it answers is data for the editor to draw.
9362
+ ************************************************************/
9363
+ function json_editor_menu_items(items) {
9364
+ if (!Array.isArray(items)) return items;
9365
+ return items.map(function(item) {
9366
+ if (!item || item.type !== "button") return item;
9367
+ let mode = mode_of_item(item);
9368
+ if (mode) return Object.assign({}, item, {
9369
+ className: `${item.className} ${MODE_CLASS}${mode}`,
9370
+ title: t(JSON_EDITOR_MODE_NAME_KEYS[mode]),
9371
+ text: t(JSON_EDITOR_MODE_TEXT_KEYS[mode])
9372
+ });
9373
+ let cls = button_class(item.className);
9374
+ if (!cls) return item;
9375
+ let m = SHORTCUT.exec(String(item.title || ""));
9376
+ let name = t(JSON_EDITOR_BUTTON_KEYS[cls]);
9377
+ return Object.assign({}, item, { title: m ? `${name} ${m[0].trim()}` : name });
9378
+ });
9379
+ }
9380
+ /************************************************************
9381
+ * The editor's `onRenderContextMenu`: the items it is about to
9382
+ * draw, translated -- each known by its icon and its place, never
9383
+ * by its words (context_item()). Anything the module does not
9384
+ * know comes back as it came: a newer editor that moves an item
9385
+ * leaves it in English, it does not mislabel it.
9386
+ *
9387
+ * Pure: what it answers is data for the editor to draw.
9388
+ ************************************************************/
9389
+ function json_editor_context_menu_items(items, context) {
9390
+ if (!Array.isArray(items)) return items;
9391
+ let table = !!(context && context.mode === "table");
9392
+ return items.map((item) => context_item(item, table));
9393
+ }
9394
+ /************************************************************
9395
+ * Name the buttons of a rendered editor's menu: an aria-label
9396
+ * with its key (data-i18n-aria-label, so a language change
9397
+ * reaches it), and aria-pressed on the mode buttons -- the
9398
+ * selected one is the mode on screen (`jse-selected`). Run
9399
+ * after every render of the menu: a re-render may draw new
9400
+ * buttons, which carry none of it.
9401
+ ************************************************************/
9402
+ function name_json_editor_menu($editor) {
9403
+ if (!$editor || typeof $editor.querySelectorAll !== "function") return 0;
9404
+ let named = 0;
9405
+ for (let $b of $editor.querySelectorAll(".jse-menu button")) {
9406
+ let mode = mode_of_button($b);
9407
+ const key = mode ? JSON_EDITOR_MODE_NAME_KEYS[mode] : JSON_EDITOR_BUTTON_KEYS[button_class([...$b.classList])];
9408
+ if (!key) continue;
9409
+ $b.setAttribute("aria-label", t(key));
9410
+ $b.setAttribute("data-i18n-aria-label", key);
9411
+ if (mode) $b.setAttribute("aria-pressed", $b.classList.contains("jse-selected") ? "true" : "false");
9412
+ named++;
9413
+ }
9414
+ const MORE = "more options";
9415
+ for (let $b of $editor.querySelectorAll("div.jse-contextmenu button")) {
9416
+ if ($b.getAttribute("data-type") === "jse-open-dropdown") {
9417
+ $b.setAttribute("title", t(MORE));
9418
+ $b.setAttribute("data-i18n-title", MORE);
9419
+ $b.setAttribute("aria-label", t(MORE));
9420
+ $b.setAttribute("data-i18n-aria-label", MORE);
9421
+ named++;
9422
+ continue;
9423
+ }
9424
+ let text = ($b.textContent || "").trim();
9425
+ if (text) {
9426
+ $b.setAttribute("aria-label", text);
9427
+ named++;
9428
+ }
9429
+ }
9430
+ return named;
9431
+ }
9432
+ //#endregion
8680
9433
  //#region src/c_yui_form.js
8681
9434
  /***********************************************************************
8682
9435
  * c_yui_form.js
@@ -8726,7 +9479,9 @@ var attrs_table$17 = [
8726
9479
  var PRIVATE_DATA$17 = {
8727
9480
  rearm_timer: null,
8728
9481
  tables: null,
8729
- table_seq: 0
9482
+ table_seq: 0,
9483
+ json_names_posted: false,
9484
+ lang_shell: null
8730
9485
  };
8731
9486
  var __gclass__$17 = null;
8732
9487
  /***************************
@@ -8757,13 +9512,23 @@ function mt_destroy$17(gobj) {
8757
9512
  * Framework Method start
8758
9513
  ************************************************************/
8759
9514
  function mt_start$17(gobj) {
9515
+ let priv = gobj.priv;
9516
+ let shell = yui_shell_of(gobj);
9517
+ if (shell) {
9518
+ gobj_subscribe_event(shell, "EV_LANGUAGE_CHANGED", {}, gobj);
9519
+ priv.lang_shell = shell;
9520
+ }
8760
9521
  let record = gobj_read_attr(gobj, "record");
8761
9522
  if (record) gobj_send_event(gobj, "EV_LOAD_RECORD", record, gobj);
8762
9523
  }
8763
9524
  /************************************************************
8764
9525
  * Framework Method stop
8765
9526
  ************************************************************/
8766
- function mt_stop$17(gobj) {}
9527
+ function mt_stop$17(gobj) {
9528
+ let priv = gobj.priv;
9529
+ if (priv.lang_shell && !gobj_is_destroying(priv.lang_shell)) gobj_unsubscribe_event(priv.lang_shell, "EV_LANGUAGE_CHANGED", {}, gobj);
9530
+ priv.lang_shell = null;
9531
+ }
8767
9532
  /***************************
8768
9533
  * Local Methods
8769
9534
  ***************************/
@@ -9219,6 +9984,80 @@ function name_form_control$1($field, conf) {
9219
9984
  $clear.setAttribute("data-i18n-aria-label", CLEAR_KEY);
9220
9985
  }
9221
9986
  }
9987
+ /************************************************************
9988
+ * The ✕ of ONE selected option of a Tom Select: its
9989
+ * remove_button plugin calls this for every option it draws.
9990
+ *
9991
+ * The plugin's own was a div titled "Remove" in English -- never
9992
+ * translated, no aria-label -- with tabindex -1, and the form
9993
+ * turns Backspace deletion off (no_backspace_delete): a keyboard
9994
+ * could not take one option out at all. Named here through t()
9995
+ * with its key (so a language change reaches it), and pressed by
9996
+ * Enter or Space as the clear button is: the key clicks it, which
9997
+ * runs the plugin's own handler.
9998
+ ************************************************************/
9999
+ function remove_option_button() {
10000
+ const KEY = "remove this option";
10001
+ let $b = createElement2([
10002
+ "div",
10003
+ {
10004
+ class: "remove",
10005
+ role: "button",
10006
+ tabindex: "0",
10007
+ title: t(KEY),
10008
+ "data-i18n-title": KEY,
10009
+ "aria-label": t(KEY),
10010
+ "data-i18n-aria-label": KEY
10011
+ },
10012
+ "×"
10013
+ ]);
10014
+ $b.addEventListener("keydown", function(evt) {
10015
+ if (evt.key === "Enter" || evt.key === " ") {
10016
+ evt.preventDefault();
10017
+ evt.stopPropagation();
10018
+ $b.click();
10019
+ }
10020
+ });
10021
+ return $b;
10022
+ }
10023
+ /************************************************************
10024
+ * The `onRenderMenu` of a json editor of this form.
10025
+ *
10026
+ * The editor calls it with the items it is about to draw: they go
10027
+ * back translated (json_editor_menu_items(), data for the editor
10028
+ * to draw). What no item can carry -- an aria-label, the state of
10029
+ * the mode buttons -- is written on the drawn buttons, after the
10030
+ * render, by the form's FSM (EV_NAME_JSON_EDITORS): the callback
10031
+ * only posts the event, once until it is handled. Posted, not
10032
+ * sent: when it runs the editor has not drawn the menu yet.
10033
+ *
10034
+ * A new function each call: handing an editor a new one is what
10035
+ * makes it draw its menu again (ac_language_changed).
10036
+ ************************************************************/
10037
+ function json_editor_render_menu(gobj) {
10038
+ return function(items) {
10039
+ ask_json_editor_names(gobj);
10040
+ return json_editor_menu_items(items);
10041
+ };
10042
+ }
10043
+ /************************************************************
10044
+ * The `onRenderContextMenu` of a json editor of this form: the
10045
+ * same, for the menu it opens on a right click or Ctrl+Q. It is
10046
+ * built each time it opens, in the language of that moment.
10047
+ ************************************************************/
10048
+ function json_editor_render_context_menu(gobj) {
10049
+ return function(items, context) {
10050
+ ask_json_editor_names(gobj);
10051
+ return json_editor_context_menu_items(items, context);
10052
+ };
10053
+ }
10054
+ function ask_json_editor_names(gobj) {
10055
+ let priv = gobj.priv;
10056
+ if (!priv.json_names_posted && !gobj_is_destroying(gobj)) {
10057
+ priv.json_names_posted = true;
10058
+ gobj_post_event(gobj, "EV_NAME_JSON_EDITORS", {}, gobj);
10059
+ }
10060
+ }
9222
10061
  function build_form(gobj) {
9223
10062
  let form_id = gobj_read_str_attr(gobj, "form_id");
9224
10063
  let $form = createElement2(["form", {
@@ -9250,7 +10089,7 @@ function build_html_form(gobj, $form, prefix, template) {
9250
10089
  let label_keys = topic_name ? [topic_name + "." + field_desc.name, field_desc.name] : [field_desc.name];
9251
10090
  const NO_I18N = "\0";
9252
10091
  html_field_conf.label = t(label_keys, { defaultValue: field_desc.header });
9253
- html_field_conf.label_i18n = t(field_desc.name, { defaultValue: NO_I18N }) !== NO_I18N ? field_desc.name : null;
10092
+ html_field_conf.label_i18n = label_keys.find((k) => t(k, { defaultValue: NO_I18N }) !== NO_I18N) || null;
9254
10093
  html_field_conf.placeholder = field_desc.placeholder ? t(field_desc.placeholder) : "";
9255
10094
  let placeholders = gobj_read_attr(gobj, "placeholders");
9256
10095
  if (placeholders[key]) html_field_conf.placeholder = placeholders[key];
@@ -9956,7 +10795,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
9956
10795
  "clear_button": { "title": t("remove all selected options") },
9957
10796
  "no_active_items": {},
9958
10797
  "no_backspace_delete": {},
9959
- "remove_button": {}
10798
+ "remove_button": { html: remove_option_button }
9960
10799
  }
9961
10800
  });
9962
10801
  $extend.tom_select.on("change", function() {
@@ -10025,7 +10864,9 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
10025
10864
  },
10026
10865
  timestampTag: function({ field, value, path }) {
10027
10866
  return is_time_field(field);
10028
- }
10867
+ },
10868
+ onRenderMenu: json_editor_render_menu(gobj),
10869
+ onRenderContextMenu: json_editor_render_context_menu(gobj)
10029
10870
  }
10030
10871
  });
10031
10872
  break;
@@ -10557,6 +11398,9 @@ function template2columns(gobj, columns, template, sub_elements) {
10557
11398
  }
10558
11399
  if (column) {
10559
11400
  if (readonly) delete column.editor;
11401
+ yui_tabulator_name_editor(column, function() {
11402
+ return t(field_desc.header || field_desc.name);
11403
+ });
10560
11404
  columns.push(column);
10561
11405
  }
10562
11406
  });
@@ -11317,6 +12161,39 @@ function ac_hide$11(gobj, event, kw, src) {
11317
12161
  function ac_timeout(gobj, event, kw, src) {
11318
12162
  return 0;
11319
12163
  }
12164
+ /************************************************************
12165
+ * EV_NAME_JSON_EDITORS {} -- a menu of a json editor of this
12166
+ * form was drawn, its menu or its context menu (posted by
12167
+ * ask_json_editor_names): name its buttons. Every editor of the
12168
+ * form: naming is idempotent, and a kw is plain json, which an
12169
+ * editor is not.
12170
+ ************************************************************/
12171
+ function ac_name_json_editors(gobj, event, kw, src) {
12172
+ let priv = gobj.priv;
12173
+ priv.json_names_posted = false;
12174
+ let $container = gobj_read_attr(gobj, "$container");
12175
+ if (!$container) {
12176
+ log_error(`${gobj_short_name(gobj)}: ${event} without a container`);
12177
+ return -1;
12178
+ }
12179
+ for (let $editor of $container.querySelectorAll(".jsoneditor")) name_json_editor_menu($editor);
12180
+ return 0;
12181
+ }
12182
+ /************************************************************
12183
+ * EV_LANGUAGE_CHANGED {} -- from the shell. A json editor draws
12184
+ * its menu once: its titles and the text of its mode buttons stay
12185
+ * in the old language until it draws it again, so each is told to
12186
+ * (a new onRenderMenu), and the render names it again.
12187
+ ************************************************************/
12188
+ function ac_language_changed$10(gobj, event, kw, src) {
12189
+ let $container = gobj_read_attr(gobj, "$container");
12190
+ if (!$container) return 0;
12191
+ for (let $editor of $container.querySelectorAll(".jsoneditor")) {
12192
+ let editor = $editor.jsoneditor;
12193
+ if (editor && typeof editor.updateProps === "function") editor.updateProps({ onRenderMenu: json_editor_render_menu(gobj) });
12194
+ }
12195
+ return 0;
12196
+ }
11320
12197
  /***************************
11321
12198
  * FSM
11322
12199
  ***************************/
@@ -11466,6 +12343,16 @@ function create_gclass$17(gclass_name) {
11466
12343
  "EV_TIMEOUT",
11467
12344
  ac_timeout,
11468
12345
  null
12346
+ ],
12347
+ [
12348
+ "EV_NAME_JSON_EDITORS",
12349
+ ac_name_json_editors,
12350
+ null
12351
+ ],
12352
+ [
12353
+ "EV_LANGUAGE_CHANGED",
12354
+ ac_language_changed$10,
12355
+ null
11469
12356
  ]
11470
12357
  ]]];
11471
12358
  const event_types = [
@@ -11484,7 +12371,9 @@ function create_gclass$17(gclass_name) {
11484
12371
  ["EV_REFRESH", 0],
11485
12372
  ["EV_SHOW", 0],
11486
12373
  ["EV_HIDE", 0],
11487
- ["EV_TIMEOUT", 0]
12374
+ ["EV_TIMEOUT", 0],
12375
+ ["EV_NAME_JSON_EDITORS", 0],
12376
+ ["EV_LANGUAGE_CHANGED", 0]
11488
12377
  ];
11489
12378
  __gclass__$17 = gclass_create(gclass_name, event_types, states, gmt$17, 0, attrs_table$17, PRIVATE_DATA$17, 0, 0, 0, 0);
11490
12379
  if (!__gclass__$17) return -1;
@@ -11504,7 +12393,7 @@ function register_c_yui_form() {
11504
12393
  *
11505
12394
  * Utilities for maplibre
11506
12395
  *
11507
- * Copyright (c) 2025, ArtGins.
12396
+ * Copyright (c) 2025-2026, ArtGins.
11508
12397
  * All Rights Reserved.
11509
12398
  ***********************************************************************/
11510
12399
  var EditControl = class {
@@ -11700,9 +12589,14 @@ function label_ctrl($root, selector, text) {
11700
12589
  $el.setAttribute("aria-label", text);
11701
12590
  }
11702
12591
  }
12592
+ function relabel_written($root, selector, was, now) {
12593
+ if (!$root || !now) return;
12594
+ for (const $el of $root.querySelectorAll(selector)) if ($el.getAttribute("aria-label") === was) $el.setAttribute("aria-label", now);
12595
+ }
11703
12596
  function yui_maplibre_relocalize(map, t) {
11704
12597
  if (!map || typeof map.getContainer !== "function") return;
11705
12598
  const strings = yui_maplibre_locale(t);
12599
+ const was = Object.assign({}, map._locale);
11706
12600
  try {
11707
12601
  map._locale = Object.assign({}, map._locale, strings);
11708
12602
  } catch (e) {}
@@ -11714,6 +12608,8 @@ function yui_maplibre_relocalize(map, t) {
11714
12608
  label_ctrl($c, ".maplibregl-ctrl-attrib-button", strings["AttributionControl.ToggleAttribution"]);
11715
12609
  label_ctrl($c, ".maplibregl-ctrl-fullscreen", strings["FullscreenControl.Enter"]);
11716
12610
  label_ctrl($c, ".maplibregl-popup-close-button", strings["Popup.Close"]);
12611
+ relabel_written($c, ".maplibregl-canvas", was["Map.Title"], strings["Map.Title"]);
12612
+ relabel_written($c, ".maplibregl-marker", was["Marker.Title"], strings["Marker.Title"]);
11717
12613
  const mac = typeof navigator !== "undefined" && (navigator.userAgent || "").includes("Mac");
11718
12614
  const $desktop = $c.querySelector(".maplibregl-desktop-message");
11719
12615
  if ($desktop) $desktop.textContent = mac ? strings["CooperativeGesturesHandler.MacHelpText"] : strings["CooperativeGesturesHandler.WindowsHelpText"];
@@ -14462,6 +15358,8 @@ function make_toolbar$5(gobj) {
14462
15358
  type: "text",
14463
15359
  placeholder: t("search"),
14464
15360
  "data-i18n-placeholder": "search",
15361
+ title: t("search"),
15362
+ "data-i18n-title": "search",
14465
15363
  "aria-label": t("search"),
14466
15364
  "data-i18n-aria-label": "search"
14467
15365
  },
@@ -16002,6 +16900,8 @@ function make_toolbar$4(gobj) {
16002
16900
  type: "text",
16003
16901
  placeholder: t("search"),
16004
16902
  "data-i18n-placeholder": "search",
16903
+ title: t("search"),
16904
+ "data-i18n-title": "search",
16005
16905
  "aria-label": t("search"),
16006
16906
  "data-i18n-aria-label": "search"
16007
16907
  },
@@ -19275,6 +20175,8 @@ function make_toolbar$2(gobj) {
19275
20175
  type: "text",
19276
20176
  placeholder: t("search"),
19277
20177
  "data-i18n-placeholder": "search",
20178
+ title: t("search"),
20179
+ "data-i18n-title": "search",
19278
20180
  "aria-label": t("search"),
19279
20181
  "data-i18n-aria-label": "search"
19280
20182
  },
@@ -21095,7 +21997,7 @@ function open_gclass_view(host, gclass_name, opts) {
21095
21997
  gobj_start(jv);
21096
21998
  return handle;
21097
21999
  }
21098
- let $win_parent = window_layer_of(host, shell);
22000
+ let $win_parent = window_layer_of(host);
21099
22001
  handle.win = gobj_create_service(`gclass-win-${clean_name(gobj_name(host))}`, "C_YUI_WINDOW", {
21100
22002
  $parent: $win_parent,
21101
22003
  subscriber: null,
@@ -21163,14 +22065,19 @@ function close_gclass_view(handle) {
21163
22065
  * window on `body` and mounted in the shell's popup layer
21164
22066
  * lands BEHIND its opener whatever number it is given, and
21165
22067
  * the button reads as one that did nothing.
22068
+ *
22069
+ * Opened from no window: null -- the window layer of the
22070
+ * shell, where every window lives (the legacy '#top-layer'
22071
+ * when a page has one). It was the shell's popup layer: above
22072
+ * the drawer, in the way of the toolbar's dropdowns.
21166
22073
  ************************************************************/
21167
- function window_layer_of(host, shell) {
22074
+ function window_layer_of(host) {
21168
22075
  let $c = gobj_read_pointer_attr(host, "$container");
21169
22076
  if ($c && typeof $c.closest === "function") {
21170
22077
  let $win = $c.closest(".C_YUI_WINDOW");
21171
22078
  if ($win && $win.parentNode) return $win.parentNode;
21172
22079
  }
21173
- return typeof document !== "undefined" && document.getElementById("top-layer") || shell && yui_shell_popup_layer(shell) || null;
22080
+ return typeof document !== "undefined" && document.getElementById("top-layer") || null;
21174
22081
  }
21175
22082
  /************************************************************
21176
22083
  * A window is not a sheet: below this width there is no room
@@ -21839,6 +22746,8 @@ function make_find_items(gobj) {
21839
22746
  type: "text",
21840
22747
  placeholder: t("search"),
21841
22748
  "data-i18n-placeholder": "search",
22749
+ title: t("search"),
22750
+ "data-i18n-title": "search",
21842
22751
  "aria-label": t("search"),
21843
22752
  "data-i18n-aria-label": "search"
21844
22753
  },
@@ -23624,7 +24533,7 @@ function card_descriptor(it, show_label, badge_of) {
23624
24533
  "aria-label": label || it.id
23625
24534
  };
23626
24535
  if (label) a_attrs["data-i18n-aria-label"] = label;
23627
- let tip = it.tooltip || it.aria_label;
24536
+ let tip = it.tooltip || it.aria_label || label;
23628
24537
  if (tip) {
23629
24538
  a_attrs.title = tip;
23630
24539
  a_attrs["data-i18n-title"] = tip;
@@ -23633,6 +24542,7 @@ function card_descriptor(it, show_label, badge_of) {
23633
24542
  a_attrs["aria-disabled"] = "true";
23634
24543
  a_attrs["tabindex"] = "-1";
23635
24544
  }
24545
+ if (badge) name_control_with_badge(a_attrs, badge);
23636
24546
  return [
23637
24547
  "a",
23638
24548
  a_attrs,
@@ -24035,6 +24945,7 @@ function render_tabs(gobj, items) {
24035
24945
  a_attrs.title = tip;
24036
24946
  a_attrs["data-i18n-title"] = tip;
24037
24947
  }
24948
+ name_control_with_badge(a_attrs, badge_el);
24038
24949
  let li_class = empty_string(it.class) ? "" : String(it.class);
24039
24950
  lis.push([
24040
24951
  "li",
@@ -24114,11 +25025,14 @@ function render_breadcrumb(gobj, items) {
24114
25025
  href: it.route ? "#" + it.route : "#",
24115
25026
  "data-item-id": it.id,
24116
25027
  "data-route": it.route || "",
24117
- "data-disabled": it.disabled ? "1" : "0"
25028
+ "data-disabled": it.disabled ? "1" : "0",
25029
+ "aria-label": it.name || it.id
24118
25030
  };
24119
- if (it.name) {
24120
- a_attrs["aria-label"] = it.name;
24121
- a_attrs["data-i18n-aria-label"] = it.name;
25031
+ if (it.name) a_attrs["data-i18n-aria-label"] = it.name;
25032
+ let tip = it.tooltip || it.aria_label || it.name;
25033
+ if (tip) {
25034
+ a_attrs.title = tip;
25035
+ a_attrs["data-i18n-title"] = tip;
24122
25036
  }
24123
25037
  lis.push([
24124
25038
  "li",
@@ -24251,17 +25165,19 @@ function render_accordion(gobj, items) {
24251
25165
  "aria-expanded": "false",
24252
25166
  "aria-controls": body_id
24253
25167
  };
25168
+ let head_badge = item_badge_node(gobj, it, false);
24254
25169
  if (!empty_string(head_text)) {
24255
25170
  head_attrs.i18n = head_text;
24256
25171
  head_attrs.title = head_text;
24257
25172
  head_attrs["data-i18n-title"] = head_text;
24258
25173
  head_attrs["aria-label"] = head_text;
24259
25174
  head_attrs["data-i18n-aria-label"] = head_text;
25175
+ name_control_with_badge(head_attrs, head_badge);
24260
25176
  }
24261
25177
  let $hdr = createElement2([
24262
25178
  "button",
24263
25179
  head_attrs,
24264
- [text_node(head_text), item_badge_node(gobj, it, false)]
25180
+ [text_node(head_text), head_badge]
24265
25181
  ]);
24266
25182
  $container.appendChild($hdr);
24267
25183
  let $ul = createElement2(["ul", {
@@ -24350,6 +25266,7 @@ function item_li(gobj, it, opts) {
24350
25266
  a_attrs["aria-disabled"] = "true";
24351
25267
  a_attrs["tabindex"] = "-1";
24352
25268
  }
25269
+ name_control_with_badge(a_attrs, badge_el);
24353
25270
  return [
24354
25271
  "li",
24355
25272
  {},
@@ -24396,6 +25313,7 @@ function item_iconbar(gobj, it, opts) {
24396
25313
  a_attrs.title = tip;
24397
25314
  a_attrs["data-i18n-title"] = tip;
24398
25315
  }
25316
+ name_control_with_badge(a_attrs, badge_el);
24399
25317
  return [
24400
25318
  "div",
24401
25319
  { class: "level-item" },
@@ -24653,7 +25571,10 @@ function render_node(node) {
24653
25571
  if (node.icon && /^yi-[a-z0-9-]+$/.test(node.icon)) row.push([
24654
25572
  "span",
24655
25573
  { class: "icon ROUTEMAP_ICON" },
24656
- [["i", { class: node.icon }]]
25574
+ [["i", {
25575
+ class: node.icon,
25576
+ "aria-hidden": "true"
25577
+ }]]
24657
25578
  ]);
24658
25579
  row.push([
24659
25580
  "span",
@@ -24702,16 +25623,23 @@ function render_node(node) {
24702
25623
  ]);
24703
25624
  let row_class = (node.current ? " ROUTEMAP_CURRENT" : "") + (node.ref ? " ROUTEMAP_IS_REF" : "");
24704
25625
  let $row;
24705
- if (node.route) $row = createElement2([
24706
- "a",
24707
- {
25626
+ if (node.route) {
25627
+ let a_attrs = {
24708
25628
  class: "ROUTEMAP_LINK ROUTEMAP_ROW" + row_class,
24709
25629
  href: "#" + node.route,
24710
25630
  title: node.route
24711
- },
24712
- row
24713
- ]);
24714
- else $row = createElement2([
25631
+ };
25632
+ if (node.label) {
25633
+ a_attrs["aria-label"] = node.label;
25634
+ a_attrs["data-i18n-aria-label"] = node.label;
25635
+ }
25636
+ if (node.current) a_attrs["aria-current"] = "page";
25637
+ $row = createElement2([
25638
+ "a",
25639
+ a_attrs,
25640
+ row
25641
+ ]);
25642
+ } else $row = createElement2([
24715
25643
  "span",
24716
25644
  { class: "ROUTEMAP_ROW ROUTEMAP_STRUCT" + row_class },
24717
25645
  row
@@ -24995,7 +25923,7 @@ function yui_shell_show_route_map(shell, opts) {
24995
25923
  if (gclass_find_by_name("C_YUI_WINDOW")) {
24996
25924
  let win_ref = { gobj: null };
24997
25925
  let $body = build_body(shell, t);
24998
- let $parent = yui_shell_popup_layer(shell) || typeof document !== "undefined" && document.getElementById("top-layer") || null;
25926
+ let $parent = typeof document !== "undefined" && document.getElementById("top-layer") || null;
24999
25927
  let win = gobj_create_service(WIN_NAME$2, "C_YUI_WINDOW", {
25000
25928
  $parent,
25001
25929
  subscriber: null,
@@ -26069,7 +26997,13 @@ function add_tab(gobj, gobj2, id, text, icon) {
26069
26997
  let $item = createElement2([
26070
26998
  "li",
26071
26999
  { class: "" },
26072
- [["a", { href: id }]]
27000
+ [["a", {
27001
+ href: id,
27002
+ title: t(text),
27003
+ "data-i18n-title": text,
27004
+ "aria-label": t(text),
27005
+ "data-i18n-aria-label": text
27006
+ }]]
26073
27007
  ]);
26074
27008
  $tabs.appendChild($item);
26075
27009
  $item.gobj = gobj2;
@@ -26077,7 +27011,10 @@ function add_tab(gobj, gobj2, id, text, icon) {
26077
27011
  if (icon) $a.appendChild(createElement2([
26078
27012
  "span",
26079
27013
  { class: "icon is-small is-hidden-mobile" },
26080
- ["i", { class: `${icon}` }]
27014
+ ["i", {
27015
+ class: `${icon}`,
27016
+ "aria-hidden": "true"
27017
+ }]
26081
27018
  ]));
26082
27019
  $a.appendChild(createElement2([
26083
27020
  "span",
@@ -26266,7 +27203,9 @@ function add_topic_card(gobj, id, text, icon, desc) {
26266
27203
  class: "yui-nav-item yui-nav-card TREEDB_TOPIC_CARD",
26267
27204
  href: "#",
26268
27205
  "data-topic-href": id,
26269
- "aria-label": text,
27206
+ title: t(text),
27207
+ "data-i18n-title": text,
27208
+ "aria-label": t(text),
26270
27209
  "data-i18n-aria-label": text
26271
27210
  },
26272
27211
  [[
@@ -27110,7 +28049,7 @@ function open_json_viewer$1(gobj, opts) {
27110
28049
  }
27111
28050
  });
27112
28051
  } else {
27113
- let $win_parent = shell && yui_shell_popup_layer(shell) || typeof document !== "undefined" && document.getElementById("top-layer") || null;
28052
+ let $win_parent = typeof document !== "undefined" && document.getElementById("top-layer") || null;
27114
28053
  priv.json_win = gobj_create_service(`treedb-topics-jsonwin-${clean_name(gobj_name(gobj))}`, "C_YUI_WINDOW", {
27115
28054
  $parent: $win_parent,
27116
28055
  subscriber: null,
@@ -27237,7 +28176,9 @@ function request_print_tranger$1(gobj, path) {
27237
28176
  }, gobj);
27238
28177
  if (ret) {
27239
28178
  log_error(ret);
27240
- answer_print_tranger_refused$1(gobj, path, { error: refusal_text$1(ret) || "print-tranger failed" });
28179
+ const refusal = refusal_text$1(ret);
28180
+ if (refusal) answer_print_tranger_refused$1(gobj, path, { error: refusal });
28181
+ else answer_print_tranger_refused$1(gobj, path, { i18n: "print-tranger failed" });
27241
28182
  return;
27242
28183
  }
27243
28184
  if (path) priv.drills_in_flight.add(path);
@@ -27305,9 +28246,13 @@ function ac_mt_command_answer$2(gobj, event, kw, src) {
27305
28246
  }
27306
28247
  } else gobj.priv.drills_in_flight.clear();
27307
28248
  if (result < 0) {
27308
- if (path) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
28249
+ if (path && comment) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
28250
+ path,
28251
+ error: comment
28252
+ }, gobj);
28253
+ else if (path) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
27309
28254
  path,
27310
- error: comment || "print-tranger failed"
28255
+ i18n: "print-tranger failed"
27311
28256
  }, gobj);
27312
28257
  else yui_shell_show_error(yui_shell_of(gobj), comment || "print-tranger failed", { t });
27313
28258
  return 0;
@@ -29092,7 +30037,7 @@ function present_json_popup(gobj, $box, opts) {
29092
30037
  win: null
29093
30038
  };
29094
30039
  let win = gobj_create_service(opts.name, "C_YUI_WINDOW", {
29095
- $parent: popup_mount_layer(gobj),
30040
+ $parent: popup_mount_layer(),
29096
30041
  subscriber: null,
29097
30042
  modal: false,
29098
30043
  showMax: true,
@@ -29146,10 +30091,17 @@ function retire_popup_window(win) {
29146
30091
  log_warning(`${GCLASS_NAME$5}: json window already gone: ${e}`);
29147
30092
  }
29148
30093
  }
29149
- function popup_mount_layer(gobj) {
29150
- let $layer = yui_shell_popup_layer(yui_shell_of(gobj));
29151
- if ($layer) return $layer;
29152
- return document.getElementById("popup-layer") || document.body;
30094
+ /************************************************************
30095
+ * Where a json window of this view lives: null, the WINDOW
30096
+ * layer of the shell, where every window lives -- below the
30097
+ * shell's dialogs, so a confirm asked over it paints above it
30098
+ * (the legacy C_YUI_MAIN '#popup-layer' when a page has one).
30099
+ * It was the shell's popup layer: above the drawer, in the way
30100
+ * of the toolbar's dropdowns, and in an order of its own no
30101
+ * other window could rise above.
30102
+ ************************************************************/
30103
+ function popup_mount_layer() {
30104
+ return document.getElementById("popup-layer") || null;
29153
30105
  }
29154
30106
  /******************************
29155
30107
  * Framework Methods
@@ -29764,7 +30716,10 @@ function col_title_formatter(gobj, col) {
29764
30716
  let text = col_label(col, topic_name);
29765
30717
  let attrs = {};
29766
30718
  const NO_I18N = "\0";
29767
- if (t(col.id, { defaultValue: NO_I18N }) !== NO_I18N) attrs.i18n = col.id;
30719
+ let key = null;
30720
+ if (topic_name && t(topic_name + "." + col.id, { defaultValue: NO_I18N }) !== NO_I18N) key = topic_name + "." + col.id;
30721
+ else if (t(col.id, { defaultValue: NO_I18N }) !== NO_I18N) key = col.id;
30722
+ if (key) attrs.i18n = key;
29768
30723
  return createElement2([
29769
30724
  "span",
29770
30725
  attrs,
@@ -29858,6 +30813,9 @@ function create_tabulator(gobj) {
29858
30813
  colDef.editable = function() {
29859
30814
  return gobj_read_bool_attr(gobj, "editable") && !gobj_read_bool_attr(gobj, "readonly");
29860
30815
  };
30816
+ yui_tabulator_name_editor(colDef, function() {
30817
+ return col_label(col, gobj_read_str_attr(gobj, "topic_name"));
30818
+ });
29861
30819
  }
29862
30820
  const FILTERABLE = [
29863
30821
  "string",
@@ -71216,7 +72174,7 @@ function open_json_viewer(gobj) {
71216
72174
  });
71217
72175
  gobj_start(jv);
71218
72176
  } else {
71219
- let $win_parent = shell && yui_shell_popup_layer(shell) || typeof document !== "undefined" && document.getElementById("top-layer") || null;
72177
+ let $win_parent = typeof document !== "undefined" && document.getElementById("top-layer") || null;
71220
72178
  priv.json_win = gobj_create_service(`treedb-jsonwin-${clean_name(gobj_name(gobj))}`, "C_YUI_WINDOW", {
71221
72179
  $parent: $win_parent,
71222
72180
  subscriber: null,
@@ -71330,7 +72288,9 @@ function request_print_tranger(gobj, path) {
71330
72288
  }, gobj);
71331
72289
  if (ret) {
71332
72290
  log_error(ret);
71333
- answer_print_tranger_refused(gobj, path, { error: refusal_text(ret) || "print-tranger failed" });
72291
+ const refusal = refusal_text(ret);
72292
+ if (refusal) answer_print_tranger_refused(gobj, path, { error: refusal });
72293
+ else answer_print_tranger_refused(gobj, path, { i18n: "print-tranger failed" });
71334
72294
  return;
71335
72295
  }
71336
72296
  if (path) priv.drills_in_flight.add(path);
@@ -71735,9 +72695,13 @@ function ac_mt_command_answer$1(gobj, event, kw, src) {
71735
72695
  }
71736
72696
  } else priv.drills_in_flight.clear();
71737
72697
  if (result < 0) {
71738
- if (path) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
72698
+ if (path && comment) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
72699
+ path,
72700
+ error: comment
72701
+ }, gobj);
72702
+ else if (path) gobj_send_event(jv, "EV_SUBTREE_ERROR", {
71739
72703
  path,
71740
- error: comment || "print-tranger failed"
72704
+ i18n: "print-tranger failed"
71741
72705
  }, gobj);
71742
72706
  else yui_shell_show_error(yui_shell_of(gobj), comment || "print-tranger failed", { t });
71743
72707
  return 0;
@@ -83329,7 +84293,8 @@ function apply_dev_traces() {
83329
84293
  * Shell-agnostic: the legacy C_YUI_MAIN shell has a
83330
84294
  * '#top-layer' stacking element; the new C_YUI_SHELL does not.
83331
84295
  * We pass that element when present, otherwise null — C_YUI_WINDOW
83332
- * falls back to document.body by contract.
84296
+ * then takes the window layer of the shell (document.body while
84297
+ * there is none), and follows the shell across a logout.
83333
84298
  ************************************************************/
83334
84299
  function setup_dev(self, show) {
83335
84300
  if (show) {
@@ -83419,7 +84384,8 @@ var WIN_NAME$1 = "Frontend-View-Window";
83419
84384
  * Shell-agnostic: the legacy C_YUI_MAIN shell has a
83420
84385
  * '#top-layer' stacking element; the new C_YUI_SHELL does not.
83421
84386
  * We pass that element when present, otherwise null — C_YUI_WINDOW
83422
- * falls back to document.body by contract.
84387
+ * then takes the window layer of the shell (document.body while
84388
+ * there is none), and follows the shell across a logout.
83423
84389
  *
83424
84390
  * Returns the window gobj, or null.
83425
84391
  ************************************************************/