Cortex
Cortex

Reputation: 27

Is there any way to render focus on ng-template modal using keyboard arrow key?

I used one directive and service for gaining focus and that is working on all pages when pressing the arrow keys but on ng-template modal the focus is not gathering and am getting the find index value -1.Please refer this link Link for complete code to see the full code.The solution provided in that link is working on everywhere except the ng-template modal.Please refer with some ideas to solve this issues.

   <ng-template #content let-modal>
     <div class="modal-header">
     <h3 class="modal-title">Accept Offer</h3>
     </div>
     <div class="modal-body">
     <p>Do you really want to accept the offer?</p>
     </div>
     <div class="modal-footer>
     <button type="submit" class="btn btn-primary tab" (click)="onAcceptOffer()" arrow-div>Submit</button>
        <button type="button" class="btn btn-secondary tab" (click)="modal.dismiss('Crossclick');
       isClicked=false" arrow-div>Cancel</button></div>
     </ng-template>

Upvotes: 0

Views: 305

Answers (1)

Eliseo
Eliseo

Reputation: 57939

Update 2022-08-11 Control input Select (I change the function onArrowDown)

We can improve the code of the link using a directive instead of use fromEvent in main component.

I want that keydown can be listend from document or from another div. Futhermore, we need control if the element is an Input or a select to change a bit how the arrow work

So we can make a directive like

export enum Key {
  Tab = 9,
  Enter = 13,
  Escape = 27,
  Space = 32,
  PageUp = 33,
  PageDown = 34,
  End = 35,
  Home = 36,
  ArrowLeft = 37,
  ArrowUp = 38,
  ArrowRight = 39,
  ArrowDown = 40,
}

@Directive({
  selector: '[div-group-arrow]',
})
export class DivGroupArrowDirective implements OnInit, OnDestroy {
  @ContentChildren(ControlArrowDirective, { descendants: true })
  items: QueryList<ControlArrowDirective>;
  active: boolean = true;
  subscription: Subscription;

  @Input() step = 1;
  @Input() focus = true;
  @Input() main = false;
  constructor(private elementRef: ElementRef) {}
  ngOnInit() {
    this.subscription = fromEvent(
      this.main ? document : this.elementRef.nativeElement,
      'keydown'
    )
      .pipe(
        filter(
          (event: any) =>
            this.active &&
            event.which >= Key.ArrowLeft &&
            event.which <= Key.ArrowDown
        )
      )
      .subscribe((event) => {
        this.onArrowDown(event);
      });
  }
  ngOnDestroy() {
    this.subscription && this.subscription.unsubscribe();
  }
  onArrowDown(event: any) {
    const focused = this.items.find(
      (x) => x.elementRef.nativeElement == document.activeElement
    );
    if (!focused) {
      this.items.first.elementRef.nativeElement.focus();
      return;
    }
    let index = this.items.reduce((a, b, i) => (b == focused ? i : a), 0);

    const htmlElement = focused.elementRef.nativeElement;
    const isInput = htmlElement.tagName == 'INPUT';
    const isSelect=htmlElement.tagName == 'SELECT';
    const oldIndex = index;
    switch (event.which) {
      case Key.ArrowLeft:
        if ((isInput && !htmlElement.selectionStart) || isSelect)event.preventDefault();
        index += !isInput || !htmlElement.selectionStart ? -1 : 0;
        break;
      case Key.ArrowRight:
        if ((isInput && htmlElement.selectionEnd == htmlElement.value.length) || isSelect)
          event.preventDefault();
        index +=
          !isInput || htmlElement.selectionEnd == htmlElement.value.length
            ? 1
            : 0;
        break;
      case Key.ArrowUp:
        if (!isSelect) {
          index -= this.step;
          event.preventDefault();
        }
        break;
      case Key.ArrowDown:
        if (!isSelect) {
          index += this.step;
          event.preventDefault();
        }
        break;
    }
    if (index >= 0 && index < this.items.length && index != oldIndex) {
      const next = this.items.find((_, i) => i == index).elementRef
        .nativeElement;
      if (next.tagName == 'INPUT') {
        next.selectionStart = 0;
        next.selectionEnd = this.focus ? next.value.length : 0;
      }
      next.focus();
    }
  }

}

See that we have a property "active" to help us "stop" the listener

The control-arrow is simple

@Directive({
  selector: '[control-arrow]'
})
export class ControlArrowDirective {

  constructor(public elementRef:ElementRef) { }

}

We can then use, e.g. (I use ngb-bootstrap modal but we can use the same using material or whatever

<!--see that in main.html, we use [main]="true"-->
<div class="container mt-3" div-group-arrow [main]="true" [step]="2">
  <input control-arrow class="me-2 my-2" />
  <input control-arrow class="me-2 my-2"/><br />
  <input control-arrow class="me-2"/>
  <input control-arrow class="me-2 mb-3"/>

  <button class="btn btn-lg btn-outline-primary" (click)="open(content, null)">
    Launch demo modal
  </button>
</div>

<ng-template #content let-modal>
  <!---we enclose all in a div with the directive "div-group-arrow"-->
  <div div-group-arrow>
    <div class="modal-header">
      ...
    </div>
    <div class="modal-body">
      ...
    </div>
    <div class="modal-footer">
      <button
        control-arrow
        type="button"
        class="btn btn-outline-dark"
        (click)="modal.close('Save click')"
      >
        Save
      </button>

      <button
        control-arrow
        type="button"
        class="btn btn-outline-dark"
        (click)="modal.dismiss('Cancel click')"
      >
        Cancel
      </button>
    </div>
  </div>
</ng-template>

The stackblitz

Update Really I don't like the property "active". We can avoid simply change the filter

.pipe(
    filter(
      (event: any) =>
        event.which >= Key.ArrowLeft &&
        event.which <= Key.ArrowDown &&
        ((document.activeElement.tagName=='BODY' && this.main)
              ||this.elementRef.nativeElement.contains(document.activeElement))
    )
  )

Upvotes: 1

Related Questions