@yuneta/gobj-ui 7.23.70 → 7.23.71

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.
@@ -6403,6 +6403,57 @@ function plan_toolbar(wanted) {
6403
6403
  };
6404
6404
  }
6405
6405
  //#endregion
6406
+ //#region src/form_field_readonly.js
6407
+ /***********************************************************************
6408
+ * form_field_readonly.js
6409
+ *
6410
+ * Is this field of the form read-only?
6411
+ *
6412
+ * Pure, so the rule can be tested without a DOM: the gclass turns
6413
+ * the answer into `readonly`/`disabled` on a control, this decides
6414
+ * the answer. Same split as form_toolbar_plan.js and
6415
+ * treedb_write_plan.js.
6416
+ *
6417
+ * The rule it exists to state ONCE: **`writable` governs the WRITE
6418
+ * of a column's VALUE, and an fkey is not written — it is LINKED.**
6419
+ * An fkey column is normally declared with no `writable` flag at
6420
+ * all (`treedb_authzs`'s `users.roles` is `['fkey']`, and so is
6421
+ * almost every fkey in the tree), so reading "not writable" as "not
6422
+ * editable" takes away the only way to link a record to its parent
6423
+ * — with `users.roles`, the only way to give a person a role.
6424
+ *
6425
+ * The other half of this rule is already written down in
6426
+ * c_yui_treedb_topic_with_form.js, where the record travels back:
6427
+ * the form sends "the writable cols, the fkeys (a link is edited by
6428
+ * linking) and the pkey". The two halves have to agree, and when
6429
+ * they did not, the form disabled the control whose value the save
6430
+ * path was still waiting for.
6431
+ *
6432
+ * A `file` column is an fkey too (flagged `['fkey','file']`) and is
6433
+ * NOT covered: it answers `type: "file"`, its bytes travel beside
6434
+ * the record, and the SDK declares as legal a `file` column that
6435
+ * only a load fills.
6436
+ *
6437
+ * Copyright (c) 2026, ArtGins.
6438
+ * All Rights Reserved.
6439
+ ***********************************************************************/
6440
+ /***************************************************************
6441
+ * field_is_readonly(form_readonly, field_desc) -> boolean
6442
+ *
6443
+ * form_readonly the FORM is read-only: a record opened to be
6444
+ * looked at, or a store nobody may write. It is
6445
+ * a property of the opening, not of the schema,
6446
+ * and it wins over everything.
6447
+ * field_desc as treedb_get_field_desc() answers it
6448
+ * (`type`, `is_writable`).
6449
+ ***************************************************************/
6450
+ function field_is_readonly(form_readonly, field_desc) {
6451
+ if (form_readonly) return true;
6452
+ let desc = field_desc || {};
6453
+ if (desc.type === "fkey") return false;
6454
+ return !desc.is_writable;
6455
+ }
6456
+ //#endregion
6406
6457
  //#region src/yui_theme.js
6407
6458
  /***********************************************************************
6408
6459
  * yui_theme.js
@@ -7215,7 +7266,7 @@ function build_form_field_conf(gobj, field_desc) {
7215
7266
  placeholder: "",
7216
7267
  options: [],
7217
7268
  extras: {},
7218
- readonly: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly") || !field_desc.is_writable,
7269
+ readonly: field_is_readonly((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "readonly"), field_desc),
7219
7270
  required: field_desc.is_required,
7220
7271
  hidden: field_desc.is_hidden,
7221
7272
  default_value: field_desc.default_value
@@ -6398,6 +6398,57 @@ function plan_toolbar(wanted) {
6398
6398
  };
6399
6399
  }
6400
6400
  //#endregion
6401
+ //#region src/form_field_readonly.js
6402
+ /***********************************************************************
6403
+ * form_field_readonly.js
6404
+ *
6405
+ * Is this field of the form read-only?
6406
+ *
6407
+ * Pure, so the rule can be tested without a DOM: the gclass turns
6408
+ * the answer into `readonly`/`disabled` on a control, this decides
6409
+ * the answer. Same split as form_toolbar_plan.js and
6410
+ * treedb_write_plan.js.
6411
+ *
6412
+ * The rule it exists to state ONCE: **`writable` governs the WRITE
6413
+ * of a column's VALUE, and an fkey is not written — it is LINKED.**
6414
+ * An fkey column is normally declared with no `writable` flag at
6415
+ * all (`treedb_authzs`'s `users.roles` is `['fkey']`, and so is
6416
+ * almost every fkey in the tree), so reading "not writable" as "not
6417
+ * editable" takes away the only way to link a record to its parent
6418
+ * — with `users.roles`, the only way to give a person a role.
6419
+ *
6420
+ * The other half of this rule is already written down in
6421
+ * c_yui_treedb_topic_with_form.js, where the record travels back:
6422
+ * the form sends "the writable cols, the fkeys (a link is edited by
6423
+ * linking) and the pkey". The two halves have to agree, and when
6424
+ * they did not, the form disabled the control whose value the save
6425
+ * path was still waiting for.
6426
+ *
6427
+ * A `file` column is an fkey too (flagged `['fkey','file']`) and is
6428
+ * NOT covered: it answers `type: "file"`, its bytes travel beside
6429
+ * the record, and the SDK declares as legal a `file` column that
6430
+ * only a load fills.
6431
+ *
6432
+ * Copyright (c) 2026, ArtGins.
6433
+ * All Rights Reserved.
6434
+ ***********************************************************************/
6435
+ /***************************************************************
6436
+ * field_is_readonly(form_readonly, field_desc) -> boolean
6437
+ *
6438
+ * form_readonly the FORM is read-only: a record opened to be
6439
+ * looked at, or a store nobody may write. It is
6440
+ * a property of the opening, not of the schema,
6441
+ * and it wins over everything.
6442
+ * field_desc as treedb_get_field_desc() answers it
6443
+ * (`type`, `is_writable`).
6444
+ ***************************************************************/
6445
+ function field_is_readonly(form_readonly, field_desc) {
6446
+ if (form_readonly) return true;
6447
+ let desc = field_desc || {};
6448
+ if (desc.type === "fkey") return false;
6449
+ return !desc.is_writable;
6450
+ }
6451
+ //#endregion
6401
6452
  //#region src/yui_theme.js
6402
6453
  /***********************************************************************
6403
6454
  * yui_theme.js
@@ -7210,7 +7261,7 @@ function build_form_field_conf(gobj, field_desc) {
7210
7261
  placeholder: "",
7211
7262
  options: [],
7212
7263
  extras: {},
7213
- readonly: gobj_read_bool_attr(gobj, "readonly") || !field_desc.is_writable,
7264
+ readonly: field_is_readonly(gobj_read_bool_attr(gobj, "readonly"), field_desc),
7214
7265
  required: field_desc.is_required,
7215
7266
  hidden: field_desc.is_hidden,
7216
7267
  default_value: field_desc.default_value
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.70",
3
+ "version": "7.23.71",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
package/src/c_yui_form.js CHANGED
@@ -65,6 +65,7 @@ import {
65
65
  import "./c_yui_form.css";
66
66
  import {attach_clear, refresh_clear} from "./yui_inputs.js";
67
67
  import {plan_toolbar, DEFAULT_TOOLBAR} from "./form_toolbar_plan.js";
68
+ import {field_is_readonly} from "./form_field_readonly.js";
68
69
  /* Read at field-build time only — deliberately NOT watched: the hosting
69
70
  * dialog rebuilds the form on every open, and re-rendering a form under
70
71
  * the user mid-edit would throw away what they typed. */
@@ -601,8 +602,13 @@ function build_form_field_conf(gobj, field_desc)
601
602
  extras: {}, // extra html input/textarea attributes
602
603
  /* A form opened to LOOK at a record has no editable field, and
603
604
  * that is a property of the opening, not of the schema: the same
604
- * col is writable when the same form is opened to edit. */
605
- readonly: gobj_read_bool_attr(gobj, "readonly") || !field_desc.is_writable,
605
+ * col is writable when the same form is opened to edit. And an
606
+ * fkey is editable WITHOUT `writable`, because a link is not
607
+ * written, it is linked -- the rule and its why live in
608
+ * form_field_readonly.js. */
609
+ readonly: field_is_readonly(
610
+ gobj_read_bool_attr(gobj, "readonly"), field_desc
611
+ ),
606
612
  required: field_desc.is_required,
607
613
  hidden: field_desc.is_hidden,
608
614
  default_value: field_desc.default_value,
@@ -0,0 +1,61 @@
1
+ /***********************************************************************
2
+ * form_field_readonly.js
3
+ *
4
+ * Is this field of the form read-only?
5
+ *
6
+ * Pure, so the rule can be tested without a DOM: the gclass turns
7
+ * the answer into `readonly`/`disabled` on a control, this decides
8
+ * the answer. Same split as form_toolbar_plan.js and
9
+ * treedb_write_plan.js.
10
+ *
11
+ * The rule it exists to state ONCE: **`writable` governs the WRITE
12
+ * of a column's VALUE, and an fkey is not written — it is LINKED.**
13
+ * An fkey column is normally declared with no `writable` flag at
14
+ * all (`treedb_authzs`'s `users.roles` is `['fkey']`, and so is
15
+ * almost every fkey in the tree), so reading "not writable" as "not
16
+ * editable" takes away the only way to link a record to its parent
17
+ * — with `users.roles`, the only way to give a person a role.
18
+ *
19
+ * The other half of this rule is already written down in
20
+ * c_yui_treedb_topic_with_form.js, where the record travels back:
21
+ * the form sends "the writable cols, the fkeys (a link is edited by
22
+ * linking) and the pkey". The two halves have to agree, and when
23
+ * they did not, the form disabled the control whose value the save
24
+ * path was still waiting for.
25
+ *
26
+ * A `file` column is an fkey too (flagged `['fkey','file']`) and is
27
+ * NOT covered: it answers `type: "file"`, its bytes travel beside
28
+ * the record, and the SDK declares as legal a `file` column that
29
+ * only a load fills.
30
+ *
31
+ * Copyright (c) 2026, ArtGins.
32
+ * All Rights Reserved.
33
+ ***********************************************************************/
34
+
35
+
36
+ /***************************************************************
37
+ * field_is_readonly(form_readonly, field_desc) -> boolean
38
+ *
39
+ * form_readonly the FORM is read-only: a record opened to be
40
+ * looked at, or a store nobody may write. It is
41
+ * a property of the opening, not of the schema,
42
+ * and it wins over everything.
43
+ * field_desc as treedb_get_field_desc() answers it
44
+ * (`type`, `is_writable`).
45
+ ***************************************************************/
46
+ function field_is_readonly(form_readonly, field_desc)
47
+ {
48
+ if(form_readonly) {
49
+ return true;
50
+ }
51
+
52
+ let desc = field_desc || {};
53
+ if(desc.type === "fkey") {
54
+ return false;
55
+ }
56
+
57
+ return !desc.is_writable;
58
+ }
59
+
60
+
61
+ export {field_is_readonly};
@@ -0,0 +1,50 @@
1
+ import {describe, it, expect} from "vitest";
2
+ import {field_is_readonly} from "./form_field_readonly.js";
3
+
4
+ describe("field_is_readonly", () => {
5
+
6
+ it("a form opened to LOOK has no editable field, fkey included", () => {
7
+ /* A record opened to be read: the schema does not enter into it. */
8
+ expect(field_is_readonly(true, {type: "fkey", is_writable: false}))
9
+ .toBe(true);
10
+ expect(field_is_readonly(true, {type: "fkey", is_writable: true}))
11
+ .toBe(true);
12
+ expect(field_is_readonly(true, {type: "string", is_writable: true}))
13
+ .toBe(true);
14
+ });
15
+
16
+ it("an fkey is EDITABLE without `writable`: a link is edited by linking", () => {
17
+ /* `treedb_authzs`'s `users.roles` is declared `['fkey']` and
18
+ * nothing else -- as almost every fkey in the tree is. Reading
19
+ * that as "not editable" left a user with no way to be given a
20
+ * role, while the save path was still sending the column. */
21
+ expect(field_is_readonly(false, {type: "fkey", is_writable: false}))
22
+ .toBe(false);
23
+ });
24
+
25
+ it("a plain column still needs `writable`", () => {
26
+ expect(field_is_readonly(false, {type: "string", is_writable: false}))
27
+ .toBe(true);
28
+ expect(field_is_readonly(false, {type: "string", is_writable: true}))
29
+ .toBe(false);
30
+ expect(field_is_readonly(false, {type: "enum", is_writable: false}))
31
+ .toBe(true);
32
+ });
33
+
34
+ it("a `file` column is NOT covered by the fkey exemption", () => {
35
+ /* It is an fkey (`['fkey','file']`) but answers `type: "file"`,
36
+ * and the SDK declares as legal a `file` column that only a load
37
+ * fills. */
38
+ expect(field_is_readonly(false, {type: "file", is_writable: false, is_file: true}))
39
+ .toBe(true);
40
+ expect(field_is_readonly(false, {type: "file", is_writable: true, is_file: true}))
41
+ .toBe(false);
42
+ });
43
+
44
+ it("no descriptor is read-only, not editable", () => {
45
+ /* Nothing known about a field is not a reason to let it be
46
+ * written. */
47
+ expect(field_is_readonly(false, undefined)).toBe(true);
48
+ expect(field_is_readonly(false, {})).toBe(true);
49
+ });
50
+ });