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>

e52c035fcd63e737b9086e7dca89283312232d2b

Wolfsblvt <wolfsblvt@gmail.com>

Signed
2 files changed, +38 -6Ignore whitespace
public/scripts/action-loader.js+7 -1
@@ -488,7 +488,13 @@ function showOverlay(customContent = null) {
488488
489489 const content = getOverlayContent(customContent);
490490
491491 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+ });
492498
493499 // No close button, loaders are not closable
494500 loaderPopup.closeButton.style.display = 'none';
public/scripts/popup.js+31 -5
@@ -54,6 +54,7 @@ export const POPUP_RESULT = {
5454 * @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`.
5555 * @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.
5656 * @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.
5758 * @property {(popup: Popup) => Promise<boolean?>|boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close
5859 * @property {(popup: Popup) => Promise<void?>|void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up
5960 * @property {(popup: Popup) => Promise<void?>|void?} [onOpen=null] - Handler called after the popup opens
@@ -173,6 +174,8 @@ export class Popup {
173174
174175 /** @type {Promise<any>} */ #promise;
175176 /** @type {(result: any) => any} */ #resolver;
177+
178+ /** @type {boolean} */ #allowEscapeClose;
176179 /** @type {boolean} */ #isClosingPrevented;
177180 /** @type {number} */ #lastEscapePress = 0;
178181 /** @type {boolean} */ #isShowingForceCloseConfirm = false;
@@ -185,13 +188,39 @@ export class Popup {
185188 * @param {string} [inputValue=''] - The initial value of the input field
186189 * @param {PopupOptions} [options={}] - Additional options for the popup
187190 */
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+ } = {}) {
189215 Popup.util.popups.push(this);
190216
191217 // Make this popup uniquely identifiable
192218 this.id = uuidv4();
193219 this.type = type;
194220
221+ // Setup some args being passed in as private properties
222+ this.#allowEscapeClose = allowEscapeClose;
223+
195224 // Utilize event handlers being passed in
196225 this.onClosing = onClosing;
197226 this.onClose = onClose;
@@ -479,10 +508,7 @@ export class Popup {
479508
480509 // Bind dialog listeners manually, so we can be sure context is preserved
481510 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) {
486512 evt.preventDefault();
487513 evt.stopPropagation();
488514 // Set flag so closeListener also blocks the close event (browser may fire it after multiple Escape presses)