fn-input 0.0.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.
@@ -0,0 +1,1106 @@
1
+ import * as i0 from '@angular/core';
2
+ import { EventEmitter, inject, signal, ViewChildren, ViewChild, Output, Input, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import * as i2 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import { HttpClient } from '@angular/common/http';
6
+ import * as i1 from '@angular/forms';
7
+ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
8
+ import * as i3 from '@ngx-translate/core';
9
+ import { TranslateService, TranslateModule } from '@ngx-translate/core';
10
+ import { Subscription, merge } from 'rxjs';
11
+ import { startWith } from 'rxjs/operators';
12
+
13
+ const DEFAULT_CURRENCY_META = [
14
+ { code: 'USD', locale: 'en-US', symbol: '$', digit: '1.2-2' },
15
+ { code: 'MYR', locale: 'en-MY', symbol: 'RM', digit: '1.2-2' },
16
+ { code: 'EUR', locale: 'en-US', symbol: '€', digit: '1.2-2' },
17
+ { code: 'GBP', locale: 'en-US', symbol: '£', digit: '1.2-2' },
18
+ { code: 'JPY', locale: 'ja-JP', symbol: '¥', digit: '1.0-0' },
19
+ { code: 'CNY', locale: 'zh-CN', symbol: '¥', digit: '1.2-2' },
20
+ { code: 'INR', locale: 'en-IN', symbol: '₹', digit: '1.2-2' },
21
+ ];
22
+
23
+ class FNInput {
24
+ cdr;
25
+ field = {};
26
+ helperHandle;
27
+ toastService;
28
+ currencyMeta = DEFAULT_CURRENCY_META;
29
+ defaultLocale = 'en-US';
30
+ form;
31
+ valueChange = new EventEmitter();
32
+ fieldBlur = new EventEmitter();
33
+ translate = inject(TranslateService);
34
+ hasFocus = signal(false, ...(ngDevMode ? [{ debugName: "hasFocus" }] : /* istanbul ignore next */ []));
35
+ textareaElement;
36
+ iconContainers;
37
+ http = inject(HttpClient);
38
+ subs = new Subscription();
39
+ sizeMap = {
40
+ extrasmall: 10,
41
+ small: 16,
42
+ medium: 20,
43
+ large: 24,
44
+ 'x-large': 32,
45
+ xxlarge: 48,
46
+ };
47
+ getIconSizeName(size) {
48
+ switch (size) {
49
+ case 16:
50
+ return 'small';
51
+ case 20:
52
+ return 'medium';
53
+ case 24:
54
+ return 'large';
55
+ case 32:
56
+ return 'x-large';
57
+ default:
58
+ return 'medium';
59
+ }
60
+ }
61
+ get isAlphanumeric() {
62
+ return this.field.isAlphanumeric !== false;
63
+ }
64
+ get isEmailField() {
65
+ return this.field.type === 'email' || this.field.name?.toLowerCase().includes('email');
66
+ }
67
+ get alphanumericPattern() {
68
+ if (this.field.isAddressLine) {
69
+ return /[^A-Za-z0-9 \-_&(),/]/g;
70
+ }
71
+ return /[^A-Za-z0-9 \-_&()]/g;
72
+ }
73
+ isDisabled = false;
74
+ control;
75
+ isVisible = signal(true, ...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ [])); // Track visibility state
76
+ helperText = '';
77
+ // Use a counter-based approach for unique IDs (safer than Math.random())
78
+ static idCounter = 0;
79
+ uniqueId = `fn-input-${++FNInput.idCounter}`;
80
+ constructor(cdr) {
81
+ this.cdr = cdr;
82
+ }
83
+ ngOnInit() {
84
+ if (this.field.hidden)
85
+ return;
86
+ this.helperText = this.field.helperText ?? '';
87
+ this.initFormControl();
88
+ if (this.field.value)
89
+ this.control.setValue(this.field.value);
90
+ this.setupVisibilityCondition();
91
+ this.setupFieldMessageListener();
92
+ }
93
+ ngOnDestroy() {
94
+ this.subs.unsubscribe();
95
+ }
96
+ ngOnChanges(changes) {
97
+ if (changes['field'] || changes['form']) {
98
+ this.updateAllIcons();
99
+ }
100
+ }
101
+ setupFieldMessageListener() {
102
+ if (!this.control)
103
+ return;
104
+ const val$ = this.control.valueChanges.pipe(startWith(this.control.value));
105
+ const status$ = this.control.statusChanges.pipe(startWith(this.control.status));
106
+ this.subs.add(merge(val$, status$).subscribe(() => {
107
+ try {
108
+ this.cdr.detectChanges();
109
+ }
110
+ catch (e) {
111
+ console.warn('CDR detectChanges failed during field message update:', e);
112
+ this.cdr.markForCheck();
113
+ }
114
+ }));
115
+ }
116
+ ngAfterViewInit() {
117
+ // Access the textarea element by ViewChild or native element reference
118
+ this.autoResizeInitial();
119
+ if (this.field.type === 'textarea' && this.control?.value) {
120
+ setTimeout(() => this.autoResizeInitial(), 30);
121
+ }
122
+ // Apply initial formatting after view is initialized
123
+ if (this.field.type === 'number' && this.field.isCurrency && this.control?.value) {
124
+ this.formatInitialCurrencyValue();
125
+ }
126
+ }
127
+ enforceLowercase(event) {
128
+ const input = event.target;
129
+ const start = input.selectionStart;
130
+ const end = input.selectionEnd;
131
+ input.value = input.value.toLowerCase();
132
+ if (this.control) {
133
+ this.control.setValue(input.value, { emitEvent: false });
134
+ }
135
+ this.valueChange.emit({ name: this.field.name, value: input.value });
136
+ // restore cursor position so typing is smooth
137
+ input.setSelectionRange(start, end);
138
+ }
139
+ /**
140
+ * Handle email input - allows only valid email characters and enforces lowercase.
141
+ * Allowed: a-z, 0-9, . _ + - @
142
+ * Rules:
143
+ * - Only one @ allowed
144
+ * - No consecutive dots (..)
145
+ * - Automatic lowercase
146
+ */
147
+ handleEmailInput(event) {
148
+ const input = event.target;
149
+ const cursorPosition = input.selectionStart || 0;
150
+ const originalValue = input.value;
151
+ // 1. Convert to lowercase
152
+ const lowerValue = originalValue.toLowerCase();
153
+ // 2. Split at the first @ to handle local and domain parts separately
154
+ const parts = lowerValue.split('@');
155
+ let localPart = parts[0];
156
+ let domainPart = parts.length > 1 ? parts.slice(1).join('') : null;
157
+ // 3. Filter local part: a-z, 0-9, ., _, +, -
158
+ // Rule: Cannot start with a dot (.)
159
+ localPart = localPart.replaceAll(/[^a-z0-9._+-]/g, '');
160
+ while (localPart.startsWith('.')) {
161
+ localPart = localPart.substring(1);
162
+ }
163
+ while (localPart.includes('..')) {
164
+ localPart = localPart.replaceAll('..', '.');
165
+ }
166
+ // 4. Filter domain part: a-z, 0-9, ., -
167
+ // Rule: No underscores (_), no plus signs (+), cannot start with a hyphen (-)
168
+ let filteredValue = localPart;
169
+ if (domainPart !== null) {
170
+ domainPart = domainPart.replaceAll(/[^a-z0-9.-]/g, '');
171
+ while (domainPart.startsWith('-')) {
172
+ domainPart = domainPart.substring(1);
173
+ }
174
+ while (domainPart.includes('..')) {
175
+ domainPart = domainPart.replaceAll('..', '.');
176
+ }
177
+ filteredValue += '@' + domainPart;
178
+ }
179
+ if (originalValue !== filteredValue) {
180
+ const charsRemovedBeforeCursor = this.countRemovedCharsBeforeCursor(originalValue.toLowerCase(), filteredValue, cursorPosition);
181
+ input.value = filteredValue;
182
+ this.control.setValue(filteredValue, { emitEvent: false });
183
+ const newCursorPos = Math.max(0, cursorPosition - charsRemovedBeforeCursor);
184
+ input.setSelectionRange(newCursorPos, newCursorPos);
185
+ }
186
+ else if (originalValue !== lowerValue) {
187
+ // If only case changed (e.g., typed Uppercase)
188
+ input.value = filteredValue;
189
+ this.control.setValue(filteredValue, { emitEvent: false });
190
+ input.setSelectionRange(cursorPosition, cursorPosition);
191
+ }
192
+ this.valueChange.emit({ name: this.field.name, value: filteredValue });
193
+ this.control.markAsTouched();
194
+ }
195
+ onFocus() {
196
+ this.hasFocus.set(true);
197
+ }
198
+ formatInitialCurrencyValue() {
199
+ const numericValue = Number(this.control.value);
200
+ if (!Number.isNaN(numericValue)) {
201
+ setTimeout(() => {
202
+ const inputElement = document.getElementById(this.uniqueId);
203
+ if (inputElement) {
204
+ inputElement.value = this.formatCurrencyWithCommas(numericValue);
205
+ }
206
+ }, 50);
207
+ }
208
+ }
209
+ initFormControl() {
210
+ const controller = this.form.controls?.[this.field.name];
211
+ this.control = controller;
212
+ if (this.field.value)
213
+ this.control.setValue(this.field.value);
214
+ if (this.control) {
215
+ // Handle disabled state
216
+ if (this.field.disabled ?? false) {
217
+ this.control?.disable();
218
+ }
219
+ else {
220
+ this.control?.enable();
221
+ }
222
+ }
223
+ }
224
+ autoResizeInitial() {
225
+ const textarea = this.textareaElement?.nativeElement;
226
+ if (textarea) {
227
+ // Get the number of rows (default to 2 if not specified)
228
+ const rows = this.field.rows || 1;
229
+ // Calculate line height from computed styles
230
+ const computedStyle = globalThis.getComputedStyle(textarea);
231
+ const lineHeight = Number.parseFloat(computedStyle.lineHeight) || 32; // Default to 24px if not set
232
+ // Calculate minimum height based on rows
233
+ const minHeight = rows * lineHeight;
234
+ // Reset height to auto to get accurate scrollHeight
235
+ textarea.style.height = 'auto';
236
+ // Use the larger of scrollHeight or minHeight
237
+ const newHeight = Math.max(textarea.scrollHeight, minHeight);
238
+ textarea.style.height = newHeight + 'px';
239
+ textarea.style.overflow = 'hidden';
240
+ }
241
+ }
242
+ handleTextArea(event) {
243
+ const element = event.target;
244
+ const cursorPosition = element.selectionStart;
245
+ const originalValue = element.value;
246
+ // Apply alphanumeric filtering if enabled
247
+ if (this.field.type === 'textarea' && this.isAlphanumeric) {
248
+ // Only allow characters based on the selected pattern
249
+ const alphanumericPattern = this.alphanumericPattern;
250
+ let filteredValue = originalValue.replaceAll(alphanumericPattern, '');
251
+ // Prevent leading spaces
252
+ if (filteredValue.startsWith(' ')) {
253
+ filteredValue = filteredValue.trimStart();
254
+ }
255
+ // Replace multiple consecutive spaces with single space
256
+ filteredValue = filteredValue.replaceAll(/\s{2,}/g, ' ');
257
+ if (originalValue !== filteredValue) {
258
+ // Calculate the number of characters removed before the cursor position
259
+ const charsRemovedBeforeCursor = originalValue.length - filteredValue.length;
260
+ element.value = filteredValue;
261
+ this.control.setValue(filteredValue, { emitEvent: false });
262
+ // Restore cursor position - account for removed characters
263
+ const newCursorPos = Math.max(0, (cursorPosition || 0) - charsRemovedBeforeCursor);
264
+ element.setSelectionRange(newCursorPos, newCursorPos);
265
+ }
266
+ // increase height
267
+ this.autoResizeInitial();
268
+ this.valueChange.emit({ name: this.field.name, value: filteredValue });
269
+ }
270
+ else {
271
+ let filteredValue = originalValue;
272
+ // Strict filtering if !isAlphanumeric
273
+ if (!this.isAlphanumeric) {
274
+ console.log('isAlphanumeric', this.isAlphanumeric);
275
+ const allowedPattern = /[^a-zA-Z0-9\s(){}[\];,."='+\-*/<>!&|%_@$?:]/g;
276
+ filteredValue = filteredValue.replaceAll(allowedPattern, '');
277
+ }
278
+ // Prevent leading spaces
279
+ if (filteredValue.startsWith(' ')) {
280
+ filteredValue = filteredValue.trimStart();
281
+ }
282
+ // Replace multiple consecutive spaces with single space
283
+ filteredValue = filteredValue.replaceAll(/\s{2,}/g, ' ');
284
+ if (originalValue !== filteredValue) {
285
+ // Calculate the number of characters removed before the cursor position
286
+ const charsRemovedBeforeCursor = originalValue.length - filteredValue.length;
287
+ element.value = filteredValue;
288
+ this.control.setValue(filteredValue, { emitEvent: false });
289
+ // Restore cursor position - account for removed characters
290
+ const newCursorPos = Math.max(0, (cursorPosition || 0) - charsRemovedBeforeCursor);
291
+ element.setSelectionRange(newCursorPos, newCursorPos);
292
+ }
293
+ // increase height
294
+ this.autoResizeInitial();
295
+ this.valueChange.emit({ name: this.field.name, value: filteredValue });
296
+ }
297
+ this.control.markAsTouched();
298
+ }
299
+ handleInput(event) {
300
+ const target = event.target;
301
+ const cursorPosition = target.selectionStart;
302
+ const originalValue = target.value;
303
+ let filteredValue = originalValue;
304
+ // IF NOT alphanumeric, user wants strict filtering:
305
+ // "only accept tihs.) {} [] ; , . " ' = + - * / < > ! & | % &_( )-/.;@!, $% : dont allow emoji"
306
+ // Interpretation: Allow alphanumeric + specified special chars. Ban emojis.
307
+ // Actually, the user said: "if isAlphanumeric false only accept..."
308
+ // So if isAlphanumeric is TRUE, we use the existing handleAlphanumericInput logic (or similar).
309
+ // The current logic in handleInput is generic.
310
+ // The existing code has a separate `handleAlphanumericInput` method but it's not called here?
311
+ // Wait, the template likely calls `handleAlphanumericInput` if `isAlphanumeric` is true, or `handleInput` otherwise?
312
+ // Let's check the template.
313
+ // BUT assuming `handleInput` is the generic handler:
314
+ if (!this.isAlphanumeric) {
315
+ // Regex to allow: a-z, A-Z, 0-9, and: ) {} [] ; , . " ' = + - * / < > ! & | % _ ( - @ $ ? :
316
+ // Note: User list: ) {} [] ; , . " ' = + - * / < > ! & | % &_( )-/.;@!, $%
317
+ // Combined unique special chars: ) { } [ ] ; , . " ' = + - * / < > ! & | % _ ( @ $ ? :
318
+ // Also space is implied? usually yes.
319
+ // And NO EMOJIS.
320
+ // easier to just replace anything NOT in the allowed set.
321
+ // Allowed chars regex pattern:
322
+ // A-Za-z0-9
323
+ // Space: \s
324
+ // Special: (){}[\];,."='+\-*/<>!&|%_@$?:
325
+ // Note: - inside [] needs to be escaped or at end. ] needs escape. \ needs escape? (not in list).
326
+ // User list has: `.` `"` `'` `?` (wait, ? not in list?)
327
+ // User list: `) {} [] ; , . " ' = + - * / < > ! & | % &_( )-/.;@!, $%`
328
+ // Uniques: ) { } [ ] ; , . " ' = + - * / < > ! & | % _ ( @ $
329
+ // (The `&` is repeated, `.` repeated, `,` repeated etc.)
330
+ // I will create a regex that matches anything NOT in this list and replace with empty string.
331
+ // [^a-zA-Z0-9\s(){}[\];,."='+\-*/<>!&|%_@$]
332
+ // Note: `*`, `+`, `?` etc need escaping in regex if outside [], but inside [] they are literals mostly.
333
+ // `-` needs to be last or escaped. `]` needs escape. `^` needs escape if first? No, invalid here.
334
+ // Let's verify valid chars:
335
+ // a-z A-Z 0-9
336
+ // space
337
+ // ) { } [ ] ; , . " ' = + - * / < > ! & | % _ ( @ $ :
338
+ const allowedPattern = /[^a-zA-Z0-9\s(){}[\];,."='+\-*/<>!&|%_@$?:]/g;
339
+ filteredValue = filteredValue.replaceAll(allowedPattern, '');
340
+ }
341
+ // Prevent leading spaces
342
+ if (filteredValue.startsWith(' ')) {
343
+ filteredValue = filteredValue.trimStart();
344
+ }
345
+ // replaceAll multiple consecutive spaces with single space
346
+ filteredValue = filteredValue.replaceAll(/\s{2,}/g, ' ');
347
+ if (originalValue !== filteredValue) {
348
+ // Calculate the number of characters removed before the cursor position
349
+ const charsRemovedBeforeCursor = originalValue.length - filteredValue.length;
350
+ target.value = filteredValue;
351
+ this.control.setValue(filteredValue, { emitEvent: false });
352
+ // Restore cursor position - account for removed characters
353
+ const newCursorPos = Math.max(0, (cursorPosition || 0) - charsRemovedBeforeCursor);
354
+ target.setSelectionRange(newCursorPos, newCursorPos);
355
+ }
356
+ // Trailing spaces will be trimmed on blur, not during input
357
+ this.valueChange.emit({ name: this.field.name, value: filteredValue });
358
+ this.control.markAsTouched();
359
+ }
360
+ /**
361
+ * Handle alphanumeric input - allows only letters, numbers, and specific special characters
362
+ * Allowed: A-Z, a-z, 0-9, space, hyphen, underscore, ampersand, and parentheses
363
+ * Also prevents leading spaces and multiple consecutive spaces
364
+ */
365
+ handleAlphanumericInput(event) {
366
+ const target = event.target;
367
+ const cursorPosition = target.selectionStart;
368
+ const originalValue = target.value;
369
+ // Only allow characters based on the selected pattern
370
+ const alphanumericPattern = this.alphanumericPattern;
371
+ let filteredValue = originalValue.replaceAll(alphanumericPattern, '');
372
+ // Prevent leading spaces
373
+ if (filteredValue.startsWith(' ')) {
374
+ filteredValue = filteredValue.trimStart();
375
+ }
376
+ // Replace multiple consecutive spaces with single space
377
+ filteredValue = filteredValue.replaceAll(/\s{2,}/g, ' ');
378
+ if (originalValue !== filteredValue) {
379
+ // Calculate the number of characters removed before the cursor position
380
+ const charsRemovedBeforeCursor = originalValue.length - filteredValue.length;
381
+ target.value = filteredValue;
382
+ this.control.setValue(filteredValue, { emitEvent: false });
383
+ // Restore cursor position - account for removed characters
384
+ const newCursorPos = Math.max(0, (cursorPosition || 0) - charsRemovedBeforeCursor);
385
+ target.setSelectionRange(newCursorPos, newCursorPos);
386
+ }
387
+ this.valueChange.emit({ name: this.field.name, value: filteredValue });
388
+ this.control.markAsTouched();
389
+ }
390
+ handleBlur(e, maxDecimals = 2) {
391
+ this.hasFocus.set(false);
392
+ const target = e.target;
393
+ const cleanValueString = target.value;
394
+ if (!cleanValueString) {
395
+ this.fieldBlur.emit({ name: this.field.name, value: '' });
396
+ this.cdr.detectChanges();
397
+ return;
398
+ }
399
+ // Delegate to type-specific handlers
400
+ if (this.field.type === 'text' || this.field.type === 'textarea') {
401
+ this.handleTextFieldBlur(target, cleanValueString);
402
+ }
403
+ else if (this.field.type === 'number') {
404
+ this.handleNumberFieldBlur(target, cleanValueString, maxDecimals);
405
+ }
406
+ // Emit final value
407
+ this.fieldBlur.emit({
408
+ name: this.field.name,
409
+ value: target.value,
410
+ });
411
+ this.cdr.detectChanges();
412
+ }
413
+ /**
414
+ * Handle blur for text and textarea fields - trim trailing spaces
415
+ */
416
+ handleTextFieldBlur(target, cleanValueString) {
417
+ const trimmed = cleanValueString.trim();
418
+ if (cleanValueString !== trimmed) {
419
+ target.value = trimmed;
420
+ this.control.setValue(trimmed, { emitEvent: false });
421
+ }
422
+ }
423
+ /**
424
+ * Handle blur for number fields - validate and format
425
+ */
426
+ handleNumberFieldBlur(target, cleanValueString, maxDecimals) {
427
+ // Remove commas for parsing/validation
428
+ const numString = cleanValueString.replaceAll(',', '');
429
+ // Check if the string is a valid number format
430
+ const isValidNumberFormat = /^-?\d+(\.\d+)?$/.test(numString);
431
+ // If not a valid number format, clear the field
432
+ if (!isValidNumberFormat) {
433
+ target.value = '';
434
+ this.control.setValue('');
435
+ return;
436
+ }
437
+ // Format currency fields or set raw value for non-currency
438
+ if (this.field.type === 'number' && this.field.isCurrency) {
439
+ this.processCurrencyValue(target, numString, maxDecimals);
440
+ }
441
+ else {
442
+ // Non-currency: preserve the raw string value
443
+ target.value = numString;
444
+ }
445
+ }
446
+ /**
447
+ * Process currency value - handle formatting and decimal places
448
+ */
449
+ processCurrencyValue(target, numString, maxDecimals) {
450
+ // Handle negative values - convert to positive
451
+ let processedValue = numString.startsWith('-') ? numString.substring(1) : numString;
452
+ // Handle edge case of "-0" or just "-"
453
+ if (processedValue === '' || processedValue === '0') {
454
+ processedValue = '0';
455
+ }
456
+ // Format the value with proper decimal places
457
+ const cleanValueString = this.formatDecimalPlaces(processedValue, maxDecimals);
458
+ const formattedValue = this.formatCurrencyWithCommas(cleanValueString, maxDecimals);
459
+ // Update the control value with the numeric string (not formatted)
460
+ this.control.setValue(cleanValueString, { emitEvent: false });
461
+ // Trigger validators and mark as touched
462
+ this.control.updateValueAndValidity();
463
+ this.control.markAsTouched();
464
+ // Set display value with commas after Angular's form control update
465
+ setTimeout(() => {
466
+ target.value = formattedValue;
467
+ }, 0);
468
+ }
469
+ /**
470
+ * Format decimal places - pad or truncate to maxDecimals
471
+ */
472
+ formatDecimalPlaces(value, maxDecimals) {
473
+ let [intPart, decPart] = value.includes('.') ? value.split('.') : [value, ''];
474
+ // Pad or truncate decimal part to maxDecimals
475
+ if (decPart.length < maxDecimals) {
476
+ decPart = decPart.padEnd(maxDecimals, '0');
477
+ }
478
+ else if (decPart.length > maxDecimals) {
479
+ decPart = decPart.slice(0, maxDecimals);
480
+ }
481
+ // Build the clean value string (without commas)
482
+ return maxDecimals > 0 ? `${intPart}.${decPart}` : intPart;
483
+ }
484
+ handleNumberInput(event, maxDecimals = 2) {
485
+ const target = event.target;
486
+ const originalValue = target.value;
487
+ const cursorPosition = target.selectionStart ?? 0;
488
+ // Step 1: Filter and format the input value
489
+ let value = this.filterNumericInput(target.value, maxDecimals);
490
+ // Step 2: Update cursor position if value changed
491
+ this.updateCursorPositionAfterFilter(target, originalValue, value, cursorPosition);
492
+ // Step 3: Update form control and validate
493
+ this.updateFormControlValue(value, maxDecimals);
494
+ // Step 4: Emit changes
495
+ this.valueChange.emit({ name: this.field.name, value: target.value });
496
+ this.control.markAsTouched();
497
+ }
498
+ /**
499
+ * Filter numeric input to only allow digits and decimal point
500
+ * Ensures only one decimal point and enforces max decimal places
501
+ */
502
+ filterNumericInput(value, maxDecimals) {
503
+ // Remove ALL non-numeric characters except decimal point
504
+ let filtered = value.replaceAll(/[^0-9.]/g, '');
505
+ // Ensure only one decimal point
506
+ filtered = this.filterDecimalPoints(filtered);
507
+ // Restrict to maxDecimals decimal places if decimal exists
508
+ filtered = this.enforceDecimalLimit(filtered, maxDecimals);
509
+ return filtered;
510
+ }
511
+ /**
512
+ * Ensure only one decimal point exists in the value
513
+ */
514
+ filterDecimalPoints(value) {
515
+ const parts = value.split('.');
516
+ if (parts.length > 2) {
517
+ return parts[0] + '.' + parts.slice(1).join('');
518
+ }
519
+ return value;
520
+ }
521
+ /**
522
+ * Enforce maximum decimal places limit
523
+ */
524
+ enforceDecimalLimit(value, maxDecimals) {
525
+ if (!value.includes('.')) {
526
+ return value;
527
+ }
528
+ const [integerPart, decimalPart] = value.split('.');
529
+ if (decimalPart && decimalPart.length > maxDecimals) {
530
+ return integerPart + '.' + decimalPart.slice(0, maxDecimals);
531
+ }
532
+ return value;
533
+ }
534
+ /**
535
+ * Update cursor position after filtering input
536
+ */
537
+ updateCursorPositionAfterFilter(target, originalValue, newValue, cursorPosition) {
538
+ if (target.value === newValue) {
539
+ return;
540
+ }
541
+ const charsRemovedBeforeCursor = this.countRemovedCharsBeforeCursor(originalValue, newValue, cursorPosition);
542
+ target.value = newValue;
543
+ const newPosition = Math.max(0, cursorPosition - charsRemovedBeforeCursor);
544
+ target.setSelectionRange(newPosition, newPosition);
545
+ }
546
+ /**
547
+ * Update form control value and perform validation
548
+ */
549
+ updateFormControlValue(value, maxDecimals) {
550
+ this.control.setValue(value, { emitEvent: false });
551
+ this.validateIntegerDigits(value, maxDecimals);
552
+ this.clearNumericError();
553
+ }
554
+ /**
555
+ * Count how many characters were removed before the cursor position
556
+ * This helps maintain correct cursor position after filtering input
557
+ */
558
+ countRemovedCharsBeforeCursor(original, filtered, cursorPos) {
559
+ let removedCount = 0;
560
+ let filteredIndex = 0;
561
+ for (let i = 0; i < cursorPos && i < original.length; i++) {
562
+ if (filteredIndex < filtered.length && original[i] === filtered[filteredIndex]) {
563
+ filteredIndex++;
564
+ }
565
+ else {
566
+ removedCount++;
567
+ }
568
+ }
569
+ return removedCount;
570
+ }
571
+ /**
572
+ * Handle paste event for number inputs
573
+ * Prevents pasting of non-numeric characters and enforces digit limits
574
+ */
575
+ handleNumberPaste(event, maxDecimals = 2) {
576
+ event.preventDefault();
577
+ const target = event.target;
578
+ const pastedText = event.clipboardData?.getData('text') || '';
579
+ const isCurrency = this.field.type === 'number' && !!this.field.isCurrency;
580
+ const filteredText = isCurrency
581
+ ? this.getFilteredCurrencyPastedText(pastedText)
582
+ : this.getFilteredIntegerPastedText(pastedText);
583
+ const finalValue = this.buildFinalPasteValue(target, filteredText, isCurrency, maxDecimals);
584
+ // Update the input
585
+ target.value = finalValue;
586
+ this.control.setValue(finalValue, { emitEvent: false });
587
+ this.validateIntegerDigits(finalValue, maxDecimals);
588
+ this.clearNumericError();
589
+ this.valueChange.emit({ name: this.field.name, value: finalValue });
590
+ this.control.markAsTouched();
591
+ this.setCursorAfterPaste(target, filteredText, finalValue);
592
+ }
593
+ /**
594
+ * Filter pasted text for currency input (allows digits and decimal point)
595
+ */
596
+ getFilteredCurrencyPastedText(pastedText) {
597
+ return pastedText.replaceAll(/[^0-9.]/g, '');
598
+ }
599
+ /**
600
+ * Filter pasted text for integer-only input (allows digits only)
601
+ */
602
+ getFilteredIntegerPastedText(pastedText) {
603
+ return pastedText.replaceAll(/\D/g, '');
604
+ }
605
+ /**
606
+ * Build final value after paste operation
607
+ */
608
+ buildFinalPasteValue(target, filteredText, isCurrency, maxDecimals) {
609
+ const currentValue = target.value;
610
+ const start = target.selectionStart;
611
+ const end = target.selectionEnd;
612
+ let finalValue;
613
+ // If selection API is not supported or no selection, replace entire content
614
+ if (start === null || end === null || (start === 0 && end === 0)) {
615
+ finalValue = filteredText;
616
+ }
617
+ else {
618
+ finalValue = currentValue.substring(0, start) + filteredText + currentValue.substring(end);
619
+ }
620
+ return isCurrency ? this.applyDecimalRestrictions(finalValue, maxDecimals) : finalValue;
621
+ }
622
+ /**
623
+ * Apply decimal restrictions for currency fields
624
+ */
625
+ applyDecimalRestrictions(value, maxDecimals) {
626
+ let result = value;
627
+ // Ensure only one decimal point
628
+ const parts = result.split('.');
629
+ if (parts.length > 2) {
630
+ result = parts[0] + '.' + parts.slice(1).join('');
631
+ }
632
+ // Restrict to maxDecimals decimal places
633
+ if (result.includes('.')) {
634
+ const [intPart, decPart] = result.split('.');
635
+ if (decPart.length > maxDecimals) {
636
+ result = intPart + '.' + decPart.slice(0, maxDecimals);
637
+ }
638
+ }
639
+ return result;
640
+ }
641
+ /**
642
+ * Validate integer digit limit and set/clear errors
643
+ */
644
+ validateIntegerDigits(value, maxDecimals) {
645
+ if (this.field.type !== 'number' || !this.field.maxIntegerDigits) {
646
+ return;
647
+ }
648
+ const maxIntDigits = this.field.maxIntegerDigits ?? 15;
649
+ const [intPart] = value.includes('.') ? value.split('.') : [value];
650
+ if (intPart.length > maxIntDigits) {
651
+ this.setMaxIntegerDigitsError(intPart.length, maxIntDigits, maxDecimals);
652
+ }
653
+ else {
654
+ this.clearMaxIntegerDigitsError();
655
+ }
656
+ }
657
+ /**
658
+ * Set max integer digits error
659
+ */
660
+ setMaxIntegerDigitsError(actual, max, maxDecimals) {
661
+ const currentErrors = this.control.errors || {};
662
+ this.control.setErrors({
663
+ ...currentErrors,
664
+ maxIntegerDigits: {
665
+ actual,
666
+ max,
667
+ maxDecimals,
668
+ message: `Please shorten your input to ${max} whole digits and ${maxDecimals} decimal or fewer.`,
669
+ },
670
+ });
671
+ }
672
+ /**
673
+ * Clear max integer digits error if present
674
+ */
675
+ clearMaxIntegerDigitsError() {
676
+ if (!this.control.hasError('maxIntegerDigits')) {
677
+ return;
678
+ }
679
+ const errors = { ...this.control.errors };
680
+ delete errors['maxIntegerDigits'];
681
+ this.control.setErrors(Object.keys(errors).length ? errors : null);
682
+ }
683
+ /**
684
+ * Clear numeric error if present
685
+ */
686
+ clearNumericError() {
687
+ if (!this.control.hasError('numeric')) {
688
+ return;
689
+ }
690
+ const errors = { ...this.control.errors };
691
+ delete errors['numeric'];
692
+ this.control.setErrors(Object.keys(errors).length ? errors : null);
693
+ }
694
+ /**
695
+ * Set cursor position after paste operation
696
+ */
697
+ setCursorAfterPaste(target, filteredText, finalValue) {
698
+ // Input type="number" does not support selection APIs
699
+ if (target.type === 'number') {
700
+ return;
701
+ }
702
+ const start = target.selectionStart || 0;
703
+ const newCursorPos = start + filteredText.length;
704
+ const actualPos = Math.min(newCursorPos, finalValue.length);
705
+ target.setSelectionRange(actualPos, actualPos);
706
+ }
707
+ // Format number with comma separators
708
+ // Uses string-based formatting for large numbers to preserve precision
709
+ formatCurrencyWithCommas(value, maxDecimals = 2) {
710
+ if (this.field.type !== 'number' || !this.field.isCurrency)
711
+ return value.toString();
712
+ const strValue = value.toString();
713
+ // Return original value for empty or invalid inputs
714
+ if (!strValue || strValue === 'NaN')
715
+ return '';
716
+ // Use string-based formatting to preserve precision for large numbers
717
+ const [integerPart, decimalPart] = strValue.includes('.')
718
+ ? strValue.split('.')
719
+ : [strValue, ''];
720
+ // Format integer part with comma separators (every 3 digits from right)
721
+ // Using iterative approach instead of regex to avoid ReDoS concerns
722
+ let formattedInteger = '';
723
+ const len = integerPart.length;
724
+ for (let i = 0; i < len; i++) {
725
+ if (i > 0 && (len - i) % 3 === 0) {
726
+ formattedInteger += ',';
727
+ }
728
+ formattedInteger += integerPart[i];
729
+ }
730
+ // Format decimal part (pad or truncate to maxDecimals)
731
+ let formattedDecimal = decimalPart || '';
732
+ if (formattedDecimal.length < maxDecimals) {
733
+ formattedDecimal = formattedDecimal.padEnd(maxDecimals, '0');
734
+ }
735
+ else if (formattedDecimal.length > maxDecimals) {
736
+ formattedDecimal = formattedDecimal.slice(0, maxDecimals);
737
+ }
738
+ return maxDecimals > 0 ? `${formattedInteger}.${formattedDecimal}` : formattedInteger;
739
+ }
740
+ handleNumberKeydown(event, isCurrency) {
741
+ if (isCurrency)
742
+ return;
743
+ // Allow: Backspace, Delete, Tab, Arrow keys
744
+ if (['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
745
+ return;
746
+ }
747
+ // Allow clipboard operations (Ctrl+V, Ctrl+C, Ctrl+X, Cmd+V, Cmd+C, Cmd+X)
748
+ // and selection (Ctrl+A, Cmd+A)
749
+ if (event.ctrlKey || event.metaKey) {
750
+ return;
751
+ }
752
+ // Block everything that is not 0-9
753
+ if (!/^\d$/.test(event.key)) {
754
+ event.preventDefault();
755
+ }
756
+ }
757
+ // Get locale from currency code using currencyMeta
758
+ getLocaleFromCurrency(currencyCode) {
759
+ const currencyObj = this.currencyMeta.find((c) => c.code === currencyCode);
760
+ return currencyObj?.locale || this.defaultLocale;
761
+ }
762
+ showPassword = false;
763
+ passwordStrengthLabel = '';
764
+ strengthClass = '';
765
+ passwordStrengthPercent = 0;
766
+ isPasswordFocused = false;
767
+ onPasswordFocus() {
768
+ this.hasFocus.set(true);
769
+ this.isPasswordFocused = true;
770
+ this.cdr.detectChanges();
771
+ }
772
+ onPasswordBlur() {
773
+ this.isPasswordFocused = false;
774
+ this.cdr.detectChanges();
775
+ }
776
+ handlePasswordBlur(e) {
777
+ this.handleBlur(e); // Original blur functionality (sets hasFocus to false)
778
+ this.onPasswordBlur(); // Hide password feedback
779
+ }
780
+ checkPasswordStrength(value) {
781
+ if (!value || this.field.type !== 'password') {
782
+ this.passwordStrengthLabel = '';
783
+ this.strengthClass = '';
784
+ this.passwordStrengthPercent = 0;
785
+ return;
786
+ }
787
+ let score = 0;
788
+ if (value.length >= 8)
789
+ score++;
790
+ if (/[A-Z]/.test(value))
791
+ score++;
792
+ if (/\d/.test(value))
793
+ score++;
794
+ if (/[\W_]/.test(value))
795
+ score++;
796
+ if (score <= 1) {
797
+ this.passwordStrengthLabel = this.field.weakLabel || 'Weak';
798
+ this.strengthClass = 'text-[var(--RHB-Red-100)]';
799
+ this.passwordStrengthPercent = 25;
800
+ }
801
+ else if (score === 2 || score === 3) {
802
+ this.passwordStrengthLabel = this.field.mediumLabel || 'Medium';
803
+ this.strengthClass = 'text-[var(--Orange-100)]';
804
+ this.passwordStrengthPercent = score === 2 ? 50 : 75;
805
+ }
806
+ else {
807
+ this.passwordStrengthLabel = this.field.strongLabel || 'Strong';
808
+ this.strengthClass = 'text-[var(--Green-100)]';
809
+ this.passwordStrengthPercent = 100;
810
+ }
811
+ }
812
+ increment() {
813
+ if (this.field.type !== 'number')
814
+ return;
815
+ const step = this.field.step || 1;
816
+ const current = Number(this.control.value) || 0;
817
+ let nextValue = current + step;
818
+ if (typeof this.field.max === 'number') {
819
+ nextValue = Math.min(nextValue, this.field.max);
820
+ }
821
+ this.control.setValue(nextValue);
822
+ this.control.markAsTouched();
823
+ this.valueChange.emit({ name: this.field.name, value: nextValue });
824
+ // Update display value for currency inputs
825
+ if (this.field.isCurrency) {
826
+ setTimeout(() => {
827
+ const inputElement = document.getElementById(this.uniqueId);
828
+ if (inputElement) {
829
+ inputElement.value = this.formatCurrencyWithCommas(nextValue);
830
+ }
831
+ });
832
+ }
833
+ }
834
+ decrement() {
835
+ if (this.field.type !== 'number')
836
+ return;
837
+ const step = this.field.step || 1;
838
+ const current = Number(this.control.value) || 0;
839
+ let nextValue = current - step;
840
+ // Ensure minimum value is 0 (no negative values)
841
+ const minValue = Math.max(this.field.min || 0, 0);
842
+ nextValue = Math.max(nextValue, minValue);
843
+ this.control.setValue(nextValue);
844
+ this.control.markAsTouched();
845
+ this.valueChange.emit({ name: this.field.name, value: nextValue });
846
+ // Update display value for currency inputs
847
+ if (this.field.isCurrency) {
848
+ setTimeout(() => {
849
+ const inputElement = document.getElementById(this.uniqueId);
850
+ if (inputElement) {
851
+ inputElement.value = this.formatCurrencyWithCommas(nextValue);
852
+ }
853
+ });
854
+ }
855
+ }
856
+ copyText(e) {
857
+ e.stopPropagation();
858
+ if (!this.control.value)
859
+ return;
860
+ navigator.clipboard.writeText(this.control.value).then(() => {
861
+ if (this.toastService) {
862
+ this.toastService.success('Copied to clipboard', 'Success', 1000);
863
+ }
864
+ });
865
+ }
866
+ /**
867
+ * Set up visibility condition listener
868
+ * Watches the dependent field and shows/hides this field based on conditions
869
+ */
870
+ setupVisibilityCondition() {
871
+ if (!this.field.visibilityCondition || !this.form)
872
+ return;
873
+ const { dependsOn } = this.field.visibilityCondition;
874
+ const dependentControl = this.form.get(dependsOn);
875
+ if (!dependentControl) {
876
+ return;
877
+ }
878
+ // Initial visibility check
879
+ this.updateVisibility(dependentControl.value);
880
+ // Subscribe to value changes
881
+ dependentControl.valueChanges.subscribe((value) => {
882
+ this.updateVisibility(value);
883
+ });
884
+ }
885
+ /**
886
+ * Update field visibility based on dependent field value
887
+ */
888
+ updateVisibility(dependentValue) {
889
+ if (!this.field.visibilityCondition)
890
+ return;
891
+ const { showWhen, hideWhen } = this.field.visibilityCondition;
892
+ let shouldBeVisible = false;
893
+ // Check showWhen condition
894
+ if (showWhen !== undefined) {
895
+ if (Array.isArray(showWhen)) {
896
+ shouldBeVisible = showWhen.includes(dependentValue);
897
+ }
898
+ else {
899
+ shouldBeVisible = dependentValue === showWhen;
900
+ }
901
+ }
902
+ // Check hideWhen condition (overrides showWhen)
903
+ if (hideWhen !== undefined) {
904
+ const shouldHide = Array.isArray(hideWhen)
905
+ ? hideWhen.includes(dependentValue)
906
+ : dependentValue === hideWhen;
907
+ if (shouldHide) {
908
+ shouldBeVisible = false;
909
+ }
910
+ }
911
+ // Update visibility
912
+ this.isVisible.set(shouldBeVisible);
913
+ // Only clear value when hiding if there's no initial value
914
+ // This preserves data when editing existing records
915
+ if (!shouldBeVisible && this.control && !this.control.value) {
916
+ this.control.setValue(null);
917
+ this.control.markAsUntouched();
918
+ this.control.updateValueAndValidity();
919
+ }
920
+ this.cdr.detectChanges();
921
+ }
922
+ showFormFieldMessage(control, helperText) {
923
+ if (this.helperHandle) {
924
+ return this.helperHandle.showFormFieldMessage(control, helperText);
925
+ }
926
+ const isError = control?.touched && !!control?.errors;
927
+ const isHelper = !isError && Boolean(helperText ?? '');
928
+ return isError || isHelper;
929
+ }
930
+ getTranslatedLabel(label) {
931
+ if (!label)
932
+ return '';
933
+ try {
934
+ const labelStr = label.toString();
935
+ return (labelStr
936
+ .split('::')
937
+ .map((part) => part.trim())
938
+ .filter((part) => !!part)
939
+ .map((part) => this.translate.instant(part))
940
+ .join(' ') + this.getEndSymbol(labelStr));
941
+ }
942
+ catch (e) {
943
+ console.warn('Translation failed in getTranslatedLabel:', label, e);
944
+ return label;
945
+ }
946
+ }
947
+ getEndSymbol(label) {
948
+ return label.toString().includes('required') && label.toString().includes('FNFieldMessage')
949
+ ? '.'
950
+ : '';
951
+ }
952
+ // --- ICON LOGIC ---
953
+ updateAllIcons() {
954
+ // We'll call this after ViewInit or when inputs change
955
+ setTimeout(() => {
956
+ this.iconContainers?.forEach((container) => {
957
+ const name = container.nativeElement.getAttribute('data-icon-name');
958
+ const variant = container.nativeElement.getAttribute('data-icon-variant') || 'Line';
959
+ const size = (container.nativeElement.getAttribute('data-icon-size') ||
960
+ 'medium');
961
+ const color = container.nativeElement.getAttribute('data-icon-color');
962
+ const disabled = container.nativeElement.getAttribute('data-icon-disabled') === 'true';
963
+ if (name) {
964
+ this.loadIconToContainer(container.nativeElement, name, variant, size, color, disabled);
965
+ }
966
+ });
967
+ }, 0);
968
+ }
969
+ loadIconToContainer(container, name, variant, sizeName, color, disabled) {
970
+ const size = this.sizeMap[sizeName];
971
+ const iconPath = this.getIconPath(name, variant, size);
972
+ if (!iconPath) {
973
+ container.innerHTML = '';
974
+ return;
975
+ }
976
+ this.http.get(iconPath, { responseType: 'text' }).subscribe({
977
+ next: (raw) => {
978
+ try {
979
+ const parser = new DOMParser();
980
+ const doc = parser.parseFromString(raw, 'image/svg+xml');
981
+ const svg = doc.querySelector('svg');
982
+ if (!svg) {
983
+ container.innerHTML = '';
984
+ return;
985
+ }
986
+ // SVG Normalization logic
987
+ for (const s of svg.querySelectorAll('style'))
988
+ s.remove();
989
+ if (color) {
990
+ for (const el of svg.querySelectorAll('*')) {
991
+ const style = el.getAttribute('style');
992
+ if (style) {
993
+ const cleaned = style
994
+ .replaceAll(/fill\s*:\s*[^;]+;?/gi, '')
995
+ .replaceAll(/stroke\s*:\s*[^;]+;?/gi, '');
996
+ if (cleaned)
997
+ el.setAttribute('style', cleaned);
998
+ else
999
+ el.removeAttribute('style');
1000
+ }
1001
+ const fill = el.getAttribute('fill');
1002
+ if (fill && fill !== 'none' && !fill.startsWith('url(')) {
1003
+ el.setAttribute('fill', 'currentColor');
1004
+ }
1005
+ const stroke = el.getAttribute('stroke');
1006
+ if (stroke && stroke !== 'none' && !stroke.startsWith('url(')) {
1007
+ el.setAttribute('stroke', 'currentColor');
1008
+ }
1009
+ }
1010
+ }
1011
+ svg.removeAttribute('width');
1012
+ svg.removeAttribute('height');
1013
+ if (!svg.getAttribute('viewBox')) {
1014
+ svg.setAttribute('viewBox', `0 0 ${size} ${size}`);
1015
+ }
1016
+ svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
1017
+ svg.setAttribute('focusable', 'false');
1018
+ svg.setAttribute('aria-hidden', 'true');
1019
+ svg.setAttribute('width', '100%');
1020
+ svg.setAttribute('height', '100%');
1021
+ const style = `;display:block;max-width:100%;max-height:100%;${disabled ? 'opacity:0.5;cursor:not-allowed;' : ''}`;
1022
+ svg.setAttribute('style', (svg.getAttribute('style') || '') + style);
1023
+ container.innerHTML = '';
1024
+ container.appendChild(svg);
1025
+ this.cdr.detectChanges();
1026
+ }
1027
+ catch (error) {
1028
+ console.error(`[fn-input] Error parsing SVG for icon "${name}":`, error);
1029
+ container.innerHTML = '';
1030
+ }
1031
+ },
1032
+ error: () => {
1033
+ container.innerHTML = '';
1034
+ this.cdr.detectChanges();
1035
+ },
1036
+ });
1037
+ }
1038
+ getIconPath(icon, variant, size) {
1039
+ if (!icon)
1040
+ return null;
1041
+ if (size === 16 || size === 10) {
1042
+ return `assets/icons/${variant}/${size}px/${icon}.svg`;
1043
+ }
1044
+ return `assets/icons/${variant}/${size}px/${icon}--${size}.svg`;
1045
+ }
1046
+ // --- MESSAGE LOGIC ---
1047
+ getFieldMessage() {
1048
+ if (!this.control)
1049
+ return this.field?.helperText || '';
1050
+ if (this.control.touched && this.control.errors) {
1051
+ for (const key of Object.keys(this.control.errors)) {
1052
+ const errorValue = this.control.errors[key];
1053
+ if (errorValue && typeof errorValue === 'object' && errorValue.message) {
1054
+ return errorValue.message;
1055
+ }
1056
+ if (this.field?.errors?.[key]) {
1057
+ return this.field.errors[key];
1058
+ }
1059
+ }
1060
+ return (this.field?.errors?.['default'] ||
1061
+ 'Please enter ' + this.translate.instant(this.field?.label));
1062
+ }
1063
+ return this.field?.helperText || '';
1064
+ }
1065
+ get isError() {
1066
+ return !!(this.control?.touched && this.control?.errors);
1067
+ }
1068
+ get isSuccess() {
1069
+ return !!(this.control?.valid && this.control?.touched);
1070
+ }
1071
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: FNInput, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1072
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.2", type: FNInput, isStandalone: true, selector: "fn-input", inputs: { field: "field", helperHandle: "helperHandle", toastService: "toastService", currencyMeta: "currencyMeta", defaultLocale: "defaultLocale", form: "form" }, outputs: { valueChange: "valueChange", fieldBlur: "fieldBlur" }, viewQueries: [{ propertyName: "textareaElement", first: true, predicate: ["fnTextarea"], descendants: true }, { propertyName: "iconContainers", predicate: ["iconContainer"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (field.name && isVisible() && !field.hidden) {\r\n <div class=\"flex flex-col gap-2\">\r\n @if (field.type! !== 'hidden') {\r\n <label\r\n [for]=\"uniqueId\"\r\n class=\"fn-label !whitespace-normal\"\r\n [ngClass]=\"\r\n (field.statusLabel ? field.statusLabel : field.labelVariant || 'p4') +\r\n ' ' +\r\n (field.className || '')\r\n \"\r\n [style.color]=\"field.color || null\"\r\n >\r\n {{ getTranslatedLabel(field.label) }}\r\n @if (!field.required && !field.hideOptional) {\r\n <span>&nbsp;(Optional)</span>\r\n }\r\n </label>\r\n }\r\n\r\n <ng-container>\r\n @switch (field.type) {\r\n <!-- Textarea Field -->\r\n @case ('textarea') {\r\n <textarea\r\n #fnTextarea\r\n style=\"resize: none\"\r\n [name]=\"field.name\"\r\n [id]=\"uniqueId\"\r\n [required]=\"field.required || false\"\r\n [placeholder]=\"field.placeholder || '' | translate\"\r\n [disabled]=\"field.disabled || false\"\r\n [rows]=\"field.rows || 1\"\r\n [readonly]=\"field.readOnly || false\"\r\n [formControl]=\"control\"\r\n (input)=\"handleTextArea($event)\"\r\n (blur)=\"handleBlur($event)\"\r\n class=\"peer w-full leading-6 rounded-none border-0 border-b border-[var(--Base-30)] pb-1.75 bg-transparent text-base text-[var(--Base-100)] focus:outline-none focus:ring-0 placeholder:!text-[var(--Base-50)] placeholder:!text-base focus:[caret-color:var(--RHB-Blue-100)]\"\r\n [ngClass]=\"{\r\n 'opacity-100': control.disabled || field.readOnly,\r\n '!text-[var(--Base-30)]': control.disabled || field.readOnly,\r\n '!border-[var(--RHB-Blue-100)]': hasFocus(),\r\n '!border-[var(--RHB-Red-100)]': control.touched && control.errors,\r\n '!border-[var(--Green-100)]':\r\n field['hasSuccessBorder'] && control.valid && !control.errors,\r\n }\"\r\n (focus)=\"onFocus()\"\r\n ></textarea>\r\n }\r\n\r\n <!-- Password Field -->\r\n\r\n @case ('password') {\r\n <div class=\"relative w-full\">\r\n <input\r\n [type]=\"showPassword ? 'text' : 'password'\"\r\n [id]=\"uniqueId\"\r\n [required]=\"field.required || false\"\r\n [disabled]=\"field.disabled || false\"\r\n [formControl]=\"control\"\r\n [placeholder]=\"field.placeholder || '' | translate\"\r\n [readonly]=\"field.readOnly || false\"\r\n (input)=\"isAlphanumeric ? handleAlphanumericInput($event) : handleInput($event)\"\r\n (focus)=\"onPasswordFocus()\"\r\n (blur)=\"handlePasswordBlur($event)\"\r\n class=\"peer h-8 w-full leading-6 rounded-none border-0 border-b border-[var(--Base-30)] pb-1.75 bg-transparent text-base text-[var(--Base-100)] focus:outline-none focus:ring-0 placeholder:!text-[var(--Base-50)] placeholder:!text-base focus:[caret-color:var(--RHB-Blue-100)] overflow-hidden text-ellipsis\"\r\n [ngClass]=\"{\r\n 'opacity-100': control.disabled || field.readOnly,\r\n '!text-[var(--Base-30)]': control.disabled || field.readOnly,\r\n '!border-[var(--RHB-Blue-100)]': hasFocus() && !control.disabled,\r\n '!border-[var(--RHB-Red-100)]': control.touched && control.errors,\r\n '!border-[var(--Green-100)]': field['hasSuccessBorder'] && control.valid,\r\n 'pr-12': field.toggleMask && !field.isCopyText,\r\n 'pr-16': field.isCopyText && !field.toggleMask,\r\n 'pr-24': field.toggleMask && field.isCopyText && !field.hasGenerateKey,\r\n 'pr-32': field.toggleMask && field.isCopyText && field.hasGenerateKey,\r\n }\"\r\n />\r\n @if (field.toggleMask && !field.isCopyText) {\r\n <!-- Only Eye Icon -->\r\n <span\r\n class=\"absolute right-0 top-0 cursor-pointer text-[var(--Base-10)] hover:text-[var(--RHB-Blue-100)]\"\r\n (click)=\"showPassword = !showPassword\"\r\n (keydown)=\"showPassword = !showPassword\"\r\n >\r\n @if (!showPassword) {\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"eye-close\"\r\n data-icon-variant=\"Line\"\r\n data-icon-size=\"large\"\r\n data-icon-color=\"var(--Base-100)\"\r\n ></div>\r\n } @else {\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"eye-open\"\r\n data-icon-variant=\"Line\"\r\n data-icon-size=\"large\"\r\n data-icon-color=\"var(--Base-100)\"\r\n ></div>\r\n }\r\n </span>\r\n } @else if (field.isCopyText && !field.toggleMask) {\r\n <!-- Only Copy Icon -->\r\n <span\r\n class=\"absolute right-12 top-0 cursor-pointer text-[var(--Base-10)] hover:text-[var(--RHB-Blue-100)]\"\r\n (click)=\"copyText($event)\"\r\n (keydown)=\"copyText($event)\"\r\n >\r\n <div #iconContainer class=\"icon-container\" data-icon-name=\"two-square\"></div>\r\n </span>\r\n @if (field.hasGenerateKey) {\r\n <span\r\n class=\"absolute right-4 top-0 cursor-pointer text-[var(--Base-10)] hover:text-[var(--RHB-Blue-100)]\"\r\n (click)=\"field.onGenerateKey && field.onGenerateKey()\"\r\n (keydown)=\"field.onGenerateKey && field.onGenerateKey()\"\r\n >\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"two-square\"\r\n data-icon-variant=\"Line\"\r\n data-icon-size=\"large\"\r\n ></div>\r\n </span>\r\n }\r\n } @else if (field.toggleMask && field.isCopyText) {\r\n <!-- Both Eye + Copy Icons -->\r\n <span\r\n class=\"absolute right-20 top-0 cursor-pointer text-[var(--Base-10)] hover:text-[var(--RHB-Blue-100)]\"\r\n (click)=\"showPassword = !showPassword\"\r\n (keydown)=\"showPassword = !showPassword\"\r\n >\r\n @if (!showPassword) {\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"eye-close\"\r\n data-icon-variant=\"Line\"\r\n data-icon-size=\"large\"\r\n data-icon-color=\"var(--Base-100)\"\r\n ></div>\r\n } @else {\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"eye-open\"\r\n data-icon-variant=\"Line\"\r\n data-icon-size=\"large\"\r\n data-icon-color=\"var(--Base-100)\"\r\n ></div>\r\n }\r\n </span>\r\n\r\n <span\r\n class=\"absolute right-12 top-0 cursor-pointer text-[var(--Base-10)] hover:text-[var(--RHB-Blue-100)]\"\r\n (click)=\"copyText($event)\"\r\n (keydown)=\"copyText($event)\"\r\n >\r\n <div #iconContainer class=\"icon-container\" data-icon-name=\"two-square\"></div>\r\n </span>\r\n\r\n @if (field.hasGenerateKey) {\r\n <span\r\n class=\"absolute right-4 top-0 text-[var(--Base-10)]\"\r\n [ngClass]=\"{\r\n 'cursor-pointer hover:text-[var(--RHB-Blue-100)]':\r\n control.value && control.value.length > 0,\r\n 'cursor-not-allowed opacity-50': !control.value || control.value.length === 0,\r\n }\"\r\n (click)=\"\r\n control.value &&\r\n control.value.length > 0 &&\r\n field.onGenerateKey &&\r\n field.onGenerateKey()\r\n \"\r\n (keydown)=\"\r\n control.value &&\r\n control.value.length > 0 &&\r\n field.onGenerateKey &&\r\n field.onGenerateKey()\r\n \"\r\n >\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"round-arrow-top-left\"\r\n ></div>\r\n </span>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Number Field -->\r\n @case ('number') {\r\n <input\r\n [type]=\"field.type === 'number' && field.isCurrency ? 'text' : 'number'\"\r\n [name]=\"field.name\"\r\n [id]=\"uniqueId\"\r\n [required]=\"field.required || false\"\r\n [placeholder]=\"field.placeholder || '' | translate\"\r\n [disabled]=\"isDisabled || false\"\r\n [readOnly]=\"field.readOnly || false\"\r\n [formControl]=\"control\"\r\n [min]=\"field.type === 'number' && !field.isCurrency ? 0 : null\"\r\n [max]=\"field.type === 'number' && !field.isCurrency ? field.max : null\"\r\n [step]=\"field.type === 'number' && !field.isCurrency ? field.step || 1 : null\"\r\n (input)=\"handleNumberInput($event, field?.minFractionDigits || 2)\"\r\n (paste)=\"handleNumberPaste($event, field?.minFractionDigits || 2)\"\r\n (blur)=\"handleBlur($event, field?.minFractionDigits || 2)\"\r\n (keydown)=\"handleNumberKeydown($event, field?.isCurrency || false)\"\r\n class=\"peer h-8 w-full leading-6 rounded-none border-0 border-b border-[var(--Base-30)] pb-1.75 bg-transparent text-base text-[var(--Base-100)] focus:outline-none focus:ring-0 placeholder:!text-[var(--Base-50)] placeholder:!text-base focus:[caret-color:var(--RHB-Blue-100)]\"\r\n [ngClass]=\"{\r\n 'opacity-100': control.disabled || field.readOnly,\r\n '!text-[var(--Base-30)]': control.disabled || field.readOnly,\r\n '!border-[var(--RHB-Blue-100)]': hasFocus(),\r\n '!border-[var(--RHB-Red-100)]': control.touched && control.errors,\r\n '!border-[var(--Green-100)]':\r\n field['hasSuccessBorder'] && control.valid && !control.errors,\r\n 'text-[24px] font-bold leading-[32px]': field.isCurrency,\r\n }\"\r\n (focus)=\"onFocus()\"\r\n />\r\n }\r\n\r\n <!-- Default Input Field (text, email, etc.) -->\r\n @default {\r\n <div class=\"relative z-0\">\r\n <input\r\n [type]=\"field.type\"\r\n [name]=\"field.name\"\r\n [id]=\"uniqueId\"\r\n [required]=\"field.required || false\"\r\n [placeholder]=\"field.placeholder || '' | translate\"\r\n [disabled]=\"field.disabled || false\"\r\n [readOnly]=\"field.readOnly || false\"\r\n [formControl]=\"control\"\r\n (input)=\"\r\n isEmailField\r\n ? handleEmailInput($event)\r\n : field.type === 'text' && isAlphanumeric\r\n ? handleAlphanumericInput($event)\r\n : handleInput($event)\r\n \"\r\n (blur)=\"handleBlur($event)\"\r\n class=\"peer h-8 w-full leading-6 rounded-none border-0 border-b border-[var(--Base-30)] pb-1.75 bg-transparent text-base text-[var(--Base-100)] focus:outline-none focus:ring-0 placeholder:!text-[var(--Base-50)] placeholder:!text-base focus:[caret-color:var(--RHB-Blue-100)]\"\r\n [ngClass]=\"{\r\n 'opacity-100': control.disabled || field.readOnly,\r\n '!text-[var(--Base-30)]': control.disabled || field.readOnly,\r\n '!border-[var(--RHB-Blue-100)]': hasFocus(),\r\n '!border-[var(--RHB-Red-100)]': control.touched && control.errors,\r\n '!border-[var(--Green-100)]':\r\n field['hasSuccessBorder'] && control.valid && !control.errors,\r\n 'pr-8': field.icon,\r\n }\"\r\n (focus)=\"onFocus()\"\r\n />\r\n @if (field.icon) {\r\n <span class=\"absolute bottom-2 right-0.5 flex items-center\">\r\n <div\r\n #iconContainer\r\n class=\"bg-white\"\r\n [attr.data-icon-name]=\"field.icon.name\"\r\n [attr.data-icon-variant]=\"field.icon.variant\"\r\n [attr.data-icon-size]=\"getIconSizeName(field.icon.size)\"\r\n [attr.data-icon-color]=\"control.disabled ? 'var(--Base-30)' : ''\"\r\n ></div>\r\n </span>\r\n }\r\n </div>\r\n }\r\n }\r\n </ng-container>\r\n\r\n @if (showFormFieldMessage(control, helperText)) {\r\n <div class=\"fn-field-message-container\">\r\n <span\r\n class=\"fn-field-message-text\"\r\n [ngClass]=\"{\r\n error: isError,\r\n success: isSuccess,\r\n }\"\r\n >\r\n {{ getFieldMessage() | translate }}\r\n </span>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".fn-label-container{display:flex;flex-direction:column;gap:4px;width:100%}.fn-label-text{color:var(--fn-sys-color-on-surface-variant);font-family:var(--fn-sys-font-family-base);font-size:14px;font-weight:500;line-height:20px}.fn-label-text.disabled{color:var(--fn-sys-color-outline)}.fn-label-text.error{color:var(--fn-sys-color-error)}.fn-field-message-container{display:flex;align-items:center;gap:4px;margin-top:4px;min-height:20px}.fn-field-message-text{font-family:var(--fn-sys-font-family-base);font-size:12px;font-weight:400;line-height:16px;color:var(--fn-sys-color-on-surface-variant)}.fn-field-message-text.error{color:var(--fn-sys-color-error)}.fn-field-message-text.success{color:var(--fn-sys-color-success, #2e7d32)}\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.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1073
+ }
1074
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: FNInput, decorators: [{
1075
+ type: Component,
1076
+ args: [{ selector: 'fn-input', standalone: true, imports: [FormsModule, CommonModule, ReactiveFormsModule, TranslateModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (field.name && isVisible() && !field.hidden) {\r\n <div class=\"flex flex-col gap-2\">\r\n @if (field.type! !== 'hidden') {\r\n <label\r\n [for]=\"uniqueId\"\r\n class=\"fn-label !whitespace-normal\"\r\n [ngClass]=\"\r\n (field.statusLabel ? field.statusLabel : field.labelVariant || 'p4') +\r\n ' ' +\r\n (field.className || '')\r\n \"\r\n [style.color]=\"field.color || null\"\r\n >\r\n {{ getTranslatedLabel(field.label) }}\r\n @if (!field.required && !field.hideOptional) {\r\n <span>&nbsp;(Optional)</span>\r\n }\r\n </label>\r\n }\r\n\r\n <ng-container>\r\n @switch (field.type) {\r\n <!-- Textarea Field -->\r\n @case ('textarea') {\r\n <textarea\r\n #fnTextarea\r\n style=\"resize: none\"\r\n [name]=\"field.name\"\r\n [id]=\"uniqueId\"\r\n [required]=\"field.required || false\"\r\n [placeholder]=\"field.placeholder || '' | translate\"\r\n [disabled]=\"field.disabled || false\"\r\n [rows]=\"field.rows || 1\"\r\n [readonly]=\"field.readOnly || false\"\r\n [formControl]=\"control\"\r\n (input)=\"handleTextArea($event)\"\r\n (blur)=\"handleBlur($event)\"\r\n class=\"peer w-full leading-6 rounded-none border-0 border-b border-[var(--Base-30)] pb-1.75 bg-transparent text-base text-[var(--Base-100)] focus:outline-none focus:ring-0 placeholder:!text-[var(--Base-50)] placeholder:!text-base focus:[caret-color:var(--RHB-Blue-100)]\"\r\n [ngClass]=\"{\r\n 'opacity-100': control.disabled || field.readOnly,\r\n '!text-[var(--Base-30)]': control.disabled || field.readOnly,\r\n '!border-[var(--RHB-Blue-100)]': hasFocus(),\r\n '!border-[var(--RHB-Red-100)]': control.touched && control.errors,\r\n '!border-[var(--Green-100)]':\r\n field['hasSuccessBorder'] && control.valid && !control.errors,\r\n }\"\r\n (focus)=\"onFocus()\"\r\n ></textarea>\r\n }\r\n\r\n <!-- Password Field -->\r\n\r\n @case ('password') {\r\n <div class=\"relative w-full\">\r\n <input\r\n [type]=\"showPassword ? 'text' : 'password'\"\r\n [id]=\"uniqueId\"\r\n [required]=\"field.required || false\"\r\n [disabled]=\"field.disabled || false\"\r\n [formControl]=\"control\"\r\n [placeholder]=\"field.placeholder || '' | translate\"\r\n [readonly]=\"field.readOnly || false\"\r\n (input)=\"isAlphanumeric ? handleAlphanumericInput($event) : handleInput($event)\"\r\n (focus)=\"onPasswordFocus()\"\r\n (blur)=\"handlePasswordBlur($event)\"\r\n class=\"peer h-8 w-full leading-6 rounded-none border-0 border-b border-[var(--Base-30)] pb-1.75 bg-transparent text-base text-[var(--Base-100)] focus:outline-none focus:ring-0 placeholder:!text-[var(--Base-50)] placeholder:!text-base focus:[caret-color:var(--RHB-Blue-100)] overflow-hidden text-ellipsis\"\r\n [ngClass]=\"{\r\n 'opacity-100': control.disabled || field.readOnly,\r\n '!text-[var(--Base-30)]': control.disabled || field.readOnly,\r\n '!border-[var(--RHB-Blue-100)]': hasFocus() && !control.disabled,\r\n '!border-[var(--RHB-Red-100)]': control.touched && control.errors,\r\n '!border-[var(--Green-100)]': field['hasSuccessBorder'] && control.valid,\r\n 'pr-12': field.toggleMask && !field.isCopyText,\r\n 'pr-16': field.isCopyText && !field.toggleMask,\r\n 'pr-24': field.toggleMask && field.isCopyText && !field.hasGenerateKey,\r\n 'pr-32': field.toggleMask && field.isCopyText && field.hasGenerateKey,\r\n }\"\r\n />\r\n @if (field.toggleMask && !field.isCopyText) {\r\n <!-- Only Eye Icon -->\r\n <span\r\n class=\"absolute right-0 top-0 cursor-pointer text-[var(--Base-10)] hover:text-[var(--RHB-Blue-100)]\"\r\n (click)=\"showPassword = !showPassword\"\r\n (keydown)=\"showPassword = !showPassword\"\r\n >\r\n @if (!showPassword) {\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"eye-close\"\r\n data-icon-variant=\"Line\"\r\n data-icon-size=\"large\"\r\n data-icon-color=\"var(--Base-100)\"\r\n ></div>\r\n } @else {\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"eye-open\"\r\n data-icon-variant=\"Line\"\r\n data-icon-size=\"large\"\r\n data-icon-color=\"var(--Base-100)\"\r\n ></div>\r\n }\r\n </span>\r\n } @else if (field.isCopyText && !field.toggleMask) {\r\n <!-- Only Copy Icon -->\r\n <span\r\n class=\"absolute right-12 top-0 cursor-pointer text-[var(--Base-10)] hover:text-[var(--RHB-Blue-100)]\"\r\n (click)=\"copyText($event)\"\r\n (keydown)=\"copyText($event)\"\r\n >\r\n <div #iconContainer class=\"icon-container\" data-icon-name=\"two-square\"></div>\r\n </span>\r\n @if (field.hasGenerateKey) {\r\n <span\r\n class=\"absolute right-4 top-0 cursor-pointer text-[var(--Base-10)] hover:text-[var(--RHB-Blue-100)]\"\r\n (click)=\"field.onGenerateKey && field.onGenerateKey()\"\r\n (keydown)=\"field.onGenerateKey && field.onGenerateKey()\"\r\n >\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"two-square\"\r\n data-icon-variant=\"Line\"\r\n data-icon-size=\"large\"\r\n ></div>\r\n </span>\r\n }\r\n } @else if (field.toggleMask && field.isCopyText) {\r\n <!-- Both Eye + Copy Icons -->\r\n <span\r\n class=\"absolute right-20 top-0 cursor-pointer text-[var(--Base-10)] hover:text-[var(--RHB-Blue-100)]\"\r\n (click)=\"showPassword = !showPassword\"\r\n (keydown)=\"showPassword = !showPassword\"\r\n >\r\n @if (!showPassword) {\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"eye-close\"\r\n data-icon-variant=\"Line\"\r\n data-icon-size=\"large\"\r\n data-icon-color=\"var(--Base-100)\"\r\n ></div>\r\n } @else {\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"eye-open\"\r\n data-icon-variant=\"Line\"\r\n data-icon-size=\"large\"\r\n data-icon-color=\"var(--Base-100)\"\r\n ></div>\r\n }\r\n </span>\r\n\r\n <span\r\n class=\"absolute right-12 top-0 cursor-pointer text-[var(--Base-10)] hover:text-[var(--RHB-Blue-100)]\"\r\n (click)=\"copyText($event)\"\r\n (keydown)=\"copyText($event)\"\r\n >\r\n <div #iconContainer class=\"icon-container\" data-icon-name=\"two-square\"></div>\r\n </span>\r\n\r\n @if (field.hasGenerateKey) {\r\n <span\r\n class=\"absolute right-4 top-0 text-[var(--Base-10)]\"\r\n [ngClass]=\"{\r\n 'cursor-pointer hover:text-[var(--RHB-Blue-100)]':\r\n control.value && control.value.length > 0,\r\n 'cursor-not-allowed opacity-50': !control.value || control.value.length === 0,\r\n }\"\r\n (click)=\"\r\n control.value &&\r\n control.value.length > 0 &&\r\n field.onGenerateKey &&\r\n field.onGenerateKey()\r\n \"\r\n (keydown)=\"\r\n control.value &&\r\n control.value.length > 0 &&\r\n field.onGenerateKey &&\r\n field.onGenerateKey()\r\n \"\r\n >\r\n <div\r\n #iconContainer\r\n class=\"icon-container\"\r\n data-icon-name=\"round-arrow-top-left\"\r\n ></div>\r\n </span>\r\n }\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Number Field -->\r\n @case ('number') {\r\n <input\r\n [type]=\"field.type === 'number' && field.isCurrency ? 'text' : 'number'\"\r\n [name]=\"field.name\"\r\n [id]=\"uniqueId\"\r\n [required]=\"field.required || false\"\r\n [placeholder]=\"field.placeholder || '' | translate\"\r\n [disabled]=\"isDisabled || false\"\r\n [readOnly]=\"field.readOnly || false\"\r\n [formControl]=\"control\"\r\n [min]=\"field.type === 'number' && !field.isCurrency ? 0 : null\"\r\n [max]=\"field.type === 'number' && !field.isCurrency ? field.max : null\"\r\n [step]=\"field.type === 'number' && !field.isCurrency ? field.step || 1 : null\"\r\n (input)=\"handleNumberInput($event, field?.minFractionDigits || 2)\"\r\n (paste)=\"handleNumberPaste($event, field?.minFractionDigits || 2)\"\r\n (blur)=\"handleBlur($event, field?.minFractionDigits || 2)\"\r\n (keydown)=\"handleNumberKeydown($event, field?.isCurrency || false)\"\r\n class=\"peer h-8 w-full leading-6 rounded-none border-0 border-b border-[var(--Base-30)] pb-1.75 bg-transparent text-base text-[var(--Base-100)] focus:outline-none focus:ring-0 placeholder:!text-[var(--Base-50)] placeholder:!text-base focus:[caret-color:var(--RHB-Blue-100)]\"\r\n [ngClass]=\"{\r\n 'opacity-100': control.disabled || field.readOnly,\r\n '!text-[var(--Base-30)]': control.disabled || field.readOnly,\r\n '!border-[var(--RHB-Blue-100)]': hasFocus(),\r\n '!border-[var(--RHB-Red-100)]': control.touched && control.errors,\r\n '!border-[var(--Green-100)]':\r\n field['hasSuccessBorder'] && control.valid && !control.errors,\r\n 'text-[24px] font-bold leading-[32px]': field.isCurrency,\r\n }\"\r\n (focus)=\"onFocus()\"\r\n />\r\n }\r\n\r\n <!-- Default Input Field (text, email, etc.) -->\r\n @default {\r\n <div class=\"relative z-0\">\r\n <input\r\n [type]=\"field.type\"\r\n [name]=\"field.name\"\r\n [id]=\"uniqueId\"\r\n [required]=\"field.required || false\"\r\n [placeholder]=\"field.placeholder || '' | translate\"\r\n [disabled]=\"field.disabled || false\"\r\n [readOnly]=\"field.readOnly || false\"\r\n [formControl]=\"control\"\r\n (input)=\"\r\n isEmailField\r\n ? handleEmailInput($event)\r\n : field.type === 'text' && isAlphanumeric\r\n ? handleAlphanumericInput($event)\r\n : handleInput($event)\r\n \"\r\n (blur)=\"handleBlur($event)\"\r\n class=\"peer h-8 w-full leading-6 rounded-none border-0 border-b border-[var(--Base-30)] pb-1.75 bg-transparent text-base text-[var(--Base-100)] focus:outline-none focus:ring-0 placeholder:!text-[var(--Base-50)] placeholder:!text-base focus:[caret-color:var(--RHB-Blue-100)]\"\r\n [ngClass]=\"{\r\n 'opacity-100': control.disabled || field.readOnly,\r\n '!text-[var(--Base-30)]': control.disabled || field.readOnly,\r\n '!border-[var(--RHB-Blue-100)]': hasFocus(),\r\n '!border-[var(--RHB-Red-100)]': control.touched && control.errors,\r\n '!border-[var(--Green-100)]':\r\n field['hasSuccessBorder'] && control.valid && !control.errors,\r\n 'pr-8': field.icon,\r\n }\"\r\n (focus)=\"onFocus()\"\r\n />\r\n @if (field.icon) {\r\n <span class=\"absolute bottom-2 right-0.5 flex items-center\">\r\n <div\r\n #iconContainer\r\n class=\"bg-white\"\r\n [attr.data-icon-name]=\"field.icon.name\"\r\n [attr.data-icon-variant]=\"field.icon.variant\"\r\n [attr.data-icon-size]=\"getIconSizeName(field.icon.size)\"\r\n [attr.data-icon-color]=\"control.disabled ? 'var(--Base-30)' : ''\"\r\n ></div>\r\n </span>\r\n }\r\n </div>\r\n }\r\n }\r\n </ng-container>\r\n\r\n @if (showFormFieldMessage(control, helperText)) {\r\n <div class=\"fn-field-message-container\">\r\n <span\r\n class=\"fn-field-message-text\"\r\n [ngClass]=\"{\r\n error: isError,\r\n success: isSuccess,\r\n }\"\r\n >\r\n {{ getFieldMessage() | translate }}\r\n </span>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".fn-label-container{display:flex;flex-direction:column;gap:4px;width:100%}.fn-label-text{color:var(--fn-sys-color-on-surface-variant);font-family:var(--fn-sys-font-family-base);font-size:14px;font-weight:500;line-height:20px}.fn-label-text.disabled{color:var(--fn-sys-color-outline)}.fn-label-text.error{color:var(--fn-sys-color-error)}.fn-field-message-container{display:flex;align-items:center;gap:4px;margin-top:4px;min-height:20px}.fn-field-message-text{font-family:var(--fn-sys-font-family-base);font-size:12px;font-weight:400;line-height:16px;color:var(--fn-sys-color-on-surface-variant)}.fn-field-message-text.error{color:var(--fn-sys-color-error)}.fn-field-message-text.success{color:var(--fn-sys-color-success, #2e7d32)}\n"] }]
1077
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { field: [{
1078
+ type: Input
1079
+ }], helperHandle: [{
1080
+ type: Input
1081
+ }], toastService: [{
1082
+ type: Input
1083
+ }], currencyMeta: [{
1084
+ type: Input
1085
+ }], defaultLocale: [{
1086
+ type: Input
1087
+ }], form: [{
1088
+ type: Input
1089
+ }], valueChange: [{
1090
+ type: Output
1091
+ }], fieldBlur: [{
1092
+ type: Output
1093
+ }], textareaElement: [{
1094
+ type: ViewChild,
1095
+ args: ['fnTextarea']
1096
+ }], iconContainers: [{
1097
+ type: ViewChildren,
1098
+ args: ['iconContainer']
1099
+ }] } });
1100
+
1101
+ /**
1102
+ * Generated bundle index. Do not edit.
1103
+ */
1104
+
1105
+ export { DEFAULT_CURRENCY_META, FNInput };
1106
+ //# sourceMappingURL=fn-input.mjs.map