|
7 | 7 | (function (window, document, Math) { |
8 | 8 | var ctx = document.createElement('canvas').getContext('2d'); |
9 | 9 | var currentColor = { r: 0, g: 0, b: 0, h: 0, s: 0, v: 0, a: 1 }; |
10 | | - var container, picker, colorArea, colorAreaDims, colorMarker, colorPreview, colorValue, clearButton, |
11 | | - closeButton, hueSlider, hueMarker, alphaSlider, alphaMarker, currentEl, currentFormat, oldColor; |
| 10 | + var container, picker, colorArea, colorAreaDims, colorMarker, colorPreview, colorValue, clearButton, closeButton, |
| 11 | + hueSlider, hueMarker, alphaSlider, alphaMarker, currentEl, currentFormat, oldColor, keyboardNav; |
12 | 12 |
|
13 | 13 | // Default settings |
14 | 14 | var settings = { |
|
322 | 322 | colorValue.select(); |
323 | 323 | } |
324 | 324 |
|
| 325 | + // Always focus the first element when using keyboard navigation |
| 326 | + if (keyboardNav || settings.swatchesOnly) { |
| 327 | + getFocusableElements().shift().focus(); |
| 328 | + } |
| 329 | + |
325 | 330 | // Trigger an "open" event |
326 | 331 | currentEl.dispatchEvent(new Event('open', { bubbles: true })); |
327 | 332 | }); |
|
1032 | 1037 | }); |
1033 | 1038 |
|
1034 | 1039 | addListener(document, 'mousedown', function (event) { |
| 1040 | + keyboardNav = false; |
1035 | 1041 | picker.classList.remove('clr-keyboard-nav'); |
1036 | 1042 | closePicker(); |
1037 | 1043 | }); |
1038 | 1044 |
|
1039 | 1045 | addListener(document, 'keydown', function (event) { |
| 1046 | + var key = event.key; |
| 1047 | + var target = event.target; |
| 1048 | + var shiftKey = event.shiftKey; |
1040 | 1049 | var navKeys = ['Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']; |
1041 | 1050 |
|
1042 | | - if (event.key === 'Escape') { |
| 1051 | + if (key === 'Escape') { |
1043 | 1052 | closePicker(true); |
1044 | 1053 |
|
1045 | 1054 | // Display focus rings when using the keyboard |
1046 | | - } else if (navKeys.includes(event.key)) { |
| 1055 | + } else if (navKeys.includes(key)) { |
| 1056 | + keyboardNav = true; |
1047 | 1057 | picker.classList.add('clr-keyboard-nav'); |
1048 | 1058 | } |
| 1059 | + |
| 1060 | + // Trap the focus within the color picker while it's open |
| 1061 | + if (key === 'Tab' && target.matches('.clr-picker *')) { |
| 1062 | + var focusables = getFocusableElements(); |
| 1063 | + var firstFocusable = focusables.shift(); |
| 1064 | + var lastFocusable = focusables.pop(); |
| 1065 | + |
| 1066 | + if (shiftKey && target === firstFocusable) { |
| 1067 | + lastFocusable.focus(); |
| 1068 | + event.preventDefault(); |
| 1069 | + } else if (!shiftKey && target === lastFocusable) { |
| 1070 | + firstFocusable.focus(); |
| 1071 | + event.preventDefault(); |
| 1072 | + } |
| 1073 | + } |
1049 | 1074 | }); |
1050 | 1075 |
|
1051 | 1076 | addListener(document, 'click', '.clr-field button', function (event) { |
|
1077 | 1102 | addListener(alphaSlider, 'input', setAlpha); |
1078 | 1103 | } |
1079 | 1104 |
|
| 1105 | + /** |
| 1106 | + * Return a list of focusable elements within the color picker. |
| 1107 | + * @return {array} The list of focusable DOM elemnts. |
| 1108 | + */ |
| 1109 | + function getFocusableElements() { |
| 1110 | + var controls = Array.from(picker.querySelectorAll('input, button')); |
| 1111 | + var focusables = controls.filter(function (node) {return !!node.offsetWidth;}); |
| 1112 | + |
| 1113 | + return focusables; |
| 1114 | + } |
| 1115 | + |
1080 | 1116 | /** |
1081 | 1117 | * Shortcut for getElementById to optimize the minified JS. |
1082 | 1118 | * @param {string} id The element id. |
|
1105 | 1141 | }); |
1106 | 1142 |
|
1107 | 1143 | // If the selector is not a string then it's a function |
1108 | | - // in which case we need regular event listener |
| 1144 | + // in which case we need a regular event listener |
1109 | 1145 | } else { |
1110 | 1146 | fn = selector; |
1111 | 1147 | context.addEventListener(type, fn); |
|
0 commit comments