modals beforeClose promise
3clyp50 committed
Jan 25, 2026 at 16:46 UTC
a62dfe4c5172724e10557d68578d0ca29221086b
1 file changed
+51
-27
webui/js/modals.js
+51
-27
@@ -99,15 +99,17 @@ function createModalElement(path) {
99
inner: newModal.querySelector(".modal-inner"),
100
styles: [],
101
scripts: [],
102
+ beforeClose: null,
103
};
104
}
105
106
// Function to open modal with content from URL
106
-export function openModal(modalPath) {
107
+export function openModal(modalPath, beforeClose = null) {
108
return new Promise((resolve) => {
109
try {
110
// Create new modal instance
111
const modal = createModalElement(modalPath);
112
+ modal.beforeClose = beforeClose;
113
114
new MutationObserver(
115
(_, o) =>
@@ -184,23 +186,42 @@ export function closeModal(modalPath = null) {
186
187
// Get the modal from stack at the found index
188
modal = modalStack[modalIndex];
187
- // Remove the modal from stack
188
- modalStack.splice(modalIndex, 1);
189
} else {
190
- // Just remove the last modal
191
- modal = modalStack.pop();
190
+ // Just get the last modal (removal happens after beforeClose)
191
+ modal = modalStack[modalStack.length - 1];
192
}
193
194
- // Remove modal-specific styles and scripts immediately
195
- modal.styles.forEach((styleId) => {
196
- document.querySelector(`[data-modal-style="${styleId}"]`)?.remove();
197
- });
198
- modal.scripts.forEach((scriptId) => {
199
- document.querySelector(`[data-modal-script="${scriptId}"]`)?.remove();
200
- });
194
+ const canClose = async () => {
195
+ if (!modal.beforeClose) return true;
196
+ try {
197
+ const result = await Promise.resolve(modal.beforeClose());
198
+ return result !== false;
199
+ } catch (error) {
200
+ console.error("Error in beforeClose handler:", error);
201
+ return true;
202
+ }
203
+ };
204
+
205
+ return Promise.resolve(canClose()).then((shouldClose) => {
206
+ if (!shouldClose) return false;
207
+
208
+ if (modalPath) {
209
+ // Remove the modal from stack after beforeClose check
210
+ modalStack.splice(modalIndex, 1);
211
+ } else {
212
+ modalStack.pop();
213
+ }
214
+
215
+ // Remove modal-specific styles and scripts immediately
216
+ modal.styles.forEach((styleId) => {
217
+ document.querySelector(`[data-modal-style="${styleId}"]`)?.remove();
218
+ });
219
+ modal.scripts.forEach((scriptId) => {
220
+ document.querySelector(`[data-modal-script="${scriptId}"]`)?.remove();
221
+ });
222
202
- // First remove the show class to trigger the transition
203
- modal.element.classList.remove("show");
223
+ // First remove the show class to trigger the transition
224
+ modal.element.classList.remove("show");
225
226
// commented out to prevent race conditions
227
@@ -223,21 +244,24 @@ export function closeModal(modalPath = null) {
244
// }
245
// }, 500); // 500ms should be enough for the transition to complete
246
226
- // remove immediately
227
- if (modal.element.parentNode) {
228
- modal.element.parentNode.removeChild(modal.element);
229
- }
247
+ // remove immediately
248
+ if (modal.element.parentNode) {
249
+ modal.element.parentNode.removeChild(modal.element);
250
+ }
251
252
232
- // Handle backdrop visibility and body overflow
233
- if (modalStack.length === 0) {
234
- // Hide backdrop when no modals are left
235
- backdrop.style.display = "none";
236
- document.body.style.overflow = "";
237
- } else {
238
- // Update modal z-indexes
239
- updateModalZIndexes();
240
- }
253
+ // Handle backdrop visibility and body overflow
254
+ if (modalStack.length === 0) {
255
+ // Hide backdrop when no modals are left
256
+ backdrop.style.display = "none";
257
+ document.body.style.overflow = "";
258
+ } else {
259
+ // Update modal z-indexes
260
+ updateModalZIndexes();
261
+ }
262
+
263
+ return true;
264
+ });
265
}
266
267
// Function to scroll to element by ID within the last modal