@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 +2 -0
- package/dist/avatar.js +159 -9
- package/package.json +3 -3
- package/types/src/avatar.d.ts +55 -0
- package/types/src/avatar.d.ts.map +1 -1
- package/vue.d.ts +4 -0
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"))
|
|
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
|
-
|
|
66
|
-
|
|
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
|
|
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=${
|
|
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.
|
|
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.
|
|
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": "
|
|
71
|
+
"gitHead": "e4768cce5e0fbd5bc3165cd493a1e8d57cf0fff4"
|
|
72
72
|
}
|
package/types/src/avatar.d.ts
CHANGED
|
@@ -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;
|
|
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
|
}
|