funda-ui 4.4.35 → 4.5.12

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 (40) hide show
  1. package/Date/index.js +1078 -77
  2. package/EventCalendar/index.js +8 -2
  3. package/EventCalendarTimeline/index.js +32 -1
  4. package/Input/index.d.ts +7 -0
  5. package/Input/index.js +699 -57
  6. package/MasonryLayout/index.js +11 -1
  7. package/MultipleSelect/index.d.ts +1 -0
  8. package/MultipleSelect/index.js +7 -5
  9. package/RangeSlider/index.js +1078 -77
  10. package/Select/index.js +34 -2
  11. package/Textarea/index.d.ts +7 -0
  12. package/Textarea/index.js +707 -10
  13. package/Tree/index.js +2 -0
  14. package/Utils/inputsCalculation.d.ts +18 -1
  15. package/Utils/inputsCalculation.js +26 -0
  16. package/lib/cjs/Date/index.js +1078 -77
  17. package/lib/cjs/EventCalendar/index.js +8 -2
  18. package/lib/cjs/EventCalendarTimeline/index.js +32 -1
  19. package/lib/cjs/Input/index.d.ts +7 -0
  20. package/lib/cjs/Input/index.js +699 -57
  21. package/lib/cjs/MasonryLayout/index.js +11 -1
  22. package/lib/cjs/MultipleSelect/index.d.ts +1 -0
  23. package/lib/cjs/MultipleSelect/index.js +7 -5
  24. package/lib/cjs/RangeSlider/index.js +1078 -77
  25. package/lib/cjs/Select/index.js +34 -2
  26. package/lib/cjs/Textarea/index.d.ts +7 -0
  27. package/lib/cjs/Textarea/index.js +707 -10
  28. package/lib/cjs/Tree/index.js +2 -0
  29. package/lib/cjs/Utils/inputsCalculation.d.ts +18 -1
  30. package/lib/cjs/Utils/inputsCalculation.js +26 -0
  31. package/lib/esm/EventCalendar/index.tsx +8 -6
  32. package/lib/esm/EventCalendarTimeline/index.tsx +439 -403
  33. package/lib/esm/Input/index.tsx +299 -77
  34. package/lib/esm/MasonryLayout/index.tsx +9 -2
  35. package/lib/esm/MultipleSelect/index.tsx +6 -4
  36. package/lib/esm/Textarea/index.tsx +332 -39
  37. package/lib/esm/Tree/TreeList.tsx +4 -1
  38. package/lib/esm/Tree/index.tsx +1 -0
  39. package/lib/esm/Utils/libs/inputsCalculation.ts +60 -31
  40. package/package.json +1 -1
@@ -3,6 +3,9 @@ import React, { useState, useEffect, useRef, forwardRef, ChangeEvent, Compositio
3
3
 
4
4
  import useComId from 'funda-utils/dist/cjs/useComId';
5
5
  import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
6
+ import { actualPropertyValue, getTextTop } from 'funda-utils/dist/cjs/inputsCalculation';
7
+ import useDebounce from 'funda-utils/dist/cjs/useDebounce';
8
+
6
9
 
7
10
 
8
11
  export type InputProps = {
@@ -20,8 +23,8 @@ export type InputProps = {
20
23
  units?: React.ReactNode | string;
21
24
  name?: string;
22
25
  step?: any;
23
- min?: any;
24
- max?: any;
26
+ min?: any;
27
+ max?: any;
25
28
  src?: any;
26
29
  size?: any;
27
30
  minLength?: any;
@@ -36,6 +39,13 @@ export type InputProps = {
36
39
  iconLeft?: React.ReactNode | string;
37
40
  iconRight?: React.ReactNode | string;
38
41
  appendControl?: React.ReactNode;
42
+ aiPredict?: boolean;
43
+ aiPredictRemainingTextRGB?: number[];
44
+ aiPredictConfirmKey?: Array<string[]>;
45
+ aiPredictFetchFuncAsync?: any;
46
+ aiPredictFetchFuncMethod?: string;
47
+ aiPredictFetchFuncMethodParams?: any[];
48
+ aiPredictFetchCallback?: (data: any) => void;
39
49
  /** -- */
40
50
  id?: string;
41
51
  style?: React.CSSProperties;
@@ -86,6 +96,13 @@ const Input = forwardRef((props: InputProps, externalRef: any) => {
86
96
  inputMode,
87
97
  id,
88
98
  appendControl,
99
+ aiPredict = false,
100
+ aiPredictRemainingTextRGB = [153, 153, 153],
101
+ aiPredictConfirmKey = [['Enter'], ['Tab'], ['Shift', ' ']],
102
+ aiPredictFetchFuncAsync,
103
+ aiPredictFetchFuncMethod,
104
+ aiPredictFetchFuncMethodParams,
105
+ aiPredictFetchCallback,
89
106
  iconLeft,
90
107
  iconRight,
91
108
  autoComplete,
@@ -112,8 +129,175 @@ const Input = forwardRef((props: InputProps, externalRef: any) => {
112
129
  const [onComposition, setOnComposition] = useState<boolean>(false);
113
130
  const [changedVal, setChangedVal] = useState<string>(value || '');
114
131
 
132
+
133
+ //================================================================
134
+ // AI Predict
135
+ //================================================================
136
+ const [currentSuggestion, setCurrentSuggestion] = useState<string>('');
137
+ const [tempMatchedSuggestion, setTempMatchedSuggestion] = useState<string[]>([]);
138
+ const [textWidth, setTextWidth] = useState<number>(0);
139
+ const aiInputRef = useRef<any>(null);
140
+ const originInputComputedStyle = useRef<Record<string, any>>({
141
+ fontSize: 16,
142
+ fontFamily: 'inherit',
143
+ letterSpacing: 'normal',
144
+ textTop: 10
145
+ });
146
+ const [hasErr, setHasErr] = useState<boolean>(false);
147
+ const currentSuggestionIndex = useRef<number>(0);
148
+
115
149
 
116
150
 
151
+
152
+ // A list of suggestions
153
+ //----------------
154
+ const [suggestions, setSuggestions] = useState<string[]>([]);
155
+
156
+ //performance
157
+ const handleChangeSuggestionsFetchSafe = useDebounce((e: any, curVal: string) => {
158
+ const _oparams: any[] = aiPredictFetchFuncMethodParams || [];
159
+ const _params: any[] = _oparams.map((item: any) => item !== '$QUERY_STRING' ? item : curVal);
160
+ fetchSuggestionsData((_params).join(',')).then((resSuggestions: string[]) => {
161
+ handleInputAiPredictChange(curVal, resSuggestions);
162
+ });
163
+ }, 350, []);
164
+
165
+ async function fetchSuggestionsData(params: any) {
166
+
167
+ if (typeof aiPredictFetchFuncAsync === 'object') {
168
+
169
+ const response: any = await aiPredictFetchFuncAsync[`${aiPredictFetchFuncMethod}`](...params.split(','));
170
+ let _ORGIN_DATA = response.data;
171
+
172
+ // reset data structure
173
+ if (typeof (aiPredictFetchCallback) === 'function') {
174
+ _ORGIN_DATA = aiPredictFetchCallback(_ORGIN_DATA);
175
+ }
176
+
177
+ // Determine whether the data structure matches
178
+ if (!Array.isArray(_ORGIN_DATA)) {
179
+ console.warn('The data structure does not match, please refer to the example in the component documentation.');
180
+ setHasErr(true);
181
+ _ORGIN_DATA = [];
182
+ }
183
+
184
+
185
+ //
186
+ setSuggestions(_ORGIN_DATA);
187
+
188
+ return _ORGIN_DATA;
189
+ } else {
190
+ return [];
191
+ }
192
+
193
+
194
+ }
195
+
196
+
197
+
198
+ // Calculates the width of the input text
199
+ //----------------
200
+ const calculateTextWidth = (text: string) => {
201
+ if (valRef.current) {
202
+ const canvas = document.createElement('canvas');
203
+ const context: any = canvas.getContext('2d');
204
+ context.font = `${originInputComputedStyle.current.fontSize}px ${originInputComputedStyle.current.fontFamily}`;
205
+ return context.measureText(text).width;
206
+ }
207
+ return 0;
208
+ };
209
+
210
+
211
+ // Get the rest of the suggested text
212
+ //----------------
213
+ const getRemainingText = (fullSuggestion: string) => {
214
+ if (!changedVal || !fullSuggestion) return '';
215
+
216
+
217
+ // Only the parts of the suggested text that were not entered are returned
218
+ const lastInputChar = changedVal[changedVal.length - 1];
219
+ const lastCharIndex = fullSuggestion.toLowerCase().lastIndexOf(lastInputChar.toLowerCase());
220
+ return fullSuggestion.slice(lastCharIndex + 1);
221
+
222
+ };
223
+
224
+ // Match exactly from the start
225
+ //----------------
226
+ const preciseMatch = (input: any, suggestions: string[]) => {
227
+ if (!input) return '';
228
+
229
+ const filtered = suggestions.filter(s =>
230
+ s.toLowerCase().startsWith(input.toLowerCase())
231
+ );
232
+
233
+ setTempMatchedSuggestion(filtered);
234
+ return filtered;
235
+ };
236
+
237
+
238
+ // Fuzzy matching
239
+ //----------------
240
+ const fuzzyMatch = (input: any, suggestions: string[]) => {
241
+ if (!input) return '';
242
+
243
+ // Convert input to a regular expression pattern with support for arbitrary position matching
244
+ const pattern = input.split('').map((char: string) =>
245
+ char.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
246
+ ).join('.*');
247
+ const regex = new RegExp(pattern, 'i');
248
+
249
+ // Find the first matching suggestion (multiple matches)
250
+ const filtered = suggestions.filter((suggestion: string) => regex.test(suggestion));
251
+ setTempMatchedSuggestion(filtered);
252
+ return filtered;
253
+ };
254
+
255
+ // Handle input variations
256
+ //----------------
257
+ const handleInputAiPredictChange = (newValue: string, curSuggestions: string[]) => {
258
+ setTextWidth(calculateTextWidth(newValue));
259
+
260
+ // Match results
261
+ const matchedSuggestion = fuzzyMatch(newValue, curSuggestions);
262
+ setCurrentSuggestion(matchedSuggestion[0] || '');
263
+
264
+ };
265
+
266
+
267
+
268
+
269
+ // Calculate the color shade of the prompt text
270
+ //----------------
271
+ const calculateOpacity = () => {
272
+ // Transparency is calculated based on the input length
273
+ const baseOpacity = 0.5;
274
+ const inputLength = changedVal.length;
275
+ return Math.max(0.2, baseOpacity - (inputLength * 0.05));
276
+ };
277
+
278
+
279
+
280
+ // Confirm
281
+ //----------------
282
+ const handleAiPredictKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
283
+ // Prevents the default behavior of the enter key
284
+ e.preventDefault();
285
+
286
+ const remainingText = getRemainingText(currentSuggestion);
287
+ if (remainingText) {
288
+ // Only the second half of the text is added
289
+ setChangedVal(changedVal + remainingText);
290
+ setCurrentSuggestion('');
291
+ }
292
+ };
293
+
294
+ //
295
+ const remainingText = getRemainingText(currentSuggestion);
296
+
297
+
298
+ //================================================================
299
+ // General
300
+ //================================================================
117
301
  // exposes the following methods
118
302
  useImperativeHandle(
119
303
  contentRef,
@@ -151,7 +335,7 @@ const Input = forwardRef((props: InputProps, externalRef: any) => {
151
335
  rootRef.current?.classList.add('focus');
152
336
 
153
337
  //
154
- onFocus?.(event, onComposition, valRef.current);
338
+ onFocus?.(event, onComposition, valRef.current);
155
339
  }
156
340
 
157
341
  function handleChange(event: ChangeEvent<HTMLInputElement>) {
@@ -159,7 +343,7 @@ const Input = forwardRef((props: InputProps, externalRef: any) => {
159
343
 
160
344
  setChangedVal(val);
161
345
 
162
-
346
+
163
347
  //----
164
348
  //remove focus style
165
349
  if (val === '') {
@@ -198,28 +382,75 @@ const Input = forwardRef((props: InputProps, externalRef: any) => {
198
382
  if (newData) setChangedVal(newData); // Avoid the error "react checkbox changing an uncontrolled input to be controlled"
199
383
  }
200
384
  }
201
-
385
+
386
+
202
387
  function handleKeyPressed(event: KeyboardEvent<HTMLInputElement>) {
203
388
  if (typeof (onKeyPressedCallback) === 'function') {
204
389
  const newData: any = onKeyPressedCallback(event, valRef.current);
205
390
  if (newData) setChangedVal(newData); // Avoid the error "react checkbox changing an uncontrolled input to be controlled"
206
391
  }
207
392
 
393
+
208
394
  if (event.code == "Enter") {
209
395
  // DO NOT USE "preventDefault()"
210
396
  onPressEnter?.(event, valRef.current);
211
397
  }
212
398
 
399
+
400
+ // AI Predict
401
+ //----
402
+ if (aiPredict && currentSuggestion !== '') {
403
+ const keyBindings: Array<string[]> = aiPredictConfirmKey;
404
+ // The parameter 'registerKeyEvents' is an array, and each element is an object
405
+ // eg. { keys: ["Control", "S"], action: () => { console.log("Ctrl+S"); } }
406
+ const registerKeyEvents: Record<string, any>[] = keyBindings.map((s: string[]) => {
407
+ return {
408
+ keys: s,
409
+ action: () => {
410
+ handleAiPredictKeyDown(event);
411
+ },
412
+ };
413
+ });
414
+
415
+ registerKeyEvents.forEach((binding: Record<string, any>) => {
416
+ const keysPressed = binding.keys.every((key: string) =>
417
+ key === "Shift" ? event.shiftKey :
418
+ key === "Control" ? event.ctrlKey :
419
+ key === "Alt" ? event.altKey :
420
+ key === "Meta" ? event.metaKey :
421
+ event.key === key
422
+ );
423
+
424
+ if (keysPressed) {
425
+ binding.action();
426
+ }
427
+ });
428
+
429
+
430
+ // switch result of suggestions
431
+ if (event.code === 'ArrowUp') {
432
+ currentSuggestionIndex.current = (currentSuggestionIndex.current - 1 + tempMatchedSuggestion.length) % tempMatchedSuggestion.length;
433
+ }
434
+
435
+ if (event.code === 'ArrowDown') {
436
+ currentSuggestionIndex.current = (currentSuggestionIndex.current + 1) % tempMatchedSuggestion.length;
437
+ }
438
+ setCurrentSuggestion(tempMatchedSuggestion[currentSuggestionIndex.current] || '');
439
+
440
+ }
441
+
442
+
213
443
  }
214
444
 
445
+
215
446
  useEffect(() => {
216
-
447
+
217
448
  // update default value
218
449
  //--------------
219
450
  if (typeof value !== 'undefined') {
220
451
  setChangedVal(`${value}`); // Avoid displaying the number 0
221
452
  }
222
-
453
+
223
454
 
224
455
  }, [value]);
225
456
 
@@ -232,6 +463,19 @@ const Input = forwardRef((props: InputProps, externalRef: any) => {
232
463
  setChangedVal(`${defaultValue}`); // Avoid displaying the number 0
233
464
  }
234
465
 
466
+
467
+ // AI Predict initalization
468
+ //--------------
469
+ if (aiPredict && valRef.current !== null) {
470
+ originInputComputedStyle.current = {
471
+ fontSize: actualPropertyValue(valRef.current as HTMLInputElement, 'fontSize'),
472
+ fontFamily: actualPropertyValue(valRef.current as HTMLInputElement, 'fontFamily'),
473
+ letterSpacing: actualPropertyValue(valRef.current as HTMLInputElement, 'letterSpacing'),
474
+ textTop: getTextTop(valRef.current)
475
+ };
476
+ }
477
+
478
+
235
479
  }, []);
236
480
 
237
481
 
@@ -239,76 +483,19 @@ const Input = forwardRef((props: InputProps, externalRef: any) => {
239
483
  <>
240
484
 
241
485
  <div className={clsWrite(wrapperClassName, 'mb-3 position-relative')} ref={rootRef}>
242
- {label ? <>{typeof label === 'string' ? <label htmlFor={idRes} className="form-label" dangerouslySetInnerHTML={{__html: `${label}`}}></label> : <label htmlFor={idRes} className="form-label">{label}</label>}</> : null}
243
-
486
+ {label ? <>{typeof label === 'string' ? <label htmlFor={idRes} className="form-label" dangerouslySetInnerHTML={{ __html: `${label}` }}></label> : <label htmlFor={idRes} className="form-label">{label}</label>}</> : null}
487
+
244
488
  <div className={combinedCls(
245
- 'position-relative',
489
+ 'position-relative z-1',
246
490
  clsWrite(controlGroupWrapperClassName, 'input-group'),
247
491
  {
248
492
  'has-left-content': propExist(iconLeft),
249
493
  'has-right-content': propExist(iconRight) || propExist(units)
250
494
  }
251
495
  )}>
252
- {propExist(iconLeft) ? <><span className={clsWrite(controlGroupTextClassName, 'input-group-text')}>{iconLeft}</span></>: null}
253
-
254
- {appendControl && (propExist(iconLeft)) ? <>
255
- <div className="input-group-control-container w-100 position-relative">
256
-
257
- <input
258
- ref={(node) => {
259
- valRef.current = node;
260
- if (typeof externalRef === 'function') {
261
- externalRef(node);
262
- } else if (externalRef) {
263
- externalRef.current = node;
264
- }
265
- }}
266
-
267
- tabIndex={tabIndex || 0}
268
- type={typeRes}
269
- className={combinedCls(
270
- propExist(iconLeft) ? 'rounded-start-0' : 'rounded',
271
- clsWrite(controlClassName, 'form-control ')
272
- )}
273
- id={idRes}
274
- name={name}
275
- step={step || null}
276
- min={min || null}
277
- max={max || null}
278
- src={src || null}
279
- size={size || null}
280
- alt={alt || null}
281
- inputMode={inputMode || 'text'}
282
- pattern={pattern || null}
283
- placeholder={placeholder || ''}
284
- defaultValue={defaultValue}
285
- value={changedVal}
286
- minLength={minLength || null}
287
- maxLength={maxLength || null}
288
- autoComplete={typeof autoComplete === 'undefined' ? 'on' : autoComplete}
289
- autoCapitalize={typeof autoCapitalize === 'undefined' ? 'off' : autoCapitalize}
290
- spellCheck={typeof spellCheck === 'undefined' ? false : spellCheck}
291
- onFocus={handleFocus}
292
- onBlur={handleBlur}
293
- onChange={handleChange}
294
- onKeyDown={handleKeyPressed}
295
- onCompositionStart={handleComposition}
296
- onCompositionUpdate={handleComposition}
297
- onCompositionEnd={handleComposition}
298
- disabled={disabled || null}
299
- required={required || null}
300
- readOnly={readOnly || null}
301
- style={style}
302
- {...attributes}
303
- />
304
- {appendControl || ''}
305
- {propExist(units) ? <><span className={clsWrite(controlGroupTextClassName, 'input-group-text')}>{units}</span></> : null}
306
- {propExist(iconRight) ? <><span className={clsWrite(controlGroupTextClassName, 'input-group-text')}>{iconRight}</span></> : null}
307
- </div>
308
-
309
- </> : <>
310
-
496
+ {propExist(iconLeft) ? <><span className={clsWrite(controlGroupTextClassName, 'input-group-text')}>{iconLeft}</span></> : null}
311
497
 
498
+ <div className="input-group-control-container flex-fill position-relative">
312
499
  <input
313
500
  ref={(node) => {
314
501
  valRef.current = node;
@@ -318,14 +505,16 @@ const Input = forwardRef((props: InputProps, externalRef: any) => {
318
505
  externalRef.current = node;
319
506
  }
320
507
  }}
321
-
508
+
322
509
  tabIndex={tabIndex || 0}
323
510
  type={typeRes}
324
511
  className={combinedCls(
325
512
  clsWrite(controlClassName, 'form-control'),
326
513
  controlExClassName,
327
514
  {
328
- 'rounded': !propExist(iconLeft) && !propExist(iconRight) && !propExist(units)
515
+ 'rounded': !propExist(iconLeft) && !propExist(iconRight) && !propExist(units),
516
+ 'rounded-start-0': propExist(iconLeft),
517
+ 'rounded-end-0': propExist(iconRight) || propExist(units)
329
518
  }
330
519
  )}
331
520
  id={idRes}
@@ -347,7 +536,15 @@ const Input = forwardRef((props: InputProps, externalRef: any) => {
347
536
  spellCheck={typeof spellCheck === 'undefined' ? false : spellCheck}
348
537
  onFocus={handleFocus}
349
538
  onBlur={handleBlur}
350
- onChange={handleChange}
539
+ onChange={(e: any) => {
540
+ handleChange(e);
541
+
542
+ // AI Predict
543
+ if (aiPredict) {
544
+ handleChangeSuggestionsFetchSafe(e, e.target.value);
545
+ }
546
+
547
+ }}
351
548
  onKeyDown={handleKeyPressed}
352
549
  onCompositionStart={handleComposition}
353
550
  onCompositionUpdate={handleComposition}
@@ -358,16 +555,41 @@ const Input = forwardRef((props: InputProps, externalRef: any) => {
358
555
  style={style}
359
556
  {...attributes}
360
557
  />
558
+
361
559
  {appendControl || ''}
362
- {propExist(units) ? <><span className={clsWrite(controlGroupTextClassName, 'input-group-text')}>{units}</span></> : null}
363
- {propExist(iconRight) ? <><span className={clsWrite(controlGroupTextClassName, 'input-group-text')}>{iconRight}</span></> : null}
560
+
561
+ {/* AI Predict */}
562
+ {aiPredict && remainingText && (
563
+ <div
564
+ ref={aiInputRef}
565
+ className="position-absolute z-1"
566
+ style={{
567
+ left: `${originInputComputedStyle.current.fontSize + textWidth}px`,
568
+ top: originInputComputedStyle.current.textTop + 'px',
569
+ color: `rgba(${aiPredictRemainingTextRGB[0]}, ${aiPredictRemainingTextRGB[1]}, ${aiPredictRemainingTextRGB[2]}, ${calculateOpacity()})`,
570
+ pointerEvents: 'none',
571
+ fontSize: originInputComputedStyle.current.fontSize + 'px',
572
+ fontFamily: originInputComputedStyle.current.fontFamily,
573
+ letterSpacing: originInputComputedStyle.current.letterSpacing
574
+ }}
575
+ >
576
+ {remainingText}
577
+ </div>
578
+ )}
579
+
580
+ {/* Required marking */}
581
+ {required ? <>{requiredLabel || requiredLabel === '' ? requiredLabel : <span className="position-absolute end-0 top-0 my-2 mx-2"><span className="text-danger">*</span></span>}</> : ''}
582
+
583
+ </div>
364
584
 
365
585
 
366
- </>}
586
+ {propExist(units) ? <><span className={clsWrite(controlGroupTextClassName, 'input-group-text')}>{units}</span></> : null}
587
+ {propExist(iconRight) ? <><span className={clsWrite(controlGroupTextClassName, 'input-group-text')}>{iconRight}</span></> : null}
588
+
367
589
 
368
-
369
590
  </div>
370
- {required ? <>{requiredLabel || requiredLabel === '' ? requiredLabel : <span className="position-absolute end-0 top-0 my-2 mx-2"><span className="text-danger">*</span></span>}</> : ''}
591
+
592
+
371
593
 
372
594
  </div>
373
595
 
@@ -87,15 +87,22 @@ const MasonryLayout = (props: MasonryLayoutProps) => {
87
87
  items = children;
88
88
  }
89
89
 
90
+ // get wrapper width
91
+ const wrapperWidth = rootRef.current?.offsetWidth || 0;
92
+ const perBrickWidth = wrapperWidth/colCount; // Prevent the width of the internal elements from overflowing
93
+
94
+
90
95
  items.forEach((el: React.ReactNode, i: number) => {
91
96
  const columnIndex = i % colCount;
92
97
  columnWrapper[`${itemWrapperKey}${columnIndex}`].push(
93
98
  <div
94
99
  key={i}
95
100
  className="masonry-item"
96
- style={{ marginBottom: `${GAP}px` }}
101
+ style={{
102
+ marginBottom: `${GAP}px`
103
+ }}
97
104
  >
98
- {el}
105
+ <div style={perBrickWidth > 0 ? {maxWidth: perBrickWidth + 'px'} : undefined}>{el}</div>
99
106
  </div>
100
107
  );
101
108
  });
@@ -40,6 +40,7 @@ export type MultipleSelectProps = {
40
40
  availableHeaderTitle?: string;
41
41
  selectedHeaderTitle?: string;
42
42
  selectedHeaderNote?: string;
43
+ selectedResultReverse?: boolean;
43
44
  removeAllBtnLabel?: string | React.ReactNode;
44
45
  addAllBtnLabel?: string | React.ReactNode;
45
46
  iconAdd?: React.ReactNode | string;
@@ -88,6 +89,7 @@ const MultipleSelect = forwardRef((props: MultipleSelectProps, externalRef: any)
88
89
  availableHeaderTitle,
89
90
  selectedHeaderTitle,
90
91
  selectedHeaderNote,
92
+ selectedResultReverse = false,
91
93
  removeAllBtnLabel,
92
94
  addAllBtnLabel,
93
95
  iconAdd,
@@ -314,7 +316,7 @@ const MultipleSelect = forwardRef((props: MultipleSelectProps, externalRef: any)
314
316
  const index = newData.findIndex((item: string | number) => item == _val);
315
317
  if (index !== -1) newData.splice(index, 1);
316
318
 
317
- const _res = (_val) ? Array.from(new Set([_val, ...newData])) : newData;
319
+ const _res = (_val) ? Array.from(new Set([...newData, _val])) : newData;
318
320
  onChange?.(_li, _res, VALUE_BY_BRACKETS ? convertArrToValByBrackets(_res) : _res.join(','), _data, 'add');
319
321
 
320
322
  // hide current item
@@ -330,7 +332,7 @@ const MultipleSelect = forwardRef((props: MultipleSelectProps, externalRef: any)
330
332
  const index = newData.findIndex((item: any) => item.value == _val);
331
333
  if (index !== -1) newData.splice(index, 1);
332
334
 
333
- const _res = (_val) ? Array.from(new Set([_data, ...newData])) : newData;
335
+ const _res = (_val) ? Array.from(new Set([...newData, _data])) : newData;
334
336
 
335
337
 
336
338
  return _res;
@@ -596,13 +598,13 @@ const MultipleSelect = forwardRef((props: MultipleSelectProps, externalRef: any)
596
598
  listContainerClassName="m-select__selected m-select__options-contentlist--sortable m-select__options-contentlist"
597
599
  ref={selectedListRef}
598
600
  indentStr={INDENT_LAST_PLACEHOLDER}
599
- valSelected={valSelected}
601
+ valSelected={selectedResultReverse ? valSelected.reverse() : valSelected}
600
602
  iconRemove={iconRemove}
601
603
  onSelect={removeItem}
602
604
  alternateCollapse={alternateCollapse}
603
605
  first={true}
604
606
  arrow={arrow}
605
- data={valSelectedData}
607
+ data={selectedResultReverse ? valSelectedData.reverse() : valSelectedData}
606
608
  childClassName={clsWrite(childClassName, 'm-select__options-contentlist--custom')}
607
609
  selected
608
610
  />