@dashadmin/dash-components 1.3.25 → 1.3.28

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