@dmitryvim/form-builder 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/formbuilder.min.js +150 -110
- package/dist/browser/formbuilder.v0.2.13.min.js +362 -0
- package/dist/cjs/index.cjs +831 -459
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +830 -457
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +150 -110
- package/dist/types/components/file.d.ts +1 -1
- package/dist/types/components/text.d.ts +1 -0
- package/dist/types/instance/state.d.ts +1 -0
- package/dist/types/types/config.d.ts +37 -6
- package/dist/types/utils/helpers.d.ts +5 -0
- package/dist/types/utils/translation.d.ts +8 -3
- package/dist/types/utils/validation.d.ts +2 -1
- package/package.json +1 -1
- package/dist/browser/formbuilder.v0.2.11.min.js +0 -322
package/dist/cjs/index.cjs
CHANGED
|
@@ -2,58 +2,79 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
// src/utils/translation.ts
|
|
6
|
+
function t(key, state, params) {
|
|
7
|
+
const locale = state.config.locale || "en";
|
|
8
|
+
const localeTranslations = state.config.translations[locale];
|
|
9
|
+
const fallbackTranslations = state.config.translations.en;
|
|
10
|
+
let text = (localeTranslations == null ? void 0 : localeTranslations[key]) || (fallbackTranslations == null ? void 0 : fallbackTranslations[key]) || key;
|
|
11
|
+
if (params) {
|
|
12
|
+
for (const [paramKey, paramValue] of Object.entries(params)) {
|
|
13
|
+
text = text.replace(new RegExp(`\\{${paramKey}\\}`, "g"), String(paramValue));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
|
|
5
19
|
// src/utils/validation.ts
|
|
6
|
-
function addLengthHint(element, parts) {
|
|
7
|
-
if (element.minLength
|
|
8
|
-
if (element.minLength
|
|
9
|
-
parts.push(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
function addLengthHint(element, parts, state) {
|
|
21
|
+
if (element.minLength != null || element.maxLength != null) {
|
|
22
|
+
if (element.minLength != null && element.maxLength != null) {
|
|
23
|
+
parts.push(
|
|
24
|
+
t("hintLengthRange", state, {
|
|
25
|
+
min: element.minLength,
|
|
26
|
+
max: element.maxLength
|
|
27
|
+
})
|
|
28
|
+
);
|
|
29
|
+
} else if (element.maxLength != null) {
|
|
30
|
+
parts.push(t("hintMaxLength", state, { max: element.maxLength }));
|
|
31
|
+
} else if (element.minLength != null) {
|
|
32
|
+
parts.push(t("hintMinLength", state, { min: element.minLength }));
|
|
14
33
|
}
|
|
15
34
|
}
|
|
16
35
|
}
|
|
17
|
-
function addRangeHint(element, parts) {
|
|
18
|
-
if (element.min
|
|
19
|
-
if (element.min
|
|
20
|
-
parts.push(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} else if (element.
|
|
24
|
-
parts.push(
|
|
36
|
+
function addRangeHint(element, parts, state) {
|
|
37
|
+
if (element.min != null || element.max != null) {
|
|
38
|
+
if (element.min != null && element.max != null) {
|
|
39
|
+
parts.push(
|
|
40
|
+
t("hintValueRange", state, { min: element.min, max: element.max })
|
|
41
|
+
);
|
|
42
|
+
} else if (element.max != null) {
|
|
43
|
+
parts.push(t("hintMaxValue", state, { max: element.max }));
|
|
44
|
+
} else if (element.min != null) {
|
|
45
|
+
parts.push(t("hintMinValue", state, { min: element.min }));
|
|
25
46
|
}
|
|
26
47
|
}
|
|
27
48
|
}
|
|
28
|
-
function addFileSizeHint(element, parts) {
|
|
49
|
+
function addFileSizeHint(element, parts, state) {
|
|
29
50
|
if (element.maxSizeMB) {
|
|
30
|
-
parts.push(
|
|
51
|
+
parts.push(t("hintMaxSize", state, { size: element.maxSizeMB }));
|
|
31
52
|
}
|
|
32
53
|
}
|
|
33
|
-
function addFormatHint(element, parts) {
|
|
54
|
+
function addFormatHint(element, parts, state) {
|
|
34
55
|
var _a;
|
|
35
56
|
if ((_a = element.accept) == null ? void 0 : _a.extensions) {
|
|
36
57
|
parts.push(
|
|
37
|
-
|
|
58
|
+
t("hintFormats", state, {
|
|
59
|
+
formats: element.accept.extensions.map((ext) => ext.toUpperCase()).join(",")
|
|
60
|
+
})
|
|
38
61
|
);
|
|
39
62
|
}
|
|
40
63
|
}
|
|
41
|
-
function addPatternHint(element, parts) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
parts.push("plain text only");
|
|
45
|
-
} else if ((_a = element.pattern) == null ? void 0 : _a.includes("\u0410-\u042F")) {
|
|
46
|
-
parts.push("text with punctuation");
|
|
64
|
+
function addPatternHint(element, parts, state) {
|
|
65
|
+
if (element.pattern) {
|
|
66
|
+
parts.push(t("hintPattern", state, { pattern: element.pattern }));
|
|
47
67
|
}
|
|
48
68
|
}
|
|
49
|
-
function makeFieldHint(element) {
|
|
69
|
+
function makeFieldHint(element, state) {
|
|
50
70
|
const parts = [];
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
addLengthHint(element, parts, state);
|
|
72
|
+
if (element.type !== "slider") {
|
|
73
|
+
addRangeHint(element, parts, state);
|
|
74
|
+
}
|
|
75
|
+
addFileSizeHint(element, parts, state);
|
|
76
|
+
addFormatHint(element, parts, state);
|
|
77
|
+
addPatternHint(element, parts, state);
|
|
57
78
|
return parts.join(" \u2022 ");
|
|
58
79
|
}
|
|
59
80
|
function validateSchema(schema) {
|
|
@@ -191,6 +212,11 @@ function validateSchema(schema) {
|
|
|
191
212
|
function isPlainObject(obj) {
|
|
192
213
|
return obj && typeof obj === "object" && obj.constructor === Object;
|
|
193
214
|
}
|
|
215
|
+
function escapeHtml(text) {
|
|
216
|
+
const div = document.createElement("div");
|
|
217
|
+
div.textContent = text;
|
|
218
|
+
return div.innerHTML;
|
|
219
|
+
}
|
|
194
220
|
function pathJoin(base, key) {
|
|
195
221
|
return base ? `${base}.${key}` : key;
|
|
196
222
|
}
|
|
@@ -269,13 +295,62 @@ function deepEqual(a, b) {
|
|
|
269
295
|
}
|
|
270
296
|
|
|
271
297
|
// src/components/text.ts
|
|
298
|
+
function createCharCounter(element, input, isTextarea = false) {
|
|
299
|
+
const counter = document.createElement("span");
|
|
300
|
+
counter.className = "char-counter";
|
|
301
|
+
counter.style.cssText = `
|
|
302
|
+
position: absolute;
|
|
303
|
+
${isTextarea ? "bottom: 8px" : "top: 50%; transform: translateY(-50%)"};
|
|
304
|
+
right: 10px;
|
|
305
|
+
font-size: var(--fb-font-size-small);
|
|
306
|
+
color: var(--fb-text-secondary-color);
|
|
307
|
+
pointer-events: none;
|
|
308
|
+
background: var(--fb-background-color);
|
|
309
|
+
padding: 0 4px;
|
|
310
|
+
`;
|
|
311
|
+
const updateCounter = () => {
|
|
312
|
+
const len = input.value.length;
|
|
313
|
+
const min = element.minLength;
|
|
314
|
+
const max = element.maxLength;
|
|
315
|
+
if (min == null && max == null) {
|
|
316
|
+
counter.textContent = "";
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (len === 0 || min != null && len < min) {
|
|
320
|
+
if (min != null && max != null) {
|
|
321
|
+
counter.textContent = `${min}-${max}`;
|
|
322
|
+
} else if (max != null) {
|
|
323
|
+
counter.textContent = `\u2264${max}`;
|
|
324
|
+
} else if (min != null) {
|
|
325
|
+
counter.textContent = `\u2265${min}`;
|
|
326
|
+
}
|
|
327
|
+
counter.style.color = "var(--fb-text-secondary-color)";
|
|
328
|
+
} else if (max != null && len > max) {
|
|
329
|
+
counter.textContent = `${len}/${max}`;
|
|
330
|
+
counter.style.color = "var(--fb-error-color)";
|
|
331
|
+
} else {
|
|
332
|
+
if (max != null) {
|
|
333
|
+
counter.textContent = `${len}/${max}`;
|
|
334
|
+
} else {
|
|
335
|
+
counter.textContent = `${len}`;
|
|
336
|
+
}
|
|
337
|
+
counter.style.color = "var(--fb-text-secondary-color)";
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
input.addEventListener("input", updateCounter);
|
|
341
|
+
updateCounter();
|
|
342
|
+
return counter;
|
|
343
|
+
}
|
|
272
344
|
function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
273
345
|
const state = ctx.state;
|
|
346
|
+
const inputWrapper = document.createElement("div");
|
|
347
|
+
inputWrapper.style.cssText = "position: relative;";
|
|
274
348
|
const textInput = document.createElement("input");
|
|
275
349
|
textInput.type = "text";
|
|
276
350
|
textInput.className = "w-full rounded-lg";
|
|
277
351
|
textInput.style.cssText = `
|
|
278
352
|
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
353
|
+
padding-right: 60px;
|
|
279
354
|
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
280
355
|
border-radius: var(--fb-border-radius);
|
|
281
356
|
background-color: ${state.config.readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
|
|
@@ -283,6 +358,8 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
283
358
|
font-size: var(--fb-font-size);
|
|
284
359
|
font-family: var(--fb-font-family);
|
|
285
360
|
transition: all var(--fb-transition-duration) ease-in-out;
|
|
361
|
+
width: 100%;
|
|
362
|
+
box-sizing: border-box;
|
|
286
363
|
`;
|
|
287
364
|
textInput.name = pathKey;
|
|
288
365
|
textInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
|
|
@@ -317,15 +394,12 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
317
394
|
textInput.addEventListener("blur", handleChange);
|
|
318
395
|
textInput.addEventListener("input", handleChange);
|
|
319
396
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
`;
|
|
327
|
-
textHint.textContent = makeFieldHint(element);
|
|
328
|
-
wrapper.appendChild(textHint);
|
|
397
|
+
inputWrapper.appendChild(textInput);
|
|
398
|
+
if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
|
|
399
|
+
const counter = createCharCounter(element, textInput, false);
|
|
400
|
+
inputWrapper.appendChild(counter);
|
|
401
|
+
}
|
|
402
|
+
wrapper.appendChild(inputWrapper);
|
|
329
403
|
}
|
|
330
404
|
function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
331
405
|
var _a, _b;
|
|
@@ -352,11 +426,13 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
352
426
|
function addTextItem(value = "", index = -1) {
|
|
353
427
|
const itemWrapper = document.createElement("div");
|
|
354
428
|
itemWrapper.className = "multiple-text-item flex items-center gap-2";
|
|
429
|
+
const inputContainer = document.createElement("div");
|
|
430
|
+
inputContainer.style.cssText = "position: relative; flex: 1;";
|
|
355
431
|
const textInput = document.createElement("input");
|
|
356
432
|
textInput.type = "text";
|
|
357
|
-
textInput.className = "flex-1";
|
|
358
433
|
textInput.style.cssText = `
|
|
359
434
|
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
435
|
+
padding-right: 60px;
|
|
360
436
|
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
361
437
|
border-radius: var(--fb-border-radius);
|
|
362
438
|
background-color: ${state.config.readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
|
|
@@ -364,8 +440,10 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
364
440
|
font-size: var(--fb-font-size);
|
|
365
441
|
font-family: var(--fb-font-family);
|
|
366
442
|
transition: all var(--fb-transition-duration) ease-in-out;
|
|
443
|
+
width: 100%;
|
|
444
|
+
box-sizing: border-box;
|
|
367
445
|
`;
|
|
368
|
-
textInput.placeholder = element.placeholder || "
|
|
446
|
+
textInput.placeholder = element.placeholder || t("placeholderText", state);
|
|
369
447
|
textInput.value = value;
|
|
370
448
|
textInput.readOnly = state.config.readonly;
|
|
371
449
|
if (!state.config.readonly) {
|
|
@@ -397,7 +475,12 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
397
475
|
textInput.addEventListener("blur", handleChange);
|
|
398
476
|
textInput.addEventListener("input", handleChange);
|
|
399
477
|
}
|
|
400
|
-
|
|
478
|
+
inputContainer.appendChild(textInput);
|
|
479
|
+
if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
|
|
480
|
+
const counter = createCharCounter(element, textInput, false);
|
|
481
|
+
inputContainer.appendChild(counter);
|
|
482
|
+
}
|
|
483
|
+
itemWrapper.appendChild(inputContainer);
|
|
401
484
|
if (index === -1) {
|
|
402
485
|
container.appendChild(itemWrapper);
|
|
403
486
|
} else {
|
|
@@ -450,47 +533,54 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
450
533
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
451
534
|
});
|
|
452
535
|
}
|
|
536
|
+
let addRow = null;
|
|
537
|
+
let countDisplay = null;
|
|
538
|
+
if (!state.config.readonly) {
|
|
539
|
+
addRow = document.createElement("div");
|
|
540
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
541
|
+
const addBtn = document.createElement("button");
|
|
542
|
+
addBtn.type = "button";
|
|
543
|
+
addBtn.className = "add-text-btn px-3 py-1 rounded";
|
|
544
|
+
addBtn.style.cssText = `
|
|
545
|
+
color: var(--fb-primary-color);
|
|
546
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
547
|
+
background-color: transparent;
|
|
548
|
+
font-size: var(--fb-font-size);
|
|
549
|
+
transition: all var(--fb-transition-duration);
|
|
550
|
+
`;
|
|
551
|
+
addBtn.textContent = "+";
|
|
552
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
553
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
554
|
+
});
|
|
555
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
556
|
+
addBtn.style.backgroundColor = "transparent";
|
|
557
|
+
});
|
|
558
|
+
addBtn.onclick = () => {
|
|
559
|
+
values.push(element.default || "");
|
|
560
|
+
addTextItem(element.default || "");
|
|
561
|
+
updateAddButton();
|
|
562
|
+
updateRemoveButtons();
|
|
563
|
+
};
|
|
564
|
+
countDisplay = document.createElement("span");
|
|
565
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
566
|
+
addRow.appendChild(addBtn);
|
|
567
|
+
addRow.appendChild(countDisplay);
|
|
568
|
+
wrapper.appendChild(addRow);
|
|
569
|
+
}
|
|
453
570
|
function updateAddButton() {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if (
|
|
457
|
-
const
|
|
458
|
-
addBtn.
|
|
459
|
-
addBtn.
|
|
460
|
-
addBtn.style.
|
|
461
|
-
color: var(--fb-primary-color);
|
|
462
|
-
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
463
|
-
background-color: transparent;
|
|
464
|
-
font-size: var(--fb-font-size);
|
|
465
|
-
transition: all var(--fb-transition-duration);
|
|
466
|
-
`;
|
|
467
|
-
addBtn.textContent = "+";
|
|
468
|
-
addBtn.addEventListener("mouseenter", () => {
|
|
469
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
470
|
-
});
|
|
471
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
472
|
-
addBtn.style.backgroundColor = "transparent";
|
|
473
|
-
});
|
|
474
|
-
addBtn.onclick = () => {
|
|
475
|
-
values.push(element.default || "");
|
|
476
|
-
addTextItem(element.default || "");
|
|
477
|
-
updateAddButton();
|
|
478
|
-
updateRemoveButtons();
|
|
479
|
-
};
|
|
480
|
-
wrapper.appendChild(addBtn);
|
|
571
|
+
if (!addRow || !countDisplay) return;
|
|
572
|
+
const addBtn = addRow.querySelector(".add-text-btn");
|
|
573
|
+
if (addBtn) {
|
|
574
|
+
const disabled = values.length >= maxCount;
|
|
575
|
+
addBtn.disabled = disabled;
|
|
576
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
577
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
481
578
|
}
|
|
579
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
482
580
|
}
|
|
483
581
|
values.forEach((value) => addTextItem(value));
|
|
484
582
|
updateAddButton();
|
|
485
583
|
updateRemoveButtons();
|
|
486
|
-
const hint = document.createElement("p");
|
|
487
|
-
hint.className = "mt-1";
|
|
488
|
-
hint.style.cssText = `
|
|
489
|
-
font-size: var(--fb-font-size-small);
|
|
490
|
-
color: var(--fb-text-secondary-color);
|
|
491
|
-
`;
|
|
492
|
-
hint.textContent = makeFieldHint(element);
|
|
493
|
-
wrapper.appendChild(hint);
|
|
494
584
|
}
|
|
495
585
|
function validateTextElement(element, key, context) {
|
|
496
586
|
var _a, _b, _c;
|
|
@@ -531,26 +621,31 @@ function validateTextElement(element, key, context) {
|
|
|
531
621
|
};
|
|
532
622
|
const validateTextInput = (input, val, fieldKey) => {
|
|
533
623
|
let hasError = false;
|
|
624
|
+
const { state } = context;
|
|
534
625
|
if (!skipValidation && val) {
|
|
535
626
|
if (element.minLength !== void 0 && element.minLength !== null && val.length < element.minLength) {
|
|
536
|
-
|
|
537
|
-
|
|
627
|
+
const msg = t("minLength", state, { min: element.minLength });
|
|
628
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
629
|
+
markValidity(input, msg);
|
|
538
630
|
hasError = true;
|
|
539
631
|
} else if (element.maxLength !== void 0 && element.maxLength !== null && val.length > element.maxLength) {
|
|
540
|
-
|
|
541
|
-
|
|
632
|
+
const msg = t("maxLength", state, { max: element.maxLength });
|
|
633
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
634
|
+
markValidity(input, msg);
|
|
542
635
|
hasError = true;
|
|
543
636
|
} else if (element.pattern) {
|
|
544
637
|
try {
|
|
545
638
|
const re = new RegExp(element.pattern);
|
|
546
639
|
if (!re.test(val)) {
|
|
547
|
-
|
|
548
|
-
|
|
640
|
+
const msg = t("patternMismatch", state);
|
|
641
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
642
|
+
markValidity(input, msg);
|
|
549
643
|
hasError = true;
|
|
550
644
|
}
|
|
551
645
|
} catch {
|
|
552
|
-
|
|
553
|
-
|
|
646
|
+
const msg = t("invalidPattern", state);
|
|
647
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
648
|
+
markValidity(input, msg);
|
|
554
649
|
hasError = true;
|
|
555
650
|
}
|
|
556
651
|
}
|
|
@@ -571,17 +666,18 @@ function validateTextElement(element, key, context) {
|
|
|
571
666
|
validateTextInput(input, val, `${key}[${index}]`);
|
|
572
667
|
});
|
|
573
668
|
if (!skipValidation) {
|
|
669
|
+
const { state } = context;
|
|
574
670
|
const minCount = (_a = element.minCount) != null ? _a : 1;
|
|
575
671
|
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
576
672
|
const filteredValues = rawValues.filter((v) => v.trim() !== "");
|
|
577
673
|
if (element.required && filteredValues.length === 0) {
|
|
578
|
-
errors.push(`${key}: required`);
|
|
674
|
+
errors.push(`${key}: ${t("required", state)}`);
|
|
579
675
|
}
|
|
580
676
|
if (filteredValues.length < minCount) {
|
|
581
|
-
errors.push(`${key}:
|
|
677
|
+
errors.push(`${key}: ${t("minItems", state, { min: minCount })}`);
|
|
582
678
|
}
|
|
583
679
|
if (filteredValues.length > maxCount) {
|
|
584
|
-
errors.push(`${key}:
|
|
680
|
+
errors.push(`${key}: ${t("maxItems", state, { max: maxCount })}`);
|
|
585
681
|
}
|
|
586
682
|
}
|
|
587
683
|
return { value: values, errors };
|
|
@@ -589,8 +685,9 @@ function validateTextElement(element, key, context) {
|
|
|
589
685
|
const input = scopeRoot.querySelector(`[name$="${key}"]`);
|
|
590
686
|
const val = (_c = input == null ? void 0 : input.value) != null ? _c : "";
|
|
591
687
|
if (!skipValidation && element.required && val === "") {
|
|
592
|
-
|
|
593
|
-
|
|
688
|
+
const msg = t("required", context.state);
|
|
689
|
+
errors.push(`${key}: ${msg}`);
|
|
690
|
+
markValidity(input, msg);
|
|
594
691
|
return { value: null, errors };
|
|
595
692
|
}
|
|
596
693
|
if (input) {
|
|
@@ -634,8 +731,11 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
634
731
|
// src/components/textarea.ts
|
|
635
732
|
function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
636
733
|
const state = ctx.state;
|
|
734
|
+
const textareaWrapper = document.createElement("div");
|
|
735
|
+
textareaWrapper.style.cssText = "position: relative;";
|
|
637
736
|
const textareaInput = document.createElement("textarea");
|
|
638
737
|
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";
|
|
738
|
+
textareaInput.style.cssText = "padding-bottom: 24px;";
|
|
639
739
|
textareaInput.name = pathKey;
|
|
640
740
|
textareaInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
|
|
641
741
|
textareaInput.rows = element.rows || 4;
|
|
@@ -649,11 +749,12 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
649
749
|
textareaInput.addEventListener("blur", handleChange);
|
|
650
750
|
textareaInput.addEventListener("input", handleChange);
|
|
651
751
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
752
|
+
textareaWrapper.appendChild(textareaInput);
|
|
753
|
+
if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
|
|
754
|
+
const counter = createCharCounter(element, textareaInput, true);
|
|
755
|
+
textareaWrapper.appendChild(counter);
|
|
756
|
+
}
|
|
757
|
+
wrapper.appendChild(textareaWrapper);
|
|
657
758
|
}
|
|
658
759
|
function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
659
760
|
var _a, _b;
|
|
@@ -680,9 +781,12 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
680
781
|
function addTextareaItem(value = "", index = -1) {
|
|
681
782
|
const itemWrapper = document.createElement("div");
|
|
682
783
|
itemWrapper.className = "multiple-textarea-item";
|
|
784
|
+
const textareaContainer = document.createElement("div");
|
|
785
|
+
textareaContainer.style.cssText = "position: relative;";
|
|
683
786
|
const textareaInput = document.createElement("textarea");
|
|
684
787
|
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";
|
|
685
|
-
textareaInput.
|
|
788
|
+
textareaInput.style.cssText = "padding-bottom: 24px;";
|
|
789
|
+
textareaInput.placeholder = element.placeholder || t("placeholderText", state);
|
|
686
790
|
textareaInput.rows = element.rows || 4;
|
|
687
791
|
textareaInput.value = value;
|
|
688
792
|
textareaInput.readOnly = state.config.readonly;
|
|
@@ -694,7 +798,12 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
694
798
|
textareaInput.addEventListener("blur", handleChange);
|
|
695
799
|
textareaInput.addEventListener("input", handleChange);
|
|
696
800
|
}
|
|
697
|
-
|
|
801
|
+
textareaContainer.appendChild(textareaInput);
|
|
802
|
+
if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
|
|
803
|
+
const counter = createCharCounter(element, textareaInput, true);
|
|
804
|
+
textareaContainer.appendChild(counter);
|
|
805
|
+
}
|
|
806
|
+
itemWrapper.appendChild(textareaContainer);
|
|
698
807
|
if (index === -1) {
|
|
699
808
|
container.appendChild(itemWrapper);
|
|
700
809
|
} else {
|
|
@@ -736,30 +845,54 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
736
845
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
737
846
|
});
|
|
738
847
|
}
|
|
848
|
+
let addRow = null;
|
|
849
|
+
let countDisplay = null;
|
|
850
|
+
if (!state.config.readonly) {
|
|
851
|
+
addRow = document.createElement("div");
|
|
852
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
853
|
+
const addBtn = document.createElement("button");
|
|
854
|
+
addBtn.type = "button";
|
|
855
|
+
addBtn.className = "add-textarea-btn px-3 py-1 rounded";
|
|
856
|
+
addBtn.style.cssText = `
|
|
857
|
+
color: var(--fb-primary-color);
|
|
858
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
859
|
+
background-color: transparent;
|
|
860
|
+
font-size: var(--fb-font-size);
|
|
861
|
+
transition: all var(--fb-transition-duration);
|
|
862
|
+
`;
|
|
863
|
+
addBtn.textContent = "+";
|
|
864
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
865
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
866
|
+
});
|
|
867
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
868
|
+
addBtn.style.backgroundColor = "transparent";
|
|
869
|
+
});
|
|
870
|
+
addBtn.onclick = () => {
|
|
871
|
+
values.push(element.default || "");
|
|
872
|
+
addTextareaItem(element.default || "");
|
|
873
|
+
updateAddButton();
|
|
874
|
+
updateRemoveButtons();
|
|
875
|
+
};
|
|
876
|
+
countDisplay = document.createElement("span");
|
|
877
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
878
|
+
addRow.appendChild(addBtn);
|
|
879
|
+
addRow.appendChild(countDisplay);
|
|
880
|
+
wrapper.appendChild(addRow);
|
|
881
|
+
}
|
|
739
882
|
function updateAddButton() {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
if (
|
|
743
|
-
const
|
|
744
|
-
addBtn.
|
|
745
|
-
addBtn.
|
|
746
|
-
addBtn.
|
|
747
|
-
addBtn.onclick = () => {
|
|
748
|
-
values.push(element.default || "");
|
|
749
|
-
addTextareaItem(element.default || "");
|
|
750
|
-
updateAddButton();
|
|
751
|
-
updateRemoveButtons();
|
|
752
|
-
};
|
|
753
|
-
wrapper.appendChild(addBtn);
|
|
883
|
+
if (!addRow || !countDisplay) return;
|
|
884
|
+
const addBtn = addRow.querySelector(".add-textarea-btn");
|
|
885
|
+
if (addBtn) {
|
|
886
|
+
const disabled = values.length >= maxCount;
|
|
887
|
+
addBtn.disabled = disabled;
|
|
888
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
889
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
754
890
|
}
|
|
891
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
755
892
|
}
|
|
756
893
|
values.forEach((value) => addTextareaItem(value));
|
|
757
894
|
updateAddButton();
|
|
758
895
|
updateRemoveButtons();
|
|
759
|
-
const hint = document.createElement("p");
|
|
760
|
-
hint.className = "text-xs text-gray-500 mt-1";
|
|
761
|
-
hint.textContent = makeFieldHint(element);
|
|
762
|
-
wrapper.appendChild(hint);
|
|
763
896
|
}
|
|
764
897
|
function validateTextareaElement(element, key, context) {
|
|
765
898
|
return validateTextElement(element, key, context);
|
|
@@ -769,11 +902,61 @@ function updateTextareaField(element, fieldPath, value, context) {
|
|
|
769
902
|
}
|
|
770
903
|
|
|
771
904
|
// src/components/number.ts
|
|
905
|
+
function createNumberCounter(element, input) {
|
|
906
|
+
const counter = document.createElement("span");
|
|
907
|
+
counter.className = "number-counter";
|
|
908
|
+
counter.style.cssText = `
|
|
909
|
+
position: absolute;
|
|
910
|
+
top: 50%;
|
|
911
|
+
transform: translateY(-50%);
|
|
912
|
+
right: 10px;
|
|
913
|
+
font-size: var(--fb-font-size-small);
|
|
914
|
+
color: var(--fb-text-secondary-color);
|
|
915
|
+
pointer-events: none;
|
|
916
|
+
background: var(--fb-background-color);
|
|
917
|
+
padding: 0 4px;
|
|
918
|
+
`;
|
|
919
|
+
const updateCounter = () => {
|
|
920
|
+
const val = input.value ? parseFloat(input.value) : null;
|
|
921
|
+
const min = element.min;
|
|
922
|
+
const max = element.max;
|
|
923
|
+
if (min == null && max == null) {
|
|
924
|
+
counter.textContent = "";
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (val == null || min != null && val < min) {
|
|
928
|
+
if (min != null && max != null) {
|
|
929
|
+
counter.textContent = `${min}-${max}`;
|
|
930
|
+
} else if (max != null) {
|
|
931
|
+
counter.textContent = `\u2264${max}`;
|
|
932
|
+
} else if (min != null) {
|
|
933
|
+
counter.textContent = `\u2265${min}`;
|
|
934
|
+
}
|
|
935
|
+
counter.style.color = "var(--fb-text-secondary-color)";
|
|
936
|
+
} else if (max != null && val > max) {
|
|
937
|
+
counter.textContent = `${val}/${max}`;
|
|
938
|
+
counter.style.color = "var(--fb-error-color)";
|
|
939
|
+
} else {
|
|
940
|
+
if (max != null) {
|
|
941
|
+
counter.textContent = `${val}/${max}`;
|
|
942
|
+
} else {
|
|
943
|
+
counter.textContent = `${val}`;
|
|
944
|
+
}
|
|
945
|
+
counter.style.color = "var(--fb-text-secondary-color)";
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
input.addEventListener("input", updateCounter);
|
|
949
|
+
updateCounter();
|
|
950
|
+
return counter;
|
|
951
|
+
}
|
|
772
952
|
function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
773
953
|
const state = ctx.state;
|
|
954
|
+
const inputWrapper = document.createElement("div");
|
|
955
|
+
inputWrapper.style.cssText = "position: relative;";
|
|
774
956
|
const numberInput = document.createElement("input");
|
|
775
957
|
numberInput.type = "number";
|
|
776
958
|
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";
|
|
959
|
+
numberInput.style.cssText = "padding-right: 60px; width: 100%; box-sizing: border-box;";
|
|
777
960
|
numberInput.name = pathKey;
|
|
778
961
|
numberInput.placeholder = element.placeholder || "0";
|
|
779
962
|
if (element.min !== void 0) numberInput.min = element.min.toString();
|
|
@@ -789,11 +972,12 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
789
972
|
numberInput.addEventListener("blur", handleChange);
|
|
790
973
|
numberInput.addEventListener("input", handleChange);
|
|
791
974
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
975
|
+
inputWrapper.appendChild(numberInput);
|
|
976
|
+
if (!state.config.readonly && (element.min != null || element.max != null)) {
|
|
977
|
+
const counter = createNumberCounter(element, numberInput);
|
|
978
|
+
inputWrapper.appendChild(counter);
|
|
979
|
+
}
|
|
980
|
+
wrapper.appendChild(inputWrapper);
|
|
797
981
|
}
|
|
798
982
|
function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
799
983
|
var _a, _b;
|
|
@@ -820,9 +1004,12 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
820
1004
|
function addNumberItem(value = "", index = -1) {
|
|
821
1005
|
const itemWrapper = document.createElement("div");
|
|
822
1006
|
itemWrapper.className = "multiple-number-item flex items-center gap-2";
|
|
1007
|
+
const inputContainer = document.createElement("div");
|
|
1008
|
+
inputContainer.style.cssText = "position: relative; flex: 1;";
|
|
823
1009
|
const numberInput = document.createElement("input");
|
|
824
1010
|
numberInput.type = "number";
|
|
825
|
-
numberInput.className = "
|
|
1011
|
+
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";
|
|
1012
|
+
numberInput.style.cssText = "padding-right: 60px; width: 100%; box-sizing: border-box;";
|
|
826
1013
|
numberInput.placeholder = element.placeholder || "0";
|
|
827
1014
|
if (element.min !== void 0) numberInput.min = element.min.toString();
|
|
828
1015
|
if (element.max !== void 0) numberInput.max = element.max.toString();
|
|
@@ -837,7 +1024,12 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
837
1024
|
numberInput.addEventListener("blur", handleChange);
|
|
838
1025
|
numberInput.addEventListener("input", handleChange);
|
|
839
1026
|
}
|
|
840
|
-
|
|
1027
|
+
inputContainer.appendChild(numberInput);
|
|
1028
|
+
if (!state.config.readonly && (element.min != null || element.max != null)) {
|
|
1029
|
+
const counter = createNumberCounter(element, numberInput);
|
|
1030
|
+
inputContainer.appendChild(counter);
|
|
1031
|
+
}
|
|
1032
|
+
itemWrapper.appendChild(inputContainer);
|
|
841
1033
|
if (index === -1) {
|
|
842
1034
|
container.appendChild(itemWrapper);
|
|
843
1035
|
} else {
|
|
@@ -879,30 +1071,54 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
879
1071
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
880
1072
|
});
|
|
881
1073
|
}
|
|
1074
|
+
let addRow = null;
|
|
1075
|
+
let countDisplay = null;
|
|
1076
|
+
if (!state.config.readonly) {
|
|
1077
|
+
addRow = document.createElement("div");
|
|
1078
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
1079
|
+
const addBtn = document.createElement("button");
|
|
1080
|
+
addBtn.type = "button";
|
|
1081
|
+
addBtn.className = "add-number-btn px-3 py-1 rounded";
|
|
1082
|
+
addBtn.style.cssText = `
|
|
1083
|
+
color: var(--fb-primary-color);
|
|
1084
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
1085
|
+
background-color: transparent;
|
|
1086
|
+
font-size: var(--fb-font-size);
|
|
1087
|
+
transition: all var(--fb-transition-duration);
|
|
1088
|
+
`;
|
|
1089
|
+
addBtn.textContent = "+";
|
|
1090
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
1091
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1092
|
+
});
|
|
1093
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
1094
|
+
addBtn.style.backgroundColor = "transparent";
|
|
1095
|
+
});
|
|
1096
|
+
addBtn.onclick = () => {
|
|
1097
|
+
values.push(element.default || "");
|
|
1098
|
+
addNumberItem(element.default || "");
|
|
1099
|
+
updateAddButton();
|
|
1100
|
+
updateRemoveButtons();
|
|
1101
|
+
};
|
|
1102
|
+
countDisplay = document.createElement("span");
|
|
1103
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
1104
|
+
addRow.appendChild(addBtn);
|
|
1105
|
+
addRow.appendChild(countDisplay);
|
|
1106
|
+
wrapper.appendChild(addRow);
|
|
1107
|
+
}
|
|
882
1108
|
function updateAddButton() {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
if (
|
|
886
|
-
const
|
|
887
|
-
addBtn.
|
|
888
|
-
addBtn.
|
|
889
|
-
addBtn.
|
|
890
|
-
addBtn.onclick = () => {
|
|
891
|
-
values.push(element.default || "");
|
|
892
|
-
addNumberItem(element.default || "");
|
|
893
|
-
updateAddButton();
|
|
894
|
-
updateRemoveButtons();
|
|
895
|
-
};
|
|
896
|
-
wrapper.appendChild(addBtn);
|
|
1109
|
+
if (!addRow || !countDisplay) return;
|
|
1110
|
+
const addBtn = addRow.querySelector(".add-number-btn");
|
|
1111
|
+
if (addBtn) {
|
|
1112
|
+
const disabled = values.length >= maxCount;
|
|
1113
|
+
addBtn.disabled = disabled;
|
|
1114
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1115
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
897
1116
|
}
|
|
1117
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
898
1118
|
}
|
|
899
1119
|
values.forEach((value) => addNumberItem(value));
|
|
900
1120
|
updateAddButton();
|
|
901
1121
|
updateRemoveButtons();
|
|
902
|
-
const hint = document.createElement("p");
|
|
903
|
-
hint.className = "text-xs text-gray-500 mt-1";
|
|
904
|
-
hint.textContent = makeFieldHint(element);
|
|
905
|
-
wrapper.appendChild(hint);
|
|
906
1122
|
}
|
|
907
1123
|
function validateNumberElement(element, key, context) {
|
|
908
1124
|
var _a, _b, _c, _d, _e;
|
|
@@ -943,13 +1159,16 @@ function validateNumberElement(element, key, context) {
|
|
|
943
1159
|
};
|
|
944
1160
|
const validateNumberInput = (input, v, fieldKey) => {
|
|
945
1161
|
let hasError = false;
|
|
1162
|
+
const { state } = context;
|
|
946
1163
|
if (!skipValidation && element.min !== void 0 && element.min !== null && v < element.min) {
|
|
947
|
-
|
|
948
|
-
|
|
1164
|
+
const msg = t("minValue", state, { min: element.min });
|
|
1165
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
1166
|
+
markValidity(input, msg);
|
|
949
1167
|
hasError = true;
|
|
950
1168
|
} else if (!skipValidation && element.max !== void 0 && element.max !== null && v > element.max) {
|
|
951
|
-
|
|
952
|
-
|
|
1169
|
+
const msg = t("maxValue", state, { max: element.max });
|
|
1170
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
1171
|
+
markValidity(input, msg);
|
|
953
1172
|
hasError = true;
|
|
954
1173
|
}
|
|
955
1174
|
if (!hasError) {
|
|
@@ -971,8 +1190,9 @@ function validateNumberElement(element, key, context) {
|
|
|
971
1190
|
}
|
|
972
1191
|
const v = parseFloat(raw);
|
|
973
1192
|
if (!skipValidation && !Number.isFinite(v)) {
|
|
974
|
-
|
|
975
|
-
|
|
1193
|
+
const msg = t("notANumber", context.state);
|
|
1194
|
+
errors.push(`${key}[${index}]: ${msg}`);
|
|
1195
|
+
markValidity(input, msg);
|
|
976
1196
|
values.push(null);
|
|
977
1197
|
return;
|
|
978
1198
|
}
|
|
@@ -981,26 +1201,29 @@ function validateNumberElement(element, key, context) {
|
|
|
981
1201
|
values.push(Number(v.toFixed(d)));
|
|
982
1202
|
});
|
|
983
1203
|
if (!skipValidation) {
|
|
1204
|
+
const { state } = context;
|
|
984
1205
|
const minCount = (_a = element.minCount) != null ? _a : 1;
|
|
985
1206
|
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
986
1207
|
const filteredValues = values.filter((v) => v !== null);
|
|
987
1208
|
if (element.required && filteredValues.length === 0) {
|
|
988
|
-
errors.push(`${key}: required`);
|
|
1209
|
+
errors.push(`${key}: ${t("required", state)}`);
|
|
989
1210
|
}
|
|
990
1211
|
if (filteredValues.length < minCount) {
|
|
991
|
-
errors.push(`${key}:
|
|
1212
|
+
errors.push(`${key}: ${t("minItems", state, { min: minCount })}`);
|
|
992
1213
|
}
|
|
993
1214
|
if (filteredValues.length > maxCount) {
|
|
994
|
-
errors.push(`${key}:
|
|
1215
|
+
errors.push(`${key}: ${t("maxItems", state, { max: maxCount })}`);
|
|
995
1216
|
}
|
|
996
1217
|
}
|
|
997
1218
|
return { value: values, errors };
|
|
998
1219
|
} else {
|
|
999
1220
|
const input = scopeRoot.querySelector(`[name$="${key}"]`);
|
|
1000
1221
|
const raw = (_c = input == null ? void 0 : input.value) != null ? _c : "";
|
|
1222
|
+
const { state } = context;
|
|
1001
1223
|
if (!skipValidation && element.required && raw === "") {
|
|
1002
|
-
|
|
1003
|
-
|
|
1224
|
+
const msg = t("required", state);
|
|
1225
|
+
errors.push(`${key}: ${msg}`);
|
|
1226
|
+
markValidity(input, msg);
|
|
1004
1227
|
return { value: null, errors };
|
|
1005
1228
|
}
|
|
1006
1229
|
if (raw === "") {
|
|
@@ -1009,8 +1232,9 @@ function validateNumberElement(element, key, context) {
|
|
|
1009
1232
|
}
|
|
1010
1233
|
const v = parseFloat(raw);
|
|
1011
1234
|
if (!skipValidation && !Number.isFinite(v)) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1235
|
+
const msg = t("notANumber", state);
|
|
1236
|
+
errors.push(`${key}: ${msg}`);
|
|
1237
|
+
markValidity(input, msg);
|
|
1014
1238
|
return { value: null, errors };
|
|
1015
1239
|
}
|
|
1016
1240
|
validateNumberInput(input, v, key);
|
|
@@ -1077,10 +1301,12 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1077
1301
|
selectInput.addEventListener("change", handleChange);
|
|
1078
1302
|
}
|
|
1079
1303
|
wrapper.appendChild(selectInput);
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1304
|
+
if (!state.config.readonly) {
|
|
1305
|
+
const selectHint = document.createElement("p");
|
|
1306
|
+
selectHint.className = "text-xs text-gray-500 mt-1";
|
|
1307
|
+
selectHint.textContent = makeFieldHint(element, state);
|
|
1308
|
+
wrapper.appendChild(selectHint);
|
|
1309
|
+
}
|
|
1084
1310
|
}
|
|
1085
1311
|
function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
1086
1312
|
var _a, _b, _c, _d;
|
|
@@ -1165,32 +1391,62 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1165
1391
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1166
1392
|
});
|
|
1167
1393
|
}
|
|
1394
|
+
let addRow = null;
|
|
1395
|
+
let countDisplay = null;
|
|
1396
|
+
if (!state.config.readonly) {
|
|
1397
|
+
addRow = document.createElement("div");
|
|
1398
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
1399
|
+
const addBtn = document.createElement("button");
|
|
1400
|
+
addBtn.type = "button";
|
|
1401
|
+
addBtn.className = "add-select-btn px-3 py-1 rounded";
|
|
1402
|
+
addBtn.style.cssText = `
|
|
1403
|
+
color: var(--fb-primary-color);
|
|
1404
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
1405
|
+
background-color: transparent;
|
|
1406
|
+
font-size: var(--fb-font-size);
|
|
1407
|
+
transition: all var(--fb-transition-duration);
|
|
1408
|
+
`;
|
|
1409
|
+
addBtn.textContent = "+";
|
|
1410
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
1411
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1412
|
+
});
|
|
1413
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
1414
|
+
addBtn.style.backgroundColor = "transparent";
|
|
1415
|
+
});
|
|
1416
|
+
addBtn.onclick = () => {
|
|
1417
|
+
var _a2, _b2;
|
|
1418
|
+
const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
|
|
1419
|
+
values.push(defaultValue);
|
|
1420
|
+
addSelectItem(defaultValue);
|
|
1421
|
+
updateAddButton();
|
|
1422
|
+
updateRemoveButtons();
|
|
1423
|
+
};
|
|
1424
|
+
countDisplay = document.createElement("span");
|
|
1425
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
1426
|
+
addRow.appendChild(addBtn);
|
|
1427
|
+
addRow.appendChild(countDisplay);
|
|
1428
|
+
wrapper.appendChild(addRow);
|
|
1429
|
+
}
|
|
1168
1430
|
function updateAddButton() {
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
if (
|
|
1172
|
-
const
|
|
1173
|
-
addBtn.
|
|
1174
|
-
addBtn.
|
|
1175
|
-
addBtn.
|
|
1176
|
-
addBtn.onclick = () => {
|
|
1177
|
-
var _a2, _b2;
|
|
1178
|
-
const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
|
|
1179
|
-
values.push(defaultValue);
|
|
1180
|
-
addSelectItem(defaultValue);
|
|
1181
|
-
updateAddButton();
|
|
1182
|
-
updateRemoveButtons();
|
|
1183
|
-
};
|
|
1184
|
-
wrapper.appendChild(addBtn);
|
|
1431
|
+
if (!addRow || !countDisplay) return;
|
|
1432
|
+
const addBtn = addRow.querySelector(".add-select-btn");
|
|
1433
|
+
if (addBtn) {
|
|
1434
|
+
const disabled = values.length >= maxCount;
|
|
1435
|
+
addBtn.disabled = disabled;
|
|
1436
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1437
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1185
1438
|
}
|
|
1439
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
1186
1440
|
}
|
|
1187
1441
|
values.forEach((value) => addSelectItem(value));
|
|
1188
1442
|
updateAddButton();
|
|
1189
1443
|
updateRemoveButtons();
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1444
|
+
if (!state.config.readonly) {
|
|
1445
|
+
const hint = document.createElement("p");
|
|
1446
|
+
hint.className = "text-xs text-gray-500 mt-1";
|
|
1447
|
+
hint.textContent = makeFieldHint(element, state);
|
|
1448
|
+
wrapper.appendChild(hint);
|
|
1449
|
+
}
|
|
1194
1450
|
}
|
|
1195
1451
|
function validateSelectElement(element, key, context) {
|
|
1196
1452
|
var _a;
|
|
@@ -1232,17 +1488,18 @@ function validateSelectElement(element, key, context) {
|
|
|
1232
1488
|
const validateMultipleCount = (key2, values, element2, filterFn) => {
|
|
1233
1489
|
var _a2, _b;
|
|
1234
1490
|
if (skipValidation) return;
|
|
1491
|
+
const { state } = context;
|
|
1235
1492
|
const filteredValues = values.filter(filterFn);
|
|
1236
1493
|
const minCount = "minCount" in element2 ? (_a2 = element2.minCount) != null ? _a2 : 1 : 1;
|
|
1237
1494
|
const maxCount = "maxCount" in element2 ? (_b = element2.maxCount) != null ? _b : Infinity : Infinity;
|
|
1238
1495
|
if (element2.required && filteredValues.length === 0) {
|
|
1239
|
-
errors.push(`${key2}: required`);
|
|
1496
|
+
errors.push(`${key2}: ${t("required", state)}`);
|
|
1240
1497
|
}
|
|
1241
1498
|
if (filteredValues.length < minCount) {
|
|
1242
|
-
errors.push(`${key2}:
|
|
1499
|
+
errors.push(`${key2}: ${t("minItems", state, { min: minCount })}`);
|
|
1243
1500
|
}
|
|
1244
1501
|
if (filteredValues.length > maxCount) {
|
|
1245
|
-
errors.push(`${key2}:
|
|
1502
|
+
errors.push(`${key2}: ${t("maxItems", state, { max: maxCount })}`);
|
|
1246
1503
|
}
|
|
1247
1504
|
};
|
|
1248
1505
|
if ("multiple" in element && element.multiple) {
|
|
@@ -1264,8 +1521,9 @@ function validateSelectElement(element, key, context) {
|
|
|
1264
1521
|
);
|
|
1265
1522
|
const val = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
1266
1523
|
if (!skipValidation && element.required && val === "") {
|
|
1267
|
-
|
|
1268
|
-
|
|
1524
|
+
const msg = t("required", context.state);
|
|
1525
|
+
errors.push(`${key}: ${msg}`);
|
|
1526
|
+
markValidity(input, msg);
|
|
1269
1527
|
return { value: null, errors };
|
|
1270
1528
|
} else {
|
|
1271
1529
|
markValidity(input, null);
|
|
@@ -1317,18 +1575,11 @@ function updateSelectField(element, fieldPath, value, context) {
|
|
|
1317
1575
|
}
|
|
1318
1576
|
}
|
|
1319
1577
|
|
|
1320
|
-
// src/utils/translation.ts
|
|
1321
|
-
function t(key, state) {
|
|
1322
|
-
const locale = state.config.locale || "en";
|
|
1323
|
-
const translations = state.config.translations[locale] || state.config.translations.en;
|
|
1324
|
-
return translations[key] || key;
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
1578
|
// src/components/file.ts
|
|
1328
|
-
function renderLocalImagePreview(container, file, fileName) {
|
|
1579
|
+
function renderLocalImagePreview(container, file, fileName, state) {
|
|
1329
1580
|
const img = document.createElement("img");
|
|
1330
1581
|
img.className = "w-full h-full object-contain";
|
|
1331
|
-
img.alt = fileName || "
|
|
1582
|
+
img.alt = fileName || t("previewAlt", state);
|
|
1332
1583
|
const reader = new FileReader();
|
|
1333
1584
|
reader.onload = (e) => {
|
|
1334
1585
|
var _a;
|
|
@@ -1348,14 +1599,14 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
|
|
|
1348
1599
|
<div class="relative group h-full">
|
|
1349
1600
|
<video class="w-full h-full object-contain" controls preload="auto" muted>
|
|
1350
1601
|
<source src="${videoUrl}" type="${videoType}">
|
|
1351
|
-
|
|
1602
|
+
${escapeHtml(t("videoNotSupported", state))}
|
|
1352
1603
|
</video>
|
|
1353
1604
|
<div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
|
|
1354
1605
|
<button class="bg-red-600 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs delete-file-btn">
|
|
1355
|
-
${t("removeElement", state)}
|
|
1606
|
+
${escapeHtml(t("removeElement", state))}
|
|
1356
1607
|
</button>
|
|
1357
1608
|
<button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
|
|
1358
|
-
|
|
1609
|
+
${escapeHtml(t("changeButton", state))}
|
|
1359
1610
|
</button>
|
|
1360
1611
|
</div>
|
|
1361
1612
|
</div>
|
|
@@ -1405,11 +1656,11 @@ function handleVideoDelete(container, resourceId, state, deps) {
|
|
|
1405
1656
|
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1406
1657
|
<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"/>
|
|
1407
1658
|
</svg>
|
|
1408
|
-
<div class="text-sm text-center">${t("clickDragText", state)}</div>
|
|
1659
|
+
<div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
|
|
1409
1660
|
</div>
|
|
1410
1661
|
`;
|
|
1411
1662
|
}
|
|
1412
|
-
function renderUploadedVideoPreview(container, thumbnailUrl, videoType) {
|
|
1663
|
+
function renderUploadedVideoPreview(container, thumbnailUrl, videoType, state) {
|
|
1413
1664
|
const video = document.createElement("video");
|
|
1414
1665
|
video.className = "w-full h-full object-contain";
|
|
1415
1666
|
video.controls = true;
|
|
@@ -1420,7 +1671,7 @@ function renderUploadedVideoPreview(container, thumbnailUrl, videoType) {
|
|
|
1420
1671
|
source.type = videoType;
|
|
1421
1672
|
video.appendChild(source);
|
|
1422
1673
|
video.appendChild(
|
|
1423
|
-
document.createTextNode("
|
|
1674
|
+
document.createTextNode(t("videoNotSupported", state))
|
|
1424
1675
|
);
|
|
1425
1676
|
container.appendChild(video);
|
|
1426
1677
|
}
|
|
@@ -1439,7 +1690,7 @@ function renderDeleteButton(container, resourceId, state) {
|
|
|
1439
1690
|
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1440
1691
|
<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"/>
|
|
1441
1692
|
</svg>
|
|
1442
|
-
<div class="text-sm text-center">${t("clickDragText", state)}</div>
|
|
1693
|
+
<div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
|
|
1443
1694
|
</div>
|
|
1444
1695
|
`;
|
|
1445
1696
|
});
|
|
@@ -1449,7 +1700,7 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
|
|
|
1449
1700
|
return;
|
|
1450
1701
|
}
|
|
1451
1702
|
if (meta.type && meta.type.startsWith("image/")) {
|
|
1452
|
-
renderLocalImagePreview(container, meta.file, fileName);
|
|
1703
|
+
renderLocalImagePreview(container, meta.file, fileName, state);
|
|
1453
1704
|
} else if (meta.type && meta.type.startsWith("video/")) {
|
|
1454
1705
|
const newContainer = renderLocalVideoPreview(
|
|
1455
1706
|
container,
|
|
@@ -1461,7 +1712,7 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
|
|
|
1461
1712
|
);
|
|
1462
1713
|
container = newContainer;
|
|
1463
1714
|
} else {
|
|
1464
|
-
container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div class="text-2xl mb-2">\u{1F4C1}</div><div class="text-sm">${fileName}</div></div>`;
|
|
1715
|
+
container.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400"><div class="text-2xl mb-2">\u{1F4C1}</div><div class="text-sm">${escapeHtml(fileName)}</div></div>`;
|
|
1465
1716
|
}
|
|
1466
1717
|
if (!isReadonly && !(meta.type && meta.type.startsWith("video/"))) {
|
|
1467
1718
|
renderDeleteButton(container, resourceId, state);
|
|
@@ -1477,11 +1728,11 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
|
|
|
1477
1728
|
if (thumbnailUrl) {
|
|
1478
1729
|
clear(container);
|
|
1479
1730
|
if (meta && meta.type && meta.type.startsWith("video/")) {
|
|
1480
|
-
renderUploadedVideoPreview(container, thumbnailUrl, meta.type);
|
|
1731
|
+
renderUploadedVideoPreview(container, thumbnailUrl, meta.type, state);
|
|
1481
1732
|
} else {
|
|
1482
1733
|
const img = document.createElement("img");
|
|
1483
1734
|
img.className = "w-full h-full object-contain";
|
|
1484
|
-
img.alt = fileName || "
|
|
1735
|
+
img.alt = fileName || t("previewAlt", state);
|
|
1485
1736
|
img.src = thumbnailUrl;
|
|
1486
1737
|
container.appendChild(img);
|
|
1487
1738
|
}
|
|
@@ -1495,7 +1746,7 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
|
|
|
1495
1746
|
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1496
1747
|
<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"/>
|
|
1497
1748
|
</svg>
|
|
1498
|
-
<div class="text-sm text-center">${fileName || "
|
|
1749
|
+
<div class="text-sm text-center">${escapeHtml(fileName || t("previewUnavailable", state))}</div>
|
|
1499
1750
|
</div>
|
|
1500
1751
|
`;
|
|
1501
1752
|
}
|
|
@@ -1552,16 +1803,16 @@ async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
|
1552
1803
|
try {
|
|
1553
1804
|
const thumbnailUrl = await state.config.getThumbnail(resourceId);
|
|
1554
1805
|
if (thumbnailUrl) {
|
|
1555
|
-
previewContainer.innerHTML = `<img src="${thumbnailUrl}" alt="${actualFileName}" class="w-full h-auto">`;
|
|
1806
|
+
previewContainer.innerHTML = `<img src="${thumbnailUrl}" alt="${escapeHtml(actualFileName)}" class="w-full h-auto">`;
|
|
1556
1807
|
} else {
|
|
1557
|
-
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F5BC}\uFE0F</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
1808
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F5BC}\uFE0F</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
|
|
1558
1809
|
}
|
|
1559
1810
|
} catch (error) {
|
|
1560
1811
|
console.warn("getThumbnail failed for", resourceId, error);
|
|
1561
|
-
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F5BC}\uFE0F</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
1812
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F5BC}\uFE0F</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
|
|
1562
1813
|
}
|
|
1563
1814
|
} else {
|
|
1564
|
-
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F5BC}\uFE0F</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
1815
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F5BC}\uFE0F</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
|
|
1565
1816
|
}
|
|
1566
1817
|
} else if (isVideo) {
|
|
1567
1818
|
if (state.config.getThumbnail) {
|
|
@@ -1572,7 +1823,7 @@ async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
|
1572
1823
|
<div class="relative group">
|
|
1573
1824
|
<video class="w-full h-auto" controls preload="auto" muted>
|
|
1574
1825
|
<source src="${videoUrl}" type="${(meta == null ? void 0 : meta.type) || "video/mp4"}">
|
|
1575
|
-
|
|
1826
|
+
${escapeHtml(t("videoNotSupported", state))}
|
|
1576
1827
|
</video>
|
|
1577
1828
|
<div class="absolute inset-0 bg-black bg-opacity-20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center pointer-events-none">
|
|
1578
1829
|
<div class="bg-white bg-opacity-90 rounded-full p-3">
|
|
@@ -1584,14 +1835,14 @@ async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
|
1584
1835
|
</div>
|
|
1585
1836
|
`;
|
|
1586
1837
|
} else {
|
|
1587
|
-
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F3A5}</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
1838
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F3A5}</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
|
|
1588
1839
|
}
|
|
1589
1840
|
} catch (error) {
|
|
1590
1841
|
console.warn("getThumbnail failed for video", resourceId, error);
|
|
1591
|
-
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F3A5}</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
1842
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F3A5}</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
|
|
1592
1843
|
}
|
|
1593
1844
|
} else {
|
|
1594
|
-
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F3A5}</div><div class="text-sm">${actualFileName}</div></div></div>`;
|
|
1845
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">\u{1F3A5}</div><div class="text-sm">${escapeHtml(actualFileName)}</div></div></div>`;
|
|
1595
1846
|
}
|
|
1596
1847
|
} else {
|
|
1597
1848
|
const fileIcon = isPSD ? "\u{1F3A8}" : "\u{1F4C1}";
|
|
@@ -1601,13 +1852,13 @@ async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
|
1601
1852
|
<div class="flex items-center space-x-3">
|
|
1602
1853
|
<div class="text-3xl text-gray-400">${fileIcon}</div>
|
|
1603
1854
|
<div class="flex-1 min-w-0">
|
|
1604
|
-
<div class="text-sm font-medium text-gray-900 truncate">${actualFileName}</div>
|
|
1855
|
+
<div class="text-sm font-medium text-gray-900 truncate">${escapeHtml(actualFileName)}</div>
|
|
1605
1856
|
<div class="text-xs text-gray-500">${fileDescription}</div>
|
|
1606
1857
|
</div>
|
|
1607
1858
|
</div>
|
|
1608
1859
|
`;
|
|
1609
1860
|
} else {
|
|
1610
|
-
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">${fileIcon}</div><div class="text-sm">${actualFileName}</div><div class="text-xs text-gray-500 mt-1">${fileDescription}</div></div></div>`;
|
|
1861
|
+
previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">${fileIcon}</div><div class="text-sm">${escapeHtml(actualFileName)}</div><div class="text-xs text-gray-500 mt-1">${fileDescription}</div></div></div>`;
|
|
1611
1862
|
}
|
|
1612
1863
|
}
|
|
1613
1864
|
const fileNameElement = document.createElement("p");
|
|
@@ -1630,12 +1881,18 @@ async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
|
1630
1881
|
fileResult.appendChild(downloadButton);
|
|
1631
1882
|
return fileResult;
|
|
1632
1883
|
}
|
|
1633
|
-
function renderResourcePills(container, rids, state, onRemove) {
|
|
1884
|
+
function renderResourcePills(container, rids, state, onRemove, hint, countInfo) {
|
|
1634
1885
|
clear(container);
|
|
1886
|
+
const buildHintLine = () => {
|
|
1887
|
+
const parts = [t("clickDragTextMultiple", state)];
|
|
1888
|
+
if (hint) parts.push(hint);
|
|
1889
|
+
if (countInfo) parts.push(countInfo);
|
|
1890
|
+
return parts.join(" \u2022 ");
|
|
1891
|
+
};
|
|
1635
1892
|
const isInitialRender = !container.classList.contains("grid");
|
|
1636
1893
|
if ((!rids || rids.length === 0) && isInitialRender) {
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1894
|
+
const gridContainer2 = document.createElement("div");
|
|
1895
|
+
gridContainer2.className = "grid grid-cols-4 gap-3 mb-3";
|
|
1639
1896
|
for (let i = 0; i < 4; i++) {
|
|
1640
1897
|
const slot = document.createElement("div");
|
|
1641
1898
|
slot.className = "aspect-square bg-gray-100 border-2 border-dashed border-gray-300 rounded flex items-center justify-center cursor-pointer hover:border-gray-400 transition-colors";
|
|
@@ -1666,36 +1923,17 @@ function renderResourcePills(container, rids, state, onRemove) {
|
|
|
1666
1923
|
);
|
|
1667
1924
|
if (fileInput) fileInput.click();
|
|
1668
1925
|
};
|
|
1669
|
-
|
|
1670
|
-
}
|
|
1671
|
-
const
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
uploadLink.onclick = (e) => {
|
|
1677
|
-
e.stopPropagation();
|
|
1678
|
-
let filesWrapper = container.parentElement;
|
|
1679
|
-
while (filesWrapper && !filesWrapper.classList.contains("space-y-2")) {
|
|
1680
|
-
filesWrapper = filesWrapper.parentElement;
|
|
1681
|
-
}
|
|
1682
|
-
if (!filesWrapper && container.classList.contains("space-y-2")) {
|
|
1683
|
-
filesWrapper = container;
|
|
1684
|
-
}
|
|
1685
|
-
const fileInput = filesWrapper == null ? void 0 : filesWrapper.querySelector(
|
|
1686
|
-
'input[type="file"]'
|
|
1687
|
-
);
|
|
1688
|
-
if (fileInput) fileInput.click();
|
|
1689
|
-
};
|
|
1690
|
-
textContainer.appendChild(uploadLink);
|
|
1691
|
-
textContainer.appendChild(
|
|
1692
|
-
document.createTextNode(` ${t("dragDropText", state)}`)
|
|
1693
|
-
);
|
|
1694
|
-
container.appendChild(gridContainer);
|
|
1695
|
-
container.appendChild(textContainer);
|
|
1926
|
+
gridContainer2.appendChild(slot);
|
|
1927
|
+
}
|
|
1928
|
+
const hintText2 = document.createElement("div");
|
|
1929
|
+
hintText2.className = "text-center text-xs text-gray-500 mt-2";
|
|
1930
|
+
hintText2.textContent = buildHintLine();
|
|
1931
|
+
container.appendChild(gridContainer2);
|
|
1932
|
+
container.appendChild(hintText2);
|
|
1696
1933
|
return;
|
|
1697
1934
|
}
|
|
1698
|
-
|
|
1935
|
+
const gridContainer = document.createElement("div");
|
|
1936
|
+
gridContainer.className = "files-list grid grid-cols-4 gap-3";
|
|
1699
1937
|
const currentImagesCount = rids ? rids.length : 0;
|
|
1700
1938
|
const rowsNeeded = Math.floor(currentImagesCount / 4) + 1;
|
|
1701
1939
|
const slotsNeeded = rowsNeeded * 4;
|
|
@@ -1710,7 +1948,7 @@ function renderResourcePills(container, rids, state, onRemove) {
|
|
|
1710
1948
|
console.error("Failed to render thumbnail:", err);
|
|
1711
1949
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1712
1950
|
<div class="text-2xl mb-1">\u{1F4C1}</div>
|
|
1713
|
-
<div class="text-xs"
|
|
1951
|
+
<div class="text-xs">${escapeHtml(t("previewError", state))}</div>
|
|
1714
1952
|
</div>`;
|
|
1715
1953
|
});
|
|
1716
1954
|
if (onRemove) {
|
|
@@ -1743,15 +1981,20 @@ function renderResourcePills(container, rids, state, onRemove) {
|
|
|
1743
1981
|
if (fileInput) fileInput.click();
|
|
1744
1982
|
};
|
|
1745
1983
|
}
|
|
1746
|
-
|
|
1984
|
+
gridContainer.appendChild(slot);
|
|
1747
1985
|
}
|
|
1986
|
+
container.appendChild(gridContainer);
|
|
1987
|
+
const hintText = document.createElement("div");
|
|
1988
|
+
hintText.className = "text-center text-xs text-gray-500 mt-2";
|
|
1989
|
+
hintText.textContent = buildHintLine();
|
|
1990
|
+
container.appendChild(hintText);
|
|
1748
1991
|
}
|
|
1749
|
-
function renderThumbnailError(slot, iconSize = "w-12 h-12") {
|
|
1992
|
+
function renderThumbnailError(slot, state, iconSize = "w-12 h-12") {
|
|
1750
1993
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1751
|
-
<svg class="${iconSize} text-red-400" fill="currentColor" viewBox="0 0 24 24">
|
|
1994
|
+
<svg class="${escapeHtml(iconSize)} text-red-400" fill="currentColor" viewBox="0 0 24 24">
|
|
1752
1995
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
|
|
1753
1996
|
</svg>
|
|
1754
|
-
<div class="text-xs mt-1 text-red-600"
|
|
1997
|
+
<div class="text-xs mt-1 text-red-600">${escapeHtml(t("previewError", state))}</div>
|
|
1755
1998
|
</div>`;
|
|
1756
1999
|
}
|
|
1757
2000
|
async function renderThumbnailForResource(slot, rid, meta, state) {
|
|
@@ -1789,7 +2032,7 @@ async function renderThumbnailForResource(slot, rid, meta, state) {
|
|
|
1789
2032
|
if (state.config.onThumbnailError) {
|
|
1790
2033
|
state.config.onThumbnailError(err, rid);
|
|
1791
2034
|
}
|
|
1792
|
-
renderThumbnailError(slot);
|
|
2035
|
+
renderThumbnailError(slot, state);
|
|
1793
2036
|
}
|
|
1794
2037
|
} else {
|
|
1795
2038
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
@@ -1838,7 +2081,7 @@ async function renderThumbnailForResource(slot, rid, meta, state) {
|
|
|
1838
2081
|
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
1839
2082
|
<path d="M8 5v14l11-7z"/>
|
|
1840
2083
|
</svg>
|
|
1841
|
-
<div class="text-xs mt-1">${(meta == null ? void 0 : meta.name) || "Video"}</div>
|
|
2084
|
+
<div class="text-xs mt-1">${escapeHtml((meta == null ? void 0 : meta.name) || "Video")}</div>
|
|
1842
2085
|
</div>`;
|
|
1843
2086
|
}
|
|
1844
2087
|
} catch (error) {
|
|
@@ -1846,30 +2089,32 @@ async function renderThumbnailForResource(slot, rid, meta, state) {
|
|
|
1846
2089
|
if (state.config.onThumbnailError) {
|
|
1847
2090
|
state.config.onThumbnailError(err, rid);
|
|
1848
2091
|
}
|
|
1849
|
-
renderThumbnailError(slot, "w-8 h-8");
|
|
2092
|
+
renderThumbnailError(slot, state, "w-8 h-8");
|
|
1850
2093
|
}
|
|
1851
2094
|
} else {
|
|
1852
2095
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1853
2096
|
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
1854
2097
|
<path d="M8 5v14l11-7z"/>
|
|
1855
2098
|
</svg>
|
|
1856
|
-
<div class="text-xs mt-1">${(meta == null ? void 0 : meta.name) || "Video"}</div>
|
|
2099
|
+
<div class="text-xs mt-1">${escapeHtml((meta == null ? void 0 : meta.name) || "Video")}</div>
|
|
1857
2100
|
</div>`;
|
|
1858
2101
|
}
|
|
1859
2102
|
} else {
|
|
1860
2103
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1861
2104
|
<div class="text-2xl mb-1">\u{1F4C1}</div>
|
|
1862
|
-
<div class="text-xs">${(meta == null ? void 0 : meta.name) || "File"}</div>
|
|
2105
|
+
<div class="text-xs">${escapeHtml((meta == null ? void 0 : meta.name) || "File")}</div>
|
|
1863
2106
|
</div>`;
|
|
1864
2107
|
}
|
|
1865
2108
|
}
|
|
1866
|
-
function setEmptyFileContainer(fileContainer, state) {
|
|
2109
|
+
function setEmptyFileContainer(fileContainer, state, hint) {
|
|
2110
|
+
const hintHtml = hint ? `<div class="text-xs text-gray-500 mt-1">${escapeHtml(hint)}</div>` : "";
|
|
1867
2111
|
fileContainer.innerHTML = `
|
|
1868
2112
|
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1869
2113
|
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1870
2114
|
<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"/>
|
|
1871
2115
|
</svg>
|
|
1872
|
-
<div class="text-sm text-center">${t("clickDragText", state)}</div>
|
|
2116
|
+
<div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
|
|
2117
|
+
${hintHtml}
|
|
1873
2118
|
</div>
|
|
1874
2119
|
`;
|
|
1875
2120
|
}
|
|
@@ -2134,13 +2379,13 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2134
2379
|
console.error("Failed to render file preview:", err);
|
|
2135
2380
|
const emptyState = document.createElement("div");
|
|
2136
2381
|
emptyState.className = "aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500";
|
|
2137
|
-
emptyState.innerHTML = `<div class="text-center"
|
|
2382
|
+
emptyState.innerHTML = `<div class="text-center">${escapeHtml(t("previewUnavailable", state))}</div>`;
|
|
2138
2383
|
wrapper.appendChild(emptyState);
|
|
2139
2384
|
});
|
|
2140
2385
|
} else {
|
|
2141
2386
|
const emptyState = document.createElement("div");
|
|
2142
2387
|
emptyState.className = "aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500";
|
|
2143
|
-
emptyState.innerHTML = `<div class="text-center">${t("noFileSelected", state)}</div>`;
|
|
2388
|
+
emptyState.innerHTML = `<div class="text-center">${escapeHtml(t("noFileSelected", state))}</div>`;
|
|
2144
2389
|
wrapper.appendChild(emptyState);
|
|
2145
2390
|
}
|
|
2146
2391
|
} else {
|
|
@@ -2184,7 +2429,8 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2184
2429
|
}
|
|
2185
2430
|
);
|
|
2186
2431
|
} else {
|
|
2187
|
-
|
|
2432
|
+
const hint = makeFieldHint(element, state);
|
|
2433
|
+
setEmptyFileContainer(fileContainer, state, hint);
|
|
2188
2434
|
}
|
|
2189
2435
|
fileContainer.onclick = fileUploadHandler;
|
|
2190
2436
|
setupDragAndDrop(fileContainer, dragHandler);
|
|
@@ -2203,18 +2449,6 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2203
2449
|
};
|
|
2204
2450
|
fileWrapper.appendChild(fileContainer);
|
|
2205
2451
|
fileWrapper.appendChild(picker);
|
|
2206
|
-
const uploadText = document.createElement("p");
|
|
2207
|
-
uploadText.className = "text-xs text-gray-600 mt-2 text-center";
|
|
2208
|
-
uploadText.innerHTML = `<span class="underline cursor-pointer">${t("uploadText", state)}</span> ${t("dragDropTextSingle", state)}`;
|
|
2209
|
-
const uploadSpan = uploadText.querySelector("span");
|
|
2210
|
-
if (uploadSpan) {
|
|
2211
|
-
uploadSpan.onclick = () => picker.click();
|
|
2212
|
-
}
|
|
2213
|
-
fileWrapper.appendChild(uploadText);
|
|
2214
|
-
const fileHint = document.createElement("p");
|
|
2215
|
-
fileHint.className = "text-xs text-gray-500 mt-1 text-center";
|
|
2216
|
-
fileHint.textContent = makeFieldHint(element);
|
|
2217
|
-
fileWrapper.appendChild(fileHint);
|
|
2218
2452
|
wrapper.appendChild(fileWrapper);
|
|
2219
2453
|
}
|
|
2220
2454
|
}
|
|
@@ -2234,18 +2468,24 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
|
2234
2468
|
});
|
|
2235
2469
|
});
|
|
2236
2470
|
} else {
|
|
2237
|
-
resultsWrapper.innerHTML = `<div class="aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500"><div class="text-center">${t("noFilesSelected", state)}</div></div>`;
|
|
2471
|
+
resultsWrapper.innerHTML = `<div class="aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500"><div class="text-center">${escapeHtml(t("noFilesSelected", state))}</div></div>`;
|
|
2238
2472
|
}
|
|
2239
2473
|
wrapper.appendChild(resultsWrapper);
|
|
2240
2474
|
} else {
|
|
2241
2475
|
let updateFilesList2 = function() {
|
|
2242
|
-
renderResourcePills(
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2476
|
+
renderResourcePills(
|
|
2477
|
+
list,
|
|
2478
|
+
initialFiles,
|
|
2479
|
+
state,
|
|
2480
|
+
(ridToRemove) => {
|
|
2481
|
+
const index = initialFiles.indexOf(ridToRemove);
|
|
2482
|
+
if (index > -1) {
|
|
2483
|
+
initialFiles.splice(index, 1);
|
|
2484
|
+
}
|
|
2485
|
+
updateFilesList2();
|
|
2486
|
+
},
|
|
2487
|
+
filesFieldHint
|
|
2488
|
+
);
|
|
2249
2489
|
};
|
|
2250
2490
|
const filesWrapper = document.createElement("div");
|
|
2251
2491
|
filesWrapper.className = "space-y-2";
|
|
@@ -2263,6 +2503,7 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
|
2263
2503
|
list.className = "files-list";
|
|
2264
2504
|
const initialFiles = ctx.prefill[element.key] || [];
|
|
2265
2505
|
addPrefillFilesToIndex(initialFiles, state);
|
|
2506
|
+
const filesFieldHint = makeFieldHint(element, state);
|
|
2266
2507
|
updateFilesList2();
|
|
2267
2508
|
setupFilesDropHandler(
|
|
2268
2509
|
filesContainer,
|
|
@@ -2283,10 +2524,6 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
|
2283
2524
|
filesContainer.appendChild(list);
|
|
2284
2525
|
filesWrapper.appendChild(filesContainer);
|
|
2285
2526
|
filesWrapper.appendChild(filesPicker);
|
|
2286
|
-
const filesHint = document.createElement("p");
|
|
2287
|
-
filesHint.className = "text-xs text-gray-500 mt-1 text-center";
|
|
2288
|
-
filesHint.textContent = makeFieldHint(element);
|
|
2289
|
-
filesWrapper.appendChild(filesHint);
|
|
2290
2527
|
wrapper.appendChild(filesWrapper);
|
|
2291
2528
|
}
|
|
2292
2529
|
}
|
|
@@ -2308,7 +2545,7 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2308
2545
|
});
|
|
2309
2546
|
});
|
|
2310
2547
|
} else {
|
|
2311
|
-
resultsWrapper.innerHTML = `<div class="aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500"><div class="text-center">${t("noFilesSelected", state)}</div></div>`;
|
|
2548
|
+
resultsWrapper.innerHTML = `<div class="aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500"><div class="text-center">${escapeHtml(t("noFilesSelected", state))}</div></div>`;
|
|
2312
2549
|
}
|
|
2313
2550
|
wrapper.appendChild(resultsWrapper);
|
|
2314
2551
|
} else {
|
|
@@ -2328,19 +2565,24 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2328
2565
|
filesWrapper.appendChild(filesContainer);
|
|
2329
2566
|
const initialFiles = Array.isArray(ctx.prefill[element.key]) ? [...ctx.prefill[element.key]] : [];
|
|
2330
2567
|
addPrefillFilesToIndex(initialFiles, state);
|
|
2568
|
+
const multipleFilesHint = makeFieldHint(element, state);
|
|
2569
|
+
const buildCountInfo = () => {
|
|
2570
|
+
const countText = initialFiles.length === 1 ? t("fileCountSingle", state, { count: initialFiles.length }) : t("fileCountPlural", state, { count: initialFiles.length });
|
|
2571
|
+
const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
|
|
2572
|
+
return countText + minMaxText;
|
|
2573
|
+
};
|
|
2331
2574
|
const updateFilesDisplay = () => {
|
|
2332
|
-
renderResourcePills(
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
filesWrapper.appendChild(countInfo);
|
|
2575
|
+
renderResourcePills(
|
|
2576
|
+
filesContainer,
|
|
2577
|
+
initialFiles,
|
|
2578
|
+
state,
|
|
2579
|
+
(index) => {
|
|
2580
|
+
initialFiles.splice(initialFiles.indexOf(index), 1);
|
|
2581
|
+
updateFilesDisplay();
|
|
2582
|
+
},
|
|
2583
|
+
multipleFilesHint,
|
|
2584
|
+
buildCountInfo()
|
|
2585
|
+
);
|
|
2344
2586
|
};
|
|
2345
2587
|
setupFilesDropHandler(
|
|
2346
2588
|
filesContainer,
|
|
@@ -2370,16 +2612,17 @@ function validateFileElement(element, key, context) {
|
|
|
2370
2612
|
const validateFileCount = (key2, resourceIds, element2) => {
|
|
2371
2613
|
var _a2, _b;
|
|
2372
2614
|
if (skipValidation) return;
|
|
2615
|
+
const { state } = context;
|
|
2373
2616
|
const minFiles = "minCount" in element2 ? (_a2 = element2.minCount) != null ? _a2 : 0 : 0;
|
|
2374
2617
|
const maxFiles = "maxCount" in element2 ? (_b = element2.maxCount) != null ? _b : Infinity : Infinity;
|
|
2375
2618
|
if (element2.required && resourceIds.length === 0) {
|
|
2376
|
-
errors.push(`${key2}: required`);
|
|
2619
|
+
errors.push(`${key2}: ${t("required", state)}`);
|
|
2377
2620
|
}
|
|
2378
2621
|
if (resourceIds.length < minFiles) {
|
|
2379
|
-
errors.push(`${key2}:
|
|
2622
|
+
errors.push(`${key2}: ${t("minFiles", state, { min: minFiles })}`);
|
|
2380
2623
|
}
|
|
2381
2624
|
if (resourceIds.length > maxFiles) {
|
|
2382
|
-
errors.push(`${key2}:
|
|
2625
|
+
errors.push(`${key2}: ${t("maxFiles", state, { max: maxFiles })}`);
|
|
2383
2626
|
}
|
|
2384
2627
|
};
|
|
2385
2628
|
if (isMultipleField) {
|
|
@@ -2407,7 +2650,7 @@ function validateFileElement(element, key, context) {
|
|
|
2407
2650
|
);
|
|
2408
2651
|
const rid = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
2409
2652
|
if (!skipValidation && element.required && rid === "") {
|
|
2410
|
-
errors.push(`${key}: required`);
|
|
2653
|
+
errors.push(`${key}: ${t("required", context.state)}`);
|
|
2411
2654
|
return { value: null, errors };
|
|
2412
2655
|
}
|
|
2413
2656
|
return { value: rid || null, errors };
|
|
@@ -2650,14 +2893,16 @@ function renderColourElement(element, ctx, wrapper, pathKey) {
|
|
|
2650
2893
|
const editUI = createEditColourUI(initialValue, pathKey, ctx);
|
|
2651
2894
|
wrapper.appendChild(editUI);
|
|
2652
2895
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2896
|
+
if (!state.config.readonly) {
|
|
2897
|
+
const colourHint = document.createElement("p");
|
|
2898
|
+
colourHint.className = "mt-1";
|
|
2899
|
+
colourHint.style.cssText = `
|
|
2900
|
+
font-size: var(--fb-font-size-small);
|
|
2901
|
+
color: var(--fb-text-secondary-color);
|
|
2902
|
+
`;
|
|
2903
|
+
colourHint.textContent = makeFieldHint(element, state);
|
|
2904
|
+
wrapper.appendChild(colourHint);
|
|
2905
|
+
}
|
|
2661
2906
|
}
|
|
2662
2907
|
function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
2663
2908
|
var _a, _b;
|
|
@@ -2747,48 +2992,65 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
|
2747
2992
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
2748
2993
|
});
|
|
2749
2994
|
}
|
|
2995
|
+
let addRow = null;
|
|
2996
|
+
let countDisplay = null;
|
|
2997
|
+
if (!state.config.readonly) {
|
|
2998
|
+
addRow = document.createElement("div");
|
|
2999
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
3000
|
+
const addBtn = document.createElement("button");
|
|
3001
|
+
addBtn.type = "button";
|
|
3002
|
+
addBtn.className = "add-colour-btn px-3 py-1 rounded";
|
|
3003
|
+
addBtn.style.cssText = `
|
|
3004
|
+
color: var(--fb-primary-color);
|
|
3005
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
3006
|
+
background-color: transparent;
|
|
3007
|
+
font-size: var(--fb-font-size);
|
|
3008
|
+
transition: all var(--fb-transition-duration);
|
|
3009
|
+
`;
|
|
3010
|
+
addBtn.textContent = "+";
|
|
3011
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
3012
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3013
|
+
});
|
|
3014
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
3015
|
+
addBtn.style.backgroundColor = "transparent";
|
|
3016
|
+
});
|
|
3017
|
+
addBtn.onclick = () => {
|
|
3018
|
+
const defaultColour = element.default || "#000000";
|
|
3019
|
+
values.push(defaultColour);
|
|
3020
|
+
addColourItem(defaultColour);
|
|
3021
|
+
updateAddButton();
|
|
3022
|
+
updateRemoveButtons();
|
|
3023
|
+
};
|
|
3024
|
+
countDisplay = document.createElement("span");
|
|
3025
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
3026
|
+
addRow.appendChild(addBtn);
|
|
3027
|
+
addRow.appendChild(countDisplay);
|
|
3028
|
+
wrapper.appendChild(addRow);
|
|
3029
|
+
}
|
|
2750
3030
|
function updateAddButton() {
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
if (
|
|
2754
|
-
const
|
|
2755
|
-
addBtn.
|
|
2756
|
-
addBtn.
|
|
2757
|
-
addBtn.style.
|
|
2758
|
-
color: var(--fb-primary-color);
|
|
2759
|
-
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
2760
|
-
background-color: transparent;
|
|
2761
|
-
font-size: var(--fb-font-size);
|
|
2762
|
-
transition: all var(--fb-transition-duration);
|
|
2763
|
-
`;
|
|
2764
|
-
addBtn.textContent = "+";
|
|
2765
|
-
addBtn.addEventListener("mouseenter", () => {
|
|
2766
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
2767
|
-
});
|
|
2768
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
2769
|
-
addBtn.style.backgroundColor = "transparent";
|
|
2770
|
-
});
|
|
2771
|
-
addBtn.onclick = () => {
|
|
2772
|
-
const defaultColour = element.default || "#000000";
|
|
2773
|
-
values.push(defaultColour);
|
|
2774
|
-
addColourItem(defaultColour);
|
|
2775
|
-
updateAddButton();
|
|
2776
|
-
updateRemoveButtons();
|
|
2777
|
-
};
|
|
2778
|
-
wrapper.appendChild(addBtn);
|
|
3031
|
+
if (!addRow || !countDisplay) return;
|
|
3032
|
+
const addBtn = addRow.querySelector(".add-colour-btn");
|
|
3033
|
+
if (addBtn) {
|
|
3034
|
+
const disabled = values.length >= maxCount;
|
|
3035
|
+
addBtn.disabled = disabled;
|
|
3036
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
3037
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
2779
3038
|
}
|
|
3039
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
2780
3040
|
}
|
|
2781
3041
|
values.forEach((value) => addColourItem(value));
|
|
2782
3042
|
updateAddButton();
|
|
2783
3043
|
updateRemoveButtons();
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
3044
|
+
if (!state.config.readonly) {
|
|
3045
|
+
const hint = document.createElement("p");
|
|
3046
|
+
hint.className = "mt-1";
|
|
3047
|
+
hint.style.cssText = `
|
|
3048
|
+
font-size: var(--fb-font-size-small);
|
|
3049
|
+
color: var(--fb-text-secondary-color);
|
|
3050
|
+
`;
|
|
3051
|
+
hint.textContent = makeFieldHint(element, state);
|
|
3052
|
+
wrapper.appendChild(hint);
|
|
3053
|
+
}
|
|
2792
3054
|
}
|
|
2793
3055
|
function validateColourElement(element, key, context) {
|
|
2794
3056
|
var _a, _b, _c;
|
|
@@ -2828,10 +3090,12 @@ function validateColourElement(element, key, context) {
|
|
|
2828
3090
|
}
|
|
2829
3091
|
};
|
|
2830
3092
|
const validateColourValue = (input, val, fieldKey) => {
|
|
3093
|
+
const { state } = context;
|
|
2831
3094
|
if (!val) {
|
|
2832
3095
|
if (!skipValidation && element.required) {
|
|
2833
|
-
|
|
2834
|
-
|
|
3096
|
+
const msg = t("required", state);
|
|
3097
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
3098
|
+
markValidity(input, msg);
|
|
2835
3099
|
return "";
|
|
2836
3100
|
}
|
|
2837
3101
|
markValidity(input, null);
|
|
@@ -2839,8 +3103,9 @@ function validateColourElement(element, key, context) {
|
|
|
2839
3103
|
}
|
|
2840
3104
|
const normalized = normalizeColourValue(val);
|
|
2841
3105
|
if (!skipValidation && !isValidHexColour(normalized)) {
|
|
2842
|
-
|
|
2843
|
-
|
|
3106
|
+
const msg = t("invalidHexColour", state);
|
|
3107
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
3108
|
+
markValidity(input, msg);
|
|
2844
3109
|
return val;
|
|
2845
3110
|
}
|
|
2846
3111
|
markValidity(input, null);
|
|
@@ -2856,17 +3121,18 @@ function validateColourElement(element, key, context) {
|
|
|
2856
3121
|
values.push(validated);
|
|
2857
3122
|
});
|
|
2858
3123
|
if (!skipValidation) {
|
|
3124
|
+
const { state } = context;
|
|
2859
3125
|
const minCount = (_a = element.minCount) != null ? _a : 1;
|
|
2860
3126
|
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
2861
3127
|
const filteredValues = values.filter((v) => v !== "");
|
|
2862
3128
|
if (element.required && filteredValues.length === 0) {
|
|
2863
|
-
errors.push(`${key}: required`);
|
|
3129
|
+
errors.push(`${key}: ${t("required", state)}`);
|
|
2864
3130
|
}
|
|
2865
3131
|
if (filteredValues.length < minCount) {
|
|
2866
|
-
errors.push(`${key}:
|
|
3132
|
+
errors.push(`${key}: ${t("minItems", state, { min: minCount })}`);
|
|
2867
3133
|
}
|
|
2868
3134
|
if (filteredValues.length > maxCount) {
|
|
2869
|
-
errors.push(`${key}:
|
|
3135
|
+
errors.push(`${key}: ${t("maxItems", state, { max: maxCount })}`);
|
|
2870
3136
|
}
|
|
2871
3137
|
}
|
|
2872
3138
|
return { value: values, errors };
|
|
@@ -2876,8 +3142,9 @@ function validateColourElement(element, key, context) {
|
|
|
2876
3142
|
);
|
|
2877
3143
|
const val = (_c = hexInput == null ? void 0 : hexInput.value) != null ? _c : "";
|
|
2878
3144
|
if (!skipValidation && element.required && val === "") {
|
|
2879
|
-
|
|
2880
|
-
|
|
3145
|
+
const msg = t("required", context.state);
|
|
3146
|
+
errors.push(`${key}: ${msg}`);
|
|
3147
|
+
markValidity(hexInput, msg);
|
|
2881
3148
|
return { value: "", errors };
|
|
2882
3149
|
}
|
|
2883
3150
|
const validated = validateColourValue(hexInput, val, key);
|
|
@@ -2970,13 +3237,15 @@ function alignToStep(value, step) {
|
|
|
2970
3237
|
function createSliderUI(value, pathKey, element, ctx, readonly) {
|
|
2971
3238
|
var _a;
|
|
2972
3239
|
const container = document.createElement("div");
|
|
2973
|
-
container.className = "slider-container
|
|
2974
|
-
const
|
|
2975
|
-
|
|
3240
|
+
container.className = "slider-container";
|
|
3241
|
+
const mainRow = document.createElement("div");
|
|
3242
|
+
mainRow.className = "flex items-start gap-3";
|
|
3243
|
+
const sliderSection = document.createElement("div");
|
|
3244
|
+
sliderSection.className = "flex-1";
|
|
2976
3245
|
const slider = document.createElement("input");
|
|
2977
3246
|
slider.type = "range";
|
|
2978
3247
|
slider.name = pathKey;
|
|
2979
|
-
slider.className = "slider-input
|
|
3248
|
+
slider.className = "slider-input w-full";
|
|
2980
3249
|
slider.disabled = readonly;
|
|
2981
3250
|
const scale = element.scale || "linear";
|
|
2982
3251
|
const min = element.min;
|
|
@@ -3014,25 +3283,13 @@ function createSliderUI(value, pathKey, element, ctx, readonly) {
|
|
|
3014
3283
|
cursor: ${readonly ? "not-allowed" : "pointer"};
|
|
3015
3284
|
opacity: ${readonly ? "0.6" : "1"};
|
|
3016
3285
|
`;
|
|
3017
|
-
|
|
3018
|
-
valueDisplay.className = "slider-value";
|
|
3019
|
-
valueDisplay.style.cssText = `
|
|
3020
|
-
min-width: 60px;
|
|
3021
|
-
text-align: right;
|
|
3022
|
-
font-size: var(--fb-font-size);
|
|
3023
|
-
color: var(--fb-text-color);
|
|
3024
|
-
font-family: var(--fb-font-family-mono, monospace);
|
|
3025
|
-
font-weight: 500;
|
|
3026
|
-
`;
|
|
3027
|
-
valueDisplay.textContent = value.toFixed(step < 1 ? 2 : 0);
|
|
3028
|
-
sliderRow.appendChild(slider);
|
|
3029
|
-
sliderRow.appendChild(valueDisplay);
|
|
3030
|
-
container.appendChild(sliderRow);
|
|
3286
|
+
sliderSection.appendChild(slider);
|
|
3031
3287
|
const labelsRow = document.createElement("div");
|
|
3032
3288
|
labelsRow.className = "flex justify-between";
|
|
3033
3289
|
labelsRow.style.cssText = `
|
|
3034
3290
|
font-size: var(--fb-font-size-small);
|
|
3035
3291
|
color: var(--fb-text-secondary-color);
|
|
3292
|
+
margin-top: 4px;
|
|
3036
3293
|
`;
|
|
3037
3294
|
const minLabel = document.createElement("span");
|
|
3038
3295
|
minLabel.textContent = min.toString();
|
|
@@ -3040,7 +3297,22 @@ function createSliderUI(value, pathKey, element, ctx, readonly) {
|
|
|
3040
3297
|
maxLabel.textContent = max.toString();
|
|
3041
3298
|
labelsRow.appendChild(minLabel);
|
|
3042
3299
|
labelsRow.appendChild(maxLabel);
|
|
3043
|
-
|
|
3300
|
+
sliderSection.appendChild(labelsRow);
|
|
3301
|
+
const valueDisplay = document.createElement("span");
|
|
3302
|
+
valueDisplay.className = "slider-value";
|
|
3303
|
+
valueDisplay.style.cssText = `
|
|
3304
|
+
min-width: 60px;
|
|
3305
|
+
text-align: right;
|
|
3306
|
+
font-size: var(--fb-font-size);
|
|
3307
|
+
color: var(--fb-text-color);
|
|
3308
|
+
font-family: var(--fb-font-family-mono, monospace);
|
|
3309
|
+
font-weight: 500;
|
|
3310
|
+
padding-top: 2px;
|
|
3311
|
+
`;
|
|
3312
|
+
valueDisplay.textContent = value.toFixed(step < 1 ? 2 : 0);
|
|
3313
|
+
mainRow.appendChild(sliderSection);
|
|
3314
|
+
mainRow.appendChild(valueDisplay);
|
|
3315
|
+
container.appendChild(mainRow);
|
|
3044
3316
|
if (!readonly) {
|
|
3045
3317
|
const updateValue = () => {
|
|
3046
3318
|
let displayValue;
|
|
@@ -3095,14 +3367,16 @@ function renderSliderElement(element, ctx, wrapper, pathKey) {
|
|
|
3095
3367
|
state.config.readonly
|
|
3096
3368
|
);
|
|
3097
3369
|
wrapper.appendChild(sliderUI);
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3370
|
+
if (!state.config.readonly) {
|
|
3371
|
+
const hint = document.createElement("p");
|
|
3372
|
+
hint.className = "mt-1";
|
|
3373
|
+
hint.style.cssText = `
|
|
3374
|
+
font-size: var(--fb-font-size-small);
|
|
3375
|
+
color: var(--fb-text-secondary-color);
|
|
3376
|
+
`;
|
|
3377
|
+
hint.textContent = makeFieldHint(element, state);
|
|
3378
|
+
wrapper.appendChild(hint);
|
|
3379
|
+
}
|
|
3106
3380
|
}
|
|
3107
3381
|
function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
3108
3382
|
var _a, _b;
|
|
@@ -3204,47 +3478,64 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
|
3204
3478
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
3205
3479
|
});
|
|
3206
3480
|
}
|
|
3481
|
+
let addRow = null;
|
|
3482
|
+
let countDisplay = null;
|
|
3483
|
+
if (!state.config.readonly) {
|
|
3484
|
+
addRow = document.createElement("div");
|
|
3485
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
3486
|
+
const addBtn = document.createElement("button");
|
|
3487
|
+
addBtn.type = "button";
|
|
3488
|
+
addBtn.className = "add-slider-btn px-3 py-1 rounded";
|
|
3489
|
+
addBtn.style.cssText = `
|
|
3490
|
+
color: var(--fb-primary-color);
|
|
3491
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
3492
|
+
background-color: transparent;
|
|
3493
|
+
font-size: var(--fb-font-size);
|
|
3494
|
+
transition: all var(--fb-transition-duration);
|
|
3495
|
+
`;
|
|
3496
|
+
addBtn.textContent = "+";
|
|
3497
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
3498
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3499
|
+
});
|
|
3500
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
3501
|
+
addBtn.style.backgroundColor = "transparent";
|
|
3502
|
+
});
|
|
3503
|
+
addBtn.onclick = () => {
|
|
3504
|
+
values.push(defaultValue);
|
|
3505
|
+
addSliderItem(defaultValue);
|
|
3506
|
+
updateAddButton();
|
|
3507
|
+
updateRemoveButtons();
|
|
3508
|
+
};
|
|
3509
|
+
countDisplay = document.createElement("span");
|
|
3510
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
3511
|
+
addRow.appendChild(addBtn);
|
|
3512
|
+
addRow.appendChild(countDisplay);
|
|
3513
|
+
wrapper.appendChild(addRow);
|
|
3514
|
+
}
|
|
3207
3515
|
function updateAddButton() {
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
if (
|
|
3211
|
-
const
|
|
3212
|
-
addBtn.
|
|
3213
|
-
addBtn.
|
|
3214
|
-
addBtn.style.
|
|
3215
|
-
color: var(--fb-primary-color);
|
|
3216
|
-
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
3217
|
-
background-color: transparent;
|
|
3218
|
-
font-size: var(--fb-font-size);
|
|
3219
|
-
transition: all var(--fb-transition-duration);
|
|
3220
|
-
`;
|
|
3221
|
-
addBtn.textContent = "+";
|
|
3222
|
-
addBtn.addEventListener("mouseenter", () => {
|
|
3223
|
-
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3224
|
-
});
|
|
3225
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
3226
|
-
addBtn.style.backgroundColor = "transparent";
|
|
3227
|
-
});
|
|
3228
|
-
addBtn.onclick = () => {
|
|
3229
|
-
values.push(defaultValue);
|
|
3230
|
-
addSliderItem(defaultValue);
|
|
3231
|
-
updateAddButton();
|
|
3232
|
-
updateRemoveButtons();
|
|
3233
|
-
};
|
|
3234
|
-
wrapper.appendChild(addBtn);
|
|
3516
|
+
if (!addRow || !countDisplay) return;
|
|
3517
|
+
const addBtn = addRow.querySelector(".add-slider-btn");
|
|
3518
|
+
if (addBtn) {
|
|
3519
|
+
const disabled = values.length >= maxCount;
|
|
3520
|
+
addBtn.disabled = disabled;
|
|
3521
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
3522
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
3235
3523
|
}
|
|
3524
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
3236
3525
|
}
|
|
3237
3526
|
values.forEach((value) => addSliderItem(value));
|
|
3238
3527
|
updateAddButton();
|
|
3239
3528
|
updateRemoveButtons();
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3529
|
+
if (!state.config.readonly) {
|
|
3530
|
+
const hint = document.createElement("p");
|
|
3531
|
+
hint.className = "mt-1";
|
|
3532
|
+
hint.style.cssText = `
|
|
3533
|
+
font-size: var(--fb-font-size-small);
|
|
3534
|
+
color: var(--fb-text-secondary-color);
|
|
3535
|
+
`;
|
|
3536
|
+
hint.textContent = makeFieldHint(element, state);
|
|
3537
|
+
wrapper.appendChild(hint);
|
|
3538
|
+
}
|
|
3248
3539
|
}
|
|
3249
3540
|
function validateSliderElement(element, key, context) {
|
|
3250
3541
|
var _a, _b, _c;
|
|
@@ -3302,11 +3593,13 @@ function validateSliderElement(element, key, context) {
|
|
|
3302
3593
|
}
|
|
3303
3594
|
};
|
|
3304
3595
|
const validateSliderValue = (slider, fieldKey) => {
|
|
3596
|
+
const { state } = context;
|
|
3305
3597
|
const rawValue = slider.value;
|
|
3306
3598
|
if (!rawValue) {
|
|
3307
3599
|
if (!skipValidation && element.required) {
|
|
3308
|
-
|
|
3309
|
-
|
|
3600
|
+
const msg = t("required", state);
|
|
3601
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
3602
|
+
markValidity(slider, msg);
|
|
3310
3603
|
return null;
|
|
3311
3604
|
}
|
|
3312
3605
|
markValidity(slider, null);
|
|
@@ -3323,13 +3616,15 @@ function validateSliderElement(element, key, context) {
|
|
|
3323
3616
|
}
|
|
3324
3617
|
if (!skipValidation) {
|
|
3325
3618
|
if (value < min) {
|
|
3326
|
-
|
|
3327
|
-
|
|
3619
|
+
const msg = t("minValue", state, { min });
|
|
3620
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
3621
|
+
markValidity(slider, msg);
|
|
3328
3622
|
return value;
|
|
3329
3623
|
}
|
|
3330
3624
|
if (value > max) {
|
|
3331
|
-
|
|
3332
|
-
|
|
3625
|
+
const msg = t("maxValue", state, { max });
|
|
3626
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
3627
|
+
markValidity(slider, msg);
|
|
3333
3628
|
return value;
|
|
3334
3629
|
}
|
|
3335
3630
|
}
|
|
@@ -3346,17 +3641,18 @@ function validateSliderElement(element, key, context) {
|
|
|
3346
3641
|
values.push(value);
|
|
3347
3642
|
});
|
|
3348
3643
|
if (!skipValidation) {
|
|
3644
|
+
const { state } = context;
|
|
3349
3645
|
const minCount = (_b = element.minCount) != null ? _b : 1;
|
|
3350
3646
|
const maxCount = (_c = element.maxCount) != null ? _c : Infinity;
|
|
3351
3647
|
const filteredValues = values.filter((v) => v !== null);
|
|
3352
3648
|
if (element.required && filteredValues.length === 0) {
|
|
3353
|
-
errors.push(`${key}: required`);
|
|
3649
|
+
errors.push(`${key}: ${t("required", state)}`);
|
|
3354
3650
|
}
|
|
3355
3651
|
if (filteredValues.length < minCount) {
|
|
3356
|
-
errors.push(`${key}:
|
|
3652
|
+
errors.push(`${key}: ${t("minItems", state, { min: minCount })}`);
|
|
3357
3653
|
}
|
|
3358
3654
|
if (filteredValues.length > maxCount) {
|
|
3359
|
-
errors.push(`${key}:
|
|
3655
|
+
errors.push(`${key}: ${t("maxItems", state, { max: maxCount })}`);
|
|
3360
3656
|
}
|
|
3361
3657
|
}
|
|
3362
3658
|
return { value: values, errors };
|
|
@@ -3366,7 +3662,7 @@ function validateSliderElement(element, key, context) {
|
|
|
3366
3662
|
);
|
|
3367
3663
|
if (!slider) {
|
|
3368
3664
|
if (!skipValidation && element.required) {
|
|
3369
|
-
errors.push(`${key}: required`);
|
|
3665
|
+
errors.push(`${key}: ${t("required", context.state)}`);
|
|
3370
3666
|
}
|
|
3371
3667
|
return { value: null, errors };
|
|
3372
3668
|
}
|
|
@@ -3520,10 +3816,6 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
3520
3816
|
const containerWrap = document.createElement("div");
|
|
3521
3817
|
containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
|
|
3522
3818
|
containerWrap.setAttribute("data-container", pathKey);
|
|
3523
|
-
const header = document.createElement("div");
|
|
3524
|
-
header.className = "flex justify-between items-center mb-4";
|
|
3525
|
-
const left = document.createElement("div");
|
|
3526
|
-
left.className = "flex-1";
|
|
3527
3819
|
const itemsWrap = document.createElement("div");
|
|
3528
3820
|
const columns = element.columns || 1;
|
|
3529
3821
|
if (columns === 1) {
|
|
@@ -3531,8 +3823,6 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
3531
3823
|
} else {
|
|
3532
3824
|
itemsWrap.className = `grid grid-cols-${columns} gap-4`;
|
|
3533
3825
|
}
|
|
3534
|
-
containerWrap.appendChild(header);
|
|
3535
|
-
header.appendChild(left);
|
|
3536
3826
|
if (!ctx.state.config.readonly) {
|
|
3537
3827
|
const hintsElement = createPrefillHints(element, pathKey);
|
|
3538
3828
|
if (hintsElement) {
|
|
@@ -3553,7 +3843,6 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
3553
3843
|
}
|
|
3554
3844
|
});
|
|
3555
3845
|
containerWrap.appendChild(itemsWrap);
|
|
3556
|
-
left.innerHTML = `<span>${element.label || element.key}</span>`;
|
|
3557
3846
|
wrapper.appendChild(containerWrap);
|
|
3558
3847
|
}
|
|
3559
3848
|
function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
@@ -3561,14 +3850,10 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3561
3850
|
const state = ctx.state;
|
|
3562
3851
|
const containerWrap = document.createElement("div");
|
|
3563
3852
|
containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
|
|
3564
|
-
const
|
|
3565
|
-
|
|
3566
|
-
const left = document.createElement("div");
|
|
3567
|
-
left.className = "flex-1";
|
|
3853
|
+
const countDisplay = document.createElement("span");
|
|
3854
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
3568
3855
|
const itemsWrap = document.createElement("div");
|
|
3569
3856
|
itemsWrap.className = "space-y-4";
|
|
3570
|
-
containerWrap.appendChild(header);
|
|
3571
|
-
header.appendChild(left);
|
|
3572
3857
|
if (!ctx.state.config.readonly) {
|
|
3573
3858
|
const hintsElement = createPrefillHints(element, element.key);
|
|
3574
3859
|
if (hintsElement) {
|
|
@@ -3582,7 +3867,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3582
3867
|
const createAddButton = () => {
|
|
3583
3868
|
const add = document.createElement("button");
|
|
3584
3869
|
add.type = "button";
|
|
3585
|
-
add.className = "add-container-btn
|
|
3870
|
+
add.className = "add-container-btn px-3 py-1 rounded";
|
|
3586
3871
|
add.style.cssText = `
|
|
3587
3872
|
color: var(--fb-primary-color);
|
|
3588
3873
|
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
@@ -3663,7 +3948,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3663
3948
|
existingAddBtn.style.opacity = currentCount >= max ? "0.5" : "1";
|
|
3664
3949
|
existingAddBtn.style.pointerEvents = currentCount >= max ? "none" : "auto";
|
|
3665
3950
|
}
|
|
3666
|
-
|
|
3951
|
+
countDisplay.textContent = `${currentCount}/${max === Infinity ? "\u221E" : max}`;
|
|
3667
3952
|
};
|
|
3668
3953
|
if (pre && Array.isArray(pre)) {
|
|
3669
3954
|
pre.forEach((prefillObj, idx) => {
|
|
@@ -3771,7 +4056,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3771
4056
|
}
|
|
3772
4057
|
containerWrap.appendChild(itemsWrap);
|
|
3773
4058
|
if (!state.config.readonly) {
|
|
3774
|
-
|
|
4059
|
+
const addRow = document.createElement("div");
|
|
4060
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
4061
|
+
addRow.appendChild(createAddButton());
|
|
4062
|
+
addRow.appendChild(countDisplay);
|
|
4063
|
+
containerWrap.appendChild(addRow);
|
|
3775
4064
|
}
|
|
3776
4065
|
updateAddButton();
|
|
3777
4066
|
wrapper.appendChild(containerWrap);
|
|
@@ -3797,16 +4086,17 @@ function validateContainerElement(element, key, context) {
|
|
|
3797
4086
|
const validateContainerCount = (key2, items, element2) => {
|
|
3798
4087
|
var _a, _b;
|
|
3799
4088
|
if (skipValidation) return;
|
|
4089
|
+
const { state } = context;
|
|
3800
4090
|
const minItems = "minCount" in element2 ? (_a = element2.minCount) != null ? _a : 0 : 0;
|
|
3801
4091
|
const maxItems = "maxCount" in element2 ? (_b = element2.maxCount) != null ? _b : Infinity : Infinity;
|
|
3802
4092
|
if (element2.required && items.length === 0) {
|
|
3803
|
-
errors.push(`${key2}: required`);
|
|
4093
|
+
errors.push(`${key2}: ${t("required", state)}`);
|
|
3804
4094
|
}
|
|
3805
4095
|
if (items.length < minItems) {
|
|
3806
|
-
errors.push(`${key2}:
|
|
4096
|
+
errors.push(`${key2}: ${t("minItems", state, { min: minItems })}`);
|
|
3807
4097
|
}
|
|
3808
4098
|
if (items.length > maxItems) {
|
|
3809
|
-
errors.push(`${key2}:
|
|
4099
|
+
errors.push(`${key2}: ${t("maxItems", state, { max: maxItems })}`);
|
|
3810
4100
|
}
|
|
3811
4101
|
};
|
|
3812
4102
|
if ("multiple" in element && element.multiple) {
|
|
@@ -4360,7 +4650,7 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
4360
4650
|
default: {
|
|
4361
4651
|
const unsupported = document.createElement("div");
|
|
4362
4652
|
unsupported.className = "text-red-500 text-sm";
|
|
4363
|
-
unsupported.textContent =
|
|
4653
|
+
unsupported.textContent = t("unsupportedFieldType", ctx.state, { type: element.type });
|
|
4364
4654
|
wrapper.appendChild(unsupported);
|
|
4365
4655
|
}
|
|
4366
4656
|
}
|
|
@@ -4404,31 +4694,112 @@ var defaultConfig = {
|
|
|
4404
4694
|
locale: "en",
|
|
4405
4695
|
translations: {
|
|
4406
4696
|
en: {
|
|
4407
|
-
|
|
4697
|
+
// UI texts
|
|
4408
4698
|
removeElement: "Remove",
|
|
4409
|
-
uploadText: "Upload",
|
|
4410
|
-
dragDropText: "or drag and drop files",
|
|
4411
|
-
dragDropTextSingle: "or drag and drop file",
|
|
4412
4699
|
clickDragText: "Click or drag file",
|
|
4700
|
+
clickDragTextMultiple: "Click or drag files",
|
|
4413
4701
|
noFileSelected: "No file selected",
|
|
4414
4702
|
noFilesSelected: "No files selected",
|
|
4415
|
-
downloadButton: "Download"
|
|
4703
|
+
downloadButton: "Download",
|
|
4704
|
+
changeButton: "Change",
|
|
4705
|
+
placeholderText: "Enter text",
|
|
4706
|
+
previewAlt: "Preview",
|
|
4707
|
+
previewUnavailable: "Preview unavailable",
|
|
4708
|
+
previewError: "Preview error",
|
|
4709
|
+
videoNotSupported: "Your browser does not support the video tag.",
|
|
4710
|
+
// Field hints
|
|
4711
|
+
hintLengthRange: "{min}-{max} chars",
|
|
4712
|
+
hintMaxLength: "\u2264{max} chars",
|
|
4713
|
+
hintMinLength: "\u2265{min} chars",
|
|
4714
|
+
hintValueRange: "{min}-{max}",
|
|
4715
|
+
hintMaxValue: "\u2264{max}",
|
|
4716
|
+
hintMinValue: "\u2265{min}",
|
|
4717
|
+
hintMaxSize: "\u2264{size}MB",
|
|
4718
|
+
hintFormats: "{formats}",
|
|
4719
|
+
hintRequired: "Required",
|
|
4720
|
+
hintOptional: "Optional",
|
|
4721
|
+
hintPattern: "Format: {pattern}",
|
|
4722
|
+
fileCountSingle: "{count} file",
|
|
4723
|
+
fileCountPlural: "{count} files",
|
|
4724
|
+
fileCountRange: "({min}-{max})",
|
|
4725
|
+
// Validation errors
|
|
4726
|
+
required: "Required",
|
|
4727
|
+
minItems: "Minimum {min} items required",
|
|
4728
|
+
maxItems: "Maximum {max} items allowed",
|
|
4729
|
+
minLength: "Minimum {min} characters",
|
|
4730
|
+
maxLength: "Maximum {max} characters",
|
|
4731
|
+
minValue: "Must be at least {min}",
|
|
4732
|
+
maxValue: "Must be at most {max}",
|
|
4733
|
+
patternMismatch: "Invalid format",
|
|
4734
|
+
invalidPattern: "Invalid pattern in schema",
|
|
4735
|
+
notANumber: "Must be a number",
|
|
4736
|
+
invalidHexColour: "Invalid hex color",
|
|
4737
|
+
minFiles: "Minimum {min} files required",
|
|
4738
|
+
maxFiles: "Maximum {max} files allowed",
|
|
4739
|
+
unsupportedFieldType: "Unsupported field type: {type}"
|
|
4416
4740
|
},
|
|
4417
4741
|
ru: {
|
|
4418
|
-
|
|
4742
|
+
// UI texts
|
|
4419
4743
|
removeElement: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C",
|
|
4420
|
-
uploadText: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u0435",
|
|
4421
|
-
dragDropText: "\u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B\u044B",
|
|
4422
|
-
dragDropTextSingle: "\u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B",
|
|
4423
4744
|
clickDragText: "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B",
|
|
4745
|
+
clickDragTextMultiple: "\u041D\u0430\u0436\u043C\u0438\u0442\u0435 \u0438\u043B\u0438 \u043F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B\u044B",
|
|
4424
4746
|
noFileSelected: "\u0424\u0430\u0439\u043B \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D",
|
|
4425
4747
|
noFilesSelected: "\u041D\u0435\u0442 \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
4426
|
-
downloadButton: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C"
|
|
4748
|
+
downloadButton: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C",
|
|
4749
|
+
changeButton: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C",
|
|
4750
|
+
placeholderText: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442",
|
|
4751
|
+
previewAlt: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440",
|
|
4752
|
+
previewUnavailable: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D",
|
|
4753
|
+
previewError: "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430",
|
|
4754
|
+
videoNotSupported: "\u0412\u0430\u0448 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0432\u0438\u0434\u0435\u043E.",
|
|
4755
|
+
// Field hints
|
|
4756
|
+
hintLengthRange: "{min}-{max} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
|
|
4757
|
+
hintMaxLength: "\u2264{max} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
|
|
4758
|
+
hintMinLength: "\u2265{min} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
|
|
4759
|
+
hintValueRange: "{min}-{max}",
|
|
4760
|
+
hintMaxValue: "\u2264{max}",
|
|
4761
|
+
hintMinValue: "\u2265{min}",
|
|
4762
|
+
hintMaxSize: "\u2264{size}\u041C\u0411",
|
|
4763
|
+
hintFormats: "{formats}",
|
|
4764
|
+
hintRequired: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435",
|
|
4765
|
+
hintOptional: "\u041D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435",
|
|
4766
|
+
hintPattern: "\u0424\u043E\u0440\u043C\u0430\u0442: {pattern}",
|
|
4767
|
+
fileCountSingle: "{count} \u0444\u0430\u0439\u043B",
|
|
4768
|
+
fileCountPlural: "{count} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
4769
|
+
fileCountRange: "({min}-{max})",
|
|
4770
|
+
// Validation errors
|
|
4771
|
+
required: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435",
|
|
4772
|
+
minItems: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",
|
|
4773
|
+
maxItems: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",
|
|
4774
|
+
minLength: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
|
|
4775
|
+
maxLength: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
|
|
4776
|
+
minValue: "\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u043C\u0435\u043D\u0435\u0435 {min}",
|
|
4777
|
+
maxValue: "\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u043D\u0435 \u0431\u043E\u043B\u0435\u0435 {max}",
|
|
4778
|
+
patternMismatch: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442",
|
|
4779
|
+
invalidPattern: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u043F\u0430\u0442\u0442\u0435\u0440\u043D \u0432 \u0441\u0445\u0435\u043C\u0435",
|
|
4780
|
+
notANumber: "\u0414\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0447\u0438\u0441\u043B\u043E\u043C",
|
|
4781
|
+
invalidHexColour: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0446\u0432\u0435\u0442\u0430",
|
|
4782
|
+
minFiles: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
4783
|
+
maxFiles: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
4784
|
+
unsupportedFieldType: "\u041D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0442\u0438\u043F \u043F\u043E\u043B\u044F: {type}"
|
|
4427
4785
|
}
|
|
4428
4786
|
},
|
|
4429
4787
|
theme: {}
|
|
4430
4788
|
};
|
|
4431
4789
|
function createInstanceState(config) {
|
|
4790
|
+
const mergedTranslations = {
|
|
4791
|
+
...defaultConfig.translations
|
|
4792
|
+
};
|
|
4793
|
+
if (config == null ? void 0 : config.translations) {
|
|
4794
|
+
for (const [locale, userTranslations] of Object.entries(
|
|
4795
|
+
config.translations
|
|
4796
|
+
)) {
|
|
4797
|
+
mergedTranslations[locale] = {
|
|
4798
|
+
...defaultConfig.translations[locale] || {},
|
|
4799
|
+
...userTranslations
|
|
4800
|
+
};
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4432
4803
|
return {
|
|
4433
4804
|
schema: null,
|
|
4434
4805
|
formRoot: null,
|
|
@@ -4437,7 +4808,8 @@ function createInstanceState(config) {
|
|
|
4437
4808
|
version: "1.0.0",
|
|
4438
4809
|
config: {
|
|
4439
4810
|
...defaultConfig,
|
|
4440
|
-
...config
|
|
4811
|
+
...config,
|
|
4812
|
+
translations: mergedTranslations
|
|
4441
4813
|
},
|
|
4442
4814
|
debounceTimer: null
|
|
4443
4815
|
};
|