UNPKG

@blockly/field-grid-dropdown

Version:
252 lines (219 loc) 6.86 kB
/** * @license * Copyright 2020 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Grid dropdown field. * @author kozbial@google.com (Monica Kozbial) */ import * as Blockly from 'blockly/core'; import {Grid} from './grid'; import type {GridItem} from './grid_item'; /** * A config object for defining a field grid dropdown. */ export interface FieldGridDropdownConfig extends Blockly.FieldDropdownConfig { columns?: string | number; primaryColour?: string; borderColour?: string; } /** * Construct a FieldGridDropdown from a JSON arg object. */ export interface FieldGridDropdownFromJsonConfig extends FieldGridDropdownConfig { options?: Blockly.MenuGenerator; } type FieldGridDropdownValidator = Blockly.FieldDropdownValidator; /** * Grid dropdown field. */ export class FieldGridDropdown extends Blockly.FieldDropdown { /** * The number of columns in the dropdown grid. Must be an integer value * greater than 0. Defaults to 3. */ private columns = 3; private primaryColour?: string; private borderColour?: string; /** Object representing the grid of choices show in the dropdown. */ private grid?: Grid; /** * Class for an grid dropdown field. * * @param menuGenerator A non-empty array of options for a dropdown list, * or a function which generates these options. * @param validator A function that is called to validate * changes to the field's value. Takes in a language-neutral dropdown * option & returns a validated language-neutral dropdown option, or null * to abort the change. * @param config A map of options used to configure the field. * See the [field creation documentation]{@link * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation} * for a list of properties this parameter supports. * @extends {Blockly.Field} * @constructor * @throws {TypeError} If `menuGenerator` options are incorrectly structured. */ constructor( menuGenerator: Blockly.MenuGenerator, validator?: FieldGridDropdownValidator, config?: FieldGridDropdownConfig, ) { super(menuGenerator, validator, config); if (config?.columns) { this.setColumns(parseInt(`${config.columns}`)); } if (config && config.primaryColour) { this.primaryColour = config.primaryColour; } if (config && config.borderColour) { this.borderColour = config.borderColour; } } /** * Constructs a FieldGridDropdown from a JSON arg object. * * @param config A JSON object with options. * @returns The new field instance. * @package * @nocollapse */ static fromJson(config: FieldGridDropdownFromJsonConfig) { if (!config.options) { throw new Error( 'options are required for the dropdown field. The ' + 'options property must be assigned an array of ' + '[humanReadableValue, languageNeutralValue] tuples.', ); } // `this` might be a subclass of FieldDropdown if that class doesn't // override the static fromJson method. return new this(config.options, undefined, config); } /** * Sets the number of columns on the grid. Updates the styling to reflect. * * @param columns The number of columns. Is rounded to * an integer value and must be greater than 0. Invalid * values are ignored. */ setColumns(columns: number) { if (!isNaN(columns) && columns >= 1) { this.columns = columns; // If the field is currently being shown, reload the grid. if ( Blockly.DropDownDiv.getOwner() === this && Blockly.DropDownDiv.isVisible() ) { this.grid?.dispose(); this.showEditor_(); } } } /* eslint-disable @typescript-eslint/naming-convention */ /** * Create a dropdown menu under the text. * * @param e Optional mouse event that triggered the field to open, or * undefined if triggered programmatically. */ protected showEditor_(e?: MouseEvent) { Blockly.DropDownDiv.clearContent(); const rtl = !!this.getSourceBlock()?.workspace.RTL; this.grid = new Grid( Blockly.DropDownDiv.getContentDiv(), this.getOptions(false), this.columns, rtl, (selectedItem: GridItem) => { Blockly.DropDownDiv.hideIfOwner(this); this.setValue(selectedItem.getValue()); }, ); Blockly.DropDownDiv.getContentDiv().classList.add( 'blocklyFieldGridContainer', ); const colours = this.getColours(); if (colours && colours.border) { Blockly.DropDownDiv.setColour(colours.primary, colours.border); } Blockly.DropDownDiv.showPositionedByField( this, this.dropdownDispose_.bind(this), ); const selectedValue = this.getValue(); if (selectedValue) { this.grid.setSelectedValue(selectedValue); } } /** * Updates the field's value to the given value. * * @param newValue The new value for this field. */ protected override doValueUpdate_(newValue: string) { super.doValueUpdate_(newValue); this.grid?.setSelectedValue(newValue); } /** * Determine the colours for the dropdowndiv. The dropdown should match block * colour unless other colours are specified in the config. * * @returns The colours to set for the dropdowndiv. */ private getColours() { if (this.primaryColour && this.borderColour) { return { primary: this.primaryColour, border: this.borderColour, }; } const sourceBlock = this.getSourceBlock(); if (!(sourceBlock instanceof Blockly.BlockSvg)) return; const colourSource = sourceBlock.isShadow() ? sourceBlock.getParent() : sourceBlock; if (!colourSource) return; return { primary: this.primaryColour ?? colourSource.getColour(), border: this.borderColour ?? colourSource.getColourTertiary(), }; } } Blockly.fieldRegistry.register('field_grid_dropdown', FieldGridDropdown); /** * CSS for grid field. */ Blockly.Css.register(` .blocklyFieldGridContainer { padding: 7px; overflow: auto; } .blocklyFieldGrid { display: grid; grid-gap: 7px; grid-template-columns: repeat(var(--grid-columns), min-content); } .blocklyFieldGrid .blocklyFieldGridItem { border: 1px solid rgba(1, 1, 1, 0.5); border-radius: 4px; color: white; min-width: auto; background: none; white-space: nowrap; cursor: pointer; padding: 6px 15px; } .blocklyFieldGrid .blocklyFieldGridRow { display: contents; } .blocklyFieldGrid .blocklyFieldGridItem.blocklyFieldGridItemSelected { background-color: rgba(1, 1, 1, 0.25); } .blocklyFieldGrid .blocklyFieldGridItem:focus { box-shadow: 0 0 0 4px hsla(0, 0%, 100%, .2); outline: none; } `);