427 lines
17 KiB
JavaScript
427 lines
17 KiB
JavaScript
/*
|
|
* (c) Copyright Ascensio System SIA 2010-2024
|
|
*
|
|
* This program is a free software product. You can redistribute it and/or
|
|
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
|
* version 3 as published by the Free Software Foundation. In accordance with
|
|
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
|
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
|
* of any third-party rights.
|
|
*
|
|
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
|
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
|
*
|
|
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
|
* street, Riga, Latvia, EU, LV-1050.
|
|
*
|
|
* The interactive user interfaces in modified source and object code versions
|
|
* of the Program must display Appropriate Legal Notices, as required under
|
|
* Section 5 of the GNU AGPL version 3.
|
|
*
|
|
* Pursuant to Section 7(b) of the License you must retain the original Product
|
|
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
|
* grant you any rights under trademark law for use of our trademarks.
|
|
*
|
|
* All the Product's GUI elements, including illustrations and icon sets, as
|
|
* well as technical writing content are licensed under the terms of the
|
|
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
|
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
|
*
|
|
*/
|
|
/**
|
|
* ScreenReaderFocus.js
|
|
*
|
|
* Created on 22.11.2023
|
|
*
|
|
*/
|
|
|
|
|
|
if (Common === undefined)
|
|
var Common = {};
|
|
|
|
if (Common.UI === undefined) {
|
|
Common.UI = {};
|
|
}
|
|
|
|
Common.UI.ScreenReaderFocusManager = new(function() {
|
|
var _needShow = false,
|
|
_focusVisible = false,
|
|
_focusMode = false,
|
|
_currentLevel = 0,
|
|
_lastLevel = 0,
|
|
_currentSection = document,
|
|
_lastSection = document,
|
|
_currentControls = [],
|
|
_currentItemIndex,
|
|
_isLockedKeyEvents = false,
|
|
_unlockKeyEvents = false,
|
|
_isDocReady = false,
|
|
_isEditDiagram = false,
|
|
_isSidePanelMode = false,
|
|
_api,
|
|
_app,
|
|
_appPrefix;
|
|
|
|
var _setCurrentSection = function (btn, section) {
|
|
_lastSection = _currentSection;
|
|
if (section) {
|
|
_currentSection = section;
|
|
return;
|
|
}
|
|
if (btn === 'esc') {
|
|
if (_currentLevel === 0) {
|
|
_currentSection = document;
|
|
}
|
|
return;
|
|
}
|
|
if (_isEditDiagram) {
|
|
_currentSection = [$(window.parent.document).find('.advanced-settings-dlg:visible')[0], window.document];
|
|
} else if ($('#file-menu-panel').is(':visible')) {
|
|
_currentSection = $('#file-menu-panel');
|
|
} else {
|
|
_currentSection = _currentLevel === 0 ? document : ((btn && btn.closest('.hint-section')) || document);
|
|
}
|
|
};
|
|
|
|
var _lockedKeyEvents = function (isLocked) {
|
|
if (_api && (isLocked || !Common.Utils.ModalWindow.isVisible())) {
|
|
_isLockedKeyEvents = isLocked;
|
|
_api.asc_enableKeyEvents(!isLocked);
|
|
}
|
|
};
|
|
|
|
var _showFocus = function () {
|
|
if (_currentControls.length === 0 || (($('#file-menu-panel').is(':visible') || _isEditDiagram) && _currentLevel === 1)) {
|
|
_getControls();
|
|
// console.log(_currentControls);
|
|
}
|
|
if (!_focusVisible) {
|
|
if ($('#file-menu-panel').is(':visible')) {
|
|
_setFocusInActiveFileMenuItem();
|
|
} else {
|
|
_setFocusInActiveTab();
|
|
}
|
|
} else if (_currentLevel !== _lastLevel && (_currentLevel === 0 || _currentLevel === 1 && $('#file-menu-panel').is(':visible'))) {
|
|
var id = $(_lastSection).prop('id');
|
|
if (id === 'toolbar') {
|
|
_setFocusInActiveTab();
|
|
} else if (id === 'left-menu' || id === 'right-menu') {
|
|
_setFocusInActiveCategory(id);
|
|
} else if (id === 'file-menu-panel') {
|
|
_setFocusInActiveFileMenuItem();
|
|
}
|
|
} else if (_currentLevel === 1 && _currentLevel !== _lastLevel && ($(_currentSection).prop('id') === 'left-menu' || $(_currentSection).prop('id') === 'right-menu')) {
|
|
_setFocusInSideMenu($(_currentSection).prop('id') === 'left-menu');
|
|
}
|
|
var currItem = _currentControls[_currentItemIndex];
|
|
// console.log(_currentControls[_currentItemIndex]);
|
|
if (currItem) {
|
|
if ($(currItem).parent().hasClass('ribtab') && !$(currItem).parent().hasClass('active') && $(currItem).data('tab') !== 'file') {
|
|
$(currItem).trigger(jQuery.Event('click', {which: 1}));
|
|
}
|
|
$(_currentControls[_currentItemIndex]).focus();
|
|
}
|
|
if (_currentControls.length > 0) {
|
|
!_isLockedKeyEvents && _lockedKeyEvents(true);
|
|
_focusVisible = true;
|
|
} else {
|
|
_focusVisible = false;
|
|
}
|
|
};
|
|
|
|
var _hideFocus = function () {
|
|
_focusVisible = false;
|
|
_focusMode = false;
|
|
_isSidePanelMode = false;
|
|
};
|
|
|
|
var _nextItem = function () {
|
|
_lastLevel = _currentLevel;
|
|
_currentItemIndex++;
|
|
if (_currentItemIndex > _currentControls.length - 1) {
|
|
_currentItemIndex = 0;
|
|
}
|
|
};
|
|
|
|
var _prevItem = function () {
|
|
_lastLevel = _currentLevel;
|
|
_currentItemIndex--;
|
|
if (_currentItemIndex < 0) {
|
|
_currentItemIndex = _currentControls.length - 1;
|
|
}
|
|
};
|
|
|
|
var _nextLevel = function(level) {
|
|
_lastLevel = _currentLevel;
|
|
_currentItemIndex = 0;
|
|
_currentControls.length = 0;
|
|
if (level !== undefined) {
|
|
_currentLevel = level;
|
|
} else {
|
|
_currentLevel++;
|
|
}
|
|
};
|
|
|
|
var _prevLevel = function() {
|
|
_lastLevel = _currentLevel;
|
|
_currentControls.length = 0;
|
|
_currentLevel--;
|
|
};
|
|
|
|
var _resetToDefault = function() {
|
|
_currentLevel = ($('#file-menu-panel').is(':visible') || _isEditDiagram) ? 1 : 0;
|
|
_setCurrentSection();
|
|
_currentControls.length = 0;
|
|
};
|
|
|
|
var _setFocusInActiveTab = function () {
|
|
var activeTab;
|
|
for (var i=0; i<_currentControls.length; i++) {
|
|
var parent = $(_currentControls[i]).parent();
|
|
if (parent && parent.hasClass('ribtab') && parent.hasClass('active')) {
|
|
activeTab = _currentControls[i];
|
|
break;
|
|
}
|
|
}
|
|
if (activeTab) {
|
|
_currentItemIndex = i;
|
|
}
|
|
};
|
|
|
|
var _setFocusInActiveCategory = function (id) {
|
|
var activeCategory;
|
|
for (var i=0; i<_currentControls.length; i++) {
|
|
var item = $(_currentControls[i]);
|
|
if ($(item.closest('.hint-section')).prop('id') === id && item.hasClass('btn-category') && item.hasClass('active')) {
|
|
activeCategory = _currentControls[i];
|
|
break;
|
|
}
|
|
}
|
|
if (activeCategory) {
|
|
_currentItemIndex = i;
|
|
}
|
|
};
|
|
|
|
var _setFocusInActiveFileMenuItem = function () {
|
|
var activeItem;
|
|
for (var i=0; i<_currentControls.length; i++) {
|
|
if ($(_currentControls[i]).parent().hasClass('active')) {
|
|
activeItem = _currentControls[i];
|
|
break;
|
|
}
|
|
}
|
|
if (activeItem) {
|
|
_currentItemIndex = i;
|
|
}
|
|
};
|
|
|
|
var _setFocusInSideMenu = function (isLeftMenu) {
|
|
var index = 0,
|
|
view, btn;
|
|
if (isLeftMenu) {
|
|
view = _app.getController('LeftMenu').getView('LeftMenu');
|
|
btn = view.getFocusElement();
|
|
}
|
|
if (btn) {
|
|
for (var i=0; i<_currentControls.length; i++) {
|
|
if ($(_currentControls[i]).is($(btn))) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
_currentItemIndex = index;
|
|
};
|
|
|
|
var _isItemDisabled = function (item) {
|
|
return (item.hasClass('disabled') || item.parent().hasClass('disabled') || item.attr('disabled'));
|
|
};
|
|
|
|
var _getControls = function() {
|
|
_currentControls = [];
|
|
var arr = [];
|
|
if (_.isArray(_currentSection)) {
|
|
_currentSection.forEach(function (section) {
|
|
arr = arr.concat($(section).find('[data-hint=' + (_currentLevel) + ']').toArray());
|
|
});
|
|
} else {
|
|
arr = $(_currentSection).find('[data-hint=' + (_currentLevel) + ']').toArray();
|
|
}
|
|
_currentControls = arr.filter(function (item) {
|
|
return ($(item).is(':visible') && !_isItemDisabled($(item)));
|
|
});
|
|
_currentControls.forEach(function (item) {
|
|
if ($(item).attr("tabindex") === undefined) $(item).attr("tabindex", 0);
|
|
});
|
|
};
|
|
|
|
var _exitFocusMode = function () {
|
|
_hideFocus();
|
|
_resetToDefault();
|
|
_isLockedKeyEvents && _lockedKeyEvents(false);
|
|
};
|
|
|
|
var _init = function(api) {
|
|
if (Common.Utils.isIE || Common.UI.isMac && Common.Utils.isGecko) // turn off hints on IE and FireFox (shortcut F6 selects link in address bar)
|
|
return;
|
|
_api = api;
|
|
_app = window.DE || window.PE || window.SSE || window.PDFE || window.VE;
|
|
_isDocReady = true;
|
|
|
|
var filter = Common.localStorage.getKeysFilter();
|
|
_appPrefix = (filter && filter.length) ? filter.split(',')[0] : '';
|
|
|
|
if ( !Common.Utils.ScreeenReaderHelper ) {
|
|
require(['common/main/lib/util/ScreenReaderHelper'], function () {
|
|
Common.Utils.ScreeenReaderHelper.setEnabled(true);
|
|
});
|
|
}
|
|
|
|
$('#editor_sdk').on('click', function () {
|
|
_exitFocusMode();
|
|
});
|
|
$(document).on('mousedown', function () {
|
|
_exitFocusMode();
|
|
});
|
|
$(document).on('keyup', function(e) {
|
|
if ((e.keyCode == Common.UI.Keys.ALT || e.keyCode === 91) && _needShow && !(window.SSE && window.SSE.getController('Statusbar').getIsDragDrop())) {
|
|
e.preventDefault();
|
|
if (!_focusVisible) {
|
|
$('input:focus').blur(); // to change value in inputField
|
|
_currentLevel = ($('#file-menu-panel').is(':visible') || _isEditDiagram) ? 1 : 0;
|
|
_setCurrentSection();
|
|
_showFocus();
|
|
} else {
|
|
_exitFocusMode();
|
|
}
|
|
} else if (_unlockKeyEvents) {
|
|
_lockedKeyEvents(false);
|
|
_unlockKeyEvents = false;
|
|
} else if (_focusVisible) {
|
|
e.preventDefault();
|
|
}
|
|
_needShow = false;
|
|
});
|
|
$(document).on('keydown.after.bs.dropdown', function(e) {
|
|
if (_focusVisible) {
|
|
var tag = $(_currentControls[_currentItemIndex]).prop('tagName'),
|
|
isInputFocused = tag === 'INPUT' || tag === 'TEXTAREA',
|
|
isDirectionEvent = e.keyCode == Common.UI.Keys.TAB || e.keyCode == Common.UI.Keys.LEFT || e.keyCode == Common.UI.Keys.RIGHT ||
|
|
e.keyCode == Common.UI.Keys.UP || e.keyCode == Common.UI.Keys.DOWN,
|
|
isControlEvent = e.keyCode == Common.UI.Keys.ESC || e.keyCode == Common.UI.Keys.RETURN || e.keyCode == Common.UI.Keys.SPACE;
|
|
if($(_currentControls[_currentItemIndex]).data('move-focus-only-tab') && e.keyCode !== Common.UI.Keys.TAB && !isControlEvent) return;
|
|
if (!(isDirectionEvent || isControlEvent) || (e.keyCode == Common.UI.Keys.SPACE && isInputFocused)) return;
|
|
if (isDirectionEvent) e.preventDefault(); // allow to write in inputs
|
|
Common.UI.Menu.Manager.hideAll();
|
|
var turnOffHints = false,
|
|
btn = _currentControls[_currentItemIndex] && $(_currentControls[_currentItemIndex]);
|
|
var isFileMenu = $('#file-menu-panel').is(':visible'),
|
|
isBtnCategory = btn && btn.hasClass('btn-category'),
|
|
left = e.keyCode == Common.UI.Keys.LEFT,
|
|
right = e.keyCode == Common.UI.Keys.RIGHT,
|
|
up = e.keyCode == Common.UI.Keys.UP,
|
|
down = e.keyCode == Common.UI.Keys.DOWN,
|
|
tab = e.keyCode == Common.UI.Keys.TAB,
|
|
shiftTab = e.shiftKey && e.keyCode == Common.UI.Keys.TAB,
|
|
isPrevItem,
|
|
isNextItem,
|
|
isPrevLevel,
|
|
isNextLevel;
|
|
if (isFileMenu) {
|
|
isPrevItem = left || up;
|
|
isNextItem = _currentLevel === 2 ? (right || down || tab && !shiftTab) : (right || down);
|
|
isPrevLevel = shiftTab;
|
|
isNextLevel = tab && !shiftTab;
|
|
} else if (isBtnCategory) {
|
|
isPrevItem = up || shiftTab;
|
|
isNextItem = down || tab;
|
|
isPrevLevel = false;
|
|
isNextLevel = right || left;
|
|
} else if (_isSidePanelMode) {
|
|
isPrevItem = shiftTab;
|
|
isNextItem = tab;
|
|
isPrevLevel = false;
|
|
isNextLevel = false;
|
|
} else {
|
|
isPrevItem = left || shiftTab;
|
|
isNextItem = right || tab && !shiftTab;
|
|
isPrevLevel = up;
|
|
isNextLevel = down;
|
|
}
|
|
if (e.keyCode == Common.UI.Keys.ESC) {
|
|
_exitFocusMode();
|
|
return;
|
|
} else if (e.keyCode == Common.UI.Keys.RETURN || e.keyCode == Common.UI.Keys.SPACE) {
|
|
if (btn) {
|
|
if (btn.attr('for')) { // to trigger event in checkbox
|
|
(e.keyCode == Common.UI.Keys.RETURN) ? $('#' + btn.attr('for')).trigger(jQuery.Event('click', {which: 1})) : e.preventDefault(); // prevent type space in document
|
|
} else {
|
|
if (btn.data('tab') === 'file' || isFileMenu && _currentLevel === 1 || isBtnCategory)
|
|
btn.trigger(jQuery.Event('click', {which: 1}));
|
|
else
|
|
setTimeout(function() {btn.trigger(jQuery.Event('click', {which: 1}));}, 1); // click on toolbar buttons
|
|
}
|
|
if (btn.data('toggle') !== 'dropdown') btn.blur();
|
|
}
|
|
if (btn && btn.data('tab') === 'file' || isFileMenu && _currentLevel === 1 || isBtnCategory && btn.hasClass('active')) {
|
|
(isBtnCategory && btn.hasClass('active')) && (_isSidePanelMode = true);
|
|
_nextLevel();
|
|
_setCurrentSection(btn);
|
|
} else {
|
|
_hideFocus();
|
|
_resetToDefault();
|
|
Common.UI.HintManager.isHintVisible() && Common.UI.HintManager.clearHints(false, true);
|
|
if (btn && btn.data('toggle') !== 'dropdown') _unlockKeyEvents = true;
|
|
_isLockedKeyEvents = false;
|
|
return;
|
|
}
|
|
} else if (isPrevItem) {
|
|
turnOffHints = true;
|
|
_prevItem();
|
|
} else if (isNextItem) {
|
|
turnOffHints = true;
|
|
_nextItem();
|
|
} else if (isNextLevel) {
|
|
var attr = '[data-hint="' + (_currentLevel + 1) + '"]';
|
|
if ($(_currentSection).find(attr).length === 0 || btn && $(btn.closest('.hint-section')).find(attr).filter(':visible').length === 0) return;
|
|
turnOffHints = true;
|
|
_nextLevel();
|
|
_setCurrentSection(btn);
|
|
if (isBtnCategory) _isSidePanelMode = true;
|
|
} else if (isPrevLevel) {
|
|
if (_currentLevel === 0) return;
|
|
turnOffHints = true;
|
|
_prevLevel();
|
|
_setCurrentSection(btn);
|
|
}
|
|
if (!_focusMode && turnOffHints) {
|
|
_focusMode = true;
|
|
Common.UI.HintManager.isHintVisible() && Common.UI.HintManager.clearHints(false, true);
|
|
}
|
|
_showFocus();
|
|
}
|
|
});
|
|
$(document).on('keydown', function(e) {
|
|
_needShow = Common.Utils.InternalSettings.get(_appPrefix + "settings-show-alt-hints") && e.keyCode == Common.UI.Keys.ALT && !e.shiftKey &&
|
|
!Common.Utils.ModalWindow.isVisible() && _isDocReady && !(window.PE && $('#pe-preview').is(':visible'));
|
|
|
|
// Add outline style for focus elements for test
|
|
if (Common.localStorage.getBool('screen-reader-focus-mode', false)) {
|
|
!$(document.body).hasClass('focus-mode') && $(document.body).addClass('focus-mode');
|
|
} else {
|
|
$(document.body).hasClass('focus-mode') && $(document.body).removeClass('focus-mode');
|
|
}
|
|
});
|
|
};
|
|
|
|
var _isFocusMode = function () {
|
|
return !!_focusMode;
|
|
}
|
|
|
|
return {
|
|
init: _init,
|
|
isFocusMode: _isFocusMode,
|
|
exitFocusMode: _exitFocusMode
|
|
}
|
|
})(); |