@liedekef/ftable 1.1.11 → 1.1.13

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 (38) hide show
  1. package/ftable.esm.js +161 -81
  2. package/ftable.js +161 -81
  3. package/ftable.min.js +3 -3
  4. package/ftable.umd.js +161 -81
  5. package/package.json +1 -1
  6. package/themes/basic/ftable_basic.css +1 -0
  7. package/themes/basic/ftable_basic.min.css +1 -1
  8. package/themes/ftable_theme_base.less +1 -0
  9. package/themes/lightcolor/blue/ftable.css +1 -0
  10. package/themes/lightcolor/blue/ftable.min.css +1 -1
  11. package/themes/lightcolor/gray/ftable.css +1 -0
  12. package/themes/lightcolor/gray/ftable.min.css +1 -1
  13. package/themes/lightcolor/green/ftable.css +1 -0
  14. package/themes/lightcolor/green/ftable.min.css +1 -1
  15. package/themes/lightcolor/orange/ftable.css +1 -0
  16. package/themes/lightcolor/orange/ftable.min.css +1 -1
  17. package/themes/lightcolor/red/ftable.css +1 -0
  18. package/themes/lightcolor/red/ftable.min.css +1 -1
  19. package/themes/metro/blue/ftable.css +1 -0
  20. package/themes/metro/blue/ftable.min.css +1 -1
  21. package/themes/metro/brown/ftable.css +1 -0
  22. package/themes/metro/brown/ftable.min.css +1 -1
  23. package/themes/metro/crimson/ftable.css +1 -0
  24. package/themes/metro/crimson/ftable.min.css +1 -1
  25. package/themes/metro/darkgray/ftable.css +1 -0
  26. package/themes/metro/darkgray/ftable.min.css +1 -1
  27. package/themes/metro/darkorange/ftable.css +1 -0
  28. package/themes/metro/darkorange/ftable.min.css +1 -1
  29. package/themes/metro/green/ftable.css +1 -0
  30. package/themes/metro/green/ftable.min.css +1 -1
  31. package/themes/metro/lightgray/ftable.css +1 -0
  32. package/themes/metro/lightgray/ftable.min.css +1 -1
  33. package/themes/metro/pink/ftable.css +1 -0
  34. package/themes/metro/pink/ftable.min.css +1 -1
  35. package/themes/metro/purple/ftable.css +1 -0
  36. package/themes/metro/purple/ftable.min.css +1 -1
  37. package/themes/metro/red/ftable.css +1 -0
  38. package/themes/metro/red/ftable.min.css +1 -1
package/ftable.esm.js CHANGED
@@ -157,7 +157,11 @@ class FTableDOMHelper {
157
157
  if (options.className) {
158
158
  element.className = options.className;
159
159
  }
160
-
160
+
161
+ if (options.style) {
162
+ element.style.cssText = options.style;
163
+ }
164
+
161
165
  if (options.attributes) {
162
166
  Object.entries(options.attributes).forEach(([key, value]) => {
163
167
  element.setAttribute(key, value);
@@ -1493,6 +1497,50 @@ class FTable extends FTableEventEmitter {
1493
1497
 
1494
1498
  // Add essential CSS if not already present
1495
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)
1496
1544
  }
1497
1545
 
1498
1546
  parseDefaultSorting(sortStr) {
@@ -1655,25 +1703,13 @@ class FTable extends FTableEventEmitter {
1655
1703
  field.edit = true;
1656
1704
  field.type = 'hidden';
1657
1705
  }
1658
- if (!field.hasOwnProperty('visibility')) {
1659
- field.visibility = 'hidden';
1660
- }
1706
+ field.visibility = field.visibility ?? 'visible';
1661
1707
  } else {
1662
- if (field.create === undefined) {
1663
- field.create = true;
1664
- }
1665
- if (field.edit === undefined) {
1666
- field.edit = true;
1667
- }
1668
- if (field.list === undefined) {
1669
- field.list = true;
1670
- }
1671
- if (field.sorting === undefined) {
1672
- field.sorting = true;
1673
- }
1674
- if (!field.hasOwnProperty('visibility')) {
1675
- field.visibility = 'visible';
1676
- }
1708
+ field.create = field.create ?? true;
1709
+ field.edit = field.edit ?? true;
1710
+ field.list = field.list ?? true;
1711
+ field.sorting = field.sorting ?? true;
1712
+ field.visibility = field.visibility ?? 'visible';
1677
1713
  }
1678
1714
  });
1679
1715
 
@@ -1686,7 +1722,7 @@ class FTable extends FTableEventEmitter {
1686
1722
  // Find key field
1687
1723
  this.keyField = this.fieldList.find(name => this.options.fields[name].key === true);
1688
1724
  if (!this.keyField) {
1689
- this.logger.warn('No key field defined');
1725
+ this.logger.info('No key field defined');
1690
1726
  }
1691
1727
  }
1692
1728
 
@@ -1862,7 +1898,7 @@ class FTable extends FTableEventEmitter {
1862
1898
  }
1863
1899
 
1864
1900
  // Hide column if needed
1865
- if (field.visibility === 'hidden') {
1901
+ if (field.visibility === 'hidden' || field.visibility === 'separator') {
1866
1902
  FTableDOMHelper.hide(th);
1867
1903
  }
1868
1904
  });
@@ -1905,7 +1941,10 @@ class FTable extends FTableEventEmitter {
1905
1941
 
1906
1942
  // Add empty cell for selecting column if enabled
1907
1943
  if (this.options.selecting && this.options.selectingCheckboxes) {
1908
- FTableDOMHelper.create('th', { parent: searchRow });
1944
+ FTableDOMHelper.create('th', {
1945
+ className: 'ftable-toolbarsearch-column-header',
1946
+ parent: searchRow
1947
+ });
1909
1948
  }
1910
1949
 
1911
1950
  // Add search input cells for data columns
@@ -2040,7 +2079,7 @@ class FTable extends FTableEventEmitter {
2040
2079
  }
2041
2080
 
2042
2081
  // Hide search cell if column is hidden
2043
- if (field.visibility === 'hidden') {
2082
+ if (field.visibility === 'hidden' || field.visibility === 'separator') {
2044
2083
  FTableDOMHelper.hide(th);
2045
2084
  }
2046
2085
  }
@@ -2048,7 +2087,7 @@ class FTable extends FTableEventEmitter {
2048
2087
  if (this.options.toolbarsearch && this.options.toolbarreset) {
2049
2088
  // Add reset button cell
2050
2089
  const resetTh = FTableDOMHelper.create('th', {
2051
- className: 'ftable-toolbarsearch-reset',
2090
+ className: 'ftable-toolbarsearch-column-header ftable-toolbarsearch-reset',
2052
2091
  parent: searchRow
2053
2092
  });
2054
2093
 
@@ -2165,8 +2204,18 @@ class FTable extends FTableEventEmitter {
2165
2204
  this.load();
2166
2205
  }
2167
2206
 
2207
+ getNextVisibleHeader(th) {
2208
+ const headers = Array.from(this.elements.table.querySelectorAll('thead th:not(.ftable-command-column-header, .ftable-toolbarsearch-column-header)'));
2209
+ const index = headers.indexOf(th);
2210
+ for (let i = index + 1; i < headers.length; i++) {
2211
+ if (headers[i].offsetParent !== null) { // visible
2212
+ return headers[i];
2213
+ }
2214
+ }
2215
+ return null;
2216
+ }
2217
+
2168
2218
  makeColumnResizable(th, container) {
2169
- // Create resize bar if it doesn't exist
2170
2219
  if (!this.elements.resizeBar) {
2171
2220
  this.elements.resizeBar = FTableDOMHelper.create('div', {
2172
2221
  className: 'ftable-column-resize-bar',
@@ -2183,59 +2232,88 @@ class FTable extends FTableEventEmitter {
2183
2232
  let isResizing = false;
2184
2233
  let startX = 0;
2185
2234
  let startWidth = 0;
2235
+ let containerRect;
2236
+ let nextTh = null;
2237
+ let nextStartWidth = 0;
2238
+ let nextField = null;
2186
2239
 
2187
2240
  resizeHandler.addEventListener('mousedown', (e) => {
2188
2241
  e.preventDefault();
2189
2242
  e.stopPropagation();
2190
-
2243
+
2191
2244
  isResizing = true;
2245
+
2246
+ // Capture layout
2247
+ containerRect = this.elements.mainContainer.getBoundingClientRect();
2192
2248
  startX = e.clientX;
2193
2249
  startWidth = th.offsetWidth;
2194
-
2195
- // Show resize bar
2196
- const rect = th.getBoundingClientRect();
2197
- const containerRect = this.elements.mainContainer.getBoundingClientRect();
2198
-
2199
- this.elements.resizeBar.style.left = (rect.right - containerRect.left) + 'px';
2200
- this.elements.resizeBar.style.top = (rect.top - containerRect.top) + 'px';
2250
+
2251
+ // Find next visible column
2252
+ nextTh = this.getNextVisibleHeader(th);
2253
+ if (nextTh) {
2254
+ nextStartWidth = nextTh.offsetWidth;
2255
+ const fieldName = nextTh.dataset.fieldName;
2256
+ nextField = this.options.fields[fieldName];
2257
+ } else {
2258
+ return;
2259
+ }
2260
+
2261
+ // Position resize bar
2262
+ const thRect = th.getBoundingClientRect();
2263
+ this.elements.resizeBar.style.left = (thRect.right - containerRect.left) + 'px';
2264
+ this.elements.resizeBar.style.top = (thRect.top - containerRect.top) + 'px';
2201
2265
  this.elements.resizeBar.style.height = this.elements.table.offsetHeight + 'px';
2266
+
2202
2267
  FTableDOMHelper.show(this.elements.resizeBar);
2203
-
2268
+
2204
2269
  document.addEventListener('mousemove', handleMouseMove);
2205
2270
  document.addEventListener('mouseup', handleMouseUp);
2206
2271
  });
2207
2272
 
2208
2273
  const handleMouseMove = (e) => {
2209
2274
  if (!isResizing) return;
2210
-
2211
- const diff = e.clientX - startX;
2212
- const newWidth = Math.max(50, startWidth + diff); // Minimum 50px width
2213
-
2214
- // Update resize bar position
2215
- const containerRect = this.elements.mainContainer.getBoundingClientRect();
2275
+
2276
+ // Move resize bar with mouse
2216
2277
  this.elements.resizeBar.style.left = (e.clientX - containerRect.left) + 'px';
2217
2278
  };
2218
2279
 
2219
2280
  const handleMouseUp = (e) => {
2220
2281
  if (!isResizing) return;
2221
-
2222
2282
  isResizing = false;
2223
- const diff = e.clientX - startX;
2224
- const newWidth = Math.max(50, startWidth + diff);
2225
-
2226
- // Apply new width
2227
- th.style.width = newWidth + 'px';
2228
-
2229
- // Hide resize bar
2230
- FTableDOMHelper.hide(this.elements.resizeBar);
2231
-
2232
- document.removeEventListener('mousemove', handleMouseMove);
2233
- document.removeEventListener('mouseup', handleMouseUp);
2234
-
2235
- // Save column width preference if enabled
2283
+
2284
+ const diff = e.clientX - startX; // px
2285
+ const totalWidth = containerRect.width;
2286
+
2287
+ // Current column new width in px
2288
+ const newCurrentPx = Math.max(50, startWidth + diff);
2289
+ const newCurrentPct = (newCurrentPx / totalWidth) * 100;
2290
+
2291
+ // Next column adjustment
2292
+ if (nextTh) {
2293
+ const newNextPx = Math.max(50, nextStartWidth - diff); // opposite delta
2294
+ const newNextPct = (newNextPx / totalWidth) * 100;
2295
+
2296
+ // Apply to next
2297
+ nextField.width = `${newNextPct.toFixed(2)}%`;
2298
+ nextTh.style.width = nextField.width;
2299
+ }
2300
+
2301
+ // Apply to current
2302
+ const field = this.options.fields[th.dataset.fieldName];
2303
+ field.width = `${newCurrentPct.toFixed(2)}%`;
2304
+ th.style.width = field.width;
2305
+
2306
+ // Final normalization (optional, but safe)
2307
+ this.normalizeColumnWidths();
2308
+
2309
+ // Save
2236
2310
  if (this.options.saveUserPreferences) {
2237
2311
  this.saveColumnSettings();
2238
2312
  }
2313
+
2314
+ FTableDOMHelper.hide(this.elements.resizeBar);
2315
+ document.removeEventListener('mousemove', handleMouseMove);
2316
+ document.removeEventListener('mouseup', handleMouseUp);
2239
2317
  };
2240
2318
  }
2241
2319
 
@@ -2466,8 +2544,8 @@ class FTable extends FTableEventEmitter {
2466
2544
  this.subscribeOptionEvents();
2467
2545
 
2468
2546
  // Add toolbar buttons
2469
- this.createToolbarButtons();
2470
2547
  this.createCustomToolbarItems();
2548
+ this.createToolbarButtons();
2471
2549
 
2472
2550
  // Keyboard shortcuts
2473
2551
  this.bindKeyboardEvents();
@@ -2564,6 +2642,7 @@ class FTable extends FTableEventEmitter {
2564
2642
  const field = this.options.fields[fieldName];
2565
2643
  const isVisible = field.visibility !== 'hidden';
2566
2644
  const isFixed = field.visibility === 'fixed';
2645
+ const isSeparator = field.visibility === 'separator';
2567
2646
  const isSorted = this.isFieldSorted(fieldName);
2568
2647
 
2569
2648
  const listItem = FTableDOMHelper.create('li', {
@@ -2576,24 +2655,32 @@ class FTable extends FTableEventEmitter {
2576
2655
  parent: listItem
2577
2656
  });
2578
2657
 
2579
- const checkbox = FTableDOMHelper.create('input', {
2580
- attributes: {
2581
- type: 'checkbox',
2582
- id: `column-${fieldName}`
2583
- },
2584
- parent: label
2585
- });
2586
-
2587
- checkbox.checked = isVisible;
2658
+ if (!isSeparator) {
2659
+ const checkbox = FTableDOMHelper.create('input', {
2660
+ attributes: {
2661
+ type: 'checkbox',
2662
+ id: `column-${fieldName}`
2663
+ },
2664
+ parent: label
2665
+ });
2666
+ checkbox.checked = isVisible;
2588
2667
 
2589
- // Disable checkbox if column is fixed or currently sorted
2590
- if (isFixed || (isSorted && isVisible)) {
2591
- checkbox.disabled = true;
2592
- listItem.style.opacity = '0.6';
2668
+ // Disable checkbox if column is fixed or currently sorted
2669
+ if (isFixed || (isSorted && isVisible)) {
2670
+ checkbox.disabled = true;
2671
+ listItem.style.opacity = '0.6';
2672
+ }
2673
+ // Handle checkbox change
2674
+ if (!checkbox.disabled) {
2675
+ checkbox.addEventListener('change', () => {
2676
+ this.setColumnVisibility(fieldName, checkbox.checked);
2677
+ });
2678
+ }
2593
2679
  }
2594
2680
 
2595
2681
  const labelText = FTableDOMHelper.create('span', {
2596
2682
  text: field.title || fieldName,
2683
+ style: isSeparator ? 'font-weight: bold;' : null,
2597
2684
  parent: label
2598
2685
  });
2599
2686
 
@@ -2608,12 +2695,6 @@ class FTable extends FTableEventEmitter {
2608
2695
  sortIndicator.style.color = '#666';
2609
2696
  }
2610
2697
 
2611
- // Handle checkbox change
2612
- if (!checkbox.disabled) {
2613
- checkbox.addEventListener('change', () => {
2614
- this.setColumnVisibility(fieldName, checkbox.checked);
2615
- });
2616
- }
2617
2698
  });
2618
2699
  }
2619
2700
 
@@ -2721,7 +2802,7 @@ class FTable extends FTableEventEmitter {
2721
2802
  if (!this.options.toolbar || !this.options.toolbar.items) return;
2722
2803
 
2723
2804
  this.options.toolbar.items.forEach(item => {
2724
- const button = FTableDOMHelper.create('button', {
2805
+ const button = FTableDOMHelper.create('span', {
2725
2806
  className: `ftable-toolbar-item ftable-toolbar-item-custom ${item.buttonClass || ''}`,
2726
2807
  parent: this.elements.toolbarDiv
2727
2808
  });
@@ -2749,6 +2830,7 @@ class FTable extends FTableEventEmitter {
2749
2830
  if (item.text) {
2750
2831
  FTableDOMHelper.create('span', {
2751
2832
  text: item.text,
2833
+ className: `ftable-toolbar-item-text ftable-toolbar-item-custom-text ${item.buttonTextClass || ''}`,
2752
2834
  parent: button
2753
2835
  });
2754
2836
  }
@@ -2818,6 +2900,7 @@ class FTable extends FTableEventEmitter {
2818
2900
  // Update sorting display
2819
2901
  this.renderSortingInfo();
2820
2902
 
2903
+ this.normalizeColumnWidths();
2821
2904
  }
2822
2905
 
2823
2906
  buildLoadParams() {
@@ -3507,7 +3590,7 @@ class FTable extends FTableEventEmitter {
3507
3590
  if (this.options.selectOnRowClick !== false) {
3508
3591
  row.addEventListener('click', (e) => {
3509
3592
  // input elements can't select the row, nor norowselectonclick class
3510
- if (!['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'].includes(e.target.tagName) &&
3593
+ if (!['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA', 'A'].includes(e.target.tagName) &&
3511
3594
  !e.target.classList.contains('norowselectonclick')) {
3512
3595
  this.toggleRowSelection(row);
3513
3596
  }
@@ -3900,11 +3983,8 @@ class FTable extends FTableEventEmitter {
3900
3983
  }
3901
3984
 
3902
3985
  // Public API Methods
3903
- reload(hard = false) {
3904
- if (hard) {
3905
- // Clear list cache
3906
- this.clearListCache();
3907
- }
3986
+ reload() {
3987
+ this.clearListCache();
3908
3988
  return this.load();
3909
3989
  }
3910
3990