@yuneta/gobj-ui 2.3.0 → 2.4.0

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.
@@ -5383,7 +5383,7 @@ function register_c_yui_wizard() {
5383
5383
  *
5384
5384
  * Form (in Window or not)
5385
5385
  *
5386
- * Copyright (c) 2024-2025, ArtGins.
5386
+ * Copyright (c) 2024-2026, ArtGins.
5387
5387
  * All Rights Reserved.
5388
5388
  ***********************************************************************/
5389
5389
  /***************************************************************
@@ -5998,6 +5998,17 @@ function apply_form_mode(gobj, $form) {
5998
5998
  }
5999
5999
  }
6000
6000
  /******************************************************************
6001
+ * True when the app renders dark: explicit <html data-theme>, or
6002
+ * the OS scheme when the attribute is absent ("system" theme
6003
+ * removes it — see themes.js). Read at field-build time; the
6004
+ * hosting dialog rebuilds the form on every open.
6005
+ ******************************************************************/
6006
+ function form_is_dark_theme() {
6007
+ let attr = document.documentElement.getAttribute("data-theme");
6008
+ if (attr) return attr === "dark";
6009
+ return typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
6010
+ }
6011
+ /******************************************************************
6001
6012
  * With html field conf builds an html element,
6002
6013
  * ready to add to the form
6003
6014
  *
@@ -6438,7 +6449,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
6438
6449
  break;
6439
6450
  case "jsoneditor": {
6440
6451
  let attrs = {
6441
- class: "jsoneditor jse-theme-dark",
6452
+ class: "jsoneditor" + (form_is_dark_theme() ? " jse-theme-dark" : ""),
6442
6453
  name,
6443
6454
  style: ""
6444
6455
  };
@@ -6447,7 +6458,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
6447
6458
  else if (required) attrs.required = "";
6448
6459
  $extend = (0, _yuneta_gobj_js.createElement2)(["div", attrs]);
6449
6460
  $control.appendChild($extend);
6450
- $extend.jsoneditor = new vanilla_jsoneditor.JSONEditor({
6461
+ $extend.jsoneditor = (0, vanilla_jsoneditor.createJSONEditor)({
6451
6462
  target: $extend,
6452
6463
  props: {
6453
6464
  readOnly: !!readonly,
@@ -15402,7 +15413,7 @@ function register_c_yui_treedb_topics() {
15402
15413
  *
15403
15414
  * Table of TreeDB topic with FORM for editing
15404
15415
  *
15405
- * Copyright (c) 2025, ArtGins.
15416
+ * Copyright (c) 2025-2026, ArtGins.
15406
15417
  * All Rights Reserved.
15407
15418
  ***********************************************************************/
15408
15419
  /***************************************************************
@@ -15457,7 +15468,8 @@ var PRIVATE_DATA$2 = {
15457
15468
  topic_name: "",
15458
15469
  tabulator: null,
15459
15470
  form: null,
15460
- $popup: null
15471
+ $popup: null,
15472
+ on_keydown: null
15461
15473
  };
15462
15474
  var __gclass__$2 = null;
15463
15475
  function popup_mount_layer() {
@@ -16026,6 +16038,14 @@ function create_tabulator(gobj) {
16026
16038
  n = 0;
16027
16039
  }
16028
16040
  $rc.textContent = `${n} ${(0, i18next.t)("rows")}`;
16041
+ let single_page = true;
16042
+ try {
16043
+ let max = tabulator.getPageMax();
16044
+ single_page = max === false || max <= 1;
16045
+ } catch (e) {
16046
+ single_page = true;
16047
+ }
16048
+ tabulator.element.classList.toggle("yui-single-page", single_page);
16029
16049
  }
16030
16050
  tabulator._ready = false;
16031
16051
  tabulator.on("tableBuilt", function() {
@@ -16201,6 +16221,13 @@ function open_form_dialog(gobj, mode, record) {
16201
16221
  let desc = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc");
16202
16222
  let popup_id = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "popup_id");
16203
16223
  let topic_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name");
16224
+ let pkey = desc.pkey || "id";
16225
+ let title;
16226
+ if (mode === "create") title = (0, i18next.t)("new") + " " + (0, i18next.t)(topic_name);
16227
+ else {
16228
+ let rid = record ? String(record[pkey] ?? "") : "";
16229
+ title = (0, i18next.t)(topic_name) + ((0, _yuneta_gobj_js.empty_string)(rid) ? "" : " — " + rid);
16230
+ }
16204
16231
  let $element = (0, _yuneta_gobj_js.createElement2)([
16205
16232
  "div",
16206
16233
  {
@@ -16223,7 +16250,7 @@ function open_form_dialog(gobj, mode, record) {
16223
16250
  class: "modal-card-title",
16224
16251
  style: "text-align:center;"
16225
16252
  },
16226
- (0, i18next.t)(topic_name)
16253
+ title
16227
16254
  ], [
16228
16255
  "button",
16229
16256
  {
@@ -16269,8 +16296,16 @@ function open_form_dialog(gobj, mode, record) {
16269
16296
  }
16270
16297
  popup_mount_layer().appendChild($element);
16271
16298
  (0, _yuneta_gobj_js.refresh_language)($element, i18next.t);
16299
+ priv.on_keydown = function(evt) {
16300
+ if (evt.key === "Escape") {
16301
+ evt.stopPropagation();
16302
+ request_close_form_dialog(gobj);
16303
+ }
16304
+ };
16305
+ document.addEventListener("keydown", priv.on_keydown);
16272
16306
  (0, _yuneta_gobj_js.gobj_start)(form);
16273
- let $with_focus = $element.querySelector(".with-focus");
16307
+ let $with_focus = $element.querySelector(".with-focus:not([readonly])");
16308
+ if (!$with_focus) $with_focus = $element.querySelector("input.input:not([readonly]), textarea, select");
16274
16309
  if ($with_focus) $with_focus.focus();
16275
16310
  }
16276
16311
  /************************************************************
@@ -16296,6 +16331,10 @@ function request_close_form_dialog(gobj) {
16296
16331
  ************************************************************/
16297
16332
  function close_form_dialog(gobj) {
16298
16333
  let priv = gobj.priv;
16334
+ if (priv.on_keydown) {
16335
+ document.removeEventListener("keydown", priv.on_keydown);
16336
+ priv.on_keydown = null;
16337
+ }
16299
16338
  if (priv.form) {
16300
16339
  if ((0, _yuneta_gobj_js.gobj_is_running)(priv.form)) (0, _yuneta_gobj_js.gobj_stop)(priv.form);
16301
16340
  (0, _yuneta_gobj_js.gobj_destroy)(priv.form);
@@ -1,7 +1,7 @@
1
1
  import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, duplicate_objects, empty_string, escapeHtml, event_flag_t, gclass_create, gclass_find_by_name, gclass_flag_t, getPositionRelativeToBody, gobj_command, gobj_create, gobj_create_pure_child, gobj_create_service, gobj_current_state, gobj_default_service, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_has_attr, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_pure_number, is_string, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_get_ids, kwid_new_dict, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_json, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
2
2
  import i18next, { t } from "i18next";
3
3
  import TomSelect from "tom-select";
4
- import { JSONEditor } from "vanilla-jsoneditor";
4
+ import { createJSONEditor } from "vanilla-jsoneditor";
5
5
  import { TabulatorFull } from "tabulator-tables";
6
6
  import maplibregl from "maplibre-gl";
7
7
  import uPlot from "uplot";
@@ -5378,7 +5378,7 @@ function register_c_yui_wizard() {
5378
5378
  *
5379
5379
  * Form (in Window or not)
5380
5380
  *
5381
- * Copyright (c) 2024-2025, ArtGins.
5381
+ * Copyright (c) 2024-2026, ArtGins.
5382
5382
  * All Rights Reserved.
5383
5383
  ***********************************************************************/
5384
5384
  /***************************************************************
@@ -5993,6 +5993,17 @@ function apply_form_mode(gobj, $form) {
5993
5993
  }
5994
5994
  }
5995
5995
  /******************************************************************
5996
+ * True when the app renders dark: explicit <html data-theme>, or
5997
+ * the OS scheme when the attribute is absent ("system" theme
5998
+ * removes it — see themes.js). Read at field-build time; the
5999
+ * hosting dialog rebuilds the form on every open.
6000
+ ******************************************************************/
6001
+ function form_is_dark_theme() {
6002
+ let attr = document.documentElement.getAttribute("data-theme");
6003
+ if (attr) return attr === "dark";
6004
+ return typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
6005
+ }
6006
+ /******************************************************************
5996
6007
  * With html field conf builds an html element,
5997
6008
  * ready to add to the form
5998
6009
  *
@@ -6433,7 +6444,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
6433
6444
  break;
6434
6445
  case "jsoneditor": {
6435
6446
  let attrs = {
6436
- class: "jsoneditor jse-theme-dark",
6447
+ class: "jsoneditor" + (form_is_dark_theme() ? " jse-theme-dark" : ""),
6437
6448
  name,
6438
6449
  style: ""
6439
6450
  };
@@ -6442,7 +6453,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
6442
6453
  else if (required) attrs.required = "";
6443
6454
  $extend = createElement2(["div", attrs]);
6444
6455
  $control.appendChild($extend);
6445
- $extend.jsoneditor = new JSONEditor({
6456
+ $extend.jsoneditor = createJSONEditor({
6446
6457
  target: $extend,
6447
6458
  props: {
6448
6459
  readOnly: !!readonly,
@@ -15397,7 +15408,7 @@ function register_c_yui_treedb_topics() {
15397
15408
  *
15398
15409
  * Table of TreeDB topic with FORM for editing
15399
15410
  *
15400
- * Copyright (c) 2025, ArtGins.
15411
+ * Copyright (c) 2025-2026, ArtGins.
15401
15412
  * All Rights Reserved.
15402
15413
  ***********************************************************************/
15403
15414
  /***************************************************************
@@ -15452,7 +15463,8 @@ var PRIVATE_DATA$2 = {
15452
15463
  topic_name: "",
15453
15464
  tabulator: null,
15454
15465
  form: null,
15455
- $popup: null
15466
+ $popup: null,
15467
+ on_keydown: null
15456
15468
  };
15457
15469
  var __gclass__$2 = null;
15458
15470
  function popup_mount_layer() {
@@ -16021,6 +16033,14 @@ function create_tabulator(gobj) {
16021
16033
  n = 0;
16022
16034
  }
16023
16035
  $rc.textContent = `${n} ${t("rows")}`;
16036
+ let single_page = true;
16037
+ try {
16038
+ let max = tabulator.getPageMax();
16039
+ single_page = max === false || max <= 1;
16040
+ } catch (e) {
16041
+ single_page = true;
16042
+ }
16043
+ tabulator.element.classList.toggle("yui-single-page", single_page);
16024
16044
  }
16025
16045
  tabulator._ready = false;
16026
16046
  tabulator.on("tableBuilt", function() {
@@ -16196,6 +16216,13 @@ function open_form_dialog(gobj, mode, record) {
16196
16216
  let desc = gobj_read_attr(gobj, "desc");
16197
16217
  let popup_id = gobj_read_str_attr(gobj, "popup_id");
16198
16218
  let topic_name = gobj_read_str_attr(gobj, "topic_name");
16219
+ let pkey = desc.pkey || "id";
16220
+ let title;
16221
+ if (mode === "create") title = t("new") + " " + t(topic_name);
16222
+ else {
16223
+ let rid = record ? String(record[pkey] ?? "") : "";
16224
+ title = t(topic_name) + (empty_string(rid) ? "" : " — " + rid);
16225
+ }
16199
16226
  let $element = createElement2([
16200
16227
  "div",
16201
16228
  {
@@ -16218,7 +16245,7 @@ function open_form_dialog(gobj, mode, record) {
16218
16245
  class: "modal-card-title",
16219
16246
  style: "text-align:center;"
16220
16247
  },
16221
- t(topic_name)
16248
+ title
16222
16249
  ], [
16223
16250
  "button",
16224
16251
  {
@@ -16264,8 +16291,16 @@ function open_form_dialog(gobj, mode, record) {
16264
16291
  }
16265
16292
  popup_mount_layer().appendChild($element);
16266
16293
  refresh_language($element, t);
16294
+ priv.on_keydown = function(evt) {
16295
+ if (evt.key === "Escape") {
16296
+ evt.stopPropagation();
16297
+ request_close_form_dialog(gobj);
16298
+ }
16299
+ };
16300
+ document.addEventListener("keydown", priv.on_keydown);
16267
16301
  gobj_start(form);
16268
- let $with_focus = $element.querySelector(".with-focus");
16302
+ let $with_focus = $element.querySelector(".with-focus:not([readonly])");
16303
+ if (!$with_focus) $with_focus = $element.querySelector("input.input:not([readonly]), textarea, select");
16269
16304
  if ($with_focus) $with_focus.focus();
16270
16305
  }
16271
16306
  /************************************************************
@@ -16291,6 +16326,10 @@ function request_close_form_dialog(gobj) {
16291
16326
  ************************************************************/
16292
16327
  function close_form_dialog(gobj) {
16293
16328
  let priv = gobj.priv;
16329
+ if (priv.on_keydown) {
16330
+ document.removeEventListener("keydown", priv.on_keydown);
16331
+ priv.on_keydown = null;
16332
+ }
16294
16333
  if (priv.form) {
16295
16334
  if (gobj_is_running(priv.form)) gobj_stop(priv.form);
16296
16335
  gobj_destroy(priv.form);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -42,6 +42,6 @@
42
42
  "tabulator-tables": "^6.5.2",
43
43
  "tom-select": "^2.6.0",
44
44
  "uplot": "^1.6.32",
45
- "vanilla-jsoneditor": "^0.23.0"
45
+ "vanilla-jsoneditor": "^3.12.0"
46
46
  }
47
47
  }
@@ -0,0 +1,50 @@
1
+ /***********************************************************************
2
+ * c_yui_form.css
3
+ *
4
+ * Theme integration for the third-party widgets C_YUI_FORM hosts
5
+ * (tom-select, vanilla-jsoneditor) plus form-owned input states.
6
+ *
7
+ * Everything maps to Bulma CSS vars, so a single rule set follows
8
+ * light/dark automatically (explicit data-theme or system theme) —
9
+ * no per-theme selectors needed.
10
+ *
11
+ * Copyright (c) 2026, ArtGins.
12
+ * All Rights Reserved.
13
+ ***********************************************************************/
14
+
15
+ /*--- tom-select (fkey fields): ships light-only colors ---*/
16
+ .C_YUI_FORM .ts-wrapper .ts-control {
17
+ background: var(--bulma-scheme-main, #fff);
18
+ color: var(--bulma-text, #4a4a4a);
19
+ border-color: var(--bulma-border, #dbdbdb);
20
+ }
21
+ .C_YUI_FORM .ts-control > input {
22
+ color: var(--bulma-text, #4a4a4a);
23
+ }
24
+ .C_YUI_FORM .ts-wrapper.multi .ts-control > div {
25
+ background: var(--bulma-background, #f2f2f2);
26
+ color: var(--bulma-text, #4a4a4a);
27
+ }
28
+ .C_YUI_FORM .ts-dropdown {
29
+ background: var(--bulma-scheme-main, #fff);
30
+ color: var(--bulma-text, #4a4a4a);
31
+ border-color: var(--bulma-border, #dbdbdb);
32
+ }
33
+ .C_YUI_FORM .ts-dropdown .option.active {
34
+ background: var(--bulma-link, #485fc7);
35
+ color: var(--bulma-link-invert, #fff);
36
+ }
37
+
38
+ /*--- vanilla-jsoneditor: align its accent with the app palette ---*/
39
+ .C_YUI_FORM .jsoneditor {
40
+ --jse-theme-color: var(--bulma-link, #485fc7);
41
+ --jse-theme-color-highlight: var(--bulma-link, #485fc7);
42
+ }
43
+
44
+ /*--- readonly fields (e.g. the pkey in update mode) look readonly ---*/
45
+ .C_YUI_FORM input.input[readonly] {
46
+ background-color: var(--bulma-background, #f5f5f5);
47
+ color: var(--bulma-text-weak, #7a7a7a);
48
+ border-color: var(--bulma-border, #dbdbdb);
49
+ cursor: default;
50
+ }
package/src/c_yui_form.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Form (in Window or not)
5
5
  *
6
- * Copyright (c) 2024-2025, ArtGins.
6
+ * Copyright (c) 2024-2026, ArtGins.
7
7
  * All Rights Reserved.
8
8
  ***********************************************************************/
9
9
  import {
@@ -53,8 +53,9 @@ import {t} from "i18next";
53
53
  import "tom-select/dist/css/tom-select.css"; // Import Tom-Select CSS
54
54
  import TomSelect from "tom-select"; // Import Tom-Select JS
55
55
 
56
- import { JSONEditor } from 'vanilla-jsoneditor';
56
+ import { createJSONEditor } from 'vanilla-jsoneditor';
57
57
  import "vanilla-jsoneditor/themes/jse-theme-dark.css";
58
+ import "./c_yui_form.css";
58
59
 
59
60
  import "tabulator-tables/dist/css/tabulator.min.css"; // Import Tabulator CSS
60
61
  import "tabulator-tables/dist/css/tabulator_bulma.css";
@@ -835,6 +836,23 @@ function apply_form_mode(gobj, $form)
835
836
  }
836
837
  }
837
838
 
839
+ /******************************************************************
840
+ * True when the app renders dark: explicit <html data-theme>, or
841
+ * the OS scheme when the attribute is absent ("system" theme
842
+ * removes it — see themes.js). Read at field-build time; the
843
+ * hosting dialog rebuilds the form on every open.
844
+ ******************************************************************/
845
+ function form_is_dark_theme()
846
+ {
847
+ let attr = document.documentElement.getAttribute("data-theme");
848
+ if(attr) {
849
+ return attr === "dark";
850
+ }
851
+ return typeof window !== "undefined" &&
852
+ window.matchMedia &&
853
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
854
+ }
855
+
838
856
  /******************************************************************
839
857
  * With html field conf builds an html element,
840
858
  * ready to add to the form
@@ -1236,7 +1254,7 @@ function create_form_field(
1236
1254
  case "jsoneditor":
1237
1255
  {
1238
1256
  let attrs = {
1239
- class: 'jsoneditor jse-theme-dark',
1257
+ class: 'jsoneditor' + (form_is_dark_theme() ? ' jse-theme-dark' : ''),
1240
1258
  name: name,
1241
1259
  style: ''
1242
1260
  };
@@ -1250,7 +1268,7 @@ function create_form_field(
1250
1268
  let extend = ['div', attrs];
1251
1269
  $extend = createElement2(extend);
1252
1270
  $control.appendChild($extend);
1253
- $extend.jsoneditor = new JSONEditor({
1271
+ $extend.jsoneditor = createJSONEditor({
1254
1272
  target: $extend,
1255
1273
  props: {
1256
1274
  readOnly: !!readonly,
@@ -141,16 +141,24 @@
141
141
  .yui-nav-vertical .yui-nav-label { white-space: nowrap; }
142
142
 
143
143
  /*--- icon-bar (bottom/top icon bar) ---*/
144
+ /* Distribute-or-scroll: items grow to share spare width but never
145
+ * shrink below their content (flex: 1 0 auto); when they don't fit the
146
+ * bar scrolls horizontally. justify-content must stay flex-start —
147
+ * with space-around + overflow the leading items get clipped past the
148
+ * left edge and become unreachable. */
144
149
  .yui-nav-iconbar {
145
150
  display: flex;
146
- justify-content: space-around;
151
+ justify-content: flex-start;
147
152
  align-items: stretch;
148
153
  margin: 0;
149
154
  padding: 0.25rem 0;
150
155
  width: 100%;
151
156
  background: var(--bulma-scheme-main, #fff);
157
+ overflow-x: auto;
158
+ scrollbar-width: thin;
152
159
  }
153
- .yui-nav-iconbar .level-item { flex: 1 1 0; justify-content: center; }
160
+ .yui-nav-iconbar .level-item { flex: 1 0 auto; justify-content: center; }
161
+ .yui-nav-iconbar .yui-nav-label { white-space: nowrap; }
154
162
  .yui-nav-iconbar .yui-nav-item {
155
163
  display: flex;
156
164
  flex-direction: column;
@@ -22,3 +22,26 @@
22
22
  .tabulator .tabulator-footer .yui-tabulator-rowcount {
23
23
  line-height: 1.5;
24
24
  }
25
+
26
+ /* All rows fit in one page: the page-size selector and the
27
+ * First/Prev/1/Next/Last strip are pure noise (worst on mobile) —
28
+ * update_rowcount() toggles the class on every data change. */
29
+ .tabulator.yui-single-page .tabulator-footer .tabulator-paginator {
30
+ display: none;
31
+ }
32
+
33
+ /* Toolbar wrapping discipline (mobile): the record-buttons group
34
+ * must never split across lines — Bulma .buttons wraps internally,
35
+ * which stacked the toolbar into 3 rows on narrow phones. Keep the
36
+ * group on one line and scroll it horizontally if it overflows
37
+ * (same distribute-or-scroll pattern as the nav icon-bar); the
38
+ * search+refresh block then wraps as a whole, so the toolbar is
39
+ * 1 row when it fits and exactly 2 when it doesn't. */
40
+ .toolbar_tabulator_table .buttons {
41
+ flex-wrap: nowrap;
42
+ overflow-x: auto;
43
+ scrollbar-width: thin;
44
+ }
45
+ .toolbar_tabulator_table .control.has-icons-left {
46
+ min-width: 0;
47
+ }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Table of TreeDB topic with FORM for editing
5
5
  *
6
- * Copyright (c) 2025, ArtGins.
6
+ * Copyright (c) 2025-2026, ArtGins.
7
7
  * All Rights Reserved.
8
8
  ***********************************************************************/
9
9
 
@@ -132,6 +132,7 @@ let PRIVATE_DATA = {
132
132
  tabulator: null,
133
133
  form: null, // hosted C_YUI_FORM child (while dialog open)
134
134
  $popup: null, // dialog DOM element
135
+ on_keydown: null, // document Escape handler (while dialog open)
135
136
  };
136
137
 
137
138
  let __gclass__ = null;
@@ -827,7 +828,9 @@ function create_tabulator(gobj)
827
828
  }
828
829
  let tabulator = new Tabulator($table_el, tabulator_settings);
829
830
 
830
- /* Keep the footer in sync with the visible (active) row count. */
831
+ /* Keep the footer in sync with the visible (active) row count,
832
+ * and hide the pagination chrome while everything fits in one
833
+ * page (it comes back as soon as a second page exists). */
831
834
  function update_rowcount() {
832
835
  let $rc = tabulator.element &&
833
836
  tabulator.element.querySelector(".yui-tabulator-rowcount");
@@ -841,6 +844,15 @@ function create_tabulator(gobj)
841
844
  n = 0;
842
845
  }
843
846
  $rc.textContent = `${n} ${t("rows")}`;
847
+
848
+ let single_page = true;
849
+ try {
850
+ let max = tabulator.getPageMax(); // false = pagination off
851
+ single_page = (max === false) || max <= 1;
852
+ } catch(e) {
853
+ single_page = true;
854
+ }
855
+ tabulator.element.classList.toggle("yui-single-page", single_page);
844
856
  }
845
857
 
846
858
  tabulator._ready = false;
@@ -1103,12 +1115,23 @@ function open_form_dialog(gobj, mode, record)
1103
1115
  let popup_id = gobj_read_str_attr(gobj, "popup_id");
1104
1116
  let topic_name = gobj_read_str_attr(gobj, "topic_name");
1105
1117
 
1118
+ /* Title says what you are doing: "new <topic>" on create,
1119
+ * "<topic> — <pkey>" on update. */
1120
+ let pkey = desc.pkey || "id";
1121
+ let title;
1122
+ if(mode === "create") {
1123
+ title = t("new") + " " + t(topic_name);
1124
+ } else {
1125
+ let rid = record ? String(record[pkey] ?? "") : "";
1126
+ title = t(topic_name) + (empty_string(rid) ? "" : " — " + rid);
1127
+ }
1128
+
1106
1129
  let $element = createElement2([
1107
1130
  'div', {id: popup_id, class: 'modal is-active'}, [
1108
1131
  ['div', {class: 'modal-background'}, ''],
1109
1132
  ['div', {class: 'modal-card modal-is-responsive'}, [
1110
1133
  ['header', {class: 'modal-card-head p-3'}, [
1111
- ['p', {class: 'modal-card-title', style: 'text-align:center;'}, t(topic_name)],
1134
+ ['p', {class: 'modal-card-title', style: 'text-align:center;'}, title],
1112
1135
  ['button', {class: 'delete is-large', 'aria-label': 'close'}, '', {
1113
1136
  click: function(evt) {
1114
1137
  evt.stopPropagation();
@@ -1161,9 +1184,25 @@ function open_form_dialog(gobj, mode, record)
1161
1184
  popup_mount_layer().appendChild($element);
1162
1185
  refresh_language($element, t);
1163
1186
 
1187
+ /* Escape closes through the same unsaved-changes guard as the X. */
1188
+ priv.on_keydown = function(evt) {
1189
+ if(evt.key === "Escape") {
1190
+ evt.stopPropagation();
1191
+ request_close_form_dialog(gobj);
1192
+ }
1193
+ };
1194
+ document.addEventListener("keydown", priv.on_keydown);
1195
+
1164
1196
  gobj_start(form); // mt_start loads `record` into the form
1165
1197
 
1166
- let $with_focus = $element.querySelector('.with-focus');
1198
+ /* Focus the pkey unless the mode made it readonly (update)
1199
+ * then the first editable field. */
1200
+ let $with_focus = $element.querySelector('.with-focus:not([readonly])');
1201
+ if(!$with_focus) {
1202
+ $with_focus = $element.querySelector(
1203
+ 'input.input:not([readonly]), textarea, select'
1204
+ );
1205
+ }
1167
1206
  if($with_focus) {
1168
1207
  $with_focus.focus();
1169
1208
  }
@@ -1197,6 +1236,10 @@ function request_close_form_dialog(gobj)
1197
1236
  function close_form_dialog(gobj)
1198
1237
  {
1199
1238
  let priv = gobj.priv;
1239
+ if(priv.on_keydown) {
1240
+ document.removeEventListener("keydown", priv.on_keydown);
1241
+ priv.on_keydown = null;
1242
+ }
1200
1243
  if(priv.form) {
1201
1244
  if(gobj_is_running(priv.form)) {
1202
1245
  gobj_stop(priv.form);