funda-ui 4.7.133 → 4.7.152

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 (57) 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/Chatbox/index.d.ts +7 -0
  8. package/Chatbox/index.js +247 -163
  9. package/Checkbox/index.js +4 -2
  10. package/LiveSearch/index.js +2 -1
  11. package/Refresher/index.js +3 -3
  12. package/Select/index.css +33 -0
  13. package/Select/index.d.ts +1 -0
  14. package/Select/index.js +350 -39
  15. package/SplitterPanel/index.js +3 -3
  16. package/Switch/index.js +4 -2
  17. package/Utils/format-string.d.ts +2 -1
  18. package/Utils/format-string.js +22 -12
  19. package/Utils/time.d.ts +3 -3
  20. package/Utils/useIsMobile.js +3 -3
  21. package/lib/cjs/CascadingSelect/index.d.ts +2 -0
  22. package/lib/cjs/CascadingSelect/index.js +294 -22
  23. package/lib/cjs/CascadingSelectE2E/index.d.ts +2 -0
  24. package/lib/cjs/CascadingSelectE2E/index.js +300 -28
  25. package/lib/cjs/Chatbox/index.d.ts +7 -0
  26. package/lib/cjs/Chatbox/index.js +247 -163
  27. package/lib/cjs/Checkbox/index.js +4 -2
  28. package/lib/cjs/LiveSearch/index.js +2 -1
  29. package/lib/cjs/Refresher/index.js +3 -3
  30. package/lib/cjs/Select/index.d.ts +1 -0
  31. package/lib/cjs/Select/index.js +350 -39
  32. package/lib/cjs/SplitterPanel/index.js +3 -3
  33. package/lib/cjs/Switch/index.js +4 -2
  34. package/lib/cjs/Utils/format-string.d.ts +2 -1
  35. package/lib/cjs/Utils/format-string.js +22 -12
  36. package/lib/cjs/Utils/time.d.ts +3 -3
  37. package/lib/cjs/Utils/useIsMobile.js +3 -3
  38. package/lib/css/CascadingSelect/index.css +15 -4
  39. package/lib/css/CascadingSelectE2E/index.css +15 -4
  40. package/lib/css/Select/index.css +33 -0
  41. package/lib/esm/CascadingSelect/index.scss +22 -7
  42. package/lib/esm/CascadingSelect/index.tsx +49 -1
  43. package/lib/esm/CascadingSelectE2E/Group.tsx +1 -0
  44. package/lib/esm/CascadingSelectE2E/index.scss +23 -6
  45. package/lib/esm/CascadingSelectE2E/index.tsx +53 -1
  46. package/lib/esm/Chatbox/index.tsx +96 -11
  47. package/lib/esm/Checkbox/index.tsx +5 -3
  48. package/lib/esm/LiveSearch/index.tsx +2 -1
  49. package/lib/esm/Select/index.scss +43 -2
  50. package/lib/esm/Select/index.tsx +81 -24
  51. package/lib/esm/Select/utils/func.ts +0 -10
  52. package/lib/esm/Switch/index.tsx +4 -2
  53. package/lib/esm/Textarea/index.tsx +0 -1
  54. package/lib/esm/Utils/hooks/useIsMobile.tsx +9 -12
  55. package/lib/esm/Utils/libs/format-string.ts +22 -12
  56. package/lib/esm/Utils/libs/time.ts +6 -6
  57. 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)}
@@ -13,7 +13,6 @@ import { htmlEncode } from 'funda-utils/dist/cjs/sanitize';
13
13
 
14
14
 
15
15
 
16
-
17
16
  // loader
18
17
  import PureLoader from './PureLoader';
19
18
  import TypingEffect from "./TypingEffect";
@@ -46,16 +45,24 @@ export type QuestionData = {
46
45
  list: Array<string>;
47
46
  };
48
47
 
48
+ export type SelectedOption = {
49
+ [key: string]: string | number;
50
+ curIndex: number;
51
+ curValue: string;
52
+ };
49
53
 
50
54
  export interface FloatingButton {
51
55
  label: string; // HTML string
52
56
  value: string;
53
57
  onClick: string;
58
+ active?: boolean; // Specify if the button should be active by default
54
59
  isSelect?: boolean; // Mark whether it is a drop-down selection button
55
60
  dynamicOptions?: boolean; // Mark whether to use dynamic options
61
+ defaultSelected?: number; // Specify default selected option index
56
62
  [key: string]: any; // Allows dynamic `onSelect__<number>` attributes, such as `onSelect__1`, `onSelect__2`, ...
57
63
  }
58
64
 
65
+
59
66
  export interface FloatingButtonSelectOption {
60
67
  label: string;
61
68
  value: string;
@@ -178,6 +185,11 @@ const Chatbox = (props: ChatboxProps) => {
178
185
  const [enableStreamMode, setEnableStreamMode] = useState<boolean>(true);
179
186
  const animatedMessagesRef = useRef<Set<number>>(new Set()); // Add a ref to keep track of messages that have already been animated
180
187
 
188
+ // Keep track of whether the default values have been initialized
189
+ const [initializedDefaults, setInitializedDefaults] = useState<Record<string, boolean>>({});
190
+
191
+
192
+
181
193
  //
182
194
  const timer = useRef<any>(null);
183
195
 
@@ -508,6 +520,19 @@ const Chatbox = (props: ChatboxProps) => {
508
520
  return newState;
509
521
  });
510
522
  };
523
+
524
+ // The onClick action specifically used to perform the default options
525
+ const executeDefaultOptionAction = async (actionStr: string, buttonId: string) => {
526
+ try {
527
+ const actionFn = new Function('method', 'isActive', 'button', actionStr);
528
+ // To perform the action, pass false as the "isActive" parameter, as this is the default option
529
+ await actionFn(exposedMethods(), false, document.getElementById(buttonId));
530
+ } catch (error) {
531
+ console.error('Error executing default option action:', error);
532
+ }
533
+ };
534
+
535
+
511
536
  const executeButtonAction = async (actionStr: string, buttonId: string, buttonElement: HTMLButtonElement) => {
512
537
  try {
513
538
  const actionFn = new Function('method', 'isActive', 'button', actionStr);
@@ -547,7 +572,11 @@ const Chatbox = (props: ChatboxProps) => {
547
572
 
548
573
 
549
574
  // options
550
- const [selectedOpt, setSelectedOpt] = useState<Record<string, string | number>>({});
575
+ const [selectedOpt, setSelectedOpt] = useState<SelectedOption>({
576
+ curIndex: -1,
577
+ curValue: ''
578
+ });
579
+
551
580
  // Store dynamic options
552
581
  const [dynamicOptions, setDynamicOptions] = useState<Record<string, FloatingButtonSelectOption[]>>({});
553
582
 
@@ -575,7 +604,7 @@ const Chatbox = (props: ChatboxProps) => {
575
604
  return options;
576
605
  };
577
606
 
578
- const handleExecuteButtonSelect = (buttonId: string, option: FloatingButtonSelectOption, index: number, value: string) => {
607
+ const handleExecuteButtonSelect = (buttonId: string, option: FloatingButtonSelectOption, index: number, value: string, isDefaultSelection: boolean = false) => {
579
608
 
580
609
  if (option.value === "cancel") {
581
610
  setSelectedOpt(prev => {
@@ -597,11 +626,15 @@ const Chatbox = (props: ChatboxProps) => {
597
626
  }));
598
627
  }
599
628
 
629
+
630
+ // The button action is performed and the drop-down menu is closed only when it is not the default selection
631
+ if (!isDefaultSelection) {
632
+ executeButtonAction(option.onClick, buttonId, document.getElementById(buttonId) as HTMLButtonElement);
633
+
634
+ // Close the drop-down
635
+ closeDropdowns();
636
+ }
600
637
 
601
- executeButtonAction(option.onClick, buttonId, document.getElementById(buttonId) as HTMLButtonElement);
602
-
603
- // Close the drop-down
604
- closeDropdowns();
605
638
  };
606
639
 
607
640
  // click outside
@@ -1054,10 +1087,11 @@ const Chatbox = (props: ChatboxProps) => {
1054
1087
  customMethodsRef.current,
1055
1088
  conversationHistory.current
1056
1089
  );
1057
-
1058
1090
  const { content, isStream } = customResponse;
1059
1091
  let contentRes: any = content;
1060
1092
 
1093
+
1094
+
1061
1095
  // Update stream mode
1062
1096
  setEnableStreamMode(isStream);
1063
1097
 
@@ -1164,6 +1198,7 @@ const Chatbox = (props: ChatboxProps) => {
1164
1198
  // hide loader
1165
1199
  setLoaderDisplay(false);
1166
1200
 
1201
+
1167
1202
  let result: any = jsonResponse;
1168
1203
  if (extractPath) {
1169
1204
  for (const path of extractPath) {
@@ -1176,6 +1211,7 @@ const Chatbox = (props: ChatboxProps) => {
1176
1211
  // Replace with a valid label
1177
1212
  content = fixHtmlTags(content, args().withReasoning, args().reasoningSwitchLabel);
1178
1213
 
1214
+
1179
1215
  return {
1180
1216
  reply: formatLatestDisplayContent(content),
1181
1217
  useStreamRender: false
@@ -1244,6 +1280,53 @@ const Chatbox = (props: ChatboxProps) => {
1244
1280
  (window as any).chatboxCopyToClipboard = chatboxCopyToClipboard;
1245
1281
  }, []);
1246
1282
 
1283
+
1284
+
1285
+ // Initialize the default value of toolkit buttons
1286
+ useEffect(() => {
1287
+ if (args().toolkitButtons) {
1288
+ args().toolkitButtons.forEach((btn: FloatingButton, index: number) => {
1289
+ const _id = `${args().prefix || 'custom-'}chatbox-btn-tools-${chatId}${index}`;
1290
+
1291
+ if (btn.isSelect) {
1292
+
1293
+ if (!initializedDefaults[_id] && typeof btn.defaultSelected === 'number') {
1294
+ const options = getButtonOptions(btn, _id);
1295
+
1296
+ // If there is a default selected item, initialize the selected state
1297
+ if (btn.defaultSelected >= 0 && btn.defaultSelected < options.length) {
1298
+ const defaultOption = options[btn.defaultSelected];
1299
+ if (defaultOption) {
1300
+ // Update the selected status
1301
+ // console.log('--> defaultOption: ', defaultOption);
1302
+
1303
+ // Pass the "isDefaultSelection" parameter as true
1304
+ handleExecuteButtonSelect(_id, defaultOption, btn.defaultSelected, defaultOption.value, true);
1305
+
1306
+ // Perform the onClick action alone
1307
+ executeDefaultOptionAction(defaultOption.onClick, _id);
1308
+
1309
+
1310
+ // Mark this button with the default value initialized
1311
+ setInitializedDefaults(prev => ({
1312
+ ...prev,
1313
+ [_id]: true
1314
+ }));
1315
+
1316
+ }
1317
+ }
1318
+ }
1319
+ } else if (btn.active) {
1320
+ // For non-select buttons, if defaultActive is true, execute the onClick action
1321
+ executeButtonAction(btn.onClick, _id, document.getElementById(_id) as HTMLButtonElement);
1322
+ }
1323
+
1324
+
1325
+ })
1326
+ }
1327
+ }, [chatId, args().toolkitButtons]); // It is only executed when the component is first rendered and when toolkitButtons changes
1328
+
1329
+
1247
1330
  return (
1248
1331
  <>
1249
1332
 
@@ -1570,12 +1653,13 @@ const Chatbox = (props: ChatboxProps) => {
1570
1653
 
1571
1654
  if (btn.isSelect) {
1572
1655
  const options = getButtonOptions(btn, _id);
1573
-
1656
+
1574
1657
  return (
1575
1658
  <div key={index} className="toolkit-select-wrapper">
1576
1659
  <button
1577
1660
  id={_id}
1578
- className={`toolkit-select-btn ${btn.value || ''} ${isActive ? 'active' : ''} ${selectedOpt.curValue !== 'cancel' && typeof selectedOpt.curValue !== 'undefined' && selectedOpt.curValue !== '' ? 'opt-active' : ''}`}
1661
+ data-value={btn.value || ''}
1662
+ className={`toolkit-select-btn ${isActive ? 'active' : ''} ${selectedOpt.curValue !== 'cancel' && typeof selectedOpt.curValue !== 'undefined' && selectedOpt.curValue !== '' ? 'opt-active' : ''}`}
1579
1663
  onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
1580
1664
  e.preventDefault();
1581
1665
  setActiveButtons(prev => ({
@@ -1610,7 +1694,8 @@ const Chatbox = (props: ChatboxProps) => {
1610
1694
  {options.map((option: FloatingButtonSelectOption, optIndex: number) => (
1611
1695
  <div
1612
1696
  key={optIndex}
1613
- className={`toolkit-select-option ${option.value || ''} ${selectedOpt.curIndex === optIndex ? 'selected' : ''}`}
1697
+ data-value={option.value || ''}
1698
+ className={`toolkit-select-option ${selectedOpt.curIndex === optIndex ? 'selected' : ''}`}
1614
1699
  onClick={() => handleExecuteButtonSelect(_id, option, optIndex, option.value)}
1615
1700
  >
1616
1701
  <span dangerouslySetInnerHTML={{ __html: option.label }}></span>
@@ -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;