@yuneta/gobj-ui 7.26.30 → 7.26.32
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 +246 -72
- package/dist/gobj-ui.cjs.js +64 -20
- package/dist/gobj-ui.es.js +64 -20
- package/package.json +1 -1
- package/scripts/validate-locales.mjs +1755 -328
- package/src/c_g6_nodes_tree.js +6 -4
- package/src/c_yui_form.js +17 -4
- package/src/c_yui_treedb_graph.js +6 -3
- package/src/c_yui_treedb_topics.js +6 -3
- package/src/c_yui_window.js +32 -5
- package/src/i18n_key_functions.js +54 -31
- package/src/i18n_key_functions.test.js +461 -49
- package/src/window_layer.wiring.test.js +106 -0
- package/src/yui_table_select.js +20 -13
- package/src/yui_tabulator_i18n.js +10 -2
- package/src/yui_tabulator_i18n.test.js +80 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* window_layer.wiring.test.js
|
|
3
|
+
*
|
|
4
|
+
* Where a C_YUI_WINDOW lives when its host names no `$parent`:
|
|
5
|
+
* the OVERLAY layer of the shell, below the layers of the
|
|
6
|
+
* dropdowns, the dialogs and the toasts -- not document.body,
|
|
7
|
+
* where it covered the whole shell (a stacking context of its
|
|
8
|
+
* own) and the dialog of its own close opened under it.
|
|
9
|
+
*
|
|
10
|
+
* Copyright (c) 2026, ArtGins.
|
|
11
|
+
* All Rights Reserved.
|
|
12
|
+
***********************************************************************/
|
|
13
|
+
import {describe, test, expect, beforeAll} from "vitest";
|
|
14
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
15
|
+
|
|
16
|
+
install_dom_double();
|
|
17
|
+
|
|
18
|
+
if(typeof globalThis.matchMedia === "undefined") {
|
|
19
|
+
globalThis.matchMedia = () => ({matches: false, addEventListener() {}, removeEventListener() {}});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
SDATA, SDATA_END, data_type_t, gclass_create, gobj_write_attr,
|
|
24
|
+
gobj_start_up, gobj_create_yuno, gobj_create, gobj_create_service,
|
|
25
|
+
gobj_start, gobj_stop, gobj_destroy, gobj_read_attr,
|
|
26
|
+
} = await import("@yuneta/gobj-js");
|
|
27
|
+
const {register_c_yui_shell} = await import("./c_yui_shell.js");
|
|
28
|
+
const {register_c_yui_nav} = await import("./c_yui_nav.js");
|
|
29
|
+
const {register_c_yui_window} = await import("./c_yui_window.js");
|
|
30
|
+
|
|
31
|
+
let yuno = null;
|
|
32
|
+
|
|
33
|
+
beforeAll(async () => {
|
|
34
|
+
const i18next = (await import("i18next")).default;
|
|
35
|
+
await i18next.init({lng: "en", resources: {}});
|
|
36
|
+
gobj_start_up(null, null, null, null, null, null, null);
|
|
37
|
+
gclass_create("C_TEST_LAYER_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
38
|
+
gclass_create(
|
|
39
|
+
"C_TEST_LAYER_VIEW", [], [["ST_IDLE", []]], {
|
|
40
|
+
mt_create: (gobj) => {
|
|
41
|
+
gobj_write_attr(gobj, "$container", document.createElement("div"));
|
|
42
|
+
}
|
|
43
|
+
}, 0, [
|
|
44
|
+
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "view root"),
|
|
45
|
+
SDATA_END()
|
|
46
|
+
], {}, 0, 0, 0, 0
|
|
47
|
+
);
|
|
48
|
+
register_c_yui_shell();
|
|
49
|
+
register_c_yui_nav();
|
|
50
|
+
register_c_yui_window();
|
|
51
|
+
yuno = gobj_create_yuno("layer_yuno", "C_TEST_LAYER_HOST", {});
|
|
52
|
+
gobj_start(yuno);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("the default parent of a window", () => {
|
|
56
|
+
test("with no shell: document.body", () => {
|
|
57
|
+
const w = gobj_create_service("layer_w0", "C_YUI_WINDOW", {header: "w", body: ["div", {}, "w"]}, yuno);
|
|
58
|
+
gobj_start(w);
|
|
59
|
+
expect(gobj_read_attr(w, "$container").parentNode).toBe(document.body);
|
|
60
|
+
gobj_stop(w);
|
|
61
|
+
gobj_destroy(w);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("with a shell: its overlay layer, also for a window that is not its child", () => {
|
|
65
|
+
const shell = gobj_create("layer_shell", "C_YUI_SHELL", {
|
|
66
|
+
config: {
|
|
67
|
+
shell: {
|
|
68
|
+
zones: {center: {host: "stage.main"}},
|
|
69
|
+
stages: {main: {zone: "center", default_route: "/a"}}
|
|
70
|
+
},
|
|
71
|
+
menu: {primary: {items: [
|
|
72
|
+
{id: "a", name: "a", route: "/a",
|
|
73
|
+
target: {stage: "main", gclass: "C_TEST_LAYER_VIEW", kw: {}}}
|
|
74
|
+
]}}
|
|
75
|
+
},
|
|
76
|
+
use_hash: false
|
|
77
|
+
}, yuno);
|
|
78
|
+
gobj_start(shell);
|
|
79
|
+
|
|
80
|
+
/* A child of the shell, and one parented to the app (the dev
|
|
81
|
+
* window, a device window of a view): both in the overlay. */
|
|
82
|
+
const inside = gobj_create_service("layer_w1", "C_YUI_WINDOW", {header: "a", body: ["div", {}, "a"]}, shell);
|
|
83
|
+
gobj_start(inside);
|
|
84
|
+
const outside = gobj_create_service("layer_w2", "C_YUI_WINDOW", {header: "b", body: ["div", {}, "b"]}, yuno);
|
|
85
|
+
gobj_start(outside);
|
|
86
|
+
|
|
87
|
+
const overlay = shell.priv.layers.overlay;
|
|
88
|
+
expect(overlay).toBeTruthy();
|
|
89
|
+
expect(gobj_read_attr(inside, "$container").parentNode).toBe(overlay);
|
|
90
|
+
expect(gobj_read_attr(outside, "$container").parentNode).toBe(overlay);
|
|
91
|
+
|
|
92
|
+
/* And a `$parent` named by the host still wins. */
|
|
93
|
+
const $mine = document.createElement("div");
|
|
94
|
+
const named = gobj_create_service("layer_w3", "C_YUI_WINDOW",
|
|
95
|
+
{header: "c", body: ["div", {}, "c"], $parent: $mine}, yuno);
|
|
96
|
+
gobj_start(named);
|
|
97
|
+
expect(gobj_read_attr(named, "$container").parentNode).toBe($mine);
|
|
98
|
+
|
|
99
|
+
for(const w of [inside, outside, named]) {
|
|
100
|
+
gobj_stop(w);
|
|
101
|
+
gobj_destroy(w);
|
|
102
|
+
}
|
|
103
|
+
gobj_stop(shell);
|
|
104
|
+
gobj_destroy(shell);
|
|
105
|
+
});
|
|
106
|
+
});
|
package/src/yui_table_select.js
CHANGED
|
@@ -199,8 +199,8 @@ function yui_wire_selection(tabulator, on_change)
|
|
|
199
199
|
***************************************************************/
|
|
200
200
|
function yui_selection_bar(t, opts)
|
|
201
201
|
{
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
const o = opts || {};
|
|
203
|
+
const actions = Array.isArray(o.actions) ? o.actions : [];
|
|
204
204
|
let name = o.name || "TABLE";
|
|
205
205
|
let count = 0;
|
|
206
206
|
|
|
@@ -218,7 +218,6 @@ function yui_selection_bar(t, opts)
|
|
|
218
218
|
click: action.on_click
|
|
219
219
|
}]
|
|
220
220
|
);
|
|
221
|
-
$b._yui_label = action.label || "";
|
|
222
221
|
$buttons.push($b);
|
|
223
222
|
}
|
|
224
223
|
|
|
@@ -254,19 +253,27 @@ function yui_selection_bar(t, opts)
|
|
|
254
253
|
return $b.querySelector("span.icon ~ span");
|
|
255
254
|
}
|
|
256
255
|
|
|
256
|
+
/* A button's label and its name, in the current language */
|
|
257
|
+
function paint_label($b, key)
|
|
258
|
+
{
|
|
259
|
+
let $label = label_of($b);
|
|
260
|
+
if($label && key) {
|
|
261
|
+
$label.textContent = t(key);
|
|
262
|
+
}
|
|
263
|
+
$b.title = key ? t(key) : "";
|
|
264
|
+
$b.setAttribute("aria-label", $b.title);
|
|
265
|
+
}
|
|
266
|
+
|
|
257
267
|
function refresh()
|
|
258
268
|
{
|
|
259
269
|
$count.textContent = t("{{n}} selected", {n: count});
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
$b.title = key ? t(key) : "";
|
|
268
|
-
$b.setAttribute("aria-label", $b.title);
|
|
269
|
-
}
|
|
270
|
+
/* An action's label is the HOST's key, read where the host calls
|
|
271
|
+
* yui_selection_bar() (I18N_KEY_FUNCTIONS); the clear button's, a
|
|
272
|
+
* literal read here */
|
|
273
|
+
actions.forEach((action, i) => {
|
|
274
|
+
paint_label($buttons[i], action.label || "");
|
|
275
|
+
});
|
|
276
|
+
paint_label($clear, "clear selection");
|
|
270
277
|
/* Bulma's `is-hidden` carries `!important`: the class is what
|
|
271
278
|
* toggles, an inline display would lose to it. */
|
|
272
279
|
$el.classList.toggle("is-hidden", count <= 0);
|
|
@@ -134,8 +134,14 @@ function yui_tabulator_name_filters(table, t)
|
|
|
134
134
|
* already exists in two apps as the header filter's
|
|
135
135
|
* PLACEHOLDER ("filtrar columna...") and carries no
|
|
136
136
|
* interpolation, so reusing it named all five boxes the
|
|
137
|
-
* same -- which is where this started.
|
|
138
|
-
|
|
137
|
+
* same -- which is where this started.
|
|
138
|
+
*
|
|
139
|
+
* The `title` too: every control carries both, and a filter
|
|
140
|
+
* box shows only its placeholder, which goes as soon as
|
|
141
|
+
* something is typed in it. */
|
|
142
|
+
let name = t("filter by column", {column: title});
|
|
143
|
+
$input.setAttribute("aria-label", name);
|
|
144
|
+
$input.setAttribute("title", name);
|
|
139
145
|
}
|
|
140
146
|
} catch(e) {
|
|
141
147
|
/* a table between renders has no columns to name */
|
|
@@ -181,6 +187,8 @@ function yui_tabulator_name_row_selects(table, t)
|
|
|
181
187
|
for(let $box of boxes) {
|
|
182
188
|
$box.setAttribute("aria-label", t("select row"));
|
|
183
189
|
$box.setAttribute("data-i18n-aria-label", "select row");
|
|
190
|
+
$box.setAttribute("title", t("select row"));
|
|
191
|
+
$box.setAttribute("data-i18n-title", "select row");
|
|
184
192
|
}
|
|
185
193
|
} catch(e) {
|
|
186
194
|
/* a table between renders has no boxes to name */
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* yui_tabulator_i18n.test.js
|
|
3
|
+
*
|
|
4
|
+
* The names given AFTER the render to what Tabulator draws for
|
|
5
|
+
* itself: the header filters and the row-selection boxes carry
|
|
6
|
+
* an aria-label AND a title, like every control.
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) 2026, ArtGins.
|
|
9
|
+
* All Rights Reserved.
|
|
10
|
+
***********************************************************************/
|
|
11
|
+
import {describe, it, expect} from "vitest";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
yui_tabulator_name_filters,
|
|
15
|
+
yui_tabulator_name_row_selects,
|
|
16
|
+
} from "./yui_tabulator_i18n.js";
|
|
17
|
+
|
|
18
|
+
/* An element double: attributes, and the one child a selector finds. */
|
|
19
|
+
function element(child)
|
|
20
|
+
{
|
|
21
|
+
const attrs = {};
|
|
22
|
+
return {
|
|
23
|
+
attrs,
|
|
24
|
+
setAttribute: (k, v) => {
|
|
25
|
+
attrs[k] = String(v);
|
|
26
|
+
},
|
|
27
|
+
getAttribute: (k) => (k in attrs) ? attrs[k] : null,
|
|
28
|
+
querySelector: () => child || null,
|
|
29
|
+
querySelectorAll: () => (child ? [child] : []),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* A column double with its header filter input. */
|
|
34
|
+
function column(title, $input)
|
|
35
|
+
{
|
|
36
|
+
const $el = element($input);
|
|
37
|
+
return {
|
|
38
|
+
getElement: () => $el,
|
|
39
|
+
getDefinition: () => ({title: title}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const t = (key, opts) => (key === "filter by column")
|
|
44
|
+
? `Filtrar por ${opts.column}`
|
|
45
|
+
: (key === "select row" ? "Seleccionar fila" : key);
|
|
46
|
+
|
|
47
|
+
describe("yui_tabulator_name_filters", () => {
|
|
48
|
+
it("names each header filter with an aria-label and a title", () => {
|
|
49
|
+
const $a = element();
|
|
50
|
+
const $b = element();
|
|
51
|
+
const table = {getColumns: () => [column("Id", $a), column("Name", $b)]};
|
|
52
|
+
|
|
53
|
+
yui_tabulator_name_filters(table, t);
|
|
54
|
+
|
|
55
|
+
expect($a.attrs["aria-label"]).toBe("Filtrar por Id");
|
|
56
|
+
expect($a.attrs["title"]).toBe("Filtrar por Id");
|
|
57
|
+
expect($b.attrs["aria-label"]).toBe("Filtrar por Name");
|
|
58
|
+
expect($b.attrs["title"]).toBe("Filtrar por Name");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("leaves a column without a filter alone", () => {
|
|
62
|
+
const table = {getColumns: () => [column("Id", null)]};
|
|
63
|
+
expect(() => yui_tabulator_name_filters(table, t)).not.toThrow();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("yui_tabulator_name_row_selects", () => {
|
|
68
|
+
it("renames Tabulator's English box with an aria-label and a title, and their keys", () => {
|
|
69
|
+
const $box = element();
|
|
70
|
+
$box.setAttribute("aria-label", "Select Row");
|
|
71
|
+
const table = {element: element($box)};
|
|
72
|
+
|
|
73
|
+
yui_tabulator_name_row_selects(table, t);
|
|
74
|
+
|
|
75
|
+
expect($box.attrs["aria-label"]).toBe("Seleccionar fila");
|
|
76
|
+
expect($box.attrs["data-i18n-aria-label"]).toBe("select row");
|
|
77
|
+
expect($box.attrs["title"]).toBe("Seleccionar fila");
|
|
78
|
+
expect($box.attrs["data-i18n-title"]).toBe("select row");
|
|
79
|
+
});
|
|
80
|
+
});
|