Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions blocks/procedures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import type {Block} from '../core/block.js';
import type {BlockSvg} from '../core/block_svg.js';
import type {BlockDefinition} from '../core/blocks.js';
import * as common from '../core/common.js';
import {defineBlocks} from '../core/common.js';
import {config} from '../core/config.js';
import type {Connection} from '../core/connection.js';
Expand All @@ -27,6 +26,7 @@ import {FieldCheckbox} from '../core/field_checkbox.js';
import {FieldLabel} from '../core/field_label.js';
import * as fieldRegistry from '../core/field_registry.js';
import {FieldTextInput} from '../core/field_textinput.js';
import {getFocusManager} from '../core/focus_manager.js';
import '../core/icons/comment_icon.js';
import {MutatorIcon as Mutator} from '../core/icons/mutator_icon.js';
import '../core/icons/warning_icon.js';
Expand Down Expand Up @@ -1178,7 +1178,7 @@ const PROCEDURE_CALL_COMMON = {
const def = Procedures.getDefinition(name, workspace);
if (def) {
(workspace as WorkspaceSvg).centerOnBlock(def.id);
common.setSelected(def as BlockSvg);
getFocusManager().focusNode(def as BlockSvg);
}
},
});
Expand Down
43 changes: 10 additions & 33 deletions core/block_svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,20 +266,14 @@ export class BlockSvg

/** Selects this block. Highlights the block visually. */
select() {
if (this.isShadow()) {
Comment thread
BenHenning marked this conversation as resolved.
this.getParent()?.select();
return;
}
this.addSelect();
common.fireSelectedEvent(this);
}

/** Unselects this block. Unhighlights the block visually. */
unselect() {
if (this.isShadow()) {
this.getParent()?.unselect();
return;
}
this.removeSelect();
common.fireSelectedEvent(null);
}

/**
Expand Down Expand Up @@ -862,25 +856,6 @@ export class BlockSvg
blockAnimations.disposeUiEffect(this);
}

// Selecting a shadow block highlights an ancestor block, but that highlight
// should be removed if the shadow block will be deleted. So, before
// deleting blocks and severing the connections between them, check whether
// doing so would delete a selected block and make sure that any associated
// parent is updated.
const selection = common.getSelected();
if (selection instanceof Block) {
let selectionAncestor: Block | null = selection;
while (selectionAncestor !== null) {
if (selectionAncestor === this) {
// The block to be deleted contains the selected block, so remove any
// selection highlight associated with the selected block before
// deleting them.
selection.unselect();
}
selectionAncestor = selectionAncestor.getParent();
}
}

super.dispose(!!healStack);
dom.removeNode(this.svgGroup);
}
Expand All @@ -893,8 +868,7 @@ export class BlockSvg
this.disposing = true;
super.disposeInternal();

if (common.getSelected() === this) {
this.unselect();
if (getFocusManager().getFocusedNode() === this) {
Comment thread
maribethb marked this conversation as resolved.
this.workspace.cancelCurrentGesture();
}

Expand Down Expand Up @@ -1839,14 +1813,17 @@ export class BlockSvg

/** See IFocusableNode.onNodeFocus. */
onNodeFocus(): void {
common.setSelected(this);
this.select();
}

/** See IFocusableNode.onNodeBlur. */
onNodeBlur(): void {
if (common.getSelected() === this) {
common.setSelected(null);
}
this.unselect();
}

/** See IFocusableNode.canBeFocused. */
canBeFocused(): boolean {
return true;
}

/**
Expand Down
42 changes: 39 additions & 3 deletions core/bubbles/bubble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
* SPDX-License-Identifier: Apache-2.0
*/

import {ISelectable} from '../blockly.js';
import * as browserEvents from '../browser_events.js';
import * as common from '../common.js';
import {BubbleDragStrategy} from '../dragging/bubble_drag_strategy.js';
import {getFocusManager} from '../focus_manager.js';
import {IBubble} from '../interfaces/i_bubble.js';
import type {IFocusableTree} from '../interfaces/i_focusable_tree.js';
import {ISelectable} from '../interfaces/i_selectable.js';
import {ContainerRegion} from '../metrics_manager.js';
import {Scrollbar} from '../scrollbar.js';
import {Coordinate} from '../utils/coordinate.js';
Expand Down Expand Up @@ -86,6 +88,8 @@ export abstract class Bubble implements IBubble, ISelectable {

private dragStrategy = new BubbleDragStrategy(this, this.workspace);

private focusableElement: SVGElement | HTMLElement;

/**
* @param workspace The workspace this bubble belongs to.
* @param anchor The anchor location of the thing this bubble is attached to.
Expand All @@ -97,6 +101,7 @@ export abstract class Bubble implements IBubble, ISelectable {
public readonly workspace: WorkspaceSvg,
protected anchor: Coordinate,
protected ownerRect?: Rect,
private overriddenFocusableElement?: SVGElement | HTMLElement,
Comment thread
BenHenning marked this conversation as resolved.
Outdated
) {
this.id = idGenerator.getNextUniqueId();
this.svgRoot = dom.createSvgElement(
Expand Down Expand Up @@ -127,6 +132,10 @@ export abstract class Bubble implements IBubble, ISelectable {
);
this.contentContainer = dom.createSvgElement(Svg.G, {}, this.svgRoot);

this.focusableElement = overriddenFocusableElement ?? this.svgRoot;
this.focusableElement.setAttribute('id', this.id);
this.focusableElement.setAttribute('tabindex', '-1');

browserEvents.conditionalBind(
this.background,
'pointerdown',
Expand Down Expand Up @@ -211,8 +220,7 @@ export abstract class Bubble implements IBubble, ISelectable {
/** Brings the bubble to the front and passes the pointer event off to the gesture system. */
Comment thread
maribethb marked this conversation as resolved.
Outdated
private onMouseDown(e: PointerEvent) {
this.workspace.getGesture(e)?.handleBubbleStart(e, this);
this.bringToFront();
common.setSelected(this);
getFocusManager().focusNode(this);
}

/** Positions the bubble relative to its anchor. Does not render its tail. */
Expand Down Expand Up @@ -647,9 +655,37 @@ export abstract class Bubble implements IBubble, ISelectable {

select(): void {
// Bubbles don't have any visual for being selected.
common.fireSelectedEvent(this);
}

unselect(): void {
// Bubbles don't have any visual for being selected.
common.fireSelectedEvent(null);
}

/** See IFocusableNode.getFocusableElement. */
getFocusableElement(): HTMLElement | SVGElement {
return this.focusableElement;
}

/** See IFocusableNode.getFocusableTree. */
getFocusableTree(): IFocusableTree {
return this.workspace;
}

/** See IFocusableNode.onNodeFocus. */
onNodeFocus(): void {
this.select();
this.bringToFront();
}

/** See IFocusableNode.onNodeBlur. */
onNodeBlur(): void {
this.unselect();
}

/** See IFocusableNode.canBeFocused. */
canBeFocused(): boolean {
return true;
}
}
57 changes: 21 additions & 36 deletions core/bubbles/textinput_bubble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,10 @@ export class TextInputBubble extends Bubble {
protected anchor: Coordinate,
protected ownerRect?: Rect,
) {
super(workspace, anchor, ownerRect);
super(workspace, anchor, ownerRect, TextInputBubble.createTextArea());
dom.addClass(this.svgRoot, 'blocklyTextInputBubble');
({inputRoot: this.inputRoot, textArea: this.textArea} = this.createEditor(
this.contentContainer,
));
this.textArea = this.getFocusableElement() as HTMLTextAreaElement;
this.inputRoot = this.createEditor(this.contentContainer, this.textArea);
this.resizeGroup = this.createResizeHandle(this.svgRoot, workspace);
this.setSize(this.DEFAULT_SIZE, true);
}
Expand Down Expand Up @@ -131,11 +130,21 @@ export class TextInputBubble extends Bubble {
this.locationChangeListeners.push(listener);
}

private static createTextArea(): HTMLTextAreaElement {
Comment thread
BenHenning marked this conversation as resolved.
const textArea = document.createElementNS(
dom.HTML_NS,
'textarea',
) as HTMLTextAreaElement;
textArea.className = 'blocklyTextarea blocklyText';
// textArea.setAttribute('tabindex', '-1');
Comment thread
BenHenning marked this conversation as resolved.
Outdated
return textArea;
}

/** Creates the editor UI for this bubble. */
private createEditor(container: SVGGElement): {
inputRoot: SVGForeignObjectElement;
textArea: HTMLTextAreaElement;
} {
private createEditor(
container: SVGGElement,
textArea: HTMLTextAreaElement,
): SVGForeignObjectElement {
const inputRoot = dom.createSvgElement(
Svg.FOREIGNOBJECT,
{
Expand All @@ -149,22 +158,16 @@ export class TextInputBubble extends Bubble {
body.setAttribute('xmlns', dom.HTML_NS);
body.className = 'blocklyMinimalBody';

const textArea = document.createElementNS(
dom.HTML_NS,
'textarea',
) as HTMLTextAreaElement;
textArea.className = 'blocklyTextarea blocklyText';
textArea.setAttribute('dir', this.workspace.RTL ? 'RTL' : 'LTR');

body.appendChild(textArea);
inputRoot.appendChild(body);

this.bindTextAreaEvents(textArea);
setTimeout(() => {
textArea.focus();
}, 0);
// setTimeout(() => {
// textArea.focus();
// }, 0);
Comment thread
BenHenning marked this conversation as resolved.
Outdated

return {inputRoot, textArea};
return inputRoot;
}

/** Binds events to the text area element. */
Expand All @@ -174,13 +177,6 @@ export class TextInputBubble extends Bubble {
e.stopPropagation();
});

browserEvents.conditionalBind(
textArea,
'focus',
this,
this.onStartEdit,
true,
);
browserEvents.conditionalBind(textArea, 'change', this, this.onTextChange);
}

Expand Down Expand Up @@ -314,17 +310,6 @@ export class TextInputBubble extends Bubble {
this.onSizeChange();
}

/**
* Handles starting an edit of the text area. Brings the bubble to the front.
*/
private onStartEdit() {
if (this.bringToFront()) {
// Since the act of moving this node within the DOM causes a loss of
// focus, we need to reapply the focus.
this.textArea.focus();
}
}

/** Handles a text change event for the text area. Calls event listeners. */
private onTextChange() {
this.text = this.textArea.value;
Expand Down
4 changes: 2 additions & 2 deletions core/clipboard/workspace_comment_paster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
*/

import {RenderedWorkspaceComment} from '../comments/rendered_workspace_comment.js';
import * as common from '../common.js';
import {EventType} from '../events/type.js';
import * as eventUtils from '../events/utils.js';
import {getFocusManager} from '../focus_manager.js';
import {ICopyData} from '../interfaces/i_copyable.js';
import {IPaster} from '../interfaces/i_paster.js';
import * as commentSerialiation from '../serialization/workspace_comments.js';
Expand Down Expand Up @@ -49,7 +49,7 @@ export class WorkspaceCommentPaster
if (eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(EventType.COMMENT_CREATE))(comment));
}
common.setSelected(comment);
getFocusManager().focusNode(comment);
Comment thread
BenHenning marked this conversation as resolved.
return comment;
}
}
Expand Down
33 changes: 32 additions & 1 deletion core/comments/rendered_workspace_comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import * as common from '../common.js';
import * as contextMenu from '../contextmenu.js';
import {ContextMenuRegistry} from '../contextmenu_registry.js';
import {CommentDragStrategy} from '../dragging/comment_drag_strategy.js';
import {getFocusManager} from '../focus_manager.js';
import {IBoundedElement} from '../interfaces/i_bounded_element.js';
import {IContextMenu} from '../interfaces/i_contextmenu.js';
import {ICopyable} from '../interfaces/i_copyable.js';
import {IDeletable} from '../interfaces/i_deletable.js';
import {IDraggable} from '../interfaces/i_draggable.js';
import type {IFocusableTree} from '../interfaces/i_focusable_tree.js';
import {IRenderedElement} from '../interfaces/i_rendered_element.js';
import {ISelectable} from '../interfaces/i_selectable.js';
import * as layers from '../layers.js';
Expand Down Expand Up @@ -60,6 +62,8 @@ export class RenderedWorkspaceComment
this.view.setSize(this.getSize());
this.view.setEditable(this.isEditable());
this.view.getSvgRoot().setAttribute('data-id', this.id);
this.view.getSvgRoot().setAttribute('id', this.id);
this.view.getSvgRoot().setAttribute('tabindex', '-1');

this.addModelUpdateBindings();

Expand Down Expand Up @@ -222,7 +226,7 @@ export class RenderedWorkspaceComment
gesture.handleCommentStart(e, this);
this.workspace.getLayerManager()?.append(this, layers.BLOCK);
}
common.setSelected(this);
getFocusManager().focusNode(this);
}
}

Expand Down Expand Up @@ -263,11 +267,13 @@ export class RenderedWorkspaceComment
/** Visually highlights the comment. */
select(): void {
dom.addClass(this.getSvgRoot(), 'blocklySelected');
common.fireSelectedEvent(this);
}

/** Visually unhighlights the comment. */
unselect(): void {
dom.removeClass(this.getSvgRoot(), 'blocklySelected');
common.fireSelectedEvent(null);
}

/**
Expand Down Expand Up @@ -322,4 +328,29 @@ export class RenderedWorkspaceComment
this.moveTo(alignedXY, ['snap']);
}
}

/** See IFocusableNode.getFocusableElement. */
getFocusableElement(): HTMLElement | SVGElement {
return this.getSvgRoot();
}

/** See IFocusableNode.getFocusableTree. */
getFocusableTree(): IFocusableTree {
return this.workspace;
}

/** See IFocusableNode.onNodeFocus. */
onNodeFocus(): void {
this.select();
}

/** See IFocusableNode.onNodeBlur. */
onNodeBlur(): void {
this.unselect();
}

/** See IFocusableNode.canBeFocused. */
canBeFocused(): boolean {
return true;
}
}
Loading