@dmitryvim/form-builder 0.3.4 → 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/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(1, lineCount);
507
+ textarea.rows = Math.max(minRows, lineCount);
476
508
  const resize = () => {
477
509
  if (!textarea.isConnected) return;
478
- textarea.style.height = "0";
510
+ textarea.style.height = "";
479
511
  const cs = getComputedStyle(textarea);
480
512
  const borderY = parseFloat(cs.borderTopWidth || "0") + parseFloat(cs.borderBottomWidth || "0");
481
- textarea.style.height = `${textarea.scrollHeight + borderY}px`;
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 mt-2";
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: 6px;
544
- padding: 6px 10px;
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: 4px; }
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: 8px;
694
- padding: 5px 6px 5px 10px;
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 || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
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);
@@ -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("input");
879
- input.type = "text";
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";
@@ -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) 24px 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 || "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0442\u0435\u043A\u0441\u0442";
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
- if (element.autoExpand || readonly) {
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);
@@ -1148,7 +1194,7 @@ function renderMultipleTextareaElement(element, ctx, wrapper, pathKey) {
1148
1194
  values.push(element.default || "");
1149
1195
  }
1150
1196
  const container = document.createElement("div");
1151
- container.className = "space-y-2";
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) 24px 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
- if (element.autoExpand || readonly) {
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, state } = context;
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}["]`
@@ -1458,7 +1500,7 @@ function renderMultipleNumberElement(element, ctx, wrapper, pathKey) {
1458
1500
  values.push(element.default || "");
1459
1501
  }
1460
1502
  const container = document.createElement("div");
1461
- container.className = "space-y-2";
1503
+ container.className = "fb-row";
1462
1504
  wrapper.appendChild(container);
1463
1505
  function updateIndices() {
1464
1506
  const items = container.querySelectorAll(".multiple-number-item");
@@ -1772,7 +1814,7 @@ function renderMultipleSelectElement(element, ctx, wrapper, pathKey) {
1772
1814
  values.push(element.default || element.options?.[0]?.value || "");
1773
1815
  }
1774
1816
  const container = document.createElement("div");
1775
- container.className = "space-y-2";
1817
+ container.className = "fb-row";
1776
1818
  wrapper.appendChild(container);
1777
1819
  function updateIndices() {
1778
1820
  const items = container.querySelectorAll(".multiple-select-item");
@@ -2202,7 +2244,7 @@ function renderMultipleSwitcherElement(element, ctx, wrapper, pathKey) {
2202
2244
  }
2203
2245
  const readonly = isElementReadonly(element, state, ctx);
2204
2246
  const container = document.createElement("div");
2205
- container.className = "space-y-2";
2247
+ container.className = "fb-row";
2206
2248
  wrapper.appendChild(container);
2207
2249
  function updateIndices() {
2208
2250
  const items = container.querySelectorAll(".multiple-switcher-item");
@@ -2774,7 +2816,9 @@ function ensureFileStyles() {
2774
2816
  align-items: stretch;
2775
2817
  gap: 0;
2776
2818
  overflow: hidden;
2777
- min-height: 180px;
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: 140px;
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: 8px;
2846
+ gap: 4px;
2802
2847
  color: #2563eb;
2803
- padding: 16px;
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-height: 120px;
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: 8px;
2875
+ gap: 4px;
2829
2876
  color: #2563eb;
2830
- padding: 12px;
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 = "space-y-2";
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 = "space-y-2";
5228
+ filesWrapper.className = "fb-row";
5180
5229
  filesWrapper.dataset.filesWrapper = pathKey;
5181
5230
  const filesPicker = document.createElement("input");
5182
5231
  filesPicker.type = "file";
@@ -5848,7 +5897,7 @@ function renderMultipleColourElement(element, ctx, wrapper, pathKey) {
5848
5897
  values.push(element.default || "#000000");
5849
5898
  }
5850
5899
  const container = document.createElement("div");
5851
- container.className = "space-y-2";
5900
+ container.className = "fb-row";
5852
5901
  wrapper.appendChild(container);
5853
5902
  function updateIndices() {
5854
5903
  const items = container.querySelectorAll(".multiple-colour-item");
@@ -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 = "space-y-2";
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 "space-y-2";
6795
+ return "fb-row";
6747
6796
  }
6748
6797
  const cols = columns || 1;
6749
- return cols === 1 ? "space-y-2" : `grid grid-cols-${cols} gap-2`;
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 = "space-y-2";
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 = "space-y-2";
6946
+ childWrapper.className = "fb-row";
6898
6947
  } else {
6899
6948
  const columns = element.columns || 1;
6900
6949
  if (columns === 1) {
6901
- childWrapper.className = "space-y-2";
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 = "space-y-2";
6993
+ childWrapper.className = "fb-row";
6945
6994
  } else {
6946
6995
  const columns = element.columns || 1;
6947
6996
  if (columns === 1) {
6948
- childWrapper.className = "space-y-2";
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 = null;
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 = "text-sm font-medium text-gray-900";
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 mb-1";
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 = "mb-2 fb-field-wrapper fb-markdown-wrapper";
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 = "mb-2 fb-field-wrapper";
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 v2)
10948
- inputPaddingY: "0.25rem",
10949
- // 4px (compact density v2)
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: "12px",
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 = "space-y-2";
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 = "space-y-2";
11607
+ fieldsWrapper.className = "fb-row";
11548
11608
  } else {
11549
11609
  fieldsWrapper.className = `grid grid-cols-${columns} gap-2`;
11550
11610
  }