@@ -3,31 +3,71 @@ import { produce } from "immer";
33
44export const SEPARATOR = "." ;
55
6+ interface Status {
7+ isFocused : boolean ;
8+ isTouched : boolean ;
9+ }
10+
611interface Field {
712 id : string ;
813 name : string ;
914
10- defaultValue ? : string ;
11- initialValue ? : string ;
15+ defaultValue : string ;
16+ initialValue : string ;
1217 currentValue : string ;
1318
14- isFocused : boolean ;
15- isTouched : boolean ;
19+ status : Status ;
1620}
1721
1822type Fields = {
1923 [ key : string ] : Field | undefined ;
2024} ;
2125
22- export class GroupStoreMutable extends TinyEmitter < Callback > {
23- protected fields : Fields = { } ;
26+ interface GroupState {
27+ fields : Fields ;
28+ status : Status ;
29+ }
30+
31+ const defaultStatus = ( ) : Status => ( { isFocused : false , isTouched : false } ) ;
2432
25- constructor ( ) {
26- super ( ) ;
33+ const calculateGroupStatus = ( state : GroupState ) : Status => {
34+ let isFocused = false ;
35+ let isTouched = false ;
36+
37+ for ( const key of Object . keys ( state . fields ) ) {
38+ const field = state . fields [ key ] ;
39+ if ( field == null ) {
40+ continue ;
41+ }
42+
43+ if ( field . status . isFocused ) {
44+ isFocused = true ;
45+ }
46+ if ( field . status . isTouched ) {
47+ isTouched = true ;
48+ }
49+
50+ // If group is both focused and touched already, nothing is going to change anymore
51+ if ( isFocused && isTouched ) {
52+ // Thus, break.
53+ break ;
54+ }
2755 }
2856
57+ return {
58+ isFocused : isFocused ,
59+ isTouched : isTouched
60+ } ;
61+ } ;
62+
63+ export class GroupStoreMutable extends TinyEmitter < Callback > {
64+ protected state : GroupState = {
65+ fields : { } ,
66+ status : defaultStatus ( )
67+ } ;
68+
2969 public getField ( fieldId : string ) : Field | undefined {
30- return this . fields [ fieldId ] ;
70+ return this . state . fields [ fieldId ] ;
3171 }
3272
3373 public generateFieldId ( name : string , groupId : string ) : string {
@@ -38,56 +78,60 @@ export class GroupStoreMutable extends TinyEmitter<Callback> {
3878 name : string ,
3979 groupId : string ,
4080 defaultValue : string ,
41- initialValue : string
81+ initialValue ? : string
4282 ) : void {
4383 const id = this . generateFieldId ( name , groupId ) ;
4484
45- if ( this . fields [ id ] != null ) {
85+ if ( this . state . fields [ id ] != null ) {
4686 throw new Error ( `Field with an id '${ id } ' is already registered.` ) ;
4787 }
4888
89+ if ( initialValue == null ) {
90+ initialValue = defaultValue ;
91+ }
92+
4993 const newField : Field = {
5094 id : id ,
5195 name : name ,
5296 defaultValue : defaultValue ,
5397 initialValue : initialValue ,
54- currentValue : initialValue || defaultValue ,
55- isFocused : false ,
56- isTouched : false
98+ currentValue : initialValue ,
99+ status : defaultStatus ( )
57100 } ;
58101
59- this . fields = produce ( this . fields , fields => {
60- fields [ id ] = newField ;
102+ this . state = produce ( this . state , state => {
103+ state . fields [ id ] = newField ;
61104 } ) ;
62105 this . emit ( ) ;
63106 }
64107
65108 public unregisterField ( id : string ) : void {
66- if ( this . fields [ id ] == null ) {
109+ if ( this . state . fields [ id ] == null ) {
67110 return ;
68111 }
69112
70- this . fields = produce ( this . fields , fields => {
71- fields [ id ] = undefined ;
113+ this . state = produce ( this . state , state => {
114+ state . fields [ id ] = undefined ;
72115 } ) ;
73116 this . emit ( ) ;
74117 }
75118
76119 public updateValue ( fieldId : string , value : string ) : void {
77- if ( this . fields [ fieldId ] == null ) {
120+ console . log ( `Value updated ${ fieldId } ` ) ;
121+ if ( this . state . fields [ fieldId ] == null ) {
78122 throw new Error (
79123 `Cannot update non-existent field value. (field id '${ fieldId } ').`
80124 ) ;
81125 }
82- this . fields = produce ( this . fields , fields => {
83- const field = fields [ fieldId ] ;
126+ this . state = produce ( this . state , state => {
127+ const field = state . fields [ fieldId ] ;
84128 if ( field == null ) {
85129 return ;
86130 }
87131
88132 // Value equality check should be abstracted for particular component and value type
89133 if ( field . currentValue !== value ) {
90- field . isTouched = true ;
134+ field . status . isTouched = true ;
91135 }
92136 field . currentValue = value ;
93137 } ) ;
@@ -96,41 +140,69 @@ export class GroupStoreMutable extends TinyEmitter<Callback> {
96140 }
97141
98142 public focus ( fieldId : string ) : void {
143+ console . log ( `Focus ${ fieldId } ` ) ;
99144 this . setFocused ( fieldId , true ) ;
100145 this . emit ( ) ;
101146 }
102147
103148 public blur ( fieldId : string ) : void {
149+ console . log ( `Blur ${ fieldId } ` ) ;
104150 this . setFocused ( fieldId , false ) ;
105151 this . emit ( ) ;
106152 }
107153
108154 private setFocused ( fieldId : string , isFocused : boolean ) : void {
109- console . log ( `Setting focus for field '${ fieldId } ' to ${ isFocused } ` ) ;
110- if ( this . fields [ fieldId ] == null ) {
155+ if ( this . state . fields [ fieldId ] == null ) {
111156 throw new Error (
112157 `Cannot update non-existent field value. (field id '${ fieldId } ').`
113158 ) ;
114159 }
115- this . fields = produce ( this . fields , fields => {
116- const field = fields [ fieldId ] ;
117- if ( field == null ) {
160+
161+ this . state = produce ( this . state , state => {
162+ const field = state . fields [ fieldId ] ;
163+ if ( field == null || field . status . isFocused === isFocused ) {
118164 return ;
119165 }
120- field . isFocused = isFocused ;
166+
167+ field . status . isFocused = isFocused ;
121168
122169 // If the field is not touched yet and got focused, make it touched.
123- if ( ! field . isTouched && isFocused ) {
124- field . isTouched = true ;
170+ if ( ! field . status . isTouched && isFocused ) {
171+ field . status . isTouched = true ;
172+ }
173+
174+ // Recalculate group status, because field status has changed
175+ state . status = calculateGroupStatus ( state ) ;
176+ } ) ;
177+ }
178+
179+ public reset ( ) : void {
180+ console . log ( `Reset state` ) ;
181+ this . state = produce ( this . state , state => {
182+ // Reset fields
183+ const fields = state . fields ;
184+ for ( const key of Object . keys ( fields ) ) {
185+ const field = fields [ key ] ;
186+ if ( field == null ) {
187+ continue ;
188+ }
189+
190+ field . currentValue = field . initialValue ;
191+ field . status = defaultStatus ( ) ;
125192 }
193+
194+ // Reset group status
195+ state . status = defaultStatus ( ) ;
126196 } ) ;
197+ this . emit ( ) ;
127198 }
128199
129200 public toObject ( ) : unknown {
130201 const result : { [ key : string ] : unknown } = { } ;
131- for ( const key in this . fields ) {
132- if ( this . fields . hasOwnProperty ( key ) ) {
133- const field = this . fields [ key ] ;
202+ const fields = this . state . fields ;
203+ for ( const key in fields ) {
204+ if ( fields . hasOwnProperty ( key ) ) {
205+ const field = fields [ key ] ;
134206 if ( field == null ) {
135207 continue ;
136208 }
0 commit comments