@vaadin/component-base 24.4.0-alpha12 → 24.4.0-alpha14
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 +2 -2
- package/src/define.js +1 -1
- package/src/url-utils.d.ts +7 -3
- package/src/url-utils.js +30 -8
- package/src/virtualizer-iron-list-adapter.js +79 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/component-base",
|
|
3
|
-
"version": "24.4.0-
|
|
3
|
+
"version": "24.4.0-alpha14",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"@vaadin/testing-helpers": "^0.6.0",
|
|
43
43
|
"sinon": "^13.0.2"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "303c07338b748bc6036a92a92cf1733c3bc351eb"
|
|
46
46
|
}
|
package/src/define.js
CHANGED
package/src/url-utils.d.ts
CHANGED
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Checks if two paths match based on their origin, pathname, and query parameters.
|
|
9
|
+
*
|
|
10
|
+
* The function matches an actual URL against an expected URL to see if they share
|
|
11
|
+
* the same base origin (like https://example.com), the same path (like /path/to/page),
|
|
12
|
+
* and if the actual URL contains at least all the query parameters with the same values
|
|
13
|
+
* from the expected URL.
|
|
10
14
|
*/
|
|
11
|
-
export declare function matchPaths(
|
|
15
|
+
export declare function matchPaths(actual: string, expected: string): boolean;
|
package/src/url-utils.js
CHANGED
|
@@ -5,15 +5,37 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* with the same
|
|
8
|
+
* Checks if one set of URL parameters contains all the parameters
|
|
9
|
+
* with the same values from another set.
|
|
10
10
|
*
|
|
11
|
-
* @param {
|
|
12
|
-
* @param {
|
|
11
|
+
* @param {URLSearchParams} actual
|
|
12
|
+
* @param {URLSearchParams} expected
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
function containsQueryParams(actual, expected) {
|
|
15
|
+
return [...expected.entries()].every(([key, value]) => {
|
|
16
|
+
return actual.getAll(key).includes(value);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Checks if two paths match based on their origin, pathname, and query parameters.
|
|
22
|
+
*
|
|
23
|
+
* The function matches an actual URL against an expected URL to see if they share
|
|
24
|
+
* the same base origin (like https://example.com), the same path (like /path/to/page),
|
|
25
|
+
* and if the actual URL contains at least all the query parameters with the same values
|
|
26
|
+
* from the expected URL.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} actual The actual URL to match.
|
|
29
|
+
* @param {string} expected The expected URL to match.
|
|
30
|
+
*/
|
|
31
|
+
export function matchPaths(actual, expected) {
|
|
15
32
|
const base = document.baseURI;
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
33
|
+
const actualUrl = new URL(actual, base);
|
|
34
|
+
const expectedUrl = new URL(expected, base);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
actualUrl.origin === expectedUrl.origin &&
|
|
38
|
+
actualUrl.pathname === expectedUrl.pathname &&
|
|
39
|
+
containsQueryParams(actualUrl.searchParams, expectedUrl.searchParams)
|
|
40
|
+
);
|
|
19
41
|
}
|
|
@@ -55,6 +55,13 @@ export class IronListAdapter {
|
|
|
55
55
|
this._scrollLineHeight = this._getScrollLineHeight();
|
|
56
56
|
this.scrollTarget.addEventListener('wheel', (e) => this.__onWheel(e));
|
|
57
57
|
|
|
58
|
+
this.scrollTarget.addEventListener('virtualizer-element-focused', (e) => this.__onElementFocused(e));
|
|
59
|
+
this.elementsContainer.addEventListener('focusin', (e) => {
|
|
60
|
+
this.scrollTarget.dispatchEvent(
|
|
61
|
+
new CustomEvent('virtualizer-element-focused', { detail: { element: this.__getFocusedElement() } }),
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
58
65
|
if (this.reorderElements) {
|
|
59
66
|
// Reordering the physical elements cancels the user's grab of the scroll bar handle on Safari.
|
|
60
67
|
// Need to defer reordering until the user lets go of the scroll bar handle.
|
|
@@ -227,6 +234,7 @@ export class IronListAdapter {
|
|
|
227
234
|
// Clean up temporary placeholder sizing
|
|
228
235
|
if (el.__virtualizerPlaceholder) {
|
|
229
236
|
el.style.paddingTop = '';
|
|
237
|
+
el.style.opacity = '';
|
|
230
238
|
el.__virtualizerPlaceholder = false;
|
|
231
239
|
}
|
|
232
240
|
|
|
@@ -251,6 +259,7 @@ export class IronListAdapter {
|
|
|
251
259
|
// Assign a temporary placeholder sizing to elements that would otherwise end up having
|
|
252
260
|
// no height.
|
|
253
261
|
el.style.paddingTop = `${this.__placeholderHeight}px`;
|
|
262
|
+
el.style.opacity = '0';
|
|
254
263
|
el.__virtualizerPlaceholder = true;
|
|
255
264
|
|
|
256
265
|
// Manually schedule the resize handler to make sure the placeholder padding is
|
|
@@ -425,6 +434,75 @@ export class IronListAdapter {
|
|
|
425
434
|
/** @private */
|
|
426
435
|
toggleScrollListener() {}
|
|
427
436
|
|
|
437
|
+
/** @private */
|
|
438
|
+
__getFocusedElement(visibleElements = this.__getVisibleElements()) {
|
|
439
|
+
return visibleElements.find(
|
|
440
|
+
(element) =>
|
|
441
|
+
element.contains(this.elementsContainer.getRootNode().activeElement) ||
|
|
442
|
+
element.contains(this.scrollTarget.getRootNode().activeElement),
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** @private */
|
|
447
|
+
__nextFocusableSiblingMissing(focusedElement, visibleElements) {
|
|
448
|
+
return (
|
|
449
|
+
// Check if focused element is the last visible DOM element
|
|
450
|
+
visibleElements.indexOf(focusedElement) === visibleElements.length - 1 &&
|
|
451
|
+
// ...while there are more items available
|
|
452
|
+
this.size > focusedElement.__virtualIndex + 1
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** @private */
|
|
457
|
+
__previousFocusableSiblingMissing(focusedElement, visibleElements) {
|
|
458
|
+
return (
|
|
459
|
+
// Check if focused element is the first visible DOM element
|
|
460
|
+
visibleElements.indexOf(focusedElement) === 0 &&
|
|
461
|
+
// ...while there are preceding items available
|
|
462
|
+
focusedElement.__virtualIndex > 0
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** @private */
|
|
467
|
+
__onElementFocused(e) {
|
|
468
|
+
if (!this.reorderElements) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const focusedElement = e.detail.element;
|
|
473
|
+
if (!focusedElement) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// User has tabbed to or within a virtualizer element.
|
|
478
|
+
// Check if a next or previous focusable sibling is missing while it should be there (so the user can continue tabbing).
|
|
479
|
+
// The focusable sibling might be missing due to the elements not yet being in the correct DOM order.
|
|
480
|
+
// First try flushing (which also flushes any active __scrollReorderDebouncer).
|
|
481
|
+
const visibleElements = this.__getVisibleElements();
|
|
482
|
+
if (
|
|
483
|
+
this.__previousFocusableSiblingMissing(focusedElement, visibleElements) ||
|
|
484
|
+
this.__nextFocusableSiblingMissing(focusedElement, visibleElements)
|
|
485
|
+
) {
|
|
486
|
+
this.flush();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// If the focusable sibling is still missing (because the focused element is at the edge of the viewport and
|
|
490
|
+
// the virtual scrolling logic hasn't had the need to recycle elements), scroll the virtualizer just enough to
|
|
491
|
+
// have the focusable sibling inside the visible viewport to force the virtualizer to recycle.
|
|
492
|
+
const reorderedVisibleElements = this.__getVisibleElements();
|
|
493
|
+
if (this.__nextFocusableSiblingMissing(focusedElement, reorderedVisibleElements)) {
|
|
494
|
+
this._scrollTop +=
|
|
495
|
+
Math.ceil(focusedElement.getBoundingClientRect().bottom) -
|
|
496
|
+
Math.floor(this.scrollTarget.getBoundingClientRect().bottom - 1);
|
|
497
|
+
this.flush();
|
|
498
|
+
} else if (this.__previousFocusableSiblingMissing(focusedElement, reorderedVisibleElements)) {
|
|
499
|
+
this._scrollTop -=
|
|
500
|
+
Math.ceil(this.scrollTarget.getBoundingClientRect().top + 1) -
|
|
501
|
+
Math.floor(focusedElement.getBoundingClientRect().top);
|
|
502
|
+
this.flush();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
428
506
|
_scrollHandler() {
|
|
429
507
|
// The scroll target is hidden.
|
|
430
508
|
if (this.scrollTarget.offsetHeight === 0) {
|
|
@@ -662,13 +740,7 @@ export class IronListAdapter {
|
|
|
662
740
|
|
|
663
741
|
// Which row to use as a target?
|
|
664
742
|
const visibleElements = this.__getVisibleElements();
|
|
665
|
-
|
|
666
|
-
const elementWithFocus = visibleElements.find(
|
|
667
|
-
(element) =>
|
|
668
|
-
element.contains(this.elementsContainer.getRootNode().activeElement) ||
|
|
669
|
-
element.contains(this.scrollTarget.getRootNode().activeElement),
|
|
670
|
-
);
|
|
671
|
-
const targetElement = elementWithFocus || visibleElements[0];
|
|
743
|
+
const targetElement = this.__getFocusedElement(visibleElements) || visibleElements[0];
|
|
672
744
|
if (!targetElement) {
|
|
673
745
|
// All elements are hidden, don't reorder
|
|
674
746
|
return;
|