@liedekef/ftable 1.1.49 → 1.1.50

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 +233 -87
  2. package/ftable.js +233 -87
  3. package/ftable.min.js +2 -24
  4. package/ftable.umd.js +233 -87
  5. package/package.json +1 -1
  6. package/themes/basic/ftable_basic.css +15 -8
  7. package/themes/basic/ftable_basic.min.css +1 -1
  8. package/themes/ftable_theme_base.less +17 -11
  9. package/themes/lightcolor/blue/ftable.css +15 -8
  10. package/themes/lightcolor/blue/ftable.min.css +1 -1
  11. package/themes/lightcolor/gray/ftable.css +15 -8
  12. package/themes/lightcolor/gray/ftable.min.css +1 -1
  13. package/themes/lightcolor/green/ftable.css +15 -8
  14. package/themes/lightcolor/green/ftable.min.css +1 -1
  15. package/themes/lightcolor/orange/ftable.css +15 -8
  16. package/themes/lightcolor/orange/ftable.min.css +1 -1
  17. package/themes/lightcolor/red/ftable.css +15 -8
  18. package/themes/lightcolor/red/ftable.min.css +1 -1
  19. package/themes/metro/blue/ftable.css +15 -8
  20. package/themes/metro/blue/ftable.min.css +1 -1
  21. package/themes/metro/brown/ftable.css +15 -8
  22. package/themes/metro/brown/ftable.min.css +1 -1
  23. package/themes/metro/crimson/ftable.css +15 -8
  24. package/themes/metro/crimson/ftable.min.css +1 -1
  25. package/themes/metro/darkgray/ftable.css +15 -8
  26. package/themes/metro/darkgray/ftable.min.css +1 -1
  27. package/themes/metro/darkorange/ftable.css +15 -8
  28. package/themes/metro/darkorange/ftable.min.css +1 -1
  29. package/themes/metro/green/ftable.css +15 -8
  30. package/themes/metro/green/ftable.min.css +1 -1
  31. package/themes/metro/lightgray/ftable.css +15 -8
  32. package/themes/metro/lightgray/ftable.min.css +1 -1
  33. package/themes/metro/pink/ftable.css +15 -8
  34. package/themes/metro/pink/ftable.min.css +1 -1
  35. package/themes/metro/purple/ftable.css +15 -8
  36. package/themes/metro/purple/ftable.min.css +1 -1
  37. package/themes/metro/red/ftable.css +15 -8
  38. package/themes/metro/red/ftable.min.css +1 -1
package/ftable.js CHANGED
@@ -1453,12 +1453,9 @@ class FTableFormBuilder {
1453
1453
  parent: display
1454
1454
  });
1455
1455
 
1456
- // Create options dropdown
1457
- const dropdown = FTableDOMHelper.create('div', {
1458
- className: 'ftable-multiselect-dropdown',
1459
- parent: container,
1460
- style: 'display: none;'
1461
- });
1456
+ // Dropdown and overlay will be created on demand and appended to body
1457
+ let dropdown = null;
1458
+ let dropdownOverlay = null;
1462
1459
 
1463
1460
  // Store selected values and checkbox references
1464
1461
  const selectedValues = new Set(
@@ -1526,9 +1523,53 @@ class FTableFormBuilder {
1526
1523
  hiddenInput.value = Array.from(selectedValues).join(',');
1527
1524
  };
1528
1525
 
1526
+ // Function to close dropdown
1527
+ const closeDropdown = () => {
1528
+ if (dropdown) {
1529
+ dropdown.remove();
1530
+ dropdown = null;
1531
+ }
1532
+ if (dropdownOverlay) {
1533
+ dropdownOverlay.remove();
1534
+ dropdownOverlay = null;
1535
+ }
1536
+ if (container._cleanupHandlers) {
1537
+ container._cleanupHandlers();
1538
+ container._cleanupHandlers = null;
1539
+ }
1540
+ };
1541
+
1542
+ // Function to position dropdown
1543
+ const positionDropdown = () => {
1544
+ if (!dropdown) return;
1545
+
1546
+ const rect = display.getBoundingClientRect();
1547
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
1548
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
1549
+
1550
+ let left = rect.left + scrollLeft;
1551
+ let top = rect.bottom + scrollTop + 4; // 4px gap
1552
+
1553
+ dropdown.style.position = 'absolute';
1554
+ dropdown.style.left = `${left}px`;
1555
+ dropdown.style.top = `${top}px`;
1556
+ dropdown.style.width = `${rect.width}px`;
1557
+ dropdown.style.minWidth = `${rect.width}px`;
1558
+ dropdown.style.boxSizing = 'border-box';
1559
+ dropdown.style.zIndex = '10000';
1560
+
1561
+ // Adjust horizontal position if needed
1562
+ const dropdownRect = dropdown.getBoundingClientRect();
1563
+ const viewportWidth = window.innerWidth;
1564
+ if (dropdownRect.right > viewportWidth) {
1565
+ left = Math.max(10, viewportWidth - dropdownRect.width - 10);
1566
+ dropdown.style.left = `${left}px`;
1567
+ }
1568
+ };
1569
+
1529
1570
  // Populate options
1530
1571
  const populateOptions = () => {
1531
- if (!field.options) return;
1572
+ if (!field.options || !dropdown) return;
1532
1573
 
1533
1574
  const options = Array.isArray(field.options) ? field.options :
1534
1575
  Object.entries(field.options).map(([k, v]) => ({Value: k, DisplayText: v}));
@@ -1585,26 +1626,74 @@ class FTableFormBuilder {
1585
1626
  // Toggle dropdown
1586
1627
  const toggleDropdown = (e) => {
1587
1628
  if (e) e.stopPropagation();
1588
- const isVisible = dropdown.style.display !== 'none';
1589
- dropdown.style.display = isVisible ? 'none' : 'block';
1590
1629
 
1591
- if (!isVisible) {
1592
- // Close other dropdowns
1593
- document.querySelectorAll('.ftable-multiselect-dropdown').forEach(dd => {
1594
- if (dd !== dropdown) dd.style.display = 'none';
1630
+ if (dropdown) {
1631
+ // Dropdown is open, close it
1632
+ closeDropdown();
1633
+ } else {
1634
+ // Close any other open multiselect dropdowns
1635
+ document.querySelectorAll('.ftable-multiselect-dropdown').forEach(dd => dd.remove());
1636
+ document.querySelectorAll('.ftable-multiselect-overlay').forEach(ov => ov.remove());
1637
+
1638
+ // Create overlay
1639
+ dropdownOverlay = FTableDOMHelper.create('div', {
1640
+ className: 'ftable-multiselect-overlay',
1641
+ parent: document.body
1642
+ });
1643
+
1644
+ // Create dropdown
1645
+ dropdown = FTableDOMHelper.create('div', {
1646
+ className: 'ftable-multiselect-dropdown',
1647
+ parent: document.body
1595
1648
  });
1649
+
1650
+ // Populate options
1651
+ populateOptions();
1652
+
1653
+ // Position dropdown
1654
+ positionDropdown();
1655
+
1656
+ // Handle clicks outside
1657
+ dropdownOverlay.addEventListener('click', (event) => {
1658
+ if (event.target === dropdownOverlay) {
1659
+ closeDropdown();
1660
+ }
1661
+ });
1662
+
1663
+ // Reposition on scroll/resize
1664
+ const repositionHandler = () => positionDropdown();
1665
+ window.addEventListener('scroll', repositionHandler, true);
1666
+ window.addEventListener('resize', repositionHandler);
1667
+
1668
+ // Store cleanup function
1669
+ container._cleanupHandlers = () => {
1670
+ window.removeEventListener('scroll', repositionHandler, true);
1671
+ window.removeEventListener('resize', repositionHandler);
1672
+ };
1596
1673
  }
1597
1674
  };
1598
1675
 
1599
1676
  display.addEventListener('click', toggleDropdown);
1600
1677
  toggleBtn.addEventListener('click', toggleDropdown);
1601
1678
 
1602
- // Close dropdown when clicking outside
1603
- document.addEventListener('click', (e) => {
1604
- if (!container.contains(e.target)) {
1605
- dropdown.style.display = 'none';
1606
- }
1679
+ // Clean up when container is removed from DOM
1680
+ const observer = new MutationObserver((mutations) => {
1681
+ mutations.forEach((mutation) => {
1682
+ mutation.removedNodes.forEach((node) => {
1683
+ if (node === container || node.contains && node.contains(container)) {
1684
+ closeDropdown();
1685
+ observer.disconnect();
1686
+ }
1687
+ });
1688
+ });
1607
1689
  });
1690
+
1691
+ // Start observing once container is in the DOM
1692
+ setTimeout(() => {
1693
+ if (container.parentNode) {
1694
+ observer.observe(container.parentNode, { childList: true, subtree: true });
1695
+ }
1696
+ }, 0);
1608
1697
 
1609
1698
  // Initialize
1610
1699
  populateOptions();
@@ -1932,9 +2021,6 @@ class FTable extends FTableEventEmitter {
1932
2021
  this.updateSortingHeaders();
1933
2022
  this.renderSortingInfo();
1934
2023
 
1935
- // Add essential CSS if not already present
1936
- //this.addEssentialCSS();
1937
-
1938
2024
  // now make sure all tables have a % width
1939
2025
  this.initColumnWidths();
1940
2026
  }
@@ -2014,40 +2100,6 @@ class FTable extends FTableEventEmitter {
2014
2100
  return result;
2015
2101
  }
2016
2102
 
2017
- addEssentialCSS() {
2018
- // Check if our CSS is already added
2019
- if (document.querySelector('#ftable-essential-css')) return;
2020
-
2021
- const css = `
2022
- .ftable-row-animation {
2023
- transition: background-color 0.3s ease;
2024
- }
2025
-
2026
- .ftable-row-added {
2027
- background-color: #d4edda !important;
2028
- }
2029
-
2030
- .ftable-row-edited {
2031
- background-color: #d1ecf1 !important;
2032
- }
2033
-
2034
- .ftable-row-deleted {
2035
- opacity: 0;
2036
- transform: translateY(-10px);
2037
- transition: opacity 0.3s ease, transform 0.3s ease;
2038
- }
2039
-
2040
- .ftable-toolbarsearch {
2041
- width: 90%;
2042
- }
2043
- `;
2044
-
2045
- const style = document.createElement('style');
2046
- style.id = 'ftable-essential-css';
2047
- style.textContent = css;
2048
- document.head.appendChild(style);
2049
- }
2050
-
2051
2103
  createPagingUI() {
2052
2104
  this.elements.bottomPanel = FTableDOMHelper.create('div', {
2053
2105
  className: 'ftable-bottom-panel',
@@ -2704,12 +2756,9 @@ class FTable extends FTableEventEmitter {
2704
2756
  parent: display
2705
2757
  });
2706
2758
 
2707
- // Create options dropdown
2708
- const dropdown = FTableDOMHelper.create('div', {
2709
- className: 'ftable-multiselect-dropdown',
2710
- parent: container,
2711
- style: 'display: none;'
2712
- });
2759
+ // Dropdown and overlay will be created on demand and appended to body
2760
+ let dropdown = null;
2761
+ let dropdownOverlay = null;
2713
2762
 
2714
2763
  // Store selected values and checkbox references
2715
2764
  const selectedValues = new Set();
@@ -2776,18 +2825,53 @@ class FTable extends FTableEventEmitter {
2776
2825
  }
2777
2826
  };
2778
2827
 
2779
- // Add reset method to container
2780
- container.resetMultiSelect = () => {
2781
- selectedValues.clear();
2782
- checkboxMap.forEach(checkbox => {
2783
- checkbox.checked = false;
2784
- });
2785
- updateDisplay();
2828
+ // Function to close dropdown
2829
+ const closeDropdown = () => {
2830
+ if (dropdown) {
2831
+ dropdown.remove();
2832
+ dropdown = null;
2833
+ }
2834
+ if (dropdownOverlay) {
2835
+ dropdownOverlay.remove();
2836
+ dropdownOverlay = null;
2837
+ }
2838
+ if (container._cleanupHandlers) {
2839
+ container._cleanupHandlers();
2840
+ container._cleanupHandlers = null;
2841
+ }
2842
+ };
2843
+
2844
+ // Function to position dropdown
2845
+ const positionDropdown = () => {
2846
+ if (!dropdown) return;
2847
+
2848
+ const rect = display.getBoundingClientRect();
2849
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
2850
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
2851
+
2852
+ let left = rect.left + scrollLeft;
2853
+ let top = rect.bottom + scrollTop + 4; // 4px gap
2854
+
2855
+ dropdown.style.position = 'absolute';
2856
+ dropdown.style.left = `${left}px`;
2857
+ dropdown.style.top = `${top}px`;
2858
+ dropdown.style.width = `${rect.width}px`;
2859
+ dropdown.style.minWidth = `${rect.width}px`;
2860
+ dropdown.style.boxSizing = 'border-box';
2861
+ dropdown.style.zIndex = '10000';
2862
+
2863
+ // Adjust horizontal position if needed
2864
+ const dropdownRect = dropdown.getBoundingClientRect();
2865
+ const viewportWidth = window.innerWidth;
2866
+ if (dropdownRect.right > viewportWidth) {
2867
+ left = Math.max(10, viewportWidth - dropdownRect.width - 10);
2868
+ dropdown.style.left = `${left}px`;
2869
+ }
2786
2870
  };
2787
2871
 
2788
2872
  // Populate options in both hidden select and dropdown
2789
2873
  const populateOptions = () => {
2790
- if (!optionsSource) return;
2874
+ if (!optionsSource || !dropdown) return;
2791
2875
 
2792
2876
  const options = Array.isArray(optionsSource) ? optionsSource :
2793
2877
  Object.entries(optionsSource).map(([k, v]) => ({Value: k, DisplayText: v}));
@@ -2803,12 +2887,14 @@ class FTable extends FTableEventEmitter {
2803
2887
 
2804
2888
  const optText = option.DisplayText || option.text || option;
2805
2889
 
2806
- // Add to hidden select
2807
- FTableDOMHelper.create('option', {
2808
- value: optValue,
2809
- textContent: optText,
2810
- parent: hiddenSelect
2811
- });
2890
+ // Add to hidden select (only once)
2891
+ if (!hiddenSelect.querySelector(`option[value="${optValue}"]`)) {
2892
+ FTableDOMHelper.create('option', {
2893
+ value: optValue,
2894
+ textContent: optText,
2895
+ parent: hiddenSelect
2896
+ });
2897
+ }
2812
2898
 
2813
2899
  // Add to visual dropdown
2814
2900
  const optionDiv = FTableDOMHelper.create('div', {
@@ -2822,6 +2908,9 @@ class FTable extends FTableEventEmitter {
2822
2908
  parent: optionDiv
2823
2909
  });
2824
2910
 
2911
+ // Set initial checked state
2912
+ checkbox.checked = selectedValues.has(optValue.toString());
2913
+
2825
2914
  // Store checkbox reference
2826
2915
  checkboxMap.set(optValue.toString(), checkbox);
2827
2916
 
@@ -2851,29 +2940,86 @@ class FTable extends FTableEventEmitter {
2851
2940
  // Toggle dropdown
2852
2941
  const toggleDropdown = (e) => {
2853
2942
  if (e) e.stopPropagation();
2854
- const isVisible = dropdown.style.display !== 'none';
2855
- dropdown.style.display = isVisible ? 'none' : 'block';
2856
2943
 
2857
- if (!isVisible) {
2858
- // Close other dropdowns
2859
- document.querySelectorAll('.ftable-multiselect-dropdown').forEach(dd => {
2860
- if (dd !== dropdown) dd.style.display = 'none';
2944
+ if (dropdown) {
2945
+ // Dropdown is open, close it
2946
+ closeDropdown();
2947
+ } else {
2948
+ // Close any other open multiselect dropdowns
2949
+ document.querySelectorAll('.ftable-multiselect-dropdown').forEach(dd => dd.remove());
2950
+ document.querySelectorAll('.ftable-multiselect-overlay').forEach(ov => ov.remove());
2951
+
2952
+ // Create overlay
2953
+ dropdownOverlay = FTableDOMHelper.create('div', {
2954
+ className: 'ftable-multiselect-overlay',
2955
+ parent: document.body
2861
2956
  });
2957
+
2958
+ // Create dropdown
2959
+ dropdown = FTableDOMHelper.create('div', {
2960
+ className: 'ftable-multiselect-dropdown',
2961
+ parent: document.body
2962
+ });
2963
+
2964
+ // Populate options
2965
+ populateOptions();
2966
+
2967
+ // Position dropdown
2968
+ positionDropdown();
2969
+
2970
+ // Handle clicks outside
2971
+ dropdownOverlay.addEventListener('click', (event) => {
2972
+ if (event.target === dropdownOverlay) {
2973
+ closeDropdown();
2974
+ }
2975
+ });
2976
+
2977
+ // Reposition on scroll/resize
2978
+ const repositionHandler = () => positionDropdown();
2979
+ window.addEventListener('scroll', repositionHandler, true);
2980
+ window.addEventListener('resize', repositionHandler);
2981
+
2982
+ // Store cleanup function
2983
+ container._cleanupHandlers = () => {
2984
+ window.removeEventListener('scroll', repositionHandler, true);
2985
+ window.removeEventListener('resize', repositionHandler);
2986
+ };
2862
2987
  }
2863
2988
  };
2864
2989
 
2865
2990
  display.addEventListener('click', toggleDropdown);
2866
2991
  toggleBtn.addEventListener('click', toggleDropdown);
2867
2992
 
2868
- // Close dropdown when clicking outside
2869
- document.addEventListener('click', (e) => {
2870
- if (!container.contains(e.target)) {
2871
- dropdown.style.display = 'none';
2872
- }
2993
+ // Add reset method to container
2994
+ container.resetMultiSelect = () => {
2995
+ selectedValues.clear();
2996
+ checkboxMap.forEach(checkbox => {
2997
+ checkbox.checked = false;
2998
+ });
2999
+ closeDropdown();
3000
+ updateDisplay();
3001
+ };
3002
+
3003
+ // Clean up when container is removed from DOM
3004
+ const observer = new MutationObserver((mutations) => {
3005
+ mutations.forEach((mutation) => {
3006
+ mutation.removedNodes.forEach((node) => {
3007
+ if (node === container || node.contains && node.contains(container)) {
3008
+ closeDropdown();
3009
+ observer.disconnect();
3010
+ }
3011
+ });
3012
+ });
2873
3013
  });
3014
+
3015
+ // Start observing once container is in the DOM
3016
+ setTimeout(() => {
3017
+ if (container.parentNode) {
3018
+ observer.observe(container.parentNode, { childList: true, subtree: true });
3019
+ }
3020
+ }, 0);
2874
3021
 
2875
3022
  // Initialize
2876
- populateOptions();
2877
3023
  updateDisplay();
2878
3024
 
2879
3025
  return container;