Skip to content

Commit f78a49c

Browse files

File tree

5 files changed

+71
-49
lines changed

5 files changed

+71
-49
lines changed

README.md

+16-10
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
11
# Textarea Caret Position
22

3-
Get the `top` and `left` coordinates of a caret in a `<textarea>`, in pixels.
4-
Useful for textarea autocompletes like GitHub, Twitter etc.
3+
Get the `top` and `left` coordinates of the caret in a `<textarea>` or
4+
`<input type="text">`, in pixels. Useful for textarea autocompletes like
5+
GitHub or Twitter, or for single-line autocompletes like the name drop-down
6+
in Facebook or the company dropdown on Google Finance.
57

68
How it's done: a faux `<div>` is created off-screen and styled exactly like the
7-
textarea. Then, the text of the textarea up to the caret is copied into the div
8-
and a `<span>` is inserted right after it. Then, the text content of the span is
9-
set to the remainder of the text in the textarea, in order to faithfully
10-
reproduce the wrapping in the faux div.
9+
`textarea` or `input`. Then, the text of the element up to the caret is copied
10+
into the `div` and a `<span>` is inserted right after it. Then, the text content
11+
of the span is set to the remainder of the text in the textarea, in order to
12+
faithfully reproduce the wrapping in the faux `div`. The same is done for the
13+
`input` to simplify the code, though it makes no difference.
1114

1215
## Demo
1316

1417
**Check out the [JSFiddle](http://jsfiddle.net/dandv/aFPA7/).**
1518

1619
## Features
1720

21+
* supports `<textarea>`s and `<input type="text">' elements
1822
* pixel precision
23+
* RTL (right-to-left) support
1924
* no dependencies whatsoever
2025
* browser compatibility: Chrome, Safari, Firefox (despite [two](https://bugzilla.mozilla.org/show_bug.cgi?id=753662) [bugs](https://bugzilla.mozilla.org/show_bug.cgi?id=984275) it has), Opera, IE9+
2126
* supports any font family and size, as well as text-transforms
2227
* the text area can have arbitrary padding or borders
2328
* not confused by horizontal or vertical scrollbars in the textarea
2429
* supports hard returns, tabs (except on IE) and consecutive spaces in the text
2530
* correct position on lines longer than the columns in the text area
26-
* no ["ghost" position in the empty space](https://github.com/component/textarea-caret-position/blob/06d2197f85f96405b43724e56dc56f220c0092a5/test/position_off_after_wrapping_with_whitespace_before_EOL.gif) at the end of a line when wrapping long words
31+
* [no problem](http://archive.today/F4XCV#13402035) getting the correct position when the input text is scrolled (i.e. the first visible character is no longer the first in the text)
32+
* no ["ghost" position in the empty space](https://github.com/component/textarea-caret-position/blob/06d2197f85f96405b43724e56dc56f220c0092a5/test/position_off_after_wrapping_with_whitespace_before_EOL.gif) at the end of a line when wrapping long words in a `<textarea>`
2733

2834

2935
## API
@@ -38,15 +44,15 @@ document.querySelector('textarea').addEventListener('input', function () {
3844
})
3945
```
4046

41-
### var coordinates = getCaretCoordinates(textarea, position)
47+
### var coordinates = getCaretCoordinates(element, position)
4248

4349
`position` is a integer of the location of the caret. You basically pass `this.selectionStart` or `this.selectionEnd`. This way, this library isn't opinionated about what the caret is.
4450

4551
`coordinates` is an object of the form `{top: , left: }`.
4652

4753
## Known issues
4854

49-
* Tab characters aren't supported in IE9 (issue #14)
55+
* Tab characters in `<textarea>`s aren't supported in IE9 (issue #14)
5056

5157
## Dependencies
5258

@@ -81,8 +87,8 @@ Firefox 27
8187

8288
## Contributors
8389

84-
* Jonathan Ong ([jonathanong](https://github.com/jonathanong))
8590
* Dan Dascalescu ([dandv](https://github.com/dandv))
91+
* Jonathan Ong ([jonathanong](https://github.com/jonathanong))
8692

8793

8894
## License

component.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "textarea-caret-position",
33
"repo": "component/textarea-caret-position",
4-
"version": "1.1.2",
5-
"description": "(x, y) coordinates of a textarea's caret",
4+
"version": "2.0.0",
5+
"description": "(x, y) coordinates of the caret in a textarea or input type='text'",
66
"development": {
77
"component/assert": "*"
88
},

index.js

+16-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
66
// so we have to do every single property specifically.
77
var properties = [
8+
'direction', // RTL support
89
'boxSizing',
910
'width', // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
1011
'height',
@@ -27,6 +28,7 @@ var properties = [
2728
'fontWeight',
2829
'fontStretch',
2930
'fontSize',
31+
'fontSizeAdjust',
3032
'lineHeight',
3133
'fontFamily',
3234

@@ -40,45 +42,51 @@ var properties = [
4042
];
4143

4244
var isFirefox = !(window.mozInnerScreenX == null);
43-
module.exports = function (textarea, position, recalculate) {
45+
// module.exports = function (textarea, position, recalculate) {
46+
function getCaretCoordinates(element, position, recalculate) {
4447
// mirrored div
4548
var div = document.createElement('div');
46-
div.id = 'textarea-caret-position-mirror-div';
49+
div.id = 'input-textarea-caret-position-mirror-div';
4750
document.body.appendChild(div);
4851

4952
var style = div.style;
50-
var computed = window.getComputedStyle? getComputedStyle(textarea) : textarea.currentStyle; // currentStyle for IE < 9
53+
var computed = window.getComputedStyle? getComputedStyle(element) : element.currentStyle; // currentStyle for IE < 9
5154

5255
// default textarea styles
5356
style.whiteSpace = 'pre-wrap';
54-
style.wordWrap = 'break-word';
57+
if (element.nodeName !== 'INPUT')
58+
style.wordWrap = 'break-word'; // only for textarea-s
5559

5660
// position off-screen
5761
style.position = 'absolute'; // required to return coordinates properly
5862
style.visibility = 'hidden'; // not 'display: none' because we want rendering
5963

60-
// transfer textarea properties to the div
64+
// transfer the element's properties to the div
6165
properties.forEach(function (prop) {
6266
style[prop] = computed[prop];
6367
});
6468

6569
if (isFirefox) {
6670
style.width = parseInt(computed.width) - 2 + 'px' // Firefox adds 2 pixels to the padding - https://bugzilla.mozilla.org/show_bug.cgi?id=753662
6771
// Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
68-
if (textarea.scrollHeight > parseInt(computed.height))
72+
if (element.scrollHeight > parseInt(computed.height))
6973
style.overflowY = 'scroll';
7074
} else {
7175
style.overflow = 'hidden'; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
7276
}
7377

74-
div.textContent = textarea.value.substring(0, position);
78+
div.textContent = element.value.substring(0, position);
79+
// the second special handling for input type="text" vs textarea: spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
80+
if (element.nodeName === 'INPUT')
81+
div.textContent = div.textContent.replace(/\s/g, "\u00a0");
7582

7683
var span = document.createElement('span');
7784
// Wrapping must be replicated *exactly*, including when a long word gets
7885
// onto the next line, with whitespace at the end of the line before (#7).
7986
// The *only* reliable way to do that is to copy the *entire* rest of the
8087
// textarea's content into the <span> created at the caret position.
81-
span.textContent = textarea.value.substring(position);
88+
// for inputs, just '.' would be enough, but why bother?
89+
span.textContent = element.value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all
8290
div.appendChild(span);
8391

8492
var coordinates = {

test/index.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
textarea {
1+
input[type="text"], textarea {
22
font-family: 'Times New Roman'; /* a proportional font makes it more difficult to calculate the position */
33
font-size: 14px;
44
line-height: 16px;

test/index.html

+36-28
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
<html>
33
<head>
44
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
5-
<title>textarea-caret-position testing ground</title>
5+
<title>input and textarea caret-position testing ground</title>
66
<link rel="stylesheet" type="text/css" href="index.css">
77
<script src="../build/build.js"></script>
8+
<script src="../index.js"></script>
89
</head>
910
<body>
1011
<p>Click anywhere in the text to see a red vertical line &ndash; a 1-pixel-thick
1112
<code>div</code> that should be positioned exactly at the location of the caret.</p>
1213

14+
<input type="text" size="15" maxlength="240" placeholder="Enter text here">
15+
16+
<hr/>
17+
1318
<textarea rows="25" cols="40">
1419
I threw a wish in the well,
1520
Don't ask me, I'll never tell
@@ -36,33 +41,36 @@
3641
</textarea>
3742

3843
<script>
39-
var textarea = document.querySelector('textarea');
40-
var fontSize = getComputedStyle(textarea).getPropertyValue('font-size');
41-
var getCaretCoordinates = require('textarea-caret-position');
42-
43-
var rect = document.createElement('div');
44-
document.body.appendChild(rect);
45-
rect.style.position = 'absolute';
46-
rect.style.backgroundColor = 'red';
47-
rect.style.height = fontSize;
48-
rect.style.width = '1px';
49-
50-
['keyup', 'click', 'scroll'].forEach(function (event) {
51-
textarea.addEventListener(event, update);
52-
});
53-
54-
function update() {
55-
var coordinates = getCaretCoordinates(textarea, textarea.selectionEnd);
56-
console.log('(top, left) = (%s, %s)', coordinates.top, coordinates.left);
57-
rect.style.top = textarea.offsetTop
58-
- textarea.scrollTop
59-
+ coordinates.top
60-
+ 'px';
61-
rect.style.left = textarea.offsetLeft
62-
- textarea.scrollLeft
63-
+ coordinates.left
64-
+ 'px';
65-
}
44+
if (window.require) // if not in Component, allow the bare HTML demo to work
45+
getCaretCoordinates = require('textarea-caret-position');
46+
['input[type="text"]', 'textarea'].forEach(function (selector) {
47+
var element = document.querySelector(selector);
48+
var fontSize = getComputedStyle(element).getPropertyValue('font-size');
49+
50+
var rect = document.createElement('div');
51+
document.body.appendChild(rect);
52+
rect.style.position = 'absolute';
53+
rect.style.backgroundColor = 'red';
54+
rect.style.height = fontSize;
55+
rect.style.width = '1px';
56+
57+
['keyup', 'click', 'scroll'].forEach(function (event) {
58+
element.addEventListener(event, update);
59+
});
60+
61+
function update() {
62+
var coordinates = getCaretCoordinates(element, element.selectionEnd);
63+
console.log('(top, left) = (%s, %s)', coordinates.top, coordinates.left);
64+
rect.style.top = element.offsetTop
65+
- element.scrollTop
66+
+ coordinates.top
67+
+ 'px';
68+
rect.style.left = element.offsetLeft
69+
- element.scrollLeft
70+
+ coordinates.left
71+
+ 'px';
72+
}
73+
});
6674
</script>
6775
</body>
6876
</html>

0 commit comments

Comments
 (0)