@yuneta/gobj-ui 2.3.1 → 2.5.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.
@@ -1562,7 +1562,7 @@ function yui_toolbar(attrs = {}, items = []) {
1562
1562
  * The tabs in a content-layer to manage sub-containers can be managed by yui_tabs.js
1563
1563
  * or be integrated in each gobj.
1564
1564
  *
1565
- * Copyright (c) 2025, ArtGins.
1565
+ * Copyright (c) 2025-2026, ArtGins.
1566
1566
  * All Rights Reserved.
1567
1567
  ***********************************************************************/
1568
1568
  /***************************************************************
@@ -2153,9 +2153,19 @@ console.log("GLOBAL callback");
2153
2153
  ************************************************************/
2154
2154
  function display_volatil_modal({ title, msg, callback, type, x_close, buttons }) {
2155
2155
  const callback_ = callback;
2156
+ let type_ = (type || "info").toLowerCase();
2157
+ if (type_ === "danger") type_ = "error";
2158
+ const TYPE_ICONS = {
2159
+ "info": "yi-circle-info",
2160
+ "question": "yi-question",
2161
+ "success": "yi-square-check",
2162
+ "warning": "yi-triangle-exclamation",
2163
+ "error": "yi-circle-exclamation"
2164
+ };
2165
+ let icon = TYPE_ICONS[type_] || TYPE_ICONS["info"];
2156
2166
  let $element = (0, _yuneta_gobj_js.createElement2)([
2157
2167
  "div",
2158
- { class: "modal" },
2168
+ { class: `modal yui-volatil is-${type_}` },
2159
2169
  [[
2160
2170
  "div",
2161
2171
  { class: "modal-background" },
@@ -2167,44 +2177,39 @@ function display_volatil_modal({ title, msg, callback, type, x_close, buttons })
2167
2177
  ]]
2168
2178
  ]);
2169
2179
  const destroyModal = () => {
2180
+ document.removeEventListener("keydown", on_keydown, true);
2170
2181
  $element.classList.remove("is-active");
2171
2182
  $element.parentNode.removeChild($element);
2172
2183
  };
2184
+ const on_keydown = (evt) => {
2185
+ if (evt.key !== "Escape") return;
2186
+ let $modals = document.querySelectorAll(".modal.is-active");
2187
+ if ($modals.length && $modals[$modals.length - 1] !== $element) return;
2188
+ evt.stopPropagation();
2189
+ evt.preventDefault();
2190
+ let $cancel = $element.querySelector(".cancelButton, .delete");
2191
+ if ($cancel) $cancel.click();
2192
+ else destroyModal();
2193
+ };
2194
+ document.addEventListener("keydown", on_keydown, true);
2173
2195
  let $modal_card = $element.querySelector(".modal-card");
2174
- if (title || x_close) {
2175
- if (!type) type = "";
2176
- let background_color = "";
2177
- switch (type.toLowerCase()) {
2178
- case "info":
2179
- background_color = "has-background-info";
2180
- break;
2181
- case "success":
2182
- background_color = "has-background-success";
2183
- break;
2184
- case "warning":
2185
- background_color = "has-background-warning";
2186
- break;
2187
- case "error":
2188
- case "danger":
2189
- background_color = "has-background-danger";
2190
- break;
2191
- }
2192
- let title_ = !title ? [] : [
2193
- "p",
2194
- { class: "modal-card-title has-text-centered" },
2195
- title
2196
- ];
2197
- let x_close_ = !x_close ? [] : ["button", {
2198
- class: "delete is-large",
2199
- "aria-label": "close"
2200
- }];
2201
- let $header = (0, _yuneta_gobj_js.createElement2)([
2202
- "header",
2203
- { class: `modal-card-head ${background_color} p-3` },
2204
- [title_, x_close_]
2205
- ]);
2206
- $modal_card.appendChild($header);
2207
- }
2196
+ $modal_card.appendChild((0, _yuneta_gobj_js.createElement2)([
2197
+ "div",
2198
+ { class: "yui-volatil-icon" },
2199
+ [["i", {
2200
+ class: icon,
2201
+ "aria-hidden": "true"
2202
+ }]]
2203
+ ]));
2204
+ if (title) $modal_card.appendChild((0, _yuneta_gobj_js.createElement2)([
2205
+ "p",
2206
+ { class: "yui-volatil-title has-text-centered" },
2207
+ title
2208
+ ]));
2209
+ if (x_close) $modal_card.appendChild((0, _yuneta_gobj_js.createElement2)(["button", {
2210
+ class: "delete yui-volatil-x",
2211
+ "aria-label": "close"
2212
+ }]));
2208
2213
  let $section = (0, _yuneta_gobj_js.createElement2)([
2209
2214
  "section",
2210
2215
  { class: "modal-card-body has-text-centered" },
@@ -2265,7 +2270,7 @@ function display_info_message(title, msg, callback) {
2265
2270
  msg,
2266
2271
  callback,
2267
2272
  type: "info",
2268
- buttons: [{ content: `<button class="button is-success px-6 with-focus">${(0, i18next.t)("accept")}</button>` }]
2273
+ buttons: [{ content: `<button class="button is-info px-6 with-focus">${(0, i18next.t)("accept")}</button>` }]
2269
2274
  });
2270
2275
  }
2271
2276
  /************************************************************
@@ -2278,7 +2283,7 @@ function display_warning_message(title, msg, callback) {
2278
2283
  msg,
2279
2284
  callback,
2280
2285
  type: "warning",
2281
- buttons: [{ content: `<button class="button is-success px-6 with-focus">${(0, i18next.t)("accept")}</button>` }]
2286
+ buttons: [{ content: `<button class="button is-warning px-6 with-focus">${(0, i18next.t)("accept")}</button>` }]
2282
2287
  });
2283
2288
  }
2284
2289
  /************************************************************
@@ -2291,7 +2296,7 @@ function display_error_message(title, msg, callback, without_buttons) {
2291
2296
  msg,
2292
2297
  callback,
2293
2298
  type: "error",
2294
- buttons: without_buttons ? [] : [{ content: `<button class="button is-success px-6 with-focus">${(0, i18next.t)("accept")}</button>` }]
2299
+ buttons: without_buttons ? [] : [{ content: `<button class="button is-danger px-6 with-focus">${(0, i18next.t)("accept")}</button>` }]
2295
2300
  });
2296
2301
  }
2297
2302
  /************************************************************
@@ -2299,7 +2304,7 @@ function display_error_message(title, msg, callback, without_buttons) {
2299
2304
  ************************************************************/
2300
2305
  function get_yesnocancel(msg, callback) {
2301
2306
  let content = `<div class="">
2302
- <p class="title">${(0, i18next.t)(msg)}</p>
2307
+ <p class="yui-volatil-msg">${(0, i18next.t)(msg)}</p>
2303
2308
  <div class="buttons is-centered">
2304
2309
  <button class="button to-close is-success px-6 with-focus yesButton">${(0, i18next.t)("yes")}</button>
2305
2310
  <button class="button to-close is-danger px-6 noButton">${(0, i18next.t)("no")}</button>
@@ -2315,7 +2320,7 @@ function get_yesnocancel(msg, callback) {
2315
2320
  title: null,
2316
2321
  msg: content,
2317
2322
  callback: callback_,
2318
- type: "info"
2323
+ type: "question"
2319
2324
  });
2320
2325
  }
2321
2326
  /************************************************************
@@ -2323,7 +2328,7 @@ function get_yesnocancel(msg, callback) {
2323
2328
  ************************************************************/
2324
2329
  function get_yesno(msg, callback) {
2325
2330
  let content = `<div class="">
2326
- <p class="title">${msg}</p>
2331
+ <p class="yui-volatil-msg">${msg}</p>
2327
2332
  <div class="buttons is-centered">
2328
2333
  <button class="button to-close is-success px-6 with-focus yesButton">${(0, i18next.t)("yes")}</button>
2329
2334
  <button class="button to-close is-danger px-6 noButton">${(0, i18next.t)("no")}</button>
@@ -2338,7 +2343,7 @@ function get_yesno(msg, callback) {
2338
2343
  title: null,
2339
2344
  msg: content,
2340
2345
  callback: callback_,
2341
- type: "info"
2346
+ type: "question"
2342
2347
  });
2343
2348
  }
2344
2349
  /************************************************************
@@ -2346,7 +2351,7 @@ function get_yesno(msg, callback) {
2346
2351
  ************************************************************/
2347
2352
  function get_ok(msg, callback) {
2348
2353
  let content = `<div class="">
2349
- <p class="title">${msg}</p>
2354
+ <p class="yui-volatil-msg">${msg}</p>
2350
2355
  <div class="buttons is-centered">
2351
2356
  <button class="button to-close is-success px-6 with-focus yesButton">${(0, i18next.t)("accept")}</button>
2352
2357
  </div>
@@ -2360,7 +2365,7 @@ function get_ok(msg, callback) {
2360
2365
  title: null,
2361
2366
  msg: content,
2362
2367
  callback: callback_,
2363
- type: "info"
2368
+ type: "success"
2364
2369
  });
2365
2370
  }
2366
2371
  /************************************************************
@@ -5383,7 +5388,7 @@ function register_c_yui_wizard() {
5383
5388
  *
5384
5389
  * Form (in Window or not)
5385
5390
  *
5386
- * Copyright (c) 2024-2025, ArtGins.
5391
+ * Copyright (c) 2024-2026, ArtGins.
5387
5392
  * All Rights Reserved.
5388
5393
  ***********************************************************************/
5389
5394
  /***************************************************************
@@ -5998,6 +6003,17 @@ function apply_form_mode(gobj, $form) {
5998
6003
  }
5999
6004
  }
6000
6005
  /******************************************************************
6006
+ * True when the app renders dark: explicit <html data-theme>, or
6007
+ * the OS scheme when the attribute is absent ("system" theme
6008
+ * removes it — see themes.js). Read at field-build time; the
6009
+ * hosting dialog rebuilds the form on every open.
6010
+ ******************************************************************/
6011
+ function form_is_dark_theme() {
6012
+ let attr = document.documentElement.getAttribute("data-theme");
6013
+ if (attr) return attr === "dark";
6014
+ return typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
6015
+ }
6016
+ /******************************************************************
6001
6017
  * With html field conf builds an html element,
6002
6018
  * ready to add to the form
6003
6019
  *
@@ -6438,7 +6454,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
6438
6454
  break;
6439
6455
  case "jsoneditor": {
6440
6456
  let attrs = {
6441
- class: "jsoneditor jse-theme-dark",
6457
+ class: "jsoneditor" + (form_is_dark_theme() ? " jse-theme-dark" : ""),
6442
6458
  name,
6443
6459
  style: ""
6444
6460
  };
@@ -15402,7 +15418,7 @@ function register_c_yui_treedb_topics() {
15402
15418
  *
15403
15419
  * Table of TreeDB topic with FORM for editing
15404
15420
  *
15405
- * Copyright (c) 2025, ArtGins.
15421
+ * Copyright (c) 2025-2026, ArtGins.
15406
15422
  * All Rights Reserved.
15407
15423
  ***********************************************************************/
15408
15424
  /***************************************************************
@@ -15457,7 +15473,8 @@ var PRIVATE_DATA$2 = {
15457
15473
  topic_name: "",
15458
15474
  tabulator: null,
15459
15475
  form: null,
15460
- $popup: null
15476
+ $popup: null,
15477
+ on_keydown: null
15461
15478
  };
15462
15479
  var __gclass__$2 = null;
15463
15480
  function popup_mount_layer() {
@@ -16026,6 +16043,14 @@ function create_tabulator(gobj) {
16026
16043
  n = 0;
16027
16044
  }
16028
16045
  $rc.textContent = `${n} ${(0, i18next.t)("rows")}`;
16046
+ let single_page = true;
16047
+ try {
16048
+ let max = tabulator.getPageMax();
16049
+ single_page = max === false || max <= 1;
16050
+ } catch (e) {
16051
+ single_page = true;
16052
+ }
16053
+ tabulator.element.classList.toggle("yui-single-page", single_page);
16029
16054
  }
16030
16055
  tabulator._ready = false;
16031
16056
  tabulator.on("tableBuilt", function() {
@@ -16201,6 +16226,13 @@ function open_form_dialog(gobj, mode, record) {
16201
16226
  let desc = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "desc");
16202
16227
  let popup_id = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "popup_id");
16203
16228
  let topic_name = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "topic_name");
16229
+ let pkey = desc.pkey || "id";
16230
+ let title;
16231
+ if (mode === "create") title = (0, i18next.t)("new") + " " + (0, i18next.t)(topic_name);
16232
+ else {
16233
+ let rid = record ? String(record[pkey] ?? "") : "";
16234
+ title = (0, i18next.t)(topic_name) + ((0, _yuneta_gobj_js.empty_string)(rid) ? "" : " — " + rid);
16235
+ }
16204
16236
  let $element = (0, _yuneta_gobj_js.createElement2)([
16205
16237
  "div",
16206
16238
  {
@@ -16223,7 +16255,7 @@ function open_form_dialog(gobj, mode, record) {
16223
16255
  class: "modal-card-title",
16224
16256
  style: "text-align:center;"
16225
16257
  },
16226
- (0, i18next.t)(topic_name)
16258
+ title
16227
16259
  ], [
16228
16260
  "button",
16229
16261
  {
@@ -16269,8 +16301,16 @@ function open_form_dialog(gobj, mode, record) {
16269
16301
  }
16270
16302
  popup_mount_layer().appendChild($element);
16271
16303
  (0, _yuneta_gobj_js.refresh_language)($element, i18next.t);
16304
+ priv.on_keydown = function(evt) {
16305
+ if (evt.key === "Escape") {
16306
+ evt.stopPropagation();
16307
+ request_close_form_dialog(gobj);
16308
+ }
16309
+ };
16310
+ document.addEventListener("keydown", priv.on_keydown);
16272
16311
  (0, _yuneta_gobj_js.gobj_start)(form);
16273
- let $with_focus = $element.querySelector(".with-focus");
16312
+ let $with_focus = $element.querySelector(".with-focus:not([readonly])");
16313
+ if (!$with_focus) $with_focus = $element.querySelector("input.input:not([readonly]), textarea, select");
16274
16314
  if ($with_focus) $with_focus.focus();
16275
16315
  }
16276
16316
  /************************************************************
@@ -16296,6 +16336,10 @@ function request_close_form_dialog(gobj) {
16296
16336
  ************************************************************/
16297
16337
  function close_form_dialog(gobj) {
16298
16338
  let priv = gobj.priv;
16339
+ if (priv.on_keydown) {
16340
+ document.removeEventListener("keydown", priv.on_keydown);
16341
+ priv.on_keydown = null;
16342
+ }
16299
16343
  if (priv.form) {
16300
16344
  if ((0, _yuneta_gobj_js.gobj_is_running)(priv.form)) (0, _yuneta_gobj_js.gobj_stop)(priv.form);
16301
16345
  (0, _yuneta_gobj_js.gobj_destroy)(priv.form);
@@ -1557,7 +1557,7 @@ function yui_toolbar(attrs = {}, items = []) {
1557
1557
  * The tabs in a content-layer to manage sub-containers can be managed by yui_tabs.js
1558
1558
  * or be integrated in each gobj.
1559
1559
  *
1560
- * Copyright (c) 2025, ArtGins.
1560
+ * Copyright (c) 2025-2026, ArtGins.
1561
1561
  * All Rights Reserved.
1562
1562
  ***********************************************************************/
1563
1563
  /***************************************************************
@@ -2148,9 +2148,19 @@ console.log("GLOBAL callback");
2148
2148
  ************************************************************/
2149
2149
  function display_volatil_modal({ title, msg, callback, type, x_close, buttons }) {
2150
2150
  const callback_ = callback;
2151
+ let type_ = (type || "info").toLowerCase();
2152
+ if (type_ === "danger") type_ = "error";
2153
+ const TYPE_ICONS = {
2154
+ "info": "yi-circle-info",
2155
+ "question": "yi-question",
2156
+ "success": "yi-square-check",
2157
+ "warning": "yi-triangle-exclamation",
2158
+ "error": "yi-circle-exclamation"
2159
+ };
2160
+ let icon = TYPE_ICONS[type_] || TYPE_ICONS["info"];
2151
2161
  let $element = createElement2([
2152
2162
  "div",
2153
- { class: "modal" },
2163
+ { class: `modal yui-volatil is-${type_}` },
2154
2164
  [[
2155
2165
  "div",
2156
2166
  { class: "modal-background" },
@@ -2162,44 +2172,39 @@ function display_volatil_modal({ title, msg, callback, type, x_close, buttons })
2162
2172
  ]]
2163
2173
  ]);
2164
2174
  const destroyModal = () => {
2175
+ document.removeEventListener("keydown", on_keydown, true);
2165
2176
  $element.classList.remove("is-active");
2166
2177
  $element.parentNode.removeChild($element);
2167
2178
  };
2179
+ const on_keydown = (evt) => {
2180
+ if (evt.key !== "Escape") return;
2181
+ let $modals = document.querySelectorAll(".modal.is-active");
2182
+ if ($modals.length && $modals[$modals.length - 1] !== $element) return;
2183
+ evt.stopPropagation();
2184
+ evt.preventDefault();
2185
+ let $cancel = $element.querySelector(".cancelButton, .delete");
2186
+ if ($cancel) $cancel.click();
2187
+ else destroyModal();
2188
+ };
2189
+ document.addEventListener("keydown", on_keydown, true);
2168
2190
  let $modal_card = $element.querySelector(".modal-card");
2169
- if (title || x_close) {
2170
- if (!type) type = "";
2171
- let background_color = "";
2172
- switch (type.toLowerCase()) {
2173
- case "info":
2174
- background_color = "has-background-info";
2175
- break;
2176
- case "success":
2177
- background_color = "has-background-success";
2178
- break;
2179
- case "warning":
2180
- background_color = "has-background-warning";
2181
- break;
2182
- case "error":
2183
- case "danger":
2184
- background_color = "has-background-danger";
2185
- break;
2186
- }
2187
- let title_ = !title ? [] : [
2188
- "p",
2189
- { class: "modal-card-title has-text-centered" },
2190
- title
2191
- ];
2192
- let x_close_ = !x_close ? [] : ["button", {
2193
- class: "delete is-large",
2194
- "aria-label": "close"
2195
- }];
2196
- let $header = createElement2([
2197
- "header",
2198
- { class: `modal-card-head ${background_color} p-3` },
2199
- [title_, x_close_]
2200
- ]);
2201
- $modal_card.appendChild($header);
2202
- }
2191
+ $modal_card.appendChild(createElement2([
2192
+ "div",
2193
+ { class: "yui-volatil-icon" },
2194
+ [["i", {
2195
+ class: icon,
2196
+ "aria-hidden": "true"
2197
+ }]]
2198
+ ]));
2199
+ if (title) $modal_card.appendChild(createElement2([
2200
+ "p",
2201
+ { class: "yui-volatil-title has-text-centered" },
2202
+ title
2203
+ ]));
2204
+ if (x_close) $modal_card.appendChild(createElement2(["button", {
2205
+ class: "delete yui-volatil-x",
2206
+ "aria-label": "close"
2207
+ }]));
2203
2208
  let $section = createElement2([
2204
2209
  "section",
2205
2210
  { class: "modal-card-body has-text-centered" },
@@ -2260,7 +2265,7 @@ function display_info_message(title, msg, callback) {
2260
2265
  msg,
2261
2266
  callback,
2262
2267
  type: "info",
2263
- buttons: [{ content: `<button class="button is-success px-6 with-focus">${t("accept")}</button>` }]
2268
+ buttons: [{ content: `<button class="button is-info px-6 with-focus">${t("accept")}</button>` }]
2264
2269
  });
2265
2270
  }
2266
2271
  /************************************************************
@@ -2273,7 +2278,7 @@ function display_warning_message(title, msg, callback) {
2273
2278
  msg,
2274
2279
  callback,
2275
2280
  type: "warning",
2276
- buttons: [{ content: `<button class="button is-success px-6 with-focus">${t("accept")}</button>` }]
2281
+ buttons: [{ content: `<button class="button is-warning px-6 with-focus">${t("accept")}</button>` }]
2277
2282
  });
2278
2283
  }
2279
2284
  /************************************************************
@@ -2286,7 +2291,7 @@ function display_error_message(title, msg, callback, without_buttons) {
2286
2291
  msg,
2287
2292
  callback,
2288
2293
  type: "error",
2289
- buttons: without_buttons ? [] : [{ content: `<button class="button is-success px-6 with-focus">${t("accept")}</button>` }]
2294
+ buttons: without_buttons ? [] : [{ content: `<button class="button is-danger px-6 with-focus">${t("accept")}</button>` }]
2290
2295
  });
2291
2296
  }
2292
2297
  /************************************************************
@@ -2294,7 +2299,7 @@ function display_error_message(title, msg, callback, without_buttons) {
2294
2299
  ************************************************************/
2295
2300
  function get_yesnocancel(msg, callback) {
2296
2301
  let content = `<div class="">
2297
- <p class="title">${t(msg)}</p>
2302
+ <p class="yui-volatil-msg">${t(msg)}</p>
2298
2303
  <div class="buttons is-centered">
2299
2304
  <button class="button to-close is-success px-6 with-focus yesButton">${t("yes")}</button>
2300
2305
  <button class="button to-close is-danger px-6 noButton">${t("no")}</button>
@@ -2310,7 +2315,7 @@ function get_yesnocancel(msg, callback) {
2310
2315
  title: null,
2311
2316
  msg: content,
2312
2317
  callback: callback_,
2313
- type: "info"
2318
+ type: "question"
2314
2319
  });
2315
2320
  }
2316
2321
  /************************************************************
@@ -2318,7 +2323,7 @@ function get_yesnocancel(msg, callback) {
2318
2323
  ************************************************************/
2319
2324
  function get_yesno(msg, callback) {
2320
2325
  let content = `<div class="">
2321
- <p class="title">${msg}</p>
2326
+ <p class="yui-volatil-msg">${msg}</p>
2322
2327
  <div class="buttons is-centered">
2323
2328
  <button class="button to-close is-success px-6 with-focus yesButton">${t("yes")}</button>
2324
2329
  <button class="button to-close is-danger px-6 noButton">${t("no")}</button>
@@ -2333,7 +2338,7 @@ function get_yesno(msg, callback) {
2333
2338
  title: null,
2334
2339
  msg: content,
2335
2340
  callback: callback_,
2336
- type: "info"
2341
+ type: "question"
2337
2342
  });
2338
2343
  }
2339
2344
  /************************************************************
@@ -2341,7 +2346,7 @@ function get_yesno(msg, callback) {
2341
2346
  ************************************************************/
2342
2347
  function get_ok(msg, callback) {
2343
2348
  let content = `<div class="">
2344
- <p class="title">${msg}</p>
2349
+ <p class="yui-volatil-msg">${msg}</p>
2345
2350
  <div class="buttons is-centered">
2346
2351
  <button class="button to-close is-success px-6 with-focus yesButton">${t("accept")}</button>
2347
2352
  </div>
@@ -2355,7 +2360,7 @@ function get_ok(msg, callback) {
2355
2360
  title: null,
2356
2361
  msg: content,
2357
2362
  callback: callback_,
2358
- type: "info"
2363
+ type: "success"
2359
2364
  });
2360
2365
  }
2361
2366
  /************************************************************
@@ -5378,7 +5383,7 @@ function register_c_yui_wizard() {
5378
5383
  *
5379
5384
  * Form (in Window or not)
5380
5385
  *
5381
- * Copyright (c) 2024-2025, ArtGins.
5386
+ * Copyright (c) 2024-2026, ArtGins.
5382
5387
  * All Rights Reserved.
5383
5388
  ***********************************************************************/
5384
5389
  /***************************************************************
@@ -5993,6 +5998,17 @@ function apply_form_mode(gobj, $form) {
5993
5998
  }
5994
5999
  }
5995
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
+ /******************************************************************
5996
6012
  * With html field conf builds an html element,
5997
6013
  * ready to add to the form
5998
6014
  *
@@ -6433,7 +6449,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
6433
6449
  break;
6434
6450
  case "jsoneditor": {
6435
6451
  let attrs = {
6436
- class: "jsoneditor jse-theme-dark",
6452
+ class: "jsoneditor" + (form_is_dark_theme() ? " jse-theme-dark" : ""),
6437
6453
  name,
6438
6454
  style: ""
6439
6455
  };
@@ -15397,7 +15413,7 @@ function register_c_yui_treedb_topics() {
15397
15413
  *
15398
15414
  * Table of TreeDB topic with FORM for editing
15399
15415
  *
15400
- * Copyright (c) 2025, ArtGins.
15416
+ * Copyright (c) 2025-2026, ArtGins.
15401
15417
  * All Rights Reserved.
15402
15418
  ***********************************************************************/
15403
15419
  /***************************************************************
@@ -15452,7 +15468,8 @@ var PRIVATE_DATA$2 = {
15452
15468
  topic_name: "",
15453
15469
  tabulator: null,
15454
15470
  form: null,
15455
- $popup: null
15471
+ $popup: null,
15472
+ on_keydown: null
15456
15473
  };
15457
15474
  var __gclass__$2 = null;
15458
15475
  function popup_mount_layer() {
@@ -16021,6 +16038,14 @@ function create_tabulator(gobj) {
16021
16038
  n = 0;
16022
16039
  }
16023
16040
  $rc.textContent = `${n} ${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);
16024
16049
  }
16025
16050
  tabulator._ready = false;
16026
16051
  tabulator.on("tableBuilt", function() {
@@ -16196,6 +16221,13 @@ function open_form_dialog(gobj, mode, record) {
16196
16221
  let desc = gobj_read_attr(gobj, "desc");
16197
16222
  let popup_id = gobj_read_str_attr(gobj, "popup_id");
16198
16223
  let topic_name = gobj_read_str_attr(gobj, "topic_name");
16224
+ let pkey = desc.pkey || "id";
16225
+ let title;
16226
+ if (mode === "create") title = t("new") + " " + t(topic_name);
16227
+ else {
16228
+ let rid = record ? String(record[pkey] ?? "") : "";
16229
+ title = t(topic_name) + (empty_string(rid) ? "" : " — " + rid);
16230
+ }
16199
16231
  let $element = createElement2([
16200
16232
  "div",
16201
16233
  {
@@ -16218,7 +16250,7 @@ function open_form_dialog(gobj, mode, record) {
16218
16250
  class: "modal-card-title",
16219
16251
  style: "text-align:center;"
16220
16252
  },
16221
- t(topic_name)
16253
+ title
16222
16254
  ], [
16223
16255
  "button",
16224
16256
  {
@@ -16264,8 +16296,16 @@ function open_form_dialog(gobj, mode, record) {
16264
16296
  }
16265
16297
  popup_mount_layer().appendChild($element);
16266
16298
  refresh_language($element, 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);
16267
16306
  gobj_start(form);
16268
- 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");
16269
16309
  if ($with_focus) $with_focus.focus();
16270
16310
  }
16271
16311
  /************************************************************
@@ -16291,6 +16331,10 @@ function request_close_form_dialog(gobj) {
16291
16331
  ************************************************************/
16292
16332
  function close_form_dialog(gobj) {
16293
16333
  let priv = gobj.priv;
16334
+ if (priv.on_keydown) {
16335
+ document.removeEventListener("keydown", priv.on_keydown);
16336
+ priv.on_keydown = null;
16337
+ }
16294
16338
  if (priv.form) {
16295
16339
  if (gobj_is_running(priv.form)) gobj_stop(priv.form);
16296
16340
  gobj_destroy(priv.form);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "2.3.1",
3
+ "version": "2.5.0",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -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 {
@@ -55,6 +55,7 @@ import TomSelect from "tom-select"; // Import Tom-Select JS
55
55
 
56
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
  };
@@ -421,3 +421,81 @@ a.flex-horizontal-section { /* To make flex any <a> */
421
421
  color: var(--bulma-link-invert);
422
422
  }
423
423
  }
424
+
425
+ /*============================================================
426
+ * Volatil modals (display_volatil_modal):
427
+ * get_yesnocancel / get_yesno / get_ok and the typed
428
+ * info/warning/error messages. Icon-centric layout: a
429
+ * narrow rounded card with a tinted round icon of the
430
+ * type; everything maps to Bulma vars so one rule set
431
+ * follows light and dark.
432
+ *============================================================*/
433
+ .modal.yui-volatil .modal-background {
434
+ background-color: color-mix(in srgb, var(--bulma-scheme-invert, #000) 45%, transparent);
435
+ }
436
+ .modal.yui-volatil .modal-card {
437
+ position: relative;
438
+ width: min(26rem, calc(100vw - 2rem));
439
+ border-radius: 0.75rem;
440
+ background: var(--bulma-scheme-main, #fff);
441
+ box-shadow: 0 12px 40px color-mix(in srgb, var(--bulma-scheme-invert, #000) 30%, transparent);
442
+ padding: 1.5rem 1.5rem 1.25rem;
443
+ }
444
+ .modal.yui-volatil .modal-card-body {
445
+ background: transparent;
446
+ padding: 0;
447
+ }
448
+ .modal.yui-volatil .modal-card-foot {
449
+ background: transparent;
450
+ border-top: none;
451
+ padding: 1rem 0 0;
452
+ }
453
+ .yui-volatil-icon {
454
+ width: 3.25rem;
455
+ height: 3.25rem;
456
+ border-radius: 9999px;
457
+ display: flex;
458
+ align-items: center;
459
+ justify-content: center;
460
+ margin: 0 auto 0.75rem;
461
+ font-size: 1.5rem;
462
+ background: color-mix(in srgb, var(--yui-volatil-base, var(--bulma-info, #3e8ed0)) 15%, transparent);
463
+ color: var(--yui-volatil-ink, var(--bulma-info, #3e8ed0));
464
+ }
465
+ .modal.yui-volatil.is-info,
466
+ .modal.yui-volatil.is-question {
467
+ --yui-volatil-base: var(--bulma-info, #3e8ed0);
468
+ --yui-volatil-ink: var(--bulma-info-on-scheme, #3e8ed0);
469
+ }
470
+ .modal.yui-volatil.is-success {
471
+ --yui-volatil-base: var(--bulma-success, #48c78e);
472
+ --yui-volatil-ink: var(--bulma-success-on-scheme, #257953);
473
+ }
474
+ .modal.yui-volatil.is-warning {
475
+ --yui-volatil-base: var(--bulma-warning, #ffe08a);
476
+ --yui-volatil-ink: var(--bulma-warning-on-scheme, #946c00);
477
+ }
478
+ .modal.yui-volatil.is-error {
479
+ --yui-volatil-base: var(--bulma-danger, #f14668);
480
+ --yui-volatil-ink: var(--bulma-danger-on-scheme, #cc0f35);
481
+ }
482
+ .yui-volatil-title {
483
+ font-size: 1.15rem;
484
+ font-weight: 600;
485
+ text-transform: capitalize;
486
+ margin-bottom: 0.25rem;
487
+ color: var(--bulma-text-strong, inherit);
488
+ }
489
+ .yui-volatil-x {
490
+ position: absolute;
491
+ top: 0.75rem;
492
+ right: 0.75rem;
493
+ }
494
+ .yui-volatil-msg {
495
+ font-size: 1.05rem;
496
+ color: var(--bulma-text, inherit);
497
+ }
498
+ .modal.yui-volatil .modal-card-body .buttons {
499
+ margin-top: 1.25rem;
500
+ margin-bottom: 0;
501
+ }
package/src/c_yui_main.js CHANGED
@@ -27,7 +27,7 @@
27
27
  * The tabs in a content-layer to manage sub-containers can be managed by yui_tabs.js
28
28
  * or be integrated in each gobj.
29
29
  *
30
- * Copyright (c) 2025, ArtGins.
30
+ * Copyright (c) 2025-2026, ArtGins.
31
31
  * All Rights Reserved.
32
32
  ***********************************************************************/
33
33
  /* global ResizeObserver, window, document */
@@ -879,50 +879,77 @@ function display_volatil_modal(
879
879
  {title, msg, callback, type, x_close, buttons}
880
880
  ) {
881
881
  const callback_ = callback; // ✅ Assign to a constant
882
+
883
+ /* Normalize the type: it picks the tinted icon and the accent
884
+ * color (see the .yui-volatil rules in c_yui_main.css). */
885
+ let type_ = (type || "info").toLowerCase();
886
+ if(type_ === "danger") {
887
+ type_ = "error";
888
+ }
889
+ const TYPE_ICONS = {
890
+ "info": "yi-circle-info",
891
+ "question": "yi-question",
892
+ "success": "yi-square-check",
893
+ "warning": "yi-triangle-exclamation",
894
+ "error": "yi-circle-exclamation",
895
+ };
896
+ let icon = TYPE_ICONS[type_] || TYPE_ICONS["info"];
897
+
882
898
  let $element = createElement2([
883
- 'div', {class: 'modal'}, [
899
+ 'div', {class: `modal yui-volatil is-${type_}`}, [
884
900
  ['div', {class: 'modal-background'}, ''],
885
901
  ['div', {class: 'modal-card'}, []]
886
902
  ]
887
903
  ]);
888
904
 
889
905
  const destroyModal = ()=> {
906
+ document.removeEventListener('keydown', on_keydown, true);
890
907
  $element.classList.remove('is-active');
891
908
  $element.parentNode.removeChild($element);
892
909
  };
893
910
 
894
- let $modal_card = $element.querySelector('.modal-card');
895
- if(title || x_close) {
896
- if(!type) {
897
- type = "";
911
+ /* Escape = cancel. Capture phase on document, so Escape handlers
912
+ * beneath the modal (e.g. the treedb edit dialog's own) don't also
913
+ * fire and stack a second confirm. Only the top-most open modal
914
+ * reacts; the cancel/x click path carries the callback semantics,
915
+ * a buttonless modal just dismisses. */
916
+ const on_keydown = (evt)=> {
917
+ if(evt.key !== "Escape") {
918
+ return;
919
+ }
920
+ let $modals = document.querySelectorAll('.modal.is-active');
921
+ if($modals.length && $modals[$modals.length - 1] !== $element) {
922
+ return;
898
923
  }
899
- let background_color = "";
900
- switch(type.toLowerCase()) {
901
- case "info":
902
- background_color = 'has-background-info';
903
- break;
904
- case "success":
905
- background_color = 'has-background-success';
906
- break;
907
- case "warning":
908
- background_color = 'has-background-warning';
909
- break;
910
- case "error":
911
- case "danger":
912
- background_color = 'has-background-danger';
913
- break;
924
+ evt.stopPropagation();
925
+ evt.preventDefault();
926
+ let $cancel = $element.querySelector('.cancelButton, .delete');
927
+ if($cancel) {
928
+ $cancel.click();
929
+ } else {
930
+ destroyModal();
914
931
  }
932
+ };
933
+ document.addEventListener('keydown', on_keydown, true);
915
934
 
916
- let title_ = !title ? [] : ['p', {class: 'modal-card-title has-text-centered'}, title];
917
- let x_close_ = !x_close ? [] : ['button', {class: 'delete is-large', 'aria-label': 'close'}];
935
+ let $modal_card = $element.querySelector('.modal-card');
918
936
 
919
- let $header = createElement2(
920
- ['header', {class: `modal-card-head ${background_color} p-3`}, [
921
- title_,
922
- x_close_
923
- ]]
924
- );
925
- $modal_card.appendChild($header);
937
+ /* Icon-centric layout (no colored header bar): a tinted round
938
+ * icon of the type, an optional title, an optional top-right x. */
939
+ $modal_card.appendChild(createElement2(
940
+ ['div', {class: 'yui-volatil-icon'}, [
941
+ ['i', {class: icon, 'aria-hidden': 'true'}]
942
+ ]]
943
+ ));
944
+ if(title) {
945
+ $modal_card.appendChild(createElement2(
946
+ ['p', {class: 'yui-volatil-title has-text-centered'}, title]
947
+ ));
948
+ }
949
+ if(x_close) {
950
+ $modal_card.appendChild(createElement2(
951
+ ['button', {class: 'delete yui-volatil-x', 'aria-label': 'close'}]
952
+ ));
926
953
  }
927
954
 
928
955
  let $section = createElement2(
@@ -1017,7 +1044,7 @@ function display_info_message(title, msg, callback)
1017
1044
  type: 'info',
1018
1045
  buttons: [{
1019
1046
  content:
1020
- `<button class="button is-success px-6 with-focus">${t('accept')}</button>`,
1047
+ `<button class="button is-info px-6 with-focus">${t('accept')}</button>`,
1021
1048
  }]
1022
1049
  });
1023
1050
  }
@@ -1037,7 +1064,7 @@ function display_warning_message(title, msg, callback)
1037
1064
  type: 'warning',
1038
1065
  buttons: [{
1039
1066
  content:
1040
- `<button class="button is-success px-6 with-focus">${t('accept')}</button>`,
1067
+ `<button class="button is-warning px-6 with-focus">${t('accept')}</button>`,
1041
1068
  }]
1042
1069
  });
1043
1070
  }
@@ -1057,7 +1084,7 @@ function display_error_message(title, msg, callback, without_buttons)
1057
1084
  type: 'error',
1058
1085
  buttons: without_buttons? []: [{
1059
1086
  content:
1060
- `<button class="button is-success px-6 with-focus">${t('accept')}</button>`,
1087
+ `<button class="button is-danger px-6 with-focus">${t('accept')}</button>`,
1061
1088
  }]
1062
1089
  });
1063
1090
  }
@@ -1069,7 +1096,7 @@ function get_yesnocancel(msg, callback)
1069
1096
  {
1070
1097
  let content =
1071
1098
  `<div class="">
1072
- <p class="title">${t(msg)}</p>
1099
+ <p class="yui-volatil-msg">${t(msg)}</p>
1073
1100
  <div class="buttons is-centered">
1074
1101
  <button class="button to-close is-success px-6 with-focus yesButton">${t('yes')}</button>
1075
1102
  <button class="button to-close is-danger px-6 noButton">${t('no')}</button>
@@ -1091,7 +1118,7 @@ function get_yesnocancel(msg, callback)
1091
1118
  title: null,
1092
1119
  msg: content,
1093
1120
  callback: callback_,
1094
- type: 'info'
1121
+ type: 'question'
1095
1122
  });
1096
1123
  }
1097
1124
 
@@ -1102,7 +1129,7 @@ function get_yesno(msg, callback)
1102
1129
  {
1103
1130
  let content =
1104
1131
  `<div class="">
1105
- <p class="title">${msg}</p>
1132
+ <p class="yui-volatil-msg">${msg}</p>
1106
1133
  <div class="buttons is-centered">
1107
1134
  <button class="button to-close is-success px-6 with-focus yesButton">${t('yes')}</button>
1108
1135
  <button class="button to-close is-danger px-6 noButton">${t('no')}</button>
@@ -1123,7 +1150,7 @@ function get_yesno(msg, callback)
1123
1150
  title: null,
1124
1151
  msg: content,
1125
1152
  callback: callback_,
1126
- type: 'info'
1153
+ type: 'question'
1127
1154
  });
1128
1155
  }
1129
1156
 
@@ -1134,7 +1161,7 @@ function get_ok(msg, callback)
1134
1161
  {
1135
1162
  let content =
1136
1163
  `<div class="">
1137
- <p class="title">${msg}</p>
1164
+ <p class="yui-volatil-msg">${msg}</p>
1138
1165
  <div class="buttons is-centered">
1139
1166
  <button class="button to-close is-success px-6 with-focus yesButton">${t('accept')}</button>
1140
1167
  </div>
@@ -1154,7 +1181,7 @@ function get_ok(msg, callback)
1154
1181
  title: null,
1155
1182
  msg: content,
1156
1183
  callback: callback_,
1157
- type: 'info'
1184
+ type: 'success'
1158
1185
  });
1159
1186
  }
1160
1187
 
@@ -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);
package/src/yui_icons.css CHANGED
@@ -239,3 +239,18 @@
239
239
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.7 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z'/%3E%3C/svg%3E");
240
240
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.7 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z'/%3E%3C/svg%3E");
241
241
  }
242
+
243
+ .yi-circle-info::before {
244
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z'/%3E%3C/svg%3E");
245
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z'/%3E%3C/svg%3E");
246
+ }
247
+
248
+ .yi-triangle-exclamation::before {
249
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z'/%3E%3C/svg%3E");
250
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z'/%3E%3C/svg%3E");
251
+ }
252
+
253
+ .yi-circle-exclamation::before {
254
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zm32 224a32 32 0 1 1 -64 0 32 32 0 1 1 64 0z'/%3E%3C/svg%3E");
255
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zm32 224a32 32 0 1 1 -64 0 32 32 0 1 1 64 0z'/%3E%3C/svg%3E");
256
+ }