UNPKG

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