"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Component = exports.defaultFocusLookup = void 0;
const NOOP_1 = require("../NOOP");
const proxyWrap_1 = require("../proxyWrap");
const UCGEE_1 = require("../types/interface/UCGEE");
const array_1 = require("../util/array");
const callAll_1 = require("../util/call/callAll");
const control_1 = require("../util/control");
const element_1 = require("../util/element");
const object_1 = require("../util/object");
const weakMerge_1 = require("../weakMerge");
const DEFAULT_FONT_SIZE_1 = require("./DEFAULT_FONT_SIZE");
const activeElement_1 = require("./activeElement");
const addListener_1 = require("./addListener");
const animateSimulate_1 = require("./animation/animateSimulate");
const attr_1 = require("./attr");
const color_1 = require("./color");
const ANIMATION_PROPERTY_DELTA_PAIRS_1 = require("./component/app/graph/ANIMATION_PROPERTY_DELTA_PAIRS");
const namespaceURI_1 = require("./component/namespaceURI");
const componentFromSpecId_1 = require("./componentFromSpecId");
const context_1 = require("./context");
const drag_1 = require("./drag");
const custom_1 = require("./event/custom");
const extractTrait_1 = require("./extractTrait");
const interface_1 = require("./interface");
const isHTML_1 = require("./isHTML");
const isSVG_1 = require("./isSVG");
const makeEventListener_1 = require("./makeEventListener");
const mount_1 = require("./mount");
const parseLayoutValue_1 = require("./parseLayoutValue");
const rawExtractStyle_1 = require("./rawExtractStyle");
const stopPropagation_1 = require("./stopPropagation");
const style_1 = require("./style");
const textContent_1 = require("./textContent");
const unmount_1 = require("./unmount");
const geometry_1 = require("./util/geometry");
const getFontSize_1 = require("./util/style/getFontSize");
const getOpacity_1 = require("./util/style/getOpacity");
const getPosition_1 = require("./util/style/getPosition");
const getRect_1 = require("./util/style/getRect");
const getScale_1 = require("./util/style/getScale");
const getSize_1 = require("./util/style/getSize");
const getTextAlign_1 = require("./util/style/getTextAlign");
const getTextRect_1 = require("./util/web/getTextRect");
const $childToComponent = (system, { bundle }) => {
    const { unit, specs = {} } = bundle;
    const { id, memory = { input: {} } } = unit;
    const props = {};
    (0, object_1.forEachObjKV)(memory.input ?? {}, (pinId, { _data }) => {
        props[pinId] = _data;
    });
    const component = (0, componentFromSpecId_1.componentFromSpecId)(system, (0, weakMerge_1.weakMerge)(specs, system.specs), id, props);
    return component;
};
const defaultFocusLookup = (component, options = { preventScroll: true }) => {
    if (component.$root.length > 0) {
        component.$root[0].focus(options);
    }
    if (component.$parentRoot.length > 0) {
        component.$parentRoot[0].focus(options);
    }
    if (component.$parentChildren.length > 0) {
        component.$parentChildren[0].focus(options);
    }
    if (component.$children.length > 0) {
        component.$children[0].focus(options);
    }
};
exports.defaultFocusLookup = defaultFocusLookup;
class Component {
    get $context() {
        return this._$context ?? this.$system.context[0];
    }
    get $disabled() {
        return this._$disabled || this.$slotParent?.$disabled;
    }
    constructor($props, $system, $element, $node) {
        this.$_ = [];
        this.$changed = new Set();
        this.$ref = {};
        this.$connected = false;
        this.$primitive = true;
        this.$propUnlisten = {};
        this.$unbundled = true;
        this.$controlled = false;
        this._$disabled = false;
        this.$children = [];
        this.$slotChildren = { default: [] };
        this.$slotParentChildren = { default: [] };
        this.$slotParentChildOffset = { default: [] };
        this.$childSlotName = [];
        this.$remoteChildren = [];
        this.$domChildren = [];
        this.$slot = { default: this };
        this.$slotId = {};
        this.$slotTarget = {};
        this.$subComponent = {};
        this.$root = [];
        this.$wrap = false;
        this.$output = {};
        this.$parentRoot = [];
        this.$parentRootSlotName = [];
        this.$parentChildren = [];
        this.$parentChildrenSlot = [];
        this.$mountRoot = [];
        this.$mountParentRoot = [];
        this.$mountParentChildren = [];
        this.$rootParent = null;
        this.$parent = null;
        this.$slotParent = null;
        this.$slotParentSlotName = null;
        this.$slotParentSlotIndex = null;
        this.$domParent = null;
        this.$detachedContext = null;
        this.$detachedSlotParent = null;
        this.$attachedChildren = [];
        this.$hostSlot = null;
        this.$returnIndex = 0;
        this.$mounted = false;
        this.$detached = false;
        this.$moving = false;
        this._preventDefaultCounter = {};
        this._prop_transformer = {};
        this._attachTextUnlisten = {};
        this._addBaseListener = (event, listener, opt) => {
            const roots = this.getRootLeaves();
            const allUnlisten = [];
            for (const root of roots) {
                root.$element.addEventListener(event, listener, opt);
                allUnlisten.push(() => {
                    root.$element.removeEventListener(event, listener);
                });
            }
            return (0, callAll_1.callAll)(allUnlisten);
        };
        this._releasePointerCapture = {};
        this._animateBase = (leaves, slot, prepend = false, commit) => {
            const { foreground: { html }, api: { window: { HTMLElement }, text: { measureText }, document: { createElement }, layout: { reflectTreeTrait }, }, } = this.$system;
            let target = slot;
            while (target.isParent() ||
                (target.$element instanceof HTMLElement && target.$element).style
                    .display === 'contents') {
                target = target.$slotParent;
            }
            let trait = (0, extractTrait_1.extractTrait)(target, measureText);
            let style = (0, rawExtractStyle_1.rawExtractStyle)(target.$element, trait, measureText);
            let attr = (0, attr_1.extractAttr)(target.$element);
            // delete style['transform']
            delete style['opacity'];
            const tree = {
                value: {
                    name: 'div',
                    style: {
                        ...style,
                        width: '100%',
                        height: '100%',
                    },
                    attr,
                    textContent: '',
                },
                children: leaves.map((leaf) => ({
                    value: {
                        name: leaf.$element.nodeName,
                        attr: (0, attr_1.extractAttr)(leaf.$element),
                        style: (0, rawExtractStyle_1.rawExtractStyle)(leaf.$element, trait, measureText),
                        textContent: (0, textContent_1.extractTextContent)(leaf),
                    },
                    children: [],
                })),
            };
            reflectTreeTrait(this.$system, trait, [tree], () => {
                return [];
            });
            let targetTraits = tree.children.map((child) => child.value.trait);
            let finished = new Set();
            let frames = [];
            const allStop = [];
            let j = 0;
            for (let i = 0; i < leaves.length; i++) {
                const leaf = leaves[i];
                const leafTrait = (0, extractTrait_1.extractTrait)(leaf, measureText);
                const frame = createElement('div');
                (0, style_1.applyStyle)(frame, {
                    position: 'absolute',
                    left: `${leafTrait.x}px`,
                    top: `${leafTrait.y}px`,
                    width: `${leafTrait.width}px`,
                    height: `${leafTrait.height}px`,
                    pointerEvents: 'none',
                    zIndex: '0',
                    // border: `1px solid ${randomColorString()}`,
                });
                if (prepend) {
                    target.$element.prepend(frame);
                }
                else {
                    target.$element.appendChild(frame);
                }
                leaf.unmount();
                frame.appendChild(leaf.$element);
                leaf.setSlotParent(target, 'default', 0);
                leaf.mount(target.$context);
                frames.push(frame);
                const { x: scrollX0, y: scrollY0 } = (0, getPosition_1.getScrollPosition)(target.$element, target.$context.$element);
                const stop = (0, animateSimulate_1.animateSimulate)(this.$system, leafTrait, () => {
                    const targetTrait = targetTraits[i];
                    return targetTrait;
                }, ANIMATION_PROPERTY_DELTA_PAIRS_1.ANIMATION_PROPERTY_DELTA_PAIRS, ({ x, y, width, height, sx, sy, opacity, fontSize }) => {
                    if (j % leaves.length === 0) {
                        trait = (0, extractTrait_1.extractTrait)(target, measureText);
                        reflectTreeTrait(this.$system, trait, [tree], () => {
                            return [];
                        });
                        targetTraits = tree.children.map((child) => child.value.trait);
                        j = 0;
                    }
                    j++;
                    const { x: scrollX, y: scrollY } = (0, getPosition_1.getScrollPosition)(target.$element, target.$context.$element);
                    const scrollDx = scrollX - scrollX0;
                    const scrollDy = scrollY - scrollY0;
                    frame.style.left = `${x - trait.x + ((Math.abs(sx) - 1) * width) / 2 - scrollDx}px`;
                    frame.style.top = `${y - trait.y + ((Math.abs(sy) - 1) * height) / 2 - scrollDy}px`;
                    frame.style.width = `${width}px`;
                    frame.style.height = `${height}px`;
                    frame.style.transform = `scale(${sx}, ${sy})`;
                    frame.style.opacity = `1`;
                    frame.style.fontSize = `${fontSize}px`;
                }, () => {
                    if (finished.has(i)) {
                        return;
                    }
                    finished.add(i);
                    if (finished.size === leaves.length) {
                        stopAll();
                        for (let i = 0; i < leaves.length; i++) {
                            const frame = frames[i];
                            const leaf = leaves[i];
                            frame.removeChild(leaf.$element);
                            target.$element.removeChild(frame);
                        }
                        commit();
                    }
                });
                allStop.push(stop);
            }
            const stopAll = (0, callAll_1.callAll)(allStop);
            return stopAll;
        };
        this.templateLeaves = (leaves, root, parentRoot, self) => {
            for (const leaf of leaves) {
                this.templateLeaf(leaf, root, parentRoot, self);
            }
        };
        this.templateLeaf = (leaf, root, parentRoot, self) => {
            const leafParent = leaf.$parent;
            if (leafParent === leaf) {
                self(leaf);
            }
            else {
                if (leaf.$parent) {
                    const subComponentId = leaf.$parent.getSubComponentId(leaf);
                    const parentId = leafParent.getSubComponentParentId(subComponentId);
                    if (parentId) {
                        const parent = leafParent.getSubComponent(parentId);
                        parentRoot(parent, leaf);
                    }
                    else {
                        root(leafParent, leaf);
                    }
                }
                else {
                    //
                }
            }
        };
        this.domRemoveLeaves = (leaves) => {
            this.templateLeaves(leaves, (parent, leafComp) => {
                const at = parent.$root.indexOf(leafComp);
                parent.domRemoveRoot(leafComp, at);
            }, (parent, leafComp) => {
                const index = parent.$parentRoot.indexOf(leafComp);
                parent.domRemoveParentRootAt(leafComp, 'default', index);
            }, (leafComp) => {
                //
            });
        };
        this.domRemoveLeaf = (leaf) => {
            this.templateLeaf(leaf, (parent, leafComp) => {
                const at = parent.$root.indexOf(leafComp);
                parent.domRemoveRoot(leafComp, at);
            }, (parent, leafComp) => {
                const at = parent.$parentRoot.indexOf(leafComp);
                parent.domRemoveParentRootAt(leafComp, 'default', at);
            }, () => {
                //
            });
        };
        this.domAppendBase = (leaves) => {
            this.templateLeaves(leaves, (parent, leafComp) => {
                parent.domAppendRoot(leafComp, this.$root.length - 1, this.$root.length - 1);
            }, (parent, leafComp) => {
                const index = parent.$parentRoot.indexOf(leafComp);
                parent.domAppendParentRoot(leafComp, 'default', index);
            }, () => {
                //
            });
        };
        this.$animationCount = 0;
        this._svg_wrapper = [];
        this._svg_wrapper_mirror = [];
        this._svg_wrapper_element = [];
        this._html_wrapper = [];
        this.$listener = [];
        this.$unlisten = [];
        this.$named_listener_count = {};
        this.$named_unlisten = {};
        this._addUnitEventListener = (event) => {
            if (event.startsWith('_')) {
                const _event = event.slice(1);
                const unlisten = this.addEventListener((0, custom_1.makeCustomListener)(_event, (data) => {
                    this.$unit.$emit({ event, data }, NOOP_1.NOOP);
                }));
                this.$named_unlisten[event] = unlisten;
            }
            else {
                const unlisten = this.addEventListener((0, makeEventListener_1.makeUIEventListener)(event, (data) => {
                    this.$unit.$emit({ event: event, data }, NOOP_1.NOOP);
                }));
                this.$named_unlisten[event] = unlisten;
            }
        };
        this._removeUnitEventListener = (event) => {
            const unlisten = this.$named_unlisten[event];
            delete this.$named_unlisten[event];
            unlisten();
        };
        this._instanceChild = ($child, at) => {
            const component = $childToComponent(this.$system, $child);
            const __ = (0, interface_1.getComponentInterface)(component);
            const $childRef = this.$unit.$refChild({ at, __ });
            component.connect($childRef);
            return component;
        };
        this.$control = ($emitter) => {
            return (0, callAll_1.callAll)([
                $emitter.$addListener({ event: 'set_sub_component' }, ([{ subComponentId, bundle }, path = []]) => {
                    if (!this.$controlled) {
                        const child = $childToComponent(this.$system, { bundle });
                        const __ = (0, interface_1.getComponentInterface)(child);
                        const $subComponent = this.$unit.$refUnit({
                            unitId: subComponentId,
                            __,
                        });
                        child.connect($subComponent);
                        this.setSubComponent(subComponentId, child);
                    }
                }),
                $emitter.$addListener({ event: 'register_root' }, ([{ subComponentId }, path = []]) => {
                    if (!this.$controlled) {
                        const subComponent = this.$subComponent[subComponentId];
                        if (subComponent) {
                            if (subComponent.$rootParent &&
                                subComponent.$rootParent.$mountParentRoot.includes(subComponent)) {
                                subComponent.$rootParent.removeParentRoot(subComponent);
                            }
                            if (!this.hasRoot(subComponent)) {
                                this.registerRoot(subComponent);
                            }
                        }
                    }
                }),
                $emitter.$addListener({ event: 'unregister_root' }, ([{ subComponentId }, path = []]) => {
                    if (!this.$controlled) {
                        const subComponent = this.$subComponent[subComponentId];
                        if (subComponent) {
                            for (const parentRoot of [...subComponent.$parentRoot]) {
                                subComponent.unregisterParentRoot(parentRoot);
                                this.registerRoot(parentRoot);
                            }
                            if (this.hasRoot(subComponent)) {
                                this.unregisterRoot(subComponent);
                            }
                        }
                    }
                }),
                $emitter.$addListener({ event: 'register_parent_root' }, ([{ component, subComponentId, slotName }, path = []]) => {
                    if (!this.$controlled && !this.$parent.$controlled) {
                        if (path.length > 0) {
                            return;
                        }
                        const { globalId } = component;
                        const components = this.$system.getLocalComponents(globalId);
                        if (!components.length) {
                            return;
                        }
                        const subComponent = this.$parent.$subComponent[subComponentId];
                        if (subComponent) {
                            if (subComponent.$rootParent &&
                                subComponent.$rootParent.$mountParentRoot.includes(subComponent)) {
                                subComponent.$rootParent.removeParentRoot(subComponent);
                            }
                            else if (subComponent.$parent &&
                                subComponent.$parent.$mountRoot.includes(subComponent)) {
                                subComponent.$parent.removeRoot(subComponent);
                            }
                            if (!this.hasParentRoot(subComponent)) {
                                this.registerParentRoot(subComponent, slotName);
                            }
                        }
                    }
                }),
                $emitter.$addListener({ event: 'unregister_parent_root' }, ([{ component }, path = []]) => {
                    if (!this.$controlled) {
                        if (path.length > 0) {
                            return;
                        }
                        const { globalId } = component;
                        const components = this.$system.getLocalComponents(globalId);
                        for (const component of components) {
                            if (this.hasParentRoot(component)) {
                                this.unregisterParentRoot(component);
                                break;
                            }
                        }
                    }
                }),
                $emitter.$addListener({ event: 'reorder_sub_component' }, ([{ parentId, childId, to }, path = []]) => {
                    if (path.length > 0) {
                        return;
                    }
                    if (!this.$controlled) {
                        this.reorderSubComponent(parentId, childId, to);
                    }
                }),
                $emitter.$addListener({ event: 'move_sub_component_root' }, ([{ parentId, prevParentMap, children, index, slotMap, prevSlotMap }, path = [],]) => {
                    if (!this.$controlled) {
                        if (path.length > 0) {
                            return;
                        }
                        for (const childId of children) {
                            const child = this.getSubComponent(childId);
                            const currentParentId = this.getSubComponentParentId(childId);
                            if (currentParentId) {
                                const parent = this.getSubComponent(currentParentId);
                                parent.unregisterParentRoot(child);
                            }
                            else {
                                if (this.hasRoot(child)) {
                                    this.unregisterRoot(child);
                                }
                            }
                            if (parentId) {
                                const parent = this.getSubComponent(parentId);
                                const slotName = 'default';
                                parent.registerParentRoot(child, slotName, index);
                            }
                            else {
                                if (!this.hasRoot(child)) {
                                    this.registerRoot(child, index);
                                }
                            }
                        }
                    }
                }),
            ]);
        };
        this.isParent = () => {
            const { api: { window: { HTMLElement }, }, } = this.$system;
            if (this.$element instanceof HTMLElement) {
                return !!this.$element.classList?.contains('__parent');
            }
            return false;
        };
        this._insertAt_ = this._insertAt.bind(this);
        this._removeChild_ = this._removeChild.bind(this);
        this._svg_wrapper_unlisten = [];
        this._svg_view_box_resize = (svg, mirror, $element) => {
            if (!$element.isConnected) {
                const oldViewBox = $element.getAttribute('data-viewbox');
                if (oldViewBox) {
                    svg.setAttribute('viewBox', oldViewBox);
                    return;
                }
            }
            function parseTransformMatrix(matrixString) {
                if (!matrixString || matrixString === 'none') {
                    return [1, 0, 0, 1, 0, 0];
                }
                return matrixString
                    .replace('matrix(', '')
                    .replace(')', '')
                    .split(',')
                    .map(parseFloat);
            }
            function applyMatrixToPoint(matrix, x, y) {
                const [a, b, c, d, e, f] = matrix;
                const newX = a * x + c * y + e;
                const newY = b * x + d * y + f;
                return { x: newX, y: newY };
            }
            function getTransformedRect(element, rect) {
                if (!element.isConnected) {
                    return rect;
                }
                const matrixString = getComputedStyle(element).transform;
                const matrix = parseTransformMatrix(matrixString);
                const topLeft = applyMatrixToPoint(matrix, rect.x, rect.y);
                const topRight = applyMatrixToPoint(matrix, rect.x + rect.width, rect.y);
                const bottomLeft = applyMatrixToPoint(matrix, rect.x, rect.y + rect.height);
                const bottomRight = applyMatrixToPoint(matrix, rect.x + rect.width, rect.y + rect.height);
                const xCoords = [topLeft.x, topRight.x, bottomLeft.x, bottomRight.x];
                const yCoords = [topLeft.y, topRight.y, bottomLeft.y, bottomRight.y];
                const maxX = Math.max(...xCoords);
                const maxY = Math.max(...yCoords);
                const minX = Math.min(...xCoords);
                const minY = Math.min(...yCoords);
                return {
                    x: minX,
                    y: minY,
                    width: maxX - minX,
                    height: maxY - minY,
                };
            }
            const strokeWidth = (0, parseLayoutValue_1.parseLayoutValue)($element.style.strokeWidth ??
                $element.getAttribute('stroke-width') ??
                '3px')[0] + 3;
            const rect = mirror.getBoundingClientRect();
            const tRect = getTransformedRect($element, rect);
            const { x, y, width, height } = tRect;
            const viewBox = `${x - strokeWidth} ${y - strokeWidth} ${width + 2 * strokeWidth} ${height + 2 * strokeWidth}`;
            $element.setAttribute('data-viewbox', viewBox);
            svg.setAttribute('viewBox', viewBox);
        };
        this._wrapElement = (parent, child) => {
            const { $element } = child;
            let target = $element;
            if ((0, isHTML_1.isHTML)(this.$system, parent.$element) &&
                (0, isSVG_1.isSVG)(this.$system, $element)) {
                const { api: { document: { MutationObserver }, }, } = this.$system;
                const svg = this._svgWrapper();
                child.$wrapElement = svg;
                target = svg;
                const config = {
                    childList: false,
                    subtree: false,
                    attributes: true,
                };
                const mirror = $element.cloneNode();
                const { foreground } = this.$system;
                foreground.svg.appendChild(mirror);
                mirror.style.visibility = 'hidden';
                const resize = () => {
                    this._svg_view_box_resize(svg, mirror, $element);
                };
                resize();
                const observer = new MutationObserver((records) => {
                    let tapped = new Set(['data-viewbox']);
                    for (const record of records.reverse()) {
                        const { attributeName } = record;
                        if (tapped.has(attributeName)) {
                            continue;
                        }
                        tapped.add(attributeName);
                        const value = $element.getAttribute(attributeName) ?? '';
                        mirror.setAttribute(attributeName, value);
                    }
                    mirror.style.visibility = 'hidden';
                    if (tapped.size > 1) {
                        resize();
                    }
                });
                observer.observe($element, config);
                let transitionCount = child.$animationCount;
                let cooldown = -1;
                let frame;
                let start = () => {
                    resize();
                    if (cooldown > 0) {
                        cooldown--;
                        if (cooldown === 0) {
                            return;
                        }
                    }
                    frame = requestAnimationFrame(start);
                };
                let stop = () => {
                    cancelAnimationFrame(frame);
                };
                const onTransitionStart = (event) => {
                    transitionCount++;
                    if (transitionCount === 1) {
                        cooldown = -1;
                        start();
                    }
                };
                const onTransitionEnd = (event) => {
                    transitionCount--;
                    if (transitionCount === 0) {
                        cooldown = 10;
                    }
                };
                $element.addEventListener('transitionstart', onTransitionStart);
                $element.addEventListener('transitionend', onTransitionEnd);
                $element.addEventListener('animationstart', onTransitionStart);
                $element.addEventListener('animationstend', onTransitionEnd);
                this._svg_wrapper.push(target);
                this._svg_wrapper_element.push($element);
                this._svg_wrapper_mirror.push(mirror);
                this._svg_wrapper_unlisten.push(() => {
                    observer.disconnect();
                    stop();
                });
                target.appendChild($element);
                if (transitionCount > 0) {
                    start();
                }
            }
            else if (((0, isSVG_1.isSVG)(this.$system, parent.$element) ||
                (0, isSVG_1.isSVGSVG)(this.$system, parent.$element)) &&
                (0, isHTML_1.isHTML)(this.$system, $element)) {
                target = this._htmlWrapper();
                child.$wrapElement = target;
                this._html_wrapper.push(target);
                target.appendChild($element);
            }
            return target;
        };
        this._unwrapElement = (parent, child) => {
            const { $element, $wrapElement } = child;
            let target = $element;
            if ((0, isHTML_1.isHTML)(this.$system, parent) && (0, isSVG_1.isSVG)(this.$system, $element)) {
                const at = this._svg_wrapper.findIndex((wrapper) => wrapper === $wrapElement);
                target = this._svg_wrapper[at];
                this._svg_wrapper.splice(at, 1);
                this._svg_wrapper_mirror.splice(at, 1);
                this._svg_wrapper_element.splice(at, 1);
                this._svg_wrapper_unlisten[at]();
                this._svg_wrapper_unlisten.splice(at, 1);
                target.removeChild($element);
            }
            else if (((0, isSVG_1.isSVG)(this.$system, parent) || (0, isSVG_1.isSVGSVG)(this.$system, parent)) &&
                (0, isHTML_1.isHTML)(this.$system, $element)) {
                const at = this._html_wrapper.findIndex((wrapper) => wrapper === $wrapElement);
                target = this._html_wrapper[at];
                this._html_wrapper.splice(at, 1);
                target.removeChild($element);
            }
            return target;
        };
        this._incrementParentChildOffset = (slotName, at) => {
            if (this.$slotParentChildOffset[slotName][at] === undefined) {
                this.$slotParentChildOffset[slotName][at] =
                    this.$slotParentChildOffset[slotName][at - 1] ?? -1;
            }
            for (let i = at; i < this.$slotParentChildOffset[slotName].length; i++) {
                this.$slotParentChildOffset[slotName][i] =
                    (this.$slotParentChildOffset[slotName][i] ?? -1) + 1;
            }
        };
        this._decrementParentChildOffset = (slotName, at) => {
            for (let i = at; i < this.$slotParentChildOffset[slotName].length; i++) {
                this.$slotParentChildOffset[slotName][i] = Math.max(this.$slotParentChildOffset[slotName][i] - 1, -1);
            }
            if (this.$slotParentChildOffset[slotName][at] < at) {
                this.$slotParentChildOffset[slotName].splice(at, 1);
            }
        };
        this.addEventListener = (listener) => {
            const roots = this.getRootLeaves();
            const allUnlisten = [];
            for (const root of roots) {
                const unlisten = listener(root);
                allUnlisten.push(unlisten);
            }
            return (0, callAll_1.callAll)(allUnlisten);
        };
        this.addEventListeners = (listeners) => {
            const roots = this.getRootLeaves();
            const allUnlisten = [];
            for (const root of roots) {
                const unlisten = (0, addListener_1.addListeners)(root, listeners);
                allUnlisten.push(unlisten);
            }
            return (0, callAll_1.callAll)(allUnlisten);
        };
        this.$props = $props;
        this.$system = $system;
        this.$element = $element;
        this._$node = $node ?? $element;
    }
    get $node() {
        return this._$node ?? this.$element;
    }
    set $node($node) {
        this._$node = $node;
    }
    getProp(name) {
        return this.$props[name];
    }
    setProp(prop, data) {
        const prev = this.$props[prop];
        this.$props[prop] = data;
        const current = this._current(prop);
        if (this.$mounted) {
            this.onPropChanged(prop, current, prev);
        }
        else {
            this.$changed.add(prop);
        }
    }
    interceptProp(prop, transformer) {
        this._prop_transformer[prop] = this._prop_transformer[prop] || [];
        this._prop_transformer[prop].push(transformer);
        return () => {
            (0, array_1.remove)(this._prop_transformer[prop], transformer);
        };
    }
    getSlot(slotName) {
        return this.$slot[slotName];
    }
    onPropChanged(prop, current, prev) { }
    refreshProp(prop) {
        this.setProp(prop, this.$props[prop]);
    }
    onConnected($unit) { }
    onDisconnected() { }
    onMount() { }
    onUnmount($context) { }
    onDestroy() {
        for (const child of this.$children) {
            child.destroy();
        }
    }
    dispatchEvent(type, detail = {}, bubbles = true, id) {
        const { cache: { events }, api: { window: { nextTick }, }, } = this.$system;
        if (id) {
            if (events[id]) {
                return;
            }
        }
        events[id] = true;
        const roots = this.getRootLeaves();
        for (const root of roots) {
            (0, context_1.dispatchCustomEvent)(root.$element, type, detail, bubbles);
        }
        if (id) {
            nextTick(() => {
                delete events[id];
            });
        }
    }
    stopPropagation(event) {
        this._stopPropagationSet = this._stopPropagationSet ?? new Set();
        if (this._stopPropagationSet.has(event)) {
            return;
        }
        this._stopPropagationSet.add(event);
        return this._addBaseListener(event, stopPropagation_1.stopPropagation, { passive: true });
    }
    cancelStopPropagation(event) {
        if (!this._stopPropagationSet.has(event)) {
            return;
        }
        this._stopPropagationSet.delete(event);
        const base = this.getRootBase();
        for (const [_, leaf] of base) {
            leaf.$element.removeEventListener(event, stopPropagation_1.stopPropagation);
        }
    }
    stopImmediatePropagation(event) {
        this._stopImmediatePropagationSet =
            this._stopImmediatePropagationSet ?? new Set();
        if (this._stopImmediatePropagationSet.has(event)) {
            return;
        }
        this._stopImmediatePropagationSet.add(event);
        this.$element.addEventListener(event, stopPropagation_1.stopImmediatePropagation, {
            passive: true,
        });
    }
    attachText(type, text) {
        if (this._attachTextUnlisten[type]) {
            this.detachText(type);
        }
        const base = this.getRootBase();
        const unlistenAll = [];
        for (const [_, leaf] of base) {
            const listener = (event) => {
                event.dataTransfer.setData(type, text);
            };
            leaf.$element.addEventListener('dragstart', listener);
            unlistenAll.push(() => {
                leaf.$element.removeEventListener('dragstart', listener);
            });
        }
        this._attachTextUnlisten[type] = (0, callAll_1.callAll)(unlistenAll);
    }
    detachText(type) {
        const unlisten = this._attachTextUnlisten[type];
        if (unlisten) {
            unlisten();
        }
    }
    attachDropTarget() {
        if (this._dropTargetUnlisten) {
            return;
        }
        const roots = this.getRootLeaves();
        const dragOverListener = (event) => {
            event.preventDefault();
        };
        const dropListener = async (event) => {
            event.preventDefault();
            const texts = await (0, drag_1.readDropEventItemsAsText)(event);
            if (this.$unit) {
                this.$unit.$emit({ event: 'drop_', data: texts }, NOOP_1.NOOP);
            }
        };
        const unlisten = [];
        for (const root of roots) {
            root.$element.addEventListener('dragover', dragOverListener);
            root.$element.addEventListener('drop', dropListener);
            unlisten.push(() => {
                root.$element.removeEventListener('dragover', dragOverListener);
                root.$element.removeEventListener('drop', dropListener);
            });
        }
        this._dropTargetUnlisten = (0, callAll_1.callAll)(unlisten);
    }
    detachDropTarget() {
        if (!this._dropTargetUnlisten) {
            return;
        }
        this._dropTargetUnlisten();
        this._dropTargetUnlisten = undefined;
    }
    preventDefault(event) {
        this._preventDefaultCounter[event] = this._preventDefaultCounter[event] ?? 0;
        let unlisten = NOOP_1.NOOP;
        const unlisten_ = () => {
            this._preventDefaultCounter[event]--;
            if (this._preventDefaultCounter[event] === 0) {
                unlisten();
            }
        };
        this._preventDefaultCounter[event]++;
        if (this._preventDefaultCounter[event] === 1) {
            const listener = (event) => {
                event.preventDefault();
                return false;
            };
            const opt = { passive: false };
            unlisten = this._addBaseListener(event, listener, opt);
        }
        return unlisten_;
    }
    hasPointerCapture(pointerId) {
        return !!this._releasePointerCapture[pointerId];
    }
    setPointerCapture(pointerId) {
        const { api: { window: { Text }, input: { pointer: { setPointerCapture }, }, }, } = this.$system;
        const leaf = this.getFirstRootLeaf();
        if (leaf.$node instanceof Text) {
            throw new Error('cannot set pointer capture on a text element');
        }
        this._releasePointerCapture[pointerId] = setPointerCapture(leaf.$node, pointerId);
    }
    releasePointerCapture(pointerId) {
        const release = this._releasePointerCapture[pointerId];
        if (release) {
            release();
            delete this._releasePointerCapture[pointerId];
        }
        else {
            // throw new Error('element is not capturing pointer')
        }
    }
    scroll(opt) {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        const leaf = this.getFirstRootLeaf();
        if (leaf && leaf.$element instanceof HTMLElement) {
            leaf.$element.scroll(opt);
        }
    }
    scrollIntoView(opt) {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        if (this.$slot['default'] === this) {
            if (this.$primitive) {
                if (this.$element instanceof HTMLElement) {
                    this.$element.scrollIntoView(opt);
                }
            }
            else {
                if (this.$root.length > 0) {
                    this.$root[0].scrollIntoView(opt);
                }
            }
        }
        else {
            this.$slot['default'].scrollIntoView(opt);
        }
    }
    togglePopover() {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        const firstLeaf = this.getFirstRootLeaf();
        if (firstLeaf.$element instanceof HTMLElement) {
            if (firstLeaf.$element.togglePopover) {
                firstLeaf.$element.togglePopover();
            }
        }
        else {
            //
        }
    }
    hidePopover() {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        const firstLeaf = this.getFirstRootLeaf();
        if (firstLeaf.$element instanceof HTMLElement) {
            if (firstLeaf.$element.hidePopover) {
                firstLeaf.$element.hidePopover();
            }
        }
        else {
            //
        }
    }
    showPopover() {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        const firstLeaf = this.getFirstRootLeaf();
        if (firstLeaf.$element instanceof HTMLElement) {
            if (firstLeaf.$element.showPopover) {
                firstLeaf.$element.showPopover();
            }
        }
        else {
            //
        }
    }
    register(remoteId) {
        if (this.$remoteId) {
            throw new Error('cannot register a registered component');
        }
        const { registerLocalComponent } = this.$system;
        this.$remoteId = remoteId;
        registerLocalComponent(this, remoteId);
    }
    unregister() {
        if (!this.$remoteId) {
            return;
        }
        const { unregisterLocalComponent } = this.$system;
        unregisterLocalComponent(this, this.$remoteId);
        this.$remoteId = undefined;
    }
    isFocusInside() {
        let activeElementInside = false;
        const activeElement = (0, activeElement_1.getActiveElement)(this.$system);
        if (activeElement) {
            activeElementInside = this.$element.contains(activeElement);
            for (const $root of this.$root) {
                activeElementInside = activeElementInside || $root.isFocusInside();
            }
        }
        return activeElementInside;
    }
    detach(host, opt) {
        const { api: { window: { HTMLElement, SVGElement }, }, } = this.$system;
        const { getLocalComponents } = this.$system;
        const { animate = false, prepend = false } = opt;
        if (this.$detached) {
            throw new Error('component is already detached');
        }
        this.$detached = true;
        const hostGlobalId = host.replace('unit://', '');
        const hosts = getLocalComponents(hostGlobalId);
        if (hosts.length === 0) {
            return;
        }
        const leaves = this.getRootLeaves();
        const alreadyAnimating = !!this._baseAnimationAbort;
        if (this._baseAnimationAbort) {
            this._baseAnimationAbort();
            this._baseAnimationAbort = undefined;
        }
        if (alreadyAnimating) {
            for (const leaf of leaves) {
                if (leaf.$hostSlot) {
                    (0, array_1.remove)(leaf.$hostSlot.$attachedChildren, leaf);
                }
                leaf.$hostSlot = null;
            }
        }
        const activeElementInside = this.isFocusInside();
        const activeElement = (0, activeElement_1.getActiveElement)(this.$system);
        const hostComponent = hosts[0];
        const hostSlot = hostComponent.getSlot('default');
        const all = [...leaves];
        for (const attachedChild of hostSlot.$attachedChildren) {
            all.unshift(attachedChild);
        }
        const commit = () => {
            for (const leaf of leaves) {
                (0, array_1.push)(hostSlot.$attachedChildren, leaf);
                leaf.$hostSlot = hostSlot;
            }
            for (let i = 0; i < all.length; i++) {
                const leaf = all[i];
                hostSlot.domCommitInsertChild(leaf, i);
            }
            if (activeElementInside) {
                if (activeElement instanceof HTMLElement ||
                    activeElement instanceof SVGElement) {
                    activeElement.focus();
                }
            }
        };
        this.$detachedSlotParent = this.$slotParent;
        this.$detachedContext = this.$context;
        let index = 0;
        if (this.$rootParent) {
            index = this.$rootParent.$parentRoot.indexOf(this);
        }
        else if (this.$parent) {
            index = this.$parent.$root.indexOf(this);
        }
        else {
            index = this.$context.$children.indexOf(this);
        }
        this.$returnIndex = index;
        if (animate) {
            this._baseAnimationAbort = this._animateBase(all, hostSlot, prepend, commit);
        }
        else {
            this.domRemoveLeaves(leaves);
            commit();
        }
    }
    attach(opt) {
        if (!this.$detached) {
            throw new Error('component is not detached');
        }
        const { animate } = opt;
        this.$detached = false;
        const alreadyAnimating = !!this._baseAnimationAbort;
        if (this._baseAnimationAbort) {
            this._baseAnimationAbort();
            this._baseAnimationAbort = undefined;
        }
        const leaves = this.getRootLeaves();
        const couple = (leaf) => {
            if (leaf.$hostSlot) {
                (0, array_1.remove)(leaf.$hostSlot.$attachedChildren, leaf);
            }
            leaf.$hostSlot = null;
            leaf.unmount();
            if (leaf.$rootParent) {
                leaf.$rootParent.domInsertParentRootAt(leaf, this.$returnIndex, 'default');
            }
            else if (leaf.$parent) {
                leaf.$parent.domInsertRootAt(leaf, this.$returnIndex);
            }
            else {
                //
            }
            leaf.mount(this.$parent.$context);
        };
        if (animate) {
            const targetSlots = [];
            this.templateLeaves(leaves, (root) => {
                targetSlots.push(root.$slot['default']);
            }, (parentRoot) => {
                targetSlots.push(parentRoot.$slot['default']);
            }, () => {
                //
            });
            const targetSlot = this.$detachedSlotParent;
            if (!alreadyAnimating) {
                for (let i = 0; i < leaves.length; i++) {
                    const leaf = leaves[i];
                    this.domRemoveLeaf(leaf);
                }
            }
            this._baseAnimationAbort = this._animateBase(leaves, targetSlot, false, () => {
                for (let i = 0; i < leaves.length; i++) {
                    const leaf = leaves[i];
                    couple(leaf);
                }
            });
        }
        else {
            for (let i = 0; i < leaves.length; i++) {
                const leaf = leaves[i];
                couple(leaf);
            }
        }
    }
    mountChild(child, commit = true) {
        child.mount(this.$context, commit);
    }
    unmountDescendent(child) {
        return child.unmount();
    }
    _forAllMountDescendent(callback) {
        for (const subComponent of this.$mountRoot) {
            callback(subComponent);
        }
        for (const subComponent of this.$mountParentChildren) {
            callback(subComponent);
        }
    }
    _forAllDescendent(callback) {
        for (const subComponent of this.$root) {
            callback(subComponent);
        }
        for (const subComponent of this.$parentChildren) {
            callback(subComponent);
        }
    }
    commit() {
        if (this.$detached) {
            return;
        }
        for (let i = 0; i < this._svg_wrapper.length; i++) {
            const svg = this._svg_wrapper[i];
            const mirror = this._svg_wrapper_mirror[i];
            const element = this._svg_wrapper_element[i];
            this._svg_view_box_resize(svg, mirror, element);
        }
        this.onMount();
        this._forAllMountDescendent((child) => {
            child.commit();
        });
        const $changed = new Set(this.$changed);
        for (const name of $changed) {
            const current = this._current(name);
            this.$changed.delete(name);
            this.onPropChanged(name, current, current);
        }
    }
    _current(name) {
        const data = this.$props[name];
        const current = (this._prop_transformer[name] || []).reduce((acc, t) => {
            return t(acc);
        }, data) ?? data;
        return current;
    }
    mount(context, commit = true) {
        if (this.$detached) {
            return;
        }
        if (this.$mounted) {
            throw new Error('cannot mount a mounted component');
        }
        this._$context = context;
        this.$mounted = true;
        this._forAllMountDescendent((child) => {
            this.mountChild(child, false);
        });
        if (commit) {
            this.commit();
        }
        if (this.isBase()) {
            this.dispatchEvent('mount', {}, false);
        }
    }
    unmount() {
        if (this.$detached) {
            return;
        }
        if (!this.$mounted) {
            throw new Error('cannot unmount unmounted component');
        }
        const _$context = this._$context;
        this._forAllMountDescendent((child) => {
            if (child.$detached) {
                return;
            }
            this.unmountDescendent(child);
        });
        this.$mounted = false;
        this._$context = null;
        this.onUnmount(_$context);
        if (this.isBase()) {
            this.dispatchEvent('unmount', {}, false);
        }
    }
    focus(options = { preventScroll: true }) {
        const { api: { window: { Text }, }, } = this.$system;
        if (this.$disabled) {
            return;
        }
        if (this.$slot['default'] === this) {
            if (this.$element instanceof Text) {
                return;
            }
            if (this.$primitive) {
                if ((0, element_1.isElementFocusable)(this.$system, this.$element)) {
                    this.$element.focus(options);
                }
                else {
                    (0, exports.defaultFocusLookup)(this, options);
                }
            }
            else {
                (0, exports.defaultFocusLookup)(this, options);
            }
        }
        else {
            this.$slot['default'].focus(options);
        }
    }
    blur() {
        const { api: { window: { Text }, }, } = this.$system;
        if (this.$slot['default'] === this) {
            if (this.$element instanceof Text) {
                return;
            }
            if (!this.$primitive) {
                let i = 0;
                while (this.$root[i] instanceof Text) {
                    i++;
                }
                if (this.$mountRoot[i]) {
                    this.$mountRoot[i].blur();
                }
            }
            else {
                this.$element.blur();
            }
        }
        else {
            this.$slot['default'].blur();
        }
    }
    deselect() {
        const { api: { selection: { removeSelection }, }, } = this.$system;
        if (this.containsSelection()) {
            removeSelection();
        }
    }
    containsSelection() {
        const { api: { selection: { containsSelection }, }, } = this.$system;
        if (!this.$primitive) {
            return this.$root.some((root) => root.containsSelection());
        }
        else {
            return containsSelection(this.$element);
        }
    }
    animate(keyframes, options) {
        const base = this.getAnimatableBase();
        const animations = [];
        for (const leaf of base) {
            const animation = leaf.$element.animate(keyframes, options);
            leaf.$element.dispatchEvent(new CustomEvent('animationstart'));
            leaf.$animationCount++;
            leaf.$element.addEventListener('end', () => {
                leaf.$animationCount--;
                leaf.$element.dispatchEvent(new CustomEvent('animationend'));
            });
            leaf.$element.addEventListener('cancel', () => {
                leaf.$animationCount--;
                leaf.$element.dispatchEvent(new CustomEvent('animationend'));
            });
            animations.push(animation);
        }
        return animations;
    }
    getAnimatableBase() {
        const { api: { window: { Text }, }, } = this.$system;
        const base = this.getRootBase();
        const root = [];
        for (const [_, leaf] of base) {
            if (leaf.$element instanceof Text) {
                continue;
            }
            root.push(leaf);
        }
        return root;
    }
    getAnimationsById(id) {
        const animations = this.getAnimations();
        const animations_ = animations.filter((a) => {
            return a.id === id;
        });
        return animations_;
    }
    getAnimations() {
        const base = this.getAnimatableBase();
        const animations = [];
        for (const leaf of base) {
            const leafAnimations = leaf.$element.getAnimations();
            for (const animation of leafAnimations) {
                animations.push(animation);
            }
        }
        return animations;
    }
    cancelAnimation(id) {
        const animations = this.getAnimationsById(id);
        for (const animation of animations) {
            animation.cancel();
        }
    }
    commitAnimation(id) {
        const animations = this.getAnimationsById(id);
        for (const animation of animations) {
            animation.commitStyles();
        }
    }
    getOffset() {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        if (this.isSVG() || this.isText()) {
            return this;
        }
        if (this.$element instanceof SVGSVGElement) {
            return this;
        }
        let offset = this;
        while (offset &&
            !(offset.$element instanceof HTMLElement &&
                offset.$element.style.display !== 'contents' &&
                (offset.$element.style.position === '' ||
                    offset.$element.style.position === 'relative' ||
                    offset.$element.style.position === 'absolute'))) {
            offset = offset.$slotParent ?? offset.$domParent;
        }
        return offset;
    }
    getElementPosition() {
        const context_position = {
            x: this.$context.$x,
            y: this.$context.$y,
        };
        if (!this.$primitive) {
            throw new Error('cannot calculate position of multiple elements');
        }
        const relative_position = (0, getPosition_1.getRelativePosition)(this.$element, this.$context.$element);
        const position = (0, geometry_1.addVector)(context_position, relative_position);
        return position;
    }
    getElementSize() {
        if (!this.$primitive) {
            throw new Error('cannot calculate size of multiple elements');
        }
        return (0, getSize_1.getSize)(this.$element);
    }
    getColor() {
        const { api: { window: { HTMLElement, SVGElement }, }, } = this.$system;
        const defaultColor = () => {
            if (this.$slotParent) {
                return this.$slotParent.getColor();
            }
            else if (this.$context) {
                return (0, color_1.hexToRgba)(this.$context.$color);
            }
            else {
                return (0, color_1.hexToRgba)(this.$system.color);
            }
        };
        if (this.$primitive) {
            if (this.$element instanceof HTMLElement ||
                this.$element instanceof SVGElement) {
                const styleColor = this.$element.style.color;
                if (styleColor === 'currentcolor') {
                    return defaultColor();
                }
                if (styleColor) {
                    const hex = (0, color_1.colorToHex)(styleColor);
                    return (hex && (0, color_1.hexToRgba)(hex)) || defaultColor();
                }
                else {
                    return defaultColor();
                }
            }
            else {
                return defaultColor();
            }
        }
        else {
            return defaultColor();
        }
    }
    getBackgroundColor() {
        const { api: { window: { HTMLElement, SVGElement, getComputedStyle }, }, } = this.$system;
        const defaultColor = () => {
            return (0, color_1.hexToRgba)(this.$system.color);
        };
        if (this.$primitive) {
            if (this.$element instanceof HTMLElement ||
                this.$element instanceof SVGElement) {
                const styleColor = getComputedStyle(this.$element).backgroundColor;
                if (styleColor === 'currentcolor') {
                    return defaultColor();
                }
                if (styleColor) {
                    const hex = (0, color_1.colorToHex)(styleColor);
                    return (hex && (0, color_1.hexToRgba)(hex)) || defaultColor();
                }
                else {
                    return defaultColor();
                }
            }
            else {
                return defaultColor();
            }
        }
        else {
            return defaultColor();
        }
    }
    getFontSize() {
        const { $width, $height } = this.$context;
        if (this.$primitive) {
            let parentFontSize = DEFAULT_FONT_SIZE_1.DEFAULT_FONT_SIZE;
            const fontSize = (0, getFontSize_1.getFontSize)(this.$element, $width, $height, parentFontSize);
            if (fontSize) {
                return fontSize;
            }
        }
        if (this.$slotParent) {
            return this.$slotParent.getFontSize();
        }
        return DEFAULT_FONT_SIZE_1.DEFAULT_FONT_SIZE;
    }
    getOpacity() {
        if (this.$primitive) {
            const opacity = (0, getOpacity_1.getOpacity)(this.$element);
            if (opacity !== undefined) {
                return opacity;
            }
        }
        if (this.$slotParent) {
            return this.$slotParent.getOpacity();
        }
        return DEFAULT_FONT_SIZE_1.DEFAULT_OPACITY;
    }
    getScale() {
        let scale = { sx: 1, sy: 1 };
        if (this.$primitive) {
            scale = (0, getScale_1.getScale)(this.$element);
        }
        if (this.$slotParent) {
            const parentScale = this.$slotParent.getScale();
            return { sx: scale.sx * parentScale.sx, sy: scale.sy * parentScale.sy };
        }
        return scale;
    }
    getTextAlign() {
        if (this.$primitive) {
            const textAlign = (0, getTextAlign_1.getTextAlign)(this.$element);
            if (textAlign) {
                return textAlign;
            }
        }
        if (this.$parent) {
            return this.$parent.getTextAlign();
        }
        return DEFAULT_FONT_SIZE_1.DEFAULT_TEXT_ALIGN;
    }
    _svgWrapper() {
        const svg = this.$system.api.document.createElementNS(namespaceURI_1.namespaceURI, 'svg');
        svg.style.display = 'block';
        svg.style.width = '100%';
        svg.style.height = '100%';
        return svg;
    }
    _htmlWrapper() {
        const foreignObject = this.$system.api.document.createElementNS(namespaceURI_1.namespaceURI, 'foreignObject');
        foreignObject.style.width = '100%';
        foreignObject.style.height = '100%';
        return foreignObject;
    }
    isSVG() {
        return (0, isSVG_1.isSVG)(this.$system, this.$element);
    }
    isHTML() {
        return (0, isHTML_1.isHTML)(this.$system, this.$element);
    }
    isText() {
        const { api: { window: { Text }, }, } = this.$system;
        return this.$element instanceof Text;
    }
    isBase() {
        return this.$primitive && !this.$wrap && this.$root.length === 0;
    }
    getRootBase(path = []) {
        if (this.$detached) {
            return [];
        }
        if (this.isBase()) {
            return [[path, this]];
        }
        let p = [];
        for (const subComponent of this.$root) {
            const subComponentId = this.getSubComponentId(subComponent);
            const subComponentRootBase = subComponent.getRootBase([
                ...path,
                subComponentId,
            ]);
            p = [...p, ...subComponentRootBase];
        }
        if (p.length === 0) {
            p = [[path, this]];
        }
        return p;
    }
    getRootLeaves(path = []) {
        if (this.isBase()) {
            return [this];
        }
        let p = [];
        for (const subComponent of this.$root) {
            const subComponentId = this.getSubComponentId(subComponent);
            const subComponentRootBase = subComponent.getRootLeaves([
                ...path,
                subComponentId,
            ]);
            p = [...p, ...subComponentRootBase];
        }
        if (p.length === 0) {
            p = [this];
        }
        return p;
    }
    getFirstRootLeaf() {
        const base = this.getRootBase();
        const [_, leaf] = base[0];
        return leaf;
    }
    getBase(path = []) {
        if (this.$detached) {
            return [];
        }
        if (this.isBase()) {
            return [[path, this]];
        }
        let p = [];
        const appendSubComponent = (subComponent) => {
            const subComponentId = this.getSubComponentId(subComponent);
            const subComponentRootBase = subComponent.getRootBase([
                ...path,
                subComponentId,
            ]);
            p = [...p, ...subComponentRootBase];
            for (const _subComponent of subComponent.$parentRoot) {
                appendSubComponent(_subComponent);
            }
        };
        for (const subComponent of this.$root) {
            appendSubComponent(subComponent);
        }
        return p;
    }
    pathGetSubComponent(path) {
        if (path.length == 0) {
            return this;
        }
        else {
            const [head, ...tail] = path;
            const subComponent = this.getSubComponent(head);
            if (!subComponent) {
                return null;
            }
            return subComponent.pathGetSubComponent(tail);
        }
    }
    getRect() {
        return (0, getRect_1.getRect)(this.$node);
    }
    getBoundingClientRect() {
        const { api: { window: { Text }, }, } = this.$system;
        if (!this.$primitive) {
            const leaves = this.getRootLeaves();
            const rects = leaves.map((leaf) => leaf.getBoundingClientRect());
            return (0, geometry_1.rectsBoundingRect)(rects);
        }
        let bcr;
        if (this.$node instanceof Text) {
            bcr = (0, getTextRect_1.getTextRect)(this.$system, this.$node);
        }
        else {
            bcr = this.$node.getBoundingClientRect();
        }
        const { $x, $y, $sx, $sy } = this.$context;
        const { x, y, width, height } = bcr;
        return {
            x: (x - $x) / $sx,
            y: (y - $y) / $sy,
            width: width / $sx,
            height: height / $sy,
        };
    }
    getSelection() {
        const { api: { document: { getSelection }, }, } = this.$system;
        const base = this.getRootBase();
        const selections = [];
        for (const [path, leaf] of base) {
            selections.push(...leaf.getSelection().map((selection) => ({
                path: [...path, ...selection.path],
                ...selection,
            })));
        }
        return selections;
    }
    setSelectionRange(start, end, direction) {
        //
    }
    appendChild(child, slotName = 'default') {
        const at = this.$children.length;
        const at_ = this.$slotChildren[slotName].length;
        this.memAppendChild(child, slotName, at);
        this.domAppendChild(child, slotName, at_);
        this.postAppendChild(child, slotName, at);
        return at;
    }
    insertChild(child, slotName = 'default', at) {
        this.memInsertChild(child, slotName, at);
        this.domInsertChild(child, slotName, at);
        this.postInsertChild(child, slotName, at);
        return at;
    }
    appendChildren(children, slotName) {
        this.memAppendChildren(children, slotName);
        this.domAppendChildren(children, slotName);
        this.postAppendChildren(children, slotName);
    }
    memAppendChildren(children, slotName) {
        this.$slotChildren[slotName] = this.$slotChildren[slotName] || [];
        for (const child of children) {
            this.memInsertChild(child, slotName, this.$slotChildren[slotName].length);
        }
    }
    postAppendChildren(children, slotName) {
        this.$slotChildren[slotName] = this.$slotChildren[slotName] || [];
        for (const child of children) {
            this.postAppendChild(child, slotName, this.$slotChildren[slotName].length);
        }
    }
    memAppendChild(child, slotName, at) {
        this.$slotChildren[slotName] = this.$slotChildren[slotName] || [];
        this.$slotChildren[slotName].push(child);
        this.$children.push(child);
        this.$childSlotName[at] = slotName;
        const slot = this.$slot[slotName];
        slot.memAppendParentChild(child, slotName, at, at);
    }
    memInsertChild(child, slotName, at) {
        this.$slotChildren[slotName] = this.$slotChildren[slotName] || [];
        (0, array_1.insert)(this.$slotChildren[slotName], child, at);
        const i = this.$children.length;
        this.$children.push(child);
        this.$childSlotName[i] = slotName;
        const slot = this.$slot[slotName];
        slot.memInsertParentChild(child, at, slotName);
    }
    domAppendChildren(children, slotName) {
        const fragment = new Component({}, this.$system);
        fragment.$element = new DocumentFragment();
        const slot = this.getLeafSlot(slotName);
        let i = 0;
        for (const child of children) {
            fragment._domAppendChild(child, slotName, i);
            i++;
        }
        const at_ = this.$slotChildren[slotName].length;
        this._domAppendChild(fragment, slotName, at_);
    }
    getLeafSlot(slotName) {
        let slot = this.$slot[slotName];
        while (slot.$slot[slotName] && slot.$slot[slotName] !== slot) {
            slot = slot.$slot[slotName];
        }
        return slot;
    }
    setSlotParent(slotParent, slotName, at) {
        this.$slotParent = slotParent;
        this.$slotParentSlotName = slotName;
        this.$slotParentSlotIndex = at;
    }
    _domAppendChild(child, slotName = 'default', at) {
        this._domInsertChild(child, slotName, at);
    }
    _domInsertChild(child, slotName = 'default', at) {
        const slot = this.getLeafSlot(slotName);
        slot.domInsertParentChildAt(child, slotName, at);
    }
    domAppendChild(child, slotName = 'default', at) {
        if (child.$detached) {
            return;
        }
        this._domAppendChild(child, slotName, at);
    }
    domInsertChild(child, slotName = 'default', at) {
        this._domInsertChild(child, slotName, at);
    }
    postAppendChild(child, slotName, at) {
        const slot = this.$slot[slotName];
        slot.postAppendParentChild(child, slotName, at);
    }
    postInsertChild(child, slotName, at) {
        const slot = this.$slot[slotName];
        slot.postAppendParentChild(child, slotName, at);
    }
    _createSVGWrapper() {
        const { api: { document: { createElementNS }, }, } = this.$system;
        const element = createElementNS(namespaceURI_1.namespaceURI, 'svg');
        element.style.display = 'block';
        element.style.width = '100%';
        element.style.height = '100%';
        const component = new Component({}, this.$system, element);
        return component;
    }
    _createHTMLWrapper() {
        const { api: { document: { createElement }, }, } = this.$system;
        const element = createElement('div');
        element.style.width = '100%';
        element.style.height = '100%';
        const component = new Component({}, this.$system, element);
        return component;
    }
    templateChildWrapper(child, svg, html, fallback) {
        if (this.isHTML() && child.isSVG()) {
            return svg();
        }
        else if (this.isSVG() && child.isHTML()) {
            return html();
        }
        else {
            return fallback();
        }
    }
    domRemoveChild(child, slotName = 'default', at) {
        const slot = this.$slot[slotName];
        slot.domRemoveParentChildAt(child, slotName, at);
        if (child.$detached) {
            return;
        }
    }
    postRemoveChild(child, at) {
        if (this.$mounted) {
            child.unmount();
        }
    }
    hasChild(at) {
        const has = this.$children.length > at;
        return has;
    }
    ascendChild(child, slotName = 'default') {
        this.removeChild(child);
        this.appendChild(child, slotName);
    }
    connect($unit, deep = true) {
        if (this.$connected) {
            // throw new Error('component is already connected')
            return NOOP_1.NOOP;
        }
        this._connect($unit, deep);
        if (deep) {
            if (this.$unbundled) {
                for (const unitId in this.$subComponent) {
                    const childSubComponent = this.$subComponent[unitId];
                    const __ = (0, interface_1.getComponentInterface)(childSubComponent);
                    const subUnit = $unit.$refSubComponent({ unitId, __ });
                    childSubComponent.connect(subUnit);
                }
            }
        }
        return () => {
            this.disconnect();
        };
    }
    _connect($unit, deep = true) {
        if (this.$connected) {
            // throw new Error('component is already connected')
            return;
        }
        this.$unit = (0, proxyWrap_1.proxyWrap)($unit, UCGEE_1.UCGEE);
        const listen = (event) => {
            this.$named_listener_count[event] = this.$named_listener_count[event] || 0;
            this.$named_listener_count[event]++;
            if (!this.$named_unlisten[event]) {
                this._addUnitEventListener(event);
            }
        };
        const unlisten = (event) => {
            this.$named_listener_count[event]--;
            if (this.$named_listener_count[event] === 0) {
                this._removeUnitEventListener(event);
            }
        };
        const handler = {
            unit: (moment) => {
                const { event: event_event, data: event_data } = moment;
                if (event_event === 'restore') {
                    this._disconnect();
                    this._connect($unit);
                }
                else if (event_event === 'append_child') {
                    const { bundle } = event_data;
                    const at = this.$children.length;
                    const child = this._instanceChild({ bundle }, at);
                    const slot_name = 'default';
                    (0, array_1.push)(this.$remoteChildren, child);
                    this.appendChild(child, slot_name);
                }
                else if (event_event === 'append_children') {
                    const { bundles } = event_data;
                    const children = bundles.map((bundle, i) => this._instanceChild({ bundle }, this.$children.length + i));
                    const slot_name = 'default';
                    for (const child of children) {
                        (0, array_1.push)(this.$remoteChildren, child);
                    }
                    this.appendChildren(children, slot_name);
                }
                else if (event_event === 'remove_child') {
                    const { at } = event_data;
                    const child = this.$remoteChildren[at];
                    this.removeChildAt(at);
                    child.disconnect();
                    child.destroy();
                    (0, array_1.remove)(this.$remoteChildren, child);
                }
                else if (event_event === 'play') {
                    this.play();
                }
                else if (event_event === 'pause') {
                    this.pause();
                }
                else if (event_event === 'register') {
                    this.register(event_data);
                }
                else if (event_event === 'unregister') {
                    this.unregister();
                }
                else {
                    throw new Error('invalid event');
                }
            },
        };
        const unit_listener = (moment) => {
            const { type } = moment;
            handler[type] && handler[type](moment);
        };
        this.$named_listener_count = {};
        $unit.$getGlobalId({}, (remoteId) => {
            this.register(remoteId);
        });
        const all_unlisten = [];
        const events = [
            'append_child',
            'append_children',
            'remove_child',
            'register',
            'unregister',
            'play',
            'pause',
            'restore',
        ];
        const unit_unlisten = this.$unit.$watch({ events }, unit_listener);
        all_unlisten.push(unit_unlisten);
        this.$unit.$getSetup({}, (setup) => {
            const { events, animations, stopPropagation, stopImmediatePropagation, preventDefault, attachDropTarget, attachText, detachTo, } = setup;
            for (const event of events) {
                listen(event);
            }
            for (const animation of animations) {
                const { keyframes, opt } = animation;
                this.animate(keyframes, opt);
            }
            for (const event of stopPropagation) {
                this.stopPropagation(event);
            }
            for (const [type, text] of attachText) {
                this.attachText(type, text);
            }
            for (const [] of attachDropTarget) {
                this.attachDropTarget();
            }
            if (detachTo) {
                const { host, opt } = detachTo;
                setTimeout(() => {
                    this.detach(host, opt);
                }, 0);
            }
        });
        const $emitter = $unit;
        const unlisten_control = this.$control($emitter);
        all_unlisten.push(unlisten_control);
        const unlisten_emitter = (0, callAll_1.callAll)([
            $emitter.$addListener({ event: 'listen' }, ([{ event }]) => {
                if (makeEventListener_1.UI_EVENT_SET.has(event) || event.startsWith('_')) {
                    listen(event);
                }
            }),
            $emitter.$addListener({ event: 'unlisten' }, ([{ event }]) => {
                if (makeEventListener_1.UI_EVENT_SET.has(event || event.startsWith('_'))) {
                    unlisten(event);
                }
            }),
            $emitter.$addListener({ event: 'call' }, ([{ method, data }]) => {
                this._call(method, data);
            }),
        ]);
        all_unlisten.push(unlisten_emitter);
        this._unit_unlisten = (0, callAll_1.callAll)(all_unlisten);
        $unit.$children({}, ($children) => {
            if ($children.length > 0) {
                const children = $children.map(this._instanceChild);
                this.$remoteChildren = children;
                this.setChildren(children);
            }
        });
        this.$connected = true;
        this.onConnected($unit);
    }
    reorderSubComponent(parentId, childId, to) {
        const child = this.getSubComponent(childId);
        if (parentId) {
            const parent = this.getSubComponent(parentId);
            const slotName = 'default';
            if (parent.hasParentRoot(child)) {
                parent.removeParentRoot(child);
            }
            parent.insertParentChildAt(child, slotName, to);
        }
        else {
            if (this.hasRoot(child)) {
                this.removeRoot(child);
            }
            this.insertRootAt(child, to);
        }
    }
    disconnect(deep = true) {
        this._disconnect();
        if (this.$unbundled) {
            for (const subUnitId in this.$subComponent) {
                const childSubComponent = this.$subComponent[subUnitId];
                childSubComponent.disconnect();
            }
        }
    }
    _disconnect(deep = true) {
        if (!this.$connected) {
            throw new Error('component is not connected');
        }
        this._unit_unlisten();
        for (const child of this.$remoteChildren) {
            child.disconnect();
        }
        this.unregister();
        this.$connected = false;
        this.onDisconnected();
    }
    setChildren(children = [], slotNames = []) {
        const oldChildren = [...this.$children];
        for (const oldChild of oldChildren) {
            this.removeChild(oldChild);
        }
        for (let i = 0; i < children.length; i++) {
            const child = children[i];
            const slotName = slotNames[i];
            this.appendChild(child, slotName);
        }
    }
    memRemoveChildAt(child, slotName, at) {
        const slot = this.$slot[slotName];
        slot.memRemoveParentChildAt(child, slotName, at);
        (0, array_1.remove)(this.$slotChildren[slotName], child);
        (0, array_1.removeAt)(this.$children, at);
        (0, array_1.removeAt)(this.$childSlotName, at);
    }
    removeChildAt(at) {
        const child = this.$children[at];
        const slotName = this.$childSlotName[at];
        const at_ = this.$slotChildren[slotName].indexOf(child);
        this.memRemoveChildAt(child, slotName, at);
        this.domRemoveChild(child, slotName, at_);
        this.postRemoveChild(child, at);
        return at;
    }
    removeChild(child) {
        const at = this.$children.indexOf(child);
        if (at > -1) {
            return this.removeChildAt(at);
        }
        else {
            throw new Error('child component not found');
        }
    }
    removeChildren() {
        const children = [...this.$children];
        for (const child of children) {
            this.removeChild(child);
        }
    }
    hasRoot(component) {
        return this.$root.includes(component);
    }
    pushRoot(component) {
        (0, array_1.push)(this.$root, component);
    }
    placeRootAt(component, at) {
        (0, array_1.insert)(this.$root, component, at);
    }
    pullRoot(component) {
        const index = this.$root.indexOf(component);
        if (index > -1) {
            this.$root.splice(index, 1);
        }
        else {
            throw new Error('cannot pull; not root component');
        }
    }
    fitRoot(component, at) {
        this.$root.splice(at, 0, component);
    }
    registerRoot(component, at) {
        at = at ?? this.$root.length;
        this.placeRootAt(component, at);
        this.insertRootAt(component, at);
    }
    registerRootAt(component, at) {
        (0, array_1.insert)(this.$root, component, at);
        this.insertRootAt(component, at);
    }
    unregisterRoot(component) {
        this.pullRoot(component);
        this.removeRoot(component);
    }
    memAppendRoot(component) {
        if (this.$mountRoot.indexOf(component) > -1) {
            throw new Error('root is already mounted');
        }
        (0, array_1.push)(this.$mountRoot, component);
    }
    postAppendRoot(component) {
        (0, control_1._if)(this.$mounted, mount_1.mount, component, this.$context);
    }
    domAppendRoot(component, at, index) {
        this.domInsertParentChildAt(component, 'default', at);
    }
    _insertAt(parent, child, at) {
        if (child.$domParent) {
            child.$domParent._removeChild_(child);
        }
        this.$domChildren.splice(at, 0, child);
        child.$domParent = parent;
        const target = parent._wrapElement(parent, child);
        (0, element_1.insertAt)(parent.$element, target, at);
    }
    domCommitInsertChild(component, at) {
        if (component.isParent()) {
            let i = 0;
            for (const root of component.$mountRoot) {
                this.domCommitInsertChild(root, at + i);
                i++;
            }
            return;
        }
        if (this.isParent()) {
            if (this.$slotParent) {
                this.$slotParent.domCommitInsertChild(component, this.$slotParentSlotIndex + at);
            }
            else {
                this._insertAt_(this, component, at);
            }
        }
        else {
            this._insertAt_(this, component, at);
        }
    }
    appendRoot(component) {
        const at = this.$mountRoot.length;
        const index = this.$root.indexOf(component);
        this.memAppendRoot(component);
        this.domAppendRoot(component, at, index);
        this.postAppendRoot(component);
    }
    insertRootAt(component, at) {
        this.memInsertRootAt(component, at);
        this.domInsertRootAt(component, at);
        this.postInsertRootAt(component, at);
    }
    postInsertRootAt(component, at) {
        this.$mounted && this.mountChild(component);
    }
    memInsertRootAt(component, at) {
        if (this.$mountRoot.indexOf(component) > -1) {
            throw new Error('root is already mounted');
        }
        (0, array_1.insert)(this.$mountRoot, component, at);
    }
    domInsertRootAt(component, at) {
        this.domInsertParentChildAt(component, 'default', at);
    }
    appendAllRoot() {
        for (const root of this.$root) {
            this.appendRoot(root);
        }
    }
    appendAllMissingRoot() {
        for (const root of this.$root) {
            if (this.$mountRoot.includes(root)) {
                //
            }
            else {
                this.appendRoot(root);
            }
        }
    }
    removeAllRoot() {
        for (const root of [...this.$mountRoot]) {
            this.removeRoot(root);
        }
    }
    removeRoot(component) {
        const at = this.$mountRoot.indexOf(component);
        this.domRemoveRoot(component, at);
        this.memRemoveRoot(component);
        this.postRemoveRoot(component);
    }
    memRemoveRoot(component) {
        (0, array_1.pull)(this.$mountRoot, component);
    }
    domRemoveRoot(component, at) {
        this.domRemoveParentChildAt(component, 'default', at);
    }
    postRemoveRoot(component) {
        (0, control_1._if)(this.$mounted, unmount_1.unmount, component);
    }
    compose() {
        for (const component of this.$root) {
            component.collapse();
            this.appendRoot(component);
        }
    }
    decompose(deep = true) {
        for (const component of [...this.$mountRoot]) {
            this.removeRoot(component);
            deep && component.uncollapse(deep);
        }
    }
    hasParentRoot(component) {
        return this.$parentRoot.includes(component);
    }
    pushParentRoot(component, slotName) {
        (0, array_1.push)(this.$parentRoot, component);
        (0, array_1.push)(this.$parentRootSlotName, slotName);
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.memPushParentChild(component, slotName);
        component.$rootParent = slot;
    }
    placeParentRoot(component, at, slotName) {
        (0, array_1.insert)(this.$parentRoot, component, at);
        (0, array_1.insert)(this.$parentRootSlotName, slotName, at);
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.memInsertParentChild(component, at, slotName);
        component.$rootParent = slot;
    }
    unshiftParentRoot(component, slotName) {
        (0, array_1.unshift)(this.$parentRoot, component);
        (0, array_1.unshift)(this.$parentRootSlotName, slotName);
        component.$rootParent = null;
    }
    pullParentRoot(component) {
        const i = this.$parentRoot.indexOf(component);
        if (i > -1) {
            const slotName = this.$parentRootSlotName[i];
            (0, array_1.removeAt)(this.$parentRoot, i);
            (0, array_1.removeAt)(this.$parentRootSlotName, i);
            const slot = (0, object_1.get)(this.$slot, slotName);
            slot.memPullParentChild(component);
        }
        else {
            throw new Error('parent root not found');
        }
    }
    registerParentRoot(component, slotName = 'default', at) {
        at = at ?? this.$parentRoot.length;
        this.placeParentRoot(component, at, slotName);
        this.insertParentRootAt(component, at, slotName);
    }
    unregisterParentRoot(component) {
        this.removeParentRoot(component);
        this.pullParentRoot(component);
    }
    collapse(deep = true) {
        let i = 0;
        for (const component of this.$parentRoot) {
            const slotName = this.$parentRootSlotName[i];
            deep && component.collapse();
            this.appendParentRoot(component, slotName);
            i++;
        }
    }
    uncollapse(deep = true) {
        for (const component of this.$parentRoot) {
            this.removeParentRoot(component);
            deep && component.uncollapse(deep);
        }
    }
    appendParentRoot(component, slotName) {
        const at = this.$parentRoot.indexOf(component);
        this.memAppendParentRoot(component, slotName, at);
        this.domAppendParentRoot(component, slotName, at);
        this.postAppendParentRoot(component, slotName, at);
    }
    memAppendParentRoot(component, slotName, at) {
        (0, array_1.push)(this.$mountParentRoot, component);
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.memAppendParentChild(component, 'default', at, at);
    }
    domAppendParentRoot(component, slotName, at) {
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.domInsertParentChildAt(component, 'default', at);
    }
    postAppendParentRoot(component, slotName, at) {
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.postAppendParentChild(component, slotName, at);
    }
    memPushParentChild(component, slotName) {
        (0, array_1.push)(this.$parentChildren, component);
        (0, array_1.push)(this.$parentChildrenSlot, slotName);
    }
    memInsertParentChild(component, at, slotName) {
        (0, array_1.insert)(this.$parentChildren, component, at);
        (0, array_1.insert)(this.$parentChildrenSlot, slotName, at);
    }
    memPullParentChild(component) {
        const at = this.$parentChildren.indexOf(component);
        (0, array_1.removeAt)(this.$parentChildren, at);
        (0, array_1.removeAt)(this.$parentChildrenSlot, at);
    }
    memAppendParentChild(component, slotName, at, _at) {
        (0, array_1.push)(this.$mountParentChildren, component);
    }
    postAppendParentChild(component, slotName, at) {
        this.postInsertParentChildAt(component, slotName, at);
    }
    postInsertParentChild(component, slotName, at) {
        if (this.$mounted) {
            this.mountChild(component);
        }
    }
    postInsertParentChildAt(component, slotName, at) {
        if (this.$mounted) {
            this.mountChild(component);
        }
    }
    insertParentChildAt(component, slotName, i) {
        const slot = this.$slot[slotName];
        if (this === slot) {
            this.memInsertParentChildAt(component, slotName, i);
            this.domInsertParentChildAt(component, slotName, i);
            this.postInsertParentChildAt(component, slotName, i);
        }
        else {
            slot.insertParentChildAt(component, 'default', i);
        }
    }
    memInsertParentChildAt(component, slotName, i) {
        if (this.$mountRoot.indexOf(component) > -1) {
            throw new Error('root is already mounted');
        }
        (0, array_1.insert)(this.$mountParentChildren, component, i);
    }
    domInsertParentChildAt(component, slotName, at) {
        component.setSlotParent(this, slotName, at);
        this.$slotParentChildren[slotName] =
            this.$slotParentChildren[slotName] ?? [];
        this.$slotParentChildren[slotName].splice(at, 0, component);
        this.$slotParentChildOffset[slotName] =
            this.$slotParentChildOffset[slotName] ?? [];
        if (component.$detached) {
            return;
        }
        if (!component.$primitive) {
            let i = 0;
            for (const root of component.$mountRoot) {
                this._domInsertParentChildAt(root, slotName, at + i);
                i++;
            }
            return;
        }
        if (!this.$primitive) {
            if (this.$slotParent) {
                this.$slotParent.domInsertParentChildAt(component, this.$slotParentSlotName, this.$slotParentSlotIndex + at);
                return;
            }
        }
        this._incrementParentChildOffset(slotName, at);
        this.domCommitInsertChild(component, at);
    }
    _domInsertParentChildAt(component, slotName, at) {
        if (component.$detached) {
            return;
        }
        if (!component.$primitive) {
            for (const root of component.$mountRoot) {
                this._domInsertParentChildAt(root, slotName, at);
            }
            return;
        }
        if (!this.$primitive) {
            if (this.$slotParent) {
                this.$slotParent.domInsertParentChildAt(component, this.$slotParentSlotName, this.$slotParentSlotIndex + at);
                return;
            }
        }
        this._incrementParentChildOffset(slotName, at);
        const lastIndex = this.$slotParentChildOffset[slotName][at];
        this.domCommitInsertChild(component, lastIndex);
    }
    removeParentChild(component, slotName = 'default', at) {
        const slot = this.$slot[slotName];
        if (slot === this) {
            const _at = this.$parentChildren.indexOf(component);
            this.domRemoveParentChildAt(component, slotName, _at);
            this.memRemoveParentChildAt(component, slotName, _at);
        }
        else {
            slot.removeParentChild(component, 'default', at);
        }
    }
    memRemoveParentChildAt(component, slotName, at) {
        const at_ = this.$mountParentChildren.indexOf(component);
        (0, array_1.removeAt)(this.$mountParentChildren, at_);
    }
    domRemoveParentChildAt(component, slotName, at) {
        if (component.$detached) {
            return;
        }
        if (!component.$primitive) {
            let i = 0;
            for (const root of component.$mountRoot) {
                this._domRemoveParentChildAt(root, slotName, at + i);
                i++;
            }
            return;
        }
        if (!this.$primitive) {
            if (this.$slotParent) {
                this.$slotParent._domRemoveParentChildAt(component, this.$slotParentSlotName, this.$slotParentSlotIndex + at);
                return;
            }
        }
        component.setSlotParent(null, null, null);
        this.$slotParentChildren[slotName].splice(at, 1);
        this._decrementParentChildOffset(slotName, at);
        this.domCommitRemoveChild(component, at);
    }
    _domRemoveParentChildAt(component, slotName, at) {
        if (component.$detached) {
            return;
        }
        if (!component.$primitive) {
            for (const root of component.$mountRoot) {
                this._domRemoveParentChildAt(root, slotName, at);
            }
            return;
        }
        if (!this.$primitive) {
            if (this.$slotParent) {
                this.$slotParent._domRemoveParentChildAt(component, this.$slotParentSlotName, this.$slotParentSlotIndex + at);
                return;
            }
        }
        this._decrementParentChildOffset(slotName, at);
        this.domCommitRemoveChild(component, at);
    }
    domCommitRemoveChild(component, at) {
        if (component.isParent()) {
            let i = 0;
            for (const root of component.$root) {
                this.domCommitRemoveChild(root, at + i);
                i++;
            }
            return;
        }
        if (this.isParent()) {
            if (this.$element.parentElement &&
                this.$element.parentElement ===
                    component.$element.parentElement) {
                this.$element.parentElement.removeChild(component.$element);
            }
            else if (this.$slotParent) {
                const index = this.$slotParent.$slotParentChildren[this.$slotParentSlotName].indexOf(this);
                this.$slotParent.domCommitRemoveChild(component, index + at);
            }
            else {
                this._removeChild_(component);
            }
        }
        else {
            this._removeChild_(component);
        }
    }
    _removeChild(child) {
        (0, array_1.remove)(this.$domChildren, child);
        child.$domParent = null;
        if (this.$element.contains(child.$element)) {
            const target = this._unwrapElement(this.$element, child);
            (0, element_1.removeChild)(this.$element, target);
        }
    }
    insertParentRootAt(component, at, slotName) {
        this.memInsertParentRootAt(component, at, slotName);
        this.domInsertParentRootAt(component, at, slotName);
        this.postInsertParentRootAt(component, at, slotName);
    }
    memInsertParentRootAt(component, i, slotName) {
        (0, array_1.insert)(this.$mountParentRoot, component, i);
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.memInsertParentChildAt(component, slotName, i);
    }
    domInsertParentRootAt(component, i, slotName) {
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.domInsertParentChildAt(component, slotName, i);
    }
    postInsertParentRootAt(component, _at, slotName) {
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.postInsertParentChildAt(component, slotName, _at);
    }
    prependParentRoot(component, slotName) {
        this.insertParentRootAt(component, 0, slotName);
    }
    removeParentRoot(component) {
        const at = this.$parentRoot.indexOf(component);
        const at_ = this.$mountParentRoot.indexOf(component);
        const slotName = this.$parentRootSlotName[at];
        this.postRemoveParentRootAt(component, slotName, at);
        this.domRemoveParentRootAt(component, slotName, at_);
        this.memRemoveParentRootAt(component, slotName, at_);
    }
    memRemoveParentRoot(component) {
        const at = this.$mountParentRoot.indexOf(component);
        const slotName = this.$parentRootSlotName[at];
        this.memRemoveParentRootAt(component, slotName, at);
    }
    memRemoveParentRootAt(component, slotName, at) {
        (0, array_1.pull)(this.$mountParentRoot, component);
        const slot = this.$slot[slotName];
        const slotName_ = this.$slotTarget[slotName] ?? 'default';
        slot.memRemoveParentChildAt(component, slotName_, at);
    }
    domRemoveParentRootAt(component, slotName, at) {
        const slot = this.$slot[slotName];
        const slotName_ = this.$slotTarget[slotName] ?? 'default';
        slot.domRemoveParentChildAt(component, slotName_, at);
    }
    postRemoveParentRootAt(component, slotName, at) {
        if (this.$mounted) {
            component.unmount();
        }
    }
    getSubComponent(id) {
        return this.$subComponent[id];
    }
    setControlled(controlled, deep = true) {
        this.$controlled = controlled;
        for (const subComponentId in this.$subComponent) {
            const subComponent = this.$subComponent[subComponentId];
            subComponent.setControlled(controlled, deep);
        }
    }
    setDisabled(disabled) {
        this._$disabled = disabled;
    }
    setSystem(system) {
        this.$system = system;
        for (const subComponentId in this.$subComponent) {
            const subComponent = this.$subComponent[subComponentId];
            subComponent.setSystem(system);
        }
    }
    setDetached(detached) {
        this.$detached = detached;
        for (const subComponentId in this.$subComponent) {
            const subComponent = this.$subComponent[subComponentId];
            subComponent.setDetached(detached);
        }
    }
    setSubComponent(id, component) {
        (0, object_1.set)(component, '$parent', this);
        this.$subComponent[id] = component;
    }
    setSubComponents(component_map) {
        (0, object_1.forEachObjKV)(component_map, this.setSubComponent.bind(this));
    }
    decoupleSubComponent(id) {
        const subComponent = this.getSubComponent(id);
        const parent = this.getSubComponentParent(id);
        if (parent) {
            parent.removeParentRoot(subComponent);
        }
        else {
            this.removeRoot(subComponent);
        }
        for (const subSubComponentId in subComponent.$subComponent) {
            subComponent.decoupleSubComponent(subSubComponentId);
        }
    }
    removeSubComponent(id) {
        const component = this.$subComponent[id];
        (0, object_1.set)(component, '$parent', null);
        delete this.$subComponent[id];
        return component;
    }
    getSubComponentParentId(id) {
        const subComponent = this.getSubComponent(id);
        for (const parentSubComponentId in this.$subComponent) {
            const parentSubComponent = this.$subComponent[parentSubComponentId];
            if (parentSubComponent.$parentRoot.includes(subComponent)) {
                return parentSubComponentId;
            }
        }
        return null;
    }
    getSubComponentParent(id) {
        const subComponentParentId = this.getSubComponentParentId(id);
        if (subComponentParentId) {
            return this.getSubComponent(subComponentParentId);
        }
        return null;
    }
    getSlotSubComponentId(slotName) {
        const subComponentId = this.$slotId[slotName];
        return subComponentId;
    }
    getSubComponentId(subComponent) {
        for (const subComponentId in this.$subComponent) {
            const _subComponent = this.$subComponent[subComponentId];
            if (subComponent === _subComponent) {
                return subComponentId;
            }
        }
        return null;
    }
    getParentRootSlotId(subComponent) {
        const i = this.$parentRoot.indexOf(subComponent);
        if (i > -1) {
            const slotName = this.$parentRootSlotName[i];
            return slotName;
        }
        else {
            return null;
        }
    }
    getSlotPath(slotName) {
        const slotSubComponentId = this.getSlotSubComponentId(slotName);
        if (slotSubComponentId) {
            const slotSubComponent = this.getSubComponent(slotSubComponentId);
            const mappedSlotName = this.$slotTarget[slotName];
            const mappedSlotPath = slotSubComponent.getSlotPath(mappedSlotName);
            return [slotSubComponentId, ...mappedSlotPath];
        }
        else {
            return [];
        }
    }
    getPath() {
        let path = [];
        let c = this;
        let p = this.$parent;
        while (p) {
            const subComponentId = p.getSubComponentId(c);
            path.unshift(subComponentId);
            c = p;
            p = p.$parent;
        }
        return path;
    }
    click() {
        const leaf = this.getFirstRootLeaf();
        leaf.click();
    }
    _call(method, data = []) {
        if (this[method]) {
            this[method](...data);
        }
        else {
            throw 'invalid method';
        }
    }
    reset() {
        //
    }
    play() {
        const animations = this.getAnimations();
        for (const animation of animations) {
            animation.play();
        }
    }
    pause() {
        const animations = this.getAnimations();
        for (const animation of animations) {
            animation.pause();
        }
    }
    destroy() {
        this.onDestroy();
    }
}
exports.Component = Component;
//# sourceMappingURL=component.js.map