@yuneta/gobj-ui 7.26.27 → 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 +458 -22
  2. package/dist/gobj-ui.cjs.js +1063 -216
  3. package/dist/gobj-ui.es.js +1064 -217
  4. package/package.json +1 -1
  5. package/scripts/validate-locales.mjs +2269 -316
  6. package/src/c_g6_nodes_tree.js +1086 -253
  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 +241 -5
  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 +16 -3
  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
@@ -0,0 +1,84 @@
1
+ /***********************************************************************
2
+ * g6_canvas_tab_order.js
3
+ *
4
+ * The canvases of a G6 graph in the page's Tab order: the main
5
+ * one in its place, the other layers out of it.
6
+ *
7
+ * G6 stacks four canvases (background, main, label, transient)
8
+ * and gives each of them `tabIndex = 1` (configCanvasDom, when it
9
+ * builds the Canvas and again on a renderer change). A POSITIVE
10
+ * tabindex goes before every control of the page: Tab from a
11
+ * graph went to its other canvases, then to the top of the page
12
+ * and through every control there, and only then to the graph's
13
+ * own buttons. The main canvas -- the one G6 listens on, and the
14
+ * one a view puts the keyboard on -- takes 0, so Tab goes from it
15
+ * to the graph's toolbars and buttons in DOM order; the other
16
+ * layers receive nothing (`pointer-events: none`) and take -1.
17
+ *
18
+ * Every view that creates a G6 Graph calls
19
+ * keep_canvas_tab_order() right after `new Graph()`: the order is
20
+ * put right each time G6 writes the 1 -- once the canvas is built
21
+ * (on the first render(), whichever function of the view calls
22
+ * it), and after a renderer change. One place, so a view with
23
+ * several render() calls cannot miss the one that builds the
24
+ * canvas.
25
+ *
26
+ * Copyright (c) 2026, ArtGins.
27
+ * All Rights Reserved.
28
+ ***********************************************************************/
29
+ import {
30
+ gobj_short_name,
31
+ log_error,
32
+ } from "@yuneta/gobj-js";
33
+
34
+ import {GraphEvent} from "@antv/g6";
35
+
36
+ /************************************************************
37
+ * Main canvas 0, every other layer -1. Returns 0, or -1 with
38
+ * the cause logged.
39
+ ************************************************************/
40
+ function set_canvas_tab_order(gobj, graph)
41
+ {
42
+ let canvas = graph? graph.getCanvas() : null;
43
+
44
+ if(!canvas || typeof canvas.getLayers !== "function") {
45
+ log_error(`${gobj_short_name(gobj)}: no canvas to set the Tab order of`);
46
+ return -1;
47
+ }
48
+
49
+ let ret = 0;
50
+ let layers = canvas.getLayers() || {};
51
+ for(let name of Object.keys(layers)) {
52
+ let $el = null;
53
+ try {
54
+ $el = layers[name].getContextService().getDomElement();
55
+ } catch(e) {
56
+ $el = null;
57
+ }
58
+ if(!$el) {
59
+ log_error(`${gobj_short_name(gobj)}: canvas layer '${name}' has no element`);
60
+ ret = -1;
61
+ continue;
62
+ }
63
+ $el.tabIndex = (name === "main")? 0 : -1;
64
+ }
65
+ return ret;
66
+ }
67
+
68
+ /************************************************************
69
+ * Keep the order for the life of `graph`: on every moment G6
70
+ * writes its own.
71
+ ************************************************************/
72
+ function keep_canvas_tab_order(gobj, graph)
73
+ {
74
+ for(let ev of [GraphEvent.AFTER_CANVAS_INIT, GraphEvent.AFTER_RENDERER_CHANGE]) {
75
+ graph.on(ev, () => {
76
+ set_canvas_tab_order(gobj, graph);
77
+ });
78
+ }
79
+ }
80
+
81
+ export {
82
+ set_canvas_tab_order,
83
+ keep_canvas_tab_order,
84
+ };
@@ -0,0 +1,232 @@
1
+ /***********************************************************************
2
+ * g6_canvas_tab_order.wiring.test.js
3
+ *
4
+ * Every view that draws a G6 graph puts its canvases in the page's
5
+ * Tab order the same way (g6_canvas_tab_order.js): the main canvas
6
+ * 0, the other layers -1 (REV-CLOUD V20.5 #2, and the four views
7
+ * the second pass left out).
8
+ *
9
+ * G6 stacks four canvases and gives each `tabIndex = 1`; a
10
+ * positive tabindex goes before every control of the page, so Tab
11
+ * from a graph went through its other canvases and the whole page
12
+ * before it reached the graph's own buttons.
13
+ *
14
+ * The real gclasses, on a G6 Graph double that does what G6 does
15
+ * here: the FIRST render() builds the four canvases with
16
+ * `tabIndex = 1` and says so with `aftercanvasinit`; a renderer
17
+ * change writes the 1 again and says `afterrendererchange`.
18
+ *
19
+ * Copyright (c) 2026, ArtGins.
20
+ * All Rights Reserved.
21
+ ***********************************************************************/
22
+ import {describe, test, expect, beforeAll, beforeEach, vi} from "vitest";
23
+ import {install_dom_double} from "../test/dom_double.js";
24
+
25
+ install_dom_double();
26
+
27
+ /* The views watch their container; nothing resizes here. */
28
+ if(typeof globalThis.ResizeObserver === "undefined") {
29
+ globalThis.ResizeObserver = class {
30
+ observe() {
31
+ }
32
+ unobserve() {
33
+ }
34
+ disconnect() {
35
+ }
36
+ };
37
+ }
38
+
39
+ const graphs = vi.hoisted(() => []);
40
+ vi.mock("@antv/g6", async (importOriginal) => {
41
+ const real = await importOriginal();
42
+ class FakeGraph {
43
+ constructor(options) {
44
+ this.options = options;
45
+ this.handlers = {};
46
+ this.layers = null;
47
+ graphs.push(this);
48
+ return new Proxy(this, {
49
+ get: (t, k) => (k in t) ? t[k] : (() => undefined),
50
+ });
51
+ }
52
+ container_el() {
53
+ const c = this.options.container;
54
+ return (typeof c === "string") ? document.getElementById(c) : c;
55
+ }
56
+ write_canvas_dom() {
57
+ for(const name of Object.keys(this.layers)) {
58
+ this.layers[name].el.tabIndex = 1;
59
+ }
60
+ }
61
+ render() {
62
+ if(!this.layers) {
63
+ const $c = this.container_el();
64
+ this.layers = {};
65
+ for(const name of ["background", "main", "label", "transient"]) {
66
+ const el = document.createElement("canvas");
67
+ el.setAttribute("data-layer", name);
68
+ $c.appendChild(el);
69
+ this.layers[name] = {
70
+ el: el,
71
+ getContextService: () => ({getDomElement: () => el}),
72
+ };
73
+ }
74
+ this.write_canvas_dom();
75
+ this.emit(real.GraphEvent.AFTER_CANVAS_INIT, {canvas: this.getCanvas()});
76
+ }
77
+ return Promise.resolve();
78
+ }
79
+ setRenderer() {
80
+ this.write_canvas_dom();
81
+ this.emit(real.GraphEvent.AFTER_RENDERER_CHANGE, {});
82
+ }
83
+ getCanvas() {
84
+ return {
85
+ getLayers: () => this.layers,
86
+ getContainer: () => this.container_el(),
87
+ };
88
+ }
89
+ on(name, fn) {
90
+ (this.handlers[name] = this.handlers[name] || []).push(fn);
91
+ }
92
+ emit(name, ev) {
93
+ for(const fn of (this.handlers[name] || [])) {
94
+ fn(ev);
95
+ }
96
+ }
97
+ getZoom() {
98
+ return 1;
99
+ }
100
+ getData() {
101
+ return {nodes: [], edges: [], combos: []};
102
+ }
103
+ getNodeData() {
104
+ return [];
105
+ }
106
+ getEdgeData() {
107
+ return [];
108
+ }
109
+ getPlugins() {
110
+ return [];
111
+ }
112
+ getBehaviors() {
113
+ return [];
114
+ }
115
+ getOptions() {
116
+ return {behaviors: []};
117
+ }
118
+ draw() {
119
+ return Promise.resolve();
120
+ }
121
+ fitView() {
122
+ return Promise.resolve();
123
+ }
124
+ zoomTo() {
125
+ return Promise.resolve();
126
+ }
127
+ destroy() {
128
+ }
129
+ }
130
+ return {...real, Graph: FakeGraph};
131
+ });
132
+
133
+ const {
134
+ SDATA_END, gclass_create, gobj_start_up, gobj_create_yuno, gobj_create_pure_child,
135
+ gobj_start, gobj_read_attr, set_log_callback, register_c_timer,
136
+ } = await import("@yuneta/gobj-js");
137
+ const {register_c_yui_json_graph} = await import("./c_yui_json_graph.js");
138
+ const {register_c_yui_gobj_tree_js} = await import("./c_yui_gobj_tree_js.js");
139
+ const {register_c_yui_fsm_graph} = await import("./c_yui_fsm_graph.js");
140
+ const {register_c_yui_treedb_schema} = await import("./c_yui_treedb_schema.js");
141
+ const {register_c_g6_nodes_tree} = await import("./c_g6_nodes_tree.js");
142
+
143
+ const logged = [];
144
+ let yuno = null;
145
+
146
+ beforeAll(async () => {
147
+ const i18next = (await import("i18next")).default;
148
+ await i18next.init({lng: "en", resources: {en: {translation: {}}}});
149
+ gobj_start_up(null, null, null, null, null, null, null);
150
+ set_log_callback((level, msg) => {
151
+ logged.push({level: String(level), msg: String(msg)});
152
+ });
153
+ register_c_timer();
154
+ register_c_yui_json_graph();
155
+ register_c_yui_gobj_tree_js();
156
+ register_c_yui_fsm_graph();
157
+ register_c_yui_treedb_schema();
158
+ register_c_g6_nodes_tree();
159
+ gclass_create("C_TEST_TAB_ORDER_HOST", [], [["ST_IDLE", []]], {}, 0,
160
+ [SDATA_END()], {}, 0, 0, 0, 0);
161
+ yuno = gobj_create_yuno("tab_order_yuno", "C_TEST_TAB_ORDER_HOST", {});
162
+ });
163
+
164
+ beforeEach(() => {
165
+ logged.length = 0;
166
+ });
167
+
168
+ const DESC = {topic_name: "users", cols: [
169
+ {id: "id", type: "string", flag: ["persistent"]},
170
+ ]};
171
+
172
+ /* Each view, with what it needs to render a first time. */
173
+ const VIEWS = {
174
+ C_YUI_JSON_GRAPH: {json_data: {a: 1, b: [1, 2]}},
175
+ C_YUI_GOBJ_TREE_JS: {},
176
+ C_YUI_FSM_GRAPH: {fsm: {
177
+ entry_state: "ST_IDLE",
178
+ states: [{name: "ST_IDLE"}, {name: "ST_BUSY"}],
179
+ rows: [
180
+ {event: "EV_GO", cells: [{next_state: "ST_BUSY"}, null]},
181
+ {event: "EV_DONE", cells: [null, {next_state: "ST_IDLE"}]},
182
+ ],
183
+ published: [],
184
+ unreachable: [],
185
+ }},
186
+ C_YUI_TREEDB_SCHEMA: {descs: {users: DESC}},
187
+ C_G6_NODES_TREE: {with_toolbar: false, operation_mode: "reading"},
188
+ };
189
+
190
+ let seq = 0;
191
+
192
+ async function mount(gclass_name, attrs)
193
+ {
194
+ const $container = document.createElement("div");
195
+ document.body.appendChild($container);
196
+ const n0 = graphs.length;
197
+ const view = gobj_create_pure_child(`tab_order_${++seq}`, gclass_name,
198
+ Object.assign({$container: $container}, attrs), yuno);
199
+ /* Mounted as a host mounts it: a view that builds its own root
200
+ * (json, gobj tree) is in the document before it starts, and
201
+ * G6 finds its canvas mount there by id. */
202
+ document.body.appendChild(gobj_read_attr(view, "$container"));
203
+ gobj_start(view);
204
+ /* render() resolves on its own time, as G6's does. */
205
+ await new Promise((resolve) => setTimeout(resolve, 0));
206
+ return {view, $container, graph: graphs.length > n0 ? graphs[graphs.length - 1] : null};
207
+ }
208
+
209
+ function tab_order(graph)
210
+ {
211
+ const order = {};
212
+ for(const [name, layer] of Object.entries(graph.layers || {})) {
213
+ order[name] = layer.el.tabIndex;
214
+ }
215
+ return order;
216
+ }
217
+
218
+ describe("every G6 view: the main canvas in the Tab order, the other layers out", () => {
219
+ for(const [gclass_name, attrs] of Object.entries(VIEWS)) {
220
+ test(`${gclass_name}: after its first render, and after a renderer change`, async () => {
221
+ const {graph} = await mount(gclass_name, attrs);
222
+ expect(graph, `${gclass_name} built a graph`).toBeTruthy();
223
+ expect(graph.layers, `${gclass_name} rendered when it started`).toBeTruthy();
224
+ expect(tab_order(graph)).toEqual({background: -1, main: 0, label: -1, transient: -1});
225
+
226
+ graph.setRenderer();
227
+ expect(tab_order(graph)).toEqual({background: -1, main: 0, label: -1, transient: -1});
228
+
229
+ expect(logged.filter((l) => l.level === "error").map((l) => l.msg)).toEqual([]);
230
+ });
231
+ }
232
+ });