"use strict";
/**
* Rsx_Modal Component
*
* Instance of a modal dialog. Handles lifecycle, sizing, and user interaction.
* Typically created and managed by the Modal static API class.
*/
class Rsx_Modal extends Component {
on_create() {
this.data.title = '';
this.data.body_content = null;
this.data.buttons = [];
this.data.closable = true;
this.data.max_width = 800;
this.data.close_on_submit = true;
this.data.is_visible = false;
this.data.result_promise = null;
this.data.resolve_fn = null;
// Store reference to bootstrap modal instance
this._bs_modal = null;
this._resize_handler = null;
}
on_ready() {
const that = this;
// Set up close button handler
this.$sid('close_btn').on('click', function (e) {
e.preventDefault();
if (that.data.closable) {
that.close(false);
}
});
// Set up backdrop click handler
this.$sid('backdrop').on('click', function (e) {
if (that.data.closable && e.target === this) {
that.close(false);
}
});
// Set up ESC key handler
$(document).on('keydown.rsx_modal_' + this._cid, function (e) {
if (e.key === 'Escape' && that.data.closable && that.data.is_visible) {
that.close(false);
}
});
// Set up resize handler
this._resize_handler = debounce(() => {
if (that.data.is_visible) {
that._apply_sizing();
}
}, 100);
$(window).on('resize.rsx_modal_' + this._cid, this._resize_handler);
}
/**
* Configure and show the modal
* @param {Object} options - Modal options (title, body, buttons, etc.)
* @param {Object} internal_options - Internal options (skip_backdrop, animate)
*/
async show(options) {
let internal_options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const that = this;
const skip_backdrop = internal_options.skip_backdrop || false;
const should_animate = internal_options.animate || false;
console.log('[Rsx_Modal] show() called with options:', options);
// Store options
this.data.title = options.title || '';
this.data.closable = options.closable !== undefined ? options.closable : true;
this.data.max_width = options.max_width || 800;
this.data.close_on_submit = options.close_on_submit !== undefined ? options.close_on_submit : true;
this.data.buttons = options.buttons || [];
this.data.skip_backdrop = skip_backdrop;
this.data.icon = options.icon || null;
console.log('[Rsx_Modal] Setting title to:', this.data.title);
console.log('[Rsx_Modal] Title element:', this.$sid('title'));
// Set title
this.$sid('title').text(this.data.title);
// Show/hide close button based on closable
if (this.data.closable) {
this.$sid('close_btn').show();
} else {
this.$sid('close_btn').hide();
}
// Set body content (with optional icon)
this._set_body_content(options.body, this.data.icon);
// Set buttons
this._set_buttons();
// Create promise that will resolve when modal closes
const result_promise = new Promise(resolve => {
that.data.resolve_fn = resolve;
});
// Show modal and backdrop
this.data.is_visible = true;
// Append to body so it's on top (don't append backdrop if using shared)
if (!skip_backdrop) {
$('body').append(this.$sid('backdrop'));
}
$('body').append(this.$);
// Apply sizing before showing
this._apply_sizing();
// Fade in modal (and backdrop if not using shared)
await this._fade_in(should_animate);
// Auto-focus first input element
this._focus_first_input();
return result_promise;
}
/**
* Set body content with optional icon
*/
_set_body_content(body, icon) {
const $body = this.$sid('body');
$body.empty();
// If icon provided, add it
if (icon) {
const $icon = $(``);
$body.append($icon);
$body.addClass('has-icon');
} else {
$body.removeClass('has-icon');
}
// Get or create body content wrapper
let $content = this.$sid('body_content');
if (!$content.exists()) {
$content = $('
');
$body.append($content);
}
if (typeof body === 'string') {
// Text content - escape and convert newlines
const escaped = $('').text(body).html().replace(/\n/g, '
');
$content.html(escaped);
} else if (body instanceof jQuery) {
// jQuery element
$content.append(body);
} else if (body && typeof body === 'object') {
// Assume it's a jqhtml component instance
$content.append(body.$);
}
}
/**
* Set buttons in footer
*/
_set_buttons() {
const that = this;
const $footer = this.$sid('footer');
$footer.empty();
if (this.data.buttons.length === 0) {
$footer.hide();
return;
}
$footer.show();
for (let button_def of this.data.buttons) {
const $button = $('