@@ -3,31 +3,71 @@ import { produce } from "immer";
3
3
4
4
export const SEPARATOR = "." ;
5
5
6
+ interface Status {
7
+ isFocused : boolean ;
8
+ isTouched : boolean ;
9
+ }
10
+
6
11
interface Field {
7
12
id : string ;
8
13
name : string ;
9
14
10
- defaultValue ? : string ;
11
- initialValue ? : string ;
15
+ defaultValue : string ;
16
+ initialValue : string ;
12
17
currentValue : string ;
13
18
14
- isFocused : boolean ;
15
- isTouched : boolean ;
19
+ status : Status ;
16
20
}
17
21
18
22
type Fields = {
19
23
[ key : string ] : Field | undefined ;
20
24
} ;
21
25
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 } ) ;
24
32
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
+ }
27
55
}
28
56
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
+
29
69
public getField ( fieldId : string ) : Field | undefined {
30
- return this . fields [ fieldId ] ;
70
+ return this . state . fields [ fieldId ] ;
31
71
}
32
72
33
73
public generateFieldId ( name : string , groupId : string ) : string {
@@ -38,56 +78,60 @@ export class GroupStoreMutable extends TinyEmitter<Callback> {
38
78
name : string ,
39
79
groupId : string ,
40
80
defaultValue : string ,
41
- initialValue : string
81
+ initialValue ? : string
42
82
) : void {
43
83
const id = this . generateFieldId ( name , groupId ) ;
44
84
45
- if ( this . fields [ id ] != null ) {
85
+ if ( this . state . fields [ id ] != null ) {
46
86
throw new Error ( `Field with an id '${ id } ' is already registered.` ) ;
47
87
}
48
88
89
+ if ( initialValue == null ) {
90
+ initialValue = defaultValue ;
91
+ }
92
+
49
93
const newField : Field = {
50
94
id : id ,
51
95
name : name ,
52
96
defaultValue : defaultValue ,
53
97
initialValue : initialValue ,
54
- currentValue : initialValue || defaultValue ,
55
- isFocused : false ,
56
- isTouched : false
98
+ currentValue : initialValue ,
99
+ status : defaultStatus ( )
57
100
} ;
58
101
59
- this . fields = produce ( this . fields , fields => {
60
- fields [ id ] = newField ;
102
+ this . state = produce ( this . state , state => {
103
+ state . fields [ id ] = newField ;
61
104
} ) ;
62
105
this . emit ( ) ;
63
106
}
64
107
65
108
public unregisterField ( id : string ) : void {
66
- if ( this . fields [ id ] == null ) {
109
+ if ( this . state . fields [ id ] == null ) {
67
110
return ;
68
111
}
69
112
70
- this . fields = produce ( this . fields , fields => {
71
- fields [ id ] = undefined ;
113
+ this . state = produce ( this . state , state => {
114
+ state . fields [ id ] = undefined ;
72
115
} ) ;
73
116
this . emit ( ) ;
74
117
}
75
118
76
119
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 ) {
78
122
throw new Error (
79
123
`Cannot update non-existent field value. (field id '${ fieldId } ').`
80
124
) ;
81
125
}
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 ] ;
84
128
if ( field == null ) {
85
129
return ;
86
130
}
87
131
88
132
// Value equality check should be abstracted for particular component and value type
89
133
if ( field . currentValue !== value ) {
90
- field . isTouched = true ;
134
+ field . status . isTouched = true ;
91
135
}
92
136
field . currentValue = value ;
93
137
} ) ;
@@ -96,41 +140,69 @@ export class GroupStoreMutable extends TinyEmitter<Callback> {
96
140
}
97
141
98
142
public focus ( fieldId : string ) : void {
143
+ console . log ( `Focus ${ fieldId } ` ) ;
99
144
this . setFocused ( fieldId , true ) ;
100
145
this . emit ( ) ;
101
146
}
102
147
103
148
public blur ( fieldId : string ) : void {
149
+ console . log ( `Blur ${ fieldId } ` ) ;
104
150
this . setFocused ( fieldId , false ) ;
105
151
this . emit ( ) ;
106
152
}
107
153
108
154
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 ) {
111
156
throw new Error (
112
157
`Cannot update non-existent field value. (field id '${ fieldId } ').`
113
158
) ;
114
159
}
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 ) {
118
164
return ;
119
165
}
120
- field . isFocused = isFocused ;
166
+
167
+ field . status . isFocused = isFocused ;
121
168
122
169
// 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 ( ) ;
125
192
}
193
+
194
+ // Reset group status
195
+ state . status = defaultStatus ( ) ;
126
196
} ) ;
197
+ this . emit ( ) ;
127
198
}
128
199
129
200
public toObject ( ) : unknown {
130
201
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 ] ;
134
206
if ( field == null ) {
135
207
continue ;
136
208
}
0 commit comments