Browse Source

Merge multi-frame

pull/1800/head
Artur Arseniev 8 years ago
parent
commit
dd9aaf10df
  1. 2
      dist/css/grapes.min.css
  2. 56
      src/canvas/index.js
  3. 16
      src/canvas/model/Canvas.js
  4. 145
      src/canvas/view/CanvasView.js
  5. 33
      src/commands/index.js
  6. 77
      src/commands/view/CanvasMove.js
  7. 34
      src/commands/view/ImageComponent.js
  8. 76
      src/commands/view/InsertCustom.js
  9. 26
      src/commands/view/SelectComponent.js
  10. 7
      src/commands/view/SelectPosition.js
  11. 45
      src/commands/view/ShowOffset.js
  12. 27
      src/commands/view/TextComponent.js
  13. 4
      src/editor/model/Editor.js
  14. 29
      src/styles/scss/_gjs_canvas.scss
  15. 18
      src/styles/scss/_gjs_status.scss
  16. 2
      src/styles/scss/main.scss
  17. 407
      src/utils/Dragger.js
  18. 27
      src/utils/Sorter.js
  19. 10
      src/utils/mixins.js

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

56
src/canvas/index.js

@ -62,13 +62,14 @@ module.exports = () => {
* @param {Object} config Configurations * @param {Object} config Configurations
* @private * @private
*/ */
init(config) { init(config = {}) {
c = config || {}; c = {
for (var name in defaults) { ...defaults,
if (!(name in c)) c[name] = defaults[name]; ...config
} };
var ppfx = c.pStylePrefix; this.em = c.em;
const ppfx = c.pStylePrefix;
if (ppfx) c.stylePrefix = ppfx + c.stylePrefix; if (ppfx) c.stylePrefix = ppfx + c.stylePrefix;
canvas = new Canvas(config); canvas = new Canvas(config);
@ -289,6 +290,16 @@ module.exports = () => {
return CanvasView.getElementPos(el, opts); return CanvasView.getElementPos(el, opts);
}, },
/**
* Returns element's offsets like margins and paddings
* @param {HTMLElement} el
* @return {Object}
* @private
*/
getElementOffsets(el) {
return CanvasView.getElementOffsets(el);
},
/** /**
* This method comes handy when you need to attach something like toolbars * This method comes handy when you need to attach something like toolbars
* to elements inside the canvas, dealing with all relative position, * to elements inside the canvas, dealing with all relative position,
@ -379,22 +390,17 @@ module.exports = () => {
/** /**
* X and Y mouse position relative to the canvas * X and Y mouse position relative to the canvas
* @param {Event} e * @param {Event} ev
* @return {Object} * @return {Object}
* @private * @private
*/ */
getMouseRelativeCanvas(e, options) { getMouseRelativeCanvas(ev) {
var opts = options || {}; const zoom = this.em.getZoomDecimal();
var frame = this.getFrameEl(); const { top, left } = CanvasView.getPosition();
var body = this.getBody();
var addTop = frame.offsetTop || 0;
var addLeft = frame.offsetLeft || 0;
var yOffset = body.scrollTop || 0;
var xOffset = body.scrollLeft || 0;
return { return {
y: e.clientY + addTop + yOffset, y: ev.clientY * zoom + top,
x: e.clientX + addLeft + xOffset x: ev.clientX * zoom + left
}; };
}, },
@ -463,7 +469,7 @@ module.exports = () => {
updateClientY(ev) { updateClientY(ev) {
ev.preventDefault(); ev.preventDefault();
this.lastClientY = getPointerEvent(ev).clientY; this.lastClientY = getPointerEvent(ev).clientY * this.em.getZoomDecimal();
}, },
/** /**
@ -503,13 +509,25 @@ module.exports = () => {
}, },
getScrollListeners() { getScrollListeners() {
return [this.getFrameEl().contentWindow, this.getElement()]; return [this.getFrameEl().contentWindow];
}, },
postRender() { postRender() {
if (hasDnd(c.em)) this.droppable = new Droppable(c.em); if (hasDnd(c.em)) this.droppable = new Droppable(c.em);
}, },
setZoom(value) {
return canvas.set('zoom', parseFloat(value));
},
getZoom() {
return parseFloat(canvas.get('zoom'));
},
getZoomDecimal() {
return this.getZoom() / 100;
},
/** /**
* Returns wrapper element * Returns wrapper element
* @return {HTMLElement} * @return {HTMLElement}

16
src/canvas/model/Canvas.js

@ -5,11 +5,19 @@ module.exports = Backbone.Model.extend({
defaults: { defaults: {
frame: '', frame: '',
wrapper: '', wrapper: '',
rulers: false rulers: false,
zoom: 100,
x: 0,
y: 0
}, },
initialize(config) { initialize() {
var conf = this.conf || {}; this.set('frame', new Frame());
this.set('frame', new Frame(conf.frame)); this.listenTo(this, 'change:zoom', this.onZoomChange);
},
onZoomChange() {
const zoom = this.get('zoom');
zoom < 1 && this.set('zoom', 1);
} }
}); });

145
src/canvas/view/CanvasView.js

@ -1,23 +1,97 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
import { on, off, getElement } from 'utils/mixins'; import { on, off, getElement, getKeyChar } from 'utils/mixins';
const FrameView = require('./FrameView'); const FrameView = require('./FrameView');
const $ = Backbone.$; const $ = Backbone.$;
let timerZoom;
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
events: {
wheel: 'onWheel'
},
template() {
const { pfx } = this;
return `
<div class="${pfx}canvas__frames" data-frames></div>
<div id="${pfx}tools" class="${pfx}canvas__tools" data-tools></div>
`;
},
initialize(o) { initialize(o) {
_.bindAll(this, 'renderBody', 'onFrameScroll', 'clearOff'); _.bindAll(this, 'renderBody', 'onFrameScroll', 'clearOff', 'onKeyPress');
on(window, 'scroll resize', this.clearOff); on(window, 'scroll resize', this.clearOff);
const { model } = this;
this.config = o.config || {}; this.config = o.config || {};
this.em = this.config.em || {}; this.em = this.config.em || {};
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || ''; this.ppfx = this.config.pStylePrefix || '';
this.className = this.config.stylePrefix + 'canvas'; this.className = this.config.stylePrefix + 'canvas';
this.listenTo(this.em, 'change:canvasOffset', this.clearOff); this.listenTo(this.em, 'change:canvasOffset', this.clearOff);
this.listenTo(model, 'change:zoom change:x change:y', this.updateFrames);
this.toggleListeners(1);
this.frame = new FrameView({ this.frame = new FrameView({
model: this.model.get('frame'), model: this.model.get('frame'),
config: this.config config: this.config
}); });
}, },
remove() {
Backbone.View.prototype.remove.apply(this, arguments);
this.toggleListeners();
},
preventDefault(ev) {
if (ev) {
ev.preventDefault();
ev._parentEvent && ev._parentEvent.preventDefault();
}
},
toggleListeners(enable) {
const method = enable ? 'on' : 'off';
const methods = { on, off };
methods[method](document, 'keypress', this.onKeyPress);
},
onKeyPress(ev) {
const { em } = this;
const key = getKeyChar(ev);
if (key === ' ' && em.getZoomDecimal() !== 1) {
this.preventDefault(ev);
em.get('Editor').runCommand('core:canvas-move');
}
},
onWheel(ev) {
if (ev.ctrlKey || ev.metaKey) {
this.preventDefault(ev);
const { model } = this;
const delta = Math.max(-1, Math.min(1, ev.wheelDelta || -ev.detail));
const zoom = model.get('zoom');
model.set('zoom', zoom + delta * 2);
}
},
updateFrames() {
const { em, model } = this;
const { x, y } = model.attributes;
const zoom = this.getZoom();
const defOpts = { preserveSelected: 1 };
const mpl = zoom ? 1 / zoom : 1;
this.framesArea.style.transform = `scale(${zoom}) translate(${x *
mpl}px, ${y * mpl}px)`;
this.clearOff();
this.onFrameScroll();
em.stopDefault(defOpts);
timerZoom && clearTimeout(timerZoom);
timerZoom = setTimeout(() => em.runDefault(defOpts));
},
getZoom() {
return this.em.getZoomDecimal();
},
/** /**
* Checks if the element is visible in the canvas's viewport * Checks if the element is visible in the canvas's viewport
* @param {HTMLElement} el * @param {HTMLElement} el
@ -43,8 +117,9 @@ module.exports = Backbone.View.extend({
onFrameScroll() { onFrameScroll() {
var u = 'px'; var u = 'px';
var body = this.frame.el.contentDocument.body; var body = this.frame.el.contentDocument.body;
this.toolsEl.style.top = '-' + body.scrollTop + u; const zoom = this.getZoom();
this.toolsEl.style.left = '-' + body.scrollLeft + u; this.toolsEl.style.top = '-' + body.scrollTop * zoom + u;
this.toolsEl.style.left = '-' + body.scrollLeft * zoom + u;
this.em.trigger('canvasScroll'); this.em.trigger('canvasScroll');
}, },
@ -188,6 +263,7 @@ module.exports = Backbone.View.extend({
oEvent.initEvent(e.type, true, true); oEvent.initEvent(e.type, true, true);
} }
oEvent.keyCodeVal = e.keyCode; oEvent.keyCodeVal = e.keyCode;
oEvent._parentEvent = e;
['keyCode', 'which'].forEach(prop => { ['keyCode', 'which'].forEach(prop => {
Object.defineProperty(oEvent, prop, { Object.defineProperty(oEvent, prop, {
get() { get() {
@ -199,12 +275,12 @@ module.exports = Backbone.View.extend({
}; };
[ [
{ event: 'keydown keyup', class: 'KeyboardEvent' } { event: 'keydown keyup keypress', class: 'KeyboardEvent' },
//{ event: 'mousedown mousemove mouseup', class: 'MouseEvent' }, { event: 'wheel', class: 'WheelEvent' }
].forEach(obj => ].forEach(obj =>
obj.event.split(' ').forEach(event => { obj.event.split(' ').forEach(event => {
fdoc.addEventListener(event, e => fdoc.addEventListener(event, e =>
doc.dispatchEvent(createCustomEvent(e, obj.class)) this.el.dispatchEvent(createCustomEvent(e, obj.class))
); );
}) })
); );
@ -263,6 +339,7 @@ module.exports = Backbone.View.extend({
* @private * @private
*/ */
getElementPos(el, opts) { getElementPos(el, opts) {
const zoom = this.getZoom();
var opt = opts || {}; var opt = opts || {};
var frmOff = this.getFrameOffset(); var frmOff = this.getFrameOffset();
var cvsOff = this.getCanvasOffset(); var cvsOff = this.getCanvasOffset();
@ -271,15 +348,39 @@ module.exports = Backbone.View.extend({
var frmTop = opt.avoidFrameOffset ? 0 : frmOff.top; var frmTop = opt.avoidFrameOffset ? 0 : frmOff.top;
var frmLeft = opt.avoidFrameOffset ? 0 : frmOff.left; var frmLeft = opt.avoidFrameOffset ? 0 : frmOff.left;
const top = eo.top + frmTop - cvsOff.top; const top = eo.top * zoom + frmTop - cvsOff.top;
const left = eo.left + frmLeft - cvsOff.left; const left = eo.left * zoom + frmLeft - cvsOff.left;
// clientHeight/clientWidth are for SVGs const height = eo.height * zoom;
const height = el.offsetHeight || el.clientHeight; const width = eo.width * zoom;
const width = el.offsetWidth || el.clientWidth;
return { top, left, height, width }; return { top, left, height, width };
}, },
/**
* Returns element's offsets like margins and paddings
* @param {HTMLElement} el
* @return {Object}
* @private
*/
getElementOffsets(el) {
const result = {};
const styles = window.getComputedStyle(el);
[
'marginTop',
'marginRight',
'marginBottom',
'marginLeft',
'paddingTop',
'paddingRight',
'paddingBottom',
'paddingLeft'
].forEach(offset => {
result[offset] = parseFloat(styles[offset]) * this.getZoom();
});
return result;
},
/** /**
* Returns position data of the canvas element * Returns position data of the canvas element
* @return {Object} obj Position object * @return {Object} obj Position object
@ -289,11 +390,13 @@ module.exports = Backbone.View.extend({
const doc = this.frame.el.contentDocument; const doc = this.frame.el.contentDocument;
if (!doc) return; if (!doc) return;
const bEl = doc.body; const bEl = doc.body;
const zoom = this.getZoom();
const fo = this.getFrameOffset(); const fo = this.getFrameOffset();
const co = this.getCanvasOffset(); const co = this.getCanvasOffset();
return { return {
top: fo.top + bEl.scrollTop - co.top, top: fo.top + bEl.scrollTop * zoom - co.top,
left: fo.left + bEl.scrollLeft - co.left left: fo.left + bEl.scrollLeft * zoom - co.left
}; };
}, },
@ -340,11 +443,15 @@ module.exports = Backbone.View.extend({
}, },
render() { render() {
this.wrapper = this.model.get('wrapper'); const { el, $el, ppfx, model } = this;
this.wrapper = model.get('wrapper');
$el.html(this.template());
const $frames = $el.find('[data-frames]');
this.framesArea = $frames.get(0);
if (this.wrapper && typeof this.wrapper.render == 'function') { if (this.wrapper && typeof this.wrapper.render == 'function') {
this.model.get('frame').set('wrapper', this.wrapper); model.get('frame').set('wrapper', this.wrapper);
this.$el.append(this.frame.render().el); $frames.append(this.frame.render().el);
var frame = this.frame; var frame = this.frame;
if (this.config.scripts.length === 0) { if (this.config.scripts.length === 0) {
frame.el.onload = this.renderBody; frame.el.onload = this.renderBody;
@ -352,8 +459,7 @@ module.exports = Backbone.View.extend({
this.renderScripts(); // will call renderBody later this.renderScripts(); // will call renderBody later
} }
} }
var ppfx = this.ppfx; $el.find('[data-tools]').append(`
this.$el.append(`
<div id="${ppfx}tools" style="pointer-events:none"> <div id="${ppfx}tools" style="pointer-events:none">
<div class="${ppfx}highlighter"></div> <div class="${ppfx}highlighter"></div>
<div class="${ppfx}badge"></div> <div class="${ppfx}badge"></div>
@ -367,7 +473,6 @@ module.exports = Backbone.View.extend({
<div class="${ppfx}offset-fixed-v"></div> <div class="${ppfx}offset-fixed-v"></div>
</div> </div>
`); `);
const el = this.el;
const toolsEl = el.querySelector(`#${ppfx}tools`); const toolsEl = el.querySelector(`#${ppfx}tools`);
this.hlEl = el.querySelector(`.${ppfx}highlighter`); this.hlEl = el.querySelector(`.${ppfx}highlighter`);
this.badgeEl = el.querySelector(`.${ppfx}badge`); this.badgeEl = el.querySelector(`.${ppfx}badge`);

33
src/commands/index.js

@ -26,7 +26,7 @@
* @module Commands * @module Commands
*/ */
import { isFunction, isUndefined } from 'underscore'; import { isFunction } from 'underscore';
import CommandAbstract from './view/CommandAbstract'; import CommandAbstract from './view/CommandAbstract';
module.exports = () => { module.exports = () => {
@ -81,10 +81,7 @@ module.exports = () => {
defaultCommands['select-comp'] = require('./view/SelectComponent'); defaultCommands['select-comp'] = require('./view/SelectComponent');
defaultCommands['create-comp'] = require('./view/CreateComponent'); defaultCommands['create-comp'] = require('./view/CreateComponent');
defaultCommands['delete-comp'] = require('./view/DeleteComponent'); defaultCommands['delete-comp'] = require('./view/DeleteComponent');
defaultCommands['image-comp'] = require('./view/ImageComponent');
defaultCommands['move-comp'] = require('./view/MoveComponent'); defaultCommands['move-comp'] = require('./view/MoveComponent');
defaultCommands['text-comp'] = require('./view/TextComponent');
defaultCommands['insert-custom'] = require('./view/InsertCustom');
defaultCommands['export-template'] = ViewCode; defaultCommands['export-template'] = ViewCode;
defaultCommands['sw-visibility'] = require('./view/SwitchVisibility'); defaultCommands['sw-visibility'] = require('./view/SwitchVisibility');
defaultCommands['open-layers'] = require('./view/OpenLayers'); defaultCommands['open-layers'] = require('./view/OpenLayers');
@ -189,6 +186,7 @@ module.exports = () => {
[ [
['copy', 'CopyComponent'], ['copy', 'CopyComponent'],
['paste', 'PasteComponent'], ['paste', 'PasteComponent'],
['canvas-move', 'CanvasMove'],
['component-next', 'ComponentNext'], ['component-next', 'ComponentNext'],
['component-prev', 'ComponentPrev'], ['component-prev', 'ComponentPrev'],
['component-enter', 'ComponentEnter'], ['component-enter', 'ComponentEnter'],
@ -198,9 +196,7 @@ module.exports = () => {
['component-style-clear', 'ComponentStyleClear'] ['component-style-clear', 'ComponentStyleClear']
].forEach( ].forEach(
item => item =>
(defaultCommands[`core:${item[0]}`] = require(`./view/${ (defaultCommands[`core:${item[0]}`] = require(`./view/${item[1]}`))
item[1]
}`).run)
); );
if (c.em) c.model = c.em.get('Canvas'); if (c.em) c.model = c.em.get('Canvas');
@ -239,11 +235,13 @@ module.exports = () => {
* myCommand.run(); * myCommand.run();
* */ * */
get(id) { get(id) {
var el = commands[id]; let el = commands[id];
if (typeof el == 'function') { if (isFunction(el)) {
el = new el(c); el = new el(c);
commands[id] = el; commands[id] = el;
} else if (!el) {
em.logWarning(`'${id}' command not found`);
} }
return el; return el;
@ -346,10 +344,12 @@ module.exports = () => {
if (command && command.run) { if (command && command.run) {
const id = command.id; const id = command.id;
const editor = em.get('Editor'); const editor = em.get('Editor');
result = command.callRun(editor, options);
if (id && command.stop && !command.noStop) { if (!this.isActive(id)) {
active[id] = result; if (id && command.stop && !command.noStop) {
active[id] = result;
}
result = command.callRun(editor, options);
} }
} }
@ -357,7 +357,7 @@ module.exports = () => {
}, },
/** /**
* [runCommand description] * Stop the command
* @param {Object} command * @param {Object} command
* @param {Object} options * @param {Object} options
* @return {*} Result of the command * @return {*} Result of the command
@ -369,8 +369,11 @@ module.exports = () => {
if (command && command.run) { if (command && command.run) {
const id = command.id; const id = command.id;
const editor = em.get('Editor'); const editor = em.get('Editor');
result = command.callStop(editor, options);
if (id) delete active[id]; if (this.isActive(id)) {
if (id) delete active[id];
result = command.callStop(editor, options);
}
} }
return result; return result;

77
src/commands/view/CanvasMove.js

@ -0,0 +1,77 @@
import { bindAll } from 'underscore';
import { on, off, getKeyChar } from 'utils/mixins';
import Dragger from 'utils/Dragger';
module.exports = {
run(ed) {
bindAll(this, 'onKeyUp', 'enableDragger', 'disableDragger');
this.editor = ed;
this.canvasModel = this.canvas.getCanvasView().model;
this.toggleMove(1);
},
stop(ed) {
this.toggleMove();
this.disableDragger();
},
onKeyUp(ev) {
if (getKeyChar(ev) === ' ') {
this.editor.stopCommand(this.id);
}
},
enableDragger(ev) {
this.toggleDragger(1, ev);
},
disableDragger(ev) {
this.toggleDragger(0, ev);
},
toggleDragger(enable, ev) {
const { canvasModel, em } = this;
let { dragger } = this;
const methodCls = enable ? 'add' : 'remove';
this.getCanvas().classList[methodCls](`${this.ppfx}is__grabbing`);
if (!dragger) {
dragger = Dragger.init({
getPosition() {
return {
x: canvasModel.get('x'),
y: canvasModel.get('y')
};
},
setPosition({ x, y }) {
canvasModel.set({ x, y });
},
onStart(ev, dragger) {
em.trigger('canvas:move:start', dragger);
},
onDrag(ev, dragger) {
em.trigger('canvas:move', dragger);
},
onEnd(ev, dragger) {
em.trigger('canvas:move:end', dragger);
}
});
this.dragger = dragger;
}
enable ? dragger.start(ev) : dragger.stop();
},
toggleMove(enable) {
const { ppfx } = this;
const methodCls = enable ? 'add' : 'remove';
const methodEv = enable ? 'on' : 'off';
const methodsEv = { on, off };
const canvas = this.getCanvas();
const classes = [`${ppfx}is__grab`];
!enable && classes.push(`${ppfx}is__grabbing`);
classes.forEach(cls => canvas.classList[methodCls](cls));
methodsEv[methodEv](document, 'keyup', this.onKeyUp);
methodsEv[methodEv](canvas, 'mousedown', this.enableDragger);
methodsEv[methodEv](document, 'mouseup', this.disableDragger);
}
};

34
src/commands/view/ImageComponent.js

@ -1,34 +0,0 @@
import _ from 'underscore';
import Backbone from 'backbone';
var InsertCustom = require('./InsertCustom');
module.exports = _.extend({}, InsertCustom, {
/**
* Trigger before insert
* @param {Object} object
* @private
*
* */
beforeInsert(object) {
object.type = 'image';
object.style = {};
object.attributes = {};
object.attributes.onmousedown = 'return false';
if (
this.config.firstCentered &&
this.getCanvasWrapper() == this.sorter.target
) {
object.style.margin = '0 auto';
}
},
/**
* Trigger after insert
* @param {Object} model Model created after insert
* @private
* */
afterInsert(model) {
model.trigger('dblclick');
if (this.sender) this.sender.set('active', false);
}
});

76
src/commands/view/InsertCustom.js

@ -1,76 +0,0 @@
import _ from 'underscore';
import Backbone from 'backbone';
var CreateComponent = require('./CreateComponent');
module.exports = _.extend({}, CreateComponent, {
init(...args) {
CreateComponent.init.apply(this, args);
_.bindAll(this, 'insertComponent');
this.allowDraw = 0;
},
/**
* Run method
* @private
* */
run(em, sender, options) {
this.em = em;
this.sender = sender;
this.opt = options || {};
this.$wr = this.$wrapper;
this.enable();
},
enable(...args) {
CreateComponent.enable.apply(this, args);
this.$wr.on('click', this.insertComponent);
},
/**
* Start insert event
* @private
* */
insertComponent() {
this.$wr.off('click', this.insertComponent);
this.stopSelectPosition();
var object = this.buildContent();
this.beforeInsert(object);
var index = this.sorter.lastPos.index;
// By default, collections do not trigger add event, so silent is used
var model = this.create(this.sorter.target, object, index, null, {
silent: false
});
if (this.opt.terminateAfterInsert && this.sender)
this.sender.set('active', false);
else this.enable();
if (!model) return;
this.afterInsert(model, this);
},
/**
* Trigger before insert
* @param {Object} obj
* @private
* */
beforeInsert(obj) {},
/**
* Trigger after insert
* @param {Object} model Model created after insert
* @private
* */
afterInsert(model) {},
/**
* Create different object, based on content, to insert inside canvas
*
* @return {Object}
* @private
* */
buildContent() {
return this.opt.content || {};
}
});

26
src/commands/view/SelectComponent.js

@ -3,7 +3,6 @@ import { on, off, getUnitFromValue } from 'utils/mixins';
const ToolbarView = require('dom_components/view/ToolbarView'); const ToolbarView = require('dom_components/view/ToolbarView');
const Toolbar = require('dom_components/model/Toolbar'); const Toolbar = require('dom_components/model/Toolbar');
const key = require('keymaster');
const $ = require('backbone').$; const $ = require('backbone').$;
let showOffsets; let showOffsets;
@ -44,7 +43,7 @@ module.exports = {
* @private * @private
* */ * */
toggleSelectComponent(enable) { toggleSelectComponent(enable) {
const em = this.em; const { em } = this;
const method = enable ? 'on' : 'off'; const method = enable ? 'on' : 'off';
const methods = { on, off }; const methods = { on, off };
const body = this.getCanvasBody(); const body = this.getCanvasBody();
@ -106,8 +105,8 @@ module.exports = {
* @param {Object} e * @param {Object} e
* @private * @private
*/ */
onOut(e) { onOut(ev) {
e.stopPropagation(); ev && ev.stopPropagation();
this.hideBadge(); this.hideBadge();
this.hideHighlighter(); this.hideHighlighter();
this.hideElementOffset(); this.hideElementOffset();
@ -138,7 +137,8 @@ module.exports = {
* @param {Object} pos * @param {Object} pos
*/ */
hideElementOffset(el, pos) { hideElementOffset(el, pos) {
this.editor.stopCommand('show-offset'); const { editor } = this;
editor && editor.stopCommand('show-offset');
}, },
/** /**
@ -280,13 +280,15 @@ module.exports = {
var bStyle = badge.style; var bStyle = badge.style;
var u = 'px'; var u = 'px';
bStyle.display = 'block'; bStyle.display = 'block';
var canvasPos = canvas.getCanvasView().getPosition(); var canvasPos = this.getCanvasPosition();
if (canvasPos) { if (canvasPos) {
var badgeH = badge ? badge.offsetHeight : 0; const canvasTop = canvasPos.top;
var badgeW = badge ? badge.offsetWidth : 0; const canvasLeft = canvasPos.left;
var top = const posTop = pos.top - (badge ? badge.offsetHeight : 0);
pos.top - badgeH < canvasPos.top ? canvasPos.top : pos.top - badgeH; const badgeW = badge ? badge.offsetWidth : 0;
var left = pos.left + badgeW < canvasPos.left ? canvasPos.left : pos.left; var top = posTop < canvasTop ? canvasTop : posTop;
var left = pos.left + badgeW < canvasLeft ? canvasLeft : pos.left;
bStyle.top = top + u; bStyle.top = top + u;
bStyle.left = left + u; bStyle.left = left + u;
} }
@ -673,7 +675,7 @@ module.exports = {
this.stopSelectComponent(); this.stopSelectComponent();
!opts.preserveSelected && em.setSelected(null); !opts.preserveSelected && em.setSelected(null);
this.clean(); this.clean();
this.hideBadge(); this.onOut();
this.hideFixedElementOffset(); this.hideFixedElementOffset();
this.canvas.getToolbarEl().style.display = 'none'; this.canvas.getToolbarEl().style.display = 'none';

7
src/commands/view/SelectPosition.js

@ -22,7 +22,8 @@ module.exports = {
wmargin: 1, wmargin: 1,
nested: 1, nested: 1,
em: this.editorModel, em: this.editorModel,
canvasRelative: 1 canvasRelative: 1,
scale: () => this.em.getZoomDecimal()
}); });
trg && this.sorter.startSort(trg); trg && this.sorter.startSort(trg);
}, },
@ -63,8 +64,8 @@ module.exports = {
this.cDim.length === 0 this.cDim.length === 0
? $(this.outsideElem) ? $(this.outsideElem)
: !this.posIsLastEl && this.cDim[this.posIndex] : !this.posIsLastEl && this.cDim[this.posIndex]
? $(this.cDim[this.posIndex][5]).parent() ? $(this.cDim[this.posIndex][5]).parent()
: $(this.outsideElem); : $(this.outsideElem);
this.posTargetModel = this.posTargetEl.data('model'); this.posTargetModel = this.posTargetEl.data('model');
this.posTargetCollection = this.posTargetEl.data('model-comp'); this.posTargetCollection = this.posTargetEl.data('model-comp');
} }

45
src/commands/view/ShowOffset.js

@ -11,11 +11,13 @@ module.exports = {
var opt = opts || {}; var opt = opts || {};
var state = opt.state || ''; var state = opt.state || '';
var config = editor.getConfig(); var config = editor.getConfig();
const zoom = this.em.getZoomDecimal();
if ( if (
!config.showOffsets || !config.showOffsets ||
(!config.showOffsetsSelected && state == 'Fixed') (!config.showOffsetsSelected && state == 'Fixed')
) { ) {
editor.stopCommand(this.id, opts);
return; return;
} }
@ -78,9 +80,11 @@ module.exports = {
} }
var unit = 'px'; var unit = 'px';
var marginLeftSt = style.marginLeft.replace(unit, ''); var marginLeftSt = parseFloat(style.marginLeft.replace(unit, '')) * zoom;
var marginTopSt = parseInt(style.marginTop.replace(unit, '')); var marginRightSt = parseFloat(style.marginRight.replace(unit, '')) * zoom;
var marginBottomSt = parseInt(style.marginBottom.replace(unit, '')); var marginTopSt = parseFloat(style.marginTop.replace(unit, '')) * zoom;
var marginBottomSt =
parseFloat(style.marginBottom.replace(unit, '')) * zoom;
var mtStyle = marginT.style; var mtStyle = marginT.style;
var mbStyle = marginB.style; var mbStyle = marginB.style;
var mlStyle = marginL.style; var mlStyle = marginL.style;
@ -89,54 +93,55 @@ module.exports = {
var pbStyle = padB.style; var pbStyle = padB.style;
var plStyle = padL.style; var plStyle = padL.style;
var prStyle = padR.style; var prStyle = padR.style;
var posLeft = parseInt(pos.left); var posLeft = parseFloat(pos.left);
var widthEl = parseFloat(style.width) * zoom + unit;
// Margin style // Margin style
mtStyle.height = style.marginTop; mtStyle.height = marginTopSt + unit;
mtStyle.width = style.width; mtStyle.width = widthEl;
mtStyle.top = pos.top - style.marginTop.replace(unit, '') + unit; mtStyle.top = pos.top - marginTopSt + unit;
mtStyle.left = posLeft + unit; mtStyle.left = posLeft + unit;
mbStyle.height = style.marginBottom; mbStyle.height = marginBottomSt + unit;
mbStyle.width = style.width; mbStyle.width = widthEl;
mbStyle.top = pos.top + pos.height + unit; mbStyle.top = pos.top + pos.height + unit;
mbStyle.left = posLeft + unit; mbStyle.left = posLeft + unit;
var marginSideH = pos.height + marginTopSt + marginBottomSt + unit; var marginSideH = pos.height + marginTopSt + marginBottomSt + unit;
var marginSideT = pos.top - marginTopSt + unit; var marginSideT = pos.top - marginTopSt + unit;
mlStyle.height = marginSideH; mlStyle.height = marginSideH;
mlStyle.width = style.marginLeft; mlStyle.width = marginLeftSt + unit;
mlStyle.top = marginSideT; mlStyle.top = marginSideT;
mlStyle.left = posLeft - marginLeftSt + unit; mlStyle.left = posLeft - marginLeftSt + unit;
mrStyle.height = marginSideH; mrStyle.height = marginSideH;
mrStyle.width = style.marginRight; mrStyle.width = marginRightSt + unit;
mrStyle.top = marginSideT; mrStyle.top = marginSideT;
mrStyle.left = posLeft + pos.width + unit; mrStyle.left = posLeft + pos.width + unit;
// Padding style // Padding style
var padTop = parseInt(style.paddingTop.replace(unit, '')); var padTop = parseFloat(style.paddingTop) * zoom;
ptStyle.height = style.paddingTop; ptStyle.height = padTop + unit;
ptStyle.width = style.width; ptStyle.width = widthEl;
ptStyle.top = pos.top + unit; ptStyle.top = pos.top + unit;
ptStyle.left = posLeft + unit; ptStyle.left = posLeft + unit;
var padBot = parseInt(style.paddingBottom.replace(unit, '')); var padBot = parseFloat(style.paddingBottom) * zoom;
pbStyle.height = style.paddingBottom; pbStyle.height = padBot + unit;
pbStyle.width = style.width; pbStyle.width = widthEl;
pbStyle.top = pos.top + pos.height - padBot + unit; pbStyle.top = pos.top + pos.height - padBot + unit;
pbStyle.left = posLeft + unit; pbStyle.left = posLeft + unit;
var padSideH = pos.height - padBot - padTop + unit; var padSideH = pos.height - padBot - padTop + unit;
var padSideT = pos.top + padTop + unit; var padSideT = pos.top + padTop + unit;
plStyle.height = padSideH; plStyle.height = padSideH;
plStyle.width = style.paddingLeft; plStyle.width = parseFloat(style.paddingLeft) * zoom + unit;
plStyle.top = padSideT; plStyle.top = padSideT;
plStyle.left = pos.left + unit; plStyle.left = pos.left + unit;
var padRight = parseInt(style.paddingRight.replace(unit, '')); var padRight = parseFloat(style.paddingRight) * zoom;
prStyle.height = padSideH; prStyle.height = padSideH;
prStyle.width = style.paddingRight; prStyle.width = padRight + unit;
prStyle.top = padSideT; prStyle.top = padSideT;
prStyle.left = pos.left + pos.width - padRight + unit; prStyle.left = pos.left + pos.width - padRight + unit;
}, },

27
src/commands/view/TextComponent.js

@ -1,27 +0,0 @@
import _ from 'underscore';
import Backbone from 'backbone';
var CreateComponent = require('./CreateComponent');
module.exports = _.extend({}, CreateComponent, {
/**
* This event is triggered at the beginning of a draw operation
* @param {Object} component Object component before creation
* @private
* */
beforeDraw(component) {
component.type = 'text';
if (!component.style) component.style = {};
component.style.padding = '10px';
},
/**
* This event is triggered at the end of a draw operation
* @param {Object} model Component model created
* @private
* */
afterDraw(model) {
if (!model || !model.set) return;
model.trigger('focus');
if (this.sender) this.sender.set('active', false);
}
});

4
src/editor/model/Editor.js

@ -608,6 +608,10 @@ module.exports = Backbone.Model.extend({
return this.get('changesCount'); return this.get('changesCount');
}, },
getZoomDecimal() {
return this.get('Canvas').getZoomDecimal();
},
/** /**
* Destroy editor * Destroy editor
*/ */

29
src/styles/scss/_gjs_canvas.scss

@ -13,6 +13,21 @@ $canvasTop: 40px;
left: 0; left: 0;
top: 40px; top: 40px;
&#{gjs-is(grab)},
&#{gjs-is(grabbing)} {
.#{$cv-prefix}canvas__frames {
pointer-events: none;
}
}
&__frames {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.#{$app-prefix}ghost { .#{$app-prefix}ghost {
display: none; display: none;
pointer-events: none; pointer-events: none;
@ -49,13 +64,13 @@ $canvasTop: 40px;
} }
/* This simulate body behaviour */ /* This simulate body behaviour */
> div:first-child { // > div:first-child {
background-color: #fff; // background-color: #fff;
position: relative; // position: relative;
height: 100%; // height: 100%;
overflow: auto; // overflow: auto;
width: 100%; // width: 100%;
} // }
} }
.#{$cv-prefix}canvas * { .#{$cv-prefix}canvas * {

18
src/styles/scss/_gjs_status.scss

@ -0,0 +1,18 @@
$gjs-is-prefix: '.#{$app-prefix}is__';
@function gjs-is($name) {
@return '#{$gjs-is-prefix}#{$name}';
}
#{$gjs-is-prefix} {
&grab,
&grab * {
cursor: grab !important;
}
&grabbing,
&grabbing * {
@include user-select(none);
cursor: grabbing !important;
}
}

2
src/styles/scss/main.scss

@ -50,6 +50,8 @@
$prefix: $app-prefix; $prefix: $app-prefix;
@import "gjs_status";
.#{$prefix} { .#{$prefix} {
@each $cnum, $ccol in (one, $primaryColor), (two, $secondaryColor), (three, $tertiaryColor), (four, $quaternaryColor) { @each $cnum, $ccol in (one, $primaryColor), (two, $secondaryColor), (three, $tertiaryColor), (four, $quaternaryColor) {
&#{$cnum} { &#{$cnum} {

407
src/utils/Dragger.js

@ -1,48 +1,53 @@
import Backbone from 'backbone'; import { bindAll, isFunction } from 'underscore';
const $ = Backbone.$; import { on, off } from 'utils/mixins';
var getBoundingRect = (el, win) => {
var w = win || window;
var rect = el.getBoundingClientRect();
return {
left: rect.left + w.pageXOffset,
top: rect.top + w.pageYOffset,
width: rect.width,
height: rect.height
};
};
module.exports = {
// TODO move to opts
setKey(keys, command) {
//key(keys, command);
},
const options = {
/** /**
* Return element position * Callback on start
* @param {HTMLElement} el * onStart(ev, dragger) {
* @return {Object} * console.log('pointer start', dragger.startPointer, 'position start', dragger.startPosition);
* },
*/ */
getElementRect(el) { onStart: null,
var posFetcher = this.opts.posFetcher || ''; /**
return posFetcher * Callback on drag
? posFetcher(el, { * onDrag(ev, dragger) {
avoidFrameOffset: 1 * console.log('pointer', dragger.currentPointer, 'position', dragger.position, 'delta', dragger.delta);
}) * },
: getBoundingRect(el); */
}, onDrag: null,
/**
* Callback on drag
* onEnd(ev, dragger) {
* console.log('pointer', dragger.currentPointer, 'position', dragger.position, 'delta', dragger.delta);
* },
*/
onEnd: null,
/**
* Indicate a callback where to pass an object with new coordinates
*/
setPosition: null,
/**
* Indicate a callback where to get initial coordinates.
* getPosition: () => {
* ...
* return { x: 10, y: 100 }
* }
*/
getPosition: null
};
module.exports = {
/** /**
* Init the resizer * Init the resizer
* @param {Object} opts * @param {Object} opts
*/ */
init(opts) { init(opts) {
bindAll(this, 'drag', 'stop');
this.opts = options;
this.setOptions(opts); this.setOptions(opts);
this.handleMouseDown = this.handleMouseDown.bind(this); this.delta = { x: 0, y: 0 };
this.drag = this.drag.bind(this); this.startPosition = this.getStartPosition();
this.move = this.move.bind(this);
this.stop = this.stop.bind(this);
this.setKey('up, right, down, left', this.handleKey);
return this; return this;
}, },
@ -50,135 +55,105 @@ module.exports = {
* Update options * Update options
* @param {Object} options * @param {Object} options
*/ */
setOptions(opts) { setOptions(opts = {}) {
this.opts = opts || {}; this.opts = {
...this.opts,
...opts
};
}, },
/** toggleDrag(enable) {
* Focus dragger on the element const docs = this.getDocumentEl();
* @param {HTMLElement} el const method = enable ? 'on' : 'off';
*/ const methods = { on, off };
focus(el) { methods[method](docs, 'mousemove', this.drag);
// Avoid focusing on already focused element methods[method](docs, 'mouseup', this.stop);
if (el && el === this.el) {
return;
}
this.getDocumentEl(el);
this.blur();
this.el = el;
this.handlers = this.opts.dragHandlers || [el];
var elRect = this.getElementRect(el); //<-- TODO have wrong top:left
this.elRect = elRect;
this.startTop = elRect.top;
this.startLeft = elRect.left;
// TODO init snapper
this.getDocumentEl().on('mousedown', this.handleMouseDown);
}, },
/** /**
* Blur from the focused element * Start dragging
* @param {Event} e
*/ */
blur() { start(ev) {
this.getDocumentEl().off('mousedown', this.handleMouseDown); const { onStart } = this.opts;
this.el = null; this.toggleDrag(1);
this.startPointer = this.getPointerPos(ev);
this.startPosition = this.getStartPosition();
isFunction(onStart) && onStart(ev, this);
this.drag(ev);
}, },
/** /**
* Start dragging * Drag event
* @param {Event} e * @param {Event} event
*/ */
start(e) { drag(ev) {
this.startPos = this.getMousePos(e); const { startPointer } = this;
var docs = this.getDocumentEl(); const currentPos = this.getPointerPos(ev);
docs.on('mousemove', this.drag); const delta = {
docs.on('mouseup', this.stop); x: currentPos.x - startPointer.x,
y: currentPos.y - startPointer.y
};
let { lockedAxis } = this;
// Start callback // Lock one axis
var onStart = this.opts.onStart; if (ev.shiftKey) {
if (typeof onStart === 'function') { lockedAxis = !lockedAxis && this.detectAxisLock(delta.x, delta.y);
onStart(e, { } else {
docs, lockedAxis = null;
el: this.el,
start: this.startPos,
elRect: this.elRect
});
} }
this.drag(e); if (lockedAxis === 'x') {
}, delta.x = startPointer.x;
} else if (lockedAxis === 'y') {
delta.y = startPointer.y;
}
/** this.lockedAxis = lockedAxis;
* Stop dragging this.delta = delta;
*/ this.move(delta.x, delta.y);
stop(e) { this.currentPointer = currentPos;
var docs = this.getDocumentEl(); const { onDrag } = this.opts;
docs.off('mousemove', this.drag); isFunction(onDrag) && onDrag(ev, this);
docs.off('mouseup', this.stop);
this.lockedAxis = null;
// Stop callback // In case the mouse button was released outside of the window
var onEnd = this.opts.onEnd; ev.which === 0 && this.stop(ev);
if (typeof onEnd === 'function') {
onEnd(e, {
docs,
delta: this.delta,
end: {
x: this.startLeft + this.delta.x,
y: this.startTop + this.delta.y
}
});
}
}, },
/** /**
* Handle mousedown to check if it's possible to drag * Stop dragging
* @param {Event} e
*/ */
handleMouseDown(e) { stop(ev) {
var el = e.target; const { delta } = this;
if (this.isHandler(el)) { this.toggleDrag();
this.start(e); this.lockedAxis = null;
} this.move(delta.x, delta.y, 1);
const { onEnd } = this.opts;
isFunction(onEnd) && onEnd(ev, this);
}, },
/** /**
* Detects if the clicked element is a valid handler * Move the element
* @param {HTMLElement} el * @param {integer} x
* @return {Boolean} * @param {integer} y
*/ */
isHandler(el) { move: function(x, y, end) {
var handlers = this.handlers; const { el, opts } = this;
const pos = this.startPosition;
for (var n in handlers) { const { setPosition } = opts;
if (handlers[n] === el) return true; const xPos = pos.x + x;
} const yPos = pos.y + y;
this.position = {
x: xPos,
y: yPos,
end
};
return false; isFunction(setPosition) && setPosition(this.position);
},
/** if (el) {
* Handle key press el.style.left = `${xPos}px`;
* @param {Event} e el.style.top = `${yPos}px`;
* @param {Object} handler
*/
handleKey(e, handler) {
switch (handler.shortcut) {
case 'up':
this.move(0, -1);
break;
case 'right':
this.move(1, 0);
break;
case 'down':
this.move(0, 1);
break;
case 'left':
this.move(-1, 0);
break;
} }
}, },
@ -186,15 +161,15 @@ module.exports = {
* Returns documents * Returns documents
*/ */
getDocumentEl(el) { getDocumentEl(el) {
var el = el || this.el; el = el || this.el;
if (!this.$doc) {
var docs = [document]; if (!this.docs) {
if (el) { const docs = [document];
docs.push(el.ownerDocument); el && docs.push(el.ownerDocument);
} this.docs = docs;
this.$doc = $(docs);
} }
return this.$doc;
return this.docs;
}, },
/** /**
@ -202,126 +177,44 @@ module.exports = {
* @param {Event} event * @param {Event} event
* @return {Object} * @return {Object}
*/ */
getMousePos(e) { getPointerPos(ev) {
var mouseFetch = this.opts.mousePosFetcher; const getPos = this.opts.getPointerPosition;
return mouseFetch return getPos
? mouseFetch(e) ? getPos(ev)
: { : {
x: e.clientX, x: ev.clientX,
y: e.clientY y: ev.clientY
}; };
}, },
/** getStartPosition() {
* Drag event const { el, opts } = this;
* @param {Event} event const getPos = opts.getPosition;
*/ let result = { x: 0, y: 0 };
drag(e) {
var lockedAxis = this.lockedAxis;
var currentPos = this.getMousePos(e);
var delta = {
x: currentPos.x - this.startPos.x,
y: currentPos.y - this.startPos.y
};
// Lock one axis
if (e.shiftKey) {
if (!lockedAxis) {
var relX = delta.x;
var relY = delta.y;
var absX = Math.abs(relX);
var absY = Math.abs(relY);
// Vertical or Horizontal lock
if (relY >= absX || relY <= -absX) {
lockedAxis = 'x';
} else if (relX > absY || relX < -absY) {
lockedAxis = 'y';
}
}
} else {
lockedAxis = null;
}
if (lockedAxis === 'x') {
delta.x = this.startPos.x;
}
if (lockedAxis === 'y') { if (isFunction(getPos)) {
delta.y = this.startPos.y; result = getPos();
} else if (el) {
result = {
x: parseFloat(el.style.left),
y: parseFloat(el.style.top)
};
} }
this.lockedAxis = lockedAxis; return result;
this.delta = delta;
this.move(delta.x, delta.y);
// Drag callback
const onDrag = this.opts.onDrag;
if (typeof onDrag === 'function') {
onDrag(e, {
delta,
current: {
x: this.startLeft + delta.x,
y: this.startTop + delta.y
},
lockedAxis
});
}
// In case the mouse button was released outside of the window
if (e.which === 0) {
this.stop(e);
}
}, },
/** detectAxisLock(x, y) {
* Move the element const relX = x;
* @param {integer} x const relY = y;
* @param {integer} y const absX = Math.abs(relX);
*/ const absY = Math.abs(relY);
move: function(x, y) {
this.moveX(x);
this.moveY(y);
},
/** // Vertical or Horizontal lock
* Move in x direction if (relY >= absX || relY <= -absX) {
* @param {integer} x return 'x';
*/ } else if (relX > absY || relX < -absY) {
moveX(x) { return 'y';
var el = this.el;
var opts = this.opts;
var xPos = this.startLeft + x;
const setX = this.opts.setX;
if (typeof setX === 'function') {
setX(xPos, {
el,
start: this.startLeft,
delta: x
});
} else {
el.style.left = xPos + 'px';
}
},
/**
* Move in y direction
* @param {integer} y
*/
moveY(y) {
var el = this.el;
var opts = this.opts;
var yPos = this.startTop + y;
const setY = this.opts.setY;
if (typeof setY === 'function') {
setY(yPos, {
el,
start: this.startTop,
delta: y
});
} else {
el.style.top = yPos + 'px';
} }
} }
}; };

27
src/utils/Sorter.js

@ -1,5 +1,5 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
import { isString, isFunction, isArray } from 'underscore'; import { isString, isFunction, isArray, result } from 'underscore';
import { on, off, matches, getElement } from 'utils/mixins'; import { on, off, matches, getElement } from 'utils/mixins';
const $ = Backbone.$; const $ = Backbone.$;
@ -50,6 +50,7 @@ module.exports = Backbone.View.extend({
this.dragHelper = null; this.dragHelper = null;
this.canvasRelative = o.canvasRelative || 0; this.canvasRelative = o.canvasRelative || 0;
this.selectOnEnd = !o.avoidSelectOnEnd; this.selectOnEnd = !o.avoidSelectOnEnd;
this.scale = o.scale;
if (this.em && this.em.on) { if (this.em && this.em.on) {
this.em.on('change:canvasOffset', this.udpateOffset); this.em.on('change:canvasOffset', this.udpateOffset);
@ -57,6 +58,10 @@ module.exports = Backbone.View.extend({
} }
}, },
getScale() {
return result(this, scale) || 1;
},
getContainerEl() { getContainerEl() {
if (!this.el) { if (!this.el) {
var el = this.opt.container; var el = this.opt.container;
@ -702,19 +707,17 @@ module.exports = Backbone.View.extend({
* @return {Array<number>} * @return {Array<number>}
*/ */
getDim(el) { getDim(el) {
const { em, canvasRelative } = this;
var top, left, height, width; var top, left, height, width;
if (this.canvasRelative && this.em) { if (canvasRelative && em) {
var pos = this.em.get('Canvas').getElementPos(el); const canvas = em.get('Canvas');
var styles = window.getComputedStyle(el); const pos = canvas.getElementPos(el);
var marginTop = parseFloat(styles['marginTop']); const elOffsets = canvas.getElementOffsets(el);
var marginBottom = parseFloat(styles['marginBottom']); top = pos.top - elOffsets.marginTop;
var marginRight = parseFloat(styles['marginRight']); left = pos.left - elOffsets.marginLeft;
var marginLeft = parseFloat(styles['marginLeft']); height = pos.height + elOffsets.marginTop + elOffsets.marginBottom;
top = pos.top - marginTop; width = pos.width + elOffsets.marginLeft + elOffsets.marginRight;
left = pos.left - marginLeft;
height = pos.height + marginTop + marginBottom;
width = pos.width + marginLeft + marginRight;
} else { } else {
var o = this.offset(el); var o = this.offset(el);
top = this.relative top = this.relative

10
src/utils/mixins.js

@ -129,6 +129,14 @@ const getModel = (el, $) => {
const getPointerEvent = ev => const getPointerEvent = ev =>
ev.touches && ev.touches[0] ? ev.touches[0] : ev; ev.touches && ev.touches[0] ? ev.touches[0] : ev;
/**
* Get cross-browser keycode
* @param {Event} ev
* @return {Number}
*/
const getKeyCode = ev => ev.which || ev.keyCode;
const getKeyChar = ev => String.fromCharCode(getKeyCode(ev));
export { export {
on, on,
off, off,
@ -137,6 +145,8 @@ export {
matches, matches,
getModel, getModel,
camelCase, camelCase,
getKeyCode,
getKeyChar,
getElement, getElement,
shallowDiff, shallowDiff,
normalizeFloat, normalizeFloat,

Loading…
Cancel
Save