funda-ui 4.7.711 → 4.7.730

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 (37) hide show
  1. package/DragDropList/index.d.ts +1 -0
  2. package/DragDropList/index.js +143 -52
  3. package/DynamicFields/index.d.ts +1 -0
  4. package/DynamicFields/index.js +108 -8
  5. package/EventCalendarTimeline/index.css +12 -1
  6. package/EventCalendarTimeline/index.d.ts +1 -0
  7. package/EventCalendarTimeline/index.js +99 -6
  8. package/MultipleSelect/index.js +162 -71
  9. package/Table/index.css +5 -1
  10. package/Table/index.js +410 -90
  11. package/Utils/useBoundedDrag.d.ts +1 -0
  12. package/Utils/useBoundedDrag.js +124 -39
  13. package/lib/cjs/DragDropList/index.d.ts +1 -0
  14. package/lib/cjs/DragDropList/index.js +143 -52
  15. package/lib/cjs/DynamicFields/index.d.ts +1 -0
  16. package/lib/cjs/DynamicFields/index.js +108 -8
  17. package/lib/cjs/EventCalendarTimeline/index.d.ts +1 -0
  18. package/lib/cjs/EventCalendarTimeline/index.js +99 -6
  19. package/lib/cjs/MultipleSelect/index.js +162 -71
  20. package/lib/cjs/Table/index.js +410 -90
  21. package/lib/cjs/Utils/useBoundedDrag.d.ts +1 -0
  22. package/lib/cjs/Utils/useBoundedDrag.js +124 -39
  23. package/lib/css/EventCalendarTimeline/index.css +12 -1
  24. package/lib/css/Table/index.css +5 -1
  25. package/lib/esm/DragDropList/index.tsx +23 -16
  26. package/lib/esm/DynamicFields/index.tsx +107 -8
  27. package/lib/esm/EventCalendarTimeline/index.scss +15 -1
  28. package/lib/esm/EventCalendarTimeline/index.tsx +130 -11
  29. package/lib/esm/ModalDialog/index.tsx +0 -1
  30. package/lib/esm/Table/Table.tsx +9 -7
  31. package/lib/esm/Table/TableRow.tsx +9 -3
  32. package/lib/esm/Table/index.scss +8 -2
  33. package/lib/esm/Table/utils/DragHandleSprite.tsx +6 -2
  34. package/lib/esm/Table/utils/func.ts +12 -1
  35. package/lib/esm/Table/utils/hooks/useTableDraggable.tsx +401 -93
  36. package/lib/esm/Utils/hooks/useBoundedDrag.tsx +142 -39
  37. package/package.json +1 -1
@@ -14,6 +14,7 @@ import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
14
14
  import getOs from 'funda-utils//dist/cjs/os';
15
15
 
16
16
 
17
+
17
18
  export interface EventsValueConfig {
18
19
  id: string | number;
19
20
  date: string,
@@ -79,7 +80,7 @@ export type EventCalendarTimelineProps = {
79
80
  onChangeWeek?: (startDate: string, endDate: string) => void;
80
81
  onListRenderComplete?: () => void;
81
82
  onChangeAppearanceMode?: (mode: string) => void;
82
-
83
+ enableHeaderResize?: boolean;
83
84
 
84
85
 
85
86
  // modal dialog
@@ -169,6 +170,7 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
169
170
  onChangeWeek,
170
171
  onListRenderComplete,
171
172
  onChangeAppearanceMode,
173
+ enableHeaderResize,
172
174
 
173
175
  //
174
176
  modalMaskOpacity,
@@ -273,11 +275,17 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
273
275
  const AUTO_SCROLL = autoScroll || false;
274
276
  const HEADER_SHOW_WEEK = headerShowWeek || false;
275
277
  const BODY_DRAG = draggable || false;
278
+ const ENABLE_HEADER_RESIZE = enableHeaderResize || false;
276
279
  const tableGridRef = useRef<any>(null);
277
280
  const tableGridHeaderRef = useRef<any>(null);
278
281
  const scrollHeaderRef = useRef(null);
279
282
  const scrollBodyRef = useRef(null);
280
283
  const scrollListRef = useRef(null);
284
+
285
+ // header resize drag
286
+ const isResizingRef = useRef<boolean>(false);
287
+ const resizeStartXRef = useRef<number>(0);
288
+ const resizeStartWidthRef = useRef<number>(0);
281
289
 
282
290
  // Temporary date
283
291
  const [tempDate, setTempDate] = useState<string>('');
@@ -2121,7 +2129,6 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2121
2129
 
2122
2130
  }
2123
2131
 
2124
-
2125
2132
  function tableGridReset() {
2126
2133
  if (tableGridRef.current === null) return;
2127
2134
 
@@ -2153,13 +2160,7 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2153
2160
 
2154
2161
  }
2155
2162
 
2156
-
2157
-
2158
-
2159
- // ================================================================
2160
- //
2161
- // ================================================================
2162
- //if user change the selectedYear, then udate the years array that is displayed on year tab view
2163
+ // if user change the selectedYear, then udate the years array that is displayed on year tab view
2163
2164
  useEffect(() => {
2164
2165
  const years: number[] = [];
2165
2166
  for (let y = selectedYear - 10; y < selectedYear + 10; y++) {
@@ -2230,6 +2231,102 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2230
2231
  }
2231
2232
 
2232
2233
  }, []);
2234
+
2235
+
2236
+ // ================================================================
2237
+ // Handle header resize drag
2238
+ // ================================================================
2239
+ const handleHeaderResizeMove = useCallback((e: MouseEvent | TouchEvent) => {
2240
+ if (!isResizingRef.current || !tableGridRef.current) return;
2241
+
2242
+ const tableGridEl: any = tableGridRef.current;
2243
+ // Find the header title element that should have the CSS variable
2244
+ const headerTitleEl = tableGridEl.querySelector('.custom-event-tl-table__cell-cushion-headertitle') as HTMLElement;
2245
+ if (!headerTitleEl) return;
2246
+
2247
+ // Get clientX from either MouseEvent or TouchEvent
2248
+ const clientX = 'touches' in e ? e.touches[0]?.clientX : e.clientX;
2249
+ if (clientX === undefined) return;
2250
+
2251
+ const deltaX = clientX - resizeStartXRef.current;
2252
+ const newWidth = Math.max(100, resizeStartWidthRef.current + deltaX); // Minimum width 100px
2253
+
2254
+ // Update CSS variable on the header title element
2255
+ headerTitleEl.style.setProperty('--custom-event-tl-table-header-w', `${newWidth}px`);
2256
+ }, []);
2257
+
2258
+ const handleHeaderResizeEnd = useCallback(() => {
2259
+ if (!isResizingRef.current) return;
2260
+
2261
+ isResizingRef.current = false;
2262
+ document.removeEventListener('mousemove', handleHeaderResizeMove);
2263
+ document.removeEventListener('mouseup', handleHeaderResizeEnd);
2264
+ document.removeEventListener('touchmove', handleHeaderResizeMove);
2265
+ document.removeEventListener('touchend', handleHeaderResizeEnd);
2266
+
2267
+ // Recalculate table grid after resize
2268
+ if (tableGridRef.current) {
2269
+ tableGridInit();
2270
+ }
2271
+ }, [handleHeaderResizeMove]);
2272
+
2273
+ const handleHeaderResizeStart = useCallback((e: React.MouseEvent) => {
2274
+ if (!ENABLE_HEADER_RESIZE || !tableGridRef.current) return;
2275
+
2276
+ e.preventDefault();
2277
+ e.stopPropagation();
2278
+
2279
+ isResizingRef.current = true;
2280
+ resizeStartXRef.current = e.clientX;
2281
+
2282
+ const tableGridEl: any = tableGridRef.current;
2283
+ const headerTitleEl = tableGridEl.querySelector('.custom-event-tl-table__cell-cushion-headertitle') as HTMLElement;
2284
+ if (headerTitleEl) {
2285
+ resizeStartWidthRef.current = headerTitleEl.clientWidth;
2286
+ }
2287
+
2288
+ document.addEventListener('mousemove', handleHeaderResizeMove);
2289
+ document.addEventListener('mouseup', handleHeaderResizeEnd);
2290
+ }, [ENABLE_HEADER_RESIZE, handleHeaderResizeMove, handleHeaderResizeEnd]);
2291
+
2292
+ const handleHeaderResizeTouchStart = useCallback((e: React.TouchEvent) => {
2293
+ if (!ENABLE_HEADER_RESIZE || !tableGridRef.current) return;
2294
+
2295
+ e.preventDefault();
2296
+ e.stopPropagation();
2297
+
2298
+ isResizingRef.current = true;
2299
+ const touch = e.touches[0];
2300
+ if (touch) {
2301
+ resizeStartXRef.current = touch.clientX;
2302
+ }
2303
+
2304
+ const tableGridEl: any = tableGridRef.current;
2305
+ const headerTitleEl = tableGridEl.querySelector('.custom-event-tl-table__cell-cushion-headertitle') as HTMLElement;
2306
+ if (headerTitleEl) {
2307
+ resizeStartWidthRef.current = headerTitleEl.clientWidth;
2308
+ }
2309
+
2310
+ document.addEventListener('touchmove', handleHeaderResizeMove, { passive: false });
2311
+ document.addEventListener('touchend', handleHeaderResizeEnd);
2312
+ }, [ENABLE_HEADER_RESIZE, handleHeaderResizeMove, handleHeaderResizeEnd]);
2313
+
2314
+
2315
+
2316
+ // Cleanup on unmount
2317
+ useEffect(() => {
2318
+ return () => {
2319
+ document.removeEventListener('mousemove', handleHeaderResizeMove);
2320
+ document.removeEventListener('mouseup', handleHeaderResizeEnd);
2321
+ document.removeEventListener('touchmove', handleHeaderResizeMove);
2322
+ document.removeEventListener('touchend', handleHeaderResizeEnd);
2323
+ };
2324
+ }, [handleHeaderResizeMove, handleHeaderResizeEnd]);
2325
+
2326
+
2327
+
2328
+
2329
+
2233
2330
  return (
2234
2331
  <>
2235
2332
 
@@ -2417,6 +2514,9 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2417
2514
  <thead role="presentation">
2418
2515
  <tr role="row">
2419
2516
  <th role="columnheader">
2517
+ {/**
2518
+ * The width of the left column header is set using ".custom-event-tl-table__cell-cushion-headertitle".
2519
+ */}
2420
2520
  <div className="custom-event-tl-table__cell-cushion custom-event-tl-table__cell-cushion-headertitle">
2421
2521
  {tableListSectionTitle || ''}
2422
2522
  </div>
@@ -2431,7 +2531,18 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2431
2531
 
2432
2532
 
2433
2533
  </th>
2434
- <th role="presentation" className="custom-event-tl-table__timeline-divider"><div></div></th>
2534
+ <th
2535
+ role="presentation"
2536
+ className={combinedCls(
2537
+ 'custom-event-tl-table__timeline-divider',
2538
+ ENABLE_HEADER_RESIZE ? 'custom-event-tl-table__timeline-divider--resizable' : ''
2539
+ )}
2540
+ onMouseDown={ENABLE_HEADER_RESIZE ? handleHeaderResizeStart : undefined}
2541
+ onTouchStart={ENABLE_HEADER_RESIZE ? handleHeaderResizeTouchStart : undefined}
2542
+ style={ENABLE_HEADER_RESIZE ? { cursor: 'col-resize', userSelect: 'none' } : undefined}
2543
+ >
2544
+ <div></div>
2545
+ </th>
2435
2546
  <th role="presentation">
2436
2547
  <div
2437
2548
  ref={scrollHeaderRef}
@@ -2507,12 +2618,20 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2507
2618
 
2508
2619
 
2509
2620
  </td>
2621
+
2622
+ {/**
2623
+ * The dividing line between the left title column and the right content
2624
+ */}
2510
2625
  <td
2511
2626
  role="presentation"
2512
2627
  className={combinedCls(
2513
2628
  'custom-event-tl-table__timeline-divider',
2514
- tableListDividerClassName
2629
+ tableListDividerClassName,
2630
+ ENABLE_HEADER_RESIZE ? 'custom-event-tl-table__timeline-divider--resizable' : ''
2515
2631
  )}
2632
+ onMouseDown={ENABLE_HEADER_RESIZE ? handleHeaderResizeStart : undefined}
2633
+ onTouchStart={ENABLE_HEADER_RESIZE ? handleHeaderResizeTouchStart : undefined}
2634
+ style={ENABLE_HEADER_RESIZE ? { cursor: 'col-resize', userSelect: 'none' } : undefined}
2516
2635
  >
2517
2636
  <div></div>
2518
2637
  </td>
@@ -13,7 +13,6 @@ import {
13
13
  } from 'funda-utils/dist/cjs/bodyScrollLock';
14
14
 
15
15
 
16
-
17
16
  declare global {
18
17
  interface Window {
19
18
  curVideo?: any;
@@ -5,6 +5,7 @@ import useComId from 'funda-utils/dist/cjs/useComId';
5
5
  import { clsWrite, combinedCls } from 'funda-utils/dist/cjs/cls';
6
6
 
7
7
 
8
+
8
9
  import { TableProvider } from './TableContext';
9
10
  import useTableResponsive from './utils/hooks/useTableResponsive';
10
11
  import useTableDraggable from './utils/hooks/useTableDraggable';
@@ -148,10 +149,10 @@ const Table = forwardRef<HTMLDivElement, TableProps>((
148
149
 
149
150
  // initialize drag & drop data
150
151
  const {
151
- handleDragStart,
152
- handleDragEnd,
153
- handledragOver,
154
- handleTbodyEnter
152
+ isDragging,
153
+ dragHandlers,
154
+ handleTbodyEnter,
155
+ handleTbodyLeave
155
156
  } = useTableDraggable({
156
157
  enabled: rowDraggable && rootRef.current,
157
158
  data: data,
@@ -268,9 +269,10 @@ const Table = forwardRef<HTMLDivElement, TableProps>((
268
269
 
269
270
  // drag & drop
270
271
  rowDraggable,
271
- handleDragStart,
272
- handleDragEnd,
273
- handledragOver,
272
+ isRowDragging: isDragging,
273
+ handleDragStart: dragHandlers.handleDragStart,
274
+ handleDragEnd: dragHandlers.handleDragEnd,
275
+ handledragOver: dragHandlers.handleDragOver,
274
276
  handleTbodyEnter,
275
277
 
276
278
 
@@ -44,9 +44,15 @@ const TableRow = forwardRef<HTMLTableRowElement, TableRowProps>((
44
44
  // selection
45
45
  // ================================================================
46
46
  const _res = convertMapToArr(selectedItems);
47
+ // Performance optimization: stringify itemData only once instead of N times
48
+ const itemDataStr = itemData ? JSON.stringify(itemData) : '';
47
49
  const filteredSelectedItems = _res.map((v: any) => Number(v)).map((rowNum: number) => {
48
- if (JSON.stringify(itemData) === JSON.stringify(originData[rowNum])) {
49
- return originData[rowNum];
50
+ const originItem = originData?.[rowNum];
51
+ // Fast path: reference equality
52
+ if (itemData === originItem) return originItem;
53
+ // Fallback: JSON comparison (itemDataStr is cached)
54
+ if (itemDataStr && itemDataStr === JSON.stringify(originItem)) {
55
+ return originItem;
50
56
  }
51
57
  }).filter(Boolean);
52
58
  const selectedClassName = activeClassName || 'active';
@@ -64,7 +70,7 @@ const TableRow = forwardRef<HTMLTableRowElement, TableRowProps>((
64
70
  onDragEnd={rowDraggable ? handleDragEnd : () => void(0)}
65
71
  data-row-data={`${itemData && typeof itemData === 'object' ? JSON.stringify(itemData) : itemData}`}
66
72
 
67
- className={`row-obj ${className || ''} ${active ? selectedClassName : ''} ${itemData && originData ? (filteredData.every((item: any) => filterFieldsData.some((s: string) => !item[s]?.toLowerCase().includes(itemData[s]?.toLowerCase())) ) ? 'd-none' : '') : ''} ${itemData && originData ? (filteredSelectedItems.some((item: any) => JSON.stringify(itemData) === JSON.stringify(item) ) ? selectedClassName : '') : ''}`}
73
+ className={`row-obj ${className || ''} ${active ? selectedClassName : ''} ${itemData && originData ? (filteredData.every((item: any) => filterFieldsData.some((s: string) => !item[s]?.toLowerCase().includes(itemData[s]?.toLowerCase())) ) ? 'd-none' : '') : ''} ${itemData && originData && filteredSelectedItems.length > 0 ? selectedClassName : ''}`}
68
74
  >
69
75
  {children}
70
76
  </tr>
@@ -1,3 +1,5 @@
1
+
2
+
1
3
  /* ======================================================
2
4
  <!-- Table (Synthetic) -->
3
5
  /* ====================================================== */
@@ -7,7 +9,7 @@
7
9
  --syntable-row-active-bg: #f0f8ff;
8
10
  --syntable-scrollbar-color: rgba(0, 0, 0, 0.2);
9
11
  --syntable-scrollbar-track: rgba(0, 0, 0, 0);
10
- --syntable-scrollbar-h: 3px;
12
+ --syntable-scrollbar-h: 10px;
11
13
  --syntable-scrollbar-w: 10px;
12
14
  --syntable-padding-x: 1rem;
13
15
  --syntable-padding-y: 0.5rem;
@@ -165,11 +167,13 @@
165
167
 
166
168
  .row-obj-clonelast {
167
169
  height: 0 !important;
168
-
170
+ border-color: transparent !important;
171
+ padding: 0 !important;
169
172
 
170
173
  td {
171
174
  border: none;
172
175
  box-shadow: none;
176
+ padding: 0 !important;
173
177
  }
174
178
  }
175
179
 
@@ -190,11 +194,13 @@
190
194
  }
191
195
 
192
196
  /* placeholder */
197
+ .row-obj-lastplaceholder,
193
198
  .row-placeholder {
194
199
  border: 2px dotted #b5ba91;
195
200
  background-color: #e4e9c3;
196
201
  }
197
202
 
203
+
198
204
  /* trigger */
199
205
  .drag-trigger {
200
206
  display: inline-block;
@@ -15,7 +15,8 @@ const DragHandleSprite = forwardRef((props: DragHandleSpriteProps, externalRef:
15
15
 
16
16
  const {
17
17
  rowDraggable,
18
- handleTbodyEnter
18
+ handleTbodyEnter,
19
+ handleTbodyLeave
19
20
  } = useContext(TableContext);
20
21
 
21
22
  return (
@@ -23,7 +24,10 @@ const DragHandleSprite = forwardRef((props: DragHandleSpriteProps, externalRef:
23
24
  {rowDraggable ? <span
24
25
  ref={externalRef}
25
26
  className={className || 'drag-trigger'}
26
- onMouseEnter={handleTbodyEnter}
27
+ // Only when mousedown happens on this handle will we allow row dragging.
28
+ onMouseDown={handleTbodyEnter}
29
+ onMouseUp={handleTbodyLeave}
30
+ onMouseLeave={handleTbodyLeave}
27
31
  >
28
32
  {icon || <svg width="1em" height="1em" viewBox="0 0 24 24" fill="none">
29
33
  <g>
@@ -63,6 +63,14 @@ export function initOrderProps(rootElem: any) {
63
63
 
64
64
  export function initRowColProps(rootElem: any) {
65
65
  if (rootElem === null) return;
66
+
67
+ // !!! Important, performance optimization for large data renderings
68
+ // With this protection, it is only performed once
69
+ if (typeof rootElem.dataset.rowColPropsInit !== 'undefined') return;
70
+ rootElem.dataset.rowColPropsInit = '1';
71
+
72
+
73
+ //
66
74
  const _allRows = allRows(rootElem);
67
75
  const _allHeadRows = allHeadRows(rootElem);
68
76
 
@@ -167,7 +175,10 @@ export function cellMark(row: number | string | undefined, col: number | string
167
175
 
168
176
  export function removeCellFocusClassName(root: any) {
169
177
  if (root) {
170
- [].slice.call(root.querySelectorAll('td, th')).forEach((el: HTMLTableElement) => {
178
+ // !!! Important, performance optimization for large data renderings
179
+ // Only query elements with cell-focus classes
180
+ const focusedCells = root.querySelectorAll('td.cell-focus, th.cell-focus');
181
+ focusedCells.forEach((el: HTMLElement) => {
171
182
  el.classList.remove('cell-focus');
172
183
  });
173
184
  }