form-builder-pro 1.1.2 → 1.1.4

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/index.css CHANGED
@@ -804,6 +804,25 @@ body {
804
804
  border-top-right-radius: 0;
805
805
  }
806
806
  }
807
+ /* ===== INPUT FIELD STABILITY & PERFORMANCE ===== */
808
+ /* Prevent flicker and layout shifts during typing */
809
+ .lookup-input-field,
810
+ .form-builder-config-wrapper input[type="text"],
811
+ .form-builder-config-wrapper input[type="number"],
812
+ .form-builder-config-wrapper textarea {
813
+ will-change: contents;
814
+ /* Use GPU acceleration for smooth rendering */
815
+ transform: translateZ(0);
816
+ /* Prevent layout recalculation on input change */
817
+ contain: layout style;
818
+ }
819
+ /* Ensure inputs have fixed dimensions to prevent jumps */
820
+ .form-builder-config-wrapper input,
821
+ .form-builder-config-wrapper select,
822
+ .form-builder-config-wrapper textarea {
823
+ min-height: 38px;
824
+ /* Fixed height prevents layout shift */
825
+ }
807
826
  .sr-only {
808
827
  position: absolute;
809
828
  width: 1px;
package/dist/index.d.mts CHANGED
@@ -68,7 +68,11 @@ interface FormField {
68
68
  customOptionsEnabled?: boolean;
69
69
  multiselect?: boolean;
70
70
  multiSelect?: boolean;
71
- optionSource?: 'STATIC' | 'MASTER';
71
+ optionSource?: 'STATIC' | 'MASTER' | 'LOOKUP';
72
+ lookupSourceType?: 'MODULE' | 'MASTER_TYPE';
73
+ lookupSource?: string;
74
+ lookupValueField?: string;
75
+ lookupLabelField?: string;
72
76
  isd?: ISDConfig;
73
77
  }
74
78
  /**
@@ -226,6 +230,7 @@ interface FormBuilderOptions {
226
230
  value: string;
227
231
  }[];
228
232
  };
233
+ moduleList?: string[];
229
234
  onGroupSelectionChange?: (event: {
230
235
  fieldId: string;
231
236
  groupEnumName: string;
@@ -239,6 +244,8 @@ declare class FormBuilder {
239
244
  private container;
240
245
  private unsubscribe;
241
246
  private options;
247
+ private isInputUpdate;
248
+ private lastRenderedSchemaHash;
242
249
  constructor(container: HTMLElement, options?: FormBuilderOptions);
243
250
  loadForm(json: FormSchema): void;
244
251
  cloneForm(json: FormSchema): void;
package/dist/index.d.ts CHANGED
@@ -68,7 +68,11 @@ interface FormField {
68
68
  customOptionsEnabled?: boolean;
69
69
  multiselect?: boolean;
70
70
  multiSelect?: boolean;
71
- optionSource?: 'STATIC' | 'MASTER';
71
+ optionSource?: 'STATIC' | 'MASTER' | 'LOOKUP';
72
+ lookupSourceType?: 'MODULE' | 'MASTER_TYPE';
73
+ lookupSource?: string;
74
+ lookupValueField?: string;
75
+ lookupLabelField?: string;
72
76
  isd?: ISDConfig;
73
77
  }
74
78
  /**
@@ -226,6 +230,7 @@ interface FormBuilderOptions {
226
230
  value: string;
227
231
  }[];
228
232
  };
233
+ moduleList?: string[];
229
234
  onGroupSelectionChange?: (event: {
230
235
  fieldId: string;
231
236
  groupEnumName: string;
@@ -239,6 +244,8 @@ declare class FormBuilder {
239
244
  private container;
240
245
  private unsubscribe;
241
246
  private options;
247
+ private isInputUpdate;
248
+ private lastRenderedSchemaHash;
242
249
  constructor(container: HTMLElement, options?: FormBuilderOptions);
243
250
  loadForm(json: FormSchema): void;
244
251
  cloneForm(json: FormSchema): void;
package/dist/index.js CHANGED
@@ -4404,6 +4404,14 @@ function transformField(field) {
4404
4404
  }
4405
4405
  }
4406
4406
  }
4407
+ if (field.lookupSourceType !== void 0)
4408
+ transformed.lookupSourceType = field.lookupSourceType;
4409
+ if (field.lookupSource !== void 0)
4410
+ transformed.lookupSource = field.lookupSource;
4411
+ if (field.lookupValueField !== void 0)
4412
+ transformed.lookupValueField = field.lookupValueField;
4413
+ if (field.lookupLabelField !== void 0)
4414
+ transformed.lookupLabelField = field.lookupLabelField;
4407
4415
  if (field.placeholder !== void 0)
4408
4416
  transformed.placeholder = field.placeholder;
4409
4417
  if (field.description !== void 0)
@@ -4548,6 +4556,14 @@ function fieldToPayload(field) {
4548
4556
  payload.groupName = field.groupName;
4549
4557
  if (field.masterTypeName !== void 0)
4550
4558
  payload.masterTypeName = field.masterTypeName;
4559
+ if (field.lookupSourceType !== void 0)
4560
+ payload.lookupSourceType = field.lookupSourceType;
4561
+ if (field.lookupSource !== void 0)
4562
+ payload.lookupSource = field.lookupSource;
4563
+ if (field.lookupValueField !== void 0)
4564
+ payload.lookupValueField = field.lookupValueField;
4565
+ if (field.lookupLabelField !== void 0)
4566
+ payload.lookupLabelField = field.lookupLabelField;
4551
4567
  if (field.isd !== void 0)
4552
4568
  payload.isd = field.isd;
4553
4569
  if ((field.type === "select" || field.type === "radio" || field.type === "checkbox") && field.options) {
@@ -8528,10 +8544,14 @@ var SectionList = class {
8528
8544
  // src/builder/FormBuilder.ts
8529
8545
  var advancedCssPanelState = /* @__PURE__ */ new Map();
8530
8546
  var FormBuilder = class {
8547
+ // Cache to detect meaningful changes
8531
8548
  constructor(container, options = {}) {
8532
8549
  __publicField(this, "container");
8533
8550
  __publicField(this, "unsubscribe");
8534
8551
  __publicField(this, "options");
8552
+ __publicField(this, "isInputUpdate", false);
8553
+ // Track if update is from text input
8554
+ __publicField(this, "lastRenderedSchemaHash", "");
8535
8555
  __publicField(this, "activeTab", "fields");
8536
8556
  if (!container) {
8537
8557
  throw new Error("Builder container not found. Please ensure the container element exists before initializing FormBuilder.");
@@ -8552,8 +8572,28 @@ var FormBuilder = class {
8552
8572
  }
8553
8573
  });
8554
8574
  if (extractedSections.length > 0) {
8575
+ const sectionMap = /* @__PURE__ */ new Map();
8576
+ extractedSections.forEach((section) => {
8577
+ const existingSection = sectionMap.get(section.title);
8578
+ if (existingSection) {
8579
+ const fieldIds = new Set(existingSection.fields.map((f) => f.id));
8580
+ section.fields.forEach((field) => {
8581
+ if (!fieldIds.has(field.id)) {
8582
+ existingSection.fields.push(field);
8583
+ fieldIds.add(field.id);
8584
+ }
8585
+ });
8586
+ } else {
8587
+ sectionMap.set(section.title, {
8588
+ ...section,
8589
+ fields: [...section.fields]
8590
+ });
8591
+ }
8592
+ });
8593
+ const deduplicatedSections = Array.from(sectionMap.values());
8594
+ console.log(`[FormBuilder] Loaded ${options.formTemplates.length} form templates, extracted ${extractedSections.length} sections, deduplicated to ${deduplicatedSections.length} unique templates`);
8555
8595
  const existingTemplates = options.reusableSections || [];
8556
- formStore.getState().setTemplates([...existingTemplates, ...extractedSections]);
8596
+ formStore.getState().setTemplates([...existingTemplates, ...deduplicatedSections]);
8557
8597
  }
8558
8598
  }
8559
8599
  if (options.formJson) {
@@ -8635,14 +8675,56 @@ var FormBuilder = class {
8635
8675
  }
8636
8676
  });
8637
8677
  if (extractedSections.length > 0) {
8678
+ const sectionMap = /* @__PURE__ */ new Map();
8679
+ extractedSections.forEach((section) => {
8680
+ const existingSection = sectionMap.get(section.title);
8681
+ if (existingSection) {
8682
+ const fieldIds = new Set(existingSection.fields.map((f) => f.id));
8683
+ section.fields.forEach((field) => {
8684
+ if (!fieldIds.has(field.id)) {
8685
+ existingSection.fields.push(field);
8686
+ fieldIds.add(field.id);
8687
+ }
8688
+ });
8689
+ } else {
8690
+ sectionMap.set(section.title, {
8691
+ ...section,
8692
+ fields: [...section.fields]
8693
+ });
8694
+ }
8695
+ });
8696
+ const deduplicatedSections = Array.from(sectionMap.values());
8697
+ console.log(`[FormBuilder] loadFormTemplates: extracted ${extractedSections.length} sections, deduplicated to ${deduplicatedSections.length} unique templates`);
8638
8698
  const currentTemplates = formStore.getState().templates;
8639
- formStore.getState().setTemplates([...currentTemplates, ...extractedSections]);
8699
+ formStore.getState().setTemplates([...currentTemplates, ...deduplicatedSections]);
8640
8700
  this.render();
8641
8701
  }
8642
8702
  }
8643
8703
  setupSubscriptions() {
8644
8704
  this.unsubscribe = formStore.subscribe(() => {
8645
- this.render();
8705
+ if (this.isInputUpdate) {
8706
+ this.isInputUpdate = false;
8707
+ return;
8708
+ }
8709
+ const state = formStore.getState();
8710
+ const schemaHash = JSON.stringify({
8711
+ sections: state.schema.sections.map((s) => ({
8712
+ id: s.id,
8713
+ title: s.title,
8714
+ fields: s.fields.map((f) => ({
8715
+ id: f.id,
8716
+ type: f.type,
8717
+ label: f.label
8718
+ // Exclude frequently changing text properties from hash
8719
+ // to prevent re-renders on typing
8720
+ }))
8721
+ })),
8722
+ selectedField: state.selectedFieldId
8723
+ });
8724
+ if (schemaHash !== this.lastRenderedSchemaHash) {
8725
+ this.lastRenderedSchemaHash = schemaHash;
8726
+ this.render();
8727
+ }
8646
8728
  });
8647
8729
  }
8648
8730
  destroy() {
@@ -8854,7 +8936,7 @@ var FormBuilder = class {
8854
8936
  const previewBtn = createElement("button", {
8855
8937
  className: `flex items-center px-3 py-2 text-sm bg-[#3b497e] text-white font-medium rounded-md transition-colors ${state.isPreviewMode ? "bg-[#019FA2] text-blue-700 dark:bg-blue-900 dark:text-blue-200" : "text-gray-700 dark:text-gray-200 "}`,
8856
8938
  onclick: () => formStore.getState().togglePreview()
8857
- }, [getIcon("Eye", 16), createElement("span", { className: "", text: state.isPreviewMode ? "" : "" })]);
8939
+ }, [getIcon(state.isPreviewMode ? "X" : "Eye", 16), createElement("span", { className: "", text: state.isPreviewMode ? "" : "" })]);
8858
8940
  const saveBtn = createElement("button", {
8859
8941
  className: "flex items-center px-3 py-2 text-sm font-medium text-white bg-[#019FA2] rounded-md shadow-sm transition-colors",
8860
8942
  onclick: () => {
@@ -8984,7 +9066,10 @@ var FormBuilder = class {
8984
9066
  value: state.schema.formName,
8985
9067
  placeholder: "formName (e.g., contactForm)",
8986
9068
  "data-focus-id": "form-name",
8987
- oninput: (e) => formStore.getState().setSchema({ ...state.schema, formName: e.target.value })
9069
+ oninput: (e) => {
9070
+ this.isInputUpdate = true;
9071
+ formStore.getState().setSchema({ ...state.schema, formName: e.target.value });
9072
+ }
8988
9073
  });
8989
9074
  inner.appendChild(formNameInput);
8990
9075
  const sectionList = new SectionList(state.schema, state.selectedFieldId);
@@ -9018,7 +9103,10 @@ var FormBuilder = class {
9018
9103
  className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-transparent",
9019
9104
  value: selectedField.label,
9020
9105
  "data-focus-id": `field-label-${selectedField.id}`,
9021
- oninput: (e) => formStore.getState().updateField(selectedField.id, { label: e.target.value })
9106
+ oninput: (e) => {
9107
+ this.isInputUpdate = true;
9108
+ formStore.getState().updateField(selectedField.id, { label: e.target.value });
9109
+ }
9022
9110
  }));
9023
9111
  body.appendChild(labelGroup);
9024
9112
  const placeholderGroup = createElement("div");
@@ -9027,7 +9115,10 @@ var FormBuilder = class {
9027
9115
  className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-transparent",
9028
9116
  value: selectedField.placeholder || "",
9029
9117
  "data-focus-id": `field-placeholder-${selectedField.id}`,
9030
- oninput: (e) => formStore.getState().updateField(selectedField.id, { placeholder: e.target.value })
9118
+ oninput: (e) => {
9119
+ this.isInputUpdate = true;
9120
+ formStore.getState().updateField(selectedField.id, { placeholder: e.target.value });
9121
+ }
9031
9122
  }));
9032
9123
  body.appendChild(placeholderGroup);
9033
9124
  const layoutGroup = createElement("div", { className: "layout-span-group" });
@@ -9258,6 +9349,10 @@ var FormBuilder = class {
9258
9349
  const updates = { optionSource: source };
9259
9350
  if (source === "MASTER" && !selectedField.masterTypeName && !selectedField.groupName) ; else if (source === "STATIC") {
9260
9351
  updates.customOptionsEnabled = true;
9352
+ } else if (source === "LOOKUP") {
9353
+ if (!selectedField.lookupSourceType) {
9354
+ updates.lookupSourceType = "MODULE";
9355
+ }
9261
9356
  }
9262
9357
  formStore.getState().updateField(selectedField.id, updates);
9263
9358
  this.render();
@@ -9265,8 +9360,147 @@ var FormBuilder = class {
9265
9360
  });
9266
9361
  optionSourceSelect.appendChild(createElement("option", { value: "STATIC", text: "STATIC (Custom Options)", selected: (selectedField.optionSource || "STATIC") === "STATIC" }));
9267
9362
  optionSourceSelect.appendChild(createElement("option", { value: "MASTER", text: "MASTER (From Master Types)", selected: selectedField.optionSource === "MASTER" }));
9363
+ if (selectedField.type === "select") {
9364
+ optionSourceSelect.appendChild(createElement("option", { value: "LOOKUP", text: "Lookup (Entity Fields)", selected: selectedField.optionSource === "LOOKUP" }));
9365
+ }
9268
9366
  optionSourceGroup.appendChild(optionSourceSelect);
9269
9367
  body.appendChild(optionSourceGroup);
9368
+ if (selectedField.type === "select" && selectedField.optionSource === "LOOKUP") {
9369
+ const lookupSourceTypeGroup = createElement("div", { className: "mb-4" });
9370
+ lookupSourceTypeGroup.appendChild(createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", text: "Lookup Source Type" }));
9371
+ const lookupSourceTypeSelect = createElement("select", {
9372
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-transparent",
9373
+ value: selectedField.lookupSourceType || "MODULE",
9374
+ onchange: (e) => {
9375
+ const lookupSourceType = e.target.value;
9376
+ const updates = { lookupSourceType };
9377
+ if (lookupSourceType !== selectedField.lookupSourceType) {
9378
+ updates.lookupSource = void 0;
9379
+ }
9380
+ formStore.getState().updateField(selectedField.id, updates);
9381
+ this.render();
9382
+ }
9383
+ });
9384
+ lookupSourceTypeSelect.appendChild(createElement("option", { value: "MODULE", text: "Module", selected: (selectedField.lookupSourceType || "MODULE") === "MODULE" }));
9385
+ lookupSourceTypeSelect.appendChild(createElement("option", { value: "MASTER_TYPE", text: "Master Type", selected: selectedField.lookupSourceType === "MASTER_TYPE" }));
9386
+ lookupSourceTypeGroup.appendChild(lookupSourceTypeSelect);
9387
+ body.appendChild(lookupSourceTypeGroup);
9388
+ if (selectedField.lookupSourceType === "MODULE") {
9389
+ const moduleList = this.options.moduleList || [];
9390
+ const lookupSourceGroup = createElement("div", { className: "mb-4" });
9391
+ lookupSourceGroup.appendChild(createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", text: "Lookup Source" }));
9392
+ const lookupSourceSelect = createElement("select", {
9393
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-transparent",
9394
+ value: selectedField.lookupSource || "",
9395
+ onchange: (e) => {
9396
+ const lookupSource = e.target.value;
9397
+ formStore.getState().updateField(selectedField.id, { lookupSource: lookupSource || void 0 });
9398
+ }
9399
+ });
9400
+ lookupSourceSelect.appendChild(createElement("option", { value: "", text: "Select Module", selected: !selectedField.lookupSource }));
9401
+ moduleList.forEach((module) => {
9402
+ lookupSourceSelect.appendChild(createElement("option", {
9403
+ value: module,
9404
+ text: module,
9405
+ selected: selectedField.lookupSource === module
9406
+ }));
9407
+ });
9408
+ lookupSourceGroup.appendChild(lookupSourceSelect);
9409
+ body.appendChild(lookupSourceGroup);
9410
+ } else if (selectedField.lookupSourceType === "MASTER_TYPE") {
9411
+ const masterTypes = formStore.getState().masterTypes;
9412
+ const activeMasterTypes = masterTypes.filter((mt) => mt.active === true);
9413
+ const lookupSourceGroup = createElement("div", { className: "mb-4" });
9414
+ lookupSourceGroup.appendChild(createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", text: "Lookup Source" }));
9415
+ const lookupSourceSelect = createElement("select", {
9416
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-transparent",
9417
+ value: selectedField.lookupSource || "",
9418
+ onchange: (e) => {
9419
+ const lookupSource = e.target.value;
9420
+ formStore.getState().updateField(selectedField.id, { lookupSource: lookupSource || void 0 });
9421
+ }
9422
+ });
9423
+ lookupSourceSelect.appendChild(createElement("option", { value: "", text: "Select Master Type", selected: !selectedField.lookupSource }));
9424
+ activeMasterTypes.forEach((mt) => {
9425
+ const optionValue = mt.enumName || mt.id || mt.name;
9426
+ lookupSourceSelect.appendChild(createElement("option", {
9427
+ value: optionValue,
9428
+ text: mt.displayName || mt.name,
9429
+ selected: selectedField.lookupSource === optionValue
9430
+ }));
9431
+ });
9432
+ lookupSourceGroup.appendChild(lookupSourceSelect);
9433
+ body.appendChild(lookupSourceGroup);
9434
+ }
9435
+ const lookupValueFieldGroup = createElement("div", { className: "mb-4" });
9436
+ lookupValueFieldGroup.appendChild(createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", text: "Lookup Value Field" }));
9437
+ const lookupValueFieldId = `field-lookup-value-${selectedField.id}`;
9438
+ const lookupValueFieldInput = createElement("input", {
9439
+ type: "text",
9440
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-transparent lookup-input-field",
9441
+ value: selectedField.lookupValueField || "",
9442
+ placeholder: "Enter value field name",
9443
+ "data-focus-id": lookupValueFieldId,
9444
+ oninput: (e) => {
9445
+ this.isInputUpdate = true;
9446
+ const lookupValueField = e.target.value;
9447
+ formStore.getState().updateField(selectedField.id, { lookupValueField: lookupValueField || void 0 });
9448
+ },
9449
+ onblur: (e) => {
9450
+ const lookupValueField = e.target.value;
9451
+ formStore.getState().updateField(selectedField.id, { lookupValueField: lookupValueField || void 0 });
9452
+ }
9453
+ });
9454
+ lookupValueFieldGroup.appendChild(lookupValueFieldInput);
9455
+ body.appendChild(lookupValueFieldGroup);
9456
+ const lookupLabelFieldGroup = createElement("div", { className: "mb-4" });
9457
+ lookupLabelFieldGroup.appendChild(createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", text: "Lookup Label Field" }));
9458
+ const lookupLabelFieldId = `field-lookup-label-${selectedField.id}`;
9459
+ const lookupLabelFieldInput = createElement("input", {
9460
+ type: "text",
9461
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-transparent lookup-input-field",
9462
+ value: selectedField.lookupLabelField || "",
9463
+ placeholder: "Enter label field name",
9464
+ "data-focus-id": lookupLabelFieldId,
9465
+ oninput: (e) => {
9466
+ this.isInputUpdate = true;
9467
+ const lookupLabelField = e.target.value;
9468
+ formStore.getState().updateField(selectedField.id, { lookupLabelField: lookupLabelField || void 0 });
9469
+ },
9470
+ onblur: (e) => {
9471
+ const lookupLabelField = e.target.value;
9472
+ formStore.getState().updateField(selectedField.id, { lookupLabelField: lookupLabelField || void 0 });
9473
+ }
9474
+ });
9475
+ lookupLabelFieldGroup.appendChild(lookupLabelFieldInput);
9476
+ body.appendChild(lookupLabelFieldGroup);
9477
+ const visibilityGroup = createElement("div", { className: "flex items-center justify-between mb-4" });
9478
+ visibilityGroup.appendChild(createElement("label", { className: "text-sm text-gray-700 dark:text-gray-300", text: "Visibility" }));
9479
+ visibilityGroup.appendChild(createElement("input", {
9480
+ type: "checkbox",
9481
+ className: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500",
9482
+ checked: selectedField.visible !== false,
9483
+ // Default to true if not set
9484
+ onchange: (e) => {
9485
+ const visible = e.target.checked;
9486
+ formStore.getState().updateField(selectedField.id, { visible });
9487
+ }
9488
+ }));
9489
+ body.appendChild(visibilityGroup);
9490
+ const enabledGroup2 = createElement("div", { className: "flex items-center justify-between mb-4" });
9491
+ enabledGroup2.appendChild(createElement("label", { className: "text-sm text-gray-700 dark:text-gray-300", text: "Enabled" }));
9492
+ enabledGroup2.appendChild(createElement("input", {
9493
+ type: "checkbox",
9494
+ className: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500",
9495
+ checked: selectedField.enabled !== false,
9496
+ // Default to true if not set
9497
+ onchange: (e) => {
9498
+ const enabled = e.target.checked;
9499
+ formStore.getState().updateField(selectedField.id, { enabled });
9500
+ }
9501
+ }));
9502
+ body.appendChild(enabledGroup2);
9503
+ }
9270
9504
  }
9271
9505
  if (selectedField.type === "select") {
9272
9506
  const multiSelectGroup = createElement("div", { className: "flex items-center justify-between mb-4" });