UNPKG

ng-hub-ui-table

Version:

Highly customizable Angular table and list components with pagination, sorting and filtering support.

1 lines 209 kB
{"version":3,"file":"ng-hub-ui-table.mjs","sources":["../../../projects/paginable/src/lib/assets/i18n/en.ts","../../../projects/paginable/src/lib/assets/i18n/es.ts","../../../projects/paginable/src/lib/constants/defaults.ts","../../../projects/paginable/src/lib/utils.ts","../../../projects/paginable/src/lib/services/paginate-config.service.ts","../../../projects/paginable/src/lib/services/paginable.service.ts","../../../projects/paginable/src/lib/services/paginable-translation.service.ts","../../../projects/paginable/src/lib/paginable.module.ts","../../../projects/paginable/src/lib/utils/icons.ts","../../../projects/paginable/src/lib/components/icon/icon.component.ts","../../../projects/paginable/src/lib/components/icon/icon.component.html","../../../projects/paginable/src/lib/directives/paginable-list-item.directive.ts","../../../projects/paginable/src/lib/directives/paginable-table-not-found.directive.ts","../../../projects/paginable/src/lib/pipes/ucfirst.pipe.ts","../../../projects/paginable/src/lib/components/paginator/paginator.component.ts","../../../projects/paginable/src/lib/components/paginator/paginator.component.html","../../../projects/paginable/src/lib/components/list/paginable-list/paginable-list.component.ts","../../../projects/paginable/src/lib/components/list/paginable-list/paginable-list.component.html","../../../projects/paginable/src/lib/pipes/unwrap-async.pipe.ts","../../../projects/paginable/src/lib/components/paginable-table-dropdown/paginable-table-dropdown.component.ts","../../../projects/paginable/src/lib/components/paginable-table-dropdown/paginable-table-dropdown.component.html","../../../projects/paginable/src/lib/pipes/translate.pipe.ts","../../../projects/paginable/src/lib/components/paginable-table-range-input/paginable-table-range-input.component.ts","../../../projects/paginable/src/lib/components/paginable-table-range-input/paginable-table-range-input.component.html","../../../projects/paginable/src/lib/components/resizable/resizable.component.ts","../../../projects/paginable/src/lib/components/resizable/resizable.component.html","../../../projects/paginable/src/lib/constants/breakpoints.ts","../../../projects/paginable/src/lib/directives/paginable-table-cell.directive.ts","../../../projects/paginable/src/lib/directives/paginable-table-error.directive.ts","../../../projects/paginable/src/lib/directives/paginable-table-expanding-row.directive.ts","../../../projects/paginable/src/lib/directives/paginable-table-filter.directive.ts","../../../projects/paginable/src/lib/directives/paginable-table-header.directive.ts","../../../projects/paginable/src/lib/directives/paginable-table-loading.directive.ts","../../../projects/paginable/src/lib/directives/paginable-table-row.directive.ts","../../../projects/paginable/src/lib/pipes/get.pipe.ts","../../../projects/paginable/src/lib/pipes/is-observable.pipe.ts","../../../projects/paginable/src/lib/components/dropdown/dropdown.component.ts","../../../projects/paginable/src/lib/components/dropdown/dropdown.component.html","../../../projects/paginable/src/lib/interfaces/column-filter-event.ts","../../../projects/paginable/src/lib/components/menu-filter/menu-filter.component.ts","../../../projects/paginable/src/lib/components/menu-filter/menu-filter.component.html","../../../projects/paginable/src/lib/components/table/table.component.ts","../../../projects/paginable/src/lib/components/table/table.component.html","../../../projects/paginable/src/lib/directives/resizable.directive.ts","../../../projects/paginable/src/lib/directives/tooltip.directive.ts","../../../projects/paginable/src/lib/interfaces/paginable-table-header.ts","../../../projects/paginable/src/lib/services/pagination.service.ts","../../../projects/paginable/src/lib/enums/selection-types.ts","../../../projects/paginable/src/lib/index.ts","../../../projects/paginable/src/public-api.ts","../../../projects/paginable/src/ng-hub-ui-table.ts"],"sourcesContent":["export const locale = {\n\tlang: 'en',\n\tdata: {\n\t\tLOADING: 'loading',\n\t\tSEARCH: 'search',\n\t\tROWS_PER_PAGE: 'rows per page',\n\t\tSHOWING_X_OF_Y_ROWS: 'Showing {{ amount }} of {{ total }} rows',\n\t\tNO_RESULTS_FOUND: 'No results found',\n\t\tBATCH_ACTIONS: 'Batch actions',\n\t\tAPPLY: 'Apply',\n\t\tERROR_OCURRED: 'Error ocurred getting data.',\n\t\tFROM: 'from',\n\t\tTO: 'to',\n\t\tSAVE: 'save',\n\t\tNEW_VIEW: 'new view',\n\t\tADD_VIEW: 'add view',\n\t\tEDIT_VIEW: 'edit view',\n\t\tDELETE_VIEW: 'delete view',\n\t\tSAVE_FILTERS: 'save filters',\n\t\tCANCEL: 'cancel',\n\t\tNO_VIEWS_CREATED: 'No views have been created',\n\t\tSELECT_VIEW: 'Select a view',\n\t\tCLEAN_FILTERS: 'Clean filters',\n\t\tNAME: 'name',\n\t\tA_VIEW_WILL_BE_CREATED:\n\t\t\t'A view will be created with the following configuration',\n\t\tMUST_PROVIDE_A_VIEW_NAME: 'Must provide a view name.',\n\t\tMUST_PROVIDE_VIEW_CONDITIONS:\n\t\t\t'Must provide the conditions of the view.',\n\t\tTODAY: 'today',\n\t\tYESTERDAY: 'yesterday',\n\t\tCURRENT_WEEK: 'current_week',\n\t\tLAST_WEEK: 'last_week',\n\t\tCURRENT_MONTH: 'current_month',\n\t\tLAST_MONTH: 'last_month',\n\t\tCURRENT_YEAR: 'current_year',\n\t\tLAST_YEAR: 'last_year',\n\t\tNO_ELEMENTS_FOUND: 'No elements found',\n\t\tSTAR_TYPING: 'Start typing to begin your search',\n\t\tOPERATIONS: {\n\t\t\tEQUAL_TO: 'Equal to',\n\t\t\tVARIABLE: 'Variable'\n\t\t},\n\t\tCLEAR: 'clean',\n\t\tADD_RULE: 'add condition',\n\t\tREMOVE_RULE: 'remove condition',\n\t\tMATCH_ALL: 'match all',\n\t\tMATCH_ANY: 'match any',\n\t\tMATCH_MODES: {\n\t\t\tGREATERTHAN: 'Greater than',\n\t\t\tGREATERTHANOREQUAL: 'Greater than or equal to',\n\t\t\tLESSTHAN: 'Less than',\n\t\t\tLESSTHANOREQUAL: 'Less than or equal to',\n\t\t\tEQUALS: 's',\n\t\t\tNOTEQUALS: 'Not equal to',\n\t\t\tEQUAL: 'Equal to',\n\t\t\tNOTEQUAL: 'Not equal to',\n\t\t\tBEFORE: 'Before',\n\t\t\tBEFOREOREQUAL: 'Before or equal to',\n\t\t\tAFTER: 'After',\n\t\t\tAFTEROREQUAL: 'After or equal to',\n\t\t\tSTARTSWITH: 'Starts with',\n\t\t\tCONTAINS: 'Contains',\n\t\t\tNOTCONTAINS: 'Does not contain',\n\t\t\tENDSWITH: 'Ends with'\n\t\t}\n\t},\n\tTRUE: 'true',\n\tFALSE: 'false'\n};\n","export const locale = {\n\tlang: 'es',\n\tdata: {\n\t\tLOADING: 'cargando',\n\t\tSEARCH: 'buscar',\n\t\tROWS_PER_PAGE: 'Filas por página',\n\t\tSHOWING_X_OF_Y_ROWS: 'Mostrando {{ amount }} de {{ total }} resultados',\n\t\tNO_RESULTS_FOUND: 'No se han encontrado resultados',\n\t\tBATCH_ACTIONS: 'Acciones en lote',\n\t\tAPPLY: 'Aplicar',\n\t\tERROR_OCURRED: 'Ha ocurrido un error obteniendo los datos.',\n\t\tFROM: 'desde',\n\t\tTO: 'hasta',\n\t\tSAVE: 'guardar',\n\t\tNEW_VIEW: 'nueva vista',\n\t\tADD_VIEW: 'añadir vista',\n\t\tEDIT_VIEW: 'editar vista',\n\t\tDELETE_VIEW: 'eliminar vista',\n\t\tSAVE_FILTERS: 'guardar filtros',\n\t\tCANCEL: 'cancelar',\n\t\tNO_VIEWS_CREATED: 'No hay vistas creadas',\n\t\tSELECT_VIEW: 'Selecciona una vista',\n\t\tCLEAN_FILTERS: 'Limpiar filtros',\n\t\tNAME: 'Nombre',\n\t\tA_VIEW_WILL_BE_CREATED:\n\t\t\t'La vista se creará con las siguientes condiciones',\n\t\tMUST_PROVIDE_A_VIEW_NAME: 'El nombre de la vista es requerido.',\n\t\tMUST_PROVIDE_VIEW_CONDITIONS: 'Debe añadir condiciones a la vista.',\n\t\tTODAY: 'hoy',\n\t\tYESTERDAY: 'ayer',\n\t\tCURRENT_WEEK: 'semana actual',\n\t\tLAST_WEEK: 'semana pasada',\n\t\tCURRENT_MONTH: 'mes actual',\n\t\tLAST_MONTH: 'mes pasado',\n\t\tCURRENT_YEAR: 'año actual',\n\t\tLAST_YEAR: 'año pasado',\n\t\tNO_ELEMENTS_FOUND: 'No se han encontrado elementos',\n\t\tSTAR_TYPING: 'Comienza a escribir para iniciar la búsqueda',\n\t\tOPERATIONS: {\n\t\t\tEQUAL_TO: 'Igual a',\n\t\t\tVARIABLE: 'Variable'\n\t\t},\n\t\tCLEAR: 'Limpiar',\n\t\tADD_RULE: 'Añadir condición',\n\t\tREMOVE_RULE: 'eliminar condición',\n\t\tMATCH_ALL: 'Coincidir todo',\n\t\tMATCH_ANY: 'Coincidir algo',\n\t\tMATCH_MODES: {\n\t\t\tGREATERTHAN: 'Mayor que',\n\t\t\tGREATERTHANOREQUAL: 'Mayor o igual que',\n\t\t\tLESSTHAN: 'Menor que',\n\t\t\tLESSTHANOREQUAL: 'Menor o igual que',\n\t\t\tEQUALS: 'Igual a',\n\t\t\tNOTEQUALS: 'No igual a',\n\t\t\tEQUAL: 'Igual a',\n\t\t\tNOTEQUAL: 'No igual a',\n\t\t\tBEFORE: 'Antes de',\n\t\t\tBEFOREOREQUAL: 'Antes o igual a',\n\t\t\tAFTER: 'Después de',\n\t\t\tAFTEROREQUAL: 'Después o igual a',\n\t\t\tSTARTSWITH: 'Comienza con',\n\t\t\tCONTAINS: 'Contiene',\n\t\t\tNOTCONTAINS: 'No contiene',\n\t\t\tENDSWITH: 'Termina con'\n\t\t}\n\t},\n\tTRUE: 'verdadero',\n\tFALSE: 'falso'\n};\n","import { PaginableTableConfig } from '../interfaces/paginable-table-config';\n\nexport const DEFAULT_LANGUAGE = navigator.language.split('-').at(0) ?? 'en';\nexport const DEFAULT_PAGINABLE_CONFIG: PaginableTableConfig = {\n\ttheme: null,\n\tmapping: {\n\t\tcurrentPage: 'currentPage',\n\t\tlastPage: 'lastPage',\n\t\tdata: 'data',\n\t\ttotal: 'total'\n\t},\n\tviews: {\n\t\tkey: 'paginable-table_view_'\n\t},\n\tlanguage: DEFAULT_LANGUAGE\n};\n","import { effect, Signal, signal } from '@angular/core';\n\n/**\n * Creates a debounced version of a given signal. The returned signal updates its value\n * only after the specified debounce time has elapsed since the last change in the source signal.\n *\n * @template T - The type of the signal's value.\n * @param sourceSignal - The source signal to debounce.\n * @param debounceDelay - Either a number (ms) or a signal<number> indicating debounce delay.\n * @returns A new signal that emits debounced values from the source signal.\n */\nexport function debouncedSignal<T>(\n\tsourceSignal: Signal<T>,\n\tdebounceDelay: number | Signal<number> = 0\n): Signal<T> {\n\tconst debounced = signal(sourceSignal());\n\n\teffect((onCleanup) => {\n\t\tconst delay =\n\t\t\ttypeof debounceDelay === 'number' ? debounceDelay : debounceDelay();\n\t\tconst value = sourceSignal();\n\n\t\tconst timeout = setTimeout(() => {\n\t\t\tdebounced.set(value);\n\t\t}, delay);\n\n\t\tonCleanup(() => clearTimeout(timeout));\n\t});\n\n\treturn debounced;\n}\n\n/**\n * Determines if two objects or two values are equivalent.\n *\n * Two objects or values are considered equivalent if at least one of the following is true:\n *\n * * Both objects or values pass `===` comparison.\n * * Both objects or values are of the same type and all of their properties are equal by\n * comparing them with `equals`.\n *\n * @param o1 Object or value to compare.\n * @param o2 Object or value to compare.\n * @returns true if arguments are equal.\n */\nexport function equals(o1: any, o2: any): boolean {\n\tif (o1 === o2) {\n\t\treturn true;\n\t}\n\tif (o1 === null || o2 === null) {\n\t\treturn false;\n\t}\n\tif (o1 !== o1 && o2 !== o2) {\n\t\treturn true;\n\t} // NaN === NaN\n\tlet t1 = typeof o1,\n\t\tt2 = typeof o2,\n\t\tlength: number,\n\t\tkey: any,\n\t\tkeySet: any;\n\tif (t1 == t2 && t1 == 'object') {\n\t\tif (Array.isArray(o1)) {\n\t\t\tif (!Array.isArray(o2)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif ((length = o1.length) == o2.length) {\n\t\t\t\tfor (key = 0; key < length; key++) {\n\t\t\t\t\tif (!equals(o1[key], o2[key])) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else {\n\t\t\tif (Array.isArray(o2)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tkeySet = Object.create(null);\n\t\t\tfor (key in o1) {\n\t\t\t\tif (!equals(o1[key], o2[key])) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tkeySet[key] = true;\n\t\t\t}\n\t\t\tfor (key in o2) {\n\t\t\t\tif (!(key in keySet) && typeof o2[key] !== 'undefined') {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Checks if a value is defined and not null.\n *\n * @param {any} value - is of type `any`, which means it can accept any data type.\n *\n * @returns a boolean value.\n */\nexport function isDefined(value: any): boolean {\n\treturn typeof value !== 'undefined' && value !== null;\n}\n\n/**\n * Checks if the given item is an object and not an array.\n *\n * @param {any} item - is of type \"any\", which means it can be any data type.\n *\n * @returns a boolean value.\n */\nexport function isObject(item: any): boolean {\n\treturn item && typeof item === 'object' && !Array.isArray(item);\n}\n\n/**\n * Recursively merges two objects, combining their properties into a new object.\n *\n * @param {any} target - is the object that you want to merge the `source` object into.\n * @param {any} source - is an object that contains the properties and values that you want to merge into the `target` object.\n *\n * @returns the merged object, which is the result of merging the `target` and `source` objects.\n */\nexport function mergeDeep(target: any, source: any): any {\n\tlet output = Object.assign({}, target);\n\tif (isObject(target) && isObject(source)) {\n\t\tObject.keys(source).forEach((key: any) => {\n\t\t\tif (isObject(source[key])) {\n\t\t\t\tif (!(key in target)) {\n\t\t\t\t\tObject.assign(output, { [key]: source[key] });\n\t\t\t\t} else {\n\t\t\t\t\toutput[key] = mergeDeep(target[key], source[key]);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tObject.assign(output, { [key]: source[key] });\n\t\t\t}\n\t\t});\n\t}\n\treturn output;\n}\n\n/**\n * Generates a unique ID of a specified length by randomly selecting characters from a predefined set of characters.\n *\n * @param {number} length - is the desired length of the generated unique ID.\n *\n * @returns a unique id string.\n */\nexport function generateUniqueId(length: number): string {\n\tconst characters =\n\t\t'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n\tconst charactersLength = characters.length;\n\tlet result = '';\n\n\tfor (let i = 0; i < length; i++) {\n\t\tconst randomIndex = Math.floor(Math.random() * charactersLength);\n\t\tresult += characters.charAt(randomIndex);\n\t}\n\n\treturn result;\n}\n\n/**\n * Replaces placeholders in a string with corresponding values from a given object.\n *\n * @param {string} expr - a string that represents the expression to be interpolated. It may contain\n * placeholders that will be replaced with values from the `params` object.\n * @param {any} [params] - an optional object that contains the values to be interpolated into the `expr`\n * string. It is used to replace placeholders in the `expr` string with actual values.\n *\n * @returns returns the interpolated string. If `params` is not provided, it returns the original\n * `expr` string. If `params` is provided, it replaces placeholders in the `expr` string with corresponding values from `params`\n * and returns the interpolated string.\n */\nexport function interpolateString(\n\texpr: string = '',\n\tparams: any = {},\n\ttemplateMatcher: RegExp = /{{\\s?([^{}\\s]*)\\s?}}/g\n) {\n\tif (!params) {\n\t\treturn expr;\n\t}\n\n\treturn expr.replace(templateMatcher, (substring: string, b: string) => {\n\t\tlet r = getValue(params, b);\n\t\treturn isDefined(r) ? r : substring;\n\t});\n}\n\n/**\n * Retrieves the value of a nested property from an object using dot notation.\n *\n * @param {any} target - the object from which you want to retrieve a value.\n * @param {string} key - a string that represents the property or nested properties of the `target` object that you want to\n * retrieve the value from. The `key` can be a single property name or a dot-separated string representing a nested property path.\n *\n * @returns the value of the specified key in the target object.\n */\nexport function getValue(target: any, key: string): any {\n\tlet keys = typeof key === 'string' ? key.split('.') : [key];\n\tkey = '';\n\tdo {\n\t\tkey += keys.shift();\n\t\tif (\n\t\t\tisDefined(target) &&\n\t\t\tisDefined(target[key]) &&\n\t\t\t(typeof target[key] === 'object' || !keys.length)\n\t\t) {\n\t\t\ttarget = target[key];\n\t\t\tkey = '';\n\t\t} else if (!keys.length) {\n\t\t\ttarget = undefined;\n\t\t} else {\n\t\t\tkey += '.';\n\t\t}\n\t} while (keys.length);\n\n\treturn target;\n}\n","import { InjectionToken } from '@angular/core';\nimport { PaginableTableConfig } from '../interfaces/paginable-table-config';\n\n/**\n * This is not a real service, but it looks like it from the outside.\n * It's just an InjectionTToken used to import the config object, provided from the outside\n */\n// tslint:disable-next-line: variable-name\nexport const PaginableConfigService = new InjectionToken<PaginableTableConfig>(\n\t'PaginableConfig'\n);\n","import { Inject, Injectable, Optional } from '@angular/core';\nimport { DEFAULT_PAGINABLE_CONFIG } from '../constants/defaults';\nimport { PaginableTableConfig } from '../interfaces/paginable-table-config';\nimport { mergeDeep } from '../utils';\nimport { PaginableConfigService } from './paginate-config.service';\n\n@Injectable()\nexport class PaginableService {\n\tconfig!: Required<PaginableTableConfig>;\n\n\tget mapping(): any {\n\t\treturn this.config.mapping;\n\t}\n\n\tconstructor(\n\t\t@Optional()\n\t\t@Inject(PaginableConfigService)\n\t\tprivate _config = DEFAULT_PAGINABLE_CONFIG\n\t) {\n\t\tthis.initialize();\n\t}\n\n\tinitialize() {\n\t\tthis.config = mergeDeep(DEFAULT_PAGINABLE_CONFIG, this._config);\n\t}\n}\n","import { Injectable, inject } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { locale as enLang } from '../assets/i18n/en';\nimport { locale as esLang } from '../assets/i18n/es';\nimport { PaginableService } from './paginable.service';\nimport { getValue } from '../utils';\n\n@Injectable()\nexport class PaginableTranslationService {\n\t#paginableSvc = inject(PaginableService);\n\n\tdefaultTranslations: Record<string, string | any> = [enLang, esLang].reduce(\n\t\t(acc, c) => {\n\t\t\tacc[c.lang] = c.data;\n\t\t\treturn acc;\n\t\t},\n\t\t{}\n\t);\n\n\ttranslations!: Record<string, string>;\n\n\tprivate translationSource = new Subject<any>();\n\n\ttranslationObserver = this.translationSource.asObservable();\n\n\tconstructor() {\n\t\tthis.initialize();\n\t}\n\n\tinitialize() {\n\t\tthis.setTranslations(\n\t\t\tthis.defaultTranslations[this.#paginableSvc.config.language] ??\n\t\t\t\tthis.defaultTranslations['en']\n\t\t);\n\t}\n\n\t/**\n\t * Retrieves a value from a translations object based on a given key.\n\t *\n\t * @param {string} key - The `key` parameter in the `getTranslation` function is a string that represents the unique identifier or\n\t * key for the translation you want to retrieve from the translations object. This key is used to look up the corresponding\n\t * translation value in the translations object.\n\t *\n\t * @returns The value associated with the provided `key` from the `translations` object.\n\t */\n\tgetTranslation(key: string): any {\n\t\treturn getValue(this.translations, key);\n\t}\n\n\t/**\n\t * Merges the default English translations with the provided translations and updates the translation source.\n\t *\n\t * @param {Record<string, string> | any} translations - The `translations` parameter in the `setTranslation` method is a parameter that accepts\n\t * either an object of type `Record<string, string>` or any other type. If no value is provided, it defaults to an empty object\n\t * `{}`.\n\t */\n\tsetTranslations(translations: Record<string, string> | any = {}) {\n\t\tthis.translations = { ...enLang, ...translations };\n\t\tthis.translationSource.next(this.translations);\n\t}\n}\n","import { ModuleWithProviders, NgModule } from '@angular/core';\nimport { PaginableTableConfig } from './interfaces/paginable-table-config';\nimport { PaginableTranslationService } from './services/paginable-translation.service';\nimport { PaginableService } from './services/paginable.service';\nimport { PaginableConfigService } from './services/paginate-config.service';\n// import { PaginableTableComponent } from './components/paginable-table/paginable-table.component';\n// import { PaginableTableHeaderDirective } from './directives/paginable-table-header.directive';\n// import { PaginableTableRowDirective } from './directives/paginable-table-row.directive';\n// import { PaginableTableCellDirective } from './directives/paginable-table-cell.directive';\n// import { PaginableTableLoadingDirective } from './directives/paginable-table-loading.directive';\n// import { PaginableTableErrorDirective } from './directives/paginable-table-error.directive';\n// import { PaginableTableExpandingRowDirective } from './directives/paginable-table-expanding-row.directive';\n// import { PaginableTableFilterDirective } from './directives/paginable-table-filter.directive';\n\n@NgModule({\n\timports: [\n\t\t// PaginableTableComponent,\n\t\t// PaginableTableHeaderDirective,\n\t\t// PaginableTableRowDirective,\n\t\t// PaginableTableCellDirective,\n\t\t// PaginableTableLoadingDirective,\n\t\t// PaginableTableErrorDirective,\n\t\t// PaginableTableExpandingRowDirective,\n\t\t// PaginableTableFilterDirective\n\t],\n\texports: [\n\t\t// PaginableTableComponent,\n\t\t// PaginableTableHeaderDirective,\n\t\t// PaginableTableRowDirective,\n\t\t// PaginableTableCellDirective,\n\t\t// PaginableTableLoadingDirective,\n\t\t// PaginableTableErrorDirective,\n\t\t// PaginableTableExpandingRowDirective,\n\t\t// PaginableTableFilterDirective\n\t],\n\tproviders: []\n})\nexport class TableModule {\n\tstatic forRoot(\n\t\tconfig?: PaginableTableConfig\n\t): ModuleWithProviders<TableModule> {\n\t\treturn {\n\t\t\tngModule: TableModule,\n\t\t\tproviders: [\n\t\t\t\t{\n\t\t\t\t\tprovide: PaginableConfigService,\n\t\t\t\t\tuseValue: config\n\t\t\t\t},\n\t\t\t\tPaginableService,\n\t\t\t\tPaginableTranslationService\n\t\t\t]\n\t\t};\n\t}\n}\n","/**\n * Checks if any Font Awesome base class is present in the input string.\n *\n * @param {string} input - The `containsFontAwesomeClass` function takes a string input and checks if it contains any of the Font\n * Awesome base classes such as 'fa-solid', 'fa-regular', 'fa-light', 'fa-duotone', 'fa-thin', or 'fa-brands'.\n *\n * @returns A boolean value - `true` if any of the Font Awesome base classes are\n * present in the input string, and `false` otherwise.\n */\nexport function containsFontAwesomeClass(input: string): boolean {\n\t// Define an array of Font Awesome base classes to check for\n\tconst faClasses = [\n\t\t'fa-solid',\n\t\t'fa-regular',\n\t\t'fa-light',\n\t\t'fa-duotone',\n\t\t'fa-thin',\n\t\t'fa-brands'\n\t];\n\n\t// Use a regular expression to check if any of the Font Awesome classes are present in the input string\n\tconst faClassRegex = new RegExp(`\\\\b(${faClasses.join('|')})\\\\b`, 'i');\n\n\t// Return true if any class matches, false otherwise\n\treturn faClassRegex.test(input);\n}\n\n/**\n * Checks if a string contains the 'bi' class using a regular expression in TypeScript.\n *\n * @param {string} input - The `input` parameter in the `containsBootstrapIconsClass` function is a string that represents a CSS\n * class or a list of CSS classes. The function checks if the input string contains the Bootstrap Icons class 'bi'.\n *\n * @returns returns a boolean value - `true` if the input string contains the 'bi'\n * class, and `false` otherwise.\n */\nexport function containsBootstrapIconsClass(input: string): boolean {\n\t// Define a regular expression pattern to match 'bi' class\n\tconst biClassRegex = /\\bbi\\b/;\n\n\t// Return true if the class matches, false otherwise\n\treturn biClassRegex.test(input);\n}\n","/**\n * @file icon.component.ts\n * @description Angular component for rendering icons with support for different icon libraries.\n */\n\nimport { NgClass } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, Input } from '@angular/core';\nimport { Icon } from '../../interfaces/paginable-table-header';\nimport { containsFontAwesomeClass } from '../../utils/icons';\n\n/**\n * Defines the supported icon types.\n * @typedef {'font-awesome' | 'material' | 'bootstrap'} IconType\n */\nexport type IconType = 'font-awesome' | 'material' | 'bootstrap';\n\n/**\n * @component HubIconComponent\n * @selector hub-icon\n * @description A versatile icon component that supports multiple icon libraries.\n *\n * @example\n * <hub-icon [config]=\"iconConfig\"></hub-icon>\n */\n@Component({\n\tselector: 'hub-icon, ng-hub-ui-icon',\n\tstandalone: true,\n\timports: [NgClass],\n\ttemplateUrl: './icon.component.html',\n\tchangeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class HubIconComponent implements Icon {\n\t/**\n\t * @input config\n\t * @description Sets the icon configuration. Can be a string or an Icon object.\n\t * @type {string | Icon}\n\t */\n\t@Input({ required: true })\n\tset config(value: string | Icon | undefined) {\n\t\tif (!value) {\n\t\t\tvalue = '';\n\t\t}\n\t\tif (typeof value === 'string') {\n\t\t\tthis.value = value;\n\t\t} else {\n\t\t\tthis.type = value.type ?? null;\n\t\t\tthis.variant = value.variant ?? '';\n\t\t\tthis.value = value.value ?? null;\n\t\t}\n\t}\n\n\t/**\n\t * @input type\n\t * @description The type of icon (font-awesome, material, or bootstrap).\n\t * @type {IconType}\n\t */\n\ttype: IconType;\n\n\t/**\n\t * @input value\n\t * @description The icon value or class name.\n\t * @type {string}\n\t */\n\tvalue: string;\n\n\t/**\n\t * @input variant\n\t * @description The variant of the icon (if applicable).\n\t * @type {string}\n\t */\n\tvariant: string;\n\n\t/**\n\t * @getter classlist\n\t * @description Computes the CSS classes for the icon based on its type and value.\n\t * @returns {string | null} The computed CSS class string or null if no value is set.\n\t */\n\tget classlist(): string | null {\n\t\tif (!this.value) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (!this.type) {\n\t\t\treturn this.value;\n\t\t}\n\n\t\tconst classlist: Array<string> = [];\n\t\tif (['font-awesome', 'bootstrap'].includes(this.type)) {\n\t\t\tif (Array.isArray(this.value)) {\n\t\t\t\tclasslist.push(...this.value);\n\t\t\t} else {\n\t\t\t\tclasslist.push(...this.value.split(' '));\n\t\t\t}\n\t\t}\n\n\t\tswitch (this.type) {\n\t\t\tcase 'font-awesome':\n\t\t\t\tif (!containsFontAwesomeClass(classlist.join(' '))) {\n\t\t\t\t\tclasslist.push('fa');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'bootstrap':\n\t\t\t\tif (!containsFontAwesomeClass(classlist.join(' '))) {\n\t\t\t\t\tclasslist.push('bs');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'material':\n\t\t\t\tclasslist.push('material-symbols-outlined');\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn classlist.join(' ');\n\t}\n\n\t/**\n\t * @getter content\n\t * @description Gets the content for material icons.\n\t * @returns {string | null} The icon content for material icons, or null for other types.\n\t */\n\tget content(): string | null {\n\t\treturn ['material'].includes(this.type) ? this.value : null;\n\t}\n}\n","@if (value) {\n <i [ngClass]=\"classlist\">\n {{ content }}\n </i>\n}\n","import { Directive, TemplateRef } from '@angular/core';\n\n@Directive({\n\tselector: '[listItemTpt]'\n})\nexport class PaginableListItemDirective {\n\tconstructor(public template: TemplateRef<any>) {}\n}\n","import { Directive, TemplateRef } from '@angular/core';\n\n@Directive({\n\tselector: '[noDataTpt], [paginableTableNotFound]',\n\tstandalone: true\n})\nexport class PaginableTableNotFoundDirective {\n\tconstructor(public template: TemplateRef<any>) {}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'ucfirst',\n\tstandalone: true\n})\nexport class UcfirstPipe implements PipeTransform {\n\ttransform(value: string = ''): string {\n\t\treturn value.charAt(0).toUpperCase() + value.slice(1);\n\t}\n}\n","import { Component, input, model } from '@angular/core';\n\n@Component({\n\tselector: 'hub-paginator, paginable-table-paginator',\n\tstandalone: true,\n\ttemplateUrl: './paginator.component.html',\n\tstyleUrls: ['./paginator.component.scss']\n})\nexport class PaginatorComponent {\n\treadonly page = model<number>(1);\n\treadonly numberOfPages = input<number | null>();\n}\n","<nav class=\"pager\" aria-label=\"Page navigation\">\n\t<ul class=\"pager pagination pager__list\">\n\t\t<li\n\t\t\tclass=\"page-item pager__item\"\n\t\t\t[class.pager__item--disabled]=\"page() === 1\"\n\t\t\t[class.disabled]=\"page() === 1\"\n\t\t>\n\t\t\t<a\n\t\t\t\trole=\"button\"\n\t\t\t\tclass=\"page-link pager__link\"\n\t\t\t\thref=\"javascript:{}\"\n\t\t\t\t(click)=\"page.set(1)\"\n\t\t\t>\n\t\t\t\t<i class=\"pager__icon fa fa-angle-double-left\"></i>\n\t\t\t</a>\n\t\t</li>\n\t\t<li\n\t\t\tclass=\"page-item pager__item\"\n\t\t\t[class.pager__item--disabled]=\"page() <= 1\"\n\t\t\t[class.disabled]=\"page() <= 1\"\n\t\t>\n\t\t\t<a\n\t\t\t\trole=\"button\"\n\t\t\t\tclass=\"page-link pager__link\"\n\t\t\t\thref=\"javascript:{}\"\n\t\t\t\taria-label=\"Previous\"\n\t\t\t\t(click)=\"page.set(page() - 1)\"\n\t\t\t>\n\t\t\t\t<span class=\"pager__icon-wrapper\" aria-hidden=\"true\">\n\t\t\t\t\t<i class=\"pager__icon fa fa-angle-left\"></i>\n\t\t\t\t</span>\n\t\t\t</a>\n\t\t</li>\n\t\t@if (page() - 1 > 1) {\n\t\t\t<li class=\"page-item pager__item pager__item--disabled disabled\">\n\t\t\t\t<a\n\t\t\t\t\trole=\"button\"\n\t\t\t\t\tclass=\"page-link pager__link\"\n\t\t\t\t\thref=\"javascript:{}\"\n\t\t\t\t\t>...</a\n\t\t\t\t>\n\t\t\t</li>\n\t\t}\n\t\t<li class=\"page-item pager__item\">\n\t\t\t@if (page() - 1 >= 1) {\n\t\t\t\t<a\n\t\t\t\t\trole=\"button\"\n\t\t\t\t\tclass=\"page-link pager__link\"\n\t\t\t\t\thref=\"javascript:{}\"\n\t\t\t\t\t(click)=\"page.set(page() - 1)\"\n\t\t\t\t\t>{{ page() - 1 }}</a\n\t\t\t\t>\n\t\t\t}\n\t\t</li>\n\t\t<li class=\"page-item pager__item pager__item--active active\">\n\t\t\t<a\n\t\t\t\trole=\"button\"\n\t\t\t\tclass=\"page-link pager__link\"\n\t\t\t\thref=\"javascript:{}\"\n\t\t\t\t>{{ page() }}</a\n\t\t\t>\n\t\t</li>\n\t\t@if (numberOfPages()) {\n\t\t\t<li class=\"page-item pager__item\">\n\t\t\t\t@if (page() + 1 <= numberOfPages()!) {\n\t\t\t\t\t<a\n\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\tclass=\"page-link pager__link\"\n\t\t\t\t\t\thref=\"javascript:{}\"\n\t\t\t\t\t\t(click)=\"page.set(page() + 1)\"\n\t\t\t\t\t\t>{{ page() + 1 }}</a\n\t\t\t\t\t>\n\t\t\t\t}\n\t\t\t</li>\n\t\t\t@if (page() + 1 < numberOfPages()!) {\n\t\t\t\t<li\n\t\t\t\t\tclass=\"page-item pager__item pager__item--disabled disabled\"\n\t\t\t\t>\n\t\t\t\t\t<a\n\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\tclass=\"page-link pager__link\"\n\t\t\t\t\t\thref=\"javascript:{}\"\n\t\t\t\t\t\t>...</a\n\t\t\t\t\t>\n\t\t\t\t</li>\n\t\t\t}\n\t\t\t<li\n\t\t\t\tclass=\"page-item pager__item\"\n\t\t\t\t[class.pager__item--disabled]=\"page() >= numberOfPages()!\"\n\t\t\t\t[class.disabled]=\"page() >= numberOfPages()!\"\n\t\t\t>\n\t\t\t\t<a\n\t\t\t\t\trole=\"button\"\n\t\t\t\t\tclass=\"page-link pager__link\"\n\t\t\t\t\thref=\"javascript:{}\"\n\t\t\t\t\taria-label=\"Next\"\n\t\t\t\t\t(click)=\"page.set(page() + 1)\"\n\t\t\t\t>\n\t\t\t\t\t<span class=\"pager__icon-wrapper\" aria-hidden=\"true\">\n\t\t\t\t\t\t<i class=\"pager__icon fa fa-angle-right\"></i>\n\t\t\t\t\t</span>\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t\t<li\n\t\t\t\tclass=\"page-item pager__item\"\n\t\t\t\t[class.pager__item--disabled]=\"page() === numberOfPages()\"\n\t\t\t\t[class.disabled]=\"page() === numberOfPages()\"\n\t\t\t>\n\t\t\t\t<a\n\t\t\t\t\trole=\"button\"\n\t\t\t\t\tclass=\"page-link pager__link\"\n\t\t\t\t\thref=\"javascript:{}\"\n\t\t\t\t\t(click)=\"page.set(numberOfPages() ?? 1)\"\n\t\t\t\t>\n\t\t\t\t\t<i class=\"pager__icon fa fa-angle-double-right\"></i>\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t} @else {\n\t\t\t<li class=\"page-item pager__item\">\n\t\t\t\t<a\n\t\t\t\t\trole=\"button\"\n\t\t\t\t\tclass=\"page-link pager__link\"\n\t\t\t\t\thref=\"javascript:{}\"\n\t\t\t\t\taria-label=\"Next\"\n\t\t\t\t\t(click)=\"page.set(page() + 1)\"\n\t\t\t\t>\n\t\t\t\t\t<span class=\"pager__icon-wrapper\" aria-hidden=\"true\">\n\t\t\t\t\t\t<i class=\"pager__icon fa fa-angle-right\"></i>\n\t\t\t\t\t</span>\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t}\n\t</ul>\n</nav>\n","import { CommonModule } from '@angular/common';\nimport {\n\tComponent,\n\tContentChild,\n\tInput,\n\tTemplateRef,\n\tinject\n} from '@angular/core';\nimport {\n\tFormArray,\n\tFormBuilder,\n\tFormControl,\n\tNG_VALUE_ACCESSOR,\n\tReactiveFormsModule\n} from '@angular/forms';\nimport { PaginableListItemDirective } from '../../../directives/paginable-list-item.directive';\nimport { PaginableTableNotFoundDirective } from '../../../directives/paginable-table-not-found.directive';\nimport { ListClickEvent } from '../../../interfaces/item-click-event';\nimport { PaginableTableDropdown } from '../../../interfaces/paginable-table-dropdown';\nimport { PaginableTableOptions } from '../../../interfaces/paginable-table-options';\nimport { RowButton } from '../../../interfaces/row-button';\nimport { UcfirstPipe } from '../../../pipes/ucfirst.pipe';\nimport { getValue } from '../../../utils';\nimport { PaginatorComponent } from '../../paginator/paginator.component';\n\n@Component({\n\tselector: 'hub-list, hub-ui-list, hub-paginable-list',\n\ttemplateUrl: './paginable-list.component.html',\n\tstyleUrls: ['./paginable-list.component.scss'],\n\thost: {\n\t\tclass: 'd-flex flex-column gap-4'\n\t},\n\tproviders: [\n\t\t{\n\t\t\tprovide: NG_VALUE_ACCESSOR,\n\t\t\tuseExisting: PaginableListComponent,\n\t\t\tmulti: true\n\t\t}\n\t],\n\timports: [\n\t\tCommonModule,\n\t\tReactiveFormsModule,\n\t\tPaginatorComponent,\n\t\tPaginableTableNotFoundDirective,\n\t\tUcfirstPipe\n\t],\n\tstandalone: true\n})\nexport class PaginableListComponent<T = any> {\n\t#fb = inject(FormBuilder);\n\n\t@Input() bindValue?: string;\n\t@Input() bindLabel: string = 'label';\n\t@Input() bindChildren: string = 'children';\n\n\t@Input() selectable!: string | null;\n\n\tprivate _options: PaginableTableOptions = {\n\t\tcursor: 'default',\n\t\thoverableRows: false,\n\t\tstriped: null,\n\t\tvariant: null,\n\t\tsearchable: false,\n\t\tcollapsed: true\n\t};\n\tget options(): PaginableTableOptions {\n\t\treturn this._options;\n\t}\n\t@Input()\n\tset options(v: PaginableTableOptions) {\n\t\tthis._options = v;\n\t\tthis.buildForm(this.form, this._items);\n\t}\n\n\tprivate _items!: Array<T>;\n\t@Input()\n\tget items(): Array<T> {\n\t\treturn this._items;\n\t}\n\tset items(v: Array<T>) {\n\t\tthis._items = v ?? [];\n\t\tthis.form.clear();\n\t\tthis.buildForm(this.form, this._items);\n\t}\n\n\t@Input() clickFn: (event: ListClickEvent<T>) => void | Promise<void>;\n\n\tform: FormArray = this.#fb.array([]);\n\n\tvalue: Array<T & { collapsed: boolean }> = [];\n\n\t// NOTE: Templates\n\n\t@ContentChild(PaginableListItemDirective, { read: TemplateRef })\n\titemTpt?: TemplateRef<any>;\n\t@ContentChild(PaginableTableNotFoundDirective, { read: TemplateRef })\n\tnoDataTpt?: TemplateRef<any>;\n\n\t// NOTE: Otros\n\tisDisabled: boolean = false;\n\n\tonChange: any = () => {};\n\tonTouch: any = () => {};\n\n\t// NOTE: Filters\n\n\tsearchFG = this.#fb.control({});\n\n\t// NOTE: Batch actions\n\n\t/**\n\t * Collection of actions for items\n\t *\n\t * @type {PaginableTableRowAction[]}\n\t * @memberof PaginableTableComponent\n\t */\n\tprivate _batchActions: Array<PaginableTableDropdown | RowButton> = [];\n\t@Input()\n\tget batchActions(): Array<PaginableTableDropdown | RowButton> {\n\t\treturn this._batchActions;\n\t}\n\tset batchActions(v: Array<PaginableTableDropdown | RowButton>) {\n\t\tthis._batchActions = v.map((b) => {\n\t\t\tif ((b as PaginableTableDropdown).buttons) {\n\t\t\t\tb = { fill: null, position: 'start', color: 'light', ...b };\n\t\t\t}\n\t\t\treturn b;\n\t\t});\n\t}\n\n\t// NOTE: Control access value\n\n\twriteValue(value: Array<T> = []): void {\n\t\t// this.value = this.buildValue(value);\n\t}\n\n\tregisterOnChange(fn: any): void {\n\t\tthis.onChange = fn;\n\t}\n\n\tregisterOnTouched(fn: any): void {\n\t\tthis.onTouch = fn;\n\t}\n\n\tsetDisabledState?(isDisabled: boolean): void {\n\t\tthis.isDisabled = isDisabled;\n\t}\n\n\t/**\n\t * Handles the action to be executed in a batch\n\t *\n\t * @param {Event} event\n\t * @memberof PaginableTableComponent\n\t */\n\thandleBatchAction(event: any) {\n\t\tevent.handler(this.value);\n\t}\n\n\tbuildForm(form: FormArray, items: Array<any>) {\n\t\tform.clear();\n\t\tfor (const index in items) {\n\t\t\tif (Object.prototype.hasOwnProperty.call(items, index)) {\n\t\t\t\tconst item = items[index];\n\n\t\t\t\tconst group = this.#fb.group({\n\t\t\t\t\tselected: [true],\n\t\t\t\t\tcollapsed: [this.options.collapsed],\n\t\t\t\t\tdata: [item],\n\t\t\t\t\tchildren: this.#fb.array([])\n\t\t\t\t});\n\n\t\t\t\tgroup.patchValue(item);\n\n\t\t\t\tif (item[this.bindChildren]?.length) {\n\t\t\t\t\tthis.buildForm(\n\t\t\t\t\t\tgroup.get('children') as FormArray,\n\t\t\t\t\t\titem[this.bindChildren]\n\t\t\t\t\t);\n\t\t\t\t\t// newItem['children'] = this.buildValue(children);\n\t\t\t\t}\n\t\t\t\tform.push(group);\n\t\t\t}\n\t\t}\n\t}\n\n\tbuildValue(items: Array<T>): Array<T & { collapsed: boolean }> {\n\t\tconst value: Array<T & { collapsed: boolean }> = [];\n\t\tfor (const item of items) {\n\t\t\tconst { children, ...newItem } = item as any;\n\t\t\tif (children?.length) {\n\t\t\t\tnewItem['children'] = this.buildValue(children);\n\t\t\t}\n\t\t\tvalue.push({\n\t\t\t\t...newItem,\n\t\t\t\tcollapsed: true\n\t\t\t});\n\t\t}\n\t\treturn value;\n\t}\n\n\ttoggleCollapsed(control: FormControl) {\n\t\tcontrol.patchValue(!control.value);\n\t}\n\n\t/**\n\t * Emits a structured click event for the clicked list item, including metadata and state.\n\t *\n\t * This method is typically called when an item in the list is clicked. It extracts contextual\n\t * information such as depth, index, selection state, and expansion state, then passes it to\n\t * the user-defined `clickFn` callback.\n\t *\n\t * If a `bindLabel` is configured, the emitted `value` will be derived from that property;\n\t * otherwise, the full item will be passed as `value`.\n\t *\n\t * @param item - The list item object, including `selected` and `collapsed` state.\n\t * @param depth - The nesting depth of the item within a tree structure (0 = root level).\n\t * @param index - The position of the item in the current visible list or page.\n\t * @param event - The native `MouseEvent` that triggered the click.\n\t *\n\t * @remarks\n\t * If the `clickFn` callback is not defined, the method exits early and no event is emitted.\n\t */\n\tonItemClick(\n\t\t{ collapsed, selected, ...item },\n\t\tdepth: number,\n\t\tindex: number,\n\t\tevent: MouseEvent\n\t) {\n\t\tif (!this.clickFn) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.clickFn({\n\t\t\tdepth,\n\t\t\tindex,\n\t\t\tselected,\n\t\t\tcollapsed,\n\t\t\tvalue: this.bindLabel ? getValue(item, this.bindLabel) : item,\n\t\t\titem: item as T,\n\t\t\tmouseEvent: event\n\t\t});\n\t}\n\n\tonPageClicked(page: number) {\n\t\t// if (!this.data) {\n\t\t// \treturn;\n\t\t// }\n\t\t// this.data.currentPage = page;\n\t\t// this.triggerTheParamChanges();\n\t}\n\n\tfilter() {\n\t\t// if (!this.data) {\n\t\t// \treturn;\n\t\t// }\n\t\t// this.data.currentPage = 1;\n\t\t// this.filterChange.emit({\n\t\t// \tsearchText: this.searchFG?.value ?? null,\n\t\t// \tspecificSearch: this.specificSearchFG?.value ?? null\n\t\t// });\n\t}\n}\n","@if (batchActions.length || options.searchable) {\n\t<div class=\"d-flex justify-content-between gap-3\">\n\t\t<div class=\"ms-auto\">\n\t\t\t<div class=\"d-flex justify-content-end gap-2\">\n\t\t\t\t@if (options.searchable) {\n\t\t\t\t\t<div class=\"input-group search_input-group\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tclass=\"form-control border border-light search_input\"\n\t\t\t\t\t\t\t[formControl]=\"searchFG\"\n\t\t\t\t\t\t\t[placeholder]=\"'SEARCH' | ucfirst\"\n\t\t\t\t\t\t\t(keyup.enter)=\"filter()\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div class=\"input-group-append\">\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tclass=\"btn btn-light search_button\"\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t(click)=\"filter()\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<i class=\"fa fa-search\" aria-hidden=\"true\"></i>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</div>\n\t</div>\n}\n\n<ng-container\n\t[ngTemplateOutlet]=\"containerTpt\"\n\t[ngTemplateOutletContext]=\"{\n\t\tformArray: form,\n\t\tisRoot: true,\n\t\titems: items,\n\t\tdepth: 0\n\t}\"\n></ng-container>\n\n<ng-template\n\t#containerTpt\n\tlet-formArray=\"formArray\"\n\tlet-isRoot=\"isRoot\"\n\tlet-items=\"items\"\n\tlet-depth=\"depth\"\n>\n\t<ul class=\"list-group list-group-flush tree-list\" [class.root]=\"isRoot\">\n\t\t@for (\n\t\t\tformGroup of formArray.controls;\n\t\t\ttrack formGroup;\n\t\t\tlet index = $index\n\t\t) {\n\t\t\t<li\n\t\t\t\tclass=\"list-group-item tree-list__node\"\n\t\t\t\t[class.clickable-item]=\"clickFn\"\n\t\t\t\t[formGroup]=\"formGroup\"\n\t\t\t\t(click)=\"\n\t\t\t\t\tonItemClick(formGroup.value, depth, index, $event);\n\t\t\t\t\t$event.preventDefault();\n\t\t\t\t\t$event.stopPropagation()\n\t\t\t\t\"\n\t\t\t>\n\t\t\t\t@if (formGroup.value; as item) {\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"d-flex justify-content-between align-items-center gap-2\"\n\t\t\t\t\t>\n\t\t\t\t\t\t@if (selectable === 'multiple') {\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\tclass=\"form-check-input tree-list__node-checkbox\"\n\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\tformControlName=\"selected\"\n\t\t\t\t\t\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t}\n\t\t\t\t\t\t<div class=\"tree-list__node-label\">\n\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"itemTpt || defaultItemtTpt\"\n\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{\n\t\t\t\t\t\t\t\t\tdata: items[index],\n\t\t\t\t\t\t\t\t\tdepth,\n\t\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t\t\tcollapsed:\n\t\t\t\t\t\t\t\t\t\t!!formGroup.get('collapsed').value,\n\t\t\t\t\t\t\t\t\tselected: formGroup.get('selected').value\n\t\t\t\t\t\t\t\t}\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t</ng-container>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"tree-list__node-buttons d-flex align-items-center gap-2\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t@if (item[bindChildren]?.length) {\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tclass=\"btn\"\n\t\t\t\t\t\t\t\t\t(click)=\"\n\t\t\t\t\t\t\t\t\t\ttoggleCollapsed(\n\t\t\t\t\t\t\t\t\t\t\tformGroup.get('collapsed')\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t$event.preventDefault();\n\t\t\t\t\t\t\t\t\t\t$event.stopPropagation()\n\t\t\t\t\t\t\t\t\t\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<i\n\t\t\t\t\t\t\t\t\t\tclass=\"fas\"\n\t\t\t\t\t\t\t\t\t\t[ngClass]=\"\n\t\t\t\t\t\t\t\t\t\t\titem.collapsed\n\t\t\t\t\t\t\t\t\t\t\t\t? 'fa-chevron-down'\n\t\t\t\t\t\t\t\t\t\t\t\t: 'fa-chevron-up'\n\t\t\t\t\t\t\t\t\t\t\"\n\t\t\t\t\t\t\t\t\t></i>\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t@if (item[bindChildren]?.length && !item.collapsed) {\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"containerTpt\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{\n\t\t\t\t\t\t\t\tformArray: formGroup.get('children'),\n\t\t\t\t\t\t\t\titems: items[index][bindChildren],\n\t\t\t\t\t\t\t\tdepth: depth + 1\n\t\t\t\t\t\t\t}\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</li>\n\t\t}\n\t</ul>\n</ng-template>\n\n<ng-template #noDataRowTpt>\n\t<li class=\"list-group-item\">\n\t\t<ng-container [ngTemplateOutlet]=\"noDataTpt || defaultNoDataTpt\">\n\t\t</ng-container>\n\t</li>\n</ng-template>\n\n<ng-template #defaultNoDataTpt>\n\t<div class=\"alert alert-info d-flex align-items-center m-4\" role=\"alert\">\n\t\t<i class=\"fa fa-info fa-2x me-4 mr-4\" aria-hidden=\"true\"></i>\n\t\t{{ 'NO_RESULTS_FOUND' | ucfirst }}\n\t</div>\n</ng-template>\n\n<ng-template #defaultItemtTpt let-data=\"data\">\n\t{{ data[bindLabel] }}\n</ng-template>\n","import {\n\tChangeDetectorRef,\n\tinject,\n\tOnDestroy,\n\tPipe,\n\tPipeTransform\n} from '@angular/core';\nimport { Observable, Subscription } from 'rxjs';\n\n/**\n * A standalone pipe that unwraps the value of an observable or returns the value directly if it's not an observable.\n *\n * @description\n * The `UnwrapAsyncPipe` is used to unwrap the value emitted by an observable or return the value directly if it's not an observable.\n * It subscribes to the observable and returns the emitted value. If the input is not an observable, it simply returns the value.\n *\n * @usageNotes\n * ```html\n * <div>{{ observableOrValue | unwrapAsync }}</div>\n * ```\n *\n * @publicApi\n */\n@Pipe({\n\tname: 'unwrapAsync',\n\tstandalone: true,\n\tpure: false\n})\nexport class UnwrapAsyncPipe<T = any> implements PipeTransform, OnDestroy {\n\t#cdr = inject(ChangeDetectorRef);\n\n\t/**\n\t * The unwrapped value of the observable or the direct value.\n\t */\n\tvalue: T | null = null;\n\n\t/**\n\t * The subscription to the observable.\n\t */\n\tsubscription: Subscription | null = null;\n\n\t/**\n\t * Performs cleanup tasks when the pipe is destroyed.\n\t */\n\tngOnDestroy(): void {\n\t\tthis.unsubscribe();\n\t}\n\n\t/**\n\t * Transforms the input value.\n\t *\n\t * @param value The input value to transform. It can be an observable or a direct value.\n\t * @returns The unwrapped value of the observable or the direct value.\n\t */\n\ttransform(value: T | Observable<T>): T | null {\n\t\tif (value instanceof Observable) {\n\t\t\tthis.unsubscribe();\n\t\t\tthis.subscription = value.subscribe((result) => {\n\t\t\t\tthis.value = result;\n\t\t\t\tthis.#cdr.markForCheck();\n\t\t\t});\n\t\t} else {\n\t\t\tthis.value = value;\n\t\t}\n\t\treturn this.value;\n\t}\n\n\t/**\n\t * Unsubscribes from the current subscription.\n\t */\n\tprivate unsubscribe(): void {\n\t\tif (this.subscription) {\n\t\t\tthis.subscription.unsubscribe();\n\t\t\tthis.subscription = null;\n\t\t}\n\t}\n}\n","import { NgClass, NgStyle } from '@angular/common';\nimport {\n\tComponent,\n\tElementRef,\n\tEmbeddedViewRef,\n\tHostListener,\n\tInput,\n\tTemplateRef,\n\tViewChild,\n\tViewContainerRef,\n\tinject,\n\tinput\n} from '@angular/core';\nimport { TableRowEvent } from '../../interfaces';\nimport { PaginableTableDropdown } from '../../interfaces/paginable-table-dropdown';\nimport { UnwrapAsyncPipe } from '../../pipes/unwrap-async.pipe';\nimport { HubIconComponent } from '../icon/icon.component';\n\n@Component({\n\tselector: 'hub-table-dropdown, paginable-table-dropdown',\n\n\tstandalone: true,\n\timports: [NgClass, NgStyle, HubIconComponent, UnwrapAsyncPipe],\n\ttemplateUrl: './paginable-table-dropdown.component.html',\n\tstyleUrls: ['./paginable-table-dropdown.component.scss']\n})\nexport class PaginableTableDropdownComponent<T = any> {\n\t#elementRef = inject(ElementRef);\n\n\t@ViewChild('dropdownTpt') dropdownTpt!: TemplateRef<any>;\n\n\tprivate vcr = inject(ViewContainerRef);\n\tprivate embeddedView: EmbeddedViewRef<any> | null = null;\n\tprivate renderedElement: HTMLElement | null = null;\n\n\treadonly row = input<TableRowEvent<T>>();\n\n\t#options: PaginableTableDropdown = { buttons: [] };\n\n\t@Input()\n\tget options(): PaginableTableDropdown {\n\t\treturn this.#options;\n\t}\n\tset options(v: PaginableTableDropdown) {\n\t\tthis.#options = {\n\t\t\tposition: 'end',\n\t\t\tfill: 'clear',\n\t\t\tcolor: 'muted',\n\t\t\t...v\n\t\t};\n\t\tif (this.#options.fill === 'clear') {\n\t\t\tthis.buttonClass = 'btn text-' + (this.#options.color ?? 'muted');\n\t\t} else {\n\t\t\tthis.buttonClass =\n\t\t\t\t'btn ' +\n\t\t\t\t['btn', this.#options.fill, this.#options.color]\n\t\t\t\t\t.filter((o) => o)\n\t\t\t\t\t.join('-');\n\t\t}\n\t}\n\n\treadonly appendTo = input<HTMLElement | 'body' | null>('body');\n\n\treadonly disabled = input<boolean>(false);\n\n\tbuttonClass: string | null = null;\n\tshown: boolean = false;\n\n\t/**\n\t * Checks if the clicked element is outside the component's native element and if the component is currently shown, and if so,\n\t * it closes the component.\n\t *\n\t * @param event - Represents the event that triggered the clickOut function. It contains information about the event, such as\n\t * the target element that was clicked.\n\t */\n\t@HostListener('document:click', ['$event'])\n\tclickOut(event) {\n\t\tif (\n\t\t\t!this.#elementRef.nativeElement.contains(event.target) &&\n\t\t\tthis.shown\n\t\t) {\n\t\t\tthis.close();\n\t\t}\n\t}\n\n\ttoggle() {\n\t\tif (this.shown) {\n\t\t\tthis.close();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.shown = true;\n\n\t\tconst appendTo = this.appendTo();\n\t\tconst target =\n\t\t\tappendTo === 'body'\n\t\t\t\t? document.body\n\t\t\t\t: appendTo instanceof HTMLElement\n\t\t\t\t\t? appendTo\n\t\t\t\t\t: this.#elementRef.nativeElement;\n\n\t\t// Crea la vista\n\t\tthis.embeddedView = this.vcr.createEmbeddedView(this.dropdownTpt);\n\t\tthis.embeddedView.detectChanges();\n\n\t\tconst [element] = this.embeddedView.rootNodes as HTMLElement[];\n\t\tthis.renderedElement = element;\n\n\t\tconst button = this.#elementRef.nativeElement.querySelector('button');\n\t\tconst rect = button.getBoundingClientRect();\n\n\t\tObject.assign(element.style, {\n\t\t\tposition: 'absolute',\n\t\t\tzIndex: '1050',\n\t\t\ttop: `${rect.bottom + window.scrollY}px`,\n\t\t\tleft: `${rect.left + window.scrollX}px`\n\t\t});\n\n\t\ttarget.appendChild(element);\n\t}\n\n\t/**\n\t * Sets the \"shown\" property to false.\n\t */\n\tclose() {\n\t\tthis.shown = false;\n\n\t\tif (this.embeddedView) {\n\t\t\tthis.embeddedView.destroy();\n\t\t\tthis.embeddedView = null;\n\t\t}\n\n\t\tif (this.renderedElement && this.renderedElement.parentElement) {\n\t\t\tthis.renderedElement.parentElement.removeChild(\n\t\t\t\tthis.renderedElement\n\t\t\t);\n\t\t\tthis.renderedElement = null;\n\t\t}\n\t}\n}\n","<div class=\"btn-group\" [class.show]=\"shown\">\n\t<button\n\t\tclass=\"px-2 dropdown-toggle\"\n\t\ttype=\"button\"\n\t\t[ngClass]=\"buttonClass\"\n\t\t[attr.title]=\"options.tooltip\"\n\t\tdata-toggle=\"dropdown\"\n\t\t[attr.aria-expanded]=\"shown\"\n\t\t(click)=\"toggle(); $event.stopPropagation()\"\n\t\t[disabled]=\"disabled()\"\n\t>\n\t\t<div class=\"d-flex flex-nowrap align-items-center gap-1\">\n\t\t\t<i [ngClass]=\"options.icon || 'fa fa-ellipsis-v'\"></i>\n\t\t\t@if (options.title) {\n\t\t\t\t{{ options.title }}\n\t\t\t}\n\t\t</div>\n\t</button>\n\t<ng-template #dropdownTpt>\n\t\t<div\n\t\t\tclass=\"dropdown-menu dropdown-menu-{{ options.position }}\"\n\t\t\t[class.show]=\"shown\"\n\t\t\t[ngStyle]=\"{\n\t\t\t\tright