@c2n/avatar 0.0.7 → 0.0.9

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
@@ -14,6 +14,7 @@ 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>
17
18
  ```
18
19
 
19
20
  - **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 +22,6 @@ npm install @c2n/avatar
21
22
  - **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
23
  - **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
24
  - **Accessibility**: the avatar is `role="img"` labelled by `alt` or `name`.
25
+ - **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
26
 
25
27
  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.
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) {
@@ -26,7 +26,12 @@ var Avatar = class Avatar extends LitElement {
26
26
  this.alt = void 0;
27
27
  this.autoColor = false;
28
28
  this.status = void 0;
29
+ this.editable = false;
30
+ this.disabled = false;
31
+ this.accept = "image/*";
32
+ this.maxSize = 0;
29
33
  this.imageFailed = false;
34
+ this.previewUrl = void 0;
30
35
  }
31
36
  static {
32
37
  this.styles = unsafeCSS(avatar_default);
@@ -56,14 +61,105 @@ var Avatar = class Avatar extends LitElement {
56
61
  return (hash >>> 0) % 360;
57
62
  }
58
63
  willUpdate(changed) {
59
- if (changed.has("src")) this.imageFailed = false;
64
+ if (changed.has("src")) {
65
+ this.imageFailed = false;
66
+ if (this.previewUrl) {
67
+ this.revokePreview();
68
+ this.selectedFile = void 0;
69
+ }
70
+ }
71
+ }
72
+ disconnectedCallback() {
73
+ super.disconnectedCallback();
74
+ this.revokePreview();
75
+ }
76
+ connectedCallback() {
77
+ super.connectedCallback();
78
+ if (this.selectedFile && !this.previewUrl) this.previewUrl = URL.createObjectURL(this.selectedFile);
79
+ }
80
+ /** Locally selected image file, if the avatar was changed through its picker. */
81
+ get file() {
82
+ return this.selectedFile;
83
+ }
84
+ /** Opens the avatar image picker. */
85
+ browse() {
86
+ if (this.editable && !this.disabled) this.input.click();
87
+ }
88
+ /** Clears the selected preview and the current `src` image. */
89
+ removeImage() {
90
+ if (this.disabled || !this.previewUrl && !this.src) return;
91
+ const detail = {
92
+ file: this.selectedFile,
93
+ previousSrc: this.previewUrl ?? this.src
94
+ };
95
+ this.revokePreview();
96
+ this.selectedFile = void 0;
97
+ this.removeAttribute("src");
98
+ this.src = void 0;
99
+ this.imageFailed = false;
100
+ if (this.input) this.input.value = "";
101
+ this.dispatchEvent(new CustomEvent("avatar-remove", {
102
+ detail,
103
+ bubbles: true,
104
+ composed: true
105
+ }));
60
106
  }
61
107
  handleImageError() {
62
108
  this.imageFailed = true;
63
109
  this.dispatchEvent(new Event("error"));
64
110
  }
65
- render() {
66
- const showImage = !!this.src && !this.imageFailed;
111
+ handleFileChange(event) {
112
+ event.stopPropagation();
113
+ const input = event.currentTarget;
114
+ const file = input.files?.[0];
115
+ input.value = "";
116
+ if (!file) return;
117
+ const rejection = this.validateFile(file);
118
+ if (rejection) {
119
+ this.dispatchEvent(new CustomEvent("file-reject", {
120
+ detail: rejection,
121
+ bubbles: true,
122
+ composed: true
123
+ }));
124
+ return;
125
+ }
126
+ this.revokePreview();
127
+ this.selectedFile = file;
128
+ this.previewUrl = URL.createObjectURL(file);
129
+ this.imageFailed = false;
130
+ this.dispatchEvent(new CustomEvent("avatar-change", {
131
+ detail: {
132
+ file,
133
+ previewUrl: this.previewUrl
134
+ },
135
+ bubbles: true,
136
+ composed: true
137
+ }));
138
+ }
139
+ stopNativeInput(event) {
140
+ event.stopPropagation();
141
+ }
142
+ validateFile(file) {
143
+ const rules = this.accept.split(",").map((rule) => rule.trim().toLowerCase()).filter(Boolean);
144
+ const name = file.name.toLowerCase();
145
+ const type = file.type.toLowerCase();
146
+ if (!(rules.length === 0 || rules.some((rule) => rule.startsWith(".") ? name.endsWith(rule) : rule.endsWith("/*") ? type.startsWith(rule.slice(0, -1)) : type === rule))) return {
147
+ file,
148
+ reason: "type",
149
+ message: `${file.name} is not an accepted image type.`
150
+ };
151
+ if (this.maxSize > 0 && file.size > this.maxSize) return {
152
+ file,
153
+ reason: "size",
154
+ message: `${file.name} exceeds the avatar size limit.`
155
+ };
156
+ }
157
+ revokePreview() {
158
+ if (this.previewUrl) URL.revokeObjectURL(this.previewUrl);
159
+ this.previewUrl = void 0;
160
+ }
161
+ renderVisual(source, interactive) {
162
+ const showImage = !!source && !this.imageFailed;
67
163
  const label = this.alt ?? this.name;
68
164
  return html`
69
165
  <div
@@ -72,18 +168,57 @@ var Avatar = class Avatar extends LitElement {
72
168
  "has-image": showImage
73
169
  })}
74
170
  style=${this.autoColor ? `--c2-avatar--hue: ${this.hue}` : nothing}
75
- role="img"
76
- aria-label=${label || nothing}
171
+ role=${interactive ? nothing : "img"}
172
+ aria-label=${interactive ? nothing : label || nothing}
173
+ aria-hidden=${interactive ? "true" : nothing}
77
174
  >
78
175
  ${showImage ? html`<img
79
176
  class="c2-avatar-image"
80
- src=${this.src}
81
- alt=${ifDefined(this.alt ?? (this.name || void 0))}
177
+ src=${source}
178
+ alt=${interactive ? "" : ifDefined(this.alt ?? (this.name || void 0))}
82
179
  loading="lazy"
83
180
  @error=${this.handleImageError}
84
181
  />` : html`<slot>${this.initial}</slot>`}
85
182
  </div>
183
+ `;
184
+ }
185
+ render() {
186
+ const source = this.previewUrl ?? this.src;
187
+ const showImage = !!source && !this.imageFailed;
188
+ const subject = this.name ? `${this.name} avatar` : "avatar";
189
+ return html`
190
+ <input
191
+ type="file"
192
+ tabindex="-1"
193
+ .accept=${this.accept}
194
+ ?disabled=${!this.editable || this.disabled}
195
+ aria-label=${`${subject} image file`}
196
+ @input=${this.stopNativeInput}
197
+ @change=${this.handleFileChange}
198
+ />
199
+ ${this.editable ? html`<button
200
+ type="button"
201
+ class="avatar-trigger"
202
+ ?disabled=${this.disabled}
203
+ aria-label=${showImage ? `Change ${subject}` : `Upload ${subject}`}
204
+ @click=${this.browse}
205
+ >
206
+ ${this.renderVisual(source, true)}
207
+ <span class="edit-overlay" aria-hidden="true">
208
+ <slot name="edit-icon">
209
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
210
+ <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>
211
+ <circle cx="12" cy="13" r="3"></circle>
212
+ </svg>
213
+ </slot>
214
+ </span>
215
+ </button>` : this.renderVisual(source, false)}
86
216
  <slot name="badge" class="c2-avatar-badge"> ${this.status ? html`<span class="c2-avatar-status" aria-label=${this.status}></span>` : nothing} </slot>
217
+ ${this.editable && showImage ? html`<button type="button" class="remove-action" ?disabled=${this.disabled} aria-label=${`Remove ${subject}`} @click=${this.removeImage}>
218
+ <slot name="remove-icon">
219
+ <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>
220
+ </slot>
221
+ </button>` : nothing}
87
222
  `;
88
223
  }
89
224
  };
@@ -101,7 +236,22 @@ __decorate([property({
101
236
  attribute: "auto-color"
102
237
  })], Avatar.prototype, "autoColor", void 0);
103
238
  __decorate([property({ reflect: true })], Avatar.prototype, "status", void 0);
239
+ __decorate([property({
240
+ type: Boolean,
241
+ reflect: true
242
+ })], Avatar.prototype, "editable", void 0);
243
+ __decorate([property({
244
+ type: Boolean,
245
+ reflect: true
246
+ })], Avatar.prototype, "disabled", void 0);
247
+ __decorate([property()], Avatar.prototype, "accept", void 0);
248
+ __decorate([property({
249
+ type: Number,
250
+ attribute: "max-size"
251
+ })], Avatar.prototype, "maxSize", void 0);
104
252
  __decorate([state()], Avatar.prototype, "imageFailed", void 0);
253
+ __decorate([state()], Avatar.prototype, "previewUrl", void 0);
254
+ __decorate([query("input", true)], Avatar.prototype, "input", void 0);
105
255
  Avatar = __decorate([customElement("c2-avatar")], Avatar);
106
256
  //#endregion
107
257
  export { Avatar };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c2n/avatar",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
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.7",
64
+ "@c2n/core": "0.0.9",
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": "c8db398a27f7cd417d665fdf5da455fee2d4e910"
71
+ "gitHead": "e4768cce5e0fbd5bc3165cd493a1e8d57cf0fff4"
72
72
  }
@@ -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 {
@@ -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
@@ -19,7 +19,11 @@ 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
23
27
  }
24
28
  >
25
29
  }