@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
149 lines (148 loc) • 5.97 kB
JavaScript
/*! All material copyright ESRI, All Rights Reserved, unless otherwise specified.
See https://github.com/Esri/calcite-design-system/blob/dev/LICENSE.md for details.
v3.2.1 */
import { q as queryElementRoots, l as closestElementCrossShadowBoundary, m as isBefore } from "./dom.js";
const labelClickEvent = "calciteInternalLabelClick";
const labelConnectedEvent = "calciteInternalLabelConnected";
const labelDisconnectedEvent = "calciteInternalLabelDisconnected";
const labelTagName = "calcite-label";
const labelToLabelables = /* @__PURE__ */ new WeakMap();
const onLabelClickMap = /* @__PURE__ */ new WeakMap();
const onLabelConnectedMap = /* @__PURE__ */ new WeakMap();
const onLabelDisconnectedMap = /* @__PURE__ */ new WeakMap();
const unlabeledComponents = /* @__PURE__ */ new Set();
const findLabelForComponent = (componentEl) => {
const { id } = componentEl;
const forLabel = id && queryElementRoots(componentEl, { selector: `${labelTagName}[for="${id}"]` });
if (forLabel) {
return forLabel;
}
const parentLabel = closestElementCrossShadowBoundary(componentEl, labelTagName);
if (!parentLabel || // labelable components within other custom elements are not considered labelable
hasAncestorCustomElements(parentLabel, componentEl)) {
return null;
}
return parentLabel;
};
function hasAncestorCustomElements(label, componentEl) {
let traversedElements;
const customElementAncestorCheckEventType = "custom-element-ancestor-check";
const listener = (event) => {
event.stopImmediatePropagation();
const composedPath = event.composedPath();
traversedElements = composedPath.slice(composedPath.indexOf(componentEl), composedPath.indexOf(label));
};
label.addEventListener(customElementAncestorCheckEventType, listener, { once: true });
componentEl.dispatchEvent(new CustomEvent(customElementAncestorCheckEventType, { composed: true, bubbles: true }));
label.removeEventListener(customElementAncestorCheckEventType, listener);
const ancestorCustomElements = traversedElements.filter((el) => el !== componentEl && el !== label).filter((el) => el.tagName?.includes("-"));
return ancestorCustomElements.length > 0;
}
function connectLabel(component) {
if (!component) {
return;
}
const labelEl = findLabelForComponent(component.el);
if (onLabelClickMap.has(labelEl) && labelEl === component.labelEl || !labelEl && unlabeledComponents.has(component)) {
return;
}
const boundOnLabelDisconnected = onLabelDisconnected.bind(component);
if (labelEl) {
component.labelEl = labelEl;
const labelables = labelToLabelables.get(labelEl) || [];
labelables.push(component);
labelToLabelables.set(labelEl, labelables.sort(sortByDOMOrder));
if (!onLabelClickMap.has(component.labelEl)) {
onLabelClickMap.set(component.labelEl, onLabelClick);
component.labelEl.addEventListener(labelClickEvent, onLabelClick);
}
unlabeledComponents.delete(component);
document.removeEventListener(labelConnectedEvent, onLabelConnectedMap.get(component));
onLabelDisconnectedMap.set(component, boundOnLabelDisconnected);
document.addEventListener(labelDisconnectedEvent, boundOnLabelDisconnected);
} else if (!unlabeledComponents.has(component)) {
boundOnLabelDisconnected();
document.removeEventListener(labelDisconnectedEvent, onLabelDisconnectedMap.get(component));
}
}
function disconnectLabel(component) {
if (!component) {
return;
}
unlabeledComponents.delete(component);
document.removeEventListener(labelConnectedEvent, onLabelConnectedMap.get(component));
document.removeEventListener(labelDisconnectedEvent, onLabelDisconnectedMap.get(component));
onLabelConnectedMap.delete(component);
onLabelDisconnectedMap.delete(component);
if (!component.labelEl) {
return;
}
const labelables = labelToLabelables.get(component.labelEl);
if (labelables.length === 1) {
component.labelEl.removeEventListener(labelClickEvent, onLabelClickMap.get(component.labelEl));
onLabelClickMap.delete(component.labelEl);
}
labelToLabelables.set(
component.labelEl,
labelables.filter((labelable) => labelable !== component).sort(sortByDOMOrder)
);
component.labelEl = null;
}
function sortByDOMOrder(a, b) {
return isBefore(a.el, b.el) ? -1 : 1;
}
function getLabelText(component) {
return component.label || component.labelEl?.textContent?.trim() || "";
}
function onLabelClick(event) {
const labelClickTarget = event.detail.sourceEvent.target;
const labelables = labelToLabelables.get(this);
const clickedLabelable = labelables.find((labelable) => labelable.el === labelClickTarget);
const labelableChildClicked = labelables.includes(clickedLabelable);
if (labelableChildClicked) {
return;
}
const firstLabelable = labelables[0];
if (firstLabelable.disabled) {
return;
}
firstLabelable.onLabelClick(event);
}
function onLabelConnected() {
if (unlabeledComponents.has(this)) {
connectLabel(this);
}
}
function onLabelDisconnected() {
unlabeledComponents.add(this);
const boundOnLabelConnected = onLabelConnectedMap.get(this) || onLabelConnected.bind(this);
onLabelConnectedMap.set(this, boundOnLabelConnected);
document.addEventListener(labelConnectedEvent, boundOnLabelConnected);
}
async function associateExplicitLabelToUnlabeledComponent(label) {
await label.componentOnReady();
const alreadyLabeled = labelToLabelables.has(label);
if (alreadyLabeled) {
return;
}
const forComponentEl = label.ownerDocument?.getElementById(label.for);
if (!forComponentEl) {
return;
}
requestAnimationFrame(() => {
for (const labelable of unlabeledComponents) {
if (labelable.el === forComponentEl) {
connectLabel(labelable);
break;
}
}
});
}
export {
associateExplicitLabelToUnlabeledComponent as a,
labelDisconnectedEvent as b,
connectLabel as c,
disconnectLabel as d,
getLabelText as g,
labelConnectedEvent as l
};