UNPKG

ng-hub-ui-avatar

Version:

A universal avatar component for Angular applications that fetches / generates avatar based on the information you have about the user. Supports initials, Gravatar integration, custom images and styling options. Perfect for user profiles and comment syste

928 lines 55.9 kB
import * as i0 from '@angular/core';
import { InjectionToken, Optional, Inject, Injectable, input, booleanAttribute, computed, output, SecurityContext, ViewChild, Component, NgModule, makeEnvironmentProviders } from '@angular/core';
import * as i3 from '@angular/platform-browser';
import { takeWhile, map } from 'rxjs/operators';
import * as i1 from '@angular/common/http';
import { Md5 } from 'ts-md5';

/**
 * Token used to inject the AvatarConfig object
 */
const AVATAR_CONFIG = new InjectionToken('avatar.config');

class AvatarConfigService {
    userConfig;
    constructor(userConfig) {
        this.userConfig = userConfig;
    }
    getAvatarSources(defaultSources) {
        if (this.userConfig &&
            this.userConfig.sourcePriorityOrder &&
            this.userConfig.sourcePriorityOrder.length) {
            const uniqueSources = [
                ...new Set(this.userConfig.sourcePriorityOrder)
            ];
            const validSources = uniqueSources.filter((source) => defaultSources.includes(source));
            return [
                ...validSources,
                ...defaultSources.filter((source) => !validSources.includes(source))
            ];
        }
        return defaultSources;
    }
    getAvatarColors(defaultColors) {
        return ((this.userConfig &&
            this.userConfig.colors &&
            this.userConfig.colors.length &&
            this.userConfig.colors) ||
            defaultColors);
    }
    getDisableSrcCache(defaultDisableSrcCache) {
        if (this.userConfig == null ||
            this.userConfig.disableSrcCache == null) {
            return defaultDisableSrcCache;
        }
        else {
            return this.userConfig.disableSrcCache;
        }
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: AvatarConfigService, deps: [{ token: AVATAR_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: AvatarConfigService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: AvatarConfigService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [{ type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [AVATAR_CONFIG]
                }] }] });

var AvatarSource;
(function (AvatarSource) {
    AvatarSource["FACEBOOK"] = "facebook";
    AvatarSource["GRAVATAR"] = "gravatar";
    AvatarSource["GITHUB"] = "github";
    AvatarSource["CUSTOM"] = "custom";
    AvatarSource["INITIALS"] = "initials";
    AvatarSource["VALUE"] = "value";
})(AvatarSource || (AvatarSource = {}));

/**
 * list of Supported avatar sources
 */
const defaultSources = [
    AvatarSource.FACEBOOK,
    AvatarSource.GRAVATAR,
    AvatarSource.GITHUB,
    AvatarSource.CUSTOM,
    AvatarSource.INITIALS,
    AvatarSource.VALUE
];
/**
 * list of default colors
 */
const defaultColors = [
    '#1abc9c',
    '#3498db',
    '#f1c40f',
    '#8e44ad',
    '#e74c3c',
    '#d35400',
    '#2c3e50',
    '#7f8c8d'
];
/**
 * Default disable custom source cache settings
 */
const defaultDisableSrcCache = false;
/**
 * Provides utilities methods related to Avatar component
 */
class AvatarService {
    http;
    avatarConfigService;
    avatarSources = defaultSources;
    avatarColors = defaultColors;
    failedSources = new Map();
    constructor(http, avatarConfigService) {
        this.http = http;
        this.avatarConfigService = avatarConfigService;
        this.overrideAvatarSources();
        this.overrideAvatarColors();
    }
    fetchAvatar(avatarUrl) {
        return this.http.get(avatarUrl);
    }
    getRandomColor(avatarText) {
        if (!avatarText) {
            return 'transparent';
        }
        const asciiCodeSum = this.calculateAsciiCode(avatarText);
        return this.avatarColors[asciiCodeSum % this.avatarColors.length];
    }
    compareSources(sourceType1, sourceType2) {
        return (this.getSourcePriority(sourceType1) - this.getSourcePriority(sourceType2));
    }
    isSource(source) {
        return this.avatarSources.includes(source);
    }
    isTextAvatar(sourceType) {
        return [AvatarSource.INITIALS, AvatarSource.VALUE].includes(sourceType);
    }
    buildSourceKey(source) {
        return source.sourceType + '-' + source.sourceId;
    }
    sourceHasFailedBefore(source) {
        return this.failedSources.has(this.buildSourceKey(source));
    }
    markSourceAsFailed(source) {
        this.failedSources.set(this.buildSourceKey(source), source);
    }
    overrideAvatarSources() {
        this.avatarSources = this.avatarConfigService.getAvatarSources(defaultSources);
    }
    overrideAvatarColors() {
        this.avatarColors = this.avatarConfigService.getAvatarColors(defaultColors);
    }
    calculateAsciiCode(value) {
        return value
            .split('')
            .map(letter => letter.charCodeAt(0))
            .reduce((previous, current) => previous + current);
    }
    getSourcePriority(sourceType) {
        return this.avatarSources.indexOf(sourceType);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: AvatarService, deps: [{ token: i1.HttpClient }, { token: AvatarConfigService }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: AvatarService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: AvatarService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [{ type: i1.HttpClient }, { type: AvatarConfigService }] });

/**
 * Contract of all async sources.
 * Every async source must implement the processResponse method that extracts the avatar url from the data
 */
class AsyncSource {
    sourceId;
    constructor(sourceId) {
        this.sourceId = sourceId;
    }
}

/**
 * Normalises an accent colour into a paintable value for a `--hub-*-accent` CSS slot,
 * accepting ANY colour. A bareword (a semantic name, a host-registered accent, or a CSS
 * named colour) resolves to its design-system token `var(--hub-sys-color-<name>, <name>)`
 * — the raw word is the fallback so an unregistered name still paints. A literal `#hex` /
 * `rgb()` / `oklch()` / `var(...)` is passed through unchanged. Returns `null` when the
 * value is unset or blank, so the SCSS default (or a builtin `@each`) can take over.
 *
 * @param value The raw accent value to normalise.
 * @returns The paintable CSS value, or `null` when unset/blank.
 */
function resolveHubAccent(value) {
    const color = value?.trim();
    if (!color) {
        return null;
    }
    return /^[a-zA-Z][\w-]*$/.test(color) ? `var(--hub-sys-color-${color}, ${color})` : color;
}

/**
 *  Facebook source implementation.
 *  Fetch avatar source based on facebook identifier
 *  and image size
 */
class Facebook {
    sourceId;
    sourceType = AvatarSource.FACEBOOK;
    constructor(sourceId) {
        this.sourceId = sourceId;
    }
    getAvatar(size) {
        return ('https://graph.facebook.com/' +
            `${this.sourceId}/picture?width=${size}&height=${size}`);
    }
}

/**
 *  Custom source implementation.
 *  return custom image as an avatar
 *
 */
class Custom {
    sourceId;
    sourceType = AvatarSource.CUSTOM;
    constructor(sourceId) {
        this.sourceId = sourceId;
    }
    getAvatar() {
        return this.sourceId;
    }
}

/**
 * Initials source implementation.
 * return the initials of the given value
 */
class Initials {
    sourceId;
    sourceType = AvatarSource.INITIALS;
    constructor(sourceId) {
        this.sourceId = sourceId;
    }
    getAvatar(size) {
        return this.getInitials(this.sourceId, size);
    }
    /**
     * Returns the initial letters of a name in a string.
     */
    getInitials(name, size) {
        name = name.trim();
        if (!name) {
            return '';
        }
        const initials = name.split(' ');
        if (size && size < initials.length) {
            return this.constructInitials(initials.slice(0, size));
        }
        else {
            return this.constructInitials(initials);
        }
    }
    /**
     * Iterates a person's name string to get the initials of each word in uppercase.
     */
    constructInitials(elements) {
        if (!elements || !elements.length) {
            return '';
        }
        return elements
            .filter(element => element && element.length > 0)
            .map(element => element[0].toUpperCase())
            .join('');
    }
}

function isRetina() {
    if (typeof window !== 'undefined' && window !== null) {
        if (window.devicePixelRatio > 1.25) {
            return true;
        }
        const mediaQuery = '(-webkit-min-device-pixel-ratio: 1.25), (min--moz-device-pixel-ratio: 1.25), (-o-min-device-pixel-ratio: 5/4), (min-resolution: 1.25dppx)';
        if (window.matchMedia && window.matchMedia(mediaQuery).matches) {
            return true;
        }
    }
    return false;
}
/**
 *  Gravatar source implementation.
 *  Fetch avatar source based on gravatar email
 */
class Gravatar {
    value;
    sourceType = AvatarSource.GRAVATAR;
    sourceId;
    constructor(value) {
        this.value = value;
        this.sourceId = value.match('^[a-f0-9]{32}$')
            ? value
            : Md5.hashStr(value).toString();
    }
    getAvatar(size) {
        const avatarSize = isRetina() ? size * 2 : size;
        return `https://secure.gravatar.com/avatar/${this.sourceId}?s=${avatarSize}&d=404`;
    }
}

/**
 *  Value source implementation.
 *  return the value as avatar
 */
class Value {
    sourceId;
    sourceType = AvatarSource.VALUE;
    constructor(sourceId) {
        this.sourceId = sourceId;
    }
    getAvatar() {
        return this.sourceId;
    }
}

/**
 *  GitHub source implementation.
 *  Fetch avatar source based on github identifier
 */
class Github extends AsyncSource {
    sourceType = AvatarSource.GITHUB;
    constructor(sourceId) {
        super(sourceId);
    }
    getAvatar() {
        return `https://api.github.com/users/${this.sourceId}`;
    }
    /**
     * extract github avatar from json data
     */
    processResponse(data, size) {
        if (size) {
            return `${data.avatar_url}&s=${size}`;
        }
        return data.avatar_url;
    }
}

/**
 *  Custom source implementation (with no cache).
 *  return custom image as an avatar
 *
 */
class CustomNoCache {
    sourceId;
    sourceType = AvatarSource.CUSTOM;
    constructor(sourceId) {
        this.sourceId = sourceId;
    }
    getAvatar() {
        const urlSuffix = Math.random();
        return `${this.sourceId}${this.sourceId.indexOf('?') > -1 ? '&' : '?'}_=${urlSuffix}`;
    }
}

/**
 * Factory class that implements factory method pattern.
 * Used to create Source implementation class based
 * on the source Type
 */
class SourceFactory {
    sources = {};
    constructor(avatarConfigService) {
        const disableSrcCache = avatarConfigService.getDisableSrcCache(defaultDisableSrcCache);
        this.sources[AvatarSource.FACEBOOK] = Facebook;
        this.sources[AvatarSource.GRAVATAR] = Gravatar;
        this.sources[AvatarSource.CUSTOM] = disableSrcCache ? CustomNoCache : Custom;
        this.sources[AvatarSource.INITIALS] = Initials;
        this.sources[AvatarSource.VALUE] = Value;
        this.sources[AvatarSource.GITHUB] = Github;
    }
    newInstance(sourceType, sourceValue) {
        return new this.sources[sourceType](sourceValue);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SourceFactory, deps: [{ token: AvatarConfigService }], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SourceFactory, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: SourceFactory, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [{ type: AvatarConfigService }] });

/**
 * Universal avatar component that
 * generates avatar from different sources
 *
 * export
 * class AvatarComponent
 * implements {OnChanges}
 */
class AvatarComponent {
    sourceFactory;
    avatarService;
    sanitizer;
    round = input(true, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "round" }] : /* istanbul ignore next */ []));
    size = input(50, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
    textSizeRatio = input(3, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "textSizeRatio" }] : /* istanbul ignore next */ []));
    bgColor = input(/* @ts-ignore */
    ...(ngDevMode ? [undefined, { debugName: "bgColor" }] : /* istanbul ignore next */ []));
    fgColor = input('#FFF', /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "fgColor" }] : /* istanbul ignore next */ []));
    borderColor = input(/* @ts-ignore */
    ...(ngDevMode ? [undefined, { debugName: "borderColor" }] : /* istanbul ignore next */ []));
    /**
     * When `true` (default) an initials avatar gets a background colour derived from
     * a hash of its `name`, applied inline. Set to `false` to suppress that inline
     * colour so the avatar can be themed via the `--hub-avatar-bg-color` CSS variable
     * without needing `!important`. An explicit `bgColor` always wins over both.
     */
    autoColor = input(true, { ...(ngDevMode ? { debugName: "autoColor" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
    style = input({}, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "style" }] : /* istanbul ignore next */ []));
    cornerRadius = input(0, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "cornerRadius" }] : /* istanbul ignore next */ []));
    facebook = input(undefined, { ...(ngDevMode ? { debugName: "facebook" } : /* istanbul ignore next */ {}), alias: 'facebookId' });
    gravatar = input(undefined, { ...(ngDevMode ? { debugName: "gravatar" } : /* istanbul ignore next */ {}), alias: 'gravatarId' });
    github = input(undefined, { ...(ngDevMode ? { debugName: "github" } : /* istanbul ignore next */ {}), alias: 'githubId' });
    custom = input(undefined, { ...(ngDevMode ? { debugName: "custom" } : /* istanbul ignore next */ {}), alias: 'src' });
    customAlt = input(undefined, { ...(ngDevMode ? { debugName: "customAlt" } : /* istanbul ignore next */ {}), alias: 'alt' });
    initials = input(undefined, { ...(ngDevMode ? { debugName: "initials" } : /* istanbul ignore next */ {}), alias: 'name' });
    value = input(/* @ts-ignore */
    ...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
    referrerpolicy = input(/* @ts-ignore */
    ...(ngDevMode ? [undefined, { debugName: "referrerpolicy" }] : /* istanbul ignore next */ []));
    placeholder = input(/* @ts-ignore */
    ...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
    initialsSize = input(0, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "initialsSize" }] : /* istanbul ignore next */ []));
    /**
     * Overlay badge at the bottom-end corner. A boolean / empty value renders a plain
     * dot (great for a presence indicator); a string or number renders a labelled badge
     * (e.g. a count like `"4k"`). `null` / absent (default) renders nothing.
     *
     * @example <hub-avatar badge badgeColor="success" />      // dot
     * @example <hub-avatar badge="4k" badgeColor="danger" />  // labelled
     */
    badge = input(null, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "badge" }] : /* istanbul ignore next */ []));
    /**
     * Semantic colour of the {@link badge} (and, as a host class, of the avatar itself).
     * Maps to a `--hub-sys-color-*` token; any custom string also works (set
     * `--hub-avatar-badge-color`). When unset the badge uses a neutral default.
     */
    badgeColor = input(null, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "badgeColor" }] : /* istanbul ignore next */ []));
    /**
     * Normalises {@link badgeColor} into a paintable value for the `--hub-avatar-badge-color`
     * accent slot, accepting ANY colour. A bareword (a semantic name, a host-registered accent,
     * or a CSS named colour) resolves to its design-system token `var(--hub-sys-color-<name>,
     * <name>)` — the raw word is the fallback so an unregistered name still paints. A literal
     * `#hex` / `rgb()` / `oklch()` / `var(...)` is passed through unchanged. `null` when unset,
     * so the SCSS default (and the builtin `@each` per `data-badge-color`) takes over.
     */
    badgeColorVar = computed(() => resolveHubAccent(this.badgeColor()), /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "badgeColorVar" }] : /* istanbul ignore next */ []));
    /** True when a badge should be rendered (the `badge` input is set to anything but `null` / `false`). */
    _hasBadge = computed(() => {
        const b = this.badge();
        return b !== null && b !== undefined && b !== false;
    }, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "_hasBadge" }] : /* istanbul ignore next */ []));
    /** The badge's text content; empty for a plain dot (`badge` is `true` or an empty string). */
    _badgeText = computed(() => {
        const b = this.badge();
        if (b === true || b === '' || b === null || b === undefined || b === false) {
            return '';
        }
        return String(b);
    }, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "_badgeText" }] : /* istanbul ignore next */ []));
    /** True when the badge is a plain dot (shown, but with no text content). */
    _isDot = computed(() => this._hasBadge() && this._badgeText() === '', /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "_isDot" }] : /* istanbul ignore next */ []));
    clickOnAvatar = output();
    /** Wrapper around the projected content (`<ng-content>`), used to detect whether the consumer projected anything. */
    customContentRef;
    /** True when the consumer projected custom content (an icon, SVG, image, …) into the avatar. */
    hasCustomContent = false;
    /** Inline style applied to the projected-content slot (honours `bgColor` / `fgColor` / `borderColor` / `style`). */
    customContentStyle = {};
    isAlive = true;
    avatarSrc = null;
    avatarAlt = null;
    avatarText = null;
    avatarStyle = {};
    hostStyle = {};
    currentIndex = -1;
    sources = [];
    constructor(sourceFactory, avatarService, sanitizer) {
        this.sourceFactory = sourceFactory;
        this.avatarService = avatarService;
        this.sanitizer = sanitizer;
    }
    onAvatarClicked() {
        this.clickOnAvatar.emit(this.sources[this.currentIndex]);
    }
    /**
     * Detects projected content once it is available and, when present, computes its style.
     * Runs after content init so `<ng-content>` nodes are already in place.
     */
    ngAfterContentInit() {
        const host = this.customContentRef?.nativeElement;
        this.hasCustomContent = !!host && this.hasMeaningfulProjectedContent(host);
        if (this.hasCustomContent) {
            this.customContentStyle = this.getCustomContentStyle();
        }
    }
    /**
     * Returns true when the projected slot holds a real element or non-whitespace text,
     * so whitespace-only projection does not flip the avatar into custom-content mode.
     *
     * @param host The element wrapping the projected content.
     */
    hasMeaningfulProjectedContent(host) {
        return Array.from(host.childNodes).some((node) => node.nodeType === Node.ELEMENT_NODE ||
            (node.nodeType === Node.TEXT_NODE && (node.textContent ?? '').trim().length > 0));
    }
    /**
     * Builds the inline style for the projected-content slot. Sensible visible defaults
     * (a themed background circle and a readable foreground colour) come from CSS tokens;
     * the `bgColor` / `fgColor` / `borderColor` / `style` inputs override them when set.
     */
    getCustomContentStyle() {
        const borderColor = this.borderColor();
        const bgColor = this.bgColor();
        const hasCustomFgColor = this.fgColor() !== '#FFF';
        return {
            backgroundColor: bgColor ? bgColor : undefined,
            color: hasCustomFgColor ? this.fgColor() : undefined,
            border: borderColor ? '1px solid ' + borderColor : undefined,
            ...this.getCustomStyleObject()
        };
    }
    /**
     * The avatar size as a px string. Exposed on the host as `--hub-avatar-size`
     * so the status dot (and any token-driven child) scales with the avatar.
     */
    get avatarSizePx() {
        return (parseFloat(String(this.size())) || 50) + 'px';
    }
    /**
     * Detect inputs change
     *
     * param {{ [propKey: string]: SimpleChange }} changes
     *
     * memberof AvatarComponent
     */
    ngOnChanges(changes) {
        for (const propName in changes) {
            if (this.avatarService.isSource(propName)) {
                const sourceType = AvatarSource[propName.toUpperCase()];
                const currentValue = changes[propName].currentValue;
                if (currentValue && typeof currentValue === 'string') {
                    this.addSource(sourceType, currentValue);
                }
                else {
                    const sanitized = this.sanitizer.sanitize(SecurityContext.URL, currentValue);
                    if (sanitized) {
                        this.addSource(sourceType, sanitized);
                    }
                    else {
                        this.removeSource(sourceType);
                    }
                }
            }
        }
        // Reinitialize when any source input changes so fallback order is recalculated.
        this.initializeAvatar();
        if (this.hasCustomContent) {
            this.customContentStyle = this.getCustomContentStyle();
        }
    }
    /**
     * Fetch avatar source
     *
     * memberOf AvatarComponent
     */
    fetchAvatarSource() {
        const previousSource = this.sources[this.currentIndex];
        if (previousSource) {
            this.avatarService.markSourceAsFailed(previousSource);
        }
        const source = this.findNextSource();
        if (!source) {
            return;
        }
        if (this.avatarService.isTextAvatar(source.sourceType)) {
            this.buildTextAvatar(source);
            this.avatarSrc = null;
        }
        else {
            this.buildImageAvatar(source);
        }
    }
    findNextSource() {
        while (++this.currentIndex < this.sources.length) {
            const source = this.sources[this.currentIndex];
            if (source && !this.avatarService.sourceHasFailedBefore(source)) {
                return source;
            }
        }
        return null;
    }
    ngOnDestroy() {
        this.isAlive = false;
    }
    /**
     * Initialize the avatar component and its fallback system
     */
    initializeAvatar() {
        const computedBorderRadius = this.round()
            ? '50%'
            : this.cornerRadius() + 'px';
        this.hostStyle = {
            width: this.size() + 'px',
            height: this.size() + 'px',
            borderRadius: computedBorderRadius
        };
        this.currentIndex = -1;
        if (this.sources.length > 0) {
            this.sortAvatarSources();
            this.fetchAvatarSource();
        }
    }
    sortAvatarSources() {
        this.sources.sort((source1, source2) => this.avatarService.compareSources(source1.sourceType, source2.sourceType));
    }
    buildTextAvatar(avatarSource) {
        this.avatarText = avatarSource.getAvatar(+this.initialsSize());
        this.avatarStyle = this.getInitialsStyle(avatarSource.sourceId);
    }
    buildImageAvatar(avatarSource) {
        this.avatarStyle = this.getImageStyle();
        if (avatarSource instanceof AsyncSource) {
            this.fetchAndProcessAsyncAvatar(avatarSource);
        }
        else {
            this.avatarSrc = this.sanitizer.bypassSecurityTrustUrl(avatarSource.getAvatar(+this.size()));
            this.avatarAlt = avatarSource.getAvatar(+this.size());
        }
    }
    /**
     *
     * returns initials style
     *
     * memberOf AvatarComponent
     */
    getInitialsStyle(avatarValue) {
        const borderColor = this.borderColor();
        const bgColor = this.bgColor();
        const hasCornerRadius = !this.round() || +this.cornerRadius() > 0;
        const hasCustomFgColor = this.fgColor() !== '#FFF';
        return {
            textAlign: 'center',
            borderRadius: hasCornerRadius ? (this.round() ? '100%' : this.cornerRadius() + 'px') : undefined,
            border: borderColor ? '1px solid ' + borderColor : undefined,
            textTransform: 'uppercase',
            color: hasCustomFgColor ? this.fgColor() : undefined,
            // Explicit `bgColor` wins; otherwise the hash colour is applied inline only
            // while `autoColor` is on. With `[autoColor]="false"` no inline background is
            // emitted, so `.avatar-content { background-color: var(--hub-avatar-bg-color, …) }`
            // takes over and the consumer can theme the avatar through the token.
            backgroundColor: bgColor ? bgColor : this.autoColor() ? this.avatarService.getRandomColor(avatarValue) : undefined,
            // Only the size is set inline (it scales with `size`); the family comes from
            // `.avatar-content { font-family: var(--hub-avatar-font-family, …) }` so the
            // initials honour the same token as the rest of the avatar (a `font` shorthand
            // here would pin Helvetica and shadow it).
            fontSize: Math.floor(+this.size() / this.textSizeRatio()) + 'px',
            lineHeight: this.size() + 'px',
            ...this.getCustomStyleObject()
        };
    }
    /**
     *
     * returns image style
     *
     * memberOf AvatarComponent
     */
    getImageStyle() {
        const borderColor = this.borderColor();
        const hasCornerRadius = !this.round() || +this.cornerRadius() > 0;
        return {
            maxWidth: '100%',
            borderRadius: hasCornerRadius ? (this.round() ? '50%' : this.cornerRadius() + 'px') : undefined,
            border: borderColor ? '1px solid ' + borderColor : undefined,
            width: this.size() + 'px',
            height: this.size() + 'px',
            ...this.getCustomStyleObject()
        };
    }
    getCustomStyleObject() {
        const customStyle = this.style();
        if (!customStyle) {
            return {};
        }
        if (typeof customStyle === 'string') {
            return this.parseInlineStyleString(customStyle);
        }
        return customStyle;
    }
    parseInlineStyleString(styleString) {
        const styleObject = {};
        styleString
            .split(';')
            .map((declaration) => declaration.trim())
            .filter((declaration) => declaration.length > 0)
            .forEach((declaration) => {
            const separatorIndex = declaration.indexOf(':');
            if (separatorIndex <= 0) {
                return;
            }
            const property = declaration.slice(0, separatorIndex).trim();
            const value = declaration.slice(separatorIndex + 1).trim();
            if (property && value) {
                styleObject[property] = value;
            }
        });
        return styleObject;
    }
    /**
     * Fetch avatar image asynchronously.
     *
     * param {Source} source represents avatar source
     * memberof AvatarComponent
     */
    fetchAndProcessAsyncAvatar(source) {
        if (this.avatarService.sourceHasFailedBefore(source)) {
            return;
        }
        this.avatarService
            .fetchAvatar(source.getAvatar(+this.size()))
            .pipe(takeWhile(() => this.isAlive), map((response) => source.processResponse(response, +this.size())))
            .subscribe({
            next: (avatarSrc) => (this.avatarSrc = avatarSrc),
            error: () => {
                this.fetchAvatarSource();
            }
        });
    }
    /**
     * Add avatar source
     *
     * param sourceType avatar source type e.g facebook,twitter, etc.
     * param sourceValue  source value e.g facebookId value, etc.
     */
    addSource(sourceType, sourceValue) {
        const source = this.sources.find((s) => s.sourceType === sourceType);
        if (source) {
            source.sourceId = sourceValue;
        }
        else {
            this.sources.push(this.sourceFactory.newInstance(sourceType, sourceValue));
        }
    }
    /**
     * Remove avatar source
     *
     * param sourceType avatar source type e.g facebook,twitter, etc.
     */
    removeSource(sourceType) {
        this.sources = this.sources.filter((source) => source.sourceType !== sourceType);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: AvatarComponent, deps: [{ token: SourceFactory }, { token: AvatarService }, { token: i3.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: AvatarComponent, isStandalone: true, selector: "hub-avatar", inputs: { round: { classPropertyName: "round", publicName: "round", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, textSizeRatio: { classPropertyName: "textSizeRatio", publicName: "textSizeRatio", isSignal: true, isRequired: false, transformFunction: null }, bgColor: { classPropertyName: "bgColor", publicName: "bgColor", isSignal: true, isRequired: false, transformFunction: null }, fgColor: { classPropertyName: "fgColor", publicName: "fgColor", isSignal: true, isRequired: false, transformFunction: null }, borderColor: { classPropertyName: "borderColor", publicName: "borderColor", isSignal: true, isRequired: false, transformFunction: null }, autoColor: { classPropertyName: "autoColor", publicName: "autoColor", isSignal: true, isRequired: false, transformFunction: null }, style: { classPropertyName: "style", publicName: "style", isSignal: true, isRequired: false, transformFunction: null }, cornerRadius: { classPropertyName: "cornerRadius", publicName: "cornerRadius", isSignal: true, isRequired: false, transformFunction: null }, facebook: { classPropertyName: "facebook", publicName: "facebookId", isSignal: true, isRequired: false, transformFunction: null }, gravatar: { classPropertyName: "gravatar", publicName: "gravatarId", isSignal: true, isRequired: false, transformFunction: null }, github: { classPropertyName: "github", publicName: "githubId", isSignal: true, isRequired: false, transformFunction: null }, custom: { classPropertyName: "custom", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, customAlt: { classPropertyName: "customAlt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, initials: { classPropertyName: "initials", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, referrerpolicy: { classPropertyName: "referrerpolicy", publicName: "referrerpolicy", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, initialsSize: { classPropertyName: "initialsSize", publicName: "initialsSize", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, badgeColor: { classPropertyName: "badgeColor", publicName: "badgeColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clickOnAvatar: "clickOnAvatar" }, host: { properties: { "attr.data-badge-color": "badgeColor() || null", "style.--hub-avatar-badge-color": "badgeColorVar()", "style.--hub-avatar-size": "avatarSizePx" } }, viewQueries: [{ propertyName: "customContentRef", first: true, predicate: ["customContent"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
		<div (click)="onAvatarClicked()" class="avatar-container" [class.hub-avatar--custom]="hasCustomContent" [style]="hostStyle">
			<span #customContent class="hub-avatar__custom" [style]="customContentStyle"><ng-content></ng-content></span>
			@if (!hasCustomContent) {
				@if (avatarSrc) {
					<img
						[src]="avatarSrc"
						[alt]="customAlt() ? customAlt() : avatarAlt"
						[width]="size()"
						[height]="size()"
						[style]="avatarStyle"
						[referrerPolicy]="referrerpolicy()"
						(error)="fetchAvatarSource()"
						class="avatar-content"
						loading="lazy"
					/>
				} @else {
					@if (avatarText) {
						<div class="avatar-content" [style]="avatarStyle">
							{{ avatarText }}
						</div>
					}
				}
			}
		</div>
		@if (_hasBadge()) {
			<span
				class="hub-avatar__badge"
				[class.hub-avatar__badge--dot]="_isDot()"
				[class.hub-avatar__badge--label]="!_isDot()"
				[attr.aria-hidden]="_isDot() ? 'true' : null"
				>{{ _badgeText() }}</span
			>
		}
	`, isInline: true, styles: [":root,:host{--hub-avatar-size: 50px;--hub-avatar-overflow: hidden;--hub-avatar-border-radius-round: 50%;--hub-avatar-border-radius-square: var(--hub-ref-radius-sm, .25rem);--hub-avatar-border-radius: var( --hub-avatar-border-radius-round, var(--hub-avatar-border-radius-square, .25rem) );--hub-avatar-border-width-default: var(--hub-ref-border-width, 1px);--hub-avatar-border-width: 0;--hub-avatar-border-color: transparent;--hub-avatar-accent: var(--hub-sys-color-primary, #0d6efd);--hub-avatar-accent-emphasis: color-mix(in oklch, var(--hub-avatar-accent) 80%, var(--hub-sys-color-ink, #212529));--hub-avatar-accent-subtle: color-mix(in oklch, var(--hub-avatar-accent) 12%, var(--hub-sys-surface-page, #fff));--hub-avatar-accent-on: oklch(from var(--hub-avatar-accent) clamp(0, (.62 - l) * 1000, 1) 0 h);--hub-avatar-fg-color: var(--hub-avatar-accent-on, var(--hub-ref-color-white, #fff));--hub-avatar-bg-color: var(--hub-avatar-accent, var(--hub-sys-color-primary, #0d6efd));--hub-avatar-font-family: var( --hub-ref-font-family-base, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif );--hub-avatar-font-weight: var(--hub-ref-font-weight-base, 400);--hub-avatar-font-size: calc(var(--hub-avatar-size, 50px) / 3);--hub-avatar-line-height: var(--hub-avatar-size, 50px);--hub-avatar-text-transform: uppercase;--hub-avatar-text-align: center;--hub-avatar-object-fit: cover;--hub-avatar-content-padding: calc(var(--hub-avatar-size, 50px) * .2);--hub-avatar-content-icon-size: calc(var(--hub-avatar-size, 50px) * .55);--hub-avatar-badge-size: calc(var(--hub-avatar-size, 50px) * .28);--hub-avatar-badge-offset: 0px;--hub-avatar-badge-ring-width: max(2px, calc(var(--hub-avatar-size, 50px) * .05));--hub-avatar-badge-ring-color: var(--hub-sys-surface-page, #fff);--hub-avatar-badge-color: var(--hub-sys-color-secondary, #6c757d);--hub-avatar-badge-text-color: var(--hub-ref-color-white, #fff);--hub-avatar-badge-font-size: calc(var(--hub-avatar-size, 50px) * .22);--hub-avatar-badge-padding: calc(var(--hub-avatar-size, 50px) * .08);--hub-avatar-group-overlap: calc(var(--hub-avatar-size, 50px) * .3);--hub-avatar-group-ring-width: max(2px, calc(var(--hub-avatar-size, 50px) * .04));--hub-avatar-group-ring-color: var(--hub-sys-surface-page, #fff)}:host{display:inline-block;position:relative;border-radius:var(--hub-avatar-border-radius, 50%)}.avatar-container{width:var(--hub-avatar-size, 50px);height:var(--hub-avatar-size, 50px);border-radius:var(--hub-avatar-border-radius, 50%);overflow:var(--hub-avatar-overflow, hidden)}.avatar-content{width:100%;height:100%;max-width:100%;box-sizing:border-box;border-radius:var(--hub-avatar-border-radius, 50%);border:var(--hub-avatar-border-width, 0) solid var(--hub-avatar-border-color, transparent);color:var(--hub-avatar-fg-color, var(--hub-ref-color-white, #fff));background-color:var(--hub-avatar-bg-color, var(--hub-sys-surface-page, #fff));font-family:var(--hub-avatar-font-family, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif);font-size:var(--hub-avatar-font-size, calc(var(--hub-avatar-size, 50px) / 3));font-weight:var(--hub-avatar-font-weight, var(--hub-ref-font-weight-base, 400));line-height:var(--hub-avatar-line-height, var(--hub-avatar-size, 50px));text-transform:var(--hub-avatar-text-transform, uppercase);text-align:var(--hub-avatar-text-align, center)}div.avatar-content{display:flex;align-items:center;justify-content:center}img.avatar-content{display:block;object-fit:var(--hub-avatar-object-fit, cover);background-color:transparent}.hub-avatar__custom{display:none}.hub-avatar--custom .hub-avatar__custom{display:flex;align-items:center;justify-content:center;width:100%;height:100%;box-sizing:border-box;padding:var(--hub-avatar-content-padding, calc(var(--hub-avatar-size, 50px) * .2));border-radius:inherit;overflow:hidden;background-color:var(--hub-avatar-bg-color, var(--hub-sys-color-primary, #0d6efd));color:var(--hub-avatar-fg-color, var(--hub-ref-color-white, #fff));font-size:var(--hub-avatar-content-icon-size, calc(var(--hub-avatar-size, 50px) * .55));line-height:1}.hub-avatar--custom .hub-avatar__custom>*{line-height:1}.hub-avatar--custom .hub-avatar__custom ::ng-deep svg,.hub-avatar--custom .hub-avatar__custom ::ng-deep img{display:block;width:100%;height:100%;max-width:100%;max-height:100%;object-fit:contain}.hub-avatar__badge{position:absolute;inset-block-end:var(--hub-avatar-badge-offset, 0px);inset-inline-end:var(--hub-avatar-badge-offset, 0px);box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;background:var(--hub-avatar-badge-color, var(--hub-sys-color-secondary, #6c757d));color:var(--hub-avatar-badge-text-color, var(--hub-ref-color-white, #fff));box-shadow:0 0 0 var(--hub-avatar-badge-ring-width, 2px) var(--hub-avatar-badge-ring-color, var(--hub-sys-surface-page, #fff))}.hub-avatar__badge--dot{width:var(--hub-avatar-badge-size, calc(var(--hub-avatar-size, 50px) * .28));height:var(--hub-avatar-badge-size, calc(var(--hub-avatar-size, 50px) * .28));border-radius:50%}.hub-avatar__badge--label{min-width:var(--hub-avatar-badge-size, calc(var(--hub-avatar-size, 50px) * .28));height:var(--hub-avatar-badge-size, calc(var(--hub-avatar-size, 50px) * .28));padding-inline:var(--hub-avatar-badge-padding, calc(var(--hub-avatar-size, 50px) * .08));border-radius:var(--hub-sys-radius-pill, 50rem);font-family:var(--hub-avatar-font-family, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif);font-size:var(--hub-avatar-badge-font-size, calc(var(--hub-avatar-size, 50px) * .22));font-weight:var(--hub-ref-font-weight-bold, 700);line-height:1}:host([data-badge-color=primary]){--hub-avatar-badge-color: var(--hub-sys-color-primary)}:host(.hub-avatar--primary){--hub-avatar-accent: var(--hub-sys-color-primary)}:host([data-badge-color=secondary]){--hub-avatar-badge-color: var(--hub-sys-color-secondary)}:host(.hub-avatar--secondary){--hub-avatar-accent: var(--hub-sys-color-secondary)}:host([data-badge-color=success]){--hub-avatar-badge-color: var(--hub-sys-color-success)}:host(.hub-avatar--success){--hub-avatar-accent: var(--hub-sys-color-success)}:host([data-badge-color=danger]){--hub-avatar-badge-color: var(--hub-sys-color-danger)}:host(.hub-avatar--danger){--hub-avatar-accent: var(--hub-sys-color-danger)}:host([data-badge-color=warning]){--hub-avatar-badge-color: var(--hub-sys-color-warning)}:host(.hub-avatar--warning){--hub-avatar-accent: var(--hub-sys-color-warning)}:host([data-badge-color=info]){--hub-avatar-badge-color: var(--hub-sys-color-info)}:host(.hub-avatar--info){--hub-avatar-accent: var(--hub-sys-color-info)}:host([data-badge-color=neutral]){--hub-avatar-badge-color: var(--hub-sys-color-neutral)}:host(.hub-avatar--neutral){--hub-avatar-accent: var(--hub-sys-color-neutral)}:host([data-badge-color=light]){--hub-avatar-badge-color: var(--hub-sys-color-light)}:host(.hub-avatar--light){--hub-avatar-accent: var(--hub-sys-color-light)}:host([data-badge-color=dark]){--hub-avatar-badge-color: var(--hub-sys-color-dark)}:host(.hub-avatar--dark){--hub-avatar-accent: var(--hub-sys-color-dark)}:host([data-badge-color=warning]),:host([data-badge-color=light]){--hub-avatar-badge-text-color: var(--hub-sys-text-primary, #212529)}.hub-avatar-group{display:inline-flex;align-items:center}.hub-avatar-group hub-avatar{box-shadow:0 0 0 var(--hub-avatar-group-ring-width, 2px) var(--hub-avatar-group-ring-color, var(--hub-sys-surface-page, #fff))}.hub-avatar-group hub-avatar+hub-avatar{margin-inline-start:calc(-1 * var(--hub-avatar-group-overlap, 15px))}\n"] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: AvatarComponent, decorators: [{
            type: Component,
            args: [{ selector: 'hub-avatar', standalone: true, template: `
		<div (click)="onAvatarClicked()" class="avatar-container" [class.hub-avatar--custom]="hasCustomContent" [style]="hostStyle">
			<span #customContent class="hub-avatar__custom" [style]="customContentStyle"><ng-content></ng-content></span>
			@if (!hasCustomContent) {
				@if (avatarSrc) {
					<img
						[src]="avatarSrc"
						[alt]="customAlt() ? customAlt() : avatarAlt"
						[width]="size()"
						[height]="size()"
						[style]="avatarStyle"
						[referrerPolicy]="referrerpolicy()"
						(error)="fetchAvatarSource()"
						class="avatar-content"
						loading="lazy"
					/>
				} @else {
					@if (avatarText) {
						<div class="avatar-content" [style]="avatarStyle">
							{{ avatarText }}
						</div>
					}
				}
			}
		</div>
		@if (_hasBadge()) {
			<span
				class="hub-avatar__badge"
				[class.hub-avatar__badge--dot]="_isDot()"
				[class.hub-avatar__badge--label]="!_isDot()"
				[attr.aria-hidden]="_isDot() ? 'true' : null"
				>{{ _badgeText() }}</span
			>
		}
	`, host: {
                        '[attr.data-badge-color]': 'badgeColor() || null',
                        '[style.--hub-avatar-badge-color]': 'badgeColorVar()',
                        '[style.--hub-avatar-size]': 'avatarSizePx'
                    }, styles: [":root,:host{--hub-avatar-size: 50px;--hub-avatar-overflow: hidden;--hub-avatar-border-radius-round: 50%;--hub-avatar-border-radius-square: var(--hub-ref-radius-sm, .25rem);--hub-avatar-border-radius: var( --hub-avatar-border-radius-round, var(--hub-avatar-border-radius-square, .25rem) );--hub-avatar-border-width-default: var(--hub-ref-border-width, 1px);--hub-avatar-border-width: 0;--hub-avatar-border-color: transparent;--hub-avatar-accent: var(--hub-sys-color-primary, #0d6efd);--hub-avatar-accent-emphasis: color-mix(in oklch, var(--hub-avatar-accent) 80%, var(--hub-sys-color-ink, #212529));--hub-avatar-accent-subtle: color-mix(in oklch, var(--hub-avatar-accent) 12%, var(--hub-sys-surface-page, #fff));--hub-avatar-accent-on: oklch(from var(--hub-avatar-accent) clamp(0, (.62 - l) * 1000, 1) 0 h);--hub-avatar-fg-color: var(--hub-avatar-accent-on, var(--hub-ref-color-white, #fff));--hub-avatar-bg-color: var(--hub-avatar-accent, var(--hub-sys-color-primary, #0d6efd));--hub-avatar-font-family: var( --hub-ref-font-family-base, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif );--hub-avatar-font-weight: var(--hub-ref-font-weight-base, 400);--hub-avatar-font-size: calc(var(--hub-avatar-size, 50px) / 3);--hub-avatar-line-height: var(--hub-avatar-size, 50px);--hub-avatar-text-transform: uppercase;--hub-avatar-text-align: center;--hub-avatar-object-fit: cover;--hub-avatar-content-padding: calc(var(--hub-avatar-size, 50px) * .2);--hub-avatar-content-icon-size: calc(var(--hub-avatar-size, 50px) * .55);--hub-avatar-badge-size: calc(var(--hub-avatar-size, 50px) * .28);--hub-avatar-badge-offset: 0px;--hub-avatar-badge-ring-width: max(2px, calc(var(--hub-avatar-size, 50px) * .05));--hub-avatar-badge-ring-color: var(--hub-sys-surface-page, #fff);--hub-avatar-badge-color: var(--hub-sys-color-secondary, #6c757d);--hub-avatar-badge-text-color: var(--hub-ref-color-white, #fff);--hub-avatar-badge-font-size: calc(var(--hub-avatar-size, 50px) * .22);--hub-avatar-badge-padding: calc(var(--hub-avatar-size, 50px) * .08);--hub-avatar-group-overlap: calc(var(--hub-avatar-size, 50px) * .3);--hub-avatar-group-ring-width: max(2px, calc(var(--hub-avatar-size, 50px) * .04));--hub-avatar-group-ring-color: var(--hub-sys-surface-page, #fff)}:host{display:inline-block;position:relative;border-radius:var(--hub-avatar-border-radius, 50%)}.avatar-container{width:var(--hub-avatar-size, 50px);height:var(--hub-avatar-size, 50px);border-radius:var(--hub-avatar-border-radius, 50%);overflow:var(--hub-avatar-overflow, hidden)}.avatar-content{width:100%;height:100%;max-width:100%;box-sizing:border-box;border-radius:var(--hub-avatar-border-radius, 50%);border:var(--hub-avatar-border-width, 0) solid var(--hub-avatar-border-color, transparent);color:var(--hub-avatar-fg-color, var(--hub-ref-color-white, #fff));background-color:var(--hub-avatar-bg-color, var(--hub-sys-surface-page, #fff));font-family:var(--hub-avatar-font-family, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif);font-size:var(--hub-avatar-font-size, calc(var(--hub-avatar-size, 50px) / 3));font-weight:var(--hub-avatar-font-weight, var(--hub-ref-font-weight-base, 400));line-height:var(--hub-avatar-line-height, var(--hub-avatar-size, 50px));text-transform:var(--hub-avatar-text-transform, uppercase);text-align:var(--hub-avatar-text-align, center)}div.avatar-content{display:flex;align-items:center;justify-content:center}img.avatar-content{display:block;object-fit:var(--hub-avatar-object-fit, cover);background-color:transparent}.hub-avatar__custom{display:none}.hub-avatar--custom .hub-avatar__custom{display:flex;align-items:center;justify-content:center;width:100%;height:100%;box-sizing:border-box;padding:var(--hub-avatar-content-padding, calc(var(--hub-avatar-size, 50px) * .2));border-radius:inherit;overflow:hidden;background-color:var(--hub-avatar-bg-color, var(--hub-sys-color-primary, #0d6efd));color:var(--hub-avatar-fg-color, var(--hub-ref-color-white, #fff));font-size:var(--hub-avatar-content-icon-size, calc(var(--hub-avatar-size, 50px) * .55));line-height:1}.hub-avatar--custom .hub-avatar__custom>*{line-height:1}.hub-avatar--custom .hub-avatar__custom ::ng-deep svg,.hub-avatar--custom .hub-avatar__custom ::ng-deep img{display:block;width:100%;height:100%;max-width:100%;max-height:100%;object-fit:contain}.hub-avatar__badge{position:absolute;inset-block-end:var(--hub-avatar-badge-offset, 0px);inset-inline-end:var(--hub-avatar-badge-offset, 0px);box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;background:var(--hub-avatar-badge-color, var(--hub-sys-color-secondary, #6c757d));color:var(--hub-avatar-badge-text-color, var(--hub-ref-color-white, #fff));box-shadow:0 0 0 var(--hub-avatar-badge-ring-width, 2px) var(--hub-avatar-badge-ring-color, var(--hub-sys-surface-page, #fff))}.hub-avatar__badge--dot{width:var(--hub-avatar-badge-size, calc(var(--hub-avatar-size, 50px) * .28));height:var(--hub-avatar-badge-size, calc(var(--hub-avatar-size, 50px) * .28));border-radius:50%}.hub-avatar__badge--label{min-width:var(--hub-avatar-badge-size, calc(var(--hub-avatar-size, 50px) * .28));height:var(--hub-avatar-badge-size, calc(var(--hub-avatar-size, 50px) * .28));padding-inline:var(--hub-avatar-badge-padding, calc(var(--hub-avatar-size, 50px) * .08));border-radius:var(--hub-sys-radius-pill, 50rem);font-family:var(--hub-avatar-font-family, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif);font-size:var(--hub-avatar-badge-font-size, calc(var(--hub-avatar-size, 50px) * .22));font-weight:var(--hub-ref-font-weight-bold, 700);line-height:1}:host([data-badge-color=primary]){--hub-avatar-badge-color: var(--hub-sys-color-primary)}:host(.hub-avatar--primary){--hub-avatar-accent: var(--hub-sys-color-primary)}:host([data-badge-color=secondary]){--hub-avatar-badge-color: var(--hub-sys-color-secondary)}:host(.hub-avatar--secondary){--hub-avatar-accent: var(--hub-sys-color-secondary)}:host([data-badge-color=success]){--hub-avatar-badge-color: var(--hub-sys-color-success)}:host(.hub-avatar--success){--hub-avatar-accent: var(--hub-sys-color-success)}:host([data-badge-color=danger]){--hub-avatar-badge-color: var(--hub-sys-color-danger)}:host(.hub-avatar--danger){--hub-avatar-accent: var(--hub-sys-color-danger)}:host([data-badge-color=warning]){--hub-avatar-badge-color: var(--hub-sys-color-warning)}:host(.hub-avatar--warning){--hub-avatar-accent: var(--hub-sys-color-warning)}:host([data-badge-color=info]){--hub-avatar-badge-color: var(--hub-sys-color-info)}:host(.hub-avatar--info){--hub-avatar-accent: var(--hub-sys-color-info)}:host([data-badge-color=neutral]){--hub-avatar-badge-color: var(--hub-sys-color-neutral)}:host(.hub-avatar--neutral){--hub-avatar-accent: var(--hub-sys-color-neutral)}:host([data-badge-color=light]){--hub-avatar-badge-color: var(--hub-sys-color-light)}:host(.hub-avatar--light){--hub-avatar-accent: var(--hub-sys-color-light)}:host([data-badge-color=dark]){--hub-avatar-badge-color: var(--hub-sys-color-dark)}:host(.hub-avatar--dark){--hub-avatar-accent: var(--hub-sys-color-dark)}:host([data-badge-color=warning]),:host([data-badge-color=light]){--hub-avatar-badge-text-color: var(--hub-sys-text-primary, #212529)}.hub-avatar-group{display:inline-flex;align-items:center}.hub-avatar-group hub-avatar{box-shadow:0 0 0 var(--hub-avatar-group-ring-width, 2px) var(--hub-avatar-group-ring-color, var(--hub-sys-surface-page, #fff))}.hub-avatar-group hub-avatar+hub-avatar{margin-inline-start:calc(-1 * var(--hub-avatar-group-overlap, 15px))}\n"] }]
        }], ctorParameters: () => [{ type: SourceFactory }, { type: AvatarService }, { type: i3.DomSanitizer }], propDecorators: { round: [{ type: i0.Input, args: [{ isSignal: true, alias: "round", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], textSizeRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "textSizeRatio", required: false }] }], bgColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "bgColor", required: false }] }], fgColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "fgColor", required: false }] }], borderColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderColor", required: false }] }], autoColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoColor", required: false }] }], style: [{ type: i0.Input, args: [{ isSignal: true, alias: "style", required: false }] }], cornerRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "cornerRadius", required: false }] }], facebook: [{ type: i0.Input, args: [{ isSignal: true, alias: "facebookId", required: false }] }], gravatar: [{ type: i0.Input, args: [{ isSignal: true, alias: "gravatarId", required: false }] }], github: [{ type: i0.Input, args: [{ isSignal: true, alias: "githubId", required: false }] }], custom: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], customAlt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: false }] }], initials: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], referrerpolicy: [{ type: i0.Input, args: [{ isSignal: true, alias: "referrerpolicy", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], initialsSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialsSize", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], badgeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "badgeColor", required: false }] }], clickOnAvatar: [{ type: i0.Output, args: ["clickOnAvatar"] }], customContentRef: [{
                type: ViewChild,
                args: ['customContent', { static: true }]
            }] } });

/**
 * Backward-compatibility module for `<hub-avatar>`.
 *
 * @deprecated `AvatarComponent` is now a standalone component. Import it directly
 * (`imports: [AvatarComponent]`) and, if you need custom configuration, register
 * `provideAvatar()` in your application providers. This module only re-exports the
 * standalone component and will be removed in a future major version.
 */
class AvatarModule {
    /**
     * @deprecated Use `provideAvatar(config)` with the standalone APIs instead.
     * Kept so existing `AvatarModule.forRoot()` consumers keep working.
     */
    static forRoot(avatarConfig) {
        return {
            ngModule: AvatarModule,
            providers: [
                {
                    provide: AVATAR_CONFIG,
                    useValue: avatarConfig ? avatarConfig : {}
                }
            ]
        };
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: AvatarModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
    static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.1", ngImport: i0, type: AvatarModule, imports: [AvatarComponent], exports: [AvatarComponent] });
    static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: AvatarModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: AvatarModule, decorators: [{
            type: NgModule,
            args: [{
                    imports: [AvatarComponent],
                    exports: [AvatarComponent]
                }]
        }] });

/**
 * Registers the avatar configuration for standalone applications.
 *
 * Standalone-friendly replacement for `AvatarModule.forRoot()`. Add it to your
 * `bootstrapApplication` providers (or a route's `providers`) to customise the
 * avatar source priority, colour palette or src-cache behaviour. Calling it is
 * optional — `<hub-avatar>` works out of the box with sensible defaults.
 *
 * ```ts
 * import { provideAvatar } from 'ng-hub-ui-avatar';
 *
 * bootstrapApplication(AppComponent, {
 *   providers: [
 *     provideAvatar({ sourcePriorityOrder: [AvatarSource.GRAVATAR, AvatarSource.INITIALS] })
 *   ]
 * });
 * ```
 *
 * @param config Optional avatar configuration.
 * @returns Environment providers to add to the application config.
 */
function provideAvatar(config) {
    return makeEnvironmentProviders([
        {
            provide: AVATAR_CONFIG,
            useValue: config ?? {}
        }
    ]);
}

/*
 * Public API Surface of ng-hub-ui-avatar
 */

/**
 * Generated bundle index. Do not edit.
 */

export { AvatarComponent, AvatarModule, AvatarService, AvatarSource, defaultColors, defaultDisableSrcCache, defaultSources, provideAvatar };
//# sourceMappingURL=ng-hub-ui-avatar.mjs.map