From 63c86b8ff27c4c4257041b717c440daa5f85b81b Mon Sep 17 00:00:00 2001 From: jialecl Date: Wed, 19 Aug 2026 11:48:18 +0200 Subject: [PATCH 1/3] Sugeestions popover changed to DxcPopover --- packages/lib/src/text-input/Suggestions.tsx | 7 +- packages/lib/src/text-input/TextInput.tsx | 242 +++++++++----------- packages/lib/src/text-input/utils.ts | 86 +++++++ 3 files changed, 199 insertions(+), 136 deletions(-) diff --git a/packages/lib/src/text-input/Suggestions.tsx b/packages/lib/src/text-input/Suggestions.tsx index 9fbad145f..e1ba4e12c 100644 --- a/packages/lib/src/text-input/Suggestions.tsx +++ b/packages/lib/src/text-input/Suggestions.tsx @@ -7,18 +7,13 @@ import DxcIcon from "../icon/Icon"; import scrollbarStyles from "../styles/scroll"; const SuggestionsContainer = styled.div` - box-sizing: border-box; max-height: 304px; - padding: var(--spacing-padding-xxs) var(--spacing-padding-none); - background-color: var(--color-bg-neutral-lightest); - border: var(--border-width-s) var(--border-style-default) var(--border-color-neutral-medium); - border-radius: var(--border-radius-s); - box-shadow: var(--shadow-200); color: var(--color-fg-neutral-dark); font-family: var(--typography-font-family); font-size: var(--typography-label-m); font-weight: var(--typography-label-regular); overflow-y: auto; + margin: 0px calc(${`var(--spacing-padding-xs)`} * -1); ${scrollbarStyles} `; diff --git a/packages/lib/src/text-input/TextInput.tsx b/packages/lib/src/text-input/TextInput.tsx index 71b56b0ba..4bf61e94c 100644 --- a/packages/lib/src/text-input/TextInput.tsx +++ b/packages/lib/src/text-input/TextInput.tsx @@ -1,4 +1,3 @@ -import * as Popover from "@radix-ui/react-popover"; import { ChangeEvent, FocusEvent, @@ -24,17 +23,22 @@ import Suggestions from "./Suggestions"; import TextInputPropsType, { AutosuggestWrapperProps, RefType } from "./types"; import { calculateWidth, + decrementNumber, + getNumberErrorMessage, hasSuggestions, + incrementNumber, isNumberIncorrect, isRequired, makeCancelable, patternMismatch, + setNumberProps, } from "./utils"; import HelperText from "../styles/forms/HelperText"; import Label from "../styles/forms/Label"; import ErrorMessage from "../styles/forms/ErrorMessage"; import inputStylesByState from "../styles/forms/inputStylesByState"; import { getLengthErrorMessage } from "../common/utils"; +import DxcPopover from "../popover/Popover"; const TextInputContainer = styled.div<{ margin: TextInputPropsType["margin"]; @@ -154,65 +158,42 @@ const DxcTextInput = forwardRef( const [isAutosuggestError, changeIsAutosuggestError] = useState(false); const [filteredSuggestions, changeFilteredSuggestions] = useState([]); const [visualFocusIndex, changeVisualFocusIndex] = useState(-1); - const [portalContainer, setPortalContainer] = useState(null); const width = useWidth(inputContainerRef); - useEffect(() => { - setPortalContainer(document?.getElementById(`${inputId}-portal`)); - }, []); const autosuggestWrapperFunction = (children: ReactNode) => ( - 0 || isSearching || isAutosuggestError)}> - - {children} - - {portalContainer && ( - - { - // Avoid select to lose focus when the list is closed - event.preventDefault(); - }} - onOpenAutoFocus={(event) => { - // Avoid select to lose focus when the list is opened - event.preventDefault(); - }} - sideOffset={4} - style={{ zIndex: "var(--z-textinput)" }} - > - { - changeValue(suggestion); - closeSuggestions(); - }} - suggestions={filteredSuggestions} - styles={{ width }} - value={value ?? innerValue} - visualFocusIndex={visualFocusIndex} - /> - - - )} - + 0 || isSearching || isAutosuggestError)} + onOpenAutoFocus={(event) => { + // Avoid select to lose focus when the list is opened + event.preventDefault(); + }} + onCloseAutoFocus={(event) => { + // Avoid select to lose focus when the list is closed + event.preventDefault(); + }} + onClose={() => {}} + popoverContent={ + { + changeValue(suggestion); + closeSuggestions(); + }} + suggestions={filteredSuggestions} + styles={{ width }} + value={value ?? innerValue} + visualFocusIndex={visualFocusIndex} + /> + } + > + {children} + ); - const getNumberErrorMessage = (checkedValue: number) => - numberInputContext?.minNumber != null && checkedValue < numberInputContext?.minNumber - ? translatedLabels.numberInput.valueGreaterThanOrEqualToErrorMessage?.(numberInputContext.minNumber) - : numberInputContext?.maxNumber != null && checkedValue > numberInputContext?.maxNumber - ? translatedLabels.numberInput.valueLessThanOrEqualToErrorMessage?.(numberInputContext.maxNumber) - : undefined; - const openSuggestions = () => { if (hasSuggestions(suggestions)) { changeIsOpen(true); @@ -256,70 +237,21 @@ const DxcTextInput = forwardRef( numberInputContext?.typeNumber === "number" && isNumberIncorrect(Number(newValue), numberInputContext?.minNumber, numberInputContext?.maxNumber) ) { - onChange?.({ value: formattedValue, error: getNumberErrorMessage(Number(newValue)) }); + onChange?.({ + value: formattedValue, + error: getNumberErrorMessage( + Number(newValue), + translatedLabels.numberInput.valueGreaterThanOrEqualToErrorMessage, + translatedLabels.numberInput.valueLessThanOrEqualToErrorMessage, + numberInputContext?.minNumber, + numberInputContext?.maxNumber + ), + }); } else { onChange?.({ value: formattedValue }); } }; - const decrementNumber = (currentValue = value ?? innerValue) => { - if (!disabled && !readOnly) { - const numberValue = Number(currentValue); - const steppedValue = - Math.round((numberValue - (numberInputContext?.stepNumber ?? 0) + Number.EPSILON) * 100) / 100; - - if (currentValue !== "") { - if ( - numberInputContext?.minNumber != null && - (numberValue < numberInputContext?.minNumber || steppedValue < numberInputContext?.minNumber) - ) { - changeValue(numberValue); - } else if (numberInputContext?.maxNumber != null && numberValue > numberInputContext?.maxNumber) { - changeValue(numberInputContext?.maxNumber); - } else if (numberValue === numberInputContext?.minNumber) { - changeValue(numberInputContext?.minNumber); - } else { - changeValue(steppedValue); - } - } else if (numberInputContext?.minNumber != null && numberInputContext?.minNumber >= 0) { - changeValue(numberInputContext?.minNumber); - } else if (numberInputContext?.maxNumber != null && numberInputContext?.maxNumber < 0) { - changeValue(numberInputContext?.maxNumber); - } else if (numberInputContext?.stepNumber != null) { - changeValue(-numberInputContext.stepNumber); - } - } - }; - - const incrementNumber = (currentValue = value ?? innerValue) => { - if (!disabled && !readOnly) { - const numberValue = Number(currentValue); - const steppedValue = - Math.round((numberValue + (numberInputContext?.stepNumber ?? 0) + Number.EPSILON) * 100) / 100; - - if (currentValue !== "") { - if ( - numberInputContext?.maxNumber != null && - (numberValue > numberInputContext?.maxNumber || steppedValue > numberInputContext?.maxNumber) - ) { - changeValue(numberValue); - } else if (numberInputContext?.minNumber != null && numberValue < numberInputContext?.minNumber) { - changeValue(numberInputContext?.minNumber); - } else if (numberValue === numberInputContext?.maxNumber) { - changeValue(numberInputContext?.maxNumber); - } else { - changeValue(steppedValue); - } - } else if (numberInputContext?.minNumber != null && numberInputContext?.minNumber > 0) { - changeValue(numberInputContext?.minNumber); - } else if (numberInputContext?.maxNumber != null && numberInputContext?.maxNumber <= 0) { - changeValue(numberInputContext?.maxNumber); - } else if (numberInputContext?.stepNumber != null) { - changeValue(numberInputContext.stepNumber); - } - } - }; - const handleInputContainerOnClick = () => { if (document.activeElement !== actionRef.current) { inputRef.current?.focus(); @@ -360,7 +292,16 @@ const DxcTextInput = forwardRef( numberInputContext?.typeNumber === "number" && isNumberIncorrect(Number(event.target.value), numberInputContext?.minNumber, numberInputContext?.maxNumber) ) { - onBlur?.({ value: event.target.value, error: getNumberErrorMessage(Number(event.target.value)) }); + onBlur?.({ + value: event.target.value, + error: getNumberErrorMessage( + Number(event.target.value), + translatedLabels.numberInput.valueGreaterThanOrEqualToErrorMessage, + translatedLabels.numberInput.valueLessThanOrEqualToErrorMessage, + numberInputContext?.minNumber, + numberInputContext?.maxNumber + ), + }); } else { onBlur?.({ value: event.target.value }); } @@ -371,7 +312,15 @@ const DxcTextInput = forwardRef( case "ArrowDown": event.preventDefault(); if (numberInputContext?.typeNumber === "number") { - decrementNumber(); + decrementNumber( + inputRef.current?.value, + disabled, + readOnly, + changeValue, + numberInputContext?.stepNumber, + numberInputContext?.minNumber, + numberInputContext?.maxNumber + ); } else { openSuggestions(); if (!isAutosuggestError && !isSearching && filteredSuggestions.length > 0) { @@ -389,7 +338,15 @@ const DxcTextInput = forwardRef( case "ArrowUp": event.preventDefault(); if (numberInputContext?.typeNumber === "number") { - incrementNumber(); + incrementNumber( + inputRef.current?.value, + disabled, + readOnly, + changeValue, + numberInputContext?.stepNumber, + numberInputContext?.minNumber, + numberInputContext?.maxNumber + ); } else { openSuggestions(); if (!isAutosuggestError && !isSearching && filteredSuggestions.length > 0) { @@ -435,9 +392,25 @@ const DxcTextInput = forwardRef( const handleNumberInputWheel = (event: WheelEvent) => { if (document.activeElement === inputRef.current) { if (event.deltaY < 0) { - incrementNumber(inputRef.current?.value); + incrementNumber( + inputRef.current?.value, + disabled, + readOnly, + changeValue, + numberInputContext?.stepNumber, + numberInputContext?.minNumber, + numberInputContext?.maxNumber + ); } else { - decrementNumber(inputRef.current?.value); + decrementNumber( + inputRef.current?.value, + disabled, + readOnly, + changeValue, + numberInputContext?.stepNumber, + numberInputContext?.minNumber, + numberInputContext?.maxNumber + ); } } }; @@ -451,21 +424,30 @@ const DxcTextInput = forwardRef( }; const handleDecrementActionOnClick = () => { - decrementNumber(); + decrementNumber( + inputRef.current?.value, + disabled, + readOnly, + changeValue, + numberInputContext?.stepNumber, + numberInputContext?.minNumber, + numberInputContext?.maxNumber + ); inputRef.current?.focus(); }; const handleIncrementActionOnClick = () => { - incrementNumber(); + incrementNumber( + inputRef.current?.value, + disabled, + readOnly, + changeValue, + numberInputContext?.stepNumber, + numberInputContext?.minNumber, + numberInputContext?.maxNumber + ); inputRef.current?.focus(); }; - const setNumberProps = (type?: string, min?: number, max?: number, step?: number) => { - if (min != null) inputRef.current?.setAttribute("min", min.toString()); - if (max != null) inputRef.current?.setAttribute("max", max.toString()); - if (step != null) inputRef.current?.setAttribute("step", step.toString()); - if (type != null) inputRef.current?.setAttribute("type", type); - }; - useEffect(() => { if (typeof suggestions === "function") { changeIsSearching(true); @@ -501,7 +483,8 @@ const DxcTextInput = forwardRef( numberInputContext.typeNumber, numberInputContext.minNumber, numberInputContext.maxNumber, - numberInputContext.stepNumber + numberInputContext.stepNumber, + inputRef.current ?? undefined ); } }, [value, innerValue, suggestions, numberInputContext]); @@ -624,7 +607,6 @@ const DxcTextInput = forwardRef( {!disabled && typeof error === "string" && } - {hasSuggestions(suggestions) &&
} ); } diff --git a/packages/lib/src/text-input/utils.ts b/packages/lib/src/text-input/utils.ts index 3f0c73b69..c8e99966c 100644 --- a/packages/lib/src/text-input/utils.ts +++ b/packages/lib/src/text-input/utils.ts @@ -71,3 +71,89 @@ export const transformSpecialChars = (str: string) => { } return value; }; + +export const getNumberErrorMessage = ( + checkedValue: number, + valueGreaterThanOrEqualToErrorMessage: (value: number) => string, + valueLessThanOrEqualToErrorMessage: (value: number) => string, + minNumber?: number, + maxNumber?: number +) => + minNumber != null && checkedValue < minNumber + ? valueGreaterThanOrEqualToErrorMessage?.(minNumber) + : maxNumber != null && checkedValue > maxNumber + ? valueLessThanOrEqualToErrorMessage?.(maxNumber) + : undefined; + +export const decrementNumber = ( + currentValue: string | undefined, + disabled: boolean, + readOnly: boolean, + changeValue: (value: number | string) => void, + stepNumber?: number, + minNumber?: number, + maxNumber?: number +) => { + if (!disabled && !readOnly) { + const numberValue = Number(currentValue); + const steppedValue = Math.round((numberValue - (stepNumber ?? 0) + Number.EPSILON) * 100) / 100; + + if (currentValue !== "") { + if (minNumber != null && (numberValue < minNumber || steppedValue < minNumber)) { + changeValue(numberValue); + } else if (maxNumber != null && numberValue > maxNumber) { + changeValue(maxNumber); + } else if (numberValue === minNumber) { + changeValue(minNumber); + } else { + changeValue(steppedValue); + } + } else if (minNumber != null && minNumber >= 0) { + changeValue(minNumber); + } else if (maxNumber != null && maxNumber < 0) { + changeValue(maxNumber); + } else if (stepNumber != null) { + changeValue(-stepNumber); + } + } +}; + +export const incrementNumber = ( + currentValue: string | undefined, + disabled: boolean, + readOnly: boolean, + changeValue: (value: number | string) => void, + stepNumber?: number, + minNumber?: number, + maxNumber?: number +) => { + if (!disabled && !readOnly) { + const numberValue = Number(currentValue); + const steppedValue = Math.round((numberValue + (stepNumber ?? 0) + Number.EPSILON) * 100) / 100; + + if (currentValue !== "") { + if (maxNumber != null && (numberValue > maxNumber || steppedValue > maxNumber)) { + changeValue(numberValue); + } else if (minNumber != null && numberValue < minNumber) { + changeValue(minNumber); + } else if (numberValue === maxNumber) { + changeValue(maxNumber); + } else { + changeValue(steppedValue); + } + } else if (minNumber != null && minNumber > 0) { + changeValue(minNumber); + } else if (maxNumber != null && maxNumber <= 0) { + changeValue(maxNumber); + } else if (stepNumber != null) { + changeValue(stepNumber); + } + } +}; + +export const setNumberProps = (type?: string, min?: number, max?: number, step?: number, ref?: HTMLInputElement) => { + if (min != null) ref?.setAttribute("min", min.toString()); + if (max != null) ref?.setAttribute("max", max.toString()); + if (step != null) ref?.setAttribute("step", step.toString()); + if (type != null) ref?.setAttribute("type", type); +}; From d3db8d81a80c074fdfdcc5245ff281487050d211 Mon Sep 17 00:00:00 2001 From: jialecl Date: Fri, 21 Aug 2026 10:53:41 +0200 Subject: [PATCH 2/3] Removed function onClose function from autosuggest --- packages/lib/src/text-input/TextInput.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/lib/src/text-input/TextInput.tsx b/packages/lib/src/text-input/TextInput.tsx index 4bf61e94c..6bf7a71b5 100644 --- a/packages/lib/src/text-input/TextInput.tsx +++ b/packages/lib/src/text-input/TextInput.tsx @@ -172,7 +172,6 @@ const DxcTextInput = forwardRef( // Avoid select to lose focus when the list is closed event.preventDefault(); }} - onClose={() => {}} popoverContent={ Date: Fri, 21 Aug 2026 11:17:14 +0200 Subject: [PATCH 3/3] Bleed added in exchange of negative margin --- packages/lib/src/text-input/Suggestions.tsx | 68 +++++++++++---------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/packages/lib/src/text-input/Suggestions.tsx b/packages/lib/src/text-input/Suggestions.tsx index e1ba4e12c..4d8ad9c9f 100644 --- a/packages/lib/src/text-input/Suggestions.tsx +++ b/packages/lib/src/text-input/Suggestions.tsx @@ -5,6 +5,7 @@ import Suggestion from "./Suggestion"; import { SuggestionsProps } from "./types"; import DxcIcon from "../icon/Icon"; import scrollbarStyles from "../styles/scroll"; +import DxcBleed from "../bleed/Bleed"; const SuggestionsContainer = styled.div` max-height: 304px; @@ -13,7 +14,6 @@ const SuggestionsContainer = styled.div` font-size: var(--typography-label-m); font-weight: var(--typography-label-regular); overflow-y: auto; - margin: 0px calc(${`var(--spacing-padding-xs)`} * -1); ${scrollbarStyles} `; @@ -61,38 +61,40 @@ const Suggestions = ({ }, [visualFocusIndex]); return ( - { - event.preventDefault(); - }} - style={styles} - > - {isSearching ? ( - - {translatedLabels.textInput.searchingMessage} - - ) : searchHasErrors ? ( - - - {translatedLabels.textInput.fetchingDataErrorMessage} - - ) : ( -
    - {suggestions.map((suggestion, index) => ( - - ))} -
- )} -
+ + { + event.preventDefault(); + }} + style={styles} + > + {isSearching ? ( + + {translatedLabels.textInput.searchingMessage} + + ) : searchHasErrors ? ( + + + {translatedLabels.textInput.fetchingDataErrorMessage} + + ) : ( +
    + {suggestions.map((suggestion, index) => ( + + ))} +
+ )} +
+
); };