@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.
@@ -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
- let tokens = formula.match(/'[^']*'|\d+|[A-Za-z_][A-Za-z0-9_]*|[-+(),*^/:?\s]/g);
19
- // Stores the positions of opening parentheses. This allows us to
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 currentTokens = "";
32
- // The object that we return as the output of the parsing result.
33
- let parseOutput = {
34
- recommendations: null,
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
- if (recommendation) {
42
- formattedString = `${recommendation}`;
43
- currentPosition += recommendation.length;
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
- let isOperator = this.mathematicalOperators.has(token);
57
- let isSpace = token.trim() == "";
58
- let isBracket = token == "(" || token == ")";
32
+ const isOperator = mathematicalOperators.has(token);
33
+ const isSpace = token.trim() === "";
34
+ const isBracket = token === "(" || token === ")";
59
35
  if (isSpace) {
60
- formattedString = `${formattedString}${token}`;
36
+ parseOutput.formattedString += token;
61
37
  currentPosition += token.length;
62
38
  return;
63
39
  }
64
- // If the cursor position is 'inside` the current token:
65
- //
66
- // 1. If we've got a recommendation to add, simply replace the
67
- // word with the recommendation.
68
- // 2. Ask the recommendor to fetch recommendations for this specific
69
- // token/word.
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 (this.mathematicalOperators.has(token)) {
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.getRecommendation(token);
63
+ parseOutput.recommendations = this._recommender.getRecommendations(token);
95
64
  }
96
- let tokenClassName = "";
97
- // Don't check for errors if an error has already been encountered.
65
+ /**
66
+ * Error checks
67
+ * skip error check if there is one already
68
+ */
98
69
  if (expectation != Expectation.UNDEFINED) {
99
- if (this.mathematicalOperators.has(previousToken) && isOperator) {
70
+ if (mathematicalOperators.has(previousToken) && isOperator) {
100
71
  parseOutput.errorString = `Multiple operators at position ${currentPosition}`;
101
72
  expectation = Expectation.UNDEFINED;
102
73
  }
103
- // Unnecessary closing parenthesis
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
- // Operator or ) after an operator. Eg: `23 / *` or `23 / )`
110
- // Unary `+` and `-` are not an error as they might represent
111
- // a positive or negative number respectively. But they will still
112
- // be an error if the formula ends with them.
113
- else if (expectation == Expectation.VARIABLE &&
114
- !isNumber &&
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
- // Number/Variable after the same. Eg: `a a` or `420 420`.
124
- // Having a ) is fine. Eg: `23)` might be representing `(23 + 23)
125
- else if (expectation == Expectation.OPERATOR &&
126
- !isOperator &&
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
- // Unknown symbol/variable/word
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
- // The case of division by zero. Since we can't know if an expression evaluates
140
- // to zero or not, that case can only be handled during calculation.
141
- else if (isNumber &&
142
- previousToken == "/" &&
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
- // Empty brackets. Default might be takn as 0, but that will only make sense
149
- // in addition and subtraction and not in other operators, so making this
150
- // case an error makes more sense.
151
- else if (previousToken == "(" && token == ")") {
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
- // Setting the expectation for the next token, if we have not encountered an
158
- // error already.
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 == "(" || isOperator) {
120
+ if (token === "(" || isOperator) {
161
121
  expectation = Expectation.VARIABLE;
162
122
  }
163
- else if (token == ")" || isNumber) {
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 = `${formattedString}${token}`;
172
- previousToken = token;
131
+ parseOutput.formattedString += token;
173
132
  currentPosition += token.length;
174
- currentTokens += token;
133
+ parsedString += token;
134
+ previousToken = token;
175
135
  });
176
136
  if (recommendation) {
177
- parseOutput.newCursorPosition = Math.min(parseOutput.newCursorPosition +
178
- recommendation.length, formula.length + recommendation.length);
179
- formattedString = `${formattedString}${recommendation}`;
137
+ parseOutput.newCursorPosition = Math.min(parseOutput.newCursorPosition, formula.length) + recommendation.length;
138
+ parseOutput.formattedString += recommendation;
139
+ previousToken = recommendation;
180
140
  }
181
- // formula ends with a mathematical operator
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
- // formula has unclosed `(`
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 == "+" || token == "-") &&
209
- (!currentTokens.trim() || previousToken === "(" || this.mathematicalOperators.has(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 (this.mathematicalOperators.has(token)) {
238
- while (this.mathematicalOperators.has(operatorStack.top()) &&
239
- this.operatorPrecedence[token] <=
240
- this.operatorPrecedence[operatorStack.top()]) {
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(this.operatorPrecedence).includes(symbol)) {
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 (this.operatorPrecedence[operatorB] <=
293
- this.operatorPrecedence[symbol] ||
294
- (this.operatorPrecedence[operatorB] ===
295
- this.operatorPrecedence[symbol] &&
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 (this.operatorPrecedence[operatorA] <=
303
- this.operatorPrecedence[symbol] ||
304
- (this.operatorPrecedence[operatorA] ===
305
- this.operatorPrecedence[symbol] &&
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 (!this.mathematicalOperators.has(frontItem)) {
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()) == 0) {
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-enhancements.11",
3
+ "version": "2.0.7-formula-editor.21",
4
4
  "description": "A WYSIWYG type formula editor",
5
- "main": "dist/formula-editor/src/formula-builder.js",
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": "1d90f3aad9fd17c33427a9e9b973bd0e19b24109"
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
- }