@smallpearl/ngx-helper 0.32.9 → 0.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,12 @@
1
1
  import { BooleanInput } from '@angular/cdk/coercion';
2
- import { HttpClient, HttpContextToken, HttpParams } from '@angular/common/http';
2
+ import { HttpContextToken } from '@angular/common/http';
3
3
  import { AfterViewInit, ChangeDetectorRef, ElementRef, EventEmitter, OnDestroy, OnInit, TemplateRef } from '@angular/core';
4
4
  import { ControlValueAccessor, NgControl } from '@angular/forms';
5
5
  import { MatFormFieldControl } from '@angular/material/form-field';
6
6
  import { MatSelect, MatSelectChange } from '@angular/material/select';
7
7
  import { TranslocoService } from '@jsverse/transloco';
8
- import { BehaviorSubject, Observable, Subject } from 'rxjs';
8
+ import { SPPagedEntityLoader } from '@smallpearl/ngx-helper/entities';
9
+ import { Subject } from 'rxjs';
9
10
  import * as i0 from "@angular/core";
10
11
  export interface SPMatSelectEntityHttpContext {
11
12
  entityName: string;
@@ -13,13 +14,9 @@ export interface SPMatSelectEntityHttpContext {
13
14
  endpoint: string;
14
15
  }
15
16
  export declare const SP_MAT_SELECT_ENTITY_HTTP_CONTEXT: HttpContextToken<SPMatSelectEntityHttpContext>;
16
- type EntityGroup<T> = {
17
- id?: PropertyKey;
18
- name?: string;
19
- label?: string;
20
- description?: string;
21
- items?: T[];
22
- __items__?: T[];
17
+ type EntityGroup<TEntity> = {
18
+ label: string;
19
+ entities: TEntity[];
23
20
  };
24
21
  export type SPMatSelectEntityResponseParser = <TEntity extends {
25
22
  [P in IdKey]: PropertyKey;
@@ -35,86 +32,31 @@ export type SPMatSelectEntityResponseParser = <TEntity extends {
35
32
  */
36
33
  export declare class SPMatSelectEntityComponent<TEntity extends {
37
34
  [P in IdKey]: PropertyKey;
38
- }, IdKey extends string = 'id'> implements OnInit, OnDestroy, AfterViewInit, ControlValueAccessor, MatFormFieldControl<string | number | string[] | number[]> {
39
- static _entitiesCache: Map<string, {
40
- refCount: number;
41
- entities: Array<any>;
42
- }>;
43
- matSel: MatSelect;
35
+ }, IdKey extends string = 'id'> extends SPPagedEntityLoader<TEntity, IdKey> implements OnInit, OnDestroy, AfterViewInit, ControlValueAccessor, MatFormFieldControl<string | number | string[] | number[]> {
36
+ labelFn: import("@angular/core").InputSignal<((entity: TEntity) => string) | undefined>;
37
+ filterFn: import("@angular/core").InputSignal<((entity: TEntity, search: string) => boolean) | undefined>;
38
+ inlineNew: import("@angular/core").InputSignal<boolean>;
39
+ multiple: import("@angular/core").InputSignal<boolean>;
44
40
  /**
45
- * Entity label function. Given an entity return its natural label
46
- * to display to the user.
41
+ * The entity key name that is used to classify entities into groups.
42
+ * Entities with the same key value will be grouped together. If this is
43
+ * specified, grouping will be enabled.
44
+ * @see groupByFn
47
45
  */
48
- entityLabelFn: (entity: TEntity) => string;
46
+ groupOptionsKey: import("@angular/core").InputSignal<string | undefined>;
49
47
  /**
50
- * Entity filter function - return a boolean if the entity is to be included
51
- * in the filtered entities list.
52
- * @param entity: TEntity object to test for 'search' string.
53
- * @param search - search string
48
+ * A function that takes a TEntity and returns the group id (string)
49
+ * that the entity belongs to. If this is specified, grouping of entities
50
+ * in the select will be enabled. This takes precedence over
51
+ * `groupOptionsKey`.
52
+ * @see groupOptionsKey
54
53
  */
55
- entityFilterFn: (entity: TEntity, search: string) => boolean;
56
- /**
57
- * Entity idKey, if idKey is different from the default 'id'.
58
- */
59
- idKey: string;
60
- /**
61
- * URL of the remote from where entities are to be loaded.
62
- * This won't be used if `loadFromRemoteFn` is specified.
63
- */
64
- url: string;
65
- /**
66
- * Parameters to be added to the HTTP request to retrieve data from
67
- * remote. This won't be used if `loadFromRemoteFn` is specified.
68
- */
69
- httpParams: HttpParams;
70
- /**
71
- * Function to load entities from remote.
72
- */
73
- loadFromRemoteFn: () => Observable<TEntity[]>;
74
- inlineNew: boolean;
75
- /**
76
- * Entity name, that is used to form the "New { item }" menu item if
77
- * inlineNew=true. This is also used as the key of the object in GET response
78
- * if the reponse JSON is not an array and rather an object, where the values
79
- * are stored indexed by the server model name. For eg:-
80
- *
81
- * {
82
- * 'customers': [
83
- * {...},
84
- * {...},
85
- * {...},
86
- * ]
87
- * }
88
- */
89
- entityName: string;
90
- multiple: boolean;
91
- group: boolean;
92
- /**
93
- * The group object key name under which options are stored. Defaults to
94
- * 'items' or pluralized 'entityName'. Ideally the client class should
95
- * explicitly set this property value.
96
- */
97
- groupOptionsKey: string;
98
- /**
99
- * If groupOptions = true, specify this to provide accurate label for each
100
- * group. If not specified, group label will be determined by looking up one
101
- * of the standard fields - name, label or description - whichever comes
102
- * first.
103
- */
104
- groupLabelFn: (group: any) => string;
105
- /**
106
- * Sideload data key name.
107
- */
108
- sideloadDataKey: import("@angular/core").InputSignal<string | undefined>;
109
- /**
110
- * Parser function to return the list of entities from the GET response.
111
- */
112
- responseParserFn: import("@angular/core").InputSignal<SPMatSelectEntityResponseParser | undefined>;
54
+ groupByFn: import("@angular/core").InputSignal<((entity: TEntity) => string) | undefined>;
113
55
  selectionChange: EventEmitter<TEntity | TEntity[]>;
114
56
  createNewItemSelected: EventEmitter<void>;
115
57
  readonly searchText: import("@angular/core").InputSignal<string | undefined>;
116
58
  readonly notFoundText: import("@angular/core").InputSignal<string | undefined>;
117
- readonly addItemText: import("@angular/core").InputSignal<string | undefined>;
59
+ readonly createNewText: import("@angular/core").InputSignal<string | undefined>;
118
60
  controlType: string;
119
61
  /**
120
62
  * Template for the option label. If not provided, the default label
@@ -126,7 +68,7 @@ export declare class SPMatSelectEntityComponent<TEntity extends {
126
68
  * ```
127
69
  * <sp-mat-select-entity
128
70
  * [url]="'/api/v1/customers/'"
129
- * [entityLabelFn]="entity => entity.name"
71
+ * [labelFn]="entity => entity.name"
130
72
  * [optionLabelTemplate]="optionLabelTemplate"
131
73
  * ></sp-mat-select-entity>
132
74
  * <ng-template #optionLabelTemplate let-entity>
@@ -135,10 +77,9 @@ export declare class SPMatSelectEntityComponent<TEntity extends {
135
77
  * ```
136
78
  */
137
79
  optionLabelTemplate: import("@angular/core").InputSignal<TemplateRef<any> | undefined>;
138
- _entityLabelFn: import("@angular/core").Signal<(entity: TEntity) => string>;
139
- _sideloadDataKey: import("@angular/core").Signal<string>;
140
- private _entities;
141
- private _groupedEntities;
80
+ protected _entityLabelFn: import("@angular/core").Signal<(entity: TEntity) => string>;
81
+ protected _group: import("@angular/core").Signal<boolean>;
82
+ protected _groupEntitiesKey: import("@angular/core").Signal<string>;
142
83
  stateChanges: Subject<void>;
143
84
  focused: boolean;
144
85
  touched: boolean;
@@ -146,25 +87,32 @@ export declare class SPMatSelectEntityComponent<TEntity extends {
146
87
  lastSelectValue: string | number | string[] | number[];
147
88
  searching: boolean;
148
89
  filterStr: string;
149
- filter$: BehaviorSubject<string>;
90
+ filter$: Subject<string>;
150
91
  onChanged: (_: any) => void;
151
92
  onTouched: () => void;
152
- matSelect: MatSelect;
93
+ matSelect: import("@angular/core").Signal<MatSelect | undefined>;
153
94
  filteredValues: Subject<TEntity[]>;
154
95
  filteredGroupedValues: Subject<EntityGroup<TEntity>[]>;
155
96
  destroy: Subject<void>;
156
- private loaded;
157
- private load$;
158
97
  static nextId: number;
159
98
  id: string;
160
99
  private _placeholder;
161
- protected http: HttpClient;
162
100
  protected cdr: ChangeDetectorRef;
163
101
  protected _elementRef: ElementRef<any>;
164
102
  protected _formField: import("@angular/material/form-field").MatFormField | null;
165
103
  ngControl: NgControl | null;
166
104
  transloco: TranslocoService;
167
105
  constructor();
106
+ /**
107
+ * Conditions for loading entities:
108
+ *
109
+ * 1. When the select is opened, if entities have not already been loaded.
110
+ * 2. When the search string changes.
111
+ * 3. When the scroll reaches the bottom and more entities are available
112
+ * to be loaded.
113
+ *
114
+ * We need to create an 'observer-loop' that can handle the above.
115
+ */
168
116
  ngOnInit(): void;
169
117
  ngOnDestroy(): void;
170
118
  ngAfterViewInit(): void;
@@ -198,7 +146,21 @@ export declare class SPMatSelectEntityComponent<TEntity extends {
198
146
  setDisabledState(isDisabled: boolean): void;
199
147
  onSelectOpened(ev: any): void;
200
148
  onSelectionChange(ev: MatSelectChange): void;
201
- filterValues(search: string): void;
149
+ /**
150
+ * Wrapper to filter entities based on whether grouping is enabled or not.
151
+ * Calls one of the two filtering methods -- filterGroupedEntities() or
152
+ * filterNonGroupedEntities().
153
+ * @param entities
154
+ * @param filterStr
155
+ * @returns
156
+ */
157
+ filterEntities(entities: TEntity[], filterStr: string): void;
158
+ /**
159
+ * Filters the entities based on the search string.
160
+ * @param search The search string to filter entities.
161
+ * @returns The number of entities in the filtered result set or undefined.
162
+ */
163
+ filterNonGroupedEntities(entities: TEntity[], search: string): void;
202
164
  /**
203
165
  * Filtering grouped entities logic works like this. If the search string
204
166
  * matches a group label, the entire group is to be included in the results.
@@ -206,20 +168,25 @@ export declare class SPMatSelectEntityComponent<TEntity extends {
206
168
  * groups are to be included and within those groups, only entities whose
207
169
  * label matches the search string are to be included in the result set.
208
170
  * @param search
209
- * @returns
171
+ * @returns number of groups in the filtered result set.
210
172
  */
211
- filterGroupedValues(search: string): void;
212
- loadFromRemote(): Observable<TEntity[] | ((group: EntityGroup<TEntity>) => TEntity[])>;
213
- groupLabel(group: EntityGroup<TEntity>): string;
214
- groupEntities(group: EntityGroup<TEntity>): TEntity[];
215
- groupEntitiesKey(): string;
216
- private existsInCache;
217
- private getCacheKey;
218
- private getFromCache;
219
- private addToCache;
220
- private removeFromCache;
173
+ filterGroupedEntities(entities: TEntity[], search: string): void;
174
+ /**
175
+ * Helper to arrange the given array of entities into groups based on the
176
+ * groupByFn or groupOptionsKey. groupByFn takes precedence over
177
+ * groupOptionsKey.
178
+ * @param entities
179
+ * @returns EntityGroup<TEntity>[]
180
+ */
181
+ protected groupEntities(entities: TEntity[]): EntityGroup<TEntity>[];
221
182
  private getHttpReqContext;
183
+ /**
184
+ * If more entities are available, load the next page of entities.
185
+ * This method is triggered when user scrolls to the bottom of the options
186
+ * list. Well almost to the bottom of the options list. :)
187
+ */
188
+ onInfiniteScroll(): void;
222
189
  static ɵfac: i0.ɵɵFactoryDeclaration<SPMatSelectEntityComponent<any, any>, never>;
223
- static ɵcmp: i0.ɵɵComponentDeclaration<SPMatSelectEntityComponent<any, any>, "sp-mat-select-entity", never, { "entityLabelFn": { "alias": "entityLabelFn"; "required": false; }; "entityFilterFn": { "alias": "entityFilterFn"; "required": false; }; "idKey": { "alias": "idKey"; "required": false; }; "url": { "alias": "url"; "required": false; }; "httpParams": { "alias": "httpParams"; "required": false; }; "loadFromRemoteFn": { "alias": "loadFromRemoteFn"; "required": false; }; "inlineNew": { "alias": "inlineNew"; "required": false; }; "entityName": { "alias": "entityName"; "required": false; }; "multiple": { "alias": "multiple"; "required": false; }; "group": { "alias": "group"; "required": false; }; "groupOptionsKey": { "alias": "groupOptionsKey"; "required": false; }; "groupLabelFn": { "alias": "groupLabelFn"; "required": false; }; "sideloadDataKey": { "alias": "sideloadDataKey"; "required": false; "isSignal": true; }; "responseParserFn": { "alias": "responseParserFn"; "required": false; "isSignal": true; }; "searchText": { "alias": "searchText"; "required": false; "isSignal": true; }; "notFoundText": { "alias": "notFoundText"; "required": false; "isSignal": true; }; "addItemText": { "alias": "addItemText"; "required": false; "isSignal": true; }; "optionLabelTemplate": { "alias": "optionLabelTemplate"; "required": false; "isSignal": true; }; "entities": { "alias": "entities"; "required": false; }; "value": { "alias": "value"; "required": false; }; "userAriaDescribedBy": { "alias": "aria-describedby"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "required": { "alias": "required"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; }, { "selectionChange": "selectionChange"; "createNewItemSelected": "createNewItemSelected"; }, never, never, true, never>;
190
+ static ɵcmp: i0.ɵɵComponentDeclaration<SPMatSelectEntityComponent<any, any>, "sp-mat-select-entity", never, { "labelFn": { "alias": "labelFn"; "required": false; "isSignal": true; }; "filterFn": { "alias": "filterFn"; "required": false; "isSignal": true; }; "inlineNew": { "alias": "inlineNew"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "groupOptionsKey": { "alias": "groupOptionsKey"; "required": false; "isSignal": true; }; "groupByFn": { "alias": "groupByFn"; "required": false; "isSignal": true; }; "searchText": { "alias": "searchText"; "required": false; "isSignal": true; }; "notFoundText": { "alias": "notFoundText"; "required": false; "isSignal": true; }; "createNewText": { "alias": "createNewText"; "required": false; "isSignal": true; }; "optionLabelTemplate": { "alias": "optionLabelTemplate"; "required": false; "isSignal": true; }; "entities": { "alias": "entities"; "required": false; }; "value": { "alias": "value"; "required": false; }; "userAriaDescribedBy": { "alias": "aria-describedby"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "required": { "alias": "required"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; }, { "selectionChange": "selectionChange"; "createNewItemSelected": "createNewItemSelected"; }, never, never, true, never>;
224
191
  }
225
192
  export {};
@@ -0,0 +1,2 @@
1
+ export * from './src/mat-select-infinite-scroll.directive';
2
+ export * from './src/mat-select-infinite-scroll.service';
@@ -0,0 +1,19 @@
1
+ import { AfterViewInit, EventEmitter, OnDestroy } from '@angular/core';
2
+ import { MatSelect } from '@angular/material/select';
3
+ import { MatSelectInfiniteScrollService } from './mat-select-infinite-scroll.service';
4
+ import * as i0 from "@angular/core";
5
+ export declare class MatSelectInfiniteScrollDirective implements OnDestroy, AfterViewInit {
6
+ protected matSelect: MatSelect;
7
+ private infiniteScrollService;
8
+ threshold: string;
9
+ debounceTime: number;
10
+ complete: boolean;
11
+ infiniteScroll: EventEmitter<void>;
12
+ private destroyed$;
13
+ constructor(matSelect: MatSelect, infiniteScrollService: MatSelectInfiniteScrollService);
14
+ ngAfterViewInit(): void;
15
+ getSelectItemHeightPx(panel: Element): number;
16
+ ngOnDestroy(): void;
17
+ static ɵfac: i0.ɵɵFactoryDeclaration<MatSelectInfiniteScrollDirective, never>;
18
+ static ɵdir: i0.ɵɵDirectiveDeclaration<MatSelectInfiniteScrollDirective, "[msInfiniteScroll]", never, { "threshold": { "alias": "threshold"; "required": false; }; "debounceTime": { "alias": "debounceTime"; "required": false; }; "complete": { "alias": "complete"; "required": false; }; }, { "infiniteScroll": "infiniteScroll"; }, never, never, true, never>;
19
+ }
@@ -0,0 +1,25 @@
1
+ import { NgZone } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ export declare class MatSelectInfiniteScrollService {
4
+ private ngZone;
5
+ private threshold;
6
+ private debounceTime;
7
+ private complete;
8
+ private thrPx;
9
+ private thrPc;
10
+ private destroyed$;
11
+ private selectItemHeightPx;
12
+ private panel;
13
+ constructor(ngZone: NgZone);
14
+ initialize(panel: Element, selectItemHeightPx: number, config: {
15
+ threshold: string;
16
+ debounceTime: number;
17
+ complete: boolean;
18
+ }): void;
19
+ evaluateThreshold(): void;
20
+ registerScrollListener(infiniteScrollCallback: () => void): void;
21
+ handleScrollEvent(event: Event, infiniteScrollCallback: () => void): void;
22
+ destroy(): void;
23
+ static ɵfac: i0.ɵɵFactoryDeclaration<MatSelectInfiniteScrollService, never>;
24
+ static ɵprov: i0.ɵɵInjectableDeclaration<MatSelectInfiniteScrollService>;
25
+ }
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "path": "src/assets/i18n/"
7
7
  }
8
8
  ],
9
- "version": "0.32.9",
9
+ "version": "0.33.1",
10
10
  "peerDependencies": {
11
11
  "@angular/common": "^19.1.0",
12
12
  "@angular/core": "^19.1.0",
@@ -16,6 +16,7 @@
16
16
  "@jsverse/transloco": "7.5.1",
17
17
  "@ngneat/elf": "^2.5.1",
18
18
  "@ngneat/elf-entities": "^5.0.2",
19
+ "@ngneat/elf-pagination": "^1.1.0",
19
20
  "angular-split": "^19.0.0",
20
21
  "google-libphonenumber": "^3.2.34",
21
22
  "lodash": "^4.17.21",
@@ -43,6 +44,10 @@
43
44
  "types": "./core/index.d.ts",
44
45
  "default": "./fesm2022/smallpearl-ngx-helper-core.mjs"
45
46
  },
47
+ "./entities": {
48
+ "types": "./entities/index.d.ts",
49
+ "default": "./fesm2022/smallpearl-ngx-helper-entities.mjs"
50
+ },
46
51
  "./forms": {
47
52
  "types": "./forms/index.d.ts",
48
53
  "default": "./fesm2022/smallpearl-ngx-helper-forms.mjs"
@@ -51,14 +56,6 @@
51
56
  "types": "./entity-field/index.d.ts",
52
57
  "default": "./fesm2022/smallpearl-ngx-helper-entity-field.mjs"
53
58
  },
54
- "./mat-busy-wheel": {
55
- "types": "./mat-busy-wheel/index.d.ts",
56
- "default": "./fesm2022/smallpearl-ngx-helper-mat-busy-wheel.mjs"
57
- },
58
- "./mat-context-menu": {
59
- "types": "./mat-context-menu/index.d.ts",
60
- "default": "./fesm2022/smallpearl-ngx-helper-mat-context-menu.mjs"
61
- },
62
59
  "./locale": {
63
60
  "types": "./locale/index.d.ts",
64
61
  "default": "./fesm2022/smallpearl-ngx-helper-locale.mjs"
@@ -67,6 +64,14 @@
67
64
  "types": "./hover-dropdown/index.d.ts",
68
65
  "default": "./fesm2022/smallpearl-ngx-helper-hover-dropdown.mjs"
69
66
  },
67
+ "./mat-busy-wheel": {
68
+ "types": "./mat-busy-wheel/index.d.ts",
69
+ "default": "./fesm2022/smallpearl-ngx-helper-mat-busy-wheel.mjs"
70
+ },
71
+ "./mat-context-menu": {
72
+ "types": "./mat-context-menu/index.d.ts",
73
+ "default": "./fesm2022/smallpearl-ngx-helper-mat-context-menu.mjs"
74
+ },
70
75
  "./mat-entity-crud": {
71
76
  "types": "./mat-entity-crud/index.d.ts",
72
77
  "default": "./fesm2022/smallpearl-ngx-helper-mat-entity-crud.mjs"
@@ -87,6 +92,10 @@
87
92
  "types": "./mat-select-entity/index.d.ts",
88
93
  "default": "./fesm2022/smallpearl-ngx-helper-mat-select-entity.mjs"
89
94
  },
95
+ "./mat-select-infinite-scroll": {
96
+ "types": "./mat-select-infinite-scroll/index.d.ts",
97
+ "default": "./fesm2022/smallpearl-ngx-helper-mat-select-infinite-scroll.mjs"
98
+ },
90
99
  "./mat-side-menu-layout": {
91
100
  "types": "./mat-side-menu-layout/index.d.ts",
92
101
  "default": "./fesm2022/smallpearl-ngx-helper-mat-side-menu-layout.mjs"