UNPKG

@capgeminiuk/dcx-react-library

Version:

[![CircleCI](https://circleci.com/gh/Capgemini/dcx-react-library.svg?style=svg)](https://circleci.com/gh/Capgemini/dcx-react-library)

1,559 lines (1,528 loc) 112 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('@cesium133/forgjs'), require('imask')) : typeof define === 'function' && define.amd ? define(['exports', 'react', '@cesium133/forgjs', 'imask'], factory) : (global = global || self, factory(global.dcxReactLibrary = {}, global.react, global.forgjs, global.imask)); })(this, (function (exports, React, forgjs, IMask) { function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var IMask__default = /*#__PURE__*/_interopDefaultLegacy(IMask); /** * Check if is valid or not * @param validation * @param value */ const isValid = (validation, value) => { const floatRule = new forgjs.Rule(validation.rule, validation.message); let rule = value; if (validation.rule.type === 'float' || validation.rule.type === 'int') { rule = parseFloat(value); } return { valid: floatRule.test(rule), floatRule }; }; /** * This custom hooks accept in input a validation rule and provide in * output the validity or not of the element. * For a list of Validation rules have a look on: https://github.com/oussamahamdaoui/forgJs * @Example * const {validity, onValueChange} = useValidationOnChange({ * rule: { * type: 'float', * min: 100, * }, * message: 'the value have to be float and more then 100', * }) * return (<input onChange={onValueChange}/> * @param validation */ const useValidationOnChange = (validation, value = '') => { let checkValidation = { valid: true, floatRule: { error: '' } }; if (validation) checkValidation = isValid(validation, value); const [validity, setValid] = React__default["default"].useState({ valid: checkValidation.valid, message: checkValidation.floatRule.error }); if (validation === null) return { validity: null, onValueChange: null }; const onValueChange = evt => { const { valid, floatRule } = isValid(validation, evt.currentTarget.value); setValid({ valid, message: floatRule.error }); }; return { validity, onValueChange }; }; /** * It will check a valid date given day.month and year * @param day * @param month * @param year * @returns */ function validateDateString(day, month, year) { day = Number(day); month = Number(month) - 1; //bloody 0-indexed month year = Number(year); let d = new Date(year, month, day); let yearMatches = d.getUTCFullYear() === year; let monthMatches = d.getUTCMonth() === month; let dayMatches = d.getDate() === day; return yearMatches && monthMatches && dayMatches; } const Roles = { alert: 'alert', button: 'button', combobox: 'combobox', error: 'error', formInput: 'form-input', formRadio: 'radio', formCheckbox: 'checkbox', formGroup: 'form-group', listbox: 'listbox', listItem: 'listitem', list: 'list', prefix: 'prefix', presentation: 'presentation', progress: 'progress', region: 'region', slider: 'slider', suffix: 'suffix', tab: 'tab', tablist: 'tablist', tabpanel: 'tabpanel', textbox: 'textbox', toggle: 'form-toggle' }; function subscribe() { return () => {}; } /** * Return a boolean indicating if the JS has been hydrated already. * When doing Server-Side Rendering, the result will always be false. * When doing Client-Side Rendering, the result will always be false on the * first render and true from then on. Even if a new component renders it will * always start with true. * * Example: Disable a button that needs JS to work. * ```tsx * let hydrated = useHydrated(); * return ( * <button type="button" disabled={!hydrated} onClick={doSomethingCustom}> * Click me * </button> * ); * ``` */ function useHydrated() { return React.useSyncExternalStore(subscribe, () => true, () => false); } const classNames = classes => { let result = ''; classes.forEach(c => { if (c !== null && typeof c === 'object') { for (const v in c) { if (c[v] === true) { result = result.concat(classNames(v.split(' '))).concat(' '); } } } else { if (c !== 'undefined' && c !== undefined && c !== 'null' && c !== null && typeof c === 'string') result = result.concat(c).concat(' '); } }); return result.slice(0, -1).trim(); }; const debounce = (func, time) => { let timer; const context = undefined; return function () { if (timer) clearTimeout(timer); timer = setTimeout(() => { timer = null; func.apply(context, [].slice.call(arguments)); }, time); }; }; const isEmpty = value => value === (null) || typeof value === 'object' && Object.keys(value).length === 0 || typeof value === 'string' && value.trim().length === 0; const upperFirst = string => string.charAt(0).toUpperCase() + string.slice(1); const omit = (originalObj, keysToOmit) => Object.fromEntries(Object.entries(originalObj).filter(([key]) => !keysToOmit.includes(key))); const ErrorMessage = ({ text, className, id, visuallyHiddenText }) => { const getErrorElement = () => { let errorElement = /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null); if (text && typeof text === 'string') { errorElement = /*#__PURE__*/React__default["default"].createElement("span", { id: id, className: classNames(['dcx-error-message', className]) }, visuallyHiddenText && ( /*#__PURE__*/React__default["default"].createElement("span", { className: visuallyHiddenText.className }, visuallyHiddenText.text)), text); } return errorElement; }; return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, getErrorElement()); }; const Hint = ({ text, className, id, useLabel }) => useLabel ? ( /*#__PURE__*/React__default["default"].createElement("label", { id: id, className: classNames(['dcx-hint', className]) }, text)) : ( /*#__PURE__*/React__default["default"].createElement("div", { id: id, className: classNames(['dcx-hint', className]) }, text)); const Conditional = ({ name, label, type, className, groupClassName, id, inputClassName, inputId, labelClassName, value, onChange }) => { const onChangeHandler = event => { if (onChange) onChange(event); }; const containerClasses = classNames([className, 'dcx-conditional-input']); return /*#__PURE__*/React__default["default"].createElement("div", { className: containerClasses, id: id }, /*#__PURE__*/React__default["default"].createElement("div", { className: groupClassName }, /*#__PURE__*/React__default["default"].createElement("label", { className: labelClassName, htmlFor: inputId }, label), /*#__PURE__*/React__default["default"].createElement("input", { className: inputClassName, id: inputId, name: name, type: type, value: value, onChange: onChangeHandler }))); }; const LOWEST_HEADING = 6; const HIGHEST_HEADING = 1; const Legend = ({ text, className, heading }) => ( /*#__PURE__*/React__default["default"].createElement("legend", { className: className }, heading ? /*#__PURE__*/React__default["default"].createElement(`h${heading && heading.priority >= HIGHEST_HEADING && heading.priority <= LOWEST_HEADING ? heading.priority : 1}`, { className: heading.className }, text) : text)); const Option = ({ label, value, className, disabled, id, ariaLabel }) => ( /*#__PURE__*/React__default["default"].createElement("option", { value: value, id: id, className: className, disabled: disabled, "aria-label": ariaLabel || label }, label)); const OptionGroup = ({ label, displayCount, options }) => ( /*#__PURE__*/React__default["default"].createElement("optgroup", { label: displayCount ? `${label} (${options.length})` : label }, options.map((item, index) => ( /*#__PURE__*/React__default["default"].createElement(Option, { key: index, ...item }))))); const CheckboxRadioBase = ({ label, value, type, role, id, ariaLabel, ariaDataControls, ariaDescribedBy, ariaLabelledBy, disabled, conditional, hint, inputProps, itemProps, labelProps, name, nested, selected, onChange, inputClassName, labelClassName, itemClassName }) => { let hydrated = useHydrated(); const conditionalReveal = () => !isEmpty(conditional) && selected === true; const [conditionalValue, setConditionalValue] = React.useState(conditional ? conditional.value : ''); const onChangeHandler = event => { if (onChange) onChange(event); }; const input = /*#__PURE__*/React__default["default"].createElement("input", { id: id, type: type, role: role, value: value, name: name, "aria-label": ariaLabel, "data-aria-controls": ariaDataControls, "aria-describedby": ariaDescribedBy, "aria-labelledby": ariaLabelledBy, disabled: disabled, checked: selected, ...inputProps, className: inputClassName, onChange: onChangeHandler }); const el = nested ? ( /*#__PURE__*/React__default["default"].createElement("label", { ...labelProps, className: labelClassName }, input, label)) : ( /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, input, /*#__PURE__*/React__default["default"].createElement("label", { ...labelProps, htmlFor: id, className: labelClassName }, label))); const getConditionalElement = () => { let hydratedElm = /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null); if (!hydrated && conditional !== undefined) { hydratedElm = Conditional({ ...conditional, value: conditionalValue }); } else if (conditional !== undefined && conditionalReveal()) { hydratedElm = Conditional({ ...conditional, value: conditionalValue, onChange: event => { setConditionalValue(event.currentTarget.value); if (onChange) onChange(event, event.currentTarget.value); } }); } return hydratedElm; }; return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("div", { ...itemProps, className: itemClassName }, hint && hint.position === 'above' && /*#__PURE__*/React__default["default"].createElement(Hint, { ...hint }), el, hint && hint.position !== 'above' && /*#__PURE__*/React__default["default"].createElement(Hint, { ...hint }), conditional && !conditional.position && getConditionalElement()), conditional && conditional.position === 'sibling' && getConditionalElement()); }; const Label$1 = ({ label, labelProperties, htmlFor, className }) => label ? ( /*#__PURE__*/React__default["default"].createElement("label", { ...labelProperties, htmlFor: htmlFor, className: className }, label)) : null; const FormCheckbox = ({ id, name, value, label, inputProps, itemProps, labelProps, ariaLabel, ariaDataControls = '', ariaDescribedBy = '', ariaLabelledBy, onChange, conditional, disabled, selected, hint, nested, inputClassName, labelClassName, itemClassName, isError }) => { const containerClasses = classNames([itemClassName, 'dcx-checkbox', { 'dcx-checkbox--error': isError }]); return /*#__PURE__*/React__default["default"].createElement(CheckboxRadioBase, { type: "checkbox", id: id, role: Roles.formCheckbox, name: name, value: value, label: label, inputProps: inputProps, itemProps: itemProps, labelProps: labelProps, ariaLabel: ariaLabel, ariaDataControls: ariaDataControls, ariaDescribedBy: ariaDescribedBy, ariaLabelledBy: ariaLabelledBy || labelProps?.id, onChange: onChange, conditional: conditional, disabled: disabled, selected: selected, hint: hint, nested: nested, itemClassName: containerClasses, inputClassName: inputClassName, labelClassName: labelClassName }); }; const isDivider = item => typeof item !== 'string' && item.label === undefined; const isString = item => typeof item === 'string'; const FormRadioCheckboxBase = ({ name, type, items, ariaDescribedBy, groupClasses, error, fieldsetClasses, hint, id, inputProps, itemProps, itemsClasses, inputClassName, itemClassName, labelClassName, labelProps, legend, onChange }) => { const findSelection = items => { let newSelection = {}; items.forEach(item => { if (item.selected) { newSelection = { ...newSelection, [item.id]: true }; } else { newSelection = { ...newSelection, [item.id]: false }; } }); return newSelection; }; const [selection, setSelection] = React.useState(findSelection(items)); const handleChange = (item, e) => { if (type === 'radio') { let newSelection = {}; items.forEach(item => { newSelection[item.id] = item.id === e.currentTarget.id ? e.currentTarget.checked : false; }); setSelection(newSelection); } else { setSelection({ ...selection, [isString(item) ? item : item.id]: e.currentTarget.checked }); } if (onChange) { onChange(e); } }; const isSelected = item => Object.keys(selection).some(key => key === item.id && selection[key] === true); const formGroupItems = items.map((item, index) => { if (isDivider(item)) { return /*#__PURE__*/React__default["default"].createElement("div", { key: `${id}_${index.toString()}`, id: item.id, className: item.className }, item.text); } if (isString(item)) { return /*#__PURE__*/React__default["default"].createElement(CheckboxRadioBase, { id: item, type: type, key: `${id}_${index.toString()}`, name: name, inputProps: { ...inputProps }, itemProps: { ...itemProps }, labelProps: { ...labelProps }, inputClassName: inputClassName, labelClassName: labelClassName, itemClassName: itemClassName, label: item, value: item, onChange: event => { handleChange(item, event); } }); } if (type === 'radio') { return /*#__PURE__*/React__default["default"].createElement(CheckboxRadioBase, { type: type, key: `${id}_${index.toString()}`, ...item, name: name, inputProps: { ...inputProps, ...item.inputProps }, itemProps: { ...itemProps, ...item.itemProps }, labelProps: { ...labelProps, ...item.labelProps }, inputClassName: inputClassName, labelClassName: labelClassName, itemClassName: itemClassName, selected: isSelected(item), onChange: (event, conditionalInput) => { if (conditionalInput && onChange) { onChange(event, conditionalInput); return; } handleChange(item, event); } }); } if (type === 'checkbox') { return /*#__PURE__*/React__default["default"].createElement(FormCheckbox, { key: `${id}_${index.toString()}`, ...item, name: name, inputProps: { ...inputProps, ...item.inputProps }, itemProps: { ...itemProps, ...item.itemProps }, labelProps: { ...labelProps, ...item.labelProps }, inputClassName: inputClassName, labelClassName: labelClassName, itemClassName: itemClassName, selected: isSelected(item), onChange: (event, conditionalInput) => { if (conditionalInput && onChange) { onChange(event, conditionalInput); return; } handleChange(item, event); } }); } return null; }); return items && items.length > 1 ? ( /*#__PURE__*/React__default["default"].createElement("div", { id: id, className: groupClasses }, /*#__PURE__*/React__default["default"].createElement("fieldset", { className: fieldsetClasses, "aria-describedby": ariaDescribedBy || '' }, legend && /*#__PURE__*/React__default["default"].createElement(Legend, { ...legend }), hint && /*#__PURE__*/React__default["default"].createElement(Hint, { ...hint }), error && /*#__PURE__*/React__default["default"].createElement(ErrorMessage, { ...error }), /*#__PURE__*/React__default["default"].createElement("div", { className: itemsClasses }, formGroupItems)))) : ( /*#__PURE__*/React__default["default"].createElement("div", null, "Can not render a ", type, " group with less than 2 items")); }; const CheckboxGroup = ({ name, items, ariaDescribedBy, groupClasses, error, fieldsetClasses, hint, id, inputProps, itemProps, itemsClasses, inputClassName, itemClassName, labelClassName, labelProps, legend, onChange }) => { const containerClasses = classNames([groupClasses, { 'dcx-checkbox-group': true }, { 'dcx-checkbox-group--error': !!error }]); const containerCheckboxesClasses = classNames([itemsClasses, 'dcx-checkbox-group-checkboxes']); return /*#__PURE__*/React__default["default"].createElement(FormRadioCheckboxBase, { type: 'checkbox', name, items, ariaDescribedBy, groupClasses: containerClasses, error, fieldsetClasses, hint, id, inputProps, itemProps, itemsClasses: containerCheckboxesClasses, inputClassName, itemClassName, labelClassName, labelProps, legend, onChange }); }; const RadioGroup = ({ name, items, ariaDescribedBy, groupClasses, error, fieldsetClasses, hint, id, inputProps, itemProps, itemsClasses, inputClassName, itemClassName, labelClassName, labelProps, legend, onChange }) => { const containerClasses = classNames([groupClasses, { 'dcx-radio-button-group': true }, { 'dcx-radio-button-group--error': !!error }]); const containerRadioButtonsClasses = classNames([itemsClasses, 'dcx-radio-button-group-radios']); return /*#__PURE__*/React__default["default"].createElement(FormRadioCheckboxBase, { type: 'radio', name, items, ariaDescribedBy, groupClasses: containerClasses, error, fieldsetClasses, hint: hint, id, inputProps, itemProps, itemsClasses: containerRadioButtonsClasses, inputClassName, itemClassName, labelClassName, labelProps, legend, onChange }); }; const floatVariants = ['floating', 'floating-filled']; exports.ErrorPosition = void 0; (function (ErrorPosition) { ErrorPosition["BEFORE_LABEL"] = "before-label"; ErrorPosition["BOTTOM"] = "bottom"; ErrorPosition["AFTER_LABEL"] = "after-label"; ErrorPosition["AFTER_HINT"] = "after-hint"; })(exports.ErrorPosition || (exports.ErrorPosition = {})); const FormInput = ({ name, type, value, label, validation = null, inputProps, labelProps, errorProps, prefix, suffix, onChange, onFocus, onBlur, isValid, staticErrorMessage, errorPosition, ariaLabel, ariaRequired, displayError = false, inputClassName, containerClassName, containerClassNameError, labelClassName, required, hint, variant = 'normal', inputDivProps = { style: { display: 'flex' } }, tabIndex, hiddenErrorText = '', hiddenErrorTextProps }) => { const { validity, onValueChange } = useValidationOnChange(validation, value); const [showError, setShowError] = React__default["default"].useState(displayError); React__default["default"].useEffect(() => { if (isValid && validity) isValid(validity.valid, showError && !validity.valid); // eslint-disable-next-line }, [validity?.valid, showError]); React__default["default"].useEffect(() => { setShowError(displayError); }, [displayError]); const handleChange = event => { setShowError(true); if (onValueChange) onValueChange(event); if (onChange) onChange(event); }; const handleFocus = event => { if (onFocus) onFocus(event); }; const handleBlur = event => { if (onBlur) onBlur(event); }; const isStaticMessageValid = () => typeof staticErrorMessage === 'string' && !isEmpty(staticErrorMessage); const ErrorMessage = () => { if (isStaticMessageValid()) { return /*#__PURE__*/React__default["default"].createElement("p", { ...errorProps, className: classNames(['dcx-error-message', errorProps?.className]), role: Roles.alert }, !isEmpty(hiddenErrorText) && ( /*#__PURE__*/React__default["default"].createElement("span", { ...hiddenErrorTextProps }, hiddenErrorText + ' ')), staticErrorMessage); } if (validity && !validity.valid && showError) { return /*#__PURE__*/React__default["default"].createElement("p", { ...errorProps, className: classNames(['dcx-error-message', errorProps?.className]), role: Roles.alert }, !isEmpty(hiddenErrorText) && ( /*#__PURE__*/React__default["default"].createElement("span", { ...hiddenErrorTextProps }, hiddenErrorText + ' ')), validity.message); } return null; }; const isStaticOrDynamicError = () => isStaticMessageValid() || validity && !validity.valid || false; const inputEl = /*#__PURE__*/React__default["default"].createElement("input", { name: name, type: type, value: value, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, required: required, className: inputClassName, "aria-label": ariaLabel, "aria-required": ariaRequired, tabIndex: tabIndex, ...inputProps }); const labelEl = /*#__PURE__*/React__default["default"].createElement(Label$1, { label: label, labelProperties: labelProps, htmlFor: inputProps?.id, className: labelClassName }); const containerClasses = classNames(['dcx-form-input', containerClassName, { 'dcx-form-input--filled': !!value, 'dcx-form-input--placeholder': !!inputProps?.placeholder, 'dcx-error-bottom': errorPosition === exports.ErrorPosition.BOTTOM, 'dcx-hint-bottom': hint && hint.position !== 'above', 'dcx-floating-label': floatVariants.includes(variant), 'dcx-floating-label-filled': variant === 'floating-filled', [`dcx-form-input--error ${containerClassNameError}`]: isStaticOrDynamicError() }]); return /*#__PURE__*/React__default["default"].createElement("div", { className: containerClasses }, errorPosition && errorPosition === exports.ErrorPosition.BEFORE_LABEL && ( /*#__PURE__*/React__default["default"].createElement(ErrorMessage, null)), !floatVariants.includes(variant) && labelEl, errorPosition && errorPosition === exports.ErrorPosition.AFTER_LABEL && ( /*#__PURE__*/React__default["default"].createElement(ErrorMessage, null)), hint && hint.position === 'above' && /*#__PURE__*/React__default["default"].createElement(Hint, { ...hint }), errorPosition && errorPosition === exports.ErrorPosition.AFTER_HINT && ( /*#__PURE__*/React__default["default"].createElement(ErrorMessage, null)), prefix || suffix ? ( /*#__PURE__*/React__default["default"].createElement("div", { ...inputDivProps }, prefix && !isEmpty(prefix) && ( /*#__PURE__*/React__default["default"].createElement("div", { ...prefix.properties, className: classNames(['dcx-form-input__prefix', prefix.properties.className]) }, prefix.content)), floatVariants.includes(variant) ? ( /*#__PURE__*/React__default["default"].createElement("div", { className: "dcx-wrapper-label" }, labelEl, inputEl)) : inputEl, suffix && !isEmpty(suffix) && ( /*#__PURE__*/React__default["default"].createElement("div", { ...suffix.properties, className: classNames(['dcx-form-input__suffix', suffix.properties.className]) }, suffix.content)))) : floatVariants.includes(variant) ? ( /*#__PURE__*/React__default["default"].createElement("div", { className: "dcx-wrapper-label" }, labelEl, inputEl)) : inputEl, hint && hint.position !== 'above' && /*#__PURE__*/React__default["default"].createElement(Hint, { ...hint }), errorPosition && errorPosition === exports.ErrorPosition.BOTTOM && ( /*#__PURE__*/React__default["default"].createElement(ErrorMessage, null))); }; const FormInputMasked = ({ options, onChange, value, name, type, ariaLabel, ...props }) => { const inputRef = React.useRef(null); const [mask, setMask] = React.useState(null); React.useEffect(() => { if (mask && value) { mask.value = value; } }, [mask, value]); React.useEffect(() => { if (inputRef.current && !mask) { setMask(IMask__default["default"](inputRef.current, { ...options })); } }, [mask, options]); React.useEffect(() => { if (inputRef.current && mask && onChange) { const { current } = inputRef; mask.on('accept', () => { onChange({ value: current.value, unmaskedValue: mask.unmaskedValue }); }); } }, [mask, onChange]); return /*#__PURE__*/React__default["default"].createElement("input", { ref: inputRef, ...props, name: name, type: type, "aria-label": ariaLabel }); }; /** * @deprecated since release 0.6 */ const FormRadio = ({ label, value, id, ariaLabel, ariaDataControls, ariaDescribedBy, ariaLabelledBy, disabled, conditional, hint, inputProps, itemProps, labelProps, name, nested, selected, onChange, inputClassName, labelClassName, itemClassName }) => ( /*#__PURE__*/React__default["default"].createElement(CheckboxRadioBase, { type: "radio", id: id, role: Roles.formRadio, name: name, value: value, label: label, inputProps: inputProps, itemProps: itemProps, labelProps: labelProps, ariaLabel: ariaLabel, ariaDataControls: ariaDataControls, ariaDescribedBy: ariaDescribedBy, ariaLabelledBy: ariaLabelledBy || labelProps?.id, onChange: onChange, conditional: conditional, disabled: disabled, selected: selected, hint: hint, nested: nested, inputClassName: inputClassName, labelClassName: labelClassName, itemClassName: itemClassName })); const FormSelect = ({ selectClassName, labelClassName, containerClassName, containerErrorClassName, containerFilledClassName, name, optionGroups, options = [], onChange, value, id, ariaLabel, label, labelProps, hint, error, errorMessage, errorMessageClassName, errorMessageVisuallyHidden, errorMessageId, style, nullOption, containerProps, defaultValue, tabIndex, variant = 'normal', disabled = false, selectProps }) => { let initialValue = ''; if (defaultValue !== undefined) { initialValue = defaultValue; } else if (value !== undefined) { initialValue = value; } else if (nullOption !== undefined) { initialValue = nullOption; } else if (options.length > 0 && typeof options[0] === 'string') { initialValue = options[0]; } else if (options.length > 0) { initialValue = options[0].value; } const [selectValue, setSelectValue] = React.useState(initialValue); const getOptions = options => options.map((item, index) => { let convertedItem; // in case item is a string we need to convert it to an option if (typeof item === 'string') { convertedItem = { label: item, value: item }; } else { convertedItem = { ...item }; } return /*#__PURE__*/React__default["default"].createElement(Option, { key: index, ...convertedItem }); }); const getOptionGroups = optionGroups => optionGroups.map((groupOption, index) => ( /*#__PURE__*/React__default["default"].createElement(OptionGroup, { key: index, ...groupOption }))); const handleChange = event => { setSelectValue(event.currentTarget.value); if (onChange) onChange(event); }; const containerClasses = classNames(['dcx-formselect', containerClassName, { [`dcx-formselect--error ${containerErrorClassName}`]: errorMessage !== undefined, 'dcx-floating-label': variant === 'floating', [`dcx-formselect--filled ${containerFilledClassName}`]: selectValue && selectValue !== nullOption }]); return /*#__PURE__*/React__default["default"].createElement("div", { className: containerClasses, ...containerProps }, /*#__PURE__*/React__default["default"].createElement(Label$1, { label: label, labelProperties: labelProps, htmlFor: id, className: labelClassName }), hint && /*#__PURE__*/React__default["default"].createElement(Hint, { ...hint }), error && /*#__PURE__*/React__default["default"].createElement(ErrorMessage, { ...error }), errorMessage && ( /*#__PURE__*/React__default["default"].createElement(ErrorMessage, { text: errorMessage, className: errorMessageClassName, visuallyHiddenText: errorMessageVisuallyHidden, id: errorMessageId })), /*#__PURE__*/React__default["default"].createElement("select", { value: selectValue, name: name || 'formSelect', id: id || 'formSelect', className: selectClassName, "aria-label": ariaLabel, onChange: handleChange, style: style, tabIndex: tabIndex, disabled: disabled, ...selectProps }, nullOption && /*#__PURE__*/React__default["default"].createElement(Option, { value: "", label: nullOption }), getOptions(options), optionGroups && getOptionGroups(optionGroups))); }; const ResultList = ({ resultLiRef, list, listId, userInput, activeOption, ulContainerId, ulContainerStyle, ulContainerClass, liContainerClass, liContainerStyle, activeClass, noOptionClass, noElFoundText, onClick, ariaLabeledBy }) => ( /*#__PURE__*/React__default["default"].createElement("ul", { id: ulContainerId, className: ulContainerClass, style: ulContainerStyle, "aria-labelledby": ariaLabeledBy, role: "listbox" }, userInput && list.length > 0 ? list.map((optionName, index) => ( /*#__PURE__*/React__default["default"].createElement("li", { id: listId ? `${listId}--${index + 1}` : undefined, className: classNames([liContainerClass, { [`${activeClass}`]: index === activeOption, [`${liContainerClass}--even`]: index !== activeOption && index % 2 === 0, [`${liContainerClass}--odd`]: index !== activeOption && index % 2 !== 0 }]), key: optionName, onClick: onClick, style: liContainerStyle, ref: ref => { resultLiRef.current = { ...resultLiRef.current, [index]: ref }; }, role: "option", "aria-selected": index === activeOption, "aria-setsize": list.length, "aria-posinset": index + 1, tabIndex: -1 }, optionName))) : noElFoundText && ( /*#__PURE__*/React__default["default"].createElement("li", { className: classNames([liContainerClass, noOptionClass]) }, noElFoundText)))); const SelectedItem = ({ label, ariaLabel, className, role, style, tabIndex, onClick, onFocus, onKeyDown }) => ( /*#__PURE__*/React__default["default"].createElement("span", { "aria-label": ariaLabel || label, role: role || Roles.presentation, className: className, onClick: onClick, onFocus: onFocus, onKeyDown: onKeyDown, tabIndex: tabIndex, style: style }, label)); const ENTER_KEY = 13; const Selected = ({ select, ariaLabel, className, labelClassName, removeButtonClassName, style, onFocus, onRemove }) => { const handleClick = () => onRemove && onRemove(select); const handleKeyDown = event => { if (parseInt(event.code) === ENTER_KEY) { onRemove && onRemove(select); } }; return /*#__PURE__*/React__default["default"].createElement("div", { id: select.id, className: className, role: Roles.listItem, style: style }, /*#__PURE__*/React__default["default"].createElement(SelectedItem, { className: labelClassName, label: select.label }), /*#__PURE__*/React__default["default"].createElement(SelectedItem, { className: removeButtonClassName, label: "x", role: "button", ariaLabel: ariaLabel || `Remove ${select.label}`, onClick: handleClick, onFocus: onFocus, onKeyDown: handleKeyDown, style: { marginLeft: '5px', fontWeight: 'bold' }, tabIndex: 0 })); }; exports.AutoCompleteErrorPosition = void 0; (function (AutoCompleteErrorPosition) { AutoCompleteErrorPosition["BEFORE_LABEL"] = "before-label"; AutoCompleteErrorPosition["BOTTOM"] = "bottom"; AutoCompleteErrorPosition["AFTER_LABEL"] = "after-label"; AutoCompleteErrorPosition["AFTER_HINT"] = "after-hint"; })(exports.AutoCompleteErrorPosition || (exports.AutoCompleteErrorPosition = {})); const Autocomplete = ({ options, optionsId, minCharsBeforeSearch = 1, minCharsMessage = '', promptCondition = () => false, promptMessage = '', promptId = 'input-prompt', promptClassName, debounceMs = 0, inputProps, defaultValue = '', hintText, hintClass, hintId, multiSelect = false, notFoundText, resultId, resultActiveClass, resultUlClass, resultUlStyle, resultlLiClass, resultLiStyle, resultNoOptionClass, removeAllButtonClass, searchContainerStyle, selectedListItemStyle, selected, onSelected, onChange, onRemove, onRemoveAll, onFocus, required = false, containerClassName, labelText, labelClassName, labelProps, id, errorPosition, errorMessageText = '', errorMessageClassName, errorId, errorVisuallyHiddenText, name, selectProps, prefix, suffix, tabIndex, search, accessibilityStatus = '', accessibilityHintText = '', statusUpdate, customNonJSComp = undefined }) => { const [activeOption, setActiveOption] = React.useState(0); const [filterList, setFilterList] = React.useState([]); const [showOptions, setShowOptions] = React.useState(false); const [showPrompt, setShowPrompt] = React.useState(false); const [userInput, setUserInput] = React.useState(defaultValue); const [currentAutocompleteStatus, setCurrentAutocompleteStatus] = React.useState(true); const resultRef = React.useRef(null); const [accessibilityStatusA, setAccessibilityStatusA] = React.useState(''); const [accessibilityStatusB, setAccessibilityStatusB] = React.useState(''); let hydrated = useHydrated(); const displayComp = () => { if (hydrated) { return formInput; } else { if (customNonJSComp !== undefined) { return customNonJSComp; } else { if (multiSelect) { return /*#__PURE__*/React__default["default"].createElement(FormSelect, { name: "multiSelect", options: options, ...inputProps }); } else { return /*#__PURE__*/React__default["default"].createElement(FormSelect, { name: name ?? 'select', options: options, id: id, defaultValue: defaultValue, ...selectProps }); } } } }; const showPromptMessage = (inputValue = userInput) => inputValue.trim().length === 0 && promptCondition() && promptMessage.length > 0; const showMinCharsMessage = (inputValue = userInput) => !showPromptMessage() && inputValue.trim().length < minCharsBeforeSearch && minCharsMessage.length > 0; const displayResultList = (inputValue = userInput) => showOptions && inputValue.trim().length >= minCharsBeforeSearch; const handlePrompt = (_evt, inputValue = userInput) => { const canShowPrompt = !displayResultList(inputValue) && (showMinCharsMessage(inputValue) || showPromptMessage(inputValue)); if (!showPrompt && canShowPrompt) { setShowPrompt(true); } else if (showPrompt && !canShowPrompt) { setShowPrompt(false); } }; const delayResult = React__default["default"].useMemo(() => debounce(value => { const filtered = search ? search(value, options) : options.filter(optionsName => optionsName.toLowerCase().includes(value.toLowerCase())); setActiveOption(0); setFilterList(filtered); setShowOptions(true); statusUpdate && statusUpdate(filtered.length, filtered[0], 1); }, debounceMs), [debounceMs, options, currentAutocompleteStatus]); const delayedFilterResults = React__default["default"].useCallback(delayResult, [delayResult]); const searchMemo = React__default["default"].useMemo(() => debounce(value => //@ts-ignore onChange(value), debounceMs), [debounceMs, onChange]); const debounceSearch = React__default["default"].useCallback(searchMemo, [searchMemo]); const handleChange = evt => { // prevent user input if promptCondition() is true if (promptCondition()) { return; } const { value } = evt.currentTarget; setUserInput(value); handlePrompt(evt, value); // if the user input is blank, close the options list and set the accessibility status to blank if (value === '') { setShowOptions(false); statusUpdate && statusUpdate(-1, '', 0); } else if (onChange) { debounceSearch(value); } else { delayedFilterResults(value); } }; React__default["default"].useEffect(() => { if (onChange) { setActiveOption(0); setFilterList(options); setShowOptions(true); } }, [options, onChange]); React__default["default"].useEffect(() => { setAccessibilityStatus(accessibilityStatus); }, [accessibilityStatus]); React__default["default"].useEffect(() => { setUserInput(defaultValue); }, [defaultValue]); const handleClick = evt => { const optionName = evt.currentTarget.innerHTML; setActiveOption(0); setFilterList([]); setShowOptions(false); setUserInput(multiSelect ? '' : optionName); statusUpdate && statusUpdate(-1, '', 0); if (onSelected) onSelected(optionName); }; const onKeyDown = evt => { if (!showOptions) { return; } if (evt.code === 'Enter') { evt.preventDefault(); setActiveOption(0); setShowOptions(false); if (filterList.length > 0) { setUserInput(filterList[activeOption]); statusUpdate && statusUpdate(-1, '', 0); if (onSelected) onSelected(filterList[activeOption]); } } else if (evt.code === 'ArrowUp') { if (activeOption === 0) { return; } const newActiveOption = activeOption - 1; setActiveOption(newActiveOption); const prevItem = resultRef.current && resultRef.current[newActiveOption]; prevItem && prevItem.scrollIntoView({ block: 'nearest', inline: 'start' }); statusUpdate && statusUpdate(filterList.length, filterList[newActiveOption], newActiveOption + 1); } else if (evt.code === 'ArrowDown') { if (activeOption === filterList.length - 1) { return; } const newActiveOption = activeOption + 1; setActiveOption(newActiveOption); const nextItem = resultRef.current && resultRef.current[newActiveOption]; nextItem && nextItem.scrollIntoView({ block: 'nearest', inline: 'start' }); statusUpdate && statusUpdate(filterList.length, filterList[newActiveOption], newActiveOption + 1); } else if (evt.code === 'Escape') { setShowOptions(false); } }; const onBlur = event => { setShowPrompt(false); let focusingOnInput = false; let focusingOnOptions = false; if (event.relatedTarget !== null) { // checks to see if the element comming into focus is the specific input element focusingOnInput = event.relatedTarget.id === id; // checks to see if the element comming into focus is an option focusingOnOptions = Object.keys(resultRef.current).map(value => resultRef.current[parseInt(value, 10)]).includes(event.relatedTarget); } if (!(focusingOnInput || focusingOnOptions)) { setShowOptions(false); } }; const setAccessibilityStatus = newStatus => { if (currentAutocompleteStatus) { setAccessibilityStatusA(''); setAccessibilityStatusB(newStatus); } else { setAccessibilityStatusA(newStatus); setAccessibilityStatusB(''); } // Alternates between the two status elements to make sure the change is seen for screen readers setCurrentAutocompleteStatus(!currentAutocompleteStatus); }; const getActivedescendantId = () => { if (resultRef.current === null && showOptions) { return `${optionsId}--1`; } else if (resultRef.current && resultRef.current[activeOption]) { return resultRef.current[activeOption].id; } else { return null; } }; const formInput = /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(FormInput, { name: name || 'autocompleteSearch', type: "text", value: userInput, onChange: handleChange, onFocus: handlePrompt, onBlur: onBlur, prefix: prefix, suffix: suffix, required: required, inputProps: { onKeyDown, autoComplete: 'off', id, ...inputProps, ...(showPrompt && { 'aria-describedby': promptId }), 'aria-expanded': showOptions, 'aria-owns': resultId, role: 'combobox', 'aria-activedescendant': getActivedescendantId() }, tabIndex: tabIndex, hiddenErrorText: "" }), showPrompt && ( /*#__PURE__*/React__default["default"].createElement("div", { className: promptClassName, id: promptId }, showPromptMessage() && promptMessage, showMinCharsMessage() && minCharsMessage))); const searchEl = multiSelect ? ( /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("div", { role: Roles.presentation, style: { display: 'inline-flex', flexDirection: 'row', width: '100%', flexWrap: 'wrap', position: 'relative' } }, selected && selected.map(({ id, label, value }, index) => ( /*#__PURE__*/React__default["default"].createElement(Selected, { key: index, select: { id, label, value }, onRemove: onRemove, onFocus: onFocus, style: { ...selectedListItemStyle, display: 'inline-flex' } }))), displayComp()), /*#__PURE__*/React__default["default"].createElement("div", null, /*#__PURE__*/React__default["default"].createElement(SelectedItem, { className: removeAllButtonClass, label: "x", role: "button", ariaLabel: "Remove all", onClick: onRemoveAll, style: { marginLeft: '5px', fontWeight: 'bold', verticalAlign: '-webkit-baseline-middle' }, tabIndex: 0 })))) : ( /*#__PURE__*/React__default["default"].createElement("div", { style: { position: 'relative' } }, errorPosition && errorPosition === exports.AutoCompleteErrorPosition.BEFORE_LABEL && ( /*#__PURE__*/React__default["default"].createElement(ErrorMessage, { text: errorMessageText, className: errorMessageClassName, id: errorId, visuallyHiddenText: errorVisuallyHiddenText })), labelText && ( /*#__PURE__*/React__default["default"].createElement("label", { htmlFor: id, className: labelClassName, ...labelProps }, labelText)), errorPosition && errorPosition === exports.AutoCompleteErrorPosition.AFTER_LABEL && ( /*#__PURE__*/React__default["default"].createElement(ErrorMessage, { text: errorMessageText, className: errorMessageClassName, id: errorId, visuallyHiddenText: errorVisuallyHiddenText })), hintText && ( /*#__PURE__*/React__default["default"].createElement(Hint, { text: hintText, className: hintClass, id: hintId, useLabel: false })), errorPosition && errorPosition === exports.AutoCompleteErrorPosition.AFTER_HINT && ( /*#__PURE__*/React__default["default"].createElement(ErrorMessage, { text: errorMessageText, className: errorMessageClassName, id: errorId, visuallyHiddenText: errorVisuallyHiddenText })), /*#__PURE__*/React__default["default"].createElement("div", { style: { border: '0px', clip: 'rect(0px, 0px, 0px, 0px)', height: '1px', marginBottom: '-1px', marginRight: '-1px', overflow: 'hidden', padding: '0px', position: 'absolute', whiteSpace: 'nowrap', width: '1px' } }, /*#__PURE__*/React__default["default"].createElement("div", { id: `autocomplete-status-${id}-A`, role: "status", "aria-atomic": "true", "aria-live": "polite" }, accessibilityStatusA), /*#__PURE__*/React__default["default"].createElement("div", { id: `autocomplete-status-${id}-B`, role: "status", "aria-atomic": "true", "aria-live": "polite" }, accessibilityStatusB)), displayComp())); return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, multiSelect && hintText && ( /*#__PURE__*/React__default["default"].createElement(Hint, { text: hintText, className: hintClass, id: hintId, useLabel: false })), /*#__PURE__*/React__default["default"].createElement("div", { className: containerClassName, style: { ...searchContainerStyle } }, searchEl, displayResultList() && ( /*#__PURE__*/React__default["default"].createElement(ResultList, { resultLiRef: resultRef, list: filterList, listId: optionsId, userInput: userInput, activeOption: activeOption, noElFoundText: notFoundText, onClick: handleClick, activeClass: resultActiveClass, ulContainerId: resultId, ulContainerClass: resultUlClass, ulContainerStyle: resultUlStyle, liContainerClass: resultlLiClass, liContainerStyle: resultLiStyle, noOptionClass: resultNoOptionClass, ariaLabeledBy: id })), /*#__PURE__*/React__default["default"].createElement("span", { id: `autocomplete-${id}-assistiveHint`, style: { display: 'none' } }, accessibilityHintText))); }; exports.BUTTON_TYPE = void 0; (function (BUTTON_TYPE) { BUTTON_TYPE["BUTTON"] = "button"; BUTTON_TYPE["SUBMIT"] = "submit"; BUTTON_TYPE["RESET"] = "reset"; })(exports.BUTTON_TYPE || (exports.BUTTON_TYPE = {})); const Button = ({ label, onClick, type = exports.BUTTON_TYPE.BUTTON, disabled = false, ariaLabel, disableClickForMs, customPrefixImg, customPostfixImg, isLoading, loadingLabel, customLoadingPreImage, customLoadingPostImage, formAction, name, value, className, variant, children, visuallyHiddenText,