Commit 14ebd7b5 authored by dmacfarlane's avatar dmacfarlane
Browse files

fix for #96

parent 32b75983
Loading
Loading
Loading
Loading
+152 −123
Original line number Original line Diff line number Diff line
/* From: https://github.com/component/textarea-caret-position */
/* From: https://github.com/component/textarea-caret-position */
/* jshint browser: true */
/* jshint browser: true */


// The properties that we copy into a mirrored div.
// (function () {
// Note that some browsers, such as Firefox,

// do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
  // We'll copy the properties below into the mirror div.
// so we have to do every single property specifically.
  // Note that some browsers, such as Firefox, do not concatenate properties
  // into their shorthand (e.g. padding-top, padding-bottom etc. -> padding),
  // so we have to list every single property explicitly.
  var properties = [
  var properties = [
    'direction',  // RTL support
    'direction',  // RTL support
    'boxSizing',
    'boxSizing',
@@ -48,9 +50,9 @@ var properties = [
  ];
  ];
  
  
  var isBrowser = (typeof window !== 'undefined');
  var isBrowser = (typeof window !== 'undefined');
var isFirefox = (isBrowser && (<any>window).mozInnerScreenX != null);
  var isFirefox = (isBrowser && window['mozInnerScreenX'] != null);
  
  
export function getCaretCoordinates(element, position, options = null) {
  export function getCaretCoordinates(element, position, options) {
    if (!isBrowser) {
    if (!isBrowser) {
      throw new Error('textarea-caret-position#getCaretCoordinates should only be called in a browser');
      throw new Error('textarea-caret-position#getCaretCoordinates should only be called in a browser');
    }
    }
@@ -58,30 +60,53 @@ export function getCaretCoordinates(element, position, options = null) {
    var debug = options && options.debug || false;
    var debug = options && options.debug || false;
    if (debug) {
    if (debug) {
      var el = document.querySelector('#input-textarea-caret-position-mirror-div');
      var el = document.querySelector('#input-textarea-caret-position-mirror-div');
    if (el) { el.parentNode.removeChild(el); }
      if (el) el.parentNode.removeChild(el);
    }
    }
  
  
  // mirrored div
    // The mirror div will replicate the textarea's style
    var div = document.createElement('div');
    var div = document.createElement('div');
    div.id = 'input-textarea-caret-position-mirror-div';
    div.id = 'input-textarea-caret-position-mirror-div';
    document.body.appendChild(div);
    document.body.appendChild(div);
  
  
    var style = div.style;
    var style = div.style;
  var computed = window.getComputedStyle ? getComputedStyle(element) : element.currentStyle;  // currentStyle for IE < 9
    var computed = window.getComputedStyle ? window.getComputedStyle(element) : element.currentStyle;  // currentStyle for IE < 9
    var isInput = element.nodeName === 'INPUT';
  
  
  // default textarea styles
    // Default textarea styles
    style.whiteSpace = 'pre-wrap';
    style.whiteSpace = 'pre-wrap';
  if (element.nodeName !== 'INPUT')
    if (!isInput)
      style.wordWrap = 'break-word';  // only for textarea-s
      style.wordWrap = 'break-word';  // only for textarea-s
  
  
  // position off-screen
    // Position off-screen
    style.position = 'absolute';  // required to return coordinates properly
    style.position = 'absolute';  // required to return coordinates properly
    if (!debug)
    if (!debug)
      style.visibility = 'hidden';  // not 'display: none' because we want rendering
      style.visibility = 'hidden';  // not 'display: none' because we want rendering
  
  
  // transfer the element's properties to the div
    // Transfer the element's properties to the div
    properties.forEach(function (prop) {
    properties.forEach(function (prop) {
      if (isInput && prop === 'lineHeight') {
        // Special case for <input>s because text is rendered centered and line height may be != height
        if (computed.boxSizing === "border-box") {
          var height = parseInt(computed.height);
          var outerHeight =
            parseInt(computed.paddingTop) +
            parseInt(computed.paddingBottom) +
            parseInt(computed.borderTopWidth) +
            parseInt(computed.borderBottomWidth);
          var targetHeight = outerHeight + parseInt(computed.lineHeight);
          if (height > targetHeight) {
            style.lineHeight = height - outerHeight + "px";
          } else if (height === targetHeight) {
            style.lineHeight = computed.lineHeight;
          } else {
            style.lineHeight = '0';
          }
        } else {
          style.lineHeight = computed.height;
        }
      } else {
        style[prop] = computed[prop];
        style[prop] = computed[prop];
      }
    });
    });
  
  
    if (isFirefox) {
    if (isFirefox) {
@@ -93,8 +118,9 @@ export function getCaretCoordinates(element, position, options = null) {
    }
    }
  
  
    div.textContent = element.value.substring(0, position);
    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
    // The second special handling for input type="text" vs textarea:
  if (element.nodeName === 'INPUT')
    // spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
    if (isInput)
      div.textContent = div.textContent.replace(/\s/g, '\u00a0');
      div.textContent = div.textContent.replace(/\s/g, '\u00a0');
  
  
    var span = document.createElement('span');
    var span = document.createElement('span');
@@ -102,13 +128,14 @@ export function getCaretCoordinates(element, position, options = null) {
    // onto the next line, with whitespace at the end of the line before (#7).
    // 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
    // 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.
    // textarea's content into the <span> created at the caret position.
  // for inputs, just '.' would be enough, but why bother?
    // For inputs, just '.' would be enough, but no need to bother.
    span.textContent = element.value.substring(position) || '.';  // || because a completely empty faux span doesn't render at all
    span.textContent = element.value.substring(position) || '.';  // || because a completely empty faux span doesn't render at all
    div.appendChild(span);
    div.appendChild(span);
  
  
    var coordinates = {
    var coordinates = {
      top: span.offsetTop + parseInt(computed['borderTopWidth']),
      top: span.offsetTop + parseInt(computed['borderTopWidth']),
    left: span.offsetLeft + parseInt(computed['borderLeftWidth'])
      left: span.offsetLeft + parseInt(computed['borderLeftWidth']),
      height: parseInt(computed['lineHeight'])
    };
    };
  
  
    if (debug) {
    if (debug) {
@@ -125,3 +152,5 @@ export function getCaretCoordinates(element, position, options = null) {
  // } else if(isBrowser) {
  // } else if(isBrowser) {
  //   window.getCaretCoordinates = getCaretCoordinates;
  //   window.getCaretCoordinates = getCaretCoordinates;
  // }
  // }
  
  // }());
 No newline at end of file
+4 −10
Original line number Original line Diff line number Diff line
@@ -52,7 +52,6 @@ export class MentionListComponent implements OnInit {
  items = [];
  items = [];
  activeIndex: number = 0;
  activeIndex: number = 0;
  hidden: boolean = false;
  hidden: boolean = false;
  private blockCursorSize: {height:number, width:number};
  constructor(private element: ElementRef) {}
  constructor(private element: ElementRef) {}


  ngOnInit() {
  ngOnInit() {
@@ -65,13 +64,11 @@ export class MentionListComponent implements OnInit {
  position(nativeParentElement: HTMLInputElement, iframe: HTMLIFrameElement = null, dropUp: boolean) {
  position(nativeParentElement: HTMLInputElement, iframe: HTMLIFrameElement = null, dropUp: boolean) {
    let coords = { top: 0, left: 0 };
    let coords = { top: 0, left: 0 };
    if (isInputOrTextAreaElement(nativeParentElement)) {
    if (isInputOrTextAreaElement(nativeParentElement)) {
      this.blockCursorSize = this.getBlockCursorDimensions(nativeParentElement);
      const blockCursorSize = this.getBlockCursorDimensions(nativeParentElement);
      const scrollToHeightDiff = (nativeParentElement.scrollHeight - nativeParentElement.clientHeight);
      const scrollToWidthDiff = (nativeParentElement.scrollWidth - nativeParentElement.clientWidth);
      // parent elements need to have postition:relative for this to work correctly?
      // parent elements need to have postition:relative for this to work correctly?
      coords = getCaretCoordinates(nativeParentElement, nativeParentElement.selectionStart);
      coords = getCaretCoordinates(nativeParentElement, nativeParentElement.selectionStart, null);
      coords.top = nativeParentElement.offsetTop - scrollToHeightDiff + coords.top + this.blockCursorSize.height;
      coords.top = nativeParentElement.offsetTop + coords.top + blockCursorSize.height - nativeParentElement.scrollTop;
      coords.left = nativeParentElement.offsetLeft - (scrollToWidthDiff ? scrollToWidthDiff + this.blockCursorSize.width : 0) + coords.left;
      coords.left = nativeParentElement.offsetLeft + coords.left - nativeParentElement.scrollLeft;
    }
    }
    else if (iframe) {
    else if (iframe) {
      let context: { iframe: HTMLIFrameElement, parent: Element } = { iframe: iframe, parent: iframe.offsetParent };
      let context: { iframe: HTMLIFrameElement, parent: Element } = { iframe: iframe, parent: iframe.offsetParent };
@@ -140,9 +137,6 @@ export class MentionListComponent implements OnInit {
  }
  }


  private getBlockCursorDimensions(nativeParentElement: HTMLInputElement) {
  private getBlockCursorDimensions(nativeParentElement: HTMLInputElement) {
    if (this.blockCursorSize) {
      return this.blockCursorSize;
    }
    const parentStyles = window.getComputedStyle(nativeParentElement);
    const parentStyles = window.getComputedStyle(nativeParentElement);
    return {
    return {
      height: parseFloat(parentStyles.lineHeight),
      height: parseFloat(parentStyles.lineHeight),
+3 −1
Original line number Original line Diff line number Diff line
@@ -20,11 +20,13 @@ and content editable fields. Try entering some @names below.</p>


</ng-container>
</ng-container>


<!-- other demos that can be enabled -->
<!-- other functionaity demos -->
<app-demo-async *ngIf="test=='async'"></app-demo-async>
<app-demo-async *ngIf="test=='async'"></app-demo-async>
<app-demo-config *ngIf="test=='config'"></app-demo-config>
<app-demo-config *ngIf="test=='config'"></app-demo-config>
<app-demo-options *ngIf="test=='options'"></app-demo-options>
<app-demo-options *ngIf="test=='options'"></app-demo-options>
<app-demo-template *ngIf="test=='template'"></app-demo-template>
<app-demo-template *ngIf="test=='template'"></app-demo-template>
<!-- other tests -->
<app-test-position *ngIf="test=='pos'"></app-test-position>


<br><p style="color:grey">angular-mentions on <a href="https://github.com/dmacfarlane/angular-mentions">Github</a></p>
<br><p style="color:grey">angular-mentions on <a href="https://github.com/dmacfarlane/angular-mentions">Github</a></p>
<a href="https://github.com/dmacfarlane/angular-mentions"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/652c5b9acfaddf3a9c326fa6bde407b87f7be0f4/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png"></a>
<a href="https://github.com/dmacfarlane/angular-mentions"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/652c5b9acfaddf3a9c326fa6bde407b87f7be0f4/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png"></a>
+2 −1
Original line number Original line Diff line number Diff line
@@ -17,7 +17,8 @@ export class AppComponent {
      case '/config'  : return 'config';
      case '/config'  : return 'config';
      case '/async'   : return 'async';
      case '/async'   : return 'async';
      case '/options' : return 'options';
      case '/options' : return 'options';
      case '/async'   : return 'template';
      case '/template': return 'template';
      case '/pos'     : return 'pos';
    }
    }
  }
  }
}
}
+3 −1
Original line number Original line Diff line number Diff line
@@ -13,6 +13,7 @@ import { DemoConfigComponent } from './demo-config/demo-config.component';
import { DemoOptionsComponent } from './demo-options/demo-options.component';
import { DemoOptionsComponent } from './demo-options/demo-options.component';
import { DemoTemplateComponent } from './demo-template/demo-template.component';
import { DemoTemplateComponent } from './demo-template/demo-template.component';
import { DemoTinymceComponent } from './demo-tinymce/demo-tinymce.component';
import { DemoTinymceComponent } from './demo-tinymce/demo-tinymce.component';
import { TestPositionComponent } from './test-position/test-position.component';


@NgModule({
@NgModule({
  imports: [
  imports: [
@@ -27,7 +28,8 @@ import { DemoTinymceComponent } from './demo-tinymce/demo-tinymce.component';
    DemoConfigComponent,
    DemoConfigComponent,
    DemoOptionsComponent,
    DemoOptionsComponent,
    DemoTemplateComponent,
    DemoTemplateComponent,
    DemoTinymceComponent
    DemoTinymceComponent,
    TestPositionComponent
  ],
  ],
  providers: [],
  providers: [],
  bootstrap: [AppComponent]
  bootstrap: [AppComponent]
Loading