ng-scroll-picker
Version:
Base logic inspired from https://github.com/hiyali/ng-data-picker
464 lines (452 loc) • 21.7 kB
JavaScript
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