@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
JavaScript
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 };