@acorex/components 20.10.0 → 20.10.2

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.
@@ -1,17 +1,17 @@
1
1
  import { MXInputBaseValueComponent, AXComponent, AXFocusableComponent, AXValuableComponent, AXClearableComponent } from '@acorex/cdk/common';
2
2
  import { AXCalendarService } from '@acorex/core/date-time';
3
- import { AXFormatService } from '@acorex/core/format';
4
- import { AXLocaleService } from '@acorex/core/locale';
3
+ import { AXLocaleService, AXIRLocaleProfile } from '@acorex/core/locale';
5
4
  import { AXPlatform } from '@acorex/core/platform';
6
5
  import { getWordBoundsAtPosition } from '@acorex/core/utils';
7
6
  import * as i0 from '@angular/core';
8
- import { inject, signal, input, computed, linkedSignal, viewChild, output, effect, untracked, forwardRef, HostBinding, HostListener, ViewEncapsulation, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
7
+ import { inject, signal, input, output, computed, linkedSignal, viewChild, effect, untracked, forwardRef, ViewEncapsulation, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
9
8
  import * as i1 from '@angular/forms';
10
9
  import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
11
10
  import { map } from 'rxjs';
12
11
 
13
12
  /**
14
- * A component for date and time input with various custom features.
13
+ * A component for date and time input with segment-based typing, keyboard navigation,
14
+ * and calendar-aware formatting (gregorian and solar-hijri).
15
15
  *
16
16
  * @category Components
17
17
  */
@@ -19,12 +19,9 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
19
19
  constructor() {
20
20
  super(...arguments);
21
21
  this.platformService = inject(AXPlatform);
22
- this.formatService = inject(AXFormatService);
23
22
  this.localeService = inject(AXLocaleService);
24
23
  this.calendarService = inject(AXCalendarService);
25
- /**
26
- * @ignore
27
- */
24
+ /** @ignore */
28
25
  this._editingParts = {
29
26
  year: {
30
27
  key: 'year',
@@ -33,7 +30,7 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
33
30
  enabled: false,
34
31
  default: 2023,
35
32
  typedValue: null,
36
- realValue: () => this.formatService.format(this._editingDate(), 'datetime', this._editingParts.year.placeholder),
33
+ realValue: () => this._formatPart(this._editingDate(), this._editingParts.year.placeholder),
37
34
  },
38
35
  month: {
39
36
  key: 'month',
@@ -42,7 +39,7 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
42
39
  enabled: false,
43
40
  default: 1,
44
41
  typedValue: null,
45
- realValue: () => this.formatService.format(this._editingDate(), 'datetime', this._editingParts.month.placeholder),
42
+ realValue: () => this._formatPart(this._editingDate(), this._editingParts.month.placeholder),
46
43
  },
47
44
  day: {
48
45
  key: 'day',
@@ -51,7 +48,7 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
51
48
  enabled: false,
52
49
  default: 1,
53
50
  typedValue: null,
54
- realValue: () => this.formatService.format(this._editingDate(), 'datetime', this._editingParts.day.placeholder),
51
+ realValue: () => this._formatPart(this._editingDate(), this._editingParts.day.placeholder),
55
52
  },
56
53
  hour: {
57
54
  key: 'hour',
@@ -60,7 +57,7 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
60
57
  enabled: false,
61
58
  default: 0,
62
59
  typedValue: null,
63
- realValue: () => this.formatService.format(this._editingDate(), 'datetime', this._editingParts.hour.placeholder),
60
+ realValue: () => this._formatPart(this._editingDate(), this._editingParts.hour.placeholder),
64
61
  },
65
62
  minute: {
66
63
  key: 'minute',
@@ -69,7 +66,7 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
69
66
  enabled: false,
70
67
  default: 0,
71
68
  typedValue: null,
72
- realValue: () => this.formatService.format(this._editingDate(), 'datetime', this._editingParts.minute.placeholder),
69
+ realValue: () => this._formatPart(this._editingDate(), this._editingParts.minute.placeholder),
73
70
  },
74
71
  second: {
75
72
  key: 'second',
@@ -78,7 +75,7 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
78
75
  enabled: false,
79
76
  default: 0,
80
77
  typedValue: null,
81
- realValue: () => this.formatService.format(this._editingDate(), 'datetime', this._editingParts.second.placeholder),
78
+ realValue: () => this._formatPart(this._editingDate(), this._editingParts.second.placeholder),
82
79
  },
83
80
  AMPM: {
84
81
  key: 'AMPM',
@@ -87,19 +84,18 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
87
84
  enabled: false,
88
85
  default: 0,
89
86
  typedValue: null,
90
- realValue: () => this.formatService.format(this._editingDate(), 'datetime', this._editingParts.AMPM.placeholder),
87
+ realValue: () => this._formatPart(this._editingDate(), this._editingParts.AMPM.placeholder),
91
88
  },
92
89
  };
93
- /**
94
- * @ignore
95
- */
90
+ /** @ignore */
96
91
  this._editingText = signal('', ...(ngDevMode ? [{ debugName: "_editingText" }] : []));
97
- /**
98
- * @ignore
99
- */
92
+ /** @ignore */
100
93
  this._inputChars = [];
94
+ /** @ignore */
95
+ this._activePart = signal(null, ...(ngDevMode ? [{ debugName: "_activePart" }] : []));
101
96
  /**
102
- * Indicates whether typing is allowed in the input field.
97
+ * When true, the user can type digits and navigate segments with the keyboard.
98
+ * When false, keyboard input is blocked (used by datetime-box picker mode).
103
99
  * @defaultValue false
104
100
  */
105
101
  this.allowTyping = input(false, ...(ngDevMode ? [{ debugName: "allowTyping" }] : []));
@@ -107,45 +103,66 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
107
103
  * @description The calendar type to use for the datetime input.
108
104
  */
109
105
  this.calendar = input(null, ...(ngDevMode ? [{ debugName: "calendar" }] : []));
110
- this._calendarSystem = computed(() => this.calendar() ?? this.localeService.activeProfile().calendar.system, ...(ngDevMode ? [{ debugName: "_calendarSystem" }] : []));
111
- this._activePart = signal(null, ...(ngDevMode ? [{ debugName: "_activePart" }] : []));
112
- // TODO: fix this
113
- // #effect = effect(() => {
114
- // this._activePart()
115
- // this._clearInputBuffer();
116
- // });
117
- this._editingDate = linkedSignal(() => this.calendarService.now(this._calendarSystem()), ...(ngDevMode ? [{ debugName: "_editingDate" }] : []));
118
- /**
119
- * @ignore
120
- */
121
- this.input = viewChild('input', ...(ngDevMode ? [{ debugName: "input" }] : []));
122
106
  this.minValue = input(null, ...(ngDevMode ? [{ debugName: "minValue" }] : []));
123
107
  this.maxValue = input(null, ...(ngDevMode ? [{ debugName: "maxValue" }] : []));
124
108
  /**
125
- * Emitted when a click event occurs on the datetime box.
109
+ * Emitted when a click event occurs on the datetime input.
126
110
  * @event
127
111
  */
128
112
  this.onClick = output();
129
113
  this.picker = input('datetime', ...(ngDevMode ? [{ debugName: "picker" }] : []));
130
- this._resolvedFormat = computed(() => this.format() ?? this.localeService.activeProfile().formats[this.picker()]?.short ?? '', ...(ngDevMode ? [{ debugName: "_resolvedFormat" }] : []));
131
114
  /**
132
115
  * @deprecated use locale & mode instead
133
116
  */
134
117
  this.format = input(...(ngDevMode ? [undefined, { debugName: "format" }] : []));
135
- this.#effect = effect(() => {
118
+ this._calendarSystem = computed(() => this.calendar() ?? this.localeService.activeProfile().calendar.system, ...(ngDevMode ? [{ debugName: "_calendarSystem" }] : []));
119
+ this._resolvedFormat = computed(() => {
120
+ if (this.format()) {
121
+ return this.format();
122
+ }
123
+ // Calendar override can differ from the active locale; solar-hijri uses day/month/year.
124
+ const formats = this._calendarSystem() === 'solar-hijri'
125
+ ? AXIRLocaleProfile.formats
126
+ : this.localeService.activeProfile().formats;
127
+ return formats[this.picker()]?.short ?? '';
128
+ }, ...(ngDevMode ? [{ debugName: "_resolvedFormat" }] : []));
129
+ /** @ignore */
130
+ this._editingDate = linkedSignal(() => this.calendarService.now(this._calendarSystem()), ...(ngDevMode ? [{ debugName: "_editingDate" }] : []));
131
+ /** @ignore */
132
+ this.inputRef = viewChild('input', ...(ngDevMode ? [{ debugName: "inputRef" }] : []));
133
+ /**
134
+ * @description check if page is in rtl or ltr
135
+ */
136
+ this.isRtl = signal(this.platformService.isRtl(), ...(ngDevMode ? [{ debugName: "isRtl" }] : []));
137
+ /** @ignore */
138
+ this.#formatEffect = effect(() => {
136
139
  this._resolvedFormat();
137
140
  this._calendarSystem();
138
141
  this._detectParts();
139
142
  untracked(() => {
140
- this._updateText();
143
+ this.internalValueChanged(this.value);
141
144
  });
142
- }, ...(ngDevMode ? [{ debugName: "#effect" }] : []));
143
- /**
144
- * @description check if page is in rtl or ltr
145
- */
146
- this.isRtl = signal(this.platformService.isRtl(), ...(ngDevMode ? [{ debugName: "isRtl" }] : []));
145
+ }, ...(ngDevMode ? [{ debugName: "#formatEffect" }] : []));
147
146
  }
148
- #effect;
147
+ /** @ignore */
148
+ get inputElement() {
149
+ return this.inputRef()?.nativeElement;
150
+ }
151
+ /** @ignore */
152
+ #formatEffect;
153
+ /** @ignore */
154
+ ngOnInit() {
155
+ super.ngOnInit();
156
+ this.platformService.directionChange.pipe(map((i) => i.data === 'rtl')).subscribe((isRtl) => this.isRtl.set(isRtl));
157
+ }
158
+ /**
159
+ * Formats a part using the calendar already attached to the AXDateTime.
160
+ * @ignore
161
+ */
162
+ _formatPart(date, placeholder) {
163
+ return date.format(placeholder);
164
+ }
165
+ /** @ignore */
149
166
  _detectParts() {
150
167
  Object.values(this._editingParts).forEach((e) => {
151
168
  e.enabled = false;
@@ -159,17 +176,8 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
159
176
  }
160
177
  });
161
178
  }
162
- /**
163
- * @description listen to direction change to react to it.
164
- */
165
- ngOnInit() {
166
- this.platformService.directionChange.pipe(map((i) => i.data === 'rtl')).subscribe((isRtl) => this.isRtl.set(isRtl));
167
- }
168
- /**
169
- * @ignore
170
- */
179
+ /** @ignore */
171
180
  _getOrderedParts() {
172
- // TODO: better spliter format
173
181
  const formatParts = this._resolvedFormat().split(/[^a-zA-Z]+/);
174
182
  const result = [];
175
183
  formatParts.forEach((f) => {
@@ -179,18 +187,13 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
179
187
  });
180
188
  return result;
181
189
  }
182
- /**
183
- * @ignore
184
- */
190
+ /** @ignore */
185
191
  _clearInputBuffer() {
186
192
  this._inputChars = [];
187
193
  }
188
- /**
189
- * @ignore
190
- */
194
+ /** @ignore */
191
195
  _updateText() {
192
196
  let text = this._resolvedFormat();
193
- //
194
197
  Object.values(this._editingParts).forEach((part) => {
195
198
  if (this._activePart() == part.key) {
196
199
  if (part.typedValue)
@@ -207,52 +210,50 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
207
210
  this._highlightActivePart();
208
211
  }
209
212
  }
210
- //TODO: fix this
211
- /**
212
- * Sets the internal date value, adjusting it based on editing parts.
213
- * @param value - The date to set. Returns `undefined` if no value is provided.
214
- * @ignore
215
- */
216
- // override internalSetValue(value?: Date) {
217
- // console.log('internalSetValue', value);
218
- // if (value) {
219
- // let editingDate = this.calendarService.create(value, this._calendarSystem());
220
- // let applyChanges = false;
221
- // Object.values(this._editingParts).forEach((part) => {
222
- // if (!part.enabled) {
223
- // editingDate = editingDate.set(part.key as TimeUnit, part.default);
224
- // applyChanges = true;
225
- // }
226
- // });
227
- // if (applyChanges) {
228
- // return editingDate.date;
229
- // }
230
- // }
231
- // return value;
232
- // }
233
213
  /**
234
214
  * Handles changes to the internal date value, updating editing parts and text representation.
235
- *
236
- * @param value - The new date value. If not provided, resets editing parts.
237
215
  * @ignore
238
216
  */
239
217
  internalValueChanged(value) {
240
218
  if (value && this.calendarService.isValidDate(value)) {
241
- this._editingDate.set(this.calendarService.create(value, this._calendarSystem()));
242
- }
243
- Object.values(this._editingParts).forEach((part) => {
244
- if (value) {
245
- if (part.typedValue != part.placeholder)
246
- part.typedValue = part.realValue();
219
+ const next = this.calendarService.create(value, this._calendarSystem());
220
+ const sameAsEditing = this._editingDate().date.getTime() === next.date.getTime();
221
+ this._editingDate.set(next);
222
+ // Parent ngModel echo after typing — keep the in-progress segment buffer.
223
+ if (sameAsEditing && this._activePart()) {
224
+ this._updateText();
225
+ return;
247
226
  }
248
- else
227
+ Object.values(this._editingParts).forEach((part) => {
228
+ part.typedValue = part.enabled ? part.realValue() : null;
229
+ });
230
+ this._clearInputBuffer();
231
+ }
232
+ else {
233
+ Object.values(this._editingParts).forEach((part) => {
249
234
  part.typedValue = null;
250
- });
235
+ });
236
+ this._clearInputBuffer();
237
+ }
251
238
  this._updateText();
252
239
  }
253
240
  /**
254
- * @ignore
241
+ * Normalizes Persian (U+06F0-U+06F9) and Arabic-Indic (U+0660-U+0669) digits to ASCII.
242
+ * @ignore
255
243
  */
244
+ _normalizeDigit(key) {
245
+ if (!key || key.length !== 1)
246
+ return null;
247
+ const code = key.charCodeAt(0);
248
+ if (code >= 0x30 && code <= 0x39)
249
+ return key;
250
+ if (code >= 0x06f0 && code <= 0x06f9)
251
+ return String.fromCharCode(code - 0x06f0 + 0x30);
252
+ if (code >= 0x0660 && code <= 0x0669)
253
+ return String.fromCharCode(code - 0x0660 + 0x30);
254
+ return null;
255
+ }
256
+ /** @ignore */
256
257
  _handleOnKeydownEvent(e) {
257
258
  const ignore = () => {
258
259
  if (e.key === 'Tab' || e.code === 'Space' || e.code === 'Enter') {
@@ -266,12 +267,10 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
266
267
  return;
267
268
  }
268
269
  const input = e.target;
269
- //
270
270
  const orderedParts = this._getOrderedParts();
271
271
  const part = this._activePart() ? this._editingParts[this._activePart()] : orderedParts[0];
272
272
  const nextPart = part ? orderedParts[orderedParts.indexOf(part) + 1] : null;
273
273
  const prevPart = part ? orderedParts[orderedParts.indexOf(part) - 1] : null;
274
- //
275
274
  const goNext = () => {
276
275
  this._clearInputBuffer();
277
276
  if (nextPart) {
@@ -286,41 +285,48 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
286
285
  this._highlightActivePart();
287
286
  }
288
287
  };
289
- /*************** Handle Left and Right **************/
288
+ const digit = this._normalizeDigit(e.key);
290
289
  if (e.code === 'ArrowRight' || e.code === 'ArrowLeft') {
291
290
  ignore();
292
- e.code === 'ArrowRight' ? goNext() : goPrev();
291
+ if (!this._activePart()) {
292
+ this._activePart.set(part?.key ?? null);
293
+ this._highlightActivePart();
294
+ }
295
+ else {
296
+ e.code === 'ArrowRight' ? goNext() : goPrev();
297
+ }
293
298
  }
294
299
  else if ((e.code === 'ArrowUp' || e.code === 'ArrowDown') && !e.ctrlKey) {
295
- /*************** Handle Up and Down **************/
296
- if (this.disabled || this.readonly) {
297
- ignore();
300
+ ignore();
301
+ if (this.disabled || this.readonly || !part) {
298
302
  return;
299
303
  }
300
- else {
301
- ignore();
302
- const sign = e.code === 'ArrowUp' ? +1 : -1;
303
- this._activePart.set(part.key);
304
- const newVal = this._editingDate().add(part.key, sign);
305
- this._editingParts[part.key].typedValue = this.formatService.format(newVal, 'datetime', this._editingParts[part.key].placeholder);
306
- this._editingDate.set(newVal);
307
- this._detectValueChanges();
308
- }
304
+ const sign = e.code === 'ArrowUp' ? +1 : -1;
305
+ this._activePart.set(part.key);
306
+ this._clearInputBuffer();
307
+ const unit = part.key === 'AMPM' ? 'hour' : part.key;
308
+ const amount = part.key === 'AMPM' ? sign * 12 : sign;
309
+ const newVal = this._editingDate().add(unit, amount);
310
+ this._editingDate.set(newVal);
311
+ this._getOrderedParts().forEach((p) => {
312
+ p.typedValue = p.realValue();
313
+ });
314
+ this._detectValueChanges();
309
315
  }
310
316
  else if (e.code == 'Backspace' || e.code == 'Delete') {
311
- /*************** Handle Backspace **************/
312
- if (this.disabled || this.readonly) {
317
+ if (this.disabled || this.readonly || !part) {
313
318
  return;
314
319
  }
315
320
  if (input.value) {
316
321
  ignore();
322
+ this._clearInputBuffer();
323
+ this._activePart.set(part.key);
317
324
  this._editingParts[part.key].typedValue = this._editingParts[part.key].placeholder;
318
- this._detectValueChanges();
325
+ this._updateText();
319
326
  goPrev();
320
327
  }
321
328
  }
322
329
  else if (e.code == 'Tab' || e.code == 'Space') {
323
- /*************** Handle Backspace **************/
324
330
  if (input.value) {
325
331
  if (!e.shiftKey && nextPart) {
326
332
  ignore();
@@ -331,192 +337,184 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
331
337
  goPrev();
332
338
  }
333
339
  }
334
- // TODO: FiX type in input
335
340
  }
336
- else if (e.key?.trim() && !isNaN(Number(e.key))) {
341
+ else if (digit != null) {
342
+ ignore();
343
+ if (this.disabled || this.readonly || !part) {
344
+ return;
345
+ }
346
+ this._activePart.set(part.key);
347
+ this._handleDigitInput(part, digit, goNext);
348
+ }
349
+ else if (part?.key === 'AMPM' && (e.key?.toLowerCase() === 'a' || e.key?.toLowerCase() === 'p')) {
337
350
  ignore();
338
- return;
339
351
  if (this.disabled || this.readonly) {
340
352
  return;
341
353
  }
342
- let next = false;
343
- const editing = this._editingParts[part.key];
344
- //
345
- switch (part.key) {
346
- case 'year': {
347
- if (editing.placeholder.length == 2 && this._inputChars.length == 0) {
348
- this._inputChars.push(...this.formatService
349
- .format(this.calendarService.now(this._calendarSystem()), 'datetime', 'yyyy')
350
- .substring(0, 2)
351
- .split(''));
352
- }
353
- if (this._inputChars.length < 4) {
354
- editing.typedValue = `${this._inputChars.join('')}${e.key}`;
355
- this._inputChars.push(e.key);
356
- if (this._inputChars.length > 3) {
357
- next = true;
358
- }
359
- }
360
- else {
361
- next = true;
362
- }
363
- editing.typedValue = ('0000' + editing.typedValue).slice(-4);
364
- const nv = parseInt(editing.typedValue);
365
- //if (nv > 0) {
366
- const newVal = this._editingDate().set('year', nv);
367
- this._editingDate.set(newVal);
368
- this._detectValueChanges();
369
- //}
370
- break;
354
+ const hour = this._editingDate().hour;
355
+ const wantsPM = e.key.toLowerCase() === 'p';
356
+ if (wantsPM && hour < 12) {
357
+ this._applyEditingDate(this._editingDate().add('hour', 12));
358
+ }
359
+ else if (!wantsPM && hour >= 12) {
360
+ this._applyEditingDate(this._editingDate().add('hour', -12));
361
+ }
362
+ }
363
+ else {
364
+ ignore();
365
+ }
366
+ super.emitOnKeydownEvent(e);
367
+ }
368
+ /** @ignore */
369
+ _handleDigitInput(part, digit, goNext) {
370
+ let next = false;
371
+ const editing = this._editingParts[part.key];
372
+ switch (part.key) {
373
+ case 'year': {
374
+ if (editing.placeholder.length == 2 && this._inputChars.length == 0) {
375
+ this._inputChars.push(...this._formatPart(this.calendarService.now(this._calendarSystem()), 'yyyy').substring(0, 2).split(''));
371
376
  }
372
- case 'month': {
373
- if (this._inputChars.length == 0) {
374
- this._inputChars.push(e.key);
375
- editing.typedValue = `0${e.key}`;
376
- if (parseInt(e.key) > 1) {
377
- this._clearInputBuffer();
378
- next = true;
379
- }
380
- }
381
- else if (this._inputChars.length == 1) {
382
- const newStr = parseInt(`${this._inputChars[0]}${e.key}`);
383
- if (newStr > 12) {
384
- editing.typedValue = `0${e.key}`;
385
- }
386
- else {
387
- editing.typedValue = newStr.toString();
388
- }
389
- this._clearInputBuffer();
377
+ this._inputChars.push(digit);
378
+ editing.typedValue = ('0000' + this._inputChars.join('')).slice(-4);
379
+ if (this._inputChars.length >= 4) {
380
+ next = true;
381
+ }
382
+ const nv = parseInt(editing.typedValue);
383
+ if (nv > 0) {
384
+ this._applyEditingDate(this._editingDate().set('year', nv));
385
+ }
386
+ break;
387
+ }
388
+ case 'month': {
389
+ if (this._inputChars.length == 0) {
390
+ this._inputChars.push(digit);
391
+ editing.typedValue = `0${digit}`;
392
+ if (parseInt(digit) > 1) {
390
393
  next = true;
391
394
  }
392
- editing.typedValue = editing.typedValue?.length === 1 ? '0' + editing.typedValue : editing.typedValue;
393
- const nv = parseInt(editing.typedValue);
394
- if (nv > 0) {
395
- const newVal = this._editingDate().set(part.key, nv);
396
- this._editingDate.set(newVal);
397
- this._detectValueChanges();
398
- }
399
- break;
400
395
  }
401
- case 'day': {
402
- if (this._inputChars.length == 0) {
403
- this._inputChars.push(e.key);
404
- editing.typedValue = `0${e.key}`;
405
- if (parseInt(e.key) > 3) {
406
- this._clearInputBuffer();
407
- next = true;
408
- }
409
- }
410
- else if (this._inputChars.length == 1) {
411
- const newStr = parseInt(`${this._inputChars[0]}${e.key}`);
412
- if (newStr > this._editingDate().month.totalDays) {
413
- editing.typedValue = `0${e.key}`;
414
- }
415
- else {
416
- editing.typedValue = newStr.toString();
417
- }
418
- this._clearInputBuffer();
396
+ else {
397
+ const combined = parseInt(`${this._inputChars[0]}${digit}`);
398
+ editing.typedValue = combined > 12 || combined < 1 ? `0${digit}` : `${combined}`.padStart(2, '0');
399
+ next = true;
400
+ }
401
+ const nv = parseInt(editing.typedValue);
402
+ if (nv > 0) {
403
+ this._applyEditingDate(this._editingDate().set('month', nv));
404
+ }
405
+ break;
406
+ }
407
+ case 'day': {
408
+ const totalDays = this._editingDate().month.totalDays;
409
+ if (this._inputChars.length == 0) {
410
+ this._inputChars.push(digit);
411
+ editing.typedValue = `0${digit}`;
412
+ if (parseInt(digit) > Math.floor(totalDays / 10)) {
419
413
  next = true;
420
414
  }
421
- editing.typedValue = editing.typedValue?.length === 1 ? '0' + editing.typedValue : editing.typedValue;
422
- const nv = parseInt(editing.typedValue);
423
- if (nv > 0) {
424
- const newVal = this._editingDate().set(part.key, nv);
425
- this._editingDate.set(newVal);
426
- this._detectValueChanges();
427
- }
428
- break;
429
415
  }
430
- case 'hour': {
431
- if (this._inputChars.length == 0) {
432
- this._inputChars.push(e.key);
433
- editing.typedValue = `0${e.key}`;
434
- if (parseInt(e.key) > 2) {
435
- next = true;
436
- }
437
- }
438
- else if (this._inputChars.length == 1) {
439
- const newStr = parseInt(`${this._inputChars[0]}${e.key}`);
440
- if (newStr > 23) {
441
- editing.typedValue = `0${e.key}`;
442
- }
443
- else {
444
- editing.typedValue = newStr.toString();
445
- }
416
+ else {
417
+ const combined = parseInt(`${this._inputChars[0]}${digit}`);
418
+ editing.typedValue = combined > totalDays || combined < 1 ? `0${digit}` : `${combined}`.padStart(2, '0');
419
+ next = true;
420
+ }
421
+ const nv = parseInt(editing.typedValue);
422
+ if (nv > 0) {
423
+ this._applyEditingDate(this._editingDate().set('day', nv));
424
+ }
425
+ break;
426
+ }
427
+ case 'hour': {
428
+ if (this._inputChars.length == 0) {
429
+ this._inputChars.push(digit);
430
+ editing.typedValue = `0${digit}`;
431
+ if (parseInt(digit) > 2) {
446
432
  next = true;
447
433
  }
448
- editing.typedValue = editing.typedValue?.length === 1 ? '0' + editing.typedValue : editing.typedValue;
449
- const newVal = this._editingDate().set('hour', parseInt(editing.typedValue));
450
- this._editingDate.set(newVal);
451
- this._detectValueChanges();
452
- break;
453
434
  }
454
- case 'minute':
455
- case 'second':
456
- if (this._inputChars.length == 0) {
457
- this._inputChars.push(e.key);
458
- editing.typedValue = `0${e.key}`;
459
- if (parseInt(e.key) > 5) {
460
- this._clearInputBuffer();
461
- next = true;
462
- }
463
- }
464
- else if (this._inputChars.length == 1) {
465
- const newStr = parseInt(`${this._inputChars[0]}${e.key}`);
466
- if (newStr > 59) {
467
- editing.typedValue = `0${e.key}`;
468
- }
469
- else {
470
- editing.typedValue = newStr.toString();
471
- }
472
- this._clearInputBuffer();
435
+ else {
436
+ const combined = parseInt(`${this._inputChars[0]}${digit}`);
437
+ editing.typedValue = combined > 23 ? `0${digit}` : `${combined}`.padStart(2, '0');
438
+ next = true;
439
+ }
440
+ this._applyEditingDate(this._editingDate().set('hour', parseInt(editing.typedValue)));
441
+ break;
442
+ }
443
+ case 'minute':
444
+ case 'second': {
445
+ if (this._inputChars.length == 0) {
446
+ this._inputChars.push(digit);
447
+ editing.typedValue = `0${digit}`;
448
+ if (parseInt(digit) > 5) {
473
449
  next = true;
474
450
  }
475
- editing.typedValue = editing.typedValue?.length === 1 ? '0' + editing.typedValue : editing.typedValue;
476
- // eslint-disable-next-line no-case-declarations
477
- const newVal = this._editingDate().set(part.key, parseInt(editing.typedValue));
478
- this._editingDate.set(newVal);
479
- this._detectValueChanges();
480
- break;
481
- default:
451
+ }
452
+ else {
453
+ const combined = parseInt(`${this._inputChars[0]}${digit}`);
454
+ editing.typedValue = combined > 59 ? `0${digit}` : `${combined}`.padStart(2, '0');
455
+ next = true;
456
+ }
457
+ this._applyEditingDate(this._editingDate().set(part.key, parseInt(editing.typedValue)));
458
+ break;
482
459
  }
483
- if (next) {
484
- goNext();
460
+ default:
485
461
  return;
486
- }
487
462
  }
488
- /*************** Emit Event **************/
489
- super.emitOnKeydownEvent(e);
463
+ if (next) {
464
+ goNext();
465
+ }
490
466
  }
491
- /**
492
- * @ignore
493
- */
467
+ /** @ignore */
468
+ _applyEditingDate(value) {
469
+ this._editingDate.set(value);
470
+ this._detectValueChanges();
471
+ }
472
+ /** @ignore */
494
473
  _handleKeyUpEvent() {
495
474
  if (this._activePart()) {
496
475
  this._highlightActivePart();
497
476
  }
498
477
  }
499
- /**
500
- * @ignore
501
- */
478
+ /** @ignore */
502
479
  _handleFocusEvent(e) {
503
- //this._highlightActivePart();
480
+ if (this.allowTyping()) {
481
+ setTimeout(() => this._selectFirstPart(), 0);
482
+ }
504
483
  super.emitOnFocusEvent(e);
505
484
  }
506
- /**
507
- * @ignore
508
- */
485
+ /** @ignore */
509
486
  _handleBlurEvent(e) {
487
+ this._activePart.set(null);
488
+ this._clearInputBuffer();
489
+ if (this.allowTyping()) {
490
+ this._clampToRange();
491
+ if (!this.value) {
492
+ Object.values(this._editingParts).forEach((part) => {
493
+ part.typedValue = null;
494
+ });
495
+ this._editingText.set('');
496
+ }
497
+ }
510
498
  super.emitOnBlurEvent(e);
511
499
  }
512
- /**
513
- * @ignore
514
- */
500
+ /** @ignore */
501
+ _clampToRange() {
502
+ const value = this.value;
503
+ if (!value)
504
+ return;
505
+ const min = this.minValue();
506
+ const max = this.maxValue();
507
+ if (min && value.getTime() < min.getTime()) {
508
+ this.commitValue(new Date(min), true);
509
+ }
510
+ else if (max && value.getTime() > max.getTime()) {
511
+ this.commitValue(new Date(max), true);
512
+ }
513
+ }
514
+ /** @ignore */
515
515
  _handleOnInputClickEvent(e) {
516
516
  if (this.allowTyping()) {
517
- setTimeout(() => {
518
- this._detectPartAtPosition();
519
- }, 0);
517
+ setTimeout(() => this._selectFirstPart(), 0);
520
518
  }
521
519
  this.onClick.emit({
522
520
  component: this,
@@ -524,26 +522,39 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
524
522
  nativeEvent: e,
525
523
  });
526
524
  }
527
- /**
528
- * @ignore
529
- */
530
- _detectPartAtPosition() {
531
- const input = this.input();
532
- if (!input || !input.value)
533
- return;
534
- const sStart = input.selectionEnd || 0;
535
- const re = /[a-zA-Z0-9]+/gi;
536
- const valueParts = Array.from(input.value.matchAll(re));
525
+ /** @ignore */
526
+ _selectFirstPart() {
537
527
  const orderedParts = this._getOrderedParts();
538
- const index = valueParts.findIndex((c) => c.index <= sStart && c.index + c[0].length >= sStart);
539
- this._activePart.set(orderedParts[index].key);
540
- this._highlightActivePart();
528
+ if (!orderedParts.length)
529
+ return;
530
+ this._clearInputBuffer();
531
+ this._activePart.set(orderedParts[0].key);
532
+ const wasEmpty = !this._editingText();
533
+ this._ensureEditableText();
534
+ if (wasEmpty) {
535
+ setTimeout(() => this._highlightActivePart(), 0);
536
+ }
537
+ else {
538
+ this._highlightActivePart();
539
+ }
541
540
  }
542
- /**
543
- * @ignore
544
- */
541
+ /** @ignore */
542
+ _ensureEditableText() {
543
+ if (this._editingText())
544
+ return;
545
+ const format = this._resolvedFormat();
546
+ if (!format)
547
+ return;
548
+ Object.values(this._editingParts).forEach((part) => {
549
+ if (part.enabled && part.typedValue == null) {
550
+ part.typedValue = part.placeholder;
551
+ }
552
+ });
553
+ this._editingText.set(format);
554
+ }
555
+ /** @ignore */
545
556
  _highlightActivePart() {
546
- const input = this.input();
557
+ const input = this.inputElement;
547
558
  if (!input || !input.value || !this._activePart())
548
559
  return;
549
560
  const orderedParts = this._getOrderedParts();
@@ -555,17 +566,12 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
555
566
  input.setSelectionRange(start, end);
556
567
  }
557
568
  }
558
- /**
559
- * @ignore
560
- */
569
+ /** @ignore */
561
570
  _detectValueChanges() {
562
571
  this.commitValue(this._editingDate().date, true);
563
572
  }
564
- get __hostName() {
565
- return this.name;
566
- }
567
573
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXDateTimeInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
568
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.3", type: AXDateTimeInputComponent, isStandalone: true, selector: "ax-datetime-input", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: false, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: false, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: false, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: false, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: false, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: false, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: false, isRequired: false, transformFunction: null }, allowTyping: { classPropertyName: "allowTyping", publicName: "allowTyping", isSignal: true, isRequired: false, transformFunction: null }, calendar: { classPropertyName: "calendar", publicName: "calendar", isSignal: true, isRequired: false, transformFunction: null }, minValue: { classPropertyName: "minValue", publicName: "minValue", isSignal: true, isRequired: false, transformFunction: null }, maxValue: { classPropertyName: "maxValue", publicName: "maxValue", isSignal: true, isRequired: false, transformFunction: null }, picker: { classPropertyName: "picker", publicName: "picker", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", stateChange: "stateChange", onValueChanged: "onValueChanged", onBlur: "onBlur", onFocus: "onFocus", readonlyChange: "readonlyChange", disabledChange: "disabledChange", onClick: "onClick" }, host: { listeners: { "keydown": "_handleOnKeydownEvent($event)", "keyup": "_handleKeyUpEvent()" }, properties: { "attr.name": "this.__hostName" } }, providers: [
574
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.3", type: AXDateTimeInputComponent, isStandalone: true, selector: "ax-datetime-input", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: false, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: false, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: false, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: false, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: false, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: false, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: false, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: false, isRequired: false, transformFunction: null }, allowTyping: { classPropertyName: "allowTyping", publicName: "allowTyping", isSignal: true, isRequired: false, transformFunction: null }, calendar: { classPropertyName: "calendar", publicName: "calendar", isSignal: true, isRequired: false, transformFunction: null }, minValue: { classPropertyName: "minValue", publicName: "minValue", isSignal: true, isRequired: false, transformFunction: null }, maxValue: { classPropertyName: "maxValue", publicName: "maxValue", isSignal: true, isRequired: false, transformFunction: null }, picker: { classPropertyName: "picker", publicName: "picker", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", stateChange: "stateChange", onValueChanged: "onValueChanged", onBlur: "onBlur", onFocus: "onFocus", readonlyChange: "readonlyChange", disabledChange: "disabledChange", onKeyDown: "onKeyDown", onKeyUp: "onKeyUp", onKeyPress: "onKeyPress", onClick: "onClick" }, host: { listeners: { "keydown": "_handleOnKeydownEvent($event)", "keyup": "_handleKeyUpEvent()" }, properties: { "attr.name": "name" } }, providers: [
569
575
  { provide: AXComponent, useExisting: AXDateTimeInputComponent },
570
576
  { provide: AXFocusableComponent, useExisting: AXDateTimeInputComponent },
571
577
  { provide: AXValuableComponent, useExisting: AXDateTimeInputComponent },
@@ -575,11 +581,22 @@ class AXDateTimeInputComponent extends MXInputBaseValueComponent {
575
581
  useExisting: forwardRef(() => AXDateTimeInputComponent),
576
582
  multi: true,
577
583
  },
578
- ], viewQueries: [{ propertyName: "input", first: true, predicate: ["input"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<input\n #input\n id=\"input\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n class=\"ax-input\"\n type=\"text\"\n [attr.placeholder]=\"placeholder\"\n [class.ax-state-disabled]=\"disabled\"\n [class.ax-select-none]=\"!allowTyping()\"\n [class.ax-state-readonly]=\"readonly\"\n [disabled]=\"disabled\"\n [readonly]=\"true\"\n [ngModel]=\"_editingText()\"\n (mouseup)=\"_handleOnInputClickEvent($event)\"\n (blur)=\"_handleBlurEvent($event)\"\n (focus)=\"_handleFocusEvent($event)\"\n [style.text-align]=\"isRtl() ? 'end' : 'start'\"\n [tabindex]=\"tabIndex\"\n/>\n<ng-content select=\"ax-validation-rule\"> </ng-content>\n", styles: [".ax-select-none,.ax-select-none *{-webkit-user-select:none!important;user-select:none!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
584
+ ], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<input\n #input\n id=\"input\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n class=\"ax-input\"\n type=\"text\"\n [dir]=\"isRtl() ? 'rtl' : 'ltr'\"\n [attr.placeholder]=\"placeholder || _resolvedFormat()\"\n [class.ax-state-disabled]=\"disabled\"\n [class.ax-select-none]=\"!allowTyping()\"\n [class.ax-state-readonly]=\"readonly\"\n [disabled]=\"disabled\"\n [readonly]=\"true\"\n [ngModel]=\"_editingText()\"\n (mouseup)=\"_handleOnInputClickEvent($event)\"\n (blur)=\"_handleBlurEvent($event)\"\n (focus)=\"_handleFocusEvent($event)\"\n [style.text-align]=\"isRtl() ? 'end' : 'start'\"\n [tabindex]=\"tabIndex\"\n/>\n<ng-content select=\"ax-validation-rule\"> </ng-content>\n", styles: [".ax-select-none,.ax-select-none *{-webkit-user-select:none!important;user-select:none!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
579
585
  }
580
586
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXDateTimeInputComponent, decorators: [{
581
587
  type: Component,
582
- args: [{ selector: 'ax-datetime-input', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, inputs: ['disabled', 'readonly', 'tabIndex', 'placeholder', 'value', 'state', 'name', 'id'], outputs: ['valueChange', 'stateChange', 'onValueChanged', 'onBlur', 'onFocus', 'readonlyChange', 'disabledChange'], providers: [
588
+ args: [{ selector: 'ax-datetime-input', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, inputs: ['disabled', 'readonly', 'tabIndex', 'placeholder', 'value', 'state', 'name', 'id'], outputs: [
589
+ 'valueChange',
590
+ 'stateChange',
591
+ 'onValueChanged',
592
+ 'onBlur',
593
+ 'onFocus',
594
+ 'readonlyChange',
595
+ 'disabledChange',
596
+ 'onKeyDown',
597
+ 'onKeyUp',
598
+ 'onKeyPress',
599
+ ], providers: [
583
600
  { provide: AXComponent, useExisting: AXDateTimeInputComponent },
584
601
  { provide: AXFocusableComponent, useExisting: AXDateTimeInputComponent },
585
602
  { provide: AXValuableComponent, useExisting: AXDateTimeInputComponent },
@@ -589,17 +606,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
589
606
  useExisting: forwardRef(() => AXDateTimeInputComponent),
590
607
  multi: true,
591
608
  },
592
- ], imports: [FormsModule], template: "<input\n #input\n id=\"input\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n class=\"ax-input\"\n type=\"text\"\n [attr.placeholder]=\"placeholder\"\n [class.ax-state-disabled]=\"disabled\"\n [class.ax-select-none]=\"!allowTyping()\"\n [class.ax-state-readonly]=\"readonly\"\n [disabled]=\"disabled\"\n [readonly]=\"true\"\n [ngModel]=\"_editingText()\"\n (mouseup)=\"_handleOnInputClickEvent($event)\"\n (blur)=\"_handleBlurEvent($event)\"\n (focus)=\"_handleFocusEvent($event)\"\n [style.text-align]=\"isRtl() ? 'end' : 'start'\"\n [tabindex]=\"tabIndex\"\n/>\n<ng-content select=\"ax-validation-rule\"> </ng-content>\n", styles: [".ax-select-none,.ax-select-none *{-webkit-user-select:none!important;user-select:none!important}\n"] }]
593
- }], propDecorators: { _handleOnKeydownEvent: [{
594
- type: HostListener,
595
- args: ['keydown', ['$event']]
596
- }], _handleKeyUpEvent: [{
597
- type: HostListener,
598
- args: ['keyup']
599
- }], __hostName: [{
600
- type: HostBinding,
601
- args: ['attr.name']
602
- }] } });
609
+ ], imports: [FormsModule], host: {
610
+ '(keydown)': '_handleOnKeydownEvent($event)',
611
+ '(keyup)': '_handleKeyUpEvent()',
612
+ '[attr.name]': 'name',
613
+ }, template: "<input\n #input\n id=\"input\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n class=\"ax-input\"\n type=\"text\"\n [dir]=\"isRtl() ? 'rtl' : 'ltr'\"\n [attr.placeholder]=\"placeholder || _resolvedFormat()\"\n [class.ax-state-disabled]=\"disabled\"\n [class.ax-select-none]=\"!allowTyping()\"\n [class.ax-state-readonly]=\"readonly\"\n [disabled]=\"disabled\"\n [readonly]=\"true\"\n [ngModel]=\"_editingText()\"\n (mouseup)=\"_handleOnInputClickEvent($event)\"\n (blur)=\"_handleBlurEvent($event)\"\n (focus)=\"_handleFocusEvent($event)\"\n [style.text-align]=\"isRtl() ? 'end' : 'start'\"\n [tabindex]=\"tabIndex\"\n/>\n<ng-content select=\"ax-validation-rule\"> </ng-content>\n", styles: [".ax-select-none,.ax-select-none *{-webkit-user-select:none!important;user-select:none!important}\n"] }]
614
+ }] });
603
615
 
604
616
  class AXDateTimeInputModule {
605
617
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXDateTimeInputModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }