@vaadin/component-base 23.0.0-alpha1 → 23.0.0-alpha5
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.
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +3 -2
- package/src/a11y-announcer.d.ts +10 -0
- package/src/a11y-announcer.js +32 -0
- package/src/active-mixin.d.ts +1 -1
- package/src/active-mixin.js +5 -5
- package/src/async.js +8 -5
- package/src/browser-utils.js +1 -1
- package/src/controller-mixin.d.ts +1 -1
- package/src/controller-mixin.js +1 -1
- package/src/debounce.js +10 -4
- package/src/dir-helper.d.ts +1 -1
- package/src/dir-helper.js +3 -2
- package/src/dir-mixin.d.ts +1 -1
- package/src/dir-mixin.js +7 -7
- package/src/disabled-mixin.d.ts +1 -1
- package/src/disabled-mixin.js +1 -1
- package/src/element-mixin.d.ts +1 -1
- package/src/element-mixin.js +2 -2
- package/src/focus-mixin.d.ts +1 -1
- package/src/focus-mixin.js +1 -1
- package/src/focus-trap-controller.d.ts +39 -0
- package/src/focus-trap-controller.js +139 -0
- package/src/focus-utils.d.ts +45 -0
- package/src/focus-utils.js +228 -0
- package/src/gestures.d.ts +76 -0
- package/src/gestures.js +932 -0
- package/src/iron-list-core.js +40 -38
- package/src/keyboard-mixin.d.ts +1 -1
- package/src/keyboard-mixin.js +1 -1
- package/src/resize-mixin.d.ts +19 -0
- package/src/resize-mixin.js +56 -0
- package/src/slot-controller.d.ts +57 -0
- package/src/slot-controller.js +169 -0
- package/src/slot-mixin.d.ts +1 -1
- package/src/slot-mixin.js +11 -3
- package/src/tabindex-mixin.d.ts +1 -1
- package/src/tabindex-mixin.js +1 -1
- package/src/templates.js +1 -1
- package/src/virtualizer-iron-list-adapter.js +5 -6
- package/src/virtualizer.js +6 -6
package/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { DirMixin } from './src/dir-mixin.js';
|
|
|
4
4
|
export { DisabledMixin } from './src/disabled-mixin.js';
|
|
5
5
|
export { ElementMixin } from './src/element-mixin.js';
|
|
6
6
|
export { FocusMixin } from './src/focus-mixin.js';
|
|
7
|
+
export { FocusTrapController } from './src/focus-trap-controller.js';
|
|
7
8
|
export { KeyboardMixin } from './src/keyboard-mixin.js';
|
|
9
|
+
export { SlotController } from './src/slot-controller.js';
|
|
8
10
|
export { SlotMixin } from './src/slot-mixin.js';
|
|
9
11
|
export { TabindexMixin } from './src/tabindex-mixin.js';
|
package/index.js
CHANGED
|
@@ -4,6 +4,8 @@ export { DirMixin } from './src/dir-mixin.js';
|
|
|
4
4
|
export { DisabledMixin } from './src/disabled-mixin.js';
|
|
5
5
|
export { ElementMixin } from './src/element-mixin.js';
|
|
6
6
|
export { FocusMixin } from './src/focus-mixin.js';
|
|
7
|
+
export { FocusTrapController } from './src/focus-trap-controller.js';
|
|
7
8
|
export { KeyboardMixin } from './src/keyboard-mixin.js';
|
|
9
|
+
export { SlotController } from './src/slot-controller.js';
|
|
8
10
|
export { SlotMixin } from './src/slot-mixin.js';
|
|
9
11
|
export { TabindexMixin } from './src/tabindex-mixin.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/component-base",
|
|
3
|
-
"version": "23.0.0-
|
|
3
|
+
"version": "23.0.0-alpha5",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"main": "index.js",
|
|
20
20
|
"module": "index.js",
|
|
21
|
+
"type": "module",
|
|
21
22
|
"files": [
|
|
22
23
|
"custom_typings",
|
|
23
24
|
"index.d.ts",
|
|
@@ -41,5 +42,5 @@
|
|
|
41
42
|
"@vaadin/testing-helpers": "^0.3.2",
|
|
42
43
|
"sinon": "^9.2.4"
|
|
43
44
|
},
|
|
44
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "74f9294964eb8552d96578c14af6ad214f5257bc"
|
|
45
46
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2022 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Cause a text string to be announced by screen readers.
|
|
9
|
+
*/
|
|
10
|
+
export function announce(text: string, options?: { mode?: 'polite' | 'assertive'; timeout?: number }): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2022 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const region = document.createElement('div');
|
|
8
|
+
|
|
9
|
+
region.style.position = 'fixed';
|
|
10
|
+
region.style.clip = 'rect(0px, 0px, 0px, 0px)';
|
|
11
|
+
region.setAttribute('aria-live', 'polite');
|
|
12
|
+
|
|
13
|
+
document.body.appendChild(region);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Cause a text string to be announced by screen readers.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} text The text that should be announced by the screen reader.
|
|
19
|
+
* @param {{mode?: string, timeout?: number}} options Additional options.
|
|
20
|
+
*/
|
|
21
|
+
export function announce(text, options = {}) {
|
|
22
|
+
const mode = options.mode || 'polite';
|
|
23
|
+
const timeout = options.timeout === undefined ? 150 : options.timeout;
|
|
24
|
+
|
|
25
|
+
region.setAttribute('aria-live', mode);
|
|
26
|
+
|
|
27
|
+
region.textContent = '';
|
|
28
|
+
|
|
29
|
+
setTimeout(() => {
|
|
30
|
+
region.textContent = text;
|
|
31
|
+
}, timeout);
|
|
32
|
+
}
|
package/src/active-mixin.d.ts
CHANGED
package/src/active-mixin.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { addListener } from '@vaadin/component-base/src/gestures.js';
|
|
7
7
|
import { DisabledMixin } from './disabled-mixin.js';
|
|
8
8
|
import { KeyboardMixin } from './keyboard-mixin.js';
|
|
9
9
|
|
|
@@ -19,7 +19,7 @@ import { KeyboardMixin } from './keyboard-mixin.js';
|
|
|
19
19
|
* @polymerMixin
|
|
20
20
|
*/
|
|
21
21
|
export const ActiveMixin = (superclass) =>
|
|
22
|
-
class ActiveMixinClass extends DisabledMixin(
|
|
22
|
+
class ActiveMixinClass extends DisabledMixin(KeyboardMixin(superclass)) {
|
|
23
23
|
/**
|
|
24
24
|
* An array of activation keys.
|
|
25
25
|
*
|
|
@@ -37,13 +37,13 @@ export const ActiveMixin = (superclass) =>
|
|
|
37
37
|
ready() {
|
|
38
38
|
super.ready();
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
addListener(this, 'down', (event) => {
|
|
41
41
|
if (this._shouldSetActive(event)) {
|
|
42
42
|
this._setActive(true);
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
addListener(this, 'up', () => {
|
|
47
47
|
this._setActive(false);
|
|
48
48
|
});
|
|
49
49
|
}
|
package/src/async.js
CHANGED
|
@@ -23,17 +23,17 @@
|
|
|
23
23
|
// Microtask implemented using Mutation Observer
|
|
24
24
|
let microtaskCurrHandle = 0;
|
|
25
25
|
let microtaskLastHandle = 0;
|
|
26
|
-
|
|
26
|
+
const microtaskCallbacks = [];
|
|
27
27
|
let microtaskNodeContent = 0;
|
|
28
28
|
let microtaskScheduled = false;
|
|
29
|
-
|
|
29
|
+
const microtaskNode = document.createTextNode('');
|
|
30
30
|
new window.MutationObserver(microtaskFlush).observe(microtaskNode, { characterData: true });
|
|
31
31
|
|
|
32
32
|
function microtaskFlush() {
|
|
33
33
|
microtaskScheduled = false;
|
|
34
34
|
const len = microtaskCallbacks.length;
|
|
35
35
|
for (let i = 0; i < len; i++) {
|
|
36
|
-
|
|
36
|
+
const cb = microtaskCallbacks[i];
|
|
37
37
|
if (cb) {
|
|
38
38
|
try {
|
|
39
39
|
cb();
|
|
@@ -182,10 +182,13 @@ const microTask = {
|
|
|
182
182
|
run(callback) {
|
|
183
183
|
if (!microtaskScheduled) {
|
|
184
184
|
microtaskScheduled = true;
|
|
185
|
-
microtaskNode.textContent = microtaskNodeContent
|
|
185
|
+
microtaskNode.textContent = microtaskNodeContent;
|
|
186
|
+
microtaskNodeContent += 1;
|
|
186
187
|
}
|
|
187
188
|
microtaskCallbacks.push(callback);
|
|
188
|
-
|
|
189
|
+
const result = microtaskCurrHandle;
|
|
190
|
+
microtaskCurrHandle += 1;
|
|
191
|
+
return result;
|
|
189
192
|
},
|
|
190
193
|
|
|
191
194
|
/**
|
package/src/browser-utils.js
CHANGED
package/src/controller-mixin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
|
package/src/debounce.js
CHANGED
|
@@ -17,6 +17,7 @@ export class Debouncer {
|
|
|
17
17
|
this._callback = null;
|
|
18
18
|
this._timer = null;
|
|
19
19
|
}
|
|
20
|
+
|
|
20
21
|
/**
|
|
21
22
|
* Sets the scheduler; that is, a module with the Async interface,
|
|
22
23
|
* a callback and optional arguments to be passed to the run function
|
|
@@ -35,6 +36,7 @@ export class Debouncer {
|
|
|
35
36
|
this._callback();
|
|
36
37
|
});
|
|
37
38
|
}
|
|
39
|
+
|
|
38
40
|
/**
|
|
39
41
|
* Cancels an active debouncer and returns a reference to itself.
|
|
40
42
|
*
|
|
@@ -50,6 +52,7 @@ export class Debouncer {
|
|
|
50
52
|
debouncerQueue.delete(this);
|
|
51
53
|
}
|
|
52
54
|
}
|
|
55
|
+
|
|
53
56
|
/**
|
|
54
57
|
* Cancels a debouncer's async callback.
|
|
55
58
|
*
|
|
@@ -61,6 +64,7 @@ export class Debouncer {
|
|
|
61
64
|
this._timer = null;
|
|
62
65
|
}
|
|
63
66
|
}
|
|
67
|
+
|
|
64
68
|
/**
|
|
65
69
|
* Flushes an active debouncer and returns a reference to itself.
|
|
66
70
|
*
|
|
@@ -72,6 +76,7 @@ export class Debouncer {
|
|
|
72
76
|
this._callback();
|
|
73
77
|
}
|
|
74
78
|
}
|
|
79
|
+
|
|
75
80
|
/**
|
|
76
81
|
* Returns true if the debouncer is active.
|
|
77
82
|
*
|
|
@@ -80,6 +85,7 @@ export class Debouncer {
|
|
|
80
85
|
isActive() {
|
|
81
86
|
return this._timer != null;
|
|
82
87
|
}
|
|
88
|
+
|
|
83
89
|
/**
|
|
84
90
|
* Creates a debouncer if no debouncer is passed as a parameter
|
|
85
91
|
* or it cancels an active debouncer otherwise. The following
|
|
@@ -135,16 +141,16 @@ let debouncerQueue = new Set();
|
|
|
135
141
|
* @param {!Debouncer} debouncer Debouncer to enqueue
|
|
136
142
|
* @return {void}
|
|
137
143
|
*/
|
|
138
|
-
export
|
|
144
|
+
export function enqueueDebouncer(debouncer) {
|
|
139
145
|
debouncerQueue.add(debouncer);
|
|
140
|
-
}
|
|
146
|
+
}
|
|
141
147
|
|
|
142
148
|
/**
|
|
143
149
|
* Flushes any enqueued debouncers
|
|
144
150
|
*
|
|
145
151
|
* @return {boolean} Returns whether any debouncers were flushed
|
|
146
152
|
*/
|
|
147
|
-
export
|
|
153
|
+
export function flushDebouncers() {
|
|
148
154
|
const didFlush = Boolean(debouncerQueue.size);
|
|
149
155
|
// If new debouncers are added while flushing, Set.forEach will ensure
|
|
150
156
|
// newly added ones are also flushed
|
|
@@ -158,7 +164,7 @@ export const flushDebouncers = function () {
|
|
|
158
164
|
}
|
|
159
165
|
});
|
|
160
166
|
return didFlush;
|
|
161
|
-
}
|
|
167
|
+
}
|
|
162
168
|
|
|
163
169
|
export const flush = () => {
|
|
164
170
|
let debouncers;
|
package/src/dir-helper.d.ts
CHANGED
package/src/dir-helper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -57,8 +57,9 @@ class DirHelper {
|
|
|
57
57
|
return element.scrollWidth - element.clientWidth + scrollLeft;
|
|
58
58
|
case 'reverse':
|
|
59
59
|
return element.scrollWidth - element.clientWidth - scrollLeft;
|
|
60
|
+
default:
|
|
61
|
+
return scrollLeft;
|
|
60
62
|
}
|
|
61
|
-
return scrollLeft;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
/**
|
package/src/dir-mixin.d.ts
CHANGED
package/src/dir-mixin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import { DirHelper } from './dir-helper.js';
|
|
@@ -9,29 +9,29 @@ import { DirHelper } from './dir-helper.js';
|
|
|
9
9
|
* Array of Vaadin custom element classes that have been subscribed to the dir changes.
|
|
10
10
|
*/
|
|
11
11
|
const directionSubscribers = [];
|
|
12
|
-
|
|
12
|
+
function directionUpdater() {
|
|
13
13
|
const documentDir = getDocumentDir();
|
|
14
14
|
directionSubscribers.forEach((element) => {
|
|
15
15
|
alignDirs(element, documentDir);
|
|
16
16
|
});
|
|
17
|
-
}
|
|
17
|
+
}
|
|
18
18
|
|
|
19
19
|
let scrollType;
|
|
20
20
|
|
|
21
21
|
const directionObserver = new MutationObserver(directionUpdater);
|
|
22
22
|
directionObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['dir'] });
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
function alignDirs(element, documentDir, elementDir = element.getAttribute('dir')) {
|
|
25
25
|
if (documentDir) {
|
|
26
26
|
element.setAttribute('dir', documentDir);
|
|
27
27
|
} else if (elementDir != null) {
|
|
28
28
|
element.removeAttribute('dir');
|
|
29
29
|
}
|
|
30
|
-
}
|
|
30
|
+
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
function getDocumentDir() {
|
|
33
33
|
return document.documentElement.getAttribute('dir');
|
|
34
|
-
}
|
|
34
|
+
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* A mixin to handle `dir` attribute based on the one set on the `<html>` element.
|
package/src/disabled-mixin.d.ts
CHANGED
package/src/disabled-mixin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
|
package/src/element-mixin.d.ts
CHANGED
package/src/element-mixin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import { usageStatistics } from '@vaadin/vaadin-usage-statistics/vaadin-usage-statistics.js';
|
|
@@ -32,7 +32,7 @@ const registered = new Set();
|
|
|
32
32
|
export const ElementMixin = (superClass) =>
|
|
33
33
|
class VaadinElementMixin extends DirMixin(superClass) {
|
|
34
34
|
static get version() {
|
|
35
|
-
return '23.0.0-
|
|
35
|
+
return '23.0.0-alpha5';
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** @protected */
|
package/src/focus-mixin.d.ts
CHANGED
package/src/focus-mixin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license
|
|
3
|
-
* Copyright (c) 2021 Vaadin Ltd.
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
import { ReactiveController } from 'lit';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A controller for trapping focus within a DOM node.
|
|
10
|
+
*/
|
|
11
|
+
export class FocusTrapController implements ReactiveController {
|
|
12
|
+
constructor(node: HTMLElement);
|
|
13
|
+
|
|
14
|
+
hostConnected(): void;
|
|
15
|
+
|
|
16
|
+
hostDisconnected(): void;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The controller host element.
|
|
20
|
+
*/
|
|
21
|
+
host: HTMLElement;
|
|
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
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 2022 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
|
+
/**
|
|
9
|
+
* A controller for trapping focus within a DOM node.
|
|
10
|
+
*/
|
|
11
|
+
export class FocusTrapController {
|
|
12
|
+
/**
|
|
13
|
+
* @param {HTMLElement} host
|
|
14
|
+
*/
|
|
15
|
+
constructor(host) {
|
|
16
|
+
/**
|
|
17
|
+
* The controller host element.
|
|
18
|
+
*
|
|
19
|
+
* @type {HTMLElement}
|
|
20
|
+
*/
|
|
21
|
+
this.host = host;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A node for trapping focus in.
|
|
25
|
+
*
|
|
26
|
+
* @type {HTMLElement | null}
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
this.__trapNode = null;
|
|
30
|
+
|
|
31
|
+
this.__onKeyDown = this.__onKeyDown.bind(this);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
hostConnected() {
|
|
35
|
+
document.addEventListener('keydown', this.__onKeyDown);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
hostDisconnected() {
|
|
39
|
+
document.removeEventListener('keydown', this.__onKeyDown);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Activates a focus trap for a DOM node that will prevent focus from escaping the node.
|
|
44
|
+
* The trap can be deactivated with the `.releaseFocus()` method.
|
|
45
|
+
*
|
|
46
|
+
* If focus is initially outside the trap, the method will move focus inside,
|
|
47
|
+
* on the first focusable element of the trap in the tab order.
|
|
48
|
+
* The first focusable element can be the trap node itself if it is focusable
|
|
49
|
+
* and comes first in the tab order.
|
|
50
|
+
*
|
|
51
|
+
* If there are no focusable elements, the method will throw an exception
|
|
52
|
+
* and the trap will not be set.
|
|
53
|
+
*
|
|
54
|
+
* @param {HTMLElement} trapNode
|
|
55
|
+
*/
|
|
56
|
+
trapFocus(trapNode) {
|
|
57
|
+
this.__trapNode = trapNode;
|
|
58
|
+
|
|
59
|
+
if (this.__focusableElements.length === 0) {
|
|
60
|
+
this.__trapNode = null;
|
|
61
|
+
throw new Error('The trap node should have at least one focusable descendant or be focusable itself.');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.__focusedElementIndex === -1) {
|
|
65
|
+
this.__focusableElements[0].focus();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Deactivates the focus trap set with the `.trapFocus()` method
|
|
71
|
+
* so that it becomes possible to tab outside the trap node.
|
|
72
|
+
*/
|
|
73
|
+
releaseFocus() {
|
|
74
|
+
this.__trapNode = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A `keydown` event handler that manages tabbing navigation when the trap is enabled.
|
|
79
|
+
*
|
|
80
|
+
* - Moves focus to the next focusable element of the trap on `Tab` press.
|
|
81
|
+
* When no next element to focus, the method moves focus to the first focusable element.
|
|
82
|
+
* - Moves focus to the prev focusable element of the trap on `Shift+Tab` press.
|
|
83
|
+
* When no prev element to focus, the method moves focus to the last focusable element.
|
|
84
|
+
*
|
|
85
|
+
* @param {KeyboardEvent} event
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
__onKeyDown(event) {
|
|
89
|
+
if (!this.__trapNode) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (event.key === 'Tab') {
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
|
|
96
|
+
const backward = event.shiftKey;
|
|
97
|
+
this.__focusNextElement(backward);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* - Moves focus to the next focusable element if `backward === false`.
|
|
103
|
+
* When no next element to focus, the method moves focus to the first focusable element.
|
|
104
|
+
* - Moves focus to the prev focusable element if `backward === true`.
|
|
105
|
+
* When no prev element to focus the method moves focus to the last focusable element.
|
|
106
|
+
*
|
|
107
|
+
* If no focusable elements, the method returns immediately.
|
|
108
|
+
*
|
|
109
|
+
* @param {boolean} backward
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
__focusNextElement(backward = false) {
|
|
113
|
+
const focusableElements = this.__focusableElements;
|
|
114
|
+
const step = backward ? -1 : 1;
|
|
115
|
+
const currentIndex = this.__focusedElementIndex;
|
|
116
|
+
const nextIndex = (focusableElements.length + currentIndex + step) % focusableElements.length;
|
|
117
|
+
focusableElements[nextIndex].focus();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* An array of tab-ordered focusable elements inside the trap node.
|
|
122
|
+
*
|
|
123
|
+
* @return {HTMLElement[]}
|
|
124
|
+
* @private
|
|
125
|
+
*/
|
|
126
|
+
get __focusableElements() {
|
|
127
|
+
return getFocusableElements(this.__trapNode);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The index of the element inside the trap node that currently has focus.
|
|
132
|
+
*
|
|
133
|
+
* @return {HTMLElement | undefined}
|
|
134
|
+
* @private
|
|
135
|
+
*/
|
|
136
|
+
get __focusedElementIndex() {
|
|
137
|
+
return this.__focusableElements.findIndex(isElementFocused);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 2022 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 element is hidden, false otherwise.
|
|
9
|
+
*
|
|
10
|
+
* An element is treated as hidden when any of the following conditions are met:
|
|
11
|
+
* - the element itself or one of its ancestors has `display: none`.
|
|
12
|
+
* - the element has or inherits `visibility: hidden`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isElementHidden(element: HTMLElement): boolean;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns true if the element is focusable, otherwise false.
|
|
18
|
+
*
|
|
19
|
+
* The list of focusable elements is taken from http://stackoverflow.com/a/1600194/4228703.
|
|
20
|
+
* However, there isn't a definite list, it's up to the browser.
|
|
21
|
+
* The only standard we have is DOM Level 2 HTML https://www.w3.org/TR/DOM-Level-2-HTML/html.html,
|
|
22
|
+
* according to which the only elements that have a `focus()` method are:
|
|
23
|
+
* - HTMLInputElement
|
|
24
|
+
* - HTMLSelectElement
|
|
25
|
+
* - HTMLTextAreaElement
|
|
26
|
+
* - HTMLAnchorElement
|
|
27
|
+
*
|
|
28
|
+
* This notably omits HTMLButtonElement and HTMLAreaElement.
|
|
29
|
+
* Referring to these tests with tabbables in different browsers
|
|
30
|
+
* http://allyjs.io/data-tables/focusable.html
|
|
31
|
+
*/
|
|
32
|
+
export declare function isElementFocusable(element: HTMLElement): boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns true if the element is focused, false otherwise.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isElementFocused(element: HTMLElement): boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Returns a tab-ordered array of focusable elements for a root element.
|
|
41
|
+
* The resulting array will include the root element if it is focusable.
|
|
42
|
+
*
|
|
43
|
+
* The method traverses nodes in shadow DOM trees too if any.
|
|
44
|
+
*/
|
|
45
|
+
export declare function getFocusableElements(element: HTMLElement): HTMLElement[];
|