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.mjs
CHANGED
|
@@ -4771,7 +4771,30 @@ function transformField(field) {
|
|
|
4771
4771
|
var cleanFormSchema = (schema) => {
|
|
4772
4772
|
const cleanField = transformField;
|
|
4773
4773
|
let sections = [];
|
|
4774
|
-
if (schema.
|
|
4774
|
+
if (schema.sections && Array.isArray(schema.sections)) {
|
|
4775
|
+
sections = schema.sections;
|
|
4776
|
+
} else if (schema.groups && Array.isArray(schema.groups)) {
|
|
4777
|
+
const rootFields = schema.fields && Array.isArray(schema.fields) ? schema.fields : [];
|
|
4778
|
+
const rootById = new Map(rootFields.map((f) => [String(f.id), f]));
|
|
4779
|
+
sections = schema.groups.map((group, index2) => {
|
|
4780
|
+
let fieldList = group.fields;
|
|
4781
|
+
if (Array.isArray(group.fieldIds) && group.fieldIds.length > 0 && rootById.size > 0) {
|
|
4782
|
+
fieldList = group.fieldIds.map((id) => rootById.get(String(id))).filter(Boolean);
|
|
4783
|
+
}
|
|
4784
|
+
if (!fieldList)
|
|
4785
|
+
fieldList = [];
|
|
4786
|
+
return {
|
|
4787
|
+
...group,
|
|
4788
|
+
id: group.id || `section-${index2}`,
|
|
4789
|
+
title: group.title || group.name || `Section ${index2 + 1}`,
|
|
4790
|
+
name: group.name ?? group.title,
|
|
4791
|
+
fields: fieldList,
|
|
4792
|
+
order: group.order !== void 0 ? group.order : index2,
|
|
4793
|
+
isExpanded: group.isExpanded !== void 0 ? group.isExpanded : true,
|
|
4794
|
+
expanded: group.expanded !== void 0 ? group.expanded : group.isExpanded !== false
|
|
4795
|
+
};
|
|
4796
|
+
});
|
|
4797
|
+
} else if (schema.fields && Array.isArray(schema.fields) && schema.fields.length > 0) {
|
|
4775
4798
|
sections = [{
|
|
4776
4799
|
id: schema.id ? `section-${schema.id}` : "section-1",
|
|
4777
4800
|
title: schema.formName || schema.title || "Form Fields",
|
|
@@ -4779,16 +4802,6 @@ var cleanFormSchema = (schema) => {
|
|
|
4779
4802
|
order: 0,
|
|
4780
4803
|
isExpanded: true
|
|
4781
4804
|
}];
|
|
4782
|
-
} else if (schema.sections && Array.isArray(schema.sections)) {
|
|
4783
|
-
sections = schema.sections;
|
|
4784
|
-
} else if (schema.groups && Array.isArray(schema.groups)) {
|
|
4785
|
-
sections = schema.groups.map((group, index2) => ({
|
|
4786
|
-
id: group.id || `section-${index2}`,
|
|
4787
|
-
title: group.title || group.name || `Section ${index2 + 1}`,
|
|
4788
|
-
fields: group.fields || [],
|
|
4789
|
-
order: group.order !== void 0 ? group.order : index2,
|
|
4790
|
-
isExpanded: group.isExpanded !== void 0 ? group.isExpanded : true
|
|
4791
|
-
}));
|
|
4792
4805
|
}
|
|
4793
4806
|
return {
|
|
4794
4807
|
id: schema.id,
|
|
@@ -4796,26 +4809,37 @@ var cleanFormSchema = (schema) => {
|
|
|
4796
4809
|
formName: schema.formName || schema.formId || schema.id,
|
|
4797
4810
|
layout: schema.layout || { type: "grid", columns: 12, gap: "16px" },
|
|
4798
4811
|
// Preserve form-level layout or set default
|
|
4799
|
-
sections: sections.map((section, sectionIndex) =>
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4812
|
+
sections: sections.map((section, sectionIndex) => {
|
|
4813
|
+
const order = section.order !== void 0 ? section.order : sectionIndex;
|
|
4814
|
+
return {
|
|
4815
|
+
id: section.id || `section-${sectionIndex}`,
|
|
4816
|
+
title: section.title || `Section ${sectionIndex + 1}`,
|
|
4817
|
+
name: section.name ?? section.title,
|
|
4818
|
+
description: section.description ?? null,
|
|
4819
|
+
fields: (section.fields || []).map((field, fieldIndex) => {
|
|
4820
|
+
const cleaned = cleanField(field);
|
|
4821
|
+
if (cleaned.order === void 0) {
|
|
4822
|
+
cleaned.order = fieldIndex;
|
|
4823
|
+
}
|
|
4824
|
+
return cleaned;
|
|
4825
|
+
}),
|
|
4826
|
+
isExpanded: section.isExpanded !== void 0 ? section.isExpanded : true,
|
|
4827
|
+
expanded: section.expanded !== void 0 ? section.expanded : section.isExpanded !== false,
|
|
4828
|
+
columns: section.columns,
|
|
4829
|
+
order,
|
|
4830
|
+
layout: section.layout || { type: "grid", columns: section.columns || 12, gap: "16px" },
|
|
4831
|
+
css: section.css,
|
|
4832
|
+
position: section.position ?? { row: order, column: 0, width: 12, order },
|
|
4833
|
+
visible: section.visible !== false,
|
|
4834
|
+
collapsible: section.collapsible !== false,
|
|
4835
|
+
parentGroupId: section.parentGroupId ?? null,
|
|
4836
|
+
repeatable: section.repeatable === true,
|
|
4837
|
+
dataKey: section.dataKey ?? null,
|
|
4838
|
+
addButtonLabel: section.addButtonLabel ?? null,
|
|
4839
|
+
minInstances: section.minInstances ?? null,
|
|
4840
|
+
maxInstances: section.maxInstances ?? null
|
|
4841
|
+
};
|
|
4842
|
+
})
|
|
4819
4843
|
};
|
|
4820
4844
|
};
|
|
4821
4845
|
function convertValidationArrayToObject(validation) {
|
|
@@ -4860,7 +4884,7 @@ function convertWidthToSpan(width, totalColumns = 12) {
|
|
|
4860
4884
|
const widthNum = parseWidth(width);
|
|
4861
4885
|
return Math.max(1, Math.min(12, Math.round(widthNum / 100 * totalColumns)));
|
|
4862
4886
|
}
|
|
4863
|
-
function fieldToPayload(field) {
|
|
4887
|
+
function fieldToPayload(field, opts) {
|
|
4864
4888
|
let outputType = field.type;
|
|
4865
4889
|
let outputValidations = field.validations ? { ...field.validations } : void 0;
|
|
4866
4890
|
if (field.type === "text" && isEmailLikeField(field)) {
|
|
@@ -4879,6 +4903,9 @@ function fieldToPayload(field) {
|
|
|
4879
4903
|
// Model key for binding (API / host app)
|
|
4880
4904
|
order: field.order !== void 0 ? field.order : 0
|
|
4881
4905
|
};
|
|
4906
|
+
if (opts?.groupId) {
|
|
4907
|
+
payload.groupId = opts.groupId;
|
|
4908
|
+
}
|
|
4882
4909
|
if (field.layout?.span !== void 0) {
|
|
4883
4910
|
payload.layout = {
|
|
4884
4911
|
row: field.layout.row ?? 0,
|
|
@@ -5034,20 +5061,48 @@ function fieldToPayload(field) {
|
|
|
5034
5061
|
}
|
|
5035
5062
|
return payload;
|
|
5036
5063
|
}
|
|
5064
|
+
function sectionToGroupPayload(section, index2) {
|
|
5065
|
+
const order = section.order !== void 0 ? section.order : index2;
|
|
5066
|
+
const pos = section.position ?? { row: order, column: 0, width: 12, order };
|
|
5067
|
+
const width = Math.max(1, Math.min(12, pos.width ?? 12));
|
|
5068
|
+
return {
|
|
5069
|
+
id: section.id,
|
|
5070
|
+
name: section.name ?? section.title,
|
|
5071
|
+
description: section.description ?? null,
|
|
5072
|
+
position: {
|
|
5073
|
+
row: pos.row ?? 0,
|
|
5074
|
+
column: pos.column ?? 0,
|
|
5075
|
+
width,
|
|
5076
|
+
order: pos.order ?? order
|
|
5077
|
+
},
|
|
5078
|
+
fieldIds: section.fields.map((f) => f.id),
|
|
5079
|
+
expanded: section.expanded !== void 0 ? section.expanded : section.isExpanded !== false,
|
|
5080
|
+
visible: section.visible !== false,
|
|
5081
|
+
collapsible: section.collapsible !== false,
|
|
5082
|
+
parentGroupId: section.parentGroupId ?? null,
|
|
5083
|
+
repeatable: section.repeatable === true,
|
|
5084
|
+
dataKey: section.dataKey ?? null,
|
|
5085
|
+
addButtonLabel: section.addButtonLabel ?? null,
|
|
5086
|
+
minInstances: section.minInstances ?? null,
|
|
5087
|
+
maxInstances: section.maxInstances ?? null,
|
|
5088
|
+
css: section.css
|
|
5089
|
+
};
|
|
5090
|
+
}
|
|
5037
5091
|
var builderToPlatform = (builderSchema) => {
|
|
5092
|
+
const fieldsFlat = [];
|
|
5093
|
+
const groups = builderSchema.sections.map((section, sectionIndex) => {
|
|
5094
|
+
section.fields.forEach((f) => {
|
|
5095
|
+
fieldsFlat.push(fieldToPayload(f, { groupId: section.id }));
|
|
5096
|
+
});
|
|
5097
|
+
return sectionToGroupPayload(section, sectionIndex);
|
|
5098
|
+
});
|
|
5038
5099
|
return {
|
|
5039
5100
|
id: builderSchema.id,
|
|
5040
5101
|
title: builderSchema.title,
|
|
5041
5102
|
formName: builderSchema.formName,
|
|
5042
5103
|
layout: builderSchema.layout || { type: "grid", columns: 12, gap: "16px" },
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
title: section.title,
|
|
5046
|
-
order: section.order !== void 0 ? section.order : sectionIndex,
|
|
5047
|
-
layout: section.layout || { type: "grid", columns: section.columns || 12, gap: "16px" },
|
|
5048
|
-
css: section.css,
|
|
5049
|
-
fields: section.fields.map(fieldToPayload)
|
|
5050
|
-
}))
|
|
5104
|
+
fields: fieldsFlat,
|
|
5105
|
+
groups
|
|
5051
5106
|
};
|
|
5052
5107
|
};
|
|
5053
5108
|
var platformToBuilder = (platformSchema) => {
|
|
@@ -5068,6 +5123,77 @@ function getValidationConfigForAngular(validations) {
|
|
|
5068
5123
|
};
|
|
5069
5124
|
}
|
|
5070
5125
|
|
|
5126
|
+
// src/utils/sectionHierarchy.ts
|
|
5127
|
+
function effectiveParentId(section, sectionIds) {
|
|
5128
|
+
const p = section.parentGroupId;
|
|
5129
|
+
if (!p || !sectionIds.has(p))
|
|
5130
|
+
return null;
|
|
5131
|
+
return p;
|
|
5132
|
+
}
|
|
5133
|
+
function getRootSections(sections) {
|
|
5134
|
+
const ids = new Set(sections.map((s) => s.id));
|
|
5135
|
+
return sections.filter((s) => effectiveParentId(s, ids) === null);
|
|
5136
|
+
}
|
|
5137
|
+
function getChildSections(sections, parentId, excludeSectionId) {
|
|
5138
|
+
const ids = new Set(sections.map((s) => s.id));
|
|
5139
|
+
return sections.filter((s) => {
|
|
5140
|
+
return effectiveParentId(s, ids) === parentId;
|
|
5141
|
+
}).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
5142
|
+
}
|
|
5143
|
+
function getDescendantSectionIds(sections, rootId) {
|
|
5144
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
5145
|
+
sections.forEach((s) => {
|
|
5146
|
+
const p = s.parentGroupId;
|
|
5147
|
+
if (p) {
|
|
5148
|
+
if (!byParent.has(p))
|
|
5149
|
+
byParent.set(p, []);
|
|
5150
|
+
byParent.get(p).push(s.id);
|
|
5151
|
+
}
|
|
5152
|
+
});
|
|
5153
|
+
const out = /* @__PURE__ */ new Set();
|
|
5154
|
+
const stack = [...byParent.get(rootId) || []];
|
|
5155
|
+
while (stack.length) {
|
|
5156
|
+
const id = stack.pop();
|
|
5157
|
+
if (out.has(id))
|
|
5158
|
+
continue;
|
|
5159
|
+
out.add(id);
|
|
5160
|
+
(byParent.get(id) || []).forEach((c) => stack.push(c));
|
|
5161
|
+
}
|
|
5162
|
+
return out;
|
|
5163
|
+
}
|
|
5164
|
+
function wouldCreateParentCycle(sections, sectionId, newParentId) {
|
|
5165
|
+
if (newParentId === sectionId)
|
|
5166
|
+
return true;
|
|
5167
|
+
return getDescendantSectionIds(sections, sectionId).has(newParentId);
|
|
5168
|
+
}
|
|
5169
|
+
function getValidParentSectionIds(sections, sectionId) {
|
|
5170
|
+
const descendants = getDescendantSectionIds(sections, sectionId);
|
|
5171
|
+
return sections.map((s) => s.id).filter((id) => id !== sectionId && !descendants.has(id));
|
|
5172
|
+
}
|
|
5173
|
+
function siblingsForParent(sections, sectionIds, parentId, excludeSectionId) {
|
|
5174
|
+
return sections.filter((s) => {
|
|
5175
|
+
if (s.id === excludeSectionId)
|
|
5176
|
+
return false;
|
|
5177
|
+
const eff = effectiveParentId(s, sectionIds);
|
|
5178
|
+
if (parentId === null)
|
|
5179
|
+
return eff === null;
|
|
5180
|
+
return eff === parentId;
|
|
5181
|
+
});
|
|
5182
|
+
}
|
|
5183
|
+
function nextSiblingOrder(sections, sectionId, parentId) {
|
|
5184
|
+
const sectionIds = new Set(sections.map((s) => s.id));
|
|
5185
|
+
const siblings = siblingsForParent(sections, sectionIds, parentId, sectionId);
|
|
5186
|
+
if (siblings.length === 0)
|
|
5187
|
+
return 0;
|
|
5188
|
+
return Math.max(...siblings.map((s) => s.order ?? 0)) + 1;
|
|
5189
|
+
}
|
|
5190
|
+
function getNextRootOrder(sections) {
|
|
5191
|
+
const roots = getRootSections(sections);
|
|
5192
|
+
if (roots.length === 0)
|
|
5193
|
+
return 0;
|
|
5194
|
+
return Math.max(...roots.map((r) => r.order ?? 0)) + 1;
|
|
5195
|
+
}
|
|
5196
|
+
|
|
5071
5197
|
// src/core/useFormStore.ts
|
|
5072
5198
|
var INITIAL_SCHEMA = {
|
|
5073
5199
|
id: "form_1",
|
|
@@ -5078,6 +5204,7 @@ var INITIAL_SCHEMA = {
|
|
|
5078
5204
|
var formStore = createStore((set, get) => ({
|
|
5079
5205
|
schema: INITIAL_SCHEMA,
|
|
5080
5206
|
selectedFieldId: null,
|
|
5207
|
+
selectedSectionId: null,
|
|
5081
5208
|
history: [INITIAL_SCHEMA],
|
|
5082
5209
|
historyIndex: 0,
|
|
5083
5210
|
isPreviewMode: false,
|
|
@@ -5395,6 +5522,8 @@ var formStore = createStore((set, get) => ({
|
|
|
5395
5522
|
importSection: (section) => {
|
|
5396
5523
|
const { schema, history, historyIndex } = get();
|
|
5397
5524
|
const clonedSection = cloneSection(section);
|
|
5525
|
+
clonedSection.parentGroupId = null;
|
|
5526
|
+
clonedSection.order = getNextRootOrder(schema.sections);
|
|
5398
5527
|
const newSchema = { ...schema, sections: [...schema.sections, clonedSection] };
|
|
5399
5528
|
set({
|
|
5400
5529
|
schema: newSchema,
|
|
@@ -5404,15 +5533,26 @@ var formStore = createStore((set, get) => ({
|
|
|
5404
5533
|
},
|
|
5405
5534
|
addSection: () => {
|
|
5406
5535
|
const { schema, history, historyIndex } = get();
|
|
5536
|
+
const order = getNextRootOrder(schema.sections);
|
|
5407
5537
|
const newSection = {
|
|
5408
5538
|
id: generateId(),
|
|
5409
|
-
title: `Section ${
|
|
5539
|
+
title: `Section ${order + 1}`,
|
|
5540
|
+
name: `Section ${order + 1}`,
|
|
5410
5541
|
fields: [],
|
|
5411
5542
|
columns: 1,
|
|
5412
5543
|
// Legacy - prefer layout.columns
|
|
5413
5544
|
layout: { type: "grid", columns: 12, gap: "16px" },
|
|
5414
|
-
order
|
|
5415
|
-
|
|
5545
|
+
order,
|
|
5546
|
+
position: { row: order, column: 0, width: 12, order },
|
|
5547
|
+
expanded: true,
|
|
5548
|
+
visible: true,
|
|
5549
|
+
collapsible: true,
|
|
5550
|
+
parentGroupId: null,
|
|
5551
|
+
repeatable: false,
|
|
5552
|
+
dataKey: null,
|
|
5553
|
+
addButtonLabel: null,
|
|
5554
|
+
minInstances: null,
|
|
5555
|
+
maxInstances: null
|
|
5416
5556
|
};
|
|
5417
5557
|
const newSchema = { ...schema, sections: [...schema.sections, newSection] };
|
|
5418
5558
|
set({
|
|
@@ -5422,24 +5562,74 @@ var formStore = createStore((set, get) => ({
|
|
|
5422
5562
|
});
|
|
5423
5563
|
},
|
|
5424
5564
|
removeSection: (sectionId) => {
|
|
5425
|
-
const { schema, history, historyIndex } = get();
|
|
5565
|
+
const { schema, history, historyIndex, selectedSectionId } = get();
|
|
5426
5566
|
const newSchema = {
|
|
5427
5567
|
...schema,
|
|
5428
|
-
sections: schema.sections.filter((s) => s.id !== sectionId)
|
|
5568
|
+
sections: schema.sections.filter((s) => s.id !== sectionId).map((s) => s.parentGroupId === sectionId ? { ...s, parentGroupId: null } : s)
|
|
5429
5569
|
};
|
|
5430
5570
|
set({
|
|
5431
5571
|
schema: newSchema,
|
|
5572
|
+
selectedSectionId: selectedSectionId === sectionId ? null : selectedSectionId,
|
|
5432
5573
|
history: [...history.slice(0, historyIndex + 1), newSchema],
|
|
5433
5574
|
historyIndex: historyIndex + 1
|
|
5434
5575
|
});
|
|
5435
5576
|
},
|
|
5436
5577
|
updateSection: (sectionId, updates) => {
|
|
5437
5578
|
const { schema, history, historyIndex } = get();
|
|
5579
|
+
let processedUpdates = { ...updates };
|
|
5580
|
+
if (updates.parentGroupId !== void 0) {
|
|
5581
|
+
const newParent = updates.parentGroupId;
|
|
5582
|
+
if (newParent === null || newParent === "") {
|
|
5583
|
+
processedUpdates.parentGroupId = null;
|
|
5584
|
+
processedUpdates.order = nextSiblingOrder(schema.sections, sectionId, null);
|
|
5585
|
+
} else if (wouldCreateParentCycle(schema.sections, sectionId, newParent)) {
|
|
5586
|
+
delete processedUpdates.parentGroupId;
|
|
5587
|
+
} else {
|
|
5588
|
+
processedUpdates.parentGroupId = newParent;
|
|
5589
|
+
processedUpdates.order = nextSiblingOrder(schema.sections, sectionId, newParent);
|
|
5590
|
+
}
|
|
5591
|
+
}
|
|
5438
5592
|
const newSchema = {
|
|
5439
5593
|
...schema,
|
|
5440
|
-
sections: schema.sections.map(
|
|
5441
|
-
(s
|
|
5442
|
-
|
|
5594
|
+
sections: schema.sections.map((s) => {
|
|
5595
|
+
if (s.id !== sectionId)
|
|
5596
|
+
return s;
|
|
5597
|
+
let mergedUpdates = { ...processedUpdates };
|
|
5598
|
+
if (processedUpdates.css !== void 0) {
|
|
5599
|
+
const styleKeyPassed = processedUpdates.css && "style" in processedUpdates.css;
|
|
5600
|
+
let newStyle;
|
|
5601
|
+
if (styleKeyPassed) {
|
|
5602
|
+
if (processedUpdates.css.style === void 0 || processedUpdates.css.style === null) {
|
|
5603
|
+
newStyle = void 0;
|
|
5604
|
+
} else if (typeof processedUpdates.css.style === "object") {
|
|
5605
|
+
const onlyStyleInUpdate = !("class" in processedUpdates.css) || processedUpdates.css.class === void 0;
|
|
5606
|
+
if (onlyStyleInUpdate) {
|
|
5607
|
+
newStyle = processedUpdates.css.style;
|
|
5608
|
+
} else {
|
|
5609
|
+
newStyle = { ...s.css?.style || {}, ...processedUpdates.css.style };
|
|
5610
|
+
}
|
|
5611
|
+
} else {
|
|
5612
|
+
newStyle = processedUpdates.css.style;
|
|
5613
|
+
}
|
|
5614
|
+
} else {
|
|
5615
|
+
newStyle = s.css?.style;
|
|
5616
|
+
}
|
|
5617
|
+
mergedUpdates.css = {
|
|
5618
|
+
...s.css || {},
|
|
5619
|
+
...processedUpdates.css,
|
|
5620
|
+
style: newStyle
|
|
5621
|
+
};
|
|
5622
|
+
if (!mergedUpdates.css.class)
|
|
5623
|
+
delete mergedUpdates.css.class;
|
|
5624
|
+
if (!mergedUpdates.css.style || Object.keys(mergedUpdates.css.style).length === 0) {
|
|
5625
|
+
delete mergedUpdates.css.style;
|
|
5626
|
+
}
|
|
5627
|
+
if (Object.keys(mergedUpdates.css).length === 0) {
|
|
5628
|
+
mergedUpdates.css = void 0;
|
|
5629
|
+
}
|
|
5630
|
+
}
|
|
5631
|
+
return { ...s, ...mergedUpdates };
|
|
5632
|
+
})
|
|
5443
5633
|
};
|
|
5444
5634
|
set({
|
|
5445
5635
|
schema: newSchema,
|
|
@@ -5449,14 +5639,23 @@ var formStore = createStore((set, get) => ({
|
|
|
5449
5639
|
},
|
|
5450
5640
|
moveSection: (oldIndex2, newIndex2) => {
|
|
5451
5641
|
const { schema, history, historyIndex } = get();
|
|
5452
|
-
const
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5642
|
+
const roots = getRootSections(schema.sections).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
5643
|
+
if (oldIndex2 < 0 || oldIndex2 >= roots.length || newIndex2 < 0 || newIndex2 >= roots.length || oldIndex2 === newIndex2) {
|
|
5644
|
+
return;
|
|
5645
|
+
}
|
|
5646
|
+
const reorderedRoots = [...roots];
|
|
5647
|
+
const [moved2] = reorderedRoots.splice(oldIndex2, 1);
|
|
5648
|
+
reorderedRoots.splice(newIndex2, 0, moved2);
|
|
5649
|
+
const orderByRootId = new Map(reorderedRoots.map((r, i) => [r.id, i]));
|
|
5650
|
+
const rootIdSet = new Set(reorderedRoots.map((r) => r.id));
|
|
5651
|
+
const nonRoots = schema.sections.filter((s) => !rootIdSet.has(s.id));
|
|
5652
|
+
const newSections = [
|
|
5653
|
+
...reorderedRoots.map((r) => {
|
|
5654
|
+
const full = schema.sections.find((s) => s.id === r.id);
|
|
5655
|
+
return { ...full, order: orderByRootId.get(r.id) };
|
|
5656
|
+
}),
|
|
5657
|
+
...nonRoots
|
|
5658
|
+
];
|
|
5460
5659
|
const newSchema = { ...schema, sections: newSections };
|
|
5461
5660
|
set({
|
|
5462
5661
|
schema: newSchema,
|
|
@@ -5602,7 +5801,14 @@ var formStore = createStore((set, get) => ({
|
|
|
5602
5801
|
historyIndex: historyIndex + 1
|
|
5603
5802
|
});
|
|
5604
5803
|
},
|
|
5605
|
-
selectField: (fieldId) => set({
|
|
5804
|
+
selectField: (fieldId) => set({
|
|
5805
|
+
selectedFieldId: fieldId,
|
|
5806
|
+
selectedSectionId: fieldId ? null : null
|
|
5807
|
+
}),
|
|
5808
|
+
selectSection: (sectionId) => set({
|
|
5809
|
+
selectedSectionId: sectionId,
|
|
5810
|
+
selectedFieldId: null
|
|
5811
|
+
}),
|
|
5606
5812
|
moveField: (fieldId, targetSectionId, newIndex2) => {
|
|
5607
5813
|
const { schema, history, historyIndex } = get();
|
|
5608
5814
|
let field;
|
|
@@ -9570,37 +9776,70 @@ var FieldWrapper = class {
|
|
|
9570
9776
|
};
|
|
9571
9777
|
|
|
9572
9778
|
// src/builder/Section.ts
|
|
9573
|
-
var
|
|
9574
|
-
|
|
9779
|
+
var SECTION_TITLE_DEBOUNCE_MS = 300;
|
|
9780
|
+
var sectionTitleUpdateTimeouts = /* @__PURE__ */ new Map();
|
|
9781
|
+
var Section = class _Section {
|
|
9782
|
+
constructor(section, isSelectedField, selectedSectionId, allSections, depth = 0) {
|
|
9575
9783
|
__publicField(this, "container");
|
|
9576
9784
|
__publicField(this, "section");
|
|
9785
|
+
__publicField(this, "allSections");
|
|
9577
9786
|
__publicField(this, "isSelectedField");
|
|
9787
|
+
__publicField(this, "selectedSectionId");
|
|
9788
|
+
__publicField(this, "depth");
|
|
9578
9789
|
this.section = section;
|
|
9790
|
+
this.allSections = allSections;
|
|
9579
9791
|
this.isSelectedField = isSelectedField;
|
|
9792
|
+
this.selectedSectionId = selectedSectionId;
|
|
9793
|
+
this.depth = depth;
|
|
9580
9794
|
this.container = this.render();
|
|
9581
9795
|
}
|
|
9582
9796
|
getElement() {
|
|
9583
9797
|
return this.container;
|
|
9584
9798
|
}
|
|
9585
9799
|
render() {
|
|
9800
|
+
const sectionVisible = this.section.visible !== false;
|
|
9801
|
+
const isSelectedSection = this.section.id === this.selectedSectionId;
|
|
9802
|
+
const marginClass = this.depth > 0 ? "mb-4" : "mb-6";
|
|
9586
9803
|
const sectionEl = createElement("div", {
|
|
9587
|
-
className:
|
|
9588
|
-
"data-id": this.section.id
|
|
9804
|
+
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" : ""}`,
|
|
9805
|
+
"data-id": this.section.id,
|
|
9806
|
+
"data-section-id": this.section.id
|
|
9589
9807
|
});
|
|
9590
|
-
const header = createElement("div", {
|
|
9591
|
-
|
|
9592
|
-
|
|
9808
|
+
const header = createElement("div", {
|
|
9809
|
+
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",
|
|
9810
|
+
onclick: () => {
|
|
9811
|
+
formStore.getState().selectSection(this.section.id);
|
|
9812
|
+
}
|
|
9813
|
+
});
|
|
9814
|
+
const headerLeft = createElement("div", { className: "flex items-center flex-1 min-w-0" });
|
|
9815
|
+
const dragHandle = createElement("div", { className: "cursor-move mr-3 text-gray-400 hover:text-gray-600 section-handle flex-shrink-0" }, [getIcon("GripVertical", 20)]);
|
|
9816
|
+
dragHandle.addEventListener("mousedown", (e) => e.stopPropagation());
|
|
9817
|
+
dragHandle.addEventListener("click", (e) => e.stopPropagation());
|
|
9818
|
+
headerLeft.appendChild(dragHandle);
|
|
9593
9819
|
headerLeft.appendChild(createElement("input", {
|
|
9594
|
-
className: "bg-transparent font-semibold text-gray-700 dark:text-gray-200 focus:outline-none focus:border-b border-blue-500",
|
|
9820
|
+
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",
|
|
9595
9821
|
value: this.section.title,
|
|
9596
9822
|
"data-focus-id": `section-title-${this.section.id}`,
|
|
9597
|
-
|
|
9823
|
+
onclick: (e) => e.stopPropagation(),
|
|
9824
|
+
oninput: (e) => {
|
|
9825
|
+
const sid = this.section.id;
|
|
9826
|
+
const value = e.target.value;
|
|
9827
|
+
const existing = sectionTitleUpdateTimeouts.get(sid);
|
|
9828
|
+
if (existing)
|
|
9829
|
+
clearTimeout(existing);
|
|
9830
|
+
const timeoutId = setTimeout(() => {
|
|
9831
|
+
sectionTitleUpdateTimeouts.delete(sid);
|
|
9832
|
+
formStore.getState().updateSection(sid, { title: value, name: value });
|
|
9833
|
+
}, SECTION_TITLE_DEBOUNCE_MS);
|
|
9834
|
+
sectionTitleUpdateTimeouts.set(sid, timeoutId);
|
|
9835
|
+
}
|
|
9598
9836
|
}));
|
|
9599
9837
|
header.appendChild(headerLeft);
|
|
9600
9838
|
const actions = createElement("div", { className: "flex items-center space-x-1" });
|
|
9601
9839
|
const colSelect = createElement("select", {
|
|
9602
9840
|
className: "text-xs border rounded bg-transparent mr-2 p-1 text-gray-600",
|
|
9603
9841
|
title: "Section Columns",
|
|
9842
|
+
onclick: (e) => e.stopPropagation(),
|
|
9604
9843
|
onchange: (e) => {
|
|
9605
9844
|
formStore.getState().updateSection(this.section.id, { columns: parseInt(e.target.value) });
|
|
9606
9845
|
}
|
|
@@ -9611,7 +9850,8 @@ var Section = class {
|
|
|
9611
9850
|
actions.appendChild(colSelect);
|
|
9612
9851
|
actions.appendChild(createElement("button", {
|
|
9613
9852
|
className: "text-gray-600 hover:text-red-500 transition-colors p-1",
|
|
9614
|
-
onclick: () => {
|
|
9853
|
+
onclick: (e) => {
|
|
9854
|
+
e.stopPropagation();
|
|
9615
9855
|
if (confirm("Delete this section and all its fields?")) {
|
|
9616
9856
|
formStore.getState().removeSection(this.section.id);
|
|
9617
9857
|
}
|
|
@@ -9655,6 +9895,25 @@ var Section = class {
|
|
|
9655
9895
|
fieldsGrid.appendChild(FieldWrapper.render(field, isSelected));
|
|
9656
9896
|
});
|
|
9657
9897
|
sectionEl.appendChild(fieldsGrid);
|
|
9898
|
+
const childSections = getChildSections(this.allSections, this.section.id);
|
|
9899
|
+
if (childSections.length > 0) {
|
|
9900
|
+
const nestedWrap = createElement("div", {
|
|
9901
|
+
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"
|
|
9902
|
+
});
|
|
9903
|
+
const nestedList = createElement("div", { className: "space-y-4" });
|
|
9904
|
+
childSections.forEach((child) => {
|
|
9905
|
+
const childComponent = new _Section(
|
|
9906
|
+
child,
|
|
9907
|
+
this.isSelectedField,
|
|
9908
|
+
this.selectedSectionId,
|
|
9909
|
+
this.allSections,
|
|
9910
|
+
this.depth + 1
|
|
9911
|
+
);
|
|
9912
|
+
nestedList.appendChild(childComponent.getElement());
|
|
9913
|
+
});
|
|
9914
|
+
nestedWrap.appendChild(nestedList);
|
|
9915
|
+
sectionEl.appendChild(nestedWrap);
|
|
9916
|
+
}
|
|
9658
9917
|
this.initFieldSortable(fieldsGrid);
|
|
9659
9918
|
return sectionEl;
|
|
9660
9919
|
}
|
|
@@ -9672,7 +9931,6 @@ var Section = class {
|
|
|
9672
9931
|
onAdd: (evt) => {
|
|
9673
9932
|
const item = evt.item;
|
|
9674
9933
|
const type = item.getAttribute("data-type");
|
|
9675
|
-
evt.from.getAttribute("data-section-id");
|
|
9676
9934
|
const toSectionId = this.section.id;
|
|
9677
9935
|
if (type && !item.hasAttribute("data-id")) {
|
|
9678
9936
|
item.remove();
|
|
@@ -9716,12 +9974,14 @@ var Section = class {
|
|
|
9716
9974
|
|
|
9717
9975
|
// src/builder/SectionList.ts
|
|
9718
9976
|
var SectionList = class {
|
|
9719
|
-
constructor(schema, selectedFieldId) {
|
|
9977
|
+
constructor(schema, selectedFieldId, selectedSectionId) {
|
|
9720
9978
|
__publicField(this, "container");
|
|
9721
9979
|
__publicField(this, "schema");
|
|
9722
9980
|
__publicField(this, "selectedFieldId");
|
|
9981
|
+
__publicField(this, "selectedSectionId");
|
|
9723
9982
|
this.schema = schema;
|
|
9724
9983
|
this.selectedFieldId = selectedFieldId;
|
|
9984
|
+
this.selectedSectionId = selectedSectionId;
|
|
9725
9985
|
this.container = this.render();
|
|
9726
9986
|
}
|
|
9727
9987
|
getElement() {
|
|
@@ -9744,13 +10004,18 @@ var SectionList = class {
|
|
|
9744
10004
|
placeholder.appendChild(createElement("div", { className: "text-sm text-gray-400", text: 'Drag fields from the sidebar or click "Add Section" below.' }));
|
|
9745
10005
|
listContainer.appendChild(placeholder);
|
|
9746
10006
|
}
|
|
9747
|
-
const
|
|
10007
|
+
const rootSections = getRootSections(this.schema.sections).sort((a, b) => {
|
|
9748
10008
|
const orderA = a.order !== void 0 ? a.order : 0;
|
|
9749
10009
|
const orderB = b.order !== void 0 ? b.order : 0;
|
|
9750
10010
|
return orderA - orderB;
|
|
9751
10011
|
});
|
|
9752
|
-
|
|
9753
|
-
const sectionComponent = new Section(
|
|
10012
|
+
rootSections.forEach((section) => {
|
|
10013
|
+
const sectionComponent = new Section(
|
|
10014
|
+
section,
|
|
10015
|
+
(id) => id === this.selectedFieldId,
|
|
10016
|
+
this.selectedSectionId,
|
|
10017
|
+
this.schema.sections
|
|
10018
|
+
);
|
|
9754
10019
|
listContainer.appendChild(sectionComponent.getElement());
|
|
9755
10020
|
});
|
|
9756
10021
|
this.initSectionSortable(listContainer, hasNoSections);
|
|
@@ -9999,7 +10264,17 @@ var FormBuilder = class {
|
|
|
9999
10264
|
const schemaHash = JSON.stringify({
|
|
10000
10265
|
sections: state.schema.sections.map((s) => ({
|
|
10001
10266
|
id: s.id,
|
|
10002
|
-
|
|
10267
|
+
title: s.title,
|
|
10268
|
+
description: s.description,
|
|
10269
|
+
visible: s.visible,
|
|
10270
|
+
position: s.position,
|
|
10271
|
+
expanded: s.expanded,
|
|
10272
|
+
collapsible: s.collapsible,
|
|
10273
|
+
parentGroupId: s.parentGroupId,
|
|
10274
|
+
repeatable: s.repeatable,
|
|
10275
|
+
minInstances: s.minInstances,
|
|
10276
|
+
maxInstances: s.maxInstances,
|
|
10277
|
+
css: s.css,
|
|
10003
10278
|
fields: s.fields.map((f) => ({
|
|
10004
10279
|
id: f.id,
|
|
10005
10280
|
type: f.type,
|
|
@@ -10010,6 +10285,7 @@ var FormBuilder = class {
|
|
|
10010
10285
|
}))
|
|
10011
10286
|
})),
|
|
10012
10287
|
selectedField: state.selectedFieldId,
|
|
10288
|
+
selectedSection: state.selectedSectionId,
|
|
10013
10289
|
isPreviewMode: state.isPreviewMode
|
|
10014
10290
|
});
|
|
10015
10291
|
if (schemaHash !== this.lastRenderedSchemaHash || previewModeChanged) {
|
|
@@ -10434,7 +10710,7 @@ var FormBuilder = class {
|
|
|
10434
10710
|
}
|
|
10435
10711
|
});
|
|
10436
10712
|
inner.appendChild(formNameInput);
|
|
10437
|
-
const sectionList = new SectionList(state.schema, state.selectedFieldId);
|
|
10713
|
+
const sectionList = new SectionList(state.schema, state.selectedFieldId, state.selectedSectionId);
|
|
10438
10714
|
inner.appendChild(sectionList.getElement());
|
|
10439
10715
|
const addSectionBtn = createElement("button", {
|
|
10440
10716
|
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",
|
|
@@ -10470,142 +10746,612 @@ var FormBuilder = class {
|
|
|
10470
10746
|
});
|
|
10471
10747
|
return container;
|
|
10472
10748
|
}
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10749
|
+
/**
|
|
10750
|
+
* Shared 1–12 grid span control (Field Settings + Group Settings).
|
|
10751
|
+
*/
|
|
10752
|
+
createGridSpanSelector(options) {
|
|
10753
|
+
const layoutGroup = createElement("div", { className: "layout-span-group" });
|
|
10754
|
+
const layoutLabelRow = createElement("div", { className: "flex items-center justify-between mb-2" });
|
|
10755
|
+
layoutLabelRow.appendChild(createElement("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", text: "Grid Span" }));
|
|
10756
|
+
const spanValueDisplay = createElement("span", {
|
|
10757
|
+
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",
|
|
10758
|
+
text: `${options.currentSpan}/12`,
|
|
10759
|
+
id: options.badgeElementId
|
|
10760
|
+
});
|
|
10761
|
+
layoutLabelRow.appendChild(spanValueDisplay);
|
|
10762
|
+
layoutGroup.appendChild(layoutLabelRow);
|
|
10763
|
+
const spanButtonsContainer = createElement("div", { className: "grid grid-cols-6 gap-2 mt-2" });
|
|
10764
|
+
for (let span = 1; span <= 12; span++) {
|
|
10765
|
+
const isActive = options.currentSpan === span;
|
|
10766
|
+
const spanBtn = createElement("button", {
|
|
10767
|
+
type: "button",
|
|
10768
|
+
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"}`,
|
|
10769
|
+
text: `${span}`,
|
|
10770
|
+
title: `${span} column${span > 1 ? "s" : ""} (${Math.round(span / 12 * 100)}%)`
|
|
10771
|
+
});
|
|
10772
|
+
spanBtn.addEventListener("click", (e) => {
|
|
10773
|
+
e.preventDefault();
|
|
10774
|
+
e.stopPropagation();
|
|
10775
|
+
options.onSelect(span);
|
|
10776
|
+
});
|
|
10777
|
+
spanButtonsContainer.appendChild(spanBtn);
|
|
10479
10778
|
}
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
const
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
"data-focus-id": `field-label-${selectedField.id}`,
|
|
10494
|
-
oninput: (e) => {
|
|
10495
|
-
const fieldId2 = selectedField.id;
|
|
10496
|
-
const value = e.target.value;
|
|
10497
|
-
const existing = labelUpdateTimeouts.get(fieldId2);
|
|
10498
|
-
if (existing)
|
|
10499
|
-
clearTimeout(existing);
|
|
10500
|
-
const timeoutId = setTimeout(() => {
|
|
10501
|
-
labelUpdateTimeouts.delete(fieldId2);
|
|
10502
|
-
formStore.getState().updateField(fieldId2, { label: value });
|
|
10503
|
-
}, LABEL_DEBOUNCE_MS);
|
|
10504
|
-
labelUpdateTimeouts.set(fieldId2, timeoutId);
|
|
10779
|
+
layoutGroup.appendChild(spanButtonsContainer);
|
|
10780
|
+
return layoutGroup;
|
|
10781
|
+
}
|
|
10782
|
+
/**
|
|
10783
|
+
* Shared Styling block (Padding, Background, Alignment, CSS class, Advanced CSS) — same as Field Settings.
|
|
10784
|
+
*/
|
|
10785
|
+
appendSharedStylingSection(body, target, focusState) {
|
|
10786
|
+
const cssHeader = createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3 mt-6", text: "Styling" });
|
|
10787
|
+
body.appendChild(cssHeader);
|
|
10788
|
+
const getEntity = () => {
|
|
10789
|
+
const st = formStore.getState();
|
|
10790
|
+
if (target.kind === "field") {
|
|
10791
|
+
return st.schema.sections.flatMap((s) => s.fields).find((f) => f.id === target.fieldId);
|
|
10505
10792
|
}
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
const
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
const
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
formStore.getState().updateField(selectedField.id, updates);
|
|
10527
|
-
this.render();
|
|
10793
|
+
return st.schema.sections.find((s) => s.id === target.sectionId);
|
|
10794
|
+
};
|
|
10795
|
+
const getStyleValue = (prop) => {
|
|
10796
|
+
const ent = getEntity();
|
|
10797
|
+
return ent?.css?.style?.[prop] || "";
|
|
10798
|
+
};
|
|
10799
|
+
const updateStyleProp = (prop, value) => {
|
|
10800
|
+
const ent = getEntity();
|
|
10801
|
+
if (!ent)
|
|
10802
|
+
return;
|
|
10803
|
+
const currentStyle = ent.css?.style || {};
|
|
10804
|
+
const newStyle = { ...currentStyle };
|
|
10805
|
+
if (value) {
|
|
10806
|
+
newStyle[prop] = value;
|
|
10807
|
+
} else {
|
|
10808
|
+
delete newStyle[prop];
|
|
10809
|
+
}
|
|
10810
|
+
const updatePayload = {
|
|
10811
|
+
css: {
|
|
10812
|
+
style: Object.keys(newStyle).length > 0 ? newStyle : void 0
|
|
10528
10813
|
}
|
|
10529
|
-
}
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
if (selectedField.valueSource === "formula") {
|
|
10535
|
-
const schema = formStore.getState().schema;
|
|
10536
|
-
const numericFields = getNumericFieldsForFormula(schema, selectedField.id);
|
|
10537
|
-
const availableIds = numericFields.map((f) => f.id);
|
|
10538
|
-
const availableNames = numericFields.map((f) => f.fieldName);
|
|
10539
|
-
const formulaGroup = createElement("div", { className: "mb-3" });
|
|
10540
|
-
formulaGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Formula" }));
|
|
10541
|
-
const formulaInput = createElement("input", {
|
|
10542
|
-
type: "text",
|
|
10543
|
-
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent font-mono text-sm",
|
|
10544
|
-
value: selectedField.formula || "",
|
|
10545
|
-
placeholder: "e.g. quantity * price",
|
|
10546
|
-
"data-focus-id": `field-formula-${selectedField.id}`,
|
|
10547
|
-
oninput: (e) => {
|
|
10548
|
-
const formula = e.target.value.trim();
|
|
10549
|
-
const deps = parseFormulaDependencies(formula);
|
|
10550
|
-
const validation = validateFormula(formula, availableIds, availableNames, selectedField.id);
|
|
10551
|
-
const hasCircular = deps.length > 0 && detectCircularDependency(schema, selectedField.id, formula, deps);
|
|
10552
|
-
const errEl = formulaGroup.querySelector(".formula-error");
|
|
10553
|
-
if (errEl) {
|
|
10554
|
-
if (validation.valid && !hasCircular) {
|
|
10555
|
-
errEl.textContent = "";
|
|
10556
|
-
errEl.classList.add("hidden");
|
|
10557
|
-
} else {
|
|
10558
|
-
errEl.textContent = !validation.valid ? validation.error : "Circular dependency detected";
|
|
10559
|
-
errEl.classList.remove("hidden");
|
|
10560
|
-
}
|
|
10561
|
-
}
|
|
10562
|
-
formStore.getState().updateField(selectedField.id, { formula, dependencies: deps });
|
|
10563
|
-
}
|
|
10564
|
-
});
|
|
10565
|
-
formulaGroup.appendChild(formulaInput);
|
|
10566
|
-
const formulaError = createElement("div", { className: "text-xs text-red-600 dark:text-red-400 mt-1 formula-error hidden" });
|
|
10567
|
-
formulaGroup.appendChild(formulaError);
|
|
10568
|
-
body.appendChild(formulaGroup);
|
|
10569
|
-
const insertGroup = createElement("div", { className: "mb-3" });
|
|
10570
|
-
insertGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Insert Field" }));
|
|
10571
|
-
const insertSelect = createElement("select", {
|
|
10572
|
-
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10573
|
-
onchange: (e) => {
|
|
10574
|
-
const sel = e.target;
|
|
10575
|
-
const ref = sel.value;
|
|
10576
|
-
if (!ref)
|
|
10577
|
-
return;
|
|
10578
|
-
const current = selectedField.formula || "";
|
|
10579
|
-
const insert = current ? ` ${ref} ` : ref;
|
|
10580
|
-
const newFormula = current + insert;
|
|
10581
|
-
formStore.getState().updateField(selectedField.id, {
|
|
10582
|
-
formula: newFormula,
|
|
10583
|
-
dependencies: parseFormulaDependencies(newFormula)
|
|
10584
|
-
});
|
|
10585
|
-
formulaInput.value = newFormula;
|
|
10586
|
-
sel.value = "";
|
|
10587
|
-
this.render();
|
|
10588
|
-
}
|
|
10589
|
-
});
|
|
10590
|
-
insertSelect.appendChild(createElement("option", { value: "", text: "Select field to insert...", selected: true }));
|
|
10591
|
-
numericFields.forEach((f) => {
|
|
10592
|
-
const ref = f.fieldName !== f.id ? f.fieldName : f.id;
|
|
10593
|
-
insertSelect.appendChild(createElement("option", { value: ref, text: `${f.label} (${ref})` }));
|
|
10594
|
-
});
|
|
10595
|
-
insertGroup.appendChild(insertSelect);
|
|
10596
|
-
body.appendChild(insertGroup);
|
|
10597
|
-
const hintEl = createElement("p", {
|
|
10598
|
-
className: "text-xs text-gray-500 dark:text-gray-400 mb-2",
|
|
10599
|
-
text: "Use +, -, *, / and parentheses. Reference fields by their name or ID."
|
|
10600
|
-
});
|
|
10601
|
-
body.appendChild(hintEl);
|
|
10814
|
+
};
|
|
10815
|
+
if (target.kind === "field") {
|
|
10816
|
+
formStore.getState().updateField(target.fieldId, updatePayload);
|
|
10817
|
+
} else {
|
|
10818
|
+
formStore.getState().updateSection(target.sectionId, updatePayload);
|
|
10602
10819
|
}
|
|
10603
|
-
}
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10820
|
+
};
|
|
10821
|
+
const entityId = target.kind === "field" ? target.fieldId : target.sectionId;
|
|
10822
|
+
const paddingGroup = createElement("div", { className: "mb-3" });
|
|
10823
|
+
paddingGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Padding" }));
|
|
10824
|
+
const paddingSelect = createElement("select", {
|
|
10825
|
+
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",
|
|
10826
|
+
onchange: (e) => {
|
|
10827
|
+
updateStyleProp("padding", e.target.value);
|
|
10828
|
+
}
|
|
10829
|
+
});
|
|
10830
|
+
const paddingOptions = [
|
|
10831
|
+
{ value: "", label: "None" },
|
|
10832
|
+
{ value: "4px", label: "4px - Tight" },
|
|
10833
|
+
{ value: "8px", label: "8px - Normal" },
|
|
10834
|
+
{ value: "12px", label: "12px - Comfortable" },
|
|
10835
|
+
{ value: "16px", label: "16px - Spacious" },
|
|
10836
|
+
{ value: "24px", label: "24px - Large" }
|
|
10837
|
+
];
|
|
10838
|
+
paddingOptions.forEach((opt) => {
|
|
10839
|
+
paddingSelect.appendChild(createElement("option", { value: opt.value, text: opt.label, selected: getStyleValue("padding") === opt.value }));
|
|
10840
|
+
});
|
|
10841
|
+
paddingGroup.appendChild(paddingSelect);
|
|
10842
|
+
body.appendChild(paddingGroup);
|
|
10843
|
+
const bgColorGroup = createElement("div", { className: "mb-3" });
|
|
10844
|
+
bgColorGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Background Color" }));
|
|
10845
|
+
const bgColorRow = createElement("div", { className: "flex items-center gap-2" });
|
|
10846
|
+
const bgColorInput = createElement("input", {
|
|
10847
|
+
type: "color",
|
|
10848
|
+
className: "w-10 h-10 rounded border border-gray-300 cursor-pointer",
|
|
10849
|
+
value: getStyleValue("backgroundColor") || "#ffffff",
|
|
10850
|
+
onchange: (e) => {
|
|
10851
|
+
const color = e.target.value;
|
|
10852
|
+
updateStyleProp("backgroundColor", color === "#ffffff" ? "" : color);
|
|
10853
|
+
}
|
|
10854
|
+
});
|
|
10855
|
+
const bgColorClear = createElement("button", {
|
|
10856
|
+
type: "button",
|
|
10857
|
+
className: "px-2 py-1 text-xs text-gray-600 hover:text-gray-800 border border-gray-300 rounded",
|
|
10858
|
+
text: "Clear",
|
|
10859
|
+
onclick: () => {
|
|
10860
|
+
bgColorInput.value = "#ffffff";
|
|
10861
|
+
updateStyleProp("backgroundColor", "");
|
|
10862
|
+
}
|
|
10863
|
+
});
|
|
10864
|
+
bgColorRow.appendChild(bgColorInput);
|
|
10865
|
+
bgColorRow.appendChild(bgColorClear);
|
|
10866
|
+
bgColorGroup.appendChild(bgColorRow);
|
|
10867
|
+
body.appendChild(bgColorGroup);
|
|
10868
|
+
const alignGroup = createElement("div", { className: "mb-3" });
|
|
10869
|
+
alignGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Text Alignment" }));
|
|
10870
|
+
const alignButtonsRow = createElement("div", { className: "flex gap-1" });
|
|
10871
|
+
const alignments = [
|
|
10872
|
+
{ value: "left", icon: "AlignLeft" },
|
|
10873
|
+
{ value: "center", icon: "AlignCenter" },
|
|
10874
|
+
{ value: "right", icon: "AlignRight" }
|
|
10875
|
+
];
|
|
10876
|
+
const currentAlign = getStyleValue("textAlign") || "left";
|
|
10877
|
+
alignments.forEach((align) => {
|
|
10878
|
+
const isActive = currentAlign === align.value;
|
|
10879
|
+
const btn = createElement("button", {
|
|
10880
|
+
type: "button",
|
|
10881
|
+
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"}`,
|
|
10882
|
+
title: `Align ${align.value}`,
|
|
10883
|
+
onclick: () => {
|
|
10884
|
+
const newValue = align.value === "left" ? "" : align.value;
|
|
10885
|
+
updateStyleProp("textAlign", newValue);
|
|
10886
|
+
}
|
|
10887
|
+
}, [getIcon(align.icon, 16)]);
|
|
10888
|
+
alignButtonsRow.appendChild(btn);
|
|
10889
|
+
});
|
|
10890
|
+
alignGroup.appendChild(alignButtonsRow);
|
|
10891
|
+
body.appendChild(alignGroup);
|
|
10892
|
+
const cssClassGroup = createElement("div", { className: "mb-3" });
|
|
10893
|
+
cssClassGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Custom CSS Class" }));
|
|
10894
|
+
cssClassGroup.appendChild(createElement("input", {
|
|
10895
|
+
type: "text",
|
|
10896
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent text-sm",
|
|
10897
|
+
value: getEntity()?.css?.class || "",
|
|
10898
|
+
placeholder: "e.g. my-custom-class",
|
|
10899
|
+
"data-focus-id": `${target.kind}-css-class-${entityId}`,
|
|
10900
|
+
oninput: (e) => {
|
|
10901
|
+
const cssClass = e.target.value;
|
|
10902
|
+
const ent = getEntity();
|
|
10903
|
+
if (!ent)
|
|
10904
|
+
return;
|
|
10905
|
+
if (target.kind === "field") {
|
|
10906
|
+
formStore.getState().updateField(target.fieldId, {
|
|
10907
|
+
css: { class: cssClass || void 0 }
|
|
10908
|
+
});
|
|
10909
|
+
} else {
|
|
10910
|
+
formStore.getState().updateSection(target.sectionId, {
|
|
10911
|
+
css: { class: cssClass || void 0 }
|
|
10912
|
+
});
|
|
10913
|
+
}
|
|
10914
|
+
}
|
|
10915
|
+
}));
|
|
10916
|
+
body.appendChild(cssClassGroup);
|
|
10917
|
+
const isPanelExpanded = advancedCssPanelState.get(entityId) || false;
|
|
10918
|
+
const advancedToggleGroup = createElement("div", { className: "mb-3" });
|
|
10919
|
+
const advancedToggle = createElement("button", {
|
|
10920
|
+
type: "button",
|
|
10921
|
+
className: "text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1",
|
|
10922
|
+
onclick: () => {
|
|
10923
|
+
const advancedPanel2 = document.getElementById(`advanced-css-${entityId}`);
|
|
10924
|
+
if (advancedPanel2) {
|
|
10925
|
+
advancedPanel2.classList.toggle("hidden");
|
|
10926
|
+
const isHidden = advancedPanel2.classList.contains("hidden");
|
|
10927
|
+
advancedToggle.textContent = isHidden ? "\u25B6 Show Advanced CSS" : "\u25BC Hide Advanced CSS";
|
|
10928
|
+
advancedCssPanelState.set(entityId, !isHidden);
|
|
10929
|
+
}
|
|
10930
|
+
}
|
|
10931
|
+
});
|
|
10932
|
+
advancedToggle.textContent = isPanelExpanded ? "\u25BC Hide Advanced CSS" : "\u25B6 Show Advanced CSS";
|
|
10933
|
+
advancedToggleGroup.appendChild(advancedToggle);
|
|
10934
|
+
body.appendChild(advancedToggleGroup);
|
|
10935
|
+
const advancedPanel = createElement("div", {
|
|
10936
|
+
className: isPanelExpanded ? "mb-3" : "mb-3 hidden",
|
|
10937
|
+
id: `advanced-css-${entityId}`
|
|
10938
|
+
});
|
|
10939
|
+
advancedPanel.appendChild(createElement("label", { className: "block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1", text: "Raw CSS Style (JSON)" }));
|
|
10940
|
+
const cssStyleId = `${target.kind}-css-style-${entityId}`;
|
|
10941
|
+
const freshEnt = getEntity();
|
|
10942
|
+
let initialCssStyleValue = freshEnt?.css?.style ? JSON.stringify(freshEnt.css.style, null, 2) : "";
|
|
10943
|
+
const preservedValue = focusState?.id === cssStyleId ? focusState.value : void 0;
|
|
10944
|
+
if (preservedValue !== void 0) {
|
|
10945
|
+
initialCssStyleValue = preservedValue;
|
|
10946
|
+
}
|
|
10947
|
+
const cssStyleTextarea = createElement("textarea", {
|
|
10948
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent text-xs font-mono",
|
|
10949
|
+
rows: 3,
|
|
10950
|
+
placeholder: '{"padding": "8px", "backgroundColor": "#f0f0f0"}',
|
|
10951
|
+
"data-focus-id": cssStyleId,
|
|
10952
|
+
"data-was-focused": "false",
|
|
10953
|
+
onfocus: (e) => {
|
|
10954
|
+
const textarea = e.target;
|
|
10955
|
+
textarea.setAttribute("data-was-focused", "true");
|
|
10956
|
+
const ent = getEntity();
|
|
10957
|
+
if (!textarea.value.trim() && ent?.css?.style && Object.keys(ent.css.style).length > 0) {
|
|
10958
|
+
textarea.value = JSON.stringify(ent.css.style, null, 2);
|
|
10959
|
+
}
|
|
10960
|
+
},
|
|
10961
|
+
oninput: (e) => {
|
|
10962
|
+
const styleText = e.target.value;
|
|
10963
|
+
const ent = getEntity();
|
|
10964
|
+
if (!ent)
|
|
10965
|
+
return;
|
|
10966
|
+
try {
|
|
10967
|
+
if (styleText.trim()) {
|
|
10968
|
+
const styleObj = JSON.parse(styleText);
|
|
10969
|
+
if (target.kind === "field") {
|
|
10970
|
+
formStore.getState().updateField(target.fieldId, {
|
|
10971
|
+
css: { ...ent.css, style: styleObj }
|
|
10972
|
+
});
|
|
10973
|
+
} else {
|
|
10974
|
+
formStore.getState().updateSection(target.sectionId, {
|
|
10975
|
+
css: { ...ent.css, style: styleObj }
|
|
10976
|
+
});
|
|
10977
|
+
}
|
|
10978
|
+
}
|
|
10979
|
+
} catch {
|
|
10980
|
+
}
|
|
10981
|
+
},
|
|
10982
|
+
onblur: (e) => {
|
|
10983
|
+
const textarea = e.target;
|
|
10984
|
+
const styleText = textarea.value;
|
|
10985
|
+
const wasFocused = textarea.getAttribute("data-was-focused") === "true";
|
|
10986
|
+
if (!wasFocused)
|
|
10987
|
+
return;
|
|
10988
|
+
textarea.setAttribute("data-was-focused", "false");
|
|
10989
|
+
setTimeout(() => {
|
|
10990
|
+
if (!document.body.contains(textarea))
|
|
10991
|
+
return;
|
|
10992
|
+
const advPanel = document.getElementById(`advanced-css-${entityId}`);
|
|
10993
|
+
const isPanelVisible = advPanel && !advPanel.classList.contains("hidden");
|
|
10994
|
+
if (!isPanelVisible)
|
|
10995
|
+
return;
|
|
10996
|
+
const ent = getEntity();
|
|
10997
|
+
if (!ent)
|
|
10998
|
+
return;
|
|
10999
|
+
try {
|
|
11000
|
+
if (styleText.trim()) {
|
|
11001
|
+
const styleObj = JSON.parse(styleText);
|
|
11002
|
+
if (target.kind === "field") {
|
|
11003
|
+
formStore.getState().updateField(target.fieldId, {
|
|
11004
|
+
css: { ...ent.css, style: styleObj }
|
|
11005
|
+
});
|
|
11006
|
+
} else {
|
|
11007
|
+
formStore.getState().updateSection(target.sectionId, {
|
|
11008
|
+
css: { ...ent.css, style: styleObj }
|
|
11009
|
+
});
|
|
11010
|
+
}
|
|
11011
|
+
} else if (ent.css?.style && Object.keys(ent.css.style).length > 0) {
|
|
11012
|
+
textarea.value = JSON.stringify(ent.css.style, null, 2);
|
|
11013
|
+
} else {
|
|
11014
|
+
if (target.kind === "field") {
|
|
11015
|
+
formStore.getState().updateField(target.fieldId, {
|
|
11016
|
+
css: { ...ent.css, style: void 0 }
|
|
11017
|
+
});
|
|
11018
|
+
} else {
|
|
11019
|
+
formStore.getState().updateSection(target.sectionId, {
|
|
11020
|
+
css: { ...ent.css, style: void 0 }
|
|
11021
|
+
});
|
|
11022
|
+
}
|
|
11023
|
+
}
|
|
11024
|
+
} catch {
|
|
11025
|
+
if (ent.css?.style) {
|
|
11026
|
+
textarea.value = JSON.stringify(ent.css.style, null, 2);
|
|
11027
|
+
}
|
|
11028
|
+
}
|
|
11029
|
+
}, 0);
|
|
11030
|
+
}
|
|
11031
|
+
});
|
|
11032
|
+
cssStyleTextarea.value = initialCssStyleValue;
|
|
11033
|
+
advancedPanel.appendChild(cssStyleTextarea);
|
|
11034
|
+
body.appendChild(advancedPanel);
|
|
11035
|
+
}
|
|
11036
|
+
renderGroupSettingsPanel(section, allSections, focusState) {
|
|
11037
|
+
const panel = createElement("div", { className: " dark:bg-gray-900 flex flex-col h-full overflow-y-auto" });
|
|
11038
|
+
const header = createElement("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800" });
|
|
11039
|
+
header.appendChild(createElement("h2", { className: "font-semibold text-gray-900 dark:text-white", text: "Group Settings" }));
|
|
11040
|
+
header.appendChild(createElement("button", {
|
|
11041
|
+
className: "text-gray-500 hover:text-gray-700",
|
|
11042
|
+
onclick: () => formStore.getState().selectSection(null)
|
|
11043
|
+
}, [getIcon("X", 20)]));
|
|
11044
|
+
panel.appendChild(header);
|
|
11045
|
+
const body = createElement("div", { className: "flex-1 overflow-y-auto p-4 px-2 space-y-3", id: "config-panel-body" });
|
|
11046
|
+
const sectionId = section.id;
|
|
11047
|
+
const sectionHeading = (text) => createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3 mt-2", text });
|
|
11048
|
+
body.appendChild(sectionHeading("Basic"));
|
|
11049
|
+
const labelGroup = createElement("div");
|
|
11050
|
+
labelGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Group Label" }));
|
|
11051
|
+
labelGroup.appendChild(createElement("input", {
|
|
11052
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11053
|
+
value: section.name ?? section.title,
|
|
11054
|
+
"data-focus-id": `group-label-${sectionId}`,
|
|
11055
|
+
oninput: (e) => {
|
|
11056
|
+
const v = e.target.value;
|
|
11057
|
+
const existing = labelUpdateTimeouts.get(`group-${sectionId}`);
|
|
11058
|
+
if (existing)
|
|
11059
|
+
clearTimeout(existing);
|
|
11060
|
+
const timeoutId = setTimeout(() => {
|
|
11061
|
+
labelUpdateTimeouts.delete(`group-${sectionId}`);
|
|
11062
|
+
formStore.getState().updateSection(sectionId, { title: v, name: v });
|
|
11063
|
+
}, LABEL_DEBOUNCE_MS);
|
|
11064
|
+
labelUpdateTimeouts.set(`group-${sectionId}`, timeoutId);
|
|
11065
|
+
}
|
|
11066
|
+
}));
|
|
11067
|
+
body.appendChild(labelGroup);
|
|
11068
|
+
const descGroup = createElement("div");
|
|
11069
|
+
descGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Description" }));
|
|
11070
|
+
descGroup.appendChild(createElement("input", {
|
|
11071
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11072
|
+
value: section.description ?? "",
|
|
11073
|
+
placeholder: "Optional",
|
|
11074
|
+
"data-focus-id": `group-desc-${sectionId}`,
|
|
11075
|
+
oninput: (e) => {
|
|
11076
|
+
const v = e.target.value;
|
|
11077
|
+
formStore.getState().updateSection(sectionId, { description: v || null });
|
|
11078
|
+
}
|
|
11079
|
+
}));
|
|
11080
|
+
body.appendChild(descGroup);
|
|
11081
|
+
body.appendChild(sectionHeading("Layout"));
|
|
11082
|
+
const order = section.order ?? 0;
|
|
11083
|
+
const pos = section.position ?? { width: 12};
|
|
11084
|
+
const currentWidth = Math.max(1, Math.min(12, pos.width ?? 12));
|
|
11085
|
+
body.appendChild(
|
|
11086
|
+
this.createGridSpanSelector({
|
|
11087
|
+
currentSpan: currentWidth,
|
|
11088
|
+
badgeElementId: `group-span-badge-${sectionId}`,
|
|
11089
|
+
onSelect: (span) => {
|
|
11090
|
+
const st = formStore.getState().schema.sections.find((s) => s.id === sectionId);
|
|
11091
|
+
if (!st)
|
|
11092
|
+
return;
|
|
11093
|
+
const p = st.position ?? { row: order, column: 0, width: 12, order: st.order ?? order };
|
|
11094
|
+
formStore.getState().updateSection(sectionId, {
|
|
11095
|
+
position: { ...p, width: span, order: p.order ?? st.order ?? order }
|
|
11096
|
+
});
|
|
11097
|
+
}
|
|
11098
|
+
})
|
|
11099
|
+
);
|
|
11100
|
+
body.appendChild(sectionHeading("Fields in group"));
|
|
11101
|
+
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" });
|
|
11102
|
+
if (section.fields.length === 0) {
|
|
11103
|
+
fieldRows.appendChild(createElement("div", { className: "text-gray-400 italic text-xs py-1", text: "No fields added yet" }));
|
|
11104
|
+
} else {
|
|
11105
|
+
section.fields.forEach((f) => {
|
|
11106
|
+
const display = f.displayName || f.label || f.id;
|
|
11107
|
+
fieldRows.appendChild(createElement("div", { className: "truncate", text: display }));
|
|
11108
|
+
});
|
|
11109
|
+
}
|
|
11110
|
+
body.appendChild(fieldRows);
|
|
11111
|
+
body.appendChild(sectionHeading("Display options"));
|
|
11112
|
+
body.appendChild(
|
|
11113
|
+
this.createCheckboxField(
|
|
11114
|
+
"Visible",
|
|
11115
|
+
section.visible !== false,
|
|
11116
|
+
(checked) => formStore.getState().updateSection(sectionId, { visible: checked }),
|
|
11117
|
+
`group-visible-${sectionId}`
|
|
11118
|
+
)
|
|
11119
|
+
);
|
|
11120
|
+
body.appendChild(
|
|
11121
|
+
this.createCheckboxField(
|
|
11122
|
+
"Show side/down arrow to expand/collapse",
|
|
11123
|
+
section.collapsible !== false,
|
|
11124
|
+
(checked) => formStore.getState().updateSection(sectionId, { collapsible: checked }),
|
|
11125
|
+
`group-collapsible-${sectionId}`
|
|
11126
|
+
)
|
|
11127
|
+
);
|
|
11128
|
+
body.appendChild(sectionHeading("Parent group"));
|
|
11129
|
+
const parentGroup = createElement("div", { className: "mb-2" });
|
|
11130
|
+
parentGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Parent Group" }));
|
|
11131
|
+
const parentSelect = createElement("select", {
|
|
11132
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11133
|
+
value: section.parentGroupId || "",
|
|
11134
|
+
onchange: (e) => {
|
|
11135
|
+
const v = e.target.value;
|
|
11136
|
+
const selfId = sectionId;
|
|
11137
|
+
if (!v || v === selfId) {
|
|
11138
|
+
formStore.getState().updateSection(selfId, { parentGroupId: null });
|
|
11139
|
+
} else {
|
|
11140
|
+
formStore.getState().updateSection(selfId, { parentGroupId: v });
|
|
11141
|
+
}
|
|
11142
|
+
}
|
|
11143
|
+
});
|
|
11144
|
+
parentSelect.appendChild(createElement("option", { value: "", text: "None", selected: !section.parentGroupId }));
|
|
11145
|
+
const validParentIds = new Set(getValidParentSectionIds(allSections, sectionId));
|
|
11146
|
+
allSections.forEach((g) => {
|
|
11147
|
+
if (!validParentIds.has(g.id))
|
|
11148
|
+
return;
|
|
11149
|
+
const label = g.name ?? g.title;
|
|
11150
|
+
parentSelect.appendChild(
|
|
11151
|
+
createElement("option", {
|
|
11152
|
+
value: g.id,
|
|
11153
|
+
text: label,
|
|
11154
|
+
selected: section.parentGroupId === g.id
|
|
11155
|
+
})
|
|
11156
|
+
);
|
|
11157
|
+
});
|
|
11158
|
+
parentGroup.appendChild(parentSelect);
|
|
11159
|
+
body.appendChild(parentGroup);
|
|
11160
|
+
body.appendChild(sectionHeading("Repeatable group"));
|
|
11161
|
+
body.appendChild(
|
|
11162
|
+
this.createCheckboxField(
|
|
11163
|
+
"Allow multiple instances",
|
|
11164
|
+
section.repeatable === true,
|
|
11165
|
+
(checked) => formStore.getState().updateSection(sectionId, { repeatable: checked }),
|
|
11166
|
+
`group-repeatable-${sectionId}`
|
|
11167
|
+
)
|
|
11168
|
+
);
|
|
11169
|
+
if (section.repeatable === true) {
|
|
11170
|
+
const minG = createElement("div", { className: "mb-2" });
|
|
11171
|
+
minG.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Min Instances" }));
|
|
11172
|
+
minG.appendChild(createElement("input", {
|
|
11173
|
+
type: "number",
|
|
11174
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11175
|
+
value: section.minInstances != null ? String(section.minInstances) : "",
|
|
11176
|
+
placeholder: "e.g. 1",
|
|
11177
|
+
"data-focus-id": `group-min-inst-${sectionId}`,
|
|
11178
|
+
oninput: (e) => {
|
|
11179
|
+
const raw = e.target.value;
|
|
11180
|
+
if (raw === "") {
|
|
11181
|
+
formStore.getState().updateSection(sectionId, { minInstances: null });
|
|
11182
|
+
return;
|
|
11183
|
+
}
|
|
11184
|
+
const n = parseInt(raw, 10);
|
|
11185
|
+
formStore.getState().updateSection(sectionId, { minInstances: isNaN(n) ? null : n });
|
|
11186
|
+
}
|
|
11187
|
+
}));
|
|
11188
|
+
body.appendChild(minG);
|
|
11189
|
+
const maxG = createElement("div", { className: "mb-2" });
|
|
11190
|
+
maxG.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Max Instances" }));
|
|
11191
|
+
maxG.appendChild(createElement("input", {
|
|
11192
|
+
type: "number",
|
|
11193
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11194
|
+
value: section.maxInstances != null ? String(section.maxInstances) : "",
|
|
11195
|
+
placeholder: "Unlimited",
|
|
11196
|
+
"data-focus-id": `group-max-inst-${sectionId}`,
|
|
11197
|
+
oninput: (e) => {
|
|
11198
|
+
const raw = e.target.value;
|
|
11199
|
+
if (raw === "") {
|
|
11200
|
+
formStore.getState().updateSection(sectionId, { maxInstances: null });
|
|
11201
|
+
return;
|
|
11202
|
+
}
|
|
11203
|
+
const n = parseInt(raw, 10);
|
|
11204
|
+
formStore.getState().updateSection(sectionId, { maxInstances: isNaN(n) ? null : n });
|
|
11205
|
+
}
|
|
11206
|
+
}));
|
|
11207
|
+
body.appendChild(maxG);
|
|
11208
|
+
}
|
|
11209
|
+
this.appendSharedStylingSection(body, { kind: "section", sectionId }, focusState);
|
|
11210
|
+
panel.appendChild(body);
|
|
11211
|
+
return panel;
|
|
11212
|
+
}
|
|
11213
|
+
renderConfigPanel(state, focusState = null) {
|
|
11214
|
+
const panel = createElement("div", { className: " dark:bg-gray-900 flex flex-col h-full overflow-y-auto" });
|
|
11215
|
+
if (state.selectedSectionId) {
|
|
11216
|
+
const selectedSection = state.schema.sections.find((s) => s.id === state.selectedSectionId);
|
|
11217
|
+
if (selectedSection) {
|
|
11218
|
+
return this.renderGroupSettingsPanel(selectedSection, state.schema.sections, focusState);
|
|
11219
|
+
}
|
|
11220
|
+
}
|
|
11221
|
+
const selectedField = state.schema.sections.flatMap((s) => s.fields).find((f) => f.id === state.selectedFieldId);
|
|
11222
|
+
if (!selectedField) {
|
|
11223
|
+
panel.appendChild(createElement("div", { className: "p-4 text-center text-gray-500", text: "Select a field or section to configure" }));
|
|
11224
|
+
return panel;
|
|
11225
|
+
}
|
|
11226
|
+
const header = createElement("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800" });
|
|
11227
|
+
header.appendChild(createElement("h2", { className: "font-semibold text-gray-900 dark:text-white", text: "Field Settings" }));
|
|
11228
|
+
header.appendChild(createElement("button", {
|
|
11229
|
+
className: "text-gray-500 hover:text-gray-700",
|
|
11230
|
+
onclick: () => formStore.getState().selectField(null)
|
|
11231
|
+
}, [getIcon("X", 20)]));
|
|
11232
|
+
panel.appendChild(header);
|
|
11233
|
+
const body = createElement("div", { className: "flex-1 overflow-y-auto p-4 px-2 space-y-3", id: "config-panel-body" });
|
|
11234
|
+
const labelGroup = createElement("div");
|
|
11235
|
+
labelGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Label" }));
|
|
11236
|
+
labelGroup.appendChild(createElement("input", {
|
|
11237
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11238
|
+
value: selectedField.label,
|
|
11239
|
+
"data-focus-id": `field-label-${selectedField.id}`,
|
|
11240
|
+
oninput: (e) => {
|
|
11241
|
+
const fieldId2 = selectedField.id;
|
|
11242
|
+
const value = e.target.value;
|
|
11243
|
+
const existing = labelUpdateTimeouts.get(fieldId2);
|
|
11244
|
+
if (existing)
|
|
11245
|
+
clearTimeout(existing);
|
|
11246
|
+
const timeoutId = setTimeout(() => {
|
|
11247
|
+
labelUpdateTimeouts.delete(fieldId2);
|
|
11248
|
+
formStore.getState().updateField(fieldId2, { label: value });
|
|
11249
|
+
}, LABEL_DEBOUNCE_MS);
|
|
11250
|
+
labelUpdateTimeouts.set(fieldId2, timeoutId);
|
|
11251
|
+
}
|
|
11252
|
+
}));
|
|
11253
|
+
body.appendChild(labelGroup);
|
|
11254
|
+
if (selectedField.type === "number") {
|
|
11255
|
+
const valueSourceHeader = createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2 mt-4", text: "Value Source" });
|
|
11256
|
+
body.appendChild(valueSourceHeader);
|
|
11257
|
+
const valueSourceGroup = createElement("div", { className: "mb-3" });
|
|
11258
|
+
valueSourceGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Source" }));
|
|
11259
|
+
const valueSourceSelect = createElement("select", {
|
|
11260
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11261
|
+
value: selectedField.valueSource || "manual",
|
|
11262
|
+
onchange: (e) => {
|
|
11263
|
+
const source = e.target.value;
|
|
11264
|
+
const updates = { valueSource: source };
|
|
11265
|
+
if (source === "manual") {
|
|
11266
|
+
updates.formula = void 0;
|
|
11267
|
+
updates.dependencies = void 0;
|
|
11268
|
+
} else if (source === "formula") {
|
|
11269
|
+
updates.formula = selectedField.formula || "";
|
|
11270
|
+
updates.dependencies = selectedField.dependencies || [];
|
|
11271
|
+
}
|
|
11272
|
+
formStore.getState().updateField(selectedField.id, updates);
|
|
11273
|
+
this.render();
|
|
11274
|
+
}
|
|
11275
|
+
});
|
|
11276
|
+
valueSourceSelect.appendChild(createElement("option", { value: "manual", text: "Manual", selected: (selectedField.valueSource || "manual") === "manual" }));
|
|
11277
|
+
valueSourceSelect.appendChild(createElement("option", { value: "formula", text: "Formula", selected: selectedField.valueSource === "formula" }));
|
|
11278
|
+
valueSourceGroup.appendChild(valueSourceSelect);
|
|
11279
|
+
body.appendChild(valueSourceGroup);
|
|
11280
|
+
if (selectedField.valueSource === "formula") {
|
|
11281
|
+
const schema = formStore.getState().schema;
|
|
11282
|
+
const numericFields = getNumericFieldsForFormula(schema, selectedField.id);
|
|
11283
|
+
const availableIds = numericFields.map((f) => f.id);
|
|
11284
|
+
const availableNames = numericFields.map((f) => f.fieldName);
|
|
11285
|
+
const formulaGroup = createElement("div", { className: "mb-3" });
|
|
11286
|
+
formulaGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Formula" }));
|
|
11287
|
+
const formulaInput = createElement("input", {
|
|
11288
|
+
type: "text",
|
|
11289
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent font-mono text-sm",
|
|
11290
|
+
value: selectedField.formula || "",
|
|
11291
|
+
placeholder: "e.g. quantity * price",
|
|
11292
|
+
"data-focus-id": `field-formula-${selectedField.id}`,
|
|
11293
|
+
oninput: (e) => {
|
|
11294
|
+
const formula = e.target.value.trim();
|
|
11295
|
+
const deps = parseFormulaDependencies(formula);
|
|
11296
|
+
const validation = validateFormula(formula, availableIds, availableNames, selectedField.id);
|
|
11297
|
+
const hasCircular = deps.length > 0 && detectCircularDependency(schema, selectedField.id, formula, deps);
|
|
11298
|
+
const errEl = formulaGroup.querySelector(".formula-error");
|
|
11299
|
+
if (errEl) {
|
|
11300
|
+
if (validation.valid && !hasCircular) {
|
|
11301
|
+
errEl.textContent = "";
|
|
11302
|
+
errEl.classList.add("hidden");
|
|
11303
|
+
} else {
|
|
11304
|
+
errEl.textContent = !validation.valid ? validation.error : "Circular dependency detected";
|
|
11305
|
+
errEl.classList.remove("hidden");
|
|
11306
|
+
}
|
|
11307
|
+
}
|
|
11308
|
+
formStore.getState().updateField(selectedField.id, { formula, dependencies: deps });
|
|
11309
|
+
}
|
|
11310
|
+
});
|
|
11311
|
+
formulaGroup.appendChild(formulaInput);
|
|
11312
|
+
const formulaError = createElement("div", { className: "text-xs text-red-600 dark:text-red-400 mt-1 formula-error hidden" });
|
|
11313
|
+
formulaGroup.appendChild(formulaError);
|
|
11314
|
+
body.appendChild(formulaGroup);
|
|
11315
|
+
const insertGroup = createElement("div", { className: "mb-3" });
|
|
11316
|
+
insertGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Insert Field" }));
|
|
11317
|
+
const insertSelect = createElement("select", {
|
|
11318
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11319
|
+
onchange: (e) => {
|
|
11320
|
+
const sel = e.target;
|
|
11321
|
+
const ref = sel.value;
|
|
11322
|
+
if (!ref)
|
|
11323
|
+
return;
|
|
11324
|
+
const current = selectedField.formula || "";
|
|
11325
|
+
const insert = current ? ` ${ref} ` : ref;
|
|
11326
|
+
const newFormula = current + insert;
|
|
11327
|
+
formStore.getState().updateField(selectedField.id, {
|
|
11328
|
+
formula: newFormula,
|
|
11329
|
+
dependencies: parseFormulaDependencies(newFormula)
|
|
11330
|
+
});
|
|
11331
|
+
formulaInput.value = newFormula;
|
|
11332
|
+
sel.value = "";
|
|
11333
|
+
this.render();
|
|
11334
|
+
}
|
|
11335
|
+
});
|
|
11336
|
+
insertSelect.appendChild(createElement("option", { value: "", text: "Select field to insert...", selected: true }));
|
|
11337
|
+
numericFields.forEach((f) => {
|
|
11338
|
+
const ref = f.fieldName !== f.id ? f.fieldName : f.id;
|
|
11339
|
+
insertSelect.appendChild(createElement("option", { value: ref, text: `${f.label} (${ref})` }));
|
|
11340
|
+
});
|
|
11341
|
+
insertGroup.appendChild(insertSelect);
|
|
11342
|
+
body.appendChild(insertGroup);
|
|
11343
|
+
const hintEl = createElement("p", {
|
|
11344
|
+
className: "text-xs text-gray-500 dark:text-gray-400 mb-2",
|
|
11345
|
+
text: "Use +, -, *, / and parentheses. Reference fields by their name or ID."
|
|
11346
|
+
});
|
|
11347
|
+
body.appendChild(hintEl);
|
|
11348
|
+
}
|
|
11349
|
+
}
|
|
11350
|
+
if (selectedField.type !== "image") {
|
|
11351
|
+
const placeholderGroup = createElement("div");
|
|
11352
|
+
placeholderGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Placeholder" }));
|
|
11353
|
+
placeholderGroup.appendChild(createElement("input", {
|
|
11354
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10609
11355
|
value: selectedField.placeholder || "",
|
|
10610
11356
|
"data-focus-id": `field-placeholder-${selectedField.id}`,
|
|
10611
11357
|
oninput: (e) => {
|
|
@@ -10677,48 +11423,25 @@ var FormBuilder = class {
|
|
|
10677
11423
|
imageGroup.appendChild(btnRow);
|
|
10678
11424
|
body.appendChild(imageGroup);
|
|
10679
11425
|
}
|
|
10680
|
-
const layoutGroup = createElement("div", { className: "layout-span-group" });
|
|
10681
|
-
const layoutLabelRow = createElement("div", { className: "flex items-center justify-between mb-2" });
|
|
10682
|
-
layoutLabelRow.appendChild(createElement("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", text: "Grid Span" }));
|
|
10683
11426
|
const currentSpan = selectedField.layout?.span !== void 0 ? selectedField.layout.span : Math.max(1, Math.min(12, Math.round(parseWidth(selectedField.width || "100%") / 100 * 12)));
|
|
10684
|
-
const spanValueDisplay = createElement("span", {
|
|
10685
|
-
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",
|
|
10686
|
-
text: `${currentSpan}/12`,
|
|
10687
|
-
id: `span-value-${selectedField.id}`
|
|
10688
|
-
// const widthValueDisplay = createElement('span', {
|
|
10689
|
-
// 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',
|
|
10690
|
-
// text: `${currentWidth}%`,
|
|
10691
|
-
// id: `width-value-${selectedField.id}`
|
|
10692
|
-
});
|
|
10693
|
-
layoutLabelRow.appendChild(spanValueDisplay);
|
|
10694
|
-
layoutGroup.appendChild(layoutLabelRow);
|
|
10695
|
-
const spanButtonsContainer = createElement("div", { className: "grid grid-cols-6 gap-2 mt-2" });
|
|
10696
11427
|
const fieldId = selectedField.id;
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
const layout = field.layout || { row: 0, column: 0 };
|
|
10712
|
-
state2.updateField(fieldId, {
|
|
10713
|
-
layout: { ...layout, span },
|
|
10714
|
-
width: Math.round(span / 12 * 100)
|
|
10715
|
-
});
|
|
11428
|
+
body.appendChild(
|
|
11429
|
+
this.createGridSpanSelector({
|
|
11430
|
+
currentSpan,
|
|
11431
|
+
badgeElementId: `span-value-${fieldId}`,
|
|
11432
|
+
onSelect: (span) => {
|
|
11433
|
+
const st = formStore.getState();
|
|
11434
|
+
const field = st.schema.sections.flatMap((s) => s.fields).find((f) => f.id === fieldId);
|
|
11435
|
+
if (field) {
|
|
11436
|
+
const layout = field.layout || { row: 0, column: 0 };
|
|
11437
|
+
st.updateField(fieldId, {
|
|
11438
|
+
layout: { ...layout, span },
|
|
11439
|
+
width: Math.round(span / 12 * 100)
|
|
11440
|
+
});
|
|
11441
|
+
}
|
|
10716
11442
|
}
|
|
10717
|
-
})
|
|
10718
|
-
|
|
10719
|
-
}
|
|
10720
|
-
layoutGroup.appendChild(spanButtonsContainer);
|
|
10721
|
-
body.appendChild(layoutGroup);
|
|
11443
|
+
})
|
|
11444
|
+
);
|
|
10722
11445
|
body.appendChild(this.createCheckboxField(
|
|
10723
11446
|
"Required",
|
|
10724
11447
|
!!selectedField.required || !!selectedField.validations?.required,
|
|
@@ -10816,9 +11539,9 @@ var FormBuilder = class {
|
|
|
10816
11539
|
oninput: (e) => formStore.getState().updateField(selectedField.id, { nameGeneratorSuffix: e.target.value })
|
|
10817
11540
|
}));
|
|
10818
11541
|
body.appendChild(suffixGroup);
|
|
10819
|
-
const
|
|
10820
|
-
|
|
10821
|
-
|
|
11542
|
+
const paddingGroup = createElement("div", { className: "mb-3" });
|
|
11543
|
+
paddingGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "ID Padding" }));
|
|
11544
|
+
paddingGroup.appendChild(createElement("input", {
|
|
10822
11545
|
type: "number",
|
|
10823
11546
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10824
11547
|
value: String(selectedField.nameGeneratorIdPadding ?? 4),
|
|
@@ -10830,7 +11553,7 @@ var FormBuilder = class {
|
|
|
10830
11553
|
formStore.getState().updateField(selectedField.id, { nameGeneratorIdPadding: isNaN(val) ? 4 : Math.max(1, Math.min(10, val)) });
|
|
10831
11554
|
}
|
|
10832
11555
|
}));
|
|
10833
|
-
body.appendChild(
|
|
11556
|
+
body.appendChild(paddingGroup);
|
|
10834
11557
|
}
|
|
10835
11558
|
if (selectedField.type === "binary_choice") {
|
|
10836
11559
|
const bcHeader = createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3 mt-6", text: "Yes/No Toggle Settings" });
|
|
@@ -12044,234 +12767,7 @@ var FormBuilder = class {
|
|
|
12044
12767
|
}
|
|
12045
12768
|
}
|
|
12046
12769
|
}
|
|
12047
|
-
|
|
12048
|
-
body.appendChild(cssHeader);
|
|
12049
|
-
const getStyleValue = (prop) => selectedField.css?.style?.[prop] || "";
|
|
12050
|
-
const updateStyleProp = (prop, value) => {
|
|
12051
|
-
const state2 = formStore.getState();
|
|
12052
|
-
const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12053
|
-
if (!freshField) {
|
|
12054
|
-
return;
|
|
12055
|
-
}
|
|
12056
|
-
const currentStyle = freshField.css?.style || {};
|
|
12057
|
-
const newStyle = { ...currentStyle };
|
|
12058
|
-
if (value) {
|
|
12059
|
-
newStyle[prop] = value;
|
|
12060
|
-
} else {
|
|
12061
|
-
delete newStyle[prop];
|
|
12062
|
-
}
|
|
12063
|
-
const updatePayload = {
|
|
12064
|
-
css: {
|
|
12065
|
-
style: Object.keys(newStyle).length > 0 ? newStyle : void 0
|
|
12066
|
-
// Do NOT spread freshField.css here - let updateField preserve class
|
|
12067
|
-
}
|
|
12068
|
-
};
|
|
12069
|
-
state2.updateField(selectedField.id, updatePayload);
|
|
12070
|
-
};
|
|
12071
|
-
const paddingGroup = createElement("div", { className: "mb-3" });
|
|
12072
|
-
paddingGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Padding" }));
|
|
12073
|
-
const paddingSelect = createElement("select", {
|
|
12074
|
-
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",
|
|
12075
|
-
onchange: (e) => {
|
|
12076
|
-
updateStyleProp("padding", e.target.value);
|
|
12077
|
-
}
|
|
12078
|
-
});
|
|
12079
|
-
const paddingOptions = [
|
|
12080
|
-
{ value: "", label: "None" },
|
|
12081
|
-
{ value: "4px", label: "4px - Tight" },
|
|
12082
|
-
{ value: "8px", label: "8px - Normal" },
|
|
12083
|
-
{ value: "12px", label: "12px - Comfortable" },
|
|
12084
|
-
{ value: "16px", label: "16px - Spacious" },
|
|
12085
|
-
{ value: "24px", label: "24px - Large" }
|
|
12086
|
-
];
|
|
12087
|
-
paddingOptions.forEach((opt) => {
|
|
12088
|
-
paddingSelect.appendChild(createElement("option", { value: opt.value, text: opt.label, selected: getStyleValue("padding") === opt.value }));
|
|
12089
|
-
});
|
|
12090
|
-
paddingGroup.appendChild(paddingSelect);
|
|
12091
|
-
body.appendChild(paddingGroup);
|
|
12092
|
-
const bgColorGroup = createElement("div", { className: "mb-3" });
|
|
12093
|
-
bgColorGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Background Color" }));
|
|
12094
|
-
const bgColorRow = createElement("div", { className: "flex items-center gap-2" });
|
|
12095
|
-
const bgColorInput = createElement("input", {
|
|
12096
|
-
type: "color",
|
|
12097
|
-
className: "w-10 h-10 rounded border border-gray-300 cursor-pointer",
|
|
12098
|
-
value: getStyleValue("backgroundColor") || "#ffffff",
|
|
12099
|
-
onchange: (e) => {
|
|
12100
|
-
const color = e.target.value;
|
|
12101
|
-
updateStyleProp("backgroundColor", color === "#ffffff" ? "" : color);
|
|
12102
|
-
}
|
|
12103
|
-
});
|
|
12104
|
-
const bgColorClear = createElement("button", {
|
|
12105
|
-
type: "button",
|
|
12106
|
-
className: "px-2 py-1 text-xs text-gray-600 hover:text-gray-800 border border-gray-300 rounded",
|
|
12107
|
-
text: "Clear",
|
|
12108
|
-
onclick: () => {
|
|
12109
|
-
bgColorInput.value = "#ffffff";
|
|
12110
|
-
updateStyleProp("backgroundColor", "");
|
|
12111
|
-
}
|
|
12112
|
-
});
|
|
12113
|
-
bgColorRow.appendChild(bgColorInput);
|
|
12114
|
-
bgColorRow.appendChild(bgColorClear);
|
|
12115
|
-
bgColorGroup.appendChild(bgColorRow);
|
|
12116
|
-
body.appendChild(bgColorGroup);
|
|
12117
|
-
const alignGroup = createElement("div", { className: "mb-3" });
|
|
12118
|
-
alignGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Text Alignment" }));
|
|
12119
|
-
const alignButtonsRow = createElement("div", { className: "flex gap-1" });
|
|
12120
|
-
const alignments = [
|
|
12121
|
-
{ value: "left", icon: "AlignLeft" },
|
|
12122
|
-
{ value: "center", icon: "AlignCenter" },
|
|
12123
|
-
{ value: "right", icon: "AlignRight" }
|
|
12124
|
-
];
|
|
12125
|
-
const currentAlign = getStyleValue("textAlign") || "left";
|
|
12126
|
-
alignments.forEach((align) => {
|
|
12127
|
-
const isActive = currentAlign === align.value;
|
|
12128
|
-
const btn = createElement("button", {
|
|
12129
|
-
type: "button",
|
|
12130
|
-
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"}`,
|
|
12131
|
-
title: `Align ${align.value}`,
|
|
12132
|
-
onclick: () => {
|
|
12133
|
-
const newValue = align.value === "left" ? "" : align.value;
|
|
12134
|
-
updateStyleProp("textAlign", newValue);
|
|
12135
|
-
}
|
|
12136
|
-
}, [getIcon(align.icon, 16)]);
|
|
12137
|
-
alignButtonsRow.appendChild(btn);
|
|
12138
|
-
});
|
|
12139
|
-
alignGroup.appendChild(alignButtonsRow);
|
|
12140
|
-
body.appendChild(alignGroup);
|
|
12141
|
-
const cssClassGroup = createElement("div", { className: "mb-3" });
|
|
12142
|
-
cssClassGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Custom CSS Class" }));
|
|
12143
|
-
cssClassGroup.appendChild(createElement("input", {
|
|
12144
|
-
type: "text",
|
|
12145
|
-
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent text-sm",
|
|
12146
|
-
value: selectedField.css?.class || "",
|
|
12147
|
-
placeholder: "e.g. my-custom-class",
|
|
12148
|
-
"data-focus-id": `field-css-class-${selectedField.id}`,
|
|
12149
|
-
oninput: (e) => {
|
|
12150
|
-
const cssClass = e.target.value;
|
|
12151
|
-
const state2 = formStore.getState();
|
|
12152
|
-
const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12153
|
-
if (!freshField)
|
|
12154
|
-
return;
|
|
12155
|
-
state2.updateField(selectedField.id, {
|
|
12156
|
-
css: {
|
|
12157
|
-
class: cssClass || void 0
|
|
12158
|
-
// Do NOT spread freshField.css here - let updateField preserve style
|
|
12159
|
-
}
|
|
12160
|
-
});
|
|
12161
|
-
}
|
|
12162
|
-
}));
|
|
12163
|
-
body.appendChild(cssClassGroup);
|
|
12164
|
-
const isPanelExpanded = advancedCssPanelState.get(selectedField.id) || false;
|
|
12165
|
-
const advancedToggleGroup = createElement("div", { className: "mb-3" });
|
|
12166
|
-
const advancedToggle = createElement("button", {
|
|
12167
|
-
type: "button",
|
|
12168
|
-
className: "text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1",
|
|
12169
|
-
onclick: () => {
|
|
12170
|
-
const advancedPanel2 = document.getElementById(`advanced-css-${selectedField.id}`);
|
|
12171
|
-
if (advancedPanel2) {
|
|
12172
|
-
advancedPanel2.classList.toggle("hidden");
|
|
12173
|
-
const isHidden = advancedPanel2.classList.contains("hidden");
|
|
12174
|
-
advancedToggle.textContent = isHidden ? "\u25B6 Show Advanced CSS" : "\u25BC Hide Advanced CSS";
|
|
12175
|
-
advancedCssPanelState.set(selectedField.id, !isHidden);
|
|
12176
|
-
}
|
|
12177
|
-
}
|
|
12178
|
-
});
|
|
12179
|
-
advancedToggle.textContent = isPanelExpanded ? "\u25BC Hide Advanced CSS" : "\u25B6 Show Advanced CSS";
|
|
12180
|
-
advancedToggleGroup.appendChild(advancedToggle);
|
|
12181
|
-
body.appendChild(advancedToggleGroup);
|
|
12182
|
-
const advancedPanel = createElement("div", {
|
|
12183
|
-
className: isPanelExpanded ? "mb-3" : "mb-3 hidden",
|
|
12184
|
-
id: `advanced-css-${selectedField.id}`
|
|
12185
|
-
});
|
|
12186
|
-
advancedPanel.appendChild(createElement("label", { className: "block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1", text: "Raw CSS Style (JSON)" }));
|
|
12187
|
-
const cssStyleId = `field-css-style-${selectedField.id}`;
|
|
12188
|
-
const freshState = formStore.getState();
|
|
12189
|
-
const freshFieldForInit = freshState.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12190
|
-
const fieldForCssStyle = freshFieldForInit || selectedField;
|
|
12191
|
-
let initialCssStyleValue = fieldForCssStyle.css?.style ? JSON.stringify(fieldForCssStyle.css.style, null, 2) : "";
|
|
12192
|
-
const preservedValue = focusState?.id === cssStyleId ? focusState.value : void 0;
|
|
12193
|
-
if (preservedValue !== void 0) {
|
|
12194
|
-
initialCssStyleValue = preservedValue;
|
|
12195
|
-
}
|
|
12196
|
-
const cssStyleTextarea = createElement("textarea", {
|
|
12197
|
-
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent text-xs font-mono",
|
|
12198
|
-
rows: 3,
|
|
12199
|
-
placeholder: '{"padding": "8px", "backgroundColor": "#f0f0f0"}',
|
|
12200
|
-
"data-focus-id": cssStyleId,
|
|
12201
|
-
"data-was-focused": "false",
|
|
12202
|
-
onfocus: (e) => {
|
|
12203
|
-
const textarea = e.target;
|
|
12204
|
-
textarea.setAttribute("data-was-focused", "true");
|
|
12205
|
-
const state2 = formStore.getState();
|
|
12206
|
-
const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12207
|
-
if (!textarea.value.trim() && freshField?.css?.style && Object.keys(freshField.css.style).length > 0) {
|
|
12208
|
-
const styleString = JSON.stringify(freshField.css.style, null, 2);
|
|
12209
|
-
textarea.value = styleString;
|
|
12210
|
-
}
|
|
12211
|
-
},
|
|
12212
|
-
oninput: (e) => {
|
|
12213
|
-
const styleText = e.target.value;
|
|
12214
|
-
const state2 = formStore.getState();
|
|
12215
|
-
const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12216
|
-
if (!freshField)
|
|
12217
|
-
return;
|
|
12218
|
-
try {
|
|
12219
|
-
if (styleText.trim()) {
|
|
12220
|
-
const styleObj = JSON.parse(styleText);
|
|
12221
|
-
state2.updateField(selectedField.id, {
|
|
12222
|
-
css: { ...freshField.css, style: styleObj }
|
|
12223
|
-
});
|
|
12224
|
-
}
|
|
12225
|
-
} catch (err) {
|
|
12226
|
-
}
|
|
12227
|
-
},
|
|
12228
|
-
onblur: (e) => {
|
|
12229
|
-
const textarea = e.target;
|
|
12230
|
-
const styleText = textarea.value;
|
|
12231
|
-
const wasFocused = textarea.getAttribute("data-was-focused") === "true";
|
|
12232
|
-
if (!wasFocused) {
|
|
12233
|
-
return;
|
|
12234
|
-
}
|
|
12235
|
-
textarea.setAttribute("data-was-focused", "false");
|
|
12236
|
-
setTimeout(() => {
|
|
12237
|
-
if (!document.body.contains(textarea)) {
|
|
12238
|
-
return;
|
|
12239
|
-
}
|
|
12240
|
-
const advPanel = document.getElementById(`advanced-css-${selectedField.id}`);
|
|
12241
|
-
const isPanelVisible = advPanel && !advPanel.classList.contains("hidden");
|
|
12242
|
-
if (!isPanelVisible) {
|
|
12243
|
-
return;
|
|
12244
|
-
}
|
|
12245
|
-
const state2 = formStore.getState();
|
|
12246
|
-
const freshField = state2.schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
12247
|
-
if (!freshField)
|
|
12248
|
-
return;
|
|
12249
|
-
try {
|
|
12250
|
-
if (styleText.trim()) {
|
|
12251
|
-
const styleObj = JSON.parse(styleText);
|
|
12252
|
-
state2.updateField(selectedField.id, {
|
|
12253
|
-
css: { ...freshField.css, style: styleObj }
|
|
12254
|
-
});
|
|
12255
|
-
} else {
|
|
12256
|
-
if (freshField.css?.style && Object.keys(freshField.css.style).length > 0) {
|
|
12257
|
-
textarea.value = JSON.stringify(freshField.css.style, null, 2);
|
|
12258
|
-
} else {
|
|
12259
|
-
state2.updateField(selectedField.id, {
|
|
12260
|
-
css: { ...freshField.css, style: void 0 }
|
|
12261
|
-
});
|
|
12262
|
-
}
|
|
12263
|
-
}
|
|
12264
|
-
} catch (err) {
|
|
12265
|
-
if (freshField.css?.style) {
|
|
12266
|
-
textarea.value = JSON.stringify(freshField.css.style, null, 2);
|
|
12267
|
-
}
|
|
12268
|
-
}
|
|
12269
|
-
}, 0);
|
|
12270
|
-
}
|
|
12271
|
-
});
|
|
12272
|
-
cssStyleTextarea.value = initialCssStyleValue;
|
|
12273
|
-
advancedPanel.appendChild(cssStyleTextarea);
|
|
12274
|
-
body.appendChild(advancedPanel);
|
|
12770
|
+
this.appendSharedStylingSection(body, { kind: "field", fieldId: selectedField.id }, focusState);
|
|
12275
12771
|
panel.appendChild(body);
|
|
12276
12772
|
return panel;
|
|
12277
12773
|
}
|