@yuneta/gobj-ui 2.5.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.5.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",
@@ -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: