@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.
- package/dist/gobj-ui.cjs.js +244 -153
- package/dist/gobj-ui.es.js +244 -153
- package/package.json +2 -2
- package/src/c_yui_form.js +127 -85
- package/src/form_toolbar_plan.js +59 -0
- package/src/form_toolbar_plan.test.js +54 -0
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -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
|
|
5961
|
-
$button_undo
|
|
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
|
|
5973
|
-
$button_undo
|
|
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"));
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -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
|
|
5956
|
-
$button_undo
|
|
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
|
|
5968
|
-
$button_undo
|
|
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.
|
|
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.
|
|
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
|
|
217
|
-
$button_undo
|
|
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
|
|
241
|
-
$button_undo
|
|
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
|
-
|
|
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
|
+
});
|