Skip to content

Commit 9e0e070

Browse files
committed
v2.0.0-beta.5 add dragProgress$ and (event.out-zone)
1 parent 0543f4b commit 9e0e070

12 files changed

Lines changed: 427 additions & 368 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11

2+
<a name="2.0.0-beta.5"></a>
3+
# 2.0.0-beta.5 (2018-11-29)
4+
5+
* **Performance:** Make library template event bindings (`click`, `mousedown`, `touchstart`) runs outside `zone.js` to avoid unnecessary change detection run.
6+
* **Performance (API change):** Remove `(dragProgress)` event emitter from template and add a `dragProgress$` observable accessible from `SplitComponent` class. Doing this you can track drag progress without triggering change detection inside component containing `<as-split>`, see "Sync example demo" opening devTools console to verify it.
7+
8+
29
<a name="2.0.0-beta.4"></a>
310
# 2.0.0-beta.4 (2018-11-28)
411

projects/angular-split/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "angular-split",
3-
"version": "2.0.0-beta.4",
3+
"version": "2.0.0-beta.5",
44
"description": "Lightweight Angular UI library to split views and allow dragging to resize areas using CSS flexbox layout.",
55
"author": "bertrandg",
66
"repository": {

projects/angular-split/src/lib/component/split.component.ts

Lines changed: 82 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Component, Input, Output, HostBinding, ChangeDetectionStrategy, ChangeDetectorRef, Renderer2, AfterViewInit, OnDestroy, ElementRef, NgZone } from '@angular/core';
2-
import { Observable, Subscriber } from 'rxjs';
2+
import { Observable, Subscriber, Subject } from 'rxjs';
33
import { debounceTime } from 'rxjs/operators';
44

55
import { IArea } from '../interface/IArea';
66
import { IPoint } from '../interface/IPoint';
77
import { SplitAreaDirective } from '../directive/splitArea.directive';
8-
import { getPointFromEvent, getPixelSize } from '../utils';
8+
import { getPointFromEvent, getPixelSize, getInputBoolean, isValidTotalSize } from '../utils';
99

1010
/**
1111
* angular-split
@@ -50,9 +50,9 @@ import { getPointFromEvent, getPixelSize } from '../utils';
5050
class="as-split-gutter"
5151
[style.flex-basis.px]="gutterSize"
5252
[style.order]="index*2+1"
53-
(click)="clickGutter($event, index+1)"
54-
(mousedown)="startDragging($event, index*2+1, index+1)"
55-
(touchstart)="startDragging($event, index*2+1, index+1)">
53+
(click.out-zone)="clickGutter($event, index+1)"
54+
(mousedown.out-zone)="startDragging($event, index*2+1, index+1)"
55+
(touchstart.out-zone)="startDragging($event, index*2+1, index+1)">
5656
<div class="as-split-gutter-icon"></div>
5757
</div>
5858
</ng-template>`,
@@ -62,8 +62,7 @@ export class SplitComponent implements AfterViewInit, OnDestroy {
6262
private _direction: 'horizontal' | 'vertical' = 'horizontal';
6363

6464
@Input() set direction(v: 'horizontal' | 'vertical') {
65-
v = (v === 'vertical') ? 'vertical' : 'horizontal';
66-
this._direction = v;
65+
this._direction = (v === 'vertical') ? 'vertical' : 'horizontal';
6766

6867
this.renderer.addClass(this.elRef.nativeElement, `is-${ this._direction }`);
6968
this.renderer.removeClass(this.elRef.nativeElement, `is-${ (this._direction === 'vertical') ? 'horizontal' : 'vertical' }`);
@@ -95,11 +94,10 @@ export class SplitComponent implements AfterViewInit, OnDestroy {
9594
private _useTransition: boolean = false;
9695

9796
@Input() set useTransition(v: boolean) {
98-
v = (typeof(v) === 'boolean') ? v : (v === 'false' ? false : true);
99-
this._useTransition = v;
97+
this._useTransition = getInputBoolean(v);
10098

101-
if(v) this.renderer.addClass(this.elRef.nativeElement, 'is-transition');
102-
else this.renderer.removeClass(this.elRef.nativeElement, 'is-transition');
99+
if(this._useTransition) this.renderer.addClass(this.elRef.nativeElement, 'is-transition');
100+
else this.renderer.removeClass(this.elRef.nativeElement, 'is-transition');
103101
}
104102

105103
get useTransition(): boolean {
@@ -111,11 +109,10 @@ export class SplitComponent implements AfterViewInit, OnDestroy {
111109
private _disabled: boolean = false;
112110

113111
@Input() set disabled(v: boolean) {
114-
v = (typeof(v) === 'boolean') ? v : (v === 'false' ? false : true);
115-
this._disabled = v;
112+
this._disabled = getInputBoolean(v);
116113

117-
if(v) this.renderer.addClass(this.elRef.nativeElement, 'is-disabled');
118-
else this.renderer.removeClass(this.elRef.nativeElement, 'is-disabled');
114+
if(this._disabled) this.renderer.addClass(this.elRef.nativeElement, 'is-disabled');
115+
else this.renderer.removeClass(this.elRef.nativeElement, 'is-disabled');
119116
}
120117

121118
get disabled(): boolean {
@@ -139,37 +136,42 @@ export class SplitComponent implements AfterViewInit, OnDestroy {
139136

140137
////
141138

142-
private _dragStartSubscriber: Subscriber<{gutterNum: number, sizes: Array<number>}>
139+
private dragStartSubscriber: Subscriber<{gutterNum: number, sizes: Array<number>}>
143140
@Output() get dragStart(): Observable<{gutterNum: number, sizes: Array<number>}> {
144-
return new Observable(subscriber => this._dragStartSubscriber = subscriber);
141+
return new Observable(subscriber => this.dragStartSubscriber = subscriber);
145142
}
146-
private _dragProgressSubscriber: Subscriber<{gutterNum: number, sizes: Array<number>}>
147-
@Output() get dragProgress(): Observable<{gutterNum: number, sizes: Array<number>}> {
148-
return new Observable(subscriber => this._dragProgressSubscriber = subscriber);
149-
}
150-
private _dragEndSubscriber: Subscriber<{gutterNum: number, sizes: Array<number>}>
143+
144+
private dragEndSubscriber: Subscriber<{gutterNum: number, sizes: Array<number>}>
151145
@Output() get dragEnd(): Observable<{gutterNum: number, sizes: Array<number>}> {
152-
return new Observable(subscriber => this._dragEndSubscriber = subscriber);
146+
return new Observable(subscriber => this.dragEndSubscriber = subscriber);
153147
}
154-
private _gutterClickSubscriber: Subscriber<{gutterNum: number, sizes: Array<number>}>
148+
149+
private gutterClickSubscriber: Subscriber<{gutterNum: number, sizes: Array<number>}>
155150
@Output() get gutterClick(): Observable<{gutterNum: number, sizes: Array<number>}> {
156-
return new Observable(subscriber => this._gutterClickSubscriber = subscriber);
151+
return new Observable(subscriber => this.gutterClickSubscriber = subscriber);
157152
}
158153

159-
private _transitionEndSubscriber: Subscriber<Array<number>>
154+
private transitionEndSubscriber: Subscriber<Array<number>>
160155
@Output() get transitionEnd(): Observable<Array<number>> {
161-
return new Observable(subscriber => this._transitionEndSubscriber = subscriber).pipe(
156+
return new Observable(subscriber => this.transitionEndSubscriber = subscriber).pipe(
162157
debounceTime<Array<number>>(20)
163158
);
164159
}
160+
161+
private dragProgressSubject: Subject<{gutterNum: number, sizes: Array<number>}> = new Subject();
162+
dragProgress$: Observable<{gutterNum: number, sizes: Array<number>}> = this.dragProgressSubject.asObservable();
165163

166-
@HostBinding('style.min-width') get cssMinwidth() {
167-
return (this.direction === 'horizontal') ? `${ this.getNbGutters() * this.gutterSize }px` : null;
168-
}
164+
////
169165

170-
@HostBinding('style.min-height') get cssMinheight() {
171-
return (this.direction === 'vertical') ? `${ this.getNbGutters() * this.gutterSize }px` : null;
172-
}
166+
// @HostBinding('style.min-width') get cssMinwidth() {
167+
// return (this.direction === 'horizontal') ? `${ this.getNbGutters() * this.gutterSize }px` : null;
168+
// }
169+
170+
// @HostBinding('style.min-height') get cssMinheight() {
171+
// return (this.direction === 'vertical') ? `${ this.getNbGutters() * this.gutterSize }px` : null;
172+
// }
173+
174+
////
173175

174176
private isDragging: boolean = false;
175177
private currentGutterNum: number = 0;
@@ -279,6 +281,26 @@ export class SplitComponent implements AfterViewInit, OnDestroy {
279281
this.cdRef.markForCheck();
280282
}
281283

284+
public setVisibleAreaSizes(sizes: Array<number>): boolean {
285+
if(sizes.length !== this.displayedAreas.length) {
286+
return false;
287+
}
288+
289+
sizes = sizes.map(s => s / 100);
290+
291+
const total = sizes.reduce((total: number, v: number) => total + v, 0);
292+
if(!isValidTotalSize(total)) {
293+
return false;
294+
}
295+
296+
this.displayedAreas.forEach((area, i) => {
297+
area.component['_size'] = sizes[i];
298+
})
299+
300+
this.build(false, true);
301+
return true;
302+
}
303+
282304
private build(resetOrders: boolean, resetSizes: boolean): void {
283305
this.stopDragging();
284306

@@ -306,7 +328,7 @@ export class SplitComponent implements AfterViewInit, OnDestroy {
306328
const totalUserSize = <number> this.displayedAreas.reduce((total: number, s: IArea) => s.component.size ? total + s.component.size : total, 0);
307329

308330
// If user provided 'size' for each area and total == 1, use it.
309-
if(this.displayedAreas.every(a => a.component.size !== null) && totalUserSize > .999 && totalUserSize < 1.001 ) {
331+
if(this.displayedAreas.every(a => a.component.size !== null) && isValidTotalSize(totalUserSize) ) {
310332

311333
this.displayedAreas.forEach(area => {
312334
area.size = <number> area.component.size;
@@ -367,17 +389,21 @@ export class SplitComponent implements AfterViewInit, OnDestroy {
367389
}
368390

369391
public clickGutter(event: MouseEvent, gutterNum: number): void {
392+
event.preventDefault();
393+
event.stopPropagation();
394+
370395
if(this.startPoint && this.startPoint.x === event.pageX && this.startPoint.y === event.pageY) {
371396
this.currentGutterNum = gutterNum;
372397

373398
this.notify('click');
374399
}
375400
}
376401

377-
public startDragging(startEvent: MouseEvent | TouchEvent, gutterOrder: number, gutterNum: number): void {
378-
startEvent.preventDefault();
402+
public startDragging(event: MouseEvent | TouchEvent, gutterOrder: number, gutterNum: number): void {
403+
event.preventDefault();
404+
event.stopPropagation();
379405

380-
this.startPoint = getPointFromEvent(startEvent);
406+
this.startPoint = getPointFromEvent(event);
381407
if(!this.startPoint || this.disabled) {
382408
return;
383409
}
@@ -415,6 +441,7 @@ export class SplitComponent implements AfterViewInit, OnDestroy {
415441

416442
private dragEvent(event: MouseEvent | TouchEvent, areaA: IArea, areaB: IArea): void {
417443
event.preventDefault();
444+
event.stopPropagation();
418445

419446
if(!this.isDragging) {
420447
return;
@@ -479,6 +506,7 @@ export class SplitComponent implements AfterViewInit, OnDestroy {
479506

480507
this.refreshStyleSizes();
481508

509+
// If moved from starting point, notify progress
482510
if(this.startPoint.x !== this.endPoint.x || this.startPoint.y !== this.endPoint.y) {
483511
this.notify('progress');
484512
}
@@ -487,23 +515,25 @@ export class SplitComponent implements AfterViewInit, OnDestroy {
487515
private stopDragging(event?: Event): void {
488516
if(event) {
489517
event.preventDefault();
518+
event.stopPropagation();
490519
}
491-
520+
492521
if(this.isDragging === false) {
493522
return;
494523
}
495-
524+
496525
this.displayedAreas.forEach(area => {
497526
area.component.unlockEvents();
498527
});
499-
528+
500529
while(this.dragListeners.length > 0) {
501530
const fct = this.dragListeners.pop();
502531
if(fct) {
503532
fct();
504533
}
505534
}
506535

536+
// If moved from starting point, notify end
507537
if(this.endPoint && (this.startPoint.x !== this.endPoint.x || this.startPoint.y !== this.endPoint.y)) {
508538
this.notify('end');
509539
}
@@ -520,35 +550,33 @@ export class SplitComponent implements AfterViewInit, OnDestroy {
520550
});
521551
}
522552

523-
524553
public notify(type: 'start' | 'progress' | 'end' | 'click' | 'transitionEnd'): void {
525554
const sizes: Array<number> = this.displayedAreas.map(a => a.size * 100);
526555

527556
if(type === 'start') {
528-
if(this._dragStartSubscriber) {
529-
this.ngZone.run(() => this._dragStartSubscriber.next({gutterNum: this.currentGutterNum, sizes}));
530-
}
531-
}
532-
else if(type === 'progress') {
533-
if(this._dragProgressSubscriber) {
534-
this.ngZone.run(() => this._dragProgressSubscriber.next({gutterNum: this.currentGutterNum, sizes}));
557+
if(this.dragStartSubscriber) {
558+
this.ngZone.run(() => this.dragStartSubscriber.next({gutterNum: this.currentGutterNum, sizes}));
535559
}
536560
}
537561
else if(type === 'end') {
538-
if(this._dragEndSubscriber) {
539-
this.ngZone.run(() => this._dragEndSubscriber.next({gutterNum: this.currentGutterNum, sizes}));
562+
if(this.dragEndSubscriber) {
563+
this.ngZone.run(() => this.dragEndSubscriber.next({gutterNum: this.currentGutterNum, sizes}));
540564
}
541565
}
542566
else if(type === 'click') {
543-
if(this._gutterClickSubscriber) {
544-
this.ngZone.run(() => this._gutterClickSubscriber.next({gutterNum: this.currentGutterNum, sizes}));
567+
if(this.gutterClickSubscriber) {
568+
this.ngZone.run(() => this.gutterClickSubscriber.next({gutterNum: this.currentGutterNum, sizes}));
545569
}
546570
}
547571
else if(type === 'transitionEnd') {
548-
if(this._transitionEndSubscriber) {
549-
this.ngZone.run(() => this._transitionEndSubscriber.next(sizes));
572+
if(this.transitionEndSubscriber) {
573+
this.ngZone.run(() => this.transitionEndSubscriber.next(sizes));
550574
}
551575
}
576+
else if(type === 'progress') {
577+
// Stay outside zone to allow users do what they want about change detection mecanism.
578+
this.dragProgressSubject.next({gutterNum: this.currentGutterNum, sizes});
579+
}
552580
}
553581

554582
public ngOnDestroy(): void {

projects/angular-split/src/lib/directive/eventOutsideZone.directive.ts

Lines changed: 0 additions & 70 deletions
This file was deleted.

0 commit comments

Comments
 (0)