@ptsecurity/mosaic 15.9.4 → 15.9.6

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.
@@ -2,17 +2,17 @@ import * as i3$1 from '@angular/cdk/a11y';
2
2
  import { A11yModule } from '@angular/cdk/a11y';
3
3
  import { CommonModule } from '@angular/common';
4
4
  import * as i0 from '@angular/core';
5
- import { Directive, Optional, Self, Attribute, Input, InjectionToken, Inject, forwardRef, Component, ChangeDetectionStrategy, ViewEncapsulation, NgModule } from '@angular/core';
5
+ import { forwardRef, EventEmitter, Directive, Attribute, Input, InjectionToken, Optional, Self, Inject, Component, ChangeDetectionStrategy, ViewEncapsulation, NgModule } from '@angular/core';
6
6
  import * as i1 from '@angular/forms';
7
- import { NG_VALIDATORS, Validators, FormsModule } from '@angular/forms';
7
+ import { NG_VALUE_ACCESSOR, NG_VALIDATORS, Validators, FormsModule } from '@angular/forms';
8
8
  import * as i3 from '@ptsecurity/mosaic/core';
9
9
  import { mixinErrorState, PopUpTriggers, McCommonModule } from '@ptsecurity/mosaic/core';
10
10
  import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';
11
11
  import { getSupportedInputTypes } from '@angular/cdk/platform';
12
12
  import * as i4 from '@ptsecurity/mosaic/form-field';
13
13
  import { McFormFieldControl } from '@ptsecurity/mosaic/form-field';
14
- import { Subject } from 'rxjs';
15
- import { A, C, V, X, Z, F1, F12, ZERO, NINE, NUMPAD_ZERO, NUMPAD_NINE, NUMPAD_MINUS, DASH, FF_MINUS, DELETE, BACKSPACE, TAB, ESCAPE, ENTER, LEFT_ARROW, RIGHT_ARROW, HOME, END, UP_ARROW, DOWN_ARROW } from '@ptsecurity/cdk/keycodes';
14
+ import { Subject, Subscription } from 'rxjs';
15
+ import { A, C, V, X, Z, NUMPAD_MINUS, DASH, FF_MINUS, DELETE, BACKSPACE, TAB, ESCAPE, ENTER, LEFT_ARROW, RIGHT_ARROW, HOME, END, isFunctionKey, isNumberKey, isNumpadKey, UP_ARROW, DOWN_ARROW } from '@ptsecurity/cdk/keycodes';
16
16
  import * as i2 from '@angular/cdk/bidi';
17
17
  import * as i1$1 from '@angular/cdk/overlay';
18
18
  import { McTooltipTrigger, MC_TOOLTIP_SCROLL_STRATEGY } from '@ptsecurity/mosaic/tooltip';
@@ -23,6 +23,7 @@ function getMcInputUnsupportedTypeError(inputType) {
23
23
 
24
24
  const BIG_STEP = 10;
25
25
  const SMALL_STEP = 1;
26
+ // TODO: подставлять локализованный сплиттер
26
27
  function normalizeSplitter(value) {
27
28
  return value ? value.replace(/,/g, '.') : value;
28
29
  }
@@ -46,16 +47,33 @@ function add(value1, value2) {
46
47
  const precision = Math.max(getPrecision(value1), getPrecision(value2));
47
48
  return (value1 * precision + value2 * precision) / precision;
48
49
  }
50
+ const MC_NUMBER_INPUT_VALUE_ACCESSOR = {
51
+ provide: NG_VALUE_ACCESSOR,
52
+ useExisting: forwardRef(() => McNumberInput),
53
+ multi: true
54
+ };
49
55
  class McNumberInput {
50
- constructor(elementRef, ngControl, step, bigStep, min, max) {
56
+ constructor(elementRef, renderer, step, bigStep, min, max) {
51
57
  this.elementRef = elementRef;
52
- this.ngControl = ngControl;
53
- this.focused = false;
58
+ this.renderer = renderer;
59
+ /** Emits when the value changes (either due to user input or programmatic change). */
60
+ this.valueChange = new EventEmitter();
61
+ /** Emits when the disabled state has changed */
62
+ this.disabledChange = new EventEmitter();
54
63
  this.stateChanges = new Subject();
64
+ this.controlType = 'input-number';
65
+ this._disabled = false;
66
+ this.focused = false;
67
+ this.localeSubscription = Subscription.EMPTY;
68
+ // tslint:disable-next-line:no-empty
69
+ this.onTouched = () => { };
70
+ // tslint:disable-next-line:no-empty
71
+ this.cvaOnChange = () => { };
55
72
  this.step = isDigit(step) ? parseFloat(step) : SMALL_STEP;
56
73
  this.bigStep = isDigit(bigStep) ? parseFloat(bigStep) : BIG_STEP;
57
74
  this.min = isDigit(min) ? parseFloat(min) : -Infinity;
58
75
  this.max = isDigit(max) ? parseFloat(max) : Infinity;
76
+ setTimeout(() => this.nativeElement.type = 'text', 0);
59
77
  if ('valueAsNumber' in this.nativeElement) {
60
78
  Object.defineProperty(Object.getPrototypeOf(this.nativeElement), 'valueAsNumber', {
61
79
  // tslint:disable-next-line:no-reserved-keywords
@@ -66,9 +84,71 @@ class McNumberInput {
66
84
  });
67
85
  }
68
86
  }
87
+ get value() {
88
+ return this._value;
89
+ }
90
+ set value(value) {
91
+ const oldValue = this.value;
92
+ this._value = value;
93
+ if (oldValue !== value) {
94
+ this.setViewValue(this.formatNumber(value));
95
+ this.valueChange.emit(value);
96
+ }
97
+ }
98
+ get disabled() {
99
+ return this._disabled;
100
+ }
101
+ set disabled(value) {
102
+ const newValue = coerceBooleanProperty(value);
103
+ const element = this.nativeElement;
104
+ if (this._disabled !== newValue) {
105
+ this._disabled = newValue;
106
+ this.disabledChange.emit(newValue);
107
+ }
108
+ // We need to null check the `blur` method, because it's undefined during SSR.
109
+ if (newValue && element.blur) {
110
+ // Normally, native input elements automatically blur if they turn disabled. This behavior
111
+ // is problematic, because it would mean that it triggers another change detection cycle,
112
+ // which then causes a changed after checked error if the input element was focused before.
113
+ element.blur();
114
+ }
115
+ }
69
116
  get nativeElement() {
70
117
  return this.elementRef.nativeElement;
71
118
  }
119
+ get viewValue() {
120
+ return this.nativeElement.value;
121
+ }
122
+ get ngControl() {
123
+ return this.control;
124
+ }
125
+ ngOnDestroy() {
126
+ this.localeSubscription.unsubscribe();
127
+ this.valueChange.complete();
128
+ this.disabledChange.complete();
129
+ }
130
+ onContainerClick() {
131
+ this.focus();
132
+ }
133
+ focus() {
134
+ this.nativeElement.focus();
135
+ }
136
+ // Implemented as part of ControlValueAccessor.
137
+ writeValue(value) {
138
+ this.value = value;
139
+ }
140
+ // Implemented as part of ControlValueAccessor.
141
+ registerOnChange(fn) {
142
+ this.cvaOnChange = fn;
143
+ }
144
+ // Implemented as part of ControlValueAccessor.
145
+ registerOnTouched(fn) {
146
+ this.onTouched = fn;
147
+ }
148
+ // Implemented as part of ControlValueAccessor.
149
+ setDisabledState(isDisabled) {
150
+ this.disabled = isDisabled;
151
+ }
72
152
  focusChanged(isFocused) {
73
153
  if (isFocused !== this.focused) {
74
154
  this.focused = isFocused;
@@ -83,32 +163,31 @@ class McNumberInput {
83
163
  const isCtrlV = (e) => e.keyCode === V && (e.ctrlKey || e.metaKey);
84
164
  const isCtrlX = (e) => e.keyCode === X && (e.ctrlKey || e.metaKey);
85
165
  const isCtrlZ = (e) => e.keyCode === Z && (e.ctrlKey || e.metaKey);
86
- const isFKey = (e) => e.keyCode >= F1 && e.keyCode <= F12;
87
- const isNumber = (e) => (e.keyCode >= ZERO && e.keyCode <= NINE) ||
88
- (e.keyCode >= NUMPAD_ZERO && e.keyCode <= NUMPAD_NINE);
89
166
  const isPeriod = (e) => e.key === '.' || e.key === ',';
90
167
  const minuses = [NUMPAD_MINUS, DASH, FF_MINUS];
91
168
  const serviceKeys = [DELETE, BACKSPACE, TAB, ESCAPE, ENTER];
92
169
  const arrows = [LEFT_ARROW, RIGHT_ARROW];
93
170
  const allowedKeys = [HOME, END].concat(arrows).concat(serviceKeys).concat(minuses);
94
171
  if (minuses.includes(keyCode) &&
95
- (this.nativeElement.valueAsNumber || this.min >= 0 || this.nativeElement.validity?.badInput)) {
172
+ (this.viewValue.includes(event.key) || this.min >= 0)) {
96
173
  event.preventDefault();
97
174
  return;
98
175
  }
99
176
  if (allowedKeys.indexOf(keyCode) !== -1 ||
100
- isCtrlA(event) ||
101
- isCtrlC(event) ||
102
- isCtrlV(event) ||
103
- isCtrlX(event) ||
104
- isCtrlZ(event) ||
105
- isFKey(event) ||
106
- isPeriod(event)) {
177
+ [
178
+ isCtrlA,
179
+ isCtrlC,
180
+ isCtrlV,
181
+ isCtrlX,
182
+ isCtrlZ,
183
+ isFunctionKey,
184
+ isPeriod
185
+ ].some((fn) => fn(event))) {
107
186
  // let it happen, don't do anything
108
187
  return;
109
188
  }
110
189
  // Ensure that it is not a number and stop the keypress
111
- if (event.shiftKey || !isNumber(event)) {
190
+ if (event.shiftKey || !isNumberKey(event) && !isNumpadKey(event)) {
112
191
  event.preventDefault();
113
192
  // process steps
114
193
  const step = event.shiftKey ? this.bigStep : this.step;
@@ -120,48 +199,120 @@ class McNumberInput {
120
199
  }
121
200
  }
122
201
  }
202
+ onInput(event) {
203
+ const currentValueLength = this.formatNumber(this.value)?.length || 0;
204
+ setTimeout(() => {
205
+ const fromPaste = event.inputType === 'insertFromPaste';
206
+ let formattedValue;
207
+ if (fromPaste) {
208
+ formattedValue = this.formatNumber(this.valueFromPaste);
209
+ }
210
+ else {
211
+ /*this.viewValue is raw and should be reformatted to localized number */
212
+ formattedValue = this.formatViewValue();
213
+ const offsetWhenSeparatorAdded = 2;
214
+ Promise.resolve().then(() => {
215
+ if (Math.abs(this.viewValue.length - currentValueLength) === offsetWhenSeparatorAdded) {
216
+ const cursorPosition = Math.max(0, (this.nativeElement.selectionStart || 0) + Math.sign(this.viewValue.length - currentValueLength));
217
+ this.renderer.setProperty(this.nativeElement, 'selectionStart', cursorPosition);
218
+ this.renderer.setProperty(this.nativeElement, 'selectionEnd', cursorPosition);
219
+ }
220
+ });
221
+ }
222
+ this.setViewValue(formattedValue, !fromPaste);
223
+ this.viewToModelUpdate(formattedValue);
224
+ });
225
+ }
123
226
  onPaste(event) {
124
- if (!isDigit(normalizeSplitter(event.clipboardData.getData('text')))) {
227
+ this.valueFromPaste = this.checkAndNormalizeLocalizedNumber(event.clipboardData?.getData('text'));
228
+ if (this.valueFromPaste === null) {
125
229
  event.preventDefault();
126
230
  }
127
231
  }
128
232
  stepUp(step) {
129
- this.elementRef.nativeElement.focus();
130
- const res = Math.max(Math.min(add(this.nativeElement.valueAsNumber || 0, step), this.max), this.min);
131
- this.nativeElement.value = res.toString();
132
- this.viewToModelUpdate(this.nativeElement.valueAsNumber);
233
+ this.nativeElement.focus();
234
+ const res = Math.max(Math.min(add(this.value || 0, step), this.max), this.min);
235
+ this.setViewValue(this.formatNumber(res));
236
+ this._value = res;
237
+ this.cvaOnChange(res);
238
+ this.valueChange.emit(res);
133
239
  }
134
240
  stepDown(step) {
135
- this.elementRef.nativeElement.focus();
136
- const res = Math.min(Math.max(add(this.nativeElement.valueAsNumber || 0, -step), this.min), this.max);
137
- this.nativeElement.value = res.toString();
138
- this.viewToModelUpdate(this.nativeElement.valueAsNumber);
241
+ this.nativeElement.focus();
242
+ const res = Math.min(Math.max(add(this.value || 0, -step), this.min), this.max);
243
+ this.setViewValue(this.formatNumber(res));
244
+ this._value = res;
245
+ this.cvaOnChange(res);
246
+ this.valueChange.emit(res);
247
+ }
248
+ setViewValue(value, savePosition = false) {
249
+ const cursorPosition = this.nativeElement.selectionStart;
250
+ this.renderer.setProperty(this.nativeElement, 'value', value);
251
+ if (savePosition) {
252
+ this.renderer.setProperty(this.nativeElement, 'selectionStart', cursorPosition);
253
+ this.renderer.setProperty(this.nativeElement, 'selectionEnd', cursorPosition);
254
+ }
139
255
  }
140
- viewToModelUpdate(value) {
141
- if (this.ngControl) {
142
- this.ngControl.control.setValue(value);
256
+ viewToModelUpdate(newValue) {
257
+ const normalizedValue = newValue === null ? null : +this.normalizeNumber(newValue);
258
+ if (normalizedValue !== this.value) {
259
+ this._value = normalizedValue;
260
+ this.cvaOnChange(normalizedValue);
261
+ this.valueChange.emit(normalizedValue);
262
+ }
263
+ this.ngControl?.updateValueAndValidity({ emitEvent: false });
264
+ }
265
+ formatViewValue() {
266
+ if (this.viewValue === null || this.viewValue === '' || Number.isNaN(+this.normalizeNumber(this.viewValue))) {
267
+ return null;
268
+ }
269
+ return this.viewValue;
270
+ }
271
+ formatNumber(value) {
272
+ if (value === null || value === undefined) {
273
+ return null;
274
+ }
275
+ return value.toString();
276
+ }
277
+ /**
278
+ * Method that returns a string representation of a number without localized separators
279
+ */
280
+ normalizeNumber(value, _) {
281
+ if (value === null || value === undefined) {
282
+ return '';
283
+ }
284
+ return value.toString();
285
+ }
286
+ checkAndNormalizeLocalizedNumber(num) {
287
+ if (num === null || num === undefined) {
288
+ return null;
289
+ }
290
+ /* if some locale input config satisfies pasted number, try to normalise with selected locale config */
291
+ let numberOutput = null;
292
+ const normalized = +this.normalizeNumber(num);
293
+ if (!Number.isNaN(normalized)) {
294
+ numberOutput = normalized;
143
295
  }
296
+ return numberOutput;
144
297
  }
145
298
  }
146
- /** @nocollapse */ McNumberInput.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: McNumberInput, deps: [{ token: i0.ElementRef }, { token: i1.NgControl, optional: true, self: true }, { token: 'step', attribute: true }, { token: 'big-step', attribute: true }, { token: 'min', attribute: true }, { token: 'max', attribute: true }], target: i0.ɵɵFactoryTarget.Directive });
147
- /** @nocollapse */ McNumberInput.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.9", type: McNumberInput, selector: "input[mcInput][type=\"number\"]", inputs: { bigStep: "bigStep", step: "step", min: "min", max: "max" }, host: { listeners: { "blur": "focusChanged(false)", "focus": "focusChanged(true)", "paste": "onPaste($event)", "keydown": "onKeyDown($event)" } }, exportAs: ["mcNumericalInput"], ngImport: i0 });
299
+ /** @nocollapse */ McNumberInput.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: McNumberInput, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: 'step', attribute: true }, { token: 'big-step', attribute: true }, { token: 'min', attribute: true }, { token: 'max', attribute: true }], target: i0.ɵɵFactoryTarget.Directive });
300
+ /** @nocollapse */ McNumberInput.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.2.9", type: McNumberInput, selector: "input[mcInput][type=\"number\"]", inputs: { bigStep: "bigStep", step: "step", min: "min", max: "max", value: "value", disabled: "disabled" }, host: { listeners: { "blur": "focusChanged(false)", "focus": "focusChanged(true)", "paste": "onPaste($event)", "keydown": "onKeyDown($event)", "input": "onInput($event)" } }, providers: [MC_NUMBER_INPUT_VALUE_ACCESSOR], exportAs: ["mcNumericalInput"], ngImport: i0 });
148
301
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImport: i0, type: McNumberInput, decorators: [{
149
302
  type: Directive,
150
303
  args: [{
151
304
  selector: `input[mcInput][type="number"]`,
152
305
  exportAs: 'mcNumericalInput',
306
+ providers: [MC_NUMBER_INPUT_VALUE_ACCESSOR],
153
307
  host: {
154
308
  '(blur)': 'focusChanged(false)',
155
309
  '(focus)': 'focusChanged(true)',
156
310
  '(paste)': 'onPaste($event)',
157
- '(keydown)': 'onKeyDown($event)'
311
+ '(keydown)': 'onKeyDown($event)',
312
+ '(input)': 'onInput($event)'
158
313
  }
159
314
  }]
160
- }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1.NgControl, decorators: [{
161
- type: Optional
162
- }, {
163
- type: Self
164
- }] }, { type: undefined, decorators: [{
315
+ }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: undefined, decorators: [{
165
316
  type: Attribute,
166
317
  args: ['step']
167
318
  }] }, { type: undefined, decorators: [{
@@ -181,6 +332,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImpor
181
332
  type: Input
182
333
  }], max: [{
183
334
  type: Input
335
+ }], value: [{
336
+ type: Input
337
+ }], disabled: [{
338
+ type: Input
184
339
  }] } });
185
340
 
186
341
  const MC_INPUT_VALUE_ACCESSOR = new InjectionToken('MC_INPUT_VALUE_ACCESSOR');
@@ -934,5 +1089,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.9", ngImpor
934
1089
  * Generated bundle index. Do not edit.
935
1090
  */
936
1091
 
937
- export { BIG_STEP, MAX_VALIDATOR, MC_INPUT_VALUE_ACCESSOR, MIN_VALIDATOR, MaxValidator, McInput, McInputBase, McInputMixinBase, McInputModule, McInputMono, McInputPassword, McNumberInput, McPasswordToggle, MinValidator, SMALL_STEP, add, getPrecision, isDigit, isFloat, isInt, normalizeSplitter };
1092
+ export { BIG_STEP, MAX_VALIDATOR, MC_INPUT_VALUE_ACCESSOR, MC_NUMBER_INPUT_VALUE_ACCESSOR, MIN_VALIDATOR, MaxValidator, McInput, McInputBase, McInputMixinBase, McInputModule, McInputMono, McInputPassword, McNumberInput, McPasswordToggle, MinValidator, SMALL_STEP, add, getPrecision, isDigit, isFloat, isInt, normalizeSplitter };
938
1093
  //# sourceMappingURL=ptsecurity-mosaic-input.mjs.map