/** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: Apache-2.0 */ // Former goog.module ID: Blockly.zelos.ConstantProvider import {ConnectionType} from '../../connection_type.js'; import type {RenderedConnection} from '../../rendered_connection.js'; import type {Theme} from '../../theme.js'; import * as utilsColour from '../../utils/colour.js'; import * as dom from '../../utils/dom.js'; import {Svg} from '../../utils/svg.js'; import * as svgPaths from '../../utils/svg_paths.js'; import {ConstantProvider as BaseConstantProvider} from '../common/constants.js'; import type {Shape} from '../common/constants.js'; /** An object containing sizing and path information about inside corners. */ export interface InsideCorners { width: number; height: number; pathTop: string; pathBottom: string; rightWidth: number; rightHeight: number; pathTopRight: string; pathBottomRight: string; } /** * An object that provides constants for rendering blocks in Zelos mode. */ export class ConstantProvider extends BaseConstantProvider { GRID_UNIT = 4; STATEMENT_INPUT_SPACER_MIN_WIDTH: number; override CURSOR_COLOUR = '#ffa200'; /** * Radius of the cursor for input and output connections. */ CURSOR_RADIUS = 5; override JAGGED_TEETH_HEIGHT = 0; override JAGGED_TEETH_WIDTH = 0; override START_HAT_HEIGHT = 22; override START_HAT_WIDTH = 96; override SHAPES = {HEXAGONAL: 1, ROUND: 2, SQUARE: 3, PUZZLE: 4, NOTCH: 5}; /** * Map of output/input shapes and the amount they should cause a block to be * padded. Outer key is the outer shape, inner key is the inner shape. * When a block with the outer shape contains an input block with the inner * shape on its left or right edge, the block elements are aligned such that * the padding specified is reached. */ SHAPE_IN_SHAPE_PADDING: {[key: number]: {[key: number]: number}} = { 1: { // Outer shape: hexagon. 0: 5 * this.GRID_UNIT, // Field in hexagon. 1: 2 * this.GRID_UNIT, // Hexagon in hexagon. 2: 5 * this.GRID_UNIT, // Round in hexagon. 3: 5 * this.GRID_UNIT, // Square in hexagon. }, 2: { // Outer shape: round. 0: 3 * this.GRID_UNIT, // Field in round. 1: 3 * this.GRID_UNIT, // Hexagon in round. 2: 1 * this.GRID_UNIT, // Round in round. 3: 2 * this.GRID_UNIT, // Square in round. }, 3: { // Outer shape: square. 0: 2 * this.GRID_UNIT, // Field in square. 1: 2 * this.GRID_UNIT, // Hexagon in square. 2: 2 * this.GRID_UNIT, // Round in square. 3: 2 * this.GRID_UNIT, // Square in square. }, }; override FULL_BLOCK_FIELDS = true; override FIELD_TEXT_FONTWEIGHT = 'bold'; override FIELD_TEXT_FONTFAMILY = '"Helvetica Neue", "Segoe UI", Helvetica, sans-serif'; override FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW = true; override FIELD_DROPDOWN_COLOURED_DIV = true; override FIELD_DROPDOWN_SVG_ARROW = true; override FIELD_TEXTINPUT_BOX_SHADOW = true; override FIELD_COLOUR_FULL_BLOCK = true; MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH: number; /** The selected glow colour. */ SELECTED_GLOW_COLOUR = '#fff200'; /** The size of the selected glow. */ SELECTED_GLOW_SIZE = 0.5; /** The replacement glow colour. */ REPLACEMENT_GLOW_COLOUR = '#fff200'; /** The size of the selected glow. */ REPLACEMENT_GLOW_SIZE = 2; /** * The ID of the selected glow filter, or the empty string if no filter is * set. */ selectedGlowFilterId = ''; /** * The element to use for a selected glow, or null if not set. */ private selectedGlowFilter: SVGElement | null = null; /** * The ID of the replacement glow filter, or the empty string if no filter * is set. */ replacementGlowFilterId = ''; /** * The element to use for a replacement glow, or null if not set. */ private replacementGlowFilter: SVGElement | null = null; /** * The object containing information about the hexagon used for a boolean * reporter block. Null before init is called. */ HEXAGONAL: Shape | null = null; /** * The object containing information about the hexagon used for a number or * string reporter block. Null before init is called. */ ROUNDED: Shape | null = null; /** * The object containing information about the hexagon used for a * rectangular reporter block. Null before init is called. */ SQUARED: Shape | null = null; constructor() { super(); this.SMALL_PADDING = this.GRID_UNIT; this.MEDIUM_PADDING = 2 * this.GRID_UNIT; this.MEDIUM_LARGE_PADDING = 3 * this.GRID_UNIT; this.LARGE_PADDING = 4 * this.GRID_UNIT; this.CORNER_RADIUS = 1 * this.GRID_UNIT; this.NOTCH_WIDTH = 9 * this.GRID_UNIT; this.NOTCH_HEIGHT = 2 * this.GRID_UNIT; this.NOTCH_OFFSET_LEFT = 3 * this.GRID_UNIT; this.STATEMENT_INPUT_NOTCH_OFFSET = this.NOTCH_OFFSET_LEFT; this.MIN_BLOCK_WIDTH = 2 * this.GRID_UNIT; this.MIN_BLOCK_HEIGHT = 12 * this.GRID_UNIT; this.EMPTY_STATEMENT_INPUT_HEIGHT = 6 * this.GRID_UNIT; this.TOP_ROW_MIN_HEIGHT = this.CORNER_RADIUS; this.TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT = this.LARGE_PADDING; this.BOTTOM_ROW_MIN_HEIGHT = this.CORNER_RADIUS; this.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT = 6 * this.GRID_UNIT; this.STATEMENT_BOTTOM_SPACER = -this.NOTCH_HEIGHT; /** Minimum statement input spacer width. */ this.STATEMENT_INPUT_SPACER_MIN_WIDTH = 40 * this.GRID_UNIT; this.STATEMENT_INPUT_PADDING_LEFT = 4 * this.GRID_UNIT; this.EMPTY_INLINE_INPUT_PADDING = 4 * this.GRID_UNIT; this.EMPTY_INLINE_INPUT_HEIGHT = 8 * this.GRID_UNIT; this.DUMMY_INPUT_MIN_HEIGHT = 8 * this.GRID_UNIT; this.DUMMY_INPUT_SHADOW_MIN_HEIGHT = 6 * this.GRID_UNIT; this.CURSOR_WS_WIDTH = 20 * this.GRID_UNIT; this.FIELD_TEXT_FONTSIZE = 3 * this.GRID_UNIT; this.FIELD_BORDER_RECT_RADIUS = this.CORNER_RADIUS; this.FIELD_BORDER_RECT_X_PADDING = 2 * this.GRID_UNIT; this.FIELD_BORDER_RECT_Y_PADDING = 1.625 * this.GRID_UNIT; this.FIELD_BORDER_RECT_HEIGHT = 8 * this.GRID_UNIT; this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT = 8 * this.GRID_UNIT; this.FIELD_DROPDOWN_SVG_ARROW_PADDING = this.FIELD_BORDER_RECT_X_PADDING; this.FIELD_COLOUR_DEFAULT_WIDTH = 6 * this.GRID_UNIT; this.FIELD_COLOUR_DEFAULT_HEIGHT = 8 * this.GRID_UNIT; this.FIELD_CHECKBOX_X_OFFSET = 1 * this.GRID_UNIT; /** The maximum width of a dynamic connection shape. */ this.MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH = 12 * this.GRID_UNIT; } override setFontConstants_(theme: Theme) { super.setFontConstants_(theme); this.FIELD_BORDER_RECT_HEIGHT = this.FIELD_TEXT_HEIGHT + this.FIELD_BORDER_RECT_Y_PADDING * 2; this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT = this.FIELD_BORDER_RECT_HEIGHT; } override init() { super.init(); this.HEXAGONAL = this.makeHexagonal(); this.ROUNDED = this.makeRounded(); this.SQUARED = this.makeSquared(); this.STATEMENT_INPUT_NOTCH_OFFSET = this.NOTCH_OFFSET_LEFT + (this.INSIDE_CORNERS as InsideCorners).rightWidth; } override setDynamicProperties_(theme: Theme) { super.setDynamicProperties_(theme); this.SELECTED_GLOW_COLOUR = theme.getComponentStyle('selectedGlowColour') || this.SELECTED_GLOW_COLOUR; const selectedGlowSize = Number( theme.getComponentStyle('selectedGlowSize'), ); this.SELECTED_GLOW_SIZE = selectedGlowSize && !isNaN(selectedGlowSize) ? selectedGlowSize : this.SELECTED_GLOW_SIZE; this.REPLACEMENT_GLOW_COLOUR = theme.getComponentStyle('replacementGlowColour') || this.REPLACEMENT_GLOW_COLOUR; const replacementGlowSize = Number( theme.getComponentStyle('replacementGlowSize'), ); this.REPLACEMENT_GLOW_SIZE = replacementGlowSize && !isNaN(replacementGlowSize) ? replacementGlowSize : this.REPLACEMENT_GLOW_SIZE; } override dispose() { super.dispose(); if (this.selectedGlowFilter) { dom.removeNode(this.selectedGlowFilter); } if (this.replacementGlowFilter) { dom.removeNode(this.replacementGlowFilter); } } override makeStartHat() { const height = this.START_HAT_HEIGHT; const width = this.START_HAT_WIDTH; const mainPath = svgPaths.curve('c', [ svgPaths.point(25, -height), svgPaths.point(71, -height), svgPaths.point(width, 0), ]); return {height, width, path: mainPath}; } /** * Create sizing and path information about a hexagonal shape. * * @returns An object containing sizing and path information about a hexagonal * shape for connections. */ protected makeHexagonal(): Shape { const maxWidth = this.MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH; /** * Make the main path for the hexagonal connection shape out of two lines. * The lines are defined with relative positions and require the block * height. The 'up' and 'down' versions of the paths are the same, but the Y * sign flips. The 'left' and 'right' versions of the path are also the * same, but the X sign flips. * * @param height The height of the block the connection is on. * @param up True if the path should be drawn from bottom to top, false * otherwise. * @param right True if the path is for the right side of the block. * @returns A path fragment describing a rounded connection. */ function makeMainPath(height: number, up: boolean, right: boolean): string { const halfHeight = height / 2; const width = halfHeight > maxWidth ? maxWidth : halfHeight; const forward = up ? -1 : 1; const direction = right ? -1 : 1; const dy = (forward * height) / 2; return ( svgPaths.lineTo(-direction * width, dy) + svgPaths.lineTo(direction * width, dy) ); } return { type: this.SHAPES.HEXAGONAL, isDynamic: true, width(height: number): number { const halfHeight = height / 2; return halfHeight > maxWidth ? maxWidth : halfHeight; }, height(height: number): number { return height; }, connectionOffsetY(connectionHeight: number): number { return connectionHeight / 2; }, connectionOffsetX(connectionWidth: number): number { return -connectionWidth; }, pathDown(height: number): string { return makeMainPath(height, false, false); }, pathUp(height: number): string { return makeMainPath(height, true, false); }, pathRightDown(height: number): string { return makeMainPath(height, false, true); }, pathRightUp(height: number): string { return makeMainPath(height, false, true); }, }; } /** * Create sizing and path information about a rounded shape. * * @returns An object containing sizing and path information about a rounded * shape for connections. */ protected makeRounded(): Shape { const maxWidth = this.MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH; const maxHeight = maxWidth * 2; /** * Make the main path for the rounded connection shape out of two arcs and * a line that joins them. The arcs are defined with relative positions. * Usually, the height of the block is split between the two arcs. In the * case where the height of the block exceeds the maximum height, a line is * drawn in between the two arcs. The 'up' and 'down' versions of the paths * are the same, but the Y sign flips. The 'up' and 'right' versions of the * path flip the sweep-flag which moves the arc at negative angles. * * @param blockHeight The height of the block the connection is on. * @param up True if the path should be drawn from bottom to top, false * otherwise. * @param right True if the path is for the right side of the block. * @returns A path fragment describing a rounded connection. */ function makeMainPath( blockHeight: number, up: boolean, right: boolean, ): string { const remainingHeight = blockHeight > maxHeight ? blockHeight - maxHeight : 0; const height = blockHeight > maxHeight ? maxHeight : blockHeight; const radius = height / 2; const sweep = right === up ? '0' : '1'; return ( svgPaths.arc( 'a', '0 0,' + sweep, radius, svgPaths.point((right ? 1 : -1) * radius, (up ? -1 : 1) * radius), ) + svgPaths.lineOnAxis('v', (up ? -1 : 1) * remainingHeight) + svgPaths.arc( 'a', '0 0,' + sweep, radius, svgPaths.point((right ? -1 : 1) * radius, (up ? -1 : 1) * radius), ) ); } return { type: this.SHAPES.ROUND, isDynamic: true, width(height: number): number { const halfHeight = height / 2; return halfHeight > maxWidth ? maxWidth : halfHeight; }, height(height: number): number { return height; }, connectionOffsetY(connectionHeight: number): number { return connectionHeight / 2; }, connectionOffsetX(connectionWidth: number): number { return -connectionWidth; }, pathDown(height: number): string { return makeMainPath(height, false, false); }, pathUp(height: number): string { return makeMainPath(height, true, false); }, pathRightDown(height: number): string { return makeMainPath(height, false, true); }, pathRightUp(height: number): string { return makeMainPath(height, false, true); }, }; } /** * Create sizing and path information about a squared shape. * * @returns An object containing sizing and path information about a squared * shape for connections. */ protected makeSquared(): Shape { const radius = this.CORNER_RADIUS; /** * Make the main path for the squared connection shape out of two corners * and a single line in-between (a and v). These are defined in relative * positions and require the height of the block. * The 'left' and 'right' versions of the paths are the same, but the Y sign * flips. The 'up' and 'down' versions of the path determine where the * corner point is placed and in turn the direction of the corners. * * @param height The height of the block the connection is on. * @param up True if the path should be drawn from bottom to top, false * otherwise. * @param right True if the path is for the right side of the block. * @returns A path fragment describing a squared connection. */ function makeMainPath(height: number, up: boolean, right: boolean): string { const innerHeight = height - radius * 2; const sweep = right === up ? '0' : '1'; return ( svgPaths.arc( 'a', '0 0,' + sweep, radius, svgPaths.point((right ? 1 : -1) * radius, (up ? -1 : 1) * radius), ) + svgPaths.lineOnAxis('v', (up ? -1 : 1) * innerHeight) + svgPaths.arc( 'a', '0 0,' + sweep, radius, svgPaths.point((right ? -1 : 1) * radius, (up ? -1 : 1) * radius), ) ); } return { type: this.SHAPES.SQUARE, isDynamic: true, width(_height: number): number { return radius; }, height(height: number): number { return height; }, connectionOffsetY(connectionHeight: number): number { return connectionHeight / 2; }, connectionOffsetX(connectionWidth: number): number { return -connectionWidth; }, pathDown(height: number): string { return makeMainPath(height, false, false); }, pathUp(height: number): string { return makeMainPath(height, true, false); }, pathRightDown(height: number): string { return makeMainPath(height, false, true); }, pathRightUp(height: number): string { return makeMainPath(height, false, true); }, }; } override shapeFor(connection: RenderedConnection): Shape { let checks = connection.getCheck(); if (!checks && connection.targetConnection) { checks = connection.targetConnection.getCheck(); } let outputShape; switch (connection.type) { case ConnectionType.INPUT_VALUE: case ConnectionType.OUTPUT_VALUE: outputShape = connection.getSourceBlock().getOutputShape(); // If the block has an output shape set, use that instead. if (outputShape !== null) { switch (outputShape) { case this.SHAPES.HEXAGONAL: return this.HEXAGONAL!; case this.SHAPES.ROUND: return this.ROUNDED!; case this.SHAPES.SQUARE: return this.SQUARED!; } } // Includes doesn't work in IE. if (checks && checks.includes('Boolean')) { return this.HEXAGONAL!; } if (checks && checks.includes('Number')) { return this.ROUNDED!; } if (checks && checks.includes('String')) { return this.ROUNDED!; } return this.ROUNDED!; case ConnectionType.PREVIOUS_STATEMENT: case ConnectionType.NEXT_STATEMENT: return this.NOTCH!; default: throw Error('Unknown type'); } } override makeNotch() { const width = this.NOTCH_WIDTH; const height = this.NOTCH_HEIGHT; const innerWidth = width / 3; const curveWidth = innerWidth / 3; const halfHeight = height / 2; const quarterHeight = halfHeight / 2; /** * Make the main path for the notch. * * @param dir Direction multiplier to apply to horizontal offsets along the * path. Either 1 or -1. * @returns A path fragment describing a notch. */ function makeMainPath(dir: number): string { return ( svgPaths.curve('c', [ svgPaths.point((dir * curveWidth) / 2, 0), svgPaths.point((dir * curveWidth * 3) / 4, quarterHeight / 2), svgPaths.point(dir * curveWidth, quarterHeight), ]) + svgPaths.line([svgPaths.point(dir * curveWidth, halfHeight)]) + svgPaths.curve('c', [ svgPaths.point((dir * curveWidth) / 4, quarterHeight / 2), svgPaths.point((dir * curveWidth) / 2, quarterHeight), svgPaths.point(dir * curveWidth, quarterHeight), ]) + svgPaths.lineOnAxis('h', dir * innerWidth) + svgPaths.curve('c', [ svgPaths.point((dir * curveWidth) / 2, 0), svgPaths.point((dir * curveWidth * 3) / 4, -(quarterHeight / 2)), svgPaths.point(dir * curveWidth, -quarterHeight), ]) + svgPaths.line([svgPaths.point(dir * curveWidth, -halfHeight)]) + svgPaths.curve('c', [ svgPaths.point((dir * curveWidth) / 4, -(quarterHeight / 2)), svgPaths.point((dir * curveWidth) / 2, -quarterHeight), svgPaths.point(dir * curveWidth, -quarterHeight), ]) ); } const pathLeft = makeMainPath(1); const pathRight = makeMainPath(-1); return { type: this.SHAPES.NOTCH, width, height, pathLeft, pathRight, }; } override makeInsideCorners() { const radius = this.CORNER_RADIUS; const innerTopLeftCorner = svgPaths.arc( 'a', '0 0,0', radius, svgPaths.point(-radius, radius), ); const innerTopRightCorner = svgPaths.arc( 'a', '0 0,1', radius, svgPaths.point(-radius, radius), ); const innerBottomLeftCorner = svgPaths.arc( 'a', '0 0,0', radius, svgPaths.point(radius, radius), ); const innerBottomRightCorner = svgPaths.arc( 'a', '0 0,1', radius, svgPaths.point(radius, radius), ); return { width: radius, height: radius, pathTop: innerTopLeftCorner, pathBottom: innerBottomLeftCorner, rightWidth: radius, rightHeight: radius, pathTopRight: innerTopRightCorner, pathBottomRight: innerBottomRightCorner, }; } override generateSecondaryColour_(colour: string) { return utilsColour.blend('#000', colour, 0.15) || colour; } override generateTertiaryColour_(colour: string) { return utilsColour.blend('#000', colour, 0.25) || colour; } override createDom(svg: SVGElement, tagName: string, selector: string) { super.createDom(svg, tagName, selector); /* ... filters go here ... */ const defs = dom.createSvgElement(Svg.DEFS, {}, svg); // Using a dilate distorts the block shape. // Instead use a gaussian blur, and then set all alpha to 1 with a transfer. const selectedGlowFilter = dom.createSvgElement( Svg.FILTER, { 'id': 'blocklySelectedGlowFilter' + this.randomIdentifier, 'height': '160%', 'width': '180%', 'y': '-30%', 'x': '-40%', }, defs, ); dom.createSvgElement( Svg.FEGAUSSIANBLUR, {'in': 'SourceGraphic', 'stdDeviation': this.SELECTED_GLOW_SIZE}, selectedGlowFilter, ); // Set all gaussian blur pixels to 1 opacity before applying flood const selectedComponentTransfer = dom.createSvgElement( Svg.FECOMPONENTTRANSFER, {'result': 'outBlur'}, selectedGlowFilter, ); dom.createSvgElement( Svg.FEFUNCA, {'type': 'table', 'tableValues': '0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1'}, selectedComponentTransfer, ); // Color the highlight dom.createSvgElement( Svg.FEFLOOD, { 'flood-color': this.SELECTED_GLOW_COLOUR, 'flood-opacity': 1, 'result': 'outColor', }, selectedGlowFilter, ); dom.createSvgElement( Svg.FECOMPOSITE, { 'in': 'outColor', 'in2': 'outBlur', 'operator': 'in', 'result': 'outGlow', }, selectedGlowFilter, ); this.selectedGlowFilterId = selectedGlowFilter.id; this.selectedGlowFilter = selectedGlowFilter; // Using a dilate distorts the block shape. // Instead use a gaussian blur, and then set all alpha to 1 with a transfer. const replacementGlowFilter = dom.createSvgElement( Svg.FILTER, { 'id': 'blocklyReplacementGlowFilter' + this.randomIdentifier, 'height': '160%', 'width': '180%', 'y': '-30%', 'x': '-40%', }, defs, ); dom.createSvgElement( Svg.FEGAUSSIANBLUR, {'in': 'SourceGraphic', 'stdDeviation': this.REPLACEMENT_GLOW_SIZE}, replacementGlowFilter, ); // Set all gaussian blur pixels to 1 opacity before applying flood const replacementComponentTransfer = dom.createSvgElement( Svg.FECOMPONENTTRANSFER, {'result': 'outBlur'}, replacementGlowFilter, ); dom.createSvgElement( Svg.FEFUNCA, {'type': 'table', 'tableValues': '0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1'}, replacementComponentTransfer, ); // Color the highlight dom.createSvgElement( Svg.FEFLOOD, { 'flood-color': this.REPLACEMENT_GLOW_COLOUR, 'flood-opacity': 1, 'result': 'outColor', }, replacementGlowFilter, ); dom.createSvgElement( Svg.FECOMPOSITE, { 'in': 'outColor', 'in2': 'outBlur', 'operator': 'in', 'result': 'outGlow', }, replacementGlowFilter, ); dom.createSvgElement( Svg.FECOMPOSITE, { 'in': 'SourceGraphic', 'in2': 'outGlow', 'operator': 'over', }, replacementGlowFilter, ); this.replacementGlowFilterId = replacementGlowFilter.id; this.replacementGlowFilter = replacementGlowFilter; } override getCSS_(selector: string) { return [ /* eslint-disable indent */ // Text. `${selector} .blocklyText,`, `${selector} .blocklyFlyoutLabelText {`, `font: ${this.FIELD_TEXT_FONTWEIGHT} ${this.FIELD_TEXT_FONTSIZE}` + `pt ${this.FIELD_TEXT_FONTFAMILY};`, `}`, `${selector} .blocklyTextInputBubble textarea {`, `font-weight: normal;`, `}`, // Fields. `${selector} .blocklyText {`, `fill: #fff;`, `}`, `${selector} .blocklyNonEditableText>rect:not(.blocklyDropdownRect),`, `${selector} .blocklyEditableText>rect:not(.blocklyDropdownRect) {`, `fill: ${this.FIELD_BORDER_RECT_COLOUR};`, `}`, `${selector} .blocklyNonEditableText>text,`, `${selector} .blocklyEditableText>text,`, `${selector} .blocklyNonEditableText>g>text,`, `${selector} .blocklyEditableText>g>text {`, `fill: #575E75;`, `}`, // Flyout labels. `${selector} .blocklyFlyoutLabelText {`, `fill: #575E75;`, `}`, // Bubbles. `${selector} .blocklyText.blocklyBubbleText {`, `fill: #575E75;`, `}`, // Editable field hover. `${selector} .blocklyDraggable:not(.blocklyDisabled)`, ` .blocklyEditableText:not(.editing):hover>rect,`, `${selector} .blocklyDraggable:not(.blocklyDisabled)`, ` .blocklyEditableText:not(.editing):hover>.blocklyPath {`, `stroke: #fff;`, `stroke-width: 2;`, `}`, // Text field input. `${selector} .blocklyHtmlInput {`, `font-family: ${this.FIELD_TEXT_FONTFAMILY};`, `font-weight: ${this.FIELD_TEXT_FONTWEIGHT};`, `color: #575E75;`, `}`, // Dropdown field. `${selector} .blocklyDropdownText {`, `fill: #fff !important;`, `}`, // Widget and Dropdown Div `${selector}.blocklyWidgetDiv .goog-menuitem,`, `${selector}.blocklyDropDownDiv .goog-menuitem {`, `font-family: ${this.FIELD_TEXT_FONTFAMILY};`, `}`, `${selector}.blocklyDropDownDiv .goog-menuitem-content {`, `color: #fff;`, `}`, // Connection highlight. `${selector} .blocklyHighlightedConnectionPath {`, `stroke: ${this.SELECTED_GLOW_COLOUR};`, `}`, // Disabled outline paths. `${selector} .blocklyDisabled > .blocklyOutlinePath {`, `fill: url(#blocklyDisabledPattern${this.randomIdentifier})`, `}`, // Insertion marker. `${selector} .blocklyInsertionMarker>.blocklyPath {`, `fill-opacity: ${this.INSERTION_MARKER_OPACITY};`, `stroke: none;`, `}`, ]; } } /* eslint-enable indent */