@fw-components/formula-editor 2.0.7-formula-editor-enhancements.12 → 2.0.7-formula-editor.22
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 +84 -174
- 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 +45 -88
- 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} +89 -136
- 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,199 +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
|
-
|
|
186
|
-
if (this.mathematicalOperators.has(previousToken)) {
|
|
144
|
+
if (mathematicalOperators.has(previousToken)) {
|
|
187
145
|
parseOutput.errorString = `Unexpected ending with mathematical operator at position: ${currentPosition}`;
|
|
188
146
|
}
|
|
189
|
-
// formula has unclosed `(`
|
|
190
147
|
if (!parentheses.isEmpty()) {
|
|
191
148
|
parseOutput.errorString = `Unclosed '(' at position: ${parentheses.top()}`;
|
|
192
149
|
}
|
|
193
|
-
const parser = new DOMParser();
|
|
194
|
-
const doc = parser.parseFromString(formattedString, "text/html");
|
|
195
|
-
parseOutput.formattedContent = doc.querySelector("body");
|
|
196
|
-
parseOutput.formattedString = formattedString;
|
|
197
150
|
return parseOutput;
|
|
198
151
|
}
|
|
199
152
|
buildRPN(formula) {
|
|
@@ -209,8 +162,8 @@ export class Parser {
|
|
|
209
162
|
const parsedTokens = [];
|
|
210
163
|
let currentTokens = "";
|
|
211
164
|
for (const token of tokens) {
|
|
212
|
-
if ((token
|
|
213
|
-
(!currentTokens.trim() || previousToken === "(" ||
|
|
165
|
+
if ((token === "+" || token === "-") &&
|
|
166
|
+
(!currentTokens.trim() || previousToken === "(" || mathematicalOperators.has(previousToken))) {
|
|
214
167
|
carriedToken = token;
|
|
215
168
|
}
|
|
216
169
|
else if (carriedToken) {
|
|
@@ -229,19 +182,19 @@ export class Parser {
|
|
|
229
182
|
const operatorStack = new Stack();
|
|
230
183
|
const outputQueue = new Queue();
|
|
231
184
|
for (const token of parsedTokens) {
|
|
232
|
-
if (token
|
|
185
|
+
if (token === "(") {
|
|
233
186
|
operatorStack.push("(");
|
|
234
187
|
}
|
|
235
|
-
else if (token
|
|
188
|
+
else if (token === ")") {
|
|
236
189
|
while (operatorStack.top() != "(") {
|
|
237
190
|
outputQueue.enqueue(operatorStack.pop());
|
|
238
191
|
}
|
|
239
192
|
operatorStack.pop();
|
|
240
193
|
}
|
|
241
|
-
else if (
|
|
242
|
-
while (
|
|
243
|
-
|
|
244
|
-
|
|
194
|
+
else if (mathematicalOperators.has(token)) {
|
|
195
|
+
while (mathematicalOperators.has(operatorStack.top()) &&
|
|
196
|
+
operatorPrecedence[token] <=
|
|
197
|
+
operatorPrecedence[operatorStack.top()]) {
|
|
245
198
|
outputQueue.enqueue(operatorStack.pop());
|
|
246
199
|
}
|
|
247
200
|
operatorStack.push(token);
|
|
@@ -285,7 +238,7 @@ export class Parser {
|
|
|
285
238
|
// them with the current one, adds brackets accordingly to the `results`
|
|
286
239
|
// around it, and then finally add it to the `operatorStack` for
|
|
287
240
|
// future reference.
|
|
288
|
-
else if (Object.keys(
|
|
241
|
+
else if (Object.keys(operatorPrecedence).includes(symbol)) {
|
|
289
242
|
let [rightExpression, leftExpression, operatorA, operatorB] = [
|
|
290
243
|
resultStack.pop(),
|
|
291
244
|
resultStack.pop(),
|
|
@@ -293,20 +246,20 @@ export class Parser {
|
|
|
293
246
|
operatorStack.pop(),
|
|
294
247
|
];
|
|
295
248
|
// The conditions that govern when to show a parenthesis.
|
|
296
|
-
if (
|
|
297
|
-
|
|
298
|
-
(
|
|
299
|
-
|
|
249
|
+
if (operatorPrecedence[operatorB] <=
|
|
250
|
+
operatorPrecedence[symbol] ||
|
|
251
|
+
(operatorPrecedence[operatorB] ===
|
|
252
|
+
operatorPrecedence[symbol] &&
|
|
300
253
|
["/", "-"].includes(symbol))) {
|
|
301
254
|
parsedLeftExpression = `(${leftExpression})`;
|
|
302
255
|
}
|
|
303
256
|
else {
|
|
304
257
|
parsedLeftExpression = leftExpression;
|
|
305
258
|
}
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
(
|
|
309
|
-
|
|
259
|
+
if (operatorPrecedence[operatorA] <=
|
|
260
|
+
operatorPrecedence[symbol] ||
|
|
261
|
+
(operatorPrecedence[operatorA] ===
|
|
262
|
+
operatorPrecedence[symbol] &&
|
|
310
263
|
["/", "-"].includes(symbol))) {
|
|
311
264
|
parsedRightExpression = `(${rightExpression})`;
|
|
312
265
|
}
|
|
@@ -338,7 +291,7 @@ export class Parser {
|
|
|
338
291
|
let calcStack = new Stack();
|
|
339
292
|
while (!rpn.isEmpty()) {
|
|
340
293
|
const frontItem = rpn.dequeue();
|
|
341
|
-
if (!
|
|
294
|
+
if (!mathematicalOperators.has(frontItem)) {
|
|
342
295
|
const [sign, variableKey] = /^[+-]/.test(frontItem) ? [frontItem[0], frontItem.slice(1)] : ["", frontItem];
|
|
343
296
|
const operandValue = Number.parseFloat(this.variables.get(variableKey)?.toString() ?? variableKey);
|
|
344
297
|
const number = Number.parseFloat(sign + "1") * operandValue;
|
|
@@ -360,7 +313,7 @@ export class Parser {
|
|
|
360
313
|
calcStack.push(Big(numA).mul(Big(numB)));
|
|
361
314
|
break;
|
|
362
315
|
case "/":
|
|
363
|
-
if (parseFloat(Big(numB).toString())
|
|
316
|
+
if (parseFloat(Big(numB).toString()) === 0) {
|
|
364
317
|
calculationResult.errorString = "Division by zero encountered";
|
|
365
318
|
return calculationResult;
|
|
366
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.22",
|
|
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": "daf2f4613926ce2aaef670decf920e336e5b4781"
|
|
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
|
-
}
|