mirror of
https://github.com/google/blockly.git
synced 2026-01-08 17:40:09 +01:00
* feat!: Make bubbles, comments, and icons focusable * feat!: Make ISelectable and ICopyable focusable. * feat: Consolidate selection calls. Now everything is based on focus with selection only being used as a proxy. * feat: Invert responsibility for setSelected(). Now setSelected() is only for quasi-external use. * feat: Push up shadow check to getters. Needed new common-level helper. * chore: Lint fixes. * feat!: Allow IFocusableNode to disable focus. * chore: post-merge lint fixes * fix: Fix tests + text bubble focusing. This fixed then regressed a circular dependency causing the node and advanced compilation steps to fail. This investigation is ongoing. * fix: Clean up & fix imports. This ensures the node and advanced compilation test steps now pass. * fix: Lint fixes + revert commented out logic. * chore: Remove unnecessary cast. Addresses reviewer comment. * fix: Some issues and a bunch of clean-ups. This addresses a bunch of review comments, and fixes selecting workspace comments. * chore: Lint fix. * fix: Remove unnecessary shadow consideration. * chore: Revert import. * chore: Some doc updates & added a warn statement.
182 lines
5.0 KiB
TypeScript
182 lines
5.0 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2023 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import type {Block} from '../block.js';
|
|
import type {BlockSvg} from '../block_svg.js';
|
|
import * as browserEvents from '../browser_events.js';
|
|
import type {IFocusableTree} from '../interfaces/i_focusable_tree.js';
|
|
import {hasBubble} from '../interfaces/i_has_bubble.js';
|
|
import type {IIcon} from '../interfaces/i_icon.js';
|
|
import * as tooltip from '../tooltip.js';
|
|
import {Coordinate} from '../utils/coordinate.js';
|
|
import * as dom from '../utils/dom.js';
|
|
import * as idGenerator from '../utils/idgenerator.js';
|
|
import {Size} from '../utils/size.js';
|
|
import {Svg} from '../utils/svg.js';
|
|
import type {WorkspaceSvg} from '../workspace_svg.js';
|
|
import type {IconType} from './icon_types.js';
|
|
|
|
/**
|
|
* The abstract icon class. Icons are visual elements that live in the top-start
|
|
* corner of the block. Usually they provide more "meta" information about a
|
|
* block (such as warnings or comments) as opposed to fields, which provide
|
|
* "actual" information, related to how a block functions.
|
|
*/
|
|
export abstract class Icon implements IIcon {
|
|
/**
|
|
* The position of this icon relative to its blocks top-start,
|
|
* in workspace units.
|
|
*/
|
|
protected offsetInBlock: Coordinate = new Coordinate(0, 0);
|
|
|
|
/** The position of this icon in workspace coordinates. */
|
|
protected workspaceLocation: Coordinate = new Coordinate(0, 0);
|
|
|
|
/** The root svg element visually representing this icon. */
|
|
protected svgRoot: SVGGElement | null = null;
|
|
|
|
/** The tooltip for this icon. */
|
|
protected tooltip: tooltip.TipInfo;
|
|
|
|
/** The unique ID of this icon. */
|
|
private id: string;
|
|
|
|
constructor(protected sourceBlock: Block) {
|
|
this.tooltip = sourceBlock;
|
|
this.id = idGenerator.getNextUniqueId();
|
|
}
|
|
|
|
getType(): IconType<IIcon> {
|
|
throw new Error('Icons must implement getType');
|
|
}
|
|
|
|
initView(pointerdownListener: (e: PointerEvent) => void): void {
|
|
if (this.svgRoot) return; // The icon has already been initialized.
|
|
|
|
const svgBlock = this.sourceBlock as BlockSvg;
|
|
this.svgRoot = dom.createSvgElement(Svg.G, {
|
|
'class': 'blocklyIconGroup',
|
|
'tabindex': '-1',
|
|
'id': this.id,
|
|
});
|
|
svgBlock.getSvgRoot().appendChild(this.svgRoot);
|
|
this.updateSvgRootOffset();
|
|
browserEvents.conditionalBind(
|
|
this.svgRoot,
|
|
'pointerdown',
|
|
this,
|
|
pointerdownListener,
|
|
);
|
|
(this.svgRoot as any).tooltip = this;
|
|
tooltip.bindMouseEvents(this.svgRoot);
|
|
}
|
|
|
|
dispose(): void {
|
|
tooltip.unbindMouseEvents(this.svgRoot);
|
|
dom.removeNode(this.svgRoot);
|
|
}
|
|
|
|
getWeight(): number {
|
|
return -1;
|
|
}
|
|
|
|
getSize(): Size {
|
|
return new Size(0, 0);
|
|
}
|
|
|
|
/**
|
|
* Sets the tooltip for this icon to the given value. Null to show the
|
|
* tooltip of the block.
|
|
*/
|
|
setTooltip(tip: tooltip.TipInfo | null) {
|
|
this.tooltip = tip ?? this.sourceBlock;
|
|
}
|
|
|
|
/** Returns the tooltip for this icon. */
|
|
getTooltip(): tooltip.TipInfo {
|
|
return this.tooltip;
|
|
}
|
|
|
|
applyColour(): void {}
|
|
|
|
updateEditable(): void {}
|
|
|
|
updateCollapsed(): void {
|
|
if (!this.svgRoot) return;
|
|
if (this.sourceBlock.isCollapsed()) {
|
|
this.svgRoot.style.display = 'none';
|
|
} else {
|
|
this.svgRoot.style.display = 'block';
|
|
}
|
|
if (hasBubble(this)) {
|
|
this.setBubbleVisible(false);
|
|
}
|
|
}
|
|
|
|
hideForInsertionMarker(): void {
|
|
if (!this.svgRoot) return;
|
|
this.svgRoot.style.display = 'none';
|
|
}
|
|
|
|
isShownWhenCollapsed(): boolean {
|
|
return false;
|
|
}
|
|
|
|
setOffsetInBlock(offset: Coordinate): void {
|
|
this.offsetInBlock = offset;
|
|
this.updateSvgRootOffset();
|
|
}
|
|
|
|
private updateSvgRootOffset(): void {
|
|
this.svgRoot?.setAttribute(
|
|
'transform',
|
|
`translate(${this.offsetInBlock.x}, ${this.offsetInBlock.y})`,
|
|
);
|
|
}
|
|
|
|
onLocationChange(blockOrigin: Coordinate): void {
|
|
this.workspaceLocation = Coordinate.sum(blockOrigin, this.offsetInBlock);
|
|
}
|
|
|
|
onClick(): void {}
|
|
|
|
/**
|
|
* Check whether the icon should be clickable while the block is in a flyout.
|
|
* The default is that icons are clickable in all flyouts (auto-closing or not).
|
|
* Subclasses may override this function to change this behavior.
|
|
*
|
|
* @param autoClosingFlyout true if the containing flyout is an auto-closing one.
|
|
* @returns Whether the icon should be clickable while the block is in a flyout.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
isClickableInFlyout(autoClosingFlyout: boolean): boolean {
|
|
return true;
|
|
}
|
|
|
|
/** See IFocusableNode.getFocusableElement. */
|
|
getFocusableElement(): HTMLElement | SVGElement {
|
|
const svgRoot = this.svgRoot;
|
|
if (!svgRoot) throw new Error('Attempting to focus uninitialized icon.');
|
|
return svgRoot;
|
|
}
|
|
|
|
/** See IFocusableNode.getFocusableTree. */
|
|
getFocusableTree(): IFocusableTree {
|
|
return this.sourceBlock.workspace as WorkspaceSvg;
|
|
}
|
|
|
|
/** See IFocusableNode.onNodeFocus. */
|
|
onNodeFocus(): void {}
|
|
|
|
/** See IFocusableNode.onNodeBlur. */
|
|
onNodeBlur(): void {}
|
|
|
|
/** See IFocusableNode.canBeFocused. */
|
|
canBeFocused(): boolean {
|
|
return true;
|
|
}
|
|
}
|