@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.
package/index.d.ts CHANGED
@@ -1,11 +1,6 @@
1
- export { ActiveMixin } from './src/active-mixin.js';
2
1
  export { ControllerMixin } from './src/controller-mixin.js';
2
+ export { DelegateStateMixin } from './src/delegate-state-mixin.js';
3
3
  export { DirMixin } from './src/dir-mixin.js';
4
- export { DisabledMixin } from './src/disabled-mixin.js';
5
4
  export { ElementMixin } from './src/element-mixin.js';
6
- export { FocusMixin } from './src/focus-mixin.js';
7
- export { FocusTrapController } from './src/focus-trap-controller.js';
8
- export { KeyboardMixin } from './src/keyboard-mixin.js';
9
5
  export { ResizeMixin } from './src/resize-mixin.js';
10
6
  export { SlotController } from './src/slot-controller.js';
11
- export { TabindexMixin } from './src/tabindex-mixin.js';
package/index.js CHANGED
@@ -1,10 +1,6 @@
1
- export { ActiveMixin } from './src/active-mixin.js';
2
1
  export { ControllerMixin } from './src/controller-mixin.js';
2
+ export { DelegateStateMixin } from './src/delegate-state-mixin.js';
3
3
  export { DirMixin } from './src/dir-mixin.js';
4
- export { DisabledMixin } from './src/disabled-mixin.js';
5
4
  export { ElementMixin } from './src/element-mixin.js';
6
- export { FocusMixin } from './src/focus-mixin.js';
7
- export { FocusTrapController } from './src/focus-trap-controller.js';
8
- export { KeyboardMixin } from './src/keyboard-mixin.js';
5
+ export { ResizeMixin } from './src/resize-mixin.js';
9
6
  export { SlotController } from './src/slot-controller.js';
10
- 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": "24.0.5",
3
+ "version": "24.1.0-alpha10",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -42,5 +42,5 @@
42
42
  "@vaadin/testing-helpers": "^0.4.0",
43
43
  "sinon": "^13.0.2"
44
44
  },
45
- "gitHead": "e384c1b5c02e01a4382a433f727d075de9a4ee97"
45
+ "gitHead": "12e39be7eb3b49c68708e8ca3de2fb22e91051a1"
46
46
  }
package/src/async.js CHANGED
@@ -20,13 +20,10 @@
20
20
  * asynchronous tasks.
21
21
  */
22
22
 
23
- // Microtask implemented using Mutation Observer
24
23
  let microtaskCurrHandle = 0;
25
24
  let microtaskLastHandle = 0;
26
25
  const microtaskCallbacks = [];
27
- let microtaskNodeContent = 0;
28
26
  let microtaskScheduled = false;
29
- const microtaskNode = document.createTextNode('');
30
27
 
31
28
  function microtaskFlush() {
32
29
  microtaskScheduled = false;
@@ -47,8 +44,6 @@ function microtaskFlush() {
47
44
  microtaskLastHandle += len;
48
45
  }
49
46
 
50
- new window.MutationObserver(microtaskFlush).observe(microtaskNode, { characterData: true });
51
-
52
47
  /**
53
48
  * Async interface wrapper around `setTimeout`.
54
49
  *
@@ -166,12 +161,6 @@ export { idlePeriod };
166
161
  /**
167
162
  * Async interface for enqueuing callbacks that run at microtask timing.
168
163
  *
169
- * Note that microtask timing is achieved via a single `MutationObserver`,
170
- * and thus callbacks enqueued with this API will all run in a single
171
- * batch, and not interleaved with other microtasks such as promises.
172
- * Promises are avoided as an implementation choice for the time being
173
- * due to Safari bugs that cause Promises to lack microtask guarantees.
174
- *
175
164
  * @namespace
176
165
  * @summary Async interface for enqueuing callbacks that run at microtask
177
166
  * timing.
@@ -187,8 +176,7 @@ const microTask = {
187
176
  run(callback) {
188
177
  if (!microtaskScheduled) {
189
178
  microtaskScheduled = true;
190
- microtaskNode.textContent = microtaskNodeContent;
191
- microtaskNodeContent += 1;
179
+ queueMicrotask(() => microtaskFlush());
192
180
  }
193
181
  microtaskCallbacks.push(callback);
194
182
  const result = microtaskCurrHandle;
@@ -13,6 +13,16 @@
13
13
  */
14
14
  export function getAncestorRootNodes(node: Node): Node[];
15
15
 
16
+ /**
17
+ * Takes a string with values separated by space and returns a set the values
18
+ */
19
+ export function deserializeAttributeValue(value: string): Set<string>;
20
+
21
+ /**
22
+ * Takes a set of string values and returns a string with values separated by space
23
+ */
24
+ export function serializeAttributeValue(values: Set<string>): string;
25
+
16
26
  /**
17
27
  * Adds a value to an attribute containing space-delimited values.
18
28
  */
package/src/dom-utils.js CHANGED
@@ -41,10 +41,12 @@ export function getAncestorRootNodes(node) {
41
41
  }
42
42
 
43
43
  /**
44
+ * Takes a string with values separated by space and returns a set the values
45
+ *
44
46
  * @param {string} value
45
47
  * @return {Set<string>}
46
48
  */
47
- function deserializeAttributeValue(value) {
49
+ export function deserializeAttributeValue(value) {
48
50
  if (!value) {
49
51
  return new Set();
50
52
  }
@@ -53,11 +55,13 @@ function deserializeAttributeValue(value) {
53
55
  }
54
56
 
55
57
  /**
58
+ * Takes a set of string values and returns a string with values separated by space
59
+ *
56
60
  * @param {Set<string>} values
57
61
  * @return {string}
58
62
  */
59
- function serializeAttributeValue(values) {
60
- return [...values].join(' ');
63
+ export function serializeAttributeValue(values) {
64
+ return values ? [...values].join(' ') : '';
61
65
  }
62
66
 
63
67
  /**
@@ -45,7 +45,7 @@ const registered = new Set();
45
45
  export const ElementMixin = (superClass) =>
46
46
  class VaadinElementMixin extends DirMixin(superClass) {
47
47
  static get version() {
48
- return '24.0.5';
48
+ return '24.1.0-alpha10';
49
49
  }
50
50
 
51
51
  /** @protected */
@@ -136,11 +136,15 @@ export class IronListAdapter {
136
136
  }
137
137
 
138
138
  update(startIndex = 0, endIndex = this.size - 1) {
139
+ const updatedElements = [];
139
140
  this.__getVisibleElements().forEach((el) => {
140
141
  if (el.__virtualIndex >= startIndex && el.__virtualIndex <= endIndex) {
141
142
  this.__updateElement(el, el.__virtualIndex, true);
143
+ updatedElements.push(el);
142
144
  }
143
145
  });
146
+
147
+ this.__afterElementsUpdated(updatedElements);
144
148
  }
145
149
 
146
150
  /**
@@ -203,28 +207,40 @@ export class IronListAdapter {
203
207
  this.updateElement(el, index);
204
208
  el.__lastUpdatedIndex = index;
205
209
  }
210
+ }
206
211
 
207
- const elementHeight = el.offsetHeight;
208
- if (elementHeight === 0) {
209
- // If the elements have 0 height after update (for example due to lazy rendering),
210
- // it results in iron-list requesting to create an unlimited count of elements.
211
- // Assign a temporary placeholder sizing to elements that would otherwise end up having
212
- // no height.
213
- el.style.paddingTop = `${this.__placeholderHeight}px`;
214
-
215
- // Manually schedule the resize handler to make sure the placeholder padding is
216
- // cleared in case the resize observer never triggers.
217
- requestAnimationFrame(() => this._resizeHandler());
218
- } else {
219
- // Add element height to the queue
220
- this.__elementHeightQueue.push(elementHeight);
221
- this.__elementHeightQueue.shift();
222
-
223
- // Calcualte new placeholder height based on the average of the defined values in the
224
- // element height queue
225
- const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
226
- this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
227
- }
212
+ /**
213
+ * Called synchronously right after elements have been updated.
214
+ * This is a good place to do any post-update work.
215
+ *
216
+ * @param {!Array<!HTMLElement>} updatedElements
217
+ */
218
+ __afterElementsUpdated(updatedElements) {
219
+ updatedElements.forEach((el) => {
220
+ const elementHeight = el.offsetHeight;
221
+ if (elementHeight === 0) {
222
+ // If the elements have 0 height after update (for example due to lazy rendering),
223
+ // it results in iron-list requesting to create an unlimited count of elements.
224
+ // Assign a temporary placeholder sizing to elements that would otherwise end up having
225
+ // no height.
226
+ el.style.paddingTop = `${this.__placeholderHeight}px`;
227
+
228
+ // Manually schedule the resize handler to make sure the placeholder padding is
229
+ // cleared in case the resize observer never triggers.
230
+ this.__placeholderClearDebouncer = Debouncer.debounce(this.__placeholderClearDebouncer, animationFrame, () =>
231
+ this._resizeHandler(),
232
+ );
233
+ } else {
234
+ // Add element height to the queue
235
+ this.__elementHeightQueue.push(elementHeight);
236
+ this.__elementHeightQueue.shift();
237
+
238
+ // Calculate new placeholder height based on the average of the defined values in the
239
+ // element height queue
240
+ const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
241
+ this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
242
+ }
243
+ });
228
244
  }
229
245
 
230
246
  __getIndexScrollOffset(index) {
@@ -249,42 +265,28 @@ export class IronListAdapter {
249
265
  this._debouncers._increasePoolIfNeeded.cancel();
250
266
  }
251
267
 
252
- // Prevent element update while the scroll position is being restored
253
- this.__preventElementUpdates = true;
254
-
255
- // Record the scroll position before changing the size
256
- let fvi; // First visible index
257
- let fviOffsetBefore; // Scroll offset of the first visible index
258
- if (size > 0) {
259
- fvi = this.adjustedFirstVisibleIndex;
260
- fviOffsetBefore = this.__getIndexScrollOffset(fvi);
261
- }
262
-
263
268
  // Change the size
264
269
  this.__size = size;
265
270
 
266
- this._itemsChanged({
267
- path: 'items',
268
- });
269
- flush();
270
-
271
- // Try to restore the scroll position if the new size is larger than 0
272
- if (size > 0) {
273
- fvi = Math.min(fvi, size - 1);
274
- this.scrollToIndex(fvi);
275
-
276
- const fviOffsetAfter = this.__getIndexScrollOffset(fvi);
277
- if (fviOffsetBefore !== undefined && fviOffsetAfter !== undefined) {
278
- this._scrollTop += fviOffsetBefore - fviOffsetAfter;
279
- }
271
+ if (!this._physicalItems) {
272
+ // Not initialized yet
273
+ this._itemsChanged({
274
+ path: 'items',
275
+ });
276
+ this.__preventElementUpdates = true;
277
+ flush();
278
+ this.__preventElementUpdates = false;
279
+ } else {
280
+ // Already initialized, just update _virtualCount
281
+ this._virtualCount = this.items.length;
280
282
  }
281
283
 
282
284
  if (!this.elementsContainer.children.length) {
283
285
  requestAnimationFrame(() => this._resizeHandler());
284
286
  }
285
287
 
286
- this.__preventElementUpdates = false;
287
- // Schedule and flush a resize handler
288
+ // Schedule and flush a resize handler. This will cause a
289
+ // re-render for the elements.
288
290
  this._resizeHandler();
289
291
  flush();
290
292
  }
@@ -349,16 +351,20 @@ export class IronListAdapter {
349
351
 
350
352
  /** @private */
351
353
  _assignModels(itemSet) {
354
+ const updatedElements = [];
352
355
  this._iterateItems((pidx, vidx) => {
353
356
  const el = this._physicalItems[pidx];
354
357
  el.hidden = vidx >= this.size;
355
358
  if (!el.hidden) {
356
359
  el.__virtualIndex = vidx + (this._vidxOffset || 0);
357
360
  this.__updateElement(el, el.__virtualIndex);
361
+ updatedElements.push(el);
358
362
  } else {
359
363
  delete el.__lastUpdatedIndex;
360
364
  }
361
365
  }, itemSet);
366
+
367
+ this.__afterElementsUpdated(updatedElements);
362
368
  }
363
369
 
364
370
  /** @private */
@@ -557,6 +563,29 @@ export class IronListAdapter {
557
563
  );
558
564
  }
559
565
 
566
+ /**
567
+ * Increases the pool size.
568
+ * @override
569
+ */
570
+ _increasePoolIfNeeded(count) {
571
+ if (this._physicalCount > 2 && count) {
572
+ // The iron-list logic has already created some physical items and
573
+ // has decided to create more. Since each item creation round is
574
+ // expensive, let's try to create the remaining items in one go.
575
+
576
+ // Calculate the total item count that would be needed to fill the viewport
577
+ // plus the buffer assuming rest of the items to be of the average size
578
+ // of the items already created.
579
+ const totalItemCount = Math.ceil(this._optPhysicalSize / this._physicalAverage);
580
+ const missingItemCount = totalItemCount - this._physicalCount;
581
+ // Create the remaining items in one go. Use a maximum of 100 items
582
+ // as a safety measure.
583
+ super._increasePoolIfNeeded(Math.max(count, Math.min(100, missingItemCount)));
584
+ } else {
585
+ super._increasePoolIfNeeded(count);
586
+ }
587
+ }
588
+
560
589
  /**
561
590
  * @returns {Number|undefined} - The browser's default font-size in pixels
562
591
  * @private
@@ -1,10 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2022 - 2023 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?: 'alert' | 'assertive' | 'polite'; timeout?: number }): void;
@@ -1,47 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright (c) 2022 - 2023 Vaadin Ltd.
4
- * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
- */
6
- import { animationFrame } from './async.js';
7
- import { Debouncer } from './debounce.js';
8
-
9
- const region = document.createElement('div');
10
-
11
- region.style.position = 'fixed';
12
- region.style.clip = 'rect(0px, 0px, 0px, 0px)';
13
- region.setAttribute('aria-live', 'polite');
14
-
15
- document.body.appendChild(region);
16
-
17
- let alertDebouncer;
18
- /**
19
- * Cause a text string to be announced by screen readers.
20
- *
21
- * @param {string} text The text that should be announced by the screen reader.
22
- * @param {{mode?: string, timeout?: number}} options Additional options.
23
- */
24
- export function announce(text, options = {}) {
25
- const mode = options.mode || 'polite';
26
- const timeout = options.timeout === undefined ? 150 : options.timeout;
27
-
28
- if (mode === 'alert') {
29
- region.removeAttribute('aria-live');
30
- region.removeAttribute('role');
31
- alertDebouncer = Debouncer.debounce(alertDebouncer, animationFrame, () => {
32
- region.setAttribute('role', 'alert');
33
- });
34
- } else {
35
- if (alertDebouncer) {
36
- alertDebouncer.cancel();
37
- }
38
- region.removeAttribute('role');
39
- region.setAttribute('aria-live', mode);
40
- }
41
-
42
- region.textContent = '';
43
-
44
- setTimeout(() => {
45
- region.textContent = text;
46
- }, timeout);
47
- }
@@ -1,41 +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
- import type { DisabledMixinClass } from './disabled-mixin.js';
8
- import type { KeyboardMixinClass } from './keyboard-mixin.js';
9
-
10
- /**
11
- * A mixin to toggle the `active` attribute.
12
- *
13
- * The attribute is set whenever the user activates the element by a pointer
14
- * or presses an activation key on the element from the keyboard.
15
- *
16
- * The attribute is removed as soon as the element is deactivated
17
- * by the pointer or by releasing the activation key.
18
- */
19
- export declare function ActiveMixin<T extends Constructor<HTMLElement>>(
20
- base: T,
21
- ): Constructor<ActiveMixinClass> & Constructor<DisabledMixinClass> & Constructor<KeyboardMixinClass> & T;
22
-
23
- export declare class ActiveMixinClass {
24
- /**
25
- * An array of activation keys.
26
- *
27
- * See possible values here:
28
- * https://developer.mozilla.org/ru/docs/Web/API/KeyboardEvent/key/Key_Values
29
- */
30
- protected readonly _activeKeys: string[];
31
-
32
- /**
33
- * Override to define if the component needs to be activated.
34
- */
35
- protected _shouldSetFocus(event: KeyboardEvent | MouseEvent): boolean;
36
-
37
- /**
38
- * Toggles the `active` attribute on the element.
39
- */
40
- protected _setActive(active: boolean): void;
41
- }
@@ -1,106 +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 { DisabledMixin } from './disabled-mixin.js';
7
- import { addListener } from './gestures.js';
8
- import { KeyboardMixin } from './keyboard-mixin.js';
9
-
10
- /**
11
- * A mixin to toggle the `active` attribute.
12
- *
13
- * The attribute is set whenever the user activates the element by a pointer
14
- * or presses an activation key on the element from the keyboard.
15
- *
16
- * The attribute is removed as soon as the element is deactivated
17
- * by the pointer or by releasing the activation key.
18
- *
19
- * @polymerMixin
20
- */
21
- export const ActiveMixin = (superclass) =>
22
- class ActiveMixinClass extends DisabledMixin(KeyboardMixin(superclass)) {
23
- /**
24
- * An array of activation keys.
25
- *
26
- * See possible values here:
27
- * https://developer.mozilla.org/ru/docs/Web/API/KeyboardEvent/key/Key_Values
28
- *
29
- * @protected
30
- * @return {!Array<!string>}
31
- */
32
- get _activeKeys() {
33
- return [' '];
34
- }
35
-
36
- /** @protected */
37
- ready() {
38
- super.ready();
39
-
40
- addListener(this, 'down', (event) => {
41
- if (this._shouldSetActive(event)) {
42
- this._setActive(true);
43
- }
44
- });
45
-
46
- addListener(this, 'up', () => {
47
- this._setActive(false);
48
- });
49
- }
50
-
51
- /** @protected */
52
- disconnectedCallback() {
53
- super.disconnectedCallback();
54
-
55
- // When the element is disconnecting from the DOM at the moment being active,
56
- // the `active` attribute needs to be manually removed from the element.
57
- // Otherwise, it will preserve on the element until the element is activated once again.
58
- // The case reproduces for `<vaadin-date-picker>` when closing on `Cancel` or `Today` click.
59
- this._setActive(false);
60
- }
61
-
62
- /**
63
- * @param {KeyboardEvent | MouseEvent} _event
64
- * @protected
65
- */
66
- _shouldSetActive(_event) {
67
- return !this.disabled;
68
- }
69
-
70
- /**
71
- * Sets the `active` attribute on the element if an activation key is pressed.
72
- *
73
- * @param {KeyboardEvent} event
74
- * @protected
75
- * @override
76
- */
77
- _onKeyDown(event) {
78
- super._onKeyDown(event);
79
-
80
- if (this._shouldSetActive(event) && this._activeKeys.includes(event.key)) {
81
- this._setActive(true);
82
-
83
- // Element can become hidden before the `keyup` event, e.g. on button click.
84
- // Use document listener to ensure `active` attribute is removed correctly.
85
- document.addEventListener(
86
- 'keyup',
87
- (e) => {
88
- if (this._activeKeys.includes(e.key)) {
89
- this._setActive(false);
90
- }
91
- },
92
- { once: true },
93
- );
94
- }
95
- }
96
-
97
- /**
98
- * Toggles the `active` attribute on the element.
99
- *
100
- * @param {boolean} active
101
- * @protected
102
- */
103
- _setActive(active) {
104
- this.toggleAttribute('active', active);
105
- }
106
- };
@@ -1,48 +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
- import type { DisabledMixinClass } from './disabled-mixin.js';
8
- import type { FocusMixinClass } from './focus-mixin.js';
9
- import type { TabindexMixinClass } from './tabindex-mixin.js';
10
-
11
- /**
12
- * A mixin to forward focus to an element in the light DOM.
13
- */
14
- export declare function DelegateFocusMixin<T extends Constructor<HTMLElement>>(
15
- base: T,
16
- ): Constructor<DelegateFocusMixinClass> &
17
- Constructor<DisabledMixinClass> &
18
- Constructor<FocusMixinClass> &
19
- Constructor<TabindexMixinClass> &
20
- T;
21
-
22
- export declare class DelegateFocusMixinClass {
23
- /**
24
- * Specify that this control should have input focus when the page loads.
25
- */
26
- autofocus: boolean;
27
-
28
- /**
29
- * A reference to the focusable element controlled by the mixin.
30
- * It can be an input, textarea, button or any element with tabindex > -1.
31
- *
32
- * Any component implementing this mixin is expected to provide it
33
- * by using `this._setFocusElement(input)` Polymer API.
34
- */
35
- readonly focusElement: HTMLElement | null | undefined;
36
-
37
- protected _addFocusListeners(element: HTMLElement): void;
38
-
39
- protected _removeFocusListeners(element: HTMLElement): void;
40
-
41
- protected _focusElementChanged(element: HTMLElement, oldElement: HTMLElement): void;
42
-
43
- protected _onBlur(event: FocusEvent): void;
44
-
45
- protected _onFocus(event: FocusEvent): void;
46
-
47
- protected _setFocusElement(element: HTMLElement): void;
48
- }