/**
 * Copyright IBM Corp. 2026
 *
 * This source code is licensed under the Apache-2.0 license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { selectorTabbable } from "../../globals/settings.js";
import { carbonElement } from "../../globals/decorators/carbon-element.js";
import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.126.0/helpers/decorate.js";
import HostListener from "../../globals/decorators/host-listener.js";
import HostListenerMixin from "../../globals/mixins/host-listener.js";
import { MODAL_SIZE } from "./defs.js";
import modal_default$1 from "./modal.scss.js";
import "../inline-loading/index.js";
import { LitElement, html } from "lit";
import { property } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
//#region src/components/modal/modal.ts
/**
* Copyright IBM Corp. 2019, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* Modal.
*
* @element cds-modal
* @csspart dialog The dialog.
* @fires cds-modal-beingclosed
*   The custom event fired before this modal is being closed upon a user gesture.
*   Cancellation of this event stops the user-initiated action of closing this modal.
* @fires cds-modal-closed - The custom event fired after this modal is closed upon a user gesture.
*/
let CDSModal = class CDSModal extends HostListenerMixin(LitElement) {
	constructor(..._args) {
		super(..._args);
		this._launcher = null;
		this._loadingEl = null;
		this.WORKING_LOADING_STATUSES = [
			"active",
			"finished",
			"error"
		];
		this._handleClick = (event) => {
			if (event.composedPath().indexOf(this.shadowRoot) < 0 && !this.preventCloseOnClickOutside) this._handleUserInitiatedClose(event.target);
		};
		this._handleHostKeydown = (event) => {
			const target = event.target;
			const { primaryButton } = this._getFooterElements();
			if (this.open && this.shouldSubmitOnEnter && event.key === "Enter" && target && document.activeElement !== primaryButton) {
				const closeButton = this.constructor.selectorCloseButton;
				if (!(!!target.closest(closeButton) || !!document.activeElement?.closest?.(closeButton))) {
					primaryButton?.click();
					return;
				}
			}
			if (event.key === "Tab") {
				const { first: _firstElement, last: _lastElement } = this.getFocusable();
				if (event.shiftKey && (this.shadowRoot?.activeElement === _firstElement || document.activeElement === _firstElement)) {
					event.preventDefault();
					_lastElement?.focus();
				} else if (!event.shiftKey && document.activeElement === _lastElement) {
					event.preventDefault();
					_firstElement?.focus();
				}
			}
		};
		this._handleKeydown = ({ key, target }) => {
			if (!this.open) return;
			if (key === "Esc" || key === "Escape") this._handleUserInitiatedClose(target);
		};
		this.alert = false;
		this.ariaLabel = null;
		this.containerClass = "";
		this.fullWidth = false;
		this.hasScrollingContent = false;
		this.open = false;
		this.size = "md";
		this.preventCloseOnClickOutside = false;
		this.loadingStatus = "inactive";
		this.loadingDescription = "";
		this.loadingSuccessDelay = 1500;
		this.loadingIconDescription = "Loading";
		this.shouldSubmitOnEnter = false;
		this.preventClose = false;
	}
	/**
	* Get focusable elements.
	*
	* Querying all tabbable items.
	*
	* @returns {{first: HTMLElement, last: HTMLElement, all: HTMLElement[]}} Returns an object with various elements.
	*/
	getFocusable() {
		const elements = [];
		const tabbableItems = this.querySelectorAll(selectorTabbable);
		if (tabbableItems?.length) elements.push(...tabbableItems);
		const all = elements?.filter((el) => typeof el?.focus === "function");
		return {
			first: all[0],
			last: all[all.length - 1],
			all
		};
	}
	/**
	* Handles `click` event on the modal container.
	*
	* @param event The event.
	*/
	_handleClickContainer(event) {
		if (event.target.matches(this.constructor.selectorCloseButton) && !this.preventClose) this._handleUserInitiatedClose(event.target);
	}
	/**
	* Handles user-initiated close request of this modal.
	*
	* @param triggeredBy The element that triggered this close request.
	*/
	_handleUserInitiatedClose(triggeredBy) {
		if (this.open) {
			const init = {
				bubbles: true,
				cancelable: true,
				composed: true,
				detail: { triggeredBy }
			};
			if (this.dispatchEvent(new CustomEvent(this.constructor.eventBeforeClose, init))) {
				this.open = false;
				this.dispatchEvent(new CustomEvent(this.constructor.eventClose, init));
			}
		}
	}
	/**
	* Handles `slotchange` event.
	*/
	_handleSlotChange() {
		if (this.querySelector(`cds-modal-footer`)) this.setAttribute("has-footer", "");
		else this.removeAttribute("has-footer");
	}
	_initializeLoadingEl(footer) {
		if (!footer) return null;
		if (!this._loadingEl && this.WORKING_LOADING_STATUSES.includes(this.loadingStatus)) {
			const el = document.createElement(`cds-inline-loading`);
			el.setAttribute("controlled", "");
			el.setAttribute("aria-live", "off");
			footer.appendChild(el);
			this._loadingEl = el;
		}
		return this._loadingEl;
	}
	_getFooterElements() {
		return {
			footer: this.querySelector(`cds-modal-footer`),
			primaryButton: this.querySelector(`cds-modal-footer-button[kind="primary"]`) || this.querySelector(`cds-modal-footer-button[kind="danger"]`) || null,
			secondaryButtons: Array.from(this.querySelectorAll(`cds-modal-footer-button[kind="secondary"]`))
		};
	}
	_updateLoadingElement() {
		const { footer, primaryButton, secondaryButtons } = this._getFooterElements();
		const loader = this._initializeLoadingEl(footer);
		if (!footer || !loader || !primaryButton) return;
		if (this.WORKING_LOADING_STATUSES.includes(this.loadingStatus)) {
			loader.style.display = "inline-flex";
			loader.setAttribute("status", String(this.loadingStatus));
			loader.setAttribute("aria-live", "assertive");
			loader.setAttribute("icon-description", String(this.loadingIconDescription));
			loader.textContent = this.loadingDescription;
			primaryButton.style.display = "none";
			if (secondaryButtons[0]) if (!footer.hasAttribute("has-three-buttons")) secondaryButtons[0].setAttribute("disabled", "");
			else secondaryButtons.forEach((b) => b.removeAttribute("disabled"));
			if (this.loadingStatus === "finished") setTimeout(() => {
				this.dispatchEvent(new CustomEvent(this.constructor.eventOnLoadingSuccess, {
					bubbles: true,
					cancelable: true,
					composed: true
				}));
			}, this.loadingSuccessDelay);
		} else if (this.loadingStatus === "inactive") {
			loader.style.display = "none";
			loader.setAttribute("aria-live", "off");
			if (primaryButton) primaryButton.style.display = "";
			if (secondaryButtons) secondaryButtons.forEach((b) => b.removeAttribute("disabled"));
		}
	}
	async firstUpdated() {
		if (!this.querySelector(this.constructor.selectorModalBody)) {
			const bodyElement = document.createElement(this.constructor.selectorModalBody);
			this.appendChild(bodyElement);
		}
	}
	/**
	* Computes the aria-label of the modal based on (in order of highest to lowest precedence):
	* - `modal-label`
	* - `aria-label`
	* - `modal-heading`
	*/
	_computeAriaLabel() {
		const label = this.querySelector(`cds-modal-label`)?.textContent?.trim();
		if (label) return label;
		const ariaLabel = this.ariaLabel?.trim();
		if (ariaLabel) return ariaLabel;
		return this.querySelector(`cds-modal-heading`)?.textContent?.trim() || "";
	}
	/**
	* Observes the modal footer's `has-three-buttons` attribute to account for cases
	* where the loading status and the amount of footer-buttons
	* are being changed dynamically
	*/
	_observeFooter() {
		const footer = this.querySelector(`cds-modal-footer`);
		if (!footer) return;
		this._footerObserver = new MutationObserver(() => {
			this._updateLoadingElement();
		});
		this._footerObserver.observe(footer, {
			attributes: true,
			childList: true,
			attributeFilter: ["has-three-buttons"]
		});
	}
	/**
	* Observes the modal header to account for cases where the modal-heading,
	* modal-label, and/or `aria-label` are dynamically changing
	* to update the `aria-label` put on the modal
	*/
	_observeHeader() {
		const header = this.querySelector(`cds-modal-header`);
		if (!header) return;
		this._headerObserver = new MutationObserver(() => {
			this.requestUpdate("ariaLabel");
		});
		this._headerObserver.observe(header, {
			subtree: true,
			characterData: true,
			childList: true
		});
	}
	connectedCallback() {
		super.connectedCallback?.();
		this._observeFooter();
		this._observeHeader();
	}
	disconnectedCallback() {
		this._footerObserver?.disconnect();
		this._headerObserver?.disconnect();
		super.disconnectedCallback?.();
	}
	render() {
		const { alert, size, hasScrollingContent } = this;
		const containerClass = this.containerClass.split(" ").filter(Boolean).reduce((acc, item) => ({
			...acc,
			[item]: true
		}), {});
		const containerClasses = classMap({
			[`cds--modal-container`]: true,
			[`cds--modal-container--${size}`]: size,
			...containerClass
		});
		return html`
      <div
        aria-label=${this._computeAriaLabel()}
        part="dialog"
        class=${containerClasses}
        aria-modal=${true}
        role="${alert ? "alert" : "dialog"}"
        tabindex="-1"
        @click=${this._handleClickContainer}>
        <slot @slotchange="${this._handleSlotChange}"></slot>
        ${hasScrollingContent ? html` <div class="cds--modal-content--overflow-indicator"></div> ` : ``}
      </div>
    `;
	}
	async updated(changedProperties) {
		if (changedProperties.has("open")) {
			if (this.open) {
				this._launcher = this.ownerDocument.activeElement;
				const primaryFocusNode = this.querySelector(this.constructor.selectorPrimaryFocus);
				await this.constructor._delay();
				if (primaryFocusNode) primaryFocusNode.focus();
				else {
					const { primaryButton, secondaryButtons } = this._getFooterElements();
					if (primaryButton) if (primaryButton?.getAttribute("kind") === "danger" && secondaryButtons[0]) secondaryButtons[0].focus();
					else primaryButton.focus();
					else {
						const { first } = this.getFocusable();
						first?.focus();
					}
				}
			} else if (this._launcher && typeof this._launcher.focus === "function") {
				this._launcher.focus();
				this._launcher = null;
			}
		}
		if (changedProperties.has("loadingStatus") || changedProperties.has("loadingDescription") || changedProperties.has("loadingSuccessDelay") || changedProperties.has("loadingIconDescription")) {
			await this.constructor._delay();
			this._updateLoadingElement();
		}
	}
	/**
	* @param ms The number of milliseconds.
	* @returns A promise that is resolves after the given milliseconds.
	*/
	static _delay(ms = 0) {
		return new Promise((resolve) => {
			setTimeout(resolve, ms);
		});
	}
	/**
	* A selector selecting buttons that should close this modal.
	*/
	static get selectorCloseButton() {
		return `[data-modal-close],cds-modal-close-button`;
	}
	/**
	* A selector selecting tabbable nodes.
	*/
	static get selectorTabbable() {
		return selectorTabbable;
	}
	/**
	* A selector selecting the nodes that should be focused when modal gets open.
	*/
	static get selectorPrimaryFocus() {
		return `[data-modal-primary-focus],cds-modal-footer cds-button[kind="primary"]`;
	}
	/**
	* A selector selecting the modal body component
	*/
	static get selectorModalBody() {
		return `cds-modal-body`;
	}
	/**
	* The name of the custom event fired before this modal is being closed upon a user gesture.
	* Cancellation of this event stops the user-initiated action of closing this modal.
	*/
	static get eventBeforeClose() {
		return `cds-modal-beingclosed`;
	}
	/**
	* The name of the custom event fired after this modal is closed upon a user gesture.
	*/
	static get eventClose() {
		return `cds-modal-closed`;
	}
	/**
	* The name of the custom event fired when this modal reaches a `finished` loading state
	*/
	static get eventOnLoadingSuccess() {
		return `cds-modal-on-loadingsuccess`;
	}
	static {
		this.styles = modal_default$1;
	}
};
__decorate([HostListener("click")], CDSModal.prototype, "_handleClick", void 0);
__decorate([HostListener("keydown")], CDSModal.prototype, "_handleHostKeydown", void 0);
__decorate([HostListener("document:keydown")], CDSModal.prototype, "_handleKeydown", void 0);
__decorate([property({
	type: Boolean,
	reflect: true
})], CDSModal.prototype, "alert", void 0);
__decorate([property({ attribute: "aria-label" })], CDSModal.prototype, "ariaLabel", void 0);
__decorate([property({ attribute: "container-class" })], CDSModal.prototype, "containerClass", void 0);
__decorate([property({
	type: Boolean,
	reflect: true,
	attribute: "full-width"
})], CDSModal.prototype, "fullWidth", void 0);
__decorate([property({
	type: Boolean,
	reflect: true,
	attribute: "has-scrolling-content"
})], CDSModal.prototype, "hasScrollingContent", void 0);
__decorate([property({
	type: Boolean,
	reflect: true
})], CDSModal.prototype, "open", void 0);
__decorate([property({ reflect: true })], CDSModal.prototype, "size", void 0);
__decorate([property({
	type: Boolean,
	attribute: "prevent-close-on-click-outside"
})], CDSModal.prototype, "preventCloseOnClickOutside", void 0);
__decorate([property({
	reflect: true,
	attribute: "loading-status"
})], CDSModal.prototype, "loadingStatus", void 0);
__decorate([property({
	type: String,
	attribute: "loading-description"
})], CDSModal.prototype, "loadingDescription", void 0);
__decorate([property({
	type: Number,
	attribute: "loading-success-delay"
})], CDSModal.prototype, "loadingSuccessDelay", void 0);
__decorate([property({
	type: String,
	attribute: "loading-icon-description"
})], CDSModal.prototype, "loadingIconDescription", void 0);
__decorate([property({
	type: Boolean,
	reflect: true,
	attribute: "should-submit-on-enter"
})], CDSModal.prototype, "shouldSubmitOnEnter", void 0);
__decorate([property({
	type: Boolean,
	attribute: "prevent-close"
})], CDSModal.prototype, "preventClose", void 0);
CDSModal = __decorate([carbonElement(`cds-modal`)], CDSModal);
var modal_default = CDSModal;
//#endregion
export { MODAL_SIZE, modal_default as default };

//# sourceMappingURL=modal.js.map