@yuneta/gobj-ui 2.4.0 → 2.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -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
 
@@ -34,6 +34,8 @@ import {
34
34
  gclass_create, log_error, log_warning,
35
35
  gobj_create, gobj_destroy,
36
36
  gobj_start, gobj_stop,
37
+ gobj_parent,
38
+ gobj_gclass_name,
37
39
  gobj_publish_event,
38
40
  gobj_send_event,
39
41
  gobj_subscribe_event,
@@ -68,6 +70,12 @@ import {
68
70
  ***************************************************************/
69
71
  const GCLASS_NAME = "C_YUI_SHELL";
70
72
 
73
+ /* Last shell created on the page — fallback for yui_shell_of()
74
+ * when the caller is not a shell descendant (e.g. a floating
75
+ * C_YUI_WINDOW parented to the app gobj). Real apps have exactly
76
+ * one shell; tests that create several get the most recent. */
77
+ let __last_shell__ = null;
78
+
71
79
  /* Zones rendered inside the base layer. */
72
80
  const ZONE_IDS = ["top", "top-sub", "left", "center", "right", "bottom-sub", "bottom"];
73
81
 
@@ -166,6 +174,8 @@ function mt_create(gobj)
166
174
  active_dropdown: null
167
175
  });
168
176
 
177
+ __last_shell__ = gobj;
178
+
169
179
  build_ui(gobj);
170
180
  }
171
181
 
@@ -318,6 +328,10 @@ function mt_destroy(gobj)
318
328
  }
319
329
  gobj_write_attr(gobj, "$container", null);
320
330
  gobj_write_attr(gobj, "priv", null);
331
+
332
+ if(__last_shell__ === gobj) {
333
+ __last_shell__ = null;
334
+ }
321
335
  }
322
336
 
323
337
 
@@ -2408,6 +2422,25 @@ function register_c_yui_shell()
2408
2422
  * bottom of the file to keep the skeleton layout intact.
2409
2423
  ***************************************************************/
2410
2424
 
2425
+ /************************************************************
2426
+ * Resolve the shell that governs `gobj`: the nearest
2427
+ * C_YUI_SHELL ancestor, else the last shell created on the
2428
+ * page (apps have exactly one). Null when no shell exists —
2429
+ * callers must degrade loudly (shell_modals logs a warning
2430
+ * and resolves the safe default).
2431
+ ************************************************************/
2432
+ function yui_shell_of(gobj)
2433
+ {
2434
+ let g = gobj;
2435
+ while(g) {
2436
+ if(gobj_gclass_name(g) === GCLASS_NAME) {
2437
+ return g;
2438
+ }
2439
+ g = gobj_parent(g);
2440
+ }
2441
+ return __last_shell__;
2442
+ }
2443
+
2411
2444
  /************************************************************
2412
2445
  * Programmatic navigation (bypass hash).
2413
2446
  ************************************************************/
@@ -2552,6 +2585,7 @@ function yui_shell_close_dropdown(shell_gobj)
2552
2585
 
2553
2586
  export {
2554
2587
  register_c_yui_shell,
2588
+ yui_shell_of,
2555
2589
  yui_shell_navigate,
2556
2590
  yui_shell_open_drawer,
2557
2591
  yui_shell_close_drawer,
@@ -80,7 +80,8 @@ import {
80
80
  set_submit_state,
81
81
  set_active_state,
82
82
  } from "./lib_graph.js";
83
- import {display_error_message} from "./c_yui_main.js";
83
+ import {yui_shell_show_error} from "./shell_modals.js";
84
+ import {yui_shell_of} from "./c_yui_shell.js";
84
85
 
85
86
  import {t} from "i18next";
86
87
 
@@ -941,7 +942,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
941
942
  * app-modal that wedges the whole SPA behind an empty tab. */
942
943
  show_load_error(gobj, t(comment));
943
944
  } else {
944
- display_error_message("Error", t(comment));
945
+ yui_shell_show_error(yui_shell_of(gobj), comment, {t: t});
945
946
  }
946
947
  // HACK don't return for non-descs, pass errors when need it.
947
948
  }
@@ -57,9 +57,16 @@ import {
57
57
  } from "@yuneta/gobj-js";
58
58
 
59
59
  import {
60
- get_yesnocancel,
61
- get_ok,
62
- } from "./c_yui_main.js";
60
+ yui_shell_confirm_yesnocancel,
61
+ yui_shell_confirm_ok,
62
+ yui_shell_popup_layer,
63
+ } from "./shell_modals.js";
64
+
65
+ import {
66
+ yui_shell_of,
67
+ yui_shell_push_escape,
68
+ yui_shell_pop_escape,
69
+ } from "./c_yui_shell.js";
63
70
 
64
71
  import {register_c_yui_form} from "./c_yui_form.js";
65
72
 
@@ -132,17 +139,26 @@ let PRIVATE_DATA = {
132
139
  tabulator: null,
133
140
  form: null, // hosted C_YUI_FORM child (while dialog open)
134
141
  $popup: null, // dialog DOM element
135
- on_keydown: null, // document Escape handler (while dialog open)
142
+ on_keydown: null, // document Escape handler (no-shell fallback)
143
+ escape_shell: null, // shell whose Escape chain holds escape_handler
144
+ escape_handler: null, // close handler pushed on the shell chain
136
145
  };
137
146
 
138
147
  let __gclass__ = null;
139
148
 
140
- /* Where to mount the edit/delete modal. The legacy C_YUI_MAIN
141
- * shell provided a "#popup-layer" element; the new C_YUI_SHELL does
142
- * not. Bulma `.modal` is position:fixed, so document.body is a
143
- * correct universal fallback (works under any host). */
144
- function popup_mount_layer()
149
+ /* Where to mount the edit/delete modal. Under a C_YUI_SHELL use
150
+ * its popup layer (z 20): the shell confirms live on the modal
151
+ * layer (z 99), so they always paint above this dialog — a
152
+ * body-mounted Bulma `.modal` would cover them (the shell is a
153
+ * stacking context below body-level modals). The legacy
154
+ * C_YUI_MAIN "#popup-layer" and document.body (Bulma `.modal` is
155
+ * position:fixed) remain the shell-less fallbacks. */
156
+ function popup_mount_layer(gobj)
145
157
  {
158
+ let $layer = yui_shell_popup_layer(yui_shell_of(gobj));
159
+ if($layer) {
160
+ return $layer;
161
+ }
146
162
  return document.getElementById("popup-layer") || document.body;
147
163
  }
148
164
 
@@ -200,6 +216,11 @@ function mt_stop(gobj)
200
216
  ***************************************************************/
201
217
  function mt_destroy(gobj)
202
218
  {
219
+ /* A destroy with the edit dialog open (e.g. the hosted view is
220
+ * rebuilt on a transport rebind) must tear the dialog down too:
221
+ * it unhooks the Escape handler (shell chain or document
222
+ * listener) and removes the dialog DOM. */
223
+ close_form_dialog(gobj);
203
224
  destroy_ui(gobj);
204
225
  }
205
226
 
@@ -1181,17 +1202,30 @@ function open_form_dialog(gobj, mode, record)
1181
1202
  $form_container.style.minHeight = "0";
1182
1203
  }
1183
1204
 
1184
- popup_mount_layer().appendChild($element);
1205
+ popup_mount_layer(gobj).appendChild($element);
1185
1206
  refresh_language($element, t);
1186
1207
 
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();
1208
+ /* Escape closes through the same unsaved-changes guard as the X.
1209
+ * Under a shell, ride its Escape priority chain — LIFO with the
1210
+ * shell confirm dialogs, so Escape on an open confirm never
1211
+ * reaches this dialog and stacks a second one. Without a shell
1212
+ * (bare unit tests) fall back to a document listener. */
1213
+ let shell = yui_shell_of(gobj);
1214
+ if(shell) {
1215
+ priv.escape_shell = shell;
1216
+ priv.escape_handler = function() {
1191
1217
  request_close_form_dialog(gobj);
1192
- }
1193
- };
1194
- document.addEventListener("keydown", priv.on_keydown);
1218
+ };
1219
+ yui_shell_push_escape(shell, "popup", priv.escape_handler);
1220
+ } else {
1221
+ priv.on_keydown = function(evt) {
1222
+ if(evt.key === "Escape") {
1223
+ evt.stopPropagation();
1224
+ request_close_form_dialog(gobj);
1225
+ }
1226
+ };
1227
+ document.addEventListener("keydown", priv.on_keydown);
1228
+ }
1195
1229
 
1196
1230
  gobj_start(form); // mt_start loads `record` into the form
1197
1231
 
@@ -1219,8 +1253,12 @@ function request_close_form_dialog(gobj)
1219
1253
  let kw = {};
1220
1254
  gobj_send_event(priv.form, "EV_WINDOW_TO_CLOSE", kw, gobj);
1221
1255
  if(kw.abort_close) {
1222
- get_yesnocancel(t('All changes will be lost. Are you sure?'), function(evt) {
1223
- if(evt === "yes") {
1256
+ yui_shell_confirm_yesnocancel(
1257
+ yui_shell_of(gobj),
1258
+ 'All changes will be lost. Are you sure?',
1259
+ {t: t, yes_label: "yes", no_label: "no", cancel_label: "cancel"}
1260
+ ).then(function(answer) {
1261
+ if(answer === "yes") {
1224
1262
  close_form_dialog(gobj);
1225
1263
  }
1226
1264
  });
@@ -1236,6 +1274,11 @@ function request_close_form_dialog(gobj)
1236
1274
  function close_form_dialog(gobj)
1237
1275
  {
1238
1276
  let priv = gobj.priv;
1277
+ if(priv.escape_handler) {
1278
+ yui_shell_pop_escape(priv.escape_shell, priv.escape_handler);
1279
+ priv.escape_shell = null;
1280
+ priv.escape_handler = null;
1281
+ }
1239
1282
  if(priv.on_keydown) {
1240
1283
  document.removeEventListener("keydown", priv.on_keydown);
1241
1284
  priv.on_keydown = null;
@@ -1640,7 +1683,7 @@ function show_dropdown_popup_menu(gobj, x, y, items, callback)
1640
1683
  /*
1641
1684
  * Add to popup layer
1642
1685
  */
1643
- popup_mount_layer().appendChild($element);
1686
+ popup_mount_layer(gobj).appendChild($element);
1644
1687
 
1645
1688
  /*
1646
1689
  * Set position
@@ -1929,12 +1972,18 @@ function ac_delete_rows(gobj, event, kw, src)
1929
1972
  *----------------------------*/
1930
1973
  let rows = tabulator.getSelectedData();
1931
1974
  if (!rows.length) {
1932
- get_ok(t('please select some row'));
1975
+ yui_shell_confirm_ok(
1976
+ yui_shell_of(gobj), 'please select some row',
1977
+ {t: t, ok_label: "accept"}
1978
+ );
1933
1979
  return 0;
1934
1980
  }
1935
1981
 
1936
- get_yesnocancel(t('are you sure'), function(evt) {
1937
- if(evt === "yes") {
1982
+ yui_shell_confirm_yesnocancel(
1983
+ yui_shell_of(gobj), 'are you sure',
1984
+ {t: t, yes_label: "yes", no_label: "no", cancel_label: "cancel"}
1985
+ ).then(function(answer) {
1986
+ if(answer === "yes") {
1938
1987
  for(let row of rows) {
1939
1988
  // TODO why don't send once EV_DELETE_RECORD(S)
1940
1989
  gobj_publish_event(
@@ -1954,8 +2003,11 @@ function ac_delete_rows(gobj, event, kw, src)
1954
2003
  * Delete one row
1955
2004
  * {index: , row: }
1956
2005
  *----------------------------*/
1957
- get_yesnocancel(t('are you sure'), function(evt) {
1958
- if(evt === "yes") {
2006
+ yui_shell_confirm_yesnocancel(
2007
+ yui_shell_of(gobj), 'are you sure',
2008
+ {t: t, yes_label: "yes", no_label: "no", cancel_label: "cancel"}
2009
+ ).then(function(answer) {
2010
+ if(answer === "yes") {
1959
2011
  gobj_publish_event(
1960
2012
  gobj,
1961
2013
  "EV_DELETE_RECORD",
@@ -1983,7 +2035,10 @@ function ac_copy_rows(gobj, event, kw, src)
1983
2035
  *----------------------------*/
1984
2036
  let rows = tabulator.getSelectedData();
1985
2037
  if (!rows.length) {
1986
- get_ok(t('please select some row'));
2038
+ yui_shell_confirm_ok(
2039
+ yui_shell_of(gobj), 'please select some row',
2040
+ {t: t, ok_label: "accept"}
2041
+ );
1987
2042
  return 0;
1988
2043
  }
1989
2044
 
@@ -41,9 +41,8 @@ import {
41
41
  refresh_language,
42
42
  } from "@yuneta/gobj-js";
43
43
 
44
- import {
45
- display_error_message
46
- } from "./c_yui_main.js";
44
+ import {yui_shell_show_error} from "./shell_modals.js";
45
+ import {yui_shell_of} from "./c_yui_shell.js";
47
46
 
48
47
  import {t} from "i18next";
49
48
 
@@ -819,7 +818,7 @@ function ac_mt_command_answer(gobj, event, kw, src)
819
818
  * app-modal that wedges the whole SPA behind an empty tab. */
820
819
  show_load_error(gobj, t(comment));
821
820
  } else {
822
- display_error_message("Error", t(comment));
821
+ yui_shell_show_error(yui_shell_of(gobj), comment, {t: t});
823
822
  }
824
823
  return 0;
825
824
  }
@@ -44,7 +44,8 @@ import {
44
44
 
45
45
  import {t} from "i18next";
46
46
 
47
- import {get_yesnocancel} from "./c_yui_main.js";
47
+ import {yui_shell_confirm_yesnocancel} from "./shell_modals.js";
48
+ import {yui_shell_of} from "./c_yui_shell.js";
48
49
 
49
50
  /***************************************************************
50
51
  * Constants
@@ -672,8 +673,11 @@ function close_window(gobj)
672
673
  gobj_stop_children(gobj);
673
674
  gobj_destroy(gobj);
674
675
  } else if(kw_close.warning) {
675
- get_yesnocancel(kw_close.warning, function(resp) {
676
- if(resp === "yes") {
676
+ yui_shell_confirm_yesnocancel(
677
+ yui_shell_of(gobj), kw_close.warning,
678
+ {t: t, yes_label: "yes", no_label: "no", cancel_label: "cancel"}
679
+ ).then(function(answer) {
680
+ if(answer === "yes") {
677
681
  if(on_close) {
678
682
  on_close();
679
683
  }
@@ -49,16 +49,36 @@ import {
49
49
  ***************************************************************/
50
50
  function notification_layer(shell)
51
51
  {
52
+ if(!shell) {
53
+ return null;
54
+ }
52
55
  let priv = gobj_read_attr(shell, "priv");
53
56
  return priv && priv.layers && priv.layers.notification;
54
57
  }
55
58
 
56
59
  function modal_layer(shell)
57
60
  {
61
+ if(!shell) {
62
+ return null;
63
+ }
58
64
  let priv = gobj_read_attr(shell, "priv");
59
65
  return priv && priv.layers && priv.layers.modal;
60
66
  }
61
67
 
68
+ /* Public: where a component should mount its own popup/dialog DOM
69
+ * (e.g. the treedb edit dialog). The popup layer (z 20) sits below
70
+ * the modal layer (z 99), so shell confirms always paint above the
71
+ * component's dialog — matching the Escape chain LIFO order. Null
72
+ * when there is no shell: the caller picks its legacy fallback. */
73
+ export function yui_shell_popup_layer(shell)
74
+ {
75
+ if(!shell) {
76
+ return null;
77
+ }
78
+ let priv = gobj_read_attr(shell, "priv");
79
+ return (priv && priv.layers && priv.layers.popup) || null;
80
+ }
81
+
62
82
 
63
83
  /***************************************************************
64
84
  * i18n bridge:
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
+ }