@fw-components/formula-editor 2.0.7-formula-editor.34 → 2.0.7-formula-editor.36
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.
|
@@ -9,6 +9,7 @@ import { customElement, property, state, query } from "lit/decorators.js";
|
|
|
9
9
|
import "./suggestion-menu.js";
|
|
10
10
|
import { Parser } from "./utils/parser.js";
|
|
11
11
|
import { FormulaEditorStyles } from "./styles/editor.js";
|
|
12
|
+
import { getFormulaTokens } from "./utils/get-formula-tokens.js";
|
|
12
13
|
let FormulaEditor = class FormulaEditor extends LitElement {
|
|
13
14
|
constructor() {
|
|
14
15
|
super(...arguments);
|
|
@@ -20,12 +21,13 @@ let FormulaEditor = class FormulaEditor extends LitElement {
|
|
|
20
21
|
*/
|
|
21
22
|
this.content = "";
|
|
22
23
|
this.placeholder = "Type your formula...";
|
|
24
|
+
this.recommendationLabels = new Map();
|
|
23
25
|
this.variables = new Map();
|
|
24
26
|
this.minSuggestionLen = 2;
|
|
25
27
|
this.errorString = "";
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
this.
|
|
28
|
+
this.formulaRegex = /'[^']*'|[A-Za-z0-9_#@]+|[-+(),*^/\s]/g;
|
|
29
|
+
this.allowedNumbers = true;
|
|
30
|
+
this.allowedOperators = new Set(["^", "+", "-", "*", "/"]);
|
|
29
31
|
}
|
|
30
32
|
updated(_changedProperties) {
|
|
31
33
|
if (_changedProperties.has("content")) {
|
|
@@ -35,7 +37,7 @@ let FormulaEditor = class FormulaEditor extends LitElement {
|
|
|
35
37
|
this._adjustTextAreaHeight();
|
|
36
38
|
}
|
|
37
39
|
if (_changedProperties.has("variables")) {
|
|
38
|
-
this._parser = new Parser(this.variables, this.minSuggestionLen);
|
|
40
|
+
this._parser = new Parser(this.variables, this.formulaRegex, this.allowedNumbers, this.allowedOperators, this.minSuggestionLen);
|
|
39
41
|
this.recommendations = Array.from(this.variables.keys());
|
|
40
42
|
}
|
|
41
43
|
}
|
|
@@ -87,6 +89,7 @@ let FormulaEditor = class FormulaEditor extends LitElement {
|
|
|
87
89
|
formulaString: this.content,
|
|
88
90
|
error: this.errorString,
|
|
89
91
|
recommendations: this.recommendations,
|
|
92
|
+
formulaTokens: getFormulaTokens(this.content || "", this.formulaRegex)
|
|
90
93
|
},
|
|
91
94
|
bubbles: true,
|
|
92
95
|
}));
|
|
@@ -148,6 +151,7 @@ let FormulaEditor = class FormulaEditor extends LitElement {
|
|
|
148
151
|
.recommendations=${this.recommendations}
|
|
149
152
|
.currentSelection=${this._selectedRecommendation}
|
|
150
153
|
.onRecommendationClick=${this.onRecommendationClick.bind(this)}
|
|
154
|
+
.recommendationLabels=${this.recommendationLabels}
|
|
151
155
|
></suggestion-menu>`
|
|
152
156
|
: ''}
|
|
153
157
|
`;
|
|
@@ -174,6 +178,9 @@ __decorate([
|
|
|
174
178
|
__decorate([
|
|
175
179
|
property()
|
|
176
180
|
], FormulaEditor.prototype, "placeholder", void 0);
|
|
181
|
+
__decorate([
|
|
182
|
+
property()
|
|
183
|
+
], FormulaEditor.prototype, "recommendationLabels", void 0);
|
|
177
184
|
__decorate([
|
|
178
185
|
property()
|
|
179
186
|
], FormulaEditor.prototype, "label", void 0);
|
|
@@ -186,6 +193,15 @@ __decorate([
|
|
|
186
193
|
__decorate([
|
|
187
194
|
property()
|
|
188
195
|
], FormulaEditor.prototype, "errorString", void 0);
|
|
196
|
+
__decorate([
|
|
197
|
+
property()
|
|
198
|
+
], FormulaEditor.prototype, "formulaRegex", void 0);
|
|
199
|
+
__decorate([
|
|
200
|
+
property()
|
|
201
|
+
], FormulaEditor.prototype, "allowedNumbers", void 0);
|
|
202
|
+
__decorate([
|
|
203
|
+
property()
|
|
204
|
+
], FormulaEditor.prototype, "allowedOperators", void 0);
|
|
189
205
|
__decorate([
|
|
190
206
|
query("#wysiwyg-editor")
|
|
191
207
|
], FormulaEditor.prototype, "editor", void 0);
|
|
@@ -11,6 +11,7 @@ let SuggestionMenu = class SuggestionMenu extends LitElement {
|
|
|
11
11
|
constructor() {
|
|
12
12
|
super(...arguments);
|
|
13
13
|
this.recommendations = [];
|
|
14
|
+
this.recommendationLabels = new Map();
|
|
14
15
|
this.onRecommendationClick = () => { };
|
|
15
16
|
this._selectedRecommendationIndex = -1;
|
|
16
17
|
}
|
|
@@ -49,7 +50,7 @@ let SuggestionMenu = class SuggestionMenu extends LitElement {
|
|
|
49
50
|
${this.recommendations.map((recommendation, index) => html `<li
|
|
50
51
|
class="${this._selectedRecommendationIndex === index ? "selected" : ""}"
|
|
51
52
|
@click=${(e) => this.handleRecommendationSelect(index)}
|
|
52
|
-
>${recommendation}</li>`)}
|
|
53
|
+
>${this.recommendationLabels.get(recommendation) ?? recommendation}</li>`)}
|
|
53
54
|
</ul>
|
|
54
55
|
`;
|
|
55
56
|
}
|
|
@@ -57,6 +58,9 @@ let SuggestionMenu = class SuggestionMenu extends LitElement {
|
|
|
57
58
|
__decorate([
|
|
58
59
|
property()
|
|
59
60
|
], SuggestionMenu.prototype, "recommendations", void 0);
|
|
61
|
+
__decorate([
|
|
62
|
+
property()
|
|
63
|
+
], SuggestionMenu.prototype, "recommendationLabels", void 0);
|
|
60
64
|
__decorate([
|
|
61
65
|
property()
|
|
62
66
|
], SuggestionMenu.prototype, "onRecommendationClick", void 0);
|
|
@@ -3,19 +3,23 @@ import { Recommender } from "./recommendor.js";
|
|
|
3
3
|
import { Stack } from "./stack.js";
|
|
4
4
|
import { Queue } from "./queue.js";
|
|
5
5
|
import { Expectation } from "../types";
|
|
6
|
-
import {
|
|
6
|
+
import { operatorPrecedence, unaryOperators } from "./constants.js";
|
|
7
|
+
import { getFormulaTokens } from "./get-formula-tokens.js";
|
|
7
8
|
export class Parser {
|
|
8
|
-
constructor(variables, minSuggestionLen) {
|
|
9
|
+
constructor(variables, formulaRegex, allowedNumbers, allowedOperators, minSuggestionLen) {
|
|
9
10
|
this.variables = variables;
|
|
11
|
+
this.formulaRegex = formulaRegex;
|
|
10
12
|
this._recommender = new Recommender(Array.from(this.variables.keys()), minSuggestionLen);
|
|
13
|
+
this.allowedNumbers = allowedNumbers;
|
|
14
|
+
this.allowedOperators = allowedOperators;
|
|
11
15
|
}
|
|
12
|
-
|
|
13
|
-
if (!
|
|
14
|
-
return
|
|
15
|
-
return
|
|
16
|
+
isNumber(value) {
|
|
17
|
+
if (!this.allowedNumbers || value.trim() === "")
|
|
18
|
+
return false;
|
|
19
|
+
return !Number.isNaN(Number(value));
|
|
16
20
|
}
|
|
17
21
|
parseInput(formula, prevCurPos = null, recommendation = null) {
|
|
18
|
-
const tokens =
|
|
22
|
+
const tokens = getFormulaTokens(formula, this.formulaRegex);
|
|
19
23
|
const parentheses = new Stack();
|
|
20
24
|
let expectation = Expectation.VARIABLE;
|
|
21
25
|
let currentPosition = 0;
|
|
@@ -33,8 +37,8 @@ export class Parser {
|
|
|
33
37
|
return parseOutput;
|
|
34
38
|
}
|
|
35
39
|
tokens?.forEach((token) => {
|
|
36
|
-
let isNumber =
|
|
37
|
-
const isOperator =
|
|
40
|
+
let isNumber = this.variables.has(token) || this.isNumber(token);
|
|
41
|
+
const isOperator = this.allowedOperators.has(token);
|
|
38
42
|
const isSpace = token.trim() === "";
|
|
39
43
|
const isBracket = token === "(" || token === ")";
|
|
40
44
|
if (isSpace) {
|
|
@@ -51,7 +55,7 @@ export class Parser {
|
|
|
51
55
|
if (currentPosition <= prevCurPos && currentPosition + token.length >= prevCurPos) {
|
|
52
56
|
if (recommendation) {
|
|
53
57
|
isNumber = true;
|
|
54
|
-
if (
|
|
58
|
+
if (this.allowedOperators.has(token)) {
|
|
55
59
|
const updatedTokenString = `${token} ${recommendation}`;
|
|
56
60
|
parseOutput.formattedString += updatedTokenString;
|
|
57
61
|
currentPosition += updatedTokenString.length;
|
|
@@ -72,7 +76,7 @@ export class Parser {
|
|
|
72
76
|
* skip error check if there is one already
|
|
73
77
|
*/
|
|
74
78
|
if (expectation != Expectation.UNDEFINED) {
|
|
75
|
-
if (
|
|
79
|
+
if (this.allowedOperators.has(previousToken) && isOperator) {
|
|
76
80
|
parseOutput.errorString = `Multiple operators at position ${currentPosition}`;
|
|
77
81
|
expectation = Expectation.UNDEFINED;
|
|
78
82
|
}
|
|
@@ -85,7 +89,7 @@ export class Parser {
|
|
|
85
89
|
* No error for Unary `+` and `-` as they might represent a positive or negative number respectively
|
|
86
90
|
*/
|
|
87
91
|
else if (expectation === Expectation.VARIABLE && !isNumber && !isSpace && token != "("
|
|
88
|
-
&& !((unaryOperators.includes(token)) && (!parsedString.trim() || previousToken === "(" ||
|
|
92
|
+
&& !((unaryOperators.includes(token)) && (!parsedString.trim() || previousToken === "(" || this.allowedOperators.has(previousToken)))) {
|
|
89
93
|
parseOutput.errorString = `Expected variable/number at position ${currentPosition}`;
|
|
90
94
|
expectation = Expectation.UNDEFINED;
|
|
91
95
|
}
|
|
@@ -143,10 +147,10 @@ export class Parser {
|
|
|
143
147
|
parseOutput.formattedString += recommendation;
|
|
144
148
|
previousToken = recommendation;
|
|
145
149
|
}
|
|
146
|
-
if (
|
|
150
|
+
if (this.allowedOperators.has(previousToken) || !previousToken.trim().length) {
|
|
147
151
|
parseOutput.recommendations = !parseOutput.errorString?.length ? Array.from(this.variables.keys()) : [];
|
|
148
152
|
}
|
|
149
|
-
if (
|
|
153
|
+
if (this.allowedOperators.has(previousToken)) {
|
|
150
154
|
parseOutput.errorString = `Unexpected ending with mathematical operator at position: ${currentPosition}`;
|
|
151
155
|
}
|
|
152
156
|
if (!parentheses.isEmpty()) {
|
|
@@ -157,14 +161,14 @@ export class Parser {
|
|
|
157
161
|
buildRPN(formula) {
|
|
158
162
|
if (this.parseInput(formula).errorString)
|
|
159
163
|
return null;
|
|
160
|
-
const tokens =
|
|
164
|
+
const tokens = getFormulaTokens(formula, this.formulaRegex)?.filter((el) => !/\s+/.test(el) && el !== "");
|
|
161
165
|
let previousToken = "";
|
|
162
166
|
let carriedToken = null;
|
|
163
167
|
const parsedTokens = [];
|
|
164
168
|
let currentTokens = "";
|
|
165
169
|
// Check if variables include unary operators `-` and `+`.
|
|
166
170
|
for (const token of tokens) {
|
|
167
|
-
if ((unaryOperators.includes(token)) && (!currentTokens.trim() || previousToken === "(" ||
|
|
171
|
+
if ((unaryOperators.includes(token)) && (!currentTokens.trim() || previousToken === "(" || this.allowedOperators.has(previousToken))) {
|
|
168
172
|
carriedToken = token;
|
|
169
173
|
}
|
|
170
174
|
else if (carriedToken) {
|
|
@@ -192,8 +196,8 @@ export class Parser {
|
|
|
192
196
|
}
|
|
193
197
|
operatorStack.pop();
|
|
194
198
|
}
|
|
195
|
-
else if (
|
|
196
|
-
while (
|
|
199
|
+
else if (this.allowedOperators.has(token)) {
|
|
200
|
+
while (this.allowedOperators.has(operatorStack.top()) && operatorPrecedence[token] <= operatorPrecedence[operatorStack.top()]) {
|
|
197
201
|
outputQueue.enqueue(operatorStack.pop());
|
|
198
202
|
}
|
|
199
203
|
operatorStack.push(token);
|
|
@@ -270,7 +274,7 @@ export class Parser {
|
|
|
270
274
|
const calcStack = new Stack();
|
|
271
275
|
while (!formulaRPN.isEmpty()) {
|
|
272
276
|
const frontItem = formulaRPN.dequeue();
|
|
273
|
-
if (!
|
|
277
|
+
if (!this.allowedOperators.has(frontItem)) {
|
|
274
278
|
const [sign, variableKey] = /^[+-]/.test(frontItem) ? [frontItem[0], frontItem.slice(1)] : ["", frontItem];
|
|
275
279
|
const operandValue = Number.parseFloat(this.variables.get(variableKey)?.toString() ?? variableKey);
|
|
276
280
|
const number = Number.parseFloat(sign + "1") * operandValue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fw-components/formula-editor",
|
|
3
|
-
"version": "2.0.7-formula-editor.
|
|
3
|
+
"version": "2.0.7-formula-editor.36",
|
|
4
4
|
"description": "A WYSIWYG type formula editor",
|
|
5
5
|
"main": "dist/formula-editor/src/formula-editor.js",
|
|
6
6
|
"exports": {
|
|
@@ -31,5 +31,5 @@
|
|
|
31
31
|
"@types/big.js": "^6.1.6",
|
|
32
32
|
"es-dev-server": "^2.1.0"
|
|
33
33
|
},
|
|
34
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "78fd3b78f8f72ac4ab0b4b7ec57848b2a78cccad"
|
|
35
35
|
}
|