@yuneta/gobj-ui 7.23.56 → 7.23.58

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.
@@ -5900,18 +5900,18 @@ function refresh_clear($input) {
5900
5900
  * Copyright (c) 2026, ArtGins.
5901
5901
  * All Rights Reserved.
5902
5902
  ***********************************************************************/
5903
- var LEFT_BUTTONS = [
5904
- "save",
5903
+ var LEFT_BUTTONS = ["copy", "paste"];
5904
+ var RIGHT_BUTTONS = [
5905
5905
  "undo",
5906
- "clear"
5906
+ "clear",
5907
+ "save"
5907
5908
  ];
5908
- var RIGHT_BUTTONS = ["copy", "paste"];
5909
5909
  var DEFAULT_TOOLBAR = [
5910
- "save",
5910
+ "copy",
5911
+ "paste",
5911
5912
  "undo",
5912
5913
  "clear",
5913
- "copy",
5914
- "paste"
5914
+ "save"
5915
5915
  ];
5916
5916
  /***************************************************************
5917
5917
  * plan_toolbar(wanted) -> {left, right, unknown}
@@ -7064,7 +7064,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
7064
7064
  "div",
7065
7065
  {
7066
7066
  class: "xcontrol control",
7067
- style: "max-width:100px;"
7067
+ style: "max-width:220px;"
7068
7068
  },
7069
7069
  []
7070
7070
  ]]
@@ -7322,6 +7322,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
7322
7322
  ]);
7323
7323
  $control.appendChild($extend);
7324
7324
  if (inputType !== "color" && inputType !== "datetime-local" && (!readonly || is_pkey)) attach_clear($control, $extend);
7325
+ if (inputType === "color") attach_color_value($control, $extend);
7325
7326
  break;
7326
7327
  }
7327
7328
  case "textarea": {
@@ -7520,6 +7521,37 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
7520
7521
  return $element;
7521
7522
  }
7522
7523
  /******************************************************************
7524
+ * A colour swatch beside its own value.
7525
+ *
7526
+ * The swatch says WHICH colour and nothing else -- and the value is
7527
+ * the half a person needs: to read it, to write it down, to paste it
7528
+ * into the next record. Read-only it was unreachable altogether;
7529
+ * editable it was a colour you could only pick, never copy.
7530
+ *
7531
+ * A `<span>`, not a second input: the record is collected from the
7532
+ * controls marked `yui-form-data-input`, and one field with two of
7533
+ * them is a field read twice. It carries `user-select: all`, so one
7534
+ * click takes the whole value and not the half a double-click would
7535
+ * stop at (`#e0a800` is three words to a browser).
7536
+ ******************************************************************/
7537
+ function attach_color_value($control, $input) {
7538
+ let $value = (0, _yuneta_gobj_js.createElement2)([
7539
+ "span",
7540
+ { class: "yui-color-value is-family-monospace is-size-7" },
7541
+ ""
7542
+ ]);
7543
+ function refresh() {
7544
+ let v = $input.value || "";
7545
+ $value.textContent = v;
7546
+ $value.title = v;
7547
+ }
7548
+ refresh();
7549
+ $input.addEventListener("input", refresh);
7550
+ $input.addEventListener("change", refresh);
7551
+ $input.yui_color_value = $value;
7552
+ $control.appendChild($value);
7553
+ }
7554
+ /******************************************************************
7523
7555
  * `readonly` is an attribute of a TEXT control and of nothing else.
7524
7556
  *
7525
7557
  * A `<select>` ignores it. So does a checkbox, and so does a radio:
@@ -8099,6 +8131,10 @@ function set_form_values(gobj, template, $form, record) {
8099
8131
  break;
8100
8132
  case "color":
8101
8133
  $input.value = convertColor(value);
8134
+ if ($input.yui_color_value) {
8135
+ $input.yui_color_value.textContent = $input.value;
8136
+ $input.yui_color_value.title = $input.value;
8137
+ }
8102
8138
  break;
8103
8139
  default: $input.value = value;
8104
8140
  }
@@ -5895,18 +5895,18 @@ function refresh_clear($input) {
5895
5895
  * Copyright (c) 2026, ArtGins.
5896
5896
  * All Rights Reserved.
5897
5897
  ***********************************************************************/
5898
- var LEFT_BUTTONS = [
5899
- "save",
5898
+ var LEFT_BUTTONS = ["copy", "paste"];
5899
+ var RIGHT_BUTTONS = [
5900
5900
  "undo",
5901
- "clear"
5901
+ "clear",
5902
+ "save"
5902
5903
  ];
5903
- var RIGHT_BUTTONS = ["copy", "paste"];
5904
5904
  var DEFAULT_TOOLBAR = [
5905
- "save",
5905
+ "copy",
5906
+ "paste",
5906
5907
  "undo",
5907
5908
  "clear",
5908
- "copy",
5909
- "paste"
5909
+ "save"
5910
5910
  ];
5911
5911
  /***************************************************************
5912
5912
  * plan_toolbar(wanted) -> {left, right, unknown}
@@ -7059,7 +7059,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
7059
7059
  "div",
7060
7060
  {
7061
7061
  class: "xcontrol control",
7062
- style: "max-width:100px;"
7062
+ style: "max-width:220px;"
7063
7063
  },
7064
7064
  []
7065
7065
  ]]
@@ -7317,6 +7317,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
7317
7317
  ]);
7318
7318
  $control.appendChild($extend);
7319
7319
  if (inputType !== "color" && inputType !== "datetime-local" && (!readonly || is_pkey)) attach_clear($control, $extend);
7320
+ if (inputType === "color") attach_color_value($control, $extend);
7320
7321
  break;
7321
7322
  }
7322
7323
  case "textarea": {
@@ -7515,6 +7516,37 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
7515
7516
  return $element;
7516
7517
  }
7517
7518
  /******************************************************************
7519
+ * A colour swatch beside its own value.
7520
+ *
7521
+ * The swatch says WHICH colour and nothing else -- and the value is
7522
+ * the half a person needs: to read it, to write it down, to paste it
7523
+ * into the next record. Read-only it was unreachable altogether;
7524
+ * editable it was a colour you could only pick, never copy.
7525
+ *
7526
+ * A `<span>`, not a second input: the record is collected from the
7527
+ * controls marked `yui-form-data-input`, and one field with two of
7528
+ * them is a field read twice. It carries `user-select: all`, so one
7529
+ * click takes the whole value and not the half a double-click would
7530
+ * stop at (`#e0a800` is three words to a browser).
7531
+ ******************************************************************/
7532
+ function attach_color_value($control, $input) {
7533
+ let $value = createElement2([
7534
+ "span",
7535
+ { class: "yui-color-value is-family-monospace is-size-7" },
7536
+ ""
7537
+ ]);
7538
+ function refresh() {
7539
+ let v = $input.value || "";
7540
+ $value.textContent = v;
7541
+ $value.title = v;
7542
+ }
7543
+ refresh();
7544
+ $input.addEventListener("input", refresh);
7545
+ $input.addEventListener("change", refresh);
7546
+ $input.yui_color_value = $value;
7547
+ $control.appendChild($value);
7548
+ }
7549
+ /******************************************************************
7518
7550
  * `readonly` is an attribute of a TEXT control and of nothing else.
7519
7551
  *
7520
7552
  * A `<select>` ignores it. So does a checkbox, and so does a radio:
@@ -8094,6 +8126,10 @@ function set_form_values(gobj, template, $form, record) {
8094
8126
  break;
8095
8127
  case "color":
8096
8128
  $input.value = convertColor(value);
8129
+ if ($input.yui_color_value) {
8130
+ $input.yui_color_value.textContent = $input.value;
8131
+ $input.yui_color_value.title = $input.value;
8132
+ }
8097
8133
  break;
8098
8134
  default: $input.value = value;
8099
8135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.56",
3
+ "version": "7.23.58",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -79,3 +79,18 @@
79
79
  .C_YUI_FORM .yui-jse-readonly {
80
80
  background-color: var(--bulma-background, #f5f5f5);
81
81
  }
82
+
83
+ /* The value of a colour, beside its swatch: what the swatch cannot say.
84
+ * `user-select: all` so one click takes the whole `#e0a800` -- to a
85
+ * browser that is three words, and a double-click stops at one of them. */
86
+ .C_YUI_FORM .yui-color-value {
87
+ margin-left: 0.5rem;
88
+ vertical-align: middle;
89
+ color: var(--bulma-text-weak, #7a7a7a);
90
+ user-select: all;
91
+ -webkit-user-select: all;
92
+ cursor: text;
93
+ }
94
+ .C_YUI_FORM input[type="color"] {
95
+ vertical-align: middle;
96
+ }
package/src/c_yui_form.js CHANGED
@@ -1024,7 +1024,9 @@ function create_form_field(
1024
1024
 
1025
1025
  ['div', {class: 'field-body'}, [
1026
1026
  ['div', {class: 'field'}, [
1027
- ['div', {class: 'xcontrol control', style: 'max-width:100px;'}, []]
1027
+ /* Room for the swatch AND its value
1028
+ * beside it (attach_color_value). */
1029
+ ['div', {class: 'xcontrol control', style: 'max-width:220px;'}, []]
1028
1030
  ]]
1029
1031
  ]]
1030
1032
  ]]
@@ -1201,6 +1203,9 @@ function create_form_field(
1201
1203
  (!readonly || is_pkey)) {
1202
1204
  attach_clear($control, $extend);
1203
1205
  }
1206
+ if(inputType === 'color') {
1207
+ attach_color_value($control, $extend);
1208
+ }
1204
1209
  break;
1205
1210
  }
1206
1211
 
@@ -1456,6 +1461,43 @@ function create_form_field(
1456
1461
  return $element;
1457
1462
  }
1458
1463
 
1464
+ /******************************************************************
1465
+ * A colour swatch beside its own value.
1466
+ *
1467
+ * The swatch says WHICH colour and nothing else -- and the value is
1468
+ * the half a person needs: to read it, to write it down, to paste it
1469
+ * into the next record. Read-only it was unreachable altogether;
1470
+ * editable it was a colour you could only pick, never copy.
1471
+ *
1472
+ * A `<span>`, not a second input: the record is collected from the
1473
+ * controls marked `yui-form-data-input`, and one field with two of
1474
+ * them is a field read twice. It carries `user-select: all`, so one
1475
+ * click takes the whole value and not the half a double-click would
1476
+ * stop at (`#e0a800` is three words to a browser).
1477
+ ******************************************************************/
1478
+ function attach_color_value($control, $input)
1479
+ {
1480
+ let $value = createElement2(
1481
+ ['span', {class: 'yui-color-value is-family-monospace is-size-7'}, '']
1482
+ );
1483
+
1484
+ function refresh() {
1485
+ let v = $input.value || "";
1486
+ $value.textContent = v;
1487
+ $value.title = v;
1488
+ }
1489
+
1490
+ refresh();
1491
+ $input.addEventListener('input', refresh);
1492
+ /* The swatch's value is also written from outside (loading a record
1493
+ * into the form), and that fires nothing. `set_field_value()` ends
1494
+ * in a synthetic `change`, which is why this listens to both. */
1495
+ $input.addEventListener('change', refresh);
1496
+ $input.yui_color_value = $value;
1497
+
1498
+ $control.appendChild($value);
1499
+ }
1500
+
1459
1501
  /******************************************************************
1460
1502
  * `readonly` is an attribute of a TEXT control and of nothing else.
1461
1503
  *
@@ -2398,6 +2440,12 @@ function set_form_values(gobj, template, $form, record)
2398
2440
  break;
2399
2441
  case "color":
2400
2442
  $input.value = convertColor(value);
2443
+ /* Writing `.value` fires nothing, so the readout
2444
+ * beside the swatch has to be told. */
2445
+ if($input.yui_color_value) {
2446
+ $input.yui_color_value.textContent = $input.value;
2447
+ $input.yui_color_value.title = $input.value;
2448
+ }
2401
2449
  break;
2402
2450
  default:
2403
2451
  $input.value = value;
@@ -10,13 +10,25 @@
10
10
  * All Rights Reserved.
11
11
  ***********************************************************************/
12
12
 
13
- /* Save/undo/clear sit on the left of the bar, copy/paste on the right.
14
- * That split is what the layout has always drawn, and keeping it means
15
- * a caller who drops one group does not leave a hole in the middle. */
16
- const LEFT_BUTTONS = ["save", "undo", "clear"];
17
- const RIGHT_BUTTONS = ["copy", "paste"];
13
+ /* Two groups, and the split is a real one: `undo`, `clear` and `save`
14
+ * act on the RECORD being edited, `copy` and `paste` move it in and out
15
+ * of the clipboard. Keeping them apart means a caller who drops one
16
+ * group does not leave a hole in the middle.
17
+ *
18
+ * WHICH SIDE EACH ONE TAKES CHANGED (2026-09-04), and the reason is that
19
+ * the form changed shape: it was an embedded panel and it opens as a
20
+ * MODAL DIALOG now. In a dialog the primary action goes bottom-RIGHT --
21
+ * every platform puts it there, and it is where the eye ends after
22
+ * reading the fields. `save` sat hard against the left edge, which is
23
+ * where a person looks last.
24
+ *
25
+ * So: clipboard on the left, record actions on the right, and `save`
26
+ * LAST of all. The order inside a group is the order asked for, which
27
+ * is why DEFAULT_TOOLBAR is written in the order it is drawn. */
28
+ const LEFT_BUTTONS = ["copy", "paste"];
29
+ const RIGHT_BUTTONS = ["undo", "clear", "save"];
18
30
 
19
- const DEFAULT_TOOLBAR = ["save", "undo", "clear", "copy", "paste"];
31
+ const DEFAULT_TOOLBAR = ["copy", "paste", "undo", "clear", "save"];
20
32
 
21
33
 
22
34
  /***************************************************************
@@ -5,17 +5,26 @@ describe("plan_toolbar", () => {
5
5
 
6
6
  it("says nothing -> the five buttons this gclass always had", () => {
7
7
  const p = plan_toolbar(undefined);
8
- expect(p.left).toEqual(["save", "undo", "clear"]);
9
- expect(p.right).toEqual(["copy", "paste"]);
8
+ expect(p.left).toEqual(["copy", "paste"]);
9
+ expect(p.right).toEqual(["undo", "clear", "save"]);
10
10
  expect(p.unknown).toEqual([]);
11
11
  expect(DEFAULT_TOOLBAR.length).toBe(5);
12
12
  });
13
13
 
14
+ it("save is the LAST button of the bar", () => {
15
+ /* The form opens as a modal dialog and a dialog's primary action
16
+ * goes bottom-right. Asserted on the plan and not on the CSS,
17
+ * because the plan is what decides the two groups. */
18
+ const p = plan_toolbar(undefined);
19
+ expect(p.right[p.right.length - 1]).toBe("save");
20
+ expect(p.left).not.toContain("save");
21
+ });
22
+
14
23
  it("a non-array is not an empty toolbar: it is the default", () => {
15
24
  /* Reading a JSON attr that was never set must not silently
16
25
  * leave a form with no save button. */
17
26
  for(const v of [null, "save", 0, {}]) {
18
- expect(plan_toolbar(v).left).toContain("save");
27
+ expect(plan_toolbar(v).right).toContain("save");
19
28
  }
20
29
  });
21
30
 
@@ -28,21 +37,21 @@ describe("plan_toolbar", () => {
28
37
 
29
38
  it("one action: a dialog asks for save only", () => {
30
39
  const p = plan_toolbar(["save"]);
31
- expect(p.left).toEqual(["save"]);
32
- expect(p.right).toEqual([]);
40
+ expect(p.right).toEqual(["save"]);
41
+ expect(p.left).toEqual([]);
33
42
  });
34
43
 
35
44
  it("keeps the order asked for inside each side", () => {
36
45
  const p = plan_toolbar(["clear", "save", "paste", "copy"]);
37
- expect(p.left).toEqual(["clear", "save"]);
38
- expect(p.right).toEqual(["paste", "copy"]);
46
+ expect(p.right).toEqual(["clear", "save"]);
47
+ expect(p.left).toEqual(["paste", "copy"]);
39
48
  });
40
49
 
41
50
  it("an unknown name is REPORTED, never silently dropped", () => {
42
51
  /* A typo here would otherwise remove a button and leave no
43
52
  * trace of why it is missing. */
44
53
  const p = plan_toolbar(["save", "sav", "undo"]);
45
- expect(p.left).toEqual(["save", "undo"]);
54
+ expect(p.right).toEqual(["save", "undo"]);
46
55
  expect(p.unknown).toEqual(["sav"]);
47
56
  });
48
57
 
@@ -59,7 +68,7 @@ describe("plan_toolbar", () => {
59
68
 
60
69
  it("drops one whole side without disturbing the other", () => {
61
70
  const p = plan_toolbar(["save", "undo", "clear"]);
62
- expect(p.left).toEqual(["save", "undo", "clear"]);
63
- expect(p.right).toEqual([]);
71
+ expect(p.right).toEqual(["save", "undo", "clear"]);
72
+ expect(p.left).toEqual([]);
64
73
  });
65
74
  });