Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
41c8215
feat: Make WorkspaceSvg focusable.
BenHenning Apr 21, 2025
14b486e
chore: remove accidental 'test.only'.
BenHenning Apr 21, 2025
26cf8db
feat: Make Toolbox & Flyout focusable.
BenHenning Apr 22, 2025
5ef2d7e
Merge branch 'add-focus-manager-callbacks-and-improvements' into make…
BenHenning Apr 22, 2025
996208d
Merge branch 'make-workspace-focusable' into make-toolbox-and-flyout-…
BenHenning Apr 22, 2025
d3acbff
feat!: Force lifecycle methods for fields.
BenHenning Apr 22, 2025
ed0f140
feat: Make fields ephemerally focusable.
BenHenning Apr 22, 2025
94672d9
chore: Lint fixes.
BenHenning Apr 22, 2025
2430646
chore: Remove incorrect aria-label.
BenHenning Apr 22, 2025
41bc01a
feat: Make RenderedConnection focusable.
BenHenning Apr 23, 2025
4479b82
Merge branch 'add-focus-manager-callbacks-and-improvements' into make…
BenHenning Apr 23, 2025
2637736
fix: Ensure Block paths are focusable.
BenHenning Apr 23, 2025
49192ba
chore: Fix line comment.
BenHenning Apr 23, 2025
917c4b6
Merge branch 'make-workspace-focusable' into make-toolbox-and-flyout-…
BenHenning Apr 23, 2025
d276dbc
chore: reduce branching.
BenHenning Apr 24, 2025
c819130
Merge branch 'make-toolbox-and-flyout-focusable' into make-fields-foc…
BenHenning Apr 24, 2025
90fdde2
feat: make drop down & widget divs focusable.
BenHenning Apr 24, 2025
7c2f705
chore: undo breaking field changes.
BenHenning Apr 24, 2025
9726389
chore: some more clean-ups after removals.
BenHenning Apr 24, 2025
1094787
feat: fix field node retrieval.
BenHenning Apr 24, 2025
082a6ef
chore: lint fixes.
BenHenning Apr 24, 2025
0006a45
Merge branch 'make-fields-focusable' into make-connections-focusable
BenHenning Apr 24, 2025
7f14372
feat: add remaining connection support
BenHenning Apr 24, 2025
59198db
chore: lint fixes.
BenHenning Apr 24, 2025
a346a92
fix: remove unnecessary shadow check.
BenHenning Apr 24, 2025
4ed61bf
Merge branch 'make-workspace-focusable' into make-toolbox-and-flyout-…
BenHenning Apr 24, 2025
898c5a4
Merge branch 'make-toolbox-and-flyout-focusable' into make-fields-foc…
BenHenning Apr 24, 2025
fdf4b4f
Merge branch 'make-fields-focusable' into make-connections-focusable
BenHenning Apr 24, 2025
80c8859
chore: add braces.
BenHenning Apr 24, 2025
b3bd5e7
Merge branch 'rc/v12.0.0' into make-workspace-focusable
BenHenning Apr 24, 2025
1f0cefc
Merge branch 'make-workspace-focusable' into make-toolbox-and-flyout-…
BenHenning Apr 24, 2025
c2384c6
chore: empty commit to make CI pass.
BenHenning Apr 24, 2025
57391a7
Merge branch 'rc/v12.0.0' into make-toolbox-and-flyout-focusable
BenHenning Apr 24, 2025
8057051
Merge branch 'make-toolbox-and-flyout-focusable' into make-fields-foc…
BenHenning Apr 24, 2025
ad96cad
Merge branch 'make-fields-focusable' into make-connections-focusable
BenHenning Apr 24, 2025
c75aea7
feat: Make WorkspaceSvg and BlockSvg focusable (#8916)
BenHenning Apr 24, 2025
d4883f5
feat: Make toolbox and flyout focusable (#8920)
BenHenning Apr 24, 2025
2a1a8b3
Merge branch 'make-toolbox-and-flyout-focusable-roll-forward' into ma…
BenHenning Apr 29, 2025
6abdd90
Merge branch 'make-fields-focusable' into make-connections-focusable
BenHenning Apr 29, 2025
8fc5c05
chore: remove connections rendering changes.
BenHenning Apr 29, 2025
8f65a3b
fix: Undo break for Block focusability.
BenHenning Apr 30, 2025
e5abf72
fix: Remove CSS for active/passive focus.
BenHenning Apr 30, 2025
e849e0c
Merge branch 'make-workspace-focusable-roll-forward' into make-toolbo…
BenHenning Apr 30, 2025
585c950
feat: Make FlyoutButton focusable.
BenHenning Apr 30, 2025
a520554
Merge branch 'make-toolbox-and-flyout-focusable-roll-forward' into ma…
BenHenning Apr 30, 2025
3168ff8
Merge branch 'make-fields-focusable' into make-connections-focusable
BenHenning Apr 30, 2025
151d8f4
fix: Make RenderedConnection display correctly.
BenHenning Apr 30, 2025
d6dcc4b
fix: Actually make FlyoutButton focusable.
BenHenning Apr 30, 2025
a9cf3d7
chore: lint fixes.
BenHenning Apr 30, 2025
f18670a
chore: Use strict equals.
BenHenning Apr 30, 2025
34970cc
chore: Empty commit to re-trigger CI.
BenHenning Apr 30, 2025
b11aa43
Merge branch 'make-toolbox-and-flyout-focusable-roll-forward' into ma…
BenHenning Apr 30, 2025
3cf8fb8
chore: Add field doc.
BenHenning Apr 30, 2025
896dbd7
Merge branch 'make-fields-focusable' into make-connections-focusable
BenHenning Apr 30, 2025
d146f61
chore: Add field docs.
BenHenning Apr 30, 2025
e3a6f98
Merge branch 'rc/v12.0.0' into make-workspace-focusable-roll-forward
BenHenning Apr 30, 2025
b738b4a
Merge branch 'make-workspace-focusable-roll-forward' into make-toolbo…
BenHenning Apr 30, 2025
ef6f661
Merge branch 'rc/v12.0.0' into make-toolbox-and-flyout-focusable-roll…
BenHenning Apr 30, 2025
b493147
Merge branch 'make-toolbox-and-flyout-focusable-roll-forward' into ma…
BenHenning Apr 30, 2025
dba2346
Merge branch 'rc/v12.0.0' into make-fields-focusable
BenHenning Apr 30, 2025
8146d5d
Merge branch 'make-fields-focusable' into make-connections-focusable
BenHenning Apr 30, 2025
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
5 changes: 5 additions & 0 deletions core/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {Input} from './inputs/input.js';
import type {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js';
import type {IConnectionChecker} from './interfaces/i_connection_checker.js';
import * as blocks from './serialization/blocks.js';
import {idGenerator} from './utils.js';
import * as Xml from './xml.js';

/**
Expand Down Expand Up @@ -55,6 +56,9 @@ export class Connection implements IASTNodeLocationWithBlock {
/** DOM representation of a shadow block, or null if none. */
private shadowDom: Element | null = null;

/** The unique ID of this connection. */
id: string;

/**
* Horizontal location of this connection.
*
Expand All @@ -80,6 +84,7 @@ export class Connection implements IASTNodeLocationWithBlock {
public type: number,
) {
this.sourceBlock_ = source;
this.id = `${source.id}_connection_${idGenerator.getNextUniqueId()}`;
}

/**
Expand Down
57 changes: 54 additions & 3 deletions core/rendered_connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,24 @@ import * as ContextMenu from './contextmenu.js';
import {ContextMenuRegistry} from './contextmenu_registry.js';
import * as eventUtils from './events/utils.js';
import {IContextMenu} from './interfaces/i_contextmenu.js';
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
import {hasBubble} from './interfaces/i_has_bubble.js';
import * as internalConstants from './internal_constants.js';
import {Coordinate} from './utils/coordinate.js';
import * as svgMath from './utils/svg_math.js';
import {WorkspaceSvg} from './workspace_svg.js';

/** Maximum randomness in workspace units for bumping a block. */
const BUMP_RANDOMNESS = 10;

/**
* Class for a connection between blocks that may be rendered on screen.
*/
export class RenderedConnection extends Connection implements IContextMenu {
export class RenderedConnection
extends Connection
implements IContextMenu, IFocusableNode
{
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
sourceBlock_!: BlockSvg;
private readonly db: ConnectionDB;
Expand Down Expand Up @@ -320,13 +326,28 @@ export class RenderedConnection extends Connection implements IContextMenu {
/** Add highlighting around this connection. */
highlight() {
this.highlighted = true;
this.getSourceBlock().queueRender();

// Note that this needs to be done synchronously (vs. queuing a render pass)
// since only a displayed element can be focused, and this focusable node is
// implemented to make itself visible immediately prior to receiving DOM
// focus. It's expected that the connection's position should already be
// correct by this point (otherwise it will be corrected in a subsequent
// draw pass).
const highlightSvg = this.findHighlightSvg();
if (highlightSvg) {
highlightSvg.style.display = '';
}
}

/** Remove the highlighting around this connection. */
unhighlight() {
this.highlighted = false;
this.getSourceBlock().queueRender();

// Note that this is done synchronously for parity with highlight().
const highlightSvg = this.findHighlightSvg();
if (highlightSvg) {
highlightSvg.style.display = 'none';
}
}

/** Returns true if this connection is highlighted, false otherwise. */
Expand Down Expand Up @@ -626,6 +647,36 @@ export class RenderedConnection extends Connection implements IContextMenu {

ContextMenu.show(e, menuOptions, block.RTL, workspace, location);
}

/** See IFocusableNode.getFocusableElement. */
getFocusableElement(): HTMLElement | SVGElement {
const highlightSvg = this.findHighlightSvg();
if (highlightSvg) return highlightSvg;
throw new Error('No highlight SVG found corresponding to this connection.');
}

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

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

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

private findHighlightSvg(): SVGElement | null {
// This cast is valid as TypeScript's definition is wrong. See:
// https://github.com/microsoft/TypeScript/issues/60996.
return document.getElementById(this.id) as
| unknown
| null as SVGElement | null;
}
}

export namespace RenderedConnection {
Expand Down
13 changes: 5 additions & 8 deletions core/renderers/common/drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,19 +435,16 @@ export class Drawer {
for (const elem of row.elements) {
if (!(elem instanceof Connection)) continue;

if (elem.highlighted) {
this.drawConnectionHighlightPath(elem);
} else {
this.block_.pathObject.removeConnectionHighlight?.(
elem.connectionModel,
);
const highlightSvg = this.drawConnectionHighlightPath(elem);
if (highlightSvg) {
highlightSvg.style.display = elem.highlighted ? '' : 'none';
}
}
}
}

/** Returns a path to highlight the given connection. */
drawConnectionHighlightPath(measurable: Connection) {
drawConnectionHighlightPath(measurable: Connection): SVGElement | undefined {
const conn = measurable.connectionModel;
let path = '';
if (
Expand All @@ -459,7 +456,7 @@ export class Drawer {
path = this.getStatementConnectionHighlightPath(measurable);
}
const block = conn.getSourceBlock();
block.pathObject.addConnectionHighlight?.(
return block.pathObject.addConnectionHighlight?.(
conn,
path,
conn.getOffsetInBlock(),
Expand Down
2 changes: 1 addition & 1 deletion core/renderers/common/i_path_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export interface IPathObject {
connectionPath: string,
offset: Coordinate,
rtl: boolean,
): void;
): SVGElement;

/**
* Apply the stored colours to the block's path, taking into account whether
Expand Down
36 changes: 16 additions & 20 deletions core/renderers/common/path_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,37 +268,33 @@ export class PathObject implements IPathObject {
connectionPath: string,
offset: Coordinate,
rtl: boolean,
) {
if (this.connectionHighlights.has(connection)) {
if (this.currentHighlightMatchesNew(connection, connectionPath, offset)) {
return;
}
this.removeConnectionHighlight(connection);
): SVGElement {
const transformation =
`translate(${offset.x}, ${offset.y})` + (rtl ? ' scale(-1 1)' : '');

const previousHighlight = this.connectionHighlights.get(connection);
if (previousHighlight) {
// Since a connection already exists, make sure that its path and
// transform are correct.
previousHighlight.setAttribute('d', connectionPath);
previousHighlight.setAttribute('transform', transformation);
return previousHighlight;
}

const highlight = dom.createSvgElement(
Svg.PATH,
{
'id': connection.id,
'class': 'blocklyHighlightedConnectionPath',
'style': 'display: none;',
'tabindex': '-1',
'd': connectionPath,
'transform':
`translate(${offset.x}, ${offset.y})` + (rtl ? ' scale(-1 1)' : ''),
'transform': transformation,
},
this.svgRoot,
);
this.connectionHighlights.set(connection, highlight);
}

private currentHighlightMatchesNew(
connection: RenderedConnection,
newPath: string,
newOffset: Coordinate,
): boolean {
const currPath = this.connectionHighlights
.get(connection)
?.getAttribute('d');
const currOffset = this.highlightOffsets.get(connection);
return currPath === newPath && Coordinate.equals(currOffset, newOffset);
return highlight;
}

/**
Expand Down
9 changes: 5 additions & 4 deletions core/renderers/zelos/drawer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,16 @@ export class Drawer extends BaseDrawer {
}

/** Returns a path to highlight the given connection. */
drawConnectionHighlightPath(measurable: Connection) {
override drawConnectionHighlightPath(
measurable: Connection,
): SVGElement | undefined {
const conn = measurable.connectionModel;
if (
conn.type === ConnectionType.NEXT_STATEMENT ||
conn.type === ConnectionType.PREVIOUS_STATEMENT ||
(conn.type === ConnectionType.OUTPUT_VALUE && !measurable.isDynamicShape)
) {
super.drawConnectionHighlightPath(measurable);
return;
return super.drawConnectionHighlightPath(measurable);
}

let path = '';
Expand All @@ -261,7 +262,7 @@ export class Drawer extends BaseDrawer {
(output.shape as DynamicShape).pathDown(output.height);
}
const block = conn.getSourceBlock();
block.pathObject.addConnectionHighlight?.(
return block.pathObject.addConnectionHighlight?.(
conn,
path,
conn.getOffsetInBlock(),
Expand Down
10 changes: 10 additions & 0 deletions core/workspace_svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2710,6 +2710,7 @@ export class WorkspaceSvg
}

const fieldIndicatorIndex = id.indexOf('_field_');
const connectionIndicatorIndex = id.indexOf('_connection_');
if (fieldIndicatorIndex !== -1) {
const blockId = id.substring(0, fieldIndicatorIndex);
const block = this.getBlockById(blockId);
Expand All @@ -2719,6 +2720,15 @@ export class WorkspaceSvg
}
}
return null;
} else if (connectionIndicatorIndex !== -1) {
const blockId = id.substring(0, connectionIndicatorIndex);
const block = this.getBlockById(blockId);
if (block) {
for (const connection of block.getConnections_(true)) {
if (connection.id === id) return connection;
}
}
return null;
}

return this.getBlockById(id) as IFocusableNode;
Expand Down
Loading