@os-design/form 1.0.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/LICENCE.md +1 -0
- package/README.md +458 -0
- package/dist/cjs/SubscribableData.js +52 -0
- package/dist/cjs/SubscribableData.js.map +1 -0
- package/dist/cjs/SubscriptionManager.js +83 -0
- package/dist/cjs/SubscriptionManager.js.map +1 -0
- package/dist/cjs/index.js +242 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/utils/clone.js +36 -0
- package/dist/cjs/utils/clone.js.map +1 -0
- package/dist/cjs/utils/isEqual.js +44 -0
- package/dist/cjs/utils/isEqual.js.map +1 -0
- package/dist/cjs/utils/useDeepEqualMemo.js +25 -0
- package/dist/cjs/utils/useDeepEqualMemo.js.map +1 -0
- package/dist/esm/SubscribableData.js +23 -0
- package/dist/esm/SubscribableData.js.map +1 -0
- package/dist/esm/SubscriptionManager.js +46 -0
- package/dist/esm/SubscriptionManager.js.map +1 -0
- package/dist/esm/index.js +157 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/utils/clone.js +17 -0
- package/dist/esm/utils/clone.js.map +1 -0
- package/dist/esm/utils/isEqual.js +27 -0
- package/dist/esm/utils/isEqual.js.map +1 -0
- package/dist/esm/utils/useDeepEqualMemo.js +15 -0
- package/dist/esm/utils/useDeepEqualMemo.js.map +1 -0
- package/dist/types/SubscribableData.d.ts +11 -0
- package/dist/types/SubscribableData.d.ts.map +1 -0
- package/dist/types/SubscriptionManager.d.ts +19 -0
- package/dist/types/SubscriptionManager.d.ts.map +1 -0
- package/dist/types/index.d.ts +57 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/utils/clone.d.ts +3 -0
- package/dist/types/utils/clone.d.ts.map +1 -0
- package/dist/types/utils/isEqual.d.ts +3 -0
- package/dist/types/utils/isEqual.d.ts.map +1 -0
- package/dist/types/utils/useDeepEqualMemo.d.ts +4 -0
- package/dist/types/utils/useDeepEqualMemo.d.ts.map +1 -0
- package/package.json +38 -0
package/LICENCE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Copyright (c) 2019-present, Ilya Ordin, all rights reserved
|
package/README.md
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# @os-design/form [](https://yarnpkg.com/package/@os-design/form) [](https://bundlephobia.com/result?p=@os-design/form)
|
|
2
|
+
|
|
3
|
+
Create forms in React and React Native much faster and easier.
|
|
4
|
+
|
|
5
|
+
**Features:**
|
|
6
|
+
|
|
7
|
+
- 🤓 Rerenders only updated fields, not the whole form.
|
|
8
|
+
- 📱 Use with any design system or native components. Supports React Native.
|
|
9
|
+
- 0️⃣ Small size. Zero dependencies.
|
|
10
|
+
- 📙 Lots of useful features.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Install the package using the following command:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
yarn add @os-design/form
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 🙈 Simple form
|
|
21
|
+
|
|
22
|
+
Let's assume that we want to build a form for creating blog posts. To create a form, use the `useForm` hook and pass the initial data to it. This hook returns the `Field` component, which should be used to render each of your input. To get all the form data use `form.values.getAll()`.
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { useForm } from '@os-design/form';
|
|
26
|
+
|
|
27
|
+
interface FormData {
|
|
28
|
+
title: string;
|
|
29
|
+
content: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const Form: React.FC = () => {
|
|
33
|
+
const { Field, form } = useForm<FormData>({ title: '', content: '' });
|
|
34
|
+
|
|
35
|
+
const onSubmit = useCallback(() => {
|
|
36
|
+
console.log(form.values.getAll()); // To get all the form data
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<Field name='title' render={(props) => <Input {...props} />} />
|
|
42
|
+
<Field name='content' render={(props) => <TextArea {...props} />} />
|
|
43
|
+
<Button onClick={onSubmit}>Add</Button>
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The `props` is an object that contains 2 properties: `value` and `onChange`, so you can just pass all the properties of this object to your input component for simplicity.
|
|
50
|
+
|
|
51
|
+
## ❗ Showing errors
|
|
52
|
+
|
|
53
|
+
If the user entered incorrect values, the form should display errors. To support errors, you need to make 2 steps: pass the errors to the form and display them next to the input components.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
const Form: React.FC = () => {
|
|
57
|
+
const { Field, form } = useForm<FormData>({ title: '', content: '' });
|
|
58
|
+
|
|
59
|
+
const onSubmit = useCallback(() => {
|
|
60
|
+
form.errors.set('title', 'The title is too long 😔');
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
<Field
|
|
66
|
+
name='title'
|
|
67
|
+
render={(props, { error }) => (
|
|
68
|
+
<FormItem error={error}>
|
|
69
|
+
<Input {...props} />
|
|
70
|
+
</FormItem>
|
|
71
|
+
)}
|
|
72
|
+
/>
|
|
73
|
+
<Field
|
|
74
|
+
name='content'
|
|
75
|
+
render={(props, { error }) => (
|
|
76
|
+
<FormItem error={error}>
|
|
77
|
+
<TextArea {...props} />
|
|
78
|
+
</FormItem>
|
|
79
|
+
)}
|
|
80
|
+
/>
|
|
81
|
+
<Button onClick={onSubmit}>Add</Button>
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
If you want to display an error message not next the input component (e.g. if an error is not related to the field), you can create a separate component and place it wherever you want.
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
interface FormData {
|
|
91
|
+
title: string;
|
|
92
|
+
content: string;
|
|
93
|
+
_error?: never; // Use any name that is not used by any field
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Component to display errors unrelated to any fields
|
|
97
|
+
const Error: React.FC = () => {
|
|
98
|
+
const error = useError<FormData>('_error');
|
|
99
|
+
return error ? <Alert type='error'>{error}</Alert> : null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const Form: React.FC = () => {
|
|
103
|
+
const { Field, form } = useForm<FormData>({ title: '', content: '' });
|
|
104
|
+
|
|
105
|
+
const onSubmit = useCallback(() => {
|
|
106
|
+
form.errors.set('_error', 'The server went on vacation 🌴'); // Set the error
|
|
107
|
+
console.log(form.values.getAll()); // { title: '', content: '' } - data only, without the `_error` prop
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
// Wrap your form in a `FormProvider` to pass the `form` to the child components (to the `Error` component in our case)
|
|
111
|
+
return (
|
|
112
|
+
<FormProvider form={form}>
|
|
113
|
+
<>
|
|
114
|
+
<Field
|
|
115
|
+
name='title'
|
|
116
|
+
render={(props, { error }) => (
|
|
117
|
+
<FormItem error={error}>
|
|
118
|
+
<Input {...props} />
|
|
119
|
+
</FormItem>
|
|
120
|
+
)}
|
|
121
|
+
/>
|
|
122
|
+
<Field
|
|
123
|
+
name='content'
|
|
124
|
+
render={(props, { error }) => (
|
|
125
|
+
<FormItem error={error}>
|
|
126
|
+
<TextArea {...props} />
|
|
127
|
+
</FormItem>
|
|
128
|
+
)}
|
|
129
|
+
/>
|
|
130
|
+
|
|
131
|
+
<Error />
|
|
132
|
+
|
|
133
|
+
<Button onClick={onSubmit}>Add</Button>
|
|
134
|
+
</>
|
|
135
|
+
</FormProvider>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 🪆 Getting the form in child components
|
|
141
|
+
|
|
142
|
+
To pass the form to the child components, you have to:
|
|
143
|
+
|
|
144
|
+
1. Wrap your form in a `FormProvider`.
|
|
145
|
+
2. Use the `useExistingForm` hook in the child components.
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
interface BaseFormProps {
|
|
149
|
+
children: React.ReactNode;
|
|
150
|
+
}
|
|
151
|
+
const BaseForm: React.FC<BaseFormProps> = ({ children }) => {
|
|
152
|
+
const { Field } = useExistingForm<FormData>();
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<>
|
|
156
|
+
<Field name='title' render={(props) => <Input {...props} />} />
|
|
157
|
+
<Field name='content' render={(props) => <TextArea {...props} />} />
|
|
158
|
+
{children}
|
|
159
|
+
</>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const FormCreate: React.FC = () => {
|
|
164
|
+
const { form } = useForm<FormData>({ title: '', content: '' });
|
|
165
|
+
|
|
166
|
+
const onCreate = useCallback(() => {
|
|
167
|
+
console.log('Creating...', form.values.getAll());
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<FormProvider form={form}>
|
|
172
|
+
<BaseForm>
|
|
173
|
+
<Button onClick={onCreate}>Create</Button>
|
|
174
|
+
</BaseForm>
|
|
175
|
+
</FormProvider>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const FormUpdate: React.FC = () => {
|
|
180
|
+
const { form } = useForm<FormData>({ title: 'Title', content: 'Content' });
|
|
181
|
+
|
|
182
|
+
const onUpdate = useCallback(() => {
|
|
183
|
+
console.log('Updating...', form.values.getAll());
|
|
184
|
+
}, []);
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<FormProvider form={form}>
|
|
188
|
+
<BaseForm>
|
|
189
|
+
<Button onClick={onUpdate}>Update</Button>
|
|
190
|
+
</BaseForm>
|
|
191
|
+
</FormProvider>
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## 🚫 Blocking the button until the form is changed
|
|
197
|
+
|
|
198
|
+
Let's upgrade our form and allow the user to click on the `Save` button only if one of the field values has been changed. To implement it, let's pass the `modified` flag to the `disabled` property of the button.
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
const Form: React.FC = () => {
|
|
202
|
+
const { Field, form, modified } = useForm<FormData>({
|
|
203
|
+
title: 'Title',
|
|
204
|
+
content: 'Content',
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const onSubmit = useCallback(() => {
|
|
208
|
+
console.log(form.values.getAll());
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<>
|
|
213
|
+
<Field name='title' render={(props) => <Input {...props} />} />
|
|
214
|
+
<Field name='content' render={(props) => <TextArea {...props} />} />
|
|
215
|
+
<Button disabled={!modified} onClick={onSubmit}>
|
|
216
|
+
Save
|
|
217
|
+
</Button>
|
|
218
|
+
</>
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## 🎯 Getting only changed values
|
|
224
|
+
|
|
225
|
+
If the form has many fields, it is better to send only the changed values to the server (not all the form data). To do this, you need to determine which fields have been changed using the `modifiedFields` and get only them.
|
|
226
|
+
|
|
227
|
+
`modifiedFields` is an array with the names of the fields that have been changed. If the form has 2 fields: `title`, `content` and each of them has been changed, then `modifiedFields` will be equal to `['title', 'content']`.
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
const Form: React.FC = () => {
|
|
231
|
+
const { Field, form, modifiedFields } = useForm<FormData>({
|
|
232
|
+
title: '',
|
|
233
|
+
content: '',
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const onSubmit = useCallback(() => {
|
|
237
|
+
const onlyChangedValues = modifiedFields.reduce(
|
|
238
|
+
(acc, field) => ({ ...acc, [field]: form.values.get(field) }),
|
|
239
|
+
{}
|
|
240
|
+
);
|
|
241
|
+
console.log(onlyChangedValues);
|
|
242
|
+
}, [modifiedFields]);
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<>
|
|
246
|
+
<Field name='title' render={(props) => <Input {...props} />} />
|
|
247
|
+
<Field name='content' render={(props) => <TextArea {...props} />} />
|
|
248
|
+
<Button onClick={onSubmit}>Save</Button>
|
|
249
|
+
</>
|
|
250
|
+
);
|
|
251
|
+
};
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## ↩️ Resetting a field to its initial value
|
|
255
|
+
|
|
256
|
+
Let's assume the user changed his profile data in the form, but then decided to rollback one of the field values. It would be really cool if the user could just click the button next to the input component and the field value would return to the initial value.
|
|
257
|
+
|
|
258
|
+
You can easily add this feature as follows:
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
const ResetButton: React.FC<ButtonProps> = (props) => (
|
|
262
|
+
<Button type='ghost' size='small' wide='never' {...props}>
|
|
263
|
+
<RollbackIcon />
|
|
264
|
+
</Button>
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
interface ProfileData {
|
|
268
|
+
name: string;
|
|
269
|
+
password: string;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const Form: React.FC = () => {
|
|
273
|
+
const { Field, form } = useForm<ProfileData>({
|
|
274
|
+
name: 'Kate',
|
|
275
|
+
password: 'secret',
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const onSubmit = useCallback(() => {
|
|
279
|
+
console.log(form.values.getAll());
|
|
280
|
+
}, []);
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<>
|
|
284
|
+
<Field
|
|
285
|
+
name='name'
|
|
286
|
+
render={(props, { modified, reset }) => (
|
|
287
|
+
<Input
|
|
288
|
+
{...props}
|
|
289
|
+
right={modified && <ResetButton onClick={reset} />}
|
|
290
|
+
/>
|
|
291
|
+
)}
|
|
292
|
+
/>
|
|
293
|
+
<Field
|
|
294
|
+
name='password'
|
|
295
|
+
render={(props, { modified, reset }) => (
|
|
296
|
+
<InputPassword
|
|
297
|
+
{...props}
|
|
298
|
+
right={modified && <ResetButton onClick={reset} />}
|
|
299
|
+
/>
|
|
300
|
+
)}
|
|
301
|
+
/>
|
|
302
|
+
<Button onClick={onSubmit}>Save</Button>
|
|
303
|
+
</>
|
|
304
|
+
);
|
|
305
|
+
};
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## 🎚️ Transforming field values
|
|
309
|
+
|
|
310
|
+
There are few cases where this is necessary:
|
|
311
|
+
|
|
312
|
+
1. Your input component does not support the field type. For example, you want to enter numbers in the text input that uses the string type.
|
|
313
|
+
2. The value in the store is not equal to the value in the input component. For example, the price is entered in dollars, but you store it in cents.
|
|
314
|
+
3. The value should be post-processed. For example, the email address should always be in lowercase.
|
|
315
|
+
|
|
316
|
+
Let's consider the first case. To implement it, you should set 2 transformers:
|
|
317
|
+
|
|
318
|
+
- `toValue` - transforms the value before putting it into the input component.
|
|
319
|
+
- `fromValue` - transforms the value before calling the onChange event handler.
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
interface PersonData {
|
|
323
|
+
name: string;
|
|
324
|
+
age: number;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const Form: React.FC = () => {
|
|
328
|
+
const { Field, form } = useForm<PersonData>({ name: 'Kate', age: 18 });
|
|
329
|
+
|
|
330
|
+
const onSubmit = useCallback(() => {
|
|
331
|
+
console.log(form.values.getAll());
|
|
332
|
+
}, []);
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<>
|
|
336
|
+
<Field name='name' render={(props) => <Input {...props} />} />
|
|
337
|
+
<Field
|
|
338
|
+
name='age'
|
|
339
|
+
toValue={(value) => value.toString()} // To input component
|
|
340
|
+
fromValue={(value) => Number(value)} // From input component
|
|
341
|
+
render={(props) => <Input {...props} />}
|
|
342
|
+
/>
|
|
343
|
+
<Button onClick={onSubmit}>Save</Button>
|
|
344
|
+
</>
|
|
345
|
+
);
|
|
346
|
+
};
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## 🎛️ Changing another fields
|
|
350
|
+
|
|
351
|
+
In my practice, this came in handy in 2 cases:
|
|
352
|
+
|
|
353
|
+
1. To generate a field value by specific field. For example, to generate a meta title of a blog post by its title.
|
|
354
|
+
2. To reset some field values if a specific field is changed.
|
|
355
|
+
|
|
356
|
+
Let's consider the first case. To implement it, use the `useTransformer` hook.
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
interface FormData {
|
|
360
|
+
title: string;
|
|
361
|
+
metaTitle: string;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const Form: React.FC = () => {
|
|
365
|
+
const { Field, form, useTransformer } = useForm<FormData>({
|
|
366
|
+
title: '',
|
|
367
|
+
metaTitle: '',
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const onSubmit = useCallback(() => {
|
|
371
|
+
console.log(form.values.getAll());
|
|
372
|
+
}, []);
|
|
373
|
+
|
|
374
|
+
useTransformer('title', (value) => ({
|
|
375
|
+
metaTitle: `The length of the title is ${value.length}`,
|
|
376
|
+
}));
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<>
|
|
380
|
+
<Field name='title' render={(props) => <Input {...props} />} />
|
|
381
|
+
<Field name='metaTitle' render={(props) => <Input {...props} />} />
|
|
382
|
+
<Button onClick={onSubmit}>Save</Button>
|
|
383
|
+
</>
|
|
384
|
+
);
|
|
385
|
+
};
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## 👀 Tracking field changes
|
|
389
|
+
|
|
390
|
+
You can track changes for a specific field using the `useValue` hook.
|
|
391
|
+
|
|
392
|
+
For example, if the url slug is generated by the title on the server side, you need to send a request every time the title field changes and update the url slug field after receiving a response.
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
interface FormData {
|
|
396
|
+
title: string;
|
|
397
|
+
urlSlug: string;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const Form: React.FC = () => {
|
|
401
|
+
const { Field, form, useValue } = useForm<FormData>({
|
|
402
|
+
title: '',
|
|
403
|
+
urlSlug: '',
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
const onSubmit = useCallback(() => {
|
|
407
|
+
console.log(form.values.getAll());
|
|
408
|
+
}, []);
|
|
409
|
+
|
|
410
|
+
const title = useValue('title');
|
|
411
|
+
|
|
412
|
+
useEffect(() => {
|
|
413
|
+
// Send a request to the server to generate the url slug by title
|
|
414
|
+
setTimeout(() => {
|
|
415
|
+
// Update the url slug field
|
|
416
|
+
form.values.set('urlSlug', title.length.toString());
|
|
417
|
+
}, 1000);
|
|
418
|
+
}, [title]);
|
|
419
|
+
|
|
420
|
+
return (
|
|
421
|
+
<>
|
|
422
|
+
<Field name='title' render={(props) => <Input {...props} />} />
|
|
423
|
+
<Field name='urlSlug' render={(props) => <Input {...props} />} />
|
|
424
|
+
<Button onClick={onSubmit}>Add</Button>
|
|
425
|
+
</>
|
|
426
|
+
);
|
|
427
|
+
};
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
You can also track changes to all fields, for example, for logging.
|
|
431
|
+
|
|
432
|
+
```tsx
|
|
433
|
+
const Form: React.FC = () => {
|
|
434
|
+
const { Field, form } = useForm<FormData>({
|
|
435
|
+
title: '',
|
|
436
|
+
content: '',
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
const onSubmit = useCallback(() => {
|
|
440
|
+
console.log(form.values.getAll());
|
|
441
|
+
}, []);
|
|
442
|
+
|
|
443
|
+
useEffect(() => {
|
|
444
|
+
const subscription = form.values.subscribeToAllFields((name, value) => {
|
|
445
|
+
console.log(`${name} = ${value}`);
|
|
446
|
+
});
|
|
447
|
+
return () => subscription.unsubscribe();
|
|
448
|
+
}, []);
|
|
449
|
+
|
|
450
|
+
return (
|
|
451
|
+
<>
|
|
452
|
+
<Field name='title' render={(props) => <Input {...props} />} />
|
|
453
|
+
<Field name='content' render={(props) => <TextArea {...props} />} />
|
|
454
|
+
<Button onClick={onSubmit}>Save</Button>
|
|
455
|
+
</>
|
|
456
|
+
);
|
|
457
|
+
};
|
|
458
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports["default"] = void 0;
|
|
8
|
+
var _SubscriptionManager2 = _interopRequireDefault(require("./SubscriptionManager"));
|
|
9
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
|
10
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
11
|
+
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
|
12
|
+
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
13
|
+
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
|
|
14
|
+
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
|
15
|
+
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
|
|
16
|
+
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
|
|
17
|
+
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
|
18
|
+
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
|
|
19
|
+
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
|
|
20
|
+
var SubscribableData = /*#__PURE__*/function (_SubscriptionManager) {
|
|
21
|
+
_inherits(SubscribableData, _SubscriptionManager);
|
|
22
|
+
var _super = _createSuper(SubscribableData);
|
|
23
|
+
function SubscribableData(data) {
|
|
24
|
+
var _this;
|
|
25
|
+
_classCallCheck(this, SubscribableData);
|
|
26
|
+
_this = _super.call(this);
|
|
27
|
+
_this.data = void 0;
|
|
28
|
+
_this.data = data;
|
|
29
|
+
return _this;
|
|
30
|
+
}
|
|
31
|
+
_createClass(SubscribableData, [{
|
|
32
|
+
key: "get",
|
|
33
|
+
value: function get(name) {
|
|
34
|
+
return this.data[name];
|
|
35
|
+
}
|
|
36
|
+
}, {
|
|
37
|
+
key: "getAll",
|
|
38
|
+
value: function getAll() {
|
|
39
|
+
return this.data;
|
|
40
|
+
}
|
|
41
|
+
}, {
|
|
42
|
+
key: "set",
|
|
43
|
+
value: function set(name, value) {
|
|
44
|
+
this.data[name] = value;
|
|
45
|
+
this.call(name, value);
|
|
46
|
+
}
|
|
47
|
+
}]);
|
|
48
|
+
return SubscribableData;
|
|
49
|
+
}(_SubscriptionManager2["default"]);
|
|
50
|
+
var _default = SubscribableData;
|
|
51
|
+
exports["default"] = _default;
|
|
52
|
+
//# sourceMappingURL=SubscribableData.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubscribableData.js","names":["SubscribableData","data","name","value","call","SubscriptionManager"],"sources":["../../src/SubscribableData.ts"],"sourcesContent":["import SubscriptionManager from './SubscriptionManager';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Data = Record<string, any>;\n\nclass SubscribableData<\n TData extends Data,\n TDataName extends keyof TData = keyof TData\n> extends SubscriptionManager<TData> {\n private data: TData;\n\n public constructor(data: TData) {\n super();\n this.data = data;\n }\n\n public get<TName extends TDataName>(name: TName): TData[TName] {\n return this.data[name];\n }\n\n public getAll(): TData {\n return this.data;\n }\n\n public set<TName extends TDataName>(name: TName, value: TData[TName]): void {\n this.data[name] = value;\n this.call(name, value);\n }\n}\n\nexport default SubscribableData;\n"],"mappings":";;;;;;;AAAA;AAAwD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKlDA,gBAAgB;EAAA;EAAA;EAMpB,0BAAmBC,IAAW,EAAE;IAAA;IAAA;IAC9B;IAAQ,MAHFA,IAAI;IAIV,MAAKA,IAAI,GAAGA,IAAI;IAAC;EACnB;EAAC;IAAA;IAAA,OAED,aAAoCC,IAAW,EAAgB;MAC7D,OAAO,IAAI,CAACD,IAAI,CAACC,IAAI,CAAC;IACxB;EAAC;IAAA;IAAA,OAED,kBAAuB;MACrB,OAAO,IAAI,CAACD,IAAI;IAClB;EAAC;IAAA;IAAA,OAED,aAAoCC,IAAW,EAAEC,KAAmB,EAAQ;MAC1E,IAAI,CAACF,IAAI,CAACC,IAAI,CAAC,GAAGC,KAAK;MACvB,IAAI,CAACC,IAAI,CAACF,IAAI,EAAEC,KAAK,CAAC;IACxB;EAAC;EAAA;AAAA,EAnBOE,gCAAmB;AAAA,eAsBdL,gBAAgB;AAAA"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports["default"] = void 0;
|
|
7
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
8
|
+
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
|
9
|
+
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
var deleteFromArray = function deleteFromArray(arr, item) {
|
|
14
|
+
var index = arr.findIndex(function (i) {
|
|
15
|
+
return i === item;
|
|
16
|
+
});
|
|
17
|
+
if (index === -1) return;
|
|
18
|
+
arr.splice(index, 1);
|
|
19
|
+
};
|
|
20
|
+
var SubscriptionManager = /*#__PURE__*/function () {
|
|
21
|
+
function SubscriptionManager() {
|
|
22
|
+
_classCallCheck(this, SubscriptionManager);
|
|
23
|
+
this.observers = void 0;
|
|
24
|
+
this.fieldObservers = void 0;
|
|
25
|
+
this.observers = [];
|
|
26
|
+
this.fieldObservers = {};
|
|
27
|
+
}
|
|
28
|
+
_createClass(SubscriptionManager, [{
|
|
29
|
+
key: "getFieldObservers",
|
|
30
|
+
value: function getFieldObservers(name) {
|
|
31
|
+
if (!this.fieldObservers[name]) {
|
|
32
|
+
this.fieldObservers[name] = [];
|
|
33
|
+
}
|
|
34
|
+
return this.fieldObservers[name];
|
|
35
|
+
}
|
|
36
|
+
}, {
|
|
37
|
+
key: "subscribeToAllFields",
|
|
38
|
+
value: function subscribeToAllFields(observer) {
|
|
39
|
+
var _this = this;
|
|
40
|
+
this.observers.push(observer);
|
|
41
|
+
return {
|
|
42
|
+
unsubscribe: function unsubscribe() {
|
|
43
|
+
return _this.unsubscribeFromAllFields(observer);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}, {
|
|
48
|
+
key: "unsubscribeFromAllFields",
|
|
49
|
+
value: function unsubscribeFromAllFields(observer) {
|
|
50
|
+
deleteFromArray(this.observers, observer);
|
|
51
|
+
}
|
|
52
|
+
}, {
|
|
53
|
+
key: "subscribeToField",
|
|
54
|
+
value: function subscribeToField(name, observer) {
|
|
55
|
+
var _this2 = this;
|
|
56
|
+
this.getFieldObservers(name).push(observer);
|
|
57
|
+
return {
|
|
58
|
+
unsubscribe: function unsubscribe() {
|
|
59
|
+
return _this2.unsubscribeFromField(name, observer);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}, {
|
|
64
|
+
key: "unsubscribeFromField",
|
|
65
|
+
value: function unsubscribeFromField(name, observer) {
|
|
66
|
+
deleteFromArray(this.getFieldObservers(name), observer);
|
|
67
|
+
}
|
|
68
|
+
}, {
|
|
69
|
+
key: "call",
|
|
70
|
+
value: function call(name, value) {
|
|
71
|
+
this.observers.forEach(function (observer) {
|
|
72
|
+
return observer(name, value);
|
|
73
|
+
});
|
|
74
|
+
this.getFieldObservers(name).forEach(function (observer) {
|
|
75
|
+
return observer(value);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}]);
|
|
79
|
+
return SubscriptionManager;
|
|
80
|
+
}();
|
|
81
|
+
var _default = SubscriptionManager;
|
|
82
|
+
exports["default"] = _default;
|
|
83
|
+
//# sourceMappingURL=SubscriptionManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubscriptionManager.js","names":["deleteFromArray","arr","item","index","findIndex","i","splice","SubscriptionManager","observers","fieldObservers","name","observer","push","unsubscribe","unsubscribeFromAllFields","getFieldObservers","unsubscribeFromField","value","forEach"],"sources":["../../src/SubscriptionManager.ts"],"sourcesContent":["export type Observer<K, V> = (name: K, value: V) => void;\nexport type FieldObserver<V> = (value: V) => void;\n\nexport interface Subscription {\n unsubscribe: () => void;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Data = Record<string, any>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst deleteFromArray = (arr: any[], item: any) => {\n const index = arr.findIndex((i) => i === item);\n if (index === -1) return;\n arr.splice(index, 1);\n};\n\nclass SubscriptionManager<\n TData extends Data,\n TDataName extends keyof TData = keyof TData\n> {\n private observers: Array<Observer<TDataName, TData[TDataName]>>;\n\n private fieldObservers: {\n [TName in TDataName]?: Array<FieldObserver<TData[TName]>>;\n };\n\n public constructor() {\n this.observers = [];\n this.fieldObservers = {};\n }\n\n private getFieldObservers<TName extends TDataName>(\n name: TName\n ): Array<FieldObserver<TData[TName]>> {\n if (!this.fieldObservers[name]) {\n this.fieldObservers[name] = [];\n }\n return this.fieldObservers[name] as Array<FieldObserver<TData[TName]>>;\n }\n\n public subscribeToAllFields(\n observer: Observer<TDataName, TData[TDataName]>\n ): Subscription {\n this.observers.push(observer);\n return { unsubscribe: () => this.unsubscribeFromAllFields(observer) };\n }\n\n private unsubscribeFromAllFields(\n observer: Observer<TDataName, TData[TDataName]>\n ): void {\n deleteFromArray(this.observers, observer);\n }\n\n public subscribeToField<TName extends TDataName>(\n name: TName,\n observer: FieldObserver<TData[TName]>\n ): Subscription {\n this.getFieldObservers(name).push(observer);\n return { unsubscribe: () => this.unsubscribeFromField(name, observer) };\n }\n\n private unsubscribeFromField<TName extends TDataName>(\n name: TName,\n observer: FieldObserver<TData[TName]>\n ): void {\n deleteFromArray(this.getFieldObservers(name), observer);\n }\n\n protected call<TName extends TDataName>(\n name: TName,\n value: TData[TName]\n ): void {\n this.observers.forEach((observer) => observer(name, value));\n this.getFieldObservers(name).forEach((observer) => observer(value));\n }\n}\n\nexport default SubscriptionManager;\n"],"mappings":";;;;;;;;;AAOA;;AAGA;AACA,IAAMA,eAAe,GAAG,SAAlBA,eAAe,CAAIC,GAAU,EAAEC,IAAS,EAAK;EACjD,IAAMC,KAAK,GAAGF,GAAG,CAACG,SAAS,CAAC,UAACC,CAAC;IAAA,OAAKA,CAAC,KAAKH,IAAI;EAAA,EAAC;EAC9C,IAAIC,KAAK,KAAK,CAAC,CAAC,EAAE;EAClBF,GAAG,CAACK,MAAM,CAACH,KAAK,EAAE,CAAC,CAAC;AACtB,CAAC;AAAC,IAEII,mBAAmB;EAUvB,+BAAqB;IAAA;IAAA,KANbC,SAAS;IAAA,KAETC,cAAc;IAKpB,IAAI,CAACD,SAAS,GAAG,EAAE;IACnB,IAAI,CAACC,cAAc,GAAG,CAAC,CAAC;EAC1B;EAAC;IAAA;IAAA,OAED,2BACEC,IAAW,EACyB;MACpC,IAAI,CAAC,IAAI,CAACD,cAAc,CAACC,IAAI,CAAC,EAAE;QAC9B,IAAI,CAACD,cAAc,CAACC,IAAI,CAAC,GAAG,EAAE;MAChC;MACA,OAAO,IAAI,CAACD,cAAc,CAACC,IAAI,CAAC;IAClC;EAAC;IAAA;IAAA,OAED,8BACEC,QAA+C,EACjC;MAAA;MACd,IAAI,CAACH,SAAS,CAACI,IAAI,CAACD,QAAQ,CAAC;MAC7B,OAAO;QAAEE,WAAW,EAAE;UAAA,OAAM,KAAI,CAACC,wBAAwB,CAACH,QAAQ,CAAC;QAAA;MAAC,CAAC;IACvE;EAAC;IAAA;IAAA,OAED,kCACEA,QAA+C,EACzC;MACNX,eAAe,CAAC,IAAI,CAACQ,SAAS,EAAEG,QAAQ,CAAC;IAC3C;EAAC;IAAA;IAAA,OAED,0BACED,IAAW,EACXC,QAAqC,EACvB;MAAA;MACd,IAAI,CAACI,iBAAiB,CAACL,IAAI,CAAC,CAACE,IAAI,CAACD,QAAQ,CAAC;MAC3C,OAAO;QAAEE,WAAW,EAAE;UAAA,OAAM,MAAI,CAACG,oBAAoB,CAACN,IAAI,EAAEC,QAAQ,CAAC;QAAA;MAAC,CAAC;IACzE;EAAC;IAAA;IAAA,OAED,8BACED,IAAW,EACXC,QAAqC,EAC/B;MACNX,eAAe,CAAC,IAAI,CAACe,iBAAiB,CAACL,IAAI,CAAC,EAAEC,QAAQ,CAAC;IACzD;EAAC;IAAA;IAAA,OAED,cACED,IAAW,EACXO,KAAmB,EACb;MACN,IAAI,CAACT,SAAS,CAACU,OAAO,CAAC,UAACP,QAAQ;QAAA,OAAKA,QAAQ,CAACD,IAAI,EAAEO,KAAK,CAAC;MAAA,EAAC;MAC3D,IAAI,CAACF,iBAAiB,CAACL,IAAI,CAAC,CAACQ,OAAO,CAAC,UAACP,QAAQ;QAAA,OAAKA,QAAQ,CAACM,KAAK,CAAC;MAAA,EAAC;IACrE;EAAC;EAAA;AAAA;AAAA,eAGYV,mBAAmB;AAAA"}
|