import { Component, EventEmitter, HostListener, inject, Input, OnInit, Output, ViewChild } from '@angular/core';
import { CrudOperations, OperationKeys } from '@decaf-ts/db-decorators';
import { StringOrBoolean } from '../../engine/types';
import { NgxBaseComponent } from '../../engine/NgxBaseComponent';
import { ForAngularModule } from '../../for-angular.module';
import { removeFocusTrap, stringToBoolean } from '../../helpers/utils';
import { getWindowWidth, windowEventEmitter } from '../../helpers/utils';
import { Dynamic, EventConstants, ListItemCustomEvent } from '../../engine';
import { NavController } from '@ionic/angular';
import {
IonButton,
IonItem,
IonLabel,
IonList,
IonContent,
IonIcon,
IonListHeader,
IonPopover,
IonItemSliding,
IonItemOptions,
IonItemOption
} from '@ionic/angular/standalone';
import * as AllIcons from 'ionicons/icons';
import { addIcons } from 'ionicons';
/**
* @description A component for displaying a list item with various customization options.
* @summary The ListItemComponent is an Angular component that extends NgxBaseComponent. It provides a flexible and customizable list item interface with support for icons, buttons, and various text elements. The component also handles actions and navigation based on user interactions.
*
* @class
* @extends NgxBaseComponent
*
* @param {string} [lines='none'] - Determines the line style of the item. Can be 'inset', 'inseet', or 'none'.
* @param {Record<string, any>} item - The data item to be displayed in the list item.
* @param {string} icon - The name of the icon to be displayed.
* @param {'start' | 'end'} [iconSlot='start'] - The position of the icon within the item.
* @param {StringOrBoolean} [button=true] - Determines if the item should behave as a button.
* @param {string} [title] - The main title of the list item.
* @param {string} [description] - A description for the list item.
* @param {string} [info] - Additional information for the list item.
* @param {string} [subinfo] - Sub-information for the list item.
*
* @example
* <ngx-decaf-list-item
* [item]="dataItem"
* icon="star"
* title="Item Title"
* description="Item Description"
* (clickEvent)="handleItemClick($event)">
* </ngx-decaf-list-item>
*
* @mermaid
* sequenceDiagram
* participant C as Component
* participant V as View
* participant U as User
* C->>V: Initialize component
* V->>U: Display list item
* U->>V: Click on item or action
* V->>C: Trigger handleAction()
* C->>C: Process action
* C->>V: Update view or navigate
*/
@Dynamic()
@Component({
selector: 'ngx-decaf-list-item',
templateUrl: './list-item.component.html',
styleUrls: ['./list-item.component.scss'],
standalone: true,
imports: [
ForAngularModule,
IonList,
IonListHeader,
IonItem,
IonItemSliding,
IonItemOptions,
IonItemOption,
IonIcon,
IonLabel,
IonButton,
IonContent,
IonPopover
]
})
export class ListItemComponent extends NgxBaseComponent implements OnInit {
/**
* @description Reference to the action menu popover component.
* @summary ViewChild reference that provides access to the HTMLIonPopoverElement
* used for displaying action menus. This reference is used to programmatically
* control the popover, such as dismissing it when necessary.
*
* @type {HTMLIonPopoverElement}
* @memberOf ListItemComponent
*/
@ViewChild('actionMenuComponent')
actionMenuComponent!: HTMLIonPopoverElement;
/**
* @description Controls the display of lines around the list item.
* @summary Determines how lines are displayed around the list item borders.
* 'inset' shows lines with padding, 'full' shows full-width lines, and 'none'
* removes all lines. This affects the visual separation between list items.
*
* @type {'inset' | 'full' | 'none'}
* @default 'inset'
* @memberOf ListItemComponent
*/
@Input()
lines: 'inset' | 'full' | 'none' = 'inset';
/**
* @description The data object associated with this list item.
* @summary Contains the raw data that this list item represents. This object
* is used to extract display information and for passing to event handlers
* when the item is interacted with. It overrides the base item property.
*
* @type {Record<string, unknown>}
* @memberOf ListItemComponent
*/
@Input()
override item!: Record<string, unknown>;
/**
* @description The name of the icon to display in the list item.
* @summary Specifies which icon to display using Ionic's icon system.
* The icon name should correspond to an available Ionic icon or a custom
* icon that has been registered with the icon registry.
*
* @type {string}
* @memberOf ListItemComponent
*/
@Input()
icon!: string;
/**
* @description Position of the icon within the list item.
* @summary Determines whether the icon appears at the start (left in LTR languages)
* or end (right in LTR languages) of the list item. This affects the overall
* layout and visual hierarchy of the item content.
*
* @type {'start' | 'end'}
* @default 'start'
* @memberOf ListItemComponent
*/
@Input()
iconSlot: 'start' | 'end' ='start';
/**
* @description Controls whether the list item behaves as a clickable button.
* @summary When set to true, the list item will have button-like behavior including
* hover effects, click handling, and appropriate accessibility attributes.
* When false, the item is displayed as static content without interactive behavior.
*
* @type {StringOrBoolean}
* @default true
* @memberOf ListItemComponent
*/
@Input()
button: StringOrBoolean = true;
/**
* @description The main title text displayed in the list item.
* @summary Sets the primary text content that appears prominently in the list item.
* This is typically the most important information about the item and is displayed
* with emphasis in the component's visual hierarchy.
*
* @type {string}
* @memberOf ListItemComponent
*/
@Input()
title?: string;
/**
* @description Secondary descriptive text for the list item.
* @summary Provides additional context or details about the item. This text
* is typically displayed below the title with less visual emphasis.
* Useful for providing context without cluttering the main title.
*
* @type {string}
* @memberOf ListItemComponent
*/
@Input()
description?: string;
/**
* @description Additional information text for the list item.
* @summary Displays supplementary information that provides extra context
* about the item. This could include metadata, status information, or
* other relevant details that don't fit in the title or description.
*
* @type {string}
* @memberOf ListItemComponent
*/
@Input()
info?: string;
/**
* @description Sub-information text displayed in the list item.
* @summary Provides tertiary level information that complements the info field.
* This is typically used for additional metadata or contextual details
* that are useful but not critical for understanding the item.
*
* @type {string}
* @memberOf ListItemComponent
*/
@Input()
subinfo?: string;
/**
* @description Event emitter for list item click interactions.
* @summary Emits custom events when the list item is clicked or when actions
* are performed on it. The emitted event contains information about the action,
* the item data, and other relevant context for parent components to handle.
*
* @type {EventEmitter<ListItemCustomEvent>}
* @memberOf ListItemComponent
*/
@Output()
clickEvent: EventEmitter<ListItemCustomEvent> = new EventEmitter<ListItemCustomEvent>();
/**
* @description Flag indicating whether slide items are currently enabled.
* @summary Controls the visibility of slide actions based on screen size and
* available operations. When true, users can swipe on the item to reveal
* action buttons for operations like edit and delete.
*
* @type {boolean}
* @default false
* @memberOf ListItemComponent
*/
showSlideItems: boolean = false;
/**
* @description Current window width in pixels.
* @summary Stores the current browser window width which is used to determine
* responsive behavior, such as when to show or hide slide items based on
* screen size. Updated automatically on window resize events.
*
* @type {number}
* @memberOf ListItemComponent
*/
windowWidth!: number;
/**
* @description Flag indicating whether the action menu popover is currently open.
* @summary Tracks the state of the action menu to prevent multiple instances
* from being opened simultaneously and to ensure proper cleanup when actions
* are performed. Used for managing the popover lifecycle.
*
* @type {boolean}
* @default false
* @memberOf ListItemComponent
*/
actionMenuOpen: boolean = false;
/**
* @description Angular NavController service for handling navigation.
* @summary Injected service that provides methods for programmatic navigation
* within the Ionic application. Used for navigating to different routes when
* list item actions are performed or when the item itself is clicked.
*
* @private
* @type {NavController}
* @memberOf ListItemComponent
*/
private navController: NavController = inject(NavController);
/**
* @description Creates an instance of ListItemComponent.
* @summary Initializes a new ListItemComponent by calling the parent class constructor
* with the component name for logging and identification purposes. Also registers
* all available Ionic icons to ensure they can be displayed in the component.
*
* @memberOf ListItemComponent
*/
constructor() {
super("ListItemComponent");
addIcons(AllIcons)
}
/**
* @description Initializes the component after Angular first displays the data-bound properties.
* @summary Sets up the component by determining slide item visibility, processing boolean inputs,
* building CSS class names based on properties, and capturing the current window width.
* This method prepares the component for user interaction by ensuring all properties are
* properly initialized and responsive behavior is configured.
*
* @mermaid
* sequenceDiagram
* participant A as Angular Lifecycle
* participant L as ListItemComponent
* participant W as Window
*
* A->>L: ngOnInit()
* L->>L: enableSlideItems()
* L->>L: Process button boolean
* L->>L: Build className with flex classes
* alt operations exist
* L->>L: Add 'action' class
* end
* L->>W: getWindowWidth()
* W-->>L: Return current width
* L->>L: Store windowWidth
*
* @return {Promise<void>}
* @memberOf ListItemComponent
*/
async ngOnInit(): Promise<void> {
this.showSlideItems = this.enableSlideItems();
this.button = stringToBoolean(this.button);
this.className = `${this.className} dcf-flex dcf-flex-middle grid-item`;
if(this.operations?.length)
this.className += ` action`;
this.windowWidth = getWindowWidth() as number;
}
/**
* @description Handles user interactions and actions performed on the list item.
* @summary This method is the central action handler for list item interactions. It manages
* event propagation, dismisses open action menus, removes focus traps, and either emits
* events for parent components to handle or performs navigation based on the component's
* route configuration. This method supports both event-driven and navigation-driven architectures.
*
* @param {CrudOperations} action - The type of CRUD operation being performed
* @param {Event} event - The browser event that triggered the action
* @param {HTMLElement} [target] - Optional target element for the event
* @return {Promise<boolean|void>} A promise that resolves to navigation success or void for events
*
* @mermaid
* sequenceDiagram
* participant U as User
* participant L as ListItemComponent
* participant P as Parent Component
* participant N as NavController
* participant E as Event System
*
* U->>L: Perform action (click/swipe)
* L->>L: stopImmediatePropagation()
* alt actionMenuOpen
* L->>L: Dismiss action menu
* end
* L->>L: removeFocusTrap()
* alt No route configured
* L->>E: windowEventEmitter()
* L->>P: clickEvent.emit()
* else Route configured
* L->>N: redirect(action, uid)
* N-->>L: Return navigation result
* end
*
* @memberOf ListItemComponent
*/
async handleAction(action: CrudOperations, event: Event, target?: HTMLElement): Promise<boolean|void> {
event.stopImmediatePropagation();
if(this.actionMenuOpen)
await this.actionMenuComponent.dismiss();
// forcing trap focus
removeFocusTrap();
if(!this.route) {
const event = {target: target, action, pk: this.pk, data: this.uid, name: EventConstants.CLICK, component: this.componentName } as ListItemCustomEvent;
windowEventEmitter(`ListItem${EventConstants.CLICK}`, event);
return this.clickEvent.emit(event);
}
return await this.redirect(action, (typeof this.uid === 'number' ? `${this.uid}`: this.uid));
}
/**
* @description Responsive handler that enables or disables slide items based on screen size and operations.
* @summary This method is automatically called when the window is resized and also during component
* initialization. It determines whether slide actions should be available based on the current
* window width and the presence of UPDATE or DELETE operations. Slide items are typically hidden
* on larger screens where there's space for dedicated action buttons.
*
* @return {boolean} True if slide items should be shown, false otherwise
*
* @mermaid
* sequenceDiagram
* participant W as Window
* participant L as ListItemComponent
* participant U as UI
*
* W->>L: resize event
* L->>W: getWindowWidth()
* W-->>L: Return current width
* L->>L: Store windowWidth
* alt No operations OR width > 768px
* L->>U: showSlideItems = false
* else Operations include UPDATE/DELETE
* L->>U: showSlideItems = true
* end
* L-->>U: Return showSlideItems value
*
* @memberOf ListItemComponent
*/
@HostListener('window:resize', ['$event'])
enableSlideItems(): boolean {
this.windowWidth = getWindowWidth() as number;
if(!this.operations?.length || this.windowWidth > 768)
return this.showSlideItems = false;
this.showSlideItems = this.operations.includes(OperationKeys.UPDATE) || this.operations.includes(OperationKeys.DELETE);
return this.showSlideItems;
}
/**
* @description Animates and removes an element from the DOM.
* @summary This method applies CSS animation classes to create a smooth fade-out effect
* before removing the element from the DOM. The animation enhances user experience by
* providing visual feedback when items are deleted or removed from lists. The timing
* is coordinated with the CSS animation duration to ensure the element is removed
* after the animation completes.
*
* @param {HTMLElement} element - The DOM element to animate and remove
* @return {void}
*
* @mermaid
* sequenceDiagram
* participant L as ListItemComponent
* participant E as HTMLElement
* participant D as DOM
*
* L->>E: Add animation classes
* Note over E: uk-animation-fade, uk-animation-medium, uk-animation-reverse
* E->>E: Start fade animation
* L->>L: setTimeout(600ms)
* Note over L: Wait for animation to complete
* L->>D: element.remove()
* D->>D: Remove element from DOM
*
* @memberOf ListItemComponent
*/
removeElement(element: HTMLElement): void {
element.classList.add('uk-animation-fade', 'uk-animation-medium', 'uk-animation-reverse');
setTimeout(() => {element.remove()}, 600)
}
/**
* @description Navigates to a new route based on the specified action and item ID.
* @summary This method constructs a navigation URL using the component's route configuration,
* the specified action, and an item identifier. It uses Ionic's NavController to perform
* forward navigation with appropriate animations. This method is typically used for
* CRUD operations where each action (create, read, update, delete) has its own route.
*
* @param {string} action - The action to be performed (e.g., 'edit', 'view', 'delete')
* @param {string} [id] - The unique identifier of the item to be acted upon
* @return {Promise<boolean>} A promise that resolves to true if navigation was successful
*
* @mermaid
* sequenceDiagram
* participant L as ListItemComponent
* participant N as NavController
* participant R as Router
*
* L->>L: redirect(action, id)
* L->>L: Construct URL: /{route}/{action}/{id}
* L->>N: navigateForward(url)
* N->>R: Navigate to constructed URL
* R-->>N: Return navigation result
* N-->>L: Return boolean success
*
* @memberOf ListItemComponent
*/
async redirect(action: string, id?: string): Promise<boolean> {
return await this.navController.navigateForward(`/${this.route}/${action}/${id || this.uid}`);
}
/**
* @description Presents the actions menu popover for the list item.
* @summary This method handles the display of a contextual action menu when triggered by user
* interaction (typically a long press or right-click). It stops event propagation to prevent
* unwanted side effects, removes any existing focus traps for accessibility, configures the
* popover with the triggering event, and opens the action menu. The menu typically contains
* available CRUD operations for the item.
*
* @param {Event} event - The event that triggered the action menu request
* @return {void}
*
* @mermaid
* sequenceDiagram
* participant U as User
* participant L as ListItemComponent
* participant P as Popover
* participant A as Accessibility
*
* U->>L: Trigger action menu (long press/right-click)
* L->>L: stopImmediatePropagation()
* L->>A: removeFocusTrap()
* L->>P: Set event reference
* L->>L: actionMenuOpen = true
* L->>P: Display popover with actions
*
* @memberOf ListItemComponent
*/
presentActionsMenu(event: Event): void {
event.stopImmediatePropagation();
// forcing trap focus
removeFocusTrap();
this.actionMenuComponent.event = event;
this.actionMenuOpen = true;
}
}
Source