1
1
import {
2
- AfterContentChecked , AfterContentInit , ChangeDetectionStrategy , Component , ContentChildren , ElementRef , EventEmitter , Input , NgZone , OnChanges ,
3
- OnDestroy , Output , QueryList , Renderer2 , SimpleChanges , ViewEncapsulation
2
+ AfterContentChecked , AfterContentInit , ChangeDetectionStrategy , Component , ContentChildren , ElementRef , EmbeddedViewRef , EventEmitter , Input ,
3
+ NgZone , OnChanges , OnDestroy , Output , QueryList , Renderer2 , SimpleChanges , ViewContainerRef , ViewEncapsulation
4
4
} from '@angular/core' ;
5
5
import { coerceNumberProperty , NumberInput } from './coercion/number-property' ;
6
6
import { KtdGridItemComponent } from './grid-item/grid-item.component' ;
7
7
import { combineLatest , merge , NEVER , Observable , Observer , of , Subscription } from 'rxjs' ;
8
8
import { exhaustMap , map , startWith , switchMap , takeUntil } from 'rxjs/operators' ;
9
- import { ktdGridItemDragging , ktdGridItemResizing } from './utils/grid.utils' ;
10
- import { compact , CompactType } from './utils/react-grid-layout.utils' ;
9
+ import { ktdGridItemDragging , ktdGridItemLayoutItemAreEqual , ktdGridItemResizing } from './utils/grid.utils' ;
10
+ import { compact } from './utils/react-grid-layout.utils' ;
11
11
import {
12
- GRID_ITEM_GET_RENDER_DATA_TOKEN , KtdDraggingData , KtdGridCfg , KtdGridCompactType , KtdGridItemRect , KtdGridItemRenderData , KtdGridLayout ,
13
- KtdGridLayoutItem
12
+ GRID_ITEM_GET_RENDER_DATA_TOKEN , KtdGridCfg , KtdGridCompactType , KtdGridItemRenderData , KtdGridLayout , KtdGridLayoutItem
14
13
} from './grid.definitions' ;
15
14
import { ktdMouseOrTouchEnd , ktdPointerClientX , ktdPointerClientY } from './utils/pointer.utils' ;
16
15
import { KtdDictionary } from '../types' ;
17
16
import { KtdGridService } from './grid.service' ;
18
17
import { getMutableClientRect , KtdClientRect } from './utils/client-rect' ;
19
18
import { ktdGetScrollTotalRelativeDifference$ , ktdScrollIfNearElementClientRect$ } from './utils/scroll' ;
20
19
import { BooleanInput , coerceBooleanProperty } from './coercion/boolean-property' ;
20
+ import { KtdGridItemPlaceholder } from './directives/placeholder' ;
21
21
22
22
interface KtdDragResizeEvent {
23
23
layout : KtdGridLayout ;
@@ -30,6 +30,14 @@ export type KtdResizeStart = KtdDragResizeEvent;
30
30
export type KtdDragEnd = KtdDragResizeEvent ;
31
31
export type KtdResizeEnd = KtdDragResizeEvent ;
32
32
33
+ export interface KtdGridItemResizeEvent {
34
+ width : number ;
35
+ height : number ;
36
+ gridItemRef : KtdGridItemComponent ;
37
+ }
38
+
39
+ type DragActionType = 'drag' | 'resize' ;
40
+
33
41
function getDragResizeEventData ( gridItem : KtdGridItemComponent , layout : KtdGridLayout ) : KtdDragResizeEvent {
34
42
return {
35
43
layout,
@@ -118,6 +126,9 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
118
126
/** Emits when resize ends */
119
127
@Output ( ) resizeEnded : EventEmitter < KtdResizeEnd > = new EventEmitter < KtdResizeEnd > ( ) ;
120
128
129
+ /** Emits when a grid item is being resized and its bounds have changed */
130
+ @Output ( ) gridItemResize : EventEmitter < KtdGridItemResizeEvent > = new EventEmitter < KtdGridItemResizeEvent > ( ) ;
131
+
121
132
/**
122
133
* Parent element that contains the scroll. If an string is provided it would search that element by id on the dom.
123
134
* If no data provided or null autoscroll is not performed.
@@ -227,13 +238,20 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
227
238
} ;
228
239
}
229
240
241
+ /** Reference to the view of the placeholder element. */
242
+ private placeholderRef : EmbeddedViewRef < any > | null ;
243
+
244
+ /** Element that is rendered as placeholder when a grid item is being dragged */
245
+ private placeholder : HTMLElement | null ;
246
+
230
247
/** Total height of the grid */
231
248
private _height : number ;
232
249
private _gridItemsRenderData : KtdDictionary < KtdGridItemRenderData < number > > ;
233
250
private subscriptions : Subscription [ ] ;
234
251
235
252
constructor ( private gridService : KtdGridService ,
236
253
private elementRef : ElementRef ,
254
+ private viewContainerRef : ViewContainerRef ,
237
255
private renderer : Renderer2 ,
238
256
private ngZone : NgZone ) {
239
257
@@ -323,18 +341,15 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
323
341
startWith ( this . _gridItems ) ,
324
342
switchMap ( ( gridItems : QueryList < KtdGridItemComponent > ) => {
325
343
return merge (
326
- ...gridItems . map ( ( gridItem ) => gridItem . dragStart$ . pipe ( map ( ( event ) => ( { event, gridItem, type : 'drag' } ) ) ) ) ,
327
- ...gridItems . map ( ( gridItem ) => gridItem . resizeStart$ . pipe ( map ( ( event ) => ( { event, gridItem, type : 'resize' } ) ) ) ) ,
344
+ ...gridItems . map ( ( gridItem ) => gridItem . dragStart$ . pipe ( map ( ( event ) => ( { event, gridItem, type : 'drag' as DragActionType } ) ) ) ) ,
345
+ ...gridItems . map ( ( gridItem ) => gridItem . resizeStart$ . pipe ( map ( ( event ) => ( { event, gridItem, type : 'resize' as DragActionType } ) ) ) ) ,
328
346
) . pipe ( exhaustMap ( ( { event, gridItem, type} ) => {
329
347
// Emit drag or resize start events. Ensure that is start event is inside the zone.
330
348
this . ngZone . run ( ( ) => ( type === 'drag' ? this . dragStarted : this . resizeStarted ) . emit ( getDragResizeEventData ( gridItem , this . layout ) ) ) ;
331
- // Get the correct newStateFunc depending on if we are dragging or resizing
332
- const calcNewStateFunc = type === 'drag' ? ktdGridItemDragging : ktdGridItemResizing ;
333
349
334
350
// Perform drag sequence
335
- return this . performDragSequence$ ( gridItem , event , ( gridItemId , config , compactionType , draggingData ) =>
336
- calcNewStateFunc ( gridItem , config , compactionType , draggingData )
337
- ) . pipe ( map ( ( layout ) => ( { layout, gridItem, type} ) ) ) ;
351
+ return this . performDragSequence$ ( gridItem , event , type ) . pipe (
352
+ map ( ( layout ) => ( { layout, gridItem, type} ) ) ) ;
338
353
339
354
} ) ) ;
340
355
} )
@@ -358,8 +373,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
358
373
* @param pointerDownEvent event (mousedown or touchdown) where the user initiated the drag
359
374
* @param calcNewStateFunc function that return the new layout state and the drag element position
360
375
*/
361
- private performDragSequence$ ( gridItem : KtdGridItemComponent , pointerDownEvent : MouseEvent | TouchEvent ,
362
- calcNewStateFunc : ( gridItem : KtdGridItemComponent , config : KtdGridCfg , compactionType : CompactType , draggingData : KtdDraggingData ) => { layout : KtdGridLayoutItem [ ] ; draggedItemPos : KtdGridItemRect } ) : Observable < KtdGridLayout > {
376
+ private performDragSequence$ ( gridItem : KtdGridItemComponent , pointerDownEvent : MouseEvent | TouchEvent , type : DragActionType ) : Observable < KtdGridLayout > {
363
377
364
378
return new Observable < KtdGridLayout > ( ( observer : Observer < KtdGridLayout > ) => {
365
379
// Retrieve grid (parent) and gridItem (draggedElem) client rects.
@@ -371,14 +385,12 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
371
385
this . renderer . addClass ( gridItem . elementRef . nativeElement , 'no-transitions' ) ;
372
386
this . renderer . addClass ( gridItem . elementRef . nativeElement , 'ktd-grid-item-dragging' ) ;
373
387
374
- // Create placeholder element. This element would represent the position where the dragged/resized element would be if the action ends
375
- const placeholderElement : HTMLDivElement = this . renderer . createElement ( 'div' ) ;
376
- placeholderElement . style . width = `${ dragElemClientRect . width } px` ;
377
- placeholderElement . style . height = `${ dragElemClientRect . height } px` ;
378
- placeholderElement . style . transform = `translateX(${ dragElemClientRect . left - gridElemClientRect . left } px) translateY(${ dragElemClientRect . top - gridElemClientRect . top } px)` ;
379
-
380
- this . renderer . addClass ( placeholderElement , 'ktd-grid-item-placeholder' ) ;
381
- this . renderer . appendChild ( this . elementRef . nativeElement , placeholderElement ) ;
388
+ const placeholderClientRect : KtdClientRect = {
389
+ ...dragElemClientRect ,
390
+ left : dragElemClientRect . left - gridElemClientRect . left ,
391
+ top : dragElemClientRect . top - gridElemClientRect . top
392
+ }
393
+ this . createPlaceholderElement ( placeholderClientRect , gridItem . placeholder ) ;
382
394
383
395
let newLayout : KtdGridLayoutItem [ ] ;
384
396
@@ -421,6 +433,9 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
421
433
*/
422
434
const currentLayout : KtdGridLayout = newLayout || this . layout ;
423
435
436
+ // Get the correct newStateFunc depending on if we are dragging or resizing
437
+ const calcNewStateFunc = type === 'drag' ? ktdGridItemDragging : ktdGridItemResizing ;
438
+
424
439
const { layout, draggedItemPos} = calcNewStateFunc ( gridItem , {
425
440
layout : currentLayout ,
426
441
rowHeight : this . rowHeight ,
@@ -446,12 +461,13 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
446
461
gap : this . gap ,
447
462
} , gridElemClientRect . width , gridElemClientRect . height ) ;
448
463
449
- const placeholderStyles = parseRenderItemToPixels ( this . _gridItemsRenderData [ gridItem . id ] ) ;
464
+ const newGridItemRenderData = { ...this . _gridItemsRenderData [ gridItem . id ] }
465
+ const placeholderStyles = parseRenderItemToPixels ( newGridItemRenderData ) ;
450
466
451
467
// Put the real final position to the placeholder element
452
- placeholderElement . style . width = placeholderStyles . width ;
453
- placeholderElement . style . height = placeholderStyles . height ;
454
- placeholderElement . style . transform = `translateX(${ placeholderStyles . left } ) translateY(${ placeholderStyles . top } )` ;
468
+ this . placeholder ! . style . width = placeholderStyles . width ;
469
+ this . placeholder ! . style . height = placeholderStyles . height ;
470
+ this . placeholder ! . style . transform = `translateX(${ placeholderStyles . left } ) translateY(${ placeholderStyles . top } )` ;
455
471
456
472
// modify the position of the dragged item to be the once we want (for example the mouse position or whatever)
457
473
this . _gridItemsRenderData [ gridItem . id ] = {
@@ -460,6 +476,21 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
460
476
} ;
461
477
462
478
this . render ( ) ;
479
+
480
+ // If we are performing a resize, and bounds have changed, emit event.
481
+ // NOTE: Only emit on resize for now. Use case for normal drag is not justified for now. Emitting on resize is, since we may want to re-render the grid item or the placeholder in order to fit the new bounds.
482
+ if ( type === 'resize' ) {
483
+ const prevGridItem = currentLayout . find ( item => item . id === gridItem . id ) ! ;
484
+ const newGridItem = newLayout . find ( item => item . id === gridItem . id ) ! ;
485
+ // Check if item resized has changed, if so, emit resize change event
486
+ if ( ! ktdGridItemLayoutItemAreEqual ( prevGridItem , newGridItem ) ) {
487
+ this . gridItemResize . emit ( {
488
+ width : newGridItemRenderData . width ,
489
+ height : newGridItemRenderData . height ,
490
+ gridItemRef : getDragResizeEventData ( gridItem , newLayout ) . gridItemRef
491
+ } ) ;
492
+ }
493
+ }
463
494
} ,
464
495
( error ) => observer . error ( error ) ,
465
496
( ) => {
@@ -468,10 +499,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
468
499
this . renderer . removeClass ( gridItem . elementRef . nativeElement , 'no-transitions' ) ;
469
500
this . renderer . removeClass ( gridItem . elementRef . nativeElement , 'ktd-grid-item-dragging' ) ;
470
501
471
- // Remove placeholder element from the dom
472
- // NOTE: If we don't put the removeChild inside the zone it would not work... This may be a bug from angular or maybe is the intended behaviour, although strange.
473
- // It should work since AFAIK this action should not be done in a CD cycle.
474
- this . renderer . removeChild ( this . elementRef . nativeElement , placeholderElement ) ;
502
+ this . destroyPlaceholder ( ) ;
475
503
476
504
if ( newLayout ) {
477
505
// TODO: newLayout should already be pruned. If not, it should have type Layout, not KtdGridLayout as it is now.
@@ -505,6 +533,35 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
505
533
} ) ;
506
534
}
507
535
536
+ /** Creates placeholder element */
537
+ private createPlaceholderElement ( clientRect : KtdClientRect , gridItemPlaceholder ?: KtdGridItemPlaceholder ) {
538
+ this . placeholder = this . renderer . createElement ( 'div' ) ;
539
+ this . placeholder ! . style . width = `${ clientRect . width } px` ;
540
+ this . placeholder ! . style . height = `${ clientRect . height } px` ;
541
+ this . placeholder ! . style . transform = `translateX(${ clientRect . left } px) translateY(${ clientRect . top } px)` ;
542
+ this . placeholder ! . classList . add ( 'ktd-grid-item-placeholder' ) ;
543
+ this . renderer . appendChild ( this . elementRef . nativeElement , this . placeholder ) ;
544
+
545
+ // Create and append custom placeholder if provided.
546
+ // Important: Append it after creating & appending the container placeholder. This way we ensure parent bounds are set when creating the embeddedView.
547
+ if ( gridItemPlaceholder ) {
548
+ this . placeholderRef = this . viewContainerRef . createEmbeddedView (
549
+ gridItemPlaceholder . templateRef ,
550
+ gridItemPlaceholder . data
551
+ ) ;
552
+ this . placeholderRef . rootNodes . forEach ( node => this . placeholder ! . appendChild ( node ) ) ;
553
+ this . placeholderRef . detectChanges ( ) ;
554
+ } else {
555
+ this . placeholder ! . classList . add ( 'ktd-grid-item-placeholder-default' ) ;
556
+ }
557
+ }
558
+
559
+ /** Destroys the placeholder element and its ViewRef. */
560
+ private destroyPlaceholder ( ) {
561
+ this . placeholder ?. remove ( ) ;
562
+ this . placeholderRef ?. destroy ( ) ;
563
+ this . placeholder = this . placeholderRef = null ! ;
564
+ }
508
565
509
566
static ngAcceptInputType_cols : NumberInput ;
510
567
static ngAcceptInputType_rowHeight : NumberInput ;
0 commit comments