@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.
- package/README.md +473 -19
- package/dist/gobj-ui.cjs.js +1100 -231
- package/dist/gobj-ui.es.js +1101 -232
- package/package.json +1 -1
- package/scripts/validate-locales.mjs +2334 -275
- package/src/c_g6_nodes_tree.js +1143 -260
- package/src/c_yui_form.js +11 -1
- package/src/c_yui_fsm_graph.js +5 -0
- package/src/c_yui_gclass.js +11 -0
- package/src/c_yui_gobj_tree_js.js +17 -0
- package/src/c_yui_json_graph.js +5 -0
- package/src/c_yui_nav.js +9 -6
- package/src/c_yui_pager.js +1 -1
- package/src/c_yui_period.js +11 -4
- package/src/c_yui_schema_editor.js +19 -7
- package/src/c_yui_shell.js +7 -7
- package/src/c_yui_treedb_graph.js +15 -6
- package/src/c_yui_treedb_schema.js +5 -0
- package/src/c_yui_treedb_topic_with_form.js +10 -10
- package/src/c_yui_treedb_topics.js +6 -6
- package/src/c_yui_window.js +2 -2
- package/src/c_yui_wizard.js +8 -1
- package/src/form_json_field.js +7 -0
- package/src/g6_canvas_tab_order.js +84 -0
- package/src/g6_canvas_tab_order.wiring.test.js +232 -0
- package/src/g6_nodes_tree_controls.test.js +547 -0
- package/src/g6_nodes_tree_focus.wiring.test.js +1360 -0
- package/src/g6_nodes_tree_i18n_keys.test.js +275 -0
- package/src/i18n_key_functions.js +146 -0
- package/src/i18n_key_functions.test.js +1204 -0
- package/src/lib_graph.js +38 -1
- package/src/nav_backbar_name.wiring.test.js +82 -0
- package/src/schema_flags.js +47 -0
- package/src/schema_import.js +7 -0
- package/src/shell_modals.js +20 -16
- package/src/shell_route_map.js +12 -2
- package/src/wizard_helpers.js +8 -0
- package/src/yui_dev.js +44 -33
- package/src/yui_dev_controls.test.js +174 -0
- package/src/yui_gclass_view.js +2 -2
- package/src/yui_graph_camera.js +1 -1
- package/src/yui_install.js +10 -8
- package/src/yui_table_select.js +2 -2
- 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
|
+
});
|