UNPKG

ng-scroll-picker

Version:

Base logic inspired from https://github.com/hiyali/ng-data-picker

464 lines (452 loc) 21.7 kB
import * as i0 from '@angular/core'; import { EventEmitter, ElementRef, Component, Inject, ViewChildren, ViewChild, Input, Output, NgModule } from '@angular/core'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; class NgScrollPickerComponent { constructor(elementRef) { this.data = []; this.change = new EventEmitter(); this.touchOrMouse = { isTouchable: 'ontouchstart' in window, isMouseDown: false, }; this.draggingInfo = { isDragging: false, groupIndex: null, startPageY: null, }; this.itemPerDegree = 23; this.safeDoTimeoutId = null; } ngOnInit() { this.currentIndexList = this.getInitialCurrentIndexList(); this.lastCurrentIndexList = new Array().concat(this.currentIndexList); this.groupsRectList = new Array(this.data.length); } ngAfterViewInit() { this.eventsRegister(); window.addEventListener('resize', this.safeGetRectsBindEvents.bind(this)); this.getGroupsRectList(); } ngOnDestroy() { window.removeEventListener('resize', this.safeGetRectsBindEvents.bind(this)); } setGroupData(gIndex, groupData) { if (!this.currentIndexList) { this.currentIndexList = this.getInitialCurrentIndexList(); } this.data[gIndex] = groupData; const iCI = groupData.currentIndex; let movedIndex = 0; if (typeof iCI === 'number' && iCI >= 0 && groupData.list && groupData.list.length && iCI <= groupData.list.length - 1) { movedIndex = Math.round(iCI); } this.currentIndexList[gIndex] = movedIndex; this.lastCurrentIndexList = new Array().concat(this.currentIndexList); } getInitialCurrentIndexList() { return this.data.map((item, index) => { const iCI = item.currentIndex; if (typeof iCI === 'number' && iCI >= 0 && item.list && item.list.length && iCI <= item.list.length - 1) { return Math.round(iCI); } return 0; }); } safeGetRectsBindEvents() { if (this.safeDoTimeoutId) { clearTimeout(this.safeDoTimeoutId); } this.safeDoTimeoutId = setTimeout(() => { this.getGroupsRectList(); }, 200); } getGroupsRectList() { if (this.pickerGroupLayer) { this.pickerGroupLayer.toArray().forEach((item, index) => { this.groupsRectList[index] = item.nativeElement.getBoundingClientRect(); }); } } eventsRegister() { const handleEventLayer = this.pickerHandleLayer.nativeElement; if (handleEventLayer) { this.addEventsForElement(handleEventLayer); } } addEventsForElement(el) { const _ = this.touchOrMouse.isTouchable; const eventHandlerList = [ { name: _ ? 'touchstart' : 'mousedown', handler: this.handleStart }, { name: _ ? 'touchmove' : 'mousemove', handler: this.handleMove }, { name: _ ? 'touchend' : 'mouseup', handler: this.handleEnd }, { name: _ ? 'touchcancel' : 'mouseleave', handler: this.handleCancel }, { name: 'wheel', handler: this.handleScroll }, ]; eventHandlerList.forEach((item, index) => { el.removeEventListener(item.name, item.handler, false); el.addEventListener(item.name, item.handler.bind(this), false); }); } triggerMiddleLayerGroupClick(gIndex) { const data = this.data; if (data[gIndex].onClick !== undefined) { const click = data[gIndex].onClick; if (typeof gIndex === 'number' && typeof data[gIndex].onClick === 'function') { // click( // gIndex, // this.currentIndexList[gIndex], // this.data[gIndex].list[this.currentIndexList[gIndex]] // ); const response = { gIndex, iIndex: this.currentIndexList[gIndex], selectedValue: this.data[gIndex].list[this.currentIndexList[gIndex]], groupName: this.data[gIndex].groupName, }; click(response); } } } triggerAboveLayerClick(ev, gIndex) { const movedIndex = this.currentIndexList[gIndex] + 1; this.currentIndexList[gIndex] = movedIndex; this.correctionCurrentIndex(ev, gIndex); } triggerMiddleLayerClick(ev, gIndex) { this.triggerMiddleLayerGroupClick(gIndex); } triggerBelowLayerClick(ev, gIndex) { const movedIndex = this.currentIndexList[gIndex] - 1; this.currentIndexList[gIndex] = movedIndex; this.correctionCurrentIndex(ev, gIndex); } getTouchInfo(ev) { return this.touchOrMouse.isTouchable ? ev.changedTouches[0] || ev.touches[0] : ev; } getGroupIndexBelongsEvent(ev) { const touchInfo = this.getTouchInfo(ev); for (let i = 0; i < this.groupsRectList.length; i++) { const item = this.groupsRectList[i]; if (item.left < touchInfo.pageX && touchInfo.pageX < item.right) { return i; } } return null; } handleEventClick(ev) { const gIndex = this.getGroupIndexBelongsEvent(ev); switch (ev.target.dataset.type) { case 'top': this.triggerAboveLayerClick(ev, gIndex); break; case 'middle': this.triggerMiddleLayerClick(ev, gIndex); break; case 'bottom': this.triggerBelowLayerClick(ev, gIndex); break; default: } } handleStart(ev) { if (ev.cancelable) { ev.preventDefault(); ev.stopPropagation(); } const touchInfo = this.getTouchInfo(ev); this.draggingInfo.startPageY = touchInfo.pageY; if (!this.touchOrMouse.isTouchable) { this.touchOrMouse.isMouseDown = true; } } handleMove(ev) { ev.preventDefault(); ev.stopPropagation(); if (this.touchOrMouse.isTouchable || this.touchOrMouse.isMouseDown) { this.draggingInfo.isDragging = true; this.setCurrentIndexOnMove(ev); } } handleEnd(ev) { ev.preventDefault(); ev.stopPropagation(); if (!this.draggingInfo.isDragging) { this.handleEventClick(ev); } this.draggingInfo.isDragging = false; this.touchOrMouse.isMouseDown = false; this.correctionAfterDragging(ev); } handleCancel(ev) { ev.preventDefault(); ev.stopPropagation(); if (this.touchOrMouse.isTouchable || this.touchOrMouse.isMouseDown) { this.correctionAfterDragging(ev); this.touchOrMouse.isMouseDown = false; this.draggingInfo.isDragging = false; } } handleScroll(ev) { ev.preventDefault(); ev.stopPropagation(); if (ev instanceof WheelEvent) { ev = ev; const gIndex = this.getGroupIndexBelongsEvent(ev); const deltaY = ev.deltaY; if (deltaY > 0) { // Scrolling down console.log('Scrolling down'); this.triggerAboveLayerClick(ev, gIndex); } else if (deltaY < 0) { // Scrolling up console.log('Scrolling up'); this.triggerBelowLayerClick(ev, gIndex); } else { // No vertical scrolling console.log('No vertical scrolling'); } } } setCurrentIndexOnMove(ev) { const touchInfo = this.getTouchInfo(ev); if (this.draggingInfo.groupIndex === null) { this.draggingInfo.groupIndex = this.getGroupIndexBelongsEvent(ev); } if (this.draggingInfo.groupIndex !== null) { const gIndex = this.draggingInfo.groupIndex; if (typeof gIndex === 'number' && (this.data[gIndex].divider || !this.data[gIndex].list)) { return; } if (this.draggingInfo.startPageY !== null) { const moveCount = (this.draggingInfo.startPageY - touchInfo.pageY) / 32; const movedIndex = this.currentIndexList[gIndex] + moveCount; this.currentIndexList[gIndex] = movedIndex; this.draggingInfo.startPageY = touchInfo.pageY; } } } correctionAfterDragging(ev) { const gIndex = this.draggingInfo.groupIndex; this.correctionCurrentIndex(ev, gIndex); this.draggingInfo.groupIndex = null; this.draggingInfo.startPageY = null; this.getCurrentIndexList(); } correctionCurrentIndex(ev, gIndex) { setTimeout(() => { if (typeof gIndex === 'number' && this.data[gIndex].divider !== true && this.data[gIndex].list.length > 0) { const unsafeGroupIndex = this.currentIndexList[gIndex]; let movedIndex = unsafeGroupIndex; if (unsafeGroupIndex > this.data[gIndex].list.length - 1) { movedIndex = this.data[gIndex].list.length - 1; } else if (unsafeGroupIndex < 0) { movedIndex = 0; } movedIndex = Math.round(movedIndex); this.currentIndexList[gIndex] = movedIndex; if (movedIndex !== this.lastCurrentIndexList[gIndex]) { const response = { gIndex, iIndex: movedIndex, selectedValue: this.data[gIndex].list[movedIndex], groupName: this.data[gIndex].groupName, }; // this.change.emit({ // gIndex, // iIndex: movedIndex, // selectedValue: this.data[gIndex].list[movedIndex], // }); this.change.emit(response); } this.lastCurrentIndexList = new Array().concat(this.currentIndexList); } }, 100); } isCurrentItem(gIndex, iIndex) { return this.currentIndexList[gIndex] === iIndex; } getCurrentIndexList() { return this.currentIndexList; } getGroupClass(gIndex) { const group = this.data[gIndex]; const defaultWeightClass = 'weight-' + (group.weight || 1); const groupClass = [defaultWeightClass]; if (group.className) { groupClass.push(group.className); } return groupClass; } getItemClass(gIndex, iIndex, isDivider = false) { const group = this.data[gIndex]; const itemClass = []; if (!isDivider && this.isCurrentItem(gIndex, iIndex)) { itemClass.push('smooth-item-selected'); } if (group.textAlign) { itemClass.push('text-' + group.textAlign); } return itemClass; } getItemStyle(gIndex, iIndex) { const gapCount = this.currentIndexList[gIndex] - iIndex; if (Math.abs(gapCount) < 90 / this.itemPerDegree) { const rotateStyle = { transform: 'rotateX(' + gapCount * this.itemPerDegree + 'deg) translate3d(0, 0, 5.625em)', opacity: (1 - Math.abs(gapCount) / (90 / this.itemPerDegree)).toString(), // color: gapCount === 0 ? 'rgba(237, 27, 45, 1)' : '', }; if (!this.draggingInfo.isDragging) { rotateStyle['transition'] = 'transform 150ms ease-out'; } return rotateStyle; } if (gapCount > 0) { return { transform: 'rotateX(90deg) translate3d(0, 0, 5.625em)' }; } else { return { transform: 'rotateX(-90deg) translate3d(0, 0, 5.625em)' }; } } } NgScrollPickerComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: NgScrollPickerComponent, deps: [{ token: ElementRef }], target: i0.ɵɵFactoryTarget.Component }); NgScrollPickerComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.9", type: NgScrollPickerComponent, selector: "ng-scroll-picker", inputs: { data: "data" }, outputs: { change: "change" }, viewQueries: [{ propertyName: "pickerHandleLayer", first: true, predicate: ["pickerHandleLayer"], descendants: true }, { propertyName: "pickerGroupLayer", predicate: ["pickerGroupLayer"], descendants: true }], ngImport: i0, template: ` <div class="ng-data-picker flex-box"> <div class="picker-handle-layer flex-box dir-column"> <div data-type="top" class="picker-top weight-1"></div> <div data-type="middle" class="picker-middle tengah"></div> <div data-type="bottom" class="picker-bottom weight-1"></div> </div> <div #pickerGroupLayer *ngFor="let group of data; let gIndex = index" class="picker-group" [ngClass]="getGroupClass(gIndex)" > <div class="picker-list"> <div *ngIf="group.divider; else ngIfElse" class="picker-item divider" [ngClass]="getItemClass(gIndex, gIndex, true)" > {{ group.text }} </div> <ng-template #ngIfElse> <div #ngIfElse *ngFor="let item of group.list; let iIndex = index" class="picker-item" [ngClass]="getItemClass(gIndex, iIndex)" [ngStyle]="getItemStyle(gIndex, iIndex)" > {{ item.label }} </div> </ng-template> </div> </div> <div #pickerHandleLayer class="picker-handle-layer flex-box dir-column"> <div data-type="top" class="picker-top weight-1"></div> <div data-type="middle" class="picker-middle"></div> <div data-type="bottom" class="picker-bottom weight-1"></div> </div> </div> `, isInline: true, styles: [".ng-data-picker{font-size:1rem;height:10em;position:relative;background-color:#fff;overflow:hidden}.ng-data-picker.black{color:#fff}.ng-data-picker .picker-list{height:6.25em;position:relative;top:4em}.ng-data-picker .picker-item{position:absolute;top:0;left:0;overflow:hidden;width:100%;text-overflow:ellipsis;white-space:nowrap;display:block;text-align:center;will-change:transform;contain:strict;height:2em;line-height:2;font-size:1em}.ng-data-picker .picker-handle-layer{position:absolute;width:100%;height:calc(100% + 2px);inset:-1px 0}.ng-data-picker .picker-handle-layer .picker-top{background:linear-gradient(to bottom,white 2%,rgba(255,255,255,.1) 100%);transform:translateZ(5.625em)}.ng-data-picker .picker-handle-layer .picker-middle{height:2em}.tengah{background-color:#dcdcdc;border-radius:5px}.ng-data-picker .picker-handle-layer .picker-bottom{background:linear-gradient(to top,white 2%,rgba(255,255,255,.1) 100%);transform:translateZ(5.625em)}.flex-box{display:flex}.flex-box.dir-column{flex-direction:column}.flex-box.dir-row{flex-direction:row}.flex-box .weight-1{flex:1}.flex-box .weight-2{flex:2}.flex-box .weight-3{flex:3}.flex-box .weight-4{flex:4}.flex-box .weight-5{flex:5}.flex-box .weight-6{flex:6}.flex-box .weight-7{flex:7}.flex-box .weight-8{flex:8}.flex-box .weight-9{flex:9}.flex-box .weight-10{flex:10}.flex-box .weight-11{flex:11}.flex-box .weight-12{flex:12}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: NgScrollPickerComponent, decorators: [{ type: Component, args: [{ selector: 'ng-scroll-picker', template: ` <div class="ng-data-picker flex-box"> <div class="picker-handle-layer flex-box dir-column"> <div data-type="top" class="picker-top weight-1"></div> <div data-type="middle" class="picker-middle tengah"></div> <div data-type="bottom" class="picker-bottom weight-1"></div> </div> <div #pickerGroupLayer *ngFor="let group of data; let gIndex = index" class="picker-group" [ngClass]="getGroupClass(gIndex)" > <div class="picker-list"> <div *ngIf="group.divider; else ngIfElse" class="picker-item divider" [ngClass]="getItemClass(gIndex, gIndex, true)" > {{ group.text }} </div> <ng-template #ngIfElse> <div #ngIfElse *ngFor="let item of group.list; let iIndex = index" class="picker-item" [ngClass]="getItemClass(gIndex, iIndex)" [ngStyle]="getItemStyle(gIndex, iIndex)" > {{ item.label }} </div> </ng-template> </div> </div> <div #pickerHandleLayer class="picker-handle-layer flex-box dir-column"> <div data-type="top" class="picker-top weight-1"></div> <div data-type="middle" class="picker-middle"></div> <div data-type="bottom" class="picker-bottom weight-1"></div> </div> </div> `, styles: [".ng-data-picker{font-size:1rem;height:10em;position:relative;background-color:#fff;overflow:hidden}.ng-data-picker.black{color:#fff}.ng-data-picker .picker-list{height:6.25em;position:relative;top:4em}.ng-data-picker .picker-item{position:absolute;top:0;left:0;overflow:hidden;width:100%;text-overflow:ellipsis;white-space:nowrap;display:block;text-align:center;will-change:transform;contain:strict;height:2em;line-height:2;font-size:1em}.ng-data-picker .picker-handle-layer{position:absolute;width:100%;height:calc(100% + 2px);inset:-1px 0}.ng-data-picker .picker-handle-layer .picker-top{background:linear-gradient(to bottom,white 2%,rgba(255,255,255,.1) 100%);transform:translateZ(5.625em)}.ng-data-picker .picker-handle-layer .picker-middle{height:2em}.tengah{background-color:#dcdcdc;border-radius:5px}.ng-data-picker .picker-handle-layer .picker-bottom{background:linear-gradient(to top,white 2%,rgba(255,255,255,.1) 100%);transform:translateZ(5.625em)}.flex-box{display:flex}.flex-box.dir-column{flex-direction:column}.flex-box.dir-row{flex-direction:row}.flex-box .weight-1{flex:1}.flex-box .weight-2{flex:2}.flex-box .weight-3{flex:3}.flex-box .weight-4{flex:4}.flex-box .weight-5{flex:5}.flex-box .weight-6{flex:6}.flex-box .weight-7{flex:7}.flex-box .weight-8{flex:8}.flex-box .weight-9{flex:9}.flex-box .weight-10{flex:10}.flex-box .weight-11{flex:11}.flex-box .weight-12{flex:12}\n"] }] }], ctorParameters: function () { return [{ type: i0.ElementRef, decorators: [{ type: Inject, args: [ElementRef] }] }]; }, propDecorators: { pickerGroupLayer: [{ type: ViewChildren, args: ['pickerGroupLayer'] }], pickerHandleLayer: [{ type: ViewChild, args: ['pickerHandleLayer'] }], data: [{ type: Input }], change: [{ type: Output }] } }); class NgScrollPickerModule { } NgScrollPickerModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: NgScrollPickerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); NgScrollPickerModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.9", ngImport: i0, type: NgScrollPickerModule, declarations: [NgScrollPickerComponent], imports: [CommonModule], exports: [NgScrollPickerComponent] }); NgScrollPickerModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: NgScrollPickerModule, imports: [CommonModule] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: NgScrollPickerModule, decorators: [{ type: NgModule, args: [{ declarations: [NgScrollPickerComponent], imports: [CommonModule], exports: [NgScrollPickerComponent], }] }] }); /* * Public API Surface of ng-scroll-picker */ /** * Generated bundle index. Do not edit. */ export { NgScrollPickerComponent, NgScrollPickerModule }; //# sourceMappingURL=ng-scroll-picker.mjs.map