mirror of https://github.com/artf/grapesjs.git
committed by
GitHub
21 changed files with 2463 additions and 2436 deletions
@ -1,495 +0,0 @@ |
|||
/** |
|||
* Selectors in GrapesJS are used in CSS Composer inside Rules and in Components as classes. To illustrate this concept let's take |
|||
* a look at this code: |
|||
* |
|||
* ```css
|
|||
* span > #send-btn.btn{ |
|||
* ... |
|||
* } |
|||
* ``` |
|||
* ```html
|
|||
* <span> |
|||
* <button id="send-btn" class="btn"></button> |
|||
* </span> |
|||
* ``` |
|||
* |
|||
* In this scenario we get: |
|||
* * span -> selector of type `tag` |
|||
* * send-btn -> selector of type `id` |
|||
* * btn -> selector of type `class` |
|||
* |
|||
* So, for example, being `btn` the same class entity it'll be easier to refactor and track things. |
|||
* |
|||
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/selector_manager/config/config.js)
|
|||
* ```js
|
|||
* const editor = grapesjs.init({ |
|||
* selectorManager: { |
|||
* // options
|
|||
* } |
|||
* }) |
|||
* ``` |
|||
* |
|||
* Once the editor is instantiated you can use its API and listen to its events. Before using these methods, you should get the module from the instance. |
|||
* |
|||
* ```js
|
|||
* // Listen to events
|
|||
* editor.on('selector:add', (selector) => { ... }); |
|||
* |
|||
* // Use the API
|
|||
* const sm = editor.Selectors; |
|||
* sm.add(...); |
|||
* ``` |
|||
* |
|||
* ## Available Events |
|||
* * `selector:add` - Selector added. The [Selector] is passed as an argument to the callback. |
|||
* * `selector:remove` - Selector removed. The [Selector] is passed as an argument to the callback. |
|||
* * `selector:update` - Selector updated. The [Selector] and the object containing changes are passed as arguments to the callback. |
|||
* * `selector:state` - States changed. An object containing all the available data about the triggered event is passed as an argument to the callback. |
|||
* * `selector` - Catch-all event for all the events mentioned above. An object containing all the available data about the triggered event is passed as an argument to the callback. |
|||
* |
|||
* ## Methods |
|||
* * [getConfig](#getconfig) |
|||
* * [add](#add) |
|||
* * [get](#get) |
|||
* * [remove](#remove) |
|||
* * [getAll](#getall) |
|||
* * [setState](#setstate) |
|||
* * [getState](#getstate) |
|||
* * [getStates](#getstates) |
|||
* * [setStates](#setstates) |
|||
* * [getSelected](#getselected) |
|||
* * [addSelected](#addselected) |
|||
* * [removeSelected](#removeselected) |
|||
* * [getSelectedTargets](#getselectedtargets) |
|||
* * [setComponentFirst](#setcomponentfirst) |
|||
* * [getComponentFirst](#getcomponentfirst) |
|||
* |
|||
* [Selector]: selector.html |
|||
* [State]: state.html |
|||
* [Component]: component.html |
|||
* [CssRule]: css_rule.html |
|||
* |
|||
* @module SelectorManager |
|||
*/ |
|||
|
|||
import { isString, debounce, isObject, isArray } from 'underscore'; |
|||
import { isComponent, isRule } from '../utils/mixins'; |
|||
import { Model, Collection, Module } from '../common'; |
|||
import defaults from './config/config'; |
|||
import Selector from './model/Selector'; |
|||
import Selectors from './model/Selectors'; |
|||
import State from './model/State'; |
|||
import ClassTagsView from './view/ClassTagsView'; |
|||
|
|||
const isId = str => isString(str) && str[0] == '#'; |
|||
const isClass = str => isString(str) && str[0] == '.'; |
|||
|
|||
export const evAll = 'selector'; |
|||
export const evPfx = `${evAll}:`; |
|||
export const evAdd = `${evPfx}add`; |
|||
export const evUpdate = `${evPfx}update`; |
|||
export const evRemove = `${evPfx}remove`; |
|||
export const evRemoveBefore = `${evRemove}:before`; |
|||
export const evCustom = `${evPfx}custom`; |
|||
export const evState = `${evPfx}state`; |
|||
|
|||
export default () => { |
|||
return { |
|||
...Module, |
|||
|
|||
name: 'SelectorManager', |
|||
|
|||
Selector, |
|||
|
|||
Selectors, |
|||
|
|||
events: { |
|||
all: evAll, |
|||
update: evUpdate, |
|||
add: evAdd, |
|||
remove: evRemove, |
|||
removeBefore: evRemoveBefore, |
|||
state: evState, |
|||
custom: evCustom, |
|||
}, |
|||
|
|||
/** |
|||
* Get configuration object |
|||
* @name getConfig |
|||
* @function |
|||
* @return {Object} |
|||
*/ |
|||
|
|||
init(conf = {}) { |
|||
this.__initConfig(defaults, conf); |
|||
const config = this.getConfig(); |
|||
const em = config.em; |
|||
const ppfx = config.pStylePrefix; |
|||
|
|||
if (ppfx) { |
|||
config.stylePrefix = ppfx + config.stylePrefix; |
|||
} |
|||
|
|||
// Global selectors container
|
|||
this.all = new Selectors(config.selectors); |
|||
this.selected = new Selectors([], { em, config }); |
|||
this.states = new Collection(config.states, { model: State }); |
|||
this.model = new Model({ cFirst: config.componentFirst, _undo: true }); |
|||
this.__initListen({ |
|||
collections: [this.states, this.selected], |
|||
propagate: [{ entity: this.states, event: this.events.state }], |
|||
}); |
|||
em.on('change:state', (m, value) => em.trigger(evState, value)); |
|||
this.model.on('change:cFirst', (m, value) => em.trigger('selector:type', value)); |
|||
const listenTo = |
|||
'component:toggled component:update:classes change:device styleManager:update selector:state selector:type'; |
|||
this.model.listenTo(em, listenTo, () => this.__update()); |
|||
|
|||
return this; |
|||
}, |
|||
|
|||
__update: debounce(function () { |
|||
this.__trgCustom(); |
|||
}), |
|||
|
|||
__trgCustom(opts) { |
|||
this.em.trigger(this.events.custom, this.__customData(opts)); |
|||
}, |
|||
|
|||
__customData(opts = {}) { |
|||
const { container } = opts; |
|||
return { |
|||
states: this.getStates(), |
|||
selected: this.getSelected(), |
|||
container, |
|||
}; |
|||
}, |
|||
|
|||
// postLoad() {
|
|||
// this.__postLoad();
|
|||
// const { em, model } = this;
|
|||
// const um = em.get('UndoManager');
|
|||
// um && um.add(model);
|
|||
// um && um.add(this.pages);
|
|||
// },
|
|||
|
|||
postRender() { |
|||
this.__appendTo(); |
|||
this.__trgCustom(); |
|||
}, |
|||
|
|||
select(value, opts = {}) { |
|||
const targets = Array.isArray(value) ? value : [value]; |
|||
const toSelect = this.em.get('StyleManager').select(targets, opts); |
|||
const selTags = this.selectorTags; |
|||
const res = toSelect |
|||
.filter(i => i) |
|||
.map(sel => |
|||
isComponent(sel) ? sel : isRule(sel) && !sel.get('selectorsAdd') ? sel : sel.getSelectorsString() |
|||
); |
|||
selTags && selTags.componentChanged({ targets: res }); |
|||
return this; |
|||
}, |
|||
|
|||
addSelector(name, opts = {}, cOpts = {}) { |
|||
let props = { ...opts }; |
|||
|
|||
if (isObject(name)) { |
|||
props = name; |
|||
} else { |
|||
props.name = name; |
|||
} |
|||
|
|||
if (isId(props.name)) { |
|||
props.name = props.name.substr(1); |
|||
props.type = Selector.TYPE_ID; |
|||
} else if (isClass(props.name)) { |
|||
props.name = props.name.substr(1); |
|||
} |
|||
|
|||
if (props.label && !props.name) { |
|||
props.name = this.escapeName(props.label); |
|||
} |
|||
|
|||
const cname = props.name; |
|||
const config = this.getConfig(); |
|||
const all = this.getAll(); |
|||
const selector = cname ? this.get(cname, props.type) : all.where(props)[0]; |
|||
|
|||
if (!selector) { |
|||
return all.add(props, { ...cOpts, config }); |
|||
} |
|||
|
|||
return selector; |
|||
}, |
|||
|
|||
getSelector(name, type = Selector.TYPE_CLASS) { |
|||
if (isId(name)) { |
|||
name = name.substr(1); |
|||
type = Selector.TYPE_ID; |
|||
} else if (isClass(name)) { |
|||
name = name.substr(1); |
|||
} |
|||
|
|||
return this.getAll().where({ name, type })[0]; |
|||
}, |
|||
|
|||
/** |
|||
* Add a new selector to the collection if it does not already exist. |
|||
* You can pass selectors properties or string identifiers. |
|||
* @param {Object|String} props Selector properties or string identifiers, eg. `{ name: 'my-class', label: 'My class' }`, `.my-cls` |
|||
* @param {Object} [opts] Selector options |
|||
* @return {[Selector]} |
|||
* @example |
|||
* const selector = selectorManager.add({ name: 'my-class', label: 'My class' }); |
|||
* console.log(selector.toString()) // `.my-class`
|
|||
* // Same as
|
|||
* const selector = selectorManager.add('.my-class'); |
|||
* console.log(selector.toString()) // `.my-class`
|
|||
* */ |
|||
add(props, opts = {}) { |
|||
const cOpts = isString(props) ? {} : opts; |
|||
// Keep support for arrays but avoid it in docs
|
|||
if (isArray(props)) { |
|||
return props.map(item => this.addSelector(item, opts, cOpts)); |
|||
} else { |
|||
return this.addSelector(props, opts, cOpts); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Add class selectors |
|||
* @param {Array|string} classes Array or string of classes |
|||
* @return {Array} Array of added selectors |
|||
* @private |
|||
* @example |
|||
* sm.addClass('class1'); |
|||
* sm.addClass('class1 class2'); |
|||
* sm.addClass(['class1', 'class2']); |
|||
* // -> [SelectorObject, ...]
|
|||
*/ |
|||
addClass(classes) { |
|||
const added = []; |
|||
|
|||
if (isString(classes)) { |
|||
classes = classes.trim().split(' '); |
|||
} |
|||
|
|||
classes.forEach(name => added.push(this.addSelector(name))); |
|||
return added; |
|||
}, |
|||
|
|||
/** |
|||
* Get the selector by its name/type |
|||
* @param {String} name Selector name or string identifier |
|||
* @returns {[Selector]|null} |
|||
* @example |
|||
* const selector = selectorManager.get('.my-class'); |
|||
* // Get Id
|
|||
* const selectorId = selectorManager.get('#my-id'); |
|||
* */ |
|||
get(name, type) { |
|||
// Keep support for arrays but avoid it in docs
|
|||
if (isArray(name)) { |
|||
const result = []; |
|||
const selectors = name.map(item => this.getSelector(item)).filter(item => item); |
|||
selectors.forEach(item => result.indexOf(item) < 0 && result.push(item)); |
|||
return result; |
|||
} else { |
|||
return this.getSelector(name, type) || null; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Remove Selector. |
|||
* @param {String|[Selector]} selector Selector instance or Selector string identifier |
|||
* @returns {[Selector]} Removed Selector |
|||
* @example |
|||
* const removed = selectorManager.remove('.myclass'); |
|||
* // or by passing the Selector
|
|||
* selectorManager.remove(selectorManager.get('.myclass')); |
|||
*/ |
|||
remove(selector, opts) { |
|||
return this.__remove(selector, opts); |
|||
}, |
|||
|
|||
/** |
|||
* Change the selector state |
|||
* @param {String} value State value |
|||
* @returns {this} |
|||
* @example |
|||
* selectorManager.setState('hover'); |
|||
*/ |
|||
setState(value) { |
|||
this.em.setState(value); |
|||
return this; |
|||
}, |
|||
|
|||
/** |
|||
* Get the current selector state value |
|||
* @returns {String} |
|||
*/ |
|||
getState() { |
|||
return this.em.getState(); |
|||
}, |
|||
|
|||
/** |
|||
* Get states |
|||
* @returns {Array<[State]>} |
|||
*/ |
|||
getStates() { |
|||
return [...this.states.models]; |
|||
}, |
|||
|
|||
/** |
|||
* Set a new collection of states |
|||
* @param {Array<Object>} states Array of new states |
|||
* @returns {Array<[State]>} |
|||
* @example |
|||
* const states = selectorManager.setStates([ |
|||
* { name: 'hover', label: 'Hover' }, |
|||
* { name: 'nth-of-type(2n)', label: 'Even/Odd' } |
|||
* ]); |
|||
*/ |
|||
setStates(states, opts) { |
|||
return this.states.reset(states, opts); |
|||
}, |
|||
|
|||
/** |
|||
* Get commonly selected selectors, based on all selected components. |
|||
* @returns {Array<[Selector]>} |
|||
* @example |
|||
* const selected = selectorManager.getSelected(); |
|||
* console.log(selected.map(s => s.toString())) |
|||
*/ |
|||
getSelected() { |
|||
return this.__getCommon(); |
|||
}, |
|||
|
|||
/** |
|||
* Add new selector to all selected components. |
|||
* @param {Object|String} props Selector properties or string identifiers, eg. `{ name: 'my-class', label: 'My class' }`, `.my-cls` |
|||
* @example |
|||
* selectorManager.addSelected('.new-class'); |
|||
*/ |
|||
addSelected(props) { |
|||
const added = this.add(props); |
|||
// TODO: target should be the one from StyleManager
|
|||
this.em.getSelectedAll().forEach(target => { |
|||
target.getSelectors().add(added); |
|||
}); |
|||
// TODO: update selected collection
|
|||
}, |
|||
|
|||
/** |
|||
* Remove a common selector from all selected components. |
|||
* @param {String|[Selector]} selector Selector instance or Selector string identifier |
|||
* @example |
|||
* selectorManager.removeSelected('.myclass'); |
|||
*/ |
|||
removeSelected(selector) { |
|||
this.em.getSelectedAll().forEach(trg => { |
|||
!selector.get('protected') && trg && trg.getSelectors().remove(selector); |
|||
}); |
|||
}, |
|||
|
|||
/** |
|||
* Get the array of currently selected targets. |
|||
* @returns {Array<[Component]|[CssRule]>} |
|||
* @example |
|||
* const targetsToStyle = selectorManager.getSelectedTargets(); |
|||
* console.log(targetsToStyle.map(target => target.getSelectorsString())) |
|||
*/ |
|||
getSelectedTargets() { |
|||
return this.em.get('StyleManager').getSelectedAll(); |
|||
}, |
|||
|
|||
/** |
|||
* Update component-first option. |
|||
* If the component-first is enabled, all the style changes will be applied on selected components (ID rules) instead |
|||
* of selectors (which would change styles on all components with those classes). |
|||
* @param {Boolean} value |
|||
*/ |
|||
setComponentFirst(value) { |
|||
this.getConfig().componentFirst = value; |
|||
this.model.set({ cFirst: value }); |
|||
}, |
|||
|
|||
/** |
|||
* Get the value of component-first option. |
|||
* @return {Boolean} |
|||
*/ |
|||
getComponentFirst() { |
|||
return this.getConfig().componentFirst; |
|||
}, |
|||
|
|||
/** |
|||
* Get all selectors |
|||
* @name getAll |
|||
* @function |
|||
* @return {Collection<[Selector]>} |
|||
* */ |
|||
|
|||
/** |
|||
* Return escaped selector name |
|||
* @param {String} name Selector name to escape |
|||
* @returns {String} Escaped name |
|||
* @private |
|||
*/ |
|||
escapeName(name) { |
|||
const { escapeName } = this.getConfig(); |
|||
return escapeName ? escapeName(name) : Selector.escapeName(name); |
|||
}, |
|||
|
|||
/** |
|||
* Render class selectors. If an array of selectors is provided a new instance of the collection will be rendered |
|||
* @param {Array<Object>} selectors |
|||
* @return {HTMLElement} |
|||
* @private |
|||
*/ |
|||
render(selectors) { |
|||
const { em, selectorTags } = this; |
|||
const config = this.getConfig(); |
|||
const el = selectorTags && selectorTags.el; |
|||
this.selected.reset(selectors); |
|||
this.selectorTags = new ClassTagsView({ |
|||
el, |
|||
collection: this.selected, |
|||
module: this, |
|||
config, |
|||
}); |
|||
|
|||
return this.selectorTags.render().el; |
|||
}, |
|||
|
|||
destroy() { |
|||
const { selectorTags, model } = this; |
|||
model.stopListening(); |
|||
this.__destroy(); |
|||
selectorTags && selectorTags.remove(); |
|||
this.selectorTags = {}; |
|||
}, |
|||
|
|||
/** |
|||
* Get common selectors from the current selection. |
|||
* @return {Array<Selector>} |
|||
* @private |
|||
*/ |
|||
__getCommon() { |
|||
return this.__getCommonSelectors(this.em.getSelectedAll()); |
|||
}, |
|||
|
|||
__getCommonSelectors(components, opts = {}) { |
|||
const selectors = components.map(cmp => cmp.getSelectors && cmp.getSelectors().getValid(opts)).filter(Boolean); |
|||
return this.__common(...selectors); |
|||
}, |
|||
|
|||
__common(...args) { |
|||
if (!args.length) return []; |
|||
if (args.length === 1) return args[0]; |
|||
if (args.length === 2) return args[0].filter(item => args[1].indexOf(item) >= 0); |
|||
|
|||
return args.slice(1).reduce((acc, item) => this.__common(acc, item), args[0]); |
|||
}, |
|||
}; |
|||
}; |
|||
@ -0,0 +1,512 @@ |
|||
/** |
|||
* Selectors in GrapesJS are used in CSS Composer inside Rules and in Components as classes. To illustrate this concept let's take |
|||
* a look at this code: |
|||
* |
|||
* ```css
|
|||
* span > #send-btn.btn{ |
|||
* ... |
|||
* } |
|||
* ``` |
|||
* ```html
|
|||
* <span> |
|||
* <button id="send-btn" class="btn"></button> |
|||
* </span> |
|||
* ``` |
|||
* |
|||
* In this scenario we get: |
|||
* * span -> selector of type `tag` |
|||
* * send-btn -> selector of type `id` |
|||
* * btn -> selector of type `class` |
|||
* |
|||
* So, for example, being `btn` the same class entity it'll be easier to refactor and track things. |
|||
* |
|||
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/selector_manager/config/config.js)
|
|||
* ```js
|
|||
* const editor = grapesjs.init({ |
|||
* selectorManager: { |
|||
* // options
|
|||
* } |
|||
* }) |
|||
* ``` |
|||
* |
|||
* Once the editor is instantiated you can use its API and listen to its events. Before using these methods, you should get the module from the instance. |
|||
* |
|||
* ```js
|
|||
* // Listen to events
|
|||
* editor.on('selector:add', (selector) => { ... }); |
|||
* |
|||
* // Use the API
|
|||
* const sm = editor.Selectors; |
|||
* sm.add(...); |
|||
* ``` |
|||
* |
|||
* ## Available Events |
|||
* * `selector:add` - Selector added. The [Selector] is passed as an argument to the callback. |
|||
* * `selector:remove` - Selector removed. The [Selector] is passed as an argument to the callback. |
|||
* * `selector:update` - Selector updated. The [Selector] and the object containing changes are passed as arguments to the callback. |
|||
* * `selector:state` - States changed. An object containing all the available data about the triggered event is passed as an argument to the callback. |
|||
* * `selector` - Catch-all event for all the events mentioned above. An object containing all the available data about the triggered event is passed as an argument to the callback. |
|||
* |
|||
* ## Methods |
|||
* * [getConfig](#getconfig) |
|||
* * [add](#add) |
|||
* * [get](#get) |
|||
* * [remove](#remove) |
|||
* * [getAll](#getall) |
|||
* * [setState](#setstate) |
|||
* * [getState](#getstate) |
|||
* * [getStates](#getstates) |
|||
* * [setStates](#setstates) |
|||
* * [getSelected](#getselected) |
|||
* * [addSelected](#addselected) |
|||
* * [removeSelected](#removeselected) |
|||
* * [getSelectedTargets](#getselectedtargets) |
|||
* * [setComponentFirst](#setcomponentfirst) |
|||
* * [getComponentFirst](#getcomponentfirst) |
|||
* |
|||
* [Selector]: selector.html |
|||
* [State]: state.html |
|||
* [Component]: component.html |
|||
* [CssRule]: css_rule.html |
|||
* |
|||
* @module SelectorManager |
|||
*/ |
|||
|
|||
import { isString, debounce, isObject, isArray } from 'underscore'; |
|||
import { isComponent, isRule } from '../utils/mixins'; |
|||
import Module from '../abstract/moduleLegacy'; |
|||
import { Model, Collection } from '../common'; |
|||
import defaults from './config/config'; |
|||
import Selector from './model/Selector'; |
|||
import Selectors from './model/Selectors'; |
|||
import State from './model/State'; |
|||
import ClassTagsView from './view/ClassTagsView'; |
|||
import EditorModel from '../editor/model/Editor'; |
|||
import Component from '../dom_components/model/Component'; |
|||
|
|||
const isId = (str: string) => isString(str) && str[0] == '#'; |
|||
const isClass = (str: string) => isString(str) && str[0] == '.'; |
|||
|
|||
export const evAll = 'selector'; |
|||
export const evPfx = `${evAll}:`; |
|||
export const evAdd = `${evPfx}add`; |
|||
export const evUpdate = `${evPfx}update`; |
|||
export const evRemove = `${evPfx}remove`; |
|||
export const evRemoveBefore = `${evRemove}:before`; |
|||
export const evCustom = `${evPfx}custom`; |
|||
export const evState = `${evPfx}state`; |
|||
|
|||
export default class SelectorManager extends Module { |
|||
name = 'SelectorManager'; |
|||
|
|||
Selector = Selector; |
|||
|
|||
Selectors = Selectors; |
|||
|
|||
model!: Model; |
|||
states!: Collection<State>; |
|||
selectorTags?: ClassTagsView; |
|||
selected!: Selectors; |
|||
em!: EditorModel; |
|||
|
|||
events = { |
|||
all: evAll, |
|||
update: evUpdate, |
|||
add: evAdd, |
|||
remove: evRemove, |
|||
removeBefore: evRemoveBefore, |
|||
state: evState, |
|||
custom: evCustom, |
|||
}; |
|||
|
|||
/** |
|||
* Get configuration object |
|||
* @name getConfig |
|||
* @function |
|||
* @return {Object} |
|||
*/ |
|||
|
|||
init(conf = {}) { |
|||
//super();
|
|||
this.__initConfig(defaults, conf); |
|||
const config = this.getConfig(); |
|||
const em = this.em; |
|||
const ppfx = config.pStylePrefix; |
|||
|
|||
if (ppfx) { |
|||
config.stylePrefix = ppfx + config.stylePrefix; |
|||
} |
|||
|
|||
// Global selectors container
|
|||
this.all = new Selectors(config.selectors); |
|||
this.selected = new Selectors([], { em, config }); |
|||
this.states = new Collection<State>( |
|||
config.states.map(state => new State(state)), |
|||
{ model: State } |
|||
); |
|||
this.model = new Model({ cFirst: config.componentFirst, _undo: true }); |
|||
this.__initListen({ |
|||
collections: [this.states, this.selected], |
|||
propagate: [{ entity: this.states, event: this.events.state }], |
|||
}); |
|||
em.on('change:state', (m, value) => em.trigger(evState, value)); |
|||
this.model.on('change:cFirst', (m, value) => em.trigger('selector:type', value)); |
|||
const listenTo = |
|||
'component:toggled component:update:classes change:device styleManager:update selector:state selector:type'; |
|||
this.model.listenTo(em, listenTo, () => this.__update()); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
__update = debounce(() => { |
|||
this.__trgCustom(); |
|||
}, 0); |
|||
|
|||
__trgCustom(opts?: any) { |
|||
this.em.trigger(this.events.custom, this.__customData(opts)); |
|||
} |
|||
|
|||
__customData(opts: any = {}) { |
|||
const { container } = opts; |
|||
return { |
|||
states: this.getStates(), |
|||
selected: this.getSelected(), |
|||
container, |
|||
}; |
|||
} |
|||
|
|||
// postLoad() {
|
|||
// this.__postLoad();
|
|||
// const { em, model } = this;
|
|||
// const um = em.get('UndoManager');
|
|||
// um && um.add(model);
|
|||
// um && um.add(this.pages);
|
|||
// },
|
|||
|
|||
postRender() { |
|||
this.__appendTo(); |
|||
this.__trgCustom(); |
|||
} |
|||
|
|||
select(value: any, opts = {}) { |
|||
const targets = Array.isArray(value) ? value : [value]; |
|||
const toSelect: any[] = this.em.get('StyleManager').select(targets, opts); |
|||
const selTags = this.selectorTags; |
|||
const res = toSelect |
|||
.filter(i => i) |
|||
.map(sel => (isComponent(sel) ? sel : isRule(sel) && !sel.get('selectorsAdd') ? sel : sel.getSelectorsString())); |
|||
selTags && selTags.componentChanged({ targets: res }); |
|||
return this; |
|||
} |
|||
|
|||
addSelector(name: string | { name?: string; label?: string }, opts = {}, cOpts = {}): Selector { |
|||
let props: any = { ...opts }; |
|||
|
|||
if (isObject(name)) { |
|||
props = name; |
|||
} else { |
|||
props.name = name; |
|||
} |
|||
|
|||
if (isId(props.name)) { |
|||
props.name = props.name.substr(1); |
|||
props.type = Selector.TYPE_ID; |
|||
} else if (isClass(props.name)) { |
|||
props.name = props.name.substr(1); |
|||
} |
|||
|
|||
if (props.label && !props.name) { |
|||
props.name = this.escapeName(props.label); |
|||
} |
|||
|
|||
const cname = props.name; |
|||
const config = this.getConfig(); |
|||
const all = this.getAll(); |
|||
const em = this.em; |
|||
const selector = cname ? this.get(cname, props.type) : all.where(props)[0]; |
|||
|
|||
if (!selector) { |
|||
return all.add(new Selector(props, { ...cOpts, config, em })); |
|||
} |
|||
|
|||
return selector; |
|||
} |
|||
|
|||
getSelector(name: string, type = Selector.TYPE_CLASS) { |
|||
if (isId(name)) { |
|||
name = name.substr(1); |
|||
type = Selector.TYPE_ID; |
|||
} else if (isClass(name)) { |
|||
name = name.substr(1); |
|||
} |
|||
|
|||
return this.getAll().where({ name, type })[0]; |
|||
} |
|||
|
|||
/** |
|||
* Add a new selector to the collection if it does not already exist. |
|||
* You can pass selectors properties or string identifiers. |
|||
* @param {Object|String} props Selector properties or string identifiers, eg. `{ name: 'my-class', label: 'My class' }`, `.my-cls` |
|||
* @param {Object} [opts] Selector options |
|||
* @return {[Selector]} |
|||
* @example |
|||
* const selector = selectorManager.add({ name: 'my-class', label: 'My class' }); |
|||
* console.log(selector.toString()) // `.my-class`
|
|||
* // Same as
|
|||
* const selector = selectorManager.add('.my-class'); |
|||
* console.log(selector.toString()) // `.my-class`
|
|||
* */ |
|||
add(props: string | { name?: string; label?: string }, opts = {}) { |
|||
const cOpts = isString(props) ? {} : opts; |
|||
// Keep support for arrays but avoid it in docs
|
|||
if (isArray(props)) { |
|||
return props.map(item => this.addSelector(item, opts, cOpts)); |
|||
} else { |
|||
return this.addSelector(props, opts, cOpts); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Add class selectors |
|||
* @param {Array|string} classes Array or string of classes |
|||
* @return {Array} Array of added selectors |
|||
* @private |
|||
* @example |
|||
* sm.addClass('class1'); |
|||
* sm.addClass('class1 class2'); |
|||
* sm.addClass(['class1', 'class2']); |
|||
* // -> [SelectorObject, ...]
|
|||
*/ |
|||
addClass(classes: string | string[]) { |
|||
const added: any = []; |
|||
|
|||
if (isString(classes)) { |
|||
classes = classes.trim().split(' '); |
|||
} |
|||
|
|||
classes.forEach(name => added.push(this.addSelector(name))); |
|||
return added; |
|||
} |
|||
|
|||
/** |
|||
* Get the selector by its name/type |
|||
* @param {String} name Selector name or string identifier |
|||
* @returns {[Selector]|null} |
|||
* @example |
|||
* const selector = selectorManager.get('.my-class'); |
|||
* // Get Id
|
|||
* const selectorId = selectorManager.get('#my-id'); |
|||
* */ |
|||
get(name: string | string[], type?: number) { |
|||
// Keep support for arrays but avoid it in docs
|
|||
if (isArray(name)) { |
|||
const result: Selector[] = []; |
|||
const selectors = name.map(item => this.getSelector(item)).filter(item => item); |
|||
selectors.forEach(item => result.indexOf(item) < 0 && result.push(item)); |
|||
return result; |
|||
} else { |
|||
return this.getSelector(name, type) || null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Remove Selector. |
|||
* @param {String|[Selector]} selector Selector instance or Selector string identifier |
|||
* @returns {[Selector]} Removed Selector |
|||
* @example |
|||
* const removed = selectorManager.remove('.myclass'); |
|||
* // or by passing the Selector
|
|||
* selectorManager.remove(selectorManager.get('.myclass')); |
|||
*/ |
|||
remove(selector: string | Selector, opts?: any) { |
|||
return this.__remove(selector, opts); |
|||
} |
|||
|
|||
/** |
|||
* Change the selector state |
|||
* @param {String} value State value |
|||
* @returns {this} |
|||
* @example |
|||
* selectorManager.setState('hover'); |
|||
*/ |
|||
setState(value: string) { |
|||
this.em.setState(value); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Get the current selector state value |
|||
* @returns {String} |
|||
*/ |
|||
getState() { |
|||
return this.em.getState(); |
|||
} |
|||
|
|||
/** |
|||
* Get states |
|||
* @returns {Array<[State]>} |
|||
*/ |
|||
getStates() { |
|||
return [...this.states.models]; |
|||
} |
|||
|
|||
/** |
|||
* Set a new collection of states |
|||
* @param {Array<Object>} states Array of new states |
|||
* @returns {Array<[State]>} |
|||
* @example |
|||
* const states = selectorManager.setStates([ |
|||
* { name: 'hover', label: 'Hover' }, |
|||
* { name: 'nth-of-type(2n)', label: 'Even/Odd' } |
|||
* ]); |
|||
*/ |
|||
setStates(states: State[], opts?: any) { |
|||
return this.states.reset( |
|||
states.map(state => new State(state)), |
|||
opts |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Get commonly selected selectors, based on all selected components. |
|||
* @returns {Array<[Selector]>} |
|||
* @example |
|||
* const selected = selectorManager.getSelected(); |
|||
* console.log(selected.map(s => s.toString())) |
|||
*/ |
|||
getSelected() { |
|||
return this.__getCommon(); |
|||
} |
|||
|
|||
/** |
|||
* Add new selector to all selected components. |
|||
* @param {Object|String} props Selector properties or string identifiers, eg. `{ name: 'my-class', label: 'My class' }`, `.my-cls` |
|||
* @example |
|||
* selectorManager.addSelected('.new-class'); |
|||
*/ |
|||
addSelected(props: string | { name?: string; label?: string }) { |
|||
const added = this.add(props); |
|||
// TODO: target should be the one from StyleManager
|
|||
this.em.getSelectedAll().forEach(target => { |
|||
target.getSelectors().add(added); |
|||
}); |
|||
// TODO: update selected collection
|
|||
} |
|||
|
|||
/** |
|||
* Remove a common selector from all selected components. |
|||
* @param {String|[Selector]} selector Selector instance or Selector string identifier |
|||
* @example |
|||
* selectorManager.removeSelected('.myclass'); |
|||
*/ |
|||
removeSelected(selector: any) { |
|||
this.em.getSelectedAll().forEach(trg => { |
|||
!selector.get('protected') && trg && trg.getSelectors().remove(selector); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Get the array of currently selected targets. |
|||
* @returns {Array<[Component]|[CssRule]>} |
|||
* @example |
|||
* const targetsToStyle = selectorManager.getSelectedTargets(); |
|||
* console.log(targetsToStyle.map(target => target.getSelectorsString())) |
|||
*/ |
|||
getSelectedTargets() { |
|||
return this.em.get('StyleManager').getSelectedAll(); |
|||
} |
|||
|
|||
/** |
|||
* Update component-first option. |
|||
* If the component-first is enabled, all the style changes will be applied on selected components (ID rules) instead |
|||
* of selectors (which would change styles on all components with those classes). |
|||
* @param {Boolean} value |
|||
*/ |
|||
setComponentFirst(value: boolean) { |
|||
this.getConfig().componentFirst = value; |
|||
this.model.set({ cFirst: value }); |
|||
} |
|||
|
|||
/** |
|||
* Get the value of component-first option. |
|||
* @return {Boolean} |
|||
*/ |
|||
getComponentFirst() { |
|||
return this.getConfig().componentFirst; |
|||
} |
|||
|
|||
/** |
|||
* Get all selectors |
|||
* @name getAll |
|||
* @function |
|||
* @return {Collection<[Selector]>} |
|||
* */ |
|||
|
|||
/** |
|||
* Return escaped selector name |
|||
* @param {String} name Selector name to escape |
|||
* @returns {String} Escaped name |
|||
* @private |
|||
*/ |
|||
escapeName(name: string) { |
|||
const { escapeName } = this.getConfig(); |
|||
return escapeName ? escapeName(name) : Selector.escapeName(name); |
|||
} |
|||
|
|||
/** |
|||
* Render class selectors. If an array of selectors is provided a new instance of the collection will be rendered |
|||
* @param {Array<Object>} selectors |
|||
* @return {HTMLElement} |
|||
* @private |
|||
*/ |
|||
render(selectors: any[]) { |
|||
const { em, selectorTags } = this; |
|||
const config = this.getConfig(); |
|||
const el = selectorTags && selectorTags.el; |
|||
this.selected.reset(selectors); |
|||
this.selectorTags = new ClassTagsView({ |
|||
el, |
|||
collection: this.selected, |
|||
//@ts-ignore
|
|||
module: this, |
|||
config, |
|||
}); |
|||
|
|||
return this.selectorTags.render().el; |
|||
} |
|||
|
|||
destroy() { |
|||
const { selectorTags, model } = this; |
|||
model.stopListening(); |
|||
this.__destroy(); |
|||
selectorTags?.remove(); |
|||
this.selectorTags = undefined; |
|||
} |
|||
|
|||
/** |
|||
* Get common selectors from the current selection. |
|||
* @return {Array<Selector>} |
|||
* @private |
|||
*/ |
|||
__getCommon() { |
|||
return this.__getCommonSelectors(this.em.getSelectedAll()); |
|||
} |
|||
|
|||
__getCommonSelectors(components: Component[], opts = {}) { |
|||
const selectors = components.map(cmp => cmp.getSelectors && cmp.getSelectors().getValid(opts)).filter(Boolean); |
|||
return this.__common(...selectors); |
|||
} |
|||
|
|||
__common(...args: any): Selector[] { |
|||
if (!args.length) return []; |
|||
if (args.length === 1) return args[0]; |
|||
if (args.length === 2) return args[0].filter((item: any) => args[1].indexOf(item) >= 0); |
|||
|
|||
return ( |
|||
args |
|||
.slice(1) |
|||
//@ts-ignore
|
|||
.reduce((acc, item) => this.__common(acc, item), args[0]) |
|||
); |
|||
} |
|||
} |
|||
@ -1,51 +0,0 @@ |
|||
import { filter } from 'underscore'; |
|||
import { Collection } from '../../common'; |
|||
import Selector from './Selector'; |
|||
|
|||
const combine = (tail, curr) => { |
|||
return tail.reduce( |
|||
(acc, item, n) => { |
|||
return acc.concat(combine(tail.slice(n + 1), `${curr}${item}`)); |
|||
}, |
|||
[curr] |
|||
); |
|||
}; |
|||
|
|||
export default class Selectors extends Collection { |
|||
modelId(attr) { |
|||
return `${attr.name}_${attr.type || Selector.TYPE_CLASS}`; |
|||
} |
|||
|
|||
getStyleable() { |
|||
return filter(this.models, item => item.get('active') && !item.get('private')); |
|||
} |
|||
|
|||
getValid({ noDisabled } = {}) { |
|||
return filter(this.models, item => !item.get('private')).filter(item => (noDisabled ? item.get('active') : 1)); |
|||
} |
|||
|
|||
getFullString(collection, opts = {}) { |
|||
const result = []; |
|||
const coll = collection || this; |
|||
coll.forEach(selector => result.push(selector.getFullName(opts))); |
|||
return result.join('').trim(); |
|||
} |
|||
|
|||
getFullName(opts = {}) { |
|||
const { combination, array } = opts; |
|||
let result = []; |
|||
const sels = this.map(s => s.getFullName(opts)).sort(); |
|||
|
|||
if (combination) { |
|||
sels.forEach((sel, n) => { |
|||
result = result.concat(combine(sels.slice(n + 1), sel)); |
|||
}); |
|||
} else { |
|||
result = sels; |
|||
} |
|||
|
|||
return array ? result : combination ? result.join(',') : result.join(''); |
|||
} |
|||
} |
|||
|
|||
Selectors.prototype.model = Selector; |
|||
@ -0,0 +1,56 @@ |
|||
import { filter } from "underscore"; |
|||
import { Collection } from "../../common"; |
|||
import Selector from "./Selector"; |
|||
|
|||
const combine = (tail: string[], curr: string): string[] => { |
|||
return tail.reduce( |
|||
(acc, item, n) => { |
|||
return acc.concat(combine(tail.slice(n + 1), `${curr}${item}`)); |
|||
}, |
|||
[curr] |
|||
); |
|||
}; |
|||
|
|||
export default class Selectors extends Collection<Selector> { |
|||
modelId(attr: any) { |
|||
return `${attr.name}_${attr.type || Selector.TYPE_CLASS}`; |
|||
} |
|||
|
|||
getStyleable() { |
|||
return filter( |
|||
this.models, |
|||
(item) => item.get("active") && !item.get("private") |
|||
); |
|||
} |
|||
|
|||
getValid({ noDisabled }: any = {}) { |
|||
return filter(this.models, (item) => !item.get("private")).filter((item) => |
|||
noDisabled ? item.get("active") : 1 |
|||
); |
|||
} |
|||
|
|||
getFullString(collection: Selector[], opts = {}) { |
|||
const result: string[] = []; |
|||
const coll = collection || this; |
|||
coll.forEach((selector) => result.push(selector.getFullName(opts))); |
|||
return result.join("").trim(); |
|||
} |
|||
|
|||
getFullName(opts: any = {}) { |
|||
const { combination, array } = opts; |
|||
let result: string[] = []; |
|||
const sels = this.map((s) => s.getFullName(opts)).sort(); |
|||
|
|||
if (combination) { |
|||
sels.forEach((sel, n) => { |
|||
result = result.concat(combine(sels.slice(n + 1), sel)); |
|||
}); |
|||
} else { |
|||
result = sels; |
|||
} |
|||
|
|||
return array ? result : combination ? result.join(",") : result.join(""); |
|||
} |
|||
} |
|||
|
|||
Selectors.prototype.model = Selector; |
|||
Loading…
Reference in new issue