@vaadin/component-base 24.2.0-dev.e9803eea7 → 24.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/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +3 -3
- package/src/data-provider-controller/cache.js +50 -59
- package/src/data-provider-controller/data-provider-controller.js +241 -0
- package/src/data-provider-controller/helpers.js +13 -6
- package/src/define.d.ts +10 -0
- package/src/define.js +21 -0
- package/src/dom-utils.d.ts +7 -0
- package/src/dom-utils.js +21 -0
- package/src/element-mixin.d.ts +1 -0
- package/src/element-mixin.js +1 -1
- package/src/overflow-controller.js +23 -13
- package/src/path-utils.d.ts +15 -0
- package/src/path-utils.js +29 -0
- package/src/polylit-mixin.js +38 -16
- package/src/slot-controller.js +6 -6
- package/src/slot-observer.d.ts +34 -0
- package/src/slot-observer.js +105 -0
- package/src/slot-styles-mixin.d.ts +21 -0
- package/src/slot-styles-mixin.js +76 -0
- package/src/tooltip-controller.d.ts +13 -0
- package/src/tooltip-controller.js +36 -0
- package/src/data-provider-controller.js +0 -116
package/index.d.ts
CHANGED
|
@@ -4,3 +4,4 @@ export { DirMixin } from './src/dir-mixin.js';
|
|
|
4
4
|
export { ElementMixin } from './src/element-mixin.js';
|
|
5
5
|
export { ResizeMixin } from './src/resize-mixin.js';
|
|
6
6
|
export { SlotController } from './src/slot-controller.js';
|
|
7
|
+
export { SlotStylesMixin } from './src/slot-styles-mixin.js';
|
package/index.js
CHANGED
|
@@ -4,3 +4,4 @@ export { DirMixin } from './src/dir-mixin.js';
|
|
|
4
4
|
export { ElementMixin } from './src/element-mixin.js';
|
|
5
5
|
export { ResizeMixin } from './src/resize-mixin.js';
|
|
6
6
|
export { SlotController } from './src/slot-controller.js';
|
|
7
|
+
export { SlotStylesMixin } from './src/slot-styles-mixin.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/component-base",
|
|
3
|
-
"version": "24.
|
|
3
|
+
"version": "24.3.0-alpha1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@esm-bundle/chai": "^4.3.4",
|
|
42
|
-
"@vaadin/testing-helpers": "^0.
|
|
42
|
+
"@vaadin/testing-helpers": "^0.5.0",
|
|
43
43
|
"sinon": "^13.0.2"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "9ca6f3ca220a777e8eea181a1f5717e39a732240"
|
|
46
46
|
}
|
|
@@ -1,8 +1,17 @@
|
|
|
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
|
+
* A class that stores items with their associated sub-caches.
|
|
9
|
+
*/
|
|
1
10
|
export class Cache {
|
|
2
11
|
/**
|
|
3
|
-
* @type {
|
|
12
|
+
* @type {{ isExpanded: (item: unknown) => boolean }}
|
|
4
13
|
*/
|
|
5
|
-
|
|
14
|
+
context;
|
|
6
15
|
|
|
7
16
|
/**
|
|
8
17
|
* The number of items.
|
|
@@ -18,13 +27,6 @@ export class Cache {
|
|
|
18
27
|
*/
|
|
19
28
|
pageSize;
|
|
20
29
|
|
|
21
|
-
/**
|
|
22
|
-
* The total number of items, including items from expanded sub-caches.
|
|
23
|
-
*
|
|
24
|
-
* @type {number}
|
|
25
|
-
*/
|
|
26
|
-
effectiveSize = 0;
|
|
27
|
-
|
|
28
30
|
/**
|
|
29
31
|
* An array of cached items.
|
|
30
32
|
*
|
|
@@ -49,23 +51,32 @@ export class Cache {
|
|
|
49
51
|
* in alphabetical order, rather than the order they were added.
|
|
50
52
|
*
|
|
51
53
|
* @type {Record<number, Cache>}
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
__subCacheByIndex = {};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The total number of items, including items from expanded sub-caches.
|
|
60
|
+
*
|
|
61
|
+
* @type {number}
|
|
62
|
+
* @private
|
|
52
63
|
*/
|
|
53
|
-
|
|
64
|
+
__effectiveSize = 0;
|
|
54
65
|
|
|
55
66
|
/**
|
|
56
|
-
* @param {Cache['
|
|
67
|
+
* @param {Cache['context']} context
|
|
57
68
|
* @param {number} pageSize
|
|
58
69
|
* @param {number | undefined} size
|
|
59
70
|
* @param {Cache | undefined} parentCache
|
|
60
71
|
* @param {number | undefined} parentCacheIndex
|
|
61
72
|
*/
|
|
62
|
-
constructor(
|
|
63
|
-
this.
|
|
73
|
+
constructor(context, pageSize, size, parentCache, parentCacheIndex) {
|
|
74
|
+
this.context = context;
|
|
64
75
|
this.pageSize = pageSize;
|
|
65
76
|
this.size = size || 0;
|
|
66
|
-
this.effectiveSize = size || 0;
|
|
67
77
|
this.parentCache = parentCache;
|
|
68
78
|
this.parentCacheIndex = parentCacheIndex;
|
|
79
|
+
this.__effectiveSize = size || 0;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
/**
|
|
@@ -77,6 +88,16 @@ export class Cache {
|
|
|
77
88
|
return this.parentCache && this.parentCache.items[this.parentCacheIndex];
|
|
78
89
|
}
|
|
79
90
|
|
|
91
|
+
/**
|
|
92
|
+
* An array of sub-caches sorted in the same order as their associated items
|
|
93
|
+
* appear in the `items` array.
|
|
94
|
+
*
|
|
95
|
+
* @return {Cache[]}
|
|
96
|
+
*/
|
|
97
|
+
get subCaches() {
|
|
98
|
+
return Object.values(this.__subCacheByIndex);
|
|
99
|
+
}
|
|
100
|
+
|
|
80
101
|
/**
|
|
81
102
|
* Whether the cache or any of its descendant caches have pending requests.
|
|
82
103
|
*
|
|
@@ -87,29 +108,26 @@ export class Cache {
|
|
|
87
108
|
return true;
|
|
88
109
|
}
|
|
89
110
|
|
|
90
|
-
return
|
|
111
|
+
return this.subCaches.some((subCache) => subCache.isLoading);
|
|
91
112
|
}
|
|
92
113
|
|
|
93
114
|
/**
|
|
94
|
-
*
|
|
95
|
-
* appear in the `items` array.
|
|
115
|
+
* The total number of items, including items from expanded sub-caches.
|
|
96
116
|
*
|
|
97
|
-
* @return {
|
|
117
|
+
* @return {number}
|
|
98
118
|
*/
|
|
99
|
-
get
|
|
100
|
-
return
|
|
101
|
-
return [parseInt(index), subCache];
|
|
102
|
-
});
|
|
119
|
+
get effectiveSize() {
|
|
120
|
+
return this.__effectiveSize;
|
|
103
121
|
}
|
|
104
122
|
|
|
105
123
|
/**
|
|
106
124
|
* Recalculates the effective size for the cache and its descendant caches recursively.
|
|
107
125
|
*/
|
|
108
126
|
recalculateEffectiveSize() {
|
|
109
|
-
this.
|
|
110
|
-
!this.parentItem || this.
|
|
127
|
+
this.__effectiveSize =
|
|
128
|
+
!this.parentItem || this.context.isExpanded(this.parentItem)
|
|
111
129
|
? this.size +
|
|
112
|
-
|
|
130
|
+
this.subCaches.reduce((total, subCache) => {
|
|
113
131
|
subCache.recalculateEffectiveSize();
|
|
114
132
|
return total + subCache.effectiveSize;
|
|
115
133
|
}, 0)
|
|
@@ -138,7 +156,7 @@ export class Cache {
|
|
|
138
156
|
* @return {Cache | undefined}
|
|
139
157
|
*/
|
|
140
158
|
getSubCache(index) {
|
|
141
|
-
return this
|
|
159
|
+
return this.__subCacheByIndex[index];
|
|
142
160
|
}
|
|
143
161
|
|
|
144
162
|
/**
|
|
@@ -148,32 +166,14 @@ export class Cache {
|
|
|
148
166
|
* @param {number} index
|
|
149
167
|
*/
|
|
150
168
|
removeSubCache(index) {
|
|
151
|
-
|
|
152
|
-
delete this.#subCacheByIndex[index];
|
|
153
|
-
|
|
154
|
-
this.controller.dispatchEvent(
|
|
155
|
-
new CustomEvent('sub-cache-removed', {
|
|
156
|
-
detail: {
|
|
157
|
-
cache: this,
|
|
158
|
-
subCache,
|
|
159
|
-
},
|
|
160
|
-
}),
|
|
161
|
-
);
|
|
169
|
+
delete this.__subCacheByIndex[index];
|
|
162
170
|
}
|
|
163
171
|
|
|
164
172
|
/**
|
|
165
173
|
* Removes all sub-caches.
|
|
166
174
|
*/
|
|
167
175
|
removeSubCaches() {
|
|
168
|
-
this
|
|
169
|
-
|
|
170
|
-
this.controller.dispatchEvent(
|
|
171
|
-
new CustomEvent('sub-caches-removed', {
|
|
172
|
-
detail: {
|
|
173
|
-
cache: this,
|
|
174
|
-
},
|
|
175
|
-
}),
|
|
176
|
-
);
|
|
176
|
+
this.__subCacheByIndex = {};
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
/**
|
|
@@ -184,18 +184,8 @@ export class Cache {
|
|
|
184
184
|
* @return {Cache}
|
|
185
185
|
*/
|
|
186
186
|
createSubCache(index) {
|
|
187
|
-
const subCache = new Cache(this.
|
|
188
|
-
this
|
|
189
|
-
|
|
190
|
-
this.controller.dispatchEvent(
|
|
191
|
-
new CustomEvent('sub-cache-created', {
|
|
192
|
-
detail: {
|
|
193
|
-
cache: this,
|
|
194
|
-
subCache,
|
|
195
|
-
},
|
|
196
|
-
}),
|
|
197
|
-
);
|
|
198
|
-
|
|
187
|
+
const subCache = new Cache(this.context, this.pageSize, 0, this, index);
|
|
188
|
+
this.__subCacheByIndex[index] = subCache;
|
|
199
189
|
return subCache;
|
|
200
190
|
}
|
|
201
191
|
|
|
@@ -209,7 +199,8 @@ export class Cache {
|
|
|
209
199
|
getFlatIndex(index) {
|
|
210
200
|
const clampedIndex = Math.max(0, Math.min(this.size - 1, index));
|
|
211
201
|
|
|
212
|
-
return this.subCaches.reduce((prev,
|
|
202
|
+
return this.subCaches.reduce((prev, subCache) => {
|
|
203
|
+
const index = subCache.parentCacheIndex;
|
|
213
204
|
return clampedIndex > index ? prev + subCache.effectiveSize : prev;
|
|
214
205
|
}, clampedIndex);
|
|
215
206
|
}
|
|
@@ -0,0 +1,241 @@
|
|
|
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 { Cache } from './cache.js';
|
|
7
|
+
import { getFlatIndexByPath, getFlatIndexContext } from './helpers.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A controller that stores and manages items loaded with a data provider.
|
|
11
|
+
*/
|
|
12
|
+
export class DataProviderController extends EventTarget {
|
|
13
|
+
/**
|
|
14
|
+
* The controller host element.
|
|
15
|
+
*
|
|
16
|
+
* @param {HTMLElement}
|
|
17
|
+
*/
|
|
18
|
+
host;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A callback that returns data based on the passed params such as
|
|
22
|
+
* `page`, `pageSize`, `parentItem`, etc.
|
|
23
|
+
*/
|
|
24
|
+
dataProvider;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A callback that returns additional params that need to be passed
|
|
28
|
+
* to the data provider callback with every request.
|
|
29
|
+
*/
|
|
30
|
+
dataProviderParams;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A number of items in the root cache.
|
|
34
|
+
*
|
|
35
|
+
* @type {number}
|
|
36
|
+
*/
|
|
37
|
+
size;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A number of items to display per page.
|
|
41
|
+
*
|
|
42
|
+
* @type {number}
|
|
43
|
+
*/
|
|
44
|
+
pageSize;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A callback that returns whether the given item is expanded.
|
|
48
|
+
*
|
|
49
|
+
* @type {(item: unknown) => boolean}
|
|
50
|
+
*/
|
|
51
|
+
isExpanded;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A reference to the root cache instance.
|
|
55
|
+
*
|
|
56
|
+
* @param {Cache}
|
|
57
|
+
*/
|
|
58
|
+
rootCache;
|
|
59
|
+
|
|
60
|
+
constructor(host, { size, pageSize, isExpanded, dataProvider, dataProviderParams }) {
|
|
61
|
+
super();
|
|
62
|
+
this.host = host;
|
|
63
|
+
this.size = size;
|
|
64
|
+
this.pageSize = pageSize;
|
|
65
|
+
this.isExpanded = isExpanded;
|
|
66
|
+
this.dataProvider = dataProvider;
|
|
67
|
+
this.dataProviderParams = dataProviderParams;
|
|
68
|
+
this.rootCache = this.__createRootCache();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The total number of items, including items from expanded sub-caches.
|
|
73
|
+
*/
|
|
74
|
+
get effectiveSize() {
|
|
75
|
+
return this.rootCache.effectiveSize;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** @private */
|
|
79
|
+
get __cacheContext() {
|
|
80
|
+
return { isExpanded: this.isExpanded };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Whether the root cache or any of its decendant caches have pending requests.
|
|
85
|
+
*
|
|
86
|
+
* @return {boolean}
|
|
87
|
+
*/
|
|
88
|
+
isLoading() {
|
|
89
|
+
return this.rootCache.isLoading;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Sets the size for the root cache and recalculates the effective size.
|
|
94
|
+
*
|
|
95
|
+
* @param {number} size
|
|
96
|
+
*/
|
|
97
|
+
setSize(size) {
|
|
98
|
+
this.size = size;
|
|
99
|
+
this.rootCache.size = size;
|
|
100
|
+
this.recalculateEffectiveSize();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Sets the page size and clears the cache.
|
|
105
|
+
*
|
|
106
|
+
* @param {number} pageSize
|
|
107
|
+
*/
|
|
108
|
+
setPageSize(pageSize) {
|
|
109
|
+
this.pageSize = pageSize;
|
|
110
|
+
this.clearCache();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Sets the data provider callback and clears the cache.
|
|
115
|
+
*
|
|
116
|
+
* @type {Function}
|
|
117
|
+
*/
|
|
118
|
+
setDataProvider(dataProvider) {
|
|
119
|
+
this.dataProvider = dataProvider;
|
|
120
|
+
this.clearCache();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Recalculates the effective size.
|
|
125
|
+
*/
|
|
126
|
+
recalculateEffectiveSize() {
|
|
127
|
+
this.rootCache.recalculateEffectiveSize();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Clears the cache.
|
|
132
|
+
*/
|
|
133
|
+
clearCache() {
|
|
134
|
+
this.rootCache = this.__createRootCache();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Returns context for the given flattened index, including:
|
|
139
|
+
* - the corresponding cache
|
|
140
|
+
* - the associated item (if loaded)
|
|
141
|
+
* - the corresponding index in the cache's items array.
|
|
142
|
+
* - the page containing the index.
|
|
143
|
+
* - the cache level
|
|
144
|
+
*/
|
|
145
|
+
getFlatIndexContext(flatIndex) {
|
|
146
|
+
return getFlatIndexContext(this.rootCache, flatIndex);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Returns the flattened index for the item that the given indexes point to.
|
|
151
|
+
* Each index in the path array points to a sub-item of the previous index.
|
|
152
|
+
* Using `Infinity` as an index will point to the last item on the level.
|
|
153
|
+
*
|
|
154
|
+
* @param {number[]} path
|
|
155
|
+
* @return {number}
|
|
156
|
+
*/
|
|
157
|
+
getFlatIndexByPath(path) {
|
|
158
|
+
return getFlatIndexByPath(this.rootCache, path);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Requests the data provider to load the page with the item corresponding
|
|
163
|
+
* to the given flattened index. If the item is already loaded, the method
|
|
164
|
+
* returns immediatelly.
|
|
165
|
+
*
|
|
166
|
+
* @param {number} flatIndex
|
|
167
|
+
*/
|
|
168
|
+
ensureFlatIndexLoaded(flatIndex) {
|
|
169
|
+
const { cache, page, item } = this.getFlatIndexContext(flatIndex);
|
|
170
|
+
|
|
171
|
+
if (!item) {
|
|
172
|
+
this.__loadCachePage(cache, page);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Creates a sub-cache for the item corresponding to the given flattened index and
|
|
178
|
+
* requests the data provider to load the first page into the created sub-cache.
|
|
179
|
+
* If the sub-cache already exists, the method returns immediatelly.
|
|
180
|
+
*
|
|
181
|
+
* @param {number} flatIndex
|
|
182
|
+
*/
|
|
183
|
+
ensureFlatIndexHierarchy(flatIndex) {
|
|
184
|
+
const { cache, item, index } = this.getFlatIndexContext(flatIndex);
|
|
185
|
+
|
|
186
|
+
if (item && this.isExpanded(item) && !cache.getSubCache(index)) {
|
|
187
|
+
const subCache = cache.createSubCache(index);
|
|
188
|
+
this.__loadCachePage(subCache, 0);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Loads the first page into the root cache.
|
|
194
|
+
*/
|
|
195
|
+
loadFirstPage() {
|
|
196
|
+
this.__loadCachePage(this.rootCache, 0);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** @private */
|
|
200
|
+
__createRootCache() {
|
|
201
|
+
return new Cache(this.__cacheContext, this.pageSize, this.size);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** @private */
|
|
205
|
+
__loadCachePage(cache, page) {
|
|
206
|
+
if (!this.dataProvider || cache.pendingRequests.has(page)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const params = {
|
|
211
|
+
page,
|
|
212
|
+
pageSize: this.pageSize,
|
|
213
|
+
parentItem: cache.parentItem,
|
|
214
|
+
...this.dataProviderParams(),
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const callback = (items, size) => {
|
|
218
|
+
if (size !== undefined) {
|
|
219
|
+
cache.size = size;
|
|
220
|
+
} else if (params.parentItem) {
|
|
221
|
+
cache.size = items.length;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
cache.setPage(page, items);
|
|
225
|
+
|
|
226
|
+
this.recalculateEffectiveSize();
|
|
227
|
+
|
|
228
|
+
this.dispatchEvent(new CustomEvent('page-received'));
|
|
229
|
+
|
|
230
|
+
cache.pendingRequests.delete(page);
|
|
231
|
+
|
|
232
|
+
this.dispatchEvent(new CustomEvent('page-loaded'));
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
cache.pendingRequests.set(page, callback);
|
|
236
|
+
|
|
237
|
+
this.dispatchEvent(new CustomEvent('page-requested'));
|
|
238
|
+
|
|
239
|
+
this.dataProvider(params, callback);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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 context for the given flattened index, including:
|
|
3
9
|
* - the corresponding cache
|
|
4
10
|
* - the associated item (if loaded)
|
|
5
11
|
* - the corresponding index in the cache's items array.
|
|
@@ -10,14 +16,15 @@
|
|
|
10
16
|
* @param {number} flatIndex
|
|
11
17
|
* @return {{ cache: Cache, item: object | undefined, index: number, page: number, level: number }}
|
|
12
18
|
*/
|
|
13
|
-
export function
|
|
19
|
+
export function getFlatIndexContext(cache, flatIndex, level = 0) {
|
|
14
20
|
let levelIndex = flatIndex;
|
|
15
21
|
|
|
16
|
-
for (const
|
|
22
|
+
for (const subCache of cache.subCaches) {
|
|
23
|
+
const index = subCache.parentCacheIndex;
|
|
17
24
|
if (levelIndex <= index) {
|
|
18
25
|
break;
|
|
19
26
|
} else if (levelIndex <= index + subCache.effectiveSize) {
|
|
20
|
-
return
|
|
27
|
+
return getFlatIndexContext(subCache, levelIndex - index - 1, level + 1);
|
|
21
28
|
}
|
|
22
29
|
levelIndex -= subCache.effectiveSize;
|
|
23
30
|
}
|
|
@@ -36,8 +43,8 @@ export function getFlatIndexInfo(cache, flatIndex, level = 0) {
|
|
|
36
43
|
* Each index in the array points to a sub-item of the previous index.
|
|
37
44
|
* Using `Infinity` as an index will point to the last item on the level.
|
|
38
45
|
*
|
|
39
|
-
* @param {
|
|
40
|
-
* @param {
|
|
46
|
+
* @param {Cache} cache
|
|
47
|
+
* @param {number[]} path
|
|
41
48
|
* @param {number} flatIndex
|
|
42
49
|
* @return {number}
|
|
43
50
|
*/
|
package/src/define.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export interface CustomElementType extends CustomElementConstructor {
|
|
7
|
+
is: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export declare function defineCustomElement(CustomElement: CustomElementConstructor): void;
|
package/src/define.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
export function defineCustomElement(CustomElement) {
|
|
7
|
+
const defined = customElements.get(CustomElement.is);
|
|
8
|
+
if (!defined) {
|
|
9
|
+
customElements.define(CustomElement.is, CustomElement);
|
|
10
|
+
} else {
|
|
11
|
+
const definedVersion = defined.version;
|
|
12
|
+
if (definedVersion && CustomElement.version && definedVersion === CustomElement.version) {
|
|
13
|
+
// Just loading the same thing again
|
|
14
|
+
console.warn(`The component ${CustomElement.is} has been loaded twice`);
|
|
15
|
+
} else {
|
|
16
|
+
console.error(
|
|
17
|
+
`Tried to define ${CustomElement.is} version ${CustomElement.version} when version ${defined.version} is already in use. Something will probably break.`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/dom-utils.d.ts
CHANGED
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export function getAncestorRootNodes(node: Node): Node[];
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Returns the list of flattened elements for the given `node`.
|
|
18
|
+
* This list consists of a node's children and, for any children that are
|
|
19
|
+
* `<slot>` elements, the expanded flattened list of `assignedElements`.
|
|
20
|
+
*/
|
|
21
|
+
export function getFlattenedElements(node: Node): Element[];
|
|
22
|
+
|
|
16
23
|
/**
|
|
17
24
|
* Traverses the given node and its parents, including those that are across
|
|
18
25
|
* the shadow root boundaries, until it finds a node that matches the selector.
|
package/src/dom-utils.js
CHANGED
|
@@ -40,6 +40,27 @@ export function getAncestorRootNodes(node) {
|
|
|
40
40
|
return result;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Returns the list of flattened elements for the given `node`.
|
|
45
|
+
* This list consists of a node's children and, for any children that are
|
|
46
|
+
* `<slot>` elements, the expanded flattened list of `assignedElements`.
|
|
47
|
+
*
|
|
48
|
+
* @param {Node} node
|
|
49
|
+
* @return {Element[]}
|
|
50
|
+
*/
|
|
51
|
+
export function getFlattenedElements(node) {
|
|
52
|
+
const result = [];
|
|
53
|
+
let elements;
|
|
54
|
+
if (node.localName === 'slot') {
|
|
55
|
+
elements = node.assignedElements();
|
|
56
|
+
} else {
|
|
57
|
+
result.push(node);
|
|
58
|
+
elements = [...node.children];
|
|
59
|
+
}
|
|
60
|
+
elements.forEach((elem) => result.push(...getFlattenedElements(elem)));
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
43
64
|
/**
|
|
44
65
|
* Traverses the given node and its parents, including those that are across
|
|
45
66
|
* the shadow root boundaries, until it finds a node that matches the selector.
|
package/src/element-mixin.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare function ElementMixin<T extends Constructor<HTMLElement>>(
|
|
|
16
16
|
): Constructor<DirMixinClass> & Constructor<ElementMixinClass> & T;
|
|
17
17
|
|
|
18
18
|
export declare class ElementMixinClass {
|
|
19
|
+
static is: string;
|
|
19
20
|
static version: string;
|
|
20
21
|
|
|
21
22
|
protected static finalize(): void;
|
package/src/element-mixin.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
|
|
7
6
|
import { animationFrame } from './async.js';
|
|
8
7
|
import { Debouncer } from './debounce.js';
|
|
9
8
|
|
|
@@ -47,30 +46,41 @@ export class OverflowController {
|
|
|
47
46
|
* @protected
|
|
48
47
|
*/
|
|
49
48
|
observe() {
|
|
50
|
-
|
|
49
|
+
const { host } = this;
|
|
50
|
+
|
|
51
|
+
this.__resizeObserver = new ResizeObserver((entries) => {
|
|
51
52
|
this.__debounceOverflow = Debouncer.debounce(this.__debounceOverflow, animationFrame, () => {
|
|
52
53
|
this.__updateOverflow();
|
|
53
54
|
});
|
|
54
55
|
});
|
|
55
56
|
|
|
56
|
-
this.__resizeObserver.observe(
|
|
57
|
+
this.__resizeObserver.observe(host);
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
});
|
|
59
|
+
// Observe initial children
|
|
60
|
+
[...host.children].forEach((child) => {
|
|
61
|
+
this.__resizeObserver.observe(child);
|
|
62
|
+
});
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
this.__childObserver = new MutationObserver((mutations) => {
|
|
65
|
+
mutations.forEach(({ addedNodes, removedNodes }) => {
|
|
66
|
+
addedNodes.forEach((node) => {
|
|
67
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
68
|
+
this.__resizeObserver.observe(node);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
removedNodes.forEach((node) => {
|
|
73
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
74
|
+
this.__resizeObserver.unobserve(node);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
69
77
|
});
|
|
70
78
|
|
|
71
79
|
this.__updateOverflow();
|
|
72
80
|
});
|
|
73
81
|
|
|
82
|
+
this.__childObserver.observe(host, { childList: true });
|
|
83
|
+
|
|
74
84
|
// Update overflow attribute on scroll
|
|
75
85
|
this.scrollTarget.addEventListener('scroll', this.__boundOnScroll);
|
|
76
86
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2023 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convenience method for reading a value from a path.
|
|
9
|
+
*/
|
|
10
|
+
export function get(path: string, object: object): unknown;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convenience method for setting a value to a path.
|
|
14
|
+
*/
|
|
15
|
+
export function set(path: string, value: unknown, object: object): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2023 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convenience method for reading a value from a path.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} path
|
|
11
|
+
* @param {object} object
|
|
12
|
+
*/
|
|
13
|
+
export function get(path, object) {
|
|
14
|
+
return path.split('.').reduce((obj, property) => (obj ? obj[property] : undefined), object);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convenience method for setting a value to a path.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} path
|
|
21
|
+
* @param {unknown} value
|
|
22
|
+
* @param {object} object
|
|
23
|
+
*/
|
|
24
|
+
export function set(path, value, object) {
|
|
25
|
+
const pathParts = path.split('.');
|
|
26
|
+
const lastPart = pathParts.pop();
|
|
27
|
+
const target = pathParts.reduce((target, part) => target[part], object);
|
|
28
|
+
target[lastPart] = value;
|
|
29
|
+
}
|
package/src/polylit-mixin.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
6
|
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
|
7
|
+
import { get, set } from './path-utils.js';
|
|
7
8
|
|
|
8
9
|
const caseMap = {};
|
|
9
10
|
|
|
@@ -92,15 +93,22 @@ const PolylitMixinImplementation = (superclass) => {
|
|
|
92
93
|
|
|
93
94
|
let result = defaultDescriptor;
|
|
94
95
|
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
if (options.sync) {
|
|
97
|
+
result = {
|
|
98
|
+
get: defaultDescriptor.get,
|
|
99
|
+
set(value) {
|
|
100
|
+
const oldValue = this[name];
|
|
101
|
+
this[key] = value;
|
|
102
|
+
this.requestUpdate(name, oldValue, options);
|
|
103
|
+
|
|
104
|
+
// Enforce synchronous update
|
|
105
|
+
if (this.hasUpdated) {
|
|
106
|
+
this.performUpdate();
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
configurable: true,
|
|
110
|
+
enumerable: true,
|
|
111
|
+
};
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
if (options.readOnly) {
|
|
@@ -110,6 +118,10 @@ const PolylitMixinImplementation = (superclass) => {
|
|
|
110
118
|
// This is run during construction of the element
|
|
111
119
|
instance[`_set${upper(name)}`] = function (value) {
|
|
112
120
|
setter.call(instance, value);
|
|
121
|
+
|
|
122
|
+
if (options.sync) {
|
|
123
|
+
this.performUpdate();
|
|
124
|
+
}
|
|
113
125
|
};
|
|
114
126
|
});
|
|
115
127
|
|
|
@@ -123,6 +135,19 @@ const PolylitMixinImplementation = (superclass) => {
|
|
|
123
135
|
};
|
|
124
136
|
}
|
|
125
137
|
|
|
138
|
+
if ('value' in options) {
|
|
139
|
+
// Set the default value
|
|
140
|
+
this.addCheckedInitializer((instance) => {
|
|
141
|
+
const value = typeof options.value === 'function' ? options.value.call(instance) : options.value;
|
|
142
|
+
|
|
143
|
+
if (options.readOnly) {
|
|
144
|
+
instance[`_set${upper(name)}`](value);
|
|
145
|
+
} else {
|
|
146
|
+
instance[name] = value;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
126
151
|
if (options.observer) {
|
|
127
152
|
const method = options.observer;
|
|
128
153
|
|
|
@@ -175,7 +200,7 @@ const PolylitMixinImplementation = (superclass) => {
|
|
|
175
200
|
this.$ = {};
|
|
176
201
|
}
|
|
177
202
|
|
|
178
|
-
this.
|
|
203
|
+
this.renderRoot.querySelectorAll('[id]').forEach((node) => {
|
|
179
204
|
this.$[node.id] = node;
|
|
180
205
|
});
|
|
181
206
|
}
|
|
@@ -202,8 +227,8 @@ const PolylitMixinImplementation = (superclass) => {
|
|
|
202
227
|
}
|
|
203
228
|
|
|
204
229
|
if (!this.__isReadyInvoked) {
|
|
205
|
-
this.ready();
|
|
206
230
|
this.__isReadyInvoked = true;
|
|
231
|
+
this.ready();
|
|
207
232
|
}
|
|
208
233
|
}
|
|
209
234
|
|
|
@@ -254,15 +279,12 @@ const PolylitMixinImplementation = (superclass) => {
|
|
|
254
279
|
|
|
255
280
|
/** @protected */
|
|
256
281
|
_get(path, object) {
|
|
257
|
-
return path
|
|
282
|
+
return get(path, object);
|
|
258
283
|
}
|
|
259
284
|
|
|
260
285
|
/** @protected */
|
|
261
286
|
_set(path, value, object) {
|
|
262
|
-
|
|
263
|
-
const lastPart = pathParts.pop();
|
|
264
|
-
const target = pathParts.reduce((target, part) => target[part], object);
|
|
265
|
-
target[lastPart] = value;
|
|
287
|
+
set(path, value, object);
|
|
266
288
|
}
|
|
267
289
|
}
|
|
268
290
|
|
package/src/slot-controller.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Copyright (c) 2021 - 2023 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
|
|
7
6
|
import { isEmptyTextNode } from './dom-utils.js';
|
|
7
|
+
import { SlotObserver } from './slot-observer.js';
|
|
8
8
|
import { generateUniqueId } from './unique-id-utils.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -199,17 +199,17 @@ export class SlotController extends EventTarget {
|
|
|
199
199
|
const selector = slotName === '' ? 'slot:not([name])' : `slot[name=${slotName}]`;
|
|
200
200
|
const slot = this.host.shadowRoot.querySelector(selector);
|
|
201
201
|
|
|
202
|
-
this.__slotObserver = new
|
|
202
|
+
this.__slotObserver = new SlotObserver(slot, ({ addedNodes, removedNodes }) => {
|
|
203
203
|
const current = this.multiple ? this.nodes : [this.node];
|
|
204
204
|
|
|
205
205
|
// Calling `slot.assignedNodes()` includes whitespace text nodes in case of default slot:
|
|
206
206
|
// unlike comment nodes, they are not filtered out. So we need to manually ignore them.
|
|
207
|
-
const newNodes =
|
|
207
|
+
const newNodes = addedNodes.filter((node) => !isEmptyTextNode(node) && !current.includes(node));
|
|
208
208
|
|
|
209
|
-
if (
|
|
210
|
-
this.nodes = current.filter((node) => !
|
|
209
|
+
if (removedNodes.length) {
|
|
210
|
+
this.nodes = current.filter((node) => !removedNodes.includes(node));
|
|
211
211
|
|
|
212
|
-
|
|
212
|
+
removedNodes.forEach((node) => {
|
|
213
213
|
this.teardownNode(node);
|
|
214
214
|
});
|
|
215
215
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2023 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A helper for observing slot changes.
|
|
9
|
+
*/
|
|
10
|
+
export class SlotObserver {
|
|
11
|
+
constructor(
|
|
12
|
+
slot: HTMLSlotElement,
|
|
13
|
+
callback: (info: { addedNodes: Node[]; movedNodes: Node[]; removedNodes: Node[] }) => void,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Activates an observer. This method is automatically called when
|
|
18
|
+
* a `SlotObserver` is created. It should only be called to re-activate
|
|
19
|
+
* an observer that has been deactivated via the `disconnect` method.
|
|
20
|
+
*/
|
|
21
|
+
connect(): void;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Deactivates the observer. After calling this method the observer callback
|
|
25
|
+
* will not be called when changes to slotted nodes occur. The `connect` method
|
|
26
|
+
* may be subsequently called to reactivate the observer.
|
|
27
|
+
*/
|
|
28
|
+
disconnect(): void;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Run the observer callback synchronously.
|
|
32
|
+
*/
|
|
33
|
+
flush(): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2023 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A helper for observing slot changes.
|
|
9
|
+
*/
|
|
10
|
+
export class SlotObserver {
|
|
11
|
+
constructor(slot, callback) {
|
|
12
|
+
/** @type HTMLSlotElement */
|
|
13
|
+
this.slot = slot;
|
|
14
|
+
|
|
15
|
+
/** @type Function */
|
|
16
|
+
this.callback = callback;
|
|
17
|
+
|
|
18
|
+
/** @type {Node[]} */
|
|
19
|
+
this._storedNodes = [];
|
|
20
|
+
|
|
21
|
+
this._connected = false;
|
|
22
|
+
this._scheduled = false;
|
|
23
|
+
|
|
24
|
+
this._boundSchedule = () => {
|
|
25
|
+
this._schedule();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
this.connect();
|
|
29
|
+
this._schedule();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Activates an observer. This method is automatically called when
|
|
34
|
+
* a `SlotObserver` is created. It should only be called to re-activate
|
|
35
|
+
* an observer that has been deactivated via the `disconnect` method.
|
|
36
|
+
*/
|
|
37
|
+
connect() {
|
|
38
|
+
this.slot.addEventListener('slotchange', this._boundSchedule);
|
|
39
|
+
this._connected = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Deactivates the observer. After calling this method the observer callback
|
|
44
|
+
* will not be called when changes to slotted nodes occur. The `connect` method
|
|
45
|
+
* may be subsequently called to reactivate the observer.
|
|
46
|
+
*/
|
|
47
|
+
disconnect() {
|
|
48
|
+
this.slot.removeEventListener('slotchange', this._boundSchedule);
|
|
49
|
+
this._connected = false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @private */
|
|
53
|
+
_schedule() {
|
|
54
|
+
if (!this._scheduled) {
|
|
55
|
+
this._scheduled = true;
|
|
56
|
+
|
|
57
|
+
queueMicrotask(() => {
|
|
58
|
+
this.flush();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run the observer callback synchronously.
|
|
65
|
+
*/
|
|
66
|
+
flush() {
|
|
67
|
+
if (!this._connected) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this._scheduled = false;
|
|
72
|
+
|
|
73
|
+
this._processNodes();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @private */
|
|
77
|
+
_processNodes() {
|
|
78
|
+
const currentNodes = this.slot.assignedNodes({ flatten: true });
|
|
79
|
+
|
|
80
|
+
let addedNodes = [];
|
|
81
|
+
const removedNodes = [];
|
|
82
|
+
const movedNodes = [];
|
|
83
|
+
|
|
84
|
+
if (currentNodes.length) {
|
|
85
|
+
addedNodes = currentNodes.filter((node) => !this._storedNodes.includes(node));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this._storedNodes.length) {
|
|
89
|
+
this._storedNodes.forEach((node, index) => {
|
|
90
|
+
const idx = currentNodes.indexOf(node);
|
|
91
|
+
if (idx === -1) {
|
|
92
|
+
removedNodes.push(node);
|
|
93
|
+
} else if (idx !== index) {
|
|
94
|
+
movedNodes.push(node);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (addedNodes.length || removedNodes.length || movedNodes.length) {
|
|
100
|
+
this.callback({ addedNodes, movedNodes, removedNodes });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this._storedNodes = currentNodes;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
* Mixin to insert styles into the outer scope to handle slotted components.
|
|
10
|
+
* This is useful e.g. to hide native `<input type="number">` controls.
|
|
11
|
+
*/
|
|
12
|
+
export declare function SlotStylesMixin<T extends Constructor<HTMLElement>>(
|
|
13
|
+
base: T,
|
|
14
|
+
): Constructor<SlotStylesMixinClass> & T;
|
|
15
|
+
|
|
16
|
+
export declare class SlotStylesMixinClass {
|
|
17
|
+
/**
|
|
18
|
+
* List of styles to insert into root.
|
|
19
|
+
*/
|
|
20
|
+
protected readonly slotStyles: string[];
|
|
21
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
const stylesMap = new WeakMap();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get all the styles inserted into root.
|
|
12
|
+
* @param {DocumentOrShadowRoot} root
|
|
13
|
+
* @return {Set<string>}
|
|
14
|
+
*/
|
|
15
|
+
function getRootStyles(root) {
|
|
16
|
+
if (!stylesMap.has(root)) {
|
|
17
|
+
stylesMap.set(root, new Set());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return stylesMap.get(root);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Insert styles into the root.
|
|
25
|
+
* @param {string} styles
|
|
26
|
+
* @param {DocumentOrShadowRoot} root
|
|
27
|
+
*/
|
|
28
|
+
function insertStyles(styles, root) {
|
|
29
|
+
const style = document.createElement('style');
|
|
30
|
+
style.textContent = styles;
|
|
31
|
+
|
|
32
|
+
if (root === document) {
|
|
33
|
+
document.head.appendChild(style);
|
|
34
|
+
} else {
|
|
35
|
+
root.insertBefore(style, root.firstChild);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Mixin to insert styles into the outer scope to handle slotted components.
|
|
41
|
+
* This is useful e.g. to hide native `<input type="number">` controls.
|
|
42
|
+
*
|
|
43
|
+
* @polymerMixin
|
|
44
|
+
*/
|
|
45
|
+
export const SlotStylesMixin = dedupingMixin(
|
|
46
|
+
(superclass) =>
|
|
47
|
+
class SlotStylesMixinClass extends superclass {
|
|
48
|
+
/**
|
|
49
|
+
* List of styles to insert into root.
|
|
50
|
+
* @protected
|
|
51
|
+
*/
|
|
52
|
+
get slotStyles() {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @protected */
|
|
57
|
+
connectedCallback() {
|
|
58
|
+
super.connectedCallback();
|
|
59
|
+
|
|
60
|
+
this.__applySlotStyles();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** @private */
|
|
64
|
+
__applySlotStyles() {
|
|
65
|
+
const root = this.getRootNode();
|
|
66
|
+
const rootStyles = getRootStyles(root);
|
|
67
|
+
|
|
68
|
+
this.slotStyles.forEach((styles) => {
|
|
69
|
+
if (!rootStyles.has(styles)) {
|
|
70
|
+
insertStyles(styles, root);
|
|
71
|
+
rootStyles.add(styles);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
);
|
|
@@ -23,6 +23,13 @@ type TooltipPosition =
|
|
|
23
23
|
* A controller that manages the slotted tooltip element.
|
|
24
24
|
*/
|
|
25
25
|
export class TooltipController extends SlotController {
|
|
26
|
+
/**
|
|
27
|
+
* An HTML element for linking with the tooltip overlay
|
|
28
|
+
* via `aria-describedby` attribute used by screen readers.
|
|
29
|
+
* When not set, defaults to `target`.
|
|
30
|
+
*/
|
|
31
|
+
ariaTarget: HTMLElement | HTMLElement[];
|
|
32
|
+
|
|
26
33
|
/**
|
|
27
34
|
* Object with properties passed to `generator`
|
|
28
35
|
* function to be used for generating tooltip text.
|
|
@@ -51,6 +58,12 @@ export class TooltipController extends SlotController {
|
|
|
51
58
|
*/
|
|
52
59
|
target: HTMLElement;
|
|
53
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Set an HTML element for linking with the tooltip overlay
|
|
63
|
+
* via `aria-describedby` attribute used by screen readers.
|
|
64
|
+
*/
|
|
65
|
+
setAriaTarget(ariaTarget: HTMLElement | HTMLElement[]): void;
|
|
66
|
+
|
|
54
67
|
/**
|
|
55
68
|
* Set a context object to be used by generator.
|
|
56
69
|
*/
|
|
@@ -26,6 +26,10 @@ export class TooltipController extends SlotController {
|
|
|
26
26
|
initCustomNode(tooltipNode) {
|
|
27
27
|
tooltipNode.target = this.target;
|
|
28
28
|
|
|
29
|
+
if (this.ariaTarget !== undefined) {
|
|
30
|
+
tooltipNode.ariaTarget = this.ariaTarget;
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
if (this.context !== undefined) {
|
|
30
34
|
tooltipNode.context = this.context;
|
|
31
35
|
}
|
|
@@ -45,6 +49,33 @@ export class TooltipController extends SlotController {
|
|
|
45
49
|
if (this.shouldShow !== undefined) {
|
|
46
50
|
tooltipNode.shouldShow = this.shouldShow;
|
|
47
51
|
}
|
|
52
|
+
|
|
53
|
+
this.__notifyChange();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Override to notify the host when the tooltip is removed.
|
|
58
|
+
*
|
|
59
|
+
* @param {Node} tooltipNode
|
|
60
|
+
* @protected
|
|
61
|
+
* @override
|
|
62
|
+
*/
|
|
63
|
+
teardownNode() {
|
|
64
|
+
this.__notifyChange();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set an HTML element for linking with the tooltip overlay
|
|
69
|
+
* via `aria-describedby` attribute used by screen readers.
|
|
70
|
+
* @param {HTMLElement} ariaTarget
|
|
71
|
+
*/
|
|
72
|
+
setAriaTarget(ariaTarget) {
|
|
73
|
+
this.ariaTarget = ariaTarget;
|
|
74
|
+
|
|
75
|
+
const tooltipNode = this.node;
|
|
76
|
+
if (tooltipNode) {
|
|
77
|
+
tooltipNode.ariaTarget = ariaTarget;
|
|
78
|
+
}
|
|
48
79
|
}
|
|
49
80
|
|
|
50
81
|
/**
|
|
@@ -127,4 +158,9 @@ export class TooltipController extends SlotController {
|
|
|
127
158
|
tooltipNode.target = target;
|
|
128
159
|
}
|
|
129
160
|
}
|
|
161
|
+
|
|
162
|
+
/** @private */
|
|
163
|
+
__notifyChange() {
|
|
164
|
+
this.dispatchEvent(new CustomEvent('tooltip-changed', { detail: { node: this.node } }));
|
|
165
|
+
}
|
|
130
166
|
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { Cache } from './data-provider-controller/cache.js';
|
|
2
|
-
import { getFlatIndexByPath, getFlatIndexInfo } from './data-provider-controller/helpers.js';
|
|
3
|
-
|
|
4
|
-
export class DataProviderController extends EventTarget {
|
|
5
|
-
constructor(host, { size, pageSize, isExpanded, dataProvider, dataProviderParams }) {
|
|
6
|
-
super();
|
|
7
|
-
this.host = host;
|
|
8
|
-
this.size = size;
|
|
9
|
-
this.pageSize = pageSize;
|
|
10
|
-
this.isExpanded = isExpanded;
|
|
11
|
-
this.dataProvider = dataProvider;
|
|
12
|
-
this.dataProviderParams = dataProviderParams;
|
|
13
|
-
this.rootCache = this.#createRootCache();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
get effectiveSize() {
|
|
17
|
-
return this.rootCache.effectiveSize;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
isLoading() {
|
|
21
|
-
return this.rootCache.isLoading;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
setSize(size) {
|
|
25
|
-
const delta = size - this.rootCache.size;
|
|
26
|
-
this.size = size;
|
|
27
|
-
this.rootCache.size += delta;
|
|
28
|
-
this.rootCache.effectiveSize += delta;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
setPageSize(pageSize) {
|
|
32
|
-
this.pageSize = pageSize;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
setDataProvider(dataProvider) {
|
|
36
|
-
this.dataProvider = dataProvider;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
recalculateEffectiveSize() {
|
|
40
|
-
this.rootCache.recalculateEffectiveSize();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
clearCache() {
|
|
44
|
-
this.rootCache = this.#createRootCache();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
getFlatIndexInfo(flatIndex) {
|
|
48
|
-
return getFlatIndexInfo(this.rootCache, flatIndex);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getFlatIndexByPath(path) {
|
|
52
|
-
return getFlatIndexByPath(this.rootCache, path);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
ensureFlatIndexLoaded(flatIndex) {
|
|
56
|
-
const { cache, page, item } = this.getFlatIndexInfo(flatIndex);
|
|
57
|
-
|
|
58
|
-
if (!item) {
|
|
59
|
-
this.#loadCachePage(cache, page);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
ensureFlatIndexHierarchy(flatIndex) {
|
|
64
|
-
const { cache, item, index } = this.getFlatIndexInfo(flatIndex);
|
|
65
|
-
|
|
66
|
-
if (item && this.isExpanded(item) && !cache.getSubCache(index)) {
|
|
67
|
-
const subCache = cache.createSubCache(index);
|
|
68
|
-
this.#loadCachePage(subCache, 0);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
loadFirstPage() {
|
|
73
|
-
this.#loadCachePage(this.rootCache, 0);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
#createRootCache() {
|
|
77
|
-
return new Cache(this, this.pageSize, this.size);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
#loadCachePage(cache, page) {
|
|
81
|
-
if (!this.dataProvider || cache.pendingRequests.has(page)) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const params = {
|
|
86
|
-
page,
|
|
87
|
-
pageSize: this.pageSize,
|
|
88
|
-
parentItem: cache.parentItem,
|
|
89
|
-
...this.dataProviderParams(),
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const callback = (items, size) => {
|
|
93
|
-
if (size !== undefined) {
|
|
94
|
-
cache.size = size;
|
|
95
|
-
} else if (params.parentItem) {
|
|
96
|
-
cache.size = items.length;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
cache.setPage(page, items);
|
|
100
|
-
|
|
101
|
-
this.recalculateEffectiveSize();
|
|
102
|
-
|
|
103
|
-
this.dispatchEvent(new CustomEvent('page-received'));
|
|
104
|
-
|
|
105
|
-
cache.pendingRequests.delete(page);
|
|
106
|
-
|
|
107
|
-
this.dispatchEvent(new CustomEvent('page-loaded'));
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
cache.pendingRequests.set(page, callback);
|
|
111
|
-
|
|
112
|
-
this.dispatchEvent(new CustomEvent('page-requested'));
|
|
113
|
-
|
|
114
|
-
this.dataProvider(params, callback);
|
|
115
|
-
}
|
|
116
|
-
}
|