diff --git a/core/bump_objects.js b/core/bump_objects.js
index efac96bf4..4c53356dc 100644
--- a/core/bump_objects.js
+++ b/core/bump_objects.js
@@ -12,11 +12,17 @@
goog.module('Blockly.bumpObjects');
+/* eslint-disable-next-line no-unused-vars */
+const BlockSvg = goog.requireType('Blockly.BlockSvg');
+/* eslint-disable-next-line no-unused-vars */
+const Events = goog.require('Blockly.Events');
/* eslint-disable-next-line no-unused-vars */
const IBoundedElement = goog.requireType('Blockly.IBoundedElement');
/* eslint-disable-next-line no-unused-vars */
const MetricsManager = goog.requireType('Blockly.MetricsManager');
/* eslint-disable-next-line no-unused-vars */
+const WorkspaceCommentSvg = goog.requireType('Blockly.WorkspaceCommentSvg');
+/* eslint-disable-next-line no-unused-vars */
const WorkspaceSvg = goog.requireType('Blockly.WorkspaceSvg');
const mathUtils = goog.require('Blockly.utils.math');
@@ -71,3 +77,95 @@ const bumpObjectIntoBounds = function(workspace, scrollMetrics, object) {
return false;
};
exports.bumpIntoBounds = bumpObjectIntoBounds;
+
+/**
+ * Creates a handler for bumping objects when they cross fixed bounds.
+ * @param {!WorkspaceSvg} workspace The workspace to handle.
+ * @return {function(Events.Abstract)} The event handler.
+ */
+const bumpIntoBoundsHandler = function(workspace) {
+ return function(e) {
+ const metricsManager = workspace.getMetricsManager();
+ if (!metricsManager.hasFixedEdges() || workspace.isDragging()) {
+ return;
+ }
+
+ if (Events.BUMP_EVENTS.indexOf(e.type) !== -1) {
+ const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
+
+ // Triggered by move/create event
+ const object = extractObjectFromEvent(workspace, e);
+ if (!object) {
+ return;
+ }
+ // Handle undo.
+ const oldGroup = Events.getGroup();
+ Events.setGroup(e.group);
+
+ const wasBumped = bumpObjectIntoBounds(
+ workspace, scrollMetricsInWsCoords,
+ /** @type {!IBoundedElement} */ (object));
+
+ if (wasBumped && !e.group) {
+ console.warn(
+ 'Moved object in bounds but there was no' +
+ ' event group. This may break undo.');
+ }
+ if (oldGroup !== null) {
+ Events.setGroup(oldGroup);
+ }
+ } else if (e.type === Events.VIEWPORT_CHANGE) {
+ const viewportEvent = /** @type {!Events.ViewportChange} */ (e);
+ if (viewportEvent.scale > viewportEvent.oldScale) {
+ bumpTopObjectsIntoBounds(workspace);
+ }
+ }
+ };
+};
+exports.bumpIntoBoundsHandler = bumpIntoBoundsHandler;
+
+/**
+ * Extracts the object from the given event.
+ * @param {!WorkspaceSvg} workspace The workspace the event originated
+ * from.
+ * @param {!Events.BumpEvent} e An event containing an object.
+ * @return {?BlockSvg|?WorkspaceCommentSvg} The extracted
+ * object.
+ */
+const extractObjectFromEvent = function(workspace, e) {
+ let object = null;
+ switch (e.type) {
+ case Events.BLOCK_CREATE:
+ case Events.BLOCK_MOVE:
+ object = workspace.getBlockById(e.blockId);
+ if (object) {
+ object = object.getRootBlock();
+ }
+ break;
+ case Events.COMMENT_CREATE:
+ case Events.COMMENT_MOVE:
+ object = (
+ /** @type {?WorkspaceCommentSvg} */
+ (workspace.getCommentById(e.commentId)));
+ break;
+ }
+ return object;
+};
+
+/**
+ * Bumps the top objects in the given workspace into bounds.
+ * @param {!WorkspaceSvg} workspace The workspace.
+ */
+const bumpTopObjectsIntoBounds = function(workspace) {
+ const metricsManager = workspace.getMetricsManager();
+ if (!metricsManager.hasFixedEdges() || workspace.isDragging()) {
+ return;
+ }
+
+ const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
+ const topBlocks = workspace.getTopBoundedElements();
+ for (let i = 0, block; (block = topBlocks[i]); i++) {
+ bumpObjectIntoBounds(workspace, scrollMetricsInWsCoords, block);
+ }
+};
+exports.bumpTopObjectsIntoBounds = bumpTopObjectsIntoBounds;
diff --git a/core/inject.js b/core/inject.js
index 663c015be..3b807e490 100644
--- a/core/inject.js
+++ b/core/inject.js
@@ -10,81 +10,79 @@
*/
'use strict';
-goog.provide('Blockly.inject');
+goog.module('Blockly.inject');
+goog.module.declareLegacyNamespace();
-goog.require('Blockly.BlockDragSurfaceSvg');
-goog.require('Blockly.browserEvents');
-goog.require('Blockly.bumpObjects');
-goog.require('Blockly.common');
-goog.require('Blockly.Css');
-goog.require('Blockly.DropDownDiv');
-goog.require('Blockly.Events');
-goog.require('Blockly.Grid');
-goog.require('Blockly.Msg');
-goog.require('Blockly.Options');
-goog.require('Blockly.ScrollbarPair');
-goog.require('Blockly.Touch');
-goog.require('Blockly.Tooltip');
-goog.require('Blockly.utils');
-goog.require('Blockly.utils.aria');
-goog.require('Blockly.utils.dom');
-goog.require('Blockly.utils.Svg');
-goog.require('Blockly.utils.userAgent');
-goog.require('Blockly.Workspace');
-goog.require('Blockly.WorkspaceDragSurfaceSvg');
-goog.require('Blockly.WorkspaceSvg');
-goog.require('Blockly.WidgetDiv');
-
-goog.requireType('Blockly.BlocklyOptions');
-goog.requireType('Blockly.BlockSvg');
-goog.requireType('Blockly.WorkspaceCommentSvg');
+const BlockDragSurfaceSvg = goog.require('Blockly.BlockDragSurfaceSvg');
+/* eslint-disable-next-line no-unused-vars */
+const BlocklyOptions = goog.requireType('Blockly.BlocklyOptions');
+const Css = goog.require('Blockly.Css');
+const DropDownDiv = goog.require('Blockly.DropDownDiv');
+const Grid = goog.require('Blockly.Grid');
+const Msg = goog.require('Blockly.Msg');
+const Options = goog.require('Blockly.Options');
+const ScrollbarPair = goog.require('Blockly.ScrollbarPair');
+const Touch = goog.require('Blockly.Touch');
+const Tooltip = goog.require('Blockly.Tooltip');
+const Svg = goog.require('Blockly.utils.Svg');
+const Workspace = goog.require('Blockly.Workspace');
+const WorkspaceDragSurfaceSvg = goog.require('Blockly.WorkspaceDragSurfaceSvg');
+const WorkspaceSvg = goog.require('Blockly.WorkspaceSvg');
+const WidgetDiv = goog.require('Blockly.WidgetDiv');
+const aria = goog.require('Blockly.utils.aria');
+const browserEvents = goog.require('Blockly.browserEvents');
+const bumpObjects = goog.require('Blockly.bumpObjects');
+const common = goog.require('Blockly.common');
+const dom = goog.require('Blockly.utils.dom');
+const userAgent = goog.require('Blockly.utils.userAgent');
+const utils = goog.require('Blockly.utils');
/**
* Inject a Blockly editor into the specified container element (usually a div).
* @param {Element|string} container Containing element, or its ID,
* or a CSS selector.
- * @param {Blockly.BlocklyOptions=} opt_options Optional dictionary of options.
- * @return {!Blockly.WorkspaceSvg} Newly created main workspace.
+ * @param {BlocklyOptions=} opt_options Optional dictionary of options.
+ * @return {!WorkspaceSvg} Newly created main workspace.
*/
-Blockly.inject = function(container, opt_options) {
+const inject = function(container, opt_options) {
if (typeof container == 'string') {
- container = document.getElementById(container) ||
- document.querySelector(container);
+ container =
+ document.getElementById(container) || document.querySelector(container);
}
// Verify that the container is in document.
- if (!container || !Blockly.utils.dom.containsNode(document, container)) {
+ if (!container || !dom.containsNode(document, container)) {
throw Error('Error: container is not in current document.');
}
- var options = new Blockly.Options(opt_options ||
- (/** @type {!Blockly.BlocklyOptions} */ ({})));
- var subContainer = document.createElement('div');
+ const options =
+ new Options(opt_options || (/** @type {!BlocklyOptions} */ ({})));
+ const subContainer = document.createElement('div');
subContainer.className = 'injectionDiv';
subContainer.tabIndex = 0;
- Blockly.utils.aria.setState(subContainer,
- Blockly.utils.aria.State.LABEL, Blockly.Msg['WORKSPACE_ARIA_LABEL']);
+ aria.setState(subContainer, aria.State.LABEL, Msg['WORKSPACE_ARIA_LABEL']);
container.appendChild(subContainer);
- var svg = Blockly.createDom_(subContainer, options);
+ const svg = createDom(subContainer, options);
// Create surfaces for dragging things. These are optimizations
// so that the browser does not repaint during the drag.
- var blockDragSurface = new Blockly.BlockDragSurfaceSvg(subContainer);
+ const blockDragSurface = new BlockDragSurfaceSvg(subContainer);
- var workspaceDragSurface = new Blockly.WorkspaceDragSurfaceSvg(subContainer);
+ const workspaceDragSurface = new WorkspaceDragSurfaceSvg(subContainer);
- var workspace = Blockly.createMainWorkspace_(svg, options, blockDragSurface,
- workspaceDragSurface);
+ const workspace =
+ createMainWorkspace(svg, options, blockDragSurface, workspaceDragSurface);
- Blockly.init_(workspace);
+ init(workspace);
- // Keep focus on the first workspace so entering keyboard navigation looks correct.
- Blockly.common.setMainWorkspace(workspace);
+ // Keep focus on the first workspace so entering keyboard navigation looks
+ // correct.
+ common.setMainWorkspace(workspace);
Blockly.svgResize(workspace);
subContainer.addEventListener('focusin', function() {
- Blockly.common.setMainWorkspace(workspace);
+ common.setMainWorkspace(workspace);
});
return workspace;
@@ -93,18 +91,17 @@ Blockly.inject = function(container, opt_options) {
/**
* Create the SVG image.
* @param {!Element} container Containing element.
- * @param {!Blockly.Options} options Dictionary of options.
+ * @param {!Options} options Dictionary of options.
* @return {!Element} Newly created SVG image.
- * @private
*/
-Blockly.createDom_ = function(container, options) {
+const createDom = function(container, options) {
// Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying
// out content in RTL mode. Therefore Blockly forces the use of LTR,
// then manually positions content in RTL as needed.
container.setAttribute('dir', 'LTR');
// Load CSS.
- Blockly.Css.inject(options.hasCss, options.pathToMedia);
+ Css.inject(options.hasCss, options.pathToMedia);
// Build the SVG DOM.
/*
@@ -117,61 +114,61 @@ Blockly.createDom_ = function(container, options) {
...
*/
- var svg = Blockly.utils.dom.createSvgElement(
- Blockly.utils.Svg.SVG, {
- 'xmlns': Blockly.utils.dom.SVG_NS,
- 'xmlns:html': Blockly.utils.dom.HTML_NS,
- 'xmlns:xlink': Blockly.utils.dom.XLINK_NS,
+ const svg = dom.createSvgElement(
+ Svg.SVG, {
+ 'xmlns': dom.SVG_NS,
+ 'xmlns:html': dom.HTML_NS,
+ 'xmlns:xlink': dom.XLINK_NS,
'version': '1.1',
'class': 'blocklySvg',
'tabindex': '0'
- }, container);
+ },
+ container);
/*
... filters go here ...
*/
- var defs = Blockly.utils.dom.createSvgElement(
- Blockly.utils.Svg.DEFS, {}, svg);
+ const defs = dom.createSvgElement(Svg.DEFS, {}, svg);
// Each filter/pattern needs a unique ID for the case of multiple Blockly
// instances on a page. Browser behaviour becomes undefined otherwise.
// https://neil.fraser.name/news/2015/11/01/
- var rnd = String(Math.random()).substring(2);
+ const rnd = String(Math.random()).substring(2);
- options.gridPattern = Blockly.Grid.createDom(rnd, options.gridOptions, defs);
+ options.gridPattern = Grid.createDom(rnd, options.gridOptions, defs);
return svg;
};
/**
* Create a main workspace and add it to the SVG.
* @param {!Element} svg SVG element with pattern defined.
- * @param {!Blockly.Options} options Dictionary of options.
- * @param {!Blockly.BlockDragSurfaceSvg} blockDragSurface Drag surface SVG
+ * @param {!Options} options Dictionary of options.
+ * @param {!BlockDragSurfaceSvg} blockDragSurface Drag surface SVG
* for the blocks.
- * @param {!Blockly.WorkspaceDragSurfaceSvg} workspaceDragSurface Drag surface
+ * @param {!WorkspaceDragSurfaceSvg} workspaceDragSurface Drag surface
* SVG for the workspace.
- * @return {!Blockly.WorkspaceSvg} Newly created main workspace.
- * @private
+ * @return {!WorkspaceSvg} Newly created main workspace.
*/
-Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface,
- workspaceDragSurface) {
+const createMainWorkspace = function(
+ svg, options, blockDragSurface, workspaceDragSurface) {
options.parentWorkspace = null;
- var mainWorkspace =
- new Blockly.WorkspaceSvg(options, blockDragSurface, workspaceDragSurface);
- var wsOptions = mainWorkspace.options;
+ const mainWorkspace =
+ new WorkspaceSvg(options, blockDragSurface, workspaceDragSurface);
+ const wsOptions = mainWorkspace.options;
mainWorkspace.scale = wsOptions.zoomOptions.startScale;
svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
// Set the theme name and renderer name onto the injection div.
- Blockly.utils.dom.addClass(mainWorkspace.getInjectionDiv(),
+ dom.addClass(
+ mainWorkspace.getInjectionDiv(),
mainWorkspace.getRenderer().getClassName());
- Blockly.utils.dom.addClass(mainWorkspace.getInjectionDiv(),
- mainWorkspace.getTheme().getClassName());
+ dom.addClass(
+ mainWorkspace.getInjectionDiv(), mainWorkspace.getTheme().getClassName());
if (!wsOptions.hasCategories && wsOptions.languageTree) {
// Add flyout as an