@vaadin/vaadin-avatar 21.0.1 → 22.0.0-alpha10
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 +26 -30
- package/src/vaadin-avatar-group.d.ts +13 -110
- package/src/vaadin-avatar-group.js +4 -596
- package/src/vaadin-avatar.d.ts +13 -69
- package/src/vaadin-avatar.js +4 -343
- package/theme/lumo/vaadin-avatar-group.js +1 -5
- package/theme/lumo/vaadin-avatar.js +1 -2
- package/theme/material/vaadin-avatar-group.js +1 -5
- package/theme/material/vaadin-avatar.js +1 -2
- package/vaadin-avatar-group.d.ts +0 -1
- package/src/interfaces.d.ts +0 -19
- package/src/vaadin-avatar-group-list-box.js +0 -20
- package/src/vaadin-avatar-group-overlay.js +0 -36
- package/src/vaadin-avatar-icons.js +0 -19
- package/theme/lumo/vaadin-avatar-group-styles.js +0 -102
- package/theme/lumo/vaadin-avatar-styles.js +0 -76
- package/theme/material/vaadin-avatar-group-styles.js +0 -52
- package/theme/material/vaadin-avatar-styles.js +0 -41
|
@@ -3,603 +3,11 @@
|
|
|
3
3
|
* Copyright (c) 2021 Vaadin Ltd.
|
|
4
4
|
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
|
|
8
|
-
import { calculateSplices } from '@polymer/polymer/lib/utils/array-splice.js';
|
|
9
|
-
import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class.js';
|
|
10
|
-
import { timeOut } from '@polymer/polymer/lib/utils/async.js';
|
|
11
|
-
import { Debouncer } from '@polymer/polymer/lib/utils/debounce.js';
|
|
12
|
-
import { IronA11yAnnouncer } from '@polymer/iron-a11y-announcer/iron-a11y-announcer.js';
|
|
13
|
-
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
14
|
-
import { IronResizableBehavior } from '@polymer/iron-resizable-behavior/iron-resizable-behavior.js';
|
|
15
|
-
import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js';
|
|
16
|
-
import '@vaadin/vaadin-item/src/vaadin-item.js';
|
|
17
|
-
import './vaadin-avatar-group-list-box.js';
|
|
18
|
-
import './vaadin-avatar-group-overlay.js';
|
|
19
|
-
import './vaadin-avatar.js';
|
|
20
|
-
|
|
21
|
-
const MINIMUM_DISPLAYED_AVATARS = 2;
|
|
6
|
+
import { AvatarGroup } from '@vaadin/avatar-group/src/vaadin-avatar-group.js';
|
|
22
7
|
|
|
23
8
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* To create the avatar group, first add the component to the page:
|
|
27
|
-
*
|
|
28
|
-
* ```
|
|
29
|
-
* <vaadin-avatar-group></vaadin-avatar-group>
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* And then use [`items`](#/elements/vaadin-avatar-group#property-items) property to initialize the structure:
|
|
33
|
-
*
|
|
34
|
-
* ```
|
|
35
|
-
* document.querySelector('vaadin-avatar-group').items = [
|
|
36
|
-
* {name: 'John Doe'},
|
|
37
|
-
* {abbr: 'AB'}
|
|
38
|
-
* ];
|
|
39
|
-
* ```
|
|
40
|
-
*
|
|
41
|
-
* ### Styling
|
|
42
|
-
*
|
|
43
|
-
* The following shadow DOM parts are exposed for styling:
|
|
44
|
-
*
|
|
45
|
-
* Part name | Description
|
|
46
|
-
* ----------- | ---------------
|
|
47
|
-
* `container` | The container element
|
|
48
|
-
* `avatar` | Individual avatars
|
|
49
|
-
*
|
|
50
|
-
* See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
|
|
51
|
-
*
|
|
52
|
-
* ### Internal components
|
|
53
|
-
*
|
|
54
|
-
* In addition to `<vaadin-avatar-group>` itself, the following internal
|
|
55
|
-
* components are themable:
|
|
56
|
-
*
|
|
57
|
-
* - `<vaadin-avatar-group-list-box>` - has the same API as [`<vaadin-list-box>`](#/elements/vaadin-list-box).
|
|
58
|
-
* - `<vaadin-avatar-group-overlay>` - has the same API as [`<vaadin-overlay>`](#/elements/vaadin-overlay).
|
|
59
|
-
*
|
|
60
|
-
* @extends HTMLElement
|
|
61
|
-
* @mixes ElementMixin
|
|
62
|
-
* @mixes ThemableMixin
|
|
9
|
+
* @deprecated Import `AvatarGroup` from `@vaadin/avatar-group` instead.
|
|
63
10
|
*/
|
|
64
|
-
|
|
65
|
-
static get template() {
|
|
66
|
-
return html`
|
|
67
|
-
<style>
|
|
68
|
-
:host {
|
|
69
|
-
display: block;
|
|
70
|
-
width: 100%; /* prevent collapsing inside non-stretching column flex */
|
|
71
|
-
--vaadin-avatar-group-overlap: 8px;
|
|
72
|
-
--vaadin-avatar-group-overlap-border: 2px;
|
|
73
|
-
--vaadin-avatar-size: 64px;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
:host([hidden]) {
|
|
77
|
-
display: none !important;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
[part='container'] {
|
|
81
|
-
display: flex;
|
|
82
|
-
position: relative;
|
|
83
|
-
width: 100%;
|
|
84
|
-
flex-wrap: nowrap;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
[part='avatar']:not(:first-child) {
|
|
88
|
-
-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>');
|
|
89
|
-
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>');
|
|
90
|
-
-webkit-mask-size: calc(
|
|
91
|
-
300% + var(--vaadin-avatar-group-overlap-border) * 6 - var(--vaadin-avatar-outline-width) * 6
|
|
92
|
-
);
|
|
93
|
-
mask-size: calc(
|
|
94
|
-
300% + var(--vaadin-avatar-group-overlap-border) * 6 - var(--vaadin-avatar-outline-width) * 6
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
[part='avatar']:not([dir='rtl']):not(:first-child) {
|
|
99
|
-
margin-left: calc(var(--vaadin-avatar-group-overlap) * -1 - var(--vaadin-avatar-outline-width));
|
|
100
|
-
-webkit-mask-position: calc(50% - var(--vaadin-avatar-size) + var(--vaadin-avatar-group-overlap));
|
|
101
|
-
mask-position: calc(50% - var(--vaadin-avatar-size) + var(--vaadin-avatar-group-overlap));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
[part='avatar'][dir='rtl']:not(:first-child) {
|
|
105
|
-
margin-right: calc(var(--vaadin-avatar-group-overlap) * -1);
|
|
106
|
-
-webkit-mask-position: calc(
|
|
107
|
-
50% + var(--vaadin-avatar-size) - var(--vaadin-avatar-group-overlap) + var(--vaadin-avatar-outline-width)
|
|
108
|
-
);
|
|
109
|
-
mask-position: calc(
|
|
110
|
-
50% + var(--vaadin-avatar-size) - var(--vaadin-avatar-group-overlap) + var(--vaadin-avatar-outline-width)
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
</style>
|
|
114
|
-
<div id="container" part="container">
|
|
115
|
-
<template id="items" is="dom-repeat" items="[[__computeItems(items.*, __itemsInView, maxItemsVisible)]]">
|
|
116
|
-
<vaadin-avatar
|
|
117
|
-
name="[[item.name]]"
|
|
118
|
-
abbr="[[item.abbr]]"
|
|
119
|
-
img="[[item.img]]"
|
|
120
|
-
part="avatar"
|
|
121
|
-
theme$="[[theme]]"
|
|
122
|
-
i18n="[[i18n]]"
|
|
123
|
-
color-index="[[item.colorIndex]]"
|
|
124
|
-
></vaadin-avatar>
|
|
125
|
-
</template>
|
|
126
|
-
<vaadin-avatar
|
|
127
|
-
id="overflow"
|
|
128
|
-
part="avatar"
|
|
129
|
-
hidden$="[[__computeMoreHidden(items.length, __itemsInView, __maxReached)]]"
|
|
130
|
-
abbr="[[__computeMore(items.length, __itemsInView, maxItemsVisible)]]"
|
|
131
|
-
theme$="[[theme]]"
|
|
132
|
-
on-click="_onOverflowClick"
|
|
133
|
-
on-keydown="_onOverflowKeyDown"
|
|
134
|
-
aria-haspopup="listbox"
|
|
135
|
-
></vaadin-avatar>
|
|
136
|
-
</div>
|
|
137
|
-
<vaadin-avatar-group-overlay id="overlay" opened="{{_opened}}" on-vaadin-overlay-close="_onVaadinOverlayClose">
|
|
138
|
-
<template>
|
|
139
|
-
<vaadin-avatar-group-list-box on-keydown="_onListKeyDown">
|
|
140
|
-
<template is="dom-repeat" items="[[__computeExtraItems(items.*, __itemsInView, maxItemsVisible)]]">
|
|
141
|
-
<vaadin-item theme="avatar-group-item" role="option">
|
|
142
|
-
<vaadin-avatar
|
|
143
|
-
name="[[item.name]]"
|
|
144
|
-
abbr="[[item.abbr]]"
|
|
145
|
-
img="[[item.img]]"
|
|
146
|
-
i18n="[[i18n]]"
|
|
147
|
-
part="avatar"
|
|
148
|
-
theme$="[[theme]]"
|
|
149
|
-
color-index="[[item.colorIndex]]"
|
|
150
|
-
tabindex="-1"
|
|
151
|
-
aria-hidden="true"
|
|
152
|
-
></vaadin-avatar>
|
|
153
|
-
[[item.name]]
|
|
154
|
-
</vaadin-item>
|
|
155
|
-
</template>
|
|
156
|
-
</vaadin-avatar-group-list-box>
|
|
157
|
-
</template>
|
|
158
|
-
</vaadin-avatar-group-overlay>
|
|
159
|
-
`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
static get is() {
|
|
163
|
-
return 'vaadin-avatar-group';
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
static get version() {
|
|
167
|
-
return '21.0.1';
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
static get properties() {
|
|
171
|
-
return {
|
|
172
|
-
/**
|
|
173
|
-
* An array containing the items which will be stamped as avatars.
|
|
174
|
-
*
|
|
175
|
-
* The items objects allow to configure [`name`](#/elements/vaadin-avatar#property-name),
|
|
176
|
-
* [`abbr`](#/elements/vaadin-avatar#property-abbr), [`img`](#/elements/vaadin-avatar#property-img)
|
|
177
|
-
* and [`colorIndex`](#/elements/vaadin-avatar#property-colorIndex) properties on the
|
|
178
|
-
* stamped avatars.
|
|
179
|
-
*
|
|
180
|
-
* #### Example
|
|
181
|
-
*
|
|
182
|
-
* ```js
|
|
183
|
-
* group.items = [
|
|
184
|
-
* {
|
|
185
|
-
* name: 'User name',
|
|
186
|
-
* img: 'url-to-image.png'
|
|
187
|
-
* },
|
|
188
|
-
* {
|
|
189
|
-
* abbr: 'JD',
|
|
190
|
-
* colorIndex: 1
|
|
191
|
-
* },
|
|
192
|
-
* ];
|
|
193
|
-
* ```
|
|
194
|
-
*
|
|
195
|
-
* @type {!Array<!AvatarGroupItem> | undefined}
|
|
196
|
-
*/
|
|
197
|
-
items: {
|
|
198
|
-
type: Array
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* The maximum number of avatars to display. By default, all the avatars are displayed.
|
|
203
|
-
* When _maxItemsVisible_ is set, the overflowing avatars are grouped into one avatar with
|
|
204
|
-
* a dropdown. Setting 0 or 1 has no effect so there are always at least two avatars visible.
|
|
205
|
-
*/
|
|
206
|
-
maxItemsVisible: {
|
|
207
|
-
type: Number
|
|
208
|
-
},
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* The object used to localize this component.
|
|
212
|
-
* To change the default localization, replace the entire
|
|
213
|
-
* _i18n_ object or just the property you want to modify.
|
|
214
|
-
*
|
|
215
|
-
* The object has the following JSON structure and default values:
|
|
216
|
-
* ```
|
|
217
|
-
* {
|
|
218
|
-
* // Translation of the anonymous user avatar title.
|
|
219
|
-
* anonymous: 'anonymous',
|
|
220
|
-
* // Translation of the avatar group accessible label.
|
|
221
|
-
* // {count} is replaced with the actual count of users.
|
|
222
|
-
* activeUsers: {
|
|
223
|
-
* one: 'Currently one active user',
|
|
224
|
-
* many: 'Currently {count} active users'
|
|
225
|
-
* },
|
|
226
|
-
* // Screen reader announcement when user joins group.
|
|
227
|
-
* // {user} is replaced with the name or abbreviation.
|
|
228
|
-
* // When neither is set, "anonymous" is used instead.
|
|
229
|
-
* joined: '{user} joined',
|
|
230
|
-
* // Screen reader announcement when user leaves group.
|
|
231
|
-
* // {user} is replaced with the name or abbreviation.
|
|
232
|
-
* // When neither is set, "anonymous" is used instead.
|
|
233
|
-
* left: '{user} left'
|
|
234
|
-
* }
|
|
235
|
-
* ```
|
|
236
|
-
* @type {!AvatarGroupI18n}
|
|
237
|
-
* @default {English/US}
|
|
238
|
-
*/
|
|
239
|
-
i18n: {
|
|
240
|
-
type: Object,
|
|
241
|
-
value: () => {
|
|
242
|
-
return {
|
|
243
|
-
anonymous: 'anonymous',
|
|
244
|
-
activeUsers: {
|
|
245
|
-
one: 'Currently one active user',
|
|
246
|
-
many: 'Currently {count} active users'
|
|
247
|
-
},
|
|
248
|
-
joined: '{user} joined',
|
|
249
|
-
left: '{user} left'
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
|
|
254
|
-
/** @private */
|
|
255
|
-
__maxReached: {
|
|
256
|
-
type: Boolean,
|
|
257
|
-
computed: '__computeMaxReached(items.length, maxItemsVisible)'
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
/** @private */
|
|
261
|
-
__itemsInView: {
|
|
262
|
-
type: Number,
|
|
263
|
-
value: null
|
|
264
|
-
},
|
|
265
|
-
|
|
266
|
-
/** @private */
|
|
267
|
-
_opened: {
|
|
268
|
-
type: Boolean,
|
|
269
|
-
observer: '__openedChanged',
|
|
270
|
-
value: false
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
static get observers() {
|
|
276
|
-
return [
|
|
277
|
-
'__computeMoreTitle(items.length, __itemsInView, maxItemsVisible)',
|
|
278
|
-
'__itemsChanged(items.splices, items.*)',
|
|
279
|
-
'__i18nItemsChanged(i18n.*, items.length)'
|
|
280
|
-
];
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/** @protected */
|
|
284
|
-
ready() {
|
|
285
|
-
super.ready();
|
|
286
|
-
|
|
287
|
-
IronA11yAnnouncer.requestAvailability();
|
|
288
|
-
|
|
289
|
-
this.__boundSetPosition = this.__setPosition.bind(this);
|
|
290
|
-
|
|
291
|
-
this.addEventListener('iron-resize', this._onResize.bind(this));
|
|
292
|
-
|
|
293
|
-
this._overlayElement = this.shadowRoot.querySelector('vaadin-avatar-group-overlay');
|
|
294
|
-
|
|
295
|
-
afterNextRender(this, () => {
|
|
296
|
-
this.__setItemsInView();
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* @param {string} name
|
|
302
|
-
* @param {?string} oldValue
|
|
303
|
-
* @param {?string} newValue
|
|
304
|
-
* @protected
|
|
305
|
-
*/
|
|
306
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
307
|
-
super.attributeChangedCallback(name, oldValue, newValue);
|
|
308
|
-
if (name === 'dir') {
|
|
309
|
-
this.__setPosition();
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* @return {!Array<!HTMLElement>}
|
|
315
|
-
* @protected
|
|
316
|
-
*/
|
|
317
|
-
get _avatars() {
|
|
318
|
-
return this.shadowRoot.querySelectorAll('vaadin-avatar');
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/** @private */
|
|
322
|
-
__announce(text) {
|
|
323
|
-
this.dispatchEvent(
|
|
324
|
-
new CustomEvent('iron-announce', {
|
|
325
|
-
bubbles: true,
|
|
326
|
-
composed: true,
|
|
327
|
-
detail: {
|
|
328
|
-
text
|
|
329
|
-
}
|
|
330
|
-
})
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/** @private */
|
|
335
|
-
__getMessage(user, action) {
|
|
336
|
-
return action.replace('{user}', user.name || user.abbr || this.i18n.anonymous);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/** @private */
|
|
340
|
-
_onOverflowClick(e) {
|
|
341
|
-
e.stopPropagation();
|
|
342
|
-
if (this._opened) {
|
|
343
|
-
this.$.overlay.close();
|
|
344
|
-
} else if (!e.defaultPrevented) {
|
|
345
|
-
this._opened = true;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/** @private */
|
|
350
|
-
_onOverflowKeyDown(e) {
|
|
351
|
-
if (!this._opened) {
|
|
352
|
-
if (/^(Enter|SpaceBar|\s)$/.test(e.key)) {
|
|
353
|
-
e.preventDefault();
|
|
354
|
-
this._opened = true;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/** @private */
|
|
360
|
-
_onListKeyDown(event) {
|
|
361
|
-
if (event.key === 'Escape' || event.key === 'Esc' || /^(Tab)$/.test(event.key)) {
|
|
362
|
-
this._opened = false;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/** @private */
|
|
367
|
-
_onResize() {
|
|
368
|
-
this.__debounceResize = Debouncer.debounce(this.__debounceResize, timeOut.after(0), () => {
|
|
369
|
-
this.__setItemsInView();
|
|
370
|
-
this.__setPosition();
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/** @private */
|
|
375
|
-
_onVaadinOverlayClose(e) {
|
|
376
|
-
if (e.detail.sourceEvent && e.detail.sourceEvent.composedPath().indexOf(this) !== -1) {
|
|
377
|
-
e.preventDefault();
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/** @private */
|
|
382
|
-
__computeItems(arr, itemsInView, maxItemsVisible) {
|
|
383
|
-
const items = arr.base || [];
|
|
384
|
-
const limit = this.__getLimit(items.length, itemsInView, maxItemsVisible);
|
|
385
|
-
return limit ? items.slice(0, limit) : items;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/** @private */
|
|
389
|
-
__computeExtraItems(arr, itemsInView, maxItemsVisible) {
|
|
390
|
-
const items = arr.base || [];
|
|
391
|
-
const limit = this.__getLimit(items.length, itemsInView, maxItemsVisible);
|
|
392
|
-
return limit ? items.slice(limit) : items;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/** @private */
|
|
396
|
-
__computeMaxReached(items, maxItemsVisible) {
|
|
397
|
-
return maxItemsVisible != null && items > this.__getMax(maxItemsVisible);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/** @private */
|
|
401
|
-
__computeMore(items, itemsInView, maxItemsVisible) {
|
|
402
|
-
return `+${items - this.__getLimit(items, itemsInView, maxItemsVisible)}`;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/** @private */
|
|
406
|
-
__computeMoreHidden(items, itemsInView, maxReached) {
|
|
407
|
-
return !maxReached && !(itemsInView && itemsInView < items);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/** @private */
|
|
411
|
-
__computeMoreTitle(items, itemsInView, maxItemsVisible) {
|
|
412
|
-
const limit = this.__getLimit(items, itemsInView, maxItemsVisible);
|
|
413
|
-
if (limit == null) {
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
const result = [];
|
|
417
|
-
for (let i = limit; i < items; i++) {
|
|
418
|
-
const item = this.items[i];
|
|
419
|
-
result.push(item.name || item.abbr || 'anonymous');
|
|
420
|
-
}
|
|
421
|
-
// override generated title attribute
|
|
422
|
-
this.$.overflow.setAttribute('title', result.join('\n'));
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/** @private */
|
|
426
|
-
__getLimit(items, itemsInView, maxItemsVisible) {
|
|
427
|
-
let limit = null;
|
|
428
|
-
// handle max set to 0 or 1
|
|
429
|
-
const adjustedMax = this.__getMax(maxItemsVisible);
|
|
430
|
-
if (maxItemsVisible != null && adjustedMax < items) {
|
|
431
|
-
limit = adjustedMax - 1;
|
|
432
|
-
} else if (itemsInView && itemsInView < items) {
|
|
433
|
-
limit = itemsInView;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return Math.min(limit, this.__calculateAvatarsFitWidth());
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/** @private */
|
|
440
|
-
__getMax(maxItemsVisible) {
|
|
441
|
-
return Math.max(maxItemsVisible, MINIMUM_DISPLAYED_AVATARS);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/** @private */
|
|
445
|
-
__itemsChanged(splices, itemsChange) {
|
|
446
|
-
const items = itemsChange.base;
|
|
447
|
-
this.$.items.render();
|
|
448
|
-
this.__setItemsInView();
|
|
449
|
-
|
|
450
|
-
// mutation using group.splice('items')
|
|
451
|
-
if (splices && Array.isArray(splices.indexSplices)) {
|
|
452
|
-
splices.indexSplices.forEach((mutation) => {
|
|
453
|
-
this.__announceItemsChange(items, mutation);
|
|
454
|
-
});
|
|
455
|
-
} else if (Array.isArray(items) && Array.isArray(this.__oldItems)) {
|
|
456
|
-
// mutation using group.set('items')
|
|
457
|
-
const diff = calculateSplices(items, this.__oldItems);
|
|
458
|
-
diff.forEach((mutation) => {
|
|
459
|
-
this.__announceItemsChange(items, mutation);
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
this.__oldItems = items;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/** @private */
|
|
467
|
-
__announceItemsChange(items, mutation) {
|
|
468
|
-
const { addedCount, index, removed } = mutation;
|
|
469
|
-
let addedMsg = [];
|
|
470
|
-
let removedMsg = [];
|
|
471
|
-
if (addedCount) {
|
|
472
|
-
addedMsg = items
|
|
473
|
-
.slice(index, index + addedCount)
|
|
474
|
-
.map((user) => this.__getMessage(user, this.i18n.joined || '{user} joined'));
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (removed) {
|
|
478
|
-
removedMsg = removed.map((user) => this.__getMessage(user, this.i18n.left || '{user} left'));
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const messages = removedMsg.concat(addedMsg);
|
|
482
|
-
if (messages.length > 0) {
|
|
483
|
-
this.__announce(messages.join(', '));
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/** @private */
|
|
488
|
-
__i18nItemsChanged(i18n, items) {
|
|
489
|
-
const { base } = i18n;
|
|
490
|
-
if (base && base.activeUsers) {
|
|
491
|
-
const field = items === 1 ? 'one' : 'many';
|
|
492
|
-
if (base.activeUsers[field]) {
|
|
493
|
-
this.setAttribute('aria-label', base.activeUsers[field].replace('{count}', items || 0));
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/** @private */
|
|
499
|
-
__openedChanged(opened, wasOpened) {
|
|
500
|
-
if (opened) {
|
|
501
|
-
if (!this._menuElement) {
|
|
502
|
-
this._menuElement = this._overlayElement.content.querySelector('vaadin-avatar-group-list-box');
|
|
503
|
-
this._menuElement.setAttribute('role', 'listbox');
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
this._openedWithFocusRing = this.$.overflow.hasAttribute('focus-ring');
|
|
507
|
-
|
|
508
|
-
const avatars = this._menuElement.querySelectorAll('vaadin-avatar');
|
|
509
|
-
avatars.forEach((avatar) => avatar.removeAttribute('title'));
|
|
510
|
-
|
|
511
|
-
this._menuElement.focus();
|
|
512
|
-
this.__setPosition();
|
|
513
|
-
window.addEventListener('scroll', this.__boundSetPosition, true);
|
|
514
|
-
} else if (wasOpened) {
|
|
515
|
-
this.$.overflow.focus();
|
|
516
|
-
if (this._openedWithFocusRing) {
|
|
517
|
-
this.$.overflow.setAttribute('focus-ring', '');
|
|
518
|
-
}
|
|
519
|
-
window.removeEventListener('scroll', this.__boundSetPosition, true);
|
|
520
|
-
}
|
|
521
|
-
this.$.overflow.setAttribute('aria-expanded', opened === true);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/** @private */
|
|
525
|
-
__setItemsInView() {
|
|
526
|
-
const avatars = this._avatars;
|
|
527
|
-
const items = this.items;
|
|
528
|
-
|
|
529
|
-
// always show at least two avatars
|
|
530
|
-
if (!items || !avatars || avatars.length < 3) {
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
let result = this.__calculateAvatarsFitWidth();
|
|
535
|
-
|
|
536
|
-
// only show overlay if two or more avatars don't fit
|
|
537
|
-
if (result === items.length - 1) {
|
|
538
|
-
result = items.length;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// close overlay if all avatars become visible
|
|
542
|
-
if (result >= items.length && this._opened) {
|
|
543
|
-
this.$.overlay.close();
|
|
544
|
-
// FIXME: hack to avoid jump before closing
|
|
545
|
-
this.$.overlay._flushAnimation('closing');
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// reserve space for overflow avatar
|
|
549
|
-
this.__itemsInView = result;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/** @private **/
|
|
553
|
-
__calculateAvatarsFitWidth() {
|
|
554
|
-
if (!this.shadowRoot || this._avatars.length < MINIMUM_DISPLAYED_AVATARS) {
|
|
555
|
-
return MINIMUM_DISPLAYED_AVATARS;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const avatars = this._avatars;
|
|
559
|
-
|
|
560
|
-
// assume all the avatars have the same width
|
|
561
|
-
const avatarWidth = avatars[0].clientWidth;
|
|
562
|
-
|
|
563
|
-
// take negative margin into account
|
|
564
|
-
const { marginLeft, marginRight } = getComputedStyle(avatars[1]);
|
|
565
|
-
|
|
566
|
-
const offset =
|
|
567
|
-
this.getAttribute('dir') == 'rtl'
|
|
568
|
-
? parseInt(marginRight, 0) - parseInt(marginLeft, 0)
|
|
569
|
-
: parseInt(marginLeft, 0) - parseInt(marginRight, 0);
|
|
570
|
-
|
|
571
|
-
return Math.floor((this.$.container.offsetWidth - avatarWidth) / (avatarWidth + offset));
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/** @private */
|
|
575
|
-
__setPosition() {
|
|
576
|
-
if (!this._opened) {
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
const btnRect = this.$.overflow.getBoundingClientRect();
|
|
580
|
-
const viewportHeight = Math.min(window.innerHeight, document.documentElement.clientHeight);
|
|
581
|
-
const bottomAlign = btnRect.top > (viewportHeight - btnRect.height) / 2;
|
|
582
|
-
|
|
583
|
-
const isRtl = this.getAttribute('dir') === 'rtl';
|
|
584
|
-
|
|
585
|
-
if (isRtl) {
|
|
586
|
-
this._overlayElement.style.right = document.documentElement.clientWidth - btnRect.right + 'px';
|
|
587
|
-
} else {
|
|
588
|
-
this._overlayElement.style.left = btnRect.left + 'px';
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (bottomAlign) {
|
|
592
|
-
this._overlayElement.setAttribute('bottom-aligned', '');
|
|
593
|
-
this._overlayElement.style.removeProperty('top');
|
|
594
|
-
this._overlayElement.style.bottom = viewportHeight - btnRect.top + 'px';
|
|
595
|
-
} else {
|
|
596
|
-
this._overlayElement.removeAttribute('bottom-aligned');
|
|
597
|
-
this._overlayElement.style.removeProperty('bottom');
|
|
598
|
-
this._overlayElement.style.top = btnRect.bottom + 'px';
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
customElements.define(AvatarGroupElement.is, AvatarGroupElement);
|
|
11
|
+
export const AvatarGroupElement = AvatarGroup;
|
|
604
12
|
|
|
605
|
-
export
|
|
13
|
+
export * from '@vaadin/avatar-group/src/vaadin-avatar-group.js';
|
package/src/vaadin-avatar.d.ts
CHANGED
|
@@ -1,74 +1,18 @@
|
|
|
1
|
-
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
2
|
-
|
|
3
|
-
import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js';
|
|
4
|
-
|
|
5
|
-
import { AvatarI18n } from './interfaces';
|
|
6
|
-
|
|
7
1
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* <vaadin-avatar img="avatars/avatar-1.jpg"></vaadin-avatar>
|
|
12
|
-
* ```
|
|
13
|
-
*
|
|
14
|
-
* ### Styling
|
|
15
|
-
*
|
|
16
|
-
* The following shadow DOM parts are exposed for styling:
|
|
17
|
-
*
|
|
18
|
-
* Part name | Description
|
|
19
|
-
* --------- | ---------------
|
|
20
|
-
* `abbr` | The abbreviation element
|
|
21
|
-
* `icon` | The icon element
|
|
22
|
-
*
|
|
23
|
-
* The following attributes are exposed for styling:
|
|
24
|
-
*
|
|
25
|
-
* Attribute | Description
|
|
26
|
-
* --------- | -----------
|
|
27
|
-
* `has-color-index` | Set when the avatar has `colorIndex` and the corresponding custom CSS property exists.
|
|
28
|
-
*
|
|
29
|
-
* See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
30
5
|
*/
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The path to the image
|
|
34
|
-
*/
|
|
35
|
-
img: string | null | undefined;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* A shortened form of name that is displayed
|
|
39
|
-
* in the avatar when `img` is not provided.
|
|
40
|
-
*/
|
|
41
|
-
abbr: string | null | undefined;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Full name of the user
|
|
45
|
-
* used for the title of the avatar.
|
|
46
|
-
*/
|
|
47
|
-
name: string | null | undefined;
|
|
6
|
+
import { Avatar } from '@vaadin/avatar/src/vaadin-avatar.js';
|
|
48
7
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* The object used to localize this component.
|
|
56
|
-
* To change the default localization, replace the entire
|
|
57
|
-
* _i18n_ object or just the property you want to modify.
|
|
58
|
-
*
|
|
59
|
-
* The object has the following JSON structure and default values:
|
|
60
|
-
* {
|
|
61
|
-
* // Translation of the anonymous user avatar title.
|
|
62
|
-
* anonymous: 'anonymous'
|
|
63
|
-
* }
|
|
64
|
-
*/
|
|
65
|
-
i18n: AvatarI18n;
|
|
66
|
-
}
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Import `Avatar` from `@vaadin/avatar` instead.
|
|
10
|
+
*/
|
|
11
|
+
export type AvatarElement = Avatar;
|
|
67
12
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Import `Avatar` from `@vaadin/avatar` instead.
|
|
15
|
+
*/
|
|
16
|
+
export const AvatarElement: typeof Avatar;
|
|
73
17
|
|
|
74
|
-
export
|
|
18
|
+
export * from '@vaadin/avatar/src/vaadin-avatar.js';
|