New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@vaadin/vaadin-avatar

Package Overview
Dependencies
Maintainers
19
Versions
247
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vaadin/vaadin-avatar - npm Package Compare versions

Comparing version 22.0.0-alpha1 to 22.0.0-alpha10

56

package.json
{
"name": "@vaadin/vaadin-avatar",
"version": "22.0.0-alpha1",
"version": "22.0.0-alpha10",
"publishConfig": {
"access": "public"
},
"description": "vaadin-avatar",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/vaadin/web-components.git",
"directory": "packages/vaadin-avatar"
},
"author": "Vaadin Ltd",
"homepage": "https://vaadin.com/components",
"bugs": {
"url": "https://github.com/vaadin/vaadin-avatar/issues"
},
"main": "vaadin-avatar-group.js",
"module": "vaadin-avatar-group.js",
"repository": "vaadin/vaadin-avatar",
"files": [
"src",
"theme",
"vaadin-*.d.ts",
"vaadin-*.js"
],
"keywords": [

@@ -15,35 +34,12 @@ "Vaadin",

],
"author": "Vaadin Ltd",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/vaadin/vaadin-avatar/issues"
},
"homepage": "https://vaadin.com/components",
"files": [
"vaadin-*.d.ts",
"vaadin-*.js",
"src",
"theme"
],
"dependencies": {
"@polymer/iron-a11y-announcer": "^3.0.0",
"@polymer/iron-resizable-behavior": "^3.0.0",
"@polymer/polymer": "^3.0.0",
"@vaadin/vaadin-element-mixin": "^22.0.0-alpha1",
"@vaadin/vaadin-item": "^22.0.0-alpha1",
"@vaadin/vaadin-list-box": "^22.0.0-alpha1",
"@vaadin/vaadin-lumo-styles": "^22.0.0-alpha1",
"@vaadin/vaadin-material-styles": "^22.0.0-alpha1",
"@vaadin/vaadin-overlay": "^22.0.0-alpha1",
"@vaadin/vaadin-themable-mixin": "^22.0.0-alpha1"
"@vaadin/avatar": "22.0.0-alpha10",
"@vaadin/avatar-group": "22.0.0-alpha10"
},
"devDependencies": {
"@esm-bundle/chai": "^4.1.5",
"@vaadin/testing-helpers": "^0.2.1",
"@esm-bundle/chai": "^4.3.4",
"@vaadin/testing-helpers": "^0.3.0",
"sinon": "^9.2.1"
},
"publishConfig": {
"access": "public"
},
"gitHead": "c9694d6549bff1f7fffb9ece26178e57fc228a51"
"gitHead": "6d3055383b9c3c8306ea34c6f2e2087e63c49348"
}

@@ -10,6 +10,3 @@ # <vaadin-avatar>

[![npm version](https://badgen.net/npm/v/@vaadin/vaadin-avatar)](https://www.npmjs.com/package/@vaadin/vaadin-avatar)
[![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/vaadin/vaadin-avatar)
[![Build Status](https://travis-ci.org/vaadin/vaadin-avatar.svg?branch=master)](https://travis-ci.org/vaadin/vaadin-avatar)
[![Published on Vaadin Directory](https://img.shields.io/badge/Vaadin%20Directory-published-00b4f0.svg)](https://vaadin.com/directory/component/vaadinvaadin-avatar)
[![Stars on vaadin.com/directory](https://img.shields.io/vaadin-directory/star/vaadin-avatar-directory-urlidentifier.svg)](https://vaadin.com/directory/component/vaadinvaadin-avatar)
[![Discord](https://img.shields.io/discord/732335336448852018?label=discord)](https://discord.gg/PHmkCKC)

@@ -24,5 +21,5 @@

document.querySelector('vaadin-avatar-group').items = [
{name: 'Foo Bar', colorIndex: 1},
{colorIndex: 2},
{name: 'Foo Bar', colorIndex: 3}
{ name: 'Foo Bar', colorIndex: 1 },
{ colorIndex: 2 },
{ name: 'Foo Bar', colorIndex: 3 }
];

@@ -34,3 +31,2 @@ </script>

## Installation

@@ -74,43 +70,6 @@

## Running API docs and tests in a browser
1. Fork the `vaadin-avatar` repository and clone it locally.
1. Make sure you have [node.js](https://nodejs.org/) 12.x installed.
1. Make sure you have [npm](https://www.npmjs.com/) installed.
1. When in the `vaadin-avatar` directory, run `npm install` to install dependencies.
1. Run `npm start`, browser will automatically open the component API documentation.
1. You can also open visual tests, for example:
- http://127.0.0.1:3000/test/visual/default.html
## Running tests from the command line
1. When in the `vaadin-avatar` directory, run `npm test`
## Debugging tests in the browser
1. Run `npm run debug`, then choose manual mode (M) and open the link in browser.
## Following the coding style
We are using [ESLint](http://eslint.org/) for linting JavaScript code. You can check if your code is following our standards by running `npm run lint`, which will automatically lint all `.js` files.
## Big Thanks
Cross-browser Testing Platform and Open Source <3 Provided by [Sauce Labs](https://saucelabs.com).
## Contributing
To contribute to the component, please read [the guideline](https://github.com/vaadin/vaadin-core/blob/master/CONTRIBUTING.md) first.
Read the [contributing guide](https://vaadin.com/docs/latest/guide/contributing/overview) to learn about our development process, how to propose bugfixes and improvements, and how to test your changes to Vaadin components.
## License

@@ -117,0 +76,0 @@

@@ -1,115 +0,18 @@

import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
/**
* @license
* Copyright (c) 2021 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { AvatarGroup } from '@vaadin/avatar-group/src/vaadin-avatar-group.js';
import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js';
/**
* @deprecated Import `AvatarGroup` from `@vaadin/avatar-group` instead.
*/
export type AvatarGroupElement = AvatarGroup;
import { AvatarGroupItem, AvatarGroupI18n } from './interfaces';
/**
* `<vaadin-avatar-group>` is a Web Component providing avatar group displaying functionality.
*
* To create the avatar group, first add the component to the page:
*
* ```
* <vaadin-avatar-group></vaadin-avatar-group>
* ```
*
* And then use [`items`](#/elements/vaadin-avatar-group#property-items) property to initialize the structure:
*
* ```
* document.querySelector('vaadin-avatar-group').items = [
* {name: 'John Doe'},
* {abbr: 'AB'}
* ];
* ```
*
* ### Styling
*
* The following shadow DOM parts are exposed for styling:
*
* Part name | Description
* ----------- | ---------------
* `container` | The container element
* `avatar` | Individual avatars
*
* See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
*
* ### Internal components
*
* In addition to `<vaadin-avatar-group>` itself, the following internal
* components are themable:
*
* - `<vaadin-avatar-group-list-box>` - has the same API as [`<vaadin-list-box>`](#/elements/vaadin-list-box).
* - `<vaadin-avatar-group-overlay>` - has the same API as [`<vaadin-overlay>`](#/elements/vaadin-overlay).
* @deprecated Import `AvatarGroup` from `@vaadin/avatar-group` instead.
*/
declare class AvatarGroupElement extends ElementMixin(ThemableMixin(HTMLElement)) {
readonly _avatars: HTMLElement[];
export const AvatarGroupElement: typeof AvatarGroup;
/**
* An array containing the items which will be stamped as avatars.
*
* The items objects allow to configure [`name`](#/elements/vaadin-avatar#property-name),
* [`abbr`](#/elements/vaadin-avatar#property-abbr), [`img`](#/elements/vaadin-avatar#property-img)
* and [`colorIndex`](#/elements/vaadin-avatar#property-colorIndex) properties on the
* stamped avatars.
*
* #### Example
*
* ```js
* group.items = [
* {
* name: 'User name',
* img: 'url-to-image.png'
* },
* {
* abbr: 'JD',
* colorIndex: 1
* },
* ];
* ```
*/
items: AvatarGroupItem[] | undefined;
/**
* The maximum number of avatars to display. By default, all the avatars are displayed.
* When _maxItemsVisible_ is set, the overflowing avatars are grouped into one avatar with
* a dropdown. Setting 0 or 1 has no effect so there are always at least two avatars visible.
*/
maxItemsVisible: number | null | undefined;
/**
* The object used to localize this component.
* To change the default localization, replace the entire
* _i18n_ object or just the property you want to modify.
*
* The object has the following JSON structure and default values:
* ```
* {
* // Translation of the anonymous user avatar title.
* anonymous: 'anonymous',
* // Translation of the avatar group accessible label.
* // {count} is replaced with the actual count of users.
* activeUsers: {
* one: 'Currently one active user',
* many: 'Currently {count} active users'
* },
* // Screen reader announcement when user joins group.
* // {user} is replaced with the name or abbreviation.
* // When neither is set, "anonymous" is used instead.
* joined: '{user} joined',
* // Screen reader announcement when user leaves group.
* // {user} is replaced with the name or abbreviation.
* // When neither is set, "anonymous" is used instead.
* left: '{user} left'
* }
* ```
*/
i18n: AvatarGroupI18n;
}
declare global {
interface HTMLElementTagNameMap {
'vaadin-avatar-group': AvatarGroupElement;
}
}
export { AvatarGroupElement };
export * from '@vaadin/avatar-group/src/vaadin-avatar-group.js';

@@ -6,601 +6,9 @@ /**

*/
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
import { calculateSplices } from '@polymer/polymer/lib/utils/array-splice.js';
import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class.js';
import { timeOut } from '@polymer/polymer/lib/utils/async.js';
import { Debouncer } from '@polymer/polymer/lib/utils/debounce.js';
import { IronA11yAnnouncer } from '@polymer/iron-a11y-announcer/iron-a11y-announcer.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
import { IronResizableBehavior } from '@polymer/iron-resizable-behavior/iron-resizable-behavior.js';
import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js';
import '@vaadin/vaadin-item/src/vaadin-item.js';
import './vaadin-avatar-group-list-box.js';
import './vaadin-avatar-group-overlay.js';
import './vaadin-avatar.js';
import { AvatarGroup } from '@vaadin/avatar-group/src/vaadin-avatar-group.js';
const MINIMUM_DISPLAYED_AVATARS = 2;
/**
* `<vaadin-avatar-group>` is a Web Component providing avatar group displaying functionality.
*
* To create the avatar group, first add the component to the page:
*
* ```
* <vaadin-avatar-group></vaadin-avatar-group>
* ```
*
* And then use [`items`](#/elements/vaadin-avatar-group#property-items) property to initialize the structure:
*
* ```
* document.querySelector('vaadin-avatar-group').items = [
* {name: 'John Doe'},
* {abbr: 'AB'}
* ];
* ```
*
* ### Styling
*
* The following shadow DOM parts are exposed for styling:
*
* Part name | Description
* ----------- | ---------------
* `container` | The container element
* `avatar` | Individual avatars
*
* See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
*
* ### Internal components
*
* In addition to `<vaadin-avatar-group>` itself, the following internal
* components are themable:
*
* - `<vaadin-avatar-group-list-box>` - has the same API as [`<vaadin-list-box>`](#/elements/vaadin-list-box).
* - `<vaadin-avatar-group-overlay>` - has the same API as [`<vaadin-overlay>`](#/elements/vaadin-overlay).
*
* @extends HTMLElement
* @mixes ElementMixin
* @mixes ThemableMixin
* @deprecated Import `AvatarGroup` from `@vaadin/avatar-group` instead.
*/
class AvatarGroupElement extends ElementMixin(ThemableMixin(mixinBehaviors([IronResizableBehavior], PolymerElement))) {
static get template() {
return html`
<style>
:host {
display: block;
width: 100%; /* prevent collapsing inside non-stretching column flex */
--vaadin-avatar-group-overlap: 8px;
--vaadin-avatar-group-overlap-border: 2px;
--vaadin-avatar-size: 64px;
}
export const AvatarGroupElement = AvatarGroup;
:host([hidden]) {
display: none !important;
}
[part='container'] {
display: flex;
position: relative;
width: 100%;
flex-wrap: nowrap;
}
[part='avatar']:not(:first-child) {
-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>');
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>');
-webkit-mask-size: calc(
300% + var(--vaadin-avatar-group-overlap-border) * 6 - var(--vaadin-avatar-outline-width) * 6
);
mask-size: calc(
300% + var(--vaadin-avatar-group-overlap-border) * 6 - var(--vaadin-avatar-outline-width) * 6
);
}
[part='avatar']:not([dir='rtl']):not(:first-child) {
margin-left: calc(var(--vaadin-avatar-group-overlap) * -1 - var(--vaadin-avatar-outline-width));
-webkit-mask-position: calc(50% - var(--vaadin-avatar-size) + var(--vaadin-avatar-group-overlap));
mask-position: calc(50% - var(--vaadin-avatar-size) + var(--vaadin-avatar-group-overlap));
}
[part='avatar'][dir='rtl']:not(:first-child) {
margin-right: calc(var(--vaadin-avatar-group-overlap) * -1);
-webkit-mask-position: calc(
50% + var(--vaadin-avatar-size) - var(--vaadin-avatar-group-overlap) + var(--vaadin-avatar-outline-width)
);
mask-position: calc(
50% + var(--vaadin-avatar-size) - var(--vaadin-avatar-group-overlap) + var(--vaadin-avatar-outline-width)
);
}
</style>
<div id="container" part="container">
<template id="items" is="dom-repeat" items="[[__computeItems(items.*, __itemsInView, maxItemsVisible)]]">
<vaadin-avatar
name="[[item.name]]"
abbr="[[item.abbr]]"
img="[[item.img]]"
part="avatar"
theme$="[[theme]]"
i18n="[[i18n]]"
color-index="[[item.colorIndex]]"
></vaadin-avatar>
</template>
<vaadin-avatar
id="overflow"
part="avatar"
hidden$="[[__computeMoreHidden(items.length, __itemsInView, __maxReached)]]"
abbr="[[__computeMore(items.length, __itemsInView, maxItemsVisible)]]"
theme$="[[theme]]"
on-click="_onOverflowClick"
on-keydown="_onOverflowKeyDown"
aria-haspopup="listbox"
></vaadin-avatar>
</div>
<vaadin-avatar-group-overlay id="overlay" opened="{{_opened}}" on-vaadin-overlay-close="_onVaadinOverlayClose">
<template>
<vaadin-avatar-group-list-box on-keydown="_onListKeyDown">
<template is="dom-repeat" items="[[__computeExtraItems(items.*, __itemsInView, maxItemsVisible)]]">
<vaadin-item theme="avatar-group-item" role="option">
<vaadin-avatar
name="[[item.name]]"
abbr="[[item.abbr]]"
img="[[item.img]]"
i18n="[[i18n]]"
part="avatar"
theme$="[[theme]]"
color-index="[[item.colorIndex]]"
tabindex="-1"
aria-hidden="true"
></vaadin-avatar>
[[item.name]]
</vaadin-item>
</template>
</vaadin-avatar-group-list-box>
</template>
</vaadin-avatar-group-overlay>
`;
}
static get is() {
return 'vaadin-avatar-group';
}
static get version() {
return '22.0.0-alpha1';
}
static get properties() {
return {
/**
* An array containing the items which will be stamped as avatars.
*
* The items objects allow to configure [`name`](#/elements/vaadin-avatar#property-name),
* [`abbr`](#/elements/vaadin-avatar#property-abbr), [`img`](#/elements/vaadin-avatar#property-img)
* and [`colorIndex`](#/elements/vaadin-avatar#property-colorIndex) properties on the
* stamped avatars.
*
* #### Example
*
* ```js
* group.items = [
* {
* name: 'User name',
* img: 'url-to-image.png'
* },
* {
* abbr: 'JD',
* colorIndex: 1
* },
* ];
* ```
*
* @type {!Array<!AvatarGroupItem> | undefined}
*/
items: {
type: Array
},
/**
* The maximum number of avatars to display. By default, all the avatars are displayed.
* When _maxItemsVisible_ is set, the overflowing avatars are grouped into one avatar with
* a dropdown. Setting 0 or 1 has no effect so there are always at least two avatars visible.
*/
maxItemsVisible: {
type: Number
},
/**
* The object used to localize this component.
* To change the default localization, replace the entire
* _i18n_ object or just the property you want to modify.
*
* The object has the following JSON structure and default values:
* ```
* {
* // Translation of the anonymous user avatar title.
* anonymous: 'anonymous',
* // Translation of the avatar group accessible label.
* // {count} is replaced with the actual count of users.
* activeUsers: {
* one: 'Currently one active user',
* many: 'Currently {count} active users'
* },
* // Screen reader announcement when user joins group.
* // {user} is replaced with the name or abbreviation.
* // When neither is set, "anonymous" is used instead.
* joined: '{user} joined',
* // Screen reader announcement when user leaves group.
* // {user} is replaced with the name or abbreviation.
* // When neither is set, "anonymous" is used instead.
* left: '{user} left'
* }
* ```
* @type {!AvatarGroupI18n}
* @default {English/US}
*/
i18n: {
type: Object,
value: () => {
return {
anonymous: 'anonymous',
activeUsers: {
one: 'Currently one active user',
many: 'Currently {count} active users'
},
joined: '{user} joined',
left: '{user} left'
};
}
},
/** @private */
__maxReached: {
type: Boolean,
computed: '__computeMaxReached(items.length, maxItemsVisible)'
},
/** @private */
__itemsInView: {
type: Number,
value: null
},
/** @private */
_opened: {
type: Boolean,
observer: '__openedChanged',
value: false
}
};
}
static get observers() {
return [
'__computeMoreTitle(items.length, __itemsInView, maxItemsVisible)',
'__itemsChanged(items.splices, items.*)',
'__i18nItemsChanged(i18n.*, items.length)'
];
}
/** @protected */
ready() {
super.ready();
IronA11yAnnouncer.requestAvailability();
this.__boundSetPosition = this.__setPosition.bind(this);
this.addEventListener('iron-resize', this._onResize.bind(this));
this._overlayElement = this.shadowRoot.querySelector('vaadin-avatar-group-overlay');
afterNextRender(this, () => {
this.__setItemsInView();
});
}
/**
* @param {string} name
* @param {?string} oldValue
* @param {?string} newValue
* @protected
*/
attributeChangedCallback(name, oldValue, newValue) {
super.attributeChangedCallback(name, oldValue, newValue);
if (name === 'dir') {
this.__setPosition();
}
}
/**
* @return {!Array<!HTMLElement>}
* @protected
*/
get _avatars() {
return this.shadowRoot.querySelectorAll('vaadin-avatar');
}
/** @private */
__announce(text) {
this.dispatchEvent(
new CustomEvent('iron-announce', {
bubbles: true,
composed: true,
detail: {
text
}
})
);
}
/** @private */
__getMessage(user, action) {
return action.replace('{user}', user.name || user.abbr || this.i18n.anonymous);
}
/** @private */
_onOverflowClick(e) {
e.stopPropagation();
if (this._opened) {
this.$.overlay.close();
} else if (!e.defaultPrevented) {
this._opened = true;
}
}
/** @private */
_onOverflowKeyDown(e) {
if (!this._opened) {
if (/^(Enter|SpaceBar|\s)$/.test(e.key)) {
e.preventDefault();
this._opened = true;
}
}
}
/** @private */
_onListKeyDown(event) {
if (event.key === 'Escape' || event.key === 'Esc' || /^(Tab)$/.test(event.key)) {
this._opened = false;
}
}
/** @private */
_onResize() {
this.__debounceResize = Debouncer.debounce(this.__debounceResize, timeOut.after(0), () => {
this.__setItemsInView();
this.__setPosition();
});
}
/** @private */
_onVaadinOverlayClose(e) {
if (e.detail.sourceEvent && e.detail.sourceEvent.composedPath().indexOf(this) !== -1) {
e.preventDefault();
}
}
/** @private */
__computeItems(arr, itemsInView, maxItemsVisible) {
const items = arr.base || [];
const limit = this.__getLimit(items.length, itemsInView, maxItemsVisible);
return limit ? items.slice(0, limit) : items;
}
/** @private */
__computeExtraItems(arr, itemsInView, maxItemsVisible) {
const items = arr.base || [];
const limit = this.__getLimit(items.length, itemsInView, maxItemsVisible);
return limit ? items.slice(limit) : items;
}
/** @private */
__computeMaxReached(items, maxItemsVisible) {
return maxItemsVisible != null && items > this.__getMax(maxItemsVisible);
}
/** @private */
__computeMore(items, itemsInView, maxItemsVisible) {
return `+${items - this.__getLimit(items, itemsInView, maxItemsVisible)}`;
}
/** @private */
__computeMoreHidden(items, itemsInView, maxReached) {
return !maxReached && !(itemsInView && itemsInView < items);
}
/** @private */
__computeMoreTitle(items, itemsInView, maxItemsVisible) {
const limit = this.__getLimit(items, itemsInView, maxItemsVisible);
if (limit == null) {
return;
}
const result = [];
for (let i = limit; i < items; i++) {
const item = this.items[i];
result.push(item.name || item.abbr || 'anonymous');
}
// override generated title attribute
this.$.overflow.setAttribute('title', result.join('\n'));
}
/** @private */
__getLimit(items, itemsInView, maxItemsVisible) {
let limit = null;
// handle max set to 0 or 1
const adjustedMax = this.__getMax(maxItemsVisible);
if (maxItemsVisible != null && adjustedMax < items) {
limit = adjustedMax - 1;
} else if (itemsInView && itemsInView < items) {
limit = itemsInView;
}
return Math.min(limit, this.__calculateAvatarsFitWidth());
}
/** @private */
__getMax(maxItemsVisible) {
return Math.max(maxItemsVisible, MINIMUM_DISPLAYED_AVATARS);
}
/** @private */
__itemsChanged(splices, itemsChange) {
const items = itemsChange.base;
this.$.items.render();
this.__setItemsInView();
// mutation using group.splice('items')
if (splices && Array.isArray(splices.indexSplices)) {
splices.indexSplices.forEach((mutation) => {
this.__announceItemsChange(items, mutation);
});
} else if (Array.isArray(items) && Array.isArray(this.__oldItems)) {
// mutation using group.set('items')
const diff = calculateSplices(items, this.__oldItems);
diff.forEach((mutation) => {
this.__announceItemsChange(items, mutation);
});
}
this.__oldItems = items;
}
/** @private */
__announceItemsChange(items, mutation) {
const { addedCount, index, removed } = mutation;
let addedMsg = [];
let removedMsg = [];
if (addedCount) {
addedMsg = items
.slice(index, index + addedCount)
.map((user) => this.__getMessage(user, this.i18n.joined || '{user} joined'));
}
if (removed) {
removedMsg = removed.map((user) => this.__getMessage(user, this.i18n.left || '{user} left'));
}
const messages = removedMsg.concat(addedMsg);
if (messages.length > 0) {
this.__announce(messages.join(', '));
}
}
/** @private */
__i18nItemsChanged(i18n, items) {
const { base } = i18n;
if (base && base.activeUsers) {
const field = items === 1 ? 'one' : 'many';
if (base.activeUsers[field]) {
this.setAttribute('aria-label', base.activeUsers[field].replace('{count}', items || 0));
}
}
}
/** @private */
__openedChanged(opened, wasOpened) {
if (opened) {
if (!this._menuElement) {
this._menuElement = this._overlayElement.content.querySelector('vaadin-avatar-group-list-box');
this._menuElement.setAttribute('role', 'listbox');
}
this._openedWithFocusRing = this.$.overflow.hasAttribute('focus-ring');
const avatars = this._menuElement.querySelectorAll('vaadin-avatar');
avatars.forEach((avatar) => avatar.removeAttribute('title'));
this._menuElement.focus();
this.__setPosition();
window.addEventListener('scroll', this.__boundSetPosition, true);
} else if (wasOpened) {
this.$.overflow.focus();
if (this._openedWithFocusRing) {
this.$.overflow.setAttribute('focus-ring', '');
}
window.removeEventListener('scroll', this.__boundSetPosition, true);
}
this.$.overflow.setAttribute('aria-expanded', opened === true);
}
/** @private */
__setItemsInView() {
const avatars = this._avatars;
const items = this.items;
// always show at least two avatars
if (!items || !avatars || avatars.length < 3) {
return;
}
let result = this.__calculateAvatarsFitWidth();
// only show overlay if two or more avatars don't fit
if (result === items.length - 1) {
result = items.length;
}
// close overlay if all avatars become visible
if (result >= items.length && this._opened) {
this.$.overlay.close();
// FIXME: hack to avoid jump before closing
this.$.overlay._flushAnimation('closing');
}
// reserve space for overflow avatar
this.__itemsInView = result;
}
/** @private **/
__calculateAvatarsFitWidth() {
if (!this.shadowRoot || this._avatars.length < MINIMUM_DISPLAYED_AVATARS) {
return MINIMUM_DISPLAYED_AVATARS;
}
const avatars = this._avatars;
// assume all the avatars have the same width
const avatarWidth = avatars[0].clientWidth;
// take negative margin into account
const { marginLeft, marginRight } = getComputedStyle(avatars[1]);
const offset =
this.getAttribute('dir') == 'rtl'
? parseInt(marginRight, 0) - parseInt(marginLeft, 0)
: parseInt(marginLeft, 0) - parseInt(marginRight, 0);
return Math.floor((this.$.container.offsetWidth - avatarWidth) / (avatarWidth + offset));
}
/** @private */
__setPosition() {
if (!this._opened) {
return;
}
const btnRect = this.$.overflow.getBoundingClientRect();
const viewportHeight = Math.min(window.innerHeight, document.documentElement.clientHeight);
const bottomAlign = btnRect.top > (viewportHeight - btnRect.height) / 2;
const isRtl = this.getAttribute('dir') === 'rtl';
if (isRtl) {
this._overlayElement.style.right = document.documentElement.clientWidth - btnRect.right + 'px';
} else {
this._overlayElement.style.left = btnRect.left + 'px';
}
if (bottomAlign) {
this._overlayElement.setAttribute('bottom-aligned', '');
this._overlayElement.style.removeProperty('top');
this._overlayElement.style.bottom = viewportHeight - btnRect.top + 'px';
} else {
this._overlayElement.removeAttribute('bottom-aligned');
this._overlayElement.style.removeProperty('bottom');
this._overlayElement.style.top = btnRect.bottom + 'px';
}
}
}
customElements.define(AvatarGroupElement.is, AvatarGroupElement);
export { AvatarGroupElement };
export * from '@vaadin/avatar-group/src/vaadin-avatar-group.js';

@@ -1,74 +0,18 @@

import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
/**
* @license
* Copyright (c) 2021 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { Avatar } from '@vaadin/avatar/src/vaadin-avatar.js';
import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js';
/**
* @deprecated Import `Avatar` from `@vaadin/avatar` instead.
*/
export type AvatarElement = Avatar;
import { AvatarI18n } from './interfaces';
/**
* `<vaadin-avatar>` is a Web Component providing avatar displaying functionality.
*
* ```html
* <vaadin-avatar img="avatars/avatar-1.jpg"></vaadin-avatar>
* ```
*
* ### Styling
*
* The following shadow DOM parts are exposed for styling:
*
* Part name | Description
* --------- | ---------------
* `abbr` | The abbreviation element
* `icon` | The icon element
*
* The following attributes are exposed for styling:
*
* Attribute | Description
* --------- | -----------
* `has-color-index` | Set when the avatar has `colorIndex` and the corresponding custom CSS property exists.
*
* See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
* @deprecated Import `Avatar` from `@vaadin/avatar` instead.
*/
declare class AvatarElement extends ElementMixin(ThemableMixin(HTMLElement)) {
/**
* The path to the image
*/
img: string | null | undefined;
export const AvatarElement: typeof Avatar;
/**
* A shortened form of name that is displayed
* in the avatar when `img` is not provided.
*/
abbr: string | null | undefined;
/**
* Full name of the user
* used for the title of the avatar.
*/
name: string | null | undefined;
/**
* Color index used for avatar background.
*/
colorIndex: number | null | undefined;
/**
* The object used to localize this component.
* To change the default localization, replace the entire
* _i18n_ object or just the property you want to modify.
*
* The object has the following JSON structure and default values:
* {
* // Translation of the anonymous user avatar title.
* anonymous: 'anonymous'
* }
*/
i18n: AvatarI18n;
}
declare global {
interface HTMLElementTagNameMap {
'vaadin-avatar': AvatarElement;
}
}
export { AvatarElement };
export * from '@vaadin/avatar/src/vaadin-avatar.js';

@@ -6,348 +6,9 @@ /**

*/
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js';
import './vaadin-avatar-icons.js';
import { Avatar } from '@vaadin/avatar/src/vaadin-avatar.js';
// We consider the keyboard to be active if the window has received a keydown
// event since the last mousedown event.
let keyboardActive = false;
// Listen for top-level Tab keydown and mousedown events.
// Use capture phase so we detect events even if they're handled.
window.addEventListener(
'keydown',
(e) => {
keyboardActive = e.keyCode === 9;
},
true
);
window.addEventListener(
'mousedown',
() => {
keyboardActive = false;
},
true
);
/**
* `<vaadin-avatar>` is a Web Component providing avatar displaying functionality.
*
* ```html
* <vaadin-avatar img="avatars/avatar-1.jpg"></vaadin-avatar>
* ```
*
* ### Styling
*
* The following shadow DOM parts are exposed for styling:
*
* Part name | Description
* --------- | ---------------
* `abbr` | The abbreviation element
* `icon` | The icon element
*
* The following attributes are exposed for styling:
*
* Attribute | Description
* --------- | -----------
* `has-color-index` | Set when the avatar has `colorIndex` and the corresponding custom CSS property exists.
*
* See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
*
* @extends HTMLElement
* @mixes ElementMixin
* @mixes ThemableMixin
* @deprecated Import `Avatar` from `@vaadin/avatar` instead.
*/
class AvatarElement extends ElementMixin(ThemableMixin(PolymerElement)) {
static get template() {
return html`
<style>
:host {
display: inline-block;
flex: none;
border-radius: 50%;
overflow: hidden;
height: var(--vaadin-avatar-size);
width: var(--vaadin-avatar-size);
border: var(--vaadin-avatar-outline-width) solid transparent;
margin: calc(var(--vaadin-avatar-outline-width) * -1);
background-clip: content-box;
--vaadin-avatar-outline-width: 2px;
--vaadin-avatar-size: 64px;
}
export const AvatarElement = Avatar;
img {
height: 100%;
width: 100%;
object-fit: cover;
}
[part='icon'] {
font-size: 5.6em;
}
[part='abbr'] {
font-size: 2.2em;
}
[part='icon'] > text {
font-family: 'vaadin-avatar-icons';
}
:host([hidden]) {
display: none !important;
}
svg[hidden] {
display: none !important;
}
:host([has-color-index]) {
position: relative;
background-color: var(--vaadin-avatar-user-color);
}
:host([has-color-index])::before {
position: absolute;
content: '';
top: 0;
left: 0;
bottom: 0;
right: 0;
border-radius: inherit;
box-shadow: inset 0 0 0 2px var(--vaadin-avatar-user-color);
}
</style>
<img hidden$="[[!__imgVisible]]" src$="[[img]]" aria-hidden="true" on-error="__onImageLoadError" />
<svg
part="icon"
hidden$="[[!__iconVisible]]"
id="avatar-icon"
viewBox="-50 -50 100 100"
preserveAspectRatio="xMidYMid meet"
aria-hidden="true"
>
<text dy=".35em" text-anchor="middle"></text>
</svg>
<svg
part="abbr"
hidden$="[[!__abbrVisible]]"
id="avatar-abbr"
viewBox="-50 -50 100 100"
preserveAspectRatio="xMidYMid meet"
aria-hidden="true"
>
<text dy=".35em" text-anchor="middle">[[abbr]]</text>
</svg>
`;
}
static get is() {
return 'vaadin-avatar';
}
static get version() {
return '22.0.0-alpha1';
}
static get properties() {
return {
/**
* The path to the image
*/
img: {
type: String,
reflectToAttribute: true,
observer: '__imgChanged'
},
/**
* A shortened form of name that is displayed
* in the avatar when `img` is not provided.
*/
abbr: {
type: String,
reflectToAttribute: true
},
/**
* Full name of the user
* used for the title of the avatar.
*/
name: {
type: String,
reflectToAttribute: true
},
/**
* Color index used for avatar background.
*/
colorIndex: {
type: Number,
observer: '__colorIndexChanged'
},
/**
* The object used to localize this component.
* To change the default localization, replace the entire
* _i18n_ object or just the property you want to modify.
*
* The object has the following JSON structure and default values:
{
// Translation of the anonymous user avatar title.
anonymous: 'anonymous'
}
* @type {!AvatarI18n}
* @default {English/US}
*/
i18n: {
type: Object,
value: () => {
return {
anonymous: 'anonymous'
};
}
},
/** @private */
__imgVisible: Boolean,
/** @private */
__iconVisible: Boolean,
/** @private */
__abbrVisible: Boolean
};
}
static get observers() {
return ['__imgOrAbbrOrNameChanged(img, abbr, name)', '__i18nChanged(i18n.*)'];
}
/** @protected */
ready() {
super.ready();
this.__updateVisibility();
// Should set `anonymous` if name / abbr is not provided
if (!this.name && !this.abbr) {
this.__setTitle(this.name);
}
this.setAttribute('role', 'button');
if (!this.hasAttribute('tabindex')) {
this.setAttribute('tabindex', '0');
}
this.addEventListener('focusin', () => {
this.__setFocused(true);
});
this.addEventListener('focusout', () => {
this.__setFocused(false);
});
}
/** @private */
__setFocused(focused) {
if (focused) {
this.setAttribute('focused', '');
if (keyboardActive) {
this.setAttribute('focus-ring', '');
}
} else {
this.removeAttribute('focused');
this.removeAttribute('focus-ring');
}
}
/** @private */
__colorIndexChanged(index) {
if (index != null) {
const prop = `--vaadin-user-color-${index}`;
// check if custom CSS property is defined
const isValid = Boolean(getComputedStyle(document.documentElement).getPropertyValue(prop));
if (isValid) {
this.setAttribute('has-color-index', '');
this.style.setProperty('--vaadin-avatar-user-color', `var(${prop})`);
} else {
this.removeAttribute('has-color-index');
console.warn(`The CSS property --vaadin-user-color-${index} is not defined`);
}
} else {
this.removeAttribute('has-color-index');
}
}
/** @private */
__imgChanged() {
this.__imgFailedToLoad = false;
}
/** @private */
__imgOrAbbrOrNameChanged(img, abbr, name) {
this.__updateVisibility();
if (abbr && abbr !== this.__generatedAbbr) {
this.__setTitle(name ? `${name} (${abbr})` : abbr);
return;
}
if (name) {
this.abbr = this.__generatedAbbr = name
.split(' ')
.map((word) => word.charAt(0))
.join('');
} else {
this.abbr = undefined;
}
this.__setTitle(name);
}
/** @private */
__i18nChanged(i18n) {
if (i18n.base && i18n.base.anonymous) {
if (this.__oldAnonymous && this.getAttribute('title') === this.__oldAnonymous) {
this.__setTitle();
}
this.__oldAnonymous = i18n.base.anonymous;
}
}
/** @private */
__updateVisibility() {
this.__imgVisible = !!this.img && !this.__imgFailedToLoad;
this.__abbrVisible = !this.__imgVisible && !!this.abbr;
this.__iconVisible = !this.__imgVisible && !this.abbr;
}
/** @private */
__setTitle(title) {
if (title) {
this.setAttribute('title', title);
} else {
this.setAttribute('title', this.i18n.anonymous);
}
}
/** @private */
__onImageLoadError() {
if (this.img) {
console.warn(`<vaadin-avatar> The specified image could not be loaded: ${this.img}`);
this.__imgFailedToLoad = true;
this.__updateVisibility();
}
}
}
customElements.define(AvatarElement.is, AvatarElement);
export { AvatarElement };
export * from '@vaadin/avatar/src/vaadin-avatar.js';

@@ -1,5 +0,1 @@

import './vaadin-avatar.js';
import './vaadin-avatar-group-styles.js';
import '@vaadin/vaadin-item/theme/lumo/vaadin-item.js';
import '@vaadin/vaadin-list-box/theme/lumo/vaadin-list-box.js';
import '../../src/vaadin-avatar-group.js';
import '@vaadin/avatar-group/theme/lumo/vaadin-avatar-group.js';

@@ -1,2 +0,1 @@

import './vaadin-avatar-styles.js';
import '../../src/vaadin-avatar.js';
import '@vaadin/avatar/theme/lumo/vaadin-avatar.js';

@@ -1,5 +0,1 @@

import './vaadin-avatar.js';
import './vaadin-avatar-group-styles.js';
import '@vaadin/vaadin-item/theme/material/vaadin-item.js';
import '@vaadin/vaadin-list-box/theme/material/vaadin-list-box.js';
import '../../src/vaadin-avatar-group.js';
import '@vaadin/avatar-group/theme/material/vaadin-avatar-group.js';

@@ -1,2 +0,1 @@

import './vaadin-avatar-styles.js';
import '../../src/vaadin-avatar.js';
import '@vaadin/avatar/theme/material/vaadin-avatar.js';
export * from './src/vaadin-avatar-group.js';
export * from './src/interfaces';
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc