mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
feat(i18n): add better solution for formly translations #33
This commit is contained in:
parent
03cf3d4bc0
commit
820aa166e4
70 changed files with 82 additions and 1555 deletions
|
|
@ -145,6 +145,7 @@
|
|||
"@ngrx/store": "8.1.0",
|
||||
"@ngrx/store-devtools": "^8.1.0",
|
||||
"@ngx-formly/core": "^5.2.0",
|
||||
"@ngx-formly/material": "^5.2.0",
|
||||
"@ngx-translate/core": "^11.0.1",
|
||||
"@ngx-translate/http-loader": "^4.0.0",
|
||||
"@schematics/angular": "^8.0.1",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {UiModule} from './ui/ui.module';
|
|||
import {reducers} from './root-store';
|
||||
import {CoreModule} from './core/core.module';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {FORMLY_CONFIG, FormlyModule} from '@ngx-formly/core';
|
||||
import {PagesModule} from './pages/pages.module';
|
||||
import {MainHeaderModule} from './core-ui/main-header/main-header.module';
|
||||
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
||||
|
|
@ -31,6 +31,7 @@ import {ProcrastinationModule} from './features/procrastination/procrastination.
|
|||
import {TaskRepeatCfgModule} from './features/task-repeat-cfg/task-repeat-cfg.module';
|
||||
import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core';
|
||||
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
|
||||
import {registerTranslateExtension} from './ui/formly-translate-extension/formly-translate-extension';
|
||||
|
||||
// NOTE: export required for aot to work
|
||||
export function createTranslateLoader(http: HttpClient) {
|
||||
|
|
@ -91,7 +92,13 @@ export function createTranslateLoader(http: HttpClient) {
|
|||
bootstrap: [AppComponent],
|
||||
providers: [
|
||||
{provide: ErrorHandler, useClass: GlobalErrorHandler},
|
||||
{provide: HAMMER_GESTURE_CONFIG, useClass: MyHammerConfig}
|
||||
{provide: HAMMER_GESTURE_CONFIG, useClass: MyHammerConfig},
|
||||
{
|
||||
provide: FORMLY_CONFIG,
|
||||
multi: true,
|
||||
useFactory: registerTranslateExtension,
|
||||
deps: [TranslateService],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core';
|
||||
import {MatInput} from '@angular/material/input';
|
||||
import {FieldType} from '../../../ui/formly-material-components/form-field';
|
||||
import {FieldType} from '@ngx-formly/material';
|
||||
|
||||
@Component({
|
||||
selector: 'keyboard-input',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core';
|
||||
import {MatInput} from '@angular/material/input';
|
||||
import {FieldType} from '../../formly-material-components/form-field';
|
||||
import {FieldType} from '@ngx-formly/material';
|
||||
|
||||
@Component({
|
||||
selector: 'input-duration-formly',
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export * from './src/public_api';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ngPackage": {}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
import {FormlyMatFormFieldModule} from '../../form-field';
|
||||
import {MatCheckboxModule} from '@angular/material/checkbox';
|
||||
|
||||
import {FormlyFieldCheckbox} from './checkbox.type';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FormlyFieldCheckbox],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
TranslateModule,
|
||||
|
||||
MatCheckboxModule,
|
||||
|
||||
FormlyMatFormFieldModule,
|
||||
FormlyModule.forChild({
|
||||
types: [
|
||||
{
|
||||
name: 'checkbox',
|
||||
component: FormlyFieldCheckbox,
|
||||
wrappers: ['form-field'],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class FormlyMatCheckboxModule {
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
import {AfterViewInit, Component, OnDestroy, ViewChild} from '@angular/core';
|
||||
import {FieldType} from '../../form-field';
|
||||
import {MatCheckbox} from '@angular/material/checkbox';
|
||||
import {FocusMonitor} from '@angular/cdk/a11y';
|
||||
|
||||
@Component({
|
||||
selector: 'formly-field-mat-checkbox',
|
||||
template: `
|
||||
<mat-checkbox
|
||||
[formControl]="formControl"
|
||||
[id]="id"
|
||||
[formlyAttributes]="field"
|
||||
[tabindex]="to.tabindex || 0"
|
||||
[indeterminate]="to.indeterminate && formControl.value === null"
|
||||
[color]="to.color"
|
||||
[labelPosition]="to.align || to.labelPosition">
|
||||
{{ to.label|translate }}
|
||||
<span *ngIf="to.required && to.hideRequiredMarker !== true"
|
||||
class="mat-form-field-required-marker">*</span>
|
||||
</mat-checkbox>
|
||||
`,
|
||||
})
|
||||
export class FormlyFieldCheckbox extends FieldType implements AfterViewInit, OnDestroy {
|
||||
@ViewChild(MatCheckbox, {static: true}) checkbox!: MatCheckbox;
|
||||
defaultOptions = {
|
||||
templateOptions: {
|
||||
hideFieldUnderline: true,
|
||||
indeterminate: true,
|
||||
floatLabel: 'always',
|
||||
hideLabel: true,
|
||||
align: 'start', // start or end
|
||||
},
|
||||
};
|
||||
|
||||
constructor(private focusMonitor: FocusMonitor) {
|
||||
super();
|
||||
}
|
||||
|
||||
onContainerClick(event: MouseEvent): void {
|
||||
this.checkbox.focus();
|
||||
super.onContainerClick(event);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
super.ngAfterViewInit();
|
||||
this.focusMonitor
|
||||
.monitor(this.checkbox._inputElement.nativeElement)
|
||||
.subscribe(focusOrigin => this.field.focus = !!focusOrigin);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
this.focusMonitor.stopMonitoring(this.checkbox._inputElement.nativeElement);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export {FormlyMatCheckboxModule} from './checkbox.module';
|
||||
export {FormlyFieldCheckbox} from './checkbox.type';
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './src/public_api';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ngPackage": {}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
import {FormlyMatFormFieldModule} from '../../form-field';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
import {MatDatepickerModule} from '@angular/material/datepicker';
|
||||
|
||||
import {FormlyDatepickerTypeComponent} from './datepicker.type';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FormlyDatepickerTypeComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
MatInputModule,
|
||||
MatDatepickerModule,
|
||||
TranslateModule,
|
||||
|
||||
FormlyMatFormFieldModule,
|
||||
FormlyModule.forChild({
|
||||
types: [{
|
||||
name: 'datepicker',
|
||||
component: FormlyDatepickerTypeComponent,
|
||||
wrappers: ['form-field'],
|
||||
}],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class FormlyMatDatepickerModule {
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
import {AfterViewInit, Component, TemplateRef, ViewChild} from '@angular/core';
|
||||
import {ɵdefineHiddenProp as defineHiddenProp} from '@ngx-formly/core';
|
||||
import {FieldType} from '../../form-field';
|
||||
import {MatInput} from '@angular/material/input';
|
||||
import {MatDatepickerInput} from '@angular/material/datepicker';
|
||||
|
||||
@Component({
|
||||
selector: 'formly-field-mat-datepicker',
|
||||
template: `
|
||||
<input matInput
|
||||
[id]="id"
|
||||
[errorStateMatcher]="errorStateMatcher"
|
||||
[formControl]="formControl"
|
||||
[matDatepicker]="picker"
|
||||
[matDatepickerFilter]="to.datepickerOptions.filter"
|
||||
[max]="to.datepickerOptions.max"
|
||||
[min]="to.datepickerOptions.min"
|
||||
[formlyAttributes]="field"
|
||||
[placeholder]="to.placeholder"
|
||||
[tabindex]="to.tabindex || 0"
|
||||
[readonly]="to.readonly">
|
||||
<ng-template #datepickerToggle>
|
||||
<mat-datepicker-toggle [for]="picker"></mat-datepicker-toggle>
|
||||
</ng-template>
|
||||
<mat-datepicker #picker
|
||||
[color]="to.color"
|
||||
[touchUi]="to.datepickerOptions.touchUi"
|
||||
[startView]="to.datepickerOptions.startView"
|
||||
[startAt]="to.datepickerOptions.startAt">
|
||||
</mat-datepicker>
|
||||
`,
|
||||
})
|
||||
export class FormlyDatepickerTypeComponent extends FieldType implements AfterViewInit {
|
||||
@ViewChild(MatInput, {static: true}) formFieldControl!: MatInput;
|
||||
@ViewChild(MatDatepickerInput, {static: true}) datepickerInput!: MatDatepickerInput<any>;
|
||||
@ViewChild('datepickerToggle', {static: true}) datepickerToggle!: TemplateRef<any>;
|
||||
|
||||
defaultOptions = {
|
||||
templateOptions: {
|
||||
datepickerOptions: {
|
||||
startView: 'month',
|
||||
datepickerTogglePosition: 'suffix',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
ngAfterViewInit() {
|
||||
super.ngAfterViewInit();
|
||||
// temporary fix for https://github.com/angular/material2/issues/6728
|
||||
(<any>this.datepickerInput)._formField = this.formField;
|
||||
|
||||
setTimeout(() => {
|
||||
defineHiddenProp(this.field, '_mat' + this.to.datepickerOptions.datepickerTogglePosition, this.datepickerToggle);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export {FormlyMatDatepickerModule} from './datepicker.module';
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './src/public_api';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ngPackage": {}
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
import {AfterViewInit, OnDestroy, OnInit, TemplateRef, Type, ViewChild} from '@angular/core';
|
||||
import {FieldType as CoreFieldType, FormlyFieldConfig, ɵdefineHiddenProp as defineHiddenProp} from '@ngx-formly/core';
|
||||
import {Subject} from 'rxjs';
|
||||
import {MatFormField, MatFormFieldControl} from '@angular/material/form-field';
|
||||
import {FormlyErrorStateMatcher} from './formly.error-state-matcher';
|
||||
|
||||
export abstract class FieldType<F extends FormlyFieldConfig = FormlyFieldConfig> extends CoreFieldType<F> implements OnInit, AfterViewInit, OnDestroy, MatFormFieldControl<any> {
|
||||
@ViewChild('matPrefix', {static: true}) matPrefix!: TemplateRef<any>;
|
||||
@ViewChild('matSuffix', {static: true}) matSuffix!: TemplateRef<any>;
|
||||
errorStateMatcher = new FormlyErrorStateMatcher(this);
|
||||
stateChanges = new Subject<void>();
|
||||
private _control!: MatFormFieldControl<any>;
|
||||
|
||||
get formFieldControl() {
|
||||
return this._control || this;
|
||||
}
|
||||
|
||||
set formFieldControl(control: MatFormFieldControl<any>) {
|
||||
this._control = control;
|
||||
if (this.formField && control !== this.formField._control) {
|
||||
this.formField._control = control;
|
||||
}
|
||||
}
|
||||
|
||||
_errorState = false;
|
||||
|
||||
get errorState() {
|
||||
const showError = this.options!.showError!(this);
|
||||
if (showError !== this._errorState) {
|
||||
this._errorState = showError;
|
||||
this.stateChanges.next();
|
||||
}
|
||||
|
||||
return showError;
|
||||
}
|
||||
|
||||
get controlType() {
|
||||
if (this.to.type) {
|
||||
return this.to.type;
|
||||
}
|
||||
|
||||
if ((<any>this.field.type) instanceof Type) {
|
||||
return this.field.type!.constructor.name;
|
||||
}
|
||||
|
||||
return this.field.type!;
|
||||
}
|
||||
|
||||
get focused() {
|
||||
return !!this.field.focus && !this.disabled;
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
return !!this.to.disabled;
|
||||
}
|
||||
|
||||
get required() {
|
||||
return !!this.to.required;
|
||||
}
|
||||
|
||||
get placeholder() {
|
||||
return this.to.placeholder || '';
|
||||
}
|
||||
|
||||
get shouldPlaceholderFloat() {
|
||||
return this.shouldLabelFloat;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.formControl.value;
|
||||
}
|
||||
|
||||
set value(value) {
|
||||
this.formControl.patchValue(value);
|
||||
}
|
||||
|
||||
get ngControl() {
|
||||
return this.formControl as any;
|
||||
}
|
||||
|
||||
get empty() {
|
||||
return this.value === undefined || this.value === null || this.value === '';
|
||||
}
|
||||
|
||||
get shouldLabelFloat() {
|
||||
return this.focused || !this.empty;
|
||||
}
|
||||
|
||||
get formField(): MatFormField {
|
||||
return this.field ? (<any>this.field)['__formField__'] : null;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.formField) {
|
||||
this.formField._control = this.formFieldControl;
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (this.matPrefix || this.matSuffix) {
|
||||
setTimeout(() => {
|
||||
defineHiddenProp(this.field, '_matprefix', this.matPrefix);
|
||||
defineHiddenProp(this.field, '_matsuffix', this.matSuffix);
|
||||
(<any>this.options)._markForCheck(this.field);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.formField) {
|
||||
delete this.formField._control;
|
||||
}
|
||||
this.stateChanges.complete();
|
||||
}
|
||||
|
||||
setDescribedByIds(ids: string[]): void {
|
||||
}
|
||||
|
||||
onContainerClick(event: MouseEvent): void {
|
||||
this.field.focus = true;
|
||||
this.stateChanges.next();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||
import {FormlyWrapperFormField} from './form-field.wrapper';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FormlyWrapperFormField],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
TranslateModule,
|
||||
FormlyModule.forChild({
|
||||
wrappers: [{
|
||||
name: 'form-field',
|
||||
component: FormlyWrapperFormField,
|
||||
}],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class FormlyMatFormFieldModule {
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
import {
|
||||
AfterContentChecked,
|
||||
AfterViewInit,
|
||||
Component,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Renderer2,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
ViewContainerRef
|
||||
} from '@angular/core';
|
||||
import {FieldWrapper, FormlyFieldConfig, ɵdefineHiddenProp as defineHiddenProp} from '@ngx-formly/core';
|
||||
import {MatFormField, MatFormFieldControl} from '@angular/material/form-field';
|
||||
import {Subject} from 'rxjs';
|
||||
|
||||
import {FieldType} from './field.type';
|
||||
|
||||
interface MatFormlyFieldConfig extends FormlyFieldConfig {
|
||||
_matprefix: TemplateRef<any>;
|
||||
_matsuffix: TemplateRef<any>;
|
||||
__formField__: FormlyWrapperFormField;
|
||||
_componentFactory: any;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'formly-wrapper-mat-form-field',
|
||||
template: `
|
||||
<!-- fix https://github.com/angular/material2/pull/7083 by setting width to 100% -->
|
||||
<mat-form-field
|
||||
[hideRequiredMarker]="true"
|
||||
[floatLabel]="to.floatLabel"
|
||||
[appearance]="to.appearance"
|
||||
[color]="to.color"
|
||||
[style.width]="'100%'">
|
||||
<ng-container #fieldComponent></ng-container>
|
||||
<mat-label *ngIf="to.label && to.hideLabel !== true">
|
||||
{{ to.label|translate }}
|
||||
<span *ngIf="to.required && to.hideRequiredMarker !== true"
|
||||
class="mat-form-field-required-marker">*</span>
|
||||
</mat-label>
|
||||
|
||||
<ng-container matPrefix>
|
||||
<ng-container *ngTemplateOutlet="to.prefix ? to.prefix : formlyField._matprefix"></ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matSuffix>
|
||||
<ng-container *ngTemplateOutlet="to.suffix ? to.suffix : formlyField._matsuffix"></ng-container>
|
||||
</ng-container>
|
||||
|
||||
<!-- fix https://github.com/angular/material2/issues/7737 by setting id to null -->
|
||||
<mat-error [id]="null">
|
||||
<formly-validation-message [field]="field"></formly-validation-message>
|
||||
</mat-error>
|
||||
<!-- fix https://github.com/angular/material2/issues/7737 by setting id to null -->
|
||||
<mat-hint *ngIf="to.description"
|
||||
[id]="null">{{ to.description }}</mat-hint>
|
||||
</mat-form-field>
|
||||
`,
|
||||
providers: [{provide: MatFormFieldControl, useExisting: FormlyWrapperFormField}],
|
||||
})
|
||||
export class FormlyWrapperFormField extends FieldWrapper<MatFormlyFieldConfig> implements OnInit, OnDestroy, MatFormFieldControl<any>, AfterViewInit, AfterContentChecked {
|
||||
@ViewChild('fieldComponent', {read: ViewContainerRef, static: true}) fieldComponent!: ViewContainerRef;
|
||||
@ViewChild(MatFormField, {static: true}) formField!: MatFormField;
|
||||
|
||||
stateChanges = new Subject<void>();
|
||||
private initialGapCalculated = false;
|
||||
|
||||
constructor(private renderer: Renderer2) {
|
||||
super();
|
||||
}
|
||||
|
||||
_errorState = false;
|
||||
|
||||
get errorState() {
|
||||
const showError = this.options!.showError!(this);
|
||||
if (showError !== this._errorState) {
|
||||
this._errorState = showError;
|
||||
this.stateChanges.next();
|
||||
}
|
||||
|
||||
return showError;
|
||||
}
|
||||
|
||||
get controlType() {
|
||||
return this.to.type;
|
||||
}
|
||||
|
||||
get focused() {
|
||||
return !!this.formlyField.focus && !this.disabled;
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
return !!this.to.disabled;
|
||||
}
|
||||
|
||||
get required() {
|
||||
return !!this.to.required;
|
||||
}
|
||||
|
||||
get placeholder() {
|
||||
return this.to.placeholder || '';
|
||||
}
|
||||
|
||||
get shouldPlaceholderFloat() {
|
||||
return this.shouldLabelFloat;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.formControl.value;
|
||||
}
|
||||
|
||||
get ngControl() {
|
||||
return this.formControl as any;
|
||||
}
|
||||
|
||||
get empty() {
|
||||
return !this.formControl.value;
|
||||
}
|
||||
|
||||
get shouldLabelFloat() {
|
||||
return this.focused || !this.empty;
|
||||
}
|
||||
|
||||
get formlyField() {
|
||||
return this.field as MatFormlyFieldConfig;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.formField._control = this;
|
||||
defineHiddenProp(this.field, '__formField__', this.formField);
|
||||
|
||||
const fieldComponent = this.formlyField['_componentFactory'];
|
||||
if (fieldComponent && !(fieldComponent.componentRef.instance instanceof FieldType)) {
|
||||
console.warn(`Component '${fieldComponent.component.prototype.constructor.name}' must extend 'FieldType' from '@ngx-formly/material'.`);
|
||||
}
|
||||
|
||||
// fix for https://github.com/angular/material2/issues/11437
|
||||
if (this.formlyField.hide && this.formlyField.templateOptions!.appearance === 'outline') {
|
||||
this.initialGapCalculated = true;
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
if (!this.initialGapCalculated || this.formlyField.hide) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.formField.updateOutlineGap();
|
||||
this.initialGapCalculated = true;
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
// temporary fix for https://github.com/angular/material2/issues/7891
|
||||
if (this.formField.underlineRef && this.to.hideFieldUnderline === true) {
|
||||
this.renderer.removeClass(this.formField.underlineRef.nativeElement, 'mat-form-field-underline');
|
||||
this.renderer.removeClass(this.formField.underlineRef.nativeElement.firstChild, 'mat-form-field-ripple');
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
delete this.formlyField.__formField__;
|
||||
this.stateChanges.complete();
|
||||
}
|
||||
|
||||
setDescribedByIds(ids: string[]): void {
|
||||
}
|
||||
|
||||
onContainerClick(event: MouseEvent): void {
|
||||
this.formlyField.focus = true;
|
||||
this.stateChanges.next();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import {FormControl, FormGroupDirective, NgForm} from '@angular/forms';
|
||||
import {ErrorStateMatcher} from '@angular/material/core';
|
||||
import {FieldType} from '@ngx-formly/core';
|
||||
|
||||
export class FormlyErrorStateMatcher implements ErrorStateMatcher {
|
||||
constructor(private field: FieldType) {
|
||||
}
|
||||
|
||||
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
||||
return this.field && this.field.showError;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export {FormlyMatFormFieldModule} from './form-field.module';
|
||||
export {FieldType} from './field.type';
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './src/public_api';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ngPackage": {}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
import {FormlyMatFormFieldModule} from '../../form-field';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
|
||||
import {FormlyFieldInput} from './input.type';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FormlyFieldInput],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
MatInputModule,
|
||||
TranslateModule,
|
||||
|
||||
FormlyMatFormFieldModule,
|
||||
FormlyModule.forChild({
|
||||
types: [
|
||||
{
|
||||
name: 'input',
|
||||
component: FormlyFieldInput,
|
||||
wrappers: ['form-field'],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class FormlyMatInputModule {
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {FormlyFieldConfig, FormlyFormOptions, FormlyModule} from '@ngx-formly/core';
|
||||
import {FormlyFieldInput} from './input.type';
|
||||
|
||||
@Component({
|
||||
selector: 'formly-test',
|
||||
template: `
|
||||
<formly-form [form]="form" [fields]="fields" [model]="model" [options]="options"></formly-form>
|
||||
`,
|
||||
})
|
||||
class FormlyTestComponent {
|
||||
form = new FormGroup({});
|
||||
fields: FormlyFieldConfig[];
|
||||
model: any;
|
||||
options: FormlyFormOptions;
|
||||
}
|
||||
|
||||
describe('ui-material: Formly Input Component', () => {
|
||||
let fixture: ComponentFixture<FormlyTestComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FormlyTestComponent, FormlyFieldInput],
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
FormlyModule.forRoot({
|
||||
types: [
|
||||
{
|
||||
name: 'input',
|
||||
component: FormlyFieldInput,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FormlyTestComponent);
|
||||
});
|
||||
|
||||
it('should properly set the readonly value on text inputs', () => {
|
||||
const componentInstance = fixture.componentInstance;
|
||||
componentInstance.fields = [
|
||||
{
|
||||
key: 'name',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
readonly: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
fixture.detectChanges();
|
||||
|
||||
// assert
|
||||
const inputField = fixture.debugElement.query(By.css('input'));
|
||||
expect(inputField.nativeElement.getAttribute('readonly')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should properly set the readonly value on number inputs', () => {
|
||||
const componentInstance = fixture.componentInstance;
|
||||
componentInstance.fields = [
|
||||
{
|
||||
key: 'name',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
type: 'number',
|
||||
readonly: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
fixture.detectChanges();
|
||||
|
||||
// assert
|
||||
const inputField = fixture.debugElement.query(By.css('input'));
|
||||
expect(inputField.nativeElement.getAttribute('readonly')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||
import {MatInput} from '@angular/material/input';
|
||||
import {FieldType} from '../../form-field';
|
||||
|
||||
@Component({
|
||||
selector: 'formly-field-mat-input',
|
||||
template: `
|
||||
<input *ngIf="type !== 'number'; else numberTmp"
|
||||
matInput
|
||||
[id]="id"
|
||||
[readonly]="to.readonly"
|
||||
[type]="type || 'text'"
|
||||
[errorStateMatcher]="errorStateMatcher"
|
||||
[formControl]="formControl"
|
||||
[formlyAttributes]="field"
|
||||
[tabindex]="to.tabindex || 0"
|
||||
[placeholder]="to.placeholder|translate">
|
||||
<ng-template #numberTmp>
|
||||
<input matInput
|
||||
[id]="id"
|
||||
type="number"
|
||||
[readonly]="to.readonly"
|
||||
[errorStateMatcher]="errorStateMatcher"
|
||||
[formControl]="formControl"
|
||||
[formlyAttributes]="field"
|
||||
[tabindex]="to.tabindex || 0"
|
||||
[placeholder]="to.placeholder|translate">
|
||||
</ng-template>
|
||||
`,
|
||||
})
|
||||
export class FormlyFieldInput extends FieldType implements OnInit {
|
||||
@ViewChild(MatInput, {static: true}) formFieldControl!: MatInput;
|
||||
|
||||
get type() {
|
||||
return this.to.type || 'text';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export {FormlyMatInputModule} from './input.module';
|
||||
export {FormlyFieldInput} from './input.type';
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './src/public_api';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ngPackage": {}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {FormlySelectModule} from '@ngx-formly/core/select';
|
||||
import {FormlyMatFormFieldModule} from '../../form-field';
|
||||
import {MatCheckboxModule} from '@angular/material/checkbox';
|
||||
|
||||
import {FormlyFieldMultiCheckbox} from './multicheckbox.type';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FormlyFieldMultiCheckbox],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
TranslateModule,
|
||||
|
||||
MatCheckboxModule,
|
||||
|
||||
FormlyMatFormFieldModule,
|
||||
FormlySelectModule,
|
||||
FormlyModule.forChild({
|
||||
types: [
|
||||
{
|
||||
name: 'multicheckbox',
|
||||
component: FormlyFieldMultiCheckbox,
|
||||
wrappers: ['form-field'],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class FormlyMatMultiCheckboxModule {
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {FieldType} from '../../form-field';
|
||||
|
||||
@Component({
|
||||
selector: 'formly-field-mat-multicheckbox',
|
||||
template: `
|
||||
<ng-container *ngFor="let option of to.options | formlySelectOptions:field | async; let i = index;">
|
||||
<mat-checkbox
|
||||
[id]="id + '_' + i"
|
||||
[formlyAttributes]="field"
|
||||
[tabindex]="to.tabindex || 0"
|
||||
[color]="to.color"
|
||||
[labelPosition]="to.labelPosition"
|
||||
[checked]="formControl.value && (this.to.type === 'array' ? formControl.value.includes(option.value) : formControl.value[option.value])"
|
||||
(change)="onChange(option.value, $event.checked)">
|
||||
{{ option.label|translate }}
|
||||
</mat-checkbox>
|
||||
</ng-container>
|
||||
`,
|
||||
})
|
||||
export class FormlyFieldMultiCheckbox extends FieldType {
|
||||
defaultOptions = {
|
||||
templateOptions: {
|
||||
hideFieldUnderline: true,
|
||||
floatLabel: 'always',
|
||||
options: [],
|
||||
},
|
||||
};
|
||||
|
||||
onChange(value: any, checked: boolean) {
|
||||
if (this.to.type === 'array') {
|
||||
this.formControl.patchValue(checked
|
||||
? [...(this.formControl.value || []), value]
|
||||
: [...(this.formControl.value || [])].filter(o => o !== value),
|
||||
);
|
||||
} else {
|
||||
this.formControl.patchValue({...this.formControl.value, [value]: checked});
|
||||
}
|
||||
this.formControl.markAsTouched();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export {FormlyMatMultiCheckboxModule} from './multicheckbox.module';
|
||||
export {FormlyFieldMultiCheckbox} from './multicheckbox.type';
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './src/public_api';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ngPackage": {}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {FormlySelectModule} from '@ngx-formly/core/select';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
import {FormlyMatFormFieldModule} from '../../form-field';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
|
||||
import {FormlyFieldNativeSelect} from './native-select.type';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FormlyFieldNativeSelect],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
MatInputModule,
|
||||
TranslateModule,
|
||||
|
||||
FormlyMatFormFieldModule,
|
||||
FormlySelectModule,
|
||||
FormlyModule.forChild({
|
||||
types: [
|
||||
{
|
||||
name: 'native-select',
|
||||
component: FormlyFieldNativeSelect,
|
||||
wrappers: ['form-field'],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class FormlyMatNativeSelectModule {
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
import {Component, ViewChild} from '@angular/core';
|
||||
import {FieldType} from '../../form-field';
|
||||
import {MatInput} from '@angular/material/input';
|
||||
|
||||
@Component({
|
||||
selector: 'formly-field-mat-native-select',
|
||||
template: `
|
||||
<select matNativeControl
|
||||
[id]="id"
|
||||
[readonly]="to.readonly"
|
||||
[errorStateMatcher]="errorStateMatcher"
|
||||
[formControl]="formControl"
|
||||
[formlyAttributes]="field">
|
||||
<option *ngIf="to.placeholder"
|
||||
[ngValue]="null">{{ to.placeholder }}</option>
|
||||
<ng-container *ngFor="let item of to.options | formlySelectOptions:field | async">
|
||||
<optgroup *ngIf="item.group"
|
||||
label="{{item.label|translate}}">
|
||||
<option *ngFor="let child of item.group"
|
||||
[ngValue]="child.value"
|
||||
[disabled]="child.disabled">
|
||||
{{ child.label|translate }}
|
||||
</option>
|
||||
</optgroup>
|
||||
<option *ngIf="!item.group"
|
||||
[ngValue]="item.value"
|
||||
[disabled]="item.disabled">{{ item.label|translate }}</option>
|
||||
</ng-container>
|
||||
</select>
|
||||
`,
|
||||
})
|
||||
export class FormlyFieldNativeSelect extends FieldType {
|
||||
@ViewChild(MatInput, {static: true}) formFieldControl!: MatInput;
|
||||
defaultOptions = {
|
||||
templateOptions: {
|
||||
options: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export {FormlyMatNativeSelectModule} from './native-select.module';
|
||||
export {FormlyFieldNativeSelect} from './native-select.type';
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './src/public_api';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ngPackage": {}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export {FormlyMatRadioModule} from './radio.module';
|
||||
export {FormlyFieldRadio} from './radio.type';
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {FormlySelectModule} from '@ngx-formly/core/select';
|
||||
|
||||
import {FormlyMatFormFieldModule} from '../../form-field';
|
||||
import {MatRadioModule} from '@angular/material/radio';
|
||||
|
||||
import {FormlyFieldRadio} from './radio.type';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FormlyFieldRadio],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
MatRadioModule,
|
||||
TranslateModule,
|
||||
|
||||
FormlyMatFormFieldModule,
|
||||
FormlySelectModule,
|
||||
FormlyModule.forChild({
|
||||
types: [{
|
||||
name: 'radio',
|
||||
component: FormlyFieldRadio,
|
||||
wrappers: ['form-field'],
|
||||
}],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class FormlyMatRadioModule {
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import {Component, ViewChild} from '@angular/core';
|
||||
import {FieldType} from '../../form-field';
|
||||
import {MatRadioGroup} from '@angular/material/radio';
|
||||
|
||||
@Component({
|
||||
selector: 'formly-field-mat-radio',
|
||||
template: `
|
||||
<mat-radio-group
|
||||
[formControl]="formControl"
|
||||
[formlyAttributes]="field"
|
||||
[tabindex]="to.tabindex || 0">
|
||||
<mat-radio-button *ngFor="let option of to.options | formlySelectOptions:field | async; let i = index;"
|
||||
[id]="id + '_' + i"
|
||||
[color]="to.color"
|
||||
[labelPosition]="to.labelPosition"
|
||||
[value]="option.value">
|
||||
{{ option.label|translate }}
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
`,
|
||||
})
|
||||
export class FormlyFieldRadio extends FieldType {
|
||||
@ViewChild(MatRadioGroup, {static: true}) radioGroup!: MatRadioGroup;
|
||||
defaultOptions = {
|
||||
templateOptions: {
|
||||
hideFieldUnderline: true,
|
||||
floatLabel: 'always',
|
||||
options: [],
|
||||
},
|
||||
};
|
||||
|
||||
onContainerClick(event: MouseEvent): void {
|
||||
if (this.radioGroup._radios.length && !this.radioGroup.selected) {
|
||||
this.radioGroup._radios.first.focus();
|
||||
}
|
||||
super.onContainerClick(event);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './src/public_api';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ngPackage": {}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export {FormlyMatSelectModule} from './select.module';
|
||||
export {FormlyFieldSelect} from './select.type';
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {FormlySelectModule} from '@ngx-formly/core/select';
|
||||
|
||||
import {FormlyMatFormFieldModule} from '../../form-field';
|
||||
import {MatSelectModule} from '@angular/material/select';
|
||||
|
||||
import {FormlyFieldSelect} from './select.type';
|
||||
import {MatPseudoCheckboxModule} from '@angular/material/core';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FormlyFieldSelect],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
MatSelectModule,
|
||||
MatPseudoCheckboxModule,
|
||||
TranslateModule.forChild(),
|
||||
|
||||
FormlyMatFormFieldModule,
|
||||
FormlySelectModule,
|
||||
FormlyModule.forChild({
|
||||
types: [{
|
||||
name: 'select',
|
||||
component: FormlyFieldSelect,
|
||||
wrappers: ['form-field'],
|
||||
}],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class FormlyMatSelectModule {
|
||||
}
|
||||
|
|
@ -1,236 +0,0 @@
|
|||
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {MatSelectModule} from '@angular/material/select';
|
||||
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
|
||||
import {createGenericTestComponent} from '../../../../../../ngx-formly/src/core/src/lib/test-utils';
|
||||
import {By} from '@angular/platform-browser';
|
||||
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||
import {FormlyForm, FormlyModule} from '@ngx-formly/core';
|
||||
import {FormlySelectModule} from '@ngx-formly/core/select';
|
||||
import {FormlyFieldSelect} from './select.type';
|
||||
import {of as observableOf} from 'rxjs';
|
||||
import {MatPseudoCheckboxModule} from '@angular/material/core';
|
||||
import {timeout} from 'rxjs/operators';
|
||||
|
||||
const createTestComponent = (html: string) =>
|
||||
createGenericTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||
|
||||
let testComponentInputs;
|
||||
|
||||
describe('ui-material: Formly Field Select Component', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TestComponent, FormlyFieldSelect],
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
MatSelectModule,
|
||||
MatPseudoCheckboxModule,
|
||||
ReactiveFormsModule,
|
||||
FormlySelectModule,
|
||||
FormlyModule.forRoot({
|
||||
types: [
|
||||
{
|
||||
name: 'select',
|
||||
component: FormlyFieldSelect,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe('options', () => {
|
||||
beforeEach(() => {
|
||||
testComponentInputs = {
|
||||
form: new FormGroup({}),
|
||||
options: {},
|
||||
model: {},
|
||||
};
|
||||
});
|
||||
|
||||
it('should correctly bind to a static array of data', () => {
|
||||
testComponentInputs.fields = [{
|
||||
key: 'sportId',
|
||||
type: 'select',
|
||||
templateOptions: {
|
||||
options: [
|
||||
{id: '1', name: 'Soccer'},
|
||||
{id: '2', name: 'Basketball'},
|
||||
{id: {test: 'A'}, name: 'Not Soccer or Basketball'},
|
||||
],
|
||||
valueProp: 'id',
|
||||
labelProp: 'name',
|
||||
},
|
||||
}];
|
||||
|
||||
const fixture = createTestComponent('<formly-form [form]="form" [fields]="fields" [model]="model" [options]="options"></formly-form>'),
|
||||
trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
|
||||
|
||||
trigger.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.debugElement.queryAll(By.css('mat-option')).length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should correctly bind to an Observable', async(() => {
|
||||
const sports$ = observableOf([
|
||||
{id: '1', name: 'Soccer'},
|
||||
{id: '2', name: 'Basketball'},
|
||||
{id: {test: 'A'}, name: 'Not Soccer or Basketball'},
|
||||
]);
|
||||
|
||||
testComponentInputs.fields = [{
|
||||
key: 'sportId',
|
||||
type: 'select',
|
||||
templateOptions: {
|
||||
options: sports$,
|
||||
valueProp: 'id',
|
||||
labelProp: 'name',
|
||||
},
|
||||
}];
|
||||
|
||||
const fixture = createTestComponent('<formly-form [form]="form" [fields]="fields" [model]="model" [options]="options"></formly-form>'),
|
||||
trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
|
||||
|
||||
trigger.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.debugElement.queryAll(By.css('mat-option')).length).toEqual(3);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('multi select', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
testComponentInputs = {
|
||||
form: new FormGroup({}),
|
||||
options: {},
|
||||
model: {},
|
||||
};
|
||||
|
||||
testComponentInputs.fields = [{
|
||||
key: 'sportId',
|
||||
type: 'select',
|
||||
templateOptions: {
|
||||
multiple: true,
|
||||
selectAllOption: 'Select All',
|
||||
options: [
|
||||
{id: '1', name: 'Soccer'},
|
||||
{id: '2', name: 'Basketball'},
|
||||
{id: '3', name: 'Martial Arts'},
|
||||
],
|
||||
valueProp: 'id',
|
||||
labelProp: 'name',
|
||||
},
|
||||
}];
|
||||
});
|
||||
|
||||
it('should have a "Select All" option if configured', () => {
|
||||
const fixture = createTestComponent('<formly-form [form]="form" [fields]="fields" [model]="model" [options]="options"></formly-form>');
|
||||
const trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
|
||||
|
||||
trigger.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.debugElement.queryAll(By.css('mat-option')).length).toEqual(1 + 3);
|
||||
});
|
||||
|
||||
it('should select all options if clicking the "Select All" option', () => {
|
||||
const fixture = createTestComponent('<formly-form [form]="form" [fields]="fields" [model]="model" [options]="options"></formly-form>');
|
||||
const trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
|
||||
|
||||
trigger.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
const selectAllOption = fixture.debugElement.queryAll(By.css('mat-option'))[0].nativeElement;
|
||||
selectAllOption.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(testComponentInputs.form.get('sportId').value.length).toEqual(3);
|
||||
|
||||
// clicking again should deselect all
|
||||
selectAllOption.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(testComponentInputs.form.get('sportId').value.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should use the selectAllOption prop as label for the option entry', () => {
|
||||
testComponentInputs.fields = [{
|
||||
key: 'sportId',
|
||||
type: 'select',
|
||||
templateOptions: {
|
||||
multiple: true,
|
||||
selectAllOption: 'Click me!!',
|
||||
options: [
|
||||
{id: '1', name: 'Soccer'},
|
||||
{id: '2', name: 'Basketball'},
|
||||
{id: '3', name: 'Martial Arts'},
|
||||
],
|
||||
valueProp: 'id',
|
||||
labelProp: 'name',
|
||||
},
|
||||
}];
|
||||
|
||||
const fixture = createTestComponent('<formly-form [form]="form" [fields]="fields" [model]="model" [options]="options"></formly-form>');
|
||||
const trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
|
||||
|
||||
trigger.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
const selectAllOption = fixture.debugElement.queryAll(By.css('mat-option'))[0].nativeElement;
|
||||
expect(selectAllOption.innerHTML).toContain('Click me!!');
|
||||
});
|
||||
|
||||
it('should correctly bind a multi select to an observable', fakeAsync(() => {
|
||||
// bind a value which triggers the error in case
|
||||
testComponentInputs.model = {
|
||||
sportId: [1],
|
||||
};
|
||||
testComponentInputs.fields = [{
|
||||
key: 'sportId',
|
||||
type: 'select',
|
||||
templateOptions: {
|
||||
multiple: true,
|
||||
selectAllOption: 'Click me!!',
|
||||
options: observableOf([
|
||||
{id: '1', name: 'Soccer'},
|
||||
{id: '2', name: 'Basketball'},
|
||||
{id: '3', name: 'Martial Arts'},
|
||||
]).pipe(
|
||||
timeout(50),
|
||||
),
|
||||
valueProp: 'id',
|
||||
labelProp: 'name',
|
||||
},
|
||||
}];
|
||||
|
||||
const fixture = createTestComponent('<formly-form [form]="form" [fields]="fields" [model]="model" [options]="options"></formly-form>');
|
||||
|
||||
tick(51);
|
||||
fixture.detectChanges();
|
||||
|
||||
const trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
|
||||
|
||||
trigger.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
const selectAllOption = fixture.debugElement.queryAll(By.css('mat-option'))[0].nativeElement;
|
||||
expect(selectAllOption.innerHTML).toContain('Click me!!');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@Component({selector: 'formly-form-test', template: '', entryComponents: []})
|
||||
class TestComponent {
|
||||
@ViewChild(FormlyForm) formlyForm: FormlyForm;
|
||||
|
||||
fields = testComponentInputs.fields;
|
||||
form: FormGroup = testComponentInputs.form;
|
||||
model = testComponentInputs.model || {};
|
||||
options = testComponentInputs.options;
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {MatSelectChange} from '@angular/material/select';
|
||||
import {FieldType} from '../../form-field';
|
||||
|
||||
@Component({
|
||||
selector: 'formly-field-mat-select',
|
||||
template: `
|
||||
<ng-template #selectAll
|
||||
let-selectOptions="selectOptions">
|
||||
<mat-option (click)="toggleSelectAll(selectOptions)">
|
||||
<mat-pseudo-checkbox class="mat-option-pseudo-checkbox"
|
||||
[state]="getState(selectOptions)">
|
||||
</mat-pseudo-checkbox>
|
||||
{{ to.selectAllOption }}
|
||||
</mat-option>
|
||||
</ng-template>
|
||||
|
||||
|
||||
<mat-select [id]="id"
|
||||
[formControl]="formControl"
|
||||
[formlyAttributes]="field"
|
||||
[placeholder]="to.placeholder"
|
||||
[tabindex]="to.tabindex || 0"
|
||||
[compareWith]="to.compareWith || compareWith"
|
||||
[multiple]="to.multiple"
|
||||
(selectionChange)="change($event)"
|
||||
[errorStateMatcher]="errorStateMatcher"
|
||||
[aria-labelledby]="formField?._labelId"
|
||||
[disableOptionCentering]="to.disableOptionCentering"
|
||||
>
|
||||
<ng-container *ngIf="to.options | formlySelectOptions:field | async as selectOptions">
|
||||
<ng-container *ngIf="to.multiple && to.selectAllOption"
|
||||
[ngTemplateOutlet]="selectAll"
|
||||
[ngTemplateOutletContext]="{ selectOptions: selectOptions }">
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let item of selectOptions">
|
||||
<mat-optgroup *ngIf="item.group"
|
||||
[label]="item.label|translate">
|
||||
<mat-option *ngFor="let child of item.group"
|
||||
[value]="child.value"
|
||||
[disabled]="child.disabled">
|
||||
{{ child.label|translate }}
|
||||
</mat-option>
|
||||
</mat-optgroup>
|
||||
<mat-option *ngIf="!item.group"
|
||||
[value]="item.value"
|
||||
[disabled]="item.disabled">{{ item.label|translate }}</mat-option>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
`,
|
||||
})
|
||||
export class FormlyFieldSelect extends FieldType {
|
||||
defaultOptions = {
|
||||
templateOptions: {options: []},
|
||||
};
|
||||
|
||||
get value() {
|
||||
return this.formControl.value || [];
|
||||
}
|
||||
|
||||
getState(options: any[]) {
|
||||
if (this.value.length > 0) {
|
||||
return this.value.length !== options.length
|
||||
? 'indeterminate'
|
||||
: 'checked';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
toggleSelectAll(options: any[]) {
|
||||
this.formControl.setValue(
|
||||
this.value.length !== options.length
|
||||
? options.map(x => x.value)
|
||||
: [],
|
||||
);
|
||||
}
|
||||
|
||||
change($event: MatSelectChange) {
|
||||
if (this.to.change) {
|
||||
this.to.change(this.field, $event);
|
||||
}
|
||||
}
|
||||
|
||||
compareWith(o1: any, o2: any) {
|
||||
return o1 === o2;
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './src/public_api';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ngPackage": {}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export {FormlyMatSliderModule} from './slider.module';
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {MatSliderModule} from '@angular/material/slider';
|
||||
import {FormlyMatFormFieldModule} from '../../form-field';
|
||||
|
||||
import {FormlySliderTypeComponent} from './slider.type';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FormlySliderTypeComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
MatSliderModule,
|
||||
TranslateModule,
|
||||
FormlyMatFormFieldModule,
|
||||
FormlyModule.forChild({
|
||||
types: [{
|
||||
name: 'slider',
|
||||
component: FormlySliderTypeComponent,
|
||||
wrappers: ['form-field'],
|
||||
}],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class FormlyMatSliderModule {
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import {Component, ViewChild} from '@angular/core';
|
||||
import {FieldType} from '../../form-field';
|
||||
import {MatSlider} from '@angular/material/slider';
|
||||
|
||||
@Component({
|
||||
selector: 'formly-field-mat-slider',
|
||||
template: `
|
||||
<mat-slider
|
||||
[id]="id"
|
||||
[style.width]="'100%'"
|
||||
[formControl]="formControl"
|
||||
[formlyAttributes]="field"
|
||||
[tabindex]="to.tabindex || 0"
|
||||
[color]="to.color">
|
||||
</mat-slider>
|
||||
`,
|
||||
})
|
||||
export class FormlySliderTypeComponent extends FieldType {
|
||||
@ViewChild(MatSlider, {static: true}) slider!: MatSlider;
|
||||
defaultOptions = {
|
||||
templateOptions: {
|
||||
hideFieldUnderline: true,
|
||||
floatLabel: 'always',
|
||||
},
|
||||
};
|
||||
|
||||
onContainerClick(event: MouseEvent): void {
|
||||
this.slider.focus();
|
||||
super.onContainerClick(event);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
|
||||
import {FormlyMatFormFieldModule} from '../../form-field';
|
||||
import {FormlyMatInputModule} from '../../input';
|
||||
import {FormlyMatTextAreaModule} from '../../textarea';
|
||||
import {FormlyMatRadioModule} from '../../radio';
|
||||
import {FormlyMatCheckboxModule} from '../../checkbox';
|
||||
import {FormlyMatMultiCheckboxModule} from '../../multicheckbox';
|
||||
import {FormlyMatSelectModule} from '../../select';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {FormlyMatToggleModule} from '../../toggle';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
FormlyMatFormFieldModule,
|
||||
FormlyMatInputModule,
|
||||
FormlyMatTextAreaModule,
|
||||
FormlyMatRadioModule,
|
||||
FormlyMatCheckboxModule,
|
||||
FormlyMatMultiCheckboxModule,
|
||||
FormlyMatSelectModule,
|
||||
FormlyMatToggleModule,
|
||||
TranslateModule,
|
||||
],
|
||||
})
|
||||
export class FormlyMaterialModule {
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export {FieldType} from './../../form-field';
|
||||
|
||||
export {FormlyMaterialModule} from './ui-material.module';
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
/*
|
||||
* Public API Surface of material
|
||||
*/
|
||||
|
||||
export * from './lib/ui-material';
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'core-js/es7/reflect';
|
||||
import 'zone.js/dist/zone';
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import {getTestBed} from '@angular/core/testing';
|
||||
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting,} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: any;
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting(),
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('../', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './src/public_api';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ngPackage": {}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export {FormlyMatTextAreaModule} from './textarea.module';
|
||||
export {FormlyFieldTextArea} from './textarea.type';
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
import {FormlyMatFormFieldModule} from '../../form-field';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
|
||||
import {FormlyFieldTextArea} from './textarea.type';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FormlyFieldTextArea],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
MatInputModule,
|
||||
TranslateModule,
|
||||
|
||||
FormlyMatFormFieldModule,
|
||||
FormlyModule.forChild({
|
||||
types: [
|
||||
{
|
||||
name: 'textarea',
|
||||
component: FormlyFieldTextArea,
|
||||
wrappers: ['form-field'],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class FormlyMatTextAreaModule {
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||
import {MatInput} from '@angular/material/input';
|
||||
import {FieldType} from '../../form-field';
|
||||
|
||||
@Component({
|
||||
selector: 'formly-field-mat-textarea',
|
||||
template: `
|
||||
<textarea matInput
|
||||
[id]="id"
|
||||
[readonly]="to.readonly"
|
||||
[formControl]="formControl"
|
||||
[errorStateMatcher]="errorStateMatcher"
|
||||
[cols]="to.cols"
|
||||
[rows]="to.rows"
|
||||
[formlyAttributes]="field"
|
||||
[placeholder]="to.placeholder|translate"
|
||||
[tabindex]="to.tabindex || 0"
|
||||
[readonly]="to.readonly">
|
||||
</textarea>
|
||||
`,
|
||||
})
|
||||
export class FormlyFieldTextArea extends FieldType implements OnInit {
|
||||
@ViewChild(MatInput, {static: true}) formFieldControl!: MatInput;
|
||||
defaultOptions = {
|
||||
templateOptions: {
|
||||
cols: 1,
|
||||
rows: 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './src/public_api';
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"ngPackage": {}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export {FormlyMatToggleModule} from './toggle.module';
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
|
||||
import {FormlyMatFormFieldModule} from '../../form-field';
|
||||
|
||||
import {FormlyToggleTypeComponent} from './toggle.type';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FormlyToggleTypeComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslateModule,
|
||||
ReactiveFormsModule,
|
||||
MatSlideToggleModule,
|
||||
FormlyMatFormFieldModule,
|
||||
FormlyModule.forChild({
|
||||
types: [{
|
||||
name: 'toggle',
|
||||
component: FormlyToggleTypeComponent,
|
||||
wrappers: ['form-field'],
|
||||
}],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class FormlyMatToggleModule {
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import {Component, ViewChild} from '@angular/core';
|
||||
import {FieldType} from '../../form-field';
|
||||
import {MatSlideToggle} from '@angular/material/slide-toggle';
|
||||
|
||||
@Component({
|
||||
selector: 'formly-field-mat-toggle',
|
||||
template: `
|
||||
<mat-slide-toggle
|
||||
[id]="id"
|
||||
[formControl]="formControl"
|
||||
[formlyAttributes]="field"
|
||||
[tabindex]="to.tabindex || 0">
|
||||
{{ to.label|translate }}
|
||||
</mat-slide-toggle>
|
||||
`,
|
||||
})
|
||||
export class FormlyToggleTypeComponent extends FieldType {
|
||||
@ViewChild(MatSlideToggle, {static: true}) slideToggle!: MatSlideToggle;
|
||||
defaultOptions = {
|
||||
templateOptions: {
|
||||
hideFieldUnderline: true,
|
||||
floatLabel: 'always',
|
||||
hideLabel: true,
|
||||
},
|
||||
};
|
||||
|
||||
onContainerClick(event: MouseEvent): void {
|
||||
this.slideToggle.focus();
|
||||
super.onContainerClick(event);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import {FormlyFieldConfig} from '@ngx-formly/core';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {of} from 'rxjs';
|
||||
|
||||
export class TranslateExtension {
|
||||
constructor(
|
||||
private translate: TranslateService,
|
||||
) {
|
||||
}
|
||||
|
||||
prePopulate(field: FormlyFieldConfig) {
|
||||
const to = field.templateOptions || {};
|
||||
|
||||
field.expressionProperties = {
|
||||
...(field.expressionProperties || {}),
|
||||
...(to.label
|
||||
? {'templateOptions.label': this.translate.stream(to.label)}
|
||||
: {}),
|
||||
...(to.placeholder
|
||||
? {'templateOptions.placeholder': this.translate.stream(to.placeholder)}
|
||||
: {}),
|
||||
...(to.options && Array.isArray(to.options)
|
||||
? {
|
||||
// TODO better solution working with live changes
|
||||
'templateOptions.options': of(to.options.map((opt) => {
|
||||
return {
|
||||
...opt,
|
||||
label: this.translate.instant(opt.label),
|
||||
};
|
||||
})),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function registerTranslateExtension(translate: TranslateService) {
|
||||
return {
|
||||
extensions: [{
|
||||
name: 'translate',
|
||||
extension: new TranslateExtension(translate)
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {ErrorHandler, NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {InputDurationDirective} from './duration/input-duration.directive';
|
||||
import {DurationFromStringPipe} from './duration/duration-from-string.pipe';
|
||||
|
|
@ -31,7 +31,7 @@ import {MatToolbarModule} from '@angular/material/toolbar';
|
|||
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||
import {MarkdownModule, MarkdownService} from 'ngx-markdown';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {FormlyModule} from '@ngx-formly/core';
|
||||
import {FORMLY_CONFIG, FormlyModule} from '@ngx-formly/core';
|
||||
import {ThemeSelectComponent} from './theme-select/theme-select.component';
|
||||
import {MsToStringPipe} from './duration/ms-to-string.pipe';
|
||||
import {StringToMsPipe} from './duration/string-to-ms.pipe';
|
||||
|
|
@ -60,8 +60,12 @@ import {ChipListInputComponent} from './chip-list-input/chip-list-input.componen
|
|||
import {ValidationModule} from './validation/validation.module';
|
||||
import {OwlDateTimeModule, OwlNativeDateTimeModule} from 'ng-pick-datetime';
|
||||
import {FullPageSpinnerComponent} from './full-page-spinner/full-page-spinner.component';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {FormlyMaterialModule} from './formly-material-components/src/lib/ui-material';
|
||||
import {TranslateModule, TranslateService} from '@ngx-translate/core';
|
||||
import {FormlyMaterialModule} from '@ngx-formly/material';
|
||||
import {GlobalErrorHandler} from '../core/error-handler/global-error-handler.class';
|
||||
import {HAMMER_GESTURE_CONFIG} from '@angular/platform-browser';
|
||||
import {MyHammerConfig} from '../../hammer-config.class';
|
||||
import {registerTranslateExtension} from './formly-translate-extension/formly-translate-extension';
|
||||
|
||||
|
||||
@NgModule({
|
||||
|
|
@ -222,7 +226,17 @@ import {FormlyMaterialModule} from './formly-material-components/src/lib/ui-mate
|
|||
MarkdownModule,
|
||||
ValidationModule,
|
||||
TranslateModule,
|
||||
]
|
||||
],
|
||||
providers: [
|
||||
{provide: ErrorHandler, useClass: GlobalErrorHandler},
|
||||
{provide: HAMMER_GESTURE_CONFIG, useClass: MyHammerConfig},
|
||||
{
|
||||
provide: FORMLY_CONFIG,
|
||||
multi: true,
|
||||
useFactory: registerTranslateExtension,
|
||||
deps: [TranslateService],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class UiModule {
|
||||
constructor(private _markdownService: MarkdownService) {
|
||||
|
|
|
|||
|
|
@ -458,6 +458,13 @@
|
|||
dependencies:
|
||||
tslib "^1.7.1"
|
||||
|
||||
"@ngx-formly/material@^5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@ngx-formly/material/-/material-5.2.0.tgz#dfbe8109f1af0f63221169bffabcf4e6bce90f18"
|
||||
integrity sha512-RQajdWJz5fGmyRWzdcr3z3rGJF3X1xgqY9305Bw/vJyckTRfuT7yD4bZNna01HkeADafCVipnC2EogtdETNkoQ==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@ngx-translate/core@^11.0.1":
|
||||
version "11.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@ngx-translate/core/-/core-11.0.1.tgz#cecefad41f06368f5859dac48fec8fcc4485615f"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue