From 00c03b303a11f020e7b0b831536578b1c0d3b22e Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 7 Jan 2022 16:07:08 -0800 Subject: [PATCH 1/6] refactor: convert menu.js to ES6 class --- core/menu.js | 748 ++++++++++++++++++++++++++------------------------- 1 file changed, 375 insertions(+), 373 deletions(-) diff --git a/core/menu.js b/core/menu.js index a39b9c52c1e..77203c0ae0d 100644 --- a/core/menu.js +++ b/core/menu.js @@ -29,449 +29,451 @@ const {Size} = goog.requireType('Blockly.utils.Size'); /** * A basic menu class. - * @constructor - * @alias Blockly.Menu */ -const Menu = function() { +const Menu = class { /** - * Array of menu items. - * (Nulls are never in the array, but typing the array as nullable prevents - * the compiler from objecting to .indexOf(null)) - * @type {!Array} - * @private + * @alias Blockly.Menu */ - this.menuItems_ = []; + constructor() { + /** + * Array of menu items. + * (Nulls are never in the array, but typing the array as nullable prevents + * the compiler from objecting to .indexOf(null)) + * @type {!Array} + * @private + */ + this.menuItems_ = []; + + /** + * Coordinates of the mousedown event that caused this menu to open. Used to + * prevent the consequent mouseup event due to a simple click from activating + * a menu item immediately. + * @type {?Coordinate} + * @package + */ + this.openingCoords = null; + + /** + * This is the element that we will listen to the real focus events on. + * A value of null means no menu item is highlighted. + * @type {?MenuItem} + * @private + */ + this.highlightedItem_ = null; - /** - * Coordinates of the mousedown event that caused this menu to open. Used to - * prevent the consequent mouseup event due to a simple click from activating - * a menu item immediately. - * @type {?Coordinate} - * @package - */ - this.openingCoords = null; + /** + * Mouse over event data. + * @type {?browserEvents.Data} + * @private + */ + this.mouseOverHandler_ = null; - /** - * This is the element that we will listen to the real focus events on. - * A value of null means no menu item is highlighted. - * @type {?MenuItem} - * @private - */ - this.highlightedItem_ = null; + /** + * Click event data. + * @type {?browserEvents.Data} + * @private + */ + this.clickHandler_ = null; + + /** + * Mouse enter event data. + * @type {?browserEvents.Data} + * @private + */ + this.mouseEnterHandler_ = null; + + /** + * Mouse leave event data. + * @type {?browserEvents.Data} + * @private + */ + this.mouseLeaveHandler_ = null; + + /** + * Key down event data. + * @type {?browserEvents.Data} + * @private + */ + this.onKeyDownHandler_ = null; + + /** + * The menu's root DOM element. + * @type {?Element} + * @private + */ + this.element_ = null; + + /** + * ARIA name for this menu. + * @type {?aria.Role} + * @private + */ + this.roleName_ = null; + } /** - * Mouse over event data. - * @type {?browserEvents.Data} - * @private + * Add a new menu item to the bottom of this menu. + * @param {!MenuItem} menuItem Menu item to append. */ - this.mouseOverHandler_ = null; + addChild(menuItem) { + this.menuItems_.push(menuItem); + } /** - * Click event data. - * @type {?browserEvents.Data} - * @private + * Creates the menu DOM. + * @param {!Element} container Element upon which to append this menu. */ - this.clickHandler_ = null; + render(container) { + const element = + /** @type {!HTMLDivElement} */ (document.createElement('div')); + // goog-menu is deprecated, use blocklyMenu. May 2020. + element.className = 'blocklyMenu goog-menu blocklyNonSelectable'; + element.tabIndex = 0; + if (this.roleName_) { + aria.setRole(element, this.roleName_); + } + this.element_ = element; + + // Add menu items. + for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { + element.appendChild(menuItem.createDom()); + } + + // Add event handlers. + this.mouseOverHandler_ = browserEvents.conditionalBind( + element, 'mouseover', this, this.handleMouseOver_, true); + this.clickHandler_ = browserEvents.conditionalBind( + element, 'click', this, this.handleClick_, true); + this.mouseEnterHandler_ = browserEvents.conditionalBind( + element, 'mouseenter', this, this.handleMouseEnter_, true); + this.mouseLeaveHandler_ = browserEvents.conditionalBind( + element, 'mouseleave', this, this.handleMouseLeave_, true); + this.onKeyDownHandler_ = browserEvents.conditionalBind( + element, 'keydown', this, this.handleKeyEvent_); + + container.appendChild(element); + } /** - * Mouse enter event data. - * @type {?browserEvents.Data} - * @private + * Gets the menu's element. + * @return {?Element} The DOM element. + * @package */ - this.mouseEnterHandler_ = null; + getElement() { + return this.element_; + } /** - * Mouse leave event data. - * @type {?browserEvents.Data} - * @private + * Focus the menu element. + * @package */ - this.mouseLeaveHandler_ = null; + focus() { + const el = this.getElement(); + if (el) { + el.focus({preventScroll: true}); + dom.addClass(el, 'blocklyFocused'); + } + } /** - * Key down event data. - * @type {?browserEvents.Data} + * Blur the menu element. * @private */ - this.onKeyDownHandler_ = null; + blur_() { + const el = this.getElement(); + if (el) { + el.blur(); + dom.removeClass(el, 'blocklyFocused'); + } + } /** - * The menu's root DOM element. - * @type {?Element} - * @private + * Set the menu accessibility role. + * @param {!aria.Role} roleName role name. + * @package */ - this.element_ = null; + setRole(roleName) { + this.roleName_ = roleName; + } /** - * ARIA name for this menu. - * @type {?aria.Role} - * @private + * Dispose of this menu. */ - this.roleName_ = null; -}; - - -/** - * Add a new menu item to the bottom of this menu. - * @param {!MenuItem} menuItem Menu item to append. - */ -Menu.prototype.addChild = function(menuItem) { - this.menuItems_.push(menuItem); -}; + dispose() { + // Remove event handlers. + if (this.mouseOverHandler_) { + browserEvents.unbind(this.mouseOverHandler_); + this.mouseOverHandler_ = null; + } + if (this.clickHandler_) { + browserEvents.unbind(this.clickHandler_); + this.clickHandler_ = null; + } + if (this.mouseEnterHandler_) { + browserEvents.unbind(this.mouseEnterHandler_); + this.mouseEnterHandler_ = null; + } + if (this.mouseLeaveHandler_) { + browserEvents.unbind(this.mouseLeaveHandler_); + this.mouseLeaveHandler_ = null; + } + if (this.onKeyDownHandler_) { + browserEvents.unbind(this.onKeyDownHandler_); + this.onKeyDownHandler_ = null; + } -/** - * Creates the menu DOM. - * @param {!Element} container Element upon which to append this menu. - */ -Menu.prototype.render = function(container) { - const element = - /** @type {!HTMLDivElement} */ (document.createElement('div')); - // goog-menu is deprecated, use blocklyMenu. May 2020. - element.className = 'blocklyMenu goog-menu blocklyNonSelectable'; - element.tabIndex = 0; - if (this.roleName_) { - aria.setRole(element, this.roleName_); + // Remove menu items. + for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { + menuItem.dispose(); + } + this.element_ = null; } - this.element_ = element; - // Add menu items. - for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { - element.appendChild(menuItem.createDom()); + // Child component management. + + /** + * Returns the child menu item that owns the given DOM element, + * or null if no such menu item is found. + * @param {Element} elem DOM element whose owner is to be returned. + * @return {?MenuItem} Menu item for which the DOM element belongs to. + * @private + */ + getMenuItem_(elem) { + const menuElem = this.getElement(); + // Node might be the menu border (resulting in no associated menu item), or + // a menu item's div, or some element within the menu item. + // Walk up parents until one meets either the menu's root element, or + // a menu item's div. + while (elem && elem !== menuElem) { + if (dom.hasClass(elem, 'blocklyMenuItem')) { + // Having found a menu item's div, locate that menu item in this menu. + for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { + if (menuItem.getElement() === elem) { + return menuItem; + } + } + } + elem = elem.parentElement; + } + return null; } - // Add event handlers. - this.mouseOverHandler_ = browserEvents.conditionalBind( - element, 'mouseover', this, this.handleMouseOver_, true); - this.clickHandler_ = browserEvents.conditionalBind( - element, 'click', this, this.handleClick_, true); - this.mouseEnterHandler_ = browserEvents.conditionalBind( - element, 'mouseenter', this, this.handleMouseEnter_, true); - this.mouseLeaveHandler_ = browserEvents.conditionalBind( - element, 'mouseleave', this, this.handleMouseLeave_, true); - this.onKeyDownHandler_ = browserEvents.conditionalBind( - element, 'keydown', this, this.handleKeyEvent_); - - container.appendChild(element); -}; + // Highlight management. -/** - * Gets the menu's element. - * @return {?Element} The DOM element. - * @package - */ -Menu.prototype.getElement = function() { - return this.element_; -}; - -/** - * Focus the menu element. - * @package - */ -Menu.prototype.focus = function() { - const el = this.getElement(); - if (el) { - el.focus({preventScroll: true}); - dom.addClass(el, 'blocklyFocused'); + /** + * Highlights the given menu item, or clears highlighting if null. + * @param {?MenuItem} item Item to highlight, or null. + * @package + */ + setHighlighted(item) { + const currentHighlighted = this.highlightedItem_; + if (currentHighlighted) { + currentHighlighted.setHighlighted(false); + this.highlightedItem_ = null; + } + if (item) { + item.setHighlighted(true); + this.highlightedItem_ = item; + // Bring the highlighted item into view. This has no effect if the menu is + // not scrollable. + const el = /** @type {!Element} */ (this.getElement()); + style.scrollIntoContainerView( + /** @type {!Element} */ (item.getElement()), el); + + aria.setState(el, aria.State.ACTIVEDESCENDANT, item.getId()); + } } -}; -/** - * Blur the menu element. - * @private - */ -Menu.prototype.blur_ = function() { - const el = this.getElement(); - if (el) { - el.blur(); - dom.removeClass(el, 'blocklyFocused'); + /** + * Highlights the next highlightable item (or the first if nothing is currently + * highlighted). + * @package + */ + highlightNext() { + const index = this.menuItems_.indexOf(this.highlightedItem_); + this.highlightHelper_(index, 1); } -}; - -/** - * Set the menu accessibility role. - * @param {!aria.Role} roleName role name. - * @package - */ -Menu.prototype.setRole = function(roleName) { - this.roleName_ = roleName; -}; -/** - * Dispose of this menu. - */ -Menu.prototype.dispose = function() { - // Remove event handlers. - if (this.mouseOverHandler_) { - browserEvents.unbind(this.mouseOverHandler_); - this.mouseOverHandler_ = null; - } - if (this.clickHandler_) { - browserEvents.unbind(this.clickHandler_); - this.clickHandler_ = null; - } - if (this.mouseEnterHandler_) { - browserEvents.unbind(this.mouseEnterHandler_); - this.mouseEnterHandler_ = null; - } - if (this.mouseLeaveHandler_) { - browserEvents.unbind(this.mouseLeaveHandler_); - this.mouseLeaveHandler_ = null; - } - if (this.onKeyDownHandler_) { - browserEvents.unbind(this.onKeyDownHandler_); - this.onKeyDownHandler_ = null; + /** + * Highlights the previous highlightable item (or the last if nothing is + * currently highlighted). + * @package + */ + highlightPrevious() { + const index = this.menuItems_.indexOf(this.highlightedItem_); + this.highlightHelper_(index < 0 ? this.menuItems_.length : index, -1); } - // Remove menu items. - for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { - menuItem.dispose(); + /** + * Highlights the first highlightable item. + * @private + */ + highlightFirst_() { + this.highlightHelper_(-1, 1); } - this.element_ = null; -}; -// Child component management. + /** + * Highlights the last highlightable item. + * @private + */ + highlightLast_() { + this.highlightHelper_(this.menuItems_.length, -1); + } -/** - * Returns the child menu item that owns the given DOM element, - * or null if no such menu item is found. - * @param {Element} elem DOM element whose owner is to be returned. - * @return {?MenuItem} Menu item for which the DOM element belongs to. - * @private - */ -Menu.prototype.getMenuItem_ = function(elem) { - const menuElem = this.getElement(); - // Node might be the menu border (resulting in no associated menu item), or - // a menu item's div, or some element within the menu item. - // Walk up parents until one meets either the menu's root element, or - // a menu item's div. - while (elem && elem !== menuElem) { - if (dom.hasClass(elem, 'blocklyMenuItem')) { - // Having found a menu item's div, locate that menu item in this menu. - for (let i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) { - if (menuItem.getElement() === elem) { - return menuItem; - } + /** + * Helper function that manages the details of moving the highlight among + * child menuitems in response to keyboard events. + * @param {number} startIndex Start index. + * @param {number} delta Step direction: 1 to go down, -1 to go up. + * @private + */ + highlightHelper_(startIndex, delta) { + let index = startIndex + delta; + let menuItem; + while ((menuItem = this.menuItems_[index])) { + if (menuItem.isEnabled()) { + this.setHighlighted(menuItem); + break; } + index += delta; } - elem = elem.parentElement; } - return null; -}; - -// Highlight management. -/** - * Highlights the given menu item, or clears highlighting if null. - * @param {?MenuItem} item Item to highlight, or null. - * @package - */ -Menu.prototype.setHighlighted = function(item) { - const currentHighlighted = this.highlightedItem_; - if (currentHighlighted) { - currentHighlighted.setHighlighted(false); - this.highlightedItem_ = null; - } - if (item) { - item.setHighlighted(true); - this.highlightedItem_ = item; - // Bring the highlighted item into view. This has no effect if the menu is - // not scrollable. - const el = /** @type {!Element} */ (this.getElement()); - style.scrollIntoContainerView( - /** @type {!Element} */ (item.getElement()), el); - - aria.setState(el, aria.State.ACTIVEDESCENDANT, item.getId()); - } -}; + // Mouse events. -/** - * Highlights the next highlightable item (or the first if nothing is currently - * highlighted). - * @package - */ -Menu.prototype.highlightNext = function() { - const index = this.menuItems_.indexOf(this.highlightedItem_); - this.highlightHelper_(index, 1); -}; - -/** - * Highlights the previous highlightable item (or the last if nothing is - * currently highlighted). - * @package - */ -Menu.prototype.highlightPrevious = function() { - const index = this.menuItems_.indexOf(this.highlightedItem_); - this.highlightHelper_(index < 0 ? this.menuItems_.length : index, -1); -}; - -/** - * Highlights the first highlightable item. - * @private - */ -Menu.prototype.highlightFirst_ = function() { - this.highlightHelper_(-1, 1); -}; - -/** - * Highlights the last highlightable item. - * @private - */ -Menu.prototype.highlightLast_ = function() { - this.highlightHelper_(this.menuItems_.length, -1); -}; + /** + * Handles mouseover events. Highlight menuitems as the user hovers over them. + * @param {!Event} e Mouse event to handle. + * @private + */ + handleMouseOver_(e) { + const menuItem = this.getMenuItem_(/** @type {Element} */ (e.target)); -/** - * Helper function that manages the details of moving the highlight among - * child menuitems in response to keyboard events. - * @param {number} startIndex Start index. - * @param {number} delta Step direction: 1 to go down, -1 to go up. - * @private - */ -Menu.prototype.highlightHelper_ = function(startIndex, delta) { - let index = startIndex + delta; - let menuItem; - while ((menuItem = this.menuItems_[index])) { - if (menuItem.isEnabled()) { - this.setHighlighted(menuItem); - break; + if (menuItem) { + if (menuItem.isEnabled()) { + if (this.highlightedItem_ !== menuItem) { + this.setHighlighted(menuItem); + } + } else { + this.setHighlighted(null); + } } - index += delta; } -}; - -// Mouse events. - -/** - * Handles mouseover events. Highlight menuitems as the user hovers over them. - * @param {!Event} e Mouse event to handle. - * @private - */ -Menu.prototype.handleMouseOver_ = function(e) { - const menuItem = this.getMenuItem_(/** @type {Element} */ (e.target)); - if (menuItem) { - if (menuItem.isEnabled()) { - if (this.highlightedItem_ !== menuItem) { - this.setHighlighted(menuItem); + /** + * Handles click events. Pass the event onto the child menuitem to handle. + * @param {!Event} e Click event to handle. + * @private + */ + handleClick_(e) { + const oldCoords = this.openingCoords; + // Clear out the saved opening coords immediately so they're not used twice. + this.openingCoords = null; + if (oldCoords && typeof e.clientX === 'number') { + const newCoords = new Coordinate(e.clientX, e.clientY); + if (Coordinate.distance(oldCoords, newCoords) < 1) { + // This menu was opened by a mousedown and we're handling the consequent + // click event. The coords haven't changed, meaning this was the same + // opening event. Don't do the usual behavior because the menu just popped + // up under the mouse and the user didn't mean to activate this item. + return; } - } else { - this.setHighlighted(null); } - } -}; -/** - * Handles click events. Pass the event onto the child menuitem to handle. - * @param {!Event} e Click event to handle. - * @private - */ -Menu.prototype.handleClick_ = function(e) { - const oldCoords = this.openingCoords; - // Clear out the saved opening coords immediately so they're not used twice. - this.openingCoords = null; - if (oldCoords && typeof e.clientX === 'number') { - const newCoords = new Coordinate(e.clientX, e.clientY); - if (Coordinate.distance(oldCoords, newCoords) < 1) { - // This menu was opened by a mousedown and we're handling the consequent - // click event. The coords haven't changed, meaning this was the same - // opening event. Don't do the usual behavior because the menu just popped - // up under the mouse and the user didn't mean to activate this item. - return; + const menuItem = this.getMenuItem_(/** @type {Element} */ (e.target)); + if (menuItem) { + menuItem.performAction(); } } - const menuItem = this.getMenuItem_(/** @type {Element} */ (e.target)); - if (menuItem) { - menuItem.performAction(); + /** + * Handles mouse enter events. Focus the element. + * @param {!Event} _e Mouse event to handle. + * @private + */ + handleMouseEnter_(_e) { + this.focus(); } -}; - -/** - * Handles mouse enter events. Focus the element. - * @param {!Event} _e Mouse event to handle. - * @private - */ -Menu.prototype.handleMouseEnter_ = function(_e) { - this.focus(); -}; -/** - * Handles mouse leave events. Blur and clear highlight. - * @param {!Event} _e Mouse event to handle. - * @private - */ -Menu.prototype.handleMouseLeave_ = function(_e) { - if (this.getElement()) { - this.blur_(); - this.setHighlighted(null); + /** + * Handles mouse leave events. Blur and clear highlight. + * @param {!Event} _e Mouse event to handle. + * @private + */ + handleMouseLeave_(_e) { + if (this.getElement()) { + this.blur_(); + this.setHighlighted(null); + } } -}; -// Keyboard events. + // Keyboard events. -/** - * Attempts to handle a keyboard event, if the menu item is enabled, by calling - * {@link handleKeyEventInternal_}. - * @param {!Event} e Key event to handle. - * @private - */ -Menu.prototype.handleKeyEvent_ = function(e) { - if (!this.menuItems_.length) { - // Empty menu. - return; - } - if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) { - // Do not handle the key event if any modifier key is pressed. - return; - } + /** + * Attempts to handle a keyboard event, if the menu item is enabled, by calling + * {@link handleKeyEventInternal_}. + * @param {!Event} e Key event to handle. + * @private + */ + handleKeyEvent_(e) { + if (!this.menuItems_.length) { + // Empty menu. + return; + } + if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) { + // Do not handle the key event if any modifier key is pressed. + return; + } - const highlighted = this.highlightedItem_; - switch (e.keyCode) { - case KeyCodes.ENTER: - case KeyCodes.SPACE: - if (highlighted) { - highlighted.performAction(); - } - break; + const highlighted = this.highlightedItem_; + switch (e.keyCode) { + case KeyCodes.ENTER: + case KeyCodes.SPACE: + if (highlighted) { + highlighted.performAction(); + } + break; - case KeyCodes.UP: - this.highlightPrevious(); - break; + case KeyCodes.UP: + this.highlightPrevious(); + break; - case KeyCodes.DOWN: - this.highlightNext(); - break; + case KeyCodes.DOWN: + this.highlightNext(); + break; - case KeyCodes.PAGE_UP: - case KeyCodes.HOME: - this.highlightFirst_(); - break; + case KeyCodes.PAGE_UP: + case KeyCodes.HOME: + this.highlightFirst_(); + break; - case KeyCodes.PAGE_DOWN: - case KeyCodes.END: - this.highlightLast_(); - break; + case KeyCodes.PAGE_DOWN: + case KeyCodes.END: + this.highlightLast_(); + break; - default: - // Not a key the menu is interested in. - return; + default: + // Not a key the menu is interested in. + return; + } + // The menu used this key, don't let it have secondary effects. + e.preventDefault(); + e.stopPropagation(); } - // The menu used this key, don't let it have secondary effects. - e.preventDefault(); - e.stopPropagation(); -}; -/** - * Get the size of a rendered menu. - * @return {!Size} Object with width and height properties. - * @package - */ -Menu.prototype.getSize = function() { - const menuDom = this.getElement(); - const menuSize = style.getSize(/** @type {!Element} */ - (menuDom)); - // Recalculate height for the total content, not only box height. - menuSize.height = menuDom.scrollHeight; - return menuSize; + /** + * Get the size of a rendered menu. + * @return {!Size} Object with width and height properties. + * @package + */ + getSize() { + const menuDom = this.getElement(); + const menuSize = style.getSize(/** @type {!Element} */ + (menuDom)); + // Recalculate height for the total content, not only box height. + menuSize.height = menuDom.scrollHeight; + return menuSize; + } }; exports.Menu = Menu; From b6512098752799940092c9afd172b55e756d96bc Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 7 Jan 2022 16:09:31 -0800 Subject: [PATCH 2/6] refactor: convert menuitem.js to ES6 class --- core/menuitem.js | 437 ++++++++++++++++++++++++----------------------- 1 file changed, 219 insertions(+), 218 deletions(-) diff --git a/core/menuitem.js b/core/menuitem.js index f82f4992259..cd78029dec9 100644 --- a/core/menuitem.js +++ b/core/menuitem.js @@ -22,265 +22,266 @@ const idGenerator = goog.require('Blockly.utils.idGenerator'); /** * Class representing an item in a menu. - * - * @param {string|!HTMLElement} content Text caption to display as the content - * of the item, or a HTML element to display. - * @param {string=} opt_value Data/model associated with the menu item. - * @constructor - * @alias Blockly.MenuItem */ -const MenuItem = function(content, opt_value) { +const MenuItem = class { /** - * Human-readable text of this menu item, or the HTML element to display. - * @type {string|!HTMLElement} - * @private + * @param {string|!HTMLElement} content Text caption to display as the content + * of the item, or a HTML element to display. + * @param {string=} opt_value Data/model associated with the menu item. + * @alias Blockly.MenuItem */ - this.content_ = content; + constructor(content, opt_value) { + /** + * Human-readable text of this menu item, or the HTML element to display. + * @type {string|!HTMLElement} + * @private + */ + this.content_ = content; + + /** + * Machine-readable value of this menu item. + * @type {string|undefined} + * @private + */ + this.value_ = opt_value; + + /** + * Is the menu item clickable, as opposed to greyed-out. + * @type {boolean} + * @private + */ + this.enabled_ = true; + + /** + * The DOM element for the menu item. + * @type {?Element} + * @private + */ + this.element_ = null; + + /** + * Whether the menu item is rendered right-to-left. + * @type {boolean} + * @private + */ + this.rightToLeft_ = false; + + /** + * ARIA name for this menu. + * @type {?aria.Role} + * @private + */ + this.roleName_ = null; + + /** + * Is this menu item checkable. + * @type {boolean} + * @private + */ + this.checkable_ = false; + + /** + * Is this menu item currently checked. + * @type {boolean} + * @private + */ + this.checked_ = false; + + /** + * Is this menu item currently highlighted. + * @type {boolean} + * @private + */ + this.highlight_ = false; + + /** + * Bound function to call when this menu item is clicked. + * @type {?Function} + * @private + */ + this.actionHandler_ = null; + } /** - * Machine-readable value of this menu item. - * @type {string|undefined} - * @private + * Creates the menuitem's DOM. + * @return {!Element} Completed DOM. */ - this.value_ = opt_value; + createDom() { + const element = document.createElement('div'); + element.id = idGenerator.getNextUniqueId(); + this.element_ = element; + + // Set class and style + // goog-menuitem* is deprecated, use blocklyMenuItem*. May 2020. + element.className = 'blocklyMenuItem goog-menuitem ' + + (this.enabled_ ? '' : 'blocklyMenuItemDisabled goog-menuitem-disabled ') + + (this.checked_ ? 'blocklyMenuItemSelected goog-option-selected ' : '') + + (this.highlight_ ? 'blocklyMenuItemHighlight goog-menuitem-highlight ' : + '') + + (this.rightToLeft_ ? 'blocklyMenuItemRtl goog-menuitem-rtl ' : ''); + + const content = document.createElement('div'); + content.className = 'blocklyMenuItemContent goog-menuitem-content'; + // Add a checkbox for checkable menu items. + if (this.checkable_) { + const checkbox = document.createElement('div'); + checkbox.className = 'blocklyMenuItemCheckbox goog-menuitem-checkbox'; + content.appendChild(checkbox); + } + + let contentDom = /** @type {!HTMLElement} */ (this.content_); + if (typeof this.content_ === 'string') { + contentDom = document.createTextNode(this.content_); + } + content.appendChild(contentDom); + element.appendChild(content); + + // Initialize ARIA role and state. + if (this.roleName_) { + aria.setRole(element, this.roleName_); + } + aria.setState( + element, aria.State.SELECTED, + (this.checkable_ && this.checked_) || false); + aria.setState(element, aria.State.DISABLED, !this.enabled_); + + return element; + } /** - * Is the menu item clickable, as opposed to greyed-out. - * @type {boolean} - * @private + * Dispose of this menu item. */ - this.enabled_ = true; + dispose() { + this.element_ = null; + } /** - * The DOM element for the menu item. - * @type {?Element} - * @private + * Gets the menu item's element. + * @return {?Element} The DOM element. + * @package */ - this.element_ = null; + getElement() { + return this.element_; + } /** - * Whether the menu item is rendered right-to-left. - * @type {boolean} - * @private + * Gets the unique ID for this menu item. + * @return {string} Unique component ID. + * @package */ - this.rightToLeft_ = false; + getId() { + return this.element_.id; + } /** - * ARIA name for this menu. - * @type {?aria.Role} - * @private + * Gets the value associated with the menu item. + * @return {*} value Value associated with the menu item. + * @package */ - this.roleName_ = null; + getValue() { + return this.value_; + } /** - * Is this menu item checkable. - * @type {boolean} - * @private + * Set menu item's rendering direction. + * @param {boolean} rtl True if RTL, false if LTR. + * @package */ - this.checkable_ = false; + setRightToLeft(rtl) { + this.rightToLeft_ = rtl; + } /** - * Is this menu item currently checked. - * @type {boolean} - * @private + * Set the menu item's accessibility role. + * @param {!aria.Role} roleName Role name. + * @package */ - this.checked_ = false; + setRole(roleName) { + this.roleName_ = roleName; + } /** - * Is this menu item currently highlighted. - * @type {boolean} - * @private + * Sets the menu item to be checkable or not. Set to true for menu items + * that represent checkable options. + * @param {boolean} checkable Whether the menu item is checkable. + * @package */ - this.highlight_ = false; + setCheckable(checkable) { + this.checkable_ = checkable; + } /** - * Bound function to call when this menu item is clicked. - * @type {?Function} - * @private + * Checks or unchecks the component. + * @param {boolean} checked Whether to check or uncheck the component. + * @package */ - this.actionHandler_ = null; -}; - - -/** - * Creates the menuitem's DOM. - * @return {!Element} Completed DOM. - */ -MenuItem.prototype.createDom = function() { - const element = document.createElement('div'); - element.id = idGenerator.getNextUniqueId(); - this.element_ = element; - - // Set class and style - // goog-menuitem* is deprecated, use blocklyMenuItem*. May 2020. - element.className = 'blocklyMenuItem goog-menuitem ' + - (this.enabled_ ? '' : 'blocklyMenuItemDisabled goog-menuitem-disabled ') + - (this.checked_ ? 'blocklyMenuItemSelected goog-option-selected ' : '') + - (this.highlight_ ? 'blocklyMenuItemHighlight goog-menuitem-highlight ' : - '') + - (this.rightToLeft_ ? 'blocklyMenuItemRtl goog-menuitem-rtl ' : ''); - - const content = document.createElement('div'); - content.className = 'blocklyMenuItemContent goog-menuitem-content'; - // Add a checkbox for checkable menu items. - if (this.checkable_) { - const checkbox = document.createElement('div'); - checkbox.className = 'blocklyMenuItemCheckbox goog-menuitem-checkbox'; - content.appendChild(checkbox); + setChecked(checked) { + this.checked_ = checked; } - let contentDom = /** @type {!HTMLElement} */ (this.content_); - if (typeof this.content_ === 'string') { - contentDom = document.createTextNode(this.content_); + /** + * Highlights or unhighlights the component. + * @param {boolean} highlight Whether to highlight or unhighlight the component. + * @package + */ + setHighlighted(highlight) { + this.highlight_ = highlight; + + const el = this.getElement(); + if (el && this.isEnabled()) { + // goog-menuitem-highlight is deprecated, use blocklyMenuItemHighlight. + // May 2020. + const name = 'blocklyMenuItemHighlight'; + const nameDep = 'goog-menuitem-highlight'; + if (highlight) { + dom.addClass(el, name); + dom.addClass(el, nameDep); + } else { + dom.removeClass(el, name); + dom.removeClass(el, nameDep); + } + } } - content.appendChild(contentDom); - element.appendChild(content); - // Initialize ARIA role and state. - if (this.roleName_) { - aria.setRole(element, this.roleName_); + /** + * Returns true if the menu item is enabled, false otherwise. + * @return {boolean} Whether the menu item is enabled. + * @package + */ + isEnabled() { + return this.enabled_; } - aria.setState( - element, aria.State.SELECTED, - (this.checkable_ && this.checked_) || false); - aria.setState(element, aria.State.DISABLED, !this.enabled_); - - return element; -}; - -/** - * Dispose of this menu item. - */ -MenuItem.prototype.dispose = function() { - this.element_ = null; -}; - -/** - * Gets the menu item's element. - * @return {?Element} The DOM element. - * @package - */ -MenuItem.prototype.getElement = function() { - return this.element_; -}; - -/** - * Gets the unique ID for this menu item. - * @return {string} Unique component ID. - * @package - */ -MenuItem.prototype.getId = function() { - return this.element_.id; -}; - -/** - * Gets the value associated with the menu item. - * @return {*} value Value associated with the menu item. - * @package - */ -MenuItem.prototype.getValue = function() { - return this.value_; -}; - -/** - * Set menu item's rendering direction. - * @param {boolean} rtl True if RTL, false if LTR. - * @package - */ -MenuItem.prototype.setRightToLeft = function(rtl) { - this.rightToLeft_ = rtl; -}; -/** - * Set the menu item's accessibility role. - * @param {!aria.Role} roleName Role name. - * @package - */ -MenuItem.prototype.setRole = function(roleName) { - this.roleName_ = roleName; -}; - -/** - * Sets the menu item to be checkable or not. Set to true for menu items - * that represent checkable options. - * @param {boolean} checkable Whether the menu item is checkable. - * @package - */ -MenuItem.prototype.setCheckable = function(checkable) { - this.checkable_ = checkable; -}; - -/** - * Checks or unchecks the component. - * @param {boolean} checked Whether to check or uncheck the component. - * @package - */ -MenuItem.prototype.setChecked = function(checked) { - this.checked_ = checked; -}; + /** + * Enables or disables the menu item. + * @param {boolean} enabled Whether to enable or disable the menu item. + * @package + */ + setEnabled(enabled) { + this.enabled_ = enabled; + } -/** - * Highlights or unhighlights the component. - * @param {boolean} highlight Whether to highlight or unhighlight the component. - * @package - */ -MenuItem.prototype.setHighlighted = function(highlight) { - this.highlight_ = highlight; - - const el = this.getElement(); - if (el && this.isEnabled()) { - // goog-menuitem-highlight is deprecated, use blocklyMenuItemHighlight. - // May 2020. - const name = 'blocklyMenuItemHighlight'; - const nameDep = 'goog-menuitem-highlight'; - if (highlight) { - dom.addClass(el, name); - dom.addClass(el, nameDep); - } else { - dom.removeClass(el, name); - dom.removeClass(el, nameDep); + /** + * Performs the appropriate action when the menu item is activated + * by the user. + * @package + */ + performAction() { + if (this.isEnabled() && this.actionHandler_) { + this.actionHandler_(this); } } -}; - -/** - * Returns true if the menu item is enabled, false otherwise. - * @return {boolean} Whether the menu item is enabled. - * @package - */ -MenuItem.prototype.isEnabled = function() { - return this.enabled_; -}; - -/** - * Enables or disables the menu item. - * @param {boolean} enabled Whether to enable or disable the menu item. - * @package - */ -MenuItem.prototype.setEnabled = function(enabled) { - this.enabled_ = enabled; -}; -/** - * Performs the appropriate action when the menu item is activated - * by the user. - * @package - */ -MenuItem.prototype.performAction = function() { - if (this.isEnabled() && this.actionHandler_) { - this.actionHandler_(this); + /** + * Set the handler that's called when the menu item is activated by the user. + * `obj` will be used as the 'this' object in the function when called. + * @param {function(!MenuItem)} fn The handler. + * @param {!Object} obj Used as the 'this' object in fn when called. + * @package + */ + onAction(fn, obj) { + this.actionHandler_ = fn.bind(obj); } }; -/** - * Set the handler that's called when the menu item is activated by the user. - * `obj` will be used as the 'this' object in the function when called. - * @param {function(!MenuItem)} fn The handler. - * @param {!Object} obj Used as the 'this' object in fn when called. - * @package - */ -MenuItem.prototype.onAction = function(fn, obj) { - this.actionHandler_ = fn.bind(obj); -}; - exports.MenuItem = MenuItem; From e1d3507592bf750d98b5d6bbacbe752227fba769 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 7 Jan 2022 16:12:32 -0800 Subject: [PATCH 3/6] refactor: convert scrollbar_pair.js to ES6 class --- core/scrollbar_pair.js | 495 +++++++++++++++++++++-------------------- 1 file changed, 249 insertions(+), 246 deletions(-) diff --git a/core/scrollbar_pair.js b/core/scrollbar_pair.js index 52de6e413ed..56676c8f7b2 100644 --- a/core/scrollbar_pair.js +++ b/core/scrollbar_pair.js @@ -27,297 +27,300 @@ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); /** * Class for a pair of scrollbars. Horizontal and vertical. - * @param {!WorkspaceSvg} workspace Workspace to bind the scrollbars to. - * @param {boolean=} addHorizontal Whether to add a horizontal scrollbar. - * Defaults to true. - * @param {boolean=} addVertical Whether to add a vertical scrollbar. Defaults - * to true. - * @param {string=} opt_class A class to be applied to these scrollbars. - * @param {number=} opt_margin The margin to apply to these scrollbars. - * @constructor - * @alias Blockly.ScrollbarPair */ -const ScrollbarPair = function( - workspace, addHorizontal, addVertical, opt_class, opt_margin) { +const ScrollbarPair = class { /** - * The workspace this scrollbar pair is bound to. - * @type {!WorkspaceSvg} - * @private + * @param {!WorkspaceSvg} workspace Workspace to bind the scrollbars to. + * @param {boolean=} addHorizontal Whether to add a horizontal scrollbar. + * Defaults to true. + * @param {boolean=} addVertical Whether to add a vertical scrollbar. Defaults + * to true. + * @param {string=} opt_class A class to be applied to these scrollbars. + * @param {number=} opt_margin The margin to apply to these scrollbars. + * @alias Blockly.ScrollbarPair */ - this.workspace_ = workspace; + constructor( + workspace, addHorizontal, addVertical, opt_class, opt_margin) { + /** + * The workspace this scrollbar pair is bound to. + * @type {!WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; - addHorizontal = addHorizontal === undefined ? true : addHorizontal; - addVertical = addVertical === undefined ? true : addVertical; - const isPair = addHorizontal && addVertical; + addHorizontal = addHorizontal === undefined ? true : addHorizontal; + addVertical = addVertical === undefined ? true : addVertical; + const isPair = addHorizontal && addVertical; - if (addHorizontal) { - this.hScroll = - new Scrollbar(workspace, true, isPair, opt_class, opt_margin); - } - if (addVertical) { - this.vScroll = - new Scrollbar(workspace, false, isPair, opt_class, opt_margin); - } + if (addHorizontal) { + this.hScroll = + new Scrollbar(workspace, true, isPair, opt_class, opt_margin); + } + if (addVertical) { + this.vScroll = + new Scrollbar(workspace, false, isPair, opt_class, opt_margin); + } - if (isPair) { - this.corner_ = dom.createSvgElement( - Svg.RECT, { - 'height': Scrollbar.scrollbarThickness, - 'width': Scrollbar.scrollbarThickness, - 'class': 'blocklyScrollbarBackground', - }, - null); - dom.insertAfter(this.corner_, workspace.getBubbleCanvas()); + if (isPair) { + this.corner_ = dom.createSvgElement( + Svg.RECT, { + 'height': Scrollbar.scrollbarThickness, + 'width': Scrollbar.scrollbarThickness, + 'class': 'blocklyScrollbarBackground', + }, + null); + dom.insertAfter(this.corner_, workspace.getBubbleCanvas()); + } + + /** + * Previously recorded metrics from the workspace. + * @type {?Metrics} + * @private + */ + this.oldHostMetrics_ = null; } /** - * Previously recorded metrics from the workspace. - * @type {?Metrics} - * @private + * Dispose of this pair of scrollbars. + * Unlink from all DOM elements to prevent memory leaks. + * @suppress {checkTypes} */ - this.oldHostMetrics_ = null; -}; - -/** - * Dispose of this pair of scrollbars. - * Unlink from all DOM elements to prevent memory leaks. - * @suppress {checkTypes} - */ -ScrollbarPair.prototype.dispose = function() { - dom.removeNode(this.corner_); - this.corner_ = null; - this.workspace_ = null; - this.oldHostMetrics_ = null; - if (this.hScroll) { - this.hScroll.dispose(); - this.hScroll = null; - } - if (this.vScroll) { - this.vScroll.dispose(); - this.vScroll = null; - } -}; - -/** - * Recalculate both of the scrollbars' locations and lengths. - * Also reposition the corner rectangle. - */ -ScrollbarPair.prototype.resize = function() { - // Look up the host metrics once, and use for both scrollbars. - const hostMetrics = this.workspace_.getMetrics(); - if (!hostMetrics) { - // Host element is likely not visible. - return; - } - - // Only change the scrollbars if there has been a change in metrics. - let resizeH = false; - let resizeV = false; - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.viewWidth !== hostMetrics.viewWidth || - this.oldHostMetrics_.viewHeight !== hostMetrics.viewHeight || - this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop || - this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft) { - // The window has been resized or repositioned. - resizeH = true; - resizeV = true; - } else { - // Has the content been resized or moved? - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.scrollWidth !== hostMetrics.scrollWidth || - this.oldHostMetrics_.viewLeft !== hostMetrics.viewLeft || - this.oldHostMetrics_.scrollLeft !== hostMetrics.scrollLeft) { - resizeH = true; + dispose() { + dom.removeNode(this.corner_); + this.corner_ = null; + this.workspace_ = null; + this.oldHostMetrics_ = null; + if (this.hScroll) { + this.hScroll.dispose(); + this.hScroll = null; } - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.scrollHeight !== hostMetrics.scrollHeight || - this.oldHostMetrics_.viewTop !== hostMetrics.viewTop || - this.oldHostMetrics_.scrollTop !== hostMetrics.scrollTop) { - resizeV = true; + if (this.vScroll) { + this.vScroll.dispose(); + this.vScroll = null; } } - if (resizeH || resizeV) { - try { - eventUtils.disable(); - if (this.hScroll && resizeH) { - this.hScroll.resize(hostMetrics); - } - if (this.vScroll && resizeV) { - this.vScroll.resize(hostMetrics); - } - } finally { - eventUtils.enable(); + /** + * Recalculate both of the scrollbars' locations and lengths. + * Also reposition the corner rectangle. + */ + resize() { + // Look up the host metrics once, and use for both scrollbars. + const hostMetrics = this.workspace_.getMetrics(); + if (!hostMetrics) { + // Host element is likely not visible. + return; } - this.workspace_.maybeFireViewportChangeEvent(); - } - if (this.hScroll && this.vScroll) { - // Reposition the corner square. + // Only change the scrollbars if there has been a change in metrics. + let resizeH = false; + let resizeV = false; if (!this.oldHostMetrics_ || this.oldHostMetrics_.viewWidth !== hostMetrics.viewWidth || - this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft) { - this.corner_.setAttribute('x', this.vScroll.position.x); - } - if (!this.oldHostMetrics_ || this.oldHostMetrics_.viewHeight !== hostMetrics.viewHeight || - this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop) { - this.corner_.setAttribute('y', this.hScroll.position.y); + this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop || + this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft) { + // The window has been resized or repositioned. + resizeH = true; + resizeV = true; + } else { + // Has the content been resized or moved? + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.scrollWidth !== hostMetrics.scrollWidth || + this.oldHostMetrics_.viewLeft !== hostMetrics.viewLeft || + this.oldHostMetrics_.scrollLeft !== hostMetrics.scrollLeft) { + resizeH = true; + } + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.scrollHeight !== hostMetrics.scrollHeight || + this.oldHostMetrics_.viewTop !== hostMetrics.viewTop || + this.oldHostMetrics_.scrollTop !== hostMetrics.scrollTop) { + resizeV = true; + } } - } - - // Cache the current metrics to potentially short-cut the next resize event. - this.oldHostMetrics_ = hostMetrics; -}; -/** - * Returns whether scrolling horizontally is enabled. - * @return {boolean} True if horizontal scroll is enabled. - */ -ScrollbarPair.prototype.canScrollHorizontally = function() { - return !!this.hScroll; -}; + if (resizeH || resizeV) { + try { + eventUtils.disable(); + if (this.hScroll && resizeH) { + this.hScroll.resize(hostMetrics); + } + if (this.vScroll && resizeV) { + this.vScroll.resize(hostMetrics); + } + } finally { + eventUtils.enable(); + } + this.workspace_.maybeFireViewportChangeEvent(); + } -/** - * Returns whether scrolling vertically is enabled. - * @return {boolean} True if vertical scroll is enabled. - */ -ScrollbarPair.prototype.canScrollVertically = function() { - return !!this.vScroll; -}; + if (this.hScroll && this.vScroll) { + // Reposition the corner square. + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.viewWidth !== hostMetrics.viewWidth || + this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft) { + this.corner_.setAttribute('x', this.vScroll.position.x); + } + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.viewHeight !== hostMetrics.viewHeight || + this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop) { + this.corner_.setAttribute('y', this.hScroll.position.y); + } + } -/** - * Record the origin of the workspace that the scrollbar is in, in pixels - * relative to the injection div origin. This is for times when the scrollbar is - * used in an object whose origin isn't the same as the main workspace - * (e.g. in a flyout.) - * @param {number} x The x coordinate of the scrollbar's origin, in CSS pixels. - * @param {number} y The y coordinate of the scrollbar's origin, in CSS pixels. - * @package - */ -ScrollbarPair.prototype.setOrigin = function(x, y) { - if (this.hScroll) { - this.hScroll.setOrigin(x, y); - } - if (this.vScroll) { - this.vScroll.setOrigin(x, y); + // Cache the current metrics to potentially short-cut the next resize event. + this.oldHostMetrics_ = hostMetrics; } -}; -/** - * Set the handles of both scrollbars. - * @param {number} x The horizontal content displacement, relative to the view - * in pixels. - * @param {number} y The vertical content displacement, relative to the view in - * pixels. - * @param {boolean} updateMetrics Whether to update metrics on this set call. - * Defaults to true. - */ -ScrollbarPair.prototype.set = function(x, y, updateMetrics) { - // This function is equivalent to: - // this.hScroll.set(x); - // this.vScroll.set(y); - // However, that calls setMetrics twice which causes a chain of - // getAttribute->setAttribute->getAttribute resulting in an extra layout pass. - // Combining them speeds up rendering. - if (this.hScroll) { - this.hScroll.set(x, false); + /** + * Returns whether scrolling horizontally is enabled. + * @return {boolean} True if horizontal scroll is enabled. + */ + canScrollHorizontally() { + return !!this.hScroll; } - if (this.vScroll) { - this.vScroll.set(y, false); + + /** + * Returns whether scrolling vertically is enabled. + * @return {boolean} True if vertical scroll is enabled. + */ + canScrollVertically() { + return !!this.vScroll; } - if (updateMetrics || updateMetrics === undefined) { - // Update metrics. - const xyRatio = {}; + /** + * Record the origin of the workspace that the scrollbar is in, in pixels + * relative to the injection div origin. This is for times when the scrollbar is + * used in an object whose origin isn't the same as the main workspace + * (e.g. in a flyout.) + * @param {number} x The x coordinate of the scrollbar's origin, in CSS pixels. + * @param {number} y The y coordinate of the scrollbar's origin, in CSS pixels. + * @package + */ + setOrigin(x, y) { if (this.hScroll) { - xyRatio.x = this.hScroll.getRatio_(); + this.hScroll.setOrigin(x, y); } if (this.vScroll) { - xyRatio.y = this.vScroll.getRatio_(); + this.vScroll.setOrigin(x, y); } - this.workspace_.setMetrics(xyRatio); } -}; -/** - * Set the handle of the horizontal scrollbar to be at a certain position in - * CSS pixels relative to its parents. - * @param {number} x Horizontal scroll value. - */ -ScrollbarPair.prototype.setX = function(x) { - if (this.hScroll) { - this.hScroll.set(x, true); - } -}; + /** + * Set the handles of both scrollbars. + * @param {number} x The horizontal content displacement, relative to the view + * in pixels. + * @param {number} y The vertical content displacement, relative to the view in + * pixels. + * @param {boolean} updateMetrics Whether to update metrics on this set call. + * Defaults to true. + */ + set(x, y, updateMetrics) { + // This function is equivalent to: + // this.hScroll.set(x); + // this.vScroll.set(y); + // However, that calls setMetrics twice which causes a chain of + // getAttribute->setAttribute->getAttribute resulting in an extra layout pass. + // Combining them speeds up rendering. + if (this.hScroll) { + this.hScroll.set(x, false); + } + if (this.vScroll) { + this.vScroll.set(y, false); + } -/** - * Set the handle of the vertical scrollbar to be at a certain position in - * CSS pixels relative to its parents. - * @param {number} y Vertical scroll value. - */ -ScrollbarPair.prototype.setY = function(y) { - if (this.vScroll) { - this.vScroll.set(y, true); + if (updateMetrics || updateMetrics === undefined) { + // Update metrics. + const xyRatio = {}; + if (this.hScroll) { + xyRatio.x = this.hScroll.getRatio_(); + } + if (this.vScroll) { + xyRatio.y = this.vScroll.getRatio_(); + } + this.workspace_.setMetrics(xyRatio); + } } -}; -/** - * Set whether this scrollbar's container is visible. - * @param {boolean} visible Whether the container is visible. - */ -ScrollbarPair.prototype.setContainerVisible = function(visible) { - if (this.hScroll) { - this.hScroll.setContainerVisible(visible); - } - if (this.vScroll) { - this.vScroll.setContainerVisible(visible); + /** + * Set the handle of the horizontal scrollbar to be at a certain position in + * CSS pixels relative to its parents. + * @param {number} x Horizontal scroll value. + */ + setX(x) { + if (this.hScroll) { + this.hScroll.set(x, true); + } } -}; -/** - * If any of the scrollbars are visible. Non-paired scrollbars may disappear - * when they aren't needed. - * @return {boolean} True if visible. - */ -ScrollbarPair.prototype.isVisible = function() { - let isVisible = false; - if (this.hScroll) { - isVisible = this.hScroll.isVisible(); - } - if (this.vScroll) { - isVisible = isVisible || this.vScroll.isVisible(); + /** + * Set the handle of the vertical scrollbar to be at a certain position in + * CSS pixels relative to its parents. + * @param {number} y Vertical scroll value. + */ + setY(y) { + if (this.vScroll) { + this.vScroll.set(y, true); + } } - return isVisible; -}; -/** - * Recalculates the scrollbars' locations within their path and length. - * This should be called when the contents of the workspace have changed. - * @param {!Metrics} hostMetrics A data structure describing all - * the required dimensions, possibly fetched from the host object. - */ -ScrollbarPair.prototype.resizeContent = function(hostMetrics) { - if (this.hScroll) { - this.hScroll.resizeContentHorizontal(hostMetrics); + /** + * Set whether this scrollbar's container is visible. + * @param {boolean} visible Whether the container is visible. + */ + setContainerVisible(visible) { + if (this.hScroll) { + this.hScroll.setContainerVisible(visible); + } + if (this.vScroll) { + this.vScroll.setContainerVisible(visible); + } } - if (this.vScroll) { - this.vScroll.resizeContentVertical(hostMetrics); + + /** + * If any of the scrollbars are visible. Non-paired scrollbars may disappear + * when they aren't needed. + * @return {boolean} True if visible. + */ + isVisible() { + let isVisible = false; + if (this.hScroll) { + isVisible = this.hScroll.isVisible(); + } + if (this.vScroll) { + isVisible = isVisible || this.vScroll.isVisible(); + } + return isVisible; } -}; -/** - * Recalculates the scrollbars' locations on the screen and path length. - * This should be called when the layout or size of the window has changed. - * @param {!Metrics} hostMetrics A data structure describing all - * the required dimensions, possibly fetched from the host object. - */ -ScrollbarPair.prototype.resizeView = function(hostMetrics) { - if (this.hScroll) { - this.hScroll.resizeViewHorizontal(hostMetrics); + /** + * Recalculates the scrollbars' locations within their path and length. + * This should be called when the contents of the workspace have changed. + * @param {!Metrics} hostMetrics A data structure describing all + * the required dimensions, possibly fetched from the host object. + */ + resizeContent(hostMetrics) { + if (this.hScroll) { + this.hScroll.resizeContentHorizontal(hostMetrics); + } + if (this.vScroll) { + this.vScroll.resizeContentVertical(hostMetrics); + } } - if (this.vScroll) { - this.vScroll.resizeViewVertical(hostMetrics); + + /** + * Recalculates the scrollbars' locations on the screen and path length. + * This should be called when the layout or size of the window has changed. + * @param {!Metrics} hostMetrics A data structure describing all + * the required dimensions, possibly fetched from the host object. + */ + resizeView(hostMetrics) { + if (this.hScroll) { + this.hScroll.resizeViewHorizontal(hostMetrics); + } + if (this.vScroll) { + this.vScroll.resizeViewVertical(hostMetrics); + } } }; From f4caf45a5d2b736b3e69bc88db36e96bf7be851a Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 7 Jan 2022 16:32:40 -0800 Subject: [PATCH 4/6] chore: define properties in the constructor in names.js --- core/names.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/core/names.js b/core/names.js index 1fad1e00fe4..c8957d8c307 100644 --- a/core/names.js +++ b/core/names.js @@ -35,8 +35,42 @@ goog.requireType('Blockly.Procedures'); * @alias Blockly.Names */ const Names = function(reservedWords, opt_variablePrefix) { + /** + * The prefix to attach to variable names in generated code. + * @type {string} + * @private + */ this.variablePrefix_ = opt_variablePrefix || ''; + + /** + * A dictionary of reserved words. + * @type {Object} + * @private + */ this.reservedDict_ = Object.create(null); + + /** + * A map from type (e.g. name, procedure) to maps from names to generated + * names. + * @type {Object>} + * @private + */ + this.db_ = Object.create(null); + + /** + * A map from used names to booleans to avoid collisions. + * @type {Object} + * @private + */ + this.dbReverse_ = Object.create(null); + + /** + * The variable map from the workspace, containing Blockly variable models. + * @type {?VariableMap} + * @private + */ + this.variableMap_ = null; + if (reservedWords) { const splitWords = reservedWords.split(','); for (let i = 0; i < splitWords.length; i++) { From e6b62024ca04f204b99837be1660136072f6e4f9 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 7 Jan 2022 16:34:43 -0800 Subject: [PATCH 5/6] refactor: convert names.js to ES6 class --- core/names.js | 417 +++++++++++++++++++++++++------------------------- 1 file changed, 210 insertions(+), 207 deletions(-) diff --git a/core/names.js b/core/names.js index c8957d8c307..780ed65e7d9 100644 --- a/core/names.js +++ b/core/names.js @@ -27,57 +27,235 @@ goog.requireType('Blockly.Procedures'); /** * Class for a database of entity names (variables, procedures, etc). - * @param {string} reservedWords A comma-separated string of words that are - * illegal for use as names in a language (e.g. 'new,if,this,...'). - * @param {string=} opt_variablePrefix Some languages need a '$' or a namespace - * before all variable names (but not procedure names). - * @constructor - * @alias Blockly.Names */ -const Names = function(reservedWords, opt_variablePrefix) { +const Names = class { /** - * The prefix to attach to variable names in generated code. - * @type {string} - * @private + * @param {string} reservedWords A comma-separated string of words that are + * illegal for use as names in a language (e.g. 'new,if,this,...'). + * @param {string=} opt_variablePrefix Some languages need a '$' or a namespace + * before all variable names (but not procedure names). + * @alias Blockly.Names */ - this.variablePrefix_ = opt_variablePrefix || ''; + constructor(reservedWords, opt_variablePrefix) { + /** + * The prefix to attach to variable names in generated code. + * @type {string} + * @private + */ + this.variablePrefix_ = opt_variablePrefix || ''; + + /** + * A dictionary of reserved words. + * @type {Object} + * @private + */ + this.reservedDict_ = Object.create(null); + + /** + * A map from type (e.g. name, procedure) to maps from names to generated + * names. + * @type {Object>} + * @private + */ + this.db_ = Object.create(null); + + /** + * A map from used names to booleans to avoid collisions. + * @type {Object} + * @private + */ + this.dbReverse_ = Object.create(null); + + /** + * The variable map from the workspace, containing Blockly variable models. + * @type {?VariableMap} + * @private + */ + this.variableMap_ = null; + + if (reservedWords) { + const splitWords = reservedWords.split(','); + for (let i = 0; i < splitWords.length; i++) { + this.reservedDict_[splitWords[i]] = true; + } + } + this.reset(); + } /** - * A dictionary of reserved words. - * @type {Object} - * @private + * Empty the database and start from scratch. The reserved words are kept. */ - this.reservedDict_ = Object.create(null); + reset() { + this.db_ = Object.create(null); + this.dbReverse_ = Object.create(null); + this.variableMap_ = null; + } /** - * A map from type (e.g. name, procedure) to maps from names to generated - * names. - * @type {Object>} - * @private + * Set the variable map that maps from variable name to variable object. + * @param {!VariableMap} map The map to track. */ - this.db_ = Object.create(null); + setVariableMap(map) { + this.variableMap_ = map; + } /** - * A map from used names to booleans to avoid collisions. - * @type {Object} + * Get the name for a user-defined variable, based on its ID. + * This should only be used for variables of NameType VARIABLE. + * @param {string} id The ID to look up in the variable map. + * @return {?string} The name of the referenced variable, or null if there was + * no variable map or the variable was not found in the map. * @private */ - this.dbReverse_ = Object.create(null); + getNameForUserVariable_(id) { + if (!this.variableMap_) { + console.warn( + 'Deprecated call to Names.prototype.getName without ' + + 'defining a variable map. To fix, add the following code in your ' + + 'generator\'s init() function:\n' + + 'Blockly.YourGeneratorName.nameDB_.setVariableMap(' + + 'workspace.getVariableMap());'); + return null; + } + const variable = this.variableMap_.getVariableById(id); + if (variable) { + return variable.name; + } + return null; + } + + /** + * Generate names for user variables, but only ones that are being used. + * @param {!Workspace} workspace Workspace to generate variables from. + */ + populateVariables(workspace) { + const variables = Variables.allUsedVarModels(workspace); + for (let i = 0; i < variables.length; i++) { + this.getName(variables[i].getId(), NameType.VARIABLE); + } + } /** - * The variable map from the workspace, containing Blockly variable models. - * @type {?VariableMap} - * @private + * Generate names for procedures. + * @param {!Workspace} workspace Workspace to generate procedures from. + */ + populateProcedures(workspace) { + let procedures = + goog.module.get('Blockly.Procedures').allProcedures(workspace); + // Flatten the return vs no-return procedure lists. + procedures = procedures[0].concat(procedures[1]); + for (let i = 0; i < procedures.length; i++) { + this.getName(procedures[i][0], NameType.PROCEDURE); + } + } + + /** + * Convert a Blockly entity name to a legal exportable entity name. + * @param {string} nameOrId The Blockly entity name (no constraints) or + * variable ID. + * @param {NameType|string} type The type of the name in Blockly + * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). + * @return {string} An entity name that is legal in the exported language. + */ + getName(nameOrId, type) { + let name = nameOrId; + if (type === NameType.VARIABLE) { + const varName = this.getNameForUserVariable_(nameOrId); + if (varName) { + // Successful ID lookup. + name = varName; + } + } + const normalizedName = name.toLowerCase(); + + const isVar = + type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; + + const prefix = isVar ? this.variablePrefix_ : ''; + if (!(type in this.db_)) { + this.db_[type] = Object.create(null); + } + const typeDb = this.db_[type]; + if (normalizedName in typeDb) { + return prefix + typeDb[normalizedName]; + } + const safeName = this.getDistinctName(name, type); + typeDb[normalizedName] = safeName.substr(prefix.length); + return safeName; + } + + /** + * Return a list of all known user-created names of a specified name type. + * @param {NameType|string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). + * @return {!Array} A list of Blockly entity names (no constraints). + */ + getUserNames(type) { + const typeDb = this.db_[type] || {}; + return Object.keys(typeDb); + } + + /** + * Convert a Blockly entity name to a legal exportable entity name. + * Ensure that this is a new name not overlapping any previously defined name. + * Also check against list of reserved words for the current language and + * ensure name doesn't collide. + * @param {string} name The Blockly entity name (no constraints). + * @param {NameType|string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). + * @return {string} An entity name that is legal in the exported language. */ - this.variableMap_ = null; + getDistinctName(name, type) { + let safeName = this.safeName_(name); + let i = ''; + while (this.dbReverse_[safeName + i] || + (safeName + i) in this.reservedDict_) { + // Collision with existing name. Create a unique name. + i = i ? i + 1 : 2; + } + safeName += i; + this.dbReverse_[safeName] = true; + const isVar = + type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; + const prefix = isVar ? this.variablePrefix_ : ''; + return prefix + safeName; + } - if (reservedWords) { - const splitWords = reservedWords.split(','); - for (let i = 0; i < splitWords.length; i++) { - this.reservedDict_[splitWords[i]] = true; + /** + * Given a proposed entity name, generate a name that conforms to the + * [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for + * variable and function names. + * @param {string} name Potentially illegal entity name. + * @return {string} Safe entity name. + * @private + */ + safeName_(name) { + if (!name) { + name = Msg['UNNAMED_KEY'] || 'unnamed'; + } else { + // Unfortunately names in non-latin characters will look like + // _E9_9F_B3_E4_B9_90 which is pretty meaningless. + // https://github.com/google/blockly/issues/1654 + name = encodeURI(name.replace(/ /g, '_')).replace(/[^\w]/g, '_'); + // Most languages don't allow names with leading numbers. + if ('0123456789'.indexOf(name[0]) !== -1) { + name = 'my_' + name; + } } + return name; + } + + /** + * Do the given two entity names refer to the same entity? + * Blockly names are case-insensitive. + * @param {string} name1 First name. + * @param {string} name2 Second name. + * @return {boolean} True if names are the same. + */ + static equals(name1, name2) { + // name1.localeCompare(name2) is slower. + return name1.toLowerCase() === name2.toLowerCase(); } - this.reset(); }; /** @@ -108,179 +286,4 @@ exports.NameType = NameType; */ Names.DEVELOPER_VARIABLE_TYPE = NameType.DEVELOPER_VARIABLE; -/** - * Empty the database and start from scratch. The reserved words are kept. - */ -Names.prototype.reset = function() { - this.db_ = Object.create(null); - this.dbReverse_ = Object.create(null); - this.variableMap_ = null; -}; - -/** - * Set the variable map that maps from variable name to variable object. - * @param {!VariableMap} map The map to track. - */ -Names.prototype.setVariableMap = function(map) { - this.variableMap_ = map; -}; - -/** - * Get the name for a user-defined variable, based on its ID. - * This should only be used for variables of NameType VARIABLE. - * @param {string} id The ID to look up in the variable map. - * @return {?string} The name of the referenced variable, or null if there was - * no variable map or the variable was not found in the map. - * @private - */ -Names.prototype.getNameForUserVariable_ = function(id) { - if (!this.variableMap_) { - console.warn( - 'Deprecated call to Names.prototype.getName without ' + - 'defining a variable map. To fix, add the following code in your ' + - 'generator\'s init() function:\n' + - 'Blockly.YourGeneratorName.nameDB_.setVariableMap(' + - 'workspace.getVariableMap());'); - return null; - } - const variable = this.variableMap_.getVariableById(id); - if (variable) { - return variable.name; - } - return null; -}; - -/** - * Generate names for user variables, but only ones that are being used. - * @param {!Workspace} workspace Workspace to generate variables from. - */ -Names.prototype.populateVariables = function(workspace) { - const variables = Variables.allUsedVarModels(workspace); - for (let i = 0; i < variables.length; i++) { - this.getName(variables[i].getId(), NameType.VARIABLE); - } -}; - -/** - * Generate names for procedures. - * @param {!Workspace} workspace Workspace to generate procedures from. - */ -Names.prototype.populateProcedures = function(workspace) { - let procedures = - goog.module.get('Blockly.Procedures').allProcedures(workspace); - // Flatten the return vs no-return procedure lists. - procedures = procedures[0].concat(procedures[1]); - for (let i = 0; i < procedures.length; i++) { - this.getName(procedures[i][0], NameType.PROCEDURE); - } -}; - -/** - * Convert a Blockly entity name to a legal exportable entity name. - * @param {string} nameOrId The Blockly entity name (no constraints) or - * variable ID. - * @param {NameType|string} type The type of the name in Blockly - * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). - * @return {string} An entity name that is legal in the exported language. - */ -Names.prototype.getName = function(nameOrId, type) { - let name = nameOrId; - if (type === NameType.VARIABLE) { - const varName = this.getNameForUserVariable_(nameOrId); - if (varName) { - // Successful ID lookup. - name = varName; - } - } - const normalizedName = name.toLowerCase(); - - const isVar = - type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; - - const prefix = isVar ? this.variablePrefix_ : ''; - if (!(type in this.db_)) { - this.db_[type] = Object.create(null); - } - const typeDb = this.db_[type]; - if (normalizedName in typeDb) { - return prefix + typeDb[normalizedName]; - } - const safeName = this.getDistinctName(name, type); - typeDb[normalizedName] = safeName.substr(prefix.length); - return safeName; -}; - -/** - * Return a list of all known user-created names of a specified name type. - * @param {NameType|string} type The type of entity in Blockly - * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). - * @return {!Array} A list of Blockly entity names (no constraints). - */ -Names.prototype.getUserNames = function(type) { - const typeDb = this.db_[type] || {}; - return Object.keys(typeDb); -}; - -/** - * Convert a Blockly entity name to a legal exportable entity name. - * Ensure that this is a new name not overlapping any previously defined name. - * Also check against list of reserved words for the current language and - * ensure name doesn't collide. - * @param {string} name The Blockly entity name (no constraints). - * @param {NameType|string} type The type of entity in Blockly - * ('VARIABLE', 'PROCEDURE', 'DEVELOPER_VARIABLE', etc...). - * @return {string} An entity name that is legal in the exported language. - */ -Names.prototype.getDistinctName = function(name, type) { - let safeName = this.safeName_(name); - let i = ''; - while (this.dbReverse_[safeName + i] || - (safeName + i) in this.reservedDict_) { - // Collision with existing name. Create a unique name. - i = i ? i + 1 : 2; - } - safeName += i; - this.dbReverse_[safeName] = true; - const isVar = - type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; - const prefix = isVar ? this.variablePrefix_ : ''; - return prefix + safeName; -}; - -/** - * Given a proposed entity name, generate a name that conforms to the - * [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for - * variable and function names. - * @param {string} name Potentially illegal entity name. - * @return {string} Safe entity name. - * @private - */ -Names.prototype.safeName_ = function(name) { - if (!name) { - name = Msg['UNNAMED_KEY'] || 'unnamed'; - } else { - // Unfortunately names in non-latin characters will look like - // _E9_9F_B3_E4_B9_90 which is pretty meaningless. - // https://github.com/google/blockly/issues/1654 - name = encodeURI(name.replace(/ /g, '_')).replace(/[^\w]/g, '_'); - // Most languages don't allow names with leading numbers. - if ('0123456789'.indexOf(name[0]) !== -1) { - name = 'my_' + name; - } - } - return name; -}; - -/** - * Do the given two entity names refer to the same entity? - * Blockly names are case-insensitive. - * @param {string} name1 First name. - * @param {string} name2 Second name. - * @return {boolean} True if names are the same. - */ -Names.equals = function(name1, name2) { - // name1.localeCompare(name2) is slower. - return name1.toLowerCase() === name2.toLowerCase(); -}; - exports.Names = Names; From e6df6002526a29cd88db2ff438fb095813ce78d8 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 7 Jan 2022 16:35:04 -0800 Subject: [PATCH 6/6] chore: run clang-format --- core/menu.js | 16 +++++++++------- core/menuitem.js | 6 ++++-- core/names.js | 4 ++-- core/scrollbar_pair.js | 20 +++++++++++--------- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/core/menu.js b/core/menu.js index 77203c0ae0d..b4e3e137776 100644 --- a/core/menu.js +++ b/core/menu.js @@ -46,8 +46,8 @@ const Menu = class { /** * Coordinates of the mousedown event that caused this menu to open. Used to - * prevent the consequent mouseup event due to a simple click from activating - * a menu item immediately. + * prevent the consequent mouseup event due to a simple click from + * activating a menu item immediately. * @type {?Coordinate} * @package */ @@ -285,8 +285,8 @@ const Menu = class { } /** - * Highlights the next highlightable item (or the first if nothing is currently - * highlighted). + * Highlights the next highlightable item (or the first if nothing is + * currently highlighted). * @package */ highlightNext() { @@ -374,8 +374,9 @@ const Menu = class { if (Coordinate.distance(oldCoords, newCoords) < 1) { // This menu was opened by a mousedown and we're handling the consequent // click event. The coords haven't changed, meaning this was the same - // opening event. Don't do the usual behavior because the menu just popped - // up under the mouse and the user didn't mean to activate this item. + // opening event. Don't do the usual behavior because the menu just + // popped up under the mouse and the user didn't mean to activate this + // item. return; } } @@ -410,7 +411,8 @@ const Menu = class { // Keyboard events. /** - * Attempts to handle a keyboard event, if the menu item is enabled, by calling + * Attempts to handle a keyboard event, if the menu item is enabled, by + * calling * {@link handleKeyEventInternal_}. * @param {!Event} e Key event to handle. * @private diff --git a/core/menuitem.js b/core/menuitem.js index cd78029dec9..8d86ab1ab3d 100644 --- a/core/menuitem.js +++ b/core/menuitem.js @@ -114,7 +114,8 @@ const MenuItem = class { // Set class and style // goog-menuitem* is deprecated, use blocklyMenuItem*. May 2020. element.className = 'blocklyMenuItem goog-menuitem ' + - (this.enabled_ ? '' : 'blocklyMenuItemDisabled goog-menuitem-disabled ') + + (this.enabled_ ? '' : + 'blocklyMenuItemDisabled goog-menuitem-disabled ') + (this.checked_ ? 'blocklyMenuItemSelected goog-option-selected ' : '') + (this.highlight_ ? 'blocklyMenuItemHighlight goog-menuitem-highlight ' : '') + @@ -221,7 +222,8 @@ const MenuItem = class { /** * Highlights or unhighlights the component. - * @param {boolean} highlight Whether to highlight or unhighlight the component. + * @param {boolean} highlight Whether to highlight or unhighlight the + * component. * @package */ setHighlighted(highlight) { diff --git a/core/names.js b/core/names.js index 780ed65e7d9..f8c07322035 100644 --- a/core/names.js +++ b/core/names.js @@ -32,8 +32,8 @@ const Names = class { /** * @param {string} reservedWords A comma-separated string of words that are * illegal for use as names in a language (e.g. 'new,if,this,...'). - * @param {string=} opt_variablePrefix Some languages need a '$' or a namespace - * before all variable names (but not procedure names). + * @param {string=} opt_variablePrefix Some languages need a '$' or a + * namespace before all variable names (but not procedure names). * @alias Blockly.Names */ constructor(reservedWords, opt_variablePrefix) { diff --git a/core/scrollbar_pair.js b/core/scrollbar_pair.js index 56676c8f7b2..b53813dc02d 100644 --- a/core/scrollbar_pair.js +++ b/core/scrollbar_pair.js @@ -39,8 +39,7 @@ const ScrollbarPair = class { * @param {number=} opt_margin The margin to apply to these scrollbars. * @alias Blockly.ScrollbarPair */ - constructor( - workspace, addHorizontal, addVertical, opt_class, opt_margin) { + constructor(workspace, addHorizontal, addVertical, opt_class, opt_margin) { /** * The workspace this scrollbar pair is bound to. * @type {!WorkspaceSvg} @@ -190,11 +189,13 @@ const ScrollbarPair = class { /** * Record the origin of the workspace that the scrollbar is in, in pixels - * relative to the injection div origin. This is for times when the scrollbar is - * used in an object whose origin isn't the same as the main workspace + * relative to the injection div origin. This is for times when the scrollbar + * is used in an object whose origin isn't the same as the main workspace * (e.g. in a flyout.) - * @param {number} x The x coordinate of the scrollbar's origin, in CSS pixels. - * @param {number} y The y coordinate of the scrollbar's origin, in CSS pixels. + * @param {number} x The x coordinate of the scrollbar's origin, in CSS + * pixels. + * @param {number} y The y coordinate of the scrollbar's origin, in CSS + * pixels. * @package */ setOrigin(x, y) { @@ -210,7 +211,8 @@ const ScrollbarPair = class { * Set the handles of both scrollbars. * @param {number} x The horizontal content displacement, relative to the view * in pixels. - * @param {number} y The vertical content displacement, relative to the view in + * @param {number} y The vertical content displacement, relative to the view + * in * pixels. * @param {boolean} updateMetrics Whether to update metrics on this set call. * Defaults to true. @@ -220,8 +222,8 @@ const ScrollbarPair = class { // this.hScroll.set(x); // this.vScroll.set(y); // However, that calls setMetrics twice which causes a chain of - // getAttribute->setAttribute->getAttribute resulting in an extra layout pass. - // Combining them speeds up rendering. + // getAttribute->setAttribute->getAttribute resulting in an extra layout + // pass. Combining them speeds up rendering. if (this.hScroll) { this.hScroll.set(x, false); }