@zajno/common 1.2.7 → 1.3.1

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 (142) hide show
  1. package/coverage/clover.xml +357 -97
  2. package/coverage/coverage-final.json +19 -11
  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 +1 -1
  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 +57 -21
  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 +542 -0
  46. package/coverage/lcov-report/src/viewModels/NumberModel.ts.html +188 -0
  47. package/coverage/lcov-report/src/viewModels/SelectModel.ts.html +455 -0
  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.info +718 -147
  53. package/lib/timeHelper.d.ts +3 -0
  54. package/lib/timeHelper.d.ts.map +1 -1
  55. package/lib/timeHelper.js +3 -0
  56. package/lib/timeHelper.js.map +1 -1
  57. package/lib/transitionObserver.d.ts +1 -1
  58. package/lib/transitionObserver.d.ts.map +1 -1
  59. package/lib/transitionObserver.js +12 -2
  60. package/lib/transitionObserver.js.map +1 -1
  61. package/lib/types.d.ts +5 -0
  62. package/lib/types.d.ts.map +1 -1
  63. package/lib/types.js +14 -0
  64. package/lib/types.js.map +1 -1
  65. package/lib/viewModels/FlagModel.d.ts +19 -0
  66. package/lib/viewModels/FlagModel.d.ts.map +1 -0
  67. package/lib/viewModels/FlagModel.js +38 -0
  68. package/lib/viewModels/FlagModel.js.map +1 -0
  69. package/lib/viewModels/MultiSelectModel.d.ts +41 -0
  70. package/lib/viewModels/MultiSelectModel.d.ts.map +1 -0
  71. package/lib/viewModels/MultiSelectModel.js +153 -0
  72. package/lib/viewModels/MultiSelectModel.js.map +1 -0
  73. package/lib/viewModels/NumberModel.d.ts +16 -0
  74. package/lib/viewModels/NumberModel.d.ts.map +1 -0
  75. package/lib/viewModels/NumberModel.js +40 -0
  76. package/lib/viewModels/NumberModel.js.map +1 -0
  77. package/lib/viewModels/PromptModal.d.ts +31 -0
  78. package/lib/viewModels/PromptModal.d.ts.map +1 -0
  79. package/lib/viewModels/PromptModal.js +57 -0
  80. package/lib/viewModels/PromptModal.js.map +1 -0
  81. package/lib/viewModels/{SelectViewModel.d.ts → SelectModel.d.ts} +14 -8
  82. package/lib/viewModels/SelectModel.d.ts.map +1 -0
  83. package/lib/viewModels/SelectModel.js +109 -0
  84. package/lib/viewModels/SelectModel.js.map +1 -0
  85. package/lib/viewModels/{TextInputViewModel.d.ts → TextModel.d.ts} +10 -9
  86. package/lib/viewModels/TextModel.d.ts.map +1 -0
  87. package/lib/viewModels/{TextInputViewModel.js → TextModel.js} +8 -23
  88. package/lib/viewModels/TextModel.js.map +1 -0
  89. package/lib/viewModels/Validatable.d.ts +9 -7
  90. package/lib/viewModels/Validatable.d.ts.map +1 -1
  91. package/lib/viewModels/Validatable.js +22 -8
  92. package/lib/viewModels/Validatable.js.map +1 -1
  93. package/lib/viewModels/ValuesCollector.d.ts +28 -0
  94. package/lib/viewModels/ValuesCollector.d.ts.map +1 -0
  95. package/lib/viewModels/ValuesCollector.js +51 -0
  96. package/lib/viewModels/ValuesCollector.js.map +1 -0
  97. package/lib/viewModels/index.d.ts +7 -5
  98. package/lib/viewModels/index.d.ts.map +1 -1
  99. package/lib/viewModels/index.js +7 -5
  100. package/lib/viewModels/index.js.map +1 -1
  101. package/lib/viewModels/wrappers.d.ts +15 -0
  102. package/lib/viewModels/wrappers.d.ts.map +1 -0
  103. package/lib/viewModels/wrappers.js +43 -0
  104. package/lib/viewModels/wrappers.js.map +1 -0
  105. package/package.json +1 -1
  106. package/src/timeHelper.ts +3 -0
  107. package/src/transitionObserver.ts +14 -2
  108. package/src/types.ts +16 -0
  109. package/src/viewModels/FlagModel.ts +43 -0
  110. package/src/viewModels/MultiSelectModel.ts +154 -0
  111. package/src/viewModels/NumberModel.ts +36 -0
  112. package/src/viewModels/PromptModal.ts +65 -0
  113. package/src/viewModels/SelectModel.ts +125 -0
  114. package/src/viewModels/{TextInputViewModel.ts → TextModel.ts} +22 -26
  115. package/src/viewModels/Validatable.ts +26 -11
  116. package/src/viewModels/ValuesCollector.ts +84 -0
  117. package/src/viewModels/__tests__/multiSelect.test.ts +23 -0
  118. package/src/viewModels/__tests__/select.test.ts +71 -0
  119. package/src/viewModels/__tests__/wrappers.test.ts +79 -0
  120. package/src/viewModels/index.ts +9 -5
  121. package/src/viewModels/wrappers.ts +53 -0
  122. package/lib/viewModels/PromptModalViewModel.d.ts +0 -27
  123. package/lib/viewModels/PromptModalViewModel.d.ts.map +0 -1
  124. package/lib/viewModels/PromptModalViewModel.js +0 -56
  125. package/lib/viewModels/PromptModalViewModel.js.map +0 -1
  126. package/lib/viewModels/RadioButtonGroupViewModel.d.ts +0 -11
  127. package/lib/viewModels/RadioButtonGroupViewModel.d.ts.map +0 -1
  128. package/lib/viewModels/RadioButtonGroupViewModel.js +0 -51
  129. package/lib/viewModels/RadioButtonGroupViewModel.js.map +0 -1
  130. package/lib/viewModels/RadioButtonViewModel.d.ts +0 -9
  131. package/lib/viewModels/RadioButtonViewModel.d.ts.map +0 -1
  132. package/lib/viewModels/RadioButtonViewModel.js +0 -32
  133. package/lib/viewModels/RadioButtonViewModel.js.map +0 -1
  134. package/lib/viewModels/SelectViewModel.d.ts.map +0 -1
  135. package/lib/viewModels/SelectViewModel.js +0 -88
  136. package/lib/viewModels/SelectViewModel.js.map +0 -1
  137. package/lib/viewModels/TextInputViewModel.d.ts.map +0 -1
  138. package/lib/viewModels/TextInputViewModel.js.map +0 -1
  139. package/src/viewModels/PromptModalViewModel.ts +0 -71
  140. package/src/viewModels/RadioButtonGroupViewModel.ts +0 -47
  141. package/src/viewModels/RadioButtonViewModel.ts +0 -26
  142. package/src/viewModels/SelectViewModel.ts +0 -88
@@ -81,7 +81,7 @@ export class TransitionObserver<T> implements IDisposable {
81
81
  return this;
82
82
  }
83
83
 
84
- getPromise() {
84
+ getPromise(timeout: number = null) {
85
85
  if (!this._promise) {
86
86
  if (!this.isObserving) {
87
87
  return Promise.reject(new Error('Cannot get promise for disposed TransitionObserver'));
@@ -89,7 +89,19 @@ export class TransitionObserver<T> implements IDisposable {
89
89
 
90
90
  this._promise = new Promise<T>((resolve, reject) => {
91
91
  this._promiseReject = reject;
92
- this._cb = (v => this._finishPromise(resolve, v));
92
+
93
+ let timeoutHandle: any = null;
94
+ if (timeout) {
95
+ timeoutHandle = setTimeout(() => {
96
+ this._finishPromise(this._promiseReject, new Error(`TransitionObserver Aborted – timed out after ${timeout}ms`));
97
+ }, timeout);
98
+ }
99
+
100
+ this._cb = (v => {
101
+ clearTimeout(timeoutHandle);
102
+ this._finishPromise(resolve, v);
103
+ });
104
+ this.forceCheck();
93
105
  });
94
106
  this.logger.log('started a new promise...');
95
107
  }
package/src/types.ts CHANGED
@@ -3,3 +3,19 @@ export { DeepPartial } from './deepPartial';
3
3
  export type DeepReadonly<T> = {
4
4
  readonly [P in keyof T]: DeepReadonly<T[P]>;
5
5
  };
6
+
7
+ export type Getter<T> = (() => T) | T | null;
8
+
9
+ export namespace Getter {
10
+ export function getValue<T>(getter: Getter<T>): T {
11
+ if (getter == null) {
12
+ return null;
13
+ }
14
+ if (typeof getter === 'function') {
15
+ return (getter as () => T)();
16
+ }
17
+ return getter;
18
+ }
19
+ }
20
+
21
+ export type Predicate<T> = (value: T) => boolean;
@@ -0,0 +1,43 @@
1
+ import { action, makeObservable, observable } from 'mobx';
2
+ import { ILabel } from './wrappers';
3
+ import { IValueModel } from './ValuesCollector';
4
+
5
+ export interface IFlagModel extends IValueModel<boolean> {
6
+ toggle(): void;
7
+ reset(): void;
8
+ }
9
+
10
+ export type IFlagModelReadonly = {
11
+ readonly value: boolean;
12
+ };
13
+
14
+ export type ILabeledFlagModel = IFlagModel & ILabel<string>;
15
+
16
+ export class FlagModel implements IFlagModel, IFlagModelReadonly {
17
+
18
+ @observable
19
+ private _value: boolean = false;
20
+
21
+ constructor(initial = false) {
22
+ makeObservable(this);
23
+ this._value = initial;
24
+ }
25
+
26
+ get value() {
27
+ return this._value;
28
+ }
29
+
30
+ set value(value: boolean) {
31
+ this._value = value;
32
+ }
33
+
34
+ @action
35
+ toggle = () => {
36
+ this._value = !this._value;
37
+ };
38
+
39
+ @action
40
+ reset = () => {
41
+ this._value = false;
42
+ };
43
+ }
@@ -0,0 +1,154 @@
1
+ import { createLazy } from '../lazy.light';
2
+ import { action, computed, makeObservable, observable, reaction } from 'mobx';
3
+ import { FlagModel, ILabeledFlagModel } from './FlagModel';
4
+ import { ValidatableModel } from './Validatable';
5
+ import { IValueModel } from './ValuesCollector';
6
+ import { withLabel } from './wrappers';
7
+
8
+ export class MultiSelect<T = any> extends ValidatableModel<ReadonlyArray<T>> implements IValueModel<readonly string[]> {
9
+
10
+ @observable
11
+ private readonly _indexes = new Set<number>();
12
+
13
+ public readonly opened = new FlagModel();
14
+ private readonly _initial: number[] = null;
15
+
16
+ private readonly _flags = createLazy(() => this.createFlags());
17
+ private _indexesLocked = false;
18
+
19
+ constructor(
20
+ private readonly _items: readonly T[],
21
+ private readonly _accessor: (item: T) => string,
22
+ ...selected: number[]
23
+ ) {
24
+ super();
25
+ makeObservable(this);
26
+ this._initial = selected;
27
+ this.setInitialIndexes();
28
+ }
29
+
30
+ @computed
31
+ get selectedIndexes(): ReadonlyArray<number> { return Array.from(this._indexes); }
32
+
33
+ get items(): ReadonlyArray<T> { return this._items; }
34
+
35
+ get flags() { return this._flags.value; }
36
+
37
+ @computed
38
+ get values(): ReadonlyArray<string> {
39
+ return this._items.map(i => this._accessor(i));
40
+ }
41
+
42
+ @computed
43
+ get selectedItems(): ReadonlyArray<T> {
44
+ return this.selectedIndexes.map(i => this._items[i]);
45
+ }
46
+
47
+ @computed
48
+ get selectedValues(): ReadonlyArray<string> {
49
+ const values = this.values;
50
+ return this.selectedIndexes.map(i => values[i]);
51
+ }
52
+
53
+ get value() { return this.selectedValues; }
54
+ set value(v: readonly string[]) { this.selectValues(v); }
55
+
56
+ isIndexSelected(index: number) { return this._indexes.has(index); }
57
+ isValueSelected(value: string) { return this.values.includes(value); }
58
+
59
+ get isEmpty() { return this._indexes.size === 0; }
60
+
61
+ protected get valueToValidate() { return this.selectedItems; }
62
+
63
+ setItemSelected = (item: T, selected: boolean) => {
64
+ const i = this.items.indexOf(item);
65
+ if (i >= 0) {
66
+ this.setIndexSelected(i, selected);
67
+ }
68
+ };
69
+
70
+ selectItem = (item: T) => this.setItemSelected(item, true);
71
+ deSelectItem = (item: T) => this.setItemSelected(item, false);
72
+
73
+ @action
74
+ selectItems = (items: readonly T[]) => {
75
+ items.forEach(this.selectItem);
76
+ };
77
+
78
+ setValueSelected = (value: string, selected: boolean) => {
79
+ const i = this.values.indexOf(value);
80
+ if (i >= 0) {
81
+ this.setIndexSelected(i, selected);
82
+ }
83
+ };
84
+
85
+ selectValue = (value: string) => this.setValueSelected(value, true);
86
+ deSelectValue = (value: string) => this.setValueSelected(value, false);
87
+
88
+ @action
89
+ selectValues = (values: readonly string[]) => {
90
+ values.forEach(this.selectValue);
91
+ };
92
+
93
+ @action
94
+ setIndexSelected = (index: number, selected: boolean) => {
95
+ if (this._indexesLocked) {
96
+ return;
97
+ }
98
+
99
+ if (selected) {
100
+ this._indexes.add(index);
101
+ } else {
102
+ this._indexes.delete(index);
103
+ }
104
+
105
+ if (!this._flags.hasValue) {
106
+ return;
107
+ }
108
+
109
+ try {
110
+ this._indexesLocked = true;
111
+ this._flags.value[index].value = selected;
112
+ } finally {
113
+ this._indexesLocked = false;
114
+ }
115
+ };
116
+
117
+ reset = () => {
118
+ super.reset();
119
+ this.setInitialIndexes();
120
+ };
121
+
122
+ private setInitialIndexes() {
123
+ this._indexes.clear();
124
+ this._initial.forEach(i => this._indexes.add(i));
125
+
126
+ if (this._flags.hasValue) {
127
+ this._flags.value.forEach((flag, index) => flag.value = this._indexes.has(index));
128
+ }
129
+ }
130
+
131
+ private createFlags() {
132
+ const flags: ReadonlyArray<ILabeledFlagModel> = this._items
133
+ .map((item, index) => {
134
+ const flag = withLabel(
135
+ new FlagModel(this._indexes.has(index)),
136
+ () => this._accessor(item),
137
+ );
138
+
139
+ // react on every flag is changed directly
140
+ reaction(() => flag.value, isSelected => {
141
+ this.setIndexSelected(index, isSelected);
142
+ });
143
+ return flag;
144
+ });
145
+ return flags;
146
+ }
147
+ }
148
+
149
+ export class MultiSelectString<T extends string = string> extends MultiSelect<T> {
150
+
151
+ constructor(items: ReadonlyArray<T>, ...selected: number[]) {
152
+ super(items, v => v, ...selected);
153
+ }
154
+ }
@@ -0,0 +1,36 @@
1
+ import { action, makeObservable, observable } from 'mobx';
2
+ import { IValueModel } from './ValuesCollector';
3
+
4
+ export interface INumberModel {
5
+ value: number;
6
+ }
7
+
8
+ export class NumberModel implements INumberModel, IValueModel<number> {
9
+
10
+ @observable
11
+ private _value: number = 0;
12
+
13
+ private _initial: number = 0;
14
+
15
+ constructor(initial: number = 0) {
16
+ makeObservable(this);
17
+ this._initial = initial;
18
+ this._value = this._initial;
19
+ }
20
+
21
+ get value() { return this._value; }
22
+ set value(v: number) { this._value = v; }
23
+
24
+ @action
25
+ reset = () => {
26
+ this._value = this._initial;
27
+ };
28
+
29
+ @action
30
+ increment = (d = 1) => this.value += d;
31
+
32
+ @action
33
+ decrement = (d = 1) => this.value -= d;
34
+ }
35
+
36
+ export default NumberModel;
@@ -0,0 +1,65 @@
1
+ import { observable, makeObservable, action } from 'mobx';
2
+ import { FlagModel } from './FlagModel';
3
+
4
+ export type BaseModalAction = {
5
+ title?: string;
6
+ message?: string;
7
+ };
8
+
9
+ export class ModalActionModel<T extends BaseModalAction = BaseModalAction> {
10
+ public readonly isActive = new FlagModel();
11
+
12
+ @observable.ref
13
+ private _currentAction: T = null;
14
+
15
+ constructor() {
16
+ makeObservable(this);
17
+ }
18
+
19
+ get currentAction() { return this._currentAction; }
20
+
21
+ @action
22
+ public openModal = (action: T) => {
23
+ this._currentAction = action;
24
+ this.isActive.value = true;
25
+ };
26
+
27
+ @action
28
+ public closeModal = () => {
29
+ this.isActive.value = false;
30
+ this._currentAction = null;
31
+ };
32
+
33
+ protected runAction = async (cb: () => Promise<void> | void, close = true, awaitAction = false) => {
34
+ if (cb) {
35
+ const promise = cb();
36
+ if (awaitAction) {
37
+ await promise;
38
+ }
39
+ }
40
+
41
+ if (close) {
42
+ this.closeModal();
43
+ }
44
+ };
45
+ }
46
+
47
+ export type PromptModalAction = BaseModalAction & {
48
+ confirmText?: string;
49
+ rejectText?: string;
50
+ onConfirm?: () => Promise<void> | void;
51
+ onReject?: () => Promise<void> | void;
52
+ awaitActions?: boolean;
53
+ modalImage?: any;
54
+ confirmColor?: string;
55
+ rejectColor?: string;
56
+ };
57
+
58
+ export class PromptModalViewModel extends ModalActionModel<PromptModalAction> {
59
+ get confirmColor() { return this.currentAction.confirmColor; }
60
+
61
+ get rejectColor() { return this.currentAction.rejectColor; }
62
+
63
+ onConfirm = () => this.runAction(this.currentAction.onConfirm, true, this.currentAction.awaitActions);
64
+ onReject = () => this.runAction(this.currentAction.onConfirm, true, this.currentAction.awaitActions);
65
+ }
@@ -0,0 +1,125 @@
1
+ import { createLazy } from '../lazy.light';
2
+ import { observable, computed, makeObservable, reaction } from 'mobx';
3
+ import { FlagModel, ILabeledFlagModel } from './FlagModel';
4
+ import { ValidatableModel } from './Validatable';
5
+ import { IValueModel } from './ValuesCollector';
6
+ import { withLabel } from './wrappers';
7
+
8
+ export class Select<T = any> extends ValidatableModel<T> implements IValueModel<string> {
9
+ @observable
10
+ private _index: number = undefined;
11
+
12
+ public readonly opened = new FlagModel();
13
+ private _indexLocked = false;
14
+ private _initialIndex: number = null;
15
+
16
+ private readonly _flags = createLazy(() => this.createFlags());
17
+
18
+ constructor(
19
+ private readonly _items: readonly T[],
20
+ private readonly _accessor: (item: T) => string,
21
+ initialIndex: number = 0,
22
+ ) {
23
+ super();
24
+ makeObservable(this);
25
+
26
+ this._initialIndex = initialIndex;
27
+ this._index = initialIndex;
28
+ }
29
+
30
+ protected get valueToValidate() { return this.selectedItem; }
31
+
32
+ @computed
33
+ get values(): readonly string[] {
34
+ return this._items.map(i => this._accessor(i));
35
+ }
36
+
37
+ get flags() {
38
+ return this._flags.value;
39
+ }
40
+
41
+ get items() {
42
+ return this._items;
43
+ }
44
+
45
+ get value() { return this.selectedValue; }
46
+ get selectedValue() {
47
+ const vs = this.values;
48
+ return vs.length ? vs[this._index] : null;
49
+ }
50
+
51
+ set value(v: string) { this.selectedValue = v; }
52
+ set selectedValue(value: string) {
53
+ const index = this.values.indexOf(value);
54
+ if (index >= 0) {
55
+ this.index = index;
56
+ }
57
+ }
58
+
59
+ get selectedItem(): T {
60
+ return this._items.length ? this._items[this._index] : null;
61
+ }
62
+
63
+ set selectedItem(item: T) {
64
+ const index = this._items.indexOf(item);
65
+ if (index >= 0) {
66
+ this.index = index;
67
+ }
68
+ }
69
+
70
+ get index() {
71
+ return this._index;
72
+ }
73
+
74
+ set index(val: number) {
75
+ if (this._indexLocked) {
76
+ return;
77
+ }
78
+
79
+ this._index = val;
80
+
81
+ // update all flags to be properly selected
82
+ try {
83
+ this._indexLocked = true;
84
+
85
+ if (this._flags.hasValue) {
86
+ this._flags.value.forEach((f, i) => f.value = (i === this._index));
87
+ }
88
+ } finally {
89
+ this._indexLocked = false;
90
+ }
91
+ }
92
+
93
+ reset = () => {
94
+ super.reset();
95
+ this.index = this._initialIndex;
96
+ };
97
+
98
+ private createFlags() {
99
+ const flags: ReadonlyArray<ILabeledFlagModel> = this._items
100
+ .map((item, index) => {
101
+ const flag: ILabeledFlagModel = withLabel(
102
+ new FlagModel(index === this.index),
103
+ () => this._accessor(item),
104
+ );
105
+
106
+ // react on every flag is changed directly
107
+ reaction(() => flag.value, isSelected => {
108
+ if (isSelected) {
109
+ this.index = index;
110
+ }
111
+ });
112
+
113
+ return flag;
114
+ });
115
+
116
+ return flags;
117
+ }
118
+ }
119
+
120
+
121
+ export class SelectString<T extends string = string> extends Select<T> {
122
+ constructor(items: readonly T[], initialIndex: number = 0) {
123
+ super(items, v => v, initialIndex);
124
+ }
125
+ }
@@ -1,40 +1,37 @@
1
- import { observable, autorun, computed, action, makeObservable } from 'mobx';
1
+ import { observable, computed, makeObservable, reaction } from 'mobx';
2
+ import { Getter } from '../types';
2
3
  import logger from '../logger';
3
- import { ValidatableViewModel, ValidationConfig } from './Validatable';
4
-
5
- export type StringGetter = (() => string) | string;
4
+ import { ValidatableModel, ValidationConfig } from './Validatable';
5
+ import { IValueModel } from './ValuesCollector';
6
6
 
7
7
  export type TextInputConfig = {
8
- name?: StringGetter;
9
- title?: StringGetter;
10
- value?: StringGetter;
8
+ name?: Getter<string>;
9
+ title?: Getter<string>;
10
+ value?: Getter<string>;
11
11
  async?: boolean;
12
12
 
13
- validation?: ValidationConfig;
13
+ validation?: ValidationConfig<string>;
14
14
  noSubscribe?: boolean;
15
15
  };
16
16
 
17
- function FromGetter(getter: StringGetter, setter: (val: string) => void, autorunDelay: number = null, noAutorun: boolean = null) {
18
- if (typeof getter === 'function') {
19
- if (noAutorun) {
20
- setter(getter());
21
- } else {
22
- autorun(() => {
23
- setter(getter());
24
- }, { delay: autorunDelay });
25
- return true;
26
- }
27
- } else if (typeof getter === 'string') {
28
- setter(getter);
17
+ function FromGetter(getter: Getter<string>, setter: (val: string) => void, autorunDelay: number = null, noAutorun: boolean = null) {
18
+ if (noAutorun || typeof getter !== 'function') {
19
+ setter(Getter.getValue(getter));
20
+ return null;
29
21
  }
30
- return false;
22
+
23
+ return reaction(
24
+ () => Getter.getValue(getter),
25
+ setter,
26
+ { delay: autorunDelay, fireImmediately: true },
27
+ );
31
28
  }
32
29
 
33
30
  export class Text {
34
31
  @observable
35
32
  private _value: string = null;
36
33
 
37
- constructor(config: { value: StringGetter, async?: boolean, noSubscribe?: boolean }) {
34
+ constructor(config: { value: Getter<string>, async?: boolean, noSubscribe?: boolean }) {
38
35
  makeObservable(this);
39
36
  FromGetter(config.value, val => this._value = val, config.async ? 100 : null, config.noSubscribe);
40
37
  }
@@ -42,7 +39,7 @@ export class Text {
42
39
  get value() { return this._value; }
43
40
  }
44
41
 
45
- export class TextInputVM extends ValidatableViewModel {
42
+ export class TextInputVM extends ValidatableModel implements IValueModel<string> {
46
43
  @observable
47
44
  private _value = '';
48
45
 
@@ -55,7 +52,7 @@ export class TextInputVM extends ValidatableViewModel {
55
52
  @observable
56
53
  private _title: string = null;
57
54
 
58
- private readonly _valueObserving: boolean = null;
55
+ private readonly _valueObserving: () => void = null;
59
56
 
60
57
  constructor(config?: TextInputConfig) {
61
58
  super(config && config.validation);
@@ -100,13 +97,12 @@ export class TextInputVM extends ValidatableViewModel {
100
97
  super.reset();
101
98
  }
102
99
 
103
- protected get valueToValidate() { return this.value; }
100
+ protected get valueToValidate() { return (this.value ?? '').trim(); }
104
101
 
105
102
  private onBlur() {
106
103
  this.validate();
107
104
  }
108
105
 
109
- @action
110
106
  reset() {
111
107
  this._value = '';
112
108
  this._focused = false;
@@ -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
  }