@decaf-ts/for-angular 0.0.84 → 0.0.86
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.
- package/assets/i18n/ew/en.json +7 -0
- package/fesm2022/decaf-ts-for-angular.mjs +608 -378
- package/fesm2022/decaf-ts-for-angular.mjs.map +1 -1
- package/index.d.ts +65 -19
- package/package.json +1 -1
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { HTML5InputTypes,
|
|
1
|
+
import { parseValueByType, HTML5InputTypes, UIKeys, HTML5CheckTypes, escapeHtml, parseToNumber, RenderingEngine, DecafComponent, DecafTranslateService, ComponentEventNames, UIValidator, RenderingError, UIMediaBreakPoints, LayoutGridGaps, ElementSizes, DecafEventHandler } from '@decaf-ts/ui-decorators';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { InjectionToken,
|
|
3
|
+
import { InjectionToken, isDevMode, provideEnvironmentInitializer, reflectComponentType, Injector, createEnvironmentInjector, runInInjectionContext, createComponent, inject, NgZone, Injectable, Input, Directive, signal, ChangeDetectorRef, EnvironmentInjector, Renderer2, EventEmitter, ElementRef, Output, ViewChild, Inject, ViewContainerRef, TemplateRef, Component, ViewEncapsulation, HostListener, NgModule } from '@angular/core';
|
|
4
4
|
import * as i1$1 from '@angular/common';
|
|
5
5
|
import { Location, NgComponentOutlet, CommonModule } from '@angular/common';
|
|
6
|
-
import {
|
|
6
|
+
import { ValidationKeys, DEFAULT_PATTERNS, VALIDATION_PARENT_KEY, Primitives, Model, Validation, ComparisonValidationKeys, PathProxyEngine, ModelKeys, isValidDate as isValidDate$1, parseDate, sf as sf$1, ReservedModels } from '@decaf-ts/decorator-validation';
|
|
7
7
|
import { OperationKeys, InternalError, NotFoundError } from '@decaf-ts/db-decorators';
|
|
8
8
|
import * as i1 from '@angular/forms';
|
|
9
9
|
import { FormGroup, FormControl, FormArray, AbstractControl, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
10
|
-
import { InjectableRegistryImp } from '@decaf-ts/injectable-decorators';
|
|
11
10
|
import { Logging, LoggedClass, sf } from '@decaf-ts/logging';
|
|
11
|
+
import { InjectableRegistryImp } from '@decaf-ts/injectable-decorators';
|
|
12
12
|
import { Repository, OrderDirection, Condition } from '@decaf-ts/core';
|
|
13
13
|
import { uses, Metadata, apply, metadata } from '@decaf-ts/decoration';
|
|
14
14
|
import { AnimationController, provideIonicAngular, LoadingController, IonIcon, IonButton, IonModal, IonSpinner, IonButtons, IonContent, IonHeader, IonTitle, IonToolbar, IonInput, IonItem, IonCheckbox, IonRadioGroup, IonRadio, IonSelect, IonSelectOption, IonLabel, IonBadge, IonText, IonTextarea, IonCard, IonCardHeader, IonCardContent, IonCardTitle, IonCardSubtitle, IonList, IonReorder, IonReorderGroup, IonSearchbar, IonChip, IonRefresher, IonThumbnail, IonSkeletonText, IonRefresherContent, IonInfiniteScroll, IonInfiniteScrollContent, IonListHeader, IonItemSliding, IonItemOptions, IonItemOption, IonPopover, NavController } from '@ionic/angular/standalone';
|
|
15
15
|
import { faker } from '@faker-js/faker';
|
|
16
|
-
import { forkJoin, Subject, BehaviorSubject, fromEvent, of, merge, Observable, timer, shareReplay as shareReplay$1, takeUntil as takeUntil$1,
|
|
16
|
+
import { forkJoin, Subject, BehaviorSubject, fromEvent, of, merge, Observable, timer, firstValueFrom, shareReplay as shareReplay$1, takeUntil as takeUntil$1, debounceTime } from 'rxjs';
|
|
17
17
|
import { Title, DomSanitizer } from '@angular/platform-browser';
|
|
18
18
|
import { Router, NavigationStart, NavigationEnd, ActivatedRoute } from '@angular/router';
|
|
19
19
|
import { MenuController } from '@ionic/angular';
|
|
20
|
-
import { TranslateParser, provideTranslateService, TranslateLoader, provideTranslateParser, TranslateService, TranslatePipe, TranslateModule } from '@ngx-translate/core';
|
|
21
20
|
import { provideHttpClient, HttpClient } from '@angular/common/http';
|
|
21
|
+
import { TranslateParser, provideTranslateService, TranslateLoader, provideTranslateParser, TranslateService, TranslatePipe, TranslateModule } from '@ngx-translate/core';
|
|
22
22
|
import { map, distinctUntilChanged, takeUntil, shareReplay, tap, switchMap } from 'rxjs/operators';
|
|
23
23
|
import { __decorate, __metadata } from 'tslib';
|
|
24
24
|
import { addIcons } from 'ionicons';
|
|
@@ -27,6 +27,19 @@ import { chevronUpOutline, chevronDownOutline, createOutline, trashOutline, addO
|
|
|
27
27
|
import { modalController } from '@ionic/core';
|
|
28
28
|
|
|
29
29
|
const DB_ADAPTER_FLAVOUR_TOKEN = 'DbAdapterFlavour';
|
|
30
|
+
/**
|
|
31
|
+
* Maps validation keys for password, email, and URL to their corresponding regex patterns.
|
|
32
|
+
* These patterns are used to validate field values against standard format requirements.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Maps validation keys for password, email, and URL to their corresponding regex patterns.
|
|
36
|
+
* These patterns are used to validate field values against standard format requirements.
|
|
37
|
+
*/
|
|
38
|
+
const patternValidators = {
|
|
39
|
+
[ValidationKeys.PASSWORD]: DEFAULT_PATTERNS.PASSWORD.CHAR8_ONE_OF_EACH,
|
|
40
|
+
[ValidationKeys.EMAIL]: DEFAULT_PATTERNS.EMAIL,
|
|
41
|
+
[ValidationKeys.URL]: DEFAULT_PATTERNS.URL,
|
|
42
|
+
};
|
|
30
43
|
/**
|
|
31
44
|
* @description Injection token for registering the database adapter provider.
|
|
32
45
|
* @summary Used to inject the database adapter instance that implements DecafRepositoryAdapter.
|
|
@@ -167,6 +180,7 @@ const AngularEngineKeys = {
|
|
|
167
180
|
RENDERED: 'rendered-as-',
|
|
168
181
|
MAPPER: 'mapper',
|
|
169
182
|
CHILDREN: 'children',
|
|
183
|
+
ERRORS: 'errors',
|
|
170
184
|
LISTABLE: 'listable',
|
|
171
185
|
RENDER: 'render',
|
|
172
186
|
RENDERED_ID: 'rendered-as-{0}',
|
|
@@ -332,291 +346,6 @@ const SelectFieldInterfaces = {
|
|
|
332
346
|
MODAL: 'modal',
|
|
333
347
|
};
|
|
334
348
|
|
|
335
|
-
/**
|
|
336
|
-
* @module module:lib/engine/ValidatorFactory
|
|
337
|
-
* @description Factory for generating Angular ValidatorFn from Decaf validation metadata.
|
|
338
|
-
* @summary ValidatorFactory maps validation keys defined by the Decaf validation system
|
|
339
|
-
* into Angular ValidatorFn instances. It supports type-based resolution and comparison
|
|
340
|
-
* validators and provides helpers to create proxies for nested control validation.
|
|
341
|
-
*
|
|
342
|
-
* @link {@link ValidatorFactory}
|
|
343
|
-
*/
|
|
344
|
-
/**
|
|
345
|
-
*
|
|
346
|
-
* Resolves the correct validator key and its associated properties based on the input key and type.
|
|
347
|
-
*
|
|
348
|
-
* When the validation key is TYPE, it's necessary to resolve the actual validator based on the
|
|
349
|
-
* field's type (e.g., 'password', 'email', 'url') instead of using the generic getValidator("type") logic.
|
|
350
|
-
* This allows directly invoking specific validators like getValidator('password'), ensuring the correct
|
|
351
|
-
* behavior for type-based validation.
|
|
352
|
-
*
|
|
353
|
-
* @param key - The validation key (e.g., 'type', 'required', etc.).
|
|
354
|
-
* @param value - The value that needs be provided to the validator.
|
|
355
|
-
* @param type - The field's declared type.
|
|
356
|
-
* @returns An object containing the resolved validator key and its corresponding props.
|
|
357
|
-
*/
|
|
358
|
-
const resolveValidatorKeyProps = (key, value, type) => {
|
|
359
|
-
const patternValidators = {
|
|
360
|
-
[ValidationKeys.PASSWORD]: DEFAULT_PATTERNS.PASSWORD.CHAR8_ONE_OF_EACH,
|
|
361
|
-
[ValidationKeys.EMAIL]: DEFAULT_PATTERNS.EMAIL,
|
|
362
|
-
[ValidationKeys.URL]: DEFAULT_PATTERNS.URL,
|
|
363
|
-
};
|
|
364
|
-
const isTypeBased = key === ValidationKeys.TYPE && Object.keys(patternValidators).includes(type);
|
|
365
|
-
const validatorKey = isTypeBased ? type : key;
|
|
366
|
-
if (key === ValidationKeys.TYPE && HTML5InputTypes.CHECKBOX && value !== type)
|
|
367
|
-
value = type;
|
|
368
|
-
const props = {
|
|
369
|
-
// [validatorKey]: (!isTypeBased && key === 'type') ? parseType(type) : value,
|
|
370
|
-
[validatorKey]: (!isTypeBased && validatorKey === ValidationKeys.TYPE) ? NgxRenderingEngine.get().translate(value, false) : value,
|
|
371
|
-
// Email, Password, and URL are validated using the "pattern" key
|
|
372
|
-
...(isTypeBased && { [ValidationKeys.PATTERN]: patternValidators[type] }),
|
|
373
|
-
};
|
|
374
|
-
return { validatorKey, props };
|
|
375
|
-
};
|
|
376
|
-
class ValidatorFactory {
|
|
377
|
-
static spawn(fieldProps, key) {
|
|
378
|
-
if (!Validation.keys().includes(key))
|
|
379
|
-
throw new Error('Unsupported custom validation');
|
|
380
|
-
const validatorFn = (control) => {
|
|
381
|
-
const { type, customTypes, options } = fieldProps;
|
|
382
|
-
let fieldType = (customTypes || type);
|
|
383
|
-
if ((fieldType === HTML5InputTypes.CHECKBOX || fieldType === Array.name) && Array.isArray(options))
|
|
384
|
-
fieldType = Primitives.STRING;
|
|
385
|
-
const { validatorKey, props } = resolveValidatorKeyProps(key, fieldProps[key], fieldType);
|
|
386
|
-
const validator = Validation.get(validatorKey);
|
|
387
|
-
// parseValueByType does not support undefined values
|
|
388
|
-
const value = typeof control.value !== 'undefined'
|
|
389
|
-
? parseValueByType(fieldType, control.value, fieldProps)
|
|
390
|
-
: undefined;
|
|
391
|
-
// Create a proxy to enable access to parent and child values
|
|
392
|
-
let proxy = ValidatorFactory.createProxy({});
|
|
393
|
-
if (Object.values(ComparisonValidationKeys).includes(key)) {
|
|
394
|
-
const parent = control instanceof FormGroup ? control : control[AngularEngineKeys.PARENT];
|
|
395
|
-
proxy = ValidatorFactory.createProxy(parent);
|
|
396
|
-
}
|
|
397
|
-
let errs;
|
|
398
|
-
try {
|
|
399
|
-
errs = validator.hasErrors(value, props, proxy);
|
|
400
|
-
}
|
|
401
|
-
catch (e) {
|
|
402
|
-
errs = `${key} validator failed to validate: ${e}`;
|
|
403
|
-
console.error(errs);
|
|
404
|
-
}
|
|
405
|
-
return errs ? { [validatorKey]: true } : null;
|
|
406
|
-
};
|
|
407
|
-
Object.defineProperty(validatorFn, 'name', {
|
|
408
|
-
value: `${key}Validator`,
|
|
409
|
-
});
|
|
410
|
-
return validatorFn;
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* @summary Creates a proxy wrapper for an Angular AbstractControl to assist with custom validation logic.
|
|
414
|
-
* @description Returns a structured proxy object that simulates a hierarchical tree of form values.
|
|
415
|
-
* Enables Validators handling method to access parent and child properties using consistent dot-notation in Angular forms.
|
|
416
|
-
*
|
|
417
|
-
* @param {AbstractControl} control - The control to wrap in a proxy.
|
|
418
|
-
* @returns {PathProxy<unknown>} A proxy object exposing form values and enabling recursive parent access.
|
|
419
|
-
*/
|
|
420
|
-
static createProxy(control) {
|
|
421
|
-
return PathProxyEngine.create(control, {
|
|
422
|
-
getValue(target, prop) {
|
|
423
|
-
if (target instanceof FormControl)
|
|
424
|
-
return target.value;
|
|
425
|
-
if (target instanceof FormGroup) {
|
|
426
|
-
const control = target.controls[prop];
|
|
427
|
-
return control instanceof FormControl ? control.value : control;
|
|
428
|
-
}
|
|
429
|
-
// const value = target[prop];
|
|
430
|
-
// if (value instanceof FormControl)
|
|
431
|
-
// return value.value;
|
|
432
|
-
//
|
|
433
|
-
// if (value instanceof FormGroup) {
|
|
434
|
-
// const control = value.controls[prop];
|
|
435
|
-
// return control instanceof FormControl ? control.value : control;
|
|
436
|
-
// }
|
|
437
|
-
return target?.[prop];
|
|
438
|
-
},
|
|
439
|
-
getParent: function (target) {
|
|
440
|
-
return target?.['_parent'];
|
|
441
|
-
},
|
|
442
|
-
ignoreUndefined: true,
|
|
443
|
-
ignoreNull: true,
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function getDbAdapterFlavour() {
|
|
449
|
-
return (getOnWindow(DB_ADAPTER_FLAVOUR_TOKEN) || '');
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* @description Provides an array of component types for dynamic rendering.
|
|
453
|
-
* @summary Helper function to package component constructors for registration with the
|
|
454
|
-
* rendering engine. This function accepts component classes and returns them as an array
|
|
455
|
-
* suitable for use with the CPTKN injection token.
|
|
456
|
-
* @param {...Constructor[]} components - Component constructor classes to register
|
|
457
|
-
* @return {Constructor} Array of component constructors
|
|
458
|
-
* @memberOf module:lib/for-angular-common.module
|
|
459
|
-
* @example
|
|
460
|
-
* // Register multiple custom components
|
|
461
|
-
* providers: [
|
|
462
|
-
* { provide: CPTKN, useValue: provideDynamicComponents(MyComponent, AnotherComponent) }
|
|
463
|
-
* ]
|
|
464
|
-
*/
|
|
465
|
-
function provideDecafDynamicComponents(...components) {
|
|
466
|
-
return components;
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* @description Retrieves the repository instance for a given model.
|
|
470
|
-
* @summary Creates or retrieves a DecafRepository instance for the specified model. This function
|
|
471
|
-
* resolves the model by name or class, locates the registered database adapter, and returns
|
|
472
|
-
* a fully initialized repository instance for performing CRUD operations.
|
|
473
|
-
* @param {Model | string} model - The model class or model name string
|
|
474
|
-
* @return {DecafRepository<Model>} Repository instance for the model
|
|
475
|
-
* @throws {InternalError} If model is not found or not registered with @model decorator
|
|
476
|
-
* @memberOf module:lib/for-angular-common.module
|
|
477
|
-
* @example
|
|
478
|
-
* // Get repository by model class
|
|
479
|
-
* const userRepo = getModelAndRepository(User);
|
|
480
|
-
*
|
|
481
|
-
* // Get repository by model name
|
|
482
|
-
* const productRepo = getModelAndRepository('Product');
|
|
483
|
-
*
|
|
484
|
-
* // Use repository for queries
|
|
485
|
-
* const users = await userRepo.findAll();
|
|
486
|
-
*/
|
|
487
|
-
function getModelAndRepository(model, clazz) {
|
|
488
|
-
try {
|
|
489
|
-
const modelName = (typeof model === Primitives.STRING ? model : model.constructor.name);
|
|
490
|
-
const constructor = Model.get((modelName.charAt(0).toUpperCase() + modelName.slice(1)));
|
|
491
|
-
if (!constructor)
|
|
492
|
-
return undefined;
|
|
493
|
-
const dbAdapterFlavour = getOnWindow(DB_ADAPTER_FLAVOUR_TOKEN) || undefined;
|
|
494
|
-
if (dbAdapterFlavour)
|
|
495
|
-
uses(dbAdapterFlavour)(constructor);
|
|
496
|
-
const repository = Repository.forModel(constructor);
|
|
497
|
-
model = new constructor();
|
|
498
|
-
const pk = Model.pk(repository.class);
|
|
499
|
-
if (!pk)
|
|
500
|
-
return undefined;
|
|
501
|
-
const pkType = Metadata.type(repository.class, pk).name;
|
|
502
|
-
if (clazz) {
|
|
503
|
-
clazz.repository = repository;
|
|
504
|
-
clazz.model = model;
|
|
505
|
-
clazz.pk = pk;
|
|
506
|
-
clazz.pkType = Metadata.type(repository.class, pk).name;
|
|
507
|
-
}
|
|
508
|
-
return { repository, model, pk, pkType };
|
|
509
|
-
}
|
|
510
|
-
catch (error) {
|
|
511
|
-
getLogger(getModelAndRepository).warn(error?.message || error);
|
|
512
|
-
return undefined;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* @description Provides a database adapter for dependency injection.
|
|
517
|
-
* @summary Creates an Angular provider that registers a database adapter instance. This function
|
|
518
|
-
* instantiates the adapter class, registers its flavour globally, and returns a provider object
|
|
519
|
-
* for use in Angular's dependency injection system.
|
|
520
|
-
* @template DbAdapter - The database adapter class type extending {flavour: string}
|
|
521
|
-
* @param {Constructor<DbAdapter>} adapterClass - Database adapter constructor class
|
|
522
|
-
* @param {KeyValue} [options={}] - Configuration options passed to adapter constructor
|
|
523
|
-
* @param {string} [flavour] - Optional flavour override; uses adapter.flavour if not provided
|
|
524
|
-
* @return {Provider} Angular provider object for DB_ADAPTER_PROVIDER_TOKEN
|
|
525
|
-
* @memberOf module:lib/for-angular-common.module
|
|
526
|
-
* @example
|
|
527
|
-
* // Register a SQLite adapter
|
|
528
|
-
* providers: [
|
|
529
|
-
* provideDbAdapter(SqliteAdapter, { database: 'myapp.db' }, 'sqlite')
|
|
530
|
-
* ]
|
|
531
|
-
*
|
|
532
|
-
* // Register with default flavour from adapter
|
|
533
|
-
* providers: [
|
|
534
|
-
* provideDbAdapter(PostgresAdapter, { host: 'localhost', port: 5432 })
|
|
535
|
-
* ]
|
|
536
|
-
*/
|
|
537
|
-
function provideDecafDbAdapter(clazz, options = {}, flavour) {
|
|
538
|
-
const adapter = new clazz(options);
|
|
539
|
-
if (!flavour)
|
|
540
|
-
flavour = adapter.flavour;
|
|
541
|
-
getLogger(provideDecafDbAdapter).info(`Using ${adapter.constructor.name} ${flavour} as Db Provider`);
|
|
542
|
-
setOnWindow(DB_ADAPTER_FLAVOUR_TOKEN, flavour);
|
|
543
|
-
return {
|
|
544
|
-
provide: DB_ADAPTER_PROVIDER_TOKEN,
|
|
545
|
-
useValue: adapter,
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Creates a custom page transition animation using the Ionic `AnimationController`.
|
|
550
|
-
*
|
|
551
|
-
* @param baseEl - The base HTML element for the animation.
|
|
552
|
-
* @param opts - Optional parameters for the animation, including:
|
|
553
|
-
* - `enteringEl`: The HTML element that is entering the view.
|
|
554
|
-
* - `leavingEl`: The HTML element that is leaving the view.
|
|
555
|
-
*
|
|
556
|
-
* @returns An object containing the `navAnimation`, which is a composed animation
|
|
557
|
-
* of the entering and leaving animations.
|
|
558
|
-
*
|
|
559
|
-
* The entering animation fades in and slides the element upwards, while the leaving
|
|
560
|
-
* animation fades out and slides the element downwards. Both animations use a cubic-bezier
|
|
561
|
-
* easing function for smooth transitions.
|
|
562
|
-
*/
|
|
563
|
-
const decafPageTransition = (baseEl, opts) => {
|
|
564
|
-
const animationCtrl = new AnimationController();
|
|
565
|
-
const enteringAnimation = animationCtrl
|
|
566
|
-
.create()
|
|
567
|
-
.addElement(opts?.['enteringEl'])
|
|
568
|
-
.duration(280)
|
|
569
|
-
.easing('cubic-bezier(0.36,0.66,0.04,1)')
|
|
570
|
-
.fromTo('opacity', '0.01', '1')
|
|
571
|
-
.fromTo('transform', 'translateY(40px)', 'translateY(0)');
|
|
572
|
-
const leavingAnimation = animationCtrl
|
|
573
|
-
.create()
|
|
574
|
-
.addElement(opts?.['leavingEl'])
|
|
575
|
-
.duration(200)
|
|
576
|
-
.easing('cubic-bezier(0.36,0.66,0.04,1)')
|
|
577
|
-
.fromTo('opacity', '1', '0')
|
|
578
|
-
.fromTo('transform', 'translateY(0)', 'translateY(20px)');
|
|
579
|
-
return animationCtrl.create().addAnimation([enteringAnimation, leavingAnimation]);
|
|
580
|
-
};
|
|
581
|
-
function provideDecafPageTransition() {
|
|
582
|
-
return provideIonicAngular({
|
|
583
|
-
navAnimation: decafPageTransition,
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
function provideDecafDarkMode() {
|
|
587
|
-
return provideEnvironmentInitializer(() => {
|
|
588
|
-
const doc = getWindowDocument();
|
|
589
|
-
doc?.documentElement.classList.add('has-dark-mode');
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* @const {Logger}
|
|
594
|
-
* @private
|
|
595
|
-
* @description Base logger instance for the for-angular module.ẑ
|
|
596
|
-
* @memberOf module:lib/for-angular-common.module
|
|
597
|
-
*/
|
|
598
|
-
const log = Logging.for('for-angular');
|
|
599
|
-
/**
|
|
600
|
-
* @description Retrieves a logger instance for the given context.
|
|
601
|
-
* @summary Creates or retrieves a namespaced logger instance using the Decaf logging system.
|
|
602
|
-
* The logger is automatically namespaced under "for-angular" and can be further scoped
|
|
603
|
-
* to a specific instance, function, or string identifier.
|
|
604
|
-
* @param {string | FunctionLike | unknown} instance - The instance, function, or string to scope the logger to
|
|
605
|
-
* @return {Logger} Logger instance for the specified context
|
|
606
|
-
* @memberOf module:lib/for-angular-common.module
|
|
607
|
-
* @example
|
|
608
|
-
* // Get logger for a class
|
|
609
|
-
* const logger = getLogger(MyComponent);
|
|
610
|
-
* logger.info('Component initialized');
|
|
611
|
-
*
|
|
612
|
-
* // Get logger with string identifier
|
|
613
|
-
* const serviceLogger = getLogger('UserService');
|
|
614
|
-
* serviceLogger.error('Operation failed', error);
|
|
615
|
-
*/
|
|
616
|
-
function getLogger(instance) {
|
|
617
|
-
return log.for(instance);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
349
|
/**
|
|
621
350
|
* @module module:lib/helpers/utils
|
|
622
351
|
* @description General helper utilities used across the library.
|
|
@@ -1058,57 +787,441 @@ function removeFocusTrap() {
|
|
|
1058
787
|
* @param {boolean} [lowercase=false] - Whether to convert the result to lowercase
|
|
1059
788
|
* @return {string} The cleaned and normalized string
|
|
1060
789
|
*
|
|
1061
|
-
* @function cleanSpaces
|
|
1062
|
-
* @memberOf module:for-angular
|
|
790
|
+
* @function cleanSpaces
|
|
791
|
+
* @memberOf module:for-angular
|
|
792
|
+
*/
|
|
793
|
+
function cleanSpaces(value = '', lowercase = false) {
|
|
794
|
+
value = `${value}`.trim().replace(/\s+/g, ' ');
|
|
795
|
+
return lowercase ? value.toLowerCase() : value;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* @description Determines if the user's system is currently in dark mode
|
|
799
|
+
* @summary This function checks the user's color scheme preference using the CSS media query
|
|
800
|
+
* '(prefers-color-scheme: dark)'. It returns a boolean indicating whether the system is
|
|
801
|
+
* currently set to dark mode. This is useful for implementing theme-aware functionality
|
|
802
|
+
* and adjusting UI elements based on the user's preferred color scheme.
|
|
803
|
+
*
|
|
804
|
+
* @return {Promise<boolean>} True if the system is in dark mode, false otherwise
|
|
805
|
+
*
|
|
806
|
+
* @function isDarkMode
|
|
807
|
+
* @memberOf module:for-angular
|
|
808
|
+
*/
|
|
809
|
+
async function isDarkMode() {
|
|
810
|
+
const { matches } = getWindow().matchMedia('(prefers-color-scheme: dark)');
|
|
811
|
+
return matches;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* @description Filters out strings containing or not containing a specific substring from an array or space-separated string.
|
|
815
|
+
* @summary This function removes or retains strings based on whether they include the specified substring.
|
|
816
|
+
* If the input is a single string, it is split into an array using spaces as delimiters before filtering.
|
|
817
|
+
*
|
|
818
|
+
* @param {string | string[]} original - The input string or array of strings to filter.
|
|
819
|
+
* @param {string} value - The substring to filter by.
|
|
820
|
+
* @param {boolean} [contain=true] - Determines the filtering behavior. If true, retains strings containing the substring; otherwise, removes them.
|
|
821
|
+
* @returns {string} A string that contains or excludes the specified substring based on the `contain` parameter.
|
|
822
|
+
*
|
|
823
|
+
* @function filterString
|
|
824
|
+
* @memberOf module:lib/helpers/utils
|
|
825
|
+
*/
|
|
826
|
+
function filterString(original, value, contain = true) {
|
|
827
|
+
if (typeof original === Primitives.STRING)
|
|
828
|
+
original = original.split(' ');
|
|
829
|
+
return (original.filter((str) => contain ? str.includes(value) : !str.includes(value)) || []).join(' ');
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* @summary Retrieves the icon associated with a menu item based on its label.
|
|
833
|
+
*
|
|
834
|
+
* @param {string} label - The label of the menu item to search for. The search is case-insensitive.
|
|
835
|
+
* @param {IMenuItem[]} menu - An array of menu items to search within.
|
|
836
|
+
* @returns {string} The icon associated with the menu item if found, otherwise an empty string.
|
|
837
|
+
*/
|
|
838
|
+
function getMenuIcon(label, menu) {
|
|
839
|
+
const item = menu.find((m) => m.label?.toLowerCase() === label.toLowerCase());
|
|
840
|
+
return item?.icon || '';
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function getDbAdapterFlavour() {
|
|
844
|
+
return (getOnWindow(DB_ADAPTER_FLAVOUR_TOKEN) || '');
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* @description Provides an array of component types for dynamic rendering.
|
|
848
|
+
* @summary Helper function to package component constructors for registration with the
|
|
849
|
+
* rendering engine. This function accepts component classes and returns them as an array
|
|
850
|
+
* suitable for use with the CPTKN injection token.
|
|
851
|
+
* @param {...Constructor[]} components - Component constructor classes to register
|
|
852
|
+
* @return {Constructor} Array of component constructors
|
|
853
|
+
* @memberOf module:lib/for-angular-common.module
|
|
854
|
+
* @example
|
|
855
|
+
* // Register multiple custom components
|
|
856
|
+
* providers: [
|
|
857
|
+
* { provide: CPTKN, useValue: provideDynamicComponents(MyComponent, AnotherComponent) }
|
|
858
|
+
* ]
|
|
859
|
+
*/
|
|
860
|
+
function provideDecafDynamicComponents(...components) {
|
|
861
|
+
return components;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* @description Retrieves the repository instance for a given model.
|
|
865
|
+
* @summary Creates or retrieves a DecafRepository instance for the specified model. This function
|
|
866
|
+
* resolves the model by name or class, locates the registered database adapter, and returns
|
|
867
|
+
* a fully initialized repository instance for performing CRUD operations.
|
|
868
|
+
* @param {Model | string} model - The model class or model name string
|
|
869
|
+
* @return {DecafRepository<Model>} Repository instance for the model
|
|
870
|
+
* @throws {InternalError} If model is not found or not registered with @model decorator
|
|
871
|
+
* @memberOf module:lib/for-angular-common.module
|
|
872
|
+
* @example
|
|
873
|
+
* // Get repository by model class
|
|
874
|
+
* const userRepo = getModelAndRepository(User);
|
|
875
|
+
*
|
|
876
|
+
* // Get repository by model name
|
|
877
|
+
* const productRepo = getModelAndRepository('Product');
|
|
878
|
+
*
|
|
879
|
+
* // Use repository for queries
|
|
880
|
+
* const users = await userRepo.findAll();
|
|
881
|
+
*/
|
|
882
|
+
function getModelAndRepository(model, clazz) {
|
|
883
|
+
if (!model)
|
|
884
|
+
return undefined;
|
|
885
|
+
try {
|
|
886
|
+
const modelName = (typeof model === Primitives.STRING ? model : model.constructor.name);
|
|
887
|
+
const constructor = Model.get((modelName.charAt(0).toUpperCase() + modelName.slice(1)));
|
|
888
|
+
if (!constructor)
|
|
889
|
+
return undefined;
|
|
890
|
+
const dbAdapterFlavour = getOnWindow(DB_ADAPTER_FLAVOUR_TOKEN) || undefined;
|
|
891
|
+
if (dbAdapterFlavour)
|
|
892
|
+
uses(dbAdapterFlavour)(constructor);
|
|
893
|
+
const repository = Repository.forModel(constructor);
|
|
894
|
+
model = new constructor();
|
|
895
|
+
const pk = Model.pk(repository.class);
|
|
896
|
+
if (!pk)
|
|
897
|
+
return undefined;
|
|
898
|
+
const pkType = Metadata.type(repository.class, pk).name;
|
|
899
|
+
if (clazz) {
|
|
900
|
+
clazz.repository = repository;
|
|
901
|
+
clazz.model = model;
|
|
902
|
+
clazz.pk = pk;
|
|
903
|
+
clazz.pkType = Metadata.type(repository.class, pk).name;
|
|
904
|
+
}
|
|
905
|
+
return { repository, model, pk, pkType };
|
|
906
|
+
}
|
|
907
|
+
catch (error) {
|
|
908
|
+
getLogger(getModelAndRepository).warn(error?.message || error);
|
|
909
|
+
return undefined;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* @description Provides a database adapter for dependency injection.
|
|
914
|
+
* @summary Creates an Angular provider that registers a database adapter instance. This function
|
|
915
|
+
* instantiates the adapter class, registers its flavour globally, and returns a provider object
|
|
916
|
+
* for use in Angular's dependency injection system.
|
|
917
|
+
* @template DbAdapter - The database adapter class type extending {flavour: string}
|
|
918
|
+
* @param {Constructor<DbAdapter>} adapterClass - Database adapter constructor class
|
|
919
|
+
* @param {KeyValue} [options={}] - Configuration options passed to adapter constructor
|
|
920
|
+
* @param {string} [flavour] - Optional flavour override; uses adapter.flavour if not provided
|
|
921
|
+
* @return {Provider} Angular provider object for DB_ADAPTER_PROVIDER_TOKEN
|
|
922
|
+
* @memberOf module:lib/for-angular-common.module
|
|
923
|
+
* @example
|
|
924
|
+
* // Register a SQLite adapter
|
|
925
|
+
* providers: [
|
|
926
|
+
* provideDbAdapter(SqliteAdapter, { database: 'myapp.db' }, 'sqlite')
|
|
927
|
+
* ]
|
|
928
|
+
*
|
|
929
|
+
* // Register with default flavour from adapter
|
|
930
|
+
* providers: [
|
|
931
|
+
* provideDbAdapter(PostgresAdapter, { host: 'localhost', port: 5432 })
|
|
932
|
+
* ]
|
|
933
|
+
*/
|
|
934
|
+
function provideDecafDbAdapter(clazz, options = {}, flavour) {
|
|
935
|
+
const adapter = new clazz(options);
|
|
936
|
+
if (!flavour)
|
|
937
|
+
flavour = adapter.flavour;
|
|
938
|
+
getLogger(provideDecafDbAdapter).info(`Using ${adapter.constructor.name} ${flavour} as Db Provider`);
|
|
939
|
+
setOnWindow(DB_ADAPTER_FLAVOUR_TOKEN, flavour);
|
|
940
|
+
return {
|
|
941
|
+
provide: DB_ADAPTER_PROVIDER_TOKEN,
|
|
942
|
+
useValue: adapter,
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Creates a custom page transition animation using the Ionic `AnimationController`.
|
|
947
|
+
*
|
|
948
|
+
* @param baseEl - The base HTML element for the animation.
|
|
949
|
+
* @param opts - Optional parameters for the animation, including:
|
|
950
|
+
* - `enteringEl`: The HTML element that is entering the view.
|
|
951
|
+
* - `leavingEl`: The HTML element that is leaving the view.
|
|
952
|
+
*
|
|
953
|
+
* @returns An object containing the `navAnimation`, which is a composed animation
|
|
954
|
+
* of the entering and leaving animations.
|
|
955
|
+
*
|
|
956
|
+
* The entering animation fades in and slides the element upwards, while the leaving
|
|
957
|
+
* animation fades out and slides the element downwards. Both animations use a cubic-bezier
|
|
958
|
+
* easing function for smooth transitions.
|
|
1063
959
|
*/
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
960
|
+
const decafPageTransition = (baseEl, opts) => {
|
|
961
|
+
const animationCtrl = new AnimationController();
|
|
962
|
+
const enteringAnimation = animationCtrl
|
|
963
|
+
.create()
|
|
964
|
+
.addElement(opts?.['enteringEl'])
|
|
965
|
+
.duration(280)
|
|
966
|
+
.easing('cubic-bezier(0.36,0.66,0.04,1)')
|
|
967
|
+
.fromTo('opacity', '0.01', '1')
|
|
968
|
+
.fromTo('transform', 'translateY(40px)', 'translateY(0)');
|
|
969
|
+
const leavingAnimation = animationCtrl
|
|
970
|
+
.create()
|
|
971
|
+
.addElement(opts?.['leavingEl'])
|
|
972
|
+
.duration(200)
|
|
973
|
+
.easing('cubic-bezier(0.36,0.66,0.04,1)')
|
|
974
|
+
.fromTo('opacity', '1', '0')
|
|
975
|
+
.fromTo('transform', 'translateY(0)', 'translateY(20px)');
|
|
976
|
+
return animationCtrl.create().addAnimation([enteringAnimation, leavingAnimation]);
|
|
977
|
+
};
|
|
978
|
+
function provideDecafPageTransition() {
|
|
979
|
+
return provideIonicAngular({
|
|
980
|
+
navAnimation: decafPageTransition,
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
function provideDecafDarkMode() {
|
|
984
|
+
return provideEnvironmentInitializer(() => {
|
|
985
|
+
const doc = getWindowDocument();
|
|
986
|
+
doc?.documentElement.classList.add('has-dark-mode');
|
|
987
|
+
});
|
|
1067
988
|
}
|
|
1068
989
|
/**
|
|
1069
|
-
* @
|
|
1070
|
-
* @
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
* and adjusting UI elements based on the user's preferred color scheme.
|
|
1074
|
-
*
|
|
1075
|
-
* @return {Promise<boolean>} True if the system is in dark mode, false otherwise
|
|
1076
|
-
*
|
|
1077
|
-
* @function isDarkMode
|
|
1078
|
-
* @memberOf module:for-angular
|
|
990
|
+
* @const {Logger}
|
|
991
|
+
* @private
|
|
992
|
+
* @description Base logger instance for the for-angular module.ẑ
|
|
993
|
+
* @memberOf module:lib/for-angular-common.module
|
|
1079
994
|
*/
|
|
1080
|
-
|
|
1081
|
-
const { matches } = getWindow().matchMedia('(prefers-color-scheme: dark)');
|
|
1082
|
-
return matches;
|
|
1083
|
-
}
|
|
995
|
+
const log = Logging.for('for-angular');
|
|
1084
996
|
/**
|
|
1085
|
-
* @description
|
|
1086
|
-
* @summary
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1089
|
-
* @param {string |
|
|
1090
|
-
* @
|
|
1091
|
-
* @
|
|
1092
|
-
* @
|
|
997
|
+
* @description Retrieves a logger instance for the given context.
|
|
998
|
+
* @summary Creates or retrieves a namespaced logger instance using the Decaf logging system.
|
|
999
|
+
* The logger is automatically namespaced under "for-angular" and can be further scoped
|
|
1000
|
+
* to a specific instance, function, or string identifier.
|
|
1001
|
+
* @param {string | FunctionLike | unknown} instance - The instance, function, or string to scope the logger to
|
|
1002
|
+
* @return {Logger} Logger instance for the specified context
|
|
1003
|
+
* @memberOf module:lib/for-angular-common.module
|
|
1004
|
+
* @example
|
|
1005
|
+
* // Get logger for a class
|
|
1006
|
+
* const logger = getLogger(MyComponent);
|
|
1007
|
+
* logger.info('Component initialized');
|
|
1093
1008
|
*
|
|
1094
|
-
*
|
|
1095
|
-
*
|
|
1009
|
+
* // Get logger with string identifier
|
|
1010
|
+
* const serviceLogger = getLogger('UserService');
|
|
1011
|
+
* serviceLogger.error('Operation failed', error);
|
|
1096
1012
|
*/
|
|
1097
|
-
function
|
|
1098
|
-
|
|
1099
|
-
original = original.split(' ');
|
|
1100
|
-
return (original.filter((str) => contain ? str.includes(value) : !str.includes(value)) || []).join(' ');
|
|
1013
|
+
function getLogger(instance) {
|
|
1014
|
+
return log.for(instance);
|
|
1101
1015
|
}
|
|
1016
|
+
|
|
1102
1017
|
/**
|
|
1103
|
-
* @
|
|
1018
|
+
* @module module:lib/engine/ValidatorFactory
|
|
1019
|
+
* @description Factory for generating Angular ValidatorFn from Decaf validation metadata.
|
|
1020
|
+
* @summary ValidatorFactory maps validation keys defined by the Decaf validation system
|
|
1021
|
+
* into Angular ValidatorFn instances. It supports type-based resolution and comparison
|
|
1022
|
+
* validators and provides helpers to create proxies for nested control validation.
|
|
1104
1023
|
*
|
|
1105
|
-
* @
|
|
1106
|
-
* @param {IMenuItem[]} menu - An array of menu items to search within.
|
|
1107
|
-
* @returns {string} The icon associated with the menu item if found, otherwise an empty string.
|
|
1024
|
+
* @link {@link ValidatorFactory}
|
|
1108
1025
|
*/
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1026
|
+
class ValidatorFactory {
|
|
1027
|
+
/**
|
|
1028
|
+
* @summary Extracts and parses the value from an Angular control.
|
|
1029
|
+
* @description Retrieves the control's value and converts it to the appropriate type based on field properties.
|
|
1030
|
+
* Handles undefined values gracefully by returning undefined without parsing.
|
|
1031
|
+
*
|
|
1032
|
+
* @param {AbstractControl} control - The Angular form control to extract the value from.
|
|
1033
|
+
* @param {string} fieldType - The declared type of the field.
|
|
1034
|
+
* @param {FieldProperties} fieldProps - The field properties containing type conversion metadata.
|
|
1035
|
+
* @returns {unknown} The parsed value or undefined if the control value is undefined.
|
|
1036
|
+
*/
|
|
1037
|
+
static getFieldValue(control, fieldType, fieldProps) {
|
|
1038
|
+
return typeof control.value !== 'undefined'
|
|
1039
|
+
? parseValueByType(fieldType, control.value, fieldProps)
|
|
1040
|
+
: undefined;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* @summary Resolves the effective field type from multiple possible type sources.
|
|
1044
|
+
* @description Determines the field's type by checking customTypes, subType, or type in order of priority.
|
|
1045
|
+
* For checkbox and array fields with options, converts the type to STRING for proper validation.
|
|
1046
|
+
*
|
|
1047
|
+
* @param {string} [type] - The primary field type.
|
|
1048
|
+
* @param {string | string[]} [customTypes] - Custom type definition with highest priority.
|
|
1049
|
+
* @param {unknown[]} [options] - Available options for the field (affects checkbox/array handling).
|
|
1050
|
+
* @param {string} [subType] - Secondary type definition.
|
|
1051
|
+
* @returns {string} The resolved field type.
|
|
1052
|
+
*/
|
|
1053
|
+
static getFieldType(type, customTypes, options, subType) {
|
|
1054
|
+
const fieldType = (customTypes || subType || type);
|
|
1055
|
+
if ((fieldType === HTML5InputTypes.CHECKBOX || fieldType === Array.name) &&
|
|
1056
|
+
Array.isArray(options)) {
|
|
1057
|
+
return Primitives.STRING;
|
|
1058
|
+
}
|
|
1059
|
+
return fieldType;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* @summary Creates a ValidatorFn for a specific validation key.
|
|
1063
|
+
* @description Generates an Angular ValidatorFn that applies Decaf validation logic to a form control.
|
|
1064
|
+
* Resolves the appropriate validator based on field type, parses the control value, constructs validation properties,
|
|
1065
|
+
* and handles comparison validators through proxy access to parent/child form values.
|
|
1066
|
+
*
|
|
1067
|
+
* @param {FieldProperties} fieldProps - The field properties containing type and validation metadata.
|
|
1068
|
+
* @param {string} key - The validation key to create a validator for.
|
|
1069
|
+
* @returns {ValidatorFn} A validator function that returns ValidationErrors or null.
|
|
1070
|
+
* @throws {Error} If the validation key is not supported.
|
|
1071
|
+
*/
|
|
1072
|
+
static spawn(fieldProps, key) {
|
|
1073
|
+
if (!Validation.keys().includes(key)) {
|
|
1074
|
+
throw new Error('Unsupported custom validation');
|
|
1075
|
+
}
|
|
1076
|
+
const validatorFn = (control) => {
|
|
1077
|
+
const { type, customTypes, options, subType } = fieldProps || {};
|
|
1078
|
+
const fieldType = ValidatorFactory.getFieldType(type, customTypes, options, subType);
|
|
1079
|
+
const customValidator = key === UIKeys.TYPE && subType;
|
|
1080
|
+
const { validatorKey, props } = this.resolveValidatorKeyProps(key, fieldType, fieldProps, customValidator ? subType : undefined);
|
|
1081
|
+
const validator = Validation.get(validatorKey);
|
|
1082
|
+
// parseValueByType does not support undefined values
|
|
1083
|
+
const value = ValidatorFactory.getFieldValue(control, fieldType, fieldProps);
|
|
1084
|
+
// Create a proxy to enable access to parent and child values
|
|
1085
|
+
const proxy = ValidatorFactory.getValidatorProxy(control, key);
|
|
1086
|
+
let errs;
|
|
1087
|
+
try {
|
|
1088
|
+
const validationMessage = fieldProps?.validationMessage;
|
|
1089
|
+
if (validationMessage && customValidator) {
|
|
1090
|
+
Object.assign(props, { message: validationMessage });
|
|
1091
|
+
}
|
|
1092
|
+
errs = validator.hasErrors(value, props, proxy);
|
|
1093
|
+
}
|
|
1094
|
+
catch (e) {
|
|
1095
|
+
errs = `${key} validator failed to validate: ${e}`;
|
|
1096
|
+
getLogger(ValidatorFactory).error(errs);
|
|
1097
|
+
}
|
|
1098
|
+
return errs ? { [validatorKey]: props?.['message'] ? errs : true } : null;
|
|
1099
|
+
};
|
|
1100
|
+
Object.defineProperty(validatorFn, 'name', {
|
|
1101
|
+
value: `${key}Validator`,
|
|
1102
|
+
});
|
|
1103
|
+
return validatorFn;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Retrieves the validator value from field properties, with special handling for checkbox types.
|
|
1107
|
+
* Returns the field property value, or the type if the field is a checkbox and the types differ.
|
|
1108
|
+
*
|
|
1109
|
+
* @param key - The validation key.
|
|
1110
|
+
* @param type - The field's type.
|
|
1111
|
+
* @param fieldProps - The field properties object.
|
|
1112
|
+
* @returns The validator value to use.
|
|
1113
|
+
*/
|
|
1114
|
+
static getValidatorValue(key, type, fieldProps) {
|
|
1115
|
+
return key === ValidationKeys.TYPE &&
|
|
1116
|
+
HTML5InputTypes.CHECKBOX &&
|
|
1117
|
+
fieldProps[key] !== type
|
|
1118
|
+
? type
|
|
1119
|
+
: fieldProps[key];
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Determines whether a validation should be resolved based on the field's type.
|
|
1123
|
+
* Returns true for TYPE validations with custom types or pattern-based validators.
|
|
1124
|
+
*
|
|
1125
|
+
* @param key - The validation key to evaluate.
|
|
1126
|
+
* @param type - The field's declared type.
|
|
1127
|
+
* @param customTypes - Optional custom type definition.
|
|
1128
|
+
* @returns True if validation should use type-based resolution.
|
|
1129
|
+
*/
|
|
1130
|
+
static isTypeBasedValidation(key, type, customTypes) {
|
|
1131
|
+
return (key === ValidationKeys.TYPE &&
|
|
1132
|
+
(typeof customTypes === Primitives.STRING || Object.keys(patternValidators).includes(type)));
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* @summary Creates or retrieves a proxy for validator access to parent/child form values.
|
|
1136
|
+
* @description Returns a PathProxy configured for the given validation key.
|
|
1137
|
+
* For comparison validators, creates a proxy rooted at the parent FormGroup to enable cross-field validation.
|
|
1138
|
+
* For other validators, returns an empty proxy for type safety.
|
|
1139
|
+
*
|
|
1140
|
+
* @param {AbstractControl | FormGroup} control - The form control to create a proxy for.
|
|
1141
|
+
* @param {ComparisonValidationKey} key - The validation key determining proxy scope.
|
|
1142
|
+
* @returns {PathProxy<unknown>} A proxy object for form value access.
|
|
1143
|
+
*/
|
|
1144
|
+
static getValidatorProxy(control, key) {
|
|
1145
|
+
const proxy = ValidatorFactory.createProxy({});
|
|
1146
|
+
if (Object.values(ComparisonValidationKeys).includes(key)) {
|
|
1147
|
+
return ValidatorFactory.createProxy((control instanceof FormGroup ? control : control.parent));
|
|
1148
|
+
}
|
|
1149
|
+
return proxy;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* @summary Creates a proxy wrapper for an Angular AbstractControl to assist with custom validation logic.
|
|
1153
|
+
* @description Returns a structured proxy object that simulates a hierarchical tree of form values.
|
|
1154
|
+
* Enables Validators handling method to access parent and child properties using consistent dot-notation in Angular forms.
|
|
1155
|
+
*
|
|
1156
|
+
* @param {AbstractControl} control - The control to wrap in a proxy.
|
|
1157
|
+
* @returns {PathProxy<unknown>} A proxy object exposing form values and enabling recursive parent access.
|
|
1158
|
+
*/
|
|
1159
|
+
static createProxy(control) {
|
|
1160
|
+
return PathProxyEngine.create(control, {
|
|
1161
|
+
getValue(target, prop) {
|
|
1162
|
+
if (target instanceof FormControl)
|
|
1163
|
+
return target.value;
|
|
1164
|
+
if (target instanceof FormGroup) {
|
|
1165
|
+
const control = target.controls[prop];
|
|
1166
|
+
return control instanceof FormControl ? control.value : control;
|
|
1167
|
+
}
|
|
1168
|
+
// const value = target[prop];
|
|
1169
|
+
// if (value instanceof FormControl)
|
|
1170
|
+
// return value.value;
|
|
1171
|
+
//
|
|
1172
|
+
// if (value instanceof FormGroup) {
|
|
1173
|
+
// const control = value.controls[prop];
|
|
1174
|
+
// return control instanceof FormControl ? control.value : control;
|
|
1175
|
+
// }
|
|
1176
|
+
return target?.[prop];
|
|
1177
|
+
},
|
|
1178
|
+
getParent: function (target) {
|
|
1179
|
+
return target?.['_parent'];
|
|
1180
|
+
},
|
|
1181
|
+
ignoreUndefined: true,
|
|
1182
|
+
ignoreNull: true,
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
*
|
|
1187
|
+
* Resolves the correct validator key and its associated properties based on the input key and type.
|
|
1188
|
+
*
|
|
1189
|
+
* When the validation key is TYPE, it's necessary to resolve the actual validator based on the
|
|
1190
|
+
* field's type (e.g., 'password', 'email', 'url') instead of using the generic getValidator("type") logic.
|
|
1191
|
+
* This allows directly invoking specific validators like getValidator('password'), ensuring the correct
|
|
1192
|
+
* behavior for type-based validation.
|
|
1193
|
+
*
|
|
1194
|
+
* @param key - The validation key (e.g., 'type', 'required', etc.).
|
|
1195
|
+
* @param value - The value that needs be provided to the validator.
|
|
1196
|
+
* @param type - The field's declared type.
|
|
1197
|
+
* @returns An object containing the resolved validator key and its corresponding props.
|
|
1198
|
+
*/
|
|
1199
|
+
static { this.resolveValidatorKeyProps = (key, type, fieldProps, customTypes = undefined) => {
|
|
1200
|
+
const isTypeBased = this.isTypeBasedValidation(key, type, customTypes);
|
|
1201
|
+
const validatorKey = isTypeBased ? type : key;
|
|
1202
|
+
const value = this.getValidatorValue(key, type, fieldProps);
|
|
1203
|
+
const props = this.getValidatorProps(validatorKey, type, isTypeBased, value);
|
|
1204
|
+
return { validatorKey, props };
|
|
1205
|
+
}; }
|
|
1206
|
+
/**
|
|
1207
|
+
* Constructs the properties object to be passed to a validator.
|
|
1208
|
+
* Handles translation of string values and applies pattern validators for type-based validations.
|
|
1209
|
+
*
|
|
1210
|
+
* @param validatorKey - The resolved validator key.
|
|
1211
|
+
* @param type - The field's type.
|
|
1212
|
+
* @param isTypeBased - Whether this is a type-based validation.
|
|
1213
|
+
* @param value - The value to validate.
|
|
1214
|
+
* @returns An object containing validator properties.
|
|
1215
|
+
*/
|
|
1216
|
+
static getValidatorProps(validatorKey, type, isTypeBased, value) {
|
|
1217
|
+
return {
|
|
1218
|
+
[validatorKey]: !isTypeBased && validatorKey === ValidationKeys.TYPE
|
|
1219
|
+
? NgxRenderingEngine.get().translate(value, false)
|
|
1220
|
+
: value,
|
|
1221
|
+
// Email, Password, and URL are validated using the "pattern" key
|
|
1222
|
+
...(isTypeBased && { [ValidationKeys.PATTERN]: patternValidators[type] }),
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1112
1225
|
}
|
|
1113
1226
|
|
|
1114
1227
|
/**
|
|
@@ -1989,18 +2102,19 @@ class NgxFormService {
|
|
|
1989
2102
|
static fromProps(props, updateMode = 'change') {
|
|
1990
2103
|
const validators = this.validatorsFromProps(props);
|
|
1991
2104
|
const composed = validators.length ? Validators.compose(validators) : null;
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
? props.
|
|
1995
|
-
?
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
?
|
|
2000
|
-
? undefined
|
|
2001
|
-
: props.value
|
|
2105
|
+
const value = props.value
|
|
2106
|
+
? props.type === HTML5InputTypes.CHECKBOX
|
|
2107
|
+
? Array.isArray(props.value)
|
|
2108
|
+
? props.value
|
|
2109
|
+
: undefined
|
|
2110
|
+
: props.type === HTML5InputTypes.DATE
|
|
2111
|
+
? !isValidDate$1(parseDate(props.format, props.value))
|
|
2112
|
+
? undefined
|
|
2002
2113
|
: props.value
|
|
2003
|
-
|
|
2114
|
+
: props.value
|
|
2115
|
+
: undefined;
|
|
2116
|
+
return new FormControl({
|
|
2117
|
+
value,
|
|
2004
2118
|
disabled: props.disabled,
|
|
2005
2119
|
}, {
|
|
2006
2120
|
validators: composed,
|
|
@@ -2389,12 +2503,11 @@ class NgxRenderingEngine extends RenderingEngine {
|
|
|
2389
2503
|
}
|
|
2390
2504
|
const { inputs: possibleInputs } = componentMetadata;
|
|
2391
2505
|
const inputs = { ...fieldDef.props };
|
|
2392
|
-
const unmappedKeys = Object.keys(inputs).filter((input) => {
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
});
|
|
2506
|
+
// const unmappedKeys = Object.keys(inputs).filter((input) => {
|
|
2507
|
+
// const isMapped = possibleInputs.find(({ propName }) => propName === input);
|
|
2508
|
+
// if (!isMapped) delete inputs[input];
|
|
2509
|
+
// return !isMapped;
|
|
2510
|
+
// });
|
|
2398
2511
|
// if (unmappedKeys.length > 0 && isDevelopmentMode())
|
|
2399
2512
|
// getLogger(this).warn(
|
|
2400
2513
|
// `Unmapped input properties for component ${fieldDef.tag}: ${unmappedKeys.join(', ')}`,
|
|
@@ -2789,7 +2902,8 @@ var operations = {
|
|
|
2789
2902
|
multiple: {
|
|
2790
2903
|
success: "Successfully processed all operations.",
|
|
2791
2904
|
error: "Error processing operations."
|
|
2792
|
-
}
|
|
2905
|
+
},
|
|
2906
|
+
processing: "Processing {0}..."
|
|
2793
2907
|
};
|
|
2794
2908
|
var component = {
|
|
2795
2909
|
crud_form: {
|
|
@@ -3611,6 +3725,12 @@ class NgxRepositoryDirective extends DecafComponent {
|
|
|
3611
3725
|
attr = (this.filterBy || this.pk);
|
|
3612
3726
|
}
|
|
3613
3727
|
const condition = this.filter || Condition.attribute(attr);
|
|
3728
|
+
if (!this.repository) {
|
|
3729
|
+
const repo = getModelAndRepository(Model.tableName(this.model));
|
|
3730
|
+
if (repo) {
|
|
3731
|
+
this._repository = repo.repository;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3614
3734
|
const type = this.getModelPropertyType(this.repository.class, attr);
|
|
3615
3735
|
if (this.modelId) {
|
|
3616
3736
|
return condition.eq([Primitives.NUMBER, Primitives.BIGINT].includes(type)
|
|
@@ -3760,7 +3880,16 @@ class NgxRepositoryDirective extends DecafComponent {
|
|
|
3760
3880
|
return Metadata.properties(clazz);
|
|
3761
3881
|
}
|
|
3762
3882
|
getModelPropertyType(constructor, prop) {
|
|
3763
|
-
|
|
3883
|
+
try {
|
|
3884
|
+
return Metadata.type(constructor, prop)?.name;
|
|
3885
|
+
}
|
|
3886
|
+
catch (error) {
|
|
3887
|
+
this.log
|
|
3888
|
+
.for(this)
|
|
3889
|
+
.info(`${error?.message || String(error)}. Tryng get with table Name`);
|
|
3890
|
+
constructor = Model.get(Model.tableName(this.repository.class));
|
|
3891
|
+
return Metadata.type(constructor, prop)?.name;
|
|
3892
|
+
}
|
|
3764
3893
|
}
|
|
3765
3894
|
getModelPkType(clazz) {
|
|
3766
3895
|
return this.getModelPropertyType(clazz, Model.pk(clazz));
|
|
@@ -3816,6 +3945,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
3816
3945
|
type: Input
|
|
3817
3946
|
}] } });
|
|
3818
3947
|
|
|
3948
|
+
class NgxTranslateService extends DecafTranslateService {
|
|
3949
|
+
constructor() {
|
|
3950
|
+
super(...arguments);
|
|
3951
|
+
this.translateService = inject(TranslateService);
|
|
3952
|
+
}
|
|
3953
|
+
instant(key, interpolateParams) {
|
|
3954
|
+
return this.translateService.instant(key, interpolateParams);
|
|
3955
|
+
}
|
|
3956
|
+
translate(key, params) {
|
|
3957
|
+
return this.instant(key, params);
|
|
3958
|
+
}
|
|
3959
|
+
async get(key, params) {
|
|
3960
|
+
if (key) {
|
|
3961
|
+
if (typeof params === Primitives.STRING) {
|
|
3962
|
+
params = { '0': params };
|
|
3963
|
+
}
|
|
3964
|
+
return await firstValueFrom(this.translateService.get(key, (params || {})));
|
|
3965
|
+
}
|
|
3966
|
+
return key;
|
|
3967
|
+
}
|
|
3968
|
+
setFallbackLang(lang) {
|
|
3969
|
+
return this.translateService.setFallbackLang(lang);
|
|
3970
|
+
}
|
|
3971
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NgxTranslateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3972
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NgxTranslateService, providedIn: 'root' }); }
|
|
3973
|
+
}
|
|
3974
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NgxTranslateService, decorators: [{
|
|
3975
|
+
type: Injectable,
|
|
3976
|
+
args: [{
|
|
3977
|
+
providedIn: 'root',
|
|
3978
|
+
}]
|
|
3979
|
+
}] });
|
|
3980
|
+
|
|
3819
3981
|
/**
|
|
3820
3982
|
* @module lib/engine/NgxComponentDirective
|
|
3821
3983
|
* @description Base decaf component abstraction providing shared inputs and utilities.
|
|
@@ -4047,10 +4209,10 @@ class NgxComponentDirective extends NgxRepositoryDirective {
|
|
|
4047
4209
|
* Used to translate button labels, validation messages, and other text content based
|
|
4048
4210
|
* on the current locale setting, enabling multilingual support throughout the application.
|
|
4049
4211
|
* @protected
|
|
4050
|
-
* @type {
|
|
4212
|
+
* @type {NgxTranslateService}
|
|
4051
4213
|
* @memberOf module:lib/engine/NgxComponentDirective
|
|
4052
4214
|
*/
|
|
4053
|
-
this.translateService = inject(
|
|
4215
|
+
this.translateService = inject(NgxTranslateService);
|
|
4054
4216
|
/**
|
|
4055
4217
|
* @description Event emitter for custom component events.
|
|
4056
4218
|
* @summary Emits custom events that occur within child components or the component itself.
|
|
@@ -4287,7 +4449,7 @@ class NgxComponentDirective extends NgxRepositoryDirective {
|
|
|
4287
4449
|
this._repository = repository;
|
|
4288
4450
|
if (this.model && !this.pk)
|
|
4289
4451
|
this.pk = pk;
|
|
4290
|
-
this.pkType = pkType
|
|
4452
|
+
this.pkType = pkType;
|
|
4291
4453
|
if (!this.modelName)
|
|
4292
4454
|
this.modelName = repository.class.name;
|
|
4293
4455
|
}
|
|
@@ -4383,11 +4545,7 @@ class NgxComponentDirective extends NgxRepositoryDirective {
|
|
|
4383
4545
|
* @memberOf module:lib/engine/NgxComponentDirective
|
|
4384
4546
|
*/
|
|
4385
4547
|
async translate(phrase, params) {
|
|
4386
|
-
|
|
4387
|
-
return '';
|
|
4388
|
-
if (typeof params === Primitives.STRING)
|
|
4389
|
-
params = { '0': params };
|
|
4390
|
-
return await firstValueFrom(this.translateService.get(phrase, (params || {})));
|
|
4548
|
+
return await this.translateService.get(phrase, params || {});
|
|
4391
4549
|
}
|
|
4392
4550
|
checkDarkMode() {
|
|
4393
4551
|
this.mediaService.isDarkMode().subscribe((isDark) => {
|
|
@@ -4650,9 +4808,9 @@ class NgxComponentDirective extends NgxRepositoryDirective {
|
|
|
4650
4808
|
this.listenEvent.emit(event);
|
|
4651
4809
|
}
|
|
4652
4810
|
// passed for ui decorators
|
|
4653
|
-
async submit(...args) {
|
|
4654
|
-
|
|
4655
|
-
}
|
|
4811
|
+
// override async submit(...args: unknown[]): Promise<any> {
|
|
4812
|
+
// this.log.for(this.submit).info(`submit for ${this.componentName} with ${JSON.stringify(args)}`);
|
|
4813
|
+
// }
|
|
4656
4814
|
/**
|
|
4657
4815
|
* @description Determines if a specific operation is allowed in the current context.
|
|
4658
4816
|
* @summary This method checks if an operation is included in the list of available
|
|
@@ -5856,6 +6014,13 @@ class NgxFormFieldDirective extends NgxComponentDirective {
|
|
|
5856
6014
|
* @public
|
|
5857
6015
|
*/
|
|
5858
6016
|
this.optionsMapper = {};
|
|
6017
|
+
/**
|
|
6018
|
+
* @description Whether the field is currently checked.
|
|
6019
|
+
* @summary Used for checkbox and radio button fields to track the checked state independently from the value.
|
|
6020
|
+
* @type {boolean}
|
|
6021
|
+
* @default false
|
|
6022
|
+
* @public
|
|
6023
|
+
*/
|
|
5859
6024
|
this.checked = false;
|
|
5860
6025
|
/**
|
|
5861
6026
|
* @description Flag tracking if validation error event has been dispatched.
|
|
@@ -5882,7 +6047,7 @@ class NgxFormFieldDirective extends NgxComponentDirective {
|
|
|
5882
6047
|
* @type {function(string, ...string): string}
|
|
5883
6048
|
* @public
|
|
5884
6049
|
*/
|
|
5885
|
-
this.sf = sf
|
|
6050
|
+
this.sf = sf;
|
|
5886
6051
|
/**
|
|
5887
6052
|
* @description Callback function invoked when the field value changes.
|
|
5888
6053
|
* @summary Function registered by Angular's forms system through registerOnChange.
|
|
@@ -5901,6 +6066,9 @@ class NgxFormFieldDirective extends NgxComponentDirective {
|
|
|
5901
6066
|
*/
|
|
5902
6067
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
5903
6068
|
this.onTouch = () => { };
|
|
6069
|
+
if (!UIValidator.translateService) {
|
|
6070
|
+
UIValidator.translateService = this.translateService;
|
|
6071
|
+
}
|
|
5904
6072
|
}
|
|
5905
6073
|
/**
|
|
5906
6074
|
* @description Gets the currently active form group based on context.
|
|
@@ -6072,13 +6240,54 @@ class NgxFormFieldDirective extends NgxComponentDirective {
|
|
|
6072
6240
|
* @return {void}
|
|
6073
6241
|
* @public
|
|
6074
6242
|
*/
|
|
6075
|
-
handleModalChildChanges(event) {
|
|
6243
|
+
handleModalChildChanges(event, formControl) {
|
|
6244
|
+
const element = this.component?.nativeElement;
|
|
6076
6245
|
if (this.type === HTML5InputTypes.SELECT && event) {
|
|
6077
6246
|
const { value } = event.detail;
|
|
6078
6247
|
this.value = value;
|
|
6079
6248
|
}
|
|
6080
|
-
if (
|
|
6249
|
+
if (element && formControl && !formControl.valid && formControl.updateOn === 'change') {
|
|
6250
|
+
element.dispatchEvent(new Event('ionBlur'));
|
|
6251
|
+
}
|
|
6252
|
+
if (this.isModalChild) {
|
|
6081
6253
|
this.changeDetectorRef.detectChanges();
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6256
|
+
validateControl(formControl) {
|
|
6257
|
+
return ((!formControl.valid && (formControl.touched || !formControl.pristine)) ||
|
|
6258
|
+
(!formControl.valid && formControl.dirty && formControl.updateOn === 'change'));
|
|
6259
|
+
}
|
|
6260
|
+
getErrorMessage(error) {
|
|
6261
|
+
const instance = this;
|
|
6262
|
+
let { message, key } = error;
|
|
6263
|
+
const prop = instance[key];
|
|
6264
|
+
let args = [];
|
|
6265
|
+
if (typeof message === Primitives.STRING) {
|
|
6266
|
+
if (message.includes('|')) {
|
|
6267
|
+
const parts = message.split('|');
|
|
6268
|
+
message = parts[0];
|
|
6269
|
+
parts.shift();
|
|
6270
|
+
parts.forEach((part) => {
|
|
6271
|
+
args = [...args, part];
|
|
6272
|
+
});
|
|
6273
|
+
}
|
|
6274
|
+
else {
|
|
6275
|
+
if (prop) {
|
|
6276
|
+
args = [prop];
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
else {
|
|
6281
|
+
args = Object.values(message).map((v) => `${v || prop}`);
|
|
6282
|
+
message = key;
|
|
6283
|
+
}
|
|
6284
|
+
// Check if string already be translated by validator
|
|
6285
|
+
const translate = (message, args) => {
|
|
6286
|
+
return this.translateService.instant(!message.includes(AngularEngineKeys.ERRORS)
|
|
6287
|
+
? `${AngularEngineKeys.ERRORS}.${message}`
|
|
6288
|
+
: message, args);
|
|
6289
|
+
};
|
|
6290
|
+
return /\s/.test(message) && message !== key ? message : translate(message, args);
|
|
6082
6291
|
}
|
|
6083
6292
|
/**
|
|
6084
6293
|
* @description Retrieves validation error messages for the field.
|
|
@@ -6095,11 +6304,15 @@ class NgxFormFieldDirective extends NgxComponentDirective {
|
|
|
6095
6304
|
const accordionComponent = parent
|
|
6096
6305
|
.closest('ngx-decaf-fieldset')
|
|
6097
6306
|
?.querySelector('ion-accordion-group');
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
message:
|
|
6102
|
-
|
|
6307
|
+
const invalid = this.validateControl(formControl);
|
|
6308
|
+
if (invalid) {
|
|
6309
|
+
const errors = Object.entries(formControl.errors ?? {}).map(([key, value]) => {
|
|
6310
|
+
const message = typeof value === 'boolean' ? key : value;
|
|
6311
|
+
return {
|
|
6312
|
+
key: key,
|
|
6313
|
+
message,
|
|
6314
|
+
};
|
|
6315
|
+
});
|
|
6103
6316
|
if (errors.length) {
|
|
6104
6317
|
if (accordionComponent && !this.validationErrorEventDispatched) {
|
|
6105
6318
|
const validationErrorEvent = new CustomEvent(ComponentEventNames.ValidationError, {
|
|
@@ -6111,8 +6324,7 @@ class NgxFormFieldDirective extends NgxComponentDirective {
|
|
|
6111
6324
|
}
|
|
6112
6325
|
}
|
|
6113
6326
|
for (const error of errors) {
|
|
6114
|
-
|
|
6115
|
-
return `* ${this.translateService.instant(`errors.${error?.['message']}`, { '0': `${instance[error?.['key']] ?? ''}` })}`;
|
|
6327
|
+
return `* ${this.getErrorMessage(error)}`;
|
|
6116
6328
|
}
|
|
6117
6329
|
}
|
|
6118
6330
|
}
|
|
@@ -7164,7 +7376,6 @@ let CrudFieldComponent = class CrudFieldComponent extends NgxFormFieldDirective
|
|
|
7164
7376
|
* @summary Determines the fill style of the field, such as 'outline' or 'solid'.
|
|
7165
7377
|
* This affects the border and background of the field.
|
|
7166
7378
|
*
|
|
7167
|
-
* @type {'outline' | 'solid'}
|
|
7168
7379
|
* @default 'outline'
|
|
7169
7380
|
* @memberOf CrudFieldComponent
|
|
7170
7381
|
*/
|
|
@@ -7391,7 +7602,7 @@ let CrudFieldComponent = class CrudFieldComponent extends NgxFormFieldDirective
|
|
|
7391
7602
|
return this.formControl.value.includes(value);
|
|
7392
7603
|
}
|
|
7393
7604
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CrudFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
7394
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: CrudFieldComponent, isStandalone: true, selector: "ngx-decaf-crud-field", inputs: { operation: "operation", name: "name", className: "className", path: "path", childOf: "childOf", type: "type", value: "value", disabled: "disabled", label: "label", placeholder: "placeholder", format: "format", hidden: "hidden", max: "max", maxlength: "maxlength", min: "min", minlength: "minlength", pattern: "pattern", readonly: "readonly", required: "required", step: "step", equals: "equals", different: "different", lessThan: "lessThan", lessThanOrEqual: "lessThanOrEqual", greaterThan: "greaterThan", greaterThanOrEqual: "greaterThanOrEqual", alignment: "alignment", checked: "checked", justify: "justify", cancelText: "cancelText", interface: "interface", options: "options", mode: "mode", spellcheck: "spellcheck", startEmpty: "startEmpty", inputmode: "inputmode", autocomplete: "autocomplete", fill: "fill", labelPlacement: "labelPlacement", updateOn: "updateOn", formGroup: "formGroup", formControl: "formControl", multiple: "multiple", uid: "uid", page: "page" }, host: { properties: { "attr.id": "uid", "attr.class": "className" } }, viewQueries: [{ propertyName: "component", first: true, predicate: ["component"], descendants: true, read: ElementRef }], usesInheritance: true, ngImport: i0, template: "@if (operation === OperationKeys.READ || operation === OperationKeys.DELETE || readonly) {\n @if (!hidden) {\n <ng-container>\n <div>\n <ion-item\n [class.dcf-item-readonly]=\"readonly && operation !== OperationKeys.READ\"\n [class]=\"'dcf-input-item ' + operation\"\n #component\n >\n <ion-label>\n @if (refreshing) {\n <br />\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n } @else {\n {{ label | translate }}\n <br />\n @if (type !== 'checkbox' && value) {\n <ion-text [innerHTML]=\"type === 'password' ? '********' : value\"></ion-text>\n } @else {\n @if (type === 'checkbox') {\n <ion-badge mode=\"md\" color=\"light\">\n <ion-text [color]=\"!value ? 'danger' : 'primary'\">{{ value }}</ion-text>\n </ion-badge>\n } @else {\n <ion-text [innerHTML]=\"value\"></ion-text>\n }\n }\n }\n </ion-label>\n </ion-item>\n </div>\n </ng-container>\n }\n} @else {\n @if (formControl) {\n <ng-container [formGroup]=\"multiple ? activeFormGroup : formControl.parent\">\n <div\n [id]=\"uid\"\n #container\n [class]=\"'dcf-input-item ' + (operation || 'create')\"\n [class.dcf-field-required]=\"required\"\n [class.dcf-field-readonly]=\"readonly\"\n >\n @if (type === HTML5InputTypes.TEXTAREA) {\n <ion-textarea\n (ionInput)=\"handleModalChildChanges()\"\n (ionChange)=\"handleModalChildChanges()\"\n [id]=\"name\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [autoGrow]=\"true\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [attr.readonly]=\"readonly !== undefined ? readonly : null\"\n [inputmode]=\"inputmode\"\n [spellcheck]=\"spellcheck\"\n [rows]=\"rows\"\n [labelPlacement]=\"labelPlacement\"\n [value]=\"value\"\n [fill]=\"fill\"\n [errorText]=\"getErrors(container)\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [label]=\"label | translate\"\n #component\n ></ion-textarea>\n } @else if (type === HTML5InputTypes.CHECKBOX) {\n @if (!options?.length) {\n <ion-item class=\"dcf-width-1-1\" [hidden]=\"hidden\">\n <ion-checkbox\n (ionInput)=\"handleModalChildChanges()\"\n (ionChange)=\"handleModalChildChanges()\"\n [id]=\"name\"\n [mode]=\"'md'\"\n [errorText]=\"getErrors(container)\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify || 'start'\"\n [value]=\"value\"\n [checked]=\"checked\"\n [attr.readonly]=\"readonly\"\n [formControlName]=\"name\"\n #component\n >\n <span>{{ label | translate }}</span>\n </ion-checkbox>\n </ion-item>\n } @else {\n <div class=\"dcf-checkbox-group\">\n <label class=\"dcf-label\" [for]=\"path\">{{ label | translate }}</label>\n @for (option of options; track trackItemFn($index, option.text)) {\n <ion-item class=\"dcf-width-1-1\" [button]=\"true\">\n <ion-checkbox\n (ionInput)=\"handleModalChildChanges()\"\n (ionChange)=\"handleModalChildChanges()\"\n [id]=\"option.text\"\n [mode]=\"'md'\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify\"\n [value]=\"option.value\"\n [attr.readonly]=\"readonly\"\n [checked]=\"isOptionChecked(option.value)\"\n (ionChange)=\"toggleOptionSelection(option.value, $event)\"\n #component\n >\n <span>{{ $index + 1 }}. {{ option?.text | translate }}</span>\n </ion-checkbox>\n </ion-item>\n }\n <span class=\"dcf-error\" [innerHTML]=\"getErrors(container)\"></span>\n </div>\n }\n } @else if (type === HTML5InputTypes.RADIO && options?.length) {\n <ion-radio-group\n class=\"dcf-width-1-1\"\n [formControlName]=\"name\"\n [value]=\"value\"\n #component\n >\n <label class=\"dcf-radio-group-label\" [for]=\"path\">{{ label | translate }}</label>\n @for (option of options; track $index) {\n <ion-item>\n <ion-radio\n (ionInput)=\"handleModalChildChanges()\"\n (ionChange)=\"handleModalChildChanges()\"\n [id]=\"name\"\n [errorText]=\"getErrors(container)\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [alignment]=\"alignment\"\n [justify]=\"justify\"\n [attr.readonly]=\"readonly\"\n [value]=\"option.value\"\n >\n {{ option?.text | translate }}\n </ion-radio>\n </ion-item>\n }\n </ion-radio-group>\n <span class=\"dcf-error\" [innerHTML]=\"getErrors(container)\"></span>\n } @else if (type === HTML5InputTypes.SELECT) {\n <div>\n <ion-select\n (ionInput)=\"handleModalChildChanges($event)\"\n (ionChange)=\"handleModalChildChanges($event)\"\n (click)=\"handleModalChildChanges()\"\n [id]=\"name\"\n toggleIcon=\"chevron-down-outline\"\n expandedIcon=\"chevron-up-outline\"\n [interface]=\"interface\"\n [mode]=\"'md'\"\n [hidden]=\"hidden || !options?.length\"\n [labelPlacement]=\"labelPlacement\"\n [label]=\"label | translate\"\n [value]=\"value ?? null\"\n (click)=\"openSelectOptions($event, interface)\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [interface]=\"interface\"\n #component\n >\n @if (options?.length) {\n @for (option of options; track trackItemFn($index, option.text)) {\n <ion-select-option\n [value]=\"option.value\"\n [class.dcf-disabled]=\"option.disabled\"\n [class.dcf-hidden]=\"option.hidden\"\n [hidden]=\"option.hidden\"\n >\n {{ option.text }}\n </ion-select-option>\n }\n }\n @if ((value && !required) || (!required && value !== undefined && value !== '')) {\n <ion-button\n fill=\"clear\"\n (click)=\"handleClearValue($event)\"\n slot=\"end\"\n aria-label=\"Show/hide password\"\n >\n <ngx-decaf-icon size=\"small\" slot=\"icon-only\" [name]=\"'ti-input-x'\" />\n </ion-button>\n }\n </ion-select>\n <div\n class=\"dcf-error\"\n [class.dcf-options-error]=\"!options?.length\"\n [innerHTML]=\"\n options?.length\n ? getErrors(container)\n : ('errors.empty_options' | translate: { '0': name })\n \"\n ></div>\n </div>\n } @else {\n <ion-input\n (ionInput)=\"handleModalChildChanges()\"\n (ionChange)=\"handleModalChildChanges()\"\n [id]=\"name\"\n [type]=\"type\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [inputmode]=\"inputmode\"\n [labelPlacement]=\"labelPlacement\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [attr.readonly]=\"readonly !== undefined ? readonly : null\"\n [max]=\"max !== undefined ? max : null\"\n [min]=\"min !== undefined ? min : null\"\n [pattern]=\"pattern !== undefined ? pattern : null\"\n [step]=\"step !== undefined ? step : null\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [label]=\"label | translate\"\n #component\n />\n }\n </div>\n </ng-container>\n } @else {\n <div>\n <p class=\"dcf-error\">\n {{ 'errors.form.control' | translate: { '0': name } }}\n </p>\n </div>\n }\n}\n", styles: [".dcf-item-readonly .create ion-label,.dcf-item-readonly .update ion-label{font-weight:600;transform:translatey(-.8rem)}.dcf-item-readonly .create ion-label ion-text,.dcf-item-readonly .update ion-label ion-text{font-weight:initial;position:relative;top:.8rem}ion-badge{font-weight:600!important;margin-top:.25rem;font-weight:600}ion-badge ion-text{font-weight:600;text-transform:capitalize}ion-item{--border-color: transparent}ion-item:not(.dcf-input-item){--inner-padding-start: 0rem;--padding-start: 0rem;--background: transparent}ion-item.dcf-input-item{--padding-end: 0rem;--padding-start: 0px !important;--padding-top: 0px !important;--inner-padding-start: .75rem;--background: transparent;--background-hover-opacity: .1;--background-activated-opacity: .15;--background-focused-opacity: .15;--background-hover: var(--dcf-color-primary);--background-focused: var(--dcf-color-primary);--border-color: var(--dcf-color-gray-2)}ion-item.dcf-input-item.dcf-palette-dark{--border-color: var(--dcf-color-gray-6) !important}ion-item.dcf-input-item.read,ion-item.dcf-input-item.delete{--min-height: 30px;padding:unset;margin:unset!important;margin-bottom:var(--dcf-margin-xsmall)!important}ion-item.dcf-input-item.read ion-label,ion-item.dcf-input-item.delete ion-label{font-weight:600;margin-top:0!important;margin-bottom:.25rem!important;font-size:.925rem}ion-item.dcf-input-item.read ion-label:not(:first-of-type),ion-item.dcf-input-item.delete ion-label:not(:first-of-type){margin:100rem}ion-item.dcf-input-item.read span,ion-item.dcf-input-item.read ion-text,ion-item.dcf-input-item.delete span,ion-item.dcf-input-item.delete ion-text{font-weight:400!important;font-size:.825rem;min-height:.5rem!important}ion-item.dcf-input-item.read span:not(.dcf-display-block),ion-item.dcf-input-item.read ion-text:not(.dcf-display-block),ion-item.dcf-input-item.delete span:not(.dcf-display-block),ion-item.dcf-input-item.delete ion-text:not(.dcf-display-block){display:inline-block}ion-item.dcf-input-item.read span.dcf-display-block,ion-item.dcf-input-item.read ion-text.dcf-display-block,ion-item.dcf-input-item.delete span.dcf-display-block,ion-item.dcf-input-item.delete ion-text.dcf-display-block{display:block!important}ion-item.dcf-input-item>*,ion-item.dcf-input-item ion-select{width:100%!important}ion-item.dcf-input-item.create.checkbox+.checkbox,ion-item.dcf-input-item.update.checkbox+.checkbox{margin-top:-.25rem!important}ion-item.dcf-input-item.create ion-item,ion-item.dcf-input-item.update ion-item{--border-color: transparent}ion-item.dcf-input-item.create ion-item.dcf-text-wrap ion-label>*,ion-item.dcf-input-item.update ion-item.dcf-text-wrap ion-label>*{white-space:wrap!important;word-break:break-all!important}ion-textarea textarea{scrollbar-width:thin!important;margin-bottom:.5rem!important}ion-select.dcf-select-label-placement-floating::part(label){line-height:1.2rem!important}.dcf-proccessing,.dcf-proccessing *{pointer-events:none;touch-action:none;cursor:text}ion-checkbox::part(container){border:2px solid var(--dcf-color-primary);margin-left:.5rem;margin-right:.5rem;border-radius:var(--dcf-border-radius-small);padding:3px}ion-checkbox{--size: 1.5rem;--checkbox-background-checked: var(--dcf-color-primary);--checkmark-width: 2px}ion-radio-group .dcf-radio-group-label{font-weight:600}ion-radio-group .dcf-radio-group-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}ion-radio-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-checkbox-group{width:100%}.dcf-checkbox-group .dcf-label{font-weight:600}.dcf-checkbox-group .dcf-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}.dcf-checkbox-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-error{position:absolute;color:var(--dcf-color-danger)!important;font-size:.8rem!important;font-weight:500!important;line-height:1.1rem;z-index:9999}.dcf-error.dcf-options-error{position:relative;margin-bottom:.25rem!important}.dcf-error .ti,.dcf-error ngx-decaf-icon,.dcf-error ion-icon{position:relative;top:2px!important;min-width:20px;font-size:1rem!important;text-align:left}.dcf-helper{font-size:.875rem!important;font-weight:500;margin-top:.25rem;margin-bottom:-.75rem}.dcf-helper.dcf-has-action{cursor:pointer;text-decoration:underline}.dcf-error+.dcf-helper{padding-top:1rem}::ng-deep ion-textarea{min-height:80px!important;scrollbar-color:#888 #f0f0f0;scrollbar-width:thin}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonRadioGroup, selector: "ion-radio-group", inputs: ["allowEmptySelection", "compareWith", "errorText", "helperText", "name", "value"] }, { kind: "component", type: IonRadio, selector: "ion-radio", inputs: ["alignment", "color", "disabled", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IconComponent, selector: "ngx-decaf-icon", inputs: ["name", "color", "slot", "button", "buttonFill", "buttonShape", "width", "size", "inline"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
|
|
7605
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: CrudFieldComponent, isStandalone: true, selector: "ngx-decaf-crud-field", inputs: { operation: "operation", name: "name", className: "className", path: "path", childOf: "childOf", type: "type", subType: "subType", validationMessage: "validationMessage", value: "value", disabled: "disabled", label: "label", placeholder: "placeholder", format: "format", hidden: "hidden", max: "max", maxlength: "maxlength", min: "min", minlength: "minlength", pattern: "pattern", readonly: "readonly", required: "required", step: "step", equals: "equals", different: "different", lessThan: "lessThan", lessThanOrEqual: "lessThanOrEqual", greaterThan: "greaterThan", greaterThanOrEqual: "greaterThanOrEqual", alignment: "alignment", checked: "checked", justify: "justify", cancelText: "cancelText", interface: "interface", options: "options", mode: "mode", spellcheck: "spellcheck", startEmpty: "startEmpty", inputmode: "inputmode", autocomplete: "autocomplete", fill: "fill", labelPlacement: "labelPlacement", updateOn: "updateOn", formGroup: "formGroup", formControl: "formControl", multiple: "multiple", uid: "uid", page: "page" }, host: { properties: { "attr.id": "uid", "attr.class": "className" } }, viewQueries: [{ propertyName: "component", first: true, predicate: ["component"], descendants: true, read: ElementRef }], usesInheritance: true, ngImport: i0, template: "@if (operation === OperationKeys.READ || operation === OperationKeys.DELETE || readonly) {\n @if (!hidden) {\n <ng-container>\n <div>\n <ion-item\n [class.dcf-item-readonly]=\"readonly && operation !== OperationKeys.READ\"\n [class]=\"'dcf-input-item ' + operation\"\n #component\n >\n <ion-label>\n @if (refreshing) {\n <br />\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n } @else {\n {{ label | translate }}\n <br />\n @if (type !== 'checkbox' && value) {\n <ion-text [innerHTML]=\"type === 'password' ? '********' : value\"></ion-text>\n } @else {\n @if (type === 'checkbox') {\n <ion-badge mode=\"md\" color=\"light\">\n <ion-text [color]=\"!value ? 'danger' : 'primary'\">{{ value }}</ion-text>\n </ion-badge>\n } @else {\n <ion-text [innerHTML]=\"value\"></ion-text>\n }\n }\n }\n </ion-label>\n </ion-item>\n </div>\n </ng-container>\n }\n} @else {\n @if (formControl) {\n <ng-container [formGroup]=\"multiple ? activeFormGroup : formControl.parent\">\n <div\n [id]=\"uid\"\n #container\n [class]=\"'dcf-input-item ' + (operation || 'create')\"\n [class.dcf-field-required]=\"required\"\n [class.dcf-field-readonly]=\"readonly\"\n >\n @if (type === HTML5InputTypes.TEXTAREA) {\n <ion-textarea\n (ionInput)=\"handleModalChildChanges($event, formControl)\"\n (ionChange)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"name\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [autoGrow]=\"true\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [attr.readonly]=\"readonly !== undefined ? readonly : null\"\n [inputmode]=\"inputmode\"\n [spellcheck]=\"spellcheck\"\n [rows]=\"rows\"\n [labelPlacement]=\"labelPlacement\"\n [value]=\"value\"\n [fill]=\"fill\"\n [errorText]=\"getErrors(container)\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [label]=\"label | translate\"\n #component\n ></ion-textarea>\n } @else if (type === HTML5InputTypes.CHECKBOX) {\n @if (!options?.length) {\n <ion-item class=\"dcf-width-1-1\" [hidden]=\"hidden\">\n <ion-checkbox\n (ionInput)=\"handleModalChildChanges($event, formControl)\"\n (ionChange)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"name\"\n [mode]=\"'md'\"\n [errorText]=\"getErrors(container)\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify || 'start'\"\n [value]=\"value\"\n [checked]=\"checked\"\n [attr.readonly]=\"readonly\"\n [formControlName]=\"name\"\n #component\n >\n <span>{{ label | translate }}</span>\n </ion-checkbox>\n </ion-item>\n } @else {\n <div class=\"dcf-checkbox-group\">\n <label class=\"dcf-label\" [for]=\"path\">{{ label | translate }}</label>\n @for (option of options; track trackItemFn($index, option.text)) {\n <ion-item class=\"dcf-width-1-1\" [button]=\"true\">\n <ion-checkbox\n (ionInput)=\"handleModalChildChanges($event, formControl)\"\n (ionChange)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"option.text\"\n [mode]=\"'md'\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify\"\n [value]=\"option.value\"\n [attr.readonly]=\"readonly\"\n [checked]=\"isOptionChecked(option.value)\"\n (ionChange)=\"toggleOptionSelection(option.value, $event)\"\n #component\n >\n <span>{{ $index + 1 }}. {{ option?.text | translate }}</span>\n </ion-checkbox>\n </ion-item>\n }\n <span class=\"dcf-error\" [innerHTML]=\"getErrors(container)\"></span>\n </div>\n }\n } @else if (type === HTML5InputTypes.RADIO && options?.length) {\n <ion-radio-group\n class=\"dcf-width-1-1\"\n [formControlName]=\"name\"\n [value]=\"value\"\n #component\n >\n <label class=\"dcf-radio-group-label\" [for]=\"path\">{{ label | translate }}</label>\n @for (option of options; track $index) {\n <ion-item>\n <ion-radio\n (ionInput)=\"handleModalChildChanges($event, formControl)\"\n (ionChange)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"name\"\n [errorText]=\"getErrors(container)\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [alignment]=\"alignment\"\n [justify]=\"justify\"\n [attr.readonly]=\"readonly\"\n [value]=\"option.value\"\n >\n {{ option?.text | translate }}\n </ion-radio>\n </ion-item>\n }\n </ion-radio-group>\n <span class=\"dcf-error\" [innerHTML]=\"getErrors(container)\"></span>\n } @else if (type === HTML5InputTypes.SELECT) {\n <div>\n <ion-select\n (ionInput)=\"handleModalChildChanges($event)\"\n (ionChange)=\"handleModalChildChanges($event)\"\n (click)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"name\"\n toggleIcon=\"chevron-down-outline\"\n expandedIcon=\"chevron-up-outline\"\n [interface]=\"interface\"\n [mode]=\"'md'\"\n [hidden]=\"hidden || !options?.length\"\n [labelPlacement]=\"labelPlacement\"\n [label]=\"label | translate\"\n [value]=\"value ?? null\"\n (click)=\"openSelectOptions($event, interface)\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [interface]=\"interface\"\n #component\n >\n @if (options?.length) {\n @for (option of options; track trackItemFn($index, option.text)) {\n <ion-select-option\n [value]=\"option.value\"\n [class.dcf-disabled]=\"option.disabled\"\n [class.dcf-hidden]=\"option.hidden\"\n [hidden]=\"option.hidden\"\n >\n {{ option.text }}\n </ion-select-option>\n }\n }\n @if ((value && !required) || (!required && value !== undefined && value !== '')) {\n <ion-button\n fill=\"clear\"\n (click)=\"handleClearValue($event)\"\n slot=\"end\"\n aria-label=\"Show/hide password\"\n >\n <ngx-decaf-icon size=\"small\" slot=\"icon-only\" [name]=\"'ti-input-x'\" />\n </ion-button>\n }\n </ion-select>\n <div\n class=\"dcf-error\"\n [class.dcf-options-error]=\"!options?.length\"\n [innerHTML]=\"\n options?.length\n ? getErrors(container)\n : ('errors.empty_options' | translate: { '0': name })\n \"\n ></div>\n </div>\n } @else {\n <ion-input\n (ionInput)=\"handleModalChildChanges($event, formControl)\"\n (ionChange)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"name\"\n [type]=\"type\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [inputmode]=\"inputmode\"\n [labelPlacement]=\"labelPlacement\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [attr.readonly]=\"readonly !== undefined ? readonly : null\"\n [max]=\"max !== undefined ? max : null\"\n [min]=\"min !== undefined ? min : null\"\n [pattern]=\"pattern !== undefined ? pattern : null\"\n [step]=\"step !== undefined ? step : null\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [label]=\"label | translate\"\n #component\n />\n\n <!-- <span class=\"dcf-error\" [innerHTML]=\"getErrors(container)\"></span> -->\n }\n </div>\n </ng-container>\n } @else {\n <div>\n <p class=\"dcf-error\">\n {{ 'errors.form.control' | translate: { '0': name } }}\n </p>\n </div>\n }\n}\n", styles: [".dcf-item-readonly .create ion-label,.dcf-item-readonly .update ion-label{font-weight:600;transform:translatey(-.8rem)}.dcf-item-readonly .create ion-label ion-text,.dcf-item-readonly .update ion-label ion-text{font-weight:initial;position:relative;top:.8rem}ion-badge{font-weight:600!important;margin-top:.25rem;font-weight:600}ion-badge ion-text{font-weight:600;text-transform:capitalize}ion-item{--border-color: transparent}ion-item:not(.dcf-input-item){--inner-padding-start: 0rem;--padding-start: 0rem;--background: transparent}ion-item.dcf-input-item{--padding-end: 0rem;--padding-start: 0px !important;--padding-top: 0px !important;--inner-padding-start: .75rem;--background: transparent;--background-hover-opacity: .1;--background-activated-opacity: .15;--background-focused-opacity: .15;--background-hover: var(--dcf-color-primary);--background-focused: var(--dcf-color-primary);--border-color: var(--dcf-color-gray-2)}ion-item.dcf-input-item.dcf-palette-dark{--border-color: var(--dcf-color-gray-6) !important}ion-item.dcf-input-item.read,ion-item.dcf-input-item.delete{--min-height: 30px;padding:unset;margin:unset!important;margin-bottom:var(--dcf-margin-xsmall)!important}ion-item.dcf-input-item.read ion-label,ion-item.dcf-input-item.delete ion-label{font-weight:600;margin-top:0!important;margin-bottom:.25rem!important;font-size:.925rem}ion-item.dcf-input-item.read ion-label:not(:first-of-type),ion-item.dcf-input-item.delete ion-label:not(:first-of-type){margin:100rem}ion-item.dcf-input-item.read span,ion-item.dcf-input-item.read ion-text,ion-item.dcf-input-item.delete span,ion-item.dcf-input-item.delete ion-text{font-weight:400!important;font-size:.825rem;min-height:.5rem!important}ion-item.dcf-input-item.read span:not(.dcf-display-block),ion-item.dcf-input-item.read ion-text:not(.dcf-display-block),ion-item.dcf-input-item.delete span:not(.dcf-display-block),ion-item.dcf-input-item.delete ion-text:not(.dcf-display-block){display:inline-block}ion-item.dcf-input-item.read span.dcf-display-block,ion-item.dcf-input-item.read ion-text.dcf-display-block,ion-item.dcf-input-item.delete span.dcf-display-block,ion-item.dcf-input-item.delete ion-text.dcf-display-block{display:block!important}ion-item.dcf-input-item>*,ion-item.dcf-input-item ion-select{width:100%!important}ion-item.dcf-input-item.create.checkbox+.checkbox,ion-item.dcf-input-item.update.checkbox+.checkbox{margin-top:-.25rem!important}ion-item.dcf-input-item.create ion-item,ion-item.dcf-input-item.update ion-item{--border-color: transparent}ion-item.dcf-input-item.create ion-item.dcf-text-wrap ion-label>*,ion-item.dcf-input-item.update ion-item.dcf-text-wrap ion-label>*{white-space:wrap!important;word-break:break-all!important}ion-textarea textarea{scrollbar-width:thin!important;margin-bottom:.5rem!important}ion-select.dcf-select-label-placement-floating::part(label){line-height:1.2rem!important}.dcf-proccessing,.dcf-proccessing *{pointer-events:none;touch-action:none;cursor:text}ion-checkbox::part(container){border:2px solid var(--dcf-color-primary);margin-left:.5rem;margin-right:.5rem;border-radius:var(--dcf-border-radius-small);padding:3px}ion-checkbox{--size: 1.5rem;--checkbox-background-checked: var(--dcf-color-primary);--checkmark-width: 2px}ion-radio-group .dcf-radio-group-label{font-weight:600}ion-radio-group .dcf-radio-group-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}ion-radio-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-checkbox-group{width:100%}.dcf-checkbox-group .dcf-label{font-weight:600}.dcf-checkbox-group .dcf-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}.dcf-checkbox-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-error{position:absolute;color:var(--dcf-color-danger)!important;font-size:.8rem!important;font-weight:500!important;line-height:1.1rem;z-index:9999}.dcf-error.dcf-options-error{position:relative;margin-bottom:.25rem!important;max-width:200px!important}.dcf-error .ti,.dcf-error ngx-decaf-icon,.dcf-error ion-icon{position:relative;top:2px!important;min-width:20px;font-size:1rem!important;text-align:left}.dcf-helper{font-size:.875rem!important;font-weight:500;margin-top:.25rem;margin-bottom:-.75rem}.dcf-helper.dcf-has-action{cursor:pointer;text-decoration:underline}.dcf-error+.dcf-helper{padding-top:1rem}::ng-deep ion-textarea{min-height:80px!important;scrollbar-color:#888 #f0f0f0;scrollbar-width:thin}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonRadioGroup, selector: "ion-radio-group", inputs: ["allowEmptySelection", "compareWith", "errorText", "helperText", "name", "value"] }, { kind: "component", type: IonRadio, selector: "ion-radio", inputs: ["alignment", "color", "disabled", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IconComponent, selector: "ngx-decaf-icon", inputs: ["name", "color", "slot", "button", "buttonFill", "buttonShape", "width", "size", "inline"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
|
|
7395
7606
|
};
|
|
7396
7607
|
CrudFieldComponent = __decorate([
|
|
7397
7608
|
Dynamic(),
|
|
@@ -7414,7 +7625,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
7414
7625
|
IonText,
|
|
7415
7626
|
IonTextarea,
|
|
7416
7627
|
IconComponent,
|
|
7417
|
-
], selector: 'ngx-decaf-crud-field', host: { '[attr.id]': 'uid', '[attr.class]': 'className' }, template: "@if (operation === OperationKeys.READ || operation === OperationKeys.DELETE || readonly) {\n @if (!hidden) {\n <ng-container>\n <div>\n <ion-item\n [class.dcf-item-readonly]=\"readonly && operation !== OperationKeys.READ\"\n [class]=\"'dcf-input-item ' + operation\"\n #component\n >\n <ion-label>\n @if (refreshing) {\n <br />\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n } @else {\n {{ label | translate }}\n <br />\n @if (type !== 'checkbox' && value) {\n <ion-text [innerHTML]=\"type === 'password' ? '********' : value\"></ion-text>\n } @else {\n @if (type === 'checkbox') {\n <ion-badge mode=\"md\" color=\"light\">\n <ion-text [color]=\"!value ? 'danger' : 'primary'\">{{ value }}</ion-text>\n </ion-badge>\n } @else {\n <ion-text [innerHTML]=\"value\"></ion-text>\n }\n }\n }\n </ion-label>\n </ion-item>\n </div>\n </ng-container>\n }\n} @else {\n @if (formControl) {\n <ng-container [formGroup]=\"multiple ? activeFormGroup : formControl.parent\">\n <div\n [id]=\"uid\"\n #container\n [class]=\"'dcf-input-item ' + (operation || 'create')\"\n [class.dcf-field-required]=\"required\"\n [class.dcf-field-readonly]=\"readonly\"\n >\n @if (type === HTML5InputTypes.TEXTAREA) {\n <ion-textarea\n (ionInput)=\"handleModalChildChanges()\"\n (ionChange)=\"handleModalChildChanges()\"\n [id]=\"name\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [autoGrow]=\"true\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [attr.readonly]=\"readonly !== undefined ? readonly : null\"\n [inputmode]=\"inputmode\"\n [spellcheck]=\"spellcheck\"\n [rows]=\"rows\"\n [labelPlacement]=\"labelPlacement\"\n [value]=\"value\"\n [fill]=\"fill\"\n [errorText]=\"getErrors(container)\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [label]=\"label | translate\"\n #component\n ></ion-textarea>\n } @else if (type === HTML5InputTypes.CHECKBOX) {\n @if (!options?.length) {\n <ion-item class=\"dcf-width-1-1\" [hidden]=\"hidden\">\n <ion-checkbox\n (ionInput)=\"handleModalChildChanges()\"\n (ionChange)=\"handleModalChildChanges()\"\n [id]=\"name\"\n [mode]=\"'md'\"\n [errorText]=\"getErrors(container)\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify || 'start'\"\n [value]=\"value\"\n [checked]=\"checked\"\n [attr.readonly]=\"readonly\"\n [formControlName]=\"name\"\n #component\n >\n <span>{{ label | translate }}</span>\n </ion-checkbox>\n </ion-item>\n } @else {\n <div class=\"dcf-checkbox-group\">\n <label class=\"dcf-label\" [for]=\"path\">{{ label | translate }}</label>\n @for (option of options; track trackItemFn($index, option.text)) {\n <ion-item class=\"dcf-width-1-1\" [button]=\"true\">\n <ion-checkbox\n (ionInput)=\"handleModalChildChanges()\"\n (ionChange)=\"handleModalChildChanges()\"\n [id]=\"option.text\"\n [mode]=\"'md'\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify\"\n [value]=\"option.value\"\n [attr.readonly]=\"readonly\"\n [checked]=\"isOptionChecked(option.value)\"\n (ionChange)=\"toggleOptionSelection(option.value, $event)\"\n #component\n >\n <span>{{ $index + 1 }}. {{ option?.text | translate }}</span>\n </ion-checkbox>\n </ion-item>\n }\n <span class=\"dcf-error\" [innerHTML]=\"getErrors(container)\"></span>\n </div>\n }\n } @else if (type === HTML5InputTypes.RADIO && options?.length) {\n <ion-radio-group\n class=\"dcf-width-1-1\"\n [formControlName]=\"name\"\n [value]=\"value\"\n #component\n >\n <label class=\"dcf-radio-group-label\" [for]=\"path\">{{ label | translate }}</label>\n @for (option of options; track $index) {\n <ion-item>\n <ion-radio\n (ionInput)=\"handleModalChildChanges()\"\n (ionChange)=\"handleModalChildChanges()\"\n [id]=\"name\"\n [errorText]=\"getErrors(container)\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [alignment]=\"alignment\"\n [justify]=\"justify\"\n [attr.readonly]=\"readonly\"\n [value]=\"option.value\"\n >\n {{ option?.text | translate }}\n </ion-radio>\n </ion-item>\n }\n </ion-radio-group>\n <span class=\"dcf-error\" [innerHTML]=\"getErrors(container)\"></span>\n } @else if (type === HTML5InputTypes.SELECT) {\n <div>\n <ion-select\n (ionInput)=\"handleModalChildChanges($event)\"\n (ionChange)=\"handleModalChildChanges($event)\"\n (click)=\"handleModalChildChanges()\"\n [id]=\"name\"\n toggleIcon=\"chevron-down-outline\"\n expandedIcon=\"chevron-up-outline\"\n [interface]=\"interface\"\n [mode]=\"'md'\"\n [hidden]=\"hidden || !options?.length\"\n [labelPlacement]=\"labelPlacement\"\n [label]=\"label | translate\"\n [value]=\"value ?? null\"\n (click)=\"openSelectOptions($event, interface)\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [interface]=\"interface\"\n #component\n >\n @if (options?.length) {\n @for (option of options; track trackItemFn($index, option.text)) {\n <ion-select-option\n [value]=\"option.value\"\n [class.dcf-disabled]=\"option.disabled\"\n [class.dcf-hidden]=\"option.hidden\"\n [hidden]=\"option.hidden\"\n >\n {{ option.text }}\n </ion-select-option>\n }\n }\n @if ((value && !required) || (!required && value !== undefined && value !== '')) {\n <ion-button\n fill=\"clear\"\n (click)=\"handleClearValue($event)\"\n slot=\"end\"\n aria-label=\"Show/hide password\"\n >\n <ngx-decaf-icon size=\"small\" slot=\"icon-only\" [name]=\"'ti-input-x'\" />\n </ion-button>\n }\n </ion-select>\n <div\n class=\"dcf-error\"\n [class.dcf-options-error]=\"!options?.length\"\n [innerHTML]=\"\n options?.length\n ? getErrors(container)\n : ('errors.empty_options' | translate: { '0': name })\n \"\n ></div>\n </div>\n } @else {\n <ion-input\n (ionInput)=\"handleModalChildChanges()\"\n (ionChange)=\"handleModalChildChanges()\"\n [id]=\"name\"\n [type]=\"type\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [inputmode]=\"inputmode\"\n [labelPlacement]=\"labelPlacement\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [attr.readonly]=\"readonly !== undefined ? readonly : null\"\n [max]=\"max !== undefined ? max : null\"\n [min]=\"min !== undefined ? min : null\"\n [pattern]=\"pattern !== undefined ? pattern : null\"\n [step]=\"step !== undefined ? step : null\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [label]=\"label | translate\"\n #component\n />\n }\n </div>\n </ng-container>\n } @else {\n <div>\n <p class=\"dcf-error\">\n {{ 'errors.form.control' | translate: { '0': name } }}\n </p>\n </div>\n }\n}\n", styles: [".dcf-item-readonly .create ion-label,.dcf-item-readonly .update ion-label{font-weight:600;transform:translatey(-.8rem)}.dcf-item-readonly .create ion-label ion-text,.dcf-item-readonly .update ion-label ion-text{font-weight:initial;position:relative;top:.8rem}ion-badge{font-weight:600!important;margin-top:.25rem;font-weight:600}ion-badge ion-text{font-weight:600;text-transform:capitalize}ion-item{--border-color: transparent}ion-item:not(.dcf-input-item){--inner-padding-start: 0rem;--padding-start: 0rem;--background: transparent}ion-item.dcf-input-item{--padding-end: 0rem;--padding-start: 0px !important;--padding-top: 0px !important;--inner-padding-start: .75rem;--background: transparent;--background-hover-opacity: .1;--background-activated-opacity: .15;--background-focused-opacity: .15;--background-hover: var(--dcf-color-primary);--background-focused: var(--dcf-color-primary);--border-color: var(--dcf-color-gray-2)}ion-item.dcf-input-item.dcf-palette-dark{--border-color: var(--dcf-color-gray-6) !important}ion-item.dcf-input-item.read,ion-item.dcf-input-item.delete{--min-height: 30px;padding:unset;margin:unset!important;margin-bottom:var(--dcf-margin-xsmall)!important}ion-item.dcf-input-item.read ion-label,ion-item.dcf-input-item.delete ion-label{font-weight:600;margin-top:0!important;margin-bottom:.25rem!important;font-size:.925rem}ion-item.dcf-input-item.read ion-label:not(:first-of-type),ion-item.dcf-input-item.delete ion-label:not(:first-of-type){margin:100rem}ion-item.dcf-input-item.read span,ion-item.dcf-input-item.read ion-text,ion-item.dcf-input-item.delete span,ion-item.dcf-input-item.delete ion-text{font-weight:400!important;font-size:.825rem;min-height:.5rem!important}ion-item.dcf-input-item.read span:not(.dcf-display-block),ion-item.dcf-input-item.read ion-text:not(.dcf-display-block),ion-item.dcf-input-item.delete span:not(.dcf-display-block),ion-item.dcf-input-item.delete ion-text:not(.dcf-display-block){display:inline-block}ion-item.dcf-input-item.read span.dcf-display-block,ion-item.dcf-input-item.read ion-text.dcf-display-block,ion-item.dcf-input-item.delete span.dcf-display-block,ion-item.dcf-input-item.delete ion-text.dcf-display-block{display:block!important}ion-item.dcf-input-item>*,ion-item.dcf-input-item ion-select{width:100%!important}ion-item.dcf-input-item.create.checkbox+.checkbox,ion-item.dcf-input-item.update.checkbox+.checkbox{margin-top:-.25rem!important}ion-item.dcf-input-item.create ion-item,ion-item.dcf-input-item.update ion-item{--border-color: transparent}ion-item.dcf-input-item.create ion-item.dcf-text-wrap ion-label>*,ion-item.dcf-input-item.update ion-item.dcf-text-wrap ion-label>*{white-space:wrap!important;word-break:break-all!important}ion-textarea textarea{scrollbar-width:thin!important;margin-bottom:.5rem!important}ion-select.dcf-select-label-placement-floating::part(label){line-height:1.2rem!important}.dcf-proccessing,.dcf-proccessing *{pointer-events:none;touch-action:none;cursor:text}ion-checkbox::part(container){border:2px solid var(--dcf-color-primary);margin-left:.5rem;margin-right:.5rem;border-radius:var(--dcf-border-radius-small);padding:3px}ion-checkbox{--size: 1.5rem;--checkbox-background-checked: var(--dcf-color-primary);--checkmark-width: 2px}ion-radio-group .dcf-radio-group-label{font-weight:600}ion-radio-group .dcf-radio-group-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}ion-radio-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-checkbox-group{width:100%}.dcf-checkbox-group .dcf-label{font-weight:600}.dcf-checkbox-group .dcf-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}.dcf-checkbox-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-error{position:absolute;color:var(--dcf-color-danger)!important;font-size:.8rem!important;font-weight:500!important;line-height:1.1rem;z-index:9999}.dcf-error.dcf-options-error{position:relative;margin-bottom:.25rem!important}.dcf-error .ti,.dcf-error ngx-decaf-icon,.dcf-error ion-icon{position:relative;top:2px!important;min-width:20px;font-size:1rem!important;text-align:left}.dcf-helper{font-size:.875rem!important;font-weight:500;margin-top:.25rem;margin-bottom:-.75rem}.dcf-helper.dcf-has-action{cursor:pointer;text-decoration:underline}.dcf-error+.dcf-helper{padding-top:1rem}::ng-deep ion-textarea{min-height:80px!important;scrollbar-color:#888 #f0f0f0;scrollbar-width:thin}\n"] }]
|
|
7628
|
+
], selector: 'ngx-decaf-crud-field', host: { '[attr.id]': 'uid', '[attr.class]': 'className' }, template: "@if (operation === OperationKeys.READ || operation === OperationKeys.DELETE || readonly) {\n @if (!hidden) {\n <ng-container>\n <div>\n <ion-item\n [class.dcf-item-readonly]=\"readonly && operation !== OperationKeys.READ\"\n [class]=\"'dcf-input-item ' + operation\"\n #component\n >\n <ion-label>\n @if (refreshing) {\n <br />\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n } @else {\n {{ label | translate }}\n <br />\n @if (type !== 'checkbox' && value) {\n <ion-text [innerHTML]=\"type === 'password' ? '********' : value\"></ion-text>\n } @else {\n @if (type === 'checkbox') {\n <ion-badge mode=\"md\" color=\"light\">\n <ion-text [color]=\"!value ? 'danger' : 'primary'\">{{ value }}</ion-text>\n </ion-badge>\n } @else {\n <ion-text [innerHTML]=\"value\"></ion-text>\n }\n }\n }\n </ion-label>\n </ion-item>\n </div>\n </ng-container>\n }\n} @else {\n @if (formControl) {\n <ng-container [formGroup]=\"multiple ? activeFormGroup : formControl.parent\">\n <div\n [id]=\"uid\"\n #container\n [class]=\"'dcf-input-item ' + (operation || 'create')\"\n [class.dcf-field-required]=\"required\"\n [class.dcf-field-readonly]=\"readonly\"\n >\n @if (type === HTML5InputTypes.TEXTAREA) {\n <ion-textarea\n (ionInput)=\"handleModalChildChanges($event, formControl)\"\n (ionChange)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"name\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [autoGrow]=\"true\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [attr.readonly]=\"readonly !== undefined ? readonly : null\"\n [inputmode]=\"inputmode\"\n [spellcheck]=\"spellcheck\"\n [rows]=\"rows\"\n [labelPlacement]=\"labelPlacement\"\n [value]=\"value\"\n [fill]=\"fill\"\n [errorText]=\"getErrors(container)\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [label]=\"label | translate\"\n #component\n ></ion-textarea>\n } @else if (type === HTML5InputTypes.CHECKBOX) {\n @if (!options?.length) {\n <ion-item class=\"dcf-width-1-1\" [hidden]=\"hidden\">\n <ion-checkbox\n (ionInput)=\"handleModalChildChanges($event, formControl)\"\n (ionChange)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"name\"\n [mode]=\"'md'\"\n [errorText]=\"getErrors(container)\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify || 'start'\"\n [value]=\"value\"\n [checked]=\"checked\"\n [attr.readonly]=\"readonly\"\n [formControlName]=\"name\"\n #component\n >\n <span>{{ label | translate }}</span>\n </ion-checkbox>\n </ion-item>\n } @else {\n <div class=\"dcf-checkbox-group\">\n <label class=\"dcf-label\" [for]=\"path\">{{ label | translate }}</label>\n @for (option of options; track trackItemFn($index, option.text)) {\n <ion-item class=\"dcf-width-1-1\" [button]=\"true\">\n <ion-checkbox\n (ionInput)=\"handleModalChildChanges($event, formControl)\"\n (ionChange)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"option.text\"\n [mode]=\"'md'\"\n [labelPlacement]=\"labelPlacement\"\n [justify]=\"justify\"\n [value]=\"option.value\"\n [attr.readonly]=\"readonly\"\n [checked]=\"isOptionChecked(option.value)\"\n (ionChange)=\"toggleOptionSelection(option.value, $event)\"\n #component\n >\n <span>{{ $index + 1 }}. {{ option?.text | translate }}</span>\n </ion-checkbox>\n </ion-item>\n }\n <span class=\"dcf-error\" [innerHTML]=\"getErrors(container)\"></span>\n </div>\n }\n } @else if (type === HTML5InputTypes.RADIO && options?.length) {\n <ion-radio-group\n class=\"dcf-width-1-1\"\n [formControlName]=\"name\"\n [value]=\"value\"\n #component\n >\n <label class=\"dcf-radio-group-label\" [for]=\"path\">{{ label | translate }}</label>\n @for (option of options; track $index) {\n <ion-item>\n <ion-radio\n (ionInput)=\"handleModalChildChanges($event, formControl)\"\n (ionChange)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"name\"\n [errorText]=\"getErrors(container)\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [labelPlacement]=\"labelPlacement\"\n [alignment]=\"alignment\"\n [justify]=\"justify\"\n [attr.readonly]=\"readonly\"\n [value]=\"option.value\"\n >\n {{ option?.text | translate }}\n </ion-radio>\n </ion-item>\n }\n </ion-radio-group>\n <span class=\"dcf-error\" [innerHTML]=\"getErrors(container)\"></span>\n } @else if (type === HTML5InputTypes.SELECT) {\n <div>\n <ion-select\n (ionInput)=\"handleModalChildChanges($event)\"\n (ionChange)=\"handleModalChildChanges($event)\"\n (click)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"name\"\n toggleIcon=\"chevron-down-outline\"\n expandedIcon=\"chevron-up-outline\"\n [interface]=\"interface\"\n [mode]=\"'md'\"\n [hidden]=\"hidden || !options?.length\"\n [labelPlacement]=\"labelPlacement\"\n [label]=\"label | translate\"\n [value]=\"value ?? null\"\n (click)=\"openSelectOptions($event, interface)\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [interface]=\"interface\"\n #component\n >\n @if (options?.length) {\n @for (option of options; track trackItemFn($index, option.text)) {\n <ion-select-option\n [value]=\"option.value\"\n [class.dcf-disabled]=\"option.disabled\"\n [class.dcf-hidden]=\"option.hidden\"\n [hidden]=\"option.hidden\"\n >\n {{ option.text }}\n </ion-select-option>\n }\n }\n @if ((value && !required) || (!required && value !== undefined && value !== '')) {\n <ion-button\n fill=\"clear\"\n (click)=\"handleClearValue($event)\"\n slot=\"end\"\n aria-label=\"Show/hide password\"\n >\n <ngx-decaf-icon size=\"small\" slot=\"icon-only\" [name]=\"'ti-input-x'\" />\n </ion-button>\n }\n </ion-select>\n <div\n class=\"dcf-error\"\n [class.dcf-options-error]=\"!options?.length\"\n [innerHTML]=\"\n options?.length\n ? getErrors(container)\n : ('errors.empty_options' | translate: { '0': name })\n \"\n ></div>\n </div>\n } @else {\n <ion-input\n (ionInput)=\"handleModalChildChanges($event, formControl)\"\n (ionChange)=\"handleModalChildChanges($event, formControl)\"\n [id]=\"name\"\n [type]=\"type\"\n [mode]=\"'md'\"\n [hidden]=\"hidden\"\n [inputmode]=\"inputmode\"\n [labelPlacement]=\"labelPlacement\"\n [minlength]=\"minlength !== undefined ? minlength : null\"\n [maxlength]=\"maxlength !== undefined ? maxlength : null\"\n [attr.readonly]=\"readonly !== undefined ? readonly : null\"\n [max]=\"max !== undefined ? max : null\"\n [min]=\"min !== undefined ? min : null\"\n [pattern]=\"pattern !== undefined ? pattern : null\"\n [step]=\"step !== undefined ? step : null\"\n [fill]=\"fill\"\n [placeholder]=\"placeholder | translate\"\n [formControlName]=\"name\"\n [errorText]=\"getErrors(container)\"\n [label]=\"label | translate\"\n #component\n />\n\n <!-- <span class=\"dcf-error\" [innerHTML]=\"getErrors(container)\"></span> -->\n }\n </div>\n </ng-container>\n } @else {\n <div>\n <p class=\"dcf-error\">\n {{ 'errors.form.control' | translate: { '0': name } }}\n </p>\n </div>\n }\n}\n", styles: [".dcf-item-readonly .create ion-label,.dcf-item-readonly .update ion-label{font-weight:600;transform:translatey(-.8rem)}.dcf-item-readonly .create ion-label ion-text,.dcf-item-readonly .update ion-label ion-text{font-weight:initial;position:relative;top:.8rem}ion-badge{font-weight:600!important;margin-top:.25rem;font-weight:600}ion-badge ion-text{font-weight:600;text-transform:capitalize}ion-item{--border-color: transparent}ion-item:not(.dcf-input-item){--inner-padding-start: 0rem;--padding-start: 0rem;--background: transparent}ion-item.dcf-input-item{--padding-end: 0rem;--padding-start: 0px !important;--padding-top: 0px !important;--inner-padding-start: .75rem;--background: transparent;--background-hover-opacity: .1;--background-activated-opacity: .15;--background-focused-opacity: .15;--background-hover: var(--dcf-color-primary);--background-focused: var(--dcf-color-primary);--border-color: var(--dcf-color-gray-2)}ion-item.dcf-input-item.dcf-palette-dark{--border-color: var(--dcf-color-gray-6) !important}ion-item.dcf-input-item.read,ion-item.dcf-input-item.delete{--min-height: 30px;padding:unset;margin:unset!important;margin-bottom:var(--dcf-margin-xsmall)!important}ion-item.dcf-input-item.read ion-label,ion-item.dcf-input-item.delete ion-label{font-weight:600;margin-top:0!important;margin-bottom:.25rem!important;font-size:.925rem}ion-item.dcf-input-item.read ion-label:not(:first-of-type),ion-item.dcf-input-item.delete ion-label:not(:first-of-type){margin:100rem}ion-item.dcf-input-item.read span,ion-item.dcf-input-item.read ion-text,ion-item.dcf-input-item.delete span,ion-item.dcf-input-item.delete ion-text{font-weight:400!important;font-size:.825rem;min-height:.5rem!important}ion-item.dcf-input-item.read span:not(.dcf-display-block),ion-item.dcf-input-item.read ion-text:not(.dcf-display-block),ion-item.dcf-input-item.delete span:not(.dcf-display-block),ion-item.dcf-input-item.delete ion-text:not(.dcf-display-block){display:inline-block}ion-item.dcf-input-item.read span.dcf-display-block,ion-item.dcf-input-item.read ion-text.dcf-display-block,ion-item.dcf-input-item.delete span.dcf-display-block,ion-item.dcf-input-item.delete ion-text.dcf-display-block{display:block!important}ion-item.dcf-input-item>*,ion-item.dcf-input-item ion-select{width:100%!important}ion-item.dcf-input-item.create.checkbox+.checkbox,ion-item.dcf-input-item.update.checkbox+.checkbox{margin-top:-.25rem!important}ion-item.dcf-input-item.create ion-item,ion-item.dcf-input-item.update ion-item{--border-color: transparent}ion-item.dcf-input-item.create ion-item.dcf-text-wrap ion-label>*,ion-item.dcf-input-item.update ion-item.dcf-text-wrap ion-label>*{white-space:wrap!important;word-break:break-all!important}ion-textarea textarea{scrollbar-width:thin!important;margin-bottom:.5rem!important}ion-select.dcf-select-label-placement-floating::part(label){line-height:1.2rem!important}.dcf-proccessing,.dcf-proccessing *{pointer-events:none;touch-action:none;cursor:text}ion-checkbox::part(container){border:2px solid var(--dcf-color-primary);margin-left:.5rem;margin-right:.5rem;border-radius:var(--dcf-border-radius-small);padding:3px}ion-checkbox{--size: 1.5rem;--checkbox-background-checked: var(--dcf-color-primary);--checkmark-width: 2px}ion-radio-group .dcf-radio-group-label{font-weight:600}ion-radio-group .dcf-radio-group-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}ion-radio-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-checkbox-group{width:100%}.dcf-checkbox-group .dcf-label{font-weight:600}.dcf-checkbox-group .dcf-label~ion-item{margin-top:.5rem;--inner-padding-start: .75rem}.dcf-checkbox-group+.dcf-helper{padding-left:.75rem;position:relative}.dcf-error{position:absolute;color:var(--dcf-color-danger)!important;font-size:.8rem!important;font-weight:500!important;line-height:1.1rem;z-index:9999}.dcf-error.dcf-options-error{position:relative;margin-bottom:.25rem!important;max-width:200px!important}.dcf-error .ti,.dcf-error ngx-decaf-icon,.dcf-error ion-icon{position:relative;top:2px!important;min-width:20px;font-size:1rem!important;text-align:left}.dcf-helper{font-size:.875rem!important;font-weight:500;margin-top:.25rem;margin-bottom:-.75rem}.dcf-helper.dcf-has-action{cursor:pointer;text-decoration:underline}.dcf-error+.dcf-helper{padding-top:1rem}::ng-deep ion-textarea{min-height:80px!important;scrollbar-color:#888 #f0f0f0;scrollbar-width:thin}\n"] }]
|
|
7418
7629
|
}], ctorParameters: () => [], propDecorators: { operation: [{
|
|
7419
7630
|
type: Input,
|
|
7420
7631
|
args: [{ required: true }]
|
|
@@ -7431,6 +7642,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
7431
7642
|
}], type: [{
|
|
7432
7643
|
type: Input,
|
|
7433
7644
|
args: [{ required: true }]
|
|
7645
|
+
}], subType: [{
|
|
7646
|
+
type: Input
|
|
7647
|
+
}], validationMessage: [{
|
|
7648
|
+
type: Input
|
|
7434
7649
|
}], value: [{
|
|
7435
7650
|
type: Input
|
|
7436
7651
|
}], disabled: [{
|
|
@@ -11927,8 +12142,8 @@ let ListComponent = class ListComponent extends NgxComponentDirective {
|
|
|
11927
12142
|
parseConditions(value) {
|
|
11928
12143
|
let _condition;
|
|
11929
12144
|
const model = this.model;
|
|
11930
|
-
if (typeof value === Primitives.STRING
|
|
11931
|
-
_condition = Condition.attribute(this.pk).eq(
|
|
12145
|
+
if (typeof value === Primitives.STRING) {
|
|
12146
|
+
_condition = Condition.attribute(this.pk).eq(this.pkType.toLocaleLowerCase() === Primitives.STRING ? value : Number(value));
|
|
11932
12147
|
for (const index of this.indexes) {
|
|
11933
12148
|
if (index === this.pk)
|
|
11934
12149
|
continue;
|
|
@@ -13906,7 +14121,22 @@ let TableComponent = class TableComponent extends ListComponent {
|
|
|
13906
14121
|
get _cols() {
|
|
13907
14122
|
this.mapper = this._mapper;
|
|
13908
14123
|
return Object.entries(this.mapper)
|
|
13909
|
-
.sort(([, a], [, b]) =>
|
|
14124
|
+
.sort(([, a], [, b]) => {
|
|
14125
|
+
const aSequence = a?.sequence ?? 0;
|
|
14126
|
+
const bSequence = b?.sequence ?? 0;
|
|
14127
|
+
const weight = (v) => v === UIKeys.FIRST ? 0 : typeof v === Primitives.NUMBER ? 1 : v === UIKeys.LAST ? 100 : 1;
|
|
14128
|
+
const aWeight = weight(aSequence);
|
|
14129
|
+
const bWeight = weight(bSequence);
|
|
14130
|
+
if (aWeight !== bWeight) {
|
|
14131
|
+
return aWeight - bWeight;
|
|
14132
|
+
}
|
|
14133
|
+
if (aWeight === 1 &&
|
|
14134
|
+
typeof aSequence === Primitives.NUMBER &&
|
|
14135
|
+
typeof bSequence === Primitives.NUMBER) {
|
|
14136
|
+
return aSequence - bSequence;
|
|
14137
|
+
}
|
|
14138
|
+
return 0;
|
|
14139
|
+
})
|
|
13910
14140
|
.map(([key]) => key);
|
|
13911
14141
|
}
|
|
13912
14142
|
get _headers() {
|
|
@@ -14414,5 +14644,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
14414
14644
|
* Generated bundle index. Do not edit.
|
|
14415
14645
|
*/
|
|
14416
14646
|
|
|
14417
|
-
export { ActionRoles, AngularEngineKeys, BaseComponentProps, CPTKN, CardComponent, ComponentRendererComponent, ComponentsTagNames, CrudFieldComponent, CrudFormComponent, CssClasses, DB_ADAPTER_FLAVOUR_TOKEN, DB_ADAPTER_PROVIDER_TOKEN, DecafFakerRepository, DefaultFormReactiveOptions, DefaultListEmptyOptions, DefaultModalOptions, Dynamic, DynamicModule, EmptyStateComponent, FieldsetComponent, FileUploadComponent, FilterComponent, ForAngularCommonModule, ForAngularComponentsModule, I18N_CONFIG_TOKEN, I18nLoader, I18nLoaderFactory, I18nParser, IconComponent, LOCALE_ROOT_TOKEN, LayoutComponent, ListComponent, ListComponentsTypes, ListItemComponent, ListItemPositions, ModalComponent, ModalConfirmComponent, ModelRendererComponent, NgxComponentDirective, NgxEventHandler, NgxFormDirective, NgxFormFieldDirective, NgxFormService, NgxMediaService, NgxModelPageDirective, NgxPageDirective, NgxParentComponentDirective, NgxRenderingEngine, NgxRouterService, NgxSvgDirective, PaginationComponent, RouteDirections, SearchbarComponent, SelectFieldInterfaces, SteppedFormComponent, TableComponent, WindowColorSchemes, cleanSpaces, dataMapper, decafPageTransition, filterString, formatDate, generateRandomValue, getDbAdapterFlavour, getFakerData, getInjectablesRegistry, getLocaleContext, getLocaleContextByKey, getLocaleFromClassName, getLocaleLanguage, getLogger, getMenuIcon, getModelAndRepository, getNgxInlineModal, getNgxModalComponent, getNgxModalCrudComponent, getNgxSelectOptionsModal, getOnWindow, getOnWindowDocument, getWindow, getWindowDocument, getWindowWidth, isDarkMode, isDevelopmentMode, isNotUndefined, isValidDate, itemMapper, presentModalConfirm, presentNgxInlineModal, presentNgxLightBoxModal, provideDecafDarkMode, provideDecafDbAdapter, provideDecafDynamicComponents, provideDecafI18nConfig, provideDecafI18nLoader, provideDecafPageTransition, removeFocusTrap, setOnWindow, stringToBoolean, windowEventEmitter };
|
|
14647
|
+
export { ActionRoles, AngularEngineKeys, BaseComponentProps, CPTKN, CardComponent, ComponentRendererComponent, ComponentsTagNames, CrudFieldComponent, CrudFormComponent, CssClasses, DB_ADAPTER_FLAVOUR_TOKEN, DB_ADAPTER_PROVIDER_TOKEN, DecafFakerRepository, DefaultFormReactiveOptions, DefaultListEmptyOptions, DefaultModalOptions, Dynamic, DynamicModule, EmptyStateComponent, FieldsetComponent, FileUploadComponent, FilterComponent, ForAngularCommonModule, ForAngularComponentsModule, I18N_CONFIG_TOKEN, I18nLoader, I18nLoaderFactory, I18nParser, IconComponent, LOCALE_ROOT_TOKEN, LayoutComponent, ListComponent, ListComponentsTypes, ListItemComponent, ListItemPositions, ModalComponent, ModalConfirmComponent, ModelRendererComponent, NgxComponentDirective, NgxEventHandler, NgxFormDirective, NgxFormFieldDirective, NgxFormService, NgxMediaService, NgxModelPageDirective, NgxPageDirective, NgxParentComponentDirective, NgxRenderingEngine, NgxRouterService, NgxSvgDirective, PaginationComponent, RouteDirections, SearchbarComponent, SelectFieldInterfaces, SteppedFormComponent, TableComponent, WindowColorSchemes, cleanSpaces, dataMapper, decafPageTransition, filterString, formatDate, generateRandomValue, getDbAdapterFlavour, getFakerData, getInjectablesRegistry, getLocaleContext, getLocaleContextByKey, getLocaleFromClassName, getLocaleLanguage, getLogger, getMenuIcon, getModelAndRepository, getNgxInlineModal, getNgxModalComponent, getNgxModalCrudComponent, getNgxSelectOptionsModal, getOnWindow, getOnWindowDocument, getWindow, getWindowDocument, getWindowWidth, isDarkMode, isDevelopmentMode, isNotUndefined, isValidDate, itemMapper, patternValidators, presentModalConfirm, presentNgxInlineModal, presentNgxLightBoxModal, provideDecafDarkMode, provideDecafDbAdapter, provideDecafDynamicComponents, provideDecafI18nConfig, provideDecafI18nLoader, provideDecafPageTransition, removeFocusTrap, setOnWindow, stringToBoolean, windowEventEmitter };
|
|
14418
14648
|
//# sourceMappingURL=decaf-ts-for-angular.mjs.map
|