11import {
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
44} from '@angular/core' ;
55import { coerceNumberProperty , NumberInput } from './coercion/number-property' ;
66import { KtdGridItemComponent } from './grid-item/grid-item.component' ;
77import { combineLatest , merge , NEVER , Observable , Observer , of , Subscription } from 'rxjs' ;
88import { 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' ;
1111import {
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
1413} from './grid.definitions' ;
1514import { ktdMouseOrTouchEnd , ktdPointerClientX , ktdPointerClientY } from './utils/pointer.utils' ;
1615import { KtdDictionary } from '../types' ;
1716import { KtdGridService } from './grid.service' ;
1817import { getMutableClientRect , KtdClientRect } from './utils/client-rect' ;
1918import { ktdGetScrollTotalRelativeDifference$ , ktdScrollIfNearElementClientRect$ } from './utils/scroll' ;
2019import { BooleanInput , coerceBooleanProperty } from './coercion/boolean-property' ;
20+ import { KtdGridItemPlaceholder } from './directives/placeholder' ;
2121
2222interface KtdDragResizeEvent {
2323 layout : KtdGridLayout ;
@@ -30,6 +30,14 @@ export type KtdResizeStart = KtdDragResizeEvent;
3030export type KtdDragEnd = KtdDragResizeEvent ;
3131export type KtdResizeEnd = KtdDragResizeEvent ;
3232
33+ export interface KtdGridItemResizeEvent {
34+ width : number ;
35+ height : number ;
36+ gridItemRef : KtdGridItemComponent ;
37+ }
38+
39+ type DragActionType = 'drag' | 'resize' ;
40+
3341function getDragResizeEventData ( gridItem : KtdGridItemComponent , layout : KtdGridLayout ) : KtdDragResizeEvent {
3442 return {
3543 layout,
@@ -118,6 +126,9 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
118126 /** Emits when resize ends */
119127 @Output ( ) resizeEnded : EventEmitter < KtdResizeEnd > = new EventEmitter < KtdResizeEnd > ( ) ;
120128
129+ /** Emits when a grid item is being resized and its bounds have changed */
130+ @Output ( ) gridItemResize : EventEmitter < KtdGridItemResizeEvent > = new EventEmitter < KtdGridItemResizeEvent > ( ) ;
131+
121132 /**
122133 * Parent element that contains the scroll. If an string is provided it would search that element by id on the dom.
123134 * If no data provided or null autoscroll is not performed.
@@ -227,13 +238,20 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
227238 } ;
228239 }
229240
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+
230247 /** Total height of the grid */
231248 private _height : number ;
232249 private _gridItemsRenderData : KtdDictionary < KtdGridItemRenderData < number > > ;
233250 private subscriptions : Subscription [ ] ;
234251
235252 constructor ( private gridService : KtdGridService ,
236253 private elementRef : ElementRef ,
254+ private viewContainerRef : ViewContainerRef ,
237255 private renderer : Renderer2 ,
238256 private ngZone : NgZone ) {
239257
@@ -323,18 +341,15 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
323341 startWith ( this . _gridItems ) ,
324342 switchMap ( ( gridItems : QueryList < KtdGridItemComponent > ) => {
325343 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 } ) ) ) ) ,
328346 ) . pipe ( exhaustMap ( ( { event, gridItem, type} ) => {
329347 // Emit drag or resize start events. Ensure that is start event is inside the zone.
330348 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 ;
333349
334350 // 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} ) ) ) ;
338353
339354 } ) ) ;
340355 } )
@@ -358,8 +373,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
358373 * @param pointerDownEvent event (mousedown or touchdown) where the user initiated the drag
359374 * @param calcNewStateFunc function that return the new layout state and the drag element position
360375 */
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 > {
363377
364378 return new Observable < KtdGridLayout > ( ( observer : Observer < KtdGridLayout > ) => {
365379 // Retrieve grid (parent) and gridItem (draggedElem) client rects.
@@ -371,14 +385,12 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
371385 this . renderer . addClass ( gridItem . elementRef . nativeElement , 'no-transitions' ) ;
372386 this . renderer . addClass ( gridItem . elementRef . nativeElement , 'ktd-grid-item-dragging' ) ;
373387
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 ) ;
382394
383395 let newLayout : KtdGridLayoutItem [ ] ;
384396
@@ -421,6 +433,9 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
421433 */
422434 const currentLayout : KtdGridLayout = newLayout || this . layout ;
423435
436+ // Get the correct newStateFunc depending on if we are dragging or resizing
437+ const calcNewStateFunc = type === 'drag' ? ktdGridItemDragging : ktdGridItemResizing ;
438+
424439 const { layout, draggedItemPos} = calcNewStateFunc ( gridItem , {
425440 layout : currentLayout ,
426441 rowHeight : this . rowHeight ,
@@ -446,12 +461,13 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
446461 gap : this . gap ,
447462 } , gridElemClientRect . width , gridElemClientRect . height ) ;
448463
449- const placeholderStyles = parseRenderItemToPixels ( this . _gridItemsRenderData [ gridItem . id ] ) ;
464+ const newGridItemRenderData = { ...this . _gridItemsRenderData [ gridItem . id ] }
465+ const placeholderStyles = parseRenderItemToPixels ( newGridItemRenderData ) ;
450466
451467 // 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 } )` ;
455471
456472 // modify the position of the dragged item to be the once we want (for example the mouse position or whatever)
457473 this . _gridItemsRenderData [ gridItem . id ] = {
@@ -460,6 +476,21 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
460476 } ;
461477
462478 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+ }
463494 } ,
464495 ( error ) => observer . error ( error ) ,
465496 ( ) => {
@@ -468,10 +499,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
468499 this . renderer . removeClass ( gridItem . elementRef . nativeElement , 'no-transitions' ) ;
469500 this . renderer . removeClass ( gridItem . elementRef . nativeElement , 'ktd-grid-item-dragging' ) ;
470501
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 ( ) ;
475503
476504 if ( newLayout ) {
477505 // 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
505533 } ) ;
506534 }
507535
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+ }
508565
509566 static ngAcceptInputType_cols : NumberInput ;
510567 static ngAcceptInputType_rowHeight : NumberInput ;
0 commit comments