@idds/js 1.0.56 → 1.0.58
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/dist/index.iife.js +384 -0
- package/dist/index.js +384 -0
- package/package.json +1 -1
package/dist/index.iife.js
CHANGED
|
@@ -25,11 +25,13 @@ var InaUI = (() => {
|
|
|
25
25
|
initCheckbox: () => initCheckbox,
|
|
26
26
|
initDatepicker: () => initDatepicker,
|
|
27
27
|
initDropdown: () => initDropdown,
|
|
28
|
+
initFileUpload: () => initFileUpload,
|
|
28
29
|
initFileUploadBase: () => initFileUploadBase,
|
|
29
30
|
initFileUploadItem: () => initFileUploadItem,
|
|
30
31
|
initImgCompare: () => initImgCompare,
|
|
31
32
|
initModal: () => initModal,
|
|
32
33
|
initRangeDatepicker: () => initRangeDatepicker,
|
|
34
|
+
initSingleFileUpload: () => initSingleFileUpload,
|
|
33
35
|
initTab: () => initTab,
|
|
34
36
|
initTimepicker: () => initTimepicker,
|
|
35
37
|
initToggle: () => initToggle,
|
|
@@ -971,6 +973,386 @@ var InaUI = (() => {
|
|
|
971
973
|
});
|
|
972
974
|
}
|
|
973
975
|
|
|
976
|
+
// src/js/components/stateful/file-upload.js
|
|
977
|
+
var ICONS = {
|
|
978
|
+
upload: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" y1="2" x2="12" y2="16"></line></svg>`,
|
|
979
|
+
file: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>`,
|
|
980
|
+
trash: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>`,
|
|
981
|
+
check: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>`,
|
|
982
|
+
error: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>`,
|
|
983
|
+
loader: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="${PREFIX}-file-upload__file-icon--spinning"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>`
|
|
984
|
+
};
|
|
985
|
+
function initFileUpload(rootSelector = `.${PREFIX}-file-upload`) {
|
|
986
|
+
const fileUploads = document.querySelectorAll(rootSelector);
|
|
987
|
+
fileUploads.forEach((container) => {
|
|
988
|
+
if (container.__inaFileUploadInitialized) return;
|
|
989
|
+
const input = container.querySelector(`.${PREFIX}-file-upload__input`);
|
|
990
|
+
const dropzone = container.querySelector(
|
|
991
|
+
`.${PREFIX}-file-upload__dropzone`
|
|
992
|
+
);
|
|
993
|
+
let filesContainer = container.querySelector(
|
|
994
|
+
`.${PREFIX}-file-upload__files`
|
|
995
|
+
);
|
|
996
|
+
if (!filesContainer) {
|
|
997
|
+
filesContainer = document.createElement("div");
|
|
998
|
+
filesContainer.className = `${PREFIX}-file-upload__files`;
|
|
999
|
+
container.appendChild(filesContainer);
|
|
1000
|
+
}
|
|
1001
|
+
let errorsContainer = container.querySelector(
|
|
1002
|
+
`.${PREFIX}-file-upload__errors`
|
|
1003
|
+
);
|
|
1004
|
+
if (!errorsContainer) {
|
|
1005
|
+
errorsContainer = document.createElement("div");
|
|
1006
|
+
errorsContainer.className = `${PREFIX}-file-upload__errors`;
|
|
1007
|
+
container.appendChild(errorsContainer);
|
|
1008
|
+
}
|
|
1009
|
+
if (!input || !dropzone) return;
|
|
1010
|
+
const maxFiles = parseInt(container.getAttribute("data-max-files")) || 0;
|
|
1011
|
+
const maxSize = parseInt(container.getAttribute("data-max-size")) || 0;
|
|
1012
|
+
const allowedExtensions = (container.getAttribute("data-allowed-extensions") || "").split(",").filter(Boolean);
|
|
1013
|
+
const multiple = input.hasAttribute("multiple");
|
|
1014
|
+
let uploadedFiles = [];
|
|
1015
|
+
const formatFileSize = (bytes) => {
|
|
1016
|
+
if (bytes === 0) return "0 Bytes";
|
|
1017
|
+
const k = 1024;
|
|
1018
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
1019
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1020
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
1021
|
+
};
|
|
1022
|
+
const generateId = () => Math.random().toString(36).substr(2, 9);
|
|
1023
|
+
const validateFile = (file) => {
|
|
1024
|
+
if (allowedExtensions.length > 0) {
|
|
1025
|
+
const ext = file.name.split(".").pop().toLowerCase();
|
|
1026
|
+
if (!allowedExtensions.includes(ext.toLowerCase())) {
|
|
1027
|
+
return {
|
|
1028
|
+
valid: false,
|
|
1029
|
+
error: `Ekstensi file harus: ${allowedExtensions.join(", ")}`
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (maxSize > 0 && file.size > maxSize) {
|
|
1034
|
+
return {
|
|
1035
|
+
valid: false,
|
|
1036
|
+
error: `Ukuran file maksimal ${formatFileSize(maxSize)}`
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
return { valid: true };
|
|
1040
|
+
};
|
|
1041
|
+
const renderFiles = () => {
|
|
1042
|
+
filesContainer.innerHTML = "";
|
|
1043
|
+
errorsContainer.innerHTML = "";
|
|
1044
|
+
uploadedFiles.forEach((f, index) => {
|
|
1045
|
+
const fileEl = document.createElement("div");
|
|
1046
|
+
fileEl.className = `${PREFIX}-file-upload__file`;
|
|
1047
|
+
let statusClass = "";
|
|
1048
|
+
let iconHtml = "";
|
|
1049
|
+
if (f.status === "uploading") {
|
|
1050
|
+
statusClass = `${PREFIX}-file-upload__file--uploading`;
|
|
1051
|
+
iconHtml = `<div class="${PREFIX}-file-upload__file-icon-wrapper ${PREFIX}-file-upload__file-icon-wrapper--uploading">${ICONS.loader}</div>`;
|
|
1052
|
+
} else if (f.status === "success") {
|
|
1053
|
+
statusClass = `${PREFIX}-file-upload__file--success`;
|
|
1054
|
+
iconHtml = `<div class="${PREFIX}-file-upload__file-icon-wrapper ${PREFIX}-file-upload__file-icon-wrapper--success">${ICONS.check}</div>`;
|
|
1055
|
+
} else if (f.status === "error") {
|
|
1056
|
+
statusClass = `${PREFIX}-file-upload__file--error`;
|
|
1057
|
+
iconHtml = `<div class="${PREFIX}-file-upload__file-icon-wrapper ${PREFIX}-file-upload__file-icon-wrapper--error">${ICONS.error}</div>`;
|
|
1058
|
+
} else {
|
|
1059
|
+
iconHtml = `<div class="${PREFIX}-file-upload__file-icon-wrapper ${PREFIX}-file-upload__file-icon-wrapper--success">${ICONS.check}</div>`;
|
|
1060
|
+
}
|
|
1061
|
+
if (statusClass) fileEl.classList.add(statusClass);
|
|
1062
|
+
fileEl.innerHTML = `
|
|
1063
|
+
<div class="${PREFIX}-file-upload__file-indicator">
|
|
1064
|
+
${iconHtml}
|
|
1065
|
+
</div>
|
|
1066
|
+
<div class="${PREFIX}-file-upload__file-info">
|
|
1067
|
+
<div class="${PREFIX}-file-upload__file-name">${f.file.name}</div>
|
|
1068
|
+
<div class="${PREFIX}-file-upload__file-size">${formatFileSize(f.file.size)}</div>
|
|
1069
|
+
${f.error ? `<div class="${PREFIX}-file-upload__file-error">${f.error}</div>` : ""}
|
|
1070
|
+
</div>
|
|
1071
|
+
<div class="${PREFIX}-file-upload__file-actions">
|
|
1072
|
+
<button type="button" class="${PREFIX}-file-upload__file-remove" data-id="${f.id}" title="Hapus file">
|
|
1073
|
+
${ICONS.trash}
|
|
1074
|
+
</button>
|
|
1075
|
+
</div>
|
|
1076
|
+
`;
|
|
1077
|
+
filesContainer.appendChild(fileEl);
|
|
1078
|
+
});
|
|
1079
|
+
filesContainer.querySelectorAll(`.${PREFIX}-file-upload__file-remove`).forEach((btn) => {
|
|
1080
|
+
btn.addEventListener("click", (e) => {
|
|
1081
|
+
e.stopPropagation();
|
|
1082
|
+
const id = btn.getAttribute("data-id");
|
|
1083
|
+
removeFile(id);
|
|
1084
|
+
});
|
|
1085
|
+
});
|
|
1086
|
+
};
|
|
1087
|
+
const addFiles = (newFiles) => {
|
|
1088
|
+
const validNewFiles = [];
|
|
1089
|
+
const errors = [];
|
|
1090
|
+
Array.from(newFiles).forEach((file) => {
|
|
1091
|
+
const validation = validateFile(file);
|
|
1092
|
+
if (!validation.valid) {
|
|
1093
|
+
errors.push({ file, error: validation.error });
|
|
1094
|
+
} else {
|
|
1095
|
+
validNewFiles.push({
|
|
1096
|
+
file,
|
|
1097
|
+
status: "idle",
|
|
1098
|
+
// In vanilla we might simulate upload or just set success immediately
|
|
1099
|
+
id: generateId()
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
if (multiple && maxFiles > 0) {
|
|
1104
|
+
if (uploadedFiles.length + validNewFiles.length > maxFiles) {
|
|
1105
|
+
const spaceLeft = maxFiles - uploadedFiles.length;
|
|
1106
|
+
if (spaceLeft > 0) {
|
|
1107
|
+
validNewFiles.splice(spaceLeft);
|
|
1108
|
+
} else {
|
|
1109
|
+
validNewFiles.length = 0;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (!multiple) {
|
|
1114
|
+
uploadedFiles = validNewFiles.slice(0, 1);
|
|
1115
|
+
} else {
|
|
1116
|
+
uploadedFiles = [...uploadedFiles, ...validNewFiles];
|
|
1117
|
+
}
|
|
1118
|
+
errorsContainer.innerHTML = "";
|
|
1119
|
+
errors.forEach((err) => {
|
|
1120
|
+
const errEl = document.createElement("div");
|
|
1121
|
+
errEl.className = `${PREFIX}-file-upload__error-message`;
|
|
1122
|
+
errEl.textContent = `${err.file.name}: ${err.error}`;
|
|
1123
|
+
errorsContainer.appendChild(errEl);
|
|
1124
|
+
});
|
|
1125
|
+
renderFiles();
|
|
1126
|
+
container.dispatchEvent(
|
|
1127
|
+
new CustomEvent("file-upload:change", {
|
|
1128
|
+
detail: {
|
|
1129
|
+
files: uploadedFiles.map((f) => f.file),
|
|
1130
|
+
errors
|
|
1131
|
+
},
|
|
1132
|
+
bubbles: true
|
|
1133
|
+
})
|
|
1134
|
+
);
|
|
1135
|
+
validNewFiles.forEach((f) => {
|
|
1136
|
+
f.status = "uploading";
|
|
1137
|
+
});
|
|
1138
|
+
renderFiles();
|
|
1139
|
+
setTimeout(() => {
|
|
1140
|
+
validNewFiles.forEach((f) => {
|
|
1141
|
+
f.status = "success";
|
|
1142
|
+
});
|
|
1143
|
+
renderFiles();
|
|
1144
|
+
}, 1e3);
|
|
1145
|
+
};
|
|
1146
|
+
const removeFile = (id) => {
|
|
1147
|
+
uploadedFiles = uploadedFiles.filter((f) => f.id !== id);
|
|
1148
|
+
renderFiles();
|
|
1149
|
+
input.value = "";
|
|
1150
|
+
};
|
|
1151
|
+
dropzone.addEventListener("click", () => {
|
|
1152
|
+
if (!input.disabled) input.click();
|
|
1153
|
+
});
|
|
1154
|
+
input.addEventListener("change", (e) => {
|
|
1155
|
+
if (input.files.length > 0) {
|
|
1156
|
+
addFiles(input.files);
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
|
|
1160
|
+
dropzone.addEventListener(eventName, (e) => {
|
|
1161
|
+
e.preventDefault();
|
|
1162
|
+
e.stopPropagation();
|
|
1163
|
+
});
|
|
1164
|
+
});
|
|
1165
|
+
dropzone.addEventListener("dragover", () => {
|
|
1166
|
+
if (!input.disabled)
|
|
1167
|
+
dropzone.classList.add(`${PREFIX}-file-upload__dropzone--drag-over`);
|
|
1168
|
+
});
|
|
1169
|
+
dropzone.addEventListener("dragleave", () => {
|
|
1170
|
+
dropzone.classList.remove(`${PREFIX}-file-upload__dropzone--drag-over`);
|
|
1171
|
+
});
|
|
1172
|
+
dropzone.addEventListener("drop", (e) => {
|
|
1173
|
+
dropzone.classList.remove(`${PREFIX}-file-upload__dropzone--drag-over`);
|
|
1174
|
+
if (input.disabled) return;
|
|
1175
|
+
const dt = e.dataTransfer;
|
|
1176
|
+
const files = dt.files;
|
|
1177
|
+
if (files.length > 0) {
|
|
1178
|
+
addFiles(files);
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
container.__inaFileUploadInitialized = true;
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/js/components/stateful/single-file-upload.js
|
|
1186
|
+
var ICONS2 = {
|
|
1187
|
+
upload: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" y1="2" x2="12" y2="16"></line></svg>`,
|
|
1188
|
+
trash: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>`,
|
|
1189
|
+
file: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>`,
|
|
1190
|
+
pdf: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><path d="M10 13a1 1 0 0 0-1 1v4"></path><path d="M10 13h1a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-1"></path><path d="M14 13h1a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-1v-2"></path><path d="M14 15h2"></path></svg>`,
|
|
1191
|
+
image: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>`
|
|
1192
|
+
};
|
|
1193
|
+
function initSingleFileUpload(rootSelector = `.${PREFIX}-single-file-upload`) {
|
|
1194
|
+
const fileUploads = document.querySelectorAll(rootSelector);
|
|
1195
|
+
fileUploads.forEach((container) => {
|
|
1196
|
+
if (container.__inaSingleFileUploadInitialized) return;
|
|
1197
|
+
const input = container.querySelector(
|
|
1198
|
+
`.${PREFIX}-single-file-upload__input`
|
|
1199
|
+
);
|
|
1200
|
+
const containerEl = container.querySelector(
|
|
1201
|
+
`.${PREFIX}-single-file-upload__container`
|
|
1202
|
+
);
|
|
1203
|
+
if (!input || !containerEl) return;
|
|
1204
|
+
const titleEl = container.querySelector(
|
|
1205
|
+
`.${PREFIX}-single-file-upload__title`
|
|
1206
|
+
);
|
|
1207
|
+
const descriptionEl = container.querySelector(
|
|
1208
|
+
`.${PREFIX}-single-file-upload__description`
|
|
1209
|
+
);
|
|
1210
|
+
const initialTitle = titleEl ? titleEl.textContent : "Unggah File";
|
|
1211
|
+
const initialDescription = descriptionEl ? descriptionEl.textContent : "";
|
|
1212
|
+
const maxSize = parseInt(container.getAttribute("data-max-size")) || 0;
|
|
1213
|
+
const allowedExtensions = (container.getAttribute("data-allowed-extensions") || "").split(",").filter(Boolean);
|
|
1214
|
+
let status = "idle";
|
|
1215
|
+
let currentFile = null;
|
|
1216
|
+
let progress = 0;
|
|
1217
|
+
const getFileIcon = (file) => {
|
|
1218
|
+
if (file.type.includes("pdf")) return ICONS2.pdf;
|
|
1219
|
+
if (file.type.includes("image")) return ICONS2.image;
|
|
1220
|
+
return ICONS2.file;
|
|
1221
|
+
};
|
|
1222
|
+
const formatFileSize = (bytes) => {
|
|
1223
|
+
return (bytes / (1024 * 1024)).toFixed(2) + " MB";
|
|
1224
|
+
};
|
|
1225
|
+
const updateUI = () => {
|
|
1226
|
+
containerEl.innerHTML = "";
|
|
1227
|
+
if (!currentFile && status === "idle") {
|
|
1228
|
+
containerEl.innerHTML = `
|
|
1229
|
+
<div class="${PREFIX}-single-file-upload__icon-wrapper ${PREFIX}-single-file-upload__icon-wrapper--default">
|
|
1230
|
+
${ICONS2.upload}
|
|
1231
|
+
</div>
|
|
1232
|
+
<div class="${PREFIX}-single-file-upload__content">
|
|
1233
|
+
<div class="${PREFIX}-single-file-upload__title">${initialTitle}</div>
|
|
1234
|
+
<div class="${PREFIX}-single-file-upload__description">${initialDescription}</div>
|
|
1235
|
+
</div>
|
|
1236
|
+
`;
|
|
1237
|
+
} else if (!currentFile && status === "uploading") {
|
|
1238
|
+
containerEl.innerHTML = `
|
|
1239
|
+
<div class="${PREFIX}-single-file-upload__icon-wrapper ${PREFIX}-single-file-upload__icon-wrapper--default">
|
|
1240
|
+
${ICONS2.upload}
|
|
1241
|
+
</div>
|
|
1242
|
+
<div class="${PREFIX}-single-file-upload__progress">
|
|
1243
|
+
<div class="${PREFIX}-single-file-upload__progress-bar">
|
|
1244
|
+
<div class="${PREFIX}-single-file-upload__progress-fill" style="width: ${progress}%"></div>
|
|
1245
|
+
</div>
|
|
1246
|
+
<div class="${PREFIX}-single-file-upload__progress-text">
|
|
1247
|
+
Uploading... ${progress}%
|
|
1248
|
+
</div>
|
|
1249
|
+
</div>
|
|
1250
|
+
`;
|
|
1251
|
+
} else if (currentFile && status === "success") {
|
|
1252
|
+
containerEl.innerHTML = `
|
|
1253
|
+
<div class="${PREFIX}-single-file-upload__icon-wrapper ${PREFIX}-single-file-upload__icon-wrapper--file">
|
|
1254
|
+
${getFileIcon(currentFile)}
|
|
1255
|
+
</div>
|
|
1256
|
+
<div class="${PREFIX}-single-file-upload__content">
|
|
1257
|
+
<div class="${PREFIX}-single-file-upload__title">${currentFile.name}</div>
|
|
1258
|
+
<div class="${PREFIX}-single-file-upload__description">${initialDescription}</div>
|
|
1259
|
+
</div>
|
|
1260
|
+
<button type="button" class="${PREFIX}-single-file-upload__delete-button" aria-label="Remove file">
|
|
1261
|
+
${ICONS2.trash}
|
|
1262
|
+
</button>
|
|
1263
|
+
`;
|
|
1264
|
+
const deleteBtn = containerEl.querySelector(
|
|
1265
|
+
`.${PREFIX}-single-file-upload__delete-button`
|
|
1266
|
+
);
|
|
1267
|
+
if (deleteBtn) {
|
|
1268
|
+
deleteBtn.addEventListener("click", (e) => {
|
|
1269
|
+
e.stopPropagation();
|
|
1270
|
+
removeFile();
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
const handleFile = (file) => {
|
|
1276
|
+
if (maxSize > 0 && file.size > maxSize) {
|
|
1277
|
+
alert("File size exceeds limit.");
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
if (allowedExtensions.length > 0) {
|
|
1281
|
+
const ext = file.name.split(".").pop();
|
|
1282
|
+
if (!allowedExtensions.includes(ext)) {
|
|
1283
|
+
alert("Invalid file extension.");
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
currentFile = file;
|
|
1288
|
+
status = "uploading";
|
|
1289
|
+
progress = 0;
|
|
1290
|
+
updateUI();
|
|
1291
|
+
const interval = setInterval(() => {
|
|
1292
|
+
progress += 10;
|
|
1293
|
+
updateUI();
|
|
1294
|
+
if (progress >= 100) {
|
|
1295
|
+
clearInterval(interval);
|
|
1296
|
+
status = "success";
|
|
1297
|
+
updateUI();
|
|
1298
|
+
container.dispatchEvent(
|
|
1299
|
+
new CustomEvent("single-file-upload:change", {
|
|
1300
|
+
detail: { file },
|
|
1301
|
+
bubbles: true
|
|
1302
|
+
})
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
}, 100);
|
|
1306
|
+
};
|
|
1307
|
+
const removeFile = () => {
|
|
1308
|
+
currentFile = null;
|
|
1309
|
+
status = "idle";
|
|
1310
|
+
input.value = "";
|
|
1311
|
+
updateUI();
|
|
1312
|
+
container.dispatchEvent(
|
|
1313
|
+
new CustomEvent("single-file-upload:change", {
|
|
1314
|
+
detail: { file: null },
|
|
1315
|
+
bubbles: true
|
|
1316
|
+
})
|
|
1317
|
+
);
|
|
1318
|
+
};
|
|
1319
|
+
input.addEventListener("change", () => {
|
|
1320
|
+
if (input.files[0]) handleFile(input.files[0]);
|
|
1321
|
+
});
|
|
1322
|
+
containerEl.addEventListener("click", (e) => {
|
|
1323
|
+
if (status === "idle") input.click();
|
|
1324
|
+
});
|
|
1325
|
+
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
|
|
1326
|
+
containerEl.addEventListener(eventName, (e) => {
|
|
1327
|
+
e.preventDefault();
|
|
1328
|
+
e.stopPropagation();
|
|
1329
|
+
});
|
|
1330
|
+
});
|
|
1331
|
+
containerEl.addEventListener("dragover", () => {
|
|
1332
|
+
if (!currentFile && !input.disabled) {
|
|
1333
|
+
containerEl.classList.add(
|
|
1334
|
+
`${PREFIX}-single-file-upload__container--active`
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
containerEl.addEventListener("dragleave", () => {
|
|
1339
|
+
containerEl.classList.remove(
|
|
1340
|
+
`${PREFIX}-single-file-upload__container--active`
|
|
1341
|
+
);
|
|
1342
|
+
});
|
|
1343
|
+
containerEl.addEventListener("drop", (e) => {
|
|
1344
|
+
containerEl.classList.remove(
|
|
1345
|
+
`${PREFIX}-single-file-upload__container--active`
|
|
1346
|
+
);
|
|
1347
|
+
if (input.disabled || currentFile) return;
|
|
1348
|
+
const file = e.dataTransfer.files[0];
|
|
1349
|
+
if (file) handleFile(file);
|
|
1350
|
+
});
|
|
1351
|
+
updateUI();
|
|
1352
|
+
container.__inaSingleFileUploadInitialized = true;
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
|
|
974
1356
|
// src/js/components/stateful/file-upload-base.js
|
|
975
1357
|
function initFileUploadBase(rootSelector = `.${PREFIX}-file-base`) {
|
|
976
1358
|
document.querySelectorAll(rootSelector).forEach((fileUploadBase) => {
|
|
@@ -1841,6 +2223,8 @@ var InaUI = (() => {
|
|
|
1841
2223
|
initCheckbox();
|
|
1842
2224
|
initDatepicker();
|
|
1843
2225
|
initDropdown();
|
|
2226
|
+
initFileUpload();
|
|
2227
|
+
initSingleFileUpload();
|
|
1844
2228
|
initFileUploadBase();
|
|
1845
2229
|
initFileUploadItem();
|
|
1846
2230
|
initImgCompare();
|
package/dist/index.js
CHANGED
|
@@ -976,6 +976,386 @@ function initDropdown(rootSelector = `.${PREFIX}-dropdown`) {
|
|
|
976
976
|
});
|
|
977
977
|
}
|
|
978
978
|
|
|
979
|
+
// src/js/components/stateful/file-upload.js
|
|
980
|
+
var ICONS = {
|
|
981
|
+
upload: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" y1="2" x2="12" y2="16"></line></svg>`,
|
|
982
|
+
file: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>`,
|
|
983
|
+
trash: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>`,
|
|
984
|
+
check: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>`,
|
|
985
|
+
error: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>`,
|
|
986
|
+
loader: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="${PREFIX}-file-upload__file-icon--spinning"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>`
|
|
987
|
+
};
|
|
988
|
+
function initFileUpload(rootSelector = `.${PREFIX}-file-upload`) {
|
|
989
|
+
const fileUploads = document.querySelectorAll(rootSelector);
|
|
990
|
+
fileUploads.forEach((container) => {
|
|
991
|
+
if (container.__inaFileUploadInitialized) return;
|
|
992
|
+
const input = container.querySelector(`.${PREFIX}-file-upload__input`);
|
|
993
|
+
const dropzone = container.querySelector(
|
|
994
|
+
`.${PREFIX}-file-upload__dropzone`
|
|
995
|
+
);
|
|
996
|
+
let filesContainer = container.querySelector(
|
|
997
|
+
`.${PREFIX}-file-upload__files`
|
|
998
|
+
);
|
|
999
|
+
if (!filesContainer) {
|
|
1000
|
+
filesContainer = document.createElement("div");
|
|
1001
|
+
filesContainer.className = `${PREFIX}-file-upload__files`;
|
|
1002
|
+
container.appendChild(filesContainer);
|
|
1003
|
+
}
|
|
1004
|
+
let errorsContainer = container.querySelector(
|
|
1005
|
+
`.${PREFIX}-file-upload__errors`
|
|
1006
|
+
);
|
|
1007
|
+
if (!errorsContainer) {
|
|
1008
|
+
errorsContainer = document.createElement("div");
|
|
1009
|
+
errorsContainer.className = `${PREFIX}-file-upload__errors`;
|
|
1010
|
+
container.appendChild(errorsContainer);
|
|
1011
|
+
}
|
|
1012
|
+
if (!input || !dropzone) return;
|
|
1013
|
+
const maxFiles = parseInt(container.getAttribute("data-max-files")) || 0;
|
|
1014
|
+
const maxSize = parseInt(container.getAttribute("data-max-size")) || 0;
|
|
1015
|
+
const allowedExtensions = (container.getAttribute("data-allowed-extensions") || "").split(",").filter(Boolean);
|
|
1016
|
+
const multiple = input.hasAttribute("multiple");
|
|
1017
|
+
let uploadedFiles = [];
|
|
1018
|
+
const formatFileSize = (bytes) => {
|
|
1019
|
+
if (bytes === 0) return "0 Bytes";
|
|
1020
|
+
const k = 1024;
|
|
1021
|
+
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
1022
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1023
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
1024
|
+
};
|
|
1025
|
+
const generateId = () => Math.random().toString(36).substr(2, 9);
|
|
1026
|
+
const validateFile = (file) => {
|
|
1027
|
+
if (allowedExtensions.length > 0) {
|
|
1028
|
+
const ext = file.name.split(".").pop().toLowerCase();
|
|
1029
|
+
if (!allowedExtensions.includes(ext.toLowerCase())) {
|
|
1030
|
+
return {
|
|
1031
|
+
valid: false,
|
|
1032
|
+
error: `Ekstensi file harus: ${allowedExtensions.join(", ")}`
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
if (maxSize > 0 && file.size > maxSize) {
|
|
1037
|
+
return {
|
|
1038
|
+
valid: false,
|
|
1039
|
+
error: `Ukuran file maksimal ${formatFileSize(maxSize)}`
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
return { valid: true };
|
|
1043
|
+
};
|
|
1044
|
+
const renderFiles = () => {
|
|
1045
|
+
filesContainer.innerHTML = "";
|
|
1046
|
+
errorsContainer.innerHTML = "";
|
|
1047
|
+
uploadedFiles.forEach((f, index) => {
|
|
1048
|
+
const fileEl = document.createElement("div");
|
|
1049
|
+
fileEl.className = `${PREFIX}-file-upload__file`;
|
|
1050
|
+
let statusClass = "";
|
|
1051
|
+
let iconHtml = "";
|
|
1052
|
+
if (f.status === "uploading") {
|
|
1053
|
+
statusClass = `${PREFIX}-file-upload__file--uploading`;
|
|
1054
|
+
iconHtml = `<div class="${PREFIX}-file-upload__file-icon-wrapper ${PREFIX}-file-upload__file-icon-wrapper--uploading">${ICONS.loader}</div>`;
|
|
1055
|
+
} else if (f.status === "success") {
|
|
1056
|
+
statusClass = `${PREFIX}-file-upload__file--success`;
|
|
1057
|
+
iconHtml = `<div class="${PREFIX}-file-upload__file-icon-wrapper ${PREFIX}-file-upload__file-icon-wrapper--success">${ICONS.check}</div>`;
|
|
1058
|
+
} else if (f.status === "error") {
|
|
1059
|
+
statusClass = `${PREFIX}-file-upload__file--error`;
|
|
1060
|
+
iconHtml = `<div class="${PREFIX}-file-upload__file-icon-wrapper ${PREFIX}-file-upload__file-icon-wrapper--error">${ICONS.error}</div>`;
|
|
1061
|
+
} else {
|
|
1062
|
+
iconHtml = `<div class="${PREFIX}-file-upload__file-icon-wrapper ${PREFIX}-file-upload__file-icon-wrapper--success">${ICONS.check}</div>`;
|
|
1063
|
+
}
|
|
1064
|
+
if (statusClass) fileEl.classList.add(statusClass);
|
|
1065
|
+
fileEl.innerHTML = `
|
|
1066
|
+
<div class="${PREFIX}-file-upload__file-indicator">
|
|
1067
|
+
${iconHtml}
|
|
1068
|
+
</div>
|
|
1069
|
+
<div class="${PREFIX}-file-upload__file-info">
|
|
1070
|
+
<div class="${PREFIX}-file-upload__file-name">${f.file.name}</div>
|
|
1071
|
+
<div class="${PREFIX}-file-upload__file-size">${formatFileSize(f.file.size)}</div>
|
|
1072
|
+
${f.error ? `<div class="${PREFIX}-file-upload__file-error">${f.error}</div>` : ""}
|
|
1073
|
+
</div>
|
|
1074
|
+
<div class="${PREFIX}-file-upload__file-actions">
|
|
1075
|
+
<button type="button" class="${PREFIX}-file-upload__file-remove" data-id="${f.id}" title="Hapus file">
|
|
1076
|
+
${ICONS.trash}
|
|
1077
|
+
</button>
|
|
1078
|
+
</div>
|
|
1079
|
+
`;
|
|
1080
|
+
filesContainer.appendChild(fileEl);
|
|
1081
|
+
});
|
|
1082
|
+
filesContainer.querySelectorAll(`.${PREFIX}-file-upload__file-remove`).forEach((btn) => {
|
|
1083
|
+
btn.addEventListener("click", (e) => {
|
|
1084
|
+
e.stopPropagation();
|
|
1085
|
+
const id = btn.getAttribute("data-id");
|
|
1086
|
+
removeFile(id);
|
|
1087
|
+
});
|
|
1088
|
+
});
|
|
1089
|
+
};
|
|
1090
|
+
const addFiles = (newFiles) => {
|
|
1091
|
+
const validNewFiles = [];
|
|
1092
|
+
const errors = [];
|
|
1093
|
+
Array.from(newFiles).forEach((file) => {
|
|
1094
|
+
const validation = validateFile(file);
|
|
1095
|
+
if (!validation.valid) {
|
|
1096
|
+
errors.push({ file, error: validation.error });
|
|
1097
|
+
} else {
|
|
1098
|
+
validNewFiles.push({
|
|
1099
|
+
file,
|
|
1100
|
+
status: "idle",
|
|
1101
|
+
// In vanilla we might simulate upload or just set success immediately
|
|
1102
|
+
id: generateId()
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
if (multiple && maxFiles > 0) {
|
|
1107
|
+
if (uploadedFiles.length + validNewFiles.length > maxFiles) {
|
|
1108
|
+
const spaceLeft = maxFiles - uploadedFiles.length;
|
|
1109
|
+
if (spaceLeft > 0) {
|
|
1110
|
+
validNewFiles.splice(spaceLeft);
|
|
1111
|
+
} else {
|
|
1112
|
+
validNewFiles.length = 0;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (!multiple) {
|
|
1117
|
+
uploadedFiles = validNewFiles.slice(0, 1);
|
|
1118
|
+
} else {
|
|
1119
|
+
uploadedFiles = [...uploadedFiles, ...validNewFiles];
|
|
1120
|
+
}
|
|
1121
|
+
errorsContainer.innerHTML = "";
|
|
1122
|
+
errors.forEach((err) => {
|
|
1123
|
+
const errEl = document.createElement("div");
|
|
1124
|
+
errEl.className = `${PREFIX}-file-upload__error-message`;
|
|
1125
|
+
errEl.textContent = `${err.file.name}: ${err.error}`;
|
|
1126
|
+
errorsContainer.appendChild(errEl);
|
|
1127
|
+
});
|
|
1128
|
+
renderFiles();
|
|
1129
|
+
container.dispatchEvent(
|
|
1130
|
+
new CustomEvent("file-upload:change", {
|
|
1131
|
+
detail: {
|
|
1132
|
+
files: uploadedFiles.map((f) => f.file),
|
|
1133
|
+
errors
|
|
1134
|
+
},
|
|
1135
|
+
bubbles: true
|
|
1136
|
+
})
|
|
1137
|
+
);
|
|
1138
|
+
validNewFiles.forEach((f) => {
|
|
1139
|
+
f.status = "uploading";
|
|
1140
|
+
});
|
|
1141
|
+
renderFiles();
|
|
1142
|
+
setTimeout(() => {
|
|
1143
|
+
validNewFiles.forEach((f) => {
|
|
1144
|
+
f.status = "success";
|
|
1145
|
+
});
|
|
1146
|
+
renderFiles();
|
|
1147
|
+
}, 1e3);
|
|
1148
|
+
};
|
|
1149
|
+
const removeFile = (id) => {
|
|
1150
|
+
uploadedFiles = uploadedFiles.filter((f) => f.id !== id);
|
|
1151
|
+
renderFiles();
|
|
1152
|
+
input.value = "";
|
|
1153
|
+
};
|
|
1154
|
+
dropzone.addEventListener("click", () => {
|
|
1155
|
+
if (!input.disabled) input.click();
|
|
1156
|
+
});
|
|
1157
|
+
input.addEventListener("change", (e) => {
|
|
1158
|
+
if (input.files.length > 0) {
|
|
1159
|
+
addFiles(input.files);
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
|
|
1163
|
+
dropzone.addEventListener(eventName, (e) => {
|
|
1164
|
+
e.preventDefault();
|
|
1165
|
+
e.stopPropagation();
|
|
1166
|
+
});
|
|
1167
|
+
});
|
|
1168
|
+
dropzone.addEventListener("dragover", () => {
|
|
1169
|
+
if (!input.disabled)
|
|
1170
|
+
dropzone.classList.add(`${PREFIX}-file-upload__dropzone--drag-over`);
|
|
1171
|
+
});
|
|
1172
|
+
dropzone.addEventListener("dragleave", () => {
|
|
1173
|
+
dropzone.classList.remove(`${PREFIX}-file-upload__dropzone--drag-over`);
|
|
1174
|
+
});
|
|
1175
|
+
dropzone.addEventListener("drop", (e) => {
|
|
1176
|
+
dropzone.classList.remove(`${PREFIX}-file-upload__dropzone--drag-over`);
|
|
1177
|
+
if (input.disabled) return;
|
|
1178
|
+
const dt = e.dataTransfer;
|
|
1179
|
+
const files = dt.files;
|
|
1180
|
+
if (files.length > 0) {
|
|
1181
|
+
addFiles(files);
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
container.__inaFileUploadInitialized = true;
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// src/js/components/stateful/single-file-upload.js
|
|
1189
|
+
var ICONS2 = {
|
|
1190
|
+
upload: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" y1="2" x2="12" y2="16"></line></svg>`,
|
|
1191
|
+
trash: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>`,
|
|
1192
|
+
file: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>`,
|
|
1193
|
+
pdf: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><path d="M10 13a1 1 0 0 0-1 1v4"></path><path d="M10 13h1a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-1"></path><path d="M14 13h1a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-1v-2"></path><path d="M14 15h2"></path></svg>`,
|
|
1194
|
+
image: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>`
|
|
1195
|
+
};
|
|
1196
|
+
function initSingleFileUpload(rootSelector = `.${PREFIX}-single-file-upload`) {
|
|
1197
|
+
const fileUploads = document.querySelectorAll(rootSelector);
|
|
1198
|
+
fileUploads.forEach((container) => {
|
|
1199
|
+
if (container.__inaSingleFileUploadInitialized) return;
|
|
1200
|
+
const input = container.querySelector(
|
|
1201
|
+
`.${PREFIX}-single-file-upload__input`
|
|
1202
|
+
);
|
|
1203
|
+
const containerEl = container.querySelector(
|
|
1204
|
+
`.${PREFIX}-single-file-upload__container`
|
|
1205
|
+
);
|
|
1206
|
+
if (!input || !containerEl) return;
|
|
1207
|
+
const titleEl = container.querySelector(
|
|
1208
|
+
`.${PREFIX}-single-file-upload__title`
|
|
1209
|
+
);
|
|
1210
|
+
const descriptionEl = container.querySelector(
|
|
1211
|
+
`.${PREFIX}-single-file-upload__description`
|
|
1212
|
+
);
|
|
1213
|
+
const initialTitle = titleEl ? titleEl.textContent : "Unggah File";
|
|
1214
|
+
const initialDescription = descriptionEl ? descriptionEl.textContent : "";
|
|
1215
|
+
const maxSize = parseInt(container.getAttribute("data-max-size")) || 0;
|
|
1216
|
+
const allowedExtensions = (container.getAttribute("data-allowed-extensions") || "").split(",").filter(Boolean);
|
|
1217
|
+
let status = "idle";
|
|
1218
|
+
let currentFile = null;
|
|
1219
|
+
let progress = 0;
|
|
1220
|
+
const getFileIcon = (file) => {
|
|
1221
|
+
if (file.type.includes("pdf")) return ICONS2.pdf;
|
|
1222
|
+
if (file.type.includes("image")) return ICONS2.image;
|
|
1223
|
+
return ICONS2.file;
|
|
1224
|
+
};
|
|
1225
|
+
const formatFileSize = (bytes) => {
|
|
1226
|
+
return (bytes / (1024 * 1024)).toFixed(2) + " MB";
|
|
1227
|
+
};
|
|
1228
|
+
const updateUI = () => {
|
|
1229
|
+
containerEl.innerHTML = "";
|
|
1230
|
+
if (!currentFile && status === "idle") {
|
|
1231
|
+
containerEl.innerHTML = `
|
|
1232
|
+
<div class="${PREFIX}-single-file-upload__icon-wrapper ${PREFIX}-single-file-upload__icon-wrapper--default">
|
|
1233
|
+
${ICONS2.upload}
|
|
1234
|
+
</div>
|
|
1235
|
+
<div class="${PREFIX}-single-file-upload__content">
|
|
1236
|
+
<div class="${PREFIX}-single-file-upload__title">${initialTitle}</div>
|
|
1237
|
+
<div class="${PREFIX}-single-file-upload__description">${initialDescription}</div>
|
|
1238
|
+
</div>
|
|
1239
|
+
`;
|
|
1240
|
+
} else if (!currentFile && status === "uploading") {
|
|
1241
|
+
containerEl.innerHTML = `
|
|
1242
|
+
<div class="${PREFIX}-single-file-upload__icon-wrapper ${PREFIX}-single-file-upload__icon-wrapper--default">
|
|
1243
|
+
${ICONS2.upload}
|
|
1244
|
+
</div>
|
|
1245
|
+
<div class="${PREFIX}-single-file-upload__progress">
|
|
1246
|
+
<div class="${PREFIX}-single-file-upload__progress-bar">
|
|
1247
|
+
<div class="${PREFIX}-single-file-upload__progress-fill" style="width: ${progress}%"></div>
|
|
1248
|
+
</div>
|
|
1249
|
+
<div class="${PREFIX}-single-file-upload__progress-text">
|
|
1250
|
+
Uploading... ${progress}%
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
`;
|
|
1254
|
+
} else if (currentFile && status === "success") {
|
|
1255
|
+
containerEl.innerHTML = `
|
|
1256
|
+
<div class="${PREFIX}-single-file-upload__icon-wrapper ${PREFIX}-single-file-upload__icon-wrapper--file">
|
|
1257
|
+
${getFileIcon(currentFile)}
|
|
1258
|
+
</div>
|
|
1259
|
+
<div class="${PREFIX}-single-file-upload__content">
|
|
1260
|
+
<div class="${PREFIX}-single-file-upload__title">${currentFile.name}</div>
|
|
1261
|
+
<div class="${PREFIX}-single-file-upload__description">${initialDescription}</div>
|
|
1262
|
+
</div>
|
|
1263
|
+
<button type="button" class="${PREFIX}-single-file-upload__delete-button" aria-label="Remove file">
|
|
1264
|
+
${ICONS2.trash}
|
|
1265
|
+
</button>
|
|
1266
|
+
`;
|
|
1267
|
+
const deleteBtn = containerEl.querySelector(
|
|
1268
|
+
`.${PREFIX}-single-file-upload__delete-button`
|
|
1269
|
+
);
|
|
1270
|
+
if (deleteBtn) {
|
|
1271
|
+
deleteBtn.addEventListener("click", (e) => {
|
|
1272
|
+
e.stopPropagation();
|
|
1273
|
+
removeFile();
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
const handleFile = (file) => {
|
|
1279
|
+
if (maxSize > 0 && file.size > maxSize) {
|
|
1280
|
+
alert("File size exceeds limit.");
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
if (allowedExtensions.length > 0) {
|
|
1284
|
+
const ext = file.name.split(".").pop();
|
|
1285
|
+
if (!allowedExtensions.includes(ext)) {
|
|
1286
|
+
alert("Invalid file extension.");
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
currentFile = file;
|
|
1291
|
+
status = "uploading";
|
|
1292
|
+
progress = 0;
|
|
1293
|
+
updateUI();
|
|
1294
|
+
const interval = setInterval(() => {
|
|
1295
|
+
progress += 10;
|
|
1296
|
+
updateUI();
|
|
1297
|
+
if (progress >= 100) {
|
|
1298
|
+
clearInterval(interval);
|
|
1299
|
+
status = "success";
|
|
1300
|
+
updateUI();
|
|
1301
|
+
container.dispatchEvent(
|
|
1302
|
+
new CustomEvent("single-file-upload:change", {
|
|
1303
|
+
detail: { file },
|
|
1304
|
+
bubbles: true
|
|
1305
|
+
})
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
}, 100);
|
|
1309
|
+
};
|
|
1310
|
+
const removeFile = () => {
|
|
1311
|
+
currentFile = null;
|
|
1312
|
+
status = "idle";
|
|
1313
|
+
input.value = "";
|
|
1314
|
+
updateUI();
|
|
1315
|
+
container.dispatchEvent(
|
|
1316
|
+
new CustomEvent("single-file-upload:change", {
|
|
1317
|
+
detail: { file: null },
|
|
1318
|
+
bubbles: true
|
|
1319
|
+
})
|
|
1320
|
+
);
|
|
1321
|
+
};
|
|
1322
|
+
input.addEventListener("change", () => {
|
|
1323
|
+
if (input.files[0]) handleFile(input.files[0]);
|
|
1324
|
+
});
|
|
1325
|
+
containerEl.addEventListener("click", (e) => {
|
|
1326
|
+
if (status === "idle") input.click();
|
|
1327
|
+
});
|
|
1328
|
+
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
|
|
1329
|
+
containerEl.addEventListener(eventName, (e) => {
|
|
1330
|
+
e.preventDefault();
|
|
1331
|
+
e.stopPropagation();
|
|
1332
|
+
});
|
|
1333
|
+
});
|
|
1334
|
+
containerEl.addEventListener("dragover", () => {
|
|
1335
|
+
if (!currentFile && !input.disabled) {
|
|
1336
|
+
containerEl.classList.add(
|
|
1337
|
+
`${PREFIX}-single-file-upload__container--active`
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
containerEl.addEventListener("dragleave", () => {
|
|
1342
|
+
containerEl.classList.remove(
|
|
1343
|
+
`${PREFIX}-single-file-upload__container--active`
|
|
1344
|
+
);
|
|
1345
|
+
});
|
|
1346
|
+
containerEl.addEventListener("drop", (e) => {
|
|
1347
|
+
containerEl.classList.remove(
|
|
1348
|
+
`${PREFIX}-single-file-upload__container--active`
|
|
1349
|
+
);
|
|
1350
|
+
if (input.disabled || currentFile) return;
|
|
1351
|
+
const file = e.dataTransfer.files[0];
|
|
1352
|
+
if (file) handleFile(file);
|
|
1353
|
+
});
|
|
1354
|
+
updateUI();
|
|
1355
|
+
container.__inaSingleFileUploadInitialized = true;
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
|
|
979
1359
|
// src/js/components/stateful/file-upload-base.js
|
|
980
1360
|
function initFileUploadBase(rootSelector = `.${PREFIX}-file-base`) {
|
|
981
1361
|
document.querySelectorAll(rootSelector).forEach((fileUploadBase) => {
|
|
@@ -1731,6 +2111,8 @@ if (typeof window !== void 0) {
|
|
|
1731
2111
|
initCheckbox();
|
|
1732
2112
|
initDatepicker();
|
|
1733
2113
|
initDropdown();
|
|
2114
|
+
initFileUpload();
|
|
2115
|
+
initSingleFileUpload();
|
|
1734
2116
|
initFileUploadBase();
|
|
1735
2117
|
initFileUploadItem();
|
|
1736
2118
|
initImgCompare();
|
|
@@ -1751,6 +2133,8 @@ function initAll() {
|
|
|
1751
2133
|
initCheckbox();
|
|
1752
2134
|
initDatepicker();
|
|
1753
2135
|
initDropdown();
|
|
2136
|
+
initFileUpload();
|
|
2137
|
+
initSingleFileUpload();
|
|
1754
2138
|
initFileUploadBase();
|
|
1755
2139
|
initFileUploadItem();
|
|
1756
2140
|
initImgCompare();
|