@nysds/nys-avatar 1.18.1 → 1.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,24 @@
1
- import { LitElement as g, unsafeCSS as m, html as l } from "lit";
2
- import { property as e, state as b } from "lit/decorators.js";
3
- import { ifDefined as y } from "lit/directives/if-defined.js";
1
+ import { LitElement as g, unsafeCSS as b, html as l } from "lit";
2
+ import { property as s, state as m } from "lit/decorators.js";
3
+ import { ifDefined as c } from "lit/directives/if-defined.js";
4
4
  /*!
5
5
  * █▄ █ █ █ █▀▀▀█ █▀▀▄ █▀▀▀█
6
6
  * █ █ █ █▄▄▄█ ▀▀▀▄▄ █ █ ▀▀▀▄▄
7
7
  * █ ▀█ █ █▄▄▄█ █▄▄▀ █▄▄▄█
8
8
  *
9
- * Avatar Component v1.18.1
9
+ * Avatar Component v1.18.3
10
10
  * Part of the New York State Design System
11
11
  * Repository: https://github.com/its-hcd/nysds
12
12
  * License: MIT
13
13
  */
14
- const x = ":host{--_nys-avatar-border-radius: var(--nys-radius-round, 1776px);--_nys-avatar-size: var(--nys-avatar-size, var(--nys-font-size-6xl, 36px));--_nys-avatar-shape: var(--nys-radius-round, 1776px);--_nys-avatar-border-color: var(--nys-color-ink-reverse, #ffffff);--_nys-avatar-border-size: var(--nys-border-width-sm, 1px);--_nys-avatar-width: var(--nys-font-size-6xl, 36px);--_nys-avatar-color: var(--nys-color-theme, #154973);--_nys-avatar-background-color: var(--nys-color-theme-weaker, #eff6fb);--_nys-avatar-outline-color: var(--nys-color-focus, #004dd1);--_nys-avatar-outline-width: var(--nys-border-width-md, 2px);--_nys-avatar-outline-offset: var(--nys-space-2px, 2px)}.nys-avatar{display:inline-block}.nys-avatar__component{display:flex;justify-content:center;align-items:center;border-radius:var(--_nys-avatar-border-radius);width:var(--_nys-avatar-size);height:var(--_nys-avatar-size);font-size:var(--_nys-avatar-size);overflow:hidden;box-sizing:border-box;color:var(--_nys-avatar-color);background-color:var(--_nys-avatar-background-color);border:var(--_nys-avatar-border-size) solid var(--_nys-avatar-border-color);outline-offset:var(--_nys-avatar-outline-offset);transition:all .15s ease-in-out;-webkit-user-select:none;user-select:none}:host([interactive]) .nys-avatar__component:hover,:host([interactive]) .nys-avatar__component:active{--_nys-avatar-color: var( --nys-color-text-reverse, --nys-color-ink-reverse, #ffffff );cursor:pointer}:host([interactive]) .nys-avatar__component:hover{--_nys-avatar-background-color: var(--nys-color-theme-mid, #457aa5)}:host([interactive]) .nys-avatar__component:active{--_nys-avatar-background-color: var(--nys-color-theme-strong, #0e324f)}:host([disabled]) .nys-avatar__component{--_nys-avatar-color: var(--nys-color-text-disabled, #bec0c1);--_nys-avatar-background-color: var(--nys-color-neutral-10, #f6f6f6);cursor:not-allowed}:host([disabled]) .nys-avatar__component:focus-within{outline:solid var(--_nys-avatar-outline-width) var(--_nys-avatar-outline-color)}div[part=nys-avatar__icon]{display:flex;align-items:center;justify-content:center}.nys-avatar__initials{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:calc(var(--_nys-avatar-width) * .5);font-weight:700;text-transform:uppercase}.nys-avatar__image{width:100%;height:100%;object-fit:cover}.nys-avatar__icon{width:100%;height:100%;fill:currentcolor}";
15
- var k = Object.defineProperty, a = (h, n, s, i) => {
16
- for (var r = void 0, o = h.length - 1, c; o >= 0; o--)
17
- (c = h[o]) && (r = c(n, s, r) || r);
18
- return r && k(n, s, r), r;
14
+ const x = ":host{--_nys-avatar-border-radius: var(--nys-radius-round, 1776px);--_nys-avatar-size: var(--nys-avatar-size, var(--nys-font-size-6xl, 36px));--_nys-avatar-shape: var(--nys-radius-round, 1776px);--_nys-avatar-border-color: var(--nys-color-ink-reverse, #ffffff);--_nys-avatar-border-size: var(--nys-border-width-sm, 1px);--_nys-avatar-width: var(--nys-font-size-6xl, 36px);--_nys-avatar-color: var(--nys-color-theme, #154973);--_nys-avatar-background-color: var(--nys-color-theme-weaker, #eff6fb);--_nys-avatar-outline-color: var(--nys-color-focus, #004dd1);--_nys-avatar-outline-width: var(--nys-border-width-md, 2px);--_nys-avatar-outline-offset: var(--nys-space-2px, 2px)}.nys-avatar{display:inline-block}.nys-avatar__component{display:flex;justify-content:center;align-items:center;border-radius:var(--_nys-avatar-border-radius);width:var(--_nys-avatar-size);height:var(--_nys-avatar-size);font-size:var(--_nys-avatar-size);overflow:hidden;box-sizing:border-box;color:var(--_nys-avatar-color);background-color:var(--_nys-avatar-background-color);border:var(--_nys-avatar-border-size) solid var(--_nys-avatar-border-color);outline-offset:var(--_nys-avatar-outline-offset);transition:all .15s ease-in-out;-webkit-user-select:none;user-select:none}:host([interactive]:not([disabled])) button.nys-avatar__component{appearance:none;border:var(--_nys-avatar-border-size) solid var(--_nys-avatar-border-color);padding:0;cursor:pointer}:host([interactive]:not([disabled])) button.nys-avatar__component:hover{--_nys-avatar-background-color: var(--nys-color-theme-mid, #457aa5);--_nys-avatar-color: var(--nys-color-text-reverse, #ffffff)}:host([interactive]:not([disabled])) button.nys-avatar__component:active{--_nys-avatar-background-color: var(--nys-color-theme-strong, #0e324f);--_nys-avatar-color: var(--nys-color-text-reverse, #ffffff)}:host([interactive]:not([disabled])) button.nys-avatar__component:focus-visible{outline:solid var(--_nys-avatar-outline-width) var(--_nys-avatar-outline-color);outline-offset:var(--_nys-avatar-outline-offset)}:host([disabled]) .nys-avatar__component{--_nys-avatar-color: var(--nys-color-text-disabled, #bec0c1);--_nys-avatar-background-color: var(--nys-color-neutral-10, #f6f6f6);cursor:not-allowed}:host([disabled]) .nys-avatar__component:focus-within{outline:solid var(--_nys-avatar-outline-width) var(--_nys-avatar-outline-color)}div[part=nys-avatar__icon]{display:flex;align-items:center;justify-content:center}.nys-avatar__initials{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:calc(var(--_nys-avatar-width) * .5);font-weight:700;text-transform:uppercase}.nys-avatar__image{width:100%;height:100%;object-fit:cover}.nys-avatar__icon{width:100%;height:100%;fill:currentcolor}";
15
+ var $ = Object.defineProperty, r = (h, t, o, n) => {
16
+ for (var e = void 0, i = h.length - 1, v; i >= 0; i--)
17
+ (v = h[i]) && (e = v(t, o, e) || e);
18
+ return e && $(t, o, e), e;
19
19
  };
20
20
  let w = 0;
21
- const d = class d extends g {
21
+ const y = class y extends g {
22
22
  constructor() {
23
23
  super(...arguments), this.id = "", this.ariaLabel = "", this.image = "", this.initials = "", this.icon = "", this.color = "", this.interactive = !1, this.disabled = !1, this.lazy = !1, this._slotHasContent = !1;
24
24
  }
@@ -30,21 +30,29 @@ const d = class d extends g {
30
30
  super.connectedCallback(), this.id || (this.id = `nys-avatar-${Date.now()}-${w++}`);
31
31
  }
32
32
  async _handleSlotChange() {
33
- const n = this.shadowRoot?.querySelector("slot");
34
- if (!n) {
33
+ const t = this.shadowRoot?.querySelector("slot");
34
+ if (!t) {
35
35
  this._slotHasContent = !1;
36
36
  return;
37
37
  }
38
38
  await Promise.resolve();
39
- const s = n.assignedNodes({ flatten: !0 }).filter(
40
- (i) => i.nodeType === Node.ELEMENT_NODE || i.nodeType === Node.TEXT_NODE && i.textContent?.trim()
39
+ const o = t.assignedNodes({ flatten: !0 }).filter(
40
+ (n) => n.nodeType === Node.ELEMENT_NODE || n.nodeType === Node.TEXT_NODE && n.textContent?.trim()
41
41
  );
42
- this._slotHasContent = s.length > 0;
42
+ this._slotHasContent = o.length > 0;
43
43
  }
44
44
  /**
45
45
  * Functions
46
46
  * --------------------------------------------------------------------------
47
47
  */
48
+ get _cleanAriaLabel() {
49
+ return (this.ariaLabel ?? "").replace(/[\s\u00a0]+/g, " ").trim();
50
+ }
51
+ _colorStyle() {
52
+ if (!this.color) return "";
53
+ const t = this.getContrastForeground() ?? "";
54
+ return `--_nys-avatar-background-color: ${this.color}; --_nys-avatar-color: ${t}; color: ${t}`;
55
+ }
48
56
  /**
49
57
  * Computes the appropriate foreground color (icon or initials)
50
58
  * based on the avatar's background color for sufficient contrast.
@@ -52,93 +60,94 @@ const d = class d extends g {
52
60
  * @returns CSS color string for foreground
53
61
  */
54
62
  getContrastForeground() {
55
- const n = "var(--nys-color-ink, #000)", s = "var(--nys-color-ink-reverse, #fff)", i = "var(--nys-color-text, #000)", r = "var(--nys-color-text-reverse, #fff)";
63
+ const t = "var(--nys-color-ink, #000)", o = "var(--nys-color-ink-reverse, #fff)", n = "var(--nys-color-text, #000)", e = "var(--nys-color-text-reverse, #fff)";
56
64
  if (!this.color) return;
57
- const o = document.createElement("div");
58
- o.style.color = this.color, document.body.appendChild(o);
59
- const c = getComputedStyle(o).color;
60
- document.body.removeChild(o);
61
- const v = c.match(/\d+/g);
62
- if (!v) return;
63
- const _ = Number(v[0]), p = Number(v[1]), u = Number(v[2]), f = (0.299 * _ + 0.587 * p + 0.114 * u) / 255 < 0.5;
64
- return this.initials?.length > 0 ? f ? r : i : f ? s : n;
65
+ const i = document.createElement("div");
66
+ i.style.color = this.color, document.body.appendChild(i);
67
+ const v = getComputedStyle(i).color;
68
+ document.body.removeChild(i);
69
+ const d = v.match(/\d+/g);
70
+ if (!d) return;
71
+ const _ = Number(d[0]), u = Number(d[1]), p = Number(d[2]), f = (0.299 * _ + 0.587 * u + 0.114 * p) / 255 < 0.5;
72
+ return this.initials?.length > 0 ? f ? e : n : f ? o : t;
65
73
  }
66
74
  render() {
75
+ const t = this._cleanAriaLabel, o = this._colorStyle(), n = this.image?.length > 0 ? l`<img
76
+ part="nys-avatar__image"
77
+ class="nys-avatar__image"
78
+ src=${this.image}
79
+ alt=${t || ""}
80
+ loading=${this.lazy ? "lazy" : "eager"}
81
+ />` : this.initials?.length > 0 ? l`<span
82
+ part="nys-avatar__initials"
83
+ class="nys-avatar__initials"
84
+ aria-hidden="true"
85
+ >${this.initials}</span
86
+ >` : l`<div part="nys-avatar__icon">
87
+ <slot @slotchange=${this._handleSlotChange}></slot>
88
+ ${this._slotHasContent ? null : l`<nys-icon
89
+ label="nys-avatar__icon"
90
+ name=${this.icon?.length > 0 ? this.icon : "account_circle"}
91
+ ></nys-icon>`}
92
+ </div>`, e = this.interactive ? l`<button
93
+ part="nys-avatar"
94
+ class="nys-avatar__component"
95
+ style=${c(o || void 0)}
96
+ aria-label=${c(t || void 0)}
97
+ ?disabled=${this.disabled}
98
+ >
99
+ ${n}
100
+ </button>` : l`<div
101
+ part="nys-avatar"
102
+ class="nys-avatar__component"
103
+ style=${c(o || void 0)}
104
+ role=${c(this.image ? void 0 : t ? "img" : void 0)}
105
+ aria-label=${c(this.image ? void 0 : t || void 0)}
106
+ aria-hidden=${c(this.image || t ? void 0 : "true")}
107
+ >
108
+ ${n}
109
+ </div>`;
67
110
  return l`
68
111
  <div class="nys-avatar">
69
- <div class="nys-avatar__content">
70
- <div
71
- part="nys-avatar"
72
- class="nys-avatar__component"
73
- style=${this.color ? `--_nys-avatar-background-color: ${this.color}; color: ${this.getContrastForeground()}` : ""}
74
- role=${y(
75
- this.interactive ? "button" : this.image ? void 0 : "img"
76
- )}
77
- aria-label=${y(
78
- this.image ? void 0 : this.ariaLabel ? this.ariaLabel : "avatar"
79
- )}
80
- tabindex=${y(
81
- this.interactive && !this.disabled ? 0 : void 0
82
- )}
83
- >
84
- ${this.image?.length > 0 ? l`<img
85
- part="nys-avatar__image"
86
- class="nys-avatar__image"
87
- src=${this.image}
88
- alt=${this.ariaLabel || "avatar"}
89
- loading=${this.lazy ? "lazy" : "eager"}
90
- />` : this.initials?.length > 0 ? l`<span
91
- part="nys-avatar__initials"
92
- class="nys-avatar__initials"
93
- aria-hidden="true"
94
- >${this.initials}</span
95
- >` : l`<div part="nys-avatar__icon">
96
- <slot @slotchange=${this._handleSlotChange}></slot>
97
- ${this._slotHasContent ? null : l`<nys-icon
98
- label="nys-avatar__icon"
99
- name=${this.icon?.length > 0 ? this.icon : "account_circle"}
100
- ></nys-icon>`}
101
- </div>`}
102
- </div>
103
- </div>
112
+ <div class="nys-avatar__content">${e}</div>
104
113
  </div>
105
114
  `;
106
115
  }
107
116
  };
108
- d.styles = m(x);
109
- let t = d;
110
- a([
111
- e({ type: String, reflect: !0 })
112
- ], t.prototype, "id");
113
- a([
114
- e({ type: String })
115
- ], t.prototype, "ariaLabel");
116
- a([
117
- e({ type: String })
118
- ], t.prototype, "image");
119
- a([
120
- e({ type: String })
121
- ], t.prototype, "initials");
122
- a([
123
- e({ type: String })
124
- ], t.prototype, "icon");
125
- a([
126
- e({ type: String })
127
- ], t.prototype, "color");
128
- a([
129
- e({ type: Boolean, reflect: !0 })
130
- ], t.prototype, "interactive");
131
- a([
132
- e({ type: Boolean, reflect: !0 })
133
- ], t.prototype, "disabled");
134
- a([
135
- e({ type: Boolean, reflect: !0 })
136
- ], t.prototype, "lazy");
137
- a([
138
- b()
139
- ], t.prototype, "_slotHasContent");
140
- customElements.get("nys-avatar") || customElements.define("nys-avatar", t);
117
+ y.styles = b(x);
118
+ let a = y;
119
+ r([
120
+ s({ type: String, reflect: !0 })
121
+ ], a.prototype, "id");
122
+ r([
123
+ s({ type: String })
124
+ ], a.prototype, "ariaLabel");
125
+ r([
126
+ s({ type: String })
127
+ ], a.prototype, "image");
128
+ r([
129
+ s({ type: String })
130
+ ], a.prototype, "initials");
131
+ r([
132
+ s({ type: String })
133
+ ], a.prototype, "icon");
134
+ r([
135
+ s({ type: String })
136
+ ], a.prototype, "color");
137
+ r([
138
+ s({ type: Boolean, reflect: !0 })
139
+ ], a.prototype, "interactive");
140
+ r([
141
+ s({ type: Boolean, reflect: !0 })
142
+ ], a.prototype, "disabled");
143
+ r([
144
+ s({ type: Boolean, reflect: !0 })
145
+ ], a.prototype, "lazy");
146
+ r([
147
+ m()
148
+ ], a.prototype, "_slotHasContent");
149
+ customElements.get("nys-avatar") || customElements.define("nys-avatar", a);
141
150
  export {
142
- t as NysAvatar
151
+ a as NysAvatar
143
152
  };
144
153
  //# sourceMappingURL=nys-avatar.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nys-avatar.js","sources":["../src/nys-avatar.ts"],"sourcesContent":["import { LitElement, html, unsafeCSS } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { ifDefined } from \"lit/directives/if-defined.js\";\n// @ts-ignore: SCSS module imported via bundler as inline\nimport styles from \"./nys-avatar.scss?inline\";\n\nlet avatarIdCounter = 0;\n\n/**\n * Displays a user representation as image, initials, or icon with automatic fallback chain.\n *\n * Priority: `image` > `initials` > `icon` > default icon. Set `color` to customize background;\n * foreground auto-adjusts for contrast. Use `interactive` for clickable avatars (e.g., profile menus).\n *\n * @summary User avatar with image, initials, or icon fallback and contrast-aware colors.\n * @element nys-avatar\n *\n * @slot - Custom icon content. Overrides default icon when no image or initials.\n *\n * @example Image avatar\n * ```html\n * <nys-avatar image=\"/path/to/photo.jpg\" ariaLabel=\"Jane Smith\"></nys-avatar>\n * ```\n *\n * @example Initials avatar with custom color\n * ```html\n * <nys-avatar initials=\"JS\" color=\"var(--nys-color-theme)\" ariaLabel=\"Jane Smith\"></nys-avatar>\n * ```\n */\n\nexport class NysAvatar extends LitElement {\n static styles = unsafeCSS(styles);\n\n /** Unique identifier. Auto-generated if not provided. */\n @property({ type: String, reflect: true }) id = \"\";\n\n /** Accessible label for screen readers. Required when no image `alt` is available. */\n @property({ type: String }) ariaLabel = \"\";\n\n /** Image URL. Takes priority over initials and icon. */\n @property({ type: String }) image = \"\";\n\n /** 1-2 character initials. Used when no image is provided. */\n @property({ type: String }) initials = \"\";\n\n /** Custom icon name. Falls back to `account_circle` if not set. */\n @property({ type: String }) icon = \"\";\n\n /** Background color. Foreground auto-adjusts for contrast. Accepts CSS values or variables. */\n @property({ type: String }) color = \"\";\n\n /** Makes avatar clickable with button role and focus ring. */\n @property({ type: Boolean, reflect: true }) interactive = false;\n\n /** Prevents interaction when `interactive` is true. */\n @property({ type: Boolean, reflect: true }) disabled = false;\n\n /** Enables lazy loading for the image. */\n @property({ type: Boolean, reflect: true }) lazy = false;\n @state() private _slotHasContent = false;\n\n /**\n * Lifecycle methods\n * --------------------------------------------------------------------------\n */\n\n connectedCallback() {\n super.connectedCallback();\n if (!this.id) {\n this.id = `nys-avatar-${Date.now()}-${avatarIdCounter++}`;\n }\n }\n\n private async _handleSlotChange() {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>(\"slot\");\n if (!slot) {\n this._slotHasContent = false;\n return;\n }\n\n await Promise.resolve();\n\n const assignedNodes = slot\n .assignedNodes({ flatten: true })\n .filter(\n (node) =>\n node.nodeType === Node.ELEMENT_NODE ||\n (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()),\n );\n\n this._slotHasContent = assignedNodes.length > 0;\n }\n\n /**\n * Functions\n * --------------------------------------------------------------------------\n */\n\n /**\n * Computes the appropriate foreground color (icon or initials)\n * based on the avatar's background color for sufficient contrast.\n *\n * @returns CSS color string for foreground\n */\n private getContrastForeground() {\n // Default NYSDS CSS vars for foreground.\n // Contrast must return =>\n // IF icon: \"--nys-color-ink-reverse\" or \"--nys-color-ink\"\n // If initials: \"--nys-color-text-reverse\" or \"--nys-color-text\"\n const fgIconDark = \"var(--nys-color-ink, #000)\";\n const fgIconLight = \"var(--nys-color-ink-reverse, #fff)\";\n const fgInitialDark = \"var(--nys-color-text, #000)\";\n const fgInitialLight = \"var(--nys-color-text-reverse, #fff)\";\n\n if (!this.color) return;\n\n // Create a temporary element to compute luminance (this is in case user pass in \"var(--nys-color-stuff)\")\n const div = document.createElement(\"div\");\n div.style.color = this.color;\n document.body.appendChild(div);\n const computedColor = getComputedStyle(div).color;\n document.body.removeChild(div);\n\n // Parse RGB\n const match = computedColor.match(/\\d+/g);\n if (!match) return;\n\n const r = Number(match[0]);\n const g = Number(match[1]);\n const b = Number(match[2]);\n\n // Calculate relative luminance\n const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;\n\n const isDark = luminance < 0.5;\n if (this.initials?.length > 0) {\n return isDark ? fgInitialLight : fgInitialDark;\n } else {\n return isDark ? fgIconLight : fgIconDark;\n }\n }\n\n render() {\n return html`\n <div class=\"nys-avatar\">\n <div class=\"nys-avatar__content\">\n <div\n part=\"nys-avatar\"\n class=\"nys-avatar__component\"\n style=${this.color\n ? `--_nys-avatar-background-color: ${this.color}; color: ${this.getContrastForeground()}`\n : \"\"}\n role=${ifDefined(\n this.interactive ? \"button\" : this.image ? undefined : \"img\",\n )}\n aria-label=${ifDefined(\n this.image\n ? undefined\n : this.ariaLabel\n ? this.ariaLabel\n : \"avatar\",\n )}\n tabindex=${ifDefined(\n this.interactive && !this.disabled ? 0 : undefined,\n )}\n >\n ${this.image?.length > 0\n ? html`<img\n part=\"nys-avatar__image\"\n class=\"nys-avatar__image\"\n src=${this.image}\n alt=${this.ariaLabel || \"avatar\"}\n loading=${this.lazy ? \"lazy\" : \"eager\"}\n />`\n : this.initials?.length > 0\n ? html`<span\n part=\"nys-avatar__initials\"\n class=\"nys-avatar__initials\"\n aria-hidden=\"true\"\n >${this.initials}</span\n >`\n : html`<div part=\"nys-avatar__icon\">\n <slot @slotchange=${this._handleSlotChange}></slot>\n ${!this._slotHasContent\n ? html`<nys-icon\n label=\"nys-avatar__icon\"\n name=${this.icon?.length > 0\n ? this.icon\n : \"account_circle\"}\n ></nys-icon>`\n : null}\n </div>`}\n </div>\n </div>\n </div>\n `;\n }\n}\n\nif (!customElements.get(\"nys-avatar\")) {\n customElements.define(\"nys-avatar\", NysAvatar);\n}\n"],"names":["avatarIdCounter","_NysAvatar","LitElement","slot","assignedNodes","node","fgIconDark","fgIconLight","fgInitialDark","fgInitialLight","div","computedColor","match","r","g","b","isDark","html","ifDefined","unsafeCSS","styles","NysAvatar","__decorateClass","property","state"],"mappings":"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"}
1
+ {"version":3,"file":"nys-avatar.js","sources":["../src/nys-avatar.ts"],"sourcesContent":["import { LitElement, html, unsafeCSS } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { ifDefined } from \"lit/directives/if-defined.js\";\n// @ts-ignore: SCSS module imported via bundler as inline\nimport styles from \"./nys-avatar.scss?inline\";\n\nlet avatarIdCounter = 0;\n\n/**\n * Displays a user representation as image, initials, or icon with automatic fallback chain.\n *\n * Priority: `image` > `initials` > `icon` > default icon. Set `color` to customize background;\n * foreground auto-adjusts for contrast. Use `interactive` for clickable avatars (e.g., profile menus).\n *\n * @summary User avatar with image, initials, or icon fallback and contrast-aware colors.\n * @element nys-avatar\n *\n * @slot - Custom icon content. Overrides default icon when no image or initials.\n *\n * @example Image avatar\n * ```html\n * <nys-avatar image=\"/path/to/photo.jpg\" ariaLabel=\"Jane Smith\"></nys-avatar>\n * ```\n *\n * @example Initials avatar with custom color\n * ```html\n * <nys-avatar initials=\"JS\" color=\"var(--nys-color-theme)\" ariaLabel=\"Jane Smith\"></nys-avatar>\n * ```\n */\n\nexport class NysAvatar extends LitElement {\n static styles = unsafeCSS(styles);\n\n /** Unique identifier. Auto-generated if not provided. */\n @property({ type: String, reflect: true }) id = \"\";\n\n /** Accessible label for screen readers. Required when no image `alt` is available. */\n @property({ type: String }) ariaLabel = \"\";\n\n /** Image URL. Takes priority over initials and icon. */\n @property({ type: String }) image = \"\";\n\n /** 1-2 character initials. Used when no image is provided. */\n @property({ type: String }) initials = \"\";\n\n /** Custom icon name. Falls back to `account_circle` if not set. */\n @property({ type: String }) icon = \"\";\n\n /** Background color. Foreground auto-adjusts for contrast. Accepts CSS values or variables. */\n @property({ type: String }) color = \"\";\n\n /** Makes avatar clickable with button role and focus ring. */\n @property({ type: Boolean, reflect: true }) interactive = false;\n\n /** Prevents interaction when `interactive` is true. */\n @property({ type: Boolean, reflect: true }) disabled = false;\n\n /** Enables lazy loading for the image. */\n @property({ type: Boolean, reflect: true }) lazy = false;\n @state() private _slotHasContent = false;\n\n /**\n * Lifecycle methods\n * --------------------------------------------------------------------------\n */\n\n connectedCallback() {\n super.connectedCallback();\n if (!this.id) {\n this.id = `nys-avatar-${Date.now()}-${avatarIdCounter++}`;\n }\n }\n\n private async _handleSlotChange() {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>(\"slot\");\n if (!slot) {\n this._slotHasContent = false;\n return;\n }\n\n await Promise.resolve();\n\n const assignedNodes = slot\n .assignedNodes({ flatten: true })\n .filter(\n (node) =>\n node.nodeType === Node.ELEMENT_NODE ||\n (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()),\n );\n\n this._slotHasContent = assignedNodes.length > 0;\n }\n\n /**\n * Functions\n * --------------------------------------------------------------------------\n */\n\n private get _cleanAriaLabel(): string {\n return (this.ariaLabel ?? \"\").replace(/[\\s\\u00a0]+/g, \" \").trim();\n }\n\n private _colorStyle(): string {\n if (!this.color) return \"\";\n const fg = this.getContrastForeground() ?? \"\";\n return `--_nys-avatar-background-color: ${this.color}; --_nys-avatar-color: ${fg}; color: ${fg}`;\n }\n\n /**\n * Computes the appropriate foreground color (icon or initials)\n * based on the avatar's background color for sufficient contrast.\n *\n * @returns CSS color string for foreground\n */\n private getContrastForeground() {\n // Default NYSDS CSS vars for foreground.\n // Contrast must return =>\n // IF icon: \"--nys-color-ink-reverse\" or \"--nys-color-ink\"\n // If initials: \"--nys-color-text-reverse\" or \"--nys-color-text\"\n const fgIconDark = \"var(--nys-color-ink, #000)\";\n const fgIconLight = \"var(--nys-color-ink-reverse, #fff)\";\n const fgInitialDark = \"var(--nys-color-text, #000)\";\n const fgInitialLight = \"var(--nys-color-text-reverse, #fff)\";\n\n if (!this.color) return;\n\n // Create a temporary element to compute luminance (this is in case user pass in \"var(--nys-color-stuff)\")\n const div = document.createElement(\"div\");\n div.style.color = this.color;\n document.body.appendChild(div);\n const computedColor = getComputedStyle(div).color;\n document.body.removeChild(div);\n\n // Parse RGB\n const match = computedColor.match(/\\d+/g);\n if (!match) return;\n\n const r = Number(match[0]);\n const g = Number(match[1]);\n const b = Number(match[2]);\n\n // Calculate relative luminance\n const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;\n\n const isDark = luminance < 0.5;\n if (this.initials?.length > 0) {\n return isDark ? fgInitialLight : fgInitialDark;\n } else {\n return isDark ? fgIconLight : fgIconDark;\n }\n }\n\n render() {\n const label = this._cleanAriaLabel;\n const colorStyle = this._colorStyle();\n\n const avatarContent =\n this.image?.length > 0\n ? html`<img\n part=\"nys-avatar__image\"\n class=\"nys-avatar__image\"\n src=${this.image}\n alt=${label || \"\"}\n loading=${this.lazy ? \"lazy\" : \"eager\"}\n />`\n : this.initials?.length > 0\n ? html`<span\n part=\"nys-avatar__initials\"\n class=\"nys-avatar__initials\"\n aria-hidden=\"true\"\n >${this.initials}</span\n >`\n : html`<div part=\"nys-avatar__icon\">\n <slot @slotchange=${this._handleSlotChange}></slot>\n ${!this._slotHasContent\n ? html`<nys-icon\n label=\"nys-avatar__icon\"\n name=${this.icon?.length > 0 ? this.icon : \"account_circle\"}\n ></nys-icon>`\n : null}\n </div>`;\n\n const container = this.interactive\n ? html`<button\n part=\"nys-avatar\"\n class=\"nys-avatar__component\"\n style=${ifDefined(colorStyle || undefined)}\n aria-label=${ifDefined(label || undefined)}\n ?disabled=${this.disabled}\n >\n ${avatarContent}\n </button>`\n : html`<div\n part=\"nys-avatar\"\n class=\"nys-avatar__component\"\n style=${ifDefined(colorStyle || undefined)}\n role=${ifDefined(this.image ? undefined : label ? \"img\" : undefined)}\n aria-label=${ifDefined(this.image ? undefined : label || undefined)}\n aria-hidden=${ifDefined(this.image || label ? undefined : \"true\")}\n >\n ${avatarContent}\n </div>`;\n\n return html`\n <div class=\"nys-avatar\">\n <div class=\"nys-avatar__content\">${container}</div>\n </div>\n `;\n }\n}\n\nif (!customElements.get(\"nys-avatar\")) {\n customElements.define(\"nys-avatar\", NysAvatar);\n}\n"],"names":["avatarIdCounter","_NysAvatar","LitElement","slot","assignedNodes","node","fg","fgIconDark","fgIconLight","fgInitialDark","fgInitialLight","div","computedColor","match","r","g","b","isDark","label","colorStyle","avatarContent","html","container","ifDefined","unsafeCSS","styles","NysAvatar","__decorateClass","property","state"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nysds/nys-avatar",
3
- "version": "1.18.1",
3
+ "version": "1.18.3",
4
4
  "description": "The Avatar component from the NYS Design System.",
5
5
  "module": "dist/nys-avatar.js",
6
6
  "exports": {
@@ -22,7 +22,7 @@
22
22
  "lit-analyze": "lit-analyzer '**/*.ts'"
23
23
  },
24
24
  "dependencies": {
25
- "@nysds/nys-icon": "^1.18.1"
25
+ "@nysds/nys-icon": "^1.18.3"
26
26
  },
27
27
  "devDependencies": {
28
28
  "lit": "^3.3.1",