UNPKG

vue3-avatar

Version:

A lightweight, fully customizable, accessible, and SSR-safe user avatar component for Vue 3 and Nuxt. Supports initials, custom images, pixel-art generation (identicons), groups with overflow, and auto-contrast text. Perfect for user profiles, team displa

1,067 lines (941 loc) 37.5 kB
'use strict';var vue=require('vue');function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function () {}; return { s: F, n: function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function (e) { throw e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function () { it = it.call(o); }, n: function () { var step = it.next(); normalCompletion = step.done; return step; }, e: function (e) { didErr = true; err = e; }, f: function () { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }function getInitials(name) { if (!name || typeof name !== 'string') return ''; var words = name.trim().split(/[- ]/); // Filter out empty strings from multiple spaces words = words.filter(function (word) { return word !== ""; }); if (words.length === 0) return ""; if (words.length >= 3) { return words[0][0].toUpperCase() + words[1][0].toUpperCase() + words[words.length - 1][0].toUpperCase(); } else if (words.length === 2) { return words[0][0].toUpperCase() + words[1][0].toUpperCase(); } else if (words.length === 1) { return words[0][0].toUpperCase(); } return ""; }var lightColors = ["#F0F8FF", "#FAEBD7", "#00FFFF", "#7FFFD4", "#F0FFFF", "#F5F5DC", "#FFE4C4", "#FFEBCD", "#DEB887", "#5F9EA0", "#7FFF00", "#D2691E", "#FF7F50", "#6495ED", "#FFF8DC", "#00FFFF", "#B8860B", "#A9A9A9", "#A9A9A9", "#BDB76B", "#FF8C00", "#E9967A", "#8FBC8F", "#00CED1", "#FF1493", "#00BFFF", "#1E90FF", "#FFFAF0", "#FF00FF", "#DCDCDC", "#F8F8FF", "#FFD700", "#DAA520", "#808080", "#808080", "#ADFF2F", "#F0FFF0", "#FF69B4", "#CD5C5C", "#FFFFF0", "#F0E68C", "#E6E6FA", "#FFF0F5", "#7CFC00", "#FFFACD", "#ADD8E6", "#F08080", "#E0FFFF", "#FAFAD2", "#D3D3D3", "#D3D3D3", "#90EE90", "#FFB6C1", "#FFA07A", "#20B2AA", "#87CEFA", "#B0C4DE", "#FFFFE0", "#00FF00", "#32CD32", "#FAF0E6", "#FF00FF", "#66CDAA", "#BA55D3", "#9370D8", "#3CB371", "#7B68EE", "#00FA9A", "#48D1CC", "#F5FFFA", "#FFE4E1", "#FFE4B5", "#FFDEAD", "#FDF5E6", "#FFA500", "#FF4500", "#DA70D6", "#EEE8AA", "#98FB98", "#AFEEEE", "#D87093", "#FFEFD5", "#FFDAB9", "#CD853F", "#FFC0CB", "#DDA0DD", "#B0E0E6", "#FF0000", "#BC8F8F", "#FA8072", "#F4A460", "#FFF5EE", "#C0C0C0", "#87CEEB", "#FFFAFA", "#00FF7F", "#D2B48C", "#D8BFD8", "#FF6347", "#40E0D0", "#EE82EE", "#F5DEB3", "#FFFFFF", "#F5F5F5", "#FFFF00", "#9ACD32"]; var darkColors = ["#000000", "#0000FF", "#8A2BE2", "#A52A2A", "#DC143C", "#00008B", "#008B8B", "#006400", "#8B008B", "#556B2F", "#9932CC", "#8B0000", "#483D8B", "#2F4F4F", "#2F4F4F", "#9400D3", "#696969", "#696969", "#B22222", "#228B22", "#008000", "#4B0082", "#800000", "#0000CD", "#C71585", "#191970", "#000080", "#808000", "#6B8E23", "#800080", "#4169E1", "#8B4513", "#2E8B57", "#A0522D", "#6A5ACD", "#708090", "#708090", "#4682B4", "#008080"]; var legacyBackgroundColors = ['#F44336', '#FF4081', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B']; function getAsciiValue(name) { if (!name) return 0; var username = name.trim(); var ascii = 0; for (var index = 0; index < username.length; index++) { ascii += username.charCodeAt(index); } return ascii; } function lightenColor(hex, amt) { if (!/^#[0-9A-Fa-f]{6}$/i.test(hex)) { return '#FFFFFF'; } var r = parseInt(hex.slice(1, 3), 16); var g = parseInt(hex.slice(3, 5), 16); var b = parseInt(hex.slice(5, 7), 16); var newR = Math.min(255, r + amt); var newG = Math.min(255, g + amt); var newB = Math.min(255, b + amt); var toHex = function toHex(n) { return n.toString(16).padStart(2, '0').toUpperCase(); }; return "#".concat(toHex(newR)).concat(toHex(newG)).concat(toHex(newB)); } function getAvatarColors(name) { var useLegacyColors = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var ascii = getAsciiValue(name); if (useLegacyColors) { var backgroundColor = legacyBackgroundColors[0]; if (name && typeof name === 'string') { var index = (name.length || 0) % legacyBackgroundColors.length; backgroundColor = legacyBackgroundColors[index]; } var color = lightenColor(backgroundColor, 80); return { background: backgroundColor, color: color }; } // Modern logic var darkColor = darkColors[ascii % darkColors.length]; var lightColor = lightColors[ascii % lightColors.length]; // Secondary color for gradients var secondaryColor = darkColors[(ascii + 5) % darkColors.length]; var gradient = "linear-gradient(135deg, ".concat(darkColor, " 0%, ").concat(secondaryColor, " 100%)"); // Default: Dark Background, Light Text return { background: darkColor, color: lightColor, gradient: gradient, // We also expose the pair if needed for inversion light: lightColor, dark: darkColor }; }/** * Generate a deterministic 8x8 pixel grid from a string * @param {string} str - Input string to hash * @returns {boolean[][]} - 8x8 grid of boolean values */ function generatePixelGrid(str) { if (!str) return Array(8).fill(null).map(function () { return Array(8).fill(false); }); // Simple hash function var hash = 0; for (var i = 0; i < str.length; i++) { var char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; // Convert to 32-bit integer } // Generate symmetric 8x8 grid (mirror horizontally for better aesthetics) var grid = []; for (var y = 0; y < 8; y++) { var row = []; for (var x = 0; x < 4; x++) { // Use different parts of the hash for each position var bitIndex = (y * 4 + x) % 32; var bit = Math.abs(hash) >> bitIndex & 1; row.push(bit === 1); } // Mirror the row for symmetry grid.push([].concat(row, _toConsumableArray(row.reverse()))); } return grid; } /** * Color themes for pixel avatars */ var PIXEL_THEMES = { earth: { background: "#8B7355", foreground: "#D4A574" }, neon: { background: "#FF006E", foreground: "#00F5FF" }, ocean: { background: "#006994", foreground: "#4FC3F7" }, forest: { background: "#2D5016", foreground: "#7CB342" }, sunset: { background: "#FF6B35", foreground: "#FFD23F" }, midnight: { background: "#1A1A2E", foreground: "#16213E" }, candy: { background: "#FF69B4", foreground: "#FFB6C1" }, retro: { background: "#8B4513", foreground: "#DEB887" } }; /** * Generate SVG for pixel avatar * @param {boolean[][]} grid - 8x8 boolean grid * @param {object} theme - Color theme with background and foreground * @param {number} size - Size of the avatar * @returns {string} - SVG string */ function generatePixelSVG(grid, theme) { var size = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 40; var pixelSize = size / 8; var pixels = []; for (var y = 0; y < 8; y++) { for (var x = 0; x < 8; x++) { if (grid[y][x]) { pixels.push("<rect x=\"".concat(x * pixelSize, "\" y=\"").concat(y * pixelSize, "\" width=\"").concat(pixelSize, "\" height=\"").concat(pixelSize, "\" fill=\"").concat(theme.foreground, "\"/>")); } } } return "\n <svg width=\"".concat(size, "\" height=\"").concat(size, "\" viewBox=\"0 0 ").concat(size, " ").concat(size, "\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"").concat(size, "\" height=\"").concat(size, "\" fill=\"").concat(theme.background, "\"/>\n ").concat(pixels.join("\n "), "\n </svg>\n ").trim(); }/** * Calculate the relative luminance of a color * @param {string} color - Hex color string (e.g., '#FF5733') * @returns {number} - Luminance value between 0 and 1 */ function getLuminance(color) { // Remove # if present var hex = color.replace("#", ""); // Parse RGB values var r = parseInt(hex.substring(0, 2), 16) / 255; var g = parseInt(hex.substring(2, 4), 16) / 255; var b = parseInt(hex.substring(4, 6), 16) / 255; // Apply gamma correction var rLinear = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4); var gLinear = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4); var bLinear = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4); // Calculate relative luminance return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear; } /** * Calculate YIQ value for a color (simpler alternative to luminance) * @param {string} color - Hex color string * @returns {number} - YIQ value */ function getYIQ(color) { var hex = color.replace("#", ""); var r = parseInt(hex.substring(0, 2), 16); var g = parseInt(hex.substring(2, 4), 16); var b = parseInt(hex.substring(4, 6), 16); return (r * 299 + g * 587 + b * 114) / 1000; } /** * Get contrasting text color (black or white) based on background * @param {string} backgroundColor - Hex color string * @param {string} method - 'luminance' or 'yiq' (default: 'yiq') * @returns {string} - '#FFFFFF' or '#000000' */ function getContrastColor(backgroundColor) { var method = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "yiq"; if (!backgroundColor || !backgroundColor.startsWith("#")) { return "#FFFFFF"; // Default to white for invalid colors } if (method === "luminance") { var luminance = getLuminance(backgroundColor); // Threshold of 0.5 works well for most cases return luminance > 0.5 ? "#000000" : "#FFFFFF"; } else { var yiq = getYIQ(backgroundColor); // YIQ threshold of 128 is standard return yiq >= 128 ? "#000000" : "#FFFFFF"; } }// Symbol for provide/inject var AvatarConfigKey = Symbol("AvatarConfig");var _hoisted_1 = ["role", "tabindex", "aria-label", "title", "onKeydown"]; var _hoisted_2 = ["height", "width", "src", "loading"]; var _hoisted_3 = ["innerHTML"]; var script$1 = { __name: 'Avatar', props: { name: { type: String, required: true }, color: { type: String }, background: { type: String }, size: { type: Number, default: 40 }, dark: { type: Boolean, default: false }, inline: { type: Boolean, default: false }, rounded: { type: Boolean, default: true }, shape: { type: String, validator: function validator(value) { return ["circle", "square", "squircle", "hexagon"].includes(value); } }, imageSrc: { type: String }, alt: { type: String, default: undefined }, loading: { type: String, default: "lazy", validator: function validator(value) { return ["lazy", "eager"].includes(value); } }, transition: { type: Boolean, default: true }, border: { type: Boolean, default: true }, borderColor: { type: String, default: "white" }, customAvatarStyle: { type: Object, default: function _default() { return {}; } }, status: { type: String, default: null, validator: function validator(value) { return ["away", "online", "offline", "busy"].includes(value); } }, statusPosition: { type: String, default: "bottom-right", validator: function validator(value) { return ["top-right", "top-left", "bottom-right", "bottom-left"].includes(value); } }, customStatusStyle: { type: Object, default: function _default() { return {}; } }, sameBorder: { type: Boolean, default: false }, interactive: { type: Boolean, default: false }, /** * @deprecated Use original vue-avatar color palette for backwards compatibility */ useLegacyColors: { type: Boolean, default: false }, useTextColorForBorder: { type: Boolean, default: false }, gradient: { type: Boolean, default: false }, pointer: { type: Boolean, default: false }, onClick: { type: Function, default: null }, variant: { type: String, default: "initials", validator: function validator(value) { return ["initials", "pixel"].includes(value); } }, pixelTheme: { type: String, default: "earth", validator: function validator(value) { return Object.keys(PIXEL_THEMES).includes(value); } }, autoContrast: { type: Boolean, default: false } }, emits: ["error", "activate", "load"], setup: function setup(__props, _ref) { var __emit = _ref.emit; var BORDERCOLORS = { ONLINE: "green", OFFLINE: "grey", AWAY: "orange", BUSY: "red" }; var props = __props; var emit = __emit; var imageError = vue.ref(false); var isLoaded = vue.ref(false); var globalConfig = vue.inject(AvatarConfigKey, {}); var getConfig = function getConfig(key, localValue, defaultValue) { if (localValue !== undefined && localValue !== defaultValue) return localValue; return globalConfig[key] !== undefined ? globalConfig[key] : defaultValue; }; var isClickable = vue.computed(function () { return props.pointer || props.interactive || typeof props.onClick === "function"; }); function onActivate(event) { if (typeof props.onClick === "function") { props.onClick(event); } if (props.interactive) { emit("activate", event); } } var computedColors = vue.computed(function () { return getAvatarColors(props.name, props.useLegacyColors); }); var displayName = vue.computed(function () { return getInitials(props.name); }); var pixelGrid = vue.computed(function () { return generatePixelGrid(props.name); }); var pixelSVG = vue.computed(function () { var baseTheme = PIXEL_THEMES[props.pixelTheme] || PIXEL_THEMES.earth; // Allow custom overrides via props var customBg = getConfig("background", props.background); var customColor = getConfig("color", props.color); var theme = { background: customBg || baseTheme.background, foreground: customColor || baseTheme.foreground }; // If no custom overrides, handle dark/light toggle // Consistent with initials: dark=true is dark background, dark=false is light background if (!customBg && !customColor && !props.dark) { theme = { background: baseTheme.foreground, foreground: baseTheme.background }; } return generatePixelSVG(pixelGrid.value, theme, props.size); }); var displayBackground = vue.computed(function () { var bg = getConfig("background", props.background); if (bg) return bg; var colors = computedColors.value; if (props.useLegacyColors) return colors.background; if (props.gradient && colors.gradient) return colors.gradient; return props.dark ? colors.dark : colors.light; }); var displayColor = vue.computed(function () { var color = getConfig("color", props.color); if (color) return color; // If auto-contrast is enabled, calculate based on background if (getConfig("autoContrast", props.autoContrast, false)) { var bg = displayBackground.value; // Only apply auto-contrast if background is a valid hex color if (bg && bg.startsWith("#")) { return getContrastColor(bg); } } var colors = computedColors.value; if (props.useLegacyColors) return colors.color; return props.dark ? colors.light : colors.dark; }); var displayBorderColor = vue.computed(function () { return props.useTextColorForBorder ? displayColor.value : getConfig("borderColor", props.borderColor, "white"); }); var fontSize = vue.computed(function () { var size = getConfig("size", props.size, 40); if (displayName.value.length == 1) return size / 2;else if (displayName.value.length == 2) return size / 2.5; if (displayName.value.length == 3) return size / 3;else return 14; }); var statusBackgroundColor = vue.computed(function () { var color; switch (props.status && props.status.toLowerCase()) { case "away": color = BORDERCOLORS.AWAY; break; case "online": color = BORDERCOLORS.ONLINE; break; case "offline": color = BORDERCOLORS.OFFLINE; break; default: color = BORDERCOLORS.BUSY; } return color; }); var statusStyle = vue.computed(function () { // Calculate position based on statusPosition prop var positionStyles = {}; var offset = 0; // Can be adjusted based on shape var pos = getConfig("statusPosition", props.statusPosition, "bottom-right"); if (pos.includes("bottom")) { positionStyles.bottom = "".concat(offset, "px"); positionStyles.top = "auto"; } else { positionStyles.top = "".concat(offset, "px"); positionStyles.bottom = "auto"; } if (pos.includes("right")) { positionStyles.right = "".concat(offset, "px"); positionStyles.left = "auto"; } else { positionStyles.left = "".concat(offset, "px"); positionStyles.right = "auto"; } var size = getConfig("size", props.size, 40); var defaultStatusStyle = _objectSpread2({ height: "".concat(size / 4, "px"), width: "".concat(size / 4, "px"), backgroundColor: statusBackgroundColor.value, border: "".concat(size / 30, "px solid ").concat(props.sameBorder ? displayBorderColor.value : "white") }, positionStyles); return Object.assign({}, defaultStatusStyle, props.customStatusStyle); }); var imageStyle = vue.computed(function () { var size = getConfig("size", props.size, 40); var defaultImageStyle = { display: props.inline ? "inline-flex" : "flex", borderRadius: shapeStyle.value.borderRadius, clipPath: shapeStyle.value.clipPath, margin: 0, padding: 0, alignItems: "center", justifyContent: "center", border: props.border ? "".concat(size / 20, "px solid ").concat(displayBorderColor.value) : "none" }; return Object.assign({}, defaultImageStyle, props.customAvatarStyle); }); var avatarStyle = vue.computed(function () { var size = getConfig("size", props.size, 40); var defaultAvatarStyle = { color: displayColor.value, width: size + "px", height: size + "px", fontSize: fontSize.value + "px", background: displayBackground.value, display: props.inline && "inline-flex", borderRadius: shapeStyle.value.borderRadius, clipPath: shapeStyle.value.clipPath, border: props.border && "".concat(size / 20, "px solid ").concat(displayBorderColor.value) }; return Object.assign({}, defaultAvatarStyle, props.customAvatarStyle); }); var shapeStyle = vue.computed(function () { var shape = props.shape || (props.rounded ? "circle" : "square"); if (shape === "square") return { borderRadius: "0" }; if (shape === "circle") return { borderRadius: "50%" }; if (shape === "squircle") return { borderRadius: "25%" }; if (shape === "hexagon") return { borderRadius: "0", clipPath: "polygon(25% 5%, 75% 5%, 95% 50%, 75% 95%, 25% 95%, 5% 50%)" }; return { borderRadius: "0" }; }); var rootStyle = vue.computed(function () { var size = getConfig("size", props.size, 40); return { "--va-size": "".concat(size, "px"), "--va-bg": displayBackground.value, "--va-color": displayColor.value, "--va-border-color": displayBorderColor.value, "--va-radius": shapeStyle.value.borderRadius, "--va-clip-path": shapeStyle.value.clipPath || "none", "--va-font-size": "".concat(fontSize.value, "px") }; }); var accessibleLabel = vue.computed(function () { var label = props.alt || (props.name ? "Avatar of ".concat(props.name) : "User avatar"); if (props.status) { return "".concat(label, ". User is ").concat(props.status); } return label; }); function onImageError(event) { imageError.value = true; isLoaded.value = false; emit("error", event); } function onImageLoad(event) { isLoaded.value = true; emit("load", event); } function showImage() { return props.imageSrc && !imageError.value; } return function (_ctx, _cache) { return vue.openBlock(), vue.createElementBlock("div", { class: vue.normalizeClass(["container", { 'is-clickable': isClickable.value }]), style: vue.normalizeStyle(rootStyle.value), role: isClickable.value ? 'button' : 'img', tabindex: isClickable.value ? 0 : undefined, "aria-label": accessibleLabel.value, title: __props.name, onClick: onActivate, onKeydown: [vue.withKeys(vue.withModifiers(onActivate, ["prevent"]), ["enter"]), vue.withKeys(vue.withModifiers(onActivate, ["prevent"]), ["space"])] }, [showImage() && _ctx.$slots.image ? vue.renderSlot(_ctx.$slots, "image", { src: __props.imageSrc, alt: accessibleLabel.value, size: __props.size, style: vue.normalizeStyle(imageStyle.value), class: vue.normalizeClass({ 'image-loaded': isLoaded.value, 'image-transition': __props.transition }), onError: onImageError, onLoad: onImageLoad }, undefined, undefined, 0) : showImage() ? (vue.openBlock(), vue.createElementBlock("img", { key: 1, style: vue.normalizeStyle(imageStyle.value), height: __props.size, width: __props.size, src: __props.imageSrc, loading: __props.loading, class: vue.normalizeClass({ 'image-loaded': isLoaded.value, 'image-transition': __props.transition }), alt: "", onError: onImageError, onLoad: onImageLoad }, null, 46, _hoisted_2)) : !__props.name && _ctx.$slots.placeholder ? vue.renderSlot(_ctx.$slots, "placeholder", { size: __props.size, style: vue.normalizeStyle(avatarStyle.value) }, undefined, undefined, 2) : __props.variant === 'pixel' ? (vue.openBlock(), vue.createElementBlock("div", { key: 3, style: vue.normalizeStyle(avatarStyle.value), class: "avatar avatar-pixel noselect", "aria-hidden": "true", innerHTML: pixelSVG.value }, null, 12, _hoisted_3)) : (vue.openBlock(), vue.createElementBlock("div", { key: 4, style: vue.normalizeStyle(avatarStyle.value), class: "avatar noselect", "aria-hidden": "true" }, vue.toDisplayString(displayName.value), 5)), __props.status || _ctx.$slots.status ? (vue.openBlock(), vue.createElementBlock("div", { key: 5, class: "status-indicator", style: vue.normalizeStyle(statusStyle.value), "aria-hidden": "true" }, [vue.renderSlot(_ctx.$slots, "status")], 4)) : vue.createCommentVNode("", true), vue.renderSlot(_ctx.$slots, "overlay")], 46, _hoisted_1); }; } };function styleInject(css, ref) { if ( ref === void 0 ) ref = {}; var insertAt = ref.insertAt; if (!css || typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } }var css_248z$1 = "\n@import url(\"https://fonts.googleapis.com/css2?family=Domine:wght@700&display=swap\");\n.avatar[data-v-a6123cce] {\n font-family: \"Domine\", serif;\n color: white;\n background: navy;\n font-size: 14px;\n width: 45px;\n height: 45px;\n border-radius: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n}\n.avatar-pixel[data-v-a6123cce] {\n padding: 0;\n overflow: hidden;\n}\n.avatar-pixel svg[data-v-a6123cce] {\n display: block;\n}\n.noselect[data-v-a6123cce] {\n -webkit-touch-callout: none; /* iOS Safari */\n -webkit-user-select: none; /* Safari */\n -khtml-user-select: none; /* Konqueror HTML */\n -moz-user-select: none; /* Old versions of Firefox */\n -ms-user-select: none; /* Internet Explorer/Edge */\n user-select: none; /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */\n}\n.container[data-v-a6123cce] {\n position: relative;\n}\n.container.is-clickable[data-v-a6123cce],\n.container.is-clickable[data-v-a6123cce] * {\n cursor: pointer !important;\n}\n.status-indicator[data-v-a6123cce] {\n position: absolute;\n border-radius: 50%;\n}\n.container img.image-transition[data-v-a6123cce] {\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n}\n.container img.image-transition.image-loaded[data-v-a6123cce] {\n opacity: 1;\n}\n"; styleInject(css_248z$1);script$1.__scopeId = "data-v-a6123cce";var script = vue.defineComponent({ name: "AvatarGroup", props: { max: { type: Number }, overlap: { type: Number, default: 10 }, borderColor: { type: String, default: "white" }, size: { type: Number, default: 40 }, layout: { type: String, default: "stack", // stack | triangle validator: function validator(value) { return ["stack", "triangle"].includes(value); } }, onClick: { type: Function, default: null }, pointer: { type: Boolean, default: false } }, emits: ["overflow-click"], setup: function setup(props, _ref) { var slots = _ref.slots, emit = _ref.emit; var flatten = function flatten(nodes) { var result = []; var _iterator = _createForOfIteratorHelper(nodes), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var node = _step.value; if (node.type === vue.Fragment && Array.isArray(node.children)) { result = result.concat(flatten(node.children)); } else if (node.type !== vue.Comment) { // Skip empty text nodes if (node.type === vue.Text) { if (node.children.trim().length === 0) continue; } result.push(node); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return result; }; var globalConfig = vue.inject(AvatarConfigKey, {}); var getConfig = function getConfig(key, localValue, defaultValue) { if (localValue !== undefined && localValue !== defaultValue) return localValue; return globalConfig[key] !== undefined ? globalConfig[key] : defaultValue; }; return function () { var defaultSlot = slots.default ? slots.default() : []; var children = flatten(defaultSlot); var visible = children; var overflowCount = 0; var effectiveMax = props.max; if (props.layout === "triangle") { var limit = props.max ? Math.min(props.max, 3) : 3; if (children.length > limit) { effectiveMax = limit - 1; } else { effectiveMax = limit; } } if (effectiveMax && children.length > effectiveMax) { visible = children.slice(0, effectiveMax); overflowCount = children.length - effectiveMax; } var allNames = children.map(function (child) { return child.props && child.props.name; }).filter(Boolean).join(", "); var hiddenChildren = overflowCount > 0 ? children.slice(effectiveMax) : []; var hiddenNames = hiddenChildren.map(function (child) { return child.props && child.props.name; }).filter(Boolean).join(", "); // Extract all user data var allUsers = children.map(function (child) { return child.props; }).filter(Boolean); // Extract hidden user data for the event payload var hiddenUsers = hiddenChildren.map(function (child) { return child.props; }).filter(Boolean); var handleOverflowClick = function handleOverflowClick(e) { e.stopPropagation(); emit("overflow-click", hiddenUsers, allUsers); }; var handleGroupKeydown = function handleGroupKeydown(event) { if (!props.onClick || !["Enter", " ", "Spacebar"].includes(event.key)) { return; } event.preventDefault(); props.onClick(event); }; var size = getConfig("size", props.size, 40); var borderColor = getConfig("borderColor", props.borderColor, "white"); var overlap = getConfig("overlap", props.overlap, 10); var overflowBadge = overflowCount > 0 ? vue.h("button", { type: "button", class: "avatar-overflow", title: hiddenNames, "aria-label": "Show ".concat(overflowCount, " more avatar").concat(overflowCount === 1 ? "" : "s").concat(hiddenNames ? ": ".concat(hiddenNames) : ""), onClick: handleOverflowClick, style: { width: "".concat(size, "px"), height: "".concat(size, "px"), fontSize: "".concat(size / 2.5, "px"), cursor: "pointer" } }, "+".concat(overflowCount)) : null; var visibleWithProps = visible.map(function (child) { return vue.cloneVNode(child, { size: size, borderColor: borderColor }); }); return vue.h("div", { class: ["avatar-group", "layout-".concat(props.layout), { "is-clickable": props.pointer || !!props.onClick }], title: allNames, role: props.onClick ? "button" : undefined, tabindex: props.onClick ? 0 : undefined, "aria-label": props.onClick ? "Avatar group".concat(allNames ? ": ".concat(allNames) : "") : undefined, onClick: function onClick(e) { return props.onClick && props.onClick(e); }, onKeydown: handleGroupKeydown, style: { "--va-group-overlap": "-".concat(overlap, "px"), "--va-size": "".concat(size, "px") } }, [].concat(_toConsumableArray(visibleWithProps), [overflowBadge])); }; } });var css_248z = "\n.avatar-group {\n display: flex;\n align-items: center;\n}\n.avatar-group.is-clickable {\n cursor: pointer;\n}\n.avatar-group.is-clickable * {\n cursor: pointer !important;\n}\n.avatar-group.layout-stack > * {\n margin-left: 0;\n}\n.avatar-group.layout-stack > * + * {\n margin-left: var(--va-group-overlap);\n}\n.avatar-group.layout-triangle {\n position: relative;\n display: inline-block;\n width: calc(\n var(--va-size) * 1.8\n ); /* Wider for better visibility of back avatars */\n height: calc(var(--va-size) * 1.45);\n}\n.avatar-group.layout-triangle > * {\n position: absolute !important;\n}\n.avatar-group.layout-triangle > *:nth-child(1) {\n top: 0;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10 !important;\n}\n.avatar-group.layout-triangle > *:nth-child(2) {\n bottom: 0;\n left: 0;\n z-index: 5 !important;\n}\n.avatar-group.layout-triangle > *:nth-child(3) {\n bottom: 0;\n right: 0;\n z-index: 1 !important;\n}\n.avatar-overflow {\n display: flex;\n align-items: center;\n justify-content: center;\n appearance: none;\n background: #ccc;\n color: white;\n border-radius: 50%;\n font-weight: bold;\n font-family: sans-serif;\n border: 2px solid white; /* Hardcoded default? */\n box-sizing: border-box;\n position: relative; /* To stack properly */\n padding: 0;\n}\n.avatar-overflow:focus-visible {\n outline: 2px solid #2563eb;\n outline-offset: 2px;\n}\n"; styleInject(css_248z);// IIFE injects install function into component, allowing component // to be registered via Vue.use() as well as Vue.component(), var install = function install(app) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; // Provide global configuration var globalDefaults = options.defaults || {}; app.provide(AvatarConfigKey, globalDefaults); app.component("Avatar", script$1); app.component("AvatarGroup", script); }; script$1.install = install;var namedExports=/*#__PURE__*/Object.freeze({__proto__:null,Avatar:script$1,AvatarGroup:script,AvatarConfigKey:AvatarConfigKey,'default':script$1});// only expose one global var, with named exports exposed as properties of // that global var (eg. plugin.namedExport) Object.entries(namedExports).forEach(function (_ref) { var _ref2 = _slicedToArray(_ref, 2), exportName = _ref2[0], exported = _ref2[1]; if (exportName !== 'default') script$1[exportName] = exported; });module.exports=script$1;