import { ChangeDetectionStrategy, Component, ElementRef, HostBinding, HostListener, Input, Renderer2, ViewChild } from '@angular/core'; import { IgxListPanState, IListChild, IgxListBaseDirective } from './list.common'; import { HammerGesturesManager } from '../core/touch'; import { rem } from '../core/utils'; import { NgIf, NgTemplateOutlet } from '@angular/common'; /** * The Ignite UI List Item component is a container intended for row items in the Ignite UI for Angular List component. * * Example: * ```html * * Contacts * * {{ contact.name }} * {{ contact.phone }} * * * ``` */ @Component({ providers: [HammerGesturesManager], selector: 'igx-list-item', templateUrl: 'list-item.component.html', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [NgIf, NgTemplateOutlet] }) export class IgxListItemComponent implements IListChild { /** * Provides a reference to the template's base element shown when left panning a list item. * ```typescript * const leftPanTmpl = this.listItem.leftPanningTemplateElement; * ``` */ @ViewChild('leftPanningTmpl') public leftPanningTemplateElement; /** * Provides a reference to the template's base element shown when right panning a list item. * ```typescript * const rightPanTmpl = this.listItem.rightPanningTemplateElement; * ``` */ @ViewChild('rightPanningTmpl') public rightPanningTemplateElement; /** * Sets/gets whether the `list item` is a header. * ```html * Header * ``` * ```typescript * let isHeader = this.listItem.isHeader; * ``` * * @memberof IgxListItemComponent */ @Input() public isHeader: boolean; /** * Sets/gets whether the `list item` is hidden. * By default the `hidden` value is `false`. * ```html * Hidden Item * ``` * ```typescript * let isHidden = this.listItem.hidden; * ``` * * @memberof IgxListItemComponent */ @Input() public hidden = false; /** * Sets/gets the `aria-label` attribute of the `list item`. * ```typescript * this.listItem.ariaLabel = "Item1"; * ``` * ```typescript * let itemAriaLabel = this.listItem.ariaLabel; * ``` * * @memberof IgxListItemComponent */ @HostBinding('attr.aria-label') public ariaLabel: string; /** * Gets the `touch-action` style of the `list item`. * ```typescript * let touchAction = this.listItem.touchAction; * ``` */ @HostBinding('style.touch-action') public touchAction = 'pan-y'; /** * @hidden */ private _panState: IgxListPanState = IgxListPanState.NONE; /** * @hidden */ private panOffset = 0; /** * @hidden */ private _index: number = null; /** * @hidden */ private lastPanDir = IgxListPanState.NONE; /** * Gets the `panState` of a `list item`. * ```typescript * let itemPanState = this.listItem.panState; * ``` * * @memberof IgxListItemComponent */ public get panState(): IgxListPanState { return this._panState; } /** * Gets the `index` of a `list item`. * ```typescript * let itemIndex = this.listItem.index; * ``` * * @memberof IgxListItemComponent */ @Input() public get index(): number { return this._index !== null ? this._index : this.list.children.toArray().indexOf(this); } /** * Sets the `index` of the `list item`. * ```typescript * this.listItem.index = index; * ``` * * @memberof IgxListItemComponent */ public set index(value: number) { this._index = value; } /** * Returns an element reference to the list item. * ```typescript * let listItemElement = this.listItem.element. * ``` * * @memberof IgxListItemComponent */ public get element() { return this.elementRef.nativeElement; } /** * Returns a reference container which contains the list item's content. * ```typescript * let listItemContainer = this.listItem.contentElement. * ``` * * @memberof IgxListItemComponent */ public get contentElement() { const candidates = this.element.getElementsByClassName('igx-list__item-content'); return (candidates && candidates.length > 0) ? candidates[0] : null; } /** * Returns the `context` object which represents the `template context` binding into the `list item container` * by providing the `$implicit` declaration which is the `IgxListItemComponent` itself. * ```typescript * let listItemComponent = this.listItem.context; * ``` */ public get context(): any { return { $implicit: this }; } /** * Gets the width of a `list item`. * ```typescript * let itemWidth = this.listItem.width; * ``` * * @memberof IgxListItemComponent */ public get width() { if (this.element) { return this.element.offsetWidth; } } /** * Gets the maximum left position of the `list item`. * ```typescript * let maxLeft = this.listItem.maxLeft; * ``` * * @memberof IgxListItemComponent */ public get maxLeft() { return -this.width; } /** * Gets the maximum right position of the `list item`. * ```typescript * let maxRight = this.listItem.maxRight; * ``` * * @memberof IgxListItemComponent */ public get maxRight() { return this.width; } /** @hidden @internal */ public get offsetWidthInRem() { return rem(this.element.offsetWidth); } /** @hidden @internal */ public get offsetHeightInRem() { return rem(this.element.offsetHeight); } constructor( public list: IgxListBaseDirective, private elementRef: ElementRef, private _renderer: Renderer2) { } /** * Gets the `role` attribute of the `list item`. * ```typescript * let itemRole = this.listItem.role; * ``` * * @memberof IgxListItemComponent */ @HostBinding('attr.role') public get role() { return this.isHeader ? 'separator' : 'listitem'; } /** * Indicates whether `list item` should have header style. * ```typescript * let headerStyle = this.listItem.headerStyle; * ``` * * @memberof IgxListItemComponent */ @HostBinding('class.igx-list__header') public get headerStyle(): boolean { return this.isHeader; } /** * Applies the inner style of the `list item` if the item is not counted as header. * ```typescript * let innerStyle = this.listItem.innerStyle; * ``` * * @memberof IgxListItemComponent */ @HostBinding('class.igx-list__item-base') public get innerStyle(): boolean { return !this.isHeader; } /** * Returns string value which describes the display mode of the `list item`. * ```typescript * let isHidden = this.listItem.display; * ``` * * @memberof IgxListItemComponent */ @HostBinding('style.display') public get display(): string { return this.hidden ? 'none' : ''; } /** * @hidden */ @HostListener('click', ['$event']) public clicked(evt) { this.list.itemClicked.emit({ item: this, event: evt, direction: this.lastPanDir }); this.lastPanDir = IgxListPanState.NONE; } /** * @hidden */ @HostListener('panstart') public panStart() { if (this.isTrue(this.isHeader)) { return; } if (!this.isTrue(this.list.allowLeftPanning) && !this.isTrue(this.list.allowRightPanning)) { return; } this.list.startPan.emit({ item: this, direction: this.lastPanDir, keepitem: false }); } /** * @hidden */ @HostListener('pancancel') public panCancel() { this.resetPanPosition(); this.list.endPan.emit({item: this, direction: this.lastPanDir, keepItem: false}); } /** * @hidden */ @HostListener('panmove', ['$event']) public panMove(ev) { if (this.isTrue(this.isHeader)) { return; } if (!this.isTrue(this.list.allowLeftPanning) && !this.isTrue(this.list.allowRightPanning)) { return; } const isPanningToLeft = ev.deltaX < 0; if (isPanningToLeft && this.isTrue(this.list.allowLeftPanning)) { this.showLeftPanTemplate(); this.setContentElementLeft(Math.max(this.maxLeft, ev.deltaX)); } else if (!isPanningToLeft && this.isTrue(this.list.allowRightPanning)) { this.showRightPanTemplate(); this.setContentElementLeft(Math.min(this.maxRight, ev.deltaX)); } } /** * @hidden */ @HostListener('panend') public panEnd() { if (this.isTrue(this.isHeader)) { return; } if (!this.isTrue(this.list.allowLeftPanning) && !this.isTrue(this.list.allowRightPanning)) { return; } // the translation offset of the current list item content const relativeOffset = this.panOffset; const widthTriggeringGrip = this.width * this.list.panEndTriggeringThreshold; if (relativeOffset === 0) { return; // no panning has occured } const dir = relativeOffset > 0 ? IgxListPanState.RIGHT : IgxListPanState.LEFT; this.lastPanDir = dir; const args = { item: this, direction: dir, keepItem: false}; this.list.endPan.emit(args); const oldPanState = this._panState; if (Math.abs(relativeOffset) < widthTriggeringGrip) { this.resetPanPosition(); this.list.resetPan.emit(this); return; } if (dir === IgxListPanState.LEFT) { this.list.leftPan.emit(args); } else { this.list.rightPan.emit(args); } if (args.keepItem === true) { this.setContentElementLeft(0); this._panState = IgxListPanState.NONE; } else { if (dir === IgxListPanState.LEFT) { this.setContentElementLeft(this.maxLeft); this._panState = IgxListPanState.LEFT; } else { this.setContentElementLeft(this.maxRight); this._panState = IgxListPanState.RIGHT; } } if (oldPanState !== this._panState) { const args2 = { oldState: oldPanState, newState: this._panState, item: this }; this.list.panStateChange.emit(args2); } this.hideLeftAndRightPanTemplates(); } /** * @hidden */ private showLeftPanTemplate() { this.setLeftAndRightTemplatesVisibility('visible', 'hidden'); } /** * @hidden */ private showRightPanTemplate() { this.setLeftAndRightTemplatesVisibility('hidden', 'visible'); } /** * @hidden */ private hideLeftAndRightPanTemplates() { setTimeout(() => { this.setLeftAndRightTemplatesVisibility('hidden', 'hidden'); }, 500); } /** * @hidden */ private setLeftAndRightTemplatesVisibility(leftVisibility, rightVisibility) { if (this.leftPanningTemplateElement && this.leftPanningTemplateElement.nativeElement) { this.leftPanningTemplateElement.nativeElement.style.visibility = leftVisibility; } if (this.rightPanningTemplateElement && this.rightPanningTemplateElement.nativeElement) { this.rightPanningTemplateElement.nativeElement.style.visibility = rightVisibility; } } /** * @hidden */ private setContentElementLeft(value: number) { this.panOffset = value; this.contentElement.style.transform = 'translateX(' + value + 'px)'; } /** * @hidden */ private isTrue(value: boolean): boolean { if (typeof (value) === 'boolean') { return value; } else { return value === 'true'; } } /** * @hidden */ private resetPanPosition() { this.setContentElementLeft(0); this._panState = IgxListPanState.NONE; this.hideLeftAndRightPanTemplates(); } }