@dmitryvim/form-builder 0.2.10 → 0.2.12
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 +141 -101
- package/dist/browser/formbuilder.v0.2.12.min.js +362 -0
- package/dist/cjs/index.cjs +793 -425
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +792 -423
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +141 -101
- 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.10.min.js +0 -322
package/dist/cjs/index.cjs
CHANGED
|
@@ -2,58 +2,87 @@
|
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
function addRequiredHint(element, parts, state) {
|
|
65
|
+
if (element.required) {
|
|
66
|
+
parts.push(t("hintRequired", state));
|
|
67
|
+
} else {
|
|
68
|
+
parts.push(t("hintOptional", state));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function addPatternHint(element, parts, state) {
|
|
72
|
+
if (element.pattern) {
|
|
73
|
+
parts.push(t("hintPattern", state, { pattern: element.pattern }));
|
|
47
74
|
}
|
|
48
75
|
}
|
|
49
|
-
function makeFieldHint(element) {
|
|
76
|
+
function makeFieldHint(element, state) {
|
|
50
77
|
const parts = [];
|
|
51
|
-
|
|
52
|
-
addLengthHint(element, parts);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
78
|
+
addRequiredHint(element, parts, state);
|
|
79
|
+
addLengthHint(element, parts, state);
|
|
80
|
+
if (element.type !== "slider") {
|
|
81
|
+
addRangeHint(element, parts, state);
|
|
82
|
+
}
|
|
83
|
+
addFileSizeHint(element, parts, state);
|
|
84
|
+
addFormatHint(element, parts, state);
|
|
85
|
+
addPatternHint(element, parts, state);
|
|
57
86
|
return parts.join(" \u2022 ");
|
|
58
87
|
}
|
|
59
88
|
function validateSchema(schema) {
|
|
@@ -191,6 +220,11 @@ function validateSchema(schema) {
|
|
|
191
220
|
function isPlainObject(obj) {
|
|
192
221
|
return obj && typeof obj === "object" && obj.constructor === Object;
|
|
193
222
|
}
|
|
223
|
+
function escapeHtml(text) {
|
|
224
|
+
const div = document.createElement("div");
|
|
225
|
+
div.textContent = text;
|
|
226
|
+
return div.innerHTML;
|
|
227
|
+
}
|
|
194
228
|
function pathJoin(base, key) {
|
|
195
229
|
return base ? `${base}.${key}` : key;
|
|
196
230
|
}
|
|
@@ -269,13 +303,62 @@ function deepEqual(a, b) {
|
|
|
269
303
|
}
|
|
270
304
|
|
|
271
305
|
// src/components/text.ts
|
|
306
|
+
function createCharCounter(element, input, isTextarea = false) {
|
|
307
|
+
const counter = document.createElement("span");
|
|
308
|
+
counter.className = "char-counter";
|
|
309
|
+
counter.style.cssText = `
|
|
310
|
+
position: absolute;
|
|
311
|
+
${isTextarea ? "bottom: 8px" : "top: 50%; transform: translateY(-50%)"};
|
|
312
|
+
right: 10px;
|
|
313
|
+
font-size: var(--fb-font-size-small);
|
|
314
|
+
color: var(--fb-text-secondary-color);
|
|
315
|
+
pointer-events: none;
|
|
316
|
+
background: var(--fb-background-color);
|
|
317
|
+
padding: 0 4px;
|
|
318
|
+
`;
|
|
319
|
+
const updateCounter = () => {
|
|
320
|
+
const len = input.value.length;
|
|
321
|
+
const min = element.minLength;
|
|
322
|
+
const max = element.maxLength;
|
|
323
|
+
if (min == null && max == null) {
|
|
324
|
+
counter.textContent = "";
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (len === 0 || min != null && len < min) {
|
|
328
|
+
if (min != null && max != null) {
|
|
329
|
+
counter.textContent = `${min}-${max}`;
|
|
330
|
+
} else if (max != null) {
|
|
331
|
+
counter.textContent = `\u2264${max}`;
|
|
332
|
+
} else if (min != null) {
|
|
333
|
+
counter.textContent = `\u2265${min}`;
|
|
334
|
+
}
|
|
335
|
+
counter.style.color = "var(--fb-text-secondary-color)";
|
|
336
|
+
} else if (max != null && len > max) {
|
|
337
|
+
counter.textContent = `${len}/${max}`;
|
|
338
|
+
counter.style.color = "var(--fb-error-color)";
|
|
339
|
+
} else {
|
|
340
|
+
if (max != null) {
|
|
341
|
+
counter.textContent = `${len}/${max}`;
|
|
342
|
+
} else {
|
|
343
|
+
counter.textContent = `${len}`;
|
|
344
|
+
}
|
|
345
|
+
counter.style.color = "var(--fb-text-secondary-color)";
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
input.addEventListener("input", updateCounter);
|
|
349
|
+
updateCounter();
|
|
350
|
+
return counter;
|
|
351
|
+
}
|
|
272
352
|
function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
273
353
|
const state = ctx.state;
|
|
354
|
+
const inputWrapper = document.createElement("div");
|
|
355
|
+
inputWrapper.style.cssText = "position: relative;";
|
|
274
356
|
const textInput = document.createElement("input");
|
|
275
357
|
textInput.type = "text";
|
|
276
358
|
textInput.className = "w-full rounded-lg";
|
|
277
359
|
textInput.style.cssText = `
|
|
278
360
|
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
361
|
+
padding-right: 60px;
|
|
279
362
|
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
280
363
|
border-radius: var(--fb-border-radius);
|
|
281
364
|
background-color: ${state.config.readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
|
|
@@ -283,6 +366,8 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
283
366
|
font-size: var(--fb-font-size);
|
|
284
367
|
font-family: var(--fb-font-family);
|
|
285
368
|
transition: all var(--fb-transition-duration) ease-in-out;
|
|
369
|
+
width: 100%;
|
|
370
|
+
box-sizing: border-box;
|
|
286
371
|
`;
|
|
287
372
|
textInput.name = pathKey;
|
|
288
373
|
textInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
|
|
@@ -317,15 +402,12 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
317
402
|
textInput.addEventListener("blur", handleChange);
|
|
318
403
|
textInput.addEventListener("input", handleChange);
|
|
319
404
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
`;
|
|
327
|
-
textHint.textContent = makeFieldHint(element);
|
|
328
|
-
wrapper.appendChild(textHint);
|
|
405
|
+
inputWrapper.appendChild(textInput);
|
|
406
|
+
if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
|
|
407
|
+
const counter = createCharCounter(element, textInput, false);
|
|
408
|
+
inputWrapper.appendChild(counter);
|
|
409
|
+
}
|
|
410
|
+
wrapper.appendChild(inputWrapper);
|
|
329
411
|
}
|
|
330
412
|
function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
331
413
|
var _a, _b;
|
|
@@ -352,11 +434,13 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
352
434
|
function addTextItem(value = "", index = -1) {
|
|
353
435
|
const itemWrapper = document.createElement("div");
|
|
354
436
|
itemWrapper.className = "multiple-text-item flex items-center gap-2";
|
|
437
|
+
const inputContainer = document.createElement("div");
|
|
438
|
+
inputContainer.style.cssText = "position: relative; flex: 1;";
|
|
355
439
|
const textInput = document.createElement("input");
|
|
356
440
|
textInput.type = "text";
|
|
357
|
-
textInput.className = "flex-1";
|
|
358
441
|
textInput.style.cssText = `
|
|
359
442
|
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
443
|
+
padding-right: 60px;
|
|
360
444
|
border: var(--fb-border-width) solid var(--fb-border-color);
|
|
361
445
|
border-radius: var(--fb-border-radius);
|
|
362
446
|
background-color: ${state.config.readonly ? "var(--fb-background-readonly-color)" : "var(--fb-background-color)"};
|
|
@@ -364,8 +448,10 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
364
448
|
font-size: var(--fb-font-size);
|
|
365
449
|
font-family: var(--fb-font-family);
|
|
366
450
|
transition: all var(--fb-transition-duration) ease-in-out;
|
|
451
|
+
width: 100%;
|
|
452
|
+
box-sizing: border-box;
|
|
367
453
|
`;
|
|
368
|
-
textInput.placeholder = element.placeholder || "
|
|
454
|
+
textInput.placeholder = element.placeholder || t("placeholderText", state);
|
|
369
455
|
textInput.value = value;
|
|
370
456
|
textInput.readOnly = state.config.readonly;
|
|
371
457
|
if (!state.config.readonly) {
|
|
@@ -397,7 +483,12 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
397
483
|
textInput.addEventListener("blur", handleChange);
|
|
398
484
|
textInput.addEventListener("input", handleChange);
|
|
399
485
|
}
|
|
400
|
-
|
|
486
|
+
inputContainer.appendChild(textInput);
|
|
487
|
+
if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
|
|
488
|
+
const counter = createCharCounter(element, textInput, false);
|
|
489
|
+
inputContainer.appendChild(counter);
|
|
490
|
+
}
|
|
491
|
+
itemWrapper.appendChild(inputContainer);
|
|
401
492
|
if (index === -1) {
|
|
402
493
|
container.appendChild(itemWrapper);
|
|
403
494
|
} else {
|
|
@@ -450,47 +541,54 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
450
541
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
451
542
|
});
|
|
452
543
|
}
|
|
544
|
+
let addRow = null;
|
|
545
|
+
let countDisplay = null;
|
|
546
|
+
if (!state.config.readonly) {
|
|
547
|
+
addRow = document.createElement("div");
|
|
548
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
549
|
+
const addBtn = document.createElement("button");
|
|
550
|
+
addBtn.type = "button";
|
|
551
|
+
addBtn.className = "add-text-btn px-3 py-1 rounded";
|
|
552
|
+
addBtn.style.cssText = `
|
|
553
|
+
color: var(--fb-primary-color);
|
|
554
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
555
|
+
background-color: transparent;
|
|
556
|
+
font-size: var(--fb-font-size);
|
|
557
|
+
transition: all var(--fb-transition-duration);
|
|
558
|
+
`;
|
|
559
|
+
addBtn.textContent = "+";
|
|
560
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
561
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
562
|
+
});
|
|
563
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
564
|
+
addBtn.style.backgroundColor = "transparent";
|
|
565
|
+
});
|
|
566
|
+
addBtn.onclick = () => {
|
|
567
|
+
values.push(element.default || "");
|
|
568
|
+
addTextItem(element.default || "");
|
|
569
|
+
updateAddButton();
|
|
570
|
+
updateRemoveButtons();
|
|
571
|
+
};
|
|
572
|
+
countDisplay = document.createElement("span");
|
|
573
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
574
|
+
addRow.appendChild(addBtn);
|
|
575
|
+
addRow.appendChild(countDisplay);
|
|
576
|
+
wrapper.appendChild(addRow);
|
|
577
|
+
}
|
|
453
578
|
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);
|
|
579
|
+
if (!addRow || !countDisplay) return;
|
|
580
|
+
const addBtn = addRow.querySelector(".add-text-btn");
|
|
581
|
+
if (addBtn) {
|
|
582
|
+
const disabled = values.length >= maxCount;
|
|
583
|
+
addBtn.disabled = disabled;
|
|
584
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
585
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
481
586
|
}
|
|
587
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
482
588
|
}
|
|
483
589
|
values.forEach((value) => addTextItem(value));
|
|
484
590
|
updateAddButton();
|
|
485
591
|
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
592
|
}
|
|
495
593
|
function validateTextElement(element, key, context) {
|
|
496
594
|
var _a, _b, _c;
|
|
@@ -531,26 +629,31 @@ function validateTextElement(element, key, context) {
|
|
|
531
629
|
};
|
|
532
630
|
const validateTextInput = (input, val, fieldKey) => {
|
|
533
631
|
let hasError = false;
|
|
632
|
+
const { state } = context;
|
|
534
633
|
if (!skipValidation && val) {
|
|
535
634
|
if (element.minLength !== void 0 && element.minLength !== null && val.length < element.minLength) {
|
|
536
|
-
|
|
537
|
-
|
|
635
|
+
const msg = t("minLength", state, { min: element.minLength });
|
|
636
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
637
|
+
markValidity(input, msg);
|
|
538
638
|
hasError = true;
|
|
539
639
|
} else if (element.maxLength !== void 0 && element.maxLength !== null && val.length > element.maxLength) {
|
|
540
|
-
|
|
541
|
-
|
|
640
|
+
const msg = t("maxLength", state, { max: element.maxLength });
|
|
641
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
642
|
+
markValidity(input, msg);
|
|
542
643
|
hasError = true;
|
|
543
644
|
} else if (element.pattern) {
|
|
544
645
|
try {
|
|
545
646
|
const re = new RegExp(element.pattern);
|
|
546
647
|
if (!re.test(val)) {
|
|
547
|
-
|
|
548
|
-
|
|
648
|
+
const msg = t("patternMismatch", state);
|
|
649
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
650
|
+
markValidity(input, msg);
|
|
549
651
|
hasError = true;
|
|
550
652
|
}
|
|
551
653
|
} catch {
|
|
552
|
-
|
|
553
|
-
|
|
654
|
+
const msg = t("invalidPattern", state);
|
|
655
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
656
|
+
markValidity(input, msg);
|
|
554
657
|
hasError = true;
|
|
555
658
|
}
|
|
556
659
|
}
|
|
@@ -571,17 +674,18 @@ function validateTextElement(element, key, context) {
|
|
|
571
674
|
validateTextInput(input, val, `${key}[${index}]`);
|
|
572
675
|
});
|
|
573
676
|
if (!skipValidation) {
|
|
677
|
+
const { state } = context;
|
|
574
678
|
const minCount = (_a = element.minCount) != null ? _a : 1;
|
|
575
679
|
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
576
680
|
const filteredValues = rawValues.filter((v) => v.trim() !== "");
|
|
577
681
|
if (element.required && filteredValues.length === 0) {
|
|
578
|
-
errors.push(`${key}: required`);
|
|
682
|
+
errors.push(`${key}: ${t("required", state)}`);
|
|
579
683
|
}
|
|
580
684
|
if (filteredValues.length < minCount) {
|
|
581
|
-
errors.push(`${key}:
|
|
685
|
+
errors.push(`${key}: ${t("minItems", state, { min: minCount })}`);
|
|
582
686
|
}
|
|
583
687
|
if (filteredValues.length > maxCount) {
|
|
584
|
-
errors.push(`${key}:
|
|
688
|
+
errors.push(`${key}: ${t("maxItems", state, { max: maxCount })}`);
|
|
585
689
|
}
|
|
586
690
|
}
|
|
587
691
|
return { value: values, errors };
|
|
@@ -589,8 +693,9 @@ function validateTextElement(element, key, context) {
|
|
|
589
693
|
const input = scopeRoot.querySelector(`[name$="${key}"]`);
|
|
590
694
|
const val = (_c = input == null ? void 0 : input.value) != null ? _c : "";
|
|
591
695
|
if (!skipValidation && element.required && val === "") {
|
|
592
|
-
|
|
593
|
-
|
|
696
|
+
const msg = t("required", context.state);
|
|
697
|
+
errors.push(`${key}: ${msg}`);
|
|
698
|
+
markValidity(input, msg);
|
|
594
699
|
return { value: null, errors };
|
|
595
700
|
}
|
|
596
701
|
if (input) {
|
|
@@ -634,8 +739,11 @@ function updateTextField(element, fieldPath, value, context) {
|
|
|
634
739
|
// src/components/textarea.ts
|
|
635
740
|
function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
636
741
|
const state = ctx.state;
|
|
742
|
+
const textareaWrapper = document.createElement("div");
|
|
743
|
+
textareaWrapper.style.cssText = "position: relative;";
|
|
637
744
|
const textareaInput = document.createElement("textarea");
|
|
638
745
|
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";
|
|
746
|
+
textareaInput.style.cssText = "padding-bottom: 24px;";
|
|
639
747
|
textareaInput.name = pathKey;
|
|
640
748
|
textareaInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
|
|
641
749
|
textareaInput.rows = element.rows || 4;
|
|
@@ -649,11 +757,12 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
649
757
|
textareaInput.addEventListener("blur", handleChange);
|
|
650
758
|
textareaInput.addEventListener("input", handleChange);
|
|
651
759
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
760
|
+
textareaWrapper.appendChild(textareaInput);
|
|
761
|
+
if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
|
|
762
|
+
const counter = createCharCounter(element, textareaInput, true);
|
|
763
|
+
textareaWrapper.appendChild(counter);
|
|
764
|
+
}
|
|
765
|
+
wrapper.appendChild(textareaWrapper);
|
|
657
766
|
}
|
|
658
767
|
function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
659
768
|
var _a, _b;
|
|
@@ -680,9 +789,12 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
680
789
|
function addTextareaItem(value = "", index = -1) {
|
|
681
790
|
const itemWrapper = document.createElement("div");
|
|
682
791
|
itemWrapper.className = "multiple-textarea-item";
|
|
792
|
+
const textareaContainer = document.createElement("div");
|
|
793
|
+
textareaContainer.style.cssText = "position: relative;";
|
|
683
794
|
const textareaInput = document.createElement("textarea");
|
|
684
795
|
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.
|
|
796
|
+
textareaInput.style.cssText = "padding-bottom: 24px;";
|
|
797
|
+
textareaInput.placeholder = element.placeholder || t("placeholderText", state);
|
|
686
798
|
textareaInput.rows = element.rows || 4;
|
|
687
799
|
textareaInput.value = value;
|
|
688
800
|
textareaInput.readOnly = state.config.readonly;
|
|
@@ -694,7 +806,12 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
694
806
|
textareaInput.addEventListener("blur", handleChange);
|
|
695
807
|
textareaInput.addEventListener("input", handleChange);
|
|
696
808
|
}
|
|
697
|
-
|
|
809
|
+
textareaContainer.appendChild(textareaInput);
|
|
810
|
+
if (!state.config.readonly && (element.minLength != null || element.maxLength != null)) {
|
|
811
|
+
const counter = createCharCounter(element, textareaInput, true);
|
|
812
|
+
textareaContainer.appendChild(counter);
|
|
813
|
+
}
|
|
814
|
+
itemWrapper.appendChild(textareaContainer);
|
|
698
815
|
if (index === -1) {
|
|
699
816
|
container.appendChild(itemWrapper);
|
|
700
817
|
} else {
|
|
@@ -736,30 +853,54 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
736
853
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
737
854
|
});
|
|
738
855
|
}
|
|
856
|
+
let addRow = null;
|
|
857
|
+
let countDisplay = null;
|
|
858
|
+
if (!state.config.readonly) {
|
|
859
|
+
addRow = document.createElement("div");
|
|
860
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
861
|
+
const addBtn = document.createElement("button");
|
|
862
|
+
addBtn.type = "button";
|
|
863
|
+
addBtn.className = "add-textarea-btn px-3 py-1 rounded";
|
|
864
|
+
addBtn.style.cssText = `
|
|
865
|
+
color: var(--fb-primary-color);
|
|
866
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
867
|
+
background-color: transparent;
|
|
868
|
+
font-size: var(--fb-font-size);
|
|
869
|
+
transition: all var(--fb-transition-duration);
|
|
870
|
+
`;
|
|
871
|
+
addBtn.textContent = "+";
|
|
872
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
873
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
874
|
+
});
|
|
875
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
876
|
+
addBtn.style.backgroundColor = "transparent";
|
|
877
|
+
});
|
|
878
|
+
addBtn.onclick = () => {
|
|
879
|
+
values.push(element.default || "");
|
|
880
|
+
addTextareaItem(element.default || "");
|
|
881
|
+
updateAddButton();
|
|
882
|
+
updateRemoveButtons();
|
|
883
|
+
};
|
|
884
|
+
countDisplay = document.createElement("span");
|
|
885
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
886
|
+
addRow.appendChild(addBtn);
|
|
887
|
+
addRow.appendChild(countDisplay);
|
|
888
|
+
wrapper.appendChild(addRow);
|
|
889
|
+
}
|
|
739
890
|
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);
|
|
891
|
+
if (!addRow || !countDisplay) return;
|
|
892
|
+
const addBtn = addRow.querySelector(".add-textarea-btn");
|
|
893
|
+
if (addBtn) {
|
|
894
|
+
const disabled = values.length >= maxCount;
|
|
895
|
+
addBtn.disabled = disabled;
|
|
896
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
897
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
754
898
|
}
|
|
899
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
755
900
|
}
|
|
756
901
|
values.forEach((value) => addTextareaItem(value));
|
|
757
902
|
updateAddButton();
|
|
758
903
|
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
904
|
}
|
|
764
905
|
function validateTextareaElement(element, key, context) {
|
|
765
906
|
return validateTextElement(element, key, context);
|
|
@@ -769,11 +910,61 @@ function updateTextareaField(element, fieldPath, value, context) {
|
|
|
769
910
|
}
|
|
770
911
|
|
|
771
912
|
// src/components/number.ts
|
|
913
|
+
function createNumberCounter(element, input) {
|
|
914
|
+
const counter = document.createElement("span");
|
|
915
|
+
counter.className = "number-counter";
|
|
916
|
+
counter.style.cssText = `
|
|
917
|
+
position: absolute;
|
|
918
|
+
top: 50%;
|
|
919
|
+
transform: translateY(-50%);
|
|
920
|
+
right: 10px;
|
|
921
|
+
font-size: var(--fb-font-size-small);
|
|
922
|
+
color: var(--fb-text-secondary-color);
|
|
923
|
+
pointer-events: none;
|
|
924
|
+
background: var(--fb-background-color);
|
|
925
|
+
padding: 0 4px;
|
|
926
|
+
`;
|
|
927
|
+
const updateCounter = () => {
|
|
928
|
+
const val = input.value ? parseFloat(input.value) : null;
|
|
929
|
+
const min = element.min;
|
|
930
|
+
const max = element.max;
|
|
931
|
+
if (min == null && max == null) {
|
|
932
|
+
counter.textContent = "";
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (val == null || min != null && val < min) {
|
|
936
|
+
if (min != null && max != null) {
|
|
937
|
+
counter.textContent = `${min}-${max}`;
|
|
938
|
+
} else if (max != null) {
|
|
939
|
+
counter.textContent = `\u2264${max}`;
|
|
940
|
+
} else if (min != null) {
|
|
941
|
+
counter.textContent = `\u2265${min}`;
|
|
942
|
+
}
|
|
943
|
+
counter.style.color = "var(--fb-text-secondary-color)";
|
|
944
|
+
} else if (max != null && val > max) {
|
|
945
|
+
counter.textContent = `${val}/${max}`;
|
|
946
|
+
counter.style.color = "var(--fb-error-color)";
|
|
947
|
+
} else {
|
|
948
|
+
if (max != null) {
|
|
949
|
+
counter.textContent = `${val}/${max}`;
|
|
950
|
+
} else {
|
|
951
|
+
counter.textContent = `${val}`;
|
|
952
|
+
}
|
|
953
|
+
counter.style.color = "var(--fb-text-secondary-color)";
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
input.addEventListener("input", updateCounter);
|
|
957
|
+
updateCounter();
|
|
958
|
+
return counter;
|
|
959
|
+
}
|
|
772
960
|
function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
773
961
|
const state = ctx.state;
|
|
962
|
+
const inputWrapper = document.createElement("div");
|
|
963
|
+
inputWrapper.style.cssText = "position: relative;";
|
|
774
964
|
const numberInput = document.createElement("input");
|
|
775
965
|
numberInput.type = "number";
|
|
776
966
|
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";
|
|
967
|
+
numberInput.style.cssText = "padding-right: 60px; width: 100%; box-sizing: border-box;";
|
|
777
968
|
numberInput.name = pathKey;
|
|
778
969
|
numberInput.placeholder = element.placeholder || "0";
|
|
779
970
|
if (element.min !== void 0) numberInput.min = element.min.toString();
|
|
@@ -789,11 +980,12 @@ function renderNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
789
980
|
numberInput.addEventListener("blur", handleChange);
|
|
790
981
|
numberInput.addEventListener("input", handleChange);
|
|
791
982
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
983
|
+
inputWrapper.appendChild(numberInput);
|
|
984
|
+
if (!state.config.readonly && (element.min != null || element.max != null)) {
|
|
985
|
+
const counter = createNumberCounter(element, numberInput);
|
|
986
|
+
inputWrapper.appendChild(counter);
|
|
987
|
+
}
|
|
988
|
+
wrapper.appendChild(inputWrapper);
|
|
797
989
|
}
|
|
798
990
|
function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
799
991
|
var _a, _b;
|
|
@@ -820,9 +1012,12 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
820
1012
|
function addNumberItem(value = "", index = -1) {
|
|
821
1013
|
const itemWrapper = document.createElement("div");
|
|
822
1014
|
itemWrapper.className = "multiple-number-item flex items-center gap-2";
|
|
1015
|
+
const inputContainer = document.createElement("div");
|
|
1016
|
+
inputContainer.style.cssText = "position: relative; flex: 1;";
|
|
823
1017
|
const numberInput = document.createElement("input");
|
|
824
1018
|
numberInput.type = "number";
|
|
825
|
-
numberInput.className = "
|
|
1019
|
+
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";
|
|
1020
|
+
numberInput.style.cssText = "padding-right: 60px; width: 100%; box-sizing: border-box;";
|
|
826
1021
|
numberInput.placeholder = element.placeholder || "0";
|
|
827
1022
|
if (element.min !== void 0) numberInput.min = element.min.toString();
|
|
828
1023
|
if (element.max !== void 0) numberInput.max = element.max.toString();
|
|
@@ -837,7 +1032,12 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
837
1032
|
numberInput.addEventListener("blur", handleChange);
|
|
838
1033
|
numberInput.addEventListener("input", handleChange);
|
|
839
1034
|
}
|
|
840
|
-
|
|
1035
|
+
inputContainer.appendChild(numberInput);
|
|
1036
|
+
if (!state.config.readonly && (element.min != null || element.max != null)) {
|
|
1037
|
+
const counter = createNumberCounter(element, numberInput);
|
|
1038
|
+
inputContainer.appendChild(counter);
|
|
1039
|
+
}
|
|
1040
|
+
itemWrapper.appendChild(inputContainer);
|
|
841
1041
|
if (index === -1) {
|
|
842
1042
|
container.appendChild(itemWrapper);
|
|
843
1043
|
} else {
|
|
@@ -879,30 +1079,54 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
879
1079
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
880
1080
|
});
|
|
881
1081
|
}
|
|
1082
|
+
let addRow = null;
|
|
1083
|
+
let countDisplay = null;
|
|
1084
|
+
if (!state.config.readonly) {
|
|
1085
|
+
addRow = document.createElement("div");
|
|
1086
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
1087
|
+
const addBtn = document.createElement("button");
|
|
1088
|
+
addBtn.type = "button";
|
|
1089
|
+
addBtn.className = "add-number-btn px-3 py-1 rounded";
|
|
1090
|
+
addBtn.style.cssText = `
|
|
1091
|
+
color: var(--fb-primary-color);
|
|
1092
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
1093
|
+
background-color: transparent;
|
|
1094
|
+
font-size: var(--fb-font-size);
|
|
1095
|
+
transition: all var(--fb-transition-duration);
|
|
1096
|
+
`;
|
|
1097
|
+
addBtn.textContent = "+";
|
|
1098
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
1099
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1100
|
+
});
|
|
1101
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
1102
|
+
addBtn.style.backgroundColor = "transparent";
|
|
1103
|
+
});
|
|
1104
|
+
addBtn.onclick = () => {
|
|
1105
|
+
values.push(element.default || "");
|
|
1106
|
+
addNumberItem(element.default || "");
|
|
1107
|
+
updateAddButton();
|
|
1108
|
+
updateRemoveButtons();
|
|
1109
|
+
};
|
|
1110
|
+
countDisplay = document.createElement("span");
|
|
1111
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
1112
|
+
addRow.appendChild(addBtn);
|
|
1113
|
+
addRow.appendChild(countDisplay);
|
|
1114
|
+
wrapper.appendChild(addRow);
|
|
1115
|
+
}
|
|
882
1116
|
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);
|
|
1117
|
+
if (!addRow || !countDisplay) return;
|
|
1118
|
+
const addBtn = addRow.querySelector(".add-number-btn");
|
|
1119
|
+
if (addBtn) {
|
|
1120
|
+
const disabled = values.length >= maxCount;
|
|
1121
|
+
addBtn.disabled = disabled;
|
|
1122
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1123
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
897
1124
|
}
|
|
1125
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
898
1126
|
}
|
|
899
1127
|
values.forEach((value) => addNumberItem(value));
|
|
900
1128
|
updateAddButton();
|
|
901
1129
|
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
1130
|
}
|
|
907
1131
|
function validateNumberElement(element, key, context) {
|
|
908
1132
|
var _a, _b, _c, _d, _e;
|
|
@@ -943,13 +1167,16 @@ function validateNumberElement(element, key, context) {
|
|
|
943
1167
|
};
|
|
944
1168
|
const validateNumberInput = (input, v, fieldKey) => {
|
|
945
1169
|
let hasError = false;
|
|
1170
|
+
const { state } = context;
|
|
946
1171
|
if (!skipValidation && element.min !== void 0 && element.min !== null && v < element.min) {
|
|
947
|
-
|
|
948
|
-
|
|
1172
|
+
const msg = t("minValue", state, { min: element.min });
|
|
1173
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
1174
|
+
markValidity(input, msg);
|
|
949
1175
|
hasError = true;
|
|
950
1176
|
} else if (!skipValidation && element.max !== void 0 && element.max !== null && v > element.max) {
|
|
951
|
-
|
|
952
|
-
|
|
1177
|
+
const msg = t("maxValue", state, { max: element.max });
|
|
1178
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
1179
|
+
markValidity(input, msg);
|
|
953
1180
|
hasError = true;
|
|
954
1181
|
}
|
|
955
1182
|
if (!hasError) {
|
|
@@ -971,8 +1198,9 @@ function validateNumberElement(element, key, context) {
|
|
|
971
1198
|
}
|
|
972
1199
|
const v = parseFloat(raw);
|
|
973
1200
|
if (!skipValidation && !Number.isFinite(v)) {
|
|
974
|
-
|
|
975
|
-
|
|
1201
|
+
const msg = t("notANumber", context.state);
|
|
1202
|
+
errors.push(`${key}[${index}]: ${msg}`);
|
|
1203
|
+
markValidity(input, msg);
|
|
976
1204
|
values.push(null);
|
|
977
1205
|
return;
|
|
978
1206
|
}
|
|
@@ -981,26 +1209,29 @@ function validateNumberElement(element, key, context) {
|
|
|
981
1209
|
values.push(Number(v.toFixed(d)));
|
|
982
1210
|
});
|
|
983
1211
|
if (!skipValidation) {
|
|
1212
|
+
const { state } = context;
|
|
984
1213
|
const minCount = (_a = element.minCount) != null ? _a : 1;
|
|
985
1214
|
const maxCount = (_b = element.maxCount) != null ? _b : Infinity;
|
|
986
1215
|
const filteredValues = values.filter((v) => v !== null);
|
|
987
1216
|
if (element.required && filteredValues.length === 0) {
|
|
988
|
-
errors.push(`${key}: required`);
|
|
1217
|
+
errors.push(`${key}: ${t("required", state)}`);
|
|
989
1218
|
}
|
|
990
1219
|
if (filteredValues.length < minCount) {
|
|
991
|
-
errors.push(`${key}:
|
|
1220
|
+
errors.push(`${key}: ${t("minItems", state, { min: minCount })}`);
|
|
992
1221
|
}
|
|
993
1222
|
if (filteredValues.length > maxCount) {
|
|
994
|
-
errors.push(`${key}:
|
|
1223
|
+
errors.push(`${key}: ${t("maxItems", state, { max: maxCount })}`);
|
|
995
1224
|
}
|
|
996
1225
|
}
|
|
997
1226
|
return { value: values, errors };
|
|
998
1227
|
} else {
|
|
999
1228
|
const input = scopeRoot.querySelector(`[name$="${key}"]`);
|
|
1000
1229
|
const raw = (_c = input == null ? void 0 : input.value) != null ? _c : "";
|
|
1230
|
+
const { state } = context;
|
|
1001
1231
|
if (!skipValidation && element.required && raw === "") {
|
|
1002
|
-
|
|
1003
|
-
|
|
1232
|
+
const msg = t("required", state);
|
|
1233
|
+
errors.push(`${key}: ${msg}`);
|
|
1234
|
+
markValidity(input, msg);
|
|
1004
1235
|
return { value: null, errors };
|
|
1005
1236
|
}
|
|
1006
1237
|
if (raw === "") {
|
|
@@ -1009,8 +1240,9 @@ function validateNumberElement(element, key, context) {
|
|
|
1009
1240
|
}
|
|
1010
1241
|
const v = parseFloat(raw);
|
|
1011
1242
|
if (!skipValidation && !Number.isFinite(v)) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1243
|
+
const msg = t("notANumber", state);
|
|
1244
|
+
errors.push(`${key}: ${msg}`);
|
|
1245
|
+
markValidity(input, msg);
|
|
1014
1246
|
return { value: null, errors };
|
|
1015
1247
|
}
|
|
1016
1248
|
validateNumberInput(input, v, key);
|
|
@@ -1079,7 +1311,7 @@ function renderSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1079
1311
|
wrapper.appendChild(selectInput);
|
|
1080
1312
|
const selectHint = document.createElement("p");
|
|
1081
1313
|
selectHint.className = "text-xs text-gray-500 mt-1";
|
|
1082
|
-
selectHint.textContent = makeFieldHint(element);
|
|
1314
|
+
selectHint.textContent = makeFieldHint(element, state);
|
|
1083
1315
|
wrapper.appendChild(selectHint);
|
|
1084
1316
|
}
|
|
1085
1317
|
function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
@@ -1165,31 +1397,59 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1165
1397
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1166
1398
|
});
|
|
1167
1399
|
}
|
|
1400
|
+
let addRow = null;
|
|
1401
|
+
let countDisplay = null;
|
|
1402
|
+
if (!state.config.readonly) {
|
|
1403
|
+
addRow = document.createElement("div");
|
|
1404
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
1405
|
+
const addBtn = document.createElement("button");
|
|
1406
|
+
addBtn.type = "button";
|
|
1407
|
+
addBtn.className = "add-select-btn px-3 py-1 rounded";
|
|
1408
|
+
addBtn.style.cssText = `
|
|
1409
|
+
color: var(--fb-primary-color);
|
|
1410
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
1411
|
+
background-color: transparent;
|
|
1412
|
+
font-size: var(--fb-font-size);
|
|
1413
|
+
transition: all var(--fb-transition-duration);
|
|
1414
|
+
`;
|
|
1415
|
+
addBtn.textContent = "+";
|
|
1416
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
1417
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
1418
|
+
});
|
|
1419
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
1420
|
+
addBtn.style.backgroundColor = "transparent";
|
|
1421
|
+
});
|
|
1422
|
+
addBtn.onclick = () => {
|
|
1423
|
+
var _a2, _b2;
|
|
1424
|
+
const defaultValue = element.default || ((_b2 = (_a2 = element.options) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.value) || "";
|
|
1425
|
+
values.push(defaultValue);
|
|
1426
|
+
addSelectItem(defaultValue);
|
|
1427
|
+
updateAddButton();
|
|
1428
|
+
updateRemoveButtons();
|
|
1429
|
+
};
|
|
1430
|
+
countDisplay = document.createElement("span");
|
|
1431
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
1432
|
+
addRow.appendChild(addBtn);
|
|
1433
|
+
addRow.appendChild(countDisplay);
|
|
1434
|
+
wrapper.appendChild(addRow);
|
|
1435
|
+
}
|
|
1168
1436
|
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);
|
|
1437
|
+
if (!addRow || !countDisplay) return;
|
|
1438
|
+
const addBtn = addRow.querySelector(".add-select-btn");
|
|
1439
|
+
if (addBtn) {
|
|
1440
|
+
const disabled = values.length >= maxCount;
|
|
1441
|
+
addBtn.disabled = disabled;
|
|
1442
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
1443
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
1185
1444
|
}
|
|
1445
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
1186
1446
|
}
|
|
1187
1447
|
values.forEach((value) => addSelectItem(value));
|
|
1188
1448
|
updateAddButton();
|
|
1189
1449
|
updateRemoveButtons();
|
|
1190
1450
|
const hint = document.createElement("p");
|
|
1191
1451
|
hint.className = "text-xs text-gray-500 mt-1";
|
|
1192
|
-
hint.textContent = makeFieldHint(element);
|
|
1452
|
+
hint.textContent = makeFieldHint(element, state);
|
|
1193
1453
|
wrapper.appendChild(hint);
|
|
1194
1454
|
}
|
|
1195
1455
|
function validateSelectElement(element, key, context) {
|
|
@@ -1232,17 +1492,18 @@ function validateSelectElement(element, key, context) {
|
|
|
1232
1492
|
const validateMultipleCount = (key2, values, element2, filterFn) => {
|
|
1233
1493
|
var _a2, _b;
|
|
1234
1494
|
if (skipValidation) return;
|
|
1495
|
+
const { state } = context;
|
|
1235
1496
|
const filteredValues = values.filter(filterFn);
|
|
1236
1497
|
const minCount = "minCount" in element2 ? (_a2 = element2.minCount) != null ? _a2 : 1 : 1;
|
|
1237
1498
|
const maxCount = "maxCount" in element2 ? (_b = element2.maxCount) != null ? _b : Infinity : Infinity;
|
|
1238
1499
|
if (element2.required && filteredValues.length === 0) {
|
|
1239
|
-
errors.push(`${key2}: required`);
|
|
1500
|
+
errors.push(`${key2}: ${t("required", state)}`);
|
|
1240
1501
|
}
|
|
1241
1502
|
if (filteredValues.length < minCount) {
|
|
1242
|
-
errors.push(`${key2}:
|
|
1503
|
+
errors.push(`${key2}: ${t("minItems", state, { min: minCount })}`);
|
|
1243
1504
|
}
|
|
1244
1505
|
if (filteredValues.length > maxCount) {
|
|
1245
|
-
errors.push(`${key2}:
|
|
1506
|
+
errors.push(`${key2}: ${t("maxItems", state, { max: maxCount })}`);
|
|
1246
1507
|
}
|
|
1247
1508
|
};
|
|
1248
1509
|
if ("multiple" in element && element.multiple) {
|
|
@@ -1264,8 +1525,9 @@ function validateSelectElement(element, key, context) {
|
|
|
1264
1525
|
);
|
|
1265
1526
|
const val = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
1266
1527
|
if (!skipValidation && element.required && val === "") {
|
|
1267
|
-
|
|
1268
|
-
|
|
1528
|
+
const msg = t("required", context.state);
|
|
1529
|
+
errors.push(`${key}: ${msg}`);
|
|
1530
|
+
markValidity(input, msg);
|
|
1269
1531
|
return { value: null, errors };
|
|
1270
1532
|
} else {
|
|
1271
1533
|
markValidity(input, null);
|
|
@@ -1317,18 +1579,11 @@ function updateSelectField(element, fieldPath, value, context) {
|
|
|
1317
1579
|
}
|
|
1318
1580
|
}
|
|
1319
1581
|
|
|
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
1582
|
// src/components/file.ts
|
|
1328
|
-
function renderLocalImagePreview(container, file, fileName) {
|
|
1583
|
+
function renderLocalImagePreview(container, file, fileName, state) {
|
|
1329
1584
|
const img = document.createElement("img");
|
|
1330
1585
|
img.className = "w-full h-full object-contain";
|
|
1331
|
-
img.alt = fileName || "
|
|
1586
|
+
img.alt = fileName || t("previewAlt", state);
|
|
1332
1587
|
const reader = new FileReader();
|
|
1333
1588
|
reader.onload = (e) => {
|
|
1334
1589
|
var _a;
|
|
@@ -1348,14 +1603,14 @@ function renderLocalVideoPreview(container, file, videoType, resourceId, state,
|
|
|
1348
1603
|
<div class="relative group h-full">
|
|
1349
1604
|
<video class="w-full h-full object-contain" controls preload="auto" muted>
|
|
1350
1605
|
<source src="${videoUrl}" type="${videoType}">
|
|
1351
|
-
|
|
1606
|
+
${escapeHtml(t("videoNotSupported", state))}
|
|
1352
1607
|
</video>
|
|
1353
1608
|
<div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10 flex gap-1">
|
|
1354
1609
|
<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)}
|
|
1610
|
+
${escapeHtml(t("removeElement", state))}
|
|
1356
1611
|
</button>
|
|
1357
1612
|
<button class="bg-gray-800 bg-opacity-75 hover:bg-opacity-90 text-white p-1 rounded text-xs change-file-btn">
|
|
1358
|
-
|
|
1613
|
+
${escapeHtml(t("changeButton", state))}
|
|
1359
1614
|
</button>
|
|
1360
1615
|
</div>
|
|
1361
1616
|
</div>
|
|
@@ -1405,11 +1660,11 @@ function handleVideoDelete(container, resourceId, state, deps) {
|
|
|
1405
1660
|
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1406
1661
|
<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
1662
|
</svg>
|
|
1408
|
-
<div class="text-sm text-center">${t("clickDragText", state)}</div>
|
|
1663
|
+
<div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
|
|
1409
1664
|
</div>
|
|
1410
1665
|
`;
|
|
1411
1666
|
}
|
|
1412
|
-
function renderUploadedVideoPreview(container, thumbnailUrl, videoType) {
|
|
1667
|
+
function renderUploadedVideoPreview(container, thumbnailUrl, videoType, state) {
|
|
1413
1668
|
const video = document.createElement("video");
|
|
1414
1669
|
video.className = "w-full h-full object-contain";
|
|
1415
1670
|
video.controls = true;
|
|
@@ -1420,7 +1675,7 @@ function renderUploadedVideoPreview(container, thumbnailUrl, videoType) {
|
|
|
1420
1675
|
source.type = videoType;
|
|
1421
1676
|
video.appendChild(source);
|
|
1422
1677
|
video.appendChild(
|
|
1423
|
-
document.createTextNode("
|
|
1678
|
+
document.createTextNode(t("videoNotSupported", state))
|
|
1424
1679
|
);
|
|
1425
1680
|
container.appendChild(video);
|
|
1426
1681
|
}
|
|
@@ -1439,7 +1694,7 @@ function renderDeleteButton(container, resourceId, state) {
|
|
|
1439
1694
|
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1440
1695
|
<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
1696
|
</svg>
|
|
1442
|
-
<div class="text-sm text-center">${t("clickDragText", state)}</div>
|
|
1697
|
+
<div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
|
|
1443
1698
|
</div>
|
|
1444
1699
|
`;
|
|
1445
1700
|
});
|
|
@@ -1449,7 +1704,7 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
|
|
|
1449
1704
|
return;
|
|
1450
1705
|
}
|
|
1451
1706
|
if (meta.type && meta.type.startsWith("image/")) {
|
|
1452
|
-
renderLocalImagePreview(container, meta.file, fileName);
|
|
1707
|
+
renderLocalImagePreview(container, meta.file, fileName, state);
|
|
1453
1708
|
} else if (meta.type && meta.type.startsWith("video/")) {
|
|
1454
1709
|
const newContainer = renderLocalVideoPreview(
|
|
1455
1710
|
container,
|
|
@@ -1461,7 +1716,7 @@ async function renderLocalFilePreview(container, meta, fileName, resourceId, isR
|
|
|
1461
1716
|
);
|
|
1462
1717
|
container = newContainer;
|
|
1463
1718
|
} 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>`;
|
|
1719
|
+
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
1720
|
}
|
|
1466
1721
|
if (!isReadonly && !(meta.type && meta.type.startsWith("video/"))) {
|
|
1467
1722
|
renderDeleteButton(container, resourceId, state);
|
|
@@ -1477,11 +1732,11 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
|
|
|
1477
1732
|
if (thumbnailUrl) {
|
|
1478
1733
|
clear(container);
|
|
1479
1734
|
if (meta && meta.type && meta.type.startsWith("video/")) {
|
|
1480
|
-
renderUploadedVideoPreview(container, thumbnailUrl, meta.type);
|
|
1735
|
+
renderUploadedVideoPreview(container, thumbnailUrl, meta.type, state);
|
|
1481
1736
|
} else {
|
|
1482
1737
|
const img = document.createElement("img");
|
|
1483
1738
|
img.className = "w-full h-full object-contain";
|
|
1484
|
-
img.alt = fileName || "
|
|
1739
|
+
img.alt = fileName || t("previewAlt", state);
|
|
1485
1740
|
img.src = thumbnailUrl;
|
|
1486
1741
|
container.appendChild(img);
|
|
1487
1742
|
}
|
|
@@ -1495,7 +1750,7 @@ async function renderUploadedFilePreview(container, resourceId, fileName, meta,
|
|
|
1495
1750
|
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1496
1751
|
<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
1752
|
</svg>
|
|
1498
|
-
<div class="text-sm text-center">${fileName || "
|
|
1753
|
+
<div class="text-sm text-center">${escapeHtml(fileName || t("previewUnavailable", state))}</div>
|
|
1499
1754
|
</div>
|
|
1500
1755
|
`;
|
|
1501
1756
|
}
|
|
@@ -1552,16 +1807,16 @@ async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
|
1552
1807
|
try {
|
|
1553
1808
|
const thumbnailUrl = await state.config.getThumbnail(resourceId);
|
|
1554
1809
|
if (thumbnailUrl) {
|
|
1555
|
-
previewContainer.innerHTML = `<img src="${thumbnailUrl}" alt="${actualFileName}" class="w-full h-auto">`;
|
|
1810
|
+
previewContainer.innerHTML = `<img src="${thumbnailUrl}" alt="${escapeHtml(actualFileName)}" class="w-full h-auto">`;
|
|
1556
1811
|
} 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>`;
|
|
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>`;
|
|
1558
1813
|
}
|
|
1559
1814
|
} catch (error) {
|
|
1560
1815
|
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>`;
|
|
1816
|
+
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
1817
|
}
|
|
1563
1818
|
} 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>`;
|
|
1819
|
+
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
1820
|
}
|
|
1566
1821
|
} else if (isVideo) {
|
|
1567
1822
|
if (state.config.getThumbnail) {
|
|
@@ -1572,7 +1827,7 @@ async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
|
1572
1827
|
<div class="relative group">
|
|
1573
1828
|
<video class="w-full h-auto" controls preload="auto" muted>
|
|
1574
1829
|
<source src="${videoUrl}" type="${(meta == null ? void 0 : meta.type) || "video/mp4"}">
|
|
1575
|
-
|
|
1830
|
+
${escapeHtml(t("videoNotSupported", state))}
|
|
1576
1831
|
</video>
|
|
1577
1832
|
<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
1833
|
<div class="bg-white bg-opacity-90 rounded-full p-3">
|
|
@@ -1584,14 +1839,14 @@ async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
|
1584
1839
|
</div>
|
|
1585
1840
|
`;
|
|
1586
1841
|
} 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>`;
|
|
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>`;
|
|
1588
1843
|
}
|
|
1589
1844
|
} catch (error) {
|
|
1590
1845
|
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>`;
|
|
1846
|
+
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
1847
|
}
|
|
1593
1848
|
} 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>`;
|
|
1849
|
+
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
1850
|
}
|
|
1596
1851
|
} else {
|
|
1597
1852
|
const fileIcon = isPSD ? "\u{1F3A8}" : "\u{1F4C1}";
|
|
@@ -1601,13 +1856,13 @@ async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
|
1601
1856
|
<div class="flex items-center space-x-3">
|
|
1602
1857
|
<div class="text-3xl text-gray-400">${fileIcon}</div>
|
|
1603
1858
|
<div class="flex-1 min-w-0">
|
|
1604
|
-
<div class="text-sm font-medium text-gray-900 truncate">${actualFileName}</div>
|
|
1859
|
+
<div class="text-sm font-medium text-gray-900 truncate">${escapeHtml(actualFileName)}</div>
|
|
1605
1860
|
<div class="text-xs text-gray-500">${fileDescription}</div>
|
|
1606
1861
|
</div>
|
|
1607
1862
|
</div>
|
|
1608
1863
|
`;
|
|
1609
1864
|
} 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>`;
|
|
1865
|
+
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
1866
|
}
|
|
1612
1867
|
}
|
|
1613
1868
|
const fileNameElement = document.createElement("p");
|
|
@@ -1630,12 +1885,18 @@ async function renderFilePreviewReadonly(resourceId, state, fileName) {
|
|
|
1630
1885
|
fileResult.appendChild(downloadButton);
|
|
1631
1886
|
return fileResult;
|
|
1632
1887
|
}
|
|
1633
|
-
function renderResourcePills(container, rids, state, onRemove) {
|
|
1888
|
+
function renderResourcePills(container, rids, state, onRemove, hint, countInfo) {
|
|
1634
1889
|
clear(container);
|
|
1890
|
+
const buildHintLine = () => {
|
|
1891
|
+
const parts = [t("clickDragTextMultiple", state)];
|
|
1892
|
+
if (hint) parts.push(hint);
|
|
1893
|
+
if (countInfo) parts.push(countInfo);
|
|
1894
|
+
return parts.join(" \u2022 ");
|
|
1895
|
+
};
|
|
1635
1896
|
const isInitialRender = !container.classList.contains("grid");
|
|
1636
1897
|
if ((!rids || rids.length === 0) && isInitialRender) {
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1898
|
+
const gridContainer2 = document.createElement("div");
|
|
1899
|
+
gridContainer2.className = "grid grid-cols-4 gap-3 mb-3";
|
|
1639
1900
|
for (let i = 0; i < 4; i++) {
|
|
1640
1901
|
const slot = document.createElement("div");
|
|
1641
1902
|
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 +1927,17 @@ function renderResourcePills(container, rids, state, onRemove) {
|
|
|
1666
1927
|
);
|
|
1667
1928
|
if (fileInput) fileInput.click();
|
|
1668
1929
|
};
|
|
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);
|
|
1930
|
+
gridContainer2.appendChild(slot);
|
|
1931
|
+
}
|
|
1932
|
+
const hintText2 = document.createElement("div");
|
|
1933
|
+
hintText2.className = "text-center text-xs text-gray-500 mt-2";
|
|
1934
|
+
hintText2.textContent = buildHintLine();
|
|
1935
|
+
container.appendChild(gridContainer2);
|
|
1936
|
+
container.appendChild(hintText2);
|
|
1696
1937
|
return;
|
|
1697
1938
|
}
|
|
1698
|
-
|
|
1939
|
+
const gridContainer = document.createElement("div");
|
|
1940
|
+
gridContainer.className = "files-list grid grid-cols-4 gap-3";
|
|
1699
1941
|
const currentImagesCount = rids ? rids.length : 0;
|
|
1700
1942
|
const rowsNeeded = Math.floor(currentImagesCount / 4) + 1;
|
|
1701
1943
|
const slotsNeeded = rowsNeeded * 4;
|
|
@@ -1710,7 +1952,7 @@ function renderResourcePills(container, rids, state, onRemove) {
|
|
|
1710
1952
|
console.error("Failed to render thumbnail:", err);
|
|
1711
1953
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1712
1954
|
<div class="text-2xl mb-1">\u{1F4C1}</div>
|
|
1713
|
-
<div class="text-xs"
|
|
1955
|
+
<div class="text-xs">${escapeHtml(t("previewError", state))}</div>
|
|
1714
1956
|
</div>`;
|
|
1715
1957
|
});
|
|
1716
1958
|
if (onRemove) {
|
|
@@ -1743,15 +1985,20 @@ function renderResourcePills(container, rids, state, onRemove) {
|
|
|
1743
1985
|
if (fileInput) fileInput.click();
|
|
1744
1986
|
};
|
|
1745
1987
|
}
|
|
1746
|
-
|
|
1988
|
+
gridContainer.appendChild(slot);
|
|
1747
1989
|
}
|
|
1990
|
+
container.appendChild(gridContainer);
|
|
1991
|
+
const hintText = document.createElement("div");
|
|
1992
|
+
hintText.className = "text-center text-xs text-gray-500 mt-2";
|
|
1993
|
+
hintText.textContent = buildHintLine();
|
|
1994
|
+
container.appendChild(hintText);
|
|
1748
1995
|
}
|
|
1749
|
-
function renderThumbnailError(slot, iconSize = "w-12 h-12") {
|
|
1996
|
+
function renderThumbnailError(slot, state, iconSize = "w-12 h-12") {
|
|
1750
1997
|
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">
|
|
1998
|
+
<svg class="${escapeHtml(iconSize)} text-red-400" fill="currentColor" viewBox="0 0 24 24">
|
|
1752
1999
|
<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
2000
|
</svg>
|
|
1754
|
-
<div class="text-xs mt-1 text-red-600"
|
|
2001
|
+
<div class="text-xs mt-1 text-red-600">${escapeHtml(t("previewError", state))}</div>
|
|
1755
2002
|
</div>`;
|
|
1756
2003
|
}
|
|
1757
2004
|
async function renderThumbnailForResource(slot, rid, meta, state) {
|
|
@@ -1789,7 +2036,7 @@ async function renderThumbnailForResource(slot, rid, meta, state) {
|
|
|
1789
2036
|
if (state.config.onThumbnailError) {
|
|
1790
2037
|
state.config.onThumbnailError(err, rid);
|
|
1791
2038
|
}
|
|
1792
|
-
renderThumbnailError(slot);
|
|
2039
|
+
renderThumbnailError(slot, state);
|
|
1793
2040
|
}
|
|
1794
2041
|
} else {
|
|
1795
2042
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
@@ -1838,7 +2085,7 @@ async function renderThumbnailForResource(slot, rid, meta, state) {
|
|
|
1838
2085
|
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
1839
2086
|
<path d="M8 5v14l11-7z"/>
|
|
1840
2087
|
</svg>
|
|
1841
|
-
<div class="text-xs mt-1">${(meta == null ? void 0 : meta.name) || "Video"}</div>
|
|
2088
|
+
<div class="text-xs mt-1">${escapeHtml((meta == null ? void 0 : meta.name) || "Video")}</div>
|
|
1842
2089
|
</div>`;
|
|
1843
2090
|
}
|
|
1844
2091
|
} catch (error) {
|
|
@@ -1846,30 +2093,32 @@ async function renderThumbnailForResource(slot, rid, meta, state) {
|
|
|
1846
2093
|
if (state.config.onThumbnailError) {
|
|
1847
2094
|
state.config.onThumbnailError(err, rid);
|
|
1848
2095
|
}
|
|
1849
|
-
renderThumbnailError(slot, "w-8 h-8");
|
|
2096
|
+
renderThumbnailError(slot, state, "w-8 h-8");
|
|
1850
2097
|
}
|
|
1851
2098
|
} else {
|
|
1852
2099
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1853
2100
|
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
|
|
1854
2101
|
<path d="M8 5v14l11-7z"/>
|
|
1855
2102
|
</svg>
|
|
1856
|
-
<div class="text-xs mt-1">${(meta == null ? void 0 : meta.name) || "Video"}</div>
|
|
2103
|
+
<div class="text-xs mt-1">${escapeHtml((meta == null ? void 0 : meta.name) || "Video")}</div>
|
|
1857
2104
|
</div>`;
|
|
1858
2105
|
}
|
|
1859
2106
|
} else {
|
|
1860
2107
|
slot.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1861
2108
|
<div class="text-2xl mb-1">\u{1F4C1}</div>
|
|
1862
|
-
<div class="text-xs">${(meta == null ? void 0 : meta.name) || "File"}</div>
|
|
2109
|
+
<div class="text-xs">${escapeHtml((meta == null ? void 0 : meta.name) || "File")}</div>
|
|
1863
2110
|
</div>`;
|
|
1864
2111
|
}
|
|
1865
2112
|
}
|
|
1866
|
-
function setEmptyFileContainer(fileContainer, state) {
|
|
2113
|
+
function setEmptyFileContainer(fileContainer, state, hint) {
|
|
2114
|
+
const hintHtml = hint ? `<div class="text-xs text-gray-500 mt-1">${escapeHtml(hint)}</div>` : "";
|
|
1867
2115
|
fileContainer.innerHTML = `
|
|
1868
2116
|
<div class="flex flex-col items-center justify-center h-full text-gray-400">
|
|
1869
2117
|
<svg class="w-6 h-6 mb-2" fill="currentColor" viewBox="0 0 24 24">
|
|
1870
2118
|
<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
2119
|
</svg>
|
|
1872
|
-
<div class="text-sm text-center">${t("clickDragText", state)}</div>
|
|
2120
|
+
<div class="text-sm text-center">${escapeHtml(t("clickDragText", state))}</div>
|
|
2121
|
+
${hintHtml}
|
|
1873
2122
|
</div>
|
|
1874
2123
|
`;
|
|
1875
2124
|
}
|
|
@@ -2134,13 +2383,13 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2134
2383
|
console.error("Failed to render file preview:", err);
|
|
2135
2384
|
const emptyState = document.createElement("div");
|
|
2136
2385
|
emptyState.className = "aspect-video bg-gray-100 rounded-lg flex items-center justify-center text-gray-500";
|
|
2137
|
-
emptyState.innerHTML = `<div class="text-center"
|
|
2386
|
+
emptyState.innerHTML = `<div class="text-center">${escapeHtml(t("previewUnavailable", state))}</div>`;
|
|
2138
2387
|
wrapper.appendChild(emptyState);
|
|
2139
2388
|
});
|
|
2140
2389
|
} else {
|
|
2141
2390
|
const emptyState = document.createElement("div");
|
|
2142
2391
|
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>`;
|
|
2392
|
+
emptyState.innerHTML = `<div class="text-center">${escapeHtml(t("noFileSelected", state))}</div>`;
|
|
2144
2393
|
wrapper.appendChild(emptyState);
|
|
2145
2394
|
}
|
|
2146
2395
|
} else {
|
|
@@ -2184,7 +2433,8 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2184
2433
|
}
|
|
2185
2434
|
);
|
|
2186
2435
|
} else {
|
|
2187
|
-
|
|
2436
|
+
const hint = makeFieldHint(element, state);
|
|
2437
|
+
setEmptyFileContainer(fileContainer, state, hint);
|
|
2188
2438
|
}
|
|
2189
2439
|
fileContainer.onclick = fileUploadHandler;
|
|
2190
2440
|
setupDragAndDrop(fileContainer, dragHandler);
|
|
@@ -2203,18 +2453,6 @@ function renderFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2203
2453
|
};
|
|
2204
2454
|
fileWrapper.appendChild(fileContainer);
|
|
2205
2455
|
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
2456
|
wrapper.appendChild(fileWrapper);
|
|
2219
2457
|
}
|
|
2220
2458
|
}
|
|
@@ -2234,18 +2472,24 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
|
2234
2472
|
});
|
|
2235
2473
|
});
|
|
2236
2474
|
} 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>`;
|
|
2475
|
+
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
2476
|
}
|
|
2239
2477
|
wrapper.appendChild(resultsWrapper);
|
|
2240
2478
|
} else {
|
|
2241
2479
|
let updateFilesList2 = function() {
|
|
2242
|
-
renderResourcePills(
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2480
|
+
renderResourcePills(
|
|
2481
|
+
list,
|
|
2482
|
+
initialFiles,
|
|
2483
|
+
state,
|
|
2484
|
+
(ridToRemove) => {
|
|
2485
|
+
const index = initialFiles.indexOf(ridToRemove);
|
|
2486
|
+
if (index > -1) {
|
|
2487
|
+
initialFiles.splice(index, 1);
|
|
2488
|
+
}
|
|
2489
|
+
updateFilesList2();
|
|
2490
|
+
},
|
|
2491
|
+
filesFieldHint
|
|
2492
|
+
);
|
|
2249
2493
|
};
|
|
2250
2494
|
const filesWrapper = document.createElement("div");
|
|
2251
2495
|
filesWrapper.className = "space-y-2";
|
|
@@ -2263,6 +2507,7 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
|
2263
2507
|
list.className = "files-list";
|
|
2264
2508
|
const initialFiles = ctx.prefill[element.key] || [];
|
|
2265
2509
|
addPrefillFilesToIndex(initialFiles, state);
|
|
2510
|
+
const filesFieldHint = makeFieldHint(element, state);
|
|
2266
2511
|
updateFilesList2();
|
|
2267
2512
|
setupFilesDropHandler(
|
|
2268
2513
|
filesContainer,
|
|
@@ -2283,10 +2528,6 @@ function renderFilesElement(element, ctx, wrapper, pathKey) {
|
|
|
2283
2528
|
filesContainer.appendChild(list);
|
|
2284
2529
|
filesWrapper.appendChild(filesContainer);
|
|
2285
2530
|
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
2531
|
wrapper.appendChild(filesWrapper);
|
|
2291
2532
|
}
|
|
2292
2533
|
}
|
|
@@ -2308,7 +2549,7 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2308
2549
|
});
|
|
2309
2550
|
});
|
|
2310
2551
|
} 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>`;
|
|
2552
|
+
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
2553
|
}
|
|
2313
2554
|
wrapper.appendChild(resultsWrapper);
|
|
2314
2555
|
} else {
|
|
@@ -2328,19 +2569,24 @@ function renderMultipleFileElement(element, ctx, wrapper, pathKey) {
|
|
|
2328
2569
|
filesWrapper.appendChild(filesContainer);
|
|
2329
2570
|
const initialFiles = Array.isArray(ctx.prefill[element.key]) ? [...ctx.prefill[element.key]] : [];
|
|
2330
2571
|
addPrefillFilesToIndex(initialFiles, state);
|
|
2572
|
+
const multipleFilesHint = makeFieldHint(element, state);
|
|
2573
|
+
const buildCountInfo = () => {
|
|
2574
|
+
const countText = initialFiles.length === 1 ? t("fileCountSingle", state, { count: initialFiles.length }) : t("fileCountPlural", state, { count: initialFiles.length });
|
|
2575
|
+
const minMaxText = minFiles > 0 || maxFiles < Infinity ? ` ${t("fileCountRange", state, { min: minFiles, max: maxFiles })}` : "";
|
|
2576
|
+
return countText + minMaxText;
|
|
2577
|
+
};
|
|
2331
2578
|
const updateFilesDisplay = () => {
|
|
2332
|
-
renderResourcePills(
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
filesWrapper.appendChild(countInfo);
|
|
2579
|
+
renderResourcePills(
|
|
2580
|
+
filesContainer,
|
|
2581
|
+
initialFiles,
|
|
2582
|
+
state,
|
|
2583
|
+
(index) => {
|
|
2584
|
+
initialFiles.splice(initialFiles.indexOf(index), 1);
|
|
2585
|
+
updateFilesDisplay();
|
|
2586
|
+
},
|
|
2587
|
+
multipleFilesHint,
|
|
2588
|
+
buildCountInfo()
|
|
2589
|
+
);
|
|
2344
2590
|
};
|
|
2345
2591
|
setupFilesDropHandler(
|
|
2346
2592
|
filesContainer,
|
|
@@ -2370,16 +2616,17 @@ function validateFileElement(element, key, context) {
|
|
|
2370
2616
|
const validateFileCount = (key2, resourceIds, element2) => {
|
|
2371
2617
|
var _a2, _b;
|
|
2372
2618
|
if (skipValidation) return;
|
|
2619
|
+
const { state } = context;
|
|
2373
2620
|
const minFiles = "minCount" in element2 ? (_a2 = element2.minCount) != null ? _a2 : 0 : 0;
|
|
2374
2621
|
const maxFiles = "maxCount" in element2 ? (_b = element2.maxCount) != null ? _b : Infinity : Infinity;
|
|
2375
2622
|
if (element2.required && resourceIds.length === 0) {
|
|
2376
|
-
errors.push(`${key2}: required`);
|
|
2623
|
+
errors.push(`${key2}: ${t("required", state)}`);
|
|
2377
2624
|
}
|
|
2378
2625
|
if (resourceIds.length < minFiles) {
|
|
2379
|
-
errors.push(`${key2}:
|
|
2626
|
+
errors.push(`${key2}: ${t("minFiles", state, { min: minFiles })}`);
|
|
2380
2627
|
}
|
|
2381
2628
|
if (resourceIds.length > maxFiles) {
|
|
2382
|
-
errors.push(`${key2}:
|
|
2629
|
+
errors.push(`${key2}: ${t("maxFiles", state, { max: maxFiles })}`);
|
|
2383
2630
|
}
|
|
2384
2631
|
};
|
|
2385
2632
|
if (isMultipleField) {
|
|
@@ -2407,7 +2654,7 @@ function validateFileElement(element, key, context) {
|
|
|
2407
2654
|
);
|
|
2408
2655
|
const rid = (_a = input == null ? void 0 : input.value) != null ? _a : "";
|
|
2409
2656
|
if (!skipValidation && element.required && rid === "") {
|
|
2410
|
-
errors.push(`${key}: required`);
|
|
2657
|
+
errors.push(`${key}: ${t("required", context.state)}`);
|
|
2411
2658
|
return { value: null, errors };
|
|
2412
2659
|
}
|
|
2413
2660
|
return { value: rid || null, errors };
|
|
@@ -2656,7 +2903,7 @@ function renderColourElement(element, ctx, wrapper, pathKey) {
|
|
|
2656
2903
|
font-size: var(--fb-font-size-small);
|
|
2657
2904
|
color: var(--fb-text-secondary-color);
|
|
2658
2905
|
`;
|
|
2659
|
-
colourHint.textContent = makeFieldHint(element);
|
|
2906
|
+
colourHint.textContent = makeFieldHint(element, state);
|
|
2660
2907
|
wrapper.appendChild(colourHint);
|
|
2661
2908
|
}
|
|
2662
2909
|
function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
@@ -2747,36 +2994,51 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
|
2747
2994
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
2748
2995
|
});
|
|
2749
2996
|
}
|
|
2997
|
+
let addRow = null;
|
|
2998
|
+
let countDisplay = null;
|
|
2999
|
+
if (!state.config.readonly) {
|
|
3000
|
+
addRow = document.createElement("div");
|
|
3001
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
3002
|
+
const addBtn = document.createElement("button");
|
|
3003
|
+
addBtn.type = "button";
|
|
3004
|
+
addBtn.className = "add-colour-btn px-3 py-1 rounded";
|
|
3005
|
+
addBtn.style.cssText = `
|
|
3006
|
+
color: var(--fb-primary-color);
|
|
3007
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
3008
|
+
background-color: transparent;
|
|
3009
|
+
font-size: var(--fb-font-size);
|
|
3010
|
+
transition: all var(--fb-transition-duration);
|
|
3011
|
+
`;
|
|
3012
|
+
addBtn.textContent = "+";
|
|
3013
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
3014
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3015
|
+
});
|
|
3016
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
3017
|
+
addBtn.style.backgroundColor = "transparent";
|
|
3018
|
+
});
|
|
3019
|
+
addBtn.onclick = () => {
|
|
3020
|
+
const defaultColour = element.default || "#000000";
|
|
3021
|
+
values.push(defaultColour);
|
|
3022
|
+
addColourItem(defaultColour);
|
|
3023
|
+
updateAddButton();
|
|
3024
|
+
updateRemoveButtons();
|
|
3025
|
+
};
|
|
3026
|
+
countDisplay = document.createElement("span");
|
|
3027
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
3028
|
+
addRow.appendChild(addBtn);
|
|
3029
|
+
addRow.appendChild(countDisplay);
|
|
3030
|
+
wrapper.appendChild(addRow);
|
|
3031
|
+
}
|
|
2750
3032
|
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);
|
|
3033
|
+
if (!addRow || !countDisplay) return;
|
|
3034
|
+
const addBtn = addRow.querySelector(".add-colour-btn");
|
|
3035
|
+
if (addBtn) {
|
|
3036
|
+
const disabled = values.length >= maxCount;
|
|
3037
|
+
addBtn.disabled = disabled;
|
|
3038
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
3039
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
2779
3040
|
}
|
|
3041
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
2780
3042
|
}
|
|
2781
3043
|
values.forEach((value) => addColourItem(value));
|
|
2782
3044
|
updateAddButton();
|
|
@@ -2787,7 +3049,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
|
2787
3049
|
font-size: var(--fb-font-size-small);
|
|
2788
3050
|
color: var(--fb-text-secondary-color);
|
|
2789
3051
|
`;
|
|
2790
|
-
hint.textContent = makeFieldHint(element);
|
|
3052
|
+
hint.textContent = makeFieldHint(element, state);
|
|
2791
3053
|
wrapper.appendChild(hint);
|
|
2792
3054
|
}
|
|
2793
3055
|
function validateColourElement(element, key, context) {
|
|
@@ -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;
|
|
@@ -3101,7 +3373,7 @@ function renderSliderElement(element, ctx, wrapper, pathKey) {
|
|
|
3101
3373
|
font-size: var(--fb-font-size-small);
|
|
3102
3374
|
color: var(--fb-text-secondary-color);
|
|
3103
3375
|
`;
|
|
3104
|
-
hint.textContent = makeFieldHint(element);
|
|
3376
|
+
hint.textContent = makeFieldHint(element, state);
|
|
3105
3377
|
wrapper.appendChild(hint);
|
|
3106
3378
|
}
|
|
3107
3379
|
function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
@@ -3204,35 +3476,50 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
|
3204
3476
|
removeBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
3205
3477
|
});
|
|
3206
3478
|
}
|
|
3479
|
+
let addRow = null;
|
|
3480
|
+
let countDisplay = null;
|
|
3481
|
+
if (!state.config.readonly) {
|
|
3482
|
+
addRow = document.createElement("div");
|
|
3483
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
3484
|
+
const addBtn = document.createElement("button");
|
|
3485
|
+
addBtn.type = "button";
|
|
3486
|
+
addBtn.className = "add-slider-btn px-3 py-1 rounded";
|
|
3487
|
+
addBtn.style.cssText = `
|
|
3488
|
+
color: var(--fb-primary-color);
|
|
3489
|
+
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
3490
|
+
background-color: transparent;
|
|
3491
|
+
font-size: var(--fb-font-size);
|
|
3492
|
+
transition: all var(--fb-transition-duration);
|
|
3493
|
+
`;
|
|
3494
|
+
addBtn.textContent = "+";
|
|
3495
|
+
addBtn.addEventListener("mouseenter", () => {
|
|
3496
|
+
addBtn.style.backgroundColor = "var(--fb-background-hover-color)";
|
|
3497
|
+
});
|
|
3498
|
+
addBtn.addEventListener("mouseleave", () => {
|
|
3499
|
+
addBtn.style.backgroundColor = "transparent";
|
|
3500
|
+
});
|
|
3501
|
+
addBtn.onclick = () => {
|
|
3502
|
+
values.push(defaultValue);
|
|
3503
|
+
addSliderItem(defaultValue);
|
|
3504
|
+
updateAddButton();
|
|
3505
|
+
updateRemoveButtons();
|
|
3506
|
+
};
|
|
3507
|
+
countDisplay = document.createElement("span");
|
|
3508
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
3509
|
+
addRow.appendChild(addBtn);
|
|
3510
|
+
addRow.appendChild(countDisplay);
|
|
3511
|
+
wrapper.appendChild(addRow);
|
|
3512
|
+
}
|
|
3207
3513
|
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);
|
|
3514
|
+
if (!addRow || !countDisplay) return;
|
|
3515
|
+
const addBtn = addRow.querySelector(".add-slider-btn");
|
|
3516
|
+
if (addBtn) {
|
|
3517
|
+
const disabled = values.length >= maxCount;
|
|
3518
|
+
addBtn.disabled = disabled;
|
|
3519
|
+
addBtn.style.opacity = disabled ? "0.5" : "1";
|
|
3520
|
+
addBtn.style.pointerEvents = disabled ? "none" : "auto";
|
|
3235
3521
|
}
|
|
3522
|
+
countDisplay.textContent = `${values.length}/${maxCount === Infinity ? "\u221E" : maxCount}`;
|
|
3236
3523
|
}
|
|
3237
3524
|
values.forEach((value) => addSliderItem(value));
|
|
3238
3525
|
updateAddButton();
|
|
@@ -3243,7 +3530,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
|
3243
3530
|
font-size: var(--fb-font-size-small);
|
|
3244
3531
|
color: var(--fb-text-secondary-color);
|
|
3245
3532
|
`;
|
|
3246
|
-
hint.textContent = makeFieldHint(element);
|
|
3533
|
+
hint.textContent = makeFieldHint(element, state);
|
|
3247
3534
|
wrapper.appendChild(hint);
|
|
3248
3535
|
}
|
|
3249
3536
|
function validateSliderElement(element, key, context) {
|
|
@@ -3302,11 +3589,13 @@ function validateSliderElement(element, key, context) {
|
|
|
3302
3589
|
}
|
|
3303
3590
|
};
|
|
3304
3591
|
const validateSliderValue = (slider, fieldKey) => {
|
|
3592
|
+
const { state } = context;
|
|
3305
3593
|
const rawValue = slider.value;
|
|
3306
3594
|
if (!rawValue) {
|
|
3307
3595
|
if (!skipValidation && element.required) {
|
|
3308
|
-
|
|
3309
|
-
|
|
3596
|
+
const msg = t("required", state);
|
|
3597
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
3598
|
+
markValidity(slider, msg);
|
|
3310
3599
|
return null;
|
|
3311
3600
|
}
|
|
3312
3601
|
markValidity(slider, null);
|
|
@@ -3323,13 +3612,15 @@ function validateSliderElement(element, key, context) {
|
|
|
3323
3612
|
}
|
|
3324
3613
|
if (!skipValidation) {
|
|
3325
3614
|
if (value < min) {
|
|
3326
|
-
|
|
3327
|
-
|
|
3615
|
+
const msg = t("minValue", state, { min });
|
|
3616
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
3617
|
+
markValidity(slider, msg);
|
|
3328
3618
|
return value;
|
|
3329
3619
|
}
|
|
3330
3620
|
if (value > max) {
|
|
3331
|
-
|
|
3332
|
-
|
|
3621
|
+
const msg = t("maxValue", state, { max });
|
|
3622
|
+
errors.push(`${fieldKey}: ${msg}`);
|
|
3623
|
+
markValidity(slider, msg);
|
|
3333
3624
|
return value;
|
|
3334
3625
|
}
|
|
3335
3626
|
}
|
|
@@ -3346,17 +3637,18 @@ function validateSliderElement(element, key, context) {
|
|
|
3346
3637
|
values.push(value);
|
|
3347
3638
|
});
|
|
3348
3639
|
if (!skipValidation) {
|
|
3640
|
+
const { state } = context;
|
|
3349
3641
|
const minCount = (_b = element.minCount) != null ? _b : 1;
|
|
3350
3642
|
const maxCount = (_c = element.maxCount) != null ? _c : Infinity;
|
|
3351
3643
|
const filteredValues = values.filter((v) => v !== null);
|
|
3352
3644
|
if (element.required && filteredValues.length === 0) {
|
|
3353
|
-
errors.push(`${key}: required`);
|
|
3645
|
+
errors.push(`${key}: ${t("required", state)}`);
|
|
3354
3646
|
}
|
|
3355
3647
|
if (filteredValues.length < minCount) {
|
|
3356
|
-
errors.push(`${key}:
|
|
3648
|
+
errors.push(`${key}: ${t("minItems", state, { min: minCount })}`);
|
|
3357
3649
|
}
|
|
3358
3650
|
if (filteredValues.length > maxCount) {
|
|
3359
|
-
errors.push(`${key}:
|
|
3651
|
+
errors.push(`${key}: ${t("maxItems", state, { max: maxCount })}`);
|
|
3360
3652
|
}
|
|
3361
3653
|
}
|
|
3362
3654
|
return { value: values, errors };
|
|
@@ -3366,7 +3658,7 @@ function validateSliderElement(element, key, context) {
|
|
|
3366
3658
|
);
|
|
3367
3659
|
if (!slider) {
|
|
3368
3660
|
if (!skipValidation && element.required) {
|
|
3369
|
-
errors.push(`${key}: required`);
|
|
3661
|
+
errors.push(`${key}: ${t("required", context.state)}`);
|
|
3370
3662
|
}
|
|
3371
3663
|
return { value: null, errors };
|
|
3372
3664
|
}
|
|
@@ -3520,10 +3812,6 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
3520
3812
|
const containerWrap = document.createElement("div");
|
|
3521
3813
|
containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
|
|
3522
3814
|
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
3815
|
const itemsWrap = document.createElement("div");
|
|
3528
3816
|
const columns = element.columns || 1;
|
|
3529
3817
|
if (columns === 1) {
|
|
@@ -3531,8 +3819,6 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
3531
3819
|
} else {
|
|
3532
3820
|
itemsWrap.className = `grid grid-cols-${columns} gap-4`;
|
|
3533
3821
|
}
|
|
3534
|
-
containerWrap.appendChild(header);
|
|
3535
|
-
header.appendChild(left);
|
|
3536
3822
|
if (!ctx.state.config.readonly) {
|
|
3537
3823
|
const hintsElement = createPrefillHints(element, pathKey);
|
|
3538
3824
|
if (hintsElement) {
|
|
@@ -3553,7 +3839,6 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
3553
3839
|
}
|
|
3554
3840
|
});
|
|
3555
3841
|
containerWrap.appendChild(itemsWrap);
|
|
3556
|
-
left.innerHTML = `<span>${element.label || element.key}</span>`;
|
|
3557
3842
|
wrapper.appendChild(containerWrap);
|
|
3558
3843
|
}
|
|
3559
3844
|
function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
@@ -3561,14 +3846,10 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3561
3846
|
const state = ctx.state;
|
|
3562
3847
|
const containerWrap = document.createElement("div");
|
|
3563
3848
|
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";
|
|
3849
|
+
const countDisplay = document.createElement("span");
|
|
3850
|
+
countDisplay.className = "text-sm text-gray-500";
|
|
3568
3851
|
const itemsWrap = document.createElement("div");
|
|
3569
3852
|
itemsWrap.className = "space-y-4";
|
|
3570
|
-
containerWrap.appendChild(header);
|
|
3571
|
-
header.appendChild(left);
|
|
3572
3853
|
if (!ctx.state.config.readonly) {
|
|
3573
3854
|
const hintsElement = createPrefillHints(element, element.key);
|
|
3574
3855
|
if (hintsElement) {
|
|
@@ -3582,7 +3863,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3582
3863
|
const createAddButton = () => {
|
|
3583
3864
|
const add = document.createElement("button");
|
|
3584
3865
|
add.type = "button";
|
|
3585
|
-
add.className = "add-container-btn
|
|
3866
|
+
add.className = "add-container-btn px-3 py-1 rounded";
|
|
3586
3867
|
add.style.cssText = `
|
|
3587
3868
|
color: var(--fb-primary-color);
|
|
3588
3869
|
border: var(--fb-border-width) solid var(--fb-primary-color);
|
|
@@ -3663,7 +3944,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3663
3944
|
existingAddBtn.style.opacity = currentCount >= max ? "0.5" : "1";
|
|
3664
3945
|
existingAddBtn.style.pointerEvents = currentCount >= max ? "none" : "auto";
|
|
3665
3946
|
}
|
|
3666
|
-
|
|
3947
|
+
countDisplay.textContent = `${currentCount}/${max === Infinity ? "\u221E" : max}`;
|
|
3667
3948
|
};
|
|
3668
3949
|
if (pre && Array.isArray(pre)) {
|
|
3669
3950
|
pre.forEach((prefillObj, idx) => {
|
|
@@ -3771,7 +4052,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
3771
4052
|
}
|
|
3772
4053
|
containerWrap.appendChild(itemsWrap);
|
|
3773
4054
|
if (!state.config.readonly) {
|
|
3774
|
-
|
|
4055
|
+
const addRow = document.createElement("div");
|
|
4056
|
+
addRow.className = "flex items-center gap-3 mt-2";
|
|
4057
|
+
addRow.appendChild(createAddButton());
|
|
4058
|
+
addRow.appendChild(countDisplay);
|
|
4059
|
+
containerWrap.appendChild(addRow);
|
|
3775
4060
|
}
|
|
3776
4061
|
updateAddButton();
|
|
3777
4062
|
wrapper.appendChild(containerWrap);
|
|
@@ -3797,16 +4082,17 @@ function validateContainerElement(element, key, context) {
|
|
|
3797
4082
|
const validateContainerCount = (key2, items, element2) => {
|
|
3798
4083
|
var _a, _b;
|
|
3799
4084
|
if (skipValidation) return;
|
|
4085
|
+
const { state } = context;
|
|
3800
4086
|
const minItems = "minCount" in element2 ? (_a = element2.minCount) != null ? _a : 0 : 0;
|
|
3801
4087
|
const maxItems = "maxCount" in element2 ? (_b = element2.maxCount) != null ? _b : Infinity : Infinity;
|
|
3802
4088
|
if (element2.required && items.length === 0) {
|
|
3803
|
-
errors.push(`${key2}: required`);
|
|
4089
|
+
errors.push(`${key2}: ${t("required", state)}`);
|
|
3804
4090
|
}
|
|
3805
4091
|
if (items.length < minItems) {
|
|
3806
|
-
errors.push(`${key2}:
|
|
4092
|
+
errors.push(`${key2}: ${t("minItems", state, { min: minItems })}`);
|
|
3807
4093
|
}
|
|
3808
4094
|
if (items.length > maxItems) {
|
|
3809
|
-
errors.push(`${key2}:
|
|
4095
|
+
errors.push(`${key2}: ${t("maxItems", state, { max: maxItems })}`);
|
|
3810
4096
|
}
|
|
3811
4097
|
};
|
|
3812
4098
|
if ("multiple" in element && element.multiple) {
|
|
@@ -4360,7 +4646,7 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
|
|
|
4360
4646
|
default: {
|
|
4361
4647
|
const unsupported = document.createElement("div");
|
|
4362
4648
|
unsupported.className = "text-red-500 text-sm";
|
|
4363
|
-
unsupported.textContent =
|
|
4649
|
+
unsupported.textContent = t("unsupportedFieldType", ctx.state, { type: element.type });
|
|
4364
4650
|
wrapper.appendChild(unsupported);
|
|
4365
4651
|
}
|
|
4366
4652
|
}
|
|
@@ -4404,31 +4690,112 @@ var defaultConfig = {
|
|
|
4404
4690
|
locale: "en",
|
|
4405
4691
|
translations: {
|
|
4406
4692
|
en: {
|
|
4407
|
-
|
|
4693
|
+
// UI texts
|
|
4408
4694
|
removeElement: "Remove",
|
|
4409
|
-
uploadText: "Upload",
|
|
4410
|
-
dragDropText: "or drag and drop files",
|
|
4411
|
-
dragDropTextSingle: "or drag and drop file",
|
|
4412
4695
|
clickDragText: "Click or drag file",
|
|
4696
|
+
clickDragTextMultiple: "Click or drag files",
|
|
4413
4697
|
noFileSelected: "No file selected",
|
|
4414
4698
|
noFilesSelected: "No files selected",
|
|
4415
|
-
downloadButton: "Download"
|
|
4699
|
+
downloadButton: "Download",
|
|
4700
|
+
changeButton: "Change",
|
|
4701
|
+
placeholderText: "Enter text",
|
|
4702
|
+
previewAlt: "Preview",
|
|
4703
|
+
previewUnavailable: "Preview unavailable",
|
|
4704
|
+
previewError: "Preview error",
|
|
4705
|
+
videoNotSupported: "Your browser does not support the video tag.",
|
|
4706
|
+
// Field hints
|
|
4707
|
+
hintLengthRange: "{min}-{max} chars",
|
|
4708
|
+
hintMaxLength: "\u2264{max} chars",
|
|
4709
|
+
hintMinLength: "\u2265{min} chars",
|
|
4710
|
+
hintValueRange: "{min}-{max}",
|
|
4711
|
+
hintMaxValue: "\u2264{max}",
|
|
4712
|
+
hintMinValue: "\u2265{min}",
|
|
4713
|
+
hintMaxSize: "\u2264{size}MB",
|
|
4714
|
+
hintFormats: "{formats}",
|
|
4715
|
+
hintRequired: "Required",
|
|
4716
|
+
hintOptional: "Optional",
|
|
4717
|
+
hintPattern: "Format: {pattern}",
|
|
4718
|
+
fileCountSingle: "{count} file",
|
|
4719
|
+
fileCountPlural: "{count} files",
|
|
4720
|
+
fileCountRange: "({min}-{max})",
|
|
4721
|
+
// Validation errors
|
|
4722
|
+
required: "Required",
|
|
4723
|
+
minItems: "Minimum {min} items required",
|
|
4724
|
+
maxItems: "Maximum {max} items allowed",
|
|
4725
|
+
minLength: "Minimum {min} characters",
|
|
4726
|
+
maxLength: "Maximum {max} characters",
|
|
4727
|
+
minValue: "Must be at least {min}",
|
|
4728
|
+
maxValue: "Must be at most {max}",
|
|
4729
|
+
patternMismatch: "Invalid format",
|
|
4730
|
+
invalidPattern: "Invalid pattern in schema",
|
|
4731
|
+
notANumber: "Must be a number",
|
|
4732
|
+
invalidHexColour: "Invalid hex color",
|
|
4733
|
+
minFiles: "Minimum {min} files required",
|
|
4734
|
+
maxFiles: "Maximum {max} files allowed",
|
|
4735
|
+
unsupportedFieldType: "Unsupported field type: {type}"
|
|
4416
4736
|
},
|
|
4417
4737
|
ru: {
|
|
4418
|
-
|
|
4738
|
+
// UI texts
|
|
4419
4739
|
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
4740
|
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",
|
|
4741
|
+
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
4742
|
noFileSelected: "\u0424\u0430\u0439\u043B \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D",
|
|
4425
4743
|
noFilesSelected: "\u041D\u0435\u0442 \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
4426
|
-
downloadButton: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C"
|
|
4744
|
+
downloadButton: "\u0421\u043A\u0430\u0447\u0430\u0442\u044C",
|
|
4745
|
+
changeButton: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C",
|
|
4746
|
+
placeholderText: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442",
|
|
4747
|
+
previewAlt: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440",
|
|
4748
|
+
previewUnavailable: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D",
|
|
4749
|
+
previewError: "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430",
|
|
4750
|
+
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.",
|
|
4751
|
+
// Field hints
|
|
4752
|
+
hintLengthRange: "{min}-{max} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
|
|
4753
|
+
hintMaxLength: "\u2264{max} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
|
|
4754
|
+
hintMinLength: "\u2265{min} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
|
|
4755
|
+
hintValueRange: "{min}-{max}",
|
|
4756
|
+
hintMaxValue: "\u2264{max}",
|
|
4757
|
+
hintMinValue: "\u2265{min}",
|
|
4758
|
+
hintMaxSize: "\u2264{size}\u041C\u0411",
|
|
4759
|
+
hintFormats: "{formats}",
|
|
4760
|
+
hintRequired: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435",
|
|
4761
|
+
hintOptional: "\u041D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435",
|
|
4762
|
+
hintPattern: "\u0424\u043E\u0440\u043C\u0430\u0442: {pattern}",
|
|
4763
|
+
fileCountSingle: "{count} \u0444\u0430\u0439\u043B",
|
|
4764
|
+
fileCountPlural: "{count} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
4765
|
+
fileCountRange: "({min}-{max})",
|
|
4766
|
+
// Validation errors
|
|
4767
|
+
required: "\u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u043F\u043E\u043B\u0435",
|
|
4768
|
+
minItems: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",
|
|
4769
|
+
maxItems: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432",
|
|
4770
|
+
minLength: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
|
|
4771
|
+
maxLength: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
|
|
4772
|
+
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}",
|
|
4773
|
+
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}",
|
|
4774
|
+
patternMismatch: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442",
|
|
4775
|
+
invalidPattern: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u043F\u0430\u0442\u0442\u0435\u0440\u043D \u0432 \u0441\u0445\u0435\u043C\u0435",
|
|
4776
|
+
notANumber: "\u0414\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0447\u0438\u0441\u043B\u043E\u043C",
|
|
4777
|
+
invalidHexColour: "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0446\u0432\u0435\u0442\u0430",
|
|
4778
|
+
minFiles: "\u041C\u0438\u043D\u0438\u043C\u0443\u043C {min} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
4779
|
+
maxFiles: "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C {max} \u0444\u0430\u0439\u043B\u043E\u0432",
|
|
4780
|
+
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
4781
|
}
|
|
4428
4782
|
},
|
|
4429
4783
|
theme: {}
|
|
4430
4784
|
};
|
|
4431
4785
|
function createInstanceState(config) {
|
|
4786
|
+
const mergedTranslations = {
|
|
4787
|
+
...defaultConfig.translations
|
|
4788
|
+
};
|
|
4789
|
+
if (config == null ? void 0 : config.translations) {
|
|
4790
|
+
for (const [locale, userTranslations] of Object.entries(
|
|
4791
|
+
config.translations
|
|
4792
|
+
)) {
|
|
4793
|
+
mergedTranslations[locale] = {
|
|
4794
|
+
...defaultConfig.translations[locale] || {},
|
|
4795
|
+
...userTranslations
|
|
4796
|
+
};
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4432
4799
|
return {
|
|
4433
4800
|
schema: null,
|
|
4434
4801
|
formRoot: null,
|
|
@@ -4437,7 +4804,8 @@ function createInstanceState(config) {
|
|
|
4437
4804
|
version: "1.0.0",
|
|
4438
4805
|
config: {
|
|
4439
4806
|
...defaultConfig,
|
|
4440
|
-
...config
|
|
4807
|
+
...config,
|
|
4808
|
+
translations: mergedTranslations
|
|
4441
4809
|
},
|
|
4442
4810
|
debounceTimer: null
|
|
4443
4811
|
};
|