Source: xxe/view/CaretPlaceholder.js

  1. /**
  2. * Draw a caret placeholder when the document view looses the keyboard focus.
  3. */
  4. class CaretPlaceholder {
  5. constructor(docView) {
  6. this._docView = docView;
  7. this._disabled = false;
  8. this._caret = null;
  9. const activeElem = document.activeElement;
  10. this._docViewHasFocus = (activeElem !== null &&
  11. docView.contains(activeElem));
  12. this._onFocusin = this.onFocusin.bind(this);
  13. docView.addEventListener("focusin", this._onFocusin);
  14. this._onFocusout = this.onFocusout.bind(this);
  15. docView.addEventListener("focusout", this._onFocusout);
  16. }
  17. dispose() {
  18. if (this._onFocusin) {
  19. this._docView.removeEventListener("focusin", this._onFocusin);
  20. this._onFocusin = null;
  21. }
  22. if (this._onFocusout) {
  23. this._docView.removeEventListener("focusout", this._onFocusout);
  24. this._onFocusout = null;
  25. }
  26. }
  27. onFocusin(event) {
  28. // Do not consume event.
  29. if (!this._docViewHasFocus) {
  30. this._docViewHasFocus = true;
  31. if (!this._disabled) {
  32. this.hide();
  33. }
  34. }
  35. }
  36. onFocusout(event) {
  37. // Do not consume event.
  38. if (this._docViewHasFocus) {
  39. let receivingFocus = event.relatedTarget;
  40. if (receivingFocus === null ||
  41. !this._docView.contains(receivingFocus)) {
  42. this._docViewHasFocus = false;
  43. if (!this._disabled) {
  44. this.show();
  45. }
  46. }
  47. }
  48. }
  49. get disabled() {
  50. return this._disabled;
  51. }
  52. set disabled(disable) {
  53. this._disabled = disable;
  54. }
  55. hide() {
  56. this.erase();
  57. // "Redraw" actual caret because in most cases it is lost.
  58. TextHighlight.drawDot(this._docView.dot, this._docView.dotOffset);
  59. }
  60. erase() {
  61. // Do not make any assumption about caret and its the parent.
  62. let caret = this._caret;
  63. if (caret !== null) {
  64. this._caret = null;
  65. let container = caret.parentNode;
  66. if (container !== null && container.isConnected) {
  67. container.removeChild(caret);
  68. container = NodeView.getTextualContent(container);
  69. if (container !== null) {
  70. NodeView.normalizeTextualContent(container);
  71. }
  72. }
  73. }
  74. }
  75. show() {
  76. const dot = this._docView.dot;
  77. if (dot !== null && !this._docView.hasTextSelection()) {
  78. this.erase();
  79. this.draw(dot, this._docView.dotOffset);
  80. }
  81. }
  82. draw(dot, dotOffset) {
  83. // Do not make any assumption about the contents of dotContent.
  84. // dotContent may even be null in case of a TextNode's view having
  85. // replaced content.
  86. let dotContent = NodeView.getTextualContent(dot);
  87. if (dotContent !== null) {
  88. let [charsNode, charOffset] =
  89. NodeView.textualContentToCharOffset(dotContent, dotOffset);
  90. if (charsNode !== null) {
  91. this._caret = document.createElement("a");
  92. this._caret.classList.add("xxe-caret");
  93. if (charOffset === 0) {
  94. charsNode.parentNode.insertBefore(this._caret, charsNode);
  95. } else if (charOffset === charsNode.length) {
  96. charsNode.parentNode.insertBefore(this._caret,
  97. charsNode.nextSibling);
  98. } else {
  99. let beforeNode = charsNode.splitText(charOffset);
  100. charsNode.parentNode.insertBefore(this._caret, beforeNode);
  101. }
  102. if (dotContent.textContent.length === 0) {
  103. this._caret.classList.add("xxe-caret-empty");
  104. }
  105. } else {
  106. console.error(`CaretPlaceholder.draw: INTERNAL ERROR: \
  107. cannot convert dot offset ${dotOffset} to char offset in \
  108. ${DOMUtil.dumpNode(dotContent)}`);
  109. }
  110. }
  111. }
  112. }