@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.
Files changed (39) hide show
  1. package/LICENCE.md +1 -0
  2. package/README.md +458 -0
  3. package/dist/cjs/SubscribableData.js +52 -0
  4. package/dist/cjs/SubscribableData.js.map +1 -0
  5. package/dist/cjs/SubscriptionManager.js +83 -0
  6. package/dist/cjs/SubscriptionManager.js.map +1 -0
  7. package/dist/cjs/index.js +242 -0
  8. package/dist/cjs/index.js.map +1 -0
  9. package/dist/cjs/utils/clone.js +36 -0
  10. package/dist/cjs/utils/clone.js.map +1 -0
  11. package/dist/cjs/utils/isEqual.js +44 -0
  12. package/dist/cjs/utils/isEqual.js.map +1 -0
  13. package/dist/cjs/utils/useDeepEqualMemo.js +25 -0
  14. package/dist/cjs/utils/useDeepEqualMemo.js.map +1 -0
  15. package/dist/esm/SubscribableData.js +23 -0
  16. package/dist/esm/SubscribableData.js.map +1 -0
  17. package/dist/esm/SubscriptionManager.js +46 -0
  18. package/dist/esm/SubscriptionManager.js.map +1 -0
  19. package/dist/esm/index.js +157 -0
  20. package/dist/esm/index.js.map +1 -0
  21. package/dist/esm/utils/clone.js +17 -0
  22. package/dist/esm/utils/clone.js.map +1 -0
  23. package/dist/esm/utils/isEqual.js +27 -0
  24. package/dist/esm/utils/isEqual.js.map +1 -0
  25. package/dist/esm/utils/useDeepEqualMemo.js +15 -0
  26. package/dist/esm/utils/useDeepEqualMemo.js.map +1 -0
  27. package/dist/types/SubscribableData.d.ts +11 -0
  28. package/dist/types/SubscribableData.d.ts.map +1 -0
  29. package/dist/types/SubscriptionManager.d.ts +19 -0
  30. package/dist/types/SubscriptionManager.d.ts.map +1 -0
  31. package/dist/types/index.d.ts +57 -0
  32. package/dist/types/index.d.ts.map +1 -0
  33. package/dist/types/utils/clone.d.ts +3 -0
  34. package/dist/types/utils/clone.d.ts.map +1 -0
  35. package/dist/types/utils/isEqual.d.ts +3 -0
  36. package/dist/types/utils/isEqual.d.ts.map +1 -0
  37. package/dist/types/utils/useDeepEqualMemo.d.ts +4 -0
  38. package/dist/types/utils/useDeepEqualMemo.d.ts.map +1 -0
  39. 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 [![NPM version](https://img.shields.io/npm/v/@os-design/form)](https://yarnpkg.com/package/@os-design/form) [![BundlePhobia](https://img.shields.io/bundlephobia/minzip/@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"}