diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/dom_utils.coffee | 94 | ||||
| -rw-r--r-- | lib/handler_stack.coffee | 12 |
2 files changed, 103 insertions, 3 deletions
diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 4f36e395..477abef2 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -231,5 +231,99 @@ DomUtils = @remove() false +extend DomUtils, + # From: https://github.com/component/textarea-caret-position/blob/master/index.js + getCaretCoordinates: do -> + # The properties that we copy into a mirrored div. + # Note that some browsers, such as Firefox, + # do not concatenate properties, i.e. padding-top, bottom etc. -> padding, + # so we have to do every single property specifically. + properties = [ + 'direction', # RTL support + 'boxSizing', + 'width', # on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does + 'height', + 'overflowX', + 'overflowY', # copy the scrollbar for IE + + 'borderTopWidth', + 'borderRightWidth', + 'borderBottomWidth', + 'borderLeftWidth', + + 'paddingTop', + 'paddingRight', + 'paddingBottom', + 'paddingLeft', + + # https://developer.mozilla.org/en-US/docs/Web/CSS/font + 'fontStyle', + 'fontVariant', + 'fontWeight', + 'fontStretch', + 'fontSize', + 'fontSizeAdjust', + 'lineHeight', + 'fontFamily', + + 'textAlign', + 'textTransform', + 'textIndent', + 'textDecoration', # might not make a difference, but better be safe + + 'letterSpacing', + 'wordSpacing' + ] + + `function (element, position, recalculate) { + // mirrored div + var div = document.createElement('div'); + div.id = 'input-textarea-caret-position-mirror-div'; + document.body.appendChild(div); + + var style = div.style; + var computed = window.getComputedStyle? getComputedStyle(element) : element.currentStyle; // currentStyle for IE < 9 + + // default textarea styles + style.whiteSpace = 'pre-wrap'; + if (element.nodeName !== 'INPUT') + style.wordWrap = 'break-word'; // only for textarea-s + + // position off-screen + style.position = 'absolute'; // required to return coordinates properly + style.visibility = 'hidden'; // not 'display: none' because we want rendering + + // transfer the element's properties to the div + properties.forEach(function (prop) { + style[prop] = computed[prop]; + }); + + style.overflow = 'hidden'; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll' + + div.textContent = element.value.substring(0, position); + // 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 + if (element.nodeName === 'INPUT') + div.textContent = div.textContent.replace(/\s/g, "\u00a0"); + + var span = document.createElement('span'); + // Wrapping must be replicated *exactly*, including when a long word gets + // onto the next line, with whitespace at the end of the line before (#7). + // The *only* reliable way to do that is to copy the *entire* rest of the + // textarea's content into the <span> created at the caret position. + // for inputs, just '.' would be enough, but why bother? + span.textContent = element.value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all + div.appendChild(span); + + var coordinates = { + top: span.offsetTop + parseInt(computed['borderTopWidth']), + left: span.offsetLeft + parseInt(computed['borderLeftWidth']) + }; + + document.body.removeChild(div); + + return coordinates; + } + ` + root = exports ? window root.DomUtils = DomUtils diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 76d835b7..9630759c 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -39,6 +39,7 @@ class HandlerStack # @stopBubblingAndTrue. bubbleEvent: (type, event) -> @eventNumber += 1 + eventNumber = @eventNumber # We take a copy of the array in order to avoid interference from concurrent removes (for example, to # avoid calling the same handler twice, because elements have been spliced out of the array by remove). for handler in @stack[..].reverse() @@ -46,7 +47,7 @@ class HandlerStack if handler?.id and handler[type] @currentId = handler.id result = handler[type].call @, event - @logResult type, event, handler, result if @debug + @logResult eventNumber, type, event, handler, result if @debug if not result DomUtils.suppressEvent event if @isChromeEvent event return false @@ -81,7 +82,7 @@ class HandlerStack false # Debugging. - logResult: (type, event, handler, result) -> + logResult: (eventNumber, type, event, handler, result) -> # FIXME(smblott). Badge updating is too noisy, so we filter it out. However, we do need to look at how # many badge update events are happening. It seems to be more than necessary. We also filter out # registerKeyQueue as unnecessarily noisy and not particularly helpful. @@ -93,7 +94,12 @@ class HandlerStack when @restartBubbling then "rebubble" when true then "continue" label ||= if result then "continue/truthy" else "suppress" - console.log "#{@eventNumber}", type, handler._name, label + console.log "#{eventNumber}", type, handler._name, label + + show: -> + console.log "#{@eventNumber}:" + for handler in @stack[..].reverse() + console.log " ", handler._name root.HandlerStack = HandlerStack root.handlerStack = new HandlerStack() |
