@yuneta/gobj-ui 7.26.37 → 7.26.38
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/README.md +32 -0
- package/dist/gobj-ui.cjs.js +15 -10
- package/dist/gobj-ui.es.js +15 -10
- package/package.json +1 -1
- package/src/c_yui_treedb_topic_with_form.js +10 -7
- package/src/modal_guard_back.wiring.test.js +193 -0
- package/src/shell_modals.js +22 -3
package/README.md
CHANGED
|
@@ -3325,6 +3325,38 @@ keeps working afterwards. With no guard a modal closes exactly as it always
|
|
|
3325
3325
|
did, and the returned `close()` always closes **unconditionally** — the veto is
|
|
3326
3326
|
for the user's dismiss, not for the code's.
|
|
3327
3327
|
|
|
3328
|
+
**A guard that ASKS returns the question's promise** (7.26.38+). It is a veto
|
|
3329
|
+
too, and it tells the modal when the question is over: a browser Back that
|
|
3330
|
+
asked gets its history entry back THEN, below nothing. Opening the question and
|
|
3331
|
+
returning `false` put the entry back at once, above the question's own -- so the
|
|
3332
|
+
next Back, the one meant for the question, reached the modal, which asked
|
|
3333
|
+
again, and on a phone (where Back is a gesture) every Back stacked one more
|
|
3334
|
+
question.
|
|
3335
|
+
|
|
3336
|
+
```js
|
|
3337
|
+
const sheet = yui_shell_show_modal(shell, $body, {
|
|
3338
|
+
dialog: true,
|
|
3339
|
+
title: "device settings",
|
|
3340
|
+
t: t,
|
|
3341
|
+
before_close: () => {
|
|
3342
|
+
const kw = {};
|
|
3343
|
+
gobj_send_event(form, "EV_WINDOW_TO_CLOSE", kw, gobj);
|
|
3344
|
+
if(!kw.abort_close) {
|
|
3345
|
+
return true;
|
|
3346
|
+
}
|
|
3347
|
+
return yui_shell_confirm_yesnocancel(shell, "all changes will be lost", {t})
|
|
3348
|
+
.then((answer) => {
|
|
3349
|
+
if(answer === "yes") {
|
|
3350
|
+
gobj_send_event(gobj, "EV_CLOSE_SHEET", {}, gobj); // its action: sheet.close()
|
|
3351
|
+
}
|
|
3352
|
+
});
|
|
3353
|
+
},
|
|
3354
|
+
on_close: () => {
|
|
3355
|
+
gobj_send_event(gobj, "EV_SHEET_CLOSED", {}, gobj); // its action: destroy the form
|
|
3356
|
+
}
|
|
3357
|
+
});
|
|
3358
|
+
```
|
|
3359
|
+
|
|
3328
3360
|
### Confirmations — and the red one, `yui_shell_confirm_danger`
|
|
3329
3361
|
|
|
3330
3362
|
`yui_shell_confirm_yesno(shell, message, opts)` asks a question and resolves to
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -3970,6 +3970,10 @@ function yui_shell_show_modal(shell, content, opts) {
|
|
|
3970
3970
|
(0, _yuneta_gobj_js.log_warning)(`yui_shell_show_modal: before_close threw: ${e}`);
|
|
3971
3971
|
allow = true;
|
|
3972
3972
|
}
|
|
3973
|
+
if (allow && typeof allow.then === "function") {
|
|
3974
|
+
if (typeof on_veto === "function") allow.then(on_veto, on_veto);
|
|
3975
|
+
return;
|
|
3976
|
+
}
|
|
3973
3977
|
if (allow === false) {
|
|
3974
3978
|
if (typeof on_veto === "function") on_veto();
|
|
3975
3979
|
return;
|
|
@@ -3981,7 +3985,7 @@ function yui_shell_show_modal(shell, content, opts) {
|
|
|
3981
3985
|
};
|
|
3982
3986
|
let on_back = function() {
|
|
3983
3987
|
guarded_close(function() {
|
|
3984
|
-
overlay = yui_shell_register_overlay(shell, on_back);
|
|
3988
|
+
if (!closed) overlay = yui_shell_register_overlay(shell, on_back);
|
|
3985
3989
|
});
|
|
3986
3990
|
};
|
|
3987
3991
|
escape_handler = request_close;
|
|
@@ -31549,19 +31553,19 @@ function open_form_dialog(gobj, mode, record) {
|
|
|
31549
31553
|
}
|
|
31550
31554
|
/************************************************************
|
|
31551
31555
|
* Unsaved-changes guard for the dialog's `before_close` hook.
|
|
31552
|
-
* Returns true to allow the dismiss;
|
|
31553
|
-
*
|
|
31554
|
-
*
|
|
31556
|
+
* Returns true to allow the dismiss; otherwise the form has
|
|
31557
|
+
* pending edits, and it asks: the confirm's promise is a VETO
|
|
31558
|
+
* that tells the dialog when the question is over (a Back that
|
|
31559
|
+
* asked gets its history entry back then, below nothing). On
|
|
31560
|
+
* "yes" the confirm closes the modal itself (EV_WINDOW_TO_CLOSE
|
|
31561
|
+
* contract).
|
|
31555
31562
|
************************************************************/
|
|
31556
31563
|
function form_may_close(gobj) {
|
|
31557
31564
|
let priv = gobj.priv;
|
|
31558
31565
|
if (priv.form) {
|
|
31559
31566
|
let kw = {};
|
|
31560
31567
|
(0, _yuneta_gobj_js.gobj_send_event)(priv.form, "EV_WINDOW_TO_CLOSE", kw, gobj);
|
|
31561
|
-
if (kw.abort_close) {
|
|
31562
|
-
confirm_then$1(gobj, kw.warning || "all changes will be lost", { what: "close_form" });
|
|
31563
|
-
return false;
|
|
31564
|
-
}
|
|
31568
|
+
if (kw.abort_close) return confirm_then$1(gobj, kw.warning || "all changes will be lost", { what: "close_form" });
|
|
31565
31569
|
}
|
|
31566
31570
|
return true;
|
|
31567
31571
|
}
|
|
@@ -31595,10 +31599,11 @@ function teardown_form_child(gobj) {
|
|
|
31595
31599
|
* it, and a gobj, a Tabulator row or a DOM node in there is a
|
|
31596
31600
|
* circular structure that breaks the very trace the FSM feeds.
|
|
31597
31601
|
*
|
|
31598
|
-
* Same shape as C_YUI_SCHEMA_EDITOR's confirm_then().
|
|
31602
|
+
* Same shape as C_YUI_SCHEMA_EDITOR's confirm_then(). Answers
|
|
31603
|
+
* the promise of the question, settled once it is over.
|
|
31599
31604
|
************************************************************/
|
|
31600
31605
|
function confirm_then$1(gobj, message, kw) {
|
|
31601
|
-
yui_shell_confirm_yesnocancel(yui_shell_of(gobj), message, {
|
|
31606
|
+
return yui_shell_confirm_yesnocancel(yui_shell_of(gobj), message, {
|
|
31602
31607
|
t: i18next.t,
|
|
31603
31608
|
yes_label: "yes",
|
|
31604
31609
|
no_label: "no",
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -3965,6 +3965,10 @@ function yui_shell_show_modal(shell, content, opts) {
|
|
|
3965
3965
|
log_warning(`yui_shell_show_modal: before_close threw: ${e}`);
|
|
3966
3966
|
allow = true;
|
|
3967
3967
|
}
|
|
3968
|
+
if (allow && typeof allow.then === "function") {
|
|
3969
|
+
if (typeof on_veto === "function") allow.then(on_veto, on_veto);
|
|
3970
|
+
return;
|
|
3971
|
+
}
|
|
3968
3972
|
if (allow === false) {
|
|
3969
3973
|
if (typeof on_veto === "function") on_veto();
|
|
3970
3974
|
return;
|
|
@@ -3976,7 +3980,7 @@ function yui_shell_show_modal(shell, content, opts) {
|
|
|
3976
3980
|
};
|
|
3977
3981
|
let on_back = function() {
|
|
3978
3982
|
guarded_close(function() {
|
|
3979
|
-
overlay = yui_shell_register_overlay(shell, on_back);
|
|
3983
|
+
if (!closed) overlay = yui_shell_register_overlay(shell, on_back);
|
|
3980
3984
|
});
|
|
3981
3985
|
};
|
|
3982
3986
|
escape_handler = request_close;
|
|
@@ -31544,19 +31548,19 @@ function open_form_dialog(gobj, mode, record) {
|
|
|
31544
31548
|
}
|
|
31545
31549
|
/************************************************************
|
|
31546
31550
|
* Unsaved-changes guard for the dialog's `before_close` hook.
|
|
31547
|
-
* Returns true to allow the dismiss;
|
|
31548
|
-
*
|
|
31549
|
-
*
|
|
31551
|
+
* Returns true to allow the dismiss; otherwise the form has
|
|
31552
|
+
* pending edits, and it asks: the confirm's promise is a VETO
|
|
31553
|
+
* that tells the dialog when the question is over (a Back that
|
|
31554
|
+
* asked gets its history entry back then, below nothing). On
|
|
31555
|
+
* "yes" the confirm closes the modal itself (EV_WINDOW_TO_CLOSE
|
|
31556
|
+
* contract).
|
|
31550
31557
|
************************************************************/
|
|
31551
31558
|
function form_may_close(gobj) {
|
|
31552
31559
|
let priv = gobj.priv;
|
|
31553
31560
|
if (priv.form) {
|
|
31554
31561
|
let kw = {};
|
|
31555
31562
|
gobj_send_event(priv.form, "EV_WINDOW_TO_CLOSE", kw, gobj);
|
|
31556
|
-
if (kw.abort_close) {
|
|
31557
|
-
confirm_then$1(gobj, kw.warning || "all changes will be lost", { what: "close_form" });
|
|
31558
|
-
return false;
|
|
31559
|
-
}
|
|
31563
|
+
if (kw.abort_close) return confirm_then$1(gobj, kw.warning || "all changes will be lost", { what: "close_form" });
|
|
31560
31564
|
}
|
|
31561
31565
|
return true;
|
|
31562
31566
|
}
|
|
@@ -31590,10 +31594,11 @@ function teardown_form_child(gobj) {
|
|
|
31590
31594
|
* it, and a gobj, a Tabulator row or a DOM node in there is a
|
|
31591
31595
|
* circular structure that breaks the very trace the FSM feeds.
|
|
31592
31596
|
*
|
|
31593
|
-
* Same shape as C_YUI_SCHEMA_EDITOR's confirm_then().
|
|
31597
|
+
* Same shape as C_YUI_SCHEMA_EDITOR's confirm_then(). Answers
|
|
31598
|
+
* the promise of the question, settled once it is over.
|
|
31594
31599
|
************************************************************/
|
|
31595
31600
|
function confirm_then$1(gobj, message, kw) {
|
|
31596
|
-
yui_shell_confirm_yesnocancel(yui_shell_of(gobj), message, {
|
|
31601
|
+
return yui_shell_confirm_yesnocancel(yui_shell_of(gobj), message, {
|
|
31597
31602
|
t,
|
|
31598
31603
|
yes_label: "yes",
|
|
31599
31604
|
no_label: "no",
|
package/package.json
CHANGED
|
@@ -2554,9 +2554,12 @@ function open_form_dialog(gobj, mode, record)
|
|
|
2554
2554
|
|
|
2555
2555
|
/************************************************************
|
|
2556
2556
|
* Unsaved-changes guard for the dialog's `before_close` hook.
|
|
2557
|
-
* Returns true to allow the dismiss;
|
|
2558
|
-
*
|
|
2559
|
-
*
|
|
2557
|
+
* Returns true to allow the dismiss; otherwise the form has
|
|
2558
|
+
* pending edits, and it asks: the confirm's promise is a VETO
|
|
2559
|
+
* that tells the dialog when the question is over (a Back that
|
|
2560
|
+
* asked gets its history entry back then, below nothing). On
|
|
2561
|
+
* "yes" the confirm closes the modal itself (EV_WINDOW_TO_CLOSE
|
|
2562
|
+
* contract).
|
|
2560
2563
|
************************************************************/
|
|
2561
2564
|
function form_may_close(gobj)
|
|
2562
2565
|
{
|
|
@@ -2570,9 +2573,8 @@ function form_may_close(gobj)
|
|
|
2570
2573
|
* a second key to define — and the one that was here had never
|
|
2571
2574
|
* been defined in any locale, which renders as the English
|
|
2572
2575
|
* sentence itself, in every language, for ever. */
|
|
2573
|
-
confirm_then(gobj, /* i18n: C_YUI_FORM_I18N_KEYS */ kw.warning || "all changes will be lost",
|
|
2576
|
+
return confirm_then(gobj, /* i18n: C_YUI_FORM_I18N_KEYS */ kw.warning || "all changes will be lost",
|
|
2574
2577
|
{what: "close_form"});
|
|
2575
|
-
return false;
|
|
2576
2578
|
}
|
|
2577
2579
|
}
|
|
2578
2580
|
return true;
|
|
@@ -2612,11 +2614,12 @@ function teardown_form_child(gobj)
|
|
|
2612
2614
|
* it, and a gobj, a Tabulator row or a DOM node in there is a
|
|
2613
2615
|
* circular structure that breaks the very trace the FSM feeds.
|
|
2614
2616
|
*
|
|
2615
|
-
* Same shape as C_YUI_SCHEMA_EDITOR's confirm_then().
|
|
2617
|
+
* Same shape as C_YUI_SCHEMA_EDITOR's confirm_then(). Answers
|
|
2618
|
+
* the promise of the question, settled once it is over.
|
|
2616
2619
|
************************************************************/
|
|
2617
2620
|
function confirm_then(gobj, message, kw)
|
|
2618
2621
|
{
|
|
2619
|
-
yui_shell_confirm_yesnocancel(
|
|
2622
|
+
return yui_shell_confirm_yesnocancel(
|
|
2620
2623
|
yui_shell_of(gobj), message,
|
|
2621
2624
|
{t: t, yes_label: "yes", no_label: "no", cancel_label: "cancel"}
|
|
2622
2625
|
).then(function(answer) {
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* modal_guard_back.wiring.test.js
|
|
3
|
+
*
|
|
4
|
+
* A modal whose `before_close` guard ASKS (unsaved changes). The
|
|
5
|
+
* browser Back takes the modal's history entry before the guard
|
|
6
|
+
* runs; the guard opens a question, and the modal stays. When the
|
|
7
|
+
* modal took its entry back at once, it went ABOVE the question's
|
|
8
|
+
* own entry: the next Back -- the one meant for the question --
|
|
9
|
+
* reached the modal, which asked again, and every Back stacked
|
|
10
|
+
* one more question. A guard that asks answers the question's
|
|
11
|
+
* promise, and the modal takes its entry back once the question
|
|
12
|
+
* is over.
|
|
13
|
+
*
|
|
14
|
+
* Copyright (c) 2026, ArtGins.
|
|
15
|
+
* All Rights Reserved.
|
|
16
|
+
***********************************************************************/
|
|
17
|
+
import {describe, test, expect, beforeAll, beforeEach} from "vitest";
|
|
18
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
19
|
+
|
|
20
|
+
install_dom_double();
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
SDATA_END, gclass_create,
|
|
24
|
+
gobj_start_up, gobj_create_yuno, gobj_create, gobj_start,
|
|
25
|
+
set_log_callback,
|
|
26
|
+
} = await import("@yuneta/gobj-js");
|
|
27
|
+
const {register_c_yui_shell} = await import("./c_yui_shell.js");
|
|
28
|
+
const {
|
|
29
|
+
yui_shell_show_modal,
|
|
30
|
+
yui_shell_confirm_yesnocancel,
|
|
31
|
+
} = await import("./shell_modals.js");
|
|
32
|
+
|
|
33
|
+
const logged = [];
|
|
34
|
+
let yuno = null;
|
|
35
|
+
let seq = 0;
|
|
36
|
+
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
const i18next = (await import("i18next")).default;
|
|
39
|
+
await i18next.init({lng: "en", resources: {}});
|
|
40
|
+
gobj_start_up(null, null, null, null, null, null, null);
|
|
41
|
+
set_log_callback((level, msg) => {
|
|
42
|
+
logged.push({level: String(level), msg: String(msg)});
|
|
43
|
+
});
|
|
44
|
+
gclass_create("C_TEST_GUARD_HOST", [], [["ST_IDLE", []]], {}, 0, [SDATA_END()], {}, 0, 0, 0, 0);
|
|
45
|
+
register_c_yui_shell();
|
|
46
|
+
yuno = gobj_create_yuno("guard_yuno", "C_TEST_GUARD_HOST", {});
|
|
47
|
+
gobj_start(yuno);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
logged.length = 0;
|
|
52
|
+
/* What a failed test left open belongs to its own shell. */
|
|
53
|
+
for(const $n of document.querySelectorAll(".CONFIRM, .TEST_SHEET")) {
|
|
54
|
+
if($n.parentNode) {
|
|
55
|
+
$n.parentNode.removeChild($n);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function errors()
|
|
61
|
+
{
|
|
62
|
+
return logged.filter((l) => l.level === "error").map((l) => l.msg);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Same Back as window_veto_back: a Back off a marker lands on the entry
|
|
66
|
+
* under it, which the shell stamped with the TOP overlay's id. */
|
|
67
|
+
function press_back(shell)
|
|
68
|
+
{
|
|
69
|
+
const stack = shell.priv.overlay_stack;
|
|
70
|
+
const ev = new Event("popstate");
|
|
71
|
+
ev.state = {__yui_below__: stack.length ? stack[stack.length - 1].id : 0};
|
|
72
|
+
window.dispatchEvent(ev);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function settle()
|
|
76
|
+
{
|
|
77
|
+
return new Promise(resolve => setTimeout(resolve, 0));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function sheets()
|
|
81
|
+
{
|
|
82
|
+
return document.querySelectorAll(".TEST_SHEET").length;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function questions()
|
|
86
|
+
{
|
|
87
|
+
return document.querySelectorAll(".CONFIRM").length;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* A shell, and a sheet over it whose guard asks while `state.dirty`. */
|
|
91
|
+
function open_guarded_sheet(state)
|
|
92
|
+
{
|
|
93
|
+
seq++;
|
|
94
|
+
const app = gobj_create(`gapp${seq}`, "C_TEST_GUARD_HOST", {}, yuno);
|
|
95
|
+
gobj_start(app);
|
|
96
|
+
const shell = gobj_create(`gshell${seq}`, "C_YUI_SHELL",
|
|
97
|
+
{config: {shell: {}}, default_route: "/home", subscriber: app}, app);
|
|
98
|
+
gobj_start(shell);
|
|
99
|
+
logged.length = 0; /* the shell's own start: no route table here */
|
|
100
|
+
|
|
101
|
+
const handle = yui_shell_show_modal(shell, "the sheet", {
|
|
102
|
+
dialog: true,
|
|
103
|
+
logical_class: "TEST_SHEET",
|
|
104
|
+
title: "settings",
|
|
105
|
+
before_close: function() {
|
|
106
|
+
if(!state.dirty) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
state.asked++;
|
|
110
|
+
return yui_shell_confirm_yesnocancel(shell, "all changes will be lost", {
|
|
111
|
+
yes_label: "yes", no_label: "no", cancel_label: "cancel"
|
|
112
|
+
}).then(function(answer) {
|
|
113
|
+
if(answer === "yes") {
|
|
114
|
+
handle.close();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return {shell, handle};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
describe("a modal whose guard asks before it closes", () => {
|
|
123
|
+
test("Back asks ONCE, and the next Back takes the question, not the modal", async () => {
|
|
124
|
+
const state = {dirty: true, asked: 0};
|
|
125
|
+
const {shell, handle} = open_guarded_sheet(state);
|
|
126
|
+
expect(shell.priv.overlay_stack.length).toBe(1);
|
|
127
|
+
|
|
128
|
+
/* Back: the guard asks. The modal stays, and only the question
|
|
129
|
+
* is on the stack -- the modal's entry went with the Back. */
|
|
130
|
+
press_back(shell);
|
|
131
|
+
expect(sheets()).toBe(1);
|
|
132
|
+
expect(questions()).toBe(1);
|
|
133
|
+
expect(state.asked).toBe(1);
|
|
134
|
+
expect(shell.priv.overlay_stack.length).toBe(1);
|
|
135
|
+
|
|
136
|
+
/* Back again: it is the question that goes (its safe default,
|
|
137
|
+
* cancel), and the modal takes its entry back only then. */
|
|
138
|
+
press_back(shell);
|
|
139
|
+
await settle();
|
|
140
|
+
expect(questions()).toBe(0);
|
|
141
|
+
expect(sheets()).toBe(1);
|
|
142
|
+
expect(state.asked).toBe(1);
|
|
143
|
+
expect(shell.priv.overlay_stack.length).toBe(1);
|
|
144
|
+
|
|
145
|
+
/* And a third Back asks again: one question, not two. */
|
|
146
|
+
press_back(shell);
|
|
147
|
+
expect(questions()).toBe(1);
|
|
148
|
+
expect(state.asked).toBe(2);
|
|
149
|
+
press_back(shell);
|
|
150
|
+
await settle();
|
|
151
|
+
expect(questions()).toBe(0);
|
|
152
|
+
expect(sheets()).toBe(1);
|
|
153
|
+
expect(errors()).toEqual([]);
|
|
154
|
+
|
|
155
|
+
handle.close();
|
|
156
|
+
expect(sheets()).toBe(0);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("the back arrow that asks leaves the modal's entry UNDER the question", async () => {
|
|
160
|
+
const state = {dirty: true, asked: 0};
|
|
161
|
+
const {shell, handle} = open_guarded_sheet(state);
|
|
162
|
+
const entry = shell.priv.overlay_stack[0];
|
|
163
|
+
|
|
164
|
+
document.querySelector(".TEST_SHEET .yui-dialog-back").click();
|
|
165
|
+
expect(questions()).toBe(1);
|
|
166
|
+
expect(shell.priv.overlay_stack.length).toBe(2);
|
|
167
|
+
expect(shell.priv.overlay_stack[0]).toBe(entry);
|
|
168
|
+
|
|
169
|
+
press_back(shell);
|
|
170
|
+
await settle();
|
|
171
|
+
expect(questions()).toBe(0);
|
|
172
|
+
expect(sheets()).toBe(1);
|
|
173
|
+
expect(shell.priv.overlay_stack).toEqual([entry]);
|
|
174
|
+
expect(errors()).toEqual([]);
|
|
175
|
+
|
|
176
|
+
handle.close();
|
|
177
|
+
expect(sheets()).toBe(0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("with nothing to lose, Back closes it at once", async () => {
|
|
181
|
+
const state = {dirty: false, asked: 0};
|
|
182
|
+
const {shell} = open_guarded_sheet(state);
|
|
183
|
+
expect(sheets()).toBe(1);
|
|
184
|
+
|
|
185
|
+
press_back(shell);
|
|
186
|
+
await settle();
|
|
187
|
+
expect(sheets()).toBe(0);
|
|
188
|
+
expect(questions()).toBe(0);
|
|
189
|
+
expect(state.asked).toBe(0);
|
|
190
|
+
expect(shell.priv.overlay_stack).toEqual([]);
|
|
191
|
+
expect(errors()).toEqual([]);
|
|
192
|
+
});
|
|
193
|
+
});
|
package/src/shell_modals.js
CHANGED
|
@@ -386,7 +386,17 @@ export function yui_shell_show_modal(shell, content, opts)
|
|
|
386
386
|
* VETO — the modal stays up and the caller takes over (e.g. an
|
|
387
387
|
* unsaved-changes prompt that calls close() itself on confirm). Return
|
|
388
388
|
* true/undefined to let it close. Absent guard ⇒ always closes, so
|
|
389
|
-
* existing callers are unaffected.
|
|
389
|
+
* existing callers are unaffected.
|
|
390
|
+
*
|
|
391
|
+
* A guard that ASKS first returns the question's PROMISE: a veto, and
|
|
392
|
+
* the modal is told when the question is over. It matters on browser
|
|
393
|
+
* Back, which took the modal's history entry before the guard ran:
|
|
394
|
+
* the entry goes back once the question is gone, never above it.
|
|
395
|
+
* Returned false after opening the question, the entry went back at
|
|
396
|
+
* once, over the question's own, and the next Back -- the one meant
|
|
397
|
+
* for the question -- reached the modal, which asked again: every
|
|
398
|
+
* Back stacked one more question. Same contract as the `asking`
|
|
399
|
+
* close of C_YUI_WINDOW. */
|
|
390
400
|
let guarded_close = function(on_veto) {
|
|
391
401
|
if(closed) {
|
|
392
402
|
return;
|
|
@@ -400,6 +410,12 @@ export function yui_shell_show_modal(shell, content, opts)
|
|
|
400
410
|
allow = true;
|
|
401
411
|
}
|
|
402
412
|
}
|
|
413
|
+
if(allow && typeof allow.then === "function") {
|
|
414
|
+
if(typeof on_veto === "function") {
|
|
415
|
+
allow.then(on_veto, on_veto);
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
403
419
|
if(allow === false) {
|
|
404
420
|
if(typeof on_veto === "function") {
|
|
405
421
|
on_veto();
|
|
@@ -412,10 +428,13 @@ export function yui_shell_show_modal(shell, content, opts)
|
|
|
412
428
|
guarded_close(null);
|
|
413
429
|
};
|
|
414
430
|
/* Browser Back already consumed the history entry; if the guard vetoes,
|
|
415
|
-
* re-arm a fresh entry so a later Back still targets this modal
|
|
431
|
+
* re-arm a fresh entry so a later Back still targets this modal --
|
|
432
|
+
* unless the answer to its question closed it. */
|
|
416
433
|
let on_back = function() {
|
|
417
434
|
guarded_close(function() {
|
|
418
|
-
|
|
435
|
+
if(!closed) {
|
|
436
|
+
overlay = yui_shell_register_overlay(shell, on_back);
|
|
437
|
+
}
|
|
419
438
|
});
|
|
420
439
|
};
|
|
421
440
|
|