@fw-components/formula-editor 2.0.7-formula-editor.36 → 2.0.7-formula-editor.38
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/formula-editor/src/formula-editor.js +14 -10
- package/dist/formula-editor/src/styles/editor.js +3 -3
- package/dist/formula-editor/src/styles/suggestion-menu.js +1 -6
- package/dist/formula-editor/src/suggestion-menu.js +11 -11
- package/dist/formula-editor/src/types/index.js +0 -8
- package/dist/formula-editor/src/utils/parser.js +22 -21
- package/dist/formula-editor/src/utils/queue.js +1 -4
- package/dist/formula-editor/src/utils/recommendor.js +5 -5
- package/dist/formula-editor/src/utils/stack.js +0 -3
- package/package.json +2 -2
|
@@ -37,7 +37,7 @@ let FormulaEditor = class FormulaEditor extends LitElement {
|
|
|
37
37
|
this._adjustTextAreaHeight();
|
|
38
38
|
}
|
|
39
39
|
if (_changedProperties.has("variables")) {
|
|
40
|
-
this._parser = new Parser(this.variables, this.formulaRegex, this.allowedNumbers, this.allowedOperators, this.
|
|
40
|
+
this._parser = new Parser(this.variables, this.minSuggestionLen, this.formulaRegex, this.allowedNumbers, this.allowedOperators, this.variableType);
|
|
41
41
|
this.recommendations = Array.from(this.variables.keys());
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -63,9 +63,9 @@ let FormulaEditor = class FormulaEditor extends LitElement {
|
|
|
63
63
|
*/
|
|
64
64
|
parseInput(recommendation = "") {
|
|
65
65
|
this.currentCursorPosition = this.editor.selectionStart;
|
|
66
|
-
const
|
|
67
|
-
this.recommendations =
|
|
68
|
-
this.errorString =
|
|
66
|
+
const { recommendations, errorString, formattedString, newCursorPosition } = this._parser.parseInput(this.content, this.currentCursorPosition, recommendation);
|
|
67
|
+
this.recommendations = recommendations;
|
|
68
|
+
this.errorString = errorString;
|
|
69
69
|
/**
|
|
70
70
|
* Don't modify the text stream manually if the text is being composed,
|
|
71
71
|
* unless the user manually chooses to do so by selecting a recommendation.
|
|
@@ -74,11 +74,11 @@ let FormulaEditor = class FormulaEditor extends LitElement {
|
|
|
74
74
|
* @see https://bugs.chromium.org/p/chromium/issues/detail?id=689541
|
|
75
75
|
*/
|
|
76
76
|
if (this.lastInputType !== "insertCompositionText" || recommendation) {
|
|
77
|
-
this.content =
|
|
77
|
+
this.content = formattedString;
|
|
78
78
|
}
|
|
79
79
|
if (Boolean(recommendation)) {
|
|
80
80
|
this.recommendations = [];
|
|
81
|
-
this.currentCursorPosition =
|
|
81
|
+
this.currentCursorPosition = newCursorPosition;
|
|
82
82
|
/* update cursor position in text area */
|
|
83
83
|
setTimeout(() => {
|
|
84
84
|
this.editor.setSelectionRange(this.currentCursorPosition, this.currentCursorPosition);
|
|
@@ -97,7 +97,8 @@ let FormulaEditor = class FormulaEditor extends LitElement {
|
|
|
97
97
|
formatFormula() {
|
|
98
98
|
if (!this.content)
|
|
99
99
|
return;
|
|
100
|
-
|
|
100
|
+
const newContent = this._parser.addParentheses(this.content);
|
|
101
|
+
this.content = newContent && newContent.length ? newContent : this.content;
|
|
101
102
|
this.parseInput();
|
|
102
103
|
this.recommendations = [];
|
|
103
104
|
}
|
|
@@ -131,10 +132,10 @@ let FormulaEditor = class FormulaEditor extends LitElement {
|
|
|
131
132
|
return html `
|
|
132
133
|
<style>${FormulaEditorStyles}</style>
|
|
133
134
|
|
|
134
|
-
${this.label ? html `<label for="
|
|
135
|
+
${this.label ? html `<label for="fw-formula-editor" class="editor-label">${this.label}</label>` : ""}
|
|
135
136
|
|
|
136
137
|
<textarea
|
|
137
|
-
id="
|
|
138
|
+
id="fw-formula-editor"
|
|
138
139
|
class=${this.errorString?.length ? "error" : ""}
|
|
139
140
|
.value=${this.content}
|
|
140
141
|
.placeholder=${this.placeholder}
|
|
@@ -187,6 +188,9 @@ __decorate([
|
|
|
187
188
|
__decorate([
|
|
188
189
|
property()
|
|
189
190
|
], FormulaEditor.prototype, "variables", void 0);
|
|
191
|
+
__decorate([
|
|
192
|
+
property()
|
|
193
|
+
], FormulaEditor.prototype, "variableType", void 0);
|
|
190
194
|
__decorate([
|
|
191
195
|
property()
|
|
192
196
|
], FormulaEditor.prototype, "minSuggestionLen", void 0);
|
|
@@ -203,7 +207,7 @@ __decorate([
|
|
|
203
207
|
property()
|
|
204
208
|
], FormulaEditor.prototype, "allowedOperators", void 0);
|
|
205
209
|
__decorate([
|
|
206
|
-
query("#
|
|
210
|
+
query("#fw-formula-editor")
|
|
207
211
|
], FormulaEditor.prototype, "editor", void 0);
|
|
208
212
|
__decorate([
|
|
209
213
|
query("suggestion-menu")
|
|
@@ -7,7 +7,7 @@ export const FormulaEditorStyles = css `
|
|
|
7
7
|
margin-bottom: var(--fe-label-margin-bottom, 1px);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
#
|
|
10
|
+
#fw-formula-editor {
|
|
11
11
|
display: block;
|
|
12
12
|
resize: none;
|
|
13
13
|
padding: var(--fe-padding, 4px);
|
|
@@ -26,13 +26,13 @@ export const FormulaEditorStyles = css `
|
|
|
26
26
|
line-height: 1.5;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
#
|
|
29
|
+
#fw-formula-editor:empty:before {
|
|
30
30
|
content: attr(placeholder);
|
|
31
31
|
color: var(--fe-placeholder-color, grey);
|
|
32
32
|
pointer-events: none;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
#
|
|
35
|
+
#fw-formula-editor.error {
|
|
36
36
|
text-decoration: underline;
|
|
37
37
|
-webkit-text-decoration-style: wavy;
|
|
38
38
|
text-decoration-style: wavy;
|
|
@@ -35,7 +35,7 @@ export const SuggestionMenuStyles = css `
|
|
|
35
35
|
|
|
36
36
|
/* Scrollbar styling */
|
|
37
37
|
::-webkit-scrollbar {
|
|
38
|
-
width:
|
|
38
|
+
width: 7px;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
::-webkit-scrollbar-track {
|
|
@@ -50,9 +50,4 @@ export const SuggestionMenuStyles = css `
|
|
|
50
50
|
::-webkit-scrollbar-thumb:hover {
|
|
51
51
|
background: #aaa;
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
/* Optional shadow for the dropdown */
|
|
55
|
-
.content {
|
|
56
|
-
box-shadow: 1px 2px 5px rgba(0, 0, 0, 0.13);
|
|
57
|
-
}
|
|
58
53
|
`;
|
|
@@ -13,7 +13,7 @@ let SuggestionMenu = class SuggestionMenu extends LitElement {
|
|
|
13
13
|
this.recommendations = [];
|
|
14
14
|
this.recommendationLabels = new Map();
|
|
15
15
|
this.onRecommendationClick = () => { };
|
|
16
|
-
this.
|
|
16
|
+
this._currentFocusedIndex = -1;
|
|
17
17
|
}
|
|
18
18
|
scrollToSelectedRecommendation(index) {
|
|
19
19
|
const listItem = this.suggestionList?.querySelectorAll("li")[index];
|
|
@@ -28,27 +28,27 @@ let SuggestionMenu = class SuggestionMenu extends LitElement {
|
|
|
28
28
|
navigate(direction) {
|
|
29
29
|
if (!this.recommendations?.length)
|
|
30
30
|
return;
|
|
31
|
-
let newIndex = this.
|
|
31
|
+
let newIndex = this._currentFocusedIndex;
|
|
32
32
|
if (direction === "down")
|
|
33
|
-
newIndex = (this.
|
|
33
|
+
newIndex = (this._currentFocusedIndex + 1) % this.recommendations.length;
|
|
34
34
|
else if (direction === "up")
|
|
35
|
-
newIndex = (this.
|
|
36
|
-
this.
|
|
35
|
+
newIndex = (this._currentFocusedIndex - 1 + this.recommendations.length) % this.recommendations.length;
|
|
36
|
+
this._currentFocusedIndex = newIndex;
|
|
37
37
|
this.scrollToSelectedRecommendation(newIndex);
|
|
38
38
|
}
|
|
39
|
-
handleRecommendationSelect(index = this.
|
|
39
|
+
handleRecommendationSelect(index = this._currentFocusedIndex) {
|
|
40
40
|
const recommendation = this.recommendations[index];
|
|
41
41
|
if (!recommendation)
|
|
42
42
|
return;
|
|
43
43
|
this.onRecommendationClick(recommendation);
|
|
44
|
-
this.
|
|
44
|
+
this._currentFocusedIndex = -1;
|
|
45
45
|
}
|
|
46
46
|
render() {
|
|
47
47
|
return html `
|
|
48
48
|
<style>${SuggestionMenuStyles}</style>
|
|
49
|
-
<ul class="
|
|
49
|
+
<ul class="fw-formula-suggestion-menu" @mousedown=${(e) => e.preventDefault()}>
|
|
50
50
|
${this.recommendations.map((recommendation, index) => html `<li
|
|
51
|
-
class="${this.
|
|
51
|
+
class="${this._currentFocusedIndex === index ? "selected" : ""}"
|
|
52
52
|
@click=${(e) => this.handleRecommendationSelect(index)}
|
|
53
53
|
>${this.recommendationLabels.get(recommendation) ?? recommendation}</li>`)}
|
|
54
54
|
</ul>
|
|
@@ -66,9 +66,9 @@ __decorate([
|
|
|
66
66
|
], SuggestionMenu.prototype, "onRecommendationClick", void 0);
|
|
67
67
|
__decorate([
|
|
68
68
|
state()
|
|
69
|
-
], SuggestionMenu.prototype, "
|
|
69
|
+
], SuggestionMenu.prototype, "_currentFocusedIndex", void 0);
|
|
70
70
|
__decorate([
|
|
71
|
-
query(".
|
|
71
|
+
query(".fw-formula-suggestion-menu")
|
|
72
72
|
], SuggestionMenu.prototype, "suggestionList", void 0);
|
|
73
73
|
SuggestionMenu = __decorate([
|
|
74
74
|
customElement("suggestion-menu")
|
|
@@ -4,14 +4,6 @@ export var Expectation;
|
|
|
4
4
|
Expectation[Expectation["OPERATOR"] = 1] = "OPERATOR";
|
|
5
5
|
Expectation[Expectation["UNDEFINED"] = 2] = "UNDEFINED";
|
|
6
6
|
})(Expectation || (Expectation = {}));
|
|
7
|
-
export var Operator;
|
|
8
|
-
(function (Operator) {
|
|
9
|
-
Operator["PLUS"] = "+";
|
|
10
|
-
Operator["MINUS"] = "-";
|
|
11
|
-
Operator["MUL"] = "*";
|
|
12
|
-
Operator["DIV"] = "/";
|
|
13
|
-
Operator["NONE"] = "";
|
|
14
|
-
})(Operator || (Operator = {}));
|
|
15
7
|
export class Formula {
|
|
16
8
|
constructor(name, formulaString, precision = -1) {
|
|
17
9
|
this.error = null;
|
|
@@ -3,15 +3,16 @@ 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 { operatorPrecedence, unaryOperators } from "./constants.js";
|
|
6
|
+
import { operatorPrecedence, unaryOperators, mathematicalOperators } from "./constants.js";
|
|
7
7
|
import { getFormulaTokens } from "./get-formula-tokens.js";
|
|
8
8
|
export class Parser {
|
|
9
|
-
constructor(variables, formulaRegex, allowedNumbers, allowedOperators,
|
|
9
|
+
constructor(variables, minSuggestionLen, formulaRegex, allowedNumbers, allowedOperators, variableType) {
|
|
10
10
|
this.variables = variables;
|
|
11
11
|
this.formulaRegex = formulaRegex;
|
|
12
12
|
this._recommender = new Recommender(Array.from(this.variables.keys()), minSuggestionLen);
|
|
13
|
-
this.allowedNumbers = allowedNumbers;
|
|
14
|
-
this.allowedOperators = allowedOperators;
|
|
13
|
+
this.allowedNumbers = allowedNumbers ?? true;
|
|
14
|
+
this.allowedOperators = allowedOperators ?? mathematicalOperators;
|
|
15
|
+
this.variableType = variableType;
|
|
15
16
|
}
|
|
16
17
|
isNumber(value) {
|
|
17
18
|
if (!this.allowedNumbers || value.trim() === "")
|
|
@@ -74,14 +75,21 @@ export class Parser {
|
|
|
74
75
|
/**
|
|
75
76
|
* Error checks
|
|
76
77
|
* skip error check if there is one already
|
|
77
|
-
|
|
78
|
+
*/
|
|
78
79
|
if (expectation != Expectation.UNDEFINED) {
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Unknown symbol/variable/word
|
|
82
|
+
*/
|
|
83
|
+
if (!(isNumber || isOperator || isBracket || isSpace)) {
|
|
84
|
+
parseOutput.errorString = `${this.variableType} : ${token} does not exist`;
|
|
85
|
+
expectation = Expectation.UNDEFINED;
|
|
86
|
+
}
|
|
87
|
+
else if (this.allowedOperators.has(previousToken) && isOperator) {
|
|
88
|
+
parseOutput.errorString = `Use ${this.variableType}${this.allowedNumbers ? " or numbers" : ""} after ${previousToken}. Pls do not use consecutive two mathametical operators (+,-,*,/,^)`;
|
|
81
89
|
expectation = Expectation.UNDEFINED;
|
|
82
90
|
}
|
|
83
91
|
else if (parentheses.isEmpty() && token === ")") {
|
|
84
|
-
parseOutput.errorString =
|
|
92
|
+
parseOutput.errorString = "Unexpected closing bracket. Make sure all opening brackets '(' have matching closing brackets ')'.";
|
|
85
93
|
expectation = Expectation.UNDEFINED;
|
|
86
94
|
}
|
|
87
95
|
/**
|
|
@@ -90,35 +98,28 @@ export class Parser {
|
|
|
90
98
|
*/
|
|
91
99
|
else if (expectation === Expectation.VARIABLE && !isNumber && !isSpace && token != "("
|
|
92
100
|
&& !((unaryOperators.includes(token)) && (!parsedString.trim() || previousToken === "(" || this.allowedOperators.has(previousToken)))) {
|
|
93
|
-
parseOutput.errorString = `
|
|
101
|
+
parseOutput.errorString = `Use ${this.variableType}${this.allowedNumbers ? " or numbers" : ""} after ${previousToken}.`;
|
|
94
102
|
expectation = Expectation.UNDEFINED;
|
|
95
103
|
}
|
|
96
104
|
/**
|
|
97
105
|
* Multiple number/variable together without operator
|
|
98
106
|
*/
|
|
99
107
|
else if (expectation === Expectation.OPERATOR && !isOperator && !isSpace && token != ")") {
|
|
100
|
-
parseOutput.errorString = `
|
|
101
|
-
expectation = Expectation.UNDEFINED;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Unknown symbol/variable/word
|
|
105
|
-
*/
|
|
106
|
-
else if (!(isNumber || isOperator || isBracket || isSpace)) {
|
|
107
|
-
parseOutput.errorString = `Unknown word at position ${currentPosition}`;
|
|
108
|
+
parseOutput.errorString = `Use mathametical operators (${Array.from(this.allowedOperators).join(",")}) after ${previousToken}.`;
|
|
108
109
|
expectation = Expectation.UNDEFINED;
|
|
109
110
|
}
|
|
110
111
|
/**
|
|
111
112
|
* division by zero
|
|
112
113
|
*/
|
|
113
114
|
else if (isNumber && previousToken === "/" && (this.variables.get(token) === 0 || Number(token) === 0)) {
|
|
114
|
-
parseOutput.errorString = `Division by zero
|
|
115
|
+
parseOutput.errorString = `Division by zero is not possible`;
|
|
115
116
|
expectation = Expectation.UNDEFINED;
|
|
116
117
|
}
|
|
117
118
|
/**
|
|
118
119
|
* Empty brackets
|
|
119
120
|
*/
|
|
120
121
|
else if (previousToken === "(" && token === ")") {
|
|
121
|
-
parseOutput.errorString = `
|
|
122
|
+
parseOutput.errorString = `Pls do not use empty brackets ().`;
|
|
122
123
|
expectation = Expectation.UNDEFINED;
|
|
123
124
|
}
|
|
124
125
|
}
|
|
@@ -151,10 +152,10 @@ export class Parser {
|
|
|
151
152
|
parseOutput.recommendations = !parseOutput.errorString?.length ? Array.from(this.variables.keys()) : [];
|
|
152
153
|
}
|
|
153
154
|
if (this.allowedOperators.has(previousToken)) {
|
|
154
|
-
parseOutput.errorString = `
|
|
155
|
+
parseOutput.errorString = `Pls do not use mathametical operators (${Array.from(this.allowedOperators).join(",")}) at the end.`;
|
|
155
156
|
}
|
|
156
157
|
if (!parentheses.isEmpty()) {
|
|
157
|
-
parseOutput.errorString =
|
|
158
|
+
parseOutput.errorString = "Unexpected opening bracket. Make sure all closing brackets ')' have matching opening brackets '('.";
|
|
158
159
|
}
|
|
159
160
|
return parseOutput;
|
|
160
161
|
}
|
|
@@ -18,13 +18,10 @@ export class Queue {
|
|
|
18
18
|
}
|
|
19
19
|
peek() {
|
|
20
20
|
if (this.isEmpty())
|
|
21
|
-
|
|
21
|
+
return undefined;
|
|
22
22
|
return this._elements[this._head];
|
|
23
23
|
}
|
|
24
24
|
isEmpty() {
|
|
25
25
|
return this._head === this._tail;
|
|
26
26
|
}
|
|
27
|
-
print() {
|
|
28
|
-
console.log(this._elements);
|
|
29
|
-
}
|
|
30
27
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { matchSorter } from "match-sorter";
|
|
2
2
|
export class Recommender {
|
|
3
3
|
constructor(variables, minSuggestionLen) {
|
|
4
|
-
this.
|
|
4
|
+
this._wordLimitForSuggestions = minSuggestionLen > 0 ? minSuggestionLen : 1;
|
|
5
5
|
this.variableList = variables;
|
|
6
6
|
}
|
|
7
|
-
getRecommendations(
|
|
8
|
-
if (
|
|
7
|
+
getRecommendations(inputString) {
|
|
8
|
+
if (inputString.length < this._wordLimitForSuggestions)
|
|
9
9
|
return [];
|
|
10
|
-
const recommendations = matchSorter(this.variableList,
|
|
11
|
-
if (recommendations.length === 0
|
|
10
|
+
const recommendations = matchSorter(this.variableList, inputString);
|
|
11
|
+
if (recommendations.length === 0)
|
|
12
12
|
return [];
|
|
13
13
|
return recommendations;
|
|
14
14
|
}
|
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.38",
|
|
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": "a7118acac7f6e7268373b36a909f6f1720a12ce4"
|
|
35
35
|
}
|