Merge pull request #2520 from SillyTavern/popup-onclosing-fix Fix popup onClosing handling not working correctly

ecceeec62a3d821d86fec57c1af3c40263023ab8

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
1 files changed, +30 -8Ignore whitespace
public/scripts/popup.js+30 -8
@@ -135,6 +135,7 @@ export class Popup {
135135
136136 /** @type {Promise<any>} */ #promise;
137137 /** @type {(result: any) => any} */ #resolver;
138+ /** @type {boolean} */ #isClosingPrevented;
138139
139140 /**
140141 * Constructs a new Popup object with the given text content, type, inputValue, and options
@@ -334,10 +335,21 @@ export class Popup {
334335 evt.preventDefault();
335336 evt.stopPropagation();
336337 await this.complete(POPUP_RESULT.CANCELLED);
337- window.removeEventListener('cancel', cancelListenerBound);
338338 };
339339 const cancelListenerBound =this.dlg.addEventListener('cancel', cancelListener.bind(this));
340- this.dlg.addEventListener('cancel', cancelListenerBound);
340+
341+ // Don't ask me why this is needed. I don't get it. But we have to keep it.
342+ // We make sure that the modal on it's own doesn't hide. Dunno why, if onClosing is triggered multiple times through the cancel event, and stopped
343+ // It seems to just call 'close' on the dialog even if the 'cancel' event was prevented.
344+ // Here, we just say that close should not happen if the dalog has no result.
345+ const closeListener = async (evt) => {
346+ if (this.#isClosingPrevented) {
347+ evt.preventDefault();
348+ evt.stopPropagation();
349+ this.dlg.showModal();
350+ }
351+ };
352+ this.dlg.addEventListener('close', closeListener.bind(this));
341353
342354 const keyListener = async (evt) => {
343355 switch (evt.key) {
@@ -366,16 +378,16 @@ export class Popup {
366378 evt.preventDefault();
367379 evt.stopPropagation();
368380 const result = Number(document.activeElement.getAttribute('data-result') ?? this.defaultResult);
381+
382+ // Call complete on the popup. Make sure that we handle `onClosing` cancels correctly and don't remove the listener then.
369383 await this.complete(result);
370- window.removeEventListener('keydown', keyListenerBound);
371384
372385 break;
373386 }
374387 }
375388
376389 };
377390 const keyListenerBound =this.dlg.addEventListener('keydown', keyListener.bind(this));
378- this.dlg.addEventListener('keydown', keyListenerBound);
379391 }
380392
381393 /**
@@ -445,9 +457,11 @@ export class Popup {
445457 * - popup with `POPUP_TYPE.INPUT` will return the input value - or `false` on negative and `null` on cancelled
446458 * - All other will return the result value as provided as `POPUP_RESULT` or a custom number value
447459 *
460+ * <b>IMPORTANT:</b> If the popup closing was cancelled via the `onClosing` handler, the return value will be `Promise<undefined>`.
461+ *
448462 * @param {POPUP_RESULT|number} result - The result of the popup (either an existing `POPUP_RESULT` or a custom result value)
449463 *
450464 * @returns {Promise<string|number|boolean|undefined?>} A promise that resolves with the value of the popup when it is completed. <b>Returns `undefined` if the closing action was cancelled.</b>
451465 */
452466 async complete(result) {
453467 // In all cases besides INPUT the popup value should be the result
@@ -481,8 +495,16 @@ export class Popup {
481495
482496 if (this.onClosing) {
483497 const shouldClose = this.onClosing(this);
484498 if (!shouldClose) return;{
499+ this.#isClosingPrevented = true;
500+ // Set values back if we cancel out of closing the popup
501+ this.value = undefined;
502+ this.result = undefined;
503+ this.inputResults = undefined;
504+ return undefined;
505+ }
485506 }
507+ this.#isClosingPrevented = false;
486508
487509 Popup.util.lastResult = { value, result, inputResults: this.inputResults };
488510 this.#hide();