@vaadin/avatar-group 24.6.0-alpha8 → 24.6.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.
- package/package.json +13 -12
- package/src/vaadin-avatar-group-mixin.d.ts +99 -0
- package/src/vaadin-avatar-group-mixin.js +556 -0
- package/src/vaadin-avatar-group-styles.d.ts +8 -0
- package/src/vaadin-avatar-group-styles.js +51 -0
- package/src/vaadin-avatar-group.d.ts +3 -87
- package/src/vaadin-avatar-group.js +9 -597
- package/src/vaadin-lit-avatar-group-menu-item.js +60 -0
- package/src/vaadin-lit-avatar-group-menu.js +87 -0
- package/src/vaadin-lit-avatar-group-overlay.js +64 -0
- package/src/vaadin-lit-avatar-group.d.ts +1 -0
- package/src/vaadin-lit-avatar-group.js +64 -0
- package/theme/lumo/vaadin-lit-avatar-group.d.ts +3 -0
- package/theme/lumo/vaadin-lit-avatar-group.js +3 -0
- package/theme/material/vaadin-lit-avatar-group.d.ts +3 -0
- package/theme/material/vaadin-lit-avatar-group.js +3 -0
- package/vaadin-lit-avatar-group.d.ts +1 -0
- package/vaadin-lit-avatar-group.js +2 -0
- package/web-types.json +3 -3
- package/web-types.lit.json +3 -3
|
@@ -7,21 +7,15 @@ import '@vaadin/avatar/src/vaadin-avatar.js';
|
|
|
7
7
|
import './vaadin-avatar-group-menu.js';
|
|
8
8
|
import './vaadin-avatar-group-menu-item.js';
|
|
9
9
|
import './vaadin-avatar-group-overlay.js';
|
|
10
|
-
import {
|
|
11
|
-
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
|
|
12
|
-
import { html as legacyHtml, PolymerElement } from '@polymer/polymer/polymer-element.js';
|
|
13
|
-
import { html, render } from 'lit';
|
|
14
|
-
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
15
|
-
import { announce } from '@vaadin/a11y-base/src/announce.js';
|
|
10
|
+
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
|
|
16
11
|
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
|
|
17
12
|
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
|
|
18
13
|
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
14
|
+
import { registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
15
|
+
import { AvatarGroupMixin } from './vaadin-avatar-group-mixin.js';
|
|
16
|
+
import { avatarGroupStyles } from './vaadin-avatar-group-styles.js';
|
|
23
17
|
|
|
24
|
-
|
|
18
|
+
registerStyles('vaadin-avatar-group', avatarGroupStyles, { moduleId: 'vaadin-avatar-group-styles' });
|
|
25
19
|
|
|
26
20
|
/**
|
|
27
21
|
* `<vaadin-avatar-group>` is a Web Component providing avatar group displaying functionality.
|
|
@@ -67,59 +61,12 @@ const MINIMUM_DISPLAYED_AVATARS = 2;
|
|
|
67
61
|
* @extends HTMLElement
|
|
68
62
|
* @mixes ControllerMixin
|
|
69
63
|
* @mixes ElementMixin
|
|
70
|
-
* @mixes
|
|
64
|
+
* @mixes AvatarGroupMixin
|
|
71
65
|
* @mixes ThemableMixin
|
|
72
|
-
* @mixes ResizeMixin
|
|
73
66
|
*/
|
|
74
|
-
class AvatarGroup extends
|
|
67
|
+
class AvatarGroup extends AvatarGroupMixin(ElementMixin(ThemableMixin(ControllerMixin(PolymerElement)))) {
|
|
75
68
|
static get template() {
|
|
76
|
-
return
|
|
77
|
-
<style>
|
|
78
|
-
:host {
|
|
79
|
-
display: block;
|
|
80
|
-
width: 100%; /* prevent collapsing inside non-stretching column flex */
|
|
81
|
-
--vaadin-avatar-group-overlap: 8px;
|
|
82
|
-
--vaadin-avatar-group-overlap-border: 2px;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
:host([hidden]) {
|
|
86
|
-
display: none !important;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
[part='container'] {
|
|
90
|
-
display: flex;
|
|
91
|
-
position: relative;
|
|
92
|
-
width: 100%;
|
|
93
|
-
flex-wrap: nowrap;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
::slotted(vaadin-avatar:not(:first-child)) {
|
|
97
|
-
-webkit-mask-image: url('data:image/svg+xml;utf8,<svg viewBox=%220 0 300 300%22 fill=%22none%22 xmlns=%22http://www.w3.org/2000/svg%22><path fill-rule=%22evenodd%22 clip-rule=%22evenodd%22 d=%22M300 0H0V300H300V0ZM150 200C177.614 200 200 177.614 200 150C200 122.386 177.614 100 150 100C122.386 100 100 122.386 100 150C100 177.614 122.386 200 150 200Z%22 fill=%22black%22/></svg>');
|
|
98
|
-
mask-image: url('data:image/svg+xml;utf8,<svg viewBox=%220 0 300 300%22 fill=%22none%22 xmlns=%22http://www.w3.org/2000/svg%22><path fill-rule=%22evenodd%22 clip-rule=%22evenodd%22 d=%22M300 0H0V300H300V0ZM150 200C177.614 200 200 177.614 200 150C200 122.386 177.614 100 150 100C122.386 100 100 122.386 100 150C100 177.614 122.386 200 150 200Z%22 fill=%22black%22/></svg>');
|
|
99
|
-
-webkit-mask-size: calc(
|
|
100
|
-
300% + var(--vaadin-avatar-group-overlap-border) * 6 - var(--vaadin-avatar-outline-width) * 6
|
|
101
|
-
);
|
|
102
|
-
mask-size: calc(
|
|
103
|
-
300% + var(--vaadin-avatar-group-overlap-border) * 6 - var(--vaadin-avatar-outline-width) * 6
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
::slotted(vaadin-avatar:not([dir='rtl']):not(:first-child)) {
|
|
108
|
-
margin-left: calc(var(--vaadin-avatar-group-overlap) * -1 - var(--vaadin-avatar-outline-width));
|
|
109
|
-
-webkit-mask-position: calc(50% - var(--vaadin-avatar-size) + var(--vaadin-avatar-group-overlap));
|
|
110
|
-
mask-position: calc(50% - var(--vaadin-avatar-size) + var(--vaadin-avatar-group-overlap));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
::slotted(vaadin-avatar[dir='rtl']:not(:first-child)) {
|
|
114
|
-
margin-right: calc(var(--vaadin-avatar-group-overlap) * -1);
|
|
115
|
-
-webkit-mask-position: calc(
|
|
116
|
-
50% + var(--vaadin-avatar-size) - var(--vaadin-avatar-group-overlap) + var(--vaadin-avatar-outline-width)
|
|
117
|
-
);
|
|
118
|
-
mask-position: calc(
|
|
119
|
-
50% + var(--vaadin-avatar-size) - var(--vaadin-avatar-group-overlap) + var(--vaadin-avatar-outline-width)
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
</style>
|
|
69
|
+
return html`
|
|
123
70
|
<div id="container" part="container">
|
|
124
71
|
<slot></slot>
|
|
125
72
|
<slot name="overflow"></slot>
|
|
@@ -130,6 +77,7 @@ class AvatarGroup extends ResizeMixin(OverlayClassMixin(ElementMixin(ThemableMix
|
|
|
130
77
|
position-target="[[_overflow]]"
|
|
131
78
|
no-vertical-overlap
|
|
132
79
|
on-vaadin-overlay-close="_onVaadinOverlayClose"
|
|
80
|
+
on-vaadin-overlay-open="_onVaadinOverlayOpen"
|
|
133
81
|
></vaadin-avatar-group-overlay>
|
|
134
82
|
`;
|
|
135
83
|
}
|
|
@@ -137,542 +85,6 @@ class AvatarGroup extends ResizeMixin(OverlayClassMixin(ElementMixin(ThemableMix
|
|
|
137
85
|
static get is() {
|
|
138
86
|
return 'vaadin-avatar-group';
|
|
139
87
|
}
|
|
140
|
-
|
|
141
|
-
static get properties() {
|
|
142
|
-
return {
|
|
143
|
-
/**
|
|
144
|
-
* An array containing the items which will be stamped as avatars.
|
|
145
|
-
*
|
|
146
|
-
* The items objects allow to configure [`name`](#/elements/vaadin-avatar#property-name),
|
|
147
|
-
* [`abbr`](#/elements/vaadin-avatar#property-abbr), [`img`](#/elements/vaadin-avatar#property-img)
|
|
148
|
-
* and [`colorIndex`](#/elements/vaadin-avatar#property-colorIndex) properties on the
|
|
149
|
-
* stamped avatars, and set `className` to provide CSS class names.
|
|
150
|
-
*
|
|
151
|
-
* #### Example
|
|
152
|
-
*
|
|
153
|
-
* ```js
|
|
154
|
-
* group.items = [
|
|
155
|
-
* {
|
|
156
|
-
* name: 'User name',
|
|
157
|
-
* img: 'url-to-image.png',
|
|
158
|
-
* className: 'even'
|
|
159
|
-
* },
|
|
160
|
-
* {
|
|
161
|
-
* abbr: 'JD',
|
|
162
|
-
* colorIndex: 1,
|
|
163
|
-
* className: 'odd'
|
|
164
|
-
* },
|
|
165
|
-
* ];
|
|
166
|
-
* ```
|
|
167
|
-
*
|
|
168
|
-
* @type {!Array<!AvatarGroupItem> | undefined}
|
|
169
|
-
*/
|
|
170
|
-
items: {
|
|
171
|
-
type: Array,
|
|
172
|
-
},
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* The maximum number of avatars to display. By default, all the avatars are displayed.
|
|
176
|
-
* When _maxItemsVisible_ is set, the overflowing avatars are grouped into one avatar with
|
|
177
|
-
* a dropdown. Setting 0 or 1 has no effect so there are always at least two avatars visible.
|
|
178
|
-
* @attr {number} max-items-visible
|
|
179
|
-
*/
|
|
180
|
-
maxItemsVisible: {
|
|
181
|
-
type: Number,
|
|
182
|
-
},
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* The object used to localize this component.
|
|
186
|
-
* To change the default localization, replace the entire
|
|
187
|
-
* _i18n_ object or just the property you want to modify.
|
|
188
|
-
*
|
|
189
|
-
* The object has the following JSON structure and default values:
|
|
190
|
-
* ```
|
|
191
|
-
* {
|
|
192
|
-
* // Translation of the anonymous user avatar tooltip.
|
|
193
|
-
* anonymous: 'anonymous',
|
|
194
|
-
* // Translation of the avatar group accessible label.
|
|
195
|
-
* // {count} is replaced with the actual count of users.
|
|
196
|
-
* activeUsers: {
|
|
197
|
-
* one: 'Currently one active user',
|
|
198
|
-
* many: 'Currently {count} active users'
|
|
199
|
-
* },
|
|
200
|
-
* // Screen reader announcement when user joins group.
|
|
201
|
-
* // {user} is replaced with the name or abbreviation.
|
|
202
|
-
* // When neither is set, "anonymous" is used instead.
|
|
203
|
-
* joined: '{user} joined',
|
|
204
|
-
* // Screen reader announcement when user leaves group.
|
|
205
|
-
* // {user} is replaced with the name or abbreviation.
|
|
206
|
-
* // When neither is set, "anonymous" is used instead.
|
|
207
|
-
* left: '{user} left'
|
|
208
|
-
* }
|
|
209
|
-
* ```
|
|
210
|
-
* @type {!AvatarGroupI18n}
|
|
211
|
-
* @default {English/US}
|
|
212
|
-
*/
|
|
213
|
-
i18n: {
|
|
214
|
-
type: Object,
|
|
215
|
-
value: () => {
|
|
216
|
-
return {
|
|
217
|
-
anonymous: 'anonymous',
|
|
218
|
-
activeUsers: {
|
|
219
|
-
one: 'Currently one active user',
|
|
220
|
-
many: 'Currently {count} active users',
|
|
221
|
-
},
|
|
222
|
-
joined: '{user} joined',
|
|
223
|
-
left: '{user} left',
|
|
224
|
-
};
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
/** @private */
|
|
229
|
-
_avatars: {
|
|
230
|
-
type: Array,
|
|
231
|
-
value: () => [],
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
/** @private */
|
|
235
|
-
__maxReached: {
|
|
236
|
-
type: Boolean,
|
|
237
|
-
computed: '__computeMaxReached(items.length, maxItemsVisible)',
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
/** @private */
|
|
241
|
-
__items: {
|
|
242
|
-
type: Array,
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
/** @private */
|
|
246
|
-
__itemsInView: {
|
|
247
|
-
type: Number,
|
|
248
|
-
value: null,
|
|
249
|
-
},
|
|
250
|
-
|
|
251
|
-
/** @private */
|
|
252
|
-
_overflow: {
|
|
253
|
-
type: Object,
|
|
254
|
-
},
|
|
255
|
-
|
|
256
|
-
/** @private */
|
|
257
|
-
_overflowItems: {
|
|
258
|
-
type: Array,
|
|
259
|
-
observer: '__overflowItemsChanged',
|
|
260
|
-
computed: '__computeOverflowItems(items.*, __itemsInView, maxItemsVisible)',
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
/** @private */
|
|
264
|
-
_overflowTooltip: {
|
|
265
|
-
type: Object,
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
/** @private */
|
|
269
|
-
_opened: {
|
|
270
|
-
type: Boolean,
|
|
271
|
-
observer: '__openedChanged',
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
static get observers() {
|
|
277
|
-
return [
|
|
278
|
-
'__itemsChanged(items.splices, items.*)',
|
|
279
|
-
'__i18nItemsChanged(i18n.*, items.length)',
|
|
280
|
-
'__updateAvatarsTheme(_overflow, _avatars, _theme)',
|
|
281
|
-
'__updateAvatars(items.*, __itemsInView, maxItemsVisible, _overflow, i18n)',
|
|
282
|
-
'__updateOverflowAbbr(_overflow, items.length, __itemsInView, maxItemsVisible)',
|
|
283
|
-
'__updateOverflowHidden(_overflow, items.length, __itemsInView, __maxReached)',
|
|
284
|
-
'__updateOverflowTooltip(_overflowTooltip, items.length, __itemsInView, maxItemsVisible)',
|
|
285
|
-
];
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/** @protected */
|
|
289
|
-
ready() {
|
|
290
|
-
super.ready();
|
|
291
|
-
|
|
292
|
-
this._overflowController = new SlotController(this, 'overflow', 'vaadin-avatar', {
|
|
293
|
-
initializer: (overflow) => {
|
|
294
|
-
overflow.setAttribute('aria-haspopup', 'menu');
|
|
295
|
-
overflow.setAttribute('aria-expanded', 'false');
|
|
296
|
-
overflow.addEventListener('click', (e) => this._onOverflowClick(e));
|
|
297
|
-
overflow.addEventListener('keydown', (e) => this._onOverflowKeyDown(e));
|
|
298
|
-
|
|
299
|
-
const tooltip = document.createElement('vaadin-tooltip');
|
|
300
|
-
tooltip.setAttribute('slot', 'tooltip');
|
|
301
|
-
overflow.appendChild(tooltip);
|
|
302
|
-
|
|
303
|
-
this._overflow = overflow;
|
|
304
|
-
this._overflowTooltip = tooltip;
|
|
305
|
-
},
|
|
306
|
-
});
|
|
307
|
-
this.addController(this._overflowController);
|
|
308
|
-
|
|
309
|
-
const overlay = this.$.overlay;
|
|
310
|
-
overlay.renderer = this.__overlayRenderer.bind(this);
|
|
311
|
-
this._overlayElement = overlay;
|
|
312
|
-
|
|
313
|
-
afterNextRender(this, () => {
|
|
314
|
-
this.__setItemsInView();
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/** @protected */
|
|
319
|
-
disconnectedCallback() {
|
|
320
|
-
super.disconnectedCallback();
|
|
321
|
-
|
|
322
|
-
this._opened = false;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/** @private */
|
|
326
|
-
__getMessage(user, action) {
|
|
327
|
-
return action.replace('{user}', user.name || user.abbr || this.i18n.anonymous);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Renders items when they are provided by the `items` property and clears the content otherwise.
|
|
332
|
-
* @param {!HTMLElement} root
|
|
333
|
-
* @param {!Select} _select
|
|
334
|
-
* @private
|
|
335
|
-
*/
|
|
336
|
-
__overlayRenderer(root) {
|
|
337
|
-
let menu = root.firstElementChild;
|
|
338
|
-
if (!menu) {
|
|
339
|
-
menu = document.createElement('vaadin-avatar-group-menu');
|
|
340
|
-
menu.addEventListener('keydown', (event) => this._onListKeyDown(event));
|
|
341
|
-
root.appendChild(menu);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
menu.textContent = '';
|
|
345
|
-
|
|
346
|
-
if (!this._overflowItems) {
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
this._overflowItems.forEach((item) => {
|
|
351
|
-
menu.appendChild(this.__createItemElement(item));
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/** @private */
|
|
356
|
-
__createItemElement(item) {
|
|
357
|
-
const itemElement = document.createElement('vaadin-avatar-group-menu-item');
|
|
358
|
-
|
|
359
|
-
const avatar = document.createElement('vaadin-avatar');
|
|
360
|
-
itemElement.appendChild(avatar);
|
|
361
|
-
|
|
362
|
-
avatar.setAttribute('aria-hidden', 'true');
|
|
363
|
-
avatar.setAttribute('tabindex', '-1');
|
|
364
|
-
avatar.i18n = this.i18n;
|
|
365
|
-
|
|
366
|
-
if (this._theme) {
|
|
367
|
-
avatar.setAttribute('theme', this._theme);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
avatar.name = item.name;
|
|
371
|
-
avatar.abbr = item.abbr;
|
|
372
|
-
avatar.img = item.img;
|
|
373
|
-
avatar.colorIndex = item.colorIndex;
|
|
374
|
-
if (item.className) {
|
|
375
|
-
avatar.className = item.className;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (item.name) {
|
|
379
|
-
const text = document.createTextNode(item.name);
|
|
380
|
-
itemElement.appendChild(text);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return itemElement;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/** @private */
|
|
387
|
-
_onOverflowClick(e) {
|
|
388
|
-
e.stopPropagation();
|
|
389
|
-
if (this._opened) {
|
|
390
|
-
this.$.overlay.close();
|
|
391
|
-
} else if (!e.defaultPrevented) {
|
|
392
|
-
this._opened = true;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/** @private */
|
|
397
|
-
_onOverflowKeyDown(e) {
|
|
398
|
-
if (!this._opened) {
|
|
399
|
-
if (/^(Enter|SpaceBar|\s)$/u.test(e.key)) {
|
|
400
|
-
e.preventDefault();
|
|
401
|
-
this._opened = true;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/** @private */
|
|
407
|
-
_onListKeyDown(event) {
|
|
408
|
-
if (event.key === 'Escape' || event.key === 'Tab') {
|
|
409
|
-
this._opened = false;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* @protected
|
|
415
|
-
* @override
|
|
416
|
-
*/
|
|
417
|
-
_onResize() {
|
|
418
|
-
this.__setItemsInView();
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/** @private */
|
|
422
|
-
_onVaadinOverlayClose(e) {
|
|
423
|
-
if (e.detail.sourceEvent && e.detail.sourceEvent.composedPath().includes(this)) {
|
|
424
|
-
e.preventDefault();
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/** @private */
|
|
429
|
-
__renderAvatars(items) {
|
|
430
|
-
render(
|
|
431
|
-
html`
|
|
432
|
-
${items.map(
|
|
433
|
-
(item) => html`
|
|
434
|
-
<vaadin-avatar
|
|
435
|
-
.name="${item.name}"
|
|
436
|
-
.abbr="${item.abbr}"
|
|
437
|
-
.img="${item.img}"
|
|
438
|
-
.colorIndex="${item.colorIndex}"
|
|
439
|
-
.i18n="${this.i18n}"
|
|
440
|
-
class="${ifDefined(item.className)}"
|
|
441
|
-
with-tooltip
|
|
442
|
-
></vaadin-avatar>
|
|
443
|
-
`,
|
|
444
|
-
)}
|
|
445
|
-
`,
|
|
446
|
-
this,
|
|
447
|
-
{ renderBefore: this._overflow },
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/** @private */
|
|
452
|
-
__updateAvatars(arr, itemsInView, maxItemsVisible, overflow) {
|
|
453
|
-
if (!overflow) {
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const items = arr.base || [];
|
|
458
|
-
const limit = this.__getLimit(items.length, itemsInView, maxItemsVisible);
|
|
459
|
-
|
|
460
|
-
this.__renderAvatars(limit ? items.slice(0, limit) : items);
|
|
461
|
-
|
|
462
|
-
this._avatars = [...this.querySelectorAll('vaadin-avatar')];
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/** @private */
|
|
466
|
-
__computeOverflowItems(arr, itemsInView, maxItemsVisible) {
|
|
467
|
-
const items = arr.base || [];
|
|
468
|
-
const limit = this.__getLimit(items.length, itemsInView, maxItemsVisible);
|
|
469
|
-
return limit ? items.slice(limit) : [];
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/** @private */
|
|
473
|
-
__computeMaxReached(items, maxItemsVisible) {
|
|
474
|
-
return maxItemsVisible != null && items > this.__getMax(maxItemsVisible);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/** @private */
|
|
478
|
-
__updateOverflowAbbr(overflow, items, itemsInView, maxItemsVisible) {
|
|
479
|
-
if (overflow) {
|
|
480
|
-
overflow.abbr = `+${items - this.__getLimit(items, itemsInView, maxItemsVisible)}`;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/** @private */
|
|
485
|
-
__updateOverflowHidden(overflow, items, itemsInView, maxReached) {
|
|
486
|
-
if (overflow) {
|
|
487
|
-
overflow.toggleAttribute('hidden', !maxReached && !(itemsInView && itemsInView < items));
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
/** @private */
|
|
492
|
-
__updateAvatarsTheme(overflow, avatars, theme) {
|
|
493
|
-
if (overflow) {
|
|
494
|
-
[overflow, ...avatars].forEach((avatar) => {
|
|
495
|
-
if (theme) {
|
|
496
|
-
avatar.setAttribute('theme', theme);
|
|
497
|
-
} else {
|
|
498
|
-
avatar.removeAttribute('theme');
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/** @private */
|
|
505
|
-
__updateOverflowTooltip(tooltip, items, itemsInView, maxItemsVisible) {
|
|
506
|
-
if (!tooltip) {
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const limit = this.__getLimit(items, itemsInView, maxItemsVisible);
|
|
511
|
-
if (limit == null) {
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
const result = [];
|
|
516
|
-
for (let i = limit; i < items; i++) {
|
|
517
|
-
const item = this.items[i];
|
|
518
|
-
if (item) {
|
|
519
|
-
result.push(item.name || item.abbr || 'anonymous');
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
tooltip.text = result.join('\n');
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
/** @private */
|
|
527
|
-
__getLimit(items, itemsInView, maxItemsVisible) {
|
|
528
|
-
let limit = null;
|
|
529
|
-
// Handle max set to 0 or 1
|
|
530
|
-
const adjustedMax = this.__getMax(maxItemsVisible);
|
|
531
|
-
if (maxItemsVisible != null && adjustedMax < items) {
|
|
532
|
-
limit = adjustedMax - 1;
|
|
533
|
-
} else if (itemsInView && itemsInView < items) {
|
|
534
|
-
limit = itemsInView;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return Math.min(limit, this.__calculateAvatarsFitWidth());
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/** @private */
|
|
541
|
-
__getMax(maxItemsVisible) {
|
|
542
|
-
return Math.max(maxItemsVisible, MINIMUM_DISPLAYED_AVATARS);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/** @private */
|
|
546
|
-
__itemsChanged(splices, itemsChange) {
|
|
547
|
-
const items = itemsChange.base;
|
|
548
|
-
this.__setItemsInView();
|
|
549
|
-
|
|
550
|
-
// Mutation using group.splice('items')
|
|
551
|
-
if (splices && Array.isArray(splices.indexSplices)) {
|
|
552
|
-
splices.indexSplices.forEach((mutation) => {
|
|
553
|
-
this.__announceItemsChange(items, mutation);
|
|
554
|
-
});
|
|
555
|
-
} else if (Array.isArray(items) && Array.isArray(this.__oldItems)) {
|
|
556
|
-
// Mutation using group.set('items')
|
|
557
|
-
const diff = calculateSplices(items, this.__oldItems);
|
|
558
|
-
diff.forEach((mutation) => {
|
|
559
|
-
this.__announceItemsChange(items, mutation);
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
this.__oldItems = items;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/** @private */
|
|
567
|
-
__announceItemsChange(items, mutation) {
|
|
568
|
-
const { addedCount, index, removed } = mutation;
|
|
569
|
-
let addedMsg = [];
|
|
570
|
-
let removedMsg = [];
|
|
571
|
-
if (addedCount) {
|
|
572
|
-
addedMsg = items
|
|
573
|
-
.slice(index, index + addedCount)
|
|
574
|
-
.map((user) => this.__getMessage(user, this.i18n.joined || '{user} joined'));
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
if (removed) {
|
|
578
|
-
removedMsg = removed.map((user) => this.__getMessage(user, this.i18n.left || '{user} left'));
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const messages = removedMsg.concat(addedMsg);
|
|
582
|
-
if (messages.length > 0) {
|
|
583
|
-
announce(messages.join(', '));
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
/** @private */
|
|
588
|
-
__i18nItemsChanged(i18n, items) {
|
|
589
|
-
const { base } = i18n;
|
|
590
|
-
if (base && base.activeUsers) {
|
|
591
|
-
const field = items === 1 ? 'one' : 'many';
|
|
592
|
-
if (base.activeUsers[field]) {
|
|
593
|
-
this.setAttribute('aria-label', base.activeUsers[field].replace('{count}', items || 0));
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
this._avatars.forEach((avatar) => {
|
|
597
|
-
avatar.i18n = base;
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/** @private */
|
|
603
|
-
__openedChanged(opened, wasOpened) {
|
|
604
|
-
if (opened) {
|
|
605
|
-
if (!this._menuElement) {
|
|
606
|
-
this._menuElement = this.$.overlay.querySelector('vaadin-avatar-group-menu');
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
this._openedWithFocusRing = this._overflow.hasAttribute('focus-ring');
|
|
610
|
-
|
|
611
|
-
this._menuElement.focus();
|
|
612
|
-
} else if (wasOpened) {
|
|
613
|
-
this._overflow.focus();
|
|
614
|
-
if (this._openedWithFocusRing) {
|
|
615
|
-
this._overflow.setAttribute('focus-ring', '');
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
this._overflow.setAttribute('aria-expanded', opened === true);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/** @private */
|
|
622
|
-
__overflowItemsChanged(items, oldItems) {
|
|
623
|
-
if (items || oldItems) {
|
|
624
|
-
this.$.overlay.requestContentUpdate();
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/** @private */
|
|
629
|
-
__setItemsInView() {
|
|
630
|
-
const avatars = this._avatars;
|
|
631
|
-
const items = this.items;
|
|
632
|
-
|
|
633
|
-
// Always show at least two avatars
|
|
634
|
-
if (!items || !avatars || avatars.length < 3) {
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
let result = this.__calculateAvatarsFitWidth();
|
|
639
|
-
|
|
640
|
-
// Only show overlay if two or more avatars don't fit
|
|
641
|
-
if (result === items.length - 1) {
|
|
642
|
-
result = items.length;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Close overlay if all avatars become visible
|
|
646
|
-
if (result >= items.length && this._opened) {
|
|
647
|
-
this.$.overlay.close();
|
|
648
|
-
// FIXME: hack to avoid jump before closing
|
|
649
|
-
this.$.overlay._flushAnimation('closing');
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Reserve space for overflow avatar
|
|
653
|
-
this.__itemsInView = result;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
/** @private */
|
|
657
|
-
__calculateAvatarsFitWidth() {
|
|
658
|
-
if (!this.shadowRoot || this._avatars.length < MINIMUM_DISPLAYED_AVATARS) {
|
|
659
|
-
return MINIMUM_DISPLAYED_AVATARS;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
const avatars = this._avatars;
|
|
663
|
-
|
|
664
|
-
// Assume all the avatars have the same width
|
|
665
|
-
const avatarWidth = avatars[0].clientWidth;
|
|
666
|
-
|
|
667
|
-
// Take negative margin into account
|
|
668
|
-
const { marginLeft, marginRight } = getComputedStyle(avatars[1]);
|
|
669
|
-
|
|
670
|
-
const offset = this.__isRTL
|
|
671
|
-
? parseInt(marginRight, 0) - parseInt(marginLeft, 0)
|
|
672
|
-
: parseInt(marginLeft, 0) - parseInt(marginRight, 0);
|
|
673
|
-
|
|
674
|
-
return Math.floor((this.$.container.offsetWidth - avatarWidth) / (avatarWidth + offset));
|
|
675
|
-
}
|
|
676
88
|
}
|
|
677
89
|
|
|
678
90
|
defineCustomElement(AvatarGroup);
|
|
@@ -0,0 +1,60 @@
|
|
|
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 { css, html, LitElement } from 'lit';
|
|
7
|
+
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
|
|
8
|
+
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
9
|
+
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
|
|
10
|
+
import { ItemMixin } from '@vaadin/item/src/vaadin-item-mixin.js';
|
|
11
|
+
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* An element used internally by `<vaadin-avatar-group>`. Not intended to be used separately.
|
|
15
|
+
*
|
|
16
|
+
* @customElement
|
|
17
|
+
* @extends HTMLElement
|
|
18
|
+
* @mixes DirMixin
|
|
19
|
+
* @mixes ItemMixin
|
|
20
|
+
* @mixes ThemableMixin
|
|
21
|
+
* @protected
|
|
22
|
+
*/
|
|
23
|
+
class AvatarGroupMenuItem extends ItemMixin(ThemableMixin(DirMixin(PolylitMixin(LitElement)))) {
|
|
24
|
+
static get is() {
|
|
25
|
+
return 'vaadin-avatar-group-menu-item';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static get styles() {
|
|
29
|
+
return css`
|
|
30
|
+
:host {
|
|
31
|
+
display: inline-block;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:host([hidden]) {
|
|
35
|
+
display: none !important;
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @protected */
|
|
41
|
+
render() {
|
|
42
|
+
return html`
|
|
43
|
+
<span part="checkmark" aria-hidden="true"></span>
|
|
44
|
+
<div part="content">
|
|
45
|
+
<slot></slot>
|
|
46
|
+
</div>
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @protected */
|
|
51
|
+
ready() {
|
|
52
|
+
super.ready();
|
|
53
|
+
|
|
54
|
+
this.setAttribute('role', 'menuitem');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
defineCustomElement(AvatarGroupMenuItem);
|
|
59
|
+
|
|
60
|
+
export { AvatarGroupMenuItem };
|