@zajno/common 1.2.6 → 1.3.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 (145) hide show
  1. package/coverage/clover.xml +410 -144
  2. package/coverage/coverage-final.json +38 -30
  3. package/coverage/lcov-report/index.html +34 -19
  4. package/coverage/lcov-report/src/__tests__/helpers/index.html +1 -1
  5. package/coverage/lcov-report/src/__tests__/helpers/main.ts.html +1 -1
  6. package/coverage/lcov-report/src/async/arrays.ts.html +5 -5
  7. package/coverage/lcov-report/src/async/index.html +1 -1
  8. package/coverage/lcov-report/src/dates/calc.ts.html +1 -1
  9. package/coverage/lcov-report/src/dates/convert.ts.html +1 -1
  10. package/coverage/lcov-report/src/dates/datex.ts.html +1 -1
  11. package/coverage/lcov-report/src/dates/format.ts.html +1 -1
  12. package/coverage/lcov-report/src/dates/index.html +1 -1
  13. package/coverage/lcov-report/src/dates/index.ts.html +1 -1
  14. package/coverage/lcov-report/src/dates/parse.ts.html +1 -1
  15. package/coverage/lcov-report/src/dates/period.ts.html +1 -1
  16. package/coverage/lcov-report/src/dates/shift.ts.html +1 -1
  17. package/coverage/lcov-report/src/dates/types.ts.html +1 -1
  18. package/coverage/lcov-report/src/dates/yearDate.ts.html +1 -1
  19. package/coverage/lcov-report/src/enumHelper.ts.html +11 -11
  20. package/coverage/lcov-report/src/event.ts.html +3 -3
  21. package/coverage/lcov-report/src/index.html +49 -19
  22. package/coverage/lcov-report/src/lazy.light.ts.html +155 -0
  23. package/coverage/lcov-report/src/logger/console.ts.html +1 -1
  24. package/coverage/lcov-report/src/logger/index.html +1 -1
  25. package/coverage/lcov-report/src/logger/index.ts.html +1 -1
  26. package/coverage/lcov-report/src/logger/named.ts.html +1 -1
  27. package/coverage/lcov-report/src/logger/proxy.ts.html +1 -1
  28. package/coverage/lcov-report/src/math/arrays.ts.html +1 -1
  29. package/coverage/lcov-report/src/math/calc.ts.html +1 -1
  30. package/coverage/lcov-report/src/math/distribution.ts.html +1 -1
  31. package/coverage/lcov-report/src/math/index.html +1 -1
  32. package/coverage/lcov-report/src/math/index.ts.html +1 -1
  33. package/coverage/lcov-report/src/transitionObserver.ts.html +113 -41
  34. package/coverage/lcov-report/src/types.ts.html +143 -0
  35. package/coverage/lcov-report/src/validation/ValidationErrors.ts.html +22 -22
  36. package/coverage/lcov-report/src/validation/creditCard.ts.html +4 -4
  37. package/coverage/lcov-report/src/validation/helpers.ts.html +6 -6
  38. package/coverage/lcov-report/src/validation/index.html +1 -1
  39. package/coverage/lcov-report/src/validation/index.ts.html +6 -6
  40. package/coverage/lcov-report/src/validation/types.ts.html +2 -2
  41. package/coverage/lcov-report/src/validation/validators.ts.html +5 -5
  42. package/coverage/lcov-report/src/validation/wrappers.ts.html +5 -5
  43. package/coverage/lcov-report/src/viewModels/FlagModel.ts.html +209 -0
  44. package/coverage/lcov-report/src/viewModels/LabeledFlagModel.ts.html +146 -0
  45. package/coverage/lcov-report/src/viewModels/MultiSelectModel.ts.html +530 -0
  46. package/coverage/lcov-report/src/viewModels/NumberModel.ts.html +188 -0
  47. package/coverage/lcov-report/{enumHelper.ts.html → src/viewModels/SelectModel.ts.html} +158 -152
  48. package/coverage/lcov-report/src/viewModels/SelectViewModel.ts.html +434 -0
  49. package/coverage/lcov-report/src/viewModels/Validatable.ts.html +329 -0
  50. package/coverage/lcov-report/src/viewModels/index.html +186 -0
  51. package/coverage/lcov-report/src/viewModels/wrappers.ts.html +239 -0
  52. package/coverage/lcov-report/transitionObserver.ts.html +77 -41
  53. package/coverage/lcov.info +765 -184
  54. package/lib/timeHelper.d.ts +3 -0
  55. package/lib/timeHelper.d.ts.map +1 -1
  56. package/lib/timeHelper.js +3 -0
  57. package/lib/timeHelper.js.map +1 -1
  58. package/lib/transitionObserver.d.ts +3 -1
  59. package/lib/transitionObserver.d.ts.map +1 -1
  60. package/lib/transitionObserver.js +25 -5
  61. package/lib/transitionObserver.js.map +1 -1
  62. package/lib/types.d.ts +5 -0
  63. package/lib/types.d.ts.map +1 -1
  64. package/lib/types.js +14 -0
  65. package/lib/types.js.map +1 -1
  66. package/lib/viewModels/FlagModel.d.ts +19 -0
  67. package/lib/viewModels/FlagModel.d.ts.map +1 -0
  68. package/lib/viewModels/FlagModel.js +38 -0
  69. package/lib/viewModels/FlagModel.js.map +1 -0
  70. package/lib/viewModels/MultiSelectModel.d.ts +41 -0
  71. package/lib/viewModels/MultiSelectModel.d.ts.map +1 -0
  72. package/lib/viewModels/MultiSelectModel.js +150 -0
  73. package/lib/viewModels/MultiSelectModel.js.map +1 -0
  74. package/lib/viewModels/NumberModel.d.ts +16 -0
  75. package/lib/viewModels/NumberModel.d.ts.map +1 -0
  76. package/lib/viewModels/NumberModel.js +40 -0
  77. package/lib/viewModels/NumberModel.js.map +1 -0
  78. package/lib/viewModels/PromptModal.d.ts +31 -0
  79. package/lib/viewModels/PromptModal.d.ts.map +1 -0
  80. package/lib/viewModels/PromptModal.js +57 -0
  81. package/lib/viewModels/PromptModal.js.map +1 -0
  82. package/lib/viewModels/{SelectViewModel.d.ts → SelectModel.d.ts} +14 -8
  83. package/lib/viewModels/SelectModel.d.ts.map +1 -0
  84. package/lib/viewModels/SelectModel.js +109 -0
  85. package/lib/viewModels/SelectModel.js.map +1 -0
  86. package/lib/viewModels/{TextInputViewModel.d.ts → TextModel.d.ts} +10 -9
  87. package/lib/viewModels/TextModel.d.ts.map +1 -0
  88. package/lib/viewModels/{TextInputViewModel.js → TextModel.js} +8 -23
  89. package/lib/viewModels/TextModel.js.map +1 -0
  90. package/lib/viewModels/Validatable.d.ts +9 -7
  91. package/lib/viewModels/Validatable.d.ts.map +1 -1
  92. package/lib/viewModels/Validatable.js +22 -8
  93. package/lib/viewModels/Validatable.js.map +1 -1
  94. package/lib/viewModels/ValuesCollector.d.ts +28 -0
  95. package/lib/viewModels/ValuesCollector.d.ts.map +1 -0
  96. package/lib/viewModels/ValuesCollector.js +51 -0
  97. package/lib/viewModels/ValuesCollector.js.map +1 -0
  98. package/lib/viewModels/index.d.ts +7 -5
  99. package/lib/viewModels/index.d.ts.map +1 -1
  100. package/lib/viewModels/index.js +7 -5
  101. package/lib/viewModels/index.js.map +1 -1
  102. package/lib/viewModels/wrappers.d.ts +15 -0
  103. package/lib/viewModels/wrappers.d.ts.map +1 -0
  104. package/lib/viewModels/wrappers.js +43 -0
  105. package/lib/viewModels/wrappers.js.map +1 -0
  106. package/package.json +1 -1
  107. package/src/__tests__/transitionObserver.test.ts +10 -5
  108. package/src/timeHelper.ts +3 -0
  109. package/src/transitionObserver.ts +32 -8
  110. package/src/types.ts +16 -0
  111. package/src/viewModels/FlagModel.ts +43 -0
  112. package/src/viewModels/MultiSelectModel.ts +150 -0
  113. package/src/viewModels/NumberModel.ts +36 -0
  114. package/src/viewModels/PromptModal.ts +65 -0
  115. package/src/viewModels/SelectModel.ts +125 -0
  116. package/src/viewModels/{TextInputViewModel.ts → TextModel.ts} +22 -26
  117. package/src/viewModels/Validatable.ts +26 -11
  118. package/src/viewModels/ValuesCollector.ts +84 -0
  119. package/src/viewModels/__tests__/multiSelect.test.ts +23 -0
  120. package/src/viewModels/__tests__/select.test.ts +71 -0
  121. package/src/viewModels/__tests__/wrappers.test.ts +79 -0
  122. package/src/viewModels/index.ts +9 -5
  123. package/src/viewModels/wrappers.ts +53 -0
  124. package/yarn-error.log +3709 -0
  125. package/lib/viewModels/PromptModalViewModel.d.ts +0 -27
  126. package/lib/viewModels/PromptModalViewModel.d.ts.map +0 -1
  127. package/lib/viewModels/PromptModalViewModel.js +0 -56
  128. package/lib/viewModels/PromptModalViewModel.js.map +0 -1
  129. package/lib/viewModels/RadioButtonGroupViewModel.d.ts +0 -11
  130. package/lib/viewModels/RadioButtonGroupViewModel.d.ts.map +0 -1
  131. package/lib/viewModels/RadioButtonGroupViewModel.js +0 -51
  132. package/lib/viewModels/RadioButtonGroupViewModel.js.map +0 -1
  133. package/lib/viewModels/RadioButtonViewModel.d.ts +0 -9
  134. package/lib/viewModels/RadioButtonViewModel.d.ts.map +0 -1
  135. package/lib/viewModels/RadioButtonViewModel.js +0 -32
  136. package/lib/viewModels/RadioButtonViewModel.js.map +0 -1
  137. package/lib/viewModels/SelectViewModel.d.ts.map +0 -1
  138. package/lib/viewModels/SelectViewModel.js +0 -88
  139. package/lib/viewModels/SelectViewModel.js.map +0 -1
  140. package/lib/viewModels/TextInputViewModel.d.ts.map +0 -1
  141. package/lib/viewModels/TextInputViewModel.js.map +0 -1
  142. package/src/viewModels/PromptModalViewModel.ts +0 -71
  143. package/src/viewModels/RadioButtonGroupViewModel.ts +0 -47
  144. package/src/viewModels/RadioButtonViewModel.ts +0 -26
  145. package/src/viewModels/SelectViewModel.ts +0 -88
@@ -1,15 +1,19 @@
1
- import { observable, makeObservable } from 'mobx';
1
+ import { observable, makeObservable, action } from 'mobx';
2
2
  import { ValidatorFunction, ValidatorFunctionAsync, ValidationErrors, ValidationError } from '../validation';
3
3
  import { someAsync } from '../async/arrays';
4
4
 
5
- export type ValueValidator = ValidatorFunction | ValidatorFunctionAsync;
5
+ export type ValueValidator<T> = ValidatorFunction<T> | ValidatorFunctionAsync<T>;
6
6
  export type ValidationErrorsStrings = { [code: number]: string };
7
7
 
8
- export type ValidationConfig = { validator: ValueValidator, errors: ValidationErrorsStrings };
8
+ export type ValidationConfig<T> = {
9
+ validator: ValueValidator<Readonly<T>>,
10
+ errors: ValidationErrorsStrings,
11
+ };
9
12
 
10
- export abstract class ValidatableViewModel {
13
+ const EmptyValidator = () => ValidationErrors.None;
14
+ export abstract class ValidatableModel<T = string> {
11
15
 
12
- private _validator: ValueValidator = null;
16
+ private _validator: ValueValidator<Readonly<T>> = null;
13
17
  private _strings: ValidationErrorsStrings = null;
14
18
 
15
19
  @observable
@@ -17,25 +21,30 @@ export abstract class ValidatableViewModel {
17
21
 
18
22
  private _validationError: ValidationError = null;
19
23
 
20
- constructor(config?: ValidationConfig) {
24
+ constructor(config?: ValidationConfig<T>) {
21
25
  makeObservable(this);
22
- this._validator = (config && config.validator) || (() => ValidationErrors.None);
23
- this._strings = config && config.errors;
26
+ this.setValidationConfig(config);
24
27
  }
25
28
 
26
- protected abstract get valueToValidate(): string;
29
+ protected abstract get valueToValidate(): Readonly<T>;
27
30
 
28
31
  get isValid() { return !this._error; }
29
32
 
30
33
  get error() { return this._error; }
31
34
 
35
+ public setValidationConfig(config?: ValidationConfig<T>) {
36
+ this._validator = config?.validator || EmptyValidator;
37
+ this._strings = config?.errors;
38
+ return this;
39
+ }
40
+
32
41
  async validate() {
33
42
  if (!this._validator) {
34
43
  return true;
35
44
  }
36
45
 
37
46
  try {
38
- const valid = await this._validator((this.valueToValidate ?? '').trim());
47
+ const valid = await this._validator(this.valueToValidate);
39
48
  this._validationError = valid === ValidationErrors.None
40
49
  ? null
41
50
  : new ValidationError('Unknown error', valid);
@@ -52,12 +61,18 @@ export abstract class ValidatableViewModel {
52
61
  return this._validationError == null;
53
62
  }
54
63
 
64
+ async getIsInvalid() {
65
+ const valid = await this.validate();
66
+ return !valid;
67
+ }
68
+
69
+ @action
55
70
  reset() {
56
71
  this._validationError = null;
57
72
  this._error = null;
58
73
  }
59
74
 
60
- static async IsSomeInvalid(validatables: ValidatableViewModel[], stopOnFail = true) {
75
+ static async IsSomeInvalid(validatables: ReadonlyArray<Readonly<ValidatableModel>>, stopOnFail = true) {
61
76
  if (stopOnFail) {
62
77
  return someAsync(validatables, async v => !(await v.validate()));
63
78
  }
@@ -0,0 +1,84 @@
1
+
2
+ export interface IValueModel<TValue> {
3
+ value: TValue;
4
+ }
5
+
6
+ export interface IValueCollector<TSource, TValue> {
7
+ getValue(source: TSource): TValue;
8
+ setValue(source: TSource, value: TValue): void;
9
+ }
10
+
11
+ type SimpleCollector<T> = {
12
+ get: () => T,
13
+ set: (t: T) => void,
14
+ };
15
+
16
+ type SimpleCollectorsMap<T> = {
17
+ [P in keyof T]?: SimpleCollector<T[P]>;
18
+ };
19
+
20
+ export type ModelCollectorsMap<T> = {
21
+ [P in keyof T]?: IValueModel<T[P]>;
22
+ };
23
+
24
+ export type CollectorsMap<T> = {
25
+ [P in keyof T]?: SimpleCollector<T[P]>;
26
+ };
27
+
28
+ export class ModelCollector<T extends Object> {
29
+
30
+ private readonly _collectors: SimpleCollectorsMap<T> = { };
31
+
32
+ public addModels(models: ModelCollectorsMap<T>) {
33
+ Object.entries(models).forEach(pair => this.addModel(pair[0] as keyof T, pair[1]));
34
+ return this;
35
+ }
36
+
37
+ public addModel<TKey extends keyof T, TSource extends IValueModel<T[TKey]>>(key: TKey, source: TSource) {
38
+ this._collectors[key] = {
39
+ get: () => source.value,
40
+ set: v => { source.value = v; },
41
+ };
42
+
43
+ return this;
44
+ }
45
+
46
+ public addCollector<TKey extends keyof T, TModel, TCollector extends IValueCollector<TModel, T[TKey]>>(key: TKey, model: TModel, collector: TCollector) {
47
+ this._collectors[key] = {
48
+ get: () => collector.getValue(model),
49
+ set: v => collector.setValue(model, v),
50
+ };
51
+
52
+ return this;
53
+ }
54
+
55
+ public remove(key: keyof T) {
56
+ delete this._collectors[key];
57
+
58
+ return this;
59
+ }
60
+
61
+ public collect(): T {
62
+ const res = { } as T;
63
+
64
+ Object.entries(this._collectors).forEach(pair => {
65
+ const key = pair[0] as keyof T;
66
+ const collector = pair[1] as SimpleCollector<T[keyof T]>;
67
+ res[key] = collector.get();
68
+ });
69
+
70
+ return res;
71
+ }
72
+
73
+ public populate(data: T) {
74
+ Object.entries(data).forEach(pair => {
75
+ const key = pair[0] as keyof T;
76
+ const value = pair[1] as T[keyof T];
77
+
78
+ const collector = this._collectors[key];
79
+ if (collector) {
80
+ collector.set(value);
81
+ }
82
+ });
83
+ }
84
+ }
@@ -0,0 +1,23 @@
1
+ import { MultiSelect } from '../MultiSelectModel';
2
+
3
+ describe('MultiSelectModel', () => {
4
+ it('consistent', () => {
5
+ expect(() => new MultiSelect(null, null).values).toThrow();
6
+ expect(() => new MultiSelect([1], null).values).toThrow();
7
+
8
+ const items = [1, 2, 3];
9
+ const accessor = (i: number) => `_${i}_`;
10
+ const values = items.map(accessor);
11
+ const initialSelected = [1];
12
+
13
+ const vm = new MultiSelect(items, accessor, ...initialSelected);
14
+
15
+ expect(vm.items).toStrictEqual(items);
16
+ expect(vm.values).toStrictEqual(values);
17
+ expect(vm.selectedIndexes).toStrictEqual(initialSelected);
18
+
19
+ initialSelected.forEach(index => {
20
+ expect(vm.isIndexSelected(index)).toBe(initialSelected.includes(index));
21
+ });
22
+ });
23
+ });
@@ -0,0 +1,71 @@
1
+ import { configure } from 'mobx';
2
+ import { Select, SelectString } from '../SelectModel';
3
+
4
+ configure({ enforceActions: 'never' });
5
+
6
+ describe('SelectViewModel', () => {
7
+ it('consistent', async () => {
8
+ expect(() => new Select(null, null).values).toThrow();
9
+ expect(() => new Select([1], null).values).toThrow();
10
+
11
+ const emptyVm = new Select([], null);
12
+ expect(emptyVm.selectedValue).toBeNull();
13
+
14
+ const items = [0, 1, 2];
15
+ const values = ['0!', '1!', '2!'];
16
+ const initialIndex = -1;
17
+ const vm = new Select(items, i => `${i}!`, initialIndex);
18
+
19
+ expect(vm.index).toEqual(initialIndex);
20
+ expect(vm.values).toStrictEqual(values);
21
+ expect(vm.items).toStrictEqual(items);
22
+
23
+ expect(vm.selectedItem).toBeUndefined();
24
+ expect(vm.selectedValue).toBeUndefined();
25
+
26
+ vm.selectedItem = 7;
27
+ expect(vm.index).toBe(initialIndex);
28
+
29
+ vm.selectedValue = '123';
30
+ expect(vm.index).toBe(initialIndex);
31
+
32
+ vm.opened.value = true;
33
+ expect(vm.opened.value).toBe(true);
34
+
35
+ vm.index = 1;
36
+ expect(vm.index).toBe(1);
37
+
38
+ expect(vm.flags).toHaveLength(items.length);
39
+ values.forEach((_currentValue, itemIndex) => {
40
+ vm.selectedValue = _currentValue;
41
+ expect(vm.selectedItem).toBe(items[itemIndex]);
42
+ vm.selectedItem = items[itemIndex];
43
+ expect(vm.selectedValue).toBe(_currentValue);
44
+ vm.flags.forEach((f, i) => {
45
+ expect(f.value).toBe(i === itemIndex);
46
+ expect(f.label).toBe(values[i]);
47
+ });
48
+ });
49
+
50
+ vm.flags.forEach((f, i) => {
51
+ f.value = true;
52
+ vm.flags.forEach((ff, ii) => {
53
+ expect(ff.value).toBe(i === ii);
54
+ });
55
+ expect(vm.index).toBe(i);
56
+ });
57
+
58
+ vm.selectedItem = items[1];
59
+ expect(vm.selectedValue).toBe(values[1]);
60
+
61
+ vm.reset();
62
+ expect(vm.index).toBe(initialIndex);
63
+
64
+ expect(vm.error).toBeNull();
65
+
66
+ await expect(vm.validate()).resolves.toBeTruthy();
67
+
68
+ const vmS = new SelectString(values);
69
+ expect(vmS.values).toStrictEqual(values);
70
+ });
71
+ });
@@ -0,0 +1,79 @@
1
+ import { inject, withLabel, mixinLabel } from '../wrappers';
2
+ import { NumberModel } from '../NumberModel';
3
+ import { FlagModel } from '../FlagModel';
4
+
5
+ describe('labeled', () => {
6
+ test('consistency', () => {
7
+
8
+ const initial = 0;
9
+ const num = new NumberModel(initial);
10
+
11
+ expect(num.value).toBe(initial);
12
+
13
+ const label = { str: '123' };
14
+ const labeledNum = withLabel(new NumberModel(), () => label);
15
+
16
+ expect(labeledNum.label.str).toEqual(label.str);
17
+
18
+ label.str = 'abc';
19
+ expect(labeledNum.label).toStrictEqual(label);
20
+ });
21
+ });
22
+
23
+ describe('inject', () => {
24
+ test('consistency/number', () => {
25
+ const num = new NumberModel();
26
+
27
+ num.value = 123;
28
+ expect(num.value).toBe(123);
29
+
30
+ inject(num, { value: 321 });
31
+
32
+ expect(num.value).toBe(321);
33
+
34
+ num.value = 123;
35
+ expect(num.value).toBe(123);
36
+
37
+ let external: number = 0;
38
+ inject(num, {
39
+ get value() { return external; },
40
+ set value(v) { external = v; },
41
+ });
42
+
43
+ expect(num.value).toBe(0);
44
+ num.value = 111;
45
+ expect(num.value).toBe(111);
46
+ expect(external).toBe(111);
47
+ });
48
+
49
+ test('consistency/flag', () => {
50
+ const flag = new FlagModel();
51
+
52
+ flag.value = true;
53
+ expect(flag.value).toBe(true);
54
+
55
+ inject(flag, { value: false });
56
+
57
+ expect(flag.value).toBe(false);
58
+
59
+ flag.value = true;
60
+ expect(flag.value).toBe(true);
61
+ });
62
+ });
63
+
64
+ describe('labelize', () => {
65
+ test('consistency', () => {
66
+
67
+ const Mixin = mixinLabel(FlagModel);
68
+
69
+ expect(() => new Mixin()).not.toThrow();
70
+
71
+ let label = '123';
72
+ const vm = new Mixin(() => label, false);
73
+ expect(vm.value).toBe(false);
74
+ expect(vm.label).toBe(label);
75
+
76
+ label = '312';
77
+ expect(vm.label).toBe(label);
78
+ });
79
+ });
@@ -1,6 +1,10 @@
1
- export * from './PromptModalViewModel';
2
- export * from './RadioButtonGroupViewModel';
3
- export * from './RadioButtonViewModel';
4
- export * from './SelectViewModel';
5
- export * from './TextInputViewModel';
6
1
  export * from './Validatable';
2
+ export * from './TextModel';
3
+ export * from './FlagModel';
4
+ export * from './NumberModel';
5
+ export * from './wrappers';
6
+
7
+ export * from './SelectModel';
8
+ export * from './MultiSelectModel';
9
+
10
+ export * from './PromptModal';
@@ -0,0 +1,53 @@
1
+ import { Getter } from '../types';
2
+ import { IValueModel } from './ValuesCollector';
3
+
4
+ export interface ILabel<T> {
5
+ readonly label: T;
6
+ }
7
+
8
+ export function withLabel<T, TModel extends IValueModel<T>, TLabel = string>(model: TModel, label: Getter<TLabel>) {
9
+ const _label = label;
10
+ Object.defineProperty(model, 'label', {
11
+ configurable: true,
12
+ enumerable: false,
13
+ get() { return Getter.getValue(_label); },
14
+ });
15
+
16
+ return model as TModel & ILabel<TLabel>;
17
+ }
18
+
19
+ export function inject<T, TModel extends IValueModel<T>>(model: TModel, source: IValueModel<T>) {
20
+ Object.defineProperty(model, 'value', {
21
+ configurable: true,
22
+ enumerable: false,
23
+ get() { return source.value; },
24
+ set(v: T) { source.value = v; },
25
+ });
26
+
27
+ return model;
28
+ }
29
+
30
+ export function wrap<T, TModel extends IValueModel<T>, TRes>(model: TModel, getter: (m: TModel) => TRes, setter: (v: TRes, model: TModel) => void): IValueModel<TRes> {
31
+ return {
32
+ get value() { return getter(model); },
33
+ set value(v: TRes) { setter(v, model); },
34
+ };
35
+ }
36
+
37
+ type LabeledModel<T, TModel extends IValueModel<T>, TLabel = string> = TModel & ILabel<TLabel>;
38
+
39
+ interface LabeledModelCtor<T, TModel extends IValueModel<T>, TLabel = string> {
40
+ new (label?: Getter<TLabel>, initial?: T): LabeledModel<T, TModel, TLabel>;
41
+ }
42
+
43
+ export function mixinLabel<T, TModel extends IValueModel<T>, TLabel = string>(Superclass: new (initial?: T) => TModel): LabeledModelCtor<T, TModel, TLabel> {
44
+ // @ts-ignore
45
+ class Sub extends Superclass {
46
+ constructor(label: Getter<TLabel>, initial?: T) {
47
+ super(initial);
48
+ withLabel(this, label);
49
+ }
50
+ }
51
+
52
+ return Sub as any;
53
+ }