@dmitryvim/form-builder 0.3.4 → 0.5.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.
@@ -427,6 +427,20 @@ function ensureThemingHooks(doc) {
427
427
  min-height: var(--fb-slide-card-min-height);
428
428
  padding: var(--fb-slide-card-padding);
429
429
  }
430
+ /* Slides-mode outer grid. Responsive by design \u2014 column count derives from
431
+ container width and --fb-slide-min-width, not from a schema "columns" prop.
432
+ min(100%, ...) prevents the card from overflowing a narrow container.
433
+ Override per-form: set --fb-slide-min-width / --fb-slides-gap on any
434
+ ancestor (theme, host CSS, or the form root). */
435
+ .fb-container-slides {
436
+ display: grid;
437
+ gap: var(--fb-slides-gap, 14px);
438
+ align-items: stretch;
439
+ grid-template-columns: repeat(
440
+ auto-fit,
441
+ minmax(min(100%, var(--fb-slide-min-width, 280px)), 1fr)
442
+ );
443
+ }
430
444
  [data-fb-label-row] > label {
431
445
  font-size: var(--fb-label-section-font-size);
432
446
  letter-spacing: var(--fb-label-section-letter-spacing);
@@ -473,20 +487,61 @@ function ensureThemingHooks(doc) {
473
487
  color: #ffffff;
474
488
  border-color: var(--fb-primary-color);
475
489
  }
490
+ /* \u2500\u2500\u2500 Row layout: flex-wrap container with per-child width tokens \u2500\u2500\u2500
491
+ Replaces the older Tailwind \`space-y-2\` class. Children without
492
+ data-fb-width (or width="full") take 100% basis \u2192 forced wrap \u2192 behaves
493
+ like a vertical stack. Mixed widths sit side-by-side in the same row. */
494
+ .fb-row {
495
+ display: flex;
496
+ flex-wrap: wrap;
497
+ gap: var(--fb-row-gap, 0.25rem);
498
+ align-items: flex-start;
499
+ }
500
+ .fb-row > [data-fb-width="full"],
501
+ .fb-row > :not([data-fb-width]) { flex: 0 0 100%; min-width: 0; }
502
+ .fb-row > [data-fb-width="1/2"] { flex: 0 0 calc(50% - var(--fb-row-gap, 0.25rem) / 2); min-width: 0; }
503
+ .fb-row > [data-fb-width="1/3"] { flex: 0 0 calc(33.333% - var(--fb-row-gap, 0.25rem) * 2 / 3); min-width: 0; }
504
+ .fb-row > [data-fb-width="2/3"] { flex: 0 0 calc(66.666% - var(--fb-row-gap, 0.25rem) / 3); min-width: 0; }
505
+ /* \u2500\u2500\u2500 Density tokens: re-bind --fb-input-padding-* and --fb-font-size at
506
+ the wrapper level so every component that reads those vars (text, textarea,
507
+ select, number, colour, switcher, file) picks the size up via cascade. md
508
+ uses the defaults set in theme.ts (2px / 8px / 14px). The scale primarily
509
+ varies font-size; vertical padding stays close to md so an "lg" heading
510
+ field does not visually break the form's row rhythm \u2014 only the typography
511
+ announces "this is bigger". Picaz theme overrides padding directly for
512
+ its own density, independent of this scale. */
513
+ /* size-token side-effects on file tiles: --fb-file-wide-min controls the
514
+ single-file empty dropzone height, --fb-file-zone-min controls the inner
515
+ upload/library zones. Both read in src/components/file/styles.ts. */
516
+ .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; }
517
+ .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; }
518
+ /* .fb-size-md uses defaults \u2014 no override needed */
519
+ .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; }
520
+ .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; }
476
521
  `;
477
522
  doc.head.appendChild(style);
478
523
  }
479
- function applyAutoExpand(textarea) {
524
+ function applyAutoExpand(textarea, options = {}) {
525
+ var _a;
480
526
  textarea.style.overflow = "hidden";
481
527
  textarea.style.resize = "none";
528
+ const minRows = Math.max(1, (_a = options.minRows) != null ? _a : 1);
482
529
  const lineCount = (textarea.value.match(/\n/g) || []).length + 1;
483
- textarea.rows = Math.max(1, lineCount);
530
+ textarea.rows = Math.max(minRows, lineCount);
484
531
  const resize = () => {
485
532
  if (!textarea.isConnected) return;
486
- textarea.style.height = "0";
533
+ textarea.style.height = "";
487
534
  const cs = getComputedStyle(textarea);
488
535
  const borderY = parseFloat(cs.borderTopWidth || "0") + parseFloat(cs.borderBottomWidth || "0");
489
- textarea.style.height = `${textarea.scrollHeight + borderY}px`;
536
+ const lineHeight = parseFloat(cs.lineHeight || "0") || parseFloat(cs.fontSize || "0") * 1.5 || 20;
537
+ const padY = parseFloat(cs.paddingTop || "0") + parseFloat(cs.paddingBottom || "0");
538
+ const minHeight = lineHeight * minRows + padY + borderY;
539
+ if (textarea.value === "") {
540
+ textarea.style.height = `${minHeight}px`;
541
+ return;
542
+ }
543
+ const contentHeight = textarea.scrollHeight + borderY;
544
+ textarea.style.height = `${Math.max(contentHeight, minHeight)}px`;
490
545
  };
491
546
  textarea.addEventListener("input", resize);
492
547
  setTimeout(() => {
@@ -495,13 +550,13 @@ function applyAutoExpand(textarea) {
495
550
  if (typeof ResizeObserver === "undefined") return;
496
551
  let lastWidth = -1;
497
552
  const ro = new ResizeObserver((entries) => {
498
- var _a, _b, _c, _d, _e;
553
+ var _a2, _b, _c, _d, _e;
499
554
  if (!textarea.isConnected) {
500
555
  ro.disconnect();
501
556
  return;
502
557
  }
503
558
  const entry = entries[0];
504
- const w = (_e = (_d = (_b = (_a = entry == null ? void 0 : entry.contentBoxSize) == null ? void 0 : _a[0]) == null ? void 0 : _b.inlineSize) != null ? _d : (_c = entry == null ? void 0 : entry.contentRect) == null ? void 0 : _c.width) != null ? _e : 0;
559
+ const w = (_e = (_d = (_b = (_a2 = entry == null ? void 0 : entry.contentBoxSize) == null ? void 0 : _a2[0]) == null ? void 0 : _b.inlineSize) != null ? _d : (_c = entry == null ? void 0 : entry.contentRect) == null ? void 0 : _c.width) != null ? _e : 0;
505
560
  if (w === lastWidth) return;
506
561
  lastWidth = w;
507
562
  resize();
@@ -541,8 +596,8 @@ function createAddItemRow(classNameSuffix, onClick, options = {}) {
541
596
  const label = (_a = options.label) != null ? _a : "";
542
597
  const showCounter = options.showCounter !== false;
543
598
  const row = document.createElement("div");
544
- row.className = "fb-add-row mt-2";
545
- row.style.cssText = "display:flex;align-items:stretch;width:100%;";
599
+ row.className = "fb-add-row";
600
+ row.style.cssText = "display:flex;align-items:stretch;width:100%;margin-top:4px;";
546
601
  const button = document.createElement("button");
547
602
  button.type = "button";
548
603
  button.className = `add-${classNameSuffix}-btn`;
@@ -551,8 +606,8 @@ function createAddItemRow(classNameSuffix, onClick, options = {}) {
551
606
  display: inline-flex;
552
607
  align-items: center;
553
608
  justify-content: center;
554
- gap: 6px;
555
- padding: 6px 10px;
609
+ gap: 4px;
610
+ padding: 3px 10px;
556
611
  border: 1px dashed var(--fb-primary-color);
557
612
  border-radius: var(--fb-border-radius);
558
613
  background: transparent;
@@ -698,12 +753,12 @@ function ensureChipStyles(doc) {
698
753
  const style = doc.createElement("style");
699
754
  style.setAttribute("data-fb-chip-styles", "");
700
755
  style.textContent = `
701
- .fb-chip-list { display: flex; flex-direction: column; gap: 4px; }
756
+ .fb-chip-list { display: flex; flex-direction: column; gap: 2px; }
702
757
  .fb-chip {
703
758
  display: flex;
704
759
  align-items: center;
705
- gap: 8px;
706
- padding: 5px 6px 5px 10px;
760
+ gap: 6px;
761
+ padding: 3px 6px 3px 8px;
707
762
  background: var(--fb-chip-bg, var(--fb-background-color, #fff));
708
763
  border: 1px solid var(--fb-chip-border, var(--fb-border-color, #e2e8f0));
709
764
  border-radius: 6px;
@@ -729,7 +784,13 @@ function ensureChipStyles(doc) {
729
784
  color: var(--fb-chip-text, var(--fb-text-color, inherit));
730
785
  font-size: var(--fb-font-size, 14px);
731
786
  font-family: var(--fb-font-family, inherit);
732
- line-height: 1.4;
787
+ line-height: var(--fb-line-height, 1.4);
788
+ /* Soft-wrap support (v0.4.0): chip-input is now a <textarea rows=1> that
789
+ grows vertically when content overflows the chip width. */
790
+ resize: none;
791
+ overflow: hidden;
792
+ word-break: break-word;
793
+ overflow-wrap: anywhere;
733
794
  }
734
795
  .fb-chip-input::placeholder { color: var(--fb-text-placeholder-color, #94a3b8); }
735
796
  .fb-chip-input:read-only { color: var(--fb-text-secondary-color, #475569); }
@@ -788,6 +849,7 @@ function createCharCounter(element, input) {
788
849
  return counter;
789
850
  }
790
851
  function renderTextElement(element, ctx, wrapper, pathKey) {
852
+ var _a;
791
853
  const state = ctx.state;
792
854
  const readonly = isElementReadonly(element, state, ctx);
793
855
  const inputWrapper = document.createElement("div");
@@ -814,7 +876,7 @@ function renderTextElement(element, ctx, wrapper, pathKey) {
814
876
  overflow-wrap: anywhere;
815
877
  `;
816
878
  textInput.name = pathKey;
817
- textInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
879
+ textInput.placeholder = (_a = element.placeholder) != null ? _a : "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
818
880
  textInput.value = ctx.prefill[element.key] || element.default || "";
819
881
  textInput.readOnly = readonly;
820
882
  applySingleLineMode(textInput);
@@ -888,8 +950,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
888
950
  dot.className = "fb-chip-dot";
889
951
  dot.setAttribute("aria-hidden", "true");
890
952
  chip.appendChild(dot);
891
- const input = document.createElement("input");
892
- input.type = "text";
953
+ const input = document.createElement("textarea");
954
+ input.rows = 1;
893
955
  input.className = "fb-chip-input";
894
956
  input.value = value;
895
957
  input.placeholder = element.placeholder || t("placeholderText", state);
@@ -905,6 +967,8 @@ function renderMultipleTextElement(element, ctx, wrapper, pathKey) {
905
967
  input.addEventListener("blur", handleChange);
906
968
  input.addEventListener("input", handleChange);
907
969
  }
970
+ applySingleLineMode(input);
971
+ applyAutoExpand(input);
908
972
  if (!readonly) {
909
973
  const rem = document.createElement("button");
910
974
  rem.type = "button";
@@ -1119,6 +1183,7 @@ function updateTextField(element, fieldPath, value, context) {
1119
1183
 
1120
1184
  // src/components/textarea.ts
1121
1185
  function renderTextareaElement(element, ctx, wrapper, pathKey) {
1186
+ var _a, _b;
1122
1187
  const state = ctx.state;
1123
1188
  const readonly = isElementReadonly(element, state, ctx);
1124
1189
  const textareaWrapper = document.createElement("div");
@@ -1126,13 +1191,13 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
1126
1191
  const textareaInput = document.createElement("textarea");
1127
1192
  textareaInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
1128
1193
  textareaInput.style.cssText = `
1129
- padding: var(--fb-input-padding-y) var(--fb-input-padding-x) 24px var(--fb-input-padding-x);
1194
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
1130
1195
  font-size: var(--fb-font-size);
1131
1196
  font-family: var(--fb-font-family);
1197
+ line-height: var(--fb-line-height, 1.5);
1132
1198
  `;
1133
1199
  textareaInput.name = pathKey;
1134
- textareaInput.placeholder = element.placeholder || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
1135
- textareaInput.rows = element.rows || 4;
1200
+ textareaInput.placeholder = (_a = element.placeholder) != null ? _a : "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
1136
1201
  textareaInput.value = ctx.prefill[element.key] || element.default || "";
1137
1202
  textareaInput.readOnly = readonly;
1138
1203
  if (!readonly && ctx.instance) {
@@ -1143,9 +1208,7 @@ function renderTextareaElement(element, ctx, wrapper, pathKey) {
1143
1208
  textareaInput.addEventListener("blur", handleChange);
1144
1209
  textareaInput.addEventListener("input", handleChange);
1145
1210
  }
1146
- if (element.autoExpand || readonly) {
1147
- applyAutoExpand(textareaInput);
1148
- }
1211
+ applyAutoExpand(textareaInput, { minRows: (_b = element.rows) != null ? _b : 1 });
1149
1212
  textareaWrapper.appendChild(textareaInput);
1150
1213
  if (!readonly && (element.minLength != null || element.maxLength != null)) {
1151
1214
  const counter = createCharCounter(element, textareaInput);
@@ -1165,7 +1228,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1165
1228
  values.push(element.default || "");
1166
1229
  }
1167
1230
  const container = document.createElement("div");
1168
- container.className = "space-y-2";
1231
+ container.className = "fb-row";
1169
1232
  wrapper.appendChild(container);
1170
1233
  function updateIndices() {
1171
1234
  const items = container.querySelectorAll(".multiple-textarea-item");
@@ -1177,6 +1240,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1177
1240
  });
1178
1241
  }
1179
1242
  function addTextareaItem(value = "", index = -1) {
1243
+ var _a2;
1180
1244
  const itemWrapper = document.createElement("div");
1181
1245
  itemWrapper.className = "multiple-textarea-item";
1182
1246
  const textareaContainer = document.createElement("div");
@@ -1184,12 +1248,12 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1184
1248
  const textareaInput = document.createElement("textarea");
1185
1249
  textareaInput.className = "w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none";
1186
1250
  textareaInput.style.cssText = `
1187
- padding: var(--fb-input-padding-y) var(--fb-input-padding-x) 24px var(--fb-input-padding-x);
1251
+ padding: var(--fb-input-padding-y) var(--fb-input-padding-x);
1188
1252
  font-size: var(--fb-font-size);
1189
1253
  font-family: var(--fb-font-family);
1254
+ line-height: var(--fb-line-height, 1.5);
1190
1255
  `;
1191
1256
  textareaInput.placeholder = element.placeholder || t("placeholderText", state);
1192
- textareaInput.rows = element.rows || 4;
1193
1257
  textareaInput.value = value;
1194
1258
  textareaInput.readOnly = readonly;
1195
1259
  if (!readonly && ctx.instance) {
@@ -1200,9 +1264,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1200
1264
  textareaInput.addEventListener("blur", handleChange);
1201
1265
  textareaInput.addEventListener("input", handleChange);
1202
1266
  }
1203
- if (element.autoExpand || readonly) {
1204
- applyAutoExpand(textareaInput);
1205
- }
1267
+ applyAutoExpand(textareaInput, { minRows: (_a2 = element.rows) != null ? _a2 : 1 });
1206
1268
  textareaContainer.appendChild(textareaInput);
1207
1269
  if (!readonly && (element.minLength != null || element.maxLength != null)) {
1208
1270
  const counter = createCharCounter(element, textareaInput);
@@ -1278,9 +1340,7 @@ function validateTextareaElement(element, key, context) {
1278
1340
  }
1279
1341
  function updateTextareaField(element, fieldPath, value, context) {
1280
1342
  updateTextField(element, fieldPath, value, context);
1281
- const { scopeRoot, state } = context;
1282
- const shouldAutoExpand = element.autoExpand || isElementReadonly(element, state);
1283
- if (!shouldAutoExpand) return;
1343
+ const { scopeRoot } = context;
1284
1344
  if (element.multiple) {
1285
1345
  const textareas = scopeRoot.querySelectorAll(
1286
1346
  `textarea[name^="${fieldPath}["]`
@@ -1478,7 +1538,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1478
1538
  values.push(element.default || "");
1479
1539
  }
1480
1540
  const container = document.createElement("div");
1481
- container.className = "space-y-2";
1541
+ container.className = "fb-row";
1482
1542
  wrapper.appendChild(container);
1483
1543
  function updateIndices() {
1484
1544
  const items = container.querySelectorAll(".multiple-number-item");
@@ -1796,7 +1856,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1796
1856
  values.push(element.default || ((_d = (_c = element.options) == null ? void 0 : _c[0]) == null ? void 0 : _d.value) || "");
1797
1857
  }
1798
1858
  const container = document.createElement("div");
1799
- container.className = "space-y-2";
1859
+ container.className = "fb-row";
1800
1860
  wrapper.appendChild(container);
1801
1861
  function updateIndices() {
1802
1862
  const items = container.querySelectorAll(".multiple-select-item");
@@ -2233,7 +2293,7 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
2233
2293
  }
2234
2294
  const readonly = isElementReadonly(element, state, ctx);
2235
2295
  const container = document.createElement("div");
2236
- container.className = "space-y-2";
2296
+ container.className = "fb-row";
2237
2297
  wrapper.appendChild(container);
2238
2298
  function updateIndices() {
2239
2299
  const items = container.querySelectorAll(".multiple-switcher-item");
@@ -2818,7 +2878,9 @@ function ensureFileStyles() {
2818
2878
  align-items: stretch;
2819
2879
  gap: 0;
2820
2880
  overflow: hidden;
2821
- min-height: 180px;
2881
+ /* min-height driven by --fb-file-wide-min so size-tokens (.fb-size-{xs|sm|lg|xl})
2882
+ can override per-field. Default 96px matches v0.4.0 compact density. */
2883
+ min-height: var(--fb-file-wide-min, 96px);
2822
2884
  transition: border-color 150ms, background 150ms, box-shadow 150ms;
2823
2885
  cursor: pointer;
2824
2886
  }
@@ -2837,14 +2899,15 @@ function ensureFileStyles() {
2837
2899
  upload + library on one row (~220 + 176), library wraps below. */
2838
2900
  .fb-wide-tile-upload {
2839
2901
  flex: 1 1 220px;
2840
- min-height: 140px;
2902
+ /* min-height theme-driven; default 72px matches v0.4.0 compact density. */
2903
+ min-height: var(--fb-file-zone-min, 72px);
2841
2904
  display: flex;
2842
2905
  flex-direction: column;
2843
2906
  align-items: center;
2844
2907
  justify-content: center;
2845
- gap: 8px;
2908
+ gap: 4px;
2846
2909
  color: #2563eb;
2847
- padding: 16px;
2910
+ padding: var(--fb-file-zone-padding, 8px);
2848
2911
  transition: background 150ms;
2849
2912
  cursor: pointer;
2850
2913
  background: transparent;
@@ -2864,14 +2927,16 @@ function ensureFileStyles() {
2864
2927
  visual hierarchy "upload > library" in both layouts. */
2865
2928
  .fb-wide-tile-library {
2866
2929
  flex: 0 0 176px;
2867
- min-height: 120px;
2930
+ /* Shares --fb-file-zone-min with the upload zone so size-tokens scale both
2931
+ uniformly. Default 72px matches v0.4.0 compact density. */
2932
+ min-height: var(--fb-file-zone-min, 72px);
2868
2933
  display: flex;
2869
2934
  flex-direction: column;
2870
2935
  align-items: center;
2871
2936
  justify-content: center;
2872
- gap: 8px;
2937
+ gap: 4px;
2873
2938
  color: #2563eb;
2874
- padding: 12px;
2939
+ padding: var(--fb-file-zone-padding, 8px);
2875
2940
  transition: background 150ms;
2876
2941
  cursor: pointer;
2877
2942
  background: transparent;
@@ -4148,7 +4213,9 @@ function downloadBlob(blob, fileName) {
4148
4213
  URL.revokeObjectURL(blobUrl);
4149
4214
  }, 100);
4150
4215
  } catch (error) {
4151
- throw new Error(`Blob download failed: ${error.message}`);
4216
+ throw new Error(`Blob download failed: ${error.message}`, {
4217
+ cause: error
4218
+ });
4152
4219
  }
4153
4220
  }
4154
4221
 
@@ -5106,7 +5173,7 @@ function renderFileElementEdit(element, ctx, wrapper, pathKey) {
5106
5173
  var _a, _b;
5107
5174
  const state = ctx.state;
5108
5175
  const fileWrapper = document.createElement("div");
5109
- fileWrapper.className = "space-y-2";
5176
+ fileWrapper.className = "fb-row";
5110
5177
  fileWrapper.dataset.filesWrapper = pathKey;
5111
5178
  const picker = document.createElement("input");
5112
5179
  picker.type = "file";
@@ -5257,7 +5324,7 @@ function setupMultiFileEditMode(element, ctx, wrapper, pathKey, maxFiles) {
5257
5324
  var _a, _b;
5258
5325
  const state = ctx.state;
5259
5326
  const filesWrapper = document.createElement("div");
5260
- filesWrapper.className = "space-y-2";
5327
+ filesWrapper.className = "fb-row";
5261
5328
  filesWrapper.dataset.filesWrapper = pathKey;
5262
5329
  const filesPicker = document.createElement("input");
5263
5330
  filesPicker.type = "file";
@@ -5939,7 +6006,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
5939
6006
  values.push(element.default || "#000000");
5940
6007
  }
5941
6008
  const container = document.createElement("div");
5942
- container.className = "space-y-2";
6009
+ container.className = "fb-row";
5943
6010
  wrapper.appendChild(container);
5944
6011
  function updateIndices() {
5945
6012
  const items = container.querySelectorAll(".multiple-colour-item");
@@ -6805,7 +6872,7 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
6805
6872
  const itemsWrap = document.createElement("div");
6806
6873
  const columns = element.columns || 1;
6807
6874
  if (columns === 1) {
6808
- itemsWrap.className = "space-y-2";
6875
+ itemsWrap.className = "fb-row";
6809
6876
  } else {
6810
6877
  itemsWrap.className = `grid grid-cols-${columns} gap-2`;
6811
6878
  }
@@ -6843,12 +6910,9 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
6843
6910
  containerWrap.appendChild(itemsWrap);
6844
6911
  wrapper.appendChild(containerWrap);
6845
6912
  }
6846
- function getChildWrapperClass(isSlides, columns) {
6847
- if (isSlides) {
6848
- return "space-y-2";
6849
- }
6913
+ function getChildWrapperClass(columns) {
6850
6914
  const cols = columns || 1;
6851
- return cols === 1 ? "space-y-2" : `grid grid-cols-${cols} gap-2`;
6915
+ return cols === 1 ? "fb-row" : `grid grid-cols-${cols} gap-2`;
6852
6916
  }
6853
6917
  function mountRemoveButton(item, onRemove, state) {
6854
6918
  const rem = document.createElement("button");
@@ -6892,11 +6956,8 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6892
6956
  const isSlides = element.displayMode === "slides";
6893
6957
  if (isSlides) {
6894
6958
  itemsWrap.className = "fb-container-slides";
6895
- const slideCols = element.columns;
6896
- const gridTemplateColumns = typeof slideCols === "number" && slideCols > 0 ? `repeat(${slideCols}, 1fr)` : "repeat(auto-fit, minmax(280px, 1fr))";
6897
- itemsWrap.style.cssText = `display:grid;grid-template-columns:${gridTemplateColumns};gap:var(--fb-slides-gap, 14px);align-items:start;`;
6898
6959
  } else {
6899
- itemsWrap.className = "space-y-2";
6960
+ itemsWrap.className = "fb-row";
6900
6961
  }
6901
6962
  if (!containerIsReadonly) {
6902
6963
  const hintsElement = createPrefillHints(element, element.key);
@@ -6930,7 +6991,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6930
6991
  item.setAttribute("data-fb-slide-card", "");
6931
6992
  }
6932
6993
  const childWrapper = document.createElement("div");
6933
- childWrapper.className = getChildWrapperClass(isSlides, element.columns);
6994
+ childWrapper.className = getChildWrapperClass(
6995
+ isSlides ? void 0 : element.columns
6996
+ );
6934
6997
  element.elements.forEach((child) => {
6935
6998
  var _a2;
6936
6999
  if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
@@ -6998,16 +7061,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
6998
7061
  item.setAttribute("data-fb-slide-card", "");
6999
7062
  }
7000
7063
  const childWrapper = document.createElement("div");
7001
- if (isSlides) {
7002
- childWrapper.className = "space-y-2";
7003
- } else {
7004
- const columns = element.columns || 1;
7005
- if (columns === 1) {
7006
- childWrapper.className = "space-y-2";
7007
- } else {
7008
- childWrapper.className = `grid grid-cols-${columns} gap-2`;
7009
- }
7010
- }
7064
+ childWrapper.className = getChildWrapperClass(
7065
+ isSlides ? void 0 : element.columns
7066
+ );
7011
7067
  element.elements.forEach((child) => {
7012
7068
  var _a3, _b2;
7013
7069
  if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
@@ -7046,16 +7102,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
7046
7102
  item.setAttribute("data-fb-slide-card", "");
7047
7103
  }
7048
7104
  const childWrapper = document.createElement("div");
7049
- if (isSlides) {
7050
- childWrapper.className = "space-y-2";
7051
- } else {
7052
- const columns = element.columns || 1;
7053
- if (columns === 1) {
7054
- childWrapper.className = "space-y-2";
7055
- } else {
7056
- childWrapper.className = `grid grid-cols-${columns} gap-2`;
7057
- }
7058
- }
7105
+ childWrapper.className = getChildWrapperClass(
7106
+ isSlides ? void 0 : element.columns
7107
+ );
7059
7108
  element.elements.forEach((child) => {
7060
7109
  var _a2;
7061
7110
  if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
@@ -7085,7 +7134,6 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
7085
7134
  containerWrap.appendChild(itemsWrap);
7086
7135
  if (!containerIsReadonly) {
7087
7136
  if (isSlides) {
7088
- itemsWrap.style.alignItems = "stretch";
7089
7137
  const handle = createSlideAddTile(handleAddItem, {
7090
7138
  label: element.addLabel
7091
7139
  });
@@ -8637,7 +8685,7 @@ function validateTableElement(element, key, context) {
8637
8685
  if (!hiddenInput) {
8638
8686
  return { value: null, errors };
8639
8687
  }
8640
- let value = null;
8688
+ let value;
8641
8689
  try {
8642
8690
  value = JSON.parse(hiddenInput.value);
8643
8691
  } catch {
@@ -10004,7 +10052,7 @@ function validateRichInputElement(element, key, context) {
10004
10052
  if (!hiddenInput) {
10005
10053
  return { value: null, errors };
10006
10054
  }
10007
- let rawValue = {};
10055
+ let rawValue;
10008
10056
  try {
10009
10057
  const parsed = JSON.parse(hiddenInput.value);
10010
10058
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
@@ -10651,7 +10699,8 @@ function setupEnableIfListeners(wrapper, element, ctx) {
10651
10699
  function createFieldLabel(element) {
10652
10700
  var _a, _b;
10653
10701
  const title = document.createElement("label");
10654
- title.className = "text-sm font-medium text-gray-900";
10702
+ title.className = "font-medium text-gray-900";
10703
+ title.style.cssText = "font-size: var(--fb-label-font-size, 0.75rem); line-height: var(--fb-label-line-height, 1.2);";
10655
10704
  title.textContent = (_b = (_a = element.label) != null ? _a : element.key) != null ? _b : "";
10656
10705
  if (element.required) {
10657
10706
  const req = document.createElement("span");
@@ -10682,7 +10731,8 @@ function createInfoButton(element) {
10682
10731
  }
10683
10732
  function createLabelContainer(element) {
10684
10733
  const label = document.createElement("div");
10685
- label.className = "flex items-center mb-1";
10734
+ label.className = "flex items-center";
10735
+ label.style.marginBottom = "var(--fb-label-margin-bottom, 2px)";
10686
10736
  label.dataset.fbLabelRow = "";
10687
10737
  const title = createFieldLabel(element);
10688
10738
  label.appendChild(title);
@@ -10793,11 +10843,12 @@ function renderElement2(element, ctx) {
10793
10843
  }
10794
10844
  const initiallyDisabled2 = shouldDisableElement(element, ctx);
10795
10845
  const outerWrapper = document.createElement("div");
10796
- outerWrapper.className = "mb-2 fb-field-wrapper fb-markdown-wrapper";
10846
+ outerWrapper.className = `fb-field-wrapper fb-markdown-wrapper fb-size-${element.size || "md"}`;
10797
10847
  outerWrapper.setAttribute(
10798
10848
  "data-field-key",
10799
10849
  getElementLookupKey(element, ctx.state)
10800
10850
  );
10851
+ outerWrapper.setAttribute("data-fb-width", element.width || "full");
10801
10852
  renderMarkdown(element, ctx, outerWrapper);
10802
10853
  if (initiallyDisabled2) {
10803
10854
  outerWrapper.style.display = "none";
@@ -10809,8 +10860,9 @@ function renderElement2(element, ctx) {
10809
10860
  }
10810
10861
  const initiallyDisabled = shouldDisableElement(element, ctx);
10811
10862
  const wrapper = document.createElement("div");
10812
- wrapper.className = "mb-2 fb-field-wrapper";
10863
+ wrapper.className = `fb-field-wrapper fb-size-${element.size || "md"}`;
10813
10864
  wrapper.setAttribute("data-field-key", element.key);
10865
+ wrapper.setAttribute("data-fb-width", element.width || "full");
10814
10866
  const ops = getComponentOperations(element.type);
10815
10867
  if (!(ops == null ? void 0 : ops.ownsLabel)) {
10816
10868
  const label = createLabelContainer(element);
@@ -11132,9 +11184,9 @@ var defaultTheme = {
11132
11184
  // blue-500
11133
11185
  // Spacing
11134
11186
  inputPaddingX: "0.5rem",
11135
- // 8px (compact density v2)
11136
- inputPaddingY: "0.25rem",
11137
- // 4px (compact density v2)
11187
+ // 8px (compact density v3 — v0.4.0)
11188
+ inputPaddingY: "0.125rem",
11189
+ // 2px (compact density v3 — v0.4.0)
11138
11190
  borderRadius: "0.5rem",
11139
11191
  // rounded-lg (8px)
11140
11192
  borderRadiusSmall: "0.375rem",
@@ -11172,13 +11224,20 @@ var defaultTheme = {
11172
11224
  slideCardRadius: "0.5rem",
11173
11225
  // matches borderRadius
11174
11226
  slideCardMinHeight: "0",
11175
- slideCardPadding: "12px",
11227
+ slideCardPadding: "8px",
11176
11228
  // Section-label defaults — same look as the regular field label. Themes
11177
11229
  // that want the "ПРЕИМУЩЕСТВА" caps style override these three vars.
11178
11230
  labelSectionFontSize: "0.875rem",
11179
11231
  // matches fontSize
11180
11232
  labelSectionLetterSpacing: "normal",
11181
- labelSectionTextTransform: "none"
11233
+ labelSectionTextTransform: "none",
11234
+ // Per-field label defaults — compact density: 12px @ 1.2 line-height with
11235
+ // a 2px gap to the input. Themes that prefer roomier labels (Picaz, klein)
11236
+ // can bump these via their override block.
11237
+ labelFontSize: "0.75rem",
11238
+ // 12px
11239
+ labelLineHeight: "1.2",
11240
+ labelMarginBottom: "2px"
11182
11241
  };
11183
11242
  function generateCSSVariables(theme) {
11184
11243
  const mergedTheme = { ...defaultTheme, ...theme };
@@ -11724,7 +11783,7 @@ var FormBuilderInstance = class {
11724
11783
  root.setAttribute("data-fb-root", "true");
11725
11784
  injectThemeVariables(root, this.state.config.theme);
11726
11785
  const rootContainer = document.createElement("div");
11727
- rootContainer.className = "space-y-2";
11786
+ rootContainer.className = "fb-row";
11728
11787
  if (schema.prefillHints && !this.state.config.readonly) {
11729
11788
  const hintsContainer = this.createRootPrefillHints(schema.prefillHints);
11730
11789
  rootContainer.appendChild(hintsContainer);
@@ -11732,7 +11791,7 @@ var FormBuilderInstance = class {
11732
11791
  const fieldsWrapper = document.createElement("div");
11733
11792
  const columns = schema.columns || 1;
11734
11793
  if (columns === 1) {
11735
- fieldsWrapper.className = "space-y-2";
11794
+ fieldsWrapper.className = "fb-row";
11736
11795
  } else {
11737
11796
  fieldsWrapper.className = `grid grid-cols-${columns} gap-2`;
11738
11797
  }