@yuneta/gobj-ui 7.25.6 → 7.25.8
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 +53 -1
- package/dist/gobj-ui.cjs.js +330 -63
- package/dist/gobj-ui.es.js +330 -63
- package/package.json +1 -1
- package/src/c_yui_schema_editor.js +339 -66
- package/src/schema_editor_reload.wiring.test.js +473 -0
- package/src/schema_editor_transport.wiring.test.js +174 -1
- package/src/shell_modals.js +61 -12
- package/src/shell_toast_once.test.js +125 -0
package/src/shell_modals.js
CHANGED
|
@@ -111,6 +111,22 @@ function maybe_apply_translator($node, opts)
|
|
|
111
111
|
* Non-blocking, auto-dismiss after `opts.timeout` ms (default
|
|
112
112
|
* 5000). `opts.timeout = 0` disables auto-dismiss.
|
|
113
113
|
* Returns `{ close() }` so callers can dismiss programmatically.
|
|
114
|
+
*
|
|
115
|
+
* ONE toast per message on screen: a string message of the same
|
|
116
|
+
* kind as a toast still showing is not stacked under it. One
|
|
117
|
+
* close of a transport settles every request it cut, each view
|
|
118
|
+
* shows its failure, and the operator got a column of identical
|
|
119
|
+
* "the connection dropped". A message built of nodes is never
|
|
120
|
+
* merged: its text is not a key.
|
|
121
|
+
*
|
|
122
|
+
* A repeat is still a CALLER of its own: it gets its own handle
|
|
123
|
+
* and its own time, and the toast stays while any caller holds
|
|
124
|
+
* it -- until every handle is closed or has timed out. Handed the
|
|
125
|
+
* first caller's handle and timer, a repeat asking `timeout: 0`
|
|
126
|
+
* was dismissed on the first one's timer, and closing one
|
|
127
|
+
* caller's handle closed a toast another was still showing
|
|
128
|
+
* (third independent review). The operator's ✕ closes it for
|
|
129
|
+
* everybody.
|
|
114
130
|
***************************************************************/
|
|
115
131
|
function show_notification(shell, kind, message, opts)
|
|
116
132
|
{
|
|
@@ -120,6 +136,16 @@ function show_notification(shell, kind, message, opts)
|
|
|
120
136
|
return { close: () => {} };
|
|
121
137
|
}
|
|
122
138
|
|
|
139
|
+
let timeout = (opts && opts.timeout != null) ? opts.timeout : 5000;
|
|
140
|
+
let key = (typeof message === "string") ? `${kind}\u0000${message}` : null;
|
|
141
|
+
if(key !== null) {
|
|
142
|
+
for(let $shown of Array.from($layer.children)) {
|
|
143
|
+
if($shown.__toast_key__ === key && $shown.__toast__) {
|
|
144
|
+
return $shown.__toast__.hold(timeout);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
123
149
|
let p_attrs = (typeof message === "string")
|
|
124
150
|
? {i18n: message}
|
|
125
151
|
: {};
|
|
@@ -135,34 +161,57 @@ function show_notification(shell, kind, message, opts)
|
|
|
135
161
|
$layer.appendChild($note);
|
|
136
162
|
maybe_apply_translator($note, opts);
|
|
137
163
|
|
|
138
|
-
let
|
|
164
|
+
let holders = new Set();
|
|
139
165
|
let closed = false;
|
|
140
166
|
|
|
141
|
-
let
|
|
167
|
+
let close_toast = function() {
|
|
142
168
|
if(closed) {
|
|
143
169
|
return;
|
|
144
170
|
}
|
|
145
171
|
closed = true;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
172
|
+
for(let holder of holders) {
|
|
173
|
+
if(holder.timeout_id) {
|
|
174
|
+
clearTimeout(holder.timeout_id);
|
|
175
|
+
holder.timeout_id = null;
|
|
176
|
+
}
|
|
149
177
|
}
|
|
178
|
+
holders.clear();
|
|
150
179
|
if($note.parentNode) {
|
|
151
180
|
$note.parentNode.removeChild($note);
|
|
152
181
|
}
|
|
153
182
|
};
|
|
154
183
|
|
|
184
|
+
/* One caller of this toast: its handle, and its time. */
|
|
185
|
+
let hold = function(ms) {
|
|
186
|
+
let holder = {timeout_id: null};
|
|
187
|
+
let release = function() {
|
|
188
|
+
if(!holders.has(holder)) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if(holder.timeout_id) {
|
|
192
|
+
clearTimeout(holder.timeout_id);
|
|
193
|
+
holder.timeout_id = null;
|
|
194
|
+
}
|
|
195
|
+
holders.delete(holder);
|
|
196
|
+
if(holders.size === 0) {
|
|
197
|
+
close_toast();
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
holders.add(holder);
|
|
201
|
+
if(ms > 0) {
|
|
202
|
+
holder.timeout_id = setTimeout(release, ms);
|
|
203
|
+
}
|
|
204
|
+
return { close: release };
|
|
205
|
+
};
|
|
206
|
+
|
|
155
207
|
let $del = $note.querySelector(".delete");
|
|
156
208
|
if($del) {
|
|
157
|
-
$del.addEventListener("click",
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
let timeout = (opts && opts.timeout != null) ? opts.timeout : 5000;
|
|
161
|
-
if(timeout > 0) {
|
|
162
|
-
timeout_id = setTimeout(close, timeout);
|
|
209
|
+
$del.addEventListener("click", close_toast);
|
|
163
210
|
}
|
|
164
211
|
|
|
165
|
-
|
|
212
|
+
$note.__toast_key__ = key;
|
|
213
|
+
$note.__toast__ = {hold: hold};
|
|
214
|
+
return hold(timeout);
|
|
166
215
|
}
|
|
167
216
|
|
|
168
217
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* shell_toast_once.test.js
|
|
3
|
+
*
|
|
4
|
+
* A toast that says what one already on screen says is not
|
|
5
|
+
* stacked under it: one close of the transport settles every
|
|
6
|
+
* request it cut, each view that asked shows its failure, and
|
|
7
|
+
* the operator got a column of identical "the connection
|
|
8
|
+
* dropped" (low of the independent review of the 2nd round).
|
|
9
|
+
*
|
|
10
|
+
* And a repeat is a caller of its own (third independent
|
|
11
|
+
* review): its handle and its time are its own, and the toast
|
|
12
|
+
* stays while any caller still holds it.
|
|
13
|
+
*
|
|
14
|
+
* Copyright (c) 2026, ArtGins.
|
|
15
|
+
* All Rights Reserved.
|
|
16
|
+
***********************************************************************/
|
|
17
|
+
import {describe, test, expect, beforeEach, afterEach, vi} from "vitest";
|
|
18
|
+
import {install_dom_double} from "../test/dom_double.js";
|
|
19
|
+
|
|
20
|
+
install_dom_double();
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
yui_shell_show_error,
|
|
24
|
+
yui_shell_show_info,
|
|
25
|
+
} = await import("./shell_modals.js");
|
|
26
|
+
|
|
27
|
+
let shell = null;
|
|
28
|
+
|
|
29
|
+
function toasts()
|
|
30
|
+
{
|
|
31
|
+
return shell.priv.layers.notification.children;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
shell = {priv: {layers: {notification: document.createElement("div")}}};
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("one toast per message on screen", () => {
|
|
39
|
+
|
|
40
|
+
test("the same error twice is ONE toast", () => {
|
|
41
|
+
yui_shell_show_error(shell, "the connection dropped");
|
|
42
|
+
yui_shell_show_error(shell, "the connection dropped");
|
|
43
|
+
yui_shell_show_error(shell, "the connection dropped");
|
|
44
|
+
expect(toasts().length).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("different messages, or kinds, still stack", () => {
|
|
48
|
+
yui_shell_show_error(shell, "the connection dropped");
|
|
49
|
+
yui_shell_show_error(shell, "the connection dropped during the write");
|
|
50
|
+
yui_shell_show_info(shell, "the connection dropped");
|
|
51
|
+
expect(toasts().length).toBe(3);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("closing the one on screen: the next one is shown", () => {
|
|
55
|
+
const first = yui_shell_show_error(shell, "the connection dropped");
|
|
56
|
+
first.close();
|
|
57
|
+
expect(toasts().length).toBe(0);
|
|
58
|
+
yui_shell_show_error(shell, "the connection dropped");
|
|
59
|
+
expect(toasts().length).toBe(1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("the toast goes when EVERY handle of it is closed", () => {
|
|
63
|
+
const first = yui_shell_show_error(shell, "the connection dropped");
|
|
64
|
+
const again = yui_shell_show_error(shell, "the connection dropped");
|
|
65
|
+
again.close();
|
|
66
|
+
expect(toasts().length).toBe(1); /* the first still holds it */
|
|
67
|
+
first.close();
|
|
68
|
+
expect(toasts().length).toBe(0);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/*
|
|
73
|
+
* The third independent review: a repeat was handed the FIRST toast's
|
|
74
|
+
* handle and timer. A repeat asking `timeout: 0` was dismissed on the
|
|
75
|
+
* first one's timer, and closing one caller's handle closed the toast
|
|
76
|
+
* another caller was still showing.
|
|
77
|
+
*/
|
|
78
|
+
describe("a repeat is its own caller", () => {
|
|
79
|
+
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
vi.useFakeTimers();
|
|
82
|
+
});
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
vi.useRealTimers();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("a repeat asking timeout 0 is not dismissed on the first one's timer", () => {
|
|
88
|
+
yui_shell_show_error(shell, "the connection dropped", {timeout: 1000});
|
|
89
|
+
const sticky = yui_shell_show_error(shell, "the connection dropped", {timeout: 0});
|
|
90
|
+
vi.advanceTimersByTime(5000);
|
|
91
|
+
expect(toasts().length).toBe(1);
|
|
92
|
+
sticky.close();
|
|
93
|
+
expect(toasts().length).toBe(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("closing the first caller's handle leaves the repeat's toast up", () => {
|
|
97
|
+
const first = yui_shell_show_error(shell, "the connection dropped", {timeout: 0});
|
|
98
|
+
yui_shell_show_error(shell, "the connection dropped", {timeout: 3000});
|
|
99
|
+
first.close();
|
|
100
|
+
expect(toasts().length).toBe(1);
|
|
101
|
+
vi.advanceTimersByTime(3000);
|
|
102
|
+
expect(toasts().length).toBe(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("the longest time wins, as a restart did", () => {
|
|
106
|
+
yui_shell_show_error(shell, "the connection dropped", {timeout: 1000});
|
|
107
|
+
vi.advanceTimersByTime(800);
|
|
108
|
+
yui_shell_show_error(shell, "the connection dropped", {timeout: 1000});
|
|
109
|
+
vi.advanceTimersByTime(500);
|
|
110
|
+
expect(toasts().length).toBe(1);
|
|
111
|
+
vi.advanceTimersByTime(500);
|
|
112
|
+
expect(toasts().length).toBe(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("the operator's close takes it down for every caller", () => {
|
|
116
|
+
const first = yui_shell_show_error(shell, "the connection dropped", {timeout: 0});
|
|
117
|
+
const again = yui_shell_show_error(shell, "the connection dropped", {timeout: 0});
|
|
118
|
+
toasts()[0].querySelector(".TOAST_CLOSE").click();
|
|
119
|
+
expect(toasts().length).toBe(0);
|
|
120
|
+
first.close();
|
|
121
|
+
again.close();
|
|
122
|
+
yui_shell_show_error(shell, "the connection dropped", {timeout: 0});
|
|
123
|
+
expect(toasts().length).toBe(1);
|
|
124
|
+
});
|
|
125
|
+
});
|