@servicetitan/form 22.1.3 → 22.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/date-range-picker/date-range-picker.d.ts +1 -1
- package/dist/date-range-picker/date-range-picker.d.ts.map +1 -1
- package/dist/date-range-picker/date-range-picker.js.map +1 -1
- package/dist/demo/date-range-picker.js.map +1 -1
- package/dist/demo/index.d.ts +0 -1
- package/dist/demo/index.d.ts.map +1 -1
- package/dist/demo/index.js +0 -1
- package/dist/demo/index.js.map +1 -1
- package/dist/demo/input-date-mask.d.ts.map +1 -1
- package/dist/demo/input-date-mask.js.map +1 -1
- package/dist/form-state-error-banner/form-state-error-banner.js +1 -1
- package/dist/form-state-error-banner/form-state-error-banner.js.map +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/package.json +14 -15
- package/src/date-range-picker/date-range-picker.tsx +1 -1
- package/src/demo/date-range-picker.tsx +1 -1
- package/src/demo/index.ts +0 -1
- package/src/demo/input-date-mask.tsx +0 -1
- package/src/form-state-error-banner/form-state-error-banner.tsx +1 -1
- package/src/index.ts +2 -4
- package/dist/date-range.d.ts +0 -5
- package/dist/date-range.d.ts.map +0 -1
- package/dist/date-range.js +0 -2
- package/dist/date-range.js.map +0 -1
- package/dist/demo/dropdown-state.d.ts +0 -3
- package/dist/demo/dropdown-state.d.ts.map +0 -1
- package/dist/demo/dropdown-state.js +0 -133
- package/dist/demo/dropdown-state.js.map +0 -1
- package/dist/dropdown-state.d.ts +0 -42
- package/dist/dropdown-state.d.ts.map +0 -1
- package/dist/dropdown-state.js +0 -314
- package/dist/dropdown-state.js.map +0 -1
- package/dist/form-helpers.d.ts +0 -70
- package/dist/form-helpers.d.ts.map +0 -1
- package/dist/form-helpers.js +0 -232
- package/dist/form-helpers.js.map +0 -1
- package/dist/form-validators.d.ts +0 -30
- package/dist/form-validators.d.ts.map +0 -1
- package/dist/form-validators.js +0 -56
- package/dist/form-validators.js.map +0 -1
- package/dist/persistent-form-state/domain-storage.d.ts +0 -14
- package/dist/persistent-form-state/domain-storage.d.ts.map +0 -1
- package/dist/persistent-form-state/domain-storage.js +0 -42
- package/dist/persistent-form-state/domain-storage.js.map +0 -1
- package/dist/persistent-form-state/in-memory-storage.d.ts +0 -13
- package/dist/persistent-form-state/in-memory-storage.d.ts.map +0 -1
- package/dist/persistent-form-state/in-memory-storage.js +0 -30
- package/dist/persistent-form-state/in-memory-storage.js.map +0 -1
- package/dist/persistent-form-state/index.d.ts +0 -2
- package/dist/persistent-form-state/index.d.ts.map +0 -1
- package/dist/persistent-form-state/index.js +0 -2
- package/dist/persistent-form-state/index.js.map +0 -1
- package/dist/persistent-form-state/persistent-form-state.d.ts +0 -18
- package/dist/persistent-form-state/persistent-form-state.d.ts.map +0 -1
- package/dist/persistent-form-state/persistent-form-state.js +0 -93
- package/dist/persistent-form-state/persistent-form-state.js.map +0 -1
- package/src/__tests__/__snapshots__/form-helpers.test.ts.snap +0 -37
- package/src/__tests__/form-helpers.test.ts +0 -229
- package/src/__tests__/form-validators.test.ts +0 -55
- package/src/date-range.ts +0 -4
- package/src/demo/dropdown-state.tsx +0 -233
- package/src/dropdown-state.ts +0 -205
- package/src/form-helpers.ts +0 -259
- package/src/form-validators.ts +0 -106
- package/src/persistent-form-state/__tests__/domain-storage.test.ts +0 -81
- package/src/persistent-form-state/domain-storage.ts +0 -43
- package/src/persistent-form-state/in-memory-storage.ts +0 -32
- package/src/persistent-form-state/index.ts +0 -1
- package/src/persistent-form-state/persistent-form-state.ts +0 -68
package/src/form-helpers.ts
DELETED
@@ -1,259 +0,0 @@
|
|
1
|
-
import { SyntheticEvent, ChangeEvent, Key, ReactNode } from 'react';
|
2
|
-
import { FieldState, FormState, ValidatableMapOrArray, ComposibleValidatable } from 'formstate';
|
3
|
-
import { toJS, isObservableMap, runInAction, transaction, computed, comparer } from 'mobx';
|
4
|
-
|
5
|
-
export class CheckboxFieldState extends FieldState<boolean> {
|
6
|
-
onChangeHandler = (_0: any, checked: boolean) => {
|
7
|
-
this.onChange(checked);
|
8
|
-
};
|
9
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
10
|
-
DEPRECATED_onChangeHandler = (
|
11
|
-
_0: SyntheticEvent<HTMLInputElement>,
|
12
|
-
data: { checked?: boolean }
|
13
|
-
) => {
|
14
|
-
this.onChange(!!data.checked);
|
15
|
-
};
|
16
|
-
}
|
17
|
-
|
18
|
-
export class InputFieldState<T> extends FieldState<T> {
|
19
|
-
onChangeHandler = (_0: SyntheticEvent<HTMLInputElement>, data: { value: T }) => {
|
20
|
-
this.onChange(data.value);
|
21
|
-
};
|
22
|
-
onChangeNativeHandler = (event: ChangeEvent<HTMLInputElement>) => {
|
23
|
-
this.onChange(event.currentTarget.value as unknown as T);
|
24
|
-
};
|
25
|
-
}
|
26
|
-
|
27
|
-
export class TextAreaFieldState<T> extends FieldState<T> {
|
28
|
-
/**
|
29
|
-
* react-semantic type for onChange event of TextArea component seems to have a bug with data object type
|
30
|
-
* so declaring data as any
|
31
|
-
*/
|
32
|
-
onChangeHandler = (_0: SyntheticEvent<HTMLTextAreaElement>, data: any) => {
|
33
|
-
this.onChange(data.value);
|
34
|
-
};
|
35
|
-
}
|
36
|
-
|
37
|
-
export class DropdownFieldState<T> extends FieldState<T> {
|
38
|
-
/**
|
39
|
-
* react-semantic type for onChange event of Dropdown component seems to have a bug with data object type
|
40
|
-
* so declaring data as any
|
41
|
-
*/
|
42
|
-
onChangeHandler = (_0: SyntheticEvent<HTMLElement>, data: any) => {
|
43
|
-
this.onChange(data.value);
|
44
|
-
};
|
45
|
-
}
|
46
|
-
|
47
|
-
export class DropdownSearchFieldState<T> extends FieldState<T> {
|
48
|
-
onChangeHandler = (_0: SyntheticEvent<HTMLElement>, data: { searchQuery: T }) => {
|
49
|
-
this.onChange(data.searchQuery);
|
50
|
-
};
|
51
|
-
}
|
52
|
-
|
53
|
-
export class DatetimeFieldState extends FieldState<Date | null> {
|
54
|
-
onChangeHandler = (event: { target: { value: Date | null } }) => {
|
55
|
-
this.onChange(event.target.value);
|
56
|
-
};
|
57
|
-
}
|
58
|
-
|
59
|
-
export interface Option<T> {
|
60
|
-
key: Key;
|
61
|
-
text: ReactNode;
|
62
|
-
value: T;
|
63
|
-
}
|
64
|
-
|
65
|
-
export function enumToOptions<T>(
|
66
|
-
enumObject: T,
|
67
|
-
nameProvider?: (value: T[keyof T]) => Option<T[keyof T]>['text']
|
68
|
-
): Option<T[keyof T]>[] {
|
69
|
-
return getEnumKeys(enumObject).map(k => ({
|
70
|
-
key: k as string,
|
71
|
-
text: nameProvider ? nameProvider(enumObject[k]) : k,
|
72
|
-
value: enumObject[k],
|
73
|
-
}));
|
74
|
-
}
|
75
|
-
|
76
|
-
export function getEnumKeys<T>(enumObject: T) {
|
77
|
-
let keys = Object.keys(enumObject) as (keyof T)[];
|
78
|
-
|
79
|
-
if (keys.some(k => typeof enumObject[k] === 'number')) {
|
80
|
-
keys = keys.filter(k => typeof enumObject[k] === 'number');
|
81
|
-
}
|
82
|
-
|
83
|
-
return keys;
|
84
|
-
}
|
85
|
-
|
86
|
-
export function getEnumValues<T>(enumObject: T) {
|
87
|
-
return getEnumKeys(enumObject).map(k => enumObject[k]);
|
88
|
-
}
|
89
|
-
|
90
|
-
export type FormValues = string | number | boolean | (string | number | boolean)[];
|
91
|
-
export interface FormStateAsJS {
|
92
|
-
[index: string]: FormValues | Map<any, any> | Set<any> | FormStateAsJS;
|
93
|
-
}
|
94
|
-
|
95
|
-
export function traverseFormState<T extends ValidatableMapOrArray>(
|
96
|
-
recursive: boolean,
|
97
|
-
formState: FormState<T>,
|
98
|
-
onFormVisit?: (key: string, form: FormState<any>) => void,
|
99
|
-
onFieldVisit?: (key: string, field: FieldState<any>) => void
|
100
|
-
) {
|
101
|
-
const visitChild = (key: string, child: ComposibleValidatable<any>) => {
|
102
|
-
if (child instanceof FormState) {
|
103
|
-
if (recursive) {
|
104
|
-
traverseFormState(recursive, child, onFormVisit, onFieldVisit);
|
105
|
-
}
|
106
|
-
if (onFormVisit) {
|
107
|
-
onFormVisit(key, child);
|
108
|
-
}
|
109
|
-
} else if (onFieldVisit) {
|
110
|
-
onFieldVisit(key, child as FieldState<any>);
|
111
|
-
}
|
112
|
-
};
|
113
|
-
|
114
|
-
if (Array.isArray(formState.$)) {
|
115
|
-
formState.$.forEach((child, index) => visitChild(index.toString(), child));
|
116
|
-
} else {
|
117
|
-
(isObservableMap(formState.$)
|
118
|
-
? Array.from(formState.$ as Map<any, ComposibleValidatable<any>>)
|
119
|
-
: Object.entries(formState.$ as Record<string, ComposibleValidatable<any>>)
|
120
|
-
).forEach(([key, child]) => visitChild(key, child));
|
121
|
-
}
|
122
|
-
}
|
123
|
-
|
124
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
125
|
-
export function BAD_formStateToJS<T extends ValidatableMapOrArray>(formState: FormState<T>) {
|
126
|
-
const formValues = {} as any;
|
127
|
-
traverseFormState(
|
128
|
-
false,
|
129
|
-
formState,
|
130
|
-
(key: any, form: FormState<any>) => (formValues[key] = BAD_formStateToJS(form)),
|
131
|
-
(key: any, field: FieldState<any>) => (formValues[key] = toJS(field.value))
|
132
|
-
);
|
133
|
-
return formValues as unknown as FormStateAsJS;
|
134
|
-
}
|
135
|
-
|
136
|
-
export type FormStateShape<T> = T extends FormState<infer U>[]
|
137
|
-
? FormStateMapOrObject<U>[]
|
138
|
-
: FormStateMapOrObject<T>;
|
139
|
-
|
140
|
-
type FormStateMapOrObject<T> = T extends Map<infer K, infer V>
|
141
|
-
? FormStateObject<Record<K & (string | number | symbol), V>>
|
142
|
-
: FormStateObject<T>;
|
143
|
-
|
144
|
-
type FormStateObject<T> = {
|
145
|
-
[P in keyof T]: T[P] extends FieldState<infer U>
|
146
|
-
? U
|
147
|
-
: T[P] extends FormState<infer U>
|
148
|
-
? FormStateShape<U>
|
149
|
-
: never;
|
150
|
-
};
|
151
|
-
|
152
|
-
export function formStateToJS<T extends ValidatableMapOrArray>(
|
153
|
-
formState: FormState<T>
|
154
|
-
): FormStateShape<T> {
|
155
|
-
const formValues = Array.isArray(formState.$) ? [] : ({} as any);
|
156
|
-
traverseFormState(
|
157
|
-
false,
|
158
|
-
formState,
|
159
|
-
(key: any, form: FormState<any>) => (formValues[key] = formStateToJS(form)),
|
160
|
-
(key: any, field: FieldState<any>) => (formValues[key] = toJS(field.$))
|
161
|
-
);
|
162
|
-
return formValues as unknown as FormStateShape<T>;
|
163
|
-
}
|
164
|
-
|
165
|
-
export type RecursivePartial<T> = T extends (infer U)[]
|
166
|
-
? RecursivePartialObject<U>[]
|
167
|
-
: RecursivePartialObject<T>;
|
168
|
-
|
169
|
-
type RecursivePartialObject<T> = {
|
170
|
-
[P in keyof T]?: T[P] extends Record<any, any> ? RecursivePartial<T[P]> : T[P];
|
171
|
-
};
|
172
|
-
|
173
|
-
export function setFormStateValues<T extends ValidatableMapOrArray>(
|
174
|
-
formState: FormState<T>,
|
175
|
-
data: RecursivePartial<FormStateShape<T>>,
|
176
|
-
triggerValidation = false
|
177
|
-
): FormState<T> {
|
178
|
-
if (Array.isArray(formState.$) && (data as any[]).length !== formState.$.length) {
|
179
|
-
throw new Error(
|
180
|
-
'Number of elements in data object node is different from number of element in form.' +
|
181
|
-
' All array nodes should match in size before values can be applied to form.'
|
182
|
-
);
|
183
|
-
}
|
184
|
-
transaction(() => {
|
185
|
-
traverseFormState(
|
186
|
-
false,
|
187
|
-
formState,
|
188
|
-
(key: string, form: FormState<any>) => {
|
189
|
-
if (data instanceof Map || isObservableMap(data)) {
|
190
|
-
if (data.has(key)) {
|
191
|
-
setFormStateValues(form, data.get(key));
|
192
|
-
}
|
193
|
-
} else {
|
194
|
-
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
195
|
-
setFormStateValues(form, data[key as keyof typeof data]);
|
196
|
-
}
|
197
|
-
}
|
198
|
-
},
|
199
|
-
(key: string, field: FieldState<any>) => {
|
200
|
-
if (data instanceof Map || isObservableMap(data)) {
|
201
|
-
if (data.has(key)) {
|
202
|
-
runInAction(() => {
|
203
|
-
field.value = data.get(key);
|
204
|
-
field.$ = data.get(key);
|
205
|
-
});
|
206
|
-
}
|
207
|
-
} else {
|
208
|
-
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
209
|
-
runInAction(() => {
|
210
|
-
field.value = data[key as keyof typeof data];
|
211
|
-
field.$ = data[key as keyof typeof data];
|
212
|
-
});
|
213
|
-
}
|
214
|
-
}
|
215
|
-
}
|
216
|
-
);
|
217
|
-
if (triggerValidation) {
|
218
|
-
const setValidatedSubFields = (form: FormState<any>) => {
|
219
|
-
form.validatedSubFields = (form as any).getValues();
|
220
|
-
};
|
221
|
-
traverseFormState(true, formState, (_0: string, form: FormState<any>) => {
|
222
|
-
setValidatedSubFields(form);
|
223
|
-
});
|
224
|
-
setValidatedSubFields(formState);
|
225
|
-
formState.validate();
|
226
|
-
}
|
227
|
-
});
|
228
|
-
return formState;
|
229
|
-
}
|
230
|
-
|
231
|
-
export function commitFormState<T extends ValidatableMapOrArray>(formState: FormState<T>) {
|
232
|
-
traverseFormState(true, formState, undefined, (_0: string, field: FieldState<any>) => {
|
233
|
-
field.dirty = false;
|
234
|
-
(field as any)._initValue = field.value;
|
235
|
-
});
|
236
|
-
}
|
237
|
-
|
238
|
-
export function camelCaseToTitleCase(value: string) {
|
239
|
-
const regexp = /[A-Z](?=[A-Z][a-z])|[^A-Z](?=[A-Z])|[A-Za-z](?=[^A-Za-z])/g;
|
240
|
-
return value.replace(regexp, '$& ');
|
241
|
-
}
|
242
|
-
|
243
|
-
export function isFormStateChanged<T extends ValidatableMapOrArray>(formState: FormState<T>) {
|
244
|
-
return computed(() => {
|
245
|
-
let isChanged = false;
|
246
|
-
traverseFormState(true, formState, undefined, (_0: string, field: FieldState<any>) => {
|
247
|
-
const isValueChanged = () => {
|
248
|
-
if (field instanceof InputFieldState) {
|
249
|
-
if ((field as any)._initValue === undefined && field.value === '') {
|
250
|
-
return false;
|
251
|
-
}
|
252
|
-
}
|
253
|
-
return !comparer.structural((field as any)._initValue, field.value);
|
254
|
-
};
|
255
|
-
isChanged = isChanged || (field.dirty === true && isValueChanged());
|
256
|
-
});
|
257
|
-
return isChanged;
|
258
|
-
});
|
259
|
-
}
|
package/src/form-validators.ts
DELETED
@@ -1,106 +0,0 @@
|
|
1
|
-
import { DatetimeFieldState, FormValues } from './form-helpers';
|
2
|
-
import { isObservableArray } from 'mobx';
|
3
|
-
import { DateRange } from './date-range';
|
4
|
-
|
5
|
-
interface DateRangeFieldStates {
|
6
|
-
startDate: DatetimeFieldState;
|
7
|
-
endDate: DatetimeFieldState;
|
8
|
-
}
|
9
|
-
|
10
|
-
const isDefined = (value: FormValues | undefined) => {
|
11
|
-
if (value === undefined) {
|
12
|
-
return false;
|
13
|
-
}
|
14
|
-
|
15
|
-
if (Array.isArray(value) || isObservableArray(value)) {
|
16
|
-
return !!value.length;
|
17
|
-
}
|
18
|
-
|
19
|
-
return typeof value === 'string' ? !!value.trim() : !!value;
|
20
|
-
};
|
21
|
-
|
22
|
-
export const FormValidators = {
|
23
|
-
required: (value?: FormValues) =>
|
24
|
-
FormValidators.requiredWithCustomMessage('Value is required')(value),
|
25
|
-
|
26
|
-
requiredWithCustomMessage: (errorMessage: string) => (value?: FormValues) =>
|
27
|
-
!isDefined(value) && errorMessage,
|
28
|
-
|
29
|
-
hasLowerCase: (str: string) => /[a-z]/.test(str),
|
30
|
-
|
31
|
-
hasUpperCase: (str: string) => /[A-Z]/.test(str),
|
32
|
-
|
33
|
-
hasNumber: (str: string) => /\d/.test(str),
|
34
|
-
|
35
|
-
passwordIsValidFormat: (password: string) =>
|
36
|
-
password.length > 7 &&
|
37
|
-
FormValidators.hasLowerCase(password) &&
|
38
|
-
FormValidators.hasUpperCase(password) &&
|
39
|
-
FormValidators.hasNumber(password),
|
40
|
-
|
41
|
-
emailFormatIsValid: (email: string) => {
|
42
|
-
/* tslint:disable: ter-max-len */
|
43
|
-
const regex =
|
44
|
-
/^[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
|
45
|
-
return email.length !== 0 && regex.test(email);
|
46
|
-
},
|
47
|
-
|
48
|
-
website: (errorMessage = 'Enter valid website') => {
|
49
|
-
return (value: string) =>
|
50
|
-
!/^(https?:\/\/)?(www\.)?([a-zA-Z0-9]+(-?[a-zA-Z0-9])*\.)+[\w]{2,}(\/\S*)?$/i.test(
|
51
|
-
value
|
52
|
-
) && errorMessage;
|
53
|
-
},
|
54
|
-
|
55
|
-
minDate: new Date(1900, 0, 1),
|
56
|
-
|
57
|
-
maxDate: new Date(2099, 11, 31),
|
58
|
-
|
59
|
-
isDateValid: (date: Date | null) =>
|
60
|
-
(!date || date > FormValidators.maxDate || date < FormValidators.minDate) &&
|
61
|
-
'Please provide a valid date',
|
62
|
-
|
63
|
-
isDateRangeValid: (dateRange: DateRangeFieldStates) =>
|
64
|
-
dateRange.startDate.$ &&
|
65
|
-
dateRange.endDate.$ &&
|
66
|
-
dateRange.startDate.$ > dateRange.endDate.$ &&
|
67
|
-
'Start Date should not be after End Date',
|
68
|
-
|
69
|
-
dateRangeRequired:
|
70
|
-
(errorMessage = 'Date Range is required') =>
|
71
|
-
(dateRange: DateRange | undefined) =>
|
72
|
-
(!dateRange || !dateRange.from || !dateRange.to) && errorMessage,
|
73
|
-
|
74
|
-
dateRangeValid:
|
75
|
-
(errorMessage = 'Start cannot be after End') =>
|
76
|
-
(dateRange: DateRange | undefined) =>
|
77
|
-
!!dateRange &&
|
78
|
-
!!dateRange.from &&
|
79
|
-
!!dateRange.to &&
|
80
|
-
dateRange.from > dateRange.to &&
|
81
|
-
errorMessage,
|
82
|
-
|
83
|
-
isDateRangeLessThanMaxLength: (maxDays: number) => (val: DateRange | undefined) => {
|
84
|
-
const dayInMillseconds = 1000 * 60 * 60 * 24;
|
85
|
-
return (
|
86
|
-
val?.from &&
|
87
|
-
val.to &&
|
88
|
-
(val.to.getTime() - val.from.getTime()) / dayInMillseconds >= maxDays &&
|
89
|
-
`Only ${maxDays} days can be displayed at time`
|
90
|
-
);
|
91
|
-
},
|
92
|
-
|
93
|
-
isAlphaNumeric: (str: string) => /^(\w+,?)*$/.test(str),
|
94
|
-
|
95
|
-
isMatchingRegex: (regexp: RegExp, entity: string) => (str: string) =>
|
96
|
-
!regexp.test(str) && `Invalid format for ${entity}`,
|
97
|
-
|
98
|
-
minLength: (minLength: number) => (value: string | any[] | undefined) =>
|
99
|
-
value && value.length < minLength ? `Value's min length is ${minLength}` : false,
|
100
|
-
|
101
|
-
maxLength: (maxLength: number) => (value: string | any[] | undefined) =>
|
102
|
-
value && value.length > maxLength ? `Value's max length is ${maxLength}` : false,
|
103
|
-
|
104
|
-
exactLength: (exactLength: number) => (value: string | any[] | undefined) =>
|
105
|
-
value && value.length !== exactLength ? `Value's length must be ${exactLength}` : false,
|
106
|
-
};
|
@@ -1,81 +0,0 @@
|
|
1
|
-
import { DomainStorage } from '../domain-storage';
|
2
|
-
|
3
|
-
const newData = {
|
4
|
-
string: 'value',
|
5
|
-
number: 1,
|
6
|
-
simpleObject: {
|
7
|
-
name: 'name value',
|
8
|
-
deepObject: {
|
9
|
-
name2: 'name2 value',
|
10
|
-
},
|
11
|
-
},
|
12
|
-
};
|
13
|
-
|
14
|
-
describe('domain-storage', () => {
|
15
|
-
test('getItem returns null if the key does not exist', function () {
|
16
|
-
const storage = new DomainStorage({ cacheKey: 'test', version: 1 });
|
17
|
-
|
18
|
-
const data = storage.getItem();
|
19
|
-
|
20
|
-
expect(data).toBe(null);
|
21
|
-
});
|
22
|
-
|
23
|
-
test('getItem returns value from storage', function () {
|
24
|
-
const existingValue = JSON.stringify({ value: newData, version: 1 });
|
25
|
-
localStorage.setItem('test', existingValue);
|
26
|
-
const storage = new DomainStorage<any>({ cacheKey: 'test', version: 1 });
|
27
|
-
|
28
|
-
const data = storage.getItem();
|
29
|
-
|
30
|
-
expect(data).toEqual(newData);
|
31
|
-
});
|
32
|
-
|
33
|
-
test('getItem returns null when the version does not match', function () {
|
34
|
-
const storage = new DomainStorage<any>({ cacheKey: 'test', version: 1 });
|
35
|
-
|
36
|
-
storage.setItem('', newData);
|
37
|
-
const data = storage.getItem();
|
38
|
-
expect(data).toEqual(newData);
|
39
|
-
|
40
|
-
const newStorage = new DomainStorage({ cacheKey: 'test', version: 2 });
|
41
|
-
const newStorageData = newStorage.getItem();
|
42
|
-
expect(newStorageData).toEqual(null);
|
43
|
-
});
|
44
|
-
|
45
|
-
test('getItem returns null when there is invalid formStateCache in storage', function () {
|
46
|
-
localStorage.setItem('test', '{}');
|
47
|
-
const storage = new DomainStorage<any>({ cacheKey: 'test', version: 1 });
|
48
|
-
|
49
|
-
const data = storage.getItem();
|
50
|
-
|
51
|
-
expect(data).toEqual(null);
|
52
|
-
expect(localStorage.removeItem).toHaveBeenCalledWith('test');
|
53
|
-
});
|
54
|
-
|
55
|
-
test('getItem returns null when there is invalid json in storage', function () {
|
56
|
-
localStorage.setItem('test', '{{}');
|
57
|
-
const storage = new DomainStorage<any>({ cacheKey: 'test', version: 1 });
|
58
|
-
|
59
|
-
const data = storage.getItem();
|
60
|
-
|
61
|
-
expect(data).toEqual(null);
|
62
|
-
});
|
63
|
-
|
64
|
-
test('setItem adds value to storage', function () {
|
65
|
-
const storage = new DomainStorage<any>({ cacheKey: 'test', version: 1 });
|
66
|
-
|
67
|
-
storage.setItem('', newData);
|
68
|
-
|
69
|
-
const expectedValue = JSON.stringify({ value: newData, version: 1 });
|
70
|
-
expect(localStorage.getItem('test')).toEqual(expectedValue);
|
71
|
-
});
|
72
|
-
|
73
|
-
test('removeItem removes value from storage', function () {
|
74
|
-
localStorage.setItem('test', '{}');
|
75
|
-
const storage = new DomainStorage<any>({ cacheKey: 'test', version: 1 });
|
76
|
-
|
77
|
-
storage.removeItem();
|
78
|
-
|
79
|
-
expect(localStorage.length).toEqual(0);
|
80
|
-
});
|
81
|
-
});
|
@@ -1,43 +0,0 @@
|
|
1
|
-
import { FormStateShape } from '../form-helpers';
|
2
|
-
import { ValidatableMapOrArray } from 'formstate';
|
3
|
-
|
4
|
-
interface FormStateCache<T> {
|
5
|
-
version: number;
|
6
|
-
value: FormStateShape<T>;
|
7
|
-
}
|
8
|
-
|
9
|
-
export class DomainStorage<T extends ValidatableMapOrArray> {
|
10
|
-
private readonly version: number;
|
11
|
-
private readonly cacheKey: string;
|
12
|
-
|
13
|
-
constructor({ cacheKey, version }: { cacheKey: string; version: number }) {
|
14
|
-
this.version = version;
|
15
|
-
this.cacheKey = cacheKey;
|
16
|
-
}
|
17
|
-
|
18
|
-
getItem(): FormStateShape<T> | null {
|
19
|
-
try {
|
20
|
-
const cachedData = localStorage.getItem(this.cacheKey);
|
21
|
-
if (cachedData) {
|
22
|
-
const formData: FormStateCache<T> = JSON.parse(cachedData);
|
23
|
-
if (formData.version === this.version) {
|
24
|
-
return formData.value;
|
25
|
-
}
|
26
|
-
this.removeItem();
|
27
|
-
}
|
28
|
-
} catch (e) {
|
29
|
-
this.removeItem();
|
30
|
-
}
|
31
|
-
|
32
|
-
return null;
|
33
|
-
}
|
34
|
-
|
35
|
-
removeItem(): void {
|
36
|
-
localStorage.removeItem(this.cacheKey);
|
37
|
-
}
|
38
|
-
|
39
|
-
setItem(_: string, value: FormStateShape<T>): void {
|
40
|
-
const formData = { value, version: this.version };
|
41
|
-
localStorage.setItem(this.cacheKey, JSON.stringify(formData));
|
42
|
-
}
|
43
|
-
}
|
@@ -1,32 +0,0 @@
|
|
1
|
-
import { FormStateShape } from '../form-helpers';
|
2
|
-
import { ValidatableMapOrArray } from 'formstate';
|
3
|
-
|
4
|
-
export class InMemoryStorage<T extends ValidatableMapOrArray> {
|
5
|
-
private storage = new Map<string, FormStateShape<T>>();
|
6
|
-
|
7
|
-
get length() {
|
8
|
-
return this.storage.size;
|
9
|
-
}
|
10
|
-
|
11
|
-
clear(): void {
|
12
|
-
this.storage.clear();
|
13
|
-
}
|
14
|
-
|
15
|
-
getItem(key: string): FormStateShape<T> | null {
|
16
|
-
return this.storage.get(key) !== undefined ? this.storage.get(key)! : null;
|
17
|
-
}
|
18
|
-
|
19
|
-
key(index: number): string | null {
|
20
|
-
return this.length < index ? null : Array.from(this.storage.keys())[index];
|
21
|
-
}
|
22
|
-
|
23
|
-
removeItem(key: string): void {
|
24
|
-
this.storage.delete(key);
|
25
|
-
}
|
26
|
-
|
27
|
-
setItem(key: string, value: FormStateShape<T>): void {
|
28
|
-
this.storage.set(key, value);
|
29
|
-
}
|
30
|
-
}
|
31
|
-
|
32
|
-
export const MemoryStorage = new InMemoryStorage<any>();
|
@@ -1 +0,0 @@
|
|
1
|
-
export * from './persistent-form-state';
|
@@ -1,68 +0,0 @@
|
|
1
|
-
import { autorun } from 'mobx';
|
2
|
-
import { FormState, ValidatableMapOrArray } from 'formstate';
|
3
|
-
import { InMemoryStorage, MemoryStorage } from './in-memory-storage';
|
4
|
-
import {
|
5
|
-
RecursivePartial,
|
6
|
-
FormStateShape,
|
7
|
-
setFormStateValues,
|
8
|
-
formStateToJS,
|
9
|
-
} from '../form-helpers';
|
10
|
-
import { DomainStorage } from './domain-storage';
|
11
|
-
|
12
|
-
export enum PersistenceMode {
|
13
|
-
Session,
|
14
|
-
Domain,
|
15
|
-
InMemory,
|
16
|
-
}
|
17
|
-
|
18
|
-
export class PersistentFormState<T extends ValidatableMapOrArray> extends FormState<T> {
|
19
|
-
private storageSystem!: InMemoryStorage<T> | DomainStorage<T>;
|
20
|
-
private resetFormSuper = this.reset;
|
21
|
-
|
22
|
-
constructor(
|
23
|
-
$: T,
|
24
|
-
private cacheKey: string,
|
25
|
-
private persistenceMode: PersistenceMode,
|
26
|
-
autoSave?: boolean,
|
27
|
-
version?: number
|
28
|
-
) {
|
29
|
-
super($);
|
30
|
-
|
31
|
-
if (PersistenceMode.Domain === this.persistenceMode) {
|
32
|
-
if (!version) {
|
33
|
-
throw 'Set a data structure version.';
|
34
|
-
}
|
35
|
-
this.storageSystem = new DomainStorage<T>({ cacheKey, version });
|
36
|
-
} else if (PersistenceMode.Session === this.persistenceMode) {
|
37
|
-
throw 'Use of Session storage is currently unsafe';
|
38
|
-
} else {
|
39
|
-
this.storageSystem = MemoryStorage;
|
40
|
-
}
|
41
|
-
|
42
|
-
this.getCached();
|
43
|
-
if (autoSave) {
|
44
|
-
this.trackChange();
|
45
|
-
}
|
46
|
-
}
|
47
|
-
|
48
|
-
save = () => {
|
49
|
-
this.storageSystem.setItem(this.cacheKey, formStateToJS(this));
|
50
|
-
};
|
51
|
-
|
52
|
-
resetForm = () => {
|
53
|
-
this.resetFormSuper();
|
54
|
-
this.storageSystem.removeItem(this.cacheKey);
|
55
|
-
};
|
56
|
-
|
57
|
-
private trackChange = () => {
|
58
|
-
autorun(() => this.save(), { delay: 500 });
|
59
|
-
};
|
60
|
-
|
61
|
-
private getCached = () => {
|
62
|
-
const data = this.storageSystem.getItem(this.cacheKey);
|
63
|
-
|
64
|
-
if (data) {
|
65
|
-
setFormStateValues(this, data as unknown as RecursivePartial<FormStateShape<T>>);
|
66
|
-
}
|
67
|
-
};
|
68
|
-
}
|