diff --git a/package-lock.json b/package-lock.json index 8e167e0d3..bf48a3b4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,9 +31,9 @@ "@ngrx/effects": "^13.0.2", "@ngrx/entity": "^13.0.2", "@ngrx/store": "^13.0.2", - "@ngx-formly/core": "6.0.0-next.8", - "@ngx-formly/material": "6.0.0-next.8", - "@ngx-formly/schematics": "6.0.0-next.8", + "@ngx-formly/core": "6.0.0-next.9", + "@ngx-formly/material": "6.0.0-next.9", + "@ngx-formly/schematics": "6.0.0-next.9", "@uirouter/angular": "^9.1.0", "@uirouter/core": "^6.0.8", "@uirouter/rx": "^1.0.0", @@ -6074,33 +6074,33 @@ } }, "node_modules/@ngx-formly/core": { - "version": "6.0.0-next.8", - "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-6.0.0-next.8.tgz", - "integrity": "sha512-GBFvWxIYhIgD1zQb9OThQxLPFjwcRge5BImM93yPDkO4cyDziXLdIfUWXlKJndbt5CQzEJzLmtkrD5MlnyPvgg==", + "version": "6.0.0-next.9", + "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-6.0.0-next.9.tgz", + "integrity": "sha512-hCZq12p8IECuvmDQNl1mVsFeaaY8C2FJxgY16mDRi9bVISD0K/Uhx1WFCZJssSwINDXRJPcY3KG8rkio7AzmQA==", "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/forms": ">=13.0.0", + "@angular/forms": ">=13.2.0", "rxjs": "^6.5.3 || ^7.0.0" } }, "node_modules/@ngx-formly/material": { - "version": "6.0.0-next.8", - "resolved": "https://registry.npmjs.org/@ngx-formly/material/-/material-6.0.0-next.8.tgz", - "integrity": "sha512-HRoU9CoyJLkcBPe2ZI3/JEkxHrUKI/D14FkbVBo+flI7ZmL78pQ7sY8s3aGhXgebztqeWIp0LIkxuJeLijJdnw==", + "version": "6.0.0-next.9", + "resolved": "https://registry.npmjs.org/@ngx-formly/material/-/material-6.0.0-next.9.tgz", + "integrity": "sha512-J3to6V6bKW3HAbfZFW++IWy1xCELihEIwJWI4smvUdYIss6S2gXEO9BY6V6f3Chffwc0xq/YSuuCLvIMs8Ua0g==", "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@angular/material": ">=13.0.0", - "@ngx-formly/core": "6.0.0-next.8" + "@ngx-formly/core": "6.0.0-next.9" } }, "node_modules/@ngx-formly/schematics": { - "version": "6.0.0-next.8", - "resolved": "https://registry.npmjs.org/@ngx-formly/schematics/-/schematics-6.0.0-next.8.tgz", - "integrity": "sha512-QfsrwSFOxn9a6VdXoewfCtwvqmb+/L6uhTXF/0Hq1/g8/1S0ebbj0jFJZj/sOA4VkFopCQrfO9XqLmv+DRESMg==", + "version": "6.0.0-next.9", + "resolved": "https://registry.npmjs.org/@ngx-formly/schematics/-/schematics-6.0.0-next.9.tgz", + "integrity": "sha512-gsgLBE20XnSjMowJGx2bYJYtSkZreFVSgZ8YQQbcQjoWQWvCq2ayXT/q1knS1Rk7zZNsM/xZW87Ty9hi4mqbdQ==", "dependencies": { "@angular-devkit/core": "^13.0.3", "@angular-devkit/schematics": "^13.0.3", @@ -11317,7 +11317,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "devOptional": true, + "dev": true, "engines": { "node": ">=10" } @@ -30086,7 +30086,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "devOptional": true, + "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -35626,7 +35626,7 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", - "devOptional": true, + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -35713,7 +35713,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "devOptional": true, + "dev": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -35726,7 +35726,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "devOptional": true, + "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -41632,7 +41632,7 @@ "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "devOptional": true, + "dev": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -47722,25 +47722,25 @@ "requires": {} }, "@ngx-formly/core": { - "version": "6.0.0-next.8", - "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-6.0.0-next.8.tgz", - "integrity": "sha512-GBFvWxIYhIgD1zQb9OThQxLPFjwcRge5BImM93yPDkO4cyDziXLdIfUWXlKJndbt5CQzEJzLmtkrD5MlnyPvgg==", + "version": "6.0.0-next.9", + "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-6.0.0-next.9.tgz", + "integrity": "sha512-hCZq12p8IECuvmDQNl1mVsFeaaY8C2FJxgY16mDRi9bVISD0K/Uhx1WFCZJssSwINDXRJPcY3KG8rkio7AzmQA==", "requires": { "tslib": "^2.0.0" } }, "@ngx-formly/material": { - "version": "6.0.0-next.8", - "resolved": "https://registry.npmjs.org/@ngx-formly/material/-/material-6.0.0-next.8.tgz", - "integrity": "sha512-HRoU9CoyJLkcBPe2ZI3/JEkxHrUKI/D14FkbVBo+flI7ZmL78pQ7sY8s3aGhXgebztqeWIp0LIkxuJeLijJdnw==", + "version": "6.0.0-next.9", + "resolved": "https://registry.npmjs.org/@ngx-formly/material/-/material-6.0.0-next.9.tgz", + "integrity": "sha512-J3to6V6bKW3HAbfZFW++IWy1xCELihEIwJWI4smvUdYIss6S2gXEO9BY6V6f3Chffwc0xq/YSuuCLvIMs8Ua0g==", "requires": { "tslib": "^2.0.0" } }, "@ngx-formly/schematics": { - "version": "6.0.0-next.8", - "resolved": "https://registry.npmjs.org/@ngx-formly/schematics/-/schematics-6.0.0-next.8.tgz", - "integrity": "sha512-QfsrwSFOxn9a6VdXoewfCtwvqmb+/L6uhTXF/0Hq1/g8/1S0ebbj0jFJZj/sOA4VkFopCQrfO9XqLmv+DRESMg==", + "version": "6.0.0-next.9", + "resolved": "https://registry.npmjs.org/@ngx-formly/schematics/-/schematics-6.0.0-next.9.tgz", + "integrity": "sha512-gsgLBE20XnSjMowJGx2bYJYtSkZreFVSgZ8YQQbcQjoWQWvCq2ayXT/q1knS1Rk7zZNsM/xZW87Ty9hi4mqbdQ==", "requires": { "@angular-devkit/core": "^13.0.3", "@angular-devkit/schematics": "^13.0.3", @@ -51781,7 +51781,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "devOptional": true + "dev": true }, "chrome-trace-event": { "version": "1.0.3", @@ -66605,7 +66605,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "devOptional": true, + "dev": true, "requires": { "minipass": "^3.0.0" } @@ -70769,7 +70769,7 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", - "devOptional": true, + "dev": true, "requires": { "yallist": "^4.0.0" } @@ -70836,7 +70836,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "devOptional": true, + "dev": true, "requires": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -70846,7 +70846,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "devOptional": true + "dev": true }, "mockdate": { "version": "3.0.5", @@ -75322,7 +75322,7 @@ "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "devOptional": true, + "dev": true, "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", diff --git a/package.json b/package.json index 4e23c13dc..67870f2cf 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,9 @@ "@ngrx/effects": "^13.0.2", "@ngrx/entity": "^13.0.2", "@ngrx/store": "^13.0.2", - "@ngx-formly/core": "6.0.0-next.8", - "@ngx-formly/material": "6.0.0-next.8", - "@ngx-formly/schematics": "6.0.0-next.8", + "@ngx-formly/core": "6.0.0-next.9", + "@ngx-formly/material": "6.0.0-next.9", + "@ngx-formly/schematics": "6.0.0-next.9", "@uirouter/angular": "^9.1.0", "@uirouter/core": "^6.0.8", "@uirouter/rx": "^1.0.0", diff --git a/packages/dbx-form/src/lib/formly/field/selection/pickable/pickable.field.directive.ts b/packages/dbx-form/src/lib/formly/field/selection/pickable/pickable.field.directive.ts index a9a35d626..3fdcd57ce 100644 --- a/packages/dbx-form/src/lib/formly/field/selection/pickable/pickable.field.directive.ts +++ b/packages/dbx-form/src/lib/formly/field/selection/pickable/pickable.field.directive.ts @@ -404,8 +404,7 @@ export class AbstractDbxPickableItemFieldDirective extends FieldType = {}): FormlyFieldConfig { return textField({ key, diff --git a/packages/dbx-form/src/lib/formly/field/wrapper/form.wrapper.module.ts b/packages/dbx-form/src/lib/formly/field/wrapper/form.wrapper.module.ts index 11fa9b2e0..2fafbe8f4 100644 --- a/packages/dbx-form/src/lib/formly/field/wrapper/form.wrapper.module.ts +++ b/packages/dbx-form/src/lib/formly/field/wrapper/form.wrapper.module.ts @@ -12,13 +12,15 @@ import { DbxFormExpandWrapperComponent } from './expandable.wrapper.component'; import { AutoTouchFieldWrapperComponent } from './autotouch.wrapper.component'; import { DbxFormToggleWrapperComponent } from './toggle.wrapper.component'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; -import { DbxSectionLayoutModule, DbxTextModule, DbxFlexLayoutModule } from '@dereekb/dbx-web'; +import { DbxSectionLayoutModule, DbxTextModule, DbxFlexLayoutModule, DbxLoadingModule } from '@dereekb/dbx-web'; import { DbxFormStyleWrapperComponent } from './style.wrapper.component'; +import { DbxFormWorkingWrapperComponent } from './working.wrapper.component'; @NgModule({ imports: [ CommonModule, DbxTextModule, + DbxLoadingModule, DbxFlexLayoutModule, DbxSectionLayoutModule, MatButtonModule, @@ -34,7 +36,8 @@ import { DbxFormStyleWrapperComponent } from './style.wrapper.component'; { name: 'subsection', component: DbxFormSubsectionWrapperComponent }, { name: 'info', component: DbxFormInfoWrapperComponent }, { name: 'flex', component: DbxFormFlexWrapperComponent }, - { name: 'style', component: DbxFormStyleWrapperComponent } + { name: 'style', component: DbxFormStyleWrapperComponent }, + { name: 'working', component: DbxFormWorkingWrapperComponent } ] }) ], @@ -46,7 +49,8 @@ import { DbxFormStyleWrapperComponent } from './style.wrapper.component'; DbxFormExpandWrapperComponent, DbxFormToggleWrapperComponent, DbxFormFlexWrapperComponent, - DbxFormStyleWrapperComponent + DbxFormStyleWrapperComponent, + DbxFormWorkingWrapperComponent ], exports: [] }) diff --git a/packages/dbx-form/src/lib/formly/field/wrapper/index.ts b/packages/dbx-form/src/lib/formly/field/wrapper/index.ts index 058419ad0..11e60831c 100644 --- a/packages/dbx-form/src/lib/formly/field/wrapper/index.ts +++ b/packages/dbx-form/src/lib/formly/field/wrapper/index.ts @@ -7,4 +7,5 @@ export * from './info.wrapper.component'; export * from './section.wrapper.component'; export * from './subsection.wrapper.component'; export * from './toggle.wrapper.component'; +export * from './working.wrapper.component'; export * from './wrapper'; diff --git a/packages/dbx-form/src/lib/formly/field/wrapper/working.wrapper.component.ts b/packages/dbx-form/src/lib/formly/field/wrapper/working.wrapper.component.ts new file mode 100644 index 000000000..ad435cc44 --- /dev/null +++ b/packages/dbx-form/src/lib/formly/field/wrapper/working.wrapper.component.ts @@ -0,0 +1,41 @@ +import { OnInit, OnDestroy } from '@angular/core'; +import { SimpleLoadingContext, SubscriptionObject } from '@dereekb/rxjs'; +import { Component } from '@angular/core'; +import { FieldWrapper, FormlyFieldConfig } from '@ngx-formly/core'; + +export interface DbxFormWorkingWrapperConfig { } + +export interface DbxFormWorkingWrapperFormlyConfig extends FormlyFieldConfig { + styleWrapper: DbxFormWorkingWrapperConfig; +} + +/** + * Adds a loading bar to help signify asynchronos work is occuring. + * + * By default shows loading during asynchronous validation of a field (FormControl status is "PENDING") + */ +@Component({ + template: ` +
+ + +
+ ` +}) +export class DbxFormWorkingWrapperComponent extends FieldWrapper implements OnInit, OnDestroy { + + readonly sub = new SubscriptionObject(); + readonly workingContext = new SimpleLoadingContext(false); + + ngOnInit(): void { + this.sub.subscription = this.formControl?.statusChanges.subscribe({ + next: (x) => this.workingContext.setLoading(x === 'PENDING') + }); + } + + ngOnDestroy(): void { + this.workingContext.destroy(); + this.sub.destroy(); + } + +} diff --git a/packages/dbx-form/src/lib/formly/field/wrapper/wrapper.ts b/packages/dbx-form/src/lib/formly/field/wrapper/wrapper.ts index 3612cb685..f454def39 100644 --- a/packages/dbx-form/src/lib/formly/field/wrapper/wrapper.ts +++ b/packages/dbx-form/src/lib/formly/field/wrapper/wrapper.ts @@ -7,6 +7,7 @@ import { DbxFormSubsectionConfig } from './subsection.wrapper.component'; import { DbxFormInfoConfig } from './info.wrapper.component'; import { DbxFormExpandWrapperConfig } from './expandable.wrapper.component'; import { DbxFlexSize } from '@dereekb/dbx-web'; +import { DbxFormWorkingWrapperConfig } from './working.wrapper.component'; export const EXPANDABLE_WRAPPER_KEY = 'expandable'; export const TOGGLE_WRAPPER_KEY = 'toggle'; @@ -15,6 +16,7 @@ export const SUBSECTION_WRAPPER_KEY = 'subsection'; export const INFO_WRAPPER_KEY = 'info'; export const FLEX_WRAPPER_KEY = 'flex'; export const STYLE_WRAPPER_KEY = 'style'; +export const WORKING_WRAPPER_KEY = 'working'; export function addWrapperToFormlyFieldConfig(fieldConfig: FormlyFieldConfig, wrapperKey: string, wrapperTemplateOptionsConfig: T): FormlyFieldConfig { fieldConfig.templateOptions = { @@ -65,6 +67,12 @@ export function styleWrapper(fieldConfig: FormlyFieldConfig, styleWrapper: DbxFo }); } +export function workingWrapper(fieldConfig: FormlyFieldConfig, workingWrapper: DbxFormWorkingWrapperConfig): FormlyFieldConfig { + return addWrapperToFormlyFieldConfig(fieldConfig, WORKING_WRAPPER_KEY, { + workingWrapper + }); +} + export interface DbxFlexLayoutWrapperGroupFieldConfig { field: FormlyFieldConfig; /** diff --git a/packages/dbx-form/src/lib/formly/template/available.ts b/packages/dbx-form/src/lib/formly/template/available.ts index 72b221c0d..496b97851 100644 --- a/packages/dbx-form/src/lib/formly/template/available.ts +++ b/packages/dbx-form/src/lib/formly/template/available.ts @@ -1,6 +1,7 @@ import { FormlyFieldConfig } from '@ngx-formly/core/lib/core'; import { FieldValueIsAvailableValidatorConfig, fieldValueIsAvailableValidator } from '../../validator/available'; import { textField, TextFieldConfig } from '../field/value/text/text.field'; +import { workingWrapper } from '../field/wrapper/wrapper'; export interface TextAvailableFieldConfig extends TextFieldConfig, Omit, 'message'> { isNotAvailableErrorMessage?: string; @@ -18,5 +19,5 @@ export function textIsAvailableField(config: TextAvailableFieldConfig): FormlyFi }] }; - return field; + return workingWrapper(field, {}); } diff --git a/packages/dbx-form/src/lib/validator/available.ts b/packages/dbx-form/src/lib/validator/available.ts index ddbda0af2..862b8c8af 100644 --- a/packages/dbx-form/src/lib/validator/available.ts +++ b/packages/dbx-form/src/lib/validator/available.ts @@ -1,9 +1,10 @@ -import { switchMap } from 'rxjs/operators'; -import { firstValueFrom, map, Observable } from 'rxjs'; +import { finalize, first, switchMap } from 'rxjs/operators'; +import { map, Observable, of, catchError } from 'rxjs'; import { AbstractControl, AsyncValidatorFn } from "@angular/forms"; import { asyncPusherCache, tapLog } from '@dereekb/rxjs'; export const FIELD_VALUE_IS_AVAILABLE_VALIDATION_KEY = 'fieldValueIsAvailable'; +export const FIELD_VALUE_IS_AVAILABLE_ERROR_VALIDATION_KEY = 'fieldValueIsAvailableError'; export type FieldValueIsAvailableValidatorFunction = (value: T) => Observable; @@ -12,7 +13,7 @@ export interface FieldValueIsAvailableValidatorConfig { /** * How long to wait in between value changes. */ - debounceTime?: number; + throttle?: number; /** * Returns an observable that checks whether or not the value is currently available. @@ -38,13 +39,17 @@ export interface FieldValueIsAvailableValidatorConfig { */ export function fieldValueIsAvailableValidator(config: FieldValueIsAvailableValidatorConfig): AsyncValidatorFn { const { + throttle = 400, checkValueIsAvailable, message = 'This value is not available.' } = config; - const pusher = asyncPusherCache(); - return (control: AbstractControl) => firstValueFrom(pusher(control.valueChanges)(control.value).pipe( - switchMap(() => checkValueIsAvailable(control.value)), + const pusher = asyncPusherCache({ + throttle + }); + + return (control: AbstractControl) => pusher(control.valueChanges)(control.value as T).pipe( + switchMap((x) => checkValueIsAvailable(x)), map((isAvailable) => { if (isAvailable) { return null; @@ -53,5 +58,10 @@ export function fieldValueIsAvailableValidator(config: FieldValueIsAvailableV [FIELD_VALUE_IS_AVAILABLE_VALIDATION_KEY]: { message } }; } - }))); + }), + catchError(() => of({ + [FIELD_VALUE_IS_AVAILABLE_ERROR_VALIDATION_KEY]: { message: 'An error occured.' } + })), + first() + ); } diff --git a/packages/rxjs/src/lib/rxjs/rxjs.async.ts b/packages/rxjs/src/lib/rxjs/rxjs.async.ts index 4bc0f3209..b5b0cdaa8 100644 --- a/packages/rxjs/src/lib/rxjs/rxjs.async.ts +++ b/packages/rxjs/src/lib/rxjs/rxjs.async.ts @@ -55,14 +55,14 @@ export interface AsyncPusherConfig { * @returns */ export function asyncPusher(config: AsyncPusherConfig = {}): AsyncPusher { - const { throttle, cleanupObs, distinct = true, pipe: pipeObs } = config; + const { throttle = DEFAULT_ASYNC_PUSHER_THROTTLE, cleanupObs, distinct = true, pipe: pipeObs } = config; const _subject = new BehaviorSubject(undefined as any); const _sub = new SubscriptionObject(); let obs: Observable = _subject.pipe( skipFirstMaybe(), - throttleTime(throttle ?? DEFAULT_ASYNC_PUSHER_THROTTLE, undefined, { leading: true, trailing: true }) + throttleTime(throttle, undefined, { leading: false, trailing: true }) ) as Observable; if (distinct) {