var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { assertExists, Slot } from '@blockeditor/global/utils';
import { autoUpdate, computePosition, } from '@floating-ui/dom';
import { html, LitElement, render, } from 'lit';
import { customElement, property } from 'lit/decorators.js';
/**
 * Renders a template into a portal. Defaults to `document.body`.
 *
 * Note that every time the parent component re-renders, the portal will be re-called.
 *
 * See https://lit.dev/docs/components/rendering/#writing-a-good-render()-method
 *
 * @example
 * ```ts
 * render() {
 *   return html`${showPortal
 *     ? html`<blockeditor-portal .template=${portalTemplate}></blockeditor-portal>`
 *     : null}`;
 * };
 * ```
 */
let Portal = class Portal extends LitElement {
    constructor() {
        super(...arguments);
        this.container = document.body;
        this.template = html ``;
        this.shadowDom = true;
        this._portalRoot = null;
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this._portalRoot?.remove();
    }
    createRenderRoot() {
        const portalRoot = document.createElement('div');
        const renderRoot = this.shadowDom
            ? portalRoot.attachShadow({
                mode: 'open',
                ...(typeof this.shadowDom !== 'boolean' ? this.shadowDom : {}),
            })
            : portalRoot;
        portalRoot.classList.add('blockeditor-portal', 'blockeditor-overlay');
        this.container.append(portalRoot);
        this._portalRoot = portalRoot;
        return renderRoot;
    }
    render() {
        return this.template;
    }
};
__decorate([
    property({ attribute: false })
], Portal.prototype, "container", void 0);
__decorate([
    property({ attribute: false })
], Portal.prototype, "template", void 0);
__decorate([
    property({ attribute: false })
], Portal.prototype, "shadowDom", void 0);
Portal = __decorate([
    customElement('blockeditor-portal')
], Portal);
export { Portal };
/**
 * Similar to `<blockeditor-portal>`, but only renders once when called.
 *
 * The template should be a **static** template since it will not be re-rendered unless `updatePortal` is called.
 *
 * See {@link Portal} for more details.
 */
export function createSimplePortal({ template, container = document.body, signal = new AbortController().signal, renderOptions, shadowDom = true, identifyWrapper = true, }) {
    const portalRoot = document.createElement('div');
    if (identifyWrapper) {
        portalRoot.classList.add('blockeditor-portal', 'blockeditor-overlay');
    }
    if (shadowDom) {
        portalRoot.attachShadow({
            mode: 'open',
            ...(typeof shadowDom !== 'boolean' ? shadowDom : {}),
        });
    }
    signal.addEventListener('abort', () => {
        portalRoot.remove();
    });
    const root = shadowDom ? portalRoot.shadowRoot : portalRoot;
    assertExists(root);
    let updateId = 0;
    const updatePortal = id => {
        if (id !== updateId) {
            console.warn('Potentially infinite recursion! Please clean up the old event listeners before `updatePortal`');
            return;
        }
        updateId++;
        const curId = updateId;
        const templateResult = template instanceof Function
            ? template({ updatePortal: () => updatePortal(curId) })
            : template;
        assertExists(templateResult);
        render(templateResult, root, renderOptions);
    };
    updatePortal(updateId);
    container.append(portalRoot);
    return portalRoot;
}
/**
 * Where el is the DOM element you'd like to test for visibility
 */
function isElementVisible(el) {
    // The API is not stable, so we need to check the existence of the function first
    // See also https://caniuse.com/?search=checkVisibility
    if (el.checkVisibility) {
        // See https://drafts.csswg.org/cssom-view-1/#dom-element-checkvisibility
        return el.checkVisibility();
    }
    // Fallback to the old way
    // Remove this when the `checkVisibility` API is stable
    if (!el.isConnected)
        return false;
    if (el instanceof HTMLElement) {
        // See https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
        return !(el.offsetParent === null);
    }
    return true;
}
/**
 * Similar to `createSimplePortal`, but supports auto update position.
 *
 * The template should be a **static** template since it will not be re-rendered.
 *
 * See {@link createSimplePortal} for more details.
 *
 * @example
 * ```ts
 * createLitPortal({
 *   template: RenameModal({
 *     model,
 *     abortController: renameAbortController,
 *   }),
 *   computePosition: {
 *     referenceElement: anchor,
 *     placement: 'top-end',
 *     middleware: [flip(), offset(4)],
 *     autoUpdate: true,
 *   },
 *   abortController: renameAbortController,
 * });
 * ```
 */
export function createLitPortal({ computePosition: positionConfigOrFn, abortController, closeOnClickAway = false, ...portalOptions }) {
    let positionSlot = new Slot();
    const template = portalOptions.template;
    const templateWithPosition = template instanceof Function
        ? ({ updatePortal }) => {
            // We need to create a new slot for each template, otherwise the slot may be used in the old template
            positionSlot = new Slot();
            return template({ updatePortal, positionSlot });
        }
        : template;
    const portalRoot = createSimplePortal({
        ...portalOptions,
        signal: abortController.signal,
        template: templateWithPosition,
    });
    if (closeOnClickAway) {
        // Avoid triggering click away listener on initial render
        setTimeout(() => document.addEventListener('click', e => {
            if (portalRoot.contains(e.target))
                return;
            abortController.abort();
        }, {
            signal: abortController.signal,
        }));
    }
    if (!positionConfigOrFn) {
        return portalRoot;
    }
    const visibility = portalRoot.style.visibility;
    portalRoot.style.visibility = 'hidden';
    portalRoot.style.position = 'fixed';
    portalRoot.style.left = '0';
    portalRoot.style.top = '0';
    const computePositionOptions = positionConfigOrFn instanceof Function
        ? positionConfigOrFn(portalRoot)
        : positionConfigOrFn;
    const { referenceElement, ...options } = computePositionOptions;
    assertExists(referenceElement, 'referenceElement is required');
    const update = () => {
        if (computePositionOptions.abortWhenRefRemoved !== false &&
            referenceElement instanceof Element &&
            !isElementVisible(referenceElement)) {
            abortController.abort();
        }
        computePosition(referenceElement, portalRoot, options)
            .then(positionReturn => {
            const { x, y } = positionReturn;
            // Use transform maybe cause overlay-mask offset issue
            // portalRoot.style.transform = `translate(${x}px, ${y}px)`;
            portalRoot.style.left = `${x}px`;
            portalRoot.style.top = `${y}px`;
            if (portalRoot.style.visibility === 'hidden') {
                portalRoot.style.visibility = visibility;
            }
            positionSlot.emit(positionReturn);
        })
            .catch(console.error);
    };
    if (!computePositionOptions.autoUpdate) {
        update();
    }
    else {
        const autoUpdateOptions = computePositionOptions.autoUpdate === true
            ? {}
            : computePositionOptions.autoUpdate;
        const cleanup = autoUpdate(referenceElement, portalRoot, update, autoUpdateOptions);
        abortController.signal.addEventListener('abort', () => {
            cleanup();
        });
    }
    return portalRoot;
}
//# sourceMappingURL=portal.js.map