diff --git a/docs/modules/Style-manager.md b/docs/modules/Style-manager.md index 32fb41ff6..7cc961fad 100644 --- a/docs/modules/Style-manager.md +++ b/docs/modules/Style-manager.md @@ -13,7 +13,7 @@ Coming soon Here you can find all the available built-in properties that you can use inside Style Manager via `buildProps`: -`float`, `position`, `text-align`, `display`, `font-family`, `font-weight`, `border`, `border-style`, `border-color`, `border-width`, `box-shadow`, `background-repeat`, `background-position`, `background-attachment`, `background-size`, `transition`, `transition-duration`, `transition-property`, `transition-timing-function`, `top`, `right`, `bottom`, `left`, `margin`, `margin-top`, `margin-right`, `margin-bottom`, `margin-left`, `padding`, `padding-top`, `padding-right`, `padding-bottom`, `padding-left`, `width`, `heigth`, `min-width`, `min-heigth`, `max-width`, `max-heigth`, `font-size`, `letter-spacing`, `line-height`, `text-shadow`, `border-radius`, `border-top-left-radius`, `border-top-right-radius`, `border-bottom-left-radius`, `border-bottom-right-radius`, `perspective`, `transform`, `transform-rotate-x`, `transform-rotate-y`, `transform-rotate-z`, `transform-scale-x`, `transform-scale-y`, `transform-scale-z`, `color`, `background-color`, `background`, `background-image`, `cursor` +`float`, `position`, `text-align`, `display`, `font-family`, `font-weight`, `border`, `border-style`, `border-color`, `border-width`, `box-shadow`, `background-repeat`, `background-position`, `background-attachment`, `background-size`, `transition`, `transition-duration`, `transition-property`, `transition-timing-function`, `top`, `right`, `bottom`, `left`, `margin`, `margin-top`, `margin-right`, `margin-bottom`, `margin-left`, `padding`, `padding-top`, `padding-right`, `padding-bottom`, `padding-left`, `width`, `height`, `min-width`, `min-height`, `max-width`, `max-height`, `font-size`, `letter-spacing`, `line-height`, `text-shadow`, `border-radius`, `border-top-left-radius`, `border-top-right-radius`, `border-bottom-left-radius`, `border-bottom-right-radius`, `perspective`, `transform`, `transform-rotate-x`, `transform-rotate-y`, `transform-rotate-z`, `transform-scale-x`, `transform-scale-y`, `transform-scale-z`, `color`, `background-color`, `background`, `background-image`, `cursor`, `flex-direction`, `flex-wrap`, `justify-content`, `align-items`, `align-content`, `order`, `flex-basis`, `flex-grow`, `flex-shrink`, `align-self`, `overflow`, `overflow-x`, `overflow-y` Example usage: ```js diff --git a/index.html b/index.html index 805835a1f..ff5544915 100755 --- a/index.html +++ b/index.html @@ -91,6 +91,10 @@ name: 'General', open: false, buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom'] + },{ + name: 'Flex', + open: false, + buildProps: ['flex-direction', 'flex-wrap', 'justify-content', 'align-items', 'align-content', 'order', 'flex-basis', 'flex-grow', 'flex-shrink', 'align-self'] },{ name: 'Dimension', open: false, diff --git a/src/commands/view/SelectComponent.js b/src/commands/view/SelectComponent.js index 2df42f887..28adeae84 100644 --- a/src/commands/view/SelectComponent.js +++ b/src/commands/view/SelectComponent.js @@ -350,7 +350,7 @@ module.exports = { * @private */ initResize(elem) { - const em = this.em; + const { em, canvas } = this; const editor = em ? em.get('Editor') : ''; const config = em ? em.get('Config') : ''; const pfx = config.stylePrefix || ''; @@ -444,7 +444,13 @@ module.exports = { const style = modelToStyle.getStyle(); if (!onlyHeight) { - style[keyWidth] = autoWidth ? 'auto' : `${rect.w}${unitWidth}`; + const padding = 10; + const frameOffset = canvas.getCanvasView().getFrameOffset(); + const width = + rect.w < frameOffset.width - padding + ? rect.w + : frameOffset.width - padding; + style[keyWidth] = autoWidth ? 'auto' : `${width}${unitWidth}`; } if (!onlyWidth) { diff --git a/src/editor/config/config.js b/src/editor/config/config.js index 9c86d7418..c49da707f 100644 --- a/src/editor/config/config.js +++ b/src/editor/config/config.js @@ -223,6 +223,22 @@ module.exports = { 'bottom' ] }, + { + name: 'Flex', + open: false, + buildProps: [ + 'flex-direction', + 'flex-wrap', + 'justify-content', + 'align-items', + 'align-content', + 'order', + 'flex-basis', + 'flex-grow', + 'flex-shrink', + 'align-self' + ] + }, { name: 'Dimension', open: false, diff --git a/src/style_manager/model/Property.js b/src/style_manager/model/Property.js index a62fe0677..b1903bf41 100644 --- a/src/style_manager/model/Property.js +++ b/src/style_manager/model/Property.js @@ -26,7 +26,19 @@ const Property = require('backbone').Model.extend( // Use case: // you can add all SVG CSS properties with toRequire as true // and then require them on SVG Components - toRequire: 0 + toRequire: 0, + + // Specifies dependency on other properties of the selected object. + // Property is shown only when all conditions are matched. + // + // example: { display: ['flex', 'block'], position: ['absolute'] }; + // in this case the property is only shown when display is + // of value 'flex' or 'block' AND position is 'absolute' + requires: null, + + // Specifies dependency on properties of the parent of the selected object. + // Property is shown only when all conditions are matched. + requiresParent: null }, initialize(props = {}, opts = {}) { diff --git a/src/style_manager/model/PropertyFactory.js b/src/style_manager/model/PropertyFactory.js index e3d690fd0..12768d253 100644 --- a/src/style_manager/model/PropertyFactory.js +++ b/src/style_manager/model/PropertyFactory.js @@ -40,6 +40,7 @@ module.exports = () => ({ case 'height': case 'max-height': case 'min-height': + case 'flex-basis': obj.fixedValues = ['initial', 'inherit', 'auto']; break; case 'font-size': @@ -72,6 +73,12 @@ module.exports = () => ({ obj.type = 'radio'; break; case 'display': + case 'flex-direction': + case 'flex-wrap': + case 'justify-content': + case 'align-items': + case 'align-content': + case 'align-self': case 'font-family': case 'font-weight': case 'border-style': @@ -84,6 +91,8 @@ module.exports = () => ({ case 'transition-timing-function': case 'cursor': case 'overflow': + case 'overflow-x': + case 'overflow-y': obj.type = 'select'; break; case 'top': @@ -128,6 +137,10 @@ module.exports = () => ({ case 'transform-scale-x': case 'transform-scale-y': case 'transform-scale-z': + case 'order': + case 'flex-grow': + case 'flex-shrink': + case 'flex-basis': obj.type = 'integer'; break; case 'margin': @@ -166,6 +179,24 @@ module.exports = () => ({ case 'display': obj.defaults = 'block'; break; + case 'flex-direction': + obj.defaults = 'row'; + break; + case 'flex-wrap': + obj.defaults = 'nowrap'; + break; + case 'justify-content': + obj.defaults = 'flex-start'; + break; + case 'align-items': + obj.defaults = 'stretch'; + break; + case 'align-content': + obj.defaults = 'stretch'; + break; + case 'align-self': + obj.defaults = 'auto'; + break; case 'position': obj.defaults = 'static'; break; @@ -192,6 +223,8 @@ module.exports = () => ({ case 'transform-rotate-x': case 'transform-rotate-y': case 'transform-rotate-z': + case 'order': + case 'flex-grow': obj.defaults = 0; break; case 'border-top-left-radius': @@ -203,6 +236,7 @@ module.exports = () => ({ case 'transform-scale-x': case 'transform-scale-y': case 'transform-scale-z': + case 'flex-shrink': obj.defaults = 1; break; case 'box-shadow-blur': @@ -216,6 +250,7 @@ module.exports = () => ({ case 'height': case 'background-size': case 'cursor': + case 'flex-basis': obj.defaults = 'auto'; break; case 'font-family': @@ -266,10 +301,36 @@ module.exports = () => ({ obj.defaults = 'ease'; break; case 'overflow': + case 'overflow-x': + case 'overflow-y': obj.defaults = 'visible'; break; } + /* + * Add styleable dependency on other properties. Allows properties to be + * dynamically hidden or shown based on values of other properties. + * + * Property will be styleable if all of the properties (keys) in the + * requires object have any of the values specified in the array. + */ + switch (prop) { + case 'flex-direction': + case 'flex-wrap': + case 'justify-content': + case 'align-items': + case 'align-content': + obj.requires = { display: ['flex'] }; + break; + case 'order': + case 'flex-basis': + case 'flex-grow': + case 'flex-shrink': + case 'align-self': + obj.requiresParent = { display: ['flex'] }; + break; + } + // Units switch (prop) { case 'top': @@ -294,6 +355,9 @@ module.exports = () => ({ case 'width': obj.units = ['px', '%', 'vw']; break; + case 'flex-basis': + obj.units = ['px', '%', 'vw', 'vh']; + break; case 'text-shadow-v': case 'text-shadow-h': case 'text-shadow-blur': @@ -352,6 +416,7 @@ module.exports = () => ({ case 'box-shadow-blur': case 'transition-duration': case 'perspective': + case 'flex-basis': obj.min = 0; break; } @@ -407,9 +472,64 @@ module.exports = () => ({ { value: 'block' }, { value: 'inline' }, { value: 'inline-block' }, + { value: 'flex' }, { value: 'none' } ]; break; + case 'flex-direction': + obj.list = [ + { value: 'row' }, + { value: 'row-reverse' }, + { value: 'column' }, + { value: 'column-reverse' } + ]; + break; + case 'flex-wrap': + obj.list = [ + { value: 'nowrap' }, + { value: 'wrap' }, + { value: 'wrap-reverse' } + ]; + break; + case 'justify-content': + obj.list = [ + { value: 'flex-start' }, + { value: 'flex-end' }, + { value: 'center' }, + { value: 'space-between' }, + { value: 'space-around' }, + { value: 'space-evenly' } + ]; + break; + case 'align-items': + obj.list = [ + { value: 'flex-start' }, + { value: 'flex-end' }, + { value: 'center' }, + { value: 'baseline' }, + { value: 'stretch' } + ]; + break; + case 'align-content': + obj.list = [ + { value: 'flex-start' }, + { value: 'flex-end' }, + { value: 'center' }, + { value: 'space-between' }, + { value: 'space-around' }, + { value: 'stretch' } + ]; + break; + case 'align-self': + obj.list = [ + { value: 'auto' }, + { value: 'flex-start' }, + { value: 'flex-end' }, + { value: 'center' }, + { value: 'baseline' }, + { value: 'stretch' } + ]; + break; case 'position': obj.list = [ { value: 'static' }, @@ -552,6 +672,8 @@ module.exports = () => ({ ]; break; case 'overflow': + case 'overflow-x': + case 'overflow-y': obj.list = [ { value: 'visible' }, { value: 'hidden' }, diff --git a/src/style_manager/view/PropertyView.js b/src/style_manager/view/PropertyView.js index 017996224..68037c87e 100644 --- a/src/style_manager/view/PropertyView.js +++ b/src/style_manager/view/PropertyView.js @@ -1,6 +1,7 @@ import Backbone from 'backbone'; import { bindAll, isArray, isUndefined, debounce } from 'underscore'; import { camelCase } from 'utils/mixins'; +import { includes, each } from 'underscore'; const clearProp = 'data-clear-style'; @@ -70,6 +71,15 @@ module.exports = Backbone.View.extend({ em && em.on(`update:component:style:${this.property}`, this.targetUpdated); //em && em.on(`styleable:change:${this.property}`, this.targetUpdated); + + // Listening to changes of properties in this.requires, so that styleable + // changes based on other properties are propagated + const requires = model.get('requires'); + requires && + Object.keys(requires).forEach(property => { + em && em.on(`component:styleUpdate:${property}`, this.targetUpdated); + }); + this.listenTo( this.propTarget, 'update styleManager:update', @@ -406,6 +416,10 @@ module.exports = Backbone.View.extend({ const toRequire = model.get('toRequire'); const unstylable = trg.get('unstylable'); const stylableReq = trg.get('stylable-require'); + const requires = model.get('requires'); + const requiresParent = model.get('requiresParent'); + const sectors = this.sector ? this.sector.collection : null; + const selected = this.em ? this.em.getSelected() : null; let stylable = trg.get('stylable'); // Stylable could also be an array indicating with which property @@ -427,6 +441,34 @@ module.exports = Backbone.View.extend({ (stylableReq.indexOf(id) >= 0 || stylableReq.indexOf(property) >= 0)); } + // Check if the property is available based on other property's values + if (sectors && requires) { + const properties = Object.keys(requires); + sectors.each(sector => { + sector.get('properties').each(model => { + if (includes(properties, model.id)) { + const values = requires[model.id]; + stylable = stylable && includes(values, model.get('value')); + } + }); + }); + } + + // Check if the property is available based on parent's property values + if (requiresParent) { + const parent = selected && selected.parent(); + const parentEl = parent && parent.getEl(); + if (parentEl) { + const styles = window.getComputedStyle(parentEl); + each(requiresParent, (values, property) => { + stylable = + stylable && styles[property] && includes(values, styles[property]); + }); + } else { + stylable = false; + } + } + return stylable; }, diff --git a/src/utils/mixins.js b/src/utils/mixins.js index e8915d9e8..86897dfba 100644 --- a/src/utils/mixins.js +++ b/src/utils/mixins.js @@ -126,7 +126,8 @@ const getModel = (el, $) => { * @param {Event} ev * @return {Event} */ -const getPointerEvent = (ev) => ev.touches && ev.touches[0] ? ev.touches[0] : ev; +const getPointerEvent = ev => + ev.touches && ev.touches[0] ? ev.touches[0] : ev; export { on, @@ -140,5 +141,5 @@ export { shallowDiff, normalizeFloat, getPointerEvent, - getUnitFromValue, + getUnitFromValue }; diff --git a/test/specs/style_manager/model/Models.js b/test/specs/style_manager/model/Models.js index 9dd0cdb3f..f2ab7b9dc 100644 --- a/test/specs/style_manager/model/Models.js +++ b/test/specs/style_manager/model/Models.js @@ -60,6 +60,7 @@ module.exports = { { value: 'block' }, { value: 'inline' }, { value: 'inline-block' }, + { value: 'flex' }, { value: 'none' } ] }); @@ -369,12 +370,121 @@ module.exports = { { value: 'block' }, { value: 'inline' }, { value: 'inline-block' }, + { value: 'flex' }, { value: 'none' } ] } ]); }); + test('Build flex-direction', () => { + expect(obj.build('flex-direction')).toEqual([ + { + property: 'flex-direction', + type: 'select', + defaults: 'row', + list: [ + { value: 'row' }, + { value: 'row-reverse' }, + { value: 'column' }, + { value: 'column-reverse' } + ], + requires: { display: ['flex'] } + } + ]); + }); + + test('Build flex-wrap', () => { + expect(obj.build('flex-wrap')).toEqual([ + { + property: 'flex-wrap', + type: 'select', + defaults: 'nowrap', + list: [ + { value: 'nowrap' }, + { value: 'wrap' }, + { value: 'wrap-reverse' } + ], + requires: { display: ['flex'] } + } + ]); + }); + + test('Build justify-content', () => { + expect(obj.build('justify-content')).toEqual([ + { + property: 'justify-content', + type: 'select', + defaults: 'flex-start', + list: [ + { value: 'flex-start' }, + { value: 'flex-end' }, + { value: 'center' }, + { value: 'space-between' }, + { value: 'space-around' }, + { value: 'space-evenly' } + ], + requires: { display: ['flex'] } + } + ]); + }); + + test('Build align-items', () => { + expect(obj.build('align-items')).toEqual([ + { + property: 'align-items', + type: 'select', + defaults: 'stretch', + list: [ + { value: 'flex-start' }, + { value: 'flex-end' }, + { value: 'center' }, + { value: 'baseline' }, + { value: 'stretch' } + ], + requires: { display: ['flex'] } + } + ]); + }); + + test('Build align-content', () => { + expect(obj.build('align-content')).toEqual([ + { + property: 'align-content', + type: 'select', + defaults: 'stretch', + list: [ + { value: 'flex-start' }, + { value: 'flex-end' }, + { value: 'center' }, + { value: 'space-between' }, + { value: 'space-around' }, + { value: 'stretch' } + ], + requires: { display: ['flex'] } + } + ]); + }); + + test('Build align-self', () => { + expect(obj.build('align-self')).toEqual([ + { + property: 'align-self', + type: 'select', + defaults: 'auto', + list: [ + { value: 'auto' }, + { value: 'flex-start' }, + { value: 'flex-end' }, + { value: 'center' }, + { value: 'baseline' }, + { value: 'stretch' } + ], + requiresParent: { display: ['flex'] } + } + ]); + }); + test('Build position', () => { expect(obj.build('position')).toEqual([ { @@ -431,6 +541,19 @@ module.exports = { expect(obj.build('max-width')).toEqual([res]); }); + test('Build flex-basis', () => { + var res = { + type: 'integer', + units: ['px', '%', 'vw', 'vh'], + defaults: 'auto', + fixedValues: ['initial', 'inherit', 'auto'], + requiresParent: { display: ['flex'] }, + min: 0 + }; + res.property = 'flex-basis'; + expect(obj.build('flex-basis')).toEqual([res]); + }); + test('Build height family', () => { var res = { type: 'integer', @@ -1032,6 +1155,36 @@ module.exports = { }; expect(obj.build('overflow')).toEqual([res]); }); + + test('Build overflow-x', () => { + var res = { + type: 'select', + property: 'overflow-x', + defaults: 'visible', + list: [ + { value: 'visible' }, + { value: 'hidden' }, + { value: 'scroll' }, + { value: 'auto' } + ] + }; + expect(obj.build('overflow-x')).toEqual([res]); + }); + + test('Build overflow-y', () => { + var res = { + type: 'select', + property: 'overflow-y', + defaults: 'visible', + list: [ + { value: 'visible' }, + { value: 'hidden' }, + { value: 'scroll' }, + { value: 'auto' } + ] + }; + expect(obj.build('overflow-y')).toEqual([res]); + }); }); } };