UNPKG

@wordpress/components

Version:
129 lines (122 loc) 4.46 kB
/** * 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