@yuneta/gobj-ui 7.26.33 → 7.26.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +223 -53
  2. package/dist/gobj-ui.cjs.js +1053 -144
  3. package/dist/gobj-ui.es.js +1053 -144
  4. package/package.json +2 -2
  5. package/scripts/validate-locales.mjs +672 -96
  6. package/src/c_yui_form.js +229 -11
  7. package/src/c_yui_gclass.js +2 -0
  8. package/src/c_yui_gobj_tree_js.js +2 -0
  9. package/src/c_yui_json.js +2 -0
  10. package/src/c_yui_json_graph.js +2 -0
  11. package/src/c_yui_nav.js +23 -5
  12. package/src/c_yui_shell.css +16 -0
  13. package/src/c_yui_shell.js +282 -26
  14. package/src/c_yui_treedb_graph.js +26 -8
  15. package/src/c_yui_treedb_topic_with_form.js +40 -18
  16. package/src/c_yui_treedb_topics.js +34 -11
  17. package/src/c_yui_window.js +132 -32
  18. package/src/c_yui_window_manager.js +156 -44
  19. package/src/form_json_editor_names.js +448 -0
  20. package/src/form_json_editor_names.wiring.test.js +595 -0
  21. package/src/form_label_i18n.wiring.test.js +169 -0
  22. package/src/form_table_validator.wiring.test.js +192 -0
  23. package/src/form_template_field.wiring.test.js +21 -0
  24. package/src/i18n_key_functions.js +14 -6
  25. package/src/i18n_key_functions.test.js +124 -2
  26. package/src/lib_maplibre.js +26 -2
  27. package/src/lib_maplibre_relocalize.test.js +78 -0
  28. package/src/named_controls.wiring.test.js +468 -0
  29. package/src/nav_cards_helpers.js +9 -1
  30. package/src/nav_item_badge.wiring.test.js +182 -1
  31. package/src/shell_route_map.js +25 -8
  32. package/src/table_editor_name.wiring.test.js +201 -0
  33. package/src/treedb_column_title.wiring.test.js +151 -0
  34. package/src/treedb_json_drill.wiring.test.js +41 -1
  35. package/src/window_layer.wiring.test.js +308 -43
  36. package/src/window_show.wiring.test.js +6 -2
  37. package/src/window_stacking.wiring.test.js +334 -0
  38. package/src/yui_badge.js +58 -13
  39. package/src/yui_badge.test.js +40 -3
  40. package/src/yui_dev.js +2 -1
  41. package/src/yui_file_field.js +2 -0
  42. package/src/yui_frontend_view.js +2 -1
  43. package/src/yui_gclass_view.js +9 -6
  44. package/src/yui_tabulator_i18n.js +42 -0
  45. package/src/yui_tabulator_i18n.test.js +48 -1
@@ -63,7 +63,7 @@ import {
63
63
  abandon_form_writes,
64
64
  } from "./form_writes_in_flight.js";
65
65
 
66
- import {yui_shell_show_error, yui_shell_show_modal, yui_shell_popup_layer} from "./shell_modals.js";
66
+ import {yui_shell_show_error, yui_shell_show_modal} from "./shell_modals.js";
67
67
  import {
68
68
  yui_shell_of,
69
69
  yui_shell_icons_changed,
@@ -593,9 +593,13 @@ function add_tab(gobj, gobj2, id, text, icon)
593
593
  /*
594
594
  * Create li a
595
595
  */
596
+ /* A tab is a control: its title and its name, each with its key
597
+ * (the topic's), as every control of the library carries. */
596
598
  let $item = createElement2(
597
599
  ['li', {class: ''}, [ // is-active
598
- ['a', {href: id}]
600
+ ['a', {href: id,
601
+ title: t(text), 'data-i18n-title': text,
602
+ 'aria-label': t(text), 'data-i18n-aria-label': text}]
599
603
  ]]
600
604
  );
601
605
  $tabs.appendChild($item);
@@ -613,7 +617,7 @@ function add_tab(gobj, gobj2, id, text, icon)
613
617
  $a.appendChild(
614
618
  createElement2(
615
619
  ['span', {class: 'icon is-small is-hidden-mobile'},
616
- ['i', {class: `${icon}`}]
620
+ ['i', {class: `${icon}`, 'aria-hidden': 'true'}]
617
621
  ]
618
622
  )
619
623
  );
@@ -822,7 +826,8 @@ function add_topic_card(gobj, id, text, icon, desc)
822
826
  let $card = createElement2(
823
827
  ['a', {class: 'yui-nav-item yui-nav-card TREEDB_TOPIC_CARD',
824
828
  href: '#', 'data-topic-href': id,
825
- 'aria-label': text, 'data-i18n-aria-label': text}, [
829
+ title: t(text), 'data-i18n-title': text,
830
+ 'aria-label': t(text), 'data-i18n-aria-label': text}, [
826
831
  ['span', {class: 'icon is-medium'}, [['i', {class: icon || 'yi-table',
827
832
  'aria-hidden': 'true'}]]],
828
833
  ['span', {class: 'yui-nav-label', i18n: text}, text_node(text)]
@@ -2090,9 +2095,13 @@ function open_json_viewer(gobj, opts)
2090
2095
  }
2091
2096
  });
2092
2097
  } else {
2093
- let $win_parent = (shell && yui_shell_popup_layer(shell)) ||
2094
- (typeof document !== "undefined" && document.getElementById("top-layer")) ||
2095
- null;
2098
+ /* The WINDOW layer, where every window lives (the legacy
2099
+ * '#top-layer' when a page has one): no `$parent` takes it. It
2100
+ * was the shell's popup layer -- above the drawer, in the way of
2101
+ * the toolbar's dropdowns (they opened BEHIND it where they met),
2102
+ * and in an order of its own no other window could rise above. */
2103
+ let $win_parent = (typeof document !== "undefined" &&
2104
+ document.getElementById("top-layer")) || null;
2096
2105
 
2097
2106
  priv.json_win = gobj_create_service(
2098
2107
  `treedb-topics-jsonwin-${clean_name(gobj_name(gobj))}`,
@@ -2276,8 +2285,16 @@ function request_print_tranger(gobj, path)
2276
2285
  }, gobj);
2277
2286
  if(ret) {
2278
2287
  log_error(ret);
2279
- answer_print_tranger_refused(gobj, path,
2280
- {error: /* i18n: data */ refusal_text(ret) || "print-tranger failed"});
2288
+ /* The transport's own words travel as `error`, text the viewer
2289
+ * draws as it came. Our fallback is a KEY, and travels as `i18n`:
2290
+ * as `error` it was drawn raw, "print-tranger failed" in English
2291
+ * in every language. */
2292
+ const refusal = refusal_text(ret);
2293
+ if(refusal) {
2294
+ answer_print_tranger_refused(gobj, path, {error: /* i18n: data */ refusal});
2295
+ } else {
2296
+ answer_print_tranger_refused(gobj, path, {i18n: "print-tranger failed"});
2297
+ }
2281
2298
  return;
2282
2299
  }
2283
2300
  if(path) {
@@ -2384,9 +2401,15 @@ function ac_mt_command_answer(gobj, event, kw, src)
2384
2401
  gobj.priv.drills_in_flight.clear();
2385
2402
  }
2386
2403
  if(result < 0) {
2387
- if(path) {
2404
+ if(path && comment) {
2405
+ gobj_send_event(jv, "EV_SUBTREE_ERROR",
2406
+ {path: path, error: comment}, gobj); // the backend's words
2407
+ } else if(path) {
2408
+ /* No word from the backend: our fallback, a KEY, as `i18n`
2409
+ * -- the viewer draws it translated, and it changes
2410
+ * language with the app. As `error` it was drawn raw. */
2388
2411
  gobj_send_event(jv, "EV_SUBTREE_ERROR",
2389
- {path: path, error: comment || "print-tranger failed"}, gobj);
2412
+ {path: path, i18n: "print-tranger failed"}, gobj);
2390
2413
  } else {
2391
2414
  yui_shell_show_error(yui_shell_of(gobj),
2392
2415
  /* i18n: data */ comment || "print-tranger failed", {t: t});
@@ -53,6 +53,10 @@ import {
53
53
  yui_shell_of,
54
54
  yui_shell_register_overlay,
55
55
  yui_shell_overlay_dismissed,
56
+ yui_shell_window_layer,
57
+ yui_shell_raise_window,
58
+ yui_shell_watch_changes,
59
+ yui_shell_unwatch_changes,
56
60
  } from "./c_yui_shell.js";
57
61
 
58
62
  import "./c_yui_window.css";
@@ -81,7 +85,7 @@ const MOBILE_MAX = 768;
81
85
  ***************************************************************/
82
86
  const attrs_table = [
83
87
  SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
84
- 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)"),
88
+ 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"),
85
89
  SDATA(data_type_t.DTP_INTEGER, "x", 0, "300", "X position"),
86
90
  SDATA(data_type_t.DTP_INTEGER, "y", 0, "100", "Y position"),
87
91
  SDATA(data_type_t.DTP_INTEGER, "width", 0, "700", "Width of the window"),
@@ -121,6 +125,12 @@ SDATA_END()
121
125
 
122
126
  let PRIVATE_DATA = {
123
127
  prevSize: 0,
128
+ /* The window lives in the place the shell gives it (its host
129
+ * named no `$parent`, or the one it named went with a shell):
130
+ * it follows the shell, see ac_shell_changed(). */
131
+ placed_by_shell: false,
132
+ /* The shell that holds this window's Back entry (`back_overlay`). */
133
+ back_shell: null,
124
134
  };
125
135
 
126
136
  let __gclass__ = null;
@@ -159,6 +169,12 @@ function mt_create(gobj)
159
169
 
160
170
  build_ui(gobj);
161
171
 
172
+ /* Its place belongs to a shell, and the window may outlive it (a
173
+ * window parented to the app, across a logout): told when a shell
174
+ * is built or destroyed, it takes its place again
175
+ * (ac_shell_changed). Unwatched in mt_destroy. */
176
+ yui_shell_watch_changes(gobj);
177
+
162
178
  if(manager) {
163
179
  let $c = gobj_read_attr(gobj, "$container");
164
180
  if($c) {
@@ -185,14 +201,31 @@ function mt_create(gobj)
185
201
  title_prefix: gobj_read_str_attr(gobj, "title_prefix") || "",
186
202
  icon: gobj_read_attr(gobj, "icon") || "",
187
203
  }, gobj);
188
- } else if(gobj_read_bool_attr(gobj, "back_dismissable")) {
189
- /* Floating overlay window (no dock manager): the browser Back
190
- * button closes it, like a modal/popup. Dock-managed windows
191
- * are persistent workspace surfaces and are left out — which
192
- * is also why a navigation panel prefers a manager, and falls
193
- * back to `keep_on_navigate` when the app has no dock.
194
- * Retired in mt_destroy (covers every teardown path). */
195
- register_back_overlay(gobj);
204
+ } else {
205
+ /* No dock: the window orders itself, on the SAME counter the
206
+ * dock raises its windows to (yui_shell_raise_window). Opened on
207
+ * top of every window, docked or not -- it opened under any
208
+ * docked one -- and brought up again by any press on it, which
209
+ * crosses the FSM as EV_SHOW, as a docked window's crosses it as
210
+ * the dock's EV_FOCUS_WINDOW. */
211
+ let $c = gobj_read_attr(gobj, "$container");
212
+ if($c) {
213
+ yui_shell_raise_window($c);
214
+ $c.addEventListener("pointerdown", function() {
215
+ if(!gobj_is_destroying(gobj)) {
216
+ gobj_send_event(gobj, "EV_SHOW", {}, gobj);
217
+ }
218
+ }, true);
219
+ }
220
+ if(gobj_read_bool_attr(gobj, "back_dismissable")) {
221
+ /* Floating overlay window (no dock manager): the browser Back
222
+ * button closes it, like a modal/popup. Dock-managed windows
223
+ * are persistent workspace surfaces and are left out — which
224
+ * is also why a navigation panel prefers a manager, and falls
225
+ * back to `keep_on_navigate` when the app has no dock.
226
+ * Retired in mt_destroy (covers every teardown path). */
227
+ register_back_overlay(gobj);
228
+ }
196
229
  }
197
230
 
198
231
  /* Keep the window inside the viewport on a breakpoint change.
@@ -215,20 +248,26 @@ function mt_create(gobj)
215
248
  ***************************************************************/
216
249
  function mt_destroy(gobj)
217
250
  {
251
+ let priv = gobj.priv;
252
+
253
+ yui_shell_unwatch_changes(gobj);
254
+
218
255
  let manager = gobj_read_pointer_attr(gobj, "manager");
219
256
  if(manager) {
220
257
  gobj_send_event(manager, "EV_UNREGISTER_WINDOW", {window: gobj_full_name(gobj)}, gobj);
221
258
  }
222
259
 
223
260
  /* Retire the overlay-history entry, if this was a Back-dismissable
224
- * floating window (no-op when Back itself triggered the teardown). */
261
+ * floating window (no-op when Back itself triggered the teardown),
262
+ * in the shell that HOLDS it: the one this window governs now may
263
+ * be another (ac_shell_changed). */
225
264
  let back_overlay = gobj_read_attr(gobj, "back_overlay");
226
265
  if(back_overlay) {
227
- let shell = yui_shell_of(gobj);
228
- if(shell) {
229
- yui_shell_overlay_dismissed(shell, back_overlay);
266
+ if(priv.back_shell) {
267
+ yui_shell_overlay_dismissed(priv.back_shell, back_overlay);
230
268
  }
231
269
  gobj_write_attr(gobj, "back_overlay", null);
270
+ priv.back_shell = null;
232
271
  }
233
272
 
234
273
  let on_win_resize = gobj_read_attr(gobj, "win_resize_handler");
@@ -724,8 +763,9 @@ function build_ui(gobj)
724
763
  refresh_language($container, t);
725
764
 
726
765
  let $parent = gobj_read_attr(gobj, "$parent");
766
+ gobj.priv.placed_by_shell = !$parent;
727
767
  if(!$parent) {
728
- $parent = default_parent(gobj);
768
+ $parent = window_place(gobj);
729
769
  }
730
770
  $parent.appendChild($container);
731
771
 
@@ -789,6 +829,7 @@ function register_back_overlay(gobj)
789
829
  shell,
790
830
  function() {
791
831
  gobj_write_attr(gobj, "back_overlay", null);
832
+ gobj.priv.back_shell = null;
792
833
  const closing = close_window(gobj);
793
834
  if(closing.state === "vetoed") {
794
835
  rearm();
@@ -799,6 +840,7 @@ function register_back_overlay(gobj)
799
840
  {keep_on_navigate: gobj_read_bool_attr(gobj, "keep_on_navigate")}
800
841
  );
801
842
  gobj_write_attr(gobj, "back_overlay", overlay);
843
+ gobj.priv.back_shell = overlay? shell : null;
802
844
  }
803
845
 
804
846
  /************************************************************
@@ -861,7 +903,8 @@ function close_window(gobj)
861
903
 
862
904
  /************************************************************
863
905
  * Where a window lives when its host names no `$parent`: the
864
- * OVERLAY layer of the shell -- above the content, below the
906
+ * WINDOW layer of its shell (yui_shell_window_layer()), inside
907
+ * the overlay layer -- above the content, below the drawer, the
865
908
  * toolbar's dropdowns, the dialogs and the toasts.
866
909
  *
867
910
  * It used to be document.body, and that put every window above
@@ -874,17 +917,18 @@ function close_window(gobj)
874
917
  * toolbar, any dialog opened with a window on screen, the same.
875
918
  *
876
919
  * Inside the layer the dock's z-index still orders the windows
877
- * among themselves. No shell (a page with windows and no shell):
878
- * the body, as before.
920
+ * among themselves, and no number it gives them reaches the
921
+ * drawer (7.26.31 put them in the overlay layer itself, beside
922
+ * it). No shell (a page with windows and no shell, or the time
923
+ * between a logout and the next login): the body.
924
+ *
925
+ * The place is the shell's, and a window parented to the app
926
+ * outlives it: ac_shell_changed() moves it when a shell goes or
927
+ * comes.
879
928
  ************************************************************/
880
- function default_parent(gobj)
929
+ function window_place(gobj)
881
930
  {
882
- let shell = yui_shell_of(gobj);
883
- let layers = shell && shell.priv && shell.priv.layers;
884
- if(layers && layers.overlay) {
885
- return layers.overlay;
886
- }
887
- return document.body;
931
+ return yui_shell_window_layer(yui_shell_of(gobj)) || document.body;
888
932
  }
889
933
 
890
934
  /************************************************************
@@ -1266,8 +1310,12 @@ function ac_show(gobj, event, kw, src)
1266
1310
  {
1267
1311
  /* Bring the window back in front of the reader: out of the dock if
1268
1312
  * it was minimized, above the others. The manager owns both when
1269
- * there is one; without one the window is just made visible and
1270
- * moved to the top of its layer. */
1313
+ * there is one; without one the window is made visible and given
1314
+ * the next number of the windows' one z-order -- the same counter
1315
+ * the dock raises its windows to. It used to be MOVED to the end of
1316
+ * its parent, an order the dock's numbers always beat; and a move
1317
+ * takes the focus, the scroll positions and the press that started
1318
+ * on the window. */
1271
1319
  let manager = gobj_read_pointer_attr(gobj, "manager");
1272
1320
  if(manager) {
1273
1321
  gobj_send_event(manager, "EV_FOCUS_WINDOW", {window: gobj_full_name(gobj)}, gobj);
@@ -1279,10 +1327,7 @@ function ac_show(gobj, event, kw, src)
1279
1327
  return -1;
1280
1328
  }
1281
1329
  $container.style.removeProperty('display');
1282
- let $parent = $container.parentNode;
1283
- if($parent && $parent.lastChild !== $container) {
1284
- $parent.appendChild($container);
1285
- }
1330
+ yui_shell_raise_window($container);
1286
1331
  return 0;
1287
1332
  }
1288
1333
 
@@ -1304,6 +1349,59 @@ function ac_close(gobj, event, kw, src)
1304
1349
  return 0;
1305
1350
  }
1306
1351
 
1352
+ /************************************************************
1353
+ * EV_SHELL_CHANGED -- a shell was built, or is being destroyed
1354
+ * (`src`, then destroying): the place this window lives in may
1355
+ * have gone with it, or be in the new one
1356
+ * (yui_shell_watch_changes()).
1357
+ *
1358
+ * - A window whose host named no `$parent` takes the window place
1359
+ * again: the window layer of the shell that governs it NOW,
1360
+ * document.body while there is none. So the developer window
1361
+ * outlives a logout in the body, and the login's shell takes it
1362
+ * in -- above its content, under its dialogs. From 7.26.31 to
1363
+ * 7.26.34 it stayed in the destroyed shell's layer: alive,
1364
+ * detached, invisible.
1365
+ * - A window its host placed stays where it was put, unless that
1366
+ * element was inside the shell being destroyed: then no place
1367
+ * is left, and it takes the window place from now on.
1368
+ * - Its Back entry, when the shell being destroyed held it, went
1369
+ * with that shell's history and is forgotten. The next shell
1370
+ * does not get one: a history entry pushed while a shell is
1371
+ * still being built would sit under its first route. Back no
1372
+ * longer closes such a window; its ✕ does, as before 7.26.31.
1373
+ ************************************************************/
1374
+ function ac_shell_changed(gobj, event, kw, src)
1375
+ {
1376
+ let priv = gobj.priv;
1377
+ let going = gobj_is_destroying(src);
1378
+
1379
+ if(going && priv.back_shell === src) {
1380
+ gobj_write_attr(gobj, "back_overlay", null);
1381
+ priv.back_shell = null;
1382
+ }
1383
+
1384
+ let $container = gobj_read_attr(gobj, "$container");
1385
+ if(!$container) {
1386
+ log_error(`${gobj_short_name(gobj)}: EV_SHELL_CHANGED without a container`);
1387
+ return -1;
1388
+ }
1389
+
1390
+ if(!priv.placed_by_shell) {
1391
+ let $shell = going? gobj_read_attr(src, "$container") : null;
1392
+ if(!$shell || !$shell.contains($container)) {
1393
+ return 0; // the place its host chose is still there
1394
+ }
1395
+ priv.placed_by_shell = true;
1396
+ }
1397
+
1398
+ let $place = window_place(gobj);
1399
+ if($container.parentNode !== $place) {
1400
+ $place.appendChild($container);
1401
+ }
1402
+ return 0;
1403
+ }
1404
+
1307
1405
 
1308
1406
 
1309
1407
 
@@ -1340,7 +1438,8 @@ function create_gclass(gclass_name)
1340
1438
  ["EV_REFRESH", ac_refresh, null],
1341
1439
  ["EV_SHOW", ac_show, null],
1342
1440
  ["EV_HIDE", ac_hide, null],
1343
- ["EV_CLOSE_WINDOW", ac_close, null]
1441
+ ["EV_CLOSE_WINDOW", ac_close, null],
1442
+ ["EV_SHELL_CHANGED",ac_shell_changed, null]
1344
1443
  ]]
1345
1444
  ];
1346
1445
 
@@ -1354,7 +1453,8 @@ function create_gclass(gclass_name)
1354
1453
  ["EV_REFRESH", 0],
1355
1454
  ["EV_SHOW", 0],
1356
1455
  ["EV_HIDE", 0],
1357
- ["EV_CLOSE_WINDOW", 0]
1456
+ ["EV_CLOSE_WINDOW", 0],
1457
+ ["EV_SHELL_CHANGED", 0]
1358
1458
  ];
1359
1459
 
1360
1460
  __gclass__ = gclass_create(