diff --git a/src/canvas/index.js b/src/canvas/index.js index d23eaef14..b936f2b7e 100644 --- a/src/canvas/index.js +++ b/src/canvas/index.js @@ -1,4 +1,4 @@ -import { on, off, hasDnd } from 'utils/mixins'; +import { on, off, hasDnd, getElement } from 'utils/mixins'; import Droppable from 'utils/Droppable'; module.exports = () => { @@ -86,6 +86,14 @@ module.exports = () => { return CanvasView.frame.el; }, + /** + * Returns the frame document + * @return {HTMLElement} + */ + getDocument() { + return this.getFrameEl().contentDocument; + }, + /** * Returns body element of the frame * @return {HTMLElement} @@ -342,6 +350,29 @@ module.exports = () => { return this.getFrameEl().contentDocument.activeElement.tagName !== 'BODY'; }, + /** + * Scroll canvas to the element if it's not visible. The scrolling is + * executed via `scrollIntoView` API and options of this method are + * passed to it. For instance, you can scroll smoothly with + * `{ behavior: 'smooth' }`. You can also force the scroll + * @param {HTMLElement|Component} el + * @param {Object} [opts={}] Options, same as options for `scrollIntoView` + * @example + * const selected = editor.getSelected(); + * // Scroll smoothly (this behavior can be polyfilled) + * cv.scrollTo(selected, { behavior: 'smooth' }); + * // Force the scroll, even if the element is alredy visible + * cv.scrollTo(selected, { force: true }); + */ + scrollTo(el, opts = {}) { + const elem = getElement(el); + const cv = this.getCanvasView(); + + if (!cv.isElInViewport(elem) || opts.force) { + elem.scrollIntoView(opts); + } + }, + /** * Start autoscroll */ diff --git a/src/canvas/view/CanvasView.js b/src/canvas/view/CanvasView.js index 02e352aa0..9dbf291f9 100644 --- a/src/canvas/view/CanvasView.js +++ b/src/canvas/view/CanvasView.js @@ -1,4 +1,4 @@ -import { on, off } from 'utils/mixins'; +import { on, off, getElement } from 'utils/mixins'; const FrameView = require('./FrameView'); const $ = Backbone.$; @@ -23,7 +23,7 @@ module.exports = Backbone.View.extend({ * @return {Boolean} */ isElInViewport(el) { - const rect = el.getBoundingClientRect(); + const rect = getElement(el).getBoundingClientRect(); const frameRect = this.getFrameOffset(1); const rTop = rect.top; const rLeft = rect.left; diff --git a/src/utils/mixins.js b/src/utils/mixins.js index 9d56d78e6..46b17dfed 100644 --- a/src/utils/mixins.js +++ b/src/utils/mixins.js @@ -1,5 +1,7 @@ -import { omit, keys, isUndefined } from 'underscore'; +import Backbone from 'backbone'; +import { omit, keys, isUndefined, isElement } from 'underscore'; +const $ = Backbone.$; const elProt = window.Element.prototype; const matches = elProt.matches || @@ -97,6 +99,19 @@ const hasDnd = em => { ); }; +/** + * Ensure to fetch the element from the input argument + * @param {HTMLElement|Component} el Component or HTML element + * @return {HTMLElement} + */ +const getElement = el => { + if (isElement(el)) { + return el; + } else if (el.getEl) { + return el.getEl(); + } +}; + export { on, off, @@ -104,6 +119,7 @@ export { upFirst, matches, camelCase, + getElement, shallowDiff, normalizeFloat, getUnitFromValue