@liedekef/ftable 1.1.50 → 1.1.52
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.
- package/ftable.esm.js +132 -12
- package/ftable.js +132 -12
- package/ftable.min.js +2 -2
- package/ftable.umd.js +132 -12
- package/package.json +1 -1
- package/themes/basic/ftable_basic.css +5 -2
- package/themes/basic/ftable_basic.min.css +1 -1
- package/themes/ftable_theme_base.less +6 -2
- package/themes/lightcolor/blue/ftable.css +5 -2
- package/themes/lightcolor/blue/ftable.min.css +1 -1
- package/themes/lightcolor/gray/ftable.css +5 -2
- package/themes/lightcolor/gray/ftable.min.css +1 -1
- package/themes/lightcolor/green/ftable.css +5 -2
- package/themes/lightcolor/green/ftable.min.css +1 -1
- package/themes/lightcolor/orange/ftable.css +5 -2
- package/themes/lightcolor/orange/ftable.min.css +1 -1
- package/themes/lightcolor/red/ftable.css +5 -2
- package/themes/lightcolor/red/ftable.min.css +1 -1
- package/themes/metro/blue/ftable.css +5 -2
- package/themes/metro/blue/ftable.min.css +1 -1
- package/themes/metro/brown/ftable.css +5 -2
- package/themes/metro/brown/ftable.min.css +1 -1
- package/themes/metro/crimson/ftable.css +5 -2
- package/themes/metro/crimson/ftable.min.css +1 -1
- package/themes/metro/darkgray/ftable.css +5 -2
- package/themes/metro/darkgray/ftable.min.css +1 -1
- package/themes/metro/darkorange/ftable.css +5 -2
- package/themes/metro/darkorange/ftable.min.css +1 -1
- package/themes/metro/green/ftable.css +5 -2
- package/themes/metro/green/ftable.min.css +1 -1
- package/themes/metro/lightgray/ftable.css +5 -2
- package/themes/metro/lightgray/ftable.min.css +1 -1
- package/themes/metro/pink/ftable.css +5 -2
- package/themes/metro/pink/ftable.min.css +1 -1
- package/themes/metro/purple/ftable.css +5 -2
- package/themes/metro/purple/ftable.min.css +1 -1
- package/themes/metro/red/ftable.css +5 -2
- package/themes/metro/red/ftable.min.css +1 -1
package/ftable.esm.js
CHANGED
|
@@ -1429,7 +1429,10 @@ class FTableFormBuilder {
|
|
|
1429
1429
|
// Create display area
|
|
1430
1430
|
const display = FTableDOMHelper.create('div', {
|
|
1431
1431
|
className: 'ftable-multiselect-display',
|
|
1432
|
-
parent: container
|
|
1432
|
+
parent: container,
|
|
1433
|
+
attributes: {
|
|
1434
|
+
tabindex: '0' // Makes it focusable and in tab order
|
|
1435
|
+
}
|
|
1433
1436
|
});
|
|
1434
1437
|
|
|
1435
1438
|
const selectedDisplay = FTableDOMHelper.create('div', {
|
|
@@ -1449,7 +1452,10 @@ class FTableFormBuilder {
|
|
|
1449
1452
|
type: 'button',
|
|
1450
1453
|
className: 'ftable-multiselect-toggle',
|
|
1451
1454
|
innerHTML: '▼',
|
|
1452
|
-
parent: display
|
|
1455
|
+
parent: display,
|
|
1456
|
+
attributes: {
|
|
1457
|
+
tabindex: '-1' // this skips regular focus when tabbing
|
|
1458
|
+
}
|
|
1453
1459
|
});
|
|
1454
1460
|
|
|
1455
1461
|
// Dropdown and overlay will be created on demand and appended to body
|
|
@@ -1524,6 +1530,7 @@ class FTableFormBuilder {
|
|
|
1524
1530
|
|
|
1525
1531
|
// Function to close dropdown
|
|
1526
1532
|
const closeDropdown = () => {
|
|
1533
|
+
display.focus(); // Return focus to the trigger
|
|
1527
1534
|
if (dropdown) {
|
|
1528
1535
|
dropdown.remove();
|
|
1529
1536
|
dropdown = null;
|
|
@@ -1558,7 +1565,7 @@ class FTableFormBuilder {
|
|
|
1558
1565
|
dropdown.style.zIndex = '10000';
|
|
1559
1566
|
|
|
1560
1567
|
// Adjust horizontal position if needed
|
|
1561
|
-
|
|
1568
|
+
const dropdownRect = dropdown.getBoundingClientRect();
|
|
1562
1569
|
const viewportWidth = window.innerWidth;
|
|
1563
1570
|
if (dropdownRect.right > viewportWidth) {
|
|
1564
1571
|
left = Math.max(10, viewportWidth - dropdownRect.width - 10);
|
|
@@ -1643,7 +1650,12 @@ class FTableFormBuilder {
|
|
|
1643
1650
|
// Create dropdown
|
|
1644
1651
|
dropdown = FTableDOMHelper.create('div', {
|
|
1645
1652
|
className: 'ftable-multiselect-dropdown',
|
|
1646
|
-
parent: document.body
|
|
1653
|
+
parent: document.body,
|
|
1654
|
+
attributes: {
|
|
1655
|
+
tabindex: '-1',
|
|
1656
|
+
role: 'listbox',
|
|
1657
|
+
'aria-multiselectable': 'true'
|
|
1658
|
+
}
|
|
1647
1659
|
});
|
|
1648
1660
|
|
|
1649
1661
|
// Populate options
|
|
@@ -1652,6 +1664,37 @@ class FTableFormBuilder {
|
|
|
1652
1664
|
// Position dropdown
|
|
1653
1665
|
positionDropdown();
|
|
1654
1666
|
|
|
1667
|
+
// dropdown focus
|
|
1668
|
+
dropdown.focus();
|
|
1669
|
+
|
|
1670
|
+
// Add keyboard navigation
|
|
1671
|
+
dropdown.addEventListener('keydown', (e) => {
|
|
1672
|
+
if (e.key === 'Escape') {
|
|
1673
|
+
closeDropdown();
|
|
1674
|
+
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
1675
|
+
e.preventDefault();
|
|
1676
|
+
// Navigate between options
|
|
1677
|
+
const checkboxes = Array.from(dropdown.querySelectorAll('.ftable-multiselect-checkbox'));
|
|
1678
|
+
const current = document.activeElement;
|
|
1679
|
+
const currentIndex = checkboxes.indexOf(current);
|
|
1680
|
+
|
|
1681
|
+
let nextIndex;
|
|
1682
|
+
if (e.key === 'ArrowDown') {
|
|
1683
|
+
nextIndex = currentIndex < checkboxes.length - 1 ? currentIndex + 1 : 0;
|
|
1684
|
+
} else {
|
|
1685
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : checkboxes.length - 1;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
checkboxes[nextIndex].focus();
|
|
1689
|
+
} else if (e.key === ' ' || e.key === 'Enter') {
|
|
1690
|
+
e.preventDefault();
|
|
1691
|
+
// Toggle the focused checkbox
|
|
1692
|
+
if (document.activeElement.classList.contains('ftable-multiselect-checkbox')) {
|
|
1693
|
+
document.activeElement.click();
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
});
|
|
1697
|
+
|
|
1655
1698
|
// Handle clicks outside
|
|
1656
1699
|
dropdownOverlay.addEventListener('click', (event) => {
|
|
1657
1700
|
if (event.target === dropdownOverlay) {
|
|
@@ -1661,19 +1704,36 @@ class FTableFormBuilder {
|
|
|
1661
1704
|
|
|
1662
1705
|
// Reposition on scroll/resize
|
|
1663
1706
|
const repositionHandler = () => positionDropdown();
|
|
1664
|
-
|
|
1707
|
+
const scrollHandler = (e) => {
|
|
1708
|
+
if (dropdown && dropdown.contains(e.target)) {
|
|
1709
|
+
return; // Allow scrolling inside dropdown
|
|
1710
|
+
}
|
|
1711
|
+
positionDropdown();
|
|
1712
|
+
};
|
|
1713
|
+
const selectedResizeObserver = new ResizeObserver(() => {
|
|
1714
|
+
positionDropdown();
|
|
1715
|
+
});
|
|
1716
|
+
window.addEventListener('scroll', scrollHandler, true);
|
|
1665
1717
|
window.addEventListener('resize', repositionHandler);
|
|
1718
|
+
selectedResizeObserver.observe(selectedDisplay);
|
|
1666
1719
|
|
|
1667
1720
|
// Store cleanup function
|
|
1668
1721
|
container._cleanupHandlers = () => {
|
|
1669
|
-
window.removeEventListener('scroll',
|
|
1722
|
+
window.removeEventListener('scroll', scrollHandler, true);
|
|
1670
1723
|
window.removeEventListener('resize', repositionHandler);
|
|
1724
|
+
selectedResizeObserver.disconnect();
|
|
1671
1725
|
};
|
|
1672
1726
|
}
|
|
1673
1727
|
};
|
|
1674
1728
|
|
|
1675
1729
|
display.addEventListener('click', toggleDropdown);
|
|
1676
1730
|
toggleBtn.addEventListener('click', toggleDropdown);
|
|
1731
|
+
display.addEventListener('keydown', (e) => {
|
|
1732
|
+
if (e.key === 'ArrowDown' || e.key === 'Enter') {
|
|
1733
|
+
e.preventDefault();
|
|
1734
|
+
toggleDropdown();
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1677
1737
|
|
|
1678
1738
|
// Clean up when container is removed from DOM
|
|
1679
1739
|
const observer = new MutationObserver((mutations) => {
|
|
@@ -2732,7 +2792,10 @@ class FTable extends FTableEventEmitter {
|
|
|
2732
2792
|
// Create display area
|
|
2733
2793
|
const display = FTableDOMHelper.create('div', {
|
|
2734
2794
|
className: 'ftable-multiselect-display',
|
|
2735
|
-
parent: container
|
|
2795
|
+
parent: container,
|
|
2796
|
+
attributes: {
|
|
2797
|
+
tabindex: '0' // Makes it focusable and in tab order
|
|
2798
|
+
}
|
|
2736
2799
|
});
|
|
2737
2800
|
|
|
2738
2801
|
const selectedDisplay = FTableDOMHelper.create('div', {
|
|
@@ -2752,7 +2815,10 @@ class FTable extends FTableEventEmitter {
|
|
|
2752
2815
|
type: 'button',
|
|
2753
2816
|
className: 'ftable-multiselect-toggle',
|
|
2754
2817
|
innerHTML: '▼',
|
|
2755
|
-
parent: display
|
|
2818
|
+
parent: display,
|
|
2819
|
+
attributes: {
|
|
2820
|
+
tabindex: '-1' // this skips regular focus when tabbing
|
|
2821
|
+
}
|
|
2756
2822
|
});
|
|
2757
2823
|
|
|
2758
2824
|
// Dropdown and overlay will be created on demand and appended to body
|
|
@@ -2826,6 +2892,7 @@ class FTable extends FTableEventEmitter {
|
|
|
2826
2892
|
|
|
2827
2893
|
// Function to close dropdown
|
|
2828
2894
|
const closeDropdown = () => {
|
|
2895
|
+
display.focus(); // Return focus to the trigger
|
|
2829
2896
|
if (dropdown) {
|
|
2830
2897
|
dropdown.remove();
|
|
2831
2898
|
dropdown = null;
|
|
@@ -2860,7 +2927,7 @@ class FTable extends FTableEventEmitter {
|
|
|
2860
2927
|
dropdown.style.zIndex = '10000';
|
|
2861
2928
|
|
|
2862
2929
|
// Adjust horizontal position if needed
|
|
2863
|
-
|
|
2930
|
+
const dropdownRect = dropdown.getBoundingClientRect();
|
|
2864
2931
|
const viewportWidth = window.innerWidth;
|
|
2865
2932
|
if (dropdownRect.right > viewportWidth) {
|
|
2866
2933
|
left = Math.max(10, viewportWidth - dropdownRect.width - 10);
|
|
@@ -2957,7 +3024,12 @@ class FTable extends FTableEventEmitter {
|
|
|
2957
3024
|
// Create dropdown
|
|
2958
3025
|
dropdown = FTableDOMHelper.create('div', {
|
|
2959
3026
|
className: 'ftable-multiselect-dropdown',
|
|
2960
|
-
parent: document.body
|
|
3027
|
+
parent: document.body,
|
|
3028
|
+
attributes: {
|
|
3029
|
+
tabindex: '-1',
|
|
3030
|
+
role: 'listbox',
|
|
3031
|
+
'aria-multiselectable': 'true'
|
|
3032
|
+
}
|
|
2961
3033
|
});
|
|
2962
3034
|
|
|
2963
3035
|
// Populate options
|
|
@@ -2966,6 +3038,37 @@ class FTable extends FTableEventEmitter {
|
|
|
2966
3038
|
// Position dropdown
|
|
2967
3039
|
positionDropdown();
|
|
2968
3040
|
|
|
3041
|
+
// dropdown focus
|
|
3042
|
+
dropdown.focus();
|
|
3043
|
+
|
|
3044
|
+
// Add keyboard navigation
|
|
3045
|
+
dropdown.addEventListener('keydown', (e) => {
|
|
3046
|
+
if (e.key === 'Escape') {
|
|
3047
|
+
closeDropdown();
|
|
3048
|
+
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
3049
|
+
e.preventDefault();
|
|
3050
|
+
// Navigate between options
|
|
3051
|
+
const checkboxes = Array.from(dropdown.querySelectorAll('.ftable-multiselect-checkbox'));
|
|
3052
|
+
const current = document.activeElement;
|
|
3053
|
+
const currentIndex = checkboxes.indexOf(current);
|
|
3054
|
+
|
|
3055
|
+
let nextIndex;
|
|
3056
|
+
if (e.key === 'ArrowDown') {
|
|
3057
|
+
nextIndex = currentIndex < checkboxes.length - 1 ? currentIndex + 1 : 0;
|
|
3058
|
+
} else {
|
|
3059
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : checkboxes.length - 1;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
checkboxes[nextIndex].focus();
|
|
3063
|
+
} else if (e.key === ' ' || e.key === 'Enter') {
|
|
3064
|
+
e.preventDefault();
|
|
3065
|
+
// Toggle the focused checkbox
|
|
3066
|
+
if (document.activeElement.classList.contains('ftable-multiselect-checkbox')) {
|
|
3067
|
+
document.activeElement.click();
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
});
|
|
3071
|
+
|
|
2969
3072
|
// Handle clicks outside
|
|
2970
3073
|
dropdownOverlay.addEventListener('click', (event) => {
|
|
2971
3074
|
if (event.target === dropdownOverlay) {
|
|
@@ -2975,19 +3078,36 @@ class FTable extends FTableEventEmitter {
|
|
|
2975
3078
|
|
|
2976
3079
|
// Reposition on scroll/resize
|
|
2977
3080
|
const repositionHandler = () => positionDropdown();
|
|
2978
|
-
|
|
3081
|
+
const scrollHandler = (e) => {
|
|
3082
|
+
if (dropdown && dropdown.contains(e.target)) {
|
|
3083
|
+
return; // Allow scrolling inside dropdown
|
|
3084
|
+
}
|
|
3085
|
+
positionDropdown();
|
|
3086
|
+
};
|
|
3087
|
+
const selectedResizeObserver = new ResizeObserver(() => {
|
|
3088
|
+
positionDropdown();
|
|
3089
|
+
});
|
|
3090
|
+
window.addEventListener('scroll', scrollHandler, true);
|
|
2979
3091
|
window.addEventListener('resize', repositionHandler);
|
|
3092
|
+
selectedResizeObserver.observe(selectedDisplay);
|
|
2980
3093
|
|
|
2981
3094
|
// Store cleanup function
|
|
2982
3095
|
container._cleanupHandlers = () => {
|
|
2983
|
-
window.removeEventListener('scroll',
|
|
3096
|
+
window.removeEventListener('scroll', scrollHandler, true);
|
|
2984
3097
|
window.removeEventListener('resize', repositionHandler);
|
|
3098
|
+
selectedResizeObserver.disconnect();
|
|
2985
3099
|
};
|
|
2986
3100
|
}
|
|
2987
3101
|
};
|
|
2988
3102
|
|
|
2989
3103
|
display.addEventListener('click', toggleDropdown);
|
|
2990
3104
|
toggleBtn.addEventListener('click', toggleDropdown);
|
|
3105
|
+
display.addEventListener('keydown', (e) => {
|
|
3106
|
+
if (e.key === 'ArrowDown' || e.key === 'Enter') {
|
|
3107
|
+
e.preventDefault();
|
|
3108
|
+
toggleDropdown();
|
|
3109
|
+
}
|
|
3110
|
+
});
|
|
2991
3111
|
|
|
2992
3112
|
// Add reset method to container
|
|
2993
3113
|
container.resetMultiSelect = () => {
|
package/ftable.js
CHANGED
|
@@ -1430,7 +1430,10 @@ class FTableFormBuilder {
|
|
|
1430
1430
|
// Create display area
|
|
1431
1431
|
const display = FTableDOMHelper.create('div', {
|
|
1432
1432
|
className: 'ftable-multiselect-display',
|
|
1433
|
-
parent: container
|
|
1433
|
+
parent: container,
|
|
1434
|
+
attributes: {
|
|
1435
|
+
tabindex: '0' // Makes it focusable and in tab order
|
|
1436
|
+
}
|
|
1434
1437
|
});
|
|
1435
1438
|
|
|
1436
1439
|
const selectedDisplay = FTableDOMHelper.create('div', {
|
|
@@ -1450,7 +1453,10 @@ class FTableFormBuilder {
|
|
|
1450
1453
|
type: 'button',
|
|
1451
1454
|
className: 'ftable-multiselect-toggle',
|
|
1452
1455
|
innerHTML: '▼',
|
|
1453
|
-
parent: display
|
|
1456
|
+
parent: display,
|
|
1457
|
+
attributes: {
|
|
1458
|
+
tabindex: '-1' // this skips regular focus when tabbing
|
|
1459
|
+
}
|
|
1454
1460
|
});
|
|
1455
1461
|
|
|
1456
1462
|
// Dropdown and overlay will be created on demand and appended to body
|
|
@@ -1525,6 +1531,7 @@ class FTableFormBuilder {
|
|
|
1525
1531
|
|
|
1526
1532
|
// Function to close dropdown
|
|
1527
1533
|
const closeDropdown = () => {
|
|
1534
|
+
display.focus(); // Return focus to the trigger
|
|
1528
1535
|
if (dropdown) {
|
|
1529
1536
|
dropdown.remove();
|
|
1530
1537
|
dropdown = null;
|
|
@@ -1559,7 +1566,7 @@ class FTableFormBuilder {
|
|
|
1559
1566
|
dropdown.style.zIndex = '10000';
|
|
1560
1567
|
|
|
1561
1568
|
// Adjust horizontal position if needed
|
|
1562
|
-
|
|
1569
|
+
const dropdownRect = dropdown.getBoundingClientRect();
|
|
1563
1570
|
const viewportWidth = window.innerWidth;
|
|
1564
1571
|
if (dropdownRect.right > viewportWidth) {
|
|
1565
1572
|
left = Math.max(10, viewportWidth - dropdownRect.width - 10);
|
|
@@ -1644,7 +1651,12 @@ class FTableFormBuilder {
|
|
|
1644
1651
|
// Create dropdown
|
|
1645
1652
|
dropdown = FTableDOMHelper.create('div', {
|
|
1646
1653
|
className: 'ftable-multiselect-dropdown',
|
|
1647
|
-
parent: document.body
|
|
1654
|
+
parent: document.body,
|
|
1655
|
+
attributes: {
|
|
1656
|
+
tabindex: '-1',
|
|
1657
|
+
role: 'listbox',
|
|
1658
|
+
'aria-multiselectable': 'true'
|
|
1659
|
+
}
|
|
1648
1660
|
});
|
|
1649
1661
|
|
|
1650
1662
|
// Populate options
|
|
@@ -1653,6 +1665,37 @@ class FTableFormBuilder {
|
|
|
1653
1665
|
// Position dropdown
|
|
1654
1666
|
positionDropdown();
|
|
1655
1667
|
|
|
1668
|
+
// dropdown focus
|
|
1669
|
+
dropdown.focus();
|
|
1670
|
+
|
|
1671
|
+
// Add keyboard navigation
|
|
1672
|
+
dropdown.addEventListener('keydown', (e) => {
|
|
1673
|
+
if (e.key === 'Escape') {
|
|
1674
|
+
closeDropdown();
|
|
1675
|
+
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
1676
|
+
e.preventDefault();
|
|
1677
|
+
// Navigate between options
|
|
1678
|
+
const checkboxes = Array.from(dropdown.querySelectorAll('.ftable-multiselect-checkbox'));
|
|
1679
|
+
const current = document.activeElement;
|
|
1680
|
+
const currentIndex = checkboxes.indexOf(current);
|
|
1681
|
+
|
|
1682
|
+
let nextIndex;
|
|
1683
|
+
if (e.key === 'ArrowDown') {
|
|
1684
|
+
nextIndex = currentIndex < checkboxes.length - 1 ? currentIndex + 1 : 0;
|
|
1685
|
+
} else {
|
|
1686
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : checkboxes.length - 1;
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
checkboxes[nextIndex].focus();
|
|
1690
|
+
} else if (e.key === ' ' || e.key === 'Enter') {
|
|
1691
|
+
e.preventDefault();
|
|
1692
|
+
// Toggle the focused checkbox
|
|
1693
|
+
if (document.activeElement.classList.contains('ftable-multiselect-checkbox')) {
|
|
1694
|
+
document.activeElement.click();
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
});
|
|
1698
|
+
|
|
1656
1699
|
// Handle clicks outside
|
|
1657
1700
|
dropdownOverlay.addEventListener('click', (event) => {
|
|
1658
1701
|
if (event.target === dropdownOverlay) {
|
|
@@ -1662,19 +1705,36 @@ class FTableFormBuilder {
|
|
|
1662
1705
|
|
|
1663
1706
|
// Reposition on scroll/resize
|
|
1664
1707
|
const repositionHandler = () => positionDropdown();
|
|
1665
|
-
|
|
1708
|
+
const scrollHandler = (e) => {
|
|
1709
|
+
if (dropdown && dropdown.contains(e.target)) {
|
|
1710
|
+
return; // Allow scrolling inside dropdown
|
|
1711
|
+
}
|
|
1712
|
+
positionDropdown();
|
|
1713
|
+
};
|
|
1714
|
+
const selectedResizeObserver = new ResizeObserver(() => {
|
|
1715
|
+
positionDropdown();
|
|
1716
|
+
});
|
|
1717
|
+
window.addEventListener('scroll', scrollHandler, true);
|
|
1666
1718
|
window.addEventListener('resize', repositionHandler);
|
|
1719
|
+
selectedResizeObserver.observe(selectedDisplay);
|
|
1667
1720
|
|
|
1668
1721
|
// Store cleanup function
|
|
1669
1722
|
container._cleanupHandlers = () => {
|
|
1670
|
-
window.removeEventListener('scroll',
|
|
1723
|
+
window.removeEventListener('scroll', scrollHandler, true);
|
|
1671
1724
|
window.removeEventListener('resize', repositionHandler);
|
|
1725
|
+
selectedResizeObserver.disconnect();
|
|
1672
1726
|
};
|
|
1673
1727
|
}
|
|
1674
1728
|
};
|
|
1675
1729
|
|
|
1676
1730
|
display.addEventListener('click', toggleDropdown);
|
|
1677
1731
|
toggleBtn.addEventListener('click', toggleDropdown);
|
|
1732
|
+
display.addEventListener('keydown', (e) => {
|
|
1733
|
+
if (e.key === 'ArrowDown' || e.key === 'Enter') {
|
|
1734
|
+
e.preventDefault();
|
|
1735
|
+
toggleDropdown();
|
|
1736
|
+
}
|
|
1737
|
+
});
|
|
1678
1738
|
|
|
1679
1739
|
// Clean up when container is removed from DOM
|
|
1680
1740
|
const observer = new MutationObserver((mutations) => {
|
|
@@ -2733,7 +2793,10 @@ class FTable extends FTableEventEmitter {
|
|
|
2733
2793
|
// Create display area
|
|
2734
2794
|
const display = FTableDOMHelper.create('div', {
|
|
2735
2795
|
className: 'ftable-multiselect-display',
|
|
2736
|
-
parent: container
|
|
2796
|
+
parent: container,
|
|
2797
|
+
attributes: {
|
|
2798
|
+
tabindex: '0' // Makes it focusable and in tab order
|
|
2799
|
+
}
|
|
2737
2800
|
});
|
|
2738
2801
|
|
|
2739
2802
|
const selectedDisplay = FTableDOMHelper.create('div', {
|
|
@@ -2753,7 +2816,10 @@ class FTable extends FTableEventEmitter {
|
|
|
2753
2816
|
type: 'button',
|
|
2754
2817
|
className: 'ftable-multiselect-toggle',
|
|
2755
2818
|
innerHTML: '▼',
|
|
2756
|
-
parent: display
|
|
2819
|
+
parent: display,
|
|
2820
|
+
attributes: {
|
|
2821
|
+
tabindex: '-1' // this skips regular focus when tabbing
|
|
2822
|
+
}
|
|
2757
2823
|
});
|
|
2758
2824
|
|
|
2759
2825
|
// Dropdown and overlay will be created on demand and appended to body
|
|
@@ -2827,6 +2893,7 @@ class FTable extends FTableEventEmitter {
|
|
|
2827
2893
|
|
|
2828
2894
|
// Function to close dropdown
|
|
2829
2895
|
const closeDropdown = () => {
|
|
2896
|
+
display.focus(); // Return focus to the trigger
|
|
2830
2897
|
if (dropdown) {
|
|
2831
2898
|
dropdown.remove();
|
|
2832
2899
|
dropdown = null;
|
|
@@ -2861,7 +2928,7 @@ class FTable extends FTableEventEmitter {
|
|
|
2861
2928
|
dropdown.style.zIndex = '10000';
|
|
2862
2929
|
|
|
2863
2930
|
// Adjust horizontal position if needed
|
|
2864
|
-
|
|
2931
|
+
const dropdownRect = dropdown.getBoundingClientRect();
|
|
2865
2932
|
const viewportWidth = window.innerWidth;
|
|
2866
2933
|
if (dropdownRect.right > viewportWidth) {
|
|
2867
2934
|
left = Math.max(10, viewportWidth - dropdownRect.width - 10);
|
|
@@ -2958,7 +3025,12 @@ class FTable extends FTableEventEmitter {
|
|
|
2958
3025
|
// Create dropdown
|
|
2959
3026
|
dropdown = FTableDOMHelper.create('div', {
|
|
2960
3027
|
className: 'ftable-multiselect-dropdown',
|
|
2961
|
-
parent: document.body
|
|
3028
|
+
parent: document.body,
|
|
3029
|
+
attributes: {
|
|
3030
|
+
tabindex: '-1',
|
|
3031
|
+
role: 'listbox',
|
|
3032
|
+
'aria-multiselectable': 'true'
|
|
3033
|
+
}
|
|
2962
3034
|
});
|
|
2963
3035
|
|
|
2964
3036
|
// Populate options
|
|
@@ -2967,6 +3039,37 @@ class FTable extends FTableEventEmitter {
|
|
|
2967
3039
|
// Position dropdown
|
|
2968
3040
|
positionDropdown();
|
|
2969
3041
|
|
|
3042
|
+
// dropdown focus
|
|
3043
|
+
dropdown.focus();
|
|
3044
|
+
|
|
3045
|
+
// Add keyboard navigation
|
|
3046
|
+
dropdown.addEventListener('keydown', (e) => {
|
|
3047
|
+
if (e.key === 'Escape') {
|
|
3048
|
+
closeDropdown();
|
|
3049
|
+
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
3050
|
+
e.preventDefault();
|
|
3051
|
+
// Navigate between options
|
|
3052
|
+
const checkboxes = Array.from(dropdown.querySelectorAll('.ftable-multiselect-checkbox'));
|
|
3053
|
+
const current = document.activeElement;
|
|
3054
|
+
const currentIndex = checkboxes.indexOf(current);
|
|
3055
|
+
|
|
3056
|
+
let nextIndex;
|
|
3057
|
+
if (e.key === 'ArrowDown') {
|
|
3058
|
+
nextIndex = currentIndex < checkboxes.length - 1 ? currentIndex + 1 : 0;
|
|
3059
|
+
} else {
|
|
3060
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : checkboxes.length - 1;
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
checkboxes[nextIndex].focus();
|
|
3064
|
+
} else if (e.key === ' ' || e.key === 'Enter') {
|
|
3065
|
+
e.preventDefault();
|
|
3066
|
+
// Toggle the focused checkbox
|
|
3067
|
+
if (document.activeElement.classList.contains('ftable-multiselect-checkbox')) {
|
|
3068
|
+
document.activeElement.click();
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
});
|
|
3072
|
+
|
|
2970
3073
|
// Handle clicks outside
|
|
2971
3074
|
dropdownOverlay.addEventListener('click', (event) => {
|
|
2972
3075
|
if (event.target === dropdownOverlay) {
|
|
@@ -2976,19 +3079,36 @@ class FTable extends FTableEventEmitter {
|
|
|
2976
3079
|
|
|
2977
3080
|
// Reposition on scroll/resize
|
|
2978
3081
|
const repositionHandler = () => positionDropdown();
|
|
2979
|
-
|
|
3082
|
+
const scrollHandler = (e) => {
|
|
3083
|
+
if (dropdown && dropdown.contains(e.target)) {
|
|
3084
|
+
return; // Allow scrolling inside dropdown
|
|
3085
|
+
}
|
|
3086
|
+
positionDropdown();
|
|
3087
|
+
};
|
|
3088
|
+
const selectedResizeObserver = new ResizeObserver(() => {
|
|
3089
|
+
positionDropdown();
|
|
3090
|
+
});
|
|
3091
|
+
window.addEventListener('scroll', scrollHandler, true);
|
|
2980
3092
|
window.addEventListener('resize', repositionHandler);
|
|
3093
|
+
selectedResizeObserver.observe(selectedDisplay);
|
|
2981
3094
|
|
|
2982
3095
|
// Store cleanup function
|
|
2983
3096
|
container._cleanupHandlers = () => {
|
|
2984
|
-
window.removeEventListener('scroll',
|
|
3097
|
+
window.removeEventListener('scroll', scrollHandler, true);
|
|
2985
3098
|
window.removeEventListener('resize', repositionHandler);
|
|
3099
|
+
selectedResizeObserver.disconnect();
|
|
2986
3100
|
};
|
|
2987
3101
|
}
|
|
2988
3102
|
};
|
|
2989
3103
|
|
|
2990
3104
|
display.addEventListener('click', toggleDropdown);
|
|
2991
3105
|
toggleBtn.addEventListener('click', toggleDropdown);
|
|
3106
|
+
display.addEventListener('keydown', (e) => {
|
|
3107
|
+
if (e.key === 'ArrowDown' || e.key === 'Enter') {
|
|
3108
|
+
e.preventDefault();
|
|
3109
|
+
toggleDropdown();
|
|
3110
|
+
}
|
|
3111
|
+
});
|
|
2992
3112
|
|
|
2993
3113
|
// Add reset method to container
|
|
2994
3114
|
container.resetMultiSelect = () => {
|