@fw-components/formula-editor 2.3.4 → 2.4.0

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.
@@ -5,16 +5,20 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { html, LitElement } from "lit";
8
- import { customElement, property, state, query } from "lit/decorators.js";
9
- import "./suggestion-menu.js";
8
+ import { customElement, property, query, state } from "lit/decorators.js";
9
+ import { getFormulaTokens } from "./utils/get-formula-tokens.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";
13
12
  let FormulaEditor = class FormulaEditor extends LitElement {
14
13
  constructor() {
15
14
  super(...arguments);
16
15
  this.recommendations = [];
17
16
  this.currentCursorPosition = 0;
17
+ /**
18
+ * user input event type
19
+ */
20
+ this.lastInputType = "";
21
+ this._selectedRecommendation = "";
18
22
  this.isFocused = false;
19
23
  /**
20
24
  * Text area input value
@@ -22,10 +26,14 @@ let FormulaEditor = class FormulaEditor extends LitElement {
22
26
  this.formulaString = "";
23
27
  this.placeholder = "Type your formula...";
24
28
  this.recommendationLabels = new Map();
29
+ this.label = "";
25
30
  this.variables = new Map();
31
+ this.variableType = "";
26
32
  this.minSuggestionLen = 2;
27
33
  this.errorString = "";
34
+ this.formulaRegex = /.*/;
28
35
  this.allowedNumbers = true;
36
+ this.allowedOperators = new Set();
29
37
  }
30
38
  updated(_changedProperties) {
31
39
  if (_changedProperties.has("formulaString")) {
@@ -66,7 +74,7 @@ let FormulaEditor = class FormulaEditor extends LitElement {
66
74
  this.currentCursorPosition = this.editor.selectionStart;
67
75
  const { recommendations, errorString, formattedString, newCursorPosition } = this._parser.parseInput(this.formulaString, this.currentCursorPosition, recommendation);
68
76
  this.recommendations = recommendations;
69
- this.errorString = errorString;
77
+ this.errorString = errorString || "";
70
78
  /**
71
79
  * Don't modify the text stream manually if the text is being composed,
72
80
  * unless the user manually chooses to do so by selecting a recommendation.
@@ -92,7 +100,7 @@ let FormulaEditor = class FormulaEditor extends LitElement {
92
100
  recommendations: this.recommendations,
93
101
  formulaTokens: getFormulaTokens(this.formulaString || "", this.formulaRegex)
94
102
  },
95
- bubbles: true,
103
+ bubbles: true
96
104
  }));
97
105
  }
98
106
  formatFormula() {
@@ -131,7 +139,9 @@ let FormulaEditor = class FormulaEditor extends LitElement {
131
139
  }
132
140
  render() {
133
141
  return html `
134
- <style>${FormulaEditorStyles}</style>
142
+ <style>
143
+ ${FormulaEditorStyles}
144
+ </style>
135
145
 
136
146
  ${this.label ? html `<label for="fw-formula-editor" class="editor-label">${this.label}</label>` : ""}
137
147
 
@@ -155,7 +165,7 @@ let FormulaEditor = class FormulaEditor extends LitElement {
155
165
  .onRecommendationClick=${this.onRecommendationClick.bind(this)}
156
166
  .recommendationLabels=${this.recommendationLabels}
157
167
  ></suggestion-menu>`
158
- : ''}
168
+ : ""}
159
169
  `;
160
170
  }
161
171
  };
@@ -181,31 +191,31 @@ __decorate([
181
191
  property()
182
192
  ], FormulaEditor.prototype, "placeholder", void 0);
183
193
  __decorate([
184
- property()
194
+ property({ type: Object })
185
195
  ], FormulaEditor.prototype, "recommendationLabels", void 0);
186
196
  __decorate([
187
- property()
197
+ property({ type: String })
188
198
  ], FormulaEditor.prototype, "label", void 0);
189
199
  __decorate([
190
- property()
200
+ property({ type: Object })
191
201
  ], FormulaEditor.prototype, "variables", void 0);
192
202
  __decorate([
193
- property()
203
+ property({ type: String })
194
204
  ], FormulaEditor.prototype, "variableType", void 0);
195
205
  __decorate([
196
- property()
206
+ property({ type: Number })
197
207
  ], FormulaEditor.prototype, "minSuggestionLen", void 0);
198
208
  __decorate([
199
- property()
209
+ property({ type: String })
200
210
  ], FormulaEditor.prototype, "errorString", void 0);
201
211
  __decorate([
202
- property()
212
+ property({ type: Object })
203
213
  ], FormulaEditor.prototype, "formulaRegex", void 0);
204
214
  __decorate([
205
- property()
215
+ property({ type: Boolean })
206
216
  ], FormulaEditor.prototype, "allowedNumbers", void 0);
207
217
  __decorate([
208
- property()
218
+ property({ type: Object })
209
219
  ], FormulaEditor.prototype, "allowedOperators", void 0);
210
220
  __decorate([
211
221
  query("#fw-formula-editor")
@@ -27,12 +27,13 @@ export const SuggestionMenuStyles = css `
27
27
  color: var(--secondary-color, #bab6c0);
28
28
  }
29
29
 
30
- li:hover, li:focus-visible, li.selected {
30
+ li:hover,
31
+ li:focus-visible,
32
+ li.selected {
31
33
  color: var(--fe-suggestion-focus-color, #69676c);
32
34
  background: rgba(var(--fe-suggestion-focus-background, 86, 86, 86), 0.1);
33
35
  }
34
36
 
35
-
36
37
  /* Scrollbar styling */
37
38
  ::-webkit-scrollbar {
38
39
  width: 7px;
@@ -45,12 +45,13 @@ let SuggestionMenu = class SuggestionMenu extends LitElement {
45
45
  }
46
46
  render() {
47
47
  return html `
48
- <style>${SuggestionMenuStyles}</style>
49
- <ul class="fw-formula-suggestion-menu" @mousedown=${(e) => e.preventDefault()}>
50
- ${this.recommendations.map((recommendation, index) => html `<li
51
- class="${this._currentFocusedIndex === index ? "selected" : ""}"
52
- @click=${(e) => this.handleRecommendationSelect(index)}
53
- >${this.recommendationLabels.get(recommendation) ?? recommendation}</li>`)}
48
+ <style>
49
+ ${SuggestionMenuStyles}
50
+ </style>
51
+ <ul class="fw-formula-suggestion-menu" @mousedown=${(_e) => _e.preventDefault()}>
52
+ ${this.recommendations.map((recommendation, index) => html `<li class="${this._currentFocusedIndex === index ? "selected" : ""}" @click=${(_e) => this.handleRecommendationSelect(index)}>
53
+ ${this.recommendationLabels.get(recommendation) ?? recommendation}
54
+ </li>`)}
54
55
  </ul>
55
56
  `;
56
57
  }
@@ -5,5 +5,5 @@ export const operatorPrecedence = {
5
5
  "/": 2,
6
6
  "*": 2,
7
7
  "+": 1,
8
- "-": 1,
8
+ "-": 1
9
9
  };
@@ -1,10 +1,10 @@
1
- import Big from "big.js";
1
+ import { mathematicalOperators, operatorPrecedence, unaryOperators } from "./constants.js";
2
+ import { getFormulaTokens } from "./get-formula-tokens.js";
3
+ import { Queue } from "./queue.js";
2
4
  import { Recommender } from "./recommendor.js";
3
5
  import { Stack } from "./stack.js";
4
- import { Queue } from "./queue.js";
6
+ import Big from "big.js";
5
7
  import { Expectation } from "../types";
6
- import { operatorPrecedence, unaryOperators, mathematicalOperators } from "./constants.js";
7
- import { getFormulaTokens } from "./get-formula-tokens.js";
8
8
  export class Parser {
9
9
  constructor(variables, minSuggestionLen, formulaRegex = /[A-Za-z0-9_#@.]+|[-+(),*^/\s]/g, allowedNumbers = true, allowedOperators = mathematicalOperators, variableType = "") {
10
10
  this.variables = variables;
@@ -20,7 +20,7 @@ export class Parser {
20
20
  return !Number.isNaN(Number(value));
21
21
  }
22
22
  formatFormulaToken(token) {
23
- for (let existingKey of this.variables.keys()) {
23
+ for (const existingKey of this.variables.keys()) {
24
24
  if (existingKey.toLowerCase() === token.toLowerCase()) {
25
25
  return existingKey;
26
26
  }
@@ -38,7 +38,7 @@ export class Parser {
38
38
  recommendations: [],
39
39
  formattedString: "",
40
40
  newCursorPosition: prevCurPos ?? -1,
41
- errorString: null,
41
+ errorString: null
42
42
  };
43
43
  if (!formula.trim() && recommendation) {
44
44
  parseOutput.formattedString = recommendation;
@@ -73,7 +73,6 @@ export class Parser {
73
73
  recommendation = null;
74
74
  return;
75
75
  }
76
- ;
77
76
  const updatedTokenLength = recommendation.length - token.length;
78
77
  parseOutput.newCursorPosition = Math.min(parseOutput.newCursorPosition, formula.length) + updatedTokenLength;
79
78
  token = recommendation;
@@ -84,8 +83,8 @@ export class Parser {
84
83
  /**
85
84
  * Error checks
86
85
  * skip error check if there is one already
87
- */
88
- if (expectation != Expectation.UNDEFINED) {
86
+ */
87
+ if (expectation !== Expectation.UNDEFINED) {
89
88
  /**
90
89
  * Unknown symbol/variable/word
91
90
  */
@@ -101,33 +100,37 @@ export class Parser {
101
100
  parseOutput.errorString = "Unexpected closing bracket. Make sure all opening brackets '(' have matching closing brackets ')'.";
102
101
  expectation = Expectation.UNDEFINED;
103
102
  }
103
+ else if (
104
104
  /**
105
105
  * Operator or ')' after an operator (Eg: '23 / *' or '23 / )')
106
106
  * No error for Unary `+` and `-` as they might represent a positive or negative number respectively
107
107
  */
108
- else if (expectation === Expectation.VARIABLE && !isNumber && !isSpace && token != "("
109
- && !((unaryOperators.includes(token)) && (!parsedString.trim() || previousToken === "(" || this.allowedOperators.has(previousToken)))) {
110
- parseOutput.errorString = `Please use ${this.variableType} ${this.variableType && this.allowedNumbers ? " or " : ''} ${this.allowedNumbers ? "numbers" : ""} after '${previousToken}'.`;
108
+ expectation === Expectation.VARIABLE &&
109
+ !isNumber &&
110
+ !isSpace &&
111
+ token !== "(" &&
112
+ !(unaryOperators.includes(token) && (!parsedString.trim() || previousToken === "(" || this.allowedOperators.has(previousToken)))) {
113
+ parseOutput.errorString = `Please use ${this.variableType} ${this.variableType && this.allowedNumbers ? " or " : ""} ${this.allowedNumbers ? "numbers" : ""} after '${previousToken}'.`;
111
114
  expectation = Expectation.UNDEFINED;
112
115
  }
113
- /**
114
- * Multiple number/variable together without operator
115
- */
116
- else if (expectation === Expectation.OPERATOR && !isOperator && !isSpace && token != ")") {
116
+ else if (expectation === Expectation.OPERATOR && !isOperator && !isSpace && token !== ")") {
117
+ /**
118
+ * Multiple number/variable together without operator
119
+ */
117
120
  parseOutput.errorString = `Please use mathematical operators (${Array.from(this.allowedOperators).join(" ")}) after '${previousToken}'.`;
118
121
  expectation = Expectation.UNDEFINED;
119
122
  }
120
- /**
121
- * division by zero
122
- */
123
123
  else if (isNumber && previousToken === "/" && (this.variables.get(token) === 0 || Number(token) === 0)) {
124
+ /**
125
+ * division by zero
126
+ */
124
127
  parseOutput.errorString = `Division by zero is not possible.`;
125
128
  expectation = Expectation.UNDEFINED;
126
129
  }
127
- /**
128
- * Empty brackets
129
- */
130
130
  else if (previousToken === "(" && token === ")") {
131
+ /**
132
+ * Empty brackets
133
+ */
131
134
  parseOutput.errorString = `Please don't use empty brackets ().`;
132
135
  expectation = Expectation.UNDEFINED;
133
136
  }
@@ -135,7 +138,7 @@ export class Parser {
135
138
  /**
136
139
  * Setting the expectation for the next token, if no error is there till now
137
140
  */
138
- if (expectation != Expectation.UNDEFINED) {
141
+ if (expectation !== Expectation.UNDEFINED) {
139
142
  if (token === "(" || isOperator) {
140
143
  expectation = Expectation.VARIABLE;
141
144
  }
@@ -178,7 +181,7 @@ export class Parser {
178
181
  let currentTokens = "";
179
182
  // Check if variables include unary operators `-` and `+`.
180
183
  for (const token of tokens) {
181
- if ((unaryOperators.includes(token)) && (!currentTokens.trim() || previousToken === "(" || this.allowedOperators.has(previousToken))) {
184
+ if (unaryOperators.includes(token) && (!currentTokens.trim() || previousToken === "(" || this.allowedOperators.has(previousToken))) {
182
185
  carriedToken = token;
183
186
  }
184
187
  else if (carriedToken) {
@@ -201,7 +204,7 @@ export class Parser {
201
204
  operatorStack.push("(");
202
205
  }
203
206
  else if (token === ")") {
204
- while (!operatorStack.isEmpty() && operatorStack.top() != "(") {
207
+ while (!operatorStack.isEmpty() && operatorStack.top() !== "(") {
205
208
  outputQueue.enqueue(operatorStack.pop());
206
209
  }
207
210
  operatorStack.pop();
@@ -235,7 +238,7 @@ export class Parser {
235
238
  let parsedLeftExpression;
236
239
  let parsedRightExpression;
237
240
  // check if the symbol is a number or variable or unaryOperatorPreceded Variable
238
- if (((unaryOperators.includes(symbol[0])) && this.variables.has(symbol.substring(1))) ||
241
+ if ((unaryOperators.includes(symbol[0]) && this.variables.has(symbol.substring(1))) ||
239
242
  this.variables.has(symbol) ||
240
243
  (!Number.isNaN(parseFloat(symbol)) && Number.isFinite(parseFloat(symbol)))) {
241
244
  resultStack.push(symbol);
@@ -243,21 +246,14 @@ export class Parser {
243
246
  }
244
247
  // If symbol is an operator, check operatorStack, adds brackets accordingly to the result and add it to operatorStack
245
248
  else if (Object.keys(operatorPrecedence).includes(symbol)) {
246
- const [rightExpression, leftExpression, operatorA, operatorB] = [
247
- resultStack.pop(),
248
- resultStack.pop(),
249
- operatorStack.pop(),
250
- operatorStack.pop(),
251
- ];
252
- if ((operatorPrecedence[operatorB] <= operatorPrecedence[symbol]) ||
253
- (operatorPrecedence[operatorB] === operatorPrecedence[symbol] && ["/", "-"].includes(symbol))) {
249
+ const [rightExpression, leftExpression, operatorA, operatorB] = [resultStack.pop(), resultStack.pop(), operatorStack.pop(), operatorStack.pop()];
250
+ if (operatorPrecedence[operatorB] <= operatorPrecedence[symbol] || (operatorPrecedence[operatorB] === operatorPrecedence[symbol] && ["/", "-"].includes(symbol))) {
254
251
  parsedLeftExpression = `(${leftExpression})`;
255
252
  }
256
253
  else {
257
254
  parsedLeftExpression = leftExpression;
258
255
  }
259
- if (operatorPrecedence[operatorA] <= operatorPrecedence[symbol] ||
260
- (operatorPrecedence[operatorA] === operatorPrecedence[symbol] && ["/", "-"].includes(symbol))) {
256
+ if (operatorPrecedence[operatorA] <= operatorPrecedence[symbol] || (operatorPrecedence[operatorA] === operatorPrecedence[symbol] && ["/", "-"].includes(symbol))) {
261
257
  parsedRightExpression = `(${rightExpression})`;
262
258
  }
263
259
  else {
@@ -277,7 +273,7 @@ export class Parser {
277
273
  const formulaRPN = this.buildRPN(formula);
278
274
  const calculationResult = {
279
275
  result: undefined,
280
- errorString: null,
276
+ errorString: null
281
277
  };
282
278
  if (!formulaRPN)
283
279
  return calculationResult;
@@ -287,7 +283,7 @@ export class Parser {
287
283
  if (!this.allowedOperators.has(frontItem)) {
288
284
  const [sign, variableKey] = /^[+-]/.test(frontItem) ? [frontItem[0], frontItem.slice(1)] : ["", frontItem];
289
285
  const operandValue = Number.parseFloat(this.variables.get(variableKey)?.toString() ?? variableKey);
290
- const number = Number.parseFloat(sign + "1") * operandValue;
286
+ const number = Number.parseFloat(`${sign}1`) * operandValue;
291
287
  calcStack.push(Big(number));
292
288
  }
293
289
  else {
@@ -321,7 +317,7 @@ export class Parser {
321
317
  }
322
318
  }
323
319
  catch (error) {
324
- calculationResult.errorString = error;
320
+ calculationResult.errorString = error instanceof Error ? error.message : "Unknown error during calculation";
325
321
  return calculationResult;
326
322
  }
327
323
  }
@@ -330,7 +326,8 @@ export class Parser {
330
326
  calculationResult.errorString = "Calculation error: Empty result stack";
331
327
  return calculationResult;
332
328
  }
333
- calculationResult.result = parseFloat(calcStack.top().toString());
329
+ const topValue = calcStack.top();
330
+ calculationResult.result = topValue !== undefined ? parseFloat(topValue.toString()) : undefined;
334
331
  return calculationResult;
335
332
  }
336
333
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fw-components/formula-editor",
3
- "version": "2.3.4",
3
+ "version": "2.4.0",
4
4
  "description": "A WYSIWYG type formula editor",
5
5
  "main": "dist/formula-editor/src/formula-editor.js",
6
6
  "exports": {
@@ -13,13 +13,12 @@
13
13
  "access": "public"
14
14
  },
15
15
  "scripts": {
16
- "start": "es-dev-server --app-index ../../index.html --node-resolve --watch --open",
17
16
  "build": "tsc --composite false",
18
17
  "dev": "tsc -w --composite false",
19
18
  "prepublishOnly": "npm run build"
20
19
  },
21
20
  "dependencies": {
22
- "@fw-components/styles": "^2.3.4",
21
+ "@fw-components/styles": "^2.4.0",
23
22
  "big.js": "6.2.2",
24
23
  "lit": "3.3.3",
25
24
  "match-sorter": "8.3.0"
@@ -35,5 +34,5 @@
35
34
  "es-dev-server": "2.1.0",
36
35
  "typescript": "5.9.3"
37
36
  },
38
- "gitHead": "d3d4e7e2fbb77bfc5565100ab9487703f45d78cf"
37
+ "gitHead": "1aabc46174564e7c9f3046218744244a77568b54"
39
38
  }
package/tsconfig.json CHANGED
@@ -1,20 +1,9 @@
1
1
  {
2
- "compilerOptions": {
3
- "experimentalDecorators": true,
4
- "useDefineForClassFields":false,
5
- "outDir": "./dist",
6
- "rootDir": "../",
7
- "target": "esnext",
8
- "module": "esnext",
9
- "moduleResolution": "node",
10
- "declaration": true,
11
- "declarationDir": "./dist/types",
12
- "composite": false,
13
- "lib": ["esnext", "dom"],
14
- "esModuleInterop": true,
15
- "skipLibCheck": true,
16
- "noEmitOnError": false
17
- },
18
- "include": ["src/**/*.ts"],
19
- "exclude": ["node_modules", "dist"]
20
- }
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "./dist",
6
+ "allowJs": true
7
+ },
8
+ "include": ["src"]
9
+ }