form-builder-pro 1.3.7 → 1.3.9

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.js CHANGED
@@ -4076,39 +4076,12 @@ var FormSchemaValidation = external_exports.object({
4076
4076
  }))
4077
4077
  });
4078
4078
 
4079
- // node_modules/.pnpm/zustand@4.5.7_react@19.2.4/node_modules/zustand/esm/vanilla.mjs
4080
- var createStoreImpl = (createState) => {
4081
- let state;
4082
- const listeners = /* @__PURE__ */ new Set();
4083
- const setState = (partial, replace) => {
4084
- const nextState = typeof partial === "function" ? partial(state) : partial;
4085
- if (!Object.is(nextState, state)) {
4086
- const previousState = state;
4087
- state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
4088
- listeners.forEach((listener) => listener(state, previousState));
4089
- }
4090
- };
4091
- const getState = () => state;
4092
- const getInitialState = () => initialState;
4093
- const subscribe = (listener) => {
4094
- listeners.add(listener);
4095
- return () => listeners.delete(listener);
4096
- };
4097
- const destroy2 = () => {
4098
- if ((undefined ? undefined.MODE : void 0) !== "production") {
4099
- console.warn(
4100
- "[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."
4101
- );
4102
- }
4103
- listeners.clear();
4104
- };
4105
- const api = { setState, getState, getInitialState, subscribe, destroy: destroy2 };
4106
- const initialState = state = createState(setState, getState, api);
4107
- return api;
4108
- };
4109
- var createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
4110
-
4111
4079
  // src/core/constants.ts
4080
+ var LOOKUP_SOURCE_TYPE_OPTIONS = [
4081
+ { value: "MODULE", label: "Module" },
4082
+ { value: "MASTER_TYPE", label: "Master Type" },
4083
+ { value: "SETTINGS", label: "Settings Entity" }
4084
+ ];
4112
4085
  var generateId = () => Math.random().toString(36).substring(2, 9);
4113
4086
  var FIELD_TYPES = [
4114
4087
  { type: "text", label: "Text Input", icon: "Type" },
@@ -4317,6 +4290,38 @@ var REGEX_PRESETS = [
4317
4290
  }
4318
4291
  ];
4319
4292
 
4293
+ // node_modules/.pnpm/zustand@4.5.7_react@19.2.4/node_modules/zustand/esm/vanilla.mjs
4294
+ var createStoreImpl = (createState) => {
4295
+ let state;
4296
+ const listeners = /* @__PURE__ */ new Set();
4297
+ const setState = (partial, replace) => {
4298
+ const nextState = typeof partial === "function" ? partial(state) : partial;
4299
+ if (!Object.is(nextState, state)) {
4300
+ const previousState = state;
4301
+ state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
4302
+ listeners.forEach((listener) => listener(state, previousState));
4303
+ }
4304
+ };
4305
+ const getState = () => state;
4306
+ const getInitialState = () => initialState;
4307
+ const subscribe = (listener) => {
4308
+ listeners.add(listener);
4309
+ return () => listeners.delete(listener);
4310
+ };
4311
+ const destroy2 = () => {
4312
+ if ((undefined ? undefined.MODE : void 0) !== "production") {
4313
+ console.warn(
4314
+ "[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."
4315
+ );
4316
+ }
4317
+ listeners.clear();
4318
+ };
4319
+ const api = { setState, getState, getInitialState, subscribe, destroy: destroy2 };
4320
+ const initialState = state = createState(setState, getState, api);
4321
+ return api;
4322
+ };
4323
+ var createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
4324
+
4320
4325
  // src/utils/clone.ts
4321
4326
  var cloneForm = (schema) => {
4322
4327
  return {
@@ -4696,6 +4701,8 @@ function transformField(field) {
4696
4701
  transformed.enabled = field.enabled;
4697
4702
  if (field.visible !== void 0)
4698
4703
  transformed.visible = field.visible;
4704
+ if (field.isUnique !== void 0)
4705
+ transformed.isUnique = field.isUnique;
4699
4706
  if (field.isd !== void 0)
4700
4707
  transformed.isd = field.isd;
4701
4708
  if (field.imageUrl !== void 0)
@@ -4766,7 +4773,30 @@ function transformField(field) {
4766
4773
  var cleanFormSchema = (schema) => {
4767
4774
  const cleanField = transformField;
4768
4775
  let sections = [];
4769
- if (schema.fields && Array.isArray(schema.fields) && schema.fields.length > 0) {
4776
+ if (schema.sections && Array.isArray(schema.sections)) {
4777
+ sections = schema.sections;
4778
+ } else if (schema.groups && Array.isArray(schema.groups)) {
4779
+ const rootFields = schema.fields && Array.isArray(schema.fields) ? schema.fields : [];
4780
+ const rootById = new Map(rootFields.map((f) => [String(f.id), f]));
4781
+ sections = schema.groups.map((group, index2) => {
4782
+ let fieldList = group.fields;
4783
+ if (Array.isArray(group.fieldIds) && group.fieldIds.length > 0 && rootById.size > 0) {
4784
+ fieldList = group.fieldIds.map((id) => rootById.get(String(id))).filter(Boolean);
4785
+ }
4786
+ if (!fieldList)
4787
+ fieldList = [];
4788
+ return {
4789
+ ...group,
4790
+ id: group.id || `section-${index2}`,
4791
+ title: group.title || group.name || `Section ${index2 + 1}`,
4792
+ name: group.name ?? group.title,
4793
+ fields: fieldList,
4794
+ order: group.order !== void 0 ? group.order : index2,
4795
+ isExpanded: group.isExpanded !== void 0 ? group.isExpanded : true,
4796
+ expanded: group.expanded !== void 0 ? group.expanded : group.isExpanded !== false
4797
+ };
4798
+ });
4799
+ } else if (schema.fields && Array.isArray(schema.fields) && schema.fields.length > 0) {
4770
4800
  sections = [{
4771
4801
  id: schema.id ? `section-${schema.id}` : "section-1",
4772
4802
  title: schema.formName || schema.title || "Form Fields",
@@ -4774,16 +4804,6 @@ var cleanFormSchema = (schema) => {
4774
4804
  order: 0,
4775
4805
  isExpanded: true
4776
4806
  }];
4777
- } else if (schema.sections && Array.isArray(schema.sections)) {
4778
- sections = schema.sections;
4779
- } else if (schema.groups && Array.isArray(schema.groups)) {
4780
- sections = schema.groups.map((group, index2) => ({
4781
- id: group.id || `section-${index2}`,
4782
- title: group.title || group.name || `Section ${index2 + 1}`,
4783
- fields: group.fields || [],
4784
- order: group.order !== void 0 ? group.order : index2,
4785
- isExpanded: group.isExpanded !== void 0 ? group.isExpanded : true
4786
- }));
4787
4807
  }
4788
4808
  return {
4789
4809
  id: schema.id,
@@ -4791,26 +4811,37 @@ var cleanFormSchema = (schema) => {
4791
4811
  formName: schema.formName || schema.formId || schema.id,
4792
4812
  layout: schema.layout || { type: "grid", columns: 12, gap: "16px" },
4793
4813
  // Preserve form-level layout or set default
4794
- sections: sections.map((section, sectionIndex) => ({
4795
- id: section.id || `section-${sectionIndex}`,
4796
- title: section.title || `Section ${sectionIndex + 1}`,
4797
- fields: (section.fields || []).map((field, fieldIndex) => {
4798
- const cleaned = cleanField(field);
4799
- if (cleaned.order === void 0) {
4800
- cleaned.order = fieldIndex;
4801
- }
4802
- return cleaned;
4803
- }),
4804
- isExpanded: section.isExpanded !== void 0 ? section.isExpanded : true,
4805
- columns: section.columns,
4806
- // Legacy - prefer layout.columns
4807
- order: section.order !== void 0 ? section.order : sectionIndex,
4808
- // Ensure section order is set
4809
- layout: section.layout || { type: "grid", columns: section.columns || 12, gap: "16px" },
4810
- // Preserve section layout or set default
4811
- css: section.css
4812
- // Preserve section CSS
4813
- }))
4814
+ sections: sections.map((section, sectionIndex) => {
4815
+ const order = section.order !== void 0 ? section.order : sectionIndex;
4816
+ return {
4817
+ id: section.id || `section-${sectionIndex}`,
4818
+ title: section.title || `Section ${sectionIndex + 1}`,
4819
+ name: section.name ?? section.title,
4820
+ description: section.description ?? null,
4821
+ fields: (section.fields || []).map((field, fieldIndex) => {
4822
+ const cleaned = cleanField(field);
4823
+ if (cleaned.order === void 0) {
4824
+ cleaned.order = fieldIndex;
4825
+ }
4826
+ return cleaned;
4827
+ }),
4828
+ isExpanded: section.isExpanded !== void 0 ? section.isExpanded : true,
4829
+ expanded: section.expanded !== void 0 ? section.expanded : section.isExpanded !== false,
4830
+ columns: section.columns,
4831
+ order,
4832
+ layout: section.layout || { type: "grid", columns: section.columns || 12, gap: "16px" },
4833
+ css: section.css,
4834
+ position: section.position ?? { row: order, column: 0, width: 12, order },
4835
+ visible: section.visible !== false,
4836
+ collapsible: section.collapsible !== false,
4837
+ parentGroupId: section.parentGroupId ?? null,
4838
+ repeatable: section.repeatable === true,
4839
+ dataKey: section.dataKey ?? null,
4840
+ addButtonLabel: section.addButtonLabel ?? null,
4841
+ minInstances: section.minInstances ?? null,
4842
+ maxInstances: section.maxInstances ?? null
4843
+ };
4844
+ })
4814
4845
  };
4815
4846
  };
4816
4847
  function convertValidationArrayToObject(validation) {
@@ -4855,7 +4886,7 @@ function convertWidthToSpan(width, totalColumns = 12) {
4855
4886
  const widthNum = parseWidth(width);
4856
4887
  return Math.max(1, Math.min(12, Math.round(widthNum / 100 * totalColumns)));
4857
4888
  }
4858
- function fieldToPayload(field) {
4889
+ function fieldToPayload(field, opts) {
4859
4890
  let outputType = field.type;
4860
4891
  let outputValidations = field.validations ? { ...field.validations } : void 0;
4861
4892
  if (field.type === "text" && isEmailLikeField(field)) {
@@ -4874,6 +4905,9 @@ function fieldToPayload(field) {
4874
4905
  // Model key for binding (API / host app)
4875
4906
  order: field.order !== void 0 ? field.order : 0
4876
4907
  };
4908
+ if (opts?.groupId) {
4909
+ payload.groupId = opts.groupId;
4910
+ }
4877
4911
  if (field.layout?.span !== void 0) {
4878
4912
  payload.layout = {
4879
4913
  row: field.layout.row ?? 0,
@@ -4931,6 +4965,8 @@ function fieldToPayload(field) {
4931
4965
  payload.enabled = field.enabled;
4932
4966
  if (field.visible !== void 0)
4933
4967
  payload.visible = field.visible;
4968
+ if (field.isUnique !== void 0)
4969
+ payload.isUnique = field.isUnique;
4934
4970
  if (field.css !== void 0)
4935
4971
  payload.css = field.css;
4936
4972
  if (field.optionSource !== void 0)
@@ -5027,20 +5063,48 @@ function fieldToPayload(field) {
5027
5063
  }
5028
5064
  return payload;
5029
5065
  }
5066
+ function sectionToGroupPayload(section, index2) {
5067
+ const order = section.order !== void 0 ? section.order : index2;
5068
+ const pos = section.position ?? { row: order, column: 0, width: 12, order };
5069
+ const width = Math.max(1, Math.min(12, pos.width ?? 12));
5070
+ return {
5071
+ id: section.id,
5072
+ name: section.name ?? section.title,
5073
+ description: section.description ?? null,
5074
+ position: {
5075
+ row: pos.row ?? 0,
5076
+ column: pos.column ?? 0,
5077
+ width,
5078
+ order: pos.order ?? order
5079
+ },
5080
+ fieldIds: section.fields.map((f) => f.id),
5081
+ expanded: section.expanded !== void 0 ? section.expanded : section.isExpanded !== false,
5082
+ visible: section.visible !== false,
5083
+ collapsible: section.collapsible !== false,
5084
+ parentGroupId: section.parentGroupId ?? null,
5085
+ repeatable: section.repeatable === true,
5086
+ dataKey: section.dataKey ?? null,
5087
+ addButtonLabel: section.addButtonLabel ?? null,
5088
+ minInstances: section.minInstances ?? null,
5089
+ maxInstances: section.maxInstances ?? null,
5090
+ css: section.css
5091
+ };
5092
+ }
5030
5093
  var builderToPlatform = (builderSchema) => {
5094
+ const fieldsFlat = [];
5095
+ const groups = builderSchema.sections.map((section, sectionIndex) => {
5096
+ section.fields.forEach((f) => {
5097
+ fieldsFlat.push(fieldToPayload(f, { groupId: section.id }));
5098
+ });
5099
+ return sectionToGroupPayload(section, sectionIndex);
5100
+ });
5031
5101
  return {
5032
5102
  id: builderSchema.id,
5033
5103
  title: builderSchema.title,
5034
5104
  formName: builderSchema.formName,
5035
5105
  layout: builderSchema.layout || { type: "grid", columns: 12, gap: "16px" },
5036
- sections: builderSchema.sections.map((section, sectionIndex) => ({
5037
- id: section.id,
5038
- title: section.title,
5039
- order: section.order !== void 0 ? section.order : sectionIndex,
5040
- layout: section.layout || { type: "grid", columns: section.columns || 12, gap: "16px" },
5041
- css: section.css,
5042
- fields: section.fields.map(fieldToPayload)
5043
- }))
5106
+ fields: fieldsFlat,
5107
+ groups
5044
5108
  };
5045
5109
  };
5046
5110
  var platformToBuilder = (platformSchema) => {
@@ -5061,6 +5125,77 @@ function getValidationConfigForAngular(validations) {
5061
5125
  };
5062
5126
  }
5063
5127
 
5128
+ // src/utils/sectionHierarchy.ts
5129
+ function effectiveParentId(section, sectionIds) {
5130
+ const p = section.parentGroupId;
5131
+ if (!p || !sectionIds.has(p))
5132
+ return null;
5133
+ return p;
5134
+ }
5135
+ function getRootSections(sections) {
5136
+ const ids = new Set(sections.map((s) => s.id));
5137
+ return sections.filter((s) => effectiveParentId(s, ids) === null);
5138
+ }
5139
+ function getChildSections(sections, parentId, excludeSectionId) {
5140
+ const ids = new Set(sections.map((s) => s.id));
5141
+ return sections.filter((s) => {
5142
+ return effectiveParentId(s, ids) === parentId;
5143
+ }).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
5144
+ }
5145
+ function getDescendantSectionIds(sections, rootId) {
5146
+ const byParent = /* @__PURE__ */ new Map();
5147
+ sections.forEach((s) => {
5148
+ const p = s.parentGroupId;
5149
+ if (p) {
5150
+ if (!byParent.has(p))
5151
+ byParent.set(p, []);
5152
+ byParent.get(p).push(s.id);
5153
+ }
5154
+ });
5155
+ const out = /* @__PURE__ */ new Set();
5156
+ const stack = [...byParent.get(rootId) || []];
5157
+ while (stack.length) {
5158
+ const id = stack.pop();
5159
+ if (out.has(id))
5160
+ continue;
5161
+ out.add(id);
5162
+ (byParent.get(id) || []).forEach((c) => stack.push(c));
5163
+ }
5164
+ return out;
5165
+ }
5166
+ function wouldCreateParentCycle(sections, sectionId, newParentId) {
5167
+ if (newParentId === sectionId)
5168
+ return true;
5169
+ return getDescendantSectionIds(sections, sectionId).has(newParentId);
5170
+ }
5171
+ function getValidParentSectionIds(sections, sectionId) {
5172
+ const descendants = getDescendantSectionIds(sections, sectionId);
5173
+ return sections.map((s) => s.id).filter((id) => id !== sectionId && !descendants.has(id));
5174
+ }
5175
+ function siblingsForParent(sections, sectionIds, parentId, excludeSectionId) {
5176
+ return sections.filter((s) => {
5177
+ if (s.id === excludeSectionId)
5178
+ return false;
5179
+ const eff = effectiveParentId(s, sectionIds);
5180
+ if (parentId === null)
5181
+ return eff === null;
5182
+ return eff === parentId;
5183
+ });
5184
+ }
5185
+ function nextSiblingOrder(sections, sectionId, parentId) {
5186
+ const sectionIds = new Set(sections.map((s) => s.id));
5187
+ const siblings = siblingsForParent(sections, sectionIds, parentId, sectionId);
5188
+ if (siblings.length === 0)
5189
+ return 0;
5190
+ return Math.max(...siblings.map((s) => s.order ?? 0)) + 1;
5191
+ }
5192
+ function getNextRootOrder(sections) {
5193
+ const roots = getRootSections(sections);
5194
+ if (roots.length === 0)
5195
+ return 0;
5196
+ return Math.max(...roots.map((r) => r.order ?? 0)) + 1;
5197
+ }
5198
+
5064
5199
  // src/core/useFormStore.ts
5065
5200
  var INITIAL_SCHEMA = {
5066
5201
  id: "form_1",
@@ -5071,6 +5206,7 @@ var INITIAL_SCHEMA = {
5071
5206
  var formStore = createStore((set, get) => ({
5072
5207
  schema: INITIAL_SCHEMA,
5073
5208
  selectedFieldId: null,
5209
+ selectedSectionId: null,
5074
5210
  history: [INITIAL_SCHEMA],
5075
5211
  historyIndex: 0,
5076
5212
  isPreviewMode: false,
@@ -5388,6 +5524,8 @@ var formStore = createStore((set, get) => ({
5388
5524
  importSection: (section) => {
5389
5525
  const { schema, history, historyIndex } = get();
5390
5526
  const clonedSection = cloneSection(section);
5527
+ clonedSection.parentGroupId = null;
5528
+ clonedSection.order = getNextRootOrder(schema.sections);
5391
5529
  const newSchema = { ...schema, sections: [...schema.sections, clonedSection] };
5392
5530
  set({
5393
5531
  schema: newSchema,
@@ -5397,15 +5535,26 @@ var formStore = createStore((set, get) => ({
5397
5535
  },
5398
5536
  addSection: () => {
5399
5537
  const { schema, history, historyIndex } = get();
5538
+ const order = getNextRootOrder(schema.sections);
5400
5539
  const newSection = {
5401
5540
  id: generateId(),
5402
- title: `Section ${schema.sections.length + 1}`,
5541
+ title: `Section ${order + 1}`,
5542
+ name: `Section ${order + 1}`,
5403
5543
  fields: [],
5404
5544
  columns: 1,
5405
5545
  // Legacy - prefer layout.columns
5406
5546
  layout: { type: "grid", columns: 12, gap: "16px" },
5407
- order: schema.sections.length
5408
- // Set order based on current section count
5547
+ order,
5548
+ position: { row: order, column: 0, width: 12, order },
5549
+ expanded: true,
5550
+ visible: true,
5551
+ collapsible: true,
5552
+ parentGroupId: null,
5553
+ repeatable: false,
5554
+ dataKey: null,
5555
+ addButtonLabel: null,
5556
+ minInstances: null,
5557
+ maxInstances: null
5409
5558
  };
5410
5559
  const newSchema = { ...schema, sections: [...schema.sections, newSection] };
5411
5560
  set({
@@ -5415,24 +5564,74 @@ var formStore = createStore((set, get) => ({
5415
5564
  });
5416
5565
  },
5417
5566
  removeSection: (sectionId) => {
5418
- const { schema, history, historyIndex } = get();
5567
+ const { schema, history, historyIndex, selectedSectionId } = get();
5419
5568
  const newSchema = {
5420
5569
  ...schema,
5421
- sections: schema.sections.filter((s) => s.id !== sectionId)
5570
+ sections: schema.sections.filter((s) => s.id !== sectionId).map((s) => s.parentGroupId === sectionId ? { ...s, parentGroupId: null } : s)
5422
5571
  };
5423
5572
  set({
5424
5573
  schema: newSchema,
5574
+ selectedSectionId: selectedSectionId === sectionId ? null : selectedSectionId,
5425
5575
  history: [...history.slice(0, historyIndex + 1), newSchema],
5426
5576
  historyIndex: historyIndex + 1
5427
5577
  });
5428
5578
  },
5429
5579
  updateSection: (sectionId, updates) => {
5430
5580
  const { schema, history, historyIndex } = get();
5581
+ let processedUpdates = { ...updates };
5582
+ if (updates.parentGroupId !== void 0) {
5583
+ const newParent = updates.parentGroupId;
5584
+ if (newParent === null || newParent === "") {
5585
+ processedUpdates.parentGroupId = null;
5586
+ processedUpdates.order = nextSiblingOrder(schema.sections, sectionId, null);
5587
+ } else if (wouldCreateParentCycle(schema.sections, sectionId, newParent)) {
5588
+ delete processedUpdates.parentGroupId;
5589
+ } else {
5590
+ processedUpdates.parentGroupId = newParent;
5591
+ processedUpdates.order = nextSiblingOrder(schema.sections, sectionId, newParent);
5592
+ }
5593
+ }
5431
5594
  const newSchema = {
5432
5595
  ...schema,
5433
- sections: schema.sections.map(
5434
- (s) => s.id === sectionId ? { ...s, ...updates } : s
5435
- )
5596
+ sections: schema.sections.map((s) => {
5597
+ if (s.id !== sectionId)
5598
+ return s;
5599
+ let mergedUpdates = { ...processedUpdates };
5600
+ if (processedUpdates.css !== void 0) {
5601
+ const styleKeyPassed = processedUpdates.css && "style" in processedUpdates.css;
5602
+ let newStyle;
5603
+ if (styleKeyPassed) {
5604
+ if (processedUpdates.css.style === void 0 || processedUpdates.css.style === null) {
5605
+ newStyle = void 0;
5606
+ } else if (typeof processedUpdates.css.style === "object") {
5607
+ const onlyStyleInUpdate = !("class" in processedUpdates.css) || processedUpdates.css.class === void 0;
5608
+ if (onlyStyleInUpdate) {
5609
+ newStyle = processedUpdates.css.style;
5610
+ } else {
5611
+ newStyle = { ...s.css?.style || {}, ...processedUpdates.css.style };
5612
+ }
5613
+ } else {
5614
+ newStyle = processedUpdates.css.style;
5615
+ }
5616
+ } else {
5617
+ newStyle = s.css?.style;
5618
+ }
5619
+ mergedUpdates.css = {
5620
+ ...s.css || {},
5621
+ ...processedUpdates.css,
5622
+ style: newStyle
5623
+ };
5624
+ if (!mergedUpdates.css.class)
5625
+ delete mergedUpdates.css.class;
5626
+ if (!mergedUpdates.css.style || Object.keys(mergedUpdates.css.style).length === 0) {
5627
+ delete mergedUpdates.css.style;
5628
+ }
5629
+ if (Object.keys(mergedUpdates.css).length === 0) {
5630
+ mergedUpdates.css = void 0;
5631
+ }
5632
+ }
5633
+ return { ...s, ...mergedUpdates };
5634
+ })
5436
5635
  };
5437
5636
  set({
5438
5637
  schema: newSchema,
@@ -5442,14 +5641,23 @@ var formStore = createStore((set, get) => ({
5442
5641
  },
5443
5642
  moveSection: (oldIndex2, newIndex2) => {
5444
5643
  const { schema, history, historyIndex } = get();
5445
- const newSections = [...schema.sections];
5446
- const [movedSection] = newSections.splice(oldIndex2, 1);
5447
- newSections.splice(newIndex2, 0, movedSection);
5448
- newSections.forEach((section, index2) => {
5449
- if (section.order !== index2) {
5450
- section.order = index2;
5451
- }
5452
- });
5644
+ const roots = getRootSections(schema.sections).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
5645
+ if (oldIndex2 < 0 || oldIndex2 >= roots.length || newIndex2 < 0 || newIndex2 >= roots.length || oldIndex2 === newIndex2) {
5646
+ return;
5647
+ }
5648
+ const reorderedRoots = [...roots];
5649
+ const [moved2] = reorderedRoots.splice(oldIndex2, 1);
5650
+ reorderedRoots.splice(newIndex2, 0, moved2);
5651
+ const orderByRootId = new Map(reorderedRoots.map((r, i) => [r.id, i]));
5652
+ const rootIdSet = new Set(reorderedRoots.map((r) => r.id));
5653
+ const nonRoots = schema.sections.filter((s) => !rootIdSet.has(s.id));
5654
+ const newSections = [
5655
+ ...reorderedRoots.map((r) => {
5656
+ const full = schema.sections.find((s) => s.id === r.id);
5657
+ return { ...full, order: orderByRootId.get(r.id) };
5658
+ }),
5659
+ ...nonRoots
5660
+ ];
5453
5661
  const newSchema = { ...schema, sections: newSections };
5454
5662
  set({
5455
5663
  schema: newSchema,
@@ -5595,7 +5803,14 @@ var formStore = createStore((set, get) => ({
5595
5803
  historyIndex: historyIndex + 1
5596
5804
  });
5597
5805
  },
5598
- selectField: (fieldId) => set({ selectedFieldId: fieldId }),
5806
+ selectField: (fieldId) => set({
5807
+ selectedFieldId: fieldId,
5808
+ selectedSectionId: fieldId ? null : null
5809
+ }),
5810
+ selectSection: (sectionId) => set({
5811
+ selectedSectionId: sectionId,
5812
+ selectedFieldId: null
5813
+ }),
5599
5814
  moveField: (fieldId, targetSectionId, newIndex2) => {
5600
5815
  const { schema, history, historyIndex } = get();
5601
5816
  let field;
@@ -9563,37 +9778,70 @@ var FieldWrapper = class {
9563
9778
  };
9564
9779
 
9565
9780
  // src/builder/Section.ts
9566
- var Section = class {
9567
- constructor(section, isSelectedField) {
9781
+ var SECTION_TITLE_DEBOUNCE_MS = 300;
9782
+ var sectionTitleUpdateTimeouts = /* @__PURE__ */ new Map();
9783
+ var Section = class _Section {
9784
+ constructor(section, isSelectedField, selectedSectionId, allSections, depth = 0) {
9568
9785
  __publicField(this, "container");
9569
9786
  __publicField(this, "section");
9787
+ __publicField(this, "allSections");
9570
9788
  __publicField(this, "isSelectedField");
9789
+ __publicField(this, "selectedSectionId");
9790
+ __publicField(this, "depth");
9571
9791
  this.section = section;
9792
+ this.allSections = allSections;
9572
9793
  this.isSelectedField = isSelectedField;
9794
+ this.selectedSectionId = selectedSectionId;
9795
+ this.depth = depth;
9573
9796
  this.container = this.render();
9574
9797
  }
9575
9798
  getElement() {
9576
9799
  return this.container;
9577
9800
  }
9578
9801
  render() {
9802
+ const sectionVisible = this.section.visible !== false;
9803
+ const isSelectedSection = this.section.id === this.selectedSectionId;
9804
+ const marginClass = this.depth > 0 ? "mb-4" : "mb-6";
9579
9805
  const sectionEl = createElement("div", {
9580
- className: "mb-6 rounded-lg border bg-white dark:bg-gray-900 shadow-sm transition-all border-[#e9e9e9] ",
9581
- "data-id": this.section.id
9806
+ className: `${marginClass} rounded-lg border bg-white dark:bg-gray-900 shadow-sm transition-all border-[#e9e9e9] ${!sectionVisible ? "opacity-50" : ""} ${isSelectedSection ? "ring-2 ring-[#635bff]" : ""} ${this.depth > 0 ? "shadow-inner" : ""}`,
9807
+ "data-id": this.section.id,
9808
+ "data-section-id": this.section.id
9809
+ });
9810
+ const header = createElement("div", {
9811
+ className: "flex items-center justify-between p-2 border-b border-gray-100 bg-white dark:border-gray-800 bg-gray-50 dark:bg-gray-800/50 rounded-t-lg cursor-pointer",
9812
+ onclick: () => {
9813
+ formStore.getState().selectSection(this.section.id);
9814
+ }
9582
9815
  });
9583
- const header = createElement("div", { className: "flex items-center justify-between p-2 border-b border-gray-100 bg-white dark:border-gray-800 bg-gray-50 dark:bg-gray-800/50 rounded-t-lg" });
9584
- const headerLeft = createElement("div", { className: "flex items-center flex-1" });
9585
- headerLeft.appendChild(createElement("div", { className: "cursor-move mr-3 text-gray-400 hover:text-gray-600 section-handle" }, [getIcon("GripVertical", 20)]));
9816
+ const headerLeft = createElement("div", { className: "flex items-center flex-1 min-w-0" });
9817
+ const dragHandle = createElement("div", { className: "cursor-move mr-3 text-gray-400 hover:text-gray-600 section-handle flex-shrink-0" }, [getIcon("GripVertical", 20)]);
9818
+ dragHandle.addEventListener("mousedown", (e) => e.stopPropagation());
9819
+ dragHandle.addEventListener("click", (e) => e.stopPropagation());
9820
+ headerLeft.appendChild(dragHandle);
9586
9821
  headerLeft.appendChild(createElement("input", {
9587
- className: "bg-transparent font-semibold text-gray-700 dark:text-gray-200 focus:outline-none focus:border-b border-blue-500",
9822
+ className: "bg-transparent font-semibold text-gray-700 dark:text-gray-200 focus:outline-none focus:border-b border-blue-500 min-w-0 flex-1",
9588
9823
  value: this.section.title,
9589
9824
  "data-focus-id": `section-title-${this.section.id}`,
9590
- oninput: (e) => formStore.getState().updateSection(this.section.id, { title: e.target.value })
9825
+ onclick: (e) => e.stopPropagation(),
9826
+ oninput: (e) => {
9827
+ const sid = this.section.id;
9828
+ const value = e.target.value;
9829
+ const existing = sectionTitleUpdateTimeouts.get(sid);
9830
+ if (existing)
9831
+ clearTimeout(existing);
9832
+ const timeoutId = setTimeout(() => {
9833
+ sectionTitleUpdateTimeouts.delete(sid);
9834
+ formStore.getState().updateSection(sid, { title: value, name: value });
9835
+ }, SECTION_TITLE_DEBOUNCE_MS);
9836
+ sectionTitleUpdateTimeouts.set(sid, timeoutId);
9837
+ }
9591
9838
  }));
9592
9839
  header.appendChild(headerLeft);
9593
9840
  const actions = createElement("div", { className: "flex items-center space-x-1" });
9594
9841
  const colSelect = createElement("select", {
9595
9842
  className: "text-xs border rounded bg-transparent mr-2 p-1 text-gray-600",
9596
9843
  title: "Section Columns",
9844
+ onclick: (e) => e.stopPropagation(),
9597
9845
  onchange: (e) => {
9598
9846
  formStore.getState().updateSection(this.section.id, { columns: parseInt(e.target.value) });
9599
9847
  }
@@ -9604,7 +9852,8 @@ var Section = class {
9604
9852
  actions.appendChild(colSelect);
9605
9853
  actions.appendChild(createElement("button", {
9606
9854
  className: "text-gray-600 hover:text-red-500 transition-colors p-1",
9607
- onclick: () => {
9855
+ onclick: (e) => {
9856
+ e.stopPropagation();
9608
9857
  if (confirm("Delete this section and all its fields?")) {
9609
9858
  formStore.getState().removeSection(this.section.id);
9610
9859
  }
@@ -9648,6 +9897,25 @@ var Section = class {
9648
9897
  fieldsGrid.appendChild(FieldWrapper.render(field, isSelected));
9649
9898
  });
9650
9899
  sectionEl.appendChild(fieldsGrid);
9900
+ const childSections = getChildSections(this.allSections, this.section.id);
9901
+ if (childSections.length > 0) {
9902
+ const nestedWrap = createElement("div", {
9903
+ className: this.depth === 0 ? "px-4 pb-4 pt-0 border-t border-dashed border-gray-200 dark:border-gray-700" : "pl-3 ml-2 mt-3 pt-3 border-l-2 border-[#635bff]/35 dark:border-[#635bff]/50 rounded-bl-md"
9904
+ });
9905
+ const nestedList = createElement("div", { className: "space-y-4" });
9906
+ childSections.forEach((child) => {
9907
+ const childComponent = new _Section(
9908
+ child,
9909
+ this.isSelectedField,
9910
+ this.selectedSectionId,
9911
+ this.allSections,
9912
+ this.depth + 1
9913
+ );
9914
+ nestedList.appendChild(childComponent.getElement());
9915
+ });
9916
+ nestedWrap.appendChild(nestedList);
9917
+ sectionEl.appendChild(nestedWrap);
9918
+ }
9651
9919
  this.initFieldSortable(fieldsGrid);
9652
9920
  return sectionEl;
9653
9921
  }
@@ -9665,7 +9933,6 @@ var Section = class {
9665
9933
  onAdd: (evt) => {
9666
9934
  const item = evt.item;
9667
9935
  const type = item.getAttribute("data-type");
9668
- evt.from.getAttribute("data-section-id");
9669
9936
  const toSectionId = this.section.id;
9670
9937
  if (type && !item.hasAttribute("data-id")) {
9671
9938
  item.remove();
@@ -9709,12 +9976,14 @@ var Section = class {
9709
9976
 
9710
9977
  // src/builder/SectionList.ts
9711
9978
  var SectionList = class {
9712
- constructor(schema, selectedFieldId) {
9979
+ constructor(schema, selectedFieldId, selectedSectionId) {
9713
9980
  __publicField(this, "container");
9714
9981
  __publicField(this, "schema");
9715
9982
  __publicField(this, "selectedFieldId");
9983
+ __publicField(this, "selectedSectionId");
9716
9984
  this.schema = schema;
9717
9985
  this.selectedFieldId = selectedFieldId;
9986
+ this.selectedSectionId = selectedSectionId;
9718
9987
  this.container = this.render();
9719
9988
  }
9720
9989
  getElement() {
@@ -9737,13 +10006,18 @@ var SectionList = class {
9737
10006
  placeholder.appendChild(createElement("div", { className: "text-sm text-gray-400", text: 'Drag fields from the sidebar or click "Add Section" below.' }));
9738
10007
  listContainer.appendChild(placeholder);
9739
10008
  }
9740
- const sortedSections = [...this.schema.sections].sort((a, b) => {
10009
+ const rootSections = getRootSections(this.schema.sections).sort((a, b) => {
9741
10010
  const orderA = a.order !== void 0 ? a.order : 0;
9742
10011
  const orderB = b.order !== void 0 ? b.order : 0;
9743
10012
  return orderA - orderB;
9744
10013
  });
9745
- sortedSections.forEach((section) => {
9746
- const sectionComponent = new Section(section, (id) => id === this.selectedFieldId);
10014
+ rootSections.forEach((section) => {
10015
+ const sectionComponent = new Section(
10016
+ section,
10017
+ (id) => id === this.selectedFieldId,
10018
+ this.selectedSectionId,
10019
+ this.schema.sections
10020
+ );
9747
10021
  listContainer.appendChild(sectionComponent.getElement());
9748
10022
  });
9749
10023
  this.initSectionSortable(listContainer, hasNoSections);
@@ -9992,7 +10266,17 @@ var FormBuilder = class {
9992
10266
  const schemaHash = JSON.stringify({
9993
10267
  sections: state.schema.sections.map((s) => ({
9994
10268
  id: s.id,
9995
- // Exclude title - prevents re-renders on section name typing
10269
+ title: s.title,
10270
+ description: s.description,
10271
+ visible: s.visible,
10272
+ position: s.position,
10273
+ expanded: s.expanded,
10274
+ collapsible: s.collapsible,
10275
+ parentGroupId: s.parentGroupId,
10276
+ repeatable: s.repeatable,
10277
+ minInstances: s.minInstances,
10278
+ maxInstances: s.maxInstances,
10279
+ css: s.css,
9996
10280
  fields: s.fields.map((f) => ({
9997
10281
  id: f.id,
9998
10282
  type: f.type,
@@ -10003,6 +10287,7 @@ var FormBuilder = class {
10003
10287
  }))
10004
10288
  })),
10005
10289
  selectedField: state.selectedFieldId,
10290
+ selectedSection: state.selectedSectionId,
10006
10291
  isPreviewMode: state.isPreviewMode
10007
10292
  });
10008
10293
  if (schemaHash !== this.lastRenderedSchemaHash || previewModeChanged) {
@@ -10427,7 +10712,7 @@ var FormBuilder = class {
10427
10712
  }
10428
10713
  });
10429
10714
  inner.appendChild(formNameInput);
10430
- const sectionList = new SectionList(state.schema, state.selectedFieldId);
10715
+ const sectionList = new SectionList(state.schema, state.selectedFieldId, state.selectedSectionId);
10431
10716
  inner.appendChild(sectionList.getElement());
10432
10717
  const addSectionBtn = createElement("button", {
10433
10718
  className: "w-full mt-6 py-3 dark:border-gray-700 rounded-md text-sm text-gray-500 bg-[#635bff] max-w-[140px] text-white transition-colors flex items-center justify-center font-medium",
@@ -10463,78 +10748,548 @@ var FormBuilder = class {
10463
10748
  });
10464
10749
  return container;
10465
10750
  }
10466
- renderConfigPanel(state, focusState = null) {
10467
- const panel = createElement("div", { className: " dark:bg-gray-900 flex flex-col h-full overflow-y-auto" });
10468
- const selectedField = state.schema.sections.flatMap((s) => s.fields).find((f) => f.id === state.selectedFieldId);
10469
- if (!selectedField) {
10470
- panel.appendChild(createElement("div", { className: "p-4 text-center text-gray-500", text: "Select a field to configure" }));
10471
- return panel;
10751
+ /**
10752
+ * Shared 1–12 grid span control (Field Settings + Group Settings).
10753
+ */
10754
+ createGridSpanSelector(options) {
10755
+ const layoutGroup = createElement("div", { className: "layout-span-group" });
10756
+ const layoutLabelRow = createElement("div", { className: "flex items-center justify-between mb-2" });
10757
+ layoutLabelRow.appendChild(createElement("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", text: "Grid Span" }));
10758
+ const spanValueDisplay = createElement("span", {
10759
+ className: "span-value-badge px-2 py-0.5 text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-200 rounded-full",
10760
+ text: `${options.currentSpan}/12`,
10761
+ id: options.badgeElementId
10762
+ });
10763
+ layoutLabelRow.appendChild(spanValueDisplay);
10764
+ layoutGroup.appendChild(layoutLabelRow);
10765
+ const spanButtonsContainer = createElement("div", { className: "grid grid-cols-6 gap-2 mt-2" });
10766
+ for (let span = 1; span <= 12; span++) {
10767
+ const isActive = options.currentSpan === span;
10768
+ const spanBtn = createElement("button", {
10769
+ type: "button",
10770
+ className: `span-preset-btn px-2 py-1.5 text-xs rounded transition-colors cursor-pointer ${isActive ? "bg-[#e7e7ff] text-[#635bff] font-semibold" : "bg-white border-2 border-[#e7e7ff] dark:bg-gray-800 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700"}`,
10771
+ text: `${span}`,
10772
+ title: `${span} column${span > 1 ? "s" : ""} (${Math.round(span / 12 * 100)}%)`
10773
+ });
10774
+ spanBtn.addEventListener("click", (e) => {
10775
+ e.preventDefault();
10776
+ e.stopPropagation();
10777
+ options.onSelect(span);
10778
+ });
10779
+ spanButtonsContainer.appendChild(spanBtn);
10472
10780
  }
10473
- const header = createElement("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800" });
10474
- header.appendChild(createElement("h2", { className: "font-semibold text-gray-900 dark:text-white", text: "Field Settings" }));
10475
- header.appendChild(createElement("button", {
10476
- className: "text-gray-500 hover:text-gray-700",
10477
- onclick: () => formStore.getState().selectField(null)
10478
- }, [getIcon("X", 20)]));
10479
- panel.appendChild(header);
10480
- const body = createElement("div", { className: "flex-1 overflow-y-auto p-4 px-2 space-y-3", id: "config-panel-body" });
10481
- const labelGroup = createElement("div");
10482
- labelGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Label" }));
10483
- labelGroup.appendChild(createElement("input", {
10484
- className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
10485
- value: selectedField.label,
10486
- "data-focus-id": `field-label-${selectedField.id}`,
10487
- oninput: (e) => {
10488
- const fieldId2 = selectedField.id;
10489
- const value = e.target.value;
10490
- const existing = labelUpdateTimeouts.get(fieldId2);
10491
- if (existing)
10492
- clearTimeout(existing);
10493
- const timeoutId = setTimeout(() => {
10494
- labelUpdateTimeouts.delete(fieldId2);
10495
- formStore.getState().updateField(fieldId2, { label: value });
10496
- }, LABEL_DEBOUNCE_MS);
10497
- labelUpdateTimeouts.set(fieldId2, timeoutId);
10781
+ layoutGroup.appendChild(spanButtonsContainer);
10782
+ return layoutGroup;
10783
+ }
10784
+ /**
10785
+ * Shared Styling block (Padding, Background, Alignment, CSS class, Advanced CSS) same as Field Settings.
10786
+ */
10787
+ appendSharedStylingSection(body, target, focusState) {
10788
+ const cssHeader = createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3 mt-6", text: "Styling" });
10789
+ body.appendChild(cssHeader);
10790
+ const getEntity = () => {
10791
+ const st = formStore.getState();
10792
+ if (target.kind === "field") {
10793
+ return st.schema.sections.flatMap((s) => s.fields).find((f) => f.id === target.fieldId);
10498
10794
  }
10499
- }));
10500
- body.appendChild(labelGroup);
10501
- if (selectedField.type === "number") {
10502
- const valueSourceHeader = createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2 mt-4", text: "Value Source" });
10503
- body.appendChild(valueSourceHeader);
10504
- const valueSourceGroup = createElement("div", { className: "mb-3" });
10505
- valueSourceGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Source" }));
10506
- const valueSourceSelect = createElement("select", {
10507
- className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
10508
- value: selectedField.valueSource || "manual",
10509
- onchange: (e) => {
10510
- const source = e.target.value;
10511
- const updates = { valueSource: source };
10512
- if (source === "manual") {
10513
- updates.formula = void 0;
10514
- updates.dependencies = void 0;
10515
- } else if (source === "formula") {
10516
- updates.formula = selectedField.formula || "";
10517
- updates.dependencies = selectedField.dependencies || [];
10518
- }
10519
- formStore.getState().updateField(selectedField.id, updates);
10520
- this.render();
10795
+ return st.schema.sections.find((s) => s.id === target.sectionId);
10796
+ };
10797
+ const getStyleValue = (prop) => {
10798
+ const ent = getEntity();
10799
+ return ent?.css?.style?.[prop] || "";
10800
+ };
10801
+ const updateStyleProp = (prop, value) => {
10802
+ const ent = getEntity();
10803
+ if (!ent)
10804
+ return;
10805
+ const currentStyle = ent.css?.style || {};
10806
+ const newStyle = { ...currentStyle };
10807
+ if (value) {
10808
+ newStyle[prop] = value;
10809
+ } else {
10810
+ delete newStyle[prop];
10811
+ }
10812
+ const updatePayload = {
10813
+ css: {
10814
+ style: Object.keys(newStyle).length > 0 ? newStyle : void 0
10521
10815
  }
10522
- });
10523
- valueSourceSelect.appendChild(createElement("option", { value: "manual", text: "Manual", selected: (selectedField.valueSource || "manual") === "manual" }));
10524
- valueSourceSelect.appendChild(createElement("option", { value: "formula", text: "Formula", selected: selectedField.valueSource === "formula" }));
10525
- valueSourceGroup.appendChild(valueSourceSelect);
10526
- body.appendChild(valueSourceGroup);
10527
- if (selectedField.valueSource === "formula") {
10528
- const schema = formStore.getState().schema;
10529
- const numericFields = getNumericFieldsForFormula(schema, selectedField.id);
10530
- const availableIds = numericFields.map((f) => f.id);
10531
- const availableNames = numericFields.map((f) => f.fieldName);
10532
- const formulaGroup = createElement("div", { className: "mb-3" });
10533
- formulaGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Formula" }));
10534
- const formulaInput = createElement("input", {
10535
- type: "text",
10536
- className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent font-mono text-sm",
10537
- value: selectedField.formula || "",
10816
+ };
10817
+ if (target.kind === "field") {
10818
+ formStore.getState().updateField(target.fieldId, updatePayload);
10819
+ } else {
10820
+ formStore.getState().updateSection(target.sectionId, updatePayload);
10821
+ }
10822
+ };
10823
+ const entityId = target.kind === "field" ? target.fieldId : target.sectionId;
10824
+ const paddingGroup = createElement("div", { className: "mb-3" });
10825
+ paddingGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Padding" }));
10826
+ const paddingSelect = createElement("select", {
10827
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-white dark:bg-gray-800 text-sm",
10828
+ onchange: (e) => {
10829
+ updateStyleProp("padding", e.target.value);
10830
+ }
10831
+ });
10832
+ const paddingOptions = [
10833
+ { value: "", label: "None" },
10834
+ { value: "4px", label: "4px - Tight" },
10835
+ { value: "8px", label: "8px - Normal" },
10836
+ { value: "12px", label: "12px - Comfortable" },
10837
+ { value: "16px", label: "16px - Spacious" },
10838
+ { value: "24px", label: "24px - Large" }
10839
+ ];
10840
+ paddingOptions.forEach((opt) => {
10841
+ paddingSelect.appendChild(createElement("option", { value: opt.value, text: opt.label, selected: getStyleValue("padding") === opt.value }));
10842
+ });
10843
+ paddingGroup.appendChild(paddingSelect);
10844
+ body.appendChild(paddingGroup);
10845
+ const bgColorGroup = createElement("div", { className: "mb-3" });
10846
+ bgColorGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Background Color" }));
10847
+ const bgColorRow = createElement("div", { className: "flex items-center gap-2" });
10848
+ const bgColorInput = createElement("input", {
10849
+ type: "color",
10850
+ className: "w-10 h-10 rounded border border-gray-300 cursor-pointer",
10851
+ value: getStyleValue("backgroundColor") || "#ffffff",
10852
+ onchange: (e) => {
10853
+ const color = e.target.value;
10854
+ updateStyleProp("backgroundColor", color === "#ffffff" ? "" : color);
10855
+ }
10856
+ });
10857
+ const bgColorClear = createElement("button", {
10858
+ type: "button",
10859
+ className: "px-2 py-1 text-xs text-gray-600 hover:text-gray-800 border border-gray-300 rounded",
10860
+ text: "Clear",
10861
+ onclick: () => {
10862
+ bgColorInput.value = "#ffffff";
10863
+ updateStyleProp("backgroundColor", "");
10864
+ }
10865
+ });
10866
+ bgColorRow.appendChild(bgColorInput);
10867
+ bgColorRow.appendChild(bgColorClear);
10868
+ bgColorGroup.appendChild(bgColorRow);
10869
+ body.appendChild(bgColorGroup);
10870
+ const alignGroup = createElement("div", { className: "mb-3" });
10871
+ alignGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Text Alignment" }));
10872
+ const alignButtonsRow = createElement("div", { className: "flex gap-1" });
10873
+ const alignments = [
10874
+ { value: "left", icon: "AlignLeft" },
10875
+ { value: "center", icon: "AlignCenter" },
10876
+ { value: "right", icon: "AlignRight" }
10877
+ ];
10878
+ const currentAlign = getStyleValue("textAlign") || "left";
10879
+ alignments.forEach((align) => {
10880
+ const isActive = currentAlign === align.value;
10881
+ const btn = createElement("button", {
10882
+ type: "button",
10883
+ className: `p-2 rounded border ${isActive ? "border-blue-500 bg-blue-50 text-blue-600" : "border-gray-300 text-gray-600 hover:bg-gray-50"}`,
10884
+ title: `Align ${align.value}`,
10885
+ onclick: () => {
10886
+ const newValue = align.value === "left" ? "" : align.value;
10887
+ updateStyleProp("textAlign", newValue);
10888
+ }
10889
+ }, [getIcon(align.icon, 16)]);
10890
+ alignButtonsRow.appendChild(btn);
10891
+ });
10892
+ alignGroup.appendChild(alignButtonsRow);
10893
+ body.appendChild(alignGroup);
10894
+ const cssClassGroup = createElement("div", { className: "mb-3" });
10895
+ cssClassGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Custom CSS Class" }));
10896
+ cssClassGroup.appendChild(createElement("input", {
10897
+ type: "text",
10898
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent text-sm",
10899
+ value: getEntity()?.css?.class || "",
10900
+ placeholder: "e.g. my-custom-class",
10901
+ "data-focus-id": `${target.kind}-css-class-${entityId}`,
10902
+ oninput: (e) => {
10903
+ const cssClass = e.target.value;
10904
+ const ent = getEntity();
10905
+ if (!ent)
10906
+ return;
10907
+ if (target.kind === "field") {
10908
+ formStore.getState().updateField(target.fieldId, {
10909
+ css: { class: cssClass || void 0 }
10910
+ });
10911
+ } else {
10912
+ formStore.getState().updateSection(target.sectionId, {
10913
+ css: { class: cssClass || void 0 }
10914
+ });
10915
+ }
10916
+ }
10917
+ }));
10918
+ body.appendChild(cssClassGroup);
10919
+ const isPanelExpanded = advancedCssPanelState.get(entityId) || false;
10920
+ const advancedToggleGroup = createElement("div", { className: "mb-3" });
10921
+ const advancedToggle = createElement("button", {
10922
+ type: "button",
10923
+ className: "text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1",
10924
+ onclick: () => {
10925
+ const advancedPanel2 = document.getElementById(`advanced-css-${entityId}`);
10926
+ if (advancedPanel2) {
10927
+ advancedPanel2.classList.toggle("hidden");
10928
+ const isHidden = advancedPanel2.classList.contains("hidden");
10929
+ advancedToggle.textContent = isHidden ? "\u25B6 Show Advanced CSS" : "\u25BC Hide Advanced CSS";
10930
+ advancedCssPanelState.set(entityId, !isHidden);
10931
+ }
10932
+ }
10933
+ });
10934
+ advancedToggle.textContent = isPanelExpanded ? "\u25BC Hide Advanced CSS" : "\u25B6 Show Advanced CSS";
10935
+ advancedToggleGroup.appendChild(advancedToggle);
10936
+ body.appendChild(advancedToggleGroup);
10937
+ const advancedPanel = createElement("div", {
10938
+ className: isPanelExpanded ? "mb-3" : "mb-3 hidden",
10939
+ id: `advanced-css-${entityId}`
10940
+ });
10941
+ advancedPanel.appendChild(createElement("label", { className: "block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1", text: "Raw CSS Style (JSON)" }));
10942
+ const cssStyleId = `${target.kind}-css-style-${entityId}`;
10943
+ const freshEnt = getEntity();
10944
+ let initialCssStyleValue = freshEnt?.css?.style ? JSON.stringify(freshEnt.css.style, null, 2) : "";
10945
+ const preservedValue = focusState?.id === cssStyleId ? focusState.value : void 0;
10946
+ if (preservedValue !== void 0) {
10947
+ initialCssStyleValue = preservedValue;
10948
+ }
10949
+ const cssStyleTextarea = createElement("textarea", {
10950
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent text-xs font-mono",
10951
+ rows: 3,
10952
+ placeholder: '{"padding": "8px", "backgroundColor": "#f0f0f0"}',
10953
+ "data-focus-id": cssStyleId,
10954
+ "data-was-focused": "false",
10955
+ onfocus: (e) => {
10956
+ const textarea = e.target;
10957
+ textarea.setAttribute("data-was-focused", "true");
10958
+ const ent = getEntity();
10959
+ if (!textarea.value.trim() && ent?.css?.style && Object.keys(ent.css.style).length > 0) {
10960
+ textarea.value = JSON.stringify(ent.css.style, null, 2);
10961
+ }
10962
+ },
10963
+ oninput: (e) => {
10964
+ const styleText = e.target.value;
10965
+ const ent = getEntity();
10966
+ if (!ent)
10967
+ return;
10968
+ try {
10969
+ if (styleText.trim()) {
10970
+ const styleObj = JSON.parse(styleText);
10971
+ if (target.kind === "field") {
10972
+ formStore.getState().updateField(target.fieldId, {
10973
+ css: { ...ent.css, style: styleObj }
10974
+ });
10975
+ } else {
10976
+ formStore.getState().updateSection(target.sectionId, {
10977
+ css: { ...ent.css, style: styleObj }
10978
+ });
10979
+ }
10980
+ }
10981
+ } catch {
10982
+ }
10983
+ },
10984
+ onblur: (e) => {
10985
+ const textarea = e.target;
10986
+ const styleText = textarea.value;
10987
+ const wasFocused = textarea.getAttribute("data-was-focused") === "true";
10988
+ if (!wasFocused)
10989
+ return;
10990
+ textarea.setAttribute("data-was-focused", "false");
10991
+ setTimeout(() => {
10992
+ if (!document.body.contains(textarea))
10993
+ return;
10994
+ const advPanel = document.getElementById(`advanced-css-${entityId}`);
10995
+ const isPanelVisible = advPanel && !advPanel.classList.contains("hidden");
10996
+ if (!isPanelVisible)
10997
+ return;
10998
+ const ent = getEntity();
10999
+ if (!ent)
11000
+ return;
11001
+ try {
11002
+ if (styleText.trim()) {
11003
+ const styleObj = JSON.parse(styleText);
11004
+ if (target.kind === "field") {
11005
+ formStore.getState().updateField(target.fieldId, {
11006
+ css: { ...ent.css, style: styleObj }
11007
+ });
11008
+ } else {
11009
+ formStore.getState().updateSection(target.sectionId, {
11010
+ css: { ...ent.css, style: styleObj }
11011
+ });
11012
+ }
11013
+ } else if (ent.css?.style && Object.keys(ent.css.style).length > 0) {
11014
+ textarea.value = JSON.stringify(ent.css.style, null, 2);
11015
+ } else {
11016
+ if (target.kind === "field") {
11017
+ formStore.getState().updateField(target.fieldId, {
11018
+ css: { ...ent.css, style: void 0 }
11019
+ });
11020
+ } else {
11021
+ formStore.getState().updateSection(target.sectionId, {
11022
+ css: { ...ent.css, style: void 0 }
11023
+ });
11024
+ }
11025
+ }
11026
+ } catch {
11027
+ if (ent.css?.style) {
11028
+ textarea.value = JSON.stringify(ent.css.style, null, 2);
11029
+ }
11030
+ }
11031
+ }, 0);
11032
+ }
11033
+ });
11034
+ cssStyleTextarea.value = initialCssStyleValue;
11035
+ advancedPanel.appendChild(cssStyleTextarea);
11036
+ body.appendChild(advancedPanel);
11037
+ }
11038
+ renderGroupSettingsPanel(section, allSections, focusState) {
11039
+ const panel = createElement("div", { className: " dark:bg-gray-900 flex flex-col h-full overflow-y-auto" });
11040
+ const header = createElement("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800" });
11041
+ header.appendChild(createElement("h2", { className: "font-semibold text-gray-900 dark:text-white", text: "Group Settings" }));
11042
+ header.appendChild(createElement("button", {
11043
+ className: "text-gray-500 hover:text-gray-700",
11044
+ onclick: () => formStore.getState().selectSection(null)
11045
+ }, [getIcon("X", 20)]));
11046
+ panel.appendChild(header);
11047
+ const body = createElement("div", { className: "flex-1 overflow-y-auto p-4 px-2 space-y-3", id: "config-panel-body" });
11048
+ const sectionId = section.id;
11049
+ const sectionHeading = (text) => createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3 mt-2", text });
11050
+ body.appendChild(sectionHeading("Basic"));
11051
+ const labelGroup = createElement("div");
11052
+ labelGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Group Label" }));
11053
+ labelGroup.appendChild(createElement("input", {
11054
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
11055
+ value: section.name ?? section.title,
11056
+ "data-focus-id": `group-label-${sectionId}`,
11057
+ oninput: (e) => {
11058
+ const v = e.target.value;
11059
+ const existing = labelUpdateTimeouts.get(`group-${sectionId}`);
11060
+ if (existing)
11061
+ clearTimeout(existing);
11062
+ const timeoutId = setTimeout(() => {
11063
+ labelUpdateTimeouts.delete(`group-${sectionId}`);
11064
+ formStore.getState().updateSection(sectionId, { title: v, name: v });
11065
+ }, LABEL_DEBOUNCE_MS);
11066
+ labelUpdateTimeouts.set(`group-${sectionId}`, timeoutId);
11067
+ }
11068
+ }));
11069
+ body.appendChild(labelGroup);
11070
+ const descGroup = createElement("div");
11071
+ descGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Description" }));
11072
+ descGroup.appendChild(createElement("input", {
11073
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
11074
+ value: section.description ?? "",
11075
+ placeholder: "Optional",
11076
+ "data-focus-id": `group-desc-${sectionId}`,
11077
+ oninput: (e) => {
11078
+ const v = e.target.value;
11079
+ formStore.getState().updateSection(sectionId, { description: v || null });
11080
+ }
11081
+ }));
11082
+ body.appendChild(descGroup);
11083
+ body.appendChild(sectionHeading("Layout"));
11084
+ const order = section.order ?? 0;
11085
+ const pos = section.position ?? { width: 12};
11086
+ const currentWidth = Math.max(1, Math.min(12, pos.width ?? 12));
11087
+ body.appendChild(
11088
+ this.createGridSpanSelector({
11089
+ currentSpan: currentWidth,
11090
+ badgeElementId: `group-span-badge-${sectionId}`,
11091
+ onSelect: (span) => {
11092
+ const st = formStore.getState().schema.sections.find((s) => s.id === sectionId);
11093
+ if (!st)
11094
+ return;
11095
+ const p = st.position ?? { row: order, column: 0, width: 12, order: st.order ?? order };
11096
+ formStore.getState().updateSection(sectionId, {
11097
+ position: { ...p, width: span, order: p.order ?? st.order ?? order }
11098
+ });
11099
+ }
11100
+ })
11101
+ );
11102
+ body.appendChild(sectionHeading("Fields in group"));
11103
+ const fieldRows = createElement("div", { className: "rounded-md border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/40 p-2 space-y-1 text-sm text-gray-700 dark:text-gray-300" });
11104
+ if (section.fields.length === 0) {
11105
+ fieldRows.appendChild(createElement("div", { className: "text-gray-400 italic text-xs py-1", text: "No fields added yet" }));
11106
+ } else {
11107
+ section.fields.forEach((f) => {
11108
+ const display = f.displayName || f.label || f.id;
11109
+ fieldRows.appendChild(createElement("div", { className: "truncate", text: display }));
11110
+ });
11111
+ }
11112
+ body.appendChild(fieldRows);
11113
+ body.appendChild(sectionHeading("Display options"));
11114
+ body.appendChild(
11115
+ this.createCheckboxField(
11116
+ "Visible",
11117
+ section.visible !== false,
11118
+ (checked) => formStore.getState().updateSection(sectionId, { visible: checked }),
11119
+ `group-visible-${sectionId}`
11120
+ )
11121
+ );
11122
+ body.appendChild(
11123
+ this.createCheckboxField(
11124
+ "Show side/down arrow to expand/collapse",
11125
+ section.collapsible !== false,
11126
+ (checked) => formStore.getState().updateSection(sectionId, { collapsible: checked }),
11127
+ `group-collapsible-${sectionId}`
11128
+ )
11129
+ );
11130
+ body.appendChild(sectionHeading("Parent group"));
11131
+ const parentGroup = createElement("div", { className: "mb-2" });
11132
+ parentGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Parent Group" }));
11133
+ const parentSelect = createElement("select", {
11134
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
11135
+ value: section.parentGroupId || "",
11136
+ onchange: (e) => {
11137
+ const v = e.target.value;
11138
+ const selfId = sectionId;
11139
+ if (!v || v === selfId) {
11140
+ formStore.getState().updateSection(selfId, { parentGroupId: null });
11141
+ } else {
11142
+ formStore.getState().updateSection(selfId, { parentGroupId: v });
11143
+ }
11144
+ }
11145
+ });
11146
+ parentSelect.appendChild(createElement("option", { value: "", text: "None", selected: !section.parentGroupId }));
11147
+ const validParentIds = new Set(getValidParentSectionIds(allSections, sectionId));
11148
+ allSections.forEach((g) => {
11149
+ if (!validParentIds.has(g.id))
11150
+ return;
11151
+ const label = g.name ?? g.title;
11152
+ parentSelect.appendChild(
11153
+ createElement("option", {
11154
+ value: g.id,
11155
+ text: label,
11156
+ selected: section.parentGroupId === g.id
11157
+ })
11158
+ );
11159
+ });
11160
+ parentGroup.appendChild(parentSelect);
11161
+ body.appendChild(parentGroup);
11162
+ body.appendChild(sectionHeading("Repeatable group"));
11163
+ body.appendChild(
11164
+ this.createCheckboxField(
11165
+ "Allow multiple instances",
11166
+ section.repeatable === true,
11167
+ (checked) => formStore.getState().updateSection(sectionId, { repeatable: checked }),
11168
+ `group-repeatable-${sectionId}`
11169
+ )
11170
+ );
11171
+ if (section.repeatable === true) {
11172
+ const minG = createElement("div", { className: "mb-2" });
11173
+ minG.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Min Instances" }));
11174
+ minG.appendChild(createElement("input", {
11175
+ type: "number",
11176
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
11177
+ value: section.minInstances != null ? String(section.minInstances) : "",
11178
+ placeholder: "e.g. 1",
11179
+ "data-focus-id": `group-min-inst-${sectionId}`,
11180
+ oninput: (e) => {
11181
+ const raw = e.target.value;
11182
+ if (raw === "") {
11183
+ formStore.getState().updateSection(sectionId, { minInstances: null });
11184
+ return;
11185
+ }
11186
+ const n = parseInt(raw, 10);
11187
+ formStore.getState().updateSection(sectionId, { minInstances: isNaN(n) ? null : n });
11188
+ }
11189
+ }));
11190
+ body.appendChild(minG);
11191
+ const maxG = createElement("div", { className: "mb-2" });
11192
+ maxG.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Max Instances" }));
11193
+ maxG.appendChild(createElement("input", {
11194
+ type: "number",
11195
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
11196
+ value: section.maxInstances != null ? String(section.maxInstances) : "",
11197
+ placeholder: "Unlimited",
11198
+ "data-focus-id": `group-max-inst-${sectionId}`,
11199
+ oninput: (e) => {
11200
+ const raw = e.target.value;
11201
+ if (raw === "") {
11202
+ formStore.getState().updateSection(sectionId, { maxInstances: null });
11203
+ return;
11204
+ }
11205
+ const n = parseInt(raw, 10);
11206
+ formStore.getState().updateSection(sectionId, { maxInstances: isNaN(n) ? null : n });
11207
+ }
11208
+ }));
11209
+ body.appendChild(maxG);
11210
+ }
11211
+ this.appendSharedStylingSection(body, { kind: "section", sectionId }, focusState);
11212
+ panel.appendChild(body);
11213
+ return panel;
11214
+ }
11215
+ renderConfigPanel(state, focusState = null) {
11216
+ const panel = createElement("div", { className: " dark:bg-gray-900 flex flex-col h-full overflow-y-auto" });
11217
+ if (state.selectedSectionId) {
11218
+ const selectedSection = state.schema.sections.find((s) => s.id === state.selectedSectionId);
11219
+ if (selectedSection) {
11220
+ return this.renderGroupSettingsPanel(selectedSection, state.schema.sections, focusState);
11221
+ }
11222
+ }
11223
+ const selectedField = state.schema.sections.flatMap((s) => s.fields).find((f) => f.id === state.selectedFieldId);
11224
+ if (!selectedField) {
11225
+ panel.appendChild(createElement("div", { className: "p-4 text-center text-gray-500", text: "Select a field or section to configure" }));
11226
+ return panel;
11227
+ }
11228
+ const header = createElement("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800" });
11229
+ header.appendChild(createElement("h2", { className: "font-semibold text-gray-900 dark:text-white", text: "Field Settings" }));
11230
+ header.appendChild(createElement("button", {
11231
+ className: "text-gray-500 hover:text-gray-700",
11232
+ onclick: () => formStore.getState().selectField(null)
11233
+ }, [getIcon("X", 20)]));
11234
+ panel.appendChild(header);
11235
+ const body = createElement("div", { className: "flex-1 overflow-y-auto p-4 px-2 space-y-3", id: "config-panel-body" });
11236
+ const labelGroup = createElement("div");
11237
+ labelGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Label" }));
11238
+ labelGroup.appendChild(createElement("input", {
11239
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
11240
+ value: selectedField.label,
11241
+ "data-focus-id": `field-label-${selectedField.id}`,
11242
+ oninput: (e) => {
11243
+ const fieldId2 = selectedField.id;
11244
+ const value = e.target.value;
11245
+ const existing = labelUpdateTimeouts.get(fieldId2);
11246
+ if (existing)
11247
+ clearTimeout(existing);
11248
+ const timeoutId = setTimeout(() => {
11249
+ labelUpdateTimeouts.delete(fieldId2);
11250
+ formStore.getState().updateField(fieldId2, { label: value });
11251
+ }, LABEL_DEBOUNCE_MS);
11252
+ labelUpdateTimeouts.set(fieldId2, timeoutId);
11253
+ }
11254
+ }));
11255
+ body.appendChild(labelGroup);
11256
+ if (selectedField.type === "number") {
11257
+ const valueSourceHeader = createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2 mt-4", text: "Value Source" });
11258
+ body.appendChild(valueSourceHeader);
11259
+ const valueSourceGroup = createElement("div", { className: "mb-3" });
11260
+ valueSourceGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Source" }));
11261
+ const valueSourceSelect = createElement("select", {
11262
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
11263
+ value: selectedField.valueSource || "manual",
11264
+ onchange: (e) => {
11265
+ const source = e.target.value;
11266
+ const updates = { valueSource: source };
11267
+ if (source === "manual") {
11268
+ updates.formula = void 0;
11269
+ updates.dependencies = void 0;
11270
+ } else if (source === "formula") {
11271
+ updates.formula = selectedField.formula || "";
11272
+ updates.dependencies = selectedField.dependencies || [];
11273
+ }
11274
+ formStore.getState().updateField(selectedField.id, updates);
11275
+ this.render();
11276
+ }
11277
+ });
11278
+ valueSourceSelect.appendChild(createElement("option", { value: "manual", text: "Manual", selected: (selectedField.valueSource || "manual") === "manual" }));
11279
+ valueSourceSelect.appendChild(createElement("option", { value: "formula", text: "Formula", selected: selectedField.valueSource === "formula" }));
11280
+ valueSourceGroup.appendChild(valueSourceSelect);
11281
+ body.appendChild(valueSourceGroup);
11282
+ if (selectedField.valueSource === "formula") {
11283
+ const schema = formStore.getState().schema;
11284
+ const numericFields = getNumericFieldsForFormula(schema, selectedField.id);
11285
+ const availableIds = numericFields.map((f) => f.id);
11286
+ const availableNames = numericFields.map((f) => f.fieldName);
11287
+ const formulaGroup = createElement("div", { className: "mb-3" });
11288
+ formulaGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Formula" }));
11289
+ const formulaInput = createElement("input", {
11290
+ type: "text",
11291
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent font-mono text-sm",
11292
+ value: selectedField.formula || "",
10538
11293
  placeholder: "e.g. quantity * price",
10539
11294
  "data-focus-id": `field-formula-${selectedField.id}`,
10540
11295
  oninput: (e) => {
@@ -10670,48 +11425,25 @@ var FormBuilder = class {
10670
11425
  imageGroup.appendChild(btnRow);
10671
11426
  body.appendChild(imageGroup);
10672
11427
  }
10673
- const layoutGroup = createElement("div", { className: "layout-span-group" });
10674
- const layoutLabelRow = createElement("div", { className: "flex items-center justify-between mb-2" });
10675
- layoutLabelRow.appendChild(createElement("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", text: "Grid Span" }));
10676
11428
  const currentSpan = selectedField.layout?.span !== void 0 ? selectedField.layout.span : Math.max(1, Math.min(12, Math.round(parseWidth(selectedField.width || "100%") / 100 * 12)));
10677
- const spanValueDisplay = createElement("span", {
10678
- className: "span-value-badge px-2 py-0.5 text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-200 rounded-full",
10679
- text: `${currentSpan}/12`,
10680
- id: `span-value-${selectedField.id}`
10681
- // const widthValueDisplay = createElement('span', {
10682
- // className: 'width-value-badge px-2 py-0.5 text-xs font-semibold bg-[#019FA2] text-white dark:bg-blue-900 dark:text-blue-200 rounded-full',
10683
- // text: `${currentWidth}%`,
10684
- // id: `width-value-${selectedField.id}`
10685
- });
10686
- layoutLabelRow.appendChild(spanValueDisplay);
10687
- layoutGroup.appendChild(layoutLabelRow);
10688
- const spanButtonsContainer = createElement("div", { className: "grid grid-cols-6 gap-2 mt-2" });
10689
11429
  const fieldId = selectedField.id;
10690
- for (let span = 1; span <= 12; span++) {
10691
- const isActive = currentSpan === span;
10692
- const spanBtn = createElement("button", {
10693
- type: "button",
10694
- className: `span-preset-btn px-2 py-1.5 text-xs rounded transition-colors cursor-pointer ${isActive ? "bg-[#e7e7ff] text-[#635bff] font-semibold" : "bg-white border-2 border-[#e7e7ff] dark:bg-gray-800 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700"}`,
10695
- text: `${span}`,
10696
- title: `${span} column${span > 1 ? "s" : ""} (${Math.round(span / 12 * 100)}%)`
10697
- });
10698
- spanBtn.addEventListener("click", (e) => {
10699
- e.preventDefault();
10700
- e.stopPropagation();
10701
- const state2 = formStore.getState();
10702
- const field = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === fieldId);
10703
- if (field) {
10704
- const layout = field.layout || { row: 0, column: 0 };
10705
- state2.updateField(fieldId, {
10706
- layout: { ...layout, span },
10707
- width: Math.round(span / 12 * 100)
10708
- });
11430
+ body.appendChild(
11431
+ this.createGridSpanSelector({
11432
+ currentSpan,
11433
+ badgeElementId: `span-value-${fieldId}`,
11434
+ onSelect: (span) => {
11435
+ const st = formStore.getState();
11436
+ const field = st.schema.sections.flatMap((s) => s.fields).find((f) => f.id === fieldId);
11437
+ if (field) {
11438
+ const layout = field.layout || { row: 0, column: 0 };
11439
+ st.updateField(fieldId, {
11440
+ layout: { ...layout, span },
11441
+ width: Math.round(span / 12 * 100)
11442
+ });
11443
+ }
10709
11444
  }
10710
- });
10711
- spanButtonsContainer.appendChild(spanBtn);
10712
- }
10713
- layoutGroup.appendChild(spanButtonsContainer);
10714
- body.appendChild(layoutGroup);
11445
+ })
11446
+ );
10715
11447
  body.appendChild(this.createCheckboxField(
10716
11448
  "Required",
10717
11449
  !!selectedField.required || !!selectedField.validations?.required,
@@ -10736,6 +11468,12 @@ var FormBuilder = class {
10736
11468
  (checked) => formStore.getState().updateField(selectedField.id, { visible: checked }),
10737
11469
  `visible-${selectedField.id}`
10738
11470
  ));
11471
+ body.appendChild(this.createCheckboxField(
11472
+ "Unique",
11473
+ selectedField.isUnique === true,
11474
+ (checked) => formStore.getState().updateField(selectedField.id, { isUnique: checked }),
11475
+ `unique-${selectedField.id}`
11476
+ ));
10739
11477
  if (selectedField.type === "name_generator") {
10740
11478
  const ngHeader = createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3 mt-6", text: "Name Generator Settings" });
10741
11479
  body.appendChild(ngHeader);
@@ -10803,9 +11541,9 @@ var FormBuilder = class {
10803
11541
  oninput: (e) => formStore.getState().updateField(selectedField.id, { nameGeneratorSuffix: e.target.value })
10804
11542
  }));
10805
11543
  body.appendChild(suffixGroup);
10806
- const paddingGroup2 = createElement("div", { className: "mb-3" });
10807
- paddingGroup2.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "ID Padding" }));
10808
- paddingGroup2.appendChild(createElement("input", {
11544
+ const paddingGroup = createElement("div", { className: "mb-3" });
11545
+ paddingGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "ID Padding" }));
11546
+ paddingGroup.appendChild(createElement("input", {
10809
11547
  type: "number",
10810
11548
  className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
10811
11549
  value: String(selectedField.nameGeneratorIdPadding ?? 4),
@@ -10817,7 +11555,7 @@ var FormBuilder = class {
10817
11555
  formStore.getState().updateField(selectedField.id, { nameGeneratorIdPadding: isNaN(val) ? 4 : Math.max(1, Math.min(10, val)) });
10818
11556
  }
10819
11557
  }));
10820
- body.appendChild(paddingGroup2);
11558
+ body.appendChild(paddingGroup);
10821
11559
  }
10822
11560
  if (selectedField.type === "binary_choice") {
10823
11561
  const bcHeader = createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3 mt-6", text: "Yes/No Toggle Settings" });
@@ -11107,8 +11845,14 @@ var FormBuilder = class {
11107
11845
  this.render();
11108
11846
  }
11109
11847
  });
11110
- lookupSourceTypeSelect.appendChild(createElement("option", { value: "MODULE", text: "Module", selected: (selectedField.lookupSourceType || "MODULE") === "MODULE" }));
11111
- lookupSourceTypeSelect.appendChild(createElement("option", { value: "MASTER_TYPE", text: "Master Type", selected: selectedField.lookupSourceType === "MASTER_TYPE" }));
11848
+ const currentLookupSourceType = selectedField.lookupSourceType || "MODULE";
11849
+ LOOKUP_SOURCE_TYPE_OPTIONS.forEach((opt) => {
11850
+ lookupSourceTypeSelect.appendChild(createElement("option", {
11851
+ value: opt.value,
11852
+ text: opt.label,
11853
+ selected: currentLookupSourceType === opt.value
11854
+ }));
11855
+ });
11112
11856
  lookupSourceTypeGroup.appendChild(lookupSourceTypeSelect);
11113
11857
  body.appendChild(lookupSourceTypeGroup);
11114
11858
  if (selectedField.lookupSourceType === "MODULE") {
@@ -11147,6 +11891,42 @@ var FormBuilder = class {
11147
11891
  });
11148
11892
  lookupSourceGroup.appendChild(lookupSourceSelect);
11149
11893
  body.appendChild(lookupSourceGroup);
11894
+ } else if (selectedField.lookupSourceType === "SETTINGS") {
11895
+ const settingsEntities = this.options.settingsEntities || [];
11896
+ const lookupSourceGroup = createElement("div", { className: "mb-4" });
11897
+ lookupSourceGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Source Key" }));
11898
+ const lookupSourceSelect = createElement("select", {
11899
+ className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
11900
+ value: selectedField.lookupSource || "",
11901
+ onchange: (e) => {
11902
+ const lookupSource = e.target.value;
11903
+ formStore.getState().updateField(selectedField.id, { lookupSource: lookupSource || void 0 });
11904
+ if (lookupSource) {
11905
+ formStore.getState().updateField(selectedField.id, {
11906
+ lookupValueField: void 0,
11907
+ lookupLabelField: void 0
11908
+ });
11909
+ }
11910
+ if (this.options.onLookupSourceChange && lookupSource) {
11911
+ this.options.onLookupSourceChange({
11912
+ fieldId: selectedField.id,
11913
+ lookupSourceType: "SETTINGS",
11914
+ lookupSource
11915
+ });
11916
+ }
11917
+ this.render();
11918
+ }
11919
+ });
11920
+ lookupSourceSelect.appendChild(createElement("option", { value: "", text: "Select Settings Entity", selected: !selectedField.lookupSource }));
11921
+ settingsEntities.forEach((ent) => {
11922
+ lookupSourceSelect.appendChild(createElement("option", {
11923
+ value: ent.value,
11924
+ text: ent.label,
11925
+ selected: selectedField.lookupSource === ent.value
11926
+ }));
11927
+ });
11928
+ lookupSourceGroup.appendChild(lookupSourceSelect);
11929
+ body.appendChild(lookupSourceGroup);
11150
11930
  } else if (selectedField.lookupSourceType === "MASTER_TYPE") {
11151
11931
  const masterTypes = formStore.getState().masterTypes;
11152
11932
  const activeMasterTypes = masterTypes.filter((mt) => mt.active === true);
@@ -11989,234 +12769,7 @@ var FormBuilder = class {
11989
12769
  }
11990
12770
  }
11991
12771
  }
11992
- const cssHeader = createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3 mt-6", text: "Styling" });
11993
- body.appendChild(cssHeader);
11994
- const getStyleValue = (prop) => selectedField.css?.style?.[prop] || "";
11995
- const updateStyleProp = (prop, value) => {
11996
- const state2 = formStore.getState();
11997
- const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
11998
- if (!freshField) {
11999
- return;
12000
- }
12001
- const currentStyle = freshField.css?.style || {};
12002
- const newStyle = { ...currentStyle };
12003
- if (value) {
12004
- newStyle[prop] = value;
12005
- } else {
12006
- delete newStyle[prop];
12007
- }
12008
- const updatePayload = {
12009
- css: {
12010
- style: Object.keys(newStyle).length > 0 ? newStyle : void 0
12011
- // Do NOT spread freshField.css here - let updateField preserve class
12012
- }
12013
- };
12014
- state2.updateField(selectedField.id, updatePayload);
12015
- };
12016
- const paddingGroup = createElement("div", { className: "mb-3" });
12017
- paddingGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Padding" }));
12018
- const paddingSelect = createElement("select", {
12019
- className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-white dark:bg-gray-800 text-sm",
12020
- onchange: (e) => {
12021
- updateStyleProp("padding", e.target.value);
12022
- }
12023
- });
12024
- const paddingOptions = [
12025
- { value: "", label: "None" },
12026
- { value: "4px", label: "4px - Tight" },
12027
- { value: "8px", label: "8px - Normal" },
12028
- { value: "12px", label: "12px - Comfortable" },
12029
- { value: "16px", label: "16px - Spacious" },
12030
- { value: "24px", label: "24px - Large" }
12031
- ];
12032
- paddingOptions.forEach((opt) => {
12033
- paddingSelect.appendChild(createElement("option", { value: opt.value, text: opt.label, selected: getStyleValue("padding") === opt.value }));
12034
- });
12035
- paddingGroup.appendChild(paddingSelect);
12036
- body.appendChild(paddingGroup);
12037
- const bgColorGroup = createElement("div", { className: "mb-3" });
12038
- bgColorGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Background Color" }));
12039
- const bgColorRow = createElement("div", { className: "flex items-center gap-2" });
12040
- const bgColorInput = createElement("input", {
12041
- type: "color",
12042
- className: "w-10 h-10 rounded border border-gray-300 cursor-pointer",
12043
- value: getStyleValue("backgroundColor") || "#ffffff",
12044
- onchange: (e) => {
12045
- const color = e.target.value;
12046
- updateStyleProp("backgroundColor", color === "#ffffff" ? "" : color);
12047
- }
12048
- });
12049
- const bgColorClear = createElement("button", {
12050
- type: "button",
12051
- className: "px-2 py-1 text-xs text-gray-600 hover:text-gray-800 border border-gray-300 rounded",
12052
- text: "Clear",
12053
- onclick: () => {
12054
- bgColorInput.value = "#ffffff";
12055
- updateStyleProp("backgroundColor", "");
12056
- }
12057
- });
12058
- bgColorRow.appendChild(bgColorInput);
12059
- bgColorRow.appendChild(bgColorClear);
12060
- bgColorGroup.appendChild(bgColorRow);
12061
- body.appendChild(bgColorGroup);
12062
- const alignGroup = createElement("div", { className: "mb-3" });
12063
- alignGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Text Alignment" }));
12064
- const alignButtonsRow = createElement("div", { className: "flex gap-1" });
12065
- const alignments = [
12066
- { value: "left", icon: "AlignLeft" },
12067
- { value: "center", icon: "AlignCenter" },
12068
- { value: "right", icon: "AlignRight" }
12069
- ];
12070
- const currentAlign = getStyleValue("textAlign") || "left";
12071
- alignments.forEach((align) => {
12072
- const isActive = currentAlign === align.value;
12073
- const btn = createElement("button", {
12074
- type: "button",
12075
- className: `p-2 rounded border ${isActive ? "border-blue-500 bg-blue-50 text-blue-600" : "border-gray-300 text-gray-600 hover:bg-gray-50"}`,
12076
- title: `Align ${align.value}`,
12077
- onclick: () => {
12078
- const newValue = align.value === "left" ? "" : align.value;
12079
- updateStyleProp("textAlign", newValue);
12080
- }
12081
- }, [getIcon(align.icon, 16)]);
12082
- alignButtonsRow.appendChild(btn);
12083
- });
12084
- alignGroup.appendChild(alignButtonsRow);
12085
- body.appendChild(alignGroup);
12086
- const cssClassGroup = createElement("div", { className: "mb-3" });
12087
- cssClassGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Custom CSS Class" }));
12088
- cssClassGroup.appendChild(createElement("input", {
12089
- type: "text",
12090
- className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent text-sm",
12091
- value: selectedField.css?.class || "",
12092
- placeholder: "e.g. my-custom-class",
12093
- "data-focus-id": `field-css-class-${selectedField.id}`,
12094
- oninput: (e) => {
12095
- const cssClass = e.target.value;
12096
- const state2 = formStore.getState();
12097
- const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
12098
- if (!freshField)
12099
- return;
12100
- state2.updateField(selectedField.id, {
12101
- css: {
12102
- class: cssClass || void 0
12103
- // Do NOT spread freshField.css here - let updateField preserve style
12104
- }
12105
- });
12106
- }
12107
- }));
12108
- body.appendChild(cssClassGroup);
12109
- const isPanelExpanded = advancedCssPanelState.get(selectedField.id) || false;
12110
- const advancedToggleGroup = createElement("div", { className: "mb-3" });
12111
- const advancedToggle = createElement("button", {
12112
- type: "button",
12113
- className: "text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1",
12114
- onclick: () => {
12115
- const advancedPanel2 = document.getElementById(`advanced-css-${selectedField.id}`);
12116
- if (advancedPanel2) {
12117
- advancedPanel2.classList.toggle("hidden");
12118
- const isHidden = advancedPanel2.classList.contains("hidden");
12119
- advancedToggle.textContent = isHidden ? "\u25B6 Show Advanced CSS" : "\u25BC Hide Advanced CSS";
12120
- advancedCssPanelState.set(selectedField.id, !isHidden);
12121
- }
12122
- }
12123
- });
12124
- advancedToggle.textContent = isPanelExpanded ? "\u25BC Hide Advanced CSS" : "\u25B6 Show Advanced CSS";
12125
- advancedToggleGroup.appendChild(advancedToggle);
12126
- body.appendChild(advancedToggleGroup);
12127
- const advancedPanel = createElement("div", {
12128
- className: isPanelExpanded ? "mb-3" : "mb-3 hidden",
12129
- id: `advanced-css-${selectedField.id}`
12130
- });
12131
- advancedPanel.appendChild(createElement("label", { className: "block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1", text: "Raw CSS Style (JSON)" }));
12132
- const cssStyleId = `field-css-style-${selectedField.id}`;
12133
- const freshState = formStore.getState();
12134
- const freshFieldForInit = freshState.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
12135
- const fieldForCssStyle = freshFieldForInit || selectedField;
12136
- let initialCssStyleValue = fieldForCssStyle.css?.style ? JSON.stringify(fieldForCssStyle.css.style, null, 2) : "";
12137
- const preservedValue = focusState?.id === cssStyleId ? focusState.value : void 0;
12138
- if (preservedValue !== void 0) {
12139
- initialCssStyleValue = preservedValue;
12140
- }
12141
- const cssStyleTextarea = createElement("textarea", {
12142
- className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent text-xs font-mono",
12143
- rows: 3,
12144
- placeholder: '{"padding": "8px", "backgroundColor": "#f0f0f0"}',
12145
- "data-focus-id": cssStyleId,
12146
- "data-was-focused": "false",
12147
- onfocus: (e) => {
12148
- const textarea = e.target;
12149
- textarea.setAttribute("data-was-focused", "true");
12150
- const state2 = formStore.getState();
12151
- const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
12152
- if (!textarea.value.trim() && freshField?.css?.style && Object.keys(freshField.css.style).length > 0) {
12153
- const styleString = JSON.stringify(freshField.css.style, null, 2);
12154
- textarea.value = styleString;
12155
- }
12156
- },
12157
- oninput: (e) => {
12158
- const styleText = e.target.value;
12159
- const state2 = formStore.getState();
12160
- const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
12161
- if (!freshField)
12162
- return;
12163
- try {
12164
- if (styleText.trim()) {
12165
- const styleObj = JSON.parse(styleText);
12166
- state2.updateField(selectedField.id, {
12167
- css: { ...freshField.css, style: styleObj }
12168
- });
12169
- }
12170
- } catch (err) {
12171
- }
12172
- },
12173
- onblur: (e) => {
12174
- const textarea = e.target;
12175
- const styleText = textarea.value;
12176
- const wasFocused = textarea.getAttribute("data-was-focused") === "true";
12177
- if (!wasFocused) {
12178
- return;
12179
- }
12180
- textarea.setAttribute("data-was-focused", "false");
12181
- setTimeout(() => {
12182
- if (!document.body.contains(textarea)) {
12183
- return;
12184
- }
12185
- const advPanel = document.getElementById(`advanced-css-${selectedField.id}`);
12186
- const isPanelVisible = advPanel && !advPanel.classList.contains("hidden");
12187
- if (!isPanelVisible) {
12188
- return;
12189
- }
12190
- const state2 = formStore.getState();
12191
- const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
12192
- if (!freshField)
12193
- return;
12194
- try {
12195
- if (styleText.trim()) {
12196
- const styleObj = JSON.parse(styleText);
12197
- state2.updateField(selectedField.id, {
12198
- css: { ...freshField.css, style: styleObj }
12199
- });
12200
- } else {
12201
- if (freshField.css?.style && Object.keys(freshField.css.style).length > 0) {
12202
- textarea.value = JSON.stringify(freshField.css.style, null, 2);
12203
- } else {
12204
- state2.updateField(selectedField.id, {
12205
- css: { ...freshField.css, style: void 0 }
12206
- });
12207
- }
12208
- }
12209
- } catch (err) {
12210
- if (freshField.css?.style) {
12211
- textarea.value = JSON.stringify(freshField.css.style, null, 2);
12212
- }
12213
- }
12214
- }, 0);
12215
- }
12216
- });
12217
- cssStyleTextarea.value = initialCssStyleValue;
12218
- advancedPanel.appendChild(cssStyleTextarea);
12219
- body.appendChild(advancedPanel);
12772
+ this.appendSharedStylingSection(body, { kind: "field", fieldId: selectedField.id }, focusState);
12220
12773
  panel.appendChild(body);
12221
12774
  return panel;
12222
12775
  }
@@ -12273,6 +12826,7 @@ sortablejs/modular/sortable.esm.js:
12273
12826
  exports.FormBuilder = FormBuilder;
12274
12827
  exports.FormRenderer = FormRenderer;
12275
12828
  exports.FormSchemaValidation = FormSchemaValidation;
12829
+ exports.LOOKUP_SOURCE_TYPE_OPTIONS = LOOKUP_SOURCE_TYPE_OPTIONS;
12276
12830
  exports.builderToPlatform = builderToPlatform;
12277
12831
  exports.cleanFormSchema = cleanFormSchema;
12278
12832
  exports.convertValidationObjectToArray = convertValidationObjectToArray;