UNPKG

5.72 kBJavaScriptView Raw
1const DropdownIcon = "<svg viewbox=\"0 0 18 18\"><polygon class=\"ql-stroke\" points=\"7 11 9 13 11 11 7 11\"/><polygon class=\"ql-stroke\" points=\"7 7 9 5 11 7 7 7\"/></svg>";
2let optionsCounter = 0;
3function toggleAriaAttribute(element, attribute) {
4 element.setAttribute(attribute, `${!(element.getAttribute(attribute) === 'true')}`);
5}
6class Picker {
7 constructor(select) {
8 this.select = select;
9 this.container = document.createElement('span');
10 this.buildPicker();
11 this.select.style.display = 'none';
12 // @ts-expect-error Fix me later
13 this.select.parentNode.insertBefore(this.container, this.select);
14 this.label.addEventListener('mousedown', () => {
15 this.togglePicker();
16 });
17 this.label.addEventListener('keydown', event => {
18 switch (event.key) {
19 case 'Enter':
20 this.togglePicker();
21 break;
22 case 'Escape':
23 this.escape();
24 event.preventDefault();
25 break;
26 default:
27 }
28 });
29 this.select.addEventListener('change', this.update.bind(this));
30 }
31 togglePicker() {
32 this.container.classList.toggle('ql-expanded');
33 // Toggle aria-expanded and aria-hidden to make the picker accessible
34 toggleAriaAttribute(this.label, 'aria-expanded');
35 // @ts-expect-error
36 toggleAriaAttribute(this.options, 'aria-hidden');
37 }
38 buildItem(option) {
39 const item = document.createElement('span');
40 // @ts-expect-error
41 item.tabIndex = '0';
42 item.setAttribute('role', 'button');
43 item.classList.add('ql-picker-item');
44 const value = option.getAttribute('value');
45 if (value) {
46 item.setAttribute('data-value', value);
47 }
48 if (option.textContent) {
49 item.setAttribute('data-label', option.textContent);
50 }
51 item.addEventListener('click', () => {
52 this.selectItem(item, true);
53 });
54 item.addEventListener('keydown', event => {
55 switch (event.key) {
56 case 'Enter':
57 this.selectItem(item, true);
58 event.preventDefault();
59 break;
60 case 'Escape':
61 this.escape();
62 event.preventDefault();
63 break;
64 default:
65 }
66 });
67 return item;
68 }
69 buildLabel() {
70 const label = document.createElement('span');
71 label.classList.add('ql-picker-label');
72 label.innerHTML = DropdownIcon;
73 // @ts-expect-error
74 label.tabIndex = '0';
75 label.setAttribute('role', 'button');
76 label.setAttribute('aria-expanded', 'false');
77 this.container.appendChild(label);
78 return label;
79 }
80 buildOptions() {
81 const options = document.createElement('span');
82 options.classList.add('ql-picker-options');
83
84 // Don't want screen readers to read this until options are visible
85 options.setAttribute('aria-hidden', 'true');
86 // @ts-expect-error
87 options.tabIndex = '-1';
88
89 // Need a unique id for aria-controls
90 options.id = `ql-picker-options-${optionsCounter}`;
91 optionsCounter += 1;
92 this.label.setAttribute('aria-controls', options.id);
93
94 // @ts-expect-error
95 this.options = options;
96 Array.from(this.select.options).forEach(option => {
97 const item = this.buildItem(option);
98 options.appendChild(item);
99 if (option.selected === true) {
100 this.selectItem(item);
101 }
102 });
103 this.container.appendChild(options);
104 }
105 buildPicker() {
106 Array.from(this.select.attributes).forEach(item => {
107 this.container.setAttribute(item.name, item.value);
108 });
109 this.container.classList.add('ql-picker');
110 this.label = this.buildLabel();
111 this.buildOptions();
112 }
113 escape() {
114 // Close menu and return focus to trigger label
115 this.close();
116 // Need setTimeout for accessibility to ensure that the browser executes
117 // focus on the next process thread and after any DOM content changes
118 setTimeout(() => this.label.focus(), 1);
119 }
120 close() {
121 this.container.classList.remove('ql-expanded');
122 this.label.setAttribute('aria-expanded', 'false');
123 // @ts-expect-error
124 this.options.setAttribute('aria-hidden', 'true');
125 }
126 selectItem(item) {
127 let trigger = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
128 const selected = this.container.querySelector('.ql-selected');
129 if (item === selected) return;
130 if (selected != null) {
131 selected.classList.remove('ql-selected');
132 }
133 if (item == null) return;
134 item.classList.add('ql-selected');
135 // @ts-expect-error Fix me later
136 this.select.selectedIndex = Array.from(item.parentNode.children).indexOf(item);
137 if (item.hasAttribute('data-value')) {
138 // @ts-expect-error Fix me later
139 this.label.setAttribute('data-value', item.getAttribute('data-value'));
140 } else {
141 this.label.removeAttribute('data-value');
142 }
143 if (item.hasAttribute('data-label')) {
144 // @ts-expect-error Fix me later
145 this.label.setAttribute('data-label', item.getAttribute('data-label'));
146 } else {
147 this.label.removeAttribute('data-label');
148 }
149 if (trigger) {
150 this.select.dispatchEvent(new Event('change'));
151 this.close();
152 }
153 }
154 update() {
155 let option;
156 if (this.select.selectedIndex > -1) {
157 const item =
158 // @ts-expect-error Fix me later
159 this.container.querySelector('.ql-picker-options').children[this.select.selectedIndex];
160 option = this.select.options[this.select.selectedIndex];
161 // @ts-expect-error
162 this.selectItem(item);
163 } else {
164 this.selectItem(null);
165 }
166 const isActive = option != null && option !== this.select.querySelector('option[selected]');
167 this.label.classList.toggle('ql-active', isActive);
168 }
169}
170export default Picker;
171//# sourceMappingURL=picker.js.map
\No newline at end of file