').append(``);\n this.$cn.append(this.$header);\n this.$cn2.append(`
${em.t('traitManager.label')}
`);\n this.$cn2.append(tm.render());\n var panels = editor.Panels;\n\n if (!panels.getPanel('views-container')) panelC = panels.addPanel({ id: 'views-container' });\n else panelC = panels.getPanel('views-container');\n\n panelC.set('appendContent', this.$cn.get(0)).trigger('change:appendContent');\n\n this.target = editor.getModel();\n this.listenTo(this.target, 'component:toggled', this.toggleTm);\n }\n\n this.toggleTm();\n },\n\n /**\n * Toggle Trait Manager visibility\n * @private\n */\n toggleTm() {\n const sender = this.sender;\n if (sender && sender.get && !sender.get('active')) return;\n\n if (this.target.getSelectedAll().length === 1) {\n this.$cn2.show();\n this.$header.hide();\n } else {\n this.$cn2.hide();\n this.$header.show();\n }\n },\n\n stop() {\n this.$cn2 && this.$cn2.hide();\n this.$header && this.$header.hide();\n },\n};\n","import { isArray, contains } from 'underscore';\n\nexport default {\n run(ed, s, opts = {}) {\n const em = ed.getModel();\n const clp = em.get('clipboard');\n const lastSelected = ed.getSelected();\n\n if (clp && lastSelected) {\n ed.getSelectedAll().forEach(selected => {\n const { collection } = selected;\n if (!collection) return;\n\n let added;\n const at = selected.index() + 1;\n const addOpts = { at, action: opts.action || 'paste-component' };\n\n if (contains(clp, selected) && selected.get('copyable')) {\n added = collection.add(selected.clone(), addOpts);\n } else {\n const copyable = clp.filter(cop => cop.get('copyable'));\n const pasteable = copyable.filter(cop => ed.Components.canMove(selected.parent(), cop).result);\n added = collection.add(\n pasteable.map(cop => cop.clone()),\n addOpts\n );\n }\n\n added = isArray(added) ? added : [added];\n added.forEach(add => ed.trigger('component:paste', add));\n });\n\n lastSelected.emitUpdate();\n }\n },\n};\n","import { each } from 'underscore';\n\nconst cmdVis = 'sw-visibility';\n\nexport default {\n getPanels(editor) {\n if (!this.panels) {\n this.panels = editor.Panels.getPanels();\n }\n\n return this.panels;\n },\n\n preventDrag(opts) {\n opts.abort = 1;\n },\n\n tglEffects(on) {\n const { em } = this;\n const mthEv = on ? 'on' : 'off';\n if (em) {\n const canvas = em.get('Canvas');\n const body = canvas.getBody();\n const tlb = canvas.getToolbarEl();\n tlb && (tlb.style.display = on ? 'none' : '');\n const elP = body.querySelectorAll(`.${this.ppfx}no-pointer`);\n each(elP, item => (item.style.pointerEvents = on ? 'all' : ''));\n em[mthEv]('run:tlb-move:before', this.preventDrag);\n }\n },\n\n run(editor, sender) {\n this.sender = sender;\n this.selected = [...editor.getSelectedAll()];\n editor.select();\n\n if (!this.shouldRunSwVisibility) {\n this.shouldRunSwVisibility = editor.Commands.isActive(cmdVis);\n }\n\n this.shouldRunSwVisibility && editor.stopCommand(cmdVis);\n editor.getModel().stopDefault();\n\n const panels = this.getPanels(editor);\n const canvas = editor.Canvas.getElement();\n const editorEl = editor.getEl();\n const pfx = editor.Config.stylePrefix;\n\n if (!this.helper) {\n const helper = document.createElement('span');\n helper.className = `${pfx}off-prv fa fa-eye-slash`;\n editorEl.appendChild(helper);\n helper.onclick = () => this.stopCommand();\n this.helper = helper;\n }\n\n this.helper.style.display = 'inline-block';\n\n panels.forEach(panel => panel.set('visible', false));\n\n const canvasS = canvas.style;\n canvasS.width = '100%';\n canvasS.height = '100%';\n canvasS.top = '0';\n canvasS.left = '0';\n canvasS.padding = '0';\n canvasS.margin = '0';\n editor.refresh();\n this.tglEffects(1);\n },\n\n stop(editor) {\n const { sender = {}, selected } = this;\n sender.set && sender.set('active', 0);\n const panels = this.getPanels(editor);\n\n if (this.shouldRunSwVisibility) {\n editor.runCommand(cmdVis);\n this.shouldRunSwVisibility = false;\n }\n\n editor.getModel().runDefault();\n panels.forEach(panel => panel.set('visible', true));\n\n const canvas = editor.Canvas.getElement();\n canvas.setAttribute('style', '');\n selected && editor.select(selected);\n delete this.selected;\n\n if (this.helper) {\n this.helper.style.display = 'none';\n }\n\n editor.refresh();\n this.tglEffects();\n },\n};\n","export default {\n run(editor, sender, opts) {\n const opt = opts || {};\n const canvas = editor.Canvas;\n const canvasView = canvas.getCanvasView();\n const options = {\n appendTo: canvas.getResizerEl(),\n prefix: editor.getConfig().stylePrefix,\n posFetcher: canvasView.getElementPos.bind(canvasView),\n mousePosFetcher: canvas.getMouseRelativePos,\n ...(opt.options || {}),\n };\n let { canvasResizer } = this;\n\n // Create the resizer for the canvas if not yet created\n if (!canvasResizer || opt.forceNew) {\n this.canvasResizer = editor.Utils.Resizer.init(options);\n canvasResizer = this.canvasResizer;\n }\n\n canvasResizer.setOptions(options);\n canvasResizer.blur();\n canvasResizer.focus(opt.el);\n return canvasResizer;\n },\n\n stop() {\n this.canvasResizer?.blur();\n },\n};\n","import Backbone from 'backbone';\n\nexport default class ToolbarButtonView extends Backbone.View {\n events() {\n return (\n this.model.get('events') || {\n mousedown: 'handleClick',\n }\n );\n }\n\n attributes() {\n return this.model.get('attributes');\n }\n\n initialize(opts = {}) {\n const { config = {} } = opts;\n this.em = config.em;\n this.editor = config.editor;\n }\n\n handleClick(event) {\n event.preventDefault();\n event.stopPropagation();\n\n /*\n * Since the toolbar lives outside the canvas frame, the event's\n * generated on it have clientX and clientY relative to the page.\n *\n * This causes issues during events like dragging, where they depend\n * on the clientX and clientY.\n *\n * This makes sure the offsets are calculated.\n *\n * More information on\n * https://github.com/artf/grapesjs/issues/2372\n * https://github.com/artf/grapesjs/issues/2207\n */\n\n const { editor, em } = this;\n const { left, top } = editor.Canvas.getFrameEl().getBoundingClientRect();\n\n const calibrated = {\n ...event,\n clientX: event.clientX - left,\n clientY: event.clientY - top,\n };\n\n em.trigger('toolbar:run:before');\n this.execCommand(calibrated);\n }\n\n execCommand(event) {\n const opts = { event };\n const command = this.model.get('command');\n const editor = this.editor;\n\n if (typeof command === 'function') {\n command(editor, null, opts);\n }\n\n if (typeof command === 'string') {\n editor.runCommand(command, opts);\n }\n }\n\n render() {\n const { editor, $el, model } = this;\n const id = model.get('id');\n const label = model.get('label');\n const pfx = editor.getConfig().stylePrefix;\n $el.addClass(`${pfx}toolbar-item`);\n id && $el.addClass(`${pfx}toolbar-item__${id}`);\n label && $el.append(label);\n return this;\n }\n}\n","import DomainViews from 'domain_abstract/view/DomainViews';\nimport ToolbarButtonView from './ToolbarButtonView';\n\nexport default class ToolbarView extends DomainViews {\n constructor(opts = {}, config) {\n super(opts, config);\n this.config = { editor: opts.editor || '', em: opts.em };\n this.listenTo(this.collection, 'reset', this.render);\n }\n}\n\nToolbarView.prototype.itemView = ToolbarButtonView;\n","import Backbone from 'backbone';\n\nexport default class ToolbarButton extends Backbone.Model {\n defaults() {\n return {\n command: '',\n attributes: {},\n };\n }\n}\n","import Backbone from 'backbone';\nimport ToolbarButton from './ToolbarButton';\n\nexport default Backbone.Collection.extend({ model: ToolbarButton });\n","import Backbone from 'backbone';\nimport { bindAll, isElement, debounce } from 'underscore';\nimport { on, off, getUnitFromValue, isTaggableNode, getViewEl, hasWin } from '../../utils/mixins';\nimport { isVisible, isDoc } from '../../utils/dom';\nimport ToolbarView from '../../dom_components/view/ToolbarView';\nimport Toolbar from '../../dom_components/model/Toolbar';\n\nconst $ = Backbone.$;\nlet showOffsets;\n/**\n * This command is responsible for show selecting components and displaying\n * all the necessary tools around (component toolbar, badge, highlight box, etc.)\n *\n * The command manages different boxes to display tools and when something in\n * the canvas is updated, the command triggers the appropriate method to update\n * their position (across multiple frames/components):\n * - Global Tools (updateToolsGlobal/updateGlobalPos)\n * This box contains tools intended to be displayed only on ONE component per time,\n * like Component Toolbar (updated by updateToolbar/updateToolbarPos), this means\n * you won't be able to see more than one Component Toolbar (even with multiple\n * frames or multiple selected components)\n * - Local Tools (updateToolsLocal/updateLocalPos)\n * Each frame in the canvas has its own local box, so we're able to see more than\n * one active container at the same time. When you put a mouse over an element\n * you can see stuff like the highlight box, badge, margins/paddings offsets, etc.\n * so those elements are inside the Local Tools box\n *\n *\n */\nexport default {\n init(o) {\n bindAll(this, 'onHover', 'onOut', 'onClick', 'onFrameScroll', 'onFrameUpdated', 'onContainerChange');\n },\n\n enable() {\n this.frameOff = this.canvasOff = this.adjScroll = null;\n this.startSelectComponent();\n showOffsets = 1;\n },\n\n /**\n * Start select component event\n * @private\n * */\n startSelectComponent() {\n this.toggleSelectComponent(1);\n this.em.getSelected() && this.onSelect();\n },\n\n /**\n * Stop select component event\n * @private\n * */\n stopSelectComponent() {\n this.toggleSelectComponent();\n },\n\n /**\n * Toggle select component event\n * @private\n * */\n toggleSelectComponent(enable) {\n const { em } = this;\n const listenToEl = em.getConfig().listenToEl;\n const { parentNode } = em.getContainer();\n const method = enable ? 'on' : 'off';\n const methods = { on, off };\n !listenToEl.length && parentNode && listenToEl.push(parentNode);\n const trigger = (win, body) => {\n methods[method](body, 'mouseover', this.onHover);\n methods[method](body, 'mouseleave', this.onOut);\n methods[method](body, 'click touchend', this.onClick);\n methods[method](win, 'scroll', this.onFrameScroll, true);\n };\n methods[method](window, 'resize', this.onFrameUpdated);\n methods[method](listenToEl, 'scroll', this.onContainerChange);\n em[method]('component:toggled component:update undo redo', this.onSelect, this);\n em[method]('change:componentHovered', this.onHovered, this);\n em[method](\n 'component:resize styleable:change component:input', // component:styleUpdate\n this.updateGlobalPos,\n this\n );\n em[method]('component:update:toolbar', this._upToolbar, this);\n em[method]('change:canvasOffset', this.updateAttached, this);\n em[method]('frame:updated', this.onFrameUpdated, this);\n em[method]('canvas:updateTools', this.onFrameUpdated, this);\n em.get('Canvas')\n .getFrames()\n .forEach(frame => {\n const { view } = frame;\n const win = view?.getWindow();\n win && trigger(win, view.getBody());\n });\n },\n\n /**\n * Hover command\n * @param {Object} e\n * @private\n */\n onHover(e) {\n e.stopPropagation();\n const { em } = this;\n const trg = e.target;\n const view = getViewEl(trg);\n const frameView = view && view._getFrame();\n const $el = $(trg);\n let model = $el.data('model');\n\n // Get first valid model\n if (!model) {\n let parent = $el.parent();\n while (!model && parent.length && !isDoc(parent[0])) {\n model = parent.data('model');\n parent = parent.parent();\n }\n }\n\n this.currentDoc = trg.ownerDocument;\n em.setHovered(model, { useValid: true });\n frameView && em.set('currentFrame', frameView);\n },\n\n onFrameUpdated() {\n this.updateLocalPos();\n this.updateGlobalPos();\n },\n\n onHovered(em, component) {\n let result = {};\n\n if (component) {\n component.views.forEach(view => {\n const el = view.el;\n const pos = this.getElementPos(el);\n result = { el, pos, component, view: getViewEl(el) };\n this.updateToolsLocal(result);\n\n if (el.ownerDocument === this.currentDoc) this.elHovered = result;\n });\n } else {\n this.currentDoc = null;\n this.elHovered = 0;\n this.updateToolsLocal();\n this.canvas.getFrames().forEach(frame => {\n const { view } = frame;\n const el = view && view.getToolsEl();\n el && this.toggleToolsEl(0, 0, { el });\n });\n }\n },\n\n /**\n * Say what to do after the component was selected\n * @param {Object} e\n * @param {Object} el\n * @private\n * */\n onSelect: debounce(function () {\n const { em } = this;\n const component = em.getSelected();\n const currentFrame = em.get('currentFrame') || {};\n const view = component && component.getView(currentFrame.model);\n let el = view && view.el;\n let result = {};\n\n if (el && isVisible(el)) {\n const pos = this.getElementPos(el);\n result = { el, pos, component, view: getViewEl(el) };\n }\n\n this.elSelected = result;\n this.updateToolsGlobal();\n // This will hide some elements from the select component\n this.updateLocalPos(result);\n this.initResize(component);\n }),\n\n updateGlobalPos() {\n const sel = this.getElSelected();\n if (!sel.el) return;\n sel.pos = this.getElementPos(sel.el);\n this.updateToolsGlobal();\n },\n\n updateLocalPos(data) {\n const sel = this.getElHovered();\n if (!sel.el) return;\n sel.pos = this.getElementPos(sel.el);\n this.updateToolsLocal(data);\n },\n\n getElHovered() {\n return this.elHovered || {};\n },\n\n getElSelected() {\n return this.elSelected || {};\n },\n\n onOut() {\n this.em.setHovered(0);\n },\n\n toggleToolsEl(on, view, opts = {}) {\n const el = opts.el || this.canvas.getToolsEl(view);\n el && (el.style.display = on ? '' : 'none');\n return el || {};\n },\n\n /**\n * Show element offset viewer\n * @param {HTMLElement} el\n * @param {Object} pos\n */\n showElementOffset(el, pos, opts = {}) {\n if (!showOffsets) return;\n this.editor.runCommand('show-offset', {\n el,\n elPos: pos,\n view: opts.view,\n force: 1,\n top: 0,\n left: 0,\n });\n },\n\n /**\n * Hide element offset viewer\n * @param {HTMLElement} el\n * @param {Object} pos\n */\n hideElementOffset(view) {\n this.editor.stopCommand('show-offset', {\n view,\n });\n },\n\n /**\n * Show fixed element offset viewer\n * @param {HTMLElement} el\n * @param {Object} pos\n */\n showFixedElementOffset(el, pos) {\n this.editor.runCommand('show-offset', {\n el,\n elPos: pos,\n state: 'Fixed',\n });\n },\n\n /**\n * Hide fixed element offset viewer\n * @param {HTMLElement} el\n * @param {Object} pos\n */\n hideFixedElementOffset(el, pos) {\n if (this.editor) this.editor.stopCommand('show-offset', { state: 'Fixed' });\n },\n\n /**\n * Hide Highlighter element\n */\n hideHighlighter(view) {\n this.canvas.getHighlighter(view).style.opacity = 0;\n },\n\n /**\n * On element click\n * @param {Event} e\n * @private\n */\n onClick(ev) {\n ev.stopPropagation();\n ev.preventDefault();\n const { em } = this;\n if (em.get('_cmpDrag')) return em.set('_cmpDrag');\n const $el = $(ev.target);\n let model = $el.data('model');\n\n if (!model) {\n let parent = $el.parent();\n while (!model && parent.length && !isDoc(parent[0])) {\n model = parent.data('model');\n parent = parent.parent();\n }\n }\n\n if (model) {\n // Avoid selection of inner text components during editing\n if (em.isEditing() && !model.get('textable') && model.isChildOf('text')) {\n return;\n }\n this.select(model, ev);\n }\n },\n\n /**\n * Select component\n * @param {Component} model\n * @param {Event} event\n */\n select(model, event = {}) {\n if (!model) return;\n this.editor.select(model, { event, useValid: true });\n this.initResize(model);\n },\n\n /**\n * Update badge for the component\n * @param {Object} Component\n * @param {Object} pos Position object\n * @private\n * */\n updateBadge(el, pos, opts = {}) {\n const { canvas } = this;\n const model = $(el).data('model');\n if (!model || !model.get('badgable')) return;\n const badge = this.getBadge(opts);\n\n if (!opts.posOnly) {\n const config = this.canvas.getConfig();\n const icon = model.getIcon();\n const ppfx = config.pStylePrefix || '';\n const clsBadge = `${ppfx}badge`;\n const customeLabel = config.customBadgeLabel;\n const badgeLabel = `${icon ? `
${icon}
` : ''}\n
${model.getName()}
`;\n badge.innerHTML = customeLabel ? customeLabel(model) : badgeLabel;\n }\n\n const un = 'px';\n const bStyle = badge.style;\n bStyle.display = 'block';\n\n const targetToElem = canvas.getTargetToElementFixed(el, badge, {\n pos: pos,\n });\n\n const top = targetToElem.top; //opts.topOff - badgeH < 0 ? -opts.topOff : posTop;\n const left = opts.leftOff < 0 ? -opts.leftOff : 0;\n\n bStyle.top = top + un;\n bStyle.left = left + un;\n },\n\n /**\n * Update highlighter element\n * @param {HTMLElement} el\n * @param {Object} pos Position object\n * @private\n */\n showHighlighter(view) {\n this.canvas.getHighlighter(view).style.opacity = '';\n },\n\n /**\n * Init resizer on the element if possible\n * @param {HTMLElement|Component} elem\n * @private\n */\n initResize(elem) {\n const { em, canvas } = this;\n const editor = em ? em.get('Editor') : '';\n const config = em ? em.get('Config') : '';\n const pfx = config.stylePrefix || '';\n const resizeClass = `${pfx}resizing`;\n const model = !isElement(elem) && isTaggableNode(elem) ? elem : em.getSelected();\n const resizable = model && model.get('resizable');\n let options = {};\n let modelToStyle;\n\n var toggleBodyClass = (method, e, opts) => {\n const docs = opts.docs;\n docs &&\n docs.forEach(doc => {\n const body = doc.body;\n const cls = body.className || '';\n body.className = (method == 'add' ? `${cls} ${resizeClass}` : cls.replace(resizeClass, '')).trim();\n });\n };\n\n if (editor && resizable) {\n const el = isElement(elem) ? elem : model.getEl();\n options = {\n // Here the resizer is updated with the current element height and width\n onStart(e, opts = {}) {\n const { el, config, resizer } = opts;\n const { keyHeight, keyWidth, currentUnit, keepAutoHeight, keepAutoWidth } = config;\n toggleBodyClass('add', e, opts);\n modelToStyle = em.get('StyleManager').getModelToStyle(model);\n canvas.toggleFramesEvents();\n const computedStyle = getComputedStyle(el);\n const modelStyle = modelToStyle.getStyle();\n\n let currentWidth = modelStyle[keyWidth];\n config.autoWidth = keepAutoWidth && currentWidth === 'auto';\n if (isNaN(parseFloat(currentWidth))) {\n currentWidth = computedStyle[keyWidth];\n }\n\n let currentHeight = modelStyle[keyHeight];\n config.autoHeight = keepAutoHeight && currentHeight === 'auto';\n if (isNaN(parseFloat(currentHeight))) {\n currentHeight = computedStyle[keyHeight];\n }\n\n resizer.startDim.w = parseFloat(currentWidth);\n resizer.startDim.h = parseFloat(currentHeight);\n showOffsets = 0;\n\n if (currentUnit) {\n config.unitHeight = getUnitFromValue(currentHeight);\n config.unitWidth = getUnitFromValue(currentWidth);\n }\n },\n\n // Update all positioned elements (eg. component toolbar)\n onMove() {\n editor.trigger('component:resize');\n },\n\n onEnd(e, opts) {\n toggleBodyClass('remove', e, opts);\n editor.trigger('component:resize');\n canvas.toggleFramesEvents(1);\n showOffsets = 1;\n },\n\n updateTarget(el, rect, options = {}) {\n if (!modelToStyle) {\n return;\n }\n\n const { store, selectedHandler, config } = options;\n const { keyHeight, keyWidth, autoHeight, autoWidth, unitWidth, unitHeight } = config;\n const onlyHeight = ['tc', 'bc'].indexOf(selectedHandler) >= 0;\n const onlyWidth = ['cl', 'cr'].indexOf(selectedHandler) >= 0;\n const style = {};\n const en = !store ? 1 : ''; // this will trigger the final change\n\n if (!onlyHeight) {\n const bodyw = canvas.getBody().offsetWidth;\n const width = rect.w < bodyw ? rect.w : bodyw;\n style[keyWidth] = autoWidth ? 'auto' : `${width}${unitWidth}`;\n }\n\n if (!onlyWidth) {\n style[keyHeight] = autoHeight ? 'auto' : `${rect.h}${unitHeight}`;\n }\n\n modelToStyle.addStyle({ ...style, en }, { avoidStore: !store });\n const updateEvent = 'update:component:style';\n const eventToListen = `${updateEvent}:${keyHeight} ${updateEvent}:${keyWidth}`;\n em && em.trigger(eventToListen, null, null, { noEmit: 1 });\n },\n };\n\n if (typeof resizable == 'object') {\n options = { ...options, ...resizable, parent: options };\n }\n\n this.resizer = editor.runCommand('resize', { el, options, force: 1 });\n } else {\n editor.stopCommand('resize');\n this.resizer = null;\n }\n },\n\n /**\n * Update toolbar if the component has one\n * @param {Object} mod\n */\n updateToolbar(mod) {\n const { em } = this.config;\n const model = mod == em ? em.getSelected() : mod;\n const toolbarEl = this.canvas.getToolbarEl();\n const toolbarStyle = toolbarEl.style;\n const toolbar = model.get('toolbar');\n const showToolbar = em.get('Config').showToolbar;\n\n if (model && showToolbar && toolbar && toolbar.length) {\n toolbarStyle.display = '';\n if (!this.toolbar) {\n toolbarEl.innerHTML = '';\n this.toolbar = new Toolbar(toolbar);\n const toolbarView = new ToolbarView({\n collection: this.toolbar,\n editor: this.editor,\n em,\n });\n toolbarEl.appendChild(toolbarView.render().el);\n }\n\n this.toolbar.reset(toolbar);\n toolbarStyle.top = '-100px';\n toolbarStyle.left = 0;\n } else {\n toolbarStyle.display = 'none';\n }\n },\n\n /**\n * Update toolbar positions\n * @param {HTMLElement} el\n * @param {Object} pos\n */\n updateToolbarPos(pos) {\n const unit = 'px';\n const { style } = this.canvas.getToolbarEl();\n style.top = `${pos.top}${unit}`;\n style.left = `${pos.left}${unit}`;\n style.opacity = '';\n },\n\n /**\n * Return canvas dimensions and positions\n * @return {Object}\n */\n getCanvasPosition() {\n return this.canvas.getCanvasView().getPosition();\n },\n\n /**\n * Returns badge element\n * @return {HTMLElement}\n * @private\n */\n getBadge(opts = {}) {\n return this.canvas.getBadgeEl(opts.view);\n },\n\n /**\n * On frame scroll callback\n * @private\n */\n onFrameScroll() {\n this.updateTools();\n },\n\n updateTools() {\n this.updateLocalPos();\n this.updateGlobalPos();\n },\n\n isCompSelected(comp) {\n return comp && comp.get('status') === 'selected';\n },\n\n /**\n * Update tools visible on hover\n * @param {HTMLElement} el\n * @param {Object} pos\n */\n updateToolsLocal(data) {\n const { el, pos, view, component } = data || this.getElHovered();\n\n if (!el) {\n this.lastHovered = 0;\n return;\n }\n\n const isHoverEn = component.get('hoverable');\n const isNewEl = this.lastHovered !== el;\n const badgeOpts = isNewEl ? {} : { posOnly: 1 };\n\n if (isNewEl && isHoverEn) {\n this.lastHovered = el;\n this.showHighlighter(view);\n this.showElementOffset(el, pos, { view });\n }\n\n if (this.isCompSelected(component)) {\n this.hideHighlighter(view);\n this.hideElementOffset(view);\n }\n\n const unit = 'px';\n const toolsEl = this.toggleToolsEl(1, view);\n const { style } = toolsEl;\n const frameOff = this.canvas.canvasRectOffset(el, pos);\n const topOff = frameOff.top;\n const leftOff = frameOff.left;\n\n this.updateBadge(el, pos, {\n ...badgeOpts,\n view,\n topOff,\n leftOff,\n });\n\n style.top = topOff + unit;\n style.left = leftOff + unit;\n style.width = pos.width + unit;\n style.height = pos.height + unit;\n\n this._trgToolUp('local', {\n component,\n el: toolsEl,\n top: topOff,\n left: leftOff,\n width: pos.width,\n height: pos.height,\n });\n },\n\n _upToolbar: debounce(function () {\n this.updateToolsGlobal({ force: 1 });\n }),\n\n _trgToolUp(type, opts = {}) {\n this.em.trigger('canvas:tools:update', {\n type,\n ...opts,\n });\n },\n\n updateToolsGlobal(opts = {}) {\n const { el, pos, component } = this.getElSelected();\n\n if (!el) {\n this.toggleToolsEl(); // Hides toolbar\n this.lastSelected = 0;\n return;\n }\n\n const { canvas } = this;\n const isNewEl = this.lastSelected !== el;\n\n if (isNewEl || opts.force) {\n this.lastSelected = el;\n this.updateToolbar(component);\n }\n\n const unit = 'px';\n const toolsEl = this.toggleToolsEl(1);\n const { style } = toolsEl;\n const targetToElem = canvas.getTargetToElementFixed(el, canvas.getToolbarEl(), { pos });\n const topOff = targetToElem.canvasOffsetTop;\n const leftOff = targetToElem.canvasOffsetLeft;\n style.top = topOff + unit;\n style.left = leftOff + unit;\n style.width = pos.width + unit;\n style.height = pos.height + unit;\n\n this.updateToolbarPos({ top: targetToElem.top, left: targetToElem.left });\n this._trgToolUp('global', {\n component,\n el: toolsEl,\n top: topOff,\n left: leftOff,\n width: pos.width,\n height: pos.height,\n });\n },\n\n /**\n * Update attached elements, eg. component toolbar\n */\n updateAttached: debounce(function () {\n this.updateGlobalPos();\n }),\n\n onContainerChange: debounce(function () {\n this.em.refreshCanvas();\n }, 150),\n\n /**\n * Returns element's data info\n * @param {HTMLElement} el\n * @return {Object}\n * @private\n */\n getElementPos(el) {\n return this.canvas.getCanvasView().getElementPos(el);\n },\n\n /**\n * Hide badge\n * @private\n * */\n hideBadge() {\n this.getBadge().style.display = 'none';\n },\n\n /**\n * Clean previous model from different states\n * @param {Component} model\n * @private\n */\n cleanPrevious(model) {\n model &&\n model.set({\n status: '',\n state: '',\n });\n },\n\n /**\n * Returns content window\n * @private\n */\n getContentWindow() {\n return this.canvas.getWindow();\n },\n\n run(editor) {\n if (!hasWin()) return;\n this.editor = editor && editor.get('Editor');\n this.enable();\n },\n\n stop(ed, sender, opts = {}) {\n if (!hasWin()) return;\n const { em, editor } = this;\n this.onHovered(); // force to hide toolbar\n this.stopSelectComponent();\n !opts.preserveSelected && em.setSelected(null);\n this.toggleToolsEl();\n editor && editor.stopCommand('resize');\n },\n};\n","import Backbone from 'backbone';\nconst $ = Backbone.$;\n\nexport default {\n /**\n * Start select position event\n * @param {HTMLElement} trg\n * @private\n * */\n startSelectPosition(trg, doc, opts = {}) {\n this.isPointed = false;\n var utils = this.editorModel.get('Utils');\n const container = trg.ownerDocument.body;\n\n if (utils && !this.sorter)\n this.sorter = new utils.Sorter({\n container,\n placer: this.canvas.getPlacerEl(),\n containerSel: '*',\n itemSel: '*',\n pfx: this.ppfx,\n direction: 'a',\n document: doc,\n wmargin: 1,\n nested: 1,\n em: this.editorModel,\n canvasRelative: 1,\n scale: () => this.em.getZoomDecimal(),\n });\n\n if (opts.onStart) this.sorter.onStart = opts.onStart;\n trg && this.sorter.startSort(trg, { container });\n },\n\n /**\n * Get frame position\n * @return {Object}\n * @private\n */\n getOffsetDim() {\n var frameOff = this.offset(this.canvas.getFrameEl());\n var canvasOff = this.offset(this.canvas.getElement());\n var top = frameOff.top - canvasOff.top;\n var left = frameOff.left - canvasOff.left;\n return { top, left };\n },\n\n /**\n * Stop select position event\n * @private\n * */\n stopSelectPosition() {\n this.posTargetCollection = null;\n this.posIndex = this.posMethod == 'after' && this.cDim.length !== 0 ? this.posIndex + 1 : this.posIndex; //Normalize\n if (this.sorter) {\n this.sorter.moved = 0;\n this.sorter.endMove();\n }\n if (this.cDim) {\n this.posIsLastEl = this.cDim.length !== 0 && this.posMethod == 'after' && this.posIndex == this.cDim.length;\n this.posTargetEl =\n this.cDim.length === 0\n ? $(this.outsideElem)\n : !this.posIsLastEl && this.cDim[this.posIndex]\n ? $(this.cDim[this.posIndex][5]).parent()\n : $(this.outsideElem);\n this.posTargetModel = this.posTargetEl.data('model');\n this.posTargetCollection = this.posTargetEl.data('model-comp');\n }\n },\n\n /**\n * Enabel select position\n * @private\n */\n enable() {\n this.startSelectPosition();\n },\n\n /**\n * Check if the pointer is near to the float component\n * @param {number} index\n * @param {string} method\n * @param {Array
} dims\n * @return {Boolean}\n * @private\n * */\n nearFloat(index, method, dims) {\n var i = index || 0;\n var m = method || 'before';\n var len = dims.length;\n var isLast = len !== 0 && m == 'after' && i == len;\n if (len !== 0 && ((!isLast && !dims[i][4]) || (dims[i - 1] && !dims[i - 1][4]) || (isLast && !dims[i - 1][4])))\n return 1;\n return 0;\n },\n\n run() {\n this.enable();\n },\n\n stop() {\n this.stopSelectPosition();\n this.$wrapper.css('cursor', '');\n this.$wrapper.unbind();\n },\n};\n","import Backbone from 'backbone';\nimport { isUndefined } from 'underscore';\nimport { isTextNode } from '../../utils/mixins';\nconst $ = Backbone.$;\n\nexport default {\n getOffsetMethod(state) {\n var method = state || '';\n return 'get' + method + 'OffsetViewerEl';\n },\n\n run(editor, sender, opts) {\n var opt = opts || {};\n var state = opt.state || '';\n var config = editor.getConfig();\n const zoom = this.em.getZoomDecimal();\n const el = opt.el || '';\n\n if (!config.showOffsets || isTextNode(el) || (!config.showOffsetsSelected && state == 'Fixed')) {\n editor.stopCommand(this.id, opts);\n return;\n }\n\n var canvas = editor.Canvas;\n var pos = { ...(opt.elPos || canvas.getElementPos(el)) };\n\n if (!isUndefined(opt.top)) {\n pos.top = opt.top;\n }\n if (!isUndefined(opt.left)) {\n pos.left = opt.left;\n }\n\n var style = window.getComputedStyle(el);\n var ppfx = this.ppfx;\n var stateVar = state + 'State';\n var method = this.getOffsetMethod(state);\n var offsetViewer = canvas[method](opts.view);\n offsetViewer.style.opacity = '';\n\n let marginT = this['marginT' + state];\n let marginB = this['marginB' + state];\n let marginL = this['marginL' + state];\n let marginR = this['marginR' + state];\n let padT = this['padT' + state];\n let padB = this['padB' + state];\n let padL = this['padL' + state];\n let padR = this['padR' + state];\n\n if (offsetViewer.childNodes.length) {\n this[stateVar] = '1';\n marginT = offsetViewer.querySelector('[data-offset-m-t]');\n marginB = offsetViewer.querySelector('[data-offset-m-b]');\n marginL = offsetViewer.querySelector('[data-offset-m-l]');\n marginR = offsetViewer.querySelector('[data-offset-m-r]');\n padT = offsetViewer.querySelector('[data-offset-p-t]');\n padB = offsetViewer.querySelector('[data-offset-p-b]');\n padL = offsetViewer.querySelector('[data-offset-p-l]');\n padR = offsetViewer.querySelector('[data-offset-p-r]');\n }\n\n if (!this[stateVar]) {\n var stateLow = state.toLowerCase();\n var marginName = stateLow + 'margin-v';\n var paddingName = stateLow + 'padding-v';\n var marginV = $(``).get(0);\n var paddingV = $(`
`).get(0);\n var marginEls = ppfx + marginName + '-el';\n var paddingEls = ppfx + paddingName + '-el';\n const fullMargName = `${marginEls} ${ppfx + marginName}`;\n const fullPadName = `${paddingEls} ${ppfx + paddingName}`;\n marginT = $(`
`).get(0);\n marginB = $(`
`).get(0);\n marginL = $(`
`).get(0);\n marginR = $(`
`).get(0);\n padT = $(`
`).get(0);\n padB = $(`
`).get(0);\n padL = $(`
`).get(0);\n padR = $(`
`).get(0);\n this['marginT' + state] = marginT;\n this['marginB' + state] = marginB;\n this['marginL' + state] = marginL;\n this['marginR' + state] = marginR;\n this['padT' + state] = padT;\n this['padB' + state] = padB;\n this['padL' + state] = padL;\n this['padR' + state] = padR;\n marginV.appendChild(marginT);\n marginV.appendChild(marginB);\n marginV.appendChild(marginL);\n marginV.appendChild(marginR);\n paddingV.appendChild(padT);\n paddingV.appendChild(padB);\n paddingV.appendChild(padL);\n paddingV.appendChild(padR);\n offsetViewer.appendChild(marginV);\n offsetViewer.appendChild(paddingV);\n this[stateVar] = '1';\n }\n\n var unit = 'px';\n var marginLeftSt = parseFloat(style.marginLeft.replace(unit, '')) * zoom;\n var marginRightSt = parseFloat(style.marginRight.replace(unit, '')) * zoom;\n var marginTopSt = parseFloat(style.marginTop.replace(unit, '')) * zoom;\n var marginBottomSt = parseFloat(style.marginBottom.replace(unit, '')) * zoom;\n var mtStyle = marginT.style;\n var mbStyle = marginB.style;\n var mlStyle = marginL.style;\n var mrStyle = marginR.style;\n var ptStyle = padT.style;\n var pbStyle = padB.style;\n var plStyle = padL.style;\n var prStyle = padR.style;\n var posLeft = parseFloat(pos.left);\n var widthEl = parseFloat(style.width) * zoom + unit;\n\n // Margin style\n mtStyle.height = marginTopSt + unit;\n mtStyle.width = widthEl;\n mtStyle.top = pos.top - marginTopSt + unit;\n mtStyle.left = posLeft + unit;\n\n mbStyle.height = marginBottomSt + unit;\n mbStyle.width = widthEl;\n mbStyle.top = pos.top + pos.height + unit;\n mbStyle.left = posLeft + unit;\n\n var marginSideH = pos.height + marginTopSt + marginBottomSt + unit;\n var marginSideT = pos.top - marginTopSt + unit;\n mlStyle.height = marginSideH;\n mlStyle.width = marginLeftSt + unit;\n mlStyle.top = marginSideT;\n mlStyle.left = posLeft - marginLeftSt + unit;\n\n mrStyle.height = marginSideH;\n mrStyle.width = marginRightSt + unit;\n mrStyle.top = marginSideT;\n mrStyle.left = posLeft + pos.width + unit;\n\n // Padding style\n var padTop = parseFloat(style.paddingTop) * zoom;\n ptStyle.height = padTop + unit;\n // ptStyle.width = widthEl;\n // ptStyle.top = pos.top + unit;\n // ptStyle.left = posLeft + unit;\n\n var padBot = parseFloat(style.paddingBottom) * zoom;\n pbStyle.height = padBot + unit;\n // pbStyle.width = widthEl;\n // pbStyle.top = pos.top + pos.height - padBot + unit;\n // pbStyle.left = posLeft + unit;\n\n var padSideH = pos.height - padBot - padTop + unit;\n var padSideT = pos.top + padTop + unit;\n plStyle.height = padSideH;\n plStyle.width = parseFloat(style.paddingLeft) * zoom + unit;\n plStyle.top = padSideT;\n // plStyle.left = pos.left + unit;\n // plStyle.right = 0;\n\n var padRight = parseFloat(style.paddingRight) * zoom;\n prStyle.height = padSideH;\n prStyle.width = padRight + unit;\n prStyle.top = padSideT;\n // prStyle.left = pos.left + pos.width - padRight + unit;\n // prStyle.left = 0;\n },\n\n stop(editor, sender, opts = {}) {\n var opt = opts || {};\n var state = opt.state || '';\n var method = this.getOffsetMethod(state);\n var canvas = editor.Canvas;\n var offsetViewer = canvas[method](opts.view);\n offsetViewer.style.opacity = 0;\n },\n};\n","import { bindAll } from 'underscore';\n\nexport default {\n init() {\n bindAll(this, '_onFramesChange');\n },\n\n run(ed) {\n this.toggleVis(ed);\n },\n\n stop(ed) {\n this.toggleVis(ed, 0);\n },\n\n toggleVis(ed, active = 1) {\n if (!ed.Commands.isActive('preview')) {\n const cv = ed.Canvas;\n const mth = active ? 'on' : 'off';\n cv.getFrames().forEach(frame => this._upFrame(frame, active));\n cv.getModel()[mth]('change:frames', this._onFramesChange);\n }\n },\n\n _onFramesChange(m, frames) {\n frames.forEach(frame => frame.once('loaded', () => this._upFrame(frame, true)));\n },\n\n _upFrame(frame, active) {\n const method = active ? 'add' : 'remove';\n frame.view.getBody().classList[method](`${this.ppfx}dashed`);\n },\n};\n","import { isString, isElement } from 'underscore';\nimport { createId, deepMerge, isDef } from 'utils/mixins';\n\nexport default {\n getConfig(name) {\n return this.__getConfig(name);\n },\n\n getProjectData(data) {\n const obj = {};\n const key = this.storageKey;\n if (key) {\n obj[key] = data || this.getAll();\n }\n return obj;\n },\n\n loadProjectData(data = {}, { all, onResult, reset } = {}) {\n const key = this.storageKey;\n const opts = { action: 'load' };\n const coll = all || this.getAll();\n let result = data[key];\n\n if (typeof result == 'string') {\n try {\n result = JSON.parse(result);\n } catch (err) {\n this.__logWarn('Data parsing failed', { input: result });\n }\n }\n\n reset && result && coll.reset(null, opts);\n\n if (onResult) {\n result && onResult(result, opts);\n } else if (result && isDef(result.length)) {\n coll.reset(result, opts);\n }\n\n return result;\n },\n\n clear(opts = {}) {\n const { all } = this;\n all && all.reset(null, opts);\n return this;\n },\n\n __getConfig(name) {\n const res = this.config || {};\n return name ? res[name] : res;\n },\n\n getAll(opts = {}) {\n return this.all ? (opts.array ? [...this.all.models] : this.all) : [];\n },\n\n getAllMap() {\n return this.getAll().reduce((acc, i) => {\n acc[i.get(i.idAttribute)] = i;\n return acc;\n }, {});\n },\n\n __initConfig(def = {}, conf = {}) {\n this.config = deepMerge(def, conf);\n this.em = this.config.em;\n this.cls = [];\n },\n\n __initListen(opts = {}) {\n const { all, em, events } = this;\n all &&\n em &&\n all\n .on('add', (m, c, o) => em.trigger(events.add, m, o))\n .on('remove', (m, c, o) => em.trigger(events.remove, m, o))\n .on('change', (p, c) => em.trigger(events.update, p, p.changedAttributes(), c))\n .on('all', this.__catchAllEvent, this);\n // Register collections\n this.cls = [all].concat(opts.collections || []);\n // Propagate events\n (opts.propagate || []).forEach(({ entity, event }) => {\n entity.on('all', (ev, model, coll, opts) => {\n const options = opts || coll;\n const opt = { event: ev, ...options };\n [em, all].map(md => md.trigger(event, model, opt));\n });\n });\n },\n\n __remove(model, opts = {}) {\n const { em } = this;\n const md = isString(model) ? this.get(model) : model;\n const rm = () => {\n md && this.all.remove(md, opts);\n return md;\n };\n !opts.silent && em && em.trigger(this.events.removeBefore, md, rm, opts);\n return !opts.abort && rm();\n },\n\n __catchAllEvent(event, model, coll, opts) {\n const { em, events } = this;\n const options = opts || coll;\n em && events.all && em.trigger(events.all, { event, model, options });\n this.__onAllEvent();\n },\n\n __appendTo() {\n const elTo = this.getConfig().appendTo;\n\n if (elTo) {\n const el = isElement(elTo) ? elTo : document.querySelector(elTo);\n if (!el) return this.__logWarn('\"appendTo\" element not found');\n el.appendChild(this.render());\n }\n },\n\n __onAllEvent() {},\n\n __logWarn(str, opts) {\n this.em.logWarning(`[${this.name}]: ${str}`, opts);\n },\n\n _createId(len = 16) {\n const all = this.getAll();\n const ln = all.length + len;\n const allMap = this.getAllMap();\n let id;\n\n do {\n id = createId(ln);\n } while (allMap[id]);\n\n return id;\n },\n\n __listenAdd(model, event) {\n model.on('add', (m, c, o) => this.em.trigger(event, m, o));\n },\n\n __listenRemove(model, event) {\n model.on('remove', (m, c, o) => this.em.trigger(event, m, o));\n },\n\n __listenUpdate(model, event) {\n model.on('change', (p, c) => this.em.trigger(event, p, p.changedAttributes(), c));\n },\n\n __destroy() {\n this.cls.forEach(coll => {\n coll.stopListening();\n coll.reset();\n });\n this.em = 0;\n this.config = 0;\n this.view?.remove();\n this.view = 0;\n },\n};\n","export default {\n // Style prefix\n stylePrefix: 'css-',\n\n // Default CSS style\n rules: [],\n\n /**\n * Adjust style object before creation/update.\n * @example\n * onBeforeStyle(style) {\n * const padValue = style.padding;\n * if (padValue === '10px') {\n * delete style.padding;\n * style['padding-top'] = padValue;\n * // ...\n * }\n * return style;\n * }\n */\n onBeforeStyle: null,\n};\n","import { isEmpty, forEach, isString, isArray } from 'underscore';\nimport { Model } from '../../common';\nimport StyleableModel from '../../domain_abstract/model/StyleableModel';\nimport Selectors from '../../selector_manager/model/Selectors';\nimport { getMediaLength } from '../../code_manager/model/CssGenerator';\nimport { isEmptyObj, hasWin } from '../../utils/mixins';\n\nconst { CSS } = hasWin() ? window : {};\n\n/**\n * @typedef CssRule\n * @property {Array
} selectors Array of selectors\n * @property {Object} style Object containing style definitions\n * @property {String} [selectorsAdd=''] Additional string css selectors\n * @property {String} [atRuleType=''] Type of at-rule, eg. `media`, 'font-face'\n * @property {String} [mediaText=''] At-rule value, eg. `(max-width: 1000px)`\n * @property {Boolean} [singleAtRule=false] This property is used only on at-rules, like 'page' or 'font-face', where the block containes only style declarations\n * @property {String} [state=''] State of the rule, eg: `hover`, `focused`\n * @property {Boolean|Array} [important=false] If true, sets `!important` on all properties. You can also pass an array to specify properties on which use important\n * @property {Boolean} [stylable=true] Indicates if the rule is stylable from the editor\n *\n * [Device]: device.html\n * [State]: state.html\n * [Component]: component.html\n */\nexport default class CssRule extends StyleableModel {\n defaults() {\n return {\n selectors: [],\n selectorsAdd: '',\n style: {},\n mediaText: '',\n state: '',\n stylable: true,\n atRuleType: '',\n singleAtRule: false,\n important: false,\n group: '',\n // If true, won't be stored in JSON or showed in CSS\n shallow: false,\n _undo: true,\n };\n }\n\n initialize(c, opt = {}) {\n this.config = c || {};\n this.opt = opt;\n this.em = opt.em;\n this.ensureSelectors();\n this.on('change', this.__onChange);\n }\n\n __onChange(m, opts) {\n const { em } = this;\n const changed = this.changedAttributes();\n !isEmptyObj(changed) && em && em.changesUp(opts);\n }\n\n clone() {\n const opts = { ...this.opt };\n const attr = { ...this.attributes };\n attr.selectors = this.get('selectors').map(s => s.clone());\n return new this.constructor(attr, opts);\n }\n\n ensureSelectors(m, c, opts) {\n const { em } = this;\n const sm = em && em.get('SelectorManager');\n const toListen = [this, 'change:selectors', this.ensureSelectors];\n let sels = this.getSelectors();\n this.stopListening(...toListen);\n\n if (sels.models) {\n sels = [...sels.models];\n }\n\n sels = isString(sels) ? [sels] : sels;\n\n if (Array.isArray(sels)) {\n const res = sels.filter(i => i).map(i => (sm ? sm.add(i) : i));\n sels = new Selectors(res);\n }\n\n this.set('selectors', sels, opts);\n this.listenTo(...toListen);\n }\n\n /**\n * Returns the at-rule statement when exists, eg. `@media (...)`, `@keyframes`\n * @returns {String}\n * @example\n * const cssRule = editor.Css.setRule('.class1', { color: 'red' }, {\n * atRuleType: 'media',\n * atRuleParams: '(min-width: 500px)'\n * });\n * cssRule.getAtRule(); // \"@media (min-width: 500px)\"\n */\n getAtRule() {\n const type = this.get('atRuleType');\n const condition = this.get('mediaText');\n // Avoid breaks with the last condition\n const typeStr = type ? `@${type}` : condition ? '@media' : '';\n\n return typeStr + (condition && typeStr ? ` ${condition}` : '');\n }\n\n /**\n * Return selectors of the rule as a string\n * @param {Object} [opts] Options\n * @param {Boolean} [opts.skipState] Skip state from the result\n * @returns {String}\n * @example\n * const cssRule = editor.Css.setRule('.class1:hover', { color: 'red' });\n * cssRule.selectorsToString(); // \".class1:hover\"\n * cssRule.selectorsToString({ skipState: true }); // \".class1\"\n */\n selectorsToString(opts = {}) {\n const result = [];\n const state = this.get('state');\n const addSelector = this.get('selectorsAdd');\n const selOpts = {\n escape: str => (CSS && CSS.escape ? CSS.escape(str) : str),\n };\n const selectors = this.get('selectors').getFullString(0, selOpts);\n const stateStr = state && !opts.skipState ? `:${state}` : '';\n selectors && result.push(`${selectors}${stateStr}`);\n addSelector && !opts.skipAdd && result.push(addSelector);\n return result.join(', ');\n }\n\n /**\n * Get declaration block (without the at-rule statement)\n * @param {Object} [opts={}] Options (same as in `selectorsToString`)\n * @returns {String}\n * @example\n * const cssRule = editor.Css.setRule('.class1', { color: 'red' }, {\n * atRuleType: 'media',\n * atRuleParams: '(min-width: 500px)'\n * });\n * cssRule.getDeclaration() // \".class1{color:red;}\"\n */\n getDeclaration(opts = {}) {\n let result = '';\n const { important } = this.attributes;\n const selectors = this.selectorsToString(opts);\n const style = this.styleToString({ important, ...opts });\n const singleAtRule = this.get('singleAtRule');\n\n if ((selectors || singleAtRule) && (style || opts.allowEmpty)) {\n result = singleAtRule ? style : `${selectors}{${style}}`;\n }\n\n return result;\n }\n\n /**\n * Get the Device the rule is related to.\n * @returns {[Device]|null}\n * @example\n * const device = rule.getDevice();\n * console.log(device?.getName());\n */\n getDevice() {\n const { em } = this;\n const { atRuleType, mediaText } = this.attributes;\n const devices = em?.get('DeviceManager').getDevices() || [];\n const deviceDefault = devices.filter(d => d.getWidthMedia() === '')[0];\n if (atRuleType !== 'media' || !mediaText) {\n return deviceDefault || null;\n }\n return devices.filter(d => d.getWidthMedia() === getMediaLength(mediaText))[0] || null;\n }\n\n /**\n * Get the State the rule is related to.\n * @returns {[State]|null}\n * @example\n * const state = rule.getState();\n * console.log(state?.getLabel());\n */\n getState() {\n const { em } = this;\n const stateValue = this.get('state');\n const states = em.get('SelectorManager').getStates() || [];\n return states.filter(s => s.getName() === stateValue)[0] || null;\n }\n\n /**\n * Returns the related Component (valid only for component-specific rules).\n * @returns {[Component]|null}\n * @example\n * const cmp = rule.getComponent();\n * console.log(cmp?.toHTML());\n */\n getComponent() {\n const sel = this.getSelectors();\n const sngl = sel.length == 1 && sel.at(0);\n const cmpId = sngl && sngl.isId() && sngl.get('name');\n return (cmpId && this.em?.get('DomComponents').getById(cmpId)) || null;\n }\n\n /**\n * Return the CSS string of the rule\n * @param {Object} [opts={}] Options (same as in `getDeclaration`)\n * @return {String} CSS string\n * @example\n * const cssRule = editor.Css.setRule('.class1', { color: 'red' }, {\n * atRuleType: 'media',\n * atRuleParams: '(min-width: 500px)'\n * });\n * cssRule.toCSS() // \"@media (min-width: 500px){.class1{color:red;}}\"\n */\n toCSS(opts = {}) {\n let result = '';\n const atRule = this.getAtRule();\n const block = this.getDeclaration(opts);\n if (block || opts.allowEmpty) {\n result = block;\n }\n\n if (atRule && result) {\n result = `${atRule}{${result}}`;\n }\n\n return result;\n }\n\n toJSON(...args) {\n const obj = Model.prototype.toJSON.apply(this, args);\n\n if (this.em.getConfig().avoidDefaults) {\n const defaults = this.defaults();\n\n forEach(defaults, (value, key) => {\n if (obj[key] === value) {\n delete obj[key];\n }\n });\n\n // Delete the property used for partial updates\n delete obj.style.__p;\n\n if (isEmpty(obj.selectors)) delete obj.selectors;\n if (isEmpty(obj.style)) delete obj.style;\n }\n\n return obj;\n }\n\n /**\n * Compare the actual model with parameters\n * @param {Object} selectors Collection of selectors\n * @param {String} state Css rule state\n * @param {String} width For which device this style is oriented\n * @param {Object} ruleProps Other rule props\n * @returns {Boolean}\n * @private\n */\n compare(selectors, state, width, ruleProps = {}) {\n const st = state || '';\n const wd = width || '';\n const selAdd = ruleProps.selectorsAdd || '';\n let atRule = ruleProps.atRuleType || '';\n const sel = !isArray(selectors) && !selectors.models ? [selectors] : selectors.models || selectors;\n\n // Fix atRuleType in case is not specified with width\n if (wd && !atRule) atRule = 'media';\n\n const a1 = sel.map(model => model.getFullName());\n const a2 = this.get('selectors').map(model => model.getFullName());\n\n // Check selectors\n const a1S = a1.slice().sort();\n const a2S = a2.slice().sort();\n if (a1.length !== a2.length || !a1S.every((v, i) => v === a2S[i])) {\n return false;\n }\n\n // Check other properties\n if (\n this.get('state') !== st ||\n this.get('mediaText') !== wd ||\n this.get('selectorsAdd') !== selAdd ||\n this.get('atRuleType') !== atRule\n ) {\n return false;\n }\n\n return true;\n }\n}\n","import { Collection } from '../../common';\nimport CssRule from './CssRule';\n\nexport default class CssRules extends Collection {\n initialize(models, opt) {\n // Inject editor\n if (opt && opt.em) this.editor = opt.em;\n\n // This will put the listener post CssComposer.postLoad\n setTimeout(() => {\n this.on('remove', this.onRemove);\n this.on('add', this.onAdd);\n });\n }\n\n toJSON(opts) {\n const result = Collection.prototype.toJSON.call(this, opts);\n return result.filter(rule => rule.style && !rule.shallow);\n }\n\n onAdd(model, c, o) {\n model.ensureSelectors(model, c, o); // required for undo\n }\n\n onRemove(removed) {\n const em = this.editor;\n em.stopListening(removed);\n em.get('UndoManager').remove(removed);\n }\n\n add(models, opt = {}) {\n if (typeof models === 'string') {\n models = this.editor.get('Parser').parseCss(models);\n }\n opt.em = this.editor;\n return Collection.prototype.add.apply(this, [models, opt]);\n }\n}\n\nCssRules.prototype.model = CssRule;\n","/**\n * This module manages CSS rules in the canvas.\n * 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/css_composer/config/config.js)\n * ```js\n * const editor = grapesjs.init({\n * cssComposer: {\n * // options\n * }\n * })\n * ```\n *\n * Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance\n *\n * ```js\n * const css = editor.Css;\n * ```\n *\n * * [addRules](#addrules)\n * * [setRule](#setrule)\n * * [getRule](#getrule)\n * * [getRules](#getrules)\n * * [remove](#remove)\n * * [clear](#clear)\n *\n * [CssRule]: css_rule.html\n *\n * @module Css\n */\n\nimport { isArray, isString, isUndefined } from 'underscore';\nimport { isObject } from '../utils/mixins';\nimport Module from '../abstract/moduleLegacy';\nimport Selectors from '../selector_manager/model/Selectors';\nimport Selector from '../selector_manager/model/Selector';\nimport defaults from './config/config';\nimport CssRule from './model/CssRule';\nimport CssRules from './model/CssRules';\nimport CssRulesView from './view/CssRulesView';\n\nexport default class CssComposer extends Module {\n Selectors = Selectors;\n\n /**\n * Name of the module\n * @type {String}\n * @private\n */\n name = 'CssComposer';\n\n storageKey = 'styles';\n\n getConfig() {\n return this.c;\n }\n\n /**\n * Initializes module. Automatically called with a new instance of the editor\n * @param {Object} config Configurations\n * @private\n */\n init(config) {\n this.c = config || {};\n for (var name in defaults) {\n if (!(name in this.c)) this.c[name] = defaults[name];\n }\n\n var ppfx = this.c.pStylePrefix;\n if (ppfx) this.c.stylePrefix = ppfx + this.c.stylePrefix;\n\n var elStyle = (this.c.em && this.c.em.config.style) || '';\n this.c.rules = elStyle || this.c.rules;\n\n this.em = this.c.em;\n this.rules = new CssRules([], this.c);\n return this;\n }\n\n /**\n * On load callback\n * @private\n */\n onLoad() {\n this.rules.add(this.c.rules, { silent: 1 });\n }\n\n /**\n * Do stuff after load\n * @param {Editor} em\n * @private\n */\n postLoad() {\n const um = this.em?.get('UndoManager');\n um && um.add(this.getAll());\n }\n\n store() {\n return this.getProjectData();\n }\n\n load(data) {\n return this.loadProjectData(data);\n }\n\n /**\n * Add new rule to the collection, if not yet exists with the same selectors\n * @param {Array} selectors Array of selectors\n * @param {String} state Css rule state\n * @param {String} width For which device this style is oriented\n * @param {Object} props Other props for the rule\n * @param {Object} opts Options for the add of new rule\n * @return {Model}\n * @private\n * @example\n * var sm = editor.SelectorManager;\n * var sel1 = sm.add('myClass1');\n * var sel2 = sm.add('myClass2');\n * var rule = cssComposer.add([sel1, sel2], 'hover');\n * rule.set('style', {\n * width: '100px',\n * color: '#fff',\n * });\n * */\n add(selectors, state, width, opts = {}, addOpts = {}) {\n var s = state || '';\n var w = width || '';\n var opt = { ...opts };\n var rule = this.get(selectors, s, w, opt);\n\n // do not create rules that were found before\n // unless this is a single at-rule, for which multiple declarations\n // make sense (e.g. multiple `@font-type`s)\n if (rule && rule.config && !rule.config.singleAtRule) {\n return rule;\n } else {\n opt.state = s;\n opt.mediaText = w;\n opt.selectors = [];\n w && (opt.atRuleType = 'media');\n rule = new CssRule(opt, this.c);\n rule.get('selectors').add(selectors, addOpts);\n this.rules.add(rule, addOpts);\n return rule;\n }\n }\n\n /**\n * Get the rule\n * @param {String|Array} selectors Array of selectors or selector string, eg `.myClass1.myClass2`\n * @param {String} state Css rule state, eg. 'hover'\n * @param {String} width Media rule value, eg. '(max-width: 992px)'\n * @param {Object} ruleProps Other rule props\n * @return {Model|null}\n * @private\n * @example\n * const sm = editor.SelectorManager;\n * const sel1 = sm.add('myClass1');\n * const sel2 = sm.add('myClass2');\n * const rule = cssComposer.get([sel1, sel2], 'hover', '(max-width: 992px)');\n * // Update the style\n * rule.set('style', {\n * width: '300px',\n * color: '#000',\n * });\n * */\n get(selectors, state, width, ruleProps) {\n let slc = selectors;\n if (isString(selectors)) {\n const sm = this.em.get('SelectorManager');\n const singleSel = selectors.split(',')[0].trim();\n const node = this.em.get('Parser').parserCss.checkNode({ selectors: singleSel })[0];\n slc = sm.get(node.selectors);\n }\n return this.rules.find(rule => rule.compare(slc, state, width, ruleProps)) || null;\n }\n\n getAll() {\n return this.rules;\n }\n\n /**\n * Add a raw collection of rule objects\n * This method overrides styles, in case, of already defined rule\n * @param {String|Array