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