@fw-components/formula-editor 2.0.7-formula-editor-enhancements.11 → 2.0.7-formula-editor.21
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 +64 -179
- package/dist/formula-editor/src/styles/{formula-editor-styles.js → editor.js} +2 -2
- package/dist/formula-editor/src/styles/suggestion-menu.js +58 -0
- package/dist/formula-editor/src/suggestion-menu.js +72 -86
- package/dist/formula-editor/src/{helpers/types.js → types/index.js} +6 -0
- package/dist/formula-editor/src/utils/constants.js +8 -0
- package/dist/formula-editor/src/{parser.js → utils/parser.js} +92 -135
- package/dist/formula-editor/src/utils/queue.js +28 -0
- package/dist/formula-editor/src/utils/recommendor.js +15 -0
- package/dist/formula-editor/src/utils/stack.js +20 -0
- package/package.json +9 -3
- package/dist/formula-editor/src/cursor.js +0 -142
- package/dist/formula-editor/src/formula-builder.js +0 -139
- package/dist/formula-editor/src/formula-creator.js +0 -83
- package/dist/formula-editor/src/helpers.js +0 -54
- package/dist/formula-editor/src/recommendor.js +0 -18
- package/dist/formula-editor/src/sub-components/operator-input.js +0 -24
- package/dist/styles/src/button-styles.js +0 -419
|
@@ -1,195 +1,152 @@
|
|
|
1
1
|
import Big from "big.js";
|
|
2
|
-
import { Expectation, Queue, Stack } from "./helpers.js";
|
|
3
2
|
import { Recommender } from "./recommendor.js";
|
|
3
|
+
import { Stack } from "./stack.js";
|
|
4
|
+
import { Queue } from "./queue.js";
|
|
5
|
+
import { Expectation } from "../types";
|
|
6
|
+
import { mathematicalOperators, operatorPrecedence } from "./constants.js";
|
|
4
7
|
export class Parser {
|
|
5
8
|
constructor(variables, minSuggestionLen) {
|
|
6
|
-
this.mathematicalOperators = new Set(["^", "+", "-", "*", "/"]);
|
|
7
|
-
this.operatorPrecedence = {
|
|
8
|
-
"^": 3,
|
|
9
|
-
"/": 2,
|
|
10
|
-
"*": 2,
|
|
11
|
-
"+": 1,
|
|
12
|
-
"-": 1,
|
|
13
|
-
};
|
|
14
9
|
this.variables = variables;
|
|
15
|
-
this._recommender = new Recommender(this.variables, minSuggestionLen);
|
|
10
|
+
this._recommender = new Recommender(Array.from(this.variables.keys()), minSuggestionLen);
|
|
16
11
|
}
|
|
17
12
|
parseInput(formula, prevCurPos = null, recommendation = null) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// show "Unclosed parenthesis error" for positions which are far behind
|
|
21
|
-
// our current token
|
|
22
|
-
let parentheses = new Stack();
|
|
23
|
-
// The HTML formatted string which we eventually show on the view.
|
|
24
|
-
let formattedString = ``;
|
|
25
|
-
// The expectation that we have for the current token.
|
|
13
|
+
const tokens = formula.match(/'[^']*'|\d+|[A-Za-z_][A-Za-z0-9_]*|[-+(),*^/:?\s]/g);
|
|
14
|
+
const parentheses = new Stack();
|
|
26
15
|
let expectation = Expectation.VARIABLE;
|
|
27
|
-
// Position of the current token in the formula string.
|
|
28
16
|
let currentPosition = 0;
|
|
29
|
-
// Previous 'token' (not a space or a new line) that we just encountered.
|
|
30
17
|
let previousToken = "";
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
formattedContent: null,
|
|
36
|
-
formattedString: null,
|
|
18
|
+
let parsedString = "";
|
|
19
|
+
const parseOutput = {
|
|
20
|
+
recommendations: [],
|
|
21
|
+
formattedString: "",
|
|
37
22
|
newCursorPosition: prevCurPos ?? -1,
|
|
38
23
|
errorString: null,
|
|
39
24
|
};
|
|
40
|
-
if (!formula.trim()) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const parser = new DOMParser();
|
|
45
|
-
const doc = parser.parseFromString(formattedString, "text/html");
|
|
46
|
-
parseOutput.formattedContent = doc.querySelector("body");
|
|
47
|
-
parseOutput.formattedString = formattedString;
|
|
48
|
-
parseOutput.newCursorPosition = recommendation.length;
|
|
49
|
-
return parseOutput;
|
|
50
|
-
}
|
|
25
|
+
if (!formula.trim() && recommendation) {
|
|
26
|
+
parseOutput.formattedString = recommendation;
|
|
27
|
+
parseOutput.newCursorPosition = recommendation.length;
|
|
28
|
+
return parseOutput;
|
|
51
29
|
}
|
|
52
30
|
tokens?.forEach((token) => {
|
|
53
|
-
// It is a number is either it's in the defined variables, or
|
|
54
|
-
// it's a valid number literal.
|
|
55
31
|
let isNumber = token.trim() !== "" && (this.variables.has(token) || !Number.isNaN(Number(token)));
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
32
|
+
const isOperator = mathematicalOperators.has(token);
|
|
33
|
+
const isSpace = token.trim() === "";
|
|
34
|
+
const isBracket = token === "(" || token === ")";
|
|
59
35
|
if (isSpace) {
|
|
60
|
-
formattedString
|
|
36
|
+
parseOutput.formattedString += token;
|
|
61
37
|
currentPosition += token.length;
|
|
62
38
|
return;
|
|
63
39
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Check if the cursor is in between the formula string
|
|
42
|
+
*
|
|
43
|
+
* - If we've got a recommendation to add, replace the word with the recommendation
|
|
44
|
+
* - Update recommendations based on the token/word
|
|
45
|
+
*/
|
|
70
46
|
if (currentPosition <= prevCurPos && currentPosition + token.length >= prevCurPos) {
|
|
71
47
|
if (recommendation) {
|
|
72
|
-
// Since we are sure that the recommendation will always correspond
|
|
73
|
-
// to a variable.
|
|
74
48
|
isNumber = true;
|
|
75
|
-
if (
|
|
76
|
-
// append recommendation at the end if token is an operator
|
|
49
|
+
if (mathematicalOperators.has(token)) {
|
|
77
50
|
const updatedTokenString = `${token} ${recommendation}`;
|
|
78
|
-
formattedString += updatedTokenString;
|
|
51
|
+
parseOutput.formattedString += updatedTokenString;
|
|
79
52
|
currentPosition += updatedTokenString.length;
|
|
80
53
|
parseOutput.newCursorPosition = currentPosition;
|
|
81
54
|
recommendation = null;
|
|
82
55
|
return;
|
|
83
56
|
}
|
|
84
57
|
;
|
|
85
|
-
// If the new cursor length somehow becomes larger than the
|
|
86
|
-
// length of the formula string, setting the caret to that
|
|
87
|
-
// length will move the caret to the start. Although this overflow
|
|
88
|
-
// won't happen, but still, this check prevents that.
|
|
89
58
|
const updatedTokenLength = recommendation.length - token.length;
|
|
90
59
|
parseOutput.newCursorPosition = Math.min(parseOutput.newCursorPosition, formula.length) + updatedTokenLength;
|
|
91
60
|
token = recommendation;
|
|
92
61
|
recommendation = null;
|
|
93
62
|
}
|
|
94
|
-
parseOutput.recommendations = this._recommender.
|
|
63
|
+
parseOutput.recommendations = this._recommender.getRecommendations(token);
|
|
95
64
|
}
|
|
96
|
-
|
|
97
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Error checks
|
|
67
|
+
* skip error check if there is one already
|
|
68
|
+
*/
|
|
98
69
|
if (expectation != Expectation.UNDEFINED) {
|
|
99
|
-
if (
|
|
70
|
+
if (mathematicalOperators.has(previousToken) && isOperator) {
|
|
100
71
|
parseOutput.errorString = `Multiple operators at position ${currentPosition}`;
|
|
101
72
|
expectation = Expectation.UNDEFINED;
|
|
102
73
|
}
|
|
103
|
-
|
|
104
|
-
else if (parentheses.isEmpty() && token == ")") {
|
|
74
|
+
else if (parentheses.isEmpty() && token === ")") {
|
|
105
75
|
parseOutput.errorString = `Unexpected ')' at position ${currentPosition}`;
|
|
106
|
-
tokenClassName += " error";
|
|
107
76
|
expectation = Expectation.UNDEFINED;
|
|
108
77
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
else if (expectation
|
|
114
|
-
!
|
|
115
|
-
!isSpace &&
|
|
116
|
-
token != "(" &&
|
|
117
|
-
!((token == "-" || token == "+") &&
|
|
118
|
-
(!currentTokens.trim() || previousToken === "(" || this.mathematicalOperators.has(previousToken)))) {
|
|
78
|
+
/**
|
|
79
|
+
* Operator or ')' after an operator (Eg: '23 / *' or '23 / )')
|
|
80
|
+
* No error for Unary `+` and `-` as they might represent a positive or negative number respectively
|
|
81
|
+
*/
|
|
82
|
+
else if (expectation === Expectation.VARIABLE && !isNumber && !isSpace && token != "("
|
|
83
|
+
&& !((token === "-" || token === "+") && (!parsedString.trim() || previousToken === "(" || mathematicalOperators.has(previousToken)))) {
|
|
119
84
|
parseOutput.errorString = `Expected variable/number at position ${currentPosition}`;
|
|
120
|
-
tokenClassName += " error";
|
|
121
85
|
expectation = Expectation.UNDEFINED;
|
|
122
86
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
!isSpace &&
|
|
128
|
-
token != ")") {
|
|
87
|
+
/**
|
|
88
|
+
* Multiple number/variable together without operator
|
|
89
|
+
*/
|
|
90
|
+
else if (expectation === Expectation.OPERATOR && !isOperator && !isSpace && token != ")") {
|
|
129
91
|
parseOutput.errorString = `Expected mathematical operator at position ${currentPosition}`;
|
|
130
|
-
tokenClassName += " error";
|
|
131
92
|
expectation = Expectation.UNDEFINED;
|
|
132
93
|
}
|
|
133
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Unknown symbol/variable/word
|
|
96
|
+
*/
|
|
134
97
|
else if (!(isNumber || isOperator || isBracket || isSpace)) {
|
|
135
98
|
parseOutput.errorString = `Unknown word at position ${currentPosition}`;
|
|
136
|
-
tokenClassName += " error";
|
|
137
99
|
expectation = Expectation.UNDEFINED;
|
|
138
100
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
(this.variables.get(token) == 0 || Number(token) == 0)) {
|
|
101
|
+
/**
|
|
102
|
+
* division by zero
|
|
103
|
+
*/
|
|
104
|
+
else if (isNumber && previousToken === "/" && (this.variables.get(token) === 0 || Number(token) === 0)) {
|
|
144
105
|
parseOutput.errorString = `Division by zero at position ${currentPosition}`;
|
|
145
|
-
tokenClassName += " error";
|
|
146
106
|
expectation = Expectation.UNDEFINED;
|
|
147
107
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
else if (previousToken
|
|
108
|
+
/**
|
|
109
|
+
* Empty brackets
|
|
110
|
+
*/
|
|
111
|
+
else if (previousToken === "(" && token === ")") {
|
|
152
112
|
parseOutput.errorString = `Empty brackets at position ${currentPosition}`;
|
|
153
|
-
tokenClassName += " error";
|
|
154
113
|
expectation = Expectation.UNDEFINED;
|
|
155
114
|
}
|
|
156
115
|
}
|
|
157
|
-
|
|
158
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Setting the expectation for the next token, if no error is there till now
|
|
118
|
+
*/
|
|
159
119
|
if (expectation != Expectation.UNDEFINED) {
|
|
160
|
-
if (token
|
|
120
|
+
if (token === "(" || isOperator) {
|
|
161
121
|
expectation = Expectation.VARIABLE;
|
|
162
122
|
}
|
|
163
|
-
else if (token
|
|
123
|
+
else if (token === ")" || isNumber) {
|
|
164
124
|
expectation = Expectation.OPERATOR;
|
|
165
125
|
}
|
|
166
126
|
}
|
|
167
|
-
if (token
|
|
127
|
+
if (token === "(")
|
|
168
128
|
parentheses.push(currentPosition);
|
|
169
|
-
else if (token
|
|
129
|
+
else if (token === ")")
|
|
170
130
|
parentheses.pop();
|
|
171
|
-
formattedString
|
|
172
|
-
previousToken = token;
|
|
131
|
+
parseOutput.formattedString += token;
|
|
173
132
|
currentPosition += token.length;
|
|
174
|
-
|
|
133
|
+
parsedString += token;
|
|
134
|
+
previousToken = token;
|
|
175
135
|
});
|
|
176
136
|
if (recommendation) {
|
|
177
|
-
parseOutput.newCursorPosition = Math.min(parseOutput.newCursorPosition +
|
|
178
|
-
|
|
179
|
-
|
|
137
|
+
parseOutput.newCursorPosition = Math.min(parseOutput.newCursorPosition, formula.length) + recommendation.length;
|
|
138
|
+
parseOutput.formattedString += recommendation;
|
|
139
|
+
previousToken = recommendation;
|
|
180
140
|
}
|
|
181
|
-
|
|
182
|
-
if (this.mathematicalOperators.has(previousToken) || !previousToken.trim().length) {
|
|
141
|
+
if (mathematicalOperators.has(previousToken) || !previousToken.trim().length) {
|
|
183
142
|
parseOutput.recommendations = !parseOutput.errorString?.length ? Array.from(this.variables.keys()) : [];
|
|
184
143
|
}
|
|
185
|
-
|
|
144
|
+
if (mathematicalOperators.has(previousToken)) {
|
|
145
|
+
parseOutput.errorString = `Unexpected ending with mathematical operator at position: ${currentPosition}`;
|
|
146
|
+
}
|
|
186
147
|
if (!parentheses.isEmpty()) {
|
|
187
148
|
parseOutput.errorString = `Unclosed '(' at position: ${parentheses.top()}`;
|
|
188
149
|
}
|
|
189
|
-
const parser = new DOMParser();
|
|
190
|
-
const doc = parser.parseFromString(formattedString, "text/html");
|
|
191
|
-
parseOutput.formattedContent = doc.querySelector("body");
|
|
192
|
-
parseOutput.formattedString = formattedString;
|
|
193
150
|
return parseOutput;
|
|
194
151
|
}
|
|
195
152
|
buildRPN(formula) {
|
|
@@ -205,8 +162,8 @@ export class Parser {
|
|
|
205
162
|
const parsedTokens = [];
|
|
206
163
|
let currentTokens = "";
|
|
207
164
|
for (const token of tokens) {
|
|
208
|
-
if ((token
|
|
209
|
-
(!currentTokens.trim() || previousToken === "(" ||
|
|
165
|
+
if ((token === "+" || token === "-") &&
|
|
166
|
+
(!currentTokens.trim() || previousToken === "(" || mathematicalOperators.has(previousToken))) {
|
|
210
167
|
carriedToken = token;
|
|
211
168
|
}
|
|
212
169
|
else if (carriedToken) {
|
|
@@ -225,19 +182,19 @@ export class Parser {
|
|
|
225
182
|
const operatorStack = new Stack();
|
|
226
183
|
const outputQueue = new Queue();
|
|
227
184
|
for (const token of parsedTokens) {
|
|
228
|
-
if (token
|
|
185
|
+
if (token === "(") {
|
|
229
186
|
operatorStack.push("(");
|
|
230
187
|
}
|
|
231
|
-
else if (token
|
|
188
|
+
else if (token === ")") {
|
|
232
189
|
while (operatorStack.top() != "(") {
|
|
233
190
|
outputQueue.enqueue(operatorStack.pop());
|
|
234
191
|
}
|
|
235
192
|
operatorStack.pop();
|
|
236
193
|
}
|
|
237
|
-
else if (
|
|
238
|
-
while (
|
|
239
|
-
|
|
240
|
-
|
|
194
|
+
else if (mathematicalOperators.has(token)) {
|
|
195
|
+
while (mathematicalOperators.has(operatorStack.top()) &&
|
|
196
|
+
operatorPrecedence[token] <=
|
|
197
|
+
operatorPrecedence[operatorStack.top()]) {
|
|
241
198
|
outputQueue.enqueue(operatorStack.pop());
|
|
242
199
|
}
|
|
243
200
|
operatorStack.push(token);
|
|
@@ -270,7 +227,7 @@ export class Parser {
|
|
|
270
227
|
// If we encounter a number or a variable in the RPN, it is itself
|
|
271
228
|
// a calculated entity (say a result in itself), needs no modification
|
|
272
229
|
// and can be directly put into the result stack.
|
|
273
|
-
if (((symbol === "+" || symbol[0] === "-") && this.variables.has(symbol.substring(1))) ||
|
|
230
|
+
if (((symbol[0] === "+" || symbol[0] === "-") && this.variables.has(symbol.substring(1))) ||
|
|
274
231
|
this.variables.has(symbol) ||
|
|
275
232
|
(!isNaN(parseFloat(symbol)) && isFinite(parseFloat(symbol)))) {
|
|
276
233
|
resultStack.push(symbol);
|
|
@@ -281,7 +238,7 @@ export class Parser {
|
|
|
281
238
|
// them with the current one, adds brackets accordingly to the `results`
|
|
282
239
|
// around it, and then finally add it to the `operatorStack` for
|
|
283
240
|
// future reference.
|
|
284
|
-
else if (Object.keys(
|
|
241
|
+
else if (Object.keys(operatorPrecedence).includes(symbol)) {
|
|
285
242
|
let [rightExpression, leftExpression, operatorA, operatorB] = [
|
|
286
243
|
resultStack.pop(),
|
|
287
244
|
resultStack.pop(),
|
|
@@ -289,20 +246,20 @@ export class Parser {
|
|
|
289
246
|
operatorStack.pop(),
|
|
290
247
|
];
|
|
291
248
|
// The conditions that govern when to show a parenthesis.
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
(
|
|
295
|
-
|
|
249
|
+
if (operatorPrecedence[operatorB] <=
|
|
250
|
+
operatorPrecedence[symbol] ||
|
|
251
|
+
(operatorPrecedence[operatorB] ===
|
|
252
|
+
operatorPrecedence[symbol] &&
|
|
296
253
|
["/", "-"].includes(symbol))) {
|
|
297
254
|
parsedLeftExpression = `(${leftExpression})`;
|
|
298
255
|
}
|
|
299
256
|
else {
|
|
300
257
|
parsedLeftExpression = leftExpression;
|
|
301
258
|
}
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
(
|
|
305
|
-
|
|
259
|
+
if (operatorPrecedence[operatorA] <=
|
|
260
|
+
operatorPrecedence[symbol] ||
|
|
261
|
+
(operatorPrecedence[operatorA] ===
|
|
262
|
+
operatorPrecedence[symbol] &&
|
|
306
263
|
["/", "-"].includes(symbol))) {
|
|
307
264
|
parsedRightExpression = `(${rightExpression})`;
|
|
308
265
|
}
|
|
@@ -334,7 +291,7 @@ export class Parser {
|
|
|
334
291
|
let calcStack = new Stack();
|
|
335
292
|
while (!rpn.isEmpty()) {
|
|
336
293
|
const frontItem = rpn.dequeue();
|
|
337
|
-
if (!
|
|
294
|
+
if (!mathematicalOperators.has(frontItem)) {
|
|
338
295
|
const [sign, variableKey] = /^[+-]/.test(frontItem) ? [frontItem[0], frontItem.slice(1)] : ["", frontItem];
|
|
339
296
|
const operandValue = Number.parseFloat(this.variables.get(variableKey)?.toString() ?? variableKey);
|
|
340
297
|
const number = Number.parseFloat(sign + "1") * operandValue;
|
|
@@ -356,7 +313,7 @@ export class Parser {
|
|
|
356
313
|
calcStack.push(Big(numA).mul(Big(numB)));
|
|
357
314
|
break;
|
|
358
315
|
case "/":
|
|
359
|
-
if (parseFloat(Big(numB).toString())
|
|
316
|
+
if (parseFloat(Big(numB).toString()) === 0) {
|
|
360
317
|
calculationResult.errorString = "Division by zero encountered";
|
|
361
318
|
return calculationResult;
|
|
362
319
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export class Queue {
|
|
2
|
+
constructor() {
|
|
3
|
+
this._elements = {};
|
|
4
|
+
this._head = 0;
|
|
5
|
+
this._tail = 0;
|
|
6
|
+
}
|
|
7
|
+
enqueue(item) {
|
|
8
|
+
this._elements[this._tail] = item;
|
|
9
|
+
this._tail++;
|
|
10
|
+
}
|
|
11
|
+
dequeue() {
|
|
12
|
+
if (this._tail === this._head)
|
|
13
|
+
return undefined;
|
|
14
|
+
const element = this._elements[this._head];
|
|
15
|
+
delete this._elements[this._head];
|
|
16
|
+
this._head++;
|
|
17
|
+
return element;
|
|
18
|
+
}
|
|
19
|
+
peek() {
|
|
20
|
+
return this._elements[this._head];
|
|
21
|
+
}
|
|
22
|
+
isEmpty() {
|
|
23
|
+
return this._head === this._tail;
|
|
24
|
+
}
|
|
25
|
+
print() {
|
|
26
|
+
console.log(this._elements);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { matchSorter } from "match-sorter";
|
|
2
|
+
export class Recommender {
|
|
3
|
+
constructor(variables, minSuggestionLen) {
|
|
4
|
+
this._minimumSuggestionLength = minSuggestionLen > 0 ? minSuggestionLen : 1;
|
|
5
|
+
this.variableList = variables;
|
|
6
|
+
}
|
|
7
|
+
getRecommendations(word) {
|
|
8
|
+
if (word.length < this._minimumSuggestionLength)
|
|
9
|
+
return [];
|
|
10
|
+
const recommendations = matchSorter(this.variableList, word);
|
|
11
|
+
if (recommendations.length === 0 || (recommendations.length === 1 && recommendations[0] === word))
|
|
12
|
+
return [];
|
|
13
|
+
return recommendations;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class Stack {
|
|
2
|
+
constructor() {
|
|
3
|
+
this._elements = [];
|
|
4
|
+
}
|
|
5
|
+
push(item) {
|
|
6
|
+
this._elements.push(item);
|
|
7
|
+
}
|
|
8
|
+
pop() {
|
|
9
|
+
return this._elements.pop();
|
|
10
|
+
}
|
|
11
|
+
top() {
|
|
12
|
+
return this._elements.at(-1);
|
|
13
|
+
}
|
|
14
|
+
isEmpty() {
|
|
15
|
+
return this._elements.length === 0;
|
|
16
|
+
}
|
|
17
|
+
print() {
|
|
18
|
+
console.log(this._elements);
|
|
19
|
+
}
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fw-components/formula-editor",
|
|
3
|
-
"version": "2.0.7-formula-editor
|
|
3
|
+
"version": "2.0.7-formula-editor.21",
|
|
4
4
|
"description": "A WYSIWYG type formula editor",
|
|
5
|
-
"main": "dist/formula-editor/src/formula-
|
|
5
|
+
"main": "dist/formula-editor/src/formula-editor.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/formula-editor/src/formula-editor.js",
|
|
8
|
+
"./types": "./dist/formula-editor/src/types",
|
|
9
|
+
"./types/*.js": "./dist/formula-editor/src/types/*.js",
|
|
10
|
+
"./utils/*.js": "./dist/formula-editor/src/utils/*.js"
|
|
11
|
+
},
|
|
6
12
|
"publishConfig": {
|
|
7
13
|
"access": "public"
|
|
8
14
|
},
|
|
@@ -25,5 +31,5 @@
|
|
|
25
31
|
"@types/big.js": "^6.1.6",
|
|
26
32
|
"es-dev-server": "^2.1.0"
|
|
27
33
|
},
|
|
28
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "2ee961ac801dddf4861fa5af2fce28636ffd1d74"
|
|
29
35
|
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
export class Cursor {
|
|
2
|
-
/**
|
|
3
|
-
* The functions `getCurrentCursorPosition`, `setCurrentCursorPosition` and their
|
|
4
|
-
* helpers `_createRange` and `_isChildOf` are not used for caret manipulation,
|
|
5
|
-
* but are still in the code for future reference, if the functionality breaks
|
|
6
|
-
* somehow in some obsolete browser.
|
|
7
|
-
*/
|
|
8
|
-
static getCurrentCursorPosition(parentElement) {
|
|
9
|
-
let selection = window.getSelection(), charCount = -1, node;
|
|
10
|
-
if (selection?.focusNode) {
|
|
11
|
-
if (Cursor._isChildOf(selection.focusNode, parentElement)) {
|
|
12
|
-
node = selection.focusNode;
|
|
13
|
-
charCount = selection.focusOffset;
|
|
14
|
-
while (node) {
|
|
15
|
-
if (node === parentElement) {
|
|
16
|
-
break;
|
|
17
|
-
}
|
|
18
|
-
if (node.previousSibling) {
|
|
19
|
-
node = node.previousSibling;
|
|
20
|
-
charCount += node.textContent?.length ?? 0;
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
node = node.parentNode;
|
|
24
|
-
if (node === null) {
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return charCount;
|
|
32
|
-
}
|
|
33
|
-
static setCurrentCursorPosition(chars, element) {
|
|
34
|
-
if (chars >= 0) {
|
|
35
|
-
var selection = window.getSelection();
|
|
36
|
-
let range = Cursor._createRange(element, { count: chars }, undefined);
|
|
37
|
-
if (range) {
|
|
38
|
-
range.collapse(false);
|
|
39
|
-
selection?.removeAllRanges();
|
|
40
|
-
selection?.addRange(range);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
static _createRange(node, chars, range) {
|
|
45
|
-
if (!range) {
|
|
46
|
-
range = document.createRange();
|
|
47
|
-
range.selectNode(node);
|
|
48
|
-
range.setStart(node, 0);
|
|
49
|
-
}
|
|
50
|
-
if (chars.count === 0) {
|
|
51
|
-
range.setEnd(node, chars.count);
|
|
52
|
-
}
|
|
53
|
-
else if (node && chars.count > 0) {
|
|
54
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
55
|
-
if (node.textContent.length < chars.count) {
|
|
56
|
-
chars.count -= node.textContent.length;
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
range.setEnd(node, chars.count);
|
|
60
|
-
chars.count = 0;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
for (var lp = 0; lp < node.childNodes.length; lp++) {
|
|
65
|
-
range = Cursor._createRange(node.childNodes[lp], chars, range);
|
|
66
|
-
if (chars.count === 0) {
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return range;
|
|
73
|
-
}
|
|
74
|
-
static _isChildOf(node, parentElement) {
|
|
75
|
-
while (node !== null) {
|
|
76
|
-
if (node === parentElement) {
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
node = node.parentNode;
|
|
80
|
-
}
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
static _getComposedRange(shadowRoot) {
|
|
84
|
-
// `getSelection` is not defined for the type ShadowRoot in TS,
|
|
85
|
-
// but it does exist.
|
|
86
|
-
const sr = shadowRoot;
|
|
87
|
-
if (sr.getSelection && typeof sr.getSelection === 'function') {
|
|
88
|
-
const selection = sr.getSelection();
|
|
89
|
-
if (selection && selection.rangeCount > 0) {
|
|
90
|
-
return selection.getRangeAt(0);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
// Fallback for browsers like Firefox/Safari.
|
|
94
|
-
const selection = window.getSelection();
|
|
95
|
-
if (selection && selection.rangeCount > 0) {
|
|
96
|
-
const range = selection.getRangeAt(0);
|
|
97
|
-
if (shadowRoot.contains(range.startContainer)) {
|
|
98
|
-
return range;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
static getCaretPosition(shadowRoot, element) {
|
|
104
|
-
const range = Cursor._getComposedRange(shadowRoot);
|
|
105
|
-
if (!range) {
|
|
106
|
-
return 0;
|
|
107
|
-
}
|
|
108
|
-
const prefix = range.cloneRange();
|
|
109
|
-
prefix.selectNodeContents(element);
|
|
110
|
-
prefix.setEnd(range.endContainer, range.endOffset);
|
|
111
|
-
return prefix.toString().length;
|
|
112
|
-
}
|
|
113
|
-
static { this.setCaretPosition = (pos, parent) => {
|
|
114
|
-
for (const node of parent.childNodes) {
|
|
115
|
-
if (node.nodeType == Node.TEXT_NODE) {
|
|
116
|
-
if (node.length >= pos) {
|
|
117
|
-
const range = document.createRange();
|
|
118
|
-
const sel = window.getSelection();
|
|
119
|
-
range.setStart(node, pos);
|
|
120
|
-
range.collapse(true);
|
|
121
|
-
sel.removeAllRanges();
|
|
122
|
-
sel.addRange(range);
|
|
123
|
-
return -1;
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
pos = pos - node.length;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
pos = this.setCaretPosition(pos, node);
|
|
131
|
-
if (pos < 0) {
|
|
132
|
-
return pos;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return pos;
|
|
137
|
-
}; }
|
|
138
|
-
static getCursorRect(shadowRoot) {
|
|
139
|
-
const range = Cursor._getComposedRange(shadowRoot);
|
|
140
|
-
return range ? range.getClientRects()[0] : undefined;
|
|
141
|
-
}
|
|
142
|
-
}
|