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
7 changes: 4 additions & 3 deletions browser/src/control/Control.JSDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,9 @@ window.L.Control.JSDialog = window.L.Control.extend({

instance.form = window.L.DomUtil.create('form', 'jsdialog-container ui-dialog ui-widget-content lokdialog_container', instance.container);
instance.form.setAttribute('role', 'dialog');
instance.form.setAttribute('aria-labelledby', instance.title);
instance.form.setAttribute('autocomplete', 'off');
if (instance.title)
instance.form.setAttribute('aria-labelledby', instance.title);
// Prevent overlay from getting the click, except if we want click to dismiss
// Like in the case of the inactivity message.
// https://github.com/CollaboraOnline/online/issues/7403
Expand Down Expand Up @@ -481,7 +482,7 @@ window.L.Control.JSDialog = window.L.Control.extend({
// this will only search in current instance and not in whole document
const tabControlWidget = this.findTabControl(instance);

let focusWidget, firstFocusableElement ;
let focusWidget, firstFocusableElement;

if (tabControlWidget && !instance.init_focus_id) {
// get DOM element of tabControl from current instance
Expand All @@ -498,7 +499,7 @@ window.L.Control.JSDialog = window.L.Control.extend({
if (focusables && focusables.length) firstFocusableElement = focusables[0];
}

if (firstFocusableElement && !JSDialog.IsFocusable(firstFocusableElement)){
if (firstFocusableElement && !JSDialog.IsFocusable(firstFocusableElement)) {
firstFocusableElement = JSDialog.FindFocusableWithin(firstFocusableElement, 'next');
}
}
Expand Down
2 changes: 2 additions & 0 deletions browser/src/control/jsdialog/Definitions.Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ interface OverflowGroupContainer extends Element {
interface GridWidgetJSON extends ContainerWidgetJSON {
cols: number; // number of grid columns
rows: number; // numer of grid rows
tabIndex?: number;
initialSelectedId?: string; // id of the first selected element
}

interface ToolboxWidgetJSON extends WidgetJSON {
Expand Down
16 changes: 14 additions & 2 deletions browser/src/control/jsdialog/Util.Dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ JSDialog.OpenDropdown = function (id, popupParent, entries, innerCallback, popup
allyRole: 'listbox',
cols: 1,
rows: entries.length,
tabIndex: 0,
children: []
}
]
Expand All @@ -59,7 +60,11 @@ JSDialog.OpenDropdown = function (id, popupParent, entries, innerCallback, popup
return false;
};

for (var i in entries) {
const shouldSelectFirstEntry = entries.length > 0 ? !entries.some(entry => entry.selected === true) : false;

let initialSelectedId;

for (let i = 0; i < entries.length; i++) {
var checkedValue = (entries[i].checked === undefined)
? undefined : (entries[i].uno && isChecked('.uno' + entries[i].uno));

Expand All @@ -68,6 +73,7 @@ JSDialog.OpenDropdown = function (id, popupParent, entries, innerCallback, popup
if (entries[i].type === 'json') {
// replace old grid with new widget
json.children[0] = entries[i].content;
initialSelectedId = json.children[0].initialSelectedId;
if (json.children[0].type === 'grid') json.gridKeyboardNavigation = true;
break;
}
Expand Down Expand Up @@ -121,15 +127,21 @@ JSDialog.OpenDropdown = function (id, popupParent, entries, innerCallback, popup
w2icon: entries[i].icon, // FIXME: DEPRECATED
icon: entries[i].img,
checked: entries[i].checked || checkedValue,
selected: entries[i].selected,
selected: (i === 0 && shouldSelectFirstEntry) ? true : entries[i].selected,
hasSubMenu: !!entries[i].items
};
if (entry.selected) initialSelectedId = entry.id;
break;
}

json.children[0].children.push(entry);
}

if (initialSelectedId) {
json.init_focus_id = initialSelectedId;
json.children[0].initialSelectedId = initialSelectedId;
}

var lastSubMenuOpened = null;
var generateCallback = function (targetEntries) {
return function(objectType, eventType, object, data) {
Expand Down
1 change: 1 addition & 0 deletions browser/src/control/jsdialog/Util.FocusCycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ function isFocusable(element) {
'input[type="checkbox"]:not([disabled]):not(.hidden)',
'select:not([disabled]):not(.hidden)',
'[tabindex]:not([tabindex="-1"]):not(.jsdialog-begin-marker):not(.jsdialog-end-marker):not([disabled]):not(.hidden)',
'[role="listbox"] [role="option"]:not([disabled]):not(.hidden)'
];

return focusableElements.some((selector) => element.matches(selector));
Expand Down
23 changes: 14 additions & 9 deletions browser/src/control/jsdialog/Util.KeyboardListNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,6 @@ function KeyboardListNavigation(
moveToFocusableEntry(currentElement, 'previous');
event.preventDefault();
break;
case 'Tab':
if (event.shiftKey) {
moveToFocusableEntry(currentElement, 'previous');
event.preventDefault();
} else {
moveToFocusableEntry(currentElement, 'next');
event.preventDefault();
}
break;
default:
break;
}
Expand All @@ -52,6 +43,13 @@ function moveToFocusableEntry(
}
};

const updateAriaActiveDescendant = (elem: HTMLElement) => {
const listbox = elem.closest('[role="listbox"]');
if (listbox && elem.id) {
listbox.setAttribute('aria-activedescendant', elem.id);
}
};

// If the current element is focused but not selected, add 'selected' class and return
if (
document.activeElement === currentElement &&
Expand All @@ -60,6 +58,7 @@ function moveToFocusableEntry(
) {
currentElement.classList.add('selected');
updateAriaSelected(currentElement, 'true');
updateAriaActiveDescendant(currentElement);
return;
}

Expand All @@ -72,6 +71,7 @@ function moveToFocusableEntry(
(siblingElement as HTMLElement).focus();
siblingElement.classList.add('selected');
updateAriaSelected(siblingElement, 'true');
updateAriaActiveDescendant(siblingElement);

currentElement.classList.remove('selected');
updateAriaSelected(currentElement, 'false');
Expand All @@ -80,6 +80,11 @@ function moveToFocusableEntry(

JSDialog.KeyboardListNavigation = function (container: HTMLElement) {
container.addEventListener('keydown', (event: KeyboardEvent) => {
const allowedKeys = ['ArrowDown', 'ArrowUp'];
if (!allowedKeys.includes(event.key)) {
return;
}

const activeElement = document.activeElement as HTMLElement;
if (!JSDialog.IsTextInputField(activeElement)) {
KeyboardListNavigation(event, activeElement);
Expand Down
10 changes: 10 additions & 0 deletions browser/src/control/jsdialog/Widget.Combobox.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ JSDialog.comboboxEntry = function (parentContainer, data, builder) {
var entry = window.L.DomUtil.create('div', 'ui-combobox-entry ' + builder.options.cssClass, parentContainer);
entry.id = data.id;
entry.setAttribute('role', 'option');
entry.setAttribute('tabindex', '-1');

if (data.hasSubMenu)
window.L.DomUtil.addClass(entry, 'ui-has-menu');
Expand All @@ -55,6 +56,8 @@ JSDialog.comboboxEntry = function (parentContainer, data, builder) {
if (data.selected) {
entry.setAttribute('aria-selected', 'true');
window.L.DomUtil.addClass(entry, 'selected');
} else {
entry.setAttribute('aria-selected', 'false');
}

if (data.checked)
Expand All @@ -79,6 +82,13 @@ JSDialog.comboboxEntry = function (parentContainer, data, builder) {
}
});

entry.addEventListener('keydown', function (event) {
if (event.key === 'Tab') {
JSDialog.CloseDropdown(data.comboboxId);
event.preventDefault();
}
});

if (data.hasSubMenu) {
entry.setAttribute('aria-haspopup', true);
entry.setAttribute('aria-expanded', false);
Expand Down
6 changes: 6 additions & 0 deletions browser/src/control/jsdialog/Widget.Containers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,14 @@ JSDialog.grid = function (

if (data.allyRole) {
table.role = data.allyRole;

if (data.allyRole === 'listbox')
table.setAttribute('aria-activedescendant', data.initialSelectedId);
}

if (data.tabIndex !== undefined)
table.setAttribute('tabindex', data.tabIndex);

const gridRowColStyle =
'grid-template-rows: repeat(' +
rows +
Expand Down
39 changes: 35 additions & 4 deletions browser/src/control/jsdialog/Widget.PageMarginEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function createPageMarginEntryWidget(data: any, builder: any): HTMLElement {
container.className = 'margins-popup-container';
container.setAttribute('role', 'listbox');
container.setAttribute('aria-label', _('Page margin options'));
container.setAttribute('tabindex', '0');

const lang = window.coolParams.get('lang') || 'en-US';
const useImperial = lang === 'en-US' || lang === 'en'; // we need to consider both short form as some user can user lang=en-US using document URL
Expand All @@ -56,7 +57,7 @@ function createPageMarginEntryWidget(data: any, builder: any): HTMLElement {
return `${formatted}${unit}`;
}

const onMarginClick = (evt: MouseEvent) => {
const onMarginClick = (evt: MouseEvent | KeyboardEvent) => {
const elm = evt.currentTarget as HTMLElement;
const key = elm.id;
if (!key || !options[key]) return;
Expand Down Expand Up @@ -88,17 +89,36 @@ function createPageMarginEntryWidget(data: any, builder: any): HTMLElement {
builder.callback('dialog', 'close', { id: data.id }, null);
};

Object.keys(options).forEach((key) => {
Object.keys(options).forEach((key, index) => {
const opt = options[key];
const isFirstItem = index === 0;

const item = document.createElement('div');
item.className = 'margin-item';
item.id = key;
item.setAttribute('role', 'option');
item.setAttribute('tabindex', '0');
item.setAttribute('tabindex', '-1');
item.setAttribute('aria-selected', 'false');
item.addEventListener('click', onMarginClick);

item.addEventListener('keydown', function (event: KeyboardEvent) {
if (event.key === 'Enter' || event.key === ' ') {
onMarginClick(event);
event.preventDefault();
} else if (event.key === 'Tab') {
JSDialog.CloseDropdown(data.id);
event.preventDefault();
}
});

if (isFirstItem) {
item.classList.add('selected');
item.setAttribute('aria-selected', 'true');
container.setAttribute('aria-activedescendant', item.id);

data.initialSelectedId = item.id;
}

const img = document.createElement('img');
img.className = 'margin-icon';
img.setAttribute('alt', '');
Expand Down Expand Up @@ -166,9 +186,20 @@ function createPageMarginEntryWidget(data: any, builder: any): HTMLElement {
custom.setAttribute('role', 'button');
custom.setAttribute('tabindex', '0');

custom.addEventListener('click', (evt: MouseEvent) => {
const customClickEventHdl = () => {
map.sendUnoCommand(isCalc ? '.uno:PageFormatDialog' : '.uno:PageDialog');
builder.callback('dialog', 'close', { id: data.id }, null);
};

custom.addEventListener('click', customClickEventHdl);
custom.addEventListener('keydown', function (event: KeyboardEvent) {
if (event.key === 'Enter' || event.key === ' ') {
customClickEventHdl();
event.preventDefault();
} else if (event.key === 'Tab') {
JSDialog.CloseDropdown(data.id);
event.preventDefault();
}
});
container.appendChild(custom);

Expand Down
Loading