From 8a72d3a50d09a4d9cb8b84f65325975cc80071a4 Mon Sep 17 00:00:00 2001 From: Michele Masciave Date: Fri, 11 Apr 2025 08:40:14 +0200 Subject: [PATCH 1/7] export typeahead utils --- src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/index.ts b/src/index.ts index 1aed903..434f057 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,5 +8,9 @@ export * from "./lib/types/LabelValueOption"; export * from "./lib/DatePickerInput"; export * from "./lib/ColorPickerInput"; export * from "./lib/helpers/dateUtils"; +export * from "./lib/helpers/mui"; +export * from "./lib/helpers/typeahead"; +export * from "./lib/hooks/useDebounceHook"; +export * from "./lib/hooks/useSafeNameId"; export { useFormContext } from "./lib/context/FormContext"; From f8dc2b0a903de315cb0b7d6e9032d320b15674a3 Mon Sep 17 00:00:00 2001 From: Michele Masciave Date: Wed, 16 Apr 2025 12:38:57 +0200 Subject: [PATCH 2/7] integrate placeholder fix and changelog --- CHANGELOG.md | 8 ++++++++ .../component/Typeahead/AsyncTypeaheadInput.cy.tsx | 6 +++--- .../component/Typeahead/StaticTypeaheadInput.cy.tsx | 9 +++------ src/index.ts | 1 - src/lib/AsyncTypeaheadInput.tsx | 2 +- src/lib/StaticTypeaheadInput.tsx | 2 +- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4503f74..c71d00b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Export typeahead helpers and `useDebounceHook` hook. + +### Fixed + +- Hide placeholder on multiple `AsyncTypeAheadInput` and `StaticTypeAheadInput` when at least one option is selected. + ## [3.1.1] - 2025-04-10 ### Fixed diff --git a/cypress/cypress/component/Typeahead/AsyncTypeaheadInput.cy.tsx b/cypress/cypress/component/Typeahead/AsyncTypeaheadInput.cy.tsx index 99dbe00..05f8998 100644 --- a/cypress/cypress/component/Typeahead/AsyncTypeaheadInput.cy.tsx +++ b/cypress/cypress/component/Typeahead/AsyncTypeaheadInput.cy.tsx @@ -641,9 +641,6 @@ it("placeholder", () => { onSubmit={() => { // Nothing to do }} - defaultValues={{ - [name]: simpleOptions, - }} > { , ); + cy.get(`#${name}`).should("have.attr", "placeholder", placeholder); + simpleOptions.slice(0, 2).forEach((option) => selectOption(name, option)); + cy.get(`#${name}`).should("not.have.attr", "placeholder"); }); it("test on input change", () => { diff --git a/cypress/cypress/component/Typeahead/StaticTypeaheadInput.cy.tsx b/cypress/cypress/component/Typeahead/StaticTypeaheadInput.cy.tsx index 8ec1066..ad64378 100644 --- a/cypress/cypress/component/Typeahead/StaticTypeaheadInput.cy.tsx +++ b/cypress/cypress/component/Typeahead/StaticTypeaheadInput.cy.tsx @@ -398,12 +398,7 @@ it("placeholder", () => { cy.mount(
-
+ @@ -411,6 +406,8 @@ it("placeholder", () => { ); cy.get(`#${name}`).should("have.attr", "placeholder", placeholder); + simpleOptions.slice(0, 2).forEach((option) => selectOption(name, option)); + cy.get(`#${name}`).should("not.have.attr", "placeholder"); }); it("test on input change", () => { diff --git a/src/index.ts b/src/index.ts index 434f057..8831d0a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,5 @@ export * from "./lib/helpers/dateUtils"; export * from "./lib/helpers/mui"; export * from "./lib/helpers/typeahead"; export * from "./lib/hooks/useDebounceHook"; -export * from "./lib/hooks/useSafeNameId"; export { useFormContext } from "./lib/context/FormContext"; diff --git a/src/lib/AsyncTypeaheadInput.tsx b/src/lib/AsyncTypeaheadInput.tsx index 42f8662..c2892a2 100644 --- a/src/lib/AsyncTypeaheadInput.tsx +++ b/src/lib/AsyncTypeaheadInput.tsx @@ -206,7 +206,7 @@ const AsyncTypeaheadInput = (props: AsyncTypeaheadInputPr hideValidationMessage={hideValidationMessage} useBootstrapStyle={useBootstrapStyle} helpText={helpText} - placeholder={placeholder} + placeholder={multiple && value.length > 0 ? undefined : placeholder} paginationIcon={paginationIcon} paginationText={paginationText} variant={variant} diff --git a/src/lib/StaticTypeaheadInput.tsx b/src/lib/StaticTypeaheadInput.tsx index b72e47c..ad59824 100644 --- a/src/lib/StaticTypeaheadInput.tsx +++ b/src/lib/StaticTypeaheadInput.tsx @@ -169,7 +169,7 @@ const StaticTypeaheadInput = (props: StaticTypeaheadInput hideValidationMessage={hideValidationMessage} useBootstrapStyle={useBootstrapStyle} helpText={helpText} - placeholder={placeholder} + placeholder={multiple && value.length > 0 ? undefined : placeholder} paginationIcon={paginationIcon} paginationText={paginationText} variant={variant} From a8573843aaf2a7d2b9f1de219c05ce930b328464 Mon Sep 17 00:00:00 2001 From: Michele Masciave Date: Wed, 16 Apr 2025 15:27:23 +0200 Subject: [PATCH 3/7] adjust typescript --- src/lib/AsyncTypeaheadInput.tsx | 10 ++++++---- src/lib/StaticTypeaheadInput.tsx | 4 +++- src/lib/helpers/typeahead.tsx | 20 ++++++++++---------- src/lib/hooks/useDebounceHook.ts | 4 ++-- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/lib/AsyncTypeaheadInput.tsx b/src/lib/AsyncTypeaheadInput.tsx index c2892a2..cab92da 100644 --- a/src/lib/AsyncTypeaheadInput.tsx +++ b/src/lib/AsyncTypeaheadInput.tsx @@ -69,8 +69,8 @@ const AsyncTypeaheadInput = (props: AsyncTypeaheadInputPr autocompleteProps, } = props; - const [options, setOptions] = useState(defaultOptions); - const [value, setValue] = useState(defaultSelected); + const [options, setOptions] = useState(defaultOptions); + const [value, setValue] = useState(defaultSelected); const [page, setPage] = useState(1); const [loadMoreOptions, setLoadMoreOptions] = useState(limitResults !== undefined && limitResults < defaultOptions.length); const { name, id } = useSafeNameId(props.name ?? "", props.id); @@ -107,7 +107,7 @@ const AsyncTypeaheadInput = (props: AsyncTypeaheadInputPr defaultSelected.map((x) => (typeof x === "string" ? x : x.value)), ); }, - updateValues: (options: TypeaheadOption[]) => { + updateValues: (options: TypeaheadOptions) => { const values = convertAutoCompleteOptionsToStringArray(options); const finalValue = multiple ? values : values[0]; setValue(options); @@ -171,7 +171,9 @@ const AsyncTypeaheadInput = (props: AsyncTypeaheadInputPr field.onBlur(); }} onChange={(_e, value) => { - const optionsArray = value ? (Array.isArray(value) ? value : [value]) : undefined; + // value is typed as Autocomplete (aka TypeaheadOption) or an array of Autocomplete (aka TypeaheadOption[]) + // however, the component is not intended to be used with mixed types + const optionsArray = value ? (Array.isArray(value) ? value : [value]) as TypeaheadOptions : undefined; setValue(optionsArray ?? []); const values = convertAutoCompleteOptionsToStringArray(optionsArray); const finalValue = multiple ? values : values[0]; diff --git a/src/lib/StaticTypeaheadInput.tsx b/src/lib/StaticTypeaheadInput.tsx index ad59824..715281a 100644 --- a/src/lib/StaticTypeaheadInput.tsx +++ b/src/lib/StaticTypeaheadInput.tsx @@ -140,7 +140,9 @@ const StaticTypeaheadInput = (props: StaticTypeaheadInput field.onBlur(); }} onChange={(_, value) => { - const optionsArray = value ? (Array.isArray(value) ? value : [value]) : undefined; + // value is typed as Autocomplete (aka TypeaheadOption) or an array of Autocomplete (aka TypeaheadOption[]) + // however, the component is not intended to be used with mixed types + const optionsArray = value ? (Array.isArray(value) ? value : [value]) as TypeaheadOptions : undefined; const values = convertAutoCompleteOptionsToStringArray(optionsArray); const finalValue = multiple ? values : values[0]; clearErrors(field.name); diff --git a/src/lib/helpers/typeahead.tsx b/src/lib/helpers/typeahead.tsx index 7aef6be..de85e7a 100644 --- a/src/lib/helpers/typeahead.tsx +++ b/src/lib/helpers/typeahead.tsx @@ -1,12 +1,12 @@ import { AutocompleteRenderOptionState } from "@mui/material/Autocomplete"; import { LabelValueOption } from "../types/LabelValueOption"; -import { TypeaheadOption } from "../types/Typeahead"; +import { TypeaheadOption, TypeaheadOptions } from "../types/Typeahead"; import AutosuggestHighlightMatch from "autosuggest-highlight/match"; import AutosuggestHighlightParse from "autosuggest-highlight/parse"; -const isStringArray = (options: TypeaheadOption[]): boolean => options.length > 0 && options.every((value) => typeof value === "string"); +const isStringArray = (options: TypeaheadOptions): boolean => options.length > 0 && (options as TypeaheadOption[]).every((value) => typeof value === "string"); -const convertAutoCompleteOptionsToStringArray = (options: TypeaheadOption[] | undefined): string[] => { +const convertAutoCompleteOptionsToStringArray = (options: TypeaheadOptions | undefined): string[] => { if (!options) { return []; } @@ -18,29 +18,29 @@ const convertAutoCompleteOptionsToStringArray = (options: TypeaheadOption[] | un return (options as LabelValueOption[]).map((option) => option.value) as string[]; }; -const getSingleAutoCompleteValue = (options: TypeaheadOption[], fieldValue: string | number | undefined): TypeaheadOption[] => { +const getSingleAutoCompleteValue = (options: TypeaheadOptions, fieldValue: string | number | undefined): TypeaheadOptions => { if (fieldValue == undefined) { return []; } - return options.filter((x) => + return (options as TypeaheadOption[]).filter((x) => // loose equality check to handle different types between form value and option value typeof x === "string" ? x == fieldValue : x.value == fieldValue, - ); + ) as TypeaheadOptions; }; -const getMultipleAutoCompleteValue = (options: TypeaheadOption[], fieldValue: (string | number)[] | undefined): TypeaheadOption[] => { +const getMultipleAutoCompleteValue = (options: TypeaheadOptions, fieldValue: (string | number)[] | undefined): TypeaheadOptions => { if (fieldValue == undefined) { return []; } - return options.filter((x) => + return (options as TypeaheadOption[]).filter((x: TypeaheadOption) => typeof x === "string" ? fieldValue.includes(x) : // ensure that form values matches options values even if they are of different types fieldValue.map(String).includes(String(x.value as string | number)), - ); + ) as TypeaheadOptions; }; -const sortOptionsByGroup = (options: TypeaheadOption[]): TypeaheadOption[] => +const sortOptionsByGroup = (options: TypeaheadOptions): TypeaheadOptions => options.sort((x, y) => (typeof x === "string" ? x : x.group?.name ?? "").localeCompare(typeof y === "string" ? y : y.group?.name ?? "")); const groupOptions = (option: TypeaheadOption): string => (typeof option === "string" ? option : option.group?.name ?? ""); diff --git a/src/lib/hooks/useDebounceHook.ts b/src/lib/hooks/useDebounceHook.ts index c97f72c..31d4595 100644 --- a/src/lib/hooks/useDebounceHook.ts +++ b/src/lib/hooks/useDebounceHook.ts @@ -1,12 +1,12 @@ import { useEffect, useRef, useState } from "react"; -import { TypeaheadOption, TypeaheadOptions } from "../types/Typeahead"; +import { TypeaheadOptions } from "../types/Typeahead"; interface DebounceSearch { query: string; delay: number; } -const useDebounceHook = (queryFn: (query: string) => Promise, setOptions: (results: TypeaheadOption[]) => void) => { +const useDebounceHook = (queryFn: (query: string) => Promise, setOptions: (results: TypeaheadOptions) => void) => { const queryRef = useRef(""); const [isLoading, setIsLoading] = useState(false); const [debounceSearch, setDebounceSearch] = useState(undefined); From e1267ef282dd50afce477920d143f4a4ee555a8c Mon Sep 17 00:00:00 2001 From: Michele Masciave Date: Mon, 28 Apr 2025 09:34:08 +0200 Subject: [PATCH 4/7] prepare-pr --- CHANGELOG.md | 1 + src/lib/AsyncTypeaheadInput.tsx | 2 +- src/lib/StaticTypeaheadInput.tsx | 2 +- src/lib/helpers/typeahead.tsx | 5 +++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c71d00b..dc11842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Export typeahead helpers and `useDebounceHook` hook. +- Typeahead helpers return type from `TypeaheadOption[]` to `TypeaheadOptions`, since typeaheads are not intended to be used with mixed `string` and `LabelValueOption` options. ### Fixed diff --git a/src/lib/AsyncTypeaheadInput.tsx b/src/lib/AsyncTypeaheadInput.tsx index cab92da..c2850f3 100644 --- a/src/lib/AsyncTypeaheadInput.tsx +++ b/src/lib/AsyncTypeaheadInput.tsx @@ -173,7 +173,7 @@ const AsyncTypeaheadInput = (props: AsyncTypeaheadInputPr onChange={(_e, value) => { // value is typed as Autocomplete (aka TypeaheadOption) or an array of Autocomplete (aka TypeaheadOption[]) // however, the component is not intended to be used with mixed types - const optionsArray = value ? (Array.isArray(value) ? value : [value]) as TypeaheadOptions : undefined; + const optionsArray = value ? ((Array.isArray(value) ? value : [value]) as TypeaheadOptions) : undefined; setValue(optionsArray ?? []); const values = convertAutoCompleteOptionsToStringArray(optionsArray); const finalValue = multiple ? values : values[0]; diff --git a/src/lib/StaticTypeaheadInput.tsx b/src/lib/StaticTypeaheadInput.tsx index 715281a..9e29134 100644 --- a/src/lib/StaticTypeaheadInput.tsx +++ b/src/lib/StaticTypeaheadInput.tsx @@ -142,7 +142,7 @@ const StaticTypeaheadInput = (props: StaticTypeaheadInput onChange={(_, value) => { // value is typed as Autocomplete (aka TypeaheadOption) or an array of Autocomplete (aka TypeaheadOption[]) // however, the component is not intended to be used with mixed types - const optionsArray = value ? (Array.isArray(value) ? value : [value]) as TypeaheadOptions : undefined; + const optionsArray = value ? ((Array.isArray(value) ? value : [value]) as TypeaheadOptions) : undefined; const values = convertAutoCompleteOptionsToStringArray(optionsArray); const finalValue = multiple ? values : values[0]; clearErrors(field.name); diff --git a/src/lib/helpers/typeahead.tsx b/src/lib/helpers/typeahead.tsx index de85e7a..d552b32 100644 --- a/src/lib/helpers/typeahead.tsx +++ b/src/lib/helpers/typeahead.tsx @@ -4,7 +4,8 @@ import { TypeaheadOption, TypeaheadOptions } from "../types/Typeahead"; import AutosuggestHighlightMatch from "autosuggest-highlight/match"; import AutosuggestHighlightParse from "autosuggest-highlight/parse"; -const isStringArray = (options: TypeaheadOptions): boolean => options.length > 0 && (options as TypeaheadOption[]).every((value) => typeof value === "string"); +const isStringArray = (options: TypeaheadOptions): boolean => + options.length > 0 && (options as TypeaheadOption[]).every((value) => typeof value === "string"); const convertAutoCompleteOptionsToStringArray = (options: TypeaheadOptions | undefined): string[] => { if (!options) { @@ -32,7 +33,7 @@ const getMultipleAutoCompleteValue = (options: TypeaheadOptions, fieldValue: (st if (fieldValue == undefined) { return []; } - return (options as TypeaheadOption[]).filter((x: TypeaheadOption) => + return (options as TypeaheadOption[]).filter((x) => typeof x === "string" ? fieldValue.includes(x) : // ensure that form values matches options values even if they are of different types From 12211f1448a0b73a6d7b8e9795a8e7633706d4ae Mon Sep 17 00:00:00 2001 From: Michele Masciave Date: Wed, 30 Apr 2025 11:41:20 +0200 Subject: [PATCH 5/7] fixed --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc11842..99f5cb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Export typeahead helpers and `useDebounceHook` hook. -- Typeahead helpers return type from `TypeaheadOption[]` to `TypeaheadOptions`, since typeaheads are not intended to be used with mixed `string` and `LabelValueOption` options. ### Fixed - Hide placeholder on multiple `AsyncTypeAheadInput` and `StaticTypeAheadInput` when at least one option is selected. +- Typeahead helpers return type from `TypeaheadOption[]` to `TypeaheadOptions`, since typeaheads are not intended to be used with mixed `string` and `LabelValueOption` options. ## [3.1.1] - 2025-04-10 From 2cae5063c831c1b82e65bbf7e07751e9c62c23cc Mon Sep 17 00:00:00 2001 From: Michele Masciave Date: Fri, 6 Feb 2026 15:58:25 +0100 Subject: [PATCH 6/7] solution --- CHANGELOG.md | 4 ++++ src/lib/helpers/mui.ts | 13 ++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2649f8..ca6b28e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- padding on `textFieldBootstrapStyle` in order to override MUI padding on `StaticTypeahead`, `AsyncTypeahead`, `ColorPickerInput` and `TelephoneNumberInput`. + ## [3.16.0] - 2026-02-05 ### Added diff --git a/src/lib/helpers/mui.ts b/src/lib/helpers/mui.ts index d0d2715..c565a0d 100644 --- a/src/lib/helpers/mui.ts +++ b/src/lib/helpers/mui.ts @@ -10,6 +10,17 @@ const textFieldBootstrapStyle: SxProps = { padding: "0px 12px", borderColor: "#E0E3E7", transition: "border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out", + + // override padding for color picker input + "& .MuiInputBase-input": { + padding: "0px 12px", + }, + + // override default padding when text-field is wrapped by autocomplete + ".MuiAutocomplete-root &": { + padding: "0px 12px", + }, + "&.Mui-focused": { borderColor: "#80bdff", boxShadow: "0 0 0 0.2rem rgba(0,123,255,.25)", @@ -42,7 +53,7 @@ const textFieldBootstrapStyle: SxProps = { "& fieldset": { border: "none", }, - "& .MuiFormHelperText-root ": { + "& .MuiFormHelperText-root": { marginLeft: "0.2rem", marginTop: "0.3rem", }, From db18dcb8d41011f5715d3f936661c3e871b3c8d0 Mon Sep 17 00:00:00 2001 From: Michele Masciave Date: Mon, 9 Feb 2026 09:18:43 +0100 Subject: [PATCH 7/7] x --- src/lib/helpers/mui.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/helpers/mui.ts b/src/lib/helpers/mui.ts index c565a0d..7be6964 100644 --- a/src/lib/helpers/mui.ts +++ b/src/lib/helpers/mui.ts @@ -11,7 +11,7 @@ const textFieldBootstrapStyle: SxProps = { borderColor: "#E0E3E7", transition: "border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out", - // override padding for color picker input + // override padding for mui inputs to match bootstrap's default input padding "& .MuiInputBase-input": { padding: "0px 12px", },