diff --git a/core/dropdowndiv.js b/core/dropdowndiv.js index 5ebb22a5e..070fb89bf 100644 --- a/core/dropdowndiv.js +++ b/core/dropdowndiv.js @@ -13,18 +13,22 @@ 'use strict'; -goog.provide('Blockly.DropDownDiv'); +goog.module('Blockly.DropDownDiv'); +goog.module.declareLegacyNamespace(); -goog.require('Blockly.common'); -goog.require('Blockly.utils.dom'); -goog.require('Blockly.utils.math'); -goog.require('Blockly.utils.Rect'); -goog.require('Blockly.utils.style'); - -goog.requireType('Blockly.BlockSvg'); -goog.requireType('Blockly.Field'); -goog.requireType('Blockly.utils.Size'); -goog.requireType('Blockly.WorkspaceSvg'); +/* eslint-disable-next-line no-unused-vars */ +const BlockSvg = goog.requireType('Blockly.BlockSvg'); +const Rect = goog.require('Blockly.utils.Rect'); +/* eslint-disable-next-line no-unused-vars */ +const Field = goog.requireType('Blockly.Field'); +/* eslint-disable-next-line no-unused-vars */ +const Size = goog.requireType('Blockly.utils.Size'); +/* eslint-disable-next-line no-unused-vars */ +const WorkspaceSvg = goog.requireType('Blockly.WorkspaceSvg'); +const common = goog.require('Blockly.common'); +const dom = goog.require('Blockly.utils.dom'); +const math = goog.require('Blockly.utils.math'); +const style = goog.require('Blockly.utils.style'); /** @@ -32,30 +36,7 @@ goog.requireType('Blockly.WorkspaceSvg'); * @constructor * @package */ -Blockly.DropDownDiv = function() { -}; - -/** - * Drop-downs will appear within the bounds of this element if possible. - * Set in Blockly.DropDownDiv.setBoundsElement. - * @type {Element} - * @private - */ -Blockly.DropDownDiv.boundsElement_ = null; - -/** - * The object currently using the drop-down. - * @type {Object} - * @private - */ -Blockly.DropDownDiv.owner_ = null; - -/** - * Whether the dropdown was positioned to a field or the source block. - * @type {?boolean} - * @private - */ -Blockly.DropDownDiv.positionToField_ = null; +const DropDownDiv = function() {}; /** * Arrow size in px. Should match the value in CSS @@ -63,7 +44,7 @@ Blockly.DropDownDiv.positionToField_ = null; * @type {number} * @const */ -Blockly.DropDownDiv.ARROW_SIZE = 16; +DropDownDiv.ARROW_SIZE = 16; /** * Drop-down border size in px. Should match the value in CSS (need to position @@ -71,7 +52,7 @@ Blockly.DropDownDiv.ARROW_SIZE = 16; * @type {number} * @const */ -Blockly.DropDownDiv.BORDER_SIZE = 1; +DropDownDiv.BORDER_SIZE = 1; /** * Amount the arrow must be kept away from the edges of the main drop-down div, @@ -79,21 +60,21 @@ Blockly.DropDownDiv.BORDER_SIZE = 1; * @type {number} * @const */ -Blockly.DropDownDiv.ARROW_HORIZONTAL_PADDING = 12; +DropDownDiv.ARROW_HORIZONTAL_PADDING = 12; /** * Amount drop-downs should be padded away from the source, in px. * @type {number} * @const */ -Blockly.DropDownDiv.PADDING_Y = 16; +DropDownDiv.PADDING_Y = 16; /** * Length of animations in seconds. * @type {number} * @const */ -Blockly.DropDownDiv.ANIMATION_TIME = 0.25; +DropDownDiv.ANIMATION_TIME = 0.25; /** * Timer for animation out, to be cleared if we need to immediately hide @@ -101,28 +82,71 @@ Blockly.DropDownDiv.ANIMATION_TIME = 0.25; * @type {?number} * @private */ -Blockly.DropDownDiv.animateOutTimer_ = null; +DropDownDiv.animateOutTimer_ = null; /** * Callback for when the drop-down is hidden. * @type {?Function} * @private */ -Blockly.DropDownDiv.onHide_ = null; +DropDownDiv.onHide_ = null; /** * A class name representing the current owner's workspace renderer. * @type {string} * @private */ -Blockly.DropDownDiv.rendererClassName_ = ''; +DropDownDiv.rendererClassName_ = ''; /** * A class name representing the current owner's workspace theme. * @type {string} * @private */ -Blockly.DropDownDiv.themeClassName_ = ''; +DropDownDiv.themeClassName_ = ''; + +/** + * The content element. + * @type {!Element} + * @private + */ +DropDownDiv.DIV_; + +/** + * The content element. + * @type {!Element} + * @private + */ +DropDownDiv.content_; + +/** + * The arrow element. + * @type {!Element} + * @private + */ +DropDownDiv.arrow_; + +/** + * Drop-downs will appear within the bounds of this element if possible. + * Set in DropDownDiv.setBoundsElement. + * @type {?Element} + * @private + */ +DropDownDiv.boundsElement_ = null; + +/** + * The object currently using the drop-down. + * @type {?Object} + * @private + */ +DropDownDiv.owner_ = null; + +/** + * Whether the dropdown was positioned to a field or the source block. + * @type {?boolean} + * @private + */ +DropDownDiv.positionToField_ = null; /** * Dropdown bounds info object used to encapsulate sizing information about a @@ -136,7 +160,7 @@ Blockly.DropDownDiv.themeClassName_ = ''; * height:number * }} */ -Blockly.DropDownDiv.BoundsInfo; +DropDownDiv.BoundsInfo; /** * Dropdown position metrics. @@ -151,87 +175,73 @@ Blockly.DropDownDiv.BoundsInfo; * arrowVisible:boolean * }} */ -Blockly.DropDownDiv.PositionMetrics; +DropDownDiv.PositionMetrics; /** * Create and insert the DOM element for this div. * @package */ -Blockly.DropDownDiv.createDom = function() { - if (Blockly.DropDownDiv.DIV_) { +DropDownDiv.createDom = function() { + if (DropDownDiv.DIV_) { return; // Already created. } - var div = document.createElement('div'); - div.className = 'blocklyDropDownDiv'; - var container = Blockly.common.getParentContainer() || document.body; - container.appendChild(div); - /** - * The div element. - * @type {!Element} - * @private - */ - Blockly.DropDownDiv.DIV_ = div; + const containerDiv = document.createElement('div'); + containerDiv.className = 'blocklyDropDownDiv'; + const parentDiv = Blockly.parentContainer || document.body; + parentDiv.appendChild(containerDiv); - var content = document.createElement('div'); + DropDownDiv.DIV_ = containerDiv; + + const content = document.createElement('div'); content.className = 'blocklyDropDownContent'; - div.appendChild(content); - /** - * The content element. - * @type {!Element} - * @private - */ - Blockly.DropDownDiv.content_ = content; + DropDownDiv.DIV_.appendChild(content); + DropDownDiv.content_ = content; - var arrow = document.createElement('div'); + const arrow = document.createElement('div'); arrow.className = 'blocklyDropDownArrow'; - div.appendChild(arrow); - /** - * The arrow element. - * @type {!Element} - * @private - */ - Blockly.DropDownDiv.arrow_ = arrow; + DropDownDiv.DIV_.appendChild(arrow); + DropDownDiv.arrow_ = arrow; - Blockly.DropDownDiv.DIV_.style.opacity = 0; + DropDownDiv.DIV_.style.opacity = 0; // Transition animation for transform: translate() and opacity. - Blockly.DropDownDiv.DIV_.style.transition = 'transform ' + - Blockly.DropDownDiv.ANIMATION_TIME + 's, ' + - 'opacity ' + Blockly.DropDownDiv.ANIMATION_TIME + 's'; + DropDownDiv.DIV_.style.transition = 'transform ' + + DropDownDiv.ANIMATION_TIME + 's, ' + + 'opacity ' + DropDownDiv.ANIMATION_TIME + 's'; // Handle focusin/out events to add a visual indicator when // a child is focused or blurred. - div.addEventListener('focusin', function() { - Blockly.utils.dom.addClass(div, 'blocklyFocused'); + DropDownDiv.DIV_.addEventListener('focusin', function() { + dom.addClass(DropDownDiv.DIV_, 'blocklyFocused'); }); - div.addEventListener('focusout', function() { - Blockly.utils.dom.removeClass(div, 'blocklyFocused'); + DropDownDiv.DIV_.addEventListener('focusout', function() { + dom.removeClass(DropDownDiv.DIV_, 'blocklyFocused'); }); }; /** * Set an element to maintain bounds within. Drop-downs will appear * within the box of this element if possible. - * @param {Element} boundsElement Element to bind drop-down to. + * @param {?Element} boundsElement Element to bind drop-down to. */ -Blockly.DropDownDiv.setBoundsElement = function(boundsElement) { - Blockly.DropDownDiv.boundsElement_ = boundsElement; +DropDownDiv.setBoundsElement = function(boundsElement) { + DropDownDiv.boundsElement_ = boundsElement; }; /** * Provide the div for inserting content into the drop-down. * @return {!Element} Div to populate with content. */ -Blockly.DropDownDiv.getContentDiv = function() { - return Blockly.DropDownDiv.content_; +DropDownDiv.getContentDiv = function() { + return DropDownDiv.content_; }; /** * Clear the content of the drop-down. */ -Blockly.DropDownDiv.clearContent = function() { - Blockly.DropDownDiv.content_.textContent = ''; - Blockly.DropDownDiv.content_.style.width = ''; +DropDownDiv.clearContent = function() { + DropDownDiv.content_.textContent = ''; + DropDownDiv.content_.style.width = ''; }; /** @@ -239,9 +249,9 @@ Blockly.DropDownDiv.clearContent = function() { * @param {string} backgroundColour Any CSS colour for the background. * @param {string} borderColour Any CSS colour for the border. */ -Blockly.DropDownDiv.setColour = function(backgroundColour, borderColour) { - Blockly.DropDownDiv.DIV_.style.backgroundColor = backgroundColour; - Blockly.DropDownDiv.DIV_.style.borderColor = borderColour; +DropDownDiv.setColour = function(backgroundColour, borderColour) { + DropDownDiv.DIV_.style.backgroundColor = backgroundColour; + DropDownDiv.DIV_.style.borderColor = borderColour; }; /** @@ -249,19 +259,18 @@ Blockly.DropDownDiv.setColour = function(backgroundColour, borderColour) { * by a particular block. The primary position will be below the block, * and the secondary position above the block. Drop-down will be * constrained to the block's workspace. - * @param {!Blockly.Field} field The field showing the drop-down. - * @param {!Blockly.BlockSvg} block Block to position the drop-down around. + * @param {!Field} field The field showing the drop-down. + * @param {!BlockSvg} block Block to position the drop-down around. * @param {Function=} opt_onHide Optional callback for when the drop-down is * hidden. * @param {number=} opt_secondaryYOffset Optional Y offset for above-block * positioning. * @return {boolean} True if the menu rendered below block; false if above. */ -Blockly.DropDownDiv.showPositionedByBlock = function(field, block, - opt_onHide, opt_secondaryYOffset) { - return Blockly.DropDownDiv.showPositionedByRect_( - Blockly.DropDownDiv.getScaledBboxOfBlock_(block), - field, opt_onHide, opt_secondaryYOffset); +DropDownDiv.showPositionedByBlock = function( + field, block, opt_onHide, opt_secondaryYOffset) { + return showPositionedByRect( + getScaledBboxOfBlock(block), field, opt_onHide, opt_secondaryYOffset); }; /** @@ -269,48 +278,45 @@ Blockly.DropDownDiv.showPositionedByBlock = function(field, block, * by a particular field. The primary position will be below the field, * and the secondary position above the field. Drop-down will be * constrained to the block's workspace. - * @param {!Blockly.Field} field The field to position the dropdown against. + * @param {!Field} field The field to position the dropdown against. * @param {Function=} opt_onHide Optional callback for when the drop-down is * hidden. * @param {number=} opt_secondaryYOffset Optional Y offset for above-block * positioning. * @return {boolean} True if the menu rendered below block; false if above. */ -Blockly.DropDownDiv.showPositionedByField = function(field, - opt_onHide, opt_secondaryYOffset) { - Blockly.DropDownDiv.positionToField_ = true; - return Blockly.DropDownDiv.showPositionedByRect_( - Blockly.DropDownDiv.getScaledBboxOfField_(field), - field, opt_onHide, opt_secondaryYOffset); +DropDownDiv.showPositionedByField = function( + field, opt_onHide, opt_secondaryYOffset) { + DropDownDiv.positionToField_ = true; + return showPositionedByRect( + getScaledBboxOfField(field), field, opt_onHide, opt_secondaryYOffset); }; +const internal = {}; + /** * Get the scaled bounding box of a block. - * @param {!Blockly.BlockSvg} block The block. - * @return {!Blockly.utils.Rect} The scaled bounding box of the block. - * @private + * @param {!BlockSvg} block The block. + * @return {!Rect} The scaled bounding box of the block. */ -Blockly.DropDownDiv.getScaledBboxOfBlock_ = function(block) { - var blockSvg = block.getSvgRoot(); - var bBox = blockSvg.getBBox(); - var scale = block.workspace.scale; - var scaledHeight = bBox.height * scale; - var scaledWidth = bBox.width * scale; - var xy = Blockly.utils.style.getPageOffset(blockSvg); - return new Blockly.utils.Rect( - xy.y, xy.y + scaledHeight, xy.x, xy.x + scaledWidth); +const getScaledBboxOfBlock = function(block) { + const blockSvg = block.getSvgRoot(); + const bBox = blockSvg.getBBox(); + const scale = block.workspace.scale; + const scaledHeight = bBox.height * scale; + const scaledWidth = bBox.width * scale; + const xy = style.getPageOffset(blockSvg); + return new Rect(xy.y, xy.y + scaledHeight, xy.x, xy.x + scaledWidth); }; /** * Get the scaled bounding box of a field. - * @param {!Blockly.Field} field The field. - * @return {!Blockly.utils.Rect} The scaled bounding box of the field. - * @private + * @param {!Field} field The field. + * @return {!Rect} The scaled bounding box of the field. */ -Blockly.DropDownDiv.getScaledBboxOfField_ = function(field) { - var bBox = field.getScaledBBox(); - return new Blockly.utils.Rect( - bBox.top, bBox.bottom, bBox.left, bBox.right); +const getScaledBboxOfField = function(field) { + const bBox = field.getScaledBBox(); + return new Rect(bBox.top, bBox.bottom, bBox.left, bBox.right); }; /** @@ -318,38 +324,37 @@ Blockly.DropDownDiv.getScaledBboxOfField_ = function(field) { * by a scaled bounding box. The primary position will be below the rect, * and the secondary position above the rect. Drop-down will be constrained to * the block's workspace. - * @param {!Blockly.utils.Rect} bBox The scaled bounding box. - * @param {!Blockly.Field} field The field to position the dropdown against. + * @param {!Rect} bBox The scaled bounding box. + * @param {!Field} field The field to position the dropdown against. * @param {Function=} opt_onHide Optional callback for when the drop-down is * hidden. * @param {number=} opt_secondaryYOffset Optional Y offset for above-block * positioning. * @return {boolean} True if the menu rendered below block; false if above. - * @private */ -Blockly.DropDownDiv.showPositionedByRect_ = function(bBox, field, - opt_onHide, opt_secondaryYOffset) { +const showPositionedByRect = function( + bBox, field, opt_onHide, opt_secondaryYOffset) { // If we can fit it, render below the block. - var primaryX = bBox.left + (bBox.right - bBox.left) / 2; - var primaryY = bBox.bottom; + const primaryX = bBox.left + (bBox.right - bBox.left) / 2; + const primaryY = bBox.bottom; // If we can't fit it, render above the entire parent block. - var secondaryX = primaryX; - var secondaryY = bBox.top; + const secondaryX = primaryX; + let secondaryY = bBox.top; if (opt_secondaryYOffset) { secondaryY += opt_secondaryYOffset; } - var sourceBlock = /** @type {!Blockly.BlockSvg} */ (field.getSourceBlock()); + const sourceBlock = /** @type {!BlockSvg} */ (field.getSourceBlock()); // Set bounds to main workspace; show the drop-down. - var workspace = sourceBlock.workspace; + let workspace = sourceBlock.workspace; while (workspace.options.parentWorkspace) { - workspace = /** @type {!Blockly.WorkspaceSvg} */ ( - workspace.options.parentWorkspace); + workspace = + /** @type {!WorkspaceSvg} */ (workspace.options.parentWorkspace); } - Blockly.DropDownDiv.setBoundsElement( - /** @type {Element} */ (workspace.getParentSvg().parentNode)); - return Blockly.DropDownDiv.show( - field, sourceBlock.RTL, - primaryX, primaryY, secondaryX, secondaryY, opt_onHide); + DropDownDiv.setBoundsElement( + /** @type {?Element} */ (workspace.getParentSvg().parentNode)); + return DropDownDiv.show( + field, sourceBlock.RTL, primaryX, primaryY, secondaryX, secondaryY, + opt_onHide); }; /** @@ -360,7 +365,7 @@ Blockly.DropDownDiv.showPositionedByRect_ = function(bBox, field, * will point there, and the container will be positioned below it. * If we can't maintain the container bounds at the primary point, fall-back to * the secondary point and position above. - * @param {Object} owner The object showing the drop-down + * @param {?Object} owner The object showing the drop-down * @param {boolean} rtl Right-to-left (true) or left-to-right (false). * @param {number} primaryX Desired origin point x, in absolute px. * @param {number} primaryY Desired origin point y, in absolute px. @@ -373,21 +378,20 @@ Blockly.DropDownDiv.showPositionedByRect_ = function(bBox, field, * @return {boolean} True if the menu rendered at the primary origin point. * @package */ -Blockly.DropDownDiv.show = function(owner, rtl, primaryX, primaryY, - secondaryX, secondaryY, opt_onHide) { - Blockly.DropDownDiv.owner_ = owner; - Blockly.DropDownDiv.onHide_ = opt_onHide || null; +DropDownDiv.show = function( + owner, rtl, primaryX, primaryY, secondaryX, secondaryY, opt_onHide) { + DropDownDiv.owner_ = owner; + DropDownDiv.onHide_ = opt_onHide || null; // Set direction. - var div = Blockly.DropDownDiv.DIV_; + const div = DropDownDiv.DIV_; div.style.direction = rtl ? 'rtl' : 'ltr'; - var mainWorkspace = - /** @type {!Blockly.WorkspaceSvg} */ (Blockly.common.getMainWorkspace()); - Blockly.DropDownDiv.rendererClassName_ = - mainWorkspace.getRenderer().getClassName(); - Blockly.DropDownDiv.themeClassName_ = mainWorkspace.getTheme().getClassName(); - Blockly.utils.dom.addClass(div, Blockly.DropDownDiv.rendererClassName_); - Blockly.utils.dom.addClass(div, Blockly.DropDownDiv.themeClassName_); + const mainWorkspace = + /** @type {!WorkspaceSvg} */ (Blockly.getMainWorkspace()); + DropDownDiv.rendererClassName_ = mainWorkspace.getRenderer().getClassName(); + DropDownDiv.themeClassName_ = mainWorkspace.getTheme().getClassName(); + dom.addClass(div, DropDownDiv.rendererClassName_); + dom.addClass(div, DropDownDiv.themeClassName_); // When we change `translate` multiple times in close succession, // Chrome may choose to wait and apply them all at once. @@ -398,21 +402,19 @@ Blockly.DropDownDiv.show = function(owner, rtl, primaryX, primaryY, // Using both `left`, `top` for the initial translation and then `translate` // for the animated transition to final X, Y is a workaround. - return Blockly.DropDownDiv.positionInternal_( - primaryX, primaryY, secondaryX, secondaryY); + return positionInternal(primaryX, primaryY, secondaryX, secondaryY); }; /** * Get sizing info about the bounding element. - * @return {!Blockly.DropDownDiv.BoundsInfo} An object containing size + * @return {!DropDownDiv.BoundsInfo} An object containing size * information about the bounding element (bounding box and width/height). - * @private */ -Blockly.DropDownDiv.getBoundsInfo_ = function() { - var boundPosition = Blockly.utils.style.getPageOffset( - /** @type {!Element} */ (Blockly.DropDownDiv.boundsElement_)); - var boundSize = Blockly.utils.style.getSize( - /** @type {!Element} */ (Blockly.DropDownDiv.boundsElement_)); +internal.getBoundsInfo = function() { + const boundPosition = style.getPageOffset( + /** @type {!Element} */ (DropDownDiv.boundsElement_)); + const boundSize = style.getSize( + /** @type {!Element} */ (DropDownDiv.boundsElement_)); return { left: boundPosition.x, @@ -426,74 +428,65 @@ Blockly.DropDownDiv.getBoundsInfo_ = function() { /** * Helper to position the drop-down and the arrow, maintaining bounds. - * See explanation of origin points in Blockly.DropDownDiv.show. + * See explanation of origin points in DropDownDiv.show. * @param {number} primaryX Desired origin point x, in absolute px. * @param {number} primaryY Desired origin point y, in absolute px. * @param {number} secondaryX Secondary/alternative origin point x, * in absolute px. * @param {number} secondaryY Secondary/alternative origin point y, * in absolute px. - * @return {!Blockly.DropDownDiv.PositionMetrics} Various final metrics, + * @return {!DropDownDiv.PositionMetrics} Various final metrics, * including rendered positions for drop-down and arrow. - * @private */ -Blockly.DropDownDiv.getPositionMetrics_ = function(primaryX, primaryY, - secondaryX, secondaryY) { - var boundsInfo = Blockly.DropDownDiv.getBoundsInfo_(); - var divSize = Blockly.utils.style.getSize( - /** @type {!Element} */ (Blockly.DropDownDiv.DIV_)); +internal.getPositionMetrics = function( + primaryX, primaryY, secondaryX, secondaryY) { + const boundsInfo = internal.getBoundsInfo(); + const divSize = style.getSize( + /** @type {!Element} */ (DropDownDiv.DIV_)); // Can we fit in-bounds below the target? if (primaryY + divSize.height < boundsInfo.bottom) { - return Blockly.DropDownDiv.getPositionBelowMetrics_( - primaryX, primaryY, boundsInfo, divSize); + return getPositionBelowMetrics(primaryX, primaryY, boundsInfo, divSize); } // Can we fit in-bounds above the target? if (secondaryY - divSize.height > boundsInfo.top) { - return Blockly.DropDownDiv.getPositionAboveMetrics_( - secondaryX, secondaryY, boundsInfo, divSize); + return getPositionAboveMetrics(secondaryX, secondaryY, boundsInfo, divSize); } // Can we fit outside the workspace bounds (but inside the window) below? if (primaryY + divSize.height < document.documentElement.clientHeight) { - return Blockly.DropDownDiv.getPositionBelowMetrics_( - primaryX, primaryY, boundsInfo, divSize); + return getPositionBelowMetrics(primaryX, primaryY, boundsInfo, divSize); } // Can we fit outside the workspace bounds (but inside the window) above? if (secondaryY - divSize.height > document.documentElement.clientTop) { - return Blockly.DropDownDiv.getPositionAboveMetrics_( - secondaryX, secondaryY, boundsInfo, divSize); + return getPositionAboveMetrics(secondaryX, secondaryY, boundsInfo, divSize); } // Last resort, render at top of page. - return Blockly.DropDownDiv.getPositionTopOfPageMetrics_( - primaryX, boundsInfo, divSize); + return getPositionTopOfPageMetrics(primaryX, boundsInfo, divSize); }; /** * Get the metrics for positioning the div below the source. * @param {number} primaryX Desired origin point x, in absolute px. * @param {number} primaryY Desired origin point y, in absolute px. - * @param {!Blockly.DropDownDiv.BoundsInfo} boundsInfo An object containing size + * @param {!DropDownDiv.BoundsInfo} boundsInfo An object containing size * information about the bounding element (bounding box and width/height). - * @param {!Blockly.utils.Size} divSize An object containing information about + * @param {!Size} divSize An object containing information about * the size of the DropDownDiv (width & height). - * @return {!Blockly.DropDownDiv.PositionMetrics} Various final metrics, + * @return {!DropDownDiv.PositionMetrics} Various final metrics, * including rendered positions for drop-down and arrow. - * @private */ -Blockly.DropDownDiv.getPositionBelowMetrics_ = function( +const getPositionBelowMetrics = function( primaryX, primaryY, boundsInfo, divSize) { - - var xCoords = Blockly.DropDownDiv.getPositionX( + const xCoords = DropDownDiv.getPositionX( primaryX, boundsInfo.left, boundsInfo.right, divSize.width); - var arrowY = -(Blockly.DropDownDiv.ARROW_SIZE / 2 + - Blockly.DropDownDiv.BORDER_SIZE); - var finalY = primaryY + Blockly.DropDownDiv.PADDING_Y; + const arrowY = -(DropDownDiv.ARROW_SIZE / 2 + DropDownDiv.BORDER_SIZE); + const finalY = primaryY + DropDownDiv.PADDING_Y; return { initialX: xCoords.divX, - initialY : primaryY, + initialY: primaryY, finalX: xCoords.divX, // X position remains constant during animation. finalY: finalY, arrowX: xCoords.arrowX, @@ -509,28 +502,26 @@ Blockly.DropDownDiv.getPositionBelowMetrics_ = function( * in absolute px. * @param {number} secondaryY Secondary/alternative origin point y, * in absolute px. - * @param {!Blockly.DropDownDiv.BoundsInfo} boundsInfo An object containing size + * @param {!DropDownDiv.BoundsInfo} boundsInfo An object containing size * information about the bounding element (bounding box and width/height). - * @param {!Blockly.utils.Size} divSize An object containing information about + * @param {!Size} divSize An object containing information about * the size of the DropDownDiv (width & height). - * @return {!Blockly.DropDownDiv.PositionMetrics} Various final metrics, + * @return {!DropDownDiv.PositionMetrics} Various final metrics, * including rendered positions for drop-down and arrow. - * @private */ -Blockly.DropDownDiv.getPositionAboveMetrics_ = function( +const getPositionAboveMetrics = function( secondaryX, secondaryY, boundsInfo, divSize) { - - var xCoords = Blockly.DropDownDiv.getPositionX( + const xCoords = DropDownDiv.getPositionX( secondaryX, boundsInfo.left, boundsInfo.right, divSize.width); - var arrowY = divSize.height - (Blockly.DropDownDiv.BORDER_SIZE * 2) - - (Blockly.DropDownDiv.ARROW_SIZE / 2); - var finalY = secondaryY - divSize.height - Blockly.DropDownDiv.PADDING_Y; - var initialY = secondaryY - divSize.height; // No padding on Y. + const arrowY = divSize.height - (DropDownDiv.BORDER_SIZE * 2) - + (DropDownDiv.ARROW_SIZE / 2); + const finalY = secondaryY - divSize.height - DropDownDiv.PADDING_Y; + const initialY = secondaryY - divSize.height; // No padding on Y. return { initialX: xCoords.divX, - initialY : initialY, + initialY: initialY, finalX: xCoords.divX, // X position remains constant during animation. finalY: finalY, arrowX: xCoords.arrowX, @@ -543,24 +534,21 @@ Blockly.DropDownDiv.getPositionAboveMetrics_ = function( /** * Get the metrics for positioning the div at the top of the page. * @param {number} sourceX Desired origin point x, in absolute px. - * @param {!Blockly.DropDownDiv.BoundsInfo} boundsInfo An object containing size + * @param {!DropDownDiv.BoundsInfo} boundsInfo An object containing size * information about the bounding element (bounding box and width/height). - * @param {!Blockly.utils.Size} divSize An object containing information about + * @param {!Size} divSize An object containing information about * the size of the DropDownDiv (width & height). - * @return {!Blockly.DropDownDiv.PositionMetrics} Various final metrics, + * @return {!DropDownDiv.PositionMetrics} Various final metrics, * including rendered positions for drop-down and arrow. - * @private */ -Blockly.DropDownDiv.getPositionTopOfPageMetrics_ = function( - sourceX, boundsInfo, divSize) { - - var xCoords = Blockly.DropDownDiv.getPositionX( +const getPositionTopOfPageMetrics = function(sourceX, boundsInfo, divSize) { + const xCoords = DropDownDiv.getPositionX( sourceX, boundsInfo.left, boundsInfo.right, divSize.width); // No need to provide arrow-specific information because it won't be visible. return { initialX: xCoords.divX, - initialY : 0, + initialY: 0, finalX: xCoords.divX, // X position remains constant during animation. finalY: 0, // Y position remains constant during animation. arrowAtTop: null, @@ -583,54 +571,50 @@ Blockly.DropDownDiv.getPositionTopOfPageMetrics_ = function( * the x positions of the left side of the DropDownDiv and the arrow. * @package */ -Blockly.DropDownDiv.getPositionX = function( +DropDownDiv.getPositionX = function( sourceX, boundsLeft, boundsRight, divWidth) { - var arrowX, divX; + let arrowX, divX; arrowX = divX = sourceX; // Offset the topLeft coord so that the dropdowndiv is centered. divX -= divWidth / 2; // Fit the dropdowndiv within the bounds of the workspace. - divX = Blockly.utils.math.clamp(boundsLeft, divX, boundsRight - divWidth); + divX = math.clamp(boundsLeft, divX, boundsRight - divWidth); // Offset the arrow coord so that the arrow is centered. - arrowX -= Blockly.DropDownDiv.ARROW_SIZE / 2; + arrowX -= DropDownDiv.ARROW_SIZE / 2; // Convert the arrow position to be relative to the top left of the div. - var relativeArrowX = arrowX - divX; - var horizPadding = Blockly.DropDownDiv.ARROW_HORIZONTAL_PADDING; + let relativeArrowX = arrowX - divX; + const horizPadding = DropDownDiv.ARROW_HORIZONTAL_PADDING; // Clamp the arrow position so that it stays attached to the dropdowndiv. - relativeArrowX = Blockly.utils.math.clamp( - horizPadding, - relativeArrowX, - divWidth - horizPadding - Blockly.DropDownDiv.ARROW_SIZE); + relativeArrowX = math.clamp( + horizPadding, relativeArrowX, + divWidth - horizPadding - DropDownDiv.ARROW_SIZE); - return { - arrowX: relativeArrowX, - divX: divX - }; + return {arrowX: relativeArrowX, divX: divX}; }; /** * Is the container visible? * @return {boolean} True if visible. */ -Blockly.DropDownDiv.isVisible = function() { - return !!Blockly.DropDownDiv.owner_; +DropDownDiv.isVisible = function() { + return !!DropDownDiv.owner_; }; /** * Hide the menu only if it is owned by the provided object. - * @param {Object} owner Object which must be owning the drop-down to hide. + * @param {?Object} owner Object which must be owning the drop-down to hide. * @param {boolean=} opt_withoutAnimation True if we should hide the dropdown * without animating. * @return {boolean} True if hidden. */ -Blockly.DropDownDiv.hideIfOwner = function(owner, opt_withoutAnimation) { - if (Blockly.DropDownDiv.owner_ === owner) { +DropDownDiv.hideIfOwner = function(owner, opt_withoutAnimation) { + if (DropDownDiv.owner_ === owner) { if (opt_withoutAnimation) { - Blockly.DropDownDiv.hideWithoutAnimation(); + DropDownDiv.hideWithoutAnimation(); } else { - Blockly.DropDownDiv.hide(); + DropDownDiv.hide(); } return true; } @@ -640,37 +624,35 @@ Blockly.DropDownDiv.hideIfOwner = function(owner, opt_withoutAnimation) { /** * Hide the menu, triggering animation. */ -Blockly.DropDownDiv.hide = function() { +DropDownDiv.hide = function() { // Start the animation by setting the translation and fading out. - var div = Blockly.DropDownDiv.DIV_; // Reset to (initialX, initialY) - i.e., no translation. - div.style.transform = 'translate(0, 0)'; - div.style.opacity = 0; + DropDownDiv.DIV_.style.transform = 'translate(0, 0)'; + DropDownDiv.DIV_.style.opacity = 0; // Finish animation - reset all values to default. - Blockly.DropDownDiv.animateOutTimer_ = - setTimeout(function() { - Blockly.DropDownDiv.hideWithoutAnimation(); - }, Blockly.DropDownDiv.ANIMATION_TIME * 1000); - if (Blockly.DropDownDiv.onHide_) { - Blockly.DropDownDiv.onHide_(); - Blockly.DropDownDiv.onHide_ = null; + DropDownDiv.animateOutTimer_ = setTimeout(function() { + DropDownDiv.hideWithoutAnimation(); + }, DropDownDiv.ANIMATION_TIME * 1000); + if (DropDownDiv.onHide_) { + DropDownDiv.onHide_(); + DropDownDiv.onHide_ = null; } }; /** * Hide the menu, without animation. */ -Blockly.DropDownDiv.hideWithoutAnimation = function() { - if (!Blockly.DropDownDiv.isVisible()) { +DropDownDiv.hideWithoutAnimation = function() { + if (!DropDownDiv.isVisible()) { return; } - if (Blockly.DropDownDiv.animateOutTimer_) { - clearTimeout(Blockly.DropDownDiv.animateOutTimer_); + if (DropDownDiv.animateOutTimer_) { + clearTimeout(DropDownDiv.animateOutTimer_); } // Reset style properties in case this gets called directly // instead of hide() - see discussion on #2551. - var div = Blockly.DropDownDiv.DIV_; + const div = DropDownDiv.DIV_; div.style.transform = ''; div.style.left = ''; div.style.top = ''; @@ -679,23 +661,22 @@ Blockly.DropDownDiv.hideWithoutAnimation = function() { div.style.backgroundColor = ''; div.style.borderColor = ''; - if (Blockly.DropDownDiv.onHide_) { - Blockly.DropDownDiv.onHide_(); - Blockly.DropDownDiv.onHide_ = null; + if (DropDownDiv.onHide_) { + DropDownDiv.onHide_(); + DropDownDiv.onHide_ = null; } - Blockly.DropDownDiv.clearContent(); - Blockly.DropDownDiv.owner_ = null; + DropDownDiv.clearContent(); + DropDownDiv.owner_ = null; - if (Blockly.DropDownDiv.rendererClassName_) { - Blockly.utils.dom.removeClass(div, Blockly.DropDownDiv.rendererClassName_); - Blockly.DropDownDiv.rendererClassName_ = ''; + if (DropDownDiv.rendererClassName_) { + dom.removeClass(div, DropDownDiv.rendererClassName_); + DropDownDiv.rendererClassName_ = ''; } - if (Blockly.DropDownDiv.themeClassName_) { - Blockly.utils.dom.removeClass(div, Blockly.DropDownDiv.themeClassName_); - Blockly.DropDownDiv.themeClassName_ = ''; + if (DropDownDiv.themeClassName_) { + dom.removeClass(div, DropDownDiv.themeClassName_); + DropDownDiv.themeClassName_ = ''; } - (/** @type {!Blockly.WorkspaceSvg} */ ( - Blockly.common.getMainWorkspace())).markFocused(); + (/** @type {!WorkspaceSvg} */ (common.getMainWorkspace())).markFocused(); }; /** @@ -707,31 +688,30 @@ Blockly.DropDownDiv.hideWithoutAnimation = function() { * @param {number} secondaryY Secondary/alternative origin point y, * in absolute px. * @return {boolean} True if the menu rendered at the primary origin point. - * @private */ -Blockly.DropDownDiv.positionInternal_ = function( - primaryX, primaryY, secondaryX, secondaryY) { - var metrics = Blockly.DropDownDiv.getPositionMetrics_(primaryX, primaryY, - secondaryX, secondaryY); +const positionInternal = function(primaryX, primaryY, secondaryX, secondaryY) { + const metrics = + internal.getPositionMetrics(primaryX, primaryY, secondaryX, secondaryY); // Update arrow CSS. if (metrics.arrowVisible) { - Blockly.DropDownDiv.arrow_.style.display = ''; - Blockly.DropDownDiv.arrow_.style.transform = 'translate(' + - metrics.arrowX + 'px,' + metrics.arrowY + 'px) rotate(45deg)'; - Blockly.DropDownDiv.arrow_.setAttribute('class', metrics.arrowAtTop ? - 'blocklyDropDownArrow blocklyArrowTop' : - 'blocklyDropDownArrow blocklyArrowBottom'); + DropDownDiv.arrow_.style.display = ''; + DropDownDiv.arrow_.style.transform = 'translate(' + metrics.arrowX + 'px,' + + metrics.arrowY + 'px) rotate(45deg)'; + DropDownDiv.arrow_.setAttribute( + 'class', + metrics.arrowAtTop ? 'blocklyDropDownArrow blocklyArrowTop' : + 'blocklyDropDownArrow blocklyArrowBottom'); } else { - Blockly.DropDownDiv.arrow_.style.display = 'none'; + DropDownDiv.arrow_.style.display = 'none'; } - var initialX = Math.floor(metrics.initialX); - var initialY = Math.floor(metrics.initialY); - var finalX = Math.floor(metrics.finalX); - var finalY = Math.floor(metrics.finalY); + const initialX = Math.floor(metrics.initialX); + const initialY = Math.floor(metrics.initialY); + const finalX = Math.floor(metrics.finalX); + const finalY = Math.floor(metrics.finalY); - var div = Blockly.DropDownDiv.DIV_; + const div = DropDownDiv.DIV_; // First apply initial translation. div.style.left = initialX + 'px'; div.style.top = initialY + 'px'; @@ -742,8 +722,8 @@ Blockly.DropDownDiv.positionInternal_ = function( // Add final translate, animated through `transition`. // Coordinates are relative to (initialX, initialY), // where the drop-down is absolutely positioned. - var dx = finalX - initialX; - var dy = finalY - initialY; + const dx = finalX - initialX; + const dy = finalY - initialY; div.style.transform = 'translate(' + dx + 'px,' + dy + 'px)'; return !!metrics.arrowAtTop; @@ -754,27 +734,28 @@ Blockly.DropDownDiv.positionInternal_ = function( * calculate the new position, it will just hide it instead. * @package */ -Blockly.DropDownDiv.repositionForWindowResize = function() { +DropDownDiv.repositionForWindowResize = function() { // This condition mainly catches the dropdown div when it is being used as a // dropdown. It is important not to close it in this case because on Android, // when a field is focused, the soft keyboard opens triggering a window resize // event and we want the dropdown div to stick around so users can type into // it. - if (Blockly.DropDownDiv.owner_) { - var field = /** @type {!Blockly.Field} */ (Blockly.DropDownDiv.owner_); - var block = /** @type {!Blockly.BlockSvg} */ (field.getSourceBlock()); - var bBox = Blockly.DropDownDiv.positionToField_ ? - Blockly.DropDownDiv.getScaledBboxOfField_(field) : - Blockly.DropDownDiv.getScaledBboxOfBlock_(block); + if (DropDownDiv.owner_) { + const field = /** @type {!Field} */ (DropDownDiv.owner_); + const block = /** @type {!BlockSvg} */ (field.getSourceBlock()); + const bBox = DropDownDiv.positionToField_ ? getScaledBboxOfField(field) : + getScaledBboxOfBlock(block); // If we can fit it, render below the block. - var primaryX = bBox.left + (bBox.right - bBox.left) / 2; - var primaryY = bBox.bottom; + const primaryX = bBox.left + (bBox.right - bBox.left) / 2; + const primaryY = bBox.bottom; // If we can't fit it, render above the entire parent block. - var secondaryX = primaryX; - var secondaryY = bBox.top; - Blockly.DropDownDiv.positionInternal_( - primaryX, primaryY, secondaryX, secondaryY); + const secondaryX = primaryX; + const secondaryY = bBox.top; + positionInternal(primaryX, primaryY, secondaryX, secondaryY); } else { - Blockly.DropDownDiv.hide(); + DropDownDiv.hide(); } }; +exports = DropDownDiv; + +exports.TEST_ONLY = internal; diff --git a/tests/deps.js b/tests/deps.js index fb6a088b1..2ec43e967 100644 --- a/tests/deps.js +++ b/tests/deps.js @@ -33,7 +33,7 @@ goog.addDependency('../../core/contextmenu_registry.js', ['Blockly.ContextMenuRe goog.addDependency('../../core/css.js', ['Blockly.Css'], [], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/delete_area.js', ['Blockly.DeleteArea'], ['Blockly.BlockSvg', 'Blockly.DragTarget', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/drag_target.js', ['Blockly.DragTarget'], [], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/dropdowndiv.js', ['Blockly.DropDownDiv'], ['Blockly.common', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.style']); +goog.addDependency('../../core/dropdowndiv.js', ['Blockly.DropDownDiv'], ['Blockly.common', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.style'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/events/events.js', ['Blockly.Events'], ['Blockly.registry', 'Blockly.utils']); goog.addDependency('../../core/events/events_abstract.js', ['Blockly.Events.Abstract'], ['Blockly.Events'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/events/events_block_base.js', ['Blockly.Events.BlockBase'], ['Blockly.Events.Abstract', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); diff --git a/tests/mocha/dropdowndiv_test.js b/tests/mocha/dropdowndiv_test.js index c51c83dea..d54d8ad33 100644 --- a/tests/mocha/dropdowndiv_test.js +++ b/tests/mocha/dropdowndiv_test.js @@ -4,11 +4,13 @@ * SPDX-License-Identifier: Apache-2.0 */ + +const DropDownDiv = goog.require('Blockly.DropDownDiv'); suite('DropDownDiv', function() { suite('Positioning', function() { setup(function() { sharedTestSetup.call(this); - this.boundsStub = sinon.stub(Blockly.DropDownDiv, 'getBoundsInfo_') + this.boundsStub = sinon.stub(Blockly.DropDownDiv.TEST_ONLY, 'getBoundsInfo') .returns({ left: 0, right: 100, @@ -31,7 +33,7 @@ suite('DropDownDiv', function() { sharedTestTeardown.call(this); }); test('Below, in Bounds', function() { - var metrics = Blockly.DropDownDiv.getPositionMetrics_(50, 0, 50, -10); + var metrics = Blockly.DropDownDiv.TEST_ONLY.getPositionMetrics(50, 0, 50, -10); // "Above" in value actually means below in render. chai.assert.isAtLeast(metrics.initialY, 0); chai.assert.isAbove(metrics.finalY, 0); @@ -39,7 +41,7 @@ suite('DropDownDiv', function() { chai.assert.isTrue(metrics.arrowAtTop); }); test('Above, in Bounds', function() { - var metrics = Blockly.DropDownDiv.getPositionMetrics_(50, 100, 50, 90); + var metrics = Blockly.DropDownDiv.TEST_ONLY.getPositionMetrics(50, 100, 50, 90); // "Below" in value actually means above in render. chai.assert.isAtMost(metrics.initialY, 100); chai.assert.isBelow(metrics.finalY, 100); @@ -47,7 +49,7 @@ suite('DropDownDiv', function() { chai.assert.isFalse(metrics.arrowAtTop); }); test('Below, out of Bounds', function() { - var metrics = Blockly.DropDownDiv.getPositionMetrics_(50, 60, 50, 50); + var metrics = Blockly.DropDownDiv.TEST_ONLY.getPositionMetrics(50, 60, 50, 50); // "Above" in value actually means below in render. chai.assert.isAtLeast(metrics.initialY, 60); chai.assert.isAbove(metrics.finalY, 60); @@ -55,7 +57,7 @@ suite('DropDownDiv', function() { chai.assert.isTrue(metrics.arrowAtTop); }); test('Above, in Bounds', function() { - var metrics = Blockly.DropDownDiv.getPositionMetrics_(50, 100, 50, 90); + var metrics = Blockly.DropDownDiv.TEST_ONLY.getPositionMetrics(50, 100, 50, 90); // "Below" in value actually means above in render. chai.assert.isAtMost(metrics.initialY, 100); chai.assert.isBelow(metrics.finalY, 100); @@ -64,7 +66,7 @@ suite('DropDownDiv', function() { }); test('No Solution, Render At Top', function() { this.clientHeightStub.get(function() { return 100; }); - var metrics = Blockly.DropDownDiv.getPositionMetrics_(50, 60, 50, 50); + var metrics = Blockly.DropDownDiv.TEST_ONLY.getPositionMetrics(50, 60, 50, 50); // "Above" in value actually means below in render. chai.assert.equal(metrics.initialY, 0); chai.assert.equal(metrics.finalY, 0);