@dmitryvim/form-builder 0.2.27 → 0.2.29
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/README.md +57 -1
- package/dist/browser/formbuilder.min.js +207 -125
- package/dist/browser/formbuilder.v0.2.29.min.js +956 -0
- package/dist/cjs/index.cjs +566 -142
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +552 -131
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +207 -125
- package/dist/types/components/file/constraints.d.ts +39 -3
- package/dist/types/components/file/dom.d.ts +2 -0
- package/dist/types/components/file/library.d.ts +49 -0
- package/dist/types/components/file/render-edit.d.ts +12 -9
- package/dist/types/components/file/upload.d.ts +15 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/types/config.d.ts +33 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/schema.d.ts +13 -6
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.27.min.js +0 -874
package/dist/cjs/index.cjs
CHANGED
|
@@ -2136,31 +2136,59 @@ function isFileExtensionAllowed(fileName, allowedExtensions) {
|
|
|
2136
2136
|
const ext = (_b = (_a = fileName.split(".").pop()) == null ? void 0 : _a.toLowerCase()) != null ? _b : "";
|
|
2137
2137
|
return allowedExtensions.includes(ext);
|
|
2138
2138
|
}
|
|
2139
|
-
function
|
|
2139
|
+
function isSizeWithinLimit(bytes, maxSizeMB) {
|
|
2140
2140
|
if (maxSizeMB === Infinity) return true;
|
|
2141
|
-
return
|
|
2141
|
+
return bytes <= maxSizeMB * 1024 * 1024;
|
|
2142
2142
|
}
|
|
2143
|
-
function
|
|
2143
|
+
function isFileSizeAllowed(file, maxSizeMB) {
|
|
2144
|
+
return isSizeWithinLimit(file.size, maxSizeMB);
|
|
2145
|
+
}
|
|
2146
|
+
function getAllowedMimes(accept) {
|
|
2147
|
+
if (!accept) return [];
|
|
2148
|
+
if (typeof accept === "string") return [];
|
|
2149
|
+
if (!Array.isArray(accept.mime)) return [];
|
|
2150
|
+
return accept.mime.map((m) => m.toLowerCase());
|
|
2151
|
+
}
|
|
2152
|
+
function isMimeAllowed(mimeType, allowedMimes) {
|
|
2153
|
+
if (allowedMimes.length === 0) return true;
|
|
2154
|
+
const normalizedType = mimeType.toLowerCase();
|
|
2155
|
+
return allowedMimes.some((allowed) => {
|
|
2156
|
+
if (allowed.endsWith("/*")) {
|
|
2157
|
+
const prefix = allowed.slice(0, -1);
|
|
2158
|
+
return normalizedType.startsWith(prefix);
|
|
2159
|
+
}
|
|
2160
|
+
return normalizedType === allowed;
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
function inferMimeFromResourceId(resourceId) {
|
|
2144
2164
|
var _a;
|
|
2165
|
+
const filename = resourceId.split("/").pop() || "file";
|
|
2166
|
+
const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
|
|
2167
|
+
if (extension) {
|
|
2168
|
+
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
|
|
2169
|
+
return `image/${extension === "jpg" ? "jpeg" : extension}`;
|
|
2170
|
+
}
|
|
2171
|
+
if (["mp4", "webm", "mov", "avi"].includes(extension)) {
|
|
2172
|
+
return `video/${extension === "mov" ? "quicktime" : extension}`;
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
return "application/octet-stream";
|
|
2176
|
+
}
|
|
2177
|
+
function seedInferredResource(resourceId, resourceIndex) {
|
|
2178
|
+
if (resourceIndex.has(resourceId)) return;
|
|
2179
|
+
const filename = resourceId.split("/").pop() || "file";
|
|
2180
|
+
resourceIndex.set(resourceId, {
|
|
2181
|
+
name: filename,
|
|
2182
|
+
type: inferMimeFromResourceId(resourceId),
|
|
2183
|
+
size: 0,
|
|
2184
|
+
uploadedAt: /* @__PURE__ */ new Date(),
|
|
2185
|
+
file: void 0,
|
|
2186
|
+
inferredFromExtension: true
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
function addPrefillFilesToIndex(initialFiles, resourceIndex) {
|
|
2145
2190
|
for (const resourceId of initialFiles) {
|
|
2146
|
-
|
|
2147
|
-
const filename = resourceId.split("/").pop() || "file";
|
|
2148
|
-
const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
|
|
2149
|
-
let fileType = "application/octet-stream";
|
|
2150
|
-
if (extension) {
|
|
2151
|
-
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
|
|
2152
|
-
fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
|
|
2153
|
-
} else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
|
|
2154
|
-
fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
resourceIndex.set(resourceId, {
|
|
2158
|
-
name: filename,
|
|
2159
|
-
type: fileType,
|
|
2160
|
-
size: 0,
|
|
2161
|
-
uploadedAt: /* @__PURE__ */ new Date(),
|
|
2162
|
-
file: void 0
|
|
2163
|
-
});
|
|
2191
|
+
seedInferredResource(resourceId, resourceIndex);
|
|
2164
2192
|
}
|
|
2165
2193
|
}
|
|
2166
2194
|
|
|
@@ -2437,6 +2465,84 @@ function ensureFileStyles() {
|
|
|
2437
2465
|
z-index: 10000;
|
|
2438
2466
|
}
|
|
2439
2467
|
|
|
2468
|
+
/* Two-card empty-state layout (upload card + library card) */
|
|
2469
|
+
.fb-file-card-row {
|
|
2470
|
+
display: flex;
|
|
2471
|
+
gap: 8px;
|
|
2472
|
+
align-items: stretch;
|
|
2473
|
+
}
|
|
2474
|
+
.fb-file-card-row .fb-file-dropzone,
|
|
2475
|
+
.fb-file-card-row .fb-file-library-card {
|
|
2476
|
+
flex: 1;
|
|
2477
|
+
min-width: 0;
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
/* Library picker card \u2014 mirrors .fb-file-dropzone styling */
|
|
2481
|
+
.fb-file-library-card {
|
|
2482
|
+
height: 128px;
|
|
2483
|
+
border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
|
|
2484
|
+
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2485
|
+
display: flex;
|
|
2486
|
+
flex-direction: column;
|
|
2487
|
+
align-items: center;
|
|
2488
|
+
justify-content: center;
|
|
2489
|
+
gap: 4px;
|
|
2490
|
+
cursor: pointer;
|
|
2491
|
+
background: none;
|
|
2492
|
+
padding: 0;
|
|
2493
|
+
transition:
|
|
2494
|
+
border-color var(--fb-transition-duration, 200ms),
|
|
2495
|
+
background var(--fb-transition-duration, 200ms);
|
|
2496
|
+
width: 100%;
|
|
2497
|
+
}
|
|
2498
|
+
.fb-file-library-card:hover,
|
|
2499
|
+
.fb-file-library-card:focus-visible {
|
|
2500
|
+
border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
|
|
2501
|
+
background: var(--fb-background-hover-color, #f9fafb);
|
|
2502
|
+
outline: none;
|
|
2503
|
+
}
|
|
2504
|
+
.fb-file-library-card-icon {
|
|
2505
|
+
font-size: 24px;
|
|
2506
|
+
line-height: 1;
|
|
2507
|
+
flex-shrink: 0;
|
|
2508
|
+
}
|
|
2509
|
+
.fb-file-library-card-label {
|
|
2510
|
+
font-size: 13px;
|
|
2511
|
+
color: var(--fb-text-secondary-color, #6b7280);
|
|
2512
|
+
}
|
|
2513
|
+
.fb-file-library-card-hint {
|
|
2514
|
+
font-size: 11px;
|
|
2515
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
/* Library "\u{1F4DA}" add-tile \u2014 same size/style as the "+" add tile */
|
|
2519
|
+
.fb-tile-add-library {
|
|
2520
|
+
border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
|
|
2521
|
+
display: flex;
|
|
2522
|
+
align-items: center;
|
|
2523
|
+
justify-content: center;
|
|
2524
|
+
cursor: pointer;
|
|
2525
|
+
font-size: 24px;
|
|
2526
|
+
color: var(--fb-file-upload-text-color, #9ca3af);
|
|
2527
|
+
transition:
|
|
2528
|
+
border-color var(--fb-transition-duration, 200ms),
|
|
2529
|
+
color var(--fb-transition-duration, 200ms);
|
|
2530
|
+
background: none;
|
|
2531
|
+
padding: 0;
|
|
2532
|
+
width: var(--fb-tile-size, 160px);
|
|
2533
|
+
height: var(--fb-tile-size, 160px);
|
|
2534
|
+
flex-shrink: 0;
|
|
2535
|
+
position: relative;
|
|
2536
|
+
overflow: hidden;
|
|
2537
|
+
border-radius: var(--fb-border-radius, 0.5rem);
|
|
2538
|
+
}
|
|
2539
|
+
.fb-tile-add-library:hover,
|
|
2540
|
+
.fb-tile-add-library:focus-visible {
|
|
2541
|
+
border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
|
|
2542
|
+
color: var(--fb-text-color, #1f2937);
|
|
2543
|
+
outline: none;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2440
2546
|
/* Hover zoom preview popup for image tiles \u2014 appended to document.body (fixed) */
|
|
2441
2547
|
.fb-tile-zoom-preview {
|
|
2442
2548
|
position: fixed;
|
|
@@ -2558,23 +2664,35 @@ function setEmptyFileContainer(fileContainer, state, hint) {
|
|
|
2558
2664
|
</div>
|
|
2559
2665
|
`;
|
|
2560
2666
|
}
|
|
2667
|
+
var dragDropHandlers = /* @__PURE__ */ new WeakMap();
|
|
2561
2668
|
function setupDragAndDrop(element, dropHandler) {
|
|
2562
|
-
|
|
2669
|
+
const prev = dragDropHandlers.get(element);
|
|
2670
|
+
if (prev) {
|
|
2671
|
+
element.removeEventListener("dragover", prev.dragover);
|
|
2672
|
+
element.removeEventListener("dragleave", prev.dragleave);
|
|
2673
|
+
element.removeEventListener("drop", prev.drop);
|
|
2674
|
+
}
|
|
2675
|
+
const dragover = (e) => {
|
|
2563
2676
|
e.preventDefault();
|
|
2564
2677
|
element.classList.add("border-blue-500", "bg-blue-50");
|
|
2565
|
-
}
|
|
2566
|
-
|
|
2678
|
+
};
|
|
2679
|
+
const dragleave = (e) => {
|
|
2567
2680
|
e.preventDefault();
|
|
2568
2681
|
element.classList.remove("border-blue-500", "bg-blue-50");
|
|
2569
|
-
}
|
|
2570
|
-
|
|
2682
|
+
};
|
|
2683
|
+
const drop = (e) => {
|
|
2571
2684
|
var _a;
|
|
2572
2685
|
e.preventDefault();
|
|
2573
2686
|
element.classList.remove("border-blue-500", "bg-blue-50");
|
|
2574
|
-
|
|
2575
|
-
|
|
2687
|
+
const files = (_a = e.dataTransfer) == null ? void 0 : _a.files;
|
|
2688
|
+
if (files) {
|
|
2689
|
+
dropHandler(files);
|
|
2576
2690
|
}
|
|
2577
|
-
}
|
|
2691
|
+
};
|
|
2692
|
+
element.addEventListener("dragover", dragover);
|
|
2693
|
+
element.addEventListener("dragleave", dragleave);
|
|
2694
|
+
element.addEventListener("drop", drop);
|
|
2695
|
+
dragDropHandlers.set(element, { dragover, dragleave, drop });
|
|
2578
2696
|
}
|
|
2579
2697
|
|
|
2580
2698
|
// src/components/file/preview.ts
|
|
@@ -3271,8 +3389,19 @@ async function uploadSingleFile(file, state) {
|
|
|
3271
3389
|
throw new Error(`File upload failed: ${err.message}`);
|
|
3272
3390
|
}
|
|
3273
3391
|
}
|
|
3274
|
-
async function handleFileSelect(
|
|
3392
|
+
async function handleFileSelect(opts) {
|
|
3275
3393
|
var _a, _b;
|
|
3394
|
+
const {
|
|
3395
|
+
file,
|
|
3396
|
+
container,
|
|
3397
|
+
fieldName,
|
|
3398
|
+
state,
|
|
3399
|
+
deps = null,
|
|
3400
|
+
instance = null,
|
|
3401
|
+
allowedExtensions = [],
|
|
3402
|
+
allowedMimes = [],
|
|
3403
|
+
maxSizeMB = Infinity
|
|
3404
|
+
} = opts;
|
|
3276
3405
|
if (!isFileExtensionAllowed(file.name, allowedExtensions)) {
|
|
3277
3406
|
const formats = allowedExtensions.join(", ");
|
|
3278
3407
|
showFileError(
|
|
@@ -3281,6 +3410,14 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
|
|
|
3281
3410
|
);
|
|
3282
3411
|
return;
|
|
3283
3412
|
}
|
|
3413
|
+
if (!isMimeAllowed(file.type, allowedMimes)) {
|
|
3414
|
+
const mimes = allowedMimes.join(", ");
|
|
3415
|
+
showFileError(
|
|
3416
|
+
container,
|
|
3417
|
+
t("invalidFileMime", state, { name: file.name, type: file.type, mimes })
|
|
3418
|
+
);
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3284
3421
|
if (!isFileSizeAllowed(file, maxSizeMB)) {
|
|
3285
3422
|
showFileError(
|
|
3286
3423
|
container,
|
|
@@ -3340,10 +3477,16 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
|
3340
3477
|
const afterExt = allFiles.filter(
|
|
3341
3478
|
(f) => isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
3342
3479
|
);
|
|
3343
|
-
const
|
|
3480
|
+
const rejectedByMime = afterExt.filter(
|
|
3481
|
+
(f) => !isMimeAllowed(f.type, constraints.allowedMimes)
|
|
3482
|
+
);
|
|
3483
|
+
const afterMime = afterExt.filter(
|
|
3484
|
+
(f) => isMimeAllowed(f.type, constraints.allowedMimes)
|
|
3485
|
+
);
|
|
3486
|
+
const rejectedBySize = afterMime.filter(
|
|
3344
3487
|
(f) => !isFileSizeAllowed(f, constraints.maxSize)
|
|
3345
3488
|
);
|
|
3346
|
-
const valid =
|
|
3489
|
+
const valid = afterMime.filter((f) => isFileSizeAllowed(f, constraints.maxSize));
|
|
3347
3490
|
const remaining = constraints.maxCount === Infinity ? valid.length : Math.max(0, constraints.maxCount - currentCount);
|
|
3348
3491
|
const accepted = valid.slice(0, remaining);
|
|
3349
3492
|
const skippedByCount = valid.length - accepted.length;
|
|
@@ -3353,6 +3496,11 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
|
3353
3496
|
const names = rejectedByExt.map((f) => f.name).join(", ");
|
|
3354
3497
|
errorParts.push(t("invalidFileExtension", state, { name: names, formats }));
|
|
3355
3498
|
}
|
|
3499
|
+
if (rejectedByMime.length > 0) {
|
|
3500
|
+
const mimes = constraints.allowedMimes.join(", ");
|
|
3501
|
+
const names = rejectedByMime.map((f) => f.name).join(", ");
|
|
3502
|
+
errorParts.push(t("invalidFileMime", state, { name: names, type: rejectedByMime.map((f) => f.type).join(", "), mimes }));
|
|
3503
|
+
}
|
|
3356
3504
|
if (rejectedBySize.length > 0) {
|
|
3357
3505
|
const names = rejectedBySize.map((f) => f.name).join(", ");
|
|
3358
3506
|
errorParts.push(
|
|
@@ -3445,30 +3593,166 @@ function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback
|
|
|
3445
3593
|
};
|
|
3446
3594
|
}
|
|
3447
3595
|
|
|
3448
|
-
// src/components/file/
|
|
3449
|
-
function
|
|
3596
|
+
// src/components/file/library.ts
|
|
3597
|
+
function buildAcceptContext(element) {
|
|
3450
3598
|
var _a, _b;
|
|
3451
|
-
if (!
|
|
3452
|
-
|
|
3453
|
-
const
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3599
|
+
if (!element.accept) return void 0;
|
|
3600
|
+
if (typeof element.accept === "string") {
|
|
3601
|
+
const exts2 = getAllowedExtensions(element.accept);
|
|
3602
|
+
return exts2.length > 0 ? { extensions: exts2 } : void 0;
|
|
3603
|
+
}
|
|
3604
|
+
const exts = (_a = element.accept.extensions) != null ? _a : [];
|
|
3605
|
+
const mime = (_b = element.accept.mime) != null ? _b : [];
|
|
3606
|
+
const normalizedExts = exts.map((e) => e.toLowerCase());
|
|
3607
|
+
if (normalizedExts.length === 0 && mime.length === 0) return void 0;
|
|
3608
|
+
const result = {};
|
|
3609
|
+
if (normalizedExts.length > 0) result.extensions = normalizedExts;
|
|
3610
|
+
if (mime.length > 0) result.mime = mime;
|
|
3611
|
+
return result;
|
|
3612
|
+
}
|
|
3613
|
+
function validatePickedResource(resource, allowedExtensions, allowedMimes, maxSizeMB, state) {
|
|
3614
|
+
if (!isFileExtensionAllowed(resource.name, allowedExtensions)) {
|
|
3615
|
+
const formats = allowedExtensions.join(", ");
|
|
3616
|
+
return t("invalidFileExtension", state, { name: resource.name, formats });
|
|
3617
|
+
}
|
|
3618
|
+
if (!isMimeAllowed(resource.type, allowedMimes)) {
|
|
3619
|
+
const mimes = allowedMimes.join(", ");
|
|
3620
|
+
return t("invalidFileMime", state, { name: resource.name, type: resource.type, mimes });
|
|
3621
|
+
}
|
|
3622
|
+
if (!isSizeWithinLimit(resource.size, maxSizeMB)) {
|
|
3623
|
+
return t("fileTooLarge", state, { name: resource.name, maxSize: maxSizeMB });
|
|
3624
|
+
}
|
|
3625
|
+
return null;
|
|
3626
|
+
}
|
|
3627
|
+
function readCurrentResourceIds(wrapper) {
|
|
3628
|
+
const raw = wrapper.dataset.resourceIds;
|
|
3629
|
+
if (!raw) return [];
|
|
3630
|
+
try {
|
|
3631
|
+
const parsed = JSON.parse(raw);
|
|
3632
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
3633
|
+
} catch {
|
|
3634
|
+
return [];
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
function registerPickedResource(resource, state) {
|
|
3638
|
+
var _a;
|
|
3639
|
+
const existing = state.resourceIndex.get(resource.resourceId);
|
|
3640
|
+
state.resourceIndex.set(resource.resourceId, {
|
|
3641
|
+
name: resource.name,
|
|
3642
|
+
type: resource.type,
|
|
3643
|
+
size: resource.size,
|
|
3644
|
+
uploadedAt: (_a = existing == null ? void 0 : existing.uploadedAt) != null ? _a : /* @__PURE__ */ new Date(),
|
|
3645
|
+
file: existing == null ? void 0 : existing.file
|
|
3646
|
+
});
|
|
3647
|
+
}
|
|
3648
|
+
function extractPickerError(error, state) {
|
|
3649
|
+
if (error instanceof Error && error.message) return error.message;
|
|
3650
|
+
return t("pickerError", state);
|
|
3651
|
+
}
|
|
3652
|
+
async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resourceIds, maxCount, updateCallback, instance) {
|
|
3653
|
+
var _a;
|
|
3654
|
+
if (!state.config.pickExistingFiles) return;
|
|
3655
|
+
const allowedExtensions = getAllowedExtensions(element.accept);
|
|
3656
|
+
const allowedMimes = getAllowedMimes(element.accept);
|
|
3657
|
+
const maxSizeMB = (_a = element.maxSize) != null ? _a : Infinity;
|
|
3658
|
+
const currentIds = readCurrentResourceIds(wrapper);
|
|
3659
|
+
const remaining = maxCount === Infinity ? Infinity : Math.max(0, maxCount - currentIds.length);
|
|
3660
|
+
let picked;
|
|
3661
|
+
try {
|
|
3662
|
+
picked = await state.config.pickExistingFiles({
|
|
3663
|
+
fieldPath,
|
|
3664
|
+
mode: "multiple",
|
|
3665
|
+
accept: buildAcceptContext(element),
|
|
3666
|
+
maxSizeMB: maxSizeMB === Infinity ? void 0 : maxSizeMB,
|
|
3667
|
+
remainingSlots: remaining === Infinity ? void 0 : remaining,
|
|
3668
|
+
selectedResourceIds: [...currentIds]
|
|
3468
3669
|
});
|
|
3670
|
+
} catch (error) {
|
|
3671
|
+
showFileError(wrapper, extractPickerError(error, state));
|
|
3672
|
+
return;
|
|
3673
|
+
}
|
|
3674
|
+
if (picked.length === 0) return;
|
|
3675
|
+
const existingSet = new Set(currentIds);
|
|
3676
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3677
|
+
const deduped = picked.filter((r) => {
|
|
3678
|
+
if (existingSet.has(r.resourceId)) return false;
|
|
3679
|
+
if (seen.has(r.resourceId)) return false;
|
|
3680
|
+
seen.add(r.resourceId);
|
|
3681
|
+
return true;
|
|
3682
|
+
});
|
|
3683
|
+
const validItems = deduped.filter((r) => {
|
|
3684
|
+
const err = validatePickedResource(r, allowedExtensions, allowedMimes, maxSizeMB, state);
|
|
3685
|
+
return err === null;
|
|
3686
|
+
});
|
|
3687
|
+
const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - resourceIds.length);
|
|
3688
|
+
const accepted = validItems.slice(0, freshRemaining);
|
|
3689
|
+
const skipped = validItems.length - accepted.length;
|
|
3690
|
+
if (accepted.length === 0) return;
|
|
3691
|
+
clearFileError(wrapper);
|
|
3692
|
+
if (skipped > 0) {
|
|
3693
|
+
showFileError(
|
|
3694
|
+
wrapper,
|
|
3695
|
+
t("filesLimitExceeded", state, { skipped, max: maxCount })
|
|
3696
|
+
);
|
|
3469
3697
|
}
|
|
3698
|
+
for (const resource of accepted) {
|
|
3699
|
+
registerPickedResource(resource, state);
|
|
3700
|
+
resourceIds.push(resource.resourceId);
|
|
3701
|
+
}
|
|
3702
|
+
wrapper.dataset.resourceIds = JSON.stringify(resourceIds);
|
|
3703
|
+
updateCallback();
|
|
3704
|
+
if (!state.config.readonly) {
|
|
3705
|
+
instance.triggerOnChange(fieldPath, resourceIds);
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
async function handleLibraryPickSingle(state, element, container, fileWrapper, pathKey, fieldPath, renderCallback, instance) {
|
|
3709
|
+
var _a;
|
|
3710
|
+
if (!state.config.pickExistingFiles) return;
|
|
3711
|
+
const allowedExtensions = getAllowedExtensions(element.accept);
|
|
3712
|
+
const allowedMimes = getAllowedMimes(element.accept);
|
|
3713
|
+
const maxSizeMB = (_a = element.maxSize) != null ? _a : Infinity;
|
|
3714
|
+
let picked;
|
|
3715
|
+
try {
|
|
3716
|
+
picked = await state.config.pickExistingFiles({
|
|
3717
|
+
fieldPath,
|
|
3718
|
+
mode: "single",
|
|
3719
|
+
accept: buildAcceptContext(element),
|
|
3720
|
+
maxSizeMB: maxSizeMB === Infinity ? void 0 : maxSizeMB,
|
|
3721
|
+
selectedResourceIds: []
|
|
3722
|
+
});
|
|
3723
|
+
} catch (error) {
|
|
3724
|
+
showFileError(container, extractPickerError(error, state));
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
if (picked.length === 0) return;
|
|
3728
|
+
const first = picked[0];
|
|
3729
|
+
const validationError = validatePickedResource(first, allowedExtensions, allowedMimes, maxSizeMB, state);
|
|
3730
|
+
if (validationError !== null) {
|
|
3731
|
+
showFileError(container, validationError);
|
|
3732
|
+
return;
|
|
3733
|
+
}
|
|
3734
|
+
clearFileError(container);
|
|
3735
|
+
registerPickedResource(first, state);
|
|
3736
|
+
let hiddenInput = fileWrapper.querySelector('input[type="hidden"]');
|
|
3737
|
+
if (!hiddenInput) {
|
|
3738
|
+
hiddenInput = document.createElement("input");
|
|
3739
|
+
hiddenInput.type = "hidden";
|
|
3740
|
+
hiddenInput.name = pathKey;
|
|
3741
|
+
fileWrapper.appendChild(hiddenInput);
|
|
3742
|
+
}
|
|
3743
|
+
hiddenInput.value = first.resourceId;
|
|
3744
|
+
await renderCallback(first.resourceId);
|
|
3745
|
+
if (!state.config.readonly) {
|
|
3746
|
+
instance.triggerOnChange(fieldPath, first.resourceId);
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3750
|
+
// src/components/file/render-edit.ts
|
|
3751
|
+
function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps) {
|
|
3752
|
+
var _a;
|
|
3753
|
+
seedInferredResource(initial, state.resourceIndex);
|
|
3470
3754
|
const meta = state.resourceIndex.get(initial);
|
|
3471
|
-
const isVideo = (
|
|
3755
|
+
const isVideo = (_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/");
|
|
3472
3756
|
if (isVideo) {
|
|
3473
3757
|
renderFilePreview(fileContainer, initial, state, {
|
|
3474
3758
|
fileName: initial,
|
|
@@ -3484,8 +3768,51 @@ function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, sta
|
|
|
3484
3768
|
hiddenInput.value = initial;
|
|
3485
3769
|
fileWrapper.appendChild(hiddenInput);
|
|
3486
3770
|
}
|
|
3487
|
-
|
|
3771
|
+
var UPLOAD_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" style="flex-shrink:0;color:var(--fb-file-upload-text-color,#9ca3af);">
|
|
3772
|
+
<path d="M19.35 10.04A7.49 7.49 0 0012 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 000 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"/>
|
|
3773
|
+
</svg>`;
|
|
3774
|
+
function buildEmptyDropzone(state, primaryText, subHint, openPicker) {
|
|
3775
|
+
const dropzone = document.createElement("div");
|
|
3776
|
+
dropzone.className = "fb-file-dropzone";
|
|
3777
|
+
dropzone.innerHTML = `
|
|
3778
|
+
${UPLOAD_SVG}
|
|
3779
|
+
<div class="fb-dropzone-primary-text">${escapeHtml(primaryText)}</div>
|
|
3780
|
+
${subHint ? `<div class="fb-dropzone-hint-text">${escapeHtml(subHint)}</div>` : ""}
|
|
3781
|
+
`;
|
|
3782
|
+
dropzone.onclick = openPicker;
|
|
3783
|
+
return dropzone;
|
|
3784
|
+
}
|
|
3785
|
+
function buildLibraryButton(variant, state, onClick) {
|
|
3786
|
+
const btn = document.createElement("button");
|
|
3787
|
+
btn.type = "button";
|
|
3788
|
+
btn.className = variant === "card" ? "fb-file-library-card" : "fb-tile fb-tile-add-library";
|
|
3789
|
+
if (variant === "card") {
|
|
3790
|
+
btn.innerHTML = `
|
|
3791
|
+
<span class="fb-file-library-card-icon" aria-hidden="true">\u{1F4DA}</span>
|
|
3792
|
+
<span class="fb-file-library-card-label">${escapeHtml(t("fromLibrary", state))}</span>
|
|
3793
|
+
<span class="fb-file-library-card-hint">${escapeHtml(t("libraryHint", state))}</span>
|
|
3794
|
+
`;
|
|
3795
|
+
} else {
|
|
3796
|
+
btn.innerHTML = `<span aria-hidden="true">\u{1F4DA}</span>`;
|
|
3797
|
+
btn.title = t("fromLibrary", state);
|
|
3798
|
+
btn.setAttribute("aria-label", t("fromLibrary", state));
|
|
3799
|
+
}
|
|
3800
|
+
btn.addEventListener("click", onClick);
|
|
3801
|
+
return btn;
|
|
3802
|
+
}
|
|
3803
|
+
function renderResourcePills(opts) {
|
|
3488
3804
|
var _a;
|
|
3805
|
+
const {
|
|
3806
|
+
container,
|
|
3807
|
+
rids,
|
|
3808
|
+
state,
|
|
3809
|
+
onRemove,
|
|
3810
|
+
hint,
|
|
3811
|
+
countInfo,
|
|
3812
|
+
maxCount,
|
|
3813
|
+
isReadonly = false,
|
|
3814
|
+
onLibraryPick
|
|
3815
|
+
} = opts;
|
|
3489
3816
|
ensureFileStyles();
|
|
3490
3817
|
const wrapper = container.closest("[data-files-wrapper]");
|
|
3491
3818
|
if (wrapper) {
|
|
@@ -3494,6 +3821,7 @@ function renderResourcePills(container, rids, state, onRemove, hint, countInfo,
|
|
|
3494
3821
|
while (container.firstChild) container.removeChild(container.firstChild);
|
|
3495
3822
|
const ridList = rids != null ? rids : [];
|
|
3496
3823
|
const atMax = maxCount !== void 0 && ridList.length >= maxCount;
|
|
3824
|
+
const hasLibrary = !isReadonly && typeof onLibraryPick === "function";
|
|
3497
3825
|
const buildSubHint = () => {
|
|
3498
3826
|
const parts = [];
|
|
3499
3827
|
if (hint) parts.push(hint);
|
|
@@ -3510,18 +3838,26 @@ function renderResourcePills(container, rids, state, onRemove, hint, countInfo,
|
|
|
3510
3838
|
emptyEl.className = "fb-tile-empty-text";
|
|
3511
3839
|
emptyEl.textContent = t("noFilesSelected", state);
|
|
3512
3840
|
container.appendChild(emptyEl);
|
|
3841
|
+
} else if (hasLibrary) {
|
|
3842
|
+
const row = document.createElement("div");
|
|
3843
|
+
row.className = "fb-file-card-row";
|
|
3844
|
+
const dropzone = buildEmptyDropzone(
|
|
3845
|
+
state,
|
|
3846
|
+
t("clickDragTextMultiple", state),
|
|
3847
|
+
buildSubHint(),
|
|
3848
|
+
openPicker
|
|
3849
|
+
);
|
|
3850
|
+
const libraryBtn = buildLibraryButton("card", state, onLibraryPick);
|
|
3851
|
+
row.appendChild(dropzone);
|
|
3852
|
+
row.appendChild(libraryBtn);
|
|
3853
|
+
container.appendChild(row);
|
|
3513
3854
|
} else {
|
|
3514
|
-
const dropzone =
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
</svg>
|
|
3521
|
-
<div class="fb-dropzone-primary-text">${escapeHtml(t("clickDragTextMultiple", state))}</div>
|
|
3522
|
-
${subHint2 ? `<div class="fb-dropzone-hint-text">${escapeHtml(subHint2)}</div>` : ""}
|
|
3523
|
-
`;
|
|
3524
|
-
dropzone.onclick = openPicker;
|
|
3855
|
+
const dropzone = buildEmptyDropzone(
|
|
3856
|
+
state,
|
|
3857
|
+
t("clickDragTextMultiple", state),
|
|
3858
|
+
buildSubHint(),
|
|
3859
|
+
openPicker
|
|
3860
|
+
);
|
|
3525
3861
|
container.appendChild(dropzone);
|
|
3526
3862
|
}
|
|
3527
3863
|
return;
|
|
@@ -3552,6 +3888,10 @@ function renderResourcePills(container, rids, state, onRemove, hint, countInfo,
|
|
|
3552
3888
|
addTile.innerHTML = "+";
|
|
3553
3889
|
addTile.onclick = openPicker;
|
|
3554
3890
|
tilesWrap.appendChild(addTile);
|
|
3891
|
+
if (hasLibrary) {
|
|
3892
|
+
const libraryTile = buildLibraryButton("tile", state, onLibraryPick);
|
|
3893
|
+
tilesWrap.appendChild(libraryTile);
|
|
3894
|
+
}
|
|
3555
3895
|
} else if (!isReadonly && atMax) {
|
|
3556
3896
|
const chip = document.createElement("div");
|
|
3557
3897
|
chip.className = "fb-tile-counter";
|
|
@@ -3571,7 +3911,7 @@ function renderResourcePills(container, rids, state, onRemove, hint, countInfo,
|
|
|
3571
3911
|
}
|
|
3572
3912
|
}
|
|
3573
3913
|
function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
3574
|
-
var _a, _b, _c;
|
|
3914
|
+
var _a, _b, _c, _d, _e;
|
|
3575
3915
|
const state = ctx.state;
|
|
3576
3916
|
const fileWrapper = document.createElement("div");
|
|
3577
3917
|
fileWrapper.className = "space-y-2";
|
|
@@ -3580,29 +3920,34 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3580
3920
|
picker.name = pathKey;
|
|
3581
3921
|
picker.style.display = "none";
|
|
3582
3922
|
if (element.accept) {
|
|
3583
|
-
picker.accept = typeof element.accept === "string" ? element.accept :
|
|
3923
|
+
picker.accept = typeof element.accept === "string" ? element.accept : [
|
|
3924
|
+
...(_b = (_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`)) != null ? _b : [],
|
|
3925
|
+
...(_c = element.accept.mime) != null ? _c : []
|
|
3926
|
+
].join(",") || "";
|
|
3584
3927
|
}
|
|
3585
3928
|
const fileContainer = document.createElement("div");
|
|
3586
3929
|
fileContainer.className = "file-preview-container";
|
|
3587
3930
|
const initial = ctx.prefill[element.key];
|
|
3588
3931
|
const allowedExts = getAllowedExtensions(element.accept);
|
|
3589
|
-
const
|
|
3932
|
+
const allowedMimes = getAllowedMimes(element.accept);
|
|
3933
|
+
const maxSizeMB = (_d = element.maxSize) != null ? _d : Infinity;
|
|
3590
3934
|
const handlers = {
|
|
3591
3935
|
fileUploadHandler() {
|
|
3592
3936
|
picker.click();
|
|
3593
3937
|
},
|
|
3594
3938
|
dragHandler(files) {
|
|
3595
3939
|
if (files.length > 0) {
|
|
3596
|
-
handleFileSelect(
|
|
3597
|
-
files[0],
|
|
3598
|
-
fileContainer,
|
|
3599
|
-
pathKey,
|
|
3940
|
+
handleFileSelect({
|
|
3941
|
+
file: files[0],
|
|
3942
|
+
container: fileContainer,
|
|
3943
|
+
fieldName: pathKey,
|
|
3600
3944
|
state,
|
|
3601
|
-
buildDeps(),
|
|
3602
|
-
ctx.instance,
|
|
3603
|
-
allowedExts,
|
|
3945
|
+
deps: buildDeps(),
|
|
3946
|
+
instance: ctx.instance,
|
|
3947
|
+
allowedExtensions: allowedExts,
|
|
3948
|
+
allowedMimes,
|
|
3604
3949
|
maxSizeMB
|
|
3605
|
-
);
|
|
3950
|
+
});
|
|
3606
3951
|
}
|
|
3607
3952
|
},
|
|
3608
3953
|
setupDrop(container) {
|
|
@@ -3624,7 +3969,7 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3624
3969
|
releaseLocalFileUrl((_a2 = state.resourceIndex.get(currentRid)) == null ? void 0 : _a2.file);
|
|
3625
3970
|
}
|
|
3626
3971
|
if (hiddenInput) hiddenInput.value = "";
|
|
3627
|
-
|
|
3972
|
+
renderEmptySingleState();
|
|
3628
3973
|
}
|
|
3629
3974
|
};
|
|
3630
3975
|
const buildDeps = () => ({
|
|
@@ -3634,6 +3979,50 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3634
3979
|
setupDrop: handlers.setupDrop,
|
|
3635
3980
|
onRemove: handlers.onRemove
|
|
3636
3981
|
});
|
|
3982
|
+
const renderEmptySingleState = () => {
|
|
3983
|
+
if (state.config.pickExistingFiles && !element.disableLibrary) {
|
|
3984
|
+
fileContainer.className = "file-preview-container";
|
|
3985
|
+
fileContainer.removeAttribute("style");
|
|
3986
|
+
fileContainer.onclick = null;
|
|
3987
|
+
while (fileContainer.firstChild) {
|
|
3988
|
+
fileContainer.removeChild(fileContainer.firstChild);
|
|
3989
|
+
}
|
|
3990
|
+
const row = document.createElement("div");
|
|
3991
|
+
row.className = "fb-file-card-row";
|
|
3992
|
+
row.style.cssText = "display:flex;gap:8px;align-items:stretch;";
|
|
3993
|
+
const hint = makeFieldHint(element, state);
|
|
3994
|
+
const uploadCard = buildEmptyDropzone(
|
|
3995
|
+
state,
|
|
3996
|
+
t("clickDragText", state),
|
|
3997
|
+
hint,
|
|
3998
|
+
handlers.fileUploadHandler
|
|
3999
|
+
);
|
|
4000
|
+
uploadCard.style.cssText = "flex:1;min-width:0;height:128px;";
|
|
4001
|
+
setupDragAndDrop(uploadCard, handlers.dragHandler);
|
|
4002
|
+
const libraryBtn = buildLibraryButton("card", state, () => {
|
|
4003
|
+
handleLibraryPickSingle(
|
|
4004
|
+
state,
|
|
4005
|
+
element,
|
|
4006
|
+
fileContainer,
|
|
4007
|
+
fileWrapper,
|
|
4008
|
+
pathKey,
|
|
4009
|
+
pathKey,
|
|
4010
|
+
async (rid) => {
|
|
4011
|
+
await renderSingleFileEditTile(fileContainer, rid, state, buildDeps());
|
|
4012
|
+
},
|
|
4013
|
+
ctx.instance
|
|
4014
|
+
).catch((err) => {
|
|
4015
|
+
console.error("Library pick failed:", err);
|
|
4016
|
+
});
|
|
4017
|
+
});
|
|
4018
|
+
libraryBtn.style.cssText = "flex:1;min-width:0;";
|
|
4019
|
+
row.appendChild(uploadCard);
|
|
4020
|
+
row.appendChild(libraryBtn);
|
|
4021
|
+
fileContainer.appendChild(row);
|
|
4022
|
+
} else {
|
|
4023
|
+
handlers.restoreDropzone();
|
|
4024
|
+
}
|
|
4025
|
+
};
|
|
3637
4026
|
if (initial) {
|
|
3638
4027
|
handleInitialFileData(
|
|
3639
4028
|
initial,
|
|
@@ -3644,25 +4033,26 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3644
4033
|
buildDeps()
|
|
3645
4034
|
);
|
|
3646
4035
|
const prefillMeta = state.resourceIndex.get(initial);
|
|
3647
|
-
if ((
|
|
4036
|
+
if ((_e = prefillMeta == null ? void 0 : prefillMeta.type) == null ? void 0 : _e.startsWith("video/")) {
|
|
3648
4037
|
fileContainer.onclick = handlers.fileUploadHandler;
|
|
3649
4038
|
setupDragAndDrop(fileContainer, handlers.dragHandler);
|
|
3650
4039
|
}
|
|
3651
4040
|
} else {
|
|
3652
|
-
|
|
4041
|
+
renderEmptySingleState();
|
|
3653
4042
|
}
|
|
3654
4043
|
picker.onchange = () => {
|
|
3655
4044
|
if (picker.files && picker.files.length > 0) {
|
|
3656
|
-
handleFileSelect(
|
|
3657
|
-
picker.files[0],
|
|
3658
|
-
fileContainer,
|
|
3659
|
-
pathKey,
|
|
4045
|
+
handleFileSelect({
|
|
4046
|
+
file: picker.files[0],
|
|
4047
|
+
container: fileContainer,
|
|
4048
|
+
fieldName: pathKey,
|
|
3660
4049
|
state,
|
|
3661
|
-
buildDeps(),
|
|
3662
|
-
ctx.instance,
|
|
3663
|
-
allowedExts,
|
|
4050
|
+
deps: buildDeps(),
|
|
4051
|
+
instance: ctx.instance,
|
|
4052
|
+
allowedExtensions: allowedExts,
|
|
4053
|
+
allowedMimes,
|
|
3664
4054
|
maxSizeMB
|
|
3665
|
-
);
|
|
4055
|
+
});
|
|
3666
4056
|
}
|
|
3667
4057
|
};
|
|
3668
4058
|
fileWrapper.appendChild(fileContainer);
|
|
@@ -3670,7 +4060,7 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3670
4060
|
wrapper.appendChild(fileWrapper);
|
|
3671
4061
|
}
|
|
3672
4062
|
function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
3673
|
-
var _a, _b;
|
|
4063
|
+
var _a, _b, _c, _d;
|
|
3674
4064
|
const state = ctx.state;
|
|
3675
4065
|
const filesWrapper = document.createElement("div");
|
|
3676
4066
|
filesWrapper.className = "space-y-2";
|
|
@@ -3681,7 +4071,10 @@ function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3681
4071
|
filesPicker.multiple = true;
|
|
3682
4072
|
filesPicker.style.display = "none";
|
|
3683
4073
|
if (element.accept) {
|
|
3684
|
-
filesPicker.accept = typeof element.accept === "string" ? element.accept :
|
|
4074
|
+
filesPicker.accept = typeof element.accept === "string" ? element.accept : [
|
|
4075
|
+
...(_b = (_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`)) != null ? _b : [],
|
|
4076
|
+
...(_c = element.accept.mime) != null ? _c : []
|
|
4077
|
+
].join(",") || "";
|
|
3685
4078
|
}
|
|
3686
4079
|
const filesContainer = document.createElement("div");
|
|
3687
4080
|
filesContainer.className = "files-list-wrapper";
|
|
@@ -3695,30 +4088,44 @@ function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3695
4088
|
const filesConstraints = {
|
|
3696
4089
|
maxCount: Infinity,
|
|
3697
4090
|
allowedExtensions: getAllowedExtensions(element.accept),
|
|
3698
|
-
|
|
4091
|
+
allowedMimes: getAllowedMimes(element.accept),
|
|
4092
|
+
maxSize: (_d = element.maxSize) != null ? _d : Infinity
|
|
3699
4093
|
};
|
|
3700
4094
|
filesContainer.appendChild(list);
|
|
3701
4095
|
filesWrapper.appendChild(filesPicker);
|
|
3702
4096
|
filesWrapper.appendChild(filesContainer);
|
|
3703
4097
|
wrapper.appendChild(filesWrapper);
|
|
4098
|
+
const onLibraryPickFiles = state.config.pickExistingFiles && !element.disableLibrary ? () => {
|
|
4099
|
+
handleLibraryPickMulti(
|
|
4100
|
+
state,
|
|
4101
|
+
element,
|
|
4102
|
+
filesWrapper,
|
|
4103
|
+
pathKey,
|
|
4104
|
+
initialFiles,
|
|
4105
|
+
Infinity,
|
|
4106
|
+
updateFilesList,
|
|
4107
|
+
ctx.instance
|
|
4108
|
+
).catch((err) => {
|
|
4109
|
+
console.error("Library pick failed:", err);
|
|
4110
|
+
});
|
|
4111
|
+
} : null;
|
|
3704
4112
|
function updateFilesList() {
|
|
3705
4113
|
const currentlyReadonly = isElementReadonly(element, state);
|
|
3706
|
-
renderResourcePills(
|
|
3707
|
-
list,
|
|
3708
|
-
initialFiles,
|
|
4114
|
+
renderResourcePills({
|
|
4115
|
+
container: list,
|
|
4116
|
+
rids: initialFiles,
|
|
3709
4117
|
state,
|
|
3710
|
-
currentlyReadonly ? null : (ridToRemove) => {
|
|
4118
|
+
onRemove: currentlyReadonly ? null : (ridToRemove) => {
|
|
3711
4119
|
var _a2;
|
|
3712
4120
|
releaseLocalFileUrl((_a2 = state.resourceIndex.get(ridToRemove)) == null ? void 0 : _a2.file);
|
|
3713
4121
|
const index = initialFiles.indexOf(ridToRemove);
|
|
3714
4122
|
if (index > -1) initialFiles.splice(index, 1);
|
|
3715
4123
|
updateFilesList();
|
|
3716
4124
|
},
|
|
3717
|
-
filesFieldHint,
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
);
|
|
4125
|
+
hint: filesFieldHint,
|
|
4126
|
+
isReadonly: currentlyReadonly,
|
|
4127
|
+
onLibraryPick: currentlyReadonly ? null : onLibraryPickFiles
|
|
4128
|
+
});
|
|
3722
4129
|
}
|
|
3723
4130
|
updateFilesList();
|
|
3724
4131
|
setupFilesDropHandler(
|
|
@@ -3741,7 +4148,7 @@ function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3741
4148
|
);
|
|
3742
4149
|
}
|
|
3743
4150
|
function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
3744
|
-
var _a, _b, _c, _d;
|
|
4151
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3745
4152
|
const state = ctx.state;
|
|
3746
4153
|
const minFiles = (_a = element.minCount) != null ? _a : 0;
|
|
3747
4154
|
const maxFiles = (_b = element.maxCount) != null ? _b : Infinity;
|
|
@@ -3754,7 +4161,10 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3754
4161
|
filesPicker.multiple = true;
|
|
3755
4162
|
filesPicker.style.display = "none";
|
|
3756
4163
|
if (element.accept) {
|
|
3757
|
-
filesPicker.accept = typeof element.accept === "string" ? element.accept :
|
|
4164
|
+
filesPicker.accept = typeof element.accept === "string" ? element.accept : [
|
|
4165
|
+
...(_d = (_c = element.accept.extensions) == null ? void 0 : _c.map((ext) => `.${ext}`)) != null ? _d : [],
|
|
4166
|
+
...(_e = element.accept.mime) != null ? _e : []
|
|
4167
|
+
].join(",") || "";
|
|
3758
4168
|
}
|
|
3759
4169
|
const filesContainer = document.createElement("div");
|
|
3760
4170
|
filesContainer.className = "files-list-wrapper";
|
|
@@ -3771,30 +4181,46 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3771
4181
|
const multipleConstraints = {
|
|
3772
4182
|
maxCount: maxFiles,
|
|
3773
4183
|
allowedExtensions: getAllowedExtensions(element.accept),
|
|
3774
|
-
|
|
4184
|
+
allowedMimes: getAllowedMimes(element.accept),
|
|
4185
|
+
maxSize: (_f = element.maxSize) != null ? _f : Infinity
|
|
3775
4186
|
};
|
|
3776
4187
|
const buildCountInfo = () => {
|
|
3777
4188
|
const countText = initialFiles.length === 1 ? t("fileCountSingle", state, { count: initialFiles.length }) : t("fileCountPlural", state, { count: initialFiles.length });
|
|
3778
4189
|
const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
|
|
3779
4190
|
return countText + minMaxText;
|
|
3780
4191
|
};
|
|
4192
|
+
const onLibraryPickMultiple = state.config.pickExistingFiles && !element.disableLibrary ? () => {
|
|
4193
|
+
handleLibraryPickMulti(
|
|
4194
|
+
state,
|
|
4195
|
+
element,
|
|
4196
|
+
filesWrapper,
|
|
4197
|
+
pathKey,
|
|
4198
|
+
initialFiles,
|
|
4199
|
+
maxFiles,
|
|
4200
|
+
updateFilesDisplay,
|
|
4201
|
+
ctx.instance
|
|
4202
|
+
).catch((err) => {
|
|
4203
|
+
console.error("Library pick failed:", err);
|
|
4204
|
+
});
|
|
4205
|
+
} : null;
|
|
3781
4206
|
const updateFilesDisplay = () => {
|
|
3782
4207
|
const currentlyReadonly = isElementReadonly(element, state);
|
|
3783
|
-
renderResourcePills(
|
|
3784
|
-
list,
|
|
3785
|
-
initialFiles,
|
|
4208
|
+
renderResourcePills({
|
|
4209
|
+
container: list,
|
|
4210
|
+
rids: initialFiles,
|
|
3786
4211
|
state,
|
|
3787
|
-
currentlyReadonly ? null : (index) => {
|
|
4212
|
+
onRemove: currentlyReadonly ? null : (index) => {
|
|
3788
4213
|
var _a2;
|
|
3789
4214
|
releaseLocalFileUrl((_a2 = state.resourceIndex.get(index)) == null ? void 0 : _a2.file);
|
|
3790
4215
|
initialFiles.splice(initialFiles.indexOf(index), 1);
|
|
3791
4216
|
updateFilesDisplay();
|
|
3792
4217
|
},
|
|
3793
|
-
multipleFilesHint,
|
|
3794
|
-
buildCountInfo(),
|
|
3795
|
-
maxFiles < Infinity ? maxFiles : void 0,
|
|
3796
|
-
currentlyReadonly
|
|
3797
|
-
|
|
4218
|
+
hint: multipleFilesHint,
|
|
4219
|
+
countInfo: buildCountInfo(),
|
|
4220
|
+
maxCount: maxFiles < Infinity ? maxFiles : void 0,
|
|
4221
|
+
isReadonly: currentlyReadonly,
|
|
4222
|
+
onLibraryPick: currentlyReadonly ? null : onLibraryPickMultiple
|
|
4223
|
+
});
|
|
3798
4224
|
};
|
|
3799
4225
|
setupFilesDropHandler(
|
|
3800
4226
|
filesContainer,
|
|
@@ -3852,19 +4278,30 @@ function validateFileCount(key, resourceIds, element, state, errors) {
|
|
|
3852
4278
|
errors.push(`${key}: ${t("maxFiles", state, { max: maxFiles })}`);
|
|
3853
4279
|
}
|
|
3854
4280
|
}
|
|
3855
|
-
function
|
|
3856
|
-
var _a;
|
|
4281
|
+
function validateFileTypes(key, resourceIds, element, state, errors) {
|
|
4282
|
+
var _a, _b;
|
|
3857
4283
|
const acceptField = "accept" in element ? element.accept : void 0;
|
|
3858
4284
|
const allowedExtensions = getAllowedExtensions(acceptField);
|
|
3859
|
-
|
|
4285
|
+
const allowedMimes = getAllowedMimes(acceptField);
|
|
4286
|
+
if (allowedExtensions.length === 0 && allowedMimes.length === 0) return;
|
|
3860
4287
|
const formats = allowedExtensions.join(", ");
|
|
4288
|
+
const mimes = allowedMimes.join(", ");
|
|
3861
4289
|
for (const rid of resourceIds) {
|
|
3862
4290
|
const meta = state.resourceIndex.get(rid);
|
|
3863
4291
|
const fileName = (_a = meta == null ? void 0 : meta.name) != null ? _a : rid;
|
|
3864
|
-
if (!isFileExtensionAllowed(fileName, allowedExtensions)) {
|
|
4292
|
+
if (allowedExtensions.length > 0 && !isFileExtensionAllowed(fileName, allowedExtensions)) {
|
|
3865
4293
|
errors.push(
|
|
3866
4294
|
`${key}: ${t("invalidFileExtension", state, { name: fileName, formats })}`
|
|
3867
4295
|
);
|
|
4296
|
+
continue;
|
|
4297
|
+
}
|
|
4298
|
+
if (allowedMimes.length > 0 && !(meta == null ? void 0 : meta.inferredFromExtension)) {
|
|
4299
|
+
const mimeType = (_b = meta == null ? void 0 : meta.type) != null ? _b : "";
|
|
4300
|
+
if (!isMimeAllowed(mimeType, allowedMimes)) {
|
|
4301
|
+
errors.push(
|
|
4302
|
+
`${key}: ${t("invalidFileMime", state, { name: fileName, type: mimeType, mimes })}`
|
|
4303
|
+
);
|
|
4304
|
+
}
|
|
3868
4305
|
}
|
|
3869
4306
|
}
|
|
3870
4307
|
}
|
|
@@ -3889,7 +4326,7 @@ function validateMultiFile(element, key, context) {
|
|
|
3889
4326
|
const resourceIds = readMultiFileResourceIds(scopeRoot, fullKey);
|
|
3890
4327
|
if (!skipValidation) {
|
|
3891
4328
|
validateFileCount(key, resourceIds, element, state, errors);
|
|
3892
|
-
|
|
4329
|
+
validateFileTypes(key, resourceIds, element, state, errors);
|
|
3893
4330
|
validateFileSizes(key, resourceIds, element, state, errors);
|
|
3894
4331
|
}
|
|
3895
4332
|
return { value: resourceIds, errors };
|
|
@@ -3907,7 +4344,7 @@ function validateSingleFile(element, key, context) {
|
|
|
3907
4344
|
return { value: null, errors };
|
|
3908
4345
|
}
|
|
3909
4346
|
if (!skipValidation && rid !== "") {
|
|
3910
|
-
|
|
4347
|
+
validateFileTypes(key, [rid], element, state, errors);
|
|
3911
4348
|
validateFileSizes(key, [rid], element, state, errors);
|
|
3912
4349
|
}
|
|
3913
4350
|
return { value: rid || null, errors };
|
|
@@ -4033,9 +4470,7 @@ function updateFileField(element, fieldPath, value, context) {
|
|
|
4033
4470
|
}
|
|
4034
4471
|
value.forEach((resourceId) => {
|
|
4035
4472
|
if (resourceId && typeof resourceId === "string") {
|
|
4036
|
-
|
|
4037
|
-
addResourceToIndex(resourceId, state);
|
|
4038
|
-
}
|
|
4473
|
+
seedInferredResource(resourceId, state.resourceIndex);
|
|
4039
4474
|
}
|
|
4040
4475
|
});
|
|
4041
4476
|
const filesWrapper = scopeRoot.querySelector(
|
|
@@ -4060,35 +4495,13 @@ function updateFileField(element, fieldPath, value, context) {
|
|
|
4060
4495
|
}
|
|
4061
4496
|
hiddenInput.value = value != null ? String(value) : "";
|
|
4062
4497
|
if (value && typeof value === "string") {
|
|
4063
|
-
|
|
4064
|
-
addResourceToIndex(value, state);
|
|
4065
|
-
}
|
|
4498
|
+
seedInferredResource(value, state.resourceIndex);
|
|
4066
4499
|
console.info(
|
|
4067
4500
|
`updateFileField: File field "${fieldPath}" updated. Preview update requires re-render.`
|
|
4068
4501
|
);
|
|
4069
4502
|
}
|
|
4070
4503
|
}
|
|
4071
4504
|
}
|
|
4072
|
-
function addResourceToIndex(resourceId, state) {
|
|
4073
|
-
var _a;
|
|
4074
|
-
const filename = resourceId.split("/").pop() || "file";
|
|
4075
|
-
const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
|
|
4076
|
-
let fileType = "application/octet-stream";
|
|
4077
|
-
if (extension) {
|
|
4078
|
-
if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
|
|
4079
|
-
fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
|
|
4080
|
-
} else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
|
|
4081
|
-
fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
|
|
4082
|
-
}
|
|
4083
|
-
}
|
|
4084
|
-
state.resourceIndex.set(resourceId, {
|
|
4085
|
-
name: filename,
|
|
4086
|
-
type: fileType,
|
|
4087
|
-
size: 0,
|
|
4088
|
-
uploadedAt: /* @__PURE__ */ new Date(),
|
|
4089
|
-
file: void 0
|
|
4090
|
-
});
|
|
4091
|
-
}
|
|
4092
4505
|
|
|
4093
4506
|
// src/components/colour.ts
|
|
4094
4507
|
function normalizeColourValue(value) {
|
|
@@ -8825,6 +9238,7 @@ var defaultConfig = {
|
|
|
8825
9238
|
enableFilePreview: true,
|
|
8826
9239
|
maxPreviewSize: "200px",
|
|
8827
9240
|
readonly: false,
|
|
9241
|
+
pickExistingFiles: null,
|
|
8828
9242
|
parseTableFile: null,
|
|
8829
9243
|
locale: "en",
|
|
8830
9244
|
translations: {
|
|
@@ -8861,6 +9275,10 @@ var defaultConfig = {
|
|
|
8861
9275
|
fileCountRange: "({min}-{max})",
|
|
8862
9276
|
uploadingFile: "Uploading\u2026",
|
|
8863
9277
|
filesCounter: "{count}/{max}",
|
|
9278
|
+
fromLibrary: "From library",
|
|
9279
|
+
libraryEmpty: "Library is empty",
|
|
9280
|
+
libraryHint: "Choose from previously uploaded files",
|
|
9281
|
+
pickerError: "Failed to load files from library",
|
|
8864
9282
|
// Validation errors
|
|
8865
9283
|
required: "Required",
|
|
8866
9284
|
minItems: "Minimum {min} items required",
|
|
@@ -8876,6 +9294,7 @@ var defaultConfig = {
|
|
|
8876
9294
|
minFiles: "Minimum {min} files required",
|
|
8877
9295
|
maxFiles: "Maximum {max} files allowed",
|
|
8878
9296
|
invalidFileExtension: 'File "{name}" has unsupported format. Allowed: {formats}',
|
|
9297
|
+
invalidFileMime: 'File "{name}": file type {type} not allowed (allowed: {mimes})',
|
|
8879
9298
|
fileTooLarge: 'File "{name}" exceeds maximum size of {maxSize}MB',
|
|
8880
9299
|
filesLimitExceeded: "{skipped} file(s) skipped: maximum {max} files allowed",
|
|
8881
9300
|
unsupportedFieldType: "Unsupported field type: {type}",
|
|
@@ -8927,6 +9346,10 @@ var defaultConfig = {
|
|
|
8927
9346
|
fileCountRange: "({min}-{max})",
|
|
8928
9347
|
uploadingFile: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026",
|
|
8929
9348
|
filesCounter: "{count}/{max}",
|
|
9349
|
+
fromLibrary: "\u0418\u0437 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438",
|
|
9350
|
+
libraryEmpty: "\u0411\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0430 \u043F\u0443\u0441\u0442\u0430",
|
|
9351
|
+
libraryHint: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0438\u0437 \u0440\u0430\u043D\u0435\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043D\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
9352
|
+
pickerError: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0444\u0430\u0439\u043B\u044B \u0438\u0437 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438",
|
|
8930
9353
|
// Validation errors
|
|
8931
9354
|
required: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435",
|
|
8932
9355
|
minItems: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",
|
|
@@ -8942,6 +9365,7 @@ var defaultConfig = {
|
|
|
8942
9365
|
minFiles: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8943
9366
|
maxFiles: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8944
9367
|
invalidFileExtension: '\u0424\u0430\u0439\u043B "{name}" \u0438\u043C\u0435\u0435\u0442 \u043D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442. \u0414\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u044B\u0435: {formats}',
|
|
9368
|
+
invalidFileMime: '\u0424\u0430\u0439\u043B "{name}": \u0442\u0438\u043F \u0444\u0430\u0439\u043B\u0430 {type} \u043D\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D (\u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u044B: {mimes})',
|
|
8945
9369
|
fileTooLarge: '\u0424\u0430\u0439\u043B "{name}" \u043F\u0440\u0435\u0432\u044B\u0448\u0430\u0435\u0442 \u043C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440 {maxSize}\u041C\u0411',
|
|
8946
9370
|
filesLimitExceeded: "{skipped} \u0444\u0430\u0439\u043B(\u043E\u0432) \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u043E: \u043C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8947
9371
|
unsupportedFieldType: "\u041D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0442\u0438\u043F \u043F\u043E\u043B\u044F: {type}",
|