@yuneta/gobj-ui 7.26.26 → 7.26.28

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 (44) hide show
  1. package/README.md +473 -19
  2. package/dist/gobj-ui.cjs.js +1100 -231
  3. package/dist/gobj-ui.es.js +1101 -232
  4. package/package.json +1 -1
  5. package/scripts/validate-locales.mjs +2334 -275
  6. package/src/c_g6_nodes_tree.js +1143 -260
  7. package/src/c_yui_form.js +11 -1
  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 +17 -0
  11. package/src/c_yui_json_graph.js +5 -0
  12. package/src/c_yui_nav.js +9 -6
  13. package/src/c_yui_pager.js +1 -1
  14. package/src/c_yui_period.js +11 -4
  15. package/src/c_yui_schema_editor.js +19 -7
  16. package/src/c_yui_shell.js +7 -7
  17. package/src/c_yui_treedb_graph.js +15 -6
  18. package/src/c_yui_treedb_schema.js +5 -0
  19. package/src/c_yui_treedb_topic_with_form.js +10 -10
  20. package/src/c_yui_treedb_topics.js +6 -6
  21. package/src/c_yui_window.js +2 -2
  22. package/src/c_yui_wizard.js +8 -1
  23. package/src/form_json_field.js +7 -0
  24. package/src/g6_canvas_tab_order.js +84 -0
  25. package/src/g6_canvas_tab_order.wiring.test.js +232 -0
  26. package/src/g6_nodes_tree_controls.test.js +547 -0
  27. package/src/g6_nodes_tree_focus.wiring.test.js +1360 -0
  28. package/src/g6_nodes_tree_i18n_keys.test.js +275 -0
  29. package/src/i18n_key_functions.js +146 -0
  30. package/src/i18n_key_functions.test.js +1204 -0
  31. package/src/lib_graph.js +38 -1
  32. package/src/nav_backbar_name.wiring.test.js +82 -0
  33. package/src/schema_flags.js +47 -0
  34. package/src/schema_import.js +7 -0
  35. package/src/shell_modals.js +20 -16
  36. package/src/shell_route_map.js +12 -2
  37. package/src/wizard_helpers.js +8 -0
  38. package/src/yui_dev.js +44 -33
  39. package/src/yui_dev_controls.test.js +174 -0
  40. package/src/yui_gclass_view.js +2 -2
  41. package/src/yui_graph_camera.js +1 -1
  42. package/src/yui_install.js +10 -8
  43. package/src/yui_table_select.js +2 -2
  44. package/src/yui_time.js +18 -3
package/src/c_yui_form.js CHANGED
@@ -2547,11 +2547,21 @@ function field_validation_message($input)
2547
2547
  {
2548
2548
  let key = field_validation_key($input);
2549
2549
  if(key) {
2550
- return t(key);
2550
+ return t(/* i18n: C_YUI_FORM_I18N_KEYS, FORM_JSON_FIELD_I18N_KEYS */ key);
2551
2551
  }
2552
2552
  return $input.validationMessage;
2553
2553
  }
2554
2554
 
2555
+ /* The keys this form hands on as data: the one field_validation_key()
2556
+ * returns, which reaches t() by a variable, and the warning of a close
2557
+ * it vetoes (`kw.warning`), which its HOST shows. Published for a
2558
+ * consumer's validate-locales, which asks the app's locales for every
2559
+ * key of an exported `*_I18N_KEYS` array; i18n_key_functions.test.js
2560
+ * holds it to what field_validation_key() returns and to every
2561
+ * `.warning =` of this module. (The json editor's own key is published
2562
+ * by form_json_field.js.) */
2563
+ export const C_YUI_FORM_I18N_KEYS = ["this field is required", "all changes will be lost"];
2564
+
2555
2565
  /************************************************************
2556
2566
  * The i18n key of the message, when the message is ours ("" when
2557
2567
  * 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', () => {
@@ -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
@@ -268,7 +268,7 @@ function build_ui(gobj)
268
268
  if(!$container.hasAttribute("aria-label")) {
269
269
  /* A key, translated by the shell's translator: the bare
270
270
  * menu_id read out "secondary.main.system". */
271
- let label = gobj_read_attr(gobj, "nav_label") || "navigation";
271
+ const label = gobj_read_attr(gobj, "nav_label") || "navigation";
272
272
  $container.setAttribute("aria-label", t(label));
273
273
  $container.setAttribute("data-i18n-aria-label", label);
274
274
  }
@@ -527,16 +527,19 @@ function render_backbar(gobj)
527
527
  if(label) {
528
528
  label_attrs.i18n = label;
529
529
  }
530
+ /* Named by the label when the nav has one, else by "back" -- through
531
+ * t() and with its key, or it reads the raw key in every language. */
532
+ const name_key = label || "back";
530
533
  let a_attrs = {
531
534
  class: "yui-nav-item yui-nav-back",
532
535
  href: back_route ? "#" + back_route : "#",
533
536
  "data-item-id": "__index__",
534
537
  "data-route": back_route,
535
- "aria-label": label || "back"
538
+ title: t(name_key),
539
+ "data-i18n-title": name_key,
540
+ "aria-label": t(name_key),
541
+ "data-i18n-aria-label": name_key
536
542
  };
537
- if(label) {
538
- a_attrs["data-i18n-aria-label"] = label;
539
- }
540
543
  return createElement2(
541
544
  ["div", {},
542
545
  ["a", a_attrs, [
@@ -558,7 +561,7 @@ function render_drawer(gobj, items)
558
561
  wrap.className = "yui-drawer";
559
562
  wrap.setAttribute("role", "dialog");
560
563
  wrap.setAttribute("aria-modal", "true");
561
- let nav_label = gobj_read_attr(gobj, "nav_label") || "navigation";
564
+ const nav_label = gobj_read_attr(gobj, "nav_label") || "navigation";
562
565
  wrap.setAttribute("aria-label", t(nav_label));
563
566
  wrap.setAttribute("data-i18n-aria-label", nav_label);
564
567
 
@@ -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
  ***************************************************************/
@@ -379,7 +386,7 @@ function mode_name(mode)
379
386
  return t(mode.roll.label || mode.id);
380
387
  }
381
388
  if(mode.kind === "bucket") {
382
- return period_name(mode.spec, t);
389
+ return period_name(/* i18n: YUI_TIME_I18N_KEYS */ mode.spec, t);
383
390
  }
384
391
  return t(mode.id); /* span / custom */
385
392
  }
@@ -565,7 +572,7 @@ function build_mode_button(gobj, mode)
565
572
  {
566
573
  /* The name is COMPOSED for a bucket (period_name), so it carries no
567
574
  * key: ac_language_changed() rewrites the text and both names. */
568
- let name = mode_name(mode);
575
+ let name = mode_name(/* i18n: C_YUI_PERIOD_I18N_KEYS, YUI_TIME_I18N_KEYS */ mode);
569
576
  let $btn = createElement2(
570
577
  ["button", {class: `button YUI_PERIOD_MODE YUI_PERIOD_MODE_${mode.id.toUpperCase()}`,
571
578
  type: "button", title: name, "aria-label": name},
@@ -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. */
@@ -373,7 +383,7 @@ function mt_destroy(gobj)
373
383
  function refuse_bad_name(gobj, $message)
374
384
  {
375
385
  mark_name_field(gobj, true);
376
- yui_shell_show_error(yui_shell_of(gobj), $message, {t: t});
386
+ yui_shell_show_error(yui_shell_of(gobj), /* i18n: data */ $message, {t: t});
377
387
  return -1;
378
388
  }
379
389
 
@@ -1911,7 +1921,7 @@ function open_dialog(gobj, $content, title, logical, title_prefix)
1911
1921
  priv.dialog_gen = priv.model_gen;
1912
1922
  priv.dialog_kind = logical;
1913
1923
  priv.dialog_content = $content;
1914
- priv.dialog = yui_shell_show_modal(yui_shell_of(gobj), $content, {
1924
+ priv.dialog = yui_shell_show_modal(yui_shell_of(gobj), /* i18n: data */ $content, {
1915
1925
  dialog: true,
1916
1926
  title: title,
1917
1927
  title_prefix: title_prefix || "",
@@ -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: /* i18n: C_YUI_SCHEMA_EDITOR_I18N_KEYS */ finding.severity},
2439
+ t(/* i18n: C_YUI_SCHEMA_EDITOR_I18N_KEYS */ 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? /* i18n: data */ $warning : message, `${c.topic}.${c.col}`,
4248
4260
  {what: "column", topic: c.topic, col: c.col});
4249
4261
  return 0;
4250
4262
  }
@@ -1807,7 +1807,7 @@ function build_toolbar(gobj, config)
1807
1807
  * which createElement2 maps to `data-i18n` on the rendered
1808
1808
  * element. Apps swap languages by calling
1809
1809
  * refresh_language(shell.$container, t) — no DOM rebuild here. */
1810
- let toolbar_aria = tb.aria_label || "Toolbar";
1810
+ const toolbar_aria = tb.aria_label || "toolbar";
1811
1811
  let $bar = createElement2(
1812
1812
  ["nav", {class: "yui-toolbar navbar",
1813
1813
  role: "navigation",
@@ -2060,8 +2060,8 @@ function build_toolbar_brand_item(gobj, it)
2060
2060
  function build_toolbar_avatar_item(gobj, it)
2061
2061
  {
2062
2062
  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";
2063
+ const aria_key = it.aria_label || it.name || it.id || "user menu";
2064
+ const i18n_aria = it.aria_label || it.name || "user menu";
2065
2065
  let action_type = (it.action && it.action.type) || "";
2066
2066
  let attrs = {
2067
2067
  class: "navbar-item yui-toolbar-item yui-toolbar-avatar is-unselectable",
@@ -2151,8 +2151,8 @@ function attach_context_action(gobj, $item, it)
2151
2151
  function build_toolbar_connection_item(gobj, it)
2152
2152
  {
2153
2153
  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";
2154
+ const aria_key = it.aria_label || it.name || it.id || "backend connection";
2155
+ const i18n_aria = it.aria_label || it.name || "backend connection";
2156
2156
  let attrs = {
2157
2157
  class: "navbar-item yui-toolbar-item yui-toolbar-conn " +
2158
2158
  "is-unselectable is-disconnected",
@@ -2328,8 +2328,8 @@ function open_toolbar_dropdown(gobj, item, action, $trigger)
2328
2328
  * (yui_shell_set_translator), re-apply it to the freshly built
2329
2329
  * $panel. Rebuilt per open ⇒ a later language switch is picked
2330
2330
  * 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";
2331
+ const aria_key = item && (item.aria_label || item.name || item.id) || "menu";
2332
+ const i18n_aria = (item && (item.aria_label || item.name)) || "menu";
2333
2333
  let $panel = createElement2(["div", {
2334
2334
  class: "yui-toolbar-dropdown-panel",
2335
2335
  role: "menu",
@@ -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,
@@ -1353,7 +1362,7 @@ function answer_print_tranger_refused(gobj, path, answer)
1353
1362
  Object.assign({path: path}, answer), gobj);
1354
1363
  } else {
1355
1364
  yui_shell_show_error(yui_shell_of(gobj),
1356
- answer.i18n || answer.error, {t: t});
1365
+ /* i18n: data */ answer.i18n || answer.error, {t: t});
1357
1366
  }
1358
1367
  }
1359
1368
 
@@ -2167,7 +2176,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
2167
2176
  {path: path, error: comment || "print-tranger failed"}, gobj);
2168
2177
  } else {
2169
2178
  yui_shell_show_error(yui_shell_of(gobj),
2170
- comment || "print-tranger failed", {t: t});
2179
+ /* i18n: data */ comment || "print-tranger failed", {t: t});
2171
2180
  }
2172
2181
  return 0;
2173
2182
  }
@@ -2186,7 +2195,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
2186
2195
  * app-modal that wedges the whole SPA behind an empty tab. */
2187
2196
  show_load_error(gobj, t(comment));
2188
2197
  } else {
2189
- yui_shell_show_error(yui_shell_of(gobj), comment, {t: t});
2198
+ yui_shell_show_error(yui_shell_of(gobj), /* i18n: data */ comment, {t: t});
2190
2199
  }
2191
2200
  // HACK don't return for non-descs, pass errors when need it.
2192
2201
  }
@@ -3099,7 +3108,7 @@ function refresh_legend(gobj)
3099
3108
  hidden? `${entry.total}` : `${entry.visible}/${entry.total}`]
3100
3109
  );
3101
3110
 
3102
- let body_key = is_main? 'main topic' : (hidden? 'show topic' : 'hide topic');
3111
+ const body_key = is_main? 'main topic' : (hidden? 'show topic' : 'hide topic');
3103
3112
 
3104
3113
  /* Built as an object first: an attribute that is `undefined`
3105
3114
  * 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', () => {
@@ -325,7 +325,7 @@ function present_json_popup(gobj, $box, opts)
325
325
 
326
326
  if(is_mobile()) {
327
327
  return {
328
- modal: yui_shell_show_modal(shell, $box, {
328
+ modal: yui_shell_show_modal(shell, /* i18n: data */ $box, {
329
329
  dialog: true,
330
330
  logical_class: opts.logical_class + "_SHEET",
331
331
  title_prefix: opts.title_prefix,
@@ -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] ?? "") : "";
@@ -2496,7 +2496,7 @@ function open_form_dialog(gobj, mode, record)
2496
2496
  * are wired by the shell. `before_close` preserves the unsaved-changes
2497
2497
  * guard — it vetoes the dismiss and, on confirm, closes the modal
2498
2498
  * itself. `on_close` tears the hosted form child down. */
2499
- priv.form_modal = yui_shell_show_modal(shell, $body, {
2499
+ priv.form_modal = yui_shell_show_modal(shell, /* i18n: data */ $body, {
2500
2500
  dialog: true,
2501
2501
  logical_class: "TREEDB_FORM_SHEET",
2502
2502
  title_prefix: title_prefix,
@@ -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
  }
@@ -2807,7 +2807,7 @@ function open_cell_json_dialog(gobj, row_id, col_id)
2807
2807
  name: "cellwin_" + clean_name(gobj_name(gobj)),
2808
2808
  logical_class: "TREEDB_CELL_JSON",
2809
2809
  title_prefix: String(row_id),
2810
- title: col_id,
2810
+ title: /* i18n: data */ col_id,
2811
2811
  icon: "yi-js-square",
2812
2812
  forget_window: function() {
2813
2813
  priv.cell_json_win = null;
@@ -3019,7 +3019,7 @@ function open_cell_file_dialog(gobj, row_id, col_id)
3019
3019
  name: "filewin_" + clean_name(gobj_name(gobj)),
3020
3020
  logical_class: "TREEDB_CELL_FILE",
3021
3021
  title_prefix: String(row_id),
3022
- title: col_id,
3022
+ title: /* i18n: data */ col_id,
3023
3023
  icon: "yi-eye",
3024
3024
  forget_window: function() {
3025
3025
  priv.cell_file_win = null;
@@ -3167,7 +3167,7 @@ function open_hook_choice_dialog(gobj, targets, parent_id, hook)
3167
3167
  }];
3168
3168
  })]
3169
3169
  );
3170
- priv.hook_choice_modal = yui_shell_show_modal(shell, $body, {
3170
+ priv.hook_choice_modal = yui_shell_show_modal(shell, /* i18n: data */ $body, {
3171
3171
  dialog: true,
3172
3172
  logical_class: "TREEDB_HOOK_CHOICE_SHEET",
3173
3173
  title: "choose a topic",
@@ -4132,7 +4132,7 @@ function ac_delete_rows(gobj, event, kw, src)
4132
4132
  return 0;
4133
4133
  }
4134
4134
 
4135
- confirm_then(gobj, build_delete_question(gobj, rows),
4135
+ confirm_then(gobj, /* i18n: data */ build_delete_question(gobj, rows),
4136
4136
  {what: "delete_selection", ids: yui_row_ids(tabulator, rows)});
4137
4137
 
4138
4138
  } else {
@@ -4140,7 +4140,7 @@ function ac_delete_rows(gobj, event, kw, src)
4140
4140
  * Delete one row
4141
4141
  * {row: }
4142
4142
  *----------------------------*/
4143
- confirm_then(gobj, build_delete_question(gobj, kw.row),
4143
+ confirm_then(gobj, /* i18n: data */ build_delete_question(gobj, kw.row),
4144
4144
  {what: "delete_row", ids: yui_row_ids(tabulator, [kw.row])});
4145
4145
  }
4146
4146
 
@@ -5078,7 +5078,7 @@ function ac_open_columns(gobj, event, kw, src)
5078
5078
  ));
5079
5079
  }
5080
5080
 
5081
- yui_shell_show_modal(shell, $list, {
5081
+ yui_shell_show_modal(shell, /* i18n: data */ $list, {
5082
5082
  dialog: true,
5083
5083
  logical_class: "TREEDB_COLUMNS_DIALOG",
5084
5084
  title_prefix: gobj_read_str_attr(gobj, "topic_name"),
@@ -565,7 +565,7 @@ function show_load_error(gobj, message)
565
565
  $container.insertBefore($err, $container.firstChild);
566
566
  }
567
567
  let treedb = gobj_read_attr(gobj, "treedb_name") || "";
568
- $err.textContent = (treedb ? `${treedb}: ` : "") + (message || "cannot load treedb");
568
+ $err.textContent = (treedb ? `${treedb}: ` : "") + (message || t("cannot load treedb"));
569
569
  }
570
570
 
571
571
  /************************************************************
@@ -2075,7 +2075,7 @@ function open_json_viewer(gobj, opts)
2075
2075
  close_json_viewer(gobj);
2076
2076
  return;
2077
2077
  }
2078
- priv.json_modal = yui_shell_show_modal(shell, $box, {
2078
+ priv.json_modal = yui_shell_show_modal(shell, /* i18n: data */ $box, {
2079
2079
  dialog: true,
2080
2080
  logical_class: "TREEDB_JSON_SHEET",
2081
2081
  title_prefix: priv.treedb_name,
@@ -2224,7 +2224,7 @@ function answer_print_tranger_refused(gobj, path, answer)
2224
2224
  Object.assign({path: path}, answer), gobj);
2225
2225
  } else {
2226
2226
  yui_shell_show_error(yui_shell_of(gobj),
2227
- answer.i18n || answer.error, {t: t});
2227
+ /* i18n: data */ answer.i18n || answer.error, {t: t});
2228
2228
  }
2229
2229
  }
2230
2230
 
@@ -2386,7 +2386,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
2386
2386
  {path: path, error: comment || "print-tranger failed"}, gobj);
2387
2387
  } else {
2388
2388
  yui_shell_show_error(yui_shell_of(gobj),
2389
- comment || "print-tranger failed", {t: t});
2389
+ /* i18n: data */ comment || "print-tranger failed", {t: t});
2390
2390
  }
2391
2391
  return 0;
2392
2392
  }
@@ -2411,7 +2411,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
2411
2411
  }
2412
2412
  if(result < 0) {
2413
2413
  yui_shell_show_error(yui_shell_of(gobj),
2414
- comment || "schema-file failed", {t: t});
2414
+ /* i18n: data */ comment || "schema-file failed", {t: t});
2415
2415
  return 0;
2416
2416
  }
2417
2417
  gobj_send_event(jv, "EV_SET_JSON", {json: data}, gobj);
@@ -2461,7 +2461,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
2461
2461
  * app-modal that wedges the whole SPA behind an empty tab. */
2462
2462
  show_load_error(gobj, t(comment));
2463
2463
  } else {
2464
- yui_shell_show_error(yui_shell_of(gobj), comment, {t: t});
2464
+ yui_shell_show_error(yui_shell_of(gobj), /* i18n: data */ comment, {t: t});
2465
2465
  /* A refused write leaves the TABLE showing what the operator
2466
2466
  * typed and the store holding what it had. That is tolerable
2467
2467
  * for a form (it stays open on the values it failed with) and
@@ -836,7 +836,7 @@ function close_window(gobj)
836
836
  return {state: "vetoed"};
837
837
  }
838
838
  const answer = yui_shell_confirm_yesnocancel(
839
- yui_shell_of(gobj), kw_close.warning,
839
+ yui_shell_of(gobj), /* i18n: data */ kw_close.warning,
840
840
  {t: t, yes_label: "yes", no_label: "no", cancel_label: "cancel"}
841
841
  ).then(function(answer) {
842
842
  if(answer !== "yes") {
@@ -909,7 +909,7 @@ function set_max_icon(gobj, maximized)
909
909
  }
910
910
  let $btn = $container.querySelector('.wc-max');
911
911
  if($btn) {
912
- let key = maximized ? 'restore' : 'maximize';
912
+ const key = maximized ? 'restore' : 'maximize';
913
913
  $btn.innerHTML = maximized ? WC_RESTORE : WC_MAX;
914
914
  $btn.setAttribute('title', t(key));
915
915
  $btn.setAttribute('data-i18n-title', key);
@@ -75,6 +75,12 @@ SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "HTMLElement roo
75
75
  SDATA_END()
76
76
  ];
77
77
 
78
+ /* The default keys of the three buttons, which reach t() through the
79
+ * attrs: published for a consumer's validate-locales, which asks the
80
+ * app's locales for every key of an exported `*_I18N_KEYS` array. A
81
+ * host that names its own buttons asks for its own keys besides. */
82
+ export const C_YUI_WIZARD_I18N_KEYS = ["confirm", "next", "back"];
83
+
78
84
  let PRIVATE_DATA = {
79
85
  steps: null,
80
86
  idx: 0,
@@ -267,7 +273,8 @@ function render_chrome(gobj)
267
273
 
268
274
  $back.classList.toggle('is-hidden', !model.show_back);
269
275
 
270
- $plabel.setAttribute('data-i18n', model.primary_label);
276
+ $plabel.setAttribute('data-i18n',
277
+ /* i18n: WIZARD_HELPERS_I18N_KEYS, C_YUI_WIZARD_I18N_KEYS */ model.primary_label);
271
278
  $plabel.textContent = t(model.primary_label);
272
279
  $primary.setAttribute('title', t(model.primary_label));
273
280
  $primary.setAttribute('data-i18n-title', model.primary_label);
@@ -64,6 +64,13 @@ function value_fits(value, shape)
64
64
  }
65
65
  }
66
66
 
67
+ /* The key parse_json_field() returns as data (its `error`), which the
68
+ * form hands to t(): published for a consumer's validate-locales, which
69
+ * asks the app's locales for every key of an exported `*_I18N_KEYS`
70
+ * array. i18n_key_functions.test.js holds it to every `error:` this
71
+ * module writes. */
72
+ export const FORM_JSON_FIELD_I18N_KEYS = ["invalid json"];
73
+
67
74
  /************************************************************
68
75
  * Parse what a json field holds.
69
76
  *