@vaadin/component-base 25.2.0-alpha1 → 25.2.0-alpha11

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.
@@ -7,9 +7,22 @@
7
7
  "path": "src/delegate-state-mixin.js",
8
8
  "declarations": [
9
9
  {
10
- "kind": "variable",
10
+ "kind": "mixin",
11
+ "description": "",
11
12
  "name": "DelegateStateMixin",
12
- "description": "A mixin to delegate properties and attributes to a target element."
13
+ "members": [],
14
+ "attributes": [],
15
+ "parameters": [
16
+ {
17
+ "name": "superclass"
18
+ }
19
+ ],
20
+ "mixins": [
21
+ {
22
+ "name": "dedupeMixin",
23
+ "package": "@open-wc/dedupe-mixin"
24
+ }
25
+ ]
13
26
  }
14
27
  ],
15
28
  "exports": [
@@ -91,7 +104,7 @@
91
104
  "declarations": [
92
105
  {
93
106
  "kind": "mixin",
94
- "description": "A mixin that allows to set partial I18N properties.",
107
+ "description": "A mixin that allows to set partial I18N properties.\n\nSubclasses provide their default values by overriding the\n`defaultI18n` static getter:\n\n```js\nstatic get defaultI18n() {\n return { foo: 'Foo', bar: 'Bar' };\n}\n```",
95
108
  "name": "I18nMixin",
96
109
  "members": [
97
110
  {
@@ -116,9 +129,6 @@
116
129
  }
117
130
  ],
118
131
  "parameters": [
119
- {
120
- "name": "defaultI18n"
121
- },
122
132
  {
123
133
  "name": "superClass"
124
134
  }
@@ -223,9 +233,21 @@
223
233
  "path": "src/resize-mixin.js",
224
234
  "declarations": [
225
235
  {
226
- "kind": "variable",
236
+ "kind": "mixin",
237
+ "description": "A mixin that uses a ResizeObserver to listen to host size changes.",
227
238
  "name": "ResizeMixin",
228
- "description": "A mixin that uses a ResizeObserver to listen to host size changes."
239
+ "members": [],
240
+ "parameters": [
241
+ {
242
+ "name": "superclass"
243
+ }
244
+ ],
245
+ "mixins": [
246
+ {
247
+ "name": "dedupeMixin",
248
+ "package": "@open-wc/dedupe-mixin"
249
+ }
250
+ ]
229
251
  }
230
252
  ],
231
253
  "exports": [
@@ -244,9 +266,21 @@
244
266
  "path": "src/slot-styles-mixin.js",
245
267
  "declarations": [
246
268
  {
247
- "kind": "variable",
269
+ "kind": "mixin",
270
+ "description": "Mixin to insert styles into the outer scope to handle slotted components.\nThis is useful e.g. to hide native `<input type=\"number\">` controls.",
248
271
  "name": "SlotStylesMixin",
249
- "description": "Mixin to insert styles into the outer scope to handle slotted components.\nThis is useful e.g. to hide native `<input type=\"number\">` controls."
272
+ "members": [],
273
+ "parameters": [
274
+ {
275
+ "name": "superclass"
276
+ }
277
+ ],
278
+ "mixins": [
279
+ {
280
+ "name": "dedupeMixin",
281
+ "package": "@open-wc/dedupe-mixin"
282
+ }
283
+ ]
250
284
  }
251
285
  ],
252
286
  "exports": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/component-base",
3
- "version": "25.2.0-alpha1",
3
+ "version": "25.2.0-alpha11",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -38,11 +38,11 @@
38
38
  "lit": "^3.0.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@vaadin/chai-plugins": "25.2.0-alpha1",
42
- "@vaadin/test-runner-commands": "25.2.0-alpha1",
41
+ "@vaadin/chai-plugins": "25.2.0-alpha11",
42
+ "@vaadin/test-runner-commands": "25.2.0-alpha11",
43
43
  "@vaadin/testing-helpers": "^2.0.0",
44
44
  "sinon": "^21.0.2"
45
45
  },
46
46
  "customElements": "custom-elements.json",
47
- "gitHead": "866f813f89655a351cbd25328eba1fcb317e267d"
47
+ "gitHead": "fdc37e932709f95491a027aeb2090911cb7528c6"
48
48
  }
@@ -3,7 +3,6 @@
3
3
  * Copyright (c) 2021 - 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 type { ReactiveController } from 'lit';
7
6
  import type { Cache } from './cache.js';
8
7
  import type { getFlatIndexByPath, getFlatIndexContext, getItemContext } from './helpers.js';
9
8
 
@@ -23,10 +22,7 @@ export type DataProvider<TItem, TDataProviderParams extends Record<string, unkno
23
22
  /**
24
23
  * A controller that stores and manages items loaded with a data provider.
25
24
  */
26
- export class DataProviderController<
27
- TItem,
28
- TDataProviderParams extends Record<string, unknown>,
29
- > implements ReactiveController {
25
+ export class DataProviderController<TItem, TDataProviderParams extends Record<string, unknown>> extends EventTarget {
30
26
  /**
31
27
  * The controller host element.
32
28
  */
@@ -94,10 +90,6 @@ export class DataProviderController<
94
90
  */
95
91
  get flatSize(): number;
96
92
 
97
- hostConnected(): void;
98
-
99
- hostDisconnected(): void;
100
-
101
93
  /**
102
94
  * Whether the root cache or any of its decendant caches have pending requests.
103
95
  */
@@ -105,7 +105,7 @@ export function getFlatIndexByPath(cache, [levelIndex, ...subIndexes], flatIndex
105
105
 
106
106
  const flatIndexOnLevel = cache.getFlatIndex(levelIndex);
107
107
  const subCache = cache.getSubCache(levelIndex);
108
- if (subCache && subCache.flatSize > 0 && subIndexes.length) {
108
+ if (subCache?.flatSize > 0 && subIndexes.length) {
109
109
  return getFlatIndexByPath(subCache, subIndexes, flatIndex + flatIndexOnLevel + 1);
110
110
  }
111
111
  return flatIndex + flatIndexOnLevel;
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.2.0-alpha1') {
16
+ export function defineCustomElement(CustomElement, version = '25.2.0-alpha11') {
17
17
  Object.defineProperty(CustomElement, 'version', {
18
18
  get() {
19
19
  return version;
@@ -7,119 +7,118 @@ import { dedupeMixin } from '@open-wc/dedupe-mixin';
7
7
 
8
8
  /**
9
9
  * A mixin to delegate properties and attributes to a target element.
10
- *
11
- * @polymerMixin
12
10
  */
13
- export const DelegateStateMixin = dedupeMixin(
14
- (superclass) =>
15
- class DelegateStateMixinClass extends superclass {
16
- static get properties() {
17
- return {
18
- /**
19
- * A target element to which attributes and properties are delegated.
20
- * @protected
21
- */
22
- stateTarget: {
23
- type: Object,
24
- observer: '_stateTargetChanged',
25
- },
26
- };
11
+ const DelegateStateMixinImplementation = (superclass) => {
12
+ return class DelegateStateMixinClass extends superclass {
13
+ static get properties() {
14
+ return {
15
+ /**
16
+ * A target element to which attributes and properties are delegated.
17
+ * @protected
18
+ */
19
+ stateTarget: {
20
+ type: Object,
21
+ observer: '_stateTargetChanged',
22
+ },
23
+ };
24
+ }
25
+
26
+ /**
27
+ * An array of the host attributes to delegate to the target element.
28
+ */
29
+ static get delegateAttrs() {
30
+ return [];
31
+ }
32
+
33
+ /**
34
+ * An array of the host properties to delegate to the target element.
35
+ */
36
+ static get delegateProps() {
37
+ return [];
38
+ }
39
+
40
+ /** @protected */
41
+ ready() {
42
+ super.ready();
43
+
44
+ this._createDelegateAttrsObserver();
45
+ this._createDelegatePropsObserver();
46
+ }
47
+
48
+ /** @protected */
49
+ _stateTargetChanged(target) {
50
+ if (target) {
51
+ this._ensureAttrsDelegated();
52
+ this._ensurePropsDelegated();
27
53
  }
28
-
29
- /**
30
- * An array of the host attributes to delegate to the target element.
31
- */
32
- static get delegateAttrs() {
33
- return [];
54
+ }
55
+
56
+ /** @protected */
57
+ _createDelegateAttrsObserver() {
58
+ this._createMethodObserver(`_delegateAttrsChanged(${this.constructor.delegateAttrs.join(', ')})`);
59
+ }
60
+
61
+ /** @protected */
62
+ _createDelegatePropsObserver() {
63
+ this._createMethodObserver(`_delegatePropsChanged(${this.constructor.delegateProps.join(', ')})`);
64
+ }
65
+
66
+ /** @protected */
67
+ _ensureAttrsDelegated() {
68
+ this.constructor.delegateAttrs.forEach((name) => {
69
+ this._delegateAttribute(name, this[name]);
70
+ });
71
+ }
72
+
73
+ /** @protected */
74
+ _ensurePropsDelegated() {
75
+ this.constructor.delegateProps.forEach((name) => {
76
+ this._delegateProperty(name, this[name]);
77
+ });
78
+ }
79
+
80
+ /** @protected */
81
+ _delegateAttrsChanged(...values) {
82
+ this.constructor.delegateAttrs.forEach((name, index) => {
83
+ this._delegateAttribute(name, values[index]);
84
+ });
85
+ }
86
+
87
+ /** @protected */
88
+ _delegatePropsChanged(...values) {
89
+ this.constructor.delegateProps.forEach((name, index) => {
90
+ this._delegateProperty(name, values[index]);
91
+ });
92
+ }
93
+
94
+ /** @protected */
95
+ _delegateAttribute(name, value) {
96
+ if (!this.stateTarget) {
97
+ return;
34
98
  }
35
99
 
36
- /**
37
- * An array of the host properties to delegate to the target element.
38
- */
39
- static get delegateProps() {
40
- return [];
100
+ if (name === 'invalid') {
101
+ this._delegateAttribute('aria-invalid', value ? 'true' : false);
41
102
  }
42
103
 
43
- /** @protected */
44
- ready() {
45
- super.ready();
46
-
47
- this._createDelegateAttrsObserver();
48
- this._createDelegatePropsObserver();
104
+ if (typeof value === 'boolean') {
105
+ this.stateTarget.toggleAttribute(name, value);
106
+ } else if (value) {
107
+ this.stateTarget.setAttribute(name, value);
108
+ } else {
109
+ this.stateTarget.removeAttribute(name);
49
110
  }
111
+ }
50
112
 
51
- /** @protected */
52
- _stateTargetChanged(target) {
53
- if (target) {
54
- this._ensureAttrsDelegated();
55
- this._ensurePropsDelegated();
56
- }
113
+ /** @protected */
114
+ _delegateProperty(name, value) {
115
+ if (!this.stateTarget) {
116
+ return;
57
117
  }
58
118
 
59
- /** @protected */
60
- _createDelegateAttrsObserver() {
61
- this._createMethodObserver(`_delegateAttrsChanged(${this.constructor.delegateAttrs.join(', ')})`);
62
- }
63
-
64
- /** @protected */
65
- _createDelegatePropsObserver() {
66
- this._createMethodObserver(`_delegatePropsChanged(${this.constructor.delegateProps.join(', ')})`);
67
- }
68
-
69
- /** @protected */
70
- _ensureAttrsDelegated() {
71
- this.constructor.delegateAttrs.forEach((name) => {
72
- this._delegateAttribute(name, this[name]);
73
- });
74
- }
75
-
76
- /** @protected */
77
- _ensurePropsDelegated() {
78
- this.constructor.delegateProps.forEach((name) => {
79
- this._delegateProperty(name, this[name]);
80
- });
81
- }
119
+ this.stateTarget[name] = value;
120
+ }
121
+ };
122
+ };
82
123
 
83
- /** @protected */
84
- _delegateAttrsChanged(...values) {
85
- this.constructor.delegateAttrs.forEach((name, index) => {
86
- this._delegateAttribute(name, values[index]);
87
- });
88
- }
89
-
90
- /** @protected */
91
- _delegatePropsChanged(...values) {
92
- this.constructor.delegateProps.forEach((name, index) => {
93
- this._delegateProperty(name, values[index]);
94
- });
95
- }
96
-
97
- /** @protected */
98
- _delegateAttribute(name, value) {
99
- if (!this.stateTarget) {
100
- return;
101
- }
102
-
103
- if (name === 'invalid') {
104
- this._delegateAttribute('aria-invalid', value ? 'true' : false);
105
- }
106
-
107
- if (typeof value === 'boolean') {
108
- this.stateTarget.toggleAttribute(name, value);
109
- } else if (value) {
110
- this.stateTarget.setAttribute(name, value);
111
- } else {
112
- this.stateTarget.removeAttribute(name);
113
- }
114
- }
115
-
116
- /** @protected */
117
- _delegateProperty(name, value) {
118
- if (!this.stateTarget) {
119
- return;
120
- }
121
-
122
- this.stateTarget[name] = value;
123
- }
124
- },
125
- );
124
+ export const DelegateStateMixin = dedupeMixin(DelegateStateMixinImplementation);
package/src/dir-mixin.js CHANGED
@@ -33,8 +33,6 @@ 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
- * @polymerMixin
38
36
  */
39
37
  export const DirMixin = (superClass) =>
40
38
  class VaadinDirMixin extends superClass {
package/src/dom-utils.js CHANGED
@@ -74,7 +74,7 @@ export function getClosestElement(selector, node) {
74
74
  return null;
75
75
  }
76
76
 
77
- return node.closest(selector) || getClosestElement(selector, node.getRootNode().host);
77
+ return node.closest?.(selector) || getClosestElement(selector, node.getRootNode().host);
78
78
  }
79
79
 
80
80
  /**
@@ -31,10 +31,6 @@ let statsJob;
31
31
 
32
32
  const registered = new Set();
33
33
 
34
- /**
35
- * @polymerMixin
36
- * @mixes DirMixin
37
- */
38
34
  export const ElementMixin = (superClass) =>
39
35
  class VaadinElementMixin extends DirMixin(superClass) {
40
36
  /** @protected */
package/src/gestures.js CHANGED
@@ -121,7 +121,7 @@ function hasLeftMouseButton(ev) {
121
121
  // instead we use ev.buttons (bitmask of buttons) or fall back to ev.which (deprecated, 0 for no buttons, 1 for left button)
122
122
  if (type === 'mousemove') {
123
123
  // Allow undefined for testing events
124
- let buttons = ev.buttons === undefined ? 1 : ev.buttons;
124
+ let buttons = ev.buttons ?? 1;
125
125
  if (ev instanceof window.MouseEvent && !MOUSE_HAS_BUTTONS) {
126
126
  buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0;
127
127
  }
@@ -129,7 +129,7 @@ function hasLeftMouseButton(ev) {
129
129
  return Boolean(buttons & 1);
130
130
  }
131
131
  // Allow undefined for testing events
132
- const button = ev.button === undefined ? 0 : ev.button;
132
+ const button = ev.button ?? 0;
133
133
  // Ev.button is 0 in mousedown/mouseup/click for left button activation
134
134
  return button === 0;
135
135
  }
@@ -232,7 +232,7 @@ export function deepTargetFind(x, y) {
232
232
  // This code path is only taken when native ShadowDOM is used
233
233
  // if there is a shadowroot, it may have a node at x/y
234
234
  // if there is not a shadowroot, exit the loop
235
- while (next && next.shadowRoot && !window.ShadyDOM) {
235
+ while (next?.shadowRoot && !window.ShadyDOM) {
236
236
  // If there is a node at x/y in the shadowroot, look deeper
237
237
  const oldNext = next;
238
238
  next = next.shadowRoot.elementFromPoint(x, y);
@@ -448,7 +448,7 @@ function _remove(node, evType, handler) {
448
448
  for (let i = 0, dep, gd; i < deps.length; i++) {
449
449
  dep = deps[i];
450
450
  gd = gobj[dep];
451
- if (gd && gd[name]) {
451
+ if (gd?.[name]) {
452
452
  gd[name] = (gd[name] || 1) - 1;
453
453
  gd._count = (gd._count || 1) - 1;
454
454
  if (gd._count === 0) {
@@ -531,7 +531,7 @@ function _fire(target, type, detail) {
531
531
  // Forward `preventDefault` in a clean way
532
532
  if (ev.defaultPrevented) {
533
533
  const preventer = detail.preventer || detail.sourceEvent;
534
- if (preventer && preventer.preventDefault) {
534
+ if (preventer?.preventDefault) {
535
535
  preventer.preventDefault();
536
536
  }
537
537
  }
@@ -8,8 +8,7 @@ import type { Constructor } from '@open-wc/dedupe-mixin';
8
8
  /**
9
9
  * A mixin that allows to set partial I18N properties.
10
10
  */
11
- export declare function I18nMixin<I, T extends Constructor<HTMLElement>>(
12
- defaultI18n: I,
11
+ export declare function I18nMixin<T extends Constructor<HTMLElement>, I = unknown>(
13
12
  superclass: T,
14
13
  ): Constructor<I18nMixinClass<I>> & T;
15
14
 
package/src/i18n-mixin.js CHANGED
@@ -35,9 +35,16 @@ function deepMerge(target, ...sources) {
35
35
  /**
36
36
  * A mixin that allows to set partial I18N properties.
37
37
  *
38
- * @polymerMixin
38
+ * Subclasses provide their default values by overriding the
39
+ * `defaultI18n` static getter:
40
+ *
41
+ * ```js
42
+ * static get defaultI18n() {
43
+ * return { foo: 'Foo', bar: 'Bar' };
44
+ * }
45
+ * ```
39
46
  */
40
- export const I18nMixin = (defaultI18n, superClass) =>
47
+ export const I18nMixin = (superClass) =>
41
48
  class I18nMixinClass extends superClass {
42
49
  static get properties() {
43
50
  return {
@@ -55,10 +62,20 @@ export const I18nMixin = (defaultI18n, superClass) =>
55
62
  };
56
63
  }
57
64
 
65
+ /**
66
+ * Default I18N values. Must be overridden by subclasses with actual defaults.
67
+ *
68
+ * @protected
69
+ * @return {Object}
70
+ */
71
+ static get defaultI18n() {
72
+ return {};
73
+ }
74
+
58
75
  constructor() {
59
76
  super();
60
77
 
61
- this.i18n = deepMerge({}, defaultI18n);
78
+ this.i18n = deepMerge({}, this.constructor.defaultI18n);
62
79
  }
63
80
 
64
81
  /**
@@ -80,6 +97,6 @@ export const I18nMixin = (defaultI18n, superClass) =>
80
97
  return;
81
98
  }
82
99
  this.__customI18n = value;
83
- this.__effectiveI18n = deepMerge({}, defaultI18n, this.__customI18n);
100
+ this.__effectiveI18n = deepMerge({}, this.constructor.defaultI18n, this.__customI18n);
84
101
  }
85
102
  };
@@ -3,8 +3,6 @@
3
3
  * Copyright (c) 2021 - 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 { animationFrame } from './async.js';
7
- import { Debouncer } from './debounce.js';
8
6
 
9
7
  /**
10
8
  * A controller that detects if content inside the element overflows its scrolling viewport,
@@ -48,12 +46,7 @@ export class OverflowController {
48
46
  observe() {
49
47
  const { host } = this;
50
48
 
51
- this.__resizeObserver = new ResizeObserver(() => {
52
- this.__debounceOverflow = Debouncer.debounce(this.__debounceOverflow, animationFrame, () => {
53
- this.__updateOverflow();
54
- });
55
- });
56
-
49
+ this.__resizeObserver = new ResizeObserver(() => this.__onResize());
57
50
  this.__resizeObserver.observe(host);
58
51
 
59
52
  // Observe initial children
@@ -74,26 +67,43 @@ export class OverflowController {
74
67
  this.__resizeObserver.unobserve(node);
75
68
  }
76
69
  });
77
- });
78
70
 
79
- this.__updateOverflow();
71
+ if (addedNodes.length === 0 && removedNodes.length > 0) {
72
+ this.__updateState({ sync: true });
73
+ }
74
+ });
80
75
  });
81
76
 
82
77
  this.__childObserver.observe(host, { childList: true });
83
78
 
84
79
  // Update overflow attribute on scroll
85
80
  this.scrollTarget.addEventListener('scroll', this.__boundOnScroll);
81
+ }
86
82
 
87
- this.__updateOverflow();
83
+ /** @private */
84
+ __onResize() {
85
+ this.__updateState({ sync: false });
88
86
  }
89
87
 
90
88
  /** @private */
91
89
  __onScroll() {
92
- this.__updateOverflow();
90
+ this.__updateState({ sync: true });
93
91
  }
94
92
 
95
93
  /** @private */
96
- __updateOverflow() {
94
+ __updateState({ sync }) {
95
+ cancelAnimationFrame(this.__resizeRaf);
96
+
97
+ const state = this.__readState();
98
+ if (sync) {
99
+ this.__writeState(state);
100
+ } else {
101
+ this.__resizeRaf = requestAnimationFrame(() => this.__writeState(state));
102
+ }
103
+ }
104
+
105
+ /** @private */
106
+ __readState() {
97
107
  const target = this.scrollTarget;
98
108
 
99
109
  let overflow = '';
@@ -115,10 +125,14 @@ export class OverflowController {
115
125
  overflow += ' end';
116
126
  }
117
127
 
118
- overflow = overflow.trim();
119
- if (overflow.length > 0 && this.host.getAttribute('overflow') !== overflow) {
128
+ return { overflow: overflow.trim() };
129
+ }
130
+
131
+ /** @private */
132
+ __writeState({ overflow }) {
133
+ if (overflow) {
120
134
  this.host.setAttribute('overflow', overflow);
121
- } else if (overflow.length === 0 && this.host.hasAttribute('overflow')) {
135
+ } else {
122
136
  this.host.removeAttribute('overflow');
123
137
  }
124
138
  }
@@ -7,8 +7,6 @@
7
7
  /**
8
8
  * A mixin that forwards CSS class names to the internal overlay element
9
9
  * by setting the `overlayClass` property or `overlay-class` attribute.
10
- *
11
- * @polymerMixin
12
10
  */
13
11
  export const OverlayClassMixin = (superclass) =>
14
12
  class OverlayClassMixinClass extends superclass {
@@ -59,7 +59,7 @@ const PolylitMixinImplementation = (superclass) => {
59
59
  };
60
60
  }
61
61
 
62
- if (options && options.reflectToAttribute) {
62
+ if (options?.reflectToAttribute) {
63
63
  options.reflect = true;
64
64
  }
65
65
 
@@ -26,69 +26,67 @@ const observer = new ResizeObserver((entries) => {
26
26
 
27
27
  /**
28
28
  * A mixin that uses a ResizeObserver to listen to host size changes.
29
- *
30
- * @polymerMixin
31
29
  */
32
- export const ResizeMixin = dedupeMixin(
33
- (superclass) =>
34
- class ResizeMixinClass extends superclass {
35
- /**
36
- * When true, the parent element resize will be also observed.
37
- * Override this getter and return `true` to enable this.
38
- *
39
- * @protected
40
- */
41
- get _observeParent() {
42
- return false;
43
- }
44
-
45
- /** @protected */
46
- connectedCallback() {
47
- super.connectedCallback();
48
- observer.observe(this);
30
+ const ResizeMixinImplementation = (superclass) =>
31
+ class ResizeMixinClass extends superclass {
32
+ /**
33
+ * When true, the parent element resize will be also observed.
34
+ * Override this getter and return `true` to enable this.
35
+ *
36
+ * @protected
37
+ */
38
+ get _observeParent() {
39
+ return false;
40
+ }
49
41
 
50
- if (this._observeParent) {
51
- const parent = this.parentNode instanceof ShadowRoot ? this.parentNode.host : this.parentNode;
42
+ /** @protected */
43
+ connectedCallback() {
44
+ super.connectedCallback();
45
+ observer.observe(this);
52
46
 
53
- if (!parent.resizables) {
54
- parent.resizables = new Set();
55
- observer.observe(parent);
56
- }
47
+ if (this._observeParent) {
48
+ const parent = this.parentNode instanceof ShadowRoot ? this.parentNode.host : this.parentNode;
57
49
 
58
- parent.resizables.add(this);
59
- this.__parent = parent;
50
+ if (!parent.resizables) {
51
+ parent.resizables = new Set();
52
+ observer.observe(parent);
60
53
  }
54
+
55
+ parent.resizables.add(this);
56
+ this.__parent = parent;
61
57
  }
58
+ }
62
59
 
63
- /** @protected */
64
- disconnectedCallback() {
65
- super.disconnectedCallback();
66
- observer.unobserve(this);
60
+ /** @protected */
61
+ disconnectedCallback() {
62
+ super.disconnectedCallback();
63
+ observer.unobserve(this);
67
64
 
68
- const parent = this.__parent;
69
- if (this._observeParent && parent) {
70
- const resizables = parent.resizables;
65
+ const parent = this.__parent;
66
+ if (this._observeParent && parent) {
67
+ const resizables = parent.resizables;
71
68
 
72
- if (resizables) {
73
- resizables.delete(this);
69
+ if (resizables) {
70
+ resizables.delete(this);
74
71
 
75
- if (resizables.size === 0) {
76
- observer.unobserve(parent);
77
- }
72
+ if (resizables.size === 0) {
73
+ observer.unobserve(parent);
78
74
  }
79
-
80
- this.__parent = null;
81
75
  }
82
- }
83
76
 
84
- /**
85
- * A handler invoked on host resize. By default, it does nothing.
86
- * Override the method to implement your own behavior.
87
- *
88
- * @protected
89
- */
90
- _onResize(_contentRect) {
91
- // To be implemented.
77
+ this.__parent = null;
92
78
  }
93
- },
94
- );
79
+ }
80
+
81
+ /**
82
+ * A handler invoked on host resize. By default, it does nothing.
83
+ * Override the method to implement your own behavior.
84
+ *
85
+ * @protected
86
+ */
87
+ _onResize(_contentRect) {
88
+ // To be implemented.
89
+ }
90
+ };
91
+
92
+ export const ResizeMixin = dedupeMixin(ResizeMixinImplementation);
@@ -223,7 +223,7 @@ export class SlotController extends EventTarget {
223
223
  });
224
224
  }
225
225
 
226
- if (newNodes && newNodes.length > 0) {
226
+ if (newNodes?.length > 0) {
227
227
  if (this.multiple) {
228
228
  // Remove default node if exists
229
229
  if (this.defaultNode) {
@@ -9,13 +9,13 @@
9
9
  */
10
10
  export class SlotObserver {
11
11
  constructor(slot, callback, forceInitial) {
12
- /** @type HTMLSlotElement */
12
+ /** @type {HTMLSlotElement} */
13
13
  this.slot = slot;
14
14
 
15
- /** @type Function */
15
+ /** @type {Function} */
16
16
  this.callback = callback;
17
17
 
18
- /** @type boolean */
18
+ /** @type {boolean} */
19
19
  this.forceInitial = forceInitial;
20
20
 
21
21
  /** @type {Node[]} */
@@ -39,38 +39,36 @@ function insertStyles(styles, root) {
39
39
  /**
40
40
  * Mixin to insert styles into the outer scope to handle slotted components.
41
41
  * This is useful e.g. to hide native `<input type="number">` controls.
42
- *
43
- * @polymerMixin
44
42
  */
45
- export const SlotStylesMixin = dedupeMixin(
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
- }
43
+ const SlotStylesMixinImplementation = (superclass) =>
44
+ class SlotStylesMixinClass extends superclass {
45
+ /**
46
+ * List of styles to insert into root.
47
+ * @protected
48
+ */
49
+ get slotStyles() {
50
+ return [];
51
+ }
55
52
 
56
- /** @protected */
57
- connectedCallback() {
58
- super.connectedCallback();
53
+ /** @protected */
54
+ connectedCallback() {
55
+ super.connectedCallback();
59
56
 
60
- this.__applySlotStyles();
61
- }
57
+ this.__applySlotStyles();
58
+ }
62
59
 
63
- /** @private */
64
- __applySlotStyles() {
65
- const root = this.getRootNode();
66
- const rootStyles = getRootStyles(root);
60
+ /** @private */
61
+ __applySlotStyles() {
62
+ const root = this.getRootNode();
63
+ const rootStyles = getRootStyles(root);
67
64
 
68
- this.slotStyles.forEach((styles) => {
69
- if (!rootStyles.has(styles)) {
70
- insertStyles(styles, root);
71
- rootStyles.add(styles);
72
- }
73
- });
74
- }
75
- },
76
- );
65
+ this.slotStyles.forEach((styles) => {
66
+ if (!rootStyles.has(styles)) {
67
+ insertStyles(styles, root);
68
+ rootStyles.add(styles);
69
+ }
70
+ });
71
+ }
72
+ };
73
+
74
+ export const SlotStylesMixin = dedupeMixin(SlotStylesMixinImplementation);
@@ -119,6 +119,13 @@ addGlobalStyles(
119
119
  --safe-area-inset-right: env(safe-area-inset-right, 0px);
120
120
  --safe-area-inset-bottom: env(safe-area-inset-bottom, 0px);
121
121
  --safe-area-inset-left: env(safe-area-inset-left, 0px);
122
+ --safe-area-inset-inline-start: var(--safe-area-inset-left);
123
+ --safe-area-inset-inline-end: var(--safe-area-inset-right);
124
+
125
+ &:dir(rtl) {
126
+ --safe-area-inset-inline-start: var(--safe-area-inset-right);
127
+ --safe-area-inset-inline-end: var(--safe-area-inset-left);
128
+ }
122
129
  }
123
130
 
124
131
  @supports not (color: hsl(0 0 0)) {
@@ -23,6 +23,8 @@ type TooltipPosition =
23
23
  * A controller that manages the slotted tooltip element.
24
24
  */
25
25
  export class TooltipController extends SlotController {
26
+ constructor(host: HTMLElement);
27
+
26
28
  /**
27
29
  * An HTML element for linking with the tooltip overlay
28
30
  * via `aria-describedby` attribute used by screen readers.
@@ -42,12 +44,6 @@ export class TooltipController extends SlotController {
42
44
  */
43
45
  manual: boolean;
44
46
 
45
- /**
46
- * When true, the tooltip is opened programmatically.
47
- * Only works if `manual` is set to `true`.
48
- */
49
- opened: boolean;
50
-
51
47
  /**
52
48
  * Position of the tooltip with respect to its target.
53
49
  */
@@ -74,11 +70,6 @@ export class TooltipController extends SlotController {
74
70
  */
75
71
  setManual(manual: boolean): void;
76
72
 
77
- /**
78
- * Toggle opened state on the slotted tooltip.
79
- */
80
- setOpened(opened: boolean): void;
81
-
82
73
  /**
83
74
  * Set default position for the slotted tooltip.
84
75
  * This can be overridden by setting the position
@@ -96,4 +87,18 @@ export class TooltipController extends SlotController {
96
87
  * Set an HTML element to attach the tooltip to.
97
88
  */
98
89
  setTarget(target: HTMLElement): void;
90
+
91
+ /**
92
+ * Schedule opening the slotted tooltip. Respects the tooltip's
93
+ * configured `hoverDelay` / `focusDelay` and the shared warm-up state.
94
+ * No-op when no tooltip is slotted.
95
+ */
96
+ open(options?: { hover?: boolean; focus?: boolean; immediate?: boolean }): void;
97
+
98
+ /**
99
+ * Schedule closing the slotted tooltip. Respects the tooltip's
100
+ * configured `hideDelay` unless `immediate` is true.
101
+ * No-op when no tooltip is slotted.
102
+ */
103
+ close(immediate?: boolean): void;
99
104
  }
@@ -39,10 +39,6 @@ export class TooltipController extends SlotController {
39
39
  tooltipNode.manual = this.manual;
40
40
  }
41
41
 
42
- if (this.opened !== undefined) {
43
- tooltipNode.opened = this.opened;
44
- }
45
-
46
42
  if (this.position !== undefined) {
47
43
  tooltipNode._position = this.position;
48
44
  }
@@ -113,19 +109,6 @@ export class TooltipController extends SlotController {
113
109
  }
114
110
  }
115
111
 
116
- /**
117
- * Toggle opened state on the slotted tooltip.
118
- * @param {boolean} opened
119
- */
120
- setOpened(opened) {
121
- this.opened = opened;
122
-
123
- const tooltipNode = this.node;
124
- if (tooltipNode) {
125
- tooltipNode.opened = opened;
126
- }
127
- }
128
-
129
112
  /**
130
113
  * Set default position for the slotted tooltip.
131
114
  * This can be overridden by setting the position
@@ -168,6 +151,34 @@ export class TooltipController extends SlotController {
168
151
  }
169
152
  }
170
153
 
154
+ /**
155
+ * Schedule opening the slotted tooltip. Respects the tooltip's
156
+ * configured `hoverDelay` / `focusDelay` and the shared warm-up state.
157
+ * No-op when no tooltip is slotted.
158
+ *
159
+ * @param {{ hover?: boolean, focus?: boolean, immediate?: boolean }} [options]
160
+ */
161
+ open(options) {
162
+ const tooltipNode = this.node;
163
+ if (tooltipNode?.isConnected) {
164
+ tooltipNode._stateController.open(options);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Schedule closing the slotted tooltip. Respects the tooltip's
170
+ * configured `hideDelay` unless `immediate` is true.
171
+ * No-op when no tooltip is slotted.
172
+ *
173
+ * @param {boolean} [immediate]
174
+ */
175
+ close(immediate) {
176
+ const tooltipNode = this.node;
177
+ if (tooltipNode) {
178
+ tooltipNode._stateController.close(immediate);
179
+ }
180
+ }
181
+
171
182
  /** @private */
172
183
  __onContentChange(event) {
173
184
  this.__notifyChange(event.target);
@@ -78,8 +78,6 @@ export class IronListAdapter {
78
78
  });
79
79
  attachObserver.observe(this.scrollTarget);
80
80
 
81
- this._scrollLineHeight = this._getScrollLineHeight();
82
-
83
81
  this.scrollTarget.addEventListener('virtualizer-element-focused', (e) => this.__onElementFocused(e));
84
82
  this.elementsContainer.addEventListener('focusin', () => {
85
83
  this.scrollTarget.dispatchEvent(
@@ -462,10 +460,6 @@ export class IronListAdapter {
462
460
  this._isRTL = Boolean(styles.direction === 'rtl');
463
461
  this._viewportWidth = this.elementsContainer.offsetWidth;
464
462
  this._viewportHeight = this.scrollTarget.offsetHeight;
465
- this._scrollPageHeight = this._viewportHeight - this._scrollLineHeight;
466
- if (this.grid) {
467
- this._updateGridMetrics();
468
- }
469
463
  }
470
464
 
471
465
  /** @private */
@@ -521,11 +515,19 @@ export class IronListAdapter {
521
515
 
522
516
  /** @private */
523
517
  __getFocusedElement(visibleElements = this.__getVisibleElements()) {
524
- return visibleElements.find(
525
- (element) =>
526
- element.contains(this.elementsContainer.getRootNode().activeElement) ||
527
- element.contains(this.scrollTarget.getRootNode().activeElement),
528
- );
518
+ // `document.activeElement` retargets to the outermost shadow host when
519
+ // focus lives in a nested shadow tree. Descend through nested shadow
520
+ // roots' `activeElement`s to reach the real focused node, then walk up
521
+ // the flattened tree (via `assignedSlot`/`parentNode`/`host`) until a
522
+ // visible row is reached.
523
+ let node = document.activeElement;
524
+ while (node?.shadowRoot?.activeElement) {
525
+ node = node.shadowRoot.activeElement;
526
+ }
527
+ while (node && !visibleElements.includes(node)) {
528
+ node = node.assignedSlot || node.parentNode || node.host;
529
+ }
530
+ return node;
529
531
  }
530
532
 
531
533
  /** @private */
@@ -780,20 +782,6 @@ export class IronListAdapter {
780
782
  return 0;
781
783
  }
782
784
 
783
- /**
784
- * @returns {Number|undefined} - The browser's default font-size in pixels
785
- * @private
786
- */
787
- _getScrollLineHeight() {
788
- const el = document.createElement('div');
789
- el.style.fontSize = 'initial';
790
- el.style.display = 'none';
791
- document.body.appendChild(el);
792
- const fontSize = window.getComputedStyle(el).fontSize;
793
- document.body.removeChild(el);
794
- return fontSize ? window.parseInt(fontSize) : undefined;
795
- }
796
-
797
785
  __getVisibleElements() {
798
786
  return Array.from(this.elementsContainer.children).filter((element) => !element.hidden);
799
787
  }