@yuneta/gobj-ui 7.26.31 → 7.26.33

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.
@@ -5295,10 +5295,12 @@ function name_form_control(parent, control)
5295
5295
  }
5296
5296
  control.id = 'g6-form-control-' + (++form_control_seq);
5297
5297
  label.htmlFor = control.id;
5298
- control.setAttribute('title', t(key));
5299
- control.setAttribute('data-i18n-title', key);
5300
- control.setAttribute('aria-label', t(key));
5301
- control.setAttribute('data-i18n-aria-label', key);
5298
+ /* The label's own key, read back from its DOM: it was read where the
5299
+ * label was built */
5300
+ control.setAttribute('title', t(/* i18n: data */ key));
5301
+ control.setAttribute('data-i18n-title', /* i18n: data */ key);
5302
+ control.setAttribute('aria-label', t(/* i18n: data */ key));
5303
+ control.setAttribute('data-i18n-aria-label', /* i18n: data */ key);
5302
5304
  }
5303
5305
 
5304
5306
  function create_form_color_input(parent, value, onInput)
package/src/c_yui_form.js CHANGED
@@ -803,11 +803,12 @@ function build_html_form(gobj, $form, prefix, template)
803
803
  : [field_desc.name];
804
804
  const NO_I18N = "\x00";
805
805
  html_field_conf.label = t(label_keys, {defaultValue: field_desc.header});
806
+ /* A column of the template: the host's schema, data */
806
807
  html_field_conf.label_i18n =
807
- (t(field_desc.name, {defaultValue: NO_I18N}) !== NO_I18N)
808
+ (t(/* i18n: data */ field_desc.name, {defaultValue: NO_I18N}) !== NO_I18N)
808
809
  ? field_desc.name
809
810
  : null;
810
- html_field_conf.placeholder = field_desc.placeholder?t(field_desc.placeholder):'';
811
+ html_field_conf.placeholder = field_desc.placeholder?t(/* i18n: data */ field_desc.placeholder):'';
811
812
 
812
813
  let placeholders = gobj_read_attr(gobj, "placeholders");
813
814
  if(placeholders[key]) {
@@ -2249,7 +2250,7 @@ function template2columns(gobj, columns, template, sub_elements)
2249
2250
  let validator = [];
2250
2251
  let column = {
2251
2252
  field: field_desc.name,
2252
- title: t(field_desc.header || field_desc.name),
2253
+ title: t(/* i18n: data */ field_desc.header || field_desc.name), // the host's schema
2253
2254
  titleFormatter: text_title_formatter,
2254
2255
  editable: get_editable_state,
2255
2256
  editor: false,
@@ -2626,7 +2627,7 @@ function mark_field_validity($input)
2626
2627
  if($h) {
2627
2628
  let key = field_validation_key($input);
2628
2629
  if(key) {
2629
- $h.setAttribute('data-i18n', key);
2630
+ $h.setAttribute('data-i18n', /* i18n: C_YUI_FORM_I18N_KEYS, FORM_JSON_FIELD_I18N_KEYS */ key);
2630
2631
  } else {
2631
2632
  $h.removeAttribute('data-i18n'); // the browser's own sentence
2632
2633
  }
@@ -2766,10 +2767,13 @@ function get_form_values(gobj, $form)
2766
2767
  break;
2767
2768
 
2768
2769
  case "fieldset":
2769
- /*
2770
- * HACK: really this is the path of the value.
2771
- * We are navigating by a tree data structure; this is one node, not a leaf.
2772
- */
2770
+ /* A NODE of the template, not a leaf: its value is the
2771
+ * dict its own fields fill, and they come after it in the
2772
+ * document. No json anybody typed, so it skips the
2773
+ * conversion below, which handed its null on (7.26.8 to
2774
+ * 7.26.32): the fields found no dict to write into and the
2775
+ * save stored the node as null. */
2776
+ value = {};
2773
2777
  break;
2774
2778
 
2775
2779
  case "table":
@@ -2810,7 +2814,9 @@ function get_form_values(gobj, $form)
2810
2814
  break;
2811
2815
  }
2812
2816
 
2813
- value = form_value_2_treedb_value(gobj, field_desc, value);
2817
+ if(tag !== "fieldset") {
2818
+ value = form_value_2_treedb_value(gobj, field_desc, value);
2819
+ }
2814
2820
  let path;
2815
2821
  if(empty_string(prefix)) {
2816
2822
  path = name;
@@ -1367,7 +1367,9 @@ function answer_print_tranger_refused(gobj, path, answer)
1367
1367
 
1368
1368
  /************************************************************
1369
1369
  * The text of a transport's refusal: gobj_command() answers a string,
1370
- * a command response or a number.
1370
+ * a command response or a number. Data only ("" when it says
1371
+ * nothing): the caller writes its own key as the fallback, where
1372
+ * validate-locales reads it.
1371
1373
  ************************************************************/
1372
1374
  function refusal_text(ret)
1373
1375
  {
@@ -1377,7 +1379,7 @@ function refusal_text(ret)
1377
1379
  if(ret && typeof ret === "object" && typeof ret.comment === "string" && ret.comment) {
1378
1380
  return ret.comment;
1379
1381
  }
1380
- return "print-tranger failed";
1382
+ return "";
1381
1383
  }
1382
1384
 
1383
1385
  /************************************************************
@@ -1412,7 +1414,8 @@ function request_print_tranger(gobj, path)
1412
1414
  }, gobj);
1413
1415
  if(ret) {
1414
1416
  log_error(ret);
1415
- answer_print_tranger_refused(gobj, path, {error: /* i18n: data */ refusal_text(ret)});
1417
+ answer_print_tranger_refused(gobj, path,
1418
+ {error: /* i18n: data */ refusal_text(ret) || "print-tranger failed"});
1416
1419
  return;
1417
1420
  }
1418
1421
  if(path) {
@@ -2230,7 +2230,9 @@ function answer_print_tranger_refused(gobj, path, answer)
2230
2230
 
2231
2231
  /************************************************************
2232
2232
  * The text of a transport's refusal: gobj_command() answers a string,
2233
- * a command response or a number.
2233
+ * a command response or a number. Data only ("" when it says
2234
+ * nothing): the caller writes its own key as the fallback, where
2235
+ * validate-locales reads it.
2234
2236
  ************************************************************/
2235
2237
  function refusal_text(ret)
2236
2238
  {
@@ -2240,7 +2242,7 @@ function refusal_text(ret)
2240
2242
  if(ret && typeof ret === "object" && typeof ret.comment === "string" && ret.comment) {
2241
2243
  return ret.comment;
2242
2244
  }
2243
- return "print-tranger failed";
2245
+ return "";
2244
2246
  }
2245
2247
 
2246
2248
  /************************************************************
@@ -2274,7 +2276,8 @@ function request_print_tranger(gobj, path)
2274
2276
  }, gobj);
2275
2277
  if(ret) {
2276
2278
  log_error(ret);
2277
- answer_print_tranger_refused(gobj, path, {error: /* i18n: data */ refusal_text(ret)});
2279
+ answer_print_tranger_refused(gobj, path,
2280
+ {error: /* i18n: data */ refusal_text(ret) || "print-tranger failed"});
2278
2281
  return;
2279
2282
  }
2280
2283
  if(path) {
@@ -0,0 +1,160 @@
1
+ /***********************************************************************
2
+ * form_template_field.wiring.test.js
3
+ *
4
+ * A `template` field of a C_YUI_FORM in 'exec' mode is a sub-form
5
+ * (a <fieldset>): a NODE of the record, whose value is the dict its
6
+ * own fields fill. From 7.26.8 to 7.26.32 the node went through the
7
+ * json conversion, which handed its null on: the fields below found
8
+ * no dict to write into (kw_set_dict_value() refuses a null segment)
9
+ * and the save stored the node as null -- the alarm colours of a
10
+ * device in estadodelaire, wiped by every save of its settings.
11
+ * Driven through the real gclass on a document double, with the
12
+ * template of those settings.
13
+ *
14
+ * Copyright (c) 2026, ArtGins.
15
+ * All Rights Reserved.
16
+ ***********************************************************************/
17
+ import {describe, test, expect, beforeAll, beforeEach} from "vitest";
18
+ import {install_dom_double} from "../test/dom_double.js";
19
+
20
+ install_dom_double();
21
+
22
+ if(typeof globalThis.ResizeObserver === "undefined") {
23
+ globalThis.ResizeObserver = class {
24
+ observe() {}
25
+ unobserve() {}
26
+ disconnect() {}
27
+ };
28
+ }
29
+
30
+ const {
31
+ SDATA_END, gclass_create,
32
+ gobj_start_up, gobj_create_yuno, gobj_create_pure_child,
33
+ gobj_start, gobj_send_event, gobj_read_attr,
34
+ set_log_callback,
35
+ } = await import("@yuneta/gobj-js");
36
+ const {register_c_yui_form} = await import("./c_yui_form.js");
37
+
38
+ const saved = [];
39
+ const logged = [];
40
+ let yuno = null;
41
+
42
+ /* The settings template of an estadodelaire device type, cut down to
43
+ * the sub-form and one field beside it. */
44
+ const TEMPLATE = {
45
+ template_version: {
46
+ type: "integer",
47
+ default: 7,
48
+ flag: ["hidden", "persistent"]
49
+ },
50
+ alarm_colors: {
51
+ type: "object",
52
+ flag: ["writable", "persistent", "template"],
53
+ template: {
54
+ active_not_validated: {
55
+ type: "string",
56
+ default: "red",
57
+ flag: ["writable", "persistent", "color"]
58
+ },
59
+ active_validated: {
60
+ type: "string",
61
+ default: "#e5a50a",
62
+ flag: ["writable", "persistent", "color"]
63
+ },
64
+ not_active_not_validated: {
65
+ type: "string",
66
+ default: "Fuchsia",
67
+ flag: ["writable", "persistent", "color"]
68
+ }
69
+ }
70
+ }
71
+ };
72
+
73
+ beforeAll(async () => {
74
+ const i18next = (await import("i18next")).default;
75
+ await i18next.init({lng: "en", resources: {}});
76
+ gobj_start_up(null, null, null, null, null, null, null);
77
+ set_log_callback((level, msg) => {
78
+ logged.push({level: String(level), msg: String(msg)});
79
+ });
80
+ gclass_create("C_TEST_FORM_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
81
+ gclass_create(
82
+ "C_TEST_FORM_APP",
83
+ [["EV_SAVE_RECORD", 0], ["EV_RECORD_CHANGED", 0]],
84
+ [["ST_IDLE", [
85
+ ["EV_SAVE_RECORD", (gobj, event, kw) => {
86
+ saved.push(kw);
87
+ return 0;
88
+ }, null],
89
+ ["EV_RECORD_CHANGED", () => 0, null],
90
+ ]]],
91
+ {}, 0, [SDATA_END()], {}, 0, 0, 0, 0
92
+ );
93
+ register_c_yui_form();
94
+ yuno = gobj_create_yuno("form_template_yuno", "C_TEST_FORM_HOST", {});
95
+ gobj_start(yuno);
96
+ });
97
+
98
+ beforeEach(() => {
99
+ saved.length = 0;
100
+ logged.length = 0;
101
+ });
102
+
103
+ let seq = 0;
104
+
105
+ function build(record)
106
+ {
107
+ const app = gobj_create_pure_child(`tapp${++seq}`, "C_TEST_FORM_APP", {}, yuno);
108
+ gobj_start(app);
109
+ const form = gobj_create_pure_child(`tform${seq}`, "C_YUI_FORM", {
110
+ template: TEMPLATE,
111
+ record: record,
112
+ editable: true,
113
+ }, app);
114
+ gobj_start(form);
115
+ const $c = gobj_read_attr(form, "$container");
116
+ expect($c.querySelector("fieldset")).toBeTruthy(); // exec mode: a sub-form
117
+ return form;
118
+ }
119
+
120
+ function errors()
121
+ {
122
+ return logged.filter(l => /error/i.test(l.level) || /ERROR/.test(l.msg));
123
+ }
124
+
125
+ describe("a template field in exec mode (a sub-form)", () => {
126
+ test("is saved as the dict its fields fill", () => {
127
+ const form = build({
128
+ template_version: 7,
129
+ alarm_colors: {
130
+ active_not_validated: "#ff0000",
131
+ active_validated: "#e5a50a",
132
+ not_active_not_validated: "#ff00ff"
133
+ }
134
+ });
135
+ gobj_send_event(form, "EV_SAVE_RECORD", {}, form);
136
+ expect(saved.length).toBe(1);
137
+ expect(saved[0].alarm_colors).toEqual({
138
+ active_not_validated: "#ff0000",
139
+ active_validated: "#e5a50a",
140
+ not_active_not_validated: "#ff00ff"
141
+ });
142
+ expect(saved[0].template_version).toBe(7);
143
+ expect(errors()).toEqual([]);
144
+ });
145
+
146
+ test("left null by a save before the fix, comes back with its defaults", () => {
147
+ const form = build({
148
+ template_version: 7,
149
+ alarm_colors: null
150
+ });
151
+ gobj_send_event(form, "EV_SAVE_RECORD", {}, form);
152
+ expect(saved.length).toBe(1);
153
+ expect(saved[0].alarm_colors).toEqual({
154
+ active_not_validated: "red",
155
+ active_validated: "#e5a50a",
156
+ not_active_not_validated: "Fuchsia"
157
+ });
158
+ expect(errors()).toEqual([]);
159
+ });
160
+ });
@@ -30,7 +30,8 @@
30
30
  * of the array `actions`
31
31
  * A position may also hold DOM or nothing: only
32
32
  * a string there is a key. DOM is an array (a
33
- * createElement2 spec), a call of createElement2(),
33
+ * createElement2 spec; an array of string
34
+ * literals is none), a call of createElement2(),
34
35
  * createOneHtml() or html_markup() of gobj-js, of
35
36
  * text_node() of gobj-ui, of document.createElement()
36
37
  * and the like, a const bound to one, a plain `let`
@@ -39,43 +40,64 @@
39
40
  *
40
41
  * What is read at a position: a string literal, a template with
41
42
  * no `${}`, every branch of a `?:`, `||`, `??` (the right one of
42
- * `&&`), and what the reader follows -- a const to its value, a
43
- * property of a const object, the element of a const array a
44
- * `.map()` or a for...of walks (and what a destructuring reads
45
- * from it), String(x) and `${x}` as x, the default of a parameter,
46
- * a call of a function of the same module whose every return is
47
- * one of these. A `let`, a parameter or data is not read: its keys
48
- * are published in an exported `<NAME>_I18N_KEYS` array, and the
49
- * call is MARKED with a comment right before what it hands over,
50
- * `i18n: <NAME>_I18N_KEYS` (the list) or `i18n: data` (no key a
51
- * list can hold). A marked `let` is held to its marker: every
52
- * literal it is given is read and must be in the list, and one
53
- * given nothing but literals fails (write it as a const, which the
54
- * reader follows). A `data` marker on what the reader follows to
55
- * keys and nothing else -- a literal, a const or a branch that is
56
- * one, a call that returns literals -- fails too: it claims data
57
- * where the reader reads a key.
43
+ * `&&`), and what the reader follows -- a const to its value (what
44
+ * a destructuring reads from it too), an import of a const or a
45
+ * function of a module the reader reads, a property of a const
46
+ * object or of a copy of one (`{...o}`), the element of a const
47
+ * array a `.map()` or a for...of walks (and what a destructuring
48
+ * reads from it), String(x) and `${x}` as x, the default of a
49
+ * parameter, a call of a function -- of the module or imported --
50
+ * whose every return is one of these (read once; a recursion is
51
+ * not followed). A `let`, a parameter or data is not read: its
52
+ * keys are published in an exported `<NAME>_I18N_KEYS` array (an
53
+ * `export const`, never mutated), and the call is MARKED with a
54
+ * comment right before what it hands over, `i18n: <NAME>_I18N_KEYS`
55
+ * (the list) or `i18n: data` (no key a list can hold). A marked
56
+ * `let` is held to its marker: every literal it is given is read
57
+ * and must be in the list, and one given nothing but literals fails
58
+ * (write it as a const, which the reader follows). A `data` marker
59
+ * on what the reader follows to keys and nothing else -- a
60
+ * literal, a const or a branch that is one, a call that returns
61
+ * literals -- fails too: it claims data where the reader reads a
62
+ * key; and so does one that would hide a key the reader does not
63
+ * read: a call whose returns mix keys with data, an array of
64
+ * string literals.
65
+ *
66
+ * A call reaches a function of this table however it names it:
67
+ * its name, an import under any name, a re-export, a const alias,
68
+ * a map (`API.info(...)`), `.call()`, `.apply()`.
58
69
  *
59
70
  * The rule, per CALL: a key position given anything else than a
60
71
  * literal (or DOM, or nothing), a const the reader follows, a key
61
72
  * position of the enclosing table function handed on, or a marked
62
- * value fails, with the file and the line; a TRANSLATED text,
63
- * t("..."), fails even marked (it would be translated again, and
64
- * never change language). A test (i18n_key_functions.test.js)
65
- * holds the library to it, and validate-locales an app's own code.
66
- * Publishing a list exempts no module.
73
+ * value fails, with the file and the line; a TRANSLATED text --
74
+ * t("..."), tr(), an alias of t, `t?.()`, `t.call()` -- fails even
75
+ * marked (it would be translated again, and never change
76
+ * language). A test (i18n_key_functions.test.js) holds the library
77
+ * to it, and validate-locales an app's own code. Publishing a list
78
+ * exempts no module.
67
79
  *
68
- * The table is COMPLETE, and the same test proves it: it finds
69
- * every function of the library that hands one of its parameters
70
- * -- or what it reads from one, `opts.title`, a destructured
71
- * `{label}`, each `items[].name` -- to t(), to an `i18n`, `code` or
72
- * `data-i18n*` value, or to a key position of a function of this
73
- * table (REV-CLOUD V21.4.1: up to 7.26.28 it lacked 39 such
74
- * functions, and 6 entries lacked positions).
80
+ * The table is COMPLETE and CORRECT, and the same test proves both
81
+ * (REV-CLOUD V21.4.1, V22): it finds every function of the library
82
+ * that hands one of its parameters -- or what it reads from one:
83
+ * `opts.title`, `opts?.title`, a destructured `{label}` (in the
84
+ * parameter or in the body), each `items[].name`, `arguments[1]`,
85
+ * `rest[0]`, a copy `{...opts}` -- to a translator, to an `i18n`,
86
+ * `code` or `data-i18n*` value (`el.dataset.i18nTitle` included),
87
+ * or to a key position of a function of this table, and fails on
88
+ * a function missing here; and the same search, started from NO
89
+ * table, must find every entry here, at every position, with the
90
+ * same `exported`: an entry nothing forwards to fails. A function
91
+ * that hands a parameter on through what no position can name --
92
+ * `k.toLowerCase()`, a concatenation or a template, an iteration
93
+ * of `Object.entries(opts)`, a rest parameter walked, `arguments`
94
+ * by a computed index, a parameter an array pattern destructures
95
+ * -- fails too, unless it marks it where it hands it.
75
96
  * One called by name goes here, with that position. One nothing
76
97
  * calls by name -- an FSM action, a callback, a method -- has no
77
98
  * call to read: it marks what it hands on, where it hands it. What
78
- * a gobj keeps in its `priv` is its own state, never a position.
99
+ * a gobj keeps in its `priv`, and what is read from a parameter
100
+ * `gobj` (its attrs), is its own state, never a position.
79
101
  *
80
102
  * Copyright (c) 2026, ArtGins.
81
103
  * All Rights Reserved.
@@ -140,6 +162,7 @@ export const I18N_KEY_FUNCTIONS = Object.freeze([
140
162
  keys: ["0.id", "0.roll.label", "0.spec", "0.spec.id"]},
141
163
  {module: "c_yui_period.js", name: "build_mode_button", exported: false,
142
164
  keys: ["1.id", "1.roll.label", "1.spec", "1.spec.id"]},
165
+ {module: "yui_table_select.js", name: "paint_label", exported: false, keys: ["1"]},
143
166
  {module: "c_yui_form.js", name: "cell_icon", exported: false, keys: ["1"]},
144
167
  {module: "c_yui_form.js", name: "name_form_control", exported: false, keys: ["1.label_i18n", "1.label"]},
145
168
  {module: "c_yui_form.js", name: "create_form_field", exported: false,
@@ -183,7 +206,7 @@ export const I18N_KEY_FUNCTIONS = Object.freeze([
183
206
  {module: "c_yui_schema_editor.js", name: "render_import_plan", exported: false,
184
207
  keys: ["1.conflicts[].code", "1.writes[].op"]},
185
208
  {module: "schema_validate.js", name: "add", exported: false, keys: ["1"]},
186
- {module: "c_g6_nodes_tree.js", name: "create_floating_icon", exported: false, keys: ["4"]},
209
+ {module: "c_g6_nodes_tree.js", name: "create_floating_icon", exported: true, keys: ["4"]},
187
210
  {module: "c_g6_nodes_tree.js", name: "show_dual_icons", exported: false, keys: ["3", "5"]},
188
211
  {module: "c_g6_nodes_tree.js", name: "create_form_label", exported: false, keys: ["1"]},
189
212
  {module: "c_g6_nodes_tree.js", name: "create_form_select", exported: false, keys: ["1[].i18n"]},