195 lines
6.3 KiB
JavaScript
195 lines
6.3 KiB
JavaScript
/*
|
|
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* Base Dialog Controller - Provides generic dialog rendering and lifecycle management.
|
|
*
|
|
* This controller handles:
|
|
* - Dialog rendering from templates
|
|
* - Close animations and DOM cleanup
|
|
* - Keyboard (Escape) and backdrop click handling
|
|
* - Standard confirm and info dialog patterns
|
|
*
|
|
* DEPENDENCY RULES:
|
|
* controllers → cores, stores, utils, controllers
|
|
*/
|
|
|
|
import { cloneTemplate } from "../utils/html.js";
|
|
import { TEMPLATE_IDS } from "../utils/constants.js";
|
|
|
|
/**
|
|
* Show a dialog with standard animations and close handling.
|
|
*
|
|
* @param {string} templateId - Template ID to clone
|
|
* @param {Object} [options] - Dialog options
|
|
* @param {Function} [options.onOpen] - Called when dialog opens with {overlay, dialog, close}
|
|
* @param {Function} [options.onClose] - Called when dialog closes with result
|
|
* @param {boolean} [options.closeOnBackdrop=true] - Close dialog when clicking backdrop
|
|
* @param {boolean} [options.closeOnEscape=true] - Close dialog when pressing Escape
|
|
* @returns {Promise<any>} Resolves with result when dialog closes
|
|
*/
|
|
export function showDialog(templateId, options = {}) {
|
|
const { onOpen, onClose, closeOnBackdrop = true, closeOnEscape = true } = options;
|
|
|
|
return new Promise((resolve) => {
|
|
const overlay = cloneTemplate(templateId);
|
|
if (!overlay) {
|
|
console.error(`Failed to load dialog template: ${templateId}`);
|
|
resolve(null);
|
|
return;
|
|
}
|
|
|
|
const dialog =
|
|
overlay.querySelector(".rtx-remix-dialog") ||
|
|
overlay.querySelector(".rtx-remix-confirm-dialog") ||
|
|
overlay.querySelector(".rtx-remix-info-dialog");
|
|
|
|
let isClosing = false;
|
|
|
|
/**
|
|
* Close the dialog with animation
|
|
* @param {any} result - Result to resolve with
|
|
*/
|
|
function close(result) {
|
|
if (isClosing) return;
|
|
isClosing = true;
|
|
|
|
// Apply close animations
|
|
overlay.style.animation = "rtx-fadeOut 0.15s ease-out";
|
|
if (dialog) {
|
|
dialog.style.animation = "rtx-slideOut 0.15s ease-out";
|
|
}
|
|
|
|
// Remove event listeners
|
|
document.removeEventListener("keydown", handleKeydown);
|
|
|
|
// Remove from DOM and resolve after animation
|
|
setTimeout(() => {
|
|
if (overlay.parentNode) {
|
|
overlay.parentNode.removeChild(overlay);
|
|
}
|
|
onClose?.(result);
|
|
resolve(result);
|
|
}, 150);
|
|
}
|
|
|
|
/**
|
|
* Handle keydown events
|
|
*/
|
|
function handleKeydown(e) {
|
|
if (e.key === "Escape" && closeOnEscape) {
|
|
e.preventDefault();
|
|
close(null);
|
|
}
|
|
}
|
|
|
|
// Add escape key handler
|
|
document.addEventListener("keydown", handleKeydown);
|
|
|
|
// Track mousedown for backdrop click handling
|
|
let mouseDownOnOverlay = false;
|
|
|
|
overlay.addEventListener("mousedown", (e) => {
|
|
if (e.target === overlay) {
|
|
mouseDownOnOverlay = true;
|
|
}
|
|
});
|
|
|
|
overlay.addEventListener("mouseup", (e) => {
|
|
if (mouseDownOnOverlay && e.target === overlay && closeOnBackdrop) {
|
|
close(null);
|
|
}
|
|
mouseDownOnOverlay = false;
|
|
});
|
|
|
|
// Prevent dialog clicks from bubbling
|
|
if (dialog) {
|
|
dialog.addEventListener("mousedown", () => {
|
|
mouseDownOnOverlay = false;
|
|
});
|
|
}
|
|
|
|
// Add to DOM
|
|
document.body.appendChild(overlay);
|
|
|
|
// Call onOpen callback
|
|
onOpen?.({ overlay, dialog, close });
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Show a confirmation dialog.
|
|
*
|
|
* @param {Object} options - Dialog options
|
|
* @param {string} options.title - Dialog title
|
|
* @param {string} options.message - Dialog message (can contain HTML)
|
|
* @param {string} [options.confirmText="Confirm"] - Confirm button text
|
|
* @param {string} [options.cancelText="Cancel"] - Cancel button text
|
|
* @param {boolean} [options.danger=false] - Use danger styling for confirm button
|
|
* @returns {Promise<boolean>} True if confirmed, false if cancelled
|
|
*/
|
|
export function showConfirmDialog({ title, message, confirmText = "Confirm", cancelText = "Cancel", danger = false }) {
|
|
return showDialog(TEMPLATE_IDS.CONFIRM_DIALOG, {
|
|
onOpen: ({ overlay, close }) => {
|
|
const titleEl = overlay.querySelector(".rtx-remix-confirm-title");
|
|
const messageEl = overlay.querySelector(".rtx-remix-confirm-message");
|
|
const cancelBtn = overlay.querySelector("[data-action='cancel']");
|
|
const confirmBtn = overlay.querySelector("[data-action='confirm']");
|
|
|
|
if (titleEl) titleEl.textContent = title;
|
|
if (messageEl) messageEl.innerHTML = message;
|
|
if (cancelBtn) {
|
|
cancelBtn.textContent = cancelText;
|
|
cancelBtn.addEventListener("click", () => close(false));
|
|
}
|
|
if (confirmBtn) {
|
|
confirmBtn.textContent = confirmText;
|
|
if (danger) {
|
|
confirmBtn.classList.add("rtx-remix-btn-danger");
|
|
}
|
|
confirmBtn.addEventListener("click", () => close(true));
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Show an info dialog.
|
|
*
|
|
* @param {Object} options - Dialog options
|
|
* @param {string} options.title - Dialog title
|
|
* @param {string} options.content - Dialog content (can contain HTML)
|
|
* @param {string} [options.closeText="Close"] - Close button text
|
|
* @returns {Promise<void>}
|
|
*/
|
|
export function showInfoDialog({ title, content, closeText = "Close" }) {
|
|
return showDialog(TEMPLATE_IDS.INFO_DIALOG, {
|
|
onOpen: ({ overlay, close }) => {
|
|
const titleEl = overlay.querySelector(".rtx-remix-info-title");
|
|
const contentEl = overlay.querySelector(".rtx-remix-info-content");
|
|
const closeBtn = overlay.querySelector("[data-action='close']");
|
|
|
|
if (titleEl) titleEl.textContent = title;
|
|
if (contentEl) contentEl.innerHTML = content;
|
|
if (closeBtn) {
|
|
closeBtn.textContent = closeText;
|
|
closeBtn.addEventListener("click", () => close());
|
|
}
|
|
},
|
|
});
|
|
}
|