@vaadin/avatar-group 24.6.0-alpha8 → 24.6.0-alpha9

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/avatar-group",
3
- "version": "24.6.0-alpha8",
3
+ "version": "24.6.0-alpha9",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -36,20 +36,21 @@
36
36
  "polymer"
37
37
  ],
38
38
  "dependencies": {
39
+ "@open-wc/dedupe-mixin": "^1.3.0",
39
40
  "@polymer/polymer": "^3.0.0",
40
- "@vaadin/a11y-base": "24.6.0-alpha8",
41
- "@vaadin/avatar": "24.6.0-alpha8",
42
- "@vaadin/component-base": "24.6.0-alpha8",
43
- "@vaadin/item": "24.6.0-alpha8",
44
- "@vaadin/list-box": "24.6.0-alpha8",
45
- "@vaadin/overlay": "24.6.0-alpha8",
46
- "@vaadin/vaadin-lumo-styles": "24.6.0-alpha8",
47
- "@vaadin/vaadin-material-styles": "24.6.0-alpha8",
48
- "@vaadin/vaadin-themable-mixin": "24.6.0-alpha8",
41
+ "@vaadin/a11y-base": "24.6.0-alpha9",
42
+ "@vaadin/avatar": "24.6.0-alpha9",
43
+ "@vaadin/component-base": "24.6.0-alpha9",
44
+ "@vaadin/item": "24.6.0-alpha9",
45
+ "@vaadin/list-box": "24.6.0-alpha9",
46
+ "@vaadin/overlay": "24.6.0-alpha9",
47
+ "@vaadin/vaadin-lumo-styles": "24.6.0-alpha9",
48
+ "@vaadin/vaadin-material-styles": "24.6.0-alpha9",
49
+ "@vaadin/vaadin-themable-mixin": "24.6.0-alpha9",
49
50
  "lit": "^3.0.0"
50
51
  },
51
52
  "devDependencies": {
52
- "@vaadin/chai-plugins": "24.6.0-alpha8",
53
+ "@vaadin/chai-plugins": "24.6.0-alpha9",
53
54
  "@vaadin/testing-helpers": "^1.0.0",
54
55
  "sinon": "^18.0.0"
55
56
  },
@@ -57,5 +58,5 @@
57
58
  "web-types.json",
58
59
  "web-types.lit.json"
59
60
  ],
60
- "gitHead": "a11e1510c4caa08775b202714f5fc1198c22132a"
61
+ "gitHead": "e303d77ba20c3089c9998be9a318733d9ec5b53c"
61
62
  }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2020 - 2024 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 { AvatarI18n } from '@vaadin/avatar/src/vaadin-avatar.js';
8
+ import type { OverlayClassMixinClass } from '@vaadin/component-base/src/overlay-class-mixin.js';
9
+ import type { ResizeMixinClass } from '@vaadin/component-base/src/resize-mixin.js';
10
+
11
+ export interface AvatarGroupI18n extends AvatarI18n {
12
+ activeUsers: {
13
+ one: string;
14
+ many: string;
15
+ };
16
+ joined: string;
17
+ left: string;
18
+ }
19
+
20
+ export interface AvatarGroupItem {
21
+ name?: string;
22
+ abbr?: string;
23
+ img?: string;
24
+ colorIndex?: number;
25
+ className?: string;
26
+ }
27
+
28
+ /**
29
+ * A mixin providing common avatar functionality.
30
+ */
31
+ export declare function AvatarGroupMixin<T extends Constructor<HTMLElement>>(
32
+ base: T,
33
+ ): Constructor<AvatarGroupMixinClass> & Constructor<OverlayClassMixinClass> & Constructor<ResizeMixinClass> & T;
34
+
35
+ export declare class AvatarGroupMixinClass {
36
+ /**
37
+ * An array containing the items which will be stamped as avatars.
38
+ *
39
+ * The items objects allow to configure [`name`](#/elements/vaadin-avatar#property-name),
40
+ * [`abbr`](#/elements/vaadin-avatar#property-abbr), [`img`](#/elements/vaadin-avatar#property-img)
41
+ * and [`colorIndex`](#/elements/vaadin-avatar#property-colorIndex) properties on the
42
+ * stamped avatars, and set `className` to provide CSS class names.
43
+ *
44
+ * #### Example
45
+ *
46
+ * ```js
47
+ * group.items = [
48
+ * {
49
+ * name: 'User name',
50
+ * img: 'url-to-image.png',
51
+ * className: 'even'
52
+ * },
53
+ * {
54
+ * abbr: 'JD',
55
+ * colorIndex: 1,
56
+ * className: 'odd'
57
+ * },
58
+ * ];
59
+ * ```
60
+ */
61
+ items: AvatarGroupItem[] | undefined;
62
+
63
+ /**
64
+ * The maximum number of avatars to display. By default, all the avatars are displayed.
65
+ * When _maxItemsVisible_ is set, the overflowing avatars are grouped into one avatar with
66
+ * a dropdown. Setting 0 or 1 has no effect so there are always at least two avatars visible.
67
+ * @attr {number} max-items-visible
68
+ */
69
+ maxItemsVisible: number | null | undefined;
70
+
71
+ /**
72
+ * The object used to localize this component.
73
+ * To change the default localization, replace the entire
74
+ * _i18n_ object or just the property you want to modify.
75
+ *
76
+ * The object has the following JSON structure and default values:
77
+ * ```
78
+ * {
79
+ * // Translation of the anonymous user avatar tooltip.
80
+ * anonymous: 'anonymous',
81
+ * // Translation of the avatar group accessible label.
82
+ * // {count} is replaced with the actual count of users.
83
+ * activeUsers: {
84
+ * one: 'Currently one active user',
85
+ * many: 'Currently {count} active users'
86
+ * },
87
+ * // Screen reader announcement when user joins group.
88
+ * // {user} is replaced with the name or abbreviation.
89
+ * // When neither is set, "anonymous" is used instead.
90
+ * joined: '{user} joined',
91
+ * // Screen reader announcement when user leaves group.
92
+ * // {user} is replaced with the name or abbreviation.
93
+ * // When neither is set, "anonymous" is used instead.
94
+ * left: '{user} left'
95
+ * }
96
+ * ```
97
+ */
98
+ i18n: AvatarGroupI18n;
99
+ }
@@ -0,0 +1,556 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2020 - 2024 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
7
+ import { html, render } from 'lit';
8
+ import { ifDefined } from 'lit/directives/if-defined.js';
9
+ import { announce } from '@vaadin/a11y-base/src/announce.js';
10
+ import { OverlayClassMixin } from '@vaadin/component-base/src/overlay-class-mixin.js';
11
+ import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
12
+ import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
13
+
14
+ const MINIMUM_DISPLAYED_AVATARS = 2;
15
+
16
+ /**
17
+ * A mixin providing common avatar group functionality.
18
+ *
19
+ * @polymerMixin
20
+ * @mixes ResizeMixin
21
+ * @mixes OverlayClassMixin
22
+ */
23
+ export const AvatarGroupMixin = (superClass) =>
24
+ class AvatarGroupMixinClass extends ResizeMixin(OverlayClassMixin(superClass)) {
25
+ static get properties() {
26
+ return {
27
+ /**
28
+ * An array containing the items which will be stamped as avatars.
29
+ *
30
+ * The items objects allow to configure [`name`](#/elements/vaadin-avatar#property-name),
31
+ * [`abbr`](#/elements/vaadin-avatar#property-abbr), [`img`](#/elements/vaadin-avatar#property-img)
32
+ * and [`colorIndex`](#/elements/vaadin-avatar#property-colorIndex) properties on the
33
+ * stamped avatars, and set `className` to provide CSS class names.
34
+ *
35
+ * #### Example
36
+ *
37
+ * ```js
38
+ * group.items = [
39
+ * {
40
+ * name: 'User name',
41
+ * img: 'url-to-image.png',
42
+ * className: 'even'
43
+ * },
44
+ * {
45
+ * abbr: 'JD',
46
+ * colorIndex: 1,
47
+ * className: 'odd'
48
+ * },
49
+ * ];
50
+ * ```
51
+ *
52
+ * @type {!Array<!AvatarGroupItem> | undefined}
53
+ */
54
+ items: {
55
+ type: Array,
56
+ observer: '__itemsChanged',
57
+ sync: true,
58
+ },
59
+
60
+ /**
61
+ * The maximum number of avatars to display. By default, all the avatars are displayed.
62
+ * When _maxItemsVisible_ is set, the overflowing avatars are grouped into one avatar with
63
+ * a dropdown. Setting 0 or 1 has no effect so there are always at least two avatars visible.
64
+ * @attr {number} max-items-visible
65
+ */
66
+ maxItemsVisible: {
67
+ type: Number,
68
+ sync: true,
69
+ },
70
+
71
+ /**
72
+ * The object used to localize this component.
73
+ * To change the default localization, replace the entire
74
+ * _i18n_ object or just the property you want to modify.
75
+ *
76
+ * The object has the following JSON structure and default values:
77
+ * ```
78
+ * {
79
+ * // Translation of the anonymous user avatar tooltip.
80
+ * anonymous: 'anonymous',
81
+ * // Translation of the avatar group accessible label.
82
+ * // {count} is replaced with the actual count of users.
83
+ * activeUsers: {
84
+ * one: 'Currently one active user',
85
+ * many: 'Currently {count} active users'
86
+ * },
87
+ * // Screen reader announcement when user joins group.
88
+ * // {user} is replaced with the name or abbreviation.
89
+ * // When neither is set, "anonymous" is used instead.
90
+ * joined: '{user} joined',
91
+ * // Screen reader announcement when user leaves group.
92
+ * // {user} is replaced with the name or abbreviation.
93
+ * // When neither is set, "anonymous" is used instead.
94
+ * left: '{user} left'
95
+ * }
96
+ * ```
97
+ * @type {!AvatarGroupI18n}
98
+ * @default {English/US}
99
+ */
100
+ i18n: {
101
+ type: Object,
102
+ sync: true,
103
+ value: () => {
104
+ return {
105
+ anonymous: 'anonymous',
106
+ activeUsers: {
107
+ one: 'Currently one active user',
108
+ many: 'Currently {count} active users',
109
+ },
110
+ joined: '{user} joined',
111
+ left: '{user} left',
112
+ };
113
+ },
114
+ },
115
+
116
+ /** @private */
117
+ _avatars: {
118
+ type: Array,
119
+ value: () => [],
120
+ sync: true,
121
+ },
122
+
123
+ /** @private */
124
+ __itemsInView: {
125
+ type: Number,
126
+ value: null,
127
+ sync: true,
128
+ },
129
+
130
+ /** @private */
131
+ _overflow: {
132
+ type: Object,
133
+ sync: true,
134
+ },
135
+
136
+ /** @private */
137
+ _overflowItems: {
138
+ type: Array,
139
+ observer: '__overflowItemsChanged',
140
+ computed: '__computeOverflowItems(items, __itemsInView, maxItemsVisible)',
141
+ sync: true,
142
+ },
143
+
144
+ /** @private */
145
+ _overflowTooltip: {
146
+ type: Object,
147
+ sync: true,
148
+ },
149
+
150
+ /** @private */
151
+ _opened: {
152
+ type: Boolean,
153
+ sync: true,
154
+ },
155
+ };
156
+ }
157
+
158
+ static get observers() {
159
+ return [
160
+ '__i18nItemsChanged(i18n, items)',
161
+ '__openedChanged(_opened, _overflow)',
162
+ '__updateAvatarsTheme(_overflow, _avatars, _theme)',
163
+ '__updateAvatars(items, __itemsInView, maxItemsVisible, _overflow, i18n)',
164
+ '__updateOverflowAvatar(_overflow, items, __itemsInView, maxItemsVisible)',
165
+ '__updateOverflowTooltip(_overflowTooltip, items, __itemsInView, maxItemsVisible)',
166
+ ];
167
+ }
168
+
169
+ /** @protected */
170
+ ready() {
171
+ super.ready();
172
+
173
+ this._overflowController = new SlotController(this, 'overflow', 'vaadin-avatar', {
174
+ initializer: (overflow) => {
175
+ overflow.setAttribute('aria-haspopup', 'menu');
176
+ overflow.setAttribute('aria-expanded', 'false');
177
+ overflow.addEventListener('click', (e) => this._onOverflowClick(e));
178
+ overflow.addEventListener('keydown', (e) => this._onOverflowKeyDown(e));
179
+
180
+ const tooltip = document.createElement('vaadin-tooltip');
181
+ tooltip.setAttribute('slot', 'tooltip');
182
+ overflow.appendChild(tooltip);
183
+
184
+ this._overflow = overflow;
185
+ this._overflowTooltip = tooltip;
186
+ },
187
+ });
188
+ this.addController(this._overflowController);
189
+
190
+ const overlay = this.$.overlay;
191
+ overlay.renderer = this.__overlayRenderer.bind(this);
192
+ this._overlayElement = overlay;
193
+
194
+ afterNextRender(this, () => {
195
+ this.__setItemsInView();
196
+ });
197
+ }
198
+
199
+ /** @protected */
200
+ disconnectedCallback() {
201
+ super.disconnectedCallback();
202
+
203
+ this._opened = false;
204
+ }
205
+
206
+ /** @private */
207
+ __getMessage(user, action) {
208
+ return action.replace('{user}', user.name || user.abbr || this.i18n.anonymous);
209
+ }
210
+
211
+ /**
212
+ * Renders items when they are provided by the `items` property and clears the content otherwise.
213
+ * @param {!HTMLElement} root
214
+ * @param {!Select} _select
215
+ * @private
216
+ */
217
+ __overlayRenderer(root) {
218
+ let menu = root.firstElementChild;
219
+ if (!menu) {
220
+ menu = document.createElement('vaadin-avatar-group-menu');
221
+ menu.addEventListener('keydown', (event) => this._onListKeyDown(event));
222
+ root.appendChild(menu);
223
+ }
224
+
225
+ menu.textContent = '';
226
+
227
+ if (!this._overflowItems) {
228
+ return;
229
+ }
230
+
231
+ this._overflowItems.forEach((item) => {
232
+ menu.appendChild(this.__createItemElement(item));
233
+ });
234
+ }
235
+
236
+ /** @private */
237
+ __createItemElement(item) {
238
+ const itemElement = document.createElement('vaadin-avatar-group-menu-item');
239
+
240
+ const avatar = document.createElement('vaadin-avatar');
241
+ itemElement.appendChild(avatar);
242
+
243
+ avatar.setAttribute('aria-hidden', 'true');
244
+ avatar.setAttribute('tabindex', '-1');
245
+ avatar.i18n = this.i18n;
246
+
247
+ if (this._theme) {
248
+ avatar.setAttribute('theme', this._theme);
249
+ }
250
+
251
+ avatar.name = item.name;
252
+ avatar.abbr = item.abbr;
253
+ avatar.img = item.img;
254
+ avatar.colorIndex = item.colorIndex;
255
+ if (item.className) {
256
+ avatar.className = item.className;
257
+ }
258
+
259
+ if (item.name) {
260
+ const text = document.createTextNode(item.name);
261
+ itemElement.appendChild(text);
262
+ }
263
+
264
+ return itemElement;
265
+ }
266
+
267
+ /** @private */
268
+ _onOverflowClick(e) {
269
+ e.stopPropagation();
270
+ if (this._opened) {
271
+ this.$.overlay.close();
272
+ } else if (!e.defaultPrevented) {
273
+ this._opened = true;
274
+ }
275
+ }
276
+
277
+ /** @private */
278
+ _onOverflowKeyDown(e) {
279
+ if (!this._opened) {
280
+ if (/^(Enter|SpaceBar|\s)$/u.test(e.key)) {
281
+ e.preventDefault();
282
+ this._opened = true;
283
+ }
284
+ }
285
+ }
286
+
287
+ /** @private */
288
+ _onListKeyDown(event) {
289
+ if (event.key === 'Escape' || event.key === 'Tab') {
290
+ this._opened = false;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * @protected
296
+ * @override
297
+ */
298
+ _onResize() {
299
+ this.__setItemsInView();
300
+ }
301
+
302
+ /** @private */
303
+ _onVaadinOverlayClose(e) {
304
+ if (e.detail.sourceEvent && e.detail.sourceEvent.composedPath().includes(this)) {
305
+ e.preventDefault();
306
+ }
307
+ }
308
+
309
+ /** @private */
310
+ _onVaadinOverlayOpen() {
311
+ if (this._menuElement) {
312
+ this._menuElement.focus();
313
+ }
314
+ }
315
+
316
+ /** @private */
317
+ __renderAvatars(items) {
318
+ render(
319
+ html`
320
+ ${items.map(
321
+ (item) => html`
322
+ <vaadin-avatar
323
+ .name="${item.name}"
324
+ .abbr="${item.abbr}"
325
+ .img="${item.img}"
326
+ .colorIndex="${item.colorIndex}"
327
+ .i18n="${this.i18n}"
328
+ class="${ifDefined(item.className)}"
329
+ with-tooltip
330
+ ></vaadin-avatar>
331
+ `,
332
+ )}
333
+ `,
334
+ this,
335
+ { renderBefore: this._overflow },
336
+ );
337
+ }
338
+
339
+ /** @private */
340
+ __updateAvatars(items, itemsInView, maxItemsVisible, overflow) {
341
+ if (!overflow || !Array.isArray(items)) {
342
+ return;
343
+ }
344
+
345
+ const limit = this.__getLimit(items.length, itemsInView, maxItemsVisible);
346
+
347
+ this.__renderAvatars(limit ? items.slice(0, limit) : items);
348
+
349
+ this._avatars = [...this.querySelectorAll('vaadin-avatar')];
350
+ }
351
+
352
+ /** @private */
353
+ __computeOverflowItems(items, itemsInView, maxItemsVisible) {
354
+ const count = Array.isArray(items) ? items.length : 0;
355
+ const limit = this.__getLimit(count, itemsInView, maxItemsVisible);
356
+ return limit ? items.slice(limit) : [];
357
+ }
358
+
359
+ /** @private */
360
+ __updateOverflowAvatar(overflow, items, itemsInView, maxItemsVisible) {
361
+ if (overflow) {
362
+ const count = Array.isArray(items) ? items.length : 0;
363
+ const maxReached = maxItemsVisible != null && count > this.__getMax(maxItemsVisible);
364
+
365
+ overflow.abbr = `+${count - this.__getLimit(count, itemsInView, maxItemsVisible)}`;
366
+ overflow.toggleAttribute('hidden', !maxReached && !(itemsInView && itemsInView < count));
367
+ }
368
+ }
369
+
370
+ /** @private */
371
+ __updateAvatarsTheme(overflow, avatars, theme) {
372
+ if (overflow) {
373
+ [overflow, ...avatars].forEach((avatar) => {
374
+ if (theme) {
375
+ avatar.setAttribute('theme', theme);
376
+ } else {
377
+ avatar.removeAttribute('theme');
378
+ }
379
+ });
380
+ }
381
+ }
382
+
383
+ /** @private */
384
+ __updateOverflowTooltip(tooltip, items, itemsInView, maxItemsVisible) {
385
+ if (!tooltip || !Array.isArray(items)) {
386
+ return;
387
+ }
388
+
389
+ const limit = this.__getLimit(items.length, itemsInView, maxItemsVisible);
390
+ if (limit == null) {
391
+ return;
392
+ }
393
+
394
+ const result = [];
395
+ for (let i = limit; i < items.length; i++) {
396
+ const item = items[i];
397
+ if (item) {
398
+ result.push(item.name || item.abbr || 'anonymous');
399
+ }
400
+ }
401
+
402
+ tooltip.text = result.join('\n');
403
+ }
404
+
405
+ /** @private */
406
+ __getLimit(items, itemsInView, maxItemsVisible) {
407
+ let limit = null;
408
+ // Handle max set to 0 or 1
409
+ const adjustedMax = this.__getMax(maxItemsVisible);
410
+ if (maxItemsVisible != null && adjustedMax < items) {
411
+ limit = adjustedMax - 1;
412
+ } else if (itemsInView && itemsInView < items) {
413
+ limit = itemsInView;
414
+ }
415
+
416
+ return Math.min(limit, this.__calculateAvatarsFitWidth());
417
+ }
418
+
419
+ /** @private */
420
+ __getMax(maxItemsVisible) {
421
+ return Math.max(maxItemsVisible, MINIMUM_DISPLAYED_AVATARS);
422
+ }
423
+
424
+ /** @private */
425
+ __itemsChanged(items, oldItems) {
426
+ this.__setItemsInView();
427
+
428
+ let added = [];
429
+ let removed = [];
430
+
431
+ const hasNewItems = Array.isArray(items);
432
+ const hasOldItems = Array.isArray(oldItems);
433
+
434
+ if (hasOldItems) {
435
+ removed = oldItems.filter((item) => hasNewItems && !items.includes(item));
436
+ }
437
+
438
+ if (hasNewItems) {
439
+ added = items.filter((item) => hasOldItems && !oldItems.includes(item));
440
+ }
441
+
442
+ this.__announceItemsChange(added, removed);
443
+ }
444
+
445
+ /** @private */
446
+ __announceItemsChange(added, removed) {
447
+ let addedMsg = [];
448
+ let removedMsg = [];
449
+ if (added) {
450
+ addedMsg = added.map((user) => this.__getMessage(user, this.i18n.joined || '{user} joined'));
451
+ }
452
+
453
+ if (removed) {
454
+ removedMsg = removed.map((user) => this.__getMessage(user, this.i18n.left || '{user} left'));
455
+ }
456
+
457
+ const messages = removedMsg.concat(addedMsg);
458
+ if (messages.length > 0) {
459
+ announce(messages.join(', '));
460
+ }
461
+ }
462
+
463
+ /** @private */
464
+ __i18nItemsChanged(i18n, items) {
465
+ if (i18n && i18n.activeUsers) {
466
+ const count = Array.isArray(items) ? items.length : 0;
467
+ const field = count === 1 ? 'one' : 'many';
468
+ if (i18n.activeUsers[field]) {
469
+ this.setAttribute('aria-label', i18n.activeUsers[field].replace('{count}', count || 0));
470
+ }
471
+
472
+ this._avatars.forEach((avatar) => {
473
+ avatar.i18n = i18n;
474
+ });
475
+ }
476
+ }
477
+
478
+ /** @private */
479
+ __openedChanged(opened, overflow) {
480
+ if (!overflow) {
481
+ return;
482
+ }
483
+
484
+ if (opened) {
485
+ if (!this._menuElement) {
486
+ this._menuElement = this.$.overlay.querySelector('vaadin-avatar-group-menu');
487
+ }
488
+
489
+ this._openedWithFocusRing = overflow.hasAttribute('focus-ring');
490
+ } else if (this.__oldOpened) {
491
+ overflow.focus();
492
+ if (this._openedWithFocusRing) {
493
+ overflow.setAttribute('focus-ring', '');
494
+ }
495
+ }
496
+
497
+ overflow.setAttribute('aria-expanded', opened === true);
498
+ this.__oldOpened = opened;
499
+ }
500
+
501
+ /** @private */
502
+ __overflowItemsChanged(items, oldItems) {
503
+ if (items || oldItems) {
504
+ this.$.overlay.requestContentUpdate();
505
+ }
506
+ }
507
+
508
+ /** @private */
509
+ __setItemsInView() {
510
+ const avatars = this._avatars;
511
+ const items = this.items;
512
+
513
+ // Always show at least two avatars
514
+ if (!items || !avatars || avatars.length < 3) {
515
+ return;
516
+ }
517
+
518
+ let result = this.__calculateAvatarsFitWidth();
519
+
520
+ // Only show overlay if two or more avatars don't fit
521
+ if (result === items.length - 1) {
522
+ result = items.length;
523
+ }
524
+
525
+ // Close overlay if all avatars become visible
526
+ if (result >= items.length && this._opened) {
527
+ this.$.overlay.close();
528
+ // FIXME: hack to avoid jump before closing
529
+ this.$.overlay._flushAnimation('closing');
530
+ }
531
+
532
+ // Reserve space for overflow avatar
533
+ this.__itemsInView = result;
534
+ }
535
+
536
+ /** @private */
537
+ __calculateAvatarsFitWidth() {
538
+ if (!this.shadowRoot || this._avatars.length < MINIMUM_DISPLAYED_AVATARS) {
539
+ return MINIMUM_DISPLAYED_AVATARS;
540
+ }
541
+
542
+ const avatars = this._avatars;
543
+
544
+ // Assume all the avatars have the same width
545
+ const avatarWidth = avatars[0].clientWidth;
546
+
547
+ // Take negative margin into account
548
+ const { marginLeft, marginRight } = getComputedStyle(avatars[1]);
549
+
550
+ const offset = this.__isRTL
551
+ ? parseInt(marginRight, 0) - parseInt(marginLeft, 0)
552
+ : parseInt(marginLeft, 0) - parseInt(marginRight, 0);
553
+
554
+ return Math.floor((this.$.container.offsetWidth - avatarWidth) / (avatarWidth + offset));
555
+ }
556
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2020 - 2024 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import type { CSSResult } from 'lit';
7
+
8
+ export const avatarGroupStyles: CSSResult;