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>
This commit is contained in:
Wolfsblvt
2026-03-24 00:11:38 +01:00
committed by GitHub
parent d306194c51
commit e52c035fcd
2 changed files with 38 additions and 6 deletions
+7 -1
View File
@@ -488,7 +488,13 @@ function showOverlay(customContent = null) {
const content = getOverlayContent(customContent);
loaderPopup = new Popup(content, POPUP_TYPE.DISPLAY, null, { transparent: true, animation: 'none', wide: true, large: true });
loaderPopup = new Popup(content, POPUP_TYPE.DISPLAY, null, {
allowEscapeClose: false,
transparent: true,
animation: 'none',
wide: true,
large: true,
});
// No close button, loaders are not closable
loaderPopup.closeButton.style.display = 'none';
+31 -5
View File
@@ -54,6 +54,7 @@ export const POPUP_RESULT = {
* @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`.
* @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.
* @property {CustomPopupInput[]?} [customInputs=null] - Custom inputs to add to the popup. The display below the content and the input box, one by one.
* @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.
* @property {(popup: Popup) => Promise<boolean?>|boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close
* @property {(popup: Popup) => Promise<void?>|void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up
* @property {(popup: Popup) => Promise<void?>|void?} [onOpen=null] - Handler called after the popup opens
@@ -173,6 +174,8 @@ export class Popup {
/** @type {Promise<any>} */ #promise;
/** @type {(result: any) => any} */ #resolver;
/** @type {boolean} */ #allowEscapeClose;
/** @type {boolean} */ #isClosingPrevented;
/** @type {number} */ #lastEscapePress = 0;
/** @type {boolean} */ #isShowingForceCloseConfirm = false;
@@ -185,13 +188,39 @@ export class Popup {
* @param {string} [inputValue=''] - The initial value of the input field
* @param {PopupOptions} [options={}] - Additional options for the popup
*/
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 } = {}) {
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,
allowEscapeClose = true,
onClosing = null,
onClose = null,
onOpen = null,
cropAspect = null,
cropImage = null,
} = {}) {
Popup.util.popups.push(this);
// Make this popup uniquely identifiable
this.id = uuidv4();
this.type = type;
// Setup some args being passed in as private properties
this.#allowEscapeClose = allowEscapeClose;
// Utilize event handlers being passed in
this.onClosing = onClosing;
this.onClose = onClose;
@@ -479,10 +508,7 @@ export class Popup {
// Bind dialog listeners manually, so we can be sure context is preserved
const cancelListener = async (evt) => {
// If neither cancel button nor close button is visible or present, don't allow escape to close the popup
const hasCancelButton = this.cancelButton?.offsetParent !== null && this.buttonControls?.offsetParent !== null;
const hasCloseButton = this.closeButton?.offsetParent !== null;
if (!hasCancelButton && !hasCloseButton) {
if (!this.#allowEscapeClose) {
evt.preventDefault();
evt.stopPropagation();
// Set flag so closeListener also blocks the close event (browser may fire it after multiple Escape presses)