diff --git a/src/css_composer/model/CssRule.js b/src/css_composer/model/CssRule.ts similarity index 72% rename from src/css_composer/model/CssRule.js rename to src/css_composer/model/CssRule.ts index de308d122..9841b57c5 100644 --- a/src/css_composer/model/CssRule.js +++ b/src/css_composer/model/CssRule.ts @@ -4,7 +4,70 @@ import StyleableModel from '../../domain_abstract/model/StyleableModel'; import Selectors from '../../selector_manager/model/Selectors'; import { getMediaLength } from '../../code_manager/model/CssGenerator'; import { isEmptyObj, hasWin } from '../../utils/mixins'; +import Selector from '../../selector_manager/model/Selector'; +import EditorModel from '../../editor/model/Editor'; +/** @private */ +export interface CssRuleProperties { + /** + * Array of selectors + */ + selectors: Selector[]; + /** + * Object containing style definitions + * @default {} + */ + style?: Record; + /** + * Additional string css selectors + * @default '' + */ + selectorsAdd?: string; + /** + * Type of at-rule, eg. `media`, 'font-face' + * @default '' + */ + atRuleType?: string; + /** + * At-rule value, eg. `(max-width: 1000px)` + * @default '' + */ + mediaText?: string; + /** + * This property is used only on at-rules, like 'page' or 'font-face', where the block containes only style declarations. + * @default false + */ + singleAtRule?: boolean; + /** + * State of the rule, eg: `hover`, `focused` + * @default '' + */ + state?: string; + /** + * If true, sets `!important` on all properties. You can also pass an array to specify properties on which to use important. + * @default false + */ + important?: boolean | string[]; + /** + * Indicates if the rule is stylable from the editor. + * @default true + */ + stylable?: boolean | string[]; + /** + * Group for rules. + * @default '' + */ + group?: string; + /** + * If true, the rule won't be stored in JSON or showed in CSS export. + * @default false + */ + shallow?: boolean; +} + +type AnyObject = Record; + +// @ts-ignore const { CSS } = hasWin() ? window : {}; /** @@ -23,7 +86,11 @@ const { CSS } = hasWin() ? window : {}; * [State]: state.html * [Component]: component.html */ -export default class CssRule extends StyleableModel { +export default class CssRule extends StyleableModel { + config: CssRuleProperties; + em?: EditorModel; + opt: any; + defaults() { return { selectors: [], @@ -36,38 +103,39 @@ export default class CssRule extends StyleableModel { singleAtRule: false, important: false, group: '', - // If true, won't be stored in JSON or showed in CSS shallow: false, _undo: true, }; } - initialize(c, opt = {}) { - this.config = c || {}; + constructor(props: CssRuleProperties, opt: any = {}) { + super(props); + this.config = props || {}; this.opt = opt; this.em = opt.em; - this.ensureSelectors(); + this.ensureSelectors(null, null, {}); this.on('change', this.__onChange); } - __onChange(m, opts) { + __onChange(m: CssRule, opts: any) { const { em } = this; const changed = this.changedAttributes(); - !isEmptyObj(changed) && em && em.changesUp(opts); + changed && !isEmptyObj(changed) && em?.changesUp(opts); } - clone() { + clone(): CssRule { const opts = { ...this.opt }; const attr = { ...this.attributes }; - attr.selectors = this.get('selectors').map(s => s.clone()); + attr.selectors = this.get('selectors')!.map(s => s.clone()) as Selector[]; + // @ts-ignore return new this.constructor(attr, opts); } - ensureSelectors(m, c, opts) { + ensureSelectors(m: any, c: any, opts: any) { const { em } = this; - const sm = em && em.get('SelectorManager'); + const sm = em?.get('SelectorManager'); const toListen = [this, 'change:selectors', this.ensureSelectors]; - let sels = this.getSelectors(); + let sels = this.getSelectors() as any; this.stopListening(...toListen); if (sels.models) { @@ -82,6 +150,7 @@ export default class CssRule extends StyleableModel { } this.set('selectors', sels, opts); + // @ts-ignore this.listenTo(...toListen); } @@ -114,13 +183,14 @@ export default class CssRule extends StyleableModel { * cssRule.selectorsToString(); // ".class1:hover" * cssRule.selectorsToString({ skipState: true }); // ".class1" */ - selectorsToString(opts = {}) { + selectorsToString(opts: AnyObject = {}) { const result = []; const state = this.get('state'); const addSelector = this.get('selectorsAdd'); const selOpts = { - escape: str => (CSS && CSS.escape ? CSS.escape(str) : str), + escape: (str: string) => (CSS && CSS.escape ? CSS.escape(str) : str), }; + // @ts-ignore const selectors = this.get('selectors').getFullString(0, selOpts); const stateStr = state && !opts.skipState ? `:${state}` : ''; selectors && result.push(`${selectors}${stateStr}`); @@ -139,7 +209,7 @@ export default class CssRule extends StyleableModel { * }); * cssRule.getDeclaration() // ".class1{color:red;}" */ - getDeclaration(opts = {}) { + getDeclaration(opts: AnyObject = {}) { let result = ''; const { important } = this.attributes; const selectors = this.selectorsToString(opts); @@ -164,11 +234,11 @@ export default class CssRule extends StyleableModel { const { em } = this; const { atRuleType, mediaText } = this.attributes; const devices = em?.get('DeviceManager').getDevices() || []; - const deviceDefault = devices.filter(d => d.getWidthMedia() === '')[0]; + const deviceDefault = devices.filter((d: any) => d.getWidthMedia() === '')[0]; if (atRuleType !== 'media' || !mediaText) { return deviceDefault || null; } - return devices.filter(d => d.getWidthMedia() === getMediaLength(mediaText))[0] || null; + return devices.filter((d: any) => d.getWidthMedia() === getMediaLength(mediaText))[0] || null; } /** @@ -181,8 +251,8 @@ export default class CssRule extends StyleableModel { getState() { const { em } = this; const stateValue = this.get('state'); - const states = em.get('SelectorManager').getStates() || []; - return states.filter(s => s.getName() === stateValue)[0] || null; + const states = em?.get('SelectorManager').getStates() || []; + return states.filter((s: any) => s.getName() === stateValue)[0] || null; } /** @@ -193,7 +263,7 @@ export default class CssRule extends StyleableModel { * console.log(cmp?.toHTML()); */ getComponent() { - const sel = this.getSelectors(); + const sel = this.getSelectors() as any; const sngl = sel.length == 1 && sel.at(0); const cmpId = sngl && sngl.isId() && sngl.get('name'); return (cmpId && this.em?.get('DomComponents').getById(cmpId)) || null; @@ -210,7 +280,7 @@ export default class CssRule extends StyleableModel { * }); * cssRule.toCSS() // "@media (min-width: 500px){.class1{color:red;}}" */ - toCSS(opts = {}) { + toCSS(opts: AnyObject = {}) { let result = ''; const atRule = this.getAtRule(); const block = this.getDeclaration(opts); @@ -225,10 +295,10 @@ export default class CssRule extends StyleableModel { return result; } - toJSON(...args) { + toJSON(...args: any) { const obj = Model.prototype.toJSON.apply(this, args); - if (this.em.getConfig().avoidDefaults) { + if (this.em?.getConfig().avoidDefaults) { const defaults = this.defaults(); forEach(defaults, (value, key) => { @@ -256,7 +326,7 @@ export default class CssRule extends StyleableModel { * @returns {Boolean} * @private */ - compare(selectors, state, width, ruleProps = {}) { + compare(selectors: any, state: string, width: string, ruleProps: Partial = {}) { const st = state || ''; const wd = width || ''; const selAdd = ruleProps.selectorsAdd || ''; @@ -266,8 +336,8 @@ export default class CssRule extends StyleableModel { // Fix atRuleType in case is not specified with width if (wd && !atRule) atRule = 'media'; - const a1 = sel.map(model => model.getFullName()); - const a2 = this.get('selectors').map(model => model.getFullName()); + const a1: string[] = sel.map((model: any) => model.getFullName()); + const a2: string[] = this.get('selectors')?.map(model => model.getFullName())!; // Check selectors const a1S = a1.slice().sort(); diff --git a/src/domain_abstract/model/StyleableModel.js b/src/domain_abstract/model/StyleableModel.ts similarity index 79% rename from src/domain_abstract/model/StyleableModel.js rename to src/domain_abstract/model/StyleableModel.ts index 4b0e8077e..ca3d5032e 100644 --- a/src/domain_abstract/model/StyleableModel.js +++ b/src/domain_abstract/model/StyleableModel.ts @@ -1,17 +1,20 @@ -import { isString, isArray, keys, isUndefined } from 'underscore'; +import { isString, isArray, keys } from 'underscore'; import { shallowDiff } from '../../utils/mixins'; import ParserHtml from '../../parser/model/ParserHtml'; import { Model } from '../../common'; +import { ObjectHash } from 'backbone'; + +type AnyObject = Record; const parserHtml = ParserHtml(); -export default class StyleableModel extends Model { +export default class StyleableModel extends Model { /** * Forward style string to `parseStyle` to be parse to an object * @param {string} str * @returns */ - parseStyle(str) { + parseStyle(str: string) { return parserHtml.parseStyle(str); } @@ -21,7 +24,7 @@ export default class StyleableModel extends Model { * @param {Object} prop * @return {Object} */ - extendStyle(prop) { + extendStyle(prop: AnyObject): AnyObject { return { ...this.getStyle(), ...prop }; } @@ -29,9 +32,9 @@ export default class StyleableModel extends Model { * Get style object * @return {Object} */ - getStyle(prop) { + getStyle(prop?: string | AnyObject) { const style = this.get('style') || {}; - const result = { ...style }; + const result: AnyObject = { ...style }; return prop && isString(prop) ? result[prop] : result; } @@ -41,7 +44,7 @@ export default class StyleableModel extends Model { * @param {Object} opts * @return {Object} Applied properties */ - setStyle(prop = {}, opts = {}) { + setStyle(prop: string | AnyObject = {}, opts: AnyObject = {}) { if (isString(prop)) { prop = this.parseStyle(prop); } @@ -60,7 +63,8 @@ export default class StyleableModel extends Model { // Delete the property used for partial updates delete diff.__p; keys(diff).forEach(pr => { - const em = this.em; + // @ts-ignore + const { em } = this; if (opts.noEvent) return; this.trigger(`change:style:${pr}`); if (em) { @@ -80,7 +84,7 @@ export default class StyleableModel extends Model { * this.addStyle({color: 'red'}); * this.addStyle('color', 'blue'); */ - addStyle(prop, value = '', opts = {}) { + addStyle(prop: string | AnyObject, value = '', opts = {}) { if (typeof prop == 'string') { prop = { prop: value, @@ -97,7 +101,7 @@ export default class StyleableModel extends Model { * Remove style property * @param {string} prop */ - removeStyle(prop) { + removeStyle(prop: string) { let style = this.getStyle(); delete style[prop]; this.setStyle(style); @@ -108,7 +112,7 @@ export default class StyleableModel extends Model { * @param {Object} [opts={}] Options * @return {String} */ - styleToString(opts = {}) { + styleToString(opts: AnyObject = {}) { const result = []; const style = this.getStyle(opts); @@ -127,11 +131,13 @@ export default class StyleableModel extends Model { return this.get('selectors') || this.get('classes'); } - getSelectorsString(opts) { + getSelectorsString(opts?: AnyObject) { + // @ts-ignore return this.selectorsToString ? this.selectorsToString(opts) : this.getSelectors().getFullString(); } - _validate(attr, opts) { - return true; - } + // @ts-ignore + // _validate(attr, opts) { + // return true; + // } }