form-builder-pro 1.4.1 → 1.4.3
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 +65 -3
- package/dist/index.d.mts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +950 -130
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +950 -130
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -4747,8 +4747,31 @@ function transformField(field) {
|
|
|
4747
4747
|
transformed.repeatIncrementEnabled = field.repeatIncrementEnabled;
|
|
4748
4748
|
if (field.dateConstraints !== void 0)
|
|
4749
4749
|
transformed.dateConstraints = field.dateConstraints;
|
|
4750
|
-
if (field.formulaConfig !== void 0)
|
|
4750
|
+
if (field.formulaConfig !== void 0) {
|
|
4751
4751
|
transformed.formulaConfig = field.formulaConfig;
|
|
4752
|
+
} else if (field.type === "formula" && field.formulaType) {
|
|
4753
|
+
const dp = field.floatingPrecision ?? 2;
|
|
4754
|
+
if (field.formulaType === "SIMPLE") {
|
|
4755
|
+
transformed.formulaConfig = {
|
|
4756
|
+
mode: "single",
|
|
4757
|
+
single: { expression: field.formulaExpression ?? "" },
|
|
4758
|
+
multiple: { compareField: "", conditions: [], fallbackExpression: "" },
|
|
4759
|
+
decimalPlaces: dp
|
|
4760
|
+
};
|
|
4761
|
+
} else if (field.formulaType === "SWITCH") {
|
|
4762
|
+
const cases = Array.isArray(field.switchCases) ? field.switchCases.map((c) => ({ value: c.matchValue ?? c.value ?? "", expression: c.formulaExpression ?? c.expression ?? "" })) : [];
|
|
4763
|
+
transformed.formulaConfig = {
|
|
4764
|
+
mode: "multiple",
|
|
4765
|
+
single: { expression: "" },
|
|
4766
|
+
multiple: {
|
|
4767
|
+
compareField: field.switchField ?? "",
|
|
4768
|
+
conditions: cases,
|
|
4769
|
+
fallbackExpression: field.switchDefaultFormula ?? ""
|
|
4770
|
+
},
|
|
4771
|
+
decimalPlaces: dp
|
|
4772
|
+
};
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4752
4775
|
if (field.nameGeneratorFormat !== void 0)
|
|
4753
4776
|
transformed.nameGeneratorFormat = field.nameGeneratorFormat;
|
|
4754
4777
|
if (field.nameGeneratorText !== void 0)
|
|
@@ -4759,6 +4782,13 @@ function transformField(field) {
|
|
|
4759
4782
|
transformed.nameGeneratorSuffix = field.nameGeneratorSuffix;
|
|
4760
4783
|
if (field.nameGeneratorIdPadding !== void 0)
|
|
4761
4784
|
transformed.nameGeneratorIdPadding = field.nameGeneratorIdPadding;
|
|
4785
|
+
if (field.autoPopulateFields !== void 0 && field.autoPopulateFields !== null) {
|
|
4786
|
+
const apf = field.autoPopulateFields;
|
|
4787
|
+
transformed.autoPopulateFields = {
|
|
4788
|
+
enabled: typeof apf.enabled === "boolean" ? apf.enabled : false,
|
|
4789
|
+
fields: Array.isArray(apf.fields) ? apf.fields : []
|
|
4790
|
+
};
|
|
4791
|
+
}
|
|
4762
4792
|
if (field.css !== void 0)
|
|
4763
4793
|
transformed.css = field.css;
|
|
4764
4794
|
if (field.optionsSource !== void 0)
|
|
@@ -5017,6 +5047,12 @@ function fieldToPayload(field, opts) {
|
|
|
5017
5047
|
parentFieldName: field.lookupParentFieldName ?? null
|
|
5018
5048
|
};
|
|
5019
5049
|
}
|
|
5050
|
+
if (field.optionSource === "LOOKUP" && field.autoPopulateFields !== void 0 && field.autoPopulateFields !== null) {
|
|
5051
|
+
payload.autoPopulateFields = {
|
|
5052
|
+
enabled: field.autoPopulateFields.enabled,
|
|
5053
|
+
fields: Array.isArray(field.autoPopulateFields.fields) ? field.autoPopulateFields.fields : []
|
|
5054
|
+
};
|
|
5055
|
+
}
|
|
5020
5056
|
if (field.isd !== void 0)
|
|
5021
5057
|
payload.isd = field.isd;
|
|
5022
5058
|
if (field.imageUrl !== void 0)
|
|
@@ -5066,8 +5102,32 @@ function fieldToPayload(field, opts) {
|
|
|
5066
5102
|
if (field.type === "formula") {
|
|
5067
5103
|
payload.fieldType = "FORMULA";
|
|
5068
5104
|
payload.type = "formula";
|
|
5069
|
-
|
|
5070
|
-
|
|
5105
|
+
payload.readOnly = true;
|
|
5106
|
+
payload.floatingPrecision = field.formulaConfig?.decimalPlaces ?? 2;
|
|
5107
|
+
if (field.formulaConfig?.mode === "single") {
|
|
5108
|
+
payload.formulaType = "SIMPLE";
|
|
5109
|
+
payload.formulaExpression = field.formulaConfig.single?.expression ?? "";
|
|
5110
|
+
payload.switchField = null;
|
|
5111
|
+
payload.switchCases = null;
|
|
5112
|
+
payload.switchDefaultFormula = null;
|
|
5113
|
+
} else if (field.formulaConfig?.mode === "multiple") {
|
|
5114
|
+
const multi = field.formulaConfig.multiple;
|
|
5115
|
+
payload.formulaType = "SWITCH";
|
|
5116
|
+
payload.formulaExpression = "";
|
|
5117
|
+
payload.switchField = multi?.compareField ?? null;
|
|
5118
|
+
payload.switchCases = (multi?.conditions ?? []).map((c) => ({
|
|
5119
|
+
matchValue: c.value,
|
|
5120
|
+
formulaExpression: c.expression ?? ""
|
|
5121
|
+
}));
|
|
5122
|
+
payload.switchDefaultFormula = multi?.fallbackExpression ?? "";
|
|
5123
|
+
payload.switchCases = payload.switchCases;
|
|
5124
|
+
} else {
|
|
5125
|
+
payload.formulaType = "SIMPLE";
|
|
5126
|
+
payload.formulaExpression = "";
|
|
5127
|
+
payload.switchField = null;
|
|
5128
|
+
payload.switchCases = null;
|
|
5129
|
+
payload.switchDefaultFormula = null;
|
|
5130
|
+
}
|
|
5071
5131
|
}
|
|
5072
5132
|
if (field.type === "repeater") {
|
|
5073
5133
|
payload.fieldType = "REPEATER";
|
|
@@ -10505,9 +10565,647 @@ var SectionList = class {
|
|
|
10505
10565
|
}
|
|
10506
10566
|
};
|
|
10507
10567
|
|
|
10568
|
+
// src/utils/formulaTokenParser.ts
|
|
10569
|
+
var FUNCTION_NAMES = /* @__PURE__ */ new Set(["ROUND", "ABS", "MIN", "MAX", "FLOOR", "CEIL", "SQRT", "POW"]);
|
|
10570
|
+
var OPERATOR_CHARS = /* @__PURE__ */ new Set(["+", "-", "*", "/"]);
|
|
10571
|
+
function parseExpressionToTokens(expression, fieldMap, mode) {
|
|
10572
|
+
if (!expression)
|
|
10573
|
+
return [];
|
|
10574
|
+
const tokens = [];
|
|
10575
|
+
let i = 0;
|
|
10576
|
+
const len = expression.length;
|
|
10577
|
+
while (i < len) {
|
|
10578
|
+
const ch = expression[i];
|
|
10579
|
+
if (/\s/.test(ch)) {
|
|
10580
|
+
let ws = "";
|
|
10581
|
+
while (i < len && /\s/.test(expression[i]))
|
|
10582
|
+
ws += expression[i++];
|
|
10583
|
+
tokens.push({ type: "space", value: ws, rawValue: ws });
|
|
10584
|
+
continue;
|
|
10585
|
+
}
|
|
10586
|
+
if (mode === "bracket" && ch === "{") {
|
|
10587
|
+
i++;
|
|
10588
|
+
let ref = "";
|
|
10589
|
+
while (i < len && expression[i] !== "}")
|
|
10590
|
+
ref += expression[i++];
|
|
10591
|
+
if (i < len && expression[i] === "}")
|
|
10592
|
+
i++;
|
|
10593
|
+
const trimmedRef = ref.trim();
|
|
10594
|
+
const field = fieldMap.get(trimmedRef);
|
|
10595
|
+
tokens.push({
|
|
10596
|
+
type: "field",
|
|
10597
|
+
value: field ? field.label || field.fieldName || trimmedRef : trimmedRef,
|
|
10598
|
+
rawValue: `{${trimmedRef}}`,
|
|
10599
|
+
fieldRef: trimmedRef
|
|
10600
|
+
});
|
|
10601
|
+
continue;
|
|
10602
|
+
}
|
|
10603
|
+
if (OPERATOR_CHARS.has(ch)) {
|
|
10604
|
+
tokens.push({ type: "operator", value: ch, rawValue: ch });
|
|
10605
|
+
i++;
|
|
10606
|
+
continue;
|
|
10607
|
+
}
|
|
10608
|
+
if (ch === "%") {
|
|
10609
|
+
tokens.push({ type: "percent", value: "%", rawValue: "%" });
|
|
10610
|
+
i++;
|
|
10611
|
+
continue;
|
|
10612
|
+
}
|
|
10613
|
+
if (ch === "(" || ch === ")") {
|
|
10614
|
+
tokens.push({ type: "paren", value: ch, rawValue: ch });
|
|
10615
|
+
i++;
|
|
10616
|
+
continue;
|
|
10617
|
+
}
|
|
10618
|
+
if (ch === ",") {
|
|
10619
|
+
tokens.push({ type: "comma", value: ",", rawValue: "," });
|
|
10620
|
+
i++;
|
|
10621
|
+
continue;
|
|
10622
|
+
}
|
|
10623
|
+
if (/[0-9]/.test(ch) || ch === "." && i + 1 < len && /[0-9]/.test(expression[i + 1])) {
|
|
10624
|
+
let num = "";
|
|
10625
|
+
while (i < len && /[0-9.]/.test(expression[i]))
|
|
10626
|
+
num += expression[i++];
|
|
10627
|
+
tokens.push({ type: "number", value: num, rawValue: num });
|
|
10628
|
+
continue;
|
|
10629
|
+
}
|
|
10630
|
+
if (/[a-zA-Z_]/.test(ch)) {
|
|
10631
|
+
let ident = "";
|
|
10632
|
+
while (i < len && /[a-zA-Z0-9_]/.test(expression[i]))
|
|
10633
|
+
ident += expression[i++];
|
|
10634
|
+
if (FUNCTION_NAMES.has(ident.toUpperCase())) {
|
|
10635
|
+
tokens.push({ type: "function", value: ident, rawValue: ident });
|
|
10636
|
+
continue;
|
|
10637
|
+
}
|
|
10638
|
+
if (mode === "plain") {
|
|
10639
|
+
const field = fieldMap.get(ident);
|
|
10640
|
+
tokens.push({
|
|
10641
|
+
type: "field",
|
|
10642
|
+
value: field ? field.label || field.fieldName || ident : ident,
|
|
10643
|
+
rawValue: ident,
|
|
10644
|
+
fieldRef: ident
|
|
10645
|
+
});
|
|
10646
|
+
continue;
|
|
10647
|
+
}
|
|
10648
|
+
tokens.push({ type: "unknown", value: ident, rawValue: ident });
|
|
10649
|
+
continue;
|
|
10650
|
+
}
|
|
10651
|
+
tokens.push({ type: "unknown", value: ch, rawValue: ch });
|
|
10652
|
+
i++;
|
|
10653
|
+
}
|
|
10654
|
+
return tokens;
|
|
10655
|
+
}
|
|
10656
|
+
function buildFieldMap(fields) {
|
|
10657
|
+
const map = /* @__PURE__ */ new Map();
|
|
10658
|
+
for (const f of fields) {
|
|
10659
|
+
if (f.fieldName)
|
|
10660
|
+
map.set(f.fieldName, f);
|
|
10661
|
+
if (f.id && f.id !== f.fieldName)
|
|
10662
|
+
map.set(f.id, f);
|
|
10663
|
+
}
|
|
10664
|
+
return map;
|
|
10665
|
+
}
|
|
10666
|
+
function getFieldRef(field) {
|
|
10667
|
+
return field.fieldName && field.fieldName !== field.id ? field.fieldName : field.id;
|
|
10668
|
+
}
|
|
10669
|
+
|
|
10670
|
+
// src/builder/FormulaEditorWidget.ts
|
|
10671
|
+
var _stylesInjected = false;
|
|
10672
|
+
var STYLE_ID = "formula-editor-widget-styles-v2";
|
|
10673
|
+
function injectStyles() {
|
|
10674
|
+
if (_stylesInjected && document.getElementById(STYLE_ID))
|
|
10675
|
+
return;
|
|
10676
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
10677
|
+
document.getElementById("formula-editor-widget-styles")?.remove();
|
|
10678
|
+
_stylesInjected = true;
|
|
10679
|
+
const style = document.createElement("style");
|
|
10680
|
+
style.id = STYLE_ID;
|
|
10681
|
+
style.textContent = `
|
|
10682
|
+
/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
10683
|
+
FormulaEditorWidget v2
|
|
10684
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
|
|
10685
|
+
|
|
10686
|
+
/* \u2500\u2500 Container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
10687
|
+
.few-container {
|
|
10688
|
+
position: relative;
|
|
10689
|
+
width: 100%;
|
|
10690
|
+
}
|
|
10691
|
+
|
|
10692
|
+
/* \u2500\u2500 Editor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
10693
|
+
/*
|
|
10694
|
+
* NOTE: color uses !important so it is never muted by
|
|
10695
|
+
* Angular ViewEncapsulation, host-element rules, or global resets.
|
|
10696
|
+
* This ensures operators/numbers always render at full text contrast.
|
|
10697
|
+
*/
|
|
10698
|
+
.few-editor {
|
|
10699
|
+
display: block;
|
|
10700
|
+
width: 100%;
|
|
10701
|
+
min-height: 38px;
|
|
10702
|
+
padding: 6px 32px 6px 12px; /* right padding reserves space for clear btn */
|
|
10703
|
+
border: 1px solid #e2e8f0;
|
|
10704
|
+
border-radius: 6px;
|
|
10705
|
+
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, monospace;
|
|
10706
|
+
font-size: 13px;
|
|
10707
|
+
font-weight: 700 !important; /* \u2190 operators/numbers always bold */
|
|
10708
|
+
line-height: 1.7;
|
|
10709
|
+
color: rgb(213, 12, 65) !important; /* operator/text color \u2014 chips override with their own !important */
|
|
10710
|
+
caret-color: #635bff;
|
|
10711
|
+
background: transparent;
|
|
10712
|
+
outline: none;
|
|
10713
|
+
word-break: break-word;
|
|
10714
|
+
white-space: pre-wrap;
|
|
10715
|
+
cursor: text;
|
|
10716
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
10717
|
+
box-sizing: border-box;
|
|
10718
|
+
}
|
|
10719
|
+
|
|
10720
|
+
.few-editor:focus {
|
|
10721
|
+
border-color: #635bff;
|
|
10722
|
+
box-shadow: 0 0 0 3px rgba(99, 91, 255, 0.15);
|
|
10723
|
+
}
|
|
10724
|
+
|
|
10725
|
+
.few-editor.few-has-error {
|
|
10726
|
+
border-color: #ef4444;
|
|
10727
|
+
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.12);
|
|
10728
|
+
}
|
|
10729
|
+
|
|
10730
|
+
/* \u2500\u2500 Placeholder \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
10731
|
+
.few-placeholder {
|
|
10732
|
+
position: absolute;
|
|
10733
|
+
top: 0;
|
|
10734
|
+
left: 0;
|
|
10735
|
+
right: 32px; /* don't overlap clear button */
|
|
10736
|
+
bottom: 0;
|
|
10737
|
+
padding: 6px 0 6px 12px;
|
|
10738
|
+
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, monospace;
|
|
10739
|
+
font-size: 13px;
|
|
10740
|
+
line-height: 1.7;
|
|
10741
|
+
color: #94a3b8;
|
|
10742
|
+
pointer-events: none;
|
|
10743
|
+
user-select: none;
|
|
10744
|
+
white-space: nowrap;
|
|
10745
|
+
overflow: hidden;
|
|
10746
|
+
text-overflow: ellipsis;
|
|
10747
|
+
}
|
|
10748
|
+
|
|
10749
|
+
/* \u2500\u2500 Field chip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
10750
|
+
.few-chip {
|
|
10751
|
+
display: inline-flex;
|
|
10752
|
+
align-items: center;
|
|
10753
|
+
padding: 0px 7px 1px 6px;
|
|
10754
|
+
margin: 0 2px;
|
|
10755
|
+
font-size: 11.5px;
|
|
10756
|
+
font-weight: 600 !important; /* explicit !important so the editor's 700 !important doesn't cascade in */
|
|
10757
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
10758
|
+
letter-spacing: 0.01em;
|
|
10759
|
+
border-radius: 4px;
|
|
10760
|
+
background: rgba(99, 91, 255, 0.10);
|
|
10761
|
+
color: #635bff !important; /* chip label always purple \u2014 not inherited */
|
|
10762
|
+
border: 1px solid rgba(99, 91, 255, 0.28);
|
|
10763
|
+
cursor: default;
|
|
10764
|
+
user-select: none;
|
|
10765
|
+
vertical-align: middle;
|
|
10766
|
+
line-height: 1.6;
|
|
10767
|
+
white-space: nowrap;
|
|
10768
|
+
transition: background 0.1s;
|
|
10769
|
+
}
|
|
10770
|
+
|
|
10771
|
+
.few-chip:hover {
|
|
10772
|
+
background: rgba(99, 91, 255, 0.17);
|
|
10773
|
+
}
|
|
10774
|
+
|
|
10775
|
+
.few-chip-unknown {
|
|
10776
|
+
background: rgba(239, 68, 68, 0.08);
|
|
10777
|
+
color: #dc2626 !important;
|
|
10778
|
+
border-color: rgba(239, 68, 68, 0.25);
|
|
10779
|
+
}
|
|
10780
|
+
|
|
10781
|
+
/* \u2500\u2500 Clear (\xD7) button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
10782
|
+
.few-clear-btn {
|
|
10783
|
+
position: absolute;
|
|
10784
|
+
top: 50%;
|
|
10785
|
+
right: 7px;
|
|
10786
|
+
transform: translateY(-50%);
|
|
10787
|
+
display: flex;
|
|
10788
|
+
align-items: center;
|
|
10789
|
+
justify-content: center;
|
|
10790
|
+
width: 18px;
|
|
10791
|
+
height: 18px;
|
|
10792
|
+
padding: 0;
|
|
10793
|
+
border: none;
|
|
10794
|
+
border-radius: 50%;
|
|
10795
|
+
background: #94a3b8;
|
|
10796
|
+
color: #ffffff;
|
|
10797
|
+
font-size: 12px;
|
|
10798
|
+
line-height: 1;
|
|
10799
|
+
font-weight: 700;
|
|
10800
|
+
cursor: pointer;
|
|
10801
|
+
opacity: 0;
|
|
10802
|
+
pointer-events: none;
|
|
10803
|
+
transition: opacity 0.15s, background 0.15s;
|
|
10804
|
+
z-index: 2;
|
|
10805
|
+
}
|
|
10806
|
+
|
|
10807
|
+
.few-clear-btn:hover {
|
|
10808
|
+
background: #475569;
|
|
10809
|
+
}
|
|
10810
|
+
|
|
10811
|
+
/* Shown only when the editor has content */
|
|
10812
|
+
.few-container.few-has-content .few-clear-btn {
|
|
10813
|
+
opacity: 1;
|
|
10814
|
+
pointer-events: auto;
|
|
10815
|
+
}
|
|
10816
|
+
|
|
10817
|
+
/* \u2500\u2500 Dark mode (media query) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
10818
|
+
@media (prefers-color-scheme: dark) {
|
|
10819
|
+
.few-editor {
|
|
10820
|
+
border-color: #334155;
|
|
10821
|
+
/* operator color is set via inline !important \u2014 not overridden here */
|
|
10822
|
+
}
|
|
10823
|
+
.few-editor:focus {
|
|
10824
|
+
border-color: #635bff;
|
|
10825
|
+
box-shadow: 0 0 0 3px rgba(99, 91, 255, 0.25);
|
|
10826
|
+
}
|
|
10827
|
+
.few-placeholder { color: #64748b; }
|
|
10828
|
+
.few-chip {
|
|
10829
|
+
background: rgba(99, 91, 255, 0.18);
|
|
10830
|
+
color: #a5b4fc !important;
|
|
10831
|
+
border-color: rgba(99, 91, 255, 0.38);
|
|
10832
|
+
}
|
|
10833
|
+
.few-chip-unknown {
|
|
10834
|
+
background: rgba(239, 68, 68, 0.15);
|
|
10835
|
+
color: #f87171 !important;
|
|
10836
|
+
border-color: rgba(239, 68, 68, 0.35);
|
|
10837
|
+
}
|
|
10838
|
+
.few-clear-btn {
|
|
10839
|
+
background: #64748b;
|
|
10840
|
+
color: #f1f5f9;
|
|
10841
|
+
}
|
|
10842
|
+
.few-clear-btn:hover { background: #475569; }
|
|
10843
|
+
}
|
|
10844
|
+
|
|
10845
|
+
/* \u2500\u2500 Dark mode (Tailwind class strategy) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
10846
|
+
.dark .few-editor {
|
|
10847
|
+
border-color: #334155;
|
|
10848
|
+
/* operator color is set via inline !important \u2014 not overridden here */
|
|
10849
|
+
}
|
|
10850
|
+
.dark .few-editor:focus {
|
|
10851
|
+
border-color: #635bff;
|
|
10852
|
+
box-shadow: 0 0 0 3px rgba(99, 91, 255, 0.25);
|
|
10853
|
+
}
|
|
10854
|
+
.dark .few-placeholder { color: #64748b; }
|
|
10855
|
+
.dark .few-chip {
|
|
10856
|
+
background: rgba(99, 91, 255, 0.18);
|
|
10857
|
+
color: #a5b4fc !important;
|
|
10858
|
+
border-color: rgba(99, 91, 255, 0.38);
|
|
10859
|
+
}
|
|
10860
|
+
.dark .few-chip-unknown {
|
|
10861
|
+
background: rgba(239, 68, 68, 0.15);
|
|
10862
|
+
color: #f87171 !important;
|
|
10863
|
+
border-color: rgba(239, 68, 68, 0.35);
|
|
10864
|
+
}
|
|
10865
|
+
.dark .few-clear-btn {
|
|
10866
|
+
background: #64748b;
|
|
10867
|
+
color: #f1f5f9;
|
|
10868
|
+
}
|
|
10869
|
+
.dark .few-clear-btn:hover { background: #475569; }
|
|
10870
|
+
`;
|
|
10871
|
+
document.head.appendChild(style);
|
|
10872
|
+
}
|
|
10873
|
+
var FormulaEditorWidget = class {
|
|
10874
|
+
constructor(options) {
|
|
10875
|
+
__publicField(this, "_container");
|
|
10876
|
+
__publicField(this, "_editor");
|
|
10877
|
+
__publicField(this, "_placeholderEl");
|
|
10878
|
+
__publicField(this, "_clearBtn");
|
|
10879
|
+
__publicField(this, "_options");
|
|
10880
|
+
__publicField(this, "_fieldMap");
|
|
10881
|
+
/**
|
|
10882
|
+
* The canonical internal expression — SINGLE SOURCE OF TRUTH.
|
|
10883
|
+
* The DOM is always derived from this; never the reverse.
|
|
10884
|
+
* Updated by _onEditorInput() (user edits) and setValue() (programmatic).
|
|
10885
|
+
*/
|
|
10886
|
+
__publicField(this, "_internalValue", "");
|
|
10887
|
+
injectStyles();
|
|
10888
|
+
this._options = { ...options };
|
|
10889
|
+
this._fieldMap = buildFieldMap(options.availableFields);
|
|
10890
|
+
this._container = document.createElement("div");
|
|
10891
|
+
this._container.className = "few-container";
|
|
10892
|
+
this._placeholderEl = document.createElement("div");
|
|
10893
|
+
this._placeholderEl.className = "few-placeholder";
|
|
10894
|
+
this._placeholderEl.textContent = options.placeholder ?? "Enter formula\u2026";
|
|
10895
|
+
this._editor = document.createElement("div");
|
|
10896
|
+
this._editor.className = "few-editor";
|
|
10897
|
+
this._editor.contentEditable = "true";
|
|
10898
|
+
this._editor.spellcheck = false;
|
|
10899
|
+
this._editor.setAttribute("autocomplete", "off");
|
|
10900
|
+
this._editor.setAttribute("autocorrect", "off");
|
|
10901
|
+
this._editor.setAttribute("autocapitalize", "off");
|
|
10902
|
+
this._editor.setAttribute("data-formula-editor", "true");
|
|
10903
|
+
this._editor.style.setProperty("color", "rgb(213, 12, 65)", "important");
|
|
10904
|
+
this._editor.style.setProperty("font-weight", "700", "important");
|
|
10905
|
+
this._editor.style.setProperty("caret-color", "#635bff", "important");
|
|
10906
|
+
this._clearBtn = document.createElement("button");
|
|
10907
|
+
this._clearBtn.type = "button";
|
|
10908
|
+
this._clearBtn.className = "few-clear-btn";
|
|
10909
|
+
this._clearBtn.title = "Clear formula";
|
|
10910
|
+
this._clearBtn.textContent = "\xD7";
|
|
10911
|
+
this._clearBtn.addEventListener("mousedown", (e) => {
|
|
10912
|
+
e.preventDefault();
|
|
10913
|
+
this.clear();
|
|
10914
|
+
});
|
|
10915
|
+
this._container.appendChild(this._placeholderEl);
|
|
10916
|
+
this._container.appendChild(this._editor);
|
|
10917
|
+
this._container.appendChild(this._clearBtn);
|
|
10918
|
+
this._attachEvents();
|
|
10919
|
+
if (options.initialValue) {
|
|
10920
|
+
this.setValue(options.initialValue);
|
|
10921
|
+
} else {
|
|
10922
|
+
this._syncUI();
|
|
10923
|
+
}
|
|
10924
|
+
}
|
|
10925
|
+
// ─── Internal event wiring ───────────────────────────────────────────────
|
|
10926
|
+
_attachEvents() {
|
|
10927
|
+
this._editor.addEventListener("input", () => this._onEditorInput());
|
|
10928
|
+
this._editor.addEventListener("keydown", (e) => {
|
|
10929
|
+
if (e.key === "Enter") {
|
|
10930
|
+
e.preventDefault();
|
|
10931
|
+
return;
|
|
10932
|
+
}
|
|
10933
|
+
if (e.key === "Backspace")
|
|
10934
|
+
this._handleBackspaceOnChip(e);
|
|
10935
|
+
});
|
|
10936
|
+
this._editor.addEventListener("paste", (e) => {
|
|
10937
|
+
e.preventDefault();
|
|
10938
|
+
const text = e.clipboardData?.getData("text/plain") ?? "";
|
|
10939
|
+
if (text) {
|
|
10940
|
+
this._insertTextAtCaret(text);
|
|
10941
|
+
this._onEditorInput();
|
|
10942
|
+
}
|
|
10943
|
+
});
|
|
10944
|
+
this._editor.addEventListener("dragover", (e) => e.preventDefault());
|
|
10945
|
+
this._editor.addEventListener("drop", (e) => e.preventDefault());
|
|
10946
|
+
}
|
|
10947
|
+
// ─── Core edit cycle ─────────────────────────────────────────────────────
|
|
10948
|
+
_onEditorInput() {
|
|
10949
|
+
this._normaliseDom();
|
|
10950
|
+
const newValue = this._readInternalExpression();
|
|
10951
|
+
const changed = newValue !== this._internalValue;
|
|
10952
|
+
this._internalValue = newValue;
|
|
10953
|
+
this._syncUI();
|
|
10954
|
+
if (changed) {
|
|
10955
|
+
this._options.onChange?.(newValue);
|
|
10956
|
+
}
|
|
10957
|
+
}
|
|
10958
|
+
/**
|
|
10959
|
+
* Remove browser-inserted artefacts (<br>, stray <div>/<span>) that some
|
|
10960
|
+
* browsers inject into a contenteditable. Chips (data-field-ref) are kept.
|
|
10961
|
+
*/
|
|
10962
|
+
_normaliseDom() {
|
|
10963
|
+
for (const node of Array.from(this._editor.childNodes)) {
|
|
10964
|
+
if (node.nodeType !== Node.ELEMENT_NODE)
|
|
10965
|
+
continue;
|
|
10966
|
+
const el = node;
|
|
10967
|
+
if (el.hasAttribute("data-field-ref"))
|
|
10968
|
+
continue;
|
|
10969
|
+
if (el.tagName === "BR") {
|
|
10970
|
+
el.remove();
|
|
10971
|
+
continue;
|
|
10972
|
+
}
|
|
10973
|
+
const textNode = document.createTextNode(el.textContent ?? "");
|
|
10974
|
+
this._editor.replaceChild(textNode, el);
|
|
10975
|
+
}
|
|
10976
|
+
}
|
|
10977
|
+
/**
|
|
10978
|
+
* Backspace immediately before a chip should delete the whole chip, not
|
|
10979
|
+
* step into its non-editable content.
|
|
10980
|
+
*/
|
|
10981
|
+
_handleBackspaceOnChip(e) {
|
|
10982
|
+
const sel = window.getSelection();
|
|
10983
|
+
if (!sel || sel.rangeCount === 0)
|
|
10984
|
+
return;
|
|
10985
|
+
const range = sel.getRangeAt(0);
|
|
10986
|
+
if (!range.collapsed)
|
|
10987
|
+
return;
|
|
10988
|
+
let prev = null;
|
|
10989
|
+
if (range.startContainer === this._editor) {
|
|
10990
|
+
if (range.startOffset > 0)
|
|
10991
|
+
prev = this._editor.childNodes[range.startOffset - 1];
|
|
10992
|
+
} else if (range.startContainer.nodeType === Node.TEXT_NODE) {
|
|
10993
|
+
if (range.startOffset === 0)
|
|
10994
|
+
prev = range.startContainer.previousSibling;
|
|
10995
|
+
}
|
|
10996
|
+
if (prev && prev.hasAttribute?.("data-field-ref")) {
|
|
10997
|
+
e.preventDefault();
|
|
10998
|
+
prev.remove();
|
|
10999
|
+
this._onEditorInput();
|
|
11000
|
+
}
|
|
11001
|
+
}
|
|
11002
|
+
// ─── Serialisation: DOM → internal expression ────────────────────────────
|
|
11003
|
+
_readInternalExpression() {
|
|
11004
|
+
let result = "";
|
|
11005
|
+
for (const node of Array.from(this._editor.childNodes)) {
|
|
11006
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
11007
|
+
result += node.textContent ?? "";
|
|
11008
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
11009
|
+
const el = node;
|
|
11010
|
+
const ref = el.getAttribute("data-field-ref");
|
|
11011
|
+
if (ref !== null) {
|
|
11012
|
+
result += this._options.mode === "bracket" ? `{${ref}}` : ref;
|
|
11013
|
+
} else {
|
|
11014
|
+
result += el.textContent ?? "";
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
}
|
|
11018
|
+
return result;
|
|
11019
|
+
}
|
|
11020
|
+
// ─── Deserialization: internal expression → DOM ──────────────────────────
|
|
11021
|
+
/**
|
|
11022
|
+
* Load an internal expression string.
|
|
11023
|
+
*
|
|
11024
|
+
* This is the ONLY path that populates the editor with chips.
|
|
11025
|
+
* It does NOT fire onChange (programmatic set ≠ user edit).
|
|
11026
|
+
*
|
|
11027
|
+
* Design invariant: the DOM is ALWAYS fully rebuilt from _internalValue,
|
|
11028
|
+
* never from user-visible text. This guarantees correct state on re-render
|
|
11029
|
+
* after field switches, undo/redo, or hot-reload.
|
|
11030
|
+
*/
|
|
11031
|
+
setValue(internalExpression) {
|
|
11032
|
+
this._internalValue = internalExpression ?? "";
|
|
11033
|
+
this._editor.innerHTML = "";
|
|
11034
|
+
if (this._internalValue.trim()) {
|
|
11035
|
+
const tokens = parseExpressionToTokens(
|
|
11036
|
+
this._internalValue,
|
|
11037
|
+
this._fieldMap,
|
|
11038
|
+
this._options.mode
|
|
11039
|
+
);
|
|
11040
|
+
for (const token of tokens) {
|
|
11041
|
+
if (token.type === "field" && token.fieldRef !== void 0) {
|
|
11042
|
+
const field = this._fieldMap.get(token.fieldRef) ?? {
|
|
11043
|
+
id: token.fieldRef,
|
|
11044
|
+
fieldName: token.fieldRef,
|
|
11045
|
+
label: token.fieldRef
|
|
11046
|
+
// shows raw ref — still readable
|
|
11047
|
+
};
|
|
11048
|
+
const isUnknown = !this._fieldMap.has(token.fieldRef);
|
|
11049
|
+
this._editor.appendChild(this._createChipEl(field, isUnknown));
|
|
11050
|
+
} else {
|
|
11051
|
+
this._editor.appendChild(document.createTextNode(token.value));
|
|
11052
|
+
}
|
|
11053
|
+
}
|
|
11054
|
+
}
|
|
11055
|
+
this._syncUI();
|
|
11056
|
+
}
|
|
11057
|
+
/** Return the current stored expression. Always reflects live DOM state. */
|
|
11058
|
+
getValue() {
|
|
11059
|
+
return this._readInternalExpression();
|
|
11060
|
+
}
|
|
11061
|
+
// ─── Public mutation ─────────────────────────────────────────────────────
|
|
11062
|
+
/**
|
|
11063
|
+
* Insert a field chip at the caret (or append if editor lacks focus).
|
|
11064
|
+
* Handles spacing automatically so chips never run into adjacent text.
|
|
11065
|
+
*/
|
|
11066
|
+
insertField(field) {
|
|
11067
|
+
this._editor.focus();
|
|
11068
|
+
const chip = this._createChipEl(field, false);
|
|
11069
|
+
const sel = window.getSelection();
|
|
11070
|
+
const inEditor = sel && sel.rangeCount > 0 && this._editor.contains(sel.getRangeAt(0).commonAncestorContainer);
|
|
11071
|
+
if (inEditor) {
|
|
11072
|
+
const range = sel.getRangeAt(0);
|
|
11073
|
+
range.deleteContents();
|
|
11074
|
+
if (this._needsSpaceBefore(range)) {
|
|
11075
|
+
range.insertNode(document.createTextNode(" "));
|
|
11076
|
+
range.collapse(false);
|
|
11077
|
+
}
|
|
11078
|
+
range.insertNode(chip);
|
|
11079
|
+
const spaceAfter = document.createTextNode(" ");
|
|
11080
|
+
range.setStartAfter(chip);
|
|
11081
|
+
range.setEndAfter(chip);
|
|
11082
|
+
range.insertNode(spaceAfter);
|
|
11083
|
+
range.setStartAfter(spaceAfter);
|
|
11084
|
+
range.setEndAfter(spaceAfter);
|
|
11085
|
+
sel.removeAllRanges();
|
|
11086
|
+
sel.addRange(range);
|
|
11087
|
+
} else {
|
|
11088
|
+
this._appendAtEnd(chip);
|
|
11089
|
+
}
|
|
11090
|
+
this._onEditorInput();
|
|
11091
|
+
}
|
|
11092
|
+
/**
|
|
11093
|
+
* Insert plain text at the caret (operators, function names, parens).
|
|
11094
|
+
* Operators rendered by this path are text nodes and always inherit
|
|
11095
|
+
* .few-editor's high-contrast color.
|
|
11096
|
+
*/
|
|
11097
|
+
insertText(text) {
|
|
11098
|
+
this._editor.focus();
|
|
11099
|
+
this._insertTextAtCaret(text);
|
|
11100
|
+
this._onEditorInput();
|
|
11101
|
+
}
|
|
11102
|
+
/**
|
|
11103
|
+
* Clear the entire formula expression.
|
|
11104
|
+
* Fires onChange('') so the store is updated immediately.
|
|
11105
|
+
*/
|
|
11106
|
+
clear() {
|
|
11107
|
+
this._editor.innerHTML = "";
|
|
11108
|
+
this._internalValue = "";
|
|
11109
|
+
this._syncUI();
|
|
11110
|
+
this._options.onChange?.("");
|
|
11111
|
+
}
|
|
11112
|
+
// ─── Visual state ────────────────────────────────────────────────────────
|
|
11113
|
+
/** Toggle error (red border) state. */
|
|
11114
|
+
setError(hasError) {
|
|
11115
|
+
this._editor.classList.toggle("few-has-error", hasError);
|
|
11116
|
+
}
|
|
11117
|
+
/**
|
|
11118
|
+
* Refresh the field map with a new field list (e.g. schema changed).
|
|
11119
|
+
* Re-renders from _internalValue so stale labels / unknowns are resolved.
|
|
11120
|
+
*/
|
|
11121
|
+
updateFields(fields) {
|
|
11122
|
+
this._options.availableFields = fields;
|
|
11123
|
+
this._fieldMap = buildFieldMap(fields);
|
|
11124
|
+
this.setValue(this._internalValue);
|
|
11125
|
+
}
|
|
11126
|
+
/** Return the root element to mount. */
|
|
11127
|
+
getElement() {
|
|
11128
|
+
return this._container;
|
|
11129
|
+
}
|
|
11130
|
+
/** Focus the editable area. */
|
|
11131
|
+
focus() {
|
|
11132
|
+
this._editor.focus();
|
|
11133
|
+
}
|
|
11134
|
+
/** Remove from DOM. */
|
|
11135
|
+
destroy() {
|
|
11136
|
+
this._container.remove();
|
|
11137
|
+
}
|
|
11138
|
+
// ─── Private helpers ─────────────────────────────────────────────────────
|
|
11139
|
+
_insertTextAtCaret(text) {
|
|
11140
|
+
const sel = window.getSelection();
|
|
11141
|
+
const inEditor = sel && sel.rangeCount > 0 && this._editor.contains(sel.getRangeAt(0).commonAncestorContainer);
|
|
11142
|
+
if (inEditor) {
|
|
11143
|
+
const range = sel.getRangeAt(0);
|
|
11144
|
+
range.deleteContents();
|
|
11145
|
+
const tn = document.createTextNode(text);
|
|
11146
|
+
range.insertNode(tn);
|
|
11147
|
+
range.setStartAfter(tn);
|
|
11148
|
+
range.setEndAfter(tn);
|
|
11149
|
+
sel.removeAllRanges();
|
|
11150
|
+
sel.addRange(range);
|
|
11151
|
+
} else {
|
|
11152
|
+
this._editor.appendChild(document.createTextNode(text));
|
|
11153
|
+
}
|
|
11154
|
+
}
|
|
11155
|
+
_appendAtEnd(chip) {
|
|
11156
|
+
const last = this._editor.lastChild;
|
|
11157
|
+
if (last) {
|
|
11158
|
+
if (last.nodeType === Node.TEXT_NODE) {
|
|
11159
|
+
const txt = last.textContent ?? "";
|
|
11160
|
+
if (txt.length > 0 && !/\s$/.test(txt)) {
|
|
11161
|
+
this._editor.appendChild(document.createTextNode(" "));
|
|
11162
|
+
}
|
|
11163
|
+
} else if (last.hasAttribute?.("data-field-ref")) {
|
|
11164
|
+
this._editor.appendChild(document.createTextNode(" "));
|
|
11165
|
+
}
|
|
11166
|
+
}
|
|
11167
|
+
this._editor.appendChild(chip);
|
|
11168
|
+
this._editor.appendChild(document.createTextNode(" "));
|
|
11169
|
+
}
|
|
11170
|
+
_needsSpaceBefore(range) {
|
|
11171
|
+
const { startContainer, startOffset } = range;
|
|
11172
|
+
if (startContainer.nodeType === Node.TEXT_NODE) {
|
|
11173
|
+
const text = startContainer.textContent ?? "";
|
|
11174
|
+
const ch = text[startOffset - 1];
|
|
11175
|
+
return startOffset > 0 && ch !== void 0 && !/[\s(]/.test(ch);
|
|
11176
|
+
}
|
|
11177
|
+
if (startContainer === this._editor && startOffset > 0) {
|
|
11178
|
+
const prev = this._editor.childNodes[startOffset - 1];
|
|
11179
|
+
return prev?.hasAttribute?.("data-field-ref") ?? false;
|
|
11180
|
+
}
|
|
11181
|
+
return false;
|
|
11182
|
+
}
|
|
11183
|
+
_createChipEl(field, isUnknown) {
|
|
11184
|
+
const chip = document.createElement("span");
|
|
11185
|
+
chip.contentEditable = "false";
|
|
11186
|
+
chip.setAttribute("data-field-ref", getFieldRef(field));
|
|
11187
|
+
chip.textContent = field.label || field.fieldName || field.id;
|
|
11188
|
+
chip.className = isUnknown ? "few-chip few-chip-unknown" : "few-chip";
|
|
11189
|
+
chip.title = this._options.mode === "bracket" ? `{${getFieldRef(field)}}` : getFieldRef(field);
|
|
11190
|
+
return chip;
|
|
11191
|
+
}
|
|
11192
|
+
/**
|
|
11193
|
+
* Synchronise all purely-visual state:
|
|
11194
|
+
* - placeholder visibility
|
|
11195
|
+
* - clear-button visibility (few-has-content on container)
|
|
11196
|
+
*/
|
|
11197
|
+
_syncUI() {
|
|
11198
|
+
const hasContent = this._internalValue.trim().length > 0 || !!this._editor.querySelector("[data-field-ref]");
|
|
11199
|
+
this._placeholderEl.style.display = hasContent ? "none" : "block";
|
|
11200
|
+
this._container.classList.toggle("few-has-content", hasContent);
|
|
11201
|
+
this._editor.style.setProperty("color", "rgb(213, 12, 65)", "important");
|
|
11202
|
+
this._editor.style.setProperty("font-weight", "700", "important");
|
|
11203
|
+
}
|
|
11204
|
+
};
|
|
11205
|
+
|
|
10508
11206
|
// src/builder/FormBuilder.ts
|
|
10509
11207
|
var advancedCssPanelState = /* @__PURE__ */ new Map();
|
|
10510
|
-
var
|
|
11208
|
+
var lastFocusedFormulaWidget = null;
|
|
10511
11209
|
var LABEL_DEBOUNCE_MS = 300;
|
|
10512
11210
|
var labelUpdateTimeouts = /* @__PURE__ */ new Map();
|
|
10513
11211
|
var FormBuilder = class {
|
|
@@ -10986,8 +11684,15 @@ var FormBuilder = class {
|
|
|
10986
11684
|
if (field.type !== "formula" || !field.formulaConfig)
|
|
10987
11685
|
continue;
|
|
10988
11686
|
const fcfg = field.formulaConfig;
|
|
10989
|
-
const
|
|
10990
|
-
const
|
|
11687
|
+
const allOtherFields = schema.sections.flatMap((s) => s.fields).filter((f) => f.id !== field.id);
|
|
11688
|
+
const validRefSet = /* @__PURE__ */ new Set();
|
|
11689
|
+
allOtherFields.forEach((f) => {
|
|
11690
|
+
if (f.fieldName)
|
|
11691
|
+
validRefSet.add(f.fieldName);
|
|
11692
|
+
if (f.id)
|
|
11693
|
+
validRefSet.add(f.id);
|
|
11694
|
+
});
|
|
11695
|
+
const validRefs = Array.from(validRefSet);
|
|
10991
11696
|
const exprs = [];
|
|
10992
11697
|
if (fcfg.mode === "single") {
|
|
10993
11698
|
if (fcfg.single?.expression)
|
|
@@ -11001,7 +11706,7 @@ var FormBuilder = class {
|
|
|
11001
11706
|
exprs.push(fcfg.multiple.fallbackExpression);
|
|
11002
11707
|
}
|
|
11003
11708
|
for (const expr of exprs) {
|
|
11004
|
-
const result = validateFormulaExpression(expr,
|
|
11709
|
+
const result = validateFormulaExpression(expr, validRefs);
|
|
11005
11710
|
if (!result.valid) {
|
|
11006
11711
|
alert(`Formula error in "${field.label}": ${result.error}`);
|
|
11007
11712
|
return;
|
|
@@ -11759,38 +12464,64 @@ var FormBuilder = class {
|
|
|
11759
12464
|
const numericFields = getNumericFieldsForFormula(schema, selectedField.id);
|
|
11760
12465
|
const availableIds = numericFields.map((f) => f.id);
|
|
11761
12466
|
const availableNames = numericFields.map((f) => f.fieldName);
|
|
12467
|
+
const allFieldInfosForWidget = schema.sections.flatMap((s) => s.fields).filter((f) => f.id !== selectedField.id).map((f) => ({
|
|
12468
|
+
id: f.id,
|
|
12469
|
+
fieldName: f.fieldName ?? f.id,
|
|
12470
|
+
label: f.label || f.fieldName || f.id
|
|
12471
|
+
}));
|
|
12472
|
+
const numFieldInfos = numericFields.map((f) => ({
|
|
12473
|
+
id: f.id,
|
|
12474
|
+
fieldName: f.fieldName,
|
|
12475
|
+
label: f.label || f.fieldName || f.id
|
|
12476
|
+
}));
|
|
11762
12477
|
const formulaGroup = createElement("div", { className: "mb-3" });
|
|
11763
|
-
formulaGroup.appendChild(createElement("label", {
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
12478
|
+
formulaGroup.appendChild(createElement("label", {
|
|
12479
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
12480
|
+
text: "Formula"
|
|
12481
|
+
}));
|
|
12482
|
+
const numFormulaWidget = new FormulaEditorWidget({
|
|
12483
|
+
mode: "plain",
|
|
12484
|
+
availableFields: allFieldInfosForWidget,
|
|
12485
|
+
// Always read from the store snapshot — guarantees correct reload
|
|
12486
|
+
// after field switch and re-render.
|
|
12487
|
+
initialValue: selectedField.formula || "",
|
|
11768
12488
|
placeholder: "e.g. quantity * price",
|
|
11769
|
-
|
|
11770
|
-
oninput: (e) => {
|
|
11771
|
-
const formula = e.target.value.trim();
|
|
12489
|
+
onChange: (formula) => {
|
|
11772
12490
|
const deps = parseFormulaDependencies(formula);
|
|
11773
|
-
const
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
if (
|
|
11778
|
-
|
|
11779
|
-
|
|
11780
|
-
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
|
|
12491
|
+
const isValid2 = (() => {
|
|
12492
|
+
if (!formula.trim())
|
|
12493
|
+
return true;
|
|
12494
|
+
const v = validateFormula(formula, availableIds, availableNames, selectedField.id);
|
|
12495
|
+
if (!v.valid)
|
|
12496
|
+
return false;
|
|
12497
|
+
return !(deps.length > 0 && detectCircularDependency(schema, selectedField.id, formula, deps));
|
|
12498
|
+
})();
|
|
12499
|
+
numFormulaWidget.setError(!isValid2 && formula.trim().length > 0);
|
|
12500
|
+
if (!isValid2 && formula.trim()) {
|
|
12501
|
+
const v = validateFormula(formula, availableIds, availableNames, selectedField.id);
|
|
12502
|
+
formulaError.textContent = !v.valid ? v.error : "Circular dependency detected";
|
|
12503
|
+
formulaError.classList.remove("hidden");
|
|
12504
|
+
} else {
|
|
12505
|
+
formulaError.textContent = "";
|
|
12506
|
+
formulaError.classList.add("hidden");
|
|
11784
12507
|
}
|
|
11785
12508
|
formStore.getState().updateField(selectedField.id, { formula, dependencies: deps });
|
|
11786
12509
|
}
|
|
11787
12510
|
});
|
|
11788
|
-
|
|
11789
|
-
|
|
12511
|
+
numFormulaWidget.getElement().querySelector("[data-formula-editor]")?.addEventListener("focus", () => {
|
|
12512
|
+
lastFocusedFormulaWidget = numFormulaWidget;
|
|
12513
|
+
});
|
|
12514
|
+
formulaGroup.appendChild(numFormulaWidget.getElement());
|
|
12515
|
+
const formulaError = createElement("div", {
|
|
12516
|
+
className: "text-xs text-red-600 dark:text-red-400 mt-1 formula-error hidden"
|
|
12517
|
+
});
|
|
11790
12518
|
formulaGroup.appendChild(formulaError);
|
|
11791
12519
|
body.appendChild(formulaGroup);
|
|
11792
12520
|
const insertGroup = createElement("div", { className: "mb-3" });
|
|
11793
|
-
insertGroup.appendChild(createElement("label", {
|
|
12521
|
+
insertGroup.appendChild(createElement("label", {
|
|
12522
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
12523
|
+
text: "Insert Field"
|
|
12524
|
+
}));
|
|
11794
12525
|
const insertSelect = createElement("select", {
|
|
11795
12526
|
className: "w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11796
12527
|
onchange: (e) => {
|
|
@@ -11798,34 +12529,58 @@ var FormBuilder = class {
|
|
|
11798
12529
|
const ref = sel.value;
|
|
11799
12530
|
if (!ref)
|
|
11800
12531
|
return;
|
|
11801
|
-
const
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
|
|
11806
|
-
dependencies: parseFormulaDependencies(newFormula)
|
|
11807
|
-
});
|
|
11808
|
-
formulaInput.value = newFormula;
|
|
12532
|
+
const field = numFieldInfos.find((f) => f.fieldName === ref || f.id === ref);
|
|
12533
|
+
if (field) {
|
|
12534
|
+
numFormulaWidget.insertField(field);
|
|
12535
|
+
lastFocusedFormulaWidget = numFormulaWidget;
|
|
12536
|
+
}
|
|
11809
12537
|
sel.value = "";
|
|
11810
|
-
this.render();
|
|
11811
12538
|
}
|
|
11812
12539
|
});
|
|
11813
|
-
insertSelect.appendChild(createElement("option", { value: "", text: "Select field to insert
|
|
11814
|
-
|
|
12540
|
+
insertSelect.appendChild(createElement("option", { value: "", text: "Select field to insert\u2026", selected: true }));
|
|
12541
|
+
numFieldInfos.forEach((f) => {
|
|
11815
12542
|
const ref = f.fieldName !== f.id ? f.fieldName : f.id;
|
|
11816
|
-
insertSelect.appendChild(createElement("option", { value: ref, text:
|
|
12543
|
+
insertSelect.appendChild(createElement("option", { value: ref, text: f.label }));
|
|
11817
12544
|
});
|
|
11818
12545
|
insertGroup.appendChild(insertSelect);
|
|
11819
12546
|
body.appendChild(insertGroup);
|
|
11820
|
-
|
|
11821
|
-
className: "text-
|
|
11822
|
-
text: "
|
|
12547
|
+
body.appendChild(createElement("label", {
|
|
12548
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
12549
|
+
text: "Operators"
|
|
12550
|
+
}));
|
|
12551
|
+
const numMathWrap = createElement("div", { className: "flex flex-wrap gap-1 mb-3" });
|
|
12552
|
+
[
|
|
12553
|
+
{ text: "+", insert: " + " },
|
|
12554
|
+
{ text: "-", insert: " - " },
|
|
12555
|
+
{ text: "*", insert: " * " },
|
|
12556
|
+
{ text: "/", insert: " / " },
|
|
12557
|
+
{ text: "%", insert: " % " },
|
|
12558
|
+
{ text: "(", insert: "(" },
|
|
12559
|
+
{ text: ")", insert: ")" }
|
|
12560
|
+
].forEach((op) => {
|
|
12561
|
+
numMathWrap.appendChild(createElement("button", {
|
|
12562
|
+
type: "button",
|
|
12563
|
+
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",
|
|
12564
|
+
text: op.text,
|
|
12565
|
+
onclick: () => {
|
|
12566
|
+
(lastFocusedFormulaWidget ?? numFormulaWidget).insertText(op.insert);
|
|
12567
|
+
}
|
|
12568
|
+
}));
|
|
11823
12569
|
});
|
|
11824
|
-
body.appendChild(
|
|
12570
|
+
body.appendChild(numMathWrap);
|
|
12571
|
+
body.appendChild(createElement("p", {
|
|
12572
|
+
className: "text-xs text-gray-500 dark:text-gray-400 mb-2",
|
|
12573
|
+
text: "Fields shown as labels \u2014 stored as field references internally."
|
|
12574
|
+
}));
|
|
11825
12575
|
}
|
|
11826
12576
|
}
|
|
11827
12577
|
if (selectedField.type === "formula") {
|
|
11828
12578
|
const schema = formStore.getState().schema;
|
|
12579
|
+
const allSchemaFieldInfos = schema.sections.flatMap((s) => s.fields).filter((f) => f.id !== selectedField.id).map((f) => ({
|
|
12580
|
+
id: f.id,
|
|
12581
|
+
fieldName: f.fieldName ?? f.id,
|
|
12582
|
+
label: f.label || f.fieldName || f.id
|
|
12583
|
+
}));
|
|
11829
12584
|
const availableFields = getFieldsForFormula(schema, selectedField.id);
|
|
11830
12585
|
const cfg = selectedField.formulaConfig ?? {
|
|
11831
12586
|
mode: "single",
|
|
@@ -11878,35 +12633,43 @@ var FormBuilder = class {
|
|
|
11878
12633
|
modeRow.appendChild(multipleBtn);
|
|
11879
12634
|
modeGroup.appendChild(modeRow);
|
|
11880
12635
|
body.appendChild(modeGroup);
|
|
12636
|
+
const fwFieldInfos = allSchemaFieldInfos;
|
|
12637
|
+
lastFocusedFormulaWidget = null;
|
|
12638
|
+
const registerFormulaWidget = (widget) => {
|
|
12639
|
+
widget.getElement().querySelector("[data-formula-editor]")?.addEventListener("focus", () => {
|
|
12640
|
+
lastFocusedFormulaWidget = widget;
|
|
12641
|
+
});
|
|
12642
|
+
};
|
|
11881
12643
|
if (cfg.mode === "single") {
|
|
11882
12644
|
const exprGroup = createElement("div", { className: "mb-3" });
|
|
11883
12645
|
exprGroup.appendChild(createElement("label", {
|
|
11884
12646
|
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
11885
12647
|
text: "Expression"
|
|
11886
12648
|
}));
|
|
11887
|
-
const exprTextarea = createElement("textarea", {
|
|
11888
|
-
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",
|
|
11889
|
-
placeholder: "e.g. {fieldA} + {fieldB} * 1.18",
|
|
11890
|
-
rows: "3"
|
|
11891
|
-
});
|
|
11892
|
-
exprTextarea.value = cfg.single?.expression ?? "";
|
|
11893
|
-
exprTextarea.addEventListener("focus", () => {
|
|
11894
|
-
lastFocusedExprTextarea = exprTextarea;
|
|
11895
|
-
});
|
|
11896
12649
|
const exprError = createElement("div", { className: "text-xs text-red-500 mt-1 hidden" });
|
|
11897
|
-
|
|
11898
|
-
|
|
11899
|
-
|
|
11900
|
-
|
|
11901
|
-
|
|
11902
|
-
|
|
11903
|
-
|
|
11904
|
-
|
|
12650
|
+
const singleWidget = new FormulaEditorWidget({
|
|
12651
|
+
mode: "bracket",
|
|
12652
|
+
// Use ALL schema fields for the fieldMap so any ref resolves to its label
|
|
12653
|
+
availableFields: fwFieldInfos,
|
|
12654
|
+
// Source of truth: always read from persisted cfg, never from a
|
|
12655
|
+
// closure variable. This guarantees correct reload after field switch.
|
|
12656
|
+
initialValue: cfg.single?.expression ?? "",
|
|
12657
|
+
placeholder: "e.g. {fieldA} + {fieldB} * 1.18",
|
|
12658
|
+
onChange: (expr) => {
|
|
12659
|
+
const result = validateFormulaExpression(expr, fwFieldInfos.map((f) => f.fieldName));
|
|
12660
|
+
const isValid2 = result.valid || !expr.trim();
|
|
12661
|
+
singleWidget.setError(!isValid2);
|
|
12662
|
+
exprError.textContent = isValid2 ? "" : result.error;
|
|
12663
|
+
exprError.classList.toggle("hidden", isValid2);
|
|
12664
|
+
const freshCfg = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id)?.formulaConfig ?? cfg;
|
|
12665
|
+
formStore.getState().updateField(selectedField.id, {
|
|
12666
|
+
formulaConfig: { ...freshCfg, single: { expression: expr } }
|
|
12667
|
+
});
|
|
11905
12668
|
}
|
|
11906
|
-
const freshCfg = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id)?.formulaConfig ?? cfg;
|
|
11907
|
-
formStore.getState().updateField(selectedField.id, { formulaConfig: { ...freshCfg, single: { expression: expr } } });
|
|
11908
12669
|
});
|
|
11909
|
-
|
|
12670
|
+
registerFormulaWidget(singleWidget);
|
|
12671
|
+
lastFocusedFormulaWidget = singleWidget;
|
|
12672
|
+
exprGroup.appendChild(singleWidget.getElement());
|
|
11910
12673
|
exprGroup.appendChild(exprError);
|
|
11911
12674
|
body.appendChild(exprGroup);
|
|
11912
12675
|
} else {
|
|
@@ -11933,16 +12696,15 @@ var FormBuilder = class {
|
|
|
11933
12696
|
});
|
|
11934
12697
|
compareGroup.appendChild(compareSelect);
|
|
11935
12698
|
body.appendChild(compareGroup);
|
|
11936
|
-
|
|
12699
|
+
body.appendChild(createElement("label", {
|
|
11937
12700
|
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-2",
|
|
11938
12701
|
text: "Conditions"
|
|
11939
|
-
});
|
|
11940
|
-
body.appendChild(conditionsLabel);
|
|
12702
|
+
}));
|
|
11941
12703
|
const conditions = cfg.multiple?.conditions ?? [];
|
|
11942
12704
|
conditions.forEach((cond, idx) => {
|
|
11943
12705
|
const row = createElement("div", { className: "mb-2 p-2 rounded-md border border-gray-100 dark:border-gray-800 space-y-1" });
|
|
11944
|
-
|
|
11945
|
-
|
|
12706
|
+
row.appendChild(createElement("div", { className: "text-xs text-gray-500 dark:text-gray-400", text: "When equals" }));
|
|
12707
|
+
row.appendChild(createElement("input", {
|
|
11946
12708
|
type: "text",
|
|
11947
12709
|
className: "w-full px-2 py-1 text-sm border border-gray-200 dark:border-gray-700 rounded-md bg-transparent",
|
|
11948
12710
|
value: cond.value,
|
|
@@ -11954,25 +12716,26 @@ var FormBuilder = class {
|
|
|
11954
12716
|
newConds[idx] = { ...newConds[idx], value: v };
|
|
11955
12717
|
patchMultiple({ conditions: newConds });
|
|
11956
12718
|
}
|
|
11957
|
-
});
|
|
11958
|
-
|
|
11959
|
-
const
|
|
11960
|
-
|
|
12719
|
+
}));
|
|
12720
|
+
row.appendChild(createElement("div", { className: "text-xs text-gray-500 dark:text-gray-400", text: "\u2192 Expression" }));
|
|
12721
|
+
const condWidget = new FormulaEditorWidget({
|
|
12722
|
+
mode: "bracket",
|
|
12723
|
+
availableFields: fwFieldInfos,
|
|
12724
|
+
// all-schema fieldMap
|
|
12725
|
+
initialValue: cond.expression ?? "",
|
|
11961
12726
|
placeholder: "e.g. {fieldA} + {fieldB}",
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
condTextarea.addEventListener("input", () => {
|
|
11969
|
-
const expr = condTextarea.value;
|
|
11970
|
-
const freshCfg = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id)?.formulaConfig ?? cfg;
|
|
11971
|
-
const newConds = [...freshCfg.multiple?.conditions ?? []];
|
|
11972
|
-
newConds[idx] = { ...newConds[idx], expression: expr };
|
|
11973
|
-
patchMultiple({ conditions: newConds });
|
|
12727
|
+
onChange: (expr) => {
|
|
12728
|
+
const freshCfg = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id)?.formulaConfig ?? cfg;
|
|
12729
|
+
const newConds = [...freshCfg.multiple?.conditions ?? []];
|
|
12730
|
+
newConds[idx] = { ...newConds[idx], expression: expr };
|
|
12731
|
+
patchMultiple({ conditions: newConds });
|
|
12732
|
+
}
|
|
11974
12733
|
});
|
|
11975
|
-
|
|
12734
|
+
registerFormulaWidget(condWidget);
|
|
12735
|
+
if (!lastFocusedFormulaWidget)
|
|
12736
|
+
lastFocusedFormulaWidget = condWidget;
|
|
12737
|
+
row.appendChild(condWidget.getElement());
|
|
12738
|
+
row.appendChild(createElement("button", {
|
|
11976
12739
|
type: "button",
|
|
11977
12740
|
className: "text-xs text-red-500 hover:text-red-700 dark:text-red-400",
|
|
11978
12741
|
text: "\u2212 Remove",
|
|
@@ -11983,12 +12746,7 @@ var FormBuilder = class {
|
|
|
11983
12746
|
patchMultiple({ conditions: newConds });
|
|
11984
12747
|
this.render();
|
|
11985
12748
|
}
|
|
11986
|
-
});
|
|
11987
|
-
row.appendChild(whenLabel);
|
|
11988
|
-
row.appendChild(valueInput);
|
|
11989
|
-
row.appendChild(exprLabel);
|
|
11990
|
-
row.appendChild(condTextarea);
|
|
11991
|
-
row.appendChild(removeBtn);
|
|
12749
|
+
}));
|
|
11992
12750
|
body.appendChild(row);
|
|
11993
12751
|
});
|
|
11994
12752
|
body.appendChild(createElement("button", {
|
|
@@ -12007,22 +12765,22 @@ var FormBuilder = class {
|
|
|
12007
12765
|
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
12008
12766
|
text: "Fallback Expression"
|
|
12009
12767
|
}));
|
|
12010
|
-
const
|
|
12011
|
-
|
|
12768
|
+
const fallbackWidget = new FormulaEditorWidget({
|
|
12769
|
+
mode: "bracket",
|
|
12770
|
+
availableFields: fwFieldInfos,
|
|
12771
|
+
initialValue: cfg.multiple?.fallbackExpression ?? "",
|
|
12012
12772
|
placeholder: "e.g. 0",
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
fallbackTextarea.addEventListener("focus", () => {
|
|
12017
|
-
lastFocusedExprTextarea = fallbackTextarea;
|
|
12018
|
-
});
|
|
12019
|
-
fallbackTextarea.addEventListener("input", () => {
|
|
12020
|
-
patchMultiple({ fallbackExpression: fallbackTextarea.value });
|
|
12773
|
+
onChange: (expr) => {
|
|
12774
|
+
patchMultiple({ fallbackExpression: expr });
|
|
12775
|
+
}
|
|
12021
12776
|
});
|
|
12022
|
-
|
|
12777
|
+
registerFormulaWidget(fallbackWidget);
|
|
12778
|
+
if (!lastFocusedFormulaWidget)
|
|
12779
|
+
lastFocusedFormulaWidget = fallbackWidget;
|
|
12780
|
+
fallbackGroup.appendChild(fallbackWidget.getElement());
|
|
12023
12781
|
body.appendChild(fallbackGroup);
|
|
12024
12782
|
}
|
|
12025
|
-
if (
|
|
12783
|
+
if (fwFieldInfos.length > 0) {
|
|
12026
12784
|
const insertGroup = createElement("div", { className: "mb-3" });
|
|
12027
12785
|
insertGroup.appendChild(createElement("label", {
|
|
12028
12786
|
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
@@ -12035,27 +12793,20 @@ var FormBuilder = class {
|
|
|
12035
12793
|
const ref = sel.value;
|
|
12036
12794
|
if (!ref)
|
|
12037
12795
|
return;
|
|
12038
|
-
const
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
const before = ta.value.slice(0, start);
|
|
12043
|
-
const after = ta.value.slice(end);
|
|
12044
|
-
const pad = before.length > 0 && !/\s$/.test(before) ? " " : "";
|
|
12045
|
-
ta.value = before + pad + ref + after;
|
|
12046
|
-
const newPos = before.length + pad.length + ref.length;
|
|
12047
|
-
ta.selectionStart = ta.selectionEnd = newPos;
|
|
12048
|
-
ta.dispatchEvent(new Event("input", { bubbles: true }));
|
|
12049
|
-
ta.focus();
|
|
12796
|
+
const field = fwFieldInfos.find((f) => f.fieldName === ref || f.id === ref);
|
|
12797
|
+
const target = lastFocusedFormulaWidget;
|
|
12798
|
+
if (field && target) {
|
|
12799
|
+
target.insertField(field);
|
|
12050
12800
|
}
|
|
12051
12801
|
sel.value = "";
|
|
12052
12802
|
}
|
|
12053
12803
|
});
|
|
12054
12804
|
insertSelect.appendChild(createElement("option", { value: "", text: "Select field to insert\u2026", selected: true }));
|
|
12055
|
-
|
|
12805
|
+
fwFieldInfos.forEach((f) => {
|
|
12056
12806
|
insertSelect.appendChild(createElement("option", {
|
|
12057
|
-
value:
|
|
12058
|
-
text:
|
|
12807
|
+
value: f.fieldName !== f.id ? f.fieldName : f.id,
|
|
12808
|
+
text: f.label
|
|
12809
|
+
// show only human-readable label
|
|
12059
12810
|
}));
|
|
12060
12811
|
});
|
|
12061
12812
|
insertGroup.appendChild(insertSelect);
|
|
@@ -12086,16 +12837,7 @@ var FormBuilder = class {
|
|
|
12086
12837
|
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",
|
|
12087
12838
|
text: op.text,
|
|
12088
12839
|
onclick: () => {
|
|
12089
|
-
|
|
12090
|
-
if (!ta)
|
|
12091
|
-
return;
|
|
12092
|
-
const start = ta.selectionStart ?? ta.value.length;
|
|
12093
|
-
const end = ta.selectionEnd ?? ta.value.length;
|
|
12094
|
-
ta.value = ta.value.slice(0, start) + op.insert + ta.value.slice(end);
|
|
12095
|
-
const newPos = start + op.insert.length;
|
|
12096
|
-
ta.selectionStart = ta.selectionEnd = newPos;
|
|
12097
|
-
ta.dispatchEvent(new Event("input", { bubbles: true }));
|
|
12098
|
-
ta.focus();
|
|
12840
|
+
lastFocusedFormulaWidget?.insertText(op.insert);
|
|
12099
12841
|
}
|
|
12100
12842
|
}));
|
|
12101
12843
|
});
|
|
@@ -12832,6 +13574,84 @@ var FormBuilder = class {
|
|
|
12832
13574
|
});
|
|
12833
13575
|
parentFieldGroup.appendChild(parentFieldSelect);
|
|
12834
13576
|
body.appendChild(parentFieldGroup);
|
|
13577
|
+
const autoPopHeader = createElement("h3", {
|
|
13578
|
+
className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3 mt-6",
|
|
13579
|
+
text: "Auto Populate Fields"
|
|
13580
|
+
});
|
|
13581
|
+
body.appendChild(autoPopHeader);
|
|
13582
|
+
const autoPopEnabled = selectedField.autoPopulateFields?.enabled === true;
|
|
13583
|
+
body.appendChild(this.createCheckboxField(
|
|
13584
|
+
"Enable automation for selected records",
|
|
13585
|
+
autoPopEnabled,
|
|
13586
|
+
(checked) => {
|
|
13587
|
+
const current = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
13588
|
+
formStore.getState().updateField(selectedField.id, {
|
|
13589
|
+
autoPopulateFields: {
|
|
13590
|
+
enabled: checked,
|
|
13591
|
+
fields: current?.autoPopulateFields?.fields ?? []
|
|
13592
|
+
}
|
|
13593
|
+
});
|
|
13594
|
+
this.render();
|
|
13595
|
+
},
|
|
13596
|
+
`auto-populate-enabled-${selectedField.id}`
|
|
13597
|
+
));
|
|
13598
|
+
{
|
|
13599
|
+
const autoPopFieldsGroup = createElement("div", { className: "mb-4 mt-2" });
|
|
13600
|
+
autoPopFieldsGroup.appendChild(createElement("label", {
|
|
13601
|
+
className: "block text-sm font-normal text-gray-700 dark:text-gray-300 mb-1",
|
|
13602
|
+
text: "Fields to auto-populate"
|
|
13603
|
+
}));
|
|
13604
|
+
const lookupFieldOptionsMapForAP = formStore.getState().lookupFieldOptionsMap;
|
|
13605
|
+
const availableAutoPopFields = selectedField.lookupSource ? lookupFieldOptionsMapForAP[selectedField.lookupSource] || [] : [];
|
|
13606
|
+
const selectedAutoPopFields = selectedField.autoPopulateFields?.fields ?? [];
|
|
13607
|
+
const isAutoPopDisabled = !autoPopEnabled || !selectedField.lookupSource;
|
|
13608
|
+
if (availableAutoPopFields.length === 0) {
|
|
13609
|
+
const emptyNote = createElement("p", {
|
|
13610
|
+
className: "text-xs text-gray-400 dark:text-gray-500 mt-1",
|
|
13611
|
+
text: selectedField.lookupSource ? "No fields available for this lookup source." : "Select a Lookup Source first."
|
|
13612
|
+
});
|
|
13613
|
+
autoPopFieldsGroup.appendChild(emptyNote);
|
|
13614
|
+
} else {
|
|
13615
|
+
const fieldList = createElement("div", {
|
|
13616
|
+
className: `border border-gray-200 dark:border-gray-700 rounded-md divide-y divide-gray-100 dark:divide-gray-700 overflow-y-auto max-h-40 ${isAutoPopDisabled ? "opacity-50 pointer-events-none" : ""}`
|
|
13617
|
+
});
|
|
13618
|
+
availableAutoPopFields.forEach((fieldKey) => {
|
|
13619
|
+
const isChecked = selectedAutoPopFields.includes(fieldKey);
|
|
13620
|
+
const row = createElement("label", {
|
|
13621
|
+
className: "flex items-center gap-2 px-3 py-2 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 text-sm text-gray-700 dark:text-gray-300"
|
|
13622
|
+
});
|
|
13623
|
+
const cb = createElement("input", {
|
|
13624
|
+
type: "checkbox",
|
|
13625
|
+
className: "w-4 h-4 accent-blue-600",
|
|
13626
|
+
checked: isChecked,
|
|
13627
|
+
disabled: isAutoPopDisabled,
|
|
13628
|
+
onchange: (e) => {
|
|
13629
|
+
const target = e.target;
|
|
13630
|
+
const latestField = formStore.getState().schema.sections.flatMap((s) => s.fields).find((f) => f.id === selectedField.id);
|
|
13631
|
+
const latestSelected = latestField?.autoPopulateFields?.fields ?? [];
|
|
13632
|
+
const updatedFields = target.checked ? [.../* @__PURE__ */ new Set([...latestSelected, fieldKey])] : latestSelected.filter((k) => k !== fieldKey);
|
|
13633
|
+
formStore.getState().updateField(selectedField.id, {
|
|
13634
|
+
autoPopulateFields: {
|
|
13635
|
+
enabled: latestField?.autoPopulateFields?.enabled ?? true,
|
|
13636
|
+
fields: updatedFields
|
|
13637
|
+
}
|
|
13638
|
+
});
|
|
13639
|
+
}
|
|
13640
|
+
});
|
|
13641
|
+
row.appendChild(cb);
|
|
13642
|
+
row.appendChild(createElement("span", { text: fieldKey }));
|
|
13643
|
+
fieldList.appendChild(row);
|
|
13644
|
+
});
|
|
13645
|
+
autoPopFieldsGroup.appendChild(fieldList);
|
|
13646
|
+
if (selectedAutoPopFields.length > 0) {
|
|
13647
|
+
autoPopFieldsGroup.appendChild(createElement("p", {
|
|
13648
|
+
className: "text-xs text-gray-400 dark:text-gray-500 mt-1",
|
|
13649
|
+
text: `${selectedAutoPopFields.length} field(s) selected`
|
|
13650
|
+
}));
|
|
13651
|
+
}
|
|
13652
|
+
}
|
|
13653
|
+
body.appendChild(autoPopFieldsGroup);
|
|
13654
|
+
}
|
|
12835
13655
|
body.appendChild(this.createCheckboxField(
|
|
12836
13656
|
"Visibility",
|
|
12837
13657
|
selectedField.visible !== false,
|