@dmitryvim/form-builder 0.3.3 → 0.4.0
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 +164 -120
- package/dist/browser/formbuilder.v0.4.0.min.js +1485 -0
- package/dist/cjs/index.cjs +145 -81
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/index.js +139 -79
- package/dist/esm/index.js.map +1 -1
- package/dist/form-builder.js +164 -120
- package/dist/types/components/file/library.d.ts +1 -1
- package/dist/types/components/textarea.d.ts +4 -2
- package/dist/types/styles/theme.d.ts +3 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/schema.d.ts +23 -0
- package/dist/types/utils/styles.d.ts +3 -1
- package/package.json +19 -19
- package/dist/browser/formbuilder.v0.3.3.min.js +0 -1441
package/dist/esm/index.js
CHANGED
|
@@ -465,20 +465,60 @@ function ensureThemingHooks(doc) {
|
|
|
465
465
|
color: #ffffff;
|
|
466
466
|
border-color: var(--fb-primary-color);
|
|
467
467
|
}
|
|
468
|
+
/* \u2500\u2500\u2500 Row layout: flex-wrap container with per-child width tokens \u2500\u2500\u2500
|
|
469
|
+
Replaces the older Tailwind \`space-y-2\` class. Children without
|
|
470
|
+
data-fb-width (or width="full") take 100% basis \u2192 forced wrap \u2192 behaves
|
|
471
|
+
like a vertical stack. Mixed widths sit side-by-side in the same row. */
|
|
472
|
+
.fb-row {
|
|
473
|
+
display: flex;
|
|
474
|
+
flex-wrap: wrap;
|
|
475
|
+
gap: var(--fb-row-gap, 0.25rem);
|
|
476
|
+
align-items: flex-start;
|
|
477
|
+
}
|
|
478
|
+
.fb-row > [data-fb-width="full"],
|
|
479
|
+
.fb-row > :not([data-fb-width]) { flex: 0 0 100%; min-width: 0; }
|
|
480
|
+
.fb-row > [data-fb-width="1/2"] { flex: 0 0 calc(50% - var(--fb-row-gap, 0.25rem) / 2); min-width: 0; }
|
|
481
|
+
.fb-row > [data-fb-width="1/3"] { flex: 0 0 calc(33.333% - var(--fb-row-gap, 0.25rem) * 2 / 3); min-width: 0; }
|
|
482
|
+
.fb-row > [data-fb-width="2/3"] { flex: 0 0 calc(66.666% - var(--fb-row-gap, 0.25rem) / 3); min-width: 0; }
|
|
483
|
+
/* \u2500\u2500\u2500 Density tokens: re-bind --fb-input-padding-* and --fb-font-size at
|
|
484
|
+
the wrapper level so every component that reads those vars (text, textarea,
|
|
485
|
+
select, number, colour, switcher, file) picks the size up via cascade. md
|
|
486
|
+
uses the defaults set in theme.ts (2px / 8px / 14px). The scale primarily
|
|
487
|
+
varies font-size; vertical padding stays close to md so an "lg" heading
|
|
488
|
+
field does not visually break the form's row rhythm \u2014 only the typography
|
|
489
|
+
announces "this is bigger". Picaz theme overrides padding directly for
|
|
490
|
+
its own density, independent of this scale. */
|
|
491
|
+
/* size-token side-effects on file tiles: --fb-file-wide-min controls the
|
|
492
|
+
single-file empty dropzone height, --fb-file-zone-min controls the inner
|
|
493
|
+
upload/library zones. Both read in src/components/file/styles.ts. */
|
|
494
|
+
.fb-size-xs { --fb-input-padding-y: 1px; --fb-input-padding-x: 6px; --fb-font-size: 12px; --fb-font-size-small: 10px; --fb-line-height: 1.25; --fb-file-wide-min: 64px; --fb-file-zone-min: 48px; --fb-file-zone-padding: 6px; }
|
|
495
|
+
.fb-size-sm { --fb-input-padding-y: 2px; --fb-input-padding-x: 6px; --fb-font-size: 13px; --fb-font-size-small: 11px; --fb-line-height: 1.35; --fb-file-wide-min: 80px; --fb-file-zone-min: 60px; --fb-file-zone-padding: 7px; }
|
|
496
|
+
/* .fb-size-md uses defaults \u2014 no override needed */
|
|
497
|
+
.fb-size-lg { --fb-input-padding-y: 2px; --fb-input-padding-x: 8px; --fb-font-size: 16px; --fb-font-size-small: 13px; --fb-file-wide-min: 128px; --fb-file-zone-min: 96px; --fb-file-zone-padding: 12px; }
|
|
498
|
+
.fb-size-xl { --fb-input-padding-y: 3px; --fb-input-padding-x: 8px; --fb-font-size: 18px; --fb-font-size-small: 14px; --fb-file-wide-min: 160px; --fb-file-zone-min: 120px; --fb-file-zone-padding: 14px; }
|
|
468
499
|
`;
|
|
469
500
|
doc.head.appendChild(style);
|
|
470
501
|
}
|
|
471
|
-
function applyAutoExpand(textarea) {
|
|
502
|
+
function applyAutoExpand(textarea, options = {}) {
|
|
472
503
|
textarea.style.overflow = "hidden";
|
|
473
504
|
textarea.style.resize = "none";
|
|
505
|
+
const minRows = Math.max(1, options.minRows ?? 1);
|
|
474
506
|
const lineCount = (textarea.value.match(/\n/g) || []).length + 1;
|
|
475
|
-
textarea.rows = Math.max(
|
|
507
|
+
textarea.rows = Math.max(minRows, lineCount);
|
|
476
508
|
const resize = () => {
|
|
477
509
|
if (!textarea.isConnected) return;
|
|
478
|
-
textarea.style.height = "
|
|
510
|
+
textarea.style.height = "";
|
|
479
511
|
const cs = getComputedStyle(textarea);
|
|
480
512
|
const borderY = parseFloat(cs.borderTopWidth || "0") + parseFloat(cs.borderBottomWidth || "0");
|
|
481
|
-
|
|
513
|
+
const lineHeight = parseFloat(cs.lineHeight || "0") || parseFloat(cs.fontSize || "0") * 1.5 || 20;
|
|
514
|
+
const padY = parseFloat(cs.paddingTop || "0") + parseFloat(cs.paddingBottom || "0");
|
|
515
|
+
const minHeight = lineHeight * minRows + padY + borderY;
|
|
516
|
+
if (textarea.value === "") {
|
|
517
|
+
textarea.style.height = `${minHeight}px`;
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const contentHeight = textarea.scrollHeight + borderY;
|
|
521
|
+
textarea.style.height = `${Math.max(contentHeight, minHeight)}px`;
|
|
482
522
|
};
|
|
483
523
|
textarea.addEventListener("input", resize);
|
|
484
524
|
setTimeout(() => {
|
|
@@ -530,8 +570,8 @@ function createAddItemRow(classNameSuffix, onClick, options = {}) {
|
|
|
530
570
|
const label = options.label ?? "";
|
|
531
571
|
const showCounter = options.showCounter !== false;
|
|
532
572
|
const row = document.createElement("div");
|
|
533
|
-
row.className = "fb-add-row
|
|
534
|
-
row.style.cssText = "display:flex;align-items:stretch;width:100%;";
|
|
573
|
+
row.className = "fb-add-row";
|
|
574
|
+
row.style.cssText = "display:flex;align-items:stretch;width:100%;margin-top:4px;";
|
|
535
575
|
const button = document.createElement("button");
|
|
536
576
|
button.type = "button";
|
|
537
577
|
button.className = `add-${classNameSuffix}-btn`;
|
|
@@ -540,8 +580,8 @@ function createAddItemRow(classNameSuffix, onClick, options = {}) {
|
|
|
540
580
|
display: inline-flex;
|
|
541
581
|
align-items: center;
|
|
542
582
|
justify-content: center;
|
|
543
|
-
gap:
|
|
544
|
-
padding:
|
|
583
|
+
gap: 4px;
|
|
584
|
+
padding: 3px 10px;
|
|
545
585
|
border: 1px dashed var(--fb-primary-color);
|
|
546
586
|
border-radius: var(--fb-border-radius);
|
|
547
587
|
background: transparent;
|
|
@@ -686,12 +726,12 @@ function ensureChipStyles(doc) {
|
|
|
686
726
|
const style = doc.createElement("style");
|
|
687
727
|
style.setAttribute("data-fb-chip-styles", "");
|
|
688
728
|
style.textContent = `
|
|
689
|
-
.fb-chip-list { display: flex; flex-direction: column; gap:
|
|
729
|
+
.fb-chip-list { display: flex; flex-direction: column; gap: 2px; }
|
|
690
730
|
.fb-chip {
|
|
691
731
|
display: flex;
|
|
692
732
|
align-items: center;
|
|
693
|
-
gap:
|
|
694
|
-
padding:
|
|
733
|
+
gap: 6px;
|
|
734
|
+
padding: 3px 6px 3px 8px;
|
|
695
735
|
background: var(--fb-chip-bg, var(--fb-background-color, #fff));
|
|
696
736
|
border: 1px solid var(--fb-chip-border, var(--fb-border-color, #e2e8f0));
|
|
697
737
|
border-radius: 6px;
|
|
@@ -717,7 +757,13 @@ function ensureChipStyles(doc) {
|
|
|
717
757
|
color: var(--fb-chip-text, var(--fb-text-color, inherit));
|
|
718
758
|
font-size: var(--fb-font-size, 14px);
|
|
719
759
|
font-family: var(--fb-font-family, inherit);
|
|
720
|
-
line-height: 1.4;
|
|
760
|
+
line-height: var(--fb-line-height, 1.4);
|
|
761
|
+
/* Soft-wrap support (v0.4.0): chip-input is now a <textarea rows=1> that
|
|
762
|
+
grows vertically when content overflows the chip width. */
|
|
763
|
+
resize: none;
|
|
764
|
+
overflow: hidden;
|
|
765
|
+
word-break: break-word;
|
|
766
|
+
overflow-wrap: anywhere;
|
|
721
767
|
}
|
|
722
768
|
.fb-chip-input::placeholder { color: var(--fb-text-placeholder-color, #94a3b8); }
|
|
723
769
|
.fb-chip-input:read-only { color: var(--fb-text-secondary-color, #475569); }
|
|
@@ -802,7 +848,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
|
|
|
802
848
|
overflow-wrap: anywhere;
|
|
803
849
|
`;
|
|
804
850
|
textInput.name = pathKey;
|
|
805
|
-
textInput.placeholder = element.placeholder
|
|
851
|
+
textInput.placeholder = element.placeholder ?? "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
|
|
806
852
|
textInput.value = ctx.prefill[element.key] || element.default || "";
|
|
807
853
|
textInput.readOnly = readonly;
|
|
808
854
|
applySingleLineMode(textInput);
|
|
@@ -848,7 +894,7 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
848
894
|
const readonly = isElementReadonly(element, state, ctx);
|
|
849
895
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
850
896
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
851
|
-
const minCount = element.minCount ?? 1;
|
|
897
|
+
const minCount = element.minCount ?? (element.required ? 1 : 0);
|
|
852
898
|
const maxCount = element.maxCount ?? Infinity;
|
|
853
899
|
while (values.length < minCount) {
|
|
854
900
|
values.push(element.default || "");
|
|
@@ -875,8 +921,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
875
921
|
dot.className = "fb-chip-dot";
|
|
876
922
|
dot.setAttribute("aria-hidden", "true");
|
|
877
923
|
chip.appendChild(dot);
|
|
878
|
-
const input = document.createElement("
|
|
879
|
-
input.
|
|
924
|
+
const input = document.createElement("textarea");
|
|
925
|
+
input.rows = 1;
|
|
880
926
|
input.className = "fb-chip-input";
|
|
881
927
|
input.value = value;
|
|
882
928
|
input.placeholder = element.placeholder || t("placeholderText", state);
|
|
@@ -892,6 +938,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
|
|
|
892
938
|
input.addEventListener("blur", handleChange);
|
|
893
939
|
input.addEventListener("input", handleChange);
|
|
894
940
|
}
|
|
941
|
+
applySingleLineMode(input);
|
|
942
|
+
applyAutoExpand(input);
|
|
895
943
|
if (!readonly) {
|
|
896
944
|
const rem = document.createElement("button");
|
|
897
945
|
rem.type = "button";
|
|
@@ -1034,7 +1082,7 @@ function validateTextElement(element, key, context) {
|
|
|
1034
1082
|
});
|
|
1035
1083
|
if (!skipValidation) {
|
|
1036
1084
|
const { state } = context;
|
|
1037
|
-
const minCount = element.minCount ??
|
|
1085
|
+
const minCount = element.minCount ?? 0;
|
|
1038
1086
|
const maxCount = element.maxCount ?? Infinity;
|
|
1039
1087
|
const filteredValues = rawValues.filter((v) => v.trim() !== "");
|
|
1040
1088
|
if (element.required && filteredValues.length === 0) {
|
|
@@ -1110,13 +1158,13 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1110
1158
|
const textareaInput = document.createElement("textarea");
|
|
1111
1159
|
textareaInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
|
|
1112
1160
|
textareaInput.style.cssText = `
|
|
1113
|
-
padding: var(--fb-input-padding-y) var(--fb-input-padding-x)
|
|
1161
|
+
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
1114
1162
|
font-size: var(--fb-font-size);
|
|
1115
1163
|
font-family: var(--fb-font-family);
|
|
1164
|
+
line-height: var(--fb-line-height, 1.5);
|
|
1116
1165
|
`;
|
|
1117
1166
|
textareaInput.name = pathKey;
|
|
1118
|
-
textareaInput.placeholder = element.placeholder
|
|
1119
|
-
textareaInput.rows = element.rows || 4;
|
|
1167
|
+
textareaInput.placeholder = element.placeholder ?? "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
|
|
1120
1168
|
textareaInput.value = ctx.prefill[element.key] || element.default || "";
|
|
1121
1169
|
textareaInput.readOnly = readonly;
|
|
1122
1170
|
if (!readonly && ctx.instance) {
|
|
@@ -1127,9 +1175,7 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1127
1175
|
textareaInput.addEventListener("blur", handleChange);
|
|
1128
1176
|
textareaInput.addEventListener("input", handleChange);
|
|
1129
1177
|
}
|
|
1130
|
-
|
|
1131
|
-
applyAutoExpand(textareaInput);
|
|
1132
|
-
}
|
|
1178
|
+
applyAutoExpand(textareaInput, { minRows: element.rows ?? 1 });
|
|
1133
1179
|
textareaWrapper.appendChild(textareaInput);
|
|
1134
1180
|
if (!readonly && (element.minLength != null || element.maxLength != null)) {
|
|
1135
1181
|
const counter = createCharCounter(element, textareaInput);
|
|
@@ -1142,13 +1188,13 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1142
1188
|
const readonly = isElementReadonly(element, state, ctx);
|
|
1143
1189
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
1144
1190
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
1145
|
-
const minCount = element.minCount ?? 1;
|
|
1191
|
+
const minCount = element.minCount ?? (element.required ? 1 : 0);
|
|
1146
1192
|
const maxCount = element.maxCount ?? Infinity;
|
|
1147
1193
|
while (values.length < minCount) {
|
|
1148
1194
|
values.push(element.default || "");
|
|
1149
1195
|
}
|
|
1150
1196
|
const container = document.createElement("div");
|
|
1151
|
-
container.className = "
|
|
1197
|
+
container.className = "fb-row";
|
|
1152
1198
|
wrapper.appendChild(container);
|
|
1153
1199
|
function updateIndices() {
|
|
1154
1200
|
const items = container.querySelectorAll(".multiple-textarea-item");
|
|
@@ -1167,12 +1213,12 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1167
1213
|
const textareaInput = document.createElement("textarea");
|
|
1168
1214
|
textareaInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
|
|
1169
1215
|
textareaInput.style.cssText = `
|
|
1170
|
-
padding: var(--fb-input-padding-y) var(--fb-input-padding-x)
|
|
1216
|
+
padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
|
|
1171
1217
|
font-size: var(--fb-font-size);
|
|
1172
1218
|
font-family: var(--fb-font-family);
|
|
1219
|
+
line-height: var(--fb-line-height, 1.5);
|
|
1173
1220
|
`;
|
|
1174
1221
|
textareaInput.placeholder = element.placeholder || t("placeholderText", state);
|
|
1175
|
-
textareaInput.rows = element.rows || 4;
|
|
1176
1222
|
textareaInput.value = value;
|
|
1177
1223
|
textareaInput.readOnly = readonly;
|
|
1178
1224
|
if (!readonly && ctx.instance) {
|
|
@@ -1183,9 +1229,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
|
|
|
1183
1229
|
textareaInput.addEventListener("blur", handleChange);
|
|
1184
1230
|
textareaInput.addEventListener("input", handleChange);
|
|
1185
1231
|
}
|
|
1186
|
-
|
|
1187
|
-
applyAutoExpand(textareaInput);
|
|
1188
|
-
}
|
|
1232
|
+
applyAutoExpand(textareaInput, { minRows: element.rows ?? 1 });
|
|
1189
1233
|
textareaContainer.appendChild(textareaInput);
|
|
1190
1234
|
if (!readonly && (element.minLength != null || element.maxLength != null)) {
|
|
1191
1235
|
const counter = createCharCounter(element, textareaInput);
|
|
@@ -1261,9 +1305,7 @@ function validateTextareaElement(element, key, context) {
|
|
|
1261
1305
|
}
|
|
1262
1306
|
function updateTextareaField(element, fieldPath, value, context) {
|
|
1263
1307
|
updateTextField(element, fieldPath, value, context);
|
|
1264
|
-
const { scopeRoot
|
|
1265
|
-
const shouldAutoExpand = element.autoExpand || isElementReadonly(element, state);
|
|
1266
|
-
if (!shouldAutoExpand) return;
|
|
1308
|
+
const { scopeRoot } = context;
|
|
1267
1309
|
if (element.multiple) {
|
|
1268
1310
|
const textareas = scopeRoot.querySelectorAll(
|
|
1269
1311
|
`textarea[name^="${fieldPath}["]`
|
|
@@ -1452,13 +1494,13 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
|
|
|
1452
1494
|
const readonly = isElementReadonly(element, state, ctx);
|
|
1453
1495
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
1454
1496
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
1455
|
-
const minCount = element.minCount ?? 1;
|
|
1497
|
+
const minCount = element.minCount ?? (element.required ? 1 : 0);
|
|
1456
1498
|
const maxCount = element.maxCount ?? Infinity;
|
|
1457
1499
|
while (values.length < minCount) {
|
|
1458
1500
|
values.push(element.default || "");
|
|
1459
1501
|
}
|
|
1460
1502
|
const container = document.createElement("div");
|
|
1461
|
-
container.className = "
|
|
1503
|
+
container.className = "fb-row";
|
|
1462
1504
|
wrapper.appendChild(container);
|
|
1463
1505
|
function updateIndices() {
|
|
1464
1506
|
const items = container.querySelectorAll(".multiple-number-item");
|
|
@@ -1623,7 +1665,7 @@ function validateNumberElement(element, key, context) {
|
|
|
1623
1665
|
};
|
|
1624
1666
|
if (element.multiple) {
|
|
1625
1667
|
const inputs = scopeRoot.querySelectorAll(
|
|
1626
|
-
`[name^="${key}["]`
|
|
1668
|
+
`[name^="${key}\\["]`
|
|
1627
1669
|
);
|
|
1628
1670
|
const values = [];
|
|
1629
1671
|
inputs.forEach((input, index) => {
|
|
@@ -1647,7 +1689,7 @@ function validateNumberElement(element, key, context) {
|
|
|
1647
1689
|
});
|
|
1648
1690
|
if (!skipValidation) {
|
|
1649
1691
|
const { state } = context;
|
|
1650
|
-
const minCount = element.minCount ??
|
|
1692
|
+
const minCount = element.minCount ?? 0;
|
|
1651
1693
|
const maxCount = element.maxCount ?? Infinity;
|
|
1652
1694
|
const filteredValues = values.filter((v) => v !== null);
|
|
1653
1695
|
if (element.required && filteredValues.length === 0) {
|
|
@@ -1766,13 +1808,13 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
|
|
|
1766
1808
|
const readonly = isElementReadonly(element, state, ctx);
|
|
1767
1809
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
1768
1810
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
1769
|
-
const minCount = element.minCount ?? 1;
|
|
1811
|
+
const minCount = element.minCount ?? (element.required ? 1 : 0);
|
|
1770
1812
|
const maxCount = element.maxCount ?? Infinity;
|
|
1771
1813
|
while (values.length < minCount) {
|
|
1772
1814
|
values.push(element.default || element.options?.[0]?.value || "");
|
|
1773
1815
|
}
|
|
1774
1816
|
const container = document.createElement("div");
|
|
1775
|
-
container.className = "
|
|
1817
|
+
container.className = "fb-row";
|
|
1776
1818
|
wrapper.appendChild(container);
|
|
1777
1819
|
function updateIndices() {
|
|
1778
1820
|
const items = container.querySelectorAll(".multiple-select-item");
|
|
@@ -1918,7 +1960,7 @@ function validateSelectElement(element, key, context) {
|
|
|
1918
1960
|
if (skipValidation) return;
|
|
1919
1961
|
const { state } = context;
|
|
1920
1962
|
const filteredValues = values.filter(filterFn);
|
|
1921
|
-
const minCount = "minCount" in element2 ? element2.minCount ??
|
|
1963
|
+
const minCount = "minCount" in element2 ? element2.minCount ?? 0 : 0;
|
|
1922
1964
|
const maxCount = "maxCount" in element2 ? element2.maxCount ?? Infinity : Infinity;
|
|
1923
1965
|
if (element2.required && filteredValues.length === 0) {
|
|
1924
1966
|
errors.push(`${key2}: ${t("required", state)}`);
|
|
@@ -2195,14 +2237,14 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
|
|
|
2195
2237
|
const state = ctx.state;
|
|
2196
2238
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
2197
2239
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
2198
|
-
const minCount = element.minCount ?? 1;
|
|
2240
|
+
const minCount = element.minCount ?? (element.required ? 1 : 0);
|
|
2199
2241
|
const maxCount = element.maxCount ?? Infinity;
|
|
2200
2242
|
while (values.length < minCount) {
|
|
2201
2243
|
values.push(element.default || element.options?.[0]?.value || "");
|
|
2202
2244
|
}
|
|
2203
2245
|
const readonly = isElementReadonly(element, state, ctx);
|
|
2204
2246
|
const container = document.createElement("div");
|
|
2205
|
-
container.className = "
|
|
2247
|
+
container.className = "fb-row";
|
|
2206
2248
|
wrapper.appendChild(container);
|
|
2207
2249
|
function updateIndices() {
|
|
2208
2250
|
const items = container.querySelectorAll(".multiple-switcher-item");
|
|
@@ -2355,7 +2397,7 @@ function validateSwitcherElement(element, key, context) {
|
|
|
2355
2397
|
if (skipValidation) return;
|
|
2356
2398
|
const { state } = context;
|
|
2357
2399
|
const filteredValues = values.filter(filterFn);
|
|
2358
|
-
const minCount = "minCount" in el ? el.minCount ??
|
|
2400
|
+
const minCount = "minCount" in el ? el.minCount ?? 0 : 0;
|
|
2359
2401
|
const maxCount = "maxCount" in el ? el.maxCount ?? Infinity : Infinity;
|
|
2360
2402
|
if (el.required && filteredValues.length === 0) {
|
|
2361
2403
|
errors.push(`${fieldKey}: ${t("required", state)}`);
|
|
@@ -2774,7 +2816,9 @@ function ensureFileStyles() {
|
|
|
2774
2816
|
align-items: stretch;
|
|
2775
2817
|
gap: 0;
|
|
2776
2818
|
overflow: hidden;
|
|
2777
|
-
min-height
|
|
2819
|
+
/* min-height driven by --fb-file-wide-min so size-tokens (.fb-size-{xs|sm|lg|xl})
|
|
2820
|
+
can override per-field. Default 96px matches v0.4.0 compact density. */
|
|
2821
|
+
min-height: var(--fb-file-wide-min, 96px);
|
|
2778
2822
|
transition: border-color 150ms, background 150ms, box-shadow 150ms;
|
|
2779
2823
|
cursor: pointer;
|
|
2780
2824
|
}
|
|
@@ -2793,14 +2837,15 @@ function ensureFileStyles() {
|
|
|
2793
2837
|
upload + library on one row (~220 + 176), library wraps below. */
|
|
2794
2838
|
.fb-wide-tile-upload {
|
|
2795
2839
|
flex: 1 1 220px;
|
|
2796
|
-
min-height
|
|
2840
|
+
/* min-height theme-driven; default 72px matches v0.4.0 compact density. */
|
|
2841
|
+
min-height: var(--fb-file-zone-min, 72px);
|
|
2797
2842
|
display: flex;
|
|
2798
2843
|
flex-direction: column;
|
|
2799
2844
|
align-items: center;
|
|
2800
2845
|
justify-content: center;
|
|
2801
|
-
gap:
|
|
2846
|
+
gap: 4px;
|
|
2802
2847
|
color: #2563eb;
|
|
2803
|
-
padding:
|
|
2848
|
+
padding: var(--fb-file-zone-padding, 8px);
|
|
2804
2849
|
transition: background 150ms;
|
|
2805
2850
|
cursor: pointer;
|
|
2806
2851
|
background: transparent;
|
|
@@ -2820,14 +2865,16 @@ function ensureFileStyles() {
|
|
|
2820
2865
|
visual hierarchy "upload > library" in both layouts. */
|
|
2821
2866
|
.fb-wide-tile-library {
|
|
2822
2867
|
flex: 0 0 176px;
|
|
2823
|
-
min-
|
|
2868
|
+
/* Shares --fb-file-zone-min with the upload zone so size-tokens scale both
|
|
2869
|
+
uniformly. Default 72px matches v0.4.0 compact density. */
|
|
2870
|
+
min-height: var(--fb-file-zone-min, 72px);
|
|
2824
2871
|
display: flex;
|
|
2825
2872
|
flex-direction: column;
|
|
2826
2873
|
align-items: center;
|
|
2827
2874
|
justify-content: center;
|
|
2828
|
-
gap:
|
|
2875
|
+
gap: 4px;
|
|
2829
2876
|
color: #2563eb;
|
|
2830
|
-
padding:
|
|
2877
|
+
padding: var(--fb-file-zone-padding, 8px);
|
|
2831
2878
|
transition: background 150ms;
|
|
2832
2879
|
cursor: pointer;
|
|
2833
2880
|
background: transparent;
|
|
@@ -4088,7 +4135,9 @@ function downloadBlob(blob, fileName) {
|
|
|
4088
4135
|
URL.revokeObjectURL(blobUrl);
|
|
4089
4136
|
}, 100);
|
|
4090
4137
|
} catch (error) {
|
|
4091
|
-
throw new Error(`Blob download failed: ${error.message}
|
|
4138
|
+
throw new Error(`Blob download failed: ${error.message}`, {
|
|
4139
|
+
cause: error
|
|
4140
|
+
});
|
|
4092
4141
|
}
|
|
4093
4142
|
}
|
|
4094
4143
|
|
|
@@ -5028,7 +5077,7 @@ function renderResourcePills(opts) {
|
|
|
5028
5077
|
function renderFileElementEdit(element, ctx, wrapper, pathKey) {
|
|
5029
5078
|
const state = ctx.state;
|
|
5030
5079
|
const fileWrapper = document.createElement("div");
|
|
5031
|
-
fileWrapper.className = "
|
|
5080
|
+
fileWrapper.className = "fb-row";
|
|
5032
5081
|
fileWrapper.dataset.filesWrapper = pathKey;
|
|
5033
5082
|
const picker = document.createElement("input");
|
|
5034
5083
|
picker.type = "file";
|
|
@@ -5176,7 +5225,7 @@ function buildAcceptAttribute(accept) {
|
|
|
5176
5225
|
function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
|
|
5177
5226
|
const state = ctx.state;
|
|
5178
5227
|
const filesWrapper = document.createElement("div");
|
|
5179
|
-
filesWrapper.className = "
|
|
5228
|
+
filesWrapper.className = "fb-row";
|
|
5180
5229
|
filesWrapper.dataset.filesWrapper = pathKey;
|
|
5181
5230
|
const filesPicker = document.createElement("input");
|
|
5182
5231
|
filesPicker.type = "file";
|
|
@@ -5842,13 +5891,13 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
|
|
|
5842
5891
|
const readonly = isElementReadonly(element, state, ctx);
|
|
5843
5892
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
5844
5893
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
5845
|
-
const minCount = element.minCount ?? 1;
|
|
5894
|
+
const minCount = element.minCount ?? (element.required ? 1 : 0);
|
|
5846
5895
|
const maxCount = element.maxCount ?? Infinity;
|
|
5847
5896
|
while (values.length < minCount) {
|
|
5848
5897
|
values.push(element.default || "#000000");
|
|
5849
5898
|
}
|
|
5850
5899
|
const container = document.createElement("div");
|
|
5851
|
-
container.className = "
|
|
5900
|
+
container.className = "fb-row";
|
|
5852
5901
|
wrapper.appendChild(container);
|
|
5853
5902
|
function updateIndices() {
|
|
5854
5903
|
const items = container.querySelectorAll(".multiple-colour-item");
|
|
@@ -6028,7 +6077,7 @@ function validateColourElement(element, key, context) {
|
|
|
6028
6077
|
});
|
|
6029
6078
|
if (!skipValidation) {
|
|
6030
6079
|
const { state } = context;
|
|
6031
|
-
const minCount = element.minCount ??
|
|
6080
|
+
const minCount = element.minCount ?? 0;
|
|
6032
6081
|
const maxCount = element.maxCount ?? Infinity;
|
|
6033
6082
|
const filteredValues = values.filter((v) => v !== "");
|
|
6034
6083
|
if (element.required && filteredValues.length === 0) {
|
|
@@ -6303,7 +6352,7 @@ function renderMultipleSliderElement(element, ctx, wrapper, pathKey) {
|
|
|
6303
6352
|
const readonly = isElementReadonly(element, state, ctx);
|
|
6304
6353
|
const prefillValues = ctx.prefill[element.key] || [];
|
|
6305
6354
|
const values = Array.isArray(prefillValues) ? [...prefillValues] : [];
|
|
6306
|
-
const minCount = element.minCount ?? 1;
|
|
6355
|
+
const minCount = element.minCount ?? (element.required ? 1 : 0);
|
|
6307
6356
|
const maxCount = element.maxCount ?? Infinity;
|
|
6308
6357
|
const defaultValue = element.default !== void 0 ? element.default : (element.min + element.max) / 2;
|
|
6309
6358
|
while (values.length < minCount) {
|
|
@@ -6517,7 +6566,7 @@ function validateSliderElement(element, key, context) {
|
|
|
6517
6566
|
});
|
|
6518
6567
|
if (!skipValidation) {
|
|
6519
6568
|
const { state } = context;
|
|
6520
|
-
const minCount = element.minCount ??
|
|
6569
|
+
const minCount = element.minCount ?? 0;
|
|
6521
6570
|
const maxCount = element.maxCount ?? Infinity;
|
|
6522
6571
|
const filteredValues = values.filter((v) => v !== null);
|
|
6523
6572
|
if (element.required && filteredValues.length === 0) {
|
|
@@ -6704,7 +6753,7 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
6704
6753
|
const itemsWrap = document.createElement("div");
|
|
6705
6754
|
const columns = element.columns || 1;
|
|
6706
6755
|
if (columns === 1) {
|
|
6707
|
-
itemsWrap.className = "
|
|
6756
|
+
itemsWrap.className = "fb-row";
|
|
6708
6757
|
} else {
|
|
6709
6758
|
itemsWrap.className = `grid grid-cols-${columns} gap-2`;
|
|
6710
6759
|
}
|
|
@@ -6743,10 +6792,10 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
|
|
|
6743
6792
|
}
|
|
6744
6793
|
function getChildWrapperClass(isSlides, columns) {
|
|
6745
6794
|
if (isSlides) {
|
|
6746
|
-
return "
|
|
6795
|
+
return "fb-row";
|
|
6747
6796
|
}
|
|
6748
6797
|
const cols = columns || 1;
|
|
6749
|
-
return cols === 1 ? "
|
|
6798
|
+
return cols === 1 ? "fb-row" : `grid grid-cols-${cols} gap-2`;
|
|
6750
6799
|
}
|
|
6751
6800
|
function mountRemoveButton(item, onRemove, state) {
|
|
6752
6801
|
const rem = document.createElement("button");
|
|
@@ -6793,7 +6842,7 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6793
6842
|
const gridTemplateColumns = typeof slideCols === "number" && slideCols > 0 ? `repeat(${slideCols}, 1fr)` : "repeat(auto-fit, minmax(280px, 1fr))";
|
|
6794
6843
|
itemsWrap.style.cssText = `display:grid;grid-template-columns:${gridTemplateColumns};gap:var(--fb-slides-gap, 14px);align-items:start;`;
|
|
6795
6844
|
} else {
|
|
6796
|
-
itemsWrap.className = "
|
|
6845
|
+
itemsWrap.className = "fb-row";
|
|
6797
6846
|
}
|
|
6798
6847
|
if (!containerIsReadonly) {
|
|
6799
6848
|
const hintsElement = createPrefillHints(element, element.key);
|
|
@@ -6894,11 +6943,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6894
6943
|
}
|
|
6895
6944
|
const childWrapper = document.createElement("div");
|
|
6896
6945
|
if (isSlides) {
|
|
6897
|
-
childWrapper.className = "
|
|
6946
|
+
childWrapper.className = "fb-row";
|
|
6898
6947
|
} else {
|
|
6899
6948
|
const columns = element.columns || 1;
|
|
6900
6949
|
if (columns === 1) {
|
|
6901
|
-
childWrapper.className = "
|
|
6950
|
+
childWrapper.className = "fb-row";
|
|
6902
6951
|
} else {
|
|
6903
6952
|
childWrapper.className = `grid grid-cols-${columns} gap-2`;
|
|
6904
6953
|
}
|
|
@@ -6941,11 +6990,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
|
|
|
6941
6990
|
}
|
|
6942
6991
|
const childWrapper = document.createElement("div");
|
|
6943
6992
|
if (isSlides) {
|
|
6944
|
-
childWrapper.className = "
|
|
6993
|
+
childWrapper.className = "fb-row";
|
|
6945
6994
|
} else {
|
|
6946
6995
|
const columns = element.columns || 1;
|
|
6947
6996
|
if (columns === 1) {
|
|
6948
|
-
childWrapper.className = "
|
|
6997
|
+
childWrapper.className = "fb-row";
|
|
6949
6998
|
} else {
|
|
6950
6999
|
childWrapper.className = `grid grid-cols-${columns} gap-2`;
|
|
6951
7000
|
}
|
|
@@ -8499,7 +8548,7 @@ function validateTableElement(element, key, context) {
|
|
|
8499
8548
|
if (!hiddenInput) {
|
|
8500
8549
|
return { value: null, errors };
|
|
8501
8550
|
}
|
|
8502
|
-
let value
|
|
8551
|
+
let value;
|
|
8503
8552
|
try {
|
|
8504
8553
|
value = JSON.parse(hiddenInput.value);
|
|
8505
8554
|
} catch {
|
|
@@ -9824,7 +9873,7 @@ function validateRichInputElement(element, key, context) {
|
|
|
9824
9873
|
if (!hiddenInput) {
|
|
9825
9874
|
return { value: null, errors };
|
|
9826
9875
|
}
|
|
9827
|
-
let rawValue
|
|
9876
|
+
let rawValue;
|
|
9828
9877
|
try {
|
|
9829
9878
|
const parsed = JSON.parse(hiddenInput.value);
|
|
9830
9879
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
@@ -10463,7 +10512,8 @@ function setupEnableIfListeners(wrapper, element, ctx) {
|
|
|
10463
10512
|
}
|
|
10464
10513
|
function createFieldLabel(element) {
|
|
10465
10514
|
const title = document.createElement("label");
|
|
10466
|
-
title.className = "
|
|
10515
|
+
title.className = "font-medium text-gray-900";
|
|
10516
|
+
title.style.cssText = "font-size: var(--fb-label-font-size, 0.75rem); line-height: var(--fb-label-line-height, 1.2);";
|
|
10467
10517
|
title.textContent = element.label ?? element.key ?? "";
|
|
10468
10518
|
if (element.required) {
|
|
10469
10519
|
const req = document.createElement("span");
|
|
@@ -10494,7 +10544,8 @@ function createInfoButton(element) {
|
|
|
10494
10544
|
}
|
|
10495
10545
|
function createLabelContainer(element) {
|
|
10496
10546
|
const label = document.createElement("div");
|
|
10497
|
-
label.className = "flex items-center
|
|
10547
|
+
label.className = "flex items-center";
|
|
10548
|
+
label.style.marginBottom = "var(--fb-label-margin-bottom, 2px)";
|
|
10498
10549
|
label.dataset.fbLabelRow = "";
|
|
10499
10550
|
const title = createFieldLabel(element);
|
|
10500
10551
|
label.appendChild(title);
|
|
@@ -10605,11 +10656,12 @@ function renderElement2(element, ctx) {
|
|
|
10605
10656
|
}
|
|
10606
10657
|
const initiallyDisabled2 = shouldDisableElement(element, ctx);
|
|
10607
10658
|
const outerWrapper = document.createElement("div");
|
|
10608
|
-
outerWrapper.className =
|
|
10659
|
+
outerWrapper.className = `fb-field-wrapper fb-markdown-wrapper fb-size-${element.size || "md"}`;
|
|
10609
10660
|
outerWrapper.setAttribute(
|
|
10610
10661
|
"data-field-key",
|
|
10611
10662
|
getElementLookupKey(element, ctx.state)
|
|
10612
10663
|
);
|
|
10664
|
+
outerWrapper.setAttribute("data-fb-width", element.width || "full");
|
|
10613
10665
|
renderMarkdown(element, ctx, outerWrapper);
|
|
10614
10666
|
if (initiallyDisabled2) {
|
|
10615
10667
|
outerWrapper.style.display = "none";
|
|
@@ -10621,8 +10673,9 @@ function renderElement2(element, ctx) {
|
|
|
10621
10673
|
}
|
|
10622
10674
|
const initiallyDisabled = shouldDisableElement(element, ctx);
|
|
10623
10675
|
const wrapper = document.createElement("div");
|
|
10624
|
-
wrapper.className =
|
|
10676
|
+
wrapper.className = `fb-field-wrapper fb-size-${element.size || "md"}`;
|
|
10625
10677
|
wrapper.setAttribute("data-field-key", element.key);
|
|
10678
|
+
wrapper.setAttribute("data-fb-width", element.width || "full");
|
|
10626
10679
|
const ops = getComponentOperations(element.type);
|
|
10627
10680
|
if (!ops?.ownsLabel) {
|
|
10628
10681
|
const label = createLabelContainer(element);
|
|
@@ -10944,9 +10997,9 @@ var defaultTheme = {
|
|
|
10944
10997
|
// blue-500
|
|
10945
10998
|
// Spacing
|
|
10946
10999
|
inputPaddingX: "0.5rem",
|
|
10947
|
-
// 8px (compact density
|
|
10948
|
-
inputPaddingY: "0.
|
|
10949
|
-
//
|
|
11000
|
+
// 8px (compact density v3 — v0.4.0)
|
|
11001
|
+
inputPaddingY: "0.125rem",
|
|
11002
|
+
// 2px (compact density v3 — v0.4.0)
|
|
10950
11003
|
borderRadius: "0.5rem",
|
|
10951
11004
|
// rounded-lg (8px)
|
|
10952
11005
|
borderRadiusSmall: "0.375rem",
|
|
@@ -10984,13 +11037,20 @@ var defaultTheme = {
|
|
|
10984
11037
|
slideCardRadius: "0.5rem",
|
|
10985
11038
|
// matches borderRadius
|
|
10986
11039
|
slideCardMinHeight: "0",
|
|
10987
|
-
slideCardPadding: "
|
|
11040
|
+
slideCardPadding: "8px",
|
|
10988
11041
|
// Section-label defaults — same look as the regular field label. Themes
|
|
10989
11042
|
// that want the "ПРЕИМУЩЕСТВА" caps style override these three vars.
|
|
10990
11043
|
labelSectionFontSize: "0.875rem",
|
|
10991
11044
|
// matches fontSize
|
|
10992
11045
|
labelSectionLetterSpacing: "normal",
|
|
10993
|
-
labelSectionTextTransform: "none"
|
|
11046
|
+
labelSectionTextTransform: "none",
|
|
11047
|
+
// Per-field label defaults — compact density: 12px @ 1.2 line-height with
|
|
11048
|
+
// a 2px gap to the input. Themes that prefer roomier labels (Picaz, klein)
|
|
11049
|
+
// can bump these via their override block.
|
|
11050
|
+
labelFontSize: "0.75rem",
|
|
11051
|
+
// 12px
|
|
11052
|
+
labelLineHeight: "1.2",
|
|
11053
|
+
labelMarginBottom: "2px"
|
|
10994
11054
|
};
|
|
10995
11055
|
function generateCSSVariables(theme) {
|
|
10996
11056
|
const mergedTheme = { ...defaultTheme, ...theme };
|
|
@@ -11536,7 +11596,7 @@ var FormBuilderInstance = class {
|
|
|
11536
11596
|
root.setAttribute("data-fb-root", "true");
|
|
11537
11597
|
injectThemeVariables(root, this.state.config.theme);
|
|
11538
11598
|
const rootContainer = document.createElement("div");
|
|
11539
|
-
rootContainer.className = "
|
|
11599
|
+
rootContainer.className = "fb-row";
|
|
11540
11600
|
if (schema.prefillHints && !this.state.config.readonly) {
|
|
11541
11601
|
const hintsContainer = this.createRootPrefillHints(schema.prefillHints);
|
|
11542
11602
|
rootContainer.appendChild(hintsContainer);
|
|
@@ -11544,7 +11604,7 @@ var FormBuilderInstance = class {
|
|
|
11544
11604
|
const fieldsWrapper = document.createElement("div");
|
|
11545
11605
|
const columns = schema.columns || 1;
|
|
11546
11606
|
if (columns === 1) {
|
|
11547
|
-
fieldsWrapper.className = "
|
|
11607
|
+
fieldsWrapper.className = "fb-row";
|
|
11548
11608
|
} else {
|
|
11549
11609
|
fieldsWrapper.className = `grid grid-cols-${columns} gap-2`;
|
|
11550
11610
|
}
|