Reputation: 27
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
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