@vaadin/component-base 25.3.0-alpha9 → 25.3.0-beta1

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.
@@ -50,7 +50,8 @@
50
50
  {
51
51
  "name": "superClass"
52
52
  }
53
- ]
53
+ ],
54
+ "deprecated": "This mixin is deprecated and will be removed in Vaadin 26,\nafter which components will no longer set the `dir` attribute on themselves.\nUse the `:dir(rtl)` CSS selector to style components in right-to-left mode,\nand `element.matches(':dir(rtl)')` to detect it in JavaScript."
54
55
  }
55
56
  ],
56
57
  "exports": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/component-base",
3
- "version": "25.3.0-alpha9",
3
+ "version": "25.3.0-beta1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -38,11 +38,12 @@
38
38
  "lit": "^3.0.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@vaadin/chai-plugins": "25.3.0-alpha9",
42
- "@vaadin/test-runner-commands": "25.3.0-alpha9",
41
+ "@polymer/polymer": "^3.0.0",
42
+ "@vaadin/chai-plugins": "25.3.0-beta1",
43
+ "@vaadin/test-runner-commands": "25.3.0-beta1",
43
44
  "@vaadin/testing-helpers": "^2.0.0",
44
45
  "sinon": "^22.0.0"
45
46
  },
46
47
  "customElements": "custom-elements.json",
47
- "gitHead": "cb915ebde095ec5b94a87af93dd4530f51984c52"
48
+ "gitHead": "295432e44a6967e1aff36462b2e3e1d0e64eb8cc"
48
49
  }
package/src/define.js CHANGED
@@ -13,7 +13,7 @@ function dashToCamelCase(dash) {
13
13
 
14
14
  const experimentalMap = {};
15
15
 
16
- export function defineCustomElement(CustomElement, version = '25.3.0-alpha9') {
16
+ export function defineCustomElement(CustomElement, version = '25.3.0-beta1') {
17
17
  Object.defineProperty(CustomElement, 'version', {
18
18
  get() {
19
19
  return version;
@@ -7,9 +7,23 @@ import type { Constructor } from '@open-wc/dedupe-mixin';
7
7
 
8
8
  /**
9
9
  * A mixin to handle `dir` attribute based on the one set on the `<html>` element.
10
+ *
11
+ * @deprecated This mixin is deprecated and will be removed in Vaadin 26,
12
+ * after which components will no longer set the `dir` attribute on themselves.
13
+ * Use the `:dir(rtl)` CSS selector to style components in right-to-left mode,
14
+ * and `element.matches(':dir(rtl)')` to detect it in JavaScript.
10
15
  */
11
16
  export declare function DirMixin<T extends Constructor<HTMLElement>>(base: T): Constructor<DirMixinClass> & T;
12
17
 
18
+ /**
19
+ * @deprecated This mixin is deprecated and will be removed in Vaadin 26,
20
+ * after which components will no longer set the `dir` attribute on themselves.
21
+ * Use the `:dir(rtl)` CSS selector to style components in right-to-left mode,
22
+ * and `element.matches(':dir(rtl)')` to detect it in JavaScript.
23
+ */
13
24
  export declare class DirMixinClass {
25
+ /**
26
+ * @deprecated Use `this.matches(':dir(rtl)')` instead.
27
+ */
14
28
  protected readonly __isRTL: boolean;
15
29
  }
package/src/dir-mixin.js CHANGED
@@ -33,6 +33,11 @@ directionObserver.observe(document.documentElement, { attributes: true, attribut
33
33
 
34
34
  /**
35
35
  * A mixin to handle `dir` attribute based on the one set on the `<html>` element.
36
+ *
37
+ * @deprecated This mixin is deprecated and will be removed in Vaadin 26,
38
+ * after which components will no longer set the `dir` attribute on themselves.
39
+ * Use the `:dir(rtl)` CSS selector to style components in right-to-left mode,
40
+ * and `element.matches(':dir(rtl)')` to detect it in JavaScript.
36
41
  */
37
42
  export const DirMixin = (superClass) =>
38
43
  class VaadinDirMixin extends superClass {
@@ -60,6 +65,7 @@ export const DirMixin = (superClass) =>
60
65
  /**
61
66
  * @return {boolean}
62
67
  * @protected
68
+ * @deprecated Use `this.matches(':dir(rtl)')` instead.
63
69
  */
64
70
  get __isRTL() {
65
71
  return this.getAttribute('dir') === 'rtl';
@@ -106,6 +112,10 @@ export const DirMixin = (superClass) =>
106
112
  this.__unsubscribe();
107
113
  }
108
114
 
115
+ // The two overrides below are only invoked by Polymer's property reflection.
116
+ // Vaadin components no longer extend `PolymerElement`, but some add-ons still
117
+ // apply this mixin to `PolymerElement`, so the overrides are kept for them.
118
+
109
119
  /** @protected */
110
120
  _valueToNodeAttribute(node, value, attribute) {
111
121
  // Override default Polymer attribute reflection to match native behavior of HTMLElement.dir property
@@ -62,3 +62,10 @@ export function removeValuesFromAttribute(element: HTMLElement, attr: string, va
62
62
  * Returns true if the given node is an empty text node, false otherwise.
63
63
  */
64
64
  export function isEmptyTextNode(node: Node): boolean;
65
+
66
+ /**
67
+ * Returns true if the given node has content of its own: an element with
68
+ * children, a defined custom element — which may render content in its
69
+ * shadow root — or a node with non-empty text.
70
+ */
71
+ export function hasNodeContent(node: Node | null | undefined): boolean;
package/src/dom-utils.js CHANGED
@@ -168,3 +168,22 @@ export function removeValuesFromAttribute(element, attr, valuesToRemove) {
168
168
  export function isEmptyTextNode(node) {
169
169
  return node.nodeType === Node.TEXT_NODE && node.textContent.trim() === '';
170
170
  }
171
+
172
+ /**
173
+ * Returns true if the given node has content of its own: an element with
174
+ * children, a defined custom element — which may render content in its
175
+ * shadow root — or a node with non-empty text.
176
+ *
177
+ * @param {Node | null | undefined} node
178
+ * @return {boolean}
179
+ */
180
+ export function hasNodeContent(node) {
181
+ if (!node) {
182
+ return false;
183
+ }
184
+
185
+ return Boolean(
186
+ (node.nodeType === Node.ELEMENT_NODE && (customElements.get(node.localName) || node.children.length > 0)) ||
187
+ node.textContent?.trim(),
188
+ );
189
+ }
package/src/gestures.d.ts CHANGED
@@ -43,6 +43,18 @@ export { removeListener };
43
43
  declare function register(recog: GestureRecognizer): void;
44
44
  export { register };
45
45
 
46
+ /**
47
+ * The registered gesture event recognizers by their event type.
48
+ */
49
+ declare const gestures: Record<string, GestureRecognizer | undefined>;
50
+ export { gestures };
51
+
52
+ /**
53
+ * The registered gesture event recognizers.
54
+ */
55
+ declare const recognizers: GestureRecognizer[];
56
+ export { recognizers };
57
+
46
58
  /**
47
59
  * Sets scrolling direction on node.
48
60
  *
package/src/i18n-mixin.js CHANGED
@@ -3,34 +3,7 @@
3
3
  * Copyright (c) 2025 - 2026 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
-
7
- function deepMerge(target, ...sources) {
8
- const isArray = (item) => Array.isArray(item);
9
- const isObject = (item) => item && typeof item === 'object' && !isArray(item);
10
- const merge = (target, source) => {
11
- if (isObject(source) && isObject(target)) {
12
- Object.keys(source).forEach((key) => {
13
- const sourceValue = source[key];
14
- if (isObject(sourceValue)) {
15
- if (!target[key]) {
16
- target[key] = {};
17
- }
18
- merge(target[key], sourceValue);
19
- } else if (isArray(sourceValue)) {
20
- target[key] = [...sourceValue];
21
- } else if (sourceValue !== undefined && sourceValue !== null) {
22
- target[key] = sourceValue;
23
- }
24
- });
25
- }
26
- };
27
-
28
- sources.forEach((source) => {
29
- merge(target, source);
30
- });
31
-
32
- return target;
33
- }
6
+ import { deepMergePartials } from './object-utils.js';
34
7
 
35
8
  /**
36
9
  * A mixin that allows to set partial I18N properties.
@@ -75,7 +48,7 @@ export const I18nMixin = (superClass) =>
75
48
  constructor() {
76
49
  super();
77
50
 
78
- this.i18n = deepMerge({}, this.constructor.defaultI18n);
51
+ this.i18n = deepMergePartials({}, this.constructor.defaultI18n);
79
52
  }
80
53
 
81
54
  /**
@@ -97,6 +70,6 @@ export const I18nMixin = (superClass) =>
97
70
  return;
98
71
  }
99
72
  this.__customI18n = value;
100
- this.__effectiveI18n = deepMerge({}, this.constructor.defaultI18n, this.__customI18n);
73
+ this.__effectiveI18n = deepMergePartials({}, this.constructor.defaultI18n, this.__customI18n);
101
74
  }
102
75
  };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2026 - 2026 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+
7
+ /**
8
+ * Recursively copies own properties of `source` into `target` and returns
9
+ * `target`. Plain objects are merged, other values are assigned as they are.
10
+ * An object is plain when it inherits from `Object.prototype` or from nothing,
11
+ * so values such as a `Date` or a class instance are assigned, not merged.
12
+ *
13
+ * Merges a single source. Use `deepMergePartials()` to merge several objects,
14
+ * or to merge objects that only provide some of the properties.
15
+ *
16
+ * Both arguments are expected to be plain objects. When either of them is not,
17
+ * `target` is returned without changes. A property of the target that is not a
18
+ * plain object is replaced with the merged object, unlike the arguments.
19
+ *
20
+ * Keys that would modify `Object.prototype`, such as `__proto__`, are ignored.
21
+ */
22
+ export function deepMerge<T extends object>(target: T, source: object): T;
23
+
24
+ /**
25
+ * Recursively merges partial objects into `target` in order and returns
26
+ * `target`, so that a later source overrides an earlier one.
27
+ *
28
+ * Values that are `null` or `undefined` are skipped, so a source that only
29
+ * provides some of the properties does not remove the others. For the same
30
+ * reason, a property that the target already has is not replaced with an
31
+ * object when the source has one for the same key. Arrays are copied one level
32
+ * deep, so that the result does not share an array with any of the sources.
33
+ *
34
+ * Sources that are not plain objects are ignored. When `target` is not a plain
35
+ * object, it is returned without changes.
36
+ *
37
+ * Keys that would modify `Object.prototype`, such as `__proto__`, are ignored.
38
+ */
39
+ export function deepMergePartials<T extends object>(target: T, ...sources: object[]): T;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2026 - 2026 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+
7
+ /**
8
+ * Keys that are not copied while merging, as assigning them would modify
9
+ * `Object.prototype` instead of the merge target, and so affect every
10
+ * object in the application.
11
+ */
12
+ const IGNORED_KEYS = ['__proto__', 'constructor', 'prototype'];
13
+
14
+ const isPlainObject = (value) => {
15
+ if (!value || typeof value !== 'object') {
16
+ return false;
17
+ }
18
+ const prototype = Object.getPrototypeOf(value);
19
+ return prototype === Object.prototype || prototype === null;
20
+ };
21
+
22
+ /**
23
+ * Merges `source` into `target`. With `partial`, the source is treated as an
24
+ * object that may provide only some of the properties: nullish values are
25
+ * skipped and arrays are copied instead of shared.
26
+ */
27
+ function merge(target, source, partial) {
28
+ if (!isPlainObject(target) || !isPlainObject(source)) {
29
+ return target;
30
+ }
31
+
32
+ Object.keys(source).forEach((key) => {
33
+ if (IGNORED_KEYS.includes(key)) {
34
+ return;
35
+ }
36
+
37
+ const value = source[key];
38
+
39
+ if (isPlainObject(value)) {
40
+ // Only merge into an own plain object, so that the merge can never
41
+ // continue into an object inherited from the prototype chain.
42
+ if (!Object.hasOwn(target, key) || !isPlainObject(target[key])) {
43
+ // With `partial`, a value that the target already has is kept, so that
44
+ // a source property of an unexpected type does not remove a default.
45
+ if (partial && Object.hasOwn(target, key) && target[key]) {
46
+ return;
47
+ }
48
+
49
+ target[key] = {};
50
+ }
51
+
52
+ merge(target[key], value, partial);
53
+ } else if (partial && Array.isArray(value)) {
54
+ target[key] = [...value];
55
+ } else if (!partial || (value !== undefined && value !== null)) {
56
+ target[key] = value;
57
+ }
58
+ });
59
+
60
+ return target;
61
+ }
62
+
63
+ /**
64
+ * Recursively copies own properties of `source` into `target` and returns
65
+ * `target`. Plain objects are merged, other values are assigned as they are.
66
+ * An object is plain when it inherits from `Object.prototype` or from nothing,
67
+ * so values such as a `Date` or a class instance are assigned, not merged.
68
+ *
69
+ * Merges a single source. Use `deepMergePartials()` to merge several objects,
70
+ * or to merge objects that only provide some of the properties.
71
+ *
72
+ * Both arguments are expected to be plain objects. When either of them is not,
73
+ * `target` is returned without changes. A property of the target that is not a
74
+ * plain object is replaced with the merged object, unlike the arguments.
75
+ *
76
+ * Keys that would modify `Object.prototype`, such as `__proto__`, are ignored.
77
+ *
78
+ * @param {object} target the object to merge into, modified in place
79
+ * @param {object} source the object to copy the properties from
80
+ * @return {object} the `target` object
81
+ */
82
+ export function deepMerge(target, source) {
83
+ return merge(target, source, false);
84
+ }
85
+
86
+ /**
87
+ * Recursively merges partial objects into `target` in order and returns
88
+ * `target`, so that a later source overrides an earlier one.
89
+ *
90
+ * Values that are `null` or `undefined` are skipped, so a source that only
91
+ * provides some of the properties does not remove the others. For the same
92
+ * reason, a property that the target already has is not replaced with an
93
+ * object when the source has one for the same key. Arrays are copied one level
94
+ * deep, so that the result does not share an array with any of the sources.
95
+ *
96
+ * Sources that are not plain objects are ignored. When `target` is not a plain
97
+ * object, it is returned without changes.
98
+ *
99
+ * Keys that would modify `Object.prototype`, such as `__proto__`, are ignored.
100
+ *
101
+ * @param {object} target the object to merge into, modified in place
102
+ * @param {...object} sources the objects to copy the properties from
103
+ * @return {object} the `target` object
104
+ */
105
+ export function deepMergePartials(target, ...sources) {
106
+ sources.forEach((source) => merge(target, source, true));
107
+
108
+ return target;
109
+ }
@@ -3,6 +3,7 @@
3
3
  * Copyright (c) 2022 - 2026 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
+ import { hasNodeContent } from './dom-utils.js';
6
7
  import { SlotController } from './slot-controller.js';
7
8
 
8
9
  /**
@@ -130,24 +131,6 @@ export class SlotChildObserveController extends SlotController {
130
131
  });
131
132
  }
132
133
 
133
- /**
134
- * Returns true if a node is an HTML element with children,
135
- * or is a defined custom element, or has non-empty text.
136
- *
137
- * @param {Node} node
138
- * @return {boolean}
139
- */
140
- #hasContent(node) {
141
- if (!node) {
142
- return false;
143
- }
144
-
145
- return (
146
- (node.nodeType === Node.ELEMENT_NODE && (customElements.get(node.localName) || node.children.length > 0)) ||
147
- (node.textContent && node.textContent.trim() !== '')
148
- );
149
- }
150
-
151
134
  /**
152
135
  * Fire an event to notify the controller host about node changes.
153
136
  *
@@ -157,7 +140,7 @@ export class SlotChildObserveController extends SlotController {
157
140
  _notifyChange(node) {
158
141
  this.dispatchEvent(
159
142
  new CustomEvent('slot-content-changed', {
160
- detail: { hasContent: this.#hasContent(node), node },
143
+ detail: { hasContent: hasNodeContent(node), node },
161
144
  }),
162
145
  );
163
146
  }
@@ -14,12 +14,18 @@
14
14
  * bubbling to it and diffs the **union** of `assignedNodes({ flatten: true })`
15
15
  * every descendant `<slot>`. Cross-slot reassignment of the same node does
16
16
  * not change the union and therefore fires no callback.
17
+ *
18
+ * The initial pass runs in a microtask by default. Use the `syncInitial` option
19
+ * when the callback sets state that affects the layout of the component, so that
20
+ * it has its final size once connected. Otherwise consumers that measure it
21
+ * synchronously, such as auto-width columns in `<vaadin-grid>`, would measure the
22
+ * component before that state is applied.
17
23
  */
18
24
  export class SlotObserver {
19
25
  constructor(
20
26
  target: HTMLSlotElement | DocumentFragment,
21
27
  callback: (info: { addedNodes: Node[]; currentNodes: Node[]; movedNodes: Node[]; removedNodes: Node[] }) => void,
22
- forceInitial?: boolean,
28
+ options?: { forceInitial?: boolean; syncInitial?: boolean },
23
29
  );
24
30
 
25
31
  readonly target: HTMLSlotElement | DocumentFragment;
@@ -14,9 +14,20 @@
14
14
  * bubbling to it and diffs the **union** of `assignedNodes({ flatten: true })`
15
15
  * across every descendant `<slot>`. Cross-slot reassignment of the same node
16
16
  * does not change the union and therefore fires no callback.
17
+ *
18
+ * The initial pass runs in a microtask by default. Use the `syncInitial` option
19
+ * when the callback sets state that affects the layout of the component, so that
20
+ * it has its final size once connected. Otherwise consumers that measure it
21
+ * synchronously, such as auto-width columns in `<vaadin-grid>`, would measure the
22
+ * component before that state is applied.
17
23
  */
18
24
  export class SlotObserver {
19
- constructor(target, callback, forceInitial) {
25
+ /**
26
+ * @param {HTMLSlotElement | DocumentFragment} target
27
+ * @param {Function} callback
28
+ * @param {{ forceInitial?: boolean, syncInitial?: boolean }} options
29
+ */
30
+ constructor(target, callback, options = {}) {
20
31
  /** @type {HTMLSlotElement | DocumentFragment} */
21
32
  this.target = target;
22
33
 
@@ -24,7 +35,7 @@ export class SlotObserver {
24
35
  this.callback = callback;
25
36
 
26
37
  /** @type {boolean} */
27
- this.forceInitial = forceInitial;
38
+ this.forceInitial = options.forceInitial;
28
39
 
29
40
  /** @type {Node[]} */
30
41
  this._storedNodes = [];
@@ -40,7 +51,12 @@ export class SlotObserver {
40
51
  };
41
52
 
42
53
  this.connect();
43
- this._schedule();
54
+
55
+ if (options.syncInitial) {
56
+ this.flush();
57
+ } else {
58
+ this._schedule();
59
+ }
44
60
  }
45
61
 
46
62
  /**
@@ -69,7 +85,11 @@ export class SlotObserver {
69
85
  this._scheduled = true;
70
86
 
71
87
  queueMicrotask(() => {
72
- this.flush();
88
+ // Skip if the nodes have already been processed by an explicit `flush()`
89
+ // in the meantime, to avoid running the diff a second time for nothing.
90
+ if (this._scheduled) {
91
+ this.flush();
92
+ }
73
93
  });
74
94
  }
75
95
  }
@@ -78,12 +78,7 @@ export class IronListAdapter {
78
78
  });
79
79
  attachObserver.observe(this.scrollTarget);
80
80
 
81
- this.scrollTarget.addEventListener('virtualizer-element-focused', (e) => this.__onElementFocused(e));
82
- this.elementsContainer.addEventListener('focusin', () => {
83
- this.scrollTarget.dispatchEvent(
84
- new CustomEvent('virtualizer-element-focused', { detail: { element: this.__getFocusedElement() } }),
85
- );
86
- });
81
+ this.elementsContainer.addEventListener('focusin', () => this.__onElementFocused());
87
82
 
88
83
  if (this.reorderElements) {
89
84
  // Reordering the physical elements cancels the user's grab of the scroll bar handle on Safari.
@@ -161,7 +156,7 @@ export class IronListAdapter {
161
156
  this.__skipNextVirtualIndexAdjust = true;
162
157
  super.scrollToIndex(targetVirtualIndex);
163
158
 
164
- if (this.adjustedFirstVisibleIndex !== index && this._scrollTop < this._maxScrollTop && !this.grid) {
159
+ if (this.adjustedFirstVisibleIndex !== index && this._scrollTop < this._maxScrollTop) {
165
160
  // Workaround an iron-list issue by manually adjusting the scroll position
166
161
  this._scrollTop -= this.__getIndexScrollOffset(index) || 0;
167
162
  }
@@ -189,9 +184,6 @@ export class IronListAdapter {
189
184
  if (this.__scrollReorderDebouncer) {
190
185
  this.__scrollReorderDebouncer.flush();
191
186
  }
192
- if (this.__debouncerWheelAnimationFrame) {
193
- this.__debouncerWheelAnimationFrame.flush();
194
- }
195
187
  }
196
188
 
197
189
  hostConnected() {
@@ -462,16 +454,10 @@ export class IronListAdapter {
462
454
 
463
455
  /** @private */
464
456
  updateViewportBoundaries() {
465
- const styles = window.getComputedStyle(this.scrollTarget);
466
- this._scrollerPaddingTop = this.scrollTarget === this ? 0 : parseInt(styles['padding-top'], 10);
467
- this._isRTL = Boolean(styles.direction === 'rtl');
468
- this._viewportWidth = this.elementsContainer.offsetWidth;
457
+ this._scrollerPaddingTop = parseInt(window.getComputedStyle(this.scrollTarget)['padding-top'], 10);
469
458
  this._viewportHeight = this.scrollTarget.offsetHeight;
470
459
  }
471
460
 
472
- /** @private */
473
- setAttribute() {}
474
-
475
461
  /** @private */
476
462
  _createPool(size) {
477
463
  const physicalItems = this.createElements(size);
@@ -521,20 +507,8 @@ export class IronListAdapter {
521
507
  toggleScrollListener() {}
522
508
 
523
509
  /** @private */
524
- __getFocusedElement(visibleElements = this.__getVisibleElements()) {
525
- // `document.activeElement` retargets to the outermost shadow host when
526
- // focus lives in a nested shadow tree. Descend through nested shadow
527
- // roots' `activeElement`s to reach the real focused node, then walk up
528
- // the flattened tree (via `assignedSlot`/`parentNode`/`host`) until a
529
- // visible row is reached.
530
- let node = document.activeElement;
531
- while (node?.shadowRoot?.activeElement) {
532
- node = node.shadowRoot.activeElement;
533
- }
534
- while (node && !visibleElements.includes(node)) {
535
- node = node.assignedSlot || node.parentNode || node.host;
536
- }
537
- return node;
510
+ __getFocusedElement() {
511
+ return this.__getVisibleElements().find((element) => element.matches(':focus-within'));
538
512
  }
539
513
 
540
514
  /** @private */
@@ -558,12 +532,12 @@ export class IronListAdapter {
558
532
  }
559
533
 
560
534
  /** @private */
561
- __onElementFocused(e) {
535
+ __onElementFocused() {
562
536
  if (!this.reorderElements) {
563
537
  return;
564
538
  }
565
539
 
566
- const focusedElement = e.detail.element;
540
+ const focusedElement = this.__getFocusedElement();
567
541
  if (!focusedElement) {
568
542
  return;
569
543
  }
@@ -805,7 +779,7 @@ export class IronListAdapter {
805
779
 
806
780
  // Which row to use as a target?
807
781
  const visibleElements = this.__getVisibleElements();
808
- const targetElement = this.__getFocusedElement(visibleElements) || visibleElements[0];
782
+ const targetElement = this.__getFocusedElement() || visibleElements[0];
809
783
  if (!targetElement) {
810
784
  // All elements are hidden, don't reorder
811
785
  return;