import { __decorate } from "tslib";
import { attr } from '@ni/fast-element';
import { applyMixins, ARIAGlobalStatesAndProperties, DesignSystem, FoundationElement } from '@ni/fast-foundation';
import { eventAnimationEnd } from '@ni/fast-web-utilities';
import { UserDismissed } from '../patterns/dialog/types';
import { styles } from './styles';
import { template } from './template';
import { DrawerLocation } from './types';
export { UserDismissed };
/**
 * Drawer control. Shows content in a panel on the left / right side of the screen,
 * which animates to be visible with a slide-in / slide-out animation.
 */
export class Drawer extends FoundationElement {
    constructor() {
        super(...arguments);
        this.location = DrawerLocation.right;
        this.preventDismiss = false;
        this.closing = false;
        this.animationEndHandlerFunction = () => this.animationEndHandler();
    }
    /**
     * True if the drawer is open, opening, or closing. Otherwise, false.
     */
    get open() {
        return this.resolveShow !== undefined;
    }
    /**
     * Opens the drawer
     * @returns Promise that is resolved when the drawer finishes closing. The value of the resolved
     * Promise is the reason value passed to the close() method, or UserDismissed if the drawer was
     * closed via the ESC key.
     */
    async show() {
        if (this.open) {
            throw new Error('Drawer is already open');
        }
        this.openDialog();
        return await new Promise((resolve, _reject) => {
            this.resolveShow = resolve;
        });
    }
    /**
     * Closes the drawer
     * @param reason An optional value indicating how/why the drawer was closed.
     */
    close(reason) {
        if (!this.open || this.closing) {
            throw new Error('Drawer is not open or already closing');
        }
        this.closeReason = reason;
        this.closeDialog();
    }
    /**
     * @internal
     */
    cancelHandler(event) {
        // Allowing the dialog to close itself bypasses the drawer's animation logic, so we
        // should close the drawer ourselves when preventDismiss is false.
        event.preventDefault();
        if (!this.preventDismiss) {
            this.closeReason = UserDismissed;
            this.closeDialog();
        }
        return true;
    }
    /**
     * @internal
     */
    closeHandler(event) {
        if (event.target !== this.dialog) {
            return;
        }
        if (this.resolveShow) {
            // If
            // - the browser implements dialogs with the CloseWatcher API, and
            // - the user presses ESC without first interacting with the drawer (e.g. clicking, scrolling),
            // the cancel event is not fired, but the close event still is, and the drawer just closes.
            // The animation is never started, so there is no animation end listener to clean up.
            this.doResolveShow(UserDismissed);
        }
    }
    doResolveShow(reason) {
        if (!this.resolveShow) {
            throw new Error('Do not call doResolveShow unless there is a promise to resolve');
        }
        this.resolveShow(reason);
        this.resolveShow = undefined;
    }
    openDialog() {
        this.dialog.showModal();
        this.triggerAnimation();
    }
    closeDialog() {
        this.closing = true;
        this.triggerAnimation();
    }
    triggerAnimation() {
        // Read the offsetHeight of the dialog to trigger a reflow. This guarantees that the browser
        // has processed the 'animating' class being removed before trying to readd it, even if the previous
        // animation has just finished. Otherwise, problems can occur. For example, trying to close the
        // drawer immediately after the opening animation ends does not actually close the drawer.
        // https://github.com/ni/nimble/issues/1994
        void this.dialog.offsetHeight;
        this.dialog.classList.add('animating');
        if (this.closing) {
            this.dialog.classList.add('closing');
        }
        this.dialog.addEventListener(eventAnimationEnd, this.animationEndHandlerFunction);
    }
    animationEndHandler() {
        this.dialog.removeEventListener(eventAnimationEnd, this.animationEndHandlerFunction);
        this.dialog.classList.remove('animating');
        if (this.closing) {
            this.dialog.classList.remove('closing');
            this.dialog.close();
            this.closing = false;
            this.doResolveShow(this.closeReason);
        }
    }
}
// We want the member to match the name of the constant
// eslint-disable-next-line @typescript-eslint/naming-convention
Drawer.UserDismissed = UserDismissed;
__decorate([
    attr
], Drawer.prototype, "location", void 0);
__decorate([
    attr({ attribute: 'prevent-dismiss', mode: 'boolean' })
], Drawer.prototype, "preventDismiss", void 0);
applyMixins(Drawer, ARIAGlobalStatesAndProperties);
const nimbleDrawer = Drawer.compose({
    baseName: 'drawer',
    template,
    styles
});
DesignSystem.getOrCreate().withPrefix('nimble').register(nimbleDrawer());
export const drawerTag = 'nimble-drawer';
//# sourceMappingURL=index.js.map