UNPKG

@trimble-oss/moduswebcomponents

Version:

Modus Web Components is a modern, accessible UI library built with Stencil JS that provides reusable web components following Trimble's Modus design system. This updated version focuses on improved flexibility, enhanced theming options, comprehensive cust

333 lines (327 loc) 18 kB
import { p as proxyCustomElement, H, d as createEvent, h, c as Host } from './p-X1tirp06.js'; import { i as inheritAriaAttributes } from './p-VPqXjOQn.js'; import { d as defineCustomElement$2 } from './p-s6LDESOI.js'; const convertPropsToClasses = ({ disabled, }) => { // Start with base DaisyUI class let classes = 'file-input'; // Disabled state if (disabled) { classes = `${classes} file-input-disabled`; } return classes; }; const modusWcFileDropzoneCss = "modus-wc-file-dropzone .modus-wc-file-dropzone .modus-wc-file-input{display:none}modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content{align-items:center;background-color:var(--modus-wc-color-gray-light);border:0.125rem dashed var(--modus-wc-color-gray-6);color:var(--modus-wc-color-gray-6);cursor:pointer;display:flex;flex-direction:column;font-weight:600;height:201px;justify-content:center;padding:0.75rem 1rem;transition:background-color 0.2s ease;width:431px}modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.dragging-over{background-color:var(--modus-wc-color-blue-pale);border-color:var(--modus-wc-color-trimble-blue);color:var(--modus-wc-color-gray-10)}modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.invalid-file-type{background-color:color-mix(in sRGB, var(--modus-wc-color-red-pale) 80%, transparent);border-color:var(--modus-wc-color-red-dark);color:var(--modus-wc-color-gray-10)}modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.upload-success{background-color:color-mix(in sRGB, var(--modus-wc-color-green-pale) 80%, transparent);border-color:var(--modus-wc-color-green-dark);color:var(--modus-wc-color-gray-10)}modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content .default-content{align-items:center;display:flex;flex-direction:column;justify-content:center}modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content .default-content span{text-align:center}modus-wc-file-dropzone .modus-wc-file-dropzone:has(input:disabled) .dropzone-content{background-color:var(--modus-wc-color-gray-0);border-color:var(--modus-wc-color-gray-6);color:var(--modus-wc-color-gray-6);cursor:not-allowed}[data-theme=modus-classic-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content,[data-theme=modus-modern-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content,[data-theme=connect-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content{background-color:var(--modus-wc-color-gray-10);color:var(--modus-wc-color-gray-3)}[data-theme=modus-classic-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.dragging-over,[data-theme=modus-modern-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.dragging-over,[data-theme=connect-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.dragging-over{background-color:color-mix(in sRGB, var(--modus-wc-color-highlight-blue) 30%, transparent);border-color:var(--modus-wc-color-highlight-blue);color:var(--modus-wc-color-gray-light)}[data-theme=modus-classic-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.invalid-file-type,[data-theme=modus-modern-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.invalid-file-type,[data-theme=connect-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.invalid-file-type{background-color:color-mix(in sRGB, var(--modus-wc-color-red) 50%, transparent);border-color:var(--modus-wc-color-red);color:var(--modus-wc-color-gray-light)}[data-theme=modus-classic-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.upload-success,[data-theme=modus-modern-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.upload-success,[data-theme=connect-dark] modus-wc-file-dropzone .modus-wc-file-dropzone .dropzone-content.upload-success{background-color:color-mix(in sRGB, var(--modus-wc-color-green) 50%, transparent);border-color:var(--modus-wc-color-green);color:var(--modus-wc-color-gray-light)}[data-theme=modus-classic-dark] modus-wc-file-dropzone .modus-wc-file-dropzone:has(input:disabled) .dropzone-content,[data-theme=modus-modern-dark] modus-wc-file-dropzone .modus-wc-file-dropzone:has(input:disabled) .dropzone-content,[data-theme=connect-dark] modus-wc-file-dropzone .modus-wc-file-dropzone:has(input:disabled) .dropzone-content{background-color:var(--modus-wc-color-gray-9);border-color:var(--modus-wc-color-gray-3);color:var(--modus-wc-color-gray-3);cursor:not-allowed}"; const ModusWcFileDropzone$1 = /*@__PURE__*/ proxyCustomElement(class ModusWcFileDropzone extends H { constructor() { super(); this.__registerHost(); this.fileSelect = createEvent(this, "fileSelect"); this.inheritedAttributes = {}; /** Tracks if files are being dragged over the dropzone */ this.isDraggingOver = false; /** Tracks if file has any validation error and the type of error */ this.invalidFile = 'none'; /** Stores the error message when validation fails */ this.errorMessage = ''; /** Tracks if files were successfully uploaded */ this.uploadSuccess = false; /** Custom CSS class to apply to the file dropzone element */ this.customClass = ''; /** Include state icon (upload, success, error) */ this.includeStateIcon = true; this.handleDropzoneDrop = (event) => { var _a; event.preventDefault(); event.stopPropagation(); this.isDraggingOver = false; if (this.disabled) return; const files = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.files; if (files && files.length > 0) { // Reset validation state this.invalidFile = 'none'; this.errorMessage = ''; this.uploadSuccess = false; // Check file count if (!this.isValidFileCount(files.length)) { this.setInputValue('count'); this.errorMessage = this.getErrorMessage('count'); return; } // Check total file size if (!this.isValidFileSize(files)) { this.setInputValue('size'); this.errorMessage = this.getErrorMessage('size'); return; } // Check each file for (let i = 0; i < files.length; i++) { // Check file type if (!this.isValidFileType(files[i])) { this.setInputValue('type'); this.errorMessage = this.getErrorMessage('type'); return; } // Check file name length if (!this.isValidFileName(files[i])) { this.setInputValue('name'); this.errorMessage = this.getErrorMessage('name'); return; } } this.uploadSuccess = true; this.fileSelect.emit(files); } }; this.handleDropzoneDragOver = (event) => { event.preventDefault(); event.stopPropagation(); if (this.disabled) return; if (!this.isDraggingOver) { this.isDraggingOver = true; } }; this.handleDropzoneDragLeave = (event) => { event.preventDefault(); event.stopPropagation(); this.isDraggingOver = false; }; this.handleDropzoneClick = () => { var _a; if (this.disabled) return; (_a = this.inputRef) === null || _a === void 0 ? void 0 : _a.click(); }; this.handleDropzoneKeyDown = (event) => { var _a; if (event.key === ' ' || event.key === 'Enter') { event.preventDefault(); if (this.disabled) return; (_a = this.inputRef) === null || _a === void 0 ? void 0 : _a.click(); } }; } componentWillLoad() { this.inheritedAttributes = inheritAriaAttributes(this.el); } /** * Reset the dropzone to its initial state, clearing all error and success states */ async reset() { this.invalidFile = 'none'; this.errorMessage = ''; this.uploadSuccess = false; this.isDraggingOver = false; if (this.inputRef) { this.inputRef.value = ''; } return Promise.resolve(); } // Generate error message based on validation type - called only when validation fails getErrorMessage(errorType) { switch (errorType) { case 'type': return this.invalidFileTypeMessage || 'File format not accepted'; case 'name': return 'Filename exceeds maximum length'; case 'count': return `Maximum number of files allowed is ${this.maxFileCount}`; case 'size': return `Total file size exceeds ${this.formatFileSize(this.maxTotalFileSizeBytes)}`; default: return 'Validation error'; } } isValidFileType(file) { if (!this.acceptFileTypes) return true; const acceptedTypes = this.acceptFileTypes .split(',') .map((type) => type.trim().toLowerCase()); const fileName = file.name.toLowerCase(); const fileType = file.type.toLowerCase(); return acceptedTypes.some((acceptedType) => { if (acceptedType.includes('/*')) { const mainType = acceptedType.split('/')[0]; return fileType.startsWith(mainType + '/'); } else if (acceptedType.startsWith('.')) { return fileName.endsWith(acceptedType); } else { return fileType === acceptedType; } }); } isValidFileName(file) { if (this.maxFileNameLength === undefined || this.maxFileNameLength === null) return true; const fileNameWithoutExtension = file.name.replace(/\.[^/.]+$/, ''); return fileNameWithoutExtension.length <= this.maxFileNameLength; } isValidFileCount(fileCount) { if (this.maxFileCount === undefined || this.maxFileCount === null) return true; return fileCount <= this.maxFileCount; } isValidFileSize(files) { if (this.maxTotalFileSizeBytes === undefined || this.maxTotalFileSizeBytes === null) return true; let totalSize = 0; for (let i = 0; i < files.length; i++) { totalSize += files[i].size; } return totalSize <= this.maxTotalFileSizeBytes; } setInputValue(val, inputElement) { this.invalidFile = val; const input = inputElement || this.inputRef; if (input) input.value = ''; return false; } handleFileChange(event) { const files = event.target.files; // Early return if no files if (!files || files.length === 0) { return; } // Reset validation state this.invalidFile = 'none'; this.errorMessage = ''; this.uploadSuccess = false; // Check file count if (!this.isValidFileCount(files.length)) { this.setInputValue('count', event.target); this.errorMessage = this.getErrorMessage('count'); return; } // Check total file size if (!this.isValidFileSize(files)) { this.setInputValue('size', event.target); this.errorMessage = this.getErrorMessage('size'); return; } // Check each file for (let i = 0; i < files.length; i++) { // Check file type if (!this.isValidFileType(files[i])) { this.setInputValue('type', event.target); this.errorMessage = this.getErrorMessage('type'); return; } // Check file name length if (!this.isValidFileName(files[i])) { this.setInputValue('name', event.target); this.errorMessage = this.getErrorMessage('name'); return; } } this.fileSelect.emit(files); this.uploadSuccess = true; } getClasses() { const classList = ['modus-wc-file-input']; const propClasses = convertPropsToClasses({ disabled: this.disabled, }); // The order CSS classes are added matters to CSS specificity if (propClasses) classList.push(propClasses); return classList.join(' '); } // Helper method to format file size in human-readable format formatFileSize(bytes) { if (!bytes) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } render() { const messages = { success: this.successMessage || 'Successfully uploaded', dragOver: this.fileDraggedOverInstructions || 'Drop files here', }; const hasState = this.isDraggingOver || this.invalidFile !== 'none' || this.uploadSuccess; const showIcon = this.includeStateIcon !== false; const iconMap = { dragging: 'cloud_upload', invalid: 'alert', success: 'check_circle', default: 'cloud_upload', }; const iconState = this.isDraggingOver ? iconMap.dragging : this.invalidFile !== 'none' ? iconMap.invalid : this.uploadSuccess ? iconMap.success : iconMap.default; const iconClass = this.isDraggingOver ? 'upload-icon' : this.invalidFile !== 'none' ? 'error-icon' : this.uploadSuccess ? 'success-icon' : 'upload-icon'; const message = this.isDraggingOver ? messages.dragOver : this.invalidFile !== 'none' ? this.errorMessage : this.uploadSuccess ? messages.success : this.instructions; const showDropzoneSlot = hasState || this.disabled ? 'none' : 'block'; return (h(Host, { key: 'af24e075777f3c228a5a6633e5050c85a9ce7369' }, h("div", { key: '1d779ea4719bfa0c8e89ba7ea309ff39c6cf79fb', class: "modus-wc-file-dropzone" }, h("input", { key: 'd65b32ad27ef57cee54a4782dbc8a0b166354c25', accept: this.acceptFileTypes, class: this.getClasses(), disabled: this.disabled, multiple: this.multiple, onChange: (event) => this.handleFileChange(event), ref: (el) => (this.inputRef = el), type: "file" }), h("div", Object.assign({ key: '35b82d6945aa001223d1187a9beac03626306a82', "aria-disabled": this.disabled ? 'true' : 'false', class: `dropzone-content ${this.isDraggingOver ? 'dragging-over' : ''} ${!this.isDraggingOver && this.invalidFile !== 'none' ? 'invalid-file-type' : ''} ${!this.isDraggingOver && this.uploadSuccess ? 'upload-success' : ''} ${this.customClass || ''}`, onClick: this.handleDropzoneClick, onDragLeave: this.handleDropzoneDragLeave, onDragOver: this.handleDropzoneDragOver, onDrop: this.handleDropzoneDrop, onKeyDown: this.handleDropzoneKeyDown, role: "button", tabindex: this.disabled ? -1 : 0 }, this.inheritedAttributes), h("div", { key: '634ac7af3f46c884f963def72dbf26a3ab22307d', class: "default-content" }, showIcon && (h("modus-wc-icon", { key: 'c23a30b68ec7b7d788cf81a264439ea9f1f37890', class: iconClass, name: iconState, size: "lg", variant: "solid" })), h("span", { key: '22833a4c4a0d782cbd7f036120782c3bfefd86a4' }, message), h("div", { key: '6213bb059f17c6da27d36a681ea6e0a8a08b938c', style: { display: showDropzoneSlot } }, h("slot", { key: '3c488bd1855c3903e55d5fd078ca7fde60422983', name: "dropzone" }))))))); } get el() { return this; } static get style() { return modusWcFileDropzoneCss; } }, [4, "modus-wc-file-dropzone", { "acceptFileTypes": [1, "accept-file-types"], "customClass": [1, "custom-class"], "disabled": [4], "fileDraggedOverInstructions": [1, "file-dragged-over-instructions"], "includeStateIcon": [4, "include-state-icon"], "instructions": [1], "invalidFileTypeMessage": [1, "invalid-file-type-message"], "maxFileNameLength": [2, "max-file-name-length"], "maxFileCount": [2, "max-file-count"], "maxTotalFileSizeBytes": [2, "max-total-file-size-bytes"], "multiple": [4], "successMessage": [1, "success-message"], "isDraggingOver": [32], "invalidFile": [32], "errorMessage": [32], "uploadSuccess": [32], "reset": [64] }]); function defineCustomElement$1() { if (typeof customElements === "undefined") { return; } const components = ["modus-wc-file-dropzone", "modus-wc-icon"]; components.forEach(tagName => { switch (tagName) { case "modus-wc-file-dropzone": if (!customElements.get(tagName)) { customElements.define(tagName, ModusWcFileDropzone$1); } break; case "modus-wc-icon": if (!customElements.get(tagName)) { defineCustomElement$2(); } break; } }); } const ModusWcFileDropzone = ModusWcFileDropzone$1; const defineCustomElement = defineCustomElement$1; export { ModusWcFileDropzone, defineCustomElement };