- commit
- e9d498df267cf94a69b418a27cb643cab5c15b63
- parent
- 39ea81e063ddcc4b1f74ab588d4359c669582fcd
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2025-09-02 05:43
allow to navigate values with keyboard fixes #3 does not convey the focused value to AT though
Diffstat
| M | select.css | 4 | ++++ |
| M | select.js | 29 | ++++++++++++++++++----------- |
| M | tags.js | 17 | ++++++++++------- |
| M | values.js | 43 | +++++++++++++++++++++++++++++++++++++++++-- |
4 files changed, 73 insertions, 20 deletions
diff --git a/select.css b/select.css
@@ -103,6 +103,10 @@ 103 103 border: 1px solid var(--border); 104 104 border-radius: 0.3em; 105 105 } -1 106 .select__value--focus { -1 107 background: var(--focus-bg); -1 108 color: var(--focus-text); -1 109 } 106 110 107 111 .select__measure { 108 112 inline-size: 0;
diff --git a/select.js b/select.js
@@ -8,6 +8,7 @@ export class Select {
8 8 this.id = options.id || randomString(8);
9 9 this.inputClass = options.inputClass || original.dataset.selectInputClass;
10 10 this.valueClass = options.valueClass || original.dataset.selectValueClass;
-1 11 this.valueFocusClass = options.valueFocusClass || original.dataset.selectValueFocusClass;
11 12
12 13 this.focus = -1;
13 14 this.indexMap = [];
@@ -28,7 +29,7 @@ export class Select {
28 29 if (this.original.multiple) {
29 30 var inputWrapper = create('<div class="select__input">');
30 31 inputWrapper.append(this.input);
31 -1 this.values = new Values(this.input, `${this.id}-values`, this.valueClass);
-1 32 this.values = new Values(this.input, `${this.id}-values`, this.valueClass, this.valueFocusClass);
32 33 this.wrapper.append(inputWrapper);
33 34 } else {
34 35 this.wrapper.append(this.input);
@@ -91,7 +92,9 @@ export class Select {
91 92 options[this.focus].scrollIntoView({block: 'nearest'});
92 93 } else {
93 94 this.input.setAttribute('aria-expanded', 'false');
94 -1 this.input.setAttribute('aria-activedescendant', '');
-1 95 if (!this.original.multiple || !this.values.focus) {
-1 96 this.input.removeAttribute('aria-activedescendant');
-1 97 }
95 98 }
96 99 }
97 100
@@ -224,15 +227,19 @@ export class Select {
224 227 this.open(true);
225 228 }
226 229 }
227 -1 if (this.original.multiple && !this.input.value && event.key === 'Backspace') {
228 -1 event.preventDefault();
229 -1 var n = this.original.selectedOptions.length;
230 -1 if (n) {
231 -1 var op = this.original.selectedOptions[n - 1];
232 -1 op.selected = false;
233 -1 this.updateInput();
234 -1 this.input.value = op.label;
235 -1 this.input.dispatchEvent(new Event('input'));
-1 230 if (this.original.multiple && !this.input.value) {
-1 231 if (this.values.onkeydown(event)) {
-1 232 this.close();
-1 233 } else if (event.key === 'Backspace') {
-1 234 event.preventDefault();
-1 235 var n = this.original.selectedOptions.length;
-1 236 if (n) {
-1 237 var op = this.original.selectedOptions[n - 1];
-1 238 op.selected = false;
-1 239 this.updateInput();
-1 240 this.input.value = op.label;
-1 241 this.input.dispatchEvent(new Event('input'));
-1 242 }
236 243 }
237 244 }
238 245 }
diff --git a/tags.js b/tags.js
@@ -8,6 +8,7 @@ export class TagInput {
8 8 this.id = options.id || randomString(8);
9 9 this.inputClass = options.inputClass || original.dataset.tagsInputClass;
10 10 this.valueClass = options.valueClass || original.dataset.tagsValueClass;
-1 11 this.valueFocusClass = options.valueFocusClass || original.dataset.tagsValueFocusClass;
11 12 this.separators = options.separators || (original.dataset.tagsSeparators || 'Enter').split(/\s+/);
12 13
13 14 this.createElements();
@@ -32,7 +33,7 @@ export class TagInput {
32 33 this.input.setAttribute('aria-labelledby', labels);
33 34 this.inputWrapper.append(this.input);
34 35
35 -1 this.values = new Values(this.input, `${this.id}-values`, this.valueClass);
-1 36 this.values = new Values(this.input, `${this.id}-values`, this.valueClass, this.valueFocusClass);
36 37
37 38 this.datalist = document.createElement('datalist');
38 39 this.datalist.innerHTML = this.original.innerHTML;
@@ -77,8 +78,14 @@ export class TagInput {
77 78 }
78 79
79 80 onkeydown(event) {
80 -1 if (event.key === 'Backspace') {
81 -1 if (!this.input.value) {
-1 81 if (this.input.value) {
-1 82 if (this.separators.includes(event.key)) {
-1 83 this.onchange(event);
-1 84 }
-1 85 } else {
-1 86 if (this.values.onkeydown(event)) {
-1 87 // nothing to do
-1 88 } else if (event.key === 'Backspace') {
82 89 event.preventDefault();
83 90 var n = this.original.selectedOptions.length;
84 91 if (n) {
@@ -89,10 +96,6 @@ export class TagInput {
89 96 this.input.dispatchEvent(new Event('input'));
90 97 }
91 98 }
92 -1 } else if (this.separators.includes(event.key)) {
93 -1 if (this.input.value) {
94 -1 this.onchange(event);
95 -1 }
96 99 }
97 100 }
98 101
diff --git a/values.js b/values.js
@@ -1,10 +1,12 @@ 1 1 import { create } from './utils.js'; 2 2 3 3 export class Values {4 -1 constructor(input, id, valueClass) {-1 4 constructor(input, id, valueClass, valueFocusClass) { 5 5 this.gap = 4; -1 6 this.focus = 0; 6 7 this.input = input; 7 8 this.valueClass = valueClass || 'select__value'; -1 9 this.valueFocusClass = valueFocusClass || 'select__value--focus'; 8 10 9 11 this.el = create('<ul class="select__values">'); 10 12 this.el.id = id; @@ -21,6 +23,26 @@ export class Values { 21 23 window.addEventListener('resize', this.updateSize.bind(this)); 22 24 } 23 25 -1 26 setFocus(k) { -1 27 var n = this.el.children.length; -1 28 k = Math.min(0, Math.max(-n, k)); -1 29 if (k !== this.focus) { -1 30 if (this.focus !== 0) { -1 31 const el = this.el.children[n + this.focus]; -1 32 el.classList.remove(this.valueFocusClass); -1 33 if (this.input.getAttribute('aria-activedescendant') === el.id) { -1 34 this.input.removeAttribute('aria-activedescendant'); -1 35 } -1 36 } -1 37 this.focus = k; -1 38 if (this.focus !== 0) { -1 39 const el = this.el.children[n + this.focus]; -1 40 el.classList.add(this.valueFocusClass); -1 41 this.input.setAttribute('aria-activedescendant', el.id); -1 42 } -1 43 } -1 44 } -1 45 24 46 updateSize() { 25 47 this.input.style.paddingTop = null; 26 48 this.input.style.lineHeight = null; @@ -64,10 +86,12 @@ export class Values { 64 86 } 65 87 66 88 update(original, onChange) { -1 89 this.setFocus(0); 67 90 this.el.innerHTML = '';68 -1 Array.from(original.options).forEach(op => {-1 91 Array.from(original.options).forEach((op, i) => { 69 92 if (op.selected && op.label) { 70 93 var li = document.createElement('li'); -1 94 li.id = `${this.el.id}-${i}`; 71 95 li.textContent = op.label; 72 96 li.className = this.valueClass; 73 97 li.onclick = () => { @@ -81,4 +105,19 @@ export class Values { 81 105 }); 82 106 this.updateSize(); 83 107 } -1 108 -1 109 onkeydown(event) { -1 110 if (this.focus && (event.key === 'Backspace' || event.key === 'Delete')) { -1 111 var n = this.el.children.length; -1 112 this.el.children[n + this.focus].onclick(); -1 113 } else if (event.key === 'ArrowLeft') { -1 114 this.setFocus(this.focus - 1); -1 115 } else if (event.key === 'ArrowRight') { -1 116 this.setFocus(this.focus + 1); -1 117 } else { -1 118 this.setFocus(0); -1 119 return false; -1 120 } -1 121 return true; -1 122 } 84 123 }