diff --git a/src/Form/FormAutosuggest.jsx b/src/Form/FormAutosuggest.jsx index 5fb689b2e84..a2f4b3d0fdc 100644 --- a/src/Form/FormAutosuggest.jsx +++ b/src/Form/FormAutosuggest.jsx @@ -2,6 +2,7 @@ import React, { useEffect, useState, } from 'react'; import PropTypes from 'prop-types'; +import { v4 as uuidv4 } from 'uuid'; import { useIntl } from 'react-intl'; import { KeyboardArrowUp, KeyboardArrowDown } from '../../icons'; import Icon from '../Icon'; @@ -13,17 +14,21 @@ import Spinner from '../Spinner'; import useArrowKeyNavigation from '../hooks/useArrowKeyNavigation'; import messages from './messages'; +//this is the function that is being called in the mdx file function FormAutosuggest({ children, arrowKeyNavigationSelector, ignoredArrowKeysNames, screenReaderText, value, + freeformValue, isLoading, - errorMessageText, + errorMessageText, //create more, more specific error no selected value + errorNoMatchingText, // error no matching value onChange, onSelected, helpMessage, + allowFreeFormInput, ...props }) { const intl = useIntl(); @@ -34,40 +39,46 @@ function FormAutosuggest({ const [isMenuClosed, setIsMenuClosed] = useState(true); const [isActive, setIsActive] = useState(false); const [state, setState] = useState({ - displayValue: value || '', + displayValue: freeformValue || '', errorMessage: '', dropDownItems: [], }); + console.log("VALUE:", value) const handleItemClick = (e, onClick) => { - const clickedValue = e.currentTarget.getAttribute('data-value'); - - if (onSelected && clickedValue !== value) { - onSelected(clickedValue); + const clickedDataValue = e.currentTarget.getAttribute('data-value'); + const clickedDisplayValue = e.currentTarget.innerText + + if (onSelected && clickedDataValue !== value) { + onSelected({dataValue: clickedDataValue, displayValue: clickedDisplayValue, }); + // call on selected and passes the object as an argument + // doc site example, selected will now be a data object + //then passes that in and passes that in as the value of the autosuggest component + // component believes its jsut eh display value, so whereever its value it should be displayvalue } setState(prevState => ({ ...prevState, dropDownItems: [], - displayValue: clickedValue, + displayValue: clickedDisplayValue, })); setIsMenuClosed(true); if (onClick) { onClick(e); - } - }; + } + }; function getItems(strToFind = '') { let childrenOpt = React.Children.map(children, (child) => { // eslint-disable-next-line no-shadow - const { children, onClick, ...rest } = child.props; + const { children, onClick, value, ...rest } = child.props; return React.cloneElement(child, { ...rest, children, - 'data-value': children, + 'data-value': value ?? children, onClick: (e) => handleItemClick(e, onClick), }); }); @@ -103,6 +114,7 @@ function FormAutosuggest({ handleExpand(e, isMenuClosed)} /> ); - - const handleDocumentClick = (e) => { - if (parentRef.current && !parentRef.current.contains(e.target) && isActive) { + const leaveControl = () => { setIsActive(false); + + let errorMessage = "" + + if (!state.displayValue){ + console.log("Nothing typed:", errorMessageText) + errorMessage = errorMessageText + }else if(!allowFreeFormInput){ //not allowing for freeform, meaning must exactly match + console.log("Freeform not allowed:", errorNoMatchingText) + const dropDownItems = getItems(state.displayValue) + let inputMatchesDropDown = false + React.Children.forEach(children, (child) => { + console.log("item", child.props.children) + console.log(child.props) + if (state.displayValue === child.props.children){ + onSelected({displayValue: state.displayValue, dataValue: child.props.value}) // freeform input is not allowed, HAS to match option values + + inputMatchesDropDown = true + } + }) + if (!inputMatchesDropDown){ + onSelected({displayValue: "" , dataValue: ""}) //setting components parent value back to default + errorMessage = errorNoMatchingText + console.log("Freeform not allowed and doesn't match:", state.displayValue, dropDownItems, errorMessage) + } + }else{ + onSelected({displayValue: state.displayValue, dataValue: state.displayValue}) // freeform IS allowed, therefore we are assigning the parent component value to be what the user types if there's no matching option + } setState(prevState => ({ ...prevState, dropDownItems: [], - errorMessage: !state.displayValue ? errorMessageText : '', + errorMessage: errorMessage, })); - + setIsMenuClosed(true); + } + + const handleDocumentClick = (e) => { + if (parentRef.current && !parentRef.current.contains(e.target) && isActive) { + leaveControl(); } }; @@ -139,6 +181,9 @@ function FormAutosuggest({ setIsMenuClosed(true); } + if (e.key === 'Tab' && isActive){ + leaveControl(); + } }; useEffect(() => { @@ -151,28 +196,29 @@ function FormAutosuggest({ }; }); - useEffect(() => { - if (value || value === '') { - setState(prevState => ({ - ...prevState, - displayValue: value, - })); - } - }, [value]); + // Not clear what this was used for + // useEffect(() => { + // if (value || value === '') { + // setState(prevState => ({ + // ...prevState, + // displayValue: freeformValue, + // })); + // } + // }, [value]); - const setDisplayValue = (itemValue) => { - const optValue = []; + const setDisplayValue = (typedValue) => { + const optValues = []; children.forEach(opt => { - optValue.push(opt.props.children); + optValues.push({dataValue: opt.props.value, displayValue: opt.props.children, }); }); - const normalized = itemValue.toLowerCase(); - const opt = optValue.find((o) => o.toLowerCase() === normalized); + const normalized = typedValue.toLowerCase(); + const opt = optValues.find((o) => o.displayValue.toLowerCase() === normalized); setState(prevState => ({ ...prevState, - displayValue: opt || itemValue, + displayValue: opt ? opt.displayValue : typedValue, })); }; @@ -193,9 +239,9 @@ function FormAutosuggest({ const handleOnChange = (e) => { const findStr = e.target.value; - + if (onChange) { onChange(findStr); } - + if (findStr.length) { const filteredItems = getItems(findStr); setState(prevState => ({ @@ -279,7 +325,8 @@ FormAutosuggest.defaultProps = { helpMessage: '', placeholder: '', value: null, - errorMessageText: null, + errorMessageText: "Error, no selected value", + errorNoMatchingText: "Error, no matching value", readOnly: false, children: null, name: 'form-autosuggest', diff --git a/src/Form/form-autosuggest.mdx b/src/Form/form-autosuggest.mdx index 9da8579ba20..73360e91449 100644 --- a/src/Form/form-autosuggest.mdx +++ b/src/Form/form-autosuggest.mdx @@ -19,7 +19,15 @@ Form auto-suggest enables users to manually select or type to find matching opti ```jsx live () => { - const [selected, setSelected] = useState(''); + const [selectedDisplay, setSelectedDisplay] = useState(''); + const [selectedData, setSelectedData] = useState(''); + const handleSelected = (selectedObject) => { + setSelectedDisplay(selectedObject.displayValue) + setSelectedData(selectedObject.dataValue) + } + + console.log("SELECTEDDISPLAY:", selectedDisplay) + console.log("SELECTEDDATA:", selectedData) return ( setSelected(value)} + errorNoMatchingText="Error, no matching value" + value={selectedData} + freeformValue={selectedDisplay} + onSelected={(value) => handleSelected(value)} + allowFreeFormInput={true} > - JavaScript - Python - Rube - alert(e.currentTarget.getAttribute('data-value'))}> - Option with custom onClick - + JavaScript + Python + Ruby ); }