"use strict";
/**
 * Copyright (c) Haiku 2016-2018. All rights reserved.
 */
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || Object.assign || function(t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
        s = arguments[i];
        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
            t[p] = s[p];
    }
    return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
var Curve_1 = require("./api/Curve");
var Config_1 = require("./Config");
var HaikuBase_1 = require("./HaikuBase");
var HaikuElement_1 = require("./HaikuElement");
var HaikuTimeline_1 = require("./HaikuTimeline");
var consoleErrorOnce_1 = require("./helpers/consoleErrorOnce");
var cssQueryList_1 = require("./helpers/cssQueryList");
var interactionModes_1 = require("./helpers/interactionModes");
var isMutableProperty_1 = require("./helpers/isMutableProperty");
var manaFlattenTree_1 = require("./helpers/manaFlattenTree");
var scopifyElements_1 = require("./helpers/scopifyElements");
var xmlToMana_1 = require("./helpers/xmlToMana");
var Layout3D_1 = require("./Layout3D");
var Migration_1 = require("./Migration");
var vanities_1 = require("./properties/dom/vanities");
var functionToRFO_1 = require("./reflection/functionToRFO");
var StateTransitionManager_1 = require("./StateTransitionManager");
var ValueBuilder_1 = require("./ValueBuilder");
var assign_1 = require("./vendor/assign");
var pkg = require('./../package.json');
var VERSION = pkg.version;
var STRING_TYPE = 'string';
var OBJECT_TYPE = 'object';
var HAIKU_ID_ATTRIBUTE = 'haiku-id';
var DEFAULT_TIMELINE_NAME = 'Default';
var CSS_QUERY_MAPPING = {
    name: 'elementName',
    attributes: 'attributes',
    children: 'children',
};
// tslint:disable:variable-name function-name
var HaikuComponent = /** @class */ (function (_super) {
    __extends(HaikuComponent, _super);
    function HaikuComponent(bytecode, context, host, config, container) {
        var _this = _super.call(this) || this;
        // We provide rudimentary support for passing the `template` as an XML string.
        if (typeof bytecode.template === STRING_TYPE) {
            console.warn('[haiku core] converting template xml string to object');
            bytecode.template = xmlToMana_1.default(bytecode.template);
        }
        if (!bytecode.template) {
            console.warn('[haiku core] adding missing template object');
            bytecode.template = { elementName: 'div', attributes: {}, children: [] };
        }
        if (!bytecode.timelines) {
            console.warn('[haiku core] adding missing timelines object');
            bytecode.timelines = {};
        }
        if (!bytecode.timelines[DEFAULT_TIMELINE_NAME]) {
            console.warn('[haiku core] adding missing default timeline');
            bytecode.timelines[DEFAULT_TIMELINE_NAME] = {};
        }
        if (!context) {
            throw new Error('Component requires a context');
        }
        if (!config) {
            throw new Error('Config options required');
        }
        if (!config.seed) {
            throw new Error('Seed value must be provided');
        }
        _this.PLAYER_VERSION = VERSION; // #LEGACY
        _this.CORE_VERSION = VERSION;
        _this.context = context;
        _this.container = container;
        _this.host = host;
        _this.guests = {};
        _this.bytecode = (config.hotEditingMode)
            ? bytecode
            : clone(bytecode, _this); // Important because migrations mutate the bytecode
        assertTemplate(_this.bytecode.template);
        // Allow users to expose methods that can be called in event handlers
        if (_this.bytecode.methods) {
            for (var methodNameGiven in _this.bytecode.methods) {
                if (!_this[methodNameGiven]) {
                    _this[methodNameGiven] = _this.bytecode.methods[methodNameGiven].bind(_this);
                }
            }
        }
        _this.builder = new ValueBuilder_1.default(_this);
        _this._states = {}; // Storage for getter/setter actions in userland logic
        _this.state = {}; // Public accessor object, e.g. this.state.foo = 1
        // Instantiate StateTransitions. Responsible to store and execute any state transition.
        _this.stateTransitionManager = new StateTransitionManager_1.default(_this.state, _this.getClock());
        // `assignConfig` calls bindStates because our incoming config, which
        // could occur at any point during runtime, e.g. in React, may need to update internal states, etc.
        _this.assignConfig(config);
        _this._mutableTimelines = undefined;
        _this._hydrateMutableTimelines();
        // The full version of the template gets mutated in-place by the rendering algorithm
        _this._flatManaTree = [];
        // Flag used internally to determine whether we need to re-render the full tree or can survive by just patching
        _this.doesNeedFullFlush = false;
        // If true, will continually flush the entire tree until explicitly set to false again
        _this.doAlwaysFlush = false;
        // Dictionary of event handler names to handler functions; used to efficiently manage multiple subscriptions
        _this.registeredEventHandlers = {};
        // As a performance optimization, keep track of elements we've located as key/value (selector/element) pairs
        _this._matchedElementCache = {};
        // Dictionary of ids-to-elements, representing elements that we
        // do not want to render past in the tree (i.e. cede control to some
        // other rendering context)
        _this._horizonElements = {};
        // Flag to determine whether this component should continue doing any work
        _this.isDeactivated = false;
        // Flag to indicate whether we are sleeping, an ephemeral condition where no rendering occurs
        _this.isSleeping = false;
        // Ensure full tree is are properly set up and all render nodes are connected to their models
        _this.render(__assign({}, _this.config, { forceApplyBehaviors: true }));
        try {
            // If the bytecode we got happens to be in an outdated format, we automatically update it to the latest.
            Migration_1.runMigrations(_this, {
                // Random seed for adding instance uniqueness to ids at runtime.
                referenceUniqueness: (config.hotEditingMode)
                    ? undefined // During editing, Haiku.app pads ids unless this is undefined
                    : Math.random().toString(36).slice(2),
            }, VERSION);
        }
        catch (e) {
            console.warn('[haiku core] caught error during migration', e);
        }
        // Start the default timeline to initiate the component;
        // run before the did-initialize hook in case the user wants to cancel
        _this.startTimeline(DEFAULT_TIMELINE_NAME);
        _this.routeEventToHandlerAndEmit(HaikuBase_1.GLOBAL_LISTENER_KEY, 'component:did-initialize', [_this]);
        // #FIXME: some handlers may still reference `_bytecode` directly.
        _this._bytecode = _this.bytecode;
        return _this;
    }
    /**
     * @description Track elements that are at the horizon of what we want to render, i.e., a list of
     * virtual elements that we don't want to make any updates lower than in the tree.
     */
    HaikuComponent.prototype.markHorizonElement = function (virtualElement) {
        if (virtualElement && virtualElement.attributes) {
            var flexId = virtualElement.attributes[HAIKU_ID_ATTRIBUTE] || virtualElement.attributes.id;
            if (flexId) {
                this._horizonElements[flexId] = virtualElement;
            }
        }
    };
    HaikuComponent.prototype.isHorizonElement = function (virtualElement) {
        if (virtualElement && virtualElement.attributes) {
            var flexId = virtualElement.attributes[HAIKU_ID_ATTRIBUTE] || virtualElement.attributes.id;
            return !!this._horizonElements[flexId];
        }
        return false;
    };
    HaikuComponent.prototype.registerGuest = function (subcomponent) {
        this.guests[subcomponent.getId()] = subcomponent;
    };
    HaikuComponent.prototype.visitGuestHierarchy = function (visitor) {
        visitor(this, this.$id, this.host);
        for (var $id in this.guests) {
            this.guests[$id].visitGuestHierarchy(visitor);
        }
    };
    // If the component needs to remount itself for some reason, make sure we fire the right events
    HaikuComponent.prototype.callRemount = function (incomingConfig, skipMarkForFullFlush) {
        this.routeEventToHandlerAndEmit(HaikuBase_1.GLOBAL_LISTENER_KEY, 'component:will-mount', [this]);
        // Note!: Only update config if we actually got incoming options!
        if (incomingConfig) {
            this.assignConfig(incomingConfig);
        }
        if (!skipMarkForFullFlush) {
            this.markForFullFlush();
            this.clearCaches(null);
        }
        // If autoplay is not wanted, stop the all timelines immediately after we've mounted
        // (We have to mount first so that the component displays, but then pause it at that state.)
        // If you don't want the component to show up at all, use options.automount=false.
        var timelineInstances = this.getTimelines();
        for (var timelineName in timelineInstances) {
            var timelineInstance = timelineInstances[timelineName];
            if (this.config.autoplay) {
                if (timelineName === DEFAULT_TIMELINE_NAME) {
                    // Assume we want to start the timeline from the beginning upon remount.
                    // NOTE:
                    // timeline.play() will normally trigger markForFullFlush because it assumes we need to render
                    // from the get-go. However, in case of a callRemount, we might not want to do that since it can be kind of
                    // like running the first frame twice. So we pass the option into play so it can conditionally skip the
                    // markForFullFlush step.
                    if (!timelineInstance.isExplicitlyPaused()) {
                        timelineInstance.play({ skipMarkForFullFlush: skipMarkForFullFlush });
                    }
                }
            }
            else {
                timelineInstance.pause();
            }
        }
        this.context.contextMount();
        this.routeEventToHandlerAndEmit(HaikuBase_1.GLOBAL_LISTENER_KEY, 'component:did-mount', [this]);
    };
    HaikuComponent.prototype.destroy = function () {
        var _this = this;
        _super.prototype.destroy.call(this);
        // Destroy all timelines we host.
        var timelineInstances = this.getTimelines();
        for (var timelineName in timelineInstances) {
            var timelineInstance = timelineInstances[timelineName];
            timelineInstance.destroy();
        }
        this.visitGuestHierarchy(function (component) {
            // Clean up HaikuComponent dependents.
            // TODO: is this step necessary?
            if (component !== _this) {
                component.destroy();
            }
        });
        this.visitDescendants(function (child) {
            // Clean up HaikuElement dependents.
            child.destroy();
        });
    };
    HaikuComponent.prototype.callUnmount = function () {
        // Since we're unmounting, pause all animations to avoid unnecessary calc while detached
        var timelineInstances = this.getTimelines();
        for (var timelineName in timelineInstances) {
            var timelineInstance = timelineInstances[timelineName];
            timelineInstance.pause();
        }
        this.context.contextUnmount();
        this.routeEventToHandlerAndEmit(HaikuBase_1.GLOBAL_LISTENER_KEY, 'component:will-unmount', [this]);
    };
    HaikuComponent.prototype.assignConfig = function (incomingConfig) {
        this.config = Config_1.default.build(this.config || {}, incomingConfig || {});
        // Don't assign the context config if we're a guest component;
        // assume only the top-level component should have this power
        if (this.host) {
            // Don't forget to update the configuration values shared by the context,
            // but skip component assignment so we don't end up in an infinite loop
            this.context.assignConfig(this.config, { skipComponentAssign: true });
        }
        var timelines = this.getTimelines();
        for (var name_1 in timelines) {
            var timeline = timelines[name_1];
            timeline.assignOptions(this.config);
        }
        this.bindStates();
        assign_1.default(this.bytecode.timelines, this.config.timelines);
        return this;
    };
    HaikuComponent.prototype.set = function (key, value) {
        this.state[key] = value;
        return this;
    };
    HaikuComponent.prototype.get = function (key) {
        return this.state[key];
    };
    HaikuComponent.prototype.setState = function (states, transitionParameter) {
        // Do not set any state if invalid
        if (!states || typeof states !== 'object') {
            return this;
        }
        // Set states is delegated to stateTransitionManager
        this.stateTransitionManager.setState(states, transitionParameter);
        return this;
    };
    HaikuComponent.prototype.getStates = function () {
        return this.state;
    };
    HaikuComponent.prototype.clearCaches = function (options) {
        if (options === void 0) { options = {}; }
        this.cacheClear();
        // Don't forget to repopulate the states with originals when we cc otherwise folks
        // who depend on initial states being set will be SAD!
        if (options.clearStates) {
            this._states = {};
            this.bindStates();
        }
        this._flatManaTree = manaFlattenTree_1.default(this.getTemplate(), CSS_QUERY_MAPPING);
        this._matchedElementCache = {};
        this.builder.clearCaches(options);
        this._hydrateMutableTimelines();
        // These may have been set for caching purposes
        if (this.bytecode.timelines) {
            for (var timelineName in this.bytecode.timelines) {
                delete this.bytecode.timelines[timelineName].__max;
            }
        }
    };
    HaikuComponent.prototype.getClock = function () {
        return this.context.getClock();
    };
    HaikuComponent.prototype.getTemplate = function () {
        return this.bytecode.template;
    };
    HaikuComponent.prototype.getTimelines = function () {
        var _this = this;
        return this.cacheFetch('getTimelines', function () {
            return _this.fetchTimelines();
        });
    };
    HaikuComponent.prototype.fetchTimelines = function () {
        var names = Object.keys(this.bytecode.timelines);
        for (var i = 0; i < names.length; i++) {
            var name_2 = names[i];
            if (!name_2) {
                continue;
            }
            var existing = HaikuTimeline_1.default.where({
                name: name_2,
                component: this,
            })[0];
            if (!existing) {
                HaikuTimeline_1.default.create(this, name_2, this.getTimelineDescriptor(name_2), this.config);
            }
        }
        var out = {};
        HaikuTimeline_1.default.where({ component: this }).forEach(function (timeline) {
            out[timeline.getName()] = timeline;
        });
        return out;
    };
    HaikuComponent.prototype.getTimeline = function (name) {
        return this.getTimelines()[name];
    };
    HaikuComponent.prototype.fetchTimeline = function (name, descriptor) {
        var found = this.getTimeline(name);
        if (found) {
            return found;
        }
        return HaikuTimeline_1.default.create(this, name, descriptor, this.config);
    };
    HaikuComponent.prototype.getDefaultTimeline = function () {
        var timelines = this.getTimelines();
        return timelines[DEFAULT_TIMELINE_NAME];
    };
    HaikuComponent.prototype.stopAllTimelines = function () {
        var timelines = this.getTimelines();
        for (var name_3 in timelines) {
            this.stopTimeline(name_3);
        }
    };
    HaikuComponent.prototype.startAllTimelines = function () {
        var timelines = this.getTimelines();
        for (var name_4 in timelines) {
            this.startTimeline(name_4);
        }
    };
    HaikuComponent.prototype.startTimeline = function (timelineName) {
        var time = this.context.clock.getExplicitTime();
        var descriptor = this.getTimelineDescriptor(timelineName);
        var existing = this.fetchTimeline(timelineName, descriptor);
        if (existing) {
            existing.start(time, descriptor);
        }
    };
    HaikuComponent.prototype.stopTimeline = function (timelineName) {
        var time = this.context.clock.getExplicitTime();
        var descriptor = this.getTimelineDescriptor(timelineName);
        var existing = this.getTimeline(timelineName);
        if (existing) {
            existing.stop(time, descriptor);
        }
    };
    HaikuComponent.prototype.getTimelineDescriptor = function (timelineName) {
        return this.bytecode.timelines[timelineName];
    };
    HaikuComponent.prototype.getInjectables = function (element) {
        var injectables = {};
        assign_1.default(injectables, this.builder.getSummonablesSchema(element));
        // Local states get precedence over global summonables, so assign them last
        for (var key in this._states) {
            var type = this._states[key].type;
            if (!type) {
                type = typeof this._states[key];
            }
            injectables[key] = type;
        }
        return injectables;
    };
    /**
     * @method _deactivate
     * @description When hot-reloading a component during editing, this can be used to
     * ensure that this component doesn't keep updating after its replacement is loaded.
     */
    HaikuComponent.prototype.deactivate = function () {
        this.isDeactivated = true;
    };
    HaikuComponent.prototype.activate = function () {
        this.isDeactivated = false;
    };
    HaikuComponent.prototype.sleepOn = function () {
        this.isSleeping = true;
    };
    HaikuComponent.prototype.sleepOff = function () {
        this.isSleeping = false;
    };
    /**
     * @method dump
     * @description Dump serializable info about this object
     */
    HaikuComponent.prototype.dump = function () {
        var metadata = this.getBytecodeMetadata();
        return metadata.relpath + ":" + this.getComponentId();
    };
    HaikuComponent.prototype.getBytecodeMetadata = function () {
        return this.bytecode.metadata;
    };
    HaikuComponent.prototype.getBytecodeRelpath = function () {
        var metadata = this.getBytecodeMetadata();
        return metadata && metadata.relpath;
    };
    HaikuComponent.prototype.getBytecodeProject = function () {
        var metadata = this.getBytecodeMetadata();
        return metadata && metadata.project;
    };
    HaikuComponent.prototype.getBytecodeOrganization = function () {
        var metadata = this.getBytecodeMetadata();
        return metadata && metadata.organization;
    };
    HaikuComponent.prototype.getAddressableProperties = function (out) {
        var _this = this;
        if (out === void 0) { out = {}; }
        if (!this.bytecode.states) {
            return out;
        }
        var _loop_1 = function (name_5) {
            var state = this_1.bytecode.states[name_5];
            out[name_5] = {
                name: name_5,
                type: 'state',
                prefix: name_5,
                suffix: undefined,
                fallback: state.value,
                typedef: state.type,
                mock: state.mock,
                target: this_1,
                value: function () {
                    return _this.state[name_5]; // The current live value of this state as seen by the app
                },
            };
        };
        var this_1 = this;
        for (var name_5 in this.bytecode.states) {
            _loop_1(name_5);
        }
        return out;
    };
    HaikuComponent.prototype.bindStates = function () {
        var allStates = assign_1.default({}, this.bytecode.states, this.config.states);
        for (var stateSpecName in allStates) {
            var stateSpec = allStates[stateSpecName];
            // 'null' is the signal for an empty prop, not undefined.
            if (stateSpec.value === undefined) {
                console.error('Property `' +
                    stateSpecName +
                    '` cannot be undefined; use null for empty states');
                continue;
            }
            var isValid = stateSpecValidityCheck(stateSpec, stateSpecName);
            if (isValid) {
                this._states[stateSpecName] = stateSpec.value;
                this.defineSettableState(stateSpec, stateSpecName);
            }
        }
    };
    HaikuComponent.prototype.defineSettableState = function (stateSpec, stateSpecName) {
        var _this = this;
        // Note: We define the getter/setter on the object itself, but the storage occurs on the pass-in statesTargetObject
        Object.defineProperty(this.state, stateSpecName, {
            configurable: true,
            get: function () {
                return _this._states[stateSpecName];
            },
            set: function (inputValue) {
                if (stateSpec.setter) {
                    // Important: We call the setter with a binding of the component, so it can access methods on `this`
                    _this._states[stateSpecName] = stateSpec.setter.call(_this, inputValue);
                }
                else {
                    _this._states[stateSpecName] = inputValue;
                }
                if (!_this.isDeactivated) {
                    _this.emit('state:set', stateSpecName, _this._states[stateSpecName]);
                }
                return _this._states[stateSpecName];
            },
        });
    };
    HaikuComponent.prototype.allEventHandlers = function () {
        return assign_1.default({}, this.bytecode.eventHandlers, this.config.eventHandlers);
    };
    HaikuComponent.prototype.eachEventHandler = function (iteratee) {
        var eventHandlers = this.allEventHandlers();
        for (var eventSelector in eventHandlers) {
            for (var eventName in eventHandlers[eventSelector]) {
                var descriptor = eventHandlers[eventSelector][eventName];
                if (!descriptor || !descriptor.handler) {
                    continue;
                }
                iteratee(eventSelector, eventName, descriptor);
            }
        }
    };
    HaikuComponent.prototype.routeEventToHandler = function (eventSelectorGiven, eventNameGiven, eventArgs) {
        var _this = this;
        if (this.isDeactivated) {
            return;
        }
        this.eachEventHandler(function (eventSelector, eventName, _a) {
            var handler = _a.handler;
            if (eventNameGiven === eventName) {
                if (eventSelectorGiven === eventSelector ||
                    eventSelectorGiven === HaikuBase_1.GLOBAL_LISTENER_KEY) {
                    _this.callEventHandler(eventSelector, eventName, handler, eventArgs);
                    return;
                }
            }
        });
    };
    HaikuComponent.prototype.callEventHandler = function (eventsSelector, eventName, handler, eventArgs) {
        // Only fire the event listeners if the component is in 'live' interaction mode,
        // i.e., not currently being edited inside the Haiku authoring environment
        if (!interactionModes_1.isPreviewMode(this.config.interactionMode)) {
            return;
        }
        try {
            return handler.apply(this, eventArgs);
        }
        catch (exception) {
            consoleErrorOnce_1.default(exception);
        }
    };
    HaikuComponent.prototype.routeEventToHandlerAndEmit = function (eventSelectorGiven, eventNameGiven, eventArgs) {
        if (this.isDeactivated) {
            return;
        }
        this.routeEventToHandler(eventSelectorGiven, eventNameGiven, eventArgs);
        this.emit.apply(this, [eventNameGiven].concat(eventArgs));
    };
    HaikuComponent.prototype.markForFullFlush = function () {
        this.doesNeedFullFlush = true;
        return this;
    };
    HaikuComponent.prototype.unmarkForFullFlush = function () {
        this.doesNeedFullFlush = false;
        return this;
    };
    HaikuComponent.prototype.shouldPerformFullFlush = function () {
        return this.doesNeedFullFlush || this.doAlwaysFlush;
    };
    HaikuComponent.prototype.performFullFlushRenderWithRenderer = function (renderer, options) {
        if (options === void 0) { options = {}; }
        this.context.getContainer(true); // Force recalc of container
        var tree = this.render(options);
        // Since we just produced a full tree, we don't need a further full flush.
        this.unmarkForFullFlush();
        // Undefined signals there is no update to be made
        if (tree !== undefined) {
            return renderer.render(this.container, tree, this);
        }
    };
    HaikuComponent.prototype.render = function (options) {
        if (options === void 0) { options = {}; }
        // We register ourselves with our host here because render is guaranteed to be called
        // both in our constructor and in the case that we were deactivated/reactivated.
        // This must run before the isDeactivated check since we may use the registry to activate later.
        if (this.host) {
            this.host.registerGuest(this);
        }
        if (this.isDeactivated) {
            // If deactivated, pretend like there is nothing to render
            return;
        }
        this._flatManaTree = manaFlattenTree_1.default(this.getTemplate(), CSS_QUERY_MAPPING);
        this._matchedElementCache = {};
        var expansion = expandTreeNode(this.getTemplate(), // node
        this.container, // parent
        this, // instance (component)
        this.context, this.host, options, true);
        scopifyElements_1.default(expansion, null, null);
        this.applyContextChanges(expansion, options);
        return expansion;
    };
    HaikuComponent.prototype.performPatchRenderWithRenderer = function (renderer, options, skipCache) {
        if (options === void 0) { options = {}; }
        if (renderer.shouldCreateContainer) {
            this.context.getContainer(true); // Force recalc of container
        }
        var patches = this.patch(options, skipCache);
        renderer.patch(this, patches);
        for (var $id in this.guests) {
            this.guests[$id].performPatchRenderWithRenderer(renderer, options, skipCache);
        }
    };
    HaikuComponent.prototype.patch = function (options, skipCache) {
        if (options === void 0) { options = {}; }
        if (skipCache === void 0) { skipCache = false; }
        if (this.isDeactivated) {
            // If deactivated, pretend like there is nothing to render
            return {};
        }
        return this.gatherDeltaPatches(this.getTemplate(), options, skipCache);
    };
    HaikuComponent.prototype.applyContextChanges = function (template, options) {
        var _this = this;
        if (options === void 0) { options = {}; }
        Layout3D_1.default.initializeTreeAttributes(template, true);
        this.applyBehaviors(null, options, false, // isPatchOperation
        false);
        if (this.context.renderer.mount) {
            this.eachEventHandler(function (eventSelector, eventName) {
                var registrationKey = eventSelector + ":" + eventName;
                if (_this.registeredEventHandlers[registrationKey]) {
                    return;
                }
                _this.registeredEventHandlers[registrationKey] = true;
                _this.context.renderer.mountEventListener(_this, eventSelector, eventName, function () {
                    var args = [];
                    for (var _i = 0; _i < arguments.length; _i++) {
                        args[_i] = arguments[_i];
                    }
                    _this.routeEventToHandlerAndEmit(eventSelector, eventName, args);
                });
            });
        }
        if (!this.host && options.sizing) {
            computeAndApplyPresetSizing(template, this.container, options.sizing, null);
        }
        computeAndApplyTreeLayouts(template, this.container, options, this.context);
    };
    HaikuComponent.prototype.gatherDeltaPatches = function (template, options, skipCache) {
        if (options === void 0) { options = {}; }
        if (skipCache === void 0) { skipCache = false; }
        // This is what we're going to return: a dictionary of ids to elements
        var deltas = {};
        Layout3D_1.default.initializeTreeAttributes(template, true);
        this.applyBehaviors(deltas, options, true, // isPatchOperation
        skipCache);
        if (!this.host && options.sizing) {
            computeAndApplyPresetSizing(template, this.container, options.sizing, deltas);
        }
        // TODO: Calculating the tree layout should be skipped for already visited node
        // that we have already calculated among the descendants of the changed one
        for (var flexId in deltas) {
            var changedNode = deltas[flexId];
            computeAndApplyTreeLayouts(changedNode, changedNode.__parent, options, this.context);
        }
        return deltas;
    };
    HaikuComponent.prototype.applyBehaviors = function (deltas, options, isPatchOperation, skipCache) {
        if (skipCache === void 0) { skipCache = false; }
        var globalClockTime = this.context.clock.getExplicitTime();
        for (var timelineName in this.bytecode.timelines) {
            var timelineInstance = this.getTimeline(timelineName);
            // If we update with the global clock time while a timeline is paused, the next
            // time we resume playing it will "jump forward" to the time that has elapsed.
            if (timelineInstance.isPlaying()) {
                timelineInstance.doUpdateWithGlobalClockTime(globalClockTime);
            }
            var timelineTime = timelineInstance.getBoundedTime();
            var timelineDescriptor = this.bytecode.timelines[timelineName];
            // In hot editing mode, any timeline is fair game for mutation,
            // even if it's not actually animated (e.g. dragging an SVG at keyframe 0).
            var mutableTimelineDescriptor = isPatchOperation
                ? this._mutableTimelines[timelineName]
                : timelineDescriptor;
            if (!mutableTimelineDescriptor || typeof mutableTimelineDescriptor !== 'object') {
                continue;
            }
            for (var behaviorSelector in mutableTimelineDescriptor) {
                var propertiesGroup = timelineDescriptor[behaviorSelector];
                if (!propertiesGroup) {
                    continue;
                }
                var hasExpressions = propertyGroupNeedsExpressionEvaluated(propertiesGroup, timelineTime);
                if (options.forceApplyBehaviors ||
                    (timelineInstance.isPlaying() && timelineInstance.isUnfinished()) ||
                    hasExpressions) {
                    // proceed
                }
                else {
                    continue;
                }
                var matchingElementsForBehavior = findMatchingElementsByCssSelector(behaviorSelector, this._flatManaTree, this._matchedElementCache);
                if (!matchingElementsForBehavior || matchingElementsForBehavior.length < 1) {
                    continue;
                }
                for (var j = 0; j < matchingElementsForBehavior.length; j++) {
                    var matchingElement = matchingElementsForBehavior[j];
                    var domId = (matchingElement &&
                        matchingElement.attributes &&
                        matchingElement.attributes.id);
                    var haikuId = (matchingElement &&
                        matchingElement.attributes &&
                        matchingElement.attributes[HAIKU_ID_ATTRIBUTE]);
                    var flexId = haikuId || domId;
                    var assembledOutputs = this.builder.build({}, // We provide an object onto which outputs are placed
                    timelineName, timelineTime, flexId, matchingElement, propertiesGroup, isPatchOperation, this, skipCache);
                    if (assembledOutputs) {
                        // If assembledOutputs is empty, that signals that nothing has changed
                        if (deltas && flexId) {
                            deltas[flexId] = matchingElement;
                        }
                        for (var behaviorKey in assembledOutputs) {
                            var behaviorValue = assembledOutputs[behaviorKey];
                            this.applyPropertyToNode(matchingElement, behaviorKey, behaviorValue, timelineInstance);
                        }
                    }
                }
            }
        }
    };
    HaikuComponent.prototype.applyPropertyToNode = function (node, name, value, timeline) {
        var sender = (node.__instance) ? node.__instance : this; // Who sent the command
        var receiver = node.__subcomponent || node.__receiver;
        var type = (receiver && receiver.tagName) || node.elementName;
        var vanity = vanities_1.default[type] && vanities_1.default[type][name];
        var addressables = receiver && receiver.getAddressableProperties();
        var addressee = addressables && addressables[name] !== undefined && receiver;
        if (addressee && vanity) {
            addressee.set(name, value);
            vanity(name, node, value, this.context, timeline, receiver, sender);
        }
        else if (addressee) {
            addressee.set(name, value);
        }
        else if (vanity) {
            vanity(name, node, value, this.context, timeline, receiver, sender);
        }
        else {
            node.attributes[name] = value;
        }
    };
    HaikuComponent.prototype.findElementsByHaikuId = function (componentId) {
        return findMatchingElementsByCssSelector('haiku:' + componentId, this._flatManaTree, this._matchedElementCache);
    };
    HaikuComponent.prototype._hydrateMutableTimelines = function () {
        var _a, _b, _c;
        this._mutableTimelines = {};
        if (this.bytecode.timelines) {
            for (var timelineName in this.bytecode.timelines) {
                for (var selector in this.bytecode.timelines[timelineName]) {
                    for (var propertyName in this.bytecode.timelines[timelineName][selector]) {
                        if (isMutableProperty_1.default(this.bytecode.timelines[timelineName][selector][propertyName], propertyName)) {
                            var timeline = this._mutableTimelines[timelineName] || {};
                            var propertyGroup = timeline[selector] || {};
                            this._mutableTimelines = __assign({}, this._mutableTimelines, (_a = {}, _a[timelineName] = __assign({}, timeline, (_b = {}, _b[selector] = __assign({}, propertyGroup, (_c = {}, _c[propertyName] = this.bytecode.timelines[timelineName][selector][propertyName], _c)), _b)), _a));
                        }
                    }
                }
            }
        }
    };
    HaikuComponent.prototype.addHotComponent = function (hotComponent) {
        var _a, _b;
        if (!this.bytecode.timelines ||
            !this.bytecode.timelines[hotComponent.timelineName] ||
            !this.bytecode.timelines[hotComponent.timelineName][hotComponent.selector]) {
            return;
        }
        var propertyGroup = this.bytecode.timelines[hotComponent.timelineName][hotComponent.selector];
        var timeline = this._mutableTimelines[hotComponent.timelineName] || {};
        var mutablePropertyGroup = timeline[hotComponent.selector] || {};
        this._mutableTimelines = __assign({}, this._mutableTimelines, (_a = {}, _a[hotComponent.timelineName] = __assign({}, timeline, (_b = {}, _b[hotComponent.selector] = __assign({}, mutablePropertyGroup, hotComponent.propertyNames.reduce(function (hotProperties, propertyName) { return (hotProperties[propertyName] = propertyGroup[propertyName], hotProperties); }, {})), _b)), _a));
    };
    HaikuComponent.prototype.controlTime = function (timelineName, timelineTime) {
        var explicitTime = this.context.clock.getExplicitTime();
        var timelineInstances = this.getTimelines();
        for (var localTimelineName in timelineInstances) {
            if (localTimelineName === timelineName) {
                var timelineInstance = timelineInstances[timelineName];
                timelineInstance.controlTime(timelineTime, explicitTime);
            }
        }
        for (var $id in this.guests) {
            this.guests[$id].controlTime(timelineName, this.getControlledTimeDefinedForGuestComponent(this.guests[$id], timelineName, timelineTime));
        }
    };
    HaikuComponent.prototype.getControlledTimeDefinedForGuestComponent = function (guest, timelineName, timelineTime) {
        var wrapper = guest.parentNode;
        if (!wrapper) {
            return timelineTime;
        }
        var wrapperId = wrapper.attributes && wrapper.attributes[HAIKU_ID_ATTRIBUTE];
        if (!wrapperId) {
            return timelineTime;
        }
        var playbackValue = this.getOutputValue(timelineName, timelineTime, wrapperId, 'playback');
        if (typeof playbackValue === 'number') {
            return playbackValue;
        }
        var guestTimeline = guest.getTimeline(timelineName);
        if (playbackValue === "cede" /* CEDE */) {
            return guestTimeline.getTime();
        }
        // If time is controlled and we're set to 'loop', use a modulus of the guest's max time
        // which will give the effect of looping the guest to its 0 if its max has been reached
        if (playbackValue === "loop" /* LOOP */) {
            if (guestTimeline) {
                var guestMax = guestTimeline.getMaxTime();
                var finalTime = timelineTime % guestMax; // TODO: What if final frame has a change?
                return finalTime;
            }
            return timelineTime;
        }
        if (playbackValue === "stop" /* STOP */) {
            if (guestTimeline) {
                return guestTimeline.getControlledTime() || 0;
            }
            return timelineTime;
        }
        return timelineTime;
    };
    HaikuComponent.prototype.getPropertiesGroup = function (timelineName, flexId) {
        return (this.bytecode &&
            this.bytecode.timelines &&
            this.bytecode.timelines[timelineName] &&
            this.bytecode.timelines[timelineName]["haiku:" + flexId]);
    };
    HaikuComponent.prototype.getOutputValue = function (timelineName, timelineTime, flexId, propertyName) {
        return this.builder.grabValue(timelineName, flexId, null, // matchingElement - not needed?
        propertyName, this.getPropertiesGroup(timelineName, flexId), timelineTime, this, // hostInstance
        false, // isPatchOperation
        false, // skipCache
        false);
    };
    /**
     * Execute state transitions.
     */
    HaikuComponent.prototype.tickStateTransitions = function () {
        this.stateTransitionManager.tickStateTransitions();
    };
    /**
     * Reset states to initial values by using State Transitions. Default to linear
     */
    HaikuComponent.prototype.resetStatesToInitialValuesWithTransition = function (duration, curve) {
        if (curve === void 0) { curve = Curve_1.Curve.Linear; }
        // Build initial states
        var initialStates = assign_1.default({}, this.bytecode.states, this.config.states);
        for (var key in initialStates) {
            initialStates[key] = initialStates[key].value;
        }
        // Create state transition to initial state values
        this.stateTransitionManager.setState(initialStates, { curve: curve, duration: duration });
    };
    HaikuComponent.__name__ = 'HaikuComponent';
    HaikuComponent.PLAYER_VERSION = VERSION; // #LEGACY
    HaikuComponent.CORE_VERSION = VERSION;
    HaikuComponent.all = function () { return HaikuBase_1.default.getRegistryForClass(HaikuComponent); };
    return HaikuComponent;
}(HaikuElement_1.default));
exports.default = HaikuComponent;
function isBytecode(thing) {
    return thing && typeof thing === OBJECT_TYPE && thing.template;
}
function assertTemplate(template) {
    if (!template) {
        throw new Error('Empty template not allowed');
    }
    if (typeof template === OBJECT_TYPE) {
        if (template.attributes) {
            if (!template.attributes[HAIKU_ID_ATTRIBUTE]) {
                console.warn('[haiku core] bytecode template has no id');
            }
        }
        else {
            console.warn('[haiku core] bytecode template has no attributes');
        }
        if (!template.elementName) {
            console.warn('[haiku core] unexpected bytecode template format');
        }
        return template;
    }
    throw new Error('Unknown bytecode template format');
}
function stateSpecValidityCheck(stateSpec, stateSpecName) {
    if (stateSpec.type === 'any' ||
        stateSpec.type === '*' ||
        stateSpec.type === undefined ||
        stateSpec.type === null) {
        return true;
    }
    if (stateSpec.type === 'event' || stateSpec.type === 'listener') {
        if (typeof stateSpec.value !== 'function' &&
            stateSpec.value !== null &&
            stateSpec.value !== undefined) {
            console.error('Property value `' +
                stateSpecName +
                '` must be an event listener function');
            return false;
        }
        return true;
    }
    if (stateSpec.type === 'array') {
        if (!Array.isArray(stateSpec.value)) {
            console.error('Property value `' + stateSpecName + '` must be an array');
            return false;
        }
    }
    else if (stateSpec.type === 'object') {
        if (stateSpec.value && typeof stateSpec.value !== 'object') {
            console.error('Property value `' + stateSpecName + '` must be an object');
            return false;
        }
    }
    else {
        if (typeof stateSpec.value !== stateSpec.type) {
            console.error('Property value `' + stateSpecName + '` must be a `' + stateSpec.type + '`');
            return false;
        }
    }
    return true;
}
var msKeyToInt = function (msKey) {
    return parseInt(msKey, 10);
};
var propertyGroupNeedsExpressionEvaluated = function (propertyGroup, timelineTime) {
    var foundExpressionForTime = false;
    var roundedTime = Math.round(timelineTime);
    for (var propertyName in propertyGroup) {
        var propertyKeyframes = propertyGroup[propertyName];
        var keyframeMss = Object.keys(propertyKeyframes).map(msKeyToInt).sort();
        if (keyframeMss.length < 1) {
            return;
        }
        var leftBookend = 0;
        var rightBookend = keyframeMss[keyframeMss.length - 1];
        for (var i = 0; i < keyframeMss.length; i++) {
            var currMs = keyframeMss[i];
            if (currMs >= leftBookend && currMs <= roundedTime) {
                leftBookend = currMs;
            }
            if (currMs <= rightBookend && currMs >= roundedTime) {
                rightBookend = currMs;
            }
        }
        if (propertyKeyframes[leftBookend] && typeof propertyKeyframes[leftBookend].value === 'function') {
            foundExpressionForTime = true;
        }
        else if (propertyKeyframes[rightBookend] && typeof propertyKeyframes[rightBookend].value === 'function') {
            foundExpressionForTime = true;
        }
    }
    return foundExpressionForTime;
};
function connectInstanceNodeWithHostComponent(node, host) {
    var flexId = (node &&
        node.attributes &&
        (node.attributes[HAIKU_ID_ATTRIBUTE] || node.attributes.id));
    // Clear the previous listener (avoid multiple subscriptions to the same event)
    if (node.__listener) {
        node.__instance.off('*', node.__listener);
    }
    node.__listener = function (key) {
        var args = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            args[_i - 1] = arguments[_i];
        }
        host.routeEventToHandler("haiku:" + flexId, key, [node.__instance].concat(args));
    };
    // Bubble emitted events to the host component so it can subscribe declaratively
    node.__instance.on('*', node.__listener);
}
function expandTreeNode(node, parent, component, context, host, options, doConnectInstanceToNode) {
    if (options === void 0) { options = {}; }
    // Nothing to expand if the node happens to be text or unexpected type
    if (!node || typeof node !== 'object') {
        return node;
    }
    // Give it a pointer back to the host context; used by HaikuElement
    node.__context = context;
    // Platform renderers may depend on access to the parent
    node.__parent = parent;
    // Give instances a pointer to their node and vice versa
    if (doConnectInstanceToNode) {
        node.__instance = component;
        HaikuElement_1.default.connectNodeWithElement(node, node.__instance);
        if (host) {
            connectInstanceNodeWithHostComponent(node, host);
        }
    }
    if (typeof node.elementName === STRING_TYPE) {
        if (node.children) {
            for (var i = 0; i < node.children.length; i++) {
                node.children[i] = expandTreeNode(node.children[i], // node
                node, // parent
                component, // instance (component)
                context, host, options, false);
            }
        }
        return node;
    }
    if (isBytecode(node.elementName)) {
        var subtree = void 0;
        // Example structure showing how nodes and instances are related:
        // <div root> instance id=1
        //   <div>
        //     <div>
        //       <div wrap> subcomponent (instance id=2)
        //         <div root> instance id=2
        //           ...
        if (!node.__subcomponent) {
            // Note: .render and thus .expandTree are called by the constructor,
            // automatically connecting the root node to itself (see stanza above).
            node.__subcomponent = new HaikuComponent(node.elementName, context, // context
            component, // host
            Config_1.default.buildChildSafeConfig(__assign({}, context.config, options)), node);
            subtree = node.__subcomponent.getTemplate();
        }
        else {
            // Reassigning is necessary since these objects may have changed between
            // renders in the editing environment
            node.__subcomponent.context = context; // context
            node.__subcomponent.host = component; // host
            node.__subcomponent.container = node; // container
            subtree = node.__subcomponent.render(__assign({}, node.__subcomponent.config, Config_1.default.buildChildSafeConfig(options)));
            // Don't re-start any nested timelines that have been explicitly paused
            if (!node.__subcomponent.getDefaultTimeline().isExplicitlyPaused()) {
                node.__subcomponent.startTimeline(DEFAULT_TIMELINE_NAME);
            }
        }
        if (subtree) {
            node.children = [subtree];
        }
        return node;
    }
    // In case we got a __reference node or other unknown
    console.warn('[haiku core] cannot expand node');
    return node;
}
function findMatchingElementsByCssSelector(selector, flatManaTree, cache) {
    if (cache[selector]) {
        return cache[selector];
    }
    return cache[selector] = cssQueryList_1.default(flatManaTree, selector, CSS_QUERY_MAPPING);
}
function computeAndApplyTreeLayouts(tree, container, options, context) {
    if (!tree || typeof tree === 'string') {
        return void 0;
    }
    computeAndApplyNodeLayout(tree, container);
    if (!tree.children || tree.children.length < 1) {
        return void 0;
    }
    for (var i = 0; i < tree.children.length; i++) {
        computeAndApplyTreeLayouts(tree.children[i], tree, options, context);
    }
}
function computeAndApplyNodeLayout(node, parent) {
    // No point proceeding if our parent node doesn't have a computed layout
    if (parent && parent.layout && parent.layout.computed) {
        var parentSize = parent.layout.computed.size;
        // Don't assume the node has/needs a layout, for example, control-flow injectees
        if (node.layout && node.layout.matrix) {
            node.layout.computed = Layout3D_1.default.computeLayout(node.layout, node.layout.matrix, parentSize, Layout3D_1.default.computeSizeOfNodeContent(node));
        }
    }
}
function computeAndApplyPresetSizing(element, container, mode, deltas) {
    var elementWidth = element.layout.sizeAbsolute.x;
    var elementHeight = element.layout.sizeAbsolute.y;
    var containerWidth = container.layout.computed.size.x;
    var containerHeight = container.layout.computed.size.y;
    // I.e., the amount by which we'd have to multiply the element's scale to make it
    // exactly the same size as its container (without going above it)
    var scaleDiffX = containerWidth / elementWidth;
    var scaleDiffY = containerHeight / elementHeight;
    // This makes sure that the sizing occurs with respect to a correct and consistent origin point,
    // but only if the user didn't happen to explicitly set this value (we allow their override).
    if (!element.attributes.style['transform-origin']) {
        element.attributes.style['transform-origin'] = '0% 0% 0px';
    }
    // IMPORTANT: If any value has been changed on the element, you must set this to true.
    // Otherwise the changed object won't go into the deltas dictionary, and the element won't update.
    var changed = false;
    switch (mode) {
        // Make the base element its default scale, which is just a multiplier of one. This is the default.
        case 'normal':
            if (element.layout.scale.x !== 1.0 || element.layout.scale.y !== 1.0) {
                changed = true;
                element.layout.scale.x = element.layout.scale.y = 1.0;
            }
            break;
        // Stretch the element to fit the container on both x and y dimensions (distortion allowed)
        case 'stretch':
            if (scaleDiffX !== element.layout.scale.x) {
                changed = true;
                element.layout.scale.x = scaleDiffX;
            }
            if (scaleDiffY !== element.layout.scale.y) {
                changed = true;
                element.layout.scale.y = scaleDiffY;
            }
            break;
        // CONTAIN algorithm
        // see https://developer.mozilla.org/en-US/docs/Web/CSS/background-size?v=example
        // A keyword that scales the image as large as possible and maintains image aspect ratio
        // (image doesn't get squished). Image is letterboxed within the container.
        // When the image and container have different dimensions, the empty areas (either top/bottom of left/right)
        // are filled with the background-color.
        case 'contain':
        case true: // (Legacy.)
            var containScaleToUse = null;
            // We're looking for the larger of the two scales that still allows both dimensions to fit in the box
            // The rounding is necessary to avoid precision issues, where we end up comparing e.g. 2.0000000000001 to 2
            if (~~(scaleDiffX * elementWidth) <= containerWidth &&
                ~~(scaleDiffX * elementHeight) <= containerHeight) {
                containScaleToUse = scaleDiffX;
            }
            if (~~(scaleDiffY * elementWidth) <= containerWidth &&
                ~~(scaleDiffY * elementHeight) <= containerHeight) {
                if (containScaleToUse === null) {
                    containScaleToUse = scaleDiffY;
                }
                else {
                    if (scaleDiffY >= containScaleToUse) {
                        containScaleToUse = scaleDiffY;
                    }
                }
            }
            if (element.layout.scale.x !== containScaleToUse) {
                changed = true;
                element.layout.scale.x = containScaleToUse;
            }
            if (element.layout.scale.y !== containScaleToUse) {
                changed = true;
                element.layout.scale.y = containScaleToUse;
            }
            // Offset the translation so that the element remains centered within the letterboxing
            var containTranslationOffsetX = -(containScaleToUse * elementWidth - containerWidth) / 2;
            var containTranslationOffsetY = -(containScaleToUse * elementHeight - containerHeight) / 2;
            if (element.layout.translation.x !== containTranslationOffsetX) {
                changed = true;
                element.layout.translation.x = containTranslationOffsetX;
            }
            if (element.layout.translation.y !== containTranslationOffsetY) {
                changed = true;
                element.layout.translation.y = containTranslationOffsetY;
            }
            break;
        // COVER algorithm (inverse of CONTAIN)
        // see https://developer.mozilla.org/en-US/docs/Web/CSS/background-size?v=example
        // A keyword that is the inverse of contain. Scales the image as large as possible and maintains
        // image aspect ratio (image doesn't get squished). The image "covers" the entire width or height
        // of the container. When the image and container have different dimensions, the image is clipped
        // either left/right or top/bottom.
        case 'cover':
            var coverScaleToUse = null;
            // We're looking for the smaller of two scales that ensures the entire box is covered.
            // The rounding is necessary to avoid precision issues, where we end up comparing e.g. 2.0000000000001 to 2
            if (~~(scaleDiffX * elementHeight) >= containerHeight) {
                coverScaleToUse = scaleDiffX;
            }
            else if (~~(scaleDiffY * elementWidth) >= containerWidth) {
                coverScaleToUse = scaleDiffY;
            }
            else {
                coverScaleToUse = Math.max(scaleDiffX, scaleDiffY);
            }
            if (element.layout.scale.x !== coverScaleToUse) {
                changed = true;
                element.layout.scale.x = coverScaleToUse;
            }
            if (element.layout.scale.y !== coverScaleToUse) {
                changed = true;
                element.layout.scale.y = coverScaleToUse;
            }
            // Offset the translation so that the element remains centered despite clipping
            var coverTranslationOffsetX = -(coverScaleToUse * elementWidth - containerWidth) / 2;
            var coverTranslationOffsetY = -(coverScaleToUse * elementHeight - containerHeight) / 2;
            if (element.layout.translation.x !== coverTranslationOffsetX) {
                changed = true;
                element.layout.translation.x = coverTranslationOffsetX;
            }
            if (element.layout.translation.y !== coverTranslationOffsetY) {
                changed = true;
                element.layout.translation.y = coverTranslationOffsetY;
            }
            break;
    }
    if (changed && deltas) {
        // Part of the render/update system involves populating a dictionary of per-element updates,
        // which explains why instead of returning a value here, we assign the updated element.
        // The 'deltas' dictionary is passed to us from the render functions upstream of here.
        deltas[element.attributes[HAIKU_ID_ATTRIBUTE]] = element;
    }
}
var clone = function (value, binding) {
    if (!value) {
        return value;
    }
    if (typeof value === 'boolean') {
        return value;
    }
    if (typeof value === 'number') {
        return value;
    }
    if (typeof value === 'string') {
        return value;
    }
    if (typeof value === 'function') {
        var fn = function () {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i] = arguments[_i];
            }
            return value.call.apply(value, [binding].concat(args));
        };
        // Core decorates injectee functions with metadata properties
        for (var key in value) {
            if (value.hasOwnProperty(key)) {
                fn[key] = clone(value[key], binding);
            }
        }
        fn.__rfo = functionToRFO_1.default(value).__function;
        return fn;
    }
    if (Array.isArray(value)) {
        return value.map(function (el) { return clone(el, binding); });
    }
    // Don't try to clone anything other than plain objects
    if (typeof value === 'object' && value.constructor === Object) {
        var out = {};
        for (var key in value) {
            if (!value.hasOwnProperty(key) || key.slice(0, 2) === '__') {
                continue;
            }
            // If it looks like guest bytecode, don't clone it since
            // (a) we're passing down *our* function binding, which will break event handling and
            // (b) each HaikuComponent#constructor calls clone() on its own anyway
            if (key === 'elementName' && typeof value[key] !== 'string') {
                out[key] = value[key];
            }
            else {
                out[key] = clone(value[key], binding);
            }
        }
        return out;
    }
    return value;
};
//# sourceMappingURL=HaikuComponent.js.map