@mtes-mct/monitor-ui 6.3.2 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [6.3.3](https://github.com/MTES-MCT/monitor-ui/compare/v6.3.2...v6.3.3) (2023-05-25)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **fields:** control options internally with customSearch in Select & MultiSelect ([251ad18](https://github.com/MTES-MCT/monitor-ui/commit/251ad188dbc388c6efcaa1e2bf0afdd0ba343a65))
7
+ * **fields:** reset controlled rsuite data on change with customSearch in MultiSelect ([b96b6d5](https://github.com/MTES-MCT/monitor-ui/commit/b96b6d5eb0e3e00170449ec5df0615ee7c6cea96))
8
+
9
+ ## [6.3.2](https://github.com/MTES-MCT/monitor-ui/compare/v6.3.1...v6.3.2) (2023-05-25)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **libs:** make CustomSearch fully functional ([931dda0](https://github.com/MTES-MCT/monitor-ui/commit/931dda03e79ba3710d9ae0d14a45305e0c57b819))
15
+
1
16
  ## [6.3.1](https://github.com/MTES-MCT/monitor-ui/compare/v6.3.0...v6.3.1) (2023-05-24)
2
17
 
3
18
 
@@ -6,6 +6,8 @@ export type MultiSelectProps<OptionValue extends OptionValueType = string> = Omi
6
6
  /** Used to pass something else than `window.document` as a base container to attach global events listeners. */
7
7
  baseContainer?: Document | HTMLDivElement | null | undefined;
8
8
  customSearch?: CustomSearch<Option<OptionValue>> | undefined;
9
+ /** Minimum search query length required to trigger custom search filtering. */
10
+ customSearchMinQueryLength?: number | undefined;
9
11
  error?: string | undefined;
10
12
  isErrorMessageHidden?: boolean | undefined;
11
13
  isLabelHidden?: boolean | undefined;
@@ -18,4 +20,4 @@ export type MultiSelectProps<OptionValue extends OptionValueType = string> = Omi
18
20
  options: Option<OptionValue>[];
19
21
  value?: OptionValue[] | undefined;
20
22
  };
21
- export declare function MultiSelect<OptionValue extends OptionValueType = string>({ baseContainer, customSearch, disabled, error, isErrorMessageHidden, isLabelHidden, isLight, isUndefinedWhenDisabled, label, onChange, options, optionValueKey, searchable, value, ...originalProps }: MultiSelectProps<OptionValue>): JSX.Element;
23
+ export declare function MultiSelect<OptionValue extends OptionValueType = string>({ baseContainer, customSearch, customSearchMinQueryLength, disabled, error, isErrorMessageHidden, isLabelHidden, isLight, isUndefinedWhenDisabled, label, onChange, options, optionValueKey, searchable, value, ...originalProps }: MultiSelectProps<OptionValue>): JSX.Element;
@@ -1,4 +1,5 @@
1
1
  import { type ElementType } from 'react';
2
+ import type { CustomSearch } from '../libs/CustomSearch';
2
3
  import type { Option, OptionValueType } from '../types';
3
4
  import type { AutoCompleteProps as RsuiteAutoCompleteProps } from 'rsuite';
4
5
  import type { Promisable } from 'type-fest';
@@ -6,6 +7,9 @@ export type SearchProps<OptionValue extends OptionValueType = string> = Omit<Rsu
6
7
  MenuItem?: ElementType | undefined;
7
8
  /** Used to pass something else than `window.document` as a base container to attach global events listeners. */
8
9
  baseContainer?: Document | HTMLDivElement | null | undefined;
10
+ customSearch?: CustomSearch<Option<OptionValue>> | undefined;
11
+ /** Minimum search query length required to trigger custom search filtering. */
12
+ customSearchMinQueryLength?: number | undefined;
9
13
  error?: string | undefined;
10
14
  isErrorMessageHidden?: boolean | undefined;
11
15
  isLabelHidden?: boolean | undefined;
@@ -19,4 +23,4 @@ export type SearchProps<OptionValue extends OptionValueType = string> = Omit<Rsu
19
23
  options?: Option<OptionValue>[];
20
24
  value?: OptionValue | undefined;
21
25
  };
22
- export declare function Search<OptionValue extends OptionValueType = string>({ baseContainer, className, error, isErrorMessageHidden, isLabelHidden, isLight, isSearchIconVisible, label, MenuItem, onChange, onQuery, options, optionValueKey, value, ...originalProps }: SearchProps<OptionValue>): JSX.Element;
26
+ export declare function Search<OptionValue extends OptionValueType = string>({ baseContainer, className, customSearch, customSearchMinQueryLength, error, isErrorMessageHidden, isLabelHidden, isLight, isSearchIconVisible, label, MenuItem, onChange, onQuery, options, optionValueKey, value, ...originalProps }: SearchProps<OptionValue>): JSX.Element;
@@ -6,6 +6,8 @@ export type SelectProps<OptionValue extends OptionValueType = string> = Omit<Sel
6
6
  /** Used to pass something else than `window.document` as a base container to attach global events listeners. */
7
7
  baseContainer?: Document | HTMLDivElement | null | undefined;
8
8
  customSearch?: CustomSearch<Option<OptionValue>> | undefined;
9
+ /** Minimum search query length required to trigger custom search filtering. */
10
+ customSearchMinQueryLength?: number | undefined;
9
11
  error?: string | undefined;
10
12
  isCleanable?: boolean | undefined;
11
13
  isErrorMessageHidden?: boolean | undefined;
@@ -19,4 +21,4 @@ export type SelectProps<OptionValue extends OptionValueType = string> = Omit<Sel
19
21
  options: Option<OptionValue>[];
20
22
  value?: OptionValue | undefined;
21
23
  };
22
- export declare function Select<OptionValue extends OptionValueType = string>({ baseContainer, customSearch, disabled, error, isCleanable, isErrorMessageHidden, isLabelHidden, isLight, isUndefinedWhenDisabled, label, onChange, options, optionValueKey, searchable, value, ...originalProps }: SelectProps<OptionValue>): JSX.Element;
24
+ export declare function Select<OptionValue extends OptionValueType = string>({ baseContainer, customSearch, customSearchMinQueryLength, disabled, error, isCleanable, isErrorMessageHidden, isLabelHidden, isLight, isUndefinedWhenDisabled, label, onChange, options, optionValueKey, searchable, value, ...originalProps }: SelectProps<OptionValue>): JSX.Element;
package/index.js CHANGED
@@ -20739,9 +20739,11 @@ function normalizeString(text) {
20739
20739
  return cleanText.length > 0 ? cleanText : undefined;
20740
20740
  }
20741
20741
 
20742
- function Search({ baseContainer, className, error, isErrorMessageHidden = false, isLabelHidden, isLight = false, isSearchIconVisible = true, label, MenuItem, onChange, onQuery, options = [], optionValueKey, value, ...originalProps }) {
20742
+ function Search({ baseContainer, className, customSearch = undefined, customSearchMinQueryLength = 1, error, isErrorMessageHidden = false, isLabelHidden, isLight = false, isSearchIconVisible = true, label, MenuItem, onChange, onQuery, options = [], optionValueKey, value, ...originalProps }) {
20743
20743
  // eslint-disable-next-line no-null/no-null
20744
20744
  const boxRef = useRef(null);
20745
+ /** Instance of `CustomSearch` */
20746
+ const customSearchRef = useRef(customSearch);
20745
20747
  const queryRef = useRef(undefined);
20746
20748
  const data = useMemo(() => getRsuiteDataFromOptions(options, optionValueKey), [options, optionValueKey]);
20747
20749
  const [isOpen, setIsOpen] = useState(false);
@@ -20752,6 +20754,8 @@ function Search({ baseContainer, className, error, isErrorMessageHidden = false,
20752
20754
  const key = useKey([value, originalProps.disabled, originalProps.name]);
20753
20755
  const rsuiteValue = useMemo(() => getRsuiteValueFromOptionValue(value, optionValueKey), [value, optionValueKey]);
20754
20756
  const [inputValue, setInputValue] = useState(rsuiteValue);
20757
+ // Only used when `customSearch` prop is set
20758
+ const [controlledRsuiteData, setControlledRsuiteData] = useState(customSearch ? data : undefined);
20755
20759
  const close = useCallback(() => {
20756
20760
  setIsOpen(false);
20757
20761
  }, []);
@@ -20763,6 +20767,12 @@ function Search({ baseContainer, className, error, isErrorMessageHidden = false,
20763
20767
  if (!(typeof nextQuery === 'string')) {
20764
20768
  return;
20765
20769
  }
20770
+ if (customSearch && customSearchRef.current) {
20771
+ const nextControlledRsuiteData = nextQuery.trim().length >= customSearchMinQueryLength
20772
+ ? getRsuiteDataFromOptions(customSearchRef.current.find(nextQuery), optionValueKey)
20773
+ : data;
20774
+ setControlledRsuiteData(nextControlledRsuiteData);
20775
+ }
20766
20776
  queryRef.current = normalizeString(nextQuery);
20767
20777
  if (event.type === 'change') {
20768
20778
  setInputValue(nextQuery);
@@ -20777,7 +20787,7 @@ function Search({ baseContainer, className, error, isErrorMessageHidden = false,
20777
20787
  if (onQuery) {
20778
20788
  onQuery(queryRef.current);
20779
20789
  }
20780
- }, [onChange, onQuery]);
20790
+ }, [customSearch, onChange, onQuery, data, customSearchMinQueryLength, optionValueKey]);
20781
20791
  const handleSelect = useCallback((_, item) => {
20782
20792
  if (onChange) {
20783
20793
  onChange(item.optionValue);
@@ -20789,7 +20799,13 @@ function Search({ baseContainer, className, error, isErrorMessageHidden = false,
20789
20799
  useEffect(() => {
20790
20800
  forceUpdate();
20791
20801
  }, [forceUpdate]);
20792
- return (jsxs(Field$2, { className: controlledClassName, children: [jsx(Label, { disabled: originalProps.disabled, hasError: hasError, htmlFor: originalProps.name, isHidden: isLabelHidden, children: label }), jsxs(Box$c, { ref: boxRef, isLight: isLight, children: [boxRef.current && (jsx(StyledAutoComplete, { "$isLight": isLight, container: boxRef.current, data: data, id: originalProps.name, onChange: handleChange, onSelect: handleSelect, open: isOpen, renderMenuItem: (itemLabel, item) => MenuItem ? jsx(MenuItem, { item: item.value }) : itemLabel, value: inputValue, ...originalProps }, key)), inputValue && (jsxs(Fragment, { children: [jsx(StyledCloseButton, { accent: Accent.TERTIARY, color: THEME.color.slateGray, Icon: Close, isSearchIconVisible: isSearchIconVisible, onClick: clean, size: Size.SMALL }), isSearchIconVisible && jsx(Separator, { children: "|" })] })), isSearchIconVisible && jsx(StyledIconSearch, { color: THEME.color.slateGray, size: 20 })] }), !isErrorMessageHidden && hasError && jsx(FieldError, { children: controlledError })] }));
20802
+ return (jsxs(Field$2, { className: controlledClassName, children: [jsx(Label, { disabled: originalProps.disabled, hasError: hasError, htmlFor: originalProps.name, isHidden: isLabelHidden, children: label }), jsxs(Box$c, { ref: boxRef, isLight: isLight, children: [boxRef.current && (jsx(StyledAutoComplete, { "$isLight": isLight, container: boxRef.current,
20803
+ // When we use a custom search, we use `controlledRsuiteData` to provide the matching options (data),
20804
+ // when we don't, we don't need to control that and just pass the non-internally-controlled `rsuiteData`
20805
+ data: controlledRsuiteData || data,
20806
+ // When we use a custom search, we use `controlledRsuiteData` to provide the matching options (data),
20807
+ // that's why we send this "always true" filter to disable Rsuite SelectPicker internal search filtering
20808
+ filterBy: (customSearch ? () => true : undefined), id: originalProps.name, onChange: handleChange, onSelect: handleSelect, open: isOpen, renderMenuItem: (itemLabel, item) => MenuItem ? jsx(MenuItem, { item: item.value }) : itemLabel, value: inputValue, ...originalProps }, key)), inputValue && (jsxs(Fragment, { children: [jsx(StyledCloseButton, { accent: Accent.TERTIARY, color: THEME.color.slateGray, Icon: Close, isSearchIconVisible: isSearchIconVisible, onClick: clean, size: Size.SMALL }), isSearchIconVisible && jsx(Separator, { children: "|" })] })), isSearchIconVisible && jsx(StyledIconSearch, { color: THEME.color.slateGray, size: 20 })] }), !isErrorMessageHidden && hasError && jsx(FieldError, { children: controlledError })] }));
20793
20809
  }
20794
20810
  const StyledCloseButton = styled(IconButton) `
20795
20811
  cursor: pointer;
@@ -20812,7 +20828,7 @@ const StyledAutoComplete = styled(AutoComplete) `
20812
20828
  font-size: 13px;
20813
20829
  flex-grow: 1;
20814
20830
 
20815
- > input {
20831
+ .rs-input {
20816
20832
  background-color: ${p => (p.$isLight ? p.theme.color.white : p.theme.color.gainsboro)};
20817
20833
  border-width: 0 0 1px;
20818
20834
  border-color: ${p => (p.$isLight ? p.theme.color.white : p.theme.color.gainsboro)};
@@ -20824,7 +20840,11 @@ const StyledAutoComplete = styled(AutoComplete) `
20824
20840
 
20825
20841
  :focus {
20826
20842
  outline: unset;
20827
- border-color: ${p => p.theme.color.blueGray['100']};
20843
+ border-color: transparent;
20844
+ }
20845
+ :hover {
20846
+ outline: unset;
20847
+ border-color: transparent;
20828
20848
  }
20829
20849
  }
20830
20850
  `;
@@ -25681,47 +25701,19 @@ class CustomSearch {
25681
25701
  }
25682
25702
  }
25683
25703
 
25684
- function MultiSelect({ baseContainer, customSearch, disabled = false, error, isErrorMessageHidden = false, isLabelHidden = false, isLight = false, isUndefinedWhenDisabled = false, label, onChange, options, optionValueKey, searchable = false, value, ...originalProps }) {
25704
+ function MultiSelect({ baseContainer, customSearch, customSearchMinQueryLength = 1, disabled = false, error, isErrorMessageHidden = false, isLabelHidden = false, isLight = false, isUndefinedWhenDisabled = false, label, onChange, options, optionValueKey, searchable = false, value, ...originalProps }) {
25685
25705
  // eslint-disable-next-line no-null/no-null
25686
25706
  const boxRef = useRef(null);
25687
- /**
25688
- * Current list of option labels found by `CustomSearch.find()` for the current select search query
25689
- *
25690
- * @description
25691
- * `undefined` means that search query is empty, thus all labels should be a match.
25692
- */
25693
- const customSearchLabelMatchesRef = useRef(undefined);
25694
25707
  /** Instance of `CustomSearch` */
25695
25708
  const customSearchRef = useRef(customSearch);
25696
- /** Last search query (only used when `customSearch` prop is set) */
25697
- const previousSearchQueryRef = useRef('');
25698
- const [isOpen, setIsOpen] = useState(false);
25699
25709
  const controlledError = useMemo(() => normalizeString(error), [error]);
25700
25710
  const rsuiteData = useMemo(() => getRsuiteDataFromOptions(options, optionValueKey), [options, optionValueKey]);
25701
25711
  const hasError = useMemo(() => Boolean(controlledError), [controlledError]);
25702
25712
  const key = useKey([disabled, originalProps.name, value]);
25703
25713
  const selectedRsuiteValue = useMemo(() => (value || []).map(valueItem => getRsuiteValueFromOptionValue(valueItem, optionValueKey)), [optionValueKey, value]);
25704
- const searchBy = useMemo(() =>
25705
- // Since this function is called by a `.filter()` in Rsuite,
25706
- // we first prepare `CustomSearch` results in `handleSearch()` (called each time the search query changes),
25707
- // and we use the `customSearchLabelMatches` ref-stored results, in the form of option labels,
25708
- // to check if the current option label is part of these results.
25709
- // Note that options label are expected to be unique in order for this pattern to work.
25710
- customSearchRef.current
25711
- ? (query, _label, item) => {
25712
- if (!customSearchRef.current) {
25713
- throw new Error('`customSearchRef.current` is undefined.');
25714
- }
25715
- // Since this function will be called xN times, N being the number of options,
25716
- // we only want to update found option labels once each time the search query changes.
25717
- if (query !== previousSearchQueryRef.current) {
25718
- const nextCustomSearchLabelMatches = query.trim().length > 0 ? customSearchRef.current.find(query).map(option => option.label) : undefined;
25719
- customSearchLabelMatchesRef.current = nextCustomSearchLabelMatches;
25720
- previousSearchQueryRef.current = query;
25721
- }
25722
- return customSearchLabelMatchesRef.current ? customSearchLabelMatchesRef.current.includes(item.label) : true;
25723
- }
25724
- : undefined, []);
25714
+ // Only used when `customSearch` prop is set
25715
+ const [controlledRsuiteData, setControlledRsuiteData] = useState(customSearch ? rsuiteData : undefined);
25716
+ const [isOpen, setIsOpen] = useState(false);
25725
25717
  const { forceUpdate } = useForceUpdate();
25726
25718
  const close = useCallback(() => {
25727
25719
  setIsOpen(false);
@@ -25738,8 +25730,18 @@ function MultiSelect({ baseContainer, customSearch, disabled = false, error, isE
25738
25730
  }
25739
25731
  const nextValue = nextOptionRsuiteValues ? getOptionValuesFromRsuiteDataValues(nextOptionRsuiteValues) : [];
25740
25732
  const normalizedNextValue = nextValue.length > 0 ? nextValue : undefined;
25733
+ setControlledRsuiteData(rsuiteData);
25741
25734
  onChange(normalizedNextValue);
25742
- }, [getOptionValuesFromRsuiteDataValues, onChange]);
25735
+ }, [getOptionValuesFromRsuiteDataValues, onChange, rsuiteData]);
25736
+ const handleSearch = useCallback((nextQuery) => {
25737
+ if (!customSearchRef.current || nextQuery.trim().length < customSearchMinQueryLength) {
25738
+ return;
25739
+ }
25740
+ const nextControlledRsuiteData = nextQuery.trim().length >= customSearchMinQueryLength
25741
+ ? getRsuiteDataFromOptions(customSearchRef.current.find(nextQuery), optionValueKey)
25742
+ : rsuiteData;
25743
+ setControlledRsuiteData(nextControlledRsuiteData);
25744
+ }, [customSearchMinQueryLength, optionValueKey, rsuiteData]);
25743
25745
  const renderMenuItem = useCallback((node) => jsx("span", { title: String(node), children: String(node) }), []);
25744
25746
  const toggle = useCallback((event) => {
25745
25747
  let targetElement = event.target;
@@ -25759,7 +25761,13 @@ function MultiSelect({ baseContainer, customSearch, disabled = false, error, isE
25759
25761
  useEffect(() => {
25760
25762
  forceUpdate();
25761
25763
  }, [forceUpdate]);
25762
- return (jsxs(Field$2, { className: "Field-MultiSelect", children: [jsx(Label, { disabled: disabled, hasError: hasError, htmlFor: originalProps.name, isHidden: isLabelHidden, children: label }), jsx(Box$4, { ref: boxRef, "$hasError": hasError, "$isActive": isOpen, "$isLight": isLight, onClick: toggle, children: boxRef.current && (jsx(TagPicker, { container: boxRef.current, data: rsuiteData, disabled: disabled, id: originalProps.name, onChange: handleChange, onClick: toggle, open: isOpen, renderMenuItem: renderMenuItem, searchable: !!customSearch || searchable, searchBy: searchBy, value: selectedRsuiteValue, ...originalProps }, key)) }), !isErrorMessageHidden && hasError && jsx(FieldError, { children: controlledError })] }));
25764
+ return (jsxs(Field$2, { className: "Field-MultiSelect", children: [jsx(Label, { disabled: disabled, hasError: hasError, htmlFor: originalProps.name, isHidden: isLabelHidden, children: label }), jsx(Box$4, { ref: boxRef, "$hasError": hasError, "$isActive": isOpen, "$isLight": isLight, onClick: toggle, children: boxRef.current && (jsx(TagPicker, { container: boxRef.current,
25765
+ // When we use a custom search, we use `controlledRsuiteData` to provide the matching options (data),
25766
+ // when we don't, we don't need to control that and just pass the non-internally-controlled `rsuiteData`
25767
+ data: controlledRsuiteData || rsuiteData, disabled: disabled, id: originalProps.name, onChange: handleChange, onClick: toggle, onSearch: handleSearch, open: isOpen, renderMenuItem: renderMenuItem, searchable: !!customSearch || searchable,
25768
+ // When we use a custom search, we use `controlledRsuiteData` to provide the matching options (data),
25769
+ // that's why we send this "always true" filter to disable Rsuite TagPicker internal search filtering
25770
+ searchBy: (customSearch ? () => true : undefined), value: selectedRsuiteValue, ...originalProps }, key)) }), !isErrorMessageHidden && hasError && jsx(FieldError, { children: controlledError })] }));
25763
25771
  }
25764
25772
  const Box$4 = styled.div `
25765
25773
  position: relative;
@@ -34943,48 +34951,20 @@ const StyledFieldset = styled(Fieldset) `
34943
34951
  }
34944
34952
  `;
34945
34953
 
34946
- function Select({ baseContainer, customSearch, disabled = false, error, isCleanable = true, isErrorMessageHidden = false, isLabelHidden = false, isLight = false, isUndefinedWhenDisabled = false, label, onChange, options, optionValueKey, searchable = false, value, ...originalProps }) {
34954
+ function Select({ baseContainer, customSearch, customSearchMinQueryLength = 1, disabled = false, error, isCleanable = true, isErrorMessageHidden = false, isLabelHidden = false, isLight = false, isUndefinedWhenDisabled = false, label, onChange, options, optionValueKey, searchable = false, value, ...originalProps }) {
34947
34955
  // eslint-disable-next-line no-null/no-null
34948
34956
  const boxRef = useRef(null);
34949
- /**
34950
- * Current list of option labels found by `CustomSearch.find()` for the current select search query
34951
- *
34952
- * @description
34953
- * `undefined` means that search query is empty, thus all labels should be a match.
34954
- */
34955
- const customSearchLabelMatchesRef = useRef(undefined);
34956
34957
  /** Instance of `CustomSearch` */
34957
34958
  const customSearchRef = useRef(customSearch);
34958
- /** Last search query (only used when `customSearch` prop is set) */
34959
- const previousSearchQueryRef = useRef('');
34960
- const [isOpen, setIsOpen] = useState(false);
34961
34959
  const { forceUpdate } = useForceUpdate();
34962
34960
  const controlledError = useMemo(() => normalizeString(error), [error]);
34963
34961
  const rsuiteData = useMemo(() => getRsuiteDataFromOptions(options, optionValueKey), [options, optionValueKey]);
34964
34962
  const hasError = useMemo(() => Boolean(controlledError), [controlledError]);
34965
34963
  const key = useKey([disabled, originalProps.name, value]);
34966
34964
  const selectedRsuiteValue = useMemo(() => getRsuiteValueFromOptionValue(value, optionValueKey), [value, optionValueKey]);
34967
- const searchBy = useMemo(() =>
34968
- // Since this function is called by a `.filter()` in Rsuite,
34969
- // we first prepare `CustomSearch` results in `handleSearch()` (called each time the search query changes),
34970
- // and we use the `customSearchLabelMatches` ref-stored results, in the form of option labels,
34971
- // to check if the current option label is part of these results.
34972
- // Note that options label are expected to be unique in order for this pattern to work.
34973
- customSearchRef.current
34974
- ? (query, _label, item) => {
34975
- if (!customSearchRef.current) {
34976
- throw new Error('`customSearchRef.current` is undefined.');
34977
- }
34978
- // Since this function will be called xN times, N being the number of options,
34979
- // we only want to update found option labels once each time the search query changes.
34980
- if (query !== previousSearchQueryRef.current) {
34981
- const nextCustomSearchLabelMatches = query.trim().length > 0 ? customSearchRef.current.find(query).map(option => option.label) : undefined;
34982
- customSearchLabelMatchesRef.current = nextCustomSearchLabelMatches;
34983
- previousSearchQueryRef.current = query;
34984
- }
34985
- return customSearchLabelMatchesRef.current ? customSearchLabelMatchesRef.current.includes(item.label) : true;
34986
- }
34987
- : undefined, []);
34965
+ // Only used when `customSearch` prop is set
34966
+ const [controlledRsuiteData, setControlledRsuiteData] = useState(customSearch ? rsuiteData : undefined);
34967
+ const [isOpen, setIsOpen] = useState(false);
34988
34968
  const close = useCallback(() => {
34989
34969
  setIsOpen(false);
34990
34970
  }, []);
@@ -34994,6 +34974,15 @@ function Select({ baseContainer, customSearch, disabled = false, error, isCleana
34994
34974
  }
34995
34975
  onChange(undefined);
34996
34976
  }, [onChange]);
34977
+ const handleSearch = useCallback((nextQuery) => {
34978
+ if (!customSearchRef.current || nextQuery.trim().length < customSearchMinQueryLength) {
34979
+ return;
34980
+ }
34981
+ const nextControlledRsuiteData = nextQuery.trim().length >= customSearchMinQueryLength
34982
+ ? getRsuiteDataFromOptions(customSearchRef.current.find(nextQuery), optionValueKey)
34983
+ : rsuiteData;
34984
+ setControlledRsuiteData(nextControlledRsuiteData);
34985
+ }, [customSearchMinQueryLength, optionValueKey, rsuiteData]);
34997
34986
  const handleSelect = useCallback((_, selectedItem) => {
34998
34987
  close();
34999
34988
  if (onChange) {
@@ -35021,10 +35010,16 @@ function Select({ baseContainer, customSearch, disabled = false, error, isCleana
35021
35010
  useEffect(() => {
35022
35011
  forceUpdate();
35023
35012
  }, [forceUpdate]);
35024
- return (jsxs(Field$2, { className: "Field-Select", children: [jsx(Label, { disabled: disabled, hasError: hasError, htmlFor: originalProps.name, isHidden: isLabelHidden, children: label }), jsx(Box, { ref: boxRef, onClick: toggle, children: boxRef.current && (jsx(StyledSelectPicker, { "$isLight": isLight, cleanable: isCleanable, container: boxRef.current, data: rsuiteData, disabled: disabled, id: originalProps.name, onClean: handleClean,
35013
+ return (jsxs(Field$2, { className: "Field-Select", children: [jsx(Label, { disabled: disabled, hasError: hasError, htmlFor: originalProps.name, isHidden: isLabelHidden, children: label }), jsx(Box, { ref: boxRef, onClick: toggle, children: boxRef.current && (jsx(StyledSelectPicker, { "$isLight": isLight, cleanable: isCleanable, container: boxRef.current,
35014
+ // When we use a custom search, we use `controlledRsuiteData` to provide the matching options (data),
35015
+ // when we don't, we don't need to control that and just pass the non-internally-controlled `rsuiteData`
35016
+ data: controlledRsuiteData || rsuiteData, disabled: disabled, id: originalProps.name, onClean: handleClean, onSearch: handleSearch,
35025
35017
  // `as any` because we customized `ItemDataType` type by adding `optionValue`,
35026
35018
  // which generates an optional vs required type conflict
35027
- onSelect: handleSelect, open: isOpen, renderMenuItem: renderMenuItem, searchable: !!customSearch || searchable, searchBy: searchBy, value: selectedRsuiteValue, ...originalProps }, key)) }), !isErrorMessageHidden && hasError && jsx(FieldError, { children: controlledError })] }));
35019
+ onSelect: handleSelect, open: isOpen, renderMenuItem: renderMenuItem, searchable: !!customSearch || searchable,
35020
+ // When we use a custom search, we use `controlledRsuiteData` to provide the matching options (data),
35021
+ // that's why we send this "always true" filter to disable Rsuite SelectPicker internal search filtering
35022
+ searchBy: (customSearch ? () => true : undefined), value: selectedRsuiteValue, ...originalProps }, key)) }), !isErrorMessageHidden && hasError && jsx(FieldError, { children: controlledError })] }));
35028
35023
  }
35029
35024
  const StyledSelectPicker = styled(SelectPicker) `
35030
35025
  > .rs-picker-toggle {
@@ -35190,20 +35185,17 @@ const InputBox = styled.div `
35190
35185
 
35191
35186
  function FormikSearch({ name, ...originalProps }) {
35192
35187
  const [field, meta, helpers] = useField(name);
35193
- const error = meta.touched ? meta.error : undefined;
35194
35188
  const handleChange = useMemo(() => value => {
35195
- helpers.setTouched(true);
35196
35189
  helpers.setValue(value);
35197
35190
  },
35198
35191
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35199
35192
  // eslint-disable-next-line react-hooks/exhaustive-deps
35200
35193
  []);
35201
- return jsx(Search, { error: error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35194
+ return jsx(Search, { error: meta.error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35202
35195
  }
35203
35196
 
35204
35197
  function FormikCheckbox({ name, ...originalProps }) {
35205
35198
  const [field, meta, helpers] = useField(name);
35206
- const error = meta.touched ? meta.error : undefined;
35207
35199
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35208
35200
  // eslint-disable-next-line react-hooks/exhaustive-deps
35209
35201
  const handleChange = useMemo(() => helpers.setValue, []);
@@ -35211,56 +35203,49 @@ function FormikCheckbox({ name, ...originalProps }) {
35211
35203
  // A checkbox must initialize its Formik value on mount:
35212
35204
  // it wouldn't make sense to keep it as `undefined` since `undefined` means `false` in the case of a checkbox
35213
35205
  useEffect(() => {
35214
- helpers.setTouched(true);
35215
35206
  helpers.setValue(isChecked);
35216
35207
  },
35217
35208
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35218
35209
  // eslint-disable-next-line react-hooks/exhaustive-deps
35219
35210
  []);
35220
- return jsx(Checkbox, { checked: isChecked, error: error, name: name, onChange: handleChange, ...originalProps });
35211
+ return jsx(Checkbox, { checked: isChecked, error: meta.error, name: name, onChange: handleChange, ...originalProps });
35221
35212
  }
35222
35213
 
35223
35214
  function FormikCoordinatesInput({ name, ...originalProps }) {
35224
35215
  const [field, meta, helpers] = useField(name);
35225
35216
  // eslint-disable-next-line react-hooks/exhaustive-deps
35226
35217
  const defaultValue = useMemo(() => field.value, []);
35227
- const error = meta.touched ? meta.error : undefined;
35228
35218
  const handleChange = useMemo(() => value => {
35229
- helpers.setTouched(true);
35230
35219
  helpers.setValue(value);
35231
35220
  },
35232
35221
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35233
35222
  // eslint-disable-next-line react-hooks/exhaustive-deps
35234
35223
  []);
35235
- return jsx(CoordinatesInput, { defaultValue: defaultValue, error: error, onChange: handleChange, ...originalProps });
35224
+ return jsx(CoordinatesInput, { defaultValue: defaultValue, error: meta.error, onChange: handleChange, ...originalProps });
35236
35225
  }
35237
35226
 
35238
35227
  const UntypedDatePicker = DatePicker;
35239
35228
  function FormikDatePicker({ name, ...originalProps }) {
35240
35229
  const [field, meta, helpers] = useField(name);
35241
- const error = meta.touched ? meta.error : undefined;
35242
35230
  const handleChange = useMemo(() => value => {
35243
- helpers.setTouched(true);
35244
35231
  helpers.setValue(value);
35245
35232
  },
35246
35233
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35247
35234
  // eslint-disable-next-line react-hooks/exhaustive-deps
35248
35235
  []);
35249
- return jsx(UntypedDatePicker, { defaultValue: field.value, error: error, onChange: handleChange, ...originalProps });
35236
+ return jsx(UntypedDatePicker, { defaultValue: field.value, error: meta.error, onChange: handleChange, ...originalProps });
35250
35237
  }
35251
35238
 
35252
35239
  const UntypedDateRangePicker = DateRangePicker;
35253
35240
  function FormikDateRangePicker({ name, ...originalProps }) {
35254
35241
  const [field, meta, helpers] = useField(name);
35255
- const error = meta.touched ? meta.error : undefined;
35256
35242
  const handleChange = useMemo(() => value => {
35257
- helpers.setTouched(true);
35258
35243
  helpers.setValue(value);
35259
35244
  },
35260
35245
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35261
35246
  // eslint-disable-next-line react-hooks/exhaustive-deps
35262
35247
  []);
35263
- return jsx(UntypedDateRangePicker, { defaultValue: field.value, error: error, onChange: handleChange, ...originalProps });
35248
+ return (jsx(UntypedDateRangePicker, { defaultValue: field.value, error: meta.error, onChange: handleChange, ...originalProps }));
35264
35249
  }
35265
35250
 
35266
35251
  function FormikEffect({ onChange }) {
@@ -35273,93 +35258,79 @@ function FormikEffect({ onChange }) {
35273
35258
 
35274
35259
  function FormikMultiCheckbox({ name, ...originalProps }) {
35275
35260
  const [field, meta, helpers] = useField(name);
35276
- const error = meta.touched ? meta.error : undefined;
35277
35261
  const handleChange = useMemo(() => value => {
35278
- helpers.setTouched(true);
35279
35262
  helpers.setValue(value);
35280
35263
  },
35281
35264
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35282
35265
  // eslint-disable-next-line react-hooks/exhaustive-deps
35283
35266
  []);
35284
- return jsx(MultiCheckbox, { error: error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35267
+ return jsx(MultiCheckbox, { error: meta.error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35285
35268
  }
35286
35269
 
35287
35270
  function FormikMultiSelect({ name, ...originalProps }) {
35288
35271
  const [field, meta, helpers] = useField(name);
35289
- const error = meta.touched ? meta.error : undefined;
35290
35272
  const handleChange = useMemo(() => value => {
35291
- helpers.setTouched(true);
35292
35273
  helpers.setValue(value);
35293
35274
  },
35294
35275
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35295
35276
  // eslint-disable-next-line react-hooks/exhaustive-deps
35296
35277
  []);
35297
- return jsx(MultiSelect, { error: error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35278
+ return jsx(MultiSelect, { error: meta.error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35298
35279
  }
35299
35280
 
35300
35281
  function FormikMultiRadio({ name, ...originalProps }) {
35301
35282
  const [field, meta, helpers] = useField(name);
35302
- const error = meta.touched ? meta.error : undefined;
35303
35283
  const handleChange = useMemo(() => value => {
35304
- helpers.setTouched(true);
35305
35284
  helpers.setValue(value);
35306
35285
  },
35307
35286
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35308
35287
  // eslint-disable-next-line react-hooks/exhaustive-deps
35309
35288
  []);
35310
- return jsx(MultiRadio, { error: error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35289
+ return jsx(MultiRadio, { error: meta.error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35311
35290
  }
35312
35291
 
35313
35292
  function FormikNumberInput({ name, ...originalProps }) {
35314
35293
  const [field, meta, helpers] = useField(name);
35315
- const error = meta.touched ? meta.error : undefined;
35316
35294
  const handleChange = useMemo(() => value => {
35317
- helpers.setTouched(true);
35318
35295
  helpers.setValue(value);
35319
35296
  },
35320
35297
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35321
35298
  // eslint-disable-next-line react-hooks/exhaustive-deps
35322
35299
  []);
35323
- return jsx(NumberInput, { error: error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35300
+ return jsx(NumberInput, { error: meta.error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35324
35301
  }
35325
35302
 
35326
35303
  function FormikSelect({ name, ...originalProps }) {
35327
35304
  const [field, meta, helpers] = useField(name);
35328
- const error = meta.touched ? meta.error : undefined;
35329
35305
  const handleChange = useMemo(() => value => {
35330
- helpers.setTouched(true);
35331
35306
  helpers.setValue(value);
35332
35307
  },
35333
35308
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35334
35309
  // eslint-disable-next-line react-hooks/exhaustive-deps
35335
35310
  []);
35336
- return jsx(Select, { error: error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35311
+ return jsx(Select, { error: meta.error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35337
35312
  }
35338
35313
 
35339
35314
  function FormikTextarea({ name, ...originalProps }) {
35340
35315
  const [field, meta, helpers] = useField(name);
35341
- const error = meta.touched ? meta.error : undefined;
35342
35316
  const handleChange = useMemo(() => value => {
35343
- helpers.setTouched(true);
35344
35317
  helpers.setValue(value);
35345
35318
  },
35346
35319
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35347
35320
  // eslint-disable-next-line react-hooks/exhaustive-deps
35348
35321
  []);
35349
- return jsx(Textarea, { error: error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35322
+ return jsx(Textarea, { error: meta.error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35350
35323
  }
35351
35324
 
35352
35325
  function FormikTextInput({ name, ...originalProps }) {
35353
35326
  const [field, meta, helpers] = useField(name);
35354
- const error = meta.touched ? meta.error : undefined;
35355
35327
  const handleChange = useMemo(() => value => {
35356
- helpers.setTouched(true);
35357
35328
  helpers.setValue(value);
35358
35329
  },
35359
35330
  // We don't want to trigger infinite re-rendering since `helpers.setValue` changes after each rendering
35360
35331
  // eslint-disable-next-line react-hooks/exhaustive-deps
35361
35332
  []);
35362
- return jsx(TextInput, { error: error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35333
+ return jsx(TextInput, { error: meta.error, name: name, onChange: handleChange, value: field.value, ...originalProps });
35363
35334
  }
35364
35335
 
35365
35336
  function useFieldControl(value, onChange, defaultValueWhenUndefined) {