@dmitryvim/form-builder 0.2.27 → 0.2.28
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.28.min.js +956 -0
- package/dist/cjs/index.cjs +542 -133
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +528 -122
- 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/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;
|
|
@@ -3271,8 +3377,19 @@ async function uploadSingleFile(file, state) {
|
|
|
3271
3377
|
throw new Error(`File upload failed: ${err.message}`);
|
|
3272
3378
|
}
|
|
3273
3379
|
}
|
|
3274
|
-
async function handleFileSelect(
|
|
3380
|
+
async function handleFileSelect(opts) {
|
|
3275
3381
|
var _a, _b;
|
|
3382
|
+
const {
|
|
3383
|
+
file,
|
|
3384
|
+
container,
|
|
3385
|
+
fieldName,
|
|
3386
|
+
state,
|
|
3387
|
+
deps = null,
|
|
3388
|
+
instance = null,
|
|
3389
|
+
allowedExtensions = [],
|
|
3390
|
+
allowedMimes = [],
|
|
3391
|
+
maxSizeMB = Infinity
|
|
3392
|
+
} = opts;
|
|
3276
3393
|
if (!isFileExtensionAllowed(file.name, allowedExtensions)) {
|
|
3277
3394
|
const formats = allowedExtensions.join(", ");
|
|
3278
3395
|
showFileError(
|
|
@@ -3281,6 +3398,14 @@ async function handleFileSelect(file, container, fieldName, state, deps = null,
|
|
|
3281
3398
|
);
|
|
3282
3399
|
return;
|
|
3283
3400
|
}
|
|
3401
|
+
if (!isMimeAllowed(file.type, allowedMimes)) {
|
|
3402
|
+
const mimes = allowedMimes.join(", ");
|
|
3403
|
+
showFileError(
|
|
3404
|
+
container,
|
|
3405
|
+
t("invalidFileMime", state, { name: file.name, type: file.type, mimes })
|
|
3406
|
+
);
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3284
3409
|
if (!isFileSizeAllowed(file, maxSizeMB)) {
|
|
3285
3410
|
showFileError(
|
|
3286
3411
|
container,
|
|
@@ -3340,10 +3465,16 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
|
3340
3465
|
const afterExt = allFiles.filter(
|
|
3341
3466
|
(f) => isFileExtensionAllowed(f.name, constraints.allowedExtensions)
|
|
3342
3467
|
);
|
|
3343
|
-
const
|
|
3468
|
+
const rejectedByMime = afterExt.filter(
|
|
3469
|
+
(f) => !isMimeAllowed(f.type, constraints.allowedMimes)
|
|
3470
|
+
);
|
|
3471
|
+
const afterMime = afterExt.filter(
|
|
3472
|
+
(f) => isMimeAllowed(f.type, constraints.allowedMimes)
|
|
3473
|
+
);
|
|
3474
|
+
const rejectedBySize = afterMime.filter(
|
|
3344
3475
|
(f) => !isFileSizeAllowed(f, constraints.maxSize)
|
|
3345
3476
|
);
|
|
3346
|
-
const valid =
|
|
3477
|
+
const valid = afterMime.filter((f) => isFileSizeAllowed(f, constraints.maxSize));
|
|
3347
3478
|
const remaining = constraints.maxCount === Infinity ? valid.length : Math.max(0, constraints.maxCount - currentCount);
|
|
3348
3479
|
const accepted = valid.slice(0, remaining);
|
|
3349
3480
|
const skippedByCount = valid.length - accepted.length;
|
|
@@ -3353,6 +3484,11 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
|
|
|
3353
3484
|
const names = rejectedByExt.map((f) => f.name).join(", ");
|
|
3354
3485
|
errorParts.push(t("invalidFileExtension", state, { name: names, formats }));
|
|
3355
3486
|
}
|
|
3487
|
+
if (rejectedByMime.length > 0) {
|
|
3488
|
+
const mimes = constraints.allowedMimes.join(", ");
|
|
3489
|
+
const names = rejectedByMime.map((f) => f.name).join(", ");
|
|
3490
|
+
errorParts.push(t("invalidFileMime", state, { name: names, type: rejectedByMime.map((f) => f.type).join(", "), mimes }));
|
|
3491
|
+
}
|
|
3356
3492
|
if (rejectedBySize.length > 0) {
|
|
3357
3493
|
const names = rejectedBySize.map((f) => f.name).join(", ");
|
|
3358
3494
|
errorParts.push(
|
|
@@ -3445,30 +3581,166 @@ function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback
|
|
|
3445
3581
|
};
|
|
3446
3582
|
}
|
|
3447
3583
|
|
|
3448
|
-
// src/components/file/
|
|
3449
|
-
function
|
|
3584
|
+
// src/components/file/library.ts
|
|
3585
|
+
function buildAcceptContext(element) {
|
|
3450
3586
|
var _a, _b;
|
|
3451
|
-
if (!
|
|
3452
|
-
|
|
3453
|
-
const
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3587
|
+
if (!element.accept) return void 0;
|
|
3588
|
+
if (typeof element.accept === "string") {
|
|
3589
|
+
const exts2 = getAllowedExtensions(element.accept);
|
|
3590
|
+
return exts2.length > 0 ? { extensions: exts2 } : void 0;
|
|
3591
|
+
}
|
|
3592
|
+
const exts = (_a = element.accept.extensions) != null ? _a : [];
|
|
3593
|
+
const mime = (_b = element.accept.mime) != null ? _b : [];
|
|
3594
|
+
const normalizedExts = exts.map((e) => e.toLowerCase());
|
|
3595
|
+
if (normalizedExts.length === 0 && mime.length === 0) return void 0;
|
|
3596
|
+
const result = {};
|
|
3597
|
+
if (normalizedExts.length > 0) result.extensions = normalizedExts;
|
|
3598
|
+
if (mime.length > 0) result.mime = mime;
|
|
3599
|
+
return result;
|
|
3600
|
+
}
|
|
3601
|
+
function validatePickedResource(resource, allowedExtensions, allowedMimes, maxSizeMB, state) {
|
|
3602
|
+
if (!isFileExtensionAllowed(resource.name, allowedExtensions)) {
|
|
3603
|
+
const formats = allowedExtensions.join(", ");
|
|
3604
|
+
return t("invalidFileExtension", state, { name: resource.name, formats });
|
|
3605
|
+
}
|
|
3606
|
+
if (!isMimeAllowed(resource.type, allowedMimes)) {
|
|
3607
|
+
const mimes = allowedMimes.join(", ");
|
|
3608
|
+
return t("invalidFileMime", state, { name: resource.name, type: resource.type, mimes });
|
|
3609
|
+
}
|
|
3610
|
+
if (!isSizeWithinLimit(resource.size, maxSizeMB)) {
|
|
3611
|
+
return t("fileTooLarge", state, { name: resource.name, maxSize: maxSizeMB });
|
|
3612
|
+
}
|
|
3613
|
+
return null;
|
|
3614
|
+
}
|
|
3615
|
+
function readCurrentResourceIds(wrapper) {
|
|
3616
|
+
const raw = wrapper.dataset.resourceIds;
|
|
3617
|
+
if (!raw) return [];
|
|
3618
|
+
try {
|
|
3619
|
+
const parsed = JSON.parse(raw);
|
|
3620
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
3621
|
+
} catch {
|
|
3622
|
+
return [];
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
function registerPickedResource(resource, state) {
|
|
3626
|
+
var _a;
|
|
3627
|
+
const existing = state.resourceIndex.get(resource.resourceId);
|
|
3628
|
+
state.resourceIndex.set(resource.resourceId, {
|
|
3629
|
+
name: resource.name,
|
|
3630
|
+
type: resource.type,
|
|
3631
|
+
size: resource.size,
|
|
3632
|
+
uploadedAt: (_a = existing == null ? void 0 : existing.uploadedAt) != null ? _a : /* @__PURE__ */ new Date(),
|
|
3633
|
+
file: existing == null ? void 0 : existing.file
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
3636
|
+
function extractPickerError(error, state) {
|
|
3637
|
+
if (error instanceof Error && error.message) return error.message;
|
|
3638
|
+
return t("pickerError", state);
|
|
3639
|
+
}
|
|
3640
|
+
async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resourceIds, maxCount, updateCallback, instance) {
|
|
3641
|
+
var _a;
|
|
3642
|
+
if (!state.config.pickExistingFiles) return;
|
|
3643
|
+
const allowedExtensions = getAllowedExtensions(element.accept);
|
|
3644
|
+
const allowedMimes = getAllowedMimes(element.accept);
|
|
3645
|
+
const maxSizeMB = (_a = element.maxSize) != null ? _a : Infinity;
|
|
3646
|
+
const currentIds = readCurrentResourceIds(wrapper);
|
|
3647
|
+
const remaining = maxCount === Infinity ? Infinity : Math.max(0, maxCount - currentIds.length);
|
|
3648
|
+
let picked;
|
|
3649
|
+
try {
|
|
3650
|
+
picked = await state.config.pickExistingFiles({
|
|
3651
|
+
fieldPath,
|
|
3652
|
+
mode: "multiple",
|
|
3653
|
+
accept: buildAcceptContext(element),
|
|
3654
|
+
maxSizeMB: maxSizeMB === Infinity ? void 0 : maxSizeMB,
|
|
3655
|
+
remainingSlots: remaining === Infinity ? void 0 : remaining,
|
|
3656
|
+
selectedResourceIds: [...currentIds]
|
|
3657
|
+
});
|
|
3658
|
+
} catch (error) {
|
|
3659
|
+
showFileError(wrapper, extractPickerError(error, state));
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
if (picked.length === 0) return;
|
|
3663
|
+
const existingSet = new Set(currentIds);
|
|
3664
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3665
|
+
const deduped = picked.filter((r) => {
|
|
3666
|
+
if (existingSet.has(r.resourceId)) return false;
|
|
3667
|
+
if (seen.has(r.resourceId)) return false;
|
|
3668
|
+
seen.add(r.resourceId);
|
|
3669
|
+
return true;
|
|
3670
|
+
});
|
|
3671
|
+
const validItems = deduped.filter((r) => {
|
|
3672
|
+
const err = validatePickedResource(r, allowedExtensions, allowedMimes, maxSizeMB, state);
|
|
3673
|
+
return err === null;
|
|
3674
|
+
});
|
|
3675
|
+
const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - resourceIds.length);
|
|
3676
|
+
const accepted = validItems.slice(0, freshRemaining);
|
|
3677
|
+
const skipped = validItems.length - accepted.length;
|
|
3678
|
+
if (accepted.length === 0) return;
|
|
3679
|
+
clearFileError(wrapper);
|
|
3680
|
+
if (skipped > 0) {
|
|
3681
|
+
showFileError(
|
|
3682
|
+
wrapper,
|
|
3683
|
+
t("filesLimitExceeded", state, { skipped, max: maxCount })
|
|
3684
|
+
);
|
|
3685
|
+
}
|
|
3686
|
+
for (const resource of accepted) {
|
|
3687
|
+
registerPickedResource(resource, state);
|
|
3688
|
+
resourceIds.push(resource.resourceId);
|
|
3689
|
+
}
|
|
3690
|
+
wrapper.dataset.resourceIds = JSON.stringify(resourceIds);
|
|
3691
|
+
updateCallback();
|
|
3692
|
+
if (!state.config.readonly) {
|
|
3693
|
+
instance.triggerOnChange(fieldPath, resourceIds);
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
async function handleLibraryPickSingle(state, element, container, fileWrapper, pathKey, fieldPath, renderCallback, instance) {
|
|
3697
|
+
var _a;
|
|
3698
|
+
if (!state.config.pickExistingFiles) return;
|
|
3699
|
+
const allowedExtensions = getAllowedExtensions(element.accept);
|
|
3700
|
+
const allowedMimes = getAllowedMimes(element.accept);
|
|
3701
|
+
const maxSizeMB = (_a = element.maxSize) != null ? _a : Infinity;
|
|
3702
|
+
let picked;
|
|
3703
|
+
try {
|
|
3704
|
+
picked = await state.config.pickExistingFiles({
|
|
3705
|
+
fieldPath,
|
|
3706
|
+
mode: "single",
|
|
3707
|
+
accept: buildAcceptContext(element),
|
|
3708
|
+
maxSizeMB: maxSizeMB === Infinity ? void 0 : maxSizeMB,
|
|
3709
|
+
selectedResourceIds: []
|
|
3468
3710
|
});
|
|
3711
|
+
} catch (error) {
|
|
3712
|
+
showFileError(container, extractPickerError(error, state));
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3715
|
+
if (picked.length === 0) return;
|
|
3716
|
+
const first = picked[0];
|
|
3717
|
+
const validationError = validatePickedResource(first, allowedExtensions, allowedMimes, maxSizeMB, state);
|
|
3718
|
+
if (validationError !== null) {
|
|
3719
|
+
showFileError(container, validationError);
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
clearFileError(container);
|
|
3723
|
+
registerPickedResource(first, state);
|
|
3724
|
+
let hiddenInput = fileWrapper.querySelector('input[type="hidden"]');
|
|
3725
|
+
if (!hiddenInput) {
|
|
3726
|
+
hiddenInput = document.createElement("input");
|
|
3727
|
+
hiddenInput.type = "hidden";
|
|
3728
|
+
hiddenInput.name = pathKey;
|
|
3729
|
+
fileWrapper.appendChild(hiddenInput);
|
|
3469
3730
|
}
|
|
3731
|
+
hiddenInput.value = first.resourceId;
|
|
3732
|
+
await renderCallback(first.resourceId);
|
|
3733
|
+
if (!state.config.readonly) {
|
|
3734
|
+
instance.triggerOnChange(fieldPath, first.resourceId);
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
// src/components/file/render-edit.ts
|
|
3739
|
+
function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps) {
|
|
3740
|
+
var _a;
|
|
3741
|
+
seedInferredResource(initial, state.resourceIndex);
|
|
3470
3742
|
const meta = state.resourceIndex.get(initial);
|
|
3471
|
-
const isVideo = (
|
|
3743
|
+
const isVideo = (_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/");
|
|
3472
3744
|
if (isVideo) {
|
|
3473
3745
|
renderFilePreview(fileContainer, initial, state, {
|
|
3474
3746
|
fileName: initial,
|
|
@@ -3484,8 +3756,51 @@ function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, sta
|
|
|
3484
3756
|
hiddenInput.value = initial;
|
|
3485
3757
|
fileWrapper.appendChild(hiddenInput);
|
|
3486
3758
|
}
|
|
3487
|
-
|
|
3759
|
+
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);">
|
|
3760
|
+
<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"/>
|
|
3761
|
+
</svg>`;
|
|
3762
|
+
function buildEmptyDropzone(state, primaryText, subHint, openPicker) {
|
|
3763
|
+
const dropzone = document.createElement("div");
|
|
3764
|
+
dropzone.className = "fb-file-dropzone";
|
|
3765
|
+
dropzone.innerHTML = `
|
|
3766
|
+
${UPLOAD_SVG}
|
|
3767
|
+
<div class="fb-dropzone-primary-text">${escapeHtml(primaryText)}</div>
|
|
3768
|
+
${subHint ? `<div class="fb-dropzone-hint-text">${escapeHtml(subHint)}</div>` : ""}
|
|
3769
|
+
`;
|
|
3770
|
+
dropzone.onclick = openPicker;
|
|
3771
|
+
return dropzone;
|
|
3772
|
+
}
|
|
3773
|
+
function buildLibraryButton(variant, state, onClick) {
|
|
3774
|
+
const btn = document.createElement("button");
|
|
3775
|
+
btn.type = "button";
|
|
3776
|
+
btn.className = variant === "card" ? "fb-file-library-card" : "fb-tile fb-tile-add-library";
|
|
3777
|
+
if (variant === "card") {
|
|
3778
|
+
btn.innerHTML = `
|
|
3779
|
+
<span class="fb-file-library-card-icon" aria-hidden="true">\u{1F4DA}</span>
|
|
3780
|
+
<span class="fb-file-library-card-label">${escapeHtml(t("fromLibrary", state))}</span>
|
|
3781
|
+
<span class="fb-file-library-card-hint">${escapeHtml(t("libraryHint", state))}</span>
|
|
3782
|
+
`;
|
|
3783
|
+
} else {
|
|
3784
|
+
btn.innerHTML = `<span aria-hidden="true">\u{1F4DA}</span>`;
|
|
3785
|
+
btn.title = t("fromLibrary", state);
|
|
3786
|
+
btn.setAttribute("aria-label", t("fromLibrary", state));
|
|
3787
|
+
}
|
|
3788
|
+
btn.addEventListener("click", onClick);
|
|
3789
|
+
return btn;
|
|
3790
|
+
}
|
|
3791
|
+
function renderResourcePills(opts) {
|
|
3488
3792
|
var _a;
|
|
3793
|
+
const {
|
|
3794
|
+
container,
|
|
3795
|
+
rids,
|
|
3796
|
+
state,
|
|
3797
|
+
onRemove,
|
|
3798
|
+
hint,
|
|
3799
|
+
countInfo,
|
|
3800
|
+
maxCount,
|
|
3801
|
+
isReadonly = false,
|
|
3802
|
+
onLibraryPick
|
|
3803
|
+
} = opts;
|
|
3489
3804
|
ensureFileStyles();
|
|
3490
3805
|
const wrapper = container.closest("[data-files-wrapper]");
|
|
3491
3806
|
if (wrapper) {
|
|
@@ -3494,6 +3809,7 @@ function renderResourcePills(container, rids, state, onRemove, hint, countInfo,
|
|
|
3494
3809
|
while (container.firstChild) container.removeChild(container.firstChild);
|
|
3495
3810
|
const ridList = rids != null ? rids : [];
|
|
3496
3811
|
const atMax = maxCount !== void 0 && ridList.length >= maxCount;
|
|
3812
|
+
const hasLibrary = !isReadonly && typeof onLibraryPick === "function";
|
|
3497
3813
|
const buildSubHint = () => {
|
|
3498
3814
|
const parts = [];
|
|
3499
3815
|
if (hint) parts.push(hint);
|
|
@@ -3510,18 +3826,26 @@ function renderResourcePills(container, rids, state, onRemove, hint, countInfo,
|
|
|
3510
3826
|
emptyEl.className = "fb-tile-empty-text";
|
|
3511
3827
|
emptyEl.textContent = t("noFilesSelected", state);
|
|
3512
3828
|
container.appendChild(emptyEl);
|
|
3829
|
+
} else if (hasLibrary) {
|
|
3830
|
+
const row = document.createElement("div");
|
|
3831
|
+
row.className = "fb-file-card-row";
|
|
3832
|
+
const dropzone = buildEmptyDropzone(
|
|
3833
|
+
state,
|
|
3834
|
+
t("clickDragTextMultiple", state),
|
|
3835
|
+
buildSubHint(),
|
|
3836
|
+
openPicker
|
|
3837
|
+
);
|
|
3838
|
+
const libraryBtn = buildLibraryButton("card", state, onLibraryPick);
|
|
3839
|
+
row.appendChild(dropzone);
|
|
3840
|
+
row.appendChild(libraryBtn);
|
|
3841
|
+
container.appendChild(row);
|
|
3513
3842
|
} 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;
|
|
3843
|
+
const dropzone = buildEmptyDropzone(
|
|
3844
|
+
state,
|
|
3845
|
+
t("clickDragTextMultiple", state),
|
|
3846
|
+
buildSubHint(),
|
|
3847
|
+
openPicker
|
|
3848
|
+
);
|
|
3525
3849
|
container.appendChild(dropzone);
|
|
3526
3850
|
}
|
|
3527
3851
|
return;
|
|
@@ -3552,6 +3876,10 @@ function renderResourcePills(container, rids, state, onRemove, hint, countInfo,
|
|
|
3552
3876
|
addTile.innerHTML = "+";
|
|
3553
3877
|
addTile.onclick = openPicker;
|
|
3554
3878
|
tilesWrap.appendChild(addTile);
|
|
3879
|
+
if (hasLibrary) {
|
|
3880
|
+
const libraryTile = buildLibraryButton("tile", state, onLibraryPick);
|
|
3881
|
+
tilesWrap.appendChild(libraryTile);
|
|
3882
|
+
}
|
|
3555
3883
|
} else if (!isReadonly && atMax) {
|
|
3556
3884
|
const chip = document.createElement("div");
|
|
3557
3885
|
chip.className = "fb-tile-counter";
|
|
@@ -3571,7 +3899,7 @@ function renderResourcePills(container, rids, state, onRemove, hint, countInfo,
|
|
|
3571
3899
|
}
|
|
3572
3900
|
}
|
|
3573
3901
|
function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
3574
|
-
var _a, _b, _c;
|
|
3902
|
+
var _a, _b, _c, _d, _e;
|
|
3575
3903
|
const state = ctx.state;
|
|
3576
3904
|
const fileWrapper = document.createElement("div");
|
|
3577
3905
|
fileWrapper.className = "space-y-2";
|
|
@@ -3580,29 +3908,34 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3580
3908
|
picker.name = pathKey;
|
|
3581
3909
|
picker.style.display = "none";
|
|
3582
3910
|
if (element.accept) {
|
|
3583
|
-
picker.accept = typeof element.accept === "string" ? element.accept :
|
|
3911
|
+
picker.accept = typeof element.accept === "string" ? element.accept : [
|
|
3912
|
+
...(_b = (_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`)) != null ? _b : [],
|
|
3913
|
+
...(_c = element.accept.mime) != null ? _c : []
|
|
3914
|
+
].join(",") || "";
|
|
3584
3915
|
}
|
|
3585
3916
|
const fileContainer = document.createElement("div");
|
|
3586
3917
|
fileContainer.className = "file-preview-container";
|
|
3587
3918
|
const initial = ctx.prefill[element.key];
|
|
3588
3919
|
const allowedExts = getAllowedExtensions(element.accept);
|
|
3589
|
-
const
|
|
3920
|
+
const allowedMimes = getAllowedMimes(element.accept);
|
|
3921
|
+
const maxSizeMB = (_d = element.maxSize) != null ? _d : Infinity;
|
|
3590
3922
|
const handlers = {
|
|
3591
3923
|
fileUploadHandler() {
|
|
3592
3924
|
picker.click();
|
|
3593
3925
|
},
|
|
3594
3926
|
dragHandler(files) {
|
|
3595
3927
|
if (files.length > 0) {
|
|
3596
|
-
handleFileSelect(
|
|
3597
|
-
files[0],
|
|
3598
|
-
fileContainer,
|
|
3599
|
-
pathKey,
|
|
3928
|
+
handleFileSelect({
|
|
3929
|
+
file: files[0],
|
|
3930
|
+
container: fileContainer,
|
|
3931
|
+
fieldName: pathKey,
|
|
3600
3932
|
state,
|
|
3601
|
-
buildDeps(),
|
|
3602
|
-
ctx.instance,
|
|
3603
|
-
allowedExts,
|
|
3933
|
+
deps: buildDeps(),
|
|
3934
|
+
instance: ctx.instance,
|
|
3935
|
+
allowedExtensions: allowedExts,
|
|
3936
|
+
allowedMimes,
|
|
3604
3937
|
maxSizeMB
|
|
3605
|
-
);
|
|
3938
|
+
});
|
|
3606
3939
|
}
|
|
3607
3940
|
},
|
|
3608
3941
|
setupDrop(container) {
|
|
@@ -3634,6 +3967,47 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3634
3967
|
setupDrop: handlers.setupDrop,
|
|
3635
3968
|
onRemove: handlers.onRemove
|
|
3636
3969
|
});
|
|
3970
|
+
const renderEmptySingleState = () => {
|
|
3971
|
+
if (state.config.pickExistingFiles && !element.disableLibrary) {
|
|
3972
|
+
fileContainer.className = "file-preview-container";
|
|
3973
|
+
fileContainer.removeAttribute("style");
|
|
3974
|
+
fileContainer.onclick = null;
|
|
3975
|
+
const row = document.createElement("div");
|
|
3976
|
+
row.className = "fb-file-card-row";
|
|
3977
|
+
row.style.cssText = "display:flex;gap:8px;align-items:stretch;";
|
|
3978
|
+
const hint = makeFieldHint(element, state);
|
|
3979
|
+
const uploadCard = buildEmptyDropzone(
|
|
3980
|
+
state,
|
|
3981
|
+
t("clickDragText", state),
|
|
3982
|
+
hint,
|
|
3983
|
+
handlers.fileUploadHandler
|
|
3984
|
+
);
|
|
3985
|
+
uploadCard.style.cssText = "flex:1;min-width:0;height:128px;";
|
|
3986
|
+
setupDragAndDrop(uploadCard, handlers.dragHandler);
|
|
3987
|
+
const libraryBtn = buildLibraryButton("card", state, () => {
|
|
3988
|
+
handleLibraryPickSingle(
|
|
3989
|
+
state,
|
|
3990
|
+
element,
|
|
3991
|
+
fileContainer,
|
|
3992
|
+
fileWrapper,
|
|
3993
|
+
pathKey,
|
|
3994
|
+
pathKey,
|
|
3995
|
+
async (rid) => {
|
|
3996
|
+
await renderSingleFileEditTile(fileContainer, rid, state, buildDeps());
|
|
3997
|
+
},
|
|
3998
|
+
ctx.instance
|
|
3999
|
+
).catch((err) => {
|
|
4000
|
+
console.error("Library pick failed:", err);
|
|
4001
|
+
});
|
|
4002
|
+
});
|
|
4003
|
+
libraryBtn.style.cssText = "flex:1;min-width:0;";
|
|
4004
|
+
row.appendChild(uploadCard);
|
|
4005
|
+
row.appendChild(libraryBtn);
|
|
4006
|
+
fileContainer.appendChild(row);
|
|
4007
|
+
} else {
|
|
4008
|
+
handlers.restoreDropzone();
|
|
4009
|
+
}
|
|
4010
|
+
};
|
|
3637
4011
|
if (initial) {
|
|
3638
4012
|
handleInitialFileData(
|
|
3639
4013
|
initial,
|
|
@@ -3644,25 +4018,26 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3644
4018
|
buildDeps()
|
|
3645
4019
|
);
|
|
3646
4020
|
const prefillMeta = state.resourceIndex.get(initial);
|
|
3647
|
-
if ((
|
|
4021
|
+
if ((_e = prefillMeta == null ? void 0 : prefillMeta.type) == null ? void 0 : _e.startsWith("video/")) {
|
|
3648
4022
|
fileContainer.onclick = handlers.fileUploadHandler;
|
|
3649
4023
|
setupDragAndDrop(fileContainer, handlers.dragHandler);
|
|
3650
4024
|
}
|
|
3651
4025
|
} else {
|
|
3652
|
-
|
|
4026
|
+
renderEmptySingleState();
|
|
3653
4027
|
}
|
|
3654
4028
|
picker.onchange = () => {
|
|
3655
4029
|
if (picker.files && picker.files.length > 0) {
|
|
3656
|
-
handleFileSelect(
|
|
3657
|
-
picker.files[0],
|
|
3658
|
-
fileContainer,
|
|
3659
|
-
pathKey,
|
|
4030
|
+
handleFileSelect({
|
|
4031
|
+
file: picker.files[0],
|
|
4032
|
+
container: fileContainer,
|
|
4033
|
+
fieldName: pathKey,
|
|
3660
4034
|
state,
|
|
3661
|
-
buildDeps(),
|
|
3662
|
-
ctx.instance,
|
|
3663
|
-
allowedExts,
|
|
4035
|
+
deps: buildDeps(),
|
|
4036
|
+
instance: ctx.instance,
|
|
4037
|
+
allowedExtensions: allowedExts,
|
|
4038
|
+
allowedMimes,
|
|
3664
4039
|
maxSizeMB
|
|
3665
|
-
);
|
|
4040
|
+
});
|
|
3666
4041
|
}
|
|
3667
4042
|
};
|
|
3668
4043
|
fileWrapper.appendChild(fileContainer);
|
|
@@ -3670,7 +4045,7 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3670
4045
|
wrapper.appendChild(fileWrapper);
|
|
3671
4046
|
}
|
|
3672
4047
|
function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
3673
|
-
var _a, _b;
|
|
4048
|
+
var _a, _b, _c, _d;
|
|
3674
4049
|
const state = ctx.state;
|
|
3675
4050
|
const filesWrapper = document.createElement("div");
|
|
3676
4051
|
filesWrapper.className = "space-y-2";
|
|
@@ -3681,7 +4056,10 @@ function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3681
4056
|
filesPicker.multiple = true;
|
|
3682
4057
|
filesPicker.style.display = "none";
|
|
3683
4058
|
if (element.accept) {
|
|
3684
|
-
filesPicker.accept = typeof element.accept === "string" ? element.accept :
|
|
4059
|
+
filesPicker.accept = typeof element.accept === "string" ? element.accept : [
|
|
4060
|
+
...(_b = (_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`)) != null ? _b : [],
|
|
4061
|
+
...(_c = element.accept.mime) != null ? _c : []
|
|
4062
|
+
].join(",") || "";
|
|
3685
4063
|
}
|
|
3686
4064
|
const filesContainer = document.createElement("div");
|
|
3687
4065
|
filesContainer.className = "files-list-wrapper";
|
|
@@ -3695,30 +4073,44 @@ function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3695
4073
|
const filesConstraints = {
|
|
3696
4074
|
maxCount: Infinity,
|
|
3697
4075
|
allowedExtensions: getAllowedExtensions(element.accept),
|
|
3698
|
-
|
|
4076
|
+
allowedMimes: getAllowedMimes(element.accept),
|
|
4077
|
+
maxSize: (_d = element.maxSize) != null ? _d : Infinity
|
|
3699
4078
|
};
|
|
3700
4079
|
filesContainer.appendChild(list);
|
|
3701
4080
|
filesWrapper.appendChild(filesPicker);
|
|
3702
4081
|
filesWrapper.appendChild(filesContainer);
|
|
3703
4082
|
wrapper.appendChild(filesWrapper);
|
|
4083
|
+
const onLibraryPickFiles = state.config.pickExistingFiles && !element.disableLibrary ? () => {
|
|
4084
|
+
handleLibraryPickMulti(
|
|
4085
|
+
state,
|
|
4086
|
+
element,
|
|
4087
|
+
filesWrapper,
|
|
4088
|
+
pathKey,
|
|
4089
|
+
initialFiles,
|
|
4090
|
+
Infinity,
|
|
4091
|
+
updateFilesList,
|
|
4092
|
+
ctx.instance
|
|
4093
|
+
).catch((err) => {
|
|
4094
|
+
console.error("Library pick failed:", err);
|
|
4095
|
+
});
|
|
4096
|
+
} : null;
|
|
3704
4097
|
function updateFilesList() {
|
|
3705
4098
|
const currentlyReadonly = isElementReadonly(element, state);
|
|
3706
|
-
renderResourcePills(
|
|
3707
|
-
list,
|
|
3708
|
-
initialFiles,
|
|
4099
|
+
renderResourcePills({
|
|
4100
|
+
container: list,
|
|
4101
|
+
rids: initialFiles,
|
|
3709
4102
|
state,
|
|
3710
|
-
currentlyReadonly ? null : (ridToRemove) => {
|
|
4103
|
+
onRemove: currentlyReadonly ? null : (ridToRemove) => {
|
|
3711
4104
|
var _a2;
|
|
3712
4105
|
releaseLocalFileUrl((_a2 = state.resourceIndex.get(ridToRemove)) == null ? void 0 : _a2.file);
|
|
3713
4106
|
const index = initialFiles.indexOf(ridToRemove);
|
|
3714
4107
|
if (index > -1) initialFiles.splice(index, 1);
|
|
3715
4108
|
updateFilesList();
|
|
3716
4109
|
},
|
|
3717
|
-
filesFieldHint,
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
);
|
|
4110
|
+
hint: filesFieldHint,
|
|
4111
|
+
isReadonly: currentlyReadonly,
|
|
4112
|
+
onLibraryPick: currentlyReadonly ? null : onLibraryPickFiles
|
|
4113
|
+
});
|
|
3722
4114
|
}
|
|
3723
4115
|
updateFilesList();
|
|
3724
4116
|
setupFilesDropHandler(
|
|
@@ -3741,7 +4133,7 @@ function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3741
4133
|
);
|
|
3742
4134
|
}
|
|
3743
4135
|
function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
3744
|
-
var _a, _b, _c, _d;
|
|
4136
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3745
4137
|
const state = ctx.state;
|
|
3746
4138
|
const minFiles = (_a = element.minCount) != null ? _a : 0;
|
|
3747
4139
|
const maxFiles = (_b = element.maxCount) != null ? _b : Infinity;
|
|
@@ -3754,7 +4146,10 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3754
4146
|
filesPicker.multiple = true;
|
|
3755
4147
|
filesPicker.style.display = "none";
|
|
3756
4148
|
if (element.accept) {
|
|
3757
|
-
filesPicker.accept = typeof element.accept === "string" ? element.accept :
|
|
4149
|
+
filesPicker.accept = typeof element.accept === "string" ? element.accept : [
|
|
4150
|
+
...(_d = (_c = element.accept.extensions) == null ? void 0 : _c.map((ext) => `.${ext}`)) != null ? _d : [],
|
|
4151
|
+
...(_e = element.accept.mime) != null ? _e : []
|
|
4152
|
+
].join(",") || "";
|
|
3758
4153
|
}
|
|
3759
4154
|
const filesContainer = document.createElement("div");
|
|
3760
4155
|
filesContainer.className = "files-list-wrapper";
|
|
@@ -3771,30 +4166,46 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
|
3771
4166
|
const multipleConstraints = {
|
|
3772
4167
|
maxCount: maxFiles,
|
|
3773
4168
|
allowedExtensions: getAllowedExtensions(element.accept),
|
|
3774
|
-
|
|
4169
|
+
allowedMimes: getAllowedMimes(element.accept),
|
|
4170
|
+
maxSize: (_f = element.maxSize) != null ? _f : Infinity
|
|
3775
4171
|
};
|
|
3776
4172
|
const buildCountInfo = () => {
|
|
3777
4173
|
const countText = initialFiles.length === 1 ? t("fileCountSingle", state, { count: initialFiles.length }) : t("fileCountPlural", state, { count: initialFiles.length });
|
|
3778
4174
|
const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
|
|
3779
4175
|
return countText + minMaxText;
|
|
3780
4176
|
};
|
|
4177
|
+
const onLibraryPickMultiple = state.config.pickExistingFiles && !element.disableLibrary ? () => {
|
|
4178
|
+
handleLibraryPickMulti(
|
|
4179
|
+
state,
|
|
4180
|
+
element,
|
|
4181
|
+
filesWrapper,
|
|
4182
|
+
pathKey,
|
|
4183
|
+
initialFiles,
|
|
4184
|
+
maxFiles,
|
|
4185
|
+
updateFilesDisplay,
|
|
4186
|
+
ctx.instance
|
|
4187
|
+
).catch((err) => {
|
|
4188
|
+
console.error("Library pick failed:", err);
|
|
4189
|
+
});
|
|
4190
|
+
} : null;
|
|
3781
4191
|
const updateFilesDisplay = () => {
|
|
3782
4192
|
const currentlyReadonly = isElementReadonly(element, state);
|
|
3783
|
-
renderResourcePills(
|
|
3784
|
-
list,
|
|
3785
|
-
initialFiles,
|
|
4193
|
+
renderResourcePills({
|
|
4194
|
+
container: list,
|
|
4195
|
+
rids: initialFiles,
|
|
3786
4196
|
state,
|
|
3787
|
-
currentlyReadonly ? null : (index) => {
|
|
4197
|
+
onRemove: currentlyReadonly ? null : (index) => {
|
|
3788
4198
|
var _a2;
|
|
3789
4199
|
releaseLocalFileUrl((_a2 = state.resourceIndex.get(index)) == null ? void 0 : _a2.file);
|
|
3790
4200
|
initialFiles.splice(initialFiles.indexOf(index), 1);
|
|
3791
4201
|
updateFilesDisplay();
|
|
3792
4202
|
},
|
|
3793
|
-
multipleFilesHint,
|
|
3794
|
-
buildCountInfo(),
|
|
3795
|
-
maxFiles < Infinity ? maxFiles : void 0,
|
|
3796
|
-
currentlyReadonly
|
|
3797
|
-
|
|
4203
|
+
hint: multipleFilesHint,
|
|
4204
|
+
countInfo: buildCountInfo(),
|
|
4205
|
+
maxCount: maxFiles < Infinity ? maxFiles : void 0,
|
|
4206
|
+
isReadonly: currentlyReadonly,
|
|
4207
|
+
onLibraryPick: currentlyReadonly ? null : onLibraryPickMultiple
|
|
4208
|
+
});
|
|
3798
4209
|
};
|
|
3799
4210
|
setupFilesDropHandler(
|
|
3800
4211
|
filesContainer,
|
|
@@ -3852,19 +4263,30 @@ function validateFileCount(key, resourceIds, element, state, errors) {
|
|
|
3852
4263
|
errors.push(`${key}: ${t("maxFiles", state, { max: maxFiles })}`);
|
|
3853
4264
|
}
|
|
3854
4265
|
}
|
|
3855
|
-
function
|
|
3856
|
-
var _a;
|
|
4266
|
+
function validateFileTypes(key, resourceIds, element, state, errors) {
|
|
4267
|
+
var _a, _b;
|
|
3857
4268
|
const acceptField = "accept" in element ? element.accept : void 0;
|
|
3858
4269
|
const allowedExtensions = getAllowedExtensions(acceptField);
|
|
3859
|
-
|
|
4270
|
+
const allowedMimes = getAllowedMimes(acceptField);
|
|
4271
|
+
if (allowedExtensions.length === 0 && allowedMimes.length === 0) return;
|
|
3860
4272
|
const formats = allowedExtensions.join(", ");
|
|
4273
|
+
const mimes = allowedMimes.join(", ");
|
|
3861
4274
|
for (const rid of resourceIds) {
|
|
3862
4275
|
const meta = state.resourceIndex.get(rid);
|
|
3863
4276
|
const fileName = (_a = meta == null ? void 0 : meta.name) != null ? _a : rid;
|
|
3864
|
-
if (!isFileExtensionAllowed(fileName, allowedExtensions)) {
|
|
4277
|
+
if (allowedExtensions.length > 0 && !isFileExtensionAllowed(fileName, allowedExtensions)) {
|
|
3865
4278
|
errors.push(
|
|
3866
4279
|
`${key}: ${t("invalidFileExtension", state, { name: fileName, formats })}`
|
|
3867
4280
|
);
|
|
4281
|
+
continue;
|
|
4282
|
+
}
|
|
4283
|
+
if (allowedMimes.length > 0 && !(meta == null ? void 0 : meta.inferredFromExtension)) {
|
|
4284
|
+
const mimeType = (_b = meta == null ? void 0 : meta.type) != null ? _b : "";
|
|
4285
|
+
if (!isMimeAllowed(mimeType, allowedMimes)) {
|
|
4286
|
+
errors.push(
|
|
4287
|
+
`${key}: ${t("invalidFileMime", state, { name: fileName, type: mimeType, mimes })}`
|
|
4288
|
+
);
|
|
4289
|
+
}
|
|
3868
4290
|
}
|
|
3869
4291
|
}
|
|
3870
4292
|
}
|
|
@@ -3889,7 +4311,7 @@ function validateMultiFile(element, key, context) {
|
|
|
3889
4311
|
const resourceIds = readMultiFileResourceIds(scopeRoot, fullKey);
|
|
3890
4312
|
if (!skipValidation) {
|
|
3891
4313
|
validateFileCount(key, resourceIds, element, state, errors);
|
|
3892
|
-
|
|
4314
|
+
validateFileTypes(key, resourceIds, element, state, errors);
|
|
3893
4315
|
validateFileSizes(key, resourceIds, element, state, errors);
|
|
3894
4316
|
}
|
|
3895
4317
|
return { value: resourceIds, errors };
|
|
@@ -3907,7 +4329,7 @@ function validateSingleFile(element, key, context) {
|
|
|
3907
4329
|
return { value: null, errors };
|
|
3908
4330
|
}
|
|
3909
4331
|
if (!skipValidation && rid !== "") {
|
|
3910
|
-
|
|
4332
|
+
validateFileTypes(key, [rid], element, state, errors);
|
|
3911
4333
|
validateFileSizes(key, [rid], element, state, errors);
|
|
3912
4334
|
}
|
|
3913
4335
|
return { value: rid || null, errors };
|
|
@@ -4033,9 +4455,7 @@ function updateFileField(element, fieldPath, value, context) {
|
|
|
4033
4455
|
}
|
|
4034
4456
|
value.forEach((resourceId) => {
|
|
4035
4457
|
if (resourceId && typeof resourceId === "string") {
|
|
4036
|
-
|
|
4037
|
-
addResourceToIndex(resourceId, state);
|
|
4038
|
-
}
|
|
4458
|
+
seedInferredResource(resourceId, state.resourceIndex);
|
|
4039
4459
|
}
|
|
4040
4460
|
});
|
|
4041
4461
|
const filesWrapper = scopeRoot.querySelector(
|
|
@@ -4060,35 +4480,13 @@ function updateFileField(element, fieldPath, value, context) {
|
|
|
4060
4480
|
}
|
|
4061
4481
|
hiddenInput.value = value != null ? String(value) : "";
|
|
4062
4482
|
if (value && typeof value === "string") {
|
|
4063
|
-
|
|
4064
|
-
addResourceToIndex(value, state);
|
|
4065
|
-
}
|
|
4483
|
+
seedInferredResource(value, state.resourceIndex);
|
|
4066
4484
|
console.info(
|
|
4067
4485
|
`updateFileField: File field "${fieldPath}" updated. Preview update requires re-render.`
|
|
4068
4486
|
);
|
|
4069
4487
|
}
|
|
4070
4488
|
}
|
|
4071
4489
|
}
|
|
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
4490
|
|
|
4093
4491
|
// src/components/colour.ts
|
|
4094
4492
|
function normalizeColourValue(value) {
|
|
@@ -8825,6 +9223,7 @@ var defaultConfig = {
|
|
|
8825
9223
|
enableFilePreview: true,
|
|
8826
9224
|
maxPreviewSize: "200px",
|
|
8827
9225
|
readonly: false,
|
|
9226
|
+
pickExistingFiles: null,
|
|
8828
9227
|
parseTableFile: null,
|
|
8829
9228
|
locale: "en",
|
|
8830
9229
|
translations: {
|
|
@@ -8861,6 +9260,10 @@ var defaultConfig = {
|
|
|
8861
9260
|
fileCountRange: "({min}-{max})",
|
|
8862
9261
|
uploadingFile: "Uploading\u2026",
|
|
8863
9262
|
filesCounter: "{count}/{max}",
|
|
9263
|
+
fromLibrary: "From library",
|
|
9264
|
+
libraryEmpty: "Library is empty",
|
|
9265
|
+
libraryHint: "Choose from previously uploaded files",
|
|
9266
|
+
pickerError: "Failed to load files from library",
|
|
8864
9267
|
// Validation errors
|
|
8865
9268
|
required: "Required",
|
|
8866
9269
|
minItems: "Minimum {min} items required",
|
|
@@ -8876,6 +9279,7 @@ var defaultConfig = {
|
|
|
8876
9279
|
minFiles: "Minimum {min} files required",
|
|
8877
9280
|
maxFiles: "Maximum {max} files allowed",
|
|
8878
9281
|
invalidFileExtension: 'File "{name}" has unsupported format. Allowed: {formats}',
|
|
9282
|
+
invalidFileMime: 'File "{name}": file type {type} not allowed (allowed: {mimes})',
|
|
8879
9283
|
fileTooLarge: 'File "{name}" exceeds maximum size of {maxSize}MB',
|
|
8880
9284
|
filesLimitExceeded: "{skipped} file(s) skipped: maximum {max} files allowed",
|
|
8881
9285
|
unsupportedFieldType: "Unsupported field type: {type}",
|
|
@@ -8927,6 +9331,10 @@ var defaultConfig = {
|
|
|
8927
9331
|
fileCountRange: "({min}-{max})",
|
|
8928
9332
|
uploadingFile: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026",
|
|
8929
9333
|
filesCounter: "{count}/{max}",
|
|
9334
|
+
fromLibrary: "\u0418\u0437 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438",
|
|
9335
|
+
libraryEmpty: "\u0411\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0430 \u043F\u0443\u0441\u0442\u0430",
|
|
9336
|
+
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",
|
|
9337
|
+
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
9338
|
// Validation errors
|
|
8931
9339
|
required: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435",
|
|
8932
9340
|
minItems: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",
|
|
@@ -8942,6 +9350,7 @@ var defaultConfig = {
|
|
|
8942
9350
|
minFiles: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8943
9351
|
maxFiles: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
8944
9352
|
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}',
|
|
9353
|
+
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
9354
|
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
9355
|
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
9356
|
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}",
|