form-builder-pro 1.3.9 → 1.4.1
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 +54 -0
- package/dist/index.d.mts +75 -2
- package/dist/index.d.ts +75 -2
- package/dist/index.js +808 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +803 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4099,7 +4099,8 @@ var FIELD_TYPES = [
|
|
|
4099
4099
|
{ type: "repeater", label: "Repeater", icon: "Copy" },
|
|
4100
4100
|
{ type: "file", label: "File Upload", icon: "Upload" },
|
|
4101
4101
|
{ type: "image", label: "Image", icon: "Image" },
|
|
4102
|
-
{ type: "name_generator", label: "Name Generator", icon: "Hash" }
|
|
4102
|
+
{ type: "name_generator", label: "Name Generator", icon: "Hash" },
|
|
4103
|
+
{ type: "formula", label: "Formula", icon: "Calculator" }
|
|
4103
4104
|
];
|
|
4104
4105
|
var DEFAULT_FIELD_CONFIG = {
|
|
4105
4106
|
text: { label: "Text Input", placeholder: "Enter text...", width: "100%", enabled: true, visible: true },
|
|
@@ -4156,6 +4157,19 @@ var DEFAULT_FIELD_CONFIG = {
|
|
|
4156
4157
|
nameGeneratorFormat: "TEXT_ID",
|
|
4157
4158
|
nameGeneratorText: "",
|
|
4158
4159
|
nameGeneratorIdPadding: 4
|
|
4160
|
+
},
|
|
4161
|
+
formula: {
|
|
4162
|
+
label: "Formula Field",
|
|
4163
|
+
placeholder: "\u2014",
|
|
4164
|
+
width: "50%",
|
|
4165
|
+
enabled: true,
|
|
4166
|
+
visible: true,
|
|
4167
|
+
formulaConfig: {
|
|
4168
|
+
mode: "single",
|
|
4169
|
+
single: { expression: "" },
|
|
4170
|
+
multiple: { compareField: "", conditions: [], fallbackExpression: "" },
|
|
4171
|
+
decimalPlaces: 2
|
|
4172
|
+
}
|
|
4159
4173
|
}
|
|
4160
4174
|
};
|
|
4161
4175
|
var VALIDATION_TYPE_PRESETS = {
|
|
@@ -4505,6 +4519,8 @@ function normalizeFieldType(type) {
|
|
|
4505
4519
|
return "datetime";
|
|
4506
4520
|
if (str === "NAME_GENERATOR" || normalized === "namegenerator")
|
|
4507
4521
|
return "name_generator";
|
|
4522
|
+
if (str === "FORMULA" || normalized === "formula")
|
|
4523
|
+
return "formula";
|
|
4508
4524
|
return str.toLowerCase();
|
|
4509
4525
|
}
|
|
4510
4526
|
function transformField(field) {
|
|
@@ -4733,6 +4749,8 @@ function transformField(field) {
|
|
|
4733
4749
|
transformed.repeatIncrementEnabled = field.repeatIncrementEnabled;
|
|
4734
4750
|
if (field.dateConstraints !== void 0)
|
|
4735
4751
|
transformed.dateConstraints = field.dateConstraints;
|
|
4752
|
+
if (field.formulaConfig !== void 0)
|
|
4753
|
+
transformed.formulaConfig = field.formulaConfig;
|
|
4736
4754
|
if (field.nameGeneratorFormat !== void 0)
|
|
4737
4755
|
transformed.nameGeneratorFormat = field.nameGeneratorFormat;
|
|
4738
4756
|
if (field.nameGeneratorText !== void 0)
|
|
@@ -5047,6 +5065,12 @@ function fieldToPayload(field, opts) {
|
|
|
5047
5065
|
payload.showWhenValueOffFields = field.showWhenValueOffFields;
|
|
5048
5066
|
}
|
|
5049
5067
|
}
|
|
5068
|
+
if (field.type === "formula") {
|
|
5069
|
+
payload.fieldType = "FORMULA";
|
|
5070
|
+
payload.type = "formula";
|
|
5071
|
+
if (field.formulaConfig !== void 0)
|
|
5072
|
+
payload.formulaConfig = field.formulaConfig;
|
|
5073
|
+
}
|
|
5050
5074
|
if (field.type === "repeater") {
|
|
5051
5075
|
payload.fieldType = "REPEATER";
|
|
5052
5076
|
payload.type = "number";
|
|
@@ -5994,7 +6018,8 @@ var ICONS = {
|
|
|
5994
6018
|
"MapPin": '<path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" /><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z" />',
|
|
5995
6019
|
"Briefcase": '<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 14.15v4.25c0 1.094-.787 2.036-1.872 2.18-2.087.277-4.216.42-6.378.42s-4.291-.143-6.378-.42c-1.085-.144-1.872-1.086-1.872-2.18v-4.25m16.5 0a2.18 2.18 0 00.75-1.661V8.706c0-1.081-.768-2.015-1.837-2.175a48.114 48.114 0 00-3.413-.387m4.5 8.006c-.194.165-.42.295-.67.38-1.053.33-2.177.58-3.345.726-.26.032-.52.05-.778.066m-13.882-6.626h.008v.008h-.008v-.008zm1.096-3.837a48.116 48.116 0 00-3.413.387c-1.069.16-1.837 1.094-1.837 2.175v4.784c0 .493.196.958.536 1.344m0 0a17.8 17.8 0 013.344.726c.25.085.476.215.67.38m13.784-5.32c-.34-.386-.536-.851-.536-1.344v-4.784c0-1.081-.768-2.015-1.837-2.175a48.041 48.041 0 01-3.413-.387m-4.5 8.006c.194.165.42.295.67.38 1.053.33 2.177.58 3.345.726.26.032.52.05.778.066m0-7.384V5.626a2.25 2.25 0 00-2.25-2.25h-4.5a2.25 2.25 0 00-2.25 2.25v2.247M16.5 6h-9" />',
|
|
5996
6020
|
"Eye": '<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />',
|
|
5997
|
-
"Cog": '<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />'
|
|
6021
|
+
"Cog": '<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />',
|
|
6022
|
+
"Calculator": '<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 15.75V18m-7.5-6.75h.008v.008H8.25v-.008zm0 2.25h.008v.008H8.25V13.5zm0 2.25h.008v.008H8.25v-.008zm0 2.25h.008v.008H8.25V18zm2.498-6.75h.007v.008h-.007v-.008zm0 2.25h.007v.008h-.007V13.5zm0 2.25h.007v.008h-.007v-.008zm0 2.25h.007v.008h-.007V18zm2.504-6.75h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V13.5zm0 2.25h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V18zm2.498-6.75h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V13.5zM8.25 6h7.5v2.25h-7.5V6zM12 2.25c-1.892 0-3.758.11-5.593.322C5.307 2.7 4.5 3.65 4.5 4.757V19.5a2.25 2.25 0 002.25 2.25h10.5a2.25 2.25 0 002.25-2.25V4.757c0-1.108-.806-2.057-1.907-2.185A48.507 48.507 0 0012 2.25z" />'
|
|
5998
6023
|
};
|
|
5999
6024
|
function getIcon(name, size = 20) {
|
|
6000
6025
|
const svgString = ICONS[name] || `<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />`;
|
|
@@ -6228,6 +6253,311 @@ function getNumericFieldsForFormula(schema, excludeFieldId) {
|
|
|
6228
6253
|
}
|
|
6229
6254
|
return result;
|
|
6230
6255
|
}
|
|
6256
|
+
var _tokenCache = /* @__PURE__ */ new Map();
|
|
6257
|
+
function _tokenize(expr) {
|
|
6258
|
+
if (_tokenCache.has(expr))
|
|
6259
|
+
return _tokenCache.get(expr);
|
|
6260
|
+
const tokens = [];
|
|
6261
|
+
let i = 0;
|
|
6262
|
+
while (i < expr.length) {
|
|
6263
|
+
const c = expr[i];
|
|
6264
|
+
if (/\s/.test(c)) {
|
|
6265
|
+
i++;
|
|
6266
|
+
continue;
|
|
6267
|
+
}
|
|
6268
|
+
if (/[+\-*/(),]/.test(c)) {
|
|
6269
|
+
tokens.push(c);
|
|
6270
|
+
i++;
|
|
6271
|
+
continue;
|
|
6272
|
+
}
|
|
6273
|
+
if (/[0-9.]/.test(c)) {
|
|
6274
|
+
let num = "";
|
|
6275
|
+
while (i < expr.length && /[0-9.]/.test(expr[i])) {
|
|
6276
|
+
num += expr[i++];
|
|
6277
|
+
}
|
|
6278
|
+
tokens.push(num);
|
|
6279
|
+
continue;
|
|
6280
|
+
}
|
|
6281
|
+
if (/[a-zA-Z_]/.test(c)) {
|
|
6282
|
+
let ident = "";
|
|
6283
|
+
while (i < expr.length && /[a-zA-Z0-9_]/.test(expr[i])) {
|
|
6284
|
+
ident += expr[i++];
|
|
6285
|
+
}
|
|
6286
|
+
tokens.push(ident);
|
|
6287
|
+
continue;
|
|
6288
|
+
}
|
|
6289
|
+
i++;
|
|
6290
|
+
}
|
|
6291
|
+
_tokenCache.set(expr, tokens);
|
|
6292
|
+
return tokens;
|
|
6293
|
+
}
|
|
6294
|
+
function _evalResolved(expr) {
|
|
6295
|
+
const tokens = _tokenize(expr);
|
|
6296
|
+
let pos = 0;
|
|
6297
|
+
const parseExpr = () => {
|
|
6298
|
+
let left = parseTerm();
|
|
6299
|
+
while (pos < tokens.length) {
|
|
6300
|
+
if (tokens[pos] === "+") {
|
|
6301
|
+
pos++;
|
|
6302
|
+
left += parseTerm();
|
|
6303
|
+
} else if (tokens[pos] === "-") {
|
|
6304
|
+
pos++;
|
|
6305
|
+
left -= parseTerm();
|
|
6306
|
+
} else
|
|
6307
|
+
break;
|
|
6308
|
+
}
|
|
6309
|
+
return left;
|
|
6310
|
+
};
|
|
6311
|
+
const parseTerm = () => {
|
|
6312
|
+
let left = parseFactor();
|
|
6313
|
+
while (pos < tokens.length) {
|
|
6314
|
+
if (tokens[pos] === "*") {
|
|
6315
|
+
pos++;
|
|
6316
|
+
left *= parseFactor();
|
|
6317
|
+
} else if (tokens[pos] === "/") {
|
|
6318
|
+
pos++;
|
|
6319
|
+
const r = parseFactor();
|
|
6320
|
+
if (r === 0)
|
|
6321
|
+
return NaN;
|
|
6322
|
+
left /= r;
|
|
6323
|
+
} else
|
|
6324
|
+
break;
|
|
6325
|
+
}
|
|
6326
|
+
return left;
|
|
6327
|
+
};
|
|
6328
|
+
const parseArgs = () => {
|
|
6329
|
+
const args = [];
|
|
6330
|
+
if (pos < tokens.length && tokens[pos] !== ")") {
|
|
6331
|
+
args.push(parseExpr());
|
|
6332
|
+
while (pos < tokens.length && tokens[pos] === ",") {
|
|
6333
|
+
pos++;
|
|
6334
|
+
args.push(parseExpr());
|
|
6335
|
+
}
|
|
6336
|
+
}
|
|
6337
|
+
return args;
|
|
6338
|
+
};
|
|
6339
|
+
const parseFactor = () => {
|
|
6340
|
+
if (pos >= tokens.length)
|
|
6341
|
+
return NaN;
|
|
6342
|
+
const t = tokens[pos];
|
|
6343
|
+
if (t === "(") {
|
|
6344
|
+
pos++;
|
|
6345
|
+
const v = parseExpr();
|
|
6346
|
+
if (tokens[pos] === ")")
|
|
6347
|
+
pos++;
|
|
6348
|
+
return v;
|
|
6349
|
+
}
|
|
6350
|
+
if (t === "-") {
|
|
6351
|
+
pos++;
|
|
6352
|
+
return -parseFactor();
|
|
6353
|
+
}
|
|
6354
|
+
if (t === "+") {
|
|
6355
|
+
pos++;
|
|
6356
|
+
return parseFactor();
|
|
6357
|
+
}
|
|
6358
|
+
const n = parseFloat(t);
|
|
6359
|
+
if (!isNaN(n)) {
|
|
6360
|
+
pos++;
|
|
6361
|
+
return n;
|
|
6362
|
+
}
|
|
6363
|
+
if (/^[a-zA-Z_]/.test(t) && pos + 1 < tokens.length && tokens[pos + 1] === "(") {
|
|
6364
|
+
const fn = t.toUpperCase();
|
|
6365
|
+
pos += 2;
|
|
6366
|
+
const args = parseArgs();
|
|
6367
|
+
if (pos < tokens.length && tokens[pos] === ")")
|
|
6368
|
+
pos++;
|
|
6369
|
+
switch (fn) {
|
|
6370
|
+
case "ROUND":
|
|
6371
|
+
return args.length >= 2 ? Math.round(args[0] * Math.pow(10, args[1])) / Math.pow(10, args[1]) : Math.round(args[0] ?? 0);
|
|
6372
|
+
case "ABS":
|
|
6373
|
+
return Math.abs(args[0] ?? 0);
|
|
6374
|
+
case "MIN":
|
|
6375
|
+
return args.length ? Math.min(...args) : NaN;
|
|
6376
|
+
case "MAX":
|
|
6377
|
+
return args.length ? Math.max(...args) : NaN;
|
|
6378
|
+
case "FLOOR":
|
|
6379
|
+
return Math.floor(args[0] ?? 0);
|
|
6380
|
+
case "CEIL":
|
|
6381
|
+
return Math.ceil(args[0] ?? 0);
|
|
6382
|
+
case "SQRT":
|
|
6383
|
+
return Math.sqrt(args[0] ?? 0);
|
|
6384
|
+
case "POW":
|
|
6385
|
+
return Math.pow(args[0] ?? 0, args[1] ?? 2);
|
|
6386
|
+
default:
|
|
6387
|
+
return NaN;
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
pos++;
|
|
6391
|
+
return 0;
|
|
6392
|
+
};
|
|
6393
|
+
try {
|
|
6394
|
+
const result = parseExpr();
|
|
6395
|
+
return isNaN(result) ? NaN : result;
|
|
6396
|
+
} catch {
|
|
6397
|
+
return NaN;
|
|
6398
|
+
}
|
|
6399
|
+
}
|
|
6400
|
+
function extractBracketFields(expression) {
|
|
6401
|
+
if (!expression)
|
|
6402
|
+
return [];
|
|
6403
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6404
|
+
const out = [];
|
|
6405
|
+
for (const m of expression.matchAll(/\{([^}]+)\}/g)) {
|
|
6406
|
+
const name = m[1].trim();
|
|
6407
|
+
if (name && !seen.has(name)) {
|
|
6408
|
+
seen.add(name);
|
|
6409
|
+
out.push(name);
|
|
6410
|
+
}
|
|
6411
|
+
}
|
|
6412
|
+
return out;
|
|
6413
|
+
}
|
|
6414
|
+
function evaluateFormulaExpression(expression, values) {
|
|
6415
|
+
if (!expression?.trim())
|
|
6416
|
+
return NaN;
|
|
6417
|
+
const resolved = expression.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
6418
|
+
const v = values[name.trim()];
|
|
6419
|
+
if (v === void 0 || v === null || v === "")
|
|
6420
|
+
return "0";
|
|
6421
|
+
const n = typeof v === "number" ? v : parseFloat(String(v));
|
|
6422
|
+
return isNaN(n) ? "0" : String(n);
|
|
6423
|
+
});
|
|
6424
|
+
_tokenCache.delete(resolved);
|
|
6425
|
+
return _evalResolved(resolved);
|
|
6426
|
+
}
|
|
6427
|
+
function evaluateFormulaConfig(config, values, compareValue) {
|
|
6428
|
+
if (!config)
|
|
6429
|
+
return { result: NaN, error: "No formula config" };
|
|
6430
|
+
const dp = config.decimalPlaces ?? 2;
|
|
6431
|
+
try {
|
|
6432
|
+
let expression;
|
|
6433
|
+
if (config.mode === "single") {
|
|
6434
|
+
expression = config.single?.expression ?? "";
|
|
6435
|
+
if (!expression.trim())
|
|
6436
|
+
return { result: NaN, error: "No expression defined" };
|
|
6437
|
+
} else {
|
|
6438
|
+
const cmpVal = compareValue ?? "";
|
|
6439
|
+
const matched = config.multiple?.conditions?.find((c) => c.value === cmpVal);
|
|
6440
|
+
expression = matched?.expression ?? config.multiple?.fallbackExpression ?? "";
|
|
6441
|
+
if (!expression.trim())
|
|
6442
|
+
return { result: NaN, error: "No matching condition and no fallback" };
|
|
6443
|
+
}
|
|
6444
|
+
const raw = evaluateFormulaExpression(expression, values);
|
|
6445
|
+
if (isNaN(raw))
|
|
6446
|
+
return { result: NaN, error: "Expression evaluation failed (check syntax or divide-by-zero)" };
|
|
6447
|
+
const result = parseFloat(raw.toFixed(dp));
|
|
6448
|
+
return { result };
|
|
6449
|
+
} catch (e) {
|
|
6450
|
+
return { result: NaN, error: String(e) };
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
function validateFormulaExpression(expression, availableFieldNames) {
|
|
6454
|
+
if (!expression?.trim())
|
|
6455
|
+
return { valid: false, error: "Expression cannot be empty" };
|
|
6456
|
+
const refs = extractBracketFields(expression);
|
|
6457
|
+
const known = new Set(availableFieldNames);
|
|
6458
|
+
for (const ref of refs) {
|
|
6459
|
+
if (!known.has(ref)) {
|
|
6460
|
+
return { valid: false, error: `Unknown field reference: "{${ref}}"` };
|
|
6461
|
+
}
|
|
6462
|
+
}
|
|
6463
|
+
let open = 0;
|
|
6464
|
+
for (const c of expression) {
|
|
6465
|
+
if (c === "(")
|
|
6466
|
+
open++;
|
|
6467
|
+
else if (c === ")") {
|
|
6468
|
+
open--;
|
|
6469
|
+
if (open < 0)
|
|
6470
|
+
return { valid: false, error: "Unbalanced parentheses" };
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
if (open !== 0)
|
|
6474
|
+
return { valid: false, error: "Unbalanced parentheses" };
|
|
6475
|
+
return { valid: true };
|
|
6476
|
+
}
|
|
6477
|
+
function getFieldsForFormula(schema, excludeFieldId) {
|
|
6478
|
+
const result = [];
|
|
6479
|
+
const NUMERIC_TYPES = /* @__PURE__ */ new Set(["number", "formula"]);
|
|
6480
|
+
for (const section of schema.sections) {
|
|
6481
|
+
for (const field of section.fields) {
|
|
6482
|
+
if (!NUMERIC_TYPES.has(field.type))
|
|
6483
|
+
continue;
|
|
6484
|
+
if (excludeFieldId && field.id === excludeFieldId)
|
|
6485
|
+
continue;
|
|
6486
|
+
const fieldName = field.fieldName ?? field.id;
|
|
6487
|
+
result.push({ id: field.id, fieldName, label: field.label || fieldName });
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
6490
|
+
return result;
|
|
6491
|
+
}
|
|
6492
|
+
function detectFormulaFieldCircularDependency(schema, formulaFieldId, config) {
|
|
6493
|
+
const resolveField = (ref) => {
|
|
6494
|
+
for (const s of schema.sections) {
|
|
6495
|
+
for (const f of s.fields) {
|
|
6496
|
+
if (f.fieldName === ref || f.id === ref)
|
|
6497
|
+
return f;
|
|
6498
|
+
}
|
|
6499
|
+
}
|
|
6500
|
+
return void 0;
|
|
6501
|
+
};
|
|
6502
|
+
const getDepsFromConfig = (f) => {
|
|
6503
|
+
if (f.type !== "formula" || !f.formulaConfig)
|
|
6504
|
+
return [];
|
|
6505
|
+
const cfg = f.formulaConfig;
|
|
6506
|
+
const exprs = [];
|
|
6507
|
+
if (cfg.mode === "single") {
|
|
6508
|
+
if (cfg.single?.expression)
|
|
6509
|
+
exprs.push(cfg.single.expression);
|
|
6510
|
+
} else {
|
|
6511
|
+
cfg.multiple?.conditions?.forEach((c) => {
|
|
6512
|
+
if (c.expression)
|
|
6513
|
+
exprs.push(c.expression);
|
|
6514
|
+
});
|
|
6515
|
+
if (cfg.multiple?.fallbackExpression)
|
|
6516
|
+
exprs.push(cfg.multiple.fallbackExpression);
|
|
6517
|
+
}
|
|
6518
|
+
return exprs.flatMap((e) => extractBracketFields(e));
|
|
6519
|
+
};
|
|
6520
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6521
|
+
const hasCycle = (fieldId) => {
|
|
6522
|
+
if (visited.has(fieldId))
|
|
6523
|
+
return true;
|
|
6524
|
+
visited.add(fieldId);
|
|
6525
|
+
const field = resolveField(fieldId);
|
|
6526
|
+
if (!field || field.type !== "formula" || !field.formulaConfig) {
|
|
6527
|
+
visited.delete(fieldId);
|
|
6528
|
+
return false;
|
|
6529
|
+
}
|
|
6530
|
+
for (const dep of getDepsFromConfig(field)) {
|
|
6531
|
+
const depField = resolveField(dep);
|
|
6532
|
+
if (!depField)
|
|
6533
|
+
continue;
|
|
6534
|
+
if (depField.id === formulaFieldId) {
|
|
6535
|
+
visited.delete(fieldId);
|
|
6536
|
+
return true;
|
|
6537
|
+
}
|
|
6538
|
+
if (hasCycle(depField.id)) {
|
|
6539
|
+
visited.delete(fieldId);
|
|
6540
|
+
return true;
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
visited.delete(fieldId);
|
|
6544
|
+
return false;
|
|
6545
|
+
};
|
|
6546
|
+
const thisField = resolveField(formulaFieldId);
|
|
6547
|
+
if (!thisField || !config)
|
|
6548
|
+
return false;
|
|
6549
|
+
const thisDeps = getDepsFromConfig({ ...thisField, formulaConfig: config });
|
|
6550
|
+
for (const dep of thisDeps) {
|
|
6551
|
+
const depField = resolveField(dep);
|
|
6552
|
+
if (!depField)
|
|
6553
|
+
continue;
|
|
6554
|
+
if (depField.id === formulaFieldId)
|
|
6555
|
+
return true;
|
|
6556
|
+
if (hasCycle(depField.id))
|
|
6557
|
+
return true;
|
|
6558
|
+
}
|
|
6559
|
+
return false;
|
|
6560
|
+
}
|
|
6231
6561
|
|
|
6232
6562
|
// src/core/countryData.ts
|
|
6233
6563
|
var COUNTRY_DATA = [
|
|
@@ -6877,6 +7207,19 @@ var FieldRenderer = class {
|
|
|
6877
7207
|
});
|
|
6878
7208
|
break;
|
|
6879
7209
|
}
|
|
7210
|
+
case "formula": {
|
|
7211
|
+
const formulaDisplay = value !== void 0 && value !== null ? String(value) : "\u2014";
|
|
7212
|
+
input = createElement("input", {
|
|
7213
|
+
type: "text",
|
|
7214
|
+
className: "flex min-h-touch w-full rounded-md border border-input bg-gray-50 dark:bg-gray-800 px-3 py-2 text-sm sm:text-base font-mono ring-offset-background 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",
|
|
7215
|
+
placeholder: "\u2014",
|
|
7216
|
+
value: formulaDisplay,
|
|
7217
|
+
readonly: true,
|
|
7218
|
+
disabled: true,
|
|
7219
|
+
title: "Computed field \u2014 value is calculated from a formula"
|
|
7220
|
+
});
|
|
7221
|
+
break;
|
|
7222
|
+
}
|
|
6880
7223
|
default:
|
|
6881
7224
|
const rules = getValidationRules(field);
|
|
6882
7225
|
const useNumericTextInput = field.type === "text" && isNumericTextField(field);
|
|
@@ -7301,6 +7644,21 @@ function buildFormulaValuesMap(schema, data) {
|
|
|
7301
7644
|
values[field.fieldName] = newVal;
|
|
7302
7645
|
}
|
|
7303
7646
|
}
|
|
7647
|
+
const formulaTypeFields = allFields.filter((f) => f.type === "formula" && f.formulaConfig);
|
|
7648
|
+
for (let pass = 0; pass < Math.max(1, formulaTypeFields.length); pass++) {
|
|
7649
|
+
for (const field of formulaTypeFields) {
|
|
7650
|
+
const modelKey = getModelKey(field);
|
|
7651
|
+
const compareFieldName = field.formulaConfig.multiple?.compareField;
|
|
7652
|
+
const compareValue = compareFieldName ? String(values[compareFieldName] ?? "") : "";
|
|
7653
|
+
const evalResult = evaluateFormulaConfig(field.formulaConfig, values, compareValue);
|
|
7654
|
+
const dp = field.formulaConfig.decimalPlaces ?? 2;
|
|
7655
|
+
const newVal = !evalResult.error && !isNaN(evalResult.result) ? parseFloat(evalResult.result.toFixed(dp)) : void 0;
|
|
7656
|
+
values[modelKey] = newVal;
|
|
7657
|
+
values[field.id] = newVal;
|
|
7658
|
+
if (field.fieldName)
|
|
7659
|
+
values[field.fieldName] = newVal;
|
|
7660
|
+
}
|
|
7661
|
+
}
|
|
7304
7662
|
return values;
|
|
7305
7663
|
}
|
|
7306
7664
|
function computeFormulaValue(field, schema, data) {
|
|
@@ -7324,6 +7682,31 @@ function isFormulaDependency(schema, modelKey, fieldId) {
|
|
|
7324
7682
|
if (fieldId && field.dependencies.includes(fieldId))
|
|
7325
7683
|
return true;
|
|
7326
7684
|
}
|
|
7685
|
+
if (field.type === "formula" && field.formulaConfig) {
|
|
7686
|
+
const cfg = field.formulaConfig;
|
|
7687
|
+
const exprs = [];
|
|
7688
|
+
if (cfg.mode === "single") {
|
|
7689
|
+
if (cfg.single?.expression)
|
|
7690
|
+
exprs.push(cfg.single.expression);
|
|
7691
|
+
} else {
|
|
7692
|
+
cfg.multiple?.conditions?.forEach((c) => {
|
|
7693
|
+
if (c.expression)
|
|
7694
|
+
exprs.push(c.expression);
|
|
7695
|
+
});
|
|
7696
|
+
if (cfg.multiple?.fallbackExpression)
|
|
7697
|
+
exprs.push(cfg.multiple.fallbackExpression);
|
|
7698
|
+
if (cfg.multiple?.compareField) {
|
|
7699
|
+
if (cfg.multiple.compareField === modelKey || cfg.multiple.compareField === fieldId)
|
|
7700
|
+
return true;
|
|
7701
|
+
}
|
|
7702
|
+
}
|
|
7703
|
+
for (const expr of exprs) {
|
|
7704
|
+
for (const ref of extractBracketFields(expr)) {
|
|
7705
|
+
if (ref === modelKey || fieldId && ref === fieldId)
|
|
7706
|
+
return true;
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7709
|
+
}
|
|
7327
7710
|
}
|
|
7328
7711
|
}
|
|
7329
7712
|
return false;
|
|
@@ -7392,6 +7775,15 @@ var FormRenderer = class {
|
|
|
7392
7775
|
const computed = computeFormulaValue(field, this.schema, this.data);
|
|
7393
7776
|
fieldValue = computed;
|
|
7394
7777
|
this.data[modelKey] = computed;
|
|
7778
|
+
} else if (field.type === "formula" && field.formulaConfig) {
|
|
7779
|
+
const allValues = buildFormulaValuesMap(this.schema, this.data);
|
|
7780
|
+
const compareFieldName = field.formulaConfig.multiple?.compareField;
|
|
7781
|
+
const compareValue = compareFieldName ? String(allValues[compareFieldName] ?? "") : "";
|
|
7782
|
+
const evalResult = evaluateFormulaConfig(field.formulaConfig, allValues, compareValue);
|
|
7783
|
+
const dp = field.formulaConfig.decimalPlaces ?? 2;
|
|
7784
|
+
const computed = !evalResult.error && !isNaN(evalResult.result) ? parseFloat(evalResult.result.toFixed(dp)) : void 0;
|
|
7785
|
+
fieldValue = computed;
|
|
7786
|
+
this.data[modelKey] = computed;
|
|
7395
7787
|
} else if (field.type === "name_generator") {
|
|
7396
7788
|
fieldValue = this.data[modelKey];
|
|
7397
7789
|
if (!fieldValue) {
|
|
@@ -7403,7 +7795,7 @@ var FormRenderer = class {
|
|
|
7403
7795
|
} else {
|
|
7404
7796
|
fieldValue = this.data[modelKey];
|
|
7405
7797
|
}
|
|
7406
|
-
const isFormulaField = field.type === "number" && field.valueSource === "formula";
|
|
7798
|
+
const isFormulaField = field.type === "number" && field.valueSource === "formula" || field.type === "formula";
|
|
7407
7799
|
const fieldEl = FieldRenderer.render(
|
|
7408
7800
|
field,
|
|
7409
7801
|
fieldValue,
|
|
@@ -7441,6 +7833,18 @@ var FormRenderer = class {
|
|
|
7441
7833
|
grid.appendChild(fieldWrapper);
|
|
7442
7834
|
});
|
|
7443
7835
|
sectionEl.appendChild(grid);
|
|
7836
|
+
if (section.repeatable === true) {
|
|
7837
|
+
const addLabel = section.addButtonLabel && section.addButtonLabel.trim() || "+ Add";
|
|
7838
|
+
const addRow = createElement("div", { className: "mt-3 flex justify-start" });
|
|
7839
|
+
addRow.appendChild(
|
|
7840
|
+
createElement("button", {
|
|
7841
|
+
type: "button",
|
|
7842
|
+
className: "px-4 py-2 text-sm font-medium rounded-md border border-[#019FA2] text-[#019FA2] dark:text-[#4dd4d6] dark:border-[#019FA2] bg-transparent hover:bg-[#019FA2]/10 transition-colors",
|
|
7843
|
+
text: addLabel
|
|
7844
|
+
})
|
|
7845
|
+
);
|
|
7846
|
+
sectionEl.appendChild(addRow);
|
|
7847
|
+
}
|
|
7444
7848
|
form.appendChild(sectionEl);
|
|
7445
7849
|
});
|
|
7446
7850
|
const submitBtn = createElement("button", {
|
|
@@ -9916,6 +10320,21 @@ var Section = class _Section {
|
|
|
9916
10320
|
nestedWrap.appendChild(nestedList);
|
|
9917
10321
|
sectionEl.appendChild(nestedWrap);
|
|
9918
10322
|
}
|
|
10323
|
+
if (this.section.repeatable === true) {
|
|
10324
|
+
const addLabel = this.section.addButtonLabel && this.section.addButtonLabel.trim() || "+ Add";
|
|
10325
|
+
const footer = createElement("div", {
|
|
10326
|
+
className: "px-4 pb-4 pt-2 flex justify-start border-t border-gray-100 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-800/30"
|
|
10327
|
+
});
|
|
10328
|
+
footer.appendChild(
|
|
10329
|
+
createElement("button", {
|
|
10330
|
+
type: "button",
|
|
10331
|
+
className: "px-4 py-2 text-sm font-medium rounded-md border border-[#019FA2] text-[#019FA2] dark:text-[#4dd4d6] dark:border-[#019FA2] bg-white dark:bg-gray-900 hover:bg-[#019FA2]/10 transition-colors",
|
|
10332
|
+
text: addLabel,
|
|
10333
|
+
onclick: (e) => e.preventDefault()
|
|
10334
|
+
})
|
|
10335
|
+
);
|
|
10336
|
+
sectionEl.appendChild(footer);
|
|
10337
|
+
}
|
|
9919
10338
|
this.initFieldSortable(fieldsGrid);
|
|
9920
10339
|
return sectionEl;
|
|
9921
10340
|
}
|
|
@@ -10090,6 +10509,7 @@ var SectionList = class {
|
|
|
10090
10509
|
|
|
10091
10510
|
// src/builder/FormBuilder.ts
|
|
10092
10511
|
var advancedCssPanelState = /* @__PURE__ */ new Map();
|
|
10512
|
+
var lastFocusedExprTextarea = null;
|
|
10093
10513
|
var LABEL_DEBOUNCE_MS = 300;
|
|
10094
10514
|
var labelUpdateTimeouts = /* @__PURE__ */ new Map();
|
|
10095
10515
|
var FormBuilder = class {
|
|
@@ -10274,6 +10694,7 @@ var FormBuilder = class {
|
|
|
10274
10694
|
collapsible: s.collapsible,
|
|
10275
10695
|
parentGroupId: s.parentGroupId,
|
|
10276
10696
|
repeatable: s.repeatable,
|
|
10697
|
+
addButtonLabel: s.addButtonLabel,
|
|
10277
10698
|
minInstances: s.minInstances,
|
|
10278
10699
|
maxInstances: s.maxInstances,
|
|
10279
10700
|
css: s.css,
|
|
@@ -10563,6 +10984,36 @@ var FormBuilder = class {
|
|
|
10563
10984
|
}
|
|
10564
10985
|
}
|
|
10565
10986
|
}
|
|
10987
|
+
for (const field of schema.sections.flatMap((s) => s.fields)) {
|
|
10988
|
+
if (field.type !== "formula" || !field.formulaConfig)
|
|
10989
|
+
continue;
|
|
10990
|
+
const fcfg = field.formulaConfig;
|
|
10991
|
+
const fAvailable = getFieldsForFormula(schema, field.id);
|
|
10992
|
+
const fNames = fAvailable.map((f) => f.fieldName);
|
|
10993
|
+
const exprs = [];
|
|
10994
|
+
if (fcfg.mode === "single") {
|
|
10995
|
+
if (fcfg.single?.expression)
|
|
10996
|
+
exprs.push(fcfg.single.expression);
|
|
10997
|
+
} else {
|
|
10998
|
+
fcfg.multiple?.conditions?.forEach((c) => {
|
|
10999
|
+
if (c.expression)
|
|
11000
|
+
exprs.push(c.expression);
|
|
11001
|
+
});
|
|
11002
|
+
if (fcfg.multiple?.fallbackExpression)
|
|
11003
|
+
exprs.push(fcfg.multiple.fallbackExpression);
|
|
11004
|
+
}
|
|
11005
|
+
for (const expr of exprs) {
|
|
11006
|
+
const result = validateFormulaExpression(expr, fNames);
|
|
11007
|
+
if (!result.valid) {
|
|
11008
|
+
alert(`Formula error in "${field.label}": ${result.error}`);
|
|
11009
|
+
return;
|
|
11010
|
+
}
|
|
11011
|
+
}
|
|
11012
|
+
if (detectFormulaFieldCircularDependency(schema, field.id, fcfg)) {
|
|
11013
|
+
alert(`Circular dependency detected in formula for "${field.label}"`);
|
|
11014
|
+
return;
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
10566
11017
|
schema.sections.forEach((section) => {
|
|
10567
11018
|
section.fields?.forEach((field) => {
|
|
10568
11019
|
if (field.type === "number" && field.validations) {
|
|
@@ -11164,11 +11615,37 @@ var FormBuilder = class {
|
|
|
11164
11615
|
this.createCheckboxField(
|
|
11165
11616
|
"Allow multiple instances",
|
|
11166
11617
|
section.repeatable === true,
|
|
11167
|
-
(checked) => formStore.getState().updateSection(sectionId, {
|
|
11618
|
+
(checked) => formStore.getState().updateSection(sectionId, {
|
|
11619
|
+
repeatable: checked,
|
|
11620
|
+
...checked ? {} : { addButtonLabel: null }
|
|
11621
|
+
}),
|
|
11168
11622
|
`group-repeatable-${sectionId}`
|
|
11169
11623
|
)
|
|
11170
11624
|
);
|
|
11171
11625
|
if (section.repeatable === true) {
|
|
11626
|
+
const addLabelWrap = createElement("div", { className: "mb-2" });
|
|
11627
|
+
addLabelWrap.appendChild(
|
|
11628
|
+
createElement("label", {
|
|
11629
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
11630
|
+
text: "Add Button Label"
|
|
11631
|
+
})
|
|
11632
|
+
);
|
|
11633
|
+
addLabelWrap.appendChild(
|
|
11634
|
+
createElement("input", {
|
|
11635
|
+
type: "text",
|
|
11636
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11637
|
+
value: section.addButtonLabel ?? "",
|
|
11638
|
+
placeholder: "e.g. + Billing Address",
|
|
11639
|
+
"data-focus-id": `group-add-btn-label-${sectionId}`,
|
|
11640
|
+
oninput: (e) => {
|
|
11641
|
+
const raw = e.target.value;
|
|
11642
|
+
formStore.getState().updateSection(sectionId, {
|
|
11643
|
+
addButtonLabel: raw === "" ? null : raw
|
|
11644
|
+
});
|
|
11645
|
+
}
|
|
11646
|
+
})
|
|
11647
|
+
);
|
|
11648
|
+
body.appendChild(addLabelWrap);
|
|
11172
11649
|
const minG = createElement("div", { className: "mb-2" });
|
|
11173
11650
|
minG.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Min Instances" }));
|
|
11174
11651
|
minG.appendChild(createElement("input", {
|
|
@@ -11349,7 +11826,327 @@ var FormBuilder = class {
|
|
|
11349
11826
|
body.appendChild(hintEl);
|
|
11350
11827
|
}
|
|
11351
11828
|
}
|
|
11352
|
-
if (selectedField.type
|
|
11829
|
+
if (selectedField.type === "formula") {
|
|
11830
|
+
const schema = formStore.getState().schema;
|
|
11831
|
+
const availableFields = getFieldsForFormula(schema, selectedField.id);
|
|
11832
|
+
const cfg = selectedField.formulaConfig ?? {
|
|
11833
|
+
mode: "single",
|
|
11834
|
+
single: { expression: "" },
|
|
11835
|
+
multiple: { compareField: "", conditions: [], fallbackExpression: "" },
|
|
11836
|
+
decimalPlaces: 2
|
|
11837
|
+
};
|
|
11838
|
+
const patchFormulaConfig = (patch) => {
|
|
11839
|
+
const fresh = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
11840
|
+
const current = fresh?.formulaConfig ?? cfg;
|
|
11841
|
+
formStore.getState().updateField(selectedField.id, { formulaConfig: { ...current, ...patch } });
|
|
11842
|
+
};
|
|
11843
|
+
const patchMultiple = (patch) => {
|
|
11844
|
+
const fresh = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
11845
|
+
const current = fresh?.formulaConfig ?? cfg;
|
|
11846
|
+
patchFormulaConfig({ multiple: { ...current.multiple, ...patch } });
|
|
11847
|
+
};
|
|
11848
|
+
const formulaHeader = createElement("h3", {
|
|
11849
|
+
className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3 mt-4",
|
|
11850
|
+
text: "Formula Configuration"
|
|
11851
|
+
});
|
|
11852
|
+
body.appendChild(formulaHeader);
|
|
11853
|
+
const modeGroup = createElement("div", { className: "mb-4" });
|
|
11854
|
+
modeGroup.appendChild(createElement("label", {
|
|
11855
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-2",
|
|
11856
|
+
text: "Mode"
|
|
11857
|
+
}));
|
|
11858
|
+
const modeRow = createElement("div", { className: "flex gap-2" });
|
|
11859
|
+
const activeModeClass = "flex-1 px-3 py-1.5 text-sm rounded-md border bg-[#635bff] text-white border-[#635bff] font-medium";
|
|
11860
|
+
const inactiveModeClass = "flex-1 px-3 py-1.5 text-sm rounded-md border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400 hover:border-[#635bff] transition-colors";
|
|
11861
|
+
const singleBtn = createElement("button", {
|
|
11862
|
+
type: "button",
|
|
11863
|
+
className: cfg.mode === "single" ? activeModeClass : inactiveModeClass,
|
|
11864
|
+
text: "Single Expression",
|
|
11865
|
+
onclick: () => {
|
|
11866
|
+
patchFormulaConfig({ mode: "single" });
|
|
11867
|
+
this.render();
|
|
11868
|
+
}
|
|
11869
|
+
});
|
|
11870
|
+
const multipleBtn = createElement("button", {
|
|
11871
|
+
type: "button",
|
|
11872
|
+
className: cfg.mode === "multiple" ? activeModeClass : inactiveModeClass,
|
|
11873
|
+
text: "Multiple Conditions",
|
|
11874
|
+
onclick: () => {
|
|
11875
|
+
patchFormulaConfig({ mode: "multiple" });
|
|
11876
|
+
this.render();
|
|
11877
|
+
}
|
|
11878
|
+
});
|
|
11879
|
+
modeRow.appendChild(singleBtn);
|
|
11880
|
+
modeRow.appendChild(multipleBtn);
|
|
11881
|
+
modeGroup.appendChild(modeRow);
|
|
11882
|
+
body.appendChild(modeGroup);
|
|
11883
|
+
if (cfg.mode === "single") {
|
|
11884
|
+
const exprGroup = createElement("div", { className: "mb-3" });
|
|
11885
|
+
exprGroup.appendChild(createElement("label", {
|
|
11886
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
11887
|
+
text: "Expression"
|
|
11888
|
+
}));
|
|
11889
|
+
const exprTextarea = createElement("textarea", {
|
|
11890
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent font-mono text-sm resize-none",
|
|
11891
|
+
placeholder: "e.g. {fieldA} + {fieldB} * 1.18",
|
|
11892
|
+
rows: "3"
|
|
11893
|
+
});
|
|
11894
|
+
exprTextarea.value = cfg.single?.expression ?? "";
|
|
11895
|
+
exprTextarea.addEventListener("focus", () => {
|
|
11896
|
+
lastFocusedExprTextarea = exprTextarea;
|
|
11897
|
+
});
|
|
11898
|
+
const exprError = createElement("div", { className: "text-xs text-red-500 mt-1 hidden" });
|
|
11899
|
+
exprTextarea.addEventListener("input", () => {
|
|
11900
|
+
const expr = exprTextarea.value;
|
|
11901
|
+
const result = validateFormulaExpression(expr, availableFields.map((f) => f.fieldName));
|
|
11902
|
+
if (result.valid || !expr.trim()) {
|
|
11903
|
+
exprError.classList.add("hidden");
|
|
11904
|
+
} else {
|
|
11905
|
+
exprError.textContent = result.error;
|
|
11906
|
+
exprError.classList.remove("hidden");
|
|
11907
|
+
}
|
|
11908
|
+
const freshCfg = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id)?.formulaConfig ?? cfg;
|
|
11909
|
+
formStore.getState().updateField(selectedField.id, { formulaConfig: { ...freshCfg, single: { expression: expr } } });
|
|
11910
|
+
});
|
|
11911
|
+
exprGroup.appendChild(exprTextarea);
|
|
11912
|
+
exprGroup.appendChild(exprError);
|
|
11913
|
+
body.appendChild(exprGroup);
|
|
11914
|
+
} else {
|
|
11915
|
+
const compareGroup = createElement("div", { className: "mb-3" });
|
|
11916
|
+
compareGroup.appendChild(createElement("label", {
|
|
11917
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
11918
|
+
text: "Compare Field"
|
|
11919
|
+
}));
|
|
11920
|
+
const compareSelect = createElement("select", {
|
|
11921
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11922
|
+
onchange: (e) => {
|
|
11923
|
+
const val = e.target.value;
|
|
11924
|
+
patchMultiple({ compareField: val });
|
|
11925
|
+
}
|
|
11926
|
+
});
|
|
11927
|
+
compareSelect.appendChild(createElement("option", { value: "", text: "Select field to compare\u2026", selected: !cfg.multiple?.compareField }));
|
|
11928
|
+
schema.sections.flatMap((s) => s.fields).filter((f) => f.id !== selectedField.id).forEach((f) => {
|
|
11929
|
+
const fn = f.fieldName ?? f.id;
|
|
11930
|
+
compareSelect.appendChild(createElement("option", {
|
|
11931
|
+
value: fn,
|
|
11932
|
+
text: `${f.label} (${fn})`,
|
|
11933
|
+
selected: cfg.multiple?.compareField === fn
|
|
11934
|
+
}));
|
|
11935
|
+
});
|
|
11936
|
+
compareGroup.appendChild(compareSelect);
|
|
11937
|
+
body.appendChild(compareGroup);
|
|
11938
|
+
const conditionsLabel = createElement("label", {
|
|
11939
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-2",
|
|
11940
|
+
text: "Conditions"
|
|
11941
|
+
});
|
|
11942
|
+
body.appendChild(conditionsLabel);
|
|
11943
|
+
const conditions = cfg.multiple?.conditions ?? [];
|
|
11944
|
+
conditions.forEach((cond, idx) => {
|
|
11945
|
+
const row = createElement("div", { className: "mb-2 p-2 rounded-md border border-gray-100 dark:border-gray-800 space-y-1" });
|
|
11946
|
+
const whenLabel = createElement("div", { className: "text-xs text-gray-500 dark:text-gray-400", text: "When equals" });
|
|
11947
|
+
const valueInput = createElement("input", {
|
|
11948
|
+
type: "text",
|
|
11949
|
+
className: "w-full px-2 py-1 text-sm border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11950
|
+
value: cond.value,
|
|
11951
|
+
placeholder: "e.g. BHS 146",
|
|
11952
|
+
oninput: (e) => {
|
|
11953
|
+
const v = e.target.value;
|
|
11954
|
+
const freshCfg = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id)?.formulaConfig ?? cfg;
|
|
11955
|
+
const newConds = [...freshCfg.multiple?.conditions ?? []];
|
|
11956
|
+
newConds[idx] = { ...newConds[idx], value: v };
|
|
11957
|
+
patchMultiple({ conditions: newConds });
|
|
11958
|
+
}
|
|
11959
|
+
});
|
|
11960
|
+
const exprLabel = createElement("div", { className: "text-xs text-gray-500 dark:text-gray-400", text: "\u2192 Expression" });
|
|
11961
|
+
const condTextarea = createElement("textarea", {
|
|
11962
|
+
className: "w-full px-2 py-1 text-sm border border-gray-200 dark:border-gray-700 rounded-md bg-transparent font-mono resize-none",
|
|
11963
|
+
placeholder: "e.g. {fieldA} + {fieldB}",
|
|
11964
|
+
rows: "2"
|
|
11965
|
+
});
|
|
11966
|
+
condTextarea.value = cond.expression ?? "";
|
|
11967
|
+
condTextarea.addEventListener("focus", () => {
|
|
11968
|
+
lastFocusedExprTextarea = condTextarea;
|
|
11969
|
+
});
|
|
11970
|
+
condTextarea.addEventListener("input", () => {
|
|
11971
|
+
const expr = condTextarea.value;
|
|
11972
|
+
const freshCfg = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id)?.formulaConfig ?? cfg;
|
|
11973
|
+
const newConds = [...freshCfg.multiple?.conditions ?? []];
|
|
11974
|
+
newConds[idx] = { ...newConds[idx], expression: expr };
|
|
11975
|
+
patchMultiple({ conditions: newConds });
|
|
11976
|
+
});
|
|
11977
|
+
const removeBtn = createElement("button", {
|
|
11978
|
+
type: "button",
|
|
11979
|
+
className: "text-xs text-red-500 hover:text-red-700 dark:text-red-400",
|
|
11980
|
+
text: "\u2212 Remove",
|
|
11981
|
+
onclick: () => {
|
|
11982
|
+
const freshCfg = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id)?.formulaConfig ?? cfg;
|
|
11983
|
+
const newConds = [...freshCfg.multiple?.conditions ?? []];
|
|
11984
|
+
newConds.splice(idx, 1);
|
|
11985
|
+
patchMultiple({ conditions: newConds });
|
|
11986
|
+
this.render();
|
|
11987
|
+
}
|
|
11988
|
+
});
|
|
11989
|
+
row.appendChild(whenLabel);
|
|
11990
|
+
row.appendChild(valueInput);
|
|
11991
|
+
row.appendChild(exprLabel);
|
|
11992
|
+
row.appendChild(condTextarea);
|
|
11993
|
+
row.appendChild(removeBtn);
|
|
11994
|
+
body.appendChild(row);
|
|
11995
|
+
});
|
|
11996
|
+
body.appendChild(createElement("button", {
|
|
11997
|
+
type: "button",
|
|
11998
|
+
className: "w-full mb-3 py-1.5 text-sm border border-dashed border-gray-300 dark:border-gray-600 rounded-md text-gray-500 dark:text-gray-400 hover:border-[#635bff] hover:text-[#635bff] transition-colors",
|
|
11999
|
+
text: "+ Add Condition",
|
|
12000
|
+
onclick: () => {
|
|
12001
|
+
const freshCfg = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id)?.formulaConfig ?? cfg;
|
|
12002
|
+
const newConds = [...freshCfg.multiple?.conditions ?? [], { value: "", expression: "" }];
|
|
12003
|
+
patchMultiple({ conditions: newConds });
|
|
12004
|
+
this.render();
|
|
12005
|
+
}
|
|
12006
|
+
}));
|
|
12007
|
+
const fallbackGroup = createElement("div", { className: "mb-3" });
|
|
12008
|
+
fallbackGroup.appendChild(createElement("label", {
|
|
12009
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
12010
|
+
text: "Fallback Expression"
|
|
12011
|
+
}));
|
|
12012
|
+
const fallbackTextarea = createElement("textarea", {
|
|
12013
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent font-mono text-sm resize-none",
|
|
12014
|
+
placeholder: "e.g. 0",
|
|
12015
|
+
rows: "2"
|
|
12016
|
+
});
|
|
12017
|
+
fallbackTextarea.value = cfg.multiple?.fallbackExpression ?? "";
|
|
12018
|
+
fallbackTextarea.addEventListener("focus", () => {
|
|
12019
|
+
lastFocusedExprTextarea = fallbackTextarea;
|
|
12020
|
+
});
|
|
12021
|
+
fallbackTextarea.addEventListener("input", () => {
|
|
12022
|
+
patchMultiple({ fallbackExpression: fallbackTextarea.value });
|
|
12023
|
+
});
|
|
12024
|
+
fallbackGroup.appendChild(fallbackTextarea);
|
|
12025
|
+
body.appendChild(fallbackGroup);
|
|
12026
|
+
}
|
|
12027
|
+
if (availableFields.length > 0) {
|
|
12028
|
+
const insertGroup = createElement("div", { className: "mb-3" });
|
|
12029
|
+
insertGroup.appendChild(createElement("label", {
|
|
12030
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
12031
|
+
text: "Insert Field Reference"
|
|
12032
|
+
}));
|
|
12033
|
+
const insertSelect = createElement("select", {
|
|
12034
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
12035
|
+
onchange: (e) => {
|
|
12036
|
+
const sel = e.target;
|
|
12037
|
+
const ref = sel.value;
|
|
12038
|
+
if (!ref)
|
|
12039
|
+
return;
|
|
12040
|
+
const ta = lastFocusedExprTextarea;
|
|
12041
|
+
if (ta) {
|
|
12042
|
+
const start = ta.selectionStart ?? ta.value.length;
|
|
12043
|
+
const end = ta.selectionEnd ?? ta.value.length;
|
|
12044
|
+
const before = ta.value.slice(0, start);
|
|
12045
|
+
const after = ta.value.slice(end);
|
|
12046
|
+
const pad = before.length > 0 && !/\s$/.test(before) ? " " : "";
|
|
12047
|
+
ta.value = before + pad + ref + after;
|
|
12048
|
+
const newPos = before.length + pad.length + ref.length;
|
|
12049
|
+
ta.selectionStart = ta.selectionEnd = newPos;
|
|
12050
|
+
ta.dispatchEvent(new Event("input", { bubbles: true }));
|
|
12051
|
+
ta.focus();
|
|
12052
|
+
}
|
|
12053
|
+
sel.value = "";
|
|
12054
|
+
}
|
|
12055
|
+
});
|
|
12056
|
+
insertSelect.appendChild(createElement("option", { value: "", text: "Select field to insert\u2026", selected: true }));
|
|
12057
|
+
availableFields.forEach((f) => {
|
|
12058
|
+
insertSelect.appendChild(createElement("option", {
|
|
12059
|
+
value: `{${f.fieldName}}`,
|
|
12060
|
+
text: `${f.label} ({${f.fieldName}})`
|
|
12061
|
+
}));
|
|
12062
|
+
});
|
|
12063
|
+
insertGroup.appendChild(insertSelect);
|
|
12064
|
+
body.appendChild(insertGroup);
|
|
12065
|
+
}
|
|
12066
|
+
body.appendChild(createElement("label", {
|
|
12067
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
12068
|
+
text: "Math Helpers"
|
|
12069
|
+
}));
|
|
12070
|
+
const mathWrap = createElement("div", { className: "flex flex-wrap gap-1 mb-3" });
|
|
12071
|
+
const mathOps = [
|
|
12072
|
+
{ text: "+", insert: " + " },
|
|
12073
|
+
{ text: "-", insert: " - " },
|
|
12074
|
+
{ text: "*", insert: " * " },
|
|
12075
|
+
{ text: "/", insert: " / " },
|
|
12076
|
+
{ text: "(", insert: "(" },
|
|
12077
|
+
{ text: ")", insert: ")" },
|
|
12078
|
+
{ text: "ROUND", insert: "ROUND(" },
|
|
12079
|
+
{ text: "ABS", insert: "ABS(" },
|
|
12080
|
+
{ text: "MIN", insert: "MIN(" },
|
|
12081
|
+
{ text: "MAX", insert: "MAX(" },
|
|
12082
|
+
{ text: "FLOOR", insert: "FLOOR(" },
|
|
12083
|
+
{ text: "CEIL", insert: "CEIL(" }
|
|
12084
|
+
];
|
|
12085
|
+
mathOps.forEach((op) => {
|
|
12086
|
+
mathWrap.appendChild(createElement("button", {
|
|
12087
|
+
type: "button",
|
|
12088
|
+
className: "px-2 py-0.5 text-xs bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700 rounded font-mono hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors",
|
|
12089
|
+
text: op.text,
|
|
12090
|
+
onclick: () => {
|
|
12091
|
+
const ta = lastFocusedExprTextarea;
|
|
12092
|
+
if (!ta)
|
|
12093
|
+
return;
|
|
12094
|
+
const start = ta.selectionStart ?? ta.value.length;
|
|
12095
|
+
const end = ta.selectionEnd ?? ta.value.length;
|
|
12096
|
+
ta.value = ta.value.slice(0, start) + op.insert + ta.value.slice(end);
|
|
12097
|
+
const newPos = start + op.insert.length;
|
|
12098
|
+
ta.selectionStart = ta.selectionEnd = newPos;
|
|
12099
|
+
ta.dispatchEvent(new Event("input", { bubbles: true }));
|
|
12100
|
+
ta.focus();
|
|
12101
|
+
}
|
|
12102
|
+
}));
|
|
12103
|
+
});
|
|
12104
|
+
body.appendChild(mathWrap);
|
|
12105
|
+
const dpGroup = createElement("div", { className: "mb-3" });
|
|
12106
|
+
dpGroup.appendChild(createElement("label", {
|
|
12107
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
12108
|
+
text: "Decimal Places"
|
|
12109
|
+
}));
|
|
12110
|
+
dpGroup.appendChild(createElement("input", {
|
|
12111
|
+
type: "number",
|
|
12112
|
+
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
12113
|
+
value: String(cfg.decimalPlaces ?? 2),
|
|
12114
|
+
min: "0",
|
|
12115
|
+
max: "10",
|
|
12116
|
+
placeholder: "2",
|
|
12117
|
+
oninput: (e) => {
|
|
12118
|
+
const v = e.target.value;
|
|
12119
|
+
patchFormulaConfig({ decimalPlaces: v !== "" ? parseInt(v, 10) : 2 });
|
|
12120
|
+
}
|
|
12121
|
+
}));
|
|
12122
|
+
body.appendChild(dpGroup);
|
|
12123
|
+
const zeroValues = {};
|
|
12124
|
+
availableFields.forEach((f) => {
|
|
12125
|
+
zeroValues[f.fieldName] = 0;
|
|
12126
|
+
zeroValues[f.id] = 0;
|
|
12127
|
+
});
|
|
12128
|
+
let previewText = "\u2014";
|
|
12129
|
+
try {
|
|
12130
|
+
const previewResult = evaluateFormulaConfig(cfg, zeroValues);
|
|
12131
|
+
if (!previewResult.error && !isNaN(previewResult.result)) {
|
|
12132
|
+
previewText = previewResult.result.toFixed(cfg.decimalPlaces ?? 2);
|
|
12133
|
+
} else if (previewResult.error) {
|
|
12134
|
+
previewText = `Error: ${previewResult.error}`;
|
|
12135
|
+
}
|
|
12136
|
+
} catch {
|
|
12137
|
+
}
|
|
12138
|
+
const previewSection = createElement("div", { className: "mb-4" });
|
|
12139
|
+
previewSection.appendChild(createElement("label", {
|
|
12140
|
+
className: "block text-xs font-semibold text-gray-500 uppercase tracking-wider mb-1",
|
|
12141
|
+
text: "Preview (fields = 0)"
|
|
12142
|
+
}));
|
|
12143
|
+
previewSection.appendChild(createElement("div", {
|
|
12144
|
+
className: "px-3 py-2 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md text-sm font-mono text-gray-700 dark:text-gray-300",
|
|
12145
|
+
text: previewText
|
|
12146
|
+
}));
|
|
12147
|
+
body.appendChild(previewSection);
|
|
12148
|
+
}
|
|
12149
|
+
if (selectedField.type !== "image" && selectedField.type !== "formula") {
|
|
11353
12150
|
const placeholderGroup = createElement("div");
|
|
11354
12151
|
placeholderGroup.appendChild(createElement("label", { className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1", text: "Placeholder" }));
|
|
11355
12152
|
placeholderGroup.appendChild(createElement("input", {
|
|
@@ -12831,10 +13628,15 @@ exports.builderToPlatform = builderToPlatform;
|
|
|
12831
13628
|
exports.cleanFormSchema = cleanFormSchema;
|
|
12832
13629
|
exports.convertValidationObjectToArray = convertValidationObjectToArray;
|
|
12833
13630
|
exports.detectCircularDependency = detectCircularDependency;
|
|
13631
|
+
exports.detectFormulaFieldCircularDependency = detectFormulaFieldCircularDependency;
|
|
12834
13632
|
exports.evaluateFormula = evaluateFormula;
|
|
13633
|
+
exports.evaluateFormulaConfig = evaluateFormulaConfig;
|
|
13634
|
+
exports.evaluateFormulaExpression = evaluateFormulaExpression;
|
|
13635
|
+
exports.extractBracketFields = extractBracketFields;
|
|
12835
13636
|
exports.formStore = formStore;
|
|
12836
13637
|
exports.generateName = generateName;
|
|
12837
13638
|
exports.getColSpanFromWidth = getColSpanFromWidth;
|
|
13639
|
+
exports.getFieldsForFormula = getFieldsForFormula;
|
|
12838
13640
|
exports.getNumericFieldsForFormula = getNumericFieldsForFormula;
|
|
12839
13641
|
exports.getValidationConfigForAngular = getValidationConfigForAngular;
|
|
12840
13642
|
exports.initFormBuilder = initFormBuilder;
|
|
@@ -12843,5 +13645,6 @@ exports.parseWidth = parseWidth;
|
|
|
12843
13645
|
exports.platformToBuilder = platformToBuilder;
|
|
12844
13646
|
exports.resetNameGeneratorCounter = resetNameGeneratorCounter;
|
|
12845
13647
|
exports.validateFormula = validateFormula;
|
|
13648
|
+
exports.validateFormulaExpression = validateFormulaExpression;
|
|
12846
13649
|
//# sourceMappingURL=out.js.map
|
|
12847
13650
|
//# sourceMappingURL=index.js.map
|