@limetech/lime-elements
Version:
257 lines (253 loc) • 15.6 kB
JavaScript
import { r as registerInstance, c as createEvent, h, H as Host } from './index-DBTJNfo7.js';
import { i as isTypeAccepted } from './files-P324wLau.js';
import { g as getIconName } from './get-icon-props-CgNJbSP4.js';
import { t as translate } from './translations-DVRaJQvC.js';
import { c as createRandomString } from './random-string-JbKhhoXs.js';
import { r as resizeImage } from './image-resize-DPcww8rE.js';
import './file-metadata-BwF9vTXN.js';
const profilePictureCss = () => ` "UTF-8";:host(limel-profile-picture){position:relative;display:inline-flex;min-width:1.5rem;min-height:1.5rem;border-radius:var(--profile-picture-border-radius, 100vw);background-color:rgb(var(--contrast-400))}*{box-sizing:border-box}limel-file-dropzone,limel-file-input,button.avatar{display:flex;align-items:center;justify-content:center;width:100%;height:100%}button{all:unset;display:block}button:focus{outline:none}button:focus-visible{outline:none;box-shadow:var(--shadow-depth-8-focused)}button.avatar{overflow:hidden;border-radius:var(--profile-picture-border-radius, 100vw)}:host(:not([disabled]):not([disabled=true])) button.avatar{transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--limel-theme-on-surface-color);background-color:transparent}:host(:not([disabled]):not([disabled=true])) button.avatar:hover,:host(:not([disabled]):not([disabled=true])) button.avatar:focus,:host(:not([disabled]):not([disabled=true])) button.avatar:focus-visible{will-change:color, background-color, box-shadow, transform}:host(:not([disabled]):not([disabled=true])) button.avatar:hover,:host(:not([disabled]):not([disabled=true])) button.avatar:focus-visible{transform:translate3d(0, 0.01rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color)}:host(:not([disabled]):not([disabled=true])) button.avatar:hover{box-shadow:var(--button-shadow-hovered)}:host(:not([disabled]):not([disabled=true])) button.avatar:active{--limel-clickable-transform-timing-function:cubic-bezier( 0.83, -0.15, 0.49, 1.16 );transform:translate3d(0, 0.05rem, 0);box-shadow:var(--button-shadow-pressed)}:host(:not([disabled]):not([disabled=true])) button.avatar:hover,:host(:not([disabled]):not([disabled=true])) button.avatar:active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}:host([invalid]:not([invalid=false])) button.avatar{box-shadow:var(--shadow-error-state)}button.remove{transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--limel-theme-on-surface-color);background-color:rgb(var(--contrast-900))}button.remove:hover,button.remove:focus,button.remove:focus-visible{will-change:color, background-color, box-shadow, transform}button.remove:hover,button.remove:focus-visible{transform:translate3d(0, 0.01rem, 0);color:rgb(var(--color-white));background-color:rgb(var(--color-red-default))}button.remove:hover{box-shadow:var(--button-shadow-hovered)}button.remove:active{--limel-clickable-transform-timing-function:cubic-bezier( 0.83, -0.15, 0.49, 1.16 );transform:translate3d(0, 0.05rem, 0);box-shadow:var(--button-shadow-pressed)}button.remove:hover,button.remove:active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}button.remove{cursor:pointer;height:1.25rem;width:1.25rem;border-radius:50%;background-repeat:no-repeat;background-position:center;background-size:0.75rem;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2032%2032'%3E%3Cdefs/%3E%3Cpath%20fill='rgb(255,255,255)'%20d='M7.219%205.781L5.78%207.22%2014.563%2016%205.78%2024.781%207.22%2026.22%2016%2017.437l8.781%208.782%201.438-1.438L17.437%2016l8.782-8.781L24.78%205.78%2016%2014.563z'/%3E%3C/svg%3E");position:absolute;top:0;left:0;opacity:0}:host(:hover) button.remove,:host(:focus) button.remove,:host(:focus-visible) button.remove,:host(:focus-within) button.remove,:host(:active) button.remove{animation:show 0.4s ease-in-out forwards} show{0%{transform:scale(0.9);opacity:0}100%{transform:scale(1);opacity:1}}button.avatar,img,limel-icon{border-radius:var(--profile-picture-border-radius, 100vw)}limel-icon{width:calc(100% - 1rem);min-width:1rem;max-width:4rem;color:var(--limel-theme-text-secondary-on-background-color);margin:auto}img{object-fit:var(--limel-profile-picture-object-fit);width:100%;height:100%}:host(.has-image-error) img{border:1px dashed rgb(var(--contrast-600));background:url("data:image/svg+xml;charset=utf-8, <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8' style='fill-rule:evenodd;'><path fill='rgba(186,186,192,0.16)' d='M0 0h4v4H0zM4 4h4v4H4z'/></svg>");background-size:0.5rem}limel-spinner{position:absolute;inset:0;margin:auto}limel-popover{position:absolute;inset:auto 0 0 auto;display:block;width:2.25rem;height:2.25rem}`;
const ProfilePicture = class {
constructor(hostRef) {
registerInstance(this, hostRef);
this.change = createEvent(this, "change");
this.filesRejected = createEvent(this, "filesRejected");
/**
* Defines the language for translations.
* Will translate the translatable strings on the components.
*/
this.language = 'en';
/**
* Placeholder icon of the component, displayed when no image is present.
*/
this.icon = 'user';
/**
* Disables user interaction.
* Prevents uploading new pictures or removing existing ones.
*/
this.disabled = false;
/**
* Readonly prevents changing the value but allows interaction like focus.
*/
this.readonly = false;
/**
* Marks the control as required.
*/
this.required = false;
/**
* Marks the control as invalid.
*/
this.invalid = false;
/**
* Set to `true` to put the component in the `loading` state,
* and render an indeterminate progress indicator inside.
* This does _not_ disable the interactivity of the component!
*/
this.loading = false;
/**
* How the image should fit within the container.
* - `cover` will fill the container and crop excess parts.
* - `contain` will scale the image to fit within the container without cropping.
*/
this.imageFit = 'cover';
/**
* A comma-separated list of accepted file types.
*/
this.accept = 'image/jpeg,image/png,image/heic,.jpg,.jpeg,.png,.heic';
this.imageError = false;
this.isErrorMessagePopoverOpen = false;
this.removeButtonId = createRandomString();
this.browseButtonId = createRandomString();
this.renderHelperText = () => {
if (!this.helperText) {
return;
}
return (h("limel-tooltip", { elementId: this.browseButtonId, label: this.helperText }));
};
this.handleNewFiles = async (event) => {
var _a, _b;
event.stopPropagation();
if (this.disabled) {
return;
}
const file = (_a = event.detail) === null || _a === void 0 ? void 0 : _a[0];
if (!file) {
return;
}
if (!isTypeAccepted(file, this.accept)) {
this.filesRejected.emit([file]);
return;
}
this.revokeObjectUrl();
this.imageError = false;
let out = file;
// Optional client-side resize
if (this.resize && file.fileContent instanceof File) {
try {
const processed = await resizeImage(file.fileContent, Object.assign(Object.assign({}, this.resize), { fit: (_b = this.resize.fit) !== null && _b !== void 0 ? _b : this.imageFit }));
out = Object.assign(Object.assign({}, file), { filename: processed.name, size: processed.size, contentType: processed.type, fileContent: processed });
}
catch (_c) {
// Fall back to original file if resize fails
out = file;
}
}
// Create an object URL for immediate preview if no href present
if (!out.href && out.fileContent instanceof File) {
this.objectUrl = URL.createObjectURL(out.fileContent);
}
this.change.emit(out);
};
this.handleRejectedFiles = (event) => {
event.stopPropagation();
this.filesRejected.emit(event.detail);
};
this.handleClear = (event) => {
event.stopPropagation();
this.revokeObjectUrl();
this.imageError = false;
this.change.emit(undefined);
};
this.onImageError = () => {
this.imageError = true;
};
this.openPopover = (event) => {
event.stopPropagation();
this.isErrorMessagePopoverOpen = true;
};
this.onPopoverClose = (event) => {
event.stopPropagation();
this.isErrorMessagePopoverOpen = false;
};
this.getTranslation = (key) => {
return translate.get(key, this.language);
};
}
disconnectedCallback() {
this.revokeObjectUrl();
}
handleValueChange() {
// Clear previously created object URL when value changes
this.revokeObjectUrl();
this.imageError = false;
// If a new File without href is provided, create an object URL for preview
const currentValue = this.value;
if (currentValue &&
typeof currentValue !== 'string' &&
!currentValue.href &&
currentValue.fileContent instanceof File) {
this.objectUrl = URL.createObjectURL(currentValue.fileContent);
}
}
render() {
const hostClassNames = {
'has-image-error': this.imageError,
};
if (this.readonly) {
return h(Host, { class: hostClassNames }, this.renderAvatar());
}
return (h(Host, { class: hostClassNames }, h("limel-file-dropzone", { disabled: this.disabled, accept: this.accept, onFilesSelected: this.handleNewFiles, onFilesRejected: this.handleRejectedFiles }, h("limel-file-input", { accept: this.accept, disabled: this.disabled, "aria-required": this.required ? 'true' : undefined, "aria-invalid": this.invalid ? 'true' : undefined }, this.renderBrowseButton())), this.renderClearButton(), this.renderSpinner(), this.renderErrorMessage(), this.renderHelperText()));
}
get hasValue() {
if (typeof this.value === 'string') {
return !!this.value;
}
if (this.value && (this.value.href || this.value.fileContent)) {
return true;
}
return !!this.objectUrl;
}
renderBrowseButton() {
return (h("button", { id: this.browseButtonId, type: "button", class: "avatar", disabled: this.disabled, "aria-label": this.label, "aria-busy": this.loading ? 'true' : 'false', "aria-live": "polite" }, this.renderAvatar()));
}
renderAvatar() {
const src = this.getImageSrc();
if (src) {
return (h("img", { src: src, alt: "", style: {
'--limel-profile-picture-object-fit': this.imageFit,
}, loading: "lazy", onError: this.onImageError }));
}
return this.renderIcon();
}
renderIcon() {
var _a, _b;
const icon = getIconName(this.icon);
return (h("limel-icon", { name: icon, style: {
color: `${(_a = this.icon) === null || _a === void 0 ? void 0 : _a.color}`,
'background-color': `${(_b = this.icon) === null || _b === void 0 ? void 0 : _b.backgroundColor}`,
} }));
}
renderClearButton() {
if (!this.hasValue || this.disabled) {
return;
}
return [
h("button", { class: "remove", type: "button", id: this.removeButtonId, onClick: this.handleClear }),
h("limel-tooltip", { label: this.getTranslation('profile-picture.remove'), elementId: this.removeButtonId }),
];
}
renderSpinner() {
if (!this.loading) {
return;
}
return h("limel-spinner", null);
}
// Collects derived flags used for deciding whether to show the unsupported preview message
getUnsupportedPreviewContext() {
const currentValue = this.value;
const hasNoSrc = !this.getImageSrc();
const hasLocalFile = !!(currentValue &&
typeof currentValue !== 'string' &&
currentValue.fileContent instanceof File &&
!currentValue.href);
const isResizeConfigured = !!this.resize;
return { hasNoSrc, hasLocalFile, isResizeConfigured };
}
shouldShowErrorMessage() {
const { hasNoSrc, hasLocalFile, isResizeConfigured } = this.getUnsupportedPreviewContext();
return ((hasNoSrc || this.imageError) && hasLocalFile && isResizeConfigured);
}
// Shows a non-intrusive note when there is a File without href and no object URL, which
// can happen if the browser failed to decode the source (e.g., HEIC in Chromium).
renderErrorMessage() {
if (!this.shouldShowErrorMessage()) {
return;
}
const errorIcon = {
name: 'error',
color: 'rgb(var(--color-orange-dark))',
};
const errorMessageStyles = {
maxWidth: '20rem',
borderRadius: '0.75rem',
};
return (h("limel-popover", { open: this.isErrorMessagePopoverOpen, onClick: this.openPopover, onClose: this.onPopoverClose }, h("limel-icon-button", { slot: "trigger", elevated: true, icon: errorIcon, "aria-live": "polite", label: this.getTranslation('profile-picture.unsupported-preview.title') }), h("limel-callout", { type: "warning", style: errorMessageStyles, heading: this.getTranslation('profile-picture.unsupported-preview.title') }, this.getTranslation('profile-picture.unsupported-preview.description'))));
}
getImageSrc() {
if (!this.value) {
return this.objectUrl; // Could be set from last selection before parent consumes
}
if (typeof this.value === 'string') {
return this.value;
}
if (this.value.href) {
return this.value.href;
}
if (this.value.fileContent instanceof File) {
return this.objectUrl;
}
return undefined;
}
revokeObjectUrl() {
if (this.objectUrl) {
URL.revokeObjectURL(this.objectUrl);
this.objectUrl = undefined;
}
}
static get watchers() { return {
"value": [{
"handleValueChange": 0
}]
}; }
};
ProfilePicture.style = profilePictureCss();
export { ProfilePicture as limel_profile_picture };