@onemrvapublic/design-system 16.2.8 → 16.2.10

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.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/projects/onemrva/design-system/_index.scss +1 -0
  3. package/projects/onemrva/design-system/core/index.ts +2 -0
  4. package/projects/onemrva/design-system/core/ng-package.json +6 -0
  5. package/projects/onemrva/design-system/core/src/lib/core.module.ts +91 -0
  6. package/projects/onemrva/design-system/core/src/lib/modules/index.ts +1 -0
  7. package/projects/onemrva/design-system/core/src/lib/modules/translate.loader.ts +34 -0
  8. package/projects/onemrva/design-system/core/src/lib/modules/translate.module.ts +66 -0
  9. package/projects/onemrva/design-system/core/src/lib/services/index.ts +2 -0
  10. package/projects/onemrva/design-system/core/src/lib/services/onemrva-error-handler.service.ts +24 -0
  11. package/projects/onemrva/design-system/core/src/lib/services/onemrva-missing-translation.service.ts +12 -0
  12. package/projects/onemrva/design-system/core/src/test.ts +24 -0
  13. package/projects/onemrva/design-system/package.json +1 -1
  14. package/projects/onemrva/design-system/shared/index.ts +10 -0
  15. package/projects/onemrva/design-system/shared/ng-package.json +6 -0
  16. package/projects/onemrva/design-system/shared/src/lib/components/clipboard-icon/clipboard-icon.component.css +0 -0
  17. package/projects/onemrva/design-system/shared/src/lib/components/clipboard-icon/clipboard-icon.component.html +1 -0
  18. package/projects/onemrva/design-system/shared/src/lib/components/clipboard-icon/clipboard-icon.component.spec.ts +21 -0
  19. package/projects/onemrva/design-system/shared/src/lib/components/clipboard-icon/clipboard-icon.component.ts +22 -0
  20. package/projects/onemrva/design-system/shared/src/lib/directives/clipboard.directive.ts +55 -0
  21. package/projects/onemrva/design-system/shared/src/lib/directives/color.directive.ts +47 -0
  22. package/projects/onemrva/design-system/shared/src/lib/directives/digit-only.directive.ts +46 -0
  23. package/projects/onemrva/design-system/shared/src/lib/directives/if-width-is.directive.ts +27 -0
  24. package/projects/onemrva/design-system/shared/src/lib/directives/index.ts +22 -0
  25. package/projects/onemrva/design-system/shared/src/lib/directives/mask.directive.ts +221 -0
  26. package/projects/onemrva/design-system/shared/src/lib/directives/mat-row-clickable.directive.ts +58 -0
  27. package/projects/onemrva/design-system/shared/src/lib/pipes/index.ts +5 -0
  28. package/projects/onemrva/design-system/shared/src/lib/pipes/onemrva-bce.pipe.ts +18 -0
  29. package/projects/onemrva/design-system/shared/src/lib/pipes/onemrva-niss.pipe.ts +18 -0
  30. package/projects/onemrva/design-system/shared/src/lib/shared.module.ts +10 -0
  31. package/projects/onemrva/design-system/shared/src/lib/validators/onemrva-validators.ts +66 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onemrvapublic/design-system",
3
- "version": "16.2.8",
3
+ "version": "16.2.10",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -7,6 +7,7 @@
7
7
  @forward "./mat-breadcrumb/src/onemrva-mat-breadcrumb.component" show breadcrumb;
8
8
  @forward "./mat-datepicker-header/src/onemrva-mat-datepicker-header.component"
9
9
  show datepickerHeader;
10
+ @forward "./layout/src/components/layout/layout-mixin.component" show layout;
10
11
  @forward "./mat-message-box/src/onemrva-mat-message-box.component" show
11
12
  messageBox;
12
13
  @forward "./mat-multi-select/src/onemrva-mat-multi-select.component" show
@@ -0,0 +1,2 @@
1
+ //export * from './src/lib/modules/index';
2
+ export * from './src/lib/services/index';
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "lib": {
4
+ "entryFile": "index.ts"
5
+ }
6
+ }
@@ -0,0 +1,91 @@
1
+ import { APP_INITIALIZER, ErrorHandler, NgModule } from '@angular/core';
2
+
3
+ import { MatExpansionModule } from '@angular/material/expansion';
4
+ import { MatIconModule } from '@angular/material/icon';
5
+ import {
6
+ MatSnackBarModule,
7
+ MAT_SNACK_BAR_DEFAULT_OPTIONS,
8
+ } from '@angular/material/snack-bar';
9
+ import { OnemrvaErrorHandler } from './services';
10
+ import { CommonModule } from '@angular/common';
11
+ import {
12
+ DateAdapter,
13
+ MAT_DATE_FORMATS,
14
+ MAT_DATE_LOCALE,
15
+ MatNativeDateModule, //
16
+ } from '@angular/material/core';
17
+ import {
18
+ MAT_MOMENT_DATE_ADAPTER_OPTIONS,
19
+ MatMomentDateModule, //
20
+ MomentDateAdapter,
21
+ } from '@angular/material-moment-adapter';
22
+
23
+ import { TranslateService } from '@ngx-translate/core';
24
+ import { Observable, of } from 'rxjs';
25
+
26
+ function translateDatepicker(
27
+ translateService: TranslateService,
28
+ adapter: DateAdapter<Date>
29
+ ): () => Observable<any> {
30
+ return () => {
31
+ // For some cosmic reason, return translateService.onLangChange().asObservable()) + pipe(tap()) does not work
32
+ translateService.onLangChange.subscribe((event) => {
33
+ adapter.setLocale(event.lang);
34
+ });
35
+
36
+ // But this works fine...
37
+ return of('sin (a +- b) = sin a . cos b +- cos a . sin b');
38
+ };
39
+ }
40
+
41
+ export const MY_FORMATS = {
42
+ parse: {
43
+ dateInput: 'LL',
44
+ },
45
+ display: {
46
+ dateInput: 'DD/MM/YYYY',
47
+ monthYearLabel: 'MMM YYYY',
48
+ dateA11yLabel: 'LL',
49
+ monthYearA11yLabel: 'MMMM YYYY',
50
+ },
51
+ };
52
+
53
+ @NgModule({
54
+ imports: [
55
+ CommonModule,
56
+ MatExpansionModule,
57
+ MatIconModule,
58
+ MatSnackBarModule,
59
+ MatNativeDateModule,
60
+ MatMomentDateModule,
61
+ ],
62
+ exports: [MatSnackBarModule],
63
+ providers: [
64
+ {
65
+ provide: ErrorHandler,
66
+ useClass: OnemrvaErrorHandler,
67
+ },
68
+ {
69
+ provide: MAT_SNACK_BAR_DEFAULT_OPTIONS,
70
+ useValue: { horizontalPosition: 'right' },
71
+ },
72
+ {
73
+ provide: MAT_DATE_LOCALE,
74
+ useValue: 'fr',
75
+ },
76
+
77
+ {
78
+ provide: DateAdapter,
79
+ useClass: MomentDateAdapter,
80
+ deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS],
81
+ },
82
+ { provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
83
+ {
84
+ provide: APP_INITIALIZER,
85
+ useFactory: translateDatepicker,
86
+ deps: [TranslateService, DateAdapter<any>],
87
+ multi: true,
88
+ },
89
+ ],
90
+ })
91
+ export class OnemrvaCoreModule {}
@@ -0,0 +1 @@
1
+ export * from "./translate.module";
@@ -0,0 +1,34 @@
1
+ // import {Injectable} from "@angular/core";
2
+ // import {TranslateLoader} from "@ngx-translate/core";
3
+ // import {HttpClient} from "@angular/common/http";
4
+ // import {forkJoin, Observable, of} from "rxjs";
5
+ // import {catchError, map} from "rxjs/operators";
6
+ //
7
+ // @Injectable()
8
+ // export class OnemrvaTranslateHttpLoader implements TranslateLoader {
9
+ //
10
+ // constructor(
11
+ // private http: HttpClient,
12
+ // ) {
13
+ //
14
+ // }
15
+ //
16
+ // /**
17
+ // * Gets the translations from the server
18
+ // */
19
+ // public getTranslation(lang: string): Observable<Object> {
20
+ //
21
+ // const observables = [
22
+ // this.http.get(`${environment.translate.prefix}${lang}${environment.translate.suffix}`).pipe(catchError(() => of(null))),
23
+ // ...environment.translate.modules.map((m) =>
24
+ // this.http.get(`${environment.translate.prefix}${m}/${lang}${environment.translate.suffix}`).pipe(catchError(() => of(null)))
25
+ // ),
26
+ // ];
27
+ //
28
+ // return forkJoin(observables).pipe(
29
+ // map((all) => {
30
+ // return (all.filter((v) => !!v) as Object[]).reduce((s, c) => ({ ...s, ...c }), {});
31
+ // })
32
+ // );
33
+ // }
34
+ // }
@@ -0,0 +1,66 @@
1
+ // import { HttpClient } from '@angular/common/http';
2
+ // import {APP_INITIALIZER, NgModule} from '@angular/core';
3
+ // import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
4
+ // import { environment } from "@env/environment";
5
+ // import {OnemrvaTranslateHttpLoader} from "./translate.loader";
6
+ //
7
+ // @NgModule({
8
+ // imports: [
9
+ // TranslateModule.forRoot({
10
+ // defaultLanguage: environment.translate.language,
11
+ // loader: {
12
+ // provide: TranslateLoader,
13
+ // useFactory: (http: HttpClient) => {
14
+ // return new OnemrvaTranslateHttpLoader(http);
15
+ // },
16
+ // deps: [HttpClient],
17
+ // },
18
+ // }),
19
+ // ],
20
+ // providers: [
21
+ // {
22
+ // provide: APP_INITIALIZER,
23
+ // deps: [TranslateService],
24
+ // useFactory: (translateService: TranslateService ) => {
25
+ // return async () => {
26
+ // const languages = environment.translate.languages.map(v => v.code);
27
+ // translateService.addLangs(languages);
28
+ // translateService.setDefaultLang(environment.translate.language);
29
+ //
30
+ // // getting language from localstorage
31
+ // let language:string|null = localStorage.getItem('language');
32
+ // // not set or incorrectly set ?
33
+ // if (language === null || languages.indexOf(language) < 0) {
34
+ // // getting language from browser
35
+ // if (languages.indexOf(navigator.language) >= 0 ) {
36
+ // language = navigator.language;
37
+ // } else {
38
+ // // getting language from one of the browser's languages
39
+ // for (let lng of navigator.languages) {
40
+ // if (languages.indexOf(lng) >= 0 ) {
41
+ // language = lng;
42
+ // break;
43
+ // }
44
+ // }
45
+ // // getting default language
46
+ // if (language === null || languages.indexOf(language) < 0) {
47
+ // language = environment.translate.language;
48
+ // }
49
+ // }
50
+ // }
51
+ // localStorage.setItem('language', language);
52
+ // await translateService.use(language).toPromise();
53
+ //
54
+ // translateService.onLangChange.subscribe((value) => {
55
+ // localStorage.setItem('language', value.lang);
56
+ // });
57
+ // };
58
+ // },
59
+ // multi: true,
60
+ // }
61
+ // ],
62
+ // exports: [
63
+ // TranslateModule
64
+ // ]
65
+ // })
66
+ // export class OnemrvaTranslateModule { }
@@ -0,0 +1,2 @@
1
+ export * from './onemrva-error-handler.service';
2
+ export * from './onemrva-missing-translation.service';
@@ -0,0 +1,24 @@
1
+ import { ErrorHandler, Injectable } from '@angular/core';
2
+ import {
3
+ MatSnackBar,
4
+ MatSnackBarHorizontalPosition,
5
+ MatSnackBarVerticalPosition,
6
+ } from '@angular/material/snack-bar';
7
+
8
+ @Injectable()
9
+ export class OnemrvaErrorHandler implements ErrorHandler {
10
+ constructor(private _snackBar: MatSnackBar) {}
11
+ horizontalPosition: MatSnackBarHorizontalPosition = 'end';
12
+ verticalPosition: MatSnackBarVerticalPosition = 'bottom';
13
+
14
+ handleError(error: any) {
15
+ console.error(error);
16
+ //alert("Error: " + error.message);
17
+ this._snackBar.open(`Error: ${error.message}`, '', {
18
+ duration: 5000,
19
+ panelClass: 'mat-primary',
20
+ horizontalPosition: this.horizontalPosition,
21
+ verticalPosition: this.verticalPosition,
22
+ });
23
+ }
24
+ }
@@ -0,0 +1,12 @@
1
+ import {
2
+ MissingTranslationHandler,
3
+ MissingTranslationHandlerParams,
4
+ } from '@ngx-translate/core';
5
+
6
+ export class OnemrvaMissingTranslationHandler
7
+ implements MissingTranslationHandler
8
+ {
9
+ handle(params: MissingTranslationHandlerParams) {
10
+ return `???${params.key}???`;
11
+ }
12
+ }
@@ -0,0 +1,24 @@
1
+ // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2
+
3
+ import 'zone.js';
4
+ import 'zone.js/testing';
5
+ import { getTestBed } from '@angular/core/testing';
6
+ import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
7
+
8
+ declare const require: {
9
+ context(
10
+ path: string,
11
+ deep?: boolean,
12
+ filter?: RegExp
13
+ ): {
14
+ keys(): string[];
15
+ <T>(id: string): T;
16
+ };
17
+ };
18
+
19
+ // First, initialize the Angular testing environment.
20
+ getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
21
+ // Then we find all the tests.
22
+ const context = require.context('./', true, /\.spec\.ts$/);
23
+ // And load the modules.
24
+ context.keys().map(context);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onemrvapublic/design-system",
3
- "version": "0.0.1",
3
+ "version": "16.2.9",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^16.0.0",
6
6
  "@angular/core": "^16.0.0",
@@ -0,0 +1,10 @@
1
+ /*
2
+ * Public API Surface of shared
3
+ */
4
+
5
+ export * from './src/lib/directives';
6
+ export * from './src/lib/shared.module';
7
+ export * from './src/lib/pipes';
8
+ export * from './src/lib/validators/onemrva-validators';
9
+
10
+ export const NISS_MASK = '000000/000-00';
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "lib": {
4
+ "entryFile": "index.ts"
5
+ }
6
+ }
@@ -0,0 +1 @@
1
+ <mat-icon [matTooltip]="'TEST'">content_copy</mat-icon>
@@ -0,0 +1,21 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { ClipboardIconComponent } from './clipboard-icon.component';
4
+
5
+ describe('ClipboardIconComponent', () => {
6
+ let component: ClipboardIconComponent;
7
+ let fixture: ComponentFixture<ClipboardIconComponent>;
8
+
9
+ beforeEach(() => {
10
+ TestBed.configureTestingModule({
11
+ declarations: [ClipboardIconComponent]
12
+ });
13
+ fixture = TestBed.createComponent(ClipboardIconComponent);
14
+ component = fixture.componentInstance;
15
+ fixture.detectChanges();
16
+ });
17
+
18
+ it('should create', () => {
19
+ expect(component).toBeTruthy();
20
+ });
21
+ });
@@ -0,0 +1,22 @@
1
+ import {Component, ElementRef, ViewChild} from '@angular/core';
2
+ import {MatIconModule} from "@angular/material/icon";
3
+ import {MatTooltipModule} from "@angular/material/tooltip";
4
+ import {Clipboard} from "@angular/cdk/clipboard";
5
+
6
+ @Component({
7
+ selector: 'lib-clipboard-icon',
8
+ templateUrl: './clipboard-icon.component.html',
9
+ styleUrls: ['./clipboard-icon.component.css'],
10
+ standalone: true,
11
+ imports: [
12
+ MatIconModule,
13
+ MatTooltipModule
14
+ ]
15
+ })
16
+ export class ClipboardIconComponent {
17
+ constructor(
18
+ public _elementRef : ElementRef,
19
+ private clipboardService : Clipboard) {
20
+ }
21
+
22
+ }
@@ -0,0 +1,55 @@
1
+ import {
2
+ ChangeDetectorRef,
3
+ ComponentFactoryResolver, ComponentRef,
4
+ Directive,
5
+ ElementRef,
6
+ HostBinding,
7
+ Input,
8
+ OnInit,
9
+ Renderer2,
10
+ ViewContainerRef
11
+ } from '@angular/core';
12
+ import {Clipboard} from "@angular/cdk/clipboard";
13
+ import {ClipboardIconComponent} from "../components/clipboard-icon/clipboard-icon.component";
14
+
15
+ /**
16
+ * Conditionally adds component to the tree if screen width matches at least one size in input
17
+ */
18
+ @Directive({
19
+ selector: 'span[clipboard]',
20
+ host: {
21
+ // '[style.padding]': '"pointer"',
22
+ },
23
+
24
+ })
25
+ export class OnemRvaClipboardDirective implements OnInit{
26
+
27
+ private icon: ComponentRef<ClipboardIconComponent>;
28
+ private iconEl: any;
29
+
30
+ constructor(
31
+ private elementRef: ElementRef,
32
+ private renderer: Renderer2,
33
+ private factory: ComponentFactoryResolver,
34
+ private vcRef: ViewContainerRef,
35
+ private clipboardService : Clipboard) {
36
+
37
+ const miFactory = this.factory.resolveComponentFactory(ClipboardIconComponent);
38
+ this.icon = this.vcRef.createComponent(miFactory);
39
+ this.iconEl = this.icon.injector.get(ClipboardIconComponent)._elementRef.nativeElement;
40
+
41
+ }
42
+
43
+ @HostBinding('class')
44
+ class = "";
45
+
46
+ @Input()
47
+ clipboard: string = '';
48
+
49
+ ngOnInit(): void {
50
+ this.vcRef.clear();
51
+
52
+
53
+ this.renderer.appendChild(this.elementRef.nativeElement, this.iconEl);
54
+ }
55
+ }
@@ -0,0 +1,47 @@
1
+ import {Directive, HostBinding, Input} from '@angular/core';
2
+ import {OnemrvaMatColor} from "@onemrvapublic/design-system/utils";
3
+
4
+ /**
5
+ * Conditionally adds component to the tree if screen width matches at least one size in input
6
+ */
7
+ @Directive({
8
+ selector: 'mat-card[color],mat-chip[color]',
9
+ })
10
+ export class OnemRvaColorDirective {
11
+
12
+ constructor() {}
13
+
14
+ @Input()
15
+ color: OnemrvaMatColor = '';
16
+
17
+ /** @hidden @internal */
18
+ @HostBinding('class.mat-primary')
19
+ public get _isPrimary(): boolean {
20
+ return this.color === OnemrvaMatColor.PRIMARY;
21
+ }
22
+ /** @hidden @internal */
23
+ @HostBinding('class.mat-accent')
24
+ public get _isAccent(): boolean {
25
+ return this.color === OnemrvaMatColor.ACCENT;
26
+ }
27
+ /** @hidden @internal */
28
+ @HostBinding('class.mat-error')
29
+ public get _isError(): boolean {
30
+ return this.color === OnemrvaMatColor.ERROR;
31
+ }
32
+ /** @hidden @internal */
33
+ @HostBinding('class.mat-warn')
34
+ public get _isWarn(): boolean {
35
+ return this.color === OnemrvaMatColor.WARN;
36
+ }
37
+ /** @hidden @internal */
38
+ @HostBinding('class.mat-success')
39
+ public get _isSuccess(): boolean {
40
+ return this.color === OnemrvaMatColor.SUCCESS;
41
+ }
42
+ /** @hidden @internal */
43
+ @HostBinding('class.mat-info')
44
+ public get _isInfo(): boolean {
45
+ return this.color === OnemrvaMatColor.INFO;
46
+ }
47
+ }
@@ -0,0 +1,46 @@
1
+ import { Directive, HostListener } from '@angular/core';
2
+
3
+ @Directive({
4
+ selector: '[digitOnly]',
5
+ })
6
+ export class DigitOnlyDirective {
7
+ constructor() {}
8
+
9
+ @HostListener('keydown', ['$event']) onKeyDown(event: any) {
10
+ let e = <KeyboardEvent>event;
11
+
12
+ const allowedKey = [
13
+ '0',
14
+ '1',
15
+ '2',
16
+ '3',
17
+ '4',
18
+ '5',
19
+ '6',
20
+ '7',
21
+ '8',
22
+ '9',
23
+ '.',
24
+ ',',
25
+ ' ',
26
+ 'Backspace',
27
+ 'Delete',
28
+ 'Tab',
29
+ '-',
30
+ 'Enter',
31
+ 'ArrowLeft',
32
+ 'ArrowRight',
33
+ 'ArrowUp',
34
+ 'ArrowDown',
35
+ 'Home',
36
+ 'End',
37
+ ];
38
+ if (allowedKey.indexOf(e.key) >= 0) return;
39
+
40
+ // HWKTODO manage ctrl Z and ctrl U
41
+ const allowedCtrlShortcuts = ['a', 'c', 'x', 'v'];
42
+ if (e.ctrlKey && allowedCtrlShortcuts.indexOf(e.key) >= 0) return;
43
+
44
+ e.preventDefault();
45
+ }
46
+ }
@@ -0,0 +1,27 @@
1
+ import { BreakpointObserver, Breakpoints, BreakpointState } from '@angular/cdk/layout';
2
+ import { Directive, Input, OnInit, TemplateRef, ViewContainerRef } from '@angular/core';
3
+
4
+ /**
5
+ * Conditionally adds component to the tree if screen width matches at least one size in input
6
+ */
7
+ @Directive({
8
+ selector: '[ifWidthIs]',
9
+ })
10
+ export class IfWidthIsDirective implements OnInit {
11
+ @Input() ifWidthIs!: typeof Breakpoints[keyof typeof Breakpoints][];
12
+
13
+ constructor(
14
+ public breakpointObserver: BreakpointObserver,
15
+ private _templateRef: TemplateRef<any>,
16
+ private _viewContainer: ViewContainerRef
17
+ ) {}
18
+
19
+ ngOnInit() {
20
+ this.breakpointObserver.observe([...this.ifWidthIs]).subscribe((state: BreakpointState) => {
21
+ this._viewContainer.clear();
22
+ if (state.matches) {
23
+ this._viewContainer.createEmbeddedView(this._templateRef);
24
+ }
25
+ });
26
+ }
27
+ }
@@ -0,0 +1,22 @@
1
+ import { DigitOnlyDirective } from './digit-only.directive';
2
+ import { MatRowClickableDirective } from './mat-row-clickable.directive';
3
+ import { IfWidthIsDirective } from './if-width-is.directive';
4
+ import { OnemRvaColorDirective } from './color.directive';
5
+ import { OnemrvaMaskDirective } from './mask.directive';
6
+ import {OnemRvaClipboardDirective} from "./clipboard.directive";
7
+
8
+ export const directives: any[] = [
9
+ DigitOnlyDirective,
10
+ MatRowClickableDirective,
11
+ OnemRvaClipboardDirective,
12
+ IfWidthIsDirective,
13
+ OnemRvaColorDirective,
14
+ OnemrvaMaskDirective,
15
+ ];
16
+
17
+ export * from './digit-only.directive';
18
+ export * from './mat-row-clickable.directive';
19
+ export * from './if-width-is.directive';
20
+ export * from './color.directive';
21
+ export * from './mask.directive';
22
+ export * from './clipboard.directive';
@@ -0,0 +1,221 @@
1
+ import { Directive, ElementRef, HostListener, Input } from '@angular/core';
2
+
3
+ const UNDO_STACK_MAX_LENGTH = 50;
4
+
5
+ /**
6
+ * 0: digits
7
+ * A: letters (uppercase or lowercase) and digits
8
+ * S: only letters (uppercase or lowercase)
9
+ * U: only letters uppercase
10
+ * L: only letters lowercase
11
+ */
12
+
13
+ @Directive({
14
+ selector: '[onemrvamask]',
15
+ })
16
+ export class OnemrvaMaskDirective {
17
+ @Input() onemrvamask!: string;
18
+
19
+ private inputStack: string[] = ['']; // Stack for undo/redo
20
+ private stackIdx: number = 0; // Current index in undo/redo stack
21
+
22
+ markForDelete = false;
23
+
24
+ @HostListener('keydown', ['$event']) onKeyDown(event: KeyboardEvent) {
25
+ // Allow functional keystrokes
26
+ let e = <KeyboardEvent>event;
27
+
28
+ let specialKeys = [
29
+ 'Tab',
30
+ 'Enter',
31
+ 'ArrowLeft',
32
+ 'ArrowRight',
33
+ 'ArrowUp',
34
+ 'ArrowDown',
35
+ 'Home',
36
+ 'End',
37
+ ];
38
+ if (specialKeys.indexOf(e.key) >= 0) return;
39
+
40
+ specialKeys = ['Backspace', 'Delete'];
41
+ if (specialKeys.indexOf(e.key) >= 0) {
42
+ this.markForDelete = true;
43
+ return;
44
+ }
45
+
46
+ // Undo
47
+ if (e.ctrlKey && 'z' === e.key && this.stackIdx > 0) {
48
+ this.stackIdx--;
49
+ this.el.nativeElement.value = this.inputStack[this.stackIdx];
50
+ event.preventDefault();
51
+ }
52
+
53
+ // Redo
54
+ if (
55
+ e.ctrlKey &&
56
+ 'u' === e.key &&
57
+ this.stackIdx < this.inputStack.length - 1
58
+ ) {
59
+ this.stackIdx++;
60
+ this.el.nativeElement.value = this.inputStack[this.stackIdx];
61
+ event.preventDefault();
62
+ }
63
+
64
+ const allowedCtrlShortcuts = ['a', 'c', 'x', 'v'];
65
+ if (e.ctrlKey && allowedCtrlShortcuts.indexOf(e.key) >= 0) return;
66
+ }
67
+
68
+ @HostListener('input', ['$event']) onInput(event: any) {
69
+ let selectionStart = event.target?.selectionStart;
70
+
71
+ /*
72
+ * Step 1
73
+ * This block lets a delete execution if the new value is compliant.
74
+ * There are 2 deletion cases, after which the new value is not compliant
75
+ * - dd/MX/yyyy: X is deleted, then the date becomes dd/M/yyyy
76
+ * This case is resolved by step 2 and the date will become: dd/My/yyy
77
+ * - dd/MMXyyyy: the last '/' is deleted, then the date becomes dd/MMyyyy
78
+ * In this particular case, step 2 will automatically add the missing slash, so the date will remain dd/MM/yyyy
79
+ * The issue is that the caret position will remain stuck where it was, so if the user keeps hitting the delete key, nothing will change at all. This is resolved in step 4 by moving the caret to the left
80
+ */
81
+ if (this.markForDelete) {
82
+ const isCompliantAfterDeletion =
83
+ [...this.el.nativeElement.value].findIndex((char, idx) => {
84
+ let rule = this.onemrvamask[idx];
85
+
86
+ if (rule === null || rule === undefined) return true;
87
+
88
+ if (isNaN(char) && char.toLowerCase() === char.toUpperCase())
89
+ return char !== rule;
90
+
91
+ return !isAllowed(rule, char);
92
+ }) < 0;
93
+
94
+ if (isCompliantAfterDeletion) {
95
+ this.markForDelete = false;
96
+ return;
97
+ }
98
+ }
99
+
100
+ // Step 2
101
+ const compliantValue = [...this.el.nativeElement.value]
102
+ .filter((char) => isAllowed('A', char)) // Necessary when several special char in a row
103
+ .reduce((newValue, char) => {
104
+ let idx = newValue.length;
105
+ let rule = this.onemrvamask[idx];
106
+
107
+ if (rule === null || rule === undefined) return newValue;
108
+
109
+ newValue = !isAllowed(rule, char) ? newValue : newValue + char;
110
+
111
+ // Add all trailing special characters
112
+ let nextRule = this.onemrvamask[++idx];
113
+ let i = 0;
114
+
115
+ while (
116
+ nextRule !== null &&
117
+ nextRule !== undefined &&
118
+ ['0', 'A', 'S', 'U', 'L'].indexOf(nextRule) < 0
119
+ ) {
120
+ i++;
121
+ newValue += nextRule;
122
+ nextRule = this.onemrvamask[++idx];
123
+
124
+ if (i > 50) break;
125
+ }
126
+
127
+ return newValue;
128
+ }, '');
129
+
130
+ this.el.nativeElement.value = compliantValue;
131
+
132
+ // Step 3 - Find the new cursor position. If the last new character is just before special character, move the caret after
133
+ let idx = selectionStart;
134
+ let nextRule = this.onemrvamask[idx];
135
+ while (
136
+ nextRule !== null &&
137
+ nextRule !== undefined &&
138
+ ['0', 'A', 'S', 'U', 'L'].indexOf(nextRule) < 0
139
+ ) {
140
+ idx++;
141
+ nextRule = this.onemrvamask[idx];
142
+ if (
143
+ nextRule !== null &&
144
+ nextRule &&
145
+ ['0', 'A', 'S', 'U', 'L'].indexOf(nextRule) < 0
146
+ ) {
147
+ } else break;
148
+ }
149
+
150
+ // Step 4 - Update inputStack only when all characters are processed
151
+ if (compliantValue !== this.inputStack[this.stackIdx]) {
152
+ this.stackIdx++;
153
+
154
+ let stack =
155
+ this.stackIdx > UNDO_STACK_MAX_LENGTH - 1
156
+ ? this.inputStack.slice(1)
157
+ : this.inputStack.splice(0, this.stackIdx);
158
+ this.stackIdx =
159
+ this.stackIdx > UNDO_STACK_MAX_LENGTH - 1
160
+ ? UNDO_STACK_MAX_LENGTH - 1
161
+ : this.stackIdx;
162
+
163
+ this.inputStack = [...stack, compliantValue];
164
+
165
+ this.el.nativeElement.setSelectionRange(idx, idx);
166
+ } else {
167
+ if (this.markForDelete) {
168
+ let i = idx - 1;
169
+ /* Finds the last rule character before the special character.
170
+ * e.g: (nnn)/nnnn
171
+ * If the user tries to delete '/', the caret will move just before ')'
172
+ */
173
+ while (i > 0) {
174
+ const previousRule = this.onemrvamask[i - 1];
175
+ if (['0', 'A', 'S', 'U', 'L'].indexOf(previousRule) < 0) {
176
+ i--;
177
+ } else break;
178
+ }
179
+ this.el.nativeElement.setSelectionRange(i, i);
180
+ }
181
+ }
182
+ this.markForDelete = false;
183
+ }
184
+ constructor(private el: ElementRef) {}
185
+ }
186
+
187
+ function isAllowed(rule: string, character: string) {
188
+ switch (rule) {
189
+ // Any digit
190
+ case '0':
191
+ if (character === ' ') return false;
192
+ return !isNaN(+character);
193
+
194
+ // A: letters (uppercase or lowercase) and digits
195
+ case 'A':
196
+ if (character === ' ') return false;
197
+ return (
198
+ !isNaN(+character) || character.toLowerCase() != character.toUpperCase()
199
+ );
200
+
201
+ // only letters (uppercase or lowercase)
202
+ case 'S':
203
+ return character.toLowerCase() != character.toUpperCase();
204
+
205
+ // only uppercase letters
206
+ case 'U':
207
+ return (
208
+ character.toLowerCase() != character.toUpperCase() &&
209
+ character === character.toUpperCase()
210
+ );
211
+
212
+ // only lowercase letters
213
+ case 'L':
214
+ return (
215
+ character.toLowerCase() != character.toUpperCase() &&
216
+ character === character.toLowerCase()
217
+ );
218
+ }
219
+
220
+ return false;
221
+ }
@@ -0,0 +1,58 @@
1
+ import {Directive, ElementRef, EventEmitter, HostBinding, HostListener, Input, Output} from '@angular/core';
2
+ import {OnemrvaMatColor} from "@onemrvapublic/design-system/utils";
3
+
4
+ @Directive({
5
+ selector: '[mat-row-clickable]'
6
+ })
7
+ export class MatRowClickableDirective {
8
+
9
+ @Output('mat-row-clickable')
10
+ matRowClickable: EventEmitter<any> = new EventEmitter<any>();
11
+
12
+ @Input()
13
+ color: OnemrvaMatColor = '';
14
+
15
+ constructor(private el:ElementRef) {}
16
+
17
+ @HostListener('click', ['$event'])
18
+ click(event: any) {
19
+ this.matRowClickable.emit();
20
+ }
21
+
22
+ @HostBinding('class.onemrva-clickable-row')
23
+ public cssClass = true;
24
+
25
+ ngOnInit() {
26
+ }
27
+
28
+ /** @hidden @internal */
29
+ @HostBinding('class.mat-primary')
30
+ public get _isPrimary(): boolean {
31
+ return this.color === OnemrvaMatColor.PRIMARY;
32
+ }
33
+ /** @hidden @internal */
34
+ @HostBinding('class.mat-accent')
35
+ public get _isAccent(): boolean {
36
+ return this.color === OnemrvaMatColor.ACCENT;
37
+ }
38
+ /** @hidden @internal */
39
+ @HostBinding('class.mat-error')
40
+ public get _isError(): boolean {
41
+ return this.color === OnemrvaMatColor.ERROR;
42
+ }
43
+ /** @hidden @internal */
44
+ @HostBinding('class.mat-warn')
45
+ public get _isWarn(): boolean {
46
+ return this.color === OnemrvaMatColor.WARN;
47
+ }
48
+ /** @hidden @internal */
49
+ @HostBinding('class.mat-success')
50
+ public get _isSuccess(): boolean {
51
+ return this.color === OnemrvaMatColor.SUCCESS;
52
+ }
53
+ /** @hidden @internal */
54
+ @HostBinding('class.mat-info')
55
+ public get _isInfo(): boolean {
56
+ return this.color === OnemrvaMatColor.INFO;
57
+ }
58
+ }
@@ -0,0 +1,5 @@
1
+ import { OnemrvaBcePipe } from './onemrva-bce.pipe';
2
+ import { OnemrvaNissPipe } from './onemrva-niss.pipe';
3
+
4
+ export * from './onemrva-bce.pipe';
5
+ export * from './onemrva-niss.pipe';
@@ -0,0 +1,18 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+
3
+ @Pipe({
4
+ name: 'onemrvaBce',
5
+ standalone: true,
6
+ })
7
+ export class OnemrvaBcePipe implements PipeTransform {
8
+ transform(value: string): string {
9
+ let strOut = value.trim().replace(/\/|\.|\-/g, '');
10
+
11
+ if (strOut.length !== 10) return '?01?.???.???';
12
+
13
+ return `${strOut.substring(0, 4)}.${strOut.substring(
14
+ 4,
15
+ 7
16
+ )}.${strOut.substring(7, 10)}`;
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+
3
+ @Pipe({
4
+ name: 'onemrvaNiss',
5
+ standalone: true,
6
+ })
7
+ export class OnemrvaNissPipe implements PipeTransform {
8
+ transform(value: string): string {
9
+ let strOut = value.trim().replace(/\/|\.|\-/g, '');
10
+
11
+ if (strOut.length !== 11) return '??01??/???-??';
12
+
13
+ return `${strOut.substring(0, 6)}/${strOut.substring(
14
+ 6,
15
+ 9
16
+ )}-${strOut.substring(9, 11)}`;
17
+ }
18
+ }
@@ -0,0 +1,10 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { NgModule } from '@angular/core';
3
+ import { directives } from './directives';
4
+
5
+ @NgModule({
6
+ declarations: [...directives],
7
+ imports: [CommonModule],
8
+ exports: [...directives, CommonModule],
9
+ })
10
+ export class OnemrvaSharedModule {}
@@ -0,0 +1,66 @@
1
+ import { ValidationErrors } from '@angular/forms';
2
+ import { AbstractControl, ValidatorFn } from '@angular/forms';
3
+
4
+ export class OnemrvaValidators {
5
+ static bce(required = false): ValidatorFn {
6
+ return (control: AbstractControl): ValidationErrors | null => {
7
+ if (control.value === null || control.value.trim() === '') {
8
+ return required ? null : { bceNull: true };
9
+ }
10
+
11
+ let bceCandidate = control.value.trim().replace(/\/|\.|\-/g, '');
12
+
13
+ if (bceCandidate.length !== 10)
14
+ return { bceLengthError: { value: bceCandidate } };
15
+
16
+ if (Number.isNaN(+bceCandidate))
17
+ return { bceNan: { value: bceCandidate } };
18
+
19
+ return null;
20
+ };
21
+ }
22
+
23
+ static niss(required = false): ValidatorFn {
24
+ return (control: AbstractControl): ValidationErrors | null => {
25
+ if (control.value === null || control.value.trim() === '')
26
+ return required ? null : { nissNull: true };
27
+
28
+ let nissCandidate = control.value.trim().replace(/\/|\.|\-/g, '');
29
+
30
+ if (nissCandidate.length !== 11)
31
+ return { nissLengthError: { value: nissCandidate } };
32
+
33
+ if (Number.isNaN(+nissCandidate))
34
+ return { bceNan: { value: nissCandidate } };
35
+
36
+ const date = +nissCandidate.substring(4, 6);
37
+ const month = +nissCandidate.substring(2, 4) - 1;
38
+ const currentYear = new Date().getFullYear() - 2000;
39
+ let year = +nissCandidate.substring(0, 2);
40
+ year = year > currentYear ? 1900 + year : 2000 + year;
41
+
42
+ // Check month
43
+ if (month > 11) return { nissInvalidMonth: { value: nissCandidate } };
44
+
45
+ // Check date
46
+ if (date > 31) return { nissInvalidDate: { value: nissCandidate } };
47
+
48
+ // Check date consistency
49
+ const d = new Date(year, month, date);
50
+ if (d.getDate() !== date || d.getMonth() !== month)
51
+ // This catches errors such as 32 Jan, which becomes 1 Feb inside the date object
52
+ return { nissInconsistentDate: { value: nissCandidate } };
53
+
54
+ // Check digit
55
+ let nissStart =
56
+ year < 2000
57
+ ? +nissCandidate.substring(0, 9)
58
+ : +('2' + nissCandidate.substring(0, 9));
59
+
60
+ if (97 - (nissStart % 97) !== +nissCandidate.substring(9))
61
+ return { nissCheckDigitError: { value: nissCandidate } };
62
+
63
+ return null;
64
+ };
65
+ }
66
+ }