funda-ui 4.7.222 → 4.7.252

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.
@@ -29,7 +29,6 @@ import { isNumeric } from 'funda-utils/dist/cjs/math';
29
29
  import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
30
30
 
31
31
 
32
-
33
32
  import Calendar from './Calendar';
34
33
 
35
34
 
@@ -644,6 +643,11 @@ const Date = forwardRef((props: DateProps, externalRef: any) => {
644
643
 
645
644
  resetDefauleValueExist();
646
645
 
646
+ // Automatically pop up a pop-up window
647
+ if (enableEntireAreaPopup) {
648
+ handleShow();
649
+ }
650
+
647
651
  // If there is no valid default value in the input field,
648
652
  // onChange should be triggered only after the resetDefauleValueExist() function is processed
649
653
  if (!dateDefaultValueExist) {
@@ -1093,12 +1097,9 @@ const Date = forwardRef((props: DateProps, externalRef: any) => {
1093
1097
  if (_val !== '' && !isValidYear(_val) && isNumeric(_val) && Number(_val) > 9999) _val = '9999';
1094
1098
  if (_val !== '' && !isValidYear(_val) && !isNumeric(_val)) _val = `${getCurrentYear()}`;
1095
1099
 
1096
-
1097
1100
  const _date = `${_val}-${splitVals[1]}-${splitVals[2]}`;
1098
1101
  const _full = `${_date} ${splitVals[3]}:${splitVals[4]}:${splitVals[5]}`;
1099
1102
 
1100
-
1101
-
1102
1103
  onChange?.(inputRef.current, valueResConverter(_full), isValidDate(_full), getAllSplittingInputs());
1103
1104
  setSplitVals((prevState: string[]) => {
1104
1105
  return [_val, prevState[1], prevState[2], prevState[3], prevState[4], prevState[5]];
@@ -1109,7 +1110,14 @@ const Date = forwardRef((props: DateProps, externalRef: any) => {
1109
1110
  setChangedVal(_full);
1110
1111
  setTimeVal([splitVals[3], splitVals[4], splitVals[5]]);
1111
1112
 
1112
-
1113
+ // Auto focus to next input if year is 4 digits
1114
+ if (_val.length === 4) {
1115
+ const nextInput = splitInputs.current.get(splitInputsIds[1]);
1116
+ if (nextInput) {
1117
+ (nextInput as HTMLInputElement).focus();
1118
+ (nextInput as HTMLInputElement).select();
1119
+ }
1120
+ }
1113
1121
  }}
1114
1122
  {...attributes}
1115
1123
  />
@@ -1142,13 +1150,9 @@ const Date = forwardRef((props: DateProps, externalRef: any) => {
1142
1150
  }
1143
1151
  if (_val !== '' && !isValidMonth(_val) && !isNumeric(_val)) _val = `${getCurrentMonth()}`;
1144
1152
 
1145
-
1146
-
1147
1153
  const _date = `${splitVals[0]}-${_val}-${splitVals[2]}`;
1148
1154
  const _full = `${_date} ${splitVals[3]}:${splitVals[4]}:${splitVals[5]}`;
1149
1155
 
1150
-
1151
-
1152
1156
  onChange?.(inputRef.current, valueResConverter(_full), isValidDate(_full), getAllSplittingInputs());
1153
1157
  setSplitVals((prevState: string[]) => {
1154
1158
  return [prevState[0], _val, prevState[2], prevState[3], prevState[4], prevState[5]];
@@ -1158,7 +1162,15 @@ const Date = forwardRef((props: DateProps, externalRef: any) => {
1158
1162
  setDateVal(_date);
1159
1163
  setChangedVal(_full);
1160
1164
  setTimeVal([splitVals[3], splitVals[4], splitVals[5]]);
1161
-
1165
+
1166
+ // Auto focus to next input if month is 2 digits
1167
+ if (_val.length === 2) {
1168
+ const nextInput = splitInputs.current.get(splitInputsIds[2]);
1169
+ if (nextInput) {
1170
+ (nextInput as HTMLInputElement).focus();
1171
+ (nextInput as HTMLInputElement).select();
1172
+ }
1173
+ }
1162
1174
  }}
1163
1175
  {...attributes}
1164
1176
  />
@@ -1199,13 +1211,9 @@ const Date = forwardRef((props: DateProps, externalRef: any) => {
1199
1211
  }
1200
1212
  if (_val !== '' && !isValidDay(_val) && !isNumeric(_val)) _val = `${getCurrentDay()}`;
1201
1213
 
1202
-
1203
1214
  const _date = `${splitVals[0]}-${splitVals[1]}-${_val}`;
1204
1215
  const _full = `${_date} ${splitVals[3]}:${splitVals[4]}:${splitVals[5]}`;
1205
1216
 
1206
-
1207
-
1208
-
1209
1217
  onChange?.(inputRef.current, valueResConverter(_full), isValidDate(_full), getAllSplittingInputs());
1210
1218
  setSplitVals((prevState: string[]) => {
1211
1219
  return [prevState[0], prevState[1], _val, prevState[3], prevState[4], prevState[5]];
@@ -1216,7 +1224,14 @@ const Date = forwardRef((props: DateProps, externalRef: any) => {
1216
1224
  setChangedVal(_full);
1217
1225
  setTimeVal([splitVals[3], splitVals[4], splitVals[5]]);
1218
1226
 
1219
-
1227
+ // Auto focus to next input if day is 2 digits
1228
+ if (_val.length === 2) {
1229
+ const nextInput = splitInputs.current.get(splitInputsIds[3]);
1230
+ if (nextInput) {
1231
+ (nextInput as HTMLInputElement).focus();
1232
+ (nextInput as HTMLInputElement).select();
1233
+ }
1234
+ }
1220
1235
  }}
1221
1236
  {...attributes}
1222
1237
  />
@@ -1266,7 +1281,14 @@ const Date = forwardRef((props: DateProps, externalRef: any) => {
1266
1281
  setChangedVal(_full);
1267
1282
  setTimeVal([_val, splitVals[4], splitVals[5]]);
1268
1283
 
1269
-
1284
+ // Auto focus to next input if hour is 2 digits
1285
+ if (_val.length === 2) {
1286
+ const nextInput = splitInputs.current.get(splitInputsIds[4]);
1287
+ if (nextInput) {
1288
+ (nextInput as HTMLInputElement).focus();
1289
+ (nextInput as HTMLInputElement).select();
1290
+ }
1291
+ }
1270
1292
  }}
1271
1293
  {...attributes}
1272
1294
  />
@@ -1314,7 +1336,14 @@ const Date = forwardRef((props: DateProps, externalRef: any) => {
1314
1336
  setChangedVal(_full);
1315
1337
  setTimeVal([splitVals[3], _val, splitVals[5]]);
1316
1338
 
1317
-
1339
+ // Auto focus to next input if minute is 2 digits
1340
+ if (_val.length === 2) {
1341
+ const nextInput = splitInputs.current.get(splitInputsIds[5]);
1342
+ if (nextInput) {
1343
+ (nextInput as HTMLInputElement).focus();
1344
+ (nextInput as HTMLInputElement).select();
1345
+ }
1346
+ }
1318
1347
  }}
1319
1348
  {...attributes}
1320
1349
  />
@@ -1364,7 +1393,7 @@ const Date = forwardRef((props: DateProps, externalRef: any) => {
1364
1393
  setDateVal(_date);
1365
1394
  setChangedVal(_full);
1366
1395
  setTimeVal([splitVals[3], splitVals[4], _val]);
1367
-
1396
+ // No auto focus for the last input (seconds)
1368
1397
  }}
1369
1398
  {...attributes}
1370
1399
  />
@@ -7,6 +7,7 @@ import { actualPropertyValue, getTextTop } from 'funda-utils/dist/cjs/inputsCalc
7
7
  import useDebounce from 'funda-utils/dist/cjs/useDebounce';
8
8
 
9
9
 
10
+
10
11
  export type InputProps = {
11
12
  contentRef?: React.ForwardedRef<any>;
12
13
  wrapperClassName?: string;
@@ -13,6 +13,7 @@ import {
13
13
  } from 'funda-utils/dist/cjs/bodyScrollLock';
14
14
 
15
15
 
16
+
16
17
  declare global {
17
18
  interface Window {
18
19
  curVideo?: any;
@@ -90,10 +91,14 @@ export type ModalDialogProps = {
90
91
  /** This function is called whenever the data is updated.
91
92
  * Exposes the JSON format data about the option as an argument.
92
93
  */
93
- onLoad?: (openFunc: any, closeFunc: any) => void;
94
- onOpen?: (e: any, callback: any) => void;
95
- onClose?: (e: any) => void;
96
- onSubmit?: (e: any, callback: any, incomingData: string | null | undefined) => void;
94
+ onLoad?: (openFunc: () => void, closeFunc: () => void) => void;
95
+ onOpen?: (e: React.MouseEvent<HTMLElement> | null, callback: () => void) => void;
96
+ onClose?: (e: React.MouseEvent<HTMLElement> | null) => void;
97
+ onSubmit?: (e: React.MouseEvent<HTMLButtonElement>, callback: () => void, incomingData: string | null | undefined) => void;
98
+ /**
99
+ * Called when Enter key is pressed while modal is open
100
+ */
101
+ onPressEnter?: (callback: () => void) => void;
97
102
  };
98
103
 
99
104
  const ModalDialog = forwardRef((props: ModalDialogProps, externalRef: React.ForwardedRef<ModalDialogRef>) => {
@@ -132,7 +137,8 @@ const ModalDialog = forwardRef((props: ModalDialogProps, externalRef: React.Forw
132
137
  onClose,
133
138
  onSubmit,
134
139
  id,
135
- children
140
+ children,
141
+ onPressEnter
136
142
  } = props;
137
143
 
138
144
 
@@ -186,7 +192,7 @@ const ModalDialog = forwardRef((props: ModalDialogProps, externalRef: React.Forw
186
192
 
187
193
 
188
194
  //
189
- function handleCloseWin(e: any) {
195
+ function handleCloseWin(e: React.MouseEvent<HTMLElement> | null) {
190
196
  if (typeof e !== 'undefined' && e !== null) {
191
197
  e.preventDefault();
192
198
 
@@ -206,7 +212,7 @@ const ModalDialog = forwardRef((props: ModalDialogProps, externalRef: React.Forw
206
212
  }
207
213
 
208
214
 
209
- function handleOpenWin(e: any) {
215
+ function handleOpenWin(e: React.MouseEvent<HTMLElement> | null) {
210
216
  if (typeof e !== 'undefined' && e !== null) {
211
217
  e.preventDefault();
212
218
 
@@ -217,12 +223,10 @@ const ModalDialog = forwardRef((props: ModalDialogProps, externalRef: React.Forw
217
223
  openAction();
218
224
 
219
225
  //
220
- const callback = (e: any) => {
221
- return () => {
222
- handleCloseWin(e);
223
- }
226
+ const callback = () => {
227
+ handleCloseWin(e);
224
228
  };
225
- onOpen?.(e, callback(e));
229
+ onOpen?.(e, callback);
226
230
  }
227
231
 
228
232
  function closeAction() {
@@ -428,6 +432,23 @@ const ModalDialog = forwardRef((props: ModalDialogProps, externalRef: React.Forw
428
432
 
429
433
  }, [show, data, modalRef.current]); // When show`` defaults to true, `modalRef.current` will be null
430
434
 
435
+ // 监听回车键
436
+ useEffect(() => {
437
+ if (!modalShow || !onPressEnter) return;
438
+ const handleKeyDown = (e: KeyboardEvent) => {
439
+ if (e.key === 'Enter' || e.key === 'NumpadEnter') {
440
+ const callback = () => {
441
+ handleCloseWin(null);
442
+ };
443
+ onPressEnter?.(callback);
444
+ }
445
+ };
446
+ window.addEventListener('keydown', handleKeyDown);
447
+ return () => {
448
+ window.removeEventListener('keydown', handleKeyDown);
449
+ };
450
+ }, [modalShow, onPressEnter]);
451
+
431
452
  return (
432
453
  <>
433
454
  {triggerContent ? <>
@@ -468,7 +489,7 @@ const ModalDialog = forwardRef((props: ModalDialogProps, externalRef: React.Forw
468
489
  }}
469
490
  >
470
491
  <h5 className={`modal-title ${modalTitleClassName || ''}`}>{heading || ''}</h5>
471
- {!closeDisabled ? <button type="button" className={enableVideo ? 'btn-close btn-close-white' : 'btn-close'} data-close="1" onClick={handleCloseWin}></button> : null}
492
+ {!closeDisabled ? <button type="button" className={enableVideo ? 'btn-close btn-close-white' : 'btn-close'} data-close="1" onClick={(e) => handleCloseWin(e)}></button> : null}
472
493
 
473
494
  </div>
474
495
  </>}
@@ -500,15 +521,13 @@ const ModalDialog = forwardRef((props: ModalDialogProps, externalRef: React.Forw
500
521
  {closeBtnLabel || submitBtnLabel ? <>
501
522
  <div className={`modal-footer ${modalFooterClassName || ''}`}>
502
523
 
503
- {!closeDisabled ? <>{closeBtnLabel ? <button data-close="1" onClick={handleCloseWin} type="button" className={closeBtnClassName ? closeBtnClassName : 'btn btn-secondary'}>{closeBtnLabel}</button> : null}</> : null}
524
+ {!closeDisabled ? <>{closeBtnLabel ? <button data-close="1" onClick={(e) => handleCloseWin(e)} type="button" className={closeBtnClassName ? closeBtnClassName : 'btn btn-secondary'}>{closeBtnLabel}</button> : null}</> : null}
504
525
 
505
- {submitBtnLabel ? <button data-confirm="1" data-incoming-data={`${incomingData}`} onClick={(e: any) => {
506
- const callback = (e: any) => {
507
- return () => {
508
- handleCloseWin(e);
509
- }
526
+ {submitBtnLabel ? <button data-confirm="1" data-incoming-data={`${incomingData}`} onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
527
+ const callback = () => {
528
+ handleCloseWin(e);
510
529
  };
511
- onSubmit?.(e, callback(e), incomingData);
530
+ onSubmit?.(e, callback, incomingData);
512
531
  }} type="button" className={submitBtnClassName ? submitBtnClassName : 'btn btn-primary'}>{submitBtnLabel}</button> : null}
513
532
  </div>
514
533
  </> : null}
@@ -8,7 +8,8 @@ import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
8
8
  import { TableProvider } from './TableContext';
9
9
  import useTableResponsive from './utils/hooks/useTableResponsive';
10
10
  import useTableDraggable from './utils/hooks/useTableDraggable';
11
- import { cellMark, removeCellFocusClassName, initRowColProps } from './utils/func';
11
+ import useTableKeyPress from './utils/hooks/useTableKeyPress';
12
+ import { cellMark, removeCellFocusClassName, initRowColProps, getTableRowsColCount } from './utils/func';
12
13
 
13
14
  export interface TableProps extends React.HTMLAttributes<HTMLDivElement> {
14
15
  // content ref
@@ -49,8 +50,16 @@ export interface TableProps extends React.HTMLAttributes<HTMLDivElement> {
49
50
 
50
51
  // key press
51
52
  keyboardFocusable?: boolean;
52
- onCellKeyPressed?: (classname: string, elem: HTMLTableCellElement, event: KeyboardEvent) => void;
53
- onCellPressEnter?: (classname: string, elem: HTMLTableCellElement, event: KeyboardEvent) => void;
53
+ onCellKeyPressed?: (
54
+ classname: string,
55
+ elem: HTMLTableCellElement,
56
+ event: React.KeyboardEvent<Element>,
57
+ isLeftEdge: boolean,
58
+ isRightEdge: boolean,
59
+ isTopEdge: boolean,
60
+ isBottomEdge: boolean
61
+ ) => void;
62
+ onCellPressEnter?: (classname: string, elem: HTMLTableCellElement, event: React.KeyboardEvent<Element>) => void;
54
63
 
55
64
  }
56
65
 
@@ -113,7 +122,7 @@ const Table = forwardRef<HTMLDivElement, TableProps>((
113
122
  const [selectedItems, setSelectedItems] = useState<any>(new Set());
114
123
 
115
124
  // effective element movement on keystroke
116
- const [rootDataInfo, setRootDataInfo] = useState<null | {totalRow: number}>(null);
125
+ const [rootDataInfo, setRootDataInfo] = useState<null | {totalRow: number; totalCol: {row: number; colCount: number}[];}>(null);
117
126
  const refNode = useRef(new Map<string, HTMLTableElement>());
118
127
  const [focusableCellId, setFocusableCellId] = useState<string>('');
119
128
 
@@ -150,6 +159,30 @@ const Table = forwardRef<HTMLDivElement, TableProps>((
150
159
  onRowDrag: onRowDrag
151
160
  }, [data, rootRef]);
152
161
 
162
+ const tableKeyPress = useTableKeyPress({
163
+ enabled: keyboardFocusable,
164
+ data: data,
165
+ spyElement: rootRef.current,
166
+ rootDataInfo,
167
+ refNode,
168
+ focusableCellId,
169
+ setFocusableCellId,
170
+ onCellKeyPressed,
171
+ onCellPressEnter,
172
+ }, [data, rootRef, rootDataInfo, refNode, focusableCellId]);
173
+
174
+ const updateFocusableCell = (row: number, col: number) => {
175
+
176
+ setFocusableCellId(cellMark(row, col));
177
+
178
+ // Find and focus the cell element
179
+ const targetCell = refNode.current.get(cellMark(row, col)) as HTMLElement;
180
+
181
+ if (typeof targetCell !== 'undefined') {
182
+ removeCellFocusClassName(rootRef.current);
183
+ targetCell.classList.add('cell-focus');
184
+ }
185
+ };
153
186
 
154
187
  // initialize context
155
188
  useEffect(() => {
@@ -174,27 +207,53 @@ const Table = forwardRef<HTMLDivElement, TableProps>((
174
207
  if (rootRef.current) {
175
208
  // Initialize custom props of table elements
176
209
  initRowColProps(rootRef.current);
210
+
211
+ // Count the number of columns per row
212
+ const totalCol = getTableRowsColCount(rootRef.current);
213
+ setRootDataInfo({
214
+ totalRow: Array.isArray(data) ? data.length : 0,
215
+ totalCol
216
+ });
217
+
218
+ // Initialize the focused index
219
+ if (keyboardFocusable) {
220
+ updateFocusableCell(0, 0);
221
+ }
222
+
177
223
  }
178
224
  }, [data]); // Re-run when data changes
179
225
 
180
226
 
181
227
  // exposes the following methods
182
228
  useImperativeHandle(contentRef, () => ({
183
- setFocusableCell: (row: number, col: number) => {
184
-
185
- setFocusableCellId(cellMark(row, col));
186
-
187
- // Find and focus the cell element
188
- const cellElement = rootRef.current?.querySelector(`.${cellMark(row, col)}`);
189
- if (cellElement) {
229
+ setFocusableCell: updateFocusableCell,
230
+ clearAllCellFocus: () => {
231
+ if (rootRef.current) {
190
232
  removeCellFocusClassName(rootRef.current);
191
- cellElement.focus(); // !!!Required
192
- cellElement.classList.add('cell-focus');
233
+
234
+ const focusedCells = rootRef.current.querySelectorAll('td.cell-focus, th.cell-focus');
235
+ focusedCells.forEach((cell: any) => {
236
+ if (typeof cell.blur === 'function') cell.blur();
237
+ if (cell.classList) cell.classList.remove('cell-focus');
238
+ });
193
239
  }
240
+ },
241
+ getCellElement: (row: number, col: number) => {
242
+ // Find and focus the cell element
243
+ const targetCell = refNode.current.get(cellMark(row, col)) as HTMLElement;
244
+ return typeof targetCell !== 'undefined' ? targetCell : null;
245
+ },
246
+ forceFocusCell: (row: number, col: number) => {
247
+ // Find and focus the cell element
248
+ const targetCell = refNode.current.get(cellMark(row, col)) as HTMLElement;
194
249
 
195
-
250
+ if (typeof targetCell !== 'undefined') {
251
+ // After forcing focus, you can use the keyboard to listen directly
252
+ targetCell.focus();
253
+ }
196
254
  },
197
- }), [rootRef]);
255
+ triggerCellKeyPressed: tableKeyPress.triggerCellKeyPressed
256
+ }), [rootRef, data, rootDataInfo]);
198
257
 
199
258
  return (
200
259
  <>
@@ -2,6 +2,8 @@ import React, { forwardRef, useContext } from 'react';
2
2
 
3
3
  import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
4
4
 
5
+
6
+
5
7
  import { TableContext } from './TableContext';
6
8
 
7
9
  import { cellMark, removeCellFocusClassName } from './utils/func';
@@ -39,7 +41,7 @@ const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>((
39
41
  const {
40
42
  originData,
41
43
  rootRef,
42
- rootDataInfo,
44
+ rootDataInfo,
43
45
  setRootDataInfo,
44
46
  refNode,
45
47
  focusableCellId,
@@ -58,7 +60,6 @@ const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>((
58
60
  data: originData,
59
61
  spyElement: rootRef.current,
60
62
  rootDataInfo,
61
- setRootDataInfo,
62
63
  refNode,
63
64
  focusableCellId,
64
65
  setFocusableCellId,
@@ -84,7 +85,10 @@ const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>((
84
85
  if (node) {
85
86
  const _row = node.dataset.tableRow;
86
87
  const _col = node.dataset.tableCol;
87
- refNode.current.set(cellMark(_row, _col), node);
88
+ if (typeof _row !== 'undefined' && typeof _col !== 'undefined') {
89
+ refNode.current.set(cellMark(_row, _col), node);
90
+ }
91
+
88
92
  }
89
93
 
90
94
  }}
@@ -159,7 +159,10 @@ export function tableElemScrolledInit(root: HTMLDivElement, w: number) {
159
159
 
160
160
 
161
161
  export function cellMark(row: number | string | undefined, col: number | string | undefined) {
162
- return `cell-${row}-${col}`;
162
+ const isNegative = (num: number) => {
163
+ return num < 0;
164
+ };
165
+ return `cell-${isNegative(row as number) ? 0 : row}-${isNegative(col as number) ? 0 : col}`;
163
166
  }
164
167
 
165
168
  export function removeCellFocusClassName(root: any) {
@@ -169,3 +172,11 @@ export function removeCellFocusClassName(root: any) {
169
172
  });
170
173
  }
171
174
  }
175
+
176
+ export function getTableRowsColCount(root: HTMLDivElement) {
177
+ const rows = allRows(root);
178
+ return rows.map((row: HTMLTableRowElement, i: number) => ({
179
+ row: i,
180
+ colCount: row.children.length
181
+ }));
182
+ }