@dmitryvim/form-builder 0.2.30 → 0.2.34

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.
@@ -50,8 +50,10 @@ function addRangeHint(element, parts, state) {
50
50
  }
51
51
  }
52
52
  function addFileSizeHint(element, parts, state) {
53
- if (element.maxSizeMB) {
54
- parts.push(t("hintMaxSize", state, { size: element.maxSizeMB }));
53
+ var _a;
54
+ const sizeMB = (_a = element.maxSize) != null ? _a : element.maxSizeMB;
55
+ if (sizeMB && sizeMB !== Infinity) {
56
+ parts.push(t("hintMaxSize", state, { size: sizeMB }));
55
57
  }
56
58
  }
57
59
  function addFormatHint(element, parts, state) {
@@ -125,6 +127,25 @@ function validateSchema(schema) {
125
127
  });
126
128
  }
127
129
  }
130
+ function validateContainerProps(element, elementPath, errors2) {
131
+ if ("columns" in element && element.columns !== void 0) {
132
+ const columns = element.columns;
133
+ const validColumns = [1, 2, 3, 4];
134
+ if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
135
+ errors2.push(
136
+ `${elementPath}: columns must be 1, 2, 3, or 4 (got ${columns})`
137
+ );
138
+ }
139
+ }
140
+ if ("displayMode" in element && element.displayMode !== void 0) {
141
+ const displayMode = element.displayMode;
142
+ if (displayMode !== "stack" && displayMode !== "slides") {
143
+ errors2.push(
144
+ `${elementPath}: displayMode must be "stack" or "slides" (got ${JSON.stringify(displayMode)})`
145
+ );
146
+ }
147
+ }
148
+ }
128
149
  function checkFlatOutputCollisions(elements, scopePath) {
129
150
  var _a, _b;
130
151
  const allOutputKeys = /* @__PURE__ */ new Set();
@@ -205,15 +226,7 @@ function validateSchema(schema) {
205
226
  validateElements(element.elements, `${elementPath}.elements`);
206
227
  }
207
228
  if (element.type === "container" && element.elements) {
208
- if ("columns" in element && element.columns !== void 0) {
209
- const columns = element.columns;
210
- const validColumns = [1, 2, 3, 4];
211
- if (!Number.isInteger(columns) || !validColumns.includes(columns)) {
212
- errors.push(
213
- `${elementPath}: columns must be 1, 2, 3, or 4 (got ${columns})`
214
- );
215
- }
216
- }
229
+ validateContainerProps(element, elementPath, errors);
217
230
  if ("prefillHints" in element && element.prefillHints) {
218
231
  const prefillHints = element.prefillHints;
219
232
  if (Array.isArray(prefillHints)) {
@@ -393,6 +406,169 @@ function deepEqual(a, b) {
393
406
  return a === b;
394
407
  }
395
408
 
409
+ // src/utils/styles.ts
410
+ function mountCounterInLabel(wrapper, counter) {
411
+ const labelRow = wrapper.querySelector(
412
+ ":scope > [data-fb-label-row]"
413
+ );
414
+ if (labelRow) labelRow.appendChild(counter);
415
+ }
416
+ function createAddItemRow(classNameSuffix, onClick, options = {}) {
417
+ var _a;
418
+ const label = (_a = options.label) != null ? _a : "";
419
+ const showCounter = options.showCounter !== false;
420
+ const row = document.createElement("div");
421
+ row.className = "fb-add-row mt-2";
422
+ row.style.cssText = "display:flex;align-items:stretch;width:100%;";
423
+ const button = document.createElement("button");
424
+ button.type = "button";
425
+ button.className = `add-${classNameSuffix}-btn`;
426
+ button.style.cssText = `
427
+ flex: 1 1 auto;
428
+ display: inline-flex;
429
+ align-items: center;
430
+ justify-content: center;
431
+ gap: 6px;
432
+ padding: 6px 10px;
433
+ border: 1px dashed var(--fb-primary-color);
434
+ border-radius: var(--fb-border-radius);
435
+ background: transparent;
436
+ color: var(--fb-primary-color);
437
+ font-size: var(--fb-font-size-small, var(--fb-font-size));
438
+ font-weight: 500;
439
+ font-family: var(--fb-font-family);
440
+ cursor: pointer;
441
+ transition: border-color var(--fb-transition-duration), color var(--fb-transition-duration), background-color var(--fb-transition-duration);
442
+ `;
443
+ button.textContent = label ? `+ ${label}` : "+";
444
+ button.addEventListener("mouseenter", () => {
445
+ if (button.disabled) return;
446
+ button.style.borderStyle = "solid";
447
+ button.style.backgroundColor = "var(--fb-background-hover-color)";
448
+ });
449
+ button.addEventListener("mouseleave", () => {
450
+ button.style.borderStyle = "dashed";
451
+ button.style.backgroundColor = "transparent";
452
+ });
453
+ button.onclick = onClick;
454
+ const counter = document.createElement("span");
455
+ counter.className = "fb-add-counter";
456
+ counter.style.cssText = `
457
+ margin-left: auto;
458
+ font-size: var(--fb-font-size-small, 0.875rem);
459
+ color: var(--fb-text-secondary-color);
460
+ font-weight: 400;
461
+ `;
462
+ if (!showCounter) counter.style.display = "none";
463
+ row.appendChild(button);
464
+ const update = (current, max) => {
465
+ const reached = current >= max;
466
+ row.style.display = reached ? "none" : "flex";
467
+ button.style.display = reached ? "none" : "inline-flex";
468
+ button.disabled = reached;
469
+ if (showCounter) {
470
+ counter.textContent = `${current}/${max === Infinity ? "\u221E" : max}`;
471
+ }
472
+ };
473
+ return { row, button, counter, update };
474
+ }
475
+ function createSlideAddTile(onClick, options = {}) {
476
+ var _a;
477
+ const label = (_a = options.label) != null ? _a : "";
478
+ const tile = document.createElement("button");
479
+ tile.type = "button";
480
+ tile.className = "add-container-btn fb-slide-add";
481
+ tile.style.cssText = `
482
+ display: flex;
483
+ flex-direction: column;
484
+ align-items: center;
485
+ justify-content: center;
486
+ gap: 12px;
487
+ width: 100%;
488
+ min-height: 180px;
489
+ align-self: stretch;
490
+ padding: 24px 16px;
491
+ border: 1.5px dashed var(--fb-primary-color);
492
+ border-radius: var(--fb-border-radius);
493
+ background: transparent;
494
+ color: var(--fb-primary-color);
495
+ font-size: var(--fb-font-size-small, var(--fb-font-size));
496
+ font-weight: 500;
497
+ font-family: var(--fb-font-family);
498
+ cursor: pointer;
499
+ transition: border-color var(--fb-transition-duration), color var(--fb-transition-duration), background-color var(--fb-transition-duration);
500
+ `;
501
+ const circle = document.createElement("span");
502
+ circle.className = "fb-slide-add-circle";
503
+ circle.style.cssText = `
504
+ display: inline-flex;
505
+ align-items: center;
506
+ justify-content: center;
507
+ width: 36px;
508
+ height: 36px;
509
+ border: 1px solid var(--fb-primary-color);
510
+ border-radius: 50%;
511
+ background: var(--fb-background-color);
512
+ font-size: 20px;
513
+ line-height: 1;
514
+ color: inherit;
515
+ transition: inherit;
516
+ `;
517
+ circle.textContent = "+";
518
+ tile.appendChild(circle);
519
+ if (label) {
520
+ const text = document.createElement("span");
521
+ text.textContent = label;
522
+ tile.appendChild(text);
523
+ }
524
+ tile.addEventListener("mouseenter", () => {
525
+ if (tile.disabled) return;
526
+ tile.style.borderStyle = "solid";
527
+ tile.style.backgroundColor = "var(--fb-background-hover-color)";
528
+ });
529
+ tile.addEventListener("mouseleave", () => {
530
+ tile.style.borderStyle = "dashed";
531
+ tile.style.backgroundColor = "transparent";
532
+ });
533
+ tile.onclick = onClick;
534
+ const counter = document.createElement("span");
535
+ counter.className = "fb-add-counter";
536
+ counter.style.cssText = `
537
+ margin-left: auto;
538
+ font-size: var(--fb-font-size-small, 0.875rem);
539
+ color: var(--fb-text-secondary-color);
540
+ font-weight: 400;
541
+ `;
542
+ const update = (current, max) => {
543
+ const reached = current >= max;
544
+ tile.style.display = reached ? "none" : "flex";
545
+ tile.disabled = reached;
546
+ counter.textContent = `${current}/${max === Infinity ? "\u221E" : max}`;
547
+ };
548
+ return { tile, counter, update };
549
+ }
550
+ function applyActionButtonStyles(button, isFormLevel = false) {
551
+ button.style.cssText = `
552
+ background-color: var(--fb-action-bg-color);
553
+ color: var(--fb-action-text-color);
554
+ border: var(--fb-border-width) solid var(--fb-action-border-color);
555
+ padding: ${isFormLevel ? "0.5rem 1rem" : "0.5rem 0.75rem"};
556
+ font-size: var(--fb-font-size);
557
+ font-weight: var(--fb-font-weight-medium);
558
+ border-radius: var(--fb-border-radius);
559
+ transition: all var(--fb-transition-duration);
560
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
561
+ `;
562
+ button.addEventListener("mouseenter", () => {
563
+ button.style.backgroundColor = "var(--fb-action-hover-bg-color)";
564
+ button.style.borderColor = "var(--fb-action-hover-border-color)";
565
+ });
566
+ button.addEventListener("mouseleave", () => {
567
+ button.style.backgroundColor = "var(--fb-action-bg-color)";
568
+ button.style.borderColor = "var(--fb-action-border-color)";
569
+ });
570
+ }
571
+
396
572
  // src/components/text.ts
397
573
  function createCharCounter(element, input, isTextarea = false) {
398
574
  const counter = document.createElement("span");
@@ -445,12 +621,13 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
445
621
  const readonly = isElementReadonly(element, state, ctx);
446
622
  const inputWrapper = document.createElement("div");
447
623
  inputWrapper.style.cssText = "position: relative;";
624
+ const hasCharCounter = !readonly && (element.minLength != null || element.maxLength != null);
448
625
  const textInput = document.createElement("input");
449
626
  textInput.type = "text";
450
627
  textInput.className = "w-full rounded-lg";
451
628
  textInput.style.cssText = `
452
629
  padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
453
- padding-right: 60px;
630
+ ${hasCharCounter ? "padding-right: 60px;" : ""}
454
631
  border: var(--fb-border-width) solid var(--fb-border-color);
455
632
  border-radius: var(--fb-border-radius);
456
633
  background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
@@ -495,7 +672,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
495
672
  textInput.addEventListener("input", handleChange);
496
673
  }
497
674
  inputWrapper.appendChild(textInput);
498
- if (!readonly && (element.minLength != null || element.maxLength != null)) {
675
+ if (hasCharCounter) {
499
676
  const counter = createCharCounter(element, textInput, false);
500
677
  inputWrapper.appendChild(counter);
501
678
  }
@@ -507,6 +684,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
507
684
  const readonly = isElementReadonly(element, state, ctx);
508
685
  const prefillValues = ctx.prefill[element.key] || [];
509
686
  const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
687
+ const hasCharCounter = !readonly && (element.minLength != null || element.maxLength != null);
510
688
  const minCount = (_a = element.minCount) != null ? _a : 1;
511
689
  const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
512
690
  while (values.length < minCount) {
@@ -533,7 +711,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
533
711
  textInput.type = "text";
534
712
  textInput.style.cssText = `
535
713
  padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
536
- padding-right: 60px;
714
+ ${hasCharCounter ? "padding-right: 60px;" : ""}
537
715
  border: var(--fb-border-width) solid var(--fb-border-color);
538
716
  border-radius: var(--fb-border-radius);
539
717
  background-color: ${readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
@@ -577,7 +755,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
577
755
  textInput.addEventListener("input", handleChange);
578
756
  }
579
757
  inputContainer.appendChild(textInput);
580
- if (!readonly && (element.minLength != null || element.maxLength != null)) {
758
+ if (hasCharCounter) {
581
759
  const counter = createCharCounter(element, textInput, false);
582
760
  inputContainer.appendChild(counter);
583
761
  }
@@ -634,50 +812,24 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
634
812
  removeBtn.style.pointerEvents = disabled ? "none" : "auto";
635
813
  });
636
814
  }
637
- let addRow = null;
638
- let countDisplay = null;
815
+ let addUpdate = null;
639
816
  if (!readonly) {
640
- addRow = document.createElement("div");
641
- addRow.className = "flex items-center gap-3 mt-2";
642
- const addBtn = document.createElement("button");
643
- addBtn.type = "button";
644
- addBtn.className = "add-text-btn px-3 py-1 rounded";
645
- addBtn.style.cssText = `
646
- color: var(--fb-primary-color);
647
- border: var(--fb-border-width) solid var(--fb-primary-color);
648
- background-color: transparent;
649
- font-size: var(--fb-font-size);
650
- transition: all var(--fb-transition-duration);
651
- `;
652
- addBtn.textContent = "+";
653
- addBtn.addEventListener("mouseenter", () => {
654
- addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
655
- });
656
- addBtn.addEventListener("mouseleave", () => {
657
- addBtn.style.backgroundColor = "transparent";
658
- });
659
- addBtn.onclick = () => {
660
- values.push(element.default || "");
661
- addTextItem(element.default || "");
662
- updateAddButton();
663
- updateRemoveButtons();
664
- };
665
- countDisplay = document.createElement("span");
666
- countDisplay.className = "text-sm text-gray-500";
667
- addRow.appendChild(addBtn);
668
- addRow.appendChild(countDisplay);
669
- wrapper.appendChild(addRow);
817
+ const handle = createAddItemRow(
818
+ "text",
819
+ () => {
820
+ values.push(element.default || "");
821
+ addTextItem(element.default || "");
822
+ updateAddButton();
823
+ updateRemoveButtons();
824
+ },
825
+ { label: element.addLabel }
826
+ );
827
+ addUpdate = handle.update;
828
+ mountCounterInLabel(wrapper, handle.counter);
829
+ wrapper.appendChild(handle.row);
670
830
  }
671
831
  function updateAddButton() {
672
- if (!addRow || !countDisplay) return;
673
- const addBtn = addRow.querySelector(".add-text-btn");
674
- if (addBtn) {
675
- const disabled = values.length >= maxCount;
676
- addBtn.disabled = disabled;
677
- addBtn.style.opacity = disabled ? "0.5" : "1";
678
- addBtn.style.pointerEvents = disabled ? "none" : "auto";
679
- }
680
- countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
832
+ if (addUpdate) addUpdate(values.length, maxCount);
681
833
  }
682
834
  values.forEach((value) => addTextItem(value));
683
835
  updateAddButton();
@@ -851,8 +1003,12 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
851
1003
  const textareaWrapper = document.createElement("div");
852
1004
  textareaWrapper.style.cssText = "position: relative;";
853
1005
  const textareaInput = document.createElement("textarea");
854
- textareaInput.className = "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
855
- textareaInput.style.cssText = "padding-bottom: 24px;";
1006
+ textareaInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
1007
+ textareaInput.style.cssText = `
1008
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x) 24px var(--fb-input-padding-x);
1009
+ font-size: var(--fb-font-size);
1010
+ font-family: var(--fb-font-family);
1011
+ `;
856
1012
  textareaInput.name = pathKey;
857
1013
  textareaInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
858
1014
  textareaInput.rows = element.rows || 4;
@@ -905,8 +1061,12 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
905
1061
  const textareaContainer = document.createElement("div");
906
1062
  textareaContainer.style.cssText = "position: relative;";
907
1063
  const textareaInput = document.createElement("textarea");
908
- textareaInput.className = "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
909
- textareaInput.style.cssText = "padding-bottom: 24px;";
1064
+ textareaInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
1065
+ textareaInput.style.cssText = `
1066
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x) 24px var(--fb-input-padding-x);
1067
+ font-size: var(--fb-font-size);
1068
+ font-family: var(--fb-font-family);
1069
+ `;
910
1070
  textareaInput.placeholder = element.placeholder || t("placeholderText", state);
911
1071
  textareaInput.rows = element.rows || 4;
912
1072
  textareaInput.value = value;
@@ -969,52 +1129,24 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
969
1129
  removeBtn.style.pointerEvents = disabled ? "none" : "auto";
970
1130
  });
971
1131
  }
972
- let addRow = null;
973
- let countDisplay = null;
1132
+ let addUpdate = null;
974
1133
  if (!readonly) {
975
- addRow = document.createElement("div");
976
- addRow.className = "flex items-center gap-3 mt-2";
977
- const addBtn = document.createElement("button");
978
- addBtn.type = "button";
979
- addBtn.className = "add-textarea-btn px-3 py-1 rounded";
980
- addBtn.style.cssText = `
981
- color: var(--fb-primary-color);
982
- border: var(--fb-border-width) solid var(--fb-primary-color);
983
- background-color: transparent;
984
- font-size: var(--fb-font-size);
985
- transition: all var(--fb-transition-duration);
986
- `;
987
- addBtn.textContent = "+";
988
- addBtn.addEventListener("mouseenter", () => {
989
- addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
990
- });
991
- addBtn.addEventListener("mouseleave", () => {
992
- addBtn.style.backgroundColor = "transparent";
993
- });
994
- addBtn.onclick = () => {
995
- values.push(element.default || "");
996
- addTextareaItem(element.default || "");
997
- updateAddButton();
998
- updateRemoveButtons();
999
- };
1000
- countDisplay = document.createElement("span");
1001
- countDisplay.className = "text-sm text-gray-500";
1002
- addRow.appendChild(addBtn);
1003
- addRow.appendChild(countDisplay);
1004
- wrapper.appendChild(addRow);
1134
+ const handle = createAddItemRow(
1135
+ "textarea",
1136
+ () => {
1137
+ values.push(element.default || "");
1138
+ addTextareaItem(element.default || "");
1139
+ updateAddButton();
1140
+ updateRemoveButtons();
1141
+ },
1142
+ { label: element.addLabel }
1143
+ );
1144
+ addUpdate = handle.update;
1145
+ mountCounterInLabel(wrapper, handle.counter);
1146
+ wrapper.appendChild(handle.row);
1005
1147
  }
1006
1148
  function updateAddButton() {
1007
- if (!addRow || !countDisplay) return;
1008
- const addBtn = addRow.querySelector(
1009
- ".add-textarea-btn"
1010
- );
1011
- if (addBtn) {
1012
- const disabled = values.length >= maxCount;
1013
- addBtn.disabled = disabled;
1014
- addBtn.style.opacity = disabled ? "0.5" : "1";
1015
- addBtn.style.pointerEvents = disabled ? "none" : "auto";
1016
- }
1017
- countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
1149
+ if (addUpdate) addUpdate(values.length, maxCount);
1018
1150
  }
1019
1151
  values.forEach((value) => addTextareaItem(value));
1020
1152
  updateAddButton();
@@ -1092,8 +1224,14 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1092
1224
  inputWrapper.style.cssText = "position: relative;";
1093
1225
  const numberInput = document.createElement("input");
1094
1226
  numberInput.type = "number";
1095
- numberInput.className = "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1096
- numberInput.style.cssText = "padding-right: 60px; width: 100%; box-sizing: border-box;";
1227
+ numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1228
+ numberInput.style.cssText = `
1229
+ padding: var(--fb-input-padding-y) 60px var(--fb-input-padding-y) var(--fb-input-padding-x);
1230
+ font-size: var(--fb-font-size);
1231
+ font-family: var(--fb-font-family);
1232
+ width: 100%;
1233
+ box-sizing: border-box;
1234
+ `;
1097
1235
  numberInput.name = pathKey;
1098
1236
  numberInput.placeholder = element.placeholder || "0";
1099
1237
  if (element.min !== void 0) numberInput.min = element.min.toString();
@@ -1146,8 +1284,14 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1146
1284
  inputContainer.style.cssText = "position: relative; flex: 1;";
1147
1285
  const numberInput = document.createElement("input");
1148
1286
  numberInput.type = "number";
1149
- numberInput.className = "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1150
- numberInput.style.cssText = "padding-right: 60px; width: 100%; box-sizing: border-box;";
1287
+ numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1288
+ numberInput.style.cssText = `
1289
+ padding: var(--fb-input-padding-y) 60px var(--fb-input-padding-y) var(--fb-input-padding-x);
1290
+ font-size: var(--fb-font-size);
1291
+ font-family: var(--fb-font-family);
1292
+ width: 100%;
1293
+ box-sizing: border-box;
1294
+ `;
1151
1295
  numberInput.placeholder = element.placeholder || "0";
1152
1296
  if (element.min !== void 0) numberInput.min = element.min.toString();
1153
1297
  if (element.max !== void 0) numberInput.max = element.max.toString();
@@ -1209,50 +1353,24 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1209
1353
  removeBtn.style.pointerEvents = disabled ? "none" : "auto";
1210
1354
  });
1211
1355
  }
1212
- let addRow = null;
1213
- let countDisplay = null;
1356
+ let addUpdate = null;
1214
1357
  if (!readonly) {
1215
- addRow = document.createElement("div");
1216
- addRow.className = "flex items-center gap-3 mt-2";
1217
- const addBtn = document.createElement("button");
1218
- addBtn.type = "button";
1219
- addBtn.className = "add-number-btn px-3 py-1 rounded";
1220
- addBtn.style.cssText = `
1221
- color: var(--fb-primary-color);
1222
- border: var(--fb-border-width) solid var(--fb-primary-color);
1223
- background-color: transparent;
1224
- font-size: var(--fb-font-size);
1225
- transition: all var(--fb-transition-duration);
1226
- `;
1227
- addBtn.textContent = "+";
1228
- addBtn.addEventListener("mouseenter", () => {
1229
- addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
1230
- });
1231
- addBtn.addEventListener("mouseleave", () => {
1232
- addBtn.style.backgroundColor = "transparent";
1233
- });
1234
- addBtn.onclick = () => {
1235
- values.push(element.default || "");
1236
- addNumberItem(element.default || "");
1237
- updateAddButton();
1238
- updateRemoveButtons();
1239
- };
1240
- countDisplay = document.createElement("span");
1241
- countDisplay.className = "text-sm text-gray-500";
1242
- addRow.appendChild(addBtn);
1243
- addRow.appendChild(countDisplay);
1244
- wrapper.appendChild(addRow);
1358
+ const handle = createAddItemRow(
1359
+ "number",
1360
+ () => {
1361
+ values.push(element.default || "");
1362
+ addNumberItem(element.default || "");
1363
+ updateAddButton();
1364
+ updateRemoveButtons();
1365
+ },
1366
+ { label: element.addLabel }
1367
+ );
1368
+ addUpdate = handle.update;
1369
+ mountCounterInLabel(wrapper, handle.counter);
1370
+ wrapper.appendChild(handle.row);
1245
1371
  }
1246
1372
  function updateAddButton() {
1247
- if (!addRow || !countDisplay) return;
1248
- const addBtn = addRow.querySelector(".add-number-btn");
1249
- if (addBtn) {
1250
- const disabled = values.length >= maxCount;
1251
- addBtn.disabled = disabled;
1252
- addBtn.style.opacity = disabled ? "0.5" : "1";
1253
- addBtn.style.pointerEvents = disabled ? "none" : "auto";
1254
- }
1255
- countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
1373
+ if (addUpdate) addUpdate(values.length, maxCount);
1256
1374
  }
1257
1375
  values.forEach((value) => addNumberItem(value));
1258
1376
  updateAddButton();
@@ -1421,7 +1539,12 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
1421
1539
  const state = ctx.state;
1422
1540
  const readonly = isElementReadonly(element, state, ctx);
1423
1541
  const selectInput = document.createElement("select");
1424
- selectInput.className = "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1542
+ selectInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1543
+ selectInput.style.cssText = `
1544
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
1545
+ font-size: var(--fb-font-size);
1546
+ font-family: var(--fb-font-family);
1547
+ `;
1425
1548
  selectInput.name = pathKey;
1426
1549
  selectInput.disabled = readonly;
1427
1550
  (element.options || []).forEach((option) => {
@@ -1474,7 +1597,12 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1474
1597
  const itemWrapper = document.createElement("div");
1475
1598
  itemWrapper.className = "multiple-select-item flex items-center gap-2";
1476
1599
  const selectInput = document.createElement("select");
1477
- selectInput.className = "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1600
+ selectInput.className = "flex-1 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1601
+ selectInput.style.cssText = `
1602
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
1603
+ font-size: var(--fb-font-size);
1604
+ font-family: var(--fb-font-family);
1605
+ `;
1478
1606
  selectInput.disabled = readonly;
1479
1607
  (element.options || []).forEach((option) => {
1480
1608
  const optionElement = document.createElement("option");
@@ -1531,52 +1659,26 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1531
1659
  removeBtn.style.pointerEvents = disabled ? "none" : "auto";
1532
1660
  });
1533
1661
  }
1534
- let addRow = null;
1535
- let countDisplay = null;
1662
+ let addUpdate = null;
1536
1663
  if (!readonly) {
1537
- addRow = document.createElement("div");
1538
- addRow.className = "flex items-center gap-3 mt-2";
1539
- const addBtn = document.createElement("button");
1540
- addBtn.type = "button";
1541
- addBtn.className = "add-select-btn px-3 py-1 rounded";
1542
- addBtn.style.cssText = `
1543
- color: var(--fb-primary-color);
1544
- border: var(--fb-border-width) solid var(--fb-primary-color);
1545
- background-color: transparent;
1546
- font-size: var(--fb-font-size);
1547
- transition: all var(--fb-transition-duration);
1548
- `;
1549
- addBtn.textContent = "+";
1550
- addBtn.addEventListener("mouseenter", () => {
1551
- addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
1552
- });
1553
- addBtn.addEventListener("mouseleave", () => {
1554
- addBtn.style.backgroundColor = "transparent";
1555
- });
1556
- addBtn.onclick = () => {
1557
- var _a2, _b2;
1558
- const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
1559
- values.push(defaultValue);
1560
- addSelectItem(defaultValue);
1561
- updateAddButton();
1562
- updateRemoveButtons();
1563
- };
1564
- countDisplay = document.createElement("span");
1565
- countDisplay.className = "text-sm text-gray-500";
1566
- addRow.appendChild(addBtn);
1567
- addRow.appendChild(countDisplay);
1568
- wrapper.appendChild(addRow);
1664
+ const handle = createAddItemRow(
1665
+ "select",
1666
+ () => {
1667
+ var _a2, _b2;
1668
+ const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
1669
+ values.push(defaultValue);
1670
+ addSelectItem(defaultValue);
1671
+ updateAddButton();
1672
+ updateRemoveButtons();
1673
+ },
1674
+ { label: element.addLabel }
1675
+ );
1676
+ addUpdate = handle.update;
1677
+ mountCounterInLabel(wrapper, handle.counter);
1678
+ wrapper.appendChild(handle.row);
1569
1679
  }
1570
1680
  function updateAddButton() {
1571
- if (!addRow || !countDisplay) return;
1572
- const addBtn = addRow.querySelector(".add-select-btn");
1573
- if (addBtn) {
1574
- const disabled = values.length >= maxCount;
1575
- addBtn.disabled = disabled;
1576
- addBtn.style.opacity = disabled ? "0.5" : "1";
1577
- addBtn.style.pointerEvents = disabled ? "none" : "auto";
1578
- }
1579
- countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
1681
+ if (addUpdate) addUpdate(values.length, maxCount);
1580
1682
  }
1581
1683
  values.forEach((value) => addSelectItem(value));
1582
1684
  updateAddButton();
@@ -1925,54 +2027,26 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
1925
2027
  removeBtn.style.pointerEvents = disabled ? "none" : "auto";
1926
2028
  });
1927
2029
  }
1928
- let addRow = null;
1929
- let countDisplay = null;
2030
+ let addUpdate = null;
1930
2031
  if (!readonly) {
1931
- addRow = document.createElement("div");
1932
- addRow.className = "flex items-center gap-3 mt-2";
1933
- const addBtn = document.createElement("button");
1934
- addBtn.type = "button";
1935
- addBtn.className = "add-switcher-btn px-3 py-1 rounded";
1936
- addBtn.style.cssText = `
1937
- color: var(--fb-primary-color);
1938
- border: var(--fb-border-width) solid var(--fb-primary-color);
1939
- background-color: transparent;
1940
- font-size: var(--fb-font-size);
1941
- transition: all var(--fb-transition-duration);
1942
- `;
1943
- addBtn.textContent = "+";
1944
- addBtn.addEventListener("mouseenter", () => {
1945
- addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
1946
- });
1947
- addBtn.addEventListener("mouseleave", () => {
1948
- addBtn.style.backgroundColor = "transparent";
1949
- });
1950
- addBtn.onclick = () => {
1951
- var _a2, _b2;
1952
- const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
1953
- values.push(defaultValue);
1954
- addSwitcherItem(defaultValue);
1955
- updateAddButton();
1956
- updateRemoveButtons();
1957
- };
1958
- countDisplay = document.createElement("span");
1959
- countDisplay.className = "text-sm text-gray-500";
1960
- addRow.appendChild(addBtn);
1961
- addRow.appendChild(countDisplay);
1962
- wrapper.appendChild(addRow);
2032
+ const handle = createAddItemRow(
2033
+ "switcher",
2034
+ () => {
2035
+ var _a2, _b2;
2036
+ const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
2037
+ values.push(defaultValue);
2038
+ addSwitcherItem(defaultValue);
2039
+ updateAddButton();
2040
+ updateRemoveButtons();
2041
+ },
2042
+ { label: element.addLabel }
2043
+ );
2044
+ addUpdate = handle.update;
2045
+ mountCounterInLabel(wrapper, handle.counter);
2046
+ wrapper.appendChild(handle.row);
1963
2047
  }
1964
2048
  function updateAddButton() {
1965
- if (!addRow || !countDisplay) return;
1966
- const addBtn = addRow.querySelector(
1967
- ".add-switcher-btn"
1968
- );
1969
- if (addBtn) {
1970
- const disabled = values.length >= maxCount;
1971
- addBtn.disabled = disabled;
1972
- addBtn.style.opacity = disabled ? "0.5" : "1";
1973
- addBtn.style.pointerEvents = disabled ? "none" : "auto";
1974
- }
1975
- countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
2049
+ if (addUpdate) addUpdate(values.length, maxCount);
1976
2050
  }
1977
2051
  values.forEach((value) => addSwitcherItem(value));
1978
2052
  updateAddButton();
@@ -2225,7 +2299,13 @@ function ensureFileStyles() {
2225
2299
  style.textContent = `
2226
2300
  @keyframes fb-spin { to { transform: rotate(360deg); } }
2227
2301
 
2228
- /* Spinner used during single-file and multi-file upload */
2302
+ /* \u2500\u2500\u2500 Checker background utility \u2500\u2500\u2500 */
2303
+ /* Neutral diagonal-stripe background for image previews (never crops) */
2304
+ .fb-checker {
2305
+ background-image: repeating-linear-gradient(45deg, #fafafa 0 6px, #f3f4f6 6px 12px);
2306
+ }
2307
+
2308
+ /* \u2500\u2500\u2500 Spinner \u2500\u2500\u2500 */
2229
2309
  .fb-spinner {
2230
2310
  width: 36px;
2231
2311
  height: 36px;
@@ -2236,207 +2316,271 @@ function ensureFileStyles() {
2236
2316
  flex-shrink: 0;
2237
2317
  }
2238
2318
 
2239
- /* Base tile: fixed 160\xD7160 square, theme-aware background */
2240
- .fb-tile {
2241
- width: var(--fb-tile-size, 160px);
2242
- height: var(--fb-tile-size, 160px);
2243
- flex-shrink: 0;
2244
- position: relative;
2319
+ /* \u2500\u2500\u2500 Wide single-file add tile (empty state) \u2500\u2500\u2500 */
2320
+ .fb-wide-tile {
2321
+ width: 100%;
2322
+ border-radius: 0.75rem;
2323
+ border: 1px dashed #60a5fa;
2324
+ background: rgba(239,246,255,0.5);
2325
+ display: flex;
2245
2326
  overflow: hidden;
2246
- border-radius: var(--fb-border-radius, 0.5rem);
2247
- background: var(--fb-file-upload-bg-color, #f3f4f6);
2327
+ height: 180px;
2328
+ transition: border-color 150ms, background 150ms, box-shadow 150ms;
2329
+ cursor: pointer;
2248
2330
  }
2249
-
2250
- /* Uploaded resource tile \u2014 adds a visible border */
2251
- .fb-tile-resource {
2252
- border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
2331
+ .fb-wide-tile:hover {
2332
+ background: #eff6ff;
2253
2333
  }
2254
-
2255
- /* Uploading placeholder tile \u2014 dashed border, uploading indicator */
2256
- .fb-tile-uploading {
2257
- border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
2334
+ .fb-wide-tile.fb-drag-over {
2335
+ border-color: #3b82f6;
2336
+ border-width: 2px;
2337
+ background: #eff6ff;
2338
+ box-shadow: 0 0 0 4px rgba(191,219,254,0.7);
2258
2339
  }
2259
2340
 
2260
- /* "+" add-more tile */
2261
- .fb-tile-add {
2262
- border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
2341
+ /* Upload zone inside wide tile */
2342
+ .fb-wide-tile-upload {
2343
+ flex: 1;
2263
2344
  display: flex;
2345
+ flex-direction: column;
2264
2346
  align-items: center;
2265
2347
  justify-content: center;
2348
+ gap: 8px;
2349
+ color: #2563eb;
2350
+ padding: 16px;
2351
+ transition: background 150ms;
2266
2352
  cursor: pointer;
2267
- font-size: 32px;
2268
- color: var(--fb-file-upload-text-color, #9ca3af);
2269
- transition:
2270
- border-color var(--fb-transition-duration, 200ms),
2271
- color var(--fb-transition-duration, 200ms);
2353
+ background: transparent;
2354
+ border: none;
2355
+ font-family: inherit;
2272
2356
  }
2273
- .fb-tile-add:hover {
2274
- border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
2275
- color: var(--fb-text-color, #1f2937);
2357
+ .fb-wide-tile-upload:hover {
2358
+ background: rgba(191,219,254,0.25);
2276
2359
  }
2277
2360
 
2278
- /* Count chip shown when at maxCount */
2279
- .fb-tile-counter {
2280
- font-size: 11px;
2281
- color: var(--fb-text-secondary-color, #6b7280);
2282
- background: var(--fb-file-upload-bg-color, #f3f4f6);
2283
- border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
2284
- border-radius: 4px;
2285
- padding: 2px 6px;
2286
- align-self: flex-end;
2287
- margin-bottom: 4px;
2361
+ /* Vertical dashed divider between upload and library zones */
2362
+ .fb-wide-tile-divider {
2363
+ width: 1px;
2364
+ margin: 16px 0;
2365
+ border-left: 1px dashed rgba(96,165,250,0.5);
2366
+ background: transparent;
2367
+ flex-shrink: 0;
2288
2368
  }
2289
2369
 
2290
- /* Empty-state dropzone */
2291
- .fb-file-dropzone {
2292
- width: 100%;
2293
- height: 128px;
2294
- border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
2295
- border-radius: var(--fb-border-radius, 0.5rem);
2370
+ /* Library zone inside wide tile */
2371
+ .fb-wide-tile-library {
2372
+ width: 176px;
2373
+ flex-shrink: 0;
2296
2374
  display: flex;
2297
2375
  flex-direction: column;
2298
2376
  align-items: center;
2299
2377
  justify-content: center;
2300
- gap: 4px;
2378
+ gap: 8px;
2379
+ color: #2563eb;
2380
+ padding: 12px;
2381
+ transition: background 150ms;
2301
2382
  cursor: pointer;
2302
- transition:
2303
- border-color var(--fb-transition-duration, 200ms),
2304
- background var(--fb-transition-duration, 200ms);
2383
+ background: transparent;
2384
+ border: none;
2385
+ font-family: inherit;
2305
2386
  }
2306
- .fb-file-dropzone:hover {
2307
- border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
2308
- background: var(--fb-background-hover-color, #f9fafb);
2387
+ .fb-wide-tile-library:hover {
2388
+ background: rgba(191,219,254,0.25);
2309
2389
  }
2310
2390
 
2311
- /* Inline text inside tiles */
2312
- .fb-tile-label {
2313
- font-size: 9px;
2314
- color: var(--fb-text-secondary-color, #6b7280);
2315
- text-align: center;
2316
- overflow: hidden;
2317
- word-break: break-all;
2318
- max-height: 28px;
2391
+ /* \u2500\u2500\u2500 Multi-file outer grid container \u2500\u2500\u2500 */
2392
+ .fb-multi-outer {
2393
+ border-radius: 0.75rem;
2394
+ border: 1px dashed #cbd5e1;
2395
+ background: rgba(248,250,252,0.4);
2396
+ padding: 12px;
2397
+ transition: border-color 150ms, background 150ms, box-shadow 150ms;
2398
+ }
2399
+ .fb-multi-outer.fb-drag-over {
2400
+ border-width: 2px;
2401
+ border-color: #3b82f6;
2402
+ background: rgba(239,246,255,0.4);
2403
+ box-shadow: 0 0 0 4px rgba(191,219,254,0.7);
2319
2404
  }
2320
- .fb-tile-uploading-text {
2321
- font-size: 8px;
2322
- color: var(--fb-file-upload-text-color, #9ca3af);
2405
+
2406
+ /* With files present: white solid border */
2407
+ .fb-multi-outer.fb-multi-has-files {
2408
+ border-style: solid;
2409
+ border-color: #e2e8f0;
2410
+ background: #fff;
2323
2411
  }
2324
- .fb-tile-hint {
2325
- font-size: 11px;
2326
- color: var(--fb-file-upload-text-color, #9ca3af);
2327
- margin-top: 4px;
2412
+
2413
+ /* The CSS grid inside */
2414
+ .fb-multi-grid {
2415
+ display: grid;
2416
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
2417
+ gap: 10px;
2328
2418
  }
2329
- .fb-tile-empty-text {
2330
- font-size: 12px;
2331
- color: var(--fb-text-secondary-color, #6b7280);
2332
- padding: 4px 0;
2419
+
2420
+ /* \u2500\u2500\u2500 Multi square add-tile (combined upload + library) \u2500\u2500\u2500 */
2421
+ .fb-multi-add-tile {
2422
+ aspect-ratio: 1 / 1;
2423
+ border-radius: 0.5rem;
2424
+ border: 1px dashed #60a5fa;
2425
+ background: rgba(239,246,255,0.5);
2426
+ display: flex;
2427
+ flex-direction: column;
2428
+ overflow: hidden;
2429
+ transition: background 150ms;
2333
2430
  }
2334
- .fb-dropzone-primary-text {
2335
- font-size: 13px;
2336
- color: var(--fb-text-secondary-color, #6b7280);
2431
+ .fb-multi-add-tile:hover {
2432
+ background: #eff6ff;
2337
2433
  }
2338
- .fb-dropzone-hint-text {
2339
- font-size: 11px;
2340
- color: var(--fb-file-upload-text-color, #9ca3af);
2434
+ .fb-multi-add-tile.fb-drag-over-tile {
2435
+ border-width: 2px;
2436
+ border-color: #3b82f6;
2437
+ background: rgba(255,255,255,0.8);
2341
2438
  }
2342
2439
 
2343
- /* Hover overlay + X-button on resource tiles */
2344
- .fb-tile-overlay {
2345
- position: absolute;
2346
- inset: 0;
2347
- background: transparent;
2348
- transition: background var(--fb-transition-duration, 200ms);
2349
- display: flex;
2350
- align-items: flex-start;
2351
- justify-content: flex-end;
2352
- }
2353
- .fb-tile-resource:hover .fb-tile-overlay {
2354
- background: var(--fb-tile-hover-overlay-color, rgba(0,0,0,0.4));
2355
- }
2356
- .fb-tile-x-btn {
2357
- margin: 3px;
2358
- width: 18px;
2359
- height: 18px;
2360
- background: var(--fb-error-color, #ef4444);
2361
- color: var(--fb-file-bg-color, #fff);
2362
- border: none;
2363
- border-radius: 50%;
2364
- font-size: 11px;
2365
- line-height: 1;
2366
- cursor: pointer;
2440
+ /* Upload half of add-tile */
2441
+ .fb-multi-add-upload {
2442
+ flex: 1;
2367
2443
  display: flex;
2444
+ flex-direction: column;
2368
2445
  align-items: center;
2369
2446
  justify-content: center;
2370
- opacity: 0;
2371
- transition: opacity var(--fb-transition-duration, 200ms);
2447
+ gap: 4px;
2448
+ color: #2563eb;
2449
+ cursor: pointer;
2450
+ background: transparent;
2451
+ border: none;
2452
+ font-family: inherit;
2453
+ width: 100%;
2454
+ transition: background 150ms;
2372
2455
  }
2373
- .fb-tile-resource:hover .fb-tile-x-btn {
2374
- opacity: 1;
2456
+ .fb-multi-add-upload:hover {
2457
+ background: rgba(191,219,254,0.35);
2375
2458
  }
2376
2459
 
2377
- /* Video play button overlay (readonly tiles with video thumbnails) */
2378
- .fb-video-overlay {
2379
- position: absolute;
2380
- inset: 0;
2381
- display: flex;
2382
- align-items: center;
2383
- justify-content: center;
2384
- background: var(--fb-tile-hover-overlay-color, rgba(0,0,0,0.25));
2460
+ /* Horizontal dashed divider inside add-tile */
2461
+ .fb-multi-add-divider {
2462
+ border-top: 1px dashed rgba(96,165,250,0.5);
2463
+ margin: 0;
2464
+ flex-shrink: 0;
2385
2465
  }
2386
- .fb-play-btn {
2387
- background: var(--fb-file-bg-color, rgba(255,255,255,0.9));
2388
- border-radius: 50%;
2466
+
2467
+ /* Library strip at bottom of add-tile */
2468
+ .fb-multi-add-library {
2469
+ padding: 6px 0;
2389
2470
  display: flex;
2390
2471
  align-items: center;
2391
2472
  justify-content: center;
2473
+ gap: 4px;
2474
+ color: #2563eb;
2475
+ font-size: 11px;
2476
+ font-weight: 500;
2477
+ cursor: pointer;
2478
+ background: transparent;
2479
+ border: none;
2480
+ font-family: inherit;
2481
+ width: 100%;
2482
+ transition: background 150ms;
2483
+ flex-shrink: 0;
2484
+ }
2485
+ .fb-multi-add-library:hover {
2486
+ background: rgba(191,219,254,0.35);
2392
2487
  }
2393
2488
 
2394
- /* Edit-mode local video preview wrapper */
2395
- .fb-video-preview-wrap {
2489
+ /* \u2500\u2500\u2500 Capacity placeholder squares \u2500\u2500\u2500 */
2490
+ .fb-multi-placeholder {
2491
+ aspect-ratio: 1 / 1;
2492
+ border-radius: 0.5rem;
2493
+ border: 1px solid #e2e8f0;
2494
+ }
2495
+ .fb-multi-placeholder.fb-drag-over {
2496
+ border-width: 2px;
2497
+ border-style: dashed;
2498
+ border-color: #93c5fd;
2499
+ background: rgba(219,234,254,0.6);
2500
+ }
2501
+
2502
+ /* \u2500\u2500\u2500 Filled preview tile \u2500\u2500\u2500 */
2503
+ .fb-preview-tile {
2504
+ aspect-ratio: 1 / 1;
2505
+ border-radius: 0.5rem;
2506
+ border: 1px solid #e2e8f0;
2507
+ overflow: hidden;
2396
2508
  position: relative;
2509
+ cursor: pointer;
2510
+ }
2511
+ .fb-preview-tile img {
2397
2512
  width: 100%;
2398
2513
  height: 100%;
2514
+ object-fit: contain;
2515
+ display: block;
2399
2516
  }
2400
2517
 
2401
- /* Hover overlay for edit-mode local video (Remove / Change buttons) */
2402
- .fb-video-btn-overlay {
2403
- position: absolute;
2404
- top: 8px;
2405
- right: 8px;
2406
- z-index: 10;
2518
+ /* \u2500\u2500\u2500 Uploading placeholder tile \u2500\u2500\u2500 */
2519
+ .fb-uploading-tile {
2520
+ aspect-ratio: 1 / 1;
2521
+ border-radius: 0.5rem;
2522
+ border: 2px dashed #d1d5db;
2407
2523
  display: flex;
2408
- gap: 4px;
2409
- opacity: 0;
2410
- transition: opacity var(--fb-transition-duration, 200ms);
2411
- pointer-events: none;
2524
+ flex-direction: column;
2525
+ align-items: center;
2526
+ justify-content: center;
2527
+ gap: 6px;
2528
+ padding: 6px;
2412
2529
  }
2413
- .fb-video-preview-wrap:hover .fb-video-btn-overlay {
2414
- opacity: 1;
2415
- pointer-events: auto;
2530
+
2531
+ /* \u2500\u2500\u2500 Meta line below multi grid \u2500\u2500\u2500 */
2532
+ .fb-meta-line {
2533
+ margin-top: 10px;
2534
+ display: flex;
2535
+ align-items: center;
2536
+ justify-content: space-between;
2537
+ gap: 8px;
2538
+ flex-wrap: wrap;
2416
2539
  }
2417
- .fb-video-btn {
2418
- border: none;
2419
- border-radius: var(--fb-border-radius, 4px);
2420
- font-size: 11px;
2421
- padding: 4px 8px;
2422
- cursor: pointer;
2423
- color: #fff;
2424
- line-height: 1.2;
2540
+ .fb-meta-text {
2541
+ font-size: 12px;
2542
+ color: #94a3b8;
2543
+ display: flex;
2544
+ align-items: center;
2545
+ gap: 8px;
2546
+ flex-wrap: wrap;
2425
2547
  }
2426
- .fb-video-btn-delete {
2427
- background: rgba(220, 38, 38, 0.85);
2548
+ .fb-meta-dot {
2549
+ width: 4px;
2550
+ height: 4px;
2551
+ border-radius: 50%;
2552
+ background: #cbd5e1;
2553
+ flex-shrink: 0;
2554
+ }
2555
+ .fb-meta-mono {
2556
+ font-family: ui-monospace, 'JetBrains Mono', monospace;
2557
+ font-size: 11px;
2558
+ letter-spacing: -0.02em;
2428
2559
  }
2429
- .fb-video-btn-delete:hover {
2430
- background: rgba(185, 28, 28, 0.95);
2560
+ .fb-clear-all-btn {
2561
+ font-size: 12px;
2562
+ color: #94a3b8;
2563
+ background: none;
2564
+ border: none;
2565
+ cursor: pointer;
2566
+ padding: 0;
2567
+ font-family: inherit;
2568
+ transition: color 150ms;
2569
+ white-space: nowrap;
2570
+ flex-shrink: 0;
2431
2571
  }
2432
- .fb-video-btn-change {
2433
- background: rgba(31, 41, 55, 0.85);
2572
+ .fb-clear-all-btn:hover {
2573
+ color: #dc2626;
2434
2574
  }
2435
- .fb-video-btn-change:hover {
2436
- background: rgba(17, 24, 39, 0.95);
2575
+
2576
+ /* \u2500\u2500\u2500 Empty text (readonly) \u2500\u2500\u2500 */
2577
+ .fb-tile-empty-text {
2578
+ font-size: 11px;
2579
+ color: var(--fb-text-secondary-color, #6b7280);
2580
+ padding: 4px 0;
2437
2581
  }
2438
2582
 
2439
- /* Tile action icon buttons (download / open / remove) \u2014 shown on tile hover */
2583
+ /* \u2500\u2500\u2500 Tile action buttons (for zoom popup, compat) \u2500\u2500\u2500 */
2440
2584
  .fb-tile-actions {
2441
2585
  position: absolute;
2442
2586
  top: 3px;
@@ -2448,37 +2592,35 @@ function ensureFileStyles() {
2448
2592
  transition: opacity var(--fb-transition-duration, 200ms);
2449
2593
  z-index: 10;
2450
2594
  }
2451
- .fb-tile-resource:hover .fb-tile-actions {
2595
+ .fb-preview-tile:hover .fb-tile-actions {
2452
2596
  opacity: 1;
2453
2597
  }
2454
2598
  .fb-tile-action-btn {
2455
- width: 28px;
2456
- height: 28px;
2599
+ width: 24px;
2600
+ height: 24px;
2457
2601
  display: flex;
2458
2602
  align-items: center;
2459
2603
  justify-content: center;
2460
- border: none;
2461
- border-radius: 50%;
2604
+ border: 1px solid rgba(15,23,42,0.08);
2605
+ border-radius: 0.375rem;
2462
2606
  cursor: pointer;
2463
- background: rgba(31, 41, 55, 0.75);
2464
- color: #fff;
2607
+ background: rgba(255,255,255,0.92);
2608
+ color: #374151;
2465
2609
  padding: 0;
2466
2610
  flex-shrink: 0;
2467
- transition:
2468
- background var(--fb-transition-duration, 200ms),
2469
- opacity var(--fb-transition-duration, 200ms);
2611
+ box-shadow: 0 1px 2px rgba(0,0,0,0.06);
2612
+ transition: background var(--fb-transition-duration, 200ms),
2613
+ color var(--fb-transition-duration, 200ms);
2470
2614
  }
2471
2615
  .fb-tile-action-btn:hover {
2472
- background: rgba(17, 24, 39, 0.95);
2473
- }
2474
- .fb-tile-action-remove {
2475
- background: rgba(220, 38, 38, 0.8);
2616
+ background: #ffffff;
2617
+ color: #0f172a;
2476
2618
  }
2477
2619
  .fb-tile-action-remove:hover {
2478
- background: rgba(185, 28, 28, 0.95);
2620
+ color: #dc2626;
2479
2621
  }
2480
2622
 
2481
- /* Actions row inside zoom popup \u2014 always visible while popup is shown */
2623
+ /* Zoom popup action buttons always visible */
2482
2624
  .fb-tile-zoom-preview .fb-tile-actions {
2483
2625
  position: absolute;
2484
2626
  top: 6px;
@@ -2487,116 +2629,145 @@ function ensureFileStyles() {
2487
2629
  z-index: 10000;
2488
2630
  }
2489
2631
 
2490
- /* Two-card empty-state layout (upload card + library card) */
2491
- .fb-file-card-row {
2492
- display: flex;
2493
- gap: 8px;
2494
- align-items: stretch;
2632
+ /* \u2500\u2500\u2500 Hover zoom preview popup \u2500\u2500\u2500 */
2633
+ .fb-tile-zoom-preview {
2634
+ position: fixed;
2635
+ z-index: 9999;
2636
+ background: var(--fb-background-color, #fff);
2637
+ border: 1px solid #e2e8f0;
2638
+ border-radius: 0.5rem;
2639
+ box-shadow: 0 4px 16px rgba(0,0,0,0.18);
2640
+ padding: 4px;
2641
+ width: 350px;
2642
+ height: 350px;
2643
+ pointer-events: none;
2644
+ opacity: 0;
2645
+ transition: opacity 150ms ease;
2495
2646
  }
2496
- .fb-file-card-row .fb-file-dropzone,
2497
- .fb-file-card-row .fb-file-library-card {
2498
- flex: 1;
2499
- min-width: 0;
2647
+ .fb-tile-zoom-preview.fb-tile-zoom-preview--visible {
2648
+ opacity: 1;
2649
+ }
2650
+ .fb-tile-zoom-preview-img {
2651
+ width: 100%;
2652
+ height: 100%;
2653
+ object-fit: contain;
2654
+ display: block;
2655
+ border-radius: calc(0.5rem - 2px);
2500
2656
  }
2501
2657
 
2502
- /* Library picker card \u2014 mirrors .fb-file-dropzone styling */
2503
- .fb-file-library-card {
2504
- height: 128px;
2505
- border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
2506
- border-radius: var(--fb-border-radius, 0.5rem);
2658
+ /* \u2500\u2500\u2500 Single-file uploading state \u2500\u2500\u2500 */
2659
+ .fb-single-uploading {
2660
+ height: 180px;
2661
+ border-radius: 0.75rem;
2662
+ border: 1px dashed #60a5fa;
2663
+ background: rgba(239,246,255,0.5);
2507
2664
  display: flex;
2508
2665
  flex-direction: column;
2509
2666
  align-items: center;
2510
2667
  justify-content: center;
2511
- gap: 4px;
2512
- cursor: pointer;
2513
- background: none;
2514
- padding: 0;
2515
- transition:
2516
- border-color var(--fb-transition-duration, 200ms),
2517
- background var(--fb-transition-duration, 200ms);
2518
- width: 100%;
2668
+ gap: 8px;
2519
2669
  }
2520
- .fb-file-library-card:hover,
2521
- .fb-file-library-card:focus-visible {
2522
- border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
2523
- background: var(--fb-background-hover-color, #f9fafb);
2524
- outline: none;
2670
+
2671
+ /* \u2500\u2500\u2500 Video overlays \u2500\u2500\u2500 */
2672
+ .fb-video-overlay {
2673
+ position: absolute;
2674
+ inset: 0;
2675
+ display: flex;
2676
+ align-items: center;
2677
+ justify-content: center;
2678
+ background: rgba(0,0,0,0.25);
2525
2679
  }
2526
- .fb-file-library-card-icon {
2527
- font-size: 24px;
2528
- line-height: 1;
2529
- flex-shrink: 0;
2680
+ .fb-play-btn {
2681
+ background: rgba(255,255,255,0.9);
2682
+ border-radius: 50%;
2683
+ display: flex;
2684
+ align-items: center;
2685
+ justify-content: center;
2530
2686
  }
2531
- .fb-file-library-card-label {
2532
- font-size: 13px;
2533
- color: var(--fb-text-secondary-color, #6b7280);
2687
+ .fb-video-preview-wrap {
2688
+ position: relative;
2689
+ width: 100%;
2690
+ height: 100%;
2534
2691
  }
2535
- .fb-file-library-card-hint {
2692
+ .fb-video-btn-overlay {
2693
+ position: absolute;
2694
+ top: 8px;
2695
+ right: 8px;
2696
+ z-index: 10;
2697
+ display: flex;
2698
+ gap: 4px;
2699
+ opacity: 0;
2700
+ transition: opacity 150ms;
2701
+ pointer-events: none;
2702
+ }
2703
+ .fb-video-preview-wrap:hover .fb-video-btn-overlay {
2704
+ opacity: 1;
2705
+ pointer-events: auto;
2706
+ }
2707
+ .fb-video-btn {
2708
+ border: none;
2709
+ border-radius: 4px;
2536
2710
  font-size: 11px;
2537
- color: var(--fb-file-upload-text-color, #9ca3af);
2711
+ padding: 4px 8px;
2712
+ cursor: pointer;
2713
+ color: #fff;
2714
+ line-height: 1.2;
2538
2715
  }
2716
+ .fb-video-btn-delete { background: rgba(220,38,38,0.85); }
2717
+ .fb-video-btn-delete:hover { background: rgba(185,28,28,0.95); }
2718
+ .fb-video-btn-change { background: rgba(31,41,55,0.85); }
2719
+ .fb-video-btn-change:hover { background: rgba(17,24,39,0.95); }
2539
2720
 
2540
- /* Library "\u{1F4DA}" add-tile \u2014 same size/style as the "+" add tile */
2541
- .fb-tile-add-library {
2542
- border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
2543
- display: flex;
2544
- align-items: center;
2545
- justify-content: center;
2546
- cursor: pointer;
2547
- font-size: 24px;
2548
- color: var(--fb-file-upload-text-color, #9ca3af);
2549
- transition:
2550
- border-color var(--fb-transition-duration, 200ms),
2551
- color var(--fb-transition-duration, 200ms);
2552
- background: none;
2553
- padding: 0;
2554
- width: var(--fb-tile-size, 160px);
2555
- height: var(--fb-tile-size, 160px);
2556
- flex-shrink: 0;
2557
- position: relative;
2721
+ /* \u2500\u2500\u2500 Readonly readonly tile \u2500\u2500\u2500 */
2722
+ .fb-readonly-tile {
2723
+ aspect-ratio: 1 / 1;
2724
+ border-radius: 0.5rem;
2725
+ border: 1px solid #e2e8f0;
2558
2726
  overflow: hidden;
2559
- border-radius: var(--fb-border-radius, 0.5rem);
2727
+ position: relative;
2728
+ cursor: pointer;
2560
2729
  }
2561
- .fb-tile-add-library:hover,
2562
- .fb-tile-add-library:focus-visible {
2563
- border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
2564
- color: var(--fb-text-color, #1f2937);
2565
- outline: none;
2730
+ .fb-readonly-tile img {
2731
+ width: 100%;
2732
+ height: 100%;
2733
+ object-fit: contain;
2734
+ display: block;
2566
2735
  }
2567
-
2568
- /* Hover zoom preview popup for image tiles \u2014 appended to document.body (fixed) */
2569
- .fb-tile-zoom-preview {
2570
- position: fixed;
2571
- z-index: 9999;
2572
- background: var(--fb-background-color, #fff);
2573
- border: 1px solid var(--fb-file-upload-border-color, #d1d5db);
2574
- border-radius: var(--fb-border-radius, 0.5rem);
2575
- box-shadow: 0 4px 16px rgba(0,0,0,0.18);
2576
- padding: 4px;
2577
- width: 350px;
2578
- height: 350px;
2579
- pointer-events: none;
2736
+ .fb-readonly-tile .fb-tile-actions {
2580
2737
  opacity: 0;
2581
- transition: opacity 150ms ease;
2582
2738
  }
2583
- .fb-tile-zoom-preview.fb-tile-zoom-preview--visible {
2739
+ .fb-readonly-tile:hover .fb-tile-actions {
2584
2740
  opacity: 1;
2585
2741
  }
2586
- .fb-tile-zoom-preview-img {
2742
+
2743
+ /* \u2500\u2500\u2500 Readonly single-file filled \u2500\u2500\u2500 */
2744
+ .fb-single-readonly-filled {
2745
+ position: relative;
2746
+ border-radius: 0.75rem;
2747
+ border: 1px solid #e2e8f0;
2748
+ overflow: hidden;
2749
+ height: 220px;
2750
+ display: block;
2751
+ cursor: pointer;
2752
+ }
2753
+ .fb-single-readonly-filled img {
2587
2754
  width: 100%;
2588
2755
  height: 100%;
2589
2756
  object-fit: contain;
2590
2757
  display: block;
2591
- background: var(--fb-file-upload-bg-color, #f3f4f6);
2592
- border-radius: calc(var(--fb-border-radius, 0.5rem) - 2px);
2758
+ }
2759
+
2760
+ /* \u2500\u2500\u2500 Readonly multi grid \u2500\u2500\u2500 */
2761
+ .fb-multi-readonly-grid {
2762
+ display: grid;
2763
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
2764
+ gap: 10px;
2593
2765
  }
2594
2766
  `;
2595
2767
  document.head.appendChild(style);
2596
2768
  }
2597
2769
 
2598
2770
  // src/components/file/dom.ts
2599
- var TILE_SIZE = "160px";
2600
2771
  function createFileTile() {
2601
2772
  ensureFileStyles();
2602
2773
  const tile = document.createElement("div");
@@ -2605,7 +2776,7 @@ function createFileTile() {
2605
2776
  }
2606
2777
  function showFileError(container, message) {
2607
2778
  var _a, _b;
2608
- const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
2779
+ const existing = (_a = container.closest("[data-files-wrapper]")) == null ? void 0 : _a.querySelector(".file-error-message");
2609
2780
  if (existing) existing.remove();
2610
2781
  const errorEl = document.createElement("div");
2611
2782
  errorEl.className = "file-error-message error-message";
@@ -2615,11 +2786,11 @@ function showFileError(container, message) {
2615
2786
  margin-top: 0.25rem;
2616
2787
  `;
2617
2788
  errorEl.textContent = message;
2618
- (_b = container.closest(".space-y-2")) == null ? void 0 : _b.appendChild(errorEl);
2789
+ (_b = container.closest("[data-files-wrapper]")) == null ? void 0 : _b.appendChild(errorEl);
2619
2790
  }
2620
2791
  function clearFileError(container) {
2621
2792
  var _a;
2622
- const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
2793
+ const existing = (_a = container.closest("[data-files-wrapper]")) == null ? void 0 : _a.querySelector(".file-error-message");
2623
2794
  if (existing) existing.remove();
2624
2795
  }
2625
2796
  function addDeleteButton(container, state, onDelete) {
@@ -2637,14 +2808,6 @@ function addDeleteButton(container, state, onDelete) {
2637
2808
  overlay.appendChild(deleteBtn);
2638
2809
  container.appendChild(overlay);
2639
2810
  }
2640
- function findFilePicker(container) {
2641
- var _a;
2642
- let el = container.parentElement;
2643
- while (el && !el.dataset.filesWrapper) {
2644
- el = el.parentElement;
2645
- }
2646
- return (_a = el == null ? void 0 : el.querySelector('input[type="file"]')) != null ? _a : null;
2647
- }
2648
2811
  function createUploadingTile(fileName, state) {
2649
2812
  ensureFileStyles();
2650
2813
  const tile = createFileTile();
@@ -2660,10 +2823,14 @@ function createUploadingTile(fileName, state) {
2660
2823
  return tile;
2661
2824
  }
2662
2825
  function ensureTilesWrap(list) {
2826
+ var _a, _b, _c;
2827
+ const existingGrid = list.querySelector(".fb-multi-grid");
2828
+ if (existingGrid) return existingGrid;
2663
2829
  const existing = list.querySelector(".fb-tiles-wrap");
2664
2830
  if (existing) return existing;
2665
- const dropzone = list.querySelector(".fb-file-dropzone");
2666
- if (dropzone) dropzone.remove();
2831
+ (_a = list.querySelector(".fb-file-dropzone")) == null ? void 0 : _a.remove();
2832
+ (_b = list.querySelector(".fb-wide-tile")) == null ? void 0 : _b.remove();
2833
+ (_c = list.querySelector(".fb-multi-outer")) == null ? void 0 : _c.remove();
2667
2834
  const tilesWrap = document.createElement("div");
2668
2835
  tilesWrap.className = "fb-tiles-wrap";
2669
2836
  tilesWrap.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;";
@@ -2675,7 +2842,7 @@ function ensureTilesWrap(list) {
2675
2842
  return tilesWrap;
2676
2843
  }
2677
2844
  function setEmptyFileContainer(fileContainer, state, hint) {
2678
- const hintHtml = hint ? `<div class="text-xs text-gray-500 mt-1">${escapeHtml(hint)}</div>` : "";
2845
+ const hintHtml = "";
2679
2846
  fileContainer.innerHTML = `
2680
2847
  <div class="flex flex-col items-center justify-center h-full text-gray-400">
2681
2848
  <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
@@ -2718,9 +2885,11 @@ function setupDragAndDrop(element, dropHandler) {
2718
2885
  }
2719
2886
 
2720
2887
  // src/components/file/preview.ts
2721
- var ICON_DOWNLOAD = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
2722
- var ICON_OPEN = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>`;
2723
- var ICON_REMOVE = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"/></svg>`;
2888
+ var ICON_DOWNLOAD = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
2889
+ var ICON_OPEN = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>`;
2890
+ var ICON_REMOVE = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"/></svg>`;
2891
+ var ICON_REPLACE = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 18a4 4 0 000-8 6 6 0 00-11.5 2A4 4 0 006 20h11M12 12v7M12 12l-3 3M12 12l3 3"/></svg>`;
2892
+ var ICON_LIBRARY = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h4v16H4z"/><path d="M10 4h4v16h-4z"/><path d="M16 5l3.5 1-3 14L13 19"/></svg>`;
2724
2893
  function canDownload(state, meta) {
2725
2894
  return Boolean(
2726
2895
  state.config.downloadFile || state.config.getDownloadUrl || state.config.getThumbnail || (meta == null ? void 0 : meta.file)
@@ -2732,7 +2901,16 @@ function canOpenInTab(state, meta) {
2732
2901
  );
2733
2902
  }
2734
2903
  function createTileActions(options) {
2735
- const { canRemove, removeHandler, state, resourceId, fileName, meta } = options;
2904
+ const {
2905
+ canRemove,
2906
+ removeHandler,
2907
+ state,
2908
+ resourceId,
2909
+ fileName,
2910
+ meta,
2911
+ replaceHandler,
2912
+ libraryHandler
2913
+ } = options;
2736
2914
  const group = document.createElement("div");
2737
2915
  group.className = "fb-tile-actions";
2738
2916
  const makeBtn = (icon, label, cls) => {
@@ -2747,15 +2925,41 @@ function createTileActions(options) {
2747
2925
  });
2748
2926
  return btn;
2749
2927
  };
2928
+ if (replaceHandler) {
2929
+ const replaceBtn = makeBtn(
2930
+ ICON_REPLACE,
2931
+ t("replaceFile", state),
2932
+ "fb-tile-action-replace"
2933
+ );
2934
+ replaceBtn.addEventListener("click", () => replaceHandler());
2935
+ group.appendChild(replaceBtn);
2936
+ }
2937
+ if (libraryHandler) {
2938
+ const libBtn = makeBtn(
2939
+ ICON_LIBRARY,
2940
+ t("fromLibrary", state),
2941
+ "fb-tile-action-library"
2942
+ );
2943
+ libBtn.addEventListener("click", () => libraryHandler());
2944
+ group.appendChild(libBtn);
2945
+ }
2750
2946
  if (canDownload(state, meta)) {
2751
- const dlBtn = makeBtn(ICON_DOWNLOAD, t("downloadFile", state), "fb-tile-action-download");
2947
+ const dlBtn = makeBtn(
2948
+ ICON_DOWNLOAD,
2949
+ t("downloadFile", state),
2950
+ "fb-tile-action-download"
2951
+ );
2752
2952
  dlBtn.addEventListener("click", () => {
2753
2953
  triggerTileDownload(resourceId, fileName, state, meta);
2754
2954
  });
2755
2955
  group.appendChild(dlBtn);
2756
2956
  }
2757
2957
  if (canOpenInTab(state, meta)) {
2758
- const openBtn = makeBtn(ICON_OPEN, t("openInNewTab", state), "fb-tile-action-open");
2958
+ const openBtn = makeBtn(
2959
+ ICON_OPEN,
2960
+ t("openInNewTab", state),
2961
+ "fb-tile-action-open"
2962
+ );
2759
2963
  openBtn.addEventListener("click", () => {
2760
2964
  triggerTileOpen(resourceId, state, meta).catch((err) => {
2761
2965
  console.error("Open failed:", err);
@@ -2764,7 +2968,11 @@ function createTileActions(options) {
2764
2968
  group.appendChild(openBtn);
2765
2969
  }
2766
2970
  if (canRemove && removeHandler) {
2767
- const rmBtn = makeBtn(ICON_REMOVE, t("removeElement", state), "fb-tile-action-remove");
2971
+ const rmBtn = makeBtn(
2972
+ ICON_REMOVE,
2973
+ t("removeElement", state),
2974
+ "fb-tile-action-remove"
2975
+ );
2768
2976
  rmBtn.addEventListener("click", () => {
2769
2977
  removeHandler();
2770
2978
  });
@@ -2842,11 +3050,17 @@ function positionZoomPopup(popup, tile) {
2842
3050
  } else if (tileRect.bottom + margin + popupSize + padding <= window.innerHeight) {
2843
3051
  top = tileRect.bottom + margin;
2844
3052
  } else {
2845
- top = Math.max(padding, Math.min(window.innerHeight - popupSize - padding, tileRect.top));
3053
+ top = Math.max(
3054
+ padding,
3055
+ Math.min(window.innerHeight - popupSize - padding, tileRect.top)
3056
+ );
2846
3057
  }
2847
3058
  const tileCenterX = tileRect.left + tileRect.width / 2;
2848
3059
  let left = tileCenterX - popupSize / 2;
2849
- left = Math.max(padding, Math.min(window.innerWidth - popupSize - padding, left));
3060
+ left = Math.max(
3061
+ padding,
3062
+ Math.min(window.innerWidth - popupSize - padding, left)
3063
+ );
2850
3064
  popup.style.top = `${top}px`;
2851
3065
  popup.style.left = `${left}px`;
2852
3066
  }
@@ -2890,7 +3104,9 @@ function attachZoomHover(tile, src, alt, actionsEl) {
2890
3104
  const popup = getOrCreateZoomPopup();
2891
3105
  const existingActions = popup.querySelector(".fb-tile-actions");
2892
3106
  if (existingActions) existingActions.remove();
2893
- const img = popup.querySelector(".fb-tile-zoom-preview-img");
3107
+ const img = popup.querySelector(
3108
+ ".fb-tile-zoom-preview-img"
3109
+ );
2894
3110
  img.src = src;
2895
3111
  img.alt = alt;
2896
3112
  if (actionsEl) {
@@ -2918,7 +3134,9 @@ function attachZoomHover(tile, src, alt, actionsEl) {
2918
3134
  });
2919
3135
  }
2920
3136
  function attachClonedActionListeners(cloned, original) {
2921
- const originalBtns = Array.from(original.querySelectorAll(".fb-tile-action-btn"));
3137
+ const originalBtns = Array.from(
3138
+ original.querySelectorAll(".fb-tile-action-btn")
3139
+ );
2922
3140
  const clonedBtns = Array.from(cloned.querySelectorAll(".fb-tile-action-btn"));
2923
3141
  clonedBtns.forEach((clonedBtn, i) => {
2924
3142
  const origBtn = originalBtns[i];
@@ -2932,8 +3150,7 @@ function attachClonedActionListeners(cloned, original) {
2932
3150
  }
2933
3151
  function renderLocalImagePreview(container, file, fileName, state) {
2934
3152
  const img = document.createElement("img");
2935
- img.className = "w-full h-full object-contain";
2936
- img.style.background = "var(--fb-file-upload-bg-color,#f3f4f6)";
3153
+ img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
2937
3154
  img.alt = fileName || t("previewAlt", state);
2938
3155
  const reader = new FileReader();
2939
3156
  reader.onload = (e) => {
@@ -2956,7 +3173,7 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
2956
3173
  const newContainer = setupDragDropless(container);
2957
3174
  newContainer.innerHTML = `
2958
3175
  <div class="fb-video-preview-wrap">
2959
- <video class="w-full h-full object-contain" controls preload="auto" muted src="${videoUrl}">
3176
+ <video style="width:100%;height:100%;object-fit:contain;" controls preload="auto" muted src="${videoUrl}">
2960
3177
  ${escapeHtml(t("videoNotSupported", state))}
2961
3178
  </video>
2962
3179
  <div class="fb-video-btn-overlay">
@@ -2973,7 +3190,9 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
2973
3190
  return newContainer;
2974
3191
  }
2975
3192
  function attachVideoButtonHandlers(container, resourceId, state, deps) {
2976
- const changeBtn = container.querySelector(".change-file-btn");
3193
+ const changeBtn = container.querySelector(
3194
+ ".change-file-btn"
3195
+ );
2977
3196
  if (changeBtn) {
2978
3197
  changeBtn.onclick = (e) => {
2979
3198
  var _a;
@@ -2981,7 +3200,9 @@ function attachVideoButtonHandlers(container, resourceId, state, deps) {
2981
3200
  (_a = deps == null ? void 0 : deps.picker) == null ? void 0 : _a.click();
2982
3201
  };
2983
3202
  }
2984
- const deleteBtn = container.querySelector(".delete-file-btn");
3203
+ const deleteBtn = container.querySelector(
3204
+ ".delete-file-btn"
3205
+ );
2985
3206
  if (deleteBtn) {
2986
3207
  deleteBtn.onclick = (e) => {
2987
3208
  e.stopPropagation();
@@ -3002,11 +3223,11 @@ function handleVideoDelete(container, resourceId, state, deps) {
3002
3223
  container.onclick = deps.fileUploadHandler;
3003
3224
  }
3004
3225
  container.innerHTML = `
3005
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
3006
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
3226
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--fb-text-secondary-color,#9ca3af);">
3227
+ <svg style="width:1.5rem;height:1.5rem;margin-bottom:0.5rem;" fill="currentColor" viewBox="0 0 24 24">
3007
3228
  <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
3008
3229
  </svg>
3009
- <div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
3230
+ <div style="font-size:0.875rem;text-align:center;">${escapeHtml(t("clickDragText", state))}</div>
3010
3231
  </div>
3011
3232
  `;
3012
3233
  if (deps == null ? void 0 : deps.setupDrop) {
@@ -3024,11 +3245,11 @@ function renderDeleteButton(container, resourceId, state) {
3024
3245
  hiddenInput.value = "";
3025
3246
  }
3026
3247
  container.innerHTML = `
3027
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
3028
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
3248
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--fb-text-secondary-color,#9ca3af);">
3249
+ <svg style="width:1.5rem;height:1.5rem;margin-bottom:0.5rem;" fill="currentColor" viewBox="0 0 24 24">
3029
3250
  <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
3030
3251
  </svg>
3031
- <div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
3252
+ <div style="font-size:0.875rem;text-align:center;">${escapeHtml(t("clickDragText", state))}</div>
3032
3253
  </div>
3033
3254
  `;
3034
3255
  });
@@ -3048,7 +3269,7 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
3048
3269
  deps
3049
3270
  );
3050
3271
  } else {
3051
- container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div style="font-size:36px;" class="mb-2">\u{1F4C1}</div><div class="text-sm">${escapeHtml(fileName)}</div></div>`;
3272
+ container.innerHTML = `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--fb-text-secondary-color,#9ca3af);"><div style="font-size:36px;margin-bottom:0.5rem;">\u{1F4C1}</div><div style="font-size:0.875rem;">${escapeHtml(fileName)}</div></div>`;
3052
3273
  }
3053
3274
  if (!isReadonly && !((_c = meta.type) == null ? void 0 : _c.startsWith("video/"))) {
3054
3275
  renderDeleteButton(container, resourceId, state);
@@ -3056,7 +3277,7 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
3056
3277
  }
3057
3278
  function renderUploadedVideoPreview(container, thumbnailUrl, state) {
3058
3279
  const video = document.createElement("video");
3059
- video.className = "w-full h-full object-contain";
3280
+ video.style.cssText = "width:100%;height:100%;object-fit:contain;";
3060
3281
  video.controls = true;
3061
3282
  video.preload = "metadata";
3062
3283
  video.muted = true;
@@ -3078,8 +3299,7 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
3078
3299
  renderUploadedVideoPreview(container, thumbnailUrl, state);
3079
3300
  } else {
3080
3301
  const img = document.createElement("img");
3081
- img.className = "w-full h-full object-contain";
3082
- img.style.background = "var(--fb-file-upload-bg-color,#f3f4f6)";
3302
+ img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
3083
3303
  img.alt = fileName || t("previewAlt", state);
3084
3304
  img.src = thumbnailUrl;
3085
3305
  container.appendChild(img);
@@ -3090,11 +3310,11 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
3090
3310
  } catch (error) {
3091
3311
  console.error("Failed to get thumbnail:", error);
3092
3312
  container.innerHTML = `
3093
- <div class="flex flex-col items-center justify-center h-full text-gray-400">
3094
- <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
3313
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--fb-text-secondary-color,#9ca3af);">
3314
+ <svg style="width:1.5rem;height:1.5rem;margin-bottom:0.5rem;" fill="currentColor" viewBox="0 0 24 24">
3095
3315
  <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
3096
3316
  </svg>
3097
- <div class="text-sm text-center">${escapeHtml(fileName || t("previewUnavailable", state))}</div>
3317
+ <div style="font-size:0.875rem;text-align:center;">${escapeHtml(fileName || t("previewUnavailable", state))}</div>
3098
3318
  </div>
3099
3319
  `;
3100
3320
  }
@@ -3120,7 +3340,13 @@ async function renderFilePreview(container, resourceId, state, options = {}) {
3120
3340
  deps
3121
3341
  );
3122
3342
  } else {
3123
- await renderUploadedFilePreview(container, resourceId, fileName, meta, state);
3343
+ await renderUploadedFilePreview(
3344
+ container,
3345
+ resourceId,
3346
+ fileName,
3347
+ meta,
3348
+ state
3349
+ );
3124
3350
  const isVideo = (_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/");
3125
3351
  if (!isReadonly && !isVideo) {
3126
3352
  renderDeleteButton(container, resourceId, state);
@@ -3149,7 +3375,8 @@ async function renderFilePreviewReadonly(resourceId, state, fileName, options =
3149
3375
  }
3150
3376
  const localFileUrl = (meta == null ? void 0 : meta.file) instanceof File ? getLocalFileUrl(meta.file) : null;
3151
3377
  const resolveOpenUrl = async () => {
3152
- if (state.config.getDownloadUrl) return state.config.getDownloadUrl(resourceId);
3378
+ if (state.config.getDownloadUrl)
3379
+ return state.config.getDownloadUrl(resourceId);
3153
3380
  if (state.config.getThumbnail) return state.config.getThumbnail(resourceId);
3154
3381
  return localFileUrl;
3155
3382
  };
@@ -3267,20 +3494,15 @@ async function renderSingleFileEditTile(fileContainer, resourceId, state, deps)
3267
3494
  fileContainer.appendChild(tile);
3268
3495
  }
3269
3496
  async function fillTileContent(tile, rid, meta, state, actionsEl) {
3270
- var _a, _b, _c;
3497
+ var _a, _b;
3271
3498
  if ((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("image/")) {
3272
3499
  if (meta.file && meta.file instanceof File) {
3273
3500
  const img = document.createElement("img");
3274
3501
  img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
3275
3502
  img.alt = meta.name;
3276
- const reader = new FileReader();
3277
- reader.onload = (e) => {
3278
- var _a2;
3279
- img.src = ((_a2 = e.target) == null ? void 0 : _a2.result) || "";
3280
- attachZoomHover(tile, img.src, meta.name, actionsEl != null ? actionsEl : null);
3281
- };
3282
- reader.readAsDataURL(meta.file);
3503
+ img.src = getLocalFileUrl(meta.file);
3283
3504
  tile.appendChild(img);
3505
+ attachZoomHover(tile, img.src, meta.name, actionsEl != null ? actionsEl : null);
3284
3506
  } else if (state.config.getThumbnail) {
3285
3507
  try {
3286
3508
  const url = await state.config.getThumbnail(rid);
@@ -3296,7 +3518,8 @@ async function fillTileContent(tile, rid, meta, state, actionsEl) {
3296
3518
  }
3297
3519
  } catch (error) {
3298
3520
  const err = error instanceof Error ? error : new Error(String(error));
3299
- if (state.config.onThumbnailError) state.config.onThumbnailError(err, rid);
3521
+ if (state.config.onThumbnailError)
3522
+ state.config.onThumbnailError(err, rid);
3300
3523
  tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:16px;color:var(--fb-error-color,#ef4444);">\u2715</div>`;
3301
3524
  }
3302
3525
  } else {
@@ -3329,7 +3552,8 @@ async function fillTileContent(tile, rid, meta, state, actionsEl) {
3329
3552
  }
3330
3553
  } catch (error) {
3331
3554
  const err = error instanceof Error ? error : new Error(String(error));
3332
- if (state.config.onThumbnailError) state.config.onThumbnailError(err, rid);
3555
+ if (state.config.onThumbnailError)
3556
+ state.config.onThumbnailError(err, rid);
3333
3557
  tile.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:16px;color:var(--fb-error-color,#ef4444);">\u2715</div>`;
3334
3558
  }
3335
3559
  } else {
@@ -3337,17 +3561,21 @@ async function fillTileContent(tile, rid, meta, state, actionsEl) {
3337
3561
  }
3338
3562
  if (actionsEl) tile.appendChild(actionsEl);
3339
3563
  } else {
3340
- const name = (_c = meta == null ? void 0 : meta.name) != null ? _c : "";
3341
- const hasExtension = name.includes(".");
3342
- const captionHtml = hasExtension ? `<div class="fb-tile-label">${escapeHtml(name.length > 10 ? name.substring(0, 8) + "\u2026" : name)}</div>` : "";
3343
- tile.innerHTML = `
3344
- <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:6px;gap:4px;">
3345
- <div style="font-size:36px;">\u{1F4C1}</div>
3346
- ${captionHtml}
3347
- </div>`;
3348
- if (actionsEl) tile.appendChild(actionsEl);
3564
+ fillDocumentFallback(tile, rid, meta, actionsEl);
3349
3565
  }
3350
3566
  }
3567
+ function fillDocumentFallback(tile, rid, meta, actionsEl) {
3568
+ var _a, _b;
3569
+ const fileName = (_b = (_a = meta == null ? void 0 : meta.name) != null ? _a : rid.split("/").pop()) != null ? _b : "";
3570
+ if (fileName) tile.title = fileName;
3571
+ const labelHtml = fileName ? `<div style="font-size:11px;line-height:1.2;text-align:center;color:var(--fb-text-secondary-color,#6b7280);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escapeHtml(fileName)}</div>` : "";
3572
+ tile.innerHTML = `
3573
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:6px;gap:4px;">
3574
+ <div style="font-size:36px;">\u{1F4C1}</div>
3575
+ ${labelHtml}
3576
+ </div>`;
3577
+ if (actionsEl) tile.appendChild(actionsEl);
3578
+ }
3351
3579
  async function forceDownload(resourceId, fileName, state) {
3352
3580
  try {
3353
3581
  let fileUrl = null;
@@ -3359,7 +3587,8 @@ async function forceDownload(resourceId, fileName, state) {
3359
3587
  if (fileUrl) {
3360
3588
  const finalUrl = fileUrl.startsWith("http") ? fileUrl : new URL(fileUrl, window.location.href).href;
3361
3589
  const response = await fetch(finalUrl);
3362
- if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
3590
+ if (!response.ok)
3591
+ throw new Error(`HTTP error! status: ${response.status}`);
3363
3592
  const blob = await response.blob();
3364
3593
  downloadBlob(blob, fileName);
3365
3594
  } else {
@@ -3408,11 +3637,13 @@ async function uploadSingleFile(file, state) {
3408
3637
  } catch (error) {
3409
3638
  const err = error instanceof Error ? error : new Error(String(error));
3410
3639
  if (state.config.onUploadError) state.config.onUploadError(err, file);
3411
- throw new Error(`File upload failed: ${err.message}`);
3640
+ const wrapped = new Error(`File upload failed: ${err.message}`);
3641
+ wrapped.cause = err;
3642
+ throw wrapped;
3412
3643
  }
3413
3644
  }
3414
3645
  async function handleFileSelect(opts) {
3415
- var _a, _b;
3646
+ var _a, _b, _c;
3416
3647
  const {
3417
3648
  file,
3418
3649
  container,
@@ -3448,6 +3679,10 @@ async function handleFileSelect(opts) {
3448
3679
  return;
3449
3680
  }
3450
3681
  clearFileError(container);
3682
+ const existingHiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
3683
+ 'input[type="hidden"]'
3684
+ );
3685
+ const previousRid = (existingHiddenInput == null ? void 0 : existingHiddenInput.value) || null;
3451
3686
  ensureFileStyles();
3452
3687
  container.innerHTML = `
3453
3688
  <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:6px;padding:6px;">
@@ -3458,7 +3693,13 @@ async function handleFileSelect(opts) {
3458
3693
  try {
3459
3694
  rid = await uploadSingleFile(file, state);
3460
3695
  } catch (error) {
3461
- setEmptyFileContainer(container, state);
3696
+ if (previousRid && (deps == null ? void 0 : deps.onAfterUpload)) {
3697
+ deps.onAfterUpload(container, previousRid);
3698
+ } else if (deps == null ? void 0 : deps.onRemove) {
3699
+ deps.onRemove();
3700
+ } else {
3701
+ setEmptyFileContainer(container, state);
3702
+ }
3462
3703
  throw error;
3463
3704
  }
3464
3705
  state.resourceIndex.set(rid, {
@@ -3468,18 +3709,21 @@ async function handleFileSelect(opts) {
3468
3709
  uploadedAt: /* @__PURE__ */ new Date(),
3469
3710
  file
3470
3711
  });
3471
- let hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
3472
- 'input[type="hidden"]'
3473
- );
3712
+ if (previousRid && previousRid !== rid) {
3713
+ releaseLocalFileUrl((_b = state.resourceIndex.get(previousRid)) == null ? void 0 : _b.file);
3714
+ }
3715
+ let hiddenInput = existingHiddenInput;
3474
3716
  if (!hiddenInput) {
3475
3717
  hiddenInput = document.createElement("input");
3476
3718
  hiddenInput.type = "hidden";
3477
3719
  hiddenInput.name = fieldName;
3478
- (_b = container.parentElement) == null ? void 0 : _b.appendChild(hiddenInput);
3720
+ (_c = container.parentElement) == null ? void 0 : _c.appendChild(hiddenInput);
3479
3721
  }
3480
3722
  hiddenInput.value = rid;
3481
3723
  const isVideo = file.type.startsWith("video/");
3482
- if (!isVideo && deps) {
3724
+ if (!isVideo && (deps == null ? void 0 : deps.onAfterUpload)) {
3725
+ deps.onAfterUpload(container, rid);
3726
+ } else if (!isVideo && deps) {
3483
3727
  renderSingleFileEditTile(container, rid, state, deps).catch(console.error);
3484
3728
  } else {
3485
3729
  renderFilePreview(container, rid, state, {
@@ -3508,7 +3752,9 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
3508
3752
  const rejectedBySize = afterMime.filter(
3509
3753
  (f) => !isFileSizeAllowed(f, constraints.maxSize)
3510
3754
  );
3511
- const valid = afterMime.filter((f) => isFileSizeAllowed(f, constraints.maxSize));
3755
+ const valid = afterMime.filter(
3756
+ (f) => isFileSizeAllowed(f, constraints.maxSize)
3757
+ );
3512
3758
  const remaining = constraints.maxCount === Infinity ? valid.length : Math.max(0, constraints.maxCount - currentCount);
3513
3759
  const accepted = valid.slice(0, remaining);
3514
3760
  const skippedByCount = valid.length - accepted.length;
@@ -3521,7 +3767,13 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
3521
3767
  if (rejectedByMime.length > 0) {
3522
3768
  const mimes = constraints.allowedMimes.join(", ");
3523
3769
  const names = rejectedByMime.map((f) => f.name).join(", ");
3524
- errorParts.push(t("invalidFileMime", state, { name: names, type: rejectedByMime.map((f) => f.type).join(", "), mimes }));
3770
+ errorParts.push(
3771
+ t("invalidFileMime", state, {
3772
+ name: names,
3773
+ type: rejectedByMime.map((f) => f.type).join(", "),
3774
+ mimes
3775
+ })
3776
+ );
3525
3777
  }
3526
3778
  if (rejectedBySize.length > 0) {
3527
3779
  const names = rejectedBySize.map((f) => f.name).join(", ");
@@ -3540,17 +3792,19 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
3540
3792
  return { accepted, errorMessage: errorParts.join(" \u2022 ") };
3541
3793
  }
3542
3794
  async function uploadBatch(accepted, resourceIds, listEl, state) {
3543
- await Promise.all(
3795
+ var _a;
3796
+ if (listEl) {
3797
+ const tilesWrap = ensureTilesWrap(listEl);
3798
+ const addTile = (_a = tilesWrap.querySelector(".fb-multi-add-tile-js")) != null ? _a : tilesWrap.querySelector(".fb-tile-add");
3799
+ if (addTile) addTile.style.display = "none";
3800
+ }
3801
+ const failures = [];
3802
+ await Promise.allSettled(
3544
3803
  accepted.map(async (file) => {
3545
3804
  const placeholder = createUploadingTile(file.name, state);
3546
3805
  if (listEl) {
3547
3806
  const tilesWrap = ensureTilesWrap(listEl);
3548
- const addTile = tilesWrap.querySelector(".fb-tile-add");
3549
- if (addTile) {
3550
- tilesWrap.insertBefore(placeholder, addTile);
3551
- } else {
3552
- tilesWrap.appendChild(placeholder);
3553
- }
3807
+ tilesWrap.appendChild(placeholder);
3554
3808
  }
3555
3809
  try {
3556
3810
  const rid = await uploadSingleFile(file, state);
@@ -3562,11 +3816,27 @@ async function uploadBatch(accepted, resourceIds, listEl, state) {
3562
3816
  file: void 0
3563
3817
  });
3564
3818
  resourceIds.push(rid);
3819
+ } catch (err) {
3820
+ const wrapped = err instanceof Error ? err : new Error(String(err));
3821
+ const cause = wrapped.cause;
3822
+ const root = cause instanceof Error ? cause : cause !== void 0 ? new Error(String(cause)) : wrapped;
3823
+ failures.push({ file, error: root });
3565
3824
  } finally {
3566
3825
  placeholder.remove();
3567
3826
  }
3568
3827
  })
3569
3828
  );
3829
+ return { failures };
3830
+ }
3831
+ function buildBatchErrorMessage(filterError, failures, state) {
3832
+ if (failures.length === 0) return filterError;
3833
+ const uploadMsg = failures.map(
3834
+ (f) => t("uploadFailed", state, {
3835
+ name: f.file.name,
3836
+ error: f.error.message
3837
+ })
3838
+ ).join(" \u2022 ");
3839
+ return filterError ? `${filterError} \u2022 ${uploadMsg}` : uploadMsg;
3570
3840
  }
3571
3841
  function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallback, constraints, pathKey, instance) {
3572
3842
  setupDragAndDrop(filesContainer, async (files) => {
@@ -3583,7 +3853,13 @@ function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallbac
3583
3853
  clearFileError(filesContainer);
3584
3854
  }
3585
3855
  const list = (_a = filesContainer.querySelector(".files-list")) != null ? _a : filesContainer;
3586
- await uploadBatch(accepted, resourceIds, list, state);
3856
+ const { failures } = await uploadBatch(accepted, resourceIds, list, state);
3857
+ const combined = buildBatchErrorMessage(errorMessage, failures, state);
3858
+ if (combined) {
3859
+ showFileError(filesContainer, combined);
3860
+ } else {
3861
+ clearFileError(filesContainer);
3862
+ }
3587
3863
  updateCallback();
3588
3864
  if (instance && pathKey && !state.config.readonly) {
3589
3865
  instance.triggerOnChange(pathKey, resourceIds);
@@ -3593,7 +3869,7 @@ function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallbac
3593
3869
  function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback, constraints, pathKey, instance) {
3594
3870
  filesPicker.onchange = async () => {
3595
3871
  if (!filesPicker.files) return;
3596
- const wrapperEl = filesPicker.closest(".space-y-2") || filesPicker.parentElement;
3872
+ const wrapperEl = filesPicker.closest("[data-files-wrapper]") || filesPicker.parentElement;
3597
3873
  const { accepted, errorMessage } = filterAndSlice(
3598
3874
  Array.from(filesPicker.files),
3599
3875
  resourceIds.length,
@@ -3606,7 +3882,20 @@ function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback
3606
3882
  clearFileError(wrapperEl);
3607
3883
  }
3608
3884
  const listEl = wrapperEl == null ? void 0 : wrapperEl.querySelector(".files-list");
3609
- await uploadBatch(accepted, resourceIds, listEl != null ? listEl : null, state);
3885
+ const { failures } = await uploadBatch(
3886
+ accepted,
3887
+ resourceIds,
3888
+ listEl != null ? listEl : null,
3889
+ state
3890
+ );
3891
+ if (wrapperEl) {
3892
+ const combined = buildBatchErrorMessage(errorMessage, failures, state);
3893
+ if (combined) {
3894
+ showFileError(wrapperEl, combined);
3895
+ } else {
3896
+ clearFileError(wrapperEl);
3897
+ }
3898
+ }
3610
3899
  updateCallback();
3611
3900
  filesPicker.value = "";
3612
3901
  if (instance && pathKey && !state.config.readonly) {
@@ -3639,10 +3928,17 @@ function validatePickedResource(resource, allowedExtensions, allowedMimes, maxSi
3639
3928
  }
3640
3929
  if (!isMimeAllowed(resource.type, allowedMimes)) {
3641
3930
  const mimes = allowedMimes.join(", ");
3642
- return t("invalidFileMime", state, { name: resource.name, type: resource.type, mimes });
3931
+ return t("invalidFileMime", state, {
3932
+ name: resource.name,
3933
+ type: resource.type,
3934
+ mimes
3935
+ });
3643
3936
  }
3644
3937
  if (!isSizeWithinLimit(resource.size, maxSizeMB)) {
3645
- return t("fileTooLarge", state, { name: resource.name, maxSize: maxSizeMB });
3938
+ return t("fileTooLarge", state, {
3939
+ name: resource.name,
3940
+ maxSize: maxSizeMB
3941
+ });
3646
3942
  }
3647
3943
  return null;
3648
3944
  }
@@ -3703,7 +3999,13 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
3703
3999
  return true;
3704
4000
  });
3705
4001
  const validItems = deduped.filter((r) => {
3706
- const err = validatePickedResource(r, allowedExtensions, allowedMimes, maxSizeMB, state);
4002
+ const err = validatePickedResource(
4003
+ r,
4004
+ allowedExtensions,
4005
+ allowedMimes,
4006
+ maxSizeMB,
4007
+ state
4008
+ );
3707
4009
  return err === null;
3708
4010
  });
3709
4011
  const freshRemaining = maxCount === Infinity ? validItems.length : Math.max(0, maxCount - resourceIds.length);
@@ -3728,7 +4030,7 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
3728
4030
  }
3729
4031
  }
3730
4032
  async function handleLibraryPickSingle(state, element, container, fileWrapper, pathKey, fieldPath, renderCallback, instance) {
3731
- var _a;
4033
+ var _a, _b;
3732
4034
  if (!state.config.pickExistingFiles) return;
3733
4035
  const allowedExtensions = getAllowedExtensions(element.accept);
3734
4036
  const allowedMimes = getAllowedMimes(element.accept);
@@ -3748,20 +4050,32 @@ async function handleLibraryPickSingle(state, element, container, fileWrapper, p
3748
4050
  }
3749
4051
  if (picked.length === 0) return;
3750
4052
  const first = picked[0];
3751
- const validationError = validatePickedResource(first, allowedExtensions, allowedMimes, maxSizeMB, state);
4053
+ const validationError = validatePickedResource(
4054
+ first,
4055
+ allowedExtensions,
4056
+ allowedMimes,
4057
+ maxSizeMB,
4058
+ state
4059
+ );
3752
4060
  if (validationError !== null) {
3753
4061
  showFileError(container, validationError);
3754
4062
  return;
3755
4063
  }
3756
4064
  clearFileError(container);
3757
4065
  registerPickedResource(first, state);
3758
- let hiddenInput = fileWrapper.querySelector('input[type="hidden"]');
4066
+ let hiddenInput = fileWrapper.querySelector(
4067
+ 'input[type="hidden"]'
4068
+ );
3759
4069
  if (!hiddenInput) {
3760
4070
  hiddenInput = document.createElement("input");
3761
4071
  hiddenInput.type = "hidden";
3762
4072
  hiddenInput.name = pathKey;
3763
4073
  fileWrapper.appendChild(hiddenInput);
3764
4074
  }
4075
+ const previousRid = hiddenInput.value || null;
4076
+ if (previousRid && previousRid !== first.resourceId) {
4077
+ releaseLocalFileUrl((_b = state.resourceIndex.get(previousRid)) == null ? void 0 : _b.file);
4078
+ }
3765
4079
  hiddenInput.value = first.resourceId;
3766
4080
  await renderCallback(first.resourceId);
3767
4081
  if (!state.config.readonly) {
@@ -3770,7 +4084,9 @@ async function handleLibraryPickSingle(state, element, container, fileWrapper, p
3770
4084
  }
3771
4085
 
3772
4086
  // src/components/file/render-edit.ts
3773
- function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps) {
4087
+ var ICON_CLOUD = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 18a4 4 0 000-8 6 6 0 00-11.5 2A4 4 0 006 20h11M12 12v7M12 12l-3 3M12 12l3 3"/></svg>`;
4088
+ var ICON_LIBRARY2 = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h4v16H4z"/><path d="M10 4h4v16h-4z"/><path d="M16 5l3.5 1-3 14L13 19"/></svg>`;
4089
+ function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps, extras) {
3774
4090
  var _a;
3775
4091
  seedInferredResource(initial, state.resourceIndex);
3776
4092
  const meta = state.resourceIndex.get(initial);
@@ -3782,7 +4098,7 @@ function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, sta
3782
4098
  deps
3783
4099
  }).catch(console.error);
3784
4100
  } else {
3785
- renderSingleFileEditTile(fileContainer, initial, state, deps).catch(console.error);
4101
+ renderSingleFileFilled(fileContainer, initial, state, deps, extras);
3786
4102
  }
3787
4103
  const hiddenInput = document.createElement("input");
3788
4104
  hiddenInput.type = "hidden";
@@ -3790,38 +4106,255 @@ function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, sta
3790
4106
  hiddenInput.value = initial;
3791
4107
  fileWrapper.appendChild(hiddenInput);
3792
4108
  }
3793
- 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);">
3794
- <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"/>
3795
- </svg>`;
3796
- function buildEmptyDropzone(state, primaryText, subHint, openPicker) {
3797
- const dropzone = document.createElement("div");
3798
- dropzone.className = "fb-file-dropzone";
3799
- dropzone.innerHTML = `
3800
- ${UPLOAD_SVG}
3801
- <div class="fb-dropzone-primary-text">${escapeHtml(primaryText)}</div>
3802
- ${subHint ? `<div class="fb-dropzone-hint-text">${escapeHtml(subHint)}</div>` : ""}
3803
- `;
3804
- dropzone.onclick = openPicker;
3805
- return dropzone;
4109
+ function buildWideTile(state, hasLibrary, onUploadClick, onLibraryClick, isDragOver = false, constraintHint = "") {
4110
+ ensureFileStyles();
4111
+ const outer = document.createElement("div");
4112
+ outer.className = `fb-wide-tile${hasLibrary ? " fb-file-card-row" : ""}${isDragOver ? " fb-drag-over" : ""}`;
4113
+ const uploadBtn = document.createElement("button");
4114
+ uploadBtn.type = "button";
4115
+ uploadBtn.className = "fb-wide-tile-upload fb-file-dropzone";
4116
+ const cloudIcon = document.createElement("span");
4117
+ cloudIcon.style.cssText = "width:36px;height:36px;display:block;flex-shrink:0;";
4118
+ cloudIcon.innerHTML = ICON_CLOUD;
4119
+ uploadBtn.appendChild(cloudIcon);
4120
+ const primaryText = document.createElement("div");
4121
+ primaryText.className = "fb-wide-tile-label";
4122
+ primaryText.style.cssText = "font-size:14px;font-weight:600;";
4123
+ primaryText.textContent = isDragOver ? t("dropToUpload", state) : t("clickDragText", state);
4124
+ uploadBtn.appendChild(primaryText);
4125
+ if (constraintHint) {
4126
+ const hintEl = document.createElement("div");
4127
+ hintEl.style.cssText = "font-size:11px;opacity:0.65;margin-top:2px;";
4128
+ hintEl.textContent = constraintHint;
4129
+ uploadBtn.appendChild(hintEl);
4130
+ }
4131
+ uploadBtn.onclick = (e) => {
4132
+ e.stopPropagation();
4133
+ onUploadClick();
4134
+ };
4135
+ outer.appendChild(uploadBtn);
4136
+ if (hasLibrary && onLibraryClick) {
4137
+ const divider = document.createElement("div");
4138
+ divider.className = "fb-wide-tile-divider";
4139
+ outer.appendChild(divider);
4140
+ const libBtn = document.createElement("button");
4141
+ libBtn.type = "button";
4142
+ libBtn.className = "fb-wide-tile-library fb-file-library-card";
4143
+ const libIcon = document.createElement("span");
4144
+ libIcon.style.cssText = "width:28px;height:28px;display:block;flex-shrink:0;";
4145
+ libIcon.innerHTML = ICON_LIBRARY2;
4146
+ libBtn.appendChild(libIcon);
4147
+ const libLabel = document.createElement("div");
4148
+ libLabel.style.cssText = "font-size:13px;font-weight:600;text-align:center;";
4149
+ libLabel.textContent = t("fromLibrary", state);
4150
+ libBtn.appendChild(libLabel);
4151
+ const libHint = document.createElement("div");
4152
+ libHint.style.cssText = "font-size:11px;opacity:0.75;text-align:center;";
4153
+ libHint.textContent = t("libraryHint", state);
4154
+ libBtn.appendChild(libHint);
4155
+ libBtn.onclick = (e) => {
4156
+ e.stopPropagation();
4157
+ onLibraryClick();
4158
+ };
4159
+ outer.appendChild(libBtn);
4160
+ }
4161
+ attachDragOverFeedback(outer, {
4162
+ onEnter: () => {
4163
+ const primaryText2 = outer.querySelector(".fb-wide-tile-label");
4164
+ if (primaryText2) primaryText2.textContent = t("dropToUpload", state);
4165
+ },
4166
+ onLeave: () => {
4167
+ const primaryText2 = outer.querySelector(".fb-wide-tile-label");
4168
+ if (primaryText2) primaryText2.textContent = t("clickDragText", state);
4169
+ },
4170
+ activeClass: "fb-drag-over"
4171
+ });
4172
+ return outer;
3806
4173
  }
3807
- function buildLibraryButton(variant, state, onClick) {
3808
- const btn = document.createElement("button");
3809
- btn.type = "button";
3810
- btn.className = variant === "card" ? "fb-file-library-card" : "fb-tile fb-tile-add-library";
3811
- if (variant === "card") {
3812
- btn.innerHTML = `
3813
- <span class="fb-file-library-card-icon" aria-hidden="true">\u{1F4DA}</span>
3814
- <span class="fb-file-library-card-label">${escapeHtml(t("fromLibrary", state))}</span>
3815
- <span class="fb-file-library-card-hint">${escapeHtml(t("libraryHint", state))}</span>
3816
- `;
4174
+ function attachDragOverFeedback(el, hooks) {
4175
+ let depth = 0;
4176
+ el.addEventListener("dragover", (e) => {
4177
+ e.preventDefault();
4178
+ });
4179
+ el.addEventListener("dragenter", (e) => {
4180
+ e.preventDefault();
4181
+ depth++;
4182
+ if (depth === 1) {
4183
+ el.classList.add(hooks.activeClass);
4184
+ hooks.onEnter();
4185
+ }
4186
+ });
4187
+ el.addEventListener("dragleave", (e) => {
4188
+ e.preventDefault();
4189
+ depth = Math.max(0, depth - 1);
4190
+ if (depth === 0) {
4191
+ el.classList.remove(hooks.activeClass);
4192
+ hooks.onLeave();
4193
+ }
4194
+ });
4195
+ el.addEventListener("drop", () => {
4196
+ depth = 0;
4197
+ el.classList.remove(hooks.activeClass);
4198
+ hooks.onLeave();
4199
+ });
4200
+ }
4201
+ function renderSingleFileFilled(fileContainer, resourceId, state, deps, extras) {
4202
+ var _a, _b;
4203
+ const meta = state.resourceIndex.get(resourceId);
4204
+ const isVideo = (_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/");
4205
+ if (isVideo) {
4206
+ renderFilePreview(fileContainer, resourceId, state, {
4207
+ fileName: (_b = meta == null ? void 0 : meta.name) != null ? _b : "",
4208
+ isReadonly: false,
4209
+ deps
4210
+ }).catch(console.error);
4211
+ return;
4212
+ }
4213
+ ensureFileStyles();
4214
+ const outer = document.createElement("div");
4215
+ outer.className = "fb-multi-outer fb-multi-has-files";
4216
+ const grid = document.createElement("div");
4217
+ grid.className = "fb-multi-grid fb-tiles-wrap";
4218
+ outer.appendChild(grid);
4219
+ const tile = buildPreviewTile(
4220
+ resourceId,
4221
+ state,
4222
+ Boolean(deps.onRemove),
4223
+ deps.onRemove ? () => {
4224
+ var _a2;
4225
+ return (_a2 = deps.onRemove) == null ? void 0 : _a2.call(deps);
4226
+ } : null,
4227
+ extras
4228
+ );
4229
+ grid.appendChild(tile);
4230
+ fileContainer.className = "file-preview-container";
4231
+ fileContainer.removeAttribute("style");
4232
+ while (fileContainer.firstChild)
4233
+ fileContainer.removeChild(fileContainer.firstChild);
4234
+ fileContainer.appendChild(outer);
4235
+ }
4236
+ function buildMultiAddTile(state, hasLibrary, onUploadClick, onLibraryClick, isDragOver = false) {
4237
+ const tile = document.createElement("div");
4238
+ tile.className = `fb-multi-add-tile fb-multi-add-tile-js${isDragOver ? " fb-drag-over-tile" : ""}`;
4239
+ const uploadBtn = document.createElement("button");
4240
+ uploadBtn.type = "button";
4241
+ uploadBtn.className = "fb-multi-add-upload fb-tile-add fb-file-dropzone";
4242
+ const cloudIcon = document.createElement("span");
4243
+ cloudIcon.style.cssText = "width:28px;height:28px;display:block;flex-shrink:0;";
4244
+ cloudIcon.innerHTML = ICON_CLOUD;
4245
+ uploadBtn.appendChild(cloudIcon);
4246
+ const uploadLabel = document.createElement("span");
4247
+ uploadLabel.className = "fb-multi-add-label";
4248
+ uploadLabel.style.cssText = "font-size:11px;font-weight:600;";
4249
+ uploadLabel.textContent = isDragOver ? t("dropToUpload", state) : t("clickDragTextMultiple", state);
4250
+ uploadBtn.appendChild(uploadLabel);
4251
+ uploadBtn.onclick = (e) => {
4252
+ e.stopPropagation();
4253
+ onUploadClick();
4254
+ };
4255
+ tile.appendChild(uploadBtn);
4256
+ if (hasLibrary && onLibraryClick) {
4257
+ const divider = document.createElement("div");
4258
+ divider.className = "fb-multi-add-divider";
4259
+ tile.appendChild(divider);
4260
+ const libBtn = document.createElement("button");
4261
+ libBtn.type = "button";
4262
+ libBtn.className = "fb-multi-add-library fb-tile-add-library fb-file-library-card";
4263
+ libBtn.setAttribute("aria-label", t("fromLibrary", state));
4264
+ const libIcon = document.createElement("span");
4265
+ libIcon.style.cssText = "width:14px;height:14px;display:block;flex-shrink:0;";
4266
+ libIcon.innerHTML = ICON_LIBRARY2;
4267
+ libBtn.appendChild(libIcon);
4268
+ libBtn.appendChild(document.createTextNode(t("fromLibrary", state)));
4269
+ libBtn.onclick = (e) => {
4270
+ e.stopPropagation();
4271
+ onLibraryClick();
4272
+ };
4273
+ tile.appendChild(libBtn);
4274
+ }
4275
+ return tile;
4276
+ }
4277
+ function buildPreviewTile(rid, state, canRemove, onRemove, extras) {
4278
+ var _a, _b, _c;
4279
+ ensureFileStyles();
4280
+ const meta = state.resourceIndex.get(rid);
4281
+ const tile = document.createElement("div");
4282
+ tile.className = "fb-preview-tile fb-checker fb-tile-resource resource-pill";
4283
+ tile.dataset.resourceId = rid;
4284
+ const actionsEl = createTileActions({
4285
+ canRemove: canRemove && onRemove !== null,
4286
+ removeHandler: onRemove,
4287
+ state,
4288
+ resourceId: rid,
4289
+ fileName: (_a = meta == null ? void 0 : meta.name) != null ? _a : "",
4290
+ meta,
4291
+ replaceHandler: (_b = extras == null ? void 0 : extras.replaceHandler) != null ? _b : null,
4292
+ libraryHandler: (_c = extras == null ? void 0 : extras.libraryHandler) != null ? _c : null
4293
+ });
4294
+ fillTileContent(tile, rid, meta, state, actionsEl).catch((err) => {
4295
+ console.error("Failed to render tile:", err);
4296
+ });
4297
+ return tile;
4298
+ }
4299
+ function buildPlaceholderTile(isDragOver = false) {
4300
+ const div = document.createElement("div");
4301
+ div.className = `fb-multi-placeholder fb-checker${isDragOver ? " fb-drag-over" : ""}`;
4302
+ return div;
4303
+ }
4304
+ function buildMetaLine(state, element, ridCount, maxCount, canClearAll, onClearAll) {
4305
+ const line = document.createElement("div");
4306
+ line.className = "fb-meta-line";
4307
+ const metaText = document.createElement("div");
4308
+ metaText.className = "fb-meta-text";
4309
+ if (element.maxSize && element.maxSize !== Infinity) {
4310
+ const sizeSpan = document.createElement("span");
4311
+ sizeSpan.textContent = t("hintMaxSize", state, { size: element.maxSize });
4312
+ metaText.appendChild(sizeSpan);
4313
+ metaText.appendChild(buildMetaDot());
4314
+ }
4315
+ const exts = getAllowedExtensions(
4316
+ element.accept
4317
+ );
4318
+ if (exts.length > 0) {
4319
+ const fmtSpan = document.createElement("span");
4320
+ fmtSpan.className = "fb-meta-mono";
4321
+ fmtSpan.textContent = exts.map((e) => e.toUpperCase()).join(", ");
4322
+ metaText.appendChild(fmtSpan);
4323
+ metaText.appendChild(buildMetaDot());
4324
+ }
4325
+ const countSpan = document.createElement("span");
4326
+ if (maxCount < Infinity) {
4327
+ countSpan.textContent = t("fileCountWithMax", state, {
4328
+ count: ridCount,
4329
+ max: maxCount
4330
+ });
3817
4331
  } else {
3818
- btn.innerHTML = `<span aria-hidden="true">\u{1F4DA}</span>`;
3819
- btn.title = t("fromLibrary", state);
3820
- btn.setAttribute("aria-label", t("fromLibrary", state));
4332
+ const countKey = ridCount === 1 ? "fileCountSingle" : "fileCountPlural";
4333
+ countSpan.textContent = t(countKey, state, { count: ridCount });
4334
+ }
4335
+ metaText.appendChild(countSpan);
4336
+ line.appendChild(metaText);
4337
+ if (canClearAll && ridCount > 1) {
4338
+ const clearBtn = document.createElement("button");
4339
+ clearBtn.type = "button";
4340
+ clearBtn.className = "fb-clear-all-btn";
4341
+ clearBtn.textContent = t("clearAll", state);
4342
+ clearBtn.onclick = (e) => {
4343
+ e.stopPropagation();
4344
+ if (window.confirm(t("clearAll", state) + "?")) {
4345
+ onClearAll();
4346
+ }
4347
+ };
4348
+ line.appendChild(clearBtn);
3821
4349
  }
3822
- btn.addEventListener("click", onClick);
3823
- return btn;
4350
+ return line;
4351
+ }
4352
+ function buildMetaDot() {
4353
+ const dot = document.createElement("span");
4354
+ dot.className = "fb-meta-dot";
4355
+ return dot;
3824
4356
  }
4357
+ var gridResizeObservers = /* @__PURE__ */ new WeakMap();
3825
4358
  function renderResourcePills(opts) {
3826
4359
  var _a;
3827
4360
  const {
@@ -3829,130 +4362,185 @@ function renderResourcePills(opts) {
3829
4362
  rids,
3830
4363
  state,
3831
4364
  onRemove,
3832
- hint,
3833
- countInfo,
3834
4365
  maxCount,
3835
4366
  isReadonly = false,
3836
- onLibraryPick
4367
+ onLibraryPick,
4368
+ element,
4369
+ onClearAll,
4370
+ openPicker: openPickerProp
3837
4371
  } = opts;
3838
4372
  ensureFileStyles();
3839
4373
  const wrapper = container.closest("[data-files-wrapper]");
3840
4374
  if (wrapper) {
3841
4375
  wrapper.dataset.resourceIds = JSON.stringify(rids != null ? rids : []);
3842
4376
  }
4377
+ const previousObserver = gridResizeObservers.get(container);
4378
+ if (previousObserver) {
4379
+ previousObserver.disconnect();
4380
+ gridResizeObservers.delete(container);
4381
+ }
3843
4382
  while (container.firstChild) container.removeChild(container.firstChild);
3844
4383
  const ridList = rids != null ? rids : [];
3845
- const atMax = maxCount !== void 0 && ridList.length >= maxCount;
4384
+ const effectiveMax = maxCount != null ? maxCount : Infinity;
4385
+ const atMax = effectiveMax !== Infinity && ridList.length >= effectiveMax;
3846
4386
  const hasLibrary = !isReadonly && typeof onLibraryPick === "function";
3847
- const buildSubHint = () => {
3848
- const parts = [];
3849
- if (hint) parts.push(hint);
3850
- if (countInfo) parts.push(countInfo);
3851
- return parts.join(" \u2022 ");
3852
- };
3853
- const openPicker = () => {
3854
- const picker = findFilePicker(container);
3855
- if (picker) picker.click();
3856
- };
3857
- if (ridList.length === 0) {
3858
- if (isReadonly) {
4387
+ const openPicker = openPickerProp != null ? openPickerProp : (() => {
4388
+ var _a2;
4389
+ const pickerEl = (_a2 = container.closest("[data-files-wrapper]")) == null ? void 0 : _a2.querySelector('input[type="file"]');
4390
+ if (pickerEl) pickerEl.click();
4391
+ });
4392
+ if (isReadonly) {
4393
+ if (ridList.length === 0) {
3859
4394
  const emptyEl = document.createElement("div");
3860
4395
  emptyEl.className = "fb-tile-empty-text";
3861
4396
  emptyEl.textContent = t("noFilesSelected", state);
3862
4397
  container.appendChild(emptyEl);
3863
- } else if (hasLibrary) {
3864
- const row = document.createElement("div");
3865
- row.className = "fb-file-card-row";
3866
- const dropzone = buildEmptyDropzone(
3867
- state,
3868
- t("clickDragTextMultiple", state),
3869
- buildSubHint(),
3870
- openPicker
3871
- );
3872
- const libraryBtn = buildLibraryButton("card", state, onLibraryPick);
3873
- row.appendChild(dropzone);
3874
- row.appendChild(libraryBtn);
3875
- container.appendChild(row);
3876
4398
  } else {
3877
- const dropzone = buildEmptyDropzone(
3878
- state,
3879
- t("clickDragTextMultiple", state),
3880
- buildSubHint(),
3881
- openPicker
3882
- );
3883
- container.appendChild(dropzone);
4399
+ const grid2 = document.createElement("div");
4400
+ grid2.className = "fb-multi-readonly-grid";
4401
+ container.appendChild(grid2);
4402
+ for (const rid of ridList) {
4403
+ const meta = state.resourceIndex.get(rid);
4404
+ const tile = document.createElement("div");
4405
+ tile.className = "fb-readonly-tile fb-checker fb-tile fb-tile-resource";
4406
+ tile.dataset.resourceId = rid;
4407
+ const actionsEl = createTileActions({
4408
+ canRemove: false,
4409
+ removeHandler: null,
4410
+ state,
4411
+ resourceId: rid,
4412
+ fileName: (_a = meta == null ? void 0 : meta.name) != null ? _a : "",
4413
+ meta
4414
+ });
4415
+ fillTileContent(tile, rid, meta, state, actionsEl).catch(console.error);
4416
+ tile.onclick = async () => {
4417
+ var _a2;
4418
+ let url = null;
4419
+ if (state.config.getDownloadUrl) {
4420
+ url = state.config.getDownloadUrl(rid);
4421
+ } else if (state.config.getThumbnail) {
4422
+ url = await state.config.getThumbnail(rid);
4423
+ } else if ((meta == null ? void 0 : meta.file) instanceof File) {
4424
+ url = URL.createObjectURL(meta.file);
4425
+ }
4426
+ if (url) {
4427
+ window.open(url, "_blank");
4428
+ } else if (state.config.downloadFile) {
4429
+ state.config.downloadFile(rid, (_a2 = meta == null ? void 0 : meta.name) != null ? _a2 : "");
4430
+ }
4431
+ };
4432
+ grid2.appendChild(tile);
4433
+ }
3884
4434
  }
3885
4435
  return;
3886
4436
  }
3887
- const tilesWrap = document.createElement("div");
3888
- tilesWrap.className = "fb-tiles-wrap";
3889
- tilesWrap.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;";
3890
- for (const rid of ridList) {
3891
- const meta = state.resourceIndex.get(rid);
3892
- const tile = createFileTile();
3893
- tile.classList.add("fb-tile-resource", "resource-pill");
3894
- tile.dataset.resourceId = rid;
3895
- const actionsEl = createTileActions({
3896
- canRemove: !isReadonly && onRemove !== null,
3897
- removeHandler: onRemove ? () => onRemove(rid) : null,
4437
+ const outerDiv = document.createElement("div");
4438
+ outerDiv.className = `fb-multi-outer${ridList.length > 0 ? " fb-multi-has-files" : ""}`;
4439
+ const grid = document.createElement("div");
4440
+ grid.className = "fb-multi-grid fb-tiles-wrap";
4441
+ outerDiv.appendChild(grid);
4442
+ container.appendChild(outerDiv);
4443
+ for (let i = 0; i < ridList.length; i++) {
4444
+ const rid = ridList[i];
4445
+ const tile = buildPreviewTile(
4446
+ rid,
3898
4447
  state,
3899
- resourceId: rid,
3900
- fileName: (_a = meta == null ? void 0 : meta.name) != null ? _a : ""
3901
- });
3902
- fillTileContent(tile, rid, meta, state, actionsEl).catch((err) => {
3903
- console.error("Failed to render tile:", err);
3904
- });
3905
- tilesWrap.appendChild(tile);
3906
- }
3907
- if (!isReadonly && !atMax) {
3908
- const addTile = document.createElement("div");
3909
- addTile.className = "fb-tile fb-tile-add";
3910
- addTile.innerHTML = "+";
3911
- addTile.onclick = openPicker;
3912
- tilesWrap.appendChild(addTile);
3913
- if (hasLibrary) {
3914
- const libraryTile = buildLibraryButton("tile", state, onLibraryPick);
3915
- tilesWrap.appendChild(libraryTile);
3916
- }
3917
- } else if (!isReadonly && atMax) {
3918
- const chip = document.createElement("div");
3919
- chip.className = "fb-tile-counter";
3920
- chip.textContent = t("filesCounter", state, {
3921
- count: ridList.length,
3922
- max: maxCount
3923
- });
3924
- tilesWrap.appendChild(chip);
4448
+ onRemove !== null,
4449
+ onRemove ? () => onRemove(rid) : null
4450
+ );
4451
+ grid.appendChild(tile);
3925
4452
  }
3926
- container.appendChild(tilesWrap);
3927
- const subHint = buildSubHint();
3928
- if (subHint) {
3929
- const hintEl = document.createElement("div");
3930
- hintEl.className = "fb-tile-hint";
3931
- hintEl.textContent = subHint;
3932
- container.appendChild(hintEl);
4453
+ if (!atMax) {
4454
+ const addTile = buildMultiAddTile(
4455
+ state,
4456
+ hasLibrary,
4457
+ openPicker,
4458
+ onLibraryPick != null ? onLibraryPick : null
4459
+ );
4460
+ grid.appendChild(addTile);
4461
+ }
4462
+ const occupied = ridList.length + (atMax ? 0 : 1);
4463
+ const adjustPlaceholders = () => {
4464
+ const tpl = getComputedStyle(grid).gridTemplateColumns;
4465
+ const cols = tpl ? tpl.split(" ").filter(Boolean).length : 0;
4466
+ if (!cols) return;
4467
+ const remainder = occupied % cols;
4468
+ const rowFill = remainder === 0 ? 0 : cols - remainder;
4469
+ const capacityRemaining = effectiveMax === Infinity ? rowFill : Math.max(0, effectiveMax - occupied);
4470
+ const needed = Math.min(rowFill, capacityRemaining);
4471
+ const existing = grid.querySelectorAll(".fb-multi-placeholder");
4472
+ if (existing.length > needed) {
4473
+ for (let i = existing.length - 1; i >= needed; i--) existing[i].remove();
4474
+ } else if (existing.length < needed) {
4475
+ for (let i = existing.length; i < needed; i++) {
4476
+ grid.appendChild(buildPlaceholderTile());
4477
+ }
4478
+ }
4479
+ };
4480
+ if (effectiveMax === Infinity || effectiveMax > occupied) {
4481
+ grid.appendChild(buildPlaceholderTile());
4482
+ }
4483
+ requestAnimationFrame(adjustPlaceholders);
4484
+ if (typeof ResizeObserver !== "undefined") {
4485
+ const ro = new ResizeObserver(() => adjustPlaceholders());
4486
+ ro.observe(grid);
4487
+ gridResizeObservers.set(container, ro);
4488
+ }
4489
+ attachDragOverFeedback(outerDiv, {
4490
+ activeClass: "fb-drag-over",
4491
+ onEnter: () => {
4492
+ grid.querySelectorAll(".fb-multi-placeholder").forEach((p) => {
4493
+ p.classList.add("fb-drag-over");
4494
+ });
4495
+ const addTile = grid.querySelector(".fb-multi-add-tile-js");
4496
+ if (addTile) {
4497
+ addTile.classList.add("fb-drag-over-tile");
4498
+ const label = addTile.querySelector(".fb-multi-add-label");
4499
+ if (label) label.textContent = t("dropToUpload", state);
4500
+ }
4501
+ },
4502
+ onLeave: () => {
4503
+ grid.querySelectorAll(".fb-multi-placeholder").forEach((p) => {
4504
+ p.classList.remove("fb-drag-over");
4505
+ });
4506
+ const addTile = grid.querySelector(".fb-multi-add-tile-js");
4507
+ if (addTile) {
4508
+ addTile.classList.remove("fb-drag-over-tile");
4509
+ const label = addTile.querySelector(".fb-multi-add-label");
4510
+ if (label) label.textContent = t("clickDragTextMultiple", state);
4511
+ }
4512
+ }
4513
+ });
4514
+ if (element) {
4515
+ const metaLine = buildMetaLine(
4516
+ state,
4517
+ element,
4518
+ ridList.length,
4519
+ effectiveMax,
4520
+ Boolean(onClearAll),
4521
+ onClearAll != null ? onClearAll : (() => {
4522
+ })
4523
+ );
4524
+ container.appendChild(metaLine);
3933
4525
  }
3934
4526
  }
3935
4527
  function renderFileElementEdit(element, ctx, wrapper, pathKey) {
3936
- var _a, _b, _c, _d, _e;
4528
+ var _a, _b;
3937
4529
  const state = ctx.state;
3938
4530
  const fileWrapper = document.createElement("div");
3939
4531
  fileWrapper.className = "space-y-2";
4532
+ fileWrapper.dataset.filesWrapper = pathKey;
3940
4533
  const picker = document.createElement("input");
3941
4534
  picker.type = "file";
3942
4535
  picker.name = pathKey;
3943
4536
  picker.style.display = "none";
3944
- if (element.accept) {
3945
- picker.accept = typeof element.accept === "string" ? element.accept : [
3946
- ...(_b = (_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`)) != null ? _b : [],
3947
- ...(_c = element.accept.mime) != null ? _c : []
3948
- ].join(",") || "";
3949
- }
4537
+ picker.accept = buildAcceptAttribute(element.accept);
3950
4538
  const fileContainer = document.createElement("div");
3951
4539
  fileContainer.className = "file-preview-container";
3952
4540
  const initial = ctx.prefill[element.key];
3953
4541
  const allowedExts = getAllowedExtensions(element.accept);
3954
4542
  const allowedMimes = getAllowedMimes(element.accept);
3955
- const maxSizeMB = (_d = element.maxSize) != null ? _d : Infinity;
4543
+ const maxSizeMB = (_a = element.maxSize) != null ? _a : Infinity;
3956
4544
  const handlers = {
3957
4545
  fileUploadHandler() {
3958
4546
  picker.click();
@@ -3975,17 +4563,11 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
3975
4563
  setupDrop(container) {
3976
4564
  setupDragAndDrop(container, handlers.dragHandler);
3977
4565
  },
3978
- restoreDropzone() {
3979
- const hint = makeFieldHint(element, state);
3980
- fileContainer.className = "file-preview-container w-full max-w-md bg-gray-100 rounded-lg overflow-hidden relative group cursor-pointer";
3981
- fileContainer.style.height = "128px";
3982
- setEmptyFileContainer(fileContainer, state, hint);
3983
- fileContainer.onclick = handlers.fileUploadHandler;
3984
- setupDragAndDrop(fileContainer, handlers.dragHandler);
3985
- },
3986
4566
  onRemove() {
3987
4567
  var _a2;
3988
- const hiddenInput = fileWrapper.querySelector('input[type="hidden"]');
4568
+ const hiddenInput = fileWrapper.querySelector(
4569
+ 'input[type="hidden"]'
4570
+ );
3989
4571
  const currentRid = hiddenInput == null ? void 0 : hiddenInput.value;
3990
4572
  if (currentRid) {
3991
4573
  releaseLocalFileUrl((_a2 = state.resourceIndex.get(currentRid)) == null ? void 0 : _a2.file);
@@ -3994,34 +4576,13 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
3994
4576
  renderEmptySingleState();
3995
4577
  }
3996
4578
  };
3997
- const buildDeps = () => ({
3998
- picker,
3999
- fileUploadHandler: handlers.fileUploadHandler,
4000
- dragHandler: handlers.dragHandler,
4001
- setupDrop: handlers.setupDrop,
4002
- onRemove: handlers.onRemove
4003
- });
4004
- const renderEmptySingleState = () => {
4005
- if (state.config.pickExistingFiles && !element.disableLibrary) {
4006
- fileContainer.className = "file-preview-container";
4007
- fileContainer.removeAttribute("style");
4008
- fileContainer.onclick = null;
4009
- while (fileContainer.firstChild) {
4010
- fileContainer.removeChild(fileContainer.firstChild);
4011
- }
4012
- const row = document.createElement("div");
4013
- row.className = "fb-file-card-row";
4014
- row.style.cssText = "display:flex;gap:8px;align-items:stretch;";
4015
- const hint = makeFieldHint(element, state);
4016
- const uploadCard = buildEmptyDropzone(
4017
- state,
4018
- t("clickDragText", state),
4019
- hint,
4020
- handlers.fileUploadHandler
4021
- );
4022
- uploadCard.style.cssText = "flex:1;min-width:0;height:128px;";
4023
- setupDragAndDrop(uploadCard, handlers.dragHandler);
4024
- const libraryBtn = buildLibraryButton("card", state, () => {
4579
+ const buildSingleExtras = () => {
4580
+ const hasLibrary = Boolean(
4581
+ state.config.pickExistingFiles && !element.disableLibrary
4582
+ );
4583
+ return {
4584
+ replaceHandler: state.config.uploadFile ? () => picker.click() : null,
4585
+ libraryHandler: hasLibrary ? () => {
4025
4586
  handleLibraryPickSingle(
4026
4587
  state,
4027
4588
  element,
@@ -4030,20 +4591,54 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
4030
4591
  pathKey,
4031
4592
  pathKey,
4032
4593
  async (rid) => {
4033
- await renderSingleFileEditTile(fileContainer, rid, state, buildDeps());
4594
+ renderSingleFileFilled(
4595
+ fileContainer,
4596
+ rid,
4597
+ state,
4598
+ buildDeps(),
4599
+ buildSingleExtras()
4600
+ );
4034
4601
  },
4035
4602
  ctx.instance
4036
4603
  ).catch((err) => {
4037
4604
  console.error("Library pick failed:", err);
4038
4605
  });
4039
- });
4040
- libraryBtn.style.cssText = "flex:1;min-width:0;";
4041
- row.appendChild(uploadCard);
4042
- row.appendChild(libraryBtn);
4043
- fileContainer.appendChild(row);
4044
- } else {
4045
- handlers.restoreDropzone();
4606
+ } : null
4607
+ };
4608
+ };
4609
+ const buildDeps = () => ({
4610
+ picker,
4611
+ fileUploadHandler: handlers.fileUploadHandler,
4612
+ dragHandler: handlers.dragHandler,
4613
+ setupDrop: handlers.setupDrop,
4614
+ onRemove: handlers.onRemove,
4615
+ onAfterUpload: (container, rid) => {
4616
+ renderSingleFileFilled(
4617
+ container,
4618
+ rid,
4619
+ state,
4620
+ buildDeps(),
4621
+ buildSingleExtras()
4622
+ );
4046
4623
  }
4624
+ });
4625
+ const renderEmptySingleState = () => {
4626
+ ensureFileStyles();
4627
+ fileContainer.className = "file-preview-container";
4628
+ fileContainer.removeAttribute("style");
4629
+ while (fileContainer.firstChild)
4630
+ fileContainer.removeChild(fileContainer.firstChild);
4631
+ const onLibraryClick = buildSingleExtras().libraryHandler;
4632
+ const wideTile = buildWideTile(
4633
+ state,
4634
+ onLibraryClick !== null,
4635
+ handlers.fileUploadHandler,
4636
+ onLibraryClick,
4637
+ false,
4638
+ makeFieldHint(element, state)
4639
+ );
4640
+ fileContainer.appendChild(wideTile);
4641
+ setupDragAndDrop(fileContainer, handlers.dragHandler);
4047
4642
  };
4048
4643
  if (initial) {
4049
4644
  handleInitialFileData(
@@ -4052,11 +4647,11 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
4052
4647
  pathKey,
4053
4648
  fileWrapper,
4054
4649
  state,
4055
- buildDeps()
4650
+ buildDeps(),
4651
+ buildSingleExtras()
4056
4652
  );
4057
4653
  const prefillMeta = state.resourceIndex.get(initial);
4058
- if ((_e = prefillMeta == null ? void 0 : prefillMeta.type) == null ? void 0 : _e.startsWith("video/")) {
4059
- fileContainer.onclick = handlers.fileUploadHandler;
4654
+ if ((_b = prefillMeta == null ? void 0 : prefillMeta.type) == null ? void 0 : _b.startsWith("video/")) {
4060
4655
  setupDragAndDrop(fileContainer, handlers.dragHandler);
4061
4656
  }
4062
4657
  } else {
@@ -4064,116 +4659,25 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
4064
4659
  }
4065
4660
  picker.onchange = () => {
4066
4661
  if (picker.files && picker.files.length > 0) {
4067
- handleFileSelect({
4068
- file: picker.files[0],
4069
- container: fileContainer,
4070
- fieldName: pathKey,
4071
- state,
4072
- deps: buildDeps(),
4073
- instance: ctx.instance,
4074
- allowedExtensions: allowedExts,
4075
- allowedMimes,
4076
- maxSizeMB
4077
- });
4662
+ handlers.dragHandler(picker.files);
4078
4663
  }
4079
4664
  };
4080
4665
  fileWrapper.appendChild(fileContainer);
4081
4666
  fileWrapper.appendChild(picker);
4082
4667
  wrapper.appendChild(fileWrapper);
4083
4668
  }
4084
- function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
4085
- var _a, _b, _c, _d;
4086
- const state = ctx.state;
4087
- const filesWrapper = document.createElement("div");
4088
- filesWrapper.className = "space-y-2";
4089
- filesWrapper.dataset.filesWrapper = pathKey;
4090
- const filesPicker = document.createElement("input");
4091
- filesPicker.type = "file";
4092
- filesPicker.name = pathKey;
4093
- filesPicker.multiple = true;
4094
- filesPicker.style.display = "none";
4095
- if (element.accept) {
4096
- filesPicker.accept = typeof element.accept === "string" ? element.accept : [
4097
- ...(_b = (_a = element.accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`)) != null ? _b : [],
4098
- ...(_c = element.accept.mime) != null ? _c : []
4099
- ].join(",") || "";
4100
- }
4101
- const filesContainer = document.createElement("div");
4102
- filesContainer.className = "files-list-wrapper";
4103
- filesContainer.style.cssText = "border:2px dashed var(--fb-file-upload-border-color,#d1d5db);border-radius:var(--fb-border-radius,0.5rem);padding:8px;transition:border-color var(--fb-transition-duration,200ms),background var(--fb-transition-duration,200ms);";
4104
- const list = document.createElement("div");
4105
- list.className = "files-list";
4106
- const initialFiles = ctx.prefill[element.key] || [];
4107
- addPrefillFilesToIndex(initialFiles, state.resourceIndex);
4108
- filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
4109
- const filesFieldHint = makeFieldHint(element, state);
4110
- const filesConstraints = {
4111
- maxCount: Infinity,
4112
- allowedExtensions: getAllowedExtensions(element.accept),
4113
- allowedMimes: getAllowedMimes(element.accept),
4114
- maxSize: (_d = element.maxSize) != null ? _d : Infinity
4115
- };
4116
- filesContainer.appendChild(list);
4117
- filesWrapper.appendChild(filesPicker);
4118
- filesWrapper.appendChild(filesContainer);
4119
- wrapper.appendChild(filesWrapper);
4120
- const onLibraryPickFiles = state.config.pickExistingFiles && !element.disableLibrary ? () => {
4121
- handleLibraryPickMulti(
4122
- state,
4123
- element,
4124
- filesWrapper,
4125
- pathKey,
4126
- initialFiles,
4127
- Infinity,
4128
- updateFilesList,
4129
- ctx.instance
4130
- ).catch((err) => {
4131
- console.error("Library pick failed:", err);
4132
- });
4133
- } : null;
4134
- function updateFilesList() {
4135
- const currentlyReadonly = isElementReadonly(element, state);
4136
- renderResourcePills({
4137
- container: list,
4138
- rids: initialFiles,
4139
- state,
4140
- onRemove: currentlyReadonly ? null : (ridToRemove) => {
4141
- var _a2;
4142
- releaseLocalFileUrl((_a2 = state.resourceIndex.get(ridToRemove)) == null ? void 0 : _a2.file);
4143
- const index = initialFiles.indexOf(ridToRemove);
4144
- if (index > -1) initialFiles.splice(index, 1);
4145
- updateFilesList();
4146
- },
4147
- hint: filesFieldHint,
4148
- isReadonly: currentlyReadonly,
4149
- onLibraryPick: currentlyReadonly ? null : onLibraryPickFiles
4150
- });
4151
- }
4152
- updateFilesList();
4153
- setupFilesDropHandler(
4154
- filesContainer,
4155
- initialFiles,
4156
- state,
4157
- updateFilesList,
4158
- filesConstraints,
4159
- pathKey,
4160
- ctx.instance
4161
- );
4162
- setupFilesPickerHandler(
4163
- filesPicker,
4164
- initialFiles,
4165
- state,
4166
- updateFilesList,
4167
- filesConstraints,
4168
- pathKey,
4169
- ctx.instance
4170
- );
4171
- }
4172
- function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
4173
- var _a, _b, _c, _d, _e, _f;
4669
+ function buildAcceptAttribute(accept) {
4670
+ var _a, _b, _c;
4671
+ if (!accept) return "";
4672
+ if (typeof accept === "string") return accept;
4673
+ return [
4674
+ ...(_b = (_a = accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`)) != null ? _b : [],
4675
+ ...(_c = accept.mime) != null ? _c : []
4676
+ ].join(",");
4677
+ }
4678
+ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
4679
+ var _a, _b;
4174
4680
  const state = ctx.state;
4175
- const minFiles = (_a = element.minCount) != null ? _a : 0;
4176
- const maxFiles = (_b = element.maxCount) != null ? _b : Infinity;
4177
4681
  const filesWrapper = document.createElement("div");
4178
4682
  filesWrapper.className = "space-y-2";
4179
4683
  filesWrapper.dataset.filesWrapper = pathKey;
@@ -4182,15 +4686,9 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
4182
4686
  filesPicker.name = pathKey;
4183
4687
  filesPicker.multiple = true;
4184
4688
  filesPicker.style.display = "none";
4185
- if (element.accept) {
4186
- filesPicker.accept = typeof element.accept === "string" ? element.accept : [
4187
- ...(_d = (_c = element.accept.extensions) == null ? void 0 : _c.map((ext) => `.${ext}`)) != null ? _d : [],
4188
- ...(_e = element.accept.mime) != null ? _e : []
4189
- ].join(",") || "";
4190
- }
4689
+ filesPicker.accept = buildAcceptAttribute(element.accept);
4191
4690
  const filesContainer = document.createElement("div");
4192
4691
  filesContainer.className = "files-list-wrapper";
4193
- filesContainer.style.cssText = "border:2px dashed var(--fb-file-upload-border-color,#d1d5db);border-radius:var(--fb-border-radius,0.5rem);padding:8px;transition:border-color var(--fb-transition-duration,200ms),background var(--fb-transition-duration,200ms);";
4194
4692
  const list = document.createElement("div");
4195
4693
  list.className = "files-list";
4196
4694
  filesWrapper.appendChild(filesPicker);
@@ -4199,19 +4697,18 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
4199
4697
  const initialFiles = Array.isArray(ctx.prefill[element.key]) ? [...ctx.prefill[element.key]] : [];
4200
4698
  addPrefillFilesToIndex(initialFiles, state.resourceIndex);
4201
4699
  filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
4202
- const multipleFilesHint = makeFieldHint(element, state);
4203
- const multipleConstraints = {
4700
+ const constraints = {
4204
4701
  maxCount: maxFiles,
4205
4702
  allowedExtensions: getAllowedExtensions(element.accept),
4206
4703
  allowedMimes: getAllowedMimes(element.accept),
4207
- maxSize: (_f = element.maxSize) != null ? _f : Infinity
4704
+ // Prefer schema's `maxSize`; fall back to legacy `maxSizeMB` for
4705
+ // backward compatibility (matches addFileSizeHint in validation.ts).
4706
+ maxSize: (_b = (_a = element.maxSize) != null ? _a : element.maxSizeMB) != null ? _b : Infinity
4208
4707
  };
4209
- const buildCountInfo = () => {
4210
- const countText = initialFiles.length === 1 ? t("fileCountSingle", state, { count: initialFiles.length }) : t("fileCountPlural", state, { count: initialFiles.length });
4211
- const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
4212
- return countText + minMaxText;
4708
+ const openPicker = () => {
4709
+ filesPicker.click();
4213
4710
  };
4214
- const onLibraryPickMultiple = state.config.pickExistingFiles && !element.disableLibrary ? () => {
4711
+ const onLibraryPick = state.config.pickExistingFiles && !element.disableLibrary ? () => {
4215
4712
  handleLibraryPickMulti(
4216
4713
  state,
4217
4714
  element,
@@ -4225,31 +4722,36 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
4225
4722
  console.error("Library pick failed:", err);
4226
4723
  });
4227
4724
  } : null;
4228
- const updateFilesDisplay = () => {
4725
+ function updateFilesDisplay() {
4229
4726
  const currentlyReadonly = isElementReadonly(element, state);
4230
4727
  renderResourcePills({
4231
4728
  container: list,
4232
4729
  rids: initialFiles,
4233
4730
  state,
4234
- onRemove: currentlyReadonly ? null : (index) => {
4731
+ onRemove: currentlyReadonly ? null : (ridToRemove) => {
4235
4732
  var _a2;
4236
- releaseLocalFileUrl((_a2 = state.resourceIndex.get(index)) == null ? void 0 : _a2.file);
4237
- initialFiles.splice(initialFiles.indexOf(index), 1);
4733
+ releaseLocalFileUrl((_a2 = state.resourceIndex.get(ridToRemove)) == null ? void 0 : _a2.file);
4734
+ const index = initialFiles.indexOf(ridToRemove);
4735
+ if (index > -1) initialFiles.splice(index, 1);
4238
4736
  updateFilesDisplay();
4239
4737
  },
4240
- hint: multipleFilesHint,
4241
- countInfo: buildCountInfo(),
4242
4738
  maxCount: maxFiles < Infinity ? maxFiles : void 0,
4243
4739
  isReadonly: currentlyReadonly,
4244
- onLibraryPick: currentlyReadonly ? null : onLibraryPickMultiple
4740
+ onLibraryPick: currentlyReadonly ? null : onLibraryPick,
4741
+ element,
4742
+ onClearAll: currentlyReadonly ? void 0 : () => {
4743
+ initialFiles.splice(0);
4744
+ updateFilesDisplay();
4745
+ },
4746
+ openPicker
4245
4747
  });
4246
- };
4748
+ }
4247
4749
  setupFilesDropHandler(
4248
4750
  filesContainer,
4249
4751
  initialFiles,
4250
4752
  state,
4251
4753
  updateFilesDisplay,
4252
- multipleConstraints,
4754
+ constraints,
4253
4755
  pathKey,
4254
4756
  ctx.instance
4255
4757
  );
@@ -4258,13 +4760,26 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
4258
4760
  initialFiles,
4259
4761
  state,
4260
4762
  updateFilesDisplay,
4261
- multipleConstraints,
4763
+ constraints,
4262
4764
  pathKey,
4263
4765
  ctx.instance
4264
4766
  );
4265
4767
  updateFilesDisplay();
4266
4768
  wrapper.appendChild(filesWrapper);
4267
4769
  }
4770
+ function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
4771
+ setupMultiFileEditMode(element, ctx, wrapper, pathKey, Infinity);
4772
+ }
4773
+ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
4774
+ var _a;
4775
+ setupMultiFileEditMode(
4776
+ element,
4777
+ ctx,
4778
+ wrapper,
4779
+ pathKey,
4780
+ (_a = element.maxCount) != null ? _a : Infinity
4781
+ );
4782
+ }
4268
4783
 
4269
4784
  // src/components/file/validate.ts
4270
4785
  function readMultiFileResourceIds(scopeRoot, fullKey) {
@@ -4391,33 +4906,36 @@ function renderFileElementReadonly(element, ctx, wrapper, pathKey) {
4391
4906
  hiddenInput.name = pathKey;
4392
4907
  hiddenInput.value = initial;
4393
4908
  wrapper.appendChild(hiddenInput);
4394
- renderFilePreviewReadonly(initial, state).then((filePreview) => {
4395
- wrapper.appendChild(filePreview);
4396
- }).catch((err) => {
4397
- console.error("Failed to render file preview:", err);
4398
- wrapper.appendChild(buildEmptyReadonlyTile(state));
4399
- });
4909
+ renderFilePreviewReadonly(initial, state).then((tile) => {
4910
+ tile.classList.add(
4911
+ "fb-single-readonly-filled",
4912
+ "fb-readonly-tile",
4913
+ "fb-checker"
4914
+ );
4915
+ wrapper.appendChild(tile);
4916
+ }).catch(console.error);
4400
4917
  } else {
4401
4918
  wrapper.appendChild(buildEmptyReadonlyTile(state));
4402
4919
  }
4403
4920
  }
4404
4921
  function buildEmptyReadonlyTile(state) {
4922
+ ensureFileStyles();
4405
4923
  const emptyState = document.createElement("div");
4406
4924
  emptyState.style.cssText = `
4407
- width:${TILE_SIZE};
4408
- height:${TILE_SIZE};
4925
+ height: 220px;
4409
4926
  display:flex;
4410
4927
  align-items:center;
4411
4928
  justify-content:center;
4412
- background:var(--fb-file-upload-bg-color,#f3f4f6);
4413
- border-radius:var(--fb-border-radius,0.5rem);
4414
- border:1px solid var(--fb-file-upload-border-color,#d1d5db);
4929
+ background: repeating-linear-gradient(45deg, #fafafa 0 6px, #f3f4f6 6px 12px);
4930
+ border-radius:0.75rem;
4931
+ border:1px solid #e2e8f0;
4415
4932
  `;
4416
4933
  emptyState.innerHTML = `<div style="font-size:11px;text-align:center;color:var(--fb-text-secondary-color,#6b7280);">${escapeHtml(t("noFileSelected", state))}</div>`;
4417
4934
  return emptyState;
4418
4935
  }
4419
- function renderMultiFileReadonly(rids, state, wrapper, pathKey, marginTop) {
4936
+ function renderMultiFileReadonly(rids, state, wrapper, pathKey, _marginTop) {
4420
4937
  addPrefillFilesToIndex(rids, state.resourceIndex);
4938
+ ensureFileStyles();
4421
4939
  const filesWrapper = document.createElement("div");
4422
4940
  filesWrapper.dataset.filesWrapper = pathKey;
4423
4941
  filesWrapper.dataset.resourceIds = JSON.stringify(rids);
@@ -4429,22 +4947,32 @@ function renderMultiFileReadonly(rids, state, wrapper, pathKey, marginTop) {
4429
4947
  filesWrapper.appendChild(emptyEl);
4430
4948
  return;
4431
4949
  }
4432
- const tilesWrap = document.createElement("div");
4433
- tilesWrap.style.cssText = `display:flex;flex-wrap:wrap;gap:6px;${marginTop ? `margin-top:${marginTop};` : ""}`;
4434
- filesWrapper.appendChild(tilesWrap);
4950
+ const grid = document.createElement("div");
4951
+ grid.className = "fb-multi-readonly-grid";
4952
+ filesWrapper.appendChild(grid);
4435
4953
  const placeholders = rids.map(() => {
4436
- const placeholder = document.createElement("div");
4437
- placeholder.style.cssText = `width:${TILE_SIZE};height:${TILE_SIZE};`;
4438
- tilesWrap.appendChild(placeholder);
4439
- return placeholder;
4954
+ const ph = document.createElement("div");
4955
+ ph.className = "fb-readonly-tile fb-checker fb-tile";
4956
+ grid.appendChild(ph);
4957
+ return ph;
4440
4958
  });
4441
4959
  for (let i = 0; i < rids.length; i++) {
4442
4960
  const resourceId = rids[i];
4443
4961
  const placeholder = placeholders[i];
4444
- renderFilePreviewReadonly(resourceId, state).then((tileEl) => {
4445
- placeholder.replaceWith(tileEl);
4446
- }).catch((err) => {
4447
- console.error("Failed to render readonly tile:", err);
4962
+ const meta = state.resourceIndex.get(resourceId);
4963
+ renderFilePreviewReadonly(resourceId, state, meta == null ? void 0 : meta.name).then((tile) => {
4964
+ tile.classList.add(
4965
+ "fb-readonly-tile",
4966
+ "fb-checker",
4967
+ "fb-tile-resource"
4968
+ );
4969
+ tile.dataset.resourceId = resourceId;
4970
+ placeholder.replaceWith(tile);
4971
+ }).catch(() => {
4972
+ const tile = document.createElement("div");
4973
+ tile.className = "fb-readonly-tile fb-checker fb-tile fb-tile-resource";
4974
+ tile.dataset.resourceId = resourceId;
4975
+ placeholder.replaceWith(tile);
4448
4976
  });
4449
4977
  }
4450
4978
  }
@@ -4456,7 +4984,7 @@ function renderFilesElementReadonly(element, ctx, wrapper, pathKey) {
4456
4984
  function renderMultipleFileElementReadonly(element, ctx, wrapper, pathKey) {
4457
4985
  const rawPrefill = ctx.prefill[element.key];
4458
4986
  const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
4459
- renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey, "4px");
4987
+ renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey);
4460
4988
  }
4461
4989
 
4462
4990
  // src/components/file.ts
@@ -4788,51 +5316,25 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
4788
5316
  removeBtn.style.pointerEvents = disabled ? "none" : "auto";
4789
5317
  });
4790
5318
  }
4791
- let addRow = null;
4792
- let countDisplay = null;
5319
+ let addUpdate = null;
4793
5320
  if (!readonly) {
4794
- addRow = document.createElement("div");
4795
- addRow.className = "flex items-center gap-3 mt-2";
4796
- const addBtn = document.createElement("button");
4797
- addBtn.type = "button";
4798
- addBtn.className = "add-colour-btn px-3 py-1 rounded";
4799
- addBtn.style.cssText = `
4800
- color: var(--fb-primary-color);
4801
- border: var(--fb-border-width) solid var(--fb-primary-color);
4802
- background-color: transparent;
4803
- font-size: var(--fb-font-size);
4804
- transition: all var(--fb-transition-duration);
4805
- `;
4806
- addBtn.textContent = "+";
4807
- addBtn.addEventListener("mouseenter", () => {
4808
- addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
4809
- });
4810
- addBtn.addEventListener("mouseleave", () => {
4811
- addBtn.style.backgroundColor = "transparent";
4812
- });
4813
- addBtn.onclick = () => {
4814
- const defaultColour = element.default || "#000000";
4815
- values.push(defaultColour);
4816
- addColourItem(defaultColour);
4817
- updateAddButton();
4818
- updateRemoveButtons();
4819
- };
4820
- countDisplay = document.createElement("span");
4821
- countDisplay.className = "text-sm text-gray-500";
4822
- addRow.appendChild(addBtn);
4823
- addRow.appendChild(countDisplay);
4824
- wrapper.appendChild(addRow);
5321
+ const handle = createAddItemRow(
5322
+ "colour",
5323
+ () => {
5324
+ const defaultColour = element.default || "#000000";
5325
+ values.push(defaultColour);
5326
+ addColourItem(defaultColour);
5327
+ updateAddButton();
5328
+ updateRemoveButtons();
5329
+ },
5330
+ { label: element.addLabel }
5331
+ );
5332
+ addUpdate = handle.update;
5333
+ mountCounterInLabel(wrapper, handle.counter);
5334
+ wrapper.appendChild(handle.row);
4825
5335
  }
4826
5336
  function updateAddButton() {
4827
- if (!addRow || !countDisplay) return;
4828
- const addBtn = addRow.querySelector(".add-colour-btn");
4829
- if (addBtn) {
4830
- const disabled = values.length >= maxCount;
4831
- addBtn.disabled = disabled;
4832
- addBtn.style.opacity = disabled ? "0.5" : "1";
4833
- addBtn.style.pointerEvents = disabled ? "none" : "auto";
4834
- }
4835
- countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
5337
+ if (addUpdate) addUpdate(values.length, maxCount);
4836
5338
  }
4837
5339
  values.forEach((value) => addColourItem(value));
4838
5340
  updateAddButton();
@@ -5274,50 +5776,24 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
5274
5776
  removeBtn.style.pointerEvents = disabled ? "none" : "auto";
5275
5777
  });
5276
5778
  }
5277
- let addRow = null;
5278
- let countDisplay = null;
5779
+ let addUpdate = null;
5279
5780
  if (!readonly) {
5280
- addRow = document.createElement("div");
5281
- addRow.className = "flex items-center gap-3 mt-2";
5282
- const addBtn = document.createElement("button");
5283
- addBtn.type = "button";
5284
- addBtn.className = "add-slider-btn px-3 py-1 rounded";
5285
- addBtn.style.cssText = `
5286
- color: var(--fb-primary-color);
5287
- border: var(--fb-border-width) solid var(--fb-primary-color);
5288
- background-color: transparent;
5289
- font-size: var(--fb-font-size);
5290
- transition: all var(--fb-transition-duration);
5291
- `;
5292
- addBtn.textContent = "+";
5293
- addBtn.addEventListener("mouseenter", () => {
5294
- addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
5295
- });
5296
- addBtn.addEventListener("mouseleave", () => {
5297
- addBtn.style.backgroundColor = "transparent";
5298
- });
5299
- addBtn.onclick = () => {
5300
- values.push(defaultValue);
5301
- addSliderItem(defaultValue);
5302
- updateAddButton();
5303
- updateRemoveButtons();
5304
- };
5305
- countDisplay = document.createElement("span");
5306
- countDisplay.className = "text-sm text-gray-500";
5307
- addRow.appendChild(addBtn);
5308
- addRow.appendChild(countDisplay);
5309
- wrapper.appendChild(addRow);
5781
+ const handle = createAddItemRow(
5782
+ "slider",
5783
+ () => {
5784
+ values.push(defaultValue);
5785
+ addSliderItem(defaultValue);
5786
+ updateAddButton();
5787
+ updateRemoveButtons();
5788
+ },
5789
+ { label: element.addLabel }
5790
+ );
5791
+ addUpdate = handle.update;
5792
+ mountCounterInLabel(wrapper, handle.counter);
5793
+ wrapper.appendChild(handle.row);
5310
5794
  }
5311
5795
  function updateAddButton() {
5312
- if (!addRow || !countDisplay) return;
5313
- const addBtn = addRow.querySelector(".add-slider-btn");
5314
- if (addBtn) {
5315
- const disabled = values.length >= maxCount;
5316
- addBtn.disabled = disabled;
5317
- addBtn.style.opacity = disabled ? "0.5" : "1";
5318
- addBtn.style.pointerEvents = disabled ? "none" : "auto";
5319
- }
5320
- countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
5796
+ if (addUpdate) addUpdate(values.length, maxCount);
5321
5797
  }
5322
5798
  values.forEach((value) => addSliderItem(value));
5323
5799
  updateAddButton();
@@ -5604,7 +6080,7 @@ function createPrefillHints(element, pathKey) {
5604
6080
  return null;
5605
6081
  }
5606
6082
  const hintsContainer = document.createElement("div");
5607
- hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-4";
6083
+ hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-2";
5608
6084
  element.prefillHints.forEach((hint, index) => {
5609
6085
  const hintButton = document.createElement("button");
5610
6086
  hintButton.type = "button";
@@ -5620,14 +6096,14 @@ function createPrefillHints(element, pathKey) {
5620
6096
  function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
5621
6097
  var _a, _b;
5622
6098
  const containerWrap = document.createElement("div");
5623
- containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
6099
+ containerWrap.className = "border border-gray-200 rounded-lg p-2 bg-gray-50";
5624
6100
  containerWrap.setAttribute("data-container", pathKey);
5625
6101
  const itemsWrap = document.createElement("div");
5626
6102
  const columns = element.columns || 1;
5627
6103
  if (columns === 1) {
5628
- itemsWrap.className = "space-y-4";
6104
+ itemsWrap.className = "space-y-2";
5629
6105
  } else {
5630
- itemsWrap.className = `grid grid-cols-${columns} gap-4`;
6106
+ itemsWrap.className = `grid grid-cols-${columns} gap-2`;
5631
6107
  }
5632
6108
  const containerIsReadonly = isElementReadonly(element, ctx.state, ctx);
5633
6109
  if (!containerIsReadonly) {
@@ -5662,17 +6138,71 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
5662
6138
  containerWrap.appendChild(itemsWrap);
5663
6139
  wrapper.appendChild(containerWrap);
5664
6140
  }
6141
+ function getChildWrapperClass(isSlides, columns) {
6142
+ if (isSlides) {
6143
+ return "space-y-2";
6144
+ }
6145
+ const cols = columns || 1;
6146
+ return cols === 1 ? "space-y-2" : `grid grid-cols-${cols} gap-2`;
6147
+ }
6148
+ function mountRemoveButton(item, onRemove) {
6149
+ const rem = document.createElement("button");
6150
+ rem.type = "button";
6151
+ rem.className = "fb-item-remove";
6152
+ rem.style.cssText = `
6153
+ width: 22px;
6154
+ height: 22px;
6155
+ display: inline-flex;
6156
+ align-items: center;
6157
+ justify-content: center;
6158
+ padding: 0;
6159
+ line-height: 1;
6160
+ font-size: 14px;
6161
+ color: var(--fb-error-color);
6162
+ background-color: transparent;
6163
+ border: 0;
6164
+ border-radius: 4px;
6165
+ cursor: pointer;
6166
+ flex-shrink: 0;
6167
+ transition: background-color var(--fb-transition-duration);
6168
+ `;
6169
+ rem.textContent = "\u2715";
6170
+ rem.addEventListener("mouseenter", () => {
6171
+ rem.style.backgroundColor = "var(--fb-background-hover-color)";
6172
+ });
6173
+ rem.addEventListener("mouseleave", () => {
6174
+ rem.style.backgroundColor = "transparent";
6175
+ });
6176
+ rem.onclick = onRemove;
6177
+ const labelRow = item.querySelector("[data-fb-label-row]");
6178
+ if (labelRow) {
6179
+ rem.style.marginLeft = "auto";
6180
+ labelRow.appendChild(rem);
6181
+ return;
6182
+ }
6183
+ rem.style.position = "absolute";
6184
+ rem.style.top = "8px";
6185
+ rem.style.right = "8px";
6186
+ item.style.position = "relative";
6187
+ item.appendChild(rem);
6188
+ }
5665
6189
  function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5666
6190
  var _a, _b, _c, _d;
5667
6191
  const state = ctx.state;
5668
6192
  const containerIsReadonly = isElementReadonly(element, state, ctx);
5669
6193
  const childInheritedReadonly = containerIsReadonly || ctx.inheritedReadonly;
5670
6194
  const containerWrap = document.createElement("div");
5671
- containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
5672
- const countDisplay = document.createElement("span");
5673
- countDisplay.className = "text-sm text-gray-500";
6195
+ containerWrap.className = "border border-gray-200 rounded-lg p-2 bg-gray-50";
5674
6196
  const itemsWrap = document.createElement("div");
5675
- itemsWrap.className = "space-y-4";
6197
+ const isSlides = element.displayMode === "slides";
6198
+ if (isSlides) {
6199
+ itemsWrap.className = "fb-container-slides";
6200
+ const slideCols = element.columns;
6201
+ const gridTemplateColumns = typeof slideCols === "number" && slideCols > 0 ? `repeat(${slideCols}, 1fr)` : "repeat(auto-fit, minmax(280px, 1fr))";
6202
+ itemsWrap.style.cssText = `display:grid;grid-template-columns:${gridTemplateColumns};gap:8px;align-items:start;`;
6203
+ } else {
6204
+ itemsWrap.className = "space-y-2";
6205
+ }
5676
6206
  if (!containerIsReadonly) {
5677
6207
  const hintsElement = createPrefillHints(element, element.key);
5678
6208
  if (hintsElement) {
@@ -5684,98 +6214,65 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5684
6214
  const pre = Array.isArray((_c = ctx.prefill) == null ? void 0 : _c[element.key]) ? ctx.prefill[element.key] : null;
5685
6215
  const childDefaults = extractChildDefaults(element.elements);
5686
6216
  const countItems = () => itemsWrap.querySelectorAll(":scope > .containerItem").length;
5687
- const createAddButton = () => {
5688
- const add = document.createElement("button");
5689
- add.type = "button";
5690
- add.className = "add-container-btn px-3 py-1 rounded";
5691
- add.style.cssText = `
5692
- color: var(--fb-primary-color);
5693
- border: var(--fb-border-width) solid var(--fb-primary-color);
5694
- background-color: transparent;
5695
- font-size: var(--fb-font-size);
5696
- transition: all var(--fb-transition-duration);
5697
- `;
5698
- add.textContent = "+";
5699
- add.addEventListener("mouseenter", () => {
5700
- add.style.backgroundColor = "var(--fb-background-hover-color)";
5701
- });
5702
- add.addEventListener("mouseleave", () => {
5703
- add.style.backgroundColor = "transparent";
5704
- });
5705
- add.onclick = () => {
5706
- if (countItems() < max) {
5707
- const idx = countItems();
5708
- const currentFormData = state.formRoot ? extractRootFormData(state.formRoot) : {};
5709
- const subCtx = {
5710
- state: ctx.state,
5711
- path: pathJoin(ctx.path, `${element.key}[${idx}]`),
5712
- prefill: childDefaults,
5713
- // Defaults for enableIf evaluation
5714
- formData: currentFormData,
5715
- // Current root data from DOM for enableIf
5716
- inheritedReadonly: childInheritedReadonly
5717
- };
5718
- const item = document.createElement("div");
5719
- item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
5720
- item.setAttribute("data-container-item", `${element.key}[${idx}]`);
5721
- const childWrapper = document.createElement("div");
5722
- const columns = element.columns || 1;
5723
- if (columns === 1) {
5724
- childWrapper.className = "space-y-4";
5725
- } else {
5726
- childWrapper.className = `grid grid-cols-${columns} gap-4`;
5727
- }
5728
- element.elements.forEach((child) => {
5729
- var _a2;
5730
- if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
5731
- childWrapper.appendChild(
5732
- createHiddenInput(
5733
- pathJoin(subCtx.path, child.key),
5734
- (_a2 = "default" in child ? child.default : null) != null ? _a2 : null
5735
- )
5736
- );
5737
- } else {
5738
- childWrapper.appendChild(renderElement(child, subCtx));
5739
- }
5740
- });
5741
- item.appendChild(childWrapper);
5742
- if (!containerIsReadonly) {
5743
- const rem = document.createElement("button");
5744
- rem.type = "button";
5745
- rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
5746
- rem.style.cssText = `
5747
- color: var(--fb-error-color);
5748
- background-color: transparent;
5749
- transition: background-color var(--fb-transition-duration);
5750
- `;
5751
- rem.textContent = "\u2715";
5752
- rem.addEventListener("mouseenter", () => {
5753
- rem.style.backgroundColor = "var(--fb-background-hover-color)";
5754
- });
5755
- rem.addEventListener("mouseleave", () => {
5756
- rem.style.backgroundColor = "transparent";
5757
- });
5758
- rem.onclick = () => handleRemoveItem(item);
5759
- item.style.position = "relative";
5760
- item.appendChild(rem);
5761
- }
5762
- itemsWrap.appendChild(item);
5763
- updateAddButton();
5764
- }
6217
+ const handleAddItem = () => {
6218
+ if (countItems() >= max) return;
6219
+ const idx = countItems();
6220
+ const currentFormData = state.formRoot ? extractRootFormData(state.formRoot) : {};
6221
+ const subCtx = {
6222
+ state: ctx.state,
6223
+ path: pathJoin(ctx.path, `${element.key}[${idx}]`),
6224
+ prefill: childDefaults,
6225
+ // Defaults for enableIf evaluation
6226
+ formData: currentFormData,
6227
+ // Current root data from DOM for enableIf
6228
+ inheritedReadonly: childInheritedReadonly
5765
6229
  };
5766
- return add;
6230
+ const item = document.createElement("div");
6231
+ item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
6232
+ item.setAttribute("data-container-item", `${element.key}[${idx}]`);
6233
+ const childWrapper = document.createElement("div");
6234
+ childWrapper.className = getChildWrapperClass(isSlides, element.columns);
6235
+ element.elements.forEach((child) => {
6236
+ var _a2;
6237
+ if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
6238
+ childWrapper.appendChild(
6239
+ createHiddenInput(
6240
+ pathJoin(subCtx.path, child.key),
6241
+ (_a2 = "default" in child ? child.default : null) != null ? _a2 : null
6242
+ )
6243
+ );
6244
+ } else {
6245
+ childWrapper.appendChild(renderElement(child, subCtx));
6246
+ }
6247
+ });
6248
+ item.appendChild(childWrapper);
6249
+ if (!containerIsReadonly) {
6250
+ mountRemoveButton(item, () => handleRemoveItem(item));
6251
+ }
6252
+ if (slideAddTile && slideAddTile.parentElement === itemsWrap) {
6253
+ itemsWrap.insertBefore(item, slideAddTile);
6254
+ } else {
6255
+ itemsWrap.appendChild(item);
6256
+ }
6257
+ updateAddButton();
5767
6258
  };
5768
- const updateAddButton = () => {
5769
- const currentCount = countItems();
5770
- const existingAddBtn = containerWrap.querySelector(
5771
- ".add-container-btn"
6259
+ let slideAddTile = null;
6260
+ let slideAddUpdate = null;
6261
+ let pillAddUpdate = null;
6262
+ const syncSlideTileSize = () => {
6263
+ if (!slideAddTile) return;
6264
+ const firstSlide = itemsWrap.querySelector(
6265
+ ":scope > .containerItem"
5772
6266
  );
5773
- if (existingAddBtn) {
5774
- existingAddBtn.disabled = currentCount >= max;
5775
- existingAddBtn.style.opacity = currentCount >= max ? "0.5" : "1";
5776
- existingAddBtn.style.pointerEvents = currentCount >= max ? "none" : "auto";
6267
+ if (firstSlide && firstSlide.offsetHeight > 0) {
6268
+ slideAddTile.style.minHeight = `${firstSlide.offsetHeight}px`;
5777
6269
  }
5778
- countDisplay.textContent = `${currentCount}/${max === Infinity ? "\u221E" : max}`;
6270
+ };
6271
+ const updateAddButton = () => {
6272
+ const currentCount = countItems();
6273
+ if (slideAddUpdate) slideAddUpdate(currentCount, max);
6274
+ if (pillAddUpdate) pillAddUpdate(currentCount, max);
6275
+ if (slideAddTile) syncSlideTileSize();
5779
6276
  };
5780
6277
  const handleRemoveItem = (item) => {
5781
6278
  item.remove();
@@ -5795,14 +6292,18 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5795
6292
  inheritedReadonly: childInheritedReadonly
5796
6293
  };
5797
6294
  const item = document.createElement("div");
5798
- item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
6295
+ item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
5799
6296
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
5800
6297
  const childWrapper = document.createElement("div");
5801
- const columns = element.columns || 1;
5802
- if (columns === 1) {
5803
- childWrapper.className = "space-y-4";
6298
+ if (isSlides) {
6299
+ childWrapper.className = "space-y-2";
5804
6300
  } else {
5805
- childWrapper.className = `grid grid-cols-${columns} gap-4`;
6301
+ const columns = element.columns || 1;
6302
+ if (columns === 1) {
6303
+ childWrapper.className = "space-y-2";
6304
+ } else {
6305
+ childWrapper.className = `grid grid-cols-${columns} gap-2`;
6306
+ }
5806
6307
  }
5807
6308
  element.elements.forEach((child) => {
5808
6309
  var _a3, _b2;
@@ -5817,24 +6318,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5817
6318
  });
5818
6319
  item.appendChild(childWrapper);
5819
6320
  if (!containerIsReadonly) {
5820
- const rem = document.createElement("button");
5821
- rem.type = "button";
5822
- rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
5823
- rem.style.cssText = `
5824
- color: var(--fb-error-color);
5825
- background-color: transparent;
5826
- transition: background-color var(--fb-transition-duration);
5827
- `;
5828
- rem.textContent = "\u2715";
5829
- rem.addEventListener("mouseenter", () => {
5830
- rem.style.backgroundColor = "var(--fb-background-hover-color)";
5831
- });
5832
- rem.addEventListener("mouseleave", () => {
5833
- rem.style.backgroundColor = "transparent";
5834
- });
5835
- rem.onclick = () => handleRemoveItem(item);
5836
- item.style.position = "relative";
5837
- item.appendChild(rem);
6321
+ mountRemoveButton(item, () => handleRemoveItem(item));
5838
6322
  }
5839
6323
  itemsWrap.appendChild(item);
5840
6324
  });
@@ -5852,14 +6336,18 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5852
6336
  inheritedReadonly: childInheritedReadonly
5853
6337
  };
5854
6338
  const item = document.createElement("div");
5855
- item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
6339
+ item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
5856
6340
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
5857
6341
  const childWrapper = document.createElement("div");
5858
- const columns = element.columns || 1;
5859
- if (columns === 1) {
5860
- childWrapper.className = "space-y-4";
6342
+ if (isSlides) {
6343
+ childWrapper.className = "space-y-2";
5861
6344
  } else {
5862
- childWrapper.className = `grid grid-cols-${columns} gap-4`;
6345
+ const columns = element.columns || 1;
6346
+ if (columns === 1) {
6347
+ childWrapper.className = "space-y-2";
6348
+ } else {
6349
+ childWrapper.className = `grid grid-cols-${columns} gap-2`;
6350
+ }
5863
6351
  }
5864
6352
  element.elements.forEach((child) => {
5865
6353
  var _a2;
@@ -5875,41 +6363,43 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5875
6363
  }
5876
6364
  });
5877
6365
  item.appendChild(childWrapper);
5878
- const rem = document.createElement("button");
5879
- rem.type = "button";
5880
- rem.className = "absolute top-2 right-2 px-2 py-1 rounded";
5881
- rem.style.cssText = `
5882
- color: var(--fb-error-color);
5883
- background-color: transparent;
5884
- transition: background-color var(--fb-transition-duration);
5885
- `;
5886
- rem.textContent = "\u2715";
5887
- rem.addEventListener("mouseenter", () => {
5888
- rem.style.backgroundColor = "var(--fb-background-hover-color)";
5889
- });
5890
- rem.addEventListener("mouseleave", () => {
5891
- rem.style.backgroundColor = "transparent";
5892
- });
5893
- rem.onclick = () => {
6366
+ mountRemoveButton(item, () => {
5894
6367
  if (countItems() > min) {
5895
6368
  handleRemoveItem(item);
5896
6369
  }
5897
- };
5898
- item.style.position = "relative";
5899
- item.appendChild(rem);
6370
+ });
5900
6371
  itemsWrap.appendChild(item);
5901
6372
  }
5902
6373
  }
5903
6374
  containerWrap.appendChild(itemsWrap);
5904
6375
  if (!containerIsReadonly) {
5905
- const addRow = document.createElement("div");
5906
- addRow.className = "flex items-center gap-3 mt-2";
5907
- addRow.appendChild(createAddButton());
5908
- addRow.appendChild(countDisplay);
5909
- containerWrap.appendChild(addRow);
6376
+ if (isSlides) {
6377
+ itemsWrap.style.alignItems = "stretch";
6378
+ const handle = createSlideAddTile(handleAddItem, {
6379
+ label: element.addLabel
6380
+ });
6381
+ slideAddTile = handle.tile;
6382
+ slideAddUpdate = handle.update;
6383
+ mountCounterInLabel(wrapper, handle.counter);
6384
+ itemsWrap.appendChild(handle.tile);
6385
+ } else {
6386
+ const handle = createAddItemRow("container", handleAddItem, {
6387
+ label: element.addLabel
6388
+ });
6389
+ pillAddUpdate = handle.update;
6390
+ mountCounterInLabel(wrapper, handle.counter);
6391
+ containerWrap.appendChild(handle.row);
6392
+ }
5910
6393
  }
5911
6394
  updateAddButton();
5912
6395
  wrapper.appendChild(containerWrap);
6396
+ if (slideAddTile) {
6397
+ if (typeof requestAnimationFrame === "function") {
6398
+ requestAnimationFrame(syncSlideTileSize);
6399
+ } else {
6400
+ syncSlideTileSize();
6401
+ }
6402
+ }
5913
6403
  }
5914
6404
  var validateElementFunc = null;
5915
6405
  function setValidateElement(fn) {
@@ -7932,7 +8422,7 @@ function filterFilesForDropdown(query, files, labels) {
7932
8422
  });
7933
8423
  }
7934
8424
  var TEXTAREA_FONT = "font-size: var(--fb-font-size, 14px); font-family: var(--fb-font-family, inherit); line-height: 1.6;";
7935
- var TEXTAREA_PADDING = "padding: 12px 52px 12px 14px;";
8425
+ var TEXTAREA_PADDING = "padding: 8px 40px 8px 10px;";
7936
8426
  function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
7937
8427
  var _a;
7938
8428
  const state = ctx.state;
@@ -7981,7 +8471,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
7981
8471
  });
7982
8472
  const errorEl = document.createElement("div");
7983
8473
  errorEl.className = "fb-richinput-error";
7984
- errorEl.style.cssText = "display: none; color: var(--fb-error-color, #ef4444); font-size: var(--fb-font-size-small, 12px); padding: 4px 14px 8px;";
8474
+ errorEl.style.cssText = "display: none; color: var(--fb-error-color, #ef4444); font-size: var(--fb-font-size-small, 12px); padding: 4px 10px 6px;";
7985
8475
  let errorTimer = null;
7986
8476
  function showUploadError(message) {
7987
8477
  errorEl.textContent = message;
@@ -8059,7 +8549,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
8059
8549
  });
8060
8550
  const filesRow = document.createElement("div");
8061
8551
  filesRow.className = "fb-richinput-files";
8062
- filesRow.style.cssText = "display: none; flex-wrap: wrap; gap: 6px; padding: 10px 14px 0; align-items: center;";
8552
+ filesRow.style.cssText = "display: none; flex-wrap: wrap; gap: 4px; padding: 6px 10px 0; align-items: center;";
8063
8553
  const fileInput = document.createElement("input");
8064
8554
  fileInput.type = "file";
8065
8555
  fileInput.multiple = true;
@@ -8195,13 +8685,13 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
8195
8685
  paperclipBtn.title = t("richinputAttachFile", state);
8196
8686
  paperclipBtn.style.cssText = `
8197
8687
  position: absolute;
8198
- right: 10px;
8199
- bottom: 10px;
8688
+ right: 6px;
8689
+ bottom: 6px;
8200
8690
  z-index: 2;
8201
- width: 32px;
8202
- height: 32px;
8691
+ width: 28px;
8692
+ height: 28px;
8203
8693
  border: none;
8204
- border-radius: 8px;
8694
+ border-radius: 6px;
8205
8695
  background: transparent;
8206
8696
  cursor: pointer;
8207
8697
  display: flex;
@@ -8569,7 +9059,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
8569
9059
  outerDiv.appendChild(errorEl);
8570
9060
  if (element.minLength != null || element.maxLength != null) {
8571
9061
  const counterRow = document.createElement("div");
8572
- counterRow.style.cssText = "position: relative; padding: 2px 14px 6px; text-align: right;";
9062
+ counterRow.style.cssText = "position: relative; padding: 2px 10px 4px; text-align: right;";
8573
9063
  const counter = createCharCounter(element, textarea, false);
8574
9064
  counter.style.cssText = `
8575
9065
  position: static;
@@ -8877,10 +9367,7 @@ var TAGS = {
8877
9367
  "-": ["<hr />"]
8878
9368
  };
8879
9369
  function outdent(str) {
8880
- return str.replace(
8881
- RegExp("^" + (str.match(/^(\t| )+/) || "")[0], "gm"),
8882
- ""
8883
- );
9370
+ return str.replace(RegExp("^" + (str.match(/^(\t| )+/) || "")[0], "gm"), "");
8884
9371
  }
8885
9372
  function encodeAttr(str) {
8886
9373
  return (str + "").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -9043,12 +9530,7 @@ function ensureMarkdownStyles() {
9043
9530
  `;
9044
9531
  document.head.appendChild(style);
9045
9532
  }
9046
- var ANCHOR_DANGEROUS_SCHEMES = [
9047
- "javascript:",
9048
- "data:",
9049
- "vbscript:",
9050
- "blob:"
9051
- ];
9533
+ var ANCHOR_DANGEROUS_SCHEMES = ["javascript:", "data:", "vbscript:", "blob:"];
9052
9534
  var IMG_DANGEROUS_SCHEMES = ["javascript:", "vbscript:", "blob:"];
9053
9535
  function isImgSrcDangerous(normalized) {
9054
9536
  if (IMG_DANGEROUS_SCHEMES.some((scheme) => normalized.startsWith(scheme))) {
@@ -9374,7 +9856,8 @@ function createInfoButton(element) {
9374
9856
  }
9375
9857
  function createLabelContainer(element) {
9376
9858
  const label = document.createElement("div");
9377
- label.className = "flex items-center mb-2";
9859
+ label.className = "flex items-center mb-1";
9860
+ label.dataset.fbLabelRow = "";
9378
9861
  const title = createFieldLabel(element);
9379
9862
  label.appendChild(title);
9380
9863
  if (element.description || element.hint) {
@@ -9481,7 +9964,7 @@ function renderElement2(element, ctx) {
9481
9964
  }
9482
9965
  const initiallyDisabled2 = shouldDisableElement(element, ctx);
9483
9966
  const outerWrapper = document.createElement("div");
9484
- outerWrapper.className = "mb-6 fb-field-wrapper fb-markdown-wrapper";
9967
+ outerWrapper.className = "mb-2 fb-field-wrapper fb-markdown-wrapper";
9485
9968
  outerWrapper.setAttribute(
9486
9969
  "data-field-key",
9487
9970
  getElementLookupKey(element, ctx.state)
@@ -9497,7 +9980,7 @@ function renderElement2(element, ctx) {
9497
9980
  }
9498
9981
  const initiallyDisabled = shouldDisableElement(element, ctx);
9499
9982
  const wrapper = document.createElement("div");
9500
- wrapper.className = "mb-6 fb-field-wrapper";
9983
+ wrapper.className = "mb-2 fb-field-wrapper";
9501
9984
  wrapper.setAttribute("data-field-key", element.key);
9502
9985
  const label = createLabelContainer(element);
9503
9986
  wrapper.appendChild(label);
@@ -9564,12 +10047,16 @@ var defaultConfig = {
9564
10047
  hintPattern: "Format: {pattern}",
9565
10048
  fileCountSingle: "{count} file",
9566
10049
  fileCountPlural: "{count} files",
10050
+ fileCountWithMax: "{count} / {max} files",
9567
10051
  fileCountRange: "({min}-{max})",
9568
10052
  uploadingFile: "Uploading\u2026",
9569
10053
  filesCounter: "{count}/{max}",
9570
10054
  fromLibrary: "From library",
9571
10055
  libraryEmpty: "Library is empty",
9572
10056
  libraryHint: "Choose from previously uploaded files",
10057
+ dropToUpload: "Release to upload",
10058
+ replaceFile: "Replace",
10059
+ clearAll: "Clear all",
9573
10060
  pickerError: "Failed to load files from library",
9574
10061
  // Validation errors
9575
10062
  required: "Required",
@@ -9588,6 +10075,7 @@ var defaultConfig = {
9588
10075
  invalidFileExtension: 'File "{name}" has unsupported format. Allowed: {formats}',
9589
10076
  invalidFileMime: 'File "{name}": file type {type} not allowed (allowed: {mimes})',
9590
10077
  fileTooLarge: 'File "{name}" exceeds maximum size of {maxSize}MB',
10078
+ uploadFailed: 'Failed to upload "{name}": {error}',
9591
10079
  filesLimitExceeded: "{skipped} file(s) skipped: maximum {max} files allowed",
9592
10080
  unsupportedFieldType: "Unsupported field type: {type}",
9593
10081
  invalidOption: "Invalid option",
@@ -9635,12 +10123,16 @@ var defaultConfig = {
9635
10123
  hintPattern: "\u0424\u043E\u0440\u043C\u0430\u0442: {pattern}",
9636
10124
  fileCountSingle: "{count} \u0444\u0430\u0439\u043B",
9637
10125
  fileCountPlural: "{count} \u0444\u0430\u0439\u043B\u043E\u0432",
10126
+ fileCountWithMax: "{count} / {max} \u0444\u0430\u0439\u043B\u043E\u0432",
9638
10127
  fileCountRange: "({min}-{max})",
9639
10128
  uploadingFile: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026",
9640
10129
  filesCounter: "{count}/{max}",
9641
10130
  fromLibrary: "\u0418\u0437 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438",
9642
10131
  libraryEmpty: "\u0411\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0430 \u043F\u0443\u0441\u0442\u0430",
9643
10132
  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",
10133
+ dropToUpload: "\u041E\u0442\u043F\u0443\u0441\u0442\u0438\u0442\u0435, \u0447\u0442\u043E\u0431\u044B \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C",
10134
+ replaceFile: "\u0417\u0430\u043C\u0435\u043D\u0438\u0442\u044C",
10135
+ clearAll: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u0441\u0435",
9644
10136
  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",
9645
10137
  // Validation errors
9646
10138
  required: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435",
@@ -9659,6 +10151,7 @@ var defaultConfig = {
9659
10151
  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}',
9660
10152
  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})',
9661
10153
  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',
10154
+ uploadFailed: '\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C "{name}": {error}',
9662
10155
  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",
9663
10156
  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}",
9664
10157
  invalidOption: "\u041D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435",
@@ -9781,10 +10274,10 @@ var defaultTheme = {
9781
10274
  fileUploadHoverBorderColor: "#3b82f6",
9782
10275
  // blue-500
9783
10276
  // Spacing
9784
- inputPaddingX: "0.75rem",
9785
- // 3 (12px)
9786
- inputPaddingY: "0.5rem",
9787
- // 2 (8px)
10277
+ inputPaddingX: "0.5rem",
10278
+ // 8px (compact density v2)
10279
+ inputPaddingY: "0.25rem",
10280
+ // 4px (compact density v2)
9788
10281
  borderRadius: "0.5rem",
9789
10282
  // rounded-lg (8px)
9790
10283
  borderWidth: "1px",
@@ -9881,29 +10374,6 @@ var exampleThemes = {
9881
10374
  }
9882
10375
  };
9883
10376
 
9884
- // src/utils/styles.ts
9885
- function applyActionButtonStyles(button, isFormLevel = false) {
9886
- button.style.cssText = `
9887
- background-color: var(--fb-action-bg-color);
9888
- color: var(--fb-action-text-color);
9889
- border: var(--fb-border-width) solid var(--fb-action-border-color);
9890
- padding: ${isFormLevel ? "0.5rem 1rem" : "0.5rem 0.75rem"};
9891
- font-size: var(--fb-font-size);
9892
- font-weight: var(--fb-font-weight-medium);
9893
- border-radius: var(--fb-border-radius);
9894
- transition: all var(--fb-transition-duration);
9895
- box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
9896
- `;
9897
- button.addEventListener("mouseenter", () => {
9898
- button.style.backgroundColor = "var(--fb-action-hover-bg-color)";
9899
- button.style.borderColor = "var(--fb-action-hover-border-color)";
9900
- });
9901
- button.addEventListener("mouseleave", () => {
9902
- button.style.backgroundColor = "var(--fb-action-bg-color)";
9903
- button.style.borderColor = "var(--fb-action-border-color)";
9904
- });
9905
- }
9906
-
9907
10377
  // src/components/registry.ts
9908
10378
  function validateHiddenElement(element, key, context) {
9909
10379
  var _a;
@@ -10230,7 +10700,7 @@ var FormBuilderInstance = class {
10230
10700
  existingContainer.remove();
10231
10701
  }
10232
10702
  const actionsContainer = document.createElement("div");
10233
- actionsContainer.className = "form-level-actions-container mt-6 pt-4 flex flex-wrap gap-3 justify-center";
10703
+ actionsContainer.className = "form-level-actions-container mt-3 pt-2 flex flex-wrap gap-2 justify-center";
10234
10704
  actionsContainer.style.cssText = `
10235
10705
  border-top: var(--fb-border-width) solid var(--fb-border-color);
10236
10706
  `;
@@ -10371,7 +10841,7 @@ var FormBuilderInstance = class {
10371
10841
  */
10372
10842
  createRootPrefillHints(hints) {
10373
10843
  const hintsContainer = document.createElement("div");
10374
- hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-4";
10844
+ hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-2";
10375
10845
  hints.forEach((hint) => {
10376
10846
  const hintButton = document.createElement("button");
10377
10847
  hintButton.type = "button";
@@ -10404,7 +10874,7 @@ var FormBuilderInstance = class {
10404
10874
  root.setAttribute("data-fb-root", "true");
10405
10875
  injectThemeVariables(root, this.state.config.theme);
10406
10876
  const rootContainer = document.createElement("div");
10407
- rootContainer.className = "space-y-6";
10877
+ rootContainer.className = "space-y-2";
10408
10878
  if (schema.prefillHints && !this.state.config.readonly) {
10409
10879
  const hintsContainer = this.createRootPrefillHints(schema.prefillHints);
10410
10880
  rootContainer.appendChild(hintsContainer);
@@ -10412,9 +10882,9 @@ var FormBuilderInstance = class {
10412
10882
  const fieldsWrapper = document.createElement("div");
10413
10883
  const columns = schema.columns || 1;
10414
10884
  if (columns === 1) {
10415
- fieldsWrapper.className = "space-y-4";
10885
+ fieldsWrapper.className = "space-y-2";
10416
10886
  } else {
10417
- fieldsWrapper.className = `grid grid-cols-${columns} gap-4`;
10887
+ fieldsWrapper.className = `grid grid-cols-${columns} gap-2`;
10418
10888
  }
10419
10889
  schema.elements.forEach((element) => {
10420
10890
  var _a, _b;