@liedekef/ftable 1.1.50 → 1.1.51
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 +122 -12
- package/ftable.js +122 -12
- package/ftable.min.js +2 -2
- package/ftable.umd.js +122 -12
- package/package.json +1 -1
- package/themes/basic/ftable_basic.css +4 -0
- package/themes/basic/ftable_basic.min.css +1 -1
- package/themes/ftable_theme_base.less +5 -0
- package/themes/lightcolor/blue/ftable.css +4 -0
- package/themes/lightcolor/blue/ftable.min.css +1 -1
- package/themes/lightcolor/gray/ftable.css +4 -0
- package/themes/lightcolor/gray/ftable.min.css +1 -1
- package/themes/lightcolor/green/ftable.css +4 -0
- package/themes/lightcolor/green/ftable.min.css +1 -1
- package/themes/lightcolor/orange/ftable.css +4 -0
- package/themes/lightcolor/orange/ftable.min.css +1 -1
- package/themes/lightcolor/red/ftable.css +4 -0
- package/themes/lightcolor/red/ftable.min.css +1 -1
- package/themes/metro/blue/ftable.css +4 -0
- package/themes/metro/blue/ftable.min.css +1 -1
- package/themes/metro/brown/ftable.css +4 -0
- package/themes/metro/brown/ftable.min.css +1 -1
- package/themes/metro/crimson/ftable.css +4 -0
- package/themes/metro/crimson/ftable.min.css +1 -1
- package/themes/metro/darkgray/ftable.css +4 -0
- package/themes/metro/darkgray/ftable.min.css +1 -1
- package/themes/metro/darkorange/ftable.css +4 -0
- package/themes/metro/darkorange/ftable.min.css +1 -1
- package/themes/metro/green/ftable.css +4 -0
- package/themes/metro/green/ftable.min.css +1 -1
- package/themes/metro/lightgray/ftable.css +4 -0
- package/themes/metro/lightgray/ftable.min.css +1 -1
- package/themes/metro/pink/ftable.css +4 -0
- package/themes/metro/pink/ftable.min.css +1 -1
- package/themes/metro/purple/ftable.css +4 -0
- package/themes/metro/purple/ftable.min.css +1 -1
- package/themes/metro/red/ftable.css +4 -0
- 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) {
|
|
@@ -1660,13 +1703,19 @@ class FTableFormBuilder {
|
|
|
1660
1703
|
});
|
|
1661
1704
|
|
|
1662
1705
|
// Reposition on scroll/resize
|
|
1706
|
+
const scrollHandler = (e) => {
|
|
1707
|
+
if (dropdown && dropdown.contains(e.target)) {
|
|
1708
|
+
return; // Allow scrolling inside dropdown
|
|
1709
|
+
}
|
|
1710
|
+
positionDropdown();
|
|
1711
|
+
};
|
|
1663
1712
|
const repositionHandler = () => positionDropdown();
|
|
1664
|
-
window.addEventListener('scroll',
|
|
1713
|
+
window.addEventListener('scroll', scrollHandler, true);
|
|
1665
1714
|
window.addEventListener('resize', repositionHandler);
|
|
1666
1715
|
|
|
1667
1716
|
// Store cleanup function
|
|
1668
1717
|
container._cleanupHandlers = () => {
|
|
1669
|
-
window.removeEventListener('scroll',
|
|
1718
|
+
window.removeEventListener('scroll', scrollHandler, true);
|
|
1670
1719
|
window.removeEventListener('resize', repositionHandler);
|
|
1671
1720
|
};
|
|
1672
1721
|
}
|
|
@@ -1674,6 +1723,12 @@ class FTableFormBuilder {
|
|
|
1674
1723
|
|
|
1675
1724
|
display.addEventListener('click', toggleDropdown);
|
|
1676
1725
|
toggleBtn.addEventListener('click', toggleDropdown);
|
|
1726
|
+
display.addEventListener('keydown', (e) => {
|
|
1727
|
+
if (e.key === 'ArrowDown' || e.key === 'Enter') {
|
|
1728
|
+
e.preventDefault();
|
|
1729
|
+
toggleDropdown();
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1677
1732
|
|
|
1678
1733
|
// Clean up when container is removed from DOM
|
|
1679
1734
|
const observer = new MutationObserver((mutations) => {
|
|
@@ -2732,7 +2787,10 @@ class FTable extends FTableEventEmitter {
|
|
|
2732
2787
|
// Create display area
|
|
2733
2788
|
const display = FTableDOMHelper.create('div', {
|
|
2734
2789
|
className: 'ftable-multiselect-display',
|
|
2735
|
-
parent: container
|
|
2790
|
+
parent: container,
|
|
2791
|
+
attributes: {
|
|
2792
|
+
tabindex: '0' // Makes it focusable and in tab order
|
|
2793
|
+
}
|
|
2736
2794
|
});
|
|
2737
2795
|
|
|
2738
2796
|
const selectedDisplay = FTableDOMHelper.create('div', {
|
|
@@ -2752,7 +2810,10 @@ class FTable extends FTableEventEmitter {
|
|
|
2752
2810
|
type: 'button',
|
|
2753
2811
|
className: 'ftable-multiselect-toggle',
|
|
2754
2812
|
innerHTML: '▼',
|
|
2755
|
-
parent: display
|
|
2813
|
+
parent: display,
|
|
2814
|
+
attributes: {
|
|
2815
|
+
tabindex: '-1' // this skips regular focus when tabbing
|
|
2816
|
+
}
|
|
2756
2817
|
});
|
|
2757
2818
|
|
|
2758
2819
|
// Dropdown and overlay will be created on demand and appended to body
|
|
@@ -2826,6 +2887,7 @@ class FTable extends FTableEventEmitter {
|
|
|
2826
2887
|
|
|
2827
2888
|
// Function to close dropdown
|
|
2828
2889
|
const closeDropdown = () => {
|
|
2890
|
+
display.focus(); // Return focus to the trigger
|
|
2829
2891
|
if (dropdown) {
|
|
2830
2892
|
dropdown.remove();
|
|
2831
2893
|
dropdown = null;
|
|
@@ -2860,7 +2922,7 @@ class FTable extends FTableEventEmitter {
|
|
|
2860
2922
|
dropdown.style.zIndex = '10000';
|
|
2861
2923
|
|
|
2862
2924
|
// Adjust horizontal position if needed
|
|
2863
|
-
|
|
2925
|
+
const dropdownRect = dropdown.getBoundingClientRect();
|
|
2864
2926
|
const viewportWidth = window.innerWidth;
|
|
2865
2927
|
if (dropdownRect.right > viewportWidth) {
|
|
2866
2928
|
left = Math.max(10, viewportWidth - dropdownRect.width - 10);
|
|
@@ -2957,7 +3019,12 @@ class FTable extends FTableEventEmitter {
|
|
|
2957
3019
|
// Create dropdown
|
|
2958
3020
|
dropdown = FTableDOMHelper.create('div', {
|
|
2959
3021
|
className: 'ftable-multiselect-dropdown',
|
|
2960
|
-
parent: document.body
|
|
3022
|
+
parent: document.body,
|
|
3023
|
+
attributes: {
|
|
3024
|
+
tabindex: '-1',
|
|
3025
|
+
role: 'listbox',
|
|
3026
|
+
'aria-multiselectable': 'true'
|
|
3027
|
+
}
|
|
2961
3028
|
});
|
|
2962
3029
|
|
|
2963
3030
|
// Populate options
|
|
@@ -2966,6 +3033,37 @@ class FTable extends FTableEventEmitter {
|
|
|
2966
3033
|
// Position dropdown
|
|
2967
3034
|
positionDropdown();
|
|
2968
3035
|
|
|
3036
|
+
// dropdown focus
|
|
3037
|
+
dropdown.focus();
|
|
3038
|
+
|
|
3039
|
+
// Add keyboard navigation
|
|
3040
|
+
dropdown.addEventListener('keydown', (e) => {
|
|
3041
|
+
if (e.key === 'Escape') {
|
|
3042
|
+
closeDropdown();
|
|
3043
|
+
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
3044
|
+
e.preventDefault();
|
|
3045
|
+
// Navigate between options
|
|
3046
|
+
const checkboxes = Array.from(dropdown.querySelectorAll('.ftable-multiselect-checkbox'));
|
|
3047
|
+
const current = document.activeElement;
|
|
3048
|
+
const currentIndex = checkboxes.indexOf(current);
|
|
3049
|
+
|
|
3050
|
+
let nextIndex;
|
|
3051
|
+
if (e.key === 'ArrowDown') {
|
|
3052
|
+
nextIndex = currentIndex < checkboxes.length - 1 ? currentIndex + 1 : 0;
|
|
3053
|
+
} else {
|
|
3054
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : checkboxes.length - 1;
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
checkboxes[nextIndex].focus();
|
|
3058
|
+
} else if (e.key === ' ' || e.key === 'Enter') {
|
|
3059
|
+
e.preventDefault();
|
|
3060
|
+
// Toggle the focused checkbox
|
|
3061
|
+
if (document.activeElement.classList.contains('ftable-multiselect-checkbox')) {
|
|
3062
|
+
document.activeElement.click();
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
});
|
|
3066
|
+
|
|
2969
3067
|
// Handle clicks outside
|
|
2970
3068
|
dropdownOverlay.addEventListener('click', (event) => {
|
|
2971
3069
|
if (event.target === dropdownOverlay) {
|
|
@@ -2974,13 +3072,19 @@ class FTable extends FTableEventEmitter {
|
|
|
2974
3072
|
});
|
|
2975
3073
|
|
|
2976
3074
|
// Reposition on scroll/resize
|
|
3075
|
+
const scrollHandler = (e) => {
|
|
3076
|
+
if (dropdown && dropdown.contains(e.target)) {
|
|
3077
|
+
return; // Allow scrolling inside dropdown
|
|
3078
|
+
}
|
|
3079
|
+
positionDropdown();
|
|
3080
|
+
};
|
|
2977
3081
|
const repositionHandler = () => positionDropdown();
|
|
2978
|
-
window.addEventListener('scroll',
|
|
3082
|
+
window.addEventListener('scroll', scrollHandler, true);
|
|
2979
3083
|
window.addEventListener('resize', repositionHandler);
|
|
2980
3084
|
|
|
2981
3085
|
// Store cleanup function
|
|
2982
3086
|
container._cleanupHandlers = () => {
|
|
2983
|
-
window.removeEventListener('scroll',
|
|
3087
|
+
window.removeEventListener('scroll', scrollHandler, true);
|
|
2984
3088
|
window.removeEventListener('resize', repositionHandler);
|
|
2985
3089
|
};
|
|
2986
3090
|
}
|
|
@@ -2988,6 +3092,12 @@ class FTable extends FTableEventEmitter {
|
|
|
2988
3092
|
|
|
2989
3093
|
display.addEventListener('click', toggleDropdown);
|
|
2990
3094
|
toggleBtn.addEventListener('click', toggleDropdown);
|
|
3095
|
+
display.addEventListener('keydown', (e) => {
|
|
3096
|
+
if (e.key === 'ArrowDown' || e.key === 'Enter') {
|
|
3097
|
+
e.preventDefault();
|
|
3098
|
+
toggleDropdown();
|
|
3099
|
+
}
|
|
3100
|
+
});
|
|
2991
3101
|
|
|
2992
3102
|
// Add reset method to container
|
|
2993
3103
|
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) {
|
|
@@ -1661,13 +1704,19 @@ class FTableFormBuilder {
|
|
|
1661
1704
|
});
|
|
1662
1705
|
|
|
1663
1706
|
// Reposition on scroll/resize
|
|
1707
|
+
const scrollHandler = (e) => {
|
|
1708
|
+
if (dropdown && dropdown.contains(e.target)) {
|
|
1709
|
+
return; // Allow scrolling inside dropdown
|
|
1710
|
+
}
|
|
1711
|
+
positionDropdown();
|
|
1712
|
+
};
|
|
1664
1713
|
const repositionHandler = () => positionDropdown();
|
|
1665
|
-
window.addEventListener('scroll',
|
|
1714
|
+
window.addEventListener('scroll', scrollHandler, true);
|
|
1666
1715
|
window.addEventListener('resize', repositionHandler);
|
|
1667
1716
|
|
|
1668
1717
|
// Store cleanup function
|
|
1669
1718
|
container._cleanupHandlers = () => {
|
|
1670
|
-
window.removeEventListener('scroll',
|
|
1719
|
+
window.removeEventListener('scroll', scrollHandler, true);
|
|
1671
1720
|
window.removeEventListener('resize', repositionHandler);
|
|
1672
1721
|
};
|
|
1673
1722
|
}
|
|
@@ -1675,6 +1724,12 @@ class FTableFormBuilder {
|
|
|
1675
1724
|
|
|
1676
1725
|
display.addEventListener('click', toggleDropdown);
|
|
1677
1726
|
toggleBtn.addEventListener('click', toggleDropdown);
|
|
1727
|
+
display.addEventListener('keydown', (e) => {
|
|
1728
|
+
if (e.key === 'ArrowDown' || e.key === 'Enter') {
|
|
1729
|
+
e.preventDefault();
|
|
1730
|
+
toggleDropdown();
|
|
1731
|
+
}
|
|
1732
|
+
});
|
|
1678
1733
|
|
|
1679
1734
|
// Clean up when container is removed from DOM
|
|
1680
1735
|
const observer = new MutationObserver((mutations) => {
|
|
@@ -2733,7 +2788,10 @@ class FTable extends FTableEventEmitter {
|
|
|
2733
2788
|
// Create display area
|
|
2734
2789
|
const display = FTableDOMHelper.create('div', {
|
|
2735
2790
|
className: 'ftable-multiselect-display',
|
|
2736
|
-
parent: container
|
|
2791
|
+
parent: container,
|
|
2792
|
+
attributes: {
|
|
2793
|
+
tabindex: '0' // Makes it focusable and in tab order
|
|
2794
|
+
}
|
|
2737
2795
|
});
|
|
2738
2796
|
|
|
2739
2797
|
const selectedDisplay = FTableDOMHelper.create('div', {
|
|
@@ -2753,7 +2811,10 @@ class FTable extends FTableEventEmitter {
|
|
|
2753
2811
|
type: 'button',
|
|
2754
2812
|
className: 'ftable-multiselect-toggle',
|
|
2755
2813
|
innerHTML: '▼',
|
|
2756
|
-
parent: display
|
|
2814
|
+
parent: display,
|
|
2815
|
+
attributes: {
|
|
2816
|
+
tabindex: '-1' // this skips regular focus when tabbing
|
|
2817
|
+
}
|
|
2757
2818
|
});
|
|
2758
2819
|
|
|
2759
2820
|
// Dropdown and overlay will be created on demand and appended to body
|
|
@@ -2827,6 +2888,7 @@ class FTable extends FTableEventEmitter {
|
|
|
2827
2888
|
|
|
2828
2889
|
// Function to close dropdown
|
|
2829
2890
|
const closeDropdown = () => {
|
|
2891
|
+
display.focus(); // Return focus to the trigger
|
|
2830
2892
|
if (dropdown) {
|
|
2831
2893
|
dropdown.remove();
|
|
2832
2894
|
dropdown = null;
|
|
@@ -2861,7 +2923,7 @@ class FTable extends FTableEventEmitter {
|
|
|
2861
2923
|
dropdown.style.zIndex = '10000';
|
|
2862
2924
|
|
|
2863
2925
|
// Adjust horizontal position if needed
|
|
2864
|
-
|
|
2926
|
+
const dropdownRect = dropdown.getBoundingClientRect();
|
|
2865
2927
|
const viewportWidth = window.innerWidth;
|
|
2866
2928
|
if (dropdownRect.right > viewportWidth) {
|
|
2867
2929
|
left = Math.max(10, viewportWidth - dropdownRect.width - 10);
|
|
@@ -2958,7 +3020,12 @@ class FTable extends FTableEventEmitter {
|
|
|
2958
3020
|
// Create dropdown
|
|
2959
3021
|
dropdown = FTableDOMHelper.create('div', {
|
|
2960
3022
|
className: 'ftable-multiselect-dropdown',
|
|
2961
|
-
parent: document.body
|
|
3023
|
+
parent: document.body,
|
|
3024
|
+
attributes: {
|
|
3025
|
+
tabindex: '-1',
|
|
3026
|
+
role: 'listbox',
|
|
3027
|
+
'aria-multiselectable': 'true'
|
|
3028
|
+
}
|
|
2962
3029
|
});
|
|
2963
3030
|
|
|
2964
3031
|
// Populate options
|
|
@@ -2967,6 +3034,37 @@ class FTable extends FTableEventEmitter {
|
|
|
2967
3034
|
// Position dropdown
|
|
2968
3035
|
positionDropdown();
|
|
2969
3036
|
|
|
3037
|
+
// dropdown focus
|
|
3038
|
+
dropdown.focus();
|
|
3039
|
+
|
|
3040
|
+
// Add keyboard navigation
|
|
3041
|
+
dropdown.addEventListener('keydown', (e) => {
|
|
3042
|
+
if (e.key === 'Escape') {
|
|
3043
|
+
closeDropdown();
|
|
3044
|
+
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
3045
|
+
e.preventDefault();
|
|
3046
|
+
// Navigate between options
|
|
3047
|
+
const checkboxes = Array.from(dropdown.querySelectorAll('.ftable-multiselect-checkbox'));
|
|
3048
|
+
const current = document.activeElement;
|
|
3049
|
+
const currentIndex = checkboxes.indexOf(current);
|
|
3050
|
+
|
|
3051
|
+
let nextIndex;
|
|
3052
|
+
if (e.key === 'ArrowDown') {
|
|
3053
|
+
nextIndex = currentIndex < checkboxes.length - 1 ? currentIndex + 1 : 0;
|
|
3054
|
+
} else {
|
|
3055
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : checkboxes.length - 1;
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
checkboxes[nextIndex].focus();
|
|
3059
|
+
} else if (e.key === ' ' || e.key === 'Enter') {
|
|
3060
|
+
e.preventDefault();
|
|
3061
|
+
// Toggle the focused checkbox
|
|
3062
|
+
if (document.activeElement.classList.contains('ftable-multiselect-checkbox')) {
|
|
3063
|
+
document.activeElement.click();
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
});
|
|
3067
|
+
|
|
2970
3068
|
// Handle clicks outside
|
|
2971
3069
|
dropdownOverlay.addEventListener('click', (event) => {
|
|
2972
3070
|
if (event.target === dropdownOverlay) {
|
|
@@ -2975,13 +3073,19 @@ class FTable extends FTableEventEmitter {
|
|
|
2975
3073
|
});
|
|
2976
3074
|
|
|
2977
3075
|
// Reposition on scroll/resize
|
|
3076
|
+
const scrollHandler = (e) => {
|
|
3077
|
+
if (dropdown && dropdown.contains(e.target)) {
|
|
3078
|
+
return; // Allow scrolling inside dropdown
|
|
3079
|
+
}
|
|
3080
|
+
positionDropdown();
|
|
3081
|
+
};
|
|
2978
3082
|
const repositionHandler = () => positionDropdown();
|
|
2979
|
-
window.addEventListener('scroll',
|
|
3083
|
+
window.addEventListener('scroll', scrollHandler, true);
|
|
2980
3084
|
window.addEventListener('resize', repositionHandler);
|
|
2981
3085
|
|
|
2982
3086
|
// Store cleanup function
|
|
2983
3087
|
container._cleanupHandlers = () => {
|
|
2984
|
-
window.removeEventListener('scroll',
|
|
3088
|
+
window.removeEventListener('scroll', scrollHandler, true);
|
|
2985
3089
|
window.removeEventListener('resize', repositionHandler);
|
|
2986
3090
|
};
|
|
2987
3091
|
}
|
|
@@ -2989,6 +3093,12 @@ class FTable extends FTableEventEmitter {
|
|
|
2989
3093
|
|
|
2990
3094
|
display.addEventListener('click', toggleDropdown);
|
|
2991
3095
|
toggleBtn.addEventListener('click', toggleDropdown);
|
|
3096
|
+
display.addEventListener('keydown', (e) => {
|
|
3097
|
+
if (e.key === 'ArrowDown' || e.key === 'Enter') {
|
|
3098
|
+
e.preventDefault();
|
|
3099
|
+
toggleDropdown();
|
|
3100
|
+
}
|
|
3101
|
+
});
|
|
2992
3102
|
|
|
2993
3103
|
// Add reset method to container
|
|
2994
3104
|
container.resetMultiSelect = () => {
|