@i-cell/ids-angular 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, signal, Component, ViewEncapsulation, ChangeDetectionStrategy, InjectionToken, Directive, input, contentChildren, computed, Input, inject, Injector, ChangeDetectorRef, viewChild, contentChild, isDevMode, ElementRef, effect, output } from '@angular/core';
2
+ import { Injectable, signal, Component, ViewEncapsulation, ChangeDetectionStrategy, InjectionToken, Directive, input, contentChildren, computed, Input, inject, ElementRef, ChangeDetectorRef, viewChild, contentChild, isDevMode, effect, output } from '@angular/core';
3
3
  import { ComponentBase, IdsSize, ComponentBaseWithDefaults, coerceBooleanAttribute } from '@i-cell/ids-angular/core';
4
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
5
  import { IdsIconComponent } from '@i-cell/ids-angular/icon';
6
- import { startWith, Subject } from 'rxjs';
6
+ import { tap, map, of, switchMap, Subject } from 'rxjs';
7
7
  import { Validators, FormGroupDirective, NgForm, NgControl } from '@angular/forms';
8
8
  import { hasModifierKey } from '@angular/cdk/keycodes';
9
9
 
@@ -246,6 +246,71 @@ function requiredFalseValidator(control) {
246
246
  return control.value === false ? null : { requiredFalse: true };
247
247
  }
248
248
 
249
+ /**
250
+ * Directive to map an error message to an error code for a form field.
251
+ * Mappings must be defined between the directive element's tags ordered by priority (descending from top to bottom).
252
+ * The error code is provided as an attribute, while the error message will be this directive's text content.
253
+ * The latter can be a plain string literal or even an interpolated string value.
254
+ *
255
+ * @example
256
+ * ```html
257
+ * <ids-form-field>
258
+ * <ids-label>Input field</ids-label>
259
+ * <input
260
+ * idsInput
261
+ * ngModel
262
+ * customValidator
263
+ * required
264
+ * [minlength]="3"
265
+ * [maxlength]="10"
266
+ * [pattern]="validPattern"
267
+ * >
268
+ * <ids-error-message>
269
+ * <ids-error-def code="required">{{ 'ERROR.REQUIRED.CUSTOM' | translate }}</ids-error-def>
270
+ * <ids-error-def code="minlength">'minLength' error message</ids-error-def>
271
+ * <ids-error-def code="maxlength">'maxLength' error message</ids-error-def>
272
+ * <ids-error-def code="pattern">'pattern' error message with interpolation: {{ model.value }}</ids-error-def>
273
+ * <ids-error-def code="custom">Custom validator error message</ids-error-def>
274
+ * </ids-error-message>
275
+ * </ids-form-field>
276
+ * ```
277
+ */
278
+ class IdsErrorDefinitionDirective {
279
+ constructor() {
280
+ /**
281
+ * The validation error's identifier code
282
+ */
283
+ this.code = input.required();
284
+ this._elementRef = inject(ElementRef);
285
+ }
286
+ /**
287
+ * The validation error's message that will be presented to the user
288
+ */
289
+ get errorMessage() {
290
+ return this._elementRef.nativeElement.innerText;
291
+ }
292
+ /**
293
+ * Creates a IdsErrorMessageMapping instance based on this directive's state (code, errorMessage)
294
+ * @returns A IdsErrorMessageMapping instance
295
+ */
296
+ toErrorMessageMapping() {
297
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
298
+ const self = this;
299
+ return {
300
+ code: this.code(),
301
+ message: () => self.errorMessage,
302
+ };
303
+ }
304
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsErrorDefinitionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
305
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.1.2", type: IdsErrorDefinitionDirective, isStandalone: true, selector: "ids-error-def", inputs: { code: { classPropertyName: "code", publicName: "code", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); }
306
+ }
307
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsErrorDefinitionDirective, decorators: [{
308
+ type: Directive,
309
+ args: [{
310
+ selector: 'ids-error-def',
311
+ }]
312
+ }] });
313
+
249
314
  class IdsMessageSuffixDirective {
250
315
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsMessageSuffixDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
251
316
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.2", type: IdsMessageSuffixDirective, isStandalone: true, selector: "[idsMessageSuffix]", ngImport: i0 }); }
@@ -310,32 +375,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImpor
310
375
  }] });
311
376
 
312
377
  class IdsErrorMessageComponent extends ComponentBase {
378
+ get _hostName() {
379
+ return 'error-message';
380
+ }
313
381
  constructor() {
314
- super(...arguments);
315
- this._injector = inject(Injector);
316
- this._errors = signal(null);
382
+ super();
383
+ this._parent = inject(IdsFormFieldComponent, { skipSelf: true });
384
+ this._control = signal(null);
385
+ this._errorDefDirs = contentChildren(IdsErrorDefinitionDirective);
386
+ this._errorDefs = computed(() => this._errorDefDirs().map((errorDefDir) => errorDefDir.toErrorMessageMapping()));
387
+ this._validationError = signal(null);
317
388
  this._hostClasses = computed(() => this._getHostClasses([]));
318
389
  this.suffixes = contentChildren(IdsMessageSuffixDirective);
390
+ toObservable(this._parent.control).pipe(tap((controlDir) => this._control.set(controlDir?.control ?? null)), map((controlDir) => controlDir?.statusChanges ?? of(null)), switchMap((statusChanges) => statusChanges), takeUntilDestroyed(this._destroyRef)).subscribe((status) => {
391
+ if (status === 'INVALID') {
392
+ const nextError = this._selectMostImportantValidationError();
393
+ this._validationError.set(nextError);
394
+ }
395
+ else {
396
+ this._validationError.set(null);
397
+ }
398
+ });
319
399
  }
320
- get _hostName() {
321
- return 'error-message';
322
- }
323
- ngOnInit() {
324
- const parent = this._injector.get(IdsFormFieldComponent, null, { skipSelf: true, optional: true });
325
- if (parent) {
326
- const control = parent.control();
327
- control?.statusChanges?.pipe(startWith(control.errors), takeUntilDestroyed(this._destroyRef)).subscribe(() => {
328
- this._errors.set(control.errors);
329
- });
400
+ _selectMostImportantValidationError() {
401
+ const errorDefs = this._errorDefs();
402
+ const control = this._control();
403
+ if (!errorDefs?.length || !control?.errors || Object.keys(control.errors).length === 0) {
404
+ return null;
330
405
  }
406
+ const errorCodes = new Set(Object.keys(control.errors));
407
+ return errorDefs.find((errorDef) => errorCodes.has(errorDef.code)) ?? null;
331
408
  }
332
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsErrorMessageComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
333
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.2", type: IdsErrorMessageComponent, isStandalone: true, selector: "ids-error-message", queries: [{ propertyName: "suffixes", predicate: IdsMessageSuffixDirective, isSignal: true }], usesInheritance: true, hostDirectives: [{ directive: IdsMessageDirective }], ngImport: i0, template: "<div class=\"ids-message__prefix\">\n <ng-content select=\"[idsMessagePrefix]\">\n <ids-icon aria-hidden=\"true\" fontIcon=\"exclamation-circle\" />\n </ng-content>\n</div>\n<div class=\"ids-message__text\">\n <ng-content />\n</div>\n@if (suffixes().length) {\n <div class=\"ids-message__suffix\">\n <ng-content select=\"[idsMessageSuffix]\" />\n </div>\n}\n", dependencies: [{ kind: "component", type: IdsIconComponent, selector: "ids-icon", inputs: ["size", "sizeCollection", "variant", "fontIcon", "svgIcon", "aria-hidden"] }], encapsulation: i0.ViewEncapsulation.None }); }
409
+ _getValidationErrorMessage(messageOrFn) {
410
+ return messageOrFn instanceof Function ? messageOrFn() : messageOrFn;
411
+ }
412
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsErrorMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
413
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.2", type: IdsErrorMessageComponent, isStandalone: true, selector: "ids-error-message", queries: [{ propertyName: "_errorDefDirs", predicate: IdsErrorDefinitionDirective, isSignal: true }, { propertyName: "suffixes", predicate: IdsMessageSuffixDirective, isSignal: true }], usesInheritance: true, hostDirectives: [{ directive: IdsMessageDirective }], ngImport: i0, template: "<div class=\"ids-message__prefix\">\n <ng-content select=\"[idsMessagePrefix]\">\n <ids-icon aria-hidden=\"true\" fontIcon=\"exclamation-circle\" variant=\"error\" />\n </ng-content>\n</div>\n<div class=\"ids-message__text\">\n @let validationError = _validationError();\n @if (validationError) {\n @let messageString = _getValidationErrorMessage(validationError.message);\n {{ messageString }}\n }\n</div>\n@if (suffixes().length) {\n <div class=\"ids-message__suffix\">\n <ng-content select=\"[idsMessageSuffix]\" />\n </div>\n}\n", dependencies: [{ kind: "component", type: IdsIconComponent, selector: "ids-icon", inputs: ["size", "sizeCollection", "variant", "fontIcon", "svgIcon", "aria-hidden"] }], encapsulation: i0.ViewEncapsulation.None }); }
334
414
  }
335
415
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsErrorMessageComponent, decorators: [{
336
416
  type: Component,
337
- args: [{ selector: 'ids-error-message', imports: [IdsIconComponent], hostDirectives: [IdsMessageDirective], encapsulation: ViewEncapsulation.None, template: "<div class=\"ids-message__prefix\">\n <ng-content select=\"[idsMessagePrefix]\">\n <ids-icon aria-hidden=\"true\" fontIcon=\"exclamation-circle\" />\n </ng-content>\n</div>\n<div class=\"ids-message__text\">\n <ng-content />\n</div>\n@if (suffixes().length) {\n <div class=\"ids-message__suffix\">\n <ng-content select=\"[idsMessageSuffix]\" />\n </div>\n}\n" }]
338
- }] });
417
+ args: [{ selector: 'ids-error-message', imports: [IdsIconComponent], hostDirectives: [IdsMessageDirective], encapsulation: ViewEncapsulation.None, template: "<div class=\"ids-message__prefix\">\n <ng-content select=\"[idsMessagePrefix]\">\n <ids-icon aria-hidden=\"true\" fontIcon=\"exclamation-circle\" variant=\"error\" />\n </ng-content>\n</div>\n<div class=\"ids-message__text\">\n @let validationError = _validationError();\n @if (validationError) {\n @let messageString = _getValidationErrorMessage(validationError.message);\n {{ messageString }}\n }\n</div>\n@if (suffixes().length) {\n <div class=\"ids-message__suffix\">\n <ng-content select=\"[idsMessageSuffix]\" />\n </div>\n}\n" }]
418
+ }], ctorParameters: () => [] });
339
419
 
340
420
  class IdsMessagePrefixDirective {
341
421
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsMessagePrefixDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
@@ -382,11 +462,11 @@ class IdsSuccessMessageComponent extends ComponentBase {
382
462
  return 'success-message';
383
463
  }
384
464
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsSuccessMessageComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
385
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.2", type: IdsSuccessMessageComponent, isStandalone: true, selector: "ids-success-message", queries: [{ propertyName: "suffixes", predicate: IdsMessageSuffixDirective, isSignal: true }], usesInheritance: true, hostDirectives: [{ directive: IdsMessageDirective }], ngImport: i0, template: "<div class=\"ids-message__prefix\">\n <ng-content select=\"[idsMessagePrefix]\">\n <ids-icon aria-hidden=\"true\" fontIcon=\"check\" />\n </ng-content>\n</div>\n<div class=\"ids-message__text\">\n <ng-content />\n</div>\n@if (suffixes().length) {\n <div class=\"ids-message__suffix\">\n <ng-content select=\"[idsMessageSuffix]\" />\n </div>\n}\n", dependencies: [{ kind: "component", type: IdsIconComponent, selector: "ids-icon", inputs: ["size", "sizeCollection", "variant", "fontIcon", "svgIcon", "aria-hidden"] }], encapsulation: i0.ViewEncapsulation.None }); }
465
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.2", type: IdsSuccessMessageComponent, isStandalone: true, selector: "ids-success-message", queries: [{ propertyName: "suffixes", predicate: IdsMessageSuffixDirective, isSignal: true }], usesInheritance: true, hostDirectives: [{ directive: IdsMessageDirective }], ngImport: i0, template: "<div class=\"ids-message__prefix\">\n <ng-content select=\"[idsMessagePrefix]\">\n <ids-icon aria-hidden=\"true\" fontIcon=\"check\" variant=\"success\" />\n </ng-content>\n</div>\n<div class=\"ids-message__text\">\n <ng-content />\n</div>\n@if (suffixes().length) {\n <div class=\"ids-message__suffix\">\n <ng-content select=\"[idsMessageSuffix]\" />\n </div>\n}\n", dependencies: [{ kind: "component", type: IdsIconComponent, selector: "ids-icon", inputs: ["size", "sizeCollection", "variant", "fontIcon", "svgIcon", "aria-hidden"] }], encapsulation: i0.ViewEncapsulation.None }); }
386
466
  }
387
467
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsSuccessMessageComponent, decorators: [{
388
468
  type: Component,
389
- args: [{ selector: 'ids-success-message', imports: [IdsIconComponent], hostDirectives: [IdsMessageDirective], encapsulation: ViewEncapsulation.None, template: "<div class=\"ids-message__prefix\">\n <ng-content select=\"[idsMessagePrefix]\">\n <ids-icon aria-hidden=\"true\" fontIcon=\"check\" />\n </ng-content>\n</div>\n<div class=\"ids-message__text\">\n <ng-content />\n</div>\n@if (suffixes().length) {\n <div class=\"ids-message__suffix\">\n <ng-content select=\"[idsMessageSuffix]\" />\n </div>\n}\n" }]
469
+ args: [{ selector: 'ids-success-message', imports: [IdsIconComponent], hostDirectives: [IdsMessageDirective], encapsulation: ViewEncapsulation.None, template: "<div class=\"ids-message__prefix\">\n <ng-content select=\"[idsMessagePrefix]\">\n <ids-icon aria-hidden=\"true\" fontIcon=\"check\" variant=\"success\" />\n </ng-content>\n</div>\n<div class=\"ids-message__text\">\n <ng-content />\n</div>\n@if (suffixes().length) {\n <div class=\"ids-message__suffix\">\n <ng-content select=\"[idsMessageSuffix]\" />\n </div>\n}\n" }]
390
470
  }] });
391
471
 
392
472
  const defaultConfig$1 = IDS_FORM_FIELD_DEFAULT_CONFIG_FACTORY();
@@ -509,7 +589,7 @@ class IdsInputDirective extends ComponentBaseWithDefaults {
509
589
  this._defaultConfig = this._getDefaultConfig(defaultConfig, IDS_INPUT_DEFAULT_CONFIG);
510
590
  this.errorStateChanges = new Subject();
511
591
  this.successStateChanges = new Subject();
512
- this.ngControl = inject(NgControl, { optional: true });
592
+ this.ngControl = inject(NgControl);
513
593
  this._focused = false;
514
594
  this.placeholder = input('');
515
595
  this.name = input();
@@ -556,12 +636,12 @@ class IdsInputDirective extends ComponentBaseWithDefaults {
556
636
  }
557
637
  _initErrorStateTracker() {
558
638
  this._errorStateTracker = new ErrorStateTracker(this.errorStateMatcher(), this.ngControl, this._parentFormGroup, this._parentForm, this.errorStateChanges);
559
- this._successStateSubscription = this.errorStateChanges.pipe(takeUntilDestroyed(this._destroyRef)).subscribe(() => this.hasErrorState.set(this._errorStateTracker.hasErrorState));
639
+ this.errorStateChanges.pipe(takeUntilDestroyed(this._destroyRef)).subscribe(() => this.hasErrorState.set(this._errorStateTracker.hasErrorState));
560
640
  }
561
641
  _setSuccessStateTracker(canHandleSuccessState) {
562
642
  if (canHandleSuccessState) {
563
643
  this._successStateTracker = new SuccessStateTracker(this.successStateMatcher(), this.ngControl, this._parentFormGroup, this._parentForm, this.successStateChanges);
564
- this.successStateChanges.pipe(takeUntilDestroyed(this._destroyRef)).subscribe(() => this.hasSuccessState.set(this._successStateTracker.hasSuccessState));
644
+ this._successStateSubscription = this.successStateChanges.pipe(takeUntilDestroyed(this._destroyRef)).subscribe(() => this.hasSuccessState.set(this._successStateTracker.hasSuccessState));
565
645
  }
566
646
  else {
567
647
  this._successStateTracker = undefined;
@@ -867,5 +947,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImpor
867
947
  * Generated bundle index. Do not edit.
868
948
  */
869
949
 
870
- export { AbstractErrorStateMatcher, AbstractSuccessStateMatcher, ErrorStateMatcher, ErrorStateTracker, IDS_FIELDSET_DEFAULT_CONFIG, IDS_FIELDSET_DEFAULT_CONFIG_FACTORY, IDS_FORM_FIELD_CONTROL, IDS_FORM_FIELD_DEFAULT_CONFIG, IDS_FORM_FIELD_DEFAULT_CONFIG_FACTORY, IDS_INPUT_DEFAULT_CONFIG, IDS_INPUT_DEFAULT_CONFIG_FACTORY, IDS_MESSAGE_DEFAULT_CONFIG, IDS_MESSAGE_DEFAULT_CONFIG_FACTORY, IDS_OPTION_GROUP, IDS_OPTION_PARENT_COMPONENT, IDS_PSEUDO_CHECKBOX_PARENT, IdsErrorMessageComponent, IdsFieldsetComponent, IdsFieldsetMessageDirective, IdsFieldsetRowComponent, IdsFormFieldActionDirective, IdsFormFieldComponent, IdsFormFieldControl, IdsFormFieldVariant, IdsHintMessageComponent, IdsInputDirective, IdsLabelDirective, IdsMessageDirective, IdsMessagePrefixDirective, IdsMessageSuffixDirective, IdsMessageVariant, IdsOptionComponent, IdsOptionGroupComponent, IdsOptionSelectionChange, IdsPrefixDirective, IdsPseudoCheckboxState, IdsSuccessMessageComponent, IdsSuffixDirective, IdsValidators, Message, PseudoCheckboxComponent, SuccessStateMatcher, SuccessStateTracker, _countGroupLabelsBeforeOption, _getOptionScrollPosition, formFieldControlClass, requiredFalseValidator, requiredTrueValidator, requiredValidator };
950
+ export { AbstractErrorStateMatcher, AbstractSuccessStateMatcher, ErrorStateMatcher, ErrorStateTracker, IDS_FIELDSET_DEFAULT_CONFIG, IDS_FIELDSET_DEFAULT_CONFIG_FACTORY, IDS_FORM_FIELD_CONTROL, IDS_FORM_FIELD_DEFAULT_CONFIG, IDS_FORM_FIELD_DEFAULT_CONFIG_FACTORY, IDS_INPUT_DEFAULT_CONFIG, IDS_INPUT_DEFAULT_CONFIG_FACTORY, IDS_MESSAGE_DEFAULT_CONFIG, IDS_MESSAGE_DEFAULT_CONFIG_FACTORY, IDS_OPTION_GROUP, IDS_OPTION_PARENT_COMPONENT, IDS_PSEUDO_CHECKBOX_PARENT, IdsErrorDefinitionDirective, IdsErrorMessageComponent, IdsFieldsetComponent, IdsFieldsetMessageDirective, IdsFieldsetRowComponent, IdsFormFieldActionDirective, IdsFormFieldComponent, IdsFormFieldControl, IdsFormFieldVariant, IdsHintMessageComponent, IdsInputDirective, IdsLabelDirective, IdsMessageDirective, IdsMessagePrefixDirective, IdsMessageSuffixDirective, IdsMessageVariant, IdsOptionComponent, IdsOptionGroupComponent, IdsOptionSelectionChange, IdsPrefixDirective, IdsPseudoCheckboxState, IdsSuccessMessageComponent, IdsSuffixDirective, IdsValidators, Message, PseudoCheckboxComponent, SuccessStateMatcher, SuccessStateTracker, _countGroupLabelsBeforeOption, _getOptionScrollPosition, formFieldControlClass, requiredFalseValidator, requiredTrueValidator, requiredValidator };
871
951
  //# sourceMappingURL=i-cell-ids-angular-forms.mjs.map