@vaadin/component-base 24.0.5 → 24.1.0-alpha10

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.
@@ -1,228 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
7
- import { FocusMixin } from './focus-mixin.js';
8
- import { TabindexMixin } from './tabindex-mixin.js';
9
-
10
- /**
11
- * A mixin to forward focus to an element in the light DOM.
12
- *
13
- * @polymerMixin
14
- * @mixes FocusMixin
15
- * @mixes TabindexMixin
16
- */
17
- export const DelegateFocusMixin = dedupingMixin(
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
- },
46
-
47
- /**
48
- * Override the property from `TabIndexMixin`
49
- * to ensure the `tabindex` attribute of the focus element
50
- * will be restored to `0` after re-enabling the element.
51
- *
52
- * @protected
53
- * @override
54
- */
55
- _lastTabIndex: {
56
- value: 0,
57
- },
58
- };
59
- }
60
-
61
- constructor() {
62
- super();
63
-
64
- this._boundOnBlur = this._onBlur.bind(this);
65
- this._boundOnFocus = this._onFocus.bind(this);
66
- }
67
-
68
- /** @protected */
69
- ready() {
70
- super.ready();
71
-
72
- if (this.autofocus && !this.disabled) {
73
- requestAnimationFrame(() => {
74
- this.focus();
75
- this.setAttribute('focus-ring', '');
76
- });
77
- }
78
- }
79
-
80
- /**
81
- * @protected
82
- * @override
83
- */
84
- focus() {
85
- if (!this.focusElement || this.disabled) {
86
- return;
87
- }
88
-
89
- this.focusElement.focus();
90
- this._setFocused(true);
91
- }
92
-
93
- /**
94
- * @protected
95
- * @override
96
- */
97
- blur() {
98
- if (!this.focusElement) {
99
- return;
100
- }
101
- this.focusElement.blur();
102
- this._setFocused(false);
103
- }
104
-
105
- /**
106
- * @protected
107
- * @override
108
- */
109
- click() {
110
- if (this.focusElement && !this.disabled) {
111
- this.focusElement.click();
112
- }
113
- }
114
-
115
- /** @protected */
116
- _focusElementChanged(element, oldElement) {
117
- if (element) {
118
- element.disabled = this.disabled;
119
- this._addFocusListeners(element);
120
- this.__forwardTabIndex(this.tabindex);
121
- } else if (oldElement) {
122
- this._removeFocusListeners(oldElement);
123
- }
124
- }
125
-
126
- /**
127
- * @param {HTMLElement} element
128
- * @protected
129
- */
130
- _addFocusListeners(element) {
131
- element.addEventListener('blur', this._boundOnBlur);
132
- element.addEventListener('focus', this._boundOnFocus);
133
- }
134
-
135
- /**
136
- * @param {HTMLElement} element
137
- * @protected
138
- */
139
- _removeFocusListeners(element) {
140
- element.removeEventListener('blur', this._boundOnBlur);
141
- element.removeEventListener('focus', this._boundOnFocus);
142
- }
143
-
144
- /**
145
- * Focus event does not bubble, so we dispatch it manually
146
- * on the host element to support adding focus listeners
147
- * when the focusable element is placed in light DOM.
148
- * @param {FocusEvent} event
149
- * @protected
150
- */
151
- _onFocus(event) {
152
- event.stopPropagation();
153
- this.dispatchEvent(new Event('focus'));
154
- }
155
-
156
- /**
157
- * Blur event does not bubble, so we dispatch it manually
158
- * on the host element to support adding blur listeners
159
- * when the focusable element is placed in light DOM.
160
- * @param {FocusEvent} event
161
- * @protected
162
- */
163
- _onBlur(event) {
164
- event.stopPropagation();
165
- this.dispatchEvent(new Event('blur'));
166
- }
167
-
168
- /**
169
- * @param {Event} event
170
- * @return {boolean}
171
- * @protected
172
- * @override
173
- */
174
- _shouldSetFocus(event) {
175
- return event.target === this.focusElement;
176
- }
177
-
178
- /**
179
- * @param {boolean} disabled
180
- * @param {boolean} oldDisabled
181
- * @protected
182
- * @override
183
- */
184
- _disabledChanged(disabled, oldDisabled) {
185
- super._disabledChanged(disabled, oldDisabled);
186
-
187
- if (this.focusElement) {
188
- this.focusElement.disabled = disabled;
189
- }
190
-
191
- if (disabled) {
192
- this.blur();
193
- }
194
- }
195
-
196
- /**
197
- * Override an observer from `TabindexMixin`.
198
- * Do not call super to remove tabindex attribute
199
- * from the host after it has been forwarded.
200
- * @param {string} tabindex
201
- * @protected
202
- * @override
203
- */
204
- _tabindexChanged(tabindex) {
205
- this.__forwardTabIndex(tabindex);
206
- }
207
-
208
- /** @private */
209
- __forwardTabIndex(tabindex) {
210
- if (tabindex !== undefined && this.focusElement) {
211
- this.focusElement.tabIndex = tabindex;
212
-
213
- // Preserve tabindex="-1" on the host element
214
- if (tabindex !== -1) {
215
- this.tabindex = undefined;
216
- }
217
- }
218
-
219
- if (this.disabled && tabindex) {
220
- // If tabindex attribute was changed while component was disabled
221
- if (tabindex !== -1) {
222
- this._lastTabIndex = tabindex;
223
- }
224
- this.tabindex = undefined;
225
- }
226
- }
227
- },
228
- );
@@ -1,20 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import type { Constructor } from '@open-wc/dedupe-mixin';
7
-
8
- /**
9
- * A mixin to provide disabled property for field components.
10
- */
11
- export declare function DisabledMixin<T extends Constructor<HTMLElement>>(base: T): Constructor<DisabledMixinClass> & T;
12
-
13
- export declare class DisabledMixinClass {
14
- /**
15
- * If true, the user cannot interact with this element.
16
- */
17
- disabled: boolean;
18
-
19
- protected _disabledChanged(disabled: boolean, oldDisabled: boolean): void;
20
- }
@@ -1,62 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
7
-
8
- /**
9
- * A mixin to provide disabled property for field components.
10
- *
11
- * @polymerMixin
12
- */
13
- export const DisabledMixin = dedupingMixin(
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
- },
27
- };
28
- }
29
-
30
- /**
31
- * @param {boolean} disabled
32
- * @protected
33
- */
34
- _disabledChanged(disabled) {
35
- this._setAriaDisabled(disabled);
36
- }
37
-
38
- /**
39
- * @param {boolean} disabled
40
- * @protected
41
- */
42
- _setAriaDisabled(disabled) {
43
- if (disabled) {
44
- this.setAttribute('aria-disabled', 'true');
45
- } else {
46
- this.removeAttribute('aria-disabled');
47
- }
48
- }
49
-
50
- /**
51
- * Overrides the default element `click` method in order to prevent
52
- * firing the `click` event when the element is disabled.
53
- * @protected
54
- * @override
55
- */
56
- click() {
57
- if (!this.disabled) {
58
- super.click();
59
- }
60
- }
61
- },
62
- );
@@ -1,30 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import type { Constructor } from '@open-wc/dedupe-mixin';
7
-
8
- /**
9
- * A mixin to handle `focused` and `focus-ring` attributes based on focus.
10
- */
11
- export declare function FocusMixin<T extends Constructor<HTMLElement>>(base: T): Constructor<FocusMixinClass> & T;
12
-
13
- export declare class FocusMixinClass {
14
- protected readonly _keyboardActive: boolean;
15
-
16
- /**
17
- * Override to change how focused and focus-ring attributes are set.
18
- */
19
- protected _setFocused(focused: boolean): void;
20
-
21
- /**
22
- * Override to define if the field receives focus based on the event.
23
- */
24
- protected _shouldSetFocus(event: FocusEvent): boolean;
25
-
26
- /**
27
- * Override to define if the field loses focus based on the event.
28
- */
29
- protected _shouldRemoveFocus(event: FocusEvent): boolean;
30
- }
@@ -1,93 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
7
- import { isKeyboardActive } from './focus-utils.js';
8
-
9
- /**
10
- * A mixin to handle `focused` and `focus-ring` attributes based on focus.
11
- *
12
- * @polymerMixin
13
- */
14
- export const FocusMixin = dedupingMixin(
15
- (superclass) =>
16
- class FocusMixinClass extends superclass {
17
- /**
18
- * @protected
19
- * @return {boolean}
20
- */
21
- get _keyboardActive() {
22
- return isKeyboardActive();
23
- }
24
-
25
- /** @protected */
26
- ready() {
27
- this.addEventListener('focusin', (e) => {
28
- if (this._shouldSetFocus(e)) {
29
- this._setFocused(true);
30
- }
31
- });
32
-
33
- this.addEventListener('focusout', (e) => {
34
- if (this._shouldRemoveFocus(e)) {
35
- this._setFocused(false);
36
- }
37
- });
38
-
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
- }
45
-
46
- /** @protected */
47
- disconnectedCallback() {
48
- super.disconnectedCallback();
49
-
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
- }
55
- }
56
-
57
- /**
58
- * Override to change how focused and focus-ring attributes are set.
59
- *
60
- * @param {boolean} focused
61
- * @protected
62
- */
63
- _setFocused(focused) {
64
- this.toggleAttribute('focused', focused);
65
-
66
- // Focus-ring is true when the element was focused from the keyboard.
67
- // Focus Ring [A11ycasts]: https://youtu.be/ilj2P5-5CjI
68
- this.toggleAttribute('focus-ring', focused && this._keyboardActive);
69
- }
70
-
71
- /**
72
- * Override to define if the field receives focus based on the event.
73
- *
74
- * @param {FocusEvent} _event
75
- * @return {boolean}
76
- * @protected
77
- */
78
- _shouldSetFocus(_event) {
79
- return true;
80
- }
81
-
82
- /**
83
- * Override to define if the field loses focus based on the event.
84
- *
85
- * @param {FocusEvent} _event
86
- * @return {boolean}
87
- * @protected
88
- */
89
- _shouldRemoveFocus(_event) {
90
- return true;
91
- }
92
- },
93
- );
@@ -1,39 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import type { ReactiveController } from 'lit';
7
-
8
- /**
9
- * A controller for trapping focus within a DOM node.
10
- */
11
- export class FocusTrapController implements ReactiveController {
12
- /**
13
- * The controller host element.
14
- */
15
- host: HTMLElement;
16
-
17
- constructor(node: HTMLElement);
18
-
19
- hostConnected(): void;
20
-
21
- hostDisconnected(): void;
22
-
23
- /**
24
- * Activates a focus trap for a DOM node that will prevent focus from escaping the node.
25
- * The trap can be deactivated with the `.releaseFocus()` method.
26
- *
27
- * If focus is initially outside the trap, the method will move focus inside,
28
- * on the first focusable element of the trap in the tab order.
29
- * The first focusable element can be the trap node itself if it is focusable
30
- * and comes first in the tab order.
31
- */
32
- trapFocus(trapNode: HTMLElement): void;
33
-
34
- /**
35
- * Deactivates the focus trap set with the `.trapFocus()` method
36
- * so that it becomes possible to tab outside the trap node.
37
- */
38
- releaseFocus(): void;
39
- }
@@ -1,155 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import { getFocusableElements, isElementFocused } from './focus-utils.js';
7
-
8
- const instances = [];
9
-
10
- /**
11
- * A controller for trapping focus within a DOM node.
12
- */
13
- export class FocusTrapController {
14
- /**
15
- * @param {HTMLElement} host
16
- */
17
- constructor(host) {
18
- /**
19
- * The controller host element.
20
- *
21
- * @type {HTMLElement}
22
- */
23
- this.host = host;
24
-
25
- /**
26
- * A node for trapping focus in.
27
- *
28
- * @type {HTMLElement | null}
29
- * @private
30
- */
31
- this.__trapNode = null;
32
-
33
- this.__onKeyDown = this.__onKeyDown.bind(this);
34
- }
35
-
36
- /**
37
- * An array of tab-ordered focusable elements inside the trap node.
38
- *
39
- * @return {HTMLElement[]}
40
- * @private
41
- */
42
- get __focusableElements() {
43
- return getFocusableElements(this.__trapNode);
44
- }
45
-
46
- /**
47
- * The index of the element inside the trap node that currently has focus.
48
- *
49
- * @return {HTMLElement | undefined}
50
- * @private
51
- */
52
- get __focusedElementIndex() {
53
- const focusableElements = this.__focusableElements;
54
- return focusableElements.indexOf(focusableElements.filter(isElementFocused).pop());
55
- }
56
-
57
- hostConnected() {
58
- document.addEventListener('keydown', this.__onKeyDown);
59
- }
60
-
61
- hostDisconnected() {
62
- document.removeEventListener('keydown', this.__onKeyDown);
63
- }
64
-
65
- /**
66
- * Activates a focus trap for a DOM node that will prevent focus from escaping the node.
67
- * The trap can be deactivated with the `.releaseFocus()` method.
68
- *
69
- * If focus is initially outside the trap, the method will move focus inside,
70
- * on the first focusable element of the trap in the tab order.
71
- * The first focusable element can be the trap node itself if it is focusable
72
- * and comes first in the tab order.
73
- *
74
- * If there are no focusable elements, the method will throw an exception
75
- * and the trap will not be set.
76
- *
77
- * @param {HTMLElement} trapNode
78
- */
79
- trapFocus(trapNode) {
80
- this.__trapNode = trapNode;
81
-
82
- if (this.__focusableElements.length === 0) {
83
- this.__trapNode = null;
84
- throw new Error('The trap node should have at least one focusable descendant or be focusable itself.');
85
- }
86
-
87
- instances.push(this);
88
-
89
- if (this.__focusedElementIndex === -1) {
90
- this.__focusableElements[0].focus();
91
- }
92
- }
93
-
94
- /**
95
- * Deactivates the focus trap set with the `.trapFocus()` method
96
- * so that it becomes possible to tab outside the trap node.
97
- */
98
- releaseFocus() {
99
- this.__trapNode = null;
100
-
101
- instances.pop();
102
- }
103
-
104
- /**
105
- * A `keydown` event handler that manages tabbing navigation when the trap is enabled.
106
- *
107
- * - Moves focus to the next focusable element of the trap on `Tab` press.
108
- * When no next element to focus, the method moves focus to the first focusable element.
109
- * - Moves focus to the prev focusable element of the trap on `Shift+Tab` press.
110
- * When no prev element to focus, the method moves focus to the last focusable element.
111
- *
112
- * @param {KeyboardEvent} event
113
- * @private
114
- */
115
- __onKeyDown(event) {
116
- if (!this.__trapNode) {
117
- return;
118
- }
119
-
120
- // Only handle events for the last instance
121
- if (this !== Array.from(instances).pop()) {
122
- return;
123
- }
124
-
125
- if (event.key === 'Tab') {
126
- event.preventDefault();
127
-
128
- const backward = event.shiftKey;
129
- this.__focusNextElement(backward);
130
- }
131
- }
132
-
133
- /**
134
- * - Moves focus to the next focusable element if `backward === false`.
135
- * When no next element to focus, the method moves focus to the first focusable element.
136
- * - Moves focus to the prev focusable element if `backward === true`.
137
- * When no prev element to focus the method moves focus to the last focusable element.
138
- *
139
- * If no focusable elements, the method returns immediately.
140
- *
141
- * @param {boolean} backward
142
- * @private
143
- */
144
- __focusNextElement(backward = false) {
145
- const focusableElements = this.__focusableElements;
146
- const step = backward ? -1 : 1;
147
- const currentIndex = this.__focusedElementIndex;
148
- const nextIndex = (focusableElements.length + currentIndex + step) % focusableElements.length;
149
- const element = focusableElements[nextIndex];
150
- element.focus();
151
- if (element.localName === 'input') {
152
- element.select();
153
- }
154
- }
155
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
-
7
- /**
8
- * Returns true if the window has received a keydown
9
- * event since the last mousedown event.
10
- */
11
- export declare function isKeyboardActive(): boolean;
12
-
13
- /**
14
- * Returns true if the element is hidden, false otherwise.
15
- *
16
- * An element is treated as hidden when any of the following conditions are met:
17
- * - the element itself or one of its ancestors has `display: none`.
18
- * - the element has or inherits `visibility: hidden`.
19
- */
20
- export declare function isElementHidden(element: HTMLElement): boolean;
21
-
22
- /**
23
- * Returns true if the element is focusable, otherwise false.
24
- *
25
- * The list of focusable elements is taken from http://stackoverflow.com/a/1600194/4228703.
26
- * However, there isn't a definite list, it's up to the browser.
27
- * The only standard we have is DOM Level 2 HTML https://www.w3.org/TR/DOM-Level-2-HTML/html.html,
28
- * according to which the only elements that have a `focus()` method are:
29
- * - HTMLInputElement
30
- * - HTMLSelectElement
31
- * - HTMLTextAreaElement
32
- * - HTMLAnchorElement
33
- *
34
- * This notably omits HTMLButtonElement and HTMLAreaElement.
35
- * Referring to these tests with tabbables in different browsers
36
- * http://allyjs.io/data-tables/focusable.html
37
- */
38
- export declare function isElementFocusable(element: HTMLElement): boolean;
39
-
40
- /**
41
- * Returns true if the element is focused, false otherwise.
42
- */
43
- export declare function isElementFocused(element: HTMLElement): boolean;
44
-
45
- /**
46
- * Returns a tab-ordered array of focusable elements for a root element.
47
- * The resulting array will include the root element if it is focusable.
48
- *
49
- * The method traverses nodes in shadow DOM trees too if any.
50
- */
51
- export declare function getFocusableElements(element: HTMLElement): HTMLElement[];