diff --git a/core/toolbox/category.js b/core/toolbox/category.js index 97df680f355..ddb202bbf6e 100644 --- a/core/toolbox/category.js +++ b/core/toolbox/category.js @@ -34,612 +34,633 @@ const {ToolboxItem} = goog.require('Blockly.ToolboxItem'); /** * Class for a category in a toolbox. - * @param {!toolbox.CategoryInfo} categoryDef The information needed - * to create a category in the toolbox. - * @param {!IToolbox} toolbox The parent toolbox for the category. - * @param {ICollapsibleToolboxItem=} opt_parent The parent category or null if - * the category does not have a parent. - * @constructor - * @extends {ToolboxItem} * @implements {ISelectableToolboxItem} - * @alias Blockly.ToolboxCategory */ -const ToolboxCategory = function(categoryDef, toolbox, opt_parent) { - ToolboxCategory.superClass_.constructor.call( - this, categoryDef, toolbox, opt_parent); +class ToolboxCategory extends ToolboxItem { + /** + * @param {!toolbox.CategoryInfo} categoryDef The information needed + * to create a category in the toolbox. + * @param {!IToolbox} toolbox The parent toolbox for the category. + * @param {ICollapsibleToolboxItem=} opt_parent The parent category or null if + * the category does not have a parent. + * @alias Blockly.ToolboxCategory + */ + constructor(categoryDef, toolbox, opt_parent) { + super(categoryDef, toolbox, opt_parent); + + /** @type {!toolbox.CategoryInfo} */ + this.toolboxItemDef_; + + /** + * The name that will be displayed on the category. + * @type {string} + * @protected + */ + this.name_ = ''; + + /** + * The colour of the category. + * @type {string} + * @protected + */ + this.colour_ = ''; + + /** + * The html container for the category. + * @type {?Element} + * @protected + */ + this.htmlDiv_ = null; + + /** + * The html element for the category row. + * @type {?Element} + * @protected + */ + this.rowDiv_ = null; + + /** + * The html element that holds children elements of the category row. + * @type {?Element} + * @protected + */ + this.rowContents_ = null; + + /** + * The html element for the toolbox icon. + * @type {?Element} + * @protected + */ + this.iconDom_ = null; + + /** + * The html element for the toolbox label. + * @type {?Element} + * @protected + */ + this.labelDom_ = null; + + /** + * All the css class names that are used to create a category. + * @type {!ToolboxCategory.CssConfig} + * @protected + */ + this.cssConfig_ = this.makeDefaultCssConfig_(); + + /** + * True if the category is meant to be hidden, false otherwise. + * @type {boolean} + * @protected + */ + this.isHidden_ = false; + + /** + * True if this category is disabled, false otherwise. + * @type {boolean} + * @protected + */ + this.isDisabled_ = false; + + /** + * The flyout items for this category. + * @type {string|!toolbox.FlyoutItemInfoArray} + * @protected + */ + this.flyoutItems_ = []; + } + + /** + * Initializes the toolbox item. + * This includes creating the DOM and updating the state of any items based + * on the info object. + * Init should be called immediately after the construction of the toolbox + * item, to ensure that the category contents are properly parsed. + * @override + */ + init() { + this.parseCategoryDef_(this.toolboxItemDef_); + this.parseContents_(this.toolboxItemDef_); + this.createDom_(); + if (this.toolboxItemDef_['hidden'] === 'true') { + this.hide(); + } + } + /** - * The name that will be displayed on the category. - * @type {string} + * Creates an object holding the default classes for a category. + * @return {!ToolboxCategory.CssConfig} The configuration object holding + * all the CSS classes for a category. * @protected */ - this.name_ = parsing.replaceMessageReferences(categoryDef['name']); + makeDefaultCssConfig_() { + return { + 'container': 'blocklyToolboxCategory', + 'row': 'blocklyTreeRow', + 'rowcontentcontainer': 'blocklyTreeRowContentContainer', + 'icon': 'blocklyTreeIcon', + 'label': 'blocklyTreeLabel', + 'contents': 'blocklyToolboxContents', + 'selected': 'blocklyTreeSelected', + 'openicon': 'blocklyTreeIconOpen', + 'closedicon': 'blocklyTreeIconClosed', + }; + } /** - * The colour of the category. - * @type {string} + * Parses the contents array depending on if the category is a dynamic + * category, or if its contents are meant to be shown in the flyout. + * @param {!toolbox.CategoryInfo} categoryDef The information needed + * to create a category. * @protected */ - this.colour_ = this.getColour_(categoryDef); + parseContents_(categoryDef) { + const contents = categoryDef['contents']; + + if (categoryDef['custom']) { + this.flyoutItems_ = categoryDef['custom']; + } else if (contents) { + for (let i = 0; i < contents.length; i++) { + const itemDef = contents[i]; + const flyoutItem = + /** @type {toolbox.FlyoutItemInfo} */ (itemDef); + this.flyoutItems_.push(flyoutItem); + } + } + } /** - * The html container for the category. - * @type {?Element} + * Parses the non-contents parts of the category def. + * @param {!toolbox.CategoryInfo} categoryDef The information needed to create + * a category. * @protected */ - this.htmlDiv_ = null; + parseCategoryDef_(categoryDef) { + this.name_ = parsing.replaceMessageReferences(categoryDef['name']); + this.colour_ = this.getColour_(categoryDef); + object.mixin( + this.cssConfig_, categoryDef['cssconfig'] || categoryDef['cssConfig']); + } /** - * The html element for the category row. - * @type {?Element} + * Creates the DOM for the category. + * @return {!Element} The parent element for the category. * @protected */ - this.rowDiv_ = null; + createDom_() { + this.htmlDiv_ = this.createContainer_(); + aria.setRole(this.htmlDiv_, aria.Role.TREEITEM); + aria.setState( + /** @type {!Element} */ (this.htmlDiv_), aria.State.SELECTED, false); + aria.setState( + /** @type {!Element} */ (this.htmlDiv_), aria.State.LEVEL, this.level_); + + this.rowDiv_ = this.createRowContainer_(); + this.rowDiv_.style.pointerEvents = 'auto'; + this.htmlDiv_.appendChild(this.rowDiv_); + + this.rowContents_ = this.createRowContentsContainer_(); + this.rowContents_.style.pointerEvents = 'none'; + this.rowDiv_.appendChild(this.rowContents_); + + this.iconDom_ = this.createIconDom_(); + aria.setRole(this.iconDom_, aria.Role.PRESENTATION); + this.rowContents_.appendChild(this.iconDom_); + + this.labelDom_ = this.createLabelDom_(this.name_); + this.rowContents_.appendChild(this.labelDom_); + aria.setState( + /** @type {!Element} */ (this.htmlDiv_), aria.State.LABELLEDBY, + this.labelDom_.getAttribute('id')); + + this.addColourBorder_(this.colour_); + + return this.htmlDiv_; + } /** - * The html element that holds children elements of the category row. - * @type {?Element} + * Creates the container that holds the row and any subcategories. + * @return {!Element} The div that holds the icon and the label. * @protected */ - this.rowContents_ = null; + createContainer_() { + const container = document.createElement('div'); + dom.addClass(container, this.cssConfig_['container']); + return container; + } /** - * The html element for the toolbox icon. - * @type {?Element} + * Creates the parent of the contents container. All clicks will happen on + * this div. + * @return {!Element} The div that holds the contents container. * @protected */ - this.iconDom_ = null; + createRowContainer_() { + const rowDiv = document.createElement('div'); + dom.addClass(rowDiv, this.cssConfig_['row']); + let nestedPadding = ToolboxCategory.nestedPadding * this.getLevel(); + nestedPadding = nestedPadding.toString() + 'px'; + this.workspace_.RTL ? rowDiv.style.paddingRight = nestedPadding : + rowDiv.style.paddingLeft = nestedPadding; + return rowDiv; + } /** - * The html element for the toolbox label. - * @type {?Element} + * Creates the container for the label and icon. + * This is necessary so we can set all subcategory pointer events to none. + * @return {!Element} The div that holds the icon and the label. * @protected */ - this.labelDom_ = null; + createRowContentsContainer_() { + const contentsContainer = document.createElement('div'); + dom.addClass(contentsContainer, this.cssConfig_['rowcontentcontainer']); + return contentsContainer; + } /** - * All the css class names that are used to create a category. - * @type {!ToolboxCategory.CssConfig} + * Creates the span that holds the category icon. + * @return {!Element} The span that holds the category icon. * @protected */ - this.cssConfig_ = this.makeDefaultCssConfig_(); + createIconDom_() { + const toolboxIcon = document.createElement('span'); + if (!this.parentToolbox_.isHorizontal()) { + dom.addClass(toolboxIcon, this.cssConfig_['icon']); + } - const cssConfig = categoryDef['cssconfig'] || categoryDef['cssConfig']; - object.mixin(this.cssConfig_, cssConfig); + toolboxIcon.style.display = 'inline-block'; + return toolboxIcon; + } /** - * True if the category is meant to be hidden, false otherwise. - * @type {boolean} + * Creates the span that holds the category label. + * This should have an ID for accessibility purposes. + * @param {string} name The name of the category. + * @return {!Element} The span that holds the category label. * @protected */ - this.isHidden_ = false; + createLabelDom_(name) { + const toolboxLabel = document.createElement('span'); + toolboxLabel.setAttribute('id', this.getId() + '.label'); + toolboxLabel.textContent = name; + dom.addClass(toolboxLabel, this.cssConfig_['label']); + return toolboxLabel; + } /** - * True if this category is disabled, false otherwise. - * @type {boolean} - * @protected + * Updates the colour for this category. + * @public */ - this.isDisabled_ = false; + refreshTheme() { + this.colour_ = this.getColour_(/** @type {toolbox.CategoryInfo} **/ + (this.toolboxItemDef_)); + this.addColourBorder_(this.colour_); + } /** - * The flyout items for this category. - * @type {string|!toolbox.FlyoutItemInfoArray} + * Add the strip of colour to the toolbox category. + * @param {string} colour The category colour. * @protected */ - this.flyoutItems_ = []; - - this.parseContents_(categoryDef); -}; - -object.inherits(ToolboxCategory, ToolboxItem); - -/** - * All the CSS class names that are used to create a category. - * @typedef {{ - * container:(string|undefined), - * row:(string|undefined), - * rowcontentcontainer:(string|undefined), - * icon:(string|undefined), - * label:(string|undefined), - * selected:(string|undefined), - * openicon:(string|undefined), - * closedicon:(string|undefined) - * }} - */ -ToolboxCategory.CssConfig; - -/** - * Name used for registering a toolbox category. - * @const {string} - */ -ToolboxCategory.registrationName = 'category'; - -/** - * The number of pixels to move the category over at each nested level. - * @type {number} - */ -ToolboxCategory.nestedPadding = 19; - -/** - * The width in pixels of the strip of colour next to each category. - * @type {number} - */ -ToolboxCategory.borderWidth = 8; - -/** - * The default colour of the category. This is used as the background colour of - * the category when it is selected. - * @type {string} - */ -ToolboxCategory.defaultBackgroundColour = '#57e'; + addColourBorder_(colour) { + if (colour) { + const border = + ToolboxCategory.borderWidth + 'px solid ' + (colour || '#ddd'); + if (this.workspace_.RTL) { + this.rowDiv_.style.borderRight = border; + } else { + this.rowDiv_.style.borderLeft = border; + } + } + } -/** - * Creates an object holding the default classes for a category. - * @return {!ToolboxCategory.CssConfig} The configuration object holding - * all the CSS classes for a category. - * @protected - */ -ToolboxCategory.prototype.makeDefaultCssConfig_ = function() { - return { - 'container': 'blocklyToolboxCategory', - 'row': 'blocklyTreeRow', - 'rowcontentcontainer': 'blocklyTreeRowContentContainer', - 'icon': 'blocklyTreeIcon', - 'label': 'blocklyTreeLabel', - 'contents': 'blocklyToolboxContents', - 'selected': 'blocklyTreeSelected', - 'openicon': 'blocklyTreeIconOpen', - 'closedicon': 'blocklyTreeIconClosed', - }; -}; + /** + * Gets either the colour or the style for a category. + * @param {!toolbox.CategoryInfo} categoryDef The object holding + * information on the category. + * @return {string} The hex colour for the category. + * @protected + */ + getColour_(categoryDef) { + const styleName = + categoryDef['categorystyle'] || categoryDef['categoryStyle']; + const colour = categoryDef['colour']; -/** - * Parses the contents array depending on if the category is a dynamic category, - * or if its contents are meant to be shown in the flyout. - * @param {!toolbox.CategoryInfo} categoryDef The information needed - * to create a category. - * @protected - */ -ToolboxCategory.prototype.parseContents_ = function(categoryDef) { - const contents = categoryDef['contents']; - - if (categoryDef['custom']) { - this.flyoutItems_ = categoryDef['custom']; - } else if (contents) { - for (let i = 0; i < contents.length; i++) { - const itemDef = contents[i]; - const flyoutItem = - /** @type {toolbox.FlyoutItemInfo} */ (itemDef); - this.flyoutItems_.push(flyoutItem); + if (colour && styleName) { + console.warn( + 'Toolbox category "' + this.name_ + + '" must not have both a style and a colour'); + } else if (styleName) { + return this.getColourfromStyle_(styleName); + } else { + return this.parseColour_(colour); } + return ''; } -}; -/** - * @override - */ -ToolboxCategory.prototype.init = function() { - this.createDom_(); - if (this.toolboxItemDef_['hidden'] === 'true') { - this.hide(); + /** + * Sets the colour for the category using the style name and returns the new + * colour as a hex string. + * @param {string} styleName Name of the style. + * @return {string} The hex colour for the category. + * @private + */ + getColourfromStyle_(styleName) { + const theme = this.workspace_.getTheme(); + if (styleName && theme) { + const style = theme.categoryStyles[styleName]; + if (style && style.colour) { + return this.parseColour_(style.colour); + } else { + console.warn( + 'Style "' + styleName + '" must exist and contain a colour value'); + } + } + return ''; } -}; - -/** - * Creates the DOM for the category. - * @return {!Element} The parent element for the category. - * @protected - */ -ToolboxCategory.prototype.createDom_ = function() { - this.htmlDiv_ = this.createContainer_(); - aria.setRole(this.htmlDiv_, aria.Role.TREEITEM); - aria.setState( - /** @type {!Element} */ (this.htmlDiv_), aria.State.SELECTED, false); - aria.setState( - /** @type {!Element} */ (this.htmlDiv_), aria.State.LEVEL, this.level_); - - this.rowDiv_ = this.createRowContainer_(); - this.rowDiv_.style.pointerEvents = 'auto'; - this.htmlDiv_.appendChild(this.rowDiv_); - - this.rowContents_ = this.createRowContentsContainer_(); - this.rowContents_.style.pointerEvents = 'none'; - this.rowDiv_.appendChild(this.rowContents_); - this.iconDom_ = this.createIconDom_(); - aria.setRole(this.iconDom_, aria.Role.PRESENTATION); - this.rowContents_.appendChild(this.iconDom_); - - this.labelDom_ = this.createLabelDom_(this.name_); - this.rowContents_.appendChild(this.labelDom_); - aria.setState( - /** @type {!Element} */ (this.htmlDiv_), aria.State.LABELLEDBY, - this.labelDom_.getAttribute('id')); - - this.addColourBorder_(this.colour_); + /** + * Gets the HTML element that is clickable. + * The parent toolbox element receives clicks. The parent toolbox will add an + * ID to this element so it can pass the onClick event to the correct + * toolboxItem. + * @return {!Element} The HTML element that receives clicks. + * @public + */ + getClickTarget() { + return /** @type {!Element} */ (this.rowDiv_); + } - return this.htmlDiv_; -}; + /** + * Parses the colour on the category. + * @param {number|string} colourValue HSV hue value (0 to 360), #RRGGBB + * string, or a message reference string pointing to one of those two + * values. + * @return {string} The hex colour for the category. + * @private + */ + parseColour_(colourValue) { + // Decode the colour for any potential message references + // (eg. `%{BKY_MATH_HUE}`). + const colour = parsing.replaceMessageReferences(colourValue); + if (colour == null || colour === '') { + // No attribute. No colour. + return ''; + } else { + const hue = Number(colour); + if (!isNaN(hue)) { + return colourUtils.hueToHex(hue); + } else { + const hex = colourUtils.parse(colour); + if (hex) { + return hex; + } else { + console.warn( + 'Toolbox category "' + this.name_ + + '" has unrecognized colour attribute: ' + colour); + return ''; + } + } + } + } -/** - * Creates the container that holds the row and any subcategories. - * @return {!Element} The div that holds the icon and the label. - * @protected - */ -ToolboxCategory.prototype.createContainer_ = function() { - const container = document.createElement('div'); - dom.addClass(container, this.cssConfig_['container']); - return container; -}; + /** + * Adds appropriate classes to display an open icon. + * @param {?Element} iconDiv The div that holds the icon. + * @protected + */ + openIcon_(iconDiv) { + if (!iconDiv) { + return; + } + dom.removeClasses(iconDiv, this.cssConfig_['closedicon']); + dom.addClass(iconDiv, this.cssConfig_['openicon']); + } -/** - * Creates the parent of the contents container. All clicks will happen on this - * div. - * @return {!Element} The div that holds the contents container. - * @protected - */ -ToolboxCategory.prototype.createRowContainer_ = function() { - const rowDiv = document.createElement('div'); - dom.addClass(rowDiv, this.cssConfig_['row']); - let nestedPadding = ToolboxCategory.nestedPadding * this.getLevel(); - nestedPadding = nestedPadding.toString() + 'px'; - this.workspace_.RTL ? rowDiv.style.paddingRight = nestedPadding : - rowDiv.style.paddingLeft = nestedPadding; - return rowDiv; -}; + /** + * Adds appropriate classes to display a closed icon. + * @param {?Element} iconDiv The div that holds the icon. + * @protected + */ + closeIcon_(iconDiv) { + if (!iconDiv) { + return; + } + dom.removeClasses(iconDiv, this.cssConfig_['openicon']); + dom.addClass(iconDiv, this.cssConfig_['closedicon']); + } -/** - * Creates the container for the label and icon. - * This is necessary so we can set all subcategory pointer events to none. - * @return {!Element} The div that holds the icon and the label. - * @protected - */ -ToolboxCategory.prototype.createRowContentsContainer_ = function() { - const contentsContainer = document.createElement('div'); - dom.addClass(contentsContainer, this.cssConfig_['rowcontentcontainer']); - return contentsContainer; -}; + /** + * Sets whether the category is visible or not. + * For a category to be visible its parent category must also be expanded. + * @param {boolean} isVisible True if category should be visible. + * @protected + */ + setVisible_(isVisible) { + this.htmlDiv_.style.display = isVisible ? 'block' : 'none'; + this.isHidden_ = !isVisible; -/** - * Creates the span that holds the category icon. - * @return {!Element} The span that holds the category icon. - * @protected - */ -ToolboxCategory.prototype.createIconDom_ = function() { - const toolboxIcon = document.createElement('span'); - if (!this.parentToolbox_.isHorizontal()) { - dom.addClass(toolboxIcon, this.cssConfig_['icon']); + if (this.parentToolbox_.getSelectedItem() === this) { + this.parentToolbox_.clearSelection(); + } } - toolboxIcon.style.display = 'inline-block'; - return toolboxIcon; -}; + /** + * Hide the category. + */ + hide() { + this.setVisible_(false); + } -/** - * Creates the span that holds the category label. - * This should have an ID for accessibility purposes. - * @param {string} name The name of the category. - * @return {!Element} The span that holds the category label. - * @protected - */ -ToolboxCategory.prototype.createLabelDom_ = function(name) { - const toolboxLabel = document.createElement('span'); - toolboxLabel.setAttribute('id', this.getId() + '.label'); - toolboxLabel.textContent = name; - dom.addClass(toolboxLabel, this.cssConfig_['label']); - return toolboxLabel; -}; + /** + * Show the category. Category will only appear if its parent category is also + * expanded. + */ + show() { + this.setVisible_(true); + } -/** - * Updates the colour for this category. - * @public - */ -ToolboxCategory.prototype.refreshTheme = function() { - this.colour_ = this.getColour_(/** @type {toolbox.CategoryInfo} **/ - (this.toolboxItemDef_)); - this.addColourBorder_(this.colour_); -}; + /** + * Whether the category is visible. + * A category is only visible if all of its ancestors are expanded and + * isHidden_ is false. + * @return {boolean} True if the category is visible, false otherwise. + * @public + */ + isVisible() { + return !this.isHidden_ && this.allAncestorsExpanded_(); + } -/** - * Add the strip of colour to the toolbox category. - * @param {string} colour The category colour. - * @protected - */ -ToolboxCategory.prototype.addColourBorder_ = function(colour) { - if (colour) { - const border = - ToolboxCategory.borderWidth + 'px solid ' + (colour || '#ddd'); - if (this.workspace_.RTL) { - this.rowDiv_.style.borderRight = border; - } else { - this.rowDiv_.style.borderLeft = border; + /** + * Whether all ancestors of a category (parent and parent's parent, etc.) are + * expanded. + * @return {boolean} True only if every ancestor is expanded + * @protected + */ + allAncestorsExpanded_() { + let category = this; + while (category.getParent()) { + category = category.getParent(); + if (!category.isExpanded()) { + return false; + } } + return true; } -}; -/** - * Gets either the colour or the style for a category. - * @param {!toolbox.CategoryInfo} categoryDef The object holding - * information on the category. - * @return {string} The hex colour for the category. - * @protected - */ -ToolboxCategory.prototype.getColour_ = function(categoryDef) { - const styleName = - categoryDef['categorystyle'] || categoryDef['categoryStyle']; - const colour = categoryDef['colour']; - - if (colour && styleName) { - console.warn( - 'Toolbox category "' + this.name_ + - '" must not have both a style and a colour'); - } else if (styleName) { - return this.getColourfromStyle_(styleName); - } else { - return this.parseColour_(colour); - } - return ''; -}; - -/** - * Sets the colour for the category using the style name and returns the new - * colour as a hex string. - * @param {string} styleName Name of the style. - * @return {string} The hex colour for the category. - * @private - */ -ToolboxCategory.prototype.getColourfromStyle_ = function(styleName) { - const theme = this.workspace_.getTheme(); - if (styleName && theme) { - const style = theme.categoryStyles[styleName]; - if (style && style.colour) { - return this.parseColour_(style.colour); - } else { - console.warn( - 'Style "' + styleName + '" must exist and contain a colour value'); - } + /** + * @override + */ + isSelectable() { + return this.isVisible() && !this.isDisabled_; } - return ''; -}; -/** - * Gets the HTML element that is clickable. - * The parent toolbox element receives clicks. The parent toolbox will add an ID - * to this element so it can pass the onClick event to the correct toolboxItem. - * @return {!Element} The HTML element that receives clicks. - * @public - */ -ToolboxCategory.prototype.getClickTarget = function() { - return /** @type {!Element} */ (this.rowDiv_); -}; + /** + * Handles when the toolbox item is clicked. + * @param {!Event} _e Click event to handle. + * @public + */ + onClick(_e) { + // No-op + } -/** - * Parses the colour on the category. - * @param {number|string} colourValue HSV hue value (0 to 360), #RRGGBB string, - * or a message reference string pointing to one of those two values. - * @return {string} The hex colour for the category. - * @private - */ -ToolboxCategory.prototype.parseColour_ = function(colourValue) { - // Decode the colour for any potential message references - // (eg. `%{BKY_MATH_HUE}`). - const colour = parsing.replaceMessageReferences(colourValue); - if (colour == null || colour === '') { - // No attribute. No colour. - return ''; - } else { - const hue = Number(colour); - if (!isNaN(hue)) { - return colourUtils.hueToHex(hue); + /** + * Sets the current category as selected. + * @param {boolean} isSelected True if this category is selected, false + * otherwise. + * @public + */ + setSelected(isSelected) { + if (isSelected) { + const defaultColour = + this.parseColour_(ToolboxCategory.defaultBackgroundColour); + this.rowDiv_.style.backgroundColor = this.colour_ || defaultColour; + dom.addClass(this.rowDiv_, this.cssConfig_['selected']); } else { - const hex = colourUtils.parse(colour); - if (hex) { - return hex; - } else { - console.warn( - 'Toolbox category "' + this.name_ + - '" has unrecognized colour attribute: ' + colour); - return ''; - } + this.rowDiv_.style.backgroundColor = ''; + dom.removeClass(this.rowDiv_, this.cssConfig_['selected']); } + aria.setState( + /** @type {!Element} */ (this.htmlDiv_), aria.State.SELECTED, + isSelected); } -}; -/** - * Adds appropriate classes to display an open icon. - * @param {?Element} iconDiv The div that holds the icon. - * @protected - */ -ToolboxCategory.prototype.openIcon_ = function(iconDiv) { - if (!iconDiv) { - return; + /** + * Sets whether the category is disabled. + * @param {boolean} isDisabled True to disable the category, false otherwise. + */ + setDisabled(isDisabled) { + this.isDisabled_ = isDisabled; + this.getDiv().setAttribute('disabled', isDisabled); + isDisabled ? this.getDiv().setAttribute('disabled', 'true') : + this.getDiv().removeAttribute('disabled'); } - dom.removeClasses(iconDiv, this.cssConfig_['closedicon']); - dom.addClass(iconDiv, this.cssConfig_['openicon']); -}; -/** - * Adds appropriate classes to display a closed icon. - * @param {?Element} iconDiv The div that holds the icon. - * @protected - */ -ToolboxCategory.prototype.closeIcon_ = function(iconDiv) { - if (!iconDiv) { - return; + /** + * Gets the name of the category. Used for emitting events. + * @return {string} The name of the toolbox item. + * @public + */ + getName() { + return this.name_; } - dom.removeClasses(iconDiv, this.cssConfig_['openicon']); - dom.addClass(iconDiv, this.cssConfig_['closedicon']); -}; - -/** - * Sets whether the category is visible or not. - * For a category to be visible its parent category must also be expanded. - * @param {boolean} isVisible True if category should be visible. - * @protected - */ -ToolboxCategory.prototype.setVisible_ = function(isVisible) { - this.htmlDiv_.style.display = isVisible ? 'block' : 'none'; - this.isHidden_ = !isVisible; - if (this.parentToolbox_.getSelectedItem() === this) { - this.parentToolbox_.clearSelection(); + /** + * @override + */ + getParent() { + return this.parent_; } -}; -/** - * Hide the category. - */ -ToolboxCategory.prototype.hide = function() { - this.setVisible_(false); -}; + /** + * @override + */ + getDiv() { + return this.htmlDiv_; + } -/** - * Show the category. Category will only appear if its parent category is also - * expanded. - */ -ToolboxCategory.prototype.show = function() { - this.setVisible_(true); -}; + /** + * Gets the contents of the category. These are items that are meant to be + * displayed in the flyout. + * @return {!toolbox.FlyoutItemInfoArray|string} The definition + * of items to be displayed in the flyout. + * @public + */ + getContents() { + return this.flyoutItems_; + } -/** - * Whether the category is visible. - * A category is only visible if all of its ancestors are expanded and isHidden_ - * is false. - * @return {boolean} True if the category is visible, false otherwise. - * @public - */ -ToolboxCategory.prototype.isVisible = function() { - return !this.isHidden_ && this.allAncestorsExpanded_(); -}; + /** + * Updates the contents to be displayed in the flyout. + * If the flyout is open when the contents are updated, refreshSelection on + * the toolbox must also be called. + * @param {!toolbox.FlyoutDefinition|string} contents The contents + * to be displayed in the flyout. A string can be supplied to create a + * dynamic category. + * @public + */ + updateFlyoutContents(contents) { + this.flyoutItems_ = []; -/** - * Whether all ancestors of a category (parent and parent's parent, etc.) are - * expanded. - * @return {boolean} True only if every ancestor is expanded - * @protected - */ -ToolboxCategory.prototype.allAncestorsExpanded_ = function() { - let category = this; - while (category.getParent()) { - category = category.getParent(); - if (!category.isExpanded()) { - return false; + if (typeof contents === 'string') { + this.toolboxItemDef_['custom'] = contents; + } else { + // Removes old custom field when contents is updated. + delete this.toolboxItemDef_['custom']; + this.toolboxItemDef_['contents'] = + toolbox.convertFlyoutDefToJsonArray(contents); } + this.parseContents_( + /** @type {toolbox.CategoryInfo} */ (this.toolboxItemDef_)); } - return true; -}; -/** - * @override - */ -ToolboxCategory.prototype.isSelectable = function() { - return this.isVisible() && !this.isDisabled_; -}; - -/** - * Handles when the toolbox item is clicked. - * @param {!Event} _e Click event to handle. - * @public - */ -ToolboxCategory.prototype.onClick = function(_e) { - // No-op -}; - -/** - * Sets the current category as selected. - * @param {boolean} isSelected True if this category is selected, false - * otherwise. - * @public - */ -ToolboxCategory.prototype.setSelected = function(isSelected) { - if (isSelected) { - const defaultColour = - this.parseColour_(ToolboxCategory.defaultBackgroundColour); - this.rowDiv_.style.backgroundColor = this.colour_ || defaultColour; - dom.addClass(this.rowDiv_, this.cssConfig_['selected']); - } else { - this.rowDiv_.style.backgroundColor = ''; - dom.removeClass(this.rowDiv_, this.cssConfig_['selected']); - } - aria.setState( - /** @type {!Element} */ (this.htmlDiv_), aria.State.SELECTED, isSelected); -}; - -/** - * Sets whether the category is disabled. - * @param {boolean} isDisabled True to disable the category, false otherwise. - */ -ToolboxCategory.prototype.setDisabled = function(isDisabled) { - this.isDisabled_ = isDisabled; - this.getDiv().setAttribute('disabled', isDisabled); - isDisabled ? this.getDiv().setAttribute('disabled', 'true') : - this.getDiv().removeAttribute('disabled'); -}; - -/** - * Gets the name of the category. Used for emitting events. - * @return {string} The name of the toolbox item. - * @public - */ -ToolboxCategory.prototype.getName = function() { - return this.name_; -}; + /** + * @override + */ + dispose() { + dom.removeNode(this.htmlDiv_); + } +} /** - * @override + * All the CSS class names that are used to create a category. + * @typedef {{ + * container:(string|undefined), + * row:(string|undefined), + * rowcontentcontainer:(string|undefined), + * icon:(string|undefined), + * label:(string|undefined), + * selected:(string|undefined), + * openicon:(string|undefined), + * closedicon:(string|undefined) + * }} */ -ToolboxCategory.prototype.getParent = function() { - return this.parent_; -}; +ToolboxCategory.CssConfig; /** - * @override + * Name used for registering a toolbox category. + * @type {string} */ -ToolboxCategory.prototype.getDiv = function() { - return this.htmlDiv_; -}; +ToolboxCategory.registrationName = 'category'; /** - * Gets the contents of the category. These are items that are meant to be - * displayed in the flyout. - * @return {!toolbox.FlyoutItemInfoArray|string} The definition - * of items to be displayed in the flyout. - * @public + * The number of pixels to move the category over at each nested level. + * @type {number} */ -ToolboxCategory.prototype.getContents = function() { - return this.flyoutItems_; -}; +ToolboxCategory.nestedPadding = 19; /** - * Updates the contents to be displayed in the flyout. - * If the flyout is open when the contents are updated, refreshSelection on the - * toolbox must also be called. - * @param {!toolbox.FlyoutDefinition|string} contents The contents - * to be displayed in the flyout. A string can be supplied to create a - * dynamic category. - * @public + * The width in pixels of the strip of colour next to each category. + * @type {number} */ -ToolboxCategory.prototype.updateFlyoutContents = function(contents) { - this.flyoutItems_ = []; - - if (typeof contents === 'string') { - this.toolboxItemDef_['custom'] = contents; - } else { - // Removes old custom field when contents is updated. - delete this.toolboxItemDef_['custom']; - this.toolboxItemDef_['contents'] = - toolbox.convertFlyoutDefToJsonArray(contents); - } - this.parseContents_( - /** @type {toolbox.CategoryInfo} */ (this.toolboxItemDef_)); -}; +ToolboxCategory.borderWidth = 8; /** - * @override + * The default colour of the category. This is used as the background colour of + * the category when it is selected. + * @type {string} */ -ToolboxCategory.prototype.dispose = function() { - dom.removeNode(this.htmlDiv_); -}; +ToolboxCategory.defaultBackgroundColour = '#57e'; /** * CSS for Toolbox. See css.js for use. diff --git a/core/toolbox/collapsible_category.js b/core/toolbox/collapsible_category.js index 3b24a950131..3281e877161 100644 --- a/core/toolbox/collapsible_category.js +++ b/core/toolbox/collapsible_category.js @@ -17,7 +17,6 @@ goog.module('Blockly.CollapsibleToolboxCategory'); const aria = goog.require('Blockly.utils.aria'); const dom = goog.require('Blockly.utils.dom'); -const object = goog.require('Blockly.utils.object'); const registry = goog.require('Blockly.registry'); const toolbox = goog.require('Blockly.utils.toolbox'); /* eslint-disable-next-line no-unused-vars */ @@ -32,277 +31,276 @@ const {ToolboxSeparator} = goog.require('Blockly.ToolboxSeparator'); /** * Class for a category in a toolbox that can be collapsed. - * @param {!toolbox.CategoryInfo} categoryDef The information needed - * to create a category in the toolbox. - * @param {!IToolbox} toolbox The parent toolbox for the category. - * @param {ICollapsibleToolboxItem=} opt_parent The parent category or null if - * the category does not have a parent. - * @constructor - * @extends {ToolboxCategory} * @implements {ICollapsibleToolboxItem} - * @alias Blockly.CollapsibleToolboxCategory */ -const CollapsibleToolboxCategory = function(categoryDef, toolbox, opt_parent) { +class CollapsibleToolboxCategory extends ToolboxCategory { /** - * Container for any child categories. - * @type {?Element} - * @protected + * @param {!toolbox.CategoryInfo} categoryDef The information needed + * to create a category in the toolbox. + * @param {!IToolbox} toolbox The parent toolbox for the category. + * @param {ICollapsibleToolboxItem=} opt_parent The parent category or null if + * the category does not have a parent. + * @alias Blockly.CollapsibleToolboxCategory */ - this.subcategoriesDiv_ = null; + constructor(categoryDef, toolbox, opt_parent) { + super(categoryDef, toolbox, opt_parent); + + /** + * Container for any child categories. + * @type {?Element} + * @protected + */ + this.subcategoriesDiv_ = null; + + /** + * Whether or not the category should display its subcategories. + * @type {boolean} + * @protected + */ + this.expanded_ = false; + + /** + * The child toolbox items for this category. + * @type {!Array} + * @protected + */ + this.toolboxItems_ = []; + } /** - * Whether or not the category should display its subcategories. - * @type {boolean} - * @protected + * @override */ - this.expanded_ = false; + makeDefaultCssConfig_() { + const cssConfig = super.makeDefaultCssConfig_(); + cssConfig['contents'] = 'blocklyToolboxContents'; + return cssConfig; + } /** - * The child toolbox items for this category. - * @type {!Array} - * @protected + * @override */ - this.toolboxItems_ = []; - - CollapsibleToolboxCategory.superClass_.constructor.call( - this, categoryDef, toolbox, opt_parent); -}; - -object.inherits(CollapsibleToolboxCategory, ToolboxCategory); - -/** - * All the CSS class names that are used to create a collapsible - * category. This is all the properties from the regular category plus contents. - * @typedef {{ - * container:?string, - * row:?string, - * rowcontentcontainer:?string, - * icon:?string, - * label:?string, - * selected:?string, - * openicon:?string, - * closedicon:?string, - * contents:?string - * }} - */ -CollapsibleToolboxCategory.CssConfig; - -/** - * Name used for registering a collapsible toolbox category. - * @const {string} - */ -CollapsibleToolboxCategory.registrationName = 'collapsibleCategory'; - -/** - * @override - */ -CollapsibleToolboxCategory.prototype.makeDefaultCssConfig_ = function() { - const cssConfig = - CollapsibleToolboxCategory.superClass_.makeDefaultCssConfig_.call(this); - cssConfig['contents'] = 'blocklyToolboxContents'; - return cssConfig; -}; - -/** - * @override - */ -CollapsibleToolboxCategory.prototype.parseContents_ = function(categoryDef) { - const contents = categoryDef['contents']; - let prevIsFlyoutItem = true; - - if (categoryDef['custom']) { - this.flyoutItems_ = categoryDef['custom']; - } else if (contents) { - for (let i = 0; i < contents.length; i++) { - const itemDef = contents[i]; - // Separators can exist as either a flyout item or a toolbox item so - // decide where it goes based on the type of the previous item. - if (!registry.hasItem(registry.Type.TOOLBOX_ITEM, itemDef['kind']) || - (itemDef['kind'].toLowerCase() === - ToolboxSeparator.registrationName && - prevIsFlyoutItem)) { - const flyoutItem = /** @type {toolbox.FlyoutItemInfo} */ (itemDef); - this.flyoutItems_.push(flyoutItem); - prevIsFlyoutItem = true; - } else { - this.createToolboxItem_(itemDef); - prevIsFlyoutItem = false; + parseContents_(categoryDef) { + const contents = categoryDef['contents']; + let prevIsFlyoutItem = true; + + if (categoryDef['custom']) { + this.flyoutItems_ = categoryDef['custom']; + } else if (contents) { + for (let i = 0; i < contents.length; i++) { + const itemDef = contents[i]; + // Separators can exist as either a flyout item or a toolbox item so + // decide where it goes based on the type of the previous item. + if (!registry.hasItem(registry.Type.TOOLBOX_ITEM, itemDef['kind']) || + (itemDef['kind'].toLowerCase() === + ToolboxSeparator.registrationName && + prevIsFlyoutItem)) { + const flyoutItem = /** @type {toolbox.FlyoutItemInfo} */ (itemDef); + this.flyoutItems_.push(flyoutItem); + prevIsFlyoutItem = true; + } else { + this.createToolboxItem_(itemDef); + prevIsFlyoutItem = false; + } } } } -}; -/** - * Creates a toolbox item and adds it to the list of toolbox items. - * @param {!toolbox.ToolboxItemInfo} itemDef The information needed - * to create a toolbox item. - * @private - */ -CollapsibleToolboxCategory.prototype.createToolboxItem_ = function(itemDef) { - let registryName = itemDef['kind']; - const categoryDef = /** @type {!toolbox.CategoryInfo} */ (itemDef); - - // Categories that are collapsible are created using a class registered under - // a different name. - if (registryName.toUpperCase() == 'CATEGORY' && - toolbox.isCategoryCollapsible(categoryDef)) { - registryName = CollapsibleToolboxCategory.registrationName; + /** + * Creates a toolbox item and adds it to the list of toolbox items. + * @param {!toolbox.ToolboxItemInfo} itemDef The information needed + * to create a toolbox item. + * @private + */ + createToolboxItem_(itemDef) { + let registryName = itemDef['kind']; + const categoryDef = /** @type {!toolbox.CategoryInfo} */ (itemDef); + + // Categories that are collapsible are created using a class registered + // under a different name. + if (registryName.toUpperCase() == 'CATEGORY' && + toolbox.isCategoryCollapsible(categoryDef)) { + registryName = CollapsibleToolboxCategory.registrationName; + } + const ToolboxItemClass = + registry.getClass(registry.Type.TOOLBOX_ITEM, registryName); + const toolboxItem = + new ToolboxItemClass(itemDef, this.parentToolbox_, this); + this.toolboxItems_.push(toolboxItem); } - const ToolboxItemClass = - registry.getClass(registry.Type.TOOLBOX_ITEM, registryName); - const toolboxItem = new ToolboxItemClass(itemDef, this.parentToolbox_, this); - this.toolboxItems_.push(toolboxItem); -}; - -/** - * @override - */ -CollapsibleToolboxCategory.prototype.init = function() { - CollapsibleToolboxCategory.superClass_.init.call(this); - this.setExpanded( - this.toolboxItemDef_['expanded'] === 'true' || - this.toolboxItemDef_['expanded']); -}; + /** + * @override + */ + init() { + super.init(); -/** - * @override - */ -CollapsibleToolboxCategory.prototype.createDom_ = function() { - CollapsibleToolboxCategory.superClass_.createDom_.call(this); + this.setExpanded( + this.toolboxItemDef_['expanded'] === 'true' || + this.toolboxItemDef_['expanded']); + } - const subCategories = this.getChildToolboxItems(); - this.subcategoriesDiv_ = this.createSubCategoriesDom_(subCategories); - aria.setRole(this.subcategoriesDiv_, aria.Role.GROUP); - this.htmlDiv_.appendChild(this.subcategoriesDiv_); + /** + * @override + */ + createDom_() { + super.createDom_(); - return this.htmlDiv_; -}; + const subCategories = this.getChildToolboxItems(); + this.subcategoriesDiv_ = this.createSubCategoriesDom_(subCategories); + aria.setRole(this.subcategoriesDiv_, aria.Role.GROUP); + this.htmlDiv_.appendChild(this.subcategoriesDiv_); -/** - * @override - */ -CollapsibleToolboxCategory.prototype.createIconDom_ = function() { - const toolboxIcon = document.createElement('span'); - if (!this.parentToolbox_.isHorizontal()) { - dom.addClass(toolboxIcon, this.cssConfig_['icon']); - toolboxIcon.style.visibility = 'visible'; + return this.htmlDiv_; } - toolboxIcon.style.display = 'inline-block'; - return toolboxIcon; -}; + /** + * @override + */ + createIconDom_() { + const toolboxIcon = document.createElement('span'); + if (!this.parentToolbox_.isHorizontal()) { + dom.addClass(toolboxIcon, this.cssConfig_['icon']); + toolboxIcon.style.visibility = 'visible'; + } -/** - * Create the DOM for all subcategories. - * @param {!Array} subcategories The subcategories. - * @return {!Element} The div holding all the subcategories. - * @protected - */ -CollapsibleToolboxCategory.prototype.createSubCategoriesDom_ = function( - subcategories) { - const contentsContainer = document.createElement('div'); - dom.addClass(contentsContainer, this.cssConfig_['contents']); - - for (let i = 0; i < subcategories.length; i++) { - const newCategory = subcategories[i]; - newCategory.init(); - const newCategoryDiv = newCategory.getDiv(); - contentsContainer.appendChild(newCategoryDiv); - if (newCategory.getClickTarget) { - newCategory.getClickTarget().setAttribute('id', newCategory.getId()); + toolboxIcon.style.display = 'inline-block'; + return toolboxIcon; + } + + /** + * Create the DOM for all subcategories. + * @param {!Array} subcategories The subcategories. + * @return {!Element} The div holding all the subcategories. + * @protected + */ + createSubCategoriesDom_(subcategories) { + const contentsContainer = document.createElement('div'); + dom.addClass(contentsContainer, this.cssConfig_['contents']); + + for (let i = 0; i < subcategories.length; i++) { + const newCategory = subcategories[i]; + newCategory.init(); + const newCategoryDiv = newCategory.getDiv(); + contentsContainer.appendChild(newCategoryDiv); + if (newCategory.getClickTarget) { + newCategory.getClickTarget().setAttribute('id', newCategory.getId()); + } } + return contentsContainer; } - return contentsContainer; -}; + /** + * Opens or closes the current category. + * @param {boolean} isExpanded True to expand the category, false to close. + * @public + */ + setExpanded(isExpanded) { + if (this.expanded_ === isExpanded) { + return; + } + this.expanded_ = isExpanded; + if (isExpanded) { + this.subcategoriesDiv_.style.display = 'block'; + this.openIcon_(this.iconDom_); + } else { + this.subcategoriesDiv_.style.display = 'none'; + this.closeIcon_(this.iconDom_); + } + aria.setState( + /** @type {!Element} */ (this.htmlDiv_), aria.State.EXPANDED, + isExpanded); -/** - * Opens or closes the current category. - * @param {boolean} isExpanded True to expand the category, false to close. - * @public - */ -CollapsibleToolboxCategory.prototype.setExpanded = function(isExpanded) { - if (this.expanded_ === isExpanded) { - return; + this.parentToolbox_.handleToolboxItemResize(); } - this.expanded_ = isExpanded; - if (isExpanded) { - this.subcategoriesDiv_.style.display = 'block'; - this.openIcon_(this.iconDom_); - } else { - this.subcategoriesDiv_.style.display = 'none'; - this.closeIcon_(this.iconDom_); + + /** + * @override + */ + setVisible_(isVisible) { + this.htmlDiv_.style.display = isVisible ? 'block' : 'none'; + const childToolboxItems = this.getChildToolboxItems(); + for (let i = 0; i < childToolboxItems.length; i++) { + const child = childToolboxItems[i]; + child.setVisible_(isVisible); + } + this.isHidden_ = !isVisible; + + if (this.parentToolbox_.getSelectedItem() === this) { + this.parentToolbox_.clearSelection(); + } } - aria.setState( - /** @type {!Element} */ (this.htmlDiv_), aria.State.EXPANDED, isExpanded); - this.parentToolbox_.handleToolboxItemResize(); -}; + /** + * Whether the category is expanded to show its child subcategories. + * @return {boolean} True if the toolbox item shows its children, false if it + * is collapsed. + * @public + */ + isExpanded() { + return this.expanded_; + } -/** - * @override - */ -CollapsibleToolboxCategory.prototype.setVisible_ = function(isVisible) { - this.htmlDiv_.style.display = isVisible ? 'block' : 'none'; - const childToolboxItems = this.getChildToolboxItems(); - for (let i = 0; i < childToolboxItems.length; i++) { - const child = childToolboxItems[i]; - child.setVisible_(isVisible); + /** + * @override + */ + isCollapsible() { + return true; } - this.isHidden_ = !isVisible; - if (this.parentToolbox_.getSelectedItem() === this) { - this.parentToolbox_.clearSelection(); + /** + * @override + */ + onClick(_e) { + this.toggleExpanded(); } -}; -/** - * Whether the category is expanded to show its child subcategories. - * @return {boolean} True if the toolbox item shows its children, false if it - * is collapsed. - * @public - */ -CollapsibleToolboxCategory.prototype.isExpanded = function() { - return this.expanded_; -}; + /** + * Toggles whether or not the category is expanded. + * @public + */ + toggleExpanded() { + this.setExpanded(!this.expanded_); + } -/** - * @override - */ -CollapsibleToolboxCategory.prototype.isCollapsible = function() { - return true; -}; + /** + * @override + */ + getDiv() { + return this.htmlDiv_; + } -/** - * @override - */ -CollapsibleToolboxCategory.prototype.onClick = function(_e) { - this.toggleExpanded(); -}; + /** + * Gets any children toolbox items. (ex. Gets the subcategories) + * @return {!Array} The child toolbox items. + */ + getChildToolboxItems() { + return this.toolboxItems_; + } +} /** - * Toggles whether or not the category is expanded. - * @public + * All the CSS class names that are used to create a collapsible + * category. This is all the properties from the regular category plus contents. + * @typedef {{ + * container:?string, + * row:?string, + * rowcontentcontainer:?string, + * icon:?string, + * label:?string, + * selected:?string, + * openicon:?string, + * closedicon:?string, + * contents:?string + * }} */ -CollapsibleToolboxCategory.prototype.toggleExpanded = function() { - this.setExpanded(!this.expanded_); -}; +CollapsibleToolboxCategory.CssConfig; /** + * Name used for registering a collapsible toolbox category. + * @type {string} * @override */ -CollapsibleToolboxCategory.prototype.getDiv = function() { - return this.htmlDiv_; -}; - -/** - * Gets any children toolbox items. (ex. Gets the subcategories) - * @return {!Array} The child toolbox items. - */ -CollapsibleToolboxCategory.prototype.getChildToolboxItems = function() { - return this.toolboxItems_; -}; +CollapsibleToolboxCategory.registrationName = 'collapsibleCategory'; registry.register( diff --git a/core/toolbox/toolbox.js b/core/toolbox/toolbox.js index 03bcbec71e3..8219099f40c 100644 --- a/core/toolbox/toolbox.js +++ b/core/toolbox/toolbox.js @@ -475,15 +475,15 @@ class Toolbox extends DeleteArea { registry.Type.TOOLBOX_ITEM, registryName.toLowerCase()); if (ToolboxItemClass) { const toolboxItem = new ToolboxItemClass(toolboxItemDef, this); - this.addToolboxItem_(toolboxItem); toolboxItem.init(); + this.addToolboxItem_(toolboxItem); const toolboxItemDom = toolboxItem.getDiv(); if (toolboxItemDom) { fragment.appendChild(toolboxItemDom); } // Adds the ID to the HTML element that can receive a click. // This is used in onClick_ to find the toolboxItem that was clicked. - if (toolboxItem.getClickTarget) { + if (toolboxItem.getClickTarget()) { toolboxItem.getClickTarget().setAttribute('id', toolboxItem.getId()); } } diff --git a/core/toolbox/toolbox_item.js b/core/toolbox/toolbox_item.js index 2403cf4d00e..1565ab5ebbf 100644 --- a/core/toolbox/toolbox_item.js +++ b/core/toolbox/toolbox_item.js @@ -30,128 +30,144 @@ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); /** * Class for an item in the toolbox. - * @param {!toolbox.ToolboxItemInfo} toolboxItemDef The JSON defining the - * toolbox item. - * @param {!IToolbox} toolbox The toolbox that holds the toolbox item. - * @param {ICollapsibleToolboxItem=} opt_parent The parent toolbox item - * or null if the category does not have a parent. - * @constructor * @implements {IToolboxItem} - * @alias Blockly.ToolboxItem */ -const ToolboxItem = function(toolboxItemDef, toolbox, opt_parent) { +class ToolboxItem { /** - * The id for the category. - * @type {string} - * @protected + * @param {!toolbox.ToolboxItemInfo} toolboxItemDef The JSON defining + * the toolbox item. + * @param {!IToolbox} toolbox The toolbox that holds the toolbox item. + * @param {ICollapsibleToolboxItem=} opt_parent The parent toolbox item + * or null if the category does not have a parent. + * @alias Blockly.ToolboxItem */ - this.id_ = toolboxItemDef['toolboxitemid'] || idGenerator.getNextUniqueId(); + constructor(toolboxItemDef, toolbox, opt_parent) { + /** + * The id for the category. + * @type {string} + * @protected + */ + this.id_ = toolboxItemDef['toolboxitemid'] || idGenerator.getNextUniqueId(); + + /** + * The parent of the category. + * @type {?ICollapsibleToolboxItem} + * @protected + */ + this.parent_ = opt_parent || null; + + /** + * The level that the category is nested at. + * @type {number} + * @protected + */ + this.level_ = this.parent_ ? this.parent_.getLevel() + 1 : 0; + + /** + * The JSON definition of the toolbox item. + * @type {?toolbox.ToolboxItemInfo} + * @protected + */ + this.toolboxItemDef_ = toolboxItemDef; + + /** + * The toolbox this category belongs to. + * @type {!IToolbox} + * @protected + */ + this.parentToolbox_ = toolbox; + + /** + * The workspace of the parent toolbox. + * @type {!WorkspaceSvg} + * @protected + */ + this.workspace_ = this.parentToolbox_.getWorkspace(); + } /** - * The parent of the category. - * @type {?ICollapsibleToolboxItem} - * @protected + * Initializes the toolbox item. + * This includes creating the DOM and updating the state of any items based + * on the info object. + * @public */ - this.parent_ = opt_parent || null; + init() { + // No-op by default. + } /** - * The level that the category is nested at. - * @type {number} - * @protected + * Gets the div for the toolbox item. + * @return {?Element} The div for the toolbox item. + * @public */ - this.level_ = this.parent_ ? this.parent_.getLevel() + 1 : 0; + getDiv() { + return null; + } /** - * The JSON definition of the toolbox item. - * @type {!toolbox.ToolboxItemInfo} - * @protected + * Gets the HTML element that is clickable. + * The parent toolbox element receives clicks. The parent toolbox will add an + * ID to this element so it can pass the onClick event to the correct + * toolboxItem. + * @return {?Element} The HTML element that receives clicks, or null if this + * item should not receive clicks. + * @public */ - this.toolboxItemDef_ = toolboxItemDef; + getClickTarget() { + return null; + } /** - * The toolbox this category belongs to. - * @type {!IToolbox} - * @protected + * Gets a unique identifier for this toolbox item. + * @return {string} The ID for the toolbox item. + * @public */ - this.parentToolbox_ = toolbox; + getId() { + return this.id_; + } /** - * The workspace of the parent toolbox. - * @type {!WorkspaceSvg} - * @protected + * Gets the parent if the toolbox item is nested. + * @return {?IToolboxItem} The parent toolbox item, or null if + * this toolbox item is not nested. + * @public */ - this.workspace_ = this.parentToolbox_.getWorkspace(); -}; + getParent() { + return null; + } -/** - * Initializes the toolbox item. - * This includes creating the DOM and updating the state of any items based - * on the info object. - * @public - */ -ToolboxItem.prototype.init = function() { - // No-op by default. -}; - -/** - * Gets the div for the toolbox item. - * @return {?Element} The div for the toolbox item. - * @public - */ -ToolboxItem.prototype.getDiv = function() { - return null; -}; - -/** - * Gets a unique identifier for this toolbox item. - * @return {string} The ID for the toolbox item. - * @public - */ -ToolboxItem.prototype.getId = function() { - return this.id_; -}; - -/** - * Gets the parent if the toolbox item is nested. - * @return {?IToolboxItem} The parent toolbox item, or null if - * this toolbox item is not nested. - * @public - */ -ToolboxItem.prototype.getParent = function() { - return null; -}; - -/** - * Gets the nested level of the category. - * @return {number} The nested level of the category. - * @package - */ -ToolboxItem.prototype.getLevel = function() { - return this.level_; -}; + /** + * Gets the nested level of the category. + * @return {number} The nested level of the category. + * @package + */ + getLevel() { + return this.level_; + } -/** - * Whether the toolbox item is selectable. - * @return {boolean} True if the toolbox item can be selected. - * @public - */ -ToolboxItem.prototype.isSelectable = function() { - return false; -}; + /** + * Whether the toolbox item is selectable. + * @return {boolean} True if the toolbox item can be selected. + * @public + */ + isSelectable() { + return false; + } -/** - * Whether the toolbox item is collapsible. - * @return {boolean} True if the toolbox item is collapsible. - * @public - */ -ToolboxItem.prototype.isCollapsible = function() { - return false; -}; + /** + * Whether the toolbox item is collapsible. + * @return {boolean} True if the toolbox item is collapsible. + * @public + */ + isCollapsible() { + return false; + } -/** - * Dispose of this toolbox item. No-op by default. - * @public - */ -ToolboxItem.prototype.dispose = function() {}; + /** + * Dispose of this toolbox item. No-op by default. + * @public + */ + dispose() {} +} exports.ToolboxItem = ToolboxItem; diff --git a/scripts/gulpfiles/chunks.json b/scripts/gulpfiles/chunks.json index 3c299828575..f817e0af863 100644 --- a/scripts/gulpfiles/chunks.json +++ b/scripts/gulpfiles/chunks.json @@ -253,7 +253,6 @@ "./core/extensions.js", "./core/block.js", "./core/utils/string.js", - "./core/utils/object.js", "./core/dialog.js", "./core/events/events_var_base.js", "./core/events/events_var_create.js", diff --git a/tests/deps.js b/tests/deps.js index 6118ba8b1b3..958ecff057e 100644 --- a/tests/deps.js +++ b/tests/deps.js @@ -219,7 +219,7 @@ goog.addDependency('../../core/theme/themes.js', ['Blockly.Themes'], ['Blockly.T goog.addDependency('../../core/theme/zelos.js', ['Blockly.Themes.Zelos'], ['Blockly.Theme'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/theme_manager.js', ['Blockly.ThemeManager'], ['Blockly.utils.array', 'Blockly.utils.dom'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/toolbox/category.js', ['Blockly.ToolboxCategory'], ['Blockly.Css', 'Blockly.ISelectableToolboxItem', 'Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.parsing', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); -goog.addDependency('../../core/toolbox/collapsible_category.js', ['Blockly.CollapsibleToolboxCategory'], ['Blockly.ICollapsibleToolboxItem', 'Blockly.ToolboxCategory', 'Blockly.ToolboxSeparator', 'Blockly.registry', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); +goog.addDependency('../../core/toolbox/collapsible_category.js', ['Blockly.CollapsibleToolboxCategory'], ['Blockly.ICollapsibleToolboxItem', 'Blockly.ToolboxCategory', 'Blockly.ToolboxSeparator', 'Blockly.registry', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/toolbox/separator.js', ['Blockly.ToolboxSeparator'], ['Blockly.Css', 'Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils.dom', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/toolbox/toolbox.js', ['Blockly.Toolbox'], ['Blockly.BlockSvg', 'Blockly.CollapsibleToolboxCategory', 'Blockly.ComponentManager', 'Blockly.Css', 'Blockly.DeleteArea', 'Blockly.Events.ToolboxItemSelect', 'Blockly.Events.utils', 'Blockly.IAutoHideable', 'Blockly.IKeyboardAccessible', 'Blockly.IStyleable', 'Blockly.IToolbox', 'Blockly.Options', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.common', 'Blockly.registry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'}); goog.addDependency('../../core/toolbox/toolbox_item.js', ['Blockly.ToolboxItem'], ['Blockly.IToolboxItem', 'Blockly.utils.idGenerator'], {'lang': 'es6', 'module': 'goog'});