UNPKG

ngx-primeng-toolkit

Version:

A comprehensive TypeScript utility library for Angular component state management, PrimeNG table state management, ng-select helpers, data storage, and memoized HTTP caching. Compatible with Angular 19+ and PrimeNG 19+ (optimized for Angular 20+ and Prime

1,652 lines (1,640 loc) 84.4 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { ComponentDataStorage: () => ComponentDataStorage, ComponentState: () => ComponentState, ManipulationType: () => ManipulationType, MemoizedDataStorage: () => MemoizedDataStorage, NgSelectHelper: () => NgSelectHelper, NgSelectPagedDataResponse: () => NgSelectPagedDataResponse, NullableApiResponseSchema: () => NullableApiResponseSchema, NumberStringKeyDataSchema: () => NumberStringKeyDataSchema, OffsetPaginatedNgSelectNetworkRequest: () => OffsetPaginatedNgSelectNetworkRequest, OffsetPaginatedPrimeNgTableStateNetworkRequest: () => OffsetPaginatedPrimeNgTableStateNetworkRequest, PagedDataResponseZodSchema: () => PagedDataResponseZodSchema, PrimeNgDynamicTableStateHelper: () => PrimeNgDynamicTableStateHelper, PrimeNgPagedDataTableStateHelper: () => PrimeNgPagedDataTableStateHelper, ReloadNotification: () => ReloadNotification, SkipLoadingSpinner: () => SkipLoadingSpinner, binarySearch: () => binarySearch, calculatePrimengTablePagination: () => calculatePrimengTablePagination, cleanNullishFromObject: () => cleanNullishFromObject, createBooleanColumn: () => createBooleanColumn, createBooleanSelectItems: () => createBooleanSelectItems, createDateColumn: () => createDateColumn, createDateTimeColumn: () => createDateTimeColumn, createDropdownColumn: () => createDropdownColumn, createHierarchicalTree: () => createHierarchicalTree, createKeyData: () => createKeyData, createMultiselectColumn: () => createMultiselectColumn, createNgSelectHelper: () => createNgSelectHelper, createNumericColumn: () => createNumericColumn, createPrimengNumberMatchModes: () => createPrimengNumberMatchModes, createPrimengStringMatchModes: () => createPrimengStringMatchModes, createSimpleColumn: () => createSimpleColumn, createStatusSelectItems: () => createStatusSelectItems, createTextColumn: () => createTextColumn, createTimeColumn: () => createTimeColumn, dynamicQueryResponseZodSchema: () => dynamicQueryResponseZodSchema, emptyCallback: () => emptyCallback, hasNullishInObject: () => hasNullishInObject, initNgSelect: () => initNgSelect, initNgSelectHelper: () => initNgSelectHelper, isApiResponse: () => isApiResponse, isDynamicQueryResponse: () => isDynamicQueryResponse, isPaginatedResponse: () => isPaginatedResponse, isSimplePagedResponse: () => isSimplePagedResponse, mergeTableHeaders: () => mergeTableHeaders, nullableKeyData: () => nullableKeyData, offsetPaginatedNgSelectState: () => offsetPaginatedNgSelectState, offsetPaginatedPrimeNgTableState: () => offsetPaginatedPrimeNgTableState, provideOffsetPaginatedNgSelectStateConfig: () => provideOffsetPaginatedNgSelectStateConfig, provideOffsetPaginatedPrimeNgTableStateConfig: () => provideOffsetPaginatedPrimeNgTableStateConfig, routeParamConcat: () => routeParamConcat }); module.exports = __toCommonJS(index_exports); // src/dynamic-table-state-helper.ts var import_http2 = require("@angular/common/http"); var import_core = require("@angular/core"); var import_signals = require("@ngrx/signals"); var import_rxjs = require("rxjs"); // src/http-context-tokens.ts var import_http = require("@angular/common/http"); var SkipLoadingSpinner = new import_http.HttpContextToken(() => false); // src/types.ts var import_zod = require("zod"); var ManipulationType = /* @__PURE__ */ ((ManipulationType2) => { ManipulationType2["Create"] = "create"; ManipulationType2["Update"] = "update"; ManipulationType2["CreateChild"] = "create-child"; ManipulationType2["Delete"] = "delete"; ManipulationType2["View"] = "view"; ManipulationType2["Save"] = "save"; return ManipulationType2; })(ManipulationType || {}); var dynamicQueryResponseZodSchema = import_zod.z.object({ data: import_zod.z.any().array(), last_page: import_zod.z.number(), last_row: import_zod.z.number() }); var PagedDataResponseZodSchema = import_zod.z.object({ payload: import_zod.z.any().array(), totalCount: import_zod.z.number() }); var NumberStringKeyDataSchema = import_zod.z.object({ key: import_zod.z.union([import_zod.z.number(), import_zod.z.string()]), data: import_zod.z.string() }); var NullableApiResponseSchema = (payloadSchema) => import_zod.z.object({ payload: payloadSchema.nullable() }); function createKeyData(key, data) { return { key, data }; } function isApiResponse(response) { return typeof response === "object" && response !== null && "data" in response && "status" in response && "success" in response; } function isPaginatedResponse(response) { return typeof response === "object" && response !== null && "data" in response && Array.isArray(response.data) && "meta" in response && typeof response.meta === "object"; } function isSimplePagedResponse(response) { return typeof response === "object" && response !== null && "payload" in response && Array.isArray(response.payload) && "totalCount" in response && typeof response.totalCount === "number"; } function isDynamicQueryResponse(response) { return typeof response === "object" && response !== null && "data" in response && Array.isArray(response.data) && "last_page" in response && "last_row" in response; } // src/utils.ts function cleanNullishFromObject(obj) { if (obj === void 0) { return {}; } return Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null)); } function hasNullishInObject(obj) { return Object.values(obj).some((val) => val === null || val === void 0); } function routeParamConcat(baseUrl, routeParam) { if (routeParam === void 0 || routeParam === null) { throw new Error("routeParam cannot be null or undefined"); } if (baseUrl.endsWith("/")) { return baseUrl.concat(routeParam.toString()); } return baseUrl.concat(`/${routeParam.toString()}`); } function binarySearch(arr, val) { if (arr.length === 0) { return -1; } let left = 0; let right = arr.length - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); if (arr[mid] === val) { return mid; } else if (arr[mid] < val) { left = mid + 1; } else { right = mid - 1; } } return -1; } function emptyCallback() { } function nullableKeyData(key, data) { if (key && data) { return { key, data }; } return null; } var ReloadNotification = class _ReloadNotification { static create() { return new _ReloadNotification(); } }; function createHierarchicalTree(data, idKey, parentIdKey, expanded = false) { if (!Array.isArray(data)) { throw new Error("data must be an array"); } const categoryMap = new Map( data.map((item) => { if (!Object.hasOwn(item, idKey) || !Object.hasOwn(item, parentIdKey)) { throw new Error("idKey or parentIdKey is missing", { cause: item }); } return [ item[idKey], { data: item, children: [], expanded } ]; }) ); const rootNodes = []; data.forEach((item) => { const parentId = item[parentIdKey]; if (parentId != null && categoryMap.has(parentId)) { const parent = categoryMap.get(parentId); parent.children.push(categoryMap.get(item[idKey])); } else { rootNodes.push(categoryMap.get(item[idKey])); } }); return rootNodes; } // src/dynamic-table-state-helper.ts function initialDynamicState() { return { data: [], isLoading: false, totalRecords: 0, size: 15, page: 1, filter: [], sort: [] }; } function calculatePrimengTablePagination(event, defaultValue = { page: 1, limit: 15 }) { if (!event) { return defaultValue; } const page = event.first && event.rows ? Math.floor(event.first / event.rows) : 0; const limit = event.rows ?? 15; return { page: page + 1, limit }; } var PrimeNgDynamicTableStateHelper = class _PrimeNgDynamicTableStateHelper { constructor(url, httpClient, skipLoadingSpinner = true) { this.url = url; this.httpClient = httpClient; this.urlWithOutRouteParam = url; this.skipLoadingSpinner = skipLoadingSpinner; } state = (0, import_signals.signalState)( initialDynamicState() ); urlWithOutRouteParam; skipLoadingSpinner; #uniqueKey = (0, import_core.signal)("id"); uniqueKey = this.#uniqueKey.asReadonly(); #queryParams = {}; // Public readonly signals totalRecords = this.state.totalRecords; isLoading = this.state.isLoading; data = this.state.data; /** * Creates a new instance of PrimeNgDynamicTableStateHelper * @param options - Configuration options * @returns New instance of PrimeNgDynamicTableStateHelper */ static create(options) { return new _PrimeNgDynamicTableStateHelper( options.url, options.httpClient, options.skipLoadingSpinner ?? true ); } /** * Sets whether to skip the loading spinner * @param skip - Whether to skip the loading spinner * @returns This instance for method chaining */ setSkipLoadingSpinner(skip) { this.skipLoadingSpinner = skip; return this; } /** * Sets the unique key field for table rows * @param newUniqueKey - The field name to use as unique identifier * @returns This instance for method chaining */ setUniqueKey(newUniqueKey) { this.#uniqueKey.set(newUniqueKey); return this; } /** * Updates the API URL * @param newUrl - The new API URL * @returns This instance for method chaining */ setUrl(newUrl) { this.url = newUrl; this.urlWithOutRouteParam = newUrl; return this; } /** * Appends a route parameter to the URL * @param newRouteParam - The route parameter to append * @returns This instance for method chaining */ setRouteParam(newRouteParam) { this.url = routeParamConcat(this.urlWithOutRouteParam, newRouteParam); return this; } /** * Patches existing query parameters * @param value - Query parameters to merge * @returns This instance for method chaining */ patchQueryParams(value) { this.#queryParams = { ...this.#queryParams, ...value }; return this; } /** * Removes a specific query parameter * @param key - The key to remove * @returns This instance for method chaining */ removeQueryParam(key) { delete this.#queryParams[key]; return this; } /** * Sets all query parameters (replaces existing) * @param newQueryParams - New query parameters * @returns This instance for method chaining */ setQueryParams(newQueryParams) { this.#queryParams = newQueryParams; return this; } /** * Handles PrimeNG table lazy load events * @param event - The lazy load event from PrimeNG table */ async onLazyLoad(event) { if (this.isLoading()) { return; } (0, import_signals.patchState)(this.state, { size: event.rows || 15, page: Math.floor((event.first || 0) / (event.rows || 15)) + 1, filter: this.filterMapper(event.filters || {}), sort: Object.keys(event.multiSortMeta || {}).length > 0 ? (event.multiSortMeta || []).map((sort) => ({ field: sort.field, dir: sort.order === 1 ? "asc" : "desc" })) : event.sortField ? [ { field: event.sortField, dir: (event.sortOrder || 1) === 1 ? "asc" : "desc" } ] : [] }); await this.fetchData(this.dtoBuilder()); } /** * Clears table data and resets to first page * @param table - Optional PrimeNG Table reference to reset */ async clearTableData(table) { if (this.isLoading()) { return; } (0, import_signals.patchState)(this.state, { data: [], totalRecords: 0, page: 1, filter: [], sort: [] }); if (table) { table.reset(); } await this.fetchData(this.dtoBuilder()); } /** * Manually triggers data refresh with current state */ async refresh() { if (this.isLoading()) { return; } await this.fetchData(this.dtoBuilder()); } /** * Fetches data from the API */ async fetchData(dto) { if (this.isLoading()) { return; } try { (0, import_signals.patchState)(this.state, { isLoading: true }); const params = new URLSearchParams(); Object.entries(this.#queryParams).forEach(([key, value]) => { params.append(key, String(value)); }); const urlWithParams = params.toString() ? `${this.url}?${params.toString()}` : this.url; const response = await (0, import_rxjs.firstValueFrom)( this.httpClient.post(urlWithParams, dto, { context: new import_http2.HttpContext().set( SkipLoadingSpinner, this.skipLoadingSpinner ) }) ); const validatedResponse = dynamicQueryResponseZodSchema.parse(response); (0, import_signals.patchState)(this.state, { data: validatedResponse.data, totalRecords: validatedResponse.last_row, isLoading: false }); } catch (error) { (0, import_signals.patchState)(this.state, { data: [], totalRecords: 0, isLoading: false }); throw error; } } /** * Builds the DTO for API requests */ dtoBuilder() { return { size: this.state.size(), page: this.state.page(), filter: this.state.filter(), sort: this.state.sort() }; } /** * Maps PrimeNG filters to API filter format */ filterMapper(dto) { const filters = []; Object.entries(dto).forEach(([field, filterData]) => { if (!filterData) return; const processFilter = (filter3) => { if (filter3.value === null || filter3.value === void 0 || filter3.value === "") return; const mappedType = this.evaluateInput(filter3.matchMode || "contains"); if (mappedType) { filters.push({ field, value: String(filter3.value), type: mappedType }); } }; if (Array.isArray(filterData)) { filterData.forEach(processFilter); } else { processFilter(filterData); } }); return filters; } /** * Maps PrimeNG filter match modes to API filter types */ evaluateInput(input) { const filterMap = { startsWith: "starts", notStartsWith: "!starts", endsWith: "ends", notEndsWith: "!ends", contains: "like", notContains: "!like", equals: "=", notEquals: "!=", greaterThan: ">", lessThan: "<", greaterThanOrEqual: ">=", lessThanOrEqual: "<=" }; return filterMap[input] || null; } }; // src/paged-table-state-helper.ts var import_http3 = require("@angular/common/http"); var import_core2 = require("@angular/core"); var import_signals2 = require("@ngrx/signals"); var import_rxjs2 = require("rxjs"); function initialPagedState() { return { data: [], isLoading: false, totalRecords: 0, limit: 15, page: 1 }; } var PrimeNgPagedDataTableStateHelper = class _PrimeNgPagedDataTableStateHelper { constructor(url, httpClient, skipLoadingSpinner = true) { this.url = url; this.httpClient = httpClient; this.urlWithOutRouteParam = url; this.skipLoadingSpinner = skipLoadingSpinner; } #state = (0, import_signals2.signalState)(initialPagedState()); urlWithOutRouteParam; skipLoadingSpinner; #uniqueKey = (0, import_core2.signal)("id"); uniqueKey = this.#uniqueKey.asReadonly(); #queryParams = {}; // Public readonly signals totalRecords = this.#state.totalRecords; isLoading = this.#state.isLoading; data = this.#state.data; currentPage = this.#state.page; currentPageSize = this.#state.limit; /** * Creates a new instance of PrimengPagedDataTableStateHelper * @param option - Configuration options * @returns New instance of PrimengPagedDataTableStateHelper */ static create(option) { return new _PrimeNgPagedDataTableStateHelper( option.url, option.httpClient, option.skipLoadingSpinner ?? true ); } /** * Creates a new instance without initial URL (can be set later) * @param option - Configuration options without URL * @returns New instance of PrimengPagedDataTableStateHelper */ static createWithBlankUrl(option) { return new _PrimeNgPagedDataTableStateHelper( "", option.httpClient, option.skipLoadingSpinner ?? true ); } /** * Sets whether to skip the loading spinner * @param skip - Whether to skip the loading spinner * @returns This instance for method chaining */ setSkipLoadingSpinner(skip) { this.skipLoadingSpinner = skip; return this; } /** * Sets the unique key field for table rows * @param newUniqueKey - The field name to use as unique identifier * @returns This instance for method chaining */ setUniqueKey(newUniqueKey) { this.#uniqueKey.set(newUniqueKey); return this; } /** * Updates the API URL * @param newUrl - The new API URL * @returns This instance for method chaining */ setUrl(newUrl) { this.url = newUrl; this.urlWithOutRouteParam = newUrl; return this; } /** * Appends a route parameter to the URL * @param newRouteParam - The route parameter to append * @returns This instance for method chaining */ setRouteParam(newRouteParam) { this.url = routeParamConcat(this.urlWithOutRouteParam, newRouteParam); return this; } /** * Patches existing query parameters * @param value - Query parameters to merge * @returns This instance for method chaining */ patchQueryParams(value) { this.#queryParams = { ...this.#queryParams, ...value }; return this; } /** * Removes a specific query parameter * @param key - The key to remove * @returns This instance for method chaining */ removeQueryParam(key) { delete this.#queryParams[key]; return this; } /** * Removes all query parameters * @returns This instance for method chaining */ removeAllQueryParams() { this.#queryParams = {}; return this; } /** * Sets all query parameters (replaces existing) * @param newQueryParams - New query parameters * @returns This instance for method chaining */ setQueryParams(newQueryParams) { this.#queryParams = newQueryParams; return this; } /** * Handles PrimeNG table lazy load events * @param event - The lazy load event from PrimeNG table */ async onLazyLoad(event) { if (this.isLoading()) { return; } const newPage = Math.floor((event.first || 0) / (event.rows || 15)) + 1; const newLimit = event.rows || 15; (0, import_signals2.patchState)(this.#state, { limit: newLimit, page: newPage }); await this.fetchData(this.dtoBuilder()); } /** * Clears table data and resets to first page * @param table - Optional PrimeNG Table reference to reset */ async clearTableData(table) { if (this.isLoading()) { return; } (0, import_signals2.patchState)(this.#state, { data: [], totalRecords: 0, page: 1 }); if (table) { table.reset(); } await this.fetchData(this.dtoBuilder()); } /** * Manually triggers data refresh with current state */ async refresh() { if (this.isLoading()) { return; } await this.fetchData(this.dtoBuilder()); } /** * Fetches data from the API */ async fetchData(dto) { if (this.isLoading()) { return; } try { (0, import_signals2.patchState)(this.#state, { isLoading: true }); const params = new URLSearchParams(); Object.entries(this.#queryParams).forEach(([key, value]) => { params.append(key, String(value)); }); Object.entries(dto).forEach(([key, value]) => { params.append(key, String(value)); }); const urlWithParams = params.toString() ? `${this.url}?${params.toString()}` : this.url; const response = await (0, import_rxjs2.firstValueFrom)( this.httpClient.get(urlWithParams, { context: new import_http3.HttpContext().set(SkipLoadingSpinner, this.skipLoadingSpinner) }) ); const validatedResponse = PagedDataResponseZodSchema.parse(response); (0, import_signals2.patchState)(this.#state, { data: validatedResponse.payload, totalRecords: validatedResponse.totalCount, isLoading: false }); } catch (error) { (0, import_signals2.patchState)(this.#state, { data: [], totalRecords: 0, isLoading: false }); throw error; } } /** * Builds the DTO for API requests */ dtoBuilder() { return { limit: this.#state.limit(), page: this.#state.page() }; } }; // src/table-utils.ts function createPrimengNumberMatchModes(styleClass = "p-text-capitalize", disabled = false) { return [ { label: "Equals", value: "equals", title: "Equals", styleClass, disabled }, { label: "Not Equals", value: "notEquals", title: "Not Equals", styleClass, disabled }, { label: "Greater Than", value: "greaterThan", title: "Greater Than", styleClass, disabled }, { label: "Greater Than Or Equals", value: "greaterThanOrEqual", title: "Greater Than Or Equals", styleClass, disabled }, { label: "Less Than", value: "lessThan", title: "Less Than", styleClass, disabled }, { label: "Less Than Or Equals", value: "lessThanOrEqual", title: "Less Than Or Equals", styleClass, disabled } ]; } function createPrimengStringMatchModes(styleClass = "p-text-capitalize", disabled = false) { return [ { label: "Contains", value: "contains", title: "Contains", styleClass, disabled }, { label: "Not Contains", value: "notContains", title: "Not Contains", styleClass, disabled }, { label: "Starts With", value: "startsWith", title: "Starts With", styleClass, disabled }, { label: "Not Starts With", value: "notStartsWith", title: "Not Starts With", styleClass, disabled }, { label: "Ends With", value: "endsWith", title: "Ends With", styleClass, disabled }, { label: "Not Ends With", value: "notEndsWith", title: "Not Ends With", styleClass, disabled } ]; } function createTextColumn(field, label, options = {}) { const header = { identifier: { label, field, isNested: options.isNested, hasSort: options.hasSort ?? false, styleClass: options.styleClass } }; if (options.hasFilter ?? false) { header.filter = { type: "text", placeholder: options.placeholder ?? `Search by ${label.toLowerCase()}`, matchModeOptions: options.matchModeOptions ?? createPrimengStringMatchModes(), defaultMatchMode: options.defaultMatchMode ?? "contains", ariaLabel: `Filter by ${label}`, styleClass: options.filterStyleClass }; } return header; } function createNumericColumn(field, label, options = {}) { const header = { identifier: { label, field, isNested: options.isNested, hasSort: options.hasSort ?? false, styleClass: options.styleClass } }; if (options.hasFilter ?? false) { header.filter = { type: "numeric", placeholder: options.placeholder ?? `Filter by ${label.toLowerCase()}`, matchModeOptions: options.matchModeOptions ?? createPrimengNumberMatchModes(), defaultMatchMode: options.defaultMatchMode ?? "equals", ariaLabel: `Filter by ${label}`, styleClass: options.filterStyleClass }; } return header; } function createBooleanColumn(field, label, options = {}) { const header = { identifier: { label, field, isNested: options.isNested, hasSort: options.hasSort ?? false, isBoolean: true, styleClass: options.styleClass } }; if (options.hasFilter ?? false) { header.filter = { type: "boolean", defaultMatchMode: "equals", ariaLabel: `Filter by ${label}`, styleClass: options.filterStyleClass }; } return header; } function createDateColumn(field, label, options = {}) { const header = { identifier: { label, field, isNested: options.isNested, hasSort: options.hasSort ?? false, isDate: true, styleClass: options.styleClass } }; if (options.hasFilter ?? false) { header.filter = { type: "date", placeholder: options.placeholder ?? `Select ${label.toLowerCase()}`, defaultMatchMode: "equals", ariaLabel: `Filter by ${label}`, styleClass: options.filterStyleClass }; } return header; } function createDateTimeColumn(field, label, options = {}) { const header = { identifier: { label, field, isNested: options.isNested, hasSort: options.hasSort ?? false, isDateTime: true, styleClass: options.styleClass } }; if (options.hasFilter ?? false) { header.filter = { type: "date", placeholder: options.placeholder ?? `Select ${label.toLowerCase()}`, defaultMatchMode: "equals", ariaLabel: `Filter by ${label}`, styleClass: options.filterStyleClass }; } return header; } function createTimeColumn(field, label, options = {}) { const header = { identifier: { label, field, isNested: options.isNested, hasSort: options.hasSort ?? false, isTimeOnly: true, styleClass: options.styleClass } }; if (options.hasFilter ?? false) { header.filter = { type: "text", placeholder: options.placeholder ?? `Filter by ${label.toLowerCase()}`, defaultMatchMode: "contains", ariaLabel: `Filter by ${label}`, styleClass: options.filterStyleClass }; } return header; } function createDropdownColumn(field, label, dropdownOptions, options = {}) { const header = { identifier: { label, field, hasSort: options.hasSort ?? false, styleClass: options.styleClass } }; if (options.hasFilter ?? false) { header.filter = { type: "dropdown", placeholder: options.placeholder ?? `Select ${label.toLowerCase()}`, matchModeOptions: dropdownOptions, defaultMatchMode: "equals", ariaLabel: `Filter by ${label}`, styleClass: options.filterStyleClass }; } return header; } function createMultiselectColumn(field, label, multiselectOptions, options = {}) { const header = { identifier: { label, field, hasSort: options.hasSort ?? false, styleClass: options.styleClass } }; if (options.hasFilter ?? false) { header.filter = { type: "multiselect", placeholder: options.placeholder ?? `Select ${label.toLowerCase()}`, matchModeOptions: multiselectOptions, defaultMatchMode: "equals", ariaLabel: `Filter by ${label}`, styleClass: options.filterStyleClass }; } return header; } function createSimpleColumn(field, label, options = {}) { return { identifier: { label, field, isNested: options.isNested, hasSort: options.hasSort ?? false, styleClass: options.styleClass } }; } function mergeTableHeaders(...headers) { return headers; } function createBooleanSelectItems(trueLabel = "Yes", falseLabel = "No") { return [ { label: trueLabel, value: true }, { label: falseLabel, value: false } ]; } function createStatusSelectItems(statusOptions) { return Object.entries(statusOptions).map(([value, label]) => ({ label, value: isNaN(Number(value)) ? value : Number(value) })); } // src/memoized-data-storage.ts var import_core3 = require("@angular/core"); var import_http4 = require("@angular/common/http"); var import_rxjs3 = require("rxjs"); var MemoizedDataStorage = class { /** * Creates a new instance of MemoizedDataStorage * @param httpClient Angular HttpClient instance for making HTTP requests * @param skipLoadingSpinner Whether to skip the loading spinner for HTTP requests */ constructor(httpClient, skipLoadingSpinner = true) { this.httpClient = httpClient; this.skipLoadingSpinner = skipLoadingSpinner; } skipLoadingSpinner = true; /** * Sets whether to skip the loading spinner for HTTP requests * @param skip Whether to skip the loading spinner * @returns This instance for method chaining */ setSkipLoadingSpinner(skip) { this.skipLoadingSpinner = skip; return this; } #singleData = (0, import_core3.signal)(null); #multipleData = (0, import_core3.signal)([]); #isLoading = (0, import_core3.signal)(false); // Public readonly signals for external consumption /** * Read-only signal containing single data object or null */ singleData = this.#singleData.asReadonly(); /** * Read-only signal containing array of data objects */ multipleData = this.#multipleData.asReadonly(); /** * Read-only signal indicating whether a request is currently loading */ isLoading = this.#isLoading.asReadonly(); // Private flag to control memoization behavior #isMemoizationDisabledOnNextRead = false; /** * Disables memoization for the next read operation and clears cached data * This forces the next loadSingleData or loadMultipleData call to fetch fresh data * * @example * ```typescript * const storage = new MemoizedDataStorage<User>(httpClient); * await storage.loadSingleData('/api/user/1'); // Fetches data * await storage.loadSingleData('/api/user/1'); // Returns cached data * * storage.disableMemoizationOnNextRead(); * await storage.loadSingleData('/api/user/1'); // Fetches fresh data * ``` */ disableMemoizationOnNextRead() { this.#isMemoizationDisabledOnNextRead = true; this.#singleData.set(null); this.#multipleData.set([]); } /** * Loads a single data object from the specified URL with optional query parameters * Uses memoization to avoid redundant requests unless explicitly disabled * * @param url The URL to fetch data from * @param queryParams Optional query parameters to include in the request * @returns Promise that resolves when the data is loaded * @throws Error if the HTTP request fails * * @example * ```typescript * const storage = new MemoizedDataStorage<User>(httpClient); * await storage.loadSingleData('/api/user/1', { include: 'profile' }); * const user = storage.singleData(); // User data or null * ``` */ async loadSingleData(url, queryParams = {}) { if (!this.#isMemoizationDisabledOnNextRead && this.#singleData() !== null) { return; } try { this.#isLoading.set(true); const data = await (0, import_rxjs3.firstValueFrom)( this.httpClient.get(url, { params: queryParams, context: new import_http4.HttpContext().set(SkipLoadingSpinner, this.skipLoadingSpinner) }) ); this.#singleData.set(data); } catch (error) { this.#singleData.set(null); throw error; } finally { this.#isLoading.set(false); this.#isMemoizationDisabledOnNextRead = false; } } /** * Loads multiple data objects from the specified URL with optional query parameters * Uses memoization to avoid redundant requests unless explicitly disabled * * @param url The URL to fetch data from * @param queryParams Optional query parameters to include in the request * @returns Promise that resolves when the data is loaded * @throws Error if the HTTP request fails * * @example * ```typescript * const storage = new MemoizedDataStorage<User>(httpClient); * await storage.loadMultipleData('/api/users', { page: 1, limit: 10 }); * const users = storage.multipleData(); // Array of User data * ``` */ async loadMultipleData(url, queryParams = {}) { if (!this.#isMemoizationDisabledOnNextRead && this.#multipleData().length !== 0) { return; } try { this.#isLoading.set(true); const context = new import_http4.HttpContext(); if (this.skipLoadingSpinner) { context.set(SkipLoadingSpinner, true); } const data = await (0, import_rxjs3.firstValueFrom)( this.httpClient.get(url, { params: queryParams, context }) ); this.#multipleData.set(Array.isArray(data) ? data : []); } catch (error) { this.#multipleData.set([]); throw error; } finally { this.#isLoading.set(false); this.#isMemoizationDisabledOnNextRead = false; } } /** * Clears all cached data and resets the storage to initial state * * @example * ```typescript * const storage = new MemoizedDataStorage<User>(httpClient); * await storage.loadSingleData('/api/user/1'); * storage.clear(); // Clears cached data * console.log(storage.singleData()); // null * console.log(storage.multipleData()); // [] * ``` */ clear() { this.#singleData.set(null); this.#multipleData.set([]); this.#isMemoizationDisabledOnNextRead = false; } /** * Checks if single data is currently cached * @returns true if single data is cached, false otherwise */ hasSingleData() { return this.#singleData() !== null; } /** * Checks if multiple data is currently cached * @returns true if multiple data is cached (non-empty array), false otherwise */ hasMultipleData() { return this.#multipleData().length > 0; } }; // src/component-state.ts var import_core4 = require("@angular/core"); var ComponentState = class { isAjaxDataIncoming = (0, import_core4.signal)(false); enableCheckBoxSelection = (0, import_core4.signal)(false); isSelectableRowEnabled = (0, import_core4.signal)(false); isAjaxRequestOutgoing = (0, import_core4.signal)(false); hasMultipleSelection = (0, import_core4.signal)(false); isCreateOrUpdateDialogOpen = (0, import_core4.signal)(false); isUpdateDialogOpen = (0, import_core4.signal)(false); isCreateDialogOpen = (0, import_core4.signal)(false); manipulationType = (0, import_core4.signal)("create" /* Create */); componentTitle = (0, import_core4.signal)(""); isDataManipulationPageOpen = (0, import_core4.signal)(false); isCreateOrUpdatePageOpen = (0, import_core4.signal)(false); isUpdatePageOpen = (0, import_core4.signal)(false); isCreatePageOpen = (0, import_core4.signal)(false); manipulationTypeLabel = (0, import_core4.computed)(() => { switch (this.manipulationType()) { case "create" /* Create */: return "Create"; case "update" /* Update */: return "Update"; case "create-child" /* CreateChild */: return "Create Child"; case "delete" /* Delete */: return "Delete"; case "view" /* View */: return "View"; case "save" /* Save */: return "Save"; default: return ""; } }); /** * Updates the component title * @param componentTitle - The new title for the component * @returns This instance for method chaining */ updateComponentTitle = (componentTitle) => { this.componentTitle.set(componentTitle); return this; }; /** * Updates the multiple selection status * @param newStatus - Whether multiple selection is enabled * @returns This instance for method chaining */ updateMultipleSelectionStatus = (newStatus) => { this.hasMultipleSelection.set(newStatus); return this; }; /** * Updates the checkbox selection status * @param newStatus - Whether checkbox selection is enabled * @returns This instance for method chaining */ updateCheckBoxSelectionStatus = (newStatus) => { this.enableCheckBoxSelection.set(newStatus); return this; }; /** * Updates the selectable row status * @param newStatus - Whether row selection is enabled * @returns This instance for method chaining */ updateSelectableRowStatus = (newStatus) => { this.isSelectableRowEnabled.set(newStatus); return this; }; /** * Updates the manipulation type (Create, Update, Delete, View) * @param type - The manipulation type * @returns This instance for method chaining */ updateManipulationType = (type) => { this.manipulationType.set(type); return this; }; /** * Sets the incoming Ajax data status * @param status - Whether Ajax data is incoming * @returns This instance for method chaining */ setAjaxDataIncoming = (status) => { this.isAjaxDataIncoming.set(status); return this; }; /** * Sets the outgoing Ajax request status * @param status - Whether Ajax request is outgoing * @returns This instance for method chaining */ setAjaxRequestOutgoing = (status) => { this.isAjaxRequestOutgoing.set(status); return this; }; /** * Sets the create or update dialog open status * @param status - Whether the dialog is open * @returns This instance for method chaining */ setCreateOrUpdateDialogOpen = (status) => { this.isCreateOrUpdateDialogOpen.set(status); return this; }; /** * Sets the update dialog open status * @param status - Whether the update dialog is open * @returns This instance for method chaining */ setUpdateDialogOpen = (status) => { this.isUpdateDialogOpen.set(status); return this; }; /** * Sets the create dialog open status * @param status - Whether the create dialog is open * @returns This instance for method chaining */ setCreateDialogOpen = (status) => { this.isCreateDialogOpen.set(status); return this; }; /** * Computed signal that combines component title with manipulation type */ componentTitleWithManipulationType = (0, import_core4.computed)(() => { return this.manipulationTypeLabel() + " " + this.componentTitle(); }); /** * Computed signal that indicates if component is in update state */ isOnUpdateState = (0, import_core4.computed)(() => { return this.manipulationType() === "update" /* Update */; }); /** * Computed signal that indicates if component is in create state */ isOnCreateState = (0, import_core4.computed)(() => { return this.manipulationType() === "create" /* Create */; }); /** * Computed signal that indicates if component is in delete state */ isOnDeleteState = (0, import_core4.computed)(() => { return this.manipulationType() === "delete" /* Delete */; }); /** * Computed signal that indicates if component is in view state */ isOnViewState = (0, import_core4.computed)(() => { return this.manipulationType() === "view" /* View */; }); /** * Computed signal that indicates if any Ajax operation is currently running */ isAnyAjaxOperationRunning = (0, import_core4.computed)(() => { return this.isAjaxDataIncoming() || this.isAjaxRequestOutgoing(); }); /** * Computed signal that indicates if any dialog is open */ isAnyDialogOpen = (0, import_core4.computed)(() => { return this.isCreateOrUpdateDialogOpen() || this.isUpdateDialogOpen() || this.isCreateDialogOpen(); }); /** * Resets all state to default values * @returns This instance for method chaining */ reset = () => { this.isAjaxDataIncoming.set(false); this.enableCheckBoxSelection.set(false); this.isSelectableRowEnabled.set(false); this.isAjaxRequestOutgoing.set(false); this.hasMultipleSelection.set(false); this.isCreateOrUpdateDialogOpen.set(false); this.isUpdateDialogOpen.set(false); this.isCreateDialogOpen.set(false); this.manipulationType.set("create" /* Create */); this.componentTitle.set(""); return this; }; }; // src/component-data-storage.ts var import_core5 = require("@angular/core"); var ComponentDataStorage = class { singleData = (0, import_core5.signal)(null); multipleData = (0, import_core5.signal)([]); /** * Patches multiple data by appending new data to the existing array * @param newData - Array of new data to append * @returns This instance for method chaining * * @example * ```typescript * const storage = new ComponentDataStorage<User>(); * storage.updateMultipleData([{ id: 1, name: 'John' }]); * storage.patchMultipleData([{ id: 2, name: 'Jane' }]); * // Result: [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }] * ``` */ patchMultipleData(newData) { this.multipleData.update((prevData) => { return [...prevData, ...newData]; }); return this; } /** * Patches single data by merging new properties with existing data * If no existing data, creates new object with provided data * @param newData - Partial data to merge with existing single data * @returns This instance for method chaining * * @example * ```typescript * const storage = new ComponentDataStorage<User>(); * storage.updateSingleData({ id: 1, name: 'John', email: 'john@example.com' }); * storage.patchSingleData({ email: 'john.doe@example.com' }); * // Result: { id: 1, name: 'John', email: 'john.doe@example.com' } * ``` */ patchSingleData(newData) { this.singleData.update((prev) => prev ? { ...prev, ...newData } : { ...newData }); return this; } /** * Replaces the entire multiple data array * @param newData - New array of data to replace existing data * @returns This instance for method chaining */ updateMultipleData(newData) { this.multipleData.set(newData); return this; } /** * Replaces the single data object * @param newData - New data object or null to replace existing data * @returns This instance for method chaining */ updateSingleData(newData) { this.singleData.set(newData); return this; } /** * Adds a single item to the multiple data array * @param item - Single item to add to the array * @returns This instance for method chaining */ addToMultipleData(item) { this.multipleData.update((prevData) => [...prevData, item]); return this; } /** * Removes an item from the multiple data array based on a predicate function * @param predicate - Function that returns true for items to remove * @returns This instance for method chaining * * @example * ```typescript * storage.removeFromMultipleData(user => user.id === 1); * ``` */ removeFromMultipleData(predicate) { this.multipleData.update((prevData) => prevData.filter((item) => !predicate(item))); return this; } /** * Updates an item in the multiple data array based on a predicate function * @param predicate - Function that returns true for items to update * @param updateFn - Function that returns the updated item * @returns This instance for method chaining * * @example * ```typescript * storage.updateItemInMultipleData( * user => user.id === 1, * user => ({ ...user, name: 'Updated Name' }) * ); * ``` */ updateItemInMultipleData(predicate, updateFn) { this.multipleData.update( (prevData) => prevData.map((item) => predicate(item) ? updateFn(item) : item) ); return this; } /** * Clears all data (both single and multiple) * @returns This instance for method chaining */ clearAll() { this.singleData.set(null); this.multipleData.set([]); return this; } /** * Clears only the single data * @returns This instance for method chaining */ clearSingleData() { this.singleData.set(null); return this; } /** * Clears only the multiple data * @returns This instance for method chaining */ clearMultipleData() { this.multipleData.set([]); return this; } /** * Checks if single data exists (is not null) * @returns true if single data exists, false otherwise */ hasSingleData = (0, import_core5.computed)(() => { return this.singleData() !== null; }); /** * Checks if multiple data has items * @returns true if multiple data array has items, false if empty */ hasMultipleData = (0, import_core5.computed)(() => { return !Array.isArray(this.multipleData()) ? false : this.multipleData().length > 0; }); /** * Gets the count of items in multiple data * @returns Number of items in the multiple data array */ getMultipleDataCount = (0, import_core5.computed)(() => { return !this.hasMultipleData() ? 0 : this.multipleData().length; }); /** * Finds an item in the multiple data array * @param predicate - Function that returns true for the item to find * @returns The found item or undefined */ findInMultipleData(predicate) { return this.multipleData().find(predicate); } /** * Checks if an item exists in the multiple data array * @param predicate - Function that returns true for the item to check * @returns true if item exists, false otherwise */ existsInMultipleData(predicate) { return this.multipleData().some(predicate); } }; // src/ng-select-helper.ts var import_http5 = require("@angular/common/http"); var import_core6 = require("@angular/core"); var import_rxjs4 = require("rxjs"); var NgSelectPagedDataResponse = class { constructor(payload, totalCount) { this.payload = payload; this.totalCount = totalCount; } }; var defaultResetOpts = { resetQueryParams: false, resetBody: false, resetCache: false }; var NgSelectHelper = class _NgSelectHelper { constructor(ajaxUrl, httpClient, destroyRef, usePostRequest = false, limit = 50, useCache = true, skipLoadingSpinner = true, initialSearchText = "") { this.ajaxUrl = ajaxUrl; this.httpClient = httpClient; this.destroyRef = destroyRef; this.usePostRequest = usePostRequest; this.useCache = useCache; this.skipLoadingSpinner = skipLoadingSpinner; this.#searchText = initialSearchText; this.#originalAjaxUrl = ajaxUrl; this.#limit = limit > 0 ? limit : 50; this.destroyRef.onDestroy(() => { this.#ajaxErrorSubject.complete(); this.inputSubject.complete(); this.#loadMoreDataSubject.complete(); this.#cache.clear(); if (this.runningApiReq && !this.runningApiReq.closed) { this.runningApiReq.unsubscribe(); } }); } #cache = /* @__PURE__ */ new Map(); #originalAjaxUrl; #queryParams = {}; #body = {}; #initDone = false; #searchText = ""; #limit; #page = 1; #debounceTimeInSec = 1; #totalCount = -1; #isLastApiCallSuccessful = true; #limitReached = false; #loadMoreDataSubject = new import_rxjs4.Subject(); inputSubject = new import_rxjs4.Subject(); #ajaxErrorSubject = new import_rxjs4.Subject(); ajaxError$ = this.#ajaxErrorSubject.asObservable(); #loadedData = (0, import_core6.signal)( new NgSelectPagedDataResponse([], 0) ); loadedData = this.#loadedData.asReadonly(); #isLoading = (0, import_core6.signal)(false); isLoading = this.#isLoading.asReadonly(); runningApiReq = null; /** * Creates a new instance of NgSelectHelper * @param options Configuration options * @returns New NgSelectHelper instance */ static create({ ajaxUrl, httpClient, destroyRef, usePostRequest = false, limit = 50, useCache = true, skipLoadingSpinner = true, initialSearchText = "" }) { return new _NgSelectHelper( ajaxUrl, httpClient, destroyRef, usePostRequest, limit, useCache, skipLoadingSpinner, initialSearchText ); } /** * Sets whether to skip the loading spinner for HTTP requests * @param skip Whether to skip the loading spinner * @returns This instance for method chaining */ setSkipLoadingSpinner(skip) { this.skipLoadingSpinner = skip; return this; } /** * Sets the debounce time for search input in seconds * @param debounceTimeInSecond Debounce time in seconds * @returns This instance for method chaining */ setDebounceTimeInSecond(debounceTimeInSecond) { this.#debounceTimeInSec = debounceTimeInSecond > 0 ? debounceTimeInSecond : 1; return this; } /** * Patches the request body (only works with POST requests) * @param value Body data to merge * @returns This instance for method chaining */ patchBody(value) { if (this.usePostRequest) { this.resetAll(); this.#body = Object.assign(this.#body, value); } return this; } /** * Sets the request body (only works with POST requests) * @param newBody New body data * @returns This instance for method chaining */ setBody(newBody) { if (this.usePostRequest) { this.resetAll(); this.#body = newBody; } return this; } /** * Clears the internal cache * @returns This instance for method chaining */ clearCache() { this.#cache.clear(); return this; } /** * Sets route parameters for the URL * @param newRouteParam Route parameter to append * @returns This instance for method