funda-ui 4.7.701 → 4.7.723

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.
@@ -1,3 +1,44 @@
1
+ /**
2
+ * SSE
3
+ *
4
+ * @usage:
5
+ *
6
+ const App = () => {
7
+ const { connected, messages, disconnect, reconnect } = useSSE('http://localhost:3000/sse');
8
+
9
+ return (
10
+ <div>
11
+ <p>Status: {connected ? '✅ Connected' : '❌ Disconnected'}</p>
12
+ <button onClick={disconnect}>Disconnect</button>
13
+ <button onClick={reconnect}>Reconnect</button>
14
+ {messages.map((m, i) => <div key={i}>{m}</div>)}
15
+ </div>
16
+ );
17
+ };
18
+
19
+ * It is recommended to use it in conjunction with usePageVisibility, because in HTTP mode,
20
+ * browsers allow a maximum of 6 connections; otherwise, other normal interfaces will be suspended and inaccessible.
21
+
22
+ import usePageVisibility from 'funda-ui/Utils/usePageVisibility';
23
+
24
+ const App = () => {
25
+ const { connected, messages, disconnect, reconnect } = useSSE('http://localhost:3000/sse');
26
+
27
+ // add new
28
+ usePageVisibility(
29
+ () => {
30
+ reconnect();
31
+ },
32
+ () => {
33
+ disconnect();
34
+ },
35
+ () => console.log("🎬 Page initialized while visible.")
36
+ );
37
+
38
+ return '';
39
+ };
40
+
41
+ */
1
42
  declare const useSSE: (url: string | null | undefined, retryDelay?: number) => {
2
43
  readonly connected: boolean;
3
44
  readonly messages: string[];
@@ -108,20 +108,44 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
108
108
  *
109
109
  * @usage:
110
110
  *
111
- * const App = () => {
112
- * const { connected, messages, disconnect, reconnect } = useSSE('http://localhost:3000/sse');
113
- *
114
- * return (
115
- * <div>
116
- * <p>Status: {connected ? '✅ Connected' : '❌ Disconnected'}</p>
117
- * <button onClick={disconnect}>Disconnect</button>
118
- * <button onClick={reconnect}>Reconnect</button>
119
- * {messages.map((m, i) => <div key={i}>{m}</div>)}
120
- * </div>
121
- * );
122
- * };
111
+ const App = () => {
112
+ const { connected, messages, disconnect, reconnect } = useSSE('http://localhost:3000/sse');
113
+
114
+ return (
115
+ <div>
116
+ <p>Status: {connected ? '✅ Connected' : '❌ Disconnected'}</p>
117
+ <button onClick={disconnect}>Disconnect</button>
118
+ <button onClick={reconnect}>Reconnect</button>
119
+ {messages.map((m, i) => <div key={i}>{m}</div>)}
120
+ </div>
121
+ );
122
+ };
123
+
124
+ * It is recommended to use it in conjunction with usePageVisibility, because in HTTP mode,
125
+ * browsers allow a maximum of 6 connections; otherwise, other normal interfaces will be suspended and inaccessible.
126
+
127
+ import usePageVisibility from 'funda-ui/Utils/usePageVisibility';
128
+
129
+ const App = () => {
130
+ const { connected, messages, disconnect, reconnect } = useSSE('http://localhost:3000/sse');
131
+
132
+ // add new
133
+ usePageVisibility(
134
+ () => {
135
+ reconnect();
136
+ },
137
+ () => {
138
+ disconnect();
139
+ },
140
+ () => console.log("🎬 Page initialized while visible.")
141
+ );
142
+
143
+ return '';
144
+ };
145
+
123
146
  */
124
147
 
148
+
125
149
  var useSSE = function useSSE(url) {
126
150
  var retryDelay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3000;
127
151
  var _useState = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false),
@@ -235,7 +235,7 @@
235
235
  }
236
236
  */
237
237
  /* cell */
238
- /* header */
238
+ /* The width of the left column header */
239
239
  /* event container */
240
240
  /* days container */
241
241
  /* remove empty cells */
@@ -466,10 +466,21 @@
466
466
  background-color: var(--custom-event-tl-table-divider-bg);
467
467
  border-left: var(--custom-event-tl-table-divider-border);
468
468
  border-right: var(--custom-event-tl-table-divider-border);
469
+ position: relative;
469
470
  }
470
471
  .custom-event-tl-table__timeline-table__wrapper .custom-event-tl-table__timeline-divider > div {
471
472
  width: var(--custom-event-tl-table-divider-w);
472
473
  }
474
+ .custom-event-tl-table__timeline-table__wrapper .custom-event-tl-table__timeline-divider--resizable {
475
+ cursor: col-resize;
476
+ user-select: none;
477
+ }
478
+ .custom-event-tl-table__timeline-table__wrapper .custom-event-tl-table__timeline-divider--resizable:hover {
479
+ background-color: rgba(0, 0, 0, 0.05);
480
+ }
481
+ .custom-event-tl-table__timeline-table__wrapper .custom-event-tl-table__timeline-divider--resizable:active {
482
+ background-color: rgba(0, 0, 0, 0.1);
483
+ }
473
484
  .custom-event-tl-table__timeline-table__wrapper .custom-event-tl-table__cell-cushion {
474
485
  padding: var(--custom-event-tl-table-cell-padding);
475
486
  }
@@ -20,6 +20,7 @@ export type DynamicFieldsProps = {
20
20
  label?: React.ReactNode | string;
21
21
  data: DynamicFieldsValueProps | null;
22
22
  maxFields?: any;
23
+ defaultRows?: number;
23
24
  confirmText?: string;
24
25
  doNotRemoveDom?: boolean;
25
26
  iconAddBefore?: React.ReactNode | string;
@@ -55,6 +56,7 @@ const DynamicFields = (props: DynamicFieldsProps) => {
55
56
  label,
56
57
  data,
57
58
  maxFields,
59
+ defaultRows,
58
60
  iconAddBefore,
59
61
  iconAddAfter,
60
62
  iconAdd,
@@ -96,6 +98,10 @@ const DynamicFields = (props: DynamicFieldsProps) => {
96
98
  const [tmpl, setTmpl] = useState<React.ReactNode>([]);
97
99
  const addBtnIdRef = useRef<string>('');
98
100
  addBtnIdRef.current = `dynamic-fields-add-${idRes}`;
101
+ const defaultRowsInitializedRef = useRef<boolean>(false);
102
+ const isInitializingDefaultRowsRef = useRef<boolean>(false);
103
+ const rafIdRef = useRef<number | null>(null);
104
+ const timeoutIdsRef = useRef<ReturnType<typeof setTimeout>[]>([]);
99
105
 
100
106
  // exposes the following methods
101
107
  useImperativeHandle(
@@ -169,17 +175,12 @@ const DynamicFields = (props: DynamicFieldsProps) => {
169
175
  }
170
176
  }
171
177
 
172
-
173
- function handleClickAdd(event: any = null) {
174
- if (event !== null) {
175
- if (typeof event !== 'undefined') event.preventDefault();
176
- }
177
-
178
+ function addRowWithTemplate(template: React.ReactNode) {
178
179
  //button status
179
180
  checkMaxStatus();
180
181
 
181
182
  //
182
- setVal((prevState: any[]) => [...prevState, ...generateGroup(tmpl)]);
183
+ setVal((prevState: any[]) => [...prevState, ...generateGroup(template)]);
183
184
 
184
185
 
185
186
  //
@@ -209,6 +210,14 @@ const DynamicFields = (props: DynamicFieldsProps) => {
209
210
  }, 0);
210
211
  }
211
212
 
213
+ function handleClickAdd(event: any = null) {
214
+ if (event !== null) {
215
+ if (typeof event !== 'undefined') event.preventDefault();
216
+ }
217
+
218
+ addRowWithTemplate(tmpl);
219
+ }
220
+
212
221
 
213
222
  function handleClickRemove(e: React.MouseEvent) {
214
223
  if (e !== null && typeof e !== 'undefined') e.preventDefault();
@@ -331,6 +340,96 @@ const DynamicFields = (props: DynamicFieldsProps) => {
331
340
 
332
341
  //
333
342
  onLoad?.(addBtnIdRef.current, rootRef.current, PER_ROW_DOM_STRING);
343
+
344
+ // Reset initialization flag when data becomes null
345
+ if (!data) {
346
+ defaultRowsInitializedRef.current = false;
347
+ isInitializingDefaultRowsRef.current = false;
348
+ }
349
+
350
+ // Add default rows if specified (only initialize once per data setup)
351
+ if (!defaultRowsInitializedRef.current && typeof defaultRows === 'number' && defaultRows > 0 && data && data.tmpl) {
352
+ // Get the initial data length
353
+ const initDataLength = Array.isArray(data.init) ? data.init.length : 0;
354
+
355
+ // Calculate how many rows need to be added
356
+ const maxRows = typeof maxFields !== 'undefined' ? parseFloat(maxFields) : Infinity;
357
+ const targetRows = Math.min(defaultRows, maxRows);
358
+ const rowsToAdd = Math.max(0, targetRows - initDataLength);
359
+
360
+ // Mark as initialized immediately to prevent re-adding
361
+ defaultRowsInitializedRef.current = true;
362
+
363
+ // Only add rows if needed
364
+ if (rowsToAdd > 0) {
365
+ // Mark that we're initializing default rows
366
+ isInitializingDefaultRowsRef.current = true;
367
+
368
+ // Clear any existing timeouts
369
+ timeoutIdsRef.current.forEach(id => clearTimeout(id));
370
+ timeoutIdsRef.current = [];
371
+
372
+ // Use requestAnimationFrame to ensure DOM is ready and state is updated
373
+ rafIdRef.current = requestAnimationFrame(() => {
374
+ // Check if component is still mounted
375
+ if (!rootRef.current) {
376
+ isInitializingDefaultRowsRef.current = false;
377
+ return;
378
+ }
379
+
380
+ const timeoutId1 = setTimeout(() => {
381
+ // Check if component is still mounted
382
+ if (!rootRef.current) {
383
+ isInitializingDefaultRowsRef.current = false;
384
+ return;
385
+ }
386
+
387
+ // Add rows one by one with a small delay to ensure state updates
388
+ let addedCount = 0;
389
+ const addNextRow = () => {
390
+ // Check if component is still mounted before each operation
391
+ if (addedCount < rowsToAdd && rootRef.current) {
392
+ // Use data.tmpl directly to avoid dependency on state
393
+ addRowWithTemplate(data.tmpl);
394
+ addedCount++;
395
+ // Wait a bit before adding the next row to ensure state updates
396
+ if (addedCount < rowsToAdd) {
397
+ const timeoutId2 = setTimeout(addNextRow, 10);
398
+ timeoutIdsRef.current.push(timeoutId2);
399
+ } else {
400
+ // All rows added, mark initialization as complete
401
+ isInitializingDefaultRowsRef.current = false;
402
+ }
403
+ } else {
404
+ // No more rows to add or component unmounted
405
+ isInitializingDefaultRowsRef.current = false;
406
+ }
407
+ };
408
+ addNextRow();
409
+ }, 50);
410
+ timeoutIdsRef.current.push(timeoutId1);
411
+ });
412
+ }
413
+ }
414
+
415
+ // Cleanup function to cancel requestAnimationFrame and timeouts
416
+ return () => {
417
+ // Don't cleanup if we're in the middle of initializing defaultRows
418
+ // This prevents the cleanup from canceling the async operations before they complete
419
+ if (isInitializingDefaultRowsRef.current) {
420
+ // Allow defaultRows initialization to complete
421
+ // The cleanup will happen naturally when initialization finishes
422
+ return;
423
+ }
424
+
425
+ // Normal cleanup for other cases
426
+ if (rafIdRef.current !== null) {
427
+ cancelAnimationFrame(rafIdRef.current);
428
+ rafIdRef.current = null;
429
+ }
430
+ timeoutIdsRef.current.forEach(id => clearTimeout(id));
431
+ timeoutIdsRef.current = [];
432
+ };
334
433
  }, [data]);
335
434
 
336
435
 
@@ -406,4 +505,4 @@ const DynamicFields = (props: DynamicFieldsProps) => {
406
505
  )
407
506
  };
408
507
 
409
- export default DynamicFields;
508
+ export default DynamicFields;
@@ -470,7 +470,7 @@
470
470
  word-break: break-word;
471
471
  }
472
472
 
473
- /* header */
473
+ /* The width of the left column header */
474
474
  .custom-event-tl-table__cell-cushion-headertitle {
475
475
  width: var(--custom-event-tl-table-header-w);
476
476
  }
@@ -614,10 +614,24 @@
614
614
  background-color: var(--custom-event-tl-table-divider-bg);
615
615
  border-left: var(--custom-event-tl-table-divider-border);
616
616
  border-right: var(--custom-event-tl-table-divider-border);
617
+ position: relative;
617
618
 
618
619
  > div {
619
620
  width: var(--custom-event-tl-table-divider-w);
620
621
  }
622
+
623
+ &--resizable {
624
+ cursor: col-resize;
625
+ user-select: none;
626
+
627
+ &:hover {
628
+ background-color: rgba(0, 0, 0, 0.05);
629
+ }
630
+
631
+ &:active {
632
+ background-color: rgba(0, 0, 0, 0.1);
633
+ }
634
+ }
621
635
  }
622
636
 
623
637
  /* cell */
@@ -71,7 +71,7 @@ export type EventCalendarTimelineProps = {
71
71
  cellAddBtnLabel?: string | React.ReactNode;
72
72
  forwardAndBackFillDisabled?: boolean;
73
73
  draggable?: boolean;
74
- showWeek?: boolean;
74
+ headerShowWeek?: boolean;
75
75
  autoScroll?: boolean;
76
76
  onChangeDate?: (e: any, currentData: any) => void;
77
77
  onChangeMonth?: (currentData: any) => void;
@@ -80,7 +80,7 @@ export type EventCalendarTimelineProps = {
80
80
  onChangeWeek?: (startDate: string, endDate: string) => void;
81
81
  onListRenderComplete?: () => void;
82
82
  onChangeAppearanceMode?: (mode: string) => void;
83
-
83
+ enableHeaderResize?: boolean;
84
84
 
85
85
 
86
86
  // modal dialog
@@ -161,7 +161,7 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
161
161
  cellAddBtnLabel,
162
162
  forwardAndBackFillDisabled,
163
163
  draggable,
164
- showWeek,
164
+ headerShowWeek,
165
165
  autoScroll,
166
166
  onChangeDate,
167
167
  onChangeMonth,
@@ -170,6 +170,7 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
170
170
  onChangeWeek,
171
171
  onListRenderComplete,
172
172
  onChangeAppearanceMode,
173
+ enableHeaderResize,
173
174
 
174
175
  //
175
176
  modalMaskOpacity,
@@ -272,13 +273,19 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
272
273
 
273
274
  // table grid
274
275
  const AUTO_SCROLL = autoScroll || false;
275
- const SHOW_WEEK = showWeek || false;
276
+ const HEADER_SHOW_WEEK = headerShowWeek || false;
276
277
  const BODY_DRAG = draggable || false;
278
+ const ENABLE_HEADER_RESIZE = enableHeaderResize || false;
277
279
  const tableGridRef = useRef<any>(null);
278
280
  const tableGridHeaderRef = useRef<any>(null);
279
281
  const scrollHeaderRef = useRef(null);
280
282
  const scrollBodyRef = useRef(null);
281
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);
282
289
 
283
290
  // Temporary date
284
291
  const [tempDate, setTempDate] = useState<string>('');
@@ -303,13 +310,13 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
303
310
  // Calculate CELL_MIN_W based on appearanceMode and tableCellMinWidth
304
311
  const CELL_MIN_W = useMemo(() => {
305
312
  if (typeof tableCellMinWidth === 'undefined') {
306
- return appearanceMode === 'week' ? 100 : 50;
313
+ return (headerShowWeek || false) ? 100 : 50;
307
314
  }
308
315
  if (typeof tableCellMinWidth === 'function') {
309
316
  return tableCellMinWidth(appearanceMode as 'week' | 'month');
310
317
  }
311
318
  return tableCellMinWidth;
312
- }, [tableCellMinWidth, appearanceMode]);
319
+ }, [tableCellMinWidth, appearanceMode, headerShowWeek]);
313
320
 
314
321
  const findMondayAndTruncate = (dates: string[]) => {
315
322
  const _res = dates.map((s: string) => new Date(s));
@@ -1255,7 +1262,7 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
1255
1262
 
1256
1263
  // week day
1257
1264
  const weekDay = item.week[i];
1258
- const _weekDayStr = SHOW_WEEK ? <span dangerouslySetInnerHTML={{
1265
+ const _weekDayStr = HEADER_SHOW_WEEK ? <span dangerouslySetInnerHTML={{
1259
1266
  __html: item.weekDisplay[i]
1260
1267
  }} /> : null;
1261
1268
 
@@ -1355,7 +1362,7 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
1355
1362
  'disabled': !isInteractive
1356
1363
  }
1357
1364
  )}
1358
- style={{ width: (CELL_MIN_W - 1) + 'px' }}
1365
+ style={{ width: (CELL_MIN_W as number - 1) + 'px' }}
1359
1366
  >
1360
1367
 
1361
1368
  {d}
@@ -1633,7 +1640,7 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
1633
1640
  'disabled': !isInteractive
1634
1641
  }
1635
1642
  )}
1636
- style={{ width: (CELL_MIN_W - 1) + 'px' }}
1643
+ style={{ width: (CELL_MIN_W as number - 1) + 'px' }}
1637
1644
  >
1638
1645
 
1639
1646
  {/*++++++++++++++++ EVENT ++++++++++++++++*/}
@@ -1865,18 +1872,26 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
1865
1872
  const tableGridEl: any = tableGridRef.current;
1866
1873
 
1867
1874
  // initialize cell height
1868
- const headerTitleTrElements = tableGridEl.querySelector('.custom-event-tl-table__datagrid-body__title tbody').getElementsByTagName('tr');
1869
- const trElements = tableGridEl.querySelector('.custom-event-tl-table__datagrid-body__content tbody').getElementsByTagName('tr');
1875
+ const headerTitleTbody = tableGridEl.querySelector('.custom-event-tl-table__datagrid-body__title tbody');
1876
+ const contentTbody = tableGridEl.querySelector('.custom-event-tl-table__datagrid-body__content tbody');
1877
+ if (!headerTitleTbody || !contentTbody) return;
1878
+
1879
+ const headerTitleTrElements = headerTitleTbody.getElementsByTagName('tr');
1880
+ const trElements = contentTbody.getElementsByTagName('tr');
1870
1881
 
1882
+ // Reset any previously set inline heights so we measure natural heights.
1871
1883
  for (let i = 0; i < headerTitleTrElements.length; i++) {
1884
+ // set to 'auto' (or remove inline style) to allow shrink
1885
+ headerTitleTrElements[i].style.height = 'auto';
1886
+ if (trElements[i]) trElements[i].style.height = 'auto';
1872
1887
 
1873
1888
  const targetElement = headerTitleTrElements[i].offsetHeight > trElements[i].offsetHeight ? headerTitleTrElements[i] : trElements[i];
1874
1889
  const tdOHeight = window.getComputedStyle(targetElement).height;
1875
1890
  headerTitleTrElements[i].style.height = tdOHeight;
1876
1891
  trElements[i].style.height = tdOHeight;
1877
-
1878
1892
  }
1879
1893
 
1894
+
1880
1895
  }
1881
1896
 
1882
1897
  function tableGridInit() {
@@ -1886,7 +1901,7 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
1886
1901
 
1887
1902
  const { forwardFillTotal, list: cellsList } = getCells();
1888
1903
  const tableGridEl: any = tableGridRef.current;
1889
- let _curCellMinWidth: number = CELL_MIN_W;
1904
+ let _curCellMinWidth: number = CELL_MIN_W as number;
1890
1905
  let _curColCount: number = FILL_BLANK_DATE_DISABLD ? days[month] : 7 * cellsList.length;
1891
1906
 
1892
1907
 
@@ -2114,7 +2129,6 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2114
2129
 
2115
2130
  }
2116
2131
 
2117
-
2118
2132
  function tableGridReset() {
2119
2133
  if (tableGridRef.current === null) return;
2120
2134
 
@@ -2146,13 +2160,7 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2146
2160
 
2147
2161
  }
2148
2162
 
2149
-
2150
-
2151
-
2152
- // ================================================================
2153
- //
2154
- // ================================================================
2155
- //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
2156
2164
  useEffect(() => {
2157
2165
  const years: number[] = [];
2158
2166
  for (let y = selectedYear - 10; y < selectedYear + 10; y++) {
@@ -2203,6 +2211,7 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2203
2211
  // Call a function when the list has been rendered completely
2204
2212
  onListRenderComplete?.();
2205
2213
 
2214
+
2206
2215
  }, [eventsValue, customTodayDate, appearanceMode]);
2207
2216
 
2208
2217
 
@@ -2222,6 +2231,102 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2222
2231
  }
2223
2232
 
2224
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
+
2225
2330
  return (
2226
2331
  <>
2227
2332
 
@@ -2409,6 +2514,9 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2409
2514
  <thead role="presentation">
2410
2515
  <tr role="row">
2411
2516
  <th role="columnheader">
2517
+ {/**
2518
+ * The width of the left column header is set using ".custom-event-tl-table__cell-cushion-headertitle".
2519
+ */}
2412
2520
  <div className="custom-event-tl-table__cell-cushion custom-event-tl-table__cell-cushion-headertitle">
2413
2521
  {tableListSectionTitle || ''}
2414
2522
  </div>
@@ -2423,7 +2531,18 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2423
2531
 
2424
2532
 
2425
2533
  </th>
2426
- <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>
2427
2546
  <th role="presentation">
2428
2547
  <div
2429
2548
  ref={scrollHeaderRef}
@@ -2499,12 +2618,20 @@ const EventCalendarTimeline = (props: EventCalendarTimelineProps) => {
2499
2618
 
2500
2619
 
2501
2620
  </td>
2621
+
2622
+ {/**
2623
+ * The dividing line between the left title column and the right content
2624
+ */}
2502
2625
  <td
2503
2626
  role="presentation"
2504
2627
  className={combinedCls(
2505
2628
  'custom-event-tl-table__timeline-divider',
2506
- tableListDividerClassName
2629
+ tableListDividerClassName,
2630
+ ENABLE_HEADER_RESIZE ? 'custom-event-tl-table__timeline-divider--resizable' : ''
2507
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}
2508
2635
  >
2509
2636
  <div></div>
2510
2637
  </td>
@@ -13,8 +13,6 @@ import {
13
13
  } from 'funda-utils/dist/cjs/bodyScrollLock';
14
14
 
15
15
 
16
-
17
-
18
16
  declare global {
19
17
  interface Window {
20
18
  curVideo?: any;