@dmitryvim/form-builder 0.2.29 → 0.2.30

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
@@ -153,12 +153,14 @@ function validateSchema(schema) {
153
153
  allOutputKeys.add(textKey);
154
154
  allOutputKeys.add(filesKey);
155
155
  } else {
156
- if (allOutputKeys.has(el.key)) {
157
- errors.push(
158
- `${scopePath}: Element key "${el.key}" collides with a flatOutput richinput key`
159
- );
156
+ if (el.key) {
157
+ if (allOutputKeys.has(el.key)) {
158
+ errors.push(
159
+ `${scopePath}: Element key "${el.key}" collides with a flatOutput richinput key`
160
+ );
161
+ }
162
+ allOutputKeys.add(el.key);
160
163
  }
161
- allOutputKeys.add(el.key);
162
164
  }
163
165
  }
164
166
  }
@@ -168,9 +170,17 @@ function validateSchema(schema) {
168
170
  if (!element.type) {
169
171
  errors.push(`${elementPath}: missing type`);
170
172
  }
171
- if (!element.key) {
173
+ if (!element.key && element.type !== "markdown") {
172
174
  errors.push(`${elementPath}: missing key`);
173
175
  }
176
+ if (element.type === "markdown") {
177
+ const content = element.content;
178
+ if (typeof content !== "string") {
179
+ errors.push(
180
+ `${elementPath}: markdown element requires "content" to be a string (got ${content === null ? "null" : typeof content})`
181
+ );
182
+ }
183
+ }
174
184
  if (element.enableIf) {
175
185
  const enableIf = element.enableIf;
176
186
  if (!enableIf.key || typeof enableIf.key !== "string") {
@@ -263,6 +273,18 @@ function escapeHtml(text) {
263
273
  div.textContent = text;
264
274
  return div.innerHTML;
265
275
  }
276
+ function getElementLookupKey(element, state) {
277
+ if (element.key) {
278
+ return element.key;
279
+ }
280
+ const cached = state.syntheticElementIds.get(element);
281
+ if (cached !== void 0) {
282
+ return cached;
283
+ }
284
+ const id = `fb-synthetic-${state.syntheticElementIdCounter++}`;
285
+ state.syntheticElementIds.set(element, id);
286
+ return id;
287
+ }
266
288
  function pathJoin(base, key) {
267
289
  return base ? `${base}.${key}` : key;
268
290
  }
@@ -5454,7 +5476,7 @@ function updateSliderField(element, fieldPath, value, context) {
5454
5476
  function extractChildDefaults(elements) {
5455
5477
  const defaults = {};
5456
5478
  for (const child of elements) {
5457
- if ("default" in child && child.default !== void 0) {
5479
+ if (child.key && "default" in child && child.default !== void 0) {
5458
5480
  defaults[child.key] = child.default;
5459
5481
  }
5460
5482
  }
@@ -5549,8 +5571,8 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
5549
5571
  inheritedReadonly: containerIsReadonly || ctx.inheritedReadonly
5550
5572
  };
5551
5573
  element.elements.forEach((child) => {
5552
- if (child.hidden || child.type === "hidden") {
5553
- const prefillVal = containerPrefill[child.key] ?? child.default ?? null;
5574
+ if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
5575
+ const prefillVal = containerPrefill[child.key] ?? ("default" in child ? child.default : null) ?? null;
5554
5576
  itemsWrap.appendChild(
5555
5577
  createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
5556
5578
  );
@@ -5624,11 +5646,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5624
5646
  childWrapper.className = `grid grid-cols-${columns} gap-4`;
5625
5647
  }
5626
5648
  element.elements.forEach((child) => {
5627
- if (child.hidden || child.type === "hidden") {
5649
+ if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
5628
5650
  childWrapper.appendChild(
5629
5651
  createHiddenInput(
5630
5652
  pathJoin(subCtx.path, child.key),
5631
- child.default ?? null
5653
+ ("default" in child ? child.default : null) ?? null
5632
5654
  )
5633
5655
  );
5634
5656
  } else {
@@ -5701,8 +5723,8 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5701
5723
  childWrapper.className = `grid grid-cols-${columns} gap-4`;
5702
5724
  }
5703
5725
  element.elements.forEach((child) => {
5704
- if (child.hidden || child.type === "hidden") {
5705
- const prefillVal = prefillObj?.[child.key] ?? child.default ?? null;
5726
+ if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
5727
+ const prefillVal = prefillObj?.[child.key] ?? ("default" in child ? child.default : null) ?? null;
5706
5728
  childWrapper.appendChild(
5707
5729
  createHiddenInput(pathJoin(subCtx.path, child.key), prefillVal)
5708
5730
  );
@@ -5757,11 +5779,11 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
5757
5779
  childWrapper.className = `grid grid-cols-${columns} gap-4`;
5758
5780
  }
5759
5781
  element.elements.forEach((child) => {
5760
- if (child.hidden || child.type === "hidden") {
5782
+ if (child.type !== "markdown" && (child.hidden || child.type === "hidden")) {
5761
5783
  childWrapper.appendChild(
5762
5784
  createHiddenInput(
5763
5785
  pathJoin(subCtx.path, child.key),
5764
- child.default ?? null
5786
+ ("default" in child ? child.default : null) ?? null
5765
5787
  )
5766
5788
  );
5767
5789
  } else {
@@ -5947,6 +5969,7 @@ function updateContainerField(element, fieldPath, value, context) {
5947
5969
  value.forEach((itemValue, index) => {
5948
5970
  if (isPlainObject(itemValue)) {
5949
5971
  element.elements.forEach((childElement) => {
5972
+ if (childElement.type === "markdown" || !childElement.key) return;
5950
5973
  const childPath = `${fieldPath}[${index}].${childElement.key}`;
5951
5974
  if (childElement.type === "richinput" && childElement.flatOutput) {
5952
5975
  const richChild = childElement;
@@ -5986,6 +6009,7 @@ function updateContainerField(element, fieldPath, value, context) {
5986
6009
  return;
5987
6010
  }
5988
6011
  element.elements.forEach((childElement) => {
6012
+ if (childElement.type === "markdown" || !childElement.key) return;
5989
6013
  const childPath = `${fieldPath}.${childElement.key}`;
5990
6014
  if (childElement.type === "richinput" && childElement.flatOutput) {
5991
6015
  const richChild = childElement;
@@ -8685,6 +8709,248 @@ function updateRichInputField(element, fieldPath, value, context) {
8685
8709
  }
8686
8710
  }
8687
8711
 
8712
+ // src/components/markdown/snarkdown.ts
8713
+ var TAGS = {
8714
+ "": ["<em>", "</em>"],
8715
+ _: ["<strong>", "</strong>"],
8716
+ "*": ["<strong>", "</strong>"],
8717
+ "~": ["<s>", "</s>"],
8718
+ "\n": ["<br />"],
8719
+ " ": ["<br />"],
8720
+ "-": ["<hr />"]
8721
+ };
8722
+ function outdent(str) {
8723
+ return str.replace(
8724
+ RegExp("^" + (str.match(/^(\t| )+/) || "")[0], "gm"),
8725
+ ""
8726
+ );
8727
+ }
8728
+ function encodeAttr(str) {
8729
+ return (str + "").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
8730
+ }
8731
+ function parse(md, prevLinks) {
8732
+ const tokenizer = /((?:^|\n+)(?:\n---+|\*[ ]\*(?:[ ]\*)+)\n)|(?:^```[ ]*(\w*)\n([\s\S]*?)\n```$)|((?:(?:^|\n+)(?:\t|[ ]{2,}).+)+\n*)|((?:(?:^|\n)([>*+-]|\d+\.)\s+.*)+)|(?:!\[([^\]]*?)\]\(([^)]+?)\))|(\[)|(\](?:\(([^)]+?)\))?)|(?:(?:^|\n+)([^\s].*)\n(-{3,}|={3,})(?:\n+|$))|(?:(?:^|\n+)(#{1,6})\s*(.+)(?:\n+|$))|(?:`([^`].*?)`)|([ ]{2}\n\n*|\n{2,}|__|\*\*|[_*]|~~)/gm;
8733
+ const context = [];
8734
+ let out = "";
8735
+ const links = prevLinks || {};
8736
+ let last = 0;
8737
+ let chunk;
8738
+ let prev;
8739
+ let token;
8740
+ let inner;
8741
+ let t2;
8742
+ function tag(token2) {
8743
+ const desc = TAGS[token2[1] || ""];
8744
+ const end = context[context.length - 1] === token2;
8745
+ if (!desc) return token2;
8746
+ if (!desc[1]) return desc[0];
8747
+ if (end) context.pop();
8748
+ else context.push(token2);
8749
+ return desc[end ? 1 : 0];
8750
+ }
8751
+ function flush() {
8752
+ let str = "";
8753
+ while (context.length) str += tag(context[context.length - 1]);
8754
+ return str;
8755
+ }
8756
+ md = md.replace(/^\[(.+?)\]:\s*(.+)$/gm, (_s, name, url) => {
8757
+ links[name.toLowerCase()] = url;
8758
+ return "";
8759
+ }).replace(/^\n+|\n+$/g, "");
8760
+ while (token = tokenizer.exec(md)) {
8761
+ prev = md.substring(last, token.index);
8762
+ last = tokenizer.lastIndex;
8763
+ chunk = token[0];
8764
+ if (prev.match(/[^\\](\\\\)*\\$/)) ; else if (t2 = token[3] || token[4]) {
8765
+ chunk = '<pre class="code ' + (token[4] ? "poetry" : token[2].toLowerCase()) + '"><code' + (token[2] ? ` class="language-${token[2].toLowerCase()}"` : "") + ">" + outdent(encodeAttr(t2).replace(/^\n+|\n+$/g, "")) + "</code></pre>";
8766
+ } else if (t2 = token[6]) {
8767
+ if (t2.match(/\./)) {
8768
+ token[5] = token[5].replace(/^\d+/gm, "");
8769
+ }
8770
+ inner = parse(outdent(token[5].replace(/^\s*[>*+.-]/gm, "")));
8771
+ if (t2 === ">") t2 = "blockquote";
8772
+ else {
8773
+ t2 = t2.match(/\./) ? "ol" : "ul";
8774
+ inner = inner.replace(/^(.*)(\n|$)/gm, "<li>$1</li>");
8775
+ }
8776
+ chunk = "<" + t2 + ">" + inner + "</" + t2 + ">";
8777
+ } else if (token[8]) {
8778
+ chunk = `<img src="${encodeAttr(token[8])}" alt="${encodeAttr(token[7])}">`;
8779
+ } else if (token[10]) {
8780
+ out = out.replace(
8781
+ "<a>",
8782
+ `<a href="${encodeAttr(token[11] || links[prev.toLowerCase()])}">`
8783
+ );
8784
+ chunk = flush() + "</a>";
8785
+ } else if (token[9]) {
8786
+ chunk = "<a>";
8787
+ } else if (token[12] || token[14]) {
8788
+ t2 = "h" + (token[14] ? token[14].length : token[13] > "=" ? 1 : 2);
8789
+ chunk = "<" + t2 + ">" + parse(token[12] || token[15], links) + "</" + t2 + ">";
8790
+ } else if (token[16]) {
8791
+ chunk = "<code>" + encodeAttr(token[16]) + "</code>";
8792
+ } else if (token[17] || token[1]) {
8793
+ chunk = tag(token[17] || "--");
8794
+ }
8795
+ out += prev;
8796
+ out += chunk;
8797
+ }
8798
+ return (out + md.substring(last) + flush()).replace(/^\n+|\n+$/g, "");
8799
+ }
8800
+
8801
+ // src/components/markdown/render.ts
8802
+ var STYLE_ID2 = "fb-markdown-styles";
8803
+ function ensureMarkdownStyles() {
8804
+ if (typeof document === "undefined") return;
8805
+ if (document.getElementById(STYLE_ID2)) return;
8806
+ const style = document.createElement("style");
8807
+ style.id = STYLE_ID2;
8808
+ style.setAttribute("data-fb-markdown-styles", "true");
8809
+ style.textContent = `
8810
+ .fb-markdown {
8811
+ font-family: var(--fb-font-family, inherit);
8812
+ font-size: var(--fb-font-size, 1rem);
8813
+ color: var(--fb-text-color, #1f2937);
8814
+ line-height: 1.6;
8815
+ }
8816
+ .fb-markdown h1,
8817
+ .fb-markdown h2,
8818
+ .fb-markdown h3,
8819
+ .fb-markdown h4,
8820
+ .fb-markdown h5,
8821
+ .fb-markdown h6 {
8822
+ font-weight: var(--fb-font-weight-medium, 600);
8823
+ color: var(--fb-text-color, #1f2937);
8824
+ margin-top: 0.75em;
8825
+ margin-bottom: 0.25em;
8826
+ }
8827
+ .fb-markdown h1 { font-size: 1.5rem; }
8828
+ .fb-markdown h2 { font-size: 1.25rem; }
8829
+ .fb-markdown h3 { font-size: 1.1rem; }
8830
+ .fb-markdown h4,
8831
+ .fb-markdown h5,
8832
+ .fb-markdown h6 { font-size: 1rem; }
8833
+ .fb-markdown p {
8834
+ margin-top: 0;
8835
+ margin-bottom: 0.5em;
8836
+ }
8837
+ .fb-markdown ul,
8838
+ .fb-markdown ol {
8839
+ margin: 0.25em 0 0.5em 1.5em;
8840
+ padding: 0;
8841
+ }
8842
+ .fb-markdown li {
8843
+ margin-bottom: 0.15em;
8844
+ }
8845
+ .fb-markdown a {
8846
+ color: var(--fb-primary-color, #2563eb);
8847
+ text-decoration: underline;
8848
+ }
8849
+ .fb-markdown a:hover {
8850
+ opacity: 0.8;
8851
+ }
8852
+ .fb-markdown code {
8853
+ background: var(--fb-input-background-color, #f3f4f6);
8854
+ border-radius: 3px;
8855
+ padding: 0.1em 0.35em;
8856
+ font-size: 0.9em;
8857
+ font-family: monospace;
8858
+ }
8859
+ .fb-markdown pre {
8860
+ background: var(--fb-input-background-color, #f3f4f6);
8861
+ border-radius: var(--fb-border-radius, 0.375rem);
8862
+ padding: 0.75em 1em;
8863
+ overflow-x: auto;
8864
+ margin: 0.5em 0;
8865
+ }
8866
+ .fb-markdown pre code {
8867
+ background: none;
8868
+ padding: 0;
8869
+ border-radius: 0;
8870
+ font-size: inherit;
8871
+ }
8872
+ .fb-markdown blockquote {
8873
+ border-left: 3px solid var(--fb-border-color, #d1d5db);
8874
+ margin: 0.5em 0;
8875
+ padding-left: 1em;
8876
+ color: var(--fb-text-secondary-color, #6b7280);
8877
+ }
8878
+ .fb-markdown hr {
8879
+ border: none;
8880
+ border-top: 1px solid var(--fb-border-color, #d1d5db);
8881
+ margin: 0.75em 0;
8882
+ }
8883
+ .fb-markdown strong { font-weight: var(--fb-font-weight-medium, 600); }
8884
+ .fb-markdown em { font-style: italic; }
8885
+ .fb-markdown s { text-decoration: line-through; }
8886
+ `;
8887
+ document.head.appendChild(style);
8888
+ }
8889
+ var ANCHOR_DANGEROUS_SCHEMES = [
8890
+ "javascript:",
8891
+ "data:",
8892
+ "vbscript:",
8893
+ "blob:"
8894
+ ];
8895
+ var IMG_DANGEROUS_SCHEMES = ["javascript:", "vbscript:", "blob:"];
8896
+ function isImgSrcDangerous(normalized) {
8897
+ if (IMG_DANGEROUS_SCHEMES.some((scheme) => normalized.startsWith(scheme))) {
8898
+ return true;
8899
+ }
8900
+ if (normalized.startsWith("data:") && !normalized.startsWith("data:image/")) {
8901
+ return true;
8902
+ }
8903
+ return false;
8904
+ }
8905
+ function escapeRawHtml(content) {
8906
+ return content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
8907
+ }
8908
+ function sanitizeElements(container) {
8909
+ const anchors = container.querySelectorAll("a[href]");
8910
+ anchors.forEach((a) => {
8911
+ const href = a.getAttribute("href") ?? "";
8912
+ const normalized = href.trim().toLowerCase();
8913
+ const isDangerous = ANCHOR_DANGEROUS_SCHEMES.some(
8914
+ (scheme) => normalized.startsWith(scheme)
8915
+ );
8916
+ if (isDangerous) {
8917
+ a.setAttribute("href", "#");
8918
+ }
8919
+ a.setAttribute("target", "_blank");
8920
+ a.setAttribute("rel", "noopener noreferrer");
8921
+ });
8922
+ const images = container.querySelectorAll("img[src]");
8923
+ images.forEach((img) => {
8924
+ const src = img.getAttribute("src") ?? "";
8925
+ const normalized = src.trim().toLowerCase();
8926
+ if (isImgSrcDangerous(normalized)) {
8927
+ img.setAttribute("src", "");
8928
+ }
8929
+ });
8930
+ }
8931
+ function renderMarkdown(element, _ctx, parent) {
8932
+ if (typeof element.content !== "string") {
8933
+ throw new Error(
8934
+ `renderMarkdown: markdown element${element.key ? ` "${element.key}"` : ""} requires "content" to be a string (got ${element.content === null ? "null" : typeof element.content})`
8935
+ );
8936
+ }
8937
+ ensureMarkdownStyles();
8938
+ const wrapper = document.createElement("div");
8939
+ wrapper.className = "fb-markdown";
8940
+ const escaped = escapeRawHtml(element.content);
8941
+ wrapper.innerHTML = parse(escaped);
8942
+ sanitizeElements(wrapper);
8943
+ parent.appendChild(wrapper);
8944
+ return wrapper;
8945
+ }
8946
+
8947
+ // src/components/markdown/index.ts
8948
+ function validateMarkdown(_element, _key, _context) {
8949
+ return { value: void 0, errors: [], skip: true };
8950
+ }
8951
+ function updateMarkdown(_element, _fieldPath, _value, _context) {
8952
+ }
8953
+
8688
8954
  // src/components/index.ts
8689
8955
  function showTooltip(tooltipId, button) {
8690
8956
  const tooltip = document.getElementById(tooltipId);
@@ -8915,7 +9181,7 @@ function setupEnableIfListeners(wrapper, element, ctx) {
8915
9181
  function createFieldLabel(element) {
8916
9182
  const title = document.createElement("label");
8917
9183
  title.className = "text-sm font-medium text-gray-900";
8918
- title.textContent = element.label || element.key;
9184
+ title.textContent = element.label ?? element.key ?? "";
8919
9185
  if (element.required) {
8920
9186
  const req = document.createElement("span");
8921
9187
  req.className = "text-red-500 ml-1";
@@ -9043,6 +9309,29 @@ function dispatchToRenderer(element, ctx, wrapper, pathKey) {
9043
9309
  }
9044
9310
  }
9045
9311
  function renderElement2(element, ctx) {
9312
+ if (element.type === "markdown") {
9313
+ if (element.hidden === true) {
9314
+ const placeholder = document.createElement("div");
9315
+ placeholder.style.display = "none";
9316
+ placeholder.setAttribute("data-fb-hidden-markdown", "true");
9317
+ return placeholder;
9318
+ }
9319
+ const initiallyDisabled2 = shouldDisableElement(element, ctx);
9320
+ const outerWrapper = document.createElement("div");
9321
+ outerWrapper.className = "mb-6 fb-field-wrapper fb-markdown-wrapper";
9322
+ outerWrapper.setAttribute(
9323
+ "data-field-key",
9324
+ getElementLookupKey(element, ctx.state)
9325
+ );
9326
+ renderMarkdown(element, ctx, outerWrapper);
9327
+ if (initiallyDisabled2) {
9328
+ outerWrapper.style.display = "none";
9329
+ outerWrapper.classList.add("fb-field-wrapper-disabled");
9330
+ outerWrapper.setAttribute("data-conditionally-disabled", "true");
9331
+ }
9332
+ setupEnableIfListeners(outerWrapper, element, ctx);
9333
+ return outerWrapper;
9334
+ }
9046
9335
  const initiallyDisabled = shouldDisableElement(element, ctx);
9047
9336
  const wrapper = document.createElement("div");
9048
9337
  wrapper.className = "mb-6 fb-field-wrapper";
@@ -9253,7 +9542,9 @@ function createInstanceState(config) {
9253
9542
  translations: mergedTranslations
9254
9543
  },
9255
9544
  debounceTimer: null,
9256
- prefill: {}
9545
+ prefill: {},
9546
+ syntheticElementIds: /* @__PURE__ */ new WeakMap(),
9547
+ syntheticElementIdCounter: 0
9257
9548
  };
9258
9549
  }
9259
9550
  function generateInstanceId() {
@@ -9530,6 +9821,11 @@ var componentRegistry = {
9530
9821
  // Legacy type: `type: "hidden"` — reads/writes DOM <input type="hidden"> element
9531
9822
  validate: validateHiddenElement,
9532
9823
  update: updateHiddenField
9824
+ },
9825
+ markdown: {
9826
+ // Display-only element — no value, no errors, skip from form data
9827
+ validate: validateMarkdown,
9828
+ update: updateMarkdown
9533
9829
  }
9534
9830
  };
9535
9831
  function getComponentOperations(elementType) {
@@ -9957,7 +10253,7 @@ var FormBuilderInstance = class {
9957
10253
  fieldsWrapper.className = `grid grid-cols-${columns} gap-4`;
9958
10254
  }
9959
10255
  schema.elements.forEach((element) => {
9960
- if (element.hidden || element.type === "hidden") {
10256
+ if (element.type !== "markdown" && (element.hidden || element.type === "hidden")) {
9961
10257
  const val = prefill?.[element.key] ?? element.default ?? null;
9962
10258
  fieldsWrapper.appendChild(createHiddenInput(element.key, val));
9963
10259
  return;
@@ -9992,7 +10288,7 @@ var FormBuilderInstance = class {
9992
10288
  const errors = [];
9993
10289
  const data = {};
9994
10290
  const validateElement2 = (element, ctx, customScopeRoot = null) => {
9995
- const key = element.key;
10291
+ const key = element.key ?? "";
9996
10292
  const scopeRoot = customScopeRoot || this.state.formRoot;
9997
10293
  const componentContext = {
9998
10294
  scopeRoot,
@@ -10010,7 +10306,8 @@ var FormBuilderInstance = class {
10010
10306
  errors.push(...componentResult.errors);
10011
10307
  return {
10012
10308
  value: componentResult.value,
10013
- spread: !!componentResult.spread
10309
+ spread: !!componentResult.spread,
10310
+ skip: !!componentResult.skip
10014
10311
  };
10015
10312
  }
10016
10313
  console.warn(`Unknown field type "${element.type}" for key "${key}"`);
@@ -10031,6 +10328,9 @@ var FormBuilderInstance = class {
10031
10328
  );
10032
10329
  }
10033
10330
  }
10331
+ if (element.type === "markdown") {
10332
+ return;
10333
+ }
10034
10334
  if (element.hidden || element.type === "hidden") {
10035
10335
  const hiddenInput = this.state.formRoot.querySelector(
10036
10336
  `input[type="hidden"][data-hidden-field="true"][name="${element.key}"]`
@@ -10043,9 +10343,10 @@ var FormBuilderInstance = class {
10043
10343
  }
10044
10344
  } else {
10045
10345
  const result = validateElement2(element, { path: "" });
10346
+ if (result.skip) return;
10046
10347
  if (result.spread && result.value !== null && typeof result.value === "object") {
10047
10348
  Object.assign(data, result.value);
10048
- } else {
10349
+ } else if (element.key) {
10049
10350
  data[element.key] = result.value;
10050
10351
  }
10051
10352
  }
@@ -10121,6 +10422,7 @@ var FormBuilderInstance = class {
10121
10422
  buildHiddenFieldsData(elements) {
10122
10423
  const data = {};
10123
10424
  for (const element of elements) {
10425
+ if (element.type === "markdown" || !element.key) continue;
10124
10426
  const key = element.key;
10125
10427
  if (element.hidden && element.default !== void 0) {
10126
10428
  data[key] = element.default;
@@ -10241,6 +10543,71 @@ var FormBuilderInstance = class {
10241
10543
  );
10242
10544
  }
10243
10545
  }
10546
+ /**
10547
+ * Find the field wrapper DOM element for a given element+path combo.
10548
+ * Used by reevaluateConditionalFields.
10549
+ */
10550
+ findFieldWrapper(element, currentPath) {
10551
+ const formRoot = this.state.formRoot;
10552
+ const lookupKey = getElementLookupKey(element, this.state);
10553
+ if (!currentPath) {
10554
+ return formRoot.querySelector(`[data-field-key="${lookupKey}"]`);
10555
+ }
10556
+ const pathMatch = currentPath.match(/^(.+)\[(\d+)\]$/);
10557
+ if (pathMatch) {
10558
+ const containerEl2 = formRoot.querySelector(
10559
+ `[data-container-item="${pathMatch[1]}[${pathMatch[2]}]"]`
10560
+ );
10561
+ return containerEl2 ? containerEl2.querySelector(`[data-field-key="${lookupKey}"]`) : null;
10562
+ }
10563
+ const containerEl = formRoot.querySelector(
10564
+ `[data-container="${currentPath}"]`
10565
+ );
10566
+ return containerEl ? containerEl.querySelector(`[data-field-key="${lookupKey}"]`) : null;
10567
+ }
10568
+ /**
10569
+ * Apply enableIf show/hide logic to a single field wrapper.
10570
+ * Extracted to reduce cyclomatic complexity of checkElements.
10571
+ */
10572
+ applyEnableIfVisibility(element, wrapper, currentPath, fullPath, formData) {
10573
+ try {
10574
+ const scope = element.enableIf.scope ?? "relative";
10575
+ const containerData = scope === "relative" && currentPath ? getValueByPath(formData, currentPath) : void 0;
10576
+ const shouldEnable = evaluateEnableCondition(
10577
+ element.enableIf,
10578
+ formData,
10579
+ containerData
10580
+ );
10581
+ const isCurrentlyDisabled = wrapper.getAttribute("data-conditionally-disabled") === "true";
10582
+ if (shouldEnable && isCurrentlyDisabled) {
10583
+ const containerPrefill = currentPath ? getValueByPath(formData, currentPath) : formData;
10584
+ const prefillContext = containerPrefill && typeof containerPrefill === "object" ? containerPrefill : {};
10585
+ const newWrapper = renderElement2(element, {
10586
+ path: currentPath,
10587
+ prefill: prefillContext,
10588
+ formData,
10589
+ state: this.state,
10590
+ instance: this
10591
+ });
10592
+ wrapper.parentNode?.replaceChild(newWrapper, wrapper);
10593
+ } else if (!shouldEnable && !isCurrentlyDisabled) {
10594
+ const disabledWrapper = document.createElement("div");
10595
+ disabledWrapper.className = "fb-field-wrapper-disabled";
10596
+ disabledWrapper.style.display = "none";
10597
+ disabledWrapper.setAttribute(
10598
+ "data-field-key",
10599
+ getElementLookupKey(element, this.state)
10600
+ );
10601
+ disabledWrapper.setAttribute("data-conditionally-disabled", "true");
10602
+ wrapper.parentNode?.replaceChild(disabledWrapper, wrapper);
10603
+ }
10604
+ } catch (error) {
10605
+ console.error(
10606
+ `Error re-evaluating enableIf for field "${element.key ?? "<no key>"}" at path "${fullPath}":`,
10607
+ error
10608
+ );
10609
+ }
10610
+ }
10244
10611
  /**
10245
10612
  * Re-evaluate all conditional fields (enableIf) based on current form data
10246
10613
  * This is called automatically when form data changes (via onChange events)
@@ -10250,97 +10617,31 @@ var FormBuilderInstance = class {
10250
10617
  const formData = this.validateForm(true).data;
10251
10618
  const checkElements = (elements, currentPath) => {
10252
10619
  elements.forEach((element) => {
10253
- const fullPath = currentPath ? `${currentPath}.${element.key}` : element.key;
10620
+ const fullPath = currentPath ? `${currentPath}.${element.key ?? ""}` : element.key ?? "";
10254
10621
  if (element.enableIf) {
10255
- let fieldWrapper = null;
10256
- if (currentPath) {
10257
- const pathMatch = currentPath.match(/^(.+)\[(\d+)\]$/);
10258
- if (pathMatch) {
10259
- const containerKey = pathMatch[1];
10260
- const containerIndex = pathMatch[2];
10261
- const containerElement = this.state.formRoot.querySelector(
10262
- `[data-container-item="${containerKey}[${containerIndex}]"]`
10263
- );
10264
- if (containerElement) {
10265
- fieldWrapper = containerElement.querySelector(
10266
- `[data-field-key="${element.key}"]`
10267
- );
10268
- }
10269
- } else {
10270
- const containerElement = this.state.formRoot.querySelector(
10271
- `[data-container="${currentPath}"]`
10272
- );
10273
- if (containerElement) {
10274
- fieldWrapper = containerElement.querySelector(
10275
- `[data-field-key="${element.key}"]`
10276
- );
10277
- }
10278
- }
10279
- } else {
10280
- fieldWrapper = this.state.formRoot.querySelector(
10281
- `[data-field-key="${element.key}"]`
10282
- );
10283
- }
10622
+ const fieldWrapper = this.findFieldWrapper(element, currentPath);
10284
10623
  if (fieldWrapper) {
10285
- const wrapper = fieldWrapper;
10286
- try {
10287
- let containerData = void 0;
10288
- const scope = element.enableIf.scope ?? "relative";
10289
- if (scope === "relative" && currentPath) {
10290
- containerData = getValueByPath(formData, currentPath);
10291
- }
10292
- const shouldEnable = evaluateEnableCondition(
10293
- element.enableIf,
10294
- formData,
10295
- // Use complete formData for absolute scope
10296
- containerData
10297
- // Use container-specific data for relative scope
10298
- );
10299
- const isCurrentlyDisabled = wrapper.getAttribute("data-conditionally-disabled") === "true";
10300
- if (shouldEnable && isCurrentlyDisabled) {
10301
- const containerPrefill = currentPath ? getValueByPath(formData, currentPath) : formData;
10302
- const prefillContext = containerPrefill && typeof containerPrefill === "object" ? containerPrefill : {};
10303
- const newWrapper = renderElement2(element, {
10304
- path: currentPath,
10305
- // Use container path (empty string for root-level)
10306
- prefill: prefillContext,
10307
- formData,
10308
- // Pass complete formData for enableIf evaluation
10309
- state: this.state,
10310
- instance: this
10311
- });
10312
- wrapper.parentNode?.replaceChild(newWrapper, wrapper);
10313
- } else if (!shouldEnable && !isCurrentlyDisabled) {
10314
- const disabledWrapper = document.createElement("div");
10315
- disabledWrapper.className = "fb-field-wrapper-disabled";
10316
- disabledWrapper.style.display = "none";
10317
- disabledWrapper.setAttribute("data-field-key", element.key);
10318
- disabledWrapper.setAttribute(
10319
- "data-conditionally-disabled",
10320
- "true"
10321
- );
10322
- wrapper.parentNode?.replaceChild(disabledWrapper, wrapper);
10323
- }
10324
- } catch (error) {
10325
- console.error(
10326
- `Error re-evaluating enableIf for field "${element.key}" at path "${fullPath}":`,
10327
- error
10328
- );
10329
- }
10624
+ this.applyEnableIfVisibility(
10625
+ element,
10626
+ fieldWrapper,
10627
+ currentPath,
10628
+ fullPath,
10629
+ formData
10630
+ );
10330
10631
  }
10331
10632
  }
10332
10633
  if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
10333
- const containerData = formData?.[element.key];
10634
+ const containerData = element.key ? formData?.[element.key] : void 0;
10334
10635
  if (Array.isArray(containerData)) {
10335
10636
  const containerItems = this.state.formRoot.querySelectorAll(
10336
10637
  `[data-container-item]`
10337
10638
  );
10338
- const directItems = Array.from(containerItems).filter((el) => {
10639
+ const directItems = fullPath ? Array.from(containerItems).filter((el) => {
10339
10640
  const attr = el.getAttribute("data-container-item") || "";
10340
10641
  if (!attr.startsWith(`${fullPath}[`)) return false;
10341
10642
  const suffix = attr.slice(fullPath.length);
10342
10643
  return /^\[\d+\]$/.test(suffix);
10343
- });
10644
+ }) : [];
10344
10645
  directItems.forEach((el) => {
10345
10646
  const attr = el.getAttribute("data-container-item") || "";
10346
10647
  checkElements(element.elements, attr);