openkeynav
Version:
OpenKeyNav: A JavaScript plugin for enhancing keyboard navigation and accessibility on web pages.
441 lines (412 loc) • 16.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.modiferKeyString = exports.handleKeyPress = void 0;
var _clicking = require("./clicking");
var _dragAndDrop = require("./dragAndDrop");
var _escape = require("./escape");
var _focus = require("./focus");
var _isTabbable = require("./isTabbable");
var _keylabels = require("./keylabels");
var _lifecycle = require("./lifecycle");
var _keyButton = require("./keyButton.js");
function getMetaKeyName() {
var userAgent = window.navigator.userAgent.toLowerCase();
if (userAgent.indexOf('mac') >= 0) return 'Cmd';
if (userAgent.indexOf('win') >= 0) return 'Win';
if (userAgent.indexOf('linux') >= 0) return 'Super';
// fallback
return 'Meta';
}
var modiferKeyString = exports.modiferKeyString = function modiferKeyString(openKeyNav) {
switch (openKeyNav.config.keys.modifierKey) {
case 'shiftKey':
return 'Shift';
case 'altKey':
return 'Alt';
case 'metaKey':
return getMetaKeyName();
default:
return openKeyNav.config.keys.modifierKey;
}
};
var handleKeyPress = exports.handleKeyPress = function handleKeyPress(openKeyNav, e) {
var isTextInputActive = openKeyNav.isTextInputActive();
// enable / disable openKeyNav
if (e[openKeyNav.config.keys.modifierKey] && openKeyNav.config.keys.menu.toLowerCase() == e.key.toLowerCase()) {
if (isTextInputActive) {
if (!e[openKeyNav.config.keys.inputEscape]) {
return true;
}
}
if (!openKeyNav.meta.enabled.value) {
// if openKeyNav disabled
openKeyNav.enable();
var message = "openKeyNav enabled. Press ".concat((0, _keyButton.keyButton)([modiferKeyString(openKeyNav), openKeyNav.config.keys.menu]), " to disable.");
openKeyNav.emitNotification(message);
return true;
} else {
(0, _escape.handleEscape)(openKeyNav, e);
openKeyNav.disable();
var _message = "openKeyNav disabled. Press ".concat((0, _keyButton.keyButton)([modiferKeyString(openKeyNav), openKeyNav.config.keys.menu]), " to enable.");
openKeyNav.emitNotification(_message);
return true;
}
}
// first check for modifier keys and escape
switch (e.key) {
case 'Shift': // exit this event listener if it's the shift key press
case 'Control': // exit this event listener if it's the control key press
case 'Alt': // exit this event listener if it's the alt key press
case 'Meta': // exit this event listener if it's the meta key (Command/Windows) press
case ' ':
// exit this event listener if it's the space bar key press
// Prevent default action and stop the function
// e.preventDefault();
return true;
break;
// handle escape first
case 'Escape':
// escaping
// alert("Escape");
(0, _escape.handleEscape)(openKeyNav, e);
break;
}
// check if currently in any openkeynav modes
if (openKeyNav.config.modes.clicking.value) {
return handleClickMode(openKeyNav, e);
}
if (openKeyNav.config.modes.moving.value) {
return handleMoveMode(openKeyNav, e);
}
if (openKeyNav.config.modes.menu.value) {
handleMenuMode(e);
}
if (isTextInputActive) {
if (!e[openKeyNav.config.keys.inputEscape]) {
return true;
}
}
if (!openKeyNav.meta.enabled.value) {
return true;
}
// escape and toggles
switch (e.key) {
case openKeyNav.config.keys.escape:
// escaping
// alert("Escape");
(0, _escape.handleEscape)(openKeyNav, e);
return true;
break;
// case openKeyNav.config.keys.toggleCursor: // toggle Cursor
// // toggle class openKeyNav-noCursor for body
// document.body.classList.toggle('openKeyNav-noCursor');
// return true;
// break;
}
// modes
switch (e.key) {
case openKeyNav.config.keys.click: // possibly attempting to initiate click mode
case openKeyNav.config.keys.click.toUpperCase():
e.preventDefault();
openKeyNav.config.modes.clicking.value = true;
if (e.key == openKeyNav.config.keys.click.toUpperCase()) {
openKeyNav.config.modesConfig.click.modifier = true;
}
(0, _keylabels.showClickableOverlays)(openKeyNav);
return true;
break;
// possibly attempting to initiate moving mode
case openKeyNav.config.keys.move:
case openKeyNav.config.keys.move.toUpperCase():
// Toggle move mode
e.preventDefault();
openKeyNav.config.modes.moving.value = true; // Assuming you add a 'move' flag to your modes object
if (e.key == openKeyNav.config.keys.move.toUpperCase()) {
openKeyNav.config.modesConfig.move.modifier = true;
}
(0, _keylabels.showMoveableFromOverlays)(openKeyNav); // This will be a new function similar to showClickableOverlays
return true;
case openKeyNav.config.keys.menu:
case openKeyNav.config.keys.menu.toUpperCase():
openKeyNav.config.modes.menu.value = true;
if (e.key == openKeyNav.config.keys.menu.toUpperCase()) {
openKeyNav.config.modesConfig.menu.modifier = true;
}
return true;
break;
default:
break;
}
// focus / navigation (can be modified by shift, so always check for lowercase)
switch (e.key.toLowerCase()) {
// Check if the pressed key is for headings
case openKeyNav.config.keys.heading.toLowerCase():
/*
const OpenKeyNav = {
currentHeadingIndex: 0,
keys: {
heading: 'h',
},
headings: [],
};
*/
e.preventDefault(); // Prevent default action to allow our custom behavior
(0, _focus.focusOnHeadings)(openKeyNav, 'h1, h2, h3, h4, h5, h6', e);
return true;
break;
case openKeyNav.config.keys.scroll.toLowerCase():
/*
const OpenKeyNav = {
currentScrollableIndex: 0,
keys: {
scroll: 's',
},
scrollables: [],
};
*/
e.preventDefault();
(0, _focus.focusOnScrollables)(openKeyNav, e);
return true;
break;
default:
break;
}
// handle keycodes, aka for specific headings
var numberMap = {
Digit1: '1',
Digit2: '2',
Digit3: '3',
Digit4: '4',
Digit5: '5',
Digit6: '6',
Digit7: '7',
Digit8: '8',
Digit9: '9',
Digit0: '0'
};
if (e.code) {
// a number was pressed
var numberPressed = numberMap[e.code];
switch (numberPressed) {
case openKeyNav.config.keys.heading_1:
e.preventDefault(); // Prevent default action to allow our custom behavior
(0, _focus.focusOnHeadings)(openKeyNav, 'h1', e);
break;
case openKeyNav.config.keys.heading_2:
e.preventDefault(); // Prevent default action to allow our custom behavior
(0, _focus.focusOnHeadings)(openKeyNav, 'h2', e);
break;
case openKeyNav.config.keys.heading_3:
e.preventDefault(); // Prevent default action to allow our custom behavior
(0, _focus.focusOnHeadings)(openKeyNav, 'h3', e);
break;
case openKeyNav.config.keys.heading_4:
e.preventDefault(); // Prevent default action to allow our custom behavior
(0, _focus.focusOnHeadings)(openKeyNav, 'h4', e);
break;
case openKeyNav.config.keys.heading_5:
e.preventDefault(); // Prevent default action to allow our custom behavior
(0, _focus.focusOnHeadings)(openKeyNav, 'h5', e);
break;
case openKeyNav.config.keys.heading_6:
e.preventDefault(); // Prevent default action to allow our custom behavior
(0, _focus.focusOnHeadings)(openKeyNav, 'h6', e);
break;
default:
break;
}
}
};
var handleClickMode = function handleClickMode(openKeyNav, e) {
e.preventDefault();
openKeyNav.config.typedLabel.value += e.key.toLowerCase();
var target = document.querySelector("[data-openkeynav-label=\"".concat(openKeyNav.config.typedLabel.value, "\"]"));
if (!target) {
document.querySelectorAll('iframe').forEach(function (iframe) {
addKeydownEventListenerToIframe(openKeyNav, iframe);
});
}
if (target) {
setTimeout(function () {
(0, _clicking.handleTargetClickInteraction)(openKeyNav, target, e);
}, 0);
} else {
(0, _keylabels.filterRemainingOverlays)(openKeyNav, e);
return false;
}
return true;
};
var handleMoveMode = function handleMoveMode(openKeyNav, e) {
var showMoveableToOverlays = function showMoveableToOverlays(selectedMoveable) {
// temporarily persist modifier
var modifer = openKeyNav.config.modesConfig.move.modifier;
// Remove existing overlays or switch to target overlays
openKeyNav.removeOverlays();
// Set moving mode and selected moveable element
openKeyNav.config.modes.moving.value = true;
openKeyNav.config.modesConfig.move.selectedMoveable = selectedMoveable;
openKeyNav.config.modesConfig.move.selectedMoveableHTML = selectedMoveable.innerHTML;
openKeyNav.config.modesConfig.move.modifier = modifer;
// Get the configuration index from the selected moveable
var configIndex = selectedMoveable.getAttribute('data-openkeynav-moveconfig');
if (configIndex === null) return;
// Convert the index to a number
var configKeyForSelectedMoveable = parseInt(configIndex, 10);
// Store the selected configuration index
openKeyNav.config.modesConfig.move.selectedConfig = configKeyForSelectedMoveable;
// Find the corresponding move configuration
var moveConfig = openKeyNav.config.modesConfig.move.config[configKeyForSelectedMoveable];
if (!moveConfig) return;
// Get all target elements for the selectedMoveable
// let targetElements = document.querySelectorAll(moveConfig.toElements);
// targetElements = targetElements.filter(el => {
// return isTabbable(el, openKeyNav);
// });
function tabbableFilter(openKeyNav) {
return function (el) {
return (0, _isTabbable.isTabbable)(el, openKeyNav);
};
}
var targetElements = [].filter.call(document.querySelectorAll(moveConfig.toElements), tabbableFilter(openKeyNav));
// Generate labels for the target elements
var labels = (0, _keylabels.generateLabels)(openKeyNav, targetElements.length);
targetElements.forEach(function (element, index) {
element.setAttribute('data-openkeynav-label', labels[index]);
});
targetElements.forEach(function (element, index) {
if (!openKeyNav.isNonzeroSize(element)) return;
openKeyNav.createOverlay(element, labels[index]);
element.setAttribute('data-openkeynav-dropzone', 'true');
});
};
function findMatchingElements(queryString) {
return Array.from(document.querySelectorAll(queryString));
}
function findElementWithQuery(startElement, queryString) {
var direction = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'next';
var currentElement = startElement;
while (currentElement) {
if (direction === 'previous') {
// Traverse previous siblings and their descendants
currentElement = currentElement.previousElementSibling;
while (!currentElement && startElement.parentElement) {
currentElement = startElement.parentElement.previousElementSibling;
startElement = startElement.parentElement;
}
if (currentElement) {
// Look for the last matching descendant
var descendants = currentElement.querySelectorAll(queryString);
if (descendants.length > 0) {
return descendants[descendants.length - 1];
}
if (currentElement.matches(queryString)) {
return currentElement;
}
}
} else if (direction === 'next') {
// Traverse next siblings and their descendants
currentElement = currentElement.nextElementSibling;
while (!currentElement && startElement.parentElement) {
currentElement = startElement.parentElement.nextElementSibling;
startElement = startElement.parentElement;
}
if (currentElement) {
if (currentElement.matches(queryString)) {
return currentElement;
}
var foundElement = currentElement.querySelector(queryString);
if (foundElement) {
return foundElement;
}
}
} else {
throw new Error("Invalid direction. Use 'previous' or 'next'.");
}
}
return null;
}
function cycleThroughMoveTargets(event) {
event.preventDefault();
var direction = 'next';
if (event.shiftKey) {
direction = 'previous';
}
// the moveable element should be stored as openKeyNav.config.modesConfig.move.selectedMoveable
return findElementWithQuery(openKeyNav.config.modesConfig.move.selectedMoveable, '[data-openkeynav-label]:not(.openKeyNav-label)', direction);
}
// in moving mode
// Handle typing in move mode, similar to how you handle clicking mode
// Accumulate typed characters as in labeling mode
// ensure the typed key is valid label candidate (aka not something like )
// e.key/.
var validLabelChars = (0, _keylabels.generateValidKeyChars)(openKeyNav);
var isValidLabelChar = Array.from(validLabelChars).some(function (validChar) {
return validChar.toLowerCase() == e.key.toLowerCase();
});
var selectedTarget;
if (isValidLabelChar) {
openKeyNav.config.typedLabel.value += e.key.toLowerCase();
selectedTarget = document.querySelector("[data-openkeynav-label=\"".concat(openKeyNav.config.typedLabel.value, "\"]:not(.openKeyNav-label)"));
} else {
// tab-based moving
if (e.key === "Tab") {
var queryString = '.openKeyNav-label:not(.openKeyNav-label-selected)';
selectedTarget = cycleThroughMoveTargets(e);
}
}
if (!selectedTarget) {
// no selected target. filter remaining overlays and exit.
(0, _keylabels.filterRemainingOverlays)(openKeyNav, e);
return false;
}
if (!openKeyNav.config.modesConfig.move.selectedMoveable) {
// new selected target.
// setting selectedTarget as selectedMoveable
console.log("Selected element to move:", selectedTarget);
showMoveableToOverlays(selectedTarget);
(0, _dragAndDrop.beginDrag)(openKeyNav);
return true;
}
// moving selectedMoveable to target
moveSelectedMoveableToTarget(openKeyNav, selectedTarget);
return true;
};
var handleMenuMode = function handleMenuMode(e) {
return true;
};
var moveSelectedMoveableToTarget = function moveSelectedMoveableToTarget(openKeyNav, selectedTarget) {
// const modifier = true; // for whether move is sticky or not (sticky mode?)
console.log("Selected move target:", selectedTarget);
openKeyNav.config.modesConfig.move.selectedDropZone = selectedTarget;
var callback = openKeyNav.config.modesConfig.move.config[openKeyNav.config.modesConfig.move.selectedConfig].callback;
if (!callback) {
// console.error("No callback function has been set to execute this move operation");
(0, _dragAndDrop.simulateDragAndDrop)(openKeyNav, openKeyNav.config.modesConfig.move.selectedMoveable, openKeyNav.config.modesConfig.move.selectedDropZone);
} else {
openKeyNav.config.modesConfig.move.config[openKeyNav.config.modesConfig.move.selectedConfig].callback(openKeyNav.config.modesConfig.move.selectedMoveable, openKeyNav.config.modesConfig.move.selectedDropZone);
}
if (!openKeyNav.config.modesConfig.move.modifier) {
openKeyNav.removeOverlays(true);
openKeyNav.clearMoveAttributes();
}
return true;
};
var addKeydownEventListenerToIframe = function addKeydownEventListenerToIframe(openKeyNav, iframe) {
try {
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
var potentialTarget = iframeDoc.querySelector("[data-openkeynav-label=\"".concat(openKeyNav.config.typedLabel.value, "\"]"));
if (potentialTarget) {
// target = potentialTarget; TODO: check if this was important. Target is undefined so commenting out.
// Check if the keypress listener has already been added
if (!iframeDoc.keypressListenerAdded) {
var script = iframeDoc.createElement('script');
script.textContent = '' + "document.addEventListener('keydown', function(event) {" + 'window.parent.postMessage({' + "type: 'keydown'," + 'key: event.key,' + 'keyCode: event.keyCode,' + 'altKey: event.altKey,' + 'ctrlKey: event.ctrlKey,' + 'shiftKey: event.shiftKey,' + 'metaKey: event.metaKey' + "}, '*');" + '});' + 'document.keypressListenerAdded = true;'; // Set flag to true
iframeDoc.body.appendChild(script);
}
}
} catch (error) {
console.log('Error accessing iframe content', error);
}
};