@dashadmin/dash-components 1.3.25 → 1.3.26

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.
Files changed (111) hide show
  1. package/dist/components/AuditLog/AuditLog.js +345 -1
  2. package/dist/components/AuditLog/index.js +4 -1
  3. package/dist/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.js +630 -3
  4. package/dist/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.js +53 -1
  5. package/dist/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.js +647 -3
  6. package/dist/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.js +53 -1
  7. package/dist/components/Json/Json.js +235 -1
  8. package/dist/components/JsonColorSelector/JsonColorSelector.js +1172 -1
  9. package/dist/components/JsonColorSelector/JsonColorSelectorEnhanced.js +863 -2
  10. package/dist/components/JsonColorSelector/JsonCssVarValues.js +738 -1
  11. package/dist/components/JsonColorSelector/components/ColorEditDialog.js +426 -1
  12. package/dist/components/JsonColorSelector/components/ColorPaletteItem.js +75 -1
  13. package/dist/components/JsonColorSelector/components/ImageColorExtractor.js +283 -1
  14. package/dist/components/JsonColorSelector/helpers/functions.js +101 -1
  15. package/dist/components/JsonColorSelector/useAsyncColorThief.js +105 -1
  16. package/dist/components/ListActive/ListActive.js +126 -1
  17. package/dist/components/ListActive/ListBoolean.js +124 -1
  18. package/dist/components/NotificationPreferences/NotificationPreferences.js +317 -1
  19. package/dist/components/NotificationPreferences/index.js +7 -1
  20. package/dist/components/SearchableSelects/RASearchableSelect.js +236 -1
  21. package/dist/components/SearchableSelects/RASearchableSelectCheckboxes.js +479 -1
  22. package/dist/components/SearchableSelects/RASearchableSelectChips.js +465 -1
  23. package/dist/components/SearchableSelects/RASearchableSelectChipsRecordContext.js +295 -1
  24. package/dist/components/SearchableSelects/SearchableSelect.js +202 -1
  25. package/dist/components/SearchableSelects/SearchableSelectCheckboxes.js +290 -1
  26. package/dist/components/SortableDataGrid/SortableDataGrid.js +72 -1
  27. package/dist/components/SortableDataGrid/SortableDataGridBody.js +167 -1
  28. package/dist/components/SortableDataGrid/SortableDataGridHeader.js +109 -1
  29. package/dist/components/SortableDataGrid/SortableDataGridRow.js +283 -1
  30. package/dist/components/Upload/SingleImageUploader.js +40 -1
  31. package/dist/components/custom/PackageCopyMethod.js +78 -1
  32. package/dist/components/custom/PackageRadioButtonGroup.js +45 -1
  33. package/dist/components/dialog/Basic.js +8 -1
  34. package/dist/components/dialog/Correct.js +14 -1
  35. package/dist/components/dialog/Error.js +14 -1
  36. package/dist/components/dialog/Info.js +14 -1
  37. package/dist/components/dialog/InfoV2.js +21 -1
  38. package/dist/components/dialog/NotFound.js +14 -1
  39. package/dist/components/dialog/QuickSearch.js +117 -1
  40. package/dist/components/notifications/NotificationsCenter.js +597 -1
  41. package/dist/components/notifications/index.js +24 -1
  42. package/dist/components/tables/PaginationComponent.js +36 -1
  43. package/dist/components/theme/AppLoadingFallback.js +38 -2
  44. package/dist/components/theme/ErrorBoundary.js +52 -1
  45. package/dist/components/theme/InitialLoader.js +13 -1
  46. package/dist/components/theme/NotFound.js +136 -1
  47. package/dist/components/theme/NotResults.js +38 -1
  48. package/dist/hooks/useDraggable.js +6 -1
  49. package/dist/hooks/useNotifications.js +8 -1
  50. package/dist/hooks/useQuickSearch.js +26 -1
  51. package/dist/index.js +66 -5567
  52. package/dist/utils/setNativeValue.js +17 -1
  53. package/package.json +149 -132
  54. package/src/components/AuditLog/AuditLog.tsx +476 -0
  55. package/src/components/AuditLog/index.ts +2 -0
  56. package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +917 -0
  57. package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +39 -0
  58. package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +916 -0
  59. package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +44 -0
  60. package/src/components/Json/Json.tsx +312 -0
  61. package/src/components/JsonColorSelector/JsonColorSelector.tsx +1435 -0
  62. package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +1029 -0
  63. package/src/components/JsonColorSelector/JsonCssVarValues.tsx +863 -0
  64. package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +491 -0
  65. package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +80 -0
  66. package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +328 -0
  67. package/src/components/JsonColorSelector/helpers/functions.tsx +123 -0
  68. package/src/components/JsonColorSelector/interfaces/interfaces.tsx +22 -0
  69. package/src/components/JsonColorSelector/useAsyncColorThief.tsx +97 -0
  70. package/src/components/ListActive/ListActive.tsx +106 -0
  71. package/src/components/ListActive/ListBoolean.tsx +96 -0
  72. package/src/components/NotificationPreferences/NotificationPreferences.tsx +463 -0
  73. package/src/components/NotificationPreferences/index.tsx +2 -0
  74. package/src/components/SearchableSelects/RASearchableSelect.tsx +300 -0
  75. package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +604 -0
  76. package/src/components/SearchableSelects/RASearchableSelectChips.tsx +558 -0
  77. package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +362 -0
  78. package/src/components/SearchableSelects/SearchableSelect.tsx +321 -0
  79. package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +355 -0
  80. package/src/components/SortableDataGrid/SortableDataGrid.tsx +56 -0
  81. package/src/components/SortableDataGrid/SortableDataGridBody.tsx +175 -0
  82. package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +171 -0
  83. package/src/components/SortableDataGrid/SortableDataGridRow.tsx +247 -0
  84. package/src/components/Upload/SingleImageUploader.tsx +55 -0
  85. package/src/components/custom/PackageCopyMethod.tsx +109 -0
  86. package/src/components/custom/PackageRadioButtonGroup.tsx +47 -0
  87. package/src/components/dialog/Basic.tsx +11 -0
  88. package/src/components/dialog/Correct.tsx +16 -0
  89. package/src/components/dialog/Error.tsx +15 -0
  90. package/src/components/dialog/Info.tsx +15 -0
  91. package/src/components/dialog/InfoV2.tsx +35 -0
  92. package/src/components/dialog/NotFound.tsx +18 -0
  93. package/src/components/dialog/QuickSearch.tsx +115 -0
  94. package/src/components/notifications/NotificationsCenter.tsx +792 -0
  95. package/src/components/notifications/index.ts +42 -0
  96. package/src/components/tables/PaginationComponent.tsx +17 -0
  97. package/src/components/theme/AppLoadingFallback.tsx +42 -0
  98. package/src/components/theme/ErrorBoundary.tsx +57 -0
  99. package/src/components/theme/InitialLoader.tsx +16 -0
  100. package/src/components/theme/NotFound.tsx +124 -0
  101. package/src/components/theme/NotResults.tsx +54 -0
  102. package/src/hooks/useDraggable.ts +11 -0
  103. package/src/hooks/useNotifications.ts +14 -0
  104. package/src/hooks/useQuickSearch.tsx +29 -0
  105. package/src/index.tsx +77 -0
  106. package/src/utils/setNativeValue.tsx +23 -0
  107. package/dist/color-thief-CTwGFhOB.js +0 -272
  108. /package/{dist → src}/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -0
  109. /package/{dist → src}/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -0
  110. /package/{dist → src}/components/SortableDataGrid/README.md +0 -0
  111. /package/{dist → src}/styles/index.less +0 -0
@@ -0,0 +1,917 @@
1
+ import * as React from 'react';
2
+ import {
3
+ isValidElement,
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ ReactNode,
10
+ } from 'react';
11
+ import { isEqual , get, debounce} from 'lodash';
12
+ import clsx from 'clsx';
13
+ import {
14
+ Autocomplete,
15
+ AutocompleteProps,
16
+ Checkbox,
17
+ Chip,
18
+ TextField,
19
+ TextFieldProps,
20
+ Tooltip,
21
+ createFilterOptions,
22
+ } from '@mui/material';
23
+ import { styled } from '@mui/material/styles';
24
+ import {
25
+ ChoicesProps,
26
+ FieldTitle,
27
+ RaRecord,
28
+ useChoicesContext,
29
+ useInput,
30
+ useSuggestions,
31
+ UseSuggestionsOptions,
32
+ useTimeout,
33
+ useTranslate,
34
+ warning,
35
+ useGetRecordRepresentation,
36
+ useEvent,
37
+ sanitizeInputRestProps
38
+ } from 'react-admin';
39
+
40
+ import {
41
+ CommonInputProps,
42
+ InputHelperText,
43
+ SupportCreateSuggestionOptions,
44
+ useSupportCreateSuggestion,
45
+ } from 'react-admin';
46
+
47
+ import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
48
+ import CheckBoxIcon from '@mui/icons-material/CheckBox';
49
+ //import { OptionType } from 'dayjs';
50
+
51
+ const icon = <CheckBoxOutlineBlankIcon fontSize='small' />;
52
+ const checkedIcon = <CheckBoxIcon fontSize='small' />;
53
+ const DefaultFilterToQuery = (searchText) => ({ q: searchText });
54
+ const PREFIX = 'RaAutocompleteInput';
55
+
56
+ const defaultFilterOptions = createFilterOptions();
57
+
58
+ export const AutocompleteInputClasses = {
59
+ textField: `${PREFIX}-textField`,
60
+ };
61
+
62
+
63
+ const StyledAutocomplete = styled(Autocomplete, {
64
+ name: PREFIX,
65
+ overridesResolver: (props, styles) => styles.root,
66
+ })(({ theme }) => ({
67
+ [`& .${AutocompleteInputClasses.textField}`]: {
68
+ minWidth: theme.spacing(20),
69
+ },
70
+ }));
71
+
72
+ function cutString(str: string, length: number, suffix = "..."): string {
73
+ // Find the last space character before the given length
74
+ const lastSpaceIndex = str.lastIndexOf(" ", length);
75
+
76
+ // If there is no space character before the given length, return the original string
77
+ if (lastSpaceIndex === -1) {
78
+ return str;
79
+ }
80
+
81
+ // Return the substring from the beginning of the string to the last space character
82
+ // and add the suffix
83
+ return str.substring(0, lastSpaceIndex) + suffix;
84
+ }
85
+
86
+
87
+ // @ts-ignore Expected.
88
+ export interface AutocompleteInputProps<
89
+ OptionType = RaRecord,
90
+ Multiple extends boolean | undefined = false,
91
+ DisableClearable extends boolean | undefined = false,
92
+ SupportCreate extends boolean | undefined = false,
93
+ > extends Omit<CommonInputProps, 'source'>,
94
+ ChoicesProps,
95
+ UseSuggestionsOptions,
96
+ Omit<SupportCreateSuggestionOptions, 'handleChange' | 'optionText'>,
97
+ Omit<
98
+ AutocompleteProps<OptionType, Multiple, DisableClearable, SupportCreate>,
99
+ 'onChange' | 'options' | 'renderInput'
100
+ > {
101
+ children?: ReactNode;
102
+ debounce?: number;
103
+ emptyText?: string;
104
+ emptyValue?: any;
105
+ filterToQuery?: (searchText: string) => any;
106
+ inputText?: (option: any) => string;
107
+ setFilter?: (value: string) => void;
108
+ shouldRenderSuggestions?: any;
109
+ // Source is optional as AutocompleteInput can be used inside a ReferenceInput that already defines the source
110
+ source?: string;
111
+ TextFieldProps?: TextFieldProps;
112
+ }
113
+
114
+
115
+ const getSelectedItems = (
116
+ // eslint-disable-next-line @typescript-eslint/default-param-last
117
+ choices = [],
118
+ value,
119
+ // eslint-disable-next-line @typescript-eslint/default-param-last
120
+ optionValue = 'id',
121
+ multiple,
122
+ ) => {
123
+ if (multiple) {
124
+ return (Array.isArray(value ?? []) ? value : [value])
125
+ .map((item) =>
126
+ choices.find(
127
+ (choice) => String(item) === String(get(choice, optionValue)),
128
+ ),
129
+ )
130
+ .filter((item) => !!item);
131
+ }
132
+ return (
133
+ choices.find(
134
+ (choice) => String(get(choice, optionValue)) === String(value),
135
+ ) || ''
136
+ );
137
+ };
138
+
139
+
140
+ const areSelectedItemsEqual = (
141
+ selectedChoice: RaRecord | RaRecord[],
142
+ newSelectedChoice: RaRecord | RaRecord[],
143
+ // eslint-disable-next-line @typescript-eslint/default-param-last
144
+ optionValue = 'id',
145
+ multiple: boolean,
146
+ ) => {
147
+ if (multiple) {
148
+ const selectedChoiceArray = (selectedChoice as RaRecord[]) ?? [];
149
+ const newSelectedChoiceArray = (newSelectedChoice as RaRecord[]) ?? [];
150
+ if (selectedChoiceArray.length !== newSelectedChoiceArray.length) {
151
+ return false;
152
+ }
153
+ const equalityArray = selectedChoiceArray.map((choice) =>
154
+ newSelectedChoiceArray.some(
155
+ (newChoice) => get(newChoice, optionValue) === get(choice, optionValue),
156
+ ),
157
+ );
158
+ return !equalityArray.some((item) => item === false);
159
+ }
160
+ return (
161
+ get(selectedChoice, optionValue) === get(newSelectedChoice, optionValue)
162
+ );
163
+ };
164
+
165
+ /**
166
+ * UseSelectedChoice.
167
+ *
168
+ * @returns The selected choice (or choices if multiple) by matching the input value with the choices.
169
+ */
170
+ const useSelectedChoice = <
171
+ OptionType = RaRecord,
172
+ Multiple extends boolean | undefined = false,
173
+ DisableClearable extends boolean | undefined = false,
174
+ SupportCreate extends boolean | undefined = false,
175
+ >(
176
+ value: any,
177
+ {
178
+ choices,
179
+ multiple,
180
+ optionValue,
181
+ }: AutocompleteInputProps<
182
+ OptionType,
183
+ Multiple,
184
+ DisableClearable,
185
+ SupportCreate
186
+ >,
187
+ ) => {
188
+ const selectedChoiceRef = useRef(
189
+ getSelectedItems(choices, value, optionValue, multiple),
190
+ );
191
+ const [selectedChoice, setSelectedChoice] = useState<RaRecord | RaRecord[]>(
192
+ () => getSelectedItems(choices, value, optionValue, multiple),
193
+ );
194
+
195
+ // As the selected choices are objects, we want to ensure we pass the same
196
+ // reference to the Autocomplete as it would reset its filter value otherwise.
197
+ useEffect(() => {
198
+ const newSelectedItems = getSelectedItems(
199
+ choices,
200
+ value,
201
+ optionValue,
202
+ multiple,
203
+ );
204
+
205
+ if (
206
+ !areSelectedItemsEqual(
207
+ selectedChoiceRef.current,
208
+ newSelectedItems,
209
+ optionValue,
210
+ multiple,
211
+ )
212
+ ) {
213
+ selectedChoiceRef.current = newSelectedItems;
214
+ setSelectedChoice(newSelectedItems);
215
+ }
216
+ }, [choices, value, multiple, optionValue]);
217
+ return selectedChoice || null;
218
+ };
219
+
220
+
221
+ /**
222
+ * An Input component for an autocomplete field, using an array of objects for the options
223
+ *
224
+ * Pass possible options as an array of objects in the 'choices' attribute.
225
+ *
226
+ * By default, the options are built from:
227
+ * the 'id' property as the option value,
228
+ * the 'name' property as the option text
229
+ *
230
+ * @example
231
+ * const choices = [
232
+ * { id: 'M', name: 'Male' },
233
+ * { id: 'F', name: 'Female' },
234
+ * ];
235
+ * <AutocompleteInput source="gender" choices={choices} />
236
+ *
237
+ * You can also customize the properties to use for the option name and value,
238
+ * thanks to the 'optionText' and 'optionValue' attributes.
239
+ * @example
240
+ * const choices = [
241
+ * { _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
242
+ * { _id: 456, full_name: 'Jane Austen', sex: 'F' },
243
+ * ];
244
+ * <AutocompleteInput source="author_id" choices={choices} optionText="full_name" optionValue="_id" />
245
+ *
246
+ * `optionText` also accepts a function, so you can shape the option text at will:
247
+ * @example
248
+ * const choices = [
249
+ * { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
250
+ * { id: 456, first_name: 'Jane', last_name: 'Austen' },
251
+ * ];
252
+ * const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
253
+ * <AutocompleteInput source="author_id" choices={choices} optionText={optionRenderer} />
254
+ *
255
+ * `optionText` also accepts a React Element, that can access
256
+ * the related choice through the `useRecordContext` hook. You can use Field components there.
257
+ * Note that you must also specify the `matchSuggestion` and `inputText` props
258
+ * @example
259
+ * const choices = [
260
+ * { id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
261
+ * { id: 456, first_name: 'Jane', last_name: 'Austen' },
262
+ * ];
263
+ * const matchSuggestion = (filterValue, choice) => choice.first_name.match(filterValue) || choice.last_name.match(filterValue)
264
+ * const inputText = (record) => `${record.fullName} (${record.language})`;
265
+ *
266
+ * const FullNameField = () => {
267
+ * const record = useRecordContext();
268
+ * return <span>{record.first_name} {record.last_name}</span>;
269
+ * }
270
+ * <AutocompleteInput source="author" choices={choices} optionText={<FullNameField />} matchSuggestion={matchSuggestion} inputText={inputText} />
271
+ *
272
+ * The choices are translated by default, so you can use translation identifiers as choices:
273
+ * @example
274
+ * const choices = [
275
+ * { id: 'M', name: 'myroot.gender.male' },
276
+ * { id: 'F', name: 'myroot.gender.female' },
277
+ * ];
278
+ *
279
+ * However, in some cases (e.g. inside a `<ReferenceInput>`), you may not want
280
+ * the choice to be translated. In that case, set the `translateChoice` prop to false.
281
+ * @example
282
+ * <AutocompleteInput source="gender" choices={choices} translateChoice={false}/>
283
+ *
284
+ * The object passed as `options` props is passed to the Material UI <TextField> component
285
+ * @example
286
+ * <AutocompleteInput source="author_id" options={{ color: 'secondary', InputLabelProps: { shrink: true } }} />
287
+ */
288
+
289
+ const AutocompleteCheckBoxInput = <
290
+ OptionType extends RaRecord = RaRecord,
291
+ Multiple extends boolean | undefined = false,
292
+ DisableClearable extends boolean | undefined = false,
293
+ SupportCreate extends boolean | undefined = false,
294
+ >(
295
+ props: AutocompleteInputProps<
296
+ OptionType,
297
+ Multiple,
298
+ DisableClearable,
299
+ SupportCreate
300
+ >,
301
+ ) => {
302
+ const {
303
+ choices: choicesProp,
304
+ className,
305
+ blurOnSelect = false,
306
+ clearOnBlur = false,
307
+ selectOnFocus = true,
308
+ openOnFocus = true,
309
+ renderTags,
310
+ disableCloseOnSelect = true,
311
+ filterSelectedOptions = false,
312
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
313
+ renderOption = null,
314
+ clearText = 'ra.action.clear_input_value',
315
+ closeText = 'ra.action.close',
316
+ create,
317
+ createLabel,
318
+ createItemLabel,
319
+ createValue,
320
+ debounce: debounceDelay = 250,
321
+ defaultValue,
322
+ emptyText,
323
+ emptyValue = '',
324
+ field: fieldOverride,
325
+ format,
326
+ helperText,
327
+ id: idOverride,
328
+ inputText,
329
+ isFetching: isFetchingProp,
330
+ isLoading: isLoadingProp,
331
+ isRequired: isRequiredOverride,
332
+ label,
333
+ limitChoicesToValue,
334
+ matchSuggestion,
335
+ margin,
336
+ fieldState: fieldStateOverride,
337
+ filterToQuery: filterToQueryProp = DefaultFilterToQuery,
338
+ formState: formStateOverride,
339
+ multiple = false,
340
+ noOptionsText,
341
+ onBlur,
342
+ onChange,
343
+ onCreate,
344
+ openText = 'ra.action.open',
345
+ optionText,
346
+ optionValue,
347
+ parse,
348
+ resource: resourceProp,
349
+ shouldRenderSuggestions,
350
+ setFilter,
351
+ size,
352
+ source: sourceProp,
353
+ suggestionLimit = Infinity,
354
+ // eslint-disable-next-line @typescript-eslint/no-shadow
355
+ TextFieldProps,
356
+ translateChoice,
357
+ validate,
358
+ variant,
359
+ ...rest
360
+ } = props;
361
+
362
+ const filterToQuery = useEvent(filterToQueryProp);
363
+
364
+ // const pendingStateRef = useRef<any[]>([]);
365
+ //const lastOnChangeEvent = useRef<any>(null);
366
+
367
+ const {
368
+ allChoices,
369
+ isLoading,
370
+ error: fetchError,
371
+ resource,
372
+ source,
373
+ setFilters,
374
+ isFromReference,
375
+ } = useChoicesContext({
376
+ choices: choicesProp,
377
+ isFetching: isFetchingProp,
378
+ isLoading: isLoadingProp,
379
+ resource: resourceProp,
380
+ source: sourceProp,
381
+ });
382
+
383
+ const translate = useTranslate();
384
+
385
+ const {
386
+ id,
387
+ field,
388
+ isRequired,
389
+ fieldState: { error, invalid, isTouched },
390
+ formState: { isSubmitted },
391
+ } = useInput({
392
+ defaultValue,
393
+ id: idOverride,
394
+ field: fieldOverride,
395
+ fieldState: fieldStateOverride,
396
+ formState: formStateOverride,
397
+ isRequired: isRequiredOverride,
398
+ onBlur,
399
+ onChange,
400
+ parse,
401
+ format,
402
+ resource,
403
+ source,
404
+ validate,
405
+ ...rest,
406
+ });
407
+
408
+ const finalChoices = useMemo(
409
+ () =>
410
+ // eslint-disable-next-line eqeqeq
411
+ emptyText == undefined || isRequired || multiple
412
+ ? allChoices
413
+ : [
414
+ {
415
+ [optionValue || 'id']: emptyValue,
416
+ [typeof optionText === 'string' ? optionText : 'name']: translate(
417
+ emptyText,
418
+ {
419
+ _: emptyText,
420
+ },
421
+ ),
422
+ }].concat(allChoices),
423
+ [
424
+ allChoices,
425
+ emptyValue,
426
+ emptyText,
427
+ isRequired,
428
+ multiple,
429
+ optionText,
430
+ optionValue,
431
+ translate,
432
+ ],
433
+ );
434
+
435
+ const selectedChoice = useSelectedChoice<
436
+ OptionType,
437
+ Multiple,
438
+ DisableClearable,
439
+ SupportCreate
440
+ >(field.value, {
441
+ choices: finalChoices,
442
+ // @ts-ignore Expected.
443
+ multiple,
444
+ optionValue,
445
+ });
446
+
447
+ useEffect(() => {
448
+ // eslint-disable-next-line eqeqeq
449
+ if (emptyValue == null) {
450
+ throw new Error(
451
+ 'emptyValue being set to null or undefined is not supported. Use parse to turn the empty string into null.',
452
+ );
453
+ }
454
+ }, [emptyValue]);
455
+
456
+ useEffect(() => {
457
+ // eslint-disable-next-line eqeqeq
458
+ if (isValidElement(optionText) && emptyText != undefined) {
459
+ throw new Error(
460
+ 'optionText of type React element is not supported when setting emptyText',
461
+ );
462
+ }
463
+ // eslint-disable-next-line eqeqeq
464
+ if (isValidElement(optionText) && inputText == undefined) {
465
+ throw new Error(`
466
+ If you provided a React element for the optionText prop, you must also provide the inputText prop (used for the text input)`);
467
+ }
468
+ if (
469
+ isValidElement(optionText) &&
470
+ !isFromReference &&
471
+ // eslint-disable-next-line eqeqeq
472
+ matchSuggestion == undefined
473
+ ) {
474
+ throw new Error(`
475
+ If you provided a React element for the optionText prop, you must also provide the matchSuggestion prop (used to match the user input with a choice)`);
476
+ }
477
+ }, [optionText, inputText, matchSuggestion, emptyText, isFromReference]);
478
+
479
+ useEffect(() => {
480
+ warning(
481
+ /* eslint-disable eqeqeq */
482
+ shouldRenderSuggestions != undefined && noOptionsText == undefined,
483
+ 'When providing a shouldRenderSuggestions function, we recommend you also provide the noOptionsText prop and set it to a text explaining users why no options are displayed. It supports translation keys.',
484
+ );
485
+ /* eslint-enable eqeqeq */
486
+ }, [shouldRenderSuggestions, noOptionsText]);
487
+
488
+ const getRecordRepresentation = useGetRecordRepresentation(resource);
489
+
490
+ const { getChoiceText, getChoiceValue, getSuggestions } = useSuggestions({
491
+ choices: finalChoices,
492
+ limitChoicesToValue,
493
+ matchSuggestion,
494
+ optionText:
495
+ optionText ?? (isFromReference ? getRecordRepresentation : undefined),
496
+ optionValue,
497
+ selectedItem: selectedChoice,
498
+ suggestionLimit,
499
+ translateChoice: translateChoice ?? !isFromReference,
500
+ });
501
+
502
+ const [filterValue, setFilterValue] = useState('');
503
+
504
+ //const [pendingState, setPendingState] = useState<any[]>(null);
505
+
506
+ const handleChange = (newValue: any) => {
507
+ let processedValue;
508
+
509
+ if (multiple) {
510
+ if (Array.isArray(newValue)) {
511
+ processedValue = newValue.map(getChoiceValue);
512
+ } else {
513
+ processedValue = [...(field.value ?? []), getChoiceValue(newValue)];
514
+ }
515
+ } else {
516
+ processedValue = getChoiceValue(newValue) ?? emptyValue;
517
+ }
518
+
519
+ field.onChange(processedValue);
520
+
521
+ /*if (lastOnChangeEvent.current[1] === 'clear') {
522
+ field.onChange(processedValue);
523
+ return;
524
+ }
525
+
526
+ if (lastOnChangeEvent.current[0].currentTarget.nodeName === 'svg') {
527
+ field.onChange(processedValue);
528
+ return;
529
+ }*/
530
+
531
+ /*if (lastOnChangeEvent.current[1] === 'removeOption') {
532
+ setPendingState((_) =>
533
+ processedValue.filter((x) => !new Set(pendingState).has(x)),
534
+ );
535
+
536
+ return;
537
+
538
+ }
539
+
540
+ setPendingState((prevState) =>
541
+ prevState
542
+ ? Array.from(new Set([...prevState, ...processedValue]))
543
+ : Array.from(new Set([...processedValue])),
544
+ );
545
+ */
546
+ };
547
+
548
+ /* useEffect(()=>{
549
+ if(pendingState && pendingState.length === 0) {
550
+ field.onChange([]);
551
+ }
552
+ },[pendingState])*/
553
+
554
+ // eslint-disable-next-line
555
+ const debouncedSetFilter = useCallback(
556
+ debounce((filter) => {
557
+ if (setFilter) {
558
+ return setFilter(filter);
559
+ }
560
+
561
+ if (choicesProp) {
562
+ return;
563
+ }
564
+
565
+ setFilters(filterToQuery(filter), [], true);
566
+ }, debounceDelay),
567
+ [debounceDelay, setFilters, setFilter],
568
+ );
569
+
570
+ // We must reset the filter every time the value changes to ensure we
571
+ // display at least some choices even if the input has a value.
572
+ // Otherwise, it would only display the currently selected one and the user
573
+ // would have to first clear the input before seeing any other choices
574
+ const currentValue = useRef(field.value);
575
+ useEffect(() => {
576
+ if (!isEqual(currentValue.current, field.value)) {
577
+ currentValue.current = field.value;
578
+ debouncedSetFilter('');
579
+ }
580
+ }, [field.value]); // eslint-disable-line
581
+
582
+ const {
583
+ getCreateItem,
584
+ handleChange: handleChangeWithCreateSupport,
585
+ createElement,
586
+ createId,
587
+ } = useSupportCreateSuggestion({
588
+ create,
589
+ createLabel,
590
+ createItemLabel,
591
+ createValue,
592
+ handleChange,
593
+ filter: filterValue,
594
+ onCreate,
595
+ optionText,
596
+ });
597
+
598
+ const getOptionLabel = useCallback(
599
+ (option: any, isListItem = false) => {
600
+ // eslint-disable-next-line eqeqeq
601
+ if (option == undefined) {
602
+ return '';
603
+ }
604
+
605
+ // Value selected with enter, right from the input
606
+ if (typeof option === 'string') {
607
+ return option;
608
+ }
609
+
610
+ if (option?.id === createId) {
611
+ return get(
612
+ option,
613
+ typeof optionText === 'string' ? optionText : 'name',
614
+ );
615
+ }
616
+
617
+ if (!isListItem && option[optionValue || 'id'] === emptyValue) {
618
+ return get(
619
+ option,
620
+ typeof optionText === 'string' ? optionText : 'name',
621
+ );
622
+ }
623
+
624
+ if (!isListItem && inputText !== undefined) {
625
+ return inputText(option);
626
+ }
627
+
628
+ return getChoiceText(option);
629
+ },
630
+ [getChoiceText, inputText, createId, optionText, optionValue, emptyValue],
631
+ );
632
+
633
+ /*const finalOnBlur = useCallback((): void => {
634
+ if (clearOnBlur && !multiple) {
635
+ const optionLabel = getOptionLabel(selectedChoice);
636
+ if (!isEqual(optionLabel, filterValue)) {
637
+ setFilterValue(optionLabel);
638
+ debouncedSetFilter('');
639
+ }
640
+ }
641
+
642
+ field.onBlur();
643
+ }, [
644
+ clearOnBlur,
645
+ field,
646
+ getOptionLabel,
647
+ selectedChoice,
648
+ filterValue,
649
+ debouncedSetFilter,
650
+ multiple,
651
+ //pendingStateRef
652
+ ]);*/
653
+
654
+ useEffect(() => {
655
+ if (!multiple) {
656
+ const optionLabel = getOptionLabel(selectedChoice);
657
+ if (typeof optionLabel === 'string') {
658
+ setFilterValue(optionLabel);
659
+ } else {
660
+ throw new Error(
661
+ 'When optionText returns a React element, you must also provide the inputText prop',
662
+ );
663
+ }
664
+ }
665
+ }, [getOptionLabel, multiple, selectedChoice]);
666
+
667
+
668
+ const doesQueryMatchSelection = useCallback(
669
+ (filter: string) => {
670
+ let selectedItemTexts;
671
+
672
+ if (multiple) {
673
+ selectedItemTexts = selectedChoice.map((item) => getOptionLabel(item));
674
+ } else {
675
+ selectedItemTexts = [getOptionLabel(selectedChoice)];
676
+ }
677
+
678
+ return selectedItemTexts.includes(filter);
679
+ },
680
+ [getOptionLabel, multiple, selectedChoice],
681
+ );
682
+ const doesQueryMatchSuggestion = useCallback(
683
+ (filter) => {
684
+ const hasOption = finalChoices
685
+ ? finalChoices.some((choice) => getOptionLabel(choice) === filter)
686
+ : false;
687
+
688
+ return doesQueryMatchSelection(filter) || hasOption;
689
+ },
690
+ [finalChoices, getOptionLabel, doesQueryMatchSelection],
691
+ );
692
+
693
+ const handleInputChange = (
694
+ event: any,
695
+ newInputValue: string,
696
+ _reason: string,
697
+ ) => {
698
+ if (event?.type === 'change' || !doesQueryMatchSelection(newInputValue)) {
699
+ setFilterValue(newInputValue);
700
+ debouncedSetFilter(newInputValue);
701
+ }
702
+ };
703
+
704
+ const filterOptions = (options, params) => {
705
+ let filteredOptions =
706
+ isFromReference || // When used inside a reference, AutocompleteInput shouldn't do the filtering as it's done by the reference input
707
+ matchSuggestion || // When using element as optionText (and matchSuggestion), options are filtered by getSuggestions, so they shouldn't be filtered here
708
+ limitChoicesToValue // When limiting choices to values (why? it's legacy!), options are also filtered by getSuggestions, so they shouldn't be filtered here
709
+ ? options
710
+ : defaultFilterOptions(options, params); // Otherwise, we let Material UI's Autocomplete do the filtering
711
+
712
+ // add create option if necessary
713
+ const { inputValue } = params;
714
+ if (
715
+ (onCreate || create) &&
716
+ inputValue !== '' &&
717
+ !doesQueryMatchSuggestion(filterValue)
718
+ ) {
719
+ filteredOptions = filteredOptions.concat(getCreateItem(inputValue));
720
+ }
721
+
722
+ return filteredOptions;
723
+ };
724
+
725
+ const handleAutocompleteChange = (
726
+ event: any,
727
+ newValue: any,
728
+ _reason: string,
729
+ ) => {
730
+ //lastOnChangeEvent.current = [event, _reason];
731
+ handleChangeWithCreateSupport(newValue !== null ? newValue : emptyValue);
732
+ };
733
+
734
+ const oneSecondHasPassed = useTimeout(1000, filterValue);
735
+
736
+ const suggestions = useMemo(() => {
737
+ if (!isFromReference && (matchSuggestion || limitChoicesToValue)) {
738
+ return getSuggestions(filterValue);
739
+ }
740
+ return finalChoices?.slice(0, suggestionLimit) || [];
741
+ }, [
742
+ finalChoices,
743
+ filterValue,
744
+ getSuggestions,
745
+ limitChoicesToValue,
746
+ matchSuggestion,
747
+ suggestionLimit,
748
+ isFromReference,
749
+ ]);
750
+
751
+ const isOptionEqualToValue = (option, value) => {
752
+ return String(getChoiceValue(option)) === String(getChoiceValue(value));
753
+ };
754
+ const renderHelperText =
755
+ !!fetchError ||
756
+ helperText !== false ||
757
+ ((isTouched || isSubmitted) && invalid);
758
+
759
+ return (
760
+ <>
761
+ <StyledAutocomplete
762
+ className={clsx('ra-input', `ra-input-${source}`, className)}
763
+ clearText={translate(clearText, { _: clearText })}
764
+ closeText={translate(closeText, { _: closeText })}
765
+ openText={translate(openText, { _: openText })}
766
+ id={id}
767
+ isOptionEqualToValue={isOptionEqualToValue}
768
+ //disableClearable={false}
769
+ blurOnSelect={blurOnSelect}
770
+ clearOnBlur={clearOnBlur}
771
+ selectOnFocus={selectOnFocus}
772
+ openOnFocus={openOnFocus}
773
+ disableCloseOnSelect={disableCloseOnSelect}
774
+ filterSelectedOptions={filterSelectedOptions}
775
+
776
+ renderInput={(params) => (
777
+ <TextField
778
+ name={field.name}
779
+ label={
780
+ <FieldTitle
781
+ label={label}
782
+ source={source}
783
+ resource={resourceProp}
784
+ isRequired={isRequired}
785
+ />
786
+ }
787
+ error={!!fetchError || ((isTouched || isSubmitted) && invalid)}
788
+ helperText={
789
+ renderHelperText ? (
790
+ <InputHelperText
791
+ //touched={isTouched || isSubmitted || fetchError}
792
+ error={error?.message || fetchError?.message}
793
+ helperText={helperText}
794
+ />
795
+ ) : null
796
+ }
797
+ margin={margin}
798
+ variant={variant}
799
+ className={AutocompleteInputClasses.textField}
800
+ {...TextFieldProps}
801
+ {...params}
802
+ size={size}
803
+ />
804
+ )}
805
+ multiple={multiple}
806
+ renderTags={
807
+ renderTags
808
+ ? renderTags
809
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
810
+ : (value, getTagProps) => {
811
+ /*return value.map((option, index) => (
812
+ <Chip {...getTagProps({ index })} label={option.name} />
813
+ ));*/
814
+ const attribute = optionText as string;
815
+
816
+ return (
817
+ <Tooltip
818
+ title={value.map((item) => item[attribute]).join(' , ')}
819
+ >
820
+ <Chip
821
+ label={
822
+ cutString(value[0][attribute],10) +
823
+ (value.length > 1 ? ' +' + (value.length - 1) : '')
824
+ }
825
+ sx={{
826
+ '.MuiSvgIcon-root': {
827
+ // FIXME: Workaround to allow choices deletion
828
+ // Maybe related to storybook and mui using different versions of emotion
829
+ zIndex: 100,
830
+ },
831
+ }}
832
+ variant='filled'
833
+ size='small'
834
+ />
835
+ </Tooltip>
836
+ );
837
+ }}
838
+
839
+ /*value.map((option, index) => (
840
+ <Chip
841
+ label={
842
+ isValidElement(optionText)
843
+ ? inputText(option)
844
+ : getChoiceText(option)
845
+ }
846
+ sx={{
847
+ '.MuiSvgIcon-root': {
848
+ // FIXME: Workaround to allow choices deletion
849
+ // Maybe related to storybook and mui using different versions of emotion
850
+ zIndex: 100,
851
+ },
852
+ }}
853
+ variant="outlined"
854
+ size="small"
855
+ {...getTagProps({ index })}
856
+
857
+ />
858
+
859
+ ))*/
860
+ noOptionsText={
861
+ typeof noOptionsText === 'string'
862
+ ? translate(noOptionsText, { _: noOptionsText })
863
+ : noOptionsText
864
+ }
865
+ {...sanitizeInputRestProps(rest)}
866
+ freeSolo={!!create || !!onCreate}
867
+ handleHomeEndKeys={!!create || !!onCreate}
868
+ filterOptions={filterOptions}
869
+ options={
870
+ shouldRenderSuggestions == undefined || // eslint-disable-line eqeqeq
871
+ shouldRenderSuggestions(filterValue)
872
+ ? suggestions
873
+ : []
874
+ }
875
+ getOptionLabel={getOptionLabel}
876
+ inputValue={filterValue}
877
+ loading={
878
+ isLoading &&
879
+ (!finalChoices || finalChoices.length === 0) &&
880
+ oneSecondHasPassed
881
+ }
882
+ value={selectedChoice}
883
+ onChange={(e, val, reason) => {
884
+ handleAutocompleteChange(e, val, reason);
885
+ }}
886
+ //onBlur={finalOnBlur}
887
+ onInputChange={handleInputChange}
888
+ clearOnEscape={false}
889
+
890
+
891
+ renderOption={(p, option: RaRecord, { selected }) => {
892
+ const sel: boolean =
893
+ selectedChoice && selectedChoice.find((ele) => ele.id === option.id)
894
+ ? true
895
+ : selected;
896
+
897
+ return (
898
+ <li {...p} >
899
+ <Checkbox
900
+ icon={icon}
901
+ checkedIcon={checkedIcon}
902
+ style={{ marginRight: 8 }}
903
+ checked={sel}
904
+ //defaultChecked={sel}
905
+ />
906
+ {option[optionText as string]}
907
+ </li>
908
+ );
909
+ }}
910
+
911
+ />
912
+ {createElement}
913
+ </>
914
+ );
915
+ };
916
+
917
+ export default AutocompleteCheckBoxInput;