form-builder-pro 1.2.7 → 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 +1260 -301
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1255 -302
- 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
|
}
|
|
@@ -4319,10 +4419,12 @@ function convertSpanToWidth(span, totalColumns = 12) {
|
|
|
4319
4419
|
function normalizeFieldType(type) {
|
|
4320
4420
|
if (!type)
|
|
4321
4421
|
return "text";
|
|
4322
|
-
const normalized = String(type).toLowerCase();
|
|
4422
|
+
const normalized = String(type).toLowerCase().replace(/_/g, "");
|
|
4323
4423
|
if (normalized === "decimal")
|
|
4324
4424
|
return "number";
|
|
4325
|
-
|
|
4425
|
+
if (["phonenumber", "telephone", "mobile"].includes(normalized))
|
|
4426
|
+
return "phone";
|
|
4427
|
+
return String(type).toLowerCase();
|
|
4326
4428
|
}
|
|
4327
4429
|
function transformField(field) {
|
|
4328
4430
|
const fieldId = field.id || field.fieldId;
|
|
@@ -4373,7 +4475,15 @@ function transformField(field) {
|
|
|
4373
4475
|
if (transformed.order === void 0) {
|
|
4374
4476
|
transformed.order = field.order !== void 0 ? field.order : 0;
|
|
4375
4477
|
}
|
|
4376
|
-
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) {
|
|
4377
4487
|
if (Array.isArray(field.validation)) {
|
|
4378
4488
|
const validationObj = {};
|
|
4379
4489
|
field.validation.forEach((rule) => {
|
|
@@ -4386,6 +4496,10 @@ function transformField(field) {
|
|
|
4386
4496
|
validationObj.minLength = rule.value;
|
|
4387
4497
|
} else if (rule.type === "maxLength" && typeof rule.value === "number") {
|
|
4388
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;
|
|
4389
4503
|
} else if (rule.type === "minSelected" && typeof rule.value === "number") {
|
|
4390
4504
|
validationObj.minSelected = rule.value;
|
|
4391
4505
|
} else if (rule.type === "maxSelected" && typeof rule.value === "number") {
|
|
@@ -4398,13 +4512,20 @@ function transformField(field) {
|
|
|
4398
4512
|
});
|
|
4399
4513
|
transformed.validation = validationObj;
|
|
4400
4514
|
transformed.required = validationObj.required || false;
|
|
4515
|
+
transformed.validations = validationObjectToValidations(validationObj);
|
|
4401
4516
|
} else {
|
|
4402
4517
|
transformed.validation = field.validation;
|
|
4403
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;
|
|
4404
4524
|
}
|
|
4405
4525
|
} else if (field.required !== void 0) {
|
|
4406
4526
|
transformed.required = field.required;
|
|
4407
4527
|
transformed.validation = { required: field.required };
|
|
4528
|
+
transformed.validations = { required: field.required };
|
|
4408
4529
|
}
|
|
4409
4530
|
if (normalizedType === "select") {
|
|
4410
4531
|
if (field.multiSelect !== void 0) {
|
|
@@ -4453,6 +4574,10 @@ function transformField(field) {
|
|
|
4453
4574
|
transformed.lookupValueField = lookupValueField;
|
|
4454
4575
|
if (lookupLabelField !== void 0)
|
|
4455
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;
|
|
4456
4581
|
if (field.placeholder !== void 0)
|
|
4457
4582
|
transformed.placeholder = field.placeholder;
|
|
4458
4583
|
if (field.description !== void 0)
|
|
@@ -4467,6 +4592,16 @@ function transformField(field) {
|
|
|
4467
4592
|
transformed.enabled = field.enabled;
|
|
4468
4593
|
if (field.visible !== void 0)
|
|
4469
4594
|
transformed.visible = field.visible;
|
|
4595
|
+
if (field.isd !== void 0)
|
|
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;
|
|
4470
4605
|
if (field.css !== void 0)
|
|
4471
4606
|
transformed.css = field.css;
|
|
4472
4607
|
if (field.optionsSource !== void 0)
|
|
@@ -4477,8 +4612,18 @@ function transformField(field) {
|
|
|
4477
4612
|
transformed.groupName = field.groupName;
|
|
4478
4613
|
if (field.masterTypeName !== void 0)
|
|
4479
4614
|
transformed.masterTypeName = field.masterTypeName;
|
|
4480
|
-
if ((normalizedType === "select" || normalizedType === "radio" || normalizedType === "checkbox") && field.options) {
|
|
4481
|
-
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
|
+
}
|
|
4482
4627
|
}
|
|
4483
4628
|
return transformed;
|
|
4484
4629
|
}
|
|
@@ -4548,6 +4693,10 @@ function convertValidationArrayToObject(validation) {
|
|
|
4548
4693
|
obj.minLength = rule.value;
|
|
4549
4694
|
} else if (rule.type === "maxLength" && typeof rule.value === "number") {
|
|
4550
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;
|
|
4551
4700
|
} else if (rule.type === "minSelected" && typeof rule.value === "number") {
|
|
4552
4701
|
obj.minSelected = rule.value;
|
|
4553
4702
|
} else if (rule.type === "maxSelected" && typeof rule.value === "number") {
|
|
@@ -4571,6 +4720,8 @@ function fieldToPayload(field) {
|
|
|
4571
4720
|
id: field.id,
|
|
4572
4721
|
type: field.type,
|
|
4573
4722
|
label: field.label,
|
|
4723
|
+
name: field.fieldName || field.id,
|
|
4724
|
+
// Model key for binding (API / host app)
|
|
4574
4725
|
order: field.order !== void 0 ? field.order : 0
|
|
4575
4726
|
};
|
|
4576
4727
|
if (field.layout?.span !== void 0) {
|
|
@@ -4592,8 +4743,18 @@ function fieldToPayload(field) {
|
|
|
4592
4743
|
span: 12
|
|
4593
4744
|
};
|
|
4594
4745
|
}
|
|
4595
|
-
|
|
4596
|
-
|
|
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) {
|
|
4597
4758
|
payload.required = true;
|
|
4598
4759
|
}
|
|
4599
4760
|
if (field.type === "select") {
|
|
@@ -4629,6 +4790,14 @@ function fieldToPayload(field) {
|
|
|
4629
4790
|
payload.lookupLabelField = field.lookupLabelField;
|
|
4630
4791
|
if (field.isd !== void 0)
|
|
4631
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;
|
|
4632
4801
|
if ((field.type === "select" || field.type === "radio" || field.type === "checkbox") && field.options && Array.isArray(field.options)) {
|
|
4633
4802
|
payload.options = field.options.map((opt) => ({ label: opt.label, value: opt.value }));
|
|
4634
4803
|
}
|
|
@@ -4653,6 +4822,20 @@ var builderToPlatform = (builderSchema) => {
|
|
|
4653
4822
|
var platformToBuilder = (platformSchema) => {
|
|
4654
4823
|
return cleanFormSchema(platformSchema);
|
|
4655
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
|
+
}
|
|
4656
4839
|
|
|
4657
4840
|
// src/core/useFormStore.ts
|
|
4658
4841
|
var INITIAL_SCHEMA = {
|
|
@@ -4957,9 +5140,10 @@ var formStore = createStore((set, get) => ({
|
|
|
4957
5140
|
const { existingForms, history, historyIndex } = get();
|
|
4958
5141
|
const found = existingForms.find((f) => f.id === formId);
|
|
4959
5142
|
if (found) {
|
|
5143
|
+
const cleanedSchema = cleanFormSchema(found);
|
|
4960
5144
|
set({
|
|
4961
|
-
schema:
|
|
4962
|
-
history: [...history.slice(0, historyIndex + 1),
|
|
5145
|
+
schema: cleanedSchema,
|
|
5146
|
+
history: [...history.slice(0, historyIndex + 1), cleanedSchema],
|
|
4963
5147
|
historyIndex: historyIndex + 1
|
|
4964
5148
|
});
|
|
4965
5149
|
}
|
|
@@ -4969,9 +5153,10 @@ var formStore = createStore((set, get) => ({
|
|
|
4969
5153
|
const found = existingForms.find((f) => f.id === formId);
|
|
4970
5154
|
if (found) {
|
|
4971
5155
|
const cloned = cloneForm(found);
|
|
5156
|
+
const cleanedSchema = cleanFormSchema(cloned);
|
|
4972
5157
|
set({
|
|
4973
|
-
schema:
|
|
4974
|
-
history: [...history.slice(0, historyIndex + 1),
|
|
5158
|
+
schema: cleanedSchema,
|
|
5159
|
+
history: [...history.slice(0, historyIndex + 1), cleanedSchema],
|
|
4975
5160
|
historyIndex: historyIndex + 1
|
|
4976
5161
|
});
|
|
4977
5162
|
}
|
|
@@ -5354,6 +5539,7 @@ var ICONS = {
|
|
|
5354
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" />',
|
|
5355
5540
|
// Custom SVG for toggle
|
|
5356
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" />',
|
|
5357
5543
|
// UI Icons
|
|
5358
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" />',
|
|
5359
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" />',
|
|
@@ -5386,6 +5572,224 @@ function getIcon(name, size = 20) {
|
|
|
5386
5572
|
return svg;
|
|
5387
5573
|
}
|
|
5388
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
|
+
|
|
5389
5793
|
// src/core/countryData.ts
|
|
5390
5794
|
var COUNTRY_DATA = [
|
|
5391
5795
|
{ code: "US", name: "United States", dialCode: "+1", flag: "\u{1F1FA}\u{1F1F8}" },
|
|
@@ -5441,6 +5845,64 @@ function getDefaultCountry() {
|
|
|
5441
5845
|
}
|
|
5442
5846
|
|
|
5443
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
|
+
}
|
|
5444
5906
|
var FieldRenderer = class {
|
|
5445
5907
|
static render(field, value, onChange, readOnly = false) {
|
|
5446
5908
|
const wrapper = createElement("div", { className: "w-full form-row" });
|
|
@@ -5475,89 +5937,71 @@ var FieldRenderer = class {
|
|
|
5475
5937
|
}
|
|
5476
5938
|
let input;
|
|
5477
5939
|
let validationMsg = null;
|
|
5478
|
-
const
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
rules.push({ type: "required", value: true });
|
|
5483
|
-
if (obj.regex)
|
|
5484
|
-
rules.push({ type: "pattern", regex: obj.regex, message: obj.regexMessage });
|
|
5485
|
-
if (obj.minLength !== void 0)
|
|
5486
|
-
rules.push({ type: "minLength", value: obj.minLength });
|
|
5487
|
-
if (obj.maxLength !== void 0)
|
|
5488
|
-
rules.push({ type: "maxLength", value: obj.maxLength });
|
|
5489
|
-
if (obj.minSelected !== void 0)
|
|
5490
|
-
rules.push({ type: "minSelected", value: obj.minSelected });
|
|
5491
|
-
if (obj.maxSelected !== void 0)
|
|
5492
|
-
rules.push({ type: "maxSelected", value: obj.maxSelected });
|
|
5493
|
-
if (obj.minDate)
|
|
5494
|
-
rules.push({ type: "minDate", value: obj.minDate });
|
|
5495
|
-
if (obj.maxDate)
|
|
5496
|
-
rules.push({ type: "maxDate", value: obj.maxDate });
|
|
5497
|
-
return rules;
|
|
5498
|
-
})() : [];
|
|
5499
|
-
const hasPatternValidation = validationArray.some((v) => v.type === "pattern");
|
|
5500
|
-
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)) {
|
|
5501
5944
|
validationMsg = createElement("div", { className: "text-xs text-red-600 dark:text-red-400 mt-1 hidden", id: `validation-${field.id}` });
|
|
5502
5945
|
}
|
|
5503
|
-
const validateField = (
|
|
5946
|
+
const validateField = (f, value2, inputElement, msgEl) => {
|
|
5947
|
+
const rules = getValidationRules(f);
|
|
5948
|
+
const custom2 = f.validations?.customErrorMessages;
|
|
5504
5949
|
let errorMessage = "";
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
rules.push({ type: "required", value: true });
|
|
5510
|
-
if (obj.regex)
|
|
5511
|
-
rules.push({ type: "pattern", regex: obj.regex, message: obj.regexMessage });
|
|
5512
|
-
if (obj.minLength !== void 0)
|
|
5513
|
-
rules.push({ type: "minLength", value: obj.minLength });
|
|
5514
|
-
if (obj.maxLength !== void 0)
|
|
5515
|
-
rules.push({ type: "maxLength", value: obj.maxLength });
|
|
5516
|
-
if (obj.minSelected !== void 0)
|
|
5517
|
-
rules.push({ type: "minSelected", value: obj.minSelected });
|
|
5518
|
-
if (obj.maxSelected !== void 0)
|
|
5519
|
-
rules.push({ type: "maxSelected", value: obj.maxSelected });
|
|
5520
|
-
if (obj.minDate)
|
|
5521
|
-
rules.push({ type: "minDate", value: obj.minDate });
|
|
5522
|
-
if (obj.maxDate)
|
|
5523
|
-
rules.push({ type: "maxDate", value: obj.maxDate });
|
|
5524
|
-
return rules;
|
|
5525
|
-
})() : [];
|
|
5526
|
-
if (field2.type === "date" && value2) {
|
|
5527
|
-
const minDateRule = validationArray2.find((v) => v.type === "minDate");
|
|
5528
|
-
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) {
|
|
5529
5954
|
const inputDate = new Date(value2);
|
|
5530
|
-
if (
|
|
5531
|
-
const minDate = new Date(
|
|
5955
|
+
if (rules.minDate) {
|
|
5956
|
+
const minDate = new Date(rules.minDate);
|
|
5532
5957
|
if (inputDate < minDate) {
|
|
5533
|
-
errorMessage =
|
|
5958
|
+
errorMessage = "Date must be after the minimum date";
|
|
5534
5959
|
}
|
|
5535
5960
|
}
|
|
5536
|
-
if (
|
|
5537
|
-
const maxDate = new Date(
|
|
5961
|
+
if (!errorMessage && rules.maxDate) {
|
|
5962
|
+
const maxDate = new Date(rules.maxDate);
|
|
5538
5963
|
if (inputDate > maxDate) {
|
|
5539
|
-
errorMessage =
|
|
5964
|
+
errorMessage = "Date must be before the maximum date";
|
|
5540
5965
|
}
|
|
5541
5966
|
}
|
|
5542
5967
|
}
|
|
5543
|
-
if ((
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
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}`;
|
|
5552
5996
|
}
|
|
5553
5997
|
}
|
|
5554
5998
|
}
|
|
5555
5999
|
if (errorMessage) {
|
|
5556
|
-
|
|
5557
|
-
|
|
6000
|
+
msgEl.textContent = errorMessage;
|
|
6001
|
+
msgEl.classList.remove("hidden");
|
|
5558
6002
|
inputElement.classList.add("border-red-500");
|
|
5559
6003
|
} else {
|
|
5560
|
-
|
|
6004
|
+
msgEl.classList.add("hidden");
|
|
5561
6005
|
inputElement.classList.remove("border-red-500");
|
|
5562
6006
|
}
|
|
5563
6007
|
};
|
|
@@ -5691,59 +6135,40 @@ var FieldRenderer = class {
|
|
|
5691
6135
|
case "phone":
|
|
5692
6136
|
input = this.renderPhoneField(field, value, onChange, isEnabled);
|
|
5693
6137
|
break;
|
|
6138
|
+
case "image":
|
|
6139
|
+
input = this.renderImageField(field, value ?? field.imageUrl ?? field.defaultValue, onChange, isEnabled);
|
|
6140
|
+
break;
|
|
5694
6141
|
default:
|
|
5695
|
-
const
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
rules.push({ type: "minLength", value: obj.minLength });
|
|
5704
|
-
if (obj.maxLength !== void 0)
|
|
5705
|
-
rules.push({ type: "maxLength", value: obj.maxLength });
|
|
5706
|
-
if (obj.minSelected !== void 0)
|
|
5707
|
-
rules.push({ type: "minSelected", value: obj.minSelected });
|
|
5708
|
-
if (obj.maxSelected !== void 0)
|
|
5709
|
-
rules.push({ type: "maxSelected", value: obj.maxSelected });
|
|
5710
|
-
if (obj.minDate)
|
|
5711
|
-
rules.push({ type: "minDate", value: obj.minDate });
|
|
5712
|
-
if (obj.maxDate)
|
|
5713
|
-
rules.push({ type: "maxDate", value: obj.maxDate });
|
|
5714
|
-
return rules;
|
|
5715
|
-
})() : [];
|
|
5716
|
-
const patternRule = fieldValidationArray.find((v) => v.type === "pattern");
|
|
5717
|
-
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
|
+
};
|
|
5718
6150
|
input = createElement("input", {
|
|
5719
|
-
type:
|
|
6151
|
+
type: inputType,
|
|
6152
|
+
...useNumericTextInput && { inputmode: "numeric" },
|
|
5720
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",
|
|
5721
|
-
// type: field.type === 'phone' ? 'tel' : field.type,
|
|
5722
|
-
// 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',
|
|
5723
6154
|
placeholder: field.placeholder,
|
|
5724
6155
|
value: value || "",
|
|
5725
6156
|
disabled: !isEnabled,
|
|
5726
|
-
min: field.type === "date" ?
|
|
5727
|
-
max: field.type === "date" ?
|
|
5728
|
-
pattern:
|
|
5729
|
-
// 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,
|
|
5730
6160
|
oninput: (e) => {
|
|
5731
|
-
const
|
|
5732
|
-
|
|
5733
|
-
if (
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
},
|
|
5737
|
-
onchange: (e) => {
|
|
5738
|
-
if (validationMsg && (field.type === "date" || field.type === "email" || field.type === "text" && hasPatternValidation)) {
|
|
5739
|
-
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;
|
|
5740
6166
|
}
|
|
6167
|
+
onChange?.(inputValue);
|
|
6168
|
+
runValidation();
|
|
5741
6169
|
},
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
validateField(field, e.target.value, input, validationMsg);
|
|
5745
|
-
}
|
|
5746
|
-
}
|
|
6170
|
+
onchange: () => runValidation(),
|
|
6171
|
+
onblur: () => runValidation()
|
|
5747
6172
|
});
|
|
5748
6173
|
}
|
|
5749
6174
|
wrapper.appendChild(input);
|
|
@@ -5839,6 +6264,93 @@ var FieldRenderer = class {
|
|
|
5839
6264
|
});
|
|
5840
6265
|
return container;
|
|
5841
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
|
+
}
|
|
5842
6354
|
};
|
|
5843
6355
|
|
|
5844
6356
|
// src/renderer/FormRenderer.ts
|
|
@@ -5857,6 +6369,10 @@ function convertValidationToArray(validation) {
|
|
|
5857
6369
|
rules.push({ type: "minLength", value: obj.minLength });
|
|
5858
6370
|
if (obj.maxLength !== void 0)
|
|
5859
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 });
|
|
5860
6376
|
if (obj.minSelected !== void 0)
|
|
5861
6377
|
rules.push({ type: "minSelected", value: obj.minSelected });
|
|
5862
6378
|
if (obj.maxSelected !== void 0)
|
|
@@ -5867,9 +6383,146 @@ function convertValidationToArray(validation) {
|
|
|
5867
6383
|
rules.push({ type: "maxDate", value: obj.maxDate });
|
|
5868
6384
|
return rules;
|
|
5869
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
|
+
}
|
|
5870
6473
|
function getModelKey(field) {
|
|
5871
6474
|
return field.fieldName ?? field.id;
|
|
5872
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
|
+
}
|
|
5873
6526
|
var FormRenderer = class {
|
|
5874
6527
|
constructor(container, schema, onSubmit, onDropdownValueChange, initialData) {
|
|
5875
6528
|
__publicField(this, "container");
|
|
@@ -5914,15 +6567,35 @@ var FormRenderer = class {
|
|
|
5914
6567
|
}
|
|
5915
6568
|
fieldWrapper.className = spanClass;
|
|
5916
6569
|
const modelKey = getModelKey(field);
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
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
|
+
);
|
|
5926
6599
|
fieldWrapper.appendChild(fieldEl);
|
|
5927
6600
|
grid.appendChild(fieldWrapper);
|
|
5928
6601
|
});
|
|
@@ -5945,107 +6618,19 @@ var FormRenderer = class {
|
|
|
5945
6618
|
const modelKey = getModelKey(field);
|
|
5946
6619
|
const fieldValue = this.data[modelKey];
|
|
5947
6620
|
const fieldElement = form.querySelector(`input[id*="${field.id}"], textarea[id*="${field.id}"], select[id*="${field.id}"]`);
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
isValid2 = false;
|
|
5956
|
-
altElement.setCustomValidity("This field is required");
|
|
5957
|
-
altElement.reportValidity();
|
|
5958
|
-
invalidFields.push(altElement);
|
|
5959
|
-
} else {
|
|
5960
|
-
altElement.setCustomValidity("");
|
|
5961
|
-
}
|
|
5962
|
-
if ((field.type === "text" || field.type === "email") && fieldValue) {
|
|
5963
|
-
const validationArray = convertValidationToArray(field.validation);
|
|
5964
|
-
const patternRule = validationArray.find((v) => v.type === "pattern");
|
|
5965
|
-
if (patternRule?.regex) {
|
|
5966
|
-
try {
|
|
5967
|
-
const regex = new RegExp(patternRule.regex);
|
|
5968
|
-
if (!regex.test(String(fieldValue))) {
|
|
5969
|
-
isValid2 = false;
|
|
5970
|
-
altElement.setCustomValidity(patternRule.message || "Invalid format");
|
|
5971
|
-
altElement.reportValidity();
|
|
5972
|
-
invalidFields.push(altElement);
|
|
5973
|
-
} else {
|
|
5974
|
-
altElement.setCustomValidity("");
|
|
5975
|
-
}
|
|
5976
|
-
} catch (e2) {
|
|
5977
|
-
}
|
|
5978
|
-
}
|
|
5979
|
-
}
|
|
5980
|
-
if (field.type === "checkbox" && Array.isArray(fieldValue)) {
|
|
5981
|
-
const validationArray = convertValidationToArray(field.validation);
|
|
5982
|
-
const minSelectedRule = validationArray.find((v) => v.type === "minSelected");
|
|
5983
|
-
const maxSelectedRule = validationArray.find((v) => v.type === "maxSelected");
|
|
5984
|
-
const selectedCount = fieldValue.length;
|
|
5985
|
-
const minSelected = typeof minSelectedRule?.value === "number" ? minSelectedRule.value : void 0;
|
|
5986
|
-
const maxSelected = typeof maxSelectedRule?.value === "number" ? maxSelectedRule.value : void 0;
|
|
5987
|
-
if (minSelected !== void 0 && selectedCount < minSelected) {
|
|
5988
|
-
isValid2 = false;
|
|
5989
|
-
altElement.setCustomValidity(`Please select at least ${minSelected} option(s)`);
|
|
5990
|
-
altElement.reportValidity();
|
|
5991
|
-
invalidFields.push(altElement);
|
|
5992
|
-
} else if (maxSelected !== void 0 && selectedCount > maxSelected) {
|
|
5993
|
-
isValid2 = false;
|
|
5994
|
-
altElement.setCustomValidity(`Please select at most ${maxSelected} option(s)`);
|
|
5995
|
-
altElement.reportValidity();
|
|
5996
|
-
invalidFields.push(altElement);
|
|
5997
|
-
} else {
|
|
5998
|
-
altElement.setCustomValidity("");
|
|
5999
|
-
}
|
|
6000
|
-
}
|
|
6001
|
-
}
|
|
6002
|
-
return;
|
|
6003
|
-
}
|
|
6004
|
-
if (field.required && (!fieldValue || fieldValue === "" || Array.isArray(fieldValue) && fieldValue.length === 0)) {
|
|
6005
|
-
isValid2 = false;
|
|
6006
|
-
fieldElement.setCustomValidity("This field is required");
|
|
6007
|
-
fieldElement.reportValidity();
|
|
6008
|
-
invalidFields.push(fieldElement);
|
|
6009
|
-
} else {
|
|
6010
|
-
fieldElement.setCustomValidity("");
|
|
6011
|
-
}
|
|
6012
|
-
if ((field.type === "text" || field.type === "email") && fieldValue) {
|
|
6013
|
-
const validationArray = convertValidationToArray(field.validation);
|
|
6014
|
-
const patternRule = validationArray.find((v) => v.type === "pattern");
|
|
6015
|
-
if (patternRule?.regex) {
|
|
6016
|
-
try {
|
|
6017
|
-
const regex = new RegExp(patternRule.regex);
|
|
6018
|
-
if (!regex.test(String(fieldValue))) {
|
|
6019
|
-
isValid2 = false;
|
|
6020
|
-
fieldElement.setCustomValidity(patternRule.message || "Invalid format");
|
|
6021
|
-
fieldElement.reportValidity();
|
|
6022
|
-
invalidFields.push(fieldElement);
|
|
6023
|
-
} else {
|
|
6024
|
-
fieldElement.setCustomValidity("");
|
|
6025
|
-
}
|
|
6026
|
-
} catch (e2) {
|
|
6027
|
-
}
|
|
6028
|
-
}
|
|
6029
|
-
}
|
|
6030
|
-
if (field.type === "checkbox" && Array.isArray(fieldValue)) {
|
|
6031
|
-
const validationArray = convertValidationToArray(field.validation);
|
|
6032
|
-
const minSelectedRule = validationArray.find((v) => v.type === "minSelected");
|
|
6033
|
-
const maxSelectedRule = validationArray.find((v) => v.type === "maxSelected");
|
|
6034
|
-
const selectedCount = fieldValue.length;
|
|
6035
|
-
const minSelected = typeof minSelectedRule?.value === "number" ? minSelectedRule.value : void 0;
|
|
6036
|
-
const maxSelected = typeof maxSelectedRule?.value === "number" ? maxSelectedRule.value : void 0;
|
|
6037
|
-
if (minSelected !== void 0 && selectedCount < minSelected) {
|
|
6038
|
-
isValid2 = false;
|
|
6039
|
-
fieldElement.setCustomValidity(`Please select at least ${minSelected} option(s)`);
|
|
6040
|
-
fieldElement.reportValidity();
|
|
6041
|
-
invalidFields.push(fieldElement);
|
|
6042
|
-
} 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) {
|
|
6043
6628
|
isValid2 = false;
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
invalidFields.push(
|
|
6629
|
+
element.setCustomValidity(fieldError);
|
|
6630
|
+
element.reportValidity();
|
|
6631
|
+
invalidFields.push(element);
|
|
6047
6632
|
} else {
|
|
6048
|
-
|
|
6633
|
+
element.setCustomValidity("");
|
|
6049
6634
|
}
|
|
6050
6635
|
}
|
|
6051
6636
|
});
|
|
@@ -9065,6 +9650,36 @@ var FormBuilder = class {
|
|
|
9065
9650
|
className: "flex items-center px-3 py-2 text-sm font-medium text-[#635bff] bg-[#e7e7ff] rounded-md shadow-sm transition-colors",
|
|
9066
9651
|
onclick: () => {
|
|
9067
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
|
+
});
|
|
9068
9683
|
console.log("[Form Builder] Schema being sent to app:", JSON.stringify(schema, null, 2));
|
|
9069
9684
|
if (this.options.onSave) {
|
|
9070
9685
|
this.options.onSave(schema);
|
|
@@ -9271,17 +9886,178 @@ var FormBuilder = class {
|
|
|
9271
9886
|
}
|
|
9272
9887
|
}));
|
|
9273
9888
|
body.appendChild(labelGroup);
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
"
|
|
9280
|
-
|
|
9281
|
-
|
|
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);
|
|
9282
9983
|
}
|
|
9283
|
-
}
|
|
9284
|
-
|
|
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
|
+
}
|
|
9285
10061
|
const layoutGroup = createElement("div", { className: "layout-span-group" });
|
|
9286
10062
|
const layoutLabelRow = createElement("div", { className: "flex items-center justify-between mb-2" });
|
|
9287
10063
|
layoutLabelRow.appendChild(createElement("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", text: "Grid Span" }));
|
|
@@ -9326,8 +10102,14 @@ var FormBuilder = class {
|
|
|
9326
10102
|
body.appendChild(layoutGroup);
|
|
9327
10103
|
body.appendChild(this.createCheckboxField(
|
|
9328
10104
|
"Required",
|
|
9329
|
-
!!selectedField.required,
|
|
9330
|
-
(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
|
+
},
|
|
9331
10113
|
`required-${selectedField.id}`
|
|
9332
10114
|
));
|
|
9333
10115
|
body.appendChild(this.createCheckboxField(
|
|
@@ -9697,7 +10479,9 @@ var FormBuilder = class {
|
|
|
9697
10479
|
`custom-options-${selectedField.id}`
|
|
9698
10480
|
));
|
|
9699
10481
|
}
|
|
9700
|
-
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;
|
|
9701
10485
|
if (shouldShowOptions) {
|
|
9702
10486
|
const options = selectedField.options || [];
|
|
9703
10487
|
const fieldId2 = selectedField.id;
|
|
@@ -9739,12 +10523,16 @@ var FormBuilder = class {
|
|
|
9739
10523
|
}
|
|
9740
10524
|
});
|
|
9741
10525
|
const deleteBtn = createElement("button", {
|
|
10526
|
+
type: "button",
|
|
9742
10527
|
className: "p-1.5 text-red-600 hover:bg-red-50 rounded transition-colors",
|
|
9743
10528
|
title: "Delete option",
|
|
9744
|
-
onclick: () => {
|
|
10529
|
+
onclick: (e) => {
|
|
10530
|
+
e.preventDefault();
|
|
10531
|
+
e.stopPropagation();
|
|
9745
10532
|
const currentOptions = getCurrentOptions();
|
|
9746
|
-
const newOptions = currentOptions.filter((
|
|
10533
|
+
const newOptions = currentOptions.filter((o) => o.value !== opt.value);
|
|
9747
10534
|
formStore.getState().updateField(fieldId2, { options: newOptions });
|
|
10535
|
+
this.render();
|
|
9748
10536
|
}
|
|
9749
10537
|
}, [getIcon("Trash2", 14)]);
|
|
9750
10538
|
optionRow.appendChild(labelInput);
|
|
@@ -9768,40 +10556,67 @@ var FormBuilder = class {
|
|
|
9768
10556
|
body.appendChild(addOptionBtn);
|
|
9769
10557
|
}
|
|
9770
10558
|
}
|
|
9771
|
-
const
|
|
9772
|
-
const
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
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];
|
|
9799
10614
|
}
|
|
9800
10615
|
});
|
|
9801
|
-
formStore.getState().updateField(selectedField.id, {
|
|
10616
|
+
formStore.getState().updateField(selectedField.id, { validations: newValidations });
|
|
9802
10617
|
};
|
|
9803
|
-
const
|
|
9804
|
-
const value =
|
|
10618
|
+
const getValidationsValue = (key) => {
|
|
10619
|
+
const value = validationsObj[key];
|
|
9805
10620
|
if (value === void 0 || value === null)
|
|
9806
10621
|
return "";
|
|
9807
10622
|
if (typeof value === "number")
|
|
@@ -9811,17 +10626,139 @@ var FormBuilder = class {
|
|
|
9811
10626
|
return String(value);
|
|
9812
10627
|
};
|
|
9813
10628
|
const validationElements = [];
|
|
9814
|
-
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
|
+
}
|
|
9815
10752
|
const minLenGroup = createElement("div", { className: "mb-3" });
|
|
9816
10753
|
minLenGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Min Length" }));
|
|
9817
10754
|
minLenGroup.appendChild(createElement("input", {
|
|
9818
10755
|
type: "number",
|
|
9819
10756
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
9820
|
-
value:
|
|
10757
|
+
value: getValidationsValue("minLength") || "",
|
|
9821
10758
|
placeholder: "e.g. 3",
|
|
9822
|
-
|
|
10759
|
+
oninput: (e) => {
|
|
9823
10760
|
const value = e.target.value;
|
|
9824
|
-
|
|
10761
|
+
updateValidations({ minLength: value !== "" ? parseInt(value) : void 0 });
|
|
9825
10762
|
}
|
|
9826
10763
|
}));
|
|
9827
10764
|
validationElements.push(minLenGroup);
|
|
@@ -9830,11 +10767,11 @@ var FormBuilder = class {
|
|
|
9830
10767
|
maxLenGroup.appendChild(createElement("input", {
|
|
9831
10768
|
type: "number",
|
|
9832
10769
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
9833
|
-
value:
|
|
10770
|
+
value: getValidationsValue("maxLength") || "",
|
|
9834
10771
|
placeholder: "e.g. 100",
|
|
9835
|
-
|
|
10772
|
+
oninput: (e) => {
|
|
9836
10773
|
const value = e.target.value;
|
|
9837
|
-
|
|
10774
|
+
updateValidations({ maxLength: value !== "" ? parseInt(value) : void 0 });
|
|
9838
10775
|
}
|
|
9839
10776
|
}));
|
|
9840
10777
|
validationElements.push(maxLenGroup);
|
|
@@ -9894,10 +10831,11 @@ var FormBuilder = class {
|
|
|
9894
10831
|
examplesContainer.appendChild(examplesList);
|
|
9895
10832
|
regexGroup.appendChild(examplesContainer);
|
|
9896
10833
|
}
|
|
9897
|
-
const currentRegex =
|
|
10834
|
+
const currentRegex = validationsObj.pattern || selectedField.validation?.regex || "";
|
|
9898
10835
|
const findPresetByRegex = (regex) => {
|
|
9899
10836
|
return REGEX_PRESETS.find((preset) => preset.pattern === regex);
|
|
9900
10837
|
};
|
|
10838
|
+
const regexMessage = validationsObj.customErrorMessages?.pattern || selectedField.validation?.regexMessage || "Invalid format";
|
|
9901
10839
|
let currentPreset = currentRegex ? findPresetByRegex(currentRegex) : void 0;
|
|
9902
10840
|
let selectedPresetId = currentPreset?.id || "";
|
|
9903
10841
|
let regexInput;
|
|
@@ -9912,9 +10850,9 @@ var FormBuilder = class {
|
|
|
9912
10850
|
selectedPresetId = presetId;
|
|
9913
10851
|
const preset = REGEX_PRESETS.find((p) => p.id === presetId);
|
|
9914
10852
|
if (preset) {
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
10853
|
+
updateValidations({
|
|
10854
|
+
pattern: preset.pattern,
|
|
10855
|
+
customErrorMessages: { ...validationsObj.customErrorMessages, pattern: preset.errorMessage }
|
|
9918
10856
|
});
|
|
9919
10857
|
regexInput.value = preset.pattern;
|
|
9920
10858
|
currentPreset = preset;
|
|
@@ -9969,9 +10907,9 @@ var FormBuilder = class {
|
|
|
9969
10907
|
}
|
|
9970
10908
|
}
|
|
9971
10909
|
}
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
10910
|
+
updateValidations({
|
|
10911
|
+
pattern: val || void 0,
|
|
10912
|
+
customErrorMessages: { ...validationsObj.customErrorMessages, pattern: currentPreset?.errorMessage || regexMessage || "Invalid format" }
|
|
9975
10913
|
});
|
|
9976
10914
|
if (examplesList) {
|
|
9977
10915
|
if (currentPreset) {
|
|
@@ -9994,6 +10932,21 @@ var FormBuilder = class {
|
|
|
9994
10932
|
updateExamples(examplesList, currentRegex);
|
|
9995
10933
|
}
|
|
9996
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);
|
|
9997
10950
|
validationElements.push(regexGroup);
|
|
9998
10951
|
}
|
|
9999
10952
|
if (selectedField.type === "checkbox") {
|
|
@@ -10002,12 +10955,12 @@ var FormBuilder = class {
|
|
|
10002
10955
|
minSelectedGroup.appendChild(createElement("input", {
|
|
10003
10956
|
type: "number",
|
|
10004
10957
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10005
|
-
value:
|
|
10958
|
+
value: getValidationsValue("minSelected"),
|
|
10006
10959
|
placeholder: "e.g. 1",
|
|
10007
10960
|
min: "0",
|
|
10008
10961
|
onchange: (e) => {
|
|
10009
10962
|
const val = e.target.value;
|
|
10010
|
-
|
|
10963
|
+
updateValidations({ minSelected: val ? parseInt(val) : void 0 });
|
|
10011
10964
|
}
|
|
10012
10965
|
}));
|
|
10013
10966
|
validationElements.push(minSelectedGroup);
|
|
@@ -10016,12 +10969,12 @@ var FormBuilder = class {
|
|
|
10016
10969
|
maxSelectedGroup.appendChild(createElement("input", {
|
|
10017
10970
|
type: "number",
|
|
10018
10971
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10019
|
-
value:
|
|
10972
|
+
value: getValidationsValue("maxSelected"),
|
|
10020
10973
|
placeholder: "e.g. 2",
|
|
10021
10974
|
min: "1",
|
|
10022
10975
|
onchange: (e) => {
|
|
10023
10976
|
const val = e.target.value;
|
|
10024
|
-
|
|
10977
|
+
updateValidations({ maxSelected: val ? parseInt(val) : void 0 });
|
|
10025
10978
|
}
|
|
10026
10979
|
}));
|
|
10027
10980
|
validationElements.push(maxSelectedGroup);
|
|
@@ -10032,10 +10985,10 @@ var FormBuilder = class {
|
|
|
10032
10985
|
minDateGroup.appendChild(createElement("input", {
|
|
10033
10986
|
type: "date",
|
|
10034
10987
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10035
|
-
value:
|
|
10988
|
+
value: validationsObj.minDate || "",
|
|
10036
10989
|
onchange: (e) => {
|
|
10037
10990
|
const val = e.target.value;
|
|
10038
|
-
|
|
10991
|
+
updateValidations({ minDate: val || void 0 });
|
|
10039
10992
|
}
|
|
10040
10993
|
}));
|
|
10041
10994
|
validationElements.push(minDateGroup);
|
|
@@ -10044,10 +10997,10 @@ var FormBuilder = class {
|
|
|
10044
10997
|
maxDateGroup.appendChild(createElement("input", {
|
|
10045
10998
|
type: "date",
|
|
10046
10999
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
10047
|
-
value:
|
|
11000
|
+
value: validationsObj.maxDate || "",
|
|
10048
11001
|
onchange: (e) => {
|
|
10049
11002
|
const val = e.target.value;
|
|
10050
|
-
|
|
11003
|
+
updateValidations({ maxDate: val || void 0 });
|
|
10051
11004
|
}
|
|
10052
11005
|
}));
|
|
10053
11006
|
validationElements.push(maxDateGroup);
|
|
@@ -10338,6 +11291,6 @@ sortablejs/modular/sortable.esm.js:
|
|
|
10338
11291
|
*)
|
|
10339
11292
|
*/
|
|
10340
11293
|
|
|
10341
|
-
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 };
|
|
10342
11295
|
//# sourceMappingURL=out.js.map
|
|
10343
11296
|
//# sourceMappingURL=index.mjs.map
|