UNPKG

@limetech/lime-elements

Version:
257 lines (253 loc) • 15.6 kB
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 = () => `@charset "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}@keyframes 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 };