(container: Element, node: Node, offset: number)
| 620 | } |
| 621 | |
| 622 | function getPosition(container: Element, node: Node, offset: number): Position { |
| 623 | if (node === container) { |
| 624 | return {index: offset, offset: 0}; |
| 625 | } |
| 626 | |
| 627 | let originalNode = node; |
| 628 | while (node.parentNode !== container) { |
| 629 | node = node.parentNode!; |
| 630 | } |
| 631 | |
| 632 | let index = indexOfNode(node); |
| 633 | if (node.nodeType === Node.ELEMENT_NODE) { |
| 634 | let tokenNode = node.childNodes[1]; |
| 635 | let atEnd: boolean; |
| 636 | let endOffset = 0; |
| 637 | if (originalNode === tokenNode) { |
| 638 | // Cursor is inside the token. |
| 639 | atEnd = offset > 0; |
| 640 | } else if (originalNode === node) { |
| 641 | // Cursor is inside the wrapper element. |
| 642 | atEnd = offset > 1; |
| 643 | } else { |
| 644 | // Cursor is on one of the zero width spaces. |
| 645 | atEnd = originalNode !== tokenNode.previousSibling; |
| 646 | // If the offset is greater than 1, the browser is trying to insert text into |
| 647 | // the zero width space node. This will actually end up in the next text node. |
| 648 | endOffset = atEnd && offset > 1 ? offset - 1 : 0; |
| 649 | } |
| 650 | |
| 651 | offset = atEnd ? (tokenNode?.textContent?.length ?? 0) : 0; |
| 652 | |
| 653 | // Several positions are equivalent due to the zero width spaces around tokens. |
| 654 | // Normalize offset to the end of the preceding text node, or the beginning of the following node. |
| 655 | if (offset === 0 && node.previousSibling?.nodeType === Node.TEXT_NODE) { |
| 656 | index--; |
| 657 | offset = node.previousSibling?.textContent?.length ?? 0; |
| 658 | } else if (atEnd) { |
| 659 | index++; |
| 660 | offset = endOffset; |
| 661 | } |
| 662 | } |
| 663 | return {index, offset}; |
| 664 | } |
| 665 | |
| 666 | let isProgrammaticSelectionChange = Symbol('isProgrammaticSelectionChange'); |
| 667 |
no test coverage detected