@yuneta/gobj-ui 5.6.0 → 5.7.1

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.
@@ -890,7 +890,7 @@ function mt_start$15(gobj) {
890
890
  if (typeof shell_cfg.remember_section_position === "boolean") (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "remember_section_position", shell_cfg.remember_section_position);
891
891
  build_item_index(gobj, config);
892
892
  instantiate_menus(gobj, config);
893
- build_toolbar(gobj, config);
893
+ build_toolbar$1(gobj, config);
894
894
  preinstantiate_eager_views(gobj);
895
895
  if ((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "use_hash")) {
896
896
  priv.hash_handler = () => {
@@ -1616,7 +1616,7 @@ function safe_id(s) {
1616
1616
  * Per-item `show_on` is honoured: each rendered item is wrapped with
1617
1617
  * the same Bulma-helper logic as zones.
1618
1618
  ************************************************************/
1619
- function build_toolbar(gobj, config) {
1619
+ function build_toolbar$1(gobj, config) {
1620
1620
  let tb = config && config.toolbar;
1621
1621
  if (!tb || !(0, _yuneta_gobj_js.is_array)(tb.items)) return;
1622
1622
  let priv = gobj.priv;
@@ -5760,6 +5760,59 @@ function refresh_clear($input) {
5760
5760
  $btn.classList.toggle("is-visible", clear_visible($input));
5761
5761
  }
5762
5762
  //#endregion
5763
+ //#region src/form_toolbar_plan.js
5764
+ /***********************************************************************
5765
+ * form_toolbar_plan.js
5766
+ *
5767
+ * Which buttons the C_YUI_FORM toolbar shows, and where.
5768
+ *
5769
+ * Pure, so it can be tested without a DOM: the gclass turns the
5770
+ * plan into elements, this decides the plan.
5771
+ *
5772
+ * Copyright (c) 2026, ArtGins.
5773
+ * All Rights Reserved.
5774
+ ***********************************************************************/
5775
+ var LEFT_BUTTONS = [
5776
+ "save",
5777
+ "undo",
5778
+ "clear"
5779
+ ];
5780
+ var RIGHT_BUTTONS = ["copy", "paste"];
5781
+ var DEFAULT_TOOLBAR = [
5782
+ "save",
5783
+ "undo",
5784
+ "clear",
5785
+ "copy",
5786
+ "paste"
5787
+ ];
5788
+ /***************************************************************
5789
+ * plan_toolbar(wanted) -> {left, right, unknown}
5790
+ *
5791
+ * wanted an array of button names, in the order asked for.
5792
+ * Anything that is not an array means "the default
5793
+ * five", so a caller that sets nothing keeps exactly
5794
+ * the toolbar this gclass always had.
5795
+ * [] means no toolbar at all.
5796
+ *
5797
+ * unknown names that match no button. They are reported and
5798
+ * NOT silently dropped: a typo in the list would
5799
+ * otherwise remove the save button with no trace.
5800
+ ***************************************************************/
5801
+ function plan_toolbar(wanted) {
5802
+ if (!Array.isArray(wanted)) wanted = DEFAULT_TOOLBAR;
5803
+ let left = [];
5804
+ let right = [];
5805
+ let unknown = [];
5806
+ for (let name of wanted) if (LEFT_BUTTONS.indexOf(name) >= 0) left.push(name);
5807
+ else if (RIGHT_BUTTONS.indexOf(name) >= 0) right.push(name);
5808
+ else unknown.push(name);
5809
+ return {
5810
+ left,
5811
+ right,
5812
+ unknown
5813
+ };
5814
+ }
5815
+ //#endregion
5763
5816
  //#region src/yui_theme.js
5764
5817
  /***********************************************************************
5765
5818
  * yui_theme.js
@@ -5891,6 +5944,13 @@ var attrs_table$13 = [
5891
5944
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_checkbox", 0, false, "Show a first column with checkbox to select rows"),
5892
5945
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_rownum", 0, false, "Print first column with row number"),
5893
5946
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_delete_row_btn", 0, false, "Print last column with delete row button"),
5947
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "toolbar", 0, [
5948
+ "save",
5949
+ "undo",
5950
+ "clear",
5951
+ "copy",
5952
+ "paste"
5953
+ ], "Toolbar buttons to show, in order: save, undo, clear, copy, paste. [] hides the toolbar"),
5894
5954
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "tabulator_settings", 0, {
5895
5955
  layout: "fitDataFill",
5896
5956
  validationMode: "highlight",
@@ -5953,14 +6013,20 @@ function isEventSet(table, event) {
5953
6013
  function set_changed_stated(gobj, changed) {
5954
6014
  let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
5955
6015
  let $form = $container.querySelector("form");
6016
+ const enable = ($btn, on, color) => {
6017
+ if (!$btn) return;
6018
+ if (on) $btn.removeAttribute("disabled");
6019
+ else $btn.setAttribute("disabled", true);
6020
+ const $i = $btn.querySelector("span i");
6021
+ if ($i) $i.style.color = color;
6022
+ };
6023
+ const OFF_COLOR = "hsl(var(--bulma-button-h), var(--bulma-button-s), var(--bulma-button-color-l))";
5956
6024
  let $button_save = $container.querySelector(".button-save");
5957
6025
  let $button_undo = $container.querySelector(".button-undo");
5958
6026
  if (changed) {
5959
6027
  (0, _yuneta_gobj_js.gobj_write_bool_attr)(gobj, "changes", true);
5960
- $button_save.removeAttribute("disabled");
5961
- $button_undo.removeAttribute("disabled");
5962
- $button_save.querySelector("span i").style.color = "MediumSeaGreen";
5963
- $button_undo.querySelector("span i").style.color = "Magenta";
6028
+ enable($button_save, true, "MediumSeaGreen");
6029
+ enable($button_undo, true, "Magenta");
5964
6030
  $form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
5965
6031
  if (isEventSet($table.tabulator, "dataChanged")) $table.tabulator.off("dataChanged");
5966
6032
  $table.querySelectorAll(".yui-tabulator-table-nested").forEach(($table_nested) => {
@@ -5969,10 +6035,8 @@ function set_changed_stated(gobj, changed) {
5969
6035
  });
5970
6036
  } else {
5971
6037
  (0, _yuneta_gobj_js.gobj_write_bool_attr)(gobj, "changes", false);
5972
- $button_save.setAttribute("disabled", true);
5973
- $button_undo.setAttribute("disabled", true);
5974
- $button_save.querySelector("span i").style.color = "hsl(var(--bulma-button-h), var(--bulma-button-s), var(--bulma-button-color-l))";
5975
- $button_undo.querySelector("span i").style.color = "hsl(var(--bulma-button-h), var(--bulma-button-s), var(--bulma-button-color-l))";
6038
+ enable($button_save, false, OFF_COLOR);
6039
+ enable($button_undo, false, OFF_COLOR);
5976
6040
  setTimeout(function() {
5977
6041
  $form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
5978
6042
  if (isEventSet($table.tabulator, "dataChanged")) $table.tabulator.off("dataChanged");
@@ -6001,6 +6065,175 @@ async function readClipboard$1(gobj) {
6001
6065
  }
6002
6066
  }
6003
6067
  /************************************************************
6068
+ * Bottom toolbar, from the `toolbar` attr.
6069
+ *
6070
+ * The five buttons it can show, and their order, are the
6071
+ * caller's: a dialog with a single action asks for ["save"],
6072
+ * and [] leaves no toolbar at all. Default is the five it
6073
+ * always had, so nothing moves for a caller that says nothing.
6074
+ ************************************************************/
6075
+ function toolbar_button(gobj, name) {
6076
+ switch (name) {
6077
+ case "save": return [
6078
+ "button",
6079
+ {
6080
+ class: "button p-1 button-save",
6081
+ title: "save",
6082
+ "aria-label": "save",
6083
+ disabled: true
6084
+ },
6085
+ [[
6086
+ "span",
6087
+ { class: "icon m-0" },
6088
+ "<i class=\"yi-floppy-disk\"></i>"
6089
+ ], [
6090
+ "span",
6091
+ {
6092
+ class: "is-hidden-mobile pl-1 pr-1",
6093
+ i18n: "save"
6094
+ },
6095
+ "save"
6096
+ ]],
6097
+ { click: function(evt) {
6098
+ evt.stopPropagation();
6099
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SAVE_RECORD", {}, gobj);
6100
+ } }
6101
+ ];
6102
+ case "undo": return [
6103
+ "button",
6104
+ {
6105
+ class: "button p-1 button-undo",
6106
+ title: "undo",
6107
+ "aria-label": "undo",
6108
+ disabled: true
6109
+ },
6110
+ [[
6111
+ "span",
6112
+ { class: "icon m-0" },
6113
+ "<i class=\"yi-arrow-rotate-left\"></i>"
6114
+ ], [
6115
+ "span",
6116
+ {
6117
+ class: "is-hidden-mobile pl-1 pr-1",
6118
+ i18n: "undo"
6119
+ },
6120
+ "undo"
6121
+ ]],
6122
+ { click: function(evt) {
6123
+ evt.stopPropagation();
6124
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_UNDO_RECORD", {}, gobj);
6125
+ } }
6126
+ ];
6127
+ case "clear": return [
6128
+ "button",
6129
+ {
6130
+ class: "button p-1",
6131
+ title: "clear",
6132
+ "aria-label": "clear"
6133
+ },
6134
+ [[
6135
+ "span",
6136
+ { class: "icon m-0" },
6137
+ "<i class=\"yi-broom-wide\"></i>"
6138
+ ], [
6139
+ "span",
6140
+ {
6141
+ class: "is-hidden-mobile pl-1 pr-1",
6142
+ i18n: "clear"
6143
+ },
6144
+ "clear"
6145
+ ]],
6146
+ { click: function(evt) {
6147
+ evt.stopPropagation();
6148
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CLEAR_RECORD", {}, gobj);
6149
+ } }
6150
+ ];
6151
+ case "copy": return [
6152
+ "button",
6153
+ {
6154
+ class: "button p-1",
6155
+ title: "copy",
6156
+ "aria-label": "copy"
6157
+ },
6158
+ [[
6159
+ "span",
6160
+ { class: "icon m-0" },
6161
+ "<i class=\"yi-copy\"></i>"
6162
+ ], [
6163
+ "span",
6164
+ {
6165
+ class: "is-hidden-mobile pl-1 pr-1",
6166
+ i18n: "copy"
6167
+ },
6168
+ "copy"
6169
+ ]],
6170
+ { click: function(evt) {
6171
+ evt.stopPropagation();
6172
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_COPY_RECORD", {}, gobj);
6173
+ } }
6174
+ ];
6175
+ case "paste": return [
6176
+ "button",
6177
+ {
6178
+ class: "button p-1",
6179
+ title: "paste",
6180
+ "aria-label": "paste"
6181
+ },
6182
+ [[
6183
+ "span",
6184
+ { class: "icon m-0" },
6185
+ "<i class=\"yi-paste\"></i>"
6186
+ ], [
6187
+ "span",
6188
+ {
6189
+ class: "is-hidden-mobile pl-1 pr-1",
6190
+ i18n: "paste"
6191
+ },
6192
+ "paste"
6193
+ ]],
6194
+ { click: async function(evt) {
6195
+ evt.stopPropagation();
6196
+ await readClipboard$1(gobj);
6197
+ } }
6198
+ ];
6199
+ default: return null;
6200
+ }
6201
+ }
6202
+ function build_toolbar(gobj) {
6203
+ const plan = plan_toolbar((0, _yuneta_gobj_js.gobj_read_attr)(gobj, "toolbar"));
6204
+ for (let name of plan.unknown) (0, _yuneta_gobj_js.log_warning)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: unknown toolbar button '${name}'`);
6205
+ if (!plan.left.length && !plan.right.length) return [
6206
+ "span",
6207
+ {
6208
+ class: "yui-toolbar-form-empty",
6209
+ style: "display:none;"
6210
+ },
6211
+ ""
6212
+ ];
6213
+ return [
6214
+ "div",
6215
+ {
6216
+ class: "yui-toolbar-form is-flex-shrink-0 is-flex is-flex-direction-row is-justify-content-space-between is-align-items-center",
6217
+ style: "width:100%; flex-wrap:wrap; row-gap:6px;"
6218
+ },
6219
+ [[
6220
+ "div",
6221
+ {
6222
+ class: "p-1 is-flex is-flex-direction-row is-align-items-center",
6223
+ style: "column-gap:6px;"
6224
+ },
6225
+ plan.left.map((n) => toolbar_button(gobj, n))
6226
+ ], [
6227
+ "div",
6228
+ {
6229
+ class: "p-1 is-flex is-flex-direction-row is-align-items-center",
6230
+ style: "column-gap:6px;"
6231
+ },
6232
+ plan.right.map((n) => toolbar_button(gobj, n))
6233
+ ]]
6234
+ ];
6235
+ }
6236
+ /************************************************************
6004
6237
  * Build UI
6005
6238
  ************************************************************/
6006
6239
  function build_ui$11(gobj) {
@@ -6017,149 +6250,7 @@ function build_ui$11(gobj) {
6017
6250
  style: "overflow-y:auto; min-height:0;"
6018
6251
  },
6019
6252
  build_form(gobj)
6020
- ], [
6021
- "div",
6022
- {
6023
- class: "yui-toolbar-form is-flex-shrink-0 is-flex is-flex-direction-row is-justify-content-space-between is-align-items-center",
6024
- style: "width:100%; flex-wrap:wrap; row-gap:6px;"
6025
- },
6026
- [[
6027
- "div",
6028
- {
6029
- class: "p-1 is-flex is-flex-direction-row is-align-items-center",
6030
- style: "column-gap:6px;"
6031
- },
6032
- [
6033
- [
6034
- "button",
6035
- {
6036
- class: "button p-1 button-save",
6037
- title: "save",
6038
- "aria-label": "save",
6039
- disabled: true
6040
- },
6041
- [[
6042
- "span",
6043
- { class: "icon m-0" },
6044
- "<i class=\"yi-floppy-disk\"></i>"
6045
- ], [
6046
- "span",
6047
- {
6048
- class: "is-hidden-mobile pl-1 pr-1",
6049
- i18n: "save"
6050
- },
6051
- "save"
6052
- ]],
6053
- { click: function(evt) {
6054
- evt.stopPropagation();
6055
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SAVE_RECORD", {}, gobj);
6056
- } }
6057
- ],
6058
- [
6059
- "button",
6060
- {
6061
- class: "button p-1 button-undo",
6062
- title: "undo",
6063
- "aria-label": "undo",
6064
- disabled: true
6065
- },
6066
- [[
6067
- "span",
6068
- { class: "icon m-0" },
6069
- "<i class=\"yi-arrow-rotate-left\"></i>"
6070
- ], [
6071
- "span",
6072
- {
6073
- class: "is-hidden-mobile pl-1 pr-1",
6074
- i18n: "undo"
6075
- },
6076
- "undo"
6077
- ]],
6078
- { click: function(evt) {
6079
- evt.stopPropagation();
6080
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_UNDO_RECORD", {}, gobj);
6081
- } }
6082
- ],
6083
- [
6084
- "button",
6085
- {
6086
- class: "button p-1",
6087
- title: "clear",
6088
- "aria-label": "clear"
6089
- },
6090
- [[
6091
- "span",
6092
- { class: "icon m-0" },
6093
- "<i class=\"yi-broom-wide\"></i>"
6094
- ], [
6095
- "span",
6096
- {
6097
- class: "is-hidden-mobile pl-1 pr-1",
6098
- i18n: "clear"
6099
- },
6100
- "clear"
6101
- ]],
6102
- { click: function(evt) {
6103
- evt.stopPropagation();
6104
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CLEAR_RECORD", {}, gobj);
6105
- } }
6106
- ]
6107
- ]
6108
- ], [
6109
- "div",
6110
- {
6111
- class: "p-1 is-flex is-flex-direction-row is-align-items-center",
6112
- style: "column-gap:6px;"
6113
- },
6114
- [[
6115
- "button",
6116
- {
6117
- class: "button p-1",
6118
- title: "copy",
6119
- "aria-label": "copy"
6120
- },
6121
- [[
6122
- "span",
6123
- { class: "icon m-0" },
6124
- "<i class=\"yi-copy\"></i>"
6125
- ], [
6126
- "span",
6127
- {
6128
- class: "is-hidden-mobile pl-1 pr-1",
6129
- i18n: "copy"
6130
- },
6131
- "copy"
6132
- ]],
6133
- { click: function(evt) {
6134
- evt.stopPropagation();
6135
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_COPY_RECORD", {}, gobj);
6136
- } }
6137
- ], [
6138
- "button",
6139
- {
6140
- class: "button p-1",
6141
- title: "paste",
6142
- "aria-label": "paste"
6143
- },
6144
- [[
6145
- "span",
6146
- { class: "icon m-0" },
6147
- "<i class=\"yi-paste\"></i>"
6148
- ], [
6149
- "span",
6150
- {
6151
- class: "is-hidden-mobile pl-1 pr-1",
6152
- i18n: "paste"
6153
- },
6154
- "paste"
6155
- ]],
6156
- { click: async function(evt) {
6157
- evt.stopPropagation();
6158
- await readClipboard$1(gobj);
6159
- } }
6160
- ]]
6161
- ]]
6162
- ]]
6253
+ ], build_toolbar(gobj)]
6163
6254
  ]);
6164
6255
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", $container);
6165
6256
  apply_form_mode(gobj, $container.querySelector("form"));
@@ -885,7 +885,7 @@ function mt_start$15(gobj) {
885
885
  if (typeof shell_cfg.remember_section_position === "boolean") gobj_write_attr(gobj, "remember_section_position", shell_cfg.remember_section_position);
886
886
  build_item_index(gobj, config);
887
887
  instantiate_menus(gobj, config);
888
- build_toolbar(gobj, config);
888
+ build_toolbar$1(gobj, config);
889
889
  preinstantiate_eager_views(gobj);
890
890
  if (gobj_read_attr(gobj, "use_hash")) {
891
891
  priv.hash_handler = () => {
@@ -1611,7 +1611,7 @@ function safe_id(s) {
1611
1611
  * Per-item `show_on` is honoured: each rendered item is wrapped with
1612
1612
  * the same Bulma-helper logic as zones.
1613
1613
  ************************************************************/
1614
- function build_toolbar(gobj, config) {
1614
+ function build_toolbar$1(gobj, config) {
1615
1615
  let tb = config && config.toolbar;
1616
1616
  if (!tb || !is_array(tb.items)) return;
1617
1617
  let priv = gobj.priv;
@@ -5755,6 +5755,59 @@ function refresh_clear($input) {
5755
5755
  $btn.classList.toggle("is-visible", clear_visible($input));
5756
5756
  }
5757
5757
  //#endregion
5758
+ //#region src/form_toolbar_plan.js
5759
+ /***********************************************************************
5760
+ * form_toolbar_plan.js
5761
+ *
5762
+ * Which buttons the C_YUI_FORM toolbar shows, and where.
5763
+ *
5764
+ * Pure, so it can be tested without a DOM: the gclass turns the
5765
+ * plan into elements, this decides the plan.
5766
+ *
5767
+ * Copyright (c) 2026, ArtGins.
5768
+ * All Rights Reserved.
5769
+ ***********************************************************************/
5770
+ var LEFT_BUTTONS = [
5771
+ "save",
5772
+ "undo",
5773
+ "clear"
5774
+ ];
5775
+ var RIGHT_BUTTONS = ["copy", "paste"];
5776
+ var DEFAULT_TOOLBAR = [
5777
+ "save",
5778
+ "undo",
5779
+ "clear",
5780
+ "copy",
5781
+ "paste"
5782
+ ];
5783
+ /***************************************************************
5784
+ * plan_toolbar(wanted) -> {left, right, unknown}
5785
+ *
5786
+ * wanted an array of button names, in the order asked for.
5787
+ * Anything that is not an array means "the default
5788
+ * five", so a caller that sets nothing keeps exactly
5789
+ * the toolbar this gclass always had.
5790
+ * [] means no toolbar at all.
5791
+ *
5792
+ * unknown names that match no button. They are reported and
5793
+ * NOT silently dropped: a typo in the list would
5794
+ * otherwise remove the save button with no trace.
5795
+ ***************************************************************/
5796
+ function plan_toolbar(wanted) {
5797
+ if (!Array.isArray(wanted)) wanted = DEFAULT_TOOLBAR;
5798
+ let left = [];
5799
+ let right = [];
5800
+ let unknown = [];
5801
+ for (let name of wanted) if (LEFT_BUTTONS.indexOf(name) >= 0) left.push(name);
5802
+ else if (RIGHT_BUTTONS.indexOf(name) >= 0) right.push(name);
5803
+ else unknown.push(name);
5804
+ return {
5805
+ left,
5806
+ right,
5807
+ unknown
5808
+ };
5809
+ }
5810
+ //#endregion
5758
5811
  //#region src/yui_theme.js
5759
5812
  /***********************************************************************
5760
5813
  * yui_theme.js
@@ -5886,6 +5939,13 @@ var attrs_table$13 = [
5886
5939
  SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, false, "Show a first column with checkbox to select rows"),
5887
5940
  SDATA(data_type_t.DTP_BOOLEAN, "with_rownum", 0, false, "Print first column with row number"),
5888
5941
  SDATA(data_type_t.DTP_BOOLEAN, "with_delete_row_btn", 0, false, "Print last column with delete row button"),
5942
+ SDATA(data_type_t.DTP_JSON, "toolbar", 0, [
5943
+ "save",
5944
+ "undo",
5945
+ "clear",
5946
+ "copy",
5947
+ "paste"
5948
+ ], "Toolbar buttons to show, in order: save, undo, clear, copy, paste. [] hides the toolbar"),
5889
5949
  SDATA(data_type_t.DTP_JSON, "tabulator_settings", 0, {
5890
5950
  layout: "fitDataFill",
5891
5951
  validationMode: "highlight",
@@ -5948,14 +6008,20 @@ function isEventSet(table, event) {
5948
6008
  function set_changed_stated(gobj, changed) {
5949
6009
  let $container = gobj_read_attr(gobj, "$container");
5950
6010
  let $form = $container.querySelector("form");
6011
+ const enable = ($btn, on, color) => {
6012
+ if (!$btn) return;
6013
+ if (on) $btn.removeAttribute("disabled");
6014
+ else $btn.setAttribute("disabled", true);
6015
+ const $i = $btn.querySelector("span i");
6016
+ if ($i) $i.style.color = color;
6017
+ };
6018
+ const OFF_COLOR = "hsl(var(--bulma-button-h), var(--bulma-button-s), var(--bulma-button-color-l))";
5951
6019
  let $button_save = $container.querySelector(".button-save");
5952
6020
  let $button_undo = $container.querySelector(".button-undo");
5953
6021
  if (changed) {
5954
6022
  gobj_write_bool_attr(gobj, "changes", true);
5955
- $button_save.removeAttribute("disabled");
5956
- $button_undo.removeAttribute("disabled");
5957
- $button_save.querySelector("span i").style.color = "MediumSeaGreen";
5958
- $button_undo.querySelector("span i").style.color = "Magenta";
6023
+ enable($button_save, true, "MediumSeaGreen");
6024
+ enable($button_undo, true, "Magenta");
5959
6025
  $form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
5960
6026
  if (isEventSet($table.tabulator, "dataChanged")) $table.tabulator.off("dataChanged");
5961
6027
  $table.querySelectorAll(".yui-tabulator-table-nested").forEach(($table_nested) => {
@@ -5964,10 +6030,8 @@ function set_changed_stated(gobj, changed) {
5964
6030
  });
5965
6031
  } else {
5966
6032
  gobj_write_bool_attr(gobj, "changes", false);
5967
- $button_save.setAttribute("disabled", true);
5968
- $button_undo.setAttribute("disabled", true);
5969
- $button_save.querySelector("span i").style.color = "hsl(var(--bulma-button-h), var(--bulma-button-s), var(--bulma-button-color-l))";
5970
- $button_undo.querySelector("span i").style.color = "hsl(var(--bulma-button-h), var(--bulma-button-s), var(--bulma-button-color-l))";
6033
+ enable($button_save, false, OFF_COLOR);
6034
+ enable($button_undo, false, OFF_COLOR);
5971
6035
  setTimeout(function() {
5972
6036
  $form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
5973
6037
  if (isEventSet($table.tabulator, "dataChanged")) $table.tabulator.off("dataChanged");
@@ -5996,6 +6060,175 @@ async function readClipboard$1(gobj) {
5996
6060
  }
5997
6061
  }
5998
6062
  /************************************************************
6063
+ * Bottom toolbar, from the `toolbar` attr.
6064
+ *
6065
+ * The five buttons it can show, and their order, are the
6066
+ * caller's: a dialog with a single action asks for ["save"],
6067
+ * and [] leaves no toolbar at all. Default is the five it
6068
+ * always had, so nothing moves for a caller that says nothing.
6069
+ ************************************************************/
6070
+ function toolbar_button(gobj, name) {
6071
+ switch (name) {
6072
+ case "save": return [
6073
+ "button",
6074
+ {
6075
+ class: "button p-1 button-save",
6076
+ title: "save",
6077
+ "aria-label": "save",
6078
+ disabled: true
6079
+ },
6080
+ [[
6081
+ "span",
6082
+ { class: "icon m-0" },
6083
+ "<i class=\"yi-floppy-disk\"></i>"
6084
+ ], [
6085
+ "span",
6086
+ {
6087
+ class: "is-hidden-mobile pl-1 pr-1",
6088
+ i18n: "save"
6089
+ },
6090
+ "save"
6091
+ ]],
6092
+ { click: function(evt) {
6093
+ evt.stopPropagation();
6094
+ gobj_send_event(gobj, "EV_SAVE_RECORD", {}, gobj);
6095
+ } }
6096
+ ];
6097
+ case "undo": return [
6098
+ "button",
6099
+ {
6100
+ class: "button p-1 button-undo",
6101
+ title: "undo",
6102
+ "aria-label": "undo",
6103
+ disabled: true
6104
+ },
6105
+ [[
6106
+ "span",
6107
+ { class: "icon m-0" },
6108
+ "<i class=\"yi-arrow-rotate-left\"></i>"
6109
+ ], [
6110
+ "span",
6111
+ {
6112
+ class: "is-hidden-mobile pl-1 pr-1",
6113
+ i18n: "undo"
6114
+ },
6115
+ "undo"
6116
+ ]],
6117
+ { click: function(evt) {
6118
+ evt.stopPropagation();
6119
+ gobj_send_event(gobj, "EV_UNDO_RECORD", {}, gobj);
6120
+ } }
6121
+ ];
6122
+ case "clear": return [
6123
+ "button",
6124
+ {
6125
+ class: "button p-1",
6126
+ title: "clear",
6127
+ "aria-label": "clear"
6128
+ },
6129
+ [[
6130
+ "span",
6131
+ { class: "icon m-0" },
6132
+ "<i class=\"yi-broom-wide\"></i>"
6133
+ ], [
6134
+ "span",
6135
+ {
6136
+ class: "is-hidden-mobile pl-1 pr-1",
6137
+ i18n: "clear"
6138
+ },
6139
+ "clear"
6140
+ ]],
6141
+ { click: function(evt) {
6142
+ evt.stopPropagation();
6143
+ gobj_send_event(gobj, "EV_CLEAR_RECORD", {}, gobj);
6144
+ } }
6145
+ ];
6146
+ case "copy": return [
6147
+ "button",
6148
+ {
6149
+ class: "button p-1",
6150
+ title: "copy",
6151
+ "aria-label": "copy"
6152
+ },
6153
+ [[
6154
+ "span",
6155
+ { class: "icon m-0" },
6156
+ "<i class=\"yi-copy\"></i>"
6157
+ ], [
6158
+ "span",
6159
+ {
6160
+ class: "is-hidden-mobile pl-1 pr-1",
6161
+ i18n: "copy"
6162
+ },
6163
+ "copy"
6164
+ ]],
6165
+ { click: function(evt) {
6166
+ evt.stopPropagation();
6167
+ gobj_send_event(gobj, "EV_COPY_RECORD", {}, gobj);
6168
+ } }
6169
+ ];
6170
+ case "paste": return [
6171
+ "button",
6172
+ {
6173
+ class: "button p-1",
6174
+ title: "paste",
6175
+ "aria-label": "paste"
6176
+ },
6177
+ [[
6178
+ "span",
6179
+ { class: "icon m-0" },
6180
+ "<i class=\"yi-paste\"></i>"
6181
+ ], [
6182
+ "span",
6183
+ {
6184
+ class: "is-hidden-mobile pl-1 pr-1",
6185
+ i18n: "paste"
6186
+ },
6187
+ "paste"
6188
+ ]],
6189
+ { click: async function(evt) {
6190
+ evt.stopPropagation();
6191
+ await readClipboard$1(gobj);
6192
+ } }
6193
+ ];
6194
+ default: return null;
6195
+ }
6196
+ }
6197
+ function build_toolbar(gobj) {
6198
+ const plan = plan_toolbar(gobj_read_attr(gobj, "toolbar"));
6199
+ for (let name of plan.unknown) log_warning(`${gobj_short_name(gobj)}: unknown toolbar button '${name}'`);
6200
+ if (!plan.left.length && !plan.right.length) return [
6201
+ "span",
6202
+ {
6203
+ class: "yui-toolbar-form-empty",
6204
+ style: "display:none;"
6205
+ },
6206
+ ""
6207
+ ];
6208
+ return [
6209
+ "div",
6210
+ {
6211
+ class: "yui-toolbar-form is-flex-shrink-0 is-flex is-flex-direction-row is-justify-content-space-between is-align-items-center",
6212
+ style: "width:100%; flex-wrap:wrap; row-gap:6px;"
6213
+ },
6214
+ [[
6215
+ "div",
6216
+ {
6217
+ class: "p-1 is-flex is-flex-direction-row is-align-items-center",
6218
+ style: "column-gap:6px;"
6219
+ },
6220
+ plan.left.map((n) => toolbar_button(gobj, n))
6221
+ ], [
6222
+ "div",
6223
+ {
6224
+ class: "p-1 is-flex is-flex-direction-row is-align-items-center",
6225
+ style: "column-gap:6px;"
6226
+ },
6227
+ plan.right.map((n) => toolbar_button(gobj, n))
6228
+ ]]
6229
+ ];
6230
+ }
6231
+ /************************************************************
5999
6232
  * Build UI
6000
6233
  ************************************************************/
6001
6234
  function build_ui$11(gobj) {
@@ -6012,149 +6245,7 @@ function build_ui$11(gobj) {
6012
6245
  style: "overflow-y:auto; min-height:0;"
6013
6246
  },
6014
6247
  build_form(gobj)
6015
- ], [
6016
- "div",
6017
- {
6018
- class: "yui-toolbar-form is-flex-shrink-0 is-flex is-flex-direction-row is-justify-content-space-between is-align-items-center",
6019
- style: "width:100%; flex-wrap:wrap; row-gap:6px;"
6020
- },
6021
- [[
6022
- "div",
6023
- {
6024
- class: "p-1 is-flex is-flex-direction-row is-align-items-center",
6025
- style: "column-gap:6px;"
6026
- },
6027
- [
6028
- [
6029
- "button",
6030
- {
6031
- class: "button p-1 button-save",
6032
- title: "save",
6033
- "aria-label": "save",
6034
- disabled: true
6035
- },
6036
- [[
6037
- "span",
6038
- { class: "icon m-0" },
6039
- "<i class=\"yi-floppy-disk\"></i>"
6040
- ], [
6041
- "span",
6042
- {
6043
- class: "is-hidden-mobile pl-1 pr-1",
6044
- i18n: "save"
6045
- },
6046
- "save"
6047
- ]],
6048
- { click: function(evt) {
6049
- evt.stopPropagation();
6050
- gobj_send_event(gobj, "EV_SAVE_RECORD", {}, gobj);
6051
- } }
6052
- ],
6053
- [
6054
- "button",
6055
- {
6056
- class: "button p-1 button-undo",
6057
- title: "undo",
6058
- "aria-label": "undo",
6059
- disabled: true
6060
- },
6061
- [[
6062
- "span",
6063
- { class: "icon m-0" },
6064
- "<i class=\"yi-arrow-rotate-left\"></i>"
6065
- ], [
6066
- "span",
6067
- {
6068
- class: "is-hidden-mobile pl-1 pr-1",
6069
- i18n: "undo"
6070
- },
6071
- "undo"
6072
- ]],
6073
- { click: function(evt) {
6074
- evt.stopPropagation();
6075
- gobj_send_event(gobj, "EV_UNDO_RECORD", {}, gobj);
6076
- } }
6077
- ],
6078
- [
6079
- "button",
6080
- {
6081
- class: "button p-1",
6082
- title: "clear",
6083
- "aria-label": "clear"
6084
- },
6085
- [[
6086
- "span",
6087
- { class: "icon m-0" },
6088
- "<i class=\"yi-broom-wide\"></i>"
6089
- ], [
6090
- "span",
6091
- {
6092
- class: "is-hidden-mobile pl-1 pr-1",
6093
- i18n: "clear"
6094
- },
6095
- "clear"
6096
- ]],
6097
- { click: function(evt) {
6098
- evt.stopPropagation();
6099
- gobj_send_event(gobj, "EV_CLEAR_RECORD", {}, gobj);
6100
- } }
6101
- ]
6102
- ]
6103
- ], [
6104
- "div",
6105
- {
6106
- class: "p-1 is-flex is-flex-direction-row is-align-items-center",
6107
- style: "column-gap:6px;"
6108
- },
6109
- [[
6110
- "button",
6111
- {
6112
- class: "button p-1",
6113
- title: "copy",
6114
- "aria-label": "copy"
6115
- },
6116
- [[
6117
- "span",
6118
- { class: "icon m-0" },
6119
- "<i class=\"yi-copy\"></i>"
6120
- ], [
6121
- "span",
6122
- {
6123
- class: "is-hidden-mobile pl-1 pr-1",
6124
- i18n: "copy"
6125
- },
6126
- "copy"
6127
- ]],
6128
- { click: function(evt) {
6129
- evt.stopPropagation();
6130
- gobj_send_event(gobj, "EV_COPY_RECORD", {}, gobj);
6131
- } }
6132
- ], [
6133
- "button",
6134
- {
6135
- class: "button p-1",
6136
- title: "paste",
6137
- "aria-label": "paste"
6138
- },
6139
- [[
6140
- "span",
6141
- { class: "icon m-0" },
6142
- "<i class=\"yi-paste\"></i>"
6143
- ], [
6144
- "span",
6145
- {
6146
- class: "is-hidden-mobile pl-1 pr-1",
6147
- i18n: "paste"
6148
- },
6149
- "paste"
6150
- ]],
6151
- { click: async function(evt) {
6152
- evt.stopPropagation();
6153
- await readClipboard$1(gobj);
6154
- } }
6155
- ]]
6156
- ]]
6157
- ]]
6248
+ ], build_toolbar(gobj)]
6158
6249
  ]);
6159
6250
  gobj_write_attr(gobj, "$container", $container);
6160
6251
  apply_form_mode(gobj, $container.querySelector("form"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "5.6.0",
3
+ "version": "5.7.1",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -38,7 +38,7 @@
38
38
  "@yuneta/gobj-js": "^7.8.7",
39
39
  "bulma": "^1.0.4",
40
40
  "i18next": "^26.3.6",
41
- "maplibre-gl": "^6.0.0",
41
+ "maplibre-gl": "^6.1.0",
42
42
  "tabulator-tables": "^6.5.2",
43
43
  "tom-select": "^2.6.2",
44
44
  "uplot": "^1.6.32",
package/src/c_yui_form.js CHANGED
@@ -57,6 +57,7 @@ import { createJSONEditor } from 'vanilla-jsoneditor';
57
57
  import "vanilla-jsoneditor/themes/jse-theme-dark.css";
58
58
  import "./c_yui_form.css";
59
59
  import {attach_clear, refresh_clear} from "./yui_inputs.js";
60
+ import {plan_toolbar} from "./form_toolbar_plan.js";
60
61
  /* Read at field-build time only — deliberately NOT watched: the hosting
61
62
  * dialog rebuilds the form on every open, and re-rendering a form under
62
63
  * the user mid-edit would throw away what they typed. */
@@ -96,6 +97,20 @@ SDATA(data_type_t.DTP_BOOLEAN, "selectable", 0, true, "Rows selectable
96
97
  SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, false, "Show a first column with checkbox to select rows"),
97
98
  SDATA(data_type_t.DTP_BOOLEAN, "with_rownum", 0, false, "Print first column with row number"),
98
99
  SDATA(data_type_t.DTP_BOOLEAN, "with_delete_row_btn", 0, false, "Print last column with delete row button"),
100
+ /*
101
+ * Which buttons the bottom toolbar shows, and in which order. The
102
+ * default is the five it has always shown, so nothing moves for a
103
+ * caller that does not set it.
104
+ *
105
+ * It exists because the toolbar was hardcoded: a three-field sign-up
106
+ * dialog with ONE action still got save + undo + clear + copy + paste,
107
+ * and the only way out was to not use this gclass at all. `[]` hides
108
+ * the toolbar entirely, for a caller that supplies its own buttons.
109
+ *
110
+ * Unknown names are ignored, with a warning: a typo must not silently
111
+ * drop the save button.
112
+ */
113
+ SDATA(data_type_t.DTP_JSON, "toolbar", 0, ["save", "undo", "clear", "copy", "paste"], "Toolbar buttons to show, in order: save, undo, clear, copy, paste. [] hides the toolbar"),
99
114
  /*
100
115
  * Defaults for tabulator
101
116
  */
@@ -204,6 +219,29 @@ function set_changed_stated(gobj, changed)
204
219
  let $container = gobj_read_attr(gobj, "$container");
205
220
  let $form = $container.querySelector('form');
206
221
 
222
+ /*
223
+ * Los dos pueden NO estar: desde que la botonera es configurable
224
+ * (`toolbar`), un dialogo de una sola accion pide ["save"] y aqui no
225
+ * hay undo que habilitar. Antes se daban por presentes y quitar uno
226
+ * reventaba con "can't access property setAttribute, a is null" al
227
+ * primer cambio en el formulario.
228
+ */
229
+ const enable = ($btn, on, color) => {
230
+ if(!$btn) {
231
+ return;
232
+ }
233
+ if(on) {
234
+ $btn.removeAttribute("disabled");
235
+ } else {
236
+ $btn.setAttribute("disabled", true);
237
+ }
238
+ const $i = $btn.querySelector('span i');
239
+ if($i) {
240
+ $i.style.color = color;
241
+ }
242
+ };
243
+ const OFF_COLOR = "hsl(var(--bulma-button-h), var(--bulma-button-s), var(--bulma-button-color-l))";
244
+
207
245
  let $button_save = $container.querySelector('.button-save');
208
246
  let $button_undo = $container.querySelector('.button-undo');
209
247
 
@@ -213,11 +251,8 @@ function set_changed_stated(gobj, changed)
213
251
  */
214
252
  gobj_write_bool_attr(gobj, "changes", true);
215
253
 
216
- $button_save.removeAttribute("disabled");
217
- $button_undo.removeAttribute("disabled");
218
-
219
- $button_save.querySelector('span i').style.color = "MediumSeaGreen";
220
- $button_undo.querySelector('span i').style.color = "Magenta";
254
+ enable($button_save, true, "MediumSeaGreen");
255
+ enable($button_undo, true, "Magenta");
221
256
 
222
257
  $form.querySelectorAll('.yui-tabulator-table').forEach($table => {
223
258
  if(isEventSet($table.tabulator, "dataChanged")) {
@@ -237,11 +272,8 @@ function set_changed_stated(gobj, changed)
237
272
  */
238
273
  gobj_write_bool_attr(gobj, "changes", false);
239
274
 
240
- $button_save.setAttribute("disabled", true);
241
- $button_undo.setAttribute("disabled", true);
242
-
243
- $button_save.querySelector('span i').style.color = "hsl(var(--bulma-button-h), var(--bulma-button-s), var(--bulma-button-color-l))";
244
- $button_undo.querySelector('span i').style.color = "hsl(var(--bulma-button-h), var(--bulma-button-s), var(--bulma-button-color-l))";
275
+ enable($button_save, false, OFF_COLOR);
276
+ enable($button_undo, false, OFF_COLOR);
245
277
 
246
278
  // TODO improve
247
279
  setTimeout(function() {
@@ -283,6 +315,90 @@ async function readClipboard(gobj)
283
315
  }
284
316
  }
285
317
 
318
+ /************************************************************
319
+ * Bottom toolbar, from the `toolbar` attr.
320
+ *
321
+ * The five buttons it can show, and their order, are the
322
+ * caller's: a dialog with a single action asks for ["save"],
323
+ * and [] leaves no toolbar at all. Default is the five it
324
+ * always had, so nothing moves for a caller that says nothing.
325
+ ************************************************************/
326
+ function toolbar_button(gobj, name)
327
+ {
328
+ switch(name) {
329
+ case "save":
330
+ return ['button', {class: 'button p-1 button-save', title: 'save', 'aria-label': 'save', disabled: true}, [
331
+ ['span', {class: 'icon m-0'}, '<i class="yi-floppy-disk"></i>'],
332
+ ['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'save'}, 'save']
333
+ ], {
334
+ click: function(evt) {
335
+ evt.stopPropagation();
336
+ gobj_send_event(gobj, "EV_SAVE_RECORD", {}, gobj);
337
+ }
338
+ }];
339
+ case "undo":
340
+ return ['button', {class: 'button p-1 button-undo', title: 'undo', 'aria-label': 'undo', disabled: true}, [
341
+ ['span', {class: 'icon m-0'}, '<i class="yi-arrow-rotate-left"></i>'],
342
+ ['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'undo'}, 'undo']
343
+ ], {
344
+ click: function(evt) {
345
+ evt.stopPropagation();
346
+ gobj_send_event(gobj, "EV_UNDO_RECORD", {}, gobj);
347
+ }
348
+ }];
349
+ case "clear":
350
+ return ['button', {class: 'button p-1', title: 'clear', 'aria-label': 'clear'}, [
351
+ ['span', {class: 'icon m-0'}, '<i class="yi-broom-wide"></i>'],
352
+ ['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'clear'}, 'clear']
353
+ ], {
354
+ click: function(evt) {
355
+ evt.stopPropagation();
356
+ gobj_send_event(gobj, "EV_CLEAR_RECORD", {}, gobj);
357
+ }
358
+ }];
359
+ case "copy":
360
+ return ['button', {class: 'button p-1', title: 'copy', 'aria-label': 'copy'}, [
361
+ ['span', {class: 'icon m-0'}, '<i class="yi-copy"></i>'],
362
+ ['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'copy'}, 'copy']
363
+ ], {click: function(evt) {
364
+ evt.stopPropagation();
365
+ gobj_send_event(gobj, "EV_COPY_RECORD", {}, gobj);
366
+ }
367
+ }];
368
+ case "paste":
369
+ return ['button', {class: 'button p-1', title: 'paste', 'aria-label': 'paste'}, [
370
+ ['span', {class: 'icon m-0'}, '<i class="yi-paste"></i>'],
371
+ ['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'paste'}, 'paste']
372
+ ], {click: async function(evt) {
373
+ evt.stopPropagation();
374
+ await readClipboard(gobj);
375
+ }
376
+ }];
377
+ default:
378
+ return null; /* plan_toolbar() already warned */
379
+ }
380
+ }
381
+
382
+ function build_toolbar(gobj)
383
+ {
384
+ const plan = plan_toolbar(gobj_read_attr(gobj, "toolbar"));
385
+
386
+ for(let name of plan.unknown) {
387
+ /* A typo must not silently drop the save button. */
388
+ log_warning(`${gobj_short_name(gobj)}: unknown toolbar button '${name}'`);
389
+ }
390
+ if(!plan.left.length && !plan.right.length) {
391
+ return ['span', {class: 'yui-toolbar-form-empty', style: 'display:none;'}, ''];
392
+ }
393
+
394
+ return ['div', {class: 'yui-toolbar-form is-flex-shrink-0 is-flex is-flex-direction-row is-justify-content-space-between is-align-items-center', style: 'width:100%; flex-wrap:wrap; row-gap:6px;'}, [
395
+ ['div', {class: 'p-1 is-flex is-flex-direction-row is-align-items-center', style:'column-gap:6px;'},
396
+ plan.left.map((n) => toolbar_button(gobj, n))],
397
+ ['div', {class: 'p-1 is-flex is-flex-direction-row is-align-items-center', style:'column-gap:6px;'},
398
+ plan.right.map((n) => toolbar_button(gobj, n))]
399
+ ]];
400
+ }
401
+
286
402
  /************************************************************
287
403
  * Build UI
288
404
  ************************************************************/
@@ -314,81 +430,7 @@ function build_ui(gobj)
314
430
  /*----------------------------*
315
431
  * Bottom toolbar
316
432
  *----------------------------*/
317
- ['div', {class: 'yui-toolbar-form is-flex-shrink-0 is-flex is-flex-direction-row is-justify-content-space-between is-align-items-center', style: 'width:100%; flex-wrap:wrap; row-gap:6px;'}, [
318
-
319
- /*----------------------------*
320
- * Left buttons
321
- *----------------------------*/
322
- ['div', {class: 'p-1 is-flex is-flex-direction-row is-align-items-center', style:'column-gap:6px;'}, [
323
- /*----------------------------*
324
- * Save
325
- *----------------------------*/
326
- ['button', {class: 'button p-1 button-save', title: 'save', 'aria-label': 'save', disabled: true}, [
327
- ['span', {class: 'icon m-0'}, '<i class="yi-floppy-disk"></i>'],
328
- ['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'save'}, 'save']
329
- ], {
330
- click: function(evt) {
331
- evt.stopPropagation();
332
- gobj_send_event(gobj, "EV_SAVE_RECORD", {}, gobj);
333
- }
334
- }],
335
-
336
- /*----------------------------*
337
- * Undo
338
- *----------------------------*/
339
- ['button', {class: 'button p-1 button-undo', title: 'undo', 'aria-label': 'undo', disabled: true}, [
340
- ['span', {class: 'icon m-0'}, '<i class="yi-arrow-rotate-left"></i>'],
341
- ['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'undo'}, 'undo']
342
- ], {
343
- click: function(evt) {
344
- evt.stopPropagation();
345
- gobj_send_event(gobj, "EV_UNDO_RECORD", {}, gobj);
346
- }
347
- }],
348
-
349
- /*----------------------------*
350
- * Clear
351
- *----------------------------*/
352
- ['button', {class: 'button p-1', title: 'clear', 'aria-label': 'clear'}, [
353
- ['span', {class: 'icon m-0'}, '<i class="yi-broom-wide"></i>'],
354
- ['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'clear'}, 'clear']
355
- ], {
356
- click: function(evt) {
357
- evt.stopPropagation();
358
- gobj_send_event(gobj, "EV_CLEAR_RECORD", {}, gobj);
359
- }
360
- }],
361
- ]],
362
-
363
- /*----------------------------*
364
- * Right buttons
365
- *----------------------------*/
366
- ['div', {class: 'p-1 is-flex is-flex-direction-row is-align-items-center', style:'column-gap:6px;'}, [
367
- /*----------------------------*
368
- * Copy
369
- *----------------------------*/
370
- ['button', {class: 'button p-1', title: 'copy', 'aria-label': 'copy'}, [
371
- ['span', {class: 'icon m-0'}, '<i class="yi-copy"></i>'],
372
- ['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'copy'}, 'copy']
373
- ], {click: function(evt) {
374
- evt.stopPropagation();
375
- gobj_send_event(gobj, "EV_COPY_RECORD", {}, gobj);
376
- }
377
- }],
378
-
379
- /*----------------------------*
380
- * Paste
381
- *----------------------------*/
382
- ['button', {class: 'button p-1', title: 'paste', 'aria-label': 'paste'}, [
383
- ['span', {class: 'icon m-0'}, '<i class="yi-paste"></i>'],
384
- ['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'paste'}, 'paste']
385
- ], {click: async function(evt) {
386
- evt.stopPropagation();
387
- await readClipboard(gobj);
388
- }
389
- }]
390
- ]]
391
- ]]
433
+ build_toolbar(gobj)
392
434
  ]]
393
435
  );
394
436
 
@@ -0,0 +1,59 @@
1
+ /***********************************************************************
2
+ * form_toolbar_plan.js
3
+ *
4
+ * Which buttons the C_YUI_FORM toolbar shows, and where.
5
+ *
6
+ * Pure, so it can be tested without a DOM: the gclass turns the
7
+ * plan into elements, this decides the plan.
8
+ *
9
+ * Copyright (c) 2026, ArtGins.
10
+ * All Rights Reserved.
11
+ ***********************************************************************/
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"];
18
+
19
+ const DEFAULT_TOOLBAR = ["save", "undo", "clear", "copy", "paste"];
20
+
21
+
22
+ /***************************************************************
23
+ * plan_toolbar(wanted) -> {left, right, unknown}
24
+ *
25
+ * wanted an array of button names, in the order asked for.
26
+ * Anything that is not an array means "the default
27
+ * five", so a caller that sets nothing keeps exactly
28
+ * the toolbar this gclass always had.
29
+ * [] means no toolbar at all.
30
+ *
31
+ * unknown names that match no button. They are reported and
32
+ * NOT silently dropped: a typo in the list would
33
+ * otherwise remove the save button with no trace.
34
+ ***************************************************************/
35
+ function plan_toolbar(wanted)
36
+ {
37
+ if(!Array.isArray(wanted)) {
38
+ wanted = DEFAULT_TOOLBAR;
39
+ }
40
+
41
+ let left = [];
42
+ let right = [];
43
+ let unknown = [];
44
+
45
+ for(let name of wanted) {
46
+ if(LEFT_BUTTONS.indexOf(name) >= 0) {
47
+ left.push(name);
48
+ } else if(RIGHT_BUTTONS.indexOf(name) >= 0) {
49
+ right.push(name);
50
+ } else {
51
+ unknown.push(name);
52
+ }
53
+ }
54
+
55
+ return {left, right, unknown};
56
+ }
57
+
58
+
59
+ export {plan_toolbar, DEFAULT_TOOLBAR};
@@ -0,0 +1,54 @@
1
+ import {describe, it, expect} from "vitest";
2
+ import {plan_toolbar, DEFAULT_TOOLBAR} from "./form_toolbar_plan.js";
3
+
4
+ describe("plan_toolbar", () => {
5
+
6
+ it("says nothing -> the five buttons this gclass always had", () => {
7
+ const p = plan_toolbar(undefined);
8
+ expect(p.left).toEqual(["save", "undo", "clear"]);
9
+ expect(p.right).toEqual(["copy", "paste"]);
10
+ expect(p.unknown).toEqual([]);
11
+ expect(DEFAULT_TOOLBAR.length).toBe(5);
12
+ });
13
+
14
+ it("a non-array is not an empty toolbar: it is the default", () => {
15
+ /* Reading a JSON attr that was never set must not silently
16
+ * leave a form with no save button. */
17
+ for(const v of [null, "save", 0, {}]) {
18
+ expect(plan_toolbar(v).left).toContain("save");
19
+ }
20
+ });
21
+
22
+ it("[] leaves no toolbar at all", () => {
23
+ const p = plan_toolbar([]);
24
+ expect(p.left).toEqual([]);
25
+ expect(p.right).toEqual([]);
26
+ expect(p.unknown).toEqual([]);
27
+ });
28
+
29
+ it("one action: a dialog asks for save only", () => {
30
+ const p = plan_toolbar(["save"]);
31
+ expect(p.left).toEqual(["save"]);
32
+ expect(p.right).toEqual([]);
33
+ });
34
+
35
+ it("keeps the order asked for inside each side", () => {
36
+ const p = plan_toolbar(["clear", "save", "paste", "copy"]);
37
+ expect(p.left).toEqual(["clear", "save"]);
38
+ expect(p.right).toEqual(["paste", "copy"]);
39
+ });
40
+
41
+ it("an unknown name is REPORTED, never silently dropped", () => {
42
+ /* A typo here would otherwise remove a button and leave no
43
+ * trace of why it is missing. */
44
+ const p = plan_toolbar(["save", "sav", "undo"]);
45
+ expect(p.left).toEqual(["save", "undo"]);
46
+ expect(p.unknown).toEqual(["sav"]);
47
+ });
48
+
49
+ it("drops one whole side without disturbing the other", () => {
50
+ const p = plan_toolbar(["save", "undo", "clear"]);
51
+ expect(p.left).toEqual(["save", "undo", "clear"]);
52
+ expect(p.right).toEqual([]);
53
+ });
54
+ });