@wordpress/components
Version:
UI components for WordPress.
129 lines (122 loc) • 4.46 kB
JavaScript
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { error } from '@wordpress/icons';
/**
* External dependencies
*/
import { cloneElement, forwardRef, useEffect, useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events';
import Icon from '../icon';
import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
function appendRequiredIndicator(label, required, markWhenOptional) {
if (required && !markWhenOptional) {
return /*#__PURE__*/_jsxs(_Fragment, {
children: [label, " ", `(${__('Required')})`]
});
}
if (!required && markWhenOptional) {
return /*#__PURE__*/_jsxs(_Fragment, {
children: [label, " ", `(${__('Optional')})`]
});
}
return label;
}
/**
* HTML elements that support the Constraint Validation API.
*
* Here, we exclude HTMLButtonElement because although it does technically support the API,
* normal buttons are actually exempted from any validation.
* @see https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/Form_validation
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLButtonElement/willValidate
*/
function UnforwardedControlWithError({
required,
markWhenOptional,
customValidator,
getValidityTarget,
children
}, forwardedRef) {
const [errorMessage, setErrorMessage] = useState();
const [isTouched, setIsTouched] = useState(false);
// Ensure that error messages are visible after user attemps to submit a form
// with multiple invalid fields.
useEffect(() => {
const validityTarget = getValidityTarget();
const showValidationMessage = () => setErrorMessage(validityTarget?.validationMessage);
validityTarget?.addEventListener('invalid', showValidationMessage);
return () => {
validityTarget?.removeEventListener('invalid', showValidationMessage);
};
});
const validate = () => {
const message = customValidator?.();
const validityTarget = getValidityTarget();
validityTarget?.setCustomValidity(message !== null && message !== void 0 ? message : '');
setErrorMessage(validityTarget?.validationMessage);
};
const onBlur = event => {
// Only consider "blurred from the component" if focus has fully left the wrapping div.
// This prevents unnecessary blurs from components with multiple focusable elements.
if (!event.relatedTarget || !event.currentTarget.contains(event.relatedTarget)) {
setIsTouched(true);
const validityTarget = getValidityTarget();
// Prevents a double flash of the native error tooltip when the control is already showing one.
if (!validityTarget?.validity.valid) {
if (!errorMessage) {
setErrorMessage(validityTarget?.validationMessage);
}
return;
}
validate();
}
};
const onChange = (...args) => {
children.props.onChange?.(...args);
// Only validate incrementally if the field has blurred at least once,
// or currently has an error message.
if (isTouched || errorMessage) {
validate();
}
};
const onKeyDown = event => {
// Ensures that custom validators are triggered when the user submits by pressing Enter,
// without ever blurring the control.
if (event.key === 'Enter') {
validate();
}
};
return (
/*#__PURE__*/
// Disable reason: Just listening to a bubbled event, not for interaction.
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
_jsxs("div", {
className: "components-validated-control",
ref: forwardedRef,
onBlur: onBlur,
onKeyDown: withIgnoreIMEEvents(onKeyDown),
children: [cloneElement(children, {
label: appendRequiredIndicator(children.props.label, required, markWhenOptional),
onChange,
required
}), /*#__PURE__*/_jsx("div", {
"aria-live": "polite",
children: errorMessage && /*#__PURE__*/_jsxs("p", {
className: "components-validated-control__error",
children: [/*#__PURE__*/_jsx(Icon, {
className: "components-validated-control__error-icon",
icon: error,
size: 16,
fill: "currentColor"
}), errorMessage]
})
})]
})
);
}
export const ControlWithError = forwardRef(UnforwardedControlWithError);
//# sourceMappingURL=control-with-error.js.map