_caretPosition.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. // From https://github.com/component/textarea-caret-position
  2. // We'll copy the properties below into the mirror div.
  3. // Note that some browsers, such as Firefox, do not concatenate properties
  4. // into their shorthand (e.g. padding-top, padding-bottom etc. -> padding),
  5. // so we have to list every single property explicitly.
  6. var properties = [
  7. 'direction', // RTL support
  8. 'boxSizing',
  9. 'width', // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
  10. 'height',
  11. 'overflowX',
  12. 'overflowY', // copy the scrollbar for IE
  13. 'borderTopWidth',
  14. 'borderRightWidth',
  15. 'borderBottomWidth',
  16. 'borderLeftWidth',
  17. 'borderStyle',
  18. 'paddingTop',
  19. 'paddingRight',
  20. 'paddingBottom',
  21. 'paddingLeft',
  22. // https://developer.mozilla.org/en-US/docs/Web/CSS/font
  23. 'fontStyle',
  24. 'fontVariant',
  25. 'fontWeight',
  26. 'fontStretch',
  27. 'fontSize',
  28. 'fontSizeAdjust',
  29. 'lineHeight',
  30. 'fontFamily',
  31. 'textAlign',
  32. 'textTransform',
  33. 'textIndent',
  34. 'textDecoration', // might not make a difference, but better be safe
  35. 'letterSpacing',
  36. 'wordSpacing',
  37. 'tabSize',
  38. 'MozTabSize'
  39. ];
  40. var isBrowser = (typeof window !== 'undefined');
  41. var isFirefox = (isBrowser && window.mozInnerScreenX != null);
  42. function getCaretCoordinates(element, position, options) {
  43. if (!isBrowser) {
  44. throw new Error('textarea-caret-position#getCaretCoordinates should only be called in a browser');
  45. }
  46. var debug = options && options.debug || false;
  47. if (debug) {
  48. var el = document.querySelector('#input-textarea-caret-position-mirror-div');
  49. if (el) el.parentNode.removeChild(el);
  50. }
  51. // The mirror div will replicate the textarea's style
  52. var div = document.createElement('div');
  53. div.id = 'input-textarea-caret-position-mirror-div';
  54. document.body.appendChild(div);
  55. var style = div.style;
  56. var computed = window.getComputedStyle ? window.getComputedStyle(element) : element.currentStyle; // currentStyle for IE < 9
  57. var isInput = element.nodeName === 'INPUT';
  58. // Default textarea styles
  59. style.whiteSpace = 'pre-wrap';
  60. if (!isInput)
  61. style.wordWrap = 'break-word'; // only for textarea-s
  62. // Position off-screen
  63. style.position = 'absolute'; // required to return coordinates properly
  64. if (!debug)
  65. style.visibility = 'hidden'; // not 'display: none' because we want rendering
  66. // Transfer the element's properties to the div
  67. properties.forEach(function (prop) {
  68. if (isInput && prop === 'lineHeight') {
  69. // Special case for <input>s because text is rendered centered and line height may be != height
  70. if (computed.boxSizing === "border-box") {
  71. var height = parseInt(computed.height);
  72. var outerHeight =
  73. parseInt(computed.paddingTop) +
  74. parseInt(computed.paddingBottom) +
  75. parseInt(computed.borderTopWidth) +
  76. parseInt(computed.borderBottomWidth);
  77. var targetHeight = outerHeight + parseInt(computed.lineHeight);
  78. if (height > targetHeight) {
  79. style.lineHeight = height - outerHeight + "px";
  80. } else if (height === targetHeight) {
  81. style.lineHeight = computed.lineHeight;
  82. } else {
  83. style.lineHeight = 0;
  84. }
  85. } else {
  86. style.lineHeight = computed.height;
  87. }
  88. } else {
  89. style[prop] = computed[prop];
  90. }
  91. });
  92. if (isFirefox) {
  93. // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
  94. if (element.scrollHeight > parseInt(computed.height))
  95. style.overflowY = 'scroll';
  96. } else {
  97. style.overflow = 'hidden'; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
  98. }
  99. div.textContent = element.value.substring(0, position);
  100. // The second special handling for input type="text" vs textarea:
  101. // spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
  102. if (isInput)
  103. div.textContent = div.textContent.replace(/\s/g, '\u00a0');
  104. var span = document.createElement('span');
  105. // Wrapping must be replicated *exactly*, including when a long word gets
  106. // onto the next line, with whitespace at the end of the line before (#7).
  107. // The *only* reliable way to do that is to copy the *entire* rest of the
  108. // textarea's content into the <span> created at the caret position.
  109. // For inputs, just '.' would be enough, but no need to bother.
  110. span.textContent = element.value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all
  111. div.appendChild(span);
  112. var coordinates = {
  113. top: span.offsetTop + parseInt(computed['borderTopWidth']),
  114. left: span.offsetLeft + parseInt(computed['borderLeftWidth']),
  115. height: parseInt(computed['lineHeight'])
  116. };
  117. if (debug) {
  118. span.style.backgroundColor = '#aaa';
  119. } else {
  120. document.body.removeChild(div);
  121. }
  122. return coordinates;
  123. }