/** * A chained class that sets most of the stylings of an element * Attributes: * _modifications: {Object} */ class Modifier { /** * @type {Map} _modifications */ _modifications; /** * @type {Array} */ _removeMods; /** * @type {Shape} */ _shape; /** * @type {Sides} paddingValues */ _paddingValues; constructor() { this._modifications = new Object(); this._removeMods = []; } /** * Sets the modifications for widht and height to 100%. * @returns {Modifier} this modifier object */ fillMaxSize(widthFraction = 1, heightFraction = 1) { return this.fillMaxWidth(widthFraction) .fillMaxHeight(heightFraction); } /** * Sets the modification for width to the given fraction of 1 (default 1 := 100%). * @param {number} fraction * @returns {Modifier} this modifier object */ fillMaxWidth(fraction = 1) { this._modifications["width"] = (100 * fraction) + "%"; this._modifications["max-width"] = (100 * fraction) + "%"; return this; } /** * Sets the modification for height to the given fraction of 1 (default 1 := 100%). * @param {number} fraction * @returns {Modifier} this modifier object */ fillMaxHeight(fraction = 1) { this._modifications["height"] = (100 * fraction) + "%"; this._modifications["max-height"] = (100 * fraction) + "%"; return this; } /** * * @param {string} keyWord weither 'height' or 'width' that will be adjusted and looked for * @param {Sides} parentalPadding */ _updateDimensionsBy(parentalPadding) { function updateDirection(keyWord, modifications, parentalAdjustment) { let refKeys = Object.keys(modifications) .filter(k => k.includes(keyWord)); if (refKeys.length > 0) { for (let i = 0; i < refKeys.length; i++) { let key = refKeys[i]; let value = modifications[key]; if (key.includes("calc")) { console.log( `Modifier._updateByParent... ${keyWord } - unexpected value '${value }' for '${key }', skipping...` ); } else { let newValue = `calc(${value} - ${parentalAdjustment});`; modifications[key] = newValue.trim(); } } } return modifications; } if (parentalPadding) { let pval = parentalPadding.getValues(); if (pval["horizontal"] > 0) { this._modifications = updateDirection("width", this._modifications, pval["horizontal"] + parentalPadding._unit); } if (pval["vertical"] > 0) { this._modifications = updateDirection("height", this._modifications, pval["vertical"] + parentalPadding._unit); } } return this; } /** * Takes dimensions as param which can be either of three: * * param == null (not given): * Returns new DimensionsChain (to chain Dimensions-Modifications) * * param == number (>0): * Applies Dimensions.all(dimension); * Returns this modifier * * param == Dimensions: * Sets modifications according to the dimensions object; * Returns this modifier * @param {Dimensions | number | undefined} [modify=null] modify as in modifiers * @returns { Modifier | DimensionsChain } this modifier object or a DimensionsChain */ dimensions(modify = null) { if (modify instanceof Dimensions) { modify.toModifications() .forEach(kvpair => { this._modifications[kvpair.key] = kvpair.value; }); return this; } else { let modSub = new DimensionsChain(this); if (Number.isInteger(modify) && modify > 0) { return modSub.all(modify).ensureModifier(); } // case dimension is number but < 0 or dimensions == null or anything else return modSub; } } /** * Sets the padding on all sides according to the given padding object. * Currently the padding will always be set * to the most recent padding/padding. * @param {Padding | number | undefined} modify as in modifiers * @returns {Modifier | PaddingChain} this modifier object */ padding(modify = null) { if (modify instanceof Sides && !(modify instanceof Padding)) { modify._modMethod = "padding"; modify = Object.assign(new Padding(), modify); } if (modify instanceof Padding) { modify.toModifications() .forEach(kvpair => { this._modifications[kvpair.key] = kvpair.value; }); } else { let modSub = new PaddingChain(this); if (Number.isInteger(modify) && modify > -1) { return modSub.all(modify).ensureModifier(); } return modSub } return this; } /** * Sets the margin on all sides according to the given siding object. * Currently the margin will always be set * to the most recent margin/siding. * @ATTENTION since it just increases complexity to constantly use padding and margin * it is recommended to use padding and to stick to that as often as possible. * Padding values take affect inside/within the element. * @param {Margin | number | undefined} modify * @returns {Modifier | MarginChain} this modifier object */ margin(modify = null) { if (modify instanceof Sides && !(modify instanceof Margin)) { modify._modMethod = "margin"; modify = Object.assign(new Margin(), modify); } if (modify instanceof Margin) { modify.toModifications() .forEach(kvpair => { this._modifications[kvpair.key] = kvpair.value; }); } else { let modSub = new MarginChain(this); if (Number.isInteger(modify) && modify > -1) { return modSub.all(modify).ensureModifier(); } return modSub } return this; } /** * Sets the background-color as a rgb color. * If no color is given/specified the styling will be set to "inherit" * and use the color setting from (one of) the parent. * @param {Color} color * @returns {Modifier} this modifier object */ background(color) { if (color) { if (color._hex) { this._modifications["background-color"] = color._hex; } else { this._modifications["background-color"] = color.cssRGBString(); } } else { this._modifications["background-color"] = "inherit"; } return this; } /** * Sets the color as a rgb color. * If no color is given/specified the styling will be set to "inherit" * and use the color setting from (one of) the parent. * @param {Color} color * @returns {Modifier} this modifier object */ color(color) { this._modifications["color"] = ( color ? color.cssRGBString() : "inherit" ); return this; } /** * Adds the modifications of the given Modifier to current Modifier. * This is especailly used in the cases of extending existing/pre defined * Components. * * CAUTION matching existing modifications will be overwritten. * * @todo finish second parameter "modifications" - logic * * @param modifier The "new" Modifier * @returns {Modifier} The "old/current" Modifier, * extended with the modifications of the given Modifier. */ join(modifier, modifications = {}) { let keys = Object.keys(modifier.ensureModifier()._modifications); for (let i = 0; i < keys.length; i++) { /* if (!this._modifications.hasOwnProperty(keys[i])) */ this._modifications[keys[i]] = modifier.ensureModifier()._modifications[keys[i]]; } let removeMods = modifier.ensureModifier()._removeMods; if (removeMods.length > 0) { for (let i = 0; i < removeMods.length; i++) { delete this._modifications[removeMods[i]]; } } if (modifier._paddingValues) { this._paddingValues = modifier._paddingValues; } return this; } /** * * @param {string} key a css style rule * @param {string} value the corresponding value to the css style rule * @returns {Modifier} this modifier object */ setStyleRule(key, value) { this._modifications[key] = value; return this; } /** * Sets the style rules to deactivate text-selection in the component. * @returns {Modifier} */ noneSelect() { return this .setStyleRule("user-select", "none") .setStyleRule("-ms-user-select", "none") .setStyleRule("-webkit-user-select", "none") } /** * * @param {StylePropertyMap} rulemap * @returns {Modifier} */ addStyleRuleMap(rulemap) { for (const ruleKey of Object.keys(rulemap)) { this._modifications[ruleKey] = rulemap[ruleKey]; } return this; } /** * * @param {string} key * @returns {Modifier} this modifier object */ removeStyleRule(key) { this._removeMods.push(key); if (Object.keys(this._modifications).includes(key)) { delete this._modifications[key]; } return this; } /** * Takes border as param which can be either of three: * * param == null (not given): * Returns new BorderChain (to chain Border-Modifications) * * param == number (>0): * Applies Border.width(border); * Rerturns this modifier * * param == Border: * * Sets a border line (with given linestyle) to all sides. * If lineStyle is an array, the containing LineStyles, * are applied in the order: [top, right, bottom, left]. * If the border has a shape defined, * this shape will override earlier shape definitions. * Otherwise existing shape definitions will be applied; * * Rerturns this modifier * * @param {Border | number | undefined} [modify=null] modify as in modifiers * @returns {Modifier | BorderChain} this modifier or BorderChain */ border(modify = null) { if (modify instanceof Border) { if (modify._shape) { this.clip(modify._shape); } else if (this._shape) { modify._shape = this._shape; } modify.toModifications() .forEach(e => this._modifications[e.key] = e.value); return this; } else { let modSub = new BorderChain(this); if (Number.isInteger(modify) && modify > 0) { return modSub.width(modify).ensureModifier(); } return modSub; } } /** * Takes shape as param which can be either of three: * * param == null (not given): * Returns new ShapeChain (to chain Shape-Modifications) * * param == number (>0): * Applies Shape.all(dimension); * Returns this modifier * * param == Shape: * Sets modifications according to the shape object; * Returns this modifier * * * @param {Shape | number | undefined} [modify=null] modify as in modifiers * @returns { Modifier | ShapeChain } */ clip(modify = null) { if (modify instanceof Shape) { this._shape = modify; this._modifications["border-radius"] = modify.getOrderedValues().join(' '); return this; } else { let modSub = new ShapeChain(this); if (Number.isInteger(modify) && modify > 0) { return modSub.all(modify).ensureModifier(); } return modSub; } } /** * @deprecated use Modifier.dimensions() instead * @param {number} size of width and height in pixels * @returns {DimensionsChain} */ linkDimensions(size = -1) { if (size === -1) { return new DimensionsChainedModifier(this); } else { return new DimensionsChain(this).all(size).ensureModifier() } } /** * @deprecated use Modifier.padding() instead * @param {number} amount the padding for all four sides * @returns {PaddingChain} */ linkPadding(amount = -1) { if (amount === -1) { return new PaddingChainedModifier(this); } else { return new PaddingChain(this).all(amount); } } /** * @deprecated use Modifier.margin() instead * @ATTENTION since it just increases complexity to constantly use padding and margin * it is recommended to use padding and to stick to that as often as possible. * Padding values take affect inside/within the element. * @param {number} amount the padding for all four sides * @returns {PaddingChain} */ linkMargin(amount = -1) { if (amount === -1) { return new MarginChainedModifier(this); } else { return new MarginChain(this).all(amount); } } /** * @deprecated use Modifier.clip() instead * @param {number} cornerRadius will create a rounded rectangle with the given cornerRadius * @returns {ShapeChain} */ linkClip(cornerRadius = -1) { if (cornerRadius === -1) { return new ShapeChainedModifier(this); } else { return new ShapeChain(this).all(cornerRadius); } } /** * @deprecated use Modifier.border() instead * @param {number} borderWidth sets the width of all four border sides * @returns {BorderChain} */ linkBorder(borderWidth = -1) { if (borderWidth === -1) { return new BorderChainedModifier(this); } else { return new BorderChain(this).width(borderWidth); } } /** * * @returns {Modifier} */ ensureModifier() { return this; } }