Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
54 changes: 54 additions & 0 deletions packages/blockly/core/block_svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1905,4 +1905,58 @@ export class BlockSvg
canBeFocused(): boolean {
return true;
}

/**
* Returns a set of all of the parent blocks of the given block.
*
* @internal
* @returns A set of the parents of the given block.
*/
getParents(): Set<BlockSvg> {
const parents = new Set<BlockSvg>();
let parent = this.getParent();
while (parent) {
parents.add(parent);
parent = parent.getParent();
}

return parents;
}

/**
* Returns a set of all of the parent blocks connected to an output of the
* given block or one of its parents. Also includes the given block.
*
* @internal
* @returns A set of the output-connected parents of the given block.
*/
getOutputParents(): Set<BlockSvg> {
const parents = new Set<BlockSvg>();
parents.add(this);
let parent = this.outputConnection?.targetBlock();
while (parent) {
parents.add(parent);
parent = parent.outputConnection?.targetBlock();
}

return parents;
}

/**
* Returns an ID for the visual "row" this block is part of.
*
* @internal
*/
getRowId(): string {
const connectedInput =
this.outputConnection?.targetConnection?.getParentInput();
// Blocks with an output value have the same ID as the input they're
// connected to.
if (connectedInput) {
return connectedInput.getRowId();
}

// All other blocks are their own row.
return this.id;
}
}
31 changes: 16 additions & 15 deletions packages/blockly/core/blockly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,13 @@ import {
import {IVariableMap} from './interfaces/i_variable_map.js';
import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js';
import * as internalConstants from './internal_constants.js';
import {LineCursor} from './keyboard_nav/line_cursor.js';
import {Marker} from './keyboard_nav/marker.js';
import {ToolboxNavigator} from './keyboard_nav/navigators/toolbox_navigator.js';
import {
KeyboardNavigationController,
keyboardNavigationController,
} from './keyboard_navigation_controller.js';
import type {LayerManager} from './layer_manager.js';
import * as layers from './layers.js';
import {MarkerManager} from './marker_manager.js';
import {Menu} from './menu.js';
import {MenuItem} from './menuitem.js';
import {MetricsManager} from './metrics_manager.js';
Expand Down Expand Up @@ -439,16 +437,21 @@ Names.prototype.populateProcedures = function (
};
// clang-format on

export * from './flyout_navigator.js';
export * from './interfaces/i_navigation_policy.js';
export * from './keyboard_nav/block_navigation_policy.js';
export * from './keyboard_nav/connection_navigation_policy.js';
export * from './keyboard_nav/field_navigation_policy.js';
export * from './keyboard_nav/flyout_button_navigation_policy.js';
export * from './keyboard_nav/flyout_navigation_policy.js';
export * from './keyboard_nav/flyout_separator_navigation_policy.js';
export * from './keyboard_nav/workspace_navigation_policy.js';
export * from './navigator.js';
export * from './keyboard_nav/navigation_policies/block_comment_navigation_policy.js';
export * from './keyboard_nav/navigation_policies/block_navigation_policy.js';
export * from './keyboard_nav/navigation_policies/comment_bar_button_navigation_policy.js';
export * from './keyboard_nav/navigation_policies/comment_editor_navigation_policy.js';
export * from './keyboard_nav/navigation_policies/connection_navigation_policy.js';
export * from './keyboard_nav/navigation_policies/field_navigation_policy.js';
export * from './keyboard_nav/navigation_policies/flyout_button_navigation_policy.js';
export * from './keyboard_nav/navigation_policies/flyout_separator_navigation_policy.js';
export * from './keyboard_nav/navigation_policies/icon_navigation_policy.js';
export * from './keyboard_nav/navigation_policies/toolbox_item_navigation_policy.js';
export * from './keyboard_nav/navigation_policies/workspace_comment_navigation_policy.js';
export * from './keyboard_nav/navigation_policies/workspace_navigation_policy.js';
export * from './keyboard_nav/navigators/flyout_navigator.js';
export * from './keyboard_nav/navigators/navigator.js';
export * from './toast.js';

// Re-export submodules that no longer declareLegacyNamespace.
Expand All @@ -471,7 +474,6 @@ export {
DragTarget,
Events,
Extensions,
LineCursor,
Procedures,
ShortcutItems,
Themes,
Expand Down Expand Up @@ -596,8 +598,6 @@ export {
KeyboardNavigationController,
LabelFlyoutInflater,
LayerManager,
Marker,
MarkerManager,
Menu,
MenuGenerator,
MenuGeneratorFunction,
Expand All @@ -619,6 +619,7 @@ export {
Toolbox,
ToolboxCategory,
ToolboxItem,
ToolboxNavigator,
ToolboxSeparator,
Trashcan,
UnattachedFieldError,
Expand Down
10 changes: 4 additions & 6 deletions packages/blockly/core/comments/comment_editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const COMMENT_EDITOR_FOCUS_IDENTIFIER = '_comment_textarea_';

/** The part of a comment that can be typed into. */
export class CommentEditor implements IFocusableNode {
id?: string;
id: string;
/** The foreignObject containing the HTML text area. */
private foreignObject: SVGForeignObjectElement;

Expand All @@ -42,7 +42,7 @@ export class CommentEditor implements IFocusableNode {

constructor(
public workspace: WorkspaceSvg,
commentId?: string,
commentId: string,
private onFinishEditing?: () => void,
) {
this.foreignObject = dom.createSvgElement(Svg.FOREIGNOBJECT, {
Expand All @@ -67,10 +67,8 @@ export class CommentEditor implements IFocusableNode {
body.appendChild(this.textArea);
this.foreignObject.appendChild(body);

if (commentId) {
this.id = commentId + COMMENT_EDITOR_FOCUS_IDENTIFIER;
this.textArea.setAttribute('id', this.id);
}
this.id = commentId + COMMENT_EDITOR_FOCUS_IDENTIFIER;
this.textArea.setAttribute('id', this.id);

// Register browser event listeners for the user typing in the textarea.
browserEvents.conditionalBind(
Expand Down
18 changes: 12 additions & 6 deletions packages/blockly/core/field_input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import './events/events_block_change.js';

import {BlockSvg} from './block_svg.js';
import {IFocusableNode} from './blockly.js';
import * as browserEvents from './browser_events.js';
import * as bumpObjects from './bump_objects.js';
import * as dialog from './dialog.js';
Expand All @@ -28,7 +29,6 @@ import {
UnattachedFieldError,
} from './field.js';
import {getFocusManager} from './focus_manager.js';
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
import {Msg} from './msg.js';
import * as renderManagement from './render_management.js';
import * as aria from './utils/aria.js';
Expand Down Expand Up @@ -600,16 +600,20 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
dropDownDiv.hideWithoutAnimation();
} else if (e.key === 'Tab') {
e.preventDefault();
const cursor = this.workspace_?.getCursor();
const navigator = this.workspace_?.getNavigator();

const isValidDestination = (node: IFocusableNode | null) =>
(node instanceof FieldInput ||
(node instanceof BlockSvg && node.isSimpleReporter())) &&
node !== this.getSourceBlock();

let target = e.shiftKey
? cursor?.getPreviousNode(this, isValidDestination, false)
: cursor?.getNextNode(this, isValidDestination, false);
// eslint-disable-next-line @typescript-eslint/no-this-alias
let target: IFocusableNode | null | undefined = this;
do {
target = e.shiftKey
? navigator?.getOutNode(target)
: navigator?.getInNode(target);
} while (target && !isValidDestination(target));
target =
target instanceof BlockSvg && target.isSimpleReporter()
? target.getFields().next().value
Expand All @@ -625,7 +629,9 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
targetSourceBlock instanceof BlockSvg
) {
getFocusManager().focusNode(targetSourceBlock);
} else getFocusManager().focusNode(target);
} else {
getFocusManager().focusNode(target);
}
target.showEditor();
}
}
Expand Down
88 changes: 2 additions & 86 deletions packages/blockly/core/flyout_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@ import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js';
import {FlyoutItem} from './flyout_item.js';
import {FlyoutMetricsManager} from './flyout_metrics_manager.js';
import {FlyoutNavigator} from './flyout_navigator.js';
import {FlyoutSeparator, SeparatorAxis} from './flyout_separator.js';
import {IAutoHideable} from './interfaces/i_autohideable.js';
import type {IFlyout} from './interfaces/i_flyout.js';
import type {IFlyoutInflater} from './interfaces/i_flyout_inflater.js';
import {IFocusableNode} from './interfaces/i_focusable_node.js';
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
import {FlyoutNavigator} from './keyboard_nav/navigators/flyout_navigator.js';
import type {Options} from './options.js';
import * as registry from './registry.js';
import * as renderManagement from './render_management.js';
Expand All @@ -42,7 +40,7 @@ import {WorkspaceSvg} from './workspace_svg.js';
*/
export abstract class Flyout
extends DeleteArea
implements IAutoHideable, IFlyout, IFocusableNode
implements IAutoHideable, IFlyout
{
/**
* Position the flyout.
Expand Down Expand Up @@ -797,86 +795,4 @@ export abstract class Flyout

return null;
}

/**
* See IFocusableNode.getFocusableElement.
*
* @deprecated v12: Use the Flyout's workspace for focus operations, instead.
*/
getFocusableElement(): HTMLElement | SVGElement {
throw new Error('Flyouts are not directly focusable.');
}

/**
* See IFocusableNode.getFocusableTree.
*
* @deprecated v12: Use the Flyout's workspace for focus operations, instead.
*/
getFocusableTree(): IFocusableTree {
throw new Error('Flyouts are not directly focusable.');
}

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

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

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

/**
* See IFocusableNode.getRootFocusableNode.
*
* @deprecated v12: Use the Flyout's workspace for focus operations, instead.
*/
getRootFocusableNode(): IFocusableNode {
throw new Error('Flyouts are not directly focusable.');
}

/**
* See IFocusableNode.getRestoredFocusableNode.
*
* @deprecated v12: Use the Flyout's workspace for focus operations, instead.
*/
getRestoredFocusableNode(
_previousNode: IFocusableNode | null,
): IFocusableNode | null {
throw new Error('Flyouts are not directly focusable.');
}

/**
* See IFocusableNode.getNestedTrees.
*
* @deprecated v12: Use the Flyout's workspace for focus operations, instead.
*/
getNestedTrees(): Array<IFocusableTree> {
throw new Error('Flyouts are not directly focusable.');
}

/**
* See IFocusableNode.lookUpFocusableNode.
*
* @deprecated v12: Use the Flyout's workspace for focus operations, instead.
*/
lookUpFocusableNode(_id: string): IFocusableNode | null {
throw new Error('Flyouts are not directly focusable.');
}

/** See IFocusableTree.onTreeFocus. */
onTreeFocus(
_node: IFocusableNode,
_previousTree: IFocusableTree | null,
): void {}

/**
* See IFocusableNode.onTreeBlur.
*
* @deprecated v12: Use the Flyout's workspace for focus operations, instead.
*/
onTreeBlur(_nextTree: IFocusableTree | null): void {
throw new Error('Flyouts are not directly focusable.');
}
}
24 changes: 0 additions & 24 deletions packages/blockly/core/flyout_navigator.ts

This file was deleted.

35 changes: 34 additions & 1 deletion packages/blockly/core/inputs/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import '../field_label.js';
import type {Block} from '../block.js';
import type {BlockSvg} from '../block_svg.js';
import type {Connection} from '../connection.js';
import type {ConnectionType} from '../connection_type.js';
import {ConnectionType} from '../connection_type.js';
import type {Field} from '../field.js';
import * as fieldRegistry from '../field_registry.js';
import {RenderedConnection} from '../rendered_connection.js';
Expand Down Expand Up @@ -314,4 +314,37 @@ export class Input {
protected makeConnection(type: ConnectionType): Connection {
return this.sourceBlock.makeConnection_(type);
}

/**
* Returns an ID for the visual "row" this input is part of.
*
* @internal
*/
getRowId(): string {
const inputs = this.getSourceBlock().inputList;

// The first input in a block has the same ID as its parent block.
if (this === inputs[0]) {
return (this.getSourceBlock() as BlockSvg).getRowId();
}

const inputIndex = inputs.indexOf(this);
const precedingStatementInput =
inputs[inputIndex - 1].connection?.type === ConnectionType.NEXT_STATEMENT;

// Each subsequent (a) external input (b) statement input or (c) inline
// input following a statement input is on its own row and has its own row
// ID.
if (
!this.getSourceBlock().getInputsInline() ||
this.connection?.type === ConnectionType.NEXT_STATEMENT ||
precedingStatementInput
) {
return `${this.getSourceBlock().id}-input${inputIndex}`;
}

// Value inputs on a inline input block have the same row ID as their
// preceding input, since they're all on one row.
return inputs[inputIndex - 1].getRowId();
}
}
Loading
Loading