@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
270 lines (269 loc) • 9.09 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 { html, isServer } from "lit";
import { q as queryElementRoots, l as closestElementCrossShadowBoundary } from "./dom.js";
const componentsWithInputEvent = [
"calcite-input",
"calcite-input-number",
"calcite-input-text",
"calcite-text-area"
];
function getClearValidationEventName(componentTag) {
const componentTagCamelCase = componentTag.split("-").map((part, index) => index === 0 ? part : `${part[0].toUpperCase()}${part.slice(1)}`).join("");
const clearValidationEvent = `${componentTagCamelCase}${componentsWithInputEvent.includes(componentTag) ? "Input" : "Change"}`;
return clearValidationEvent;
}
const hiddenFormInputSlotName = "hidden-form-input";
function isCheckable(component) {
return "checked" in component;
}
const onFormResetMap = /* @__PURE__ */ new WeakMap();
const formComponentSet = /* @__PURE__ */ new WeakSet();
function hasRegisteredFormComponentParent(form, formComponentEl) {
const hasParentComponentWithFormIdSet = closestElementCrossShadowBoundary(formComponentEl.parentElement, "[form]");
if (hasParentComponentWithFormIdSet) {
return true;
}
const formComponentRegisterEventName = "calciteInternalFormComponentRegister";
let hasRegisteredFormComponentParent2 = false;
form.addEventListener(formComponentRegisterEventName, (event) => {
hasRegisteredFormComponentParent2 = event.composedPath().some((element) => formComponentSet.has(element));
event.stopPropagation();
}, { once: true });
formComponentEl.dispatchEvent(new CustomEvent(formComponentRegisterEventName, {
bubbles: true,
composed: true
}));
return hasRegisteredFormComponentParent2;
}
function displayValidationMessage(component, { status, message, icon }) {
if ("status" in component) {
component.status = status;
}
if ("validationIcon" in component && typeof component.validationIcon !== "string") {
component.validationIcon = icon;
}
if ("validationMessage" in component && !component.validationMessage) {
component.validationMessage = message;
}
}
function getValidationComponent(el) {
if (el.nodeName === "CALCITE-RADIO-BUTTON") {
return closestElementCrossShadowBoundary(el, "calcite-radio-button-group");
}
return el;
}
const invalidEvent = new CustomEvent("calciteInvalid", { bubbles: true, composed: true });
function invalidHandler(event) {
const hiddenInput = event?.target;
const hiddenInputMessage = hiddenInput?.validationMessage;
const formComponent = getValidationComponent(hiddenInput?.parentElement);
if (!formComponent) {
return;
}
const componentTag = formComponent?.nodeName?.toLowerCase();
const componentTagParts = componentTag?.split("-");
if (componentTagParts.length < 2 || componentTagParts[0] !== "calcite") {
return;
}
event?.preventDefault();
if ("validity" in formComponent) {
formComponent.validity = hiddenInput?.validity;
}
formComponent.dispatchEvent(invalidEvent);
displayValidationMessage(formComponent, {
message: hiddenInputMessage,
icon: true,
status: "invalid"
});
const clearValidationEvent = getClearValidationEventName(componentTag);
formComponent.addEventListener(clearValidationEvent, () => {
if ("status" in formComponent) {
formComponent.status = "idle";
}
if ("validationIcon" in formComponent && (formComponent.validationIcon === "" || formComponent.validationIcon === true)) {
formComponent.validationIcon = false;
}
if ("validationMessage" in formComponent && formComponent.validationMessage === hiddenInputMessage) {
formComponent.validationMessage = "";
}
if ("validity" in formComponent) {
formComponent.validity = hiddenInput?.validity;
}
}, { once: true });
}
function submitForm(component) {
const { formEl } = component;
if (!formEl) {
return false;
}
formEl.addEventListener("invalid", invalidHandler, true);
formEl.requestSubmit();
formEl.removeEventListener("invalid", invalidHandler, true);
requestAnimationFrame(() => {
const invalidEls = formEl.querySelectorAll("[status=invalid]");
for (const el of invalidEls) {
if (el?.validationMessage) {
el?.setFocus();
break;
}
}
});
return true;
}
function resetForm(component) {
component.formEl?.reset();
}
function connectForm(component) {
const { el, value } = component;
const associatedForm = findAssociatedForm(component);
if (!associatedForm || hasRegisteredFormComponentParent(associatedForm, el)) {
return;
}
component.formEl = associatedForm;
component.defaultValue = value;
if (isCheckable(component)) {
component.defaultChecked = component.checked;
}
const boundOnFormReset = onFormReset.bind(component);
associatedForm.addEventListener("reset", boundOnFormReset);
onFormResetMap.set(component.el, boundOnFormReset);
formComponentSet.add(el);
}
function findAssociatedForm(component) {
const { el, form } = component;
return form ? queryElementRoots(el, { id: form }) : closestElementCrossShadowBoundary(el, "form");
}
function onFormReset() {
if ("status" in this) {
this.status = "idle";
}
if ("validationIcon" in this) {
this.validationIcon = false;
}
if ("validationMessage" in this) {
this.validationMessage = "";
}
if (isCheckable(this)) {
this.checked = this.defaultChecked;
return;
}
this.value = this.defaultValue;
this.onFormReset?.();
}
function disconnectForm(component) {
const { el, formEl } = component;
if (!formEl) {
return;
}
const boundOnFormReset = onFormResetMap.get(el);
formEl.removeEventListener("reset", boundOnFormReset);
onFormResetMap.delete(el);
component.formEl = null;
formComponentSet.delete(el);
}
function afterConnectDefaultValueSet(component, value) {
component.defaultValue = value;
}
const internalHiddenInputInputEvent = "calciteInternalHiddenInputInput";
const hiddenInputInputHandler = (event) => {
event.target.dispatchEvent(new CustomEvent(internalHiddenInputInputEvent, { bubbles: true }));
};
const removeHiddenInputChangeEventListener = (input) => input.removeEventListener("input", hiddenInputInputHandler);
function syncHiddenFormInput(component) {
const { el, formEl, name, value } = component;
const { ownerDocument } = el;
if (isServer) {
return;
}
const inputs = el.querySelectorAll(`input[slot="${hiddenFormInputSlotName}"]`);
if (!formEl || !name) {
inputs.forEach((input) => {
removeHiddenInputChangeEventListener(input);
input.remove();
});
return;
}
const values = Array.isArray(value) ? value : [value];
const extra = [];
const seen = /* @__PURE__ */ new Set();
inputs.forEach((input) => {
const valueMatch = values.find((val) => (
/* intentional non-strict equality check */
val == input.value
));
if (valueMatch != null) {
seen.add(valueMatch);
defaultSyncHiddenFormInput(component, input, valueMatch);
} else {
extra.push(input);
}
});
let docFrag;
values.forEach((value2) => {
if (seen.has(value2)) {
return;
}
let input = extra.pop();
if (!input) {
input = ownerDocument.createElement("input");
input.ariaHidden = "true";
input.slot = hiddenFormInputSlotName;
}
if (!docFrag) {
docFrag = ownerDocument.createDocumentFragment();
}
docFrag.append(input);
input.addEventListener("input", hiddenInputInputHandler);
defaultSyncHiddenFormInput(component, input, value2);
});
if (docFrag) {
el.append(docFrag);
}
extra.forEach((input) => {
removeHiddenInputChangeEventListener(input);
input.remove();
});
}
function defaultSyncHiddenFormInput(component, input, value) {
const { defaultValue, disabled, form, name, required } = component;
input.defaultValue = defaultValue;
input.disabled = disabled;
input.name = name;
input.required = required;
input.tabIndex = -1;
if (form) {
input.setAttribute("form", form);
} else {
input.removeAttribute("form");
}
if (isCheckable(component)) {
input.checked = component.checked;
input.defaultChecked = component.defaultChecked;
input.value = component.checked ? value || "on" : "";
} else {
input.value = value || "";
}
component.syncHiddenFormInput?.(input);
const validationComponent = getValidationComponent(component.el);
if (validationComponent && "validity" in validationComponent) {
for (const key in { ...input?.validity }) {
validationComponent.validity[key] = input.validity[key];
}
}
}
const HiddenFormInputSlot = ({ component }) => {
syncHiddenFormInput(component);
return html`<slot name=${hiddenFormInputSlotName}></slot>`;
};
export {
HiddenFormInputSlot as H,
afterConnectDefaultValueSet as a,
connectForm as c,
disconnectForm as d,
findAssociatedForm as f,
internalHiddenInputInputEvent as i,
resetForm as r,
submitForm as s
};