@fovestta2/web-angular 1.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.
Files changed (30) hide show
  1. package/README.md +24 -0
  2. package/esm2022/fovestta2-web-angular.mjs +5 -0
  3. package/esm2022/lib/fv-checkbox/fv-checkbox.component.mjs +40 -0
  4. package/esm2022/lib/fv-controls.module.mjs +83 -0
  5. package/esm2022/lib/fv-date-field/fv-date-field.component.mjs +125 -0
  6. package/esm2022/lib/fv-dropdown/fv-dropdown.component.mjs +121 -0
  7. package/esm2022/lib/fv-entry-field/fv-entry-field.component.mjs +106 -0
  8. package/esm2022/lib/fv-file-selector/fv-file-selector.component.mjs +139 -0
  9. package/esm2022/lib/fv-image-selector/fv-image-selector.component.mjs +156 -0
  10. package/esm2022/lib/fv-month-year-field/fv-month-year-field.component.mjs +120 -0
  11. package/esm2022/lib/fv-number-field/fv-number-field.component.mjs +108 -0
  12. package/esm2022/lib/fv-radio-group/fv-radio-group.component.mjs +47 -0
  13. package/esm2022/lib/fv-rich-text-editor/fv-rich-text-editor.component.mjs +163 -0
  14. package/esm2022/public-api.mjs +15 -0
  15. package/fesm2022/fovestta2-web-angular.mjs +1149 -0
  16. package/fesm2022/fovestta2-web-angular.mjs.map +1 -0
  17. package/index.d.ts +5 -0
  18. package/lib/fv-checkbox/fv-checkbox.component.d.ts +14 -0
  19. package/lib/fv-controls.module.d.ts +18 -0
  20. package/lib/fv-date-field/fv-date-field.component.d.ts +29 -0
  21. package/lib/fv-dropdown/fv-dropdown.component.d.ts +34 -0
  22. package/lib/fv-entry-field/fv-entry-field.component.d.ts +27 -0
  23. package/lib/fv-file-selector/fv-file-selector.component.d.ts +36 -0
  24. package/lib/fv-image-selector/fv-image-selector.component.d.ts +39 -0
  25. package/lib/fv-month-year-field/fv-month-year-field.component.d.ts +29 -0
  26. package/lib/fv-number-field/fv-number-field.component.d.ts +29 -0
  27. package/lib/fv-radio-group/fv-radio-group.component.d.ts +22 -0
  28. package/lib/fv-rich-text-editor/fv-rich-text-editor.component.d.ts +36 -0
  29. package/package.json +28 -0
  30. package/public-api.d.ts +11 -0
@@ -0,0 +1,1149 @@
1
+ import * as i0 from '@angular/core';
2
+ import { EventEmitter, Component, Input, Output, ViewChild, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i2 from '@angular/forms';
6
+ import { ReactiveFormsModule } from '@angular/forms';
7
+ import { Validator } from '@fovestta2/validation-engine';
8
+ import * as i1$1 from '@angular/platform-browser';
9
+ import { Validator as Validator$1 } from '@fovestta/validation-engine';
10
+
11
+ class FvEntryFieldComponent {
12
+ label = '';
13
+ placeholder = '';
14
+ schema;
15
+ control;
16
+ disabled = false;
17
+ readonly = false;
18
+ type = 'text';
19
+ valueChange = new EventEmitter();
20
+ blur = new EventEmitter();
21
+ focus = new EventEmitter();
22
+ errorMessage = null;
23
+ subscription;
24
+ ngOnInit() {
25
+ if (!this.control) {
26
+ console.error('FvEntryField: control is required');
27
+ return;
28
+ }
29
+ if (!this.schema) {
30
+ console.warn('FvEntryField: schema is not provided, validation will be skipped');
31
+ return;
32
+ }
33
+ // Subscribe to value changes
34
+ this.subscription = this.control.valueChanges.subscribe((value) => {
35
+ this.validateValue(value);
36
+ this.valueChange.emit(value);
37
+ });
38
+ // Validate initial value
39
+ if (this.control.value) {
40
+ this.validateValue(this.control.value);
41
+ }
42
+ }
43
+ ngOnDestroy() {
44
+ this.subscription?.unsubscribe();
45
+ }
46
+ validateValue(value) {
47
+ if (!this.schema)
48
+ return;
49
+ const result = Validator.validate(value, this.schema);
50
+ this.errorMessage = result.errorKey;
51
+ if (!result.isValid && result.errorKey) {
52
+ this.control.setErrors({ [result.errorKey]: true });
53
+ }
54
+ else {
55
+ this.control.setErrors(null);
56
+ }
57
+ }
58
+ onBlur(event) {
59
+ if (this.control && this.schema) {
60
+ this.validateValue(this.control.value);
61
+ }
62
+ this.blur.emit();
63
+ }
64
+ onFocus(event) {
65
+ this.focus.emit();
66
+ }
67
+ isRequired() {
68
+ return (this.schema?.rules?.some((r) => r.name === 'required' && r.params?.['enabled']) || false);
69
+ }
70
+ getErrorMessage() {
71
+ if (!this.errorMessage)
72
+ return '';
73
+ // You can implement a translation service here
74
+ const errorMessages = {
75
+ ERR_REQUIRED: 'This field is required',
76
+ ERR_MIN_LENGTH: 'Value is too short',
77
+ ERR_MAX_LENGTH: 'Value is too long',
78
+ ERR_REGEX_MISMATCH: 'Invalid format',
79
+ };
80
+ return errorMessages[this.errorMessage] || this.errorMessage;
81
+ }
82
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvEntryFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
83
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvEntryFieldComponent, isStandalone: true, selector: "fv-entry-field", inputs: { label: "label", placeholder: "placeholder", schema: "schema", control: "control", disabled: "disabled", readonly: "readonly", type: "type" }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus" }, ngImport: i0, template: "<div class=\"fv-entry-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-label\" [class.fv-label-required]=\"isRequired()\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"fv-required-asterisk\">*</span>\r\n </label>\r\n\r\n <input [type]=\"type\" [formControl]=\"control\" [placeholder]=\"placeholder\" [disabled]=\"disabled\" [readonly]=\"readonly\"\r\n (blur)=\"onBlur($event)\" (focus)=\"onFocus($event)\" class=\"fv-input\" [class.fv-input-error]=\"errorMessage\"\r\n [class.fv-input-disabled]=\"disabled\" />\r\n\r\n <span *ngIf=\"errorMessage\" class=\"fv-error-message\">\r\n {{ getErrorMessage() }}\r\n </span>\r\n</div>", styles: [".fv-entry-field-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-label{margin-bottom:6px;font-size:14px;font-weight:500;color:var(--fv-text-primary, #333333);display:flex;align-items:center;gap:4px}.fv-required-asterisk{color:var(--fv-error-color, #dc3545);font-weight:700}.fv-input{padding:10px 12px;border:1px solid var(--fv-border-default, #cccccc);border-radius:4px;font-size:14px;font-family:inherit;transition:all .2s ease-in-out;background-color:var(--fv-background-default, #ffffff);color:var(--fv-text-primary, #333333)}.fv-input:focus{outline:none;border-color:var(--fv-border-focus, #007bff);box-shadow:0 0 0 3px var(--fv-focus-shadow, rgba(0, 123, 255, .1))}.fv-input:hover:not(:disabled):not(:focus){border-color:var(--fv-border-hover, #999999)}.fv-input-error{border-color:var(--fv-border-error, #dc3545)!important}.fv-input-error:focus{box-shadow:0 0 0 3px var(--fv-error-shadow, rgba(220, 53, 69, .1))}.fv-input-disabled{background-color:var(--fv-background-disabled, #f5f5f5);cursor:not-allowed;opacity:.6}.fv-input::placeholder{color:var(--fv-text-placeholder, #999999);opacity:1}.fv-error-message{margin-top:4px;font-size:12px;color:var(--fv-error-color, #dc3545);display:flex;align-items:center;gap:4px}.fv-error-message:before{content:\"\\26a0\";font-size:14px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
84
+ }
85
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvEntryFieldComponent, decorators: [{
86
+ type: Component,
87
+ args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-entry-field', template: "<div class=\"fv-entry-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-label\" [class.fv-label-required]=\"isRequired()\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"fv-required-asterisk\">*</span>\r\n </label>\r\n\r\n <input [type]=\"type\" [formControl]=\"control\" [placeholder]=\"placeholder\" [disabled]=\"disabled\" [readonly]=\"readonly\"\r\n (blur)=\"onBlur($event)\" (focus)=\"onFocus($event)\" class=\"fv-input\" [class.fv-input-error]=\"errorMessage\"\r\n [class.fv-input-disabled]=\"disabled\" />\r\n\r\n <span *ngIf=\"errorMessage\" class=\"fv-error-message\">\r\n {{ getErrorMessage() }}\r\n </span>\r\n</div>", styles: [".fv-entry-field-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-label{margin-bottom:6px;font-size:14px;font-weight:500;color:var(--fv-text-primary, #333333);display:flex;align-items:center;gap:4px}.fv-required-asterisk{color:var(--fv-error-color, #dc3545);font-weight:700}.fv-input{padding:10px 12px;border:1px solid var(--fv-border-default, #cccccc);border-radius:4px;font-size:14px;font-family:inherit;transition:all .2s ease-in-out;background-color:var(--fv-background-default, #ffffff);color:var(--fv-text-primary, #333333)}.fv-input:focus{outline:none;border-color:var(--fv-border-focus, #007bff);box-shadow:0 0 0 3px var(--fv-focus-shadow, rgba(0, 123, 255, .1))}.fv-input:hover:not(:disabled):not(:focus){border-color:var(--fv-border-hover, #999999)}.fv-input-error{border-color:var(--fv-border-error, #dc3545)!important}.fv-input-error:focus{box-shadow:0 0 0 3px var(--fv-error-shadow, rgba(220, 53, 69, .1))}.fv-input-disabled{background-color:var(--fv-background-disabled, #f5f5f5);cursor:not-allowed;opacity:.6}.fv-input::placeholder{color:var(--fv-text-placeholder, #999999);opacity:1}.fv-error-message{margin-top:4px;font-size:12px;color:var(--fv-error-color, #dc3545);display:flex;align-items:center;gap:4px}.fv-error-message:before{content:\"\\26a0\";font-size:14px}\n"] }]
88
+ }], propDecorators: { label: [{
89
+ type: Input
90
+ }], placeholder: [{
91
+ type: Input
92
+ }], schema: [{
93
+ type: Input
94
+ }], control: [{
95
+ type: Input
96
+ }], disabled: [{
97
+ type: Input
98
+ }], readonly: [{
99
+ type: Input
100
+ }], type: [{
101
+ type: Input
102
+ }], valueChange: [{
103
+ type: Output
104
+ }], blur: [{
105
+ type: Output
106
+ }], focus: [{
107
+ type: Output
108
+ }] } });
109
+
110
+ class FvDateFieldComponent {
111
+ label = '';
112
+ schema;
113
+ control;
114
+ disabled = false;
115
+ readonly = false;
116
+ // Optional native constraints (can also be derived from schema)
117
+ min;
118
+ max;
119
+ valueChange = new EventEmitter();
120
+ blur = new EventEmitter();
121
+ focus = new EventEmitter();
122
+ errorMessage = null;
123
+ subscription;
124
+ ngOnInit() {
125
+ if (!this.control) {
126
+ console.error('FvDateField: control is required');
127
+ return;
128
+ }
129
+ // Try to derive min/max from schema if not explicitly provided
130
+ this.extractConstraintsFromSchema();
131
+ // Subscribe to value changes
132
+ this.subscription = this.control.valueChanges.subscribe((value) => {
133
+ this.validateValue(value);
134
+ this.valueChange.emit(value);
135
+ });
136
+ // Validate initial value
137
+ if (this.control.value) {
138
+ this.validateValue(this.control.value);
139
+ }
140
+ }
141
+ ngOnDestroy() {
142
+ this.subscription?.unsubscribe();
143
+ }
144
+ extractConstraintsFromSchema() {
145
+ if (!this.schema?.rules)
146
+ return;
147
+ for (const rule of this.schema.rules) {
148
+ if (rule.name === 'minDate' && !this.min) {
149
+ this.min = this.formatDate(rule.params?.['value']);
150
+ }
151
+ if (rule.name === 'maxDate' && !this.max) {
152
+ this.max = this.formatDate(rule.params?.['value']);
153
+ }
154
+ // We could also calculate min/max from age rules, but that's complex (dynamic dates)
155
+ // leaving out for now or can be added later.
156
+ }
157
+ }
158
+ formatDate(date) {
159
+ if (!date)
160
+ return '';
161
+ const d = new Date(date);
162
+ if (isNaN(d.getTime()))
163
+ return '';
164
+ return d.toISOString().split('T')[0];
165
+ }
166
+ validateValue(value) {
167
+ if (!this.schema)
168
+ return;
169
+ const result = Validator.validate(value, this.schema);
170
+ this.errorMessage = result.errorKey;
171
+ if (!result.isValid && result.errorKey) {
172
+ this.control.setErrors({ [result.errorKey]: true });
173
+ }
174
+ else {
175
+ this.control.setErrors(null);
176
+ }
177
+ }
178
+ onBlur(event) {
179
+ this.validateValue(this.control.value);
180
+ this.blur.emit();
181
+ }
182
+ onFocus(event) {
183
+ this.focus.emit();
184
+ }
185
+ isRequired() {
186
+ return (this.schema?.rules?.some((r) => r.name === 'required' && r.params?.['enabled']) || false);
187
+ }
188
+ getErrorMessage() {
189
+ if (!this.errorMessage)
190
+ return '';
191
+ const errorMessages = {
192
+ ERR_REQUIRED: 'Date is required',
193
+ ERR_MIN_DATE: `Date must be after ${this.min}`,
194
+ ERR_MAX_DATE: `Date must be before ${this.max}`,
195
+ ERR_MIN_AGE: 'Age requirement not met (too young)',
196
+ ERR_MAX_AGE: 'Age requirement not met (too old)',
197
+ };
198
+ return errorMessages[this.errorMessage] || this.errorMessage;
199
+ }
200
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvDateFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
201
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvDateFieldComponent, isStandalone: true, selector: "fv-date-field", inputs: { label: "label", schema: "schema", control: "control", disabled: "disabled", readonly: "readonly", min: "min", max: "max" }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus" }, ngImport: i0, template: "<div class=\"fv-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-label\">\r\n {{ label }} <span *ngIf=\"isRequired()\" class=\"required\">*</span>\r\n </label>\r\n\r\n <input type=\"date\" [formControl]=\"control\" class=\"fv-input\" [class.error]=\"!!errorMessage\" [attr.min]=\"min\"\r\n [attr.max]=\"max\" [readonly]=\"readonly\" (blur)=\"onBlur($event)\" (focus)=\"onFocus($event)\" />\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-error-message\">\r\n {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-field-container{display:flex;flex-direction:column;margin-bottom:1rem}.fv-label{font-size:.875rem;font-weight:500;margin-bottom:.5rem;color:#374151}.required{color:#ef4444}.fv-input{padding:.5rem .75rem;border:1px solid #d1d5db;border-radius:.375rem;font-size:1rem;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.fv-input:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.fv-input.error{border-color:#ef4444}.fv-input.error:focus{box-shadow:0 0 0 3px #ef44441a}.fv-error-message{margin-top:.25rem;font-size:.75rem;color:#ef4444}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
202
+ }
203
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvDateFieldComponent, decorators: [{
204
+ type: Component,
205
+ args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-date-field', template: "<div class=\"fv-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-label\">\r\n {{ label }} <span *ngIf=\"isRequired()\" class=\"required\">*</span>\r\n </label>\r\n\r\n <input type=\"date\" [formControl]=\"control\" class=\"fv-input\" [class.error]=\"!!errorMessage\" [attr.min]=\"min\"\r\n [attr.max]=\"max\" [readonly]=\"readonly\" (blur)=\"onBlur($event)\" (focus)=\"onFocus($event)\" />\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-error-message\">\r\n {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-field-container{display:flex;flex-direction:column;margin-bottom:1rem}.fv-label{font-size:.875rem;font-weight:500;margin-bottom:.5rem;color:#374151}.required{color:#ef4444}.fv-input{padding:.5rem .75rem;border:1px solid #d1d5db;border-radius:.375rem;font-size:1rem;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.fv-input:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.fv-input.error{border-color:#ef4444}.fv-input.error:focus{box-shadow:0 0 0 3px #ef44441a}.fv-error-message{margin-top:.25rem;font-size:.75rem;color:#ef4444}\n"] }]
206
+ }], propDecorators: { label: [{
207
+ type: Input
208
+ }], schema: [{
209
+ type: Input
210
+ }], control: [{
211
+ type: Input
212
+ }], disabled: [{
213
+ type: Input
214
+ }], readonly: [{
215
+ type: Input
216
+ }], min: [{
217
+ type: Input
218
+ }], max: [{
219
+ type: Input
220
+ }], valueChange: [{
221
+ type: Output
222
+ }], blur: [{
223
+ type: Output
224
+ }], focus: [{
225
+ type: Output
226
+ }] } });
227
+
228
+ class FvMonthYearFieldComponent {
229
+ label = '';
230
+ schema;
231
+ control;
232
+ disabled = false;
233
+ readonly = false;
234
+ min; // YYYY-MM
235
+ max; // YYYY-MM
236
+ valueChange = new EventEmitter();
237
+ blur = new EventEmitter();
238
+ focus = new EventEmitter();
239
+ errorMessage = null;
240
+ subscription;
241
+ ngOnInit() {
242
+ if (!this.control) {
243
+ console.error('FvMonthYearField: control is required');
244
+ return;
245
+ }
246
+ this.extractConstraintsFromSchema();
247
+ this.subscription = this.control.valueChanges.subscribe((value) => {
248
+ this.validateValue(value);
249
+ this.valueChange.emit(value);
250
+ });
251
+ if (this.control.value) {
252
+ this.validateValue(this.control.value);
253
+ }
254
+ }
255
+ ngOnDestroy() {
256
+ this.subscription?.unsubscribe();
257
+ }
258
+ extractConstraintsFromSchema() {
259
+ if (!this.schema?.rules)
260
+ return;
261
+ for (const rule of this.schema.rules) {
262
+ if (rule.name === 'minDate' && !this.min) {
263
+ this.min = this.formatMonth(rule.params?.['value']);
264
+ }
265
+ if (rule.name === 'maxDate' && !this.max) {
266
+ this.max = this.formatMonth(rule.params?.['value']);
267
+ }
268
+ }
269
+ }
270
+ formatMonth(date) {
271
+ if (!date)
272
+ return '';
273
+ const d = new Date(date);
274
+ if (isNaN(d.getTime()))
275
+ return '';
276
+ return d.toISOString().slice(0, 7); // YYYY-MM
277
+ }
278
+ validateValue(value) {
279
+ if (!this.schema)
280
+ return;
281
+ // For month picker, value is YYYY-MM.
282
+ // The generic Validator rules use new Date(value), which treats YYYY-MM as YYYY-MM-01.
283
+ // This generally works for min/max logic.
284
+ const result = Validator.validate(value, this.schema);
285
+ this.errorMessage = result.errorKey;
286
+ if (!result.isValid && result.errorKey) {
287
+ this.control.setErrors({ [result.errorKey]: true });
288
+ }
289
+ else {
290
+ this.control.setErrors(null);
291
+ }
292
+ }
293
+ onBlur(event) {
294
+ this.validateValue(this.control.value);
295
+ this.blur.emit();
296
+ }
297
+ onFocus(event) {
298
+ this.focus.emit();
299
+ }
300
+ isRequired() {
301
+ return (this.schema?.rules?.some((r) => r.name === 'required' && r.params?.['enabled']) || false);
302
+ }
303
+ getErrorMessage() {
304
+ if (!this.errorMessage)
305
+ return '';
306
+ const errorMessages = {
307
+ ERR_REQUIRED: 'Month/Year is required',
308
+ ERR_MIN_DATE: `Date must be after ${this.min}`,
309
+ ERR_MAX_DATE: `Date must be before ${this.max}`,
310
+ };
311
+ return errorMessages[this.errorMessage] || this.errorMessage;
312
+ }
313
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvMonthYearFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
314
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvMonthYearFieldComponent, isStandalone: true, selector: "fv-month-year-field", inputs: { label: "label", schema: "schema", control: "control", disabled: "disabled", readonly: "readonly", min: "min", max: "max" }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus" }, ngImport: i0, template: "<div class=\"fv-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-label\">\r\n {{ label }} <span *ngIf=\"isRequired()\" class=\"required\">*</span>\r\n </label>\r\n\r\n <input type=\"month\" [formControl]=\"control\" class=\"fv-input\" [class.error]=\"!!errorMessage\" [attr.min]=\"min\"\r\n [attr.max]=\"max\" [readonly]=\"readonly\" (blur)=\"onBlur($event)\" (focus)=\"onFocus($event)\" />\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-error-message\">\r\n {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-field-container{display:flex;flex-direction:column;margin-bottom:1rem}.fv-label{font-size:.875rem;font-weight:500;margin-bottom:.5rem;color:#374151}.required{color:#ef4444}.fv-input{padding:.5rem .75rem;border:1px solid #d1d5db;border-radius:.375rem;font-size:1rem;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.fv-input:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.fv-input.error{border-color:#ef4444}.fv-input.error:focus{box-shadow:0 0 0 3px #ef44441a}.fv-error-message{margin-top:.25rem;font-size:.75rem;color:#ef4444}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
315
+ }
316
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvMonthYearFieldComponent, decorators: [{
317
+ type: Component,
318
+ args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-month-year-field', template: "<div class=\"fv-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-label\">\r\n {{ label }} <span *ngIf=\"isRequired()\" class=\"required\">*</span>\r\n </label>\r\n\r\n <input type=\"month\" [formControl]=\"control\" class=\"fv-input\" [class.error]=\"!!errorMessage\" [attr.min]=\"min\"\r\n [attr.max]=\"max\" [readonly]=\"readonly\" (blur)=\"onBlur($event)\" (focus)=\"onFocus($event)\" />\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-error-message\">\r\n {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-field-container{display:flex;flex-direction:column;margin-bottom:1rem}.fv-label{font-size:.875rem;font-weight:500;margin-bottom:.5rem;color:#374151}.required{color:#ef4444}.fv-input{padding:.5rem .75rem;border:1px solid #d1d5db;border-radius:.375rem;font-size:1rem;line-height:1.5;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.fv-input:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.fv-input.error{border-color:#ef4444}.fv-input.error:focus{box-shadow:0 0 0 3px #ef44441a}.fv-error-message{margin-top:.25rem;font-size:.75rem;color:#ef4444}\n"] }]
319
+ }], propDecorators: { label: [{
320
+ type: Input
321
+ }], schema: [{
322
+ type: Input
323
+ }], control: [{
324
+ type: Input
325
+ }], disabled: [{
326
+ type: Input
327
+ }], readonly: [{
328
+ type: Input
329
+ }], min: [{
330
+ type: Input
331
+ }], max: [{
332
+ type: Input
333
+ }], valueChange: [{
334
+ type: Output
335
+ }], blur: [{
336
+ type: Output
337
+ }], focus: [{
338
+ type: Output
339
+ }] } });
340
+
341
+ class FvNumberFieldComponent {
342
+ label = '';
343
+ placeholder = '';
344
+ schema;
345
+ control;
346
+ disabled = false;
347
+ readonly = false;
348
+ min;
349
+ max;
350
+ step = 1;
351
+ valueChange = new EventEmitter();
352
+ blur = new EventEmitter();
353
+ focus = new EventEmitter();
354
+ errorMessage = null;
355
+ subscription;
356
+ ngOnInit() {
357
+ if (!this.control) {
358
+ console.error('FvNumberField: control is required');
359
+ return;
360
+ }
361
+ if (!this.schema) {
362
+ console.warn('FvNumberField: schema is not provided');
363
+ return;
364
+ }
365
+ this.subscription = this.control.valueChanges.subscribe((value) => {
366
+ this.validateValue(value);
367
+ this.valueChange.emit(value);
368
+ });
369
+ if (this.control.value) {
370
+ this.validateValue(this.control.value);
371
+ }
372
+ }
373
+ ngOnDestroy() {
374
+ this.subscription?.unsubscribe();
375
+ }
376
+ validateValue(value) {
377
+ if (!this.schema)
378
+ return;
379
+ const result = Validator.validate(value, this.schema);
380
+ this.errorMessage = result.errorKey;
381
+ if (!result.isValid && result.errorKey) {
382
+ this.control.setErrors({ [result.errorKey]: true });
383
+ }
384
+ else {
385
+ this.control.setErrors(null);
386
+ }
387
+ }
388
+ onBlur() {
389
+ if (this.control && this.schema) {
390
+ this.validateValue(this.control.value);
391
+ }
392
+ this.blur.emit();
393
+ }
394
+ onFocus() {
395
+ this.focus.emit();
396
+ }
397
+ isRequired() {
398
+ return (this.schema?.rules?.some((r) => r.name === 'required' && r.params?.['enabled']) || false);
399
+ }
400
+ getErrorMessage() {
401
+ if (!this.errorMessage)
402
+ return '';
403
+ const errorMessages = {
404
+ ERR_REQUIRED: 'This field is required',
405
+ ERR_NUMERIC_INVALID: 'Please enter a valid number',
406
+ ERR_RANGE_INVALID: 'Number is out of range',
407
+ };
408
+ return errorMessages[this.errorMessage] || this.errorMessage;
409
+ }
410
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvNumberFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
411
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvNumberFieldComponent, isStandalone: true, selector: "fv-number-field", inputs: { label: "label", placeholder: "placeholder", schema: "schema", control: "control", disabled: "disabled", readonly: "readonly", min: "min", max: "max", step: "step" }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus" }, ngImport: i0, template: "<div class=\"fv-number-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"fv-required-asterisk\">*</span>\r\n </label>\r\n\r\n <input type=\"number\" [formControl]=\"control\" [placeholder]=\"placeholder\" [disabled]=\"disabled\" [readonly]=\"readonly\"\r\n [min]=\"min\" [max]=\"max\" [step]=\"step\" (blur)=\"onBlur()\" (focus)=\"onFocus()\" class=\"fv-input\"\r\n [class.fv-input-error]=\"errorMessage\" [class.fv-input-disabled]=\"disabled\" />\r\n\r\n <span *ngIf=\"errorMessage\" class=\"fv-error-message\">\r\n {{ getErrorMessage() }}\r\n </span>\r\n</div>", styles: [".fv-number-field-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-label{margin-bottom:6px;font-size:14px;font-weight:500;color:var(--fv-text-primary, #333333);display:flex;align-items:center;gap:4px}.fv-required-asterisk{color:var(--fv-error-color, #dc3545);font-weight:700}.fv-input{padding:10px 12px;border:1px solid var(--fv-border-default, #cccccc);border-radius:4px;font-size:14px;font-family:inherit;transition:all .2s ease-in-out;background-color:var(--fv-background-default, #ffffff);color:var(--fv-text-primary, #333333)}.fv-input:focus{outline:none;border-color:var(--fv-border-focus, #007bff);box-shadow:0 0 0 3px var(--fv-focus-shadow, rgba(0, 123, 255, .1))}.fv-input:hover:not(:disabled):not(:focus){border-color:var(--fv-border-hover, #999999)}.fv-input-error{border-color:var(--fv-border-error, #dc3545)!important}.fv-input-error:focus{box-shadow:0 0 0 3px var(--fv-error-shadow, rgba(220, 53, 69, .1))}.fv-input-disabled{background-color:var(--fv-background-disabled, #f5f5f5);cursor:not-allowed;opacity:.6}.fv-error-message{margin-top:4px;font-size:12px;color:var(--fv-error-color, #dc3545);display:flex;align-items:center;gap:4px}.fv-error-message:before{content:\"\\26a0\";font-size:14px}.fv-input::-webkit-inner-spin-button,.fv-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.fv-input[type=number]{-moz-appearance:textfield}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.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: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
412
+ }
413
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvNumberFieldComponent, decorators: [{
414
+ type: Component,
415
+ args: [{ selector: 'fv-number-field', standalone: true, imports: [CommonModule, ReactiveFormsModule], template: "<div class=\"fv-number-field-container\">\r\n <label *ngIf=\"label\" class=\"fv-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"fv-required-asterisk\">*</span>\r\n </label>\r\n\r\n <input type=\"number\" [formControl]=\"control\" [placeholder]=\"placeholder\" [disabled]=\"disabled\" [readonly]=\"readonly\"\r\n [min]=\"min\" [max]=\"max\" [step]=\"step\" (blur)=\"onBlur()\" (focus)=\"onFocus()\" class=\"fv-input\"\r\n [class.fv-input-error]=\"errorMessage\" [class.fv-input-disabled]=\"disabled\" />\r\n\r\n <span *ngIf=\"errorMessage\" class=\"fv-error-message\">\r\n {{ getErrorMessage() }}\r\n </span>\r\n</div>", styles: [".fv-number-field-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-label{margin-bottom:6px;font-size:14px;font-weight:500;color:var(--fv-text-primary, #333333);display:flex;align-items:center;gap:4px}.fv-required-asterisk{color:var(--fv-error-color, #dc3545);font-weight:700}.fv-input{padding:10px 12px;border:1px solid var(--fv-border-default, #cccccc);border-radius:4px;font-size:14px;font-family:inherit;transition:all .2s ease-in-out;background-color:var(--fv-background-default, #ffffff);color:var(--fv-text-primary, #333333)}.fv-input:focus{outline:none;border-color:var(--fv-border-focus, #007bff);box-shadow:0 0 0 3px var(--fv-focus-shadow, rgba(0, 123, 255, .1))}.fv-input:hover:not(:disabled):not(:focus){border-color:var(--fv-border-hover, #999999)}.fv-input-error{border-color:var(--fv-border-error, #dc3545)!important}.fv-input-error:focus{box-shadow:0 0 0 3px var(--fv-error-shadow, rgba(220, 53, 69, .1))}.fv-input-disabled{background-color:var(--fv-background-disabled, #f5f5f5);cursor:not-allowed;opacity:.6}.fv-error-message{margin-top:4px;font-size:12px;color:var(--fv-error-color, #dc3545);display:flex;align-items:center;gap:4px}.fv-error-message:before{content:\"\\26a0\";font-size:14px}.fv-input::-webkit-inner-spin-button,.fv-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.fv-input[type=number]{-moz-appearance:textfield}\n"] }]
416
+ }], propDecorators: { label: [{
417
+ type: Input
418
+ }], placeholder: [{
419
+ type: Input
420
+ }], schema: [{
421
+ type: Input
422
+ }], control: [{
423
+ type: Input
424
+ }], disabled: [{
425
+ type: Input
426
+ }], readonly: [{
427
+ type: Input
428
+ }], min: [{
429
+ type: Input
430
+ }], max: [{
431
+ type: Input
432
+ }], step: [{
433
+ type: Input
434
+ }], valueChange: [{
435
+ type: Output
436
+ }], blur: [{
437
+ type: Output
438
+ }], focus: [{
439
+ type: Output
440
+ }] } });
441
+
442
+ class FvCheckboxComponent {
443
+ label = '';
444
+ control;
445
+ disabled = false;
446
+ required = false;
447
+ valueChange = new EventEmitter();
448
+ ngOnInit() {
449
+ if (!this.control) {
450
+ console.error('FvCheckbox: control is required');
451
+ }
452
+ }
453
+ onChange(event) {
454
+ const checked = event.target.checked;
455
+ this.control.setValue(checked);
456
+ this.valueChange.emit(checked);
457
+ }
458
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvCheckboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
459
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvCheckboxComponent, isStandalone: true, selector: "fv-checkbox", inputs: { label: "label", control: "control", disabled: "disabled", required: "required" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fv-checkbox-container\">\r\n <label class=\"fv-checkbox-label\">\r\n <input type=\"checkbox\" [formControl]=\"control\" [disabled]=\"disabled\" (change)=\"onChange($event)\"\r\n class=\"fv-checkbox-input\" />\r\n <span class=\"fv-checkbox-custom\"></span>\r\n <span class=\"fv-checkbox-text\">\r\n {{ label }}\r\n <span *ngIf=\"required\" class=\"fv-required-asterisk\">*</span>\r\n </span>\r\n </label>\r\n</div>", styles: [".fv-checkbox-container{margin-bottom:12px}.fv-checkbox-label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;position:relative;padding-left:32px;min-height:24px}.fv-checkbox-input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.fv-checkbox-custom{position:absolute;left:0;top:0;height:20px;width:20px;background-color:var(--fv-background-default, #ffffff);border:2px solid var(--fv-border-default, #cccccc);border-radius:4px;transition:all .2s}.fv-checkbox-label:hover .fv-checkbox-custom{border-color:var(--fv-border-hover, #999999)}.fv-checkbox-input:checked~.fv-checkbox-custom{background-color:var(--fv-border-focus, #667eea);border-color:var(--fv-border-focus, #667eea)}.fv-checkbox-custom:after{content:\"\";position:absolute;display:none;left:6px;top:2px;width:5px;height:10px;border:solid white;border-width:0 2px 2px 0;transform:rotate(45deg)}.fv-checkbox-input:checked~.fv-checkbox-custom:after{display:block}.fv-checkbox-input:disabled~.fv-checkbox-custom{background-color:var(--fv-background-disabled, #f5f5f5);cursor:not-allowed;opacity:.6}.fv-checkbox-text{font-size:14px;color:var(--fv-text-primary, #333333)}.fv-required-asterisk{color:var(--fv-error-color, #dc3545);margin-left:4px;font-weight:700}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
460
+ }
461
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvCheckboxComponent, decorators: [{
462
+ type: Component,
463
+ args: [{ selector: 'fv-checkbox', standalone: true, imports: [CommonModule, ReactiveFormsModule], template: "<div class=\"fv-checkbox-container\">\r\n <label class=\"fv-checkbox-label\">\r\n <input type=\"checkbox\" [formControl]=\"control\" [disabled]=\"disabled\" (change)=\"onChange($event)\"\r\n class=\"fv-checkbox-input\" />\r\n <span class=\"fv-checkbox-custom\"></span>\r\n <span class=\"fv-checkbox-text\">\r\n {{ label }}\r\n <span *ngIf=\"required\" class=\"fv-required-asterisk\">*</span>\r\n </span>\r\n </label>\r\n</div>", styles: [".fv-checkbox-container{margin-bottom:12px}.fv-checkbox-label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;position:relative;padding-left:32px;min-height:24px}.fv-checkbox-input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.fv-checkbox-custom{position:absolute;left:0;top:0;height:20px;width:20px;background-color:var(--fv-background-default, #ffffff);border:2px solid var(--fv-border-default, #cccccc);border-radius:4px;transition:all .2s}.fv-checkbox-label:hover .fv-checkbox-custom{border-color:var(--fv-border-hover, #999999)}.fv-checkbox-input:checked~.fv-checkbox-custom{background-color:var(--fv-border-focus, #667eea);border-color:var(--fv-border-focus, #667eea)}.fv-checkbox-custom:after{content:\"\";position:absolute;display:none;left:6px;top:2px;width:5px;height:10px;border:solid white;border-width:0 2px 2px 0;transform:rotate(45deg)}.fv-checkbox-input:checked~.fv-checkbox-custom:after{display:block}.fv-checkbox-input:disabled~.fv-checkbox-custom{background-color:var(--fv-background-disabled, #f5f5f5);cursor:not-allowed;opacity:.6}.fv-checkbox-text{font-size:14px;color:var(--fv-text-primary, #333333)}.fv-required-asterisk{color:var(--fv-error-color, #dc3545);margin-left:4px;font-weight:700}\n"] }]
464
+ }], propDecorators: { label: [{
465
+ type: Input
466
+ }], control: [{
467
+ type: Input
468
+ }], disabled: [{
469
+ type: Input
470
+ }], required: [{
471
+ type: Input
472
+ }], valueChange: [{
473
+ type: Output
474
+ }] } });
475
+
476
+ class FvRadioGroupComponent {
477
+ label = '';
478
+ control;
479
+ options = [];
480
+ disabled = false;
481
+ required = false;
482
+ name = `fv-radio-${Math.random().toString(36).substr(2, 9)}`;
483
+ valueChange = new EventEmitter();
484
+ ngOnInit() {
485
+ if (!this.control) {
486
+ console.error('FvRadioGroup: control is required');
487
+ }
488
+ }
489
+ onChange(value) {
490
+ this.control.setValue(value);
491
+ this.valueChange.emit(value);
492
+ }
493
+ isSelected(value) {
494
+ return this.control.value === value;
495
+ }
496
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvRadioGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
497
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvRadioGroupComponent, isStandalone: true, selector: "fv-radio-group", inputs: { label: "label", control: "control", options: "options", disabled: "disabled", required: "required", name: "name" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"fv-radio-group-container\">\r\n <label *ngIf=\"label\" class=\"fv-group-label\">\r\n {{ label }}\r\n <span *ngIf=\"required\" class=\"fv-required-asterisk\">*</span>\r\n </label>\r\n \r\n <div class=\"fv-radio-options\">\r\n <label \r\n *ngFor=\"let option of options\" \r\n class=\"fv-radio-label\"\r\n [class.fv-radio-disabled]=\"disabled || option.disabled\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"name\"\r\n [value]=\"option.value\"\r\n [checked]=\"isSelected(option.value)\"\r\n [disabled]=\"disabled || option.disabled\"\r\n (change)=\"onChange(option.value)\"\r\n class=\"fv-radio-input\"\r\n />\r\n <span class=\"fv-radio-custom\"></span>\r\n <span class=\"fv-radio-text\">{{ option.label }}</span>\r\n </label>\r\n </div>\r\n</div>", styles: [".fv-radio-group-container{margin-bottom:16px}.fv-group-label{display:block;margin-bottom:10px;font-size:14px;font-weight:500;color:var(--fv-text-primary, #333333)}.fv-required-asterisk{color:var(--fv-error-color, #dc3545);margin-left:4px;font-weight:700}.fv-radio-options{display:flex;flex-direction:column;gap:12px}.fv-radio-label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;position:relative;padding-left:32px;min-height:24px}.fv-radio-input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.fv-radio-custom{position:absolute;left:0;top:0;height:20px;width:20px;background-color:var(--fv-background-default, #ffffff);border:2px solid var(--fv-border-default, #cccccc);border-radius:50%;transition:all .2s}.fv-radio-label:hover .fv-radio-custom{border-color:var(--fv-border-hover, #999999)}.fv-radio-input:checked~.fv-radio-custom{border-color:var(--fv-border-focus, #667eea)}.fv-radio-custom:after{content:\"\";position:absolute;display:none;top:4px;left:4px;width:8px;height:8px;border-radius:50%;background:var(--fv-border-focus, #667eea)}.fv-radio-input:checked~.fv-radio-custom:after{display:block}.fv-radio-disabled{opacity:.6;cursor:not-allowed}.fv-radio-disabled .fv-radio-custom{background-color:var(--fv-background-disabled, #f5f5f5)}.fv-radio-text{font-size:14px;color:var(--fv-text-primary, #333333)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }] });
498
+ }
499
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvRadioGroupComponent, decorators: [{
500
+ type: Component,
501
+ args: [{ selector: 'fv-radio-group', standalone: true, imports: [CommonModule, ReactiveFormsModule], template: "<div class=\"fv-radio-group-container\">\r\n <label *ngIf=\"label\" class=\"fv-group-label\">\r\n {{ label }}\r\n <span *ngIf=\"required\" class=\"fv-required-asterisk\">*</span>\r\n </label>\r\n \r\n <div class=\"fv-radio-options\">\r\n <label \r\n *ngFor=\"let option of options\" \r\n class=\"fv-radio-label\"\r\n [class.fv-radio-disabled]=\"disabled || option.disabled\">\r\n <input\r\n type=\"radio\"\r\n [name]=\"name\"\r\n [value]=\"option.value\"\r\n [checked]=\"isSelected(option.value)\"\r\n [disabled]=\"disabled || option.disabled\"\r\n (change)=\"onChange(option.value)\"\r\n class=\"fv-radio-input\"\r\n />\r\n <span class=\"fv-radio-custom\"></span>\r\n <span class=\"fv-radio-text\">{{ option.label }}</span>\r\n </label>\r\n </div>\r\n</div>", styles: [".fv-radio-group-container{margin-bottom:16px}.fv-group-label{display:block;margin-bottom:10px;font-size:14px;font-weight:500;color:var(--fv-text-primary, #333333)}.fv-required-asterisk{color:var(--fv-error-color, #dc3545);margin-left:4px;font-weight:700}.fv-radio-options{display:flex;flex-direction:column;gap:12px}.fv-radio-label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;position:relative;padding-left:32px;min-height:24px}.fv-radio-input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.fv-radio-custom{position:absolute;left:0;top:0;height:20px;width:20px;background-color:var(--fv-background-default, #ffffff);border:2px solid var(--fv-border-default, #cccccc);border-radius:50%;transition:all .2s}.fv-radio-label:hover .fv-radio-custom{border-color:var(--fv-border-hover, #999999)}.fv-radio-input:checked~.fv-radio-custom{border-color:var(--fv-border-focus, #667eea)}.fv-radio-custom:after{content:\"\";position:absolute;display:none;top:4px;left:4px;width:8px;height:8px;border-radius:50%;background:var(--fv-border-focus, #667eea)}.fv-radio-input:checked~.fv-radio-custom:after{display:block}.fv-radio-disabled{opacity:.6;cursor:not-allowed}.fv-radio-disabled .fv-radio-custom{background-color:var(--fv-background-disabled, #f5f5f5)}.fv-radio-text{font-size:14px;color:var(--fv-text-primary, #333333)}\n"] }]
502
+ }], propDecorators: { label: [{
503
+ type: Input
504
+ }], control: [{
505
+ type: Input
506
+ }], options: [{
507
+ type: Input
508
+ }], disabled: [{
509
+ type: Input
510
+ }], required: [{
511
+ type: Input
512
+ }], name: [{
513
+ type: Input
514
+ }], valueChange: [{
515
+ type: Output
516
+ }] } });
517
+
518
+ class FvDropdownComponent {
519
+ label = '';
520
+ placeholder = 'Select an option';
521
+ options = [];
522
+ schema;
523
+ control;
524
+ disabled = false;
525
+ valueChange = new EventEmitter();
526
+ blur = new EventEmitter();
527
+ focus = new EventEmitter();
528
+ errorMessage = null;
529
+ isOpen = false;
530
+ subscription;
531
+ ngOnInit() {
532
+ if (!this.control) {
533
+ console.error('FvDropdown: control is required');
534
+ return;
535
+ }
536
+ if (!this.schema) {
537
+ console.warn('FvDropdown: schema is not provided, validation will be skipped');
538
+ return;
539
+ }
540
+ // Subscribe to value changes
541
+ this.subscription = this.control.valueChanges.subscribe((value) => {
542
+ this.validateValue(value);
543
+ this.valueChange.emit(value);
544
+ });
545
+ // Validate initial value
546
+ if (this.control.value) {
547
+ this.validateValue(this.control.value);
548
+ }
549
+ }
550
+ ngOnDestroy() {
551
+ this.subscription?.unsubscribe();
552
+ }
553
+ validateValue(value) {
554
+ if (!this.schema)
555
+ return;
556
+ const result = Validator.validate(value, this.schema);
557
+ this.errorMessage = result.errorKey;
558
+ if (!result.isValid && result.errorKey) {
559
+ this.control.setErrors({ [result.errorKey]: true });
560
+ }
561
+ else {
562
+ this.control.setErrors(null);
563
+ }
564
+ }
565
+ toggleDropdown() {
566
+ if (!this.disabled) {
567
+ this.isOpen = !this.isOpen;
568
+ if (this.isOpen) {
569
+ this.focus.emit();
570
+ }
571
+ }
572
+ }
573
+ selectOption(option) {
574
+ this.control.setValue(option.value);
575
+ this.isOpen = false;
576
+ this.blur.emit();
577
+ }
578
+ onBlur() {
579
+ // Close dropdown after a small delay to allow click events to fire
580
+ setTimeout(() => {
581
+ this.isOpen = false;
582
+ if (this.control && this.schema) {
583
+ this.validateValue(this.control.value);
584
+ }
585
+ this.blur.emit();
586
+ }, 200);
587
+ }
588
+ isRequired() {
589
+ return (this.schema?.rules?.some((r) => r.name === 'required' && r.params?.['enabled']) || false);
590
+ }
591
+ getErrorMessage() {
592
+ if (!this.errorMessage)
593
+ return '';
594
+ const errorMessages = {
595
+ ERR_REQUIRED: 'This field is required',
596
+ ERR_INVALID_VALUE: 'Invalid selection',
597
+ };
598
+ return errorMessages[this.errorMessage] || this.errorMessage;
599
+ }
600
+ getSelectedLabel() {
601
+ const selected = this.options.find((opt) => opt.value === this.control?.value);
602
+ return selected ? selected.label : this.placeholder;
603
+ }
604
+ isSelected(option) {
605
+ return option.value === this.control?.value;
606
+ }
607
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
608
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvDropdownComponent, isStandalone: true, selector: "fv-dropdown", inputs: { label: "label", placeholder: "placeholder", options: "options", schema: "schema", control: "control", disabled: "disabled" }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus" }, ngImport: i0, template: "<div class=\"fv-dropdown-container\">\r\n <label *ngIf=\"label\" class=\"fv-dropdown-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <div class=\"fv-dropdown-wrapper\">\r\n <div class=\"fv-dropdown\" [class.fv-dropdown-error]=\"errorMessage\" [class.fv-dropdown-disabled]=\"disabled\"\r\n [class.fv-dropdown-open]=\"isOpen\" (click)=\"toggleDropdown()\" (blur)=\"onBlur()\" tabindex=\"0\">\r\n <span class=\"fv-dropdown-selected\" [class.fv-dropdown-placeholder]=\"!control.value\">\r\n {{ getSelectedLabel() }}\r\n </span>\r\n <span class=\"fv-dropdown-arrow\" [class.arrow-up]=\"isOpen\">\u25BC</span>\r\n </div>\r\n\r\n <div *ngIf=\"isOpen\" class=\"fv-dropdown-options\">\r\n <div *ngFor=\"let option of options\" class=\"fv-dropdown-option\"\r\n [class.fv-dropdown-option-selected]=\"isSelected(option)\" (click)=\"selectOption(option)\">\r\n {{ option.label }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-dropdown-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-dropdown-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-dropdown-label{font-size:14px;font-weight:500;color:#333;margin-bottom:6px;display:block}.required-asterisk{color:#dc3545;font-weight:700}.fv-dropdown-wrapper{position:relative}.fv-dropdown{padding:10px;border:1px solid #cccccc;border-radius:4px;background-color:#fff;cursor:pointer;display:flex;justify-content:space-between;align-items:center;transition:border-color .2s;outline:none}.fv-dropdown:hover:not(.fv-dropdown-disabled){border-color:#007bff}.fv-dropdown:focus:not(.fv-dropdown-disabled){border-color:#007bff;box-shadow:0 0 0 2px #007bff1a}.fv-dropdown-error{border-color:#dc3545!important}.fv-dropdown-disabled{background-color:#f5f5f5;opacity:.6;cursor:not-allowed}.fv-dropdown-open{border-color:#007bff}.fv-dropdown-selected{font-size:14px;color:#333;flex:1}.fv-dropdown-placeholder{color:#999}.fv-dropdown-arrow{font-size:10px;color:#666;margin-left:8px;transition:transform .2s}.arrow-up{transform:rotate(180deg)}.fv-dropdown-options{position:absolute;top:100%;left:0;right:0;margin-top:4px;background-color:#fff;border:1px solid #cccccc;border-radius:4px;max-height:300px;overflow-y:auto;z-index:1000;box-shadow:0 4px 6px #0000001a}.fv-dropdown-option{padding:12px 16px;font-size:14px;color:#333;cursor:pointer;transition:background-color .2s;border-bottom:1px solid #eeeeee}.fv-dropdown-option:last-child{border-bottom:none}.fv-dropdown-option:hover{background-color:#f8f9fa}.fv-dropdown-option-selected{background-color:#e7f3ff;color:#007bff;font-weight:600}.fv-dropdown-error-message{margin-top:4px;font-size:12px;color:#dc3545}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }] });
609
+ }
610
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvDropdownComponent, decorators: [{
611
+ type: Component,
612
+ args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-dropdown', template: "<div class=\"fv-dropdown-container\">\r\n <label *ngIf=\"label\" class=\"fv-dropdown-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <div class=\"fv-dropdown-wrapper\">\r\n <div class=\"fv-dropdown\" [class.fv-dropdown-error]=\"errorMessage\" [class.fv-dropdown-disabled]=\"disabled\"\r\n [class.fv-dropdown-open]=\"isOpen\" (click)=\"toggleDropdown()\" (blur)=\"onBlur()\" tabindex=\"0\">\r\n <span class=\"fv-dropdown-selected\" [class.fv-dropdown-placeholder]=\"!control.value\">\r\n {{ getSelectedLabel() }}\r\n </span>\r\n <span class=\"fv-dropdown-arrow\" [class.arrow-up]=\"isOpen\">\u25BC</span>\r\n </div>\r\n\r\n <div *ngIf=\"isOpen\" class=\"fv-dropdown-options\">\r\n <div *ngFor=\"let option of options\" class=\"fv-dropdown-option\"\r\n [class.fv-dropdown-option-selected]=\"isSelected(option)\" (click)=\"selectOption(option)\">\r\n {{ option.label }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-dropdown-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-dropdown-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-dropdown-label{font-size:14px;font-weight:500;color:#333;margin-bottom:6px;display:block}.required-asterisk{color:#dc3545;font-weight:700}.fv-dropdown-wrapper{position:relative}.fv-dropdown{padding:10px;border:1px solid #cccccc;border-radius:4px;background-color:#fff;cursor:pointer;display:flex;justify-content:space-between;align-items:center;transition:border-color .2s;outline:none}.fv-dropdown:hover:not(.fv-dropdown-disabled){border-color:#007bff}.fv-dropdown:focus:not(.fv-dropdown-disabled){border-color:#007bff;box-shadow:0 0 0 2px #007bff1a}.fv-dropdown-error{border-color:#dc3545!important}.fv-dropdown-disabled{background-color:#f5f5f5;opacity:.6;cursor:not-allowed}.fv-dropdown-open{border-color:#007bff}.fv-dropdown-selected{font-size:14px;color:#333;flex:1}.fv-dropdown-placeholder{color:#999}.fv-dropdown-arrow{font-size:10px;color:#666;margin-left:8px;transition:transform .2s}.arrow-up{transform:rotate(180deg)}.fv-dropdown-options{position:absolute;top:100%;left:0;right:0;margin-top:4px;background-color:#fff;border:1px solid #cccccc;border-radius:4px;max-height:300px;overflow-y:auto;z-index:1000;box-shadow:0 4px 6px #0000001a}.fv-dropdown-option{padding:12px 16px;font-size:14px;color:#333;cursor:pointer;transition:background-color .2s;border-bottom:1px solid #eeeeee}.fv-dropdown-option:last-child{border-bottom:none}.fv-dropdown-option:hover{background-color:#f8f9fa}.fv-dropdown-option-selected{background-color:#e7f3ff;color:#007bff;font-weight:600}.fv-dropdown-error-message{margin-top:4px;font-size:12px;color:#dc3545}\n"] }]
613
+ }], propDecorators: { label: [{
614
+ type: Input
615
+ }], placeholder: [{
616
+ type: Input
617
+ }], options: [{
618
+ type: Input
619
+ }], schema: [{
620
+ type: Input
621
+ }], control: [{
622
+ type: Input
623
+ }], disabled: [{
624
+ type: Input
625
+ }], valueChange: [{
626
+ type: Output
627
+ }], blur: [{
628
+ type: Output
629
+ }], focus: [{
630
+ type: Output
631
+ }] } });
632
+
633
+ class FvFileSelectorComponent {
634
+ label = '';
635
+ placeholder = 'Select a file';
636
+ schema;
637
+ control;
638
+ disabled = false;
639
+ accept = '*/*'; // MIME types, e.g., 'application/pdf,image/*'
640
+ maxSize; // Maximum file size in bytes
641
+ valueChange = new EventEmitter();
642
+ blur = new EventEmitter();
643
+ fileInput;
644
+ errorMessage = null;
645
+ selectedFile = null;
646
+ subscription;
647
+ ngOnInit() {
648
+ if (!this.control) {
649
+ console.error('FvFileSelector: control is required');
650
+ return;
651
+ }
652
+ if (!this.schema) {
653
+ console.warn('FvFileSelector: schema is not provided, validation will be skipped');
654
+ return;
655
+ }
656
+ // Subscribe to value changes
657
+ this.subscription = this.control.valueChanges.subscribe((value) => {
658
+ this.validateValue(value);
659
+ this.valueChange.emit(value);
660
+ });
661
+ // Validate initial value
662
+ if (this.control.value) {
663
+ this.selectedFile = this.control.value;
664
+ this.validateValue(this.control.value);
665
+ }
666
+ }
667
+ ngOnDestroy() {
668
+ this.subscription?.unsubscribe();
669
+ }
670
+ validateValue(value) {
671
+ if (!this.schema)
672
+ return;
673
+ const result = Validator.validate(value, this.schema);
674
+ this.errorMessage = result.errorKey;
675
+ if (!result.isValid && result.errorKey) {
676
+ this.control.setErrors({ [result.errorKey]: true });
677
+ }
678
+ else {
679
+ this.control.setErrors(null);
680
+ }
681
+ }
682
+ onFileSelected(event) {
683
+ const input = event.target;
684
+ if (input.files && input.files.length > 0) {
685
+ const file = input.files[0];
686
+ // Check file size if maxSize is specified
687
+ if (this.maxSize && file.size > this.maxSize) {
688
+ alert(`File size exceeds the maximum allowed size of ${this.formatFileSize(this.maxSize)}`);
689
+ // Reset the input
690
+ input.value = '';
691
+ return;
692
+ }
693
+ const fileInfo = {
694
+ file: file,
695
+ name: file.name,
696
+ size: file.size,
697
+ type: file.type,
698
+ };
699
+ this.selectedFile = fileInfo;
700
+ this.control.setValue(fileInfo);
701
+ this.blur.emit();
702
+ }
703
+ }
704
+ openFileDialog() {
705
+ if (!this.disabled) {
706
+ this.fileInput.nativeElement.click();
707
+ }
708
+ }
709
+ removeFile() {
710
+ this.selectedFile = null;
711
+ this.control.setValue(null);
712
+ if (this.fileInput) {
713
+ this.fileInput.nativeElement.value = '';
714
+ }
715
+ this.blur.emit();
716
+ }
717
+ formatFileSize(bytes) {
718
+ if (bytes === 0)
719
+ return '0 Bytes';
720
+ const k = 1024;
721
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
722
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
723
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
724
+ }
725
+ isRequired() {
726
+ return (this.schema?.rules?.some((r) => r.name === 'required' && r.params?.['enabled']) || false);
727
+ }
728
+ getErrorMessage() {
729
+ if (!this.errorMessage)
730
+ return '';
731
+ const errorMessages = {
732
+ ERR_REQUIRED: 'This field is required',
733
+ ERR_INVALID_FILE: 'Invalid file',
734
+ };
735
+ return errorMessages[this.errorMessage] || this.errorMessage;
736
+ }
737
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvFileSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
738
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvFileSelectorComponent, isStandalone: true, selector: "fv-file-selector", inputs: { label: "label", placeholder: "placeholder", schema: "schema", control: "control", disabled: "disabled", accept: "accept", maxSize: "maxSize" }, outputs: { valueChange: "valueChange", blur: "blur" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div class=\"fv-file-selector-container\">\r\n <label *ngIf=\"label\" class=\"fv-file-selector-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept\" (change)=\"onFileSelected($event)\" style=\"display: none\" />\r\n\r\n <button type=\"button\" class=\"fv-file-selector-button\" [class.fv-file-selector-button-error]=\"errorMessage\"\r\n [class.fv-file-selector-button-disabled]=\"disabled\" (click)=\"openFileDialog()\" [disabled]=\"disabled\">\r\n {{ placeholder }}\r\n </button>\r\n\r\n <div *ngIf=\"selectedFile\" class=\"fv-file-info\">\r\n <div class=\"fv-file-details\">\r\n <div class=\"fv-file-name\">\uD83D\uDCC4 {{ selectedFile.name }}</div>\r\n <div class=\"fv-file-size\">{{ formatFileSize(selectedFile.size) }}</div>\r\n </div>\r\n <button type=\"button\" class=\"fv-file-remove\" (click)=\"removeFile()\" [disabled]=\"disabled\">\r\n \u2715\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-file-selector-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-file-selector-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-file-selector-label{font-size:14px;font-weight:500;color:#333;margin-bottom:6px;display:block}.required-asterisk{color:#dc3545;font-weight:700}.fv-file-selector-button{padding:10px;border:1px solid #007bff;border-radius:4px;background-color:#007bff;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,border-color .2s;text-align:center}.fv-file-selector-button:hover:not(:disabled){background-color:#0056b3;border-color:#0056b3}.fv-file-selector-button-error{border-color:#dc3545;background-color:#dc3545}.fv-file-selector-button-error:hover:not(:disabled){background-color:#c82333;border-color:#c82333}.fv-file-selector-button-disabled{background-color:#ccc;border-color:#ccc;opacity:.6;cursor:not-allowed}.fv-file-info{display:flex;align-items:center;margin-top:8px;padding:10px;background-color:#f8f9fa;border:1px solid #dee2e6;border-radius:4px}.fv-file-details{flex:1}.fv-file-name{font-size:14px;color:#333;margin-bottom:4px;word-break:break-all}.fv-file-size{font-size:12px;color:#6c757d}.fv-file-remove{padding:4px 8px;margin-left:8px;background:none;border:none;font-size:18px;color:#dc3545;font-weight:700;cursor:pointer;transition:color .2s}.fv-file-remove:hover:not(:disabled){color:#c82333}.fv-file-remove:disabled{opacity:.5;cursor:not-allowed}.fv-file-selector-error-message{margin-top:4px;font-size:12px;color:#dc3545}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }] });
739
+ }
740
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvFileSelectorComponent, decorators: [{
741
+ type: Component,
742
+ args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-file-selector', template: "<div class=\"fv-file-selector-container\">\r\n <label *ngIf=\"label\" class=\"fv-file-selector-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #fileInput type=\"file\" [accept]=\"accept\" (change)=\"onFileSelected($event)\" style=\"display: none\" />\r\n\r\n <button type=\"button\" class=\"fv-file-selector-button\" [class.fv-file-selector-button-error]=\"errorMessage\"\r\n [class.fv-file-selector-button-disabled]=\"disabled\" (click)=\"openFileDialog()\" [disabled]=\"disabled\">\r\n {{ placeholder }}\r\n </button>\r\n\r\n <div *ngIf=\"selectedFile\" class=\"fv-file-info\">\r\n <div class=\"fv-file-details\">\r\n <div class=\"fv-file-name\">\uD83D\uDCC4 {{ selectedFile.name }}</div>\r\n <div class=\"fv-file-size\">{{ formatFileSize(selectedFile.size) }}</div>\r\n </div>\r\n <button type=\"button\" class=\"fv-file-remove\" (click)=\"removeFile()\" [disabled]=\"disabled\">\r\n \u2715\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-file-selector-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-file-selector-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-file-selector-label{font-size:14px;font-weight:500;color:#333;margin-bottom:6px;display:block}.required-asterisk{color:#dc3545;font-weight:700}.fv-file-selector-button{padding:10px;border:1px solid #007bff;border-radius:4px;background-color:#007bff;color:#fff;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,border-color .2s;text-align:center}.fv-file-selector-button:hover:not(:disabled){background-color:#0056b3;border-color:#0056b3}.fv-file-selector-button-error{border-color:#dc3545;background-color:#dc3545}.fv-file-selector-button-error:hover:not(:disabled){background-color:#c82333;border-color:#c82333}.fv-file-selector-button-disabled{background-color:#ccc;border-color:#ccc;opacity:.6;cursor:not-allowed}.fv-file-info{display:flex;align-items:center;margin-top:8px;padding:10px;background-color:#f8f9fa;border:1px solid #dee2e6;border-radius:4px}.fv-file-details{flex:1}.fv-file-name{font-size:14px;color:#333;margin-bottom:4px;word-break:break-all}.fv-file-size{font-size:12px;color:#6c757d}.fv-file-remove{padding:4px 8px;margin-left:8px;background:none;border:none;font-size:18px;color:#dc3545;font-weight:700;cursor:pointer;transition:color .2s}.fv-file-remove:hover:not(:disabled){color:#c82333}.fv-file-remove:disabled{opacity:.5;cursor:not-allowed}.fv-file-selector-error-message{margin-top:4px;font-size:12px;color:#dc3545}\n"] }]
743
+ }], propDecorators: { label: [{
744
+ type: Input
745
+ }], placeholder: [{
746
+ type: Input
747
+ }], schema: [{
748
+ type: Input
749
+ }], control: [{
750
+ type: Input
751
+ }], disabled: [{
752
+ type: Input
753
+ }], accept: [{
754
+ type: Input
755
+ }], maxSize: [{
756
+ type: Input
757
+ }], valueChange: [{
758
+ type: Output
759
+ }], blur: [{
760
+ type: Output
761
+ }], fileInput: [{
762
+ type: ViewChild,
763
+ args: ['fileInput']
764
+ }] } });
765
+
766
+ class FvImageSelectorComponent {
767
+ sanitizer;
768
+ label = '';
769
+ placeholder = 'Select an image';
770
+ schema;
771
+ control;
772
+ disabled = false;
773
+ maxSize; // Maximum file size in bytes
774
+ valueChange = new EventEmitter();
775
+ blur = new EventEmitter();
776
+ imageInput;
777
+ errorMessage = null;
778
+ selectedImage = null;
779
+ subscription;
780
+ constructor(sanitizer) {
781
+ this.sanitizer = sanitizer;
782
+ }
783
+ ngOnInit() {
784
+ if (!this.control) {
785
+ console.error('FvImageSelector: control is required');
786
+ return;
787
+ }
788
+ if (!this.schema) {
789
+ console.warn('FvImageSelector: schema is not provided, validation will be skipped');
790
+ return;
791
+ }
792
+ // Subscribe to value changes
793
+ this.subscription = this.control.valueChanges.subscribe((value) => {
794
+ this.validateValue(value);
795
+ this.valueChange.emit(value);
796
+ });
797
+ // Validate initial value
798
+ if (this.control.value) {
799
+ this.selectedImage = this.control.value;
800
+ this.validateValue(this.control.value);
801
+ }
802
+ }
803
+ ngOnDestroy() {
804
+ this.subscription?.unsubscribe();
805
+ }
806
+ validateValue(value) {
807
+ if (!this.schema)
808
+ return;
809
+ const result = Validator.validate(value, this.schema);
810
+ this.errorMessage = result.errorKey;
811
+ if (!result.isValid && result.errorKey) {
812
+ this.control.setErrors({ [result.errorKey]: true });
813
+ }
814
+ else {
815
+ this.control.setErrors(null);
816
+ }
817
+ }
818
+ onImageSelected(event) {
819
+ const input = event.target;
820
+ if (input.files && input.files.length > 0) {
821
+ const file = input.files[0];
822
+ // Validate that it's an image
823
+ if (!file.type.startsWith('image/')) {
824
+ alert('Please select an image file');
825
+ input.value = '';
826
+ return;
827
+ }
828
+ // Check file size if maxSize is specified
829
+ if (this.maxSize && file.size > this.maxSize) {
830
+ alert(`Image size exceeds the maximum allowed size of ${this.formatFileSize(this.maxSize)}`);
831
+ input.value = '';
832
+ return;
833
+ }
834
+ // Create object URL and get image dimensions
835
+ const reader = new FileReader();
836
+ reader.onload = (e) => {
837
+ const img = new Image();
838
+ img.onload = () => {
839
+ const imageInfo = {
840
+ file: file,
841
+ url: this.sanitizer.bypassSecurityTrustUrl(e.target.result),
842
+ width: img.width,
843
+ height: img.height,
844
+ size: file.size,
845
+ };
846
+ this.selectedImage = imageInfo;
847
+ this.control.setValue(imageInfo);
848
+ this.blur.emit();
849
+ };
850
+ img.src = e.target.result;
851
+ };
852
+ reader.readAsDataURL(file);
853
+ }
854
+ }
855
+ openImageDialog() {
856
+ if (!this.disabled) {
857
+ this.imageInput.nativeElement.click();
858
+ }
859
+ }
860
+ removeImage() {
861
+ this.selectedImage = null;
862
+ this.control.setValue(null);
863
+ if (this.imageInput) {
864
+ this.imageInput.nativeElement.value = '';
865
+ }
866
+ this.blur.emit();
867
+ }
868
+ formatFileSize(bytes) {
869
+ if (bytes === 0)
870
+ return '0 Bytes';
871
+ const k = 1024;
872
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
873
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
874
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
875
+ }
876
+ isRequired() {
877
+ return (this.schema?.rules?.some((r) => r.name === 'required' && r.params?.['enabled']) || false);
878
+ }
879
+ getErrorMessage() {
880
+ if (!this.errorMessage)
881
+ return '';
882
+ const errorMessages = {
883
+ ERR_REQUIRED: 'This field is required',
884
+ ERR_INVALID_IMAGE: 'Invalid image',
885
+ };
886
+ return errorMessages[this.errorMessage] || this.errorMessage;
887
+ }
888
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvImageSelectorComponent, deps: [{ token: i1$1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
889
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvImageSelectorComponent, isStandalone: true, selector: "fv-image-selector", inputs: { label: "label", placeholder: "placeholder", schema: "schema", control: "control", disabled: "disabled", maxSize: "maxSize" }, outputs: { valueChange: "valueChange", blur: "blur" }, viewQueries: [{ propertyName: "imageInput", first: true, predicate: ["imageInput"], descendants: true }], ngImport: i0, template: "<div class=\"fv-image-selector-container\">\r\n <label *ngIf=\"label\" class=\"fv-image-selector-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #imageInput type=\"file\" accept=\"image/*\" (change)=\"onImageSelected($event)\" style=\"display: none\" />\r\n\r\n <div *ngIf=\"!selectedImage; else previewTemplate\">\r\n <button type=\"button\" class=\"fv-image-selector-button\" [class.fv-image-selector-button-error]=\"errorMessage\"\r\n [class.fv-image-selector-button-disabled]=\"disabled\" (click)=\"openImageDialog()\" [disabled]=\"disabled\">\r\n \uD83D\uDCF7 {{ placeholder }}\r\n </button>\r\n </div>\r\n\r\n <ng-template #previewTemplate>\r\n <div class=\"fv-image-preview-container\">\r\n <img [src]=\"selectedImage!.url\" class=\"fv-image-preview\" alt=\"Selected image\" />\r\n <div class=\"fv-image-actions\">\r\n <button type=\"button\" class=\"fv-image-change-button\" (click)=\"openImageDialog()\" [disabled]=\"disabled\">\r\n Change\r\n </button>\r\n <button type=\"button\" class=\"fv-image-remove-button\" (click)=\"removeImage()\" [disabled]=\"disabled\">\r\n Remove\r\n </button>\r\n </div>\r\n <div class=\"fv-image-info\">\r\n {{ selectedImage!.width }} \u00D7 {{ selectedImage!.height }} \u2022\r\n {{ formatFileSize(selectedImage!.size) }}\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-image-selector-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-image-selector-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-image-selector-label{font-size:14px;font-weight:500;color:#333;margin-bottom:6px;display:block}.required-asterisk{color:#dc3545;font-weight:700}.fv-image-selector-button{padding:40px;border:2px dashed #007bff;border-radius:8px;background-color:#f8f9fa;color:#007bff;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,border-color .2s;width:100%;text-align:center}.fv-image-selector-button:hover:not(:disabled){background-color:#e7f3ff}.fv-image-selector-button-error{border-color:#dc3545;color:#dc3545}.fv-image-selector-button-disabled{border-color:#ccc;background-color:#f5f5f5;color:#ccc;opacity:.6;cursor:not-allowed}.fv-image-preview-container{display:flex;flex-direction:column;align-items:center}.fv-image-preview{width:100%;max-width:600px;height:200px;object-fit:cover;border-radius:8px;background-color:#f0f0f0;margin-bottom:8px}.fv-image-actions{display:flex;gap:8px;margin-bottom:4px}.fv-image-change-button{padding:8px 16px;background-color:#007bff;color:#fff;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.fv-image-change-button:hover:not(:disabled){background-color:#0056b3}.fv-image-change-button:disabled{opacity:.6;cursor:not-allowed}.fv-image-remove-button{padding:8px 16px;background-color:#dc3545;color:#fff;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.fv-image-remove-button:hover:not(:disabled){background-color:#c82333}.fv-image-remove-button:disabled{opacity:.6;cursor:not-allowed}.fv-image-info{font-size:12px;color:#6c757d;text-align:center}.fv-image-selector-error-message{margin-top:4px;font-size:12px;color:#dc3545}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }] });
890
+ }
891
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvImageSelectorComponent, decorators: [{
892
+ type: Component,
893
+ args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-image-selector', template: "<div class=\"fv-image-selector-container\">\r\n <label *ngIf=\"label\" class=\"fv-image-selector-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <input #imageInput type=\"file\" accept=\"image/*\" (change)=\"onImageSelected($event)\" style=\"display: none\" />\r\n\r\n <div *ngIf=\"!selectedImage; else previewTemplate\">\r\n <button type=\"button\" class=\"fv-image-selector-button\" [class.fv-image-selector-button-error]=\"errorMessage\"\r\n [class.fv-image-selector-button-disabled]=\"disabled\" (click)=\"openImageDialog()\" [disabled]=\"disabled\">\r\n \uD83D\uDCF7 {{ placeholder }}\r\n </button>\r\n </div>\r\n\r\n <ng-template #previewTemplate>\r\n <div class=\"fv-image-preview-container\">\r\n <img [src]=\"selectedImage!.url\" class=\"fv-image-preview\" alt=\"Selected image\" />\r\n <div class=\"fv-image-actions\">\r\n <button type=\"button\" class=\"fv-image-change-button\" (click)=\"openImageDialog()\" [disabled]=\"disabled\">\r\n Change\r\n </button>\r\n <button type=\"button\" class=\"fv-image-remove-button\" (click)=\"removeImage()\" [disabled]=\"disabled\">\r\n Remove\r\n </button>\r\n </div>\r\n <div class=\"fv-image-info\">\r\n {{ selectedImage!.width }} \u00D7 {{ selectedImage!.height }} \u2022\r\n {{ formatFileSize(selectedImage!.size) }}\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-image-selector-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-image-selector-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-image-selector-label{font-size:14px;font-weight:500;color:#333;margin-bottom:6px;display:block}.required-asterisk{color:#dc3545;font-weight:700}.fv-image-selector-button{padding:40px;border:2px dashed #007bff;border-radius:8px;background-color:#f8f9fa;color:#007bff;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,border-color .2s;width:100%;text-align:center}.fv-image-selector-button:hover:not(:disabled){background-color:#e7f3ff}.fv-image-selector-button-error{border-color:#dc3545;color:#dc3545}.fv-image-selector-button-disabled{border-color:#ccc;background-color:#f5f5f5;color:#ccc;opacity:.6;cursor:not-allowed}.fv-image-preview-container{display:flex;flex-direction:column;align-items:center}.fv-image-preview{width:100%;max-width:600px;height:200px;object-fit:cover;border-radius:8px;background-color:#f0f0f0;margin-bottom:8px}.fv-image-actions{display:flex;gap:8px;margin-bottom:4px}.fv-image-change-button{padding:8px 16px;background-color:#007bff;color:#fff;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.fv-image-change-button:hover:not(:disabled){background-color:#0056b3}.fv-image-change-button:disabled{opacity:.6;cursor:not-allowed}.fv-image-remove-button{padding:8px 16px;background-color:#dc3545;color:#fff;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.fv-image-remove-button:hover:not(:disabled){background-color:#c82333}.fv-image-remove-button:disabled{opacity:.6;cursor:not-allowed}.fv-image-info{font-size:12px;color:#6c757d;text-align:center}.fv-image-selector-error-message{margin-top:4px;font-size:12px;color:#dc3545}\n"] }]
894
+ }], ctorParameters: () => [{ type: i1$1.DomSanitizer }], propDecorators: { label: [{
895
+ type: Input
896
+ }], placeholder: [{
897
+ type: Input
898
+ }], schema: [{
899
+ type: Input
900
+ }], control: [{
901
+ type: Input
902
+ }], disabled: [{
903
+ type: Input
904
+ }], maxSize: [{
905
+ type: Input
906
+ }], valueChange: [{
907
+ type: Output
908
+ }], blur: [{
909
+ type: Output
910
+ }], imageInput: [{
911
+ type: ViewChild,
912
+ args: ['imageInput']
913
+ }] } });
914
+
915
+ class FvRichTextEditorComponent {
916
+ label = '';
917
+ placeholder = 'Enter text...';
918
+ schema;
919
+ control;
920
+ disabled = false;
921
+ readonly = false;
922
+ minHeight = 150;
923
+ showToolbar = true;
924
+ valueChange = new EventEmitter();
925
+ blur = new EventEmitter();
926
+ focus = new EventEmitter();
927
+ editorRef;
928
+ errorMessage = null;
929
+ isFocused = false;
930
+ subscription;
931
+ ngOnInit() {
932
+ if (!this.control) {
933
+ console.error('FvRichTextEditor: control is required');
934
+ return;
935
+ }
936
+ if (!this.schema) {
937
+ console.warn('FvRichTextEditor: schema is not provided, validation will be skipped');
938
+ return;
939
+ }
940
+ // Subscribe to value changes
941
+ this.subscription = this.control.valueChanges.subscribe((value) => {
942
+ this.validateValue(value);
943
+ this.valueChange.emit(value);
944
+ });
945
+ // Validate initial value
946
+ if (this.control.value) {
947
+ this.validateValue(this.control.value);
948
+ }
949
+ }
950
+ ngOnDestroy() {
951
+ this.subscription?.unsubscribe();
952
+ }
953
+ validateValue(value) {
954
+ if (!this.schema)
955
+ return;
956
+ const result = Validator$1.validate(value, this.schema);
957
+ this.errorMessage = result.errorKey;
958
+ if (!result.isValid && result.errorKey) {
959
+ this.control.setErrors({ [result.errorKey]: true });
960
+ }
961
+ else {
962
+ this.control.setErrors(null);
963
+ }
964
+ }
965
+ onBlur() {
966
+ this.isFocused = false;
967
+ if (this.control && this.schema) {
968
+ this.validateValue(this.control.value);
969
+ }
970
+ this.blur.emit();
971
+ }
972
+ onFocus() {
973
+ this.isFocused = true;
974
+ this.focus.emit();
975
+ }
976
+ insertText(before, after = before) {
977
+ if (this.disabled || this.readonly)
978
+ return;
979
+ const textarea = this.editorRef.nativeElement;
980
+ const start = textarea.selectionStart;
981
+ const end = textarea.selectionEnd;
982
+ const text = this.control.value || '';
983
+ const selectedText = text.substring(start, end);
984
+ const newText = text.substring(0, start) +
985
+ before +
986
+ selectedText +
987
+ after +
988
+ text.substring(end);
989
+ this.control.setValue(newText);
990
+ // Restore cursor position
991
+ setTimeout(() => {
992
+ const newPosition = start + before.length + selectedText.length;
993
+ textarea.setSelectionRange(newPosition, newPosition);
994
+ textarea.focus();
995
+ });
996
+ }
997
+ handleBulletList() {
998
+ if (this.disabled || this.readonly)
999
+ return;
1000
+ const text = this.control.value || '';
1001
+ const lines = text.split('\n');
1002
+ const bulletedLines = lines.map((line) => line.trim() ? `• ${line}` : line);
1003
+ this.control.setValue(bulletedLines.join('\n'));
1004
+ }
1005
+ handleNumberedList() {
1006
+ if (this.disabled || this.readonly)
1007
+ return;
1008
+ const text = this.control.value || '';
1009
+ const lines = text.split('\n').filter((line) => line.trim());
1010
+ const numberedLines = lines.map((line, index) => `${index + 1}. ${line}`);
1011
+ this.control.setValue(numberedLines.join('\n'));
1012
+ }
1013
+ handleClear() {
1014
+ if (this.disabled || this.readonly)
1015
+ return;
1016
+ this.control.setValue('');
1017
+ }
1018
+ isRequired() {
1019
+ return (this.schema?.rules?.some((r) => r.name === 'required' && r.params?.['enabled']) || false);
1020
+ }
1021
+ getErrorMessage() {
1022
+ if (!this.errorMessage)
1023
+ return '';
1024
+ const errorMessages = {
1025
+ ERR_REQUIRED: 'This field is required',
1026
+ ERR_MIN_LENGTH: 'Text is too short',
1027
+ ERR_MAX_LENGTH: 'Text is too long',
1028
+ };
1029
+ return errorMessages[this.errorMessage] || this.errorMessage;
1030
+ }
1031
+ getCharacterCount() {
1032
+ return this.control?.value?.length || 0;
1033
+ }
1034
+ getWordCount() {
1035
+ const text = this.control?.value || '';
1036
+ return text.trim() ? text.trim().split(/\s+/).length : 0;
1037
+ }
1038
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvRichTextEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1039
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FvRichTextEditorComponent, isStandalone: true, selector: "fv-rich-text-editor", inputs: { label: "label", placeholder: "placeholder", schema: "schema", control: "control", disabled: "disabled", readonly: "readonly", minHeight: "minHeight", showToolbar: "showToolbar" }, outputs: { valueChange: "valueChange", blur: "blur", focus: "focus" }, viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }], ngImport: i0, template: "<div class=\"fv-rich-text-editor-container\">\r\n <label *ngIf=\"label\" class=\"fv-rich-text-editor-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <div *ngIf=\"showToolbar && !disabled && !readonly\" class=\"fv-rich-text-toolbar\">\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"insertText('**', '**')\" title=\"Bold\">\r\n <strong>B</strong>\r\n </button>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"insertText('*', '*')\" title=\"Italic\">\r\n <em>I</em>\r\n </button>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"insertText('__', '__')\" title=\"Underline\">\r\n <u>U</u>\r\n </button>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"insertText('~~', '~~')\" title=\"Strikethrough\">\r\n <s>S</s>\r\n </button>\r\n\r\n <div class=\"fv-toolbar-divider\"></div>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"handleBulletList()\" title=\"Bullet List\">\r\n \u2022 List\r\n </button>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"handleNumberedList()\" title=\"Numbered List\">\r\n 1. List\r\n </button>\r\n\r\n <div class=\"fv-toolbar-divider\"></div>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button fv-toolbar-button-danger\" (click)=\"handleClear()\" title=\"Clear\">\r\n Clear\r\n </button>\r\n </div>\r\n\r\n <textarea #editor class=\"fv-rich-text-editor\" [class.fv-rich-text-editor-error]=\"errorMessage\"\r\n [class.fv-rich-text-editor-disabled]=\"disabled\" [class.fv-rich-text-editor-focused]=\"isFocused\"\r\n [formControl]=\"control\" [placeholder]=\"placeholder\" [readonly]=\"readonly\" [disabled]=\"disabled\"\r\n [style.min-height.px]=\"minHeight\" (blur)=\"onBlur()\" (focus)=\"onFocus()\"></textarea>\r\n\r\n <div class=\"fv-rich-text-footer\">\r\n <div class=\"fv-rich-text-stats\">\r\n {{ getCharacterCount() }} characters \u2022 {{ getWordCount() }} words\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-rich-text-editor-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-rich-text-editor-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-rich-text-editor-label{font-size:14px;font-weight:500;color:#333;margin-bottom:6px;display:block}.required-asterisk{color:#dc3545;font-weight:700}.fv-rich-text-toolbar{display:flex;align-items:center;gap:4px;padding:4px;margin-bottom:8px;border:1px solid #dee2e6;border-radius:4px;background-color:#f8f9fa;flex-wrap:wrap}.fv-toolbar-button{padding:6px 10px;background-color:#fff;border:1px solid #dee2e6;border-radius:4px;font-size:14px;font-weight:500;color:#333;cursor:pointer;transition:background-color .2s,border-color .2s}.fv-toolbar-button:hover{background-color:#e7f3ff;border-color:#007bff}.fv-toolbar-button-danger{color:#dc3545}.fv-toolbar-button-danger:hover{background-color:#ffe0e0;border-color:#dc3545}.fv-toolbar-divider{width:1px;height:24px;background-color:#dee2e6;margin:0 4px}.fv-rich-text-editor{padding:10px;border:1px solid #cccccc;border-radius:4px;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background-color:#fff;color:#333;resize:vertical;transition:border-color .2s,box-shadow .2s;outline:none}.fv-rich-text-editor::placeholder{color:#999}.fv-rich-text-editor:focus:not(:disabled):not([readonly]){border-color:#007bff;box-shadow:0 0 0 2px #007bff1a}.fv-rich-text-editor-error{border-color:#dc3545}.fv-rich-text-editor-disabled,.fv-rich-text-editor[readonly]{background-color:#f5f5f5;opacity:.6;cursor:not-allowed}.fv-rich-text-editor-focused{border-color:#007bff;border-width:2px;padding:9px}.fv-rich-text-footer{display:flex;justify-content:flex-end;margin-top:4px}.fv-rich-text-stats{font-size:12px;color:#6c757d}.fv-rich-text-editor-error-message{margin-top:4px;font-size:12px;color:#dc3545}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
1040
+ }
1041
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvRichTextEditorComponent, decorators: [{
1042
+ type: Component,
1043
+ args: [{ standalone: true, imports: [CommonModule, ReactiveFormsModule], selector: 'fv-rich-text-editor', template: "<div class=\"fv-rich-text-editor-container\">\r\n <label *ngIf=\"label\" class=\"fv-rich-text-editor-label\">\r\n {{ label }}\r\n <span *ngIf=\"isRequired()\" class=\"required-asterisk\">*</span>\r\n </label>\r\n\r\n <div *ngIf=\"showToolbar && !disabled && !readonly\" class=\"fv-rich-text-toolbar\">\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"insertText('**', '**')\" title=\"Bold\">\r\n <strong>B</strong>\r\n </button>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"insertText('*', '*')\" title=\"Italic\">\r\n <em>I</em>\r\n </button>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"insertText('__', '__')\" title=\"Underline\">\r\n <u>U</u>\r\n </button>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"insertText('~~', '~~')\" title=\"Strikethrough\">\r\n <s>S</s>\r\n </button>\r\n\r\n <div class=\"fv-toolbar-divider\"></div>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"handleBulletList()\" title=\"Bullet List\">\r\n \u2022 List\r\n </button>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button\" (click)=\"handleNumberedList()\" title=\"Numbered List\">\r\n 1. List\r\n </button>\r\n\r\n <div class=\"fv-toolbar-divider\"></div>\r\n\r\n <button type=\"button\" class=\"fv-toolbar-button fv-toolbar-button-danger\" (click)=\"handleClear()\" title=\"Clear\">\r\n Clear\r\n </button>\r\n </div>\r\n\r\n <textarea #editor class=\"fv-rich-text-editor\" [class.fv-rich-text-editor-error]=\"errorMessage\"\r\n [class.fv-rich-text-editor-disabled]=\"disabled\" [class.fv-rich-text-editor-focused]=\"isFocused\"\r\n [formControl]=\"control\" [placeholder]=\"placeholder\" [readonly]=\"readonly\" [disabled]=\"disabled\"\r\n [style.min-height.px]=\"minHeight\" (blur)=\"onBlur()\" (focus)=\"onFocus()\"></textarea>\r\n\r\n <div class=\"fv-rich-text-footer\">\r\n <div class=\"fv-rich-text-stats\">\r\n {{ getCharacterCount() }} characters \u2022 {{ getWordCount() }} words\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage\" class=\"fv-rich-text-editor-error-message\">\r\n \u26A0 {{ getErrorMessage() }}\r\n </div>\r\n</div>", styles: [".fv-rich-text-editor-container{display:flex;flex-direction:column;margin-bottom:16px;width:100%}.fv-rich-text-editor-label{font-size:14px;font-weight:500;color:#333;margin-bottom:6px;display:block}.required-asterisk{color:#dc3545;font-weight:700}.fv-rich-text-toolbar{display:flex;align-items:center;gap:4px;padding:4px;margin-bottom:8px;border:1px solid #dee2e6;border-radius:4px;background-color:#f8f9fa;flex-wrap:wrap}.fv-toolbar-button{padding:6px 10px;background-color:#fff;border:1px solid #dee2e6;border-radius:4px;font-size:14px;font-weight:500;color:#333;cursor:pointer;transition:background-color .2s,border-color .2s}.fv-toolbar-button:hover{background-color:#e7f3ff;border-color:#007bff}.fv-toolbar-button-danger{color:#dc3545}.fv-toolbar-button-danger:hover{background-color:#ffe0e0;border-color:#dc3545}.fv-toolbar-divider{width:1px;height:24px;background-color:#dee2e6;margin:0 4px}.fv-rich-text-editor{padding:10px;border:1px solid #cccccc;border-radius:4px;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background-color:#fff;color:#333;resize:vertical;transition:border-color .2s,box-shadow .2s;outline:none}.fv-rich-text-editor::placeholder{color:#999}.fv-rich-text-editor:focus:not(:disabled):not([readonly]){border-color:#007bff;box-shadow:0 0 0 2px #007bff1a}.fv-rich-text-editor-error{border-color:#dc3545}.fv-rich-text-editor-disabled,.fv-rich-text-editor[readonly]{background-color:#f5f5f5;opacity:.6;cursor:not-allowed}.fv-rich-text-editor-focused{border-color:#007bff;border-width:2px;padding:9px}.fv-rich-text-footer{display:flex;justify-content:flex-end;margin-top:4px}.fv-rich-text-stats{font-size:12px;color:#6c757d}.fv-rich-text-editor-error-message{margin-top:4px;font-size:12px;color:#dc3545}\n"] }]
1044
+ }], propDecorators: { label: [{
1045
+ type: Input
1046
+ }], placeholder: [{
1047
+ type: Input
1048
+ }], schema: [{
1049
+ type: Input
1050
+ }], control: [{
1051
+ type: Input
1052
+ }], disabled: [{
1053
+ type: Input
1054
+ }], readonly: [{
1055
+ type: Input
1056
+ }], minHeight: [{
1057
+ type: Input
1058
+ }], showToolbar: [{
1059
+ type: Input
1060
+ }], valueChange: [{
1061
+ type: Output
1062
+ }], blur: [{
1063
+ type: Output
1064
+ }], focus: [{
1065
+ type: Output
1066
+ }], editorRef: [{
1067
+ type: ViewChild,
1068
+ args: ['editor']
1069
+ }] } });
1070
+
1071
+ class FvControlsModule {
1072
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvControlsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1073
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: FvControlsModule, imports: [CommonModule,
1074
+ ReactiveFormsModule,
1075
+ FvEntryFieldComponent,
1076
+ FvDateFieldComponent,
1077
+ FvMonthYearFieldComponent,
1078
+ FvNumberFieldComponent,
1079
+ FvCheckboxComponent,
1080
+ FvRadioGroupComponent,
1081
+ FvDropdownComponent,
1082
+ FvFileSelectorComponent,
1083
+ FvImageSelectorComponent,
1084
+ FvRichTextEditorComponent], exports: [FvEntryFieldComponent,
1085
+ FvDateFieldComponent,
1086
+ FvMonthYearFieldComponent,
1087
+ FvNumberFieldComponent,
1088
+ FvCheckboxComponent,
1089
+ FvRadioGroupComponent,
1090
+ FvDropdownComponent,
1091
+ FvFileSelectorComponent,
1092
+ FvImageSelectorComponent,
1093
+ FvRichTextEditorComponent] });
1094
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvControlsModule, imports: [CommonModule,
1095
+ ReactiveFormsModule,
1096
+ FvEntryFieldComponent,
1097
+ FvDateFieldComponent,
1098
+ FvMonthYearFieldComponent,
1099
+ FvNumberFieldComponent,
1100
+ FvCheckboxComponent,
1101
+ FvRadioGroupComponent,
1102
+ FvDropdownComponent,
1103
+ FvFileSelectorComponent,
1104
+ FvImageSelectorComponent,
1105
+ FvRichTextEditorComponent] });
1106
+ }
1107
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FvControlsModule, decorators: [{
1108
+ type: NgModule,
1109
+ args: [{
1110
+ declarations: [],
1111
+ imports: [
1112
+ CommonModule,
1113
+ ReactiveFormsModule,
1114
+ FvEntryFieldComponent,
1115
+ FvDateFieldComponent,
1116
+ FvMonthYearFieldComponent,
1117
+ FvNumberFieldComponent,
1118
+ FvCheckboxComponent,
1119
+ FvRadioGroupComponent,
1120
+ FvDropdownComponent,
1121
+ FvFileSelectorComponent,
1122
+ FvImageSelectorComponent,
1123
+ FvRichTextEditorComponent,
1124
+ ],
1125
+ exports: [
1126
+ FvEntryFieldComponent,
1127
+ FvDateFieldComponent,
1128
+ FvMonthYearFieldComponent,
1129
+ FvNumberFieldComponent,
1130
+ FvCheckboxComponent,
1131
+ FvRadioGroupComponent,
1132
+ FvDropdownComponent,
1133
+ FvFileSelectorComponent,
1134
+ FvImageSelectorComponent,
1135
+ FvRichTextEditorComponent,
1136
+ ],
1137
+ }]
1138
+ }] });
1139
+
1140
+ /*
1141
+ * Public API Surface of fv-controls
1142
+ */
1143
+
1144
+ /**
1145
+ * Generated bundle index. Do not edit.
1146
+ */
1147
+
1148
+ export { FvCheckboxComponent, FvControlsModule, FvDateFieldComponent, FvDropdownComponent, FvEntryFieldComponent, FvFileSelectorComponent, FvImageSelectorComponent, FvMonthYearFieldComponent, FvNumberFieldComponent, FvRadioGroupComponent, FvRichTextEditorComponent };
1149
+ //# sourceMappingURL=fovestta2-web-angular.mjs.map