@reactway/forms-core
Advanced tools
Comparing version 0.0.0-canary.585eb1d to 0.0.0-canary.6b99618
export declare const IdSeparator = "."; | ||
export declare const FormSelector: unique symbol; | ||
//# sourceMappingURL=constants.d.ts.map |
@@ -25,2 +25,1 @@ import { StoreUpdater } from "../store"; | ||
export {}; | ||
//# sourceMappingURL=field-helpers.d.ts.map |
import { Store } from "../store"; | ||
import { Dictionary, PartialKeys } from "./type-helpers"; | ||
import { Dictionary } from "./type-helpers"; | ||
import { ValidationUpdater, ValueUpdater, StatusUpdater } from "./state-updaters"; | ||
@@ -7,5 +7,7 @@ import { ValidationResult, Validator, CancellationToken } from "./validation"; | ||
import { Modifier } from "./modifiers"; | ||
export interface FieldState<TValue, TData extends {}> extends FieldValue<TValue, FieldState<TValue, TData>> { | ||
export interface FieldStateIdentifiers { | ||
id: string; | ||
name: string; | ||
} | ||
export interface FieldState<TValue, TData extends {}> extends FieldStateIdentifiers, FieldValue<TValue, FieldState<TValue, TData>> { | ||
status: FieldStatus; | ||
@@ -72,3 +74,3 @@ computedValue: boolean; | ||
export declare type DefaultFieldState = Pick<FieldState<any, any>, "fields" | "status" | "validation">; | ||
export declare type Initial<TFieldState extends FieldState<any, any>> = Omit<PartialKeys<TFieldState, keyof DefaultFieldState>, "id" | "name" | "fields">; | ||
export declare type Initial<TFieldState extends FieldState<any, any>> = Pick<TFieldState, "computedValue" | "data" | "getValue" | "setValue">; | ||
export declare type UpdaterId<TUpdater extends Updater> = TUpdater extends Updater<infer TId> ? TId : never; | ||
@@ -78,2 +80,1 @@ export declare type FieldStateValue<TFieldState extends FieldState<any, any>> = TFieldState extends FieldState<infer TValue, any> ? TValue : never; | ||
export declare type RenderValue<TData extends InputFieldData<any, any>> = TData extends InputFieldData<any, infer TRenderValue> ? TRenderValue : never; | ||
//# sourceMappingURL=field-state.d.ts.map |
@@ -10,2 +10,1 @@ import { NestedDictionary } from "./type-helpers"; | ||
} | ||
//# sourceMappingURL=form-state.d.ts.map |
@@ -10,2 +10,1 @@ export * from "./type-helpers"; | ||
export * from "./order-guards"; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -17,2 +17,1 @@ import { DeepReadonly } from "./type-helpers"; | ||
} | ||
//# sourceMappingURL=modifiers.d.ts.map |
export interface OrderGuard { | ||
reportIndex: (id: string) => void; | ||
} | ||
//# sourceMappingURL=order-guards.d.ts.map |
@@ -24,3 +24,4 @@ import { FieldStatus, Updater, TextSelection } from "./field-state"; | ||
setFormErrors(errors: NestedDictionary<ValidationResultOrString[]>): void; | ||
setFieldValidationResults(fieldSelector: FieldSelector, errors: ValidationResultOrString[]): void; | ||
resetFieldValidationResults(fieldSelector: FieldSelector): void; | ||
} | ||
//# sourceMappingURL=state-updaters.d.ts.map |
@@ -21,2 +21,1 @@ import { FieldState, Initial, Updater, FieldStatus, UpdatersFactories, UpdaterId } from "./field-state"; | ||
} | ||
//# sourceMappingURL=store-helpers.d.ts.map |
@@ -19,2 +19,2 @@ export interface Dictionary<TValue> { | ||
}; | ||
//# sourceMappingURL=type-helpers.d.ts.map | ||
export declare type OmitStrict<T, TKeys extends keyof T> = Omit<T, TKeys>; |
@@ -0,1 +1,2 @@ | ||
import { Dictionary } from "./type-helpers"; | ||
export interface Validator<TValue> { | ||
@@ -8,6 +9,6 @@ name: string; | ||
export interface ValidatorHelpers { | ||
error: (message: string, code?: string) => ValidationResult & { | ||
error: (message: string, code?: string, data?: Dictionary<any>) => ValidationResult & { | ||
type: ValidationResultType.Error; | ||
}; | ||
warning: (message: string, code?: string) => ValidationResult & { | ||
warning: (message: string, code?: string, data?: Dictionary<any>) => ValidationResult & { | ||
type: ValidationResultType.Warning; | ||
@@ -21,2 +22,3 @@ }; | ||
validatorName?: string; | ||
data?: Dictionary<any>; | ||
code?: string; | ||
@@ -39,2 +41,1 @@ } | ||
} | ||
//# sourceMappingURL=validation.d.ts.map |
@@ -11,2 +11,1 @@ import { CancellationToken } from "../contracts"; | ||
} | ||
//# sourceMappingURL=cancellation-token.d.ts.map |
@@ -7,2 +7,1 @@ import { FieldHelpers, ValidatorsFieldHelpers, ModifiersFieldHelpers, InputFieldHelpers, FieldSelector } from "../contracts"; | ||
export {}; | ||
//# sourceMappingURL=field-helpers.d.ts.map |
@@ -18,2 +18,1 @@ import { TinyEmitter } from "@reactway/tiny-emitter"; | ||
export {}; | ||
//# sourceMappingURL=form-stores-registry.d.ts.map |
@@ -14,2 +14,1 @@ import { FieldStatus, InputFieldData, UpdatersFactories, FieldValidation, FieldState, Updater, DefaultFieldState, FieldSelector, InputValues } from "../contracts"; | ||
export declare function isInputFieldData(candidate: {}): candidate is InputFieldData<any, any>; | ||
//# sourceMappingURL=generic.d.ts.map |
@@ -6,2 +6,1 @@ export * from "./generic"; | ||
export * from "./field-helpers"; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -9,2 +9,1 @@ import { Draft } from "immer"; | ||
export declare function selectFieldParent(state: FieldState<any, any>, fieldId: string): FieldState<any, any> | undefined; | ||
//# sourceMappingURL=store-helpers.d.ts.map |
@@ -7,2 +7,1 @@ export * from "./contracts"; | ||
export * from "./constants"; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -1,2 +0,27 @@ | ||
import{TinyEmitter as t}from"@reactway/tiny-emitter";import e from"immer";import n from"shortid";const i="value",r="status",s="validation";var a,o;!function(t){t[t.Error=0]="Error",t[t.Warning=1]="Warning"}(a||(a={})),function(t){t[t.Unknown=0]="Unknown",t[t.Validation=1]="Validation",t[t.FormSubmit=2]="FormSubmit"}(o||(o={}));const l=".",d=Symbol("form"); | ||
import { TinyEmitter } from '@reactway/tiny-emitter'; | ||
import produce from 'immer'; | ||
import shortid from 'shortid'; | ||
import Debug from 'debug'; | ||
const ValueUpdater = "value"; | ||
const StatusUpdater = "status"; | ||
const ValidationUpdater = "validation"; | ||
// These comments originated in the v4 codebase and are kept for historical purpose: | ||
// https://github.com/SimplrJS/react-forms/blame/e8443591f215fbd3fa76898520c8490d5c6673b6/packages/simplr-forms/src/contracts/error.ts | ||
var ValidationResultType; | ||
(function (ValidationResultType) { | ||
ValidationResultType[ValidationResultType["Error"] = 0] = "Error"; | ||
ValidationResultType[ValidationResultType["Warning"] = 1] = "Warning"; | ||
})(ValidationResultType || (ValidationResultType = {})); | ||
var ValidationResultOrigin; | ||
(function (ValidationResultOrigin) { | ||
ValidationResultOrigin[ValidationResultOrigin["Unknown"] = 0] = "Unknown"; | ||
ValidationResultOrigin[ValidationResultOrigin["Validation"] = 1] = "Validation"; | ||
ValidationResultOrigin[ValidationResultOrigin["FormSubmit"] = 2] = "FormSubmit"; | ||
})(ValidationResultOrigin || (ValidationResultOrigin = {})); | ||
const IdSeparator = "."; | ||
const FormSelector = Symbol("form"); | ||
/*! ***************************************************************************** | ||
@@ -16,2 +41,895 @@ Copyright (c) Microsoft Corporation. All rights reserved. | ||
***************************************************************************** */ | ||
function u(t,e,n,i){return new(n||(n=Promise))((function(r,s){function a(t){try{l(i.next(t))}catch(t){s(t)}}function o(t){try{l(i.throw(t))}catch(t){s(t)}}function l(t){t.done?r(t.value):new n((function(e){e(t.value)})).then(a,o)}l((i=i.apply(t,e||[])).next())}))}class c{constructor(t,e){this.tokenName=e,this.cancelled=!1,this.cancellationCallback=t}get cancellationRequested(){return this.cancelled}cancel(){var t;this.cancelled||(this.cancelled=!0,null===(t=this.cancellationCallback)||void 0===t||t.call(this))}}class f extends t{constructor(){super(...arguments),this.storesRegistry={}}registerStore(t,e){if(null!=this.storesRegistry[t])throw new Error(`Form with formId "${t}" is already registered.`);this.storesRegistry[t]=e,this.emit()}unregisterStore(t){this.storesRegistry[t]=void 0,this.emit()}getStore(t){return this.storesRegistry[t]}getStoresIds(){return Object.keys(this.storesRegistry)}}const h=new class{setFormStoresHandler(t,e=!0){e&&null!=this.instance&&delete this.instance,this.instance=t}get registry(){var t;return this.instance=null!==(t=this.instance)&&void 0!==t?t:new f,this.instance}};function g(t,e){return{registerValidator:e=>n=>n.getUpdater("validation").registerValidator(t,e),unregisterValidator:e=>n=>{n.getUpdater("validation").unregisterValidator(t,e)},reportValidatorIndex:e.reportValidatorIndex}}function v(t,e){return Object.assign(Object.assign(Object.assign({},g(t,e)),e),{registerModifier:e=>n=>n.getUpdater("value").registerModifier(t,e),unregisterModifier:e=>n=>{n.getUpdater("value").unregisterModifier(t,e)}})}function p(t,e,i){return{id:"validation",validateField:e=>u(this,void 0,void 0,(function*(){return function(t,e,n,i){return u(this,void 0,void 0,(function*(){const e=t.selectField(i);if(E(e,i),null!=e.validation.currentValidation&&e.validation.currentValidation.cancellationToken.cancel(),0===Object.keys(e.validation.validators).length)return;const r=[];for(const t of e.validation.validatorsOrder){const n=e.validation.validators[t];null!=n&&r.push(Object.assign({},n))}const s=e.getValue(e),a=new Date,l=new c(()=>{n.update(t=>{const e=t.selectField(i);if(null==e)return;const n=e.validation;null!=n.currentValidation&&n.currentValidation.started.getTime()===a.getTime()&&(n.currentValidation=void 0)})});e.validation.currentValidation={started:a,cancellationToken:l},e.validation.results=[];for(const t of r){if(!t.shouldValidate(s))continue;if(l.cancellationRequested)return;const e=t.validate(s,m(o.Validation,t.name));if(null==e)continue;let r=void 0;if(r=O(e)?yield e:e,0===r.length)continue;const a=r.map(e=>y(e,t.name));F(i,n,l,t=>{t.validation.results.push(...a)})}F(i,n,l,t=>{t.validation.currentValidation=void 0})}))}(t,0,i,e)})),registerValidator:(e,i)=>{const r=t.selectField(e);E(r,e);const s=n.generate();return r.validation.validators[s]=Object.assign(Object.assign({},i),{id:s}),r.validation.validatorsOrder.push(s),s},unregisterValidator:(e,n)=>{const i=t.selectField(e);if(E(i,e),null==i.validation.validators[n])return;delete i.validation.validators[n];const r=i.validation.validatorsOrder.findIndex(t=>t===n);-1!==r&&i.validation.validatorsOrder.splice(r,1)},setFormErrors:t=>{!function t(e,n){for(const i of Object.keys(n)){const r=n[i],s=e.fields[i];if(null==s||null==r)continue;if("object"==typeof r&&!Array.isArray(r)){t(s,r);continue}const l=[];for(const t of r){let e;e="string"!=typeof t?t:{message:t,type:a.Error,origin:o.FormSubmit},l.push(e)}s.validation.results=l}}(e,t)}}}function m(t,e){return{error:(n,i)=>({type:a.Error,message:n,code:i,origin:t,validatorName:e}),warning:(n,i)=>({type:a.Warning,message:n,code:i,origin:t,validatorName:e})}}function F(t,e,n,i){n.cancellationRequested||setTimeout(()=>{n.cancellationRequested||e.update(e=>{const n=e.selectField(t);null!=n&&i(n)})},0)}function y(t,e){return"string"!=typeof t?t:{message:t,validatorName:e,type:a.Error,origin:o.Validation}}function V(t){const e={id:"value",updateFieldValue:(e,n,i)=>{var r,s;const a=t.selectField(e);if(E(a,e),!A(a.data))throw new Error("Not implemented.");const o=a.data.modifiers,l=Object.keys(o);if(0===l.length)return a.data.currentValue=n,a.data.selection=i,void t.updateFieldStatus(e,t=>{t.touched=!0,t.pristine=n===a.data.initialValue});const d={value:null!==(r=a.data.transientValue)&&void 0!==r?r:a.data.currentValue,caretPosition:null===(s=a.data.selection)||void 0===s?void 0:s.selectionStart};let u=n,c=void 0,f=null==i?void 0:i.selectionStart;for(const t of l){const e=o[t];if(null==e)throw new Error("Should never happen.");const n={value:u,caretPosition:f},i=e.parse(n,d);u=i.currentValue,null!=i.caretPosition&&(f=i.caretPosition),null==c&&null!=i.transientValue&&(c=i.transientValue)}let h=void 0;null!=f&&(h={selectionStart:f,selectionEnd:f,selectionDirection:"none"}),a.data.currentValue=u,a.data.transientValue=c,console.group("Setting new selection to:"),console.log(Object.assign({},h)),console.groupEnd(),a.data.selection=h},resetFieldValue:n=>{const i=t.selectField(n);if(E(i,n),!A(i.data))throw new Error("Only input field can be reset.");e.updateFieldValue(n,i.data.initialValue)},clearFieldValue:n=>{const i=t.selectField(n);if(E(i,n),!A(i.data))throw new Error("Only input field can be cleared.");e.updateFieldValue(n,i.data.defaultValue)},registerModifier:(e,i)=>{const r=t.selectField(e);if(E(r,e),!A(r.data))throw new Error("Not implemented.");const s=n.generate();return r.data.modifiers[s]=Object.assign(Object.assign({},i),{id:s}),s},unregisterModifier:(e,n)=>{const i=t.selectField(e);if(E(i,e),!A(i.data))throw new Error("Not implemented.");if(null==i.data.modifiers[n])return;const r=i.data.modifiersOrder.findIndex(t=>t===n);-1!==r&&i.data.modifiersOrder.splice(r,1)}};return e}function w(t,n){return{id:"status",updateFieldStatus:(i,r)=>{const s=t.selectField(i);E(s,i);const a=e(s.status,()=>{});let o;const l=e(a,t=>{r(t)},t=>{o=t});r(s.status);const d=t=>null!=o.find(e=>e.path.includes(t));l.touched&&d("touched")&&b(n,t,i,t=>{t.touched=!0}),d("pristine")&&b(n,t,i,t=>{t.pristine=l.pristine}),d("readonly")&&function t(e,n){for(const i of Object.keys(e.fields)){const r=e.fields[i];null!=r&&(n(r.status),t(r,n))}}(s,t=>{t.readonly=l.readonly})}}}function b(t,e,n,i){E(e.selectField(n),n);let r=n;for(;null!=(r=e.getFieldParentId(r));){const t=e.selectField(r);E(t,n),i(t.status)}i(t.status)}function O(t){return null!=t.then&&null!=t.catch}function I(t,e){return null==e?t:`${e}.${t}`}function j(t){const e=t.lastIndexOf(".");return-1===e?t:t.slice(e+".".length)}function E(t,e){if(null==t)throw new Error(`Field '${String(e)}' does not exist in a given state.`)}function S(t,e){if(null==t)throw new Error(`Updater '${e}' does not exist.`)}function k(){return{fields:{},status:{disabled:!1,pristine:!0,touched:!1,readonly:!1,permanent:!1},validation:{results:[],validators:{},validatorsOrder:[]}}}function x(){return{disabled:!1,pristine:!0,touched:!1,readonly:!1,permanent:!1}}function R(t,e,n,i){return void 0===e&&(e=t),void 0===n&&(n=e),{defaultValue:t,initialValue:e,currentValue:n,transientValue:i}}function $(t,e,n,i){return void 0===e&&(e=t),void 0===n&&(n=e),{defaultValue:t,initialValue:e,currentValue:n,transientValue:i,modifiers:{},modifiersOrder:[]}}function U(){return{validation:p,value:V,status:w}}function P(){return{results:[],validators:{},validatorsOrder:[]}}function A(t){const e=t;return void 0!==e.defaultValue&&void 0!==e.initialValue&&void 0!==e.currentValue&&void 0!==e.modifiers&&"object"==typeof e.modifiers&&Array.isArray(e.modifiersOrder)}function B(t,e){const n=n=>{if(n===d)return t;const i=e[n];if(null!=i)return i;const r=N(t,n);return null!=r&&(e[n]=r),r};return{selectField:n,selectFieldParent:t=>{const e=T(t);if(null!=e)return n(e)},getActiveFieldId:()=>t.data.activeFieldId,getFieldParentId:T,getFormValue:()=>t.getValue(t)}}function M(t,e,n,i){const r=B(e,i),s=Object.assign(Object.assign({},r),{registerField:(t,n)=>{!function(t,e,n){if(n.computedValue&&A(n.data))throw new Error(`Field ${e} is marked to have computedValue, but also has data of an input field.`);const i=j(e),r=function t(e,n){const i=n.indexOf(".");if(-1===i)return e;{const r=n.slice(0,i),s=n.slice(i+".".length),a=e.fields[r];if(null==a)return;return t(a,s)}}(t,e);if(null==r)throw new Error(`Parent for field '${e}' to be registered on was not found.`);const s=r.fields[i];if(null!=s&&!1===s.status.permanent)throw new Error(`Field '${e}' has already been registered.`);if(null==s){r.fields[i]=Object.assign(Object.assign(Object.assign({},{fields:{},status:{disabled:!1,pristine:!0,touched:!1,readonly:!1,permanent:!1},validation:{results:[],validators:{},validatorsOrder:[]}}),n),{id:e,name:i,fields:{}})}}(e,t,n)},unregisterField:t=>{!function(t,e){const n=q(t,e);if(null==n)return;const i=j(e),r=n.fields[i];if(null==r)return;if(r.status.permanent)return;n.fields[i]=void 0}(e,t)},setActiveFieldId:t=>{e.data.activeFieldId=t},updateFieldStatus:(t,e)=>{s.getUpdater("status").updateFieldStatus(t,e)},getUpdater:i=>function(t,e,n,i,r){const s=i[r];if(null!=s)return s(t,e,n);return}(s,e,n,t,i),enqueueUpdate:t=>{setTimeout(()=>n.update(t),0)}});return s}function N(t,e){if(e===d)return t;const n=e,i=n.indexOf(".");if(-1===i)return t.fields[n];{const e=n.slice(0,i),r=n.slice(i+".".length),s=t.fields[e];if(null==s)return;return N(s,r)}}function T(t){if(t===d)return;const e=t.lastIndexOf(".");return-1===e?d:t.slice(0,e)}function q(t,e){const n=T(e);if(null!=n)return N(t,n)}let L=0,_=0,W=0;setInterval(()=>{console.log(`Total updates: ${L}, handlers: ${W}, handler calls: ${_}`)},1e3);class C{constructor(n,i){this.emitter=new t,this.handlers={},this.handlerIdsByFieldId={},this.count=0,this.fieldHandlersListener=t=>{const e=Object.keys(this.handlerIdsByFieldId);if(0===e.length)return;const n=[];t.map(t=>{const e=t.path.filter(t=>"fields"!==t).join(".");n.push(e)});const i=[];for(const t of e){if(i.includes(t))continue;n.some(e=>!!e.startsWith(t)&&"."===e.charAt(t.length))&&i.push(t)}const r=i.map(t=>this.handlerIdsByFieldId[t]).flatMap(t=>t);for(const e of r){const n=this.handlers[e];null!=n&&(_++,n(t))}},this.state=e(n(),()=>{}),this.updaters=null!=i?Object.assign({},i):U(),this.emitter.addListener(this.fieldHandlersListener)}get state(){return this._state}set state(t){var e;if(!0===window.debugState){const n=new Error;console.groupCollapsed("State being updated:",Object.assign({},t)),console.log(null===(e=n.stack)||void 0===e?void 0:e.split("\n")[3]),console.groupEnd()}this._state=t,this._helpers=B(this._state,{})}get helpers(){return this._helpers}getState(){return this.state}addListener(t,e){const n=`h${this.count++}`;if(this.handlers[n]=t,W++,null==e||0===e.length)return this.emitter.addListener(e=>{_++,t(e)});const i=[];for(const t of e){let e=this.handlerIdsByFieldId[t];if(null==e&&(e=[],this.handlerIdsByFieldId[t]=e),e.includes(n))continue;e.push(n);const r=()=>{if(null==this.handlerIdsByFieldId[t])return;const e=this.handlerIdsByFieldId[t],i=e.indexOf(n);-1!==i&&e.splice(i,1)};i.push(r)}return()=>{for(const t of i)t()}}update(t){const n=[];L++;const i=e(this.state,e=>{t(M(this.updaters,e,this,{}),e)},t=>{n.push(...t)});this.state!==i&&(this.state=i,this.emitter.emit(n))}}const H="Reactway-Forms:";const D=new class{multiline(...t){const e=[];for(const n of t)e.push(n),e.push("\n");return e.splice(e.length-1,1),e}log(...t){console.log(...this.multiline("Reactway-Forms:",...t))}warn(...t){console.warn(...this.multiline("Reactway-Forms:",...t))}error(...t){console.error(...this.multiline("Reactway-Forms:",...t))}info(...t){console.info(...this.multiline("Reactway-Forms:",...t))}};export{c as CancellationTokenImpl,d as FormSelector,h as FormsStores,l as IdSeparator,H as LOGGER_PREFIX,r as StatusUpdater,w as StatusUpdaterFactory,C as Store,o as ValidationResultOrigin,a as ValidationResultType,s as ValidationUpdater,p as ValidationUpdaterFactory,i as ValueUpdater,V as ValueUpdaterFactory,E as assertFieldIsDefined,S as assertUpdaterIsDefined,g as constructFieldHelpers,v as constructInputFieldHelpers,B as constructStoreHelpers,M as constructUpdateStoreHelpers,m as constructValidatorHelpers,D as formsLogger,I as generateFieldId,k as getDefaultState,x as getDefaultStatuses,U as getDefaultUpdatersFactories,P as getDefaultValidation,j as getFieldNameFromId,T as getFieldParentId,$ as getInitialInputData,R as getInputValues,A as isInputFieldData,O as isPromise,N as selectField,q as selectFieldParent}; | ||
function __awaiter(thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
} | ||
class CancellationTokenImpl { | ||
constructor(cancellationCallback, tokenName) { | ||
this.tokenName = tokenName; | ||
this.cancelled = false; | ||
this.cancellationCallback = cancellationCallback; | ||
} | ||
get cancellationRequested() { | ||
return this.cancelled; | ||
} | ||
cancel() { | ||
var _a; | ||
if (this.cancelled) { | ||
return; | ||
} | ||
this.cancelled = true; | ||
(_a = this.cancellationCallback) === null || _a === void 0 ? void 0 : _a.call(this); | ||
} | ||
} | ||
class StoresRegistry extends TinyEmitter { | ||
constructor() { | ||
super(...arguments); | ||
this.storesRegistry = {}; | ||
} | ||
registerStore(formId, store) { | ||
if (this.storesRegistry[formId] != null) { | ||
throw new Error(`Form with formId "${formId}" is already registered.`); | ||
} | ||
this.storesRegistry[formId] = store; | ||
this.emit(); | ||
} | ||
unregisterStore(formId) { | ||
this.storesRegistry[formId] = undefined; | ||
this.emit(); | ||
} | ||
getStore(formId) { | ||
return this.storesRegistry[formId]; | ||
} | ||
getStoresIds() { | ||
return Object.keys(this.storesRegistry); | ||
} | ||
} | ||
class FormsStoresClass { | ||
setFormStoresHandler(newHandler, disposeOldOne = true) { | ||
if (disposeOldOne) { | ||
if (this.instance != null) { | ||
delete this.instance; | ||
} | ||
} | ||
this.instance = newHandler; | ||
} | ||
get registry() { | ||
var _a; | ||
this.instance = (_a = this.instance) !== null && _a !== void 0 ? _a : new StoresRegistry(); | ||
return this.instance; | ||
} | ||
} | ||
const FormsStores = new FormsStoresClass(); | ||
function constructFieldHelpers(fieldSelector, orderGuards) { | ||
return { | ||
registerValidator: validator => registerHelpers => { | ||
const validationUpdater = registerHelpers.getUpdater("validation"); | ||
return validationUpdater.registerValidator(fieldSelector, validator); | ||
}, | ||
unregisterValidator: validator => unregisterHelpers => { | ||
const validationUpdater = unregisterHelpers.getUpdater("validation"); | ||
validationUpdater.unregisterValidator(fieldSelector, validator); | ||
}, | ||
reportValidatorIndex: orderGuards.reportValidatorIndex | ||
}; | ||
} | ||
function constructInputFieldHelpers(fieldId, orderGuards) { | ||
return Object.assign(Object.assign(Object.assign({}, constructFieldHelpers(fieldId, orderGuards)), orderGuards), { registerModifier: modifier => registerHelpers => { | ||
const valueUpdater = registerHelpers.getUpdater("value"); | ||
return valueUpdater.registerModifier(fieldId, modifier); | ||
}, unregisterModifier: modifierId => unregisterHelpers => { | ||
const valueUpdater = unregisterHelpers.getUpdater("value"); | ||
valueUpdater.unregisterModifier(fieldId, modifierId); | ||
} }); | ||
} | ||
function ValidationUpdaterFactory(helpers, state, store) { | ||
return { | ||
id: "validation", | ||
validateField: (fieldSelector) => __awaiter(this, void 0, void 0, function* () { | ||
return validateField(helpers, state, store, fieldSelector); | ||
}), | ||
registerValidator: (fieldSelector, validator) => { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState, fieldSelector); | ||
const id = shortid.generate(); | ||
const mutableValidators = fieldState.validation.validators; | ||
mutableValidators[id] = Object.assign(Object.assign({}, validator), { id: id }); | ||
const mutableValidatorsOrder = fieldState.validation.validatorsOrder; | ||
mutableValidatorsOrder.push(id); | ||
fieldState.validation.results = []; | ||
return id; | ||
}, | ||
unregisterValidator: (fieldId, validatorId) => { | ||
const fieldState = helpers.selectField(fieldId); | ||
if (fieldState == null || fieldState.validation.validators[validatorId] == null) { | ||
// Gracefully return if fieldState or validator is not found, no need to throw. | ||
return; | ||
} | ||
const mutableValidators = fieldState.validation.validators; | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete mutableValidators[validatorId]; | ||
const validatorOrderIndex = fieldState.validation.validatorsOrder.findIndex(x => x === validatorId); | ||
if (validatorOrderIndex === -1) { | ||
return; | ||
} | ||
const mutableValidatorsOrder = fieldState.validation.validatorsOrder; | ||
mutableValidatorsOrder.splice(validatorOrderIndex, 1); | ||
fieldState.validation.results = []; | ||
}, | ||
setFormErrors: errors => { | ||
// TODO: Rename this updater. | ||
setFormErrors(state, errors); | ||
}, | ||
setFieldValidationResults: (fieldSelector, errors) => { | ||
setFieldValidationResults(helpers, fieldSelector, errors); | ||
}, | ||
resetFieldValidationResults: fieldSelector => { | ||
resetFieldValidationResults(helpers, fieldSelector); | ||
} | ||
}; | ||
} | ||
function validateField(helpers, _draft, store, fieldSelector) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState, fieldSelector); | ||
if (fieldState.validation.currentValidation != null) { | ||
fieldState.validation.currentValidation.cancellationToken.cancel(); | ||
} | ||
const validatorsKeys = Object.keys(fieldState.validation.validators); | ||
if (validatorsKeys.length === 0) { | ||
return; | ||
} | ||
// Copy validators because we're in an asynchronous context | ||
// and their proxy into current state might be revoked by immer. | ||
const validators = []; | ||
for (const validatorId of fieldState.validation.validatorsOrder) { | ||
const validator = fieldState.validation.validators[validatorId]; | ||
if (validator == null) { | ||
// TODO: Should we throw if the validator is not found? | ||
continue; | ||
} | ||
validators.push(Object.assign({}, validator)); | ||
} | ||
const fieldValue = fieldState.getValue(fieldState); | ||
// Indicate that the validation has started. | ||
const validationStarted = new Date(); | ||
const cancellationToken = new CancellationTokenImpl(() => { | ||
store.update(asyncHelpers => { | ||
const asyncFieldState = asyncHelpers.selectField(fieldSelector); | ||
if (asyncFieldState == null) { | ||
return; | ||
} | ||
const validation = asyncFieldState.validation; | ||
if (validation.currentValidation == null) { | ||
return; | ||
} | ||
// Start times are different, thus a new validation is already in progress. | ||
if (validation.currentValidation.started.getTime() !== validationStarted.getTime()) { | ||
return; | ||
} | ||
validation.currentValidation = undefined; | ||
}); | ||
}); | ||
// Set current validation and reset results synchronously | ||
// for other validations to not clutter the results and find the cancellation token. | ||
fieldState.validation.currentValidation = { | ||
started: validationStarted, | ||
cancellationToken: cancellationToken | ||
}; | ||
fieldState.validation.results = []; | ||
for (const validator of validators) { | ||
if (!validator.shouldValidate(fieldValue)) { | ||
continue; | ||
} | ||
// Check for cancellation before executing possibly costy validate function. | ||
if (cancellationToken.cancellationRequested) { | ||
return; | ||
} | ||
const validatorResult = validator.validate(fieldValue, constructValidatorHelpers(ValidationResultOrigin.Validation, validator.name)); | ||
if (validatorResult == null) { | ||
continue; | ||
} | ||
// Gather results. | ||
let results = undefined; | ||
if (!isPromise(validatorResult)) { | ||
results = validatorResult; | ||
} | ||
else { | ||
results = yield validatorResult; | ||
} | ||
if (results.length === 0) { | ||
continue; | ||
} | ||
// Construct proper ValidationResults in case any strings are in results. | ||
const validationResults = results.map(x => resolveValidationResult(x, validator.name)); | ||
updateFieldAsync(fieldSelector, store, cancellationToken, state => { | ||
const mutableValidationResults = state.validation.results; | ||
mutableValidationResults.push(...validationResults); | ||
}); | ||
} | ||
updateFieldAsync(fieldSelector, store, cancellationToken, state => { | ||
state.validation.currentValidation = undefined; | ||
}); | ||
}); | ||
} | ||
function constructValidatorHelpers(origin, validatorName) { | ||
return { | ||
error: (message, code, data) => ({ | ||
type: ValidationResultType.Error, | ||
message: message, | ||
code: code, | ||
origin: origin, | ||
validatorName: validatorName, | ||
data: data | ||
}), | ||
warning: (message, code, data) => ({ | ||
type: ValidationResultType.Warning, | ||
message: message, | ||
code: code, | ||
origin: origin, | ||
validatorName: validatorName, | ||
data: data | ||
}) | ||
}; | ||
} | ||
function updateFieldAsync(fieldSelector, store, cancellationToken, updater) { | ||
// Check for cancellation right away. | ||
if (cancellationToken.cancellationRequested) { | ||
return; | ||
} | ||
setTimeout(() => { | ||
// Check for cancellation again at the start of asynchronous scope. | ||
if (cancellationToken.cancellationRequested) { | ||
return; | ||
} | ||
store.update(helpers => { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
if (fieldState == null) { | ||
return; | ||
} | ||
updater(fieldState); | ||
}); | ||
}, 0); | ||
} | ||
function resolveValidationResult(value, validatorName) { | ||
if (typeof value !== "string") { | ||
return value; | ||
} | ||
return { | ||
message: value, | ||
validatorName: validatorName, | ||
type: ValidationResultType.Error, | ||
origin: ValidationResultOrigin.Validation | ||
}; | ||
} | ||
function setFormErrors(state, errors) { | ||
for (const key of Object.keys(errors)) { | ||
const fieldError = errors[key]; | ||
const field = state.fields[key]; | ||
if (field == null || fieldError == null) { | ||
continue; | ||
} | ||
if (typeof fieldError === "object" && !Array.isArray(fieldError)) { | ||
setFormErrors(field, fieldError); | ||
continue; | ||
} | ||
const validationResults = []; | ||
for (const error of fieldError) { | ||
let validationResult; | ||
if (typeof error !== "string") { | ||
validationResult = error; | ||
} | ||
else { | ||
validationResult = { | ||
message: error, | ||
type: ValidationResultType.Error, | ||
origin: ValidationResultOrigin.FormSubmit | ||
}; | ||
} | ||
validationResults.push(validationResult); | ||
} | ||
field.validation.results = validationResults; | ||
} | ||
} | ||
function setFieldValidationResults(helpers, fieldSelector, errors) { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState); | ||
fieldState.validation.results = errors.map(result => typeof result !== "string" | ||
? result | ||
: { | ||
message: result, | ||
type: ValidationResultType.Error | ||
}); | ||
} | ||
function resetFieldValidationResults(helpers, fieldSelector) { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState); | ||
fieldState.validation.results = []; | ||
} | ||
const formsLogger = Debug("@reactway:forms"); | ||
const logger = formsLogger.extend("value-updater"); | ||
function ValueUpdaterFactory(helpers) { | ||
const valueUpdater = { | ||
id: "value", | ||
updateFieldValue: (fieldId, value, selection) => { | ||
var _a, _b; | ||
const fieldState = helpers.selectField(fieldId); | ||
assertFieldIsDefined(fieldState, fieldId); | ||
if (!isInputFieldData(fieldState.data)) { | ||
// TODO: Review | ||
throw new Error("Not implemented."); | ||
} | ||
fieldState.validation.results = []; | ||
const modifiers = fieldState.data.modifiers; | ||
const modifiersKeys = Object.keys(modifiers); | ||
if (modifiersKeys.length === 0) { | ||
// No modifiers found, thus a value is set directly to currentValue. | ||
fieldState.data.currentValue = value; | ||
fieldState.data.selection = selection; | ||
helpers.updateFieldStatus(fieldId, status => { | ||
status.touched = true; | ||
status.pristine = value === fieldState.data.initialValue; | ||
}); | ||
return; | ||
} | ||
// Modifiers found, firing up the modifiers mechanism. | ||
const previousParseValue = { | ||
value: (_a = fieldState.data.transientValue) !== null && _a !== void 0 ? _a : fieldState.data.currentValue, | ||
caretPosition: (_b = fieldState.data.selection) === null || _b === void 0 ? void 0 : _b.selectionStart | ||
}; | ||
let newValue = value; | ||
let transientValue = undefined; | ||
let newCaretPosition = selection === null || selection === void 0 ? void 0 : selection.selectionStart; | ||
for (const modifierKey of modifiersKeys) { | ||
const modifier = modifiers[modifierKey]; | ||
if (modifier == null) { | ||
throw new Error("Should never happen."); | ||
} | ||
const newParseValue = { | ||
value: newValue, | ||
caretPosition: newCaretPosition | ||
}; | ||
const result = modifier.parse(newParseValue, previousParseValue); | ||
newValue = result.currentValue; | ||
// If modifier returned selection | ||
if (result.caretPosition != null) { | ||
// It becomes our newSelection. | ||
newCaretPosition = result.caretPosition; | ||
} | ||
// If transientValue was is null or was already set before | ||
if (transientValue != null || result.transientValue == null) { | ||
// Do nothing. | ||
continue; | ||
} | ||
// When first transientValue is encountered, it wins until it is resolved. | ||
transientValue = result.transientValue; | ||
} | ||
let newSelection = undefined; | ||
if (newCaretPosition != null) { | ||
newSelection = { | ||
selectionStart: newCaretPosition, | ||
selectionEnd: newCaretPosition, | ||
selectionDirection: "none" | ||
}; | ||
} | ||
fieldState.data.currentValue = newValue; | ||
fieldState.data.transientValue = transientValue; | ||
logger("Setting new selection to:", Object.assign({}, newSelection)); | ||
fieldState.data.selection = newSelection; | ||
}, | ||
resetFieldValue: fieldId => { | ||
const fieldState = helpers.selectField(fieldId); | ||
assertFieldIsDefined(fieldState, fieldId); | ||
if (!isInputFieldData(fieldState.data)) { | ||
throw new Error("Only input field can be reset."); | ||
} | ||
valueUpdater.updateFieldValue(fieldId, fieldState.data.initialValue); | ||
}, | ||
clearFieldValue: fieldId => { | ||
const fieldState = helpers.selectField(fieldId); | ||
assertFieldIsDefined(fieldState, fieldId); | ||
if (!isInputFieldData(fieldState.data)) { | ||
throw new Error("Only input field can be cleared."); | ||
} | ||
valueUpdater.updateFieldValue(fieldId, fieldState.data.defaultValue); | ||
}, | ||
registerModifier: (fieldId, modifier) => { | ||
const fieldState = helpers.selectField(fieldId); | ||
assertFieldIsDefined(fieldState, fieldId); | ||
if (!isInputFieldData(fieldState.data)) { | ||
throw new Error("Not implemented."); | ||
} | ||
const id = shortid.generate(); | ||
const modifiers = fieldState.data.modifiers; | ||
const mutableModifiers = modifiers; | ||
mutableModifiers[id] = Object.assign(Object.assign({}, modifier), { id }); | ||
return id; | ||
}, | ||
unregisterModifier: (fieldId, modifierId) => { | ||
const fieldState = helpers.selectField(fieldId); | ||
if (fieldState == null) { | ||
return; | ||
} | ||
if (!isInputFieldData(fieldState.data)) { | ||
throw new Error("Not implemented."); | ||
} | ||
const modifiers = fieldState.data.modifiers; | ||
if (modifiers[modifierId] == null) { | ||
// Gracefully return if the modifier is not found, no need to throw. | ||
return; | ||
} | ||
const modifierOrderIndex = fieldState.data.modifiersOrder.findIndex(x => x === modifierId); | ||
if (modifierOrderIndex === -1) { | ||
return; | ||
} | ||
const mutableModifiersOrder = fieldState.data.modifiersOrder; | ||
mutableModifiersOrder.splice(modifierOrderIndex, 1); | ||
} | ||
}; | ||
return valueUpdater; | ||
} | ||
function StatusUpdaterFactory(helpers, state) { | ||
return { | ||
id: "status", | ||
updateFieldStatus: (fieldSelector, updater) => { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState, fieldSelector); | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
const prevStatus = produce(fieldState.status, () => { }); | ||
let statusPatches; | ||
const newStatus = produce(prevStatus, status => { | ||
updater(status); | ||
}, patches => { | ||
statusPatches = patches; | ||
}); | ||
updater(fieldState.status); | ||
const statusChanged = (statusKey) => { | ||
return statusPatches.find(x => x.path.includes(statusKey)) != null; | ||
}; | ||
if (newStatus.touched && statusChanged("touched")) { | ||
updateDependentStatusUpwards(state, helpers, fieldSelector, status => { | ||
status.touched = true; | ||
}); | ||
} | ||
if (statusChanged("pristine")) { | ||
updateDependentStatusUpwards(state, helpers, fieldSelector, status => { | ||
status.pristine = newStatus.pristine; | ||
}); | ||
} | ||
if (statusChanged("readonly")) { | ||
updateDependentStatusDownwards(fieldState, status => { | ||
status.readonly = newStatus.readonly; | ||
}); | ||
} | ||
if (prevStatus === newStatus) { | ||
return; | ||
} | ||
} | ||
}; | ||
} | ||
function updateDependentStatusDownwards(fieldState, updater) { | ||
// TODO: Review | ||
for (const key of Object.keys(fieldState.fields)) { | ||
const child = fieldState.fields[key]; | ||
if (child == null) { | ||
continue; | ||
} | ||
updater(child.status); | ||
updateDependentStatusDownwards(child, updater); | ||
} | ||
} | ||
function updateDependentStatusUpwards(state, helpers, fieldSelector, updater) { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState, fieldSelector); | ||
// Update all parents (that have id) statuses. | ||
let parentId = fieldSelector; | ||
while ((parentId = helpers.getFieldParentId(parentId)) != null) { | ||
const field = helpers.selectField(parentId); | ||
assertFieldIsDefined(field, fieldSelector); | ||
updater(field.status); | ||
} | ||
// Update form (upper-most) status too. | ||
// TODO: Review if this is needed and/or FormSelector should be used. | ||
updater(state.status); | ||
} | ||
function isPromise(candidate) { | ||
return candidate.then != null && candidate.catch != null; | ||
} | ||
function generateFieldId(name, parentId) { | ||
if (parentId == null) { | ||
return name; | ||
} | ||
return `${parentId}${IdSeparator}${name}`; | ||
} | ||
function getFieldNameFromId(fieldId) { | ||
const lastSeparatorIndex = fieldId.lastIndexOf(IdSeparator); | ||
if (lastSeparatorIndex === -1) { | ||
return fieldId; | ||
} | ||
return fieldId.slice(lastSeparatorIndex + IdSeparator.length); | ||
} | ||
function assertFieldIsDefined(field, fieldSelector) { | ||
if (field == null) { | ||
throw new Error(`Field '${String(fieldSelector)}' does not exist in a given state.`); | ||
} | ||
} | ||
function assertUpdaterIsDefined(updater, updaterId) { | ||
if (updater == null) { | ||
throw new Error(`Updater '${updaterId}' does not exist.`); | ||
} | ||
} | ||
function getDefaultState() { | ||
return { | ||
fields: {}, | ||
status: getDefaultStatuses(), | ||
validation: getDefaultValidation() | ||
}; | ||
} | ||
function getDefaultStatuses() { | ||
return { | ||
disabled: false, | ||
pristine: true, | ||
touched: false, | ||
readonly: false, | ||
permanent: false | ||
}; | ||
} | ||
function getInputValues(defaultValue, initialValue, currentValue, transientValue) { | ||
// Triple equals to `undefined`, because `null` might be a valid value. | ||
if (initialValue === undefined) { | ||
initialValue = defaultValue; | ||
} | ||
// Triple equals to `undefined`, because `null` might be a valid value. | ||
if (currentValue === undefined) { | ||
currentValue = initialValue; | ||
} | ||
return { | ||
defaultValue, | ||
initialValue, | ||
currentValue, | ||
transientValue | ||
}; | ||
} | ||
function getInitialInputData(defaultValue, initialValue, currentValue, transientValue) { | ||
// Triple equals to `undefined`, because `null` might be a valid value. | ||
if (initialValue === undefined) { | ||
initialValue = defaultValue; | ||
} | ||
// Triple equals to `undefined`, because `null` might be a valid value. | ||
if (currentValue === undefined) { | ||
currentValue = initialValue; | ||
} | ||
return { | ||
defaultValue, | ||
initialValue, | ||
currentValue, | ||
transientValue, | ||
modifiers: {}, | ||
modifiersOrder: [] | ||
}; | ||
} | ||
function getDefaultUpdatersFactories() { | ||
return { | ||
validation: ValidationUpdaterFactory, | ||
value: ValueUpdaterFactory, | ||
status: StatusUpdaterFactory | ||
}; | ||
} | ||
function getDefaultValidation() { | ||
return { | ||
results: [], | ||
validators: {}, | ||
validatorsOrder: [] | ||
}; | ||
} | ||
function isInputFieldData(candidate) { | ||
const inputValues = candidate; | ||
return (inputValues.defaultValue !== undefined && | ||
inputValues.initialValue !== undefined && | ||
inputValues.currentValue !== undefined && | ||
inputValues.modifiers !== undefined && | ||
typeof inputValues.modifiers === "object" && | ||
Array.isArray(inputValues.modifiersOrder)); | ||
} | ||
function constructStoreHelpers(state, fieldsCache) { | ||
const cachedSelectField = fieldSelector => { | ||
if (fieldSelector === FormSelector) { | ||
return state; | ||
} | ||
const cachedField = fieldsCache[fieldSelector]; | ||
if (cachedField != null) { | ||
return cachedField; | ||
} | ||
const selectedField = selectField(state, fieldSelector); | ||
if (selectedField != null) { | ||
fieldsCache[fieldSelector] = selectedField; | ||
} | ||
return selectedField; | ||
}; | ||
const helpers = { | ||
selectField: cachedSelectField, | ||
selectFieldParent: fieldSelector => { | ||
const parentId = getFieldParentId(fieldSelector); | ||
if (parentId == null) { | ||
return undefined; | ||
} | ||
return cachedSelectField(parentId); | ||
}, | ||
getActiveFieldId: () => { | ||
const formState = state; | ||
return formState.data.activeFieldId; | ||
}, | ||
getFieldParentId: getFieldParentId, | ||
getFormValue: () => { | ||
return state.getValue(state); | ||
} | ||
}; | ||
return helpers; | ||
} | ||
function constructUpdateStoreHelpers(updaters, draft, store, fieldsCache) { | ||
const fieldStoreHelpers = constructStoreHelpers(draft, fieldsCache); | ||
const updateStoreHelpers = Object.assign(Object.assign({}, fieldStoreHelpers), { registerField: (fieldId, initialFieldState) => { | ||
registerField(draft, fieldId, initialFieldState); | ||
}, unregisterField: id => { | ||
unregisterField(draft, id); | ||
}, setActiveFieldId: fieldId => { | ||
const formState = draft; | ||
formState.data.activeFieldId = fieldId; | ||
}, updateFieldStatus: (fieldSelector, updater) => { | ||
const statusUpdater = updateStoreHelpers.getUpdater("status"); | ||
statusUpdater.updateFieldStatus(fieldSelector, updater); | ||
}, getUpdater: updaterId => { | ||
return getUpdater(updateStoreHelpers, draft, store, updaters, updaterId); | ||
}, enqueueUpdate: updater => { | ||
setTimeout(() => store.update(updater), 0); | ||
} }); | ||
return updateStoreHelpers; | ||
} | ||
function registerField(state, fieldId, initialFieldState) { | ||
if (initialFieldState.computedValue && isInputFieldData(initialFieldState.data)) { | ||
throw new Error(`Field ${fieldId} is marked to have computedValue, but also has data of an input field.`); | ||
} | ||
const fieldName = getFieldNameFromId(fieldId); | ||
const parentField = selectRegistrationParent(state, fieldId); | ||
if (parentField == null) { | ||
throw new Error(`Parent for field '${fieldId}' to be registered on was not found.`); | ||
} | ||
const fieldState = parentField.fields[fieldName]; | ||
if (fieldState != null && fieldState.status.permanent === false) { | ||
throw new Error(`Field '${fieldId}' has already been registered.`); | ||
} | ||
if (fieldState == null) { | ||
// Make fields non-read-only | ||
const mutableFields = parentField.fields; | ||
// Add field into the state. | ||
mutableFields[fieldName] = Object.assign(Object.assign(Object.assign({}, getDefaultState()), initialFieldState), { id: fieldId, name: fieldName, fields: {} }); | ||
} | ||
} | ||
function unregisterField(state, id) { | ||
const parentField = selectFieldParent(state, id); | ||
if (parentField == null) { | ||
return; | ||
} | ||
const fieldName = getFieldNameFromId(id); | ||
const field = parentField.fields[fieldName]; | ||
if (field == null) { | ||
return; | ||
} | ||
if (field.status.permanent) { | ||
return; | ||
} | ||
// Make fields non-read-only | ||
const mutableFields = parentField.fields; | ||
mutableFields[fieldName] = undefined; | ||
} | ||
function getUpdater(helpers, fieldState, store, updaters, updaterId) { | ||
const factory = updaters[updaterId]; | ||
if (factory != null) { | ||
return factory(helpers, fieldState, store); | ||
} | ||
return undefined; | ||
} | ||
function selectRegistrationParent(state, fieldId) { | ||
const separatorIndex = fieldId.indexOf(IdSeparator); | ||
if (separatorIndex === -1) { | ||
return state; | ||
} | ||
else { | ||
const firstChildName = fieldId.slice(0, separatorIndex); | ||
const nextChildId = fieldId.slice(separatorIndex + IdSeparator.length); | ||
// TODO: Review | ||
const child = state.fields[firstChildName]; | ||
if (child == null) { | ||
return undefined; | ||
} | ||
return selectRegistrationParent(child, nextChildId); | ||
} | ||
} | ||
// TODO: Do we need recursion here? | ||
function selectField(state, selector) { | ||
if (selector === FormSelector) { | ||
return state; | ||
} | ||
const fieldId = selector; | ||
const firstSeparatorIndex = fieldId.indexOf(IdSeparator); | ||
if (firstSeparatorIndex === -1) { | ||
return state.fields[fieldId]; | ||
} | ||
else { | ||
const name = fieldId.slice(0, firstSeparatorIndex); | ||
const nextFieldId = fieldId.slice(firstSeparatorIndex + IdSeparator.length); | ||
const child = state.fields[name]; | ||
if (child == null) { | ||
return undefined; | ||
} | ||
return selectField(child, nextFieldId); | ||
} | ||
} | ||
function getFieldParentId(fieldSelector) { | ||
if (fieldSelector === FormSelector) { | ||
return undefined; | ||
} | ||
const lastSeparatorIndex = fieldSelector.lastIndexOf(IdSeparator); | ||
if (lastSeparatorIndex === -1) { | ||
return FormSelector; | ||
} | ||
return fieldSelector.slice(0, lastSeparatorIndex); | ||
} | ||
function selectFieldParent(state, fieldId) { | ||
const parentId = getFieldParentId(fieldId); | ||
if (parentId == null) { | ||
return undefined; | ||
} | ||
return selectField(state, parentId); | ||
} | ||
class Store { | ||
constructor(initialStateFactory, updatersFactories) { | ||
// super(); | ||
this.emitter = new TinyEmitter(); | ||
this.handlers = {}; | ||
this.handlerIdsByFieldId = {}; | ||
this.count = 0; | ||
this.fieldHandlersListener = (patches) => { | ||
const fieldsKeys = Object.keys(this.handlerIdsByFieldId); | ||
if (fieldsKeys.length === 0) { | ||
return; | ||
} | ||
// Strongly type the key to catch an error if it changes. | ||
const fieldsKey = "fields"; | ||
const paths = []; | ||
patches.map(patch => { | ||
const idPath = patch.path.filter(x => x !== fieldsKey).join(IdSeparator); | ||
paths.push(idPath); | ||
}); | ||
const fieldsToCall = []; | ||
for (const fieldKey of fieldsKeys) { | ||
// TODO: Is this check needed? | ||
if (fieldsToCall.includes(fieldKey)) { | ||
continue; | ||
} | ||
const fieldHandlersShouldBeCalled = paths.some(path => { | ||
if (!path.startsWith(fieldKey)) { | ||
return false; | ||
} | ||
return path.charAt(fieldKey.length) === IdSeparator; | ||
}); | ||
if (!fieldHandlersShouldBeCalled) { | ||
continue; | ||
} | ||
fieldsToCall.push(fieldKey); | ||
} | ||
const handlerIds = fieldsToCall | ||
.map(fieldId => { | ||
// We've accumulated the ids from this object, thus they exist. | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
return this.handlerIdsByFieldId[fieldId]; | ||
}) | ||
.flatMap(x => x); | ||
for (const handlerId of handlerIds) { | ||
const handler = this.handlers[handlerId]; | ||
if (handler == null) { | ||
continue; | ||
} | ||
handler(patches); | ||
} | ||
}; | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
this.state = produce(initialStateFactory(), () => { }); | ||
this.updaters = updatersFactories != null ? Object.assign({}, updatersFactories) : getDefaultUpdatersFactories(); | ||
this.emitter.addListener(this.fieldHandlersListener); | ||
} | ||
get state() { | ||
return this._state; | ||
} | ||
set state(value) { | ||
var _a; | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
if (window.debugState === true) { | ||
const err = new Error(); | ||
/* eslint-disable no-console */ | ||
console.groupCollapsed("State being updated:", Object.assign({}, value)); | ||
console.log((_a = err.stack) === null || _a === void 0 ? void 0 : _a.split("\n")[3]); | ||
console.groupEnd(); | ||
/* eslint-enable no-console */ | ||
} | ||
this._state = value; | ||
this._helpers = constructStoreHelpers(this._state, {}); | ||
} | ||
get helpers() { | ||
return this._helpers; | ||
} | ||
getState() { | ||
return this.state; | ||
} | ||
addListener(handler, dependentFields) { | ||
const handlerId = `h${this.count++}`; | ||
this.handlers[handlerId] = handler; | ||
if (dependentFields == null || dependentFields.length === 0) { | ||
return this.emitter.addListener(patches => { | ||
handler(patches); | ||
}); | ||
} | ||
const unregisterCallbacks = []; | ||
for (const fieldId of dependentFields) { | ||
let handlerIds = this.handlerIdsByFieldId[fieldId]; | ||
if (handlerIds == null) { | ||
handlerIds = []; | ||
this.handlerIdsByFieldId[fieldId] = handlerIds; | ||
} | ||
if (handlerIds.includes(handlerId)) { | ||
continue; | ||
} | ||
// Register callback | ||
handlerIds.push(handlerId); | ||
const unregisterCallback = () => { | ||
if (this.handlerIdsByFieldId[fieldId] == null) { | ||
return; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, no-shadow | ||
const handlerIds = this.handlerIdsByFieldId[fieldId]; | ||
const index = handlerIds.indexOf(handlerId); | ||
if (index === -1) { | ||
return; | ||
} | ||
handlerIds.splice(index, 1); | ||
}; | ||
unregisterCallbacks.push(unregisterCallback); | ||
} | ||
return () => { | ||
for (const callback of unregisterCallbacks) { | ||
callback(); | ||
} | ||
}; | ||
} | ||
update(updater) { | ||
const patches = []; | ||
const newState = produce(this.state, draft => { | ||
updater(constructUpdateStoreHelpers(this.updaters, draft, this, {}), draft); | ||
}, updatePatches => { | ||
patches.push(...updatePatches); | ||
}); | ||
if (this.state === newState) { | ||
return; | ||
} | ||
this.state = newState; | ||
this.emitter.emit(patches); | ||
} | ||
} | ||
export { CancellationTokenImpl, FormSelector, FormsStores, IdSeparator, StatusUpdater, StatusUpdaterFactory, Store, ValidationResultOrigin, ValidationResultType, ValidationUpdater, ValidationUpdaterFactory, ValueUpdater, ValueUpdaterFactory, assertFieldIsDefined, assertUpdaterIsDefined, constructFieldHelpers, constructInputFieldHelpers, constructStoreHelpers, constructUpdateStoreHelpers, constructValidatorHelpers, formsLogger, generateFieldId, getDefaultState, getDefaultStatuses, getDefaultUpdatersFactories, getDefaultValidation, getFieldNameFromId, getFieldParentId, getInitialInputData, getInputValues, isInputFieldData, isPromise, selectField, selectFieldParent }; |
@@ -1,2 +0,30 @@ | ||
"use strict";function t(t){return t&&"object"==typeof t&&"default"in t?t.default:t}Object.defineProperty(exports,"__esModule",{value:!0});var e=require("@reactway/tiny-emitter"),i=t(require("immer")),n=t(require("shortid"));var r,s;(r=exports.ValidationResultType||(exports.ValidationResultType={}))[r.Error=0]="Error",r[r.Warning=1]="Warning",(s=exports.ValidationResultOrigin||(exports.ValidationResultOrigin={}))[s.Unknown=0]="Unknown",s[s.Validation=1]="Validation",s[s.FormSubmit=2]="FormSubmit";const a=Symbol("form"); | ||
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var tinyEmitter = require('@reactway/tiny-emitter'); | ||
var produce = _interopDefault(require('immer')); | ||
var shortid = _interopDefault(require('shortid')); | ||
var Debug = _interopDefault(require('debug')); | ||
const ValueUpdater = "value"; | ||
const StatusUpdater = "status"; | ||
const ValidationUpdater = "validation"; | ||
// These comments originated in the v4 codebase and are kept for historical purpose: | ||
(function (ValidationResultType) { | ||
ValidationResultType[ValidationResultType["Error"] = 0] = "Error"; | ||
ValidationResultType[ValidationResultType["Warning"] = 1] = "Warning"; | ||
})(exports.ValidationResultType || (exports.ValidationResultType = {})); | ||
(function (ValidationResultOrigin) { | ||
ValidationResultOrigin[ValidationResultOrigin["Unknown"] = 0] = "Unknown"; | ||
ValidationResultOrigin[ValidationResultOrigin["Validation"] = 1] = "Validation"; | ||
ValidationResultOrigin[ValidationResultOrigin["FormSubmit"] = 2] = "FormSubmit"; | ||
})(exports.ValidationResultOrigin || (exports.ValidationResultOrigin = {})); | ||
const IdSeparator = "."; | ||
const FormSelector = Symbol("form"); | ||
/*! ***************************************************************************** | ||
@@ -16,2 +44,926 @@ Copyright (c) Microsoft Corporation. All rights reserved. | ||
***************************************************************************** */ | ||
function o(t,e,i,n){return new(i||(i=Promise))((function(r,s){function a(t){try{l(n.next(t))}catch(t){s(t)}}function o(t){try{l(n.throw(t))}catch(t){s(t)}}function l(t){t.done?r(t.value):new i((function(e){e(t.value)})).then(a,o)}l((n=n.apply(t,e||[])).next())}))}class l{constructor(t,e){this.tokenName=e,this.cancelled=!1,this.cancellationCallback=t}get cancellationRequested(){return this.cancelled}cancel(){var t;this.cancelled||(this.cancelled=!0,null===(t=this.cancellationCallback)||void 0===t||t.call(this))}}class d extends e.TinyEmitter{constructor(){super(...arguments),this.storesRegistry={}}registerStore(t,e){if(null!=this.storesRegistry[t])throw new Error(`Form with formId "${t}" is already registered.`);this.storesRegistry[t]=e,this.emit()}unregisterStore(t){this.storesRegistry[t]=void 0,this.emit()}getStore(t){return this.storesRegistry[t]}getStoresIds(){return Object.keys(this.storesRegistry)}}const u=new class{setFormStoresHandler(t,e=!0){e&&null!=this.instance&&delete this.instance,this.instance=t}get registry(){var t;return this.instance=null!==(t=this.instance)&&void 0!==t?t:new d,this.instance}};function c(t,e){return{registerValidator:e=>i=>i.getUpdater("validation").registerValidator(t,e),unregisterValidator:e=>i=>{i.getUpdater("validation").unregisterValidator(t,e)},reportValidatorIndex:e.reportValidatorIndex}}function f(t,e,i){return{id:"validation",validateField:e=>o(this,void 0,void 0,(function*(){return function(t,e,i,n){return o(this,void 0,void 0,(function*(){const e=t.selectField(n);if(x(e,n),null!=e.validation.currentValidation&&e.validation.currentValidation.cancellationToken.cancel(),0===Object.keys(e.validation.validators).length)return;const r=[];for(const t of e.validation.validatorsOrder){const i=e.validation.validators[t];null!=i&&r.push(Object.assign({},i))}const s=e.getValue(e),a=new Date,o=new l(()=>{i.update(t=>{const e=t.selectField(n);if(null==e)return;const i=e.validation;null!=i.currentValidation&&i.currentValidation.started.getTime()===a.getTime()&&(i.currentValidation=void 0)})});e.validation.currentValidation={started:a,cancellationToken:o},e.validation.results=[];for(const t of r){if(!t.shouldValidate(s))continue;if(o.cancellationRequested)return;const e=t.validate(s,p(exports.ValidationResultOrigin.Validation,t.name));if(null==e)continue;let r=void 0;if(r=V(e)?yield e:e,0===r.length)continue;const a=r.map(e=>g(e,t.name));h(n,i,o,t=>{t.validation.results.push(...a)})}h(n,i,o,t=>{t.validation.currentValidation=void 0})}))}(t,0,i,e)})),registerValidator:(e,i)=>{const r=t.selectField(e);x(r,e);const s=n.generate();return r.validation.validators[s]=Object.assign(Object.assign({},i),{id:s}),r.validation.validatorsOrder.push(s),s},unregisterValidator:(e,i)=>{const n=t.selectField(e);if(x(n,e),null==n.validation.validators[i])return;delete n.validation.validators[i];const r=n.validation.validatorsOrder.findIndex(t=>t===i);-1!==r&&n.validation.validatorsOrder.splice(r,1)},setFormErrors:t=>{!function t(e,i){for(const n of Object.keys(i)){const r=i[n],s=e.fields[n];if(null==s||null==r)continue;if("object"==typeof r&&!Array.isArray(r)){t(s,r);continue}const a=[];for(const t of r){let e;e="string"!=typeof t?t:{message:t,type:exports.ValidationResultType.Error,origin:exports.ValidationResultOrigin.FormSubmit},a.push(e)}s.validation.results=a}}(e,t)}}}function p(t,e){return{error:(i,n)=>({type:exports.ValidationResultType.Error,message:i,code:n,origin:t,validatorName:e}),warning:(i,n)=>({type:exports.ValidationResultType.Warning,message:i,code:n,origin:t,validatorName:e})}}function h(t,e,i,n){i.cancellationRequested||setTimeout(()=>{i.cancellationRequested||e.update(e=>{const i=e.selectField(t);null!=i&&n(i)})},0)}function g(t,e){return"string"!=typeof t?t:{message:t,validatorName:e,type:exports.ValidationResultType.Error,origin:exports.ValidationResultOrigin.Validation}}function v(t){const e={id:"value",updateFieldValue:(e,i,n)=>{var r,s;const a=t.selectField(e);if(x(a,e),!S(a.data))throw new Error("Not implemented.");const o=a.data.modifiers,l=Object.keys(o);if(0===l.length)return a.data.currentValue=i,a.data.selection=n,void t.updateFieldStatus(e,t=>{t.touched=!0,t.pristine=i===a.data.initialValue});const d={value:null!==(r=a.data.transientValue)&&void 0!==r?r:a.data.currentValue,caretPosition:null===(s=a.data.selection)||void 0===s?void 0:s.selectionStart};let u=i,c=void 0,f=null==n?void 0:n.selectionStart;for(const t of l){const e=o[t];if(null==e)throw new Error("Should never happen.");const i={value:u,caretPosition:f},n=e.parse(i,d);u=n.currentValue,null!=n.caretPosition&&(f=n.caretPosition),null==c&&null!=n.transientValue&&(c=n.transientValue)}let p=void 0;null!=f&&(p={selectionStart:f,selectionEnd:f,selectionDirection:"none"}),a.data.currentValue=u,a.data.transientValue=c,console.group("Setting new selection to:"),console.log(Object.assign({},p)),console.groupEnd(),a.data.selection=p},resetFieldValue:i=>{const n=t.selectField(i);if(x(n,i),!S(n.data))throw new Error("Only input field can be reset.");e.updateFieldValue(i,n.data.initialValue)},clearFieldValue:i=>{const n=t.selectField(i);if(x(n,i),!S(n.data))throw new Error("Only input field can be cleared.");e.updateFieldValue(i,n.data.defaultValue)},registerModifier:(e,i)=>{const r=t.selectField(e);if(x(r,e),!S(r.data))throw new Error("Not implemented.");const s=n.generate();return r.data.modifiers[s]=Object.assign(Object.assign({},i),{id:s}),s},unregisterModifier:(e,i)=>{const n=t.selectField(e);if(x(n,e),!S(n.data))throw new Error("Not implemented.");if(null==n.data.modifiers[i])return;const r=n.data.modifiersOrder.findIndex(t=>t===i);-1!==r&&n.data.modifiersOrder.splice(r,1)}};return e}function m(t,e){return{id:"status",updateFieldStatus:(n,r)=>{const s=t.selectField(n);x(s,n);const a=i(s.status,()=>{});let o;const l=i(a,t=>{r(t)},t=>{o=t});r(s.status);const d=t=>null!=o.find(e=>e.path.includes(t));l.touched&&d("touched")&&F(e,t,n,t=>{t.touched=!0}),d("pristine")&&F(e,t,n,t=>{t.pristine=l.pristine}),d("readonly")&&function t(e,i){for(const n of Object.keys(e.fields)){const r=e.fields[n];null!=r&&(i(r.status),t(r,i))}}(s,t=>{t.readonly=l.readonly})}}}function F(t,e,i,n){x(e.selectField(i),i);let r=i;for(;null!=(r=e.getFieldParentId(r));){const t=e.selectField(r);x(t,i),n(t.status)}n(t.status)}function V(t){return null!=t.then&&null!=t.catch}function y(t){const e=t.lastIndexOf(".");return-1===e?t:t.slice(e+".".length)}function x(t,e){if(null==t)throw new Error(`Field '${String(e)}' does not exist in a given state.`)}function w(){return{fields:{},status:{disabled:!1,pristine:!0,touched:!1,readonly:!1,permanent:!1},validation:{results:[],validators:{},validatorsOrder:[]}}}function O(){return{disabled:!1,pristine:!0,touched:!1,readonly:!1,permanent:!1}}function b(){return{validation:f,value:v,status:m}}function I(){return{results:[],validators:{},validatorsOrder:[]}}function S(t){const e=t;return void 0!==e.defaultValue&&void 0!==e.initialValue&&void 0!==e.currentValue&&void 0!==e.modifiers&&"object"==typeof e.modifiers&&Array.isArray(e.modifiersOrder)}function R(t,e){const i=i=>{if(i===a)return t;const n=e[i];if(null!=n)return n;const r=E(t,i);return null!=r&&(e[i]=r),r};return{selectField:i,selectFieldParent:t=>{const e=U(t);if(null!=e)return i(e)},getActiveFieldId:()=>t.data.activeFieldId,getFieldParentId:U,getFormValue:()=>t.getValue(t)}}function j(t,e,i,n){const r=R(e,n),s=Object.assign(Object.assign({},r),{registerField:(t,i)=>{!function(t,e,i){if(i.computedValue&&S(i.data))throw new Error(`Field ${e} is marked to have computedValue, but also has data of an input field.`);const n=y(e),r=function t(e,i){const n=i.indexOf(".");if(-1===n)return e;{const r=i.slice(0,n),s=i.slice(n+".".length),a=e.fields[r];if(null==a)return;return t(a,s)}}(t,e);if(null==r)throw new Error(`Parent for field '${e}' to be registered on was not found.`);const s=r.fields[n];if(null!=s&&!1===s.status.permanent)throw new Error(`Field '${e}' has already been registered.`);if(null==s){r.fields[n]=Object.assign(Object.assign(Object.assign({},{fields:{},status:{disabled:!1,pristine:!0,touched:!1,readonly:!1,permanent:!1},validation:{results:[],validators:{},validatorsOrder:[]}}),i),{id:e,name:n,fields:{}})}}(e,t,i)},unregisterField:t=>{!function(t,e){const i=k(t,e);if(null==i)return;const n=y(e),r=i.fields[n];if(null==r)return;if(r.status.permanent)return;i.fields[n]=void 0}(e,t)},setActiveFieldId:t=>{e.data.activeFieldId=t},updateFieldStatus:(t,e)=>{s.getUpdater("status").updateFieldStatus(t,e)},getUpdater:n=>function(t,e,i,n,r){const s=n[r];if(null!=s)return s(t,e,i);return}(s,e,i,t,n),enqueueUpdate:t=>{setTimeout(()=>i.update(t),0)}});return s}function E(t,e){if(e===a)return t;const i=e,n=i.indexOf(".");if(-1===n)return t.fields[i];{const e=i.slice(0,n),r=i.slice(n+".".length),s=t.fields[e];if(null==s)return;return E(s,r)}}function U(t){if(t===a)return;const e=t.lastIndexOf(".");return-1===e?a:t.slice(0,e)}function k(t,e){const i=U(e);if(null!=i)return E(t,i)}let T=0,P=0,$=0;setInterval(()=>{console.log(`Total updates: ${T}, handlers: ${$}, handler calls: ${P}`)},1e3);const D=new class{multiline(...t){const e=[];for(const i of t)e.push(i),e.push("\n");return e.splice(e.length-1,1),e}log(...t){console.log(...this.multiline("Reactway-Forms:",...t))}warn(...t){console.warn(...this.multiline("Reactway-Forms:",...t))}error(...t){console.error(...this.multiline("Reactway-Forms:",...t))}info(...t){console.info(...this.multiline("Reactway-Forms:",...t))}};exports.CancellationTokenImpl=l,exports.FormSelector=a,exports.FormsStores=u,exports.IdSeparator=".",exports.LOGGER_PREFIX="Reactway-Forms:",exports.StatusUpdater="status",exports.StatusUpdaterFactory=m,exports.Store=class{constructor(t,n){this.emitter=new e.TinyEmitter,this.handlers={},this.handlerIdsByFieldId={},this.count=0,this.fieldHandlersListener=t=>{const e=Object.keys(this.handlerIdsByFieldId);if(0===e.length)return;const i=[];t.map(t=>{const e=t.path.filter(t=>"fields"!==t).join(".");i.push(e)});const n=[];for(const t of e){if(n.includes(t))continue;i.some(e=>!!e.startsWith(t)&&"."===e.charAt(t.length))&&n.push(t)}const r=n.map(t=>this.handlerIdsByFieldId[t]).flatMap(t=>t);for(const e of r){const i=this.handlers[e];null!=i&&(P++,i(t))}},this.state=i(t(),()=>{}),this.updaters=null!=n?Object.assign({},n):b(),this.emitter.addListener(this.fieldHandlersListener)}get state(){return this._state}set state(t){var e;if(!0===window.debugState){const i=new Error;console.groupCollapsed("State being updated:",Object.assign({},t)),console.log(null===(e=i.stack)||void 0===e?void 0:e.split("\n")[3]),console.groupEnd()}this._state=t,this._helpers=R(this._state,{})}get helpers(){return this._helpers}getState(){return this.state}addListener(t,e){const i=`h${this.count++}`;if(this.handlers[i]=t,$++,null==e||0===e.length)return this.emitter.addListener(e=>{P++,t(e)});const n=[];for(const t of e){let e=this.handlerIdsByFieldId[t];if(null==e&&(e=[],this.handlerIdsByFieldId[t]=e),e.includes(i))continue;e.push(i);const r=()=>{if(null==this.handlerIdsByFieldId[t])return;const e=this.handlerIdsByFieldId[t],n=e.indexOf(i);-1!==n&&e.splice(n,1)};n.push(r)}return()=>{for(const t of n)t()}}update(t){const e=[];T++;const n=i(this.state,e=>{t(j(this.updaters,e,this,{}),e)},t=>{e.push(...t)});this.state!==n&&(this.state=n,this.emitter.emit(e))}},exports.ValidationUpdater="validation",exports.ValidationUpdaterFactory=f,exports.ValueUpdater="value",exports.ValueUpdaterFactory=v,exports.assertFieldIsDefined=x,exports.assertUpdaterIsDefined=function(t,e){if(null==t)throw new Error(`Updater '${e}' does not exist.`)},exports.constructFieldHelpers=c,exports.constructInputFieldHelpers=function(t,e){return Object.assign(Object.assign(Object.assign({},c(t,e)),e),{registerModifier:e=>i=>i.getUpdater("value").registerModifier(t,e),unregisterModifier:e=>i=>{i.getUpdater("value").unregisterModifier(t,e)}})},exports.constructStoreHelpers=R,exports.constructUpdateStoreHelpers=j,exports.constructValidatorHelpers=p,exports.formsLogger=D,exports.generateFieldId=function(t,e){return null==e?t:`${e}.${t}`},exports.getDefaultState=w,exports.getDefaultStatuses=O,exports.getDefaultUpdatersFactories=b,exports.getDefaultValidation=I,exports.getFieldNameFromId=y,exports.getFieldParentId=U,exports.getInitialInputData=function(t,e,i,n){return void 0===e&&(e=t),void 0===i&&(i=e),{defaultValue:t,initialValue:e,currentValue:i,transientValue:n,modifiers:{},modifiersOrder:[]}},exports.getInputValues=function(t,e,i,n){return void 0===e&&(e=t),void 0===i&&(i=e),{defaultValue:t,initialValue:e,currentValue:i,transientValue:n}},exports.isInputFieldData=S,exports.isPromise=V,exports.selectField=E,exports.selectFieldParent=k; | ||
function __awaiter(thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
} | ||
class CancellationTokenImpl { | ||
constructor(cancellationCallback, tokenName) { | ||
this.tokenName = tokenName; | ||
this.cancelled = false; | ||
this.cancellationCallback = cancellationCallback; | ||
} | ||
get cancellationRequested() { | ||
return this.cancelled; | ||
} | ||
cancel() { | ||
var _a; | ||
if (this.cancelled) { | ||
return; | ||
} | ||
this.cancelled = true; | ||
(_a = this.cancellationCallback) === null || _a === void 0 ? void 0 : _a.call(this); | ||
} | ||
} | ||
class StoresRegistry extends tinyEmitter.TinyEmitter { | ||
constructor() { | ||
super(...arguments); | ||
this.storesRegistry = {}; | ||
} | ||
registerStore(formId, store) { | ||
if (this.storesRegistry[formId] != null) { | ||
throw new Error(`Form with formId "${formId}" is already registered.`); | ||
} | ||
this.storesRegistry[formId] = store; | ||
this.emit(); | ||
} | ||
unregisterStore(formId) { | ||
this.storesRegistry[formId] = undefined; | ||
this.emit(); | ||
} | ||
getStore(formId) { | ||
return this.storesRegistry[formId]; | ||
} | ||
getStoresIds() { | ||
return Object.keys(this.storesRegistry); | ||
} | ||
} | ||
class FormsStoresClass { | ||
setFormStoresHandler(newHandler, disposeOldOne = true) { | ||
if (disposeOldOne) { | ||
if (this.instance != null) { | ||
delete this.instance; | ||
} | ||
} | ||
this.instance = newHandler; | ||
} | ||
get registry() { | ||
var _a; | ||
this.instance = (_a = this.instance) !== null && _a !== void 0 ? _a : new StoresRegistry(); | ||
return this.instance; | ||
} | ||
} | ||
const FormsStores = new FormsStoresClass(); | ||
function constructFieldHelpers(fieldSelector, orderGuards) { | ||
return { | ||
registerValidator: validator => registerHelpers => { | ||
const validationUpdater = registerHelpers.getUpdater("validation"); | ||
return validationUpdater.registerValidator(fieldSelector, validator); | ||
}, | ||
unregisterValidator: validator => unregisterHelpers => { | ||
const validationUpdater = unregisterHelpers.getUpdater("validation"); | ||
validationUpdater.unregisterValidator(fieldSelector, validator); | ||
}, | ||
reportValidatorIndex: orderGuards.reportValidatorIndex | ||
}; | ||
} | ||
function constructInputFieldHelpers(fieldId, orderGuards) { | ||
return Object.assign(Object.assign(Object.assign({}, constructFieldHelpers(fieldId, orderGuards)), orderGuards), { registerModifier: modifier => registerHelpers => { | ||
const valueUpdater = registerHelpers.getUpdater("value"); | ||
return valueUpdater.registerModifier(fieldId, modifier); | ||
}, unregisterModifier: modifierId => unregisterHelpers => { | ||
const valueUpdater = unregisterHelpers.getUpdater("value"); | ||
valueUpdater.unregisterModifier(fieldId, modifierId); | ||
} }); | ||
} | ||
function ValidationUpdaterFactory(helpers, state, store) { | ||
return { | ||
id: "validation", | ||
validateField: (fieldSelector) => __awaiter(this, void 0, void 0, function* () { | ||
return validateField(helpers, state, store, fieldSelector); | ||
}), | ||
registerValidator: (fieldSelector, validator) => { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState, fieldSelector); | ||
const id = shortid.generate(); | ||
const mutableValidators = fieldState.validation.validators; | ||
mutableValidators[id] = Object.assign(Object.assign({}, validator), { id: id }); | ||
const mutableValidatorsOrder = fieldState.validation.validatorsOrder; | ||
mutableValidatorsOrder.push(id); | ||
fieldState.validation.results = []; | ||
return id; | ||
}, | ||
unregisterValidator: (fieldId, validatorId) => { | ||
const fieldState = helpers.selectField(fieldId); | ||
if (fieldState == null || fieldState.validation.validators[validatorId] == null) { | ||
// Gracefully return if fieldState or validator is not found, no need to throw. | ||
return; | ||
} | ||
const mutableValidators = fieldState.validation.validators; | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete mutableValidators[validatorId]; | ||
const validatorOrderIndex = fieldState.validation.validatorsOrder.findIndex(x => x === validatorId); | ||
if (validatorOrderIndex === -1) { | ||
return; | ||
} | ||
const mutableValidatorsOrder = fieldState.validation.validatorsOrder; | ||
mutableValidatorsOrder.splice(validatorOrderIndex, 1); | ||
fieldState.validation.results = []; | ||
}, | ||
setFormErrors: errors => { | ||
// TODO: Rename this updater. | ||
setFormErrors(state, errors); | ||
}, | ||
setFieldValidationResults: (fieldSelector, errors) => { | ||
setFieldValidationResults(helpers, fieldSelector, errors); | ||
}, | ||
resetFieldValidationResults: fieldSelector => { | ||
resetFieldValidationResults(helpers, fieldSelector); | ||
} | ||
}; | ||
} | ||
function validateField(helpers, _draft, store, fieldSelector) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState, fieldSelector); | ||
if (fieldState.validation.currentValidation != null) { | ||
fieldState.validation.currentValidation.cancellationToken.cancel(); | ||
} | ||
const validatorsKeys = Object.keys(fieldState.validation.validators); | ||
if (validatorsKeys.length === 0) { | ||
return; | ||
} | ||
// Copy validators because we're in an asynchronous context | ||
// and their proxy into current state might be revoked by immer. | ||
const validators = []; | ||
for (const validatorId of fieldState.validation.validatorsOrder) { | ||
const validator = fieldState.validation.validators[validatorId]; | ||
if (validator == null) { | ||
// TODO: Should we throw if the validator is not found? | ||
continue; | ||
} | ||
validators.push(Object.assign({}, validator)); | ||
} | ||
const fieldValue = fieldState.getValue(fieldState); | ||
// Indicate that the validation has started. | ||
const validationStarted = new Date(); | ||
const cancellationToken = new CancellationTokenImpl(() => { | ||
store.update(asyncHelpers => { | ||
const asyncFieldState = asyncHelpers.selectField(fieldSelector); | ||
if (asyncFieldState == null) { | ||
return; | ||
} | ||
const validation = asyncFieldState.validation; | ||
if (validation.currentValidation == null) { | ||
return; | ||
} | ||
// Start times are different, thus a new validation is already in progress. | ||
if (validation.currentValidation.started.getTime() !== validationStarted.getTime()) { | ||
return; | ||
} | ||
validation.currentValidation = undefined; | ||
}); | ||
}); | ||
// Set current validation and reset results synchronously | ||
// for other validations to not clutter the results and find the cancellation token. | ||
fieldState.validation.currentValidation = { | ||
started: validationStarted, | ||
cancellationToken: cancellationToken | ||
}; | ||
fieldState.validation.results = []; | ||
for (const validator of validators) { | ||
if (!validator.shouldValidate(fieldValue)) { | ||
continue; | ||
} | ||
// Check for cancellation before executing possibly costy validate function. | ||
if (cancellationToken.cancellationRequested) { | ||
return; | ||
} | ||
const validatorResult = validator.validate(fieldValue, constructValidatorHelpers(exports.ValidationResultOrigin.Validation, validator.name)); | ||
if (validatorResult == null) { | ||
continue; | ||
} | ||
// Gather results. | ||
let results = undefined; | ||
if (!isPromise(validatorResult)) { | ||
results = validatorResult; | ||
} | ||
else { | ||
results = yield validatorResult; | ||
} | ||
if (results.length === 0) { | ||
continue; | ||
} | ||
// Construct proper ValidationResults in case any strings are in results. | ||
const validationResults = results.map(x => resolveValidationResult(x, validator.name)); | ||
updateFieldAsync(fieldSelector, store, cancellationToken, state => { | ||
const mutableValidationResults = state.validation.results; | ||
mutableValidationResults.push(...validationResults); | ||
}); | ||
} | ||
updateFieldAsync(fieldSelector, store, cancellationToken, state => { | ||
state.validation.currentValidation = undefined; | ||
}); | ||
}); | ||
} | ||
function constructValidatorHelpers(origin, validatorName) { | ||
return { | ||
error: (message, code, data) => ({ | ||
type: exports.ValidationResultType.Error, | ||
message: message, | ||
code: code, | ||
origin: origin, | ||
validatorName: validatorName, | ||
data: data | ||
}), | ||
warning: (message, code, data) => ({ | ||
type: exports.ValidationResultType.Warning, | ||
message: message, | ||
code: code, | ||
origin: origin, | ||
validatorName: validatorName, | ||
data: data | ||
}) | ||
}; | ||
} | ||
function updateFieldAsync(fieldSelector, store, cancellationToken, updater) { | ||
// Check for cancellation right away. | ||
if (cancellationToken.cancellationRequested) { | ||
return; | ||
} | ||
setTimeout(() => { | ||
// Check for cancellation again at the start of asynchronous scope. | ||
if (cancellationToken.cancellationRequested) { | ||
return; | ||
} | ||
store.update(helpers => { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
if (fieldState == null) { | ||
return; | ||
} | ||
updater(fieldState); | ||
}); | ||
}, 0); | ||
} | ||
function resolveValidationResult(value, validatorName) { | ||
if (typeof value !== "string") { | ||
return value; | ||
} | ||
return { | ||
message: value, | ||
validatorName: validatorName, | ||
type: exports.ValidationResultType.Error, | ||
origin: exports.ValidationResultOrigin.Validation | ||
}; | ||
} | ||
function setFormErrors(state, errors) { | ||
for (const key of Object.keys(errors)) { | ||
const fieldError = errors[key]; | ||
const field = state.fields[key]; | ||
if (field == null || fieldError == null) { | ||
continue; | ||
} | ||
if (typeof fieldError === "object" && !Array.isArray(fieldError)) { | ||
setFormErrors(field, fieldError); | ||
continue; | ||
} | ||
const validationResults = []; | ||
for (const error of fieldError) { | ||
let validationResult; | ||
if (typeof error !== "string") { | ||
validationResult = error; | ||
} | ||
else { | ||
validationResult = { | ||
message: error, | ||
type: exports.ValidationResultType.Error, | ||
origin: exports.ValidationResultOrigin.FormSubmit | ||
}; | ||
} | ||
validationResults.push(validationResult); | ||
} | ||
field.validation.results = validationResults; | ||
} | ||
} | ||
function setFieldValidationResults(helpers, fieldSelector, errors) { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState); | ||
fieldState.validation.results = errors.map(result => typeof result !== "string" | ||
? result | ||
: { | ||
message: result, | ||
type: exports.ValidationResultType.Error | ||
}); | ||
} | ||
function resetFieldValidationResults(helpers, fieldSelector) { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState); | ||
fieldState.validation.results = []; | ||
} | ||
const formsLogger = Debug("@reactway:forms"); | ||
const logger = formsLogger.extend("value-updater"); | ||
function ValueUpdaterFactory(helpers) { | ||
const valueUpdater = { | ||
id: "value", | ||
updateFieldValue: (fieldId, value, selection) => { | ||
var _a, _b; | ||
const fieldState = helpers.selectField(fieldId); | ||
assertFieldIsDefined(fieldState, fieldId); | ||
if (!isInputFieldData(fieldState.data)) { | ||
// TODO: Review | ||
throw new Error("Not implemented."); | ||
} | ||
fieldState.validation.results = []; | ||
const modifiers = fieldState.data.modifiers; | ||
const modifiersKeys = Object.keys(modifiers); | ||
if (modifiersKeys.length === 0) { | ||
// No modifiers found, thus a value is set directly to currentValue. | ||
fieldState.data.currentValue = value; | ||
fieldState.data.selection = selection; | ||
helpers.updateFieldStatus(fieldId, status => { | ||
status.touched = true; | ||
status.pristine = value === fieldState.data.initialValue; | ||
}); | ||
return; | ||
} | ||
// Modifiers found, firing up the modifiers mechanism. | ||
const previousParseValue = { | ||
value: (_a = fieldState.data.transientValue) !== null && _a !== void 0 ? _a : fieldState.data.currentValue, | ||
caretPosition: (_b = fieldState.data.selection) === null || _b === void 0 ? void 0 : _b.selectionStart | ||
}; | ||
let newValue = value; | ||
let transientValue = undefined; | ||
let newCaretPosition = selection === null || selection === void 0 ? void 0 : selection.selectionStart; | ||
for (const modifierKey of modifiersKeys) { | ||
const modifier = modifiers[modifierKey]; | ||
if (modifier == null) { | ||
throw new Error("Should never happen."); | ||
} | ||
const newParseValue = { | ||
value: newValue, | ||
caretPosition: newCaretPosition | ||
}; | ||
const result = modifier.parse(newParseValue, previousParseValue); | ||
newValue = result.currentValue; | ||
// If modifier returned selection | ||
if (result.caretPosition != null) { | ||
// It becomes our newSelection. | ||
newCaretPosition = result.caretPosition; | ||
} | ||
// If transientValue was is null or was already set before | ||
if (transientValue != null || result.transientValue == null) { | ||
// Do nothing. | ||
continue; | ||
} | ||
// When first transientValue is encountered, it wins until it is resolved. | ||
transientValue = result.transientValue; | ||
} | ||
let newSelection = undefined; | ||
if (newCaretPosition != null) { | ||
newSelection = { | ||
selectionStart: newCaretPosition, | ||
selectionEnd: newCaretPosition, | ||
selectionDirection: "none" | ||
}; | ||
} | ||
fieldState.data.currentValue = newValue; | ||
fieldState.data.transientValue = transientValue; | ||
logger("Setting new selection to:", Object.assign({}, newSelection)); | ||
fieldState.data.selection = newSelection; | ||
}, | ||
resetFieldValue: fieldId => { | ||
const fieldState = helpers.selectField(fieldId); | ||
assertFieldIsDefined(fieldState, fieldId); | ||
if (!isInputFieldData(fieldState.data)) { | ||
throw new Error("Only input field can be reset."); | ||
} | ||
valueUpdater.updateFieldValue(fieldId, fieldState.data.initialValue); | ||
}, | ||
clearFieldValue: fieldId => { | ||
const fieldState = helpers.selectField(fieldId); | ||
assertFieldIsDefined(fieldState, fieldId); | ||
if (!isInputFieldData(fieldState.data)) { | ||
throw new Error("Only input field can be cleared."); | ||
} | ||
valueUpdater.updateFieldValue(fieldId, fieldState.data.defaultValue); | ||
}, | ||
registerModifier: (fieldId, modifier) => { | ||
const fieldState = helpers.selectField(fieldId); | ||
assertFieldIsDefined(fieldState, fieldId); | ||
if (!isInputFieldData(fieldState.data)) { | ||
throw new Error("Not implemented."); | ||
} | ||
const id = shortid.generate(); | ||
const modifiers = fieldState.data.modifiers; | ||
const mutableModifiers = modifiers; | ||
mutableModifiers[id] = Object.assign(Object.assign({}, modifier), { id }); | ||
return id; | ||
}, | ||
unregisterModifier: (fieldId, modifierId) => { | ||
const fieldState = helpers.selectField(fieldId); | ||
if (fieldState == null) { | ||
return; | ||
} | ||
if (!isInputFieldData(fieldState.data)) { | ||
throw new Error("Not implemented."); | ||
} | ||
const modifiers = fieldState.data.modifiers; | ||
if (modifiers[modifierId] == null) { | ||
// Gracefully return if the modifier is not found, no need to throw. | ||
return; | ||
} | ||
const modifierOrderIndex = fieldState.data.modifiersOrder.findIndex(x => x === modifierId); | ||
if (modifierOrderIndex === -1) { | ||
return; | ||
} | ||
const mutableModifiersOrder = fieldState.data.modifiersOrder; | ||
mutableModifiersOrder.splice(modifierOrderIndex, 1); | ||
} | ||
}; | ||
return valueUpdater; | ||
} | ||
function StatusUpdaterFactory(helpers, state) { | ||
return { | ||
id: "status", | ||
updateFieldStatus: (fieldSelector, updater) => { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState, fieldSelector); | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
const prevStatus = produce(fieldState.status, () => { }); | ||
let statusPatches; | ||
const newStatus = produce(prevStatus, status => { | ||
updater(status); | ||
}, patches => { | ||
statusPatches = patches; | ||
}); | ||
updater(fieldState.status); | ||
const statusChanged = (statusKey) => { | ||
return statusPatches.find(x => x.path.includes(statusKey)) != null; | ||
}; | ||
if (newStatus.touched && statusChanged("touched")) { | ||
updateDependentStatusUpwards(state, helpers, fieldSelector, status => { | ||
status.touched = true; | ||
}); | ||
} | ||
if (statusChanged("pristine")) { | ||
updateDependentStatusUpwards(state, helpers, fieldSelector, status => { | ||
status.pristine = newStatus.pristine; | ||
}); | ||
} | ||
if (statusChanged("readonly")) { | ||
updateDependentStatusDownwards(fieldState, status => { | ||
status.readonly = newStatus.readonly; | ||
}); | ||
} | ||
if (prevStatus === newStatus) { | ||
return; | ||
} | ||
} | ||
}; | ||
} | ||
function updateDependentStatusDownwards(fieldState, updater) { | ||
// TODO: Review | ||
for (const key of Object.keys(fieldState.fields)) { | ||
const child = fieldState.fields[key]; | ||
if (child == null) { | ||
continue; | ||
} | ||
updater(child.status); | ||
updateDependentStatusDownwards(child, updater); | ||
} | ||
} | ||
function updateDependentStatusUpwards(state, helpers, fieldSelector, updater) { | ||
const fieldState = helpers.selectField(fieldSelector); | ||
assertFieldIsDefined(fieldState, fieldSelector); | ||
// Update all parents (that have id) statuses. | ||
let parentId = fieldSelector; | ||
while ((parentId = helpers.getFieldParentId(parentId)) != null) { | ||
const field = helpers.selectField(parentId); | ||
assertFieldIsDefined(field, fieldSelector); | ||
updater(field.status); | ||
} | ||
// Update form (upper-most) status too. | ||
// TODO: Review if this is needed and/or FormSelector should be used. | ||
updater(state.status); | ||
} | ||
function isPromise(candidate) { | ||
return candidate.then != null && candidate.catch != null; | ||
} | ||
function generateFieldId(name, parentId) { | ||
if (parentId == null) { | ||
return name; | ||
} | ||
return `${parentId}${IdSeparator}${name}`; | ||
} | ||
function getFieldNameFromId(fieldId) { | ||
const lastSeparatorIndex = fieldId.lastIndexOf(IdSeparator); | ||
if (lastSeparatorIndex === -1) { | ||
return fieldId; | ||
} | ||
return fieldId.slice(lastSeparatorIndex + IdSeparator.length); | ||
} | ||
function assertFieldIsDefined(field, fieldSelector) { | ||
if (field == null) { | ||
throw new Error(`Field '${String(fieldSelector)}' does not exist in a given state.`); | ||
} | ||
} | ||
function assertUpdaterIsDefined(updater, updaterId) { | ||
if (updater == null) { | ||
throw new Error(`Updater '${updaterId}' does not exist.`); | ||
} | ||
} | ||
function getDefaultState() { | ||
return { | ||
fields: {}, | ||
status: getDefaultStatuses(), | ||
validation: getDefaultValidation() | ||
}; | ||
} | ||
function getDefaultStatuses() { | ||
return { | ||
disabled: false, | ||
pristine: true, | ||
touched: false, | ||
readonly: false, | ||
permanent: false | ||
}; | ||
} | ||
function getInputValues(defaultValue, initialValue, currentValue, transientValue) { | ||
// Triple equals to `undefined`, because `null` might be a valid value. | ||
if (initialValue === undefined) { | ||
initialValue = defaultValue; | ||
} | ||
// Triple equals to `undefined`, because `null` might be a valid value. | ||
if (currentValue === undefined) { | ||
currentValue = initialValue; | ||
} | ||
return { | ||
defaultValue, | ||
initialValue, | ||
currentValue, | ||
transientValue | ||
}; | ||
} | ||
function getInitialInputData(defaultValue, initialValue, currentValue, transientValue) { | ||
// Triple equals to `undefined`, because `null` might be a valid value. | ||
if (initialValue === undefined) { | ||
initialValue = defaultValue; | ||
} | ||
// Triple equals to `undefined`, because `null` might be a valid value. | ||
if (currentValue === undefined) { | ||
currentValue = initialValue; | ||
} | ||
return { | ||
defaultValue, | ||
initialValue, | ||
currentValue, | ||
transientValue, | ||
modifiers: {}, | ||
modifiersOrder: [] | ||
}; | ||
} | ||
function getDefaultUpdatersFactories() { | ||
return { | ||
validation: ValidationUpdaterFactory, | ||
value: ValueUpdaterFactory, | ||
status: StatusUpdaterFactory | ||
}; | ||
} | ||
function getDefaultValidation() { | ||
return { | ||
results: [], | ||
validators: {}, | ||
validatorsOrder: [] | ||
}; | ||
} | ||
function isInputFieldData(candidate) { | ||
const inputValues = candidate; | ||
return (inputValues.defaultValue !== undefined && | ||
inputValues.initialValue !== undefined && | ||
inputValues.currentValue !== undefined && | ||
inputValues.modifiers !== undefined && | ||
typeof inputValues.modifiers === "object" && | ||
Array.isArray(inputValues.modifiersOrder)); | ||
} | ||
function constructStoreHelpers(state, fieldsCache) { | ||
const cachedSelectField = fieldSelector => { | ||
if (fieldSelector === FormSelector) { | ||
return state; | ||
} | ||
const cachedField = fieldsCache[fieldSelector]; | ||
if (cachedField != null) { | ||
return cachedField; | ||
} | ||
const selectedField = selectField(state, fieldSelector); | ||
if (selectedField != null) { | ||
fieldsCache[fieldSelector] = selectedField; | ||
} | ||
return selectedField; | ||
}; | ||
const helpers = { | ||
selectField: cachedSelectField, | ||
selectFieldParent: fieldSelector => { | ||
const parentId = getFieldParentId(fieldSelector); | ||
if (parentId == null) { | ||
return undefined; | ||
} | ||
return cachedSelectField(parentId); | ||
}, | ||
getActiveFieldId: () => { | ||
const formState = state; | ||
return formState.data.activeFieldId; | ||
}, | ||
getFieldParentId: getFieldParentId, | ||
getFormValue: () => { | ||
return state.getValue(state); | ||
} | ||
}; | ||
return helpers; | ||
} | ||
function constructUpdateStoreHelpers(updaters, draft, store, fieldsCache) { | ||
const fieldStoreHelpers = constructStoreHelpers(draft, fieldsCache); | ||
const updateStoreHelpers = Object.assign(Object.assign({}, fieldStoreHelpers), { registerField: (fieldId, initialFieldState) => { | ||
registerField(draft, fieldId, initialFieldState); | ||
}, unregisterField: id => { | ||
unregisterField(draft, id); | ||
}, setActiveFieldId: fieldId => { | ||
const formState = draft; | ||
formState.data.activeFieldId = fieldId; | ||
}, updateFieldStatus: (fieldSelector, updater) => { | ||
const statusUpdater = updateStoreHelpers.getUpdater("status"); | ||
statusUpdater.updateFieldStatus(fieldSelector, updater); | ||
}, getUpdater: updaterId => { | ||
return getUpdater(updateStoreHelpers, draft, store, updaters, updaterId); | ||
}, enqueueUpdate: updater => { | ||
setTimeout(() => store.update(updater), 0); | ||
} }); | ||
return updateStoreHelpers; | ||
} | ||
function registerField(state, fieldId, initialFieldState) { | ||
if (initialFieldState.computedValue && isInputFieldData(initialFieldState.data)) { | ||
throw new Error(`Field ${fieldId} is marked to have computedValue, but also has data of an input field.`); | ||
} | ||
const fieldName = getFieldNameFromId(fieldId); | ||
const parentField = selectRegistrationParent(state, fieldId); | ||
if (parentField == null) { | ||
throw new Error(`Parent for field '${fieldId}' to be registered on was not found.`); | ||
} | ||
const fieldState = parentField.fields[fieldName]; | ||
if (fieldState != null && fieldState.status.permanent === false) { | ||
throw new Error(`Field '${fieldId}' has already been registered.`); | ||
} | ||
if (fieldState == null) { | ||
// Make fields non-read-only | ||
const mutableFields = parentField.fields; | ||
// Add field into the state. | ||
mutableFields[fieldName] = Object.assign(Object.assign(Object.assign({}, getDefaultState()), initialFieldState), { id: fieldId, name: fieldName, fields: {} }); | ||
} | ||
} | ||
function unregisterField(state, id) { | ||
const parentField = selectFieldParent(state, id); | ||
if (parentField == null) { | ||
return; | ||
} | ||
const fieldName = getFieldNameFromId(id); | ||
const field = parentField.fields[fieldName]; | ||
if (field == null) { | ||
return; | ||
} | ||
if (field.status.permanent) { | ||
return; | ||
} | ||
// Make fields non-read-only | ||
const mutableFields = parentField.fields; | ||
mutableFields[fieldName] = undefined; | ||
} | ||
function getUpdater(helpers, fieldState, store, updaters, updaterId) { | ||
const factory = updaters[updaterId]; | ||
if (factory != null) { | ||
return factory(helpers, fieldState, store); | ||
} | ||
return undefined; | ||
} | ||
function selectRegistrationParent(state, fieldId) { | ||
const separatorIndex = fieldId.indexOf(IdSeparator); | ||
if (separatorIndex === -1) { | ||
return state; | ||
} | ||
else { | ||
const firstChildName = fieldId.slice(0, separatorIndex); | ||
const nextChildId = fieldId.slice(separatorIndex + IdSeparator.length); | ||
// TODO: Review | ||
const child = state.fields[firstChildName]; | ||
if (child == null) { | ||
return undefined; | ||
} | ||
return selectRegistrationParent(child, nextChildId); | ||
} | ||
} | ||
// TODO: Do we need recursion here? | ||
function selectField(state, selector) { | ||
if (selector === FormSelector) { | ||
return state; | ||
} | ||
const fieldId = selector; | ||
const firstSeparatorIndex = fieldId.indexOf(IdSeparator); | ||
if (firstSeparatorIndex === -1) { | ||
return state.fields[fieldId]; | ||
} | ||
else { | ||
const name = fieldId.slice(0, firstSeparatorIndex); | ||
const nextFieldId = fieldId.slice(firstSeparatorIndex + IdSeparator.length); | ||
const child = state.fields[name]; | ||
if (child == null) { | ||
return undefined; | ||
} | ||
return selectField(child, nextFieldId); | ||
} | ||
} | ||
function getFieldParentId(fieldSelector) { | ||
if (fieldSelector === FormSelector) { | ||
return undefined; | ||
} | ||
const lastSeparatorIndex = fieldSelector.lastIndexOf(IdSeparator); | ||
if (lastSeparatorIndex === -1) { | ||
return FormSelector; | ||
} | ||
return fieldSelector.slice(0, lastSeparatorIndex); | ||
} | ||
function selectFieldParent(state, fieldId) { | ||
const parentId = getFieldParentId(fieldId); | ||
if (parentId == null) { | ||
return undefined; | ||
} | ||
return selectField(state, parentId); | ||
} | ||
class Store { | ||
constructor(initialStateFactory, updatersFactories) { | ||
// super(); | ||
this.emitter = new tinyEmitter.TinyEmitter(); | ||
this.handlers = {}; | ||
this.handlerIdsByFieldId = {}; | ||
this.count = 0; | ||
this.fieldHandlersListener = (patches) => { | ||
const fieldsKeys = Object.keys(this.handlerIdsByFieldId); | ||
if (fieldsKeys.length === 0) { | ||
return; | ||
} | ||
// Strongly type the key to catch an error if it changes. | ||
const fieldsKey = "fields"; | ||
const paths = []; | ||
patches.map(patch => { | ||
const idPath = patch.path.filter(x => x !== fieldsKey).join(IdSeparator); | ||
paths.push(idPath); | ||
}); | ||
const fieldsToCall = []; | ||
for (const fieldKey of fieldsKeys) { | ||
// TODO: Is this check needed? | ||
if (fieldsToCall.includes(fieldKey)) { | ||
continue; | ||
} | ||
const fieldHandlersShouldBeCalled = paths.some(path => { | ||
if (!path.startsWith(fieldKey)) { | ||
return false; | ||
} | ||
return path.charAt(fieldKey.length) === IdSeparator; | ||
}); | ||
if (!fieldHandlersShouldBeCalled) { | ||
continue; | ||
} | ||
fieldsToCall.push(fieldKey); | ||
} | ||
const handlerIds = fieldsToCall | ||
.map(fieldId => { | ||
// We've accumulated the ids from this object, thus they exist. | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
return this.handlerIdsByFieldId[fieldId]; | ||
}) | ||
.flatMap(x => x); | ||
for (const handlerId of handlerIds) { | ||
const handler = this.handlers[handlerId]; | ||
if (handler == null) { | ||
continue; | ||
} | ||
handler(patches); | ||
} | ||
}; | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
this.state = produce(initialStateFactory(), () => { }); | ||
this.updaters = updatersFactories != null ? Object.assign({}, updatersFactories) : getDefaultUpdatersFactories(); | ||
this.emitter.addListener(this.fieldHandlersListener); | ||
} | ||
get state() { | ||
return this._state; | ||
} | ||
set state(value) { | ||
var _a; | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
if (window.debugState === true) { | ||
const err = new Error(); | ||
/* eslint-disable no-console */ | ||
console.groupCollapsed("State being updated:", Object.assign({}, value)); | ||
console.log((_a = err.stack) === null || _a === void 0 ? void 0 : _a.split("\n")[3]); | ||
console.groupEnd(); | ||
/* eslint-enable no-console */ | ||
} | ||
this._state = value; | ||
this._helpers = constructStoreHelpers(this._state, {}); | ||
} | ||
get helpers() { | ||
return this._helpers; | ||
} | ||
getState() { | ||
return this.state; | ||
} | ||
addListener(handler, dependentFields) { | ||
const handlerId = `h${this.count++}`; | ||
this.handlers[handlerId] = handler; | ||
if (dependentFields == null || dependentFields.length === 0) { | ||
return this.emitter.addListener(patches => { | ||
handler(patches); | ||
}); | ||
} | ||
const unregisterCallbacks = []; | ||
for (const fieldId of dependentFields) { | ||
let handlerIds = this.handlerIdsByFieldId[fieldId]; | ||
if (handlerIds == null) { | ||
handlerIds = []; | ||
this.handlerIdsByFieldId[fieldId] = handlerIds; | ||
} | ||
if (handlerIds.includes(handlerId)) { | ||
continue; | ||
} | ||
// Register callback | ||
handlerIds.push(handlerId); | ||
const unregisterCallback = () => { | ||
if (this.handlerIdsByFieldId[fieldId] == null) { | ||
return; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, no-shadow | ||
const handlerIds = this.handlerIdsByFieldId[fieldId]; | ||
const index = handlerIds.indexOf(handlerId); | ||
if (index === -1) { | ||
return; | ||
} | ||
handlerIds.splice(index, 1); | ||
}; | ||
unregisterCallbacks.push(unregisterCallback); | ||
} | ||
return () => { | ||
for (const callback of unregisterCallbacks) { | ||
callback(); | ||
} | ||
}; | ||
} | ||
update(updater) { | ||
const patches = []; | ||
const newState = produce(this.state, draft => { | ||
updater(constructUpdateStoreHelpers(this.updaters, draft, this, {}), draft); | ||
}, updatePatches => { | ||
patches.push(...updatePatches); | ||
}); | ||
if (this.state === newState) { | ||
return; | ||
} | ||
this.state = newState; | ||
this.emitter.emit(patches); | ||
} | ||
} | ||
exports.CancellationTokenImpl = CancellationTokenImpl; | ||
exports.FormSelector = FormSelector; | ||
exports.FormsStores = FormsStores; | ||
exports.IdSeparator = IdSeparator; | ||
exports.StatusUpdater = StatusUpdater; | ||
exports.StatusUpdaterFactory = StatusUpdaterFactory; | ||
exports.Store = Store; | ||
exports.ValidationUpdater = ValidationUpdater; | ||
exports.ValidationUpdaterFactory = ValidationUpdaterFactory; | ||
exports.ValueUpdater = ValueUpdater; | ||
exports.ValueUpdaterFactory = ValueUpdaterFactory; | ||
exports.assertFieldIsDefined = assertFieldIsDefined; | ||
exports.assertUpdaterIsDefined = assertUpdaterIsDefined; | ||
exports.constructFieldHelpers = constructFieldHelpers; | ||
exports.constructInputFieldHelpers = constructInputFieldHelpers; | ||
exports.constructStoreHelpers = constructStoreHelpers; | ||
exports.constructUpdateStoreHelpers = constructUpdateStoreHelpers; | ||
exports.constructValidatorHelpers = constructValidatorHelpers; | ||
exports.formsLogger = formsLogger; | ||
exports.generateFieldId = generateFieldId; | ||
exports.getDefaultState = getDefaultState; | ||
exports.getDefaultStatuses = getDefaultStatuses; | ||
exports.getDefaultUpdatersFactories = getDefaultUpdatersFactories; | ||
exports.getDefaultValidation = getDefaultValidation; | ||
exports.getFieldNameFromId = getFieldNameFromId; | ||
exports.getFieldParentId = getFieldParentId; | ||
exports.getInitialInputData = getInitialInputData; | ||
exports.getInputValues = getInputValues; | ||
exports.isInputFieldData = isInputFieldData; | ||
exports.isPromise = isPromise; | ||
exports.selectField = selectField; | ||
exports.selectFieldParent = selectFieldParent; |
@@ -1,11 +0,2 @@ | ||
export declare const LOGGER_PREFIX = "Reactway-Forms:"; | ||
declare class Logger { | ||
protected multiline(...args: any[]): any[]; | ||
log(...args: any[]): void; | ||
warn(...args: any[]): void; | ||
error(...args: any[]): void; | ||
info(...args: any[]): void; | ||
} | ||
export declare const formsLogger: Logger; | ||
export {}; | ||
//# sourceMappingURL=logger.d.ts.map | ||
import Debug from "debug"; | ||
export declare const formsLogger: Debug.Debugger; |
@@ -22,2 +22,1 @@ import { Draft, Patch } from "immer"; | ||
} | ||
//# sourceMappingURL=store.d.ts.map |
export * from "./validation-updater"; | ||
export * from "./value-updater"; | ||
export * from "./status-updater"; | ||
//# sourceMappingURL=index.d.ts.map |
import { StatusUpdater, FieldState, UpdateStoreHelpers } from "../contracts"; | ||
export declare function StatusUpdaterFactory(helpers: UpdateStoreHelpers, state: FieldState<any, any>): StatusUpdater; | ||
//# sourceMappingURL=status-updater.d.ts.map |
@@ -6,2 +6,1 @@ import { FieldState, UpdateStoreHelpers, ValidationUpdater, ValidationResultOrigin, ValidatorHelpers } from "../contracts"; | ||
export declare function constructValidatorHelpers(origin: ValidationResultOrigin.Validation | ValidationResultOrigin.Unknown, validatorName: string): ValidatorHelpers; | ||
//# sourceMappingURL=validation-updater.d.ts.map |
import { ValueUpdater, UpdateStoreHelpers } from "../contracts"; | ||
export declare function ValueUpdaterFactory(helpers: UpdateStoreHelpers): ValueUpdater; | ||
//# sourceMappingURL=value-updater.d.ts.map |
{ | ||
"name": "@reactway/forms-core", | ||
"version": "0.0.0-canary.585eb1d", | ||
"description": "React forms.", | ||
"main": "dist/index.js", | ||
"module": "dist/index.es.js", | ||
"types": "dist/index.d.ts", | ||
"scripts": { | ||
"build": "tsc -b .", | ||
"test": "eslint \"src/**/*.ts*\"", | ||
"build:prod": "rollup -c && tsc -p . --emitDeclarationOnly", | ||
"watch": "tsc -b . -w" | ||
}, | ||
"version": "0.0.0-canary.6b99618", | ||
"author": "Reactway <dev@reactway.com> (https://github.com/reactway)", | ||
"license": "MIT", | ||
"files": [ | ||
"dist", | ||
"*.js", | ||
"*.d.ts", | ||
"!*.config.js", | ||
"!dist/_debug*" | ||
], | ||
"dependencies": { | ||
"@reactway/tiny-emitter": "^1.0.2", | ||
"@types/shortid": "^0.0.29", | ||
"debug": "^4.1.1", | ||
"immer": "^5.2.1", | ||
@@ -30,9 +14,27 @@ "shortid": "^2.2.15" | ||
"devDependencies": { | ||
"@reactway-tools/rollup": "^0.0.0-canary.585eb1d", | ||
"@reactway/eslint-config": "1.0.1", | ||
"eslint": "6.8.0", | ||
"prettier": "1.19.1", | ||
"@reactway-tools/rollup": "^0.0.0-canary.6b99618", | ||
"@reactway/eslint-config": "^1.0.1", | ||
"@types/debug": "^4.1.5", | ||
"eslint": "^6.8.0", | ||
"prettier": "^1.19.1", | ||
"rollup": "1.32.0", | ||
"typescript": "3.7.5" | ||
} | ||
"typescript": "3.8.3" | ||
}, | ||
"files": [ | ||
"!*.config.js", | ||
"!dist/_debug*", | ||
"*.d.ts", | ||
"*.js", | ||
"dist" | ||
], | ||
"license": "MIT", | ||
"main": "dist/index.js", | ||
"module": "dist/index.es.js", | ||
"scripts": { | ||
"build": "tsc -b .", | ||
"build:prod": "rollup -c && tsc -p . --emitDeclarationOnly --declarationMap false", | ||
"test": "eslint \"src/**/*.ts*\" --max-warnings 0", | ||
"watch": "tsc -b . -w" | ||
}, | ||
"types": "dist/index.d.ts" | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
94613
2212
2
5
7
28
1
+ Addeddebug@^4.1.1
+ Addeddebug@4.4.0(transitive)
+ Addedms@2.1.3(transitive)