JSFiddle - React, Tailwind, and code Playground
HTML
<h1>Editable area:</h1>
<div id='x' contenteditable><span>asdasd</span></div>
<table>
<tr>
<td>Is caret</td>
<td id='caret'></td>
</tr>
<tr>
<td>Is at start</td>
<td id='start'></td>
</tr>
<tr>
<td>Is at end</td>
<td id='end'></td>
</tr>
<tr>
<td>First line</td>
<td id='first'></td>
</tr>
<tr>
<td>Last line</td>
<td id='last'></td>
</tr>
</table>
CSS
div#x {
width: 80%;
background-color: #DDD;
border: 1px solid gray;
border-radius: 15px;
padding: 6px 0.5em;
margin-left: auto;
margin-right: auto;
}
table {
border: 1px solid gray;
width: 200px;
margin-left: auto;
margin-right: auto;
}
td {
padding: 6px;
border-bottom: 1px dashed gray;
}
CoffeeScript
# CODE
LINE_HEIGHT = 20
is_caret_at_start_of_node = (node, range) ->
# See: http://stackoverflow.com/questions/7451468
pre_range = document.createRange()
pre_range.selectNodeContents(node)
pre_range.setEnd(range.startContainer, range.startOffset)
return pre_range.toString().trim().length == 0
is_caret_at_end_of_node = (node, range) ->
post_range = document.createRange()
post_range.selectNodeContents(node)
post_range.setStart(range.endContainer, range.endOffset)
return post_range.toString().trim().length == 0
$.fn.editableIsCaret = ->
return window.getSelection().type == 'Caret'
# alt test:
# return sel.rangeCount == 1 and sel.getRangeAt(0).collapsed
$.fn.editableRange = ->
# Return the range for the selection
sel = window.getSelection()
return unless sel.rangeCount > 0
return sel.getRangeAt(0)
$.fn.editableCaretRange = ->
return unless @editableIsCaret()
return @editableRange()
$.fn.editableSetRange = (range) ->
sel = window.getSelection()
sel.removeAllRanges() if sel.rangeCount > 0
sel.addRange(range)
$.fn.editableFocus = (at_start=true) ->
return unless @attr('contenteditable')
sel = window.getSelection()
sel.removeAllRanges() if sel.rangeCount > 0
range = document.createRange()
range.selectNodeContents(@[0])
range.collapse(at_start)
sel.addRange(range)
$.fn.editableCaretAtStart = ->
range = @editableRange()
return false unless range
return is_caret_at_start_of_node(@[0], range)
$.fn.editableCaretAtEnd = ->
range = @editableRange()
return false unless range
return is_caret_at_end_of_node(@[0], range)
$.fn.editableCaretOnFirstLine = ->
range = @editableRange()
return false unless range
# At the start of a node, the getClientRects() is [], so we have to
# use the getBoundingClientRect (which seems to work).
if is_caret_at_start_of_node(@[0], range)
return true
else if is_caret_at_end_of_node(@[0], range)
ctop = @[0].getBoundingClientRect().bottom - LINE_HEIGHT
...