@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.
@@ -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 isFileSizeAllowed(file, maxSizeMB) {
2139
+ function isSizeWithinLimit(bytes, maxSizeMB) {
2140
2140
  if (maxSizeMB === Infinity) return true;
2141
- return file.size <= maxSizeMB * 1024 * 1024;
2141
+ return bytes <= maxSizeMB * 1024 * 1024;
2142
2142
  }
2143
- function addPrefillFilesToIndex(initialFiles, resourceIndex) {
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
- if (resourceIndex.has(resourceId)) continue;
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(file, container, fieldName, state, deps = null, instance, allowedExtensions = [], maxSizeMB = Infinity) {
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 rejectedBySize = afterExt.filter(
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 = afterExt.filter((f) => isFileSizeAllowed(f, constraints.maxSize));
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/render-edit.ts
3449
- function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps) {
3584
+ // src/components/file/library.ts
3585
+ function buildAcceptContext(element) {
3450
3586
  var _a, _b;
3451
- if (!state.resourceIndex.has(initial)) {
3452
- const filename = initial.split("/").pop() || "file";
3453
- const extension = (_a = filename.split(".").pop()) == null ? void 0 : _a.toLowerCase();
3454
- let fileType = "application/octet-stream";
3455
- if (extension) {
3456
- if (["jpg", "jpeg", "png", "gif", "webp"].includes(extension)) {
3457
- fileType = `image/${extension === "jpg" ? "jpeg" : extension}`;
3458
- } else if (["mp4", "webm", "mov", "avi"].includes(extension)) {
3459
- fileType = `video/${extension === "mov" ? "quicktime" : extension}`;
3460
- }
3461
- }
3462
- state.resourceIndex.set(initial, {
3463
- name: filename,
3464
- type: fileType,
3465
- size: 0,
3466
- uploadedAt: /* @__PURE__ */ new Date(),
3467
- file: void 0
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 = (_b = meta == null ? void 0 : meta.type) == null ? void 0 : _b.startsWith("video/");
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
- function renderResourcePills(container, rids, state, onRemove, hint, countInfo, maxCount, isReadonly = false) {
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 = document.createElement("div");
3515
- dropzone.className = "fb-file-dropzone";
3516
- const subHint2 = buildSubHint();
3517
- dropzone.innerHTML = `
3518
- <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" style="flex-shrink:0;color:var(--fb-file-upload-text-color,#9ca3af);">
3519
- <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"/>
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 : ((_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`).join(",")) || "";
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 maxSizeMB = (_b = element.maxSize) != null ? _b : Infinity;
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 ((_c = prefillMeta == null ? void 0 : prefillMeta.type) == null ? void 0 : _c.startsWith("video/")) {
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
- handlers.restoreDropzone();
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 : ((_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`).join(",")) || "";
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
- maxSize: (_b = element.maxSize) != null ? _b : Infinity
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
- void 0,
3719
- void 0,
3720
- currentlyReadonly
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 : ((_c = element.accept.extensions) == null ? void 0 : _c.map((ext) => `.${ext}`).join(",")) || "";
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
- maxSize: (_d = element.maxSize) != null ? _d : Infinity
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 validateFileExtensions(key, resourceIds, element, state, errors) {
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
- if (allowedExtensions.length === 0) return;
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
- validateFileExtensions(key, resourceIds, element, state, errors);
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
- validateFileExtensions(key, [rid], element, state, errors);
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
- if (!state.resourceIndex.has(resourceId)) {
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
- if (!state.resourceIndex.has(value)) {
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}",