Add `allowEscapeClose` option to popup class for Escape key behavior override (#5340) * feat(popup): add allowEscapeClose option to override default escape key behavior Add `allowEscapeClose` parameter to PopupOptions to explicitly control whether Escape key closes the popup, overriding the default logic that checks for visible cancel/close buttons. When null (default), uses existing behavior; when true, allows escape even without buttons; when false, prevents escape even with buttons present. * feat(popup): enable Escape key closing for informational popups Add `allowEscapeClose: true` option to TEXT-type popups in export preset, persona lore, sampler select, stats, and world info assignment dialogs to allow users to dismiss these informational popups with the Escape key. * Improve Escape key interaction in popups * Adjust jsdoc for allowEscapeClose * Always return CANCELLED on Escape * fix(popup): correct jsdoc for allowEscapeClose property description --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -488,7 +488,13 @@ function showOverlay(customContent = null) { | ||
| 488 | 488 | |
| 489 | 489 | const content = getOverlayContent(customContent); |
| 490 | 490 | |
| 491 | 491 | loaderPopup = new Popup(content, POPUP_TYPE.DISPLAY, null, { transparent: true, animation: 'none', wide: true, large: true }); |
| 492 | + allowEscapeClose: false, | |
| 493 | + transparent: true, | |
| 494 | + animation: 'none', | |
| 495 | + wide: true, | |
| 496 | + large: true, | |
| 497 | + }); | |
| 492 | 498 | |
| 493 | 499 | // No close button, loaders are not closable |
| 494 | 500 | loaderPopup.closeButton.style.display = 'none'; |
| @@ -54,6 +54,7 @@ export const POPUP_RESULT = { | ||
| 54 | 54 | * @property {POPUP_RESULT|number?} [defaultResult=POPUP_RESULT.AFFIRMATIVE] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`. |
| 55 | 55 | * @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward. |
| 56 | 56 | * @property {CustomPopupInput[]?} [customInputs=null] - Custom inputs to add to the popup. The display below the content and the input box, one by one. |
| 57 | + * @property {boolean} [allowEscapeClose=true] - If true, allows closing the popup with the Escape key, returning `POPUP_RESULT.CANCELLED`. If false, requires double-escape to force close with a confirmation to prevent accidental closure. | |
| 57 | 58 | * @property {(popup: Popup) => Promise<boolean?>|boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close |
| 58 | 59 | * @property {(popup: Popup) => Promise<void?>|void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up |
| 59 | 60 | * @property {(popup: Popup) => Promise<void?>|void?} [onOpen=null] - Handler called after the popup opens |
| @@ -173,6 +174,8 @@ export class Popup { | ||
| 173 | 174 | |
| 174 | 175 | /** @type {Promise<any>} */ #promise; |
| 175 | 176 | /** @type {(result: any) => any} */ #resolver; |
| 177 | + | |
| 178 | + /** @type {boolean} */ #allowEscapeClose; | |
| 176 | 179 | /** @type {boolean} */ #isClosingPrevented; |
| 177 | 180 | /** @type {number} */ #lastEscapePress = 0; |
| 178 | 181 | /** @type {boolean} */ #isShowingForceCloseConfirm = false; |
| @@ -185,13 +188,39 @@ export class Popup { | ||
| 185 | 188 | * @param {string} [inputValue=''] - The initial value of the input field |
| 186 | 189 | * @param {PopupOptions} [options={}] - Additional options for the popup |
| 187 | 190 | */ |
| 188 | - constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, placeholder = null, tooltip = null, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, leftAlign = false, animation = 'fast', defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, customInputs = null, onClosing = null, onClose = null, onOpen = null, cropAspect = null, cropImage = null } = {}) { | |
| 191 | + constructor(content, type, inputValue = '', { | |
| 192 | + okButton = null, | |
| 193 | + cancelButton = null, | |
| 194 | + rows = 1, | |
| 195 | + placeholder = null, | |
| 196 | + tooltip = null, | |
| 197 | + wide = false, | |
| 198 | + wider = false, | |
| 199 | + large = false, | |
| 200 | + transparent = false, | |
| 201 | + allowHorizontalScrolling = false, | |
| 202 | + allowVerticalScrolling = false, | |
| 203 | + leftAlign = false, | |
| 204 | + animation = 'fast', | |
| 205 | + defaultResult = POPUP_RESULT.AFFIRMATIVE, | |
| 206 | + customButtons = null, | |
| 207 | + customInputs = null, | |
| 208 | + allowEscapeClose = true, | |
| 209 | + onClosing = null, | |
| 210 | + onClose = null, | |
| 211 | + onOpen = null, | |
| 212 | + cropAspect = null, | |
| 213 | + cropImage = null, | |
| 214 | + } = {}) { | |
| 189 | 215 | Popup.util.popups.push(this); |
| 190 | 216 | |
| 191 | 217 | // Make this popup uniquely identifiable |
| 192 | 218 | this.id = uuidv4(); |
| 193 | 219 | this.type = type; |
| 194 | 220 | |
| 221 | + // Setup some args being passed in as private properties | |
| 222 | + this.#allowEscapeClose = allowEscapeClose; | |
| 223 | + | |
| 195 | 224 | // Utilize event handlers being passed in |
| 196 | 225 | this.onClosing = onClosing; |
| 197 | 226 | this.onClose = onClose; |
| @@ -479,10 +508,7 @@ export class Popup { | ||
| 479 | 508 | |
| 480 | 509 | // Bind dialog listeners manually, so we can be sure context is preserved |
| 481 | 510 | const cancelListener = async (evt) => { |
| 482 | - // If neither cancel button nor close button is visible or present, don't allow escape to close the popup | |
| 511 | + if (!this.#allowEscapeClose) { | |
| 483 | - const hasCancelButton = this.cancelButton?.offsetParent !== null && this.buttonControls?.offsetParent !== null; | |
| 484 | - const hasCloseButton = this.closeButton?.offsetParent !== null; | |
| 485 | - if (!hasCancelButton && !hasCloseButton) { | |
| 486 | 512 | evt.preventDefault(); |
| 487 | 513 | evt.stopPropagation(); |
| 488 | 514 | // Set flag so closeListener also blocks the close event (browser may fire it after multiple Escape presses) |