Last active
May 5, 2020 19:13
-
-
Save FFKL/0a15b5421393171426af166437a943b5 to your computer and use it in GitHub Desktop.
Numbers range component
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <sv-numbers-range> | |
| <input svThreshold placeholder="Min price" formControlName="min" type="text"> | |
| <input svThreshold placeholder="Max price" formControlName="max" type="text"> | |
| </sv-numbers-range> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| AfterContentInit, | |
| ChangeDetectionStrategy, | |
| Component, | |
| ContentChildren, | |
| OnDestroy, | |
| QueryList | |
| } from '@angular/core'; | |
| import { ThresholdDirective } from './threshold.directive'; | |
| import { Subject } from 'rxjs'; | |
| import { takeUntil } from 'rxjs/operators'; | |
| @Component({ | |
| selector: 'sv-numbers-range', | |
| template: '<ng-content></ng-content>', | |
| changeDetection: ChangeDetectionStrategy.OnPush | |
| }) | |
| export class NumbersRangeComponent implements AfterContentInit, OnDestroy { | |
| @ContentChildren(ThresholdDirective) thresholds!: QueryList<ThresholdDirective>; | |
| private unsubscribe$ = new Subject<void>(); | |
| ngAfterContentInit() { | |
| if (this.thresholds.length !== 2) throw new Error('Numbers range should include 2 inputs thresholds inside'); | |
| const [minInput, maxInput] = this.thresholds.toArray(); | |
| this.subscribeOnInputValue(minInput, maxInput, (min, max) => min <= max); | |
| this.subscribeOnInputValue(maxInput, minInput, (max, min) => max >= min); | |
| } | |
| ngOnDestroy() { | |
| this.unsubscribe$.next(); | |
| this.unsubscribe$.complete(); | |
| } | |
| private subscribeOnInputValue(target: ThresholdDirective, other: ThresholdDirective, isValidFn: (t: number, o: number) => boolean) { | |
| target.valueChanged$.pipe( | |
| takeUntil(this.unsubscribe$) | |
| ).subscribe(targetVal => { | |
| const otherVal = other.getValue(); | |
| if (otherVal && !isValidFn(targetVal, otherVal)) { | |
| target.clearValue(); | |
| } | |
| }); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Directive, ElementRef, Renderer2, HostListener, Inject } from '@angular/core'; | |
| import { Subject } from 'rxjs'; | |
| import { DOCUMENT } from '@angular/common'; | |
| @Directive({ | |
| selector: 'input[svThreshold]' | |
| }) | |
| export class ThresholdDirective { | |
| constructor(private ref: ElementRef, private renderer: Renderer2, @Inject(DOCUMENT) private document: Document) {} | |
| valueChanged$: Subject<any> = new Subject(); | |
| private navigationKeys = [ | |
| 'Backspace', | |
| 'Delete', | |
| 'Tab', | |
| 'Escape', | |
| 'Enter', | |
| 'Home', | |
| 'End', | |
| 'ArrowLeft', | |
| 'ArrowRight', | |
| 'Clear', | |
| 'Copy', | |
| 'Paste' | |
| ]; | |
| @HostListener('keydown', ['$event']) | |
| onInput(e: KeyboardEvent) { | |
| if (this.isAllowedNavigationKey(e) || this.isAllowedShortcut(e)) return; | |
| if (e.key === ' ' || isNaN(Number(e.key))) { | |
| e.preventDefault(); | |
| } | |
| } | |
| @HostListener('blur', ['$event.target.value']) | |
| onBlur(val: string) { | |
| this.valueChanged$.next(Number(val)); | |
| } | |
| @HostListener('paste', ['$event']) | |
| onPaste(e: ClipboardEvent) { | |
| e.preventDefault(); | |
| const pastedInput: string = e.clipboardData! | |
| .getData('text/plain') | |
| .replace(/\D/g, ''); | |
| this.insertText(pastedInput); | |
| } | |
| @HostListener('drop', ['$event']) | |
| onDrop(event: DragEvent) { | |
| event.preventDefault(); | |
| this.ref.nativeElement.focus(); | |
| const textData = event.dataTransfer! | |
| .getData('text/plain').replace(/\D/g, ''); | |
| this.insertText(textData); | |
| } | |
| clearValue() { | |
| this.renderer.setProperty(this.ref.nativeElement, 'value', null); | |
| } | |
| getValue(): number { | |
| return Number(this.ref.nativeElement.value); | |
| } | |
| private insertText(data: string) { | |
| this.document.execCommand('insertText', false, data); | |
| } | |
| private isAllowedNavigationKey(e: KeyboardEvent) { | |
| return this.navigationKeys.includes(e.key); | |
| } | |
| private isAllowedShortcut(e: KeyboardEvent) { | |
| return (e.key === 'a' && e.ctrlKey) || // Allow: Ctrl+A | |
| (e.key === 'c' && e.ctrlKey) || // Allow: Ctrl+C | |
| (e.key === 'v' && e.ctrlKey) || // Allow: Ctrl+V | |
| (e.key === 'x' && e.ctrlKey) || // Allow: Ctrl+X | |
| (e.key === 'a' && e.metaKey) || // Allow: Cmd+A (Mac) | |
| (e.key === 'c' && e.metaKey) || // Allow: Cmd+C (Mac) | |
| (e.key === 'v' && e.metaKey) || // Allow: Cmd+V (Mac) | |
| (e.key === 'x' && e.metaKey) // Allow: Cmd+X (Mac) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment