@dmitryvim/form-builder 0.2.30 → 0.2.32

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)) {
@@ -851,8 +864,12 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
851
864
  const textareaWrapper = document.createElement("div");
852
865
  textareaWrapper.style.cssText = "position: relative;";
853
866
  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;";
867
+ textareaInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
868
+ textareaInput.style.cssText = `
869
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x) 24px var(--fb-input-padding-x);
870
+ font-size: var(--fb-font-size);
871
+ font-family: var(--fb-font-family);
872
+ `;
856
873
  textareaInput.name = pathKey;
857
874
  textareaInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
858
875
  textareaInput.rows = element.rows || 4;
@@ -905,8 +922,12 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
905
922
  const textareaContainer = document.createElement("div");
906
923
  textareaContainer.style.cssText = "position: relative;";
907
924
  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;";
925
+ textareaInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
926
+ textareaInput.style.cssText = `
927
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x) 24px var(--fb-input-padding-x);
928
+ font-size: var(--fb-font-size);
929
+ font-family: var(--fb-font-family);
930
+ `;
910
931
  textareaInput.placeholder = element.placeholder || t("placeholderText", state);
911
932
  textareaInput.rows = element.rows || 4;
912
933
  textareaInput.value = value;
@@ -1092,8 +1113,14 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
1092
1113
  inputWrapper.style.cssText = "position: relative;";
1093
1114
  const numberInput = document.createElement("input");
1094
1115
  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;";
1116
+ numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1117
+ numberInput.style.cssText = `
1118
+ padding: var(--fb-input-padding-y) 60px var(--fb-input-padding-y) var(--fb-input-padding-x);
1119
+ font-size: var(--fb-font-size);
1120
+ font-family: var(--fb-font-family);
1121
+ width: 100%;
1122
+ box-sizing: border-box;
1123
+ `;
1097
1124
  numberInput.name = pathKey;
1098
1125
  numberInput.placeholder = element.placeholder || "0";
1099
1126
  if (element.min !== void 0) numberInput.min = element.min.toString();
@@ -1146,8 +1173,14 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1146
1173
  inputContainer.style.cssText = "position: relative; flex: 1;";
1147
1174
  const numberInput = document.createElement("input");
1148
1175
  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;";
1176
+ numberInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1177
+ numberInput.style.cssText = `
1178
+ padding: var(--fb-input-padding-y) 60px var(--fb-input-padding-y) var(--fb-input-padding-x);
1179
+ font-size: var(--fb-font-size);
1180
+ font-family: var(--fb-font-family);
1181
+ width: 100%;
1182
+ box-sizing: border-box;
1183
+ `;
1151
1184
  numberInput.placeholder = element.placeholder || "0";
1152
1185
  if (element.min !== void 0) numberInput.min = element.min.toString();
1153
1186
  if (element.max !== void 0) numberInput.max = element.max.toString();
@@ -1421,7 +1454,12 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
1421
1454
  const state = ctx.state;
1422
1455
  const readonly = isElementReadonly(element, state, ctx);
1423
1456
  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";
1457
+ selectInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1458
+ selectInput.style.cssText = `
1459
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
1460
+ font-size: var(--fb-font-size);
1461
+ font-family: var(--fb-font-family);
1462
+ `;
1425
1463
  selectInput.name = pathKey;
1426
1464
  selectInput.disabled = readonly;
1427
1465
  (element.options || []).forEach((option) => {
@@ -1474,7 +1512,12 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1474
1512
  const itemWrapper = document.createElement("div");
1475
1513
  itemWrapper.className = "multiple-select-item flex items-center gap-2";
1476
1514
  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";
1515
+ selectInput.className = "flex-1 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500";
1516
+ selectInput.style.cssText = `
1517
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
1518
+ font-size: var(--fb-font-size);
1519
+ font-family: var(--fb-font-family);
1520
+ `;
1478
1521
  selectInput.disabled = readonly;
1479
1522
  (element.options || []).forEach((option) => {
1480
1523
  const optionElement = document.createElement("option");
@@ -2225,7 +2268,13 @@ function ensureFileStyles() {
2225
2268
  style.textContent = `
2226
2269
  @keyframes fb-spin { to { transform: rotate(360deg); } }
2227
2270
 
2228
- /* Spinner used during single-file and multi-file upload */
2271
+ /* \u2500\u2500\u2500 Checker background utility \u2500\u2500\u2500 */
2272
+ /* Neutral diagonal-stripe background for image previews (never crops) */
2273
+ .fb-checker {
2274
+ background-image: repeating-linear-gradient(45deg, #fafafa 0 6px, #f3f4f6 6px 12px);
2275
+ }
2276
+
2277
+ /* \u2500\u2500\u2500 Spinner \u2500\u2500\u2500 */
2229
2278
  .fb-spinner {
2230
2279
  width: 36px;
2231
2280
  height: 36px;
@@ -2236,207 +2285,271 @@ function ensureFileStyles() {
2236
2285
  flex-shrink: 0;
2237
2286
  }
2238
2287
 
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;
2288
+ /* \u2500\u2500\u2500 Wide single-file add tile (empty state) \u2500\u2500\u2500 */
2289
+ .fb-wide-tile {
2290
+ width: 100%;
2291
+ border-radius: 0.75rem;
2292
+ border: 1px dashed #60a5fa;
2293
+ background: rgba(239,246,255,0.5);
2294
+ display: flex;
2245
2295
  overflow: hidden;
2246
- border-radius: var(--fb-border-radius, 0.5rem);
2247
- background: var(--fb-file-upload-bg-color, #f3f4f6);
2296
+ height: 180px;
2297
+ transition: border-color 150ms, background 150ms, box-shadow 150ms;
2298
+ cursor: pointer;
2248
2299
  }
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);
2300
+ .fb-wide-tile:hover {
2301
+ background: #eff6ff;
2253
2302
  }
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);
2303
+ .fb-wide-tile.fb-drag-over {
2304
+ border-color: #3b82f6;
2305
+ border-width: 2px;
2306
+ background: #eff6ff;
2307
+ box-shadow: 0 0 0 4px rgba(191,219,254,0.7);
2258
2308
  }
2259
2309
 
2260
- /* "+" add-more tile */
2261
- .fb-tile-add {
2262
- border: 2px dashed var(--fb-file-upload-border-color, #d1d5db);
2310
+ /* Upload zone inside wide tile */
2311
+ .fb-wide-tile-upload {
2312
+ flex: 1;
2263
2313
  display: flex;
2314
+ flex-direction: column;
2264
2315
  align-items: center;
2265
2316
  justify-content: center;
2317
+ gap: 8px;
2318
+ color: #2563eb;
2319
+ padding: 16px;
2320
+ transition: background 150ms;
2266
2321
  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);
2322
+ background: transparent;
2323
+ border: none;
2324
+ font-family: inherit;
2272
2325
  }
2273
- .fb-tile-add:hover {
2274
- border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
2275
- color: var(--fb-text-color, #1f2937);
2326
+ .fb-wide-tile-upload:hover {
2327
+ background: rgba(191,219,254,0.25);
2276
2328
  }
2277
2329
 
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;
2330
+ /* Vertical dashed divider between upload and library zones */
2331
+ .fb-wide-tile-divider {
2332
+ width: 1px;
2333
+ margin: 16px 0;
2334
+ border-left: 1px dashed rgba(96,165,250,0.5);
2335
+ background: transparent;
2336
+ flex-shrink: 0;
2288
2337
  }
2289
2338
 
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);
2339
+ /* Library zone inside wide tile */
2340
+ .fb-wide-tile-library {
2341
+ width: 176px;
2342
+ flex-shrink: 0;
2296
2343
  display: flex;
2297
2344
  flex-direction: column;
2298
2345
  align-items: center;
2299
2346
  justify-content: center;
2300
- gap: 4px;
2347
+ gap: 8px;
2348
+ color: #2563eb;
2349
+ padding: 12px;
2350
+ transition: background 150ms;
2301
2351
  cursor: pointer;
2302
- transition:
2303
- border-color var(--fb-transition-duration, 200ms),
2304
- background var(--fb-transition-duration, 200ms);
2352
+ background: transparent;
2353
+ border: none;
2354
+ font-family: inherit;
2305
2355
  }
2306
- .fb-file-dropzone:hover {
2307
- border-color: var(--fb-file-upload-hover-border-color, #3b82f6);
2308
- background: var(--fb-background-hover-color, #f9fafb);
2356
+ .fb-wide-tile-library:hover {
2357
+ background: rgba(191,219,254,0.25);
2309
2358
  }
2310
2359
 
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;
2360
+ /* \u2500\u2500\u2500 Multi-file outer grid container \u2500\u2500\u2500 */
2361
+ .fb-multi-outer {
2362
+ border-radius: 0.75rem;
2363
+ border: 1px dashed #cbd5e1;
2364
+ background: rgba(248,250,252,0.4);
2365
+ padding: 12px;
2366
+ transition: border-color 150ms, background 150ms, box-shadow 150ms;
2367
+ }
2368
+ .fb-multi-outer.fb-drag-over {
2369
+ border-width: 2px;
2370
+ border-color: #3b82f6;
2371
+ background: rgba(239,246,255,0.4);
2372
+ box-shadow: 0 0 0 4px rgba(191,219,254,0.7);
2319
2373
  }
2320
- .fb-tile-uploading-text {
2321
- font-size: 8px;
2322
- color: var(--fb-file-upload-text-color, #9ca3af);
2374
+
2375
+ /* With files present: white solid border */
2376
+ .fb-multi-outer.fb-multi-has-files {
2377
+ border-style: solid;
2378
+ border-color: #e2e8f0;
2379
+ background: #fff;
2323
2380
  }
2324
- .fb-tile-hint {
2325
- font-size: 11px;
2326
- color: var(--fb-file-upload-text-color, #9ca3af);
2327
- margin-top: 4px;
2381
+
2382
+ /* The CSS grid inside */
2383
+ .fb-multi-grid {
2384
+ display: grid;
2385
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
2386
+ gap: 10px;
2328
2387
  }
2329
- .fb-tile-empty-text {
2330
- font-size: 12px;
2331
- color: var(--fb-text-secondary-color, #6b7280);
2332
- padding: 4px 0;
2388
+
2389
+ /* \u2500\u2500\u2500 Multi square add-tile (combined upload + library) \u2500\u2500\u2500 */
2390
+ .fb-multi-add-tile {
2391
+ aspect-ratio: 1 / 1;
2392
+ border-radius: 0.5rem;
2393
+ border: 1px dashed #60a5fa;
2394
+ background: rgba(239,246,255,0.5);
2395
+ display: flex;
2396
+ flex-direction: column;
2397
+ overflow: hidden;
2398
+ transition: background 150ms;
2333
2399
  }
2334
- .fb-dropzone-primary-text {
2335
- font-size: 13px;
2336
- color: var(--fb-text-secondary-color, #6b7280);
2400
+ .fb-multi-add-tile:hover {
2401
+ background: #eff6ff;
2337
2402
  }
2338
- .fb-dropzone-hint-text {
2339
- font-size: 11px;
2340
- color: var(--fb-file-upload-text-color, #9ca3af);
2403
+ .fb-multi-add-tile.fb-drag-over-tile {
2404
+ border-width: 2px;
2405
+ border-color: #3b82f6;
2406
+ background: rgba(255,255,255,0.8);
2341
2407
  }
2342
2408
 
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;
2409
+ /* Upload half of add-tile */
2410
+ .fb-multi-add-upload {
2411
+ flex: 1;
2367
2412
  display: flex;
2413
+ flex-direction: column;
2368
2414
  align-items: center;
2369
2415
  justify-content: center;
2370
- opacity: 0;
2371
- transition: opacity var(--fb-transition-duration, 200ms);
2416
+ gap: 4px;
2417
+ color: #2563eb;
2418
+ cursor: pointer;
2419
+ background: transparent;
2420
+ border: none;
2421
+ font-family: inherit;
2422
+ width: 100%;
2423
+ transition: background 150ms;
2372
2424
  }
2373
- .fb-tile-resource:hover .fb-tile-x-btn {
2374
- opacity: 1;
2425
+ .fb-multi-add-upload:hover {
2426
+ background: rgba(191,219,254,0.35);
2375
2427
  }
2376
2428
 
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));
2429
+ /* Horizontal dashed divider inside add-tile */
2430
+ .fb-multi-add-divider {
2431
+ border-top: 1px dashed rgba(96,165,250,0.5);
2432
+ margin: 0;
2433
+ flex-shrink: 0;
2385
2434
  }
2386
- .fb-play-btn {
2387
- background: var(--fb-file-bg-color, rgba(255,255,255,0.9));
2388
- border-radius: 50%;
2435
+
2436
+ /* Library strip at bottom of add-tile */
2437
+ .fb-multi-add-library {
2438
+ padding: 6px 0;
2389
2439
  display: flex;
2390
2440
  align-items: center;
2391
2441
  justify-content: center;
2442
+ gap: 4px;
2443
+ color: #2563eb;
2444
+ font-size: 11px;
2445
+ font-weight: 500;
2446
+ cursor: pointer;
2447
+ background: transparent;
2448
+ border: none;
2449
+ font-family: inherit;
2450
+ width: 100%;
2451
+ transition: background 150ms;
2452
+ flex-shrink: 0;
2453
+ }
2454
+ .fb-multi-add-library:hover {
2455
+ background: rgba(191,219,254,0.35);
2392
2456
  }
2393
2457
 
2394
- /* Edit-mode local video preview wrapper */
2395
- .fb-video-preview-wrap {
2458
+ /* \u2500\u2500\u2500 Capacity placeholder squares \u2500\u2500\u2500 */
2459
+ .fb-multi-placeholder {
2460
+ aspect-ratio: 1 / 1;
2461
+ border-radius: 0.5rem;
2462
+ border: 1px solid #e2e8f0;
2463
+ }
2464
+ .fb-multi-placeholder.fb-drag-over {
2465
+ border-width: 2px;
2466
+ border-style: dashed;
2467
+ border-color: #93c5fd;
2468
+ background: rgba(219,234,254,0.6);
2469
+ }
2470
+
2471
+ /* \u2500\u2500\u2500 Filled preview tile \u2500\u2500\u2500 */
2472
+ .fb-preview-tile {
2473
+ aspect-ratio: 1 / 1;
2474
+ border-radius: 0.5rem;
2475
+ border: 1px solid #e2e8f0;
2476
+ overflow: hidden;
2396
2477
  position: relative;
2478
+ cursor: pointer;
2479
+ }
2480
+ .fb-preview-tile img {
2397
2481
  width: 100%;
2398
2482
  height: 100%;
2483
+ object-fit: contain;
2484
+ display: block;
2399
2485
  }
2400
2486
 
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;
2487
+ /* \u2500\u2500\u2500 Uploading placeholder tile \u2500\u2500\u2500 */
2488
+ .fb-uploading-tile {
2489
+ aspect-ratio: 1 / 1;
2490
+ border-radius: 0.5rem;
2491
+ border: 2px dashed #d1d5db;
2407
2492
  display: flex;
2408
- gap: 4px;
2409
- opacity: 0;
2410
- transition: opacity var(--fb-transition-duration, 200ms);
2411
- pointer-events: none;
2493
+ flex-direction: column;
2494
+ align-items: center;
2495
+ justify-content: center;
2496
+ gap: 6px;
2497
+ padding: 6px;
2412
2498
  }
2413
- .fb-video-preview-wrap:hover .fb-video-btn-overlay {
2414
- opacity: 1;
2415
- pointer-events: auto;
2499
+
2500
+ /* \u2500\u2500\u2500 Meta line below multi grid \u2500\u2500\u2500 */
2501
+ .fb-meta-line {
2502
+ margin-top: 10px;
2503
+ display: flex;
2504
+ align-items: center;
2505
+ justify-content: space-between;
2506
+ gap: 8px;
2507
+ flex-wrap: wrap;
2416
2508
  }
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;
2509
+ .fb-meta-text {
2510
+ font-size: 12px;
2511
+ color: #94a3b8;
2512
+ display: flex;
2513
+ align-items: center;
2514
+ gap: 8px;
2515
+ flex-wrap: wrap;
2425
2516
  }
2426
- .fb-video-btn-delete {
2427
- background: rgba(220, 38, 38, 0.85);
2517
+ .fb-meta-dot {
2518
+ width: 4px;
2519
+ height: 4px;
2520
+ border-radius: 50%;
2521
+ background: #cbd5e1;
2522
+ flex-shrink: 0;
2428
2523
  }
2429
- .fb-video-btn-delete:hover {
2430
- background: rgba(185, 28, 28, 0.95);
2524
+ .fb-meta-mono {
2525
+ font-family: ui-monospace, 'JetBrains Mono', monospace;
2526
+ font-size: 11px;
2527
+ letter-spacing: -0.02em;
2431
2528
  }
2432
- .fb-video-btn-change {
2433
- background: rgba(31, 41, 55, 0.85);
2529
+ .fb-clear-all-btn {
2530
+ font-size: 12px;
2531
+ color: #94a3b8;
2532
+ background: none;
2533
+ border: none;
2534
+ cursor: pointer;
2535
+ padding: 0;
2536
+ font-family: inherit;
2537
+ transition: color 150ms;
2538
+ white-space: nowrap;
2539
+ flex-shrink: 0;
2434
2540
  }
2435
- .fb-video-btn-change:hover {
2436
- background: rgba(17, 24, 39, 0.95);
2541
+ .fb-clear-all-btn:hover {
2542
+ color: #dc2626;
2437
2543
  }
2438
2544
 
2439
- /* Tile action icon buttons (download / open / remove) \u2014 shown on tile hover */
2545
+ /* \u2500\u2500\u2500 Empty text (readonly) \u2500\u2500\u2500 */
2546
+ .fb-tile-empty-text {
2547
+ font-size: 11px;
2548
+ color: var(--fb-text-secondary-color, #6b7280);
2549
+ padding: 4px 0;
2550
+ }
2551
+
2552
+ /* \u2500\u2500\u2500 Tile action buttons (for zoom popup, compat) \u2500\u2500\u2500 */
2440
2553
  .fb-tile-actions {
2441
2554
  position: absolute;
2442
2555
  top: 3px;
@@ -2448,37 +2561,35 @@ function ensureFileStyles() {
2448
2561
  transition: opacity var(--fb-transition-duration, 200ms);
2449
2562
  z-index: 10;
2450
2563
  }
2451
- .fb-tile-resource:hover .fb-tile-actions {
2564
+ .fb-preview-tile:hover .fb-tile-actions {
2452
2565
  opacity: 1;
2453
2566
  }
2454
2567
  .fb-tile-action-btn {
2455
- width: 28px;
2456
- height: 28px;
2568
+ width: 24px;
2569
+ height: 24px;
2457
2570
  display: flex;
2458
2571
  align-items: center;
2459
2572
  justify-content: center;
2460
- border: none;
2461
- border-radius: 50%;
2573
+ border: 1px solid rgba(15,23,42,0.08);
2574
+ border-radius: 0.375rem;
2462
2575
  cursor: pointer;
2463
- background: rgba(31, 41, 55, 0.75);
2464
- color: #fff;
2576
+ background: rgba(255,255,255,0.92);
2577
+ color: #374151;
2465
2578
  padding: 0;
2466
2579
  flex-shrink: 0;
2467
- transition:
2468
- background var(--fb-transition-duration, 200ms),
2469
- opacity var(--fb-transition-duration, 200ms);
2580
+ box-shadow: 0 1px 2px rgba(0,0,0,0.06);
2581
+ transition: background var(--fb-transition-duration, 200ms),
2582
+ color var(--fb-transition-duration, 200ms);
2470
2583
  }
2471
2584
  .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);
2585
+ background: #ffffff;
2586
+ color: #0f172a;
2476
2587
  }
2477
2588
  .fb-tile-action-remove:hover {
2478
- background: rgba(185, 28, 28, 0.95);
2589
+ color: #dc2626;
2479
2590
  }
2480
2591
 
2481
- /* Actions row inside zoom popup \u2014 always visible while popup is shown */
2592
+ /* Zoom popup action buttons always visible */
2482
2593
  .fb-tile-zoom-preview .fb-tile-actions {
2483
2594
  position: absolute;
2484
2595
  top: 6px;
@@ -2487,116 +2598,145 @@ function ensureFileStyles() {
2487
2598
  z-index: 10000;
2488
2599
  }
2489
2600
 
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;
2601
+ /* \u2500\u2500\u2500 Hover zoom preview popup \u2500\u2500\u2500 */
2602
+ .fb-tile-zoom-preview {
2603
+ position: fixed;
2604
+ z-index: 9999;
2605
+ background: var(--fb-background-color, #fff);
2606
+ border: 1px solid #e2e8f0;
2607
+ border-radius: 0.5rem;
2608
+ box-shadow: 0 4px 16px rgba(0,0,0,0.18);
2609
+ padding: 4px;
2610
+ width: 350px;
2611
+ height: 350px;
2612
+ pointer-events: none;
2613
+ opacity: 0;
2614
+ transition: opacity 150ms ease;
2495
2615
  }
2496
- .fb-file-card-row .fb-file-dropzone,
2497
- .fb-file-card-row .fb-file-library-card {
2498
- flex: 1;
2499
- min-width: 0;
2616
+ .fb-tile-zoom-preview.fb-tile-zoom-preview--visible {
2617
+ opacity: 1;
2618
+ }
2619
+ .fb-tile-zoom-preview-img {
2620
+ width: 100%;
2621
+ height: 100%;
2622
+ object-fit: contain;
2623
+ display: block;
2624
+ border-radius: calc(0.5rem - 2px);
2500
2625
  }
2501
2626
 
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);
2627
+ /* \u2500\u2500\u2500 Single-file uploading state \u2500\u2500\u2500 */
2628
+ .fb-single-uploading {
2629
+ height: 180px;
2630
+ border-radius: 0.75rem;
2631
+ border: 1px dashed #60a5fa;
2632
+ background: rgba(239,246,255,0.5);
2507
2633
  display: flex;
2508
2634
  flex-direction: column;
2509
2635
  align-items: center;
2510
2636
  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%;
2637
+ gap: 8px;
2519
2638
  }
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;
2639
+
2640
+ /* \u2500\u2500\u2500 Video overlays \u2500\u2500\u2500 */
2641
+ .fb-video-overlay {
2642
+ position: absolute;
2643
+ inset: 0;
2644
+ display: flex;
2645
+ align-items: center;
2646
+ justify-content: center;
2647
+ background: rgba(0,0,0,0.25);
2525
2648
  }
2526
- .fb-file-library-card-icon {
2527
- font-size: 24px;
2528
- line-height: 1;
2529
- flex-shrink: 0;
2649
+ .fb-play-btn {
2650
+ background: rgba(255,255,255,0.9);
2651
+ border-radius: 50%;
2652
+ display: flex;
2653
+ align-items: center;
2654
+ justify-content: center;
2530
2655
  }
2531
- .fb-file-library-card-label {
2532
- font-size: 13px;
2533
- color: var(--fb-text-secondary-color, #6b7280);
2656
+ .fb-video-preview-wrap {
2657
+ position: relative;
2658
+ width: 100%;
2659
+ height: 100%;
2534
2660
  }
2535
- .fb-file-library-card-hint {
2661
+ .fb-video-btn-overlay {
2662
+ position: absolute;
2663
+ top: 8px;
2664
+ right: 8px;
2665
+ z-index: 10;
2666
+ display: flex;
2667
+ gap: 4px;
2668
+ opacity: 0;
2669
+ transition: opacity 150ms;
2670
+ pointer-events: none;
2671
+ }
2672
+ .fb-video-preview-wrap:hover .fb-video-btn-overlay {
2673
+ opacity: 1;
2674
+ pointer-events: auto;
2675
+ }
2676
+ .fb-video-btn {
2677
+ border: none;
2678
+ border-radius: 4px;
2536
2679
  font-size: 11px;
2537
- color: var(--fb-file-upload-text-color, #9ca3af);
2680
+ padding: 4px 8px;
2681
+ cursor: pointer;
2682
+ color: #fff;
2683
+ line-height: 1.2;
2538
2684
  }
2685
+ .fb-video-btn-delete { background: rgba(220,38,38,0.85); }
2686
+ .fb-video-btn-delete:hover { background: rgba(185,28,28,0.95); }
2687
+ .fb-video-btn-change { background: rgba(31,41,55,0.85); }
2688
+ .fb-video-btn-change:hover { background: rgba(17,24,39,0.95); }
2539
2689
 
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;
2690
+ /* \u2500\u2500\u2500 Readonly readonly tile \u2500\u2500\u2500 */
2691
+ .fb-readonly-tile {
2692
+ aspect-ratio: 1 / 1;
2693
+ border-radius: 0.5rem;
2694
+ border: 1px solid #e2e8f0;
2558
2695
  overflow: hidden;
2559
- border-radius: var(--fb-border-radius, 0.5rem);
2696
+ position: relative;
2697
+ cursor: pointer;
2560
2698
  }
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;
2699
+ .fb-readonly-tile img {
2700
+ width: 100%;
2701
+ height: 100%;
2702
+ object-fit: contain;
2703
+ display: block;
2566
2704
  }
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;
2705
+ .fb-readonly-tile .fb-tile-actions {
2580
2706
  opacity: 0;
2581
- transition: opacity 150ms ease;
2582
2707
  }
2583
- .fb-tile-zoom-preview.fb-tile-zoom-preview--visible {
2708
+ .fb-readonly-tile:hover .fb-tile-actions {
2584
2709
  opacity: 1;
2585
2710
  }
2586
- .fb-tile-zoom-preview-img {
2711
+
2712
+ /* \u2500\u2500\u2500 Readonly single-file filled \u2500\u2500\u2500 */
2713
+ .fb-single-readonly-filled {
2714
+ position: relative;
2715
+ border-radius: 0.75rem;
2716
+ border: 1px solid #e2e8f0;
2717
+ overflow: hidden;
2718
+ height: 220px;
2719
+ display: block;
2720
+ cursor: pointer;
2721
+ }
2722
+ .fb-single-readonly-filled img {
2587
2723
  width: 100%;
2588
2724
  height: 100%;
2589
2725
  object-fit: contain;
2590
2726
  display: block;
2591
- background: var(--fb-file-upload-bg-color, #f3f4f6);
2592
- border-radius: calc(var(--fb-border-radius, 0.5rem) - 2px);
2727
+ }
2728
+
2729
+ /* \u2500\u2500\u2500 Readonly multi grid \u2500\u2500\u2500 */
2730
+ .fb-multi-readonly-grid {
2731
+ display: grid;
2732
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
2733
+ gap: 10px;
2593
2734
  }
2594
2735
  `;
2595
2736
  document.head.appendChild(style);
2596
2737
  }
2597
2738
 
2598
2739
  // src/components/file/dom.ts
2599
- var TILE_SIZE = "160px";
2600
2740
  function createFileTile() {
2601
2741
  ensureFileStyles();
2602
2742
  const tile = document.createElement("div");
@@ -2605,7 +2745,7 @@ function createFileTile() {
2605
2745
  }
2606
2746
  function showFileError(container, message) {
2607
2747
  var _a, _b;
2608
- const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
2748
+ const existing = (_a = container.closest("[data-files-wrapper]")) == null ? void 0 : _a.querySelector(".file-error-message");
2609
2749
  if (existing) existing.remove();
2610
2750
  const errorEl = document.createElement("div");
2611
2751
  errorEl.className = "file-error-message error-message";
@@ -2615,11 +2755,11 @@ function showFileError(container, message) {
2615
2755
  margin-top: 0.25rem;
2616
2756
  `;
2617
2757
  errorEl.textContent = message;
2618
- (_b = container.closest(".space-y-2")) == null ? void 0 : _b.appendChild(errorEl);
2758
+ (_b = container.closest("[data-files-wrapper]")) == null ? void 0 : _b.appendChild(errorEl);
2619
2759
  }
2620
2760
  function clearFileError(container) {
2621
2761
  var _a;
2622
- const existing = (_a = container.closest(".space-y-2")) == null ? void 0 : _a.querySelector(".file-error-message");
2762
+ const existing = (_a = container.closest("[data-files-wrapper]")) == null ? void 0 : _a.querySelector(".file-error-message");
2623
2763
  if (existing) existing.remove();
2624
2764
  }
2625
2765
  function addDeleteButton(container, state, onDelete) {
@@ -2637,14 +2777,6 @@ function addDeleteButton(container, state, onDelete) {
2637
2777
  overlay.appendChild(deleteBtn);
2638
2778
  container.appendChild(overlay);
2639
2779
  }
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
2780
  function createUploadingTile(fileName, state) {
2649
2781
  ensureFileStyles();
2650
2782
  const tile = createFileTile();
@@ -2660,10 +2792,14 @@ function createUploadingTile(fileName, state) {
2660
2792
  return tile;
2661
2793
  }
2662
2794
  function ensureTilesWrap(list) {
2795
+ var _a, _b, _c;
2796
+ const existingGrid = list.querySelector(".fb-multi-grid");
2797
+ if (existingGrid) return existingGrid;
2663
2798
  const existing = list.querySelector(".fb-tiles-wrap");
2664
2799
  if (existing) return existing;
2665
- const dropzone = list.querySelector(".fb-file-dropzone");
2666
- if (dropzone) dropzone.remove();
2800
+ (_a = list.querySelector(".fb-file-dropzone")) == null ? void 0 : _a.remove();
2801
+ (_b = list.querySelector(".fb-wide-tile")) == null ? void 0 : _b.remove();
2802
+ (_c = list.querySelector(".fb-multi-outer")) == null ? void 0 : _c.remove();
2667
2803
  const tilesWrap = document.createElement("div");
2668
2804
  tilesWrap.className = "fb-tiles-wrap";
2669
2805
  tilesWrap.style.cssText = "display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;";
@@ -2675,7 +2811,7 @@ function ensureTilesWrap(list) {
2675
2811
  return tilesWrap;
2676
2812
  }
2677
2813
  function setEmptyFileContainer(fileContainer, state, hint) {
2678
- const hintHtml = hint ? `<div class="text-xs text-gray-500 mt-1">${escapeHtml(hint)}</div>` : "";
2814
+ const hintHtml = "";
2679
2815
  fileContainer.innerHTML = `
2680
2816
  <div class="flex flex-col items-center justify-center h-full text-gray-400">
2681
2817
  <svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
@@ -2718,9 +2854,11 @@ function setupDragAndDrop(element, dropHandler) {
2718
2854
  }
2719
2855
 
2720
2856
  // 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>`;
2857
+ 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>`;
2858
+ 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>`;
2859
+ 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>`;
2860
+ 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>`;
2861
+ 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
2862
  function canDownload(state, meta) {
2725
2863
  return Boolean(
2726
2864
  state.config.downloadFile || state.config.getDownloadUrl || state.config.getThumbnail || (meta == null ? void 0 : meta.file)
@@ -2732,7 +2870,16 @@ function canOpenInTab(state, meta) {
2732
2870
  );
2733
2871
  }
2734
2872
  function createTileActions(options) {
2735
- const { canRemove, removeHandler, state, resourceId, fileName, meta } = options;
2873
+ const {
2874
+ canRemove,
2875
+ removeHandler,
2876
+ state,
2877
+ resourceId,
2878
+ fileName,
2879
+ meta,
2880
+ replaceHandler,
2881
+ libraryHandler
2882
+ } = options;
2736
2883
  const group = document.createElement("div");
2737
2884
  group.className = "fb-tile-actions";
2738
2885
  const makeBtn = (icon, label, cls) => {
@@ -2747,6 +2894,16 @@ function createTileActions(options) {
2747
2894
  });
2748
2895
  return btn;
2749
2896
  };
2897
+ if (replaceHandler) {
2898
+ const replaceBtn = makeBtn(ICON_REPLACE, t("replaceFile", state), "fb-tile-action-replace");
2899
+ replaceBtn.addEventListener("click", () => replaceHandler());
2900
+ group.appendChild(replaceBtn);
2901
+ }
2902
+ if (libraryHandler) {
2903
+ const libBtn = makeBtn(ICON_LIBRARY, t("fromLibrary", state), "fb-tile-action-library");
2904
+ libBtn.addEventListener("click", () => libraryHandler());
2905
+ group.appendChild(libBtn);
2906
+ }
2750
2907
  if (canDownload(state, meta)) {
2751
2908
  const dlBtn = makeBtn(ICON_DOWNLOAD, t("downloadFile", state), "fb-tile-action-download");
2752
2909
  dlBtn.addEventListener("click", () => {
@@ -2932,8 +3089,7 @@ function attachClonedActionListeners(cloned, original) {
2932
3089
  }
2933
3090
  function renderLocalImagePreview(container, file, fileName, state) {
2934
3091
  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)";
3092
+ img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
2937
3093
  img.alt = fileName || t("previewAlt", state);
2938
3094
  const reader = new FileReader();
2939
3095
  reader.onload = (e) => {
@@ -2956,7 +3112,7 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
2956
3112
  const newContainer = setupDragDropless(container);
2957
3113
  newContainer.innerHTML = `
2958
3114
  <div class="fb-video-preview-wrap">
2959
- <video class="w-full h-full object-contain" controls preload="auto" muted src="${videoUrl}">
3115
+ <video style="width:100%;height:100%;object-fit:contain;" controls preload="auto" muted src="${videoUrl}">
2960
3116
  ${escapeHtml(t("videoNotSupported", state))}
2961
3117
  </video>
2962
3118
  <div class="fb-video-btn-overlay">
@@ -3002,11 +3158,11 @@ function handleVideoDelete(container, resourceId, state, deps) {
3002
3158
  container.onclick = deps.fileUploadHandler;
3003
3159
  }
3004
3160
  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">
3161
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--fb-text-secondary-color,#9ca3af);">
3162
+ <svg style="width:1.5rem;height:1.5rem;margin-bottom:0.5rem;" fill="currentColor" viewBox="0 0 24 24">
3007
3163
  <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
3164
  </svg>
3009
- <div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
3165
+ <div style="font-size:0.875rem;text-align:center;">${escapeHtml(t("clickDragText", state))}</div>
3010
3166
  </div>
3011
3167
  `;
3012
3168
  if (deps == null ? void 0 : deps.setupDrop) {
@@ -3024,11 +3180,11 @@ function renderDeleteButton(container, resourceId, state) {
3024
3180
  hiddenInput.value = "";
3025
3181
  }
3026
3182
  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">
3183
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--fb-text-secondary-color,#9ca3af);">
3184
+ <svg style="width:1.5rem;height:1.5rem;margin-bottom:0.5rem;" fill="currentColor" viewBox="0 0 24 24">
3029
3185
  <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
3186
  </svg>
3031
- <div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
3187
+ <div style="font-size:0.875rem;text-align:center;">${escapeHtml(t("clickDragText", state))}</div>
3032
3188
  </div>
3033
3189
  `;
3034
3190
  });
@@ -3048,7 +3204,7 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
3048
3204
  deps
3049
3205
  );
3050
3206
  } 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>`;
3207
+ 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
3208
  }
3053
3209
  if (!isReadonly && !((_c = meta.type) == null ? void 0 : _c.startsWith("video/"))) {
3054
3210
  renderDeleteButton(container, resourceId, state);
@@ -3056,7 +3212,7 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
3056
3212
  }
3057
3213
  function renderUploadedVideoPreview(container, thumbnailUrl, state) {
3058
3214
  const video = document.createElement("video");
3059
- video.className = "w-full h-full object-contain";
3215
+ video.style.cssText = "width:100%;height:100%;object-fit:contain;";
3060
3216
  video.controls = true;
3061
3217
  video.preload = "metadata";
3062
3218
  video.muted = true;
@@ -3078,8 +3234,7 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
3078
3234
  renderUploadedVideoPreview(container, thumbnailUrl, state);
3079
3235
  } else {
3080
3236
  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)";
3237
+ img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
3083
3238
  img.alt = fileName || t("previewAlt", state);
3084
3239
  img.src = thumbnailUrl;
3085
3240
  container.appendChild(img);
@@ -3090,11 +3245,11 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
3090
3245
  } catch (error) {
3091
3246
  console.error("Failed to get thumbnail:", error);
3092
3247
  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">
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">
3095
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"/>
3096
3251
  </svg>
3097
- <div class="text-sm text-center">${escapeHtml(fileName || t("previewUnavailable", state))}</div>
3252
+ <div style="font-size:0.875rem;text-align:center;">${escapeHtml(fileName || t("previewUnavailable", state))}</div>
3098
3253
  </div>
3099
3254
  `;
3100
3255
  }
@@ -3267,20 +3422,15 @@ async function renderSingleFileEditTile(fileContainer, resourceId, state, deps)
3267
3422
  fileContainer.appendChild(tile);
3268
3423
  }
3269
3424
  async function fillTileContent(tile, rid, meta, state, actionsEl) {
3270
- var _a, _b, _c;
3425
+ var _a, _b;
3271
3426
  if ((_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("image/")) {
3272
3427
  if (meta.file && meta.file instanceof File) {
3273
3428
  const img = document.createElement("img");
3274
3429
  img.style.cssText = "width:100%;height:100%;object-fit:contain;background:var(--fb-file-upload-bg-color,#f3f4f6);";
3275
3430
  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);
3431
+ img.src = getLocalFileUrl(meta.file);
3283
3432
  tile.appendChild(img);
3433
+ attachZoomHover(tile, img.src, meta.name, actionsEl != null ? actionsEl : null);
3284
3434
  } else if (state.config.getThumbnail) {
3285
3435
  try {
3286
3436
  const url = await state.config.getThumbnail(rid);
@@ -3337,17 +3487,21 @@ async function fillTileContent(tile, rid, meta, state, actionsEl) {
3337
3487
  }
3338
3488
  if (actionsEl) tile.appendChild(actionsEl);
3339
3489
  } 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);
3490
+ fillDocumentFallback(tile, rid, meta, actionsEl);
3349
3491
  }
3350
3492
  }
3493
+ function fillDocumentFallback(tile, rid, meta, actionsEl) {
3494
+ var _a, _b;
3495
+ const fileName = (_b = (_a = meta == null ? void 0 : meta.name) != null ? _a : rid.split("/").pop()) != null ? _b : "";
3496
+ if (fileName) tile.title = fileName;
3497
+ 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>` : "";
3498
+ tile.innerHTML = `
3499
+ <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:6px;gap:4px;">
3500
+ <div style="font-size:36px;">\u{1F4C1}</div>
3501
+ ${labelHtml}
3502
+ </div>`;
3503
+ if (actionsEl) tile.appendChild(actionsEl);
3504
+ }
3351
3505
  async function forceDownload(resourceId, fileName, state) {
3352
3506
  try {
3353
3507
  let fileUrl = null;
@@ -3412,7 +3566,7 @@ async function uploadSingleFile(file, state) {
3412
3566
  }
3413
3567
  }
3414
3568
  async function handleFileSelect(opts) {
3415
- var _a, _b;
3569
+ var _a, _b, _c;
3416
3570
  const {
3417
3571
  file,
3418
3572
  container,
@@ -3448,6 +3602,10 @@ async function handleFileSelect(opts) {
3448
3602
  return;
3449
3603
  }
3450
3604
  clearFileError(container);
3605
+ const existingHiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
3606
+ 'input[type="hidden"]'
3607
+ );
3608
+ const previousRid = (existingHiddenInput == null ? void 0 : existingHiddenInput.value) || null;
3451
3609
  ensureFileStyles();
3452
3610
  container.innerHTML = `
3453
3611
  <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:6px;padding:6px;">
@@ -3458,7 +3616,13 @@ async function handleFileSelect(opts) {
3458
3616
  try {
3459
3617
  rid = await uploadSingleFile(file, state);
3460
3618
  } catch (error) {
3461
- setEmptyFileContainer(container, state);
3619
+ if (previousRid && (deps == null ? void 0 : deps.onAfterUpload)) {
3620
+ deps.onAfterUpload(container, previousRid);
3621
+ } else if (deps == null ? void 0 : deps.onRemove) {
3622
+ deps.onRemove();
3623
+ } else {
3624
+ setEmptyFileContainer(container, state);
3625
+ }
3462
3626
  throw error;
3463
3627
  }
3464
3628
  state.resourceIndex.set(rid, {
@@ -3468,18 +3632,21 @@ async function handleFileSelect(opts) {
3468
3632
  uploadedAt: /* @__PURE__ */ new Date(),
3469
3633
  file
3470
3634
  });
3471
- let hiddenInput = (_a = container.parentElement) == null ? void 0 : _a.querySelector(
3472
- 'input[type="hidden"]'
3473
- );
3635
+ if (previousRid && previousRid !== rid) {
3636
+ releaseLocalFileUrl((_b = state.resourceIndex.get(previousRid)) == null ? void 0 : _b.file);
3637
+ }
3638
+ let hiddenInput = existingHiddenInput;
3474
3639
  if (!hiddenInput) {
3475
3640
  hiddenInput = document.createElement("input");
3476
3641
  hiddenInput.type = "hidden";
3477
3642
  hiddenInput.name = fieldName;
3478
- (_b = container.parentElement) == null ? void 0 : _b.appendChild(hiddenInput);
3643
+ (_c = container.parentElement) == null ? void 0 : _c.appendChild(hiddenInput);
3479
3644
  }
3480
3645
  hiddenInput.value = rid;
3481
3646
  const isVideo = file.type.startsWith("video/");
3482
- if (!isVideo && deps) {
3647
+ if (!isVideo && (deps == null ? void 0 : deps.onAfterUpload)) {
3648
+ deps.onAfterUpload(container, rid);
3649
+ } else if (!isVideo && deps) {
3483
3650
  renderSingleFileEditTile(container, rid, state, deps).catch(console.error);
3484
3651
  } else {
3485
3652
  renderFilePreview(container, rid, state, {
@@ -3540,17 +3707,18 @@ function filterAndSlice(allFiles, currentCount, constraints, state) {
3540
3707
  return { accepted, errorMessage: errorParts.join(" \u2022 ") };
3541
3708
  }
3542
3709
  async function uploadBatch(accepted, resourceIds, listEl, state) {
3710
+ var _a;
3711
+ if (listEl) {
3712
+ const tilesWrap = ensureTilesWrap(listEl);
3713
+ const addTile = (_a = tilesWrap.querySelector(".fb-multi-add-tile-js")) != null ? _a : tilesWrap.querySelector(".fb-tile-add");
3714
+ if (addTile) addTile.style.display = "none";
3715
+ }
3543
3716
  await Promise.all(
3544
3717
  accepted.map(async (file) => {
3545
3718
  const placeholder = createUploadingTile(file.name, state);
3546
3719
  if (listEl) {
3547
3720
  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
- }
3721
+ tilesWrap.appendChild(placeholder);
3554
3722
  }
3555
3723
  try {
3556
3724
  const rid = await uploadSingleFile(file, state);
@@ -3593,7 +3761,7 @@ function setupFilesDropHandler(filesContainer, resourceIds, state, updateCallbac
3593
3761
  function setupFilesPickerHandler(filesPicker, resourceIds, state, updateCallback, constraints, pathKey, instance) {
3594
3762
  filesPicker.onchange = async () => {
3595
3763
  if (!filesPicker.files) return;
3596
- const wrapperEl = filesPicker.closest(".space-y-2") || filesPicker.parentElement;
3764
+ const wrapperEl = filesPicker.closest("[data-files-wrapper]") || filesPicker.parentElement;
3597
3765
  const { accepted, errorMessage } = filterAndSlice(
3598
3766
  Array.from(filesPicker.files),
3599
3767
  resourceIds.length,
@@ -3728,7 +3896,7 @@ async function handleLibraryPickMulti(state, element, wrapper, fieldPath, resour
3728
3896
  }
3729
3897
  }
3730
3898
  async function handleLibraryPickSingle(state, element, container, fileWrapper, pathKey, fieldPath, renderCallback, instance) {
3731
- var _a;
3899
+ var _a, _b;
3732
3900
  if (!state.config.pickExistingFiles) return;
3733
3901
  const allowedExtensions = getAllowedExtensions(element.accept);
3734
3902
  const allowedMimes = getAllowedMimes(element.accept);
@@ -3762,66 +3930,286 @@ async function handleLibraryPickSingle(state, element, container, fileWrapper, p
3762
3930
  hiddenInput.name = pathKey;
3763
3931
  fileWrapper.appendChild(hiddenInput);
3764
3932
  }
3933
+ const previousRid = hiddenInput.value || null;
3934
+ if (previousRid && previousRid !== first.resourceId) {
3935
+ releaseLocalFileUrl((_b = state.resourceIndex.get(previousRid)) == null ? void 0 : _b.file);
3936
+ }
3765
3937
  hiddenInput.value = first.resourceId;
3766
3938
  await renderCallback(first.resourceId);
3767
3939
  if (!state.config.readonly) {
3768
3940
  instance.triggerOnChange(fieldPath, first.resourceId);
3769
3941
  }
3770
3942
  }
3771
-
3772
- // src/components/file/render-edit.ts
3773
- function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps) {
3774
- var _a;
3775
- seedInferredResource(initial, state.resourceIndex);
3776
- const meta = state.resourceIndex.get(initial);
3943
+
3944
+ // src/components/file/render-edit.ts
3945
+ 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>`;
3946
+ 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>`;
3947
+ function handleInitialFileData(initial, fileContainer, pathKey, fileWrapper, state, deps, extras) {
3948
+ var _a;
3949
+ seedInferredResource(initial, state.resourceIndex);
3950
+ const meta = state.resourceIndex.get(initial);
3951
+ const isVideo = (_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/");
3952
+ if (isVideo) {
3953
+ renderFilePreview(fileContainer, initial, state, {
3954
+ fileName: initial,
3955
+ isReadonly: false,
3956
+ deps
3957
+ }).catch(console.error);
3958
+ } else {
3959
+ renderSingleFileFilled(fileContainer, initial, state, deps, extras);
3960
+ }
3961
+ const hiddenInput = document.createElement("input");
3962
+ hiddenInput.type = "hidden";
3963
+ hiddenInput.name = pathKey;
3964
+ hiddenInput.value = initial;
3965
+ fileWrapper.appendChild(hiddenInput);
3966
+ }
3967
+ function buildWideTile(state, hasLibrary, onUploadClick, onLibraryClick, isDragOver = false, constraintHint = "") {
3968
+ ensureFileStyles();
3969
+ const outer = document.createElement("div");
3970
+ outer.className = `fb-wide-tile${hasLibrary ? " fb-file-card-row" : ""}${isDragOver ? " fb-drag-over" : ""}`;
3971
+ const uploadBtn = document.createElement("button");
3972
+ uploadBtn.type = "button";
3973
+ uploadBtn.className = "fb-wide-tile-upload fb-file-dropzone";
3974
+ const cloudIcon = document.createElement("span");
3975
+ cloudIcon.style.cssText = "width:36px;height:36px;display:block;flex-shrink:0;";
3976
+ cloudIcon.innerHTML = ICON_CLOUD;
3977
+ uploadBtn.appendChild(cloudIcon);
3978
+ const primaryText = document.createElement("div");
3979
+ primaryText.className = "fb-wide-tile-label";
3980
+ primaryText.style.cssText = "font-size:14px;font-weight:600;";
3981
+ primaryText.textContent = isDragOver ? t("dropToUpload", state) : t("clickDragText", state);
3982
+ uploadBtn.appendChild(primaryText);
3983
+ if (constraintHint) {
3984
+ const hintEl = document.createElement("div");
3985
+ hintEl.style.cssText = "font-size:11px;opacity:0.65;margin-top:2px;";
3986
+ hintEl.textContent = constraintHint;
3987
+ uploadBtn.appendChild(hintEl);
3988
+ }
3989
+ uploadBtn.onclick = (e) => {
3990
+ e.stopPropagation();
3991
+ onUploadClick();
3992
+ };
3993
+ outer.appendChild(uploadBtn);
3994
+ if (hasLibrary && onLibraryClick) {
3995
+ const divider = document.createElement("div");
3996
+ divider.className = "fb-wide-tile-divider";
3997
+ outer.appendChild(divider);
3998
+ const libBtn = document.createElement("button");
3999
+ libBtn.type = "button";
4000
+ libBtn.className = "fb-wide-tile-library fb-file-library-card";
4001
+ const libIcon = document.createElement("span");
4002
+ libIcon.style.cssText = "width:28px;height:28px;display:block;flex-shrink:0;";
4003
+ libIcon.innerHTML = ICON_LIBRARY2;
4004
+ libBtn.appendChild(libIcon);
4005
+ const libLabel = document.createElement("div");
4006
+ libLabel.style.cssText = "font-size:13px;font-weight:600;text-align:center;";
4007
+ libLabel.textContent = t("fromLibrary", state);
4008
+ libBtn.appendChild(libLabel);
4009
+ const libHint = document.createElement("div");
4010
+ libHint.style.cssText = "font-size:11px;opacity:0.75;text-align:center;";
4011
+ libHint.textContent = t("libraryHint", state);
4012
+ libBtn.appendChild(libHint);
4013
+ libBtn.onclick = (e) => {
4014
+ e.stopPropagation();
4015
+ onLibraryClick();
4016
+ };
4017
+ outer.appendChild(libBtn);
4018
+ }
4019
+ attachDragOverFeedback(outer, {
4020
+ onEnter: () => {
4021
+ const primaryText2 = outer.querySelector(".fb-wide-tile-label");
4022
+ if (primaryText2) primaryText2.textContent = t("dropToUpload", state);
4023
+ },
4024
+ onLeave: () => {
4025
+ const primaryText2 = outer.querySelector(".fb-wide-tile-label");
4026
+ if (primaryText2) primaryText2.textContent = t("clickDragText", state);
4027
+ },
4028
+ activeClass: "fb-drag-over"
4029
+ });
4030
+ return outer;
4031
+ }
4032
+ function attachDragOverFeedback(el, hooks) {
4033
+ let depth = 0;
4034
+ el.addEventListener("dragover", (e) => {
4035
+ e.preventDefault();
4036
+ });
4037
+ el.addEventListener("dragenter", (e) => {
4038
+ e.preventDefault();
4039
+ depth++;
4040
+ if (depth === 1) {
4041
+ el.classList.add(hooks.activeClass);
4042
+ hooks.onEnter();
4043
+ }
4044
+ });
4045
+ el.addEventListener("dragleave", (e) => {
4046
+ e.preventDefault();
4047
+ depth = Math.max(0, depth - 1);
4048
+ if (depth === 0) {
4049
+ el.classList.remove(hooks.activeClass);
4050
+ hooks.onLeave();
4051
+ }
4052
+ });
4053
+ el.addEventListener("drop", () => {
4054
+ depth = 0;
4055
+ el.classList.remove(hooks.activeClass);
4056
+ hooks.onLeave();
4057
+ });
4058
+ }
4059
+ function renderSingleFileFilled(fileContainer, resourceId, state, deps, extras) {
4060
+ var _a, _b;
4061
+ const meta = state.resourceIndex.get(resourceId);
3777
4062
  const isVideo = (_a = meta == null ? void 0 : meta.type) == null ? void 0 : _a.startsWith("video/");
3778
4063
  if (isVideo) {
3779
- renderFilePreview(fileContainer, initial, state, {
3780
- fileName: initial,
4064
+ renderFilePreview(fileContainer, resourceId, state, {
4065
+ fileName: (_b = meta == null ? void 0 : meta.name) != null ? _b : "",
3781
4066
  isReadonly: false,
3782
4067
  deps
3783
4068
  }).catch(console.error);
3784
- } else {
3785
- renderSingleFileEditTile(fileContainer, initial, state, deps).catch(console.error);
4069
+ return;
3786
4070
  }
3787
- const hiddenInput = document.createElement("input");
3788
- hiddenInput.type = "hidden";
3789
- hiddenInput.name = pathKey;
3790
- hiddenInput.value = initial;
3791
- fileWrapper.appendChild(hiddenInput);
4071
+ ensureFileStyles();
4072
+ const outer = document.createElement("div");
4073
+ outer.className = "fb-multi-outer fb-multi-has-files";
4074
+ const grid = document.createElement("div");
4075
+ grid.className = "fb-multi-grid fb-tiles-wrap";
4076
+ outer.appendChild(grid);
4077
+ const tile = buildPreviewTile(
4078
+ resourceId,
4079
+ state,
4080
+ Boolean(deps.onRemove),
4081
+ deps.onRemove ? () => {
4082
+ var _a2;
4083
+ return (_a2 = deps.onRemove) == null ? void 0 : _a2.call(deps);
4084
+ } : null,
4085
+ extras
4086
+ );
4087
+ grid.appendChild(tile);
4088
+ fileContainer.className = "file-preview-container";
4089
+ fileContainer.removeAttribute("style");
4090
+ while (fileContainer.firstChild) fileContainer.removeChild(fileContainer.firstChild);
4091
+ fileContainer.appendChild(outer);
3792
4092
  }
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;
4093
+ function buildMultiAddTile(state, hasLibrary, onUploadClick, onLibraryClick, isDragOver = false) {
4094
+ const tile = document.createElement("div");
4095
+ tile.className = `fb-multi-add-tile fb-multi-add-tile-js${isDragOver ? " fb-drag-over-tile" : ""}`;
4096
+ const uploadBtn = document.createElement("button");
4097
+ uploadBtn.type = "button";
4098
+ uploadBtn.className = "fb-multi-add-upload fb-tile-add fb-file-dropzone";
4099
+ const cloudIcon = document.createElement("span");
4100
+ cloudIcon.style.cssText = "width:28px;height:28px;display:block;flex-shrink:0;";
4101
+ cloudIcon.innerHTML = ICON_CLOUD;
4102
+ uploadBtn.appendChild(cloudIcon);
4103
+ const uploadLabel = document.createElement("span");
4104
+ uploadLabel.className = "fb-multi-add-label";
4105
+ uploadLabel.style.cssText = "font-size:11px;font-weight:600;";
4106
+ uploadLabel.textContent = isDragOver ? t("dropToUpload", state) : t("clickDragTextMultiple", state);
4107
+ uploadBtn.appendChild(uploadLabel);
4108
+ uploadBtn.onclick = (e) => {
4109
+ e.stopPropagation();
4110
+ onUploadClick();
4111
+ };
4112
+ tile.appendChild(uploadBtn);
4113
+ if (hasLibrary && onLibraryClick) {
4114
+ const divider = document.createElement("div");
4115
+ divider.className = "fb-multi-add-divider";
4116
+ tile.appendChild(divider);
4117
+ const libBtn = document.createElement("button");
4118
+ libBtn.type = "button";
4119
+ libBtn.className = "fb-multi-add-library fb-tile-add-library fb-file-library-card";
4120
+ libBtn.setAttribute("aria-label", t("fromLibrary", state));
4121
+ const libIcon = document.createElement("span");
4122
+ libIcon.style.cssText = "width:14px;height:14px;display:block;flex-shrink:0;";
4123
+ libIcon.innerHTML = ICON_LIBRARY2;
4124
+ libBtn.appendChild(libIcon);
4125
+ libBtn.appendChild(document.createTextNode(t("fromLibrary", state)));
4126
+ libBtn.onclick = (e) => {
4127
+ e.stopPropagation();
4128
+ onLibraryClick();
4129
+ };
4130
+ tile.appendChild(libBtn);
4131
+ }
4132
+ return tile;
3806
4133
  }
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
- `;
4134
+ function buildPreviewTile(rid, state, canRemove, onRemove, extras) {
4135
+ var _a, _b, _c;
4136
+ ensureFileStyles();
4137
+ const meta = state.resourceIndex.get(rid);
4138
+ const tile = document.createElement("div");
4139
+ tile.className = "fb-preview-tile fb-checker fb-tile-resource resource-pill";
4140
+ tile.dataset.resourceId = rid;
4141
+ const actionsEl = createTileActions({
4142
+ canRemove: canRemove && onRemove !== null,
4143
+ removeHandler: onRemove,
4144
+ state,
4145
+ resourceId: rid,
4146
+ fileName: (_a = meta == null ? void 0 : meta.name) != null ? _a : "",
4147
+ meta,
4148
+ replaceHandler: (_b = extras == null ? void 0 : extras.replaceHandler) != null ? _b : null,
4149
+ libraryHandler: (_c = extras == null ? void 0 : extras.libraryHandler) != null ? _c : null
4150
+ });
4151
+ fillTileContent(tile, rid, meta, state, actionsEl).catch((err) => {
4152
+ console.error("Failed to render tile:", err);
4153
+ });
4154
+ return tile;
4155
+ }
4156
+ function buildPlaceholderTile(isDragOver = false) {
4157
+ const div = document.createElement("div");
4158
+ div.className = `fb-multi-placeholder fb-checker${isDragOver ? " fb-drag-over" : ""}`;
4159
+ return div;
4160
+ }
4161
+ function buildMetaLine(state, element, ridCount, maxCount, canClearAll, onClearAll) {
4162
+ const line = document.createElement("div");
4163
+ line.className = "fb-meta-line";
4164
+ const metaText = document.createElement("div");
4165
+ metaText.className = "fb-meta-text";
4166
+ if (element.maxSize && element.maxSize !== Infinity) {
4167
+ const sizeSpan = document.createElement("span");
4168
+ sizeSpan.textContent = t("hintMaxSize", state, { size: element.maxSize });
4169
+ metaText.appendChild(sizeSpan);
4170
+ metaText.appendChild(buildMetaDot());
4171
+ }
4172
+ const exts = getAllowedExtensions(element.accept);
4173
+ if (exts.length > 0) {
4174
+ const fmtSpan = document.createElement("span");
4175
+ fmtSpan.className = "fb-meta-mono";
4176
+ fmtSpan.textContent = exts.map((e) => e.toUpperCase()).join(", ");
4177
+ metaText.appendChild(fmtSpan);
4178
+ metaText.appendChild(buildMetaDot());
4179
+ }
4180
+ const countSpan = document.createElement("span");
4181
+ if (maxCount < Infinity) {
4182
+ countSpan.textContent = t("fileCountWithMax", state, {
4183
+ count: ridCount,
4184
+ max: maxCount
4185
+ });
3817
4186
  } 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));
4187
+ const countKey = ridCount === 1 ? "fileCountSingle" : "fileCountPlural";
4188
+ countSpan.textContent = t(countKey, state, { count: ridCount });
4189
+ }
4190
+ metaText.appendChild(countSpan);
4191
+ line.appendChild(metaText);
4192
+ if (canClearAll && ridCount > 1) {
4193
+ const clearBtn = document.createElement("button");
4194
+ clearBtn.type = "button";
4195
+ clearBtn.className = "fb-clear-all-btn";
4196
+ clearBtn.textContent = t("clearAll", state);
4197
+ clearBtn.onclick = (e) => {
4198
+ e.stopPropagation();
4199
+ if (window.confirm(t("clearAll", state) + "?")) {
4200
+ onClearAll();
4201
+ }
4202
+ };
4203
+ line.appendChild(clearBtn);
3821
4204
  }
3822
- btn.addEventListener("click", onClick);
3823
- return btn;
4205
+ return line;
3824
4206
  }
4207
+ function buildMetaDot() {
4208
+ const dot = document.createElement("span");
4209
+ dot.className = "fb-meta-dot";
4210
+ return dot;
4211
+ }
4212
+ var gridResizeObservers = /* @__PURE__ */ new WeakMap();
3825
4213
  function renderResourcePills(opts) {
3826
4214
  var _a;
3827
4215
  const {
@@ -3829,130 +4217,185 @@ function renderResourcePills(opts) {
3829
4217
  rids,
3830
4218
  state,
3831
4219
  onRemove,
3832
- hint,
3833
- countInfo,
3834
4220
  maxCount,
3835
4221
  isReadonly = false,
3836
- onLibraryPick
4222
+ onLibraryPick,
4223
+ element,
4224
+ onClearAll,
4225
+ openPicker: openPickerProp
3837
4226
  } = opts;
3838
4227
  ensureFileStyles();
3839
4228
  const wrapper = container.closest("[data-files-wrapper]");
3840
4229
  if (wrapper) {
3841
4230
  wrapper.dataset.resourceIds = JSON.stringify(rids != null ? rids : []);
3842
4231
  }
4232
+ const previousObserver = gridResizeObservers.get(container);
4233
+ if (previousObserver) {
4234
+ previousObserver.disconnect();
4235
+ gridResizeObservers.delete(container);
4236
+ }
3843
4237
  while (container.firstChild) container.removeChild(container.firstChild);
3844
4238
  const ridList = rids != null ? rids : [];
3845
- const atMax = maxCount !== void 0 && ridList.length >= maxCount;
4239
+ const effectiveMax = maxCount != null ? maxCount : Infinity;
4240
+ const atMax = effectiveMax !== Infinity && ridList.length >= effectiveMax;
3846
4241
  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) {
4242
+ const openPicker = openPickerProp != null ? openPickerProp : (() => {
4243
+ var _a2;
4244
+ const pickerEl = (_a2 = container.closest("[data-files-wrapper]")) == null ? void 0 : _a2.querySelector('input[type="file"]');
4245
+ if (pickerEl) pickerEl.click();
4246
+ });
4247
+ if (isReadonly) {
4248
+ if (ridList.length === 0) {
3859
4249
  const emptyEl = document.createElement("div");
3860
4250
  emptyEl.className = "fb-tile-empty-text";
3861
4251
  emptyEl.textContent = t("noFilesSelected", state);
3862
4252
  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
4253
  } else {
3877
- const dropzone = buildEmptyDropzone(
3878
- state,
3879
- t("clickDragTextMultiple", state),
3880
- buildSubHint(),
3881
- openPicker
3882
- );
3883
- container.appendChild(dropzone);
4254
+ const grid2 = document.createElement("div");
4255
+ grid2.className = "fb-multi-readonly-grid";
4256
+ container.appendChild(grid2);
4257
+ for (const rid of ridList) {
4258
+ const meta = state.resourceIndex.get(rid);
4259
+ const tile = document.createElement("div");
4260
+ tile.className = "fb-readonly-tile fb-checker fb-tile fb-tile-resource";
4261
+ tile.dataset.resourceId = rid;
4262
+ const actionsEl = createTileActions({
4263
+ canRemove: false,
4264
+ removeHandler: null,
4265
+ state,
4266
+ resourceId: rid,
4267
+ fileName: (_a = meta == null ? void 0 : meta.name) != null ? _a : "",
4268
+ meta
4269
+ });
4270
+ fillTileContent(tile, rid, meta, state, actionsEl).catch(console.error);
4271
+ tile.onclick = async () => {
4272
+ var _a2;
4273
+ let url = null;
4274
+ if (state.config.getDownloadUrl) {
4275
+ url = state.config.getDownloadUrl(rid);
4276
+ } else if (state.config.getThumbnail) {
4277
+ url = await state.config.getThumbnail(rid);
4278
+ } else if ((meta == null ? void 0 : meta.file) instanceof File) {
4279
+ url = URL.createObjectURL(meta.file);
4280
+ }
4281
+ if (url) {
4282
+ window.open(url, "_blank");
4283
+ } else if (state.config.downloadFile) {
4284
+ state.config.downloadFile(rid, (_a2 = meta == null ? void 0 : meta.name) != null ? _a2 : "");
4285
+ }
4286
+ };
4287
+ grid2.appendChild(tile);
4288
+ }
3884
4289
  }
3885
4290
  return;
3886
4291
  }
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,
4292
+ const outerDiv = document.createElement("div");
4293
+ outerDiv.className = `fb-multi-outer${ridList.length > 0 ? " fb-multi-has-files" : ""}`;
4294
+ const grid = document.createElement("div");
4295
+ grid.className = "fb-multi-grid fb-tiles-wrap";
4296
+ outerDiv.appendChild(grid);
4297
+ container.appendChild(outerDiv);
4298
+ for (let i = 0; i < ridList.length; i++) {
4299
+ const rid = ridList[i];
4300
+ const tile = buildPreviewTile(
4301
+ rid,
3898
4302
  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);
4303
+ onRemove !== null,
4304
+ onRemove ? () => onRemove(rid) : null
4305
+ );
4306
+ grid.appendChild(tile);
3925
4307
  }
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);
4308
+ if (!atMax) {
4309
+ const addTile = buildMultiAddTile(
4310
+ state,
4311
+ hasLibrary,
4312
+ openPicker,
4313
+ onLibraryPick != null ? onLibraryPick : null
4314
+ );
4315
+ grid.appendChild(addTile);
4316
+ }
4317
+ const occupied = ridList.length + (atMax ? 0 : 1);
4318
+ const adjustPlaceholders = () => {
4319
+ const tpl = getComputedStyle(grid).gridTemplateColumns;
4320
+ const cols = tpl ? tpl.split(" ").filter(Boolean).length : 0;
4321
+ if (!cols) return;
4322
+ const remainder = occupied % cols;
4323
+ const rowFill = remainder === 0 ? 0 : cols - remainder;
4324
+ const capacityRemaining = effectiveMax === Infinity ? rowFill : Math.max(0, effectiveMax - occupied);
4325
+ const needed = Math.min(rowFill, capacityRemaining);
4326
+ const existing = grid.querySelectorAll(".fb-multi-placeholder");
4327
+ if (existing.length > needed) {
4328
+ for (let i = existing.length - 1; i >= needed; i--) existing[i].remove();
4329
+ } else if (existing.length < needed) {
4330
+ for (let i = existing.length; i < needed; i++) {
4331
+ grid.appendChild(buildPlaceholderTile());
4332
+ }
4333
+ }
4334
+ };
4335
+ if (effectiveMax === Infinity || effectiveMax > occupied) {
4336
+ grid.appendChild(buildPlaceholderTile());
4337
+ }
4338
+ requestAnimationFrame(adjustPlaceholders);
4339
+ if (typeof ResizeObserver !== "undefined") {
4340
+ const ro = new ResizeObserver(() => adjustPlaceholders());
4341
+ ro.observe(grid);
4342
+ gridResizeObservers.set(container, ro);
4343
+ }
4344
+ attachDragOverFeedback(outerDiv, {
4345
+ activeClass: "fb-drag-over",
4346
+ onEnter: () => {
4347
+ grid.querySelectorAll(".fb-multi-placeholder").forEach((p) => {
4348
+ p.classList.add("fb-drag-over");
4349
+ });
4350
+ const addTile = grid.querySelector(".fb-multi-add-tile-js");
4351
+ if (addTile) {
4352
+ addTile.classList.add("fb-drag-over-tile");
4353
+ const label = addTile.querySelector(".fb-multi-add-label");
4354
+ if (label) label.textContent = t("dropToUpload", state);
4355
+ }
4356
+ },
4357
+ onLeave: () => {
4358
+ grid.querySelectorAll(".fb-multi-placeholder").forEach((p) => {
4359
+ p.classList.remove("fb-drag-over");
4360
+ });
4361
+ const addTile = grid.querySelector(".fb-multi-add-tile-js");
4362
+ if (addTile) {
4363
+ addTile.classList.remove("fb-drag-over-tile");
4364
+ const label = addTile.querySelector(".fb-multi-add-label");
4365
+ if (label) label.textContent = t("clickDragTextMultiple", state);
4366
+ }
4367
+ }
4368
+ });
4369
+ if (element) {
4370
+ const metaLine = buildMetaLine(
4371
+ state,
4372
+ element,
4373
+ ridList.length,
4374
+ effectiveMax,
4375
+ Boolean(onClearAll),
4376
+ onClearAll != null ? onClearAll : (() => {
4377
+ })
4378
+ );
4379
+ container.appendChild(metaLine);
3933
4380
  }
3934
4381
  }
3935
4382
  function renderFileElementEdit(element, ctx, wrapper, pathKey) {
3936
- var _a, _b, _c, _d, _e;
4383
+ var _a, _b;
3937
4384
  const state = ctx.state;
3938
4385
  const fileWrapper = document.createElement("div");
3939
4386
  fileWrapper.className = "space-y-2";
4387
+ fileWrapper.dataset.filesWrapper = pathKey;
3940
4388
  const picker = document.createElement("input");
3941
4389
  picker.type = "file";
3942
4390
  picker.name = pathKey;
3943
4391
  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
- }
4392
+ picker.accept = buildAcceptAttribute(element.accept);
3950
4393
  const fileContainer = document.createElement("div");
3951
4394
  fileContainer.className = "file-preview-container";
3952
4395
  const initial = ctx.prefill[element.key];
3953
4396
  const allowedExts = getAllowedExtensions(element.accept);
3954
4397
  const allowedMimes = getAllowedMimes(element.accept);
3955
- const maxSizeMB = (_d = element.maxSize) != null ? _d : Infinity;
4398
+ const maxSizeMB = (_a = element.maxSize) != null ? _a : Infinity;
3956
4399
  const handlers = {
3957
4400
  fileUploadHandler() {
3958
4401
  picker.click();
@@ -3975,14 +4418,6 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
3975
4418
  setupDrop(container) {
3976
4419
  setupDragAndDrop(container, handlers.dragHandler);
3977
4420
  },
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
4421
  onRemove() {
3987
4422
  var _a2;
3988
4423
  const hiddenInput = fileWrapper.querySelector('input[type="hidden"]');
@@ -3994,34 +4429,11 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
3994
4429
  renderEmptySingleState();
3995
4430
  }
3996
4431
  };
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, () => {
4432
+ const buildSingleExtras = () => {
4433
+ const hasLibrary = Boolean(state.config.pickExistingFiles && !element.disableLibrary);
4434
+ return {
4435
+ replaceHandler: state.config.uploadFile ? () => picker.click() : null,
4436
+ libraryHandler: hasLibrary ? () => {
4025
4437
  handleLibraryPickSingle(
4026
4438
  state,
4027
4439
  element,
@@ -4030,20 +4442,41 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
4030
4442
  pathKey,
4031
4443
  pathKey,
4032
4444
  async (rid) => {
4033
- await renderSingleFileEditTile(fileContainer, rid, state, buildDeps());
4445
+ renderSingleFileFilled(fileContainer, rid, state, buildDeps(), buildSingleExtras());
4034
4446
  },
4035
4447
  ctx.instance
4036
4448
  ).catch((err) => {
4037
4449
  console.error("Library pick failed:", err);
4038
4450
  });
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();
4451
+ } : null
4452
+ };
4453
+ };
4454
+ const buildDeps = () => ({
4455
+ picker,
4456
+ fileUploadHandler: handlers.fileUploadHandler,
4457
+ dragHandler: handlers.dragHandler,
4458
+ setupDrop: handlers.setupDrop,
4459
+ onRemove: handlers.onRemove,
4460
+ onAfterUpload: (container, rid) => {
4461
+ renderSingleFileFilled(container, rid, state, buildDeps(), buildSingleExtras());
4046
4462
  }
4463
+ });
4464
+ const renderEmptySingleState = () => {
4465
+ ensureFileStyles();
4466
+ fileContainer.className = "file-preview-container";
4467
+ fileContainer.removeAttribute("style");
4468
+ while (fileContainer.firstChild) fileContainer.removeChild(fileContainer.firstChild);
4469
+ const onLibraryClick = buildSingleExtras().libraryHandler;
4470
+ const wideTile = buildWideTile(
4471
+ state,
4472
+ onLibraryClick !== null,
4473
+ handlers.fileUploadHandler,
4474
+ onLibraryClick,
4475
+ false,
4476
+ makeFieldHint(element, state)
4477
+ );
4478
+ fileContainer.appendChild(wideTile);
4479
+ setupDragAndDrop(fileContainer, handlers.dragHandler);
4047
4480
  };
4048
4481
  if (initial) {
4049
4482
  handleInitialFileData(
@@ -4052,11 +4485,11 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
4052
4485
  pathKey,
4053
4486
  fileWrapper,
4054
4487
  state,
4055
- buildDeps()
4488
+ buildDeps(),
4489
+ buildSingleExtras()
4056
4490
  );
4057
4491
  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;
4492
+ if ((_b = prefillMeta == null ? void 0 : prefillMeta.type) == null ? void 0 : _b.startsWith("video/")) {
4060
4493
  setupDragAndDrop(fileContainer, handlers.dragHandler);
4061
4494
  }
4062
4495
  } else {
@@ -4064,116 +4497,25 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
4064
4497
  }
4065
4498
  picker.onchange = () => {
4066
4499
  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
- });
4500
+ handlers.dragHandler(picker.files);
4078
4501
  }
4079
4502
  };
4080
4503
  fileWrapper.appendChild(fileContainer);
4081
4504
  fileWrapper.appendChild(picker);
4082
4505
  wrapper.appendChild(fileWrapper);
4083
4506
  }
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;
4507
+ function buildAcceptAttribute(accept) {
4508
+ var _a, _b, _c;
4509
+ if (!accept) return "";
4510
+ if (typeof accept === "string") return accept;
4511
+ return [
4512
+ ...(_b = (_a = accept.extensions) == null ? void 0 : _a.map((ext) => `.${ext}`)) != null ? _b : [],
4513
+ ...(_c = accept.mime) != null ? _c : []
4514
+ ].join(",");
4515
+ }
4516
+ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
4517
+ var _a, _b;
4174
4518
  const state = ctx.state;
4175
- const minFiles = (_a = element.minCount) != null ? _a : 0;
4176
- const maxFiles = (_b = element.maxCount) != null ? _b : Infinity;
4177
4519
  const filesWrapper = document.createElement("div");
4178
4520
  filesWrapper.className = "space-y-2";
4179
4521
  filesWrapper.dataset.filesWrapper = pathKey;
@@ -4182,15 +4524,9 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
4182
4524
  filesPicker.name = pathKey;
4183
4525
  filesPicker.multiple = true;
4184
4526
  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
- }
4527
+ filesPicker.accept = buildAcceptAttribute(element.accept);
4191
4528
  const filesContainer = document.createElement("div");
4192
4529
  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
4530
  const list = document.createElement("div");
4195
4531
  list.className = "files-list";
4196
4532
  filesWrapper.appendChild(filesPicker);
@@ -4199,19 +4535,18 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
4199
4535
  const initialFiles = Array.isArray(ctx.prefill[element.key]) ? [...ctx.prefill[element.key]] : [];
4200
4536
  addPrefillFilesToIndex(initialFiles, state.resourceIndex);
4201
4537
  filesWrapper.dataset.resourceIds = JSON.stringify(initialFiles);
4202
- const multipleFilesHint = makeFieldHint(element, state);
4203
- const multipleConstraints = {
4538
+ const constraints = {
4204
4539
  maxCount: maxFiles,
4205
4540
  allowedExtensions: getAllowedExtensions(element.accept),
4206
4541
  allowedMimes: getAllowedMimes(element.accept),
4207
- maxSize: (_f = element.maxSize) != null ? _f : Infinity
4542
+ // Prefer schema's `maxSize`; fall back to legacy `maxSizeMB` for
4543
+ // backward compatibility (matches addFileSizeHint in validation.ts).
4544
+ maxSize: (_b = (_a = element.maxSize) != null ? _a : element.maxSizeMB) != null ? _b : Infinity
4208
4545
  };
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;
4546
+ const openPicker = () => {
4547
+ filesPicker.click();
4213
4548
  };
4214
- const onLibraryPickMultiple = state.config.pickExistingFiles && !element.disableLibrary ? () => {
4549
+ const onLibraryPick = state.config.pickExistingFiles && !element.disableLibrary ? () => {
4215
4550
  handleLibraryPickMulti(
4216
4551
  state,
4217
4552
  element,
@@ -4225,31 +4560,36 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
4225
4560
  console.error("Library pick failed:", err);
4226
4561
  });
4227
4562
  } : null;
4228
- const updateFilesDisplay = () => {
4563
+ function updateFilesDisplay() {
4229
4564
  const currentlyReadonly = isElementReadonly(element, state);
4230
4565
  renderResourcePills({
4231
4566
  container: list,
4232
4567
  rids: initialFiles,
4233
4568
  state,
4234
- onRemove: currentlyReadonly ? null : (index) => {
4569
+ onRemove: currentlyReadonly ? null : (ridToRemove) => {
4235
4570
  var _a2;
4236
- releaseLocalFileUrl((_a2 = state.resourceIndex.get(index)) == null ? void 0 : _a2.file);
4237
- initialFiles.splice(initialFiles.indexOf(index), 1);
4571
+ releaseLocalFileUrl((_a2 = state.resourceIndex.get(ridToRemove)) == null ? void 0 : _a2.file);
4572
+ const index = initialFiles.indexOf(ridToRemove);
4573
+ if (index > -1) initialFiles.splice(index, 1);
4238
4574
  updateFilesDisplay();
4239
4575
  },
4240
- hint: multipleFilesHint,
4241
- countInfo: buildCountInfo(),
4242
4576
  maxCount: maxFiles < Infinity ? maxFiles : void 0,
4243
4577
  isReadonly: currentlyReadonly,
4244
- onLibraryPick: currentlyReadonly ? null : onLibraryPickMultiple
4578
+ onLibraryPick: currentlyReadonly ? null : onLibraryPick,
4579
+ element,
4580
+ onClearAll: currentlyReadonly ? void 0 : () => {
4581
+ initialFiles.splice(0);
4582
+ updateFilesDisplay();
4583
+ },
4584
+ openPicker
4245
4585
  });
4246
- };
4586
+ }
4247
4587
  setupFilesDropHandler(
4248
4588
  filesContainer,
4249
4589
  initialFiles,
4250
4590
  state,
4251
4591
  updateFilesDisplay,
4252
- multipleConstraints,
4592
+ constraints,
4253
4593
  pathKey,
4254
4594
  ctx.instance
4255
4595
  );
@@ -4258,13 +4598,20 @@ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
4258
4598
  initialFiles,
4259
4599
  state,
4260
4600
  updateFilesDisplay,
4261
- multipleConstraints,
4601
+ constraints,
4262
4602
  pathKey,
4263
4603
  ctx.instance
4264
4604
  );
4265
4605
  updateFilesDisplay();
4266
4606
  wrapper.appendChild(filesWrapper);
4267
4607
  }
4608
+ function renderFilesElementEdit(element, ctx, wrapper, pathKey) {
4609
+ setupMultiFileEditMode(element, ctx, wrapper, pathKey, Infinity);
4610
+ }
4611
+ function renderMultipleFileElementEdit(element, ctx, wrapper, pathKey) {
4612
+ var _a;
4613
+ setupMultiFileEditMode(element, ctx, wrapper, pathKey, (_a = element.maxCount) != null ? _a : Infinity);
4614
+ }
4268
4615
 
4269
4616
  // src/components/file/validate.ts
4270
4617
  function readMultiFileResourceIds(scopeRoot, fullKey) {
@@ -4391,33 +4738,36 @@ function renderFileElementReadonly(element, ctx, wrapper, pathKey) {
4391
4738
  hiddenInput.name = pathKey;
4392
4739
  hiddenInput.value = initial;
4393
4740
  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
- });
4741
+ renderFilePreviewReadonly(initial, state).then((tile) => {
4742
+ tile.classList.add(
4743
+ "fb-single-readonly-filled",
4744
+ "fb-readonly-tile",
4745
+ "fb-checker"
4746
+ );
4747
+ wrapper.appendChild(tile);
4748
+ }).catch(console.error);
4400
4749
  } else {
4401
4750
  wrapper.appendChild(buildEmptyReadonlyTile(state));
4402
4751
  }
4403
4752
  }
4404
4753
  function buildEmptyReadonlyTile(state) {
4754
+ ensureFileStyles();
4405
4755
  const emptyState = document.createElement("div");
4406
4756
  emptyState.style.cssText = `
4407
- width:${TILE_SIZE};
4408
- height:${TILE_SIZE};
4757
+ height: 220px;
4409
4758
  display:flex;
4410
4759
  align-items:center;
4411
4760
  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);
4761
+ background: repeating-linear-gradient(45deg, #fafafa 0 6px, #f3f4f6 6px 12px);
4762
+ border-radius:0.75rem;
4763
+ border:1px solid #e2e8f0;
4415
4764
  `;
4416
4765
  emptyState.innerHTML = `<div style="font-size:11px;text-align:center;color:var(--fb-text-secondary-color,#6b7280);">${escapeHtml(t("noFileSelected", state))}</div>`;
4417
4766
  return emptyState;
4418
4767
  }
4419
- function renderMultiFileReadonly(rids, state, wrapper, pathKey, marginTop) {
4768
+ function renderMultiFileReadonly(rids, state, wrapper, pathKey, _marginTop) {
4420
4769
  addPrefillFilesToIndex(rids, state.resourceIndex);
4770
+ ensureFileStyles();
4421
4771
  const filesWrapper = document.createElement("div");
4422
4772
  filesWrapper.dataset.filesWrapper = pathKey;
4423
4773
  filesWrapper.dataset.resourceIds = JSON.stringify(rids);
@@ -4429,22 +4779,28 @@ function renderMultiFileReadonly(rids, state, wrapper, pathKey, marginTop) {
4429
4779
  filesWrapper.appendChild(emptyEl);
4430
4780
  return;
4431
4781
  }
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);
4782
+ const grid = document.createElement("div");
4783
+ grid.className = "fb-multi-readonly-grid";
4784
+ filesWrapper.appendChild(grid);
4435
4785
  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;
4786
+ const ph = document.createElement("div");
4787
+ ph.className = "fb-readonly-tile fb-checker fb-tile";
4788
+ grid.appendChild(ph);
4789
+ return ph;
4440
4790
  });
4441
4791
  for (let i = 0; i < rids.length; i++) {
4442
4792
  const resourceId = rids[i];
4443
4793
  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);
4794
+ const meta = state.resourceIndex.get(resourceId);
4795
+ renderFilePreviewReadonly(resourceId, state, meta == null ? void 0 : meta.name).then((tile) => {
4796
+ tile.classList.add("fb-readonly-tile", "fb-checker", "fb-tile-resource");
4797
+ tile.dataset.resourceId = resourceId;
4798
+ placeholder.replaceWith(tile);
4799
+ }).catch(() => {
4800
+ const tile = document.createElement("div");
4801
+ tile.className = "fb-readonly-tile fb-checker fb-tile fb-tile-resource";
4802
+ tile.dataset.resourceId = resourceId;
4803
+ placeholder.replaceWith(tile);
4448
4804
  });
4449
4805
  }
4450
4806
  }
@@ -4456,7 +4812,7 @@ function renderFilesElementReadonly(element, ctx, wrapper, pathKey) {
4456
4812
  function renderMultipleFileElementReadonly(element, ctx, wrapper, pathKey) {
4457
4813
  const rawPrefill = ctx.prefill[element.key];
4458
4814
  const initialFiles = Array.isArray(rawPrefill) ? rawPrefill : [];
4459
- renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey, "4px");
4815
+ renderMultiFileReadonly(initialFiles, ctx.state, wrapper, pathKey);
4460
4816
  }
4461
4817
 
4462
4818
  // src/components/file.ts
@@ -5604,7 +5960,7 @@ function createPrefillHints(element, pathKey) {
5604
5960
  return null;
5605
5961
  }
5606
5962
  const hintsContainer = document.createElement("div");
5607
- hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-4";
5963
+ hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-2";
5608
5964
  element.prefillHints.forEach((hint, index) => {
5609
5965
  const hintButton = document.createElement("button");
5610
5966
  hintButton.type = "button";
@@ -5620,14 +5976,14 @@ function createPrefillHints(element, pathKey) {
5620
5976
  function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
5621
5977
  var _a, _b;
5622
5978
  const containerWrap = document.createElement("div");
5623
- containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
5979
+ containerWrap.className = "border border-gray-200 rounded-lg p-2 bg-gray-50";
5624
5980
  containerWrap.setAttribute("data-container", pathKey);
5625
5981
  const itemsWrap = document.createElement("div");
5626
5982
  const columns = element.columns || 1;
5627
5983
  if (columns === 1) {
5628
- itemsWrap.className = "space-y-4";
5984
+ itemsWrap.className = "space-y-2";
5629
5985
  } else {
5630
- itemsWrap.className = `grid grid-cols-${columns} gap-4`;
5986
+ itemsWrap.className = `grid grid-cols-${columns} gap-2`;
5631
5987
  }
5632
5988
  const containerIsReadonly = isElementReadonly(element, ctx.state, ctx);
5633
5989
  if (!containerIsReadonly) {
@@ -5662,17 +6018,32 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
5662
6018
  containerWrap.appendChild(itemsWrap);
5663
6019
  wrapper.appendChild(containerWrap);
5664
6020
  }
6021
+ function getChildWrapperClass(isSlides, columns) {
6022
+ if (isSlides) {
6023
+ return "space-y-2";
6024
+ }
6025
+ const cols = columns || 1;
6026
+ return cols === 1 ? "space-y-2" : `grid grid-cols-${cols} gap-2`;
6027
+ }
5665
6028
  function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5666
6029
  var _a, _b, _c, _d;
5667
6030
  const state = ctx.state;
5668
6031
  const containerIsReadonly = isElementReadonly(element, state, ctx);
5669
6032
  const childInheritedReadonly = containerIsReadonly || ctx.inheritedReadonly;
5670
6033
  const containerWrap = document.createElement("div");
5671
- containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
6034
+ containerWrap.className = "border border-gray-200 rounded-lg p-2 bg-gray-50";
5672
6035
  const countDisplay = document.createElement("span");
5673
6036
  countDisplay.className = "text-sm text-gray-500";
5674
6037
  const itemsWrap = document.createElement("div");
5675
- itemsWrap.className = "space-y-4";
6038
+ const isSlides = element.displayMode === "slides";
6039
+ if (isSlides) {
6040
+ itemsWrap.className = "fb-container-slides";
6041
+ const slideCols = element.columns;
6042
+ const gridTemplateColumns = typeof slideCols === "number" && slideCols > 0 ? `repeat(${slideCols}, 1fr)` : "repeat(auto-fit, minmax(280px, 1fr))";
6043
+ itemsWrap.style.cssText = `display:grid;grid-template-columns:${gridTemplateColumns};gap:8px;align-items:start;`;
6044
+ } else {
6045
+ itemsWrap.className = "space-y-2";
6046
+ }
5676
6047
  if (!containerIsReadonly) {
5677
6048
  const hintsElement = createPrefillHints(element, element.key);
5678
6049
  if (hintsElement) {
@@ -5716,15 +6087,10 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5716
6087
  inheritedReadonly: childInheritedReadonly
5717
6088
  };
5718
6089
  const item = document.createElement("div");
5719
- item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
6090
+ item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
5720
6091
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
5721
6092
  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
- }
6093
+ childWrapper.className = getChildWrapperClass(isSlides, element.columns);
5728
6094
  element.elements.forEach((child) => {
5729
6095
  var _a2;
5730
6096
  if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
@@ -5795,14 +6161,18 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5795
6161
  inheritedReadonly: childInheritedReadonly
5796
6162
  };
5797
6163
  const item = document.createElement("div");
5798
- item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
6164
+ item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
5799
6165
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
5800
6166
  const childWrapper = document.createElement("div");
5801
- const columns = element.columns || 1;
5802
- if (columns === 1) {
5803
- childWrapper.className = "space-y-4";
6167
+ if (isSlides) {
6168
+ childWrapper.className = "space-y-2";
5804
6169
  } else {
5805
- childWrapper.className = `grid grid-cols-${columns} gap-4`;
6170
+ const columns = element.columns || 1;
6171
+ if (columns === 1) {
6172
+ childWrapper.className = "space-y-2";
6173
+ } else {
6174
+ childWrapper.className = `grid grid-cols-${columns} gap-2`;
6175
+ }
5806
6176
  }
5807
6177
  element.elements.forEach((child) => {
5808
6178
  var _a3, _b2;
@@ -5852,14 +6222,18 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5852
6222
  inheritedReadonly: childInheritedReadonly
5853
6223
  };
5854
6224
  const item = document.createElement("div");
5855
- item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
6225
+ item.className = "containerItem border border-gray-300 rounded-lg p-2 bg-white";
5856
6226
  item.setAttribute("data-container-item", `${element.key}[${idx}]`);
5857
6227
  const childWrapper = document.createElement("div");
5858
- const columns = element.columns || 1;
5859
- if (columns === 1) {
5860
- childWrapper.className = "space-y-4";
6228
+ if (isSlides) {
6229
+ childWrapper.className = "space-y-2";
5861
6230
  } else {
5862
- childWrapper.className = `grid grid-cols-${columns} gap-4`;
6231
+ const columns = element.columns || 1;
6232
+ if (columns === 1) {
6233
+ childWrapper.className = "space-y-2";
6234
+ } else {
6235
+ childWrapper.className = `grid grid-cols-${columns} gap-2`;
6236
+ }
5863
6237
  }
5864
6238
  element.elements.forEach((child) => {
5865
6239
  var _a2;
@@ -7932,7 +8306,7 @@ function filterFilesForDropdown(query, files, labels) {
7932
8306
  });
7933
8307
  }
7934
8308
  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;";
8309
+ var TEXTAREA_PADDING = "padding: 8px 40px 8px 10px;";
7936
8310
  function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
7937
8311
  var _a;
7938
8312
  const state = ctx.state;
@@ -7981,7 +8355,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
7981
8355
  });
7982
8356
  const errorEl = document.createElement("div");
7983
8357
  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;";
8358
+ errorEl.style.cssText = "display: none; color: var(--fb-error-color, #ef4444); font-size: var(--fb-font-size-small, 12px); padding: 4px 10px 6px;";
7985
8359
  let errorTimer = null;
7986
8360
  function showUploadError(message) {
7987
8361
  errorEl.textContent = message;
@@ -8059,7 +8433,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
8059
8433
  });
8060
8434
  const filesRow = document.createElement("div");
8061
8435
  filesRow.className = "fb-richinput-files";
8062
- filesRow.style.cssText = "display: none; flex-wrap: wrap; gap: 6px; padding: 10px 14px 0; align-items: center;";
8436
+ filesRow.style.cssText = "display: none; flex-wrap: wrap; gap: 4px; padding: 6px 10px 0; align-items: center;";
8063
8437
  const fileInput = document.createElement("input");
8064
8438
  fileInput.type = "file";
8065
8439
  fileInput.multiple = true;
@@ -8195,13 +8569,13 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
8195
8569
  paperclipBtn.title = t("richinputAttachFile", state);
8196
8570
  paperclipBtn.style.cssText = `
8197
8571
  position: absolute;
8198
- right: 10px;
8199
- bottom: 10px;
8572
+ right: 6px;
8573
+ bottom: 6px;
8200
8574
  z-index: 2;
8201
- width: 32px;
8202
- height: 32px;
8575
+ width: 28px;
8576
+ height: 28px;
8203
8577
  border: none;
8204
- border-radius: 8px;
8578
+ border-radius: 6px;
8205
8579
  background: transparent;
8206
8580
  cursor: pointer;
8207
8581
  display: flex;
@@ -8569,7 +8943,7 @@ function renderEditMode(element, ctx, wrapper, pathKey, initialValue) {
8569
8943
  outerDiv.appendChild(errorEl);
8570
8944
  if (element.minLength != null || element.maxLength != null) {
8571
8945
  const counterRow = document.createElement("div");
8572
- counterRow.style.cssText = "position: relative; padding: 2px 14px 6px; text-align: right;";
8946
+ counterRow.style.cssText = "position: relative; padding: 2px 10px 4px; text-align: right;";
8573
8947
  const counter = createCharCounter(element, textarea, false);
8574
8948
  counter.style.cssText = `
8575
8949
  position: static;
@@ -9374,7 +9748,7 @@ function createInfoButton(element) {
9374
9748
  }
9375
9749
  function createLabelContainer(element) {
9376
9750
  const label = document.createElement("div");
9377
- label.className = "flex items-center mb-2";
9751
+ label.className = "flex items-center mb-1";
9378
9752
  const title = createFieldLabel(element);
9379
9753
  label.appendChild(title);
9380
9754
  if (element.description || element.hint) {
@@ -9481,7 +9855,7 @@ function renderElement2(element, ctx) {
9481
9855
  }
9482
9856
  const initiallyDisabled2 = shouldDisableElement(element, ctx);
9483
9857
  const outerWrapper = document.createElement("div");
9484
- outerWrapper.className = "mb-6 fb-field-wrapper fb-markdown-wrapper";
9858
+ outerWrapper.className = "mb-2 fb-field-wrapper fb-markdown-wrapper";
9485
9859
  outerWrapper.setAttribute(
9486
9860
  "data-field-key",
9487
9861
  getElementLookupKey(element, ctx.state)
@@ -9497,7 +9871,7 @@ function renderElement2(element, ctx) {
9497
9871
  }
9498
9872
  const initiallyDisabled = shouldDisableElement(element, ctx);
9499
9873
  const wrapper = document.createElement("div");
9500
- wrapper.className = "mb-6 fb-field-wrapper";
9874
+ wrapper.className = "mb-2 fb-field-wrapper";
9501
9875
  wrapper.setAttribute("data-field-key", element.key);
9502
9876
  const label = createLabelContainer(element);
9503
9877
  wrapper.appendChild(label);
@@ -9564,12 +9938,16 @@ var defaultConfig = {
9564
9938
  hintPattern: "Format: {pattern}",
9565
9939
  fileCountSingle: "{count} file",
9566
9940
  fileCountPlural: "{count} files",
9941
+ fileCountWithMax: "{count} / {max} files",
9567
9942
  fileCountRange: "({min}-{max})",
9568
9943
  uploadingFile: "Uploading\u2026",
9569
9944
  filesCounter: "{count}/{max}",
9570
9945
  fromLibrary: "From library",
9571
9946
  libraryEmpty: "Library is empty",
9572
9947
  libraryHint: "Choose from previously uploaded files",
9948
+ dropToUpload: "Release to upload",
9949
+ replaceFile: "Replace",
9950
+ clearAll: "Clear all",
9573
9951
  pickerError: "Failed to load files from library",
9574
9952
  // Validation errors
9575
9953
  required: "Required",
@@ -9635,12 +10013,16 @@ var defaultConfig = {
9635
10013
  hintPattern: "\u0424\u043E\u0440\u043C\u0430\u0442: {pattern}",
9636
10014
  fileCountSingle: "{count} \u0444\u0430\u0439\u043B",
9637
10015
  fileCountPlural: "{count} \u0444\u0430\u0439\u043B\u043E\u0432",
10016
+ fileCountWithMax: "{count} / {max} \u0444\u0430\u0439\u043B\u043E\u0432",
9638
10017
  fileCountRange: "({min}-{max})",
9639
10018
  uploadingFile: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026",
9640
10019
  filesCounter: "{count}/{max}",
9641
10020
  fromLibrary: "\u0418\u0437 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438",
9642
10021
  libraryEmpty: "\u0411\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0430 \u043F\u0443\u0441\u0442\u0430",
9643
10022
  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",
10023
+ dropToUpload: "\u041E\u0442\u043F\u0443\u0441\u0442\u0438\u0442\u0435, \u0447\u0442\u043E\u0431\u044B \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C",
10024
+ replaceFile: "\u0417\u0430\u043C\u0435\u043D\u0438\u0442\u044C",
10025
+ clearAll: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u0441\u0435",
9644
10026
  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
10027
  // Validation errors
9646
10028
  required: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435",
@@ -9781,10 +10163,10 @@ var defaultTheme = {
9781
10163
  fileUploadHoverBorderColor: "#3b82f6",
9782
10164
  // blue-500
9783
10165
  // Spacing
9784
- inputPaddingX: "0.75rem",
9785
- // 3 (12px)
9786
- inputPaddingY: "0.5rem",
9787
- // 2 (8px)
10166
+ inputPaddingX: "0.5rem",
10167
+ // 8px (compact density v2)
10168
+ inputPaddingY: "0.25rem",
10169
+ // 4px (compact density v2)
9788
10170
  borderRadius: "0.5rem",
9789
10171
  // rounded-lg (8px)
9790
10172
  borderWidth: "1px",
@@ -10230,7 +10612,7 @@ var FormBuilderInstance = class {
10230
10612
  existingContainer.remove();
10231
10613
  }
10232
10614
  const actionsContainer = document.createElement("div");
10233
- actionsContainer.className = "form-level-actions-container mt-6 pt-4 flex flex-wrap gap-3 justify-center";
10615
+ actionsContainer.className = "form-level-actions-container mt-3 pt-2 flex flex-wrap gap-2 justify-center";
10234
10616
  actionsContainer.style.cssText = `
10235
10617
  border-top: var(--fb-border-width) solid var(--fb-border-color);
10236
10618
  `;
@@ -10371,7 +10753,7 @@ var FormBuilderInstance = class {
10371
10753
  */
10372
10754
  createRootPrefillHints(hints) {
10373
10755
  const hintsContainer = document.createElement("div");
10374
- hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-4";
10756
+ hintsContainer.className = "fb-prefill-hints flex flex-wrap gap-2 mb-2";
10375
10757
  hints.forEach((hint) => {
10376
10758
  const hintButton = document.createElement("button");
10377
10759
  hintButton.type = "button";
@@ -10404,7 +10786,7 @@ var FormBuilderInstance = class {
10404
10786
  root.setAttribute("data-fb-root", "true");
10405
10787
  injectThemeVariables(root, this.state.config.theme);
10406
10788
  const rootContainer = document.createElement("div");
10407
- rootContainer.className = "space-y-6";
10789
+ rootContainer.className = "space-y-2";
10408
10790
  if (schema.prefillHints && !this.state.config.readonly) {
10409
10791
  const hintsContainer = this.createRootPrefillHints(schema.prefillHints);
10410
10792
  rootContainer.appendChild(hintsContainer);
@@ -10412,9 +10794,9 @@ var FormBuilderInstance = class {
10412
10794
  const fieldsWrapper = document.createElement("div");
10413
10795
  const columns = schema.columns || 1;
10414
10796
  if (columns === 1) {
10415
- fieldsWrapper.className = "space-y-4";
10797
+ fieldsWrapper.className = "space-y-2";
10416
10798
  } else {
10417
- fieldsWrapper.className = `grid grid-cols-${columns} gap-4`;
10799
+ fieldsWrapper.className = `grid grid-cols-${columns} gap-2`;
10418
10800
  }
10419
10801
  schema.elements.forEach((element) => {
10420
10802
  var _a, _b;