funda-ui 4.7.133 → 4.7.150

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 (51) hide show
  1. package/CascadingSelect/index.css +15 -4
  2. package/CascadingSelect/index.d.ts +2 -0
  3. package/CascadingSelect/index.js +294 -22
  4. package/CascadingSelectE2E/index.css +15 -4
  5. package/CascadingSelectE2E/index.d.ts +2 -0
  6. package/CascadingSelectE2E/index.js +300 -28
  7. package/Checkbox/index.js +4 -2
  8. package/LiveSearch/index.js +2 -1
  9. package/Refresher/index.js +3 -3
  10. package/Select/index.css +33 -0
  11. package/Select/index.d.ts +1 -0
  12. package/Select/index.js +350 -39
  13. package/SplitterPanel/index.js +3 -3
  14. package/Switch/index.js +4 -2
  15. package/Utils/format-string.d.ts +2 -1
  16. package/Utils/format-string.js +22 -12
  17. package/Utils/time.d.ts +3 -3
  18. package/Utils/useIsMobile.js +3 -3
  19. package/lib/cjs/CascadingSelect/index.d.ts +2 -0
  20. package/lib/cjs/CascadingSelect/index.js +294 -22
  21. package/lib/cjs/CascadingSelectE2E/index.d.ts +2 -0
  22. package/lib/cjs/CascadingSelectE2E/index.js +300 -28
  23. package/lib/cjs/Checkbox/index.js +4 -2
  24. package/lib/cjs/LiveSearch/index.js +2 -1
  25. package/lib/cjs/Refresher/index.js +3 -3
  26. package/lib/cjs/Select/index.d.ts +1 -0
  27. package/lib/cjs/Select/index.js +350 -39
  28. package/lib/cjs/SplitterPanel/index.js +3 -3
  29. package/lib/cjs/Switch/index.js +4 -2
  30. package/lib/cjs/Utils/format-string.d.ts +2 -1
  31. package/lib/cjs/Utils/format-string.js +22 -12
  32. package/lib/cjs/Utils/time.d.ts +3 -3
  33. package/lib/cjs/Utils/useIsMobile.js +3 -3
  34. package/lib/css/CascadingSelect/index.css +15 -4
  35. package/lib/css/CascadingSelectE2E/index.css +15 -4
  36. package/lib/css/Select/index.css +33 -0
  37. package/lib/esm/CascadingSelect/index.scss +22 -7
  38. package/lib/esm/CascadingSelect/index.tsx +49 -1
  39. package/lib/esm/CascadingSelectE2E/Group.tsx +1 -0
  40. package/lib/esm/CascadingSelectE2E/index.scss +23 -6
  41. package/lib/esm/CascadingSelectE2E/index.tsx +53 -1
  42. package/lib/esm/Checkbox/index.tsx +5 -3
  43. package/lib/esm/LiveSearch/index.tsx +2 -1
  44. package/lib/esm/Select/index.scss +43 -2
  45. package/lib/esm/Select/index.tsx +81 -24
  46. package/lib/esm/Select/utils/func.ts +0 -10
  47. package/lib/esm/Switch/index.tsx +4 -2
  48. package/lib/esm/Utils/hooks/useIsMobile.tsx +9 -12
  49. package/lib/esm/Utils/libs/format-string.ts +22 -12
  50. package/lib/esm/Utils/libs/time.ts +6 -6
  51. package/package.json +1 -1
@@ -20,9 +20,13 @@ import {
20
20
  addTreeDepth,
21
21
  addTreeIndent
22
22
  } from 'funda-utils/dist/cjs/tree';
23
+ import {
24
+ htmlToPlain
25
+ } from 'funda-utils/dist/cjs/format-string';
23
26
  import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
24
27
 
25
28
 
29
+
26
30
  import Group from './Group';
27
31
 
28
32
 
@@ -45,6 +49,8 @@ export type CascadingSelectE2EProps = {
45
49
  wrapperClassName?: string;
46
50
  controlClassName?: string;
47
51
  controlExClassName?: string;
52
+ searchable?: boolean;
53
+ searchPlaceholder?: string;
48
54
  perColumnHeadersShow?: boolean;
49
55
  exceededSidePosOffset?: number;
50
56
  value?: string;
@@ -107,6 +113,8 @@ const CascadingSelectE2E = (props: CascadingSelectE2EProps) => {
107
113
  wrapperClassName,
108
114
  controlClassName,
109
115
  controlExClassName,
116
+ searchable = false,
117
+ searchPlaceholder = '',
110
118
  perColumnHeadersShow = true,
111
119
  exceededSidePosOffset,
112
120
  disabled,
@@ -151,6 +159,9 @@ const CascadingSelectE2E = (props: CascadingSelectE2EProps) => {
151
159
  const valRef = useRef<any>(null);
152
160
  const listRef = useRef<any>(null);
153
161
 
162
+ // searchable
163
+ const [columnSearchKeywords, setColumnSearchKeywords] = useState<string[]>([]);
164
+
154
165
 
155
166
  // exposes the following methods
156
167
  useImperativeHandle(
@@ -1337,6 +1348,17 @@ const CascadingSelectE2E = (props: CascadingSelectE2EProps) => {
1337
1348
 
1338
1349
  }, [value]);
1339
1350
 
1351
+
1352
+ // Automatically complete and truncate column Search Keywords each time the number of columns changes
1353
+ useEffect(() => {
1354
+ if (listData.current.length !== columnSearchKeywords.length) {
1355
+ setColumnSearchKeywords(
1356
+ Array(listData.current.length).fill('').map((v, i) => columnSearchKeywords[i] || '')
1357
+ );
1358
+ }
1359
+ }, [listData.current.length]);
1360
+
1361
+
1340
1362
  return (
1341
1363
  <>
1342
1364
 
@@ -1372,13 +1394,43 @@ const CascadingSelectE2E = (props: CascadingSelectE2EProps) => {
1372
1394
  {listData.current.map((item: any, level: number) => {
1373
1395
 
1374
1396
  if (item.length > 0) {
1397
+
1398
+ // filter data
1399
+ let filteredItem = item;
1400
+ if (searchable && columnSearchKeywords[level]) {
1401
+ const keyword = columnSearchKeywords[level].toLowerCase();
1402
+ filteredItem = item.filter((opt: any) =>
1403
+ (htmlToPlain(opt.name) || '').toLowerCase().includes(keyword)
1404
+ );
1405
+ }
1406
+
1407
+
1375
1408
  return (
1376
1409
  <li key={level} data-col={level} className="cas-select-e2e__items-col">
1410
+
1411
+
1412
+ {/* SEARCH BOX */}
1413
+ {searchable && (
1414
+ <div className="cas-select-e2e__items-col-searchbox">
1415
+ <input
1416
+ type="text"
1417
+ placeholder={searchPlaceholder}
1418
+ value={columnSearchKeywords[level] || ''}
1419
+ onChange={e => {
1420
+ const newKeywords = [...columnSearchKeywords];
1421
+ newKeywords[level] = e.target.value;
1422
+ setColumnSearchKeywords(newKeywords);
1423
+ }}
1424
+ />
1425
+ </div>
1426
+ )}
1427
+ {/* /SEARCH BOX */}
1428
+
1377
1429
  <Group
1378
1430
  perColumnHeadersShow={perColumnHeadersShow}
1379
1431
  level={level}
1380
1432
  columnTitle={columnTitleData}
1381
- data={item}
1433
+ data={filteredItem} // filter result
1382
1434
  cleanNodeBtnClassName={cleanNodeBtnClassName}
1383
1435
  cleanNodeBtnContent={cleanNodeBtnContent}
1384
1436
  selectEv={(e, value, index) => handleClickItem(e, value, index, level, listData.current)}
@@ -58,7 +58,7 @@ const Checkbox = forwardRef((props: CheckboxProps, externalRef: any) => {
58
58
  const idRes = id || uniqueID;
59
59
  const rootRef = useRef<any>(null);
60
60
  const valRef = useRef<any>(null);
61
- const [val, setVal] = useState<any>(null || false); // Avoid the error "react checkbox changing an uncontrolled input to be controlled"
61
+ const [val, setVal] = useState<boolean>(false); // Avoid the error "react checkbox changing an uncontrolled input to be controlled"
62
62
 
63
63
  // exposes the following methods
64
64
  useImperativeHandle(
@@ -78,7 +78,7 @@ const Checkbox = forwardRef((props: CheckboxProps, externalRef: any) => {
78
78
  onChange(null, false);
79
79
  }
80
80
  },
81
- set: (value: string, cb?: any) => {
81
+ set: (value: boolean, cb?: any) => {
82
82
  setVal(value);
83
83
  cb?.();
84
84
 
@@ -133,7 +133,9 @@ const Checkbox = forwardRef((props: CheckboxProps, externalRef: any) => {
133
133
  useEffect(() => {
134
134
 
135
135
  // default value
136
- setVal(checked);
136
+ if (typeof checked === 'boolean') {
137
+ setVal(checked);
138
+ }
137
139
 
138
140
  // Set a checkbox to indeterminate state
139
141
  if (typeof indeterminate !== 'undefined') {
@@ -144,6 +144,7 @@ const LiveSearch = forwardRef((props: LiveSearchProps, externalRef: any) => {
144
144
  } = props;
145
145
 
146
146
 
147
+ const QUERY_STRING_PLACEHOLDER = '------'; // Invalid parameters for the first automatic request
147
148
  const DEPTH = depth || 1055; // the default value same as bootstrap
148
149
  const POS_OFFSET = 0;
149
150
  const EXCEEDED_SIDE_POS_OFFSET = Number(exceededSidePosOffset) || 15;
@@ -773,7 +774,7 @@ const LiveSearch = forwardRef((props: LiveSearchProps, externalRef: any) => {
773
774
  // data init
774
775
  //--------------
775
776
  const _oparams: any[] = fetchFuncMethodParams || [];
776
- const _params: any[] = _oparams.map((item: any) => item !== '$QUERY_STRING' ? item : (fetchTrigger && !fetchUpdate) ? '' : (fetchUpdate ? '------' : (fetchTrigger ? '------' : '')));
777
+ const _params: any[] = _oparams.map((item: any) => item !== '$QUERY_STRING' ? item : (fetchTrigger && !fetchUpdate) ? '' : (fetchUpdate ? QUERY_STRING_PLACEHOLDER : (fetchTrigger ? QUERY_STRING_PLACEHOLDER : '')));
777
778
  if (!firstFetch) {
778
779
  fetchData((_params).join(','));
779
780
  setFirstFetch(true); // avoid triggering two data requests if the input value has not changed
@@ -17,9 +17,11 @@
17
17
  --cus-sel-removebtn-fill: #000;
18
18
  --cus-sel-removebtn-hover-fill: #f00;
19
19
 
20
+
20
21
  position: relative; /* Required */
21
22
 
22
23
 
24
+
23
25
 
24
26
  /*------ Placeholder for input ------*/
25
27
  input::placeholder {
@@ -33,7 +35,6 @@
33
35
  fill: var(--cus-sel-arrow-fill);
34
36
  }
35
37
  }
36
-
37
38
  /*------ Clean ------*/
38
39
  .clean {
39
40
  svg .clean-fill-g {
@@ -71,6 +72,7 @@
71
72
  .custom-select-multi__control-blinking-cursor {
72
73
  display: inline-block;
73
74
  color: var(--cus-sel-placeholder-color);
75
+ width: 100%;
74
76
 
75
77
  &.animated {
76
78
  animation: 1s mf-sel-blink step-end infinite;
@@ -80,6 +82,15 @@
80
82
  color: var(--cus-sel-input-placeholder-color);
81
83
  }
82
84
 
85
+ /* Text preview */
86
+ > span {
87
+ display: inline-block;
88
+ text-overflow: ellipsis;
89
+ white-space: nowrap;
90
+ overflow: hidden;
91
+ max-width: 100%;
92
+ }
93
+
83
94
 
84
95
  }
85
96
  .custom-select-multi__control-blinking-following-cursor {
@@ -383,7 +394,7 @@
383
394
  --cus-sel-listgroup-content-scrollbar-w: 10px;
384
395
  --cus-sel-listgroup-grouptitle-color: #a2a2a2;
385
396
  --cus-sel-listgroup-groupborder-color: #d8d8d8;
386
-
397
+ --cus-sel-loader-color: #000000;
387
398
 
388
399
 
389
400
 
@@ -391,11 +402,41 @@
391
402
  min-width: var(--cus-sel-listgroup-popwin-min-width);
392
403
  z-index: 1055; /* --bs-modal-zindex */
393
404
 
405
+
394
406
  &.active {
395
407
  display: block !important;
396
408
  }
397
409
 
410
+
411
+
412
+ /*------ Loader ------*/
413
+ .cus-select-loader {
414
+ pointer-events: none;
415
+ z-index: 1;
416
+ width: 12px;
417
+ height: 12px;
418
+ text-align: center;
419
+ transform-origin: center;
420
+ transform: translate(-5px, 0) rotate(0);
421
+ animation: 1s linear infinite cus-select__spinner;
422
+
423
+ svg {
424
+ vertical-align: top;
425
+
426
+ path {
427
+ fill: var(--cus-sel-loader-color);
428
+ }
429
+ }
430
+ }
431
+
432
+ @keyframes cus-select__spinner {
433
+ to {
434
+ transform: translate(-5px, 0) rotate(-360deg);
435
+ }
436
+ }
398
437
 
438
+
439
+ /*------ Options ------*/
399
440
  .custom-select__options-contentlist {
400
441
  overflow: hidden;
401
442
  overflow-y: auto;
@@ -3,7 +3,6 @@ import React, { useEffect, useState, useRef, KeyboardEvent, forwardRef, useImper
3
3
  import {
4
4
  formatIndentVal,
5
5
  unique,
6
- stripHTML,
7
6
  removeItemOnce,
8
7
  optionsCustomSelectFlat,
9
8
  isObject
@@ -46,10 +45,12 @@ import {
46
45
  disableBodyScroll,
47
46
  enableBodyScroll,
48
47
  } from 'funda-utils/dist/cjs/bodyScrollLock';
48
+ import {
49
+ stripTagsAndContent
50
+ } from 'funda-utils/dist/cjs/format-string';
49
51
  import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
50
52
 
51
53
 
52
-
53
54
  export type SelectOptionChangeFnType = (arg1: any, arg2: any, arg3: any) => void;
54
55
 
55
56
  export interface MultiSelectDataConfig {
@@ -120,6 +121,7 @@ export type SelectProps = {
120
121
  placeholder?: string;
121
122
  options?: OptionConfig[] | string;
122
123
  lockBodyScroll?: boolean;
124
+ loader?: React.ReactNode;
123
125
  hierarchical?: boolean;
124
126
  indentation?: string;
125
127
  doubleIndent?: boolean;
@@ -186,6 +188,7 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
186
188
  spellCheck,
187
189
  options,
188
190
  cleanTrigger,
191
+ loader,
189
192
  lockBodyScroll,
190
193
  hierarchical,
191
194
  indentation,
@@ -217,6 +220,7 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
217
220
  } = props;
218
221
 
219
222
 
223
+ const QUERY_STRING_PLACEHOLDER = '------'; // Invalid parameters for the first automatic request
220
224
  const DEPTH = depth || 1055; // the default value same as bootstrap
221
225
  const MANUAL_REQ = typeof fetchTrigger !== 'undefined' && fetchTrigger === true ? true : false; // Manual requests
222
226
  const LIVE_SEARCH_DISABLED = !MANUAL_REQ && typeof window !== 'undefined' && typeof (window as any)['funda-ui__Select-disable-livesearch'] !== 'undefined' ? true : false; // Globally disable real-time search functionality (only valid for non-dynamic requests)
@@ -240,7 +244,7 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
240
244
  const listRef = useRef<any>(null);
241
245
  const listContentRef = useRef<any>(null);
242
246
  const optionsRes = options ? (isJSON(options) ? JSON.parse(options as string) : options) : [];
243
- const LIST_CONTAINER_MAX_HEIGHT = 350;
247
+ const LIST_CONTAINER_MAX_HEIGHT = 300;
244
248
 
245
249
  const keyboardSelectedItem = useRef<any>(null);
246
250
 
@@ -252,12 +256,20 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
252
256
  const [orginalData, setOrginalData] = useState<OptionConfig[]>(staticOptionsData);
253
257
  const [optionsData, setOptionsData] = useState<OptionConfig[]>(staticOptionsData);
254
258
  const [hasErr, setHasErr] = useState<boolean>(false);
259
+
260
+ // Set the final result
255
261
  const [controlLabel, setControlLabel] = useState<string | undefined>('');
256
262
  const [controlValue, setControlValue] = useState<string | undefined>('');
263
+
264
+ //
257
265
  const [controlTempValue, setControlTempValue] = useState<string | null>(null);
258
266
  const [isOpen, setIsOpen] = useState<boolean>(false);
259
267
  const [incomingData, setIncomingData] = useState<string | null | undefined>(null);
260
268
  const [firstRequestExecuted, setFirstRequestExecuted] = useState<boolean>(false);
269
+ const [handleFirstFetchCompleted, setHandleFirstFetchCompleted] = useState<boolean>(false);
270
+
271
+ // filter status
272
+ const [filterItemsHasNoMatchData, setFilterItemsHasNoMatchData] = useState<boolean>(false);
261
273
 
262
274
 
263
275
  // blinking cursor
@@ -458,6 +470,11 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
458
470
 
459
471
  if (fetchUpdate) {
460
472
 
473
+ // update filter status
474
+ setFilterItemsHasNoMatchData(false);
475
+
476
+
477
+ // Make a request
461
478
  handleFetch(val).then((response: any) => {
462
479
  _orginalData = response;
463
480
  const _filterRes = update(_orginalData);
@@ -827,6 +844,8 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
827
844
 
828
845
 
829
846
  function adjustMultiControlContainerHeight(scrollbarInit: boolean = true) {
847
+ if (rootMultiRef.current === null) return;
848
+
830
849
  setTimeout(() => {
831
850
 
832
851
 
@@ -904,7 +923,7 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
904
923
 
905
924
  // You need to wait for the height of the pop-up container to be set
906
925
  // Detect position、
907
- if (window.innerHeight - _triggerBox.top > _contentOldHeight) {
926
+ if (window.innerHeight - _triggerBox.top > 100) {
908
927
  targetPos = 'bottom';
909
928
  } else {
910
929
  targetPos = 'top';
@@ -973,9 +992,6 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
973
992
  }
974
993
 
975
994
 
976
-
977
-
978
-
979
995
  // STEP 4:
980
996
  //-----------
981
997
  // Adjust position
@@ -1067,9 +1083,9 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
1067
1083
 
1068
1084
 
1069
1085
  // nomatch & button of select all
1070
- const _nodataDiv = listContentRef.current.querySelector('.custom-select-multi__control-option-item--nomatch');
1086
+ const _noDataDiv = listContentRef.current.querySelector('.custom-select-multi__control-option-item--nomatch');
1071
1087
  const _btnSelectAll = listContentRef.current.querySelector('.custom-select-multi__control-option-item--select-all');
1072
- _nodataDiv.classList.add('hide');
1088
+ _noDataDiv.classList.add('hide');
1073
1089
  if (_btnSelectAll !== null) _btnSelectAll.classList.remove('hide');
1074
1090
 
1075
1091
 
@@ -1083,6 +1099,7 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
1083
1099
  if (listContentRef.current === null) return;
1084
1100
 
1085
1101
 
1102
+ let invisibleItems: number = 0;
1086
1103
  [].slice.call(listContentRef.current.querySelectorAll('.custom-select-multi__control-option-item')).forEach((node: any) => {
1087
1104
 
1088
1105
  // Avoid fatal errors causing page crashes
@@ -1104,22 +1121,29 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
1104
1121
 
1105
1122
  });
1106
1123
 
1124
+ // Determine if all options are hidden
1125
+ const allHidden = [].slice.call(listContentRef.current.querySelectorAll('.custom-select-multi__control-option-item'))
1126
+ .every((node: any) => node.classList.contains('hide'));
1107
1127
 
1128
+
1108
1129
  // no data label
1109
1130
  popwinNoMatchInit();
1110
1131
 
1111
1132
 
1112
1133
  // display all filtered items
1113
1134
  const _btnSelectAll = listContentRef.current.querySelector('.custom-select-multi__control-option-item--select-all');
1114
- const _nodataDiv = listContentRef.current.querySelector('.custom-select-multi__control-option-item--nomatch');
1135
+ const _noDataDiv = listContentRef.current.querySelector('.custom-select-multi__control-option-item--nomatch');
1115
1136
  if ((val === null ? '' : val).replace(/\s/g, "") === '') {
1116
1137
  [].slice.call(listContentRef.current.querySelectorAll('.custom-select-multi__control-option-item')).forEach((node: any) => {
1117
1138
  node.classList.remove('hide');
1118
1139
  });
1119
- _nodataDiv.classList.add('hide');
1140
+ _noDataDiv.classList.add('hide');
1120
1141
  if (_btnSelectAll !== null) _btnSelectAll.classList.remove('hide');
1121
1142
  }
1122
1143
 
1144
+ // filter status
1145
+ setFilterItemsHasNoMatchData(allHidden);
1146
+
1123
1147
 
1124
1148
  // Appropriate list container height
1125
1149
  popwinContainerHeightAdjust();
@@ -1154,7 +1178,7 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
1154
1178
  if (listContentRef.current === null) return;
1155
1179
 
1156
1180
  const _btnSelectAll = listContentRef.current.querySelector('.custom-select-multi__control-option-item--select-all');
1157
- const _nodataDiv = listContentRef.current.querySelector('.custom-select-multi__control-option-item--nomatch');
1181
+ const _noDataDiv = listContentRef.current.querySelector('.custom-select-multi__control-option-item--nomatch');
1158
1182
  const emptyFieldsCheck = [].slice.call(listContentRef.current.querySelectorAll('.custom-select-multi__control-option-item')).every((node: any) => {
1159
1183
  if (!node.classList.contains('hide')) {
1160
1184
  return false;
@@ -1163,10 +1187,10 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
1163
1187
  });
1164
1188
 
1165
1189
  if (emptyFieldsCheck) {
1166
- _nodataDiv.classList.remove('hide');
1190
+ _noDataDiv.classList.remove('hide');
1167
1191
  if (_btnSelectAll !== null) _btnSelectAll.classList.add('hide');
1168
1192
  } else {
1169
- _nodataDiv.classList.add('hide');
1193
+ _noDataDiv.classList.add('hide');
1170
1194
  if (_btnSelectAll !== null) _btnSelectAll.classList.remove('hide');
1171
1195
  }
1172
1196
 
@@ -1203,6 +1227,9 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
1203
1227
  // update temporary value
1204
1228
  setControlTempValue(null);
1205
1229
 
1230
+ // update filter status
1231
+ setFilterItemsHasNoMatchData(false);
1232
+
1206
1233
 
1207
1234
  // Unlocks the page
1208
1235
  if (LOCK_BODY_SCROLL) enableBodyScroll(document.querySelector('body'));
@@ -1221,8 +1248,14 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
1221
1248
  handleFirstFetch(curValue).then((response: any) => {
1222
1249
  if (response.length > 0) {
1223
1250
  // nomatch
1224
- const _nodataDiv = listContentRef.current.querySelector('.custom-select-multi__control-option-item--nomatch');
1225
- _nodataDiv.classList.add('hide');
1251
+ const _noDataDiv = listContentRef.current.querySelector('.custom-select-multi__control-option-item--nomatch');
1252
+ _noDataDiv.classList.add('hide');
1253
+
1254
+ // After the data is loaded, reinitialize the pop-up window position and height
1255
+ setTimeout(() => {
1256
+ popwinPosInit();
1257
+ }, 0);
1258
+
1226
1259
  }
1227
1260
  });
1228
1261
 
@@ -1748,6 +1781,9 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
1748
1781
  // update temporary value
1749
1782
  setControlTempValue(null);
1750
1783
 
1784
+ // update filter status
1785
+ setFilterItemsHasNoMatchData(false);
1786
+
1751
1787
  }
1752
1788
 
1753
1789
 
@@ -1833,9 +1869,12 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
1833
1869
 
1834
1870
  async function handleFirstFetch(inputVal: any = null) {
1835
1871
  const _oparams: any[] = fetchFuncMethodParams || [];
1836
- const _params: any[] = _oparams.map((item: any) => item !== '$QUERY_STRING' ? item : (MANUAL_REQ ? '------' : ''));
1872
+ const _params: any[] = _oparams.map((item: any) => item !== '$QUERY_STRING' ? item : (MANUAL_REQ ? QUERY_STRING_PLACEHOLDER : ''));
1837
1873
  const res = await fetchData((_params).join(','), finalRes(inputVal), inputVal);
1838
1874
 
1875
+ // Set an identifier indicating that the first request has been completed
1876
+ if (!handleFirstFetchCompleted) setHandleFirstFetchCompleted(true);
1877
+
1839
1878
  return res;
1840
1879
  }
1841
1880
 
@@ -2077,8 +2116,16 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
2077
2116
  handleFirstFetch(value);
2078
2117
  }
2079
2118
 
2119
+ // Forced assignment does not depend on "fetch" or "firstRequestAutoExe"
2120
+ // Don't use "value.value && value.label" directly, if it is empty, it will be treated as FALSE
2121
+ if ( value && typeof value === 'object' ) {
2122
+ if (typeof value.value !== 'undefined' && value.value !== null) setControlValue(value.value as string);
2123
+ if (typeof value.label !== 'undefined' && value.label !== null) setControlLabel(formatIndentVal(value.label, INDENT_LAST_PLACEHOLDER));
2124
+ }
2080
2125
 
2081
2126
 
2127
+ //
2128
+ //--------------
2082
2129
  return () => {
2083
2130
  if (LOCK_BODY_SCROLL) clearAllBodyScrollLocks();
2084
2131
  }
@@ -2122,6 +2169,7 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
2122
2169
  }, [orginalData]); // Avoid the issue that `setOptionsData(orginalData)` sets the original value to empty on the first entry
2123
2170
 
2124
2171
 
2172
+
2125
2173
  return (
2126
2174
  <>
2127
2175
 
@@ -2182,7 +2230,7 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
2182
2230
  disabled={disabled || null}
2183
2231
  required={required || null}
2184
2232
  readOnly={INPUT_READONLY}
2185
- value={controlTempValue || controlTempValue === '' ? controlTempValue : (MULTI_SEL_VALID ? (VALUE_BY_BRACKETS ? convertArrToValByBrackets(formatIndentVal(controlArr.labels, INDENT_LAST_PLACEHOLDER).map((v: any) => stripHTML(v))) : formatIndentVal(controlArr.labels, INDENT_LAST_PLACEHOLDER).map((v: any) => stripHTML(v)).join(',')) : stripHTML(controlLabel as never))} // do not use `defaultValue`
2233
+ value={controlTempValue || controlTempValue === '' ? controlTempValue : (MULTI_SEL_VALID ? (VALUE_BY_BRACKETS ? convertArrToValByBrackets(formatIndentVal(controlArr.labels, INDENT_LAST_PLACEHOLDER).map((v: any) => stripTagsAndContent(v))) : formatIndentVal(controlArr.labels, INDENT_LAST_PLACEHOLDER).map((v: any) => stripTagsAndContent(v)).join(',')) : stripTagsAndContent(controlLabel as never))} // do not use `defaultValue`
2186
2234
 
2187
2235
  style={{
2188
2236
  cursor: 'pointer',
@@ -2261,7 +2309,7 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
2261
2309
  'animated': generateInputFocusStr() === BLINKING_CURSOR_STR
2262
2310
  }
2263
2311
  )}>
2264
- {controlTempValue || controlTempValue === '' ? (controlTempValue.length === 0 ? <span className={`${!hideBlinkingCursor() ? 'control-placeholder' : ''}`}>{generateInputFocusStr()}</span> : <span>{controlTempValue}</span>) : (stripHTML(controlLabel as never).length === 0 ? <span className={`${!hideBlinkingCursor() ? 'control-placeholder' : ''}`}>{generateInputFocusStr()}</span> : <span>{stripHTML(controlLabel as never)}</span>)}
2312
+ {controlTempValue || controlTempValue === '' ? (controlTempValue.length === 0 ? <span className={`${!hideBlinkingCursor() ? 'control-placeholder' : ''}`}>{generateInputFocusStr()}</span> : <span>{controlTempValue}</span>) : (stripTagsAndContent(controlLabel as never).length === 0 ? <span className={`${!hideBlinkingCursor() ? 'control-placeholder' : ''}`}>{generateInputFocusStr()}</span> : <span>{stripTagsAndContent(controlLabel as never)}</span>)}
2265
2313
  </span>
2266
2314
 
2267
2315
  </div>
@@ -2458,9 +2506,9 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
2458
2506
  key={'selected-item-' + index}
2459
2507
  data-value={controlArr.values[index]}
2460
2508
  data-label-full={item}
2461
- data-label-text={formatIndentVal(stripHTML(item), INDENT_LAST_PLACEHOLDER)}
2509
+ data-label-text={formatIndentVal(stripTagsAndContent(item), INDENT_LAST_PLACEHOLDER)}
2462
2510
  >
2463
- {formatIndentVal(stripHTML(item), INDENT_LAST_PLACEHOLDER)}
2511
+ {formatIndentVal(stripTagsAndContent(item), INDENT_LAST_PLACEHOLDER)}
2464
2512
 
2465
2513
  <a
2466
2514
  href="#"
@@ -2656,9 +2704,18 @@ const Select = forwardRef((props: SelectProps, externalRef: any) => {
2656
2704
  {/* /CLEAN BUTTON (Only Single selection) */}
2657
2705
 
2658
2706
 
2659
- {/* NO MATCH */}
2660
- <button tabIndex={-1} type="button" className="list-group-item list-group-item-action no-match border-0 custom-select-multi__control-option-item--nomatch hide" disabled>{fetchNoneInfo || 'No match yet'}</button>
2661
- {/* /NO MATCH */}
2707
+ {/* NO MATCH & LOADER */}
2708
+ <button tabIndex={-1} type="button" className="list-group-item list-group-item-action no-match border-0 custom-select-multi__control-option-item--nomatch hide" disabled>
2709
+ {
2710
+ // (1) Handling async data with the click event
2711
+ (!FIRST_REQUEST_AUTO && !handleFirstFetchCompleted) ||
2712
+
2713
+ // (2) Every time the input changes or the search button is clicked, a data request will be triggered
2714
+ (fetchUpdate && !filterItemsHasNoMatchData && controlTempValue !== '')
2715
+ ? <><div className="cus-select-loader">{loader || <svg height="12px" width="12px" viewBox="0 0 512 512"><g><path fill="inherit" d="M256,0c-23.357,0-42.297,18.932-42.297,42.288c0,23.358,18.94,42.288,42.297,42.288c23.357,0,42.279-18.93,42.279-42.288C298.279,18.932,279.357,0,256,0z" /><path fill="inherit" d="M256,427.424c-23.357,0-42.297,18.931-42.297,42.288C213.703,493.07,232.643,512,256,512c23.357,0,42.279-18.93,42.279-42.288C298.279,446.355,279.357,427.424,256,427.424z" /><path fill="inherit" d="M74.974,74.983c-16.52,16.511-16.52,43.286,0,59.806c16.52,16.52,43.287,16.52,59.806,0c16.52-16.511,16.52-43.286,0-59.806C118.261,58.463,91.494,58.463,74.974,74.983z" /><path fill="inherit" d="M377.203,377.211c-16.503,16.52-16.503,43.296,0,59.815c16.519,16.52,43.304,16.52,59.806,0c16.52-16.51,16.52-43.295,0-59.815C420.489,360.692,393.722,360.7,377.203,377.211z" /><path fill="inherit" d="M84.567,256c0.018-23.348-18.922-42.279-42.279-42.279c-23.357-0.009-42.297,18.932-42.279,42.288c-0.018,23.348,18.904,42.279,42.279,42.279C65.645,298.288,84.567,279.358,84.567,256z" /><path fill="inherit" d="M469.712,213.712c-23.357,0-42.279,18.941-42.297,42.288c0,23.358,18.94,42.288,42.297,42.297c23.357,0,42.297-18.94,42.279-42.297C512.009,232.652,493.069,213.712,469.712,213.712z" /><path fill="inherit" d="M74.991,377.22c-16.519,16.511-16.519,43.296,0,59.806c16.503,16.52,43.27,16.52,59.789,0c16.52-16.519,16.52-43.295,0-59.815C118.278,360.692,91.511,360.692,74.991,377.22z" /><path fill="inherit" d="M437.026,134.798c16.52-16.52,16.52-43.304,0-59.824c-16.519-16.511-43.304-16.52-59.823,0c-16.52,16.52-16.503,43.295,0,59.815C393.722,151.309,420.507,151.309,437.026,134.798z" /></g></svg>}</div></> : <>{fetchNoneInfo || 'No match yet'}</>}
2716
+ </button>
2717
+ {/* /NO MATCH & LOADER */}
2718
+
2662
2719
 
2663
2720
 
2664
2721
  {/* OPTIONS LIST */}
@@ -35,16 +35,6 @@ export function unique(arr: any[]) {
35
35
  }
36
36
 
37
37
 
38
- /**
39
- * Remove html tag content
40
- * @param {string | number} str
41
- * @returns {string}
42
- */
43
- export function stripHTML(str: string | number) {
44
- return String(str).replace(/<\/?[^>]+(>|$)(.*?)<\/?[^>]+(>|$)/ig, '');
45
- }
46
-
47
-
48
38
  /**
49
39
  * Remove a specific item from an array
50
40
  * @param {array} arr
@@ -52,7 +52,7 @@ const Switch = forwardRef((props: SwitchProps, externalRef: any) => {
52
52
  const uniqueID = useComId();
53
53
  const idRes = id || uniqueID;
54
54
  const rootRef = useRef<any>(null);
55
- const [val, setVal] = useState<any>(null || false); // Avoid the error "react checkbox changing an uncontrolled input to be controlled"
55
+ const [val, setVal] = useState<boolean>(false); // Avoid the error "react checkbox changing an uncontrolled input to be controlled"
56
56
 
57
57
  function handleFocus(event: any) {
58
58
  rootRef.current?.classList.add('focus');
@@ -93,7 +93,9 @@ const Switch = forwardRef((props: SwitchProps, externalRef: any) => {
93
93
 
94
94
 
95
95
  useEffect(() => {
96
- setVal(checked);
96
+ if (typeof checked === 'boolean') {
97
+ setVal(checked);
98
+ }
97
99
  }, [checked]);
98
100
 
99
101
 
@@ -23,23 +23,20 @@ const App = () => {
23
23
  */
24
24
  import { useEffect, useState } from 'react';
25
25
 
26
-
27
- const useIsMobile = (breakpoint = 600) => {
28
- const [isMobile, setIsMobile] = useState(false);
29
- const [isMounted, setIsMounted] = useState(false);
30
-
26
+ const useIsMobile = (breakpoint: number = 600): boolean => {
27
+ const [isMobile, setIsMobile] = useState<boolean>(false);
28
+ const [isMounted, setIsMounted] = useState<boolean>(false);
31
29
 
32
30
  useEffect(() => {
33
31
  // Set the mounted state to true after the component has mounted
34
32
  setIsMounted(true);
35
33
 
36
34
  const handleResize = () => {
37
- if (window) {
38
-
35
+ if (typeof window !== 'undefined') {
39
36
 
40
- const detectDeviceType = () => {
37
+ const detectDeviceType = (): 'mobile' | 'tablet' | 'desktop' => {
41
38
  // 1. First check if window and navigator are available (SSR compatibility)
42
- if (typeof window === 'undefined' || !navigator) {
39
+ if (typeof window === 'undefined' || typeof navigator === 'undefined') {
43
40
  return 'desktop'; // Default to desktop
44
41
  }
45
42
 
@@ -47,10 +44,10 @@ const useIsMobile = (breakpoint = 600) => {
47
44
  const ua = navigator.userAgent.toLowerCase();
48
45
 
49
46
  // 3. Get platform info
50
- const platform = navigator.platform.toLowerCase();
47
+ const platform = navigator.platform ? navigator.platform.toLowerCase() : '';
51
48
 
52
49
  // 4. Check screen characteristics using window.matchMedia
53
- const isTouch = ('ontouchstart' in window) || navigator.maxTouchPoints > 0;
50
+ const isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
54
51
 
55
52
  const isPortrait = window.matchMedia('(orientation: portrait)').matches;
56
53
  const isLandscape = window.matchMedia('(orientation: landscape)').matches;
@@ -139,4 +136,4 @@ const useIsMobile = (breakpoint = 600) => {
139
136
  return isMounted ? isMobile : false;
140
137
  };
141
138
 
142
- export default useIsMobile;
139
+ export default useIsMobile;