diff --git a/src/style_manager/index.js b/src/style_manager/index.js index 98d311b10..72b62bc63 100644 --- a/src/style_manager/index.js +++ b/src/style_manager/index.js @@ -594,7 +594,9 @@ export default () => { const value = style[name]; const hasVal = propDef(value); const isStack = prop.getType() === 'stack'; + const isComposite = prop.getType() === 'composite'; let newLayers = isStack ? prop.__getLayersFromStyle(style) : []; + // let newProps = isComposite ? prop.__getPropsFromStyle(style) : {}; let newValue = hasVal ? value : null; let parentTarget = null; diff --git a/src/style_manager/model/PropertyComposite.js b/src/style_manager/model/PropertyComposite.js index 4cb1cd6fd..adc08f30b 100644 --- a/src/style_manager/model/PropertyComposite.js +++ b/src/style_manager/model/PropertyComposite.js @@ -1,4 +1,4 @@ -import { isString } from 'underscore'; +import { isString, isUndefined } from 'underscore'; import Property from './Property'; export default Property.extend({ @@ -92,17 +92,19 @@ export default Property.extend({ return isString(join) ? join : this.get('separator'); }, + // TODO remove __getFromStyle(style = {}) { let result = {}; const fromStyle = this.get('fromStyle'); + const sep = this.getSplitSeparator(); + const name = this.getName(); if (fromStyle) { - result = fromStyle(style); + result = fromStyle(style, { property: this, separator: sep }); } else { - const name = this.getName(); const value = style[name]; if (value) { - const values = value.split(this.getSplitSeparator()); + const values = value.split(sep); this.getProperties().forEach((prop, i) => { const len = values.length; // Try to get value from a shorthand: @@ -123,6 +125,59 @@ export default Property.extend({ return result; }, + __styleHasProps(style = {}) { + const name = this.getName(); + const props = this.getProperties(); + const nameProps = props.map(prop => prop.getName()); + const allNameProps = [name, ...nameProps]; + return allNameProps.some(prop => !isUndefined(style[prop]) && style[prop] !== ''); + }, + + __splitStyleName(style, name, sep) { + return (style[name] || '') + .split(sep) + .map(value => value.trim()) + .filter(Boolean); + }, + + __getPropsFromStyle(style = {}) { + if (!this.__styleHasProps(style)) return null; + + const props = this.getProperties(); + const sep = this.getSplitSeparator(); + const fromStyle = this.get('fromStyle'); + let result = fromStyle ? fromStyle(style, { property: this, separator: sep }) : {}; + + if (!fromStyle) { + const props4Nums = props.length === 4 && props.every(prop => prop.getType() === 'integer'); + + // Get props from the main property + const values = this.__splitStyleName(style, this.getName(), sep); + props.forEach((prop, i) => { + const value = values[i]; + let res = !isUndefined(value) ? value : prop.getDefaultValue(); + + if (props4Nums) { + // Try to get value from a shorthand: + // 11px -> 11px 11px 11px 11xp + // 11px 22px -> 11px 22px 11px 22xp + const len = values.length; + res = values[i] || values[(i % len) + (len != 1 && len % 2 ? 1 : 0)] || res; + } + + result[prop.getId()] = res || ''; + }); + + // Get props from the inner properties + props.forEach(prop => { + const value = style[prop.getName()]; + if (!isUndefined(value) && value !== '') result[prop.getId()] = value; + }); + } + + return result; + }, + clear() { this.getProperties().map(p => p.clear({ __clearIn: !this.isDetached() })); return Property.prototype.clear.call(this); diff --git a/src/style_manager/model/PropertyStack.js b/src/style_manager/model/PropertyStack.js index 93c6c9292..fd8236984 100644 --- a/src/style_manager/model/PropertyStack.js +++ b/src/style_manager/model/PropertyStack.js @@ -7,13 +7,6 @@ import { camelCase } from 'utils/mixins'; const VALUES_REG = /,(?![^\(]*\))/; const PARTS_REG = /\s(?![^(]*\))/; -const splitStyleName = (style, name, sep) => { - return (style[name] || '') - .split(sep) - .map(value => value.trim()) - .filter(Boolean); -}; - export default Property.extend({ defaults: { ...Property.prototype.defaults, @@ -141,45 +134,39 @@ export default Property.extend({ }, __getLayersFromStyle(style = {}) { + if (!this.__styleHasProps(style)) return null; + const name = this.getName(); const props = this.getProperties(); - const nameProps = props.map(prop => prop.getName()); - const allNameProps = [name, ...nameProps]; - const hasProps = allNameProps.some(prop => !isUndefined(style[prop]) && style[prop] !== ''); - - if (!hasProps) { - return null; - } else { - const sep = this.getLayerSeparator(); - const fromStyle = this.get('fromStyle'); - let result = fromStyle ? fromStyle(style, { property: this, separatorLayers: sep }) : []; - - if (!fromStyle) { - // Get layers from the main property - const layers = splitStyleName(style, name, sep) - .map(value => value.split(this.getSplitSeparator())) - .map(parts => { - const result = {}; - props.forEach((prop, i) => { - const value = parts[i]; - result[prop.getId()] = !isUndefined(value) ? value : prop.getDefaultValue(); - }); - return result; + const sep = this.getLayerSeparator(); + const fromStyle = this.get('fromStyle'); + let result = fromStyle ? fromStyle(style, { property: this, separatorLayers: sep }) : []; + + if (!fromStyle) { + // Get layers from the main property + const layers = this.__splitStyleName(style, name, sep) + .map(value => value.split(this.getSplitSeparator())) + .map(parts => { + const result = {}; + props.forEach((prop, i) => { + const value = parts[i]; + result[prop.getId()] = !isUndefined(value) ? value : prop.getDefaultValue(); }); - // Get layers from the inner properties - props.forEach(prop => { - const id = prop.getId(); - splitStyleName(style, prop.getName(), sep) - .map(value => ({ [id]: value || prop.getDefaultValue() })) - .forEach((inLayer, i) => { - layers[i] = layers[i] ? { ...layers[i], ...inLayer } : inLayer; - }); + return result; }); - result = layers; - } - - return isArray(result) ? result : [result]; + // Get layers from the inner properties + props.forEach(prop => { + const id = prop.getId(); + this.__splitStyleName(style, prop.getName(), sep) + .map(value => ({ [id]: value || prop.getDefaultValue() })) + .forEach((inLayer, i) => { + layers[i] = layers[i] ? { ...layers[i], ...inLayer } : inLayer; + }); + }); + result = layers; } + + return isArray(result) ? result : [result]; }, hasValue(opts) { diff --git a/test/specs/style_manager/model/Properties.js b/test/specs/style_manager/model/Properties.js index e798cfed5..b29a2c2c9 100644 --- a/test/specs/style_manager/model/Properties.js +++ b/test/specs/style_manager/model/Properties.js @@ -34,7 +34,10 @@ describe('StyleManager properties logic', () => { describe('Composite type', () => { const propTest = 'padding'; - const propInTest = 'padding-top'; + const propATest = `${propTest}-top`; + const propBTest = `${propTest}-right`; + const propCTest = `${propTest}-bottom`; + const propDTest = `${propTest}-left`; let compTypeProp; let compTypePropInn; @@ -49,7 +52,7 @@ describe('StyleManager properties logic', () => { ], }); compTypeProp = obj.getProperty(sectorTest, propTest); - compTypePropInn = compTypeProp.getProperty(propInTest); + compTypePropInn = compTypeProp.getProperty(propATest); em.setSelected(cmp); obj.__upSel(); }); @@ -72,11 +75,86 @@ describe('StyleManager properties logic', () => { expect(obj.getLastSelected()).toBe(rule1); }); + test('Properties correctly reflect on rule update', () => { + compTypeProp.set('detached', false); + rule1.setStyle({ padding: '1px 2px 3px 4px' }); + obj.__upSel(); + [ + [propATest, '1px'], + [propBTest, '2px'], + [propCTest, '3px'], + [propDTest, '4px'], + ].forEach(item => { + expect(compTypeProp.getProperty(item[0]).getFullValue()).toBe(item[1]); + }); + }); + + test('getPropsFromStyle returns correct values', () => { + expect( + compTypeProp.__getPropsFromStyle({ + [propTest]: '1px 2px 3px 4px', + [propCTest]: '33px', + [propBTest]: '22%', + }) + ).toEqual({ + [propATest]: '1px', + [propBTest]: '22%', + [propCTest]: '33px', + [propDTest]: '4px', + }); + + // Split correctly props in 4 numeric values + expect( + compTypeProp.__getPropsFromStyle({ + [propTest]: '111px', + [propCTest]: '33px', + [propBTest]: '22%', + }) + ).toEqual({ + [propATest]: '111px', + [propBTest]: '22%', + [propCTest]: '33px', + [propDTest]: '111px', + }); + + // Resolves correctly with values from inner properties + expect( + compTypeProp.__getPropsFromStyle({ + color: 'red', + [propCTest]: '33px', + }) + ).toEqual({ + [propATest]: '0', + [propBTest]: '0', + [propCTest]: '33px', + [propDTest]: '0', + }); + + // null if no properties are found + expect(compTypeProp.__getPropsFromStyle({ color: 'red' })).toEqual(null); + }); + + test('Update on properties reflects to the rule correctly', () => { + compTypeProp.set('detached', false); + rule1.setStyle({ padding: '1px 2px 3px 4px' }); + obj.__upSel(); + compTypeProp.getProperty(propCTest).upValue('50%'); + obj.__upSel(); + expect(rule1.getStyle()).toEqual({ + __p: false, + padding: '1px 2px 50% 4px', + [propATest]: '', + [propBTest]: '', + [propCTest]: '', + [propDTest]: '', + }); + }); + test('Updating inner property, it reflects on the rule', () => { compTypePropInn.upValue('55%'); const style = rule1.getStyle(); - const otherProps = Object.keys(style).filter(p => p.indexOf('padding') >= 0 && p !== propInTest); - expect(style[propInTest]).toBe('55%'); + const otherProps = Object.keys(style).filter(p => p.indexOf('padding') >= 0 && p !== propATest); + expect(style[propATest]).toBe('55%'); expect(compTypeProp.hasValue()).toBe(true); expect(compTypePropInn.hasValue()).toBe(true); otherProps.forEach(prop => {