form-builder-pro 1.2.8 → 1.2.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 +42 -0
- package/dist/index.d.mts +89 -2
- package/dist/index.d.ts +89 -2
- package/dist/index.js +1254 -299
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1249 -300
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4119,7 +4119,8 @@ var FIELD_TYPES = [
|
|
|
4119
4119
|
{ type: "checkbox", label: "Checkbox", icon: "CheckSquare" },
|
|
4120
4120
|
{ type: "radio", label: "Radio Group", icon: "CircleDot" },
|
|
4121
4121
|
{ type: "toggle", label: "Toggle", icon: "ToggleSwitch" },
|
|
4122
|
-
{ type: "file", label: "File Upload", icon: "Upload" }
|
|
4122
|
+
{ type: "file", label: "File Upload", icon: "Upload" },
|
|
4123
|
+
{ type: "image", label: "Image", icon: "Image" }
|
|
4123
4124
|
];
|
|
4124
4125
|
var DEFAULT_FIELD_CONFIG = {
|
|
4125
4126
|
text: { label: "Text Input", placeholder: "Enter text...", width: "100%", enabled: true, visible: true },
|
|
@@ -4145,7 +4146,39 @@ var DEFAULT_FIELD_CONFIG = {
|
|
|
4145
4146
|
checkbox: { label: "Checkbox", options: [], width: "100%", enabled: true, visible: true },
|
|
4146
4147
|
radio: { label: "Radio Group", options: [], width: "100%", enabled: true, visible: true },
|
|
4147
4148
|
toggle: { label: "Toggle", width: "50%", enabled: true, visible: true },
|
|
4148
|
-
file: { label: "File Upload", width: "100%", enabled: true, visible: true }
|
|
4149
|
+
file: { label: "File Upload", width: "100%", enabled: true, visible: true },
|
|
4150
|
+
image: { label: "Image", width: "50%", enabled: true, visible: true }
|
|
4151
|
+
};
|
|
4152
|
+
var VALIDATION_TYPE_PRESETS = {
|
|
4153
|
+
postalCode: {
|
|
4154
|
+
pattern: "^[0-9]{6}$",
|
|
4155
|
+
minLength: 6,
|
|
4156
|
+
maxLength: 6,
|
|
4157
|
+
validationType: "postalCode",
|
|
4158
|
+
customErrorMessages: { pattern: "Please enter a valid 6-digit postal code" }
|
|
4159
|
+
},
|
|
4160
|
+
phoneNumber: {
|
|
4161
|
+
pattern: "^[6-9][0-9]{9}$",
|
|
4162
|
+
minLength: 10,
|
|
4163
|
+
maxLength: 10,
|
|
4164
|
+
validationType: "phoneNumber",
|
|
4165
|
+
customErrorMessages: { pattern: "Please enter a valid 10-digit mobile number starting with 6-9" }
|
|
4166
|
+
},
|
|
4167
|
+
otp: {
|
|
4168
|
+
pattern: "^[0-9]{4,6}$",
|
|
4169
|
+
minLength: 4,
|
|
4170
|
+
maxLength: 6,
|
|
4171
|
+
validationType: "otp",
|
|
4172
|
+
customErrorMessages: { pattern: "Please enter a valid OTP (4-6 digits)" }
|
|
4173
|
+
},
|
|
4174
|
+
amount: {
|
|
4175
|
+
min: 0,
|
|
4176
|
+
allowDecimal: true,
|
|
4177
|
+
decimalPlaces: 2,
|
|
4178
|
+
allowNegative: false,
|
|
4179
|
+
validationType: "amount",
|
|
4180
|
+
customErrorMessages: { min: "Amount must be at least 0" }
|
|
4181
|
+
}
|
|
4149
4182
|
};
|
|
4150
4183
|
var REGEX_PRESETS = [
|
|
4151
4184
|
{
|
|
@@ -4271,11 +4304,72 @@ var cloneField = (field) => {
|
|
|
4271
4304
|
id: generateId(),
|
|
4272
4305
|
// Ensure options are also cloned if present
|
|
4273
4306
|
options: field.options ? field.options.map((opt) => ({ ...opt })) : void 0,
|
|
4274
|
-
validation: field.validation ? field.validation.map((v) => ({ ...v })) : void 0
|
|
4307
|
+
validation: field.validation ? field.validation.map((v) => ({ ...v })) : void 0,
|
|
4308
|
+
// Preserve formula config for number fields (dependencies stay as-is - they reference other field ids/names)
|
|
4309
|
+
valueSource: field.valueSource,
|
|
4310
|
+
formula: field.formula,
|
|
4311
|
+
dependencies: field.dependencies ? [...field.dependencies] : void 0
|
|
4275
4312
|
};
|
|
4276
4313
|
};
|
|
4277
4314
|
|
|
4278
4315
|
// src/utils/mapper.ts
|
|
4316
|
+
function validationsToValidationObject(v) {
|
|
4317
|
+
if (!v)
|
|
4318
|
+
return void 0;
|
|
4319
|
+
const obj = {};
|
|
4320
|
+
if (v.required !== void 0)
|
|
4321
|
+
obj.required = v.required;
|
|
4322
|
+
if (v.pattern)
|
|
4323
|
+
obj.regex = v.pattern;
|
|
4324
|
+
if (v.minLength !== void 0)
|
|
4325
|
+
obj.minLength = v.minLength;
|
|
4326
|
+
if (v.maxLength !== void 0)
|
|
4327
|
+
obj.maxLength = v.maxLength;
|
|
4328
|
+
if (v.min !== void 0)
|
|
4329
|
+
obj.min = v.min;
|
|
4330
|
+
if (v.max !== void 0)
|
|
4331
|
+
obj.max = v.max;
|
|
4332
|
+
if (v.customErrorMessages?.pattern)
|
|
4333
|
+
obj.regexMessage = v.customErrorMessages.pattern;
|
|
4334
|
+
if (v.minSelected !== void 0)
|
|
4335
|
+
obj.minSelected = v.minSelected;
|
|
4336
|
+
if (v.maxSelected !== void 0)
|
|
4337
|
+
obj.maxSelected = v.maxSelected;
|
|
4338
|
+
if (v.minDate)
|
|
4339
|
+
obj.minDate = v.minDate;
|
|
4340
|
+
if (v.maxDate)
|
|
4341
|
+
obj.maxDate = v.maxDate;
|
|
4342
|
+
return Object.keys(obj).length > 0 ? obj : void 0;
|
|
4343
|
+
}
|
|
4344
|
+
function validationObjectToValidations(v) {
|
|
4345
|
+
if (!v)
|
|
4346
|
+
return void 0;
|
|
4347
|
+
const validations = {};
|
|
4348
|
+
if (v.required !== void 0)
|
|
4349
|
+
validations.required = v.required;
|
|
4350
|
+
if (v.regex)
|
|
4351
|
+
validations.pattern = v.regex;
|
|
4352
|
+
if (v.minLength !== void 0)
|
|
4353
|
+
validations.minLength = v.minLength;
|
|
4354
|
+
if (v.maxLength !== void 0)
|
|
4355
|
+
validations.maxLength = v.maxLength;
|
|
4356
|
+
if (v.min !== void 0)
|
|
4357
|
+
validations.min = v.min;
|
|
4358
|
+
if (v.max !== void 0)
|
|
4359
|
+
validations.max = v.max;
|
|
4360
|
+
if (v.regexMessage) {
|
|
4361
|
+
validations.customErrorMessages = { pattern: v.regexMessage };
|
|
4362
|
+
}
|
|
4363
|
+
if (v.minSelected !== void 0)
|
|
4364
|
+
validations.minSelected = v.minSelected;
|
|
4365
|
+
if (v.maxSelected !== void 0)
|
|
4366
|
+
validations.maxSelected = v.maxSelected;
|
|
4367
|
+
if (v.minDate)
|
|
4368
|
+
validations.minDate = v.minDate;
|
|
4369
|
+
if (v.maxDate)
|
|
4370
|
+
validations.maxDate = v.maxDate;
|
|
4371
|
+
return Object.keys(validations).length > 0 ? validations : void 0;
|
|
4372
|
+
}
|
|
4279
4373
|
function convertValidationObjectToArray(validationObj) {
|
|
4280
4374
|
if (!validationObj)
|
|
4281
4375
|
return [];
|
|
@@ -4284,7 +4378,7 @@ function convertValidationObjectToArray(validationObj) {
|
|
|
4284
4378
|
}
|
|
4285
4379
|
const rules = [];
|
|
4286
4380
|
const obj = validationObj;
|
|
4287
|
-
const hasValidationProperties = !!(obj.regex || obj.regexMessage || obj.minLength !== void 0 || obj.maxLength !== void 0 || obj.minSelected !== void 0 || obj.maxSelected !== void 0);
|
|
4381
|
+
const hasValidationProperties = !!(obj.regex || obj.regexMessage || obj.minLength !== void 0 || obj.maxLength !== void 0 || obj.min !== void 0 || obj.max !== void 0 || obj.minSelected !== void 0 || obj.maxSelected !== void 0);
|
|
4288
4382
|
const isRequired = obj.required === true || hasValidationProperties && obj.required !== false;
|
|
4289
4383
|
if (isRequired) {
|
|
4290
4384
|
rules.push({ type: "required", value: true });
|
|
@@ -4302,6 +4396,12 @@ function convertValidationObjectToArray(validationObj) {
|
|
|
4302
4396
|
if (obj.maxLength !== void 0) {
|
|
4303
4397
|
rules.push({ type: "maxLength", value: obj.maxLength });
|
|
4304
4398
|
}
|
|
4399
|
+
if (obj.min !== void 0) {
|
|
4400
|
+
rules.push({ type: "min", value: obj.min });
|
|
4401
|
+
}
|
|
4402
|
+
if (obj.max !== void 0) {
|
|
4403
|
+
rules.push({ type: "max", value: obj.max });
|
|
4404
|
+
}
|
|
4305
4405
|
if (obj.minSelected !== void 0) {
|
|
4306
4406
|
rules.push({ type: "minSelected", value: obj.minSelected });
|
|
4307
4407
|
}
|
|
@@ -4375,7 +4475,15 @@ function transformField(field) {
|
|
|
4375
4475
|
if (transformed.order === void 0) {
|
|
4376
4476
|
transformed.order = field.order !== void 0 ? field.order : 0;
|
|
4377
4477
|
}
|
|
4378
|
-
if (field.
|
|
4478
|
+
if (field.validations) {
|
|
4479
|
+
let validations = field.validations;
|
|
4480
|
+
if (validations.validationType === "age") {
|
|
4481
|
+
validations = { ...validations, validationType: "custom" };
|
|
4482
|
+
}
|
|
4483
|
+
transformed.validations = validations;
|
|
4484
|
+
transformed.required = validations.required ?? false;
|
|
4485
|
+
transformed.validation = validationsToValidationObject(validations);
|
|
4486
|
+
} else if (field.validation) {
|
|
4379
4487
|
if (Array.isArray(field.validation)) {
|
|
4380
4488
|
const validationObj = {};
|
|
4381
4489
|
field.validation.forEach((rule) => {
|
|
@@ -4388,6 +4496,10 @@ function transformField(field) {
|
|
|
4388
4496
|
validationObj.minLength = rule.value;
|
|
4389
4497
|
} else if (rule.type === "maxLength" && typeof rule.value === "number") {
|
|
4390
4498
|
validationObj.maxLength = rule.value;
|
|
4499
|
+
} else if (rule.type === "min" && typeof rule.value === "number") {
|
|
4500
|
+
validationObj.min = rule.value;
|
|
4501
|
+
} else if (rule.type === "max" && typeof rule.value === "number") {
|
|
4502
|
+
validationObj.max = rule.value;
|
|
4391
4503
|
} else if (rule.type === "minSelected" && typeof rule.value === "number") {
|
|
4392
4504
|
validationObj.minSelected = rule.value;
|
|
4393
4505
|
} else if (rule.type === "maxSelected" && typeof rule.value === "number") {
|
|
@@ -4400,13 +4512,20 @@ function transformField(field) {
|
|
|
4400
4512
|
});
|
|
4401
4513
|
transformed.validation = validationObj;
|
|
4402
4514
|
transformed.required = validationObj.required || false;
|
|
4515
|
+
transformed.validations = validationObjectToValidations(validationObj);
|
|
4403
4516
|
} else {
|
|
4404
4517
|
transformed.validation = field.validation;
|
|
4405
4518
|
transformed.required = field.validation.required || false;
|
|
4519
|
+
let validations = validationObjectToValidations(field.validation);
|
|
4520
|
+
if (validations?.validationType === "age") {
|
|
4521
|
+
validations = { ...validations, validationType: "custom" };
|
|
4522
|
+
}
|
|
4523
|
+
transformed.validations = validations;
|
|
4406
4524
|
}
|
|
4407
4525
|
} else if (field.required !== void 0) {
|
|
4408
4526
|
transformed.required = field.required;
|
|
4409
4527
|
transformed.validation = { required: field.required };
|
|
4528
|
+
transformed.validations = { required: field.required };
|
|
4410
4529
|
}
|
|
4411
4530
|
if (normalizedType === "select") {
|
|
4412
4531
|
if (field.multiSelect !== void 0) {
|
|
@@ -4455,6 +4574,10 @@ function transformField(field) {
|
|
|
4455
4574
|
transformed.lookupValueField = lookupValueField;
|
|
4456
4575
|
if (lookupLabelField !== void 0)
|
|
4457
4576
|
transformed.lookupLabelField = lookupLabelField;
|
|
4577
|
+
if (field.fieldName !== void 0)
|
|
4578
|
+
transformed.fieldName = field.fieldName;
|
|
4579
|
+
else if (field.name !== void 0)
|
|
4580
|
+
transformed.fieldName = field.name;
|
|
4458
4581
|
if (field.placeholder !== void 0)
|
|
4459
4582
|
transformed.placeholder = field.placeholder;
|
|
4460
4583
|
if (field.description !== void 0)
|
|
@@ -4471,6 +4594,14 @@ function transformField(field) {
|
|
|
4471
4594
|
transformed.visible = field.visible;
|
|
4472
4595
|
if (field.isd !== void 0)
|
|
4473
4596
|
transformed.isd = field.isd;
|
|
4597
|
+
if (field.imageUrl !== void 0)
|
|
4598
|
+
transformed.imageUrl = field.imageUrl;
|
|
4599
|
+
if (field.valueSource !== void 0)
|
|
4600
|
+
transformed.valueSource = field.valueSource;
|
|
4601
|
+
if (field.formula !== void 0)
|
|
4602
|
+
transformed.formula = field.formula;
|
|
4603
|
+
if (field.dependencies !== void 0 && Array.isArray(field.dependencies))
|
|
4604
|
+
transformed.dependencies = field.dependencies;
|
|
4474
4605
|
if (field.css !== void 0)
|
|
4475
4606
|
transformed.css = field.css;
|
|
4476
4607
|
if (field.optionsSource !== void 0)
|
|
@@ -4481,8 +4612,18 @@ function transformField(field) {
|
|
|
4481
4612
|
transformed.groupName = field.groupName;
|
|
4482
4613
|
if (field.masterTypeName !== void 0)
|
|
4483
4614
|
transformed.masterTypeName = field.masterTypeName;
|
|
4484
|
-
if ((normalizedType === "select" || normalizedType === "radio" || normalizedType === "checkbox") && field.options) {
|
|
4485
|
-
transformed.options = field.options
|
|
4615
|
+
if ((normalizedType === "select" || normalizedType === "radio" || normalizedType === "checkbox") && field.options && Array.isArray(field.options)) {
|
|
4616
|
+
transformed.options = field.options.map((opt, idx) => {
|
|
4617
|
+
if (opt && typeof opt === "object" && "label" in opt && "value" in opt) {
|
|
4618
|
+
return { label: String(opt.label), value: String(opt.value) };
|
|
4619
|
+
}
|
|
4620
|
+
const label = opt?.label ?? opt?.name ?? opt?.displayName ?? opt?.text ?? `Option ${idx + 1}`;
|
|
4621
|
+
const value = opt?.value ?? opt?.id ?? opt?.name ?? String(idx);
|
|
4622
|
+
return { label: String(label), value: String(value) };
|
|
4623
|
+
});
|
|
4624
|
+
if (normalizedType === "select" && transformed.options.length > 0 && (transformed.optionSource === "STATIC" || !transformed.optionSource) && transformed.customOptionsEnabled === void 0) {
|
|
4625
|
+
transformed.customOptionsEnabled = true;
|
|
4626
|
+
}
|
|
4486
4627
|
}
|
|
4487
4628
|
return transformed;
|
|
4488
4629
|
}
|
|
@@ -4552,6 +4693,10 @@ function convertValidationArrayToObject(validation) {
|
|
|
4552
4693
|
obj.minLength = rule.value;
|
|
4553
4694
|
} else if (rule.type === "maxLength" && typeof rule.value === "number") {
|
|
4554
4695
|
obj.maxLength = rule.value;
|
|
4696
|
+
} else if (rule.type === "min" && typeof rule.value === "number") {
|
|
4697
|
+
obj.min = rule.value;
|
|
4698
|
+
} else if (rule.type === "max" && typeof rule.value === "number") {
|
|
4699
|
+
obj.max = rule.value;
|
|
4555
4700
|
} else if (rule.type === "minSelected" && typeof rule.value === "number") {
|
|
4556
4701
|
obj.minSelected = rule.value;
|
|
4557
4702
|
} else if (rule.type === "maxSelected" && typeof rule.value === "number") {
|
|
@@ -4575,6 +4720,8 @@ function fieldToPayload(field) {
|
|
|
4575
4720
|
id: field.id,
|
|
4576
4721
|
type: field.type,
|
|
4577
4722
|
label: field.label,
|
|
4723
|
+
name: field.fieldName || field.id,
|
|
4724
|
+
// Model key for binding (API / host app)
|
|
4578
4725
|
order: field.order !== void 0 ? field.order : 0
|
|
4579
4726
|
};
|
|
4580
4727
|
if (field.layout?.span !== void 0) {
|
|
@@ -4596,8 +4743,18 @@ function fieldToPayload(field) {
|
|
|
4596
4743
|
span: 12
|
|
4597
4744
|
};
|
|
4598
4745
|
}
|
|
4599
|
-
|
|
4600
|
-
|
|
4746
|
+
if (field.validations) {
|
|
4747
|
+
let validations = { ...field.validations };
|
|
4748
|
+
if (validations.validationType === "age") {
|
|
4749
|
+
validations = { ...validations, validationType: "custom" };
|
|
4750
|
+
}
|
|
4751
|
+
payload.validations = validations;
|
|
4752
|
+
if (field.validations.required) {
|
|
4753
|
+
payload.required = true;
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
payload.validation = field.validations ? validationsToValidationObject(field.validations) : convertValidationArrayToObject(field.validation);
|
|
4757
|
+
if (payload.validation?.required || payload.validations?.required) {
|
|
4601
4758
|
payload.required = true;
|
|
4602
4759
|
}
|
|
4603
4760
|
if (field.type === "select") {
|
|
@@ -4633,6 +4790,14 @@ function fieldToPayload(field) {
|
|
|
4633
4790
|
payload.lookupLabelField = field.lookupLabelField;
|
|
4634
4791
|
if (field.isd !== void 0)
|
|
4635
4792
|
payload.isd = field.isd;
|
|
4793
|
+
if (field.imageUrl !== void 0)
|
|
4794
|
+
payload.imageUrl = field.imageUrl;
|
|
4795
|
+
if (field.valueSource !== void 0)
|
|
4796
|
+
payload.valueSource = field.valueSource;
|
|
4797
|
+
if (field.formula !== void 0)
|
|
4798
|
+
payload.formula = field.formula;
|
|
4799
|
+
if (field.dependencies !== void 0 && Array.isArray(field.dependencies))
|
|
4800
|
+
payload.dependencies = field.dependencies;
|
|
4636
4801
|
if ((field.type === "select" || field.type === "radio" || field.type === "checkbox") && field.options && Array.isArray(field.options)) {
|
|
4637
4802
|
payload.options = field.options.map((opt) => ({ label: opt.label, value: opt.value }));
|
|
4638
4803
|
}
|
|
@@ -4657,6 +4822,20 @@ var builderToPlatform = (builderSchema) => {
|
|
|
4657
4822
|
var platformToBuilder = (platformSchema) => {
|
|
4658
4823
|
return cleanFormSchema(platformSchema);
|
|
4659
4824
|
};
|
|
4825
|
+
function getValidationConfigForAngular(validations) {
|
|
4826
|
+
if (!validations) {
|
|
4827
|
+
return { required: false, customErrorMessages: {} };
|
|
4828
|
+
}
|
|
4829
|
+
return {
|
|
4830
|
+
required: validations.required ?? false,
|
|
4831
|
+
minLength: validations.minLength,
|
|
4832
|
+
maxLength: validations.maxLength,
|
|
4833
|
+
min: validations.min,
|
|
4834
|
+
max: validations.max,
|
|
4835
|
+
pattern: validations.pattern,
|
|
4836
|
+
customErrorMessages: validations.customErrorMessages || {}
|
|
4837
|
+
};
|
|
4838
|
+
}
|
|
4660
4839
|
|
|
4661
4840
|
// src/core/useFormStore.ts
|
|
4662
4841
|
var INITIAL_SCHEMA = {
|
|
@@ -4961,9 +5140,10 @@ var formStore = createStore((set, get) => ({
|
|
|
4961
5140
|
const { existingForms, history, historyIndex } = get();
|
|
4962
5141
|
const found = existingForms.find((f) => f.id === formId);
|
|
4963
5142
|
if (found) {
|
|
5143
|
+
const cleanedSchema = cleanFormSchema(found);
|
|
4964
5144
|
set({
|
|
4965
|
-
schema:
|
|
4966
|
-
history: [...history.slice(0, historyIndex + 1),
|
|
5145
|
+
schema: cleanedSchema,
|
|
5146
|
+
history: [...history.slice(0, historyIndex + 1), cleanedSchema],
|
|
4967
5147
|
historyIndex: historyIndex + 1
|
|
4968
5148
|
});
|
|
4969
5149
|
}
|
|
@@ -4973,9 +5153,10 @@ var formStore = createStore((set, get) => ({
|
|
|
4973
5153
|
const found = existingForms.find((f) => f.id === formId);
|
|
4974
5154
|
if (found) {
|
|
4975
5155
|
const cloned = cloneForm(found);
|
|
5156
|
+
const cleanedSchema = cleanFormSchema(cloned);
|
|
4976
5157
|
set({
|
|
4977
|
-
schema:
|
|
4978
|
-
history: [...history.slice(0, historyIndex + 1),
|
|
5158
|
+
schema: cleanedSchema,
|
|
5159
|
+
history: [...history.slice(0, historyIndex + 1), cleanedSchema],
|
|
4979
5160
|
historyIndex: historyIndex + 1
|
|
4980
5161
|
});
|
|
4981
5162
|
}
|
|
@@ -5358,6 +5539,7 @@ var ICONS = {
|
|
|
5358
5539
|
"ToggleSwitch": '<rect x="2" y="6" width="20" height="12" rx="6" ry="6" stroke-linecap="round" stroke-linejoin="round" /><circle cx="8" cy="12" r="4" stroke-linecap="round" stroke-linejoin="round" />',
|
|
5359
5540
|
// Custom SVG for toggle
|
|
5360
5541
|
"Upload": '<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />',
|
|
5542
|
+
"Image": '<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" />',
|
|
5361
5543
|
// UI Icons
|
|
5362
5544
|
"GripVertical": '<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9" />',
|
|
5363
5545
|
"GripDots": '<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 12.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 18.75a.75.75 0 110-1.5.75.75 0 010 1.5z" />',
|
|
@@ -5390,6 +5572,224 @@ function getIcon(name, size = 20) {
|
|
|
5390
5572
|
return svg;
|
|
5391
5573
|
}
|
|
5392
5574
|
|
|
5575
|
+
// src/utils/formula.ts
|
|
5576
|
+
var FORMULA_OPERATOR_REGEX = /[\+\-\*\/\(\)]/g;
|
|
5577
|
+
function parseFormulaDependencies(formula) {
|
|
5578
|
+
if (!formula || typeof formula !== "string")
|
|
5579
|
+
return [];
|
|
5580
|
+
const tokens = formula.replace(FORMULA_OPERATOR_REGEX, " ").split(/\s+/).filter(Boolean);
|
|
5581
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5582
|
+
const deps = [];
|
|
5583
|
+
for (const t of tokens) {
|
|
5584
|
+
const trimmed = t.trim();
|
|
5585
|
+
if (trimmed && !seen.has(trimmed)) {
|
|
5586
|
+
seen.add(trimmed);
|
|
5587
|
+
deps.push(trimmed);
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5590
|
+
return deps;
|
|
5591
|
+
}
|
|
5592
|
+
function validateFormula(formula, availableFieldIds, availableFieldNames, _currentFieldId) {
|
|
5593
|
+
if (!formula || typeof formula !== "string") {
|
|
5594
|
+
return { valid: false, error: "Formula cannot be empty" };
|
|
5595
|
+
}
|
|
5596
|
+
const trimmed = formula.trim();
|
|
5597
|
+
if (!trimmed) {
|
|
5598
|
+
return { valid: false, error: "Formula cannot be empty" };
|
|
5599
|
+
}
|
|
5600
|
+
const deps = parseFormulaDependencies(trimmed);
|
|
5601
|
+
const validRefs = /* @__PURE__ */ new Set([...availableFieldIds, ...availableFieldNames]);
|
|
5602
|
+
for (const dep of deps) {
|
|
5603
|
+
if (!validRefs.has(dep)) {
|
|
5604
|
+
return { valid: false, error: `Unknown field reference: "${dep}"` };
|
|
5605
|
+
}
|
|
5606
|
+
}
|
|
5607
|
+
let open = 0;
|
|
5608
|
+
for (const c of trimmed) {
|
|
5609
|
+
if (c === "(")
|
|
5610
|
+
open++;
|
|
5611
|
+
else if (c === ")") {
|
|
5612
|
+
open--;
|
|
5613
|
+
if (open < 0)
|
|
5614
|
+
return { valid: false, error: "Unbalanced parentheses" };
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
if (open !== 0)
|
|
5618
|
+
return { valid: false, error: "Unbalanced parentheses" };
|
|
5619
|
+
return { valid: true };
|
|
5620
|
+
}
|
|
5621
|
+
function detectCircularDependency(schema, formulaFieldId, _formula, dependencies) {
|
|
5622
|
+
const visited = /* @__PURE__ */ new Set();
|
|
5623
|
+
const resolveFieldByRef = (ref) => {
|
|
5624
|
+
for (const s of schema.sections) {
|
|
5625
|
+
for (const f of s.fields) {
|
|
5626
|
+
if (f.id === ref || f.fieldName === ref)
|
|
5627
|
+
return f;
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5630
|
+
return void 0;
|
|
5631
|
+
};
|
|
5632
|
+
const hasCycle = (fieldId) => {
|
|
5633
|
+
if (visited.has(fieldId))
|
|
5634
|
+
return true;
|
|
5635
|
+
visited.add(fieldId);
|
|
5636
|
+
const field = resolveFieldByRef(fieldId);
|
|
5637
|
+
if (!field || field.type !== "number" || field.valueSource !== "formula" || !field.formula) {
|
|
5638
|
+
visited.delete(fieldId);
|
|
5639
|
+
return false;
|
|
5640
|
+
}
|
|
5641
|
+
const deps = field.dependencies ?? parseFormulaDependencies(field.formula);
|
|
5642
|
+
for (const dep of deps) {
|
|
5643
|
+
const depField = resolveFieldByRef(dep);
|
|
5644
|
+
if (!depField)
|
|
5645
|
+
continue;
|
|
5646
|
+
const depId = depField.id;
|
|
5647
|
+
if (depId === formulaFieldId) {
|
|
5648
|
+
visited.delete(fieldId);
|
|
5649
|
+
return true;
|
|
5650
|
+
}
|
|
5651
|
+
if (hasCycle(depId)) {
|
|
5652
|
+
visited.delete(fieldId);
|
|
5653
|
+
return true;
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5656
|
+
visited.delete(fieldId);
|
|
5657
|
+
return false;
|
|
5658
|
+
};
|
|
5659
|
+
for (const dep of dependencies) {
|
|
5660
|
+
const depField = resolveFieldByRef(dep);
|
|
5661
|
+
if (!depField)
|
|
5662
|
+
continue;
|
|
5663
|
+
if (depField.id === formulaFieldId)
|
|
5664
|
+
return true;
|
|
5665
|
+
if (hasCycle(depField.id))
|
|
5666
|
+
return true;
|
|
5667
|
+
}
|
|
5668
|
+
return false;
|
|
5669
|
+
}
|
|
5670
|
+
function evaluateFormula(formula, values) {
|
|
5671
|
+
if (!formula || typeof formula !== "string")
|
|
5672
|
+
return NaN;
|
|
5673
|
+
const trimmed = formula.trim();
|
|
5674
|
+
if (!trimmed)
|
|
5675
|
+
return NaN;
|
|
5676
|
+
const getValue = (ref) => {
|
|
5677
|
+
const v = values[ref];
|
|
5678
|
+
if (v === void 0 || v === null || v === "")
|
|
5679
|
+
return 0;
|
|
5680
|
+
const n = typeof v === "number" ? v : parseFloat(String(v));
|
|
5681
|
+
return isNaN(n) ? 0 : n;
|
|
5682
|
+
};
|
|
5683
|
+
const tokens = [];
|
|
5684
|
+
let i = 0;
|
|
5685
|
+
while (i < trimmed.length) {
|
|
5686
|
+
const c = trimmed[i];
|
|
5687
|
+
if (/\s/.test(c)) {
|
|
5688
|
+
i++;
|
|
5689
|
+
continue;
|
|
5690
|
+
}
|
|
5691
|
+
if (/[\+\-\*\/\(\)]/.test(c)) {
|
|
5692
|
+
tokens.push(c);
|
|
5693
|
+
i++;
|
|
5694
|
+
continue;
|
|
5695
|
+
}
|
|
5696
|
+
if (/[a-zA-Z_0-9]/.test(c)) {
|
|
5697
|
+
let ident = "";
|
|
5698
|
+
while (i < trimmed.length && /[a-zA-Z0-9_]/.test(trimmed[i])) {
|
|
5699
|
+
ident += trimmed[i++];
|
|
5700
|
+
}
|
|
5701
|
+
tokens.push(ident);
|
|
5702
|
+
continue;
|
|
5703
|
+
}
|
|
5704
|
+
i++;
|
|
5705
|
+
}
|
|
5706
|
+
let pos = 0;
|
|
5707
|
+
const parseExpr = () => {
|
|
5708
|
+
let left = parseTerm();
|
|
5709
|
+
while (pos < tokens.length) {
|
|
5710
|
+
const op = tokens[pos];
|
|
5711
|
+
if (op === "+") {
|
|
5712
|
+
pos++;
|
|
5713
|
+
left += parseTerm();
|
|
5714
|
+
} else if (op === "-") {
|
|
5715
|
+
pos++;
|
|
5716
|
+
left -= parseTerm();
|
|
5717
|
+
} else
|
|
5718
|
+
break;
|
|
5719
|
+
}
|
|
5720
|
+
return left;
|
|
5721
|
+
};
|
|
5722
|
+
const parseTerm = () => {
|
|
5723
|
+
let left = parseFactor();
|
|
5724
|
+
while (pos < tokens.length) {
|
|
5725
|
+
const op = tokens[pos];
|
|
5726
|
+
if (op === "*") {
|
|
5727
|
+
pos++;
|
|
5728
|
+
left *= parseFactor();
|
|
5729
|
+
} else if (op === "/") {
|
|
5730
|
+
pos++;
|
|
5731
|
+
const right = parseFactor();
|
|
5732
|
+
if (right === 0)
|
|
5733
|
+
return NaN;
|
|
5734
|
+
left /= right;
|
|
5735
|
+
} else
|
|
5736
|
+
break;
|
|
5737
|
+
}
|
|
5738
|
+
return left;
|
|
5739
|
+
};
|
|
5740
|
+
const parseFactor = () => {
|
|
5741
|
+
if (pos >= tokens.length)
|
|
5742
|
+
return NaN;
|
|
5743
|
+
const t = tokens[pos];
|
|
5744
|
+
if (t === "(") {
|
|
5745
|
+
pos++;
|
|
5746
|
+
const v = parseExpr();
|
|
5747
|
+
if (pos < tokens.length && tokens[pos] === ")")
|
|
5748
|
+
pos++;
|
|
5749
|
+
return v;
|
|
5750
|
+
}
|
|
5751
|
+
if (t === "-") {
|
|
5752
|
+
pos++;
|
|
5753
|
+
return -parseFactor();
|
|
5754
|
+
}
|
|
5755
|
+
if (t === "+") {
|
|
5756
|
+
pos++;
|
|
5757
|
+
return parseFactor();
|
|
5758
|
+
}
|
|
5759
|
+
const n = parseFloat(t);
|
|
5760
|
+
if (!isNaN(n)) {
|
|
5761
|
+
pos++;
|
|
5762
|
+
return n;
|
|
5763
|
+
}
|
|
5764
|
+
pos++;
|
|
5765
|
+
return getValue(t);
|
|
5766
|
+
};
|
|
5767
|
+
try {
|
|
5768
|
+
const result = parseExpr();
|
|
5769
|
+
return isNaN(result) ? NaN : result;
|
|
5770
|
+
} catch {
|
|
5771
|
+
return NaN;
|
|
5772
|
+
}
|
|
5773
|
+
}
|
|
5774
|
+
function getNumericFieldsForFormula(schema, excludeFieldId) {
|
|
5775
|
+
const result = [];
|
|
5776
|
+
for (const section of schema.sections) {
|
|
5777
|
+
for (const field of section.fields) {
|
|
5778
|
+
if (field.type !== "number")
|
|
5779
|
+
continue;
|
|
5780
|
+
if (excludeFieldId && field.id === excludeFieldId)
|
|
5781
|
+
continue;
|
|
5782
|
+
const fieldName = field.fieldName ?? field.id;
|
|
5783
|
+
result.push({
|
|
5784
|
+
id: field.id,
|
|
5785
|
+
fieldName,
|
|
5786
|
+
label: field.label || fieldName
|
|
5787
|
+
});
|
|
5788
|
+
}
|
|
5789
|
+
}
|
|
5790
|
+
return result;
|
|
5791
|
+
}
|
|
5792
|
+
|
|
5393
5793
|
// src/core/countryData.ts
|
|
5394
5794
|
var COUNTRY_DATA = [
|
|
5395
5795
|
{ code: "US", name: "United States", dialCode: "+1", flag: "\u{1F1FA}\u{1F1F8}" },
|
|
@@ -5445,6 +5845,64 @@ function getDefaultCountry() {
|
|
|
5445
5845
|
}
|
|
5446
5846
|
|
|
5447
5847
|
// src/renderer/FieldRenderer.ts
|
|
5848
|
+
function getValidationRules(field) {
|
|
5849
|
+
const v = field.validations;
|
|
5850
|
+
if (v) {
|
|
5851
|
+
return {
|
|
5852
|
+
required: v.required,
|
|
5853
|
+
pattern: v.pattern,
|
|
5854
|
+
patternMessage: v.customErrorMessages?.pattern,
|
|
5855
|
+
minLength: v.minLength,
|
|
5856
|
+
maxLength: v.maxLength,
|
|
5857
|
+
min: v.min,
|
|
5858
|
+
max: v.max,
|
|
5859
|
+
minDate: v.minDate,
|
|
5860
|
+
maxDate: v.maxDate
|
|
5861
|
+
};
|
|
5862
|
+
}
|
|
5863
|
+
const val = field.validation;
|
|
5864
|
+
if (!val)
|
|
5865
|
+
return {};
|
|
5866
|
+
if (Array.isArray(val)) {
|
|
5867
|
+
const rules = {};
|
|
5868
|
+
val.forEach((r) => {
|
|
5869
|
+
if (r.type === "required")
|
|
5870
|
+
rules.required = true;
|
|
5871
|
+
else if (r.type === "pattern") {
|
|
5872
|
+
rules.pattern = r.regex;
|
|
5873
|
+
rules.patternMessage = r.message;
|
|
5874
|
+
} else if (r.type === "minLength")
|
|
5875
|
+
rules.minLength = r.value;
|
|
5876
|
+
else if (r.type === "maxLength")
|
|
5877
|
+
rules.maxLength = r.value;
|
|
5878
|
+
else if (r.type === "min")
|
|
5879
|
+
rules.min = r.value;
|
|
5880
|
+
else if (r.type === "max")
|
|
5881
|
+
rules.max = r.value;
|
|
5882
|
+
else if (r.type === "minDate")
|
|
5883
|
+
rules.minDate = r.value;
|
|
5884
|
+
else if (r.type === "maxDate")
|
|
5885
|
+
rules.maxDate = r.value;
|
|
5886
|
+
});
|
|
5887
|
+
return rules;
|
|
5888
|
+
}
|
|
5889
|
+
const o = val;
|
|
5890
|
+
return {
|
|
5891
|
+
required: o.required,
|
|
5892
|
+
pattern: o.regex,
|
|
5893
|
+
patternMessage: o.regexMessage,
|
|
5894
|
+
minLength: o.minLength,
|
|
5895
|
+
maxLength: o.maxLength,
|
|
5896
|
+
min: o.min,
|
|
5897
|
+
max: o.max,
|
|
5898
|
+
minDate: o.minDate,
|
|
5899
|
+
maxDate: o.maxDate
|
|
5900
|
+
};
|
|
5901
|
+
}
|
|
5902
|
+
function isNumericTextField(field) {
|
|
5903
|
+
const v = field.validations;
|
|
5904
|
+
return !!(v?.validationType && ["postalCode", "phoneNumber", "otp"].includes(v.validationType));
|
|
5905
|
+
}
|
|
5448
5906
|
var FieldRenderer = class {
|
|
5449
5907
|
static render(field, value, onChange, readOnly = false) {
|
|
5450
5908
|
const wrapper = createElement("div", { className: "w-full form-row" });
|
|
@@ -5479,89 +5937,71 @@ var FieldRenderer = class {
|
|
|
5479
5937
|
}
|
|
5480
5938
|
let input;
|
|
5481
5939
|
let validationMsg = null;
|
|
5482
|
-
const
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
rules.push({ type: "required", value: true });
|
|
5487
|
-
if (obj.regex)
|
|
5488
|
-
rules.push({ type: "pattern", regex: obj.regex, message: obj.regexMessage });
|
|
5489
|
-
if (obj.minLength !== void 0)
|
|
5490
|
-
rules.push({ type: "minLength", value: obj.minLength });
|
|
5491
|
-
if (obj.maxLength !== void 0)
|
|
5492
|
-
rules.push({ type: "maxLength", value: obj.maxLength });
|
|
5493
|
-
if (obj.minSelected !== void 0)
|
|
5494
|
-
rules.push({ type: "minSelected", value: obj.minSelected });
|
|
5495
|
-
if (obj.maxSelected !== void 0)
|
|
5496
|
-
rules.push({ type: "maxSelected", value: obj.maxSelected });
|
|
5497
|
-
if (obj.minDate)
|
|
5498
|
-
rules.push({ type: "minDate", value: obj.minDate });
|
|
5499
|
-
if (obj.maxDate)
|
|
5500
|
-
rules.push({ type: "maxDate", value: obj.maxDate });
|
|
5501
|
-
return rules;
|
|
5502
|
-
})() : [];
|
|
5503
|
-
const hasPatternValidation = validationArray.some((v) => v.type === "pattern");
|
|
5504
|
-
if (field.type === "date" || field.type === "email" || field.type === "text" && hasPatternValidation) {
|
|
5940
|
+
const validationRules = getValidationRules(field);
|
|
5941
|
+
const hasPatternValidation = !!validationRules.pattern;
|
|
5942
|
+
const hasNumericTextValidation = isNumericTextField(field);
|
|
5943
|
+
if (field.type === "date" || field.type === "email" || field.type === "number" || field.type === "text" && (hasPatternValidation || hasNumericTextValidation || validationRules.minLength !== void 0 || validationRules.maxLength !== void 0)) {
|
|
5505
5944
|
validationMsg = createElement("div", { className: "text-xs text-red-600 dark:text-red-400 mt-1 hidden", id: `validation-${field.id}` });
|
|
5506
5945
|
}
|
|
5507
|
-
const validateField = (
|
|
5946
|
+
const validateField = (f, value2, inputElement, msgEl) => {
|
|
5947
|
+
const rules = getValidationRules(f);
|
|
5948
|
+
const custom2 = f.validations?.customErrorMessages;
|
|
5508
5949
|
let errorMessage = "";
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
rules.push({ type: "required", value: true });
|
|
5514
|
-
if (obj.regex)
|
|
5515
|
-
rules.push({ type: "pattern", regex: obj.regex, message: obj.regexMessage });
|
|
5516
|
-
if (obj.minLength !== void 0)
|
|
5517
|
-
rules.push({ type: "minLength", value: obj.minLength });
|
|
5518
|
-
if (obj.maxLength !== void 0)
|
|
5519
|
-
rules.push({ type: "maxLength", value: obj.maxLength });
|
|
5520
|
-
if (obj.minSelected !== void 0)
|
|
5521
|
-
rules.push({ type: "minSelected", value: obj.minSelected });
|
|
5522
|
-
if (obj.maxSelected !== void 0)
|
|
5523
|
-
rules.push({ type: "maxSelected", value: obj.maxSelected });
|
|
5524
|
-
if (obj.minDate)
|
|
5525
|
-
rules.push({ type: "minDate", value: obj.minDate });
|
|
5526
|
-
if (obj.maxDate)
|
|
5527
|
-
rules.push({ type: "maxDate", value: obj.maxDate });
|
|
5528
|
-
return rules;
|
|
5529
|
-
})() : [];
|
|
5530
|
-
if (field2.type === "date" && value2) {
|
|
5531
|
-
const minDateRule = validationArray2.find((v) => v.type === "minDate");
|
|
5532
|
-
const maxDateRule = validationArray2.find((v) => v.type === "maxDate");
|
|
5950
|
+
if (rules.required && (!value2 || String(value2).trim() === "")) {
|
|
5951
|
+
errorMessage = custom2?.required || "This field is required";
|
|
5952
|
+
}
|
|
5953
|
+
if (!errorMessage && f.type === "date" && value2) {
|
|
5533
5954
|
const inputDate = new Date(value2);
|
|
5534
|
-
if (
|
|
5535
|
-
const minDate = new Date(
|
|
5955
|
+
if (rules.minDate) {
|
|
5956
|
+
const minDate = new Date(rules.minDate);
|
|
5536
5957
|
if (inputDate < minDate) {
|
|
5537
|
-
errorMessage =
|
|
5958
|
+
errorMessage = "Date must be after the minimum date";
|
|
5538
5959
|
}
|
|
5539
5960
|
}
|
|
5540
|
-
if (
|
|
5541
|
-
const maxDate = new Date(
|
|
5961
|
+
if (!errorMessage && rules.maxDate) {
|
|
5962
|
+
const maxDate = new Date(rules.maxDate);
|
|
5542
5963
|
if (inputDate > maxDate) {
|
|
5543
|
-
errorMessage =
|
|
5964
|
+
errorMessage = "Date must be before the maximum date";
|
|
5544
5965
|
}
|
|
5545
5966
|
}
|
|
5546
5967
|
}
|
|
5547
|
-
if ((
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5968
|
+
if (!errorMessage && (f.type === "email" || f.type === "text" || f.type === "phone") && value2 && rules.pattern) {
|
|
5969
|
+
try {
|
|
5970
|
+
if (!new RegExp(rules.pattern).test(value2)) {
|
|
5971
|
+
errorMessage = rules.patternMessage || custom2?.pattern || "Invalid format";
|
|
5972
|
+
}
|
|
5973
|
+
} catch (_e) {
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
if (!errorMessage && value2 && (f.type === "text" || f.type === "phone")) {
|
|
5977
|
+
const len = String(value2).length;
|
|
5978
|
+
if (rules.minLength !== void 0 && len < rules.minLength) {
|
|
5979
|
+
errorMessage = custom2?.minLength || `Minimum length is ${rules.minLength}`;
|
|
5980
|
+
}
|
|
5981
|
+
if (!errorMessage && rules.maxLength !== void 0 && len > rules.maxLength) {
|
|
5982
|
+
errorMessage = custom2?.maxLength || `Maximum length is ${rules.maxLength}`;
|
|
5983
|
+
}
|
|
5984
|
+
}
|
|
5985
|
+
if (!errorMessage && f.type === "number" && value2 !== "" && value2 !== void 0) {
|
|
5986
|
+
const num = parseFloat(String(value2));
|
|
5987
|
+
if (!isNaN(num)) {
|
|
5988
|
+
if (f.validations?.allowNegative === false && num < 0) {
|
|
5989
|
+
errorMessage = custom2?.min || "Negative values are not allowed";
|
|
5990
|
+
}
|
|
5991
|
+
if (!errorMessage && rules.min !== void 0 && num < rules.min) {
|
|
5992
|
+
errorMessage = custom2?.min || `Value must be at least ${rules.min}`;
|
|
5993
|
+
}
|
|
5994
|
+
if (!errorMessage && rules.max !== void 0 && num > rules.max) {
|
|
5995
|
+
errorMessage = custom2?.max || `Value must be at most ${rules.max}`;
|
|
5556
5996
|
}
|
|
5557
5997
|
}
|
|
5558
5998
|
}
|
|
5559
5999
|
if (errorMessage) {
|
|
5560
|
-
|
|
5561
|
-
|
|
6000
|
+
msgEl.textContent = errorMessage;
|
|
6001
|
+
msgEl.classList.remove("hidden");
|
|
5562
6002
|
inputElement.classList.add("border-red-500");
|
|
5563
6003
|
} else {
|
|
5564
|
-
|
|
6004
|
+
msgEl.classList.add("hidden");
|
|
5565
6005
|
inputElement.classList.remove("border-red-500");
|
|
5566
6006
|
}
|
|
5567
6007
|
};
|
|
@@ -5695,59 +6135,40 @@ var FieldRenderer = class {
|
|
|
5695
6135
|
case "phone":
|
|
5696
6136
|
input = this.renderPhoneField(field, value, onChange, isEnabled);
|
|
5697
6137
|
break;
|
|
6138
|
+
case "image":
|
|
6139
|
+
input = this.renderImageField(field, value ?? field.imageUrl ?? field.defaultValue, onChange, isEnabled);
|
|
6140
|
+
break;
|
|
5698
6141
|
default:
|
|
5699
|
-
const
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
rules.push({ type: "minLength", value: obj.minLength });
|
|
5708
|
-
if (obj.maxLength !== void 0)
|
|
5709
|
-
rules.push({ type: "maxLength", value: obj.maxLength });
|
|
5710
|
-
if (obj.minSelected !== void 0)
|
|
5711
|
-
rules.push({ type: "minSelected", value: obj.minSelected });
|
|
5712
|
-
if (obj.maxSelected !== void 0)
|
|
5713
|
-
rules.push({ type: "maxSelected", value: obj.maxSelected });
|
|
5714
|
-
if (obj.minDate)
|
|
5715
|
-
rules.push({ type: "minDate", value: obj.minDate });
|
|
5716
|
-
if (obj.maxDate)
|
|
5717
|
-
rules.push({ type: "maxDate", value: obj.maxDate });
|
|
5718
|
-
return rules;
|
|
5719
|
-
})() : [];
|
|
5720
|
-
const patternRule = fieldValidationArray.find((v) => v.type === "pattern");
|
|
5721
|
-
const patternRegex = patternRule?.regex;
|
|
6142
|
+
const rules = getValidationRules(field);
|
|
6143
|
+
const useNumericTextInput = field.type === "text" && isNumericTextField(field);
|
|
6144
|
+
const inputType = useNumericTextInput ? "text" : field.type === "number" ? "number" : field.type;
|
|
6145
|
+
const runValidation = () => {
|
|
6146
|
+
if (validationMsg && (field.type === "date" || field.type === "email" || field.type === "text" || field.type === "number")) {
|
|
6147
|
+
validateField(field, input.value, input, validationMsg);
|
|
6148
|
+
}
|
|
6149
|
+
};
|
|
5722
6150
|
input = createElement("input", {
|
|
5723
|
-
type:
|
|
6151
|
+
type: inputType,
|
|
6152
|
+
...useNumericTextInput && { inputmode: "numeric" },
|
|
5724
6153
|
className: "flex min-h-touch w-full rounded-md border border-input bg-background px-3 py-2 text-sm sm:text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
5725
|
-
// type: field.type === 'phone' ? 'tel' : field.type,
|
|
5726
|
-
// className: 'flex min-h-touch w-full rounded-md border border-gray-300 bg-background px-3 py-2 text-sm sm:text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
|
5727
6154
|
placeholder: field.placeholder,
|
|
5728
6155
|
value: value || "",
|
|
5729
6156
|
disabled: !isEnabled,
|
|
5730
|
-
min: field.type === "date" ?
|
|
5731
|
-
max: field.type === "date" ?
|
|
5732
|
-
pattern:
|
|
5733
|
-
// Apply pattern for both email and text fields
|
|
6157
|
+
min: field.type === "date" ? rules.minDate : field.type === "number" ? rules.min !== void 0 ? String(rules.min) : void 0 : void 0,
|
|
6158
|
+
max: field.type === "date" ? rules.maxDate : field.type === "number" ? rules.max !== void 0 ? String(rules.max) : void 0 : void 0,
|
|
6159
|
+
pattern: !useNumericTextInput ? rules.pattern || void 0 : void 0,
|
|
5734
6160
|
oninput: (e) => {
|
|
5735
|
-
const
|
|
5736
|
-
|
|
5737
|
-
if (
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
},
|
|
5741
|
-
onchange: (e) => {
|
|
5742
|
-
if (validationMsg && (field.type === "date" || field.type === "email" || field.type === "text" && hasPatternValidation)) {
|
|
5743
|
-
validateField(field, e.target.value, input, validationMsg);
|
|
6161
|
+
const inputEl = e.target;
|
|
6162
|
+
let inputValue = inputEl.value;
|
|
6163
|
+
if (useNumericTextInput) {
|
|
6164
|
+
inputValue = inputValue.replace(/[eE+-]/g, "");
|
|
6165
|
+
inputEl.value = inputValue;
|
|
5744
6166
|
}
|
|
6167
|
+
onChange?.(inputValue);
|
|
6168
|
+
runValidation();
|
|
5745
6169
|
},
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
validateField(field, e.target.value, input, validationMsg);
|
|
5749
|
-
}
|
|
5750
|
-
}
|
|
6170
|
+
onchange: () => runValidation(),
|
|
6171
|
+
onblur: () => runValidation()
|
|
5751
6172
|
});
|
|
5752
6173
|
}
|
|
5753
6174
|
wrapper.appendChild(input);
|
|
@@ -5843,6 +6264,93 @@ var FieldRenderer = class {
|
|
|
5843
6264
|
});
|
|
5844
6265
|
return container;
|
|
5845
6266
|
}
|
|
6267
|
+
/**
|
|
6268
|
+
* Render image field with upload, preview, and remove/replace
|
|
6269
|
+
*/
|
|
6270
|
+
static renderImageField(field, imageValue, onChange, isEnabled = true) {
|
|
6271
|
+
const ACCEPT = "image/jpeg,image/png,image/gif,image/webp";
|
|
6272
|
+
const MAX_SIZE_MB = 5;
|
|
6273
|
+
const MAX_SIZE_BYTES = MAX_SIZE_MB * 1024 * 1024;
|
|
6274
|
+
const container = createElement("div", { className: "image-field-wrapper flex flex-col gap-3 w-full" });
|
|
6275
|
+
const previewWrap = createElement("div", {
|
|
6276
|
+
className: "relative rounded-md border border-input bg-gray-50 dark:bg-gray-800 overflow-hidden min-h-[120px] flex items-center justify-center"
|
|
6277
|
+
});
|
|
6278
|
+
if (imageValue) {
|
|
6279
|
+
const img = createElement("img", {
|
|
6280
|
+
src: imageValue,
|
|
6281
|
+
alt: field.label || "Uploaded image",
|
|
6282
|
+
className: "max-h-48 max-w-full object-contain"
|
|
6283
|
+
});
|
|
6284
|
+
img.onerror = () => {
|
|
6285
|
+
previewWrap.innerHTML = "";
|
|
6286
|
+
previewWrap.appendChild(createElement("p", {
|
|
6287
|
+
className: "text-xs text-red-500 p-4",
|
|
6288
|
+
text: "Failed to load image"
|
|
6289
|
+
}));
|
|
6290
|
+
};
|
|
6291
|
+
previewWrap.appendChild(img);
|
|
6292
|
+
if (isEnabled) {
|
|
6293
|
+
const removeBtn = createElement("button", {
|
|
6294
|
+
type: "button",
|
|
6295
|
+
className: "absolute top-2 right-2 p-1.5 bg-red-500 text-white rounded-md hover:bg-red-600 text-xs",
|
|
6296
|
+
title: "Remove image",
|
|
6297
|
+
onclick: () => onChange?.(void 0)
|
|
6298
|
+
});
|
|
6299
|
+
removeBtn.innerHTML = "\xD7";
|
|
6300
|
+
previewWrap.appendChild(removeBtn);
|
|
6301
|
+
}
|
|
6302
|
+
} else {
|
|
6303
|
+
previewWrap.appendChild(createElement("p", {
|
|
6304
|
+
className: "text-xs text-muted-foreground p-4",
|
|
6305
|
+
text: "No image uploaded"
|
|
6306
|
+
}));
|
|
6307
|
+
}
|
|
6308
|
+
container.appendChild(previewWrap);
|
|
6309
|
+
if (isEnabled) {
|
|
6310
|
+
const fileInput = createElement("input", {
|
|
6311
|
+
type: "file",
|
|
6312
|
+
accept: ACCEPT,
|
|
6313
|
+
className: "hidden",
|
|
6314
|
+
id: `image-upload-${field.id}`
|
|
6315
|
+
});
|
|
6316
|
+
const uploadBtn = createElement("button", {
|
|
6317
|
+
type: "button",
|
|
6318
|
+
className: "px-3 py-2 text-sm border border-gray-300 dark:border-gray-700 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
6319
|
+
text: imageValue ? "Replace" : "Upload Image",
|
|
6320
|
+
onclick: () => fileInput.click()
|
|
6321
|
+
});
|
|
6322
|
+
fileInput.onchange = (e) => {
|
|
6323
|
+
const target = e.target;
|
|
6324
|
+
const file = target.files?.[0];
|
|
6325
|
+
if (!file)
|
|
6326
|
+
return;
|
|
6327
|
+
if (!file.type.match(/^image\/(jpeg|png|gif|webp)$/)) {
|
|
6328
|
+
alert(`Please select a valid image (JPG, PNG, GIF, WebP)`);
|
|
6329
|
+
target.value = "";
|
|
6330
|
+
return;
|
|
6331
|
+
}
|
|
6332
|
+
if (file.size > MAX_SIZE_BYTES) {
|
|
6333
|
+
alert(`Image must be under ${MAX_SIZE_MB}MB`);
|
|
6334
|
+
target.value = "";
|
|
6335
|
+
return;
|
|
6336
|
+
}
|
|
6337
|
+
const reader = new FileReader();
|
|
6338
|
+
reader.onload = () => {
|
|
6339
|
+
const result = reader.result;
|
|
6340
|
+
if (result)
|
|
6341
|
+
onChange?.(result);
|
|
6342
|
+
};
|
|
6343
|
+
reader.onerror = () => {
|
|
6344
|
+
alert("Failed to read image");
|
|
6345
|
+
};
|
|
6346
|
+
reader.readAsDataURL(file);
|
|
6347
|
+
target.value = "";
|
|
6348
|
+
};
|
|
6349
|
+
container.appendChild(fileInput);
|
|
6350
|
+
container.appendChild(uploadBtn);
|
|
6351
|
+
}
|
|
6352
|
+
return container;
|
|
6353
|
+
}
|
|
5846
6354
|
};
|
|
5847
6355
|
|
|
5848
6356
|
// src/renderer/FormRenderer.ts
|
|
@@ -5861,6 +6369,10 @@ function convertValidationToArray(validation) {
|
|
|
5861
6369
|
rules.push({ type: "minLength", value: obj.minLength });
|
|
5862
6370
|
if (obj.maxLength !== void 0)
|
|
5863
6371
|
rules.push({ type: "maxLength", value: obj.maxLength });
|
|
6372
|
+
if (obj.min !== void 0)
|
|
6373
|
+
rules.push({ type: "min", value: obj.min });
|
|
6374
|
+
if (obj.max !== void 0)
|
|
6375
|
+
rules.push({ type: "max", value: obj.max });
|
|
5864
6376
|
if (obj.minSelected !== void 0)
|
|
5865
6377
|
rules.push({ type: "minSelected", value: obj.minSelected });
|
|
5866
6378
|
if (obj.maxSelected !== void 0)
|
|
@@ -5871,9 +6383,146 @@ function convertValidationToArray(validation) {
|
|
|
5871
6383
|
rules.push({ type: "maxDate", value: obj.maxDate });
|
|
5872
6384
|
return rules;
|
|
5873
6385
|
}
|
|
6386
|
+
function getValidationRulesForField(field) {
|
|
6387
|
+
const v = field.validations;
|
|
6388
|
+
if (v) {
|
|
6389
|
+
const obj = {};
|
|
6390
|
+
if (v.required)
|
|
6391
|
+
obj.required = true;
|
|
6392
|
+
if (v.pattern)
|
|
6393
|
+
obj.regex = v.pattern;
|
|
6394
|
+
if (v.customErrorMessages?.pattern)
|
|
6395
|
+
obj.regexMessage = v.customErrorMessages.pattern;
|
|
6396
|
+
if (v.minLength !== void 0)
|
|
6397
|
+
obj.minLength = v.minLength;
|
|
6398
|
+
if (v.maxLength !== void 0)
|
|
6399
|
+
obj.maxLength = v.maxLength;
|
|
6400
|
+
if (v.min !== void 0)
|
|
6401
|
+
obj.min = v.min;
|
|
6402
|
+
if (v.max !== void 0)
|
|
6403
|
+
obj.max = v.max;
|
|
6404
|
+
if (v.minSelected !== void 0)
|
|
6405
|
+
obj.minSelected = v.minSelected;
|
|
6406
|
+
if (v.maxSelected !== void 0)
|
|
6407
|
+
obj.maxSelected = v.maxSelected;
|
|
6408
|
+
if (v.minDate)
|
|
6409
|
+
obj.minDate = v.minDate;
|
|
6410
|
+
if (v.maxDate)
|
|
6411
|
+
obj.maxDate = v.maxDate;
|
|
6412
|
+
return convertValidationToArray(obj);
|
|
6413
|
+
}
|
|
6414
|
+
return convertValidationToArray(field.validation);
|
|
6415
|
+
}
|
|
6416
|
+
function getFieldValidationError(field, fieldValue) {
|
|
6417
|
+
const isRequired = field.validations?.required ?? field.required;
|
|
6418
|
+
if (isRequired && (!fieldValue || fieldValue === "" || Array.isArray(fieldValue) && fieldValue.length === 0)) {
|
|
6419
|
+
return field.validations?.customErrorMessages?.required || "This field is required";
|
|
6420
|
+
}
|
|
6421
|
+
if ((field.type === "text" || field.type === "email" || field.type === "phone") && fieldValue) {
|
|
6422
|
+
const validationArray = getValidationRulesForField(field);
|
|
6423
|
+
const patternRule = validationArray.find((v) => v.type === "pattern");
|
|
6424
|
+
if (patternRule?.regex) {
|
|
6425
|
+
try {
|
|
6426
|
+
if (!new RegExp(patternRule.regex).test(String(fieldValue))) {
|
|
6427
|
+
return field.validations?.customErrorMessages?.pattern || patternRule.message || "Invalid format";
|
|
6428
|
+
}
|
|
6429
|
+
} catch (_e) {
|
|
6430
|
+
}
|
|
6431
|
+
}
|
|
6432
|
+
const minLenRule = validationArray.find((v) => v.type === "minLength");
|
|
6433
|
+
const maxLenRule = validationArray.find((v) => v.type === "maxLength");
|
|
6434
|
+
const len = String(fieldValue).length;
|
|
6435
|
+
if (minLenRule && typeof minLenRule.value === "number" && len < minLenRule.value) {
|
|
6436
|
+
return field.validations?.customErrorMessages?.minLength || `Minimum length is ${minLenRule.value}`;
|
|
6437
|
+
}
|
|
6438
|
+
if (maxLenRule && typeof maxLenRule.value === "number" && len > maxLenRule.value) {
|
|
6439
|
+
return field.validations?.customErrorMessages?.maxLength || `Maximum length is ${maxLenRule.value}`;
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6442
|
+
if (field.type === "number" && fieldValue !== "" && fieldValue !== void 0) {
|
|
6443
|
+
const v = field.validations;
|
|
6444
|
+
const num = parseFloat(String(fieldValue));
|
|
6445
|
+
if (!isNaN(num)) {
|
|
6446
|
+
if (v?.min !== void 0 && num < v.min) {
|
|
6447
|
+
return v.customErrorMessages?.min || `Value must be at least ${v.min}`;
|
|
6448
|
+
}
|
|
6449
|
+
if (v?.max !== void 0 && num > v.max) {
|
|
6450
|
+
return v.customErrorMessages?.max || `Value must be at most ${v.max}`;
|
|
6451
|
+
}
|
|
6452
|
+
if (v?.allowNegative === false && num < 0) {
|
|
6453
|
+
return v.customErrorMessages?.min || "Negative values are not allowed";
|
|
6454
|
+
}
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6457
|
+
if (field.type === "checkbox" && Array.isArray(fieldValue)) {
|
|
6458
|
+
const validationArray = getValidationRulesForField(field);
|
|
6459
|
+
const minSelectedRule = validationArray.find((v) => v.type === "minSelected");
|
|
6460
|
+
const maxSelectedRule = validationArray.find((v) => v.type === "maxSelected");
|
|
6461
|
+
const selectedCount = fieldValue.length;
|
|
6462
|
+
const minSelected = typeof minSelectedRule?.value === "number" ? minSelectedRule.value : void 0;
|
|
6463
|
+
const maxSelected = typeof maxSelectedRule?.value === "number" ? maxSelectedRule.value : void 0;
|
|
6464
|
+
if (minSelected !== void 0 && selectedCount < minSelected) {
|
|
6465
|
+
return `Please select at least ${minSelected} option(s)`;
|
|
6466
|
+
}
|
|
6467
|
+
if (maxSelected !== void 0 && selectedCount > maxSelected) {
|
|
6468
|
+
return `Please select at most ${maxSelected} option(s)`;
|
|
6469
|
+
}
|
|
6470
|
+
}
|
|
6471
|
+
return "";
|
|
6472
|
+
}
|
|
5874
6473
|
function getModelKey(field) {
|
|
5875
6474
|
return field.fieldName ?? field.id;
|
|
5876
6475
|
}
|
|
6476
|
+
function buildFormulaValuesMap(schema, data) {
|
|
6477
|
+
const values = {};
|
|
6478
|
+
const allFields = schema.sections.flatMap((s) => s.fields);
|
|
6479
|
+
for (const field of allFields) {
|
|
6480
|
+
const modelKey = getModelKey(field);
|
|
6481
|
+
const val = data[modelKey];
|
|
6482
|
+
values[modelKey] = val;
|
|
6483
|
+
values[field.id] = val;
|
|
6484
|
+
if (field.fieldName)
|
|
6485
|
+
values[field.fieldName] = val;
|
|
6486
|
+
}
|
|
6487
|
+
const formulaFields = allFields.filter((f) => f.type === "number" && f.valueSource === "formula" && f.formula);
|
|
6488
|
+
for (let pass = 0; pass < Math.max(1, formulaFields.length); pass++) {
|
|
6489
|
+
for (const field of formulaFields) {
|
|
6490
|
+
const modelKey = getModelKey(field);
|
|
6491
|
+
const result = evaluateFormula(field.formula, values);
|
|
6492
|
+
const newVal = isNaN(result) ? void 0 : result;
|
|
6493
|
+
values[modelKey] = newVal;
|
|
6494
|
+
values[field.id] = newVal;
|
|
6495
|
+
if (field.fieldName)
|
|
6496
|
+
values[field.fieldName] = newVal;
|
|
6497
|
+
}
|
|
6498
|
+
}
|
|
6499
|
+
return values;
|
|
6500
|
+
}
|
|
6501
|
+
function computeFormulaValue(field, schema, data) {
|
|
6502
|
+
if (field.type !== "number" || field.valueSource !== "formula" || !field.formula)
|
|
6503
|
+
return void 0;
|
|
6504
|
+
const values = buildFormulaValuesMap(schema, data);
|
|
6505
|
+
const modelKey = getModelKey(field);
|
|
6506
|
+
const result = values[modelKey];
|
|
6507
|
+
if (typeof result !== "number")
|
|
6508
|
+
return void 0;
|
|
6509
|
+
const decimalPlaces = field.validations?.decimalPlaces ?? (field.validations?.allowDecimal ? 2 : 0);
|
|
6510
|
+
const rounded = decimalPlaces > 0 ? Math.round(result * Math.pow(10, decimalPlaces)) / Math.pow(10, decimalPlaces) : Math.round(result);
|
|
6511
|
+
return rounded;
|
|
6512
|
+
}
|
|
6513
|
+
function isFormulaDependency(schema, modelKey, fieldId) {
|
|
6514
|
+
for (const section of schema.sections) {
|
|
6515
|
+
for (const field of section.fields) {
|
|
6516
|
+
if (field.type === "number" && field.valueSource === "formula" && field.dependencies) {
|
|
6517
|
+
if (field.dependencies.includes(modelKey))
|
|
6518
|
+
return true;
|
|
6519
|
+
if (fieldId && field.dependencies.includes(fieldId))
|
|
6520
|
+
return true;
|
|
6521
|
+
}
|
|
6522
|
+
}
|
|
6523
|
+
}
|
|
6524
|
+
return false;
|
|
6525
|
+
}
|
|
5877
6526
|
var FormRenderer = class {
|
|
5878
6527
|
constructor(container, schema, onSubmit, onDropdownValueChange, initialData) {
|
|
5879
6528
|
__publicField(this, "container");
|
|
@@ -5918,15 +6567,35 @@ var FormRenderer = class {
|
|
|
5918
6567
|
}
|
|
5919
6568
|
fieldWrapper.className = spanClass;
|
|
5920
6569
|
const modelKey = getModelKey(field);
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
6570
|
+
let fieldValue;
|
|
6571
|
+
if (field.type === "number" && field.valueSource === "formula" && field.formula) {
|
|
6572
|
+
const computed = computeFormulaValue(field, this.schema, this.data);
|
|
6573
|
+
fieldValue = computed;
|
|
6574
|
+
this.data[modelKey] = computed;
|
|
6575
|
+
} else if (field.type === "image") {
|
|
6576
|
+
fieldValue = this.data[modelKey] ?? field.imageUrl ?? field.defaultValue;
|
|
6577
|
+
} else {
|
|
6578
|
+
fieldValue = this.data[modelKey];
|
|
6579
|
+
}
|
|
6580
|
+
const isFormulaField = field.type === "number" && field.valueSource === "formula";
|
|
6581
|
+
const fieldEl = FieldRenderer.render(
|
|
6582
|
+
field,
|
|
6583
|
+
fieldValue,
|
|
6584
|
+
(val) => {
|
|
6585
|
+
this.data[modelKey] = val;
|
|
6586
|
+
if (field.type === "select" && this.onDropdownValueChange) {
|
|
6587
|
+
this.onDropdownValueChange({
|
|
6588
|
+
fieldId: field.id,
|
|
6589
|
+
value: val || ""
|
|
6590
|
+
});
|
|
6591
|
+
}
|
|
6592
|
+
if (isFormulaDependency(this.schema, modelKey, field.id)) {
|
|
6593
|
+
this.render();
|
|
6594
|
+
}
|
|
6595
|
+
},
|
|
6596
|
+
isFormulaField
|
|
6597
|
+
// Formula fields are read-only
|
|
6598
|
+
);
|
|
5930
6599
|
fieldWrapper.appendChild(fieldEl);
|
|
5931
6600
|
grid.appendChild(fieldWrapper);
|
|
5932
6601
|
});
|
|
@@ -5949,107 +6618,19 @@ var FormRenderer = class {
|
|
|
5949
6618
|
const modelKey = getModelKey(field);
|
|
5950
6619
|
const fieldValue = this.data[modelKey];
|
|
5951
6620
|
const fieldElement = form.querySelector(`input[id*="${field.id}"], textarea[id*="${field.id}"], select[id*="${field.id}"]`);
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
isValid2 = false;
|
|
5960
|
-
altElement.setCustomValidity("This field is required");
|
|
5961
|
-
altElement.reportValidity();
|
|
5962
|
-
invalidFields.push(altElement);
|
|
5963
|
-
} else {
|
|
5964
|
-
altElement.setCustomValidity("");
|
|
5965
|
-
}
|
|
5966
|
-
if ((field.type === "text" || field.type === "email") && fieldValue) {
|
|
5967
|
-
const validationArray = convertValidationToArray(field.validation);
|
|
5968
|
-
const patternRule = validationArray.find((v) => v.type === "pattern");
|
|
5969
|
-
if (patternRule?.regex) {
|
|
5970
|
-
try {
|
|
5971
|
-
const regex = new RegExp(patternRule.regex);
|
|
5972
|
-
if (!regex.test(String(fieldValue))) {
|
|
5973
|
-
isValid2 = false;
|
|
5974
|
-
altElement.setCustomValidity(patternRule.message || "Invalid format");
|
|
5975
|
-
altElement.reportValidity();
|
|
5976
|
-
invalidFields.push(altElement);
|
|
5977
|
-
} else {
|
|
5978
|
-
altElement.setCustomValidity("");
|
|
5979
|
-
}
|
|
5980
|
-
} catch (e2) {
|
|
5981
|
-
}
|
|
5982
|
-
}
|
|
5983
|
-
}
|
|
5984
|
-
if (field.type === "checkbox" && Array.isArray(fieldValue)) {
|
|
5985
|
-
const validationArray = convertValidationToArray(field.validation);
|
|
5986
|
-
const minSelectedRule = validationArray.find((v) => v.type === "minSelected");
|
|
5987
|
-
const maxSelectedRule = validationArray.find((v) => v.type === "maxSelected");
|
|
5988
|
-
const selectedCount = fieldValue.length;
|
|
5989
|
-
const minSelected = typeof minSelectedRule?.value === "number" ? minSelectedRule.value : void 0;
|
|
5990
|
-
const maxSelected = typeof maxSelectedRule?.value === "number" ? maxSelectedRule.value : void 0;
|
|
5991
|
-
if (minSelected !== void 0 && selectedCount < minSelected) {
|
|
5992
|
-
isValid2 = false;
|
|
5993
|
-
altElement.setCustomValidity(`Please select at least ${minSelected} option(s)`);
|
|
5994
|
-
altElement.reportValidity();
|
|
5995
|
-
invalidFields.push(altElement);
|
|
5996
|
-
} else if (maxSelected !== void 0 && selectedCount > maxSelected) {
|
|
5997
|
-
isValid2 = false;
|
|
5998
|
-
altElement.setCustomValidity(`Please select at most ${maxSelected} option(s)`);
|
|
5999
|
-
altElement.reportValidity();
|
|
6000
|
-
invalidFields.push(altElement);
|
|
6001
|
-
} else {
|
|
6002
|
-
altElement.setCustomValidity("");
|
|
6003
|
-
}
|
|
6004
|
-
}
|
|
6005
|
-
}
|
|
6006
|
-
return;
|
|
6007
|
-
}
|
|
6008
|
-
if (field.required && (!fieldValue || fieldValue === "" || Array.isArray(fieldValue) && fieldValue.length === 0)) {
|
|
6009
|
-
isValid2 = false;
|
|
6010
|
-
fieldElement.setCustomValidity("This field is required");
|
|
6011
|
-
fieldElement.reportValidity();
|
|
6012
|
-
invalidFields.push(fieldElement);
|
|
6013
|
-
} else {
|
|
6014
|
-
fieldElement.setCustomValidity("");
|
|
6015
|
-
}
|
|
6016
|
-
if ((field.type === "text" || field.type === "email") && fieldValue) {
|
|
6017
|
-
const validationArray = convertValidationToArray(field.validation);
|
|
6018
|
-
const patternRule = validationArray.find((v) => v.type === "pattern");
|
|
6019
|
-
if (patternRule?.regex) {
|
|
6020
|
-
try {
|
|
6021
|
-
const regex = new RegExp(patternRule.regex);
|
|
6022
|
-
if (!regex.test(String(fieldValue))) {
|
|
6023
|
-
isValid2 = false;
|
|
6024
|
-
fieldElement.setCustomValidity(patternRule.message || "Invalid format");
|
|
6025
|
-
fieldElement.reportValidity();
|
|
6026
|
-
invalidFields.push(fieldElement);
|
|
6027
|
-
} else {
|
|
6028
|
-
fieldElement.setCustomValidity("");
|
|
6029
|
-
}
|
|
6030
|
-
} catch (e2) {
|
|
6031
|
-
}
|
|
6032
|
-
}
|
|
6033
|
-
}
|
|
6034
|
-
if (field.type === "checkbox" && Array.isArray(fieldValue)) {
|
|
6035
|
-
const validationArray = convertValidationToArray(field.validation);
|
|
6036
|
-
const minSelectedRule = validationArray.find((v) => v.type === "minSelected");
|
|
6037
|
-
const maxSelectedRule = validationArray.find((v) => v.type === "maxSelected");
|
|
6038
|
-
const selectedCount = fieldValue.length;
|
|
6039
|
-
const minSelected = typeof minSelectedRule?.value === "number" ? minSelectedRule.value : void 0;
|
|
6040
|
-
const maxSelected = typeof maxSelectedRule?.value === "number" ? maxSelectedRule.value : void 0;
|
|
6041
|
-
if (minSelected !== void 0 && selectedCount < minSelected) {
|
|
6042
|
-
isValid2 = false;
|
|
6043
|
-
fieldElement.setCustomValidity(`Please select at least ${minSelected} option(s)`);
|
|
6044
|
-
fieldElement.reportValidity();
|
|
6045
|
-
invalidFields.push(fieldElement);
|
|
6046
|
-
} else if (maxSelected !== void 0 && selectedCount > maxSelected) {
|
|
6621
|
+
const fieldError = getFieldValidationError(field, fieldValue);
|
|
6622
|
+
const element = fieldElement ?? Array.from(form.querySelectorAll("input, textarea, select")).find((el) => {
|
|
6623
|
+
const wrapper = el.closest("div");
|
|
6624
|
+
return wrapper && wrapper.textContent?.includes(field.label);
|
|
6625
|
+
});
|
|
6626
|
+
if (element) {
|
|
6627
|
+
if (fieldError) {
|
|
6047
6628
|
isValid2 = false;
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
invalidFields.push(
|
|
6629
|
+
element.setCustomValidity(fieldError);
|
|
6630
|
+
element.reportValidity();
|
|
6631
|
+
invalidFields.push(element);
|
|
6051
6632
|
} else {
|
|
6052
|
-
|
|
6633
|
+
element.setCustomValidity("");
|
|
6053
6634
|
}
|
|
6054
6635
|
}
|
|
6055
6636
|
});
|
|
@@ -9069,6 +9650,36 @@ var FormBuilder = class {
|
|
|
9069
9650
|
className: "flex items-center px-3 py-2 text-sm font-medium text-[#635bff] bg-[#e7e7ff] rounded-md shadow-sm transition-colors",
|
|
9070
9651
|
onclick: () => {
|
|
9071
9652
|
const schema = formStore.getState().schema;
|
|
9653
|
+
const numericFields = schema.sections.flatMap((s) => s.fields).filter((f) => f.type === "number");
|
|
9654
|
+
const allIds = numericFields.map((f) => f.id);
|
|
9655
|
+
const allNames = numericFields.map((f) => f.fieldName ?? f.id);
|
|
9656
|
+
for (const field of schema.sections.flatMap((s) => s.fields)) {
|
|
9657
|
+
if (field.type === "number" && field.valueSource === "formula" && field.formula) {
|
|
9658
|
+
const validation = validateFormula(field.formula, allIds, allNames, field.id);
|
|
9659
|
+
if (!validation.valid) {
|
|
9660
|
+
alert(`Formula error in "${field.label}": ${validation.error}`);
|
|
9661
|
+
return;
|
|
9662
|
+
}
|
|
9663
|
+
const deps = field.dependencies ?? parseFormulaDependencies(field.formula);
|
|
9664
|
+
if (detectCircularDependency(schema, field.id, field.formula, deps)) {
|
|
9665
|
+
alert(`Circular dependency in formula for "${field.label}"`);
|
|
9666
|
+
return;
|
|
9667
|
+
}
|
|
9668
|
+
}
|
|
9669
|
+
}
|
|
9670
|
+
schema.sections.forEach((section) => {
|
|
9671
|
+
section.fields?.forEach((field) => {
|
|
9672
|
+
if (field.type === "number" && field.validations) {
|
|
9673
|
+
console.log("[Form Builder] Number field validations before save:", {
|
|
9674
|
+
fieldId: field.id,
|
|
9675
|
+
label: field.label,
|
|
9676
|
+
validations: field.validations,
|
|
9677
|
+
hasMin: "min" in field.validations,
|
|
9678
|
+
hasMax: "max" in field.validations
|
|
9679
|
+
});
|
|
9680
|
+
}
|
|
9681
|
+
});
|
|
9682
|
+
});
|
|
9072
9683
|
console.log("[Form Builder] Schema being sent to app:", JSON.stringify(schema, null, 2));
|
|
9073
9684
|
if (this.options.onSave) {
|
|
9074
9685
|
this.options.onSave(schema);
|
|
@@ -9275,17 +9886,178 @@ var FormBuilder = class {
|
|
|
9275
9886
|
}
|
|
9276
9887
|
}));
|
|
9277
9888
|
body.appendChild(labelGroup);
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
"
|
|
9284
|
-
|
|
9285
|
-
|
|
9889
|
+
if (selectedField.type === "number") {
|
|
9890
|
+
const valueSourceHeader = createElement("h3", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2 mt-4", text: "Value Source" });
|
|
9891
|
+
body.appendChild(valueSourceHeader);
|
|
9892
|
+
const valueSourceGroup = createElement("div", { className: "mb-3" });
|
|
9893
|
+
valueSourceGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Source" }));
|
|
9894
|
+
const valueSourceSelect = createElement("select", {
|
|
9895
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
9896
|
+
value: selectedField.valueSource || "manual",
|
|
9897
|
+
onchange: (e) => {
|
|
9898
|
+
const source = e.target.value;
|
|
9899
|
+
const updates = { valueSource: source };
|
|
9900
|
+
if (source === "manual") {
|
|
9901
|
+
updates.formula = void 0;
|
|
9902
|
+
updates.dependencies = void 0;
|
|
9903
|
+
} else if (source === "formula") {
|
|
9904
|
+
updates.formula = selectedField.formula || "";
|
|
9905
|
+
updates.dependencies = selectedField.dependencies || [];
|
|
9906
|
+
}
|
|
9907
|
+
formStore.getState().updateField(selectedField.id, updates);
|
|
9908
|
+
this.render();
|
|
9909
|
+
}
|
|
9910
|
+
});
|
|
9911
|
+
valueSourceSelect.appendChild(createElement("option", { value: "manual", text: "Manual", selected: (selectedField.valueSource || "manual") === "manual" }));
|
|
9912
|
+
valueSourceSelect.appendChild(createElement("option", { value: "formula", text: "Formula", selected: selectedField.valueSource === "formula" }));
|
|
9913
|
+
valueSourceGroup.appendChild(valueSourceSelect);
|
|
9914
|
+
body.appendChild(valueSourceGroup);
|
|
9915
|
+
if (selectedField.valueSource === "formula") {
|
|
9916
|
+
const schema = formStore.getState().schema;
|
|
9917
|
+
const numericFields = getNumericFieldsForFormula(schema, selectedField.id);
|
|
9918
|
+
const availableIds = numericFields.map((f) => f.id);
|
|
9919
|
+
const availableNames = numericFields.map((f) => f.fieldName);
|
|
9920
|
+
const formulaGroup = createElement("div", { className: "mb-3" });
|
|
9921
|
+
formulaGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Formula" }));
|
|
9922
|
+
const formulaInput = createElement("input", {
|
|
9923
|
+
type: "text",
|
|
9924
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent font-mono text-sm",
|
|
9925
|
+
value: selectedField.formula || "",
|
|
9926
|
+
placeholder: "e.g. quantity * price",
|
|
9927
|
+
"data-focus-id": `field-formula-${selectedField.id}`,
|
|
9928
|
+
oninput: (e) => {
|
|
9929
|
+
const formula = e.target.value.trim();
|
|
9930
|
+
const deps = parseFormulaDependencies(formula);
|
|
9931
|
+
const validation = validateFormula(formula, availableIds, availableNames, selectedField.id);
|
|
9932
|
+
const hasCircular = deps.length > 0 && detectCircularDependency(schema, selectedField.id, formula, deps);
|
|
9933
|
+
const errEl = formulaGroup.querySelector(".formula-error");
|
|
9934
|
+
if (errEl) {
|
|
9935
|
+
if (validation.valid && !hasCircular) {
|
|
9936
|
+
errEl.textContent = "";
|
|
9937
|
+
errEl.classList.add("hidden");
|
|
9938
|
+
} else {
|
|
9939
|
+
errEl.textContent = !validation.valid ? validation.error : "Circular dependency detected";
|
|
9940
|
+
errEl.classList.remove("hidden");
|
|
9941
|
+
}
|
|
9942
|
+
}
|
|
9943
|
+
formStore.getState().updateField(selectedField.id, { formula, dependencies: deps });
|
|
9944
|
+
}
|
|
9945
|
+
});
|
|
9946
|
+
formulaGroup.appendChild(formulaInput);
|
|
9947
|
+
const formulaError = createElement("div", { className: "text-xs text-red-600 dark:text-red-400 mt-1 formula-error hidden" });
|
|
9948
|
+
formulaGroup.appendChild(formulaError);
|
|
9949
|
+
body.appendChild(formulaGroup);
|
|
9950
|
+
const insertGroup = createElement("div", { className: "mb-3" });
|
|
9951
|
+
insertGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Insert Field" }));
|
|
9952
|
+
const insertSelect = createElement("select", {
|
|
9953
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
9954
|
+
onchange: (e) => {
|
|
9955
|
+
const sel = e.target;
|
|
9956
|
+
const ref = sel.value;
|
|
9957
|
+
if (!ref)
|
|
9958
|
+
return;
|
|
9959
|
+
const current = selectedField.formula || "";
|
|
9960
|
+
const insert = current ? ` ${ref} ` : ref;
|
|
9961
|
+
const newFormula = current + insert;
|
|
9962
|
+
formStore.getState().updateField(selectedField.id, {
|
|
9963
|
+
formula: newFormula,
|
|
9964
|
+
dependencies: parseFormulaDependencies(newFormula)
|
|
9965
|
+
});
|
|
9966
|
+
formulaInput.value = newFormula;
|
|
9967
|
+
sel.value = "";
|
|
9968
|
+
this.render();
|
|
9969
|
+
}
|
|
9970
|
+
});
|
|
9971
|
+
insertSelect.appendChild(createElement("option", { value: "", text: "Select field to insert...", selected: true }));
|
|
9972
|
+
numericFields.forEach((f) => {
|
|
9973
|
+
const ref = f.fieldName !== f.id ? f.fieldName : f.id;
|
|
9974
|
+
insertSelect.appendChild(createElement("option", { value: ref, text: `${f.label} (${ref})` }));
|
|
9975
|
+
});
|
|
9976
|
+
insertGroup.appendChild(insertSelect);
|
|
9977
|
+
body.appendChild(insertGroup);
|
|
9978
|
+
const hintEl = createElement("p", {
|
|
9979
|
+
className: "text-xs text-gray-500 dark:text-gray-400 mb-2",
|
|
9980
|
+
text: "Use +, -, *, / and parentheses. Reference fields by their name or ID."
|
|
9981
|
+
});
|
|
9982
|
+
body.appendChild(hintEl);
|
|
9286
9983
|
}
|
|
9287
|
-
}
|
|
9288
|
-
|
|
9984
|
+
}
|
|
9985
|
+
if (selectedField.type !== "image") {
|
|
9986
|
+
const placeholderGroup = createElement("div");
|
|
9987
|
+
placeholderGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Placeholder" }));
|
|
9988
|
+
placeholderGroup.appendChild(createElement("input", {
|
|
9989
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
9990
|
+
value: selectedField.placeholder || "",
|
|
9991
|
+
"data-focus-id": `field-placeholder-${selectedField.id}`,
|
|
9992
|
+
oninput: (e) => {
|
|
9993
|
+
formStore.getState().updateField(selectedField.id, { placeholder: e.target.value });
|
|
9994
|
+
}
|
|
9995
|
+
}));
|
|
9996
|
+
body.appendChild(placeholderGroup);
|
|
9997
|
+
}
|
|
9998
|
+
if (selectedField.type === "image") {
|
|
9999
|
+
const imageUrl = selectedField.imageUrl ?? selectedField.defaultValue;
|
|
10000
|
+
const imageGroup = createElement("div", { className: "mb-4" });
|
|
10001
|
+
imageGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-2", text: "Image" }));
|
|
10002
|
+
const previewWrap = createElement("div", {
|
|
10003
|
+
className: "rounded-md border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 overflow-hidden min-h-[80px] flex items-center justify-center mb-2"
|
|
10004
|
+
});
|
|
10005
|
+
if (imageUrl) {
|
|
10006
|
+
const img = createElement("img", {
|
|
10007
|
+
src: imageUrl,
|
|
10008
|
+
alt: selectedField.label || "Image",
|
|
10009
|
+
className: "max-h-32 max-w-full object-contain"
|
|
10010
|
+
});
|
|
10011
|
+
img.onerror = () => {
|
|
10012
|
+
previewWrap.innerHTML = "";
|
|
10013
|
+
previewWrap.appendChild(createElement("p", { className: "text-xs text-red-500 p-2", text: "Failed to load" }));
|
|
10014
|
+
};
|
|
10015
|
+
previewWrap.appendChild(img);
|
|
10016
|
+
} else {
|
|
10017
|
+
previewWrap.appendChild(createElement("p", { className: "text-xs text-muted-foreground p-2", text: "No image" }));
|
|
10018
|
+
}
|
|
10019
|
+
imageGroup.appendChild(previewWrap);
|
|
10020
|
+
const btnRow = createElement("div", { className: "flex gap-2" });
|
|
10021
|
+
const fileInput = createElement("input", {
|
|
10022
|
+
type: "file",
|
|
10023
|
+
accept: "image/jpeg,image/png,image/gif,image/webp",
|
|
10024
|
+
className: "hidden",
|
|
10025
|
+
id: `config-image-${selectedField.id}`
|
|
10026
|
+
});
|
|
10027
|
+
fileInput.onchange = (e) => {
|
|
10028
|
+
const file = e.target.files?.[0];
|
|
10029
|
+
if (!file || !file.type.match(/^image\/(jpeg|png|gif|webp)$/))
|
|
10030
|
+
return;
|
|
10031
|
+
if (file.size > 5 * 1024 * 1024) {
|
|
10032
|
+
alert("Image must be under 5MB");
|
|
10033
|
+
return;
|
|
10034
|
+
}
|
|
10035
|
+
const reader = new FileReader();
|
|
10036
|
+
reader.onload = () => {
|
|
10037
|
+
const base64 = reader.result;
|
|
10038
|
+
if (base64)
|
|
10039
|
+
formStore.getState().updateField(selectedField.id, { imageUrl: base64, defaultValue: base64 });
|
|
10040
|
+
};
|
|
10041
|
+
reader.readAsDataURL(file);
|
|
10042
|
+
e.target.value = "";
|
|
10043
|
+
};
|
|
10044
|
+
btnRow.appendChild(fileInput);
|
|
10045
|
+
btnRow.appendChild(createElement("button", {
|
|
10046
|
+
type: "button",
|
|
10047
|
+
className: "px-3 py-2 text-sm border border-gray-300 dark:border-gray-700 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800",
|
|
10048
|
+
text: imageUrl ? "Replace" : "Upload",
|
|
10049
|
+
onclick: () => fileInput.click()
|
|
10050
|
+
}));
|
|
10051
|
+
btnRow.appendChild(createElement("button", {
|
|
10052
|
+
type: "button",
|
|
10053
|
+
className: "px-3 py-2 text-sm border border-red-200 text-red-600 rounded-md hover:bg-red-50 dark:hover:bg-red-900/20",
|
|
10054
|
+
text: "Remove",
|
|
10055
|
+
disabled: !imageUrl,
|
|
10056
|
+
onclick: () => formStore.getState().updateField(selectedField.id, { imageUrl: void 0, defaultValue: void 0 })
|
|
10057
|
+
}));
|
|
10058
|
+
imageGroup.appendChild(btnRow);
|
|
10059
|
+
body.appendChild(imageGroup);
|
|
10060
|
+
}
|
|
9289
10061
|
const layoutGroup = createElement("div", { className: "layout-span-group" });
|
|
9290
10062
|
const layoutLabelRow = createElement("div", { className: "flex items-center justify-between mb-2" });
|
|
9291
10063
|
layoutLabelRow.appendChild(createElement("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", text: "Grid Span" }));
|
|
@@ -9330,8 +10102,14 @@ var FormBuilder = class {
|
|
|
9330
10102
|
body.appendChild(layoutGroup);
|
|
9331
10103
|
body.appendChild(this.createCheckboxField(
|
|
9332
10104
|
"Required",
|
|
9333
|
-
!!selectedField.required,
|
|
9334
|
-
(checked) =>
|
|
10105
|
+
!!selectedField.required || !!selectedField.validations?.required,
|
|
10106
|
+
(checked) => {
|
|
10107
|
+
const currentValidations = selectedField.validations || {};
|
|
10108
|
+
formStore.getState().updateField(selectedField.id, {
|
|
10109
|
+
required: checked,
|
|
10110
|
+
validations: { ...currentValidations, required: checked }
|
|
10111
|
+
});
|
|
10112
|
+
},
|
|
9335
10113
|
`required-${selectedField.id}`
|
|
9336
10114
|
));
|
|
9337
10115
|
body.appendChild(this.createCheckboxField(
|
|
@@ -9701,7 +10479,9 @@ var FormBuilder = class {
|
|
|
9701
10479
|
`custom-options-${selectedField.id}`
|
|
9702
10480
|
));
|
|
9703
10481
|
}
|
|
9704
|
-
const
|
|
10482
|
+
const isStaticSelect = selectedField.type === "select" && (selectedField.optionSource === "STATIC" || !selectedField.optionSource);
|
|
10483
|
+
const hasOptions = selectedField.options && selectedField.options.length > 0;
|
|
10484
|
+
const shouldShowOptions = selectedField.type === "select" ? isStaticSelect && (!!selectedField.customOptionsEnabled || !!hasOptions) : true;
|
|
9705
10485
|
if (shouldShowOptions) {
|
|
9706
10486
|
const options = selectedField.options || [];
|
|
9707
10487
|
const fieldId2 = selectedField.id;
|
|
@@ -9743,12 +10523,16 @@ var FormBuilder = class {
|
|
|
9743
10523
|
}
|
|
9744
10524
|
});
|
|
9745
10525
|
const deleteBtn = createElement("button", {
|
|
10526
|
+
type: "button",
|
|
9746
10527
|
className: "p-1.5 text-red-600 hover:bg-red-50 rounded transition-colors",
|
|
9747
10528
|
title: "Delete option",
|
|
9748
|
-
onclick: () => {
|
|
10529
|
+
onclick: (e) => {
|
|
10530
|
+
e.preventDefault();
|
|
10531
|
+
e.stopPropagation();
|
|
9749
10532
|
const currentOptions = getCurrentOptions();
|
|
9750
|
-
const newOptions = currentOptions.filter((
|
|
10533
|
+
const newOptions = currentOptions.filter((o) => o.value !== opt.value);
|
|
9751
10534
|
formStore.getState().updateField(fieldId2, { options: newOptions });
|
|
10535
|
+
this.render();
|
|
9752
10536
|
}
|
|
9753
10537
|
}, [getIcon("Trash2", 14)]);
|
|
9754
10538
|
optionRow.appendChild(labelInput);
|
|
@@ -9772,40 +10556,67 @@ var FormBuilder = class {
|
|
|
9772
10556
|
body.appendChild(addOptionBtn);
|
|
9773
10557
|
}
|
|
9774
10558
|
}
|
|
9775
|
-
const
|
|
9776
|
-
const
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
10559
|
+
const validationsObj = selectedField.validations || (() => {
|
|
10560
|
+
const v = selectedField.validation;
|
|
10561
|
+
if (!v)
|
|
10562
|
+
return {};
|
|
10563
|
+
if (Array.isArray(v)) {
|
|
10564
|
+
const obj = {};
|
|
10565
|
+
v.forEach((rule) => {
|
|
10566
|
+
if (rule.type === "required")
|
|
10567
|
+
obj.required = true;
|
|
10568
|
+
else if (rule.type === "pattern" && rule.regex) {
|
|
10569
|
+
obj.pattern = rule.regex;
|
|
10570
|
+
if (rule.message)
|
|
10571
|
+
obj.customErrorMessages = { ...obj.customErrorMessages, pattern: rule.message };
|
|
10572
|
+
} else if (rule.type === "minLength" && typeof rule.value === "number")
|
|
10573
|
+
obj.minLength = rule.value;
|
|
10574
|
+
else if (rule.type === "maxLength" && typeof rule.value === "number")
|
|
10575
|
+
obj.maxLength = rule.value;
|
|
10576
|
+
else if (rule.type === "min" && typeof rule.value === "number")
|
|
10577
|
+
obj.min = rule.value;
|
|
10578
|
+
else if (rule.type === "max" && typeof rule.value === "number")
|
|
10579
|
+
obj.max = rule.value;
|
|
10580
|
+
else if (rule.type === "minSelected" && typeof rule.value === "number")
|
|
10581
|
+
obj.minSelected = rule.value;
|
|
10582
|
+
else if (rule.type === "maxSelected" && typeof rule.value === "number")
|
|
10583
|
+
obj.maxSelected = rule.value;
|
|
10584
|
+
else if (rule.type === "minDate" && typeof rule.value === "string")
|
|
10585
|
+
obj.minDate = rule.value;
|
|
10586
|
+
else if (rule.type === "maxDate" && typeof rule.value === "string")
|
|
10587
|
+
obj.maxDate = rule.value;
|
|
10588
|
+
});
|
|
10589
|
+
return obj;
|
|
10590
|
+
}
|
|
10591
|
+
const o = v;
|
|
10592
|
+
return {
|
|
10593
|
+
required: o.required,
|
|
10594
|
+
pattern: o.regex,
|
|
10595
|
+
minLength: o.minLength,
|
|
10596
|
+
maxLength: o.maxLength,
|
|
10597
|
+
min: o.min,
|
|
10598
|
+
max: o.max,
|
|
10599
|
+
minSelected: o.minSelected,
|
|
10600
|
+
maxSelected: o.maxSelected,
|
|
10601
|
+
minDate: o.minDate,
|
|
10602
|
+
maxDate: o.maxDate,
|
|
10603
|
+
customErrorMessages: o.regexMessage ? { pattern: o.regexMessage } : void 0
|
|
10604
|
+
};
|
|
10605
|
+
})();
|
|
10606
|
+
const updateValidations = (updates) => {
|
|
10607
|
+
const currentField = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
10608
|
+
const currentValidations = currentField?.validations ? { ...currentField.validations } : { ...validationsObj };
|
|
10609
|
+
const newValidations = { ...currentValidations, ...updates };
|
|
10610
|
+
Object.keys(newValidations).forEach((key) => {
|
|
10611
|
+
const v = newValidations[key];
|
|
10612
|
+
if (v === void 0 || v === null) {
|
|
10613
|
+
delete newValidations[key];
|
|
9803
10614
|
}
|
|
9804
10615
|
});
|
|
9805
|
-
formStore.getState().updateField(selectedField.id, {
|
|
10616
|
+
formStore.getState().updateField(selectedField.id, { validations: newValidations });
|
|
9806
10617
|
};
|
|
9807
|
-
const
|
|
9808
|
-
const value =
|
|
10618
|
+
const getValidationsValue = (key) => {
|
|
10619
|
+
const value = validationsObj[key];
|
|
9809
10620
|
if (value === void 0 || value === null)
|
|
9810
10621
|
return "";
|
|
9811
10622
|
if (typeof value === "number")
|
|
@@ -9815,17 +10626,139 @@ var FormBuilder = class {
|
|
|
9815
10626
|
return String(value);
|
|
9816
10627
|
};
|
|
9817
10628
|
const validationElements = [];
|
|
9818
|
-
if (
|
|
10629
|
+
if (selectedField.type === "number") {
|
|
10630
|
+
const numValidationTypeGroup = createElement("div", { className: "mb-3" });
|
|
10631
|
+
numValidationTypeGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Validation Type" }));
|
|
10632
|
+
const numValidationTypeSelect = createElement("select", {
|
|
10633
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10634
|
+
onchange: (e) => {
|
|
10635
|
+
const presetId = e.target.value;
|
|
10636
|
+
if (presetId) {
|
|
10637
|
+
const preset = VALIDATION_TYPE_PRESETS[presetId];
|
|
10638
|
+
if (preset && presetId === "amount") {
|
|
10639
|
+
updateValidations({ ...preset, validationType: presetId });
|
|
10640
|
+
}
|
|
10641
|
+
} else {
|
|
10642
|
+
updateValidations({ validationType: "custom" });
|
|
10643
|
+
}
|
|
10644
|
+
}
|
|
10645
|
+
});
|
|
10646
|
+
[
|
|
10647
|
+
{ value: "", text: "Custom" },
|
|
10648
|
+
{ value: "amount", text: "Amount (min 0, 2 decimals)" }
|
|
10649
|
+
].forEach((opt) => {
|
|
10650
|
+
numValidationTypeSelect.appendChild(createElement("option", {
|
|
10651
|
+
value: opt.value,
|
|
10652
|
+
text: opt.text,
|
|
10653
|
+
selected: validationsObj.validationType === opt.value || !validationsObj.validationType && opt.value === ""
|
|
10654
|
+
}));
|
|
10655
|
+
});
|
|
10656
|
+
numValidationTypeGroup.appendChild(numValidationTypeSelect);
|
|
10657
|
+
validationElements.push(numValidationTypeGroup);
|
|
10658
|
+
const minValGroup = createElement("div", { className: "mb-3" });
|
|
10659
|
+
minValGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Min Value" }));
|
|
10660
|
+
minValGroup.appendChild(createElement("input", {
|
|
10661
|
+
type: "number",
|
|
10662
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10663
|
+
value: getValidationsValue("min") || "",
|
|
10664
|
+
placeholder: "e.g. 0",
|
|
10665
|
+
oninput: (e) => {
|
|
10666
|
+
const val = e.target.value;
|
|
10667
|
+
updateValidations({ min: val !== "" ? parseFloat(val) : void 0 });
|
|
10668
|
+
}
|
|
10669
|
+
}));
|
|
10670
|
+
validationElements.push(minValGroup);
|
|
10671
|
+
const maxValGroup = createElement("div", { className: "mb-3" });
|
|
10672
|
+
maxValGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Max Value" }));
|
|
10673
|
+
maxValGroup.appendChild(createElement("input", {
|
|
10674
|
+
type: "number",
|
|
10675
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10676
|
+
value: getValidationsValue("max") || "",
|
|
10677
|
+
placeholder: "e.g. 100",
|
|
10678
|
+
oninput: (e) => {
|
|
10679
|
+
const val = e.target.value;
|
|
10680
|
+
updateValidations({ max: val !== "" ? parseFloat(val) : void 0 });
|
|
10681
|
+
}
|
|
10682
|
+
}));
|
|
10683
|
+
validationElements.push(maxValGroup);
|
|
10684
|
+
validationElements.push(this.createCheckboxField(
|
|
10685
|
+
"Allow Decimal",
|
|
10686
|
+
validationsObj.allowDecimal === true,
|
|
10687
|
+
(checked) => updateValidations({
|
|
10688
|
+
allowDecimal: checked,
|
|
10689
|
+
// When unchecked, remove decimalPlaces so it's not in the payload
|
|
10690
|
+
decimalPlaces: checked ? validationsObj.decimalPlaces ?? 2 : void 0
|
|
10691
|
+
}),
|
|
10692
|
+
`allow-decimal-${selectedField.id}`
|
|
10693
|
+
));
|
|
10694
|
+
const decimalPlacesGroup = createElement("div", { className: "mb-3" });
|
|
10695
|
+
decimalPlacesGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Decimal Places" }));
|
|
10696
|
+
const decimalPlacesInput = createElement("input", {
|
|
10697
|
+
type: "number",
|
|
10698
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent disabled:opacity-50 disabled:cursor-not-allowed",
|
|
10699
|
+
value: validationsObj.allowDecimal === true ? String(validationsObj.decimalPlaces ?? 2) : "",
|
|
10700
|
+
placeholder: "e.g. 2",
|
|
10701
|
+
min: "0",
|
|
10702
|
+
max: "10",
|
|
10703
|
+
disabled: validationsObj.allowDecimal !== true,
|
|
10704
|
+
oninput: (e) => {
|
|
10705
|
+
const val = e.target.value;
|
|
10706
|
+
updateValidations({ decimalPlaces: val !== "" ? parseInt(val) : void 0 });
|
|
10707
|
+
}
|
|
10708
|
+
});
|
|
10709
|
+
decimalPlacesGroup.appendChild(decimalPlacesInput);
|
|
10710
|
+
validationElements.push(decimalPlacesGroup);
|
|
10711
|
+
validationElements.push(this.createCheckboxField(
|
|
10712
|
+
"Allow Negative",
|
|
10713
|
+
validationsObj.allowNegative === true,
|
|
10714
|
+
(checked) => updateValidations({ allowNegative: checked }),
|
|
10715
|
+
`allow-negative-${selectedField.id}`
|
|
10716
|
+
));
|
|
10717
|
+
}
|
|
10718
|
+
if (["text", "textarea", "email", "phone"].includes(selectedField.type)) {
|
|
10719
|
+
if (selectedField.type === "text" || selectedField.type === "phone") {
|
|
10720
|
+
const validationTypeGroup = createElement("div", { className: "mb-3" });
|
|
10721
|
+
validationTypeGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Validation Type" }));
|
|
10722
|
+
const validationTypeSelect = createElement("select", {
|
|
10723
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10724
|
+
onchange: (e) => {
|
|
10725
|
+
const presetId = e.target.value;
|
|
10726
|
+
if (presetId) {
|
|
10727
|
+
const preset = VALIDATION_TYPE_PRESETS[presetId];
|
|
10728
|
+
if (preset) {
|
|
10729
|
+
updateValidations({ ...preset, validationType: presetId });
|
|
10730
|
+
}
|
|
10731
|
+
} else {
|
|
10732
|
+
updateValidations({ validationType: "custom" });
|
|
10733
|
+
}
|
|
10734
|
+
}
|
|
10735
|
+
});
|
|
10736
|
+
const options = [
|
|
10737
|
+
{ value: "", text: "Custom" },
|
|
10738
|
+
{ value: "postalCode", text: "Postal Code (6 digit)" },
|
|
10739
|
+
{ value: "phoneNumber", text: "Phone Number (10 digit)" },
|
|
10740
|
+
{ value: "otp", text: "OTP (4/6 digit)" }
|
|
10741
|
+
];
|
|
10742
|
+
options.forEach((opt) => {
|
|
10743
|
+
validationTypeSelect.appendChild(createElement("option", {
|
|
10744
|
+
value: opt.value,
|
|
10745
|
+
text: opt.text,
|
|
10746
|
+
selected: validationsObj.validationType === opt.value || !validationsObj.validationType && opt.value === ""
|
|
10747
|
+
}));
|
|
10748
|
+
});
|
|
10749
|
+
validationTypeGroup.appendChild(validationTypeSelect);
|
|
10750
|
+
validationElements.push(validationTypeGroup);
|
|
10751
|
+
}
|
|
9819
10752
|
const minLenGroup = createElement("div", { className: "mb-3" });
|
|
9820
10753
|
minLenGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Min Length" }));
|
|
9821
10754
|
minLenGroup.appendChild(createElement("input", {
|
|
9822
10755
|
type: "number",
|
|
9823
10756
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
9824
|
-
value:
|
|
10757
|
+
value: getValidationsValue("minLength") || "",
|
|
9825
10758
|
placeholder: "e.g. 3",
|
|
9826
|
-
|
|
10759
|
+
oninput: (e) => {
|
|
9827
10760
|
const value = e.target.value;
|
|
9828
|
-
|
|
10761
|
+
updateValidations({ minLength: value !== "" ? parseInt(value) : void 0 });
|
|
9829
10762
|
}
|
|
9830
10763
|
}));
|
|
9831
10764
|
validationElements.push(minLenGroup);
|
|
@@ -9834,11 +10767,11 @@ var FormBuilder = class {
|
|
|
9834
10767
|
maxLenGroup.appendChild(createElement("input", {
|
|
9835
10768
|
type: "number",
|
|
9836
10769
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
9837
|
-
value:
|
|
10770
|
+
value: getValidationsValue("maxLength") || "",
|
|
9838
10771
|
placeholder: "e.g. 100",
|
|
9839
|
-
|
|
10772
|
+
oninput: (e) => {
|
|
9840
10773
|
const value = e.target.value;
|
|
9841
|
-
|
|
10774
|
+
updateValidations({ maxLength: value !== "" ? parseInt(value) : void 0 });
|
|
9842
10775
|
}
|
|
9843
10776
|
}));
|
|
9844
10777
|
validationElements.push(maxLenGroup);
|
|
@@ -9898,10 +10831,11 @@ var FormBuilder = class {
|
|
|
9898
10831
|
examplesContainer.appendChild(examplesList);
|
|
9899
10832
|
regexGroup.appendChild(examplesContainer);
|
|
9900
10833
|
}
|
|
9901
|
-
const currentRegex =
|
|
10834
|
+
const currentRegex = validationsObj.pattern || selectedField.validation?.regex || "";
|
|
9902
10835
|
const findPresetByRegex = (regex) => {
|
|
9903
10836
|
return REGEX_PRESETS.find((preset) => preset.pattern === regex);
|
|
9904
10837
|
};
|
|
10838
|
+
const regexMessage = validationsObj.customErrorMessages?.pattern || selectedField.validation?.regexMessage || "Invalid format";
|
|
9905
10839
|
let currentPreset = currentRegex ? findPresetByRegex(currentRegex) : void 0;
|
|
9906
10840
|
let selectedPresetId = currentPreset?.id || "";
|
|
9907
10841
|
let regexInput;
|
|
@@ -9916,9 +10850,9 @@ var FormBuilder = class {
|
|
|
9916
10850
|
selectedPresetId = presetId;
|
|
9917
10851
|
const preset = REGEX_PRESETS.find((p) => p.id === presetId);
|
|
9918
10852
|
if (preset) {
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
10853
|
+
updateValidations({
|
|
10854
|
+
pattern: preset.pattern,
|
|
10855
|
+
customErrorMessages: { ...validationsObj.customErrorMessages, pattern: preset.errorMessage }
|
|
9922
10856
|
});
|
|
9923
10857
|
regexInput.value = preset.pattern;
|
|
9924
10858
|
currentPreset = preset;
|
|
@@ -9973,9 +10907,9 @@ var FormBuilder = class {
|
|
|
9973
10907
|
}
|
|
9974
10908
|
}
|
|
9975
10909
|
}
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
10910
|
+
updateValidations({
|
|
10911
|
+
pattern: val || void 0,
|
|
10912
|
+
customErrorMessages: { ...validationsObj.customErrorMessages, pattern: currentPreset?.errorMessage || regexMessage || "Invalid format" }
|
|
9979
10913
|
});
|
|
9980
10914
|
if (examplesList) {
|
|
9981
10915
|
if (currentPreset) {
|
|
@@ -9998,6 +10932,21 @@ var FormBuilder = class {
|
|
|
9998
10932
|
updateExamples(examplesList, currentRegex);
|
|
9999
10933
|
}
|
|
10000
10934
|
}
|
|
10935
|
+
const patternMsgGroup = createElement("div", { className: "mb-3 mt-2" });
|
|
10936
|
+
patternMsgGroup.appendChild(createElement("label", { className: "block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1", text: "Pattern Error Message" }));
|
|
10937
|
+
patternMsgGroup.appendChild(createElement("input", {
|
|
10938
|
+
type: "text",
|
|
10939
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent text-sm",
|
|
10940
|
+
value: validationsObj.customErrorMessages?.pattern || "",
|
|
10941
|
+
placeholder: "e.g. Invalid format",
|
|
10942
|
+
oninput: (e) => {
|
|
10943
|
+
const val = e.target.value;
|
|
10944
|
+
updateValidations({
|
|
10945
|
+
customErrorMessages: { ...validationsObj.customErrorMessages, pattern: val || void 0 }
|
|
10946
|
+
});
|
|
10947
|
+
}
|
|
10948
|
+
}));
|
|
10949
|
+
regexGroup.appendChild(patternMsgGroup);
|
|
10001
10950
|
validationElements.push(regexGroup);
|
|
10002
10951
|
}
|
|
10003
10952
|
if (selectedField.type === "checkbox") {
|
|
@@ -10006,12 +10955,12 @@ var FormBuilder = class {
|
|
|
10006
10955
|
minSelectedGroup.appendChild(createElement("input", {
|
|
10007
10956
|
type: "number",
|
|
10008
10957
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10009
|
-
value:
|
|
10958
|
+
value: getValidationsValue("minSelected"),
|
|
10010
10959
|
placeholder: "e.g. 1",
|
|
10011
10960
|
min: "0",
|
|
10012
10961
|
onchange: (e) => {
|
|
10013
10962
|
const val = e.target.value;
|
|
10014
|
-
|
|
10963
|
+
updateValidations({ minSelected: val ? parseInt(val) : void 0 });
|
|
10015
10964
|
}
|
|
10016
10965
|
}));
|
|
10017
10966
|
validationElements.push(minSelectedGroup);
|
|
@@ -10020,12 +10969,12 @@ var FormBuilder = class {
|
|
|
10020
10969
|
maxSelectedGroup.appendChild(createElement("input", {
|
|
10021
10970
|
type: "number",
|
|
10022
10971
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10023
|
-
value:
|
|
10972
|
+
value: getValidationsValue("maxSelected"),
|
|
10024
10973
|
placeholder: "e.g. 2",
|
|
10025
10974
|
min: "1",
|
|
10026
10975
|
onchange: (e) => {
|
|
10027
10976
|
const val = e.target.value;
|
|
10028
|
-
|
|
10977
|
+
updateValidations({ maxSelected: val ? parseInt(val) : void 0 });
|
|
10029
10978
|
}
|
|
10030
10979
|
}));
|
|
10031
10980
|
validationElements.push(maxSelectedGroup);
|
|
@@ -10036,10 +10985,10 @@ var FormBuilder = class {
|
|
|
10036
10985
|
minDateGroup.appendChild(createElement("input", {
|
|
10037
10986
|
type: "date",
|
|
10038
10987
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10039
|
-
value:
|
|
10988
|
+
value: validationsObj.minDate || "",
|
|
10040
10989
|
onchange: (e) => {
|
|
10041
10990
|
const val = e.target.value;
|
|
10042
|
-
|
|
10991
|
+
updateValidations({ minDate: val || void 0 });
|
|
10043
10992
|
}
|
|
10044
10993
|
}));
|
|
10045
10994
|
validationElements.push(minDateGroup);
|
|
@@ -10048,10 +10997,10 @@ var FormBuilder = class {
|
|
|
10048
10997
|
maxDateGroup.appendChild(createElement("input", {
|
|
10049
10998
|
type: "date",
|
|
10050
10999
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10051
|
-
value:
|
|
11000
|
+
value: validationsObj.maxDate || "",
|
|
10052
11001
|
onchange: (e) => {
|
|
10053
11002
|
const val = e.target.value;
|
|
10054
|
-
|
|
11003
|
+
updateValidations({ maxDate: val || void 0 });
|
|
10055
11004
|
}
|
|
10056
11005
|
}));
|
|
10057
11006
|
validationElements.push(maxDateGroup);
|
|
@@ -10342,6 +11291,6 @@ sortablejs/modular/sortable.esm.js:
|
|
|
10342
11291
|
*)
|
|
10343
11292
|
*/
|
|
10344
11293
|
|
|
10345
|
-
export { FormBuilder, FormRenderer, FormSchemaValidation, builderToPlatform, cleanFormSchema, convertValidationObjectToArray, formStore, getColSpanFromWidth, initFormBuilder, parseWidth, platformToBuilder };
|
|
11294
|
+
export { FormBuilder, FormRenderer, FormSchemaValidation, builderToPlatform, cleanFormSchema, convertValidationObjectToArray, detectCircularDependency, evaluateFormula, formStore, getColSpanFromWidth, getNumericFieldsForFormula, getValidationConfigForAngular, initFormBuilder, parseFormulaDependencies, parseWidth, platformToBuilder, validateFormula };
|
|
10346
11295
|
//# sourceMappingURL=out.js.map
|
|
10347
11296
|
//# sourceMappingURL=index.mjs.map
|