@vaadin/component-base 23.2.0 → 23.3.0-alpha1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/component-base",
|
|
3
|
-
"version": "23.
|
|
3
|
+
"version": "23.3.0-alpha1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"@vaadin/testing-helpers": "^0.3.2",
|
|
43
43
|
"sinon": "^13.0.2"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "beabc527d4b1274eb798ff701d406fed45cfe638"
|
|
46
46
|
}
|
package/src/element-mixin.js
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
import type { Constructor } from '@open-wc/dedupe-mixin';
|
|
7
|
+
import type { KeyboardMixinClass } from './keyboard-mixin.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A mixin for navigating items with keyboard.
|
|
11
|
+
*/
|
|
12
|
+
export declare function KeyboardDirectionMixin<T extends Constructor<HTMLElement>>(
|
|
13
|
+
base: T,
|
|
14
|
+
): Constructor<KeyboardDirectionMixinClass> & Constructor<KeyboardMixinClass> & T;
|
|
15
|
+
|
|
16
|
+
export declare class KeyboardDirectionMixinClass {
|
|
17
|
+
protected readonly focused: Element | null;
|
|
18
|
+
|
|
19
|
+
protected readonly _vertical: boolean;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns index of the next item that satisfies the given condition,
|
|
23
|
+
* based on the index of the current item and a numeric increment.
|
|
24
|
+
*/
|
|
25
|
+
protected _getAvailableIndex(
|
|
26
|
+
items: Element[],
|
|
27
|
+
index: number,
|
|
28
|
+
increment: number,
|
|
29
|
+
condition: (item: Element) => boolean,
|
|
30
|
+
): number;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Focus the item at given index. Override this method to add custom logic.
|
|
34
|
+
*/
|
|
35
|
+
protected _focus(index: number, navigating: boolean): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Focus the given item. Override this method to add custom logic.
|
|
39
|
+
*/
|
|
40
|
+
protected _focusItem(item: Element, navigating: boolean): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
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
|
+
import { isElementFocused, isElementHidden } from './focus-utils.js';
|
|
7
|
+
import { KeyboardMixin } from './keyboard-mixin.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A mixin for navigating items with keyboard.
|
|
11
|
+
*
|
|
12
|
+
* @polymerMixin
|
|
13
|
+
* @mixes KeyboardMixin
|
|
14
|
+
*/
|
|
15
|
+
export const KeyboardDirectionMixin = (superclass) =>
|
|
16
|
+
class KeyboardDirectionMixinClass extends KeyboardMixin(superclass) {
|
|
17
|
+
/** @protected */
|
|
18
|
+
focus() {
|
|
19
|
+
const items = this._getItems();
|
|
20
|
+
if (Array.isArray(items)) {
|
|
21
|
+
const idx = this._getAvailableIndex(items, 0, null, (item) => !isElementHidden(item));
|
|
22
|
+
if (idx >= 0) {
|
|
23
|
+
items[idx].focus();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @return {Element | null}
|
|
30
|
+
* @protected
|
|
31
|
+
*/
|
|
32
|
+
get focused() {
|
|
33
|
+
return (this._getItems() || []).find(isElementFocused);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @return {boolean}
|
|
38
|
+
* @protected
|
|
39
|
+
*/
|
|
40
|
+
get _vertical() {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the list of items participating in keyboard navigation.
|
|
46
|
+
* By default, it treats all the light DOM children as items.
|
|
47
|
+
* Override this method to provide custom list of elements.
|
|
48
|
+
*
|
|
49
|
+
* @return {Element[]}
|
|
50
|
+
* @protected
|
|
51
|
+
*/
|
|
52
|
+
_getItems() {
|
|
53
|
+
return Array.from(this.children);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Override an event listener from `KeyboardMixin`.
|
|
58
|
+
*
|
|
59
|
+
* @param {!KeyboardEvent} event
|
|
60
|
+
* @protected
|
|
61
|
+
* @override
|
|
62
|
+
*/
|
|
63
|
+
_onKeyDown(event) {
|
|
64
|
+
super._onKeyDown(event);
|
|
65
|
+
|
|
66
|
+
if (event.metaKey || event.ctrlKey) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { key } = event;
|
|
71
|
+
const items = this._getItems() || [];
|
|
72
|
+
const currentIdx = items.indexOf(this.focused);
|
|
73
|
+
|
|
74
|
+
let idx;
|
|
75
|
+
let increment;
|
|
76
|
+
|
|
77
|
+
const isRTL = !this._vertical && this.getAttribute('dir') === 'rtl';
|
|
78
|
+
const dirIncrement = isRTL ? -1 : 1;
|
|
79
|
+
|
|
80
|
+
if (this.__isPrevKey(key)) {
|
|
81
|
+
increment = -dirIncrement;
|
|
82
|
+
idx = currentIdx - dirIncrement;
|
|
83
|
+
} else if (this.__isNextKey(key)) {
|
|
84
|
+
increment = dirIncrement;
|
|
85
|
+
idx = currentIdx + dirIncrement;
|
|
86
|
+
} else if (key === 'Home') {
|
|
87
|
+
increment = 1;
|
|
88
|
+
idx = 0;
|
|
89
|
+
} else if (key === 'End') {
|
|
90
|
+
increment = -1;
|
|
91
|
+
idx = items.length - 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
idx = this._getAvailableIndex(items, idx, increment, (item) => !isElementHidden(item));
|
|
95
|
+
|
|
96
|
+
if (idx >= 0) {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
this._focus(idx, true);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {string} key
|
|
104
|
+
* @return {boolean}
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
__isPrevKey(key) {
|
|
108
|
+
return this._vertical ? key === 'ArrowUp' : key === 'ArrowLeft';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {string} key
|
|
113
|
+
* @return {boolean}
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
__isNextKey(key) {
|
|
117
|
+
return this._vertical ? key === 'ArrowDown' : key === 'ArrowRight';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Focus the item at given index. Override this method to add custom logic.
|
|
122
|
+
*
|
|
123
|
+
* @param {number} index
|
|
124
|
+
* @param {boolean} navigating
|
|
125
|
+
* @protected
|
|
126
|
+
*/
|
|
127
|
+
_focus(index, navigating = false) {
|
|
128
|
+
const items = this._getItems();
|
|
129
|
+
|
|
130
|
+
this._focusItem(items[index], navigating);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Focus the given item. Override this method to add custom logic.
|
|
135
|
+
*
|
|
136
|
+
* @param {Element} item
|
|
137
|
+
* @param {boolean} navigating
|
|
138
|
+
* @protected
|
|
139
|
+
*/
|
|
140
|
+
_focusItem(item) {
|
|
141
|
+
if (item) {
|
|
142
|
+
item.focus();
|
|
143
|
+
|
|
144
|
+
// Generally, the items are expected to implement `FocusMixin`
|
|
145
|
+
// that would set this attribute based on the `keydown` event.
|
|
146
|
+
// We set it manually to handle programmatic focus() calls.
|
|
147
|
+
item.setAttribute('focus-ring', '');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns index of the next item that satisfies the given condition,
|
|
153
|
+
* based on the index of the current item and a numeric increment.
|
|
154
|
+
*
|
|
155
|
+
* @param {Element[]} items - array of items to iterate over
|
|
156
|
+
* @param {number} index - index of the current item
|
|
157
|
+
* @param {number} increment - numeric increment, can be either 1 or -1
|
|
158
|
+
* @param {Function} condition - function used to check the item
|
|
159
|
+
* @return {number}
|
|
160
|
+
* @protected
|
|
161
|
+
*/
|
|
162
|
+
_getAvailableIndex(items, index, increment, condition) {
|
|
163
|
+
const totalItems = items.length;
|
|
164
|
+
let idx = index;
|
|
165
|
+
for (let i = 0; typeof idx === 'number' && i < totalItems; i += 1, idx += increment || 1) {
|
|
166
|
+
if (idx < 0) {
|
|
167
|
+
idx = totalItems - 1;
|
|
168
|
+
} else if (idx >= totalItems) {
|
|
169
|
+
idx = 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const item = items[idx];
|
|
173
|
+
|
|
174
|
+
if (!item.hasAttribute('disabled') && this.__isMatchingItem(item, condition)) {
|
|
175
|
+
return idx;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return -1;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Returns true if the item matches condition.
|
|
183
|
+
*
|
|
184
|
+
* @param {Element} item - item to check
|
|
185
|
+
* @param {Function} condition - function used to check the item
|
|
186
|
+
* @return {number}
|
|
187
|
+
* @private
|
|
188
|
+
*/
|
|
189
|
+
__isMatchingItem(item, condition) {
|
|
190
|
+
return typeof condition === 'function' ? condition(item) : true;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
import { SlotController } from './slot-controller.js';
|
|
7
|
+
|
|
8
|
+
type TooltipPosition =
|
|
9
|
+
| 'bottom-end'
|
|
10
|
+
| 'bottom-start'
|
|
11
|
+
| 'bottom'
|
|
12
|
+
| 'end-bottom'
|
|
13
|
+
| 'end-top'
|
|
14
|
+
| 'end'
|
|
15
|
+
| 'start-bottom'
|
|
16
|
+
| 'start-top'
|
|
17
|
+
| 'start'
|
|
18
|
+
| 'top-end'
|
|
19
|
+
| 'top-start'
|
|
20
|
+
| 'top';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A controller that manages the slotted tooltip element.
|
|
24
|
+
*/
|
|
25
|
+
export class TooltipController extends SlotController {
|
|
26
|
+
/**
|
|
27
|
+
* Object with properties passed to `textGenerator`
|
|
28
|
+
* function to be used for generating tooltip text.
|
|
29
|
+
*/
|
|
30
|
+
context: Record<string, unknown>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* When true, the tooltip is controlled programmatically
|
|
34
|
+
* instead of reacting to focus and mouse events.
|
|
35
|
+
*/
|
|
36
|
+
manual: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* When true, the tooltip is opened programmatically.
|
|
40
|
+
* Only works if `manual` is set to `true`.
|
|
41
|
+
*/
|
|
42
|
+
opened: boolean;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Position of the tooltip with respect to its target.
|
|
46
|
+
*/
|
|
47
|
+
position: TooltipPosition;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* An HTML element to attach the tooltip to.
|
|
51
|
+
*/
|
|
52
|
+
target: HTMLElement;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Set a context object to be used by text generator.
|
|
56
|
+
*/
|
|
57
|
+
setContext(context: Record<string, unknown>): void;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Toggle manual state on the slotted tooltip.
|
|
61
|
+
*/
|
|
62
|
+
setManual(manual: boolean): void;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Toggle opened state on the slotted tooltip.
|
|
66
|
+
*/
|
|
67
|
+
setOpened(opened: boolean): void;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Set position on the slotted tooltip.
|
|
71
|
+
*/
|
|
72
|
+
setPosition(position: TooltipPosition): void;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Set function used to detect whether to show
|
|
76
|
+
* the tooltip based on a condition.
|
|
77
|
+
*/
|
|
78
|
+
setShouldShow(shouldShow: (target: HTMLElement) => boolean): void;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Set an HTML element to attach the tooltip to.
|
|
82
|
+
*/
|
|
83
|
+
setTarget(target: HTMLElement): void;
|
|
84
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
import { SlotController } from './slot-controller.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A controller that manages the slotted tooltip element.
|
|
10
|
+
*/
|
|
11
|
+
export class TooltipController extends SlotController {
|
|
12
|
+
constructor(host) {
|
|
13
|
+
// Do not provide slot factory to create tooltip lazily.
|
|
14
|
+
super(host, 'tooltip');
|
|
15
|
+
|
|
16
|
+
this.setTarget(host);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Override to initialize the newly added custom tooltip.
|
|
21
|
+
*
|
|
22
|
+
* @param {Node} tooltipNode
|
|
23
|
+
* @protected
|
|
24
|
+
* @override
|
|
25
|
+
*/
|
|
26
|
+
initCustomNode(tooltipNode) {
|
|
27
|
+
tooltipNode.target = this.target;
|
|
28
|
+
|
|
29
|
+
if (this.context !== undefined) {
|
|
30
|
+
tooltipNode.context = this.context;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (this.manual !== undefined) {
|
|
34
|
+
tooltipNode.manual = this.manual;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (this.opened !== undefined) {
|
|
38
|
+
tooltipNode.opened = this.opened;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (this.position !== undefined) {
|
|
42
|
+
tooltipNode.position = this.position;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (this.shouldShow !== undefined) {
|
|
46
|
+
tooltipNode.shouldShow = this.shouldShow;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Set a context object to be used by text generator.
|
|
52
|
+
* @param {object} context
|
|
53
|
+
*/
|
|
54
|
+
setContext(context) {
|
|
55
|
+
this.context = context;
|
|
56
|
+
|
|
57
|
+
const tooltipNode = this.node;
|
|
58
|
+
if (tooltipNode) {
|
|
59
|
+
tooltipNode.context = context;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Toggle manual state on the slotted tooltip.
|
|
65
|
+
* @param {boolean} manual
|
|
66
|
+
*/
|
|
67
|
+
setManual(manual) {
|
|
68
|
+
this.manual = manual;
|
|
69
|
+
|
|
70
|
+
const tooltipNode = this.node;
|
|
71
|
+
if (tooltipNode) {
|
|
72
|
+
tooltipNode.manual = manual;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Toggle opened state on the slotted tooltip.
|
|
78
|
+
* @param {boolean} opened
|
|
79
|
+
*/
|
|
80
|
+
setOpened(opened) {
|
|
81
|
+
this.opened = opened;
|
|
82
|
+
|
|
83
|
+
const tooltipNode = this.node;
|
|
84
|
+
if (tooltipNode) {
|
|
85
|
+
tooltipNode.opened = opened;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Set position on the slotted tooltip.
|
|
91
|
+
* @param {string} position
|
|
92
|
+
*/
|
|
93
|
+
setPosition(position) {
|
|
94
|
+
this.position = position;
|
|
95
|
+
|
|
96
|
+
const tooltipNode = this.node;
|
|
97
|
+
if (tooltipNode) {
|
|
98
|
+
tooltipNode.position = position;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Set function used to detect whether to show
|
|
104
|
+
* the tooltip based on a condition.
|
|
105
|
+
* @param {Function} shouldShow
|
|
106
|
+
*/
|
|
107
|
+
setShouldShow(shouldShow) {
|
|
108
|
+
this.shouldShow = shouldShow;
|
|
109
|
+
|
|
110
|
+
const tooltipNode = this.node;
|
|
111
|
+
if (tooltipNode) {
|
|
112
|
+
tooltipNode.shouldShow = shouldShow;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Set an HTML element to attach the tooltip to.
|
|
118
|
+
* @param {HTMLElement} target
|
|
119
|
+
*/
|
|
120
|
+
setTarget(target) {
|
|
121
|
+
this.target = target;
|
|
122
|
+
|
|
123
|
+
const tooltipNode = this.node;
|
|
124
|
+
if (tooltipNode) {
|
|
125
|
+
tooltipNode.target = target;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|