@vaadin/avatar
Version:
vaadin-avatar
239 lines (207 loc) • 6.41 kB
JavaScript
/**
* @license
* Copyright (c) 2017 - 2026 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
import { I18nMixin } from '@vaadin/component-base/src/i18n-mixin.js';
const DEFAULT_I18N = {
anonymous: 'anonymous',
};
/**
* A mixin providing common avatar functionality.
*
* @polymerMixin
* @mixes FocusMixin
*/
export const AvatarMixin = (superClass) =>
class AvatarMixinClass extends I18nMixin(DEFAULT_I18N, FocusMixin(superClass)) {
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 tooltip of the avatar.
*/
name: {
type: String,
reflectToAttribute: true,
},
/**
* Color index used for avatar background.
* @attr {number} color-index
*/
colorIndex: {
type: Number,
observer: '__colorIndexChanged',
},
/**
* When true, the avatar has tooltip shown on hover and focus.
* The tooltip text is based on the `name` and `abbr` properties.
* When neither is provided, `i18n.anonymous` is used instead.
* @attr {boolean} with-tooltip
*/
withTooltip: {
type: Boolean,
value: false,
observer: '__withTooltipChanged',
},
/** @protected */
__imgVisible: Boolean,
/** @protected */
__iconVisible: Boolean,
/** @protected */
__abbrVisible: Boolean,
/** @private */
__tooltipNode: Object,
};
}
static get observers() {
return [
'__imgOrAbbrOrNameChanged(img, abbr, name)',
'__i18nChanged(__effectiveI18n)',
'__tooltipChanged(__tooltipNode, name, abbr)',
];
}
/**
* The object used to localize this component. To change the default
* localization, replace this with an object that provides all properties, or
* just the individual properties you want to change.
*
* The object has the following JSON structure and default values:
* ```js
* {
* // Translation of the anonymous user avatar tooltip.
* anonymous: 'anonymous'
* }
* ```
* @type {!AvatarI18n}
*/
get i18n() {
return super.i18n;
}
set i18n(value) {
super.i18n = value;
}
/** @protected */
ready() {
super.ready();
this.__updateVisibility();
// By default, if the user hasn't provided a custom role,
// the role attribute is set to "img".
if (!this.hasAttribute('role')) {
this.setAttribute('role', 'img');
}
// Should set `anonymous` if name / abbr is not provided
if (!this.name && !this.abbr) {
this.__setTooltip();
}
}
/** @private */
__colorIndexChanged(index) {
if (index != null) {
this.setAttribute('has-color-index', '');
this.style.setProperty('--vaadin-avatar-user-color', `var(--vaadin-user-color-${index})`);
} else {
this.removeAttribute('has-color-index');
this.style.removeProperty('--vaadin-avatar-user-color');
}
}
/** @private */
__imgChanged() {
this.__imgFailedToLoad = false;
}
/** @private */
__imgOrAbbrOrNameChanged(_img, abbr, name) {
this.__updateVisibility();
if (abbr && abbr !== this.__generatedAbbr) {
return;
}
if (name) {
this.abbr = this.__generatedAbbr = name
.split(' ')
.map((word) => word.charAt(0))
.join('');
} else {
this.abbr = undefined;
}
}
/** @private */
__tooltipChanged(tooltipNode, name, abbr) {
if (tooltipNode) {
if (abbr && abbr !== this.__generatedAbbr) {
this.__setTooltip(name ? `${name} (${abbr})` : abbr);
} else {
this.__setTooltip(name);
}
}
if (abbr) {
// By default, generate aria-label attribute containing the abbr value.
// When no tooltip is set, prefix the aria-label with the name value.
this.setAttribute('aria-label', !tooltipNode && name ? `${name} (${abbr})` : abbr);
} else {
this.removeAttribute('aria-label');
}
}
/** @private */
__withTooltipChanged(withTooltip, oldWithTooltip) {
this.toggleAttribute('has-tooltip', withTooltip);
if (withTooltip) {
// Create and attach tooltip
const tooltipNode = document.createElement('vaadin-tooltip');
tooltipNode.setAttribute('slot', 'tooltip');
this.appendChild(tooltipNode);
this.__tooltipNode = tooltipNode;
} else if (oldWithTooltip) {
// Cleanup and detach tooltip
this.__tooltipNode.target = null;
this.__tooltipNode.remove();
this.__tooltipNode = null;
}
}
/** @private */
__i18nChanged(effectiveI18n) {
if (effectiveI18n && effectiveI18n.anonymous) {
if (this.__oldAnonymous && this.__tooltipNode && this.__tooltipNode.text === this.__oldAnonymous) {
this.__setTooltip();
}
this.__oldAnonymous = effectiveI18n.anonymous;
}
}
/** @private */
__updateVisibility() {
this.__imgVisible = !!this.img && !this.__imgFailedToLoad;
this.__abbrVisible = !this.__imgVisible && !!this.abbr;
this.__iconVisible = !this.__imgVisible && !this.abbr;
}
/** @private */
__setTooltip(tooltip) {
const tooltipNode = this.__tooltipNode;
if (tooltipNode) {
tooltipNode.text = tooltip || this.__effectiveI18n.anonymous;
}
}
/** @protected */
__onImageLoadError() {
if (this.img) {
console.warn(`<vaadin-avatar> The specified image could not be loaded: ${this.img}`);
this.__imgFailedToLoad = true;
this.__updateVisibility();
}
}
};