Skip to content
Merged
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
1 change: 1 addition & 0 deletions locale/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,7 @@ export default {
context_enable_option: 'Enable',
add_comment: 'Add comment',
delete_comment: 'Delete comment',
comment_text_label: 'Comment text',
view_in_canvas: 'View in canvas',
exit_canvas_view: 'Stop orbiting object',
detach_block_option: 'Detach',
Expand Down
62 changes: 58 additions & 4 deletions main/blocklyinit.js
Original file line number Diff line number Diff line change
Expand Up @@ -753,16 +753,62 @@ export function initializeWorkspace() {
}
workspaceSearch.init();
workspaceSearch.setSearchPlaceholder(translate('workspace_search_placeholder'));
// @blockly/plugin-workspace-search's createTextInput() only sets a
// placeholder, never an accessible name.
workspaceSearch.inputElement?.setAttribute('aria-label', translate('workspace_search_placeholder'));
window.flockWorkspaceSearch = workspaceSearch;

// Comment textareas (block-comment bubbles and standalone workspace
// comments both render a <textarea class="blocklyCommentText">) only ever
// get a placeholder from Blockly core, never an accessible name, and are
// created on demand whenever a comment is opened/added.
const labelCommentTextarea = (el) => {
if (el.tagName === 'TEXTAREA' && el.classList.contains('blocklyCommentText')) {
el.setAttribute('aria-label', translate('comment_text_label'));
}
};
const refreshCommentTextareaLabels = () =>
workspace
.getInjectionDiv()
.querySelectorAll('textarea.blocklyCommentText')
.forEach(labelCommentTextarea);
refreshCommentTextareaLabels();
// Re-applies comment_text_label to textareas already mounted when the
// language changes; new ones are covered by the MutationObserver below.
window.flockRefreshCommentTextareaLabels = refreshCommentTextareaLabels;
new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (!(node instanceof Element)) continue;
labelCommentTextarea(node);
node.querySelectorAll?.('textarea.blocklyCommentText').forEach(labelCommentTextarea);
}
}
}).observe(workspace.getInjectionDiv(), { childList: true, subtree: true });
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Shared label map populated by buildSearchIndex (overrideSearchPlugin), used by getBlockLabel
workspace.flockBlockLabelMap ??= new Map();

// @blockly/toolbox-search's createDom_() wipes the category row's label
// span via replaceChildren(), leaving the treeitem's aria-labelledby
// pointing at an id that no longer exists, and never gives the <input>
// itself an accessible name (placeholder alone doesn't count).
const fixSearchCategoryAria = (input) => {
const label = translate('toolbox_search_placeholder');
const treeItem = input.closest('[role="treeitem"]');
if (treeItem?.getAttribute('aria-labelledby') === 'toolbox-search-input.label') {
treeItem.removeAttribute('aria-labelledby');
treeItem.setAttribute('aria-label', label);
}
input.setAttribute('aria-label', label);
};

// Mobile: custom HTML search results panel (bypasses the SVG flyout entirely)
requestAnimationFrame(() => {
let searchInput = document.querySelector(".blocklyToolbox input[type='search']");
if (!searchInput) return;
searchInput.placeholder = translate('toolbox_search_placeholder');
fixSearchCategoryAria(searchInput);

let originalParent = searchInput.parentElement;
const isMobile = isMobileSearchLayout;
Expand Down Expand Up @@ -1102,6 +1148,7 @@ export function initializeWorkspace() {
const newInput = document.querySelector(".blocklyToolbox input[type='search']");
if (!newInput) return;
newInput.placeholder = translate('toolbox_search_placeholder');
fixSearchCategoryAria(newInput);
searchInput = newInput;
originalParent = newInput.parentElement;
searchCategory = workspace
Expand All @@ -1127,7 +1174,6 @@ export function initializeWorkspace() {
const wsMobileInput = document.createElement('input');
wsMobileInput.type = 'text';
wsMobileInput.className = 'ws-search-mobile-input';
wsMobileInput.placeholder = translate('workspace_search_placeholder');
wsMobileInput.setAttribute('autocomplete', 'one-time-code');

const wsMobileCount = document.createElement('span');
Expand All @@ -1137,23 +1183,31 @@ export function initializeWorkspace() {
const wsMobilePrev = document.createElement('button');
wsMobilePrev.type = 'button';
wsMobilePrev.className = 'ws-search-mobile-btn';
wsMobilePrev.setAttribute('aria-label', translate('shortcut_select_previous_result'));
wsMobilePrev.textContent = '▲';

const wsMobileNext = document.createElement('button');
wsMobileNext.type = 'button';
wsMobileNext.className = 'ws-search-mobile-btn';
wsMobileNext.setAttribute('aria-label', translate('shortcut_select_next_result'));
wsMobileNext.textContent = '▼';

const wsMobileClose = document.createElement('button');
wsMobileClose.type = 'button';
wsMobileClose.className = 'ws-search-mobile-btn ws-search-mobile-close';
wsMobileClose.setAttribute('aria-label', translate('close'));
wsMobileClose.textContent = '×';

wsMobileBar.append(wsMobileInput, wsMobileCount, wsMobilePrev, wsMobileNext, wsMobileClose);

const refreshWsMobileSearchLabels = () => {
const searchLabel = translate('workspace_search_placeholder');
wsMobileInput.placeholder = searchLabel;
wsMobileInput.setAttribute('aria-label', searchLabel);
wsMobilePrev.setAttribute('aria-label', translate('shortcut_select_previous_result'));
wsMobileNext.setAttribute('aria-label', translate('shortcut_select_next_result'));
wsMobileClose.setAttribute('aria-label', translate('close'));
};
refreshWsMobileSearchLabels();
window.flockRefreshMobileWorkspaceSearchLabels = refreshWsMobileSearchLabels;

const updateWsMobileCount = () => {
const total = workspaceSearch.blocks?.length ?? 0;
const idx = workspaceSearch.currentBlockIndex ?? -1;
Expand Down
3 changes: 3 additions & 0 deletions main/translation.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ export async function setLanguage(language) {

// Update workspace search placeholder and toolbar button
window.flockWorkspaceSearch?.setSearchPlaceholder?.(translate('workspace_search_placeholder'));
window.flockWorkspaceSearch?.inputElement?.setAttribute('aria-label', translate('workspace_search_placeholder'));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
window.flockRefreshMobileWorkspaceSearchLabels?.();
window.flockRefreshCommentTextareaLabels?.();
const wsSearchBtn = document.getElementById('workspaceSearchBtn');
if (wsSearchBtn) {
const label = translate('workspace_search_placeholder');
Expand Down
6 changes: 6 additions & 0 deletions ui/contextmenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,8 @@ export function initContextMenus(workspace) {
const blockToolbar = document.createElement('div');
blockToolbar.className = 'fc-block-toolbar';
blockToolbar.setAttribute('role', 'toolbar');
blockToolbar.setAttribute('aria-hidden', 'true');
Comment thread
coderabbitai[bot] marked this conversation as resolved.
blockToolbar.inert = true;
document.body.appendChild(blockToolbar);

// Keyboard-only overlay of shortcut-letter badges, one per visible button.
Expand Down Expand Up @@ -1108,6 +1110,8 @@ export function initContextMenus(workspace) {
: getToolbarLabel('view_in_canvas', 'View in canvas')
);
blockToolbar.classList.add('visible');
blockToolbar.removeAttribute('aria-hidden');
blockToolbar.inert = false;
// Clear any stale badges from a previous keyboard selection; in keyboard
// mode positionBlockToolbar() draws fresh ones (it also re-runs on block
// move / viewport change to keep them aligned with the buttons).
Expand All @@ -1124,6 +1128,8 @@ export function initContextMenus(workspace) {
toolbarBlock = null;
toolbarKeyboardMode = false;
blockToolbar.classList.remove('visible');
blockToolbar.setAttribute('aria-hidden', 'true');
blockToolbar.inert = true;
clearBadges();
}

Expand Down
Loading