@reltio/components 1.4.2162 → 1.4.2163

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.
@@ -56,31 +56,43 @@ var __rest = (this && this.__rest) || function (s, e) {
56
56
  }
57
57
  return t;
58
58
  };
59
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
60
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
61
+ if (ar || !(i in from)) {
62
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
63
+ ar[i] = from[i];
64
+ }
65
+ }
66
+ return to.concat(ar || Array.prototype.slice.call(from));
67
+ };
59
68
  import React, { useState, useCallback, useMemo, useContext, useEffect } from 'react';
60
69
  import classnames from 'classnames';
61
- import { identity } from 'ramda';
70
+ import { identity, path } from 'ramda';
62
71
  import { useContextSelector } from '@fluentui/react-context-selector';
63
72
  import Autocomplete from '@mui/material/Autocomplete';
64
- import ListItem from '@mui/material/ListItem';
65
- import ListItemText from '@mui/material/ListItemText';
73
+ import Button from '@mui/material/Button';
74
+ import ChevronRightIcon from '@mui/icons-material/ChevronRight';
66
75
  import LinearProgress from '@mui/material/LinearProgress';
67
- import Typography from '@mui/material/Typography';
76
+ import ListItemButton from '@mui/material/ListItemButton';
68
77
  import TextField from '@mui/material/TextField';
69
- import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
78
+ import Typography from '@mui/material/Typography';
70
79
  import i18n from 'ui-i18n';
71
- import { searchAddresses, fetchAddressDetails, debounce, AddressType } from '@reltio/mdm-sdk';
80
+ import { searchAddresses, fetchAddressDetails, debounce, AddressType, isEmptyValue } from '@reltio/mdm-sdk';
72
81
  import { AddressAutoCompleteContext } from '../contexts/AutoCompleteContext';
73
82
  import { AttributeValueContext } from '../contexts/AttributeValueContext';
74
- import { convertSearchResultToOption, getOptionLabel, isOptionEqualToValue } from './helpers';
83
+ import { convertSearchResultToOption, getOptionLabel, isMinLength, isOptionEqualToValue } from './helpers';
75
84
  import { useSafePromise } from '../hooks/useSafePromise';
85
+ import { ExpandedValueTooltip } from '../ExpandedValueTooltip';
86
+ import { EmptySearchResult } from '../EmptySearchResult';
76
87
  import styles from './AddressAutocompleteEditor.module.css';
77
88
  export var INPUT_DEBOUNCE_INTERVAL = 400;
89
+ var EMPTY_OPTION_ID = 'empty_option_id';
78
90
  export var AddressAutocompleteEditor = function (_a) {
79
91
  var _b = _a.value, value = _b === void 0 ? '' : _b, onChange = _a.onChange, InputProps = _a.InputProps, _c = _a.fullWidth, fullWidth = _c === void 0 ? true : _c, otherProps = __rest(_a, ["value", "onChange", "InputProps", "fullWidth"]);
80
92
  var _d = useState(value), inputValue = _d[0], setInputValue = _d[1];
81
- var _e = useState([]), options = _e[0], setOptions = _e[1];
82
- var _f = useState(false), loading = _f[0], setLoading = _f[1];
83
- var _g = useState(false), detailsLoading = _g[0], setDetailsLoading = _g[1];
93
+ var _e = useState(null), options = _e[0], setOptions = _e[1];
94
+ var _f = useState([]), previousOptions = _f[0], setPreviousOptions = _f[1];
95
+ var _g = useState(false), loading = _g[0], setLoading = _g[1];
84
96
  var _h = useState(false), open = _h[0], setOpen = _h[1];
85
97
  var searchSafePromise = useSafePromise();
86
98
  var detailsSafePromise = useSafePromise();
@@ -91,45 +103,61 @@ export var AddressAutocompleteEditor = function (_a) {
91
103
  var countries = autocompleteSettings.countries, countryNames = autocompleteSettings.countryNames, limit = autocompleteSettings.limit, minSearchTextLen = autocompleteSettings.minSearchTextLen;
92
104
  var countriesHash = countries.toString();
93
105
  useEffect(function () {
94
- setOptions([]);
106
+ setOptions(null);
107
+ setPreviousOptions([]);
95
108
  }, [countriesHash]);
96
- var fetchAddressSuggestions = useCallback(function (searchTerm) { return __awaiter(void 0, void 0, void 0, function () {
97
- var results, convertedOptions, error_1;
98
- return __generator(this, function (_a) {
99
- switch (_a.label) {
109
+ var fetchAddressSuggestions = useCallback(function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
110
+ var results, _c, convertedOptions, error_1;
111
+ var searchTerm = _b.searchTerm, container = _b.container;
112
+ return __generator(this, function (_d) {
113
+ switch (_d.label) {
100
114
  case 0:
101
115
  setLoading(true);
102
- _a.label = 1;
116
+ _d.label = 1;
103
117
  case 1:
104
- _a.trys.push([1, 3, 4, 5]);
105
- return [4 /*yield*/, searchSafePromise(searchAddresses({
106
- text: searchTerm,
107
- limit: limit,
108
- countries: countries
109
- }))];
118
+ _d.trys.push([1, 6, 7, 8]);
119
+ if (!container) return [3 /*break*/, 3];
120
+ return [4 /*yield*/, searchSafePromise(searchAddresses({ container: container }))];
110
121
  case 2:
111
- results = _a.sent();
122
+ _c = _d.sent();
123
+ return [3 /*break*/, 5];
124
+ case 3: return [4 /*yield*/, searchSafePromise(searchAddresses({
125
+ text: searchTerm,
126
+ limit: limit,
127
+ countries: countries
128
+ }))];
129
+ case 4:
130
+ _c = _d.sent();
131
+ _d.label = 5;
132
+ case 5:
133
+ results = _c;
112
134
  convertedOptions = results.map(convertSearchResultToOption);
135
+ if (container) {
136
+ setPreviousOptions(options || []);
137
+ }
138
+ else {
139
+ setPreviousOptions([]);
140
+ }
113
141
  setOptions(convertedOptions);
114
- return [3 /*break*/, 5];
115
- case 3:
116
- error_1 = _a.sent();
142
+ return [3 /*break*/, 8];
143
+ case 6:
144
+ error_1 = _d.sent();
117
145
  console.warn('Error fetching address suggestions:', error_1);
118
146
  setOptions([]);
119
- return [3 /*break*/, 5];
120
- case 4:
147
+ return [3 /*break*/, 8];
148
+ case 7:
121
149
  setLoading(false);
122
150
  return [7 /*endfinally*/];
123
- case 5: return [2 /*return*/];
151
+ case 8: return [2 /*return*/];
124
152
  }
125
153
  });
126
- }); }, [countries, limit, searchSafePromise]);
154
+ }); }, [countries, limit, searchSafePromise, options]);
127
155
  var fetchAddressDetailsById = useCallback(function (addressId) { return __awaiter(void 0, void 0, void 0, function () {
128
156
  var error_2;
129
157
  return __generator(this, function (_a) {
130
158
  switch (_a.label) {
131
159
  case 0:
132
- setDetailsLoading(true);
160
+ setLoading(true);
133
161
  _a.label = 1;
134
162
  case 1:
135
163
  _a.trys.push([1, 3, 4, 5]);
@@ -138,9 +166,9 @@ export var AddressAutocompleteEditor = function (_a) {
138
166
  case 3:
139
167
  error_2 = _a.sent();
140
168
  console.warn('Error fetching address details:', error_2);
141
- return [2 /*return*/, null];
169
+ return [3 /*break*/, 5];
142
170
  case 4:
143
- setDetailsLoading(false);
171
+ setLoading(false);
144
172
  return [7 /*endfinally*/];
145
173
  case 5: return [2 /*return*/];
146
174
  }
@@ -149,13 +177,17 @@ export var AddressAutocompleteEditor = function (_a) {
149
177
  var debouncedFetchAddressSuggestions = useCallback(debounce(fetchAddressSuggestions, INPUT_DEBOUNCE_INTERVAL), [
150
178
  fetchAddressSuggestions
151
179
  ]);
152
- var handleInputChange = useCallback(function (event, newInputValue) {
180
+ var handleInputChange = useCallback(function (event, newInputValue, reason) {
181
+ if (reason === 'reset') {
182
+ return;
183
+ }
153
184
  setInputValue(newInputValue);
154
- if (newInputValue.trim().length >= minSearchTextLen) {
155
- debouncedFetchAddressSuggestions(newInputValue);
185
+ if (isMinLength(newInputValue, minSearchTextLen)) {
186
+ debouncedFetchAddressSuggestions({ searchTerm: newInputValue });
156
187
  }
157
188
  else {
158
189
  setOptions([]);
190
+ setPreviousOptions([]);
159
191
  }
160
192
  }, [debouncedFetchAddressSuggestions, minSearchTextLen]);
161
193
  var handleOptionSelect = useCallback(function (event, selectedOption) { return __awaiter(void 0, void 0, void 0, function () {
@@ -165,9 +197,11 @@ export var AddressAutocompleteEditor = function (_a) {
165
197
  case 0:
166
198
  if (!selectedOption) return [3 /*break*/, 3];
167
199
  if (selectedOption.type === AddressType.Container) {
200
+ fetchAddressSuggestions({ container: selectedOption.id });
168
201
  return [2 /*return*/];
169
202
  }
170
203
  setInputValue(selectedOption.fullAddress);
204
+ setPreviousOptions([]);
171
205
  if (onChange) {
172
206
  onChange(selectedOption.fullAddress);
173
207
  }
@@ -189,35 +223,69 @@ export var AddressAutocompleteEditor = function (_a) {
189
223
  case 4: return [2 /*return*/];
190
224
  }
191
225
  });
192
- }); }, [fetchAddressDetailsById, onChange, onPopulateAttributes, attributeTypeUri, valueUri]);
226
+ }); }, [fetchAddressDetailsById, fetchAddressSuggestions, onChange, onPopulateAttributes, attributeTypeUri, valueUri]);
227
+ var handleFocus = useCallback(function () {
228
+ if (!options && isMinLength(inputValue, minSearchTextLen)) {
229
+ debouncedFetchAddressSuggestions({ searchTerm: inputValue });
230
+ }
231
+ setOpen(true);
232
+ }, [debouncedFetchAddressSuggestions, inputValue, minSearchTextLen, options]);
193
233
  var handleBlur = useCallback(function () {
194
234
  setOpen(false);
195
235
  }, []);
196
236
  var renderOption = useCallback(function (props, option) {
197
237
  var _a;
238
+ if (option.id === EMPTY_OPTION_ID) {
239
+ return null;
240
+ }
198
241
  var isContainer = option.type === AddressType.Container;
199
- return (React.createElement(ListItem, __assign({}, props, { className: classnames(styles.option, (_a = {}, _a[styles.containerOption] = isContainer, _a)) }),
200
- React.createElement(ListItemText, { primary: option.fullAddress, primaryTypographyProps: { variant: 'body1' }, secondaryTypographyProps: { variant: 'body2', color: 'textSecondary' } }),
201
- isContainer && (React.createElement("div", null,
202
- React.createElement(ArrowForwardIosIcon, null)))));
203
- }, []);
204
- var renderInput = useCallback(function (props) { return (React.createElement(TextField, __assign({}, otherProps, props, { placeholder: i18n.text('Start typing to autocomplete'), fullWidth: fullWidth, onBlur: handleBlur, InputProps: __assign(__assign(__assign({}, InputProps), props.InputProps), { endAdornment: React.createElement(React.Fragment, null, props.InputProps.endAdornment) }) }))); }, [fullWidth, handleBlur, InputProps, otherProps]);
242
+ return (React.createElement(ListItemButton, __assign({}, props, { className: classnames(styles.option, (_a = {}, _a[styles.containerOption] = isContainer, _a)), component: "li", disabled: loading }),
243
+ React.createElement(ExpandedValueTooltip, { value: option.fullAddress },
244
+ React.createElement(Typography, { variant: "body2", className: styles.optionText }, option.fullAddress)),
245
+ isContainer && (React.createElement("div", { className: styles.containerForwardIcon },
246
+ React.createElement(ChevronRightIcon, null)))));
247
+ }, [loading]);
248
+ var renderInput = useCallback(function (props) {
249
+ var _a;
250
+ return (React.createElement(TextField, __assign({}, props, otherProps, { placeholder: i18n.text('Start typing to autocomplete'), fullWidth: fullWidth, onBlur: handleBlur, InputProps: __assign(__assign(__assign({}, props.InputProps), InputProps), { classes: __assign(__assign({}, InputProps === null || InputProps === void 0 ? void 0 : InputProps.classes), { root: classnames(styles.textFieldRoot, path(['classes', 'root'], InputProps)), input: classnames(styles.textFieldInput, path(['classes', 'input'], InputProps)), underline: classnames((_a = {}, _a[styles.underline] = isEmptyValue(value), _a)) }) }) })));
251
+ }, [fullWidth, handleBlur, InputProps, otherProps, value]);
252
+ var handleBackToAllSearchResults = useCallback(function () {
253
+ setOptions(previousOptions);
254
+ setPreviousOptions([]);
255
+ }, [previousOptions]);
205
256
  var renderListbox = useCallback(React.forwardRef(function ListboxComponent(props, ref) {
206
- return (React.createElement(React.Fragment, null,
207
- detailsLoading && React.createElement(LinearProgress, { color: "primary" }),
208
- React.createElement("ul", __assign({ ref: ref }, props),
209
- React.createElement("li", { className: styles.countryHeader },
210
- React.createElement(Typography, { variant: "body2", fontWeight: 500 },
257
+ var onMouseDown = props.onMouseDown, restProps = __rest(props, ["onMouseDown"]);
258
+ var shouldShowEmptyState = (options === null || options === void 0 ? void 0 : options.length) === 0 && isMinLength(inputValue, minSearchTextLen) && !loading;
259
+ var shouldShowMinLengthMessage = !isMinLength(inputValue, minSearchTextLen) && !loading;
260
+ var shouldShowOptions = (options === null || options === void 0 ? void 0 : options.length) > 0 && !shouldShowMinLengthMessage;
261
+ var countriesLabel = countryNames.join(', ');
262
+ return (React.createElement("div", { onMouseDown: onMouseDown },
263
+ loading && React.createElement(LinearProgress, { className: styles.loadingProgress, color: "primary" }),
264
+ React.createElement("div", { className: styles.countryHeader },
265
+ React.createElement(ExpandedValueTooltip, { value: countriesLabel },
266
+ React.createElement(Typography, { variant: "body2", color: "textSecondary", className: styles.headerCountries },
211
267
  i18n.text('Searching in'),
212
268
  ": ",
213
- countryNames.join(', ')),
214
- React.createElement(Typography, { variant: "caption", color: "textSecondary" }, i18n.text('To search in a different country, update the Country field'))),
215
- props.children)));
216
- }), [detailsLoading, countryNames]);
269
+ countriesLabel)),
270
+ React.createElement(Typography, { variant: "caption", color: "textSecondary", className: styles.headerHelper }, i18n.text('To search in a different country, update the Country field'))),
271
+ React.createElement("ul", __assign({ ref: ref }, restProps, { className: styles.listbox }),
272
+ shouldShowMinLengthMessage && (React.createElement("li", { className: styles.emptyState },
273
+ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, i18n.text('Please enter at least ${minSearchTextLen} characters', {
274
+ minSearchTextLen: minSearchTextLen
275
+ })))),
276
+ shouldShowEmptyState && (React.createElement("li", { className: styles.emptyState },
277
+ React.createElement(EmptySearchResult, { className: styles.noData }))),
278
+ shouldShowOptions && props.children),
279
+ previousOptions.length > 0 && (React.createElement("div", { className: styles.backToAllResults },
280
+ React.createElement(Button, { variant: "text", onClick: handleBackToAllSearchResults, color: "primary", disabled: loading, classes: { root: styles.backToAllResultsButton } }, i18n.text('Back to all results'))))));
281
+ }), [loading, countryNames, previousOptions.length, (options || []).length, inputValue, minSearchTextLen]);
217
282
  var openOptionsList = useCallback(function (_event) {
218
283
  setOpen(true);
219
284
  }, []);
220
- return (React.createElement(Autocomplete, { className: styles.root, open: open, onOpen: openOptionsList, value: inputValue, onChange: handleOptionSelect, inputValue: inputValue, onInputChange: handleInputChange, options: options, getOptionLabel: getOptionLabel, loading: loading, renderInput: renderInput, renderOption: renderOption, renderGroup: function (params) { return params.children; }, ListboxComponent: renderListbox, noOptionsText: inputValue.trim().length < minSearchTextLen
221
- ? i18n.text('Please enter at least {{minSearchTextLen}} characters', { minSearchTextLen: minSearchTextLen })
222
- : i18n.text('No addresses found'), loadingText: i18n.text('Searching addresses...'), filterOptions: identity, isOptionEqualToValue: isOptionEqualToValue, popupIcon: null, clearIcon: null, freeSolo: true, "data-reltio-id": "address-autocomplete-editor" }));
285
+ var displayOptions = useMemo(function () {
286
+ return __spreadArray([{ id: EMPTY_OPTION_ID, type: AddressType.Address, fullAddress: '' }], (options || []), true);
287
+ }, [options]);
288
+ return (React.createElement(Autocomplete, { className: styles.root, open: open, onOpen: openOptionsList, value: inputValue, onChange: handleOptionSelect, inputValue: inputValue, onInputChange: handleInputChange, onFocus: handleFocus, options: displayOptions, getOptionLabel: getOptionLabel, renderInput: renderInput, renderOption: renderOption, renderGroup: function (params) { return params.children; }, ListboxComponent: renderListbox, classes: {
289
+ paper: styles.popupContainer
290
+ }, filterOptions: identity, isOptionEqualToValue: isOptionEqualToValue, popupIcon: null, clearIcon: null, freeSolo: true, "data-reltio-id": "address-autocomplete-editor" }));
223
291
  };
@@ -1,9 +1,9 @@
1
- const styles = {"root":"AddressAutocompleteEditor-root--1FDrD","countryHeader":"AddressAutocompleteEditor-countryHeader--k5tRp","loadingContainer":"AddressAutocompleteEditor-loadingContainer--j-U45","option":"AddressAutocompleteEditor-option--3hah-","containerOption":"AddressAutocompleteEditor-containerOption--aLgUn","listbox":"AddressAutocompleteEditor-listbox--cZCpg"};
1
+ const styles = {"root":"AddressAutocompleteEditor-root--1FDrD","textFieldRoot":"AddressAutocompleteEditor-textFieldRoot--udDvK","textFieldInput":"AddressAutocompleteEditor-textFieldInput--Xf-Cf","underline":"AddressAutocompleteEditor-underline--ZNYqN","countryHeader":"AddressAutocompleteEditor-countryHeader--k5tRp","headerCountries":"AddressAutocompleteEditor-headerCountries--WoYl5","headerHelper":"AddressAutocompleteEditor-headerHelper--XPT5S","loadingState":"AddressAutocompleteEditor-loadingState--PRwRK","emptyState":"AddressAutocompleteEditor-emptyState--s8M04","option":"AddressAutocompleteEditor-option--3hah-","popupContainer":"AddressAutocompleteEditor-popupContainer--360Kv","listbox":"AddressAutocompleteEditor-listbox--cZCpg","optionText":"AddressAutocompleteEditor-optionText--IcsBw","containerForwardIcon":"AddressAutocompleteEditor-containerForwardIcon--Ke5rd","backToAllResults":"AddressAutocompleteEditor-backToAllResults--2wqzi","backToAllResultsButton":"AddressAutocompleteEditor-backToAllResultsButton--T5R-3","loadingProgress":"AddressAutocompleteEditor-loadingProgress--AfWtc"};
2
2
  if (typeof document !== 'undefined') {
3
3
  const head = document.head || document.getElementsByTagName('head')[0]
4
4
  const style = document.createElement('style');
5
5
  style.type = 'text/css'
6
- style.innerHTML = `.AddressAutocompleteEditor-root--1FDrD{width:100%}.AddressAutocompleteEditor-countryHeader--k5tRp{background-color:rgba(0,0,0,.04);border-bottom:1px solid rgba(0,0,0,.12);font-weight:500;padding:8px 16px}.AddressAutocompleteEditor-loadingContainer--j-U45{align-items:center;display:flex;justify-content:center;padding:16px}.AddressAutocompleteEditor-option--3hah-{padding:8px 16px}.AddressAutocompleteEditor-containerOption--aLgUn:hover{background-color:rgba(0,0,0,.04)!important}.AddressAutocompleteEditor-listbox--cZCpg{padding:0}`;
6
+ style.innerHTML = `.AddressAutocompleteEditor-root--1FDrD{width:100%}.AddressAutocompleteEditor-root--1FDrD .AddressAutocompleteEditor-textFieldRoot--udDvK{font-size:14px;padding:0}:is(.AddressAutocompleteEditor-root--1FDrD .AddressAutocompleteEditor-textFieldRoot--udDvK) .AddressAutocompleteEditor-textFieldInput--Xf-Cf{height:19px;line-height:19px;padding:10px 12px 11px!important}:is(.AddressAutocompleteEditor-root--1FDrD .AddressAutocompleteEditor-underline--ZNYqN):before{display:none}.AddressAutocompleteEditor-countryHeader--k5tRp{padding:12px 12px 8px}.AddressAutocompleteEditor-countryHeader--k5tRp .AddressAutocompleteEditor-headerCountries--WoYl5{font-size:12px;letter-spacing:0;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.AddressAutocompleteEditor-countryHeader--k5tRp .AddressAutocompleteEditor-headerHelper--XPT5S{font-size:10px;letter-spacing:0}.AddressAutocompleteEditor-loadingState--PRwRK{padding:16px;text-align:center}.AddressAutocompleteEditor-emptyState--s8M04{padding:8px}.AddressAutocompleteEditor-option--3hah-{padding:4px 12px}.AddressAutocompleteEditor-popupContainer--360Kv{min-width:170px}.AddressAutocompleteEditor-listbox--cZCpg{margin:0;max-height:300px;overflow-y:auto;padding:0}.AddressAutocompleteEditor-listbox--cZCpg li:first-child{border-top:1px solid rgba(0,0,0,.12);padding-top:7px}.AddressAutocompleteEditor-optionText--IcsBw{font-size:14px;letter-spacing:.24px;line-height:16px;overflow:hidden;padding:4px 0;text-overflow:ellipsis;white-space:nowrap;width:100%}.AddressAutocompleteEditor-containerForwardIcon--Ke5rd{height:24px}.AddressAutocompleteEditor-backToAllResults--2wqzi{border-top:1px solid rgba(0,0,0,.12)}.AddressAutocompleteEditor-backToAllResultsButton--T5R-3{letter-spacing:0}.AddressAutocompleteEditor-loadingProgress--AfWtc{left:0;position:absolute;right:0;top:0}`;
7
7
  head.appendChild(style);
8
8
  }
9
9
  export default styles;
@@ -93,6 +93,14 @@ var mockAddressSearchResults = [
93
93
  Description: 'Jose Malhoa 22'
94
94
  }
95
95
  ];
96
+ var mockAddressContainerSearchResults = [
97
+ {
98
+ Id: 'container-1',
99
+ Type: AddressType.Container,
100
+ Text: 'Container Option',
101
+ Description: 'Container'
102
+ }
103
+ ];
96
104
  var mockAddressDetails = [
97
105
  {
98
106
  Id: 'address-1',
@@ -282,15 +290,34 @@ describe('AddressAutocompleteEditor', function () {
282
290
  return [4 /*yield*/, user.paste('Lisbon')];
283
291
  case 2:
284
292
  _a.sent();
285
- expect(screen.getByText('Searching addresses...')).toBeInTheDocument();
293
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
286
294
  resolveSearch([]);
287
- return [4 /*yield*/, waitForElementToBeRemoved(function () { return screen.queryByText('Searching addresses...'); })];
295
+ return [4 /*yield*/, waitForElementToBeRemoved(function () { return screen.queryByRole('progressbar'); })];
288
296
  case 3:
289
297
  _a.sent();
290
298
  return [2 /*return*/];
291
299
  }
292
300
  });
293
301
  }); });
302
+ it('should show empty state when no options are found', function () { return __awaiter(void 0, void 0, void 0, function () {
303
+ var user, input;
304
+ return __generator(this, function (_a) {
305
+ switch (_a.label) {
306
+ case 0:
307
+ mockSearchAddresses.mockResolvedValue([]);
308
+ user = setUp().user;
309
+ input = screen.getByRole('combobox');
310
+ return [4 /*yield*/, user.click(input)];
311
+ case 1:
312
+ _a.sent();
313
+ return [4 /*yield*/, user.paste('Lisbon')];
314
+ case 2:
315
+ _a.sent();
316
+ expect(screen.getByText('No results found')).toBeInTheDocument();
317
+ return [2 /*return*/];
318
+ }
319
+ });
320
+ }); });
294
321
  describe('Address search', function () {
295
322
  it('should fetch address suggestions when user types minimum characters', function () { return __awaiter(void 0, void 0, void 0, function () {
296
323
  var user, input;
@@ -378,9 +405,7 @@ describe('AddressAutocompleteEditor', function () {
378
405
  switch (_a.label) {
379
406
  case 0:
380
407
  onChange = jest.fn();
381
- mockSearchAddresses.mockResolvedValue([
382
- { Id: 'container-1', Type: AddressType.Container, Text: 'Container Option', Description: 'Container' }
383
- ]);
408
+ mockSearchAddresses.mockResolvedValue(mockAddressContainerSearchResults);
384
409
  user = setUp({ onChange: onChange }).user;
385
410
  input = screen.getByRole('combobox');
386
411
  return [4 /*yield*/, user.click(input)];
@@ -455,6 +480,76 @@ describe('AddressAutocompleteEditor', function () {
455
480
  }
456
481
  });
457
482
  }); });
483
+ it('should request address list for container options', function () { return __awaiter(void 0, void 0, void 0, function () {
484
+ var onChange, user, input, option;
485
+ return __generator(this, function (_a) {
486
+ switch (_a.label) {
487
+ case 0:
488
+ onChange = jest.fn();
489
+ mockSearchAddresses.mockResolvedValue(mockAddressContainerSearchResults);
490
+ user = setUp({ onChange: onChange }).user;
491
+ input = screen.getByRole('combobox');
492
+ return [4 /*yield*/, user.click(input)];
493
+ case 1:
494
+ _a.sent();
495
+ return [4 /*yield*/, user.paste('Lisbon')];
496
+ case 2:
497
+ _a.sent();
498
+ expect(screen.getByText('Container Option')).toBeInTheDocument();
499
+ expect(mockSearchAddresses).toHaveBeenCalledWith({
500
+ text: 'Lisbon',
501
+ limit: 10,
502
+ countries: ['PT', 'US']
503
+ });
504
+ mockSearchAddresses.mockReset();
505
+ mockSearchAddresses.mockResolvedValue(mockAddressSearchResults);
506
+ option = screen.getByText('Container Option');
507
+ return [4 /*yield*/, user.click(option)];
508
+ case 3:
509
+ _a.sent();
510
+ expect(mockSearchAddresses).toHaveBeenCalledWith({
511
+ container: 'container-1'
512
+ });
513
+ expect(screen.getByText('Avenida Jose Malhoa 22, Lisbon')).toBeInTheDocument();
514
+ expect(screen.queryByText('Container Option')).not.toBeInTheDocument();
515
+ expect(onChange).not.toHaveBeenCalled();
516
+ expect(mockFetchAddressDetails).not.toHaveBeenCalled();
517
+ return [2 /*return*/];
518
+ }
519
+ });
520
+ }); });
521
+ it('should back to all search results after selecting container option', function () { return __awaiter(void 0, void 0, void 0, function () {
522
+ var onChange, user, input, option;
523
+ return __generator(this, function (_a) {
524
+ switch (_a.label) {
525
+ case 0:
526
+ onChange = jest.fn();
527
+ mockSearchAddresses.mockResolvedValue(mockAddressContainerSearchResults);
528
+ user = setUp({ onChange: onChange }).user;
529
+ input = screen.getByRole('combobox');
530
+ return [4 /*yield*/, user.click(input)];
531
+ case 1:
532
+ _a.sent();
533
+ return [4 /*yield*/, user.paste('Lisbon')];
534
+ case 2:
535
+ _a.sent();
536
+ expect(screen.queryByText('Back to all results')).not.toBeInTheDocument();
537
+ mockSearchAddresses.mockReset();
538
+ mockSearchAddresses.mockResolvedValue(mockAddressSearchResults);
539
+ option = screen.getByText('Container Option');
540
+ return [4 /*yield*/, user.click(option)];
541
+ case 3:
542
+ _a.sent();
543
+ expect(screen.getByText('Back to all results')).toBeInTheDocument();
544
+ return [4 /*yield*/, user.click(screen.getByText('Back to all results'))];
545
+ case 4:
546
+ _a.sent();
547
+ expect(screen.queryByText('Back to all results')).not.toBeInTheDocument();
548
+ expect(screen.getByText('Container Option')).toBeInTheDocument();
549
+ return [2 /*return*/];
550
+ }
551
+ });
552
+ }); });
458
553
  });
459
554
  describe('Autocomplete settings', function () {
460
555
  it('should use default autocomplete settings, when no settings from metadata', function () { return __awaiter(void 0, void 0, void 0, function () {
@@ -506,6 +601,7 @@ describe('AddressAutocompleteEditor', function () {
506
601
  case 2:
507
602
  _a.sent();
508
603
  expect(mockSearchAddresses).not.toHaveBeenCalled();
604
+ expect(screen.getByText('Please enter at least 3 characters')).toBeInTheDocument();
509
605
  return [4 /*yield*/, user.paste('sbon')];
510
606
  case 3:
511
607
  _a.sent();
@@ -8,3 +8,4 @@ export type AddressOption = {
8
8
  export declare const convertSearchResultToOption: (result: AddressSearchResult) => AddressOption;
9
9
  export declare const getOptionLabel: (option: AddressOption | string) => string;
10
10
  export declare const isOptionEqualToValue: (option: AddressOption, value: string) => boolean;
11
+ export declare const isMinLength: (value: string, minLength: number) => boolean;
@@ -16,3 +16,6 @@ export var isOptionEqualToValue = function (option, value) {
16
16
  }
17
17
  return option.fullAddress === value;
18
18
  };
19
+ export var isMinLength = function (value, minLength) {
20
+ return (value === null || value === void 0 ? void 0 : value.trim().length) >= minLength;
21
+ };
@@ -1 +1 @@
1
- export declare const useStyles: (props?: any) => import("@mui/styles").ClassNameMap<"dense" | "icon" | "subHeader" | "moreButton" | "buttonLabel" | "moreAttributes" | "popupContainer" | "moreAttributesPopup" | "noResultsCaptionContainer">;
1
+ export declare const useStyles: (props?: any) => import("@mui/styles").ClassNameMap<"dense" | "icon" | "subHeader" | "moreButton" | "buttonLabel" | "popupContainer" | "moreAttributes" | "moreAttributesPopup" | "noResultsCaptionContainer">;
@@ -80,6 +80,15 @@ var __rest = (this && this.__rest) || function (s, e) {
80
80
  }
81
81
  return t;
82
82
  };
83
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
84
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
85
+ if (ar || !(i in from)) {
86
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
87
+ ar[i] = from[i];
88
+ }
89
+ }
90
+ return to.concat(ar || Array.prototype.slice.call(from));
91
+ };
83
92
  var __importDefault = (this && this.__importDefault) || function (mod) {
84
93
  return (mod && mod.__esModule) ? mod : { "default": mod };
85
94
  };
@@ -90,26 +99,29 @@ var classnames_1 = __importDefault(require("classnames"));
90
99
  var ramda_1 = require("ramda");
91
100
  var react_context_selector_1 = require("@fluentui/react-context-selector");
92
101
  var Autocomplete_1 = __importDefault(require("@mui/material/Autocomplete"));
93
- var ListItem_1 = __importDefault(require("@mui/material/ListItem"));
94
- var ListItemText_1 = __importDefault(require("@mui/material/ListItemText"));
102
+ var Button_1 = __importDefault(require("@mui/material/Button"));
103
+ var ChevronRight_1 = __importDefault(require("@mui/icons-material/ChevronRight"));
95
104
  var LinearProgress_1 = __importDefault(require("@mui/material/LinearProgress"));
96
- var Typography_1 = __importDefault(require("@mui/material/Typography"));
105
+ var ListItemButton_1 = __importDefault(require("@mui/material/ListItemButton"));
97
106
  var TextField_1 = __importDefault(require("@mui/material/TextField"));
98
- var ArrowForwardIos_1 = __importDefault(require("@mui/icons-material/ArrowForwardIos"));
107
+ var Typography_1 = __importDefault(require("@mui/material/Typography"));
99
108
  var ui_i18n_1 = __importDefault(require("ui-i18n"));
100
109
  var mdm_sdk_1 = require("@reltio/mdm-sdk");
101
110
  var AutoCompleteContext_1 = require("../contexts/AutoCompleteContext");
102
111
  var AttributeValueContext_1 = require("../contexts/AttributeValueContext");
103
112
  var helpers_1 = require("./helpers");
104
113
  var useSafePromise_1 = require("../hooks/useSafePromise");
114
+ var ExpandedValueTooltip_1 = require("../ExpandedValueTooltip");
115
+ var EmptySearchResult_1 = require("../EmptySearchResult");
105
116
  var AddressAutocompleteEditor_module_css_1 = __importDefault(require("./AddressAutocompleteEditor.module.css"));
106
117
  exports.INPUT_DEBOUNCE_INTERVAL = 400;
118
+ var EMPTY_OPTION_ID = 'empty_option_id';
107
119
  var AddressAutocompleteEditor = function (_a) {
108
120
  var _b = _a.value, value = _b === void 0 ? '' : _b, onChange = _a.onChange, InputProps = _a.InputProps, _c = _a.fullWidth, fullWidth = _c === void 0 ? true : _c, otherProps = __rest(_a, ["value", "onChange", "InputProps", "fullWidth"]);
109
121
  var _d = (0, react_1.useState)(value), inputValue = _d[0], setInputValue = _d[1];
110
- var _e = (0, react_1.useState)([]), options = _e[0], setOptions = _e[1];
111
- var _f = (0, react_1.useState)(false), loading = _f[0], setLoading = _f[1];
112
- var _g = (0, react_1.useState)(false), detailsLoading = _g[0], setDetailsLoading = _g[1];
122
+ var _e = (0, react_1.useState)(null), options = _e[0], setOptions = _e[1];
123
+ var _f = (0, react_1.useState)([]), previousOptions = _f[0], setPreviousOptions = _f[1];
124
+ var _g = (0, react_1.useState)(false), loading = _g[0], setLoading = _g[1];
113
125
  var _h = (0, react_1.useState)(false), open = _h[0], setOpen = _h[1];
114
126
  var searchSafePromise = (0, useSafePromise_1.useSafePromise)();
115
127
  var detailsSafePromise = (0, useSafePromise_1.useSafePromise)();
@@ -120,45 +132,61 @@ var AddressAutocompleteEditor = function (_a) {
120
132
  var countries = autocompleteSettings.countries, countryNames = autocompleteSettings.countryNames, limit = autocompleteSettings.limit, minSearchTextLen = autocompleteSettings.minSearchTextLen;
121
133
  var countriesHash = countries.toString();
122
134
  (0, react_1.useEffect)(function () {
123
- setOptions([]);
135
+ setOptions(null);
136
+ setPreviousOptions([]);
124
137
  }, [countriesHash]);
125
- var fetchAddressSuggestions = (0, react_1.useCallback)(function (searchTerm) { return __awaiter(void 0, void 0, void 0, function () {
126
- var results, convertedOptions, error_1;
127
- return __generator(this, function (_a) {
128
- switch (_a.label) {
138
+ var fetchAddressSuggestions = (0, react_1.useCallback)(function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
139
+ var results, _c, convertedOptions, error_1;
140
+ var searchTerm = _b.searchTerm, container = _b.container;
141
+ return __generator(this, function (_d) {
142
+ switch (_d.label) {
129
143
  case 0:
130
144
  setLoading(true);
131
- _a.label = 1;
145
+ _d.label = 1;
132
146
  case 1:
133
- _a.trys.push([1, 3, 4, 5]);
134
- return [4 /*yield*/, searchSafePromise((0, mdm_sdk_1.searchAddresses)({
135
- text: searchTerm,
136
- limit: limit,
137
- countries: countries
138
- }))];
147
+ _d.trys.push([1, 6, 7, 8]);
148
+ if (!container) return [3 /*break*/, 3];
149
+ return [4 /*yield*/, searchSafePromise((0, mdm_sdk_1.searchAddresses)({ container: container }))];
139
150
  case 2:
140
- results = _a.sent();
151
+ _c = _d.sent();
152
+ return [3 /*break*/, 5];
153
+ case 3: return [4 /*yield*/, searchSafePromise((0, mdm_sdk_1.searchAddresses)({
154
+ text: searchTerm,
155
+ limit: limit,
156
+ countries: countries
157
+ }))];
158
+ case 4:
159
+ _c = _d.sent();
160
+ _d.label = 5;
161
+ case 5:
162
+ results = _c;
141
163
  convertedOptions = results.map(helpers_1.convertSearchResultToOption);
164
+ if (container) {
165
+ setPreviousOptions(options || []);
166
+ }
167
+ else {
168
+ setPreviousOptions([]);
169
+ }
142
170
  setOptions(convertedOptions);
143
- return [3 /*break*/, 5];
144
- case 3:
145
- error_1 = _a.sent();
171
+ return [3 /*break*/, 8];
172
+ case 6:
173
+ error_1 = _d.sent();
146
174
  console.warn('Error fetching address suggestions:', error_1);
147
175
  setOptions([]);
148
- return [3 /*break*/, 5];
149
- case 4:
176
+ return [3 /*break*/, 8];
177
+ case 7:
150
178
  setLoading(false);
151
179
  return [7 /*endfinally*/];
152
- case 5: return [2 /*return*/];
180
+ case 8: return [2 /*return*/];
153
181
  }
154
182
  });
155
- }); }, [countries, limit, searchSafePromise]);
183
+ }); }, [countries, limit, searchSafePromise, options]);
156
184
  var fetchAddressDetailsById = (0, react_1.useCallback)(function (addressId) { return __awaiter(void 0, void 0, void 0, function () {
157
185
  var error_2;
158
186
  return __generator(this, function (_a) {
159
187
  switch (_a.label) {
160
188
  case 0:
161
- setDetailsLoading(true);
189
+ setLoading(true);
162
190
  _a.label = 1;
163
191
  case 1:
164
192
  _a.trys.push([1, 3, 4, 5]);
@@ -167,9 +195,9 @@ var AddressAutocompleteEditor = function (_a) {
167
195
  case 3:
168
196
  error_2 = _a.sent();
169
197
  console.warn('Error fetching address details:', error_2);
170
- return [2 /*return*/, null];
198
+ return [3 /*break*/, 5];
171
199
  case 4:
172
- setDetailsLoading(false);
200
+ setLoading(false);
173
201
  return [7 /*endfinally*/];
174
202
  case 5: return [2 /*return*/];
175
203
  }
@@ -178,13 +206,17 @@ var AddressAutocompleteEditor = function (_a) {
178
206
  var debouncedFetchAddressSuggestions = (0, react_1.useCallback)((0, mdm_sdk_1.debounce)(fetchAddressSuggestions, exports.INPUT_DEBOUNCE_INTERVAL), [
179
207
  fetchAddressSuggestions
180
208
  ]);
181
- var handleInputChange = (0, react_1.useCallback)(function (event, newInputValue) {
209
+ var handleInputChange = (0, react_1.useCallback)(function (event, newInputValue, reason) {
210
+ if (reason === 'reset') {
211
+ return;
212
+ }
182
213
  setInputValue(newInputValue);
183
- if (newInputValue.trim().length >= minSearchTextLen) {
184
- debouncedFetchAddressSuggestions(newInputValue);
214
+ if ((0, helpers_1.isMinLength)(newInputValue, minSearchTextLen)) {
215
+ debouncedFetchAddressSuggestions({ searchTerm: newInputValue });
185
216
  }
186
217
  else {
187
218
  setOptions([]);
219
+ setPreviousOptions([]);
188
220
  }
189
221
  }, [debouncedFetchAddressSuggestions, minSearchTextLen]);
190
222
  var handleOptionSelect = (0, react_1.useCallback)(function (event, selectedOption) { return __awaiter(void 0, void 0, void 0, function () {
@@ -194,9 +226,11 @@ var AddressAutocompleteEditor = function (_a) {
194
226
  case 0:
195
227
  if (!selectedOption) return [3 /*break*/, 3];
196
228
  if (selectedOption.type === mdm_sdk_1.AddressType.Container) {
229
+ fetchAddressSuggestions({ container: selectedOption.id });
197
230
  return [2 /*return*/];
198
231
  }
199
232
  setInputValue(selectedOption.fullAddress);
233
+ setPreviousOptions([]);
200
234
  if (onChange) {
201
235
  onChange(selectedOption.fullAddress);
202
236
  }
@@ -218,36 +252,70 @@ var AddressAutocompleteEditor = function (_a) {
218
252
  case 4: return [2 /*return*/];
219
253
  }
220
254
  });
221
- }); }, [fetchAddressDetailsById, onChange, onPopulateAttributes, attributeTypeUri, valueUri]);
255
+ }); }, [fetchAddressDetailsById, fetchAddressSuggestions, onChange, onPopulateAttributes, attributeTypeUri, valueUri]);
256
+ var handleFocus = (0, react_1.useCallback)(function () {
257
+ if (!options && (0, helpers_1.isMinLength)(inputValue, minSearchTextLen)) {
258
+ debouncedFetchAddressSuggestions({ searchTerm: inputValue });
259
+ }
260
+ setOpen(true);
261
+ }, [debouncedFetchAddressSuggestions, inputValue, minSearchTextLen, options]);
222
262
  var handleBlur = (0, react_1.useCallback)(function () {
223
263
  setOpen(false);
224
264
  }, []);
225
265
  var renderOption = (0, react_1.useCallback)(function (props, option) {
226
266
  var _a;
267
+ if (option.id === EMPTY_OPTION_ID) {
268
+ return null;
269
+ }
227
270
  var isContainer = option.type === mdm_sdk_1.AddressType.Container;
228
- return (react_1.default.createElement(ListItem_1.default, __assign({}, props, { className: (0, classnames_1.default)(AddressAutocompleteEditor_module_css_1.default.option, (_a = {}, _a[AddressAutocompleteEditor_module_css_1.default.containerOption] = isContainer, _a)) }),
229
- react_1.default.createElement(ListItemText_1.default, { primary: option.fullAddress, primaryTypographyProps: { variant: 'body1' }, secondaryTypographyProps: { variant: 'body2', color: 'textSecondary' } }),
230
- isContainer && (react_1.default.createElement("div", null,
231
- react_1.default.createElement(ArrowForwardIos_1.default, null)))));
232
- }, []);
233
- var renderInput = (0, react_1.useCallback)(function (props) { return (react_1.default.createElement(TextField_1.default, __assign({}, otherProps, props, { placeholder: ui_i18n_1.default.text('Start typing to autocomplete'), fullWidth: fullWidth, onBlur: handleBlur, InputProps: __assign(__assign(__assign({}, InputProps), props.InputProps), { endAdornment: react_1.default.createElement(react_1.default.Fragment, null, props.InputProps.endAdornment) }) }))); }, [fullWidth, handleBlur, InputProps, otherProps]);
271
+ return (react_1.default.createElement(ListItemButton_1.default, __assign({}, props, { className: (0, classnames_1.default)(AddressAutocompleteEditor_module_css_1.default.option, (_a = {}, _a[AddressAutocompleteEditor_module_css_1.default.containerOption] = isContainer, _a)), component: "li", disabled: loading }),
272
+ react_1.default.createElement(ExpandedValueTooltip_1.ExpandedValueTooltip, { value: option.fullAddress },
273
+ react_1.default.createElement(Typography_1.default, { variant: "body2", className: AddressAutocompleteEditor_module_css_1.default.optionText }, option.fullAddress)),
274
+ isContainer && (react_1.default.createElement("div", { className: AddressAutocompleteEditor_module_css_1.default.containerForwardIcon },
275
+ react_1.default.createElement(ChevronRight_1.default, null)))));
276
+ }, [loading]);
277
+ var renderInput = (0, react_1.useCallback)(function (props) {
278
+ var _a;
279
+ return (react_1.default.createElement(TextField_1.default, __assign({}, props, otherProps, { placeholder: ui_i18n_1.default.text('Start typing to autocomplete'), fullWidth: fullWidth, onBlur: handleBlur, InputProps: __assign(__assign(__assign({}, props.InputProps), InputProps), { classes: __assign(__assign({}, InputProps === null || InputProps === void 0 ? void 0 : InputProps.classes), { root: (0, classnames_1.default)(AddressAutocompleteEditor_module_css_1.default.textFieldRoot, (0, ramda_1.path)(['classes', 'root'], InputProps)), input: (0, classnames_1.default)(AddressAutocompleteEditor_module_css_1.default.textFieldInput, (0, ramda_1.path)(['classes', 'input'], InputProps)), underline: (0, classnames_1.default)((_a = {}, _a[AddressAutocompleteEditor_module_css_1.default.underline] = (0, mdm_sdk_1.isEmptyValue)(value), _a)) }) }) })));
280
+ }, [fullWidth, handleBlur, InputProps, otherProps, value]);
281
+ var handleBackToAllSearchResults = (0, react_1.useCallback)(function () {
282
+ setOptions(previousOptions);
283
+ setPreviousOptions([]);
284
+ }, [previousOptions]);
234
285
  var renderListbox = (0, react_1.useCallback)(react_1.default.forwardRef(function ListboxComponent(props, ref) {
235
- return (react_1.default.createElement(react_1.default.Fragment, null,
236
- detailsLoading && react_1.default.createElement(LinearProgress_1.default, { color: "primary" }),
237
- react_1.default.createElement("ul", __assign({ ref: ref }, props),
238
- react_1.default.createElement("li", { className: AddressAutocompleteEditor_module_css_1.default.countryHeader },
239
- react_1.default.createElement(Typography_1.default, { variant: "body2", fontWeight: 500 },
286
+ var onMouseDown = props.onMouseDown, restProps = __rest(props, ["onMouseDown"]);
287
+ var shouldShowEmptyState = (options === null || options === void 0 ? void 0 : options.length) === 0 && (0, helpers_1.isMinLength)(inputValue, minSearchTextLen) && !loading;
288
+ var shouldShowMinLengthMessage = !(0, helpers_1.isMinLength)(inputValue, minSearchTextLen) && !loading;
289
+ var shouldShowOptions = (options === null || options === void 0 ? void 0 : options.length) > 0 && !shouldShowMinLengthMessage;
290
+ var countriesLabel = countryNames.join(', ');
291
+ return (react_1.default.createElement("div", { onMouseDown: onMouseDown },
292
+ loading && react_1.default.createElement(LinearProgress_1.default, { className: AddressAutocompleteEditor_module_css_1.default.loadingProgress, color: "primary" }),
293
+ react_1.default.createElement("div", { className: AddressAutocompleteEditor_module_css_1.default.countryHeader },
294
+ react_1.default.createElement(ExpandedValueTooltip_1.ExpandedValueTooltip, { value: countriesLabel },
295
+ react_1.default.createElement(Typography_1.default, { variant: "body2", color: "textSecondary", className: AddressAutocompleteEditor_module_css_1.default.headerCountries },
240
296
  ui_i18n_1.default.text('Searching in'),
241
297
  ": ",
242
- countryNames.join(', ')),
243
- react_1.default.createElement(Typography_1.default, { variant: "caption", color: "textSecondary" }, ui_i18n_1.default.text('To search in a different country, update the Country field'))),
244
- props.children)));
245
- }), [detailsLoading, countryNames]);
298
+ countriesLabel)),
299
+ react_1.default.createElement(Typography_1.default, { variant: "caption", color: "textSecondary", className: AddressAutocompleteEditor_module_css_1.default.headerHelper }, ui_i18n_1.default.text('To search in a different country, update the Country field'))),
300
+ react_1.default.createElement("ul", __assign({ ref: ref }, restProps, { className: AddressAutocompleteEditor_module_css_1.default.listbox }),
301
+ shouldShowMinLengthMessage && (react_1.default.createElement("li", { className: AddressAutocompleteEditor_module_css_1.default.emptyState },
302
+ react_1.default.createElement(Typography_1.default, { variant: "body2", color: "textSecondary" }, ui_i18n_1.default.text('Please enter at least ${minSearchTextLen} characters', {
303
+ minSearchTextLen: minSearchTextLen
304
+ })))),
305
+ shouldShowEmptyState && (react_1.default.createElement("li", { className: AddressAutocompleteEditor_module_css_1.default.emptyState },
306
+ react_1.default.createElement(EmptySearchResult_1.EmptySearchResult, { className: AddressAutocompleteEditor_module_css_1.default.noData }))),
307
+ shouldShowOptions && props.children),
308
+ previousOptions.length > 0 && (react_1.default.createElement("div", { className: AddressAutocompleteEditor_module_css_1.default.backToAllResults },
309
+ react_1.default.createElement(Button_1.default, { variant: "text", onClick: handleBackToAllSearchResults, color: "primary", disabled: loading, classes: { root: AddressAutocompleteEditor_module_css_1.default.backToAllResultsButton } }, ui_i18n_1.default.text('Back to all results'))))));
310
+ }), [loading, countryNames, previousOptions.length, (options || []).length, inputValue, minSearchTextLen]);
246
311
  var openOptionsList = (0, react_1.useCallback)(function (_event) {
247
312
  setOpen(true);
248
313
  }, []);
249
- return (react_1.default.createElement(Autocomplete_1.default, { className: AddressAutocompleteEditor_module_css_1.default.root, open: open, onOpen: openOptionsList, value: inputValue, onChange: handleOptionSelect, inputValue: inputValue, onInputChange: handleInputChange, options: options, getOptionLabel: helpers_1.getOptionLabel, loading: loading, renderInput: renderInput, renderOption: renderOption, renderGroup: function (params) { return params.children; }, ListboxComponent: renderListbox, noOptionsText: inputValue.trim().length < minSearchTextLen
250
- ? ui_i18n_1.default.text('Please enter at least {{minSearchTextLen}} characters', { minSearchTextLen: minSearchTextLen })
251
- : ui_i18n_1.default.text('No addresses found'), loadingText: ui_i18n_1.default.text('Searching addresses...'), filterOptions: ramda_1.identity, isOptionEqualToValue: helpers_1.isOptionEqualToValue, popupIcon: null, clearIcon: null, freeSolo: true, "data-reltio-id": "address-autocomplete-editor" }));
314
+ var displayOptions = (0, react_1.useMemo)(function () {
315
+ return __spreadArray([{ id: EMPTY_OPTION_ID, type: mdm_sdk_1.AddressType.Address, fullAddress: '' }], (options || []), true);
316
+ }, [options]);
317
+ return (react_1.default.createElement(Autocomplete_1.default, { className: AddressAutocompleteEditor_module_css_1.default.root, open: open, onOpen: openOptionsList, value: inputValue, onChange: handleOptionSelect, inputValue: inputValue, onInputChange: handleInputChange, onFocus: handleFocus, options: displayOptions, getOptionLabel: helpers_1.getOptionLabel, renderInput: renderInput, renderOption: renderOption, renderGroup: function (params) { return params.children; }, ListboxComponent: renderListbox, classes: {
318
+ paper: AddressAutocompleteEditor_module_css_1.default.popupContainer
319
+ }, filterOptions: ramda_1.identity, isOptionEqualToValue: helpers_1.isOptionEqualToValue, popupIcon: null, clearIcon: null, freeSolo: true, "data-reltio-id": "address-autocomplete-editor" }));
252
320
  };
253
321
  exports.AddressAutocompleteEditor = AddressAutocompleteEditor;
@@ -1,9 +1,9 @@
1
- const styles = {"root":"AddressAutocompleteEditor-root--1FDrD","countryHeader":"AddressAutocompleteEditor-countryHeader--k5tRp","loadingContainer":"AddressAutocompleteEditor-loadingContainer--j-U45","option":"AddressAutocompleteEditor-option--3hah-","containerOption":"AddressAutocompleteEditor-containerOption--aLgUn","listbox":"AddressAutocompleteEditor-listbox--cZCpg"};
1
+ const styles = {"root":"AddressAutocompleteEditor-root--1FDrD","textFieldRoot":"AddressAutocompleteEditor-textFieldRoot--udDvK","textFieldInput":"AddressAutocompleteEditor-textFieldInput--Xf-Cf","underline":"AddressAutocompleteEditor-underline--ZNYqN","countryHeader":"AddressAutocompleteEditor-countryHeader--k5tRp","headerCountries":"AddressAutocompleteEditor-headerCountries--WoYl5","headerHelper":"AddressAutocompleteEditor-headerHelper--XPT5S","loadingState":"AddressAutocompleteEditor-loadingState--PRwRK","emptyState":"AddressAutocompleteEditor-emptyState--s8M04","option":"AddressAutocompleteEditor-option--3hah-","popupContainer":"AddressAutocompleteEditor-popupContainer--360Kv","listbox":"AddressAutocompleteEditor-listbox--cZCpg","optionText":"AddressAutocompleteEditor-optionText--IcsBw","containerForwardIcon":"AddressAutocompleteEditor-containerForwardIcon--Ke5rd","backToAllResults":"AddressAutocompleteEditor-backToAllResults--2wqzi","backToAllResultsButton":"AddressAutocompleteEditor-backToAllResultsButton--T5R-3","loadingProgress":"AddressAutocompleteEditor-loadingProgress--AfWtc"};
2
2
  if (typeof document !== 'undefined') {
3
3
  const head = document.head || document.getElementsByTagName('head')[0]
4
4
  const style = document.createElement('style');
5
5
  style.type = 'text/css'
6
- style.innerHTML = `.AddressAutocompleteEditor-root--1FDrD{width:100%}.AddressAutocompleteEditor-countryHeader--k5tRp{background-color:rgba(0,0,0,.04);border-bottom:1px solid rgba(0,0,0,.12);font-weight:500;padding:8px 16px}.AddressAutocompleteEditor-loadingContainer--j-U45{align-items:center;display:flex;justify-content:center;padding:16px}.AddressAutocompleteEditor-option--3hah-{padding:8px 16px}.AddressAutocompleteEditor-containerOption--aLgUn:hover{background-color:rgba(0,0,0,.04)!important}.AddressAutocompleteEditor-listbox--cZCpg{padding:0}`;
6
+ style.innerHTML = `.AddressAutocompleteEditor-root--1FDrD{width:100%}.AddressAutocompleteEditor-root--1FDrD .AddressAutocompleteEditor-textFieldRoot--udDvK{font-size:14px;padding:0}:is(.AddressAutocompleteEditor-root--1FDrD .AddressAutocompleteEditor-textFieldRoot--udDvK) .AddressAutocompleteEditor-textFieldInput--Xf-Cf{height:19px;line-height:19px;padding:10px 12px 11px!important}:is(.AddressAutocompleteEditor-root--1FDrD .AddressAutocompleteEditor-underline--ZNYqN):before{display:none}.AddressAutocompleteEditor-countryHeader--k5tRp{padding:12px 12px 8px}.AddressAutocompleteEditor-countryHeader--k5tRp .AddressAutocompleteEditor-headerCountries--WoYl5{font-size:12px;letter-spacing:0;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.AddressAutocompleteEditor-countryHeader--k5tRp .AddressAutocompleteEditor-headerHelper--XPT5S{font-size:10px;letter-spacing:0}.AddressAutocompleteEditor-loadingState--PRwRK{padding:16px;text-align:center}.AddressAutocompleteEditor-emptyState--s8M04{padding:8px}.AddressAutocompleteEditor-option--3hah-{padding:4px 12px}.AddressAutocompleteEditor-popupContainer--360Kv{min-width:170px}.AddressAutocompleteEditor-listbox--cZCpg{margin:0;max-height:300px;overflow-y:auto;padding:0}.AddressAutocompleteEditor-listbox--cZCpg li:first-child{border-top:1px solid rgba(0,0,0,.12);padding-top:7px}.AddressAutocompleteEditor-optionText--IcsBw{font-size:14px;letter-spacing:.24px;line-height:16px;overflow:hidden;padding:4px 0;text-overflow:ellipsis;white-space:nowrap;width:100%}.AddressAutocompleteEditor-containerForwardIcon--Ke5rd{height:24px}.AddressAutocompleteEditor-backToAllResults--2wqzi{border-top:1px solid rgba(0,0,0,.12)}.AddressAutocompleteEditor-backToAllResultsButton--T5R-3{letter-spacing:0}.AddressAutocompleteEditor-loadingProgress--AfWtc{left:0;position:absolute;right:0;top:0}`;
7
7
  head.appendChild(style);
8
8
  }
9
9
  module.exports = styles;
@@ -98,6 +98,14 @@ var mockAddressSearchResults = [
98
98
  Description: 'Jose Malhoa 22'
99
99
  }
100
100
  ];
101
+ var mockAddressContainerSearchResults = [
102
+ {
103
+ Id: 'container-1',
104
+ Type: mdm_sdk_1.AddressType.Container,
105
+ Text: 'Container Option',
106
+ Description: 'Container'
107
+ }
108
+ ];
101
109
  var mockAddressDetails = [
102
110
  {
103
111
  Id: 'address-1',
@@ -287,15 +295,34 @@ describe('AddressAutocompleteEditor', function () {
287
295
  return [4 /*yield*/, user.paste('Lisbon')];
288
296
  case 2:
289
297
  _a.sent();
290
- expect(react_2.screen.getByText('Searching addresses...')).toBeInTheDocument();
298
+ expect(react_2.screen.getByRole('progressbar')).toBeInTheDocument();
291
299
  resolveSearch([]);
292
- return [4 /*yield*/, (0, react_2.waitForElementToBeRemoved)(function () { return react_2.screen.queryByText('Searching addresses...'); })];
300
+ return [4 /*yield*/, (0, react_2.waitForElementToBeRemoved)(function () { return react_2.screen.queryByRole('progressbar'); })];
293
301
  case 3:
294
302
  _a.sent();
295
303
  return [2 /*return*/];
296
304
  }
297
305
  });
298
306
  }); });
307
+ it('should show empty state when no options are found', function () { return __awaiter(void 0, void 0, void 0, function () {
308
+ var user, input;
309
+ return __generator(this, function (_a) {
310
+ switch (_a.label) {
311
+ case 0:
312
+ mockSearchAddresses.mockResolvedValue([]);
313
+ user = setUp().user;
314
+ input = react_2.screen.getByRole('combobox');
315
+ return [4 /*yield*/, user.click(input)];
316
+ case 1:
317
+ _a.sent();
318
+ return [4 /*yield*/, user.paste('Lisbon')];
319
+ case 2:
320
+ _a.sent();
321
+ expect(react_2.screen.getByText('No results found')).toBeInTheDocument();
322
+ return [2 /*return*/];
323
+ }
324
+ });
325
+ }); });
299
326
  describe('Address search', function () {
300
327
  it('should fetch address suggestions when user types minimum characters', function () { return __awaiter(void 0, void 0, void 0, function () {
301
328
  var user, input;
@@ -383,9 +410,7 @@ describe('AddressAutocompleteEditor', function () {
383
410
  switch (_a.label) {
384
411
  case 0:
385
412
  onChange = jest.fn();
386
- mockSearchAddresses.mockResolvedValue([
387
- { Id: 'container-1', Type: mdm_sdk_1.AddressType.Container, Text: 'Container Option', Description: 'Container' }
388
- ]);
413
+ mockSearchAddresses.mockResolvedValue(mockAddressContainerSearchResults);
389
414
  user = setUp({ onChange: onChange }).user;
390
415
  input = react_2.screen.getByRole('combobox');
391
416
  return [4 /*yield*/, user.click(input)];
@@ -460,6 +485,76 @@ describe('AddressAutocompleteEditor', function () {
460
485
  }
461
486
  });
462
487
  }); });
488
+ it('should request address list for container options', function () { return __awaiter(void 0, void 0, void 0, function () {
489
+ var onChange, user, input, option;
490
+ return __generator(this, function (_a) {
491
+ switch (_a.label) {
492
+ case 0:
493
+ onChange = jest.fn();
494
+ mockSearchAddresses.mockResolvedValue(mockAddressContainerSearchResults);
495
+ user = setUp({ onChange: onChange }).user;
496
+ input = react_2.screen.getByRole('combobox');
497
+ return [4 /*yield*/, user.click(input)];
498
+ case 1:
499
+ _a.sent();
500
+ return [4 /*yield*/, user.paste('Lisbon')];
501
+ case 2:
502
+ _a.sent();
503
+ expect(react_2.screen.getByText('Container Option')).toBeInTheDocument();
504
+ expect(mockSearchAddresses).toHaveBeenCalledWith({
505
+ text: 'Lisbon',
506
+ limit: 10,
507
+ countries: ['PT', 'US']
508
+ });
509
+ mockSearchAddresses.mockReset();
510
+ mockSearchAddresses.mockResolvedValue(mockAddressSearchResults);
511
+ option = react_2.screen.getByText('Container Option');
512
+ return [4 /*yield*/, user.click(option)];
513
+ case 3:
514
+ _a.sent();
515
+ expect(mockSearchAddresses).toHaveBeenCalledWith({
516
+ container: 'container-1'
517
+ });
518
+ expect(react_2.screen.getByText('Avenida Jose Malhoa 22, Lisbon')).toBeInTheDocument();
519
+ expect(react_2.screen.queryByText('Container Option')).not.toBeInTheDocument();
520
+ expect(onChange).not.toHaveBeenCalled();
521
+ expect(mockFetchAddressDetails).not.toHaveBeenCalled();
522
+ return [2 /*return*/];
523
+ }
524
+ });
525
+ }); });
526
+ it('should back to all search results after selecting container option', function () { return __awaiter(void 0, void 0, void 0, function () {
527
+ var onChange, user, input, option;
528
+ return __generator(this, function (_a) {
529
+ switch (_a.label) {
530
+ case 0:
531
+ onChange = jest.fn();
532
+ mockSearchAddresses.mockResolvedValue(mockAddressContainerSearchResults);
533
+ user = setUp({ onChange: onChange }).user;
534
+ input = react_2.screen.getByRole('combobox');
535
+ return [4 /*yield*/, user.click(input)];
536
+ case 1:
537
+ _a.sent();
538
+ return [4 /*yield*/, user.paste('Lisbon')];
539
+ case 2:
540
+ _a.sent();
541
+ expect(react_2.screen.queryByText('Back to all results')).not.toBeInTheDocument();
542
+ mockSearchAddresses.mockReset();
543
+ mockSearchAddresses.mockResolvedValue(mockAddressSearchResults);
544
+ option = react_2.screen.getByText('Container Option');
545
+ return [4 /*yield*/, user.click(option)];
546
+ case 3:
547
+ _a.sent();
548
+ expect(react_2.screen.getByText('Back to all results')).toBeInTheDocument();
549
+ return [4 /*yield*/, user.click(react_2.screen.getByText('Back to all results'))];
550
+ case 4:
551
+ _a.sent();
552
+ expect(react_2.screen.queryByText('Back to all results')).not.toBeInTheDocument();
553
+ expect(react_2.screen.getByText('Container Option')).toBeInTheDocument();
554
+ return [2 /*return*/];
555
+ }
556
+ });
557
+ }); });
463
558
  });
464
559
  describe('Autocomplete settings', function () {
465
560
  it('should use default autocomplete settings, when no settings from metadata', function () { return __awaiter(void 0, void 0, void 0, function () {
@@ -511,6 +606,7 @@ describe('AddressAutocompleteEditor', function () {
511
606
  case 2:
512
607
  _a.sent();
513
608
  expect(mockSearchAddresses).not.toHaveBeenCalled();
609
+ expect(react_2.screen.getByText('Please enter at least 3 characters')).toBeInTheDocument();
514
610
  return [4 /*yield*/, user.paste('sbon')];
515
611
  case 3:
516
612
  _a.sent();
@@ -8,3 +8,4 @@ export type AddressOption = {
8
8
  export declare const convertSearchResultToOption: (result: AddressSearchResult) => AddressOption;
9
9
  export declare const getOptionLabel: (option: AddressOption | string) => string;
10
10
  export declare const isOptionEqualToValue: (option: AddressOption, value: string) => boolean;
11
+ export declare const isMinLength: (value: string, minLength: number) => boolean;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isOptionEqualToValue = exports.getOptionLabel = exports.convertSearchResultToOption = void 0;
3
+ exports.isMinLength = exports.isOptionEqualToValue = exports.getOptionLabel = exports.convertSearchResultToOption = void 0;
4
4
  var mdm_sdk_1 = require("@reltio/mdm-sdk");
5
5
  var convertSearchResultToOption = function (result) {
6
6
  return {
@@ -22,3 +22,7 @@ var isOptionEqualToValue = function (option, value) {
22
22
  return option.fullAddress === value;
23
23
  };
24
24
  exports.isOptionEqualToValue = isOptionEqualToValue;
25
+ var isMinLength = function (value, minLength) {
26
+ return (value === null || value === void 0 ? void 0 : value.trim().length) >= minLength;
27
+ };
28
+ exports.isMinLength = isMinLength;
@@ -1 +1 @@
1
- export declare const useStyles: (props?: any) => import("@mui/styles").ClassNameMap<"dense" | "icon" | "subHeader" | "moreButton" | "buttonLabel" | "moreAttributes" | "popupContainer" | "moreAttributesPopup" | "noResultsCaptionContainer">;
1
+ export declare const useStyles: (props?: any) => import("@mui/styles").ClassNameMap<"dense" | "icon" | "subHeader" | "moreButton" | "buttonLabel" | "popupContainer" | "moreAttributes" | "moreAttributesPopup" | "noResultsCaptionContainer">;
@@ -94,12 +94,12 @@ var getCountriesFromValues = function (_a) {
94
94
  (0, mdm_sdk_1.findAttributeTypeByUri)(metadata, countriesMapping === null || countriesMapping === void 0 ? void 0 : countriesMapping.attribute, modifiedEntity.type);
95
95
  if (countryAttributeType && !(0, mdm_sdk_1.isComplexAttribute)(countryAttributeType)) {
96
96
  var existingCountriesValues = (0, mdm_sdk_1.findAttributeValuesByTypeUri)(metadata, modifiedEntity, countriesMapping.attribute);
97
- var currentCountryValues = existingCountriesValues
97
+ var currentCountryValues = (0, ramda_1.uniqBy)((0, ramda_1.prop)('value'), existingCountriesValues
98
98
  .filter(mdm_sdk_1.isOv)
99
99
  .filter(function (countryValue) {
100
100
  return (0, mdm_sdk_1.areOneHierarchyUris)(triggerAttributeValueParentUri, (0, mdm_sdk_1.getParentUri)(countryValue.uri));
101
101
  })
102
- .filter(function (countryValue) { return countryValue.value; });
102
+ .filter(function (countryValue) { return countryValue.value; }));
103
103
  if (currentCountryValues === null || currentCountryValues === void 0 ? void 0 : currentCountryValues.length) {
104
104
  var countries = currentCountryValues.map(function (value) { return value.lookupCode || value.value.toString(); });
105
105
  return {
@@ -9,7 +9,7 @@ var __assign = (this && this.__assign) || function () {
9
9
  };
10
10
  return __assign.apply(this, arguments);
11
11
  };
12
- import { pluck } from 'ramda';
12
+ import { pluck, prop, uniqBy } from 'ramda';
13
13
  import { findAttributeTypeByUri, findAttributeValuesByTypeUri, isComplexAttribute, isOv, getParentUri, generateUri, InputCleanseAttributeType, areOneHierarchyUris, DataTypes, isNested } from '@reltio/mdm-sdk';
14
14
  var MINIMUM_NESTED_LEVEL = 1;
15
15
  var MIN_SEARCH_TEXT_LEN = 3;
@@ -91,12 +91,12 @@ var getCountriesFromValues = function (_a) {
91
91
  findAttributeTypeByUri(metadata, countriesMapping === null || countriesMapping === void 0 ? void 0 : countriesMapping.attribute, modifiedEntity.type);
92
92
  if (countryAttributeType && !isComplexAttribute(countryAttributeType)) {
93
93
  var existingCountriesValues = findAttributeValuesByTypeUri(metadata, modifiedEntity, countriesMapping.attribute);
94
- var currentCountryValues = existingCountriesValues
94
+ var currentCountryValues = uniqBy(prop('value'), existingCountriesValues
95
95
  .filter(isOv)
96
96
  .filter(function (countryValue) {
97
97
  return areOneHierarchyUris(triggerAttributeValueParentUri, getParentUri(countryValue.uri));
98
98
  })
99
- .filter(function (countryValue) { return countryValue.value; });
99
+ .filter(function (countryValue) { return countryValue.value; }));
100
100
  if (currentCountryValues === null || currentCountryValues === void 0 ? void 0 : currentCountryValues.length) {
101
101
  var countries = currentCountryValues.map(function (value) { return value.lookupCode || value.value.toString(); });
102
102
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reltio/components",
3
- "version": "1.4.2162",
3
+ "version": "1.4.2163",
4
4
  "license": "SEE LICENSE IN LICENSE FILE",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./index.js",
@@ -11,7 +11,7 @@
11
11
  "@fluentui/react-context-selector": "^9.1.26",
12
12
  "@googlemaps/markerclusterer": "^2.5.3",
13
13
  "@react-sigma/core": "3.4.0",
14
- "@reltio/mdm-sdk": "^1.4.1979",
14
+ "@reltio/mdm-sdk": "^1.4.1980",
15
15
  "@vis.gl/react-google-maps": "^1.3.0",
16
16
  "d3-cloud": "^1.2.5",
17
17
  "d3-geo": "^2.0.1",