UNPKG

@atlaskit/form

Version:

A form allows people to input information.

258 lines (249 loc) 10.3 kB
/* field.tsx generated by @compiled/babel-plugin v0.39.1 */ import "./field.compiled.css"; import { ax, ix } from "@compiled/react/runtime"; import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { useId } from '@atlaskit/ds-lib/use-id'; import { FieldId } from './field-id-context'; import { FormContext, IsDisabledContext } from './form'; import { Label } from './label'; import { ErrorMessage, HelperMessage, MessageWrapper, ValidMessage } from './messages'; import RequiredAsterisk from './required-asterisk'; const fieldWrapperStyles = null; function isEvent(event) { return Boolean(event && event.target); } function isFunction(x) { return typeof x === 'function'; } // Must be exported to satisfy error TS4023 from Jira builds // src/packages/servicedesk/virtual-agent/common/src/ui/base-text-field/index.tsx(10,14): // error TS4023: Exported variable `BaseTextField` has or is using name // `FieldComponentProps` from external module // `/opt/atlassian/pipelines/agent/build/jira/tsDist/@atlaskit__form/app/src/field` // but cannot be named. function usePreviousRef(current) { const ref = useRef(current); // will be updated on the next render useEffect(() => { ref.current = current; }); // return the existing current (pre render) return ref; } function isShallowEqual(previousValue, currentValue) { if (previousValue === currentValue) { return true; } // not checking functions if (typeof previousValue === 'function' && typeof currentValue === 'function') { return true; } if (Array.isArray(previousValue) && Array.isArray(currentValue)) { return JSON.stringify(previousValue) === JSON.stringify(currentValue); } if (typeof previousValue === 'object' && typeof currentValue === 'object') { return JSON.stringify(previousValue) === JSON.stringify(currentValue); } return false; } export default function Field(props) { var _getCurrentValue; const { children = () => null, component } = props; const { registerField, getCurrentValue } = useContext(FormContext); const isDisabled = useContext(IsDisabledContext) || props.isDisabled || false; const defaultValue = isFunction(props.defaultValue) ? props.defaultValue() : props.defaultValue; const latestPropsRef = usePreviousRef(props); /** * HACK: defaultValue can potentially be an array or object which cannot be * passed directly into a `useEffect` dependency array, since it will trigger * the hook every time. */ const isDefaultValueChanged = !isShallowEqual(latestPropsRef.current.defaultValue, props.defaultValue); const [state, setState] = useState({ fieldProps: { onChange: () => {}, onBlur: () => {}, onFocus: () => {}, /* Previously, defaultValue was being set as undefined in Field.defaultProps, which * effectively made it an optional prop to external consumers of Field. However the * prop types defined defaultValue as required, so inside the component it was not * valid for defaultValue to be undefined. We need to suppress the error * after changing defaultValue to explicitly be an optional prop. * If default value has changed we are using new default value. * Otherwise we need to check if we already have value for this field * (because we are using changing key prop to re-run field level validation, and that * cause the component re-mounting) to not override the actual value with the default value. */ // @ts-ignore value: isDefaultValueChanged ? defaultValue : (_getCurrentValue = getCurrentValue(props.name)) !== null && _getCurrentValue !== void 0 ? _getCurrentValue : defaultValue }, error: undefined, valid: false, meta: { dirty: false, dirtySinceLastSubmit: false, touched: false, valid: false, validating: false, submitting: false, submitFailed: false, error: undefined, submitError: undefined } }); const latestStateRef = usePreviousRef(state); useEffect(() => { function fieldStateToMeta(value = {}) { return { dirty: value.dirty || false, dirtySinceLastSubmit: value.dirtySinceLastSubmit || false, touched: value.touched || false, valid: value.valid || false, submitting: value.submitting || false, submitFailed: value.submitFailed || false, error: value.error, submitError: value.submitError, validating: !!value.validating }; } const unregister = registerField(latestPropsRef.current.name, /** * Similar as for setting initial state value. * Additionally we are checking if the default value is a function, * it is used in checkbox fields, where fields with same name and * defaultIsChecked should create array of values. In this situation we can't * override the default value on re-registering, but also we don't need to change * the key prop to re-run validation. */ // @ts-ignore isDefaultValueChanged || // @ts-ignore isFunction(latestPropsRef.current.defaultValue) ? latestPropsRef.current.defaultValue : latestStateRef.current.fieldProps.value, fieldState => { /** * Do not update dirtySinceLastSubmit until submission has finished. */ const modifiedDirtySinceLastSubmit = fieldState.submitting ? latestStateRef.current.meta.dirtySinceLastSubmit : fieldState.dirtySinceLastSubmit; /** * Do not update submitFailed until submission has finished. */ const modifiedSubmitFailed = fieldState.submitting ? latestStateRef.current.meta.submitFailed : fieldState.submitFailed; /** * Do not use submitError if the value has changed. */ const modifiedSubmitError = modifiedDirtySinceLastSubmit && latestPropsRef.current.validate ? undefined : fieldState.submitError; const modifiedError = modifiedSubmitError || (fieldState.touched || fieldState.dirty) && fieldState.error; /** * If there has been a submit error, then use logic in modifiedError to determine validity, * so we can determine when there is a submit error which we do not want to display * because the value has been changed. */ const modifiedValid = modifiedSubmitFailed ? modifiedError === undefined : fieldState.valid; function getTransform(eventOrValue, currentValue) { if (latestPropsRef.current.transform) { return latestPropsRef.current.transform(eventOrValue, currentValue); } if (isEvent(eventOrValue)) { const { currentTarget } = eventOrValue; if (currentTarget.type === 'checkbox') { if (currentTarget.checked) { return currentTarget.value || true; } return currentTarget.value ? undefined : false; } else if (currentTarget) { return currentTarget.value; } return; } else { return eventOrValue; } } setState({ fieldProps: { onChange: e => { fieldState.change(getTransform(e, fieldState.value)); }, onBlur: fieldState.blur, onFocus: fieldState.focus, value: fieldState.value }, error: modifiedError || undefined, /** * The following parameters are optionally typed in final-form to indicate that not all parameters need * to be subscribed to. We cast them as booleans (using || false), since this is what they are semantically. */ valid: modifiedValid || false, meta: fieldStateToMeta(fieldState) }); }, { dirty: true, dirtySinceLastSubmit: true, touched: true, valid: true, submitting: true, submitFailed: true, value: true, error: true, submitError: true, validating: true }, { getValidator: () => function validate(value, formState, fieldState) { const supplied = latestPropsRef.current.validate; if (supplied && fieldState) { return supplied(value, formState, fieldStateToMeta(fieldState)); } } }); return unregister; }, [latestPropsRef, latestStateRef, registerField, props.name, props.isRequired, isDefaultValueChanged]); const uid = useId(); const fieldId = useMemo(() => { return props.id ? props.id : `${props.name}-${uid}`; }, [props.id, props.name, uid]); const getDescribedBy = () => { let value = ''; if (state.error) { value += `${fieldId}-error `; } if (state.valid) { value += `${fieldId}-valid `; } return `${value}${fieldId}-helper`; }; const extendedFieldProps = { ...state.fieldProps, name: props.name, isDisabled, isInvalid: Boolean(state.error), isRequired: Boolean(props.isRequired), 'aria-required': String(Boolean(props.isRequired)), 'aria-invalid': state.error ? 'true' : 'false', 'aria-describedby': getDescribedBy(), 'aria-labelledby': `${fieldId}-label`, id: fieldId }; return /*#__PURE__*/React.createElement("div", { "data-testid": props.testId, className: ax(["_1pfhu2gc"]) }, props.label && /*#__PURE__*/React.createElement(Label, { htmlFor: fieldId, id: `${fieldId}-label`, testId: props.testId && `${props.testId}--label` }, props.label, props.isRequired && /*#__PURE__*/React.createElement(RequiredAsterisk, null), props.elementAfterLabel), /*#__PURE__*/React.createElement(FieldId.Provider, { value: fieldId }, component ? /*#__PURE__*/React.createElement(React.Fragment, null, component({ fieldProps: extendedFieldProps }), /*#__PURE__*/React.createElement(MessageWrapper, null, props.helperMessage && /*#__PURE__*/React.createElement(HelperMessage, null, props.helperMessage), state.error && /*#__PURE__*/React.createElement(ErrorMessage, null, props.errorMessage || state.error), state.meta.touched && state.valid && props.validMessage && /*#__PURE__*/React.createElement(ValidMessage, null, props.validMessage))) : children({ fieldProps: extendedFieldProps, error: state.error, valid: state.valid, meta: state.meta }))); }