Skip to content

Commit

Permalink
feat: add coordinates directive to handle x & y coordinates (#133)
Browse files Browse the repository at this point in the history
fixes mobile touch and touchmove issues
fixes mobile color selection when scrolled
removes duplicate coordinate code

breaking change: all mouse listeners now use angular hostlisteners
  • Loading branch information
scttcper authored Jun 6, 2018
1 parent e25870a commit f4a328b
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 310 deletions.
102 changes: 49 additions & 53 deletions src/lib/common/alpha.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,27 @@ import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
ElementRef,
EventEmitter,
Input,
NgModule,
OnChanges,
OnDestroy,
Output,
ViewChild,
} from '@angular/core';
import { fromEvent , Subscription } from 'rxjs';


import { CheckboardModule } from './checkboard.component';
import { calculateAlphaChange } from './helpers/alpha';
import { CoordinatesModule } from './coordinates.directive';
import { HSLA, RGBA } from './helpers/color.interfaces';


@Component({
selector: 'color-alpha',
template: `
<div class="alpha" [style.border-radius]="radius">
<div class="alpha-checkboard">
<color-checkboard></color-checkboard>
</div>
<div class="alpha-gradient"
[style.box-shadow]="shadow" [style.border-radius]="radius"
[ngStyle]="gradient"
></div>
<div
#container
class="alpha-container color-alpha-{{direction}}"
(mousedown)="handleMousedown($event)"
>
<div class="alpha-gradient" [ngStyle]="gradient" [style.box-shadow]="shadow" [style.border-radius]="radius"></div>
<div ngx-color-coordinates (coordinatesChange)="handleChange($event)" class="alpha-container color-alpha-{{direction}}">
<div class="alpha-pointer" [style.left.%]="pointerLeft" [style.top.%]="pointerTop">
<div class="alpha-slider" [ngStyle]="pointer"></div>
</div>
Expand Down Expand Up @@ -86,20 +75,17 @@ import { HSLA, RGBA } from './helpers/color.interfaces';
changeDetection: ChangeDetectionStrategy.OnPush,
preserveWhitespaces: false,
})
export class AlphaComponent implements OnChanges, OnDestroy {
export class AlphaComponent implements OnChanges {
@Input() hsl: HSLA;
@Input() rgb: RGBA;
@Input() pointer: { [key: string]: string };
@Input() shadow: string;
@Input() radius: string;
@Input() direction: 'horizontal' | 'vertical' = 'horizontal';
@Output() onChange = new EventEmitter<any>();
@ViewChild('container') container: ElementRef;
gradient: { [key: string]: string };
pointerLeft: number;
pointerTop: number;
mousemove: Subscription;
mouseup: Subscription;

ngOnChanges() {
if (this.direction === 'vertical') {
Expand All @@ -121,47 +107,57 @@ export class AlphaComponent implements OnChanges, OnDestroy {
this.pointerLeft = this.rgb.a * 100;
}
}
ngOnDestroy() {
this.unsubscribe();
}
handleMousemove($event: Event) {
this.handleChange($event);
}
handleMousedown($event: Event) {
this.handleChange($event);
this.subscribe();
}
subscribe() {
this.mousemove = fromEvent(document, 'mousemove').subscribe((ev: Event) =>
this.handleMousemove(ev),
);
this.mouseup = fromEvent(document, 'mouseup').subscribe(() =>
this.unsubscribe(),
);
}
unsubscribe() {
if (this.mousemove) {
this.mousemove.unsubscribe();
}
if (this.mouseup) {
this.mouseup.unsubscribe();
handleChange({ top, left, containerHeight, containerWidth, $event }) {
let data;
if (this.direction === 'vertical') {
let a;
if (top < 0) {
a = 0;
} else if (top > containerHeight) {
a = 1;
} else {
a = Math.round(top * 100 / containerHeight) / 100;
}

if (this.hsl.a !== a) {
data = {
h: this.hsl.h,
s: this.hsl.s,
l: this.hsl.l,
a,
source: 'rgb',
};
}
} else {
let a;
if (left < 0) {
a = 0;
} else if (left > containerWidth) {
a = 1;
} else {
a = Math.round(left * 100 / containerWidth) / 100;
}

if (this.hsl.a !== a) {
data = {
h: this.hsl.h,
s: this.hsl.s,
l: this.hsl.l,
a,
source: 'rgb',
};
}
}
}
handleChange($event: Event) {
const data = calculateAlphaChange(
$event,
this,
this.container.nativeElement,
);
if (data) {
this.onChange.emit({ data, $event });
if (!data) {
return null;
}
this.onChange.emit({ data, $event });
}
}

@NgModule({
declarations: [AlphaComponent],
exports: [AlphaComponent],
imports: [CommonModule, CheckboardModule],
imports: [CommonModule, CheckboardModule, CoordinatesModule],
})
export class AlphaModule {}
108 changes: 108 additions & 0 deletions src/lib/common/coordinates.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import {
Directive,
ElementRef,
HostListener,
NgModule,
OnDestroy,
OnInit,
Output,
} from '@angular/core';

import { Subject, Subscription } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';

@Directive({ selector: '[ngx-color-coordinates]' })
export class CoordinatesDirective implements OnInit, OnDestroy {
@Output()
coordinatesChange = new Subject<{
x: number;
y: number;
top: number;
left: number;
containerWidth: number;
containerHeight: number;
$event: any;
}>();
private mousechange = new Subject<{
x: number;
y: number;
$event: any;
isTouch: boolean;
}>();

private mouseListening = false;
private sub: Subscription;
@HostListener('window:mousemove', ['$event', '$event.pageX', '$event.pageY'])
@HostListener('window:touchmove', [
'$event',
'$event.touches[0].clientX',
'$event.touches[0].clientY',
'true',
])
mousemove($event: Event, x: number, y: number, isTouch = false) {
if (this.mouseListening) {
$event.preventDefault();
this.mousechange.next({ $event, x, y, isTouch });
}
}
@HostListener('window:mouseup')
@HostListener('window:touchend')
mouseup() {
this.mouseListening = false;
}
@HostListener('mousedown', ['$event', '$event.pageX', '$event.pageY'])
@HostListener('touchstart', [
'$event',
'$event.touches[0].clientX',
'$event.touches[0].clientY',
'true',
])
mousedown($event: Event, x: number, y: number, isTouch = false) {
$event.preventDefault();
this.mouseListening = true;
this.mousechange.next({ $event, x, y, isTouch });
}

constructor(private el: ElementRef) {}

ngOnInit() {
this.sub = this.mousechange
.pipe(
// limit times it is updated for the same area
distinctUntilChanged((p, q) => p.x === q.x && p.y === q.y),
)
.subscribe(n => this.handleChange(n.x, n.y, n.$event, n.isTouch));
}

ngOnDestroy() {
this.sub.unsubscribe();
}

handleChange(x: number, y: number, $event: Event, isTouch: boolean) {
const containerWidth = this.el.nativeElement.clientWidth;
const containerHeight = this.el.nativeElement.clientHeight;
const left =
x -
(this.el.nativeElement.getBoundingClientRect().left + window.pageXOffset);
let top = y - this.el.nativeElement.getBoundingClientRect().top;

if (!isTouch) {
top = top - window.pageYOffset;
}
this.coordinatesChange.next({
x,
y,
top,
left,
containerWidth,
containerHeight,
$event,
});
}
}

@NgModule({
declarations: [CoordinatesDirective],
exports: [CoordinatesDirective],
})
export class CoordinatesModule {}
13 changes: 4 additions & 9 deletions src/lib/common/editable-input.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,10 @@ import { fromEvent, Subscription } from 'rxjs';
selector: 'color-editable-input',
template: `
<div class="wrap" [ngStyle]="wrapStyle">
<input [ngStyle]="inputStyle"
[value]="currentValue"
[placeholder]="placeholder"
spellCheck="false"
(keydown)="handleKeydown($event)"
(keyup)="handleKeyup($event)"
(focus)="handleFocus($event)"
(focusout)="handleFocusOut($event)"
/>
<input [ngStyle]="inputStyle" spellCheck="false"
[value]="currentValue" [placeholder]="placeholder"
(keydown)="handleKeydown($event)" (keyup)="handleKeyup($event)"
(focus)="handleFocus($event)" (focusout)="handleFocusOut($event)" />
<span *ngIf="label" [ngStyle]="labelStyle" (mousedown)="handleMousedown($event)">
{{ label }}
</span>
Expand Down
51 changes: 0 additions & 51 deletions src/lib/common/helpers/alpha.ts

This file was deleted.

8 changes: 0 additions & 8 deletions src/lib/common/helpers/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,3 @@ export function getContrastingColor(data) {
const yiq = (col.rgb.r * 299 + col.rgb.g * 587 + col.rgb.b * 114) / 1000;
return yiq >= 128 ? '#000' : '#fff';
}


export const red = {
hsl: { a: 1, h: 0, l: 0.5, s: 1 },
hex: '#ff0000',
rgb: { r: 255, g: 0, b: 0, a: 1 },
hsv: { h: 0, s: 1, v: 1, a: 1 },
};
Loading

0 comments on commit f4a328b

Please sign in to comment.