0

I want to get something like this, in textarea. I cant change textarea to contentaditable div. How do I get the position of the selected text? I need this to show this pop-up from above sample

2 Answers2

0

You should be able to easily convert this into angular code. This is the basics of what you need to do, its not thoroughly tested but it bare minimum works.

Working example

const textarea = document.getElementById('text')
const result = document.getElementById('selected')
const tooltip = document.getElementById('tooltip')

const cols = document.getElementById('text').cols;
const width = document.getElementById('text').clientWidth;
const height = window.getComputedStyle(textarea).lineHeight;


textarea.onclick = function getSelection() {
  const pos = {
    top: textarea.offsetTop,
    left: textarea.offsetLeft,
  };
  result.textContent = `${textarea.selectionStart}, ${textarea.selectionEnd}`;
  let selection
  if (textarea.selectionStart) {
    selection = textarea.selectionStart;
  } else if (document.selection) {
    textarea.focus();
    const r = document.selection.createRange();
    if (r == null) {
      selection = 0;
    }
    let re = textarea.createTextRange();
    let rc = re.duplicate();
    re.moveToBookmark(r.getBookmark());
    rc.setEndPoint('EndToStart', re);
    selection = rc.text.length;
  } else {
    selection = 0
  }
  const row = Math.floor((selection - 1) / cols);
  const col = (selection - (row * cols));
  const x = Math.floor((col * (width / cols)));
  const y = (parseInt(height) * row);
  tooltip.innerHTML = "<b>row: " + row + "<br>columns" + col + "<br>width: " + width + "</b>";
  tooltip.style.top = `${pos.top+y}px`;
  tooltip.style.left = `${pos.left+x+10}px`;
}
textarea {
  height: 80px;
  line-height: 12px;
  overflow-y: scroll;
  display: block;
}

#tooltip {
  position: absolute;
  background:red;
  color: white;
}
<textarea id="text">Lopsum</textarea>
<span id="tooltip"></span>
<span id="selected"></span>
Njuguna Mureithi
  • 3,506
  • 1
  • 21
  • 41
0

The only way I imagine is create a duplicate of the text area (very similar to this SO, (the Owen Kelvin's response) about highligth words in a text area

As we only need the position, we can split the "texarea" and the "background". Futhermore, using the Yurzui response in this another SO we can control when resize the textarea

We can imagine an .html like

<div class="container">
    <div
      #backdrop
      class="backdrop"
      [style.width.px]="textWidth"
      [style.height.px]="textHeight"
    >
      <div
        class="highlights"
        [innerHTML]="sanitizer.bypassSecurityTrustHtml(highlightedText)"
      ></div>
    </div>
  </div>
  <textarea
    #textarea
    spellcheck="false"
    (resize)="rect = null;"
    (select)="applyHighlights(textarea.value)"
    (mousedown)="mouseDown()"
    (blur)="rect = null"
    [ngModel]="textValue"
    (ngModelChange)="textValue = $event; applyHighlights(textarea.value)"
    (scroll)="handleScroll(); applyHighlights(textarea.value)"
  ></textarea>

<div
  class="tooltip"
  *ngIf="rect"
  [style.top.px]="rect.y"
  [style.left.px]="rect.x"
></div>

See that the "text" is hidden because we has a div container like

.container {
  overflow:hidden;
  width:0;
  height:0;
}

And we make that the dimensions of "text" are condition by the two variables "textWidth" and "textHeight"

The code is

  ngOnInit() {
    this.resize();
  }

  resize() {
    const event = {
      width: this.$textarea.nativeElement.getBoundingClientRect().width,
      height: this.$textarea.nativeElement.getBoundingClientRect().height,
    };
    this.textWidth = event.width;
    this.textHeight = event.height;
  }
  mouseDown() {
    setTimeout(() => {
      const start = this.$textarea.nativeElement.selectionStart;
      const end = this.$textarea.nativeElement.selectionEnd;
      if (start == end) this.rect = null;
    });
  }
  applyHighlights(text: string) {
    if (text) {
      let start = this.$textarea.nativeElement.selectionStart;
      let end = this.$textarea.nativeElement.selectionEnd;
      if (start == end) this.highlightedText = text;
      else {
        const selected = text.substr(start, end - start);
        this.toolTipText=this.getTooltipText(selected)
        this.highlightedText =
          text.substr(0, start) +
          "<span id='mark'>" +
          selected +
          '</span>' +
          text.substr(end);
        this.resize();
        setTimeout(() => {
          const recArea = this.$textarea.nativeElement.getBoundingClientRect();
          const recText = this.$backdrop.nativeElement.getBoundingClientRect();

          const rect = document.getElementById('mark').getBoundingClientRect();
          rect.y += window.scrollY;
          rect.x+=rect.width/2
          this.rect = rect.y - window.scrollY < recArea.y ? null : rect;
        });
      }
    }

  }
  handleScroll() {
    var scrollTop = this.$textarea.nativeElement.scrollTop;
    this.$backdrop.nativeElement.scrollTop = scrollTop;

    var scrollLeft = this.$textarea.nativeElement.scrollLeft;
    this.$backdrop.nativeElement.scrollLeft = scrollLeft;
  }

And in the stackblitz I put in a custom form component (the reason is that Owen make this work for me

Eliseo
  • 50,109
  • 4
  • 29
  • 67