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.
@@ -2,66 +2,94 @@
2
2
  * Listens for changes in the pressed state of a given key
3
3
  *
4
4
  * @usage:
5
+ *
6
+ const App = () => {
7
+ const keyboardFocusable = true;
8
+ const rootRef = useRef<any>(null);
9
+ // Effective element movement on keystroke
10
+
11
+ const refNode = useRef(new Map<string, HTMLTableElement>());
12
+ const [focusableCellId, setFocusableCellId] = useState<string>('');
13
+
14
+ // Count the number of columns per row
15
+ const rootDataInfo = {"totalRow":2,"totalCol":[{"row":0,"colCount":6},{"row":1,"colCount":6},{"row":2,"colCount":6}]};
5
16
 
6
- const App = () => {
7
-
8
- const keyboardFocusable = true;
9
- const rootRef = useRef<any>(null);
10
-
11
- // effective element movement on keystroke
12
- const [rootDataInfo, setRootDataInfo] = useState<null | {totalRow: number}>(null);
13
- const refNode = useRef(new Map<string, HTMLTableElement>());
14
- const [focusableCellId, setFocusableCellId] = useState<string>('');
15
-
16
- const { handleKeyPressed } = useTableKeyPress({
17
- enabled: keyboardFocusable,
18
- data: [{a: 1, b: 2, c: 3}],
19
- spyElement: rootRef.current,
20
- rootDataInfo,
21
- setRootDataInfo,
22
- refNode,
23
- focusableCellId,
24
- setFocusableCellId,
25
- onCellKeyPressed,
26
- onCellPressEnter,
27
- }, [rootRef]);
28
-
29
-
30
- return (
31
- <div
32
- ref={rootRef}
33
- tabIndex={-1}
34
- onKeyDown={handleKeyPressed} // require "tabIndex" attribute
35
- >Test</div>
36
- );
37
- };
17
+ // Example: handle cell key press with edge detection
18
+ const handleCellKeyPressed = (
19
+ classname: string,
20
+ elem: HTMLTableCellElement,
21
+ event: React.KeyboardEvent<Element>,
22
+ isLeftEdge: boolean,
23
+ isRightEdge: boolean,
24
+ isTopEdge: boolean,
25
+ isBottomEdge: boolean
26
+ ) => {
27
+ if (isLeftEdge) {
28
+ // Handle when at the leftmost cell
29
+ }
30
+ if (isRightEdge) {
31
+ // Handle when at the rightmost cell
32
+ }
33
+ if (isTopEdge) {
34
+ // Handle when at the topmost cell
35
+ }
36
+ if (isBottomEdge) {
37
+ // Handle when at the bottommost cell
38
+ }
39
+ // Your business logic here
40
+ };
41
+
42
+ const { handleKeyPressed } = useTableKeyPress({
43
+ enabled: keyboardFocusable,
44
+ data: [{ a: 1, b: 2, c: 3 }],
45
+ spyElement: rootRef.current,
46
+ rootDataInfo,
47
+ refNode,
48
+ focusableCellId,
49
+ setFocusableCellId,
50
+ onCellKeyPressed: handleCellKeyPressed,
51
+ onCellPressEnter: () => {},
52
+ }, [rootRef]);
38
53
 
54
+ return (
55
+ <div
56
+ ref={rootRef}
57
+ tabIndex={-1}
58
+ onKeyDown={handleKeyPressed} // require "tabIndex" attribute
59
+ >Test</div>
60
+ );
61
+ };
39
62
  */
40
- import { useEffect, useCallback, KeyboardEvent } from "react";
63
+ import { useEffect, useCallback, KeyboardEvent, useRef } from "react";
41
64
 
42
65
  import { initOrderProps, initRowColProps, cellMark, removeCellFocusClassName } from '../func';
43
66
 
44
67
  export interface UseTableKeyPressProps {
45
68
  enabled?: boolean;
46
- data: any[];
69
+ data?: any[];
47
70
  spyElement?: any;
48
- rootDataInfo: null | {totalRow: number};
49
- setRootDataInfo: (s: null | {totalRow: number}) => void;
71
+ rootDataInfo: null | { totalRow: number; totalCol: { row: number; colCount: number }[] };
50
72
  refNode: any;
51
73
  focusableCellId: string;
52
74
  setFocusableCellId: (s: string) => void;
53
- onCellKeyPressed: (classname: string, elem: HTMLTableCellElement, event: KeyboardEvent) => void;
54
- onCellPressEnter: (classname: string, elem: HTMLTableCellElement, event: KeyboardEvent) => void;
75
+ onCellKeyPressed?: (
76
+ classname: string,
77
+ elem: HTMLTableCellElement,
78
+ event: KeyboardEvent<Element>,
79
+ isLeftEdge: boolean,
80
+ isRightEdge: boolean,
81
+ isTopEdge: boolean,
82
+ isBottomEdge: boolean
83
+ ) => void;
84
+ onCellPressEnter?: (classname: string, elem: HTMLTableCellElement, event: KeyboardEvent<Element>) => void;
55
85
  onKeyDown?: (e: any) => void;
56
86
  }
57
87
 
58
-
59
88
  const useTableKeyPress = ({
60
89
  enabled,
61
90
  data,
62
91
  spyElement,
63
92
  rootDataInfo,
64
- setRootDataInfo,
65
93
  refNode,
66
94
  focusableCellId,
67
95
  setFocusableCellId,
@@ -69,10 +97,16 @@ const useTableKeyPress = ({
69
97
  onCellPressEnter,
70
98
  onKeyDown
71
99
  }: UseTableKeyPressProps, deps: any[]) => {
100
+ const focusableCellIdRef = useRef(focusableCellId);
101
+ useEffect(() => {
102
+ focusableCellIdRef.current = focusableCellId;
103
+ }, [focusableCellId]);
72
104
 
73
105
  const handleKeyPressed = useCallback( async (event: KeyboardEvent<HTMLTableCellElement>) => {
74
106
  const key = event.code;
75
107
 
108
+
109
+ // If Enter is pressed and keyboard navigation is disabled, just trigger onCellPressEnter
76
110
  if ((key === 'Enter' || key === 'NumpadEnter') && !enabled) {
77
111
  const currentCell = event.target as HTMLTableCellElement;
78
112
  const row = Number(currentCell.getAttribute('data-table-row'));
@@ -82,50 +116,99 @@ const useTableKeyPress = ({
82
116
  return;
83
117
  }
84
118
 
119
+ // Guard: Only proceed if all required data and enabled flag are valid
85
120
  if (!Array.isArray(data) || rootDataInfo === null || spyElement === null || typeof enabled === 'undefined' || enabled === false) return;
86
121
 
87
- const oldCellSignal = focusableCellId?.replace('cell-', '').split('-');
122
+
123
+ // Parse the current focused cell's row and column
124
+ const oldCellSignal = focusableCellIdRef.current?.replace('cell-', '').split('-');
88
125
  let _row = Number(oldCellSignal[0]);
89
126
  let _col = Number(oldCellSignal[1]);
90
127
 
91
128
 
129
+ // Move function to handle arrow key navigation
92
130
  const move = (key: string) => {
93
-
131
+ let isLeftEdge = false;
132
+ let isRightEdge = false;
133
+ let isTopEdge = false;
134
+ let isBottomEdge = false;
135
+ let maxCol = 0;
136
+ if (rootDataInfo && Array.isArray(rootDataInfo.totalCol)) {
137
+ const rowInfo = rootDataInfo.totalCol.find(r => r.row === _row);
138
+ if (rowInfo) {
139
+ maxCol = rowInfo.colCount;
140
+ }
141
+ }
94
142
  switch (key) {
95
143
  case 'ArrowLeft':
96
144
  case 'Numpad4':
97
- _col = _col - 1 < 0 ? 0 : _col - 1;
98
- break;
145
+ if (_col - 1 < 0) {
146
+ isLeftEdge = true;
147
+ _col = 0;
148
+ } else {
149
+ _col = _col - 1;
150
+ }
151
+ break;
99
152
  case 'ArrowRight':
100
- case 'Numpad6':
101
- _col = _col + 1 > data.length - 1 ? data.length -1 : _col + 1;
102
- break;
153
+ case 'Numpad6': {
154
+ if (_col + 1 > maxCol - 1) {
155
+ isRightEdge = true;
156
+ _col = maxCol - 1;
157
+ } else {
158
+ _col = _col + 1;
159
+ }
160
+ break;
161
+ }
103
162
  case 'ArrowUp':
104
163
  case 'Numpad8':
105
- _row = _row - 1 < 0 ? 0 : _row - 1;
106
- break;
164
+ if (_row - 1 < 0) {
165
+ isTopEdge = true;
166
+ _row = 0;
167
+ } else {
168
+ _row = _row - 1;
169
+ }
170
+ break;
107
171
  case 'ArrowDown':
108
172
  case 'Numpad2':
109
- _row = _row + 1 > rootDataInfo.totalRow - 1 ? rootDataInfo.totalRow - 1 : _row + 1;
110
- break;
173
+
174
+
175
+ if (_row + 1 > rootDataInfo.totalRow - 1) {
176
+ isBottomEdge = true;
177
+ _row = rootDataInfo.totalRow - 1;
178
+ } else {
179
+ _row = _row + 1;
180
+ }
181
+ break;
111
182
  }
112
183
 
184
+
185
+
113
186
  const nextCellSignal: string = cellMark(_row, _col);
114
-
115
- // focus current cell
187
+ // Focus the current cell
116
188
  removeCellFocusClassName(spyElement);
117
- spyElement.querySelector(`.${nextCellSignal}`)?.classList.add('cell-focus');
118
189
 
190
+
191
+ const targetCell = refNode.current.get(nextCellSignal);
192
+ if (typeof targetCell !== 'undefined') {
193
+ targetCell.classList.add('cell-focus');
194
+ }
119
195
 
120
- //
196
+
121
197
  setFocusableCellId(nextCellSignal);
122
-
123
- // callback
124
- onCellKeyPressed?.(nextCellSignal, refNode.current.get(nextCellSignal), event);
198
+ // Callback with edge detection
199
+ onCellKeyPressed?.(
200
+ nextCellSignal,
201
+ refNode.current.get(nextCellSignal),
202
+ event,
203
+ isLeftEdge,
204
+ isRightEdge,
205
+ isTopEdge,
206
+ isBottomEdge
207
+ );
125
208
  onKeyDown?.(event);
126
-
127
209
  };
128
210
 
211
+ // Handle arrow key navigation
129
212
  if (key === 'ArrowLeft' || key === 'Numpad4') {
130
213
  move('ArrowLeft');
131
214
  }
@@ -134,7 +217,6 @@ const useTableKeyPress = ({
134
217
  move('ArrowRight');
135
218
  }
136
219
 
137
-
138
220
  if (key === 'ArrowUp' || key === 'Numpad8') {
139
221
  move('ArrowUp');
140
222
  }
@@ -143,32 +225,30 @@ const useTableKeyPress = ({
143
225
  move('ArrowDown');
144
226
  }
145
227
 
228
+ // Handle Enter key
146
229
  if (key === 'Enter' || key === 'NumpadEnter') {
147
230
  const nextCellSignal: string = cellMark(_row, _col);
148
231
  onCellPressEnter?.(nextCellSignal, refNode.current.get(nextCellSignal), event);
149
232
  }
150
233
 
151
- }, [focusableCellId]);
234
+ }, [focusableCellId, rootDataInfo, data]);
152
235
 
153
236
  useEffect(() => {
154
237
  if (enabled) {
155
-
156
- // Initialize custom props of table elements
238
+ // Initialize custom props of table elements (only once)
157
239
  initOrderProps(spyElement);
158
240
  initRowColProps(spyElement);
159
-
160
- // Update cell ids
161
- if (Array.isArray(data)) {
162
- setRootDataInfo({
163
- totalRow: data.length
164
- });
165
- }
166
241
  }
167
242
  }, [enabled, spyElement, ...deps]);
168
243
 
169
-
170
244
  return {
171
- handleKeyPressed
245
+ handleKeyPressed,
246
+
247
+ /**
248
+ * Expose handleKeyPressed for external usage, e.g., via contentRef in Table component.
249
+ * This allows calling handleKeyPressed programmatically from outside, such as with a custom onCellKeyPressed method.
250
+ */
251
+ triggerCellKeyPressed: handleKeyPressed,
172
252
  };
173
253
  };
174
254
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "UIUX Lab",
3
3
  "email": "uiuxlab@gmail.com",
4
4
  "name": "funda-ui",
5
- "version": "4.7.222",
5
+ "version": "4.7.252",
6
6
  "description": "React components using pure Bootstrap 5+ which does not contain any external style and script libraries.",
7
7
  "repository": {
8
8
  "type": "git",