Skip to content

Commit 51ff9ce

Browse files
committed
Add selection border
Signed-off-by: Hollow Man <[email protected]>
1 parent 1c9b6d5 commit 51ff9ce

File tree

5 files changed

+290
-14
lines changed

5 files changed

+290
-14
lines changed

src/DOM_FOCUS_IMPLEMENTATION.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# DOM-Based Focus Management Implementation
2+
3+
This implementation follows Christopher's suggestion to create a visible DOM element for the MultiselectDraggable that can hold browser focus, addressing the focus management issues in Blockly v12.
4+
5+
## Key Changes
6+
7+
### 1. Visual Selection Outline
8+
- Added a rectangular outline around all selected items (similar to Google Slides)
9+
- The outline is a visible SVG element that can receive browser focus
10+
- Styled with dashed border and proper focus indicators
11+
12+
### 2. DOM Focus Management
13+
- `getFocusableElement()` now returns the actual DOM element (`selectionOutline_`)
14+
- Added proper `tabindex="0"` and accessibility attributes
15+
- Implemented keyboard event handling for accessibility
16+
17+
### 3. Real-time Updates
18+
- Outline position and size updates automatically when:
19+
- Items are added/removed from selection
20+
- Items are dragged
21+
- Selection changes
22+
- Visual feedback when gaining/losing focus
23+
24+
### 4. Integration with Existing System
25+
- All existing `updateFocusedNode()` calls now trigger outline visibility
26+
- Added `onBecomeFocused()` method to ensure outline is shown
27+
- Proper cleanup in `dispose()` method
28+
29+
## Technical Details
30+
31+
### Visual Elements
32+
- **Container**: `.blocklyMultiselectOutline` - Transparent container
33+
- **Outline**: `.blocklyMultiselectOutlineRect` - Focusable dashed rectangle
34+
- **Styling**: Blue dashed border (#4285f4) with rounded corners
35+
36+
### Focus States
37+
- **Normal**: 2px dashed border
38+
- **Focused**: 3px border with enhanced visibility
39+
- **Hover**: Reduced opacity for better UX
40+
41+
### Accessibility
42+
- Proper `role="button"` and `aria-label`
43+
- Keyboard navigation support (Enter, Space, Escape)
44+
- Focus indicators follow web accessibility standards
45+
46+
## Benefits
47+
48+
1. **Solves v12 Focus Issues**: Provides a real DOM element that focus manager can work with
49+
2. **Visual Feedback**: Users can clearly see what's selected
50+
3. **Accessibility**: Screen readers and keyboard navigation work properly
51+
4. **Backward Compatible**: Doesn't break existing functionality
52+
53+
## Usage
54+
55+
The implementation is automatic - when multiple blocks are selected:
56+
1. The outline appears around the selection
57+
2. The outline can receive focus from the focus manager
58+
3. Users can interact with it via keyboard or mouse
59+
4. The outline updates in real-time during operations
60+
61+
This follows the recommended approach (2) from Ben's suggestions and implements Christopher's DOM element idea successfully.

src/multiselect_contextmenu.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ const registerDuplicate = function() {
235235
connectionDB[0].connect(connectionDB[1]);
236236
});
237237
Blockly.getFocusManager().updateFocusedNode(multiDraggable);
238+
// Call the new method to ensure outline is visible
239+
if (multiDraggable.onBecomeFocused) {
240+
multiDraggable.onBecomeFocused();
241+
}
238242
Blockly.Events.setGroup(false);
239243
},
240244
scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
@@ -790,6 +794,10 @@ const registerPaste = function(useCopyPasteCrossTab) {
790794
});
791795
Blockly.Events.setGroup(false);
792796
Blockly.getFocusManager().updateFocusedNode(multiDraggable);
797+
// Call the new method to ensure outline is visible
798+
if (multiDraggable.onBecomeFocused) {
799+
multiDraggable.onBecomeFocused();
800+
}
793801
return true;
794802
},
795803
scopeType: Blockly.ContextMenuRegistry.ScopeType.WORKSPACE,
@@ -857,6 +865,10 @@ const registerSelectAll = function() {
857865
});
858866

859867
Blockly.getFocusManager().updateFocusedNode(multiDraggable);
868+
// Call the new method to ensure outline is visible
869+
if (multiDraggable.onBecomeFocused) {
870+
multiDraggable.onBecomeFocused();
871+
}
860872
},
861873
scopeType: Blockly.ContextMenuRegistry.ScopeType.WORKSPACE,
862874
id,
@@ -1128,6 +1140,10 @@ const registerCommentDuplicate = function() {
11281140
}
11291141
}
11301142
Blockly.getFocusManager().updateFocusedNode(multiDraggable);
1143+
// Call the new method to ensure outline is visible
1144+
if (multiDraggable.onBecomeFocused) {
1145+
multiDraggable.onBecomeFocused();
1146+
}
11311147
Blockly.Events.setGroup(false);
11321148
},
11331149
scopeType: Blockly.ContextMenuRegistry.ScopeType.COMMENT,

src/multiselect_controls.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,10 @@ export class MultiselectControls {
406406
if (this.dragSelection.size && !(Blockly.getSelected() instanceof
407407
MultiselectDraggable)) {
408408
Blockly.getFocusManager().updateFocusedNode(this.multiDraggable);
409+
// Call the new method to ensure outline is visible
410+
if (this.multiDraggable.onBecomeFocused) {
411+
this.multiDraggable.onBecomeFocused();
412+
}
409413
} else if (this.lastSelectedElement_ &&
410414
!inPasteShortcut.get(this.workspace_)) {
411415
this.updateDraggables_(this.lastSelectedElement_);
@@ -529,6 +533,10 @@ export class MultiselectControls {
529533
// our set is not empty.
530534
if (this.dragSelection.size && !Blockly.getSelected()) {
531535
Blockly.getFocusManager().updateFocusedNode(this.multiDraggable);
536+
// Call the new method to ensure outline is visible
537+
if (this.multiDraggable.onBecomeFocused) {
538+
this.multiDraggable.onBecomeFocused();
539+
}
532540
}
533541
if (this.hasDisableWorkspaceDrag_) {
534542
this.workspace_.options.moveOptions.drag = true;
@@ -571,4 +579,22 @@ Blockly.Css.register(`
571579
.blocklyMultiselect>image:active, .blocklyMultiselect>svg>image:active {
572580
opacity: .8;
573581
}
582+
583+
.blocklyMultiselectOutline {
584+
pointer-events: none;
585+
}
586+
587+
.blocklyMultiselectOutlineRect {
588+
pointer-events: all;
589+
cursor: move;
590+
}
591+
592+
.blocklyMultiselectOutlineRect:focus {
593+
outline: none;
594+
stroke-width: 3;
595+
}
596+
597+
.blocklyMultiselectOutlineRect:hover {
598+
stroke-opacity: 0.8;
599+
}
574600
`);

0 commit comments

Comments
 (0)