@smartsoft001/crud-shell-angular 2.124.0 → 2.126.0

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,57 +1,26 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, ElementRef, Component, input, Directive, computed, signal, InjectionToken, Pipe, NgModule, viewChild, viewChildren, output, ChangeDetectorRef, effect, Injector } from '@angular/core';
3
- import { NgrxStoreService, PaginationMode, BaseComponent as BaseComponent$1, PopoverService, StyleService, ButtonComponent, MenuService, FormComponent, ListComponent as ListComponent$1, SharedModule, DetailsComponent, AuthService, CreateDynamicComponent, DynamicComponentLoader, ToastService, DetailsService, DynamicContentDirective, HardwareService, ListMode, FILE_SERVICE_CONFIG } from '@smartsoft001/angular';
4
- import { toSignal } from '@angular/core/rxjs-interop';
2
+ import { Injectable, inject, ElementRef, Injector, Directive, ChangeDetectionStrategy, Component, InjectionToken, DestroyRef, signal, input, computed, Pipe, NgModule, ChangeDetectorRef, viewChild, viewChildren, output, effect } from '@angular/core';
3
+ import { toSignal, toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { map, first, filter, take } from 'rxjs/operators';
5
+ import { NgrxStoreService, PaginationMode, BaseComponent as BaseComponent$1, PopoverService, StyleService, ButtonComponent, DateEditComponent, InputFlagComponent, InputIntComponent, InputRadioComponent, InputTextComponent, MenuService, FormComponent, ListComponent as ListComponent$1, IModelLabelProvider, SharedModule, DetailsComponent, AuthService, CreateDynamicComponent, DynamicComponentLoader, ToastService, DetailsService, DynamicContentDirective, PageComponent, HardwareService, ListMode, FILE_SERVICE_CONFIG } from '@smartsoft001/angular';
6
+ import 'reflect-metadata';
5
7
  import * as i1 from '@ngrx/store';
6
8
  import { createFeatureSelector, createSelector, select as select$1 } from '@ngrx/store';
7
- import { map, first } from 'rxjs/operators';
9
+ import { HttpClient } from '@angular/common/http';
10
+ import { firstValueFrom, Subscription, BehaviorSubject, combineLatest, Subject } from 'rxjs';
8
11
  import { FieldType, getModelOptions, getModelFieldsWithOptions } from '@smartsoft001/models';
9
12
  import { TranslateService, TranslatePipe } from '@ngx-translate/core';
10
13
  import { __decorate, __metadata } from 'tslib';
14
+ import * as i1$1 from '@angular/forms';
15
+ import { UntypedFormControl, FormsModule, ReactiveFormsModule, UntypedFormGroup, UntypedFormArray } from '@angular/forms';
11
16
  import { Debounce, debounce } from 'lodash-decorators';
12
- import { FormsModule, UntypedFormControl, UntypedFormGroup, UntypedFormArray } from '@angular/forms';
13
17
  import moment from 'moment';
14
- import { GuidService, SpecificationService } from '@smartsoft001/utils';
15
- import { Subscription, firstValueFrom, BehaviorSubject, combineLatest, Subject } from 'rxjs';
16
- import { NgComponentOutlet, NgClass, CommonModule, NgTemplateOutlet, Location } from '@angular/common';
17
18
  import * as _ from 'lodash';
19
+ import { NgComponentOutlet, CommonModule, NgTemplateOutlet, Location } from '@angular/common';
18
20
  import { DynamicIoDirective, DynamicIoModule } from 'ng-dynamic-component';
19
- import { HttpClient } from '@angular/common/http';
20
- import 'reflect-metadata';
21
21
  import * as i3 from '@angular/router';
22
22
  import { Router, ActivatedRoute, RouterModule } from '@angular/router';
23
-
24
- class CrudConfig {
25
- apiUrl;
26
- entity;
27
- type;
28
- reducerFactory;
29
- baseQuery;
30
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
31
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudConfig });
32
- }
33
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudConfig, decorators: [{
34
- type: Injectable
35
- }] });
36
- class CrudFullConfig extends CrudConfig {
37
- title;
38
- details;
39
- edit;
40
- add;
41
- remove;
42
- search;
43
- export;
44
- pagination;
45
- sort;
46
- list;
47
- buttons;
48
- inputComponents;
49
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFullConfig, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
50
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFullConfig });
51
- }
52
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFullConfig, decorators: [{
53
- type: Injectable
54
- }] });
23
+ import { SpecificationService } from '@smartsoft001/utils';
55
24
 
56
25
  /*
57
26
  * Create
@@ -277,6 +246,42 @@ const deleteFailure = function (entity, id, error) {
277
246
  };
278
247
  };
279
248
 
249
+ class CrudConfig {
250
+ apiUrl;
251
+ entity;
252
+ type;
253
+ reducerFactory;
254
+ baseQuery;
255
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
256
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudConfig });
257
+ }
258
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudConfig, decorators: [{
259
+ type: Injectable
260
+ }] });
261
+ class CrudFullConfig extends CrudConfig {
262
+ title;
263
+ details;
264
+ edit;
265
+ add;
266
+ remove;
267
+ search;
268
+ export;
269
+ pagination;
270
+ sort;
271
+ list;
272
+ buttons;
273
+ inputComponents;
274
+ // Declarative tier-2 styling surface (FRA-293 Faza 2).
275
+ // Deep threading into IListOptions/IPageOptions lands in Phase 3.
276
+ cssClass;
277
+ variant;
278
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFullConfig, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
279
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFullConfig });
280
+ }
281
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFullConfig, decorators: [{
282
+ type: Injectable
283
+ }] });
284
+
280
285
  const getCrudState = (entity) => createFeatureSelector(entity);
281
286
  const getCrudSelected = (entity) => createSelector(getCrudState(entity), (state) => state.selected);
282
287
  const getCrudMultiSelected = (entity) => createSelector(getCrudState(entity), (state) => state.multiSelected);
@@ -287,108 +292,418 @@ const getCrudLoaded = (entity) => createSelector(getCrudState(entity), (state) =
287
292
  const getCrudFilter = (entity) => createSelector(getCrudState(entity), (state) => state?.filter);
288
293
  const getCrudError = (entity) => createSelector(getCrudState(entity), (state) => state?.error);
289
294
 
290
- class CrudFacade {
291
- store;
292
- config;
293
- loaded;
294
- loading;
295
- selected;
296
- multiSelected;
297
- list;
298
- filter;
299
- totalCount;
300
- links;
301
- error;
302
- constructor(store, config) {
303
- this.store = store;
304
- this.config = config;
305
- if (NgrxStoreService.store) {
306
- this.store = NgrxStoreService.store;
307
- }
308
- // Initialize all signals in the constructor (injection context)
309
- this.loaded = toSignal(this.store.pipe(select$1(getCrudLoaded(this.config.entity))));
310
- this.loading = toSignal(this.store.pipe(select$1(getCrudLoaded(this.config.entity)), map((l) => !l)));
311
- this.selected = toSignal(this.store.pipe(select$1(getCrudSelected(this.config.entity))));
312
- this.multiSelected = toSignal(this.store.pipe(select$1(getCrudMultiSelected(this.config.entity))));
313
- this.list = toSignal(this.store.pipe(select$1(getCrudList(this.config.entity))));
314
- this.filter = toSignal(this.store.pipe(select$1(getCrudFilter(this.config.entity))));
315
- this.totalCount = toSignal(this.store.pipe(select$1(getCrudTotalCount(this.config.entity))));
316
- this.links = toSignal(this.store.pipe(select$1(getCrudLinks(this.config.entity))));
317
- this.error = toSignal(this.store.pipe(select$1(getCrudError(this.config.entity))));
318
- }
295
+ // NOTE: Restored 1:1 from the legacy library (FRA-293 / GAP-01).
296
+ // This is an inert stub: both classes are empty injectables. The original
297
+ // real-time implementation never existed as live code — the constructor below
298
+ // (which would have extended ngx-socket-io's `Socket`) was always commented out.
299
+ // `NotSocketService` is the null-object fallback used when `options.socket` is off.
300
+ class SocketService {
301
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: SocketService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
302
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: SocketService });
303
+ }
304
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: SocketService, decorators: [{
305
+ type: Injectable
306
+ }] });
307
+ class NotSocketService {
308
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: NotSocketService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
309
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: NotSocketService });
310
+ }
311
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: NotSocketService, decorators: [{
312
+ type: Injectable
313
+ }] });
314
+
315
+ class CrudService {
316
+ config = inject((CrudConfig));
317
+ http = inject(HttpClient);
318
+ socket = inject((SocketService), { optional: true });
319
+ // changes(criteria: { id?: string } = {}): Observable<ItemChangedData> {
320
+ // this.socket.emit('changes', criteria);
321
+ // return this.socket.fromEvent('changes');
322
+ // }
323
+ _formatMap = {
324
+ csv: 'text/csv',
325
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
326
+ };
319
327
  create(item) {
320
- this.store.dispatch(create(this.config.entity, item));
328
+ return firstValueFrom(this.http
329
+ .post(this.config.apiUrl, item, { observe: 'response' })
330
+ .pipe(map((response) => {
331
+ const location = response.headers['Location'];
332
+ if (!location)
333
+ return null;
334
+ const array = location.split('/');
335
+ return array[array.length - 1];
336
+ })));
321
337
  }
322
338
  createMany(items, options) {
323
- this.store.dispatch(createMany(this.config.entity, { items, options }));
324
- }
325
- read(filter = {}) {
326
- let baseQuery = [];
327
- if (this.config.baseQuery) {
328
- baseQuery = this.config.baseQuery;
329
- }
330
- const fullFilter = {
331
- ...(filter ? filter : {}),
332
- query: filter && filter.query ? filter.query : baseQuery,
333
- };
334
- this.store.dispatch(read(this.config.entity, fullFilter));
335
- }
336
- clear() {
337
- this.store.dispatch(clear(this.config.entity));
339
+ return firstValueFrom(this.http.post(this.config.apiUrl + '/bulk?mode=' + options.mode, items));
338
340
  }
339
- select(id) {
340
- this.store.dispatch(select(this.config.entity, id));
341
+ getById(id) {
342
+ return firstValueFrom(this.http.get(this.config.apiUrl + '/' + id));
341
343
  }
342
- unselect() {
343
- this.store.dispatch(unselect(this.config.entity));
344
+ getList(filter = null) {
345
+ return firstValueFrom(this.http.get(this.config.apiUrl + this.getQuery(filter)));
344
346
  }
345
- multiSelect(items) {
346
- this.store.dispatch(multiSelect(this.config.entity, items));
347
+ exportList(filter = null, format) {
348
+ if (!format) {
349
+ format = 'csv';
350
+ }
351
+ if (format === 'xlsx') {
352
+ return firstValueFrom(this.http
353
+ .get(this.config.apiUrl + this.getQuery(filter), {
354
+ headers: {
355
+ 'Content-Type': this._formatMap[format],
356
+ },
357
+ observe: 'response',
358
+ reportProgress: false,
359
+ responseType: 'blob',
360
+ })
361
+ .pipe(map((res) => {
362
+ const downloadLink = document.createElement('a');
363
+ const blob = res.body;
364
+ downloadLink.href = URL.createObjectURL(blob);
365
+ downloadLink.download = 'data.' + format;
366
+ document.body.appendChild(downloadLink);
367
+ downloadLink.click();
368
+ document.body.removeChild(downloadLink);
369
+ })));
370
+ }
371
+ return firstValueFrom(this.http
372
+ .get(this.config.apiUrl + this.getQuery(filter), {
373
+ headers: {
374
+ 'Content-Type': this._formatMap[format],
375
+ },
376
+ responseType: 'text',
377
+ })
378
+ .pipe(map((res) => {
379
+ const downloadLink = document.createElement('a');
380
+ const blob = new Blob(['\ufeff', res]);
381
+ downloadLink.href = URL.createObjectURL(blob);
382
+ downloadLink.download = 'data.' + format;
383
+ document.body.appendChild(downloadLink);
384
+ downloadLink.click();
385
+ document.body.removeChild(downloadLink);
386
+ })));
347
387
  }
348
388
  update(item) {
349
- this.getStore().dispatch(update(this.config.entity, item));
350
- }
351
- export(filter = {}, format) {
352
- this.getStore().dispatch(exportList(this.config.entity, filter, format));
389
+ return firstValueFrom(this.http.put(this.config.apiUrl + '/' + item.id, item));
353
390
  }
354
391
  updatePartial(item) {
355
- this.getStore().dispatch(updatePartial(this.config.entity, item));
392
+ return firstValueFrom(this.http.patch(this.config.apiUrl + '/' + item.id, item));
356
393
  }
357
394
  updatePartialMany(items) {
358
- this.getStore().dispatch(updatePartialMany(this.config.entity, items));
395
+ const updatePromises = items.map((item) => {
396
+ return this.updatePartial(item);
397
+ });
398
+ return Promise.all(updatePromises);
359
399
  }
360
400
  delete(id) {
361
- this.getStore().dispatch(deleteItem(this.config.entity, id));
401
+ return firstValueFrom(this.http.delete(this.config.apiUrl + '/' + id));
362
402
  }
363
- getStore() {
364
- if (NgrxStoreService.store)
365
- return NgrxStoreService.store;
366
- return this.store;
403
+ getQuery(filter) {
404
+ let query = '';
405
+ if (filter && filter.searchText) {
406
+ query += '&$search=' + filter.searchText;
407
+ }
408
+ if (filter && filter.limit) {
409
+ query += `&limit=${filter.limit}&offset=${filter.offset ? filter.offset : 0}`;
410
+ }
411
+ if (filter && filter.sortBy) {
412
+ query += `&sort=${(filter.sortDesc ? '-' : '') + filter.sortBy}`;
413
+ }
414
+ if (filter && filter.query) {
415
+ filter.query.forEach((q) => {
416
+ if (q.value &&
417
+ typeof q.value === 'string' &&
418
+ q.value.match(/^-?\d+$/) &&
419
+ q.value[0] !== "'" &&
420
+ q.value[0] !== '"') {
421
+ query += '&' + q.key + q.type + `'${q.value}'`;
422
+ }
423
+ else {
424
+ query += '&' + q.key + q.type + q.value;
425
+ }
426
+ });
427
+ }
428
+ return query ? '?' + query.replace('&', '') : '';
367
429
  }
368
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFacade, deps: [{ token: i1.Store }, { token: CrudConfig }], target: i0.ɵɵFactoryTarget.Injectable });
369
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFacade });
430
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
431
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudService });
370
432
  }
371
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFacade, decorators: [{
433
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudService, decorators: [{
372
434
  type: Injectable
373
- }], ctorParameters: () => [{ type: i1.Store }, { type: CrudConfig }] });
435
+ }] });
374
436
 
375
- const initialState = {
376
- loaded: false,
377
- };
378
- const crudReducer = (state, action, entity) => {
379
- if (!state) {
380
- state = { ...initialState };
381
- }
382
- switch (action.type) {
383
- case `[${entity}] Create`:
384
- return {
385
- ...state,
386
- loaded: false,
387
- error: null,
388
- };
389
- case `[${entity}] Create Success`:
390
- return {
391
- ...state,
437
+ class CrudEffects {
438
+ actions$;
439
+ service;
440
+ config;
441
+ store;
442
+ state;
443
+ constructor(actions$, service, config, store, state) {
444
+ this.actions$ = actions$;
445
+ this.service = service;
446
+ this.config = config;
447
+ this.store = store;
448
+ this.state = state;
449
+ }
450
+ init() {
451
+ const metadata = Reflect.getMetadata(this.config.entity, CrudEffects);
452
+ if (metadata)
453
+ return;
454
+ Reflect.defineMetadata(this.config.entity, true, CrudEffects);
455
+ this.actions$.subscribe((action) => {
456
+ switch (action.type) {
457
+ case `[${this.config.entity}] Create`:
458
+ this.service
459
+ .create(action.item)
460
+ .then(() => this.store.dispatch(createSuccess(this.config.entity, action.item)))
461
+ .catch((error) => {
462
+ this.store.dispatch(createFailure(this.config.entity, action.item, error));
463
+ });
464
+ break;
465
+ case `[${this.config.entity}] Create Success`: {
466
+ const createState = this.state.getValue()[this.config.entity];
467
+ this.store.dispatch(read(this.config.entity, createState?.filter ? { ...createState.filter, offset: 0 } : null));
468
+ break;
469
+ }
470
+ case `[${this.config.entity}] Create Many`:
471
+ this.service
472
+ .createMany(action.data.items, action.data.options)
473
+ .then(() => this.store.dispatch(createManySuccess(this.config.entity, {
474
+ items: action.data.items,
475
+ options: action.data.options,
476
+ })))
477
+ .catch((error) => {
478
+ this.store.dispatch(createManyFailure(this.config.entity, {
479
+ items: action.data.items,
480
+ options: action.data.options,
481
+ }, error));
482
+ });
483
+ break;
484
+ case `[${this.config.entity}] Create Many Success`: {
485
+ const createManyState = this.state.getValue()[this.config.entity];
486
+ this.store.dispatch(read(this.config.entity, createManyState?.filter
487
+ ? { ...createManyState.filter, offset: 0 }
488
+ : null));
489
+ break;
490
+ }
491
+ case `[${this.config.entity}] Read`:
492
+ this.service
493
+ .getList(action?.filter)
494
+ .then((result) => this.store.dispatch(readSuccess(this.config.entity, action?.filter, result)))
495
+ .catch((error) => {
496
+ this.store.dispatch(readFailure(this.config.entity, action?.filter, error));
497
+ });
498
+ break;
499
+ case `[${this.config.entity}] Export`:
500
+ this.service
501
+ .exportList(action?.filter, action.format)
502
+ .then(() => this.store.dispatch(exportListSuccess(this.config.entity, action?.filter)))
503
+ .catch((error) => {
504
+ this.store.dispatch(exportListFailure(this.config.entity, action?.filter, error));
505
+ });
506
+ break;
507
+ case `[${this.config.entity}] Select`:
508
+ this.service
509
+ .getById(action.id)
510
+ .then((result) => this.store.dispatch(selectSuccess(this.config.entity, action.id, result)))
511
+ .catch((error) => {
512
+ this.store.dispatch(selectFailure(this.config.entity, action.id, error));
513
+ });
514
+ break;
515
+ case `[${this.config.entity}] Update`:
516
+ this.service
517
+ .updatePartial(action.item)
518
+ .then(() => this.store.dispatch(updateSuccess(this.config.entity, action.item)))
519
+ .catch((error) => {
520
+ this.store.dispatch(updateFailure(this.config.entity, action.item, error));
521
+ this.store.dispatch(read(this.config.entity));
522
+ });
523
+ break;
524
+ case `[${this.config.entity}] Update Success`:
525
+ this.store
526
+ .pipe(select$1(getCrudFilter(this.config.entity)), first())
527
+ .subscribe((filter) => {
528
+ this.store.dispatch(read(this.config.entity, {
529
+ ...filter,
530
+ offset: 0,
531
+ }));
532
+ this.store.dispatch(select(this.config.entity, action.item.id));
533
+ });
534
+ break;
535
+ case `[${this.config.entity}] Update partial`:
536
+ this.service
537
+ .updatePartial(action.item)
538
+ .then(() => this.store.dispatch(updatePartialSuccess(this.config.entity, action.item)))
539
+ .catch((error) => {
540
+ this.store.dispatch(updatePartialFailure(this.config.entity, action.item, error));
541
+ this.store.dispatch(read(this.config.entity));
542
+ });
543
+ break;
544
+ case `[${this.config.entity}] Update partial Success`:
545
+ this.store
546
+ .pipe(select$1(getCrudFilter(this.config.entity)), first())
547
+ .subscribe((filter) => {
548
+ this.store.dispatch(read(this.config.entity, {
549
+ ...filter,
550
+ offset: 0,
551
+ }));
552
+ this.store.dispatch(select(this.config.entity, action.item.id));
553
+ });
554
+ break;
555
+ case `[${this.config.entity}] Update partial many`:
556
+ this.service
557
+ .updatePartialMany(action.items)
558
+ .then(() => this.store.dispatch(updatePartialManySuccess(this.config.entity, action.items)))
559
+ .catch((error) => {
560
+ this.store.dispatch(updatePartialManyFailure(this.config.entity, action.items, error));
561
+ this.store.dispatch(read(this.config.entity));
562
+ });
563
+ break;
564
+ case `[${this.config.entity}] Update partial many Success`:
565
+ this.store
566
+ .pipe(select$1(getCrudFilter(this.config.entity)), first())
567
+ .subscribe((filter) => {
568
+ this.store.dispatch(read(this.config.entity, {
569
+ ...filter,
570
+ offset: 0,
571
+ }));
572
+ });
573
+ break;
574
+ case `[${this.config.entity}] Delete`:
575
+ this.service
576
+ .delete(action.id)
577
+ .then(() => this.store.dispatch(deleteSuccess(this.config.entity, action.id)))
578
+ .catch((error) => {
579
+ this.store.dispatch(deleteFailure(this.config.entity, action.id, error));
580
+ });
581
+ break;
582
+ case `[${this.config.entity}] Delete Success`:
583
+ this.store
584
+ .pipe(select$1(getCrudFilter(this.config.entity)), first())
585
+ .subscribe((filter) => {
586
+ this.store.dispatch(read(this.config.entity, {
587
+ ...filter,
588
+ offset: 0,
589
+ }));
590
+ });
591
+ break;
592
+ default:
593
+ console.warn(`No effect for action type ${action.type} in entity ${this.config.entity}`);
594
+ break;
595
+ }
596
+ });
597
+ }
598
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudEffects, deps: [{ token: i1.ActionsSubject }, { token: CrudService }, { token: CrudConfig }, { token: i1.Store }, { token: i1.State }], target: i0.ɵɵFactoryTarget.Injectable });
599
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudEffects });
600
+ }
601
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudEffects, decorators: [{
602
+ type: Injectable
603
+ }], ctorParameters: () => [{ type: i1.ActionsSubject }, { type: CrudService }, { type: CrudConfig }, { type: i1.Store }, { type: i1.State }] });
604
+
605
+ class CrudFacade {
606
+ store;
607
+ config;
608
+ loaded;
609
+ loading;
610
+ selected;
611
+ multiSelected;
612
+ list;
613
+ filter;
614
+ totalCount;
615
+ links;
616
+ error;
617
+ constructor(store, config) {
618
+ this.store = store;
619
+ this.config = config;
620
+ if (NgrxStoreService.store) {
621
+ this.store = NgrxStoreService.store;
622
+ }
623
+ // Initialize all signals in the constructor (injection context)
624
+ this.loaded = toSignal(this.store.pipe(select$1(getCrudLoaded(this.config.entity))));
625
+ this.loading = toSignal(this.store.pipe(select$1(getCrudLoaded(this.config.entity)), map((l) => !l)));
626
+ this.selected = toSignal(this.store.pipe(select$1(getCrudSelected(this.config.entity))));
627
+ this.multiSelected = toSignal(this.store.pipe(select$1(getCrudMultiSelected(this.config.entity))));
628
+ this.list = toSignal(this.store.pipe(select$1(getCrudList(this.config.entity))));
629
+ this.filter = toSignal(this.store.pipe(select$1(getCrudFilter(this.config.entity))));
630
+ this.totalCount = toSignal(this.store.pipe(select$1(getCrudTotalCount(this.config.entity))));
631
+ this.links = toSignal(this.store.pipe(select$1(getCrudLinks(this.config.entity))));
632
+ this.error = toSignal(this.store.pipe(select$1(getCrudError(this.config.entity))));
633
+ }
634
+ create(item) {
635
+ this.store.dispatch(create(this.config.entity, item));
636
+ }
637
+ createMany(items, options) {
638
+ this.store.dispatch(createMany(this.config.entity, { items, options }));
639
+ }
640
+ read(filter = {}) {
641
+ let baseQuery = [];
642
+ if (this.config.baseQuery) {
643
+ baseQuery = this.config.baseQuery;
644
+ }
645
+ const fullFilter = {
646
+ ...(filter ? filter : {}),
647
+ query: filter && filter.query ? filter.query : baseQuery,
648
+ };
649
+ this.store.dispatch(read(this.config.entity, fullFilter));
650
+ }
651
+ clear() {
652
+ this.store.dispatch(clear(this.config.entity));
653
+ }
654
+ select(id) {
655
+ this.store.dispatch(select(this.config.entity, id));
656
+ }
657
+ unselect() {
658
+ this.store.dispatch(unselect(this.config.entity));
659
+ }
660
+ multiSelect(items) {
661
+ this.store.dispatch(multiSelect(this.config.entity, items));
662
+ }
663
+ update(item) {
664
+ this.getStore().dispatch(update(this.config.entity, item));
665
+ }
666
+ export(filter = {}, format) {
667
+ this.getStore().dispatch(exportList(this.config.entity, filter, format));
668
+ }
669
+ updatePartial(item) {
670
+ this.getStore().dispatch(updatePartial(this.config.entity, item));
671
+ }
672
+ updatePartialMany(items) {
673
+ this.getStore().dispatch(updatePartialMany(this.config.entity, items));
674
+ }
675
+ delete(id) {
676
+ this.getStore().dispatch(deleteItem(this.config.entity, id));
677
+ }
678
+ getStore() {
679
+ if (NgrxStoreService.store)
680
+ return NgrxStoreService.store;
681
+ return this.store;
682
+ }
683
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFacade, deps: [{ token: i1.Store }, { token: CrudConfig }], target: i0.ɵɵFactoryTarget.Injectable });
684
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFacade });
685
+ }
686
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudFacade, decorators: [{
687
+ type: Injectable
688
+ }], ctorParameters: () => [{ type: i1.Store }, { type: CrudConfig }] });
689
+
690
+ const initialState = {
691
+ loaded: false,
692
+ };
693
+ const crudReducer = (state, action, entity) => {
694
+ if (!state) {
695
+ state = { ...initialState };
696
+ }
697
+ switch (action.type) {
698
+ case `[${entity}] Create`:
699
+ return {
700
+ ...state,
701
+ loaded: false,
702
+ error: null,
703
+ };
704
+ case `[${entity}] Create Success`:
705
+ return {
706
+ ...state,
392
707
  loaded: true,
393
708
  error: null,
394
709
  };
@@ -586,11 +901,12 @@ function getReducer(entity) {
586
901
  };
587
902
  }
588
903
 
589
- class ExportComponent extends BaseComponent$1 {
904
+ class ExportBaseComponent extends BaseComponent$1 {
590
905
  facade = inject((CrudFacade));
591
906
  popoverService = inject(PopoverService);
592
907
  styleService = inject(StyleService);
593
908
  elementRef = inject(ElementRef);
909
+ injector = inject(Injector);
594
910
  buttonExportCsvOptions = this.initButtonExportOptions('csv');
595
911
  buttonExportXlsxOptions = this.initButtonExportOptions('xlsx');
596
912
  ngOnInit() {
@@ -604,14 +920,21 @@ class ExportComponent extends BaseComponent$1 {
604
920
  offset: undefined,
605
921
  limit: undefined,
606
922
  }, format);
607
- const loading = this.facade.loading();
608
- if (loading)
609
- return;
610
- this.popoverService.close().then();
923
+ toObservable(this.facade.loaded, { injector: this.injector })
924
+ .pipe(filter((l) => !!l), take(1))
925
+ .subscribe(() => this.popoverService.close().then());
611
926
  },
612
927
  loading: this.facade.loading,
613
928
  };
614
929
  }
930
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ExportBaseComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive });
931
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.1", type: ExportBaseComponent, isStandalone: true, usesInheritance: true, ngImport: i0 });
932
+ }
933
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ExportBaseComponent, decorators: [{
934
+ type: Directive
935
+ }] });
936
+
937
+ class ExportComponent extends ExportBaseComponent {
615
938
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ExportComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
616
939
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.1", type: ExportComponent, isStandalone: true, selector: "smart-crud-export", usesInheritance: true, ngImport: i0, template: `
617
940
  <div class="smart:p-5">
@@ -630,7 +953,7 @@ class ExportComponent extends BaseComponent$1 {
630
953
  >
631
954
  </p>
632
955
  </div>
633
- `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "smart-button", inputs: ["options", "disabled", "class"] }] });
956
+ `, isInline: true, dependencies: [{ kind: "component", type: ButtonComponent, selector: "smart-button", inputs: ["options", "disabled", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
634
957
  }
635
958
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ExportComponent, decorators: [{
636
959
  type: Component,
@@ -655,17 +978,88 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
655
978
  </div>
656
979
  `,
657
980
  imports: [ButtonComponent],
981
+ changeDetection: ChangeDetectionStrategy.OnPush,
658
982
  }]
659
983
  }] });
660
984
 
985
+ /**
986
+ * @deprecated - please, use IModelPossibilitiesProvider from @smartsoft001/angular
987
+ */
988
+ class ICrudModelPossibilitiesProvider {
989
+ }
990
+ /**
991
+ * @deprecated - please, use MODEL_POSSIBILITIES_PROVIDER from @smartsoft001/angular
992
+ */
993
+ const CRUD_MODEL_POSSIBILITIES_PROVIDER = new InjectionToken('CRUD_MODEL_POSSIBILITIES_PROVIDER');
994
+
661
995
  class BaseComponent {
662
996
  facade = inject((CrudFacade));
663
997
  translateService = inject(TranslateService);
998
+ config = inject((CrudConfig));
999
+ modelPossibilitiesProvider = inject(CRUD_MODEL_POSSIBILITIES_PROVIDER, { optional: true });
1000
+ injector = inject(Injector);
1001
+ destroyRef = inject(DestroyRef);
1002
+ _model;
1003
+ /**
1004
+ * Local, mutation-free working copy of the last filter this component pushed
1005
+ * to `facade.read(...)`. The store-bound `filter()` input is frozen under
1006
+ * NgRx `strictStateImmutability`, so refresh/clear operate on a clone; the
1007
+ * read getters (`value`, `minValue`, `maxValue`, `hasValue`, …) prefer this
1008
+ * pending copy until the real store update flows back through the input.
1009
+ */
1010
+ _pendingFilter = signal(undefined, ...(ngDevMode ? [{ debugName: "_pendingFilter" }] : []));
1011
+ /** Snapshot of `filter()` that `_pendingFilter` was derived from. */
1012
+ _pendingFilterSource;
664
1013
  possibilities;
665
1014
  item = input(...(ngDevMode ? [undefined, { debugName: "item" }] : []));
666
1015
  filter = input(...(ngDevMode ? [undefined, { debugName: "filter" }] : []));
1016
+ /**
1017
+ * The filter the component should read from: the pending working copy when it
1018
+ * is still in sync with the current input, otherwise the raw input. Once the
1019
+ * store update arrives (a new `filter()` reference), the pending copy is
1020
+ * considered stale and the fresh input wins.
1021
+ */
1022
+ effectiveFilter = computed(() => {
1023
+ const source = this.filter();
1024
+ const pending = this._pendingFilter();
1025
+ if (pending && source === this._pendingFilterSource)
1026
+ return pending;
1027
+ return source;
1028
+ }, ...(ngDevMode ? [{ debugName: "effectiveFilter" }] : []));
1029
+ /**
1030
+ * OnPush-safe replacement for `@if (value)` in templates (partial GAP-19).
1031
+ * True when filter().query has a matching entry (key + type) with a
1032
+ * non-empty value.
1033
+ */
1034
+ hasValue = computed(() => {
1035
+ const filter = this.effectiveFilter();
1036
+ const item = this.item();
1037
+ if (!filter || !item || !filter.query)
1038
+ return false;
1039
+ const query = filter.query.find((q) => q.key === item.key && q.type === item.type);
1040
+ if (!query)
1041
+ return false;
1042
+ const val = query.value;
1043
+ return val !== null && val !== undefined && val !== '';
1044
+ }, ...(ngDevMode ? [{ debugName: "hasValue" }] : []));
1045
+ /**
1046
+ * OnPush-safe replacement for `@if (minValue)` in templates (partial
1047
+ * GAP-19). True when filter().query has a matching `>=` entry (range "from")
1048
+ * with a non-empty value.
1049
+ */
1050
+ hasMinValue = computed(() => this.hasQueryValue('>='), ...(ngDevMode ? [{ debugName: "hasMinValue" }] : []));
1051
+ /**
1052
+ * OnPush-safe replacement for `@if (maxValue)` in templates (partial
1053
+ * GAP-19). True when filter().query has a matching `<=` entry (range "to")
1054
+ * with a non-empty value.
1055
+ */
1056
+ hasMaxValue = computed(() => this.hasQueryValue('<='), ...(ngDevMode ? [{ debugName: "hasMaxValue" }] : []));
1057
+ /** Lazily-built model instance used to derive field options/labels. */
1058
+ get model() {
1059
+ return (this._model ??= new this.config.type());
1060
+ }
667
1061
  get value() {
668
- const filter = this.filter();
1062
+ const filter = this.effectiveFilter();
669
1063
  const item = this.item();
670
1064
  if (!filter || !item || !filter.query)
671
1065
  return null;
@@ -681,7 +1075,7 @@ class BaseComponent {
681
1075
  this.refresh(val);
682
1076
  }
683
1077
  get minValue() {
684
- const filter = this.filter();
1078
+ const filter = this.effectiveFilter();
685
1079
  const item = this.item();
686
1080
  if (!filter || !item || !filter.query)
687
1081
  return null;
@@ -697,7 +1091,7 @@ class BaseComponent {
697
1091
  this.refresh(val, '>=');
698
1092
  }
699
1093
  get maxValue() {
700
- const filter = this.filter();
1094
+ const filter = this.effectiveFilter();
701
1095
  const item = this.item();
702
1096
  if (!filter || !item || !filter.query)
703
1097
  return null;
@@ -715,8 +1109,51 @@ class BaseComponent {
715
1109
  get lang() {
716
1110
  return this.translateService.currentLang;
717
1111
  }
1112
+ /**
1113
+ * Returns a filter object safe to mutate. The store-bound `filter()` input is
1114
+ * frozen under NgRx `strictStateImmutability`, so when (any part of) the
1115
+ * source is frozen we hand back a deep-enough clone — object + query array +
1116
+ * query entries — leaving the store untouched. When the source is a plain,
1117
+ * extensible object (no immutability checks active) we keep operating on it in
1118
+ * place so consumers that hold the same reference still observe the change.
1119
+ */
1120
+ cloneFilter() {
1121
+ const source = this.effectiveFilter();
1122
+ if (!source)
1123
+ return undefined;
1124
+ if (!this.isMutationSafe(source)) {
1125
+ return {
1126
+ ...source,
1127
+ query: source.query ? source.query.map((q) => ({ ...q })) : [],
1128
+ };
1129
+ }
1130
+ return source;
1131
+ }
1132
+ /**
1133
+ * True when `filter` (and its query array + entries) can be mutated directly,
1134
+ * i.e. nothing is frozen.
1135
+ */
1136
+ isMutationSafe(filter) {
1137
+ if (Object.isFrozen(filter))
1138
+ return false;
1139
+ const query = filter.query;
1140
+ if (!query)
1141
+ return true;
1142
+ if (Object.isFrozen(query))
1143
+ return false;
1144
+ return query.every((q) => !Object.isFrozen(q));
1145
+ }
1146
+ /**
1147
+ * Records the mutated clone as the pending working copy (so read getters
1148
+ * reflect it immediately) and dispatches it through the facade.
1149
+ */
1150
+ commitFilter(filter) {
1151
+ this._pendingFilterSource = this.filter();
1152
+ this._pendingFilter.set(filter);
1153
+ this.facade.read(filter);
1154
+ }
718
1155
  refresh(val, type = null) {
719
- const filter = this.filter();
1156
+ const filter = this.cloneFilter();
720
1157
  const item = this.item();
721
1158
  if (!filter || !item)
722
1159
  return;
@@ -726,7 +1163,7 @@ class BaseComponent {
726
1163
  filter.query = [];
727
1164
  filter.offset = 0;
728
1165
  if (this.isArrayType()) {
729
- this.refreshForArray(val, type);
1166
+ this.refreshForArray(filter, val, type);
730
1167
  return;
731
1168
  }
732
1169
  let query = filter.query.find((q) => q.key === item.key && q.type === type);
@@ -737,7 +1174,7 @@ class BaseComponent {
737
1174
  filter.query.splice(index, 1);
738
1175
  }
739
1176
  }
740
- this.facade.read(filter);
1177
+ this.commitFilter(filter);
741
1178
  return;
742
1179
  }
743
1180
  if (!query) {
@@ -750,32 +1187,99 @@ class BaseComponent {
750
1187
  }
751
1188
  query.value = val;
752
1189
  query.label = item.label;
753
- this.facade.read(filter);
1190
+ this.commitFilter(filter);
1191
+ }
1192
+ /**
1193
+ * Builds the `InputOptions` consumed by shared `smart-input-*` components for
1194
+ * a single reactive control. When `withPossibilities` is true (radio/select
1195
+ * style filters) the base `possibilities` signal is mapped into the
1196
+ * `{ id, text, checked }` shape the shared inputs expect; otherwise it is
1197
+ * omitted (text/flag filters do not need possibilities).
1198
+ */
1199
+ buildInputOptions(control, withPossibilities = false) {
1200
+ const options = {
1201
+ treeLevel: 0,
1202
+ control,
1203
+ model: this.model,
1204
+ fieldKey: this.item()?.key ?? '',
1205
+ };
1206
+ if (withPossibilities) {
1207
+ const source = this.possibilities ? this.possibilities() : [];
1208
+ options.possibilities = signal(source.map(({ id, text }) => ({ id, text, checked: false })), ...(ngDevMode ? [{ debugName: "possibilities" }] : []));
1209
+ }
1210
+ return options;
1211
+ }
1212
+ /**
1213
+ * Creates a reactive control bridged to the legacy filter state for a given
1214
+ * query slot: seeded from the current query value for that slot (`null` ->
1215
+ * `value` / `item.type`, `'>='` -> `minValue`, `'<='` -> `maxValue`), and
1216
+ * wired so every change re-runs the (debounced) `refresh` for that slot.
1217
+ */
1218
+ bindControl(type = null) {
1219
+ const seed = type === '>='
1220
+ ? this.minValue
1221
+ : type === '<='
1222
+ ? this.maxValue
1223
+ : this.value;
1224
+ const control = new UntypedFormControl(seed);
1225
+ control.valueChanges
1226
+ .pipe(takeUntilDestroyed(this.destroyRef))
1227
+ .subscribe((v) => this.refresh(v, type));
1228
+ // TODO(GAP-19): re-sync control when filter() changes externally
1229
+ return control;
1230
+ }
1231
+ /**
1232
+ * Creates a reactive control bridged to the default (`value` / `item.type`)
1233
+ * query slot.
1234
+ */
1235
+ bindValueControl() {
1236
+ return this.bindControl(null);
754
1237
  }
755
1238
  clear() {
756
- const filter = this.filter();
1239
+ const filter = this.cloneFilter();
757
1240
  const item = this.item();
758
1241
  if (!filter || !item || !filter.query)
759
1242
  return;
760
1243
  filter.query = filter.query?.filter((q) => q.key !== item.key);
761
1244
  filter.offset = 0;
762
- this.facade.read(filter);
1245
+ this.commitFilter(filter);
763
1246
  }
764
1247
  ngOnInit() {
765
1248
  this.initPossibilities();
766
1249
  }
767
1250
  initPossibilities() {
768
1251
  const item = this.item();
769
- if (item?.possibilities) {
770
- this.possibilities = item.possibilities;
1252
+ if (!item)
1253
+ return;
1254
+ let possibilities = item.possibilities;
1255
+ if (this.modelPossibilitiesProvider) {
1256
+ const fromProvider = this.modelPossibilitiesProvider.get(this.config.type);
1257
+ if (fromProvider && fromProvider[item.key]) {
1258
+ possibilities = toSignal(fromProvider[item.key], {
1259
+ initialValue: [],
1260
+ injector: this.injector,
1261
+ });
1262
+ }
771
1263
  }
1264
+ if (possibilities)
1265
+ this.possibilities = possibilities;
1266
+ }
1267
+ hasQueryValue(type) {
1268
+ const filter = this.effectiveFilter();
1269
+ const item = this.item();
1270
+ if (!filter || !item || !filter.query)
1271
+ return false;
1272
+ const query = filter.query.find((q) => q.key === item.key && q.type === type);
1273
+ if (!query)
1274
+ return false;
1275
+ const val = query.value;
1276
+ return val !== null && val !== undefined && val !== '';
772
1277
  }
773
1278
  isArrayType() {
774
1279
  const item = this.item();
775
1280
  return item?.fieldType === FieldType.check;
776
1281
  }
777
- refreshForArray(vals, type) {
778
- const filter = this.filter();
1282
+ refreshForArray(filter, vals, type) {
779
1283
  const item = this.item();
780
1284
  if (!filter || !item)
781
1285
  return;
@@ -789,7 +1293,7 @@ class BaseComponent {
789
1293
  }
790
1294
  });
791
1295
  if (vals === null || vals === undefined || !vals.length) {
792
- this.facade.read(filter);
1296
+ this.commitFilter(filter);
793
1297
  return;
794
1298
  }
795
1299
  vals.forEach((val) => {
@@ -800,7 +1304,7 @@ class BaseComponent {
800
1304
  };
801
1305
  filter.query.push(query);
802
1306
  });
803
- this.facade.read(filter);
1307
+ this.commitFilter(filter);
804
1308
  }
805
1309
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: BaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
806
1310
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.1", type: BaseComponent, isStandalone: true, inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
@@ -815,101 +1319,115 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
815
1319
  type: Directive
816
1320
  }], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], refresh: [] } });
817
1321
 
1322
+ // TODO(FRA-293 GAP-19): re-sync rendered checked state when filter() changes
1323
+ // externally (the `value` getter already reads the reactive filter() signal,
1324
+ // so the `list` computed stays OnPush-safe).
818
1325
  class FilterCheckComponent extends BaseComponent {
819
1326
  list;
820
- ngAfterContentInit() {
1327
+ hasCheckedValues = computed(() => {
1328
+ const v = this.value;
1329
+ return Array.isArray(v) && v.length > 0;
1330
+ }, ...(ngDevMode ? [{ debugName: "hasCheckedValues" }] : []));
1331
+ ngOnInit() {
1332
+ super.ngOnInit();
821
1333
  this.list = computed(() => {
822
- const possibilities = this.possibilities();
1334
+ const possibilities = this.possibilities ? this.possibilities() : [];
1335
+ const v = this.value ?? [];
823
1336
  return possibilities.map((pos) => ({
824
1337
  value: pos,
825
- isCheck: this.value.some((r) => r === pos.id),
1338
+ isCheck: v.some((r) => r === pos.id),
826
1339
  }));
827
1340
  }, ...(ngDevMode ? [{ debugName: "list" }] : []));
828
1341
  }
829
1342
  onCheckChange(checked, entry) {
830
- if (checked && !this.value.some((r) => r === entry.value.id)) {
831
- this.value = [...this.value, entry.value.id];
1343
+ if (checked && !(this.value ?? []).some((r) => r === entry.value.id)) {
1344
+ this.value = [...(this.value ?? []), entry.value.id];
832
1345
  }
833
- if (!checked && this.value.some((r) => r === entry.value.id)) {
1346
+ if (!checked && (this.value ?? []).some((r) => r === entry.value.id)) {
834
1347
  this.value = this.value?.filter((r) => r !== entry.value.id);
835
1348
  }
836
1349
  }
837
1350
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterCheckComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
838
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterCheckComponent, isStandalone: true, selector: "smart-crud-filter-check", usesInheritance: true, ngImport: i0, template: `
839
- <!-- <ion-row>-->
840
- <!-- <ion-col>-->
841
- <!-- <ion-item-divider class="text-xl font-light">-->
842
- <!-- <ion-icon class="mr-6 text-2xl" slot="start" name="filter-outline"></ion-icon>-->
843
- <!-- <ion-label>-->
844
- {{ item()?.label || '' | translate }}
845
- <!-- </ion-label>-->
846
- @if (value?.length) {
847
- <!-- <ion-button-->
848
- <!-- slot="end"-->
849
- <!-- color="danger"-->
850
- <!-- (click)="refresh([])"-->
851
- <!-- class="square-button m-0 p-0 text-lg h-9 w-9"-->
852
- <!-- >-->
853
- <!-- <ion-icon class="text-2xl m-0 p-0" slot="icon-only" name="close-outline"></ion-icon>-->
854
- <!-- </ion-button>-->
855
- }
856
- <!-- </ion-item-divider>-->
857
-
858
- @for (entry of list(); track entry) {
859
- <!-- <ion-item>-->
860
- <!--<ion-label>-->{{ entry.value.text | translate
861
- }}<!--</ion-label>-->
862
- <!-- <ion-checkbox-->
863
- <!-- slot="end"-->
864
- <!-- [checked]="entry.isCheck"-->
865
- <!-- (ionChange)="onCheckChange($event.detail.checked, entry)"-->
866
- <!-- ></ion-checkbox>-->
867
- <!-- </ion-item>-->
868
- }
869
- <!-- </ion-col>-->
870
- <!-- </ion-row>-->
871
- `, isInline: true, styles: [":host{width:100%;margin-bottom:1.2rem}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }] });
1351
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterCheckComponent, isStandalone: true, selector: "smart-crud-filter-check", host: { classAttribute: "smart:block smart:w-full" }, usesInheritance: true, ngImport: i0, template: `
1352
+ <fieldset class="smart:w-full">
1353
+ <legend
1354
+ class="smart:flex smart:items-center smart:justify-between smart:text-sm smart:font-medium smart:text-gray-900"
1355
+ >
1356
+ <span>{{ item()?.label || '' | translate }}</span>
1357
+ @if (hasCheckedValues()) {
1358
+ <button
1359
+ type="button"
1360
+ (click)="refresh([])"
1361
+ aria-label="clear"
1362
+ class="smart:rounded smart:px-2 smart:py-1 smart:text-red-600 smart:hover:bg-red-50"
1363
+ >
1364
+ ×
1365
+ </button>
1366
+ }
1367
+ </legend>
1368
+ <div class="smart:mt-2 smart:space-y-2">
1369
+ @for (entry of list(); track entry.value.id) {
1370
+ <label class="smart:flex smart:items-center smart:gap-x-2">
1371
+ <input
1372
+ type="checkbox"
1373
+ [checked]="entry.isCheck"
1374
+ (change)="onCheckChange($any($event.target).checked, entry)"
1375
+ class="smart:h-4 smart:w-4 smart:rounded smart:border-gray-300 smart:text-indigo-600 smart:focus:ring-indigo-500"
1376
+ />
1377
+ <span class="smart:text-sm smart:text-gray-900">{{
1378
+ entry.value.text | translate
1379
+ }}</span>
1380
+ </label>
1381
+ }
1382
+ </div>
1383
+ </fieldset>
1384
+ `, isInline: true, dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
872
1385
  }
873
1386
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterCheckComponent, decorators: [{
874
1387
  type: Component,
875
- args: [{ selector: 'smart-crud-filter-check', template: `
876
- <!-- <ion-row>-->
877
- <!-- <ion-col>-->
878
- <!-- <ion-item-divider class="text-xl font-light">-->
879
- <!-- <ion-icon class="mr-6 text-2xl" slot="start" name="filter-outline"></ion-icon>-->
880
- <!-- <ion-label>-->
881
- {{ item()?.label || '' | translate }}
882
- <!-- </ion-label>-->
883
- @if (value?.length) {
884
- <!-- <ion-button-->
885
- <!-- slot="end"-->
886
- <!-- color="danger"-->
887
- <!-- (click)="refresh([])"-->
888
- <!-- class="square-button m-0 p-0 text-lg h-9 w-9"-->
889
- <!-- >-->
890
- <!-- <ion-icon class="text-2xl m-0 p-0" slot="icon-only" name="close-outline"></ion-icon>-->
891
- <!-- </ion-button>-->
892
- }
893
- <!-- </ion-item-divider>-->
894
-
895
- @for (entry of list(); track entry) {
896
- <!-- <ion-item>-->
897
- <!--<ion-label>-->{{ entry.value.text | translate
898
- }}<!--</ion-label>-->
899
- <!-- <ion-checkbox-->
900
- <!-- slot="end"-->
901
- <!-- [checked]="entry.isCheck"-->
902
- <!-- (ionChange)="onCheckChange($event.detail.checked, entry)"-->
903
- <!-- ></ion-checkbox>-->
904
- <!-- </ion-item>-->
905
- }
906
- <!-- </ion-col>-->
907
- <!-- </ion-row>-->
908
- `, imports: [TranslatePipe], styles: [":host{width:100%;margin-bottom:1.2rem}\n"] }]
1388
+ args: [{
1389
+ selector: 'smart-crud-filter-check',
1390
+ host: { class: 'smart:block smart:w-full' },
1391
+ template: `
1392
+ <fieldset class="smart:w-full">
1393
+ <legend
1394
+ class="smart:flex smart:items-center smart:justify-between smart:text-sm smart:font-medium smart:text-gray-900"
1395
+ >
1396
+ <span>{{ item()?.label || '' | translate }}</span>
1397
+ @if (hasCheckedValues()) {
1398
+ <button
1399
+ type="button"
1400
+ (click)="refresh([])"
1401
+ aria-label="clear"
1402
+ class="smart:rounded smart:px-2 smart:py-1 smart:text-red-600 smart:hover:bg-red-50"
1403
+ >
1404
+ ×
1405
+ </button>
1406
+ }
1407
+ </legend>
1408
+ <div class="smart:mt-2 smart:space-y-2">
1409
+ @for (entry of list(); track entry.value.id) {
1410
+ <label class="smart:flex smart:items-center smart:gap-x-2">
1411
+ <input
1412
+ type="checkbox"
1413
+ [checked]="entry.isCheck"
1414
+ (change)="onCheckChange($any($event.target).checked, entry)"
1415
+ class="smart:h-4 smart:w-4 smart:rounded smart:border-gray-300 smart:text-indigo-600 smart:focus:ring-indigo-500"
1416
+ />
1417
+ <span class="smart:text-sm smart:text-gray-900">{{
1418
+ entry.value.text | translate
1419
+ }}</span>
1420
+ </label>
1421
+ }
1422
+ </div>
1423
+ </fieldset>
1424
+ `,
1425
+ imports: [TranslatePipe],
1426
+ changeDetection: ChangeDetectionStrategy.OnPush,
1427
+ }]
909
1428
  }] });
910
1429
 
911
1430
  class FilterDateComponent extends BaseComponent {
912
- id = GuidService.create();
913
1431
  get allowAdvanced() {
914
1432
  const item = this.item();
915
1433
  return item?.type === '=';
@@ -945,150 +1463,437 @@ class FilterDateComponent extends BaseComponent {
945
1463
  return this.maxValue;
946
1464
  }
947
1465
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterDateComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
948
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterDateComponent, isStandalone: true, selector: "smart-crud-filter-date", usesInheritance: true, ngImport: i0, template: "<!--<ion-row>-->\n<!-- <ion-col>-->\n<!-- <ion-label>-->\n{{ item()?.label || '' | translate }}\n<!-- </ion-label>-->\n<!-- <ion-item button=\"true\" id=\"open-date-input-{{ id }}\" color=\"primary\">-->\n<!-- <ion-text>-->\n{{ customValue ? customValue : ('select' | translate) }}\n<!-- </ion-text>-->\n<!-- <ion-popover trigger=\"open-date-input-{{ id }}\" show-backdrop=\"false\">-->\n<ng-template>\n <!-- <ion-datetime-->\n <!-- displayFormat=\"YYYY-MM-DD\"-->\n <!-- pickerFormat=\"YYYY-MM-DD\"-->\n <!-- [(ngModel)]=\"customValue\"-->\n <!-- presentation=\"date\"-->\n <!-- ></ion-datetime>-->\n</ng-template>\n<!-- </ion-popover>-->\n<!-- </ion-item>-->\n<!-- </ion-col>-->\n\n@if (value) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button color=\"danger\" (click)=\"refresh(null)\" class=\"square-button m-3\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!--</ion-row>-->\n", styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1466
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterDateComponent, isStandalone: true, selector: "smart-crud-filter-date", host: { classAttribute: "smart:block smart:w-full" }, usesInheritance: true, ngImport: i0, template: `
1467
+ <label class="smart:mb-1 smart:block smart:text-sm smart:font-medium">
1468
+ {{ item()?.label || '' | translate }}
1469
+ </label>
1470
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1471
+ <smart-date-edit
1472
+ class="smart:flex-1"
1473
+ [ngModel]="customValue || ''"
1474
+ (ngModelChange)="customValue = $event"
1475
+ ></smart-date-edit>
1476
+ @if (hasValue()) {
1477
+ <button
1478
+ type="button"
1479
+ (click)="refresh(null)"
1480
+ aria-label="clear"
1481
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1482
+ >
1483
+ ×
1484
+ </button>
1485
+ }
1486
+ </div>
1487
+ `, isInline: true, dependencies: [{ kind: "component", type: DateEditComponent, selector: "smart-date-edit", inputs: ["variant", "class", "ngModel"], outputs: ["ngModelChange", "validChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
949
1488
  }
950
1489
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterDateComponent, decorators: [{
951
1490
  type: Component,
952
- args: [{ selector: 'smart-crud-filter-date', imports: [FormsModule, TranslatePipe], template: "<!--<ion-row>-->\n<!-- <ion-col>-->\n<!-- <ion-label>-->\n{{ item()?.label || '' | translate }}\n<!-- </ion-label>-->\n<!-- <ion-item button=\"true\" id=\"open-date-input-{{ id }}\" color=\"primary\">-->\n<!-- <ion-text>-->\n{{ customValue ? customValue : ('select' | translate) }}\n<!-- </ion-text>-->\n<!-- <ion-popover trigger=\"open-date-input-{{ id }}\" show-backdrop=\"false\">-->\n<ng-template>\n <!-- <ion-datetime-->\n <!-- displayFormat=\"YYYY-MM-DD\"-->\n <!-- pickerFormat=\"YYYY-MM-DD\"-->\n <!-- [(ngModel)]=\"customValue\"-->\n <!-- presentation=\"date\"-->\n <!-- ></ion-datetime>-->\n</ng-template>\n<!-- </ion-popover>-->\n<!-- </ion-item>-->\n<!-- </ion-col>-->\n\n@if (value) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button color=\"danger\" (click)=\"refresh(null)\" class=\"square-button m-3\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!--</ion-row>-->\n", styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"] }]
1491
+ args: [{
1492
+ selector: 'smart-crud-filter-date',
1493
+ host: { class: 'smart:block smart:w-full' },
1494
+ // TODO(FRA-293 GAP-19): re-sync picker when filter() changes externally
1495
+ template: `
1496
+ <label class="smart:mb-1 smart:block smart:text-sm smart:font-medium">
1497
+ {{ item()?.label || '' | translate }}
1498
+ </label>
1499
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1500
+ <smart-date-edit
1501
+ class="smart:flex-1"
1502
+ [ngModel]="customValue || ''"
1503
+ (ngModelChange)="customValue = $event"
1504
+ ></smart-date-edit>
1505
+ @if (hasValue()) {
1506
+ <button
1507
+ type="button"
1508
+ (click)="refresh(null)"
1509
+ aria-label="clear"
1510
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1511
+ >
1512
+ ×
1513
+ </button>
1514
+ }
1515
+ </div>
1516
+ `,
1517
+ imports: [DateEditComponent, FormsModule, TranslatePipe],
1518
+ changeDetection: ChangeDetectionStrategy.OnPush,
1519
+ }]
953
1520
  }] });
954
1521
 
955
1522
  class FilterDateTimeComponent extends FilterDateComponent {
956
- _subscriptions = new Subscription();
957
- id = GuidService.create();
958
- // @ViewChild(IonDatetime, { read: IonDatetime, static: false })
959
- // dateTimePicker: IonDatetime;
960
- ngOnInit() {
961
- super.ngOnInit();
962
- // if (this.dateTimePicker) {
963
- // this._subscriptions.add(
964
- // this.dateTimePicker.ionChange.subscribe((val: CustomEvent) => {
965
- // this.customValue = val.detail.value;
966
- // }),
967
- // );
968
- // } else {
969
- // console.error('dateTimePicker not found!');
970
- // }
1523
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterDateTimeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1524
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterDateTimeComponent, isStandalone: true, selector: "smart-crud-filter-date-time", host: { classAttribute: "smart:block smart:w-full" }, usesInheritance: true, ngImport: i0, template: `
1525
+ <label class="smart:mb-1 smart:block smart:text-sm smart:font-medium">
1526
+ {{ item()?.label || '' | translate }}
1527
+ </label>
1528
+ <div class="smart:flex smart:w-full smart:flex-col smart:gap-2">
1529
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1530
+ <label class="smart:text-sm">{{ 'from' | translate }}</label>
1531
+ <input
1532
+ type="datetime-local"
1533
+ class="smart:flex-1 smart:rounded smart:border smart:border-gray-300 smart:px-2 smart:py-1"
1534
+ [ngModel]="customMinValue || ''"
1535
+ (ngModelChange)="customMinValue = $event"
1536
+ />
1537
+ @if (hasMinValue()) {
1538
+ <button
1539
+ type="button"
1540
+ (click)="refresh(null, '>=')"
1541
+ aria-label="clear-from"
1542
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1543
+ >
1544
+ ×
1545
+ </button>
1546
+ }
1547
+ </div>
1548
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1549
+ <label class="smart:text-sm">{{ 'to' | translate }}</label>
1550
+ <input
1551
+ type="datetime-local"
1552
+ class="smart:flex-1 smart:rounded smart:border smart:border-gray-300 smart:px-2 smart:py-1"
1553
+ [ngModel]="customMaxValue || ''"
1554
+ (ngModelChange)="customMaxValue = $event"
1555
+ />
1556
+ @if (hasMaxValue()) {
1557
+ <button
1558
+ type="button"
1559
+ (click)="refresh(null, '<=')"
1560
+ aria-label="clear-to"
1561
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1562
+ >
1563
+ ×
1564
+ </button>
1565
+ }
1566
+ </div>
1567
+ </div>
1568
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1569
+ }
1570
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterDateTimeComponent, decorators: [{
1571
+ type: Component,
1572
+ args: [{
1573
+ selector: 'smart-crud-filter-date-time',
1574
+ host: { class: 'smart:block smart:w-full' },
1575
+ // TODO(FRA-293 GAP-19): re-sync picker when filter() changes externally
1576
+ template: `
1577
+ <label class="smart:mb-1 smart:block smart:text-sm smart:font-medium">
1578
+ {{ item()?.label || '' | translate }}
1579
+ </label>
1580
+ <div class="smart:flex smart:w-full smart:flex-col smart:gap-2">
1581
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1582
+ <label class="smart:text-sm">{{ 'from' | translate }}</label>
1583
+ <input
1584
+ type="datetime-local"
1585
+ class="smart:flex-1 smart:rounded smart:border smart:border-gray-300 smart:px-2 smart:py-1"
1586
+ [ngModel]="customMinValue || ''"
1587
+ (ngModelChange)="customMinValue = $event"
1588
+ />
1589
+ @if (hasMinValue()) {
1590
+ <button
1591
+ type="button"
1592
+ (click)="refresh(null, '>=')"
1593
+ aria-label="clear-from"
1594
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1595
+ >
1596
+ ×
1597
+ </button>
1598
+ }
1599
+ </div>
1600
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1601
+ <label class="smart:text-sm">{{ 'to' | translate }}</label>
1602
+ <input
1603
+ type="datetime-local"
1604
+ class="smart:flex-1 smart:rounded smart:border smart:border-gray-300 smart:px-2 smart:py-1"
1605
+ [ngModel]="customMaxValue || ''"
1606
+ (ngModelChange)="customMaxValue = $event"
1607
+ />
1608
+ @if (hasMaxValue()) {
1609
+ <button
1610
+ type="button"
1611
+ (click)="refresh(null, '<=')"
1612
+ aria-label="clear-to"
1613
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1614
+ >
1615
+ ×
1616
+ </button>
1617
+ }
1618
+ </div>
1619
+ </div>
1620
+ `,
1621
+ imports: [TranslatePipe, FormsModule],
1622
+ changeDetection: ChangeDetectionStrategy.OnPush,
1623
+ }]
1624
+ }] });
1625
+
1626
+ class FilterDateWithEditComponent extends FilterDateComponent {
1627
+ advanced = signal(false, ...(ngDevMode ? [{ debugName: "advanced" }] : []));
1628
+ toggleAdvanced() {
1629
+ this.advanced.update((val) => !val);
1630
+ if (this.advanced()) {
1631
+ this.value = null;
1632
+ }
971
1633
  }
972
- ngOnDestroy() {
973
- if (this._subscriptions) {
974
- this._subscriptions.unsubscribe();
1634
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterDateWithEditComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1635
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterDateWithEditComponent, isStandalone: true, selector: "smart-crud-filter-date-with-edit", host: { classAttribute: "smart:block smart:w-full" }, usesInheritance: true, ngImport: i0, template: `
1636
+ <label class="smart:mb-1 smart:block smart:text-sm smart:font-medium">
1637
+ {{ item()?.label || '' | translate }}
1638
+ </label>
1639
+
1640
+ @if (!advanced()) {
1641
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1642
+ <smart-date-edit
1643
+ class="smart:flex-1"
1644
+ [ngModel]="customValue || ''"
1645
+ (ngModelChange)="customValue = $event"
1646
+ ></smart-date-edit>
1647
+ @if (allowAdvanced) {
1648
+ <button
1649
+ type="button"
1650
+ (click)="toggleAdvanced()"
1651
+ aria-label="advanced"
1652
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:hover:bg-gray-100"
1653
+ >
1654
+
1655
+ </button>
1656
+ }
1657
+ @if (hasValue()) {
1658
+ <button
1659
+ type="button"
1660
+ (click)="refresh(null)"
1661
+ aria-label="clear"
1662
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1663
+ >
1664
+ ×
1665
+ </button>
975
1666
  }
1667
+ </div>
1668
+ } @else if (allowAdvanced) {
1669
+ <button
1670
+ type="button"
1671
+ (click)="toggleAdvanced()"
1672
+ aria-label="advanced"
1673
+ class="smart:mb-2 smart:rounded smart:px-2 smart:py-2 smart:hover:bg-gray-100"
1674
+ >
1675
+
1676
+ </button>
976
1677
  }
977
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterDateTimeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
978
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterDateTimeComponent, isStandalone: true, selector: "smart-crud-filter-date-time", usesInheritance: true, ngImport: i0, template: "<!--<ion-row>-->\n<!-- <ion-col>-->\n<!-- <ion-label>-->\n{{ item()?.label || '' | translate }}\n<!-- </ion-label>-->\n<!-- </ion-col>-->\n@if (value || minValue || maxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button color=\"danger\" (click)=\"clear()\" class=\"square-button m-3\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!--</ion-row>-->\n<!--<ion-row>-->\n<!-- <ion-col>-->\n<!-- <ion-item>-->\n<!-- <ion-label>-->\n{{ 'from' | translate }}\n<!-- </ion-label>-->\n<!-- <ion-input-->\n<!-- placeholder=\"YYYY-MM-DD HH:mm:ss\"-->\n<!-- [(ngModel)]=\"customMinValue\"-->\n<!-- pattern=\"^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$\"-->\n<!-- ></ion-input>-->\n<!-- </ion-item>-->\n<!-- </ion-col>-->\n@if (customMinValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '>=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!-- <ion-col>-->\n<!-- <ion-item>-->\n<!-- <ion-label>-->\n{{ 'to' | translate }}\n<!-- </ion-label>-->\n<!-- <ion-input-->\n<!-- placeholder=\"YYYY-MM-DD HH:mm:ss\"-->\n<!-- [(ngModel)]=\"customMaxValue\"-->\n<!-- pattern=\"^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$\"-->\n<!-- ></ion-input>-->\n<!-- </ion-item>-->\n<!-- </ion-col>-->\n@if (customMaxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '<=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!--</ion-row>-->\n", styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1678
+
1679
+ @if ((advanced() || hasMinValue() || hasMaxValue()) && allowAdvanced) {
1680
+ <div class="smart:flex smart:w-full smart:flex-col smart:gap-2">
1681
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1682
+ <label class="smart:text-sm">{{ 'from' | translate }}</label>
1683
+ <smart-date-edit
1684
+ class="smart:flex-1"
1685
+ [ngModel]="customMinValue || ''"
1686
+ (ngModelChange)="customMinValue = $event"
1687
+ ></smart-date-edit>
1688
+ @if (hasMinValue()) {
1689
+ <button
1690
+ type="button"
1691
+ (click)="refresh(null, '>=')"
1692
+ aria-label="clear-from"
1693
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1694
+ >
1695
+ ×
1696
+ </button>
1697
+ }
1698
+ </div>
1699
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1700
+ <label class="smart:text-sm">{{ 'to' | translate }}</label>
1701
+ <smart-date-edit
1702
+ class="smart:flex-1"
1703
+ [ngModel]="customMaxValue || ''"
1704
+ (ngModelChange)="customMaxValue = $event"
1705
+ ></smart-date-edit>
1706
+ @if (hasMaxValue()) {
1707
+ <button
1708
+ type="button"
1709
+ (click)="refresh(null, '<=')"
1710
+ aria-label="clear-to"
1711
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1712
+ >
1713
+ ×
1714
+ </button>
1715
+ }
1716
+ </div>
1717
+ </div>
1718
+ }
1719
+ `, isInline: true, dependencies: [{ kind: "component", type: DateEditComponent, selector: "smart-date-edit", inputs: ["variant", "class", "ngModel"], outputs: ["ngModelChange", "validChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
979
1720
  }
980
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterDateTimeComponent, decorators: [{
1721
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterDateWithEditComponent, decorators: [{
981
1722
  type: Component,
982
- args: [{ selector: 'smart-crud-filter-date-time', imports: [TranslatePipe, FormsModule], template: "<!--<ion-row>-->\n<!-- <ion-col>-->\n<!-- <ion-label>-->\n{{ item()?.label || '' | translate }}\n<!-- </ion-label>-->\n<!-- </ion-col>-->\n@if (value || minValue || maxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button color=\"danger\" (click)=\"clear()\" class=\"square-button m-3\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!--</ion-row>-->\n<!--<ion-row>-->\n<!-- <ion-col>-->\n<!-- <ion-item>-->\n<!-- <ion-label>-->\n{{ 'from' | translate }}\n<!-- </ion-label>-->\n<!-- <ion-input-->\n<!-- placeholder=\"YYYY-MM-DD HH:mm:ss\"-->\n<!-- [(ngModel)]=\"customMinValue\"-->\n<!-- pattern=\"^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$\"-->\n<!-- ></ion-input>-->\n<!-- </ion-item>-->\n<!-- </ion-col>-->\n@if (customMinValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '>=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!-- <ion-col>-->\n<!-- <ion-item>-->\n<!-- <ion-label>-->\n{{ 'to' | translate }}\n<!-- </ion-label>-->\n<!-- <ion-input-->\n<!-- placeholder=\"YYYY-MM-DD HH:mm:ss\"-->\n<!-- [(ngModel)]=\"customMaxValue\"-->\n<!-- pattern=\"^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$\"-->\n<!-- ></ion-input>-->\n<!-- </ion-item>-->\n<!-- </ion-col>-->\n@if (customMaxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '<=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!--</ion-row>-->\n", styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"] }]
1723
+ args: [{
1724
+ selector: 'smart-crud-filter-date-with-edit',
1725
+ host: { class: 'smart:block smart:w-full' },
1726
+ // TODO(FRA-293 GAP-19): re-sync picker when filter() changes externally
1727
+ template: `
1728
+ <label class="smart:mb-1 smart:block smart:text-sm smart:font-medium">
1729
+ {{ item()?.label || '' | translate }}
1730
+ </label>
1731
+
1732
+ @if (!advanced()) {
1733
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1734
+ <smart-date-edit
1735
+ class="smart:flex-1"
1736
+ [ngModel]="customValue || ''"
1737
+ (ngModelChange)="customValue = $event"
1738
+ ></smart-date-edit>
1739
+ @if (allowAdvanced) {
1740
+ <button
1741
+ type="button"
1742
+ (click)="toggleAdvanced()"
1743
+ aria-label="advanced"
1744
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:hover:bg-gray-100"
1745
+ >
1746
+
1747
+ </button>
1748
+ }
1749
+ @if (hasValue()) {
1750
+ <button
1751
+ type="button"
1752
+ (click)="refresh(null)"
1753
+ aria-label="clear"
1754
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1755
+ >
1756
+ ×
1757
+ </button>
1758
+ }
1759
+ </div>
1760
+ } @else if (allowAdvanced) {
1761
+ <button
1762
+ type="button"
1763
+ (click)="toggleAdvanced()"
1764
+ aria-label="advanced"
1765
+ class="smart:mb-2 smart:rounded smart:px-2 smart:py-2 smart:hover:bg-gray-100"
1766
+ >
1767
+
1768
+ </button>
1769
+ }
1770
+
1771
+ @if ((advanced() || hasMinValue() || hasMaxValue()) && allowAdvanced) {
1772
+ <div class="smart:flex smart:w-full smart:flex-col smart:gap-2">
1773
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1774
+ <label class="smart:text-sm">{{ 'from' | translate }}</label>
1775
+ <smart-date-edit
1776
+ class="smart:flex-1"
1777
+ [ngModel]="customMinValue || ''"
1778
+ (ngModelChange)="customMinValue = $event"
1779
+ ></smart-date-edit>
1780
+ @if (hasMinValue()) {
1781
+ <button
1782
+ type="button"
1783
+ (click)="refresh(null, '>=')"
1784
+ aria-label="clear-from"
1785
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1786
+ >
1787
+ ×
1788
+ </button>
1789
+ }
1790
+ </div>
1791
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1792
+ <label class="smart:text-sm">{{ 'to' | translate }}</label>
1793
+ <smart-date-edit
1794
+ class="smart:flex-1"
1795
+ [ngModel]="customMaxValue || ''"
1796
+ (ngModelChange)="customMaxValue = $event"
1797
+ ></smart-date-edit>
1798
+ @if (hasMaxValue()) {
1799
+ <button
1800
+ type="button"
1801
+ (click)="refresh(null, '<=')"
1802
+ aria-label="clear-to"
1803
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1804
+ >
1805
+ ×
1806
+ </button>
1807
+ }
1808
+ </div>
1809
+ </div>
1810
+ }
1811
+ `,
1812
+ imports: [DateEditComponent, FormsModule, TranslatePipe],
1813
+ changeDetection: ChangeDetectionStrategy.OnPush,
1814
+ }]
983
1815
  }] });
984
1816
 
985
- class FilterDateWithEditComponent extends FilterDateComponent {
986
- _subscriptions = new Subscription();
987
- advanced = signal(false, ...(ngDevMode ? [{ debugName: "advanced" }] : []));
988
- id = GuidService.create();
989
- // @ViewChild(IonDatetime, { read: IonDatetime, static: false })
990
- // dateTimePicker: IonDatetime;
991
- toggleAdvanced() {
992
- this.advanced.update((val) => !val);
993
- if (this.advanced()) {
994
- this.value = null;
995
- }
996
- }
1817
+ class FilterFlagComponent extends BaseComponent {
1818
+ inputOptions = signal(undefined, ...(ngDevMode ? [{ debugName: "inputOptions" }] : []));
1819
+ fieldOptions = signal(undefined, ...(ngDevMode ? [{ debugName: "fieldOptions" }] : []));
997
1820
  ngOnInit() {
998
1821
  super.ngOnInit();
999
- // if (this.dateTimePicker) {
1000
- // this._subscriptions.add(
1001
- // this.dateTimePicker.ionChange.subscribe((val: CustomEvent) => {
1002
- // this.customValue = val.detail.value;
1003
- // }),
1004
- // );
1005
- // } else {
1006
- // console.error('dateTimePicker not found!');
1007
- // }
1822
+ const control = this.bindValueControl();
1823
+ this.inputOptions.set(this.buildInputOptions(control));
1008
1824
  }
1009
- ngOnDestroy() {
1010
- if (this._subscriptions) {
1011
- this._subscriptions.unsubscribe();
1825
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterFlagComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1826
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterFlagComponent, isStandalone: true, selector: "smart-crud-filter-flag", host: { classAttribute: "smart:block smart:w-full" }, usesInheritance: true, ngImport: i0, template: `
1827
+ @if (inputOptions()) {
1828
+ <div class="smart:flex smart:w-full smart:items-center smart:gap-2">
1829
+ <smart-input-flag
1830
+ class="smart:flex-1"
1831
+ [options]="inputOptions()!"
1832
+ [fieldOptions]="fieldOptions()"
1833
+ ></smart-input-flag>
1834
+ @if (value === true || value === false) {
1835
+ <button
1836
+ type="button"
1837
+ (click)="refresh(null)"
1838
+ aria-label="clear"
1839
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1840
+ >
1841
+ ×
1842
+ </button>
1012
1843
  }
1844
+ </div>
1013
1845
  }
1014
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterDateWithEditComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1015
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterDateWithEditComponent, isStandalone: true, selector: "smart-crud-filter-date-with-edit", usesInheritance: true, ngImport: i0, template: "<!--<ion-row>-->\n<!-- <ion-col>-->\n<!-- <ion-label>-->\n{{ item()?.label || '' | translate }}\n<!-- </ion-label>-->\n\n@if (!advanced()) {\n <!-- <ion-input-->\n <!-- placeholder=\"YYYY-MM-DD\"-->\n <!-- [(ngModel)]=\"customValue\"-->\n <!-- [pattern]=\"'([12]\\\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\\\d|3[01]))'\"-->\n <!-- ></ion-input>-->\n}\n<!-- </ion-col>-->\n\n@if (!advanced()) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button id=\"open-date-input-{{ id }}\" class=\"square-button m-3\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"calendar-outline\"></ion-icon>-->\n <!-- <ion-popover trigger=\"open-date-input-{{ id }}\" show-backdrop=\"false\">-->\n <ng-template>\n <!-- <ion-datetime-->\n <!-- displayFormat=\"YYYY-MM-DD\"-->\n <!-- pickerFormat=\"YYYY-MM-DD\"-->\n <!-- [(ngModel)]=\"customValue\"-->\n <!-- presentation=\"date\"-->\n <!-- first-day-of-week=\"1\"-->\n <!-- locale=\"{{ lang }}\"-->\n <!-- ></ion-datetime>-->\n </ng-template>\n <!-- </ion-popover>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n@if (allowAdvanced) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- (click)=\"toggleAdvanced()\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- [disabled]=\"minValue || maxValue\"-->\n <!-- >-->\n @if (!advanced()) {\n <!-- <ion-icon slot=\"icon-only\" name=\"settings-outline\"></ion-icon>-->\n } @else {\n <!-- <ion-icon slot=\"icon-only\" name=\"settings\"></ion-icon>-->\n }\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n@if (value || minValue || maxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button color=\"danger\" (click)=\"clear()\" class=\"square-button m-3\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!--</ion-row>-->\n\n@if ((advanced() || minValue || maxValue) && allowAdvanced) {\n <!-- <ion-row>-->\n <!-- <ion-col>-->\n <!-- <ion-item>-->\n <!-- <ion-label>-->\n {{ 'from' | translate }}\n <!-- </ion-label>-->\n <!-- <ion-input-->\n <!-- placeholder=\"YYYY-MM-DD\"-->\n <!-- [(ngModel)]=\"customMinValue\"-->\n <!-- [pattern]=\"'([12]\\\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\\\d|3[01]))'\"-->\n <!-- ></ion-input>-->\n <!-- </ion-item>-->\n <!-- </ion-col>-->\n @if (customMinValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '>=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n }\n <!-- <ion-col>-->\n <!-- <ion-item>-->\n <!-- <ion-label>-->\n {{ 'to' | translate }}\n <!-- </ion-label>-->\n <!-- <ion-input-->\n <!-- placeholder=\"YYYY-MM-DD\"-->\n <!-- [(ngModel)]=\"customMaxValue\"-->\n <!-- [pattern]=\"'([12]\\\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\\\d|3[01]))'\"-->\n <!-- ></ion-input>-->\n <!-- </ion-item>-->\n <!-- </ion-col>-->\n\n @if (customMaxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '<=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n }\n <!-- </ion-row>-->\n}\n", styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1016
- }
1017
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterDateWithEditComponent, decorators: [{
1018
- type: Component,
1019
- args: [{ selector: 'smart-crud-filter-date-with-edit', imports: [TranslatePipe, FormsModule], template: "<!--<ion-row>-->\n<!-- <ion-col>-->\n<!-- <ion-label>-->\n{{ item()?.label || '' | translate }}\n<!-- </ion-label>-->\n\n@if (!advanced()) {\n <!-- <ion-input-->\n <!-- placeholder=\"YYYY-MM-DD\"-->\n <!-- [(ngModel)]=\"customValue\"-->\n <!-- [pattern]=\"'([12]\\\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\\\d|3[01]))'\"-->\n <!-- ></ion-input>-->\n}\n<!-- </ion-col>-->\n\n@if (!advanced()) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button id=\"open-date-input-{{ id }}\" class=\"square-button m-3\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"calendar-outline\"></ion-icon>-->\n <!-- <ion-popover trigger=\"open-date-input-{{ id }}\" show-backdrop=\"false\">-->\n <ng-template>\n <!-- <ion-datetime-->\n <!-- displayFormat=\"YYYY-MM-DD\"-->\n <!-- pickerFormat=\"YYYY-MM-DD\"-->\n <!-- [(ngModel)]=\"customValue\"-->\n <!-- presentation=\"date\"-->\n <!-- first-day-of-week=\"1\"-->\n <!-- locale=\"{{ lang }}\"-->\n <!-- ></ion-datetime>-->\n </ng-template>\n <!-- </ion-popover>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n@if (allowAdvanced) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- (click)=\"toggleAdvanced()\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- [disabled]=\"minValue || maxValue\"-->\n <!-- >-->\n @if (!advanced()) {\n <!-- <ion-icon slot=\"icon-only\" name=\"settings-outline\"></ion-icon>-->\n } @else {\n <!-- <ion-icon slot=\"icon-only\" name=\"settings\"></ion-icon>-->\n }\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n@if (value || minValue || maxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button color=\"danger\" (click)=\"clear()\" class=\"square-button m-3\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!--</ion-row>-->\n\n@if ((advanced() || minValue || maxValue) && allowAdvanced) {\n <!-- <ion-row>-->\n <!-- <ion-col>-->\n <!-- <ion-item>-->\n <!-- <ion-label>-->\n {{ 'from' | translate }}\n <!-- </ion-label>-->\n <!-- <ion-input-->\n <!-- placeholder=\"YYYY-MM-DD\"-->\n <!-- [(ngModel)]=\"customMinValue\"-->\n <!-- [pattern]=\"'([12]\\\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\\\d|3[01]))'\"-->\n <!-- ></ion-input>-->\n <!-- </ion-item>-->\n <!-- </ion-col>-->\n @if (customMinValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '>=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n }\n <!-- <ion-col>-->\n <!-- <ion-item>-->\n <!-- <ion-label>-->\n {{ 'to' | translate }}\n <!-- </ion-label>-->\n <!-- <ion-input-->\n <!-- placeholder=\"YYYY-MM-DD\"-->\n <!-- [(ngModel)]=\"customMaxValue\"-->\n <!-- [pattern]=\"'([12]\\\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\\\d|3[01]))'\"-->\n <!-- ></ion-input>-->\n <!-- </ion-item>-->\n <!-- </ion-col>-->\n\n @if (customMaxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '<=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n }\n <!-- </ion-row>-->\n}\n", styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"] }]
1020
- }] });
1021
-
1022
- class FilterFlagComponent extends BaseComponent {
1023
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterFlagComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1024
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterFlagComponent, isStandalone: true, selector: "smart-crud-filter-flag", usesInheritance: true, ngImport: i0, template: `
1025
- <!-- <ion-row>-->
1026
- <!-- <ion-col>-->
1027
- <div
1028
- class="flag-container w-full flex flex-row flex-nowrap justify-between pr-5"
1029
- >
1030
- <!-- <ion-label class="align-middle my-6" position="static">-->
1031
- {{ item()?.label || '' | translate }}
1032
- <!-- </ion-label>-->
1033
- <!-- <ion-checkbox [(ngModel)]="value" slot="end"></ion-checkbox>-->
1034
- </div>
1035
- <!-- </ion-col>-->
1036
- @if (typeof value === 'boolean') {
1037
- <!-- <ion-col size="auto">-->
1038
- <!-- <ion-button-->
1039
- <!-- color="danger"-->
1040
- <!-- (click)="refresh(null)"-->
1041
- <!-- class="square-button m-3"-->
1042
- <!-- >-->
1043
- <!-- <ion-icon slot="icon-only" name="close-outline"></ion-icon>-->
1044
- <!-- </ion-button>-->
1045
- <!-- </ion-col>-->
1046
- }
1047
- <!-- </ion-row>-->
1048
- `, isInline: true, styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1846
+ `, isInline: true, dependencies: [{ kind: "component", type: InputFlagComponent, selector: "smart-input-flag" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1049
1847
  }
1050
1848
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterFlagComponent, decorators: [{
1051
1849
  type: Component,
1052
- args: [{ selector: 'smart-crud-filter-flag', template: `
1053
- <!-- <ion-row>-->
1054
- <!-- <ion-col>-->
1055
- <div
1056
- class="flag-container w-full flex flex-row flex-nowrap justify-between pr-5"
1057
- >
1058
- <!-- <ion-label class="align-middle my-6" position="static">-->
1059
- {{ item()?.label || '' | translate }}
1060
- <!-- </ion-label>-->
1061
- <!-- <ion-checkbox [(ngModel)]="value" slot="end"></ion-checkbox>-->
1062
- </div>
1063
- <!-- </ion-col>-->
1064
- @if (typeof value === 'boolean') {
1065
- <!-- <ion-col size="auto">-->
1066
- <!-- <ion-button-->
1067
- <!-- color="danger"-->
1068
- <!-- (click)="refresh(null)"-->
1069
- <!-- class="square-button m-3"-->
1070
- <!-- >-->
1071
- <!-- <ion-icon slot="icon-only" name="close-outline"></ion-icon>-->
1072
- <!-- </ion-button>-->
1073
- <!-- </ion-col>-->
1074
- }
1075
- <!-- </ion-row>-->
1076
- `, imports: [TranslatePipe, FormsModule], styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"] }]
1850
+ args: [{
1851
+ selector: 'smart-crud-filter-flag',
1852
+ host: { class: 'smart:block smart:w-full' },
1853
+ template: `
1854
+ @if (inputOptions()) {
1855
+ <div class="smart:flex smart:w-full smart:items-center smart:gap-2">
1856
+ <smart-input-flag
1857
+ class="smart:flex-1"
1858
+ [options]="inputOptions()!"
1859
+ [fieldOptions]="fieldOptions()"
1860
+ ></smart-input-flag>
1861
+ @if (value === true || value === false) {
1862
+ <button
1863
+ type="button"
1864
+ (click)="refresh(null)"
1865
+ aria-label="clear"
1866
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1867
+ >
1868
+ ×
1869
+ </button>
1870
+ }
1871
+ </div>
1872
+ }
1873
+ `,
1874
+ imports: [InputFlagComponent],
1875
+ changeDetection: ChangeDetectionStrategy.OnPush,
1876
+ }]
1077
1877
  }] });
1078
1878
 
1879
+ // TODO(FRA-293 GAP-19): the advanced range controls (minControl/maxControl)
1880
+ // are not re-synced when filter() changes externally; see bindControl().
1079
1881
  class FilterIntComponent extends BaseComponent {
1080
- _subscriptions = new Subscription();
1882
+ valueOptions = signal(undefined, ...(ngDevMode ? [{ debugName: "valueOptions" }] : []));
1883
+ valueFieldOptions = signal(undefined, ...(ngDevMode ? [{ debugName: "valueFieldOptions" }] : []));
1081
1884
  advanced = signal(false, ...(ngDevMode ? [{ debugName: "advanced" }] : []));
1885
+ minControl;
1886
+ maxControl;
1082
1887
  get allowAdvanced() {
1083
1888
  return this.item()?.type === '=';
1084
1889
  }
1085
1890
  ngOnInit() {
1086
1891
  super.ngOnInit();
1087
- }
1088
- ngOnDestroy() {
1089
- if (this._subscriptions) {
1090
- this._subscriptions.unsubscribe();
1091
- }
1892
+ const valueControl = this.bindControl(null);
1893
+ this.minControl = this.bindControl('>=');
1894
+ this.maxControl = this.bindControl('<=');
1895
+ this.valueOptions.set(this.buildInputOptions(valueControl));
1896
+ this.valueFieldOptions.set(undefined);
1092
1897
  }
1093
1898
  toggleAdvanced() {
1094
1899
  this.advanced.update((val) => !val);
@@ -1096,129 +1901,319 @@ class FilterIntComponent extends BaseComponent {
1096
1901
  this.value = null;
1097
1902
  }
1098
1903
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterIntComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1099
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterIntComponent, isStandalone: true, selector: "smart-crud-filter-int", usesInheritance: true, ngImport: i0, template: "<!--<ion-row>-->\n<!-- <ion-col>-->\n<!-- <ion-label>-->\n{{ item()?.label || '' | translate }}\n<!-- </ion-label>-->\n@if (!advanced()) {\n <!-- <ion-input [(ngModel)]=\"value\" type=\"number\" step=\"1\"></ion-input>-->\n}\n<!-- </ion-col>-->\n@if (allowAdvanced) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- (click)=\"toggleAdvanced()\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- [disabled]=\"minValue || maxValue\"-->\n <!-- >-->\n @if (!advanced()) {\n <!-- <ion-icon slot=\"icon-only\" name=\"settings-outline\"></ion-icon>-->\n } @else {\n <!-- <ion-icon slot=\"icon-only\" name=\"settings\"></ion-icon>-->\n }\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n@if (value || minValue || maxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button color=\"danger\" (click)=\"clear()\" class=\"square-button m-3\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!--</ion-row>-->\n@if ((advanced() || minValue || maxValue) && allowAdvanced) {\n <!-- <ion-row>-->\n <!-- <ion-col>-->\n <!-- <ion-item>-->\n <!-- <ion-label>-->\n {{ 'from' | translate }}\n <!-- </ion-label>-->\n <!-- <ion-input [(ngModel)]=\"minValue\" type=\"number\" step=\"1\"></ion-input>-->\n <!-- </ion-item>-->\n <!-- </ion-col>-->\n @if (minValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '>=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n }\n <!-- <ion-col>-->\n <!-- <ion-item>-->\n <!-- <ion-label>-->\n {{ 'to' | translate }}\n <!-- </ion-label>-->\n <!-- <ion-input [(ngModel)]=\"maxValue\" type=\"number\" step=\"1\"></ion-input>-->\n <!-- </ion-item>-->\n <!-- </ion-col>-->\n @if (maxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '<=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n }\n <!-- </ion-row>-->\n}\n", styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1904
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterIntComponent, isStandalone: true, selector: "smart-crud-filter-int", host: { classAttribute: "smart:block smart:w-full" }, usesInheritance: true, ngImport: i0, template: `
1905
+ @if (valueOptions()) {
1906
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
1907
+ @if (!advanced()) {
1908
+ <smart-input-int
1909
+ class="smart:flex-1"
1910
+ [options]="valueOptions()!"
1911
+ [fieldOptions]="valueFieldOptions()"
1912
+ ></smart-input-int>
1913
+ }
1914
+
1915
+ @if (allowAdvanced) {
1916
+ <button
1917
+ type="button"
1918
+ (click)="toggleAdvanced()"
1919
+ [disabled]="hasMinValue() || hasMaxValue()"
1920
+ aria-label="settings"
1921
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-gray-600 smart:hover:bg-gray-100 smart:disabled:opacity-40"
1922
+ >
1923
+
1924
+ </button>
1925
+ }
1926
+
1927
+ @if (hasValue() || hasMinValue() || hasMaxValue()) {
1928
+ <button
1929
+ type="button"
1930
+ (click)="clear()"
1931
+ aria-label="clear"
1932
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1933
+ >
1934
+ ×
1935
+ </button>
1936
+ }
1937
+ </div>
1938
+
1939
+ @if ((advanced() || hasMinValue() || hasMaxValue()) && allowAdvanced) {
1940
+ <div
1941
+ class="smart:mt-2 smart:flex smart:w-full smart:items-end smart:gap-2"
1942
+ >
1943
+ <div class="smart:flex-1">
1944
+ <label
1945
+ class="smart:block smart:text-sm/6 smart:font-medium smart:text-gray-900 smart:dark:text-white"
1946
+ >
1947
+ {{ 'from' | translate }}
1948
+ </label>
1949
+ <input
1950
+ type="number"
1951
+ step="1"
1952
+ [formControl]="minControl"
1953
+ class="smart:mt-2 smart:block smart:w-full smart:rounded-md smart:bg-white smart:px-3 smart:py-1.5 smart:text-base smart:text-gray-900 smart:outline-1 -outline-offset-1 smart:outline-gray-300 smart:focus:outline-2 smart:focus:outline-offset-2 smart:focus:outline-indigo-600 smart:dark:bg-white/5 smart:dark:text-white smart:dark:outline-white/10"
1954
+ />
1955
+ </div>
1956
+ @if (hasMinValue()) {
1957
+ <button
1958
+ type="button"
1959
+ (click)="refresh(null, '>=')"
1960
+ aria-label="clear-from"
1961
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1962
+ >
1963
+ ×
1964
+ </button>
1965
+ }
1966
+
1967
+ <div class="smart:flex-1">
1968
+ <label
1969
+ class="smart:block smart:text-sm/6 smart:font-medium smart:text-gray-900 smart:dark:text-white"
1970
+ >
1971
+ {{ 'to' | translate }}
1972
+ </label>
1973
+ <input
1974
+ type="number"
1975
+ step="1"
1976
+ [formControl]="maxControl"
1977
+ class="smart:mt-2 smart:block smart:w-full smart:rounded-md smart:bg-white smart:px-3 smart:py-1.5 smart:text-base smart:text-gray-900 smart:outline-1 -outline-offset-1 smart:outline-gray-300 smart:focus:outline-2 smart:focus:outline-offset-2 smart:focus:outline-indigo-600 smart:dark:bg-white/5 smart:dark:text-white smart:dark:outline-white/10"
1978
+ />
1979
+ </div>
1980
+ @if (hasMaxValue()) {
1981
+ <button
1982
+ type="button"
1983
+ (click)="refresh(null, '<=')"
1984
+ aria-label="clear-to"
1985
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
1986
+ >
1987
+ ×
1988
+ </button>
1989
+ }
1990
+ </div>
1991
+ }
1992
+ }
1993
+ `, isInline: true, dependencies: [{ kind: "component", type: InputIntComponent, selector: "smart-input-int" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1100
1994
  }
1101
1995
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterIntComponent, decorators: [{
1102
1996
  type: Component,
1103
- args: [{ selector: 'smart-crud-filter-int', imports: [TranslatePipe, FormsModule], template: "<!--<ion-row>-->\n<!-- <ion-col>-->\n<!-- <ion-label>-->\n{{ item()?.label || '' | translate }}\n<!-- </ion-label>-->\n@if (!advanced()) {\n <!-- <ion-input [(ngModel)]=\"value\" type=\"number\" step=\"1\"></ion-input>-->\n}\n<!-- </ion-col>-->\n@if (allowAdvanced) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- (click)=\"toggleAdvanced()\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- [disabled]=\"minValue || maxValue\"-->\n <!-- >-->\n @if (!advanced()) {\n <!-- <ion-icon slot=\"icon-only\" name=\"settings-outline\"></ion-icon>-->\n } @else {\n <!-- <ion-icon slot=\"icon-only\" name=\"settings\"></ion-icon>-->\n }\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n@if (value || minValue || maxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button color=\"danger\" (click)=\"clear()\" class=\"square-button m-3\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n}\n<!--</ion-row>-->\n@if ((advanced() || minValue || maxValue) && allowAdvanced) {\n <!-- <ion-row>-->\n <!-- <ion-col>-->\n <!-- <ion-item>-->\n <!-- <ion-label>-->\n {{ 'from' | translate }}\n <!-- </ion-label>-->\n <!-- <ion-input [(ngModel)]=\"minValue\" type=\"number\" step=\"1\"></ion-input>-->\n <!-- </ion-item>-->\n <!-- </ion-col>-->\n @if (minValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '>=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n }\n <!-- <ion-col>-->\n <!-- <ion-item>-->\n <!-- <ion-label>-->\n {{ 'to' | translate }}\n <!-- </ion-label>-->\n <!-- <ion-input [(ngModel)]=\"maxValue\" type=\"number\" step=\"1\"></ion-input>-->\n <!-- </ion-item>-->\n <!-- </ion-col>-->\n @if (maxValue) {\n <!-- <ion-col size=\"auto\">-->\n <!-- <ion-button-->\n <!-- color=\"danger\"-->\n <!-- (click)=\"refresh(null, '<=')\"-->\n <!-- class=\"square-button m-3\"-->\n <!-- >-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close-outline\"></ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-col>-->\n }\n <!-- </ion-row>-->\n}\n", styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"] }]
1997
+ args: [{
1998
+ selector: 'smart-crud-filter-int',
1999
+ host: { class: 'smart:block smart:w-full' },
2000
+ template: `
2001
+ @if (valueOptions()) {
2002
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
2003
+ @if (!advanced()) {
2004
+ <smart-input-int
2005
+ class="smart:flex-1"
2006
+ [options]="valueOptions()!"
2007
+ [fieldOptions]="valueFieldOptions()"
2008
+ ></smart-input-int>
2009
+ }
2010
+
2011
+ @if (allowAdvanced) {
2012
+ <button
2013
+ type="button"
2014
+ (click)="toggleAdvanced()"
2015
+ [disabled]="hasMinValue() || hasMaxValue()"
2016
+ aria-label="settings"
2017
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-gray-600 smart:hover:bg-gray-100 smart:disabled:opacity-40"
2018
+ >
2019
+
2020
+ </button>
2021
+ }
2022
+
2023
+ @if (hasValue() || hasMinValue() || hasMaxValue()) {
2024
+ <button
2025
+ type="button"
2026
+ (click)="clear()"
2027
+ aria-label="clear"
2028
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
2029
+ >
2030
+ ×
2031
+ </button>
2032
+ }
2033
+ </div>
2034
+
2035
+ @if ((advanced() || hasMinValue() || hasMaxValue()) && allowAdvanced) {
2036
+ <div
2037
+ class="smart:mt-2 smart:flex smart:w-full smart:items-end smart:gap-2"
2038
+ >
2039
+ <div class="smart:flex-1">
2040
+ <label
2041
+ class="smart:block smart:text-sm/6 smart:font-medium smart:text-gray-900 smart:dark:text-white"
2042
+ >
2043
+ {{ 'from' | translate }}
2044
+ </label>
2045
+ <input
2046
+ type="number"
2047
+ step="1"
2048
+ [formControl]="minControl"
2049
+ class="smart:mt-2 smart:block smart:w-full smart:rounded-md smart:bg-white smart:px-3 smart:py-1.5 smart:text-base smart:text-gray-900 smart:outline-1 -outline-offset-1 smart:outline-gray-300 smart:focus:outline-2 smart:focus:outline-offset-2 smart:focus:outline-indigo-600 smart:dark:bg-white/5 smart:dark:text-white smart:dark:outline-white/10"
2050
+ />
2051
+ </div>
2052
+ @if (hasMinValue()) {
2053
+ <button
2054
+ type="button"
2055
+ (click)="refresh(null, '>=')"
2056
+ aria-label="clear-from"
2057
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
2058
+ >
2059
+ ×
2060
+ </button>
2061
+ }
2062
+
2063
+ <div class="smart:flex-1">
2064
+ <label
2065
+ class="smart:block smart:text-sm/6 smart:font-medium smart:text-gray-900 smart:dark:text-white"
2066
+ >
2067
+ {{ 'to' | translate }}
2068
+ </label>
2069
+ <input
2070
+ type="number"
2071
+ step="1"
2072
+ [formControl]="maxControl"
2073
+ class="smart:mt-2 smart:block smart:w-full smart:rounded-md smart:bg-white smart:px-3 smart:py-1.5 smart:text-base smart:text-gray-900 smart:outline-1 -outline-offset-1 smart:outline-gray-300 smart:focus:outline-2 smart:focus:outline-offset-2 smart:focus:outline-indigo-600 smart:dark:bg-white/5 smart:dark:text-white smart:dark:outline-white/10"
2074
+ />
2075
+ </div>
2076
+ @if (hasMaxValue()) {
2077
+ <button
2078
+ type="button"
2079
+ (click)="refresh(null, '<=')"
2080
+ aria-label="clear-to"
2081
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
2082
+ >
2083
+ ×
2084
+ </button>
2085
+ }
2086
+ </div>
2087
+ }
2088
+ }
2089
+ `,
2090
+ imports: [InputIntComponent, ReactiveFormsModule, TranslatePipe],
2091
+ changeDetection: ChangeDetectionStrategy.OnPush,
2092
+ }]
1104
2093
  }] });
1105
2094
 
1106
2095
  class FilterRadioComponent extends BaseComponent {
2096
+ inputOptions = signal(undefined, ...(ngDevMode ? [{ debugName: "inputOptions" }] : []));
2097
+ fieldOptions = signal(undefined, ...(ngDevMode ? [{ debugName: "fieldOptions" }] : []));
2098
+ ngOnInit() {
2099
+ super.ngOnInit();
2100
+ const control = this.bindControl(null);
2101
+ this.inputOptions.set(this.buildInputOptions(control, true));
2102
+ }
1107
2103
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterRadioComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1108
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterRadioComponent, isStandalone: true, selector: "smart-crud-filter-radio", usesInheritance: true, ngImport: i0, template: `
1109
- <!-- <ion-row>-->
1110
- <!-- <ion-col>-->
1111
- <!-- <ion-radio-group [(ngModel)]="value">-->
1112
- <!-- <ion-item-divider class="font-light text-xl">-->
1113
- <!-- <ion-icon class="mr-6 text-2xl" slot="start" name="filter-outline"></ion-icon>-->
1114
- <!-- <ion-label>-->
1115
- {{ item()?.label || '' | translate }}
1116
- <!-- </ion-label>-->
1117
- @if (value || value === false) {
1118
- <!-- <ion-button-->
1119
- <!-- slot="end"-->
1120
- <!-- color="danger"-->
1121
- <!-- (click)="refresh(null)"-->
1122
- <!-- class="square-button m-0 text-lg h-9 w-9 p-0"-->
1123
- <!-- >-->
1124
- <!-- <ion-icon class="mr-6 text-2xl m-0 p-0" slot="icon-only" name="close-outline"></ion-icon>-->
1125
- <!-- </ion-button>-->
1126
- }
1127
- <!-- </ion-item-divider>-->
1128
-
1129
- @for (item of possibilities(); track item) {
1130
- <!-- <ion-item>-->
1131
- <!-- <ion-label>{{ item.text | translate }}</ion-label>-->
1132
- <!-- <ion-radio class="mr-10" [value]="item.id"></ion-radio>-->
1133
- <!-- </ion-item>-->
1134
- }
1135
- <!-- </ion-radio-group>-->
1136
- <!-- </ion-col>-->
1137
- <!-- </ion-row>-->
1138
- `, isInline: true, styles: [":host{width:100%;margin-bottom:1.2rem}:host ion-item-divider{//--padding-end: .8rem;//--ion-background-color: linear-gradient(// 90deg,// transparent,// var(--ion-color-light) //)}:host ion-item-divider ion-icon{//--color: var(--ion-color-primary)}:host ion-item-divider ion-label{//--padding: var(--smart-button-padding-left) / 2 // var(--smart-button-padding-top) / 2 var(--smart-button-padding-right) / // 2 var(--smart-button-padding-bottom) / 2}:host ion-item-divider .square-button{//--padding-start: .2em;//--padding-end: .2em}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2104
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterRadioComponent, isStandalone: true, selector: "smart-crud-filter-radio", host: { classAttribute: "smart:mb-5 smart:block smart:w-full" }, usesInheritance: true, ngImport: i0, template: `
2105
+ @if (inputOptions()) {
2106
+ <div class="smart:flex smart:w-full smart:items-start smart:gap-2">
2107
+ <smart-input-radio
2108
+ class="smart:flex-1"
2109
+ [options]="inputOptions()!"
2110
+ [fieldOptions]="fieldOptions()"
2111
+ ></smart-input-radio>
2112
+ @if (value || value === false) {
2113
+ <button
2114
+ type="button"
2115
+ (click)="refresh(null)"
2116
+ aria-label="clear"
2117
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-1 smart:text-red-600 smart:hover:bg-red-50"
2118
+ >
2119
+ ×
2120
+ </button>
2121
+ }
2122
+ </div>
2123
+ }
2124
+ `, isInline: true, dependencies: [{ kind: "component", type: InputRadioComponent, selector: "smart-input-radio" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1139
2125
  }
1140
2126
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterRadioComponent, decorators: [{
1141
2127
  type: Component,
1142
- args: [{ selector: 'smart-crud-filter-radio', template: `
1143
- <!-- <ion-row>-->
1144
- <!-- <ion-col>-->
1145
- <!-- <ion-radio-group [(ngModel)]="value">-->
1146
- <!-- <ion-item-divider class="font-light text-xl">-->
1147
- <!-- <ion-icon class="mr-6 text-2xl" slot="start" name="filter-outline"></ion-icon>-->
1148
- <!-- <ion-label>-->
1149
- {{ item()?.label || '' | translate }}
1150
- <!-- </ion-label>-->
1151
- @if (value || value === false) {
1152
- <!-- <ion-button-->
1153
- <!-- slot="end"-->
1154
- <!-- color="danger"-->
1155
- <!-- (click)="refresh(null)"-->
1156
- <!-- class="square-button m-0 text-lg h-9 w-9 p-0"-->
1157
- <!-- >-->
1158
- <!-- <ion-icon class="mr-6 text-2xl m-0 p-0" slot="icon-only" name="close-outline"></ion-icon>-->
1159
- <!-- </ion-button>-->
1160
- }
1161
- <!-- </ion-item-divider>-->
1162
-
1163
- @for (item of possibilities(); track item) {
1164
- <!-- <ion-item>-->
1165
- <!-- <ion-label>{{ item.text | translate }}</ion-label>-->
1166
- <!-- <ion-radio class="mr-10" [value]="item.id"></ion-radio>-->
1167
- <!-- </ion-item>-->
1168
- }
1169
- <!-- </ion-radio-group>-->
1170
- <!-- </ion-col>-->
1171
- <!-- </ion-row>-->
1172
- `, imports: [TranslatePipe, FormsModule], styles: [":host{width:100%;margin-bottom:1.2rem}:host ion-item-divider{//--padding-end: .8rem;//--ion-background-color: linear-gradient(// 90deg,// transparent,// var(--ion-color-light) //)}:host ion-item-divider ion-icon{//--color: var(--ion-color-primary)}:host ion-item-divider ion-label{//--padding: var(--smart-button-padding-left) / 2 // var(--smart-button-padding-top) / 2 var(--smart-button-padding-right) / // 2 var(--smart-button-padding-bottom) / 2}:host ion-item-divider .square-button{//--padding-start: .2em;//--padding-end: .2em}\n"] }]
2128
+ args: [{
2129
+ selector: 'smart-crud-filter-radio',
2130
+ host: { class: 'smart:mb-5 smart:block smart:w-full' },
2131
+ template: `
2132
+ @if (inputOptions()) {
2133
+ <div class="smart:flex smart:w-full smart:items-start smart:gap-2">
2134
+ <smart-input-radio
2135
+ class="smart:flex-1"
2136
+ [options]="inputOptions()!"
2137
+ [fieldOptions]="fieldOptions()"
2138
+ ></smart-input-radio>
2139
+ @if (value || value === false) {
2140
+ <button
2141
+ type="button"
2142
+ (click)="refresh(null)"
2143
+ aria-label="clear"
2144
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-1 smart:text-red-600 smart:hover:bg-red-50"
2145
+ >
2146
+ ×
2147
+ </button>
2148
+ }
2149
+ </div>
2150
+ }
2151
+ `,
2152
+ imports: [InputRadioComponent],
2153
+ changeDetection: ChangeDetectionStrategy.OnPush,
2154
+ }]
1173
2155
  }] });
1174
2156
 
1175
2157
  class FilterTextComponent extends BaseComponent {
2158
+ inputOptions = signal(undefined, ...(ngDevMode ? [{ debugName: "inputOptions" }] : []));
2159
+ fieldOptions = signal(undefined, ...(ngDevMode ? [{ debugName: "fieldOptions" }] : []));
2160
+ ngOnInit() {
2161
+ super.ngOnInit();
2162
+ const control = this.bindValueControl();
2163
+ this.inputOptions.set(this.buildInputOptions(control));
2164
+ }
1176
2165
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterTextComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1177
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterTextComponent, isStandalone: true, selector: "smart-crud-filter-text", usesInheritance: true, ngImport: i0, template: `
1178
- <!-- <ion-row>-->
1179
- <!-- <ion-col>-->
1180
- <!-- <ion-label position="floating">-->
1181
- {{ item()?.label || '' | translate }}
1182
- <!-- </ion-label>-->
1183
- <!-- <ion-input [(ngModel)]="value"></ion-input>-->
1184
- <!-- </ion-col>-->
1185
- @if (value) {
1186
- <!-- <ion-col size="auto">-->
1187
- <!-- <ion-button-->
1188
- <!-- color="danger"-->
1189
- <!-- (click)="refresh(null)"-->
1190
- <!-- class="square-button m-3"-->
1191
- <!-- >-->
1192
- <!-- <ion-icon slot="icon-only" name="close-outline"></ion-icon>-->
1193
- <!-- </ion-button>-->
1194
- <!-- </ion-col>-->
1195
- }
1196
- <!-- </ion-row>-->
1197
- `, isInline: true, styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2166
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterTextComponent, isStandalone: true, selector: "smart-crud-filter-text", host: { classAttribute: "smart:block smart:w-full" }, usesInheritance: true, ngImport: i0, template: `
2167
+ @if (inputOptions()) {
2168
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
2169
+ <smart-input-text
2170
+ class="smart:flex-1"
2171
+ [options]="inputOptions()!"
2172
+ [fieldOptions]="fieldOptions()"
2173
+ ></smart-input-text>
2174
+ @if (hasValue()) {
2175
+ <button
2176
+ type="button"
2177
+ (click)="refresh(null)"
2178
+ aria-label="clear"
2179
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
2180
+ >
2181
+ ×
2182
+ </button>
2183
+ }
2184
+ </div>
2185
+ }
2186
+ `, isInline: true, dependencies: [{ kind: "component", type: InputTextComponent, selector: "smart-input-text" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1198
2187
  }
1199
2188
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterTextComponent, decorators: [{
1200
2189
  type: Component,
1201
- args: [{ selector: 'smart-crud-filter-text', template: `
1202
- <!-- <ion-row>-->
1203
- <!-- <ion-col>-->
1204
- <!-- <ion-label position="floating">-->
1205
- {{ item()?.label || '' | translate }}
1206
- <!-- </ion-label>-->
1207
- <!-- <ion-input [(ngModel)]="value"></ion-input>-->
1208
- <!-- </ion-col>-->
1209
- @if (value) {
1210
- <!-- <ion-col size="auto">-->
1211
- <!-- <ion-button-->
1212
- <!-- color="danger"-->
1213
- <!-- (click)="refresh(null)"-->
1214
- <!-- class="square-button m-3"-->
1215
- <!-- >-->
1216
- <!-- <ion-icon slot="icon-only" name="close-outline"></ion-icon>-->
1217
- <!-- </ion-button>-->
1218
- <!-- </ion-col>-->
1219
- }
1220
- <!-- </ion-row>-->
1221
- `, imports: [TranslatePipe, FormsModule], styles: [":host{width:100%}:host .square-button{height:var(--smart-button-height)!important}\n"] }]
2190
+ args: [{
2191
+ selector: 'smart-crud-filter-text',
2192
+ host: { class: 'smart:block smart:w-full' },
2193
+ template: `
2194
+ @if (inputOptions()) {
2195
+ <div class="smart:flex smart:w-full smart:items-end smart:gap-2">
2196
+ <smart-input-text
2197
+ class="smart:flex-1"
2198
+ [options]="inputOptions()!"
2199
+ [fieldOptions]="fieldOptions()"
2200
+ ></smart-input-text>
2201
+ @if (hasValue()) {
2202
+ <button
2203
+ type="button"
2204
+ (click)="refresh(null)"
2205
+ aria-label="clear"
2206
+ class="smart:shrink-0 smart:rounded smart:px-2 smart:py-2 smart:text-red-600 smart:hover:bg-red-50"
2207
+ >
2208
+ ×
2209
+ </button>
2210
+ }
2211
+ </div>
2212
+ }
2213
+ `,
2214
+ imports: [InputTextComponent],
2215
+ changeDetection: ChangeDetectionStrategy.OnPush,
2216
+ }]
1222
2217
  }] });
1223
2218
 
1224
2219
  class FilterComponent {
@@ -1226,7 +2221,7 @@ class FilterComponent {
1226
2221
  item = input(...(ngDevMode ? [undefined, { debugName: "item" }] : []));
1227
2222
  filter = input(...(ngDevMode ? [undefined, { debugName: "filter" }] : []));
1228
2223
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1229
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterComponent, isStandalone: true, selector: "smart-crud-filter", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--<ion-item>-->\n@switch (item()?.fieldType) {\n @case (FieldType.date) {\n <smart-crud-filter-date\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date>\n }\n @case (FieldType.dateWithEdit) {\n <smart-crud-filter-date-with-edit\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date-with-edit>\n }\n @case (FieldType.dateTime) {\n <smart-crud-filter-date-time\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date-time>\n }\n @case (FieldType.radio) {\n <smart-crud-filter-radio\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-radio>\n }\n @case (FieldType.check) {\n <smart-crud-filter-check\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-check>\n }\n @case (FieldType.flag) {\n <smart-crud-filter-flag\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-flag>\n }\n @case (FieldType.int) {\n <smart-crud-filter-int\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-int>\n }\n @default {\n <smart-crud-filter-text\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-text>\n }\n}\n<!--</ion-item>-->\n", dependencies: [{ kind: "component", type: FilterTextComponent, selector: "smart-crud-filter-text" }, { kind: "component", type: FilterIntComponent, selector: "smart-crud-filter-int" }, { kind: "component", type: FilterFlagComponent, selector: "smart-crud-filter-flag" }, { kind: "component", type: FilterCheckComponent, selector: "smart-crud-filter-check" }, { kind: "component", type: FilterRadioComponent, selector: "smart-crud-filter-radio" }, { kind: "component", type: FilterDateTimeComponent, selector: "smart-crud-filter-date-time" }, { kind: "component", type: FilterDateWithEditComponent, selector: "smart-crud-filter-date-with-edit" }, { kind: "component", type: FilterDateComponent, selector: "smart-crud-filter-date" }] });
2224
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FilterComponent, isStandalone: true, selector: "smart-crud-filter", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"smart:py-1\">\n @switch (item()?.fieldType) {\n @case (FieldType.date) {\n <smart-crud-filter-date\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date>\n }\n @case (FieldType.dateWithEdit) {\n <smart-crud-filter-date-with-edit\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date-with-edit>\n }\n @case (FieldType.dateTime) {\n <smart-crud-filter-date-time\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date-time>\n }\n @case (FieldType.radio) {\n <smart-crud-filter-radio\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-radio>\n }\n @case (FieldType.check) {\n <smart-crud-filter-check\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-check>\n }\n @case (FieldType.flag) {\n <smart-crud-filter-flag\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-flag>\n }\n @case (FieldType.int) {\n <smart-crud-filter-int\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-int>\n }\n @default {\n <smart-crud-filter-text\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-text>\n }\n }\n</div>\n", dependencies: [{ kind: "component", type: FilterTextComponent, selector: "smart-crud-filter-text" }, { kind: "component", type: FilterIntComponent, selector: "smart-crud-filter-int" }, { kind: "component", type: FilterFlagComponent, selector: "smart-crud-filter-flag" }, { kind: "component", type: FilterCheckComponent, selector: "smart-crud-filter-check" }, { kind: "component", type: FilterRadioComponent, selector: "smart-crud-filter-radio" }, { kind: "component", type: FilterDateTimeComponent, selector: "smart-crud-filter-date-time" }, { kind: "component", type: FilterDateWithEditComponent, selector: "smart-crud-filter-date-with-edit" }, { kind: "component", type: FilterDateComponent, selector: "smart-crud-filter-date" }] });
1230
2225
  }
1231
2226
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FilterComponent, decorators: [{
1232
2227
  type: Component,
@@ -1239,9 +2234,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
1239
2234
  FilterDateTimeComponent,
1240
2235
  FilterDateWithEditComponent,
1241
2236
  FilterDateComponent,
1242
- ], template: "<!--<ion-item>-->\n@switch (item()?.fieldType) {\n @case (FieldType.date) {\n <smart-crud-filter-date\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date>\n }\n @case (FieldType.dateWithEdit) {\n <smart-crud-filter-date-with-edit\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date-with-edit>\n }\n @case (FieldType.dateTime) {\n <smart-crud-filter-date-time\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date-time>\n }\n @case (FieldType.radio) {\n <smart-crud-filter-radio\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-radio>\n }\n @case (FieldType.check) {\n <smart-crud-filter-check\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-check>\n }\n @case (FieldType.flag) {\n <smart-crud-filter-flag\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-flag>\n }\n @case (FieldType.int) {\n <smart-crud-filter-int\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-int>\n }\n @default {\n <smart-crud-filter-text\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-text>\n }\n}\n<!--</ion-item>-->\n" }]
2237
+ ], template: "<div class=\"smart:py-1\">\n @switch (item()?.fieldType) {\n @case (FieldType.date) {\n <smart-crud-filter-date\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date>\n }\n @case (FieldType.dateWithEdit) {\n <smart-crud-filter-date-with-edit\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date-with-edit>\n }\n @case (FieldType.dateTime) {\n <smart-crud-filter-date-time\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-date-time>\n }\n @case (FieldType.radio) {\n <smart-crud-filter-radio\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-radio>\n }\n @case (FieldType.check) {\n <smart-crud-filter-check\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-check>\n }\n @case (FieldType.flag) {\n <smart-crud-filter-flag\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-flag>\n }\n @case (FieldType.int) {\n <smart-crud-filter-int\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-int>\n }\n @default {\n <smart-crud-filter-text\n [item]=\"item()\"\n [filter]=\"filter()\"\n ></smart-crud-filter-text>\n }\n }\n</div>\n" }]
1243
2238
  }], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }] } });
1244
2239
 
2240
+ // Rendered by an OnPush concrete (FiltersComponent). The base exposes signals
2241
+ // (`list()`, `filter()`) and is therefore OnPush-compatible.
2242
+ class FiltersBaseComponent {
2243
+ menuService = inject(MenuService);
2244
+ config = inject((CrudConfig));
2245
+ facade = inject((CrudFacade));
2246
+ styleService = inject(StyleService);
2247
+ elementRef = inject(ElementRef);
2248
+ list;
2249
+ filter;
2250
+ /**
2251
+ * Hide menu used only from MenuService
2252
+ */
2253
+ hideMenu = input(false, ...(ngDevMode ? [{ debugName: "hideMenu" }] : []));
2254
+ async onClose() {
2255
+ await this.menuService.closeEnd();
2256
+ }
2257
+ ngOnInit() {
2258
+ this.styleService.init(this.elementRef);
2259
+ this.list = signal([], ...(ngDevMode ? [{ debugName: "list" }] : []));
2260
+ const modelFilters = getModelOptions(this.config.type)?.filters;
2261
+ this.list.set([
2262
+ ...(modelFilters
2263
+ ? modelFilters.map((item) => {
2264
+ if (!item.label) {
2265
+ item.label = 'MODEL.' + item.key;
2266
+ }
2267
+ return item;
2268
+ })
2269
+ : []),
2270
+ ...getModelFieldsWithOptions(new this.config.type())
2271
+ .filter((item) => item.options?.list?.filter)
2272
+ .map((item) => ({
2273
+ key: item.key,
2274
+ type: item.options.type === FieldType.text ||
2275
+ item.options.type === FieldType.longText
2276
+ ? '~='
2277
+ : '=',
2278
+ label: 'MODEL.' + item.key,
2279
+ fieldType: item.options.type,
2280
+ })),
2281
+ ]);
2282
+ this.filter = this.facade.filter;
2283
+ }
2284
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FiltersBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2285
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.1", type: FiltersBaseComponent, isStandalone: true, inputs: { hideMenu: { classPropertyName: "hideMenu", publicName: "hideMenu", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
2286
+ }
2287
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FiltersBaseComponent, decorators: [{
2288
+ type: Directive
2289
+ }], propDecorators: { hideMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideMenu", required: false }] }] } });
2290
+
1245
2291
  /**
1246
2292
  * This component is only to use in crud module
1247
2293
  * @requires CrudModule
@@ -1297,97 +2343,79 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
1297
2343
  })
1298
2344
  body: string;
1299
2345
  */
1300
- class FiltersComponent {
1301
- menuService = inject(MenuService);
1302
- config = inject((CrudConfig));
1303
- facade = inject((CrudFacade));
1304
- styleService = inject(StyleService);
1305
- elementRef = inject(ElementRef);
1306
- list;
1307
- filter;
1308
- /**
1309
- * Hide menu used only from MenuService
1310
- */
1311
- hideMenu = input(false, ...(ngDevMode ? [{ debugName: "hideMenu" }] : []));
1312
- async onClose() {
1313
- await this.menuService.closeEnd();
1314
- }
1315
- ngOnInit() {
1316
- this.styleService.init(this.elementRef);
1317
- this.list = signal([], ...(ngDevMode ? [{ debugName: "list" }] : []));
1318
- const modelFilters = getModelOptions(this.config.type)?.filters;
1319
- this.list.set([
1320
- ...(modelFilters
1321
- ? modelFilters.map((item) => {
1322
- if (!item.label) {
1323
- item.label = 'MODEL.' + item.key;
1324
- }
1325
- return item;
1326
- })
1327
- : []),
1328
- ...getModelFieldsWithOptions(new this.config.type())
1329
- .filter((item) => item.options?.list?.filter)
1330
- .map((item) => ({
1331
- key: item.key,
1332
- type: item.options.type === FieldType.text ||
1333
- item.options.type === FieldType.longText
1334
- ? '~='
1335
- : '=',
1336
- label: 'MODEL.' + item.key,
1337
- fieldType: item.options.type,
1338
- })),
1339
- ]);
1340
- this.filter = this.facade.filter;
1341
- }
1342
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1343
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FiltersComponent, isStandalone: true, selector: "smart-crud-filters", inputs: { hideMenu: { classPropertyName: "hideMenu", publicName: "hideMenu", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1344
- <!--<ion-header [hidden]="hideMenu()">-->
1345
- <!-- <ion-toolbar>-->
1346
- <!-- <ion-buttons slot="end">-->
1347
- <!-- <ion-button (click)="onClose()">-->
1348
- <!-- <ion-icon slot="icon-only" name="close"> </ion-icon>-->
1349
- <!-- </ion-button>-->
1350
- <!-- </ion-buttons>-->
1351
- <!-- <ion-title>{{ 'filters' | translate }}</ion-title>-->
1352
- <!-- </ion-toolbar>-->
1353
- <!--</ion-header>-->
1354
- <!--<ion-content style="height: 100vh">-->
1355
- <!-- <ion-list>-->
1356
- @for (item of list(); track item) {
1357
- <smart-crud-filter [item]="item" [filter]="filter()"></smart-crud-filter>
1358
- }
1359
- <!-- </ion-list>-->
1360
- <div class="h-20">&nbsp;</div>
1361
- <!--</ion-content>-->
1362
- `, isInline: true, dependencies: [{ kind: "component", type: FilterComponent, selector: "smart-crud-filter", inputs: ["item", "filter"] }] });
2346
+ class FiltersComponent extends FiltersBaseComponent {
2347
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FiltersComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2348
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FiltersComponent, isStandalone: true, selector: "smart-crud-filters", usesInheritance: true, ngImport: i0, template: `
2349
+ <div class="smart:flex smart:h-full smart:flex-col">
2350
+ @if (!hideMenu()) {
2351
+ <header
2352
+ class="smart:flex smart:items-center smart:justify-between smart:border-b smart:border-gray-200 smart:px-4 smart:py-3"
2353
+ >
2354
+ <h2 class="smart:text-lg smart:font-semibold smart:text-gray-900">
2355
+ {{ 'filters' | translate }}
2356
+ </h2>
2357
+ <button
2358
+ type="button"
2359
+ (click)="onClose()"
2360
+ aria-label="close"
2361
+ class="smart:rounded smart:p-1 smart:text-gray-500 smart:hover:bg-gray-100"
2362
+ >
2363
+
2364
+ </button>
2365
+ </header>
2366
+ }
2367
+ <div
2368
+ class="smart:flex-1 smart:space-y-4 smart:overflow-y-auto smart:px-4 smart:py-3"
2369
+ >
2370
+ @for (item of list(); track item.key) {
2371
+ <smart-crud-filter
2372
+ [item]="item"
2373
+ [filter]="filter()"
2374
+ ></smart-crud-filter>
2375
+ }
2376
+ </div>
2377
+ </div>
2378
+ `, isInline: true, dependencies: [{ kind: "component", type: FilterComponent, selector: "smart-crud-filter", inputs: ["item", "filter"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1363
2379
  }
1364
2380
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FiltersComponent, decorators: [{
1365
2381
  type: Component,
1366
2382
  args: [{
1367
2383
  selector: 'smart-crud-filters',
2384
+ changeDetection: ChangeDetectionStrategy.OnPush,
1368
2385
  template: `
1369
- <!--<ion-header [hidden]="hideMenu()">-->
1370
- <!-- <ion-toolbar>-->
1371
- <!-- <ion-buttons slot="end">-->
1372
- <!-- <ion-button (click)="onClose()">-->
1373
- <!-- <ion-icon slot="icon-only" name="close"> </ion-icon>-->
1374
- <!-- </ion-button>-->
1375
- <!-- </ion-buttons>-->
1376
- <!-- <ion-title>{{ 'filters' | translate }}</ion-title>-->
1377
- <!-- </ion-toolbar>-->
1378
- <!--</ion-header>-->
1379
- <!--<ion-content style="height: 100vh">-->
1380
- <!-- <ion-list>-->
1381
- @for (item of list(); track item) {
1382
- <smart-crud-filter [item]="item" [filter]="filter()"></smart-crud-filter>
1383
- }
1384
- <!-- </ion-list>-->
1385
- <div class="h-20">&nbsp;</div>
1386
- <!--</ion-content>-->
2386
+ <div class="smart:flex smart:h-full smart:flex-col">
2387
+ @if (!hideMenu()) {
2388
+ <header
2389
+ class="smart:flex smart:items-center smart:justify-between smart:border-b smart:border-gray-200 smart:px-4 smart:py-3"
2390
+ >
2391
+ <h2 class="smart:text-lg smart:font-semibold smart:text-gray-900">
2392
+ {{ 'filters' | translate }}
2393
+ </h2>
2394
+ <button
2395
+ type="button"
2396
+ (click)="onClose()"
2397
+ aria-label="close"
2398
+ class="smart:rounded smart:p-1 smart:text-gray-500 smart:hover:bg-gray-100"
2399
+ >
2400
+
2401
+ </button>
2402
+ </header>
2403
+ }
2404
+ <div
2405
+ class="smart:flex-1 smart:space-y-4 smart:overflow-y-auto smart:px-4 smart:py-3"
2406
+ >
2407
+ @for (item of list(); track item.key) {
2408
+ <smart-crud-filter
2409
+ [item]="item"
2410
+ [filter]="filter()"
2411
+ ></smart-crud-filter>
2412
+ }
2413
+ </div>
2414
+ </div>
1387
2415
  `,
1388
- imports: [FilterComponent],
2416
+ imports: [FilterComponent, TranslatePipe],
1389
2417
  }]
1390
- }], propDecorators: { hideMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideMenu", required: false }] }] } });
2418
+ }] });
1391
2419
 
1392
2420
  class FiltersConfigComponent {
1393
2421
  facade = inject((CrudFacade));
@@ -1414,55 +2442,121 @@ class FiltersConfigComponent {
1414
2442
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FiltersConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1415
2443
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: FiltersConfigComponent, isStandalone: true, selector: "smart-crud-filters-config", ngImport: i0, template: `
1416
2444
  @let query = this.query();
1417
- @if (query && query?.length) {
1418
- <!-- <ion-toolbar>-->
1419
- @for (item of query; track item) {
1420
- <!-- <ion-chip (click)="onRemoveQuery(item)" color="medium" outline="true">-->
1421
- <!-- <ion-icon name="filter-circle-outline" color="primary"></ion-icon>-->
1422
- <!-- <ion-label>-->
1423
- {{ 'MODEL.' + item.key | translate }} {{ item.type }}
1424
- {{ item.value | translate }}
1425
- <!-- </ion-label>-->
1426
- <!-- <ion-icon name="close" color="danger"></ion-icon>-->
1427
- <!-- </ion-chip>-->
1428
- }
1429
- <!-- </ion-toolbar>-->
2445
+ @if (query && query.length) {
2446
+ <div class="smart:flex smart:flex-wrap smart:gap-2 smart:px-4 smart:py-2">
2447
+ @for (item of query; track item) {
2448
+ <button
2449
+ type="button"
2450
+ (click)="onRemoveQuery(item)"
2451
+ class="smart:inline-flex smart:items-center smart:gap-1 smart:rounded-full smart:border smart:border-gray-300 smart:bg-gray-50 smart:px-3 smart:py-1 smart:text-sm smart:text-gray-700 smart:hover:bg-gray-100"
2452
+ [attr.aria-label]="
2453
+ ('remove' | translate) + ' ' + ('MODEL.' + item.key | translate)
2454
+ "
2455
+ >
2456
+ <span
2457
+ >{{ 'MODEL.' + item.key | translate }} {{ item.type }}
2458
+ {{ item.value | translate }}</span
2459
+ >
2460
+ <span aria-hidden="true" class="smart:text-red-600">✕</span>
2461
+ </button>
2462
+ }
2463
+ </div>
1430
2464
  }
1431
- `, isInline: true, styles: [":host ion-label{font-size:var(--small-font-size)}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }] });
2465
+ `, isInline: true, dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1432
2466
  }
1433
2467
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: FiltersConfigComponent, decorators: [{
1434
2468
  type: Component,
1435
- args: [{ selector: 'smart-crud-filters-config', template: `
2469
+ args: [{
2470
+ selector: 'smart-crud-filters-config',
2471
+ template: `
1436
2472
  @let query = this.query();
1437
- @if (query && query?.length) {
1438
- <!-- <ion-toolbar>-->
1439
- @for (item of query; track item) {
1440
- <!-- <ion-chip (click)="onRemoveQuery(item)" color="medium" outline="true">-->
1441
- <!-- <ion-icon name="filter-circle-outline" color="primary"></ion-icon>-->
1442
- <!-- <ion-label>-->
1443
- {{ 'MODEL.' + item.key | translate }} {{ item.type }}
1444
- {{ item.value | translate }}
1445
- <!-- </ion-label>-->
1446
- <!-- <ion-icon name="close" color="danger"></ion-icon>-->
1447
- <!-- </ion-chip>-->
1448
- }
1449
- <!-- </ion-toolbar>-->
2473
+ @if (query && query.length) {
2474
+ <div class="smart:flex smart:flex-wrap smart:gap-2 smart:px-4 smart:py-2">
2475
+ @for (item of query; track item) {
2476
+ <button
2477
+ type="button"
2478
+ (click)="onRemoveQuery(item)"
2479
+ class="smart:inline-flex smart:items-center smart:gap-1 smart:rounded-full smart:border smart:border-gray-300 smart:bg-gray-50 smart:px-3 smart:py-1 smart:text-sm smart:text-gray-700 smart:hover:bg-gray-100"
2480
+ [attr.aria-label]="
2481
+ ('remove' | translate) + ' ' + ('MODEL.' + item.key | translate)
2482
+ "
2483
+ >
2484
+ <span
2485
+ >{{ 'MODEL.' + item.key | translate }} {{ item.type }}
2486
+ {{ item.value | translate }}</span
2487
+ >
2488
+ <span aria-hidden="true" class="smart:text-red-600">✕</span>
2489
+ </button>
2490
+ }
2491
+ </div>
1450
2492
  }
1451
- `, imports: [TranslatePipe], styles: [":host ion-label{font-size:var(--small-font-size)}\n"] }]
2493
+ `,
2494
+ imports: [TranslatePipe],
2495
+ changeDetection: ChangeDetectionStrategy.OnPush,
2496
+ }]
1452
2497
  }] });
1453
2498
 
1454
- /**
1455
- * @deprecated - please, use IModelPossibilitiesProvider from @smartsoft001/angular
1456
- */
1457
- class ICrudModelPossibilitiesProvider {
2499
+ class MultiselectBaseComponent {
2500
+ facade = inject((CrudFacade));
2501
+ config = inject((CrudFullConfig));
2502
+ menuService = inject(MenuService);
2503
+ _list;
2504
+ _changes;
2505
+ item;
2506
+ valid;
2507
+ buttonOptions = {
2508
+ click: async () => {
2509
+ const result = this._list.map((item) => {
2510
+ return {
2511
+ ...this._changes,
2512
+ id: item.id,
2513
+ };
2514
+ });
2515
+ this.facade.updatePartialMany(result);
2516
+ await this.menuService.closeEnd();
2517
+ },
2518
+ confirm: true,
2519
+ };
2520
+ lock;
2521
+ showForm = false;
2522
+ list;
2523
+ constructor() {
2524
+ this.list = computed(() => {
2525
+ const list = this.facade.multiSelected() || [];
2526
+ this.lock = true;
2527
+ const model = new this.config.type();
2528
+ const fieldsWithOptions = getModelFieldsWithOptions(model)?.filter((f) => f.options.update?.multi);
2529
+ this.showForm = !!fieldsWithOptions.length;
2530
+ fieldsWithOptions.forEach(({ key }) => {
2531
+ const uniques = _.uniq(list.map((i) => i[key]));
2532
+ if (uniques.length === 1) {
2533
+ model[key] = uniques[0];
2534
+ }
2535
+ });
2536
+ this.item = model;
2537
+ return list;
2538
+ }, ...(ngDevMode ? [{ debugName: "list" }] : []));
2539
+ }
2540
+ async onClose() {
2541
+ await this.menuService.closeEnd();
2542
+ }
2543
+ onPartialChange(changes, list) {
2544
+ this.lock = false;
2545
+ this._list = list;
2546
+ this._changes = changes;
2547
+ }
2548
+ onValidChange(valid) {
2549
+ this.valid = valid;
2550
+ }
2551
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: MultiselectBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2552
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.1", type: MultiselectBaseComponent, isStandalone: true, ngImport: i0 });
1458
2553
  }
1459
- /**
1460
- * @deprecated - please, use MODEL_POSSIBILITIES_PROVIDER from @smartsoft001/angular
1461
- */
1462
- const CRUD_MODEL_POSSIBILITIES_PROVIDER = new InjectionToken('CRUD_MODEL_POSSIBILITIES_PROVIDER');
2554
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: MultiselectBaseComponent, decorators: [{
2555
+ type: Directive
2556
+ }], ctorParameters: () => [] });
1463
2557
 
1464
2558
  class FormOptionsPipe {
1465
- modelPossibilitiesProvider = inject(CRUD_MODEL_POSSIBILITIES_PROVIDER);
2559
+ modelPossibilitiesProvider = inject(CRUD_MODEL_POSSIBILITIES_PROVIDER, { optional: true });
1466
2560
  transform(item, mode, type, uniqueProvider, inputComponents) {
1467
2561
  if (!mode || !type)
1468
2562
  return null;
@@ -1520,60 +2614,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
1520
2614
  }]
1521
2615
  }] });
1522
2616
 
1523
- class MultiselectComponent {
1524
- facade = inject((CrudFacade));
1525
- config = inject((CrudFullConfig));
1526
- menuService = inject(MenuService);
1527
- _list;
1528
- _changes;
1529
- item;
1530
- valid;
1531
- buttonOptions = {
1532
- click: async () => {
1533
- const result = this._list.map((item) => {
1534
- return {
1535
- ...this._changes,
1536
- id: item.id,
1537
- };
1538
- });
1539
- this.facade.updatePartialMany(result);
1540
- await this.menuService.closeEnd();
1541
- },
1542
- confirm: true,
1543
- };
1544
- lock;
1545
- showForm = false;
1546
- list;
1547
- constructor() {
1548
- this.list = computed(() => {
1549
- const list = this.facade.multiSelected() || [];
1550
- this.lock = true;
1551
- const model = new this.config.type();
1552
- const fieldsWithOptions = getModelFieldsWithOptions(model)?.filter((f) => f.options.update?.multi);
1553
- this.showForm = !!fieldsWithOptions.length;
1554
- fieldsWithOptions.forEach(({ key }) => {
1555
- const uniques = _.uniq(list.map((i) => i[key]));
1556
- if (uniques.length === 1) {
1557
- model[key] = uniques[0];
1558
- }
1559
- });
1560
- this.item = model;
1561
- return list;
1562
- }, ...(ngDevMode ? [{ debugName: "list" }] : []));
1563
- }
1564
- async onClose() {
1565
- await this.menuService.closeEnd();
1566
- }
1567
- onPartialChange(changes, list) {
1568
- this.lock = false;
1569
- this._list = list;
1570
- this._changes = changes;
1571
- }
1572
- onValidChange(valid) {
1573
- this.valid = valid;
1574
- }
1575
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: MultiselectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1576
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: MultiselectComponent, isStandalone: true, selector: "smart-crud-multiselect", ngImport: i0, template: "@let list = this.list();\n@if (list) {\n <!-- <ion-header>-->\n <!-- <ion-toolbar>-->\n <!-- <ion-buttons slot=\"end\">-->\n <!-- <ion-button (click)=\"onClose()\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close\"> </ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-buttons>-->\n <!--<ion-title>-->{{ 'selected' | translate }}: {{ list?.length\n }}<!--</ion-title>-->\n <!-- </ion-toolbar>-->\n <!-- </ion-header>-->\n\n @if (config?.list?.components?.multi) {\n <div>\n <ng-template\n [ngComponentOutlet]=\"config?.list?.components?.multi\"\n [ndcDynamicInputs]=\"{ items: list }\"\n ></ng-template>\n </div>\n }\n\n @if (showForm && item) {\n <div>\n <smart-form\n [options]=\"item | smartFormOptions: 'multiUpdate' : config.type\"\n (valuePartialChange)=\"onPartialChange($event, list)\"\n (validChange)=\"onValidChange($event)\"\n ></smart-form>\n\n <smart-button\n class=\"smart:float-right\"\n [disabled]=\"lock || !valid\"\n [options]=\"buttonOptions\"\n >\n {{ 'change' | translate }}\n </smart-button>\n </div>\n }\n}\n", dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: DynamicIoDirective, selector: "[ndcDynamicInputs],[ndcDynamicOutputs]", inputs: ["ndcDynamicInputs", "ndcDynamicOutputs"], exportAs: ["ndcDynamicIo"] }, { kind: "component", type: FormComponent, selector: "smart-form", inputs: ["options", "class"], outputs: ["invokeSubmit", "valueChange", "valuePartialChange", "validChange"] }, { kind: "component", type: ButtonComponent, selector: "smart-button", inputs: ["options", "disabled", "class"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: FormOptionsPipe, name: "smartFormOptions" }] });
2617
+ class MultiselectComponent extends MultiselectBaseComponent {
2618
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: MultiselectComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2619
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: MultiselectComponent, isStandalone: true, selector: "smart-crud-multiselect", usesInheritance: true, ngImport: i0, template: "@let list = this.list();\n@if (list) {\n <header\n class=\"smart:flex smart:items-center smart:justify-between smart:border-b smart:border-gray-200 smart:px-4 smart:py-3\"\n >\n <h2 class=\"smart:text-lg smart:font-semibold smart:text-gray-900\">\n {{ 'selected' | translate }}: {{ list.length }}\n </h2>\n <button\n type=\"button\"\n (click)=\"onClose()\"\n aria-label=\"close\"\n class=\"smart:rounded smart:p-1 smart:text-gray-500 smart:hover:bg-gray-100\"\n >\n \u2715\n </button>\n </header>\n\n @if (config?.list?.components?.multi) {\n <div>\n <ng-template\n [ngComponentOutlet]=\"config?.list?.components?.multi\"\n [ndcDynamicInputs]=\"{ items: list }\"\n ></ng-template>\n </div>\n }\n\n @if (showForm && item) {\n <div>\n <smart-form\n [options]=\"item | smartFormOptions: 'multiUpdate' : config.type\"\n (valuePartialChange)=\"onPartialChange($event, list)\"\n (validChange)=\"onValidChange($event)\"\n ></smart-form>\n\n <smart-button\n class=\"smart:float-right\"\n [disabled]=\"lock || !valid\"\n [options]=\"buttonOptions\"\n >\n {{ 'change' | translate }}\n </smart-button>\n </div>\n }\n}\n", dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: DynamicIoDirective, selector: "[ndcDynamicInputs],[ndcDynamicOutputs]", inputs: ["ndcDynamicInputs", "ndcDynamicOutputs"], exportAs: ["ndcDynamicIo"] }, { kind: "component", type: FormComponent, selector: "smart-form", inputs: ["options", "class"], outputs: ["invokeSubmit", "valueChange", "valuePartialChange", "validChange"] }, { kind: "component", type: ButtonComponent, selector: "smart-button", inputs: ["options", "disabled", "class"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: FormOptionsPipe, name: "smartFormOptions" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1577
2620
  }
1578
2621
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: MultiselectComponent, decorators: [{
1579
2622
  type: Component,
@@ -1584,8 +2627,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
1584
2627
  FormOptionsPipe,
1585
2628
  FormComponent,
1586
2629
  ButtonComponent,
1587
- ], template: "@let list = this.list();\n@if (list) {\n <!-- <ion-header>-->\n <!-- <ion-toolbar>-->\n <!-- <ion-buttons slot=\"end\">-->\n <!-- <ion-button (click)=\"onClose()\">-->\n <!-- <ion-icon slot=\"icon-only\" name=\"close\"> </ion-icon>-->\n <!-- </ion-button>-->\n <!-- </ion-buttons>-->\n <!--<ion-title>-->{{ 'selected' | translate }}: {{ list?.length\n }}<!--</ion-title>-->\n <!-- </ion-toolbar>-->\n <!-- </ion-header>-->\n\n @if (config?.list?.components?.multi) {\n <div>\n <ng-template\n [ngComponentOutlet]=\"config?.list?.components?.multi\"\n [ndcDynamicInputs]=\"{ items: list }\"\n ></ng-template>\n </div>\n }\n\n @if (showForm && item) {\n <div>\n <smart-form\n [options]=\"item | smartFormOptions: 'multiUpdate' : config.type\"\n (valuePartialChange)=\"onPartialChange($event, list)\"\n (validChange)=\"onValidChange($event)\"\n ></smart-form>\n\n <smart-button\n class=\"smart:float-right\"\n [disabled]=\"lock || !valid\"\n [options]=\"buttonOptions\"\n >\n {{ 'change' | translate }}\n </smart-button>\n </div>\n }\n}\n" }]
1588
- }], ctorParameters: () => [] });
2630
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let list = this.list();\n@if (list) {\n <header\n class=\"smart:flex smart:items-center smart:justify-between smart:border-b smart:border-gray-200 smart:px-4 smart:py-3\"\n >\n <h2 class=\"smart:text-lg smart:font-semibold smart:text-gray-900\">\n {{ 'selected' | translate }}: {{ list.length }}\n </h2>\n <button\n type=\"button\"\n (click)=\"onClose()\"\n aria-label=\"close\"\n class=\"smart:rounded smart:p-1 smart:text-gray-500 smart:hover:bg-gray-100\"\n >\n \u2715\n </button>\n </header>\n\n @if (config?.list?.components?.multi) {\n <div>\n <ng-template\n [ngComponentOutlet]=\"config?.list?.components?.multi\"\n [ndcDynamicInputs]=\"{ items: list }\"\n ></ng-template>\n </div>\n }\n\n @if (showForm && item) {\n <div>\n <smart-form\n [options]=\"item | smartFormOptions: 'multiUpdate' : config.type\"\n (valuePartialChange)=\"onPartialChange($event, list)\"\n (validChange)=\"onValidChange($event)\"\n ></smart-form>\n\n <smart-button\n class=\"smart:float-right\"\n [disabled]=\"lock || !valid\"\n [options]=\"buttonOptions\"\n >\n {{ 'change' | translate }}\n </smart-button>\n </div>\n }\n}\n" }]
2631
+ }] });
1589
2632
 
1590
2633
  class CrudListGroupService {
1591
2634
  facade = inject((CrudFacade));
@@ -1643,10 +2686,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
1643
2686
  type: Injectable
1644
2687
  }], propDecorators: { refresh: [] } });
1645
2688
 
1646
- class GroupComponent extends BaseComponent$1 {
2689
+ class GroupBaseComponent extends BaseComponent$1 {
1647
2690
  styleService = inject(StyleService);
1648
2691
  elementRef = inject(ElementRef);
1649
2692
  groupService = inject((CrudListGroupService));
2693
+ cd = inject(ChangeDetectorRef);
1650
2694
  groups = input(null, ...(ngDevMode ? [{ debugName: "groups" }] : []));
1651
2695
  listOptions = input(null, ...(ngDevMode ? [{ debugName: "listOptions" }] : []));
1652
2696
  change(val, item, force = false) {
@@ -1659,7 +2703,16 @@ class GroupComponent extends BaseComponent$1 {
1659
2703
  });
1660
2704
  }
1661
2705
  this.groupService.change(val, item, force);
1662
- item.show = val;
2706
+ if (val) {
2707
+ setTimeout(() => {
2708
+ item.show = val;
2709
+ this.cd.detectChanges();
2710
+ });
2711
+ }
2712
+ else {
2713
+ item.show = val;
2714
+ this.cd.detectChanges();
2715
+ }
1663
2716
  }
1664
2717
  ngOnInit() {
1665
2718
  this.styleService.init(this.elementRef);
@@ -1671,292 +2724,175 @@ class GroupComponent extends BaseComponent$1 {
1671
2724
  }
1672
2725
  super.ngOnDestroy();
1673
2726
  }
1674
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: GroupComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1675
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: GroupComponent, isStandalone: true, selector: "smart-crud-group", inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: false, transformFunction: null }, listOptions: { classPropertyName: "listOptions", publicName: "listOptions", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: `
1676
- <!-- TODO: AccordionComponent moved to @smartsoft001-pro/angular (FRA-101)
1677
- @for (item of groups(); track item.key) {
1678
- <smart-accordion
1679
- [show]="item.show || false"
1680
- (showChange)="change($event, item)"
1681
- >
1682
- <smart-accordion-header [ngClass]="{ 'font-bold': item.show }">
1683
- {{ item.text | translate }}
1684
- </smart-accordion-header>
1685
- <smart-accordion-body>
1686
- @if (item.show && !item.children && listOptions()) {
1687
- <smart-list [options]="listOptions()!"></smart-list>
1688
- }
1689
- @if (item.children) {
1690
- <div style="margin-left: 50px">
1691
- <smart-crud-group
1692
- [groups]="item.children"
1693
- [listOptions]="listOptions()"
1694
- ></smart-crud-group>
1695
- </div>
1696
- }
1697
- </smart-accordion-body>
1698
- </smart-accordion>
1699
- }
1700
- -->
2727
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: GroupBaseComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive });
2728
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.1", type: GroupBaseComponent, isStandalone: true, inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: false, transformFunction: null }, listOptions: { classPropertyName: "listOptions", publicName: "listOptions", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0 });
2729
+ }
2730
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: GroupBaseComponent, decorators: [{
2731
+ type: Directive
2732
+ }], propDecorators: { groups: [{ type: i0.Input, args: [{ isSignal: true, alias: "groups", required: false }] }], listOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "listOptions", required: false }] }] } });
1701
2733
 
1702
- <!-- TODO: remove this template after moving to @smartsoft001-pro/angular (FRA-101) -->
2734
+ class GroupComponent extends GroupBaseComponent {
2735
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: GroupComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2736
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: GroupComponent, isStandalone: true, selector: "smart-crud-group", usesInheritance: true, ngImport: i0, template: `
1703
2737
  @for (item of groups(); track item.key) {
1704
- <div (click)="change(!item.show, item)">
1705
- {{ item.text | translate }}
2738
+ <div class="smart:border-b smart:border-gray-200">
2739
+ <button
2740
+ type="button"
2741
+ (click)="change(!item.show, item)"
2742
+ [attr.aria-expanded]="item.show || false"
2743
+ [attr.aria-controls]="'smart-crud-group-' + item.key"
2744
+ class="smart:flex smart:w-full smart:items-center smart:justify-between smart:py-3 smart:text-left smart:text-sm"
2745
+ [class.smart:font-bold]="item.show"
2746
+ >
2747
+ <span>{{ item.text | translate }}</span>
2748
+ <span aria-hidden="true" class="smart:text-gray-500">{{
2749
+ item.show ? '▾' : '▸'
2750
+ }}</span>
2751
+ </button>
2752
+ @if (item.show) {
2753
+ <div [id]="'smart-crud-group-' + item.key" class="smart:pb-3">
2754
+ @if (!item.children && listOptions()) {
2755
+ <smart-list [options]="listOptions()!"></smart-list>
2756
+ }
2757
+ @if (item.children) {
2758
+ <div class="smart:ml-12">
2759
+ <smart-crud-group
2760
+ [groups]="item.children"
2761
+ [listOptions]="listOptions()"
2762
+ ></smart-crud-group>
2763
+ </div>
2764
+ }
2765
+ </div>
2766
+ }
1706
2767
  </div>
1707
- @if (item.show && !item.children && listOptions()) {
1708
- <smart-list [options]="listOptions()!"></smart-list>
1709
- }
1710
- @if (item.children) {
1711
- <div style="margin-left: 50px">
1712
- <smart-crud-group
1713
- [groups]="item.children"
1714
- [listOptions]="listOptions()"
1715
- ></smart-crud-group>
1716
- </div>
1717
- }
1718
2768
  }
1719
- `, isInline: true, dependencies: [{ kind: "component", type: GroupComponent, selector: "smart-crud-group", inputs: ["groups", "listOptions"] }, { kind: "component", type:
1720
- // TODO: AccordionComponent moved to @smartsoft001-pro/angular (FRA-101)
1721
- // AccordionComponent,
1722
- // AccordionHeaderComponent,
1723
- // AccordionBodyComponent,
1724
- ListComponent$1, selector: "smart-list", inputs: ["options", "class"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2769
+ `, isInline: true, dependencies: [{ kind: "component", type: GroupComponent, selector: "smart-crud-group" }, { kind: "component", type: ListComponent$1, selector: "smart-list", inputs: ["options", "class"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1725
2770
  }
1726
2771
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: GroupComponent, decorators: [{
1727
2772
  type: Component,
1728
2773
  args: [{
1729
2774
  selector: 'smart-crud-group',
2775
+ changeDetection: ChangeDetectionStrategy.OnPush,
1730
2776
  template: `
1731
- <!-- TODO: AccordionComponent moved to @smartsoft001-pro/angular (FRA-101)
1732
- @for (item of groups(); track item.key) {
1733
- <smart-accordion
1734
- [show]="item.show || false"
1735
- (showChange)="change($event, item)"
1736
- >
1737
- <smart-accordion-header [ngClass]="{ 'font-bold': item.show }">
1738
- {{ item.text | translate }}
1739
- </smart-accordion-header>
1740
- <smart-accordion-body>
1741
- @if (item.show && !item.children && listOptions()) {
1742
- <smart-list [options]="listOptions()!"></smart-list>
1743
- }
1744
- @if (item.children) {
1745
- <div style="margin-left: 50px">
1746
- <smart-crud-group
1747
- [groups]="item.children"
1748
- [listOptions]="listOptions()"
1749
- ></smart-crud-group>
1750
- </div>
1751
- }
1752
- </smart-accordion-body>
1753
- </smart-accordion>
1754
- }
1755
- -->
1756
-
1757
- <!-- TODO: remove this template after moving to @smartsoft001-pro/angular (FRA-101) -->
1758
2777
  @for (item of groups(); track item.key) {
1759
- <div (click)="change(!item.show, item)">
1760
- {{ item.text | translate }}
2778
+ <div class="smart:border-b smart:border-gray-200">
2779
+ <button
2780
+ type="button"
2781
+ (click)="change(!item.show, item)"
2782
+ [attr.aria-expanded]="item.show || false"
2783
+ [attr.aria-controls]="'smart-crud-group-' + item.key"
2784
+ class="smart:flex smart:w-full smart:items-center smart:justify-between smart:py-3 smart:text-left smart:text-sm"
2785
+ [class.smart:font-bold]="item.show"
2786
+ >
2787
+ <span>{{ item.text | translate }}</span>
2788
+ <span aria-hidden="true" class="smart:text-gray-500">{{
2789
+ item.show ? '▾' : '▸'
2790
+ }}</span>
2791
+ </button>
2792
+ @if (item.show) {
2793
+ <div [id]="'smart-crud-group-' + item.key" class="smart:pb-3">
2794
+ @if (!item.children && listOptions()) {
2795
+ <smart-list [options]="listOptions()!"></smart-list>
2796
+ }
2797
+ @if (item.children) {
2798
+ <div class="smart:ml-12">
2799
+ <smart-crud-group
2800
+ [groups]="item.children"
2801
+ [listOptions]="listOptions()"
2802
+ ></smart-crud-group>
2803
+ </div>
2804
+ }
2805
+ </div>
2806
+ }
1761
2807
  </div>
1762
- @if (item.show && !item.children && listOptions()) {
1763
- <smart-list [options]="listOptions()!"></smart-list>
1764
- }
1765
- @if (item.children) {
1766
- <div style="margin-left: 50px">
1767
- <smart-crud-group
1768
- [groups]="item.children"
1769
- [listOptions]="listOptions()"
1770
- ></smart-crud-group>
1771
- </div>
1772
- }
1773
2808
  }
1774
2809
  `,
1775
- imports: [
1776
- // TODO: AccordionComponent moved to @smartsoft001-pro/angular (FRA-101)
1777
- // AccordionComponent,
1778
- // AccordionHeaderComponent,
1779
- // AccordionBodyComponent,
1780
- ListComponent$1,
1781
- NgClass,
1782
- TranslatePipe,
1783
- ],
2810
+ imports: [ListComponent$1, TranslatePipe],
1784
2811
  }]
1785
- }], propDecorators: { groups: [{ type: i0.Input, args: [{ isSignal: true, alias: "groups", required: false }] }], listOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "listOptions", required: false }] }] } });
2812
+ }] });
1786
2813
 
1787
2814
  class CrudListPaginationFactory {
1788
2815
  facade = inject((CrudFacade));
2816
+ injector = inject(Injector);
1789
2817
  async create(options) {
1790
2818
  return {
1791
2819
  mode: options.mode,
1792
- limit: options.limit,
1793
- loadNextPage: () => {
1794
- if (!options.provider.getLinks() || !options.provider.getLinks().next)
1795
- return Promise.resolve(false);
1796
- return new Promise((res) => {
1797
- if (this.facade.loaded()) {
1798
- setTimeout(() => {
1799
- res(options.provider.getLinks() && options.provider.getLinks().next);
1800
- });
1801
- }
1802
- const filter = options.provider.getFilter();
1803
- this.facade.read({
1804
- ...filter,
1805
- offset: (filter.offset || 0) + (filter.limit || 0),
1806
- });
1807
- });
1808
- },
1809
- loadPrevPage: () => {
1810
- if (!options.provider.getLinks() || !options.provider.getLinks().prev)
1811
- return Promise.resolve(false);
1812
- return new Promise((res) => {
1813
- const loaded = this.facade.loaded();
1814
- if (loaded) {
1815
- setTimeout(() => {
1816
- res(options.provider.getLinks() && options.provider.getLinks().prev);
1817
- });
1818
- }
1819
- const filter = options.provider.getFilter();
1820
- this.facade.read({
1821
- ...filter,
1822
- offset: (filter.offset || 0) - (filter.limit || 0),
1823
- });
1824
- });
1825
- },
1826
- page: computed(() => {
1827
- const f = this.facade.filter();
1828
- return f?.limit ? (f.offset || 0) / f.limit + 1 : 0;
1829
- }),
1830
- totalPages: computed(() => {
1831
- const filter = this.facade.filter();
1832
- const totalCount = this.facade.totalCount();
1833
- return filter?.limit && totalCount
1834
- ? Math.ceil(totalCount / filter.limit)
1835
- : 0;
1836
- }),
1837
- };
1838
- }
1839
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudListPaginationFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1840
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudListPaginationFactory });
1841
- }
1842
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudListPaginationFactory, decorators: [{
1843
- type: Injectable
1844
- }] });
1845
-
1846
- class CrudService {
1847
- config = inject((CrudConfig));
1848
- http = inject(HttpClient);
1849
- _formatMap = {
1850
- csv: 'text/csv',
1851
- xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1852
- };
1853
- create(item) {
1854
- return firstValueFrom(this.http
1855
- .post(this.config.apiUrl, item, { observe: 'response' })
1856
- .pipe(map((response) => {
1857
- const location = response.headers['Location'];
1858
- if (!location)
1859
- return null;
1860
- const array = location.split('/');
1861
- return array[array.length - 1];
1862
- })));
1863
- }
1864
- createMany(items, options) {
1865
- return firstValueFrom(this.http.post(this.config.apiUrl + '/bulk?mode=' + options.mode, items));
1866
- }
1867
- getById(id) {
1868
- return firstValueFrom(this.http.get(this.config.apiUrl + '/' + id));
1869
- }
1870
- getList(filter = null) {
1871
- return firstValueFrom(this.http.get(this.config.apiUrl + this.getQuery(filter)));
1872
- }
1873
- exportList(filter = null, format) {
1874
- if (!format) {
1875
- format = 'csv';
1876
- }
1877
- if (format === 'xlsx') {
1878
- return firstValueFrom(this.http
1879
- .get(this.config.apiUrl + this.getQuery(filter), {
1880
- headers: {
1881
- 'Content-Type': this._formatMap[format],
1882
- },
1883
- observe: 'response',
1884
- reportProgress: false,
1885
- responseType: 'blob',
1886
- })
1887
- .pipe(map((res) => {
1888
- const downloadLink = document.createElement('a');
1889
- const blob = res.body;
1890
- downloadLink.href = URL.createObjectURL(blob);
1891
- downloadLink.download = 'data.' + format;
1892
- document.body.appendChild(downloadLink);
1893
- downloadLink.click();
1894
- document.body.removeChild(downloadLink);
1895
- })));
1896
- }
1897
- return firstValueFrom(this.http
1898
- .get(this.config.apiUrl + this.getQuery(filter), {
1899
- headers: {
1900
- 'Content-Type': this._formatMap[format],
1901
- },
1902
- responseType: 'text',
1903
- })
1904
- .pipe(map((res) => {
1905
- const downloadLink = document.createElement('a');
1906
- const blob = new Blob(['\ufeff', res]);
1907
- downloadLink.href = URL.createObjectURL(blob);
1908
- downloadLink.download = 'data.' + format;
1909
- document.body.appendChild(downloadLink);
1910
- downloadLink.click();
1911
- document.body.removeChild(downloadLink);
1912
- })));
1913
- }
1914
- update(item) {
1915
- return firstValueFrom(this.http.put(this.config.apiUrl + '/' + item.id, item));
1916
- }
1917
- updatePartial(item) {
1918
- return firstValueFrom(this.http.patch(this.config.apiUrl + '/' + item.id, item));
1919
- }
1920
- updatePartialMany(items) {
1921
- const updatePromises = items.map((item) => {
1922
- return this.updatePartial(item);
1923
- });
1924
- return Promise.all(updatePromises);
1925
- }
1926
- delete(id) {
1927
- return firstValueFrom(this.http.delete(this.config.apiUrl + '/' + id));
1928
- }
1929
- getQuery(filter) {
1930
- let query = '';
1931
- if (filter && filter.searchText) {
1932
- query += '&$search=' + filter.searchText;
1933
- }
1934
- if (filter && filter.limit) {
1935
- query += `&limit=${filter.limit}&offset=${filter.offset ? filter.offset : 0}`;
1936
- }
1937
- if (filter && filter.sortBy) {
1938
- query += `&sort=${(filter.sortDesc ? '-' : '') + filter.sortBy}`;
1939
- }
1940
- if (filter && filter.query) {
1941
- filter.query.forEach((q) => {
1942
- if (q.value &&
1943
- typeof q.value === 'string' &&
1944
- q.value.match(/^-?\d+$/) &&
1945
- q.value[0] !== "'" &&
1946
- q.value[0] !== '"') {
1947
- query += '&' + q.key + q.type + `'${q.value}'`;
1948
- }
1949
- else {
1950
- query += '&' + q.key + q.type + q.value;
1951
- }
1952
- });
1953
- }
1954
- return query ? '?' + query.replace('&', '') : '';
2820
+ limit: options.limit,
2821
+ loadNextPage: () => {
2822
+ if (!options.provider.getLinks() || !options.provider.getLinks().next)
2823
+ return Promise.resolve(false);
2824
+ return new Promise((res) => {
2825
+ toObservable(this.facade.loaded, { injector: this.injector })
2826
+ .pipe(filter((l) => !!l), take(1))
2827
+ .subscribe(() => {
2828
+ setTimeout(() => {
2829
+ res(options.provider.getLinks() &&
2830
+ options.provider.getLinks().next);
2831
+ });
2832
+ });
2833
+ const currentFilter = options.provider.getFilter();
2834
+ this.facade.read({
2835
+ ...currentFilter,
2836
+ offset: (currentFilter.offset || 0) + (currentFilter.limit || 0),
2837
+ });
2838
+ });
2839
+ },
2840
+ loadPrevPage: () => {
2841
+ if (!options.provider.getLinks() || !options.provider.getLinks().prev)
2842
+ return Promise.resolve(false);
2843
+ return new Promise((res) => {
2844
+ toObservable(this.facade.loaded, { injector: this.injector })
2845
+ .pipe(filter((l) => !!l), take(1))
2846
+ .subscribe(() => {
2847
+ setTimeout(() => {
2848
+ res(options.provider.getLinks() &&
2849
+ options.provider.getLinks().prev);
2850
+ });
2851
+ });
2852
+ const currentFilter = options.provider.getFilter();
2853
+ this.facade.read({
2854
+ ...currentFilter,
2855
+ offset: (currentFilter.offset || 0) - (currentFilter.limit || 0),
2856
+ });
2857
+ });
2858
+ },
2859
+ page: computed(() => {
2860
+ const f = this.facade.filter();
2861
+ return f?.limit ? (f.offset || 0) / f.limit + 1 : 0;
2862
+ }),
2863
+ totalPages: computed(() => {
2864
+ const filter = this.facade.filter();
2865
+ const totalCount = this.facade.totalCount();
2866
+ return filter?.limit && totalCount
2867
+ ? Math.ceil(totalCount / filter.limit)
2868
+ : 0;
2869
+ }),
2870
+ };
1955
2871
  }
1956
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1957
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudService });
2872
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudListPaginationFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2873
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudListPaginationFactory });
1958
2874
  }
1959
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudService, decorators: [{
2875
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudListPaginationFactory, decorators: [{
2876
+ type: Injectable
2877
+ }] });
2878
+
2879
+ class CrudModelLabelProvider extends IModelLabelProvider {
2880
+ translateService = inject(TranslateService);
2881
+ // Delegate to an app-level provider if one exists higher in the injector tree;
2882
+ // otherwise fall back to translating 'MODEL.<key>'. (FRA-293 GAP / belt-and-suspenders.)
2883
+ parent = inject(IModelLabelProvider, {
2884
+ optional: true,
2885
+ skipSelf: true,
2886
+ });
2887
+ get(options) {
2888
+ if (this.parent)
2889
+ return this.parent.get(options);
2890
+ return signal(this.translateService.instant('MODEL.' + options.key));
2891
+ }
2892
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudModelLabelProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2893
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudModelLabelProvider });
2894
+ }
2895
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudModelLabelProvider, decorators: [{
1960
2896
  type: Injectable
1961
2897
  }] });
1962
2898
 
@@ -2009,7 +2945,13 @@ class CrudComponentsModule {
2009
2945
  FilterIntComponent,
2010
2946
  MultiselectComponent,
2011
2947
  GroupComponent] });
2012
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudComponentsModule, providers: [CrudService, CrudFacade, CrudListPaginationFactory], imports: [ExportComponent,
2948
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudComponentsModule, providers: [
2949
+ CrudService,
2950
+ CrudFacade,
2951
+ SocketService,
2952
+ CrudListPaginationFactory,
2953
+ { provide: IModelLabelProvider, useClass: CrudModelLabelProvider },
2954
+ ], imports: [ExportComponent,
2013
2955
  FilterComponent,
2014
2956
  FilterTextComponent,
2015
2957
  FilterDateComponent,
@@ -2038,178 +2980,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
2038
2980
  DynamicIoModule,
2039
2981
  ],
2040
2982
  exports: [CrudPipesModule, ...COMPONENTS],
2041
- providers: [CrudService, CrudFacade, CrudListPaginationFactory],
2983
+ providers: [
2984
+ CrudService,
2985
+ CrudFacade,
2986
+ SocketService,
2987
+ CrudListPaginationFactory,
2988
+ { provide: IModelLabelProvider, useClass: CrudModelLabelProvider },
2989
+ ],
2042
2990
  }]
2043
2991
  }] });
2044
2992
 
2045
- class CrudEffects {
2046
- actions$;
2047
- service;
2048
- config;
2049
- store;
2050
- state;
2051
- constructor(actions$, service, config, store, state) {
2052
- this.actions$ = actions$;
2053
- this.service = service;
2054
- this.config = config;
2055
- this.store = store;
2056
- this.state = state;
2057
- }
2058
- init() {
2059
- const metadata = Reflect.getMetadata(this.config.entity, CrudEffects);
2060
- if (metadata)
2061
- return;
2062
- Reflect.defineMetadata(this.config.entity, true, CrudEffects);
2063
- this.actions$.subscribe((action) => {
2064
- switch (action.type) {
2065
- case `[${this.config.entity}] Create`:
2066
- this.service
2067
- .create(action.item)
2068
- .then(() => this.store.dispatch(createSuccess(this.config.entity, action.item)))
2069
- .catch((error) => {
2070
- this.store.dispatch(createFailure(this.config.entity, action.item, error));
2071
- });
2072
- break;
2073
- case `[${this.config.entity}] Create Success`: {
2074
- const createState = this.state.getValue()[this.config.entity];
2075
- this.store.dispatch(read(this.config.entity, createState?.filter ? { ...createState.filter, offset: 0 } : null));
2076
- break;
2077
- }
2078
- case `[${this.config.entity}] Create Many`:
2079
- this.service
2080
- .createMany(action.data.items, action.data.options)
2081
- .then(() => this.store.dispatch(createManySuccess(this.config.entity, {
2082
- items: action.data.items,
2083
- options: action.data.options,
2084
- })))
2085
- .catch((error) => {
2086
- this.store.dispatch(createManyFailure(this.config.entity, {
2087
- items: action.data.items,
2088
- options: action.data.options,
2089
- }, error));
2090
- });
2091
- break;
2092
- case `[${this.config.entity}] Create Many Success`: {
2093
- const createManyState = this.state.getValue()[this.config.entity];
2094
- this.store.dispatch(read(this.config.entity, createManyState?.filter
2095
- ? { ...createManyState.filter, offset: 0 }
2096
- : null));
2097
- break;
2098
- }
2099
- case `[${this.config.entity}] Read`:
2100
- this.service
2101
- .getList(action?.filter)
2102
- .then((result) => this.store.dispatch(readSuccess(this.config.entity, action?.filter, result)))
2103
- .catch((error) => {
2104
- this.store.dispatch(readFailure(this.config.entity, action?.filter, error));
2105
- });
2106
- break;
2107
- case `[${this.config.entity}] Export`:
2108
- this.service
2109
- .exportList(action?.filter, action.format)
2110
- .then(() => this.store.dispatch(exportListSuccess(this.config.entity, action?.filter)))
2111
- .catch((error) => {
2112
- this.store.dispatch(exportListFailure(this.config.entity, action?.filter, error));
2113
- });
2114
- break;
2115
- case `[${this.config.entity}] Select`:
2116
- this.service
2117
- .getById(action.id)
2118
- .then((result) => this.store.dispatch(selectSuccess(this.config.entity, action.id, result)))
2119
- .catch((error) => {
2120
- this.store.dispatch(selectFailure(this.config.entity, action.id, error));
2121
- });
2122
- break;
2123
- case `[${this.config.entity}] Update`:
2124
- this.service
2125
- .updatePartial(action.item)
2126
- .then(() => this.store.dispatch(updateSuccess(this.config.entity, action.item)))
2127
- .catch((error) => {
2128
- this.store.dispatch(updateFailure(this.config.entity, action.item, error));
2129
- this.store.dispatch(read(this.config.entity));
2130
- });
2131
- break;
2132
- case `[${this.config.entity}] Update Success`:
2133
- this.store
2134
- .pipe(select$1(getCrudFilter(this.config.entity)), first())
2135
- .subscribe((filter) => {
2136
- this.store.dispatch(read(this.config.entity, {
2137
- ...filter,
2138
- offset: 0,
2139
- }));
2140
- this.store.dispatch(select(this.config.entity, action.item.id));
2141
- });
2142
- break;
2143
- case `[${this.config.entity}] Update partial`:
2144
- this.service
2145
- .updatePartial(action.item)
2146
- .then(() => this.store.dispatch(updatePartialSuccess(this.config.entity, action.item)))
2147
- .catch((error) => {
2148
- this.store.dispatch(updatePartialFailure(this.config.entity, action.item, error));
2149
- this.store.dispatch(read(this.config.entity));
2150
- });
2151
- break;
2152
- case `[${this.config.entity}] Update partial Success`:
2153
- this.store
2154
- .pipe(select$1(getCrudFilter(this.config.entity)), first())
2155
- .subscribe((filter) => {
2156
- this.store.dispatch(read(this.config.entity, {
2157
- ...filter,
2158
- offset: 0,
2159
- }));
2160
- this.store.dispatch(select(this.config.entity, action.item.id));
2161
- });
2162
- break;
2163
- case `[${this.config.entity}] Update partial many`:
2164
- this.service
2165
- .updatePartialMany(action.items)
2166
- .then(() => this.store.dispatch(updatePartialManySuccess(this.config.entity, action.items)))
2167
- .catch((error) => {
2168
- this.store.dispatch(updatePartialManyFailure(this.config.entity, action.items, error));
2169
- this.store.dispatch(read(this.config.entity));
2170
- });
2171
- break;
2172
- case `[${this.config.entity}] Update partial many Success`:
2173
- this.store
2174
- .pipe(select$1(getCrudFilter(this.config.entity)), first())
2175
- .subscribe((filter) => {
2176
- this.store.dispatch(read(this.config.entity, {
2177
- ...filter,
2178
- offset: 0,
2179
- }));
2180
- });
2181
- break;
2182
- case `[${this.config.entity}] Delete`:
2183
- this.service
2184
- .delete(action.id)
2185
- .then(() => this.store.dispatch(deleteSuccess(this.config.entity, action.id)))
2186
- .catch((error) => {
2187
- this.store.dispatch(deleteFailure(this.config.entity, action.id, error));
2188
- });
2189
- break;
2190
- case `[${this.config.entity}] Delete Success`:
2191
- this.store
2192
- .pipe(select$1(getCrudFilter(this.config.entity)), first())
2193
- .subscribe((filter) => {
2194
- this.store.dispatch(read(this.config.entity, {
2195
- ...filter,
2196
- offset: 0,
2197
- }));
2198
- });
2199
- break;
2200
- default:
2201
- console.warn(`No effect for action type ${action.type} in entity ${this.config.entity}`);
2202
- break;
2203
- }
2204
- });
2205
- }
2206
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudEffects, deps: [{ token: i1.ActionsSubject }, { token: CrudService }, { token: CrudConfig }, { token: i1.Store }, { token: i1.State }], target: i0.ɵɵFactoryTarget.Injectable });
2207
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudEffects });
2208
- }
2209
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: CrudEffects, decorators: [{
2210
- type: Injectable
2211
- }], ctorParameters: () => [{ type: i1.ActionsSubject }, { type: CrudService }, { type: CrudConfig }, { type: i1.Store }, { type: i1.State }] });
2212
-
2213
2993
  class CrudItemPageBaseComponent extends BaseComponent$1 {
2214
2994
  config = inject((CrudFullConfig));
2215
2995
  facade = inject((CrudFacade));
@@ -2351,6 +3131,7 @@ class ItemComponent extends CreateDynamicComponent('crud-item-page') {
2351
3131
  _mode;
2352
3132
  pageOptions = signal({
2353
3133
  title: '',
3134
+ variant: this.config.variant,
2354
3135
  showBackButton: true,
2355
3136
  hideMenuButton: true,
2356
3137
  }, ...(ngDevMode ? [{ debugName: "pageOptions" }] : []));
@@ -2370,7 +3151,6 @@ class ItemComponent extends CreateDynamicComponent('crud-item-page') {
2370
3151
  }
2371
3152
  selected;
2372
3153
  standardComponents = viewChildren(ItemStandardComponent, ...(ngDevMode ? [{ debugName: "standardComponents" }] : []));
2373
- // @ViewChild(IonContent, { static: true }) content: IonContent; //TODO: rewrite when rewriting ionic
2374
3154
  contentTpl = viewChild('contentTpl', ...(ngDevMode ? [{ debugName: "contentTpl" }] : []));
2375
3155
  topTpl = viewChild('topTpl', ...(ngDevMode ? [{ debugName: "topTpl" }] : []));
2376
3156
  bottomTpl = viewChild('bottomTpl', ...(ngDevMode ? [{ debugName: "bottomTpl" }] : []));
@@ -2666,54 +3446,54 @@ class ItemComponent extends CreateDynamicComponent('crud-item-page') {
2666
3446
  }
2667
3447
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2668
3448
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: ItemComponent, isStandalone: true, selector: "smart-crud-item-page", viewQueries: [{ propertyName: "standardComponents", predicate: ItemStandardComponent, descendants: true, isSignal: true }, { propertyName: "contentTpl", first: true, predicate: ["contentTpl"], descendants: true, isSignal: true }, { propertyName: "topTpl", first: true, predicate: ["topTpl"], descendants: true, isSignal: true }, { propertyName: "bottomTpl", first: true, predicate: ["bottomTpl"], descendants: true, isSignal: true }, { propertyName: "dynamicContents", predicate: DynamicContentDirective, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
2669
- <!-- <smart-page [options]="pageOptions()">-->
2670
- <div #topTpl class="text-xl py-2.5 separator"></div>
2671
- @if (template() === 'default') {
2672
- <smart-crud-item-standard-page
2673
- [detailsOptions]="detailsOptions()"
2674
- [mode]="mode"
2675
- [uniqueProvider]="uniqueProvider()"
2676
- (onPartialChange)="onPartialChange($event)"
2677
- (onChange)="onChange($event)"
2678
- (onValidChange)="onValidChange($event)"
2679
- >
2680
- <ng-container [ngTemplateOutlet]="contentTpl"></ng-container>
2681
- </smart-crud-item-standard-page>
2682
- }
2683
- <ng-template #contentTpl>
2684
- <ng-content></ng-content>
2685
- </ng-template>
2686
- <div class="dynamic-content"></div>
2687
- <div #bottomTpl class="text-xl py-2.5 separator"></div>
2688
- <!-- </smart-page>-->
2689
- `, isInline: true, dependencies: [{ kind: "component", type: /*PageComponent,*/ ItemStandardComponent, selector: "smart-crud-item-standard-page" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
3449
+ <smart-page [options]="pageOptions()" [class]="config.cssClass || ''">
3450
+ <div #topTpl class="text-xl py-2.5 separator"></div>
3451
+ @if (template() === 'default') {
3452
+ <smart-crud-item-standard-page
3453
+ [detailsOptions]="detailsOptions()"
3454
+ [mode]="mode"
3455
+ [uniqueProvider]="uniqueProvider()"
3456
+ (onPartialChange)="onPartialChange($event)"
3457
+ (onChange)="onChange($event)"
3458
+ (onValidChange)="onValidChange($event)"
3459
+ >
3460
+ <ng-container [ngTemplateOutlet]="contentTpl"></ng-container>
3461
+ </smart-crud-item-standard-page>
3462
+ }
3463
+ <ng-template #contentTpl>
3464
+ <ng-content></ng-content>
3465
+ </ng-template>
3466
+ <div class="dynamic-content"></div>
3467
+ <div #bottomTpl class="text-xl py-2.5 separator"></div>
3468
+ </smart-page>
3469
+ `, isInline: true, dependencies: [{ kind: "component", type: PageComponent, selector: "smart-page", inputs: ["options", "class"] }, { kind: "component", type: ItemStandardComponent, selector: "smart-crud-item-standard-page" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
2690
3470
  }
2691
3471
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ItemComponent, decorators: [{
2692
3472
  type: Component,
2693
3473
  args: [{
2694
3474
  selector: 'smart-crud-item-page',
2695
- imports: [/*PageComponent,*/ ItemStandardComponent, NgTemplateOutlet],
3475
+ imports: [PageComponent, ItemStandardComponent, NgTemplateOutlet],
2696
3476
  template: `
2697
- <!-- <smart-page [options]="pageOptions()">-->
2698
- <div #topTpl class="text-xl py-2.5 separator"></div>
2699
- @if (template() === 'default') {
2700
- <smart-crud-item-standard-page
2701
- [detailsOptions]="detailsOptions()"
2702
- [mode]="mode"
2703
- [uniqueProvider]="uniqueProvider()"
2704
- (onPartialChange)="onPartialChange($event)"
2705
- (onChange)="onChange($event)"
2706
- (onValidChange)="onValidChange($event)"
2707
- >
2708
- <ng-container [ngTemplateOutlet]="contentTpl"></ng-container>
2709
- </smart-crud-item-standard-page>
2710
- }
2711
- <ng-template #contentTpl>
2712
- <ng-content></ng-content>
2713
- </ng-template>
2714
- <div class="dynamic-content"></div>
2715
- <div #bottomTpl class="text-xl py-2.5 separator"></div>
2716
- <!-- </smart-page>-->
3477
+ <smart-page [options]="pageOptions()" [class]="config.cssClass || ''">
3478
+ <div #topTpl class="text-xl py-2.5 separator"></div>
3479
+ @if (template() === 'default') {
3480
+ <smart-crud-item-standard-page
3481
+ [detailsOptions]="detailsOptions()"
3482
+ [mode]="mode"
3483
+ [uniqueProvider]="uniqueProvider()"
3484
+ (onPartialChange)="onPartialChange($event)"
3485
+ (onChange)="onChange($event)"
3486
+ (onValidChange)="onValidChange($event)"
3487
+ >
3488
+ <ng-container [ngTemplateOutlet]="contentTpl"></ng-container>
3489
+ </smart-crud-item-standard-page>
3490
+ }
3491
+ <ng-template #contentTpl>
3492
+ <ng-content></ng-content>
3493
+ </ng-template>
3494
+ <div class="dynamic-content"></div>
3495
+ <div #bottomTpl class="text-xl py-2.5 separator"></div>
3496
+ </smart-page>
2717
3497
  `,
2718
3498
  }]
2719
3499
  }], ctorParameters: () => [], propDecorators: { standardComponents: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => ItemStandardComponent), { isSignal: true }] }], contentTpl: [{ type: i0.ViewChild, args: ['contentTpl', { isSignal: true }] }], topTpl: [{ type: i0.ViewChild, args: ['topTpl', { isSignal: true }] }], bottomTpl: [{ type: i0.ViewChild, args: ['bottomTpl', { isSignal: true }] }], dynamicContents: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => DynamicContentDirective), { isSignal: true }] }] } });
@@ -2750,7 +3530,7 @@ class ListStandardComponent extends CrudListPageBaseComponent {
2750
3530
  [listOptions]="listOptions() || null"
2751
3531
  ></smart-crud-group>
2752
3532
  }
2753
- `, isInline: true, dependencies: [{ kind: "component", type: FiltersConfigComponent, selector: "smart-crud-filters-config" }, { kind: "component", type: ListComponent$1, selector: "smart-list", inputs: ["options", "class"] }, { kind: "component", type: GroupComponent, selector: "smart-crud-group", inputs: ["groups", "listOptions"] }] });
3533
+ `, isInline: true, dependencies: [{ kind: "component", type: FiltersConfigComponent, selector: "smart-crud-filters-config" }, { kind: "component", type: ListComponent$1, selector: "smart-list", inputs: ["options", "class"] }, { kind: "component", type: GroupComponent, selector: "smart-crud-group" }] });
2754
3534
  }
2755
3535
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ListStandardComponent, decorators: [{
2756
3536
  type: Component,
@@ -2843,7 +3623,7 @@ class ListComponent extends CreateDynamicComponent('crud-list-page') {
2843
3623
  if (this.config.list?.resetQuery === 'beforeInit') {
2844
3624
  newFilter = {
2845
3625
  query: this.config.baseQuery ? [...this.config.baseQuery] : [],
2846
- paginationMode: this.config.list.paginationMode,
3626
+ paginationMode: this.config.list?.paginationMode,
2847
3627
  limit: this.config.pagination
2848
3628
  ? this.config.pagination.limit
2849
3629
  : undefined,
@@ -2862,7 +3642,7 @@ class ListComponent extends CreateDynamicComponent('crud-list-page') {
2862
3642
  }
2863
3643
  else {
2864
3644
  newFilter = {
2865
- paginationMode: this.config.list.paginationMode,
3645
+ paginationMode: this.config.list?.paginationMode,
2866
3646
  limit: this.searchService.filter?.limit
2867
3647
  ? this.searchService.filter.limit
2868
3648
  : this.config.pagination
@@ -2892,6 +3672,7 @@ class ListComponent extends CreateDynamicComponent('crud-list-page') {
2892
3672
  const endButtons = this.getEndButtons();
2893
3673
  this.pageOptions.set({
2894
3674
  title: this.config.title || '',
3675
+ variant: this.config.variant,
2895
3676
  search: this.config.search
2896
3677
  ? {
2897
3678
  text: computed(() => this.filter()?.searchText ?? ''),
@@ -3008,8 +3789,8 @@ class ListComponent extends CreateDynamicComponent('crud-list-page') {
3008
3789
  }
3009
3790
  : undefined,
3010
3791
  pagination: await this.paginationFacade.create({
3011
- mode: this.config.list.paginationMode,
3012
- limit: this.config.pagination.limit,
3792
+ mode: this.config.list?.paginationMode,
3793
+ limit: this.config.pagination?.limit ?? 0,
3013
3794
  provider: {
3014
3795
  getFilter: () => {
3015
3796
  return this.filter() || {};
@@ -3125,42 +3906,42 @@ class ListComponent extends CreateDynamicComponent('crud-list-page') {
3125
3906
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ListComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
3126
3907
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: ListComponent, isStandalone: true, selector: "smart-crud-list-page", viewQueries: [{ propertyName: "topTpl", first: true, predicate: ["topTpl"], descendants: true, isSignal: true }, { propertyName: "contentTpl", first: true, predicate: ["contentTpl"], descendants: true, isSignal: true }, { propertyName: "dynamicContents", predicate: DynamicContentDirective, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
3127
3908
  @if (filter() && pageOptions()) {
3128
- <!-- <smart-page [options]="pageOptions()">-->
3129
- <div #topTpl></div>
3909
+ <smart-page [options]="pageOptions()" [class]="config.cssClass || ''">
3910
+ <div #topTpl></div>
3130
3911
 
3131
- @if (template() === 'default' && listOptions()) {
3132
- <smart-crud-list-standard-page [listOptions]="listOptions()">
3133
- <ng-container [ngTemplateOutlet]="contentTpl"></ng-container>
3134
- </smart-crud-list-standard-page>
3135
- }
3136
- <ng-template #contentTpl>
3137
- <ng-content></ng-content>
3138
- </ng-template>
3139
- <div class="dynamic-content"></div>
3140
- <!-- </smart-page>-->
3912
+ @if (template() === 'default' && listOptions()) {
3913
+ <smart-crud-list-standard-page [listOptions]="listOptions()">
3914
+ <ng-container [ngTemplateOutlet]="contentTpl"></ng-container>
3915
+ </smart-crud-list-standard-page>
3916
+ }
3917
+ <ng-template #contentTpl>
3918
+ <ng-content></ng-content>
3919
+ </ng-template>
3920
+ <div class="dynamic-content"></div>
3921
+ </smart-page>
3141
3922
  }
3142
- `, isInline: true, dependencies: [{ kind: "component", type: ListStandardComponent, selector: "smart-crud-list-standard-page" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
3923
+ `, isInline: true, dependencies: [{ kind: "component", type: PageComponent, selector: "smart-page", inputs: ["options", "class"] }, { kind: "component", type: ListStandardComponent, selector: "smart-crud-list-standard-page" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
3143
3924
  }
3144
3925
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ListComponent, decorators: [{
3145
3926
  type: Component,
3146
3927
  args: [{
3147
3928
  selector: 'smart-crud-list-page',
3148
- imports: [ListStandardComponent, NgTemplateOutlet],
3929
+ imports: [PageComponent, ListStandardComponent, NgTemplateOutlet],
3149
3930
  template: `
3150
3931
  @if (filter() && pageOptions()) {
3151
- <!-- <smart-page [options]="pageOptions()">-->
3152
- <div #topTpl></div>
3932
+ <smart-page [options]="pageOptions()" [class]="config.cssClass || ''">
3933
+ <div #topTpl></div>
3153
3934
 
3154
- @if (template() === 'default' && listOptions()) {
3155
- <smart-crud-list-standard-page [listOptions]="listOptions()">
3156
- <ng-container [ngTemplateOutlet]="contentTpl"></ng-container>
3157
- </smart-crud-list-standard-page>
3158
- }
3159
- <ng-template #contentTpl>
3160
- <ng-content></ng-content>
3161
- </ng-template>
3162
- <div class="dynamic-content"></div>
3163
- <!-- </smart-page>-->
3935
+ @if (template() === 'default' && listOptions()) {
3936
+ <smart-crud-list-standard-page [listOptions]="listOptions()">
3937
+ <ng-container [ngTemplateOutlet]="contentTpl"></ng-container>
3938
+ </smart-crud-list-standard-page>
3939
+ }
3940
+ <ng-template #contentTpl>
3941
+ <ng-content></ng-content>
3942
+ </ng-template>
3943
+ <div class="dynamic-content"></div>
3944
+ </smart-page>
3164
3945
  }
3165
3946
  `,
3166
3947
  }]
@@ -3255,7 +4036,9 @@ class CrudCoreModule {
3255
4036
  CrudEffects,
3256
4037
  PageService,
3257
4038
  CrudFacade,
4039
+ SocketService,
3258
4040
  CrudListPaginationFactory,
4041
+ { provide: IModelLabelProvider, useClass: CrudModelLabelProvider },
3259
4042
  ], imports: [SharedModule,
3260
4043
  CrudPipesModule,
3261
4044
  FormsModule,
@@ -3279,7 +4062,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
3279
4062
  CrudEffects,
3280
4063
  PageService,
3281
4064
  CrudFacade,
4065
+ SocketService,
3282
4066
  CrudListPaginationFactory,
4067
+ { provide: IModelLabelProvider, useClass: CrudModelLabelProvider },
3283
4068
  ],
3284
4069
  }]
3285
4070
  }], ctorParameters: () => [{ type: CrudConfig }, { type: CrudEffects }] });
@@ -3294,6 +4079,9 @@ class CrudModule {
3294
4079
  provide: FILE_SERVICE_CONFIG,
3295
4080
  useValue: { apiUrl: options.config.apiUrl },
3296
4081
  },
4082
+ ...(options.socket
4083
+ ? [SocketService]
4084
+ : [{ provide: SocketService, useClass: NotSocketService }]),
3297
4085
  ],
3298
4086
  };
3299
4087
  }
@@ -3332,5 +4120,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
3332
4120
  * Generated bundle index. Do not edit.
3333
4121
  */
3334
4122
 
3335
- export { CRUD_MODEL_POSSIBILITIES_PROVIDER, CrudComponentsModule, CrudConfig, CrudCoreModule, CrudFacade, CrudFullConfig, CrudFullModule, CrudItemPageBaseComponent, CrudListPageBaseComponent, CrudListPaginationFactory, CrudModule, CrudPipesModule, CrudSearchService, CrudService, ExportComponent, FilterCheckComponent, FilterComponent, FilterDateComponent, FilterDateTimeComponent, FilterDateWithEditComponent, FilterFlagComponent, FilterIntComponent, FilterRadioComponent, FilterTextComponent, FiltersComponent, FiltersConfigComponent, FormOptionsPipe, GroupComponent, ICrudModelPossibilitiesProvider, ItemComponent, ItemStandardComponent, ListComponent, ListStandardComponent, MultiselectComponent, PAGES, PageService, getCrudError, getCrudFilter, getCrudLinks, getCrudList, getCrudLoaded, getCrudMultiSelected, getCrudSelected, getCrudState, getCrudTotalCount, getReducer, initialState };
4123
+ export { CRUD_MODEL_POSSIBILITIES_PROVIDER, CrudComponentsModule, CrudConfig, CrudCoreModule, CrudEffects, CrudFacade, CrudFullConfig, CrudFullModule, CrudItemPageBaseComponent, CrudListPageBaseComponent, CrudListPaginationFactory, CrudModelLabelProvider, CrudModule, CrudPipesModule, CrudSearchService, CrudService, ExportBaseComponent, ExportComponent, FilterCheckComponent, FilterComponent, FilterDateComponent, FilterDateTimeComponent, FilterDateWithEditComponent, FilterFlagComponent, FilterIntComponent, FilterRadioComponent, FilterTextComponent, FiltersBaseComponent, FiltersComponent, FiltersConfigComponent, FormOptionsPipe, GroupBaseComponent, GroupComponent, ICrudModelPossibilitiesProvider, ItemComponent, ItemStandardComponent, ListComponent, ListStandardComponent, MultiselectBaseComponent, MultiselectComponent, PAGES, PageService, clear, create, createFailure, createMany, createManyFailure, createManySuccess, createSuccess, deleteFailure, deleteItem, deleteSuccess, exportList, exportListFailure, exportListSuccess, getCrudError, getCrudFilter, getCrudLinks, getCrudList, getCrudLoaded, getCrudMultiSelected, getCrudSelected, getCrudState, getCrudTotalCount, getReducer, initialState, multiSelect, read, readFailure, readSuccess, select, selectFailure, selectSuccess, unselect, update, updateFailure, updatePartial, updatePartialFailure, updatePartialMany, updatePartialManyFailure, updatePartialManySuccess, updatePartialSuccess, updateSuccess };
3336
4124
  //# sourceMappingURL=smartsoft001-crud-shell-angular.mjs.map