@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
143 lines (139 loc) • 5.46 kB
JavaScript
/*!
* All material copyright ESRI, All Rights Reserved, unless otherwise specified.
* See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
* v1.5.0-next.4
*/
;
const browser = require('./browser-28ea2ce1.js');
// ⚠️ browser-sniffing is not a best practice and should be avoided ⚠️
const isFirefox = /firefox/i.test(browser.getUserAgentString());
const interactiveElementToParent = isFirefox
? new WeakMap()
: null;
function interceptedClick() {
const { disabled } = this;
if (!disabled) {
HTMLElement.prototype.click.call(this);
}
}
function onPointerDown(event) {
const interactiveElement = event.target;
if (isFirefox && !interactiveElementToParent.get(interactiveElement)) {
return;
}
const { disabled } = interactiveElement;
if (disabled) {
// prevent click from moving focus on host
event.preventDefault();
}
}
const nonBubblingWhenDisabledMouseEvents = ["mousedown", "mouseup", "click"];
function onNonBubblingWhenDisabledMouseEvent(event) {
if (isFirefox && !interactiveElementToParent.get(event.target)) {
return;
}
const { disabled } = event.target;
// prevent disallowed mouse events from being emitted on the disabled host (per https://github.com/whatwg/html/issues/5886)
//⚠ we generally avoid stopping propagation of events, but this is needed to adhere to the intended spec changes above ⚠
if (disabled) {
event.stopImmediatePropagation();
event.preventDefault();
}
}
const captureOnlyOptions = { capture: true };
/**
* This helper updates the host element to prevent keyboard interaction on its subtree and sets the appropriate aria attribute for accessibility.
*
* This should be used in the `componentDidRender` lifecycle hook.
*
* **Notes**
*
* this util is not needed for simple components whose root element or elements are an interactive component (custom element or native control). For those cases, set the `disabled` props on the root components instead.
* technically, users can override `tabindex` and restore keyboard navigation, but this will be considered user error
*
* @param component
* @param hostIsTabbable
*/
function updateHostInteraction(component, hostIsTabbable = false) {
if (component.disabled) {
component.el.setAttribute("tabindex", "-1");
component.el.setAttribute("aria-disabled", "true");
if (component.el.contains(document.activeElement)) {
document.activeElement.blur();
}
blockInteraction(component);
return;
}
restoreInteraction(component);
if (typeof hostIsTabbable === "function") {
component.el.setAttribute("tabindex", hostIsTabbable.call(component) ? "0" : "-1");
}
else if (hostIsTabbable === true) {
component.el.setAttribute("tabindex", "0");
}
else if (hostIsTabbable === false) {
component.el.removeAttribute("tabindex");
}
else ;
component.el.removeAttribute("aria-disabled");
}
function blockInteraction(component) {
component.el.click = interceptedClick;
addInteractionListeners(isFirefox ? getParentElement(component) : component.el);
}
function addInteractionListeners(element) {
if (!element) {
// this path is only applicable to Firefox
return;
}
element.addEventListener("pointerdown", onPointerDown, captureOnlyOptions);
nonBubblingWhenDisabledMouseEvents.forEach((event) => element.addEventListener(event, onNonBubblingWhenDisabledMouseEvent, captureOnlyOptions));
}
function getParentElement(component) {
return interactiveElementToParent.get(component.el);
}
function restoreInteraction(component) {
delete component.el.click; // fallback on HTMLElement.prototype.click
removeInteractionListeners(isFirefox ? getParentElement(component) : component.el);
}
function removeInteractionListeners(element) {
if (!element) {
// this path is only applicable to Firefox
return;
}
element.removeEventListener("pointerdown", onPointerDown, captureOnlyOptions);
nonBubblingWhenDisabledMouseEvents.forEach((event) => element.removeEventListener(event, onNonBubblingWhenDisabledMouseEvent, captureOnlyOptions));
}
/**
* This utility helps disable components consistently in Firefox.
*
* It needs to be called in `connectedCallback` and is only needed for Firefox as it does not call capture event listeners before non-capture ones (see https://bugzilla.mozilla.org/show_bug.cgi?id=1731504).
*
* @param component
*/
function connectInteractive(component) {
if (!component.disabled || !isFirefox) {
return;
}
const parent = component.el.parentElement || component.el; /* assume element is host if it has no parent when connected */
interactiveElementToParent.set(component.el, parent);
blockInteraction(component);
}
/**
* This utility restores interactivity to disabled components consistently in Firefox.
*
* It needs to be called in `disconnectedCallback` and is only needed for Firefox as it does not call capture event listeners before non-capture ones (see https://bugzilla.mozilla.org/show_bug.cgi?id=1731504).
*
* @param component
*/
function disconnectInteractive(component) {
if (!isFirefox) {
return;
}
// always remove on disconnect as render or connect will restore it
interactiveElementToParent.delete(component.el);
restoreInteraction(component);
}
exports.connectInteractive = connectInteractive;
exports.disconnectInteractive = disconnectInteractive;
exports.updateHostInteraction = updateHostInteraction;