@vaadin/a11y-base 25.2.0-alpha8 → 25.2.0-beta1

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.
@@ -10,7 +10,22 @@
10
10
  "kind": "mixin",
11
11
  "description": "A mixin to toggle the `active` attribute.\n\nThe attribute is set whenever the user activates the element by a pointer\nor presses an activation key on the element from the keyboard.\n\nThe attribute is removed as soon as the element is deactivated\nby the pointer or by releasing the activation key.",
12
12
  "name": "ActiveMixin",
13
- "members": [],
13
+ "members": [
14
+ {
15
+ "kind": "field",
16
+ "name": "disabled",
17
+ "privacy": "public",
18
+ "type": {
19
+ "text": "boolean"
20
+ },
21
+ "description": "If true, the user cannot interact with this element.",
22
+ "attribute": "disabled",
23
+ "inheritedFrom": {
24
+ "name": "DisabledMixin",
25
+ "module": "src/disabled-mixin.js"
26
+ }
27
+ }
28
+ ],
14
29
  "mixins": [
15
30
  {
16
31
  "name": "DisabledMixin",
@@ -25,6 +40,20 @@
25
40
  {
26
41
  "name": "superclass"
27
42
  }
43
+ ],
44
+ "attributes": [
45
+ {
46
+ "name": "disabled",
47
+ "type": {
48
+ "text": "boolean"
49
+ },
50
+ "description": "If true, the user cannot interact with this element.",
51
+ "fieldName": "disabled",
52
+ "inheritedFrom": {
53
+ "name": "DisabledMixin",
54
+ "module": "src/disabled-mixin.js"
55
+ }
56
+ }
28
57
  ]
29
58
  }
30
59
  ],
@@ -44,9 +73,76 @@
44
73
  "path": "src/delegate-focus-mixin.js",
45
74
  "declarations": [
46
75
  {
47
- "kind": "variable",
76
+ "kind": "mixin",
77
+ "description": "",
48
78
  "name": "DelegateFocusMixin",
49
- "description": "A mixin to forward focus to an element in the light DOM."
79
+ "members": [
80
+ {
81
+ "kind": "field",
82
+ "name": "autofocus",
83
+ "privacy": "public",
84
+ "type": {
85
+ "text": "boolean"
86
+ },
87
+ "description": "Specify that this control should have input focus when the page loads.",
88
+ "attribute": "autofocus"
89
+ },
90
+ {
91
+ "kind": "field",
92
+ "name": "disabled",
93
+ "privacy": "public",
94
+ "type": {
95
+ "text": "boolean"
96
+ },
97
+ "description": "If true, the user cannot interact with this element.",
98
+ "attribute": "disabled",
99
+ "inheritedFrom": {
100
+ "name": "DisabledMixin",
101
+ "module": "src/disabled-mixin.js"
102
+ }
103
+ }
104
+ ],
105
+ "attributes": [
106
+ {
107
+ "name": "autofocus",
108
+ "type": {
109
+ "text": "boolean"
110
+ },
111
+ "description": "Specify that this control should have input focus when the page loads.",
112
+ "fieldName": "autofocus"
113
+ },
114
+ {
115
+ "name": "disabled",
116
+ "type": {
117
+ "text": "boolean"
118
+ },
119
+ "description": "If true, the user cannot interact with this element.",
120
+ "fieldName": "disabled",
121
+ "inheritedFrom": {
122
+ "name": "DisabledMixin",
123
+ "module": "src/disabled-mixin.js"
124
+ }
125
+ }
126
+ ],
127
+ "mixins": [
128
+ {
129
+ "name": "FocusMixin",
130
+ "module": "src/focus-mixin.js"
131
+ },
132
+ {
133
+ "name": "TabindexMixin",
134
+ "module": "src/tabindex-mixin.js"
135
+ },
136
+ {
137
+ "name": "dedupeMixin",
138
+ "package": "@open-wc/dedupe-mixin"
139
+ }
140
+ ],
141
+ "parameters": [
142
+ {
143
+ "name": "superclass"
144
+ }
145
+ ]
50
146
  }
51
147
  ],
52
148
  "exports": [
@@ -65,9 +161,42 @@
65
161
  "path": "src/disabled-mixin.js",
66
162
  "declarations": [
67
163
  {
68
- "kind": "variable",
164
+ "kind": "mixin",
165
+ "description": "",
69
166
  "name": "DisabledMixin",
70
- "description": "A mixin to provide disabled property for field components."
167
+ "members": [
168
+ {
169
+ "kind": "field",
170
+ "name": "disabled",
171
+ "privacy": "public",
172
+ "type": {
173
+ "text": "boolean"
174
+ },
175
+ "description": "If true, the user cannot interact with this element.",
176
+ "attribute": "disabled"
177
+ }
178
+ ],
179
+ "attributes": [
180
+ {
181
+ "name": "disabled",
182
+ "type": {
183
+ "text": "boolean"
184
+ },
185
+ "description": "If true, the user cannot interact with this element.",
186
+ "fieldName": "disabled"
187
+ }
188
+ ],
189
+ "parameters": [
190
+ {
191
+ "name": "superclass"
192
+ }
193
+ ],
194
+ "mixins": [
195
+ {
196
+ "name": "dedupeMixin",
197
+ "package": "@open-wc/dedupe-mixin"
198
+ }
199
+ ]
71
200
  }
72
201
  ],
73
202
  "exports": [
@@ -86,9 +215,21 @@
86
215
  "path": "src/focus-mixin.js",
87
216
  "declarations": [
88
217
  {
89
- "kind": "variable",
218
+ "kind": "mixin",
219
+ "description": "",
90
220
  "name": "FocusMixin",
91
- "description": "A mixin to handle `focused` and `focus-ring` attributes based on focus."
221
+ "members": [],
222
+ "parameters": [
223
+ {
224
+ "name": "superclass"
225
+ }
226
+ ],
227
+ "mixins": [
228
+ {
229
+ "name": "dedupeMixin",
230
+ "package": "@open-wc/dedupe-mixin"
231
+ }
232
+ ]
92
233
  }
93
234
  ],
94
235
  "exports": [
@@ -140,9 +281,21 @@
140
281
  "path": "src/keyboard-mixin.js",
141
282
  "declarations": [
142
283
  {
143
- "kind": "variable",
284
+ "kind": "mixin",
285
+ "description": "",
144
286
  "name": "KeyboardMixin",
145
- "description": "A mixin that manages keyboard handling.\nThe mixin subscribes to the keyboard events while an actual implementation\nfor the event handlers is left to the client (a component or another mixin)."
287
+ "members": [],
288
+ "parameters": [
289
+ {
290
+ "name": "superclass"
291
+ }
292
+ ],
293
+ "mixins": [
294
+ {
295
+ "name": "dedupeMixin",
296
+ "package": "@open-wc/dedupe-mixin"
297
+ }
298
+ ]
146
299
  }
147
300
  ],
148
301
  "exports": [
@@ -265,8 +418,36 @@
265
418
  "kind": "mixin",
266
419
  "description": "A mixin to toggle the `tabindex` attribute.\n\nThe attribute is set to -1 whenever the user disables the element\nand restored with the last known value once the element is enabled.",
267
420
  "name": "TabindexMixin",
268
- "members": [],
269
- "attributes": [],
421
+ "members": [
422
+ {
423
+ "kind": "field",
424
+ "name": "disabled",
425
+ "privacy": "public",
426
+ "type": {
427
+ "text": "boolean"
428
+ },
429
+ "description": "If true, the user cannot interact with this element.",
430
+ "attribute": "disabled",
431
+ "inheritedFrom": {
432
+ "name": "DisabledMixin",
433
+ "module": "src/disabled-mixin.js"
434
+ }
435
+ }
436
+ ],
437
+ "attributes": [
438
+ {
439
+ "name": "disabled",
440
+ "type": {
441
+ "text": "boolean"
442
+ },
443
+ "description": "If true, the user cannot interact with this element.",
444
+ "fieldName": "disabled",
445
+ "inheritedFrom": {
446
+ "name": "DisabledMixin",
447
+ "module": "src/disabled-mixin.js"
448
+ }
449
+ }
450
+ ],
270
451
  "mixins": [
271
452
  {
272
453
  "name": "DisabledMixin",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/a11y-base",
3
- "version": "25.2.0-alpha8",
3
+ "version": "25.2.0-beta1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -32,15 +32,15 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@open-wc/dedupe-mixin": "^1.3.0",
35
- "@vaadin/component-base": "25.2.0-alpha8",
35
+ "@vaadin/component-base": "25.2.0-beta1",
36
36
  "lit": "^3.0.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@vaadin/chai-plugins": "25.2.0-alpha8",
40
- "@vaadin/test-runner-commands": "25.2.0-alpha8",
39
+ "@vaadin/chai-plugins": "25.2.0-beta1",
40
+ "@vaadin/test-runner-commands": "25.2.0-beta1",
41
41
  "@vaadin/testing-helpers": "^2.0.0",
42
42
  "sinon": "^21.0.2"
43
43
  },
44
44
  "customElements": "custom-elements.json",
45
- "gitHead": "2b82e20cdfc605b1187e9a24ae42869e1500ab68"
45
+ "gitHead": "471a23f60d1eb725f98a33f62cb9664d9c0a4163"
46
46
  }
@@ -15,10 +15,6 @@ import { KeyboardMixin } from './keyboard-mixin.js';
15
15
  *
16
16
  * The attribute is removed as soon as the element is deactivated
17
17
  * by the pointer or by releasing the activation key.
18
- *
19
- * @polymerMixin
20
- * @mixes DisabledMixin
21
- * @mixes KeyboardMixin
22
18
  */
23
19
  export const ActiveMixin = (superclass) =>
24
20
  class ActiveMixinClass extends DisabledMixin(KeyboardMixin(superclass)) {
package/src/announce.js CHANGED
@@ -23,7 +23,7 @@ let alertDebouncer;
23
23
  */
24
24
  export function announce(text, options = {}) {
25
25
  const mode = options.mode || 'polite';
26
- const timeout = options.timeout === undefined ? 150 : options.timeout;
26
+ const timeout = options.timeout ?? 150;
27
27
 
28
28
  if (mode === 'alert') {
29
29
  region.removeAttribute('aria-live');
@@ -27,7 +27,7 @@ let lockCount = 0;
27
27
  * @param {?Node} node
28
28
  * @return {boolean}
29
29
  */
30
- const isElement = (node) => node && node.nodeType === Node.ELEMENT_NODE;
30
+ const isElement = (node) => node?.nodeType === Node.ELEMENT_NODE;
31
31
 
32
32
  /**
33
33
  * @param {...unknown} args
@@ -3,13 +3,12 @@
3
3
  * Copyright (c) 2021 - 2026 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import type { ReactiveController } from 'lit';
7
6
 
8
7
  /**
9
8
  * A controller for handling modal state on the elements with `dialog` and `alertdialog` role.
10
9
  * See https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-modal
11
10
  */
12
- export class AriaModalController implements ReactiveController {
11
+ export class AriaModalController {
13
12
  /**
14
13
  * The controller host element.
15
14
  */
@@ -21,7 +20,7 @@ export class AriaModalController implements ReactiveController {
21
20
  */
22
21
  callback: () => HTMLElement | HTMLElement[];
23
22
 
24
- constructor(node: HTMLElement);
23
+ constructor(host: HTMLElement, callback?: () => HTMLElement | HTMLElement[]);
25
24
 
26
25
  /**
27
26
  * Make the controller host element modal by trapping focus inside it and hiding
@@ -9,237 +9,236 @@ import { TabindexMixin } from './tabindex-mixin.js';
9
9
 
10
10
  /**
11
11
  * A mixin to forward focus to an element in the light DOM.
12
- *
13
- * @polymerMixin
14
- * @mixes FocusMixin
15
- * @mixes TabindexMixin
16
12
  */
17
- export const DelegateFocusMixin = dedupeMixin(
18
- (superclass) =>
19
- class DelegateFocusMixinClass extends FocusMixin(TabindexMixin(superclass)) {
20
- static get properties() {
21
- return {
22
- /**
23
- * Specify that this control should have input focus when the page loads.
24
- */
25
- autofocus: {
26
- type: Boolean,
27
- },
28
-
29
- /**
30
- * A reference to the focusable element controlled by the mixin.
31
- * It can be an input, textarea, button or any element with tabindex > -1.
32
- *
33
- * Any component implementing this mixin is expected to provide it
34
- * by using `this._setFocusElement(input)` Polymer API.
35
- *
36
- * Toggling `tabindex` attribute on the host element propagates its value to `focusElement`.
37
- *
38
- * @protected
39
- * @type {!HTMLElement}
40
- */
41
- focusElement: {
42
- type: Object,
43
- readOnly: true,
44
- observer: '_focusElementChanged',
45
- sync: true,
46
- },
47
-
48
- /**
49
- * Override the property from `TabIndexMixin`
50
- * to ensure the `tabindex` attribute of the focus element
51
- * will be restored to `0` after re-enabling the element.
52
- *
53
- * @protected
54
- * @override
55
- */
56
- _lastTabIndex: {
57
- value: 0,
58
- },
59
- };
60
- }
61
-
62
- constructor() {
63
- super();
64
-
65
- this._boundOnBlur = this._onBlur.bind(this);
66
- this._boundOnFocus = this._onFocus.bind(this);
67
- }
68
-
69
- /** @protected */
70
- ready() {
71
- super.ready();
72
-
73
- if (this.autofocus && !this.disabled) {
74
- requestAnimationFrame(() => {
75
- this.focus();
76
- });
77
- }
78
- }
79
-
80
- /**
81
- * @param {FocusOptions=} options
82
- * @protected
83
- * @override
84
- */
85
- focus(options) {
86
- if (this.focusElement && !this.disabled) {
87
- this.focusElement.focus();
88
-
89
- // Set focus-ring attribute on programmatic focus by default
90
- // unless explicitly disabled by `{ focusVisible: false }`.
91
- if (!(options && options.focusVisible === false)) {
92
- this.setAttribute('focus-ring', '');
93
- }
94
- }
95
- }
96
-
97
- /**
98
- * @protected
99
- * @override
100
- */
101
- blur() {
102
- if (this.focusElement) {
103
- this.focusElement.blur();
13
+ const DelegateFocusMixinImplementation = (superclass) => {
14
+ return class DelegateFocusMixinClass extends FocusMixin(TabindexMixin(superclass)) {
15
+ static get properties() {
16
+ return {
17
+ /**
18
+ * Specify that this control should have input focus when the page loads.
19
+ */
20
+ autofocus: {
21
+ type: Boolean,
22
+ },
23
+
24
+ /**
25
+ * A reference to the focusable element controlled by the mixin.
26
+ * It can be an input, textarea, button or any element with tabindex > -1.
27
+ *
28
+ * Any component implementing this mixin is expected to provide it
29
+ * by using `this._setFocusElement(input)` Polymer API.
30
+ *
31
+ * Toggling `tabindex` attribute on the host element propagates its value to `focusElement`.
32
+ *
33
+ * @protected
34
+ * @type {!HTMLElement}
35
+ */
36
+ focusElement: {
37
+ type: Object,
38
+ readOnly: true,
39
+ observer: '_focusElementChanged',
40
+ sync: true,
41
+ },
42
+
43
+ /**
44
+ * Override the property from `TabIndexMixin`
45
+ * to ensure the `tabindex` attribute of the focus element
46
+ * will be restored to `0` after re-enabling the element.
47
+ *
48
+ * @protected
49
+ * @override
50
+ */
51
+ _lastTabIndex: {
52
+ value: 0,
53
+ },
54
+ };
55
+ }
56
+
57
+ constructor() {
58
+ super();
59
+
60
+ this._boundOnBlur = this._onBlur.bind(this);
61
+ this._boundOnFocus = this._onFocus.bind(this);
62
+ }
63
+
64
+ /** @protected */
65
+ ready() {
66
+ super.ready();
67
+
68
+ if (this.autofocus && !this.disabled) {
69
+ requestAnimationFrame(() => {
70
+ this.focus();
71
+ });
72
+ }
73
+ }
74
+
75
+ /**
76
+ * @param {FocusOptions=} options
77
+ * @protected
78
+ * @override
79
+ */
80
+ focus(options) {
81
+ if (this.focusElement && !this.disabled) {
82
+ this.focusElement.focus();
83
+
84
+ // Set focus-ring attribute on programmatic focus by default
85
+ // unless explicitly disabled by `{ focusVisible: false }`.
86
+ if (options?.focusVisible !== false) {
87
+ this.setAttribute('focus-ring', '');
104
88
  }
105
89
  }
106
-
107
- /**
108
- * @protected
109
- * @override
110
- */
111
- click() {
112
- if (this.focusElement && !this.disabled) {
113
- this.focusElement.click();
90
+ }
91
+
92
+ /**
93
+ * @protected
94
+ * @override
95
+ */
96
+ blur() {
97
+ if (this.focusElement) {
98
+ this.focusElement.blur();
99
+ }
100
+ }
101
+
102
+ /**
103
+ * @protected
104
+ * @override
105
+ */
106
+ click() {
107
+ if (this.focusElement && !this.disabled) {
108
+ this.focusElement.click();
109
+ }
110
+ }
111
+
112
+ /** @protected */
113
+ _focusElementChanged(element, oldElement) {
114
+ if (element) {
115
+ element.disabled = this.disabled;
116
+ this._addFocusListeners(element);
117
+ this.__forwardTabIndex(this.tabindex);
118
+ } else if (oldElement) {
119
+ this._removeFocusListeners(oldElement);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * @param {HTMLElement} element
125
+ * @protected
126
+ */
127
+ _addFocusListeners(element) {
128
+ element.addEventListener('blur', this._boundOnBlur);
129
+ element.addEventListener('focus', this._boundOnFocus);
130
+ }
131
+
132
+ /**
133
+ * @param {HTMLElement} element
134
+ * @protected
135
+ */
136
+ _removeFocusListeners(element) {
137
+ element.removeEventListener('blur', this._boundOnBlur);
138
+ element.removeEventListener('focus', this._boundOnFocus);
139
+ }
140
+
141
+ /**
142
+ * Focus event does not bubble, so we dispatch it manually
143
+ * on the host element to support adding focus listeners
144
+ * when the focusable element is placed in light DOM.
145
+ * @param {FocusEvent} event
146
+ * @protected
147
+ */
148
+ _onFocus(event) {
149
+ event.stopPropagation();
150
+ /** @internal to not document it in CEM */
151
+ this.dispatchEvent(new Event('focus'));
152
+ }
153
+
154
+ /**
155
+ * Blur event does not bubble, so we dispatch it manually
156
+ * on the host element to support adding blur listeners
157
+ * when the focusable element is placed in light DOM.
158
+ * @param {FocusEvent} event
159
+ * @protected
160
+ */
161
+ _onBlur(event) {
162
+ event.stopPropagation();
163
+ /** @internal to not document it in CEM */
164
+ this.dispatchEvent(new Event('blur'));
165
+ }
166
+
167
+ /**
168
+ * @param {FocusEvent} event
169
+ * @return {boolean}
170
+ * @protected
171
+ * @override
172
+ */
173
+ _shouldSetFocus(event) {
174
+ return event.target === this.focusElement;
175
+ }
176
+
177
+ /**
178
+ * @param {FocusEvent} event
179
+ * @return {boolean}
180
+ * @protected
181
+ * @override
182
+ */
183
+ _shouldRemoveFocus(event) {
184
+ return event.target === this.focusElement;
185
+ }
186
+
187
+ /**
188
+ * @param {boolean} disabled
189
+ * @param {boolean} oldDisabled
190
+ * @protected
191
+ * @override
192
+ */
193
+ _disabledChanged(disabled, oldDisabled) {
194
+ super._disabledChanged(disabled, oldDisabled);
195
+
196
+ if (this.focusElement) {
197
+ this.focusElement.disabled = disabled;
198
+ }
199
+
200
+ if (disabled) {
201
+ this.blur();
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Override an observer from `TabindexMixin`.
207
+ * Do not call super to remove tabindex attribute
208
+ * from the host after it has been forwarded.
209
+ * @param {string} tabindex
210
+ * @protected
211
+ * @override
212
+ */
213
+ _tabindexChanged(tabindex) {
214
+ this.__forwardTabIndex(tabindex);
215
+ }
216
+
217
+ /** @private */
218
+ __forwardTabIndex(tabindex) {
219
+ if (tabindex !== undefined && this.focusElement) {
220
+ this.focusElement.tabIndex = tabindex;
221
+
222
+ // Preserve tabindex="-1" on the host element
223
+ if (tabindex !== -1) {
224
+ this.tabindex = undefined;
114
225
  }
115
226
  }
116
227
 
117
- /** @protected */
118
- _focusElementChanged(element, oldElement) {
119
- if (element) {
120
- element.disabled = this.disabled;
121
- this._addFocusListeners(element);
122
- this.__forwardTabIndex(this.tabindex);
123
- } else if (oldElement) {
124
- this._removeFocusListeners(oldElement);
228
+ if (this.disabled && tabindex) {
229
+ // If tabindex attribute was changed while component was disabled
230
+ if (tabindex !== -1) {
231
+ this._lastTabIndex = tabindex;
125
232
  }
233
+ this.tabindex = undefined;
126
234
  }
127
235
 
128
- /**
129
- * @param {HTMLElement} element
130
- * @protected
131
- */
132
- _addFocusListeners(element) {
133
- element.addEventListener('blur', this._boundOnBlur);
134
- element.addEventListener('focus', this._boundOnFocus);
135
- }
136
-
137
- /**
138
- * @param {HTMLElement} element
139
- * @protected
140
- */
141
- _removeFocusListeners(element) {
142
- element.removeEventListener('blur', this._boundOnBlur);
143
- element.removeEventListener('focus', this._boundOnFocus);
144
- }
145
-
146
- /**
147
- * Focus event does not bubble, so we dispatch it manually
148
- * on the host element to support adding focus listeners
149
- * when the focusable element is placed in light DOM.
150
- * @param {FocusEvent} event
151
- * @protected
152
- */
153
- _onFocus(event) {
154
- event.stopPropagation();
155
- this.dispatchEvent(new Event('focus'));
156
- }
157
-
158
- /**
159
- * Blur event does not bubble, so we dispatch it manually
160
- * on the host element to support adding blur listeners
161
- * when the focusable element is placed in light DOM.
162
- * @param {FocusEvent} event
163
- * @protected
164
- */
165
- _onBlur(event) {
166
- event.stopPropagation();
167
- this.dispatchEvent(new Event('blur'));
236
+ // Lit does not remove attribute when setting property to undefined
237
+ if (tabindex === undefined && this.hasAttribute('tabindex')) {
238
+ this.removeAttribute('tabindex');
168
239
  }
240
+ }
241
+ };
242
+ };
169
243
 
170
- /**
171
- * @param {FocusEvent} event
172
- * @return {boolean}
173
- * @protected
174
- * @override
175
- */
176
- _shouldSetFocus(event) {
177
- return event.target === this.focusElement;
178
- }
179
-
180
- /**
181
- * @param {FocusEvent} event
182
- * @return {boolean}
183
- * @protected
184
- * @override
185
- */
186
- _shouldRemoveFocus(event) {
187
- return event.target === this.focusElement;
188
- }
189
-
190
- /**
191
- * @param {boolean} disabled
192
- * @param {boolean} oldDisabled
193
- * @protected
194
- * @override
195
- */
196
- _disabledChanged(disabled, oldDisabled) {
197
- super._disabledChanged(disabled, oldDisabled);
198
-
199
- if (this.focusElement) {
200
- this.focusElement.disabled = disabled;
201
- }
202
-
203
- if (disabled) {
204
- this.blur();
205
- }
206
- }
207
-
208
- /**
209
- * Override an observer from `TabindexMixin`.
210
- * Do not call super to remove tabindex attribute
211
- * from the host after it has been forwarded.
212
- * @param {string} tabindex
213
- * @protected
214
- * @override
215
- */
216
- _tabindexChanged(tabindex) {
217
- this.__forwardTabIndex(tabindex);
218
- }
219
-
220
- /** @private */
221
- __forwardTabIndex(tabindex) {
222
- if (tabindex !== undefined && this.focusElement) {
223
- this.focusElement.tabIndex = tabindex;
224
-
225
- // Preserve tabindex="-1" on the host element
226
- if (tabindex !== -1) {
227
- this.tabindex = undefined;
228
- }
229
- }
230
-
231
- if (this.disabled && tabindex) {
232
- // If tabindex attribute was changed while component was disabled
233
- if (tabindex !== -1) {
234
- this._lastTabIndex = tabindex;
235
- }
236
- this.tabindex = undefined;
237
- }
238
-
239
- // Lit does not remove attribute when setting property to undefined
240
- if (tabindex === undefined && this.hasAttribute('tabindex')) {
241
- this.removeAttribute('tabindex');
242
- }
243
- }
244
- },
245
- );
244
+ export const DelegateFocusMixin = dedupeMixin(DelegateFocusMixinImplementation);
@@ -7,57 +7,56 @@ import { dedupeMixin } from '@open-wc/dedupe-mixin';
7
7
 
8
8
  /**
9
9
  * A mixin to provide disabled property for field components.
10
- *
11
- * @polymerMixin
12
10
  */
13
- export const DisabledMixin = dedupeMixin(
14
- (superclass) =>
15
- class DisabledMixinClass extends superclass {
16
- static get properties() {
17
- return {
18
- /**
19
- * If true, the user cannot interact with this element.
20
- */
21
- disabled: {
22
- type: Boolean,
23
- value: false,
24
- observer: '_disabledChanged',
25
- reflectToAttribute: true,
26
- sync: true,
27
- },
28
- };
29
- }
11
+ const DisabledMixinImplementation = (superclass) => {
12
+ return class DisabledMixinClass extends superclass {
13
+ static get properties() {
14
+ return {
15
+ /**
16
+ * If true, the user cannot interact with this element.
17
+ */
18
+ disabled: {
19
+ type: Boolean,
20
+ value: false,
21
+ observer: '_disabledChanged',
22
+ reflectToAttribute: true,
23
+ sync: true,
24
+ },
25
+ };
26
+ }
30
27
 
31
- /**
32
- * @param {boolean} disabled
33
- * @protected
34
- */
35
- _disabledChanged(disabled) {
36
- this._setAriaDisabled(disabled);
37
- }
28
+ /**
29
+ * @param {boolean} disabled
30
+ * @protected
31
+ */
32
+ _disabledChanged(disabled) {
33
+ this._setAriaDisabled(disabled);
34
+ }
38
35
 
39
- /**
40
- * @param {boolean} disabled
41
- * @protected
42
- */
43
- _setAriaDisabled(disabled) {
44
- if (disabled) {
45
- this.setAttribute('aria-disabled', 'true');
46
- } else {
47
- this.removeAttribute('aria-disabled');
48
- }
36
+ /**
37
+ * @param {boolean} disabled
38
+ * @protected
39
+ */
40
+ _setAriaDisabled(disabled) {
41
+ if (disabled) {
42
+ this.setAttribute('aria-disabled', 'true');
43
+ } else {
44
+ this.removeAttribute('aria-disabled');
49
45
  }
46
+ }
50
47
 
51
- /**
52
- * Overrides the default element `click` method in order to prevent
53
- * firing the `click` event when the element is disabled.
54
- * @protected
55
- * @override
56
- */
57
- click() {
58
- if (!this.disabled) {
59
- super.click();
60
- }
48
+ /**
49
+ * Overrides the default element `click` method in order to prevent
50
+ * firing the `click` event when the element is disabled.
51
+ * @protected
52
+ * @override
53
+ */
54
+ click() {
55
+ if (!this.disabled) {
56
+ super.click();
61
57
  }
62
- },
63
- );
58
+ }
59
+ };
60
+ };
61
+
62
+ export const DisabledMixin = dedupeMixin(DisabledMixinImplementation);
@@ -8,101 +8,100 @@ import { isKeyboardActive } from './focus-utils.js';
8
8
 
9
9
  /**
10
10
  * A mixin to handle `focused` and `focus-ring` attributes based on focus.
11
- *
12
- * @polymerMixin
13
11
  */
14
- export const FocusMixin = dedupeMixin(
15
- (superclass) =>
16
- class FocusMixinClass extends superclass {
17
- /**
18
- * @protected
19
- * @return {boolean}
20
- */
21
- get _keyboardActive() {
22
- return isKeyboardActive();
23
- }
12
+ const FocusMixinImplementation = (superclass) => {
13
+ return class FocusMixinClass extends superclass {
14
+ /**
15
+ * @protected
16
+ * @return {boolean}
17
+ */
18
+ get _keyboardActive() {
19
+ return isKeyboardActive();
20
+ }
24
21
 
25
- /** @protected */
26
- ready() {
27
- this.addEventListener('focusin', (e) => {
28
- if (this._shouldSetFocus(e)) {
29
- this._setFocused(true);
30
- }
31
- });
22
+ /** @protected */
23
+ ready() {
24
+ this.addEventListener('focusin', (e) => {
25
+ if (this._shouldSetFocus(e)) {
26
+ this._setFocused(true);
27
+ }
28
+ });
32
29
 
33
- this.addEventListener('focusout', (e) => {
34
- if (this._shouldRemoveFocus(e)) {
35
- this._setFocused(false);
36
- }
37
- });
30
+ this.addEventListener('focusout', (e) => {
31
+ if (this._shouldRemoveFocus(e)) {
32
+ this._setFocused(false);
33
+ }
34
+ });
38
35
 
39
- // In super.ready() other 'focusin' and 'focusout' listeners might be
40
- // added, so we call it after our own ones to ensure they execute first.
41
- // Issue to watch out: when incorrect, <vaadin-combo-box> refocuses the
42
- // input field on iOS after "Done" is pressed.
43
- super.ready();
44
- }
36
+ // In super.ready() other 'focusin' and 'focusout' listeners might be
37
+ // added, so we call it after our own ones to ensure they execute first.
38
+ // Issue to watch out: when incorrect, <vaadin-combo-box> refocuses the
39
+ // input field on iOS after "Done" is pressed.
40
+ super.ready();
41
+ }
45
42
 
46
- /** @protected */
47
- disconnectedCallback() {
48
- super.disconnectedCallback();
43
+ /** @protected */
44
+ disconnectedCallback() {
45
+ super.disconnectedCallback();
49
46
 
50
- // In non-Chrome browsers, blur does not fire on the element when it is disconnected.
51
- // reproducible in `<vaadin-date-picker>` when closing on `Cancel` or `Today` click.
52
- if (this.hasAttribute('focused')) {
53
- this._setFocused(false);
54
- }
47
+ // In non-Chrome browsers, blur does not fire on the element when it is disconnected.
48
+ // reproducible in `<vaadin-date-picker>` when closing on `Cancel` or `Today` click.
49
+ if (this.hasAttribute('focused')) {
50
+ this._setFocused(false);
55
51
  }
52
+ }
56
53
 
57
- /**
58
- * @param {FocusOptions=} options
59
- * @protected
60
- * @override
61
- */
62
- focus(options) {
63
- super.focus(options);
54
+ /**
55
+ * @param {FocusOptions=} options
56
+ * @protected
57
+ * @override
58
+ */
59
+ focus(options) {
60
+ super.focus(options);
64
61
 
65
- // Set focus-ring attribute on programmatic focus by default
66
- // unless explicitly disabled by `{ focusVisible: false }`.
67
- if (!(options && options.focusVisible === false)) {
68
- this.setAttribute('focus-ring', '');
69
- }
62
+ // Set focus-ring attribute on programmatic focus by default
63
+ // unless explicitly disabled by `{ focusVisible: false }`.
64
+ if (options?.focusVisible !== false) {
65
+ this.setAttribute('focus-ring', '');
70
66
  }
67
+ }
71
68
 
72
- /**
73
- * Override to change how focused and focus-ring attributes are set.
74
- *
75
- * @param {boolean} focused
76
- * @protected
77
- */
78
- _setFocused(focused) {
79
- this.toggleAttribute('focused', focused);
69
+ /**
70
+ * Override to change how focused and focus-ring attributes are set.
71
+ *
72
+ * @param {boolean} focused
73
+ * @protected
74
+ */
75
+ _setFocused(focused) {
76
+ this.toggleAttribute('focused', focused);
80
77
 
81
- // Focus-ring is true when the element was focused from the keyboard.
82
- // Focus Ring [A11ycasts]: https://youtu.be/ilj2P5-5CjI
83
- this.toggleAttribute('focus-ring', focused && this._keyboardActive);
84
- }
78
+ // Focus-ring is true when the element was focused from the keyboard.
79
+ // Focus Ring [A11ycasts]: https://youtu.be/ilj2P5-5CjI
80
+ this.toggleAttribute('focus-ring', focused && this._keyboardActive);
81
+ }
85
82
 
86
- /**
87
- * Override to define if the field receives focus based on the event.
88
- *
89
- * @param {FocusEvent} _event
90
- * @return {boolean}
91
- * @protected
92
- */
93
- _shouldSetFocus(_event) {
94
- return true;
95
- }
83
+ /**
84
+ * Override to define if the field receives focus based on the event.
85
+ *
86
+ * @param {FocusEvent} _event
87
+ * @return {boolean}
88
+ * @protected
89
+ */
90
+ _shouldSetFocus(_event) {
91
+ return true;
92
+ }
96
93
 
97
- /**
98
- * Override to define if the field loses focus based on the event.
99
- *
100
- * @param {FocusEvent} _event
101
- * @return {boolean}
102
- * @protected
103
- */
104
- _shouldRemoveFocus(_event) {
105
- return true;
106
- }
107
- },
108
- );
94
+ /**
95
+ * Override to define if the field loses focus based on the event.
96
+ *
97
+ * @param {FocusEvent} _event
98
+ * @return {boolean}
99
+ * @protected
100
+ */
101
+ _shouldRemoveFocus(_event) {
102
+ return true;
103
+ }
104
+ };
105
+ };
106
+
107
+ export const FocusMixin = dedupeMixin(FocusMixinImplementation);
@@ -8,9 +8,6 @@ import { KeyboardMixin } from './keyboard-mixin.js';
8
8
 
9
9
  /**
10
10
  * A mixin for navigating items with keyboard.
11
- *
12
- * @polymerMixin
13
- * @mixes KeyboardMixin
14
11
  */
15
12
  export const KeyboardDirectionMixin = (superclass) =>
16
13
  class KeyboardDirectionMixinClass extends KeyboardMixin(superclass) {
@@ -127,7 +124,7 @@ export const KeyboardDirectionMixin = (superclass) =>
127
124
 
128
125
  if (idx >= 0) {
129
126
  event.preventDefault();
130
- this._focus(idx, { focusVisible: true }, true);
127
+ this._focus(idx, { focusVisible: true, preventScroll: true }, true);
131
128
  }
132
129
  }
133
130
 
@@ -9,77 +9,76 @@ import { dedupeMixin } from '@open-wc/dedupe-mixin';
9
9
  * A mixin that manages keyboard handling.
10
10
  * The mixin subscribes to the keyboard events while an actual implementation
11
11
  * for the event handlers is left to the client (a component or another mixin).
12
- *
13
- * @polymerMixin
14
12
  */
15
- export const KeyboardMixin = dedupeMixin(
16
- (superclass) =>
17
- class KeyboardMixinClass extends superclass {
18
- /** @protected */
19
- ready() {
20
- super.ready();
13
+ const KeyboardMixinImplementation = (superclass) => {
14
+ return class KeyboardMixinClass extends superclass {
15
+ /** @protected */
16
+ ready() {
17
+ super.ready();
21
18
 
22
- this.addEventListener('keydown', (event) => {
23
- this._onKeyDown(event);
24
- });
19
+ this.addEventListener('keydown', (event) => {
20
+ this._onKeyDown(event);
21
+ });
25
22
 
26
- this.addEventListener('keyup', (event) => {
27
- this._onKeyUp(event);
28
- });
29
- }
23
+ this.addEventListener('keyup', (event) => {
24
+ this._onKeyUp(event);
25
+ });
26
+ }
30
27
 
31
- /**
32
- * A handler for the `keydown` event. By default, it calls
33
- * separate methods for handling "Enter" and "Escape" keys.
34
- * Override the method to implement your own behavior.
35
- *
36
- * @param {KeyboardEvent} event
37
- * @protected
38
- */
39
- _onKeyDown(event) {
40
- switch (event.key) {
41
- case 'Enter':
42
- this._onEnter(event);
43
- break;
44
- case 'Escape':
45
- this._onEscape(event);
46
- break;
47
- default:
48
- break;
49
- }
28
+ /**
29
+ * A handler for the `keydown` event. By default, it calls
30
+ * separate methods for handling "Enter" and "Escape" keys.
31
+ * Override the method to implement your own behavior.
32
+ *
33
+ * @param {KeyboardEvent} event
34
+ * @protected
35
+ */
36
+ _onKeyDown(event) {
37
+ switch (event.key) {
38
+ case 'Enter':
39
+ this._onEnter(event);
40
+ break;
41
+ case 'Escape':
42
+ this._onEscape(event);
43
+ break;
44
+ default:
45
+ break;
50
46
  }
47
+ }
51
48
 
52
- /**
53
- * A handler for the `keyup` event. By default, it does nothing.
54
- * Override the method to implement your own behavior.
55
- *
56
- * @param {KeyboardEvent} _event
57
- * @protected
58
- */
59
- _onKeyUp(_event) {
60
- // To be implemented.
61
- }
49
+ /**
50
+ * A handler for the `keyup` event. By default, it does nothing.
51
+ * Override the method to implement your own behavior.
52
+ *
53
+ * @param {KeyboardEvent} _event
54
+ * @protected
55
+ */
56
+ _onKeyUp(_event) {
57
+ // To be implemented.
58
+ }
62
59
 
63
- /**
64
- * A handler for the "Enter" key. By default, it does nothing.
65
- * Override the method to implement your own behavior.
66
- *
67
- * @param {KeyboardEvent} _event
68
- * @protected
69
- */
70
- _onEnter(_event) {
71
- // To be implemented.
72
- }
60
+ /**
61
+ * A handler for the "Enter" key. By default, it does nothing.
62
+ * Override the method to implement your own behavior.
63
+ *
64
+ * @param {KeyboardEvent} _event
65
+ * @protected
66
+ */
67
+ _onEnter(_event) {
68
+ // To be implemented.
69
+ }
73
70
 
74
- /**
75
- * A handler for the "Escape" key. By default, it does nothing.
76
- * Override the method to implement your own behavior.
77
- *
78
- * @param {KeyboardEvent} _event
79
- * @protected
80
- */
81
- _onEscape(_event) {
82
- // To be implemented.
83
- }
84
- },
85
- );
71
+ /**
72
+ * A handler for the "Escape" key. By default, it does nothing.
73
+ * Override the method to implement your own behavior.
74
+ *
75
+ * @param {KeyboardEvent} _event
76
+ * @protected
77
+ */
78
+ _onEscape(_event) {
79
+ // To be implemented.
80
+ }
81
+ };
82
+ };
83
+
84
+ export const KeyboardMixin = dedupeMixin(KeyboardMixinImplementation);
package/src/list-mixin.js CHANGED
@@ -12,9 +12,6 @@ import { KeyboardDirectionMixin } from './keyboard-direction-mixin.js';
12
12
 
13
13
  /**
14
14
  * A mixin for list elements, facilitating navigation and selection of items.
15
- *
16
- * @polymerMixin
17
- * @mixes KeyboardDirectionMixin
18
15
  */
19
16
  export const ListMixin = (superClass) =>
20
17
  class ListMixinClass extends KeyboardDirectionMixin(superClass) {
@@ -285,40 +282,19 @@ export const ListMixin = (superClass) =>
285
282
  });
286
283
  this._setFocusable(idx);
287
284
  this._scrollToItem(idx);
288
- super._focus(idx, options);
285
+ super._focus(idx, options ?? { preventScroll: true });
289
286
  }
290
287
 
291
288
  /**
292
- * Scroll the container to have the next item by the edge of the viewport.
289
+ * Scroll the container to have the item visible.
293
290
  * @param {number} idx
294
291
  * @protected
295
292
  */
296
293
  _scrollToItem(idx) {
297
- const item = this.items[idx];
298
- if (!item) {
299
- return;
294
+ const item = this._getItems()[idx];
295
+ if (item) {
296
+ item.scrollIntoView({ block: 'nearest', inline: 'nearest' });
300
297
  }
301
-
302
- const props = this._vertical ? ['top', 'bottom'] : this._isRTL ? ['right', 'left'] : ['left', 'right'];
303
-
304
- const scrollerRect = this._scrollerElement.getBoundingClientRect();
305
- const nextItemRect = (this.items[idx + 1] || item).getBoundingClientRect();
306
- const prevItemRect = (this.items[idx - 1] || item).getBoundingClientRect();
307
-
308
- let scrollDistance = 0;
309
- if (
310
- (!this._isRTL && nextItemRect[props[1]] >= scrollerRect[props[1]]) ||
311
- (this._isRTL && nextItemRect[props[1]] <= scrollerRect[props[1]])
312
- ) {
313
- scrollDistance = nextItemRect[props[1]] - scrollerRect[props[1]];
314
- } else if (
315
- (!this._isRTL && prevItemRect[props[0]] <= scrollerRect[props[0]]) ||
316
- (this._isRTL && prevItemRect[props[0]] >= scrollerRect[props[0]])
317
- ) {
318
- scrollDistance = prevItemRect[props[0]] - scrollerRect[props[0]];
319
- }
320
-
321
- this._scroll(scrollDistance);
322
298
  }
323
299
 
324
300
  /**
@@ -336,11 +312,18 @@ export const ListMixin = (superClass) =>
336
312
  }
337
313
 
338
314
  /**
339
- * Fired when the selection is changed.
340
- * Not fired when used in `multiple` selection mode.
315
+ * Override method inherited from `KeyboardDirectionMixin` to allow
316
+ * focusing disabled items that are configured so.
341
317
  *
342
- * @event selected-changed
343
- * @param {Object} detail
344
- * @param {Object} detail.value the index of the item selected in the items array.
318
+ * @param {Element} item
319
+ * @protected
320
+ * @override
345
321
  */
322
+ _isItemFocusable(item) {
323
+ if (item.disabled && item.__shouldAllowFocusWhenDisabled) {
324
+ return item.__shouldAllowFocusWhenDisabled();
325
+ }
326
+
327
+ return super._isItemFocusable(item);
328
+ }
346
329
  };
@@ -10,9 +10,6 @@ import { DisabledMixin } from './disabled-mixin.js';
10
10
  *
11
11
  * The attribute is set to -1 whenever the user disables the element
12
12
  * and restored with the last known value once the element is enabled.
13
- *
14
- * @polymerMixin
15
- * @mixes DisabledMixin
16
13
  */
17
14
  export const TabindexMixin = (superclass) =>
18
15
  class TabindexMixinClass extends DisabledMixin(superclass) {