blob: 7d1a38c5c8e1f5028b26418b3631a037e0017b8e [file] [log] [blame]
"use strict";
const FocusEvent = require("../generated/FocusEvent.js");
const idlUtils = require("../generated/utils.js");
const { isDisabled } = require("./form-controls.js");
const { firstChildWithLocalName } = require("./traversal");
const { createAnEvent } = require("./events");
const { HTML_NS, SVG_NS } = require("./namespaces");
const { isRenderedElement } = require("./svg/render");
const focusableFormElements = new Set(["input", "select", "textarea", "button"]);
// https://html.spec.whatwg.org/multipage/interaction.html#focusable-area, but also some of
// https://html.spec.whatwg.org/multipage/interaction.html#focusing-steps and some of
// https://svgwg.org/svg2-draft/interact.html#TermFocusable
exports.isFocusableAreaElement = elImpl => {
// We implemented most of the suggested focusable elements found here:
// https://html.spec.whatwg.org/multipage/interaction.html#tabindex-value
// However, some suggested elements are not focusable in web browsers, as detailed here:
// https://github.com/whatwg/html/issues/5490
if (elImpl._namespaceURI === HTML_NS) {
if (!elImpl._ownerDocument._defaultView) {
return false;
}
if (!elImpl.isConnected) {
return false;
}
if (!Number.isNaN(parseInt(elImpl.getAttributeNS(null, "tabindex")))) {
return true;
}
if (elImpl._localName === "iframe") {
return true;
}
if (elImpl._localName === "a" && elImpl.hasAttributeNS(null, "href")) {
return true;
}
if (elImpl._localName === "summary" && elImpl.parentNode &&
elImpl.parentNode._localName === "details" &&
elImpl === firstChildWithLocalName(elImpl.parentNode, "summary")) {
return true;
}
if (focusableFormElements.has(elImpl._localName) && !isDisabled(elImpl)) {
if (elImpl._localName === "input" && elImpl.type === "hidden") {
return false;
}
return true;
}
if (elImpl.hasAttributeNS(null, "contenteditable")) {
return true;
}
return false;
// This does not check for a designMode Document as specified in
// https://html.spec.whatwg.org/multipage/interaction.html#editing-host because the designMode
// attribute is not implemented.
}
if (elImpl._namespaceURI === SVG_NS) {
if (!Number.isNaN(parseInt(elImpl.getAttributeNS(null, "tabindex"))) && isRenderedElement(elImpl)) {
return true;
}
if (elImpl._localName === "a" && elImpl.hasAttributeNS(null, "href")) {
return true;
}
return false;
}
return false;
};
// https://html.spec.whatwg.org/multipage/interaction.html#fire-a-focus-event plus the steps of
// https://html.spec.whatwg.org/multipage/interaction.html#focus-update-steps that adjust Documents to Windows
// It's extended with the bubbles option to also handle focusin/focusout, which are "defined" in
// https://w3c.github.io/uievents/#event-type-focusin. See https://github.com/whatwg/html/issues/3514.
exports.fireFocusEventWithTargetAdjustment = (name, target, relatedTarget, { bubbles = false } = {}) => {
if (target === null) {
// E.g. firing blur with nothing previously focused.
return;
}
const event = createAnEvent(name, target._globalObject, FocusEvent, {
bubbles,
composed: true,
relatedTarget,
view: target._ownerDocument._defaultView,
detail: 0
});
if (target._defaultView) {
target = idlUtils.implForWrapper(target._defaultView);
}
target._dispatch(event);
};