@liedekef/ftable 1.1.12 → 1.1.14

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 (79) hide show
  1. package/ftable.esm.js +124 -33
  2. package/ftable.js +124 -33
  3. package/ftable.min.js +3 -3
  4. package/ftable.umd.js +124 -33
  5. package/package.json +1 -1
  6. package/themes/basic/ftable_basic.css +75 -37
  7. package/themes/basic/ftable_basic.less +0 -61
  8. package/themes/basic/ftable_basic.min.css +1 -1
  9. package/themes/ftable_theme_base.less +87 -10
  10. package/themes/lightcolor/blue/ftable.css +76 -66
  11. package/themes/lightcolor/blue/ftable.min.css +1 -1
  12. package/themes/lightcolor/ftable_lightcolor_base.less +0 -71
  13. package/themes/lightcolor/gray/ftable.css +76 -66
  14. package/themes/lightcolor/gray/ftable.min.css +1 -1
  15. package/themes/lightcolor/green/ftable.css +76 -66
  16. package/themes/lightcolor/green/ftable.min.css +1 -1
  17. package/themes/lightcolor/orange/ftable.css +76 -66
  18. package/themes/lightcolor/orange/ftable.min.css +1 -1
  19. package/themes/lightcolor/red/ftable.css +76 -66
  20. package/themes/lightcolor/red/ftable.min.css +1 -1
  21. package/themes/metro/blue/ftable.css +76 -74
  22. package/themes/metro/blue/ftable.min.css +1 -1
  23. package/themes/metro/brown/ftable.css +76 -74
  24. package/themes/metro/brown/ftable.min.css +1 -1
  25. package/themes/metro/crimson/ftable.css +76 -74
  26. package/themes/metro/crimson/ftable.min.css +1 -1
  27. package/themes/metro/darkgray/ftable.css +76 -74
  28. package/themes/metro/darkgray/ftable.min.css +1 -1
  29. package/themes/metro/darkorange/ftable.css +76 -74
  30. package/themes/metro/darkorange/ftable.min.css +1 -1
  31. package/themes/metro/ftable_metro_base.less +0 -96
  32. package/themes/metro/green/ftable.css +76 -74
  33. package/themes/metro/green/ftable.min.css +1 -1
  34. package/themes/metro/lightgray/ftable.css +76 -74
  35. package/themes/metro/lightgray/ftable.min.css +1 -1
  36. package/themes/metro/pink/ftable.css +76 -74
  37. package/themes/metro/pink/ftable.min.css +1 -1
  38. package/themes/metro/purple/ftable.css +76 -74
  39. package/themes/metro/purple/ftable.min.css +1 -1
  40. package/themes/metro/red/ftable.css +76 -74
  41. package/themes/metro/red/ftable.min.css +1 -1
  42. package/themes/basic/clone.png +0 -0
  43. package/themes/basic/close.png +0 -0
  44. package/themes/basic/column-asc.png +0 -0
  45. package/themes/basic/column-desc.png +0 -0
  46. package/themes/basic/column-sortable.png +0 -0
  47. package/themes/basic/delete.png +0 -0
  48. package/themes/basic/edit.png +0 -0
  49. package/themes/lightcolor/add.png +0 -0
  50. package/themes/lightcolor/blue/loading.gif +0 -0
  51. package/themes/lightcolor/clone.png +0 -0
  52. package/themes/lightcolor/close.png +0 -0
  53. package/themes/lightcolor/column-asc.png +0 -0
  54. package/themes/lightcolor/column-desc.png +0 -0
  55. package/themes/lightcolor/column-sortable.png +0 -0
  56. package/themes/lightcolor/delete.png +0 -0
  57. package/themes/lightcolor/edit.png +0 -0
  58. package/themes/lightcolor/gray/loading.gif +0 -0
  59. package/themes/lightcolor/green/loading.gif +0 -0
  60. package/themes/lightcolor/orange/loading.gif +0 -0
  61. package/themes/lightcolor/red/loading.gif +0 -0
  62. package/themes/metro/add.png +0 -0
  63. package/themes/metro/blue/loading.gif +0 -0
  64. package/themes/metro/brown/loading.gif +0 -0
  65. package/themes/metro/clone.png +0 -0
  66. package/themes/metro/close.png +0 -0
  67. package/themes/metro/column-asc.png +0 -0
  68. package/themes/metro/column-desc.png +0 -0
  69. package/themes/metro/column-sortable.png +0 -0
  70. package/themes/metro/crimson/loading.gif +0 -0
  71. package/themes/metro/darkgray/loading.gif +0 -0
  72. package/themes/metro/darkorange/loading.gif +0 -0
  73. package/themes/metro/delete.png +0 -0
  74. package/themes/metro/edit.png +0 -0
  75. package/themes/metro/green/loading.gif +0 -0
  76. package/themes/metro/lightgray/loading.gif +0 -0
  77. package/themes/metro/pink/loading.gif +0 -0
  78. package/themes/metro/purple/loading.gif +0 -0
  79. package/themes/metro/red/loading.gif +0 -0
package/ftable.esm.js CHANGED
@@ -1497,6 +1497,50 @@ class FTable extends FTableEventEmitter {
1497
1497
 
1498
1498
  // Add essential CSS if not already present
1499
1499
  //this.addEssentialCSS();
1500
+
1501
+ // now make sure all tables have a % width
1502
+ this.initColumnWidths();
1503
+ }
1504
+
1505
+ initColumnWidths() {
1506
+ const visibleFields = this.columnList.filter(fieldName => {
1507
+ const field = this.options.fields[fieldName];
1508
+ return field.visibility !== 'hidden';
1509
+ });
1510
+
1511
+ const count = visibleFields.length;
1512
+ visibleFields.forEach(fieldName => {
1513
+ const field = this.options.fields[fieldName];
1514
+ // Use configured width or equal distribution
1515
+ //field.width = field.width || `${(100 / count).toFixed(2)}%`;
1516
+ field.width = field.width || `${(100 / count)}%`;
1517
+ });
1518
+ }
1519
+
1520
+ normalizeColumnWidths() {
1521
+ const container = this.elements.mainContainer;
1522
+ const visibleHeaders = this.columnList
1523
+ .map(fieldName => ({
1524
+ th: this.elements.table.querySelector(`[data-field-name="${fieldName}"]`),
1525
+ field: this.options.fields[fieldName]
1526
+ }))
1527
+ .filter(item => item.th && item.field.visibility !== 'hidden');
1528
+
1529
+ if (visibleHeaders.length === 0) return;
1530
+
1531
+ const totalContainerWidth = container.offsetWidth;
1532
+ let totalPercent = 0;
1533
+
1534
+ visibleHeaders.forEach(item => {
1535
+ const widthPct = (item.th.offsetWidth / totalContainerWidth) * 100;
1536
+ //item.field.width = `${widthPct.toFixed(2)}%`;
1537
+ item.field.width = `${widthPct}%`;
1538
+ item.th.style.width = item.field.width;
1539
+ totalPercent += widthPct;
1540
+ });
1541
+
1542
+ // Optional: adjust for rounding drift
1543
+ // (not critical, but can help)
1500
1544
  }
1501
1545
 
1502
1546
  parseDefaultSorting(sortStr) {
@@ -1831,7 +1875,7 @@ class FTable extends FTableEventEmitter {
1831
1875
  container.setAttribute('title', field.tooltip);
1832
1876
  }
1833
1877
 
1834
- FTableDOMHelper.create('span', {
1878
+ const textHeader = FTableDOMHelper.create('span', {
1835
1879
  className: 'ftable-column-header-text',
1836
1880
  text: field.title || fieldName,
1837
1881
  parent: container
@@ -1839,6 +1883,10 @@ class FTable extends FTableEventEmitter {
1839
1883
 
1840
1884
  // Make sortable if enabled
1841
1885
  if (this.options.sorting && field.sorting !== false) {
1886
+ // Add some empty spaces after the text so the background icon has room next to it
1887
+ // one could play with css and ::after, but then the width calculation of columns borks, resize bar is off etc ...
1888
+ //textHeader.innerHTML += '     ';
1889
+ FTableDOMHelper.addClass(textHeader, 'ftable-sortable-text'); // Add class for spacing
1842
1890
  FTableDOMHelper.addClass(th, 'ftable-column-header-sortable');
1843
1891
  th.addEventListener('click', (e) => {
1844
1892
  e.preventDefault();
@@ -1897,7 +1945,10 @@ class FTable extends FTableEventEmitter {
1897
1945
 
1898
1946
  // Add empty cell for selecting column if enabled
1899
1947
  if (this.options.selecting && this.options.selectingCheckboxes) {
1900
- FTableDOMHelper.create('th', { parent: searchRow });
1948
+ FTableDOMHelper.create('th', {
1949
+ className: 'ftable-toolbarsearch-column-header',
1950
+ parent: searchRow
1951
+ });
1901
1952
  }
1902
1953
 
1903
1954
  // Add search input cells for data columns
@@ -2040,7 +2091,7 @@ class FTable extends FTableEventEmitter {
2040
2091
  if (this.options.toolbarsearch && this.options.toolbarreset) {
2041
2092
  // Add reset button cell
2042
2093
  const resetTh = FTableDOMHelper.create('th', {
2043
- className: 'ftable-toolbarsearch-reset',
2094
+ className: 'ftable-toolbarsearch-column-header ftable-toolbarsearch-reset',
2044
2095
  parent: searchRow
2045
2096
  });
2046
2097
 
@@ -2157,8 +2208,18 @@ class FTable extends FTableEventEmitter {
2157
2208
  this.load();
2158
2209
  }
2159
2210
 
2211
+ getNextVisibleHeader(th) {
2212
+ const headers = Array.from(this.elements.table.querySelectorAll('thead th:not(.ftable-command-column-header, .ftable-toolbarsearch-column-header)'));
2213
+ const index = headers.indexOf(th);
2214
+ for (let i = index + 1; i < headers.length; i++) {
2215
+ if (headers[i].offsetParent !== null) { // visible
2216
+ return headers[i];
2217
+ }
2218
+ }
2219
+ return null;
2220
+ }
2221
+
2160
2222
  makeColumnResizable(th, container) {
2161
- // Create resize bar if it doesn't exist
2162
2223
  if (!this.elements.resizeBar) {
2163
2224
  this.elements.resizeBar = FTableDOMHelper.create('div', {
2164
2225
  className: 'ftable-column-resize-bar',
@@ -2175,59 +2236,88 @@ class FTable extends FTableEventEmitter {
2175
2236
  let isResizing = false;
2176
2237
  let startX = 0;
2177
2238
  let startWidth = 0;
2239
+ let containerRect;
2240
+ let nextTh = null;
2241
+ let nextStartWidth = 0;
2242
+ let nextField = null;
2178
2243
 
2179
2244
  resizeHandler.addEventListener('mousedown', (e) => {
2180
2245
  e.preventDefault();
2181
2246
  e.stopPropagation();
2182
-
2247
+
2183
2248
  isResizing = true;
2249
+
2250
+ // Capture layout
2251
+ containerRect = this.elements.mainContainer.getBoundingClientRect();
2184
2252
  startX = e.clientX;
2185
2253
  startWidth = th.offsetWidth;
2186
-
2187
- // Show resize bar
2188
- const rect = th.getBoundingClientRect();
2189
- const containerRect = this.elements.mainContainer.getBoundingClientRect();
2190
-
2191
- this.elements.resizeBar.style.left = (rect.right - containerRect.left) + 'px';
2192
- this.elements.resizeBar.style.top = (rect.top - containerRect.top) + 'px';
2254
+
2255
+ // Find next visible column
2256
+ nextTh = this.getNextVisibleHeader(th);
2257
+ if (nextTh) {
2258
+ nextStartWidth = nextTh.offsetWidth;
2259
+ const fieldName = nextTh.dataset.fieldName;
2260
+ nextField = this.options.fields[fieldName];
2261
+ } else {
2262
+ return;
2263
+ }
2264
+
2265
+ // Position resize bar
2266
+ const thRect = th.getBoundingClientRect();
2267
+ this.elements.resizeBar.style.left = (thRect.right - containerRect.left) + 'px';
2268
+ this.elements.resizeBar.style.top = (thRect.top - containerRect.top) + 'px';
2193
2269
  this.elements.resizeBar.style.height = this.elements.table.offsetHeight + 'px';
2270
+
2194
2271
  FTableDOMHelper.show(this.elements.resizeBar);
2195
-
2272
+
2196
2273
  document.addEventListener('mousemove', handleMouseMove);
2197
2274
  document.addEventListener('mouseup', handleMouseUp);
2198
2275
  });
2199
2276
 
2200
2277
  const handleMouseMove = (e) => {
2201
2278
  if (!isResizing) return;
2202
-
2203
- const diff = e.clientX - startX;
2204
- const newWidth = Math.max(50, startWidth + diff); // Minimum 50px width
2205
-
2206
- // Update resize bar position
2207
- const containerRect = this.elements.mainContainer.getBoundingClientRect();
2279
+
2280
+ // Move resize bar with mouse
2208
2281
  this.elements.resizeBar.style.left = (e.clientX - containerRect.left) + 'px';
2209
2282
  };
2210
2283
 
2211
2284
  const handleMouseUp = (e) => {
2212
2285
  if (!isResizing) return;
2213
-
2214
2286
  isResizing = false;
2215
- const diff = e.clientX - startX;
2216
- const newWidth = Math.max(50, startWidth + diff);
2217
-
2218
- // Apply new width
2219
- th.style.width = newWidth + 'px';
2220
-
2221
- // Hide resize bar
2222
- FTableDOMHelper.hide(this.elements.resizeBar);
2223
-
2224
- document.removeEventListener('mousemove', handleMouseMove);
2225
- document.removeEventListener('mouseup', handleMouseUp);
2226
-
2227
- // Save column width preference if enabled
2287
+
2288
+ const diff = e.clientX - startX; // px
2289
+ const totalWidth = containerRect.width;
2290
+
2291
+ // Current column new width in px
2292
+ const newCurrentPx = Math.max(50, startWidth + diff);
2293
+ const newCurrentPct = (newCurrentPx / totalWidth) * 100;
2294
+
2295
+ // Next column adjustment
2296
+ if (nextTh) {
2297
+ const newNextPx = Math.max(50, nextStartWidth - diff); // opposite delta
2298
+ const newNextPct = (newNextPx / totalWidth) * 100;
2299
+
2300
+ // Apply to next
2301
+ nextField.width = `${newNextPct.toFixed(2)}%`;
2302
+ nextTh.style.width = nextField.width;
2303
+ }
2304
+
2305
+ // Apply to current
2306
+ const field = this.options.fields[th.dataset.fieldName];
2307
+ field.width = `${newCurrentPct.toFixed(2)}%`;
2308
+ th.style.width = field.width;
2309
+
2310
+ // Final normalization (optional, but safe)
2311
+ this.normalizeColumnWidths();
2312
+
2313
+ // Save
2228
2314
  if (this.options.saveUserPreferences) {
2229
2315
  this.saveColumnSettings();
2230
2316
  }
2317
+
2318
+ FTableDOMHelper.hide(this.elements.resizeBar);
2319
+ document.removeEventListener('mousemove', handleMouseMove);
2320
+ document.removeEventListener('mouseup', handleMouseUp);
2231
2321
  };
2232
2322
  }
2233
2323
 
@@ -2814,6 +2904,7 @@ class FTable extends FTableEventEmitter {
2814
2904
  // Update sorting display
2815
2905
  this.renderSortingInfo();
2816
2906
 
2907
+ this.normalizeColumnWidths();
2817
2908
  }
2818
2909
 
2819
2910
  buildLoadParams() {
package/ftable.js CHANGED
@@ -1502,6 +1502,50 @@ class FTable extends FTableEventEmitter {
1502
1502
 
1503
1503
  // Add essential CSS if not already present
1504
1504
  //this.addEssentialCSS();
1505
+
1506
+ // now make sure all tables have a % width
1507
+ this.initColumnWidths();
1508
+ }
1509
+
1510
+ initColumnWidths() {
1511
+ const visibleFields = this.columnList.filter(fieldName => {
1512
+ const field = this.options.fields[fieldName];
1513
+ return field.visibility !== 'hidden';
1514
+ });
1515
+
1516
+ const count = visibleFields.length;
1517
+ visibleFields.forEach(fieldName => {
1518
+ const field = this.options.fields[fieldName];
1519
+ // Use configured width or equal distribution
1520
+ //field.width = field.width || `${(100 / count).toFixed(2)}%`;
1521
+ field.width = field.width || `${(100 / count)}%`;
1522
+ });
1523
+ }
1524
+
1525
+ normalizeColumnWidths() {
1526
+ const container = this.elements.mainContainer;
1527
+ const visibleHeaders = this.columnList
1528
+ .map(fieldName => ({
1529
+ th: this.elements.table.querySelector(`[data-field-name="${fieldName}"]`),
1530
+ field: this.options.fields[fieldName]
1531
+ }))
1532
+ .filter(item => item.th && item.field.visibility !== 'hidden');
1533
+
1534
+ if (visibleHeaders.length === 0) return;
1535
+
1536
+ const totalContainerWidth = container.offsetWidth;
1537
+ let totalPercent = 0;
1538
+
1539
+ visibleHeaders.forEach(item => {
1540
+ const widthPct = (item.th.offsetWidth / totalContainerWidth) * 100;
1541
+ //item.field.width = `${widthPct.toFixed(2)}%`;
1542
+ item.field.width = `${widthPct}%`;
1543
+ item.th.style.width = item.field.width;
1544
+ totalPercent += widthPct;
1545
+ });
1546
+
1547
+ // Optional: adjust for rounding drift
1548
+ // (not critical, but can help)
1505
1549
  }
1506
1550
 
1507
1551
  parseDefaultSorting(sortStr) {
@@ -1836,7 +1880,7 @@ class FTable extends FTableEventEmitter {
1836
1880
  container.setAttribute('title', field.tooltip);
1837
1881
  }
1838
1882
 
1839
- FTableDOMHelper.create('span', {
1883
+ const textHeader = FTableDOMHelper.create('span', {
1840
1884
  className: 'ftable-column-header-text',
1841
1885
  text: field.title || fieldName,
1842
1886
  parent: container
@@ -1844,6 +1888,10 @@ class FTable extends FTableEventEmitter {
1844
1888
 
1845
1889
  // Make sortable if enabled
1846
1890
  if (this.options.sorting && field.sorting !== false) {
1891
+ // Add some empty spaces after the text so the background icon has room next to it
1892
+ // one could play with css and ::after, but then the width calculation of columns borks, resize bar is off etc ...
1893
+ //textHeader.innerHTML += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
1894
+ FTableDOMHelper.addClass(textHeader, 'ftable-sortable-text'); // Add class for spacing
1847
1895
  FTableDOMHelper.addClass(th, 'ftable-column-header-sortable');
1848
1896
  th.addEventListener('click', (e) => {
1849
1897
  e.preventDefault();
@@ -1902,7 +1950,10 @@ class FTable extends FTableEventEmitter {
1902
1950
 
1903
1951
  // Add empty cell for selecting column if enabled
1904
1952
  if (this.options.selecting && this.options.selectingCheckboxes) {
1905
- FTableDOMHelper.create('th', { parent: searchRow });
1953
+ FTableDOMHelper.create('th', {
1954
+ className: 'ftable-toolbarsearch-column-header',
1955
+ parent: searchRow
1956
+ });
1906
1957
  }
1907
1958
 
1908
1959
  // Add search input cells for data columns
@@ -2045,7 +2096,7 @@ class FTable extends FTableEventEmitter {
2045
2096
  if (this.options.toolbarsearch && this.options.toolbarreset) {
2046
2097
  // Add reset button cell
2047
2098
  const resetTh = FTableDOMHelper.create('th', {
2048
- className: 'ftable-toolbarsearch-reset',
2099
+ className: 'ftable-toolbarsearch-column-header ftable-toolbarsearch-reset',
2049
2100
  parent: searchRow
2050
2101
  });
2051
2102
 
@@ -2162,8 +2213,18 @@ class FTable extends FTableEventEmitter {
2162
2213
  this.load();
2163
2214
  }
2164
2215
 
2216
+ getNextVisibleHeader(th) {
2217
+ const headers = Array.from(this.elements.table.querySelectorAll('thead th:not(.ftable-command-column-header, .ftable-toolbarsearch-column-header)'));
2218
+ const index = headers.indexOf(th);
2219
+ for (let i = index + 1; i < headers.length; i++) {
2220
+ if (headers[i].offsetParent !== null) { // visible
2221
+ return headers[i];
2222
+ }
2223
+ }
2224
+ return null;
2225
+ }
2226
+
2165
2227
  makeColumnResizable(th, container) {
2166
- // Create resize bar if it doesn't exist
2167
2228
  if (!this.elements.resizeBar) {
2168
2229
  this.elements.resizeBar = FTableDOMHelper.create('div', {
2169
2230
  className: 'ftable-column-resize-bar',
@@ -2180,59 +2241,88 @@ class FTable extends FTableEventEmitter {
2180
2241
  let isResizing = false;
2181
2242
  let startX = 0;
2182
2243
  let startWidth = 0;
2244
+ let containerRect;
2245
+ let nextTh = null;
2246
+ let nextStartWidth = 0;
2247
+ let nextField = null;
2183
2248
 
2184
2249
  resizeHandler.addEventListener('mousedown', (e) => {
2185
2250
  e.preventDefault();
2186
2251
  e.stopPropagation();
2187
-
2252
+
2188
2253
  isResizing = true;
2254
+
2255
+ // Capture layout
2256
+ containerRect = this.elements.mainContainer.getBoundingClientRect();
2189
2257
  startX = e.clientX;
2190
2258
  startWidth = th.offsetWidth;
2191
-
2192
- // Show resize bar
2193
- const rect = th.getBoundingClientRect();
2194
- const containerRect = this.elements.mainContainer.getBoundingClientRect();
2195
-
2196
- this.elements.resizeBar.style.left = (rect.right - containerRect.left) + 'px';
2197
- this.elements.resizeBar.style.top = (rect.top - containerRect.top) + 'px';
2259
+
2260
+ // Find next visible column
2261
+ nextTh = this.getNextVisibleHeader(th);
2262
+ if (nextTh) {
2263
+ nextStartWidth = nextTh.offsetWidth;
2264
+ const fieldName = nextTh.dataset.fieldName;
2265
+ nextField = this.options.fields[fieldName];
2266
+ } else {
2267
+ return;
2268
+ }
2269
+
2270
+ // Position resize bar
2271
+ const thRect = th.getBoundingClientRect();
2272
+ this.elements.resizeBar.style.left = (thRect.right - containerRect.left) + 'px';
2273
+ this.elements.resizeBar.style.top = (thRect.top - containerRect.top) + 'px';
2198
2274
  this.elements.resizeBar.style.height = this.elements.table.offsetHeight + 'px';
2275
+
2199
2276
  FTableDOMHelper.show(this.elements.resizeBar);
2200
-
2277
+
2201
2278
  document.addEventListener('mousemove', handleMouseMove);
2202
2279
  document.addEventListener('mouseup', handleMouseUp);
2203
2280
  });
2204
2281
 
2205
2282
  const handleMouseMove = (e) => {
2206
2283
  if (!isResizing) return;
2207
-
2208
- const diff = e.clientX - startX;
2209
- const newWidth = Math.max(50, startWidth + diff); // Minimum 50px width
2210
-
2211
- // Update resize bar position
2212
- const containerRect = this.elements.mainContainer.getBoundingClientRect();
2284
+
2285
+ // Move resize bar with mouse
2213
2286
  this.elements.resizeBar.style.left = (e.clientX - containerRect.left) + 'px';
2214
2287
  };
2215
2288
 
2216
2289
  const handleMouseUp = (e) => {
2217
2290
  if (!isResizing) return;
2218
-
2219
2291
  isResizing = false;
2220
- const diff = e.clientX - startX;
2221
- const newWidth = Math.max(50, startWidth + diff);
2222
-
2223
- // Apply new width
2224
- th.style.width = newWidth + 'px';
2225
-
2226
- // Hide resize bar
2227
- FTableDOMHelper.hide(this.elements.resizeBar);
2228
-
2229
- document.removeEventListener('mousemove', handleMouseMove);
2230
- document.removeEventListener('mouseup', handleMouseUp);
2231
-
2232
- // Save column width preference if enabled
2292
+
2293
+ const diff = e.clientX - startX; // px
2294
+ const totalWidth = containerRect.width;
2295
+
2296
+ // Current column new width in px
2297
+ const newCurrentPx = Math.max(50, startWidth + diff);
2298
+ const newCurrentPct = (newCurrentPx / totalWidth) * 100;
2299
+
2300
+ // Next column adjustment
2301
+ if (nextTh) {
2302
+ const newNextPx = Math.max(50, nextStartWidth - diff); // opposite delta
2303
+ const newNextPct = (newNextPx / totalWidth) * 100;
2304
+
2305
+ // Apply to next
2306
+ nextField.width = `${newNextPct.toFixed(2)}%`;
2307
+ nextTh.style.width = nextField.width;
2308
+ }
2309
+
2310
+ // Apply to current
2311
+ const field = this.options.fields[th.dataset.fieldName];
2312
+ field.width = `${newCurrentPct.toFixed(2)}%`;
2313
+ th.style.width = field.width;
2314
+
2315
+ // Final normalization (optional, but safe)
2316
+ this.normalizeColumnWidths();
2317
+
2318
+ // Save
2233
2319
  if (this.options.saveUserPreferences) {
2234
2320
  this.saveColumnSettings();
2235
2321
  }
2322
+
2323
+ FTableDOMHelper.hide(this.elements.resizeBar);
2324
+ document.removeEventListener('mousemove', handleMouseMove);
2325
+ document.removeEventListener('mouseup', handleMouseUp);
2236
2326
  };
2237
2327
  }
2238
2328
 
@@ -2819,6 +2909,7 @@ class FTable extends FTableEventEmitter {
2819
2909
  // Update sorting display
2820
2910
  this.renderSortingInfo();
2821
2911
 
2912
+ this.normalizeColumnWidths();
2822
2913
  }
2823
2914
 
2824
2915
  buildLoadParams() {