import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, Renderer2, SimpleChanges, TemplateRef, ViewChild, ViewEncapsulation, HostBinding, ComponentRef, ViewContainerRef, ComponentFactoryResolver, HostListener, Injector } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { UpdateHostClassService as UpdateCls } from './services/update-host-class.service'; import { slideMotion } from './animations/slide'; import { toBoolean, convertToDate, convertToStr } from './utils/convert'; import { TimePickerPanelComponent } from './time-picker-panel.component'; import { TimePickerLocaleService } from './services/time-picker.locale.service'; let num = 0; @Component({ encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, // tslint:disable-next-line:component-selector selector: 'farris-time-picker', exportAs: 'timePicker', // styleUrls: ['./style/index.scss'], templateUrl: './time-picker.component.html', animations: [slideMotion], providers: [ UpdateCls, { provide: NG_VALUE_ACCESSOR, useExisting: TimePickerComponent, multi: true } ] }) export class TimePickerComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges { @ViewChild('timeInput') public timeInput: ElementRef; @HostBinding('class.f-component-timepicker') hostCls = true; private _disabled = false; private _readonly = false; private _editable = true; private _value: string; private _allowEmpty = true; private _autoFocus = false; private _onChange: (value: string) => void; private _onTouched: (value: string) => void; private _hideDisabledOptions = false; private cRef: ComponentRef = null; isInit = false; groupIcon = ''; @HostBinding('style.display') display = 'block'; @Input() hourStep = 1; @Input() minuteStep = 1; @Input() secondStep = 1; @Input() clearText = 'clear'; @Input() popupClassName = ''; @Input() placeholder; @Input() addOn: TemplateRef; @Input() defaultOpenValue = new Date(); @Input() disabledHours: () => number[]; @Input() disabledMinutes: (hour: number) => number[]; @Input() disabledSeconds: (hour: number, minute: number) => number[]; @Input() format = 'HH:mm:ss'; @Input() isOpen = false; @Input() set use12Hours(value: boolean) { this._use12Hours = !!value; if (value && this.format) { this.format = this.format.replace(/H/g, 'h'); } else if (!value && this.format) { this.format = this.format.replace(/h/g, 'H'); } if (this._use12Hours) { if (!this.format) { this.format = 'hh:mm:ss a'; } else { if (this.format.indexOf('a') === -1 ) { this.format = this.format + ' a'; } } } } get use12Hours(): boolean { return this._use12Hours; } @Output() readonly openChange = new EventEmitter(); @Output() valueChange = new EventEmitter(); @Output() clear = new EventEmitter(); @Input() set hideDisabledOptions(value: boolean) { this._hideDisabledOptions = toBoolean(value); } get hideDisabledOptions(): boolean { return this._hideDisabledOptions; } @Input() set allowEmpty(value: boolean) { this._allowEmpty = toBoolean(value); } get allowEmpty(): boolean { return this._allowEmpty; } @Input() set autoFocus(value: boolean) { this._autoFocus = toBoolean(value); this.updateAutoFocus(); } get autoFocus(): boolean { return this._autoFocus; } @Input() set disabled(value: boolean | string) { this._disabled = toBoolean(value); } get disabled(): boolean | string { return this._disabled; } @Input() set readonly(value: boolean | string) { this._readonly = toBoolean(value); } get readonly(): boolean | string { return this._readonly; } @Input() set editable(value: boolean | string) { this._editable = toBoolean(value); } get editable(): boolean | string { return this._editable; } set value(value: string) { this._value = value; if (this._onChange) { this._onChange(this.value); } } get value(): string { return this._value; } private _use12Hours = false; private overlayRef: Element; private overlayPanelRef: Element; private documentClickEvent: any; private changeFlag = false; constructor( private element: ElementRef, private renderer: Renderer2, private vcRef: ViewContainerRef, private cfr: ComponentFactoryResolver, public cdr: ChangeDetectorRef, private localeService: TimePickerLocaleService ) { if (this.localeService) { const localConfig = this.localeService.getLocaleConfig(); this.placeholder = this.placeholder !== undefined ? this.placeholder : localConfig.placeholder ? localConfig.placeholder : '请选择时间'; } } ngOnInit(): void { } ngOnChanges(changes: SimpleChanges): void { const { use12Hours} = changes; if (use12Hours && (!this.format || this.format.indexOf('a') === -1 )) { if (this.use12Hours) { this.format = this.format + ' a'; } } } ngAfterViewInit(): void { this.isInit = true; this.updateAutoFocus(); if (!document.body.querySelector('.time-picker-overlay')) { this.overlayRef = this.renderer.createElement('div'); this.overlayPanelRef = this.renderer.createElement('div'); this.renderer.addClass(this.overlayRef, 'time-picker-overlay'); this.renderer.addClass( this.overlayPanelRef, `overlay-panel-${num++}` ); this.renderer.appendChild(this.overlayRef, this.overlayPanelRef); document.body.appendChild(this.overlayRef); } else { this.overlayRef = document.body.querySelector( '.time-picker-overlay' ); this.overlayPanelRef = this.renderer.createElement('div'); this.renderer.addClass( this.overlayPanelRef, `overlay-panel-${num++}` ); this.renderer.appendChild(this.overlayRef, this.overlayPanelRef); } this.overlayRef.addEventListener('click', (e) => { e.stopPropagation(); }); this.timeInput.nativeElement.addEventListener('blur', () => { this.onBlur(); }); } // tslint:disable-next-line:use-life-cycle-interface ngOnDestroy(): void { this.timeInput.nativeElement.removeEventListener('blur', this.onBlur); } @HostListener('mouseenter', ['$event']) onMouseEnter(event) { if ( this.timeInput.nativeElement.value && !this.readonly && !this.disabled ) { event.target.querySelector('.input-group-clear').style.display = ''; } } @HostListener('mouseleave', ['$event']) onMouseLeave(event) { if (!this.readonly && !this.disabled) { event.target.querySelector('.input-group-clear').style.display = 'none'; } } open(event: MouseEvent) { event.stopPropagation(); if (this.disabled || (!this.editable && this.readonly)) { return; } if (this.isOpen) { this.close(); return; } this.creatPanel(); this.isOpen = true; this.openChange.emit(this.isOpen); this.renderer.appendChild( this.overlayPanelRef, this.cRef.location.nativeElement ); this.renderer.setStyle(this.overlayRef, 'display', 'block'); this.documentClickEvent = this.registerMouseDownEvent(); setTimeout(() => { this.setPosition(); this.renderer.addClass(this.cRef.location.nativeElement, 'f-area-show'); }, 10); } close(): void { this.isOpen = false; this.cRef.instance.opened = false; this.renderer.removeClass(this.cRef.location.nativeElement, 'f-area-show'); while (this.overlayPanelRef.lastChild) { this.overlayPanelRef.removeChild(this.overlayPanelRef.lastChild); } this.openChange.emit(this.isOpen); this.focus(); this.removeDocumentListener(); if (this.cRef) { this.cRef.destroy(); this.cRef = null; } } private registerMouseDownEvent() { const mousedownEvent = (e) => { if (e.target === this.overlayPanelRef.parentElement) { this.renderer.setStyle(this.overlayRef, 'display', 'none'); this.close(); } }; document.body.addEventListener('mousedown', mousedownEvent, true); document.body.addEventListener('mousewheel', mousedownEvent, true); return () => { document.body.removeEventListener('mousedown', mousedownEvent, true); document.body.removeEventListener('mousewheel', mousedownEvent, true); }; } private removeDocumentListener() { if (this.documentClickEvent) { this.documentClickEvent(); this.documentClickEvent = null; } } creatPanel() { if (this.cRef === null) { this.vcRef.clear(); this.cRef = this.vcRef.createComponent( this.cfr.resolveComponentFactory(TimePickerPanelComponent) ); this.cRef.location.nativeElement.classList.add('f-area-hide'); // 解决在grid懒加载中,没有刷新视图的问题 this.cRef.changeDetectorRef.detectChanges(); this.cRef.instance.format = this.format; this.cRef.instance.hourStep = this.hourStep; this.cRef.instance.minuteStep = this.minuteStep; this.cRef.instance.secondStep = this.secondStep; this.cRef.instance.disabledHours = this.disabledHours; this.cRef.instance.disabledMinutes = this.disabledMinutes; this.cRef.instance.disabledSeconds = this.disabledSeconds; this.cRef.instance.hideDisabledOptions = this.hideDisabledOptions; this.cRef.instance.use12Hours = this.use12Hours; this.cRef.instance.opened = this.isOpen; this.cRef.instance.defaultOpenValue = this.defaultOpenValue; this.cRef.instance.value = this.value; this.cRef.instance.valueChange.subscribe((val: string) => { this.value = val; this.cdr.detectChanges(); this.valueChange.emit(val); }); } else { this.cRef.instance.value = this.value; } } setPosition() { const { top: elTop, left: elLeft, height: elHeight, width: elWidth } = this.element.nativeElement.getBoundingClientRect(); const panelHeight = 194; if (elTop + panelHeight + 30 > window.innerHeight) { this.renderer.setStyle(this.cRef.location.nativeElement, 'top', `${elTop - panelHeight}px`); } else { this.renderer.setStyle(this.cRef.location.nativeElement, 'top', `${elTop + elHeight}px`); } const panelWidth = this.cRef.instance.panelWidth; if (window.innerWidth - elLeft < panelWidth) { const r = window.innerWidth - elLeft - elWidth; this.renderer.setStyle(this.cRef.location.nativeElement, 'right', `${r}px`); } else { this.renderer.setStyle(this.cRef.location.nativeElement, 'left', `${elLeft}px`); } this.cRef.instance.opened = true; } updateAutoFocus(): void { } onClickClearBtn(): void { this.value = null; this.cRef.instance.time.clear(); } focus(): void { if (this.timeInput.nativeElement) { this.timeInput.nativeElement.focus(); } } blur(): void { if (this.timeInput.nativeElement) { this.timeInput.nativeElement.blur(); } } onBlur(): void { if (this.value) { const date = convertToDate(this.value, this.format); if (this.cRef) { this.cRef.instance.value = this.value; } this.value = convertToStr(date, this.format); if (this.changeFlag) { this.valueChange.emit(this.value); this.changeFlag = false; } if (this._onTouched) { this._onTouched(this.value); } } } onValueChange(val: string) { this.changeFlag = true; } onClear() { this.clear.emit(); this.value = ''; if (this.cRef && this.cRef.instance) { this.cRef.instance.time.clear(); } if (this._onTouched) { this._onTouched(this.value); } this.cdr.detectChanges(); } writeValue(time: string): void { this._value = time; this.cdr.markForCheck(); } registerOnChange(fn: (time: string) => void): void { this._onChange = fn; } registerOnTouched(fn: (time: string) => void): void { this._onTouched = fn; } setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; this.cdr.markForCheck(); } }