var Interfaces = /*#__PURE__*/Object.freeze({
    __proto__: null
});

class Model {
    constructor(data) {
        this.m_state = null;
        this.m_state = new Jade.StateBag();
        this.m_state.set(data, { silent: true });
        this.m_state.on("change", () => { this.onStateChanged(); });
    }
    get state() {
        return this.m_state;
    }
    onStateChanged() {
    }
    toJSON() {
        return this.state.toJSON();
    }
}

var Model$1 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    'default': Model
});

var DataModule = /*#__PURE__*/Object.freeze({
    __proto__: null,
    Interfaces: Interfaces,
    Model: Model$1
});

class BaseModel$1 extends Model {
    constructor(options) {
        super(options.data);
        this.m_root = options.root;
        this.state.unset("root");
    }
    get root() {
        return this.m_root;
    }
}

var BaseModel$2 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    'default': BaseModel$1
});

class ArbiterModel extends BaseModel$1 {
    constructor(options) {
        super(options);
        this.m_parent = options.parent;
    }
    get attributes() {
        return this.state.get("attributes");
    }
    get data() {
        return this.state.get("data");
    }
    get errors() {
        return this.state.get("errors");
    }
    get json() {
        return this.state.get("json");
    }
    get parent() {
        return this.m_parent;
    }
    get properties() {
        return this.state.get("properties");
    }
    get target() {
        return this.state.get("target");
    }
    get innerTarget() {
        return this.state.get("innerTarget");
    }
    get id() {
        return this.state.get("id");
    }
    get targetType() {
        return this.state.get("targetType");
    }
    get type() {
        return this.state.get("type");
    }
    toJSON() {
        let result = {
            attributes: this.attributes,
            data: this.data,
            errors: this.errors,
            properties: this.properties,
            innerTarget: this.innerTarget,
            target: this.target,
            targetType: this.targetType,
            type: this.type
        };
        if (this.id != null) {
            result.id = this.id;
        }
        if (this.json != null) {
            result.json = this.json;
        }
        return result;
    }
}

var ArbiterModel$1 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    'default': ArbiterModel
});

class FeatureModel$1 extends BaseModel$1 {
    constructor(options) {
        super(options);
        this.m_errors = [];
        this.m_index = null;
        this.m_postbackOnChangeThrottle = _.throttle((triggerPostback) => {
            if (triggerPostback) {
                this.root.postbackHandler();
            }
        }, 500);
        this.m_parent = options.parent;
        this.state.unset("parent");
        this.m_initializer = _.camelCase(this.binding.slice(this.binding.lastIndexOf(".") + 1));
    }
    get id() {
        return this.binding;
    }
    get attributes() {
        return this.state.get("attributes");
    }
    get binding() {
        return this.state.get("binding");
    }
    get parent() {
        return this.m_parent;
    }
    get initializers() {
        return this.root.initializers[this.m_initializer];
    }
    get index() {
        if (this.m_index === null) {
            this.m_index = this.getIndex();
        }
        return this.m_index;
    }
    get dynamics() {
        return this.state.get("dynamics", []);
    }
    get errors() {
        return this.root.errors.getByFeature(this.parent.index, this.index);
    }
    get featureSet() {
        return this.root.context.featureSet;
    }
    getEditors() {
        return this.root.editors.getEditorsByFeature(this);
    }
    getValue(id, defaultValue) {
        if (this.hasFlex(id)) {
            return this.state.get(`flex.values.${id}.value`, defaultValue);
        }
        return this.state.get(id, defaultValue);
    }
    setValue(id, value, options = {}) {
        let args = {
            name: id,
            oldValue: this.getValue(id),
            newValue: value,
            cancel: false
        };
        let resolvedId = this.hasFlex(id) ?
            `flex.values.${id}.value` : id;
        if (!options.silent && this.hasValueChanged(resolvedId, value)) {
            this.featureSet.controller.valueChanged(args);
            this.root
                .context
                .globals
                .events
                .featureValueChangedPublish(args);
        }
        if (args.cancel) {
            return;
        }
        this.state.set(resolvedId, value, options);
        this.m_postbackOnChangeThrottle(options.postback);
    }
    getRemoteValue(key, defaultValue) {
        return this.root.context.featureSet.getValue(key, defaultValue);
    }
    setRemoteValue(key, value, options = {}) {
        this.root.context.featureSet.setValue(key, value, options);
    }
    toJSON() {
        return this.state.attributes;
    }
    onStateChanged() {
        super.onStateChanged();
        let set = this.parent.parent;
        let group = this.parent;
        let feature;
        let changed = this.state.changed;
        Object.keys(changed).forEach(key => {
            set.valueChanged({
                group: group,
                feature: feature,
                name: key,
                value: this.state.get(key)
            });
        });
    }
    getIndex() {
        let result = null;
        this.parent.features.some((feature, index) => {
            if (feature.id === this.id) {
                result = index;
                return true;
            }
        });
        return result;
    }
    hasProperty(id) {
        return this.state.attributes[id] !== undefined;
    }
    hasFlex(id) {
        return this.state.attributes.flex?.values[id] !== undefined;
    }
    hasValueChanged(id, newValue) {
        return this.state.get(id) !== newValue;
    }
}

var GroupEventTypes;
(function (GroupEventTypes) {
    GroupEventTypes["GridLabelResize"] = "fb:group:{0}:grid:label:resize";
    GroupEventTypes["GridDescriptionResize"] = "fb:group:{0}:grid:description:resize";
    GroupEventTypes["GridInputSelected"] = "fb:group:{0}:grid:input:selected";
    GroupEventTypes["GridFilter"] = "fb:group:{0}:grid:filter";
    GroupEventTypes["CategoriesExpandAll"] = "fb:group:{0}:grid:categories:expand-all";
    GroupEventTypes["CategoriesCollapseAll"] = "fb:group:{0}:grid:categories:collapse-all";
    GroupEventTypes["CategoryToggle"] = "fb:group:{0}:grid:category:toggle";
})(GroupEventTypes || (GroupEventTypes = {}));
class GroupEvents extends Jade.EventBus {
    constructor(options) {
        super(options);
        this.m_groupId = options.groupId;
    }
    get groupId() {
        return this.m_groupId;
    }
    inputSelectedSubscribe(callback) {
        return this.subscribe(GroupEventTypes.GridInputSelected, callback);
    }
    labelResizeSubscribe(callback) {
        return this.subscribe(GroupEventTypes.GridLabelResize, callback);
    }
    filterSubscribe(callback) {
        return this.subscribe(GroupEventTypes.GridFilter, callback);
    }
    categoriesCollapseAllSubscribe(callback) {
        return this.subscribe(GroupEventTypes.CategoriesCollapseAll, callback);
    }
    categoriesExpandAllSubscribe(callback) {
        return this.subscribe(GroupEventTypes.CategoriesExpandAll, callback);
    }
    categoryToggleSubscribe(callback) {
        return this.subscribe(GroupEventTypes.CategoryToggle, callback);
    }
    inputSelectedPublish(e) {
        this.publish(GroupEventTypes.GridInputSelected, e);
    }
    labelResizePublish(e) {
        this.publish(GroupEventTypes.GridLabelResize, e);
    }
    filterPublish(e) {
        this.publish(GroupEventTypes.GridFilter, e);
    }
    categoriesCollapseAllPublish() {
        this.publish(GroupEventTypes.CategoriesCollapseAll);
    }
    categoriesExpandAllPublish() {
        this.publish(GroupEventTypes.CategoriesExpandAll);
    }
    categoryTogglePublish() {
        this.publish(GroupEventTypes.CategoryToggle);
    }
    subscribe(eventName, callback) {
        return super.subscribe(eventName.replaceAll("{0}", this.groupId), callback);
    }
    publish(eventName, args) {
        super.publish(eventName.replaceAll("{0}", this.groupId), args);
    }
}

class GroupEnvironment {
    constructor(options) {
        this.m_parent = options.parent;
        this.m_events = new GroupEvents({
            label: `Group: ${this.parent.id}`,
            groupId: this.parent.id
        });
        this.m_storage = this.parent.parent.parent
            .storage.groups.get(this.parent.id);
    }
    get events() {
        return this.m_events;
    }
    get parent() {
        return this.m_parent;
    }
    get storage() {
        return this.m_storage;
    }
}

class GroupModel$1 extends BaseModel$1 {
    constructor(options) {
        super(options);
        this.m_index = null;
        this.m_features = [];
        this.m_parent = options.parent;
        this.state.unset("parent");
        options.data.features.forEach(feature => {
            this.m_features[this.m_features.length] =
                new FeatureModel$1({
                    data: feature,
                    parent: this,
                    root: this.root
                });
        });
        this.m_environment = new GroupEnvironment({
            parent: this
        });
    }
    get locals() {
        return this.m_environment;
    }
    get globals() {
        return this.parent.parent.parent.context.globals;
    }
    get isMicro() {
        return this.assembler ==
            "ProspectDirector.Web.UI.FeatureSets.MicroEditorAssembler";
    }
    get assembler() {
        return this.state.get("assembler");
    }
    get features() {
        return this.m_features;
    }
    get id() {
        return this.state.get("id");
    }
    get name() {
        return this.state.get("name");
    }
    get parent() {
        return this.m_parent;
    }
    get index() {
        if (this.m_index === null) {
            this.m_index = this.getIndex();
        }
        return this.m_index;
    }
    get errors() {
        return this.root.errors.getByGroup(this.index);
    }
    getEditors() {
        return this.root.editors.getEditorsByGroup(this);
    }
    toJSON() {
        return {
            features: this.features.map(feature => {
                return feature.toJSON();
            }),
            id: this.id,
            name: this.name,
            assembler: this.assembler
        };
    }
    getIndex() {
        let result = null;
        this.parent.groups.some((group, index) => {
            if (group.id === this.id) {
                result = index;
                return true;
            }
        });
        return result;
    }
}

class ControllerModel$1 extends BaseModel$1 {
    constructor(options) {
        super(options);
        this.m_parent = options.parent;
        this.m_events = new Jade.EventBus();
        this.onInit();
    }
    get parent() {
        return this.m_parent;
    }
    onInit() {
    }
    onActivate() {
    }
    activate() {
        this.onActivate();
    }
    commit() {
        this.m_events.publish("___commit___");
    }
    toJSON() {
        return {};
    }
    onValueChanged(arg1, handler) {
        let names = _.isArray(arg1) ? arg1 : [arg1];
        names.forEach(name => {
            this.m_events.subscribe(name, handler);
        });
    }
    valueChanged(args) {
        this.m_events.publish(args.name, args);
    }
    onCommit(handler) {
        return this.m_events.subscribe("___commit___", handler);
    }
}

class SetModel$2 extends BaseModel$1 {
    constructor(options) {
        super(options);
        this.m_groups = [];
        this.m_parent = options.parent;
        this.state.unset("parent");
        options.data.groups.forEach((group, index) => {
            this.m_groups[this.m_groups.length] =
                new GroupModel$1({
                    data: group,
                    parent: this,
                    root: this.root
                });
        });
    }
    get parent() {
        return this.m_parent;
    }
    get groups() {
        return this.m_groups;
    }
    get controller() {
        this.ensureController();
        return this.m_controller;
    }
    get controllerTypeName() {
        return this.state.get("controller");
    }
    get valueChangesAreSilent() {
        return this.m_valueChangesAreSilent;
    }
    set valueChangesAreSilent(value) {
        this.m_valueChangesAreSilent = value;
    }
    valueChanged(args) {
        this.root.context.globals.events.publish("fb:feature:value:changed", {
            group: args.group,
            feature: args.feature,
            name: args.name,
            value: args.value
        });
    }
    onValueChanged(subscriber) {
        return this.root.context.globals.events.subscribe("fb:feature:value:changed", subscriber);
    }
    toJSON() {
        return {
            groups: this.groups.map(group => {
                return group.toJSON();
            })
        };
    }
    hasChanges() {
        let result = false;
        this.groups.forEach(group => {
            group.features.forEach(feature => {
                if (result == false) {
                    result = _.keys(feature.state.changed).length > 0;
                }
            });
        });
        return result;
    }
    getValue(key, defaultValue) {
        var result = undefined;
        this.groups.forEach((group) => {
            if (result == undefined) {
                group.features.forEach(feature => {
                    if (result === undefined) {
                        let value = feature.getValue(key);
                        if (value != undefined) {
                            result = value;
                        }
                    }
                });
            }
        });
        return result === undefined || result == null ?
            defaultValue : result;
    }
    setValue(key, value, options = {}) {
        let found = false;
        this.groups.forEach((group) => {
            if (found == false) {
                group.features.forEach(feature => {
                    if (found == false) {
                        let foundValue = feature.getValue(key);
                        if (foundValue !== undefined) {
                            feature.setValue(key, value, options);
                            found = true;
                        }
                    }
                });
            }
        });
    }
    ensureController() {
        if (!this.m_controller) {
            let type = Jade.Type.getType(this.controllerTypeName) || ControllerModel$1;
            if (type) {
                try {
                    this.m_controller = this.createController(type);
                }
                catch (ex) {
                    this.m_controller = this.createController(ControllerModel$1);
                }
            }
        }
    }
    createController(type) {
        return new type({
            parent: this,
            data: {},
            root: this.root
        });
    }
}

class ModelDictionary {
    constructor(data) {
        this.m_data = null;
        this.m_data = data;
    }
    get data() {
        return this.m_data;
    }
    get(key, defaultValue = null) {
        if (!this.exists(key)) {
            return defaultValue;
        }
        return this.data[key] == null ? defaultValue : this.data[key];
    }
    set(key, value) {
        if (name != null) {
            this.m_data[key] = value;
        }
    }
    exists(key) {
        return this.m_data[key] !== undefined;
    }
    delete(key) {
        delete this.m_data[key];
    }
    toJSON() {
        return this.data;
    }
}

var ContextAttributeKey;
(function (ContextAttributeKey) {
    ContextAttributeKey["ChangeSource"] = "changeSource";
    ContextAttributeKey["LabelResizePosition"] = "labelResizePosition";
    ContextAttributeKey["DescriptionResizePosition"] = "descriptionResizePosition";
    ContextAttributeKey["FilterTerm"] = "filterTerm";
})(ContextAttributeKey || (ContextAttributeKey = {}));
class ContextAttributes extends ModelDictionary {
    get hasChangeSource() {
        return this.get(ContextAttributeKey.ChangeSource) != null;
    }
    get changeSource() {
        let result = this.get(ContextAttributeKey.ChangeSource);
        return _.isString(result) ? {
            id: result.split("|")[0],
            name: result.split("|")[1]
        } : result;
    }
    set changeSource(value) {
        this.set(ContextAttributeKey.ChangeSource, value);
    }
    get labelResizePosition() {
        return this.get(ContextAttributeKey.LabelResizePosition);
    }
    set labelResizePosition(value) {
        this.set(ContextAttributeKey.LabelResizePosition, value);
    }
    get descriptionResizePosition() {
        return this.get(ContextAttributeKey.DescriptionResizePosition);
    }
    set descriptionResizePosition(value) {
        this.set(ContextAttributeKey.DescriptionResizePosition, value);
    }
    get filterTerm() {
        return this.get(ContextAttributeKey.FilterTerm);
    }
    set filterTerm(value) {
        this.set(ContextAttributeKey.FilterTerm, value);
    }
    deleteChangeSource() {
        this.delete(ContextAttributeKey.ChangeSource);
    }
}

var GlobalEventTypes;
(function (GlobalEventTypes) {
    GlobalEventTypes["ErrorsLoad"] = "fb:errors:load";
    GlobalEventTypes["FeaturesLoaded"] = "fb:features:loaded";
    GlobalEventTypes["FeatureValueChanged"] = "fb:feature:value:changed";
    GlobalEventTypes["NavigationSelect"] = "fb:navigation:select";
})(GlobalEventTypes || (GlobalEventTypes = {}));
class GlobalEvents extends Jade.EventBus {
    featuresLoadedSubscribe(callback) {
        return super.subscribe(GlobalEventTypes.FeaturesLoaded, callback);
    }
    featureValueChangedSubscribe(callback) {
        super.publish(GlobalEventTypes.FeatureValueChanged, callback);
    }
    navigationSelectSubscribe(callback) {
        return super.subscribe(GlobalEventTypes.NavigationSelect, callback);
    }
    featuresLoadedPublish() {
        super.publish(GlobalEventTypes.FeaturesLoaded);
    }
    featureValueChangedPublish(e) {
        super.publish(GlobalEventTypes.FeatureValueChanged, e);
    }
    navigationSelectPublish(e) {
        super.publish(GlobalEventTypes.NavigationSelect, e);
    }
}

class GlobalEnvironment {
    constructor(options) {
        this.m_parent = options.parent;
        this.m_events = new GlobalEvents({
            label: "GLOBAL"
        });
        this.m_storage = this.parent.storage.global;
    }
    get events() {
        return this.m_events;
    }
    get parent() {
        return this.m_parent;
    }
    get storage() {
        return this.m_storage;
    }
}

class StorageBase extends Model {
    constructor(options) {
        super(options);
        this.m_parent = options.parent;
        this.state.unset("parent");
    }
    get parent() {
        return this.m_parent;
    }
    toJSON() {
        return this.state.toJSON();
    }
}

class GlobalStorage extends StorageBase {
    get selectedGroupId() {
        return this.state.get("selectedGroupId");
    }
    set selectedGroupId(value) {
        this.state.set("selectedGroupId", value);
    }
    get postbackInputId() {
        return this.state.get("postbackInputId");
    }
    set postbackInputId(value) {
        this.state.set("postbackInputId", value);
    }
    get navScroll() {
        return this.state.get("navScroll", 0);
    }
    set navScroll(value) {
        this.state.set("navScroll", value);
    }
    toJSON() {
        return this.state.toJSON();
    }
}

class GroupStorage extends StorageBase {
    get collapsed() {
        return this.state.get("collapsed", []);
    }
    set collapsed(value) {
        this.state.set("collapsed", value);
    }
    get filter() {
        return this.state.get("filter");
    }
    set filter(value) {
        this.state.set("filter", value);
    }
    get focusedInputId() {
        return this.state.get("focusedInputId");
    }
    set focusedInputId(value) {
        this.state.set("focusedInputId", value);
    }
    get footerHeight() {
        return this.state.get("footerHeight");
    }
    set footerHeight(value) {
        this.state.set("footerHeight", value);
    }
    get id() {
        return this.state.get("id");
    }
    set id(value) {
        this.state.get("id", value);
    }
    get labelWidth() {
        return this.state.get("labelWidth");
    }
    set labelWidth(value) {
        this.state.set("labelWidth", value);
    }
    get scroll() {
        return this.state.get("scroll", 0);
    }
    set scroll(value) {
        this.state.set("scroll", value);
    }
}

class GroupStorageCollection extends Jade.Collection {
    get(groupId) {
        let result;
        this.each(group => {
            group.id == groupId ?
                result = group : null;
        });
        return result;
    }
    toJSON() {
        let result = [];
        this.each(storage => {
            result.push(storage.toJSON());
        });
        return result;
    }
}

const STORAGE_KEY = "__storage__";
class StorageManager extends Model {
    constructor(options) {
        super(options);
        this.m_parent = options.parent;
        this.state.unset("parent");
        this.deserialize();
    }
    get parent() {
        return this.m_parent;
    }
    get global() {
        return this.m_global;
    }
    get groups() {
        return this.m_groups;
    }
    get storageData() {
        return this.parent.attributes
            .get(STORAGE_KEY);
    }
    set storageData(value) {
        this.parent.attributes
            .set(STORAGE_KEY, value);
    }
    toJSON() {
        return {
            global: this.global.toJSON(),
            groups: this.groups.toJSON()
        };
    }
    serialize() {
        this.storageData = JSON.stringify(this.toJSON());
    }
    deserialize() {
        let data = null;
        if (this.storageData != null) {
            try {
                data = JSON.parse(this.storageData);
            }
            catch (ex) {
                data = this.storageData;
            }
        }
        this.m_global = new GlobalStorage(_.extend({
            parent: this.parent
        }, data?.global || {}));
        let groups = new GroupStorageCollection();
        this.parent.data.featureSet.groups.forEach(group => {
            groups.add(new GroupStorage({
                parent: this.parent,
                id: group.id
            }));
        });
        if (data?.groups) {
            data.groups.forEach(data => {
                let group = groups.get(data.id);
                if (group != null) {
                    group.state.set(data);
                }
            });
        }
        this.m_groups = groups;
    }
}

class ContextModel extends BaseModel$1 {
    constructor(options) {
        super(options);
        this.m_data = options.data;
        this.m_attributes = new ContextAttributes(options.data.attributes);
        this.m_storage = new StorageManager({
            parent: this
        });
        this.m_environment = new GlobalEnvironment({
            parent: this
        });
        this.m_parent = options.parent;
        this.state.unset("parent");
        this.m_arbiter = new ArbiterModel({
            data: options.data.arbiter,
            parent: this,
            root: options.root
        });
        this.m_set = new SetModel$2({
            data: options.data.featureSet,
            parent: this,
            root: options.root
        });
    }
    get attributes() {
        return this.m_attributes;
    }
    get arbiter() {
        return this.m_arbiter;
    }
    get featureSet() {
        return this.m_set;
    }
    get parent() {
        return this.m_parent;
    }
    get globals() {
        return this.m_environment;
    }
    get storage() {
        return this.m_storage;
    }
    get data() {
        return this.m_data;
    }
    toJSON() {
        this.storage.serialize();
        return {
            arbiter: this.arbiter.toJSON(),
            featureSet: this.featureSet.toJSON(),
            attributes: this.attributes.toJSON()
        };
    }
}

var ContextModel$1 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    'default': ContextModel
});

class EditorModel extends Model {
    constructor(options) {
        super(options.data);
        this.m_feature = options.feature;
        this.state.unset("feature", { silent: true });
    }
    get feature() {
        return this.m_feature;
    }
    get group() {
        return this.feature.parent;
    }
    get set() {
        return this.group.parent;
    }
    get root() {
        return this.set.root;
    }
    get controller() {
        return this.set.controller;
    }
    get attributes() {
        return this.state.get("attributes");
    }
    get script() {
        return this.state.get("script");
    }
    get uid() {
        return this.state.get("uid");
    }
    getFeatureValue(name, defaultValue) {
        return this.feature.getValue(name, defaultValue);
    }
    setFeatureValue(name, value, options = {}) {
        this.setSilence(options);
        this.setChangeSource(options);
        this.feature.setValue(name, value, options);
    }
    getRemoteFeatureValue(name, defaultValue) {
        return this.feature.getRemoteValue(name, defaultValue);
    }
    setRemoteFeatureValue(name, value, options = {}) {
        this.setSilence(options);
        this.setChangeSource(options);
        this.feature.setRemoteValue(name, value, options);
    }
    setSilence(options) {
        let valueChangesSilenced = this.root.context.featureSet.valueChangesAreSilent;
        options.silent = options.silent || valueChangesSilenced;
    }
    setChangeSource(options) {
        if (options.silent !== true && options.postback === true) {
            this.root.context.storage.global.postbackInputId = this.uid;
        }
    }
}

var EditorModel$1 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    'default': EditorModel
});

class MicroEditorModel extends EditorModel {
    get name() {
        return this.state.get("name", "");
    }
    get category() {
        return this.state.get("category", "");
    }
    get description() {
        return this.state.get("description", "");
    }
    get order() {
        return this.state.get("order", 0);
    }
    get label() {
        return this.state.get("label", "");
    }
    get required() {
        return this.attributes["__required__"] == true;
    }
    get readOnly() {
        return this.attributes["__readonly__"] == true;
    }
    get enabled() {
        return this.attributes["__enabled__"] == null
            || this.attributes["__enabled__"] == true;
    }
    get postback() {
        return this.attributes["__postback__"] != null;
    }
    getFeaturePrimaryValue(defaultValue) {
        return this.getFeatureValue(this.name, defaultValue);
    }
    setFeaturePrimaryValue(value, options = {}) {
        this.setFeatureValue(this.name, value, options);
    }
    getFeatureValue(name, defaultValue) {
        return super.getFeatureValue(name, defaultValue);
    }
    setFeatureValue(name, value, options = {}) {
        options.postback = (options.postback == undefined ?
            this.postback : options.postback);
        super.setFeatureValue(name, value, options);
    }
    getFeatureValueByProp(prop, defaultValue) {
        let name = this.state.get(prop);
        let result = null;
        if (name != null) {
            name = _.camelCase(name);
            result = this.feature.getValue(name, defaultValue);
        }
        return result;
    }
    setFeatureValueByProp(prop, value) {
        let name = this.state.get(prop);
        if (name != null) {
            name = _.camelCase(name);
            this.feature.setValue(name, value);
        }
    }
    getAttribute(name, defaultValue) {
        return this.attributes[name] || defaultValue;
    }
    getFeatureValueByAttributeMap(name, defaultValue) {
        let result = null;
        let propertyName = this.getAttribute(name);
        if (propertyName != null) {
            result = this.getFeatureValueByProp(propertyName, defaultValue);
        }
        return result || defaultValue;
    }
    isPropertyDefined(name) {
        return this.state.has(name);
    }
    static isMicroEditor(item) {
        return item["label"] !== undefined
            && item["category"] !== undefined
            && item["description"] !== undefined
            && item["name"] !== undefined
            && item["order"] !== undefined;
    }
}

class MacroEditorModel extends EditorModel {
    get primaryValueName() {
        return this.state.get("primaryValue");
    }
    get primaryValue() {
        return this.getFeatureValue(this.primaryValueName);
    }
    set primaryValue(value) {
        this.setFeatureValue(this.primaryValueName, value);
    }
}

class EditorCollection {
    constructor(data) {
        this.m_data = {};
        this.m_source = data;
    }
    get data() {
        return this.m_data;
    }
    get source() {
        return this.m_source;
    }
    getEditorsByFeature(feature) {
        let result = [];
        let groupId = feature.parent.id;
        let featureId = feature.id;
        this.loadEditors(feature);
        let groupExists = this.data[groupId] != undefined;
        let featureExists = this.data[groupId][featureId] != null;
        if (groupExists && featureExists) {
            result = this.data[groupId][featureId];
        }
        return result;
    }
    getEditorsByGroup(group) {
        let result = [];
        if (this.data[group.id] == null) {
            group.features.forEach(feature => {
                let editors = this.getEditorsByFeature(feature);
                result = result.concat(editors);
            });
        }
        result = this.sortEditors(result);
        return result;
    }
    toJSON() {
        return this.source;
    }
    loadEditors(targetFeature) {
        let groupId = targetFeature.parent.id;
        let featureId = targetFeature.binding;
        let editors = [];
        if (this.data[groupId] == undefined) {
            this.data[groupId] = {};
        }
        if (this.data[groupId][featureId] == undefined) {
            editors = this.data[groupId][featureId] = [];
        }
        if (editors.length > 0) {
            return;
        }
        Object.keys(this.source).forEach(key => {
            this.source[key].forEach(item => {
                if (item.type == targetFeature.binding) {
                    let result = null;
                    if (MicroEditorModel.isMicroEditor(item)) {
                        result = new MicroEditorModel({
                            data: item,
                            feature: targetFeature
                        });
                    }
                    else {
                        result = new MacroEditorModel({
                            data: item,
                            feature: targetFeature
                        });
                    }
                    editors[editors.length] = result;
                }
            });
        });
    }
    sortEditors(editors) {
        let result = editors.slice(0);
        let padding = (value) => {
            return Jade.String.padLeft(value, "0", 10);
        };
        result.sort((a, b) => {
            let aSortValue = "";
            let bSortValue = "";
            if (a instanceof MicroEditorModel) {
                aSortValue = `${a.category}_${padding(a.order)}_${a.label}`;
            }
            if (b instanceof MicroEditorModel) {
                bSortValue = `${b.category}_${padding(b.order)}_${b.label}`;
            }
            return aSortValue.toLowerCase().localeCompare(bSortValue.toLowerCase());
        });
        return result;
    }
}

var EditorCollection$1 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    'default': EditorCollection
});

class ErrorModel {
    constructor(data) {
        this.m_message = data.message;
        this.m_code = data.code;
        this.m_key = data.key;
        this.m_tags = data.tags;
        this.parseKey();
    }
    get code() {
        return this.m_code;
    }
    get featureIndex() {
        return this.m_featureIndex;
    }
    get groupIndex() {
        return this.m_groupIndex;
    }
    get key() {
        return this.m_key;
    }
    get message() {
        return this.m_message;
    }
    get propertyName() {
        return this.m_propertyName;
    }
    get tags() {
        return this.m_tags;
    }
    toJSON() {
        return {
            code: this.code,
            key: this.key,
            message: this.message,
            tags: this.tags
        };
    }
    parseKey() {
        let parts = this.key.split(".");
        this.m_groupIndex = parseInt(parts[1].replace("Groups[", "").replace("]", ""));
        this.m_featureIndex = parseInt(parts[2].replace("Features[", "").replace("]", ""));
        this.m_propertyName = parts[3];
    }
}

var ErrorModel$1 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    'default': ErrorModel
});

var ErrorModelCollectionEventType;
(function (ErrorModelCollectionEventType) {
    ErrorModelCollectionEventType[ErrorModelCollectionEventType["add"] = 1] = "add";
    ErrorModelCollectionEventType[ErrorModelCollectionEventType["clear"] = 2] = "clear";
    ErrorModelCollectionEventType[ErrorModelCollectionEventType["remove"] = 3] = "remove";
    ErrorModelCollectionEventType[ErrorModelCollectionEventType["load"] = 4] = "load";
})(ErrorModelCollectionEventType || (ErrorModelCollectionEventType = {}));
class ErrorModelCollectionEvent {
    constructor(options) {
        this.m_error = options.error;
        this.m_errors = options.errors;
        this.m_eventType = options.eventType;
    }
    get errors() {
        return this.m_errors;
    }
    get error() {
        return this.m_error;
    }
    get eventType() {
        return this.m_eventType;
    }
}

class ErrorModelCollection {
    constructor(errors = []) {
        this.m_collection = new Jade.Collection();
        this.m_eventBus = new Jade.EventBus({
            label: "error-model-collection"
        });
        errors?.forEach(item => {
            this.collection.add(new ErrorModel(item));
        });
    }
    get collection() {
        return this.m_collection;
    }
    get eventBus() {
        return this.m_eventBus;
    }
    get eventName() {
        return "error-model-collection:event";
    }
    add(item) {
        let result = this.collection.add(item);
        this.publish(item, ErrorModelCollectionEventType.add);
        return result;
    }
    clear() {
        this.collection.clear();
        this.publish(null, ErrorModelCollectionEventType.clear);
    }
    getByGroup(groupIndex) {
        let result = [];
        this.collection.each(item => {
            item.groupIndex === groupIndex ?
                result.push(item) : null;
        });
        return result;
    }
    getByFeature(groupIndex, featureIndex) {
        let result = [];
        this.getByGroup(groupIndex).forEach(item => {
            item.featureIndex === featureIndex ?
                result.push(item) : null;
        });
        return result;
    }
    load(items = []) {
        items = items || [];
        this.clear();
        items.forEach(error => {
            this.collection.add(new ErrorModel(error));
        });
        this.publish(null, ErrorModelCollectionEventType.load);
    }
    remove(item) {
        let result = this.collection.remove(item);
        this.publish(item, ErrorModelCollectionEventType.remove);
        return result;
    }
    subscribe(callback, type = null) {
        let result = type != null ?
            this.eventBus.subscribe(`${this.eventName}:${type}`, callback) :
            this.eventBus.subscribe(this.eventName, callback);
        return result;
    }
    toJSON() {
        return this.collection.transform((item) => {
            return item.toJSON();
        });
    }
    publish(item, eventType = null) {
        let target = new ErrorModelCollectionEvent({
            error: item,
            errors: this,
            eventType: ErrorModelCollectionEventType.add
        });
        this.eventBus.publish(`${this.eventName}:${eventType}`, target);
    }
}

class RootModel extends Model {
    constructor(options) {
        super(options);
        this.m_errors = new ErrorModelCollection();
        this.m_context = new ContextModel({
            root: this,
            parent: this,
            data: options.context,
        });
        this.m_editorCollection =
            new EditorCollection(options.editors);
        this.errors.load(options.errors);
    }
    get context() {
        return this.m_context;
    }
    get editors() {
        return this.m_editorCollection;
    }
    get initialized() {
        return this.state.get("initialized");
    }
    get initializers() {
        return this.state.get("initializers");
    }
    get errors() {
        return this.m_errors;
    }
    get postbackHandler() {
        return this.m_postbackHandlerFn || (() => { });
    }
    set postbackHandler(callback) {
        this.m_postbackHandlerFn = callback;
    }
    toJSON() {
        return {
            context: this.context.toJSON(),
            editors: this.editors.toJSON(),
            errors: this.errors.toJSON(),
            initialized: this.initialized,
            intializers: this.initializers
        };
    }
}

var RootModel$1 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    'default': RootModel
});

class BaseModel extends Model {
    constructor(options) {
        super(options.data);
        this.m_root = options.root;
        this.state.unset("root");
    }
    get root() {
        return this.m_root;
    }
}

class FeatureModel extends BaseModel {
    constructor(options) {
        super(options);
        this.m_errors = [];
        this.m_index = null;
        this.m_postbackOnChangeThrottle = _.throttle((triggerPostback) => {
            if (triggerPostback) {
                this.root.postbackHandler();
            }
        }, 500);
        this.m_parent = options.parent;
        this.state.unset("parent");
        this.m_initializer = _.camelCase(this.binding.slice(this.binding.lastIndexOf(".") + 1));
    }
    get id() {
        return this.binding;
    }
    get attributes() {
        return this.state.get("attributes");
    }
    get binding() {
        return this.state.get("binding");
    }
    get parent() {
        return this.m_parent;
    }
    get initializers() {
        return this.root.initializers[this.m_initializer];
    }
    get index() {
        if (this.m_index === null) {
            this.m_index = this.getIndex();
        }
        return this.m_index;
    }
    get dynamics() {
        return this.state.get("dynamics", []);
    }
    get errors() {
        return this.root.errors.getByFeature(this.parent.index, this.index);
    }
    get featureSet() {
        return this.root.context.featureSet;
    }
    getEditors() {
        return this.root.editors.getEditorsByFeature(this);
    }
    getValue(id, defaultValue) {
        if (this.hasFlex(id)) {
            return this.state.get(`flex.values.${id}.value`, defaultValue);
        }
        return this.state.get(id, defaultValue);
    }
    setValue(id, value, options = {}) {
        let args = {
            name: id,
            oldValue: this.getValue(id),
            newValue: value,
            cancel: false
        };
        let resolvedId = this.hasFlex(id) ?
            `flex.values.${id}.value` : id;
        if (!options.silent && this.hasValueChanged(resolvedId, value)) {
            this.featureSet.controller.valueChanged(args);
            this.root
                .context
                .globals
                .events
                .featureValueChangedPublish(args);
        }
        if (args.cancel) {
            return;
        }
        this.state.set(resolvedId, value, options);
        this.m_postbackOnChangeThrottle(options.postback);
    }
    getRemoteValue(key, defaultValue) {
        return this.root.context.featureSet.getValue(key, defaultValue);
    }
    setRemoteValue(key, value, options = {}) {
        this.root.context.featureSet.setValue(key, value, options);
    }
    toJSON() {
        return this.state.attributes;
    }
    onStateChanged() {
        super.onStateChanged();
        let set = this.parent.parent;
        let group = this.parent;
        let feature;
        let changed = this.state.changed;
        Object.keys(changed).forEach(key => {
            set.valueChanged({
                group: group,
                feature: feature,
                name: key,
                value: this.state.get(key)
            });
        });
    }
    getIndex() {
        let result = null;
        this.parent.features.some((feature, index) => {
            if (feature.id === this.id) {
                result = index;
                return true;
            }
        });
        return result;
    }
    hasProperty(id) {
        return this.state.attributes[id] !== undefined;
    }
    hasFlex(id) {
        return this.state.attributes.flex?.values[id] !== undefined;
    }
    hasValueChanged(id, newValue) {
        return this.state.get(id) !== newValue;
    }
}

class GroupModel extends BaseModel {
    constructor(options) {
        super(options);
        this.m_index = null;
        this.m_features = [];
        this.m_parent = options.parent;
        this.state.unset("parent");
        options.data.features.forEach(feature => {
            this.m_features[this.m_features.length] =
                new FeatureModel({
                    data: feature,
                    parent: this,
                    root: this.root
                });
        });
        this.m_environment = new GroupEnvironment({
            parent: this
        });
    }
    get locals() {
        return this.m_environment;
    }
    get globals() {
        return this.parent.parent.parent.context.globals;
    }
    get isMicro() {
        return this.assembler ==
            "ProspectDirector.Web.UI.FeatureSets.MicroEditorAssembler";
    }
    get assembler() {
        return this.state.get("assembler");
    }
    get features() {
        return this.m_features;
    }
    get id() {
        return this.state.get("id");
    }
    get name() {
        return this.state.get("name");
    }
    get parent() {
        return this.m_parent;
    }
    get index() {
        if (this.m_index === null) {
            this.m_index = this.getIndex();
        }
        return this.m_index;
    }
    get errors() {
        return this.root.errors.getByGroup(this.index);
    }
    getEditors() {
        return this.root.editors.getEditorsByGroup(this);
    }
    toJSON() {
        return {
            features: this.features.map(feature => {
                return feature.toJSON();
            }),
            id: this.id,
            name: this.name,
            assembler: this.assembler
        };
    }
    getIndex() {
        let result = null;
        this.parent.groups.some((group, index) => {
            if (group.id === this.id) {
                result = index;
                return true;
            }
        });
        return result;
    }
}

class ControllerModel extends BaseModel {
    constructor(options) {
        super(options);
        this.m_parent = options.parent;
        this.m_events = new Jade.EventBus();
        this.onInit();
    }
    get parent() {
        return this.m_parent;
    }
    onInit() {
    }
    onActivate() {
    }
    activate() {
        this.onActivate();
    }
    commit() {
        this.m_events.publish("___commit___");
    }
    toJSON() {
        return {};
    }
    onValueChanged(arg1, handler) {
        let names = _.isArray(arg1) ? arg1 : [arg1];
        names.forEach(name => {
            this.m_events.subscribe(name, handler);
        });
    }
    valueChanged(args) {
        this.m_events.publish(args.name, args);
    }
    onCommit(handler) {
        return this.m_events.subscribe("___commit___", handler);
    }
}

class SetModel extends BaseModel {
    constructor(options) {
        super(options);
        this.m_groups = [];
        this.m_parent = options.parent;
        this.state.unset("parent");
        options.data.groups.forEach((group, index) => {
            this.m_groups[this.m_groups.length] =
                new GroupModel({
                    data: group,
                    parent: this,
                    root: this.root
                });
        });
    }
    get parent() {
        return this.m_parent;
    }
    get groups() {
        return this.m_groups;
    }
    get controller() {
        this.ensureController();
        return this.m_controller;
    }
    get controllerTypeName() {
        return this.state.get("controller");
    }
    get valueChangesAreSilent() {
        return this.m_valueChangesAreSilent;
    }
    set valueChangesAreSilent(value) {
        this.m_valueChangesAreSilent = value;
    }
    valueChanged(args) {
        this.root.context.globals.events.publish("fb:feature:value:changed", {
            group: args.group,
            feature: args.feature,
            name: args.name,
            value: args.value
        });
    }
    onValueChanged(subscriber) {
        return this.root.context.globals.events.subscribe("fb:feature:value:changed", subscriber);
    }
    toJSON() {
        return {
            groups: this.groups.map(group => {
                return group.toJSON();
            })
        };
    }
    hasChanges() {
        let result = false;
        this.groups.forEach(group => {
            group.features.forEach(feature => {
                if (result == false) {
                    result = _.keys(feature.state.changed).length > 0;
                }
            });
        });
        return result;
    }
    getValue(key, defaultValue) {
        var result = undefined;
        this.groups.forEach((group) => {
            if (result == undefined) {
                group.features.forEach(feature => {
                    if (result === undefined) {
                        let value = feature.getValue(key);
                        if (value != undefined) {
                            result = value;
                        }
                    }
                });
            }
        });
        return result === undefined || result == null ?
            defaultValue : result;
    }
    setValue(key, value, options = {}) {
        let found = false;
        this.groups.forEach((group) => {
            if (found == false) {
                group.features.forEach(feature => {
                    if (found == false) {
                        let foundValue = feature.getValue(key);
                        if (foundValue !== undefined) {
                            feature.setValue(key, value, options);
                            found = true;
                        }
                    }
                });
            }
        });
    }
    ensureController() {
        if (!this.m_controller) {
            let type = Jade.Type.getType(this.controllerTypeName) || ControllerModel;
            if (type) {
                try {
                    this.m_controller = this.createController(type);
                }
                catch (ex) {
                    this.m_controller = this.createController(ControllerModel);
                }
            }
        }
    }
    createController(type) {
        return new type({
            parent: this,
            data: {},
            root: this.root
        });
    }
}

var SetModel$1 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    'default': SetModel
});

var ModelsModule = /*#__PURE__*/Object.freeze({
    __proto__: null,
    AribterModel: ArbiterModel$1,
    BaseModel: BaseModel$2,
    ContextModel: ContextModel$1,
    EditorCollection: EditorCollection$1,
    EditorModel: EditorModel$1,
    ErrorModel: ErrorModel$1,
    FeatureModel: ArbiterModel$1,
    GroupModel: ArbiterModel$1,
    MacroEditorModel: ArbiterModel$1,
    MicroEditorModel: ArbiterModel$1,
    RootModel: RootModel$1,
    SetModel: SetModel$1
});

class BaseView extends Jade.UI.Control {
    constructor(options) {
        super(options);
        this.m_source = options.source;
        this.state.unset("source");
    }
    get source() {
        return this.m_source;
    }
    set source(value) {
        this.m_source = value;
    }
    get globals() {
        return this.source.root.context.globals;
    }
    get subscribers() {
        this.ensureSubscribers();
        return this.m_subscribers;
    }
    onDestroy(e) {
        super.onDestroy(e);
        this.subscribers.forEach(subscriber => {
            subscriber.unsubscribe();
        });
    }
    ensureSubscribers() {
        if (this.m_subscribers == null) {
            this.m_subscribers = [];
        }
    }
}

class AssemblerView extends BaseView {
    constructor(options) {
        super(options);
    }
    get visible() {
        return this.state.get("visible", false);
    }
    get source() {
        return super.source;
    }
    get globals() {
        return this.source.root.context.globals;
    }
    get isDefaultView() {
        return this.state.get("isDefaultView", false);
    }
    get hasBeenViewed() {
        return this.state.get("hasBeenViewed", false);
    }
    set hasBeenViewed(value) {
        this.state.set("hasBeenViewed", value);
    }
    get hasFocus() {
        return this.state.get("hasFocus", false);
    }
    set hasFocus(value) {
        this.state.set("hasFocus", value);
    }
    template() {
        return `<div class="-fb-layout-group"></div>`;
    }
    show() {
        this.node.show();
        this.focusView();
    }
    hide() {
        this.node.hide();
        this.blurView();
    }
    onActivate(e) {
        super.onActivate(e);
    }
    onComplete(e) {
        super.onComplete(e);
        if (this.isDefaultView) {
            this.focusView();
        }
        this.node.css({
            display: this.visible ? "block" : "none"
        });
    }
    focusView() {
        this.hasFocus = true;
        if (!this.hasBeenViewed) {
            this.onInitialView();
            this.hasBeenViewed = true;
        }
        this.onFocusedView();
    }
    blurView() {
        this.hasFocus = false;
        this.onBlurView();
    }
    onInitialView() {
    }
    onBlurView() {
    }
    onFocusedView() {
    }
}

class EditorExtenders {
    constructor() {
        this.m_dictionary = {};
    }
    get items() {
        let result = [];
        for (let key in this.m_dictionary) {
            result.push(this.m_dictionary[key]);
        }
        return result;
    }
    get(id) {
        return this.m_dictionary[id];
    }
    getId(factory) {
        let result = null;
        for (let key in this.m_dictionary) {
            if (this.m_dictionary[key] === factory) {
                result = key;
                break;
            }
        }
        return result;
    }
    add(id, factory) {
        this.m_dictionary[id] = factory;
    }
}

class FeatureBlend {
    static get editorExtenders() {
        this.ensureEditorExtenders();
        return this.m_editorExtenders;
    }
    static get errorTooltipSkin() {
        return this.m_errorTooltipSkin;
    }
    static set errorTooltipSkin(value) {
        this.m_errorTooltipSkin = value;
    }
    static get defaultPopUpSkin() {
        return this.m_defaultPopUpSkin;
    }
    static set defaultPopUpSkin(value) {
        this.m_defaultPopUpSkin = value;
    }
    static get onVariableEdit() {
        return this.m_onVariableEdit;
    }
    static set onVariableEdit(value) {
        this.m_onVariableEdit = value;
    }
    static addEditorType(id, type) {
        this.m_editorTypes[id] = type;
    }
    static getEditorType(id) {
        return this.m_editorTypes[id];
    }
    static removeEditorType(id) {
        delete this.m_editorTypes[id];
    }
    static ensureEditorExtenders() {
        if (this.m_editorExtenders == null) {
            this.m_editorExtenders = new EditorExtenders();
        }
    }
}
FeatureBlend.m_editorTypes = {};
FeatureBlend.m_errorTooltipSkin = "";
FeatureBlend.m_defaultPopUpSkin = "themed";

let Jade$1 = window["Jade"];
class EditorBase extends Jade$1.UI.Control {
    constructor(options) {
        super(options);
    }
    get focusCallback() {
        return this.state.get("focus", () => { });
    }
    focus() {
        this.focusCallback();
    }
    onValidationErrors() {
    }
}
class EditorBaseOf extends EditorBase {
    constructor(options) {
        super(options);
        this.m_source = options.source;
        this.state.unset("source", { silent: true });
        this.m_loadErrorsSubscription =
            this.source.root.errors.subscribe((e) => {
                this.onValidationErrors();
            }, ErrorModelCollectionEventType.load);
        this.m_clearErrorsSubscription =
            this.source.root.errors.subscribe((e) => {
                this.onValidationErrors();
            }, ErrorModelCollectionEventType.clear);
        this.onCreated();
    }
    get source() {
        return this.m_source;
    }
    get feature() {
        return this.source.feature;
    }
    get featureGroup() {
        return this.feature.parent;
    }
    get featureSet() {
        return this.featureGroup.parent;
    }
    get globals() {
        return this.source.root.context.globals;
    }
    get locals() {
        return this.featureGroup.locals;
    }
    onDestroy(e) {
        super.onDestroy(e);
        if (this.m_clearErrorsSubscription) {
            this.m_clearErrorsSubscription.unsubscribe();
        }
        if (this.m_loadErrorsSubscription) {
            this.m_loadErrorsSubscription.unsubscribe();
        }
    }
    onCreated() {
    }
    getPropertyName(name) {
        return _.camelCase(this.source.state.get(name));
    }
}

class MacroEditorBase extends EditorBaseOf {
    constructor(options) {
        super(options);
        this.source.controller.onCommit(() => {
            this.onCommit();
        });
    }
    get fill() {
        return this.state.get("fill", false);
    }
    get isPrimaryEditor() {
        return this.source.feature.index == 0;
    }
    blur() {
    }
    resize() {
        this.onResized();
    }
    onActivate(e) {
        super.onActivate(e);
        if (this.fill) {
            this.node.css({
                position: "absolute",
                inset: 0
            });
        }
    }
    onCommit() {
    }
    onResized() {
    }
}

class MacroView extends AssemblerView {
    constructor() {
        super(...arguments);
        this.m_editorViews = [];
    }
    get editorModels() {
        if (this.m_editorModels == null) {
            this.m_editorModels = this.source.getEditors();
        }
        return this.m_editorModels;
    }
    get editorViews() {
        return this.controls.getByType(MacroEditorBase).toArray();
    }
    template() {
        return `<div class="-fb-layout-group -fb-layout-group-macro"></div>`;
    }
    onActivate(e) {
        super.onActivate(e);
        this.createEditors();
        Jade.Application
            .instance
            .contexts
            .current
            .events
            .subscribe("Window:Resized", () => {
            this.triggerEditorViewsResize();
        });
    }
    onInitialView() {
        this.addEditorsToControlCollection();
        this.triggerEditorViewsResize(50);
    }
    onFocusedView() {
        this.editorViews.forEach((view, index) => {
            view.focus();
        });
        this.triggerEditorViewsResize(50);
    }
    onBlurView() {
        this.editorViews.forEach((view, index) => {
            view.blur();
        });
    }
    addEditorsToControlCollection() {
        this.m_editorViews.forEach(view => {
            this.controls.add(view);
        });
    }
    createEditors() {
        this.editorModels.forEach((editor) => {
            this.createEditor(editor, FeatureBlend.getEditorType(editor.script));
        });
    }
    createEditor(model, type) {
        if (type == null) {
            return;
        }
        let editor = new type({
            source: model,
            fill: this.editorModels.length === 1
        });
        if (editor == null) {
            return;
        }
        this.m_editorViews.push(editor);
    }
    triggerEditorViewsResize(delay = 0) {
        setTimeout(() => {
            this.editorViews.forEach(view => {
                view.resize();
            });
        }, delay);
    }
}

class HeaderView extends BaseView {
    constructor(options) {
        super(options);
    }
    get source() {
        return super.source;
    }
    get filterNode() {
        return this.node.find("input[role=filter]");
    }
    get rightGroupNode() {
        return this.node.children("div:last-child");
    }
    get leftGroupNode() {
        return this.node.children("div:first-child");
    }
    template() {
        return `<div class="-fb-grid-header">
            <div role="group-l"></div>
            <div role="group-r"></div>
        </div>`;
    }
    onActivate(e) {
        super.onActivate(e);
        this.buttons_init();
        this.filter_init();
    }
    onComplete(e) {
        super.onComplete(e);
        let filter = this.source.locals.storage?.filter;
        if (filter != null) {
            this.filterNode.val(filter);
        }
    }
    filter_init() {
        this.rightGroupNode.css({
            display: "inline-block",
            width: "200px",
            boxSizing: "border-box",
            paddingBottom: "3px"
        });
        this.rightGroupNode.append(`<input type="text" role="filter" placeholder="Filter..." class="-fb-grid-header-filter" />`);
        this.filterNode.on(Jade.BrowserEvent.KEYUP, (e) => {
            e.cancelBubble = true;
            e.stopPropagation();
            e.returnValue = false;
            this.source.locals.events.filterPublish({
                term: this.filterNode.val()
            });
            return false;
        });
    }
    buttons_init() {
        this.leftGroupNode.css({
            display: "inline-block",
            boxSizing: "border-box"
        });
        this.addButton("simple-down", () => {
            this.source.locals.events.categoriesExpandAllPublish();
        });
        this.addButton("simple-right", () => {
            this.source.locals.events.categoriesCollapseAllPublish();
        });
    }
    addButton(icon, callback = () => { }) {
        let button = $(`<div class="-fb-grid-header-button">
                    <i class="icofont-${icon}"></i>
                </div>`);
        button.on(Jade.BrowserEvent.CLICK, () => {
            callback();
        });
        this.leftGroupNode.append(button);
    }
}

class InputEditorView extends Jade.UI.Control {
    constructor(options) {
        super(options);
        this.m_editorModel = options.editor;
        this.state.unset("editor");
    }
    get editorModel() {
        return this.m_editorModel;
    }
    get editorView() {
        return this.m_editorView;
    }
    get focusCallback() {
        return this.state.get("focus", () => { });
    }
    template() {
        return `<div class="-fb-grid-input-editor"></div>`;
    }
    focus() {
        this.editorView.focus();
    }
    show() {
        this.node.show();
    }
    hide() {
        this.node.hide();
    }
    onActivate(e) {
        super.onActivate(e);
        this.editor_init();
    }
    editor_init() {
        let editorType = FeatureBlend.getEditorType(this.editorModel.script);
        if (editorType != null) {
            this.m_editorView = new editorType({
                source: this.editorModel,
                focused: () => {
                    this.focusCallback();
                }
            });
            this.controls.add(this.m_editorView);
        }
    }
}

var DynamicVariableType;
(function (DynamicVariableType) {
    DynamicVariableType[DynamicVariableType["None"] = 0] = "None";
    DynamicVariableType[DynamicVariableType["Context"] = 1] = "Context";
})(DynamicVariableType || (DynamicVariableType = {}));
var DynamicVariableCss;
(function (DynamicVariableCss) {
    DynamicVariableCss["None"] = "-fb-dynamics-none";
    DynamicVariableCss["Context"] = "-fb-dynamics-datatag";
})(DynamicVariableCss || (DynamicVariableCss = {}));
class InputView extends Jade.UI.Control {
    constructor(options) {
        super(options);
        this.m_editorModel = options.editor;
        this.state.unset("editor");
        this.m_loadErrorsSubscription =
            this.editorModel.root.errors.subscribe((e) => {
                this.processValidationErrors();
            }, ErrorModelCollectionEventType.load);
        this.m_clearErrorsSubscription =
            this.editorModel.root.errors.subscribe((e) => {
                this.processValidationErrors();
            }, ErrorModelCollectionEventType.clear);
    }
    get group() {
        return this.editorModel.group;
    }
    get editorModel() {
        return this.m_editorModel;
    }
    get feature() {
        return this.editorModel.feature;
    }
    get editorView() {
        return this.m_editorView;
    }
    get contentNode() {
        return this.node.find(".-fb-grid-input-content");
    }
    get labelNode() {
        return this.node.find(".-fb-grid-input-label");
    }
    get deckNode() {
        return this.node.find(".-fb-grid-input-deck");
    }
    get visible() {
        return this.node.is(":visible");
    }
    template() {
        return `<div class="-fb-grid-input ${this.editorModel.required ? "-fb-grid-input-required" : ""}">
                    <div class="-fb-grid-input-label">
                        ${this.editorModel.label}
                    </div>
                    <div class="-fb-grid-input-content" data-controls></div>
                    <div class="-fb-grid-input-deck"></div>
                </div>`;
    }
    onActivate(e) {
        super.onActivate(e);
        this.labelNode.on(Jade.BrowserEvent.CLICK, (e) => { this.labelNode_onClick(e); });
        this.contentNode.on(Jade.BrowserEvent.MOUSEDOWN, (e) => { this.contentNode_onClick(e); });
        this.contentNode.on(Jade.BrowserEvent.FOCUS, "input, select, button, a, textarea", (e) => {
            this.contentNode_onFocus(e);
        });
        this.contentNode.on(Jade.BrowserEvent.BLUR, "input, select, button, a, textarea", (e) => {
            this.contentNode_onBlur(e);
        });
        this.editor_init();
        this.extenders_init();
        this.group.locals.events.labelResizeSubscribe((e) => {
            this.node.css({
                gridTemplateColumns: this.getGridTemplateColumns(e.width)
            });
        });
        this.processValidationErrors();
        if (!this.editorModel.enabled) {
            this.node.addClass("-fb-disabled");
        }
        if (this.editorModel.readOnly) {
            this.node.addClass("-fb-readonly");
        }
    }
    onDestroy(e) {
        super.onDestroy(e);
        if (this.m_clearErrorsSubscription) {
            this.m_clearErrorsSubscription.unsubscribe();
        }
        if (this.m_loadErrorsSubscription) {
            this.m_loadErrorsSubscription.unsubscribe();
        }
    }
    editor_init() {
        this.m_editorView = new InputEditorView({
            editor: this.editorModel,
            focus: () => {
                this.highlightLabel();
            }
        });
        this.controls.add(this.m_editorView);
    }
    labelNode_onClick(e) {
        this.editorView.focus();
        this.highlightLabel();
    }
    contentNode_onClick(e) {
        this.highlightLabel();
    }
    contentNode_onFocus(e) {
        this.highlightLabel();
        if (this.labelNode.hasClass("-fb-grid-input-label-error")) {
            Tipped.show(this.labelNode);
        }
    }
    contentNode_onBlur(e) {
        this.highlightLabel();
        if (this.labelNode.hasClass("-fb-grid-input-label-error")) {
            Tipped.hide(this.labelNode);
        }
    }
    highlightLabel() {
        this.editorModel.group.locals.events.inputSelectedPublish({
            model: this.editorModel
        });
        this.labelNode.addClass("-fb-input-selected");
        this.commitFocusToStorage();
    }
    getGridTemplateColumns(labelWidth) {
        return `${labelWidth}px auto ${this.deckNode.children().length > 0 ? " max-content" : ""}`;
    }
    processValidationErrors() {
        let errors = [];
        this.editorModel.feature.errors.forEach(error => {
            if (error.propertyName != null && error.propertyName.toLowerCase() === this.editorModel.name.toLowerCase()) {
                errors[errors.length] = error;
            }
        });
        if (errors.length === 0) {
            this.labelNode.removeClass("-fb-grid-input-label-error");
            this.labelNode.css({
                backgroundImage: "none"
            });
            Tipped.remove(this.labelNode);
            return;
        }
        this.labelNode.addClass("-fb-grid-input-label-error");
        this.labelNode.css({
            backgroundImage: `url(${Jade.IconSet.get("pdjs").resolve("sign_warning.png", Jade.IconSetSizeSpecificDirectory.x16)})`
        });
        let text = errors.flatMap((item) => {
            return item.message;
        }).join("</div><div>");
        Tipped.remove(this.labelNode);
        Tipped.create(this.labelNode, `<div class="-fb-popup-error -fb-grid-input-label-error-tooltip"><div>${text}</div></div>`, {
            skin: FeatureBlend.errorTooltipSkin
        });
    }
    commitFocusToStorage() {
        this.editorModel.group.locals.storage.focusedInputId = this.editorModel.uid;
    }
    extenders_init() {
        FeatureBlend.editorExtenders.items.forEach((item) => {
            let extender = item({
                editorModel: this.editorModel,
                extenderId: FeatureBlend.editorExtenders.getId(item)
            });
            if (!extender) {
                return;
            }
            let button = extender.mount({
                click: (e) => {
                    e.visible ?
                        this.editorView.hide() :
                        this.editorView.show();
                }
            });
            if (extender.isActive()) {
                if (extender != null) {
                    this.controls.add(extender);
                }
                if (button != null) {
                    this.deckNode.append(button);
                }
                this.node.css({
                    gridTemplateColumns: this.getGridTemplateColumns(150)
                });
                this.deckNode.css({
                    display: this.deckNode.children.length > 0 ? "block" : "none"
                });
            }
        });
    }
}

class CategoryView extends BaseView {
    constructor(options) {
        super(options);
        this.m_editors = options.editors;
        this.state.unset("editors");
    }
    get source() {
        return super.source;
    }
    get label() {
        return this.state.get("label");
    }
    get editors() {
        return this.m_editors;
    }
    get header() {
        return this.node.find(".-fb-grid-category-header");
    }
    get content() {
        return this.node.find(".-fb-grid-category-inputs");
    }
    get toggle() {
        return this.header.children(".-fb-grid-category-header-toggle");
    }
    get inputs() {
        return this.controls.getByType(InputView);
    }
    get visible() {
        return this.node.is(":visible");
    }
    get isExpanded() {
        return this.content.is(":visible");
    }
    template() {
        return `<div class="-fb-grid-category">
                    <div class="-fb-grid-category-header"><i class="icofont-simple-down -fb-grid-category-header-toggle" />${this.label}</div>
                    <div class="-fb-grid-category-inputs" data-controls></div>
                </div>`;
    }
    expand() {
        this.content.show();
        this.toggle.removeClass("icofont-simple-right");
        this.toggle.addClass("icofont-simple-down");
        this.source.locals.events.categoryTogglePublish();
    }
    collapse() {
        this.content.hide();
        this.toggle.removeClass("icofont-simple-down");
        this.toggle.addClass("icofont-simple-right");
        this.source.locals.events.categoryTogglePublish();
    }
    onActivate(e) {
        super.onActivate(e);
        this.header.on(Jade.BrowserEvent.CLICK, (e) => { this.toggle_onClick(e); });
        this.populate();
    }
    populate() {
        this.editors.forEach((editor) => {
            let input = new InputView({
                editor: editor
            });
            this.controls.add(input);
        });
    }
    toggle_onClick(e) {
        this.isExpanded ? this.collapse() : this.expand();
    }
}

class FilterCommand {
    static invoke(content, term) {
        if (Jade.String.isNullOrWhiteSpace(term)) {
            content.node.removeClass("-fb-grid-filtered");
        }
        else {
            content.node.addClass("-fb-grid-filtered");
        }
        let result = this.filterCategories(content, term);
        if (result.lastVisible == null) {
            this.writeMessage(content, `No inputs matched the text <b>"${term}"</b>.`);
        }
        else {
            this.clearMessage(content);
        }
    }
    static filterCategories(content, term) {
        let result = {
            lastVisible: null
        };
        content.categories.each((view) => {
            let inputs = this.filterInputs(view, term);
            if (inputs.lastVisible != null) {
                result.lastVisible = view;
            }
            view.node.css({
                borderBottom: ""
            });
        });
        return result;
    }
    static filterInputs(category, term) {
        let result = {
            lastVisible: null
        };
        category.inputs.each((input) => {
            let isMatch = input.editorModel.label.toLowerCase().indexOf(term.toLowerCase()) >= 0;
            if (isMatch) {
                input.node.removeClass("-fb-filtered");
                result = {
                    lastVisible: input
                };
            }
            else {
                input.node.addClass("-fb-filtered");
            }
        });
        if (result.lastVisible != null) {
            category.node.show();
        }
        else {
            category.node.hide();
        }
        return result;
    }
    static clearMessage(content) {
        content.node.find("div[role=fb-content-message]").remove();
    }
    static writeMessage(content, text) {
        this.clearMessage(content);
        let message = $(`<div role="fb-content-message"><div>${text}</div></div>`);
        message.css({
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
            position: "absolute",
            top: 0,
            right: 0,
            bottom: 0,
            left: 0,
            textAlign: "center",
            fontSiz: "12px",
            fontFamily: "Arial"
        });
        content.node.append(message);
    }
}

class GridLabelResizer extends BaseView {
    constructor(options) {
        super(options);
        this.m_parent = options.parent;
        this.state.unset("parent");
    }
    get parent() {
        return this.m_parent;
    }
    get source() {
        return super.source;
    }
    template() {
        return `<div class="-fb-grid-resizer -fb-grid-resizer-content"></div>`;
    }
    onActivate(e) {
        super.onActivate(e);
        this.node.draggable({
            axis: "x",
            containment: "parent",
            drag: (e) => {
                $(e.target).css({
                    opacity: ".3"
                });
            },
            stop: (e, ui) => {
                let parentWidth = this.parent.node.width();
                let parentOffsetLeft = this.parent.node.offset().left;
                let width = ui.offset.left - parentOffsetLeft;
                if (width < 100) {
                    width = 100;
                }
                if (width > (parentWidth - 100)) {
                    width = (parentWidth) - 100;
                }
                $(e.target).css({
                    left: `${width}px`,
                    opacity: 0
                });
                this.source.locals.events.labelResizePublish({
                    width: width - 20
                });
            }
        });
    }
    onDestroy(e) {
        super.onDestroy(e);
        if (this.node.data("ui-draggable")) {
            this.node.draggable("destroy");
        }
    }
}

class ContentView extends BaseView {
    constructor(options) {
        super(options);
    }
    get source() {
        return super.source;
    }
    get categories() {
        return this.controls.getByType(CategoryView);
    }
    template() {
        return `<div class="-fb-grid-content">
                    <div data-controls></div>
                </div>`;
    }
    onActivate(e) {
        super.onActivate(e);
        this.populate();
        this.node.on(Jade.BrowserEvent.SCROLL, _.bind(this.onScroll, this));
        this.controls.add(new GridLabelResizer({
            parent: this,
            source: this.source
        }));
        this.addEventSubscriptions();
    }
    onComplete(e) {
        super.onComplete(e);
        let scroll = this.source
            .locals.storage?.scroll;
        if (scroll != null) {
            this.node.scrollTop(scroll);
        }
        let filter = this.source
            .locals.storage?.filter;
        if (filter != null) {
            FilterCommand.invoke(this, filter);
        }
    }
    onScroll(e) {
        this.source.locals.storage.scroll = this.node.scrollTop();
    }
    populate() {
        let categories = [];
        let categorized = {};
        this.source.getEditors().forEach(editor => {
            if (categories.indexOf(editor.category) < 0) {
                categories[categories.length] = editor.category;
            }
            if (categorized[editor.category] == null) {
                categorized[editor.category] = [];
            }
            let categorizedArray = categorized[editor.category];
            categorizedArray[categorizedArray.length] = editor;
        });
        categories.sort((value1, value2) => {
            return value1.localeCompare(value2);
        });
        Object.keys(categorized).forEach((key) => {
            let category = new CategoryView({
                label: key,
                editors: categorized[key],
                source: this.source
            });
            this.controls.add(category);
        });
        let collpased = this.source.locals.storage.collapsed;
        this.categories.each(category => {
            if (collpased.indexOf(category.label) > -1) {
                category.collapse();
            }
        });
    }
    addEventSubscriptions() {
        this.source.locals.events.inputSelectedSubscribe(() => {
            this.node.find(".-fb-input-selected")
                .removeClass("-fb-input-selected");
        });
        this.source.locals.events.filterSubscribe((e) => {
            FilterCommand.invoke(this, e.term);
            this.source.locals.storage.filter = e.term;
        });
        this.source.locals.events.categoriesExpandAllSubscribe(() => {
            this.categories.each(category => {
                category.expand();
            });
            this.addCategoryCollapseStateToStorage();
        });
        this.source.locals.events.categoriesCollapseAllSubscribe(() => {
            this.categories.each(category => {
                category.collapse();
            });
            this.addCategoryCollapseStateToStorage();
        });
        this.source.locals.events.categoryToggleSubscribe(() => {
            this.addCategoryCollapseStateToStorage();
        });
    }
    addCategoryCollapseStateToStorage() {
        let collapsed = [];
        this.categories.each((category) => {
            if (!category.isExpanded) {
                collapsed.push(category.label);
            }
        });
        this.source.locals.storage.collapsed = collapsed;
    }
}

class FooterResizer extends BaseView {
    constructor(options) {
        super(options);
        this.m_grid = options.grid;
        this.state.unset("grid");
        this.m_footer = options.footer;
        this.state.unset("footer");
    }
    get source() {
        return super.source;
    }
    get footer() {
        return this.m_footer;
    }
    get grid() {
        return this.m_grid;
    }
    template() {
        return `<div class="-fb-grid-resizer -fb-grid-resizer-footer"></div>`;
    }
    onActivate(e) {
        super.onActivate(e);
        this.draggable_init();
    }
    onDestroy(e) {
        super.onDestroy(e);
        if (this.node.data("ui-draggable")) {
            this.node.draggable("destroy");
        }
    }
    draggable_init() {
        this.node.draggable({
            axis: "y",
            containment: this.grid.node,
            start: (e) => {
                this.dragStart();
            },
            drag: (e) => {
                $(e.target).css({
                    opacity: ".3"
                });
            },
            stop: (e, ui) => {
                this.setPosition(ui.position.top);
                this.dragStop();
            }
        });
    }
    setPosition(top) {
        let height = this.footer.node.height() + (top * -1);
        height = height < 50 ? 50 : height;
        this.footer.node.css({
            height: `${height}px`
        });
        this.source.locals.storage.footerHeight = height;
    }
    dragStart() {
        let gridHeight = this.grid.node.height();
        let footerHeight = this.footer.node.height();
        this.node.css({
            top: gridHeight - footerHeight,
            opacity: .3
        });
        this.node.detach();
        this.grid.node.append(this.node);
    }
    dragStop() {
        this.node.css({
            top: 0,
            opacity: 0
        });
        this.node.detach();
        this.footer.node.append(this.node);
    }
}

class FooterView extends BaseView {
    constructor(options) {
        super(options);
        this.m_parent = options.parent;
        this.state.unset("parent");
    }
    get parent() {
        return this.m_parent;
    }
    get source() {
        return super.source;
    }
    get contentNode() {
        return this.node.children(".-fb-grid-footer-content");
    }
    get contentTitleNode() {
        return this.contentNode.children(".-fb-grid-footer-content-title");
    }
    get contentBodyNode() {
        return this.contentNode.children(".-fb-grid-footer-content-body");
    }
    template() {
        return `<div class="-fb-grid-footer">
                    <div class="-fb-grid-footer-content">
                        <div class="-fb-grid-footer-content-title"></div>
                        <div class="-fb-grid-footer-content-body"></div>
                    </div>
                </div>`;
    }
    onActivate(e) {
        super.onActivate(e);
        this.source.locals.events.inputSelectedSubscribe((e) => {
            this.contentTitleNode.empty();
            this.contentTitleNode.append(`${e.model.label}`);
            this.contentBodyNode.empty();
            this.contentBodyNode.append(`${e.model.description}`);
        });
        this.resizer_init();
        let footerHeight = this.source.locals.storage.footerHeight;
        if (footerHeight != null) {
            this.node.css({
                height: `${footerHeight}px`
            });
        }
    }
    resizer_init() {
        this.m_resizer = new FooterResizer({
            grid: this.parent,
            footer: this,
            source: this.source
        });
        this.controls.add(this.m_resizer);
    }
}

class GridView extends BaseView {
    constructor(options) {
        super(options);
    }
    get content() {
        return this.m_content;
    }
    get source() {
        return super.source;
    }
    get header() {
        return this.m_header;
    }
    get footer() {
        return this.m_footer;
    }
    template() {
        return `<div class="-fb-grid"></div>`;
    }
    forEachInput(callback) {
        this.content.categories.each((category) => {
            category.inputs.each((input) => {
                callback(input);
            });
        });
    }
    onActivate(e) {
        super.onActivate(e);
        this.m_header = new HeaderView({
            source: this.source
        });
        this.controls.add(this.m_header);
        this.m_content = new ContentView({
            source: this.source
        });
        this.controls.add(this.m_content);
        this.m_footer = new FooterView({
            source: this.source,
            parent: this
        });
        this.controls.add(this.m_footer);
    }
}

class MicroView extends AssemblerView {
    constructor(options) {
        super(options);
    }
    get autoFocusOnLoad() {
        return this.state.get("autoFocusOnLoad", true);
    }
    get grid() {
        return this.m_grid;
    }
    template() {
        return `<div class="-fb-layout-group -fb-layout-group-micro"></div>`;
    }
    onActivate(e) {
        super.onActivate(e);
        this.m_grid = new GridView({
            source: this.source
        });
    }
    onInitialView() {
        super.onInitialView();
        if (this.m_grid) {
            this.controls.add(this.m_grid);
        }
    }
    onFocusedView() {
        super.onFocusedView();
        this.focusInput();
    }
    focusInput() {
        if (this.focusPostbackInput()) {
            return;
        }
        if (this.focusLastFocusedInput()) {
            return;
        }
        this.focusFirstFocusableInput();
    }
    focusPostbackInput() {
        let result = false;
        let postbackInputId = this.source.globals.storage.postbackInputId;
        let isPostback = !Jade.String.isNullOrWhiteSpace(postbackInputId);
        if (isPostback) {
            this.grid.forEachInput(input => {
                if (result === false && input.editorModel.uid === postbackInputId) {
                    input.editorView.focus();
                    result = true;
                    this.source.globals.storage.postbackInputId == null;
                }
            });
        }
        return result;
    }
    focusLastFocusedInput() {
        let result = false;
        let focusedInputId = this.source.locals.storage.focusedInputId;
        let hasLastFocued = !Jade.String.isNullOrWhiteSpace(focusedInputId);
        if (hasLastFocued) {
            this.grid.forEachInput(input => {
                if (result === false && input.editorModel.uid === focusedInputId) {
                    input.editorView.focus();
                    result = true;
                }
            });
        }
        return result;
    }
    focusFirstFocusableInput() {
        if (!this.autoFocusOnLoad) {
            return;
        }
        let found = false;
        this.grid.forEachInput(input => {
            if (!found && input.editorModel.enabled) {
                input.editorView.focus();
                found = true;
            }
        });
    }
}

class SimpleContentView extends BaseView {
    constructor(options) {
        super(options);
    }
    get autoFocusOnLoad() {
        return this.state.get("autoFocusOnLoad", true);
    }
    get source() {
        return super.source;
    }
    get assemblers() {
        return this.controls.getByType(AssemblerView);
    }
    template() {
        return `<div role="content" class="-fb-layout-content"></div>`;
    }
    selectGroup(groupId) {
        this.assemblers.each(view => {
            if (view.source.id != groupId) {
                view.hide();
            }
        });
        this.assemblers.each(view => {
            if (view.source.id == groupId) {
                view.source.globals.storage.selectedGroupId = view.source.id;
                view.show();
            }
        });
    }
    onActivate(e) {
        super.onActivate(e);
        this.generateGroupViews();
        this.globals.events.navigationSelectSubscribe((e) => {
            this.selectGroup(e.id);
        });
    }
    generateGroupViews() {
        this.source.featureSet.groups.forEach((group, index) => {
            let view = null;
            if (group.isMicro) {
                view = new MicroView({
                    source: group,
                    visible: index === 0,
                    isDefaultView: index === 0,
                    autoFocusOnLoad: this.autoFocusOnLoad
                });
            }
            else {
                view = new MacroView({
                    source: group,
                    visible: index === 0,
                    isDefaultView: index === 0
                });
            }
            this.controls.add(view);
        });
    }
}

class LayoutViewBase extends BaseView {
    constructor(options) {
        super(options);
    }
    get autoFocusOnLoad() {
        return this.state.get("autoFocusOnLoad", true);
    }
    get source() {
        return super.source;
    }
    set source(value) {
        super.source = value;
    }
    get cssClass() {
        return this.state.get("cssClass", "-fb-layout-tabbed");
    }
    get controller() {
        return this.source?.featureSet?.controller;
    }
    onLoadModel() {
    }
    loadModel(model) {
        this.source = model.context;
        this.source.featureSet.valueChangesAreSilent = true;
        this.onLoadModel();
        this.source.featureSet.valueChangesAreSilent = false;
        this.controller.activate();
    }
    commit() {
        if (this.source != null && this.controller != null) {
            this.controller.commit();
        }
    }
    loadErrors(errors) {
        if (this.source == null) {
            return;
        }
        this.source.root.errors.clear();
        if (errors != null) {
            this.source.root.errors.load(errors);
        }
    }
}

class SimpleLayoutView extends LayoutViewBase {
    template() {
        return `
            <div class="-fb-layout -fb-layout-simple">
                <div role="contents" class="-fb-layout-contents"></div>
            </div>
        `;
    }
    onLoadModel() {
        this.m_content = new SimpleContentView({
            insertAt: "[role=contents]",
            source: this.source,
            autoFocusOnLoad: this.autoFocusOnLoad
        });
        this.controls.add(this.m_content);
    }
}

class Content extends BaseView {
    constructor(options) {
        super(options);
    }
    get autoFocusOnLoad() {
        return this.state.get("autoFocusOnLoad", true);
    }
    get source() {
        return super.source;
    }
    get assemblers() {
        return this.controls.getByType(AssemblerView);
    }
    template() {
        return `<div role="content" class="-fb-layout-content"></div>`;
    }
    selectGroup(groupId) {
        this.assemblers.each(view => {
            if (view.source.id != groupId) {
                view.hide();
            }
        });
        this.assemblers.each(view => {
            if (view.source.id == groupId) {
                view.source.globals.storage.selectedGroupId = view.source.id;
                view.show();
            }
        });
    }
    onActivate(e) {
        super.onActivate(e);
        this.generateGroupViews();
        this.globals.events.navigationSelectSubscribe((e) => {
            this.selectGroup(e.id);
        });
    }
    generateGroupViews() {
        this.source.featureSet.groups.forEach((group, index) => {
            let view = null;
            if (group.isMicro) {
                view = new MicroView({
                    source: group,
                    visible: index === 0,
                    isDefaultView: index === 0,
                    autoFocusOnLoad: this.autoFocusOnLoad
                });
            }
            else {
                view = new MacroView({
                    source: group,
                    visible: index === 0,
                    isDefaultView: index === 0
                });
            }
            this.controls.add(view);
        });
    }
}

class NavigationItem extends BaseView {
    constructor(options) {
        super(options);
        let loadErrorsSubscription = this.source.root.errors.subscribe((e) => {
            this.processValidationErrors();
        }, ErrorModelCollectionEventType.load);
        this.subscribers.push(loadErrorsSubscription);
        let clearErrorsSubscription = this.source.root.errors.subscribe((e) => {
            this.processValidationErrors();
        }, ErrorModelCollectionEventType.clear);
        this.subscribers.push(clearErrorsSubscription);
    }
    get source() {
        return super.source;
    }
    template() {
        let icon = Jade.IconSet.get("pdjs")
            .resolve("sign_warning.png", Jade.IconSetSizeSpecificDirectory.x16);
        return `<a href="#" role="tab" class="-fb-layout-tab">
                    ${this.source.name}
                    <img src="${icon}"/>
                </a>`;
    }
    select() {
        this.onSelect();
    }
    onActivate(e) {
        super.onActivate(e);
        this.node.on(Jade.BrowserEvent.CLICK, e => {
            e.cancelBubble = true;
            e.returnValue = false;
            e.stopPropagation();
            this.onSelect();
            return false;
        });
        this.m_selectEventSubscription =
            this.globals.events.navigationSelectSubscribe((e) => {
                e.id == this.source.id ?
                    this.node.addClass("-fb-selected") :
                    this.node.removeClass("-fb-selected");
            });
        this.processValidationErrors();
    }
    onDestroy(e) {
        super.onDestroy(e);
        if (this.m_selectEventSubscription != null) {
            this.m_selectEventSubscription.unsubscribe();
        }
    }
    processValidationErrors() {
        if (this.node == null) {
            return;
        }
        this.node.removeClass("-fb-error");
        Tipped.remove(this.node);
        if (this.source.errors.length === 0) {
            return;
        }
        let messages = [];
        this.source.errors.forEach(error => {
            messages[messages.length] = error.message;
        });
        this.addValidationErrorSummary(messages);
    }
    addValidationErrorSummary(messages) {
        let template = `<div class="-fb-popup-error -fb-navigation-error-tooltip">
                                    <div>Errors</div>
                                    <div>
                                        <div>${messages.join(`</div><div>`)}</div>
                                    </div>
                                </div>`;
        Tipped.create(this.node, template, {
            skin: FeatureBlend.errorTooltipSkin,
            showOn: "mouseenter",
            hideOnClickOutside: true,
            showDelay: 500,
            hideAfter: 500,
            hideDelay: 0,
            position: {
                target: "topmiddle",
                tooltip: "bottommiddle"
            },
            offset: {
                y: -5,
                x: 0
            },
            detach: false,
            stem: true,
            maxWidth: 300,
            onShow: () => {
            }
        });
        this.node.addClass("-fb-error");
    }
    onSelect() {
        this.globals.events.navigationSelectPublish({
            id: this.source.id
        });
    }
}

class PopUp extends Jade.UI.Control {
    constructor(options) {
        super(options);
        this.m_popUpCreated = false;
    }
    get anchor() {
        return this.state.get("anchor");
    }
    get height() {
        return this.state.get("height", null);
    }
    get width() {
        return this.state.get("width", 200);
    }
    get visible() {
        return !!Tipped.visible(this.anchor);
    }
    get tipped() {
        return this.state.get("tipped", {});
    }
    get onCreateCallback() {
        return this.state.get("onCreate", () => { });
    }
    template() {
        return `<div style="height:${this.height}px;width:${this.width}px;display:none;"></div>`;
    }
    show() {
        this.ensureTippedPopUp();
        Tipped.show(this.anchor);
    }
    hide() {
        Tipped.hide(this.anchor);
    }
    destroy() {
        Tipped.remove(this.anchor.get(0));
        super.destroy();
    }
    popUp_onCreate() {
        this.onCreateCallback();
    }
    ensureTippedPopUp() {
        if (this.m_popUpCreated == false) {
            this.node.detach();
            Tipped.create(this.anchor, this.node, this.getPopUpOptions());
            this.node.show();
            this.popUp_onCreate();
            this.m_popUpCreated = true;
        }
    }
    getPopUpOptions(options) {
        return _.merge({
            skin: "white",
            radius: false,
            shadow: false,
            stem: false,
            position: "bottomleft",
            showOn: false,
            hideAfter: 500,
            hideDelay: 500,
            maxWidth: this.width,
            fadeOut: 100,
            showDelay: 0,
            hideOn: "click-outside",
            hideOthers: true,
            detach: false
        }, options || {}, this.tipped);
    }
}

class PopUpItem extends Jade.UI.Control {
    constructor(options) {
        super(options);
    }
    get data() {
        return this.state.get("data", {});
    }
    get uid() {
        return this.resolveMap(this.uidMap);
    }
    get uidMap() {
        return this.state.get("uidMap", "uid");
    }
    get label() {
        return this.resolveMap(this.labelMap);
    }
    get labelMap() {
        return this.state.get("labelMap", "label");
    }
    get icon() {
        return this.resolveMap(this.iconMap);
    }
    get iconMap() {
        return this.state.get("iconMap", "icon");
    }
    get clickCallback() {
        return this.state.get("click", null);
    }
    template() {
        let result = $(`<div data-role="item" data-id="${this.uid}" title="${this.label}" class="-pdjs-pop-up-item">${this.label}</div>`);
        if (this.icon != null) {
            result.css({
                paddingLeft: "25px",
                backgroundImage: `url(${Jade.IconSet.get("pd").resolve(this.icon, Jade.IconSetSizeSpecificDirectory.x16)})`
            });
        }
        return result;
    }
    onActivate(e) {
        super.onActivate(e);
        if (_.isFunction(this.clickCallback)) {
            this.node.on(Jade.BrowserEvent.CLICK, _.bind(this.clickCallback, this));
        }
    }
    resolveMap(id, defaultValue) {
        return this.state.get(`data.${id}`, defaultValue);
    }
}

class NavigationOverflowItem extends PopUpItem {
    constructor(options) {
        super(options);
        this.m_source = options.source;
        this.state.unset("source");
    }
    get source() {
        return this.m_source;
    }
    template() {
        let icon = Jade.IconSet.get("pdjs")
            .resolve("sign_warning.png", Jade.IconSetSizeSpecificDirectory.x16);
        return `<a href="#" role="tab" class="-fb-layout-nav-overflow-item">
                    ${this.source.name}
                    <img src="${icon}"/>
                </a>`;
    }
    onActivate(e) {
        super.onActivate(e);
        this.node.on(Jade.BrowserEvent.CLICK, e => {
            e.cancelBubble = true;
            e.returnValue = false;
            e.stopPropagation();
            this.onSelect();
            return false;
        });
        this.m_selectEventSubscription =
            this.source.globals.events.navigationSelectSubscribe((e) => {
                e.id == this.source.id ?
                    this.node.addClass("-fb-selected") :
                    this.node.removeClass("-fb-selected");
            });
        this.m_loadErrorsSubscription =
            this.source.root.errors.subscribe((e) => {
                this.processValidationErrors();
            }, ErrorModelCollectionEventType.load);
        this.m_clearErrorsSubscription =
            this.source.root.errors.subscribe((e) => {
                this.processValidationErrors();
            }, ErrorModelCollectionEventType.clear);
    }
    onDestroy(e) {
        super.onDestroy(e);
        if (this.m_clearErrorsSubscription) {
            this.m_clearErrorsSubscription.unsubscribe();
        }
        if (this.m_loadErrorsSubscription) {
            this.m_loadErrorsSubscription.unsubscribe();
        }
        if (this.m_selectEventSubscription) {
            this.m_selectEventSubscription.unsubscribe();
        }
    }
    onSelect() {
        this.source.globals.events.navigationSelectPublish({
            id: this.source.id,
            scrollIntoView: true
        });
    }
    processValidationErrors() {
        if (this.node == null) {
            return;
        }
        this.source.errors.length === 0 ?
            this.node.removeClass("-fb-error") :
            this.node.addClass("-fb-error");
    }
}

class NavigationOverflow extends Jade.UI.Control {
    constructor(options) {
        super(options);
    }
    get anchor() {
        return this.state.get("anchor");
    }
    get imageNode() {
        return this.node.children("img");
    }
    get menu() {
        return this.m_menu;
    }
    set menu(value) {
        this.m_menu = value;
    }
    get menuNode() {
        return this.node.children("div[role=menu]");
    }
    template() {
        return `<div class="-fb-layout-nav-overflow">
                    <div role="menu">
                        <img src="${this.getIcon("ellipsis_vertical.png")}"/>
                    </div>
                </div>`;
    }
    onActivate(e) {
        super.onActivate(e);
        this.node.on(Jade.BrowserEvent.CLICK, _.bind(this.onClick, this));
        this.createMenu();
        this.show();
        this.hide();
    }
    update(items) {
        this.createMenu();
        items.forEach(item => {
            let newItem = new NavigationOverflowItem({
                source: item.source,
                click: () => {
                    this.menu.hide();
                }
            });
            this.menu.controls.add(newItem);
        });
    }
    show() {
        this.node.show();
    }
    hide() {
        this.node.hide();
    }
    onDestroy(e) {
        super.onDestroy(e);
        this.menu.destroy();
    }
    createMenu() {
        if (this.menu) {
            this.menu.destroy();
            this.menu = null;
        }
        this.menu = new PopUp({
            anchor: this.menuNode,
            width: 250,
            tipped: {
                skin: FeatureBlend.defaultPopUpSkin,
                position: {
                    target: "bottomright",
                    tooltip: "topright"
                },
                offset: {
                    y: 4,
                    x: 1
                },
                padding: false
            }
        });
        this.controls.add(this.menu);
        this.menu.node.addClass("-pdjs-shared-ui-controls-toolbar-menu");
    }
    onClick(e) {
        this.menu.visible ?
            this.menu.hide() :
            this.menu.show();
    }
    getIcon(icon) {
        return Jade.IconSet.get("pdjs")
            .resolve(icon, Jade.IconSetSizeSpecificDirectory.x16);
    }
}

class Navigation extends BaseView {
    constructor(options) {
        super(options);
        this.m_observer = null;
    }
    get source() {
        return super.source;
    }
    get orientation() {
        return this.state.get("orientation", "horizontal");
    }
    get itemsNode() {
        return this.node.children("[role=tabs]");
    }
    get items() {
        return this.controls.getByType(NavigationItem).toArray();
    }
    get overflowNode() {
        return this.node.children("div[role=overflow]");
    }
    get overflow() {
        return this.m_overflow;
    }
    template() {
        return `<div role="tabstrip" class="-fb-layout-tabstrip">
                    <div role="tabs" class="-fb-layout-tabs" data-controls></div>
                    <div role="overflow" class="-fb-layout-tabs-overflow"></div>
                    <div role="border" class="-fb-layout-tabs-border"></div>
                </div>`;
    }
    onActivate(e) {
        super.onActivate(e);
        this.addResizeObserver();
        this.overflow_init();
        this.source.featureSet.groups.forEach(group => {
            this.controls.add(new NavigationItem({
                label: group.name,
                source: group
            }));
        });
        this.itemsNode.append("<div></div>");
        this.source.globals.events.navigationSelectSubscribe((e) => {
            if (e.scrollIntoView) {
                this.scrollIntoView(e.id);
            }
        });
        this.itemsNode.scrollLeft(this.source.globals.storage.navScroll);
    }
    onComplete(e) {
        super.onComplete(e);
        this.selectDefaultNavigation();
    }
    onDestroy(e) {
        super.onDestroy(e);
        this.overflow.destroy();
    }
    onResize(e) {
        let element = this.itemsNode.get(0);
        let isOverflowed = element.clientWidth < element.scrollWidth ||
            element.clientHeight < element.scrollHeight;
        isOverflowed ?
            this.overflow.show() :
            this.overflow.hide();
        let overflowWidth = this.overflowNode.get(0).clientWidth;
        let widthMax = element.clientWidth + (isOverflowed ? overflowWidth : 0);
        let widthSum = 0;
        this.items.forEach(item => {
            widthSum += item.node.get(0).clientWidth;
        });
        isOverflowed = widthMax < widthSum;
        isOverflowed ?
            this.overflow.show() :
            this.overflow.hide();
        this.overflow.update(this.items);
    }
    overflow_init() {
        if (this.orientation !== "horizontal") {
            return;
        }
        this.m_overflow = new NavigationOverflow({
            insertAt: "div[role=overflow]"
        });
        this.controls.add(this.m_overflow);
        let items = [];
        this.source.featureSet.groups.forEach(group => {
            items.push(new NavigationItem({
                label: group.name,
                source: group
            }));
        });
        this.overflow.update(items);
        this.addResizeObserver();
    }
    addResizeObserver() {
        let debounced = _.debounce((e) => {
            this.onResize(e);
        }, 250);
        this.m_observer = new ResizeObserver((e) => {
            debounced(e);
        });
        this.m_observer.observe(this.node.get(0));
    }
    selectDefaultNavigation() {
        let selectedGroupId = this.globals.storage.selectedGroupId;
        if (!Jade.String.isNullOrWhiteSpace(selectedGroupId)) {
            this.controls.getByType(NavigationItem).each(item => {
                if (item.source.id == selectedGroupId) {
                    this.source.globals.events.navigationSelectPublish({
                        id: selectedGroupId
                    });
                }
            });
        }
        else {
            this.source.globals.events.navigationSelectPublish({
                id: _.first(this.source.featureSet.groups).id
            });
        }
    }
    scrollIntoView(id) {
        let total = 0;
        let found = false;
        this.items.forEach(item => {
            item.source.id == id ? found = true : null;
            if (!found) {
                total += item.node.width();
            }
        });
        this.source.globals.storage.navScroll = total;
        this.itemsNode.scrollLeft(total);
    }
}

class LayoutView extends LayoutViewBase {
    template() {
        return `
            <div class="-fb-layout ${this.cssClass}">
                <div role="nav" class="-fb-layout-nav"></div>
                <div role="contents" class="-fb-layout-contents"></div>
            </div>
        `;
    }
    onLoadModel() {
        this.m_content = new Content({
            insertAt: "[role=contents]",
            source: this.source,
            autoFocusOnLoad: this.autoFocusOnLoad
        });
        this.controls.add(this.m_content);
        this.m_navigation = new Navigation({
            insertAt: "[role=nav]",
            source: this.source
        });
        this.controls.add(this.m_navigation);
    }
}

var FeatureBlendLayoutType;
(function (FeatureBlendLayoutType) {
    FeatureBlendLayoutType["Panels"] = "Panels";
    FeatureBlendLayoutType["Tabbed"] = "Tabbed";
    FeatureBlendLayoutType["Simple"] = "Simple";
})(FeatureBlendLayoutType || (FeatureBlendLayoutType = {}));
class FeatureBlendView extends Jade.UI.Control {
    constructor(options) {
        super(options);
    }
    get autoFocusOnLoad() {
        return this.state.get("autoFocusOnLoad", true);
    }
    get layout() {
        this.ensureLayout();
        return this.m_layout;
    }
    set layout(value) {
        this.m_layout = value;
    }
    get layoutType() {
        return this.state.get("layout");
    }
    get source() {
        return this.m_source;
    }
    set source(value) {
        this.m_source = value;
    }
    loadModel(model) {
        this.source = model;
        this.layout.loadModel(model);
    }
    commit() {
        this.layout.commit();
    }
    loadErrors(errors) {
        this.layout.loadErrors(errors);
    }
    layout_init() {
        switch (this.layoutType) {
            case FeatureBlendLayoutType.Panels:
                this.m_layout = new LayoutView({
                    cssClass: "-fb-layout-panels",
                    source: this.source.context,
                    autoFocusOnLoad: this.autoFocusOnLoad
                });
                break;
            case FeatureBlendLayoutType.Tabbed:
                this.m_layout = new LayoutView({
                    cssClass: "-fb-layout-tabbed",
                    source: this.source.context,
                    autoFocusOnLoad: this.autoFocusOnLoad
                });
                break;
            case FeatureBlendLayoutType.Simple:
                this.m_layout = new SimpleLayoutView({
                    source: this.source.context,
                    autoFocusOnLoad: this.autoFocusOnLoad
                });
                break;
        }
        this.controls.add(this.m_layout);
    }
    ensureLayout() {
        if (this.m_layout == null) {
            this.layout_init();
        }
    }
}

class MicroEditorBase extends EditorBaseOf {
    constructor(options) {
        super(options);
        this.source.controller.onCommit(() => {
            this.onCommit();
        });
    }
    focus() {
        super.focus();
    }
    onCommit() {
    }
    setDisabledStyle(disabled) {
        let node = this.node.closest(".-fb-grid-input");
        disabled ?
            node.addClass("-fb-grid-input-disabled") :
            node.removeClass("-fb-grid-input-disabled");
    }
    setReadOnlyStyle(readonly) {
        let node = this.node.closest(".-fb-grid-input");
        readonly ?
            node.addClass("-fb-grid-input-readonly") :
            node.removeClass("-fb-grid-input-readonly");
    }
}

class EditorExtender extends Jade.UI.Control {
    constructor(options) {
        super(options);
        this.m_editorModel = options.editorModel;
        this.state.unset("editorModel", { silent: true });
    }
    get button() {
        this.ensureButton();
        return this.m_button;
    }
    get editorModel() {
        return this.m_editorModel;
    }
    get extenderId() {
        return this.state.get("extenderId");
    }
    get feature() {
        return this.editorModel.feature;
    }
    get featureGroup() {
        return this.feature.parent;
    }
    get featureSet() {
        return this.featureGroup.parent;
    }
    get globals() {
        return this.editorModel.root.context.globals;
    }
    get visible() {
        return this.node.css("display") != "none";
    }
    get locals() {
        return this.featureGroup.locals;
    }
    get clickCallback() {
        return this.state.get("click", () => { });
    }
    set clickCallback(value) {
        this.state.set("click", value);
    }
    mount(e) {
        this.clickCallback = e.click || (() => { });
        this.button.on(Jade.BrowserEvent.CLICK, () => {
            this.toggleVisibleState(!this.visible);
            this.clickCallback({
                visible: this.visible
            });
            this.activeButton_onClick({
                visible: this.visible
            });
        });
        return this.button;
    }
    isActive() {
        return true;
    }
    setActiveState(active) {
    }
    createButton() {
        return $(`<button></button>`);
    }
    ensureButton() {
        if (this.m_button == null) {
            this.m_button = this.createButton();
        }
    }
    toggleVisibleState(visible = true) {
        if (!visible) {
            this.node.hide();
            this.button.removeClass("-fb-enabled");
            this.button.removeClass("-fb-selected");
        }
        else {
            this.node.show();
            this.button.addClass("-fb-enabled");
            this.button.addClass("-fb-selected");
        }
    }
    activeButton_onClick(e) {
    }
}

window["FeatureBlendJS"] = {};
window["FeatureBlendJS"].Data = {};
window["FeatureBlendJS"].Models = {};
window["FeatureBlendJS"].Modules = {};
window["FeatureBlendJS"].Views = {};
window["FeatureBlendJS"].Views.Editors = {};
window["FeatureBlendJS"].Views.Layout = {};
window["FeatureBlendJS"].FeatureBlend = FeatureBlend;
window["FeatureBlendJS"].Models.ControllerModel = ControllerModel$1;
window["FeatureBlendJS"].Models.RootModel = RootModel;
window["FeatureBlendJS"].Models.FeatureModel = FeatureModel$1;
window["FeatureBlendJS"].Models.EditorModel = EditorModel;
window["FeatureBlendJS"].Models.MicroEditorModel = MicroEditorModel;
window["FeatureBlendJS"].Views.FeatureBlendView = FeatureBlendView;
window["FeatureBlendJS"].Views.FeatureBlendLayoutType = FeatureBlendLayoutType;
window["FeatureBlendJS"].Views.MacroEditorBase = MacroEditorBase;
window["FeatureBlendJS"].Views.MicroEditorBase = MicroEditorBase;
window["FeatureBlendJS"].Views.EditorExtender = EditorExtender;
window["FeatureBlendJS"].Views.Layout.TabbedLayoutView = LayoutView;
window["FeatureBlendJS"].Views.Layout.SimpleLayoutView = SimpleLayoutView;
window["FeatureBlendJS"].Views.Layout.PanelsLayoutView = LayoutView;

var Namespaces = /*#__PURE__*/Object.freeze({
    __proto__: null
});

export { DataModule as Data, ModelsModule as Models, Namespaces };
