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() | 
