form-builder-pro 1.3.8 → 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.css +49 -9
- package/dist/index.d.mts +35 -2
- package/dist/index.d.ts +35 -2
- package/dist/index.js +973 -477
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +973 -477
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4773,7 +4773,30 @@ function transformField(field) {
|
|
|
4773
4773
|
var cleanFormSchema = (schema) => {
|
|
4774
4774
|
const cleanField = transformField;
|
|
4775
4775
|
let sections = [];
|
|
4776
|
-
if (schema.
|
|
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) {
|
|
4777
4800
|
sections = [{
|
|
4778
4801
|
id: schema.id ? `section-${schema.id}` : "section-1",
|
|
4779
4802
|
title: schema.formName || schema.title || "Form Fields",
|
|
@@ -4781,16 +4804,6 @@ var cleanFormSchema = (schema) => {
|
|
|
4781
4804
|
order: 0,
|
|
4782
4805
|
isExpanded: true
|
|
4783
4806
|
}];
|
|
4784
|
-
} else if (schema.sections && Array.isArray(schema.sections)) {
|
|
4785
|
-
sections = schema.sections;
|
|
4786
|
-
} else if (schema.groups && Array.isArray(schema.groups)) {
|
|
4787
|
-
sections = schema.groups.map((group, index2) => ({
|
|
4788
|
-
id: group.id || `section-${index2}`,
|
|
4789
|
-
title: group.title || group.name || `Section ${index2 + 1}`,
|
|
4790
|
-
fields: group.fields || [],
|
|
4791
|
-
order: group.order !== void 0 ? group.order : index2,
|
|
4792
|
-
isExpanded: group.isExpanded !== void 0 ? group.isExpanded : true
|
|
4793
|
-
}));
|
|
4794
4807
|
}
|
|
4795
4808
|
return {
|
|
4796
4809
|
id: schema.id,
|
|
@@ -4798,26 +4811,37 @@ var cleanFormSchema = (schema) => {
|
|
|
4798
4811
|
formName: schema.formName || schema.formId || schema.id,
|
|
4799
4812
|
layout: schema.layout || { type: "grid", columns: 12, gap: "16px" },
|
|
4800
4813
|
// Preserve form-level layout or set default
|
|
4801
|
-
sections: sections.map((section, sectionIndex) =>
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
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
|
+
})
|
|
4821
4845
|
};
|
|
4822
4846
|
};
|
|
4823
4847
|
function convertValidationArrayToObject(validation) {
|
|
@@ -4862,7 +4886,7 @@ function convertWidthToSpan(width, totalColumns = 12) {
|
|
|
4862
4886
|
const widthNum = parseWidth(width);
|
|
4863
4887
|
return Math.max(1, Math.min(12, Math.round(widthNum / 100 * totalColumns)));
|
|
4864
4888
|
}
|
|
4865
|
-
function fieldToPayload(field) {
|
|
4889
|
+
function fieldToPayload(field, opts) {
|
|
4866
4890
|
let outputType = field.type;
|
|
4867
4891
|
let outputValidations = field.validations ? { ...field.validations } : void 0;
|
|
4868
4892
|
if (field.type === "text" && isEmailLikeField(field)) {
|
|
@@ -4881,6 +4905,9 @@ function fieldToPayload(field) {
|
|
|
4881
4905
|
// Model key for binding (API / host app)
|
|
4882
4906
|
order: field.order !== void 0 ? field.order : 0
|
|
4883
4907
|
};
|
|
4908
|
+
if (opts?.groupId) {
|
|
4909
|
+
payload.groupId = opts.groupId;
|
|
4910
|
+
}
|
|
4884
4911
|
if (field.layout?.span !== void 0) {
|
|
4885
4912
|
payload.layout = {
|
|
4886
4913
|
row: field.layout.row ?? 0,
|
|
@@ -5036,20 +5063,48 @@ function fieldToPayload(field) {
|
|
|
5036
5063
|
}
|
|
5037
5064
|
return payload;
|
|
5038
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
|
+
}
|
|
5039
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
|
+
});
|
|
5040
5101
|
return {
|
|
5041
5102
|
id: builderSchema.id,
|
|
5042
5103
|
title: builderSchema.title,
|
|
5043
5104
|
formName: builderSchema.formName,
|
|
5044
5105
|
layout: builderSchema.layout || { type: "grid", columns: 12, gap: "16px" },
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
title: section.title,
|
|
5048
|
-
order: section.order !== void 0 ? section.order : sectionIndex,
|
|
5049
|
-
layout: section.layout || { type: "grid", columns: section.columns || 12, gap: "16px" },
|
|
5050
|
-
css: section.css,
|
|
5051
|
-
fields: section.fields.map(fieldToPayload)
|
|
5052
|
-
}))
|
|
5106
|
+
fields: fieldsFlat,
|
|
5107
|
+
groups
|
|
5053
5108
|
};
|
|
5054
5109
|
};
|
|
5055
5110
|
var platformToBuilder = (platformSchema) => {
|
|
@@ -5070,6 +5125,77 @@ function getValidationConfigForAngular(validations) {
|
|
|
5070
5125
|
};
|
|
5071
5126
|
}
|
|
5072
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
|
+
|
|
5073
5199
|
// src/core/useFormStore.ts
|
|
5074
5200
|
var INITIAL_SCHEMA = {
|
|
5075
5201
|
id: "form_1",
|
|
@@ -5080,6 +5206,7 @@ var INITIAL_SCHEMA = {
|
|
|
5080
5206
|
var formStore = createStore((set, get) => ({
|
|
5081
5207
|
schema: INITIAL_SCHEMA,
|
|
5082
5208
|
selectedFieldId: null,
|
|
5209
|
+
selectedSectionId: null,
|
|
5083
5210
|
history: [INITIAL_SCHEMA],
|
|
5084
5211
|
historyIndex: 0,
|
|
5085
5212
|
isPreviewMode: false,
|
|
@@ -5397,6 +5524,8 @@ var formStore = createStore((set, get) => ({
|
|
|
5397
5524
|
importSection: (section) => {
|
|
5398
5525
|
const { schema, history, historyIndex } = get();
|
|
5399
5526
|
const clonedSection = cloneSection(section);
|
|
5527
|
+
clonedSection.parentGroupId = null;
|
|
5528
|
+
clonedSection.order = getNextRootOrder(schema.sections);
|
|
5400
5529
|
const newSchema = { ...schema, sections: [...schema.sections, clonedSection] };
|
|
5401
5530
|
set({
|
|
5402
5531
|
schema: newSchema,
|
|
@@ -5406,15 +5535,26 @@ var formStore = createStore((set, get) => ({
|
|
|
5406
5535
|
},
|
|
5407
5536
|
addSection: () => {
|
|
5408
5537
|
const { schema, history, historyIndex } = get();
|
|
5538
|
+
const order = getNextRootOrder(schema.sections);
|
|
5409
5539
|
const newSection = {
|
|
5410
5540
|
id: generateId(),
|
|
5411
|
-
title: `Section ${
|
|
5541
|
+
title: `Section ${order + 1}`,
|
|
5542
|
+
name: `Section ${order + 1}`,
|
|
5412
5543
|
fields: [],
|
|
5413
5544
|
columns: 1,
|
|
5414
5545
|
// Legacy - prefer layout.columns
|
|
5415
5546
|
layout: { type: "grid", columns: 12, gap: "16px" },
|
|
5416
|
-
order
|
|
5417
|
-
|
|
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
|
|
5418
5558
|
};
|
|
5419
5559
|
const newSchema = { ...schema, sections: [...schema.sections, newSection] };
|
|
5420
5560
|
set({
|
|
@@ -5424,24 +5564,74 @@ var formStore = createStore((set, get) => ({
|
|
|
5424
5564
|
});
|
|
5425
5565
|
},
|
|
5426
5566
|
removeSection: (sectionId) => {
|
|
5427
|
-
const { schema, history, historyIndex } = get();
|
|
5567
|
+
const { schema, history, historyIndex, selectedSectionId } = get();
|
|
5428
5568
|
const newSchema = {
|
|
5429
5569
|
...schema,
|
|
5430
|
-
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)
|
|
5431
5571
|
};
|
|
5432
5572
|
set({
|
|
5433
5573
|
schema: newSchema,
|
|
5574
|
+
selectedSectionId: selectedSectionId === sectionId ? null : selectedSectionId,
|
|
5434
5575
|
history: [...history.slice(0, historyIndex + 1), newSchema],
|
|
5435
5576
|
historyIndex: historyIndex + 1
|
|
5436
5577
|
});
|
|
5437
5578
|
},
|
|
5438
5579
|
updateSection: (sectionId, updates) => {
|
|
5439
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
|
+
}
|
|
5440
5594
|
const newSchema = {
|
|
5441
5595
|
...schema,
|
|
5442
|
-
sections: schema.sections.map(
|
|
5443
|
-
(s
|
|
5444
|
-
|
|
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
|
+
})
|
|
5445
5635
|
};
|
|
5446
5636
|
set({
|
|
5447
5637
|
schema: newSchema,
|
|
@@ -5451,14 +5641,23 @@ var formStore = createStore((set, get) => ({
|
|
|
5451
5641
|
},
|
|
5452
5642
|
moveSection: (oldIndex2, newIndex2) => {
|
|
5453
5643
|
const { schema, history, historyIndex } = get();
|
|
5454
|
-
const
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
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
|
+
];
|
|
5462
5661
|
const newSchema = { ...schema, sections: newSections };
|
|
5463
5662
|
set({
|
|
5464
5663
|
schema: newSchema,
|
|
@@ -5604,7 +5803,14 @@ var formStore = createStore((set, get) => ({
|
|
|
5604
5803
|
historyIndex: historyIndex + 1
|
|
5605
5804
|
});
|
|
5606
5805
|
},
|
|
5607
|
-
selectField: (fieldId) => set({
|
|
5806
|
+
selectField: (fieldId) => set({
|
|
5807
|
+
selectedFieldId: fieldId,
|
|
5808
|
+
selectedSectionId: fieldId ? null : null
|
|
5809
|
+
}),
|
|
5810
|
+
selectSection: (sectionId) => set({
|
|
5811
|
+
selectedSectionId: sectionId,
|
|
5812
|
+
selectedFieldId: null
|
|
5813
|
+
}),
|
|
5608
5814
|
moveField: (fieldId, targetSectionId, newIndex2) => {
|
|
5609
5815
|
const { schema, history, historyIndex } = get();
|
|
5610
5816
|
let field;
|
|
@@ -9572,37 +9778,70 @@ var FieldWrapper = class {
|
|
|
9572
9778
|
};
|
|
9573
9779
|
|
|
9574
9780
|
// src/builder/Section.ts
|
|
9575
|
-
var
|
|
9576
|
-
|
|
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) {
|
|
9577
9785
|
__publicField(this, "container");
|
|
9578
9786
|
__publicField(this, "section");
|
|
9787
|
+
__publicField(this, "allSections");
|
|
9579
9788
|
__publicField(this, "isSelectedField");
|
|
9789
|
+
__publicField(this, "selectedSectionId");
|
|
9790
|
+
__publicField(this, "depth");
|
|
9580
9791
|
this.section = section;
|
|
9792
|
+
this.allSections = allSections;
|
|
9581
9793
|
this.isSelectedField = isSelectedField;
|
|
9794
|
+
this.selectedSectionId = selectedSectionId;
|
|
9795
|
+
this.depth = depth;
|
|
9582
9796
|
this.container = this.render();
|
|
9583
9797
|
}
|
|
9584
9798
|
getElement() {
|
|
9585
9799
|
return this.container;
|
|
9586
9800
|
}
|
|
9587
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";
|
|
9588
9805
|
const sectionEl = createElement("div", {
|
|
9589
|
-
className:
|
|
9590
|
-
"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
|
|
9591
9809
|
});
|
|
9592
|
-
const header = createElement("div", {
|
|
9593
|
-
|
|
9594
|
-
|
|
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
|
+
}
|
|
9815
|
+
});
|
|
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);
|
|
9595
9821
|
headerLeft.appendChild(createElement("input", {
|
|
9596
|
-
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",
|
|
9597
9823
|
value: this.section.title,
|
|
9598
9824
|
"data-focus-id": `section-title-${this.section.id}`,
|
|
9599
|
-
|
|
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
|
+
}
|
|
9600
9838
|
}));
|
|
9601
9839
|
header.appendChild(headerLeft);
|
|
9602
9840
|
const actions = createElement("div", { className: "flex items-center space-x-1" });
|
|
9603
9841
|
const colSelect = createElement("select", {
|
|
9604
9842
|
className: "text-xs border rounded bg-transparent mr-2 p-1 text-gray-600",
|
|
9605
9843
|
title: "Section Columns",
|
|
9844
|
+
onclick: (e) => e.stopPropagation(),
|
|
9606
9845
|
onchange: (e) => {
|
|
9607
9846
|
formStore.getState().updateSection(this.section.id, { columns: parseInt(e.target.value) });
|
|
9608
9847
|
}
|
|
@@ -9613,7 +9852,8 @@ var Section = class {
|
|
|
9613
9852
|
actions.appendChild(colSelect);
|
|
9614
9853
|
actions.appendChild(createElement("button", {
|
|
9615
9854
|
className: "text-gray-600 hover:text-red-500 transition-colors p-1",
|
|
9616
|
-
onclick: () => {
|
|
9855
|
+
onclick: (e) => {
|
|
9856
|
+
e.stopPropagation();
|
|
9617
9857
|
if (confirm("Delete this section and all its fields?")) {
|
|
9618
9858
|
formStore.getState().removeSection(this.section.id);
|
|
9619
9859
|
}
|
|
@@ -9657,6 +9897,25 @@ var Section = class {
|
|
|
9657
9897
|
fieldsGrid.appendChild(FieldWrapper.render(field, isSelected));
|
|
9658
9898
|
});
|
|
9659
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
|
+
}
|
|
9660
9919
|
this.initFieldSortable(fieldsGrid);
|
|
9661
9920
|
return sectionEl;
|
|
9662
9921
|
}
|
|
@@ -9674,7 +9933,6 @@ var Section = class {
|
|
|
9674
9933
|
onAdd: (evt) => {
|
|
9675
9934
|
const item = evt.item;
|
|
9676
9935
|
const type = item.getAttribute("data-type");
|
|
9677
|
-
evt.from.getAttribute("data-section-id");
|
|
9678
9936
|
const toSectionId = this.section.id;
|
|
9679
9937
|
if (type && !item.hasAttribute("data-id")) {
|
|
9680
9938
|
item.remove();
|
|
@@ -9718,12 +9976,14 @@ var Section = class {
|
|
|
9718
9976
|
|
|
9719
9977
|
// src/builder/SectionList.ts
|
|
9720
9978
|
var SectionList = class {
|
|
9721
|
-
constructor(schema, selectedFieldId) {
|
|
9979
|
+
constructor(schema, selectedFieldId, selectedSectionId) {
|
|
9722
9980
|
__publicField(this, "container");
|
|
9723
9981
|
__publicField(this, "schema");
|
|
9724
9982
|
__publicField(this, "selectedFieldId");
|
|
9983
|
+
__publicField(this, "selectedSectionId");
|
|
9725
9984
|
this.schema = schema;
|
|
9726
9985
|
this.selectedFieldId = selectedFieldId;
|
|
9986
|
+
this.selectedSectionId = selectedSectionId;
|
|
9727
9987
|
this.container = this.render();
|
|
9728
9988
|
}
|
|
9729
9989
|
getElement() {
|
|
@@ -9746,13 +10006,18 @@ var SectionList = class {
|
|
|
9746
10006
|
placeholder.appendChild(createElement("div", { className: "text-sm text-gray-400", text: 'Drag fields from the sidebar or click "Add Section" below.' }));
|
|
9747
10007
|
listContainer.appendChild(placeholder);
|
|
9748
10008
|
}
|
|
9749
|
-
const
|
|
10009
|
+
const rootSections = getRootSections(this.schema.sections).sort((a, b) => {
|
|
9750
10010
|
const orderA = a.order !== void 0 ? a.order : 0;
|
|
9751
10011
|
const orderB = b.order !== void 0 ? b.order : 0;
|
|
9752
10012
|
return orderA - orderB;
|
|
9753
10013
|
});
|
|
9754
|
-
|
|
9755
|
-
const sectionComponent = new Section(
|
|
10014
|
+
rootSections.forEach((section) => {
|
|
10015
|
+
const sectionComponent = new Section(
|
|
10016
|
+
section,
|
|
10017
|
+
(id) => id === this.selectedFieldId,
|
|
10018
|
+
this.selectedSectionId,
|
|
10019
|
+
this.schema.sections
|
|
10020
|
+
);
|
|
9756
10021
|
listContainer.appendChild(sectionComponent.getElement());
|
|
9757
10022
|
});
|
|
9758
10023
|
this.initSectionSortable(listContainer, hasNoSections);
|
|
@@ -10001,7 +10266,17 @@ var FormBuilder = class {
|
|
|
10001
10266
|
const schemaHash = JSON.stringify({
|
|
10002
10267
|
sections: state.schema.sections.map((s) => ({
|
|
10003
10268
|
id: s.id,
|
|
10004
|
-
|
|
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,
|
|
10005
10280
|
fields: s.fields.map((f) => ({
|
|
10006
10281
|
id: f.id,
|
|
10007
10282
|
type: f.type,
|
|
@@ -10012,6 +10287,7 @@ var FormBuilder = class {
|
|
|
10012
10287
|
}))
|
|
10013
10288
|
})),
|
|
10014
10289
|
selectedField: state.selectedFieldId,
|
|
10290
|
+
selectedSection: state.selectedSectionId,
|
|
10015
10291
|
isPreviewMode: state.isPreviewMode
|
|
10016
10292
|
});
|
|
10017
10293
|
if (schemaHash !== this.lastRenderedSchemaHash || previewModeChanged) {
|
|
@@ -10436,7 +10712,7 @@ var FormBuilder = class {
|
|
|
10436
10712
|
}
|
|
10437
10713
|
});
|
|
10438
10714
|
inner.appendChild(formNameInput);
|
|
10439
|
-
const sectionList = new SectionList(state.schema, state.selectedFieldId);
|
|
10715
|
+
const sectionList = new SectionList(state.schema, state.selectedFieldId, state.selectedSectionId);
|
|
10440
10716
|
inner.appendChild(sectionList.getElement());
|
|
10441
10717
|
const addSectionBtn = createElement("button", {
|
|
10442
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",
|
|
@@ -10472,142 +10748,612 @@ var FormBuilder = class {
|
|
|
10472
10748
|
});
|
|
10473
10749
|
return container;
|
|
10474
10750
|
}
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
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);
|
|
10481
10780
|
}
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
const
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
"data-focus-id": `field-label-${selectedField.id}`,
|
|
10496
|
-
oninput: (e) => {
|
|
10497
|
-
const fieldId2 = selectedField.id;
|
|
10498
|
-
const value = e.target.value;
|
|
10499
|
-
const existing = labelUpdateTimeouts.get(fieldId2);
|
|
10500
|
-
if (existing)
|
|
10501
|
-
clearTimeout(existing);
|
|
10502
|
-
const timeoutId = setTimeout(() => {
|
|
10503
|
-
labelUpdateTimeouts.delete(fieldId2);
|
|
10504
|
-
formStore.getState().updateField(fieldId2, { label: value });
|
|
10505
|
-
}, LABEL_DEBOUNCE_MS);
|
|
10506
|
-
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);
|
|
10507
10794
|
}
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
const
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
const
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
formStore.getState().updateField(selectedField.id, updates);
|
|
10529
|
-
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
|
|
10530
10815
|
}
|
|
10531
|
-
}
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
if (selectedField.valueSource === "formula") {
|
|
10537
|
-
const schema = formStore.getState().schema;
|
|
10538
|
-
const numericFields = getNumericFieldsForFormula(schema, selectedField.id);
|
|
10539
|
-
const availableIds = numericFields.map((f) => f.id);
|
|
10540
|
-
const availableNames = numericFields.map((f) => f.fieldName);
|
|
10541
|
-
const formulaGroup = createElement("div", { className: "mb-3" });
|
|
10542
|
-
formulaGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Formula" }));
|
|
10543
|
-
const formulaInput = createElement("input", {
|
|
10544
|
-
type: "text",
|
|
10545
|
-
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent font-mono text-sm",
|
|
10546
|
-
value: selectedField.formula || "",
|
|
10547
|
-
placeholder: "e.g. quantity * price",
|
|
10548
|
-
"data-focus-id": `field-formula-${selectedField.id}`,
|
|
10549
|
-
oninput: (e) => {
|
|
10550
|
-
const formula = e.target.value.trim();
|
|
10551
|
-
const deps = parseFormulaDependencies(formula);
|
|
10552
|
-
const validation = validateFormula(formula, availableIds, availableNames, selectedField.id);
|
|
10553
|
-
const hasCircular = deps.length > 0 && detectCircularDependency(schema, selectedField.id, formula, deps);
|
|
10554
|
-
const errEl = formulaGroup.querySelector(".formula-error");
|
|
10555
|
-
if (errEl) {
|
|
10556
|
-
if (validation.valid && !hasCircular) {
|
|
10557
|
-
errEl.textContent = "";
|
|
10558
|
-
errEl.classList.add("hidden");
|
|
10559
|
-
} else {
|
|
10560
|
-
errEl.textContent = !validation.valid ? validation.error : "Circular dependency detected";
|
|
10561
|
-
errEl.classList.remove("hidden");
|
|
10562
|
-
}
|
|
10563
|
-
}
|
|
10564
|
-
formStore.getState().updateField(selectedField.id, { formula, dependencies: deps });
|
|
10565
|
-
}
|
|
10566
|
-
});
|
|
10567
|
-
formulaGroup.appendChild(formulaInput);
|
|
10568
|
-
const formulaError = createElement("div", { className: "text-xs text-red-600 dark:text-red-400 mt-1 formula-error hidden" });
|
|
10569
|
-
formulaGroup.appendChild(formulaError);
|
|
10570
|
-
body.appendChild(formulaGroup);
|
|
10571
|
-
const insertGroup = createElement("div", { className: "mb-3" });
|
|
10572
|
-
insertGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Insert Field" }));
|
|
10573
|
-
const insertSelect = createElement("select", {
|
|
10574
|
-
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10575
|
-
onchange: (e) => {
|
|
10576
|
-
const sel = e.target;
|
|
10577
|
-
const ref = sel.value;
|
|
10578
|
-
if (!ref)
|
|
10579
|
-
return;
|
|
10580
|
-
const current = selectedField.formula || "";
|
|
10581
|
-
const insert = current ? ` ${ref} ` : ref;
|
|
10582
|
-
const newFormula = current + insert;
|
|
10583
|
-
formStore.getState().updateField(selectedField.id, {
|
|
10584
|
-
formula: newFormula,
|
|
10585
|
-
dependencies: parseFormulaDependencies(newFormula)
|
|
10586
|
-
});
|
|
10587
|
-
formulaInput.value = newFormula;
|
|
10588
|
-
sel.value = "";
|
|
10589
|
-
this.render();
|
|
10590
|
-
}
|
|
10591
|
-
});
|
|
10592
|
-
insertSelect.appendChild(createElement("option", { value: "", text: "Select field to insert...", selected: true }));
|
|
10593
|
-
numericFields.forEach((f) => {
|
|
10594
|
-
const ref = f.fieldName !== f.id ? f.fieldName : f.id;
|
|
10595
|
-
insertSelect.appendChild(createElement("option", { value: ref, text: `${f.label} (${ref})` }));
|
|
10596
|
-
});
|
|
10597
|
-
insertGroup.appendChild(insertSelect);
|
|
10598
|
-
body.appendChild(insertGroup);
|
|
10599
|
-
const hintEl = createElement("p", {
|
|
10600
|
-
className: "text-xs text-gray-500 dark:text-gray-400 mb-2",
|
|
10601
|
-
text: "Use +, -, *, / and parentheses. Reference fields by their name or ID."
|
|
10602
|
-
});
|
|
10603
|
-
body.appendChild(hintEl);
|
|
10816
|
+
};
|
|
10817
|
+
if (target.kind === "field") {
|
|
10818
|
+
formStore.getState().updateField(target.fieldId, updatePayload);
|
|
10819
|
+
} else {
|
|
10820
|
+
formStore.getState().updateSection(target.sectionId, updatePayload);
|
|
10604
10821
|
}
|
|
10605
|
-
}
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
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 || "",
|
|
11293
|
+
placeholder: "e.g. quantity * price",
|
|
11294
|
+
"data-focus-id": `field-formula-${selectedField.id}`,
|
|
11295
|
+
oninput: (e) => {
|
|
11296
|
+
const formula = e.target.value.trim();
|
|
11297
|
+
const deps = parseFormulaDependencies(formula);
|
|
11298
|
+
const validation = validateFormula(formula, availableIds, availableNames, selectedField.id);
|
|
11299
|
+
const hasCircular = deps.length > 0 && detectCircularDependency(schema, selectedField.id, formula, deps);
|
|
11300
|
+
const errEl = formulaGroup.querySelector(".formula-error");
|
|
11301
|
+
if (errEl) {
|
|
11302
|
+
if (validation.valid && !hasCircular) {
|
|
11303
|
+
errEl.textContent = "";
|
|
11304
|
+
errEl.classList.add("hidden");
|
|
11305
|
+
} else {
|
|
11306
|
+
errEl.textContent = !validation.valid ? validation.error : "Circular dependency detected";
|
|
11307
|
+
errEl.classList.remove("hidden");
|
|
11308
|
+
}
|
|
11309
|
+
}
|
|
11310
|
+
formStore.getState().updateField(selectedField.id, { formula, dependencies: deps });
|
|
11311
|
+
}
|
|
11312
|
+
});
|
|
11313
|
+
formulaGroup.appendChild(formulaInput);
|
|
11314
|
+
const formulaError = createElement("div", { className: "text-xs text-red-600 dark:text-red-400 mt-1 formula-error hidden" });
|
|
11315
|
+
formulaGroup.appendChild(formulaError);
|
|
11316
|
+
body.appendChild(formulaGroup);
|
|
11317
|
+
const insertGroup = createElement("div", { className: "mb-3" });
|
|
11318
|
+
insertGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Insert Field" }));
|
|
11319
|
+
const insertSelect = createElement("select", {
|
|
11320
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11321
|
+
onchange: (e) => {
|
|
11322
|
+
const sel = e.target;
|
|
11323
|
+
const ref = sel.value;
|
|
11324
|
+
if (!ref)
|
|
11325
|
+
return;
|
|
11326
|
+
const current = selectedField.formula || "";
|
|
11327
|
+
const insert = current ? ` ${ref} ` : ref;
|
|
11328
|
+
const newFormula = current + insert;
|
|
11329
|
+
formStore.getState().updateField(selectedField.id, {
|
|
11330
|
+
formula: newFormula,
|
|
11331
|
+
dependencies: parseFormulaDependencies(newFormula)
|
|
11332
|
+
});
|
|
11333
|
+
formulaInput.value = newFormula;
|
|
11334
|
+
sel.value = "";
|
|
11335
|
+
this.render();
|
|
11336
|
+
}
|
|
11337
|
+
});
|
|
11338
|
+
insertSelect.appendChild(createElement("option", { value: "", text: "Select field to insert...", selected: true }));
|
|
11339
|
+
numericFields.forEach((f) => {
|
|
11340
|
+
const ref = f.fieldName !== f.id ? f.fieldName : f.id;
|
|
11341
|
+
insertSelect.appendChild(createElement("option", { value: ref, text: `${f.label} (${ref})` }));
|
|
11342
|
+
});
|
|
11343
|
+
insertGroup.appendChild(insertSelect);
|
|
11344
|
+
body.appendChild(insertGroup);
|
|
11345
|
+
const hintEl = createElement("p", {
|
|
11346
|
+
className: "text-xs text-gray-500 dark:text-gray-400 mb-2",
|
|
11347
|
+
text: "Use +, -, *, / and parentheses. Reference fields by their name or ID."
|
|
11348
|
+
});
|
|
11349
|
+
body.appendChild(hintEl);
|
|
11350
|
+
}
|
|
11351
|
+
}
|
|
11352
|
+
if (selectedField.type !== "image") {
|
|
11353
|
+
const placeholderGroup = createElement("div");
|
|
11354
|
+
placeholderGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Placeholder" }));
|
|
11355
|
+
placeholderGroup.appendChild(createElement("input", {
|
|
11356
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10611
11357
|
value: selectedField.placeholder || "",
|
|
10612
11358
|
"data-focus-id": `field-placeholder-${selectedField.id}`,
|
|
10613
11359
|
oninput: (e) => {
|
|
@@ -10679,48 +11425,25 @@ var FormBuilder = class {
|
|
|
10679
11425
|
imageGroup.appendChild(btnRow);
|
|
10680
11426
|
body.appendChild(imageGroup);
|
|
10681
11427
|
}
|
|
10682
|
-
const layoutGroup = createElement("div", { className: "layout-span-group" });
|
|
10683
|
-
const layoutLabelRow = createElement("div", { className: "flex items-center justify-between mb-2" });
|
|
10684
|
-
layoutLabelRow.appendChild(createElement("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", text: "Grid Span" }));
|
|
10685
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)));
|
|
10686
|
-
const spanValueDisplay = createElement("span", {
|
|
10687
|
-
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",
|
|
10688
|
-
text: `${currentSpan}/12`,
|
|
10689
|
-
id: `span-value-${selectedField.id}`
|
|
10690
|
-
// const widthValueDisplay = createElement('span', {
|
|
10691
|
-
// 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',
|
|
10692
|
-
// text: `${currentWidth}%`,
|
|
10693
|
-
// id: `width-value-${selectedField.id}`
|
|
10694
|
-
});
|
|
10695
|
-
layoutLabelRow.appendChild(spanValueDisplay);
|
|
10696
|
-
layoutGroup.appendChild(layoutLabelRow);
|
|
10697
|
-
const spanButtonsContainer = createElement("div", { className: "grid grid-cols-6 gap-2 mt-2" });
|
|
10698
11429
|
const fieldId = selectedField.id;
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
const layout = field.layout || { row: 0, column: 0 };
|
|
10714
|
-
state2.updateField(fieldId, {
|
|
10715
|
-
layout: { ...layout, span },
|
|
10716
|
-
width: Math.round(span / 12 * 100)
|
|
10717
|
-
});
|
|
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
|
+
}
|
|
10718
11444
|
}
|
|
10719
|
-
})
|
|
10720
|
-
|
|
10721
|
-
}
|
|
10722
|
-
layoutGroup.appendChild(spanButtonsContainer);
|
|
10723
|
-
body.appendChild(layoutGroup);
|
|
11445
|
+
})
|
|
11446
|
+
);
|
|
10724
11447
|
body.appendChild(this.createCheckboxField(
|
|
10725
11448
|
"Required",
|
|
10726
11449
|
!!selectedField.required || !!selectedField.validations?.required,
|
|
@@ -10818,9 +11541,9 @@ var FormBuilder = class {
|
|
|
10818
11541
|
oninput: (e) => formStore.getState().updateField(selectedField.id, { nameGeneratorSuffix: e.target.value })
|
|
10819
11542
|
}));
|
|
10820
11543
|
body.appendChild(suffixGroup);
|
|
10821
|
-
const
|
|
10822
|
-
|
|
10823
|
-
|
|
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", {
|
|
10824
11547
|
type: "number",
|
|
10825
11548
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10826
11549
|
value: String(selectedField.nameGeneratorIdPadding ?? 4),
|
|
@@ -10832,7 +11555,7 @@ var FormBuilder = class {
|
|
|
10832
11555
|
formStore.getState().updateField(selectedField.id, { nameGeneratorIdPadding: isNaN(val) ? 4 : Math.max(1, Math.min(10, val)) });
|
|
10833
11556
|
}
|
|
10834
11557
|
}));
|
|
10835
|
-
body.appendChild(
|
|
11558
|
+
body.appendChild(paddingGroup);
|
|
10836
11559
|
}
|
|
10837
11560
|
if (selectedField.type === "binary_choice") {
|
|
10838
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" });
|
|
@@ -12046,234 +12769,7 @@ var FormBuilder = class {
|
|
|
12046
12769
|
}
|
|
12047
12770
|
}
|
|
12048
12771
|
}
|
|
12049
|
-
|
|
12050
|
-
body.appendChild(cssHeader);
|
|
12051
|
-
const getStyleValue = (prop) => selectedField.css?.style?.[prop] || "";
|
|
12052
|
-
const updateStyleProp = (prop, value) => {
|
|
12053
|
-
const state2 = formStore.getState();
|
|
12054
|
-
const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12055
|
-
if (!freshField) {
|
|
12056
|
-
return;
|
|
12057
|
-
}
|
|
12058
|
-
const currentStyle = freshField.css?.style || {};
|
|
12059
|
-
const newStyle = { ...currentStyle };
|
|
12060
|
-
if (value) {
|
|
12061
|
-
newStyle[prop] = value;
|
|
12062
|
-
} else {
|
|
12063
|
-
delete newStyle[prop];
|
|
12064
|
-
}
|
|
12065
|
-
const updatePayload = {
|
|
12066
|
-
css: {
|
|
12067
|
-
style: Object.keys(newStyle).length > 0 ? newStyle : void 0
|
|
12068
|
-
// Do NOT spread freshField.css here - let updateField preserve class
|
|
12069
|
-
}
|
|
12070
|
-
};
|
|
12071
|
-
state2.updateField(selectedField.id, updatePayload);
|
|
12072
|
-
};
|
|
12073
|
-
const paddingGroup = createElement("div", { className: "mb-3" });
|
|
12074
|
-
paddingGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Padding" }));
|
|
12075
|
-
const paddingSelect = createElement("select", {
|
|
12076
|
-
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",
|
|
12077
|
-
onchange: (e) => {
|
|
12078
|
-
updateStyleProp("padding", e.target.value);
|
|
12079
|
-
}
|
|
12080
|
-
});
|
|
12081
|
-
const paddingOptions = [
|
|
12082
|
-
{ value: "", label: "None" },
|
|
12083
|
-
{ value: "4px", label: "4px - Tight" },
|
|
12084
|
-
{ value: "8px", label: "8px - Normal" },
|
|
12085
|
-
{ value: "12px", label: "12px - Comfortable" },
|
|
12086
|
-
{ value: "16px", label: "16px - Spacious" },
|
|
12087
|
-
{ value: "24px", label: "24px - Large" }
|
|
12088
|
-
];
|
|
12089
|
-
paddingOptions.forEach((opt) => {
|
|
12090
|
-
paddingSelect.appendChild(createElement("option", { value: opt.value, text: opt.label, selected: getStyleValue("padding") === opt.value }));
|
|
12091
|
-
});
|
|
12092
|
-
paddingGroup.appendChild(paddingSelect);
|
|
12093
|
-
body.appendChild(paddingGroup);
|
|
12094
|
-
const bgColorGroup = createElement("div", { className: "mb-3" });
|
|
12095
|
-
bgColorGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Background Color" }));
|
|
12096
|
-
const bgColorRow = createElement("div", { className: "flex items-center gap-2" });
|
|
12097
|
-
const bgColorInput = createElement("input", {
|
|
12098
|
-
type: "color",
|
|
12099
|
-
className: "w-10 h-10 rounded border border-gray-300 cursor-pointer",
|
|
12100
|
-
value: getStyleValue("backgroundColor") || "#ffffff",
|
|
12101
|
-
onchange: (e) => {
|
|
12102
|
-
const color = e.target.value;
|
|
12103
|
-
updateStyleProp("backgroundColor", color === "#ffffff" ? "" : color);
|
|
12104
|
-
}
|
|
12105
|
-
});
|
|
12106
|
-
const bgColorClear = createElement("button", {
|
|
12107
|
-
type: "button",
|
|
12108
|
-
className: "px-2 py-1 text-xs text-gray-600 hover:text-gray-800 border border-gray-300 rounded",
|
|
12109
|
-
text: "Clear",
|
|
12110
|
-
onclick: () => {
|
|
12111
|
-
bgColorInput.value = "#ffffff";
|
|
12112
|
-
updateStyleProp("backgroundColor", "");
|
|
12113
|
-
}
|
|
12114
|
-
});
|
|
12115
|
-
bgColorRow.appendChild(bgColorInput);
|
|
12116
|
-
bgColorRow.appendChild(bgColorClear);
|
|
12117
|
-
bgColorGroup.appendChild(bgColorRow);
|
|
12118
|
-
body.appendChild(bgColorGroup);
|
|
12119
|
-
const alignGroup = createElement("div", { className: "mb-3" });
|
|
12120
|
-
alignGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Text Alignment" }));
|
|
12121
|
-
const alignButtonsRow = createElement("div", { className: "flex gap-1" });
|
|
12122
|
-
const alignments = [
|
|
12123
|
-
{ value: "left", icon: "AlignLeft" },
|
|
12124
|
-
{ value: "center", icon: "AlignCenter" },
|
|
12125
|
-
{ value: "right", icon: "AlignRight" }
|
|
12126
|
-
];
|
|
12127
|
-
const currentAlign = getStyleValue("textAlign") || "left";
|
|
12128
|
-
alignments.forEach((align) => {
|
|
12129
|
-
const isActive = currentAlign === align.value;
|
|
12130
|
-
const btn = createElement("button", {
|
|
12131
|
-
type: "button",
|
|
12132
|
-
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"}`,
|
|
12133
|
-
title: `Align ${align.value}`,
|
|
12134
|
-
onclick: () => {
|
|
12135
|
-
const newValue = align.value === "left" ? "" : align.value;
|
|
12136
|
-
updateStyleProp("textAlign", newValue);
|
|
12137
|
-
}
|
|
12138
|
-
}, [getIcon(align.icon, 16)]);
|
|
12139
|
-
alignButtonsRow.appendChild(btn);
|
|
12140
|
-
});
|
|
12141
|
-
alignGroup.appendChild(alignButtonsRow);
|
|
12142
|
-
body.appendChild(alignGroup);
|
|
12143
|
-
const cssClassGroup = createElement("div", { className: "mb-3" });
|
|
12144
|
-
cssClassGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Custom CSS Class" }));
|
|
12145
|
-
cssClassGroup.appendChild(createElement("input", {
|
|
12146
|
-
type: "text",
|
|
12147
|
-
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent text-sm",
|
|
12148
|
-
value: selectedField.css?.class || "",
|
|
12149
|
-
placeholder: "e.g. my-custom-class",
|
|
12150
|
-
"data-focus-id": `field-css-class-${selectedField.id}`,
|
|
12151
|
-
oninput: (e) => {
|
|
12152
|
-
const cssClass = e.target.value;
|
|
12153
|
-
const state2 = formStore.getState();
|
|
12154
|
-
const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12155
|
-
if (!freshField)
|
|
12156
|
-
return;
|
|
12157
|
-
state2.updateField(selectedField.id, {
|
|
12158
|
-
css: {
|
|
12159
|
-
class: cssClass || void 0
|
|
12160
|
-
// Do NOT spread freshField.css here - let updateField preserve style
|
|
12161
|
-
}
|
|
12162
|
-
});
|
|
12163
|
-
}
|
|
12164
|
-
}));
|
|
12165
|
-
body.appendChild(cssClassGroup);
|
|
12166
|
-
const isPanelExpanded = advancedCssPanelState.get(selectedField.id) || false;
|
|
12167
|
-
const advancedToggleGroup = createElement("div", { className: "mb-3" });
|
|
12168
|
-
const advancedToggle = createElement("button", {
|
|
12169
|
-
type: "button",
|
|
12170
|
-
className: "text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1",
|
|
12171
|
-
onclick: () => {
|
|
12172
|
-
const advancedPanel2 = document.getElementById(`advanced-css-${selectedField.id}`);
|
|
12173
|
-
if (advancedPanel2) {
|
|
12174
|
-
advancedPanel2.classList.toggle("hidden");
|
|
12175
|
-
const isHidden = advancedPanel2.classList.contains("hidden");
|
|
12176
|
-
advancedToggle.textContent = isHidden ? "\u25B6 Show Advanced CSS" : "\u25BC Hide Advanced CSS";
|
|
12177
|
-
advancedCssPanelState.set(selectedField.id, !isHidden);
|
|
12178
|
-
}
|
|
12179
|
-
}
|
|
12180
|
-
});
|
|
12181
|
-
advancedToggle.textContent = isPanelExpanded ? "\u25BC Hide Advanced CSS" : "\u25B6 Show Advanced CSS";
|
|
12182
|
-
advancedToggleGroup.appendChild(advancedToggle);
|
|
12183
|
-
body.appendChild(advancedToggleGroup);
|
|
12184
|
-
const advancedPanel = createElement("div", {
|
|
12185
|
-
className: isPanelExpanded ? "mb-3" : "mb-3 hidden",
|
|
12186
|
-
id: `advanced-css-${selectedField.id}`
|
|
12187
|
-
});
|
|
12188
|
-
advancedPanel.appendChild(createElement("label", { className: "block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1", text: "Raw CSS Style (JSON)" }));
|
|
12189
|
-
const cssStyleId = `field-css-style-${selectedField.id}`;
|
|
12190
|
-
const freshState = formStore.getState();
|
|
12191
|
-
const freshFieldForInit = freshState.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12192
|
-
const fieldForCssStyle = freshFieldForInit || selectedField;
|
|
12193
|
-
let initialCssStyleValue = fieldForCssStyle.css?.style ? JSON.stringify(fieldForCssStyle.css.style, null, 2) : "";
|
|
12194
|
-
const preservedValue = focusState?.id === cssStyleId ? focusState.value : void 0;
|
|
12195
|
-
if (preservedValue !== void 0) {
|
|
12196
|
-
initialCssStyleValue = preservedValue;
|
|
12197
|
-
}
|
|
12198
|
-
const cssStyleTextarea = createElement("textarea", {
|
|
12199
|
-
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent text-xs font-mono",
|
|
12200
|
-
rows: 3,
|
|
12201
|
-
placeholder: '{"padding": "8px", "backgroundColor": "#f0f0f0"}',
|
|
12202
|
-
"data-focus-id": cssStyleId,
|
|
12203
|
-
"data-was-focused": "false",
|
|
12204
|
-
onfocus: (e) => {
|
|
12205
|
-
const textarea = e.target;
|
|
12206
|
-
textarea.setAttribute("data-was-focused", "true");
|
|
12207
|
-
const state2 = formStore.getState();
|
|
12208
|
-
const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12209
|
-
if (!textarea.value.trim() && freshField?.css?.style && Object.keys(freshField.css.style).length > 0) {
|
|
12210
|
-
const styleString = JSON.stringify(freshField.css.style, null, 2);
|
|
12211
|
-
textarea.value = styleString;
|
|
12212
|
-
}
|
|
12213
|
-
},
|
|
12214
|
-
oninput: (e) => {
|
|
12215
|
-
const styleText = e.target.value;
|
|
12216
|
-
const state2 = formStore.getState();
|
|
12217
|
-
const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12218
|
-
if (!freshField)
|
|
12219
|
-
return;
|
|
12220
|
-
try {
|
|
12221
|
-
if (styleText.trim()) {
|
|
12222
|
-
const styleObj = JSON.parse(styleText);
|
|
12223
|
-
state2.updateField(selectedField.id, {
|
|
12224
|
-
css: { ...freshField.css, style: styleObj }
|
|
12225
|
-
});
|
|
12226
|
-
}
|
|
12227
|
-
} catch (err) {
|
|
12228
|
-
}
|
|
12229
|
-
},
|
|
12230
|
-
onblur: (e) => {
|
|
12231
|
-
const textarea = e.target;
|
|
12232
|
-
const styleText = textarea.value;
|
|
12233
|
-
const wasFocused = textarea.getAttribute("data-was-focused") === "true";
|
|
12234
|
-
if (!wasFocused) {
|
|
12235
|
-
return;
|
|
12236
|
-
}
|
|
12237
|
-
textarea.setAttribute("data-was-focused", "false");
|
|
12238
|
-
setTimeout(() => {
|
|
12239
|
-
if (!document.body.contains(textarea)) {
|
|
12240
|
-
return;
|
|
12241
|
-
}
|
|
12242
|
-
const advPanel = document.getElementById(`advanced-css-${selectedField.id}`);
|
|
12243
|
-
const isPanelVisible = advPanel && !advPanel.classList.contains("hidden");
|
|
12244
|
-
if (!isPanelVisible) {
|
|
12245
|
-
return;
|
|
12246
|
-
}
|
|
12247
|
-
const state2 = formStore.getState();
|
|
12248
|
-
const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12249
|
-
if (!freshField)
|
|
12250
|
-
return;
|
|
12251
|
-
try {
|
|
12252
|
-
if (styleText.trim()) {
|
|
12253
|
-
const styleObj = JSON.parse(styleText);
|
|
12254
|
-
state2.updateField(selectedField.id, {
|
|
12255
|
-
css: { ...freshField.css, style: styleObj }
|
|
12256
|
-
});
|
|
12257
|
-
} else {
|
|
12258
|
-
if (freshField.css?.style && Object.keys(freshField.css.style).length > 0) {
|
|
12259
|
-
textarea.value = JSON.stringify(freshField.css.style, null, 2);
|
|
12260
|
-
} else {
|
|
12261
|
-
state2.updateField(selectedField.id, {
|
|
12262
|
-
css: { ...freshField.css, style: void 0 }
|
|
12263
|
-
});
|
|
12264
|
-
}
|
|
12265
|
-
}
|
|
12266
|
-
} catch (err) {
|
|
12267
|
-
if (freshField.css?.style) {
|
|
12268
|
-
textarea.value = JSON.stringify(freshField.css.style, null, 2);
|
|
12269
|
-
}
|
|
12270
|
-
}
|
|
12271
|
-
}, 0);
|
|
12272
|
-
}
|
|
12273
|
-
});
|
|
12274
|
-
cssStyleTextarea.value = initialCssStyleValue;
|
|
12275
|
-
advancedPanel.appendChild(cssStyleTextarea);
|
|
12276
|
-
body.appendChild(advancedPanel);
|
|
12772
|
+
this.appendSharedStylingSection(body, { kind: "field", fieldId: selectedField.id }, focusState);
|
|
12277
12773
|
panel.appendChild(body);
|
|
12278
12774
|
return panel;
|
|
12279
12775
|
}
|