@c2n/avatar 0.0.8 → 0.0.10

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @c2n/avatar
2
2
 
3
- Avatar built with Lit: image, initials or any slotted content, with an optional status dot or badge.
3
+ Avatar components built with Lit: `c2-avatar` renders an image, initials or slotted content, while `c2-avatar-group` creates a responsive overlapping stack with an automatic overflow count.
4
4
 
5
5
  ```bash
6
6
  npm install @c2n/avatar
@@ -14,6 +14,14 @@ npm install @c2n/avatar
14
14
  <c2-avatar name="Ada Lovelace" initial-count="2"></c2-avatar>
15
15
  <c2-avatar name="Ada Lovelace" src="/ada.jpg" status="online"></c2-avatar>
16
16
  <c2-avatar name="Ada Lovelace" auto-color initial-count="2"></c2-avatar>
17
+ <c2-avatar name="Ada Lovelace" src="/ada.jpg" editable></c2-avatar>
18
+
19
+ <c2-avatar-group aria-label="Contributors" style="--c2-avatar-group--max-width: 160px">
20
+ <c2-avatar auto-color name="Ada Lovelace"></c2-avatar>
21
+ <c2-avatar auto-color name="Grace Hopper"></c2-avatar>
22
+ <c2-avatar auto-color name="Alan Turing"></c2-avatar>
23
+ <c2-avatar auto-color name="Katherine Johnson"></c2-avatar>
24
+ </c2-avatar-group>
17
25
  ```
18
26
 
19
27
  - **Content resolution**: the `src` image first (a failed load fires `error` and falls back to the initials), then slotted content (an icon, an emoji, an `<img>`), then the initials computed from `name`. `initial-count="2"` uses the first and last word of a longer name; `initials` overrides the computation.
@@ -21,5 +29,6 @@ npm install @c2n/avatar
21
29
  - **Status and badge**: `status="online|away|busy|offline"` draws a dot in the bottom-right corner; the `badge` slot replaces it with your own element (a count, an icon) and gets the same ring.
22
30
  - **Sizing**: one `--c2-avatar--size` token drives width, height and the font size (40% of the size by default); `--c2-avatar--width` and `--height` override it for non-square shapes.
23
31
  - **Accessibility**: the avatar is `role="img"` labelled by `alt` or `name`.
32
+ - **Editing**: `editable` lets the user choose, replace, and remove an image. Listen for `avatar-change` to upload its `detail.file`, and `avatar-remove` to delete the persisted image. The component immediately displays a local preview; use `accept` and `max-size` for client-side validation.
24
33
 
25
- Every visual aspect is a CSS custom property (`--c2-avatar--*`, `--c2-avatar__badge--*`, `--c2-avatar__image--*`). For stacked groups give each avatar `--c2-avatar--box-shadow: 0 0 0 2px <page background>` and a negative margin. The full list is in `custom-elements.json` and on the docs site.
34
+ Every visual aspect is a CSS custom property (`--c2-avatar--*`, `--c2-avatar-group--*`, `--c2-avatar__badge--*`, `--c2-avatar__image--*`). The full list is in `custom-elements.json` and on the docs site.
package/dist/avatar.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { LitElement, html, nothing, unsafeCSS } from "lit";
2
- import { property, state } from "lit/decorators.js";
2
+ import { property, query, state } from "lit/decorators.js";
3
3
  import { customElement } from "@c2n/core/element-helper.js";
4
4
  import { classMap } from "lit/directives/class-map.js";
5
5
  import { ifDefined } from "lit/directives/if-defined.js";
6
6
  //#region src/avatar.scss?inline
7
- var avatar_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: inline-flex;\n position: relative;\n vertical-align: middle;\n flex: none;\n}\n\n.c2-avatar {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n overflow: hidden;\n user-select: none;\n line-height: 1;\n width: var(--c2-avatar--width,var(--c2-avatar--size,32px));\n height: var(--c2-avatar--height,var(--c2-avatar--size,32px));\n font-size: var(--c2-avatar--font-size,calc(var(--c2-avatar--size, 32px) * 0.4));\n font-weight: var(--c2-avatar--font-weight,600);\n font-style: var(--c2-avatar--font-style);\n font-family: var(--c2-avatar--font-family);\n letter-spacing: var(--c2-avatar--letter-spacing,0.02em);\n border-top-left-radius: var(--c2-avatar--border-top-left-radius,999px);\n border-top-right-radius: var(--c2-avatar--border-top-right-radius,999px);\n border-bottom-left-radius: var(--c2-avatar--border-bottom-left-radius,999px);\n border-bottom-right-radius: var(--c2-avatar--border-bottom-right-radius,999px);\n border-top: var(--c2-avatar--border-top);\n border-bottom: var(--c2-avatar--border-bottom);\n border-right: var(--c2-avatar--border-right);\n border-left: var(--c2-avatar--border-left);\n background: var(--c2-avatar--background,rgb(0, 122, 77));\n color: var(--c2-avatar--color,#ffffff);\n box-shadow: var(--c2-avatar--box-shadow);\n}\n.c2-avatar ::slotted(svg),\n.c2-avatar ::slotted(img) {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: var(--c2-avatar__image--object-fit,cover);\n}\n.c2-avatar ::slotted(svg) {\n width: 60%;\n height: 60%;\n}\n\n:host([auto-color]) .c2-avatar {\n background: hsl(var(--c2-avatar--hue, 160), var(--c2-avatar__auto-color--saturation,55%), var(--c2-avatar__auto-color--lightness,42%));\n}\n\n.c2-avatar-image {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: var(--c2-avatar__image--object-fit,cover);\n}\n\n.c2-avatar-badge {\n position: absolute;\n right: calc(-1 * var(--c2-avatar__badge--offset,0px));\n bottom: calc(-1 * var(--c2-avatar__badge--offset,0px));\n display: flex;\n z-index: 1;\n line-height: 1;\n}\n.c2-avatar-badge::slotted(*) {\n box-sizing: border-box;\n border: var(--c2-avatar__badge--border,2px solid #ffffff);\n border-radius: 999px;\n}\n\n.c2-avatar-status {\n display: block;\n box-sizing: content-box;\n width: var(--c2-avatar__badge--size,10px);\n height: var(--c2-avatar__badge--size,10px);\n border: var(--c2-avatar__badge--border,2px solid #ffffff);\n border-radius: 999px;\n background: var(--c2-avatar__badge__offline--color,rgb(161, 161, 170));\n}\n\n:host([status=online]) .c2-avatar-status {\n background: var(--c2-avatar__badge__online--color,rgb(34, 197, 94));\n}\n\n:host([status=away]) .c2-avatar-status {\n background: var(--c2-avatar__badge__away--color,rgb(245, 158, 11));\n}\n\n:host([status=busy]) .c2-avatar-status {\n background: var(--c2-avatar__badge__busy--color,rgb(239, 68, 68));\n}";
7
+ var avatar_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: inline-flex;\n position: relative;\n vertical-align: middle;\n flex: none;\n}\n\ninput {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.c2-avatar {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n overflow: hidden;\n user-select: none;\n line-height: 1;\n width: var(--c2-avatar--width,var(--c2-avatar--size,32px));\n height: var(--c2-avatar--height,var(--c2-avatar--size,32px));\n font-size: var(--c2-avatar--font-size,calc(var(--c2-avatar--size, 32px) * 0.4));\n font-weight: var(--c2-avatar--font-weight,600);\n font-style: var(--c2-avatar--font-style);\n font-family: var(--c2-avatar--font-family);\n letter-spacing: var(--c2-avatar--letter-spacing,0.02em);\n border-top-left-radius: var(--c2-avatar--border-top-left-radius,999px);\n border-top-right-radius: var(--c2-avatar--border-top-right-radius,999px);\n border-bottom-left-radius: var(--c2-avatar--border-bottom-left-radius,999px);\n border-bottom-right-radius: var(--c2-avatar--border-bottom-right-radius,999px);\n border-top: var(--c2-avatar--border-top);\n border-bottom: var(--c2-avatar--border-bottom);\n border-right: var(--c2-avatar--border-right);\n border-left: var(--c2-avatar--border-left);\n background: var(--c2-avatar--background,rgb(0, 122, 77));\n color: var(--c2-avatar--color,#ffffff);\n box-shadow: var(--c2-avatar--box-shadow);\n}\n.c2-avatar ::slotted(svg),\n.c2-avatar ::slotted(img) {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: var(--c2-avatar__image--object-fit,cover);\n}\n.c2-avatar ::slotted(svg) {\n width: 60%;\n height: 60%;\n}\n\n:host([auto-color]) .c2-avatar {\n background: hsl(var(--c2-avatar--hue, 160), var(--c2-avatar__auto-color--saturation,55%), var(--c2-avatar__auto-color--lightness,42%));\n}\n\n.c2-avatar-image {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: var(--c2-avatar__image--object-fit,cover);\n}\n\n.avatar-trigger {\n all: unset;\n position: relative;\n display: inline-flex;\n border-radius: var(--c2-avatar--border-top-left-radius,999px) var(--c2-avatar--border-top-right-radius,999px) var(--c2-avatar--border-bottom-right-radius,999px) var(--c2-avatar--border-bottom-left-radius,999px);\n cursor: pointer;\n}\n.avatar-trigger:focus-visible {\n outline: var(--c2-avatar__editor__focus--outline,2px solid rgba(2, 101, 220, 0.45));\n outline-offset: var(--c2-avatar__editor__focus--outline-offset,2px);\n}\n.avatar-trigger:disabled {\n cursor: default;\n}\n\n.edit-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border-top-left-radius: var(--c2-avatar--border-top-left-radius,999px);\n border-top-right-radius: var(--c2-avatar--border-top-right-radius,999px);\n border-bottom-left-radius: var(--c2-avatar--border-bottom-left-radius,999px);\n border-bottom-right-radius: var(--c2-avatar--border-bottom-right-radius,999px);\n background: var(--c2-avatar__editor--background,rgba(0, 0, 0, 0.5));\n color: var(--c2-avatar__editor--color,#ffffff);\n opacity: 0;\n transition: opacity 250ms cubic-bezier(0.2, 0, 0, 1);\n}\n.edit-overlay svg,\n.edit-overlay ::slotted(*) {\n display: block;\n width: var(--c2-avatar__editor-icon--size,18px);\n height: var(--c2-avatar__editor-icon--size,18px);\n}\n\n.avatar-trigger:hover:not(:disabled) .edit-overlay,\n.avatar-trigger:focus-visible .edit-overlay {\n opacity: 1;\n}\n\n.remove-action {\n all: unset;\n position: absolute;\n z-index: 2;\n top: -6px;\n right: -6px;\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--c2-avatar__remove--size,22px);\n height: var(--c2-avatar__remove--size,22px);\n border: var(--c2-avatar__remove--border,1px solid #e4e4e7);\n border-radius: 999px;\n background: var(--c2-avatar__remove--background,#ffffff);\n color: var(--c2-avatar__remove--color,#52525b);\n box-shadow: var(--c2-avatar__remove--box-shadow,0 1px 3px rgba(0, 0, 0, 0.18));\n cursor: pointer;\n}\n.remove-action:focus-visible {\n outline: var(--c2-avatar__editor__focus--outline,2px solid rgba(2, 101, 220, 0.45));\n outline-offset: var(--c2-avatar__editor__focus--outline-offset,2px);\n}\n.remove-action:disabled {\n cursor: default;\n opacity: 0.38;\n}\n.remove-action svg,\n.remove-action ::slotted(*) {\n display: block;\n width: 60%;\n height: 60%;\n}\n\n.c2-avatar-badge {\n position: absolute;\n right: calc(-1 * var(--c2-avatar__badge--offset,0px));\n bottom: calc(-1 * var(--c2-avatar__badge--offset,0px));\n display: flex;\n z-index: 1;\n line-height: 1;\n}\n.c2-avatar-badge::slotted(*) {\n box-sizing: border-box;\n border: var(--c2-avatar__badge--border,2px solid #ffffff);\n border-radius: 999px;\n}\n\n.c2-avatar-status {\n display: block;\n box-sizing: content-box;\n width: var(--c2-avatar__badge--size,10px);\n height: var(--c2-avatar__badge--size,10px);\n border: var(--c2-avatar__badge--border,2px solid #ffffff);\n border-radius: 999px;\n background: var(--c2-avatar__badge__offline--color,rgb(161, 161, 170));\n}\n\n:host([status=online]) .c2-avatar-status {\n background: var(--c2-avatar__badge__online--color,rgb(34, 197, 94));\n}\n\n:host([status=away]) .c2-avatar-status {\n background: var(--c2-avatar__badge__away--color,rgb(245, 158, 11));\n}\n\n:host([status=busy]) .c2-avatar-status {\n background: var(--c2-avatar__badge__busy--color,rgb(239, 68, 68));\n}";
8
8
  //#endregion
9
9
  //#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorate.js
10
10
  function __decorate(decorators, target, key, desc) {
@@ -14,6 +14,141 @@ function __decorate(decorators, target, key, desc) {
14
14
  return c > 3 && r && Object.defineProperty(target, key, r), r;
15
15
  }
16
16
  //#endregion
17
+ //#region src/avatar-group.scss?inline
18
+ var avatar_group_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: flex;\n box-sizing: border-box;\n width: 100%;\n max-width: var(--c2-avatar-group--max-width,320px);\n min-width: 0;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.group,\nslot {\n display: flex;\n align-items: center;\n min-width: 0;\n}\n\n.group {\n width: 100%;\n overflow: hidden;\n}\n\nslot {\n flex: none;\n padding-inline-start: var(--c2-avatar-group--overlap,10px);\n}\n\n::slotted(c2-avatar) {\n flex: none;\n margin-inline-start: calc(-1 * var(--c2-avatar-group--overlap,10px));\n --c2-avatar--box-shadow: var(--c2-avatar-group__avatar--box-shadow,0 0 0 2px #ffffff);\n}\n\n::slotted([data-c2-avatar-group-hidden]) {\n display: none;\n}\n\n.overflow {\n position: relative;\n z-index: 1;\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--c2-avatar-group__overflow--size,32px);\n height: var(--c2-avatar-group__overflow--size,32px);\n margin-inline-start: calc(-1 * var(--c2-avatar-group--overlap,10px));\n border: var(--c2-avatar-group__overflow--border,2px solid #ffffff);\n border-radius: var(--c2-avatar-group__overflow--border-radius,999px);\n background: var(--c2-avatar-group__overflow--background,#52525b);\n color: var(--c2-avatar-group__overflow--color,#ffffff);\n font-size: var(--c2-avatar-group__overflow--font-size,12px);\n font-weight: var(--c2-avatar-group__overflow--font-weight,600);\n line-height: 1;\n white-space: nowrap;\n}\n\n.overflow[data-empty] {\n position: absolute;\n visibility: hidden;\n pointer-events: none;\n}";
19
+ //#endregion
20
+ //#region src/avatar-group.ts
21
+ var HIDDEN_ATTRIBUTE = "data-c2-avatar-group-hidden";
22
+ var AvatarGroup = class AvatarGroup extends LitElement {
23
+ constructor(..._args) {
24
+ super(..._args);
25
+ this.maxVisible = 0;
26
+ this.countMode = "hidden";
27
+ this.ariaLabel = "Avatar group";
28
+ this.renderedVisibleCount = 0;
29
+ this.renderedTotalCount = 0;
30
+ this.measureFrame = 0;
31
+ }
32
+ static {
33
+ this.styles = unsafeCSS(avatar_group_default);
34
+ }
35
+ /** Current number of slotted avatars. */
36
+ get total() {
37
+ return this.renderedTotalCount;
38
+ }
39
+ /** Current number of visible avatars. */
40
+ get visible() {
41
+ return this.renderedVisibleCount;
42
+ }
43
+ /** Current number of avatars represented by the overflow count. */
44
+ get hiddenCount() {
45
+ return Math.max(0, this.total - this.visible);
46
+ }
47
+ firstUpdated() {
48
+ if (typeof ResizeObserver === "undefined") return;
49
+ this.resizeObserver = new ResizeObserver(() => this.scheduleMeasure());
50
+ this.resizeObserver.observe(this);
51
+ this.scheduleMeasure();
52
+ }
53
+ updated(changed) {
54
+ if (changed.has("maxVisible")) this.scheduleMeasure();
55
+ }
56
+ disconnectedCallback() {
57
+ super.disconnectedCallback();
58
+ this.resizeObserver?.disconnect();
59
+ cancelAnimationFrame(this.measureFrame);
60
+ this.avatarItems.forEach((item) => item.removeAttribute(HIDDEN_ATTRIBUTE));
61
+ }
62
+ get avatarItems() {
63
+ if (!this.slotElement) return [];
64
+ return this.slotElement.assignedElements({ flatten: true }).filter((element) => element instanceof HTMLElement && element.localName === "c2-avatar");
65
+ }
66
+ handleSlotChange() {
67
+ this.scheduleMeasure();
68
+ }
69
+ /** Recalculates the responsive limit after application code changes a slotted avatar's dimensions. */
70
+ refresh() {
71
+ this.scheduleMeasure();
72
+ }
73
+ scheduleMeasure() {
74
+ cancelAnimationFrame(this.measureFrame);
75
+ this.measureFrame = requestAnimationFrame(() => this.measure());
76
+ }
77
+ measure() {
78
+ const items = this.avatarItems;
79
+ items.forEach((item) => item.removeAttribute(HIDDEN_ATTRIBUTE));
80
+ const total = items.length;
81
+ const available = this.getBoundingClientRect().width;
82
+ if (!available || !total) {
83
+ this.commitCounts(total, total);
84
+ return;
85
+ }
86
+ const styles = getComputedStyle(this);
87
+ const configuredOverlap = Number.parseFloat(styles.getPropertyValue("--c2-avatar-group--overlap"));
88
+ const overlap = Number.isFinite(configuredOverlap) ? Math.max(0, configuredOverlap) : 10;
89
+ const widths = items.map((item) => item.getBoundingClientRect().width);
90
+ const hardLimit = this.maxVisible > 0 ? Math.min(total, Math.floor(this.maxVisible)) : total;
91
+ const avatarsWidth = (count) => widths.slice(0, count).reduce((sum, width) => sum + width, 0) - Math.max(0, count - 1) * overlap;
92
+ const allAllowedFit = hardLimit === total && avatarsWidth(total) <= available;
93
+ let visible = total;
94
+ if (!allAllowedFit) {
95
+ const overflow = this.renderRoot.querySelector(".overflow")?.getBoundingClientRect().width ?? 0;
96
+ visible = 0;
97
+ for (let count = 0; count <= hardLimit; count += 1) if (avatarsWidth(count) + overflow - (count > 0 ? overlap : 0) <= available) visible = count;
98
+ else break;
99
+ }
100
+ items.forEach((item, index) => item.toggleAttribute(HIDDEN_ATTRIBUTE, index >= visible));
101
+ this.commitCounts(total, visible);
102
+ }
103
+ commitCounts(total, visible) {
104
+ if (total === this.renderedTotalCount && visible === this.renderedVisibleCount) return;
105
+ this.renderedTotalCount = total;
106
+ this.renderedVisibleCount = visible;
107
+ const detail = {
108
+ total,
109
+ visible,
110
+ hidden: Math.max(0, total - visible)
111
+ };
112
+ this.dispatchEvent(new CustomEvent("overflow-change", {
113
+ detail,
114
+ bubbles: true,
115
+ composed: true
116
+ }));
117
+ }
118
+ render() {
119
+ const hidden = this.hiddenCount;
120
+ const count = this.countMode === "total" ? this.total : hidden;
121
+ const label = this.countMode === "total" ? `${this.total} avatars total` : `${hidden} more avatar${hidden === 1 ? "" : "s"}`;
122
+ return html`
123
+ <div class="group" role="group" aria-label=${this.ariaLabel || nothing}>
124
+ <slot @slotchange=${this.handleSlotChange}></slot>
125
+ <span
126
+ class="overflow"
127
+ part="overflow"
128
+ ?data-empty=${hidden === 0}
129
+ aria-hidden=${hidden === 0 ? "true" : nothing}
130
+ aria-label=${hidden ? label : nothing}
131
+ >
132
+ ${this.countMode === "hidden" ? "+" : ""}${count}
133
+ </span>
134
+ </div>
135
+ `;
136
+ }
137
+ };
138
+ __decorate([property({
139
+ type: Number,
140
+ attribute: "max-visible"
141
+ })], AvatarGroup.prototype, "maxVisible", void 0);
142
+ __decorate([property({
143
+ reflect: true,
144
+ attribute: "count-mode"
145
+ })], AvatarGroup.prototype, "countMode", void 0);
146
+ __decorate([property({ attribute: "aria-label" })], AvatarGroup.prototype, "ariaLabel", void 0);
147
+ __decorate([state()], AvatarGroup.prototype, "renderedVisibleCount", void 0);
148
+ __decorate([state()], AvatarGroup.prototype, "renderedTotalCount", void 0);
149
+ __decorate([query("slot")], AvatarGroup.prototype, "slotElement", void 0);
150
+ AvatarGroup = __decorate([customElement("c2-avatar-group")], AvatarGroup);
151
+ //#endregion
17
152
  //#region src/avatar.ts
18
153
  var LEGACY_COUNT_ATTRIBUTE = "initialcount";
19
154
  var Avatar = class Avatar extends LitElement {
@@ -26,7 +161,12 @@ var Avatar = class Avatar extends LitElement {
26
161
  this.alt = void 0;
27
162
  this.autoColor = false;
28
163
  this.status = void 0;
164
+ this.editable = false;
165
+ this.disabled = false;
166
+ this.accept = "image/*";
167
+ this.maxSize = 0;
29
168
  this.imageFailed = false;
169
+ this.previewUrl = void 0;
30
170
  }
31
171
  static {
32
172
  this.styles = unsafeCSS(avatar_default);
@@ -56,14 +196,105 @@ var Avatar = class Avatar extends LitElement {
56
196
  return (hash >>> 0) % 360;
57
197
  }
58
198
  willUpdate(changed) {
59
- if (changed.has("src")) this.imageFailed = false;
199
+ if (changed.has("src")) {
200
+ this.imageFailed = false;
201
+ if (this.previewUrl) {
202
+ this.revokePreview();
203
+ this.selectedFile = void 0;
204
+ }
205
+ }
206
+ }
207
+ disconnectedCallback() {
208
+ super.disconnectedCallback();
209
+ this.revokePreview();
210
+ }
211
+ connectedCallback() {
212
+ super.connectedCallback();
213
+ if (this.selectedFile && !this.previewUrl) this.previewUrl = URL.createObjectURL(this.selectedFile);
214
+ }
215
+ /** Locally selected image file, if the avatar was changed through its picker. */
216
+ get file() {
217
+ return this.selectedFile;
218
+ }
219
+ /** Opens the avatar image picker. */
220
+ browse() {
221
+ if (this.editable && !this.disabled) this.input.click();
222
+ }
223
+ /** Clears the selected preview and the current `src` image. */
224
+ removeImage() {
225
+ if (this.disabled || !this.previewUrl && !this.src) return;
226
+ const detail = {
227
+ file: this.selectedFile,
228
+ previousSrc: this.previewUrl ?? this.src
229
+ };
230
+ this.revokePreview();
231
+ this.selectedFile = void 0;
232
+ this.removeAttribute("src");
233
+ this.src = void 0;
234
+ this.imageFailed = false;
235
+ if (this.input) this.input.value = "";
236
+ this.dispatchEvent(new CustomEvent("avatar-remove", {
237
+ detail,
238
+ bubbles: true,
239
+ composed: true
240
+ }));
60
241
  }
61
242
  handleImageError() {
62
243
  this.imageFailed = true;
63
244
  this.dispatchEvent(new Event("error"));
64
245
  }
65
- render() {
66
- const showImage = !!this.src && !this.imageFailed;
246
+ handleFileChange(event) {
247
+ event.stopPropagation();
248
+ const input = event.currentTarget;
249
+ const file = input.files?.[0];
250
+ input.value = "";
251
+ if (!file) return;
252
+ const rejection = this.validateFile(file);
253
+ if (rejection) {
254
+ this.dispatchEvent(new CustomEvent("file-reject", {
255
+ detail: rejection,
256
+ bubbles: true,
257
+ composed: true
258
+ }));
259
+ return;
260
+ }
261
+ this.revokePreview();
262
+ this.selectedFile = file;
263
+ this.previewUrl = URL.createObjectURL(file);
264
+ this.imageFailed = false;
265
+ this.dispatchEvent(new CustomEvent("avatar-change", {
266
+ detail: {
267
+ file,
268
+ previewUrl: this.previewUrl
269
+ },
270
+ bubbles: true,
271
+ composed: true
272
+ }));
273
+ }
274
+ stopNativeInput(event) {
275
+ event.stopPropagation();
276
+ }
277
+ validateFile(file) {
278
+ const rules = this.accept.split(",").map((rule) => rule.trim().toLowerCase()).filter(Boolean);
279
+ const name = file.name.toLowerCase();
280
+ const type = file.type.toLowerCase();
281
+ if (!(rules.length === 0 || rules.some((rule) => rule.startsWith(".") ? name.endsWith(rule) : rule.endsWith("/*") ? type.startsWith(rule.slice(0, -1)) : type === rule))) return {
282
+ file,
283
+ reason: "type",
284
+ message: `${file.name} is not an accepted image type.`
285
+ };
286
+ if (this.maxSize > 0 && file.size > this.maxSize) return {
287
+ file,
288
+ reason: "size",
289
+ message: `${file.name} exceeds the avatar size limit.`
290
+ };
291
+ }
292
+ revokePreview() {
293
+ if (this.previewUrl) URL.revokeObjectURL(this.previewUrl);
294
+ this.previewUrl = void 0;
295
+ }
296
+ renderVisual(source, interactive) {
297
+ const showImage = !!source && !this.imageFailed;
67
298
  const label = this.alt ?? this.name;
68
299
  return html`
69
300
  <div
@@ -72,18 +303,57 @@ var Avatar = class Avatar extends LitElement {
72
303
  "has-image": showImage
73
304
  })}
74
305
  style=${this.autoColor ? `--c2-avatar--hue: ${this.hue}` : nothing}
75
- role="img"
76
- aria-label=${label || nothing}
306
+ role=${interactive ? nothing : "img"}
307
+ aria-label=${interactive ? nothing : label || nothing}
308
+ aria-hidden=${interactive ? "true" : nothing}
77
309
  >
78
310
  ${showImage ? html`<img
79
311
  class="c2-avatar-image"
80
- src=${this.src}
81
- alt=${ifDefined(this.alt ?? (this.name || void 0))}
312
+ src=${source}
313
+ alt=${interactive ? "" : ifDefined(this.alt ?? (this.name || void 0))}
82
314
  loading="lazy"
83
315
  @error=${this.handleImageError}
84
316
  />` : html`<slot>${this.initial}</slot>`}
85
317
  </div>
318
+ `;
319
+ }
320
+ render() {
321
+ const source = this.previewUrl ?? this.src;
322
+ const showImage = !!source && !this.imageFailed;
323
+ const subject = this.name ? `${this.name} avatar` : "avatar";
324
+ return html`
325
+ <input
326
+ type="file"
327
+ tabindex="-1"
328
+ .accept=${this.accept}
329
+ ?disabled=${!this.editable || this.disabled}
330
+ aria-label=${`${subject} image file`}
331
+ @input=${this.stopNativeInput}
332
+ @change=${this.handleFileChange}
333
+ />
334
+ ${this.editable ? html`<button
335
+ type="button"
336
+ class="avatar-trigger"
337
+ ?disabled=${this.disabled}
338
+ aria-label=${showImage ? `Change ${subject}` : `Upload ${subject}`}
339
+ @click=${this.browse}
340
+ >
341
+ ${this.renderVisual(source, true)}
342
+ <span class="edit-overlay" aria-hidden="true">
343
+ <slot name="edit-icon">
344
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
345
+ <path d="M14.5 4h-5L8 6H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-3l-1.5-2z"></path>
346
+ <circle cx="12" cy="13" r="3"></circle>
347
+ </svg>
348
+ </slot>
349
+ </span>
350
+ </button>` : this.renderVisual(source, false)}
86
351
  <slot name="badge" class="c2-avatar-badge"> ${this.status ? html`<span class="c2-avatar-status" aria-label=${this.status}></span>` : nothing} </slot>
352
+ ${this.editable && showImage ? html`<button type="button" class="remove-action" ?disabled=${this.disabled} aria-label=${`Remove ${subject}`} @click=${this.removeImage}>
353
+ <slot name="remove-icon">
354
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"></path></svg>
355
+ </slot>
356
+ </button>` : nothing}
87
357
  `;
88
358
  }
89
359
  };
@@ -101,7 +371,22 @@ __decorate([property({
101
371
  attribute: "auto-color"
102
372
  })], Avatar.prototype, "autoColor", void 0);
103
373
  __decorate([property({ reflect: true })], Avatar.prototype, "status", void 0);
374
+ __decorate([property({
375
+ type: Boolean,
376
+ reflect: true
377
+ })], Avatar.prototype, "editable", void 0);
378
+ __decorate([property({
379
+ type: Boolean,
380
+ reflect: true
381
+ })], Avatar.prototype, "disabled", void 0);
382
+ __decorate([property()], Avatar.prototype, "accept", void 0);
383
+ __decorate([property({
384
+ type: Number,
385
+ attribute: "max-size"
386
+ })], Avatar.prototype, "maxSize", void 0);
104
387
  __decorate([state()], Avatar.prototype, "imageFailed", void 0);
388
+ __decorate([state()], Avatar.prototype, "previewUrl", void 0);
389
+ __decorate([query("input", true)], Avatar.prototype, "input", void 0);
105
390
  Avatar = __decorate([customElement("c2-avatar")], Avatar);
106
391
  //#endregion
107
- export { Avatar };
392
+ export { Avatar, AvatarGroup };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c2n/avatar",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "main": "dist/avatar.js",
6
6
  "exports": {
@@ -61,12 +61,12 @@
61
61
  }
62
62
  },
63
63
  "dependencies": {
64
- "@c2n/core": "0.0.8",
64
+ "@c2n/core": "0.0.10",
65
65
  "lit": "3.3.3"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@c2n/config": "*"
69
69
  },
70
70
  "customElements": "custom-elements.json",
71
- "gitHead": "be59cbccee1d33ca248e39f69b05c0b595ec6c6d"
71
+ "gitHead": "8d57710b3345fcb01cdb4cb15d2d43ba8bf21642"
72
72
  }
package/react.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  // attribute and it stringifies.
9
9
 
10
10
  import type { DetailedHTMLProps, HTMLAttributes } from 'react'
11
- import type { Avatar } from '@c2n/avatar'
11
+ import type { Avatar, AvatarGroup } from '@c2n/avatar'
12
12
 
13
13
  /** Standard React host-element attributes plus the element's own public properties. */
14
14
  type C2Props<T> = DetailedHTMLProps<HTMLAttributes<T>, T> & Partial<Omit<T, keyof HTMLElement>>
@@ -17,6 +17,7 @@ declare module 'react' {
17
17
  namespace JSX {
18
18
  interface IntrinsicElements {
19
19
  'c2-avatar': C2Props<Avatar>
20
+ 'c2-avatar-group': C2Props<AvatarGroup>
20
21
  }
21
22
  }
22
23
  }
@@ -0,0 +1,78 @@
1
+ import { LitElement, type PropertyValues } from 'lit';
2
+ import type { TypedAddEventListener, TypedRemoveEventListener } from '@c2n/core/event-helper.js';
3
+ export type AvatarGroupCountMode = 'hidden' | 'total';
4
+ export interface AvatarGroupOverflowDetail {
5
+ total: number;
6
+ visible: number;
7
+ hidden: number;
8
+ }
9
+ export interface AvatarGroupEventMap {
10
+ 'overflow-change': CustomEvent<AvatarGroupOverflowDetail>;
11
+ }
12
+ export interface AvatarGroup {
13
+ addEventListener: TypedAddEventListener<AvatarGroup, AvatarGroupEventMap>;
14
+ removeEventListener: TypedRemoveEventListener<AvatarGroup, AvatarGroupEventMap>;
15
+ }
16
+ /**
17
+ * Responsive stack of `c2-avatar` elements. It observes its rendered width and each avatar's actual size, then hides
18
+ * the avatars that no longer fit and replaces them with a count. The host fills the available inline space up to
19
+ * `--c2-avatar-group--max-width`, so it also reacts when its parent becomes narrower.
20
+ *
21
+ * @tag c2-avatar-group
22
+ *
23
+ * @slotcomponent c2-avatar
24
+ *
25
+ * @slot - `c2-avatar` elements displayed in source order.
26
+ *
27
+ * @event {CustomEvent<AvatarGroupOverflowDetail>} overflow-change - Fired when resizing changes the visible or hidden counts.
28
+ *
29
+ * @csspart overflow - Count shown after the visible avatars.
30
+ *
31
+ * @cssproperty {pixel} [--c2-avatar-group--max-width=320px] - Maximum responsive width of the group.
32
+ * @cssproperty {pixel} [--c2-avatar-group--overlap=10px] - Amount that adjacent avatars overlap.
33
+ * @cssproperty {box-shadow} [--c2-avatar-group__avatar--box-shadow=0 0 0 2px #ffffff] - Ring applied to slotted avatars so overlaps stay distinct.
34
+ * @cssproperty {pixel} [--c2-avatar-group__overflow--size=32px] - Width and height of the overflow count.
35
+ * @cssproperty {color} [--c2-avatar-group__overflow--background=#52525b] - Overflow count background.
36
+ * @cssproperty {color} [--c2-avatar-group__overflow--color=#ffffff] - Overflow count text colour.
37
+ * @cssproperty {border} [--c2-avatar-group__overflow--border=2px solid #ffffff] - Ring around the overflow count.
38
+ * @cssproperty {border-radius} [--c2-avatar-group__overflow--border-radius=999px] - Overflow count corner radius.
39
+ * @cssproperty {font-size} [--c2-avatar-group__overflow--font-size=12px] - Overflow count text size.
40
+ * @cssproperty {font-weight} [--c2-avatar-group__overflow--font-weight=600] - Overflow count text weight.
41
+ */
42
+ export declare class AvatarGroup extends LitElement {
43
+ static styles: import("lit").CSSResult;
44
+ /** Optional hard cap in addition to the automatically calculated responsive limit. Zero means no hard cap. */
45
+ maxVisible: number;
46
+ /** Whether the overflow indicator shows `+hidden` or the total number of avatars. */
47
+ countMode: AvatarGroupCountMode;
48
+ /** Accessible name for the group. */
49
+ ariaLabel: string;
50
+ private renderedVisibleCount;
51
+ private renderedTotalCount;
52
+ private slotElement;
53
+ private resizeObserver?;
54
+ private measureFrame;
55
+ /** Current number of slotted avatars. */
56
+ get total(): number;
57
+ /** Current number of visible avatars. */
58
+ get visible(): number;
59
+ /** Current number of avatars represented by the overflow count. */
60
+ get hiddenCount(): number;
61
+ protected firstUpdated(): void;
62
+ protected updated(changed: PropertyValues<this>): void;
63
+ disconnectedCallback(): void;
64
+ private get avatarItems();
65
+ private handleSlotChange;
66
+ /** Recalculates the responsive limit after application code changes a slotted avatar's dimensions. */
67
+ refresh(): void;
68
+ private scheduleMeasure;
69
+ private measure;
70
+ private commitCounts;
71
+ render(): import("lit-html").TemplateResult<1>;
72
+ }
73
+ declare global {
74
+ interface HTMLElementTagNameMap {
75
+ 'c2-avatar-group': AvatarGroup;
76
+ }
77
+ }
78
+ //# sourceMappingURL=avatar-group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"avatar-group.d.ts","sourceRoot":"","sources":["../../src/avatar-group.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAA;AAG/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AAGhG,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,OAAO,CAAA;AAErD,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,EAAE,WAAW,CAAC,yBAAyB,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B,gBAAgB,EAAE,qBAAqB,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAA;IACzE,mBAAmB,EAAE,wBAAwB,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAA;CAChF;AAID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBACa,WAAY,SAAQ,UAAU;IACzC,OAAgB,MAAM,0BAAoB;IAE1C,8GAA8G;IACxD,UAAU,SAAI;IAEpE,qFAAqF;IAC/B,SAAS,EAAE,oBAAoB,CAAW;IAEhG,qCAAqC;IACW,SAAS,SAAiB;IAEjE,OAAO,CAAC,oBAAoB,CAAI;IAChC,OAAO,CAAC,kBAAkB,CAAI;IACxB,OAAO,CAAC,WAAW,CAAkB;IAEpD,OAAO,CAAC,cAAc,CAAC,CAAgB;IACvC,OAAO,CAAC,YAAY,CAAI;IAExB,yCAAyC;IACzC,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,yCAAyC;IACzC,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,mEAAmE;IACnE,IAAI,WAAW,IAAI,MAAM,CAExB;cAEkB,YAAY,IAAI,IAAI;cAOpB,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,GAAG,IAAI;IAItD,oBAAoB,IAAI,IAAI;IAOrC,OAAO,KAAK,WAAW,GAKtB;IAED,OAAO,CAAC,gBAAgB;IAIxB,sGAAsG;IACtG,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,OAAO;IAkCf,OAAO,CAAC,YAAY;IAQX,MAAM;CAoBhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,iBAAiB,EAAE,WAAW,CAAA;KAC/B;CACF"}
@@ -1,9 +1,25 @@
1
1
  import { LitElement, type PropertyValues } from 'lit';
2
2
  import type { TypedAddEventListener, TypedRemoveEventListener } from '@c2n/core/event-helper.js';
3
3
  export type AvatarStatus = 'online' | 'away' | 'busy' | 'offline';
4
+ export interface AvatarChangeDetail {
5
+ file: File;
6
+ previewUrl: string;
7
+ }
8
+ export interface AvatarRemoveDetail {
9
+ file?: File;
10
+ previousSrc?: string;
11
+ }
12
+ export interface AvatarFileRejection {
13
+ file: File;
14
+ reason: 'type' | 'size';
15
+ message: string;
16
+ }
4
17
  /** Events fired by {@link Avatar}, keyed for `addEventListener`. */
5
18
  export interface AvatarEventMap {
6
19
  error: Event;
20
+ 'avatar-change': CustomEvent<AvatarChangeDetail>;
21
+ 'avatar-remove': CustomEvent<AvatarRemoveDetail>;
22
+ 'file-reject': CustomEvent<AvatarFileRejection>;
7
23
  }
8
24
  export interface Avatar {
9
25
  addEventListener: TypedAddEventListener<Avatar, AvatarEventMap>;
@@ -19,8 +35,13 @@ export interface Avatar {
19
35
  *
20
36
  * @slot - Custom content shown instead of the initials (an icon, an emoji, an `<img>`).
21
37
  * @slot badge - Small element pinned to the bottom-right corner (a count, an icon). Replaces the `status` dot.
38
+ * @slot edit-icon - Icon shown over an editable avatar. Defaults to a camera.
39
+ * @slot remove-icon - Icon shown in the remove action. Defaults to a close mark.
22
40
  *
23
41
  * @event {Event} error - Fired when the `src` image fails to load; the initials are shown instead.
42
+ * @event {CustomEvent<AvatarChangeDetail>} avatar-change - Fired after an editable avatar accepts a local image. Upload `detail.file` and use `detail.previewUrl` for the immediate preview.
43
+ * @event {CustomEvent<AvatarRemoveDetail>} avatar-remove - Fired after the current local preview or `src` image is removed.
44
+ * @event {CustomEvent<AvatarFileRejection>} file-reject - Fired when an editable avatar rejects a file by type or size.
24
45
  *
25
46
  * @cssproperty {pixel} [--c2-avatar--size=32px]
26
47
  * @cssproperty {pixel} --c2-avatar--width - Falls back to `--c2-avatar--size`.
@@ -49,6 +70,16 @@ export interface Avatar {
49
70
  * @cssproperty {lightness} [--c2-avatar__auto-color--lightness=42%] - Lightness of the hue derived from the name.
50
71
  *
51
72
  * @cssproperty {object-fit} [--c2-avatar__image--object-fit=cover]
73
+ * @cssproperty {color} [--c2-avatar__editor--background=rgba(0, 0, 0, 0.5)]
74
+ * @cssproperty {color} [--c2-avatar__editor--color=#ffffff]
75
+ * @cssproperty {outline} [--c2-avatar__editor__focus--outline=2px solid rgba(2, 101, 220, 0.45)]
76
+ * @cssproperty {pixel} [--c2-avatar__editor__focus--outline-offset=2px]
77
+ * @cssproperty {pixel} [--c2-avatar__editor-icon--size=18px]
78
+ * @cssproperty {pixel} [--c2-avatar__remove--size=22px]
79
+ * @cssproperty {color} [--c2-avatar__remove--background=#ffffff]
80
+ * @cssproperty {color} [--c2-avatar__remove--color=#52525b]
81
+ * @cssproperty {border} [--c2-avatar__remove--border=1px solid #e4e4e7]
82
+ * @cssproperty {box-shadow} [--c2-avatar__remove--box-shadow=0 1px 3px rgba(0, 0, 0, 0.18)]
52
83
  *
53
84
  * @cssproperty {pixel} [--c2-avatar__badge--size=10px] - Diameter of the status dot; slotted badges size themselves.
54
85
  * @cssproperty {border} [--c2-avatar__badge--border=2px solid #ffffff] - Ring separating the badge from the avatar.
@@ -74,7 +105,18 @@ export declare class Avatar extends LitElement {
74
105
  autoColor: boolean;
75
106
  /** Presence dot in the bottom-right corner. */
76
107
  status: AvatarStatus | undefined;
108
+ /** Allows the avatar to open an image picker and exposes a remove action for the current image. */
109
+ editable: boolean;
110
+ /** Disables editable avatar actions. */
111
+ disabled: boolean;
112
+ /** Accepted image MIME types or file extensions, using native file-input syntax. */
113
+ accept: string;
114
+ /** Maximum accepted image size in bytes. Zero means unlimited. */
115
+ maxSize: number;
77
116
  private imageFailed;
117
+ private previewUrl;
118
+ private selectedFile;
119
+ private input;
78
120
  /** Accepts the pre-1.0 camelCase `initialCount="2"` attribute as well. */
79
121
  static get observedAttributes(): string[];
80
122
  attributeChangedCallback(name: string, old: string | null, value: string | null): void;
@@ -83,7 +125,20 @@ export declare class Avatar extends LitElement {
83
125
  /** Hue (0–359) derived from the name, used by `auto-color`. */
84
126
  get hue(): number;
85
127
  willUpdate(changed: PropertyValues<this>): void;
128
+ disconnectedCallback(): void;
129
+ connectedCallback(): void;
130
+ /** Locally selected image file, if the avatar was changed through its picker. */
131
+ get file(): File | undefined;
132
+ /** Opens the avatar image picker. */
133
+ browse(): void;
134
+ /** Clears the selected preview and the current `src` image. */
135
+ removeImage(): void;
86
136
  private handleImageError;
137
+ private handleFileChange;
138
+ private stopNativeInput;
139
+ private validateFile;
140
+ private revokePreview;
141
+ private renderVisual;
87
142
  render(): import("lit-html").TemplateResult<1>;
88
143
  }
89
144
  declare global {
@@ -91,4 +146,5 @@ declare global {
91
146
  'c2-avatar': Avatar;
92
147
  }
93
148
  }
149
+ export { AvatarGroup, type AvatarGroupCountMode, type AvatarGroupOverflowDetail } from './avatar-group';
94
150
  //# sourceMappingURL=avatar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../src/avatar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAA;AAG/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AAKhG,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;AAIjE,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAA;CACb;AAED,MAAM,WAAW,MAAM;IACrB,gBAAgB,EAAE,qBAAqB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IAC/D,mBAAmB,EAAE,wBAAwB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;CACtE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,qBACa,MAAO,SAAQ,UAAU;IACpC,OAAgB,MAAM,0BAAoB;IAE1C,wFAAwF;IAC5E,IAAI,SAAK;IAErB,8GAA8G;IACtD,YAAY,SAAI;IAExE,kEAAkE;IACtD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAY;IAEpD,0EAA0E;IAC9D,GAAG,EAAE,MAAM,GAAG,SAAS,CAAY;IAE/C,0CAA0C;IAC9B,GAAG,EAAE,MAAM,GAAG,SAAS,CAAY;IAE/C,0EAA0E;IACL,SAAS,UAAQ;IAEtF,+CAA+C;IAClB,MAAM,EAAE,YAAY,GAAG,SAAS,CAAY;IAEhE,OAAO,CAAC,WAAW,CAAQ;IAEpC,0EAA0E;IAC1E,WAAoB,kBAAkB,aAErC;IAEQ,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAQxF,gEAAgE;IAChE,IAAI,OAAO,IAAI,MAAM,CASpB;IAED,+DAA+D;IAC/D,IAAI,GAAG,IAAI,MAAM,CAKhB;IAEQ,UAAU,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAIjD,OAAO,CAAC,gBAAgB;IAKf,MAAM;CAyBhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,WAAW,EAAE,MAAM,CAAA;KACpB;CACF"}
1
+ {"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../src/avatar.ts"],"names":[],"mappings":"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"}
package/vue.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  // nothing: template.compilerOptions.isCustomElement = (tag) => tag.startsWith('c2-') in vite.config.ts.
9
9
 
10
10
  import type { DefineComponent, HTMLAttributes } from 'vue'
11
- import type { Avatar, AvatarEventMap } from '@c2n/avatar'
11
+ import type { Avatar, AvatarEventMap, AvatarGroup, AvatarGroupEventMap } from '@c2n/avatar'
12
12
 
13
13
  /** The element's own public properties, plus every attribute Vue understands on a host element. */
14
14
  type C2Props<T> = Partial<Omit<T, keyof HTMLElement>> & HTMLAttributes
@@ -19,7 +19,19 @@ declare module 'vue' {
19
19
  C2Props<Avatar> & {
20
20
  'initial-count'?: unknown
21
21
  'auto-color'?: unknown
22
+ 'max-size'?: unknown
23
+ onAvatarRemove?: (event: AvatarEventMap['avatar-remove']) => void
22
24
  onError?: (event: AvatarEventMap['error']) => void
25
+ onFileReject?: (event: AvatarEventMap['file-reject']) => void
26
+ onAvatarChange?: (event: AvatarEventMap['avatar-change']) => void
27
+ }
28
+ >
29
+ 'c2-avatar-group': DefineComponent<
30
+ C2Props<AvatarGroup> & {
31
+ 'max-visible'?: unknown
32
+ 'count-mode'?: unknown
33
+ 'aria-label'?: unknown
34
+ onOverflowChange?: (event: AvatarGroupEventMap['overflow-change']) => void
23
35
  }
24
36
  >
25
37
  }