@yuneta/gobj-ui 7.26.27 → 7.26.29

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 +590 -25
  2. package/dist/gobj-ui.cjs.js +1080 -229
  3. package/dist/gobj-ui.es.js +1081 -230
  4. package/package.json +4 -2
  5. package/scripts/validate-locales.mjs +2656 -315
  6. package/src/c_g6_nodes_tree.js +1086 -253
  7. package/src/c_yui_form.js +16 -4
  8. package/src/c_yui_fsm_graph.js +5 -0
  9. package/src/c_yui_gclass.js +11 -0
  10. package/src/c_yui_gobj_tree_js.js +18 -1
  11. package/src/c_yui_json_graph.js +5 -0
  12. package/src/c_yui_nav.js +21 -16
  13. package/src/c_yui_pager.js +1 -1
  14. package/src/c_yui_period.js +10 -3
  15. package/src/c_yui_schema_editor.js +34 -18
  16. package/src/c_yui_shell.js +11 -10
  17. package/src/c_yui_treedb_graph.js +17 -9
  18. package/src/c_yui_treedb_schema.js +5 -0
  19. package/src/c_yui_treedb_topic_with_form.js +9 -9
  20. package/src/c_yui_treedb_topics.js +13 -13
  21. package/src/c_yui_window.js +2 -2
  22. package/src/c_yui_window_manager.js +5 -2
  23. package/src/c_yui_wizard.js +8 -1
  24. package/src/form_json_field.js +7 -0
  25. package/src/g6_canvas_tab_order.js +84 -0
  26. package/src/g6_canvas_tab_order.wiring.test.js +232 -0
  27. package/src/g6_nodes_tree_controls.test.js +547 -0
  28. package/src/g6_nodes_tree_focus.wiring.test.js +1360 -0
  29. package/src/g6_nodes_tree_i18n_keys.test.js +241 -5
  30. package/src/i18n_key_functions.js +235 -0
  31. package/src/i18n_key_functions.test.js +1525 -0
  32. package/src/lib_graph.js +38 -1
  33. package/src/nav_backbar_name.wiring.test.js +82 -0
  34. package/src/schema_flags.js +47 -0
  35. package/src/schema_import.js +7 -0
  36. package/src/shell_modals.js +20 -16
  37. package/src/shell_route_map.js +13 -3
  38. package/src/wizard_helpers.js +8 -0
  39. package/src/yui_dev.js +16 -3
  40. package/src/yui_dev_controls.test.js +174 -0
  41. package/src/yui_gclass_view.js +1 -1
  42. package/src/yui_graph_camera.js +1 -1
  43. package/src/yui_install.js +10 -8
  44. package/src/yui_table_select.js +2 -2
  45. package/src/yui_time.js +17 -2
package/src/c_yui_form.js CHANGED
@@ -805,11 +805,13 @@ function build_html_form(gobj, $form, prefix, template)
805
805
  }
806
806
 
807
807
  /*
808
- * Build the html field and add to form, if <tag> is not-null
808
+ * Build the html field and add to form, if <tag> is not-null.
809
+ * Its label and options are the schema's: column names and
810
+ * headers, data.
809
811
  */
810
812
  if(html_field_conf.tag) {
811
- let $field = create_form_field(gobj, $form, prefix, field_desc, html_field_conf);
812
- name_form_control($field, html_field_conf);
813
+ let $field = create_form_field(gobj, $form, prefix, field_desc, /* i18n: data */ html_field_conf);
814
+ name_form_control($field, /* i18n: data */ html_field_conf);
813
815
  $form.appendChild($field);
814
816
  }
815
817
  });
@@ -2547,11 +2549,21 @@ function field_validation_message($input)
2547
2549
  {
2548
2550
  let key = field_validation_key($input);
2549
2551
  if(key) {
2550
- return t(key);
2552
+ return t(/* i18n: C_YUI_FORM_I18N_KEYS, FORM_JSON_FIELD_I18N_KEYS */ key);
2551
2553
  }
2552
2554
  return $input.validationMessage;
2553
2555
  }
2554
2556
 
2557
+ /* The keys this form hands on as data: the one field_validation_key()
2558
+ * returns, which reaches t() by a variable, and the warning of a close
2559
+ * it vetoes (`kw.warning`), which its HOST shows. Published for a
2560
+ * consumer's validate-locales, which asks the app's locales for every
2561
+ * key of an exported `*_I18N_KEYS` array; i18n_key_functions.test.js
2562
+ * holds it to what field_validation_key() returns and to every
2563
+ * `.warning =` of this module. (The json editor's own key is published
2564
+ * by form_json_field.js.) */
2565
+ export const C_YUI_FORM_I18N_KEYS = ["this field is required", "all changes will be lost"];
2566
+
2555
2567
  /************************************************************
2556
2568
  * The i18n key of the message, when the message is ours ("" when
2557
2569
  * it is the browser's): written as the help line's data-i18n, so
@@ -71,6 +71,7 @@ import {
71
71
  import {yui_is_dark, yui_theme_now, yui_watch_theme} from "./yui_theme.js";
72
72
  import {ensure_drag_canvas_patch} from "./g6_drag_canvas_touch.js";
73
73
  import {ensure_pinch_zoom_patch} from "./g6_touch_gestures.js";
74
+ import {keep_canvas_tab_order} from "./g6_canvas_tab_order.js";
74
75
 
75
76
  import {t} from "i18next";
76
77
 
@@ -428,6 +429,10 @@ function build_graph(gobj)
428
429
  }
429
430
  }
430
431
 
432
+ /* Tab goes from the graph to its own buttons: the main canvas in
433
+ * the Tab order, the other layers out (g6_canvas_tab_order.js). */
434
+ keep_canvas_tab_order(gobj, priv.graph);
435
+
431
436
  priv.graph.on('aftertransform', () => {
432
437
  yui_graph_update_zoom(gobj_read_attr(gobj, "$container"), priv.graph);
433
438
  });
@@ -136,6 +136,17 @@ const ZONES = [
136
136
  {id: "machine", key: "machine", col: "right"},
137
137
  ];
138
138
 
139
+ /* The keys of VIEWS, MACHINE_VIEWS and ZONES: they reach t() as data
140
+ * (`v.key`, `zone.key`), where no scan of the source reads them, so
141
+ * they are published here for a consumer's validate-locales, which asks
142
+ * the app's locales for every key of an exported `*_I18N_KEYS` array. A
143
+ * view or a zone added to those tables adds its key here. */
144
+ export const C_YUI_GCLASS_I18N_KEYS = [
145
+ "zones view", "raw view",
146
+ "matrix view", "graph view",
147
+ "attributes", "commands", "methods", "trace levels", "machine"
148
+ ];
149
+
139
150
  /***************************************************************
140
151
  * Data
141
152
  ***************************************************************/
@@ -82,6 +82,7 @@ import { ensure_drag_canvas_patch } from "./g6_drag_canvas_touch.js";
82
82
  import { ensure_pinch_zoom_patch } from "./g6_touch_gestures.js";
83
83
  import { yui_is_dark, yui_theme_now, yui_watch_theme } from "./yui_theme.js";
84
84
  import {g6_event_kw} from "./g6_event_kw.js";
85
+ import {keep_canvas_tab_order} from "./g6_canvas_tab_order.js";
85
86
 
86
87
  /***************************************************************
87
88
  * Constants
@@ -523,6 +524,18 @@ const LAYOUTS = {
523
524
  },
524
525
  };
525
526
 
527
+ /* The `key` of every layout: the picker hands it to t() as data
528
+ * (`LAYOUTS[key].key`), where no scan of the source reads it, so it is
529
+ * published here for a consumer's validate-locales, which asks the
530
+ * app's locales for every key of an exported `*_I18N_KEYS` array. A
531
+ * layout added to LAYOUTS adds its key here. */
532
+ export const C_YUI_GOBJ_TREE_JS_I18N_KEYS = [
533
+ "vertical tree", "vertical tree compact",
534
+ "horizontal tree", "horizontal tree compact",
535
+ "lanes vertical", "lanes horizontal",
536
+ "dagre top-down", "dagre left-right"
537
+ ];
538
+
526
539
  function get_layout_cfg(layout_key)
527
540
  {
528
541
  return LAYOUTS[layout_key] || LAYOUTS["vertical-compact"];
@@ -1150,6 +1163,10 @@ function build_graph(gobj)
1150
1163
  graph.setTheme(priv.theme);
1151
1164
  }
1152
1165
 
1166
+ /* Tab goes from the graph to its own buttons: the main canvas in
1167
+ * the Tab order, the other layers out (g6_canvas_tab_order.js). */
1168
+ keep_canvas_tab_order(gobj, graph);
1169
+
1153
1170
  /* The readout follows ANY camera change, the wheel included — a
1154
1171
  * wheel notch passes through no action of ours. */
1155
1172
  graph.on('aftertransform', () => {
@@ -2845,7 +2862,7 @@ function ac_open_gclass(gobj, event, kw, src)
2845
2862
  close_gclass_view(priv.gclass_view);
2846
2863
  priv.gclass_view = null;
2847
2864
 
2848
- priv.gclass_view = open_gclass_view(gobj, node_data.gclass, {
2865
+ priv.gclass_view = open_gclass_view(gobj, /* i18n: data */ node_data.gclass, {
2849
2866
  title_prefix: gobj_yuno_name() || "",
2850
2867
  /* Which state THIS gobj is in. The description describes the
2851
2868
  * CLASS and cannot carry it, and the tree is the only party
@@ -83,6 +83,7 @@ import {
83
83
  count_branches,
84
84
  } from "./json_view_helpers.js";
85
85
  import {g6_event_kw} from "./g6_event_kw.js";
86
+ import {keep_canvas_tab_order} from "./g6_canvas_tab_order.js";
86
87
 
87
88
  /***************************************************************
88
89
  * Constants
@@ -754,6 +755,10 @@ function build_graph(gobj)
754
755
  graph.setTheme(priv.theme);
755
756
  }
756
757
 
758
+ /* Tab goes from the graph to its own buttons: the main canvas in
759
+ * the Tab order, the other layers out (g6_canvas_tab_order.js). */
760
+ keep_canvas_tab_order(gobj, graph);
761
+
757
762
  graph.on(NodeEvent.CLICK, (evt) => {
758
763
  /* An identity, never the G6 event: a kw is plain json, and the
759
764
  * event is circular -- the machine trace dumps the kw. */
package/src/c_yui_nav.js CHANGED
@@ -230,17 +230,19 @@ function build_ui(gobj)
230
230
  let menu_id = gobj_read_attr(gobj, "menu_id") || "nav";
231
231
  let $container;
232
232
 
233
+ /* The items are the app's nav declaration: the keys of their names
234
+ * are read there (app_config.json), not here. */
233
235
  switch(layout) {
234
- case "vertical": $container = render_vertical(gobj, items); break;
235
- case "icon-bar": $container = render_icon_bar(gobj, items); break;
236
- case "tabs": $container = render_tabs(gobj, items); break;
237
- case "drawer": $container = render_drawer(gobj, items); break;
238
- case "submenu": $container = render_submenu(gobj, items); break;
239
- case "accordion": $container = render_accordion(gobj, items); break;
240
- case "cards": $container = render_cards(gobj, items); break;
241
- case "backbar": $container = render_backbar(gobj); break;
242
- case "breadcrumb": $container = render_breadcrumb(gobj, items); break;
243
- default: $container = render_vertical(gobj, items);
236
+ case "vertical": $container = render_vertical(gobj, /* i18n: data */ items); break;
237
+ case "icon-bar": $container = render_icon_bar(gobj, /* i18n: data */ items); break;
238
+ case "tabs": $container = render_tabs(gobj, /* i18n: data */ items); break;
239
+ case "drawer": $container = render_drawer(gobj, /* i18n: data */ items); break;
240
+ case "submenu": $container = render_submenu(gobj, /* i18n: data */ items); break;
241
+ case "accordion": $container = render_accordion(gobj, /* i18n: data */ items); break;
242
+ case "cards": $container = render_cards(gobj, /* i18n: data */ items); break;
243
+ case "backbar": $container = render_backbar(gobj); break;
244
+ case "breadcrumb": $container = render_breadcrumb(gobj, /* i18n: data */ items); break;
245
+ default: $container = render_vertical(gobj, /* i18n: data */ items);
244
246
  }
245
247
  $container.setAttribute("data-nav-zone", zone);
246
248
  $container.setAttribute("data-nav-layout", layout);
@@ -268,7 +270,7 @@ function build_ui(gobj)
268
270
  if(!$container.hasAttribute("aria-label")) {
269
271
  /* A key, translated by the shell's translator: the bare
270
272
  * menu_id read out "secondary.main.system". */
271
- let label = gobj_read_attr(gobj, "nav_label") || "navigation";
273
+ const label = gobj_read_attr(gobj, "nav_label") || "navigation";
272
274
  $container.setAttribute("aria-label", t(label));
273
275
  $container.setAttribute("data-i18n-aria-label", label);
274
276
  }
@@ -527,16 +529,19 @@ function render_backbar(gobj)
527
529
  if(label) {
528
530
  label_attrs.i18n = label;
529
531
  }
532
+ /* Named by the label when the nav has one, else by "back" -- through
533
+ * t() and with its key, or it reads the raw key in every language. */
534
+ const name_key = label || "back";
530
535
  let a_attrs = {
531
536
  class: "yui-nav-item yui-nav-back",
532
537
  href: back_route ? "#" + back_route : "#",
533
538
  "data-item-id": "__index__",
534
539
  "data-route": back_route,
535
- "aria-label": label || "back"
540
+ title: t(name_key),
541
+ "data-i18n-title": name_key,
542
+ "aria-label": t(name_key),
543
+ "data-i18n-aria-label": name_key
536
544
  };
537
- if(label) {
538
- a_attrs["data-i18n-aria-label"] = label;
539
- }
540
545
  return createElement2(
541
546
  ["div", {},
542
547
  ["a", a_attrs, [
@@ -558,7 +563,7 @@ function render_drawer(gobj, items)
558
563
  wrap.className = "yui-drawer";
559
564
  wrap.setAttribute("role", "dialog");
560
565
  wrap.setAttribute("aria-modal", "true");
561
- let nav_label = gobj_read_attr(gobj, "nav_label") || "navigation";
566
+ const nav_label = gobj_read_attr(gobj, "nav_label") || "navigation";
562
567
  wrap.setAttribute("aria-label", t(nav_label));
563
568
  wrap.setAttribute("data-i18n-aria-label", nav_label);
564
569
 
@@ -237,7 +237,7 @@ function render_header(gobj)
237
237
  * deeper pages show a back arrow.
238
238
  */
239
239
  let $back_icon = $back.querySelector('i');
240
- let back_key = (model.back_kind === "close") ? 'close' : 'back';
240
+ const back_key = (model.back_kind === "close") ? 'close' : 'back';
241
241
  $back_icon.className = (back_key === 'close') ? 'yi-xmark' : 'yi-arrow-left';
242
242
  /* The key moves with the glyph: a key left on "back" made the ✕ of
243
243
  * the root page announce itself as Back after a language switch. */
@@ -112,6 +112,13 @@ const GCLASS_NAME = "C_YUI_PERIOD";
112
112
  const MODE_SPAN = "span";
113
113
  const MODE_CUSTOM = "custom";
114
114
 
115
+ /* Their names: a mode is named by its id through a variable
116
+ * (`t(mode.id)`), where no scan of the source reads it, so they are
117
+ * published for a consumer's validate-locales, which asks the app's
118
+ * locales for every key of an exported `*_I18N_KEYS` array. The names
119
+ * of the granularities and of the rolling windows are yui_time.js's. */
120
+ export const C_YUI_PERIOD_I18N_KEYS = ["span", "custom"];
121
+
115
122
  /***************************************************************
116
123
  * Data
117
124
  ***************************************************************/
@@ -475,7 +482,7 @@ function build_ui(gobj)
475
482
  if(mode.overflow || mode.hidden) {
476
483
  continue;
477
484
  }
478
- priv.$modes.appendChild(build_mode_button(gobj, mode));
485
+ priv.$modes.appendChild(build_mode_button(gobj, /* i18n: C_YUI_PERIOD_I18N_KEYS, YUI_TIME_I18N_KEYS */ mode));
479
486
  }
480
487
 
481
488
  /* Scroll plumbing, not an action: it changes nothing outside the strip.
@@ -591,7 +598,7 @@ function build_more_menu(gobj)
591
598
  if(!mode.overflow) {
592
599
  continue;
593
600
  }
594
- let name = mode_name(mode);
601
+ let name = mode_name(/* i18n: C_YUI_PERIOD_I18N_KEYS, YUI_TIME_I18N_KEYS */ mode);
595
602
  let $item = createElement2(
596
603
  ["a", {class: `dropdown-item YUI_PERIOD_MODE ` +
597
604
  `YUI_PERIOD_MODE_${mode.id.toUpperCase()}`, href: "#",
@@ -1364,7 +1371,7 @@ function ac_language_changed(gobj, event, kw, src)
1364
1371
  if(!mode.$btn) {
1365
1372
  continue;
1366
1373
  }
1367
- let name = mode_name(mode);
1374
+ let name = mode_name(/* i18n: C_YUI_PERIOD_I18N_KEYS, YUI_TIME_I18N_KEYS */ mode);
1368
1375
  let $text = mode.$btn.querySelector(".YUI_PERIOD_MODE_TEXT");
1369
1376
  if($text) {
1370
1377
  $text.textContent = name;
@@ -168,6 +168,16 @@ import {text_node} from "./yui_text.js";
168
168
  ***************************************************************/
169
169
  const GCLASS_NAME = "C_YUI_SCHEMA_EDITOR";
170
170
 
171
+ /* The keys this editor hands to t() as DATA, where no scan of the
172
+ * source reads them: the severity of a finding, which the checks of
173
+ * schema_validate.js write (`add("error", ...)`) and the report
174
+ * translates. Published for a consumer's validate-locales, which asks
175
+ * the app's locales for every key of an exported `*_I18N_KEYS` array;
176
+ * i18n_key_functions.test.js holds it to the severities those checks
177
+ * write. The flags' words are schema_flags.js's, the plan's ops
178
+ * schema_import.js's. */
179
+ export const C_YUI_SCHEMA_EDITOR_I18N_KEYS = ["error", "warning"];
180
+
171
181
  /* The three topics a schema is stored in. Named once: everything
172
182
  * below addresses them by these constants and nothing composes the
173
183
  * strings again. */
@@ -1248,7 +1258,7 @@ function render(gobj)
1248
1258
  }
1249
1259
  if(state === "ST_IDLE") {
1250
1260
  show_notice(gobj, priv.load_error ? "cannot load the schemas" : "not connected",
1251
- priv.load_error, true);
1261
+ /* i18n: data */ priv.load_error, true);
1252
1262
  destroy_diagram(gobj);
1253
1263
  clear($body);
1254
1264
  return;
@@ -1352,16 +1362,16 @@ function render_treedbs(gobj, $body)
1352
1362
  let $cards = [];
1353
1363
 
1354
1364
  for(let treedb of priv.model.treedbs) {
1355
- let tags = [
1365
+ const from_c = treedb.c_schema_version !== undefined &&
1366
+ String(treedb.c_schema_version) !== String(treedb.schema_version);
1367
+ const tags = [
1356
1368
  {i18n: "topics", text: treedb.topics.length},
1357
1369
  {i18n: "version", text: treedb.schema_version === undefined
1358
- ? "?" : treedb.schema_version}
1370
+ ? "?" : treedb.schema_version},
1371
+ ...(from_c
1372
+ ? [{i18n: "from c", text: treedb.c_schema_version, style: "is-warning is-light"}]
1373
+ : [])
1359
1374
  ];
1360
- if(treedb.c_schema_version !== undefined &&
1361
- String(treedb.c_schema_version) !== String(treedb.schema_version)
1362
- ) {
1363
- tags.push({i18n: "from c", text: treedb.c_schema_version, style: "is-warning is-light"});
1364
- }
1365
1375
  $cards.push(open_card(gobj, "SCHEMA_TREEDB_CARD", treedb.id, "yi-database",
1366
1376
  tags, "EV_SELECT_TREEDB", {treedb: treedb.id}));
1367
1377
  }
@@ -1396,7 +1406,7 @@ function render_topics(gobj, $body)
1396
1406
  let treedb = current_treedb(gobj);
1397
1407
 
1398
1408
  if(!treedb) {
1399
- show_notice(gobj, "that treedb is not here any more", priv.treedb_id);
1409
+ show_notice(gobj, "that treedb is not here any more", /* i18n: data */ priv.treedb_id);
1400
1410
  return;
1401
1411
  }
1402
1412
 
@@ -1544,7 +1554,7 @@ function render_columns(gobj, $body)
1544
1554
  let topic = current_topic(gobj);
1545
1555
 
1546
1556
  if(!topic) {
1547
- show_notice(gobj, "that topic is not here any more", priv.topic_name);
1557
+ show_notice(gobj, "that topic is not here any more", /* i18n: data */ priv.topic_name);
1548
1558
  return;
1549
1559
  }
1550
1560
 
@@ -1805,7 +1815,7 @@ function render_diagram(gobj)
1805
1815
  if(!treedb) {
1806
1816
  destroy_diagram(gobj);
1807
1817
  clear($body);
1808
- show_notice(gobj, "that treedb is not here any more", priv.treedb_id);
1818
+ show_notice(gobj, "that treedb is not here any more", /* i18n: data */ priv.treedb_id);
1809
1819
  return;
1810
1820
  }
1811
1821
  if(!gclass_find_by_name("C_YUI_TREEDB_SCHEMA")) {
@@ -2425,7 +2435,8 @@ function open_report(gobj, findings, treedb_id)
2425
2435
  return ["tr", {class: `SCHEMA_REPORT_ROW SCHEMA_REPORT_${finding.severity.toUpperCase()}`}, [
2426
2436
  ["td", {class: "SCHEMA_REPORT_SEVERITY"}, [
2427
2437
  ["span", {class: `tag ${finding.severity === "error" ? "is-danger" : "is-warning"}` +
2428
- " is-light", i18n: finding.severity}, t(finding.severity)]
2438
+ " is-light", i18n: finding.severity},
2439
+ t(finding.severity)]
2429
2440
  ]],
2430
2441
  ["td", {class: "SCHEMA_REPORT_WHAT", i18n: finding.code}, t(finding.code)],
2431
2442
  ["td", {class: "SCHEMA_REPORT_TOPIC"}, text_node(finding.topic || "")],
@@ -3527,7 +3538,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
3527
3538
  return late_write_done(gobj, command, topic_name, data);
3528
3539
  }
3529
3540
  if(result < 0) {
3530
- return end_writes(gobj, comment || t("the treedb refused the write"));
3541
+ return end_writes(gobj, /* i18n: data */ comment || "the treedb refused the write");
3531
3542
  }
3532
3543
  priv.save_wrote = true;
3533
3544
  let record = null;
@@ -4229,7 +4240,8 @@ function confirm_column_delete(gobj, verdict)
4229
4240
  capped: "the records read hold no value in this column, but not all were read: the rest may hold values that no reader will show any more",
4230
4241
  unknown: "the records of this topic could not be read: if they hold values in this column, they keep them, but no reader shows them any more"
4231
4242
  };
4232
- let message = "delete this column?";
4243
+ const message = "delete this column?";
4244
+ let $warning = null;
4233
4245
  if(NOTES[verdict]) {
4234
4246
  let children = [
4235
4247
  ["p", {class: "CONFIRM_MSG yui-confirm-msg", i18n: message}, t(message)],
@@ -4242,9 +4254,9 @@ function confirm_column_delete(gobj, verdict)
4242
4254
  ["span", {class: "ml-1"}, String(c.scanned)]
4243
4255
  ]]);
4244
4256
  }
4245
- message = createElement2(["div", {class: "SCHEMA_COLUMN_DATA_WARNING"}, children]);
4257
+ $warning = createElement2(["div", {class: "SCHEMA_COLUMN_DATA_WARNING"}, children]);
4246
4258
  }
4247
- confirm_then(gobj, message, `${c.topic}.${c.col}`,
4259
+ confirm_then(gobj, $warning? $warning : message, `${c.topic}.${c.col}`,
4248
4260
  {what: "column", topic: c.topic, col: c.col});
4249
4261
  return 0;
4250
4262
  }
@@ -4482,7 +4494,9 @@ function ac_validate(gobj, event, kw, src)
4482
4494
  written_topics: Object.keys(priv.written),
4483
4495
  baseline: priv.baseline
4484
4496
  });
4485
- open_report(gobj, findings, treedb.id);
4497
+ /* The severities are C_YUI_SCHEMA_EDITOR_I18N_KEYS; the codes are the
4498
+ * checks' own keys, read where schema_validate.js writes them. */
4499
+ open_report(gobj, /* i18n: C_YUI_SCHEMA_EDITOR_I18N_KEYS */ findings, treedb.id);
4486
4500
  if(has_errors(findings)) {
4487
4501
  log_warning(`${gobj_short_name(gobj)}: ${treedb.id} would be refused as it is`);
4488
4502
  }
@@ -4566,7 +4580,9 @@ function ac_preview_import(gobj, event, kw, src)
4566
4580
 
4567
4581
  let plan = plan_import(treedb, incoming, {prune: !!kw.prune});
4568
4582
  priv.import_plan = plan;
4569
- render_import_plan(gobj, plan);
4583
+ /* Its writes are SCHEMA_IMPORT_I18N_KEYS; its conflicts are keys of
4584
+ * their own, read where schema_import.js writes them. */
4585
+ render_import_plan(gobj, /* i18n: SCHEMA_IMPORT_I18N_KEYS */ plan);
4570
4586
  return 0;
4571
4587
  }
4572
4588
 
@@ -281,7 +281,8 @@ function mt_start(gobj)
281
281
 
282
282
  build_item_index(gobj, config);
283
283
  instantiate_menus(gobj, config);
284
- build_toolbar(gobj, config);
284
+ /* The app's shell declaration: its toolbar's keys are read there */
285
+ build_toolbar(gobj, /* i18n: data */ config);
285
286
 
286
287
  /* lifecycle: "eager" — preinstantiate views that must exist from boot. */
287
288
  preinstantiate_eager_views(gobj);
@@ -1781,7 +1782,7 @@ function safe_id(s)
1781
1782
  ************************************************************/
1782
1783
  function build_toolbar(gobj, config)
1783
1784
  {
1784
- let tb = config && config.toolbar;
1785
+ const tb = config && config.toolbar;
1785
1786
  if(!tb || !is_array(tb.items)) {
1786
1787
  return;
1787
1788
  }
@@ -1807,7 +1808,7 @@ function build_toolbar(gobj, config)
1807
1808
  * which createElement2 maps to `data-i18n` on the rendered
1808
1809
  * element. Apps swap languages by calling
1809
1810
  * refresh_language(shell.$container, t) — no DOM rebuild here. */
1810
- let toolbar_aria = tb.aria_label || "Toolbar";
1811
+ const toolbar_aria = tb.aria_label || "toolbar";
1811
1812
  let $bar = createElement2(
1812
1813
  ["nav", {class: "yui-toolbar navbar",
1813
1814
  role: "navigation",
@@ -2060,8 +2061,8 @@ function build_toolbar_brand_item(gobj, it)
2060
2061
  function build_toolbar_avatar_item(gobj, it)
2061
2062
  {
2062
2063
  let priv = gobj.priv;
2063
- let aria_key = it.aria_label || it.name || it.id || "User menu";
2064
- let i18n_aria = it.aria_label || it.name || "User menu";
2064
+ const aria_key = it.aria_label || it.name || it.id || "user menu";
2065
+ const i18n_aria = it.aria_label || it.name || "user menu";
2065
2066
  let action_type = (it.action && it.action.type) || "";
2066
2067
  let attrs = {
2067
2068
  class: "navbar-item yui-toolbar-item yui-toolbar-avatar is-unselectable",
@@ -2151,8 +2152,8 @@ function attach_context_action(gobj, $item, it)
2151
2152
  function build_toolbar_connection_item(gobj, it)
2152
2153
  {
2153
2154
  let priv = gobj.priv;
2154
- let aria_key = it.aria_label || it.name || it.id || "backend connection";
2155
- let i18n_aria = it.aria_label || it.name || "backend connection";
2155
+ const aria_key = it.aria_label || it.name || it.id || "backend connection";
2156
+ const i18n_aria = it.aria_label || it.name || "backend connection";
2156
2157
  let attrs = {
2157
2158
  class: "navbar-item yui-toolbar-item yui-toolbar-conn " +
2158
2159
  "is-unselectable is-disconnected",
@@ -2328,8 +2329,8 @@ function open_toolbar_dropdown(gobj, item, action, $trigger)
2328
2329
  * (yui_shell_set_translator), re-apply it to the freshly built
2329
2330
  * $panel. Rebuilt per open ⇒ a later language switch is picked
2330
2331
  * up the next time the dropdown opens. */
2331
- let aria_key = item && (item.aria_label || item.name || item.id) || "Menu";
2332
- let i18n_aria = (item && (item.aria_label || item.name)) || "Menu";
2332
+ const aria_key = item && (item.aria_label || item.name || item.id) || "menu";
2333
+ const i18n_aria = (item && (item.aria_label || item.name)) || "menu";
2333
2334
  let $panel = createElement2(["div", {
2334
2335
  class: "yui-toolbar-dropdown-panel",
2335
2336
  role: "menu",
@@ -2341,7 +2342,7 @@ function open_toolbar_dropdown(gobj, item, action, $trigger)
2341
2342
  let raw_items = is_array(action.items) ? action.items : [];
2342
2343
  for(let i = 0; i < raw_items.length; i++) {
2343
2344
  let sub = raw_items[i];
2344
- let $row = build_dropdown_row(gobj, sub, i);
2345
+ let $row = build_dropdown_row(gobj, /* i18n: data */ sub, i); // the app's shell declaration
2345
2346
  if(!$row) {
2346
2347
  continue;
2347
2348
  }
@@ -513,7 +513,7 @@ function show_load_error(gobj, message)
513
513
  $container.insertBefore($err, $container.firstChild);
514
514
  }
515
515
  let treedb = gobj_read_attr(gobj, "treedb_name") || "";
516
- $err.textContent = (treedb ? `${treedb}: ` : "") + (message || "cannot load treedb");
516
+ $err.textContent = (treedb ? `${treedb}: ` : "") + (message || t("cannot load treedb"));
517
517
  }
518
518
 
519
519
  /************************************************************
@@ -671,6 +671,15 @@ const NODE_MODE_ICONS = {
671
671
  shape: 'yi-hexagon-nodes',
672
672
  };
673
673
 
674
+ /* The keys that reach t() as data: the labels of NODE_MODE_LABELS.
675
+ * Published for a consumer's validate-locales, which asks the app's
676
+ * locales for every key of an exported `*_I18N_KEYS` array. A view
677
+ * added to NODE_MODE_LABELS adds its label here
678
+ * (i18n_key_functions.test.js checks it). */
679
+ export const C_YUI_TREEDB_GRAPH_I18N_KEYS = [
680
+ 'full', 'compact', 'shape'
681
+ ];
682
+
674
683
  /* The pressed view and the labels toggle, from the attrs. The
675
684
  * toggle only means something on figures, so it is disabled
676
685
  * elsewhere.
@@ -1219,7 +1228,7 @@ function open_json_viewer(gobj)
1219
1228
  close_json_viewer(gobj);
1220
1229
  return;
1221
1230
  }
1222
- priv.json_modal = yui_shell_show_modal(shell, $box, {
1231
+ priv.json_modal = yui_shell_show_modal(shell, /* i18n: data */ $box, {
1223
1232
  dialog: true,
1224
1233
  logical_class: "TREEDB_JSON_SHEET",
1225
1234
  title_prefix: priv.treedb_name,
@@ -1352,8 +1361,7 @@ function answer_print_tranger_refused(gobj, path, answer)
1352
1361
  gobj_send_event(jv, "EV_SUBTREE_ERROR",
1353
1362
  Object.assign({path: path}, answer), gobj);
1354
1363
  } else {
1355
- yui_shell_show_error(yui_shell_of(gobj),
1356
- answer.i18n || answer.error, {t: t});
1364
+ yui_shell_show_error(yui_shell_of(gobj), answer.i18n || answer.error, {t: t});
1357
1365
  }
1358
1366
  }
1359
1367
 
@@ -1404,7 +1412,7 @@ function request_print_tranger(gobj, path)
1404
1412
  }, gobj);
1405
1413
  if(ret) {
1406
1414
  log_error(ret);
1407
- answer_print_tranger_refused(gobj, path, {error: refusal_text(ret)});
1415
+ answer_print_tranger_refused(gobj, path, {error: /* i18n: data */ refusal_text(ret)});
1408
1416
  return;
1409
1417
  }
1410
1418
  if(path) {
@@ -2167,7 +2175,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
2167
2175
  {path: path, error: comment || "print-tranger failed"}, gobj);
2168
2176
  } else {
2169
2177
  yui_shell_show_error(yui_shell_of(gobj),
2170
- comment || "print-tranger failed", {t: t});
2178
+ /* i18n: data */ comment || "print-tranger failed", {t: t});
2171
2179
  }
2172
2180
  return 0;
2173
2181
  }
@@ -2184,9 +2192,9 @@ function ac_mt_command_answer(gobj, event, kw, src)
2184
2192
  /* The schema couldn't load (not a treedb, no authz for it, backend
2185
2193
  * down…). Show an inline banner in the view rather than a blocking
2186
2194
  * app-modal that wedges the whole SPA behind an empty tab. */
2187
- show_load_error(gobj, t(comment));
2195
+ show_load_error(gobj, t(/* i18n: data */ comment));
2188
2196
  } else {
2189
- yui_shell_show_error(yui_shell_of(gobj), comment, {t: t});
2197
+ yui_shell_show_error(yui_shell_of(gobj), /* i18n: data */ comment, {t: t});
2190
2198
  }
2191
2199
  // HACK don't return for non-descs, pass errors when need it.
2192
2200
  }
@@ -3099,7 +3107,7 @@ function refresh_legend(gobj)
3099
3107
  hidden? `${entry.total}` : `${entry.visible}/${entry.total}`]
3100
3108
  );
3101
3109
 
3102
- let body_key = is_main? 'main topic' : (hidden? 'show topic' : 'hide topic');
3110
+ const body_key = is_main? 'main topic' : (hidden? 'show topic' : 'hide topic');
3103
3111
 
3104
3112
  /* Built as an object first: an attribute that is `undefined`
3105
3113
  * is still SET by createElement2 (to the string "undefined"),
@@ -68,6 +68,7 @@ import {
68
68
  yui_graph_update_zoom,
69
69
  } from "./yui_graph_camera.js";
70
70
  import {ensure_pinch_zoom_patch} from "./g6_touch_gestures.js";
71
+ import {keep_canvas_tab_order} from "./g6_canvas_tab_order.js";
71
72
  import {col_key_roles} from "./treedb_topic_keys.js";
72
73
 
73
74
  import {getStrokeColor} from "./lib_graph.js";
@@ -659,6 +660,10 @@ function build_graph(gobj)
659
660
 
660
661
  graph.setTheme(dark ? "dark" : "light");
661
662
 
663
+ /* Tab goes from the graph to its own buttons: the main canvas in
664
+ * the Tab order, the other layers out (g6_canvas_tab_order.js). */
665
+ keep_canvas_tab_order(gobj, graph);
666
+
662
667
  /* The readout follows ANY camera change, the wheel included -- a
663
668
  * wheel notch passes through no action of ours. */
664
669
  graph.on('aftertransform', () => {
@@ -1512,7 +1512,7 @@ function create_tabulator(gobj)
1512
1512
 
1513
1513
  let colDef = {
1514
1514
  title: col_label(col, gobj_read_str_attr(gobj, "topic_name")),
1515
- titleFormatter: col_title_formatter(gobj, col),
1515
+ titleFormatter: col_title_formatter(gobj, /* i18n: data */ col), // a column of the schema
1516
1516
  field: col.id,
1517
1517
  sorter: sorter,
1518
1518
  hozAlign: hozAlign,
@@ -2428,7 +2428,7 @@ function open_form_dialog(gobj, mode, record)
2428
2428
  * while "new"/"edit"/"view" is what only this header can tell you.
2429
2429
  * All three are keys this library already uses. */
2430
2430
  let pkey = desc.pkey || "id";
2431
- let title = (mode === "create")? "new" : ((mode === "view")? "view" : "edit");
2431
+ const title = (mode === "create")? "new" : ((mode === "view")? "view" : "edit");
2432
2432
  let title_prefix = "";
2433
2433
  if(mode !== "create") {
2434
2434
  let rid = record ? String(record[pkey] ?? "") : "";
@@ -2548,7 +2548,7 @@ function form_may_close(gobj)
2548
2548
  * a second key to define — and the one that was here had never
2549
2549
  * been defined in any locale, which renders as the English
2550
2550
  * sentence itself, in every language, for ever. */
2551
- confirm_then(gobj, kw.warning || "all changes will be lost",
2551
+ confirm_then(gobj, /* i18n: C_YUI_FORM_I18N_KEYS */ kw.warning || "all changes will be lost",
2552
2552
  {what: "close_form"});
2553
2553
  return false;
2554
2554
  }
@@ -2676,7 +2676,7 @@ function open_schema_dialog(gobj)
2676
2676
  /* Title split in two halves: the topic name is DATA (never
2677
2677
  * translated) and "schema" is the kind (carries its i18n key), so
2678
2678
  * the header re-translates on a language switch. */
2679
- let presented = present_json_popup(gobj, $box, {
2679
+ let presented = present_json_popup(gobj, /* i18n: data */ $box, { // the viewer's own DOM
2680
2680
  name: "schemawin_" + clean_name(gobj_name(gobj)),
2681
2681
  logical_class: "TREEDB_SCHEMA",
2682
2682
  title_prefix: gobj_read_str_attr(gobj, "topic_name"),
@@ -2803,7 +2803,7 @@ function open_cell_json_dialog(gobj, row_id, col_id)
2803
2803
  * i18n key — the shared `col.id` one, so the header re-translates on a
2804
2804
  * language switch and reads as the raw id where the app defines no key
2805
2805
  * (exactly what an untranslated column header already shows). */
2806
- let presented = present_json_popup(gobj, $box, {
2806
+ let presented = present_json_popup(gobj, /* i18n: data */ $box, { // the viewer's own DOM
2807
2807
  name: "cellwin_" + clean_name(gobj_name(gobj)),
2808
2808
  logical_class: "TREEDB_CELL_JSON",
2809
2809
  title_prefix: String(row_id),
@@ -4599,7 +4599,7 @@ function ac_show_hook_data(gobj, event, kw, src)
4599
4599
  String(kw.row_id), kw.col_id);
4600
4600
  return 0;
4601
4601
  }
4602
- open_hook_choice_dialog(gobj, targets, String(kw.row_id), kw.col_id);
4602
+ open_hook_choice_dialog(gobj, /* i18n: data */ targets, String(kw.row_id), kw.col_id); // the schema's topics
4603
4603
  return 0;
4604
4604
  }
4605
4605
 
@@ -4681,7 +4681,7 @@ function ac_refresh(gobj, event, kw, src)
4681
4681
  ************************************************************/
4682
4682
  function ac_show_cell_json(gobj, event, kw, src)
4683
4683
  {
4684
- open_cell_json_dialog(gobj, kw.row_id, kw.col_id);
4684
+ open_cell_json_dialog(gobj, kw.row_id, /* i18n: data */ kw.col_id);
4685
4685
 
4686
4686
  return 0;
4687
4687
  }
@@ -4695,7 +4695,7 @@ function ac_show_cell_json(gobj, event, kw, src)
4695
4695
  ************************************************************/
4696
4696
  function ac_show_cell_file(gobj, event, kw, src)
4697
4697
  {
4698
- open_cell_file_dialog(gobj, kw.row_id, kw.col_id);
4698
+ open_cell_file_dialog(gobj, kw.row_id, /* i18n: data */ kw.col_id);
4699
4699
 
4700
4700
  return 0;
4701
4701
  }
@@ -5291,7 +5291,7 @@ function open_table_json_dialog(gobj, records)
5291
5291
  }
5292
5292
 
5293
5293
  /* The topic is DATA (the prefix), the title carries its key. */
5294
- let presented = present_json_popup(gobj, $box, {
5294
+ let presented = present_json_popup(gobj, /* i18n: data */ $box, { // the viewer's own DOM
5295
5295
  name: "tablejsonwin_" + clean_name(gobj_name(gobj)),
5296
5296
  logical_class: "TREEDB_TABLE_JSON",
5297
5297
  title_prefix: gobj_read_str_attr(gobj, "topic_name"),