@dmitryvim/form-builder 0.2.5 → 0.2.6

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.
@@ -62,9 +62,6 @@ function validateSchema(schema) {
62
62
  errors.push("Schema must be an object");
63
63
  return errors;
64
64
  }
65
- if (!schema.version) {
66
- errors.push("Schema missing version");
67
- }
68
65
  if (!Array.isArray(schema.elements)) {
69
66
  errors.push("Schema missing elements array");
70
67
  return errors;
@@ -78,6 +75,20 @@ function validateSchema(schema) {
78
75
  if (!element.key) {
79
76
  errors.push(`${elementPath}: missing key`);
80
77
  }
78
+ if (element.displayIf) {
79
+ const displayIf = element.displayIf;
80
+ if (!displayIf.key || typeof displayIf.key !== "string") {
81
+ errors.push(
82
+ `${elementPath}: displayIf must have a 'key' property of type string`
83
+ );
84
+ }
85
+ const hasOperator = "equals" in displayIf;
86
+ if (!hasOperator) {
87
+ errors.push(
88
+ `${elementPath}: displayIf must have at least one operator (equals, etc.)`
89
+ );
90
+ }
91
+ }
81
92
  if (element.type === "group" && "elements" in element && element.elements) {
82
93
  validateElements(element.elements, `${elementPath}.elements`);
83
94
  }
@@ -115,6 +126,66 @@ function clear(node) {
115
126
  while (node.firstChild) node.removeChild(node.firstChild);
116
127
  }
117
128
 
129
+ // src/utils/display-conditions.ts
130
+ function getValueByPath(data, path) {
131
+ if (!data || typeof data !== "object") {
132
+ return void 0;
133
+ }
134
+ const segments = path.match(/[^.[\]]+|\[\d+\]/g);
135
+ if (!segments || segments.length === 0) {
136
+ return void 0;
137
+ }
138
+ let current = data;
139
+ for (const segment of segments) {
140
+ if (current === void 0 || current === null) {
141
+ return void 0;
142
+ }
143
+ if (segment.startsWith("[") && segment.endsWith("]")) {
144
+ const index = parseInt(segment.slice(1, -1), 10);
145
+ if (!Array.isArray(current) || isNaN(index)) {
146
+ return void 0;
147
+ }
148
+ current = current[index];
149
+ } else {
150
+ current = current[segment];
151
+ }
152
+ }
153
+ return current;
154
+ }
155
+ function evaluateDisplayCondition(condition, formData) {
156
+ if (!condition || !condition.key) {
157
+ throw new Error(
158
+ "Invalid displayIf condition: must have a 'key' property"
159
+ );
160
+ }
161
+ const actualValue = getValueByPath(formData, condition.key);
162
+ if ("equals" in condition) {
163
+ return deepEqual(actualValue, condition.equals);
164
+ }
165
+ throw new Error(
166
+ `Invalid displayIf condition: no recognized operator (equals, etc.)`
167
+ );
168
+ }
169
+ function deepEqual(a, b) {
170
+ if (a === b) return true;
171
+ if (a == null || b == null) return a === b;
172
+ if (typeof a !== typeof b) return false;
173
+ if (typeof a === "object" && typeof b === "object") {
174
+ try {
175
+ return JSON.stringify(a) === JSON.stringify(b);
176
+ } catch (e) {
177
+ if (e instanceof TypeError && (e.message.includes("circular") || e.message.includes("cyclic"))) {
178
+ console.warn(
179
+ "deepEqual: Circular reference detected in displayIf comparison, using reference equality"
180
+ );
181
+ return a === b;
182
+ }
183
+ throw e;
184
+ }
185
+ }
186
+ return a === b;
187
+ }
188
+
118
189
  // src/components/text.ts
119
190
  function renderTextElement(element, ctx, wrapper, pathKey) {
120
191
  const state = ctx.state;
@@ -2249,7 +2320,7 @@ function renderElement(element, ctx) {
2249
2320
  return renderElementFunc(element, ctx);
2250
2321
  }
2251
2322
  function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
2252
- var _a;
2323
+ var _a, _b;
2253
2324
  const containerWrap = document.createElement("div");
2254
2325
  containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
2255
2326
  containerWrap.setAttribute("data-container", pathKey);
@@ -2264,6 +2335,9 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
2264
2335
  const subCtx = {
2265
2336
  path: pathJoin(ctx.path, element.key),
2266
2337
  prefill: ((_a = ctx.prefill) == null ? void 0 : _a[element.key]) || {},
2338
+ // Sliced data for value population
2339
+ formData: (_b = ctx.formData) != null ? _b : ctx.prefill,
2340
+ // Complete root data for displayIf evaluation
2267
2341
  state: ctx.state
2268
2342
  };
2269
2343
  element.elements.forEach((child) => {
@@ -2276,7 +2350,7 @@ function renderSingleContainerElement(element, ctx, wrapper, pathKey) {
2276
2350
  wrapper.appendChild(containerWrap);
2277
2351
  }
2278
2352
  function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2279
- var _a, _b, _c;
2353
+ var _a, _b, _c, _d;
2280
2354
  const state = ctx.state;
2281
2355
  const containerWrap = document.createElement("div");
2282
2356
  containerWrap.className = "border border-gray-200 rounded-lg p-4 bg-gray-50";
@@ -2303,12 +2377,15 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2303
2377
  add.className = "px-3 py-1.5 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors";
2304
2378
  add.textContent = t("addElement", state);
2305
2379
  add.onclick = () => {
2380
+ var _a2;
2306
2381
  if (countItems() < max) {
2307
2382
  const idx = countItems();
2308
2383
  const subCtx = {
2309
2384
  state: ctx.state,
2310
2385
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
2311
- prefill: {}
2386
+ prefill: {},
2387
+ formData: (_a2 = ctx.formData) != null ? _a2 : ctx.prefill
2388
+ // Complete root data for displayIf
2312
2389
  };
2313
2390
  const item = document.createElement("div");
2314
2391
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -2350,10 +2427,13 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2350
2427
  }
2351
2428
  if (pre && Array.isArray(pre)) {
2352
2429
  pre.forEach((prefillObj, idx) => {
2430
+ var _a2;
2353
2431
  const subCtx = {
2354
2432
  state: ctx.state,
2355
2433
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
2356
- prefill: prefillObj || {}
2434
+ prefill: prefillObj || {},
2435
+ formData: (_a2 = ctx.formData) != null ? _a2 : ctx.prefill
2436
+ // Complete root data for displayIf
2357
2437
  };
2358
2438
  const item = document.createElement("div");
2359
2439
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -2384,7 +2464,9 @@ function renderMultipleContainerElement(element, ctx, wrapper, _pathKey) {
2384
2464
  const subCtx = {
2385
2465
  state: ctx.state,
2386
2466
  path: pathJoin(ctx.path, `${element.key}[${idx}]`),
2387
- prefill: {}
2467
+ prefill: {},
2468
+ formData: (_d = ctx.formData) != null ? _d : ctx.prefill
2469
+ // Complete root data for displayIf
2388
2470
  };
2389
2471
  const item = document.createElement("div");
2390
2472
  item.className = "containerItem border border-gray-300 rounded-lg p-4 bg-white";
@@ -2668,8 +2750,32 @@ if (typeof document !== "undefined") {
2668
2750
  });
2669
2751
  }
2670
2752
  function renderElement2(element, ctx) {
2753
+ var _a;
2754
+ if (element.displayIf) {
2755
+ try {
2756
+ const dataForCondition = (_a = ctx.formData) != null ? _a : ctx.prefill;
2757
+ const shouldDisplay = evaluateDisplayCondition(
2758
+ element.displayIf,
2759
+ dataForCondition
2760
+ );
2761
+ if (!shouldDisplay) {
2762
+ const hiddenWrapper = document.createElement("div");
2763
+ hiddenWrapper.className = "fb-field-wrapper-hidden";
2764
+ hiddenWrapper.style.display = "none";
2765
+ hiddenWrapper.setAttribute("data-field-key", element.key);
2766
+ hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
2767
+ return hiddenWrapper;
2768
+ }
2769
+ } catch (error) {
2770
+ console.error(
2771
+ `Error evaluating displayIf for field "${element.key}":`,
2772
+ error
2773
+ );
2774
+ }
2775
+ }
2671
2776
  const wrapper = document.createElement("div");
2672
2777
  wrapper.className = "mb-6 fb-field-wrapper";
2778
+ wrapper.setAttribute("data-field-key", element.key);
2673
2779
  const label = document.createElement("div");
2674
2780
  label.className = "flex items-center mb-2";
2675
2781
  const title = document.createElement("label");
@@ -3162,6 +3268,7 @@ var FormBuilderInstance = class {
3162
3268
  }
3163
3269
  this.state.debounceTimer = setTimeout(() => {
3164
3270
  const formData = this.validateForm(true);
3271
+ this.reevaluateConditionalFields();
3165
3272
  if (this.state.config.onChange) {
3166
3273
  this.state.config.onChange(formData);
3167
3274
  }
@@ -3421,6 +3528,8 @@ var FormBuilderInstance = class {
3421
3528
  const block = renderElement2(element, {
3422
3529
  path: "",
3423
3530
  prefill: prefill || {},
3531
+ formData: prefill || {},
3532
+ // Pass complete root data for displayIf evaluation
3424
3533
  state: this.state,
3425
3534
  instance: this
3426
3535
  });
@@ -3465,6 +3574,19 @@ var FormBuilderInstance = class {
3465
3574
  };
3466
3575
  setValidateElement(validateElement2);
3467
3576
  this.state.schema.elements.forEach((element) => {
3577
+ if (element.displayIf) {
3578
+ try {
3579
+ const shouldDisplay = evaluateDisplayCondition(element.displayIf, data);
3580
+ if (!shouldDisplay) {
3581
+ return;
3582
+ }
3583
+ } catch (error) {
3584
+ console.error(
3585
+ `Error evaluating displayIf for field "${element.key}" during validation:`,
3586
+ error
3587
+ );
3588
+ }
3589
+ }
3468
3590
  if (element.hidden) {
3469
3591
  data[element.key] = element.default !== void 0 ? element.default : null;
3470
3592
  } else {
@@ -3621,6 +3743,71 @@ var FormBuilderInstance = class {
3621
3743
  );
3622
3744
  }
3623
3745
  }
3746
+ /**
3747
+ * Re-evaluate all conditional fields (displayIf) based on current form data
3748
+ * This is called automatically when form data changes (via onChange events)
3749
+ */
3750
+ reevaluateConditionalFields() {
3751
+ if (!this.state.schema || !this.state.formRoot) return;
3752
+ const formData = this.validateForm(true).data;
3753
+ const checkElements = (elements, currentPath) => {
3754
+ elements.forEach((element) => {
3755
+ const fullPath = currentPath ? `${currentPath}.${element.key}` : element.key;
3756
+ if (element.displayIf) {
3757
+ const fieldWrappers = this.state.formRoot.querySelectorAll(
3758
+ `[data-field-key="${element.key}"]`
3759
+ );
3760
+ fieldWrappers.forEach((wrapper) => {
3761
+ var _a, _b;
3762
+ try {
3763
+ const shouldDisplay = evaluateDisplayCondition(
3764
+ element.displayIf,
3765
+ formData
3766
+ // Use complete formData for condition evaluation
3767
+ );
3768
+ const isCurrentlyHidden = wrapper.getAttribute("data-conditionally-hidden") === "true";
3769
+ if (shouldDisplay && isCurrentlyHidden) {
3770
+ const newWrapper = renderElement2(element, {
3771
+ path: fullPath,
3772
+ // Use accumulated path
3773
+ prefill: formData,
3774
+ // Use complete formData for root-level elements
3775
+ formData,
3776
+ // Pass complete formData for displayIf evaluation
3777
+ state: this.state,
3778
+ instance: this
3779
+ });
3780
+ (_a = wrapper.parentNode) == null ? void 0 : _a.replaceChild(newWrapper, wrapper);
3781
+ } else if (!shouldDisplay && !isCurrentlyHidden) {
3782
+ const hiddenWrapper = document.createElement("div");
3783
+ hiddenWrapper.className = "fb-field-wrapper-hidden";
3784
+ hiddenWrapper.style.display = "none";
3785
+ hiddenWrapper.setAttribute("data-field-key", element.key);
3786
+ hiddenWrapper.setAttribute("data-conditionally-hidden", "true");
3787
+ (_b = wrapper.parentNode) == null ? void 0 : _b.replaceChild(hiddenWrapper, wrapper);
3788
+ }
3789
+ } catch (error) {
3790
+ console.error(
3791
+ `Error re-evaluating displayIf for field "${element.key}":`,
3792
+ error
3793
+ );
3794
+ }
3795
+ });
3796
+ }
3797
+ if ((element.type === "container" || element.type === "group") && "elements" in element && element.elements) {
3798
+ const containerData = formData == null ? void 0 : formData[element.key];
3799
+ if (Array.isArray(containerData)) {
3800
+ containerData.forEach((_, index) => {
3801
+ checkElements(element.elements, `${fullPath}[${index}]`);
3802
+ });
3803
+ } else {
3804
+ checkElements(element.elements, fullPath);
3805
+ }
3806
+ }
3807
+ });
3808
+ };
3809
+ checkElements(this.state.schema.elements, "");
3810
+ }
3624
3811
  /**
3625
3812
  * Destroy instance and clean up resources
3626
3813
  */