@maro-tech/form 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,18 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, Injectable, inject, input, output, ViewChild, Component, EventEmitter, forwardRef, Output, Input, ChangeDetectorRef, ChangeDetectionStrategy, HostListener, DestroyRef, ElementRef, booleanAttribute, ViewContainerRef, computed } from '@angular/core';
2
+ import { signal, Injectable, inject, EventEmitter, Output, Input, ChangeDetectionStrategy, Component, input, output, ViewChild, forwardRef, ChangeDetectorRef, HostListener, DestroyRef, ElementRef, booleanAttribute, ViewContainerRef, computed } from '@angular/core';
3
3
  import { HttpClient } from '@angular/common/http';
4
+ import { throwError, forkJoin, map, Subject, debounceTime, distinctUntilChanged, of } from 'rxjs';
4
5
  import * as i1$2 from '@angular/common';
5
6
  import { CommonModule } from '@angular/common';
6
7
  import * as i2 from '@angular/forms';
7
8
  import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
8
9
  import { UiButtonComponent, UiToastService } from '@maro-tech/core';
10
+ import * as i1 from 'lucide-angular';
11
+ import { Camera, CameraOff, CalendarDays, ChevronLeft, ChevronRight, LucideAngularModule, Eye, EyeOff, X, ChevronDown, Check } from 'lucide-angular';
9
12
  import { BrowserMultiFormatOneDReader } from '@zxing/browser';
10
13
  import { DecodeHintType, BarcodeFormat } from '@zxing/library';
11
- import * as i1 from 'lucide-angular';
12
- import { CalendarDays, ChevronLeft, ChevronRight, LucideAngularModule, Eye, EyeOff, X, ChevronDown, Check } from 'lucide-angular';
13
14
  import * as i1$1 from '@angular/cdk/drag-drop';
14
- import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
15
- import { forkJoin, map, Subject, debounceTime, distinctUntilChanged, of } from 'rxjs';
15
+ import { DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
16
16
  import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
17
17
  import { Router } from '@angular/router';
18
18
 
@@ -56,9 +56,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
56
56
  args: [{ providedIn: 'root' }]
57
57
  }] });
58
58
 
59
+ const ACTION_PRINT = 'ACTION_PRINT';
60
+ const ACTION_FORM = 'ACTION_FORM';
61
+ const ACTION_API = 'ACTION_API';
62
+ const ACTION_SUBMIT = 'ACTION_SUBMIT';
63
+ const isDynamicFormContainerField = (field) => field.type === 'container';
64
+ const isDynamicFormTemplateField = (field) => field.type === 'template';
65
+ const isDynamicFormLookupField = (field) => field.type === 'select' || field.type === 'barcode';
66
+ const flattenDynamicFormFields = (fields) => {
67
+ const result = [];
68
+ for (const field of fields || []) {
69
+ if (!field) {
70
+ continue;
71
+ }
72
+ result.push(field);
73
+ if (isDynamicFormContainerField(field) && Array.isArray(field.fields) && field.fields.length) {
74
+ result.push(...flattenDynamicFormFields(field.fields));
75
+ }
76
+ }
77
+ return result;
78
+ };
79
+
59
80
  class UiFormLookupService {
60
81
  http = inject(HttpClient);
61
82
  lookup(field, value) {
83
+ if (!isDynamicFormLookupField(field)) {
84
+ return throwError(() => new Error('Lookup is not supported for this field type'));
85
+ }
62
86
  const api = String(field.lookupApi || '').trim();
63
87
  const endpoint = api
64
88
  .replace(/:value/g, encodeURIComponent(value))
@@ -79,39 +103,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
79
103
  type: Injectable
80
104
  }] });
81
105
 
82
- const ACTION_PRINT = 'ACTION_PRINT';
83
- const ACTION_FORM = 'ACTION_FORM';
84
- const ACTION_API = 'ACTION_API';
85
- const ACTION_SUBMIT = 'ACTION_SUBMIT';
86
- const isDynamicFormContainerField = (field) => field.type === 'container';
87
- const isDynamicFormTemplateField = (field) => field.type === 'template';
88
- const flattenDynamicFormFields = (fields) => {
89
- const result = [];
90
- for (const field of fields || []) {
91
- if (!field) {
92
- continue;
93
- }
94
- result.push(field);
95
- if (isDynamicFormContainerField(field) && Array.isArray(field.fields) && field.fields.length) {
96
- result.push(...flattenDynamicFormFields(field.fields));
97
- }
98
- }
99
- return result;
100
- };
106
+ class FieldFrameComponent {
107
+ label;
108
+ hint;
109
+ error;
110
+ required = false;
111
+ forId;
112
+ disabled = false;
113
+ actions = [];
114
+ actionTriggered = new EventEmitter();
115
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FieldFrameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
116
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FieldFrameComponent, isStandalone: true, selector: "field-frame", inputs: { label: "label", hint: "hint", error: "error", required: "required", forId: "forId", disabled: "disabled", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, ngImport: i0, template: "<div class=\"relative w-full flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\">\n @if (label) {\n <label class=\"text-[0.7rem] font-semibold text-slate-600\" [attr.for]=\"forId\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </label>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n <ng-content />\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"actionTriggered.emit(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"absolute left-0 top-full z-10 mt-1 text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
117
+ }
118
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FieldFrameComponent, decorators: [{
119
+ type: Component,
120
+ args: [{ selector: 'field-frame', standalone: true, imports: [CommonModule, UiButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"relative w-full flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\">\n @if (label) {\n <label class=\"text-[0.7rem] font-semibold text-slate-600\" [attr.for]=\"forId\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </label>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n <ng-content />\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"actionTriggered.emit(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"absolute left-0 top-full z-10 mt-1 text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</div>\n" }]
121
+ }], propDecorators: { label: [{
122
+ type: Input
123
+ }], hint: [{
124
+ type: Input
125
+ }], error: [{
126
+ type: Input
127
+ }], required: [{
128
+ type: Input
129
+ }], forId: [{
130
+ type: Input
131
+ }], disabled: [{
132
+ type: Input
133
+ }], actions: [{
134
+ type: Input
135
+ }], actionTriggered: [{
136
+ type: Output
137
+ }] } });
101
138
 
102
- class InputBarcodeComponent {
139
+ class InputBarcodeControlComponent {
140
+ cameraIcon = Camera;
141
+ cameraOffIcon = CameraOff;
103
142
  value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
104
143
  label = input('Штрихкод', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
105
144
  placeholder = input('Відскануйте або введіть штрихкод', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
106
145
  hint = input('Сканер може просто ввести код і натиснути Enter.', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
146
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
147
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
107
148
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
108
- autoOpen = input(true, ...(ngDevMode ? [{ debugName: "autoOpen" }] : /* istanbul ignore next */ []));
109
- searchButtonLabel = input('Знайти', ...(ngDevMode ? [{ debugName: "searchButtonLabel" }] : /* istanbul ignore next */ []));
110
- openCameraLabel = input('Відкрити камеру', ...(ngDevMode ? [{ debugName: "openCameraLabel" }] : /* istanbul ignore next */ []));
111
- closeCameraLabel = input('Закрити камеру', ...(ngDevMode ? [{ debugName: "closeCameraLabel" }] : /* istanbul ignore next */ []));
112
- cameraTitle = input('Камера', ...(ngDevMode ? [{ debugName: "cameraTitle" }] : /* istanbul ignore next */ []));
113
- cameraStartingLabel = input('Запускаємо камеру…', ...(ngDevMode ? [{ debugName: "cameraStartingLabel" }] : /* istanbul ignore next */ []));
114
- cameraHint = input('Наведіть камеру на штрихкод. Після зчитування код підставиться автоматично.', ...(ngDevMode ? [{ debugName: "cameraHint" }] : /* istanbul ignore next */ []));
115
149
  valueChange = output();
116
150
  scanned = output();
117
151
  searchRequested = output();
@@ -138,9 +172,8 @@ class InputBarcodeComponent {
138
172
  this.autoOpenQueued = false;
139
173
  return;
140
174
  }
141
- if (changes['autoOpen'] && this.autoOpen() && !this.disabled()) {
175
+ if (!this.disabled())
142
176
  this.scheduleAutoOpen();
143
- }
144
177
  }
145
178
  ngAfterViewInit() {
146
179
  this.scheduleAutoOpen();
@@ -210,13 +243,13 @@ class InputBarcodeComponent {
210
243
  this.stopCamera();
211
244
  }
212
245
  scheduleAutoOpen() {
213
- if (typeof window === 'undefined' || !this.autoOpen() || this.disabled() || this.isCameraOpen() || this.isCameraStarting() || this.autoOpenQueued) {
246
+ if (typeof window === 'undefined' || this.disabled() || this.isCameraOpen() || this.isCameraStarting() || this.autoOpenQueued) {
214
247
  return;
215
248
  }
216
249
  this.autoOpenQueued = true;
217
250
  window.requestAnimationFrame(() => {
218
251
  this.autoOpenQueued = false;
219
- if (!this.autoOpen() || this.disabled() || this.isCameraOpen() || this.isCameraStarting()) {
252
+ if (this.disabled() || this.isCameraOpen() || this.isCameraStarting()) {
220
253
  return;
221
254
  }
222
255
  void this.openCamera();
@@ -363,18 +396,10 @@ class InputBarcodeComponent {
363
396
  return null;
364
397
  }
365
398
  }
366
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBarcodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
367
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputBarcodeComponent, isStandalone: true, selector: "input-barcode", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, autoOpen: { classPropertyName: "autoOpen", publicName: "autoOpen", isSignal: true, isRequired: false, transformFunction: null }, searchButtonLabel: { classPropertyName: "searchButtonLabel", publicName: "searchButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, openCameraLabel: { classPropertyName: "openCameraLabel", publicName: "openCameraLabel", isSignal: true, isRequired: false, transformFunction: null }, closeCameraLabel: { classPropertyName: "closeCameraLabel", publicName: "closeCameraLabel", isSignal: true, isRequired: false, transformFunction: null }, cameraTitle: { classPropertyName: "cameraTitle", publicName: "cameraTitle", isSignal: true, isRequired: false, transformFunction: null }, cameraStartingLabel: { classPropertyName: "cameraStartingLabel", publicName: "cameraStartingLabel", isSignal: true, isRequired: false, transformFunction: null }, cameraHint: { classPropertyName: "cameraHint", publicName: "cameraHint", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", scanned: "scanned", searchRequested: "searchRequested", cameraStateChange: "cameraStateChange" }, viewQueries: [{ propertyName: "cameraVideo", first: true, predicate: ["cameraVideo"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
368
- <div class="space-y-2">
369
- @if (label()) {
370
- <label class="flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500" for="barcode-scanner-input">
371
- <span class="text-[0.7rem] font-semibold text-slate-600">
372
- {{ label() }}
373
- </span>
374
- </label>
375
- }
399
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBarcodeControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
400
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputBarcodeControlComponent, isStandalone: true, selector: "input-barcode-control", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", scanned: "scanned", searchRequested: "searchRequested", cameraStateChange: "cameraStateChange" }, viewQueries: [{ propertyName: "cameraVideo", first: true, predicate: ["cameraVideo"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
376
401
 
377
- <div class="flex items-start gap-2">
402
+ <div class="relative flex items-start gap-2">
378
403
  <div class="min-w-0 flex-1">
379
404
  <input
380
405
  #barcodeInput
@@ -387,16 +412,14 @@ class InputBarcodeComponent {
387
412
  (ngModelChange)="onValueChange($event)"
388
413
  (keydown.enter)="requestSearch()"
389
414
  class="w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200"
415
+ [class.border-rose-300]="!!error()"
416
+ [class.ring-2]="!!error()"
417
+ [class.ring-rose-200]="!!error()"
390
418
  [placeholder]="placeholder()"
391
419
  />
392
- @if (hint()) {
393
- <p class="mt-2 text-[0.5rem] text-slate-500">
394
- {{ hint() }}
395
- </p>
396
- }
397
420
  </div>
398
421
 
399
- <div class="mt-0.5 flex flex-col gap-2">
422
+ <div class="mt-0.5 flex flex-row flex-wrap items-center gap-2">
400
423
  <ui-button
401
424
  type="button"
402
425
  variant="secondary"
@@ -404,31 +427,39 @@ class InputBarcodeComponent {
404
427
  [disabled]="disabled()"
405
428
  (click)="requestSearch()"
406
429
  >
407
- {{ searchButtonLabel() }}
408
- </ui-button>
409
- <ui-button
410
- type="button"
411
- variant="ghost"
412
- size="sm"
413
- [disabled]="disabled() || isCameraStarting()"
414
- (click)="isCameraOpen() ? closeCamera() : openCamera()"
415
- >
416
- {{ isCameraOpen() ? closeCameraLabel() : openCameraLabel() }}
430
+ Знайти
417
431
  </ui-button>
432
+ <ui-button
433
+ type="button"
434
+ variant="ghost"
435
+ size="sm"
436
+ [iconOnly]="true"
437
+ [icon]="isCameraOpen() ? cameraOffIcon : cameraIcon"
438
+ [ariaLabel]="isCameraOpen() ? 'Закрити камеру' : 'Відкрити камеру'"
439
+ [disabled]="disabled() || isCameraStarting()"
440
+ (click)="isCameraOpen() ? closeCamera() : openCamera()"
441
+ ></ui-button>
418
442
  </div>
419
443
  </div>
420
444
 
445
+ @if (hint()) {
446
+ <p class="mt-2 text-[0.5rem] text-slate-500">{{ hint() }}</p>
447
+ }
448
+ @if (error()) {
449
+ <p class="absolute left-0 top-full z-10 mt-1 text-[0.5rem] text-rose-400">{{ error() }}</p>
450
+ }
451
+
421
452
  @if (isCameraStarting()) {
422
453
  <div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-500">
423
- {{ cameraStartingLabel() }}
454
+ Запускаємо камеру…
424
455
  </div>
425
456
  }
426
457
 
427
458
  @if (isCameraOpen()) {
428
- <section class="space-y-3 rounded-3xl border border-slate-200 bg-slate-950 p-3 text-white">
459
+ <section class="mt-3 space-y-3 rounded-3xl border border-slate-200 bg-slate-950 p-3 text-white">
429
460
  <div class="flex items-center justify-between gap-3">
430
461
  <div>
431
- <p class="text-xs font-semibold uppercase tracking-[0.22em] text-slate-300">{{ cameraTitle() }}</p>
462
+ <p class="text-xs font-semibold uppercase tracking-[0.22em] text-slate-300">Камера</p>
432
463
  <p class="text-sm text-slate-200">{{ cameraStatus() }}</p>
433
464
  </div>
434
465
  <button
@@ -436,7 +467,7 @@ class InputBarcodeComponent {
436
467
  class="rounded-full border border-slate-700 px-3 py-1 text-xs font-semibold text-slate-200 transition hover:border-slate-500 hover:text-white"
437
468
  (click)="closeCamera()"
438
469
  >
439
- {{ closeCameraLabel() }}
470
+ Закрити камеру
440
471
  </button>
441
472
  </div>
442
473
 
@@ -445,7 +476,7 @@ class InputBarcodeComponent {
445
476
  </div>
446
477
 
447
478
  <p class="text-xs text-slate-300">
448
- {{ cameraHint() }}
479
+ Наведіть камеру на штрихкод.
449
480
  </p>
450
481
  </section>
451
482
  }
@@ -455,26 +486,18 @@ class InputBarcodeComponent {
455
486
  {{ cameraError() }}
456
487
  </div>
457
488
  }
458
- </div>
489
+
459
490
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }] });
460
491
  }
461
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBarcodeComponent, decorators: [{
492
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBarcodeControlComponent, decorators: [{
462
493
  type: Component,
463
494
  args: [{
464
- selector: 'input-barcode',
495
+ selector: 'input-barcode-control',
465
496
  standalone: true,
466
497
  imports: [CommonModule, FormsModule, UiButtonComponent],
467
498
  template: `
468
- <div class="space-y-2">
469
- @if (label()) {
470
- <label class="flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500" for="barcode-scanner-input">
471
- <span class="text-[0.7rem] font-semibold text-slate-600">
472
- {{ label() }}
473
- </span>
474
- </label>
475
- }
476
499
 
477
- <div class="flex items-start gap-2">
500
+ <div class="relative flex items-start gap-2">
478
501
  <div class="min-w-0 flex-1">
479
502
  <input
480
503
  #barcodeInput
@@ -487,16 +510,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
487
510
  (ngModelChange)="onValueChange($event)"
488
511
  (keydown.enter)="requestSearch()"
489
512
  class="w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200"
513
+ [class.border-rose-300]="!!error()"
514
+ [class.ring-2]="!!error()"
515
+ [class.ring-rose-200]="!!error()"
490
516
  [placeholder]="placeholder()"
491
517
  />
492
- @if (hint()) {
493
- <p class="mt-2 text-[0.5rem] text-slate-500">
494
- {{ hint() }}
495
- </p>
496
- }
497
518
  </div>
498
519
 
499
- <div class="mt-0.5 flex flex-col gap-2">
520
+ <div class="mt-0.5 flex flex-row flex-wrap items-center gap-2">
500
521
  <ui-button
501
522
  type="button"
502
523
  variant="secondary"
@@ -504,31 +525,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
504
525
  [disabled]="disabled()"
505
526
  (click)="requestSearch()"
506
527
  >
507
- {{ searchButtonLabel() }}
508
- </ui-button>
509
- <ui-button
510
- type="button"
511
- variant="ghost"
512
- size="sm"
513
- [disabled]="disabled() || isCameraStarting()"
514
- (click)="isCameraOpen() ? closeCamera() : openCamera()"
515
- >
516
- {{ isCameraOpen() ? closeCameraLabel() : openCameraLabel() }}
528
+ Знайти
517
529
  </ui-button>
530
+ <ui-button
531
+ type="button"
532
+ variant="ghost"
533
+ size="sm"
534
+ [iconOnly]="true"
535
+ [icon]="isCameraOpen() ? cameraOffIcon : cameraIcon"
536
+ [ariaLabel]="isCameraOpen() ? 'Закрити камеру' : 'Відкрити камеру'"
537
+ [disabled]="disabled() || isCameraStarting()"
538
+ (click)="isCameraOpen() ? closeCamera() : openCamera()"
539
+ ></ui-button>
518
540
  </div>
519
541
  </div>
520
542
 
543
+ @if (hint()) {
544
+ <p class="mt-2 text-[0.5rem] text-slate-500">{{ hint() }}</p>
545
+ }
546
+ @if (error()) {
547
+ <p class="absolute left-0 top-full z-10 mt-1 text-[0.5rem] text-rose-400">{{ error() }}</p>
548
+ }
549
+
521
550
  @if (isCameraStarting()) {
522
551
  <div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-500">
523
- {{ cameraStartingLabel() }}
552
+ Запускаємо камеру…
524
553
  </div>
525
554
  }
526
555
 
527
556
  @if (isCameraOpen()) {
528
- <section class="space-y-3 rounded-3xl border border-slate-200 bg-slate-950 p-3 text-white">
557
+ <section class="mt-3 space-y-3 rounded-3xl border border-slate-200 bg-slate-950 p-3 text-white">
529
558
  <div class="flex items-center justify-between gap-3">
530
559
  <div>
531
- <p class="text-xs font-semibold uppercase tracking-[0.22em] text-slate-300">{{ cameraTitle() }}</p>
560
+ <p class="text-xs font-semibold uppercase tracking-[0.22em] text-slate-300">Камера</p>
532
561
  <p class="text-sm text-slate-200">{{ cameraStatus() }}</p>
533
562
  </div>
534
563
  <button
@@ -536,7 +565,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
536
565
  class="rounded-full border border-slate-700 px-3 py-1 text-xs font-semibold text-slate-200 transition hover:border-slate-500 hover:text-white"
537
566
  (click)="closeCamera()"
538
567
  >
539
- {{ closeCameraLabel() }}
568
+ Закрити камеру
540
569
  </button>
541
570
  </div>
542
571
 
@@ -545,7 +574,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
545
574
  </div>
546
575
 
547
576
  <p class="text-xs text-slate-300">
548
- {{ cameraHint() }}
577
+ Наведіть камеру на штрихкод.
549
578
  </p>
550
579
  </section>
551
580
  }
@@ -555,19 +584,105 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
555
584
  {{ cameraError() }}
556
585
  </div>
557
586
  }
558
- </div>
587
+
559
588
  `,
560
589
  }]
561
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], autoOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoOpen", required: false }] }], searchButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchButtonLabel", required: false }] }], openCameraLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "openCameraLabel", required: false }] }], closeCameraLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeCameraLabel", required: false }] }], cameraTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "cameraTitle", required: false }] }], cameraStartingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cameraStartingLabel", required: false }] }], cameraHint: [{ type: i0.Input, args: [{ isSignal: true, alias: "cameraHint", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], scanned: [{ type: i0.Output, args: ["scanned"] }], searchRequested: [{ type: i0.Output, args: ["searchRequested"] }], cameraStateChange: [{ type: i0.Output, args: ["cameraStateChange"] }], cameraVideo: [{
590
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], scanned: [{ type: i0.Output, args: ["scanned"] }], searchRequested: [{ type: i0.Output, args: ["searchRequested"] }], cameraStateChange: [{ type: i0.Output, args: ["cameraStateChange"] }], cameraVideo: [{
562
591
  type: ViewChild,
563
592
  args: ['cameraVideo']
564
593
  }] } });
565
594
 
595
+ class InputBarcodeComponent {
596
+ value = '';
597
+ label = 'Штрихкод';
598
+ placeholder = 'Відскануйте або введіть штрихкод';
599
+ hint = 'Сканер може просто ввести код і натиснути Enter.';
600
+ error = '';
601
+ required = false;
602
+ disabled = false;
603
+ valueChange = new EventEmitter();
604
+ scanned = new EventEmitter();
605
+ searchRequested = new EventEmitter();
606
+ cameraStateChange = new EventEmitter();
607
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBarcodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
608
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputBarcodeComponent, isStandalone: true, selector: "input-barcode", inputs: { value: "value", label: "label", placeholder: "placeholder", hint: "hint", error: "error", required: "required", disabled: "disabled" }, outputs: { valueChange: "valueChange", scanned: "scanned", searchRequested: "searchRequested", cameraStateChange: "cameraStateChange" }, ngImport: i0, template: `
609
+ <field-frame [label]="label" [required]="required" [forId]="'barcode-scanner-input'" [disabled]="disabled">
610
+ <input-barcode-control [value]="value" [label]="label" [placeholder]="placeholder" [hint]="hint" [error]="error" [required]="required" [disabled]="disabled" (valueChange)="valueChange.emit($event)" (scanned)="scanned.emit($event)" (searchRequested)="searchRequested.emit()" (cameraStateChange)="cameraStateChange.emit($event)" />
611
+ </field-frame>
612
+ `, isInline: true, dependencies: [{ kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputBarcodeControlComponent, selector: "input-barcode-control", inputs: ["value", "label", "placeholder", "hint", "error", "required", "disabled"], outputs: ["valueChange", "scanned", "searchRequested", "cameraStateChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
613
+ }
614
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBarcodeComponent, decorators: [{
615
+ type: Component,
616
+ args: [{
617
+ selector: 'input-barcode',
618
+ standalone: true,
619
+ imports: [FieldFrameComponent, InputBarcodeControlComponent],
620
+ template: `
621
+ <field-frame [label]="label" [required]="required" [forId]="'barcode-scanner-input'" [disabled]="disabled">
622
+ <input-barcode-control [value]="value" [label]="label" [placeholder]="placeholder" [hint]="hint" [error]="error" [required]="required" [disabled]="disabled" (valueChange)="valueChange.emit($event)" (scanned)="scanned.emit($event)" (searchRequested)="searchRequested.emit()" (cameraStateChange)="cameraStateChange.emit($event)" />
623
+ </field-frame>
624
+ `,
625
+ changeDetection: ChangeDetectionStrategy.OnPush,
626
+ }]
627
+ }], propDecorators: { value: [{
628
+ type: Input
629
+ }], label: [{
630
+ type: Input
631
+ }], placeholder: [{
632
+ type: Input
633
+ }], hint: [{
634
+ type: Input
635
+ }], error: [{
636
+ type: Input
637
+ }], required: [{
638
+ type: Input
639
+ }], disabled: [{
640
+ type: Input
641
+ }], valueChange: [{
642
+ type: Output
643
+ }], scanned: [{
644
+ type: Output
645
+ }], searchRequested: [{
646
+ type: Output
647
+ }], cameraStateChange: [{
648
+ type: Output
649
+ }] } });
650
+
651
+ class InputCheckboxControlComponent {
652
+ value = false;
653
+ disabled = false;
654
+ valueChange = new EventEmitter();
655
+ touched = new EventEmitter();
656
+ onValueChange(event) {
657
+ this.valueChange.emit(event.target.checked);
658
+ }
659
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputCheckboxControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
660
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputCheckboxControlComponent, isStandalone: true, selector: "input-checkbox-control", inputs: { value: "value", disabled: "disabled" }, outputs: { valueChange: "valueChange", touched: "touched" }, ngImport: i0, template: `<input type="checkbox" class="h-4 w-4 rounded border-slate-300 text-slate-900 focus:ring-slate-400" [checked]="value" [disabled]="disabled" (change)="onValueChange($event)" (blur)="touched.emit()" />`, isInline: true });
661
+ }
662
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputCheckboxControlComponent, decorators: [{
663
+ type: Component,
664
+ args: [{
665
+ selector: 'input-checkbox-control',
666
+ standalone: true,
667
+ template: `<input type="checkbox" class="h-4 w-4 rounded border-slate-300 text-slate-900 focus:ring-slate-400" [checked]="value" [disabled]="disabled" (change)="onValueChange($event)" (blur)="touched.emit()" />`,
668
+ }]
669
+ }], propDecorators: { value: [{
670
+ type: Input
671
+ }], disabled: [{
672
+ type: Input
673
+ }], valueChange: [{
674
+ type: Output
675
+ }], touched: [{
676
+ type: Output
677
+ }] } });
678
+
566
679
  class InputCheckboxComponent {
567
680
  label;
681
+ hint;
682
+ error;
683
+ required = false;
568
684
  disabled = false;
569
685
  value = false;
570
- valueChange = new EventEmitter();
571
686
  onChange = () => undefined;
572
687
  onTouched = () => undefined;
573
688
  writeValue(value) { this.value = !!value; }
@@ -578,22 +693,27 @@ class InputCheckboxComponent {
578
693
  const value = event.target.checked;
579
694
  this.value = value;
580
695
  this.onChange(value);
581
- this.valueChange.emit(value);
696
+ }
697
+ handleValueChange(value) {
698
+ this.value = value;
699
+ this.onChange(value);
582
700
  }
583
701
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputCheckboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
584
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputCheckboxComponent, isStandalone: true, selector: "input-checkbox", inputs: { label: "label", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputCheckboxComponent), multi: true }], ngImport: i0, template: "<label class=\"inline-flex items-center gap-3 text-sm text-slate-700\">\n <input\n type=\"checkbox\"\n class=\"h-4 w-4 rounded border-slate-300 text-slate-900 focus:ring-slate-400\"\n [checked]=\"value\"\n [disabled]=\"disabled\"\n (change)=\"onValueChange($event)\"\n (blur)=\"onTouched()\"\n />\n <span>{{ label }}</span>\n</label>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
702
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputCheckboxComponent, isStandalone: true, selector: "input-checkbox", inputs: { label: "label", hint: "hint", error: "error", required: "required", disabled: "disabled" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputCheckboxComponent), multi: true }], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\">\n <input-checkbox-control [value]=\"value\" [disabled]=\"disabled\" (valueChange)=\"handleValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputCheckboxControlComponent, selector: "input-checkbox-control", inputs: ["value", "disabled"], outputs: ["valueChange", "touched"] }] });
585
703
  }
586
704
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputCheckboxComponent, decorators: [{
587
705
  type: Component,
588
- args: [{ selector: 'input-checkbox', standalone: true, imports: [CommonModule], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputCheckboxComponent), multi: true }], template: "<label class=\"inline-flex items-center gap-3 text-sm text-slate-700\">\n <input\n type=\"checkbox\"\n class=\"h-4 w-4 rounded border-slate-300 text-slate-900 focus:ring-slate-400\"\n [checked]=\"value\"\n [disabled]=\"disabled\"\n (change)=\"onValueChange($event)\"\n (blur)=\"onTouched()\"\n />\n <span>{{ label }}</span>\n</label>\n" }]
706
+ args: [{ selector: 'input-checkbox', standalone: true, imports: [CommonModule, FieldFrameComponent, InputCheckboxControlComponent], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputCheckboxComponent), multi: true }], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\">\n <input-checkbox-control [value]=\"value\" [disabled]=\"disabled\" (valueChange)=\"handleValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n" }]
589
707
  }], propDecorators: { label: [{
590
708
  type: Input
591
- }], disabled: [{
709
+ }], hint: [{
592
710
  type: Input
593
- }], value: [{
711
+ }], error: [{
712
+ type: Input
713
+ }], required: [{
714
+ type: Input
715
+ }], disabled: [{
594
716
  type: Input
595
- }], valueChange: [{
596
- type: Output
597
717
  }] } });
598
718
 
599
719
  const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -618,6 +738,56 @@ const validateFormControl = (control, required, pattern, email = false) => {
618
738
  return null;
619
739
  };
620
740
 
741
+ class InputColorControlComponent {
742
+ id;
743
+ name;
744
+ disabled = false;
745
+ value = '#000000';
746
+ pickerValue = '#000000';
747
+ valueChange = new EventEmitter();
748
+ touched = new EventEmitter();
749
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputColorControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
750
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputColorControlComponent, isStandalone: true, selector: "input-color-control", inputs: { id: "id", name: "name", disabled: "disabled", value: "value", pickerValue: "pickerValue" }, outputs: { valueChange: "valueChange", touched: "touched" }, ngImport: i0, template: `
751
+ <div class="flex items-center gap-3">
752
+ <label class="relative inline-flex h-11 w-11 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-white shadow-sm transition hover:border-slate-300" [class.cursor-not-allowed]="disabled" [class.opacity-60]="disabled" [attr.for]="id">
753
+ <span class="block h-9 w-9 rounded-full border border-black/10" [style.backgroundColor]="pickerValue"></span>
754
+ <input class="absolute inset-0 h-full w-full cursor-pointer opacity-0" type="color" [attr.id]="id" [attr.name]="name ? name + '-picker' : null" [disabled]="disabled" [value]="pickerValue" (input)="valueChange.emit($any($event.target).value)" (blur)="touched.emit()" />
755
+ </label>
756
+ <input class="min-w-[110px] rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs font-semibold uppercase text-slate-700 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200 disabled:cursor-not-allowed disabled:opacity-60" type="text" [attr.name]="name" [disabled]="disabled" [value]="value" (input)="valueChange.emit($any($event.target).value)" (blur)="touched.emit()" />
757
+ </div>
758
+ `, isInline: true });
759
+ }
760
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputColorControlComponent, decorators: [{
761
+ type: Component,
762
+ args: [{
763
+ selector: 'input-color-control',
764
+ standalone: true,
765
+ template: `
766
+ <div class="flex items-center gap-3">
767
+ <label class="relative inline-flex h-11 w-11 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-white shadow-sm transition hover:border-slate-300" [class.cursor-not-allowed]="disabled" [class.opacity-60]="disabled" [attr.for]="id">
768
+ <span class="block h-9 w-9 rounded-full border border-black/10" [style.backgroundColor]="pickerValue"></span>
769
+ <input class="absolute inset-0 h-full w-full cursor-pointer opacity-0" type="color" [attr.id]="id" [attr.name]="name ? name + '-picker' : null" [disabled]="disabled" [value]="pickerValue" (input)="valueChange.emit($any($event.target).value)" (blur)="touched.emit()" />
770
+ </label>
771
+ <input class="min-w-[110px] rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs font-semibold uppercase text-slate-700 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200 disabled:cursor-not-allowed disabled:opacity-60" type="text" [attr.name]="name" [disabled]="disabled" [value]="value" (input)="valueChange.emit($any($event.target).value)" (blur)="touched.emit()" />
772
+ </div>
773
+ `,
774
+ }]
775
+ }], propDecorators: { id: [{
776
+ type: Input
777
+ }], name: [{
778
+ type: Input
779
+ }], disabled: [{
780
+ type: Input
781
+ }], value: [{
782
+ type: Input
783
+ }], pickerValue: [{
784
+ type: Input
785
+ }], valueChange: [{
786
+ type: Output
787
+ }], touched: [{
788
+ type: Output
789
+ }] } });
790
+
621
791
  class InputColorComponent {
622
792
  cdr = inject(ChangeDetectorRef);
623
793
  label;
@@ -625,7 +795,6 @@ class InputColorComponent {
625
795
  name;
626
796
  id;
627
797
  required = false;
628
- fullWidth = false;
629
798
  error;
630
799
  disabled = false;
631
800
  value = '#000000';
@@ -662,6 +831,10 @@ class InputColorComponent {
662
831
  this.value = this.normalizeHexInput(value);
663
832
  this.onChange(this.value);
664
833
  }
834
+ handleValueChange(value) {
835
+ this.value = value;
836
+ this.onChange(value);
837
+ }
665
838
  markTouched() {
666
839
  this.onTouched();
667
840
  }
@@ -676,25 +849,25 @@ class InputColorComponent {
676
849
  return /^#([0-9a-fA-F]{6})$/.test(value.trim());
677
850
  }
678
851
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputColorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
679
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputColorComponent, isStandalone: true, selector: "input-color", inputs: { label: "label", hint: "hint", name: "name", id: "id", required: "required", fullWidth: "fullWidth", error: "error" }, providers: [
852
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputColorComponent, isStandalone: true, selector: "input-color", inputs: { label: "label", hint: "hint", name: "name", id: "id", required: "required", error: "error" }, providers: [
680
853
  {
681
854
  provide: NG_VALUE_ACCESSOR,
682
855
  useExisting: forwardRef(() => InputColorComponent),
683
856
  multi: true,
684
857
  },
685
858
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputColorComponent), multi: true },
686
- ], ngImport: i0, template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-center gap-3\">\n <label\n class=\"relative inline-flex h-11 w-11 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-white shadow-sm transition hover:border-slate-300\"\n [class.cursor-not-allowed]=\"disabled\"\n [class.opacity-60]=\"disabled\"\n [attr.for]=\"controlId\"\n >\n <span\n class=\"block h-9 w-9 rounded-full border border-black/10\"\n [style.backgroundColor]=\"pickerValue\"\n ></span>\n <input\n class=\"absolute inset-0 h-full w-full cursor-pointer opacity-0\"\n type=\"color\"\n [attr.id]=\"controlId\"\n [attr.name]=\"name ? name + '-picker' : null\"\n [disabled]=\"disabled\"\n [value]=\"pickerValue\"\n (input)=\"onPickerChange($event.target.value)\"\n (blur)=\"markTouched()\"\n />\n </label>\n <input\n class=\"min-w-[110px] rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs font-semibold uppercase text-slate-700 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200 disabled:cursor-not-allowed disabled:opacity-60\"\n type=\"text\"\n [attr.name]=\"name\"\n [disabled]=\"disabled\"\n [value]=\"value\"\n (input)=\"onHexInput($event.target.value)\"\n (blur)=\"markTouched()\"\n />\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
859
+ ], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\">\n <input-color-control [id]=\"controlId\" [name]=\"name\" [disabled]=\"disabled\" [value]=\"value\" [pickerValue]=\"pickerValue\" (valueChange)=\"handleValueChange($event)\" (touched)=\"markTouched()\" />\n</field-frame>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputColorControlComponent, selector: "input-color-control", inputs: ["id", "name", "disabled", "value", "pickerValue"], outputs: ["valueChange", "touched"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
687
860
  }
688
861
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputColorComponent, decorators: [{
689
862
  type: Component,
690
- args: [{ selector: 'input-color', standalone: true, imports: [CommonModule, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
863
+ args: [{ selector: 'input-color', standalone: true, imports: [CommonModule, FormsModule, FieldFrameComponent, InputColorControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
691
864
  {
692
865
  provide: NG_VALUE_ACCESSOR,
693
866
  useExisting: forwardRef(() => InputColorComponent),
694
867
  multi: true,
695
868
  },
696
869
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputColorComponent), multi: true },
697
- ], template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-center gap-3\">\n <label\n class=\"relative inline-flex h-11 w-11 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-white shadow-sm transition hover:border-slate-300\"\n [class.cursor-not-allowed]=\"disabled\"\n [class.opacity-60]=\"disabled\"\n [attr.for]=\"controlId\"\n >\n <span\n class=\"block h-9 w-9 rounded-full border border-black/10\"\n [style.backgroundColor]=\"pickerValue\"\n ></span>\n <input\n class=\"absolute inset-0 h-full w-full cursor-pointer opacity-0\"\n type=\"color\"\n [attr.id]=\"controlId\"\n [attr.name]=\"name ? name + '-picker' : null\"\n [disabled]=\"disabled\"\n [value]=\"pickerValue\"\n (input)=\"onPickerChange($event.target.value)\"\n (blur)=\"markTouched()\"\n />\n </label>\n <input\n class=\"min-w-[110px] rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs font-semibold uppercase text-slate-700 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200 disabled:cursor-not-allowed disabled:opacity-60\"\n type=\"text\"\n [attr.name]=\"name\"\n [disabled]=\"disabled\"\n [value]=\"value\"\n (input)=\"onHexInput($event.target.value)\"\n (blur)=\"markTouched()\"\n />\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n" }]
870
+ ], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\">\n <input-color-control [id]=\"controlId\" [name]=\"name\" [disabled]=\"disabled\" [value]=\"value\" [pickerValue]=\"pickerValue\" (valueChange)=\"handleValueChange($event)\" (touched)=\"markTouched()\" />\n</field-frame>\n" }]
698
871
  }], propDecorators: { label: [{
699
872
  type: Input
700
873
  }], hint: [{
@@ -705,8 +878,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
705
878
  type: Input
706
879
  }], required: [{
707
880
  type: Input
708
- }], fullWidth: [{
709
- type: Input
710
881
  }], error: [{
711
882
  type: Input
712
883
  }] } });
@@ -733,7 +904,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
733
904
  args: [{ providedIn: 'root' }]
734
905
  }] });
735
906
 
736
- class InputDateComponent {
907
+ class InputDateControlComponent {
737
908
  cdr = inject(ChangeDetectorRef);
738
909
  popupService = inject(UiPopupService);
739
910
  closeActivePopup = () => this.closeCalendar();
@@ -749,7 +920,6 @@ class InputDateComponent {
749
920
  nextMonthIcon = ChevronRight;
750
921
  rows = 4;
751
922
  required = false;
752
- fullWidth = false;
753
923
  autocomplete = 'off';
754
924
  pattern;
755
925
  error;
@@ -757,7 +927,7 @@ class InputDateComponent {
757
927
  disabled = false;
758
928
  value = '';
759
929
  static nextId = 0;
760
- generatedId = `input-date-${InputDateComponent.nextId++}`;
930
+ generatedId = `input-date-${InputDateControlComponent.nextId++}`;
761
931
  visibleMonth = this.startOfMonth(new Date());
762
932
  calendarView = 'days';
763
933
  isCalendarOpen = false;
@@ -966,26 +1136,26 @@ class InputDateComponent {
966
1136
  const day = String(date.getDate()).padStart(2, '0');
967
1137
  return `${year}-${month}-${day}`;
968
1138
  }
969
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
970
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputDateComponent, isStandalone: true, selector: "input-date", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", fullWidth: "fullWidth", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, host: { listeners: { "document:click": "onDocumentClick()", "document:keydown.escape": "onEscape()" } }, providers: [
1139
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDateControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1140
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputDateControlComponent, isStandalone: true, selector: "input-date-control", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions", disabled: "disabled" }, outputs: { actionTriggered: "actionTriggered" }, host: { listeners: { "document:click": "onDocumentClick()", "document:keydown.escape": "onEscape()" } }, providers: [
971
1141
  {
972
1142
  provide: NG_VALUE_ACCESSOR,
973
- useExisting: forwardRef(() => InputDateComponent),
1143
+ useExisting: forwardRef(() => InputDateControlComponent),
974
1144
  multi: true,
975
1145
  },
976
- { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDateComponent), multi: true },
977
- ], ngImport: i0, template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <div class=\"relative\">\n <input\n type=\"text\"\n [attr.placeholder]=\"placeholder ?? '\u0414\u0414.\u041C\u041C.\u0420\u0420\u0420\u0420'\"\n maxlength=\"10\"\n inputmode=\"numeric\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"displayValue\"\n (click)=\"openCalendar($event)\"\n (keydown)=\"allowDateKey($event)\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n <lucide-icon\n [img]=\"calendarIcon\"\n class=\"pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400\"\n aria-hidden=\"true\"\n ></lucide-icon>\n\n @if (isCalendarOpen) {\n <div\n class=\"absolute left-0 top-full z-50 mt-2 w-[min(20rem,calc(100vw-2rem))] rounded-xl border border-slate-200 bg-white p-4 shadow-xl\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"mb-4 flex items-center justify-between\" [class.justify-center]=\"calendarView === 'months'\">\n @if (calendarView !== 'months') {\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-900\"\n aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0456\u0439 \u043C\u0456\u0441\u044F\u0446\u044C\"\n (click)=\"changeMonth(-1, $event)\"\n >\n <lucide-icon [img]=\"previousMonthIcon\" class=\"h-4 w-4\"></lucide-icon>\n </button>\n }\n <div class=\"flex items-center gap-1\">\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-1 text-sm font-semibold capitalize text-slate-900 transition hover:bg-slate-100\"\n (click)=\"showMonthPicker($event)\"\n >\n {{ monthName }}\n </button>\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-1 text-sm font-semibold text-slate-900 transition hover:bg-slate-100\"\n (click)=\"showYearPicker($event)\"\n >\n {{ yearLabel }}\n </button>\n </div>\n @if (calendarView !== 'months') {\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-900\"\n aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u043C\u0456\u0441\u044F\u0446\u044C\"\n (click)=\"changeMonth(1, $event)\"\n >\n <lucide-icon [img]=\"nextMonthIcon\" class=\"h-4 w-4\"></lucide-icon>\n </button>\n }\n </div>\n\n @if (calendarView === 'days') {\n <div class=\"mb-2 grid grid-cols-7 text-center text-xs font-medium text-slate-400\">\n @for (weekday of ['\u041F\u043D', '\u0412\u0442', '\u0421\u0440', '\u0427\u0442', '\u041F\u0442', '\u0421\u0431', '\u041D\u0434']; track weekday) {\n <span class=\"py-1\">{{ weekday }}</span>\n }\n </div>\n\n <div class=\"grid grid-cols-7 gap-1\">\n @for (day of calendarDays; track day.date) {\n <button\n type=\"button\"\n class=\"aspect-square rounded-lg text-sm transition hover:bg-slate-100 hover:text-slate-900\"\n [class.text-slate-300]=\"!day.currentMonth\"\n [class.text-slate-700]=\"day.currentMonth && !isSelected(day.date)\"\n [class.bg-slate-900]=\"isSelected(day.date)\"\n [class.text-white]=\"isSelected(day.date)\"\n (click)=\"selectDate(day.date, $event)\"\n >\n {{ day.day }}\n </button>\n }\n </div>\n } @else if (calendarView === 'months') {\n <div class=\"grid grid-cols-3 gap-2\">\n @for (month of monthOptions; track month.value) {\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-3 text-sm capitalize text-slate-700 transition hover:bg-slate-100 hover:text-slate-900\"\n [class.bg-slate-900]=\"isSelectedMonth(month.value)\"\n [class.text-white]=\"isSelectedMonth(month.value)\"\n (click)=\"selectMonth(month.value, $event)\"\n >\n {{ month.label }}\n </button>\n }\n </div>\n } @else {\n <div class=\"grid grid-cols-3 gap-2\">\n @for (year of yearOptions; track year) {\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-3 text-sm text-slate-700 transition hover:bg-slate-100 hover:text-slate-900\"\n [class.bg-slate-900]=\"isSelectedYear(year)\"\n [class.text-white]=\"isSelectedYear(year)\"\n (click)=\"selectYear(year, $event)\"\n >\n {{ year }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1146
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDateControlComponent), multi: true },
1147
+ ], ngImport: i0, template: "\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <div class=\"relative\">\n <input\n type=\"text\"\n [attr.placeholder]=\"placeholder ?? '\u0414\u0414.\u041C\u041C.\u0420\u0420\u0420\u0420'\"\n maxlength=\"10\"\n inputmode=\"numeric\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"displayValue\"\n (click)=\"openCalendar($event)\"\n (keydown)=\"allowDateKey($event)\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n <lucide-icon\n [img]=\"calendarIcon\"\n class=\"pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400\"\n aria-hidden=\"true\"\n ></lucide-icon>\n\n @if (isCalendarOpen) {\n <div\n class=\"absolute left-0 top-full z-50 mt-2 w-[min(20rem,calc(100vw-2rem))] rounded-xl border border-slate-200 bg-white p-4 shadow-xl\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"mb-4 flex items-center justify-between\" [class.justify-center]=\"calendarView === 'months'\">\n @if (calendarView !== 'months') {\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-900\"\n aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0456\u0439 \u043C\u0456\u0441\u044F\u0446\u044C\"\n (click)=\"changeMonth(-1, $event)\"\n >\n <lucide-icon [img]=\"previousMonthIcon\" class=\"h-4 w-4\"></lucide-icon>\n </button>\n }\n <div class=\"flex items-center gap-1\">\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-1 text-sm font-semibold capitalize text-slate-900 transition hover:bg-slate-100\"\n (click)=\"showMonthPicker($event)\"\n >\n {{ monthName }}\n </button>\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-1 text-sm font-semibold text-slate-900 transition hover:bg-slate-100\"\n (click)=\"showYearPicker($event)\"\n >\n {{ yearLabel }}\n </button>\n </div>\n @if (calendarView !== 'months') {\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-900\"\n aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u043C\u0456\u0441\u044F\u0446\u044C\"\n (click)=\"changeMonth(1, $event)\"\n >\n <lucide-icon [img]=\"nextMonthIcon\" class=\"h-4 w-4\"></lucide-icon>\n </button>\n }\n </div>\n\n @if (calendarView === 'days') {\n <div class=\"mb-2 grid grid-cols-7 text-center text-xs font-medium text-slate-400\">\n @for (weekday of ['\u041F\u043D', '\u0412\u0442', '\u0421\u0440', '\u0427\u0442', '\u041F\u0442', '\u0421\u0431', '\u041D\u0434']; track weekday) {\n <span class=\"py-1\">{{ weekday }}</span>\n }\n </div>\n\n <div class=\"grid grid-cols-7 gap-1\">\n @for (day of calendarDays; track day.date) {\n <button\n type=\"button\"\n class=\"aspect-square rounded-lg text-sm transition hover:bg-slate-100 hover:text-slate-900\"\n [class.text-slate-300]=\"!day.currentMonth\"\n [class.text-slate-700]=\"day.currentMonth && !isSelected(day.date)\"\n [class.bg-slate-900]=\"isSelected(day.date)\"\n [class.text-white]=\"isSelected(day.date)\"\n (click)=\"selectDate(day.date, $event)\"\n >\n {{ day.day }}\n </button>\n }\n </div>\n } @else if (calendarView === 'months') {\n <div class=\"grid grid-cols-3 gap-2\">\n @for (month of monthOptions; track month.value) {\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-3 text-sm capitalize text-slate-700 transition hover:bg-slate-100 hover:text-slate-900\"\n [class.bg-slate-900]=\"isSelectedMonth(month.value)\"\n [class.text-white]=\"isSelectedMonth(month.value)\"\n (click)=\"selectMonth(month.value, $event)\"\n >\n {{ month.label }}\n </button>\n }\n </div>\n } @else {\n <div class=\"grid grid-cols-3 gap-2\">\n @for (year of yearOptions; track year) {\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-3 text-sm text-slate-700 transition hover:bg-slate-100 hover:text-slate-900\"\n [class.bg-slate-900]=\"isSelectedYear(year)\"\n [class.text-white]=\"isSelectedYear(year)\"\n (click)=\"selectYear(year, $event)\"\n >\n {{ year }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n </div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
978
1148
  }
979
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDateComponent, decorators: [{
1149
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDateControlComponent, decorators: [{
980
1150
  type: Component,
981
- args: [{ selector: 'input-date', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1151
+ args: [{ selector: 'input-date-control', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
982
1152
  {
983
1153
  provide: NG_VALUE_ACCESSOR,
984
- useExisting: forwardRef(() => InputDateComponent),
1154
+ useExisting: forwardRef(() => InputDateControlComponent),
985
1155
  multi: true,
986
1156
  },
987
- { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDateComponent), multi: true },
988
- ], template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <div class=\"relative\">\n <input\n type=\"text\"\n [attr.placeholder]=\"placeholder ?? '\u0414\u0414.\u041C\u041C.\u0420\u0420\u0420\u0420'\"\n maxlength=\"10\"\n inputmode=\"numeric\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"displayValue\"\n (click)=\"openCalendar($event)\"\n (keydown)=\"allowDateKey($event)\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n <lucide-icon\n [img]=\"calendarIcon\"\n class=\"pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400\"\n aria-hidden=\"true\"\n ></lucide-icon>\n\n @if (isCalendarOpen) {\n <div\n class=\"absolute left-0 top-full z-50 mt-2 w-[min(20rem,calc(100vw-2rem))] rounded-xl border border-slate-200 bg-white p-4 shadow-xl\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"mb-4 flex items-center justify-between\" [class.justify-center]=\"calendarView === 'months'\">\n @if (calendarView !== 'months') {\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-900\"\n aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0456\u0439 \u043C\u0456\u0441\u044F\u0446\u044C\"\n (click)=\"changeMonth(-1, $event)\"\n >\n <lucide-icon [img]=\"previousMonthIcon\" class=\"h-4 w-4\"></lucide-icon>\n </button>\n }\n <div class=\"flex items-center gap-1\">\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-1 text-sm font-semibold capitalize text-slate-900 transition hover:bg-slate-100\"\n (click)=\"showMonthPicker($event)\"\n >\n {{ monthName }}\n </button>\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-1 text-sm font-semibold text-slate-900 transition hover:bg-slate-100\"\n (click)=\"showYearPicker($event)\"\n >\n {{ yearLabel }}\n </button>\n </div>\n @if (calendarView !== 'months') {\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-900\"\n aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u043C\u0456\u0441\u044F\u0446\u044C\"\n (click)=\"changeMonth(1, $event)\"\n >\n <lucide-icon [img]=\"nextMonthIcon\" class=\"h-4 w-4\"></lucide-icon>\n </button>\n }\n </div>\n\n @if (calendarView === 'days') {\n <div class=\"mb-2 grid grid-cols-7 text-center text-xs font-medium text-slate-400\">\n @for (weekday of ['\u041F\u043D', '\u0412\u0442', '\u0421\u0440', '\u0427\u0442', '\u041F\u0442', '\u0421\u0431', '\u041D\u0434']; track weekday) {\n <span class=\"py-1\">{{ weekday }}</span>\n }\n </div>\n\n <div class=\"grid grid-cols-7 gap-1\">\n @for (day of calendarDays; track day.date) {\n <button\n type=\"button\"\n class=\"aspect-square rounded-lg text-sm transition hover:bg-slate-100 hover:text-slate-900\"\n [class.text-slate-300]=\"!day.currentMonth\"\n [class.text-slate-700]=\"day.currentMonth && !isSelected(day.date)\"\n [class.bg-slate-900]=\"isSelected(day.date)\"\n [class.text-white]=\"isSelected(day.date)\"\n (click)=\"selectDate(day.date, $event)\"\n >\n {{ day.day }}\n </button>\n }\n </div>\n } @else if (calendarView === 'months') {\n <div class=\"grid grid-cols-3 gap-2\">\n @for (month of monthOptions; track month.value) {\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-3 text-sm capitalize text-slate-700 transition hover:bg-slate-100 hover:text-slate-900\"\n [class.bg-slate-900]=\"isSelectedMonth(month.value)\"\n [class.text-white]=\"isSelectedMonth(month.value)\"\n (click)=\"selectMonth(month.value, $event)\"\n >\n {{ month.label }}\n </button>\n }\n </div>\n } @else {\n <div class=\"grid grid-cols-3 gap-2\">\n @for (year of yearOptions; track year) {\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-3 text-sm text-slate-700 transition hover:bg-slate-100 hover:text-slate-900\"\n [class.bg-slate-900]=\"isSelectedYear(year)\"\n [class.text-white]=\"isSelectedYear(year)\"\n (click)=\"selectYear(year, $event)\"\n >\n {{ year }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n" }]
1157
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDateControlComponent), multi: true },
1158
+ ], template: "\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <div class=\"relative\">\n <input\n type=\"text\"\n [attr.placeholder]=\"placeholder ?? '\u0414\u0414.\u041C\u041C.\u0420\u0420\u0420\u0420'\"\n maxlength=\"10\"\n inputmode=\"numeric\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"displayValue\"\n (click)=\"openCalendar($event)\"\n (keydown)=\"allowDateKey($event)\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n <lucide-icon\n [img]=\"calendarIcon\"\n class=\"pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400\"\n aria-hidden=\"true\"\n ></lucide-icon>\n\n @if (isCalendarOpen) {\n <div\n class=\"absolute left-0 top-full z-50 mt-2 w-[min(20rem,calc(100vw-2rem))] rounded-xl border border-slate-200 bg-white p-4 shadow-xl\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"mb-4 flex items-center justify-between\" [class.justify-center]=\"calendarView === 'months'\">\n @if (calendarView !== 'months') {\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-900\"\n aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0456\u0439 \u043C\u0456\u0441\u044F\u0446\u044C\"\n (click)=\"changeMonth(-1, $event)\"\n >\n <lucide-icon [img]=\"previousMonthIcon\" class=\"h-4 w-4\"></lucide-icon>\n </button>\n }\n <div class=\"flex items-center gap-1\">\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-1 text-sm font-semibold capitalize text-slate-900 transition hover:bg-slate-100\"\n (click)=\"showMonthPicker($event)\"\n >\n {{ monthName }}\n </button>\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-1 text-sm font-semibold text-slate-900 transition hover:bg-slate-100\"\n (click)=\"showYearPicker($event)\"\n >\n {{ yearLabel }}\n </button>\n </div>\n @if (calendarView !== 'months') {\n <button\n type=\"button\"\n class=\"rounded-lg p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-900\"\n aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u043C\u0456\u0441\u044F\u0446\u044C\"\n (click)=\"changeMonth(1, $event)\"\n >\n <lucide-icon [img]=\"nextMonthIcon\" class=\"h-4 w-4\"></lucide-icon>\n </button>\n }\n </div>\n\n @if (calendarView === 'days') {\n <div class=\"mb-2 grid grid-cols-7 text-center text-xs font-medium text-slate-400\">\n @for (weekday of ['\u041F\u043D', '\u0412\u0442', '\u0421\u0440', '\u0427\u0442', '\u041F\u0442', '\u0421\u0431', '\u041D\u0434']; track weekday) {\n <span class=\"py-1\">{{ weekday }}</span>\n }\n </div>\n\n <div class=\"grid grid-cols-7 gap-1\">\n @for (day of calendarDays; track day.date) {\n <button\n type=\"button\"\n class=\"aspect-square rounded-lg text-sm transition hover:bg-slate-100 hover:text-slate-900\"\n [class.text-slate-300]=\"!day.currentMonth\"\n [class.text-slate-700]=\"day.currentMonth && !isSelected(day.date)\"\n [class.bg-slate-900]=\"isSelected(day.date)\"\n [class.text-white]=\"isSelected(day.date)\"\n (click)=\"selectDate(day.date, $event)\"\n >\n {{ day.day }}\n </button>\n }\n </div>\n } @else if (calendarView === 'months') {\n <div class=\"grid grid-cols-3 gap-2\">\n @for (month of monthOptions; track month.value) {\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-3 text-sm capitalize text-slate-700 transition hover:bg-slate-100 hover:text-slate-900\"\n [class.bg-slate-900]=\"isSelectedMonth(month.value)\"\n [class.text-white]=\"isSelectedMonth(month.value)\"\n (click)=\"selectMonth(month.value, $event)\"\n >\n {{ month.label }}\n </button>\n }\n </div>\n } @else {\n <div class=\"grid grid-cols-3 gap-2\">\n @for (year of yearOptions; track year) {\n <button\n type=\"button\"\n class=\"rounded-lg px-2 py-3 text-sm text-slate-700 transition hover:bg-slate-100 hover:text-slate-900\"\n [class.bg-slate-900]=\"isSelectedYear(year)\"\n [class.text-white]=\"isSelectedYear(year)\"\n (click)=\"selectYear(year, $event)\"\n >\n {{ year }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n\n </div>\n" }]
989
1159
  }], propDecorators: { label: [{
990
1160
  type: Input
991
1161
  }], hint: [{
@@ -1000,8 +1170,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1000
1170
  type: Input
1001
1171
  }], required: [{
1002
1172
  type: Input
1003
- }], fullWidth: [{
1004
- type: Input
1005
1173
  }], autocomplete: [{
1006
1174
  type: Input
1007
1175
  }], pattern: [{
@@ -1010,6 +1178,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1010
1178
  type: Input
1011
1179
  }], actions: [{
1012
1180
  type: Input
1181
+ }], disabled: [{
1182
+ type: Input
1013
1183
  }], actionTriggered: [{
1014
1184
  type: Output
1015
1185
  }], onDocumentClick: [{
@@ -1020,7 +1190,68 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1020
1190
  args: ['document:keydown.escape']
1021
1191
  }] } });
1022
1192
 
1023
- class InputDatetimeComponent {
1193
+ class InputDateComponent {
1194
+ control;
1195
+ label;
1196
+ hint;
1197
+ error;
1198
+ placeholder;
1199
+ name;
1200
+ id;
1201
+ required = false;
1202
+ disabled = false;
1203
+ autocomplete = 'off';
1204
+ pattern;
1205
+ rows = 4;
1206
+ actions = [];
1207
+ actionTriggered = new EventEmitter();
1208
+ pendingValue = '';
1209
+ onChange = () => undefined;
1210
+ onTouched = () => undefined;
1211
+ ngAfterViewInit() { this.control?.registerOnChange((value) => this.onChange(value)); this.control?.registerOnTouched(() => this.onTouched()); this.control?.writeValue(this.pendingValue); }
1212
+ writeValue(value) { this.pendingValue = value; this.control?.writeValue(value); }
1213
+ registerOnChange(fn) { this.onChange = fn; }
1214
+ registerOnTouched(fn) { this.onTouched = fn; }
1215
+ setDisabledState(value) { this.disabled = value; }
1216
+ validate(control) { return validateFormControl(control, this.required, this.pattern); }
1217
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1218
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputDateComponent, isStandalone: true, selector: "input-date", inputs: { label: "label", hint: "hint", error: "error", placeholder: "placeholder", name: "name", id: "id", required: "required", disabled: "disabled", autocomplete: "autocomplete", pattern: "pattern", rows: "rows", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputDateComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDateComponent), multi: true }], viewQueries: [{ propertyName: "control", first: true, predicate: InputDateControlComponent, descendants: true }], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"id\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"actionTriggered.emit($event)\">\n <input-date-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"id\" [required]=\"required\" [disabled]=\"disabled\" [autocomplete]=\"autocomplete\" [pattern]=\"pattern\" [rows]=\"rows\" />\n</field-frame>\n", dependencies: [{ kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputDateControlComponent, selector: "input-date-control", inputs: ["label", "hint", "placeholder", "name", "id", "rows", "required", "autocomplete", "pattern", "error", "actions", "disabled"], outputs: ["actionTriggered"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1219
+ }
1220
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDateComponent, decorators: [{
1221
+ type: Component,
1222
+ args: [{ selector: 'input-date', standalone: true, imports: [FieldFrameComponent, InputDateControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputDateComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDateComponent), multi: true }], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"id\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"actionTriggered.emit($event)\">\n <input-date-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"id\" [required]=\"required\" [disabled]=\"disabled\" [autocomplete]=\"autocomplete\" [pattern]=\"pattern\" [rows]=\"rows\" />\n</field-frame>\n" }]
1223
+ }], propDecorators: { control: [{
1224
+ type: ViewChild,
1225
+ args: [InputDateControlComponent]
1226
+ }], label: [{
1227
+ type: Input
1228
+ }], hint: [{
1229
+ type: Input
1230
+ }], error: [{
1231
+ type: Input
1232
+ }], placeholder: [{
1233
+ type: Input
1234
+ }], name: [{
1235
+ type: Input
1236
+ }], id: [{
1237
+ type: Input
1238
+ }], required: [{
1239
+ type: Input
1240
+ }], disabled: [{
1241
+ type: Input
1242
+ }], autocomplete: [{
1243
+ type: Input
1244
+ }], pattern: [{
1245
+ type: Input
1246
+ }], rows: [{
1247
+ type: Input
1248
+ }], actions: [{
1249
+ type: Input
1250
+ }], actionTriggered: [{
1251
+ type: Output
1252
+ }] } });
1253
+
1254
+ class InputDatetimeControlComponent {
1024
1255
  cdr = inject(ChangeDetectorRef);
1025
1256
  popupService = inject(UiPopupService);
1026
1257
  closeActivePopup = () => this.closeCalendar();
@@ -1031,7 +1262,6 @@ class InputDatetimeComponent {
1031
1262
  id;
1032
1263
  rows = 4;
1033
1264
  required = false;
1034
- fullWidth = false;
1035
1265
  autocomplete = 'off';
1036
1266
  pattern;
1037
1267
  error;
@@ -1048,7 +1278,7 @@ class InputDatetimeComponent {
1048
1278
  selectedHour = 12;
1049
1279
  selectedMinute = 0;
1050
1280
  static nextId = 0;
1051
- generatedId = `input-datetime-${InputDatetimeComponent.nextId++}`;
1281
+ generatedId = `input-datetime-${InputDatetimeControlComponent.nextId++}`;
1052
1282
  actionTriggered = new EventEmitter();
1053
1283
  onChange = () => undefined;
1054
1284
  onTouched = () => undefined;
@@ -1118,18 +1348,18 @@ class InputDatetimeComponent {
1118
1348
  startOfMonth(date) { return new Date(date.getFullYear(), date.getMonth(), 1); }
1119
1349
  parseDate(value) { const [year, month, day] = value.split('-').map(Number); return year && month && day ? new Date(year, month - 1, day) : null; }
1120
1350
  toDateValue(date) { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; }
1121
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDatetimeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1122
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputDatetimeComponent, isStandalone: true, selector: "input-datetime", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", fullWidth: "fullWidth", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, host: { listeners: { "document:click": "onDocumentClick()", "document:keydown.escape": "onEscape()" } }, providers: [
1123
- { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputDatetimeComponent), multi: true },
1124
- { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDatetimeComponent), multi: true },
1125
- ], ngImport: i0, template: "<div class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\" [class.w-full]=\"fullWidth\">\n @if (label) { <span class=\"text-[0.7rem] font-semibold text-slate-600\">{{ label }} @if (required) { <span class=\"text-rose-500\"> *</span> }</span> }\n <div class=\"flex items-end gap-2\"><div class=\"min-w-0 flex-1\"><div class=\"relative\">\n <input type=\"text\" [attr.placeholder]=\"placeholder ?? '\u0414\u0414.\u041C\u041C.\u0420\u0420\u0420\u0420 \u0413\u0413:\u0425\u0425'\" maxlength=\"16\" inputmode=\"numeric\" [attr.name]=\"name\" [attr.autocomplete]=\"autocomplete\" [disabled]=\"disabled\" class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" [attr.id]=\"controlId\" [value]=\"displayValue\" (click)=\"openCalendar($event)\" (keydown)=\"allowDateKey($event)\" (input)=\"onValueChange($any($event.target).value)\" (blur)=\"onTouched()\" />\n <lucide-icon [img]=\"calendarIcon\" class=\"pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400\"></lucide-icon>\n @if (isCalendarOpen) { <div class=\"absolute left-0 top-full z-50 mt-2 w-[min(22rem,calc(100vw-2rem))] rounded-xl border border-slate-200 bg-white p-4 shadow-xl\" (click)=\"$event.stopPropagation()\">\n <div class=\"mb-4 flex items-center justify-between\" [class.justify-center]=\"calendarView === 'months'\">\n @if (calendarView !== 'months') { <button type=\"button\" class=\"rounded-lg p-2 text-slate-500 hover:bg-slate-100\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0456\u0439 \u043F\u0435\u0440\u0456\u043E\u0434\" (click)=\"changeMonth(-1, $event)\"><lucide-icon [img]=\"previousMonthIcon\" class=\"h-4 w-4\"></lucide-icon></button> }\n <div class=\"flex items-center gap-1\"><button type=\"button\" class=\"rounded-lg px-2 py-1 text-sm font-semibold capitalize text-slate-900 hover:bg-slate-100\" (click)=\"showMonthPicker($event)\">{{ monthName }}</button><button type=\"button\" class=\"rounded-lg px-2 py-1 text-sm font-semibold text-slate-900 hover:bg-slate-100\" (click)=\"showYearPicker($event)\">{{ yearLabel }}</button></div>\n @if (calendarView !== 'months') { <button type=\"button\" class=\"rounded-lg p-2 text-slate-500 hover:bg-slate-100\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u043F\u0435\u0440\u0456\u043E\u0434\" (click)=\"changeMonth(1, $event)\"><lucide-icon [img]=\"nextMonthIcon\" class=\"h-4 w-4\"></lucide-icon></button> }\n </div>\n @if (calendarView === 'days') {\n <div class=\"mb-2 grid grid-cols-7 text-center text-xs font-medium text-slate-400\">@for (weekday of ['\u041F\u043D','\u0412\u0442','\u0421\u0440','\u0427\u0442','\u041F\u0442','\u0421\u0431','\u041D\u0434']; track weekday) { <span class=\"py-1\">{{ weekday }}</span> }</div>\n <div class=\"grid grid-cols-7 gap-1\">@for (day of calendarDays; track day.date) { <button type=\"button\" class=\"aspect-square rounded-lg text-sm hover:bg-slate-100 hover:text-slate-900\" [class.text-slate-300]=\"!day.currentMonth\" [class.text-slate-700]=\"day.currentMonth && !isSelected(day.date)\" [class.bg-slate-900]=\"isSelected(day.date)\" [class.text-white]=\"isSelected(day.date)\" (click)=\"selectDate(day.date, $event)\">{{ day.day }}</button> }</div>\n <div class=\"mt-4 grid grid-cols-2 gap-2 border-t border-slate-100 pt-3\"><label class=\"text-[0.65rem] font-medium text-slate-500\">\u0413\u043E\u0434\u0438\u043D\u0438<input type=\"number\" min=\"0\" max=\"23\" [value]=\"selectedHour\" (input)=\"selectedHour = +$any($event.target).value; onTimeChange()\" class=\"mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" /></label><label class=\"text-[0.65rem] font-medium text-slate-500\">\u0425\u0432\u0438\u043B\u0438\u043D\u0438<input type=\"number\" min=\"0\" max=\"59\" [value]=\"selectedMinute\" (input)=\"selectedMinute = +$any($event.target).value; onTimeChange()\" class=\"mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" /></label></div>\n } @else if (calendarView === 'months') { <div class=\"grid grid-cols-3 gap-2\">@for (month of monthOptions; track month.value) { <button type=\"button\" class=\"rounded-lg px-2 py-3 text-sm capitalize text-slate-700 hover:bg-slate-100 hover:text-slate-900\" [class.bg-slate-900]=\"isSelectedMonth(month.value)\" [class.text-white]=\"isSelectedMonth(month.value)\" (click)=\"selectMonth(month.value, $event)\">{{ month.label }}</button> }</div>\n } @else { <div class=\"grid grid-cols-3 gap-2\">@for (year of yearOptions; track year) { <button type=\"button\" class=\"rounded-lg px-2 py-3 text-sm text-slate-700 hover:bg-slate-100 hover:text-slate-900\" [class.bg-slate-900]=\"isSelectedYear(year)\" [class.text-white]=\"isSelectedYear(year)\" (click)=\"selectYear(year, $event)\">{{ year }}</button> }</div> }\n </div> }\n </div></div>\n @if (actions.length) { <div class=\"flex flex-none items-center gap-2 pb-0.5\">@for (action of actions; track action.id) { <ui-button type=\"button\" [variant]=\"action.variant ?? 'secondary'\" [icon]=\"action.icon || ''\" size=\"sm\" [disabled]=\"disabled\" (click)=\"triggerAction(action)\">{{ action.label }}</ui-button> }</div> }\n </div>\n @if (hint) { <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p> } @if (error) { <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p> }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1351
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDatetimeControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1352
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputDatetimeControlComponent, isStandalone: true, selector: "input-datetime-control", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions", disabled: "disabled" }, outputs: { actionTriggered: "actionTriggered" }, host: { listeners: { "document:click": "onDocumentClick()", "document:keydown.escape": "onEscape()" } }, providers: [
1353
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputDatetimeControlComponent), multi: true },
1354
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDatetimeControlComponent), multi: true },
1355
+ ], ngImport: i0, template: " <div class=\"flex items-end gap-2\"><div class=\"min-w-0 flex-1\"><div class=\"relative\">\n <input type=\"text\" [attr.placeholder]=\"placeholder ?? '\u0414\u0414.\u041C\u041C.\u0420\u0420\u0420\u0420 \u0413\u0413:\u0425\u0425'\" maxlength=\"16\" inputmode=\"numeric\" [attr.name]=\"name\" [attr.autocomplete]=\"autocomplete\" [disabled]=\"disabled\" class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" [attr.id]=\"controlId\" [value]=\"displayValue\" (click)=\"openCalendar($event)\" (keydown)=\"allowDateKey($event)\" (input)=\"onValueChange($any($event.target).value)\" (blur)=\"onTouched()\" />\n <lucide-icon [img]=\"calendarIcon\" class=\"pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400\"></lucide-icon>\n @if (isCalendarOpen) { <div class=\"absolute left-0 top-full z-50 mt-2 w-[min(22rem,calc(100vw-2rem))] rounded-xl border border-slate-200 bg-white p-4 shadow-xl\" (click)=\"$event.stopPropagation()\">\n <div class=\"mb-4 flex items-center justify-between\" [class.justify-center]=\"calendarView === 'months'\">\n @if (calendarView !== 'months') { <button type=\"button\" class=\"rounded-lg p-2 text-slate-500 hover:bg-slate-100\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0456\u0439 \u043F\u0435\u0440\u0456\u043E\u0434\" (click)=\"changeMonth(-1, $event)\"><lucide-icon [img]=\"previousMonthIcon\" class=\"h-4 w-4\"></lucide-icon></button> }\n <div class=\"flex items-center gap-1\"><button type=\"button\" class=\"rounded-lg px-2 py-1 text-sm font-semibold capitalize text-slate-900 hover:bg-slate-100\" (click)=\"showMonthPicker($event)\">{{ monthName }}</button><button type=\"button\" class=\"rounded-lg px-2 py-1 text-sm font-semibold text-slate-900 hover:bg-slate-100\" (click)=\"showYearPicker($event)\">{{ yearLabel }}</button></div>\n @if (calendarView !== 'months') { <button type=\"button\" class=\"rounded-lg p-2 text-slate-500 hover:bg-slate-100\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u043F\u0435\u0440\u0456\u043E\u0434\" (click)=\"changeMonth(1, $event)\"><lucide-icon [img]=\"nextMonthIcon\" class=\"h-4 w-4\"></lucide-icon></button> }\n </div>\n @if (calendarView === 'days') {\n <div class=\"mb-2 grid grid-cols-7 text-center text-xs font-medium text-slate-400\">@for (weekday of ['\u041F\u043D','\u0412\u0442','\u0421\u0440','\u0427\u0442','\u041F\u0442','\u0421\u0431','\u041D\u0434']; track weekday) { <span class=\"py-1\">{{ weekday }}</span> }</div>\n <div class=\"grid grid-cols-7 gap-1\">@for (day of calendarDays; track day.date) { <button type=\"button\" class=\"aspect-square rounded-lg text-sm hover:bg-slate-100 hover:text-slate-900\" [class.text-slate-300]=\"!day.currentMonth\" [class.text-slate-700]=\"day.currentMonth && !isSelected(day.date)\" [class.bg-slate-900]=\"isSelected(day.date)\" [class.text-white]=\"isSelected(day.date)\" (click)=\"selectDate(day.date, $event)\">{{ day.day }}</button> }</div>\n <div class=\"mt-4 grid grid-cols-2 gap-2 border-t border-slate-100 pt-3\"><label class=\"text-[0.65rem] font-medium text-slate-500\">\u0413\u043E\u0434\u0438\u043D\u0438<input type=\"number\" min=\"0\" max=\"23\" [value]=\"selectedHour\" (input)=\"selectedHour = +$any($event.target).value; onTimeChange()\" class=\"mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" /></label><label class=\"text-[0.65rem] font-medium text-slate-500\">\u0425\u0432\u0438\u043B\u0438\u043D\u0438<input type=\"number\" min=\"0\" max=\"59\" [value]=\"selectedMinute\" (input)=\"selectedMinute = +$any($event.target).value; onTimeChange()\" class=\"mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" /></label></div>\n } @else if (calendarView === 'months') { <div class=\"grid grid-cols-3 gap-2\">@for (month of monthOptions; track month.value) { <button type=\"button\" class=\"rounded-lg px-2 py-3 text-sm capitalize text-slate-700 hover:bg-slate-100 hover:text-slate-900\" [class.bg-slate-900]=\"isSelectedMonth(month.value)\" [class.text-white]=\"isSelectedMonth(month.value)\" (click)=\"selectMonth(month.value, $event)\">{{ month.label }}</button> }</div>\n } @else { <div class=\"grid grid-cols-3 gap-2\">@for (year of yearOptions; track year) { <button type=\"button\" class=\"rounded-lg px-2 py-3 text-sm text-slate-700 hover:bg-slate-100 hover:text-slate-900\" [class.bg-slate-900]=\"isSelectedYear(year)\" [class.text-white]=\"isSelectedYear(year)\" (click)=\"selectYear(year, $event)\">{{ year }}</button> }</div> }\n </div> }\n </div></div>\n </div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1126
1356
  }
1127
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDatetimeComponent, decorators: [{
1357
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDatetimeControlComponent, decorators: [{
1128
1358
  type: Component,
1129
- args: [{ selector: 'input-datetime', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1130
- { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputDatetimeComponent), multi: true },
1131
- { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDatetimeComponent), multi: true },
1132
- ], template: "<div class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\" [class.w-full]=\"fullWidth\">\n @if (label) { <span class=\"text-[0.7rem] font-semibold text-slate-600\">{{ label }} @if (required) { <span class=\"text-rose-500\"> *</span> }</span> }\n <div class=\"flex items-end gap-2\"><div class=\"min-w-0 flex-1\"><div class=\"relative\">\n <input type=\"text\" [attr.placeholder]=\"placeholder ?? '\u0414\u0414.\u041C\u041C.\u0420\u0420\u0420\u0420 \u0413\u0413:\u0425\u0425'\" maxlength=\"16\" inputmode=\"numeric\" [attr.name]=\"name\" [attr.autocomplete]=\"autocomplete\" [disabled]=\"disabled\" class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" [attr.id]=\"controlId\" [value]=\"displayValue\" (click)=\"openCalendar($event)\" (keydown)=\"allowDateKey($event)\" (input)=\"onValueChange($any($event.target).value)\" (blur)=\"onTouched()\" />\n <lucide-icon [img]=\"calendarIcon\" class=\"pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400\"></lucide-icon>\n @if (isCalendarOpen) { <div class=\"absolute left-0 top-full z-50 mt-2 w-[min(22rem,calc(100vw-2rem))] rounded-xl border border-slate-200 bg-white p-4 shadow-xl\" (click)=\"$event.stopPropagation()\">\n <div class=\"mb-4 flex items-center justify-between\" [class.justify-center]=\"calendarView === 'months'\">\n @if (calendarView !== 'months') { <button type=\"button\" class=\"rounded-lg p-2 text-slate-500 hover:bg-slate-100\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0456\u0439 \u043F\u0435\u0440\u0456\u043E\u0434\" (click)=\"changeMonth(-1, $event)\"><lucide-icon [img]=\"previousMonthIcon\" class=\"h-4 w-4\"></lucide-icon></button> }\n <div class=\"flex items-center gap-1\"><button type=\"button\" class=\"rounded-lg px-2 py-1 text-sm font-semibold capitalize text-slate-900 hover:bg-slate-100\" (click)=\"showMonthPicker($event)\">{{ monthName }}</button><button type=\"button\" class=\"rounded-lg px-2 py-1 text-sm font-semibold text-slate-900 hover:bg-slate-100\" (click)=\"showYearPicker($event)\">{{ yearLabel }}</button></div>\n @if (calendarView !== 'months') { <button type=\"button\" class=\"rounded-lg p-2 text-slate-500 hover:bg-slate-100\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u043F\u0435\u0440\u0456\u043E\u0434\" (click)=\"changeMonth(1, $event)\"><lucide-icon [img]=\"nextMonthIcon\" class=\"h-4 w-4\"></lucide-icon></button> }\n </div>\n @if (calendarView === 'days') {\n <div class=\"mb-2 grid grid-cols-7 text-center text-xs font-medium text-slate-400\">@for (weekday of ['\u041F\u043D','\u0412\u0442','\u0421\u0440','\u0427\u0442','\u041F\u0442','\u0421\u0431','\u041D\u0434']; track weekday) { <span class=\"py-1\">{{ weekday }}</span> }</div>\n <div class=\"grid grid-cols-7 gap-1\">@for (day of calendarDays; track day.date) { <button type=\"button\" class=\"aspect-square rounded-lg text-sm hover:bg-slate-100 hover:text-slate-900\" [class.text-slate-300]=\"!day.currentMonth\" [class.text-slate-700]=\"day.currentMonth && !isSelected(day.date)\" [class.bg-slate-900]=\"isSelected(day.date)\" [class.text-white]=\"isSelected(day.date)\" (click)=\"selectDate(day.date, $event)\">{{ day.day }}</button> }</div>\n <div class=\"mt-4 grid grid-cols-2 gap-2 border-t border-slate-100 pt-3\"><label class=\"text-[0.65rem] font-medium text-slate-500\">\u0413\u043E\u0434\u0438\u043D\u0438<input type=\"number\" min=\"0\" max=\"23\" [value]=\"selectedHour\" (input)=\"selectedHour = +$any($event.target).value; onTimeChange()\" class=\"mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" /></label><label class=\"text-[0.65rem] font-medium text-slate-500\">\u0425\u0432\u0438\u043B\u0438\u043D\u0438<input type=\"number\" min=\"0\" max=\"59\" [value]=\"selectedMinute\" (input)=\"selectedMinute = +$any($event.target).value; onTimeChange()\" class=\"mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" /></label></div>\n } @else if (calendarView === 'months') { <div class=\"grid grid-cols-3 gap-2\">@for (month of monthOptions; track month.value) { <button type=\"button\" class=\"rounded-lg px-2 py-3 text-sm capitalize text-slate-700 hover:bg-slate-100 hover:text-slate-900\" [class.bg-slate-900]=\"isSelectedMonth(month.value)\" [class.text-white]=\"isSelectedMonth(month.value)\" (click)=\"selectMonth(month.value, $event)\">{{ month.label }}</button> }</div>\n } @else { <div class=\"grid grid-cols-3 gap-2\">@for (year of yearOptions; track year) { <button type=\"button\" class=\"rounded-lg px-2 py-3 text-sm text-slate-700 hover:bg-slate-100 hover:text-slate-900\" [class.bg-slate-900]=\"isSelectedYear(year)\" [class.text-white]=\"isSelectedYear(year)\" (click)=\"selectYear(year, $event)\">{{ year }}</button> }</div> }\n </div> }\n </div></div>\n @if (actions.length) { <div class=\"flex flex-none items-center gap-2 pb-0.5\">@for (action of actions; track action.id) { <ui-button type=\"button\" [variant]=\"action.variant ?? 'secondary'\" [icon]=\"action.icon || ''\" size=\"sm\" [disabled]=\"disabled\" (click)=\"triggerAction(action)\">{{ action.label }}</ui-button> }</div> }\n </div>\n @if (hint) { <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p> } @if (error) { <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p> }\n</div>\n" }]
1359
+ args: [{ selector: 'input-datetime-control', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1360
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputDatetimeControlComponent), multi: true },
1361
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDatetimeControlComponent), multi: true },
1362
+ ], template: " <div class=\"flex items-end gap-2\"><div class=\"min-w-0 flex-1\"><div class=\"relative\">\n <input type=\"text\" [attr.placeholder]=\"placeholder ?? '\u0414\u0414.\u041C\u041C.\u0420\u0420\u0420\u0420 \u0413\u0413:\u0425\u0425'\" maxlength=\"16\" inputmode=\"numeric\" [attr.name]=\"name\" [attr.autocomplete]=\"autocomplete\" [disabled]=\"disabled\" class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" [attr.id]=\"controlId\" [value]=\"displayValue\" (click)=\"openCalendar($event)\" (keydown)=\"allowDateKey($event)\" (input)=\"onValueChange($any($event.target).value)\" (blur)=\"onTouched()\" />\n <lucide-icon [img]=\"calendarIcon\" class=\"pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400\"></lucide-icon>\n @if (isCalendarOpen) { <div class=\"absolute left-0 top-full z-50 mt-2 w-[min(22rem,calc(100vw-2rem))] rounded-xl border border-slate-200 bg-white p-4 shadow-xl\" (click)=\"$event.stopPropagation()\">\n <div class=\"mb-4 flex items-center justify-between\" [class.justify-center]=\"calendarView === 'months'\">\n @if (calendarView !== 'months') { <button type=\"button\" class=\"rounded-lg p-2 text-slate-500 hover:bg-slate-100\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0456\u0439 \u043F\u0435\u0440\u0456\u043E\u0434\" (click)=\"changeMonth(-1, $event)\"><lucide-icon [img]=\"previousMonthIcon\" class=\"h-4 w-4\"></lucide-icon></button> }\n <div class=\"flex items-center gap-1\"><button type=\"button\" class=\"rounded-lg px-2 py-1 text-sm font-semibold capitalize text-slate-900 hover:bg-slate-100\" (click)=\"showMonthPicker($event)\">{{ monthName }}</button><button type=\"button\" class=\"rounded-lg px-2 py-1 text-sm font-semibold text-slate-900 hover:bg-slate-100\" (click)=\"showYearPicker($event)\">{{ yearLabel }}</button></div>\n @if (calendarView !== 'months') { <button type=\"button\" class=\"rounded-lg p-2 text-slate-500 hover:bg-slate-100\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u043F\u0435\u0440\u0456\u043E\u0434\" (click)=\"changeMonth(1, $event)\"><lucide-icon [img]=\"nextMonthIcon\" class=\"h-4 w-4\"></lucide-icon></button> }\n </div>\n @if (calendarView === 'days') {\n <div class=\"mb-2 grid grid-cols-7 text-center text-xs font-medium text-slate-400\">@for (weekday of ['\u041F\u043D','\u0412\u0442','\u0421\u0440','\u0427\u0442','\u041F\u0442','\u0421\u0431','\u041D\u0434']; track weekday) { <span class=\"py-1\">{{ weekday }}</span> }</div>\n <div class=\"grid grid-cols-7 gap-1\">@for (day of calendarDays; track day.date) { <button type=\"button\" class=\"aspect-square rounded-lg text-sm hover:bg-slate-100 hover:text-slate-900\" [class.text-slate-300]=\"!day.currentMonth\" [class.text-slate-700]=\"day.currentMonth && !isSelected(day.date)\" [class.bg-slate-900]=\"isSelected(day.date)\" [class.text-white]=\"isSelected(day.date)\" (click)=\"selectDate(day.date, $event)\">{{ day.day }}</button> }</div>\n <div class=\"mt-4 grid grid-cols-2 gap-2 border-t border-slate-100 pt-3\"><label class=\"text-[0.65rem] font-medium text-slate-500\">\u0413\u043E\u0434\u0438\u043D\u0438<input type=\"number\" min=\"0\" max=\"23\" [value]=\"selectedHour\" (input)=\"selectedHour = +$any($event.target).value; onTimeChange()\" class=\"mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" /></label><label class=\"text-[0.65rem] font-medium text-slate-500\">\u0425\u0432\u0438\u043B\u0438\u043D\u0438<input type=\"number\" min=\"0\" max=\"59\" [value]=\"selectedMinute\" (input)=\"selectedMinute = +$any($event.target).value; onTimeChange()\" class=\"mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\" /></label></div>\n } @else if (calendarView === 'months') { <div class=\"grid grid-cols-3 gap-2\">@for (month of monthOptions; track month.value) { <button type=\"button\" class=\"rounded-lg px-2 py-3 text-sm capitalize text-slate-700 hover:bg-slate-100 hover:text-slate-900\" [class.bg-slate-900]=\"isSelectedMonth(month.value)\" [class.text-white]=\"isSelectedMonth(month.value)\" (click)=\"selectMonth(month.value, $event)\">{{ month.label }}</button> }</div>\n } @else { <div class=\"grid grid-cols-3 gap-2\">@for (year of yearOptions; track year) { <button type=\"button\" class=\"rounded-lg px-2 py-3 text-sm text-slate-700 hover:bg-slate-100 hover:text-slate-900\" [class.bg-slate-900]=\"isSelectedYear(year)\" [class.text-white]=\"isSelectedYear(year)\" (click)=\"selectYear(year, $event)\">{{ year }}</button> }</div> }\n </div> }\n </div></div>\n </div>\n" }]
1133
1363
  }], propDecorators: { label: [{
1134
1364
  type: Input
1135
1365
  }], hint: [{
@@ -1144,8 +1374,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1144
1374
  type: Input
1145
1375
  }], required: [{
1146
1376
  type: Input
1147
- }], fullWidth: [{
1148
- type: Input
1149
1377
  }], autocomplete: [{
1150
1378
  type: Input
1151
1379
  }], pattern: [{
@@ -1154,6 +1382,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1154
1382
  type: Input
1155
1383
  }], actions: [{
1156
1384
  type: Input
1385
+ }], disabled: [{
1386
+ type: Input
1157
1387
  }], actionTriggered: [{
1158
1388
  type: Output
1159
1389
  }], onDocumentClick: [{
@@ -1164,6 +1394,148 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1164
1394
  args: ['document:keydown.escape']
1165
1395
  }] } });
1166
1396
 
1397
+ class InputDatetimeComponent {
1398
+ control;
1399
+ label;
1400
+ hint;
1401
+ error;
1402
+ placeholder;
1403
+ name;
1404
+ id;
1405
+ required = false;
1406
+ disabled = false;
1407
+ autocomplete = 'off';
1408
+ pattern;
1409
+ rows = 4;
1410
+ actions = [];
1411
+ actionTriggered = new EventEmitter();
1412
+ pendingValue = '';
1413
+ onChange = () => undefined;
1414
+ onTouched = () => undefined;
1415
+ ngAfterViewInit() { this.control?.registerOnChange((value) => this.onChange(value)); this.control?.registerOnTouched(() => this.onTouched()); this.control?.writeValue(this.pendingValue); }
1416
+ writeValue(value) { this.pendingValue = value; this.control?.writeValue(value); }
1417
+ registerOnChange(fn) { this.onChange = fn; }
1418
+ registerOnTouched(fn) { this.onTouched = fn; }
1419
+ setDisabledState(value) { this.disabled = value; }
1420
+ validate(control) { return validateFormControl(control, this.required, this.pattern); }
1421
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDatetimeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1422
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputDatetimeComponent, isStandalone: true, selector: "input-datetime", inputs: { label: "label", hint: "hint", error: "error", placeholder: "placeholder", name: "name", id: "id", required: "required", disabled: "disabled", autocomplete: "autocomplete", pattern: "pattern", rows: "rows", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputDatetimeComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDatetimeComponent), multi: true }], viewQueries: [{ propertyName: "control", first: true, predicate: InputDatetimeControlComponent, descendants: true }], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"id\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"actionTriggered.emit($event)\">\n <input-datetime-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"id\" [required]=\"required\" [disabled]=\"disabled\" [autocomplete]=\"autocomplete\" [pattern]=\"pattern\" [rows]=\"rows\" />\n</field-frame>\n", dependencies: [{ kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputDatetimeControlComponent, selector: "input-datetime-control", inputs: ["label", "hint", "placeholder", "name", "id", "rows", "required", "autocomplete", "pattern", "error", "actions", "disabled"], outputs: ["actionTriggered"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1423
+ }
1424
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDatetimeComponent, decorators: [{
1425
+ type: Component,
1426
+ args: [{ selector: 'input-datetime', standalone: true, imports: [FieldFrameComponent, InputDatetimeControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputDatetimeComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDatetimeComponent), multi: true }], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"id\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"actionTriggered.emit($event)\">\n <input-datetime-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"id\" [required]=\"required\" [disabled]=\"disabled\" [autocomplete]=\"autocomplete\" [pattern]=\"pattern\" [rows]=\"rows\" />\n</field-frame>\n" }]
1427
+ }], propDecorators: { control: [{
1428
+ type: ViewChild,
1429
+ args: [InputDatetimeControlComponent]
1430
+ }], label: [{
1431
+ type: Input
1432
+ }], hint: [{
1433
+ type: Input
1434
+ }], error: [{
1435
+ type: Input
1436
+ }], placeholder: [{
1437
+ type: Input
1438
+ }], name: [{
1439
+ type: Input
1440
+ }], id: [{
1441
+ type: Input
1442
+ }], required: [{
1443
+ type: Input
1444
+ }], disabled: [{
1445
+ type: Input
1446
+ }], autocomplete: [{
1447
+ type: Input
1448
+ }], pattern: [{
1449
+ type: Input
1450
+ }], rows: [{
1451
+ type: Input
1452
+ }], actions: [{
1453
+ type: Input
1454
+ }], actionTriggered: [{
1455
+ type: Output
1456
+ }] } });
1457
+
1458
+ class InputTextControlComponent {
1459
+ placeholder;
1460
+ name;
1461
+ id;
1462
+ type = 'text';
1463
+ element = 'input';
1464
+ rows = 4;
1465
+ autocomplete;
1466
+ pattern;
1467
+ disabled = false;
1468
+ value = '';
1469
+ valueChange = new EventEmitter();
1470
+ touched = new EventEmitter();
1471
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1472
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputTextControlComponent, isStandalone: true, selector: "input-text-control", inputs: { placeholder: "placeholder", name: "name", id: "id", type: "type", element: "element", rows: "rows", autocomplete: "autocomplete", pattern: "pattern", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange", touched: "touched" }, ngImport: i0, template: "@if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"id\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"valueChange.emit($any($event.target).value)\"\n (blur)=\"touched.emit()\"\n ></textarea>\n} @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"id\"\n [value]=\"value\"\n (input)=\"valueChange.emit($any($event.target).value)\"\n (blur)=\"touched.emit()\"\n />\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1473
+ }
1474
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextControlComponent, decorators: [{
1475
+ type: Component,
1476
+ args: [{ selector: 'input-text-control', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"id\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"valueChange.emit($any($event.target).value)\"\n (blur)=\"touched.emit()\"\n ></textarea>\n} @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"id\"\n [value]=\"value\"\n (input)=\"valueChange.emit($any($event.target).value)\"\n (blur)=\"touched.emit()\"\n />\n}\n" }]
1477
+ }], propDecorators: { placeholder: [{
1478
+ type: Input
1479
+ }], name: [{
1480
+ type: Input
1481
+ }], id: [{
1482
+ type: Input
1483
+ }], type: [{
1484
+ type: Input
1485
+ }], element: [{
1486
+ type: Input
1487
+ }], rows: [{
1488
+ type: Input
1489
+ }], autocomplete: [{
1490
+ type: Input
1491
+ }], pattern: [{
1492
+ type: Input
1493
+ }], disabled: [{
1494
+ type: Input
1495
+ }], value: [{
1496
+ type: Input
1497
+ }], valueChange: [{
1498
+ type: Output
1499
+ }], touched: [{
1500
+ type: Output
1501
+ }] } });
1502
+
1503
+ class InputEmailControlComponent {
1504
+ placeholder;
1505
+ name;
1506
+ id;
1507
+ autocomplete;
1508
+ pattern;
1509
+ disabled = false;
1510
+ value = '';
1511
+ valueChange = new EventEmitter();
1512
+ touched = new EventEmitter();
1513
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputEmailControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1514
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputEmailControlComponent, isStandalone: true, selector: "input-email-control", inputs: { placeholder: "placeholder", name: "name", id: "id", autocomplete: "autocomplete", pattern: "pattern", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange", touched: "touched" }, ngImport: i0, template: `<input-text-control [placeholder]="placeholder" [name]="name" [id]="id" type="email" [autocomplete]="autocomplete" [pattern]="pattern" [disabled]="disabled" [value]="value" (valueChange)="valueChange.emit($event)" (touched)="touched.emit()" />`, isInline: true, dependencies: [{ kind: "component", type: InputTextControlComponent, selector: "input-text-control", inputs: ["placeholder", "name", "id", "type", "element", "rows", "autocomplete", "pattern", "disabled", "value"], outputs: ["valueChange", "touched"] }] });
1515
+ }
1516
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputEmailControlComponent, decorators: [{
1517
+ type: Component,
1518
+ args: [{ selector: 'input-email-control', standalone: true, imports: [InputTextControlComponent], template: `<input-text-control [placeholder]="placeholder" [name]="name" [id]="id" type="email" [autocomplete]="autocomplete" [pattern]="pattern" [disabled]="disabled" [value]="value" (valueChange)="valueChange.emit($event)" (touched)="touched.emit()" />` }]
1519
+ }], propDecorators: { placeholder: [{
1520
+ type: Input
1521
+ }], name: [{
1522
+ type: Input
1523
+ }], id: [{
1524
+ type: Input
1525
+ }], autocomplete: [{
1526
+ type: Input
1527
+ }], pattern: [{
1528
+ type: Input
1529
+ }], disabled: [{
1530
+ type: Input
1531
+ }], value: [{
1532
+ type: Input
1533
+ }], valueChange: [{
1534
+ type: Output
1535
+ }], touched: [{
1536
+ type: Output
1537
+ }] } });
1538
+
1167
1539
  class InputEmailComponent {
1168
1540
  cdr = inject(ChangeDetectorRef);
1169
1541
  label;
@@ -1175,7 +1547,6 @@ class InputEmailComponent {
1175
1547
  element = 'input';
1176
1548
  rows = 4;
1177
1549
  required = false;
1178
- fullWidth = false;
1179
1550
  autocomplete;
1180
1551
  pattern;
1181
1552
  error;
@@ -1214,25 +1585,25 @@ class InputEmailComponent {
1214
1585
  this.actionTriggered.emit(action);
1215
1586
  }
1216
1587
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputEmailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1217
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputEmailComponent, isStandalone: true, selector: "input-email", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", fullWidth: "fullWidth", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
1588
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputEmailComponent, isStandalone: true, selector: "input-email", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
1218
1589
  {
1219
1590
  provide: NG_VALUE_ACCESSOR,
1220
1591
  useExisting: forwardRef(() => InputEmailComponent),
1221
1592
  multi: true,
1222
1593
  },
1223
1594
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputEmailComponent), multi: true },
1224
- ], ngImport: i0, template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n\n\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1595
+ ], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"triggerAction($event)\">\n <input-email-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [autocomplete]=\"autocomplete\" [pattern]=\"pattern\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"onValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputEmailControlComponent, selector: "input-email-control", inputs: ["placeholder", "name", "id", "autocomplete", "pattern", "disabled", "value"], outputs: ["valueChange", "touched"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1225
1596
  }
1226
1597
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputEmailComponent, decorators: [{
1227
1598
  type: Component,
1228
- args: [{ selector: 'input-email', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1599
+ args: [{ selector: 'input-email', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, FieldFrameComponent, InputEmailControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1229
1600
  {
1230
1601
  provide: NG_VALUE_ACCESSOR,
1231
1602
  useExisting: forwardRef(() => InputEmailComponent),
1232
1603
  multi: true,
1233
1604
  },
1234
1605
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputEmailComponent), multi: true },
1235
- ], template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n\n\n" }]
1606
+ ], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"triggerAction($event)\">\n <input-email-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [autocomplete]=\"autocomplete\" [pattern]=\"pattern\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"onValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n" }]
1236
1607
  }], propDecorators: { label: [{
1237
1608
  type: Input
1238
1609
  }], hint: [{
@@ -1247,8 +1618,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1247
1618
  type: Input
1248
1619
  }], required: [{
1249
1620
  type: Input
1250
- }], fullWidth: [{
1251
- type: Input
1252
1621
  }], autocomplete: [{
1253
1622
  type: Input
1254
1623
  }], pattern: [{
@@ -1261,6 +1630,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1261
1630
  type: Output
1262
1631
  }] } });
1263
1632
 
1633
+ class InputNumberControlComponent {
1634
+ placeholder;
1635
+ name;
1636
+ id;
1637
+ disabled = false;
1638
+ value = '';
1639
+ valueChange = new EventEmitter();
1640
+ touched = new EventEmitter();
1641
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputNumberControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1642
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputNumberControlComponent, isStandalone: true, selector: "input-number-control", inputs: { placeholder: "placeholder", name: "name", id: "id", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange", touched: "touched" }, ngImport: i0, template: `<input-text-control [placeholder]="placeholder" [name]="name" [id]="id" type="number" [disabled]="disabled" [value]="value" (valueChange)="valueChange.emit($event)" (touched)="touched.emit()" />`, isInline: true, dependencies: [{ kind: "component", type: InputTextControlComponent, selector: "input-text-control", inputs: ["placeholder", "name", "id", "type", "element", "rows", "autocomplete", "pattern", "disabled", "value"], outputs: ["valueChange", "touched"] }] });
1643
+ }
1644
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputNumberControlComponent, decorators: [{
1645
+ type: Component,
1646
+ args: [{ selector: 'input-number-control', standalone: true, imports: [InputTextControlComponent], template: `<input-text-control [placeholder]="placeholder" [name]="name" [id]="id" type="number" [disabled]="disabled" [value]="value" (valueChange)="valueChange.emit($event)" (touched)="touched.emit()" />` }]
1647
+ }], propDecorators: { placeholder: [{
1648
+ type: Input
1649
+ }], name: [{
1650
+ type: Input
1651
+ }], id: [{
1652
+ type: Input
1653
+ }], disabled: [{
1654
+ type: Input
1655
+ }], value: [{
1656
+ type: Input
1657
+ }], valueChange: [{
1658
+ type: Output
1659
+ }], touched: [{
1660
+ type: Output
1661
+ }] } });
1662
+
1264
1663
  class InputNumberComponent {
1265
1664
  cdr = inject(ChangeDetectorRef);
1266
1665
  label;
@@ -1272,7 +1671,6 @@ class InputNumberComponent {
1272
1671
  element = 'input';
1273
1672
  rows = 4;
1274
1673
  required = false;
1275
- fullWidth = false;
1276
1674
  autocomplete;
1277
1675
  disabled = false;
1278
1676
  pattern;
@@ -1314,25 +1712,25 @@ class InputNumberComponent {
1314
1712
  this.actionTriggered.emit(action);
1315
1713
  }
1316
1714
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputNumberComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1317
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputNumberComponent, isStandalone: true, selector: "input-number", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", fullWidth: "fullWidth", autocomplete: "autocomplete", disabled: "disabled", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
1715
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputNumberComponent, isStandalone: true, selector: "input-number", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", autocomplete: "autocomplete", disabled: "disabled", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
1318
1716
  {
1319
1717
  provide: NG_VALUE_ACCESSOR,
1320
1718
  useExisting: forwardRef(() => InputNumberComponent),
1321
1719
  multi: true,
1322
1720
  },
1323
1721
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputNumberComponent), multi: true },
1324
- ], ngImport: i0, template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full appearance-none rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (wheel)=\"preventWheelChange($event)\"\n (blur)=\"onTouched()\"\n />\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n", styles: ["input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{appearance:none;-webkit-appearance:none;margin:0}\n", "input[type=number]{-moz-appearance:textfield}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1722
+ ], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"triggerAction($event)\">\n <input-number-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"onValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n", styles: ["input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{appearance:none;-webkit-appearance:none;margin:0}\n", "input[type=number]{-moz-appearance:textfield}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputNumberControlComponent, selector: "input-number-control", inputs: ["placeholder", "name", "id", "disabled", "value"], outputs: ["valueChange", "touched"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1325
1723
  }
1326
1724
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputNumberComponent, decorators: [{
1327
1725
  type: Component,
1328
- args: [{ selector: 'input-number', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1726
+ args: [{ selector: 'input-number', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, FieldFrameComponent, InputNumberControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1329
1727
  {
1330
1728
  provide: NG_VALUE_ACCESSOR,
1331
1729
  useExisting: forwardRef(() => InputNumberComponent),
1332
1730
  multi: true,
1333
1731
  },
1334
1732
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputNumberComponent), multi: true },
1335
- ], template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full appearance-none rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (wheel)=\"preventWheelChange($event)\"\n (blur)=\"onTouched()\"\n />\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n", styles: ["input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{appearance:none;-webkit-appearance:none;margin:0}\n", "input[type=number]{-moz-appearance:textfield}\n"] }]
1733
+ ], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"triggerAction($event)\">\n <input-number-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"onValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n", styles: ["input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{appearance:none;-webkit-appearance:none;margin:0}\n", "input[type=number]{-moz-appearance:textfield}\n"] }]
1336
1734
  }], propDecorators: { label: [{
1337
1735
  type: Input
1338
1736
  }], hint: [{
@@ -1347,8 +1745,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1347
1745
  type: Input
1348
1746
  }], required: [{
1349
1747
  type: Input
1350
- }], fullWidth: [{
1351
- type: Input
1352
1748
  }], autocomplete: [{
1353
1749
  type: Input
1354
1750
  }], disabled: [{
@@ -1363,6 +1759,64 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1363
1759
  type: Output
1364
1760
  }] } });
1365
1761
 
1762
+ class InputPasswordControlComponent {
1763
+ showIcon = Eye;
1764
+ hideIcon = EyeOff;
1765
+ placeholder;
1766
+ name;
1767
+ id;
1768
+ autocomplete;
1769
+ pattern;
1770
+ disabled = false;
1771
+ value = '';
1772
+ valueChange = new EventEmitter();
1773
+ touched = new EventEmitter();
1774
+ visible = false;
1775
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPasswordControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1776
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputPasswordControlComponent, isStandalone: true, selector: "input-password-control", inputs: { placeholder: "placeholder", name: "name", id: "id", autocomplete: "autocomplete", pattern: "pattern", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange", touched: "touched" }, ngImport: i0, template: `
1777
+ <div class="relative">
1778
+ <input [attr.type]="visible ? 'text' : 'password'" [attr.placeholder]="placeholder" [attr.name]="name" [attr.autocomplete]="autocomplete" [attr.pattern]="pattern" [disabled]="disabled" class="w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200" [attr.id]="id" [value]="value" (input)="valueChange.emit($any($event.target).value)" (blur)="touched.emit()" />
1779
+ <button type="button" class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 transition hover:text-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-200" [attr.aria-label]="visible ? 'Сховати пароль' : 'Показати пароль'" [disabled]="disabled" (mousedown)="$event.preventDefault()" (click)="visible = !visible">
1780
+ <lucide-icon [img]="visible ? hideIcon : showIcon" class="h-4 w-4" aria-hidden="true"></lucide-icon>
1781
+ </button>
1782
+ </div>
1783
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
1784
+ }
1785
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPasswordControlComponent, decorators: [{
1786
+ type: Component,
1787
+ args: [{
1788
+ selector: 'input-password-control',
1789
+ standalone: true,
1790
+ imports: [CommonModule, LucideAngularModule],
1791
+ template: `
1792
+ <div class="relative">
1793
+ <input [attr.type]="visible ? 'text' : 'password'" [attr.placeholder]="placeholder" [attr.name]="name" [attr.autocomplete]="autocomplete" [attr.pattern]="pattern" [disabled]="disabled" class="w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200" [attr.id]="id" [value]="value" (input)="valueChange.emit($any($event.target).value)" (blur)="touched.emit()" />
1794
+ <button type="button" class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 transition hover:text-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-200" [attr.aria-label]="visible ? 'Сховати пароль' : 'Показати пароль'" [disabled]="disabled" (mousedown)="$event.preventDefault()" (click)="visible = !visible">
1795
+ <lucide-icon [img]="visible ? hideIcon : showIcon" class="h-4 w-4" aria-hidden="true"></lucide-icon>
1796
+ </button>
1797
+ </div>
1798
+ `,
1799
+ }]
1800
+ }], propDecorators: { placeholder: [{
1801
+ type: Input
1802
+ }], name: [{
1803
+ type: Input
1804
+ }], id: [{
1805
+ type: Input
1806
+ }], autocomplete: [{
1807
+ type: Input
1808
+ }], pattern: [{
1809
+ type: Input
1810
+ }], disabled: [{
1811
+ type: Input
1812
+ }], value: [{
1813
+ type: Input
1814
+ }], valueChange: [{
1815
+ type: Output
1816
+ }], touched: [{
1817
+ type: Output
1818
+ }] } });
1819
+
1366
1820
  class InputPasswordComponent {
1367
1821
  cdr = inject(ChangeDetectorRef);
1368
1822
  label;
@@ -1376,7 +1830,6 @@ class InputPasswordComponent {
1376
1830
  element = 'input';
1377
1831
  rows = 4;
1378
1832
  required = false;
1379
- fullWidth = false;
1380
1833
  autocomplete;
1381
1834
  pattern;
1382
1835
  error;
@@ -1421,25 +1874,25 @@ class InputPasswordComponent {
1421
1874
  this.actionTriggered.emit(action);
1422
1875
  }
1423
1876
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPasswordComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1424
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputPasswordComponent, isStandalone: true, selector: "input-password", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", fullWidth: "fullWidth", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
1877
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputPasswordComponent, isStandalone: true, selector: "input-password", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
1425
1878
  {
1426
1879
  provide: NG_VALUE_ACCESSOR,
1427
1880
  useExisting: forwardRef(() => InputPasswordComponent),
1428
1881
  multi: true,
1429
1882
  },
1430
1883
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputPasswordComponent), multi: true },
1431
- ], ngImport: i0, template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <div class=\"relative\">\n <input\n [attr.type]=\"isPasswordVisible ? 'text' : type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n <button\n type=\"button\"\n class=\"absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 transition hover:text-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.aria-label]=\"isPasswordVisible ? '\u0421\u0445\u043E\u0432\u0430\u0442\u0438 \u043F\u0430\u0440\u043E\u043B\u044C' : '\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u0438 \u043F\u0430\u0440\u043E\u043B\u044C'\"\n [disabled]=\"disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"togglePassword($event)\"\n >\n <lucide-icon\n [img]=\"isPasswordVisible ? hidePasswordIcon : showPasswordIcon\"\n class=\"h-4 w-4\"\n aria-hidden=\"true\"\n ></lucide-icon>\n </button>\n </div>\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1884
+ ], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"triggerAction($event)\">\n <input-password-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [autocomplete]=\"autocomplete\" [pattern]=\"pattern\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"onValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputPasswordControlComponent, selector: "input-password-control", inputs: ["placeholder", "name", "id", "autocomplete", "pattern", "disabled", "value"], outputs: ["valueChange", "touched"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1432
1885
  }
1433
1886
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPasswordComponent, decorators: [{
1434
1887
  type: Component,
1435
- args: [{ selector: 'input-password', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1888
+ args: [{ selector: 'input-password', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, LucideAngularModule, FieldFrameComponent, InputPasswordControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1436
1889
  {
1437
1890
  provide: NG_VALUE_ACCESSOR,
1438
1891
  useExisting: forwardRef(() => InputPasswordComponent),
1439
1892
  multi: true,
1440
1893
  },
1441
1894
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputPasswordComponent), multi: true },
1442
- ], template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <div class=\"relative\">\n <input\n [attr.type]=\"isPasswordVisible ? 'text' : type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 pr-10 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n <button\n type=\"button\"\n class=\"absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 transition hover:text-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.aria-label]=\"isPasswordVisible ? '\u0421\u0445\u043E\u0432\u0430\u0442\u0438 \u043F\u0430\u0440\u043E\u043B\u044C' : '\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u0438 \u043F\u0430\u0440\u043E\u043B\u044C'\"\n [disabled]=\"disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"togglePassword($event)\"\n >\n <lucide-icon\n [img]=\"isPasswordVisible ? hidePasswordIcon : showPasswordIcon\"\n class=\"h-4 w-4\"\n aria-hidden=\"true\"\n ></lucide-icon>\n </button>\n </div>\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n\n" }]
1895
+ ], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"triggerAction($event)\">\n <input-password-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [autocomplete]=\"autocomplete\" [pattern]=\"pattern\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"onValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n" }]
1443
1896
  }], propDecorators: { label: [{
1444
1897
  type: Input
1445
1898
  }], hint: [{
@@ -1454,8 +1907,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1454
1907
  type: Input
1455
1908
  }], required: [{
1456
1909
  type: Input
1457
- }], fullWidth: [{
1458
- type: Input
1459
1910
  }], autocomplete: [{
1460
1911
  type: Input
1461
1912
  }], pattern: [{
@@ -1479,7 +1930,6 @@ class InputTextComponent {
1479
1930
  element = 'input';
1480
1931
  rows = 4;
1481
1932
  required = false;
1482
- fullWidth = false;
1483
1933
  autocomplete;
1484
1934
  pattern;
1485
1935
  error;
@@ -1518,25 +1968,25 @@ class InputTextComponent {
1518
1968
  this.actionTriggered.emit(action);
1519
1969
  }
1520
1970
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1521
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputTextComponent, isStandalone: true, selector: "input-text", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", fullWidth: "fullWidth", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
1971
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputTextComponent, isStandalone: true, selector: "input-text", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
1522
1972
  {
1523
1973
  provide: NG_VALUE_ACCESSOR,
1524
1974
  useExisting: forwardRef(() => InputTextComponent),
1525
1975
  multi: true,
1526
1976
  },
1527
1977
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputTextComponent), multi: true },
1528
- ], ngImport: i0, template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n\n\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1978
+ ], ngImport: i0, template: "<field-frame\n [label]=\"label\"\n [hint]=\"hint\"\n [error]=\"error\"\n [required]=\"required\"\n [forId]=\"controlId\"\n [disabled]=\"disabled\"\n [actions]=\"actions\"\n (actionTriggered)=\"triggerAction($event)\"\n>\n <input-text-control\n [placeholder]=\"placeholder\"\n [name]=\"name\"\n [id]=\"controlId\"\n [type]=\"type\"\n [element]=\"element\"\n [rows]=\"rows\"\n [autocomplete]=\"autocomplete\"\n [pattern]=\"pattern\"\n [disabled]=\"disabled\"\n [value]=\"value\"\n (valueChange)=\"onValueChange($event)\"\n (touched)=\"onTouched()\"\n />\n</field-frame>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputTextControlComponent, selector: "input-text-control", inputs: ["placeholder", "name", "id", "type", "element", "rows", "autocomplete", "pattern", "disabled", "value"], outputs: ["valueChange", "touched"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1529
1979
  }
1530
1980
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextComponent, decorators: [{
1531
1981
  type: Component,
1532
- args: [{ selector: 'input-text', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1982
+ args: [{ selector: 'input-text', standalone: true, imports: [CommonModule, FormsModule, FieldFrameComponent, InputTextControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1533
1983
  {
1534
1984
  provide: NG_VALUE_ACCESSOR,
1535
1985
  useExisting: forwardRef(() => InputTextComponent),
1536
1986
  multi: true,
1537
1987
  },
1538
1988
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputTextComponent), multi: true },
1539
- ], template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n\n\n" }]
1989
+ ], template: "<field-frame\n [label]=\"label\"\n [hint]=\"hint\"\n [error]=\"error\"\n [required]=\"required\"\n [forId]=\"controlId\"\n [disabled]=\"disabled\"\n [actions]=\"actions\"\n (actionTriggered)=\"triggerAction($event)\"\n>\n <input-text-control\n [placeholder]=\"placeholder\"\n [name]=\"name\"\n [id]=\"controlId\"\n [type]=\"type\"\n [element]=\"element\"\n [rows]=\"rows\"\n [autocomplete]=\"autocomplete\"\n [pattern]=\"pattern\"\n [disabled]=\"disabled\"\n [value]=\"value\"\n (valueChange)=\"onValueChange($event)\"\n (touched)=\"onTouched()\"\n />\n</field-frame>\n" }]
1540
1990
  }], propDecorators: { label: [{
1541
1991
  type: Input
1542
1992
  }], hint: [{
@@ -1551,8 +2001,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1551
2001
  type: Input
1552
2002
  }], required: [{
1553
2003
  type: Input
1554
- }], fullWidth: [{
1555
- type: Input
1556
2004
  }], autocomplete: [{
1557
2005
  type: Input
1558
2006
  }], pattern: [{
@@ -1565,6 +2013,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1565
2013
  type: Output
1566
2014
  }] } });
1567
2015
 
2016
+ class InputTextareaControlComponent {
2017
+ placeholder;
2018
+ name;
2019
+ id;
2020
+ rows = 4;
2021
+ disabled = false;
2022
+ value = '';
2023
+ valueChange = new EventEmitter();
2024
+ touched = new EventEmitter();
2025
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextareaControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2026
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputTextareaControlComponent, isStandalone: true, selector: "input-textarea-control", inputs: { placeholder: "placeholder", name: "name", id: "id", rows: "rows", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange", touched: "touched" }, ngImport: i0, template: `<input-text-control [placeholder]="placeholder" [name]="name" [id]="id" element="textarea" [rows]="rows" [disabled]="disabled" [value]="value" (valueChange)="valueChange.emit($event)" (touched)="touched.emit()" />`, isInline: true, dependencies: [{ kind: "component", type: InputTextControlComponent, selector: "input-text-control", inputs: ["placeholder", "name", "id", "type", "element", "rows", "autocomplete", "pattern", "disabled", "value"], outputs: ["valueChange", "touched"] }] });
2027
+ }
2028
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextareaControlComponent, decorators: [{
2029
+ type: Component,
2030
+ args: [{ selector: 'input-textarea-control', standalone: true, imports: [InputTextControlComponent], template: `<input-text-control [placeholder]="placeholder" [name]="name" [id]="id" element="textarea" [rows]="rows" [disabled]="disabled" [value]="value" (valueChange)="valueChange.emit($event)" (touched)="touched.emit()" />` }]
2031
+ }], propDecorators: { placeholder: [{
2032
+ type: Input
2033
+ }], name: [{
2034
+ type: Input
2035
+ }], id: [{
2036
+ type: Input
2037
+ }], rows: [{
2038
+ type: Input
2039
+ }], disabled: [{
2040
+ type: Input
2041
+ }], value: [{
2042
+ type: Input
2043
+ }], valueChange: [{
2044
+ type: Output
2045
+ }], touched: [{
2046
+ type: Output
2047
+ }] } });
2048
+
1568
2049
  class InputTextareaComponent {
1569
2050
  cdr = inject(ChangeDetectorRef);
1570
2051
  label;
@@ -1576,7 +2057,6 @@ class InputTextareaComponent {
1576
2057
  element = 'textarea';
1577
2058
  rows = 4;
1578
2059
  required = false;
1579
- fullWidth = false;
1580
2060
  autocomplete;
1581
2061
  pattern;
1582
2062
  error;
@@ -1615,25 +2095,25 @@ class InputTextareaComponent {
1615
2095
  this.actionTriggered.emit(action);
1616
2096
  }
1617
2097
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextareaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1618
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputTextareaComponent, isStandalone: true, selector: "input-textarea", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", fullWidth: "fullWidth", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
2098
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputTextareaComponent, isStandalone: true, selector: "input-textarea", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", rows: "rows", required: "required", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
1619
2099
  {
1620
2100
  provide: NG_VALUE_ACCESSOR,
1621
2101
  useExisting: forwardRef(() => InputTextareaComponent),
1622
2102
  multi: true,
1623
2103
  },
1624
2104
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputTextareaComponent), multi: true },
1625
- ], ngImport: i0, template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n\n\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2105
+ ], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"triggerAction($event)\">\n <input-textarea-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [rows]=\"rows\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"onValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputTextareaControlComponent, selector: "input-textarea-control", inputs: ["placeholder", "name", "id", "rows", "disabled", "value"], outputs: ["valueChange", "touched"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1626
2106
  }
1627
2107
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextareaComponent, decorators: [{
1628
2108
  type: Component,
1629
- args: [{ selector: 'input-textarea', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
2109
+ args: [{ selector: 'input-textarea', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, FieldFrameComponent, InputTextareaControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1630
2110
  {
1631
2111
  provide: NG_VALUE_ACCESSOR,
1632
2112
  useExisting: forwardRef(() => InputTextareaComponent),
1633
2113
  multi: true,
1634
2114
  },
1635
2115
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputTextareaComponent), multi: true },
1636
- ], template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n\n\n" }]
2116
+ ], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"triggerAction($event)\">\n <input-textarea-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [rows]=\"rows\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"onValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n" }]
1637
2117
  }], propDecorators: { label: [{
1638
2118
  type: Input
1639
2119
  }], hint: [{
@@ -1648,8 +2128,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1648
2128
  type: Input
1649
2129
  }], required: [{
1650
2130
  type: Input
1651
- }], fullWidth: [{
1652
- type: Input
1653
2131
  }], autocomplete: [{
1654
2132
  type: Input
1655
2133
  }], pattern: [{
@@ -1662,15 +2140,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1662
2140
  type: Output
1663
2141
  }] } });
1664
2142
 
2143
+ class InputFileControlComponent {
2144
+ accept;
2145
+ multiple = false;
2146
+ disabled = false;
2147
+ selectedFiles = [];
2148
+ filesSelected = new EventEmitter();
2149
+ removeFile = new EventEmitter();
2150
+ touched = new EventEmitter();
2151
+ onFilesSelected(event) {
2152
+ const input = event.target;
2153
+ this.filesSelected.emit(Array.from(input.files ?? []));
2154
+ input.value = '';
2155
+ }
2156
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputFileControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2157
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputFileControlComponent, isStandalone: true, selector: "input-file-control", inputs: { accept: "accept", multiple: "multiple", disabled: "disabled", selectedFiles: "selectedFiles" }, outputs: { filesSelected: "filesSelected", removeFile: "removeFile", touched: "touched" }, ngImport: i0, template: "<input type=\"file\" class=\"rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm normal-case tracking-normal text-slate-800 file:mr-3 file:rounded-lg file:border-0 file:bg-slate-900 file:px-3 file:py-2 file:text-sm file:font-medium file:text-white\" [attr.accept]=\"accept ?? null\" [attr.multiple]=\"multiple ? '' : null\" [disabled]=\"disabled\" (change)=\"onFilesSelected($event)\" (blur)=\"touched.emit()\" />\n@if (selectedFiles.length) {\n <div class=\"mt-1 flex flex-col gap-1 text-[0.7rem] font-normal normal-case tracking-normal text-slate-700\">\n @for (file of selectedFiles; track file.name + file.size; let i = $index) {\n <div class=\"flex items-center justify-between rounded border border-slate-200 bg-slate-50 px-2 py-1\">\n <span class=\"truncate pr-2\">{{ file.name }}</span>\n <button type=\"button\" class=\"text-rose-600\" [disabled]=\"disabled\" (click)=\"removeFile.emit(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n </div>\n }\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2158
+ }
2159
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputFileControlComponent, decorators: [{
2160
+ type: Component,
2161
+ args: [{ selector: 'input-file-control', standalone: true, imports: [CommonModule], template: "<input type=\"file\" class=\"rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm normal-case tracking-normal text-slate-800 file:mr-3 file:rounded-lg file:border-0 file:bg-slate-900 file:px-3 file:py-2 file:text-sm file:font-medium file:text-white\" [attr.accept]=\"accept ?? null\" [attr.multiple]=\"multiple ? '' : null\" [disabled]=\"disabled\" (change)=\"onFilesSelected($event)\" (blur)=\"touched.emit()\" />\n@if (selectedFiles.length) {\n <div class=\"mt-1 flex flex-col gap-1 text-[0.7rem] font-normal normal-case tracking-normal text-slate-700\">\n @for (file of selectedFiles; track file.name + file.size; let i = $index) {\n <div class=\"flex items-center justify-between rounded border border-slate-200 bg-slate-50 px-2 py-1\">\n <span class=\"truncate pr-2\">{{ file.name }}</span>\n <button type=\"button\" class=\"text-rose-600\" [disabled]=\"disabled\" (click)=\"removeFile.emit(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n </div>\n }\n </div>\n}\n" }]
2162
+ }], propDecorators: { accept: [{
2163
+ type: Input
2164
+ }], multiple: [{
2165
+ type: Input
2166
+ }], disabled: [{
2167
+ type: Input
2168
+ }], selectedFiles: [{
2169
+ type: Input
2170
+ }], filesSelected: [{
2171
+ type: Output
2172
+ }], removeFile: [{
2173
+ type: Output
2174
+ }], touched: [{
2175
+ type: Output
2176
+ }] } });
2177
+
1665
2178
  class InputFileComponent {
1666
2179
  label;
1667
2180
  hint;
2181
+ error;
2182
+ required = false;
1668
2183
  accept;
1669
2184
  multiple = false;
1670
2185
  disabled = false;
1671
- value = null;
1672
- valueChange = new EventEmitter();
1673
2186
  filesChange = new EventEmitter();
2187
+ value = null;
1674
2188
  onChange = () => undefined;
1675
2189
  onTouched = () => undefined;
1676
2190
  writeValue(value) { this.value = value; }
@@ -1688,39 +2202,85 @@ class InputFileComponent {
1688
2202
  const value = this.multiple ? files : (files[0] ?? null);
1689
2203
  this.value = value;
1690
2204
  this.onChange(value);
1691
- this.valueChange.emit(value);
1692
2205
  this.filesChange.emit(files);
1693
2206
  input.value = '';
1694
2207
  }
2208
+ handleFilesSelected(files) {
2209
+ const value = this.multiple ? files : (files[0] ?? null);
2210
+ this.value = value;
2211
+ this.onChange(value);
2212
+ this.filesChange.emit(files);
2213
+ }
1695
2214
  remove(index) {
1696
2215
  const files = this.selectedFiles.filter((_file, fileIndex) => fileIndex !== index);
1697
2216
  const value = this.multiple ? files : (files[0] ?? null);
1698
2217
  this.value = value;
1699
2218
  this.onChange(value);
1700
- this.valueChange.emit(value);
1701
2219
  this.filesChange.emit(files);
1702
2220
  }
1703
2221
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1704
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputFileComponent, isStandalone: true, selector: "input-file", inputs: { label: "label", hint: "hint", accept: "accept", multiple: "multiple", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange", filesChange: "filesChange" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputFileComponent), multi: true }], ngImport: i0, template: "<label class=\"flex flex-col gap-2 text-[0.7rem] font-semibold uppercase tracking-[0.2em] text-slate-600\">\n @if (label) { <span>{{ label }}</span> }\n <input\n type=\"file\"\n class=\"rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm normal-case tracking-normal text-slate-800 file:mr-3 file:rounded-lg file:border-0 file:bg-slate-900 file:px-3 file:py-2 file:text-sm file:font-medium file:text-white\"\n [attr.accept]=\"accept ?? null\"\n [attr.multiple]=\"multiple ? '' : null\"\n [disabled]=\"disabled\"\n (change)=\"onFilesSelected($event)\"\n (blur)=\"onTouched()\"\n />\n @if (hint) { <span class=\"text-[0.7rem] font-normal normal-case tracking-normal text-slate-500\">{{ hint }}</span> }\n @if (selectedFiles.length) {\n <div class=\"flex flex-col gap-1 text-[0.7rem] font-normal normal-case tracking-normal text-slate-700\">\n @for (file of selectedFiles; track file.name + file.size; let i = $index) {\n <div class=\"flex items-center justify-between rounded border border-slate-200 bg-slate-50 px-2 py-1\">\n <span class=\"truncate pr-2\">{{ file.name }}</span>\n <button type=\"button\" class=\"text-rose-600\" [disabled]=\"disabled\" (click)=\"remove(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n </div>\n }\n </div>\n }\n</label>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2222
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputFileComponent, isStandalone: true, selector: "input-file", inputs: { label: "label", hint: "hint", error: "error", required: "required", accept: "accept", multiple: "multiple", disabled: "disabled" }, outputs: { filesChange: "filesChange" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputFileComponent), multi: true }], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\">\n <input-file-control [accept]=\"accept\" [multiple]=\"multiple\" [disabled]=\"disabled\" [selectedFiles]=\"selectedFiles\" (filesSelected)=\"handleFilesSelected($event)\" (removeFile)=\"remove($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputFileControlComponent, selector: "input-file-control", inputs: ["accept", "multiple", "disabled", "selectedFiles"], outputs: ["filesSelected", "removeFile", "touched"] }] });
1705
2223
  }
1706
2224
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputFileComponent, decorators: [{
1707
2225
  type: Component,
1708
- args: [{ selector: 'input-file', standalone: true, imports: [CommonModule], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputFileComponent), multi: true }], template: "<label class=\"flex flex-col gap-2 text-[0.7rem] font-semibold uppercase tracking-[0.2em] text-slate-600\">\n @if (label) { <span>{{ label }}</span> }\n <input\n type=\"file\"\n class=\"rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm normal-case tracking-normal text-slate-800 file:mr-3 file:rounded-lg file:border-0 file:bg-slate-900 file:px-3 file:py-2 file:text-sm file:font-medium file:text-white\"\n [attr.accept]=\"accept ?? null\"\n [attr.multiple]=\"multiple ? '' : null\"\n [disabled]=\"disabled\"\n (change)=\"onFilesSelected($event)\"\n (blur)=\"onTouched()\"\n />\n @if (hint) { <span class=\"text-[0.7rem] font-normal normal-case tracking-normal text-slate-500\">{{ hint }}</span> }\n @if (selectedFiles.length) {\n <div class=\"flex flex-col gap-1 text-[0.7rem] font-normal normal-case tracking-normal text-slate-700\">\n @for (file of selectedFiles; track file.name + file.size; let i = $index) {\n <div class=\"flex items-center justify-between rounded border border-slate-200 bg-slate-50 px-2 py-1\">\n <span class=\"truncate pr-2\">{{ file.name }}</span>\n <button type=\"button\" class=\"text-rose-600\" [disabled]=\"disabled\" (click)=\"remove(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n </div>\n }\n </div>\n }\n</label>\n" }]
2226
+ args: [{ selector: 'input-file', standalone: true, imports: [CommonModule, FieldFrameComponent, InputFileControlComponent], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputFileComponent), multi: true }], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\">\n <input-file-control [accept]=\"accept\" [multiple]=\"multiple\" [disabled]=\"disabled\" [selectedFiles]=\"selectedFiles\" (filesSelected)=\"handleFilesSelected($event)\" (removeFile)=\"remove($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n" }]
1709
2227
  }], propDecorators: { label: [{
1710
2228
  type: Input
1711
2229
  }], hint: [{
1712
2230
  type: Input
2231
+ }], error: [{
2232
+ type: Input
2233
+ }], required: [{
2234
+ type: Input
1713
2235
  }], accept: [{
1714
2236
  type: Input
1715
2237
  }], multiple: [{
1716
2238
  type: Input
1717
2239
  }], disabled: [{
1718
2240
  type: Input
1719
- }], value: [{
2241
+ }], filesChange: [{
2242
+ type: Output
2243
+ }] } });
2244
+
2245
+ class InputPhotoControlComponent {
2246
+ accept = 'image/*';
2247
+ multiple = false;
2248
+ disabled = false;
2249
+ canSelectMore = true;
2250
+ previews = [];
2251
+ filesSelected = new EventEmitter();
2252
+ removed = new EventEmitter();
2253
+ dropped = new EventEmitter();
2254
+ touched = new EventEmitter();
2255
+ selectFiles(event) {
2256
+ const input = event.target;
2257
+ this.filesSelected.emit(Array.from(input.files ?? []));
2258
+ input.value = '';
2259
+ this.touched.emit();
2260
+ }
2261
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPhotoControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2262
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputPhotoControlComponent, isStandalone: true, selector: "input-photo-control", inputs: { accept: "accept", multiple: "multiple", disabled: "disabled", canSelectMore: "canSelectMore", previews: "previews" }, outputs: { filesSelected: "filesSelected", removed: "removed", dropped: "dropped", touched: "touched" }, ngImport: i0, template: "<button type=\"button\" class=\"flex min-h-24 w-full items-center justify-center rounded-xl border border-dashed border-slate-300 bg-slate-50 px-4 py-3 text-xs font-semibold uppercase tracking-[0.15em] text-slate-600 transition hover:bg-slate-100 disabled:opacity-60\" [disabled]=\"disabled || !canSelectMore\" (click)=\"photoInput.click()\">\n {{ previews.length ? '\u0414\u043E\u0434\u0430\u0442\u0438 \u0449\u0435 \u0444\u0430\u0439\u043B' : '\u041E\u0431\u0440\u0430\u0442\u0438 \u0444\u0430\u0439\u043B' }}\n</button>\n<input #photoInput type=\"file\" class=\"hidden\" [attr.accept]=\"accept\" [attr.multiple]=\"multiple && canSelectMore ? '' : null\" [disabled]=\"disabled\" (change)=\"selectFiles($event)\" />\n@if (previews.length) {\n <div class=\"mt-2 grid grid-cols-2 gap-2 md:grid-cols-4\" cdkDropList cdkDropListOrientation=\"mixed\" [cdkDropListData]=\"previews\" (cdkDropListDropped)=\"dropped.emit($event)\">\n @for (preview of previews; track preview.url; let i = $index) {\n <div class=\"relative overflow-hidden rounded-lg border border-slate-200 bg-slate-50\" cdkDrag [cdkDragDisabled]=\"disabled || preview.source === 'file'\">\n @if (preview.isImage) { <img [src]=\"preview.url\" [alt]=\"preview.name\" class=\"h-28 w-full object-cover\" /> }\n @else { <div class=\"flex h-28 items-center justify-center px-2 text-center text-[0.65rem] text-slate-600\">{{ preview.name }}</div> }\n <div class=\"absolute left-1 top-1 rounded bg-slate-900/85 px-2 py-1 text-[10px] font-semibold text-white\">#{{ i + 1 }}</div>\n <button type=\"button\" class=\"absolute right-1 top-1 rounded bg-white/95 px-2 py-1 text-[10px] text-rose-600\" [disabled]=\"disabled\" (click)=\"removed.emit(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n </div>\n }\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }] });
2263
+ }
2264
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPhotoControlComponent, decorators: [{
2265
+ type: Component,
2266
+ args: [{ selector: 'input-photo-control', standalone: true, imports: [CommonModule, DragDropModule], template: "<button type=\"button\" class=\"flex min-h-24 w-full items-center justify-center rounded-xl border border-dashed border-slate-300 bg-slate-50 px-4 py-3 text-xs font-semibold uppercase tracking-[0.15em] text-slate-600 transition hover:bg-slate-100 disabled:opacity-60\" [disabled]=\"disabled || !canSelectMore\" (click)=\"photoInput.click()\">\n {{ previews.length ? '\u0414\u043E\u0434\u0430\u0442\u0438 \u0449\u0435 \u0444\u0430\u0439\u043B' : '\u041E\u0431\u0440\u0430\u0442\u0438 \u0444\u0430\u0439\u043B' }}\n</button>\n<input #photoInput type=\"file\" class=\"hidden\" [attr.accept]=\"accept\" [attr.multiple]=\"multiple && canSelectMore ? '' : null\" [disabled]=\"disabled\" (change)=\"selectFiles($event)\" />\n@if (previews.length) {\n <div class=\"mt-2 grid grid-cols-2 gap-2 md:grid-cols-4\" cdkDropList cdkDropListOrientation=\"mixed\" [cdkDropListData]=\"previews\" (cdkDropListDropped)=\"dropped.emit($event)\">\n @for (preview of previews; track preview.url; let i = $index) {\n <div class=\"relative overflow-hidden rounded-lg border border-slate-200 bg-slate-50\" cdkDrag [cdkDragDisabled]=\"disabled || preview.source === 'file'\">\n @if (preview.isImage) { <img [src]=\"preview.url\" [alt]=\"preview.name\" class=\"h-28 w-full object-cover\" /> }\n @else { <div class=\"flex h-28 items-center justify-center px-2 text-center text-[0.65rem] text-slate-600\">{{ preview.name }}</div> }\n <div class=\"absolute left-1 top-1 rounded bg-slate-900/85 px-2 py-1 text-[10px] font-semibold text-white\">#{{ i + 1 }}</div>\n <button type=\"button\" class=\"absolute right-1 top-1 rounded bg-white/95 px-2 py-1 text-[10px] text-rose-600\" [disabled]=\"disabled\" (click)=\"removed.emit(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n </div>\n }\n </div>\n}\n" }]
2267
+ }], propDecorators: { accept: [{
1720
2268
  type: Input
1721
- }], valueChange: [{
2269
+ }], multiple: [{
2270
+ type: Input
2271
+ }], disabled: [{
2272
+ type: Input
2273
+ }], canSelectMore: [{
2274
+ type: Input
2275
+ }], previews: [{
2276
+ type: Input
2277
+ }], filesSelected: [{
1722
2278
  type: Output
1723
- }], filesChange: [{
2279
+ }], removed: [{
2280
+ type: Output
2281
+ }], dropped: [{
2282
+ type: Output
2283
+ }], touched: [{
1724
2284
  type: Output
1725
2285
  }] } });
1726
2286
 
@@ -1728,14 +2288,15 @@ class InputPhotoComponent {
1728
2288
  http = inject(HttpClient);
1729
2289
  label;
1730
2290
  hint;
2291
+ error;
2292
+ required = false;
1731
2293
  accept = 'image/*';
1732
2294
  multiple = false;
1733
2295
  limit = Number.MAX_SAFE_INTEGER;
1734
2296
  disabled = false;
1735
- value = null;
1736
- valueChange = new EventEmitter();
1737
2297
  filesChange = new EventEmitter();
1738
2298
  uploadingChange = new EventEmitter();
2299
+ value = null;
1739
2300
  pendingFiles = [];
1740
2301
  previews = [];
1741
2302
  uploading = false;
@@ -1750,7 +2311,7 @@ class InputPhotoComponent {
1750
2311
  registerOnTouched(fn) { this.onTouched = fn; }
1751
2312
  setDisabledState(isDisabled) { this.disabled = isDisabled; }
1752
2313
  ngOnChanges(changes) {
1753
- if (changes['value'] || changes['multiple'])
2314
+ if (changes['multiple'])
1754
2315
  this.rebuildPreviews();
1755
2316
  }
1756
2317
  ngOnDestroy() {
@@ -1771,6 +2332,15 @@ class InputPhotoComponent {
1771
2332
  this.rebuildPreviews();
1772
2333
  this.uploadFiles(accepted);
1773
2334
  }
2335
+ handleFilesSelected(files) {
2336
+ if (!files.length || this.disabled)
2337
+ return;
2338
+ const accepted = this.multiple ? files.slice(0, Math.max(0, this.normalizedLimit - this.ids.length - this.pendingFiles.length)) : [files[0]];
2339
+ this.pendingFiles = this.multiple ? [...this.pendingFiles, ...accepted] : [accepted[0]];
2340
+ this.filesChange.emit(this.pendingFiles);
2341
+ this.rebuildPreviews();
2342
+ this.uploadFiles(accepted);
2343
+ }
1774
2344
  remove(index) {
1775
2345
  const ids = [...this.ids];
1776
2346
  if (index < ids.length) {
@@ -1801,7 +2371,6 @@ class InputPhotoComponent {
1801
2371
  emitValue(value) {
1802
2372
  this.value = value;
1803
2373
  this.onChange(value);
1804
- this.valueChange.emit(value);
1805
2374
  this.rebuildPreviews(value);
1806
2375
  }
1807
2376
  uploadFiles(files) {
@@ -1862,15 +2431,19 @@ class InputPhotoComponent {
1862
2431
  return normalized.startsWith('image/') || normalized === 'application/pdf';
1863
2432
  }
1864
2433
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPhotoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1865
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputPhotoComponent, isStandalone: true, selector: "input-photo", inputs: { label: "label", hint: "hint", accept: "accept", multiple: "multiple", limit: "limit", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange", filesChange: "filesChange", uploadingChange: "uploadingChange" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputPhotoComponent), multi: true }], usesOnChanges: true, ngImport: i0, template: "<label class=\"flex flex-col gap-2 text-[0.7rem] font-semibold uppercase tracking-[0.2em] text-slate-600\">\n @if (label) { <span>{{ label }}</span> }\n <button type=\"button\" class=\"flex min-h-24 items-center justify-center rounded-xl border border-dashed border-slate-300 bg-slate-50 px-4 py-3 text-xs font-semibold uppercase tracking-[0.15em] text-slate-600 transition hover:bg-slate-100 disabled:opacity-60\" [disabled]=\"disabled || !canSelectMore\" (click)=\"photoInput.click()\">\n {{ previews.length ? '\u0414\u043E\u0434\u0430\u0442\u0438 \u0449\u0435 \u0444\u0430\u0439\u043B' : '\u041E\u0431\u0440\u0430\u0442\u0438 \u0444\u0430\u0439\u043B' }}\n </button>\n <input #photoInput type=\"file\" class=\"hidden\" [attr.accept]=\"accept\" [attr.multiple]=\"multiple && canSelectMore ? '' : null\" [disabled]=\"disabled\" (change)=\"onFilesSelected($event)\" />\n @if (hint) { <span class=\"text-[0.7rem] font-normal normal-case tracking-normal text-slate-500\">{{ hint }}</span> }\n @if (previews.length) {\n <div class=\"grid grid-cols-2 gap-2 md:grid-cols-4\" cdkDropList cdkDropListOrientation=\"mixed\" [cdkDropListData]=\"previews\" (cdkDropListDropped)=\"drop($event)\">\n @for (preview of previews; track preview.url; let i = $index) {\n <div class=\"relative overflow-hidden rounded-lg border border-slate-200 bg-slate-50\" cdkDrag [cdkDragDisabled]=\"disabled || preview.source === 'file'\">\n @if (preview.isImage) { <img [src]=\"preview.url\" [alt]=\"preview.name\" class=\"h-28 w-full object-cover\" /> }\n @else { <div class=\"flex h-28 items-center justify-center px-2 text-center text-[0.65rem] text-slate-600\">{{ preview.name }}</div> }\n <div class=\"absolute left-1 top-1 rounded bg-slate-900/85 px-2 py-1 text-[10px] font-semibold text-white\">#{{ i + 1 }}</div>\n <button type=\"button\" class=\"absolute right-1 top-1 rounded bg-white/95 px-2 py-1 text-[10px] text-rose-600\" [disabled]=\"disabled\" (click)=\"remove(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n </div>\n }\n </div>\n }\n</label>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }] });
2434
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputPhotoComponent, isStandalone: true, selector: "input-photo", inputs: { label: "label", hint: "hint", error: "error", required: "required", accept: "accept", multiple: "multiple", limit: "limit", disabled: "disabled" }, outputs: { filesChange: "filesChange", uploadingChange: "uploadingChange" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputPhotoComponent), multi: true }], usesOnChanges: true, ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\">\n <input-photo-control [accept]=\"accept\" [multiple]=\"multiple\" [disabled]=\"disabled\" [canSelectMore]=\"canSelectMore\" [previews]=\"previews\" (filesSelected)=\"handleFilesSelected($event)\" (removed)=\"remove($event)\" (dropped)=\"drop($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputPhotoControlComponent, selector: "input-photo-control", inputs: ["accept", "multiple", "disabled", "canSelectMore", "previews"], outputs: ["filesSelected", "removed", "dropped", "touched"] }] });
1866
2435
  }
1867
2436
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPhotoComponent, decorators: [{
1868
2437
  type: Component,
1869
- args: [{ selector: 'input-photo', standalone: true, imports: [CommonModule, DragDropModule], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputPhotoComponent), multi: true }], template: "<label class=\"flex flex-col gap-2 text-[0.7rem] font-semibold uppercase tracking-[0.2em] text-slate-600\">\n @if (label) { <span>{{ label }}</span> }\n <button type=\"button\" class=\"flex min-h-24 items-center justify-center rounded-xl border border-dashed border-slate-300 bg-slate-50 px-4 py-3 text-xs font-semibold uppercase tracking-[0.15em] text-slate-600 transition hover:bg-slate-100 disabled:opacity-60\" [disabled]=\"disabled || !canSelectMore\" (click)=\"photoInput.click()\">\n {{ previews.length ? '\u0414\u043E\u0434\u0430\u0442\u0438 \u0449\u0435 \u0444\u0430\u0439\u043B' : '\u041E\u0431\u0440\u0430\u0442\u0438 \u0444\u0430\u0439\u043B' }}\n </button>\n <input #photoInput type=\"file\" class=\"hidden\" [attr.accept]=\"accept\" [attr.multiple]=\"multiple && canSelectMore ? '' : null\" [disabled]=\"disabled\" (change)=\"onFilesSelected($event)\" />\n @if (hint) { <span class=\"text-[0.7rem] font-normal normal-case tracking-normal text-slate-500\">{{ hint }}</span> }\n @if (previews.length) {\n <div class=\"grid grid-cols-2 gap-2 md:grid-cols-4\" cdkDropList cdkDropListOrientation=\"mixed\" [cdkDropListData]=\"previews\" (cdkDropListDropped)=\"drop($event)\">\n @for (preview of previews; track preview.url; let i = $index) {\n <div class=\"relative overflow-hidden rounded-lg border border-slate-200 bg-slate-50\" cdkDrag [cdkDragDisabled]=\"disabled || preview.source === 'file'\">\n @if (preview.isImage) { <img [src]=\"preview.url\" [alt]=\"preview.name\" class=\"h-28 w-full object-cover\" /> }\n @else { <div class=\"flex h-28 items-center justify-center px-2 text-center text-[0.65rem] text-slate-600\">{{ preview.name }}</div> }\n <div class=\"absolute left-1 top-1 rounded bg-slate-900/85 px-2 py-1 text-[10px] font-semibold text-white\">#{{ i + 1 }}</div>\n <button type=\"button\" class=\"absolute right-1 top-1 rounded bg-white/95 px-2 py-1 text-[10px] text-rose-600\" [disabled]=\"disabled\" (click)=\"remove(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n </div>\n }\n </div>\n }\n</label>\n" }]
2438
+ args: [{ selector: 'input-photo', standalone: true, imports: [CommonModule, DragDropModule, FieldFrameComponent, InputPhotoControlComponent], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputPhotoComponent), multi: true }], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\">\n <input-photo-control [accept]=\"accept\" [multiple]=\"multiple\" [disabled]=\"disabled\" [canSelectMore]=\"canSelectMore\" [previews]=\"previews\" (filesSelected)=\"handleFilesSelected($event)\" (removed)=\"remove($event)\" (dropped)=\"drop($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n" }]
1870
2439
  }], propDecorators: { label: [{
1871
2440
  type: Input
1872
2441
  }], hint: [{
1873
2442
  type: Input
2443
+ }], error: [{
2444
+ type: Input
2445
+ }], required: [{
2446
+ type: Input
1874
2447
  }], accept: [{
1875
2448
  type: Input
1876
2449
  }], multiple: [{
@@ -1879,17 +2452,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1879
2452
  type: Input
1880
2453
  }], disabled: [{
1881
2454
  type: Input
1882
- }], value: [{
1883
- type: Input
1884
- }], valueChange: [{
1885
- type: Output
1886
2455
  }], filesChange: [{
1887
2456
  type: Output
1888
2457
  }], uploadingChange: [{
1889
2458
  type: Output
1890
2459
  }] } });
1891
2460
 
1892
- class InputSelectComponent {
2461
+ class InputSelectControlComponent {
1893
2462
  closeIcon = X;
1894
2463
  chevronDownIcon = ChevronDown;
1895
2464
  checkIcon = Check;
@@ -1903,15 +2472,12 @@ class InputSelectComponent {
1903
2472
  name;
1904
2473
  data;
1905
2474
  required = false;
1906
- fullWidth = false;
1907
2475
  disabled = false;
1908
2476
  multiple = false;
1909
2477
  includeBlankOption = false;
1910
2478
  blankOptionLabel = 'Оберіть…';
1911
2479
  emptyStateLabel = 'Немає варіантів для вибору';
1912
2480
  searchable = true;
1913
- remoteSearch = false;
1914
- searchPlaceholder = 'Пошук...';
1915
2481
  _options = [];
1916
2482
  set options(value) {
1917
2483
  this._options = value ?? [];
@@ -2086,7 +2652,7 @@ class InputSelectComponent {
2086
2652
  }
2087
2653
  get visibleOptions() {
2088
2654
  const options = this.getRenderOptions();
2089
- if (!this.searchable || this.remoteSearch || !this.searchTerm.trim()) {
2655
+ if (!this.searchable || this.getDictionaryName() || !this.searchTerm.trim()) {
2090
2656
  return options;
2091
2657
  }
2092
2658
  const search = this.searchTerm.trim().toLowerCase();
@@ -2239,26 +2805,26 @@ class InputSelectComponent {
2239
2805
  loadSuggestItem(name, key) {
2240
2806
  return this.http.get(`/api/suggest/${encodeURIComponent(name)}/${encodeURIComponent(key)}`);
2241
2807
  }
2242
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2243
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputSelectComponent, isStandalone: true, selector: "input-select", inputs: { label: "label", placeholder: "placeholder", name: "name", data: "data", required: "required", fullWidth: "fullWidth", disabled: "disabled", multiple: ["multiple", "multiple", booleanAttribute], includeBlankOption: "includeBlankOption", blankOptionLabel: "blankOptionLabel", emptyStateLabel: "emptyStateLabel", searchable: "searchable", remoteSearch: "remoteSearch", searchPlaceholder: "searchPlaceholder", options: "options", error: "error", hint: "hint", triggerClass: "triggerClass", panelClass: "panelClass" }, outputs: { valueChange: "valueChange", searchChange: "searchChange" }, host: { listeners: { "document:click": "onDocumentClick()", "document:keydown.escape": "onEscape()" } }, providers: [
2808
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSelectControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2809
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputSelectControlComponent, isStandalone: true, selector: "input-select-control", inputs: { label: "label", placeholder: "placeholder", name: "name", data: "data", required: "required", disabled: "disabled", multiple: ["multiple", "multiple", booleanAttribute], includeBlankOption: "includeBlankOption", blankOptionLabel: "blankOptionLabel", emptyStateLabel: "emptyStateLabel", searchable: "searchable", options: "options", error: "error", hint: "hint", triggerClass: "triggerClass", panelClass: "panelClass" }, outputs: { valueChange: "valueChange", searchChange: "searchChange" }, host: { listeners: { "document:click": "onDocumentClick()", "document:keydown.escape": "onEscape()" } }, providers: [
2244
2810
  {
2245
2811
  provide: NG_VALUE_ACCESSOR,
2246
- useExisting: forwardRef(() => InputSelectComponent),
2812
+ useExisting: forwardRef(() => InputSelectControlComponent),
2247
2813
  multi: true,
2248
2814
  },
2249
- { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSelectComponent), multi: true },
2250
- ], viewQueries: [{ propertyName: "dropdownTrigger", first: true, predicate: ["dropdownTrigger"], descendants: true, read: ElementRef, static: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n>\n <!-- LABEL -->\n @if (label || (hint && !error)) {\n <div class=\"flex items-center justify-between\">\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) { <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span> }\n </span>\n\n @if (hint && !error) {\n <span class=\"text-[0.65rem] text-slate-400\">\n {{ hint }}\n </span>\n }\n </div>\n }\n\n <!-- SELECT -->\n <div class=\"relative\" [class.w-full]=\"fullWidth\">\n\n <button\n cdkOverlayOrigin\n #dropdownTrigger=\"cdkOverlayOrigin\"\n type=\"button\"\n class=\"flex min-h-10 w-full flex-nowrap items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 transition hover:border-slate-300 focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [class]=\"triggerClass\"\n [class.opacity-50]=\"disabled\"\n [disabled]=\"disabled\"\n (click)=\"toggleDropdown($event)\"\n >\n\n <span class=\"flex min-w-0 flex-1 flex-wrap items-center gap-2\" [class.-ml-2]=\"multiple\">\n <!-- MULTIPLE MODE -->\n @if (multiple) {\n\n @if (selectedOptions.length) {\n\n @for (option of selectedOptions; track option.value) {\n <span\n class=\"inline-flex h-6 shrink-0 items-center gap-1 rounded-md bg-slate-100 px-2 text-xs font-medium text-slate-700\"\n >\n {{ option.label }}\n\n <span\n role=\"button\"\n tabindex=\"0\"\n class=\"text-slate-400 hover:text-rose-500 transition-colors\"\n (click)=\"removeOption(option, $event)\"\n (keydown.enter)=\"removeOption(option, $event)\"\n (keydown.space)=\"$event.preventDefault(); removeOption(option, $event)\"\n >\n <lucide-icon [img]=\"closeIcon\" class=\"h-4 w-4 shrink-0\"></lucide-icon>\n </span>\n </span>\n }\n\n } @else {\n <span class=\"text-slate-400\">\n {{ placeholder ?? blankOptionLabel }}\n </span>\n }\n\n } @else {\n\n <!-- SINGLE MODE -->\n @if (selectedOptions[0]?.imageUrl) {\n <img [src]=\"selectedOptions[0]?.imageUrl\" alt=\"\" class=\"h-7 w-7 rounded object-cover\" />\n }\n <span class=\"min-w-0 truncate\">\n {{ selectedOptions[0]?.label ?? placeholder ?? blankOptionLabel }}\n </span>\n\n }\n </span>\n\n <lucide-icon [img]=\"chevronDownIcon\" class=\"shrink-0 text-xs text-slate-400\"></lucide-icon>\n\n </button>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"dropdownTrigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayHasBackdrop]=\"false\"\n [cdkConnectedOverlayPositions]=\"dropdownPositions\"\n [cdkConnectedOverlayViewportMargin]=\"popupViewportMargin\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayPush]=\"true\"\n (detach)=\"closeDropdown()\"\n >\n <!-- DROPDOWN -->\n <div\n class=\"z-50 w-full max-w-[calc(100vw-2rem)] rounded-xl border border-slate-200 bg-white shadow-lg\"\n [style.width.px]=\"popupWidth\"\n [style.max-width]=\"'calc(100vw - 2rem)'\"\n [class]=\"panelClass\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (searchable) {\n <div class=\"border-b border-slate-100 p-2\">\n <input\n type=\"text\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 outline-none transition focus:border-slate-900 focus:ring-2 focus:ring-slate-200\"\n [placeholder]=\"searchPlaceholder\"\n [ngModel]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange($event)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n />\n </div>\n }\n\n <div class=\"max-h-60 overflow-auto\">\n @if (!visibleOptions.length) {\n <div class=\"px-4 py-3 text-sm text-slate-500\">\n @if (isDictionaryLoading && data) {\n \u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F...\n } @else {\n {{ emptyStateLabel }}\n }\n </div>\n } @else {\n @for (option of visibleOptions; track trackOption($index, option)) {\n <button\n type=\"button\"\n class=\"flex w-full items-start justify-between gap-3 whitespace-normal break-words px-4 py-2 text-left text-sm hover:bg-slate-50\"\n [class.bg-slate-100]=\"isSelected(option)\"\n (click)=\"selectOption(option, $event)\"\n >\n <span class=\"flex min-w-0 flex-1 items-center gap-2\">\n @if (option.imageUrl) {\n <img [src]=\"option.imageUrl\" alt=\"\" class=\"h-8 w-8 shrink-0 rounded object-cover\" />\n }\n <span class=\"whitespace-normal break-words\">{{ option.label }}</span>\n </span>\n\n @if (isSelected(option)) {\n <lucide-icon [img]=\"checkIcon\" class=\"text-xs text-sky-500\"></lucide-icon>\n }\n </button>\n }\n }\n </div>\n\n </div>\n </ng-template>\n\n </div>\n\n @if (error) {\n <p class=\"text-[0.7rem] text-rose-500\">\n {{ error }}\n </p>\n }\n\n</div>\n", dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2815
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSelectControlComponent), multi: true },
2816
+ ], viewQueries: [{ propertyName: "dropdownTrigger", first: true, predicate: ["dropdownTrigger"], descendants: true, read: ElementRef, static: true }], usesOnChanges: true, ngImport: i0, template: " <div class=\"relative w-full\">\n\n <button\n cdkOverlayOrigin\n #dropdownTrigger=\"cdkOverlayOrigin\"\n type=\"button\"\n class=\"flex min-h-10 w-full flex-nowrap items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 transition hover:border-slate-300 focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [class]=\"triggerClass\"\n [class.opacity-50]=\"disabled\"\n [disabled]=\"disabled\"\n (click)=\"toggleDropdown($event)\"\n >\n\n <span class=\"flex min-w-0 flex-1 flex-wrap items-center gap-2\" [class.-ml-2]=\"multiple\">\n <!-- MULTIPLE MODE -->\n @if (multiple) {\n\n @if (selectedOptions.length) {\n\n @for (option of selectedOptions; track option.value) {\n <span\n class=\"inline-flex h-6 shrink-0 items-center gap-1 rounded-md bg-slate-100 px-2 text-xs font-medium text-slate-700\"\n >\n {{ option.label }}\n\n <span\n role=\"button\"\n tabindex=\"0\"\n class=\"text-slate-400 hover:text-rose-500 transition-colors\"\n (click)=\"removeOption(option, $event)\"\n (keydown.enter)=\"removeOption(option, $event)\"\n (keydown.space)=\"$event.preventDefault(); removeOption(option, $event)\"\n >\n <lucide-icon [img]=\"closeIcon\" class=\"h-4 w-4 shrink-0\"></lucide-icon>\n </span>\n </span>\n }\n\n } @else {\n <span class=\"text-slate-400\">\n {{ placeholder ?? blankOptionLabel }}\n </span>\n }\n\n } @else {\n\n <!-- SINGLE MODE -->\n @if (selectedOptions[0]?.imageUrl) {\n <img [src]=\"selectedOptions[0]?.imageUrl\" alt=\"\" class=\"h-7 w-7 rounded object-cover\" />\n }\n <span class=\"min-w-0 truncate\">\n {{ selectedOptions[0]?.label ?? placeholder ?? blankOptionLabel }}\n </span>\n\n }\n </span>\n\n <lucide-icon [img]=\"chevronDownIcon\" class=\"shrink-0 text-xs text-slate-400\"></lucide-icon>\n\n </button>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"dropdownTrigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayHasBackdrop]=\"false\"\n [cdkConnectedOverlayPositions]=\"dropdownPositions\"\n [cdkConnectedOverlayViewportMargin]=\"popupViewportMargin\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayPush]=\"true\"\n (detach)=\"closeDropdown()\"\n >\n <!-- DROPDOWN -->\n <div\n class=\"z-50 w-full max-w-[calc(100vw-2rem)] rounded-xl border border-slate-200 bg-white shadow-lg\"\n [style.width.px]=\"popupWidth\"\n [style.max-width]=\"'calc(100vw - 2rem)'\"\n [class]=\"panelClass\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (searchable) {\n <div class=\"border-b border-slate-100 p-2\">\n <input\n type=\"text\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 outline-none transition focus:border-slate-900 focus:ring-2 focus:ring-slate-200\"\n placeholder=\"\u041F\u043E\u0448\u0443\u043A...\"\n [ngModel]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange($event)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n />\n </div>\n }\n\n <div class=\"max-h-60 overflow-auto\">\n @if (!visibleOptions.length) {\n <div class=\"px-4 py-3 text-sm text-slate-500\">\n @if (isDictionaryLoading && data) {\n \u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F...\n } @else {\n {{ emptyStateLabel }}\n }\n </div>\n } @else {\n @for (option of visibleOptions; track trackOption($index, option)) {\n <button\n type=\"button\"\n class=\"flex w-full items-start justify-between gap-3 whitespace-normal break-words px-4 py-2 text-left text-sm hover:bg-slate-50\"\n [class.bg-slate-100]=\"isSelected(option)\"\n (click)=\"selectOption(option, $event)\"\n >\n <span class=\"flex min-w-0 flex-1 items-center gap-2\">\n @if (option.imageUrl) {\n <img [src]=\"option.imageUrl\" alt=\"\" class=\"h-8 w-8 shrink-0 rounded object-cover\" />\n }\n <span class=\"whitespace-normal break-words\">{{ option.label }}</span>\n </span>\n\n @if (isSelected(option)) {\n <lucide-icon [img]=\"checkIcon\" class=\"text-xs text-sky-500\"></lucide-icon>\n }\n </button>\n }\n }\n </div>\n\n </div>\n </ng-template>\n\n </div>\n", dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2251
2817
  }
2252
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSelectComponent, decorators: [{
2818
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSelectControlComponent, decorators: [{
2253
2819
  type: Component,
2254
- args: [{ selector: 'input-select', standalone: true, imports: [LucideAngularModule, CommonModule, FormsModule, CdkConnectedOverlay, CdkOverlayOrigin], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
2820
+ args: [{ selector: 'input-select-control', standalone: true, imports: [LucideAngularModule, CommonModule, FormsModule, CdkConnectedOverlay, CdkOverlayOrigin], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
2255
2821
  {
2256
2822
  provide: NG_VALUE_ACCESSOR,
2257
- useExisting: forwardRef(() => InputSelectComponent),
2823
+ useExisting: forwardRef(() => InputSelectControlComponent),
2258
2824
  multi: true,
2259
2825
  },
2260
- { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSelectComponent), multi: true },
2261
- ], template: "<div\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n>\n <!-- LABEL -->\n @if (label || (hint && !error)) {\n <div class=\"flex items-center justify-between\">\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) { <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span> }\n </span>\n\n @if (hint && !error) {\n <span class=\"text-[0.65rem] text-slate-400\">\n {{ hint }}\n </span>\n }\n </div>\n }\n\n <!-- SELECT -->\n <div class=\"relative\" [class.w-full]=\"fullWidth\">\n\n <button\n cdkOverlayOrigin\n #dropdownTrigger=\"cdkOverlayOrigin\"\n type=\"button\"\n class=\"flex min-h-10 w-full flex-nowrap items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 transition hover:border-slate-300 focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [class]=\"triggerClass\"\n [class.opacity-50]=\"disabled\"\n [disabled]=\"disabled\"\n (click)=\"toggleDropdown($event)\"\n >\n\n <span class=\"flex min-w-0 flex-1 flex-wrap items-center gap-2\" [class.-ml-2]=\"multiple\">\n <!-- MULTIPLE MODE -->\n @if (multiple) {\n\n @if (selectedOptions.length) {\n\n @for (option of selectedOptions; track option.value) {\n <span\n class=\"inline-flex h-6 shrink-0 items-center gap-1 rounded-md bg-slate-100 px-2 text-xs font-medium text-slate-700\"\n >\n {{ option.label }}\n\n <span\n role=\"button\"\n tabindex=\"0\"\n class=\"text-slate-400 hover:text-rose-500 transition-colors\"\n (click)=\"removeOption(option, $event)\"\n (keydown.enter)=\"removeOption(option, $event)\"\n (keydown.space)=\"$event.preventDefault(); removeOption(option, $event)\"\n >\n <lucide-icon [img]=\"closeIcon\" class=\"h-4 w-4 shrink-0\"></lucide-icon>\n </span>\n </span>\n }\n\n } @else {\n <span class=\"text-slate-400\">\n {{ placeholder ?? blankOptionLabel }}\n </span>\n }\n\n } @else {\n\n <!-- SINGLE MODE -->\n @if (selectedOptions[0]?.imageUrl) {\n <img [src]=\"selectedOptions[0]?.imageUrl\" alt=\"\" class=\"h-7 w-7 rounded object-cover\" />\n }\n <span class=\"min-w-0 truncate\">\n {{ selectedOptions[0]?.label ?? placeholder ?? blankOptionLabel }}\n </span>\n\n }\n </span>\n\n <lucide-icon [img]=\"chevronDownIcon\" class=\"shrink-0 text-xs text-slate-400\"></lucide-icon>\n\n </button>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"dropdownTrigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayHasBackdrop]=\"false\"\n [cdkConnectedOverlayPositions]=\"dropdownPositions\"\n [cdkConnectedOverlayViewportMargin]=\"popupViewportMargin\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayPush]=\"true\"\n (detach)=\"closeDropdown()\"\n >\n <!-- DROPDOWN -->\n <div\n class=\"z-50 w-full max-w-[calc(100vw-2rem)] rounded-xl border border-slate-200 bg-white shadow-lg\"\n [style.width.px]=\"popupWidth\"\n [style.max-width]=\"'calc(100vw - 2rem)'\"\n [class]=\"panelClass\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (searchable) {\n <div class=\"border-b border-slate-100 p-2\">\n <input\n type=\"text\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 outline-none transition focus:border-slate-900 focus:ring-2 focus:ring-slate-200\"\n [placeholder]=\"searchPlaceholder\"\n [ngModel]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange($event)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n />\n </div>\n }\n\n <div class=\"max-h-60 overflow-auto\">\n @if (!visibleOptions.length) {\n <div class=\"px-4 py-3 text-sm text-slate-500\">\n @if (isDictionaryLoading && data) {\n \u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F...\n } @else {\n {{ emptyStateLabel }}\n }\n </div>\n } @else {\n @for (option of visibleOptions; track trackOption($index, option)) {\n <button\n type=\"button\"\n class=\"flex w-full items-start justify-between gap-3 whitespace-normal break-words px-4 py-2 text-left text-sm hover:bg-slate-50\"\n [class.bg-slate-100]=\"isSelected(option)\"\n (click)=\"selectOption(option, $event)\"\n >\n <span class=\"flex min-w-0 flex-1 items-center gap-2\">\n @if (option.imageUrl) {\n <img [src]=\"option.imageUrl\" alt=\"\" class=\"h-8 w-8 shrink-0 rounded object-cover\" />\n }\n <span class=\"whitespace-normal break-words\">{{ option.label }}</span>\n </span>\n\n @if (isSelected(option)) {\n <lucide-icon [img]=\"checkIcon\" class=\"text-xs text-sky-500\"></lucide-icon>\n }\n </button>\n }\n }\n </div>\n\n </div>\n </ng-template>\n\n </div>\n\n @if (error) {\n <p class=\"text-[0.7rem] text-rose-500\">\n {{ error }}\n </p>\n }\n\n</div>\n" }]
2826
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSelectControlComponent), multi: true },
2827
+ ], template: " <div class=\"relative w-full\">\n\n <button\n cdkOverlayOrigin\n #dropdownTrigger=\"cdkOverlayOrigin\"\n type=\"button\"\n class=\"flex min-h-10 w-full flex-nowrap items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 transition hover:border-slate-300 focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [class]=\"triggerClass\"\n [class.opacity-50]=\"disabled\"\n [disabled]=\"disabled\"\n (click)=\"toggleDropdown($event)\"\n >\n\n <span class=\"flex min-w-0 flex-1 flex-wrap items-center gap-2\" [class.-ml-2]=\"multiple\">\n <!-- MULTIPLE MODE -->\n @if (multiple) {\n\n @if (selectedOptions.length) {\n\n @for (option of selectedOptions; track option.value) {\n <span\n class=\"inline-flex h-6 shrink-0 items-center gap-1 rounded-md bg-slate-100 px-2 text-xs font-medium text-slate-700\"\n >\n {{ option.label }}\n\n <span\n role=\"button\"\n tabindex=\"0\"\n class=\"text-slate-400 hover:text-rose-500 transition-colors\"\n (click)=\"removeOption(option, $event)\"\n (keydown.enter)=\"removeOption(option, $event)\"\n (keydown.space)=\"$event.preventDefault(); removeOption(option, $event)\"\n >\n <lucide-icon [img]=\"closeIcon\" class=\"h-4 w-4 shrink-0\"></lucide-icon>\n </span>\n </span>\n }\n\n } @else {\n <span class=\"text-slate-400\">\n {{ placeholder ?? blankOptionLabel }}\n </span>\n }\n\n } @else {\n\n <!-- SINGLE MODE -->\n @if (selectedOptions[0]?.imageUrl) {\n <img [src]=\"selectedOptions[0]?.imageUrl\" alt=\"\" class=\"h-7 w-7 rounded object-cover\" />\n }\n <span class=\"min-w-0 truncate\">\n {{ selectedOptions[0]?.label ?? placeholder ?? blankOptionLabel }}\n </span>\n\n }\n </span>\n\n <lucide-icon [img]=\"chevronDownIcon\" class=\"shrink-0 text-xs text-slate-400\"></lucide-icon>\n\n </button>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"dropdownTrigger\"\n [cdkConnectedOverlayOpen]=\"isOpen\"\n [cdkConnectedOverlayHasBackdrop]=\"false\"\n [cdkConnectedOverlayPositions]=\"dropdownPositions\"\n [cdkConnectedOverlayViewportMargin]=\"popupViewportMargin\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayPush]=\"true\"\n (detach)=\"closeDropdown()\"\n >\n <!-- DROPDOWN -->\n <div\n class=\"z-50 w-full max-w-[calc(100vw-2rem)] rounded-xl border border-slate-200 bg-white shadow-lg\"\n [style.width.px]=\"popupWidth\"\n [style.max-width]=\"'calc(100vw - 2rem)'\"\n [class]=\"panelClass\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (searchable) {\n <div class=\"border-b border-slate-100 p-2\">\n <input\n type=\"text\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-900 outline-none transition focus:border-slate-900 focus:ring-2 focus:ring-slate-200\"\n placeholder=\"\u041F\u043E\u0448\u0443\u043A...\"\n [ngModel]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange($event)\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.stopPropagation()\"\n />\n </div>\n }\n\n <div class=\"max-h-60 overflow-auto\">\n @if (!visibleOptions.length) {\n <div class=\"px-4 py-3 text-sm text-slate-500\">\n @if (isDictionaryLoading && data) {\n \u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0435\u043D\u043D\u044F...\n } @else {\n {{ emptyStateLabel }}\n }\n </div>\n } @else {\n @for (option of visibleOptions; track trackOption($index, option)) {\n <button\n type=\"button\"\n class=\"flex w-full items-start justify-between gap-3 whitespace-normal break-words px-4 py-2 text-left text-sm hover:bg-slate-50\"\n [class.bg-slate-100]=\"isSelected(option)\"\n (click)=\"selectOption(option, $event)\"\n >\n <span class=\"flex min-w-0 flex-1 items-center gap-2\">\n @if (option.imageUrl) {\n <img [src]=\"option.imageUrl\" alt=\"\" class=\"h-8 w-8 shrink-0 rounded object-cover\" />\n }\n <span class=\"whitespace-normal break-words\">{{ option.label }}</span>\n </span>\n\n @if (isSelected(option)) {\n <lucide-icon [img]=\"checkIcon\" class=\"text-xs text-sky-500\"></lucide-icon>\n }\n </button>\n }\n }\n </div>\n\n </div>\n </ng-template>\n\n </div>\n" }]
2262
2828
  }], ctorParameters: () => [], propDecorators: { label: [{
2263
2829
  type: Input
2264
2830
  }], placeholder: [{
@@ -2269,8 +2835,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
2269
2835
  type: Input
2270
2836
  }], required: [{
2271
2837
  type: Input
2272
- }], fullWidth: [{
2273
- type: Input
2274
2838
  }], disabled: [{
2275
2839
  type: Input
2276
2840
  }], multiple: [{
@@ -2284,10 +2848,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
2284
2848
  type: Input
2285
2849
  }], searchable: [{
2286
2850
  type: Input
2287
- }], remoteSearch: [{
2288
- type: Input
2289
- }], searchPlaceholder: [{
2290
- type: Input
2291
2851
  }], options: [{
2292
2852
  type: Input
2293
2853
  }], error: [{
@@ -2313,6 +2873,136 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
2313
2873
  args: ['document:keydown.escape']
2314
2874
  }] } });
2315
2875
 
2876
+ class InputSelectComponent {
2877
+ control;
2878
+ label;
2879
+ hint;
2880
+ error;
2881
+ placeholder;
2882
+ name;
2883
+ data;
2884
+ required = false;
2885
+ disabled = false;
2886
+ multiple = false;
2887
+ includeBlankOption = false;
2888
+ blankOptionLabel = 'Оберіть…';
2889
+ emptyStateLabel = 'Немає варіантів для вибору';
2890
+ searchable = true;
2891
+ options = [];
2892
+ triggerClass = '';
2893
+ panelClass = '';
2894
+ valueChange = new EventEmitter();
2895
+ searchChange = new EventEmitter();
2896
+ pendingValue = null;
2897
+ onChange = () => undefined;
2898
+ onTouched = () => undefined;
2899
+ ngAfterViewInit() {
2900
+ this.control?.registerOnChange((value) => {
2901
+ this.onChange(value);
2902
+ this.valueChange.emit(value);
2903
+ });
2904
+ this.control?.registerOnTouched(() => this.onTouched());
2905
+ this.control?.writeValue(this.pendingValue);
2906
+ }
2907
+ writeValue(value) {
2908
+ this.pendingValue = value;
2909
+ this.control?.writeValue(value);
2910
+ }
2911
+ registerOnChange(fn) { this.onChange = fn; }
2912
+ registerOnTouched(fn) { this.onTouched = fn; }
2913
+ setDisabledState(isDisabled) { this.disabled = isDisabled; }
2914
+ validate(control) { return validateFormControl(control, this.required); }
2915
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2916
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "21.2.19", type: InputSelectComponent, isStandalone: true, selector: "input-select", inputs: { label: "label", hint: "hint", error: "error", placeholder: "placeholder", name: "name", data: "data", required: "required", disabled: "disabled", multiple: ["multiple", "multiple", booleanAttribute], includeBlankOption: "includeBlankOption", blankOptionLabel: "blankOptionLabel", emptyStateLabel: "emptyStateLabel", searchable: "searchable", options: "options", triggerClass: "triggerClass", panelClass: "panelClass" }, outputs: { valueChange: "valueChange", searchChange: "searchChange" }, providers: [
2917
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputSelectComponent), multi: true },
2918
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSelectComponent), multi: true },
2919
+ ], viewQueries: [{ propertyName: "control", first: true, predicate: InputSelectControlComponent, descendants: true }], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [disabled]=\"disabled\">\n <input-select-control\n [placeholder]=\"placeholder\"\n [name]=\"name\"\n [data]=\"data\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [multiple]=\"multiple\"\n [includeBlankOption]=\"includeBlankOption\"\n [blankOptionLabel]=\"blankOptionLabel\"\n [emptyStateLabel]=\"emptyStateLabel\"\n [searchable]=\"searchable\"\n [options]=\"options\"\n [triggerClass]=\"triggerClass\"\n [panelClass]=\"panelClass\"\n (valueChange)=\"valueChange.emit($event)\"\n (searchChange)=\"searchChange.emit($event)\"\n />\n</field-frame>\n", dependencies: [{ kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputSelectControlComponent, selector: "input-select-control", inputs: ["label", "placeholder", "name", "data", "required", "disabled", "multiple", "includeBlankOption", "blankOptionLabel", "emptyStateLabel", "searchable", "options", "error", "hint", "triggerClass", "panelClass"], outputs: ["valueChange", "searchChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2920
+ }
2921
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSelectComponent, decorators: [{
2922
+ type: Component,
2923
+ args: [{ selector: 'input-select', standalone: true, imports: [FieldFrameComponent, InputSelectControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
2924
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputSelectComponent), multi: true },
2925
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSelectComponent), multi: true },
2926
+ ], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [disabled]=\"disabled\">\n <input-select-control\n [placeholder]=\"placeholder\"\n [name]=\"name\"\n [data]=\"data\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n [multiple]=\"multiple\"\n [includeBlankOption]=\"includeBlankOption\"\n [blankOptionLabel]=\"blankOptionLabel\"\n [emptyStateLabel]=\"emptyStateLabel\"\n [searchable]=\"searchable\"\n [options]=\"options\"\n [triggerClass]=\"triggerClass\"\n [panelClass]=\"panelClass\"\n (valueChange)=\"valueChange.emit($event)\"\n (searchChange)=\"searchChange.emit($event)\"\n />\n</field-frame>\n" }]
2927
+ }], propDecorators: { control: [{
2928
+ type: ViewChild,
2929
+ args: [InputSelectControlComponent]
2930
+ }], label: [{
2931
+ type: Input
2932
+ }], hint: [{
2933
+ type: Input
2934
+ }], error: [{
2935
+ type: Input
2936
+ }], placeholder: [{
2937
+ type: Input
2938
+ }], name: [{
2939
+ type: Input
2940
+ }], data: [{
2941
+ type: Input
2942
+ }], required: [{
2943
+ type: Input
2944
+ }], disabled: [{
2945
+ type: Input
2946
+ }], multiple: [{
2947
+ type: Input,
2948
+ args: [{ transform: booleanAttribute }]
2949
+ }], includeBlankOption: [{
2950
+ type: Input
2951
+ }], blankOptionLabel: [{
2952
+ type: Input
2953
+ }], emptyStateLabel: [{
2954
+ type: Input
2955
+ }], searchable: [{
2956
+ type: Input
2957
+ }], options: [{
2958
+ type: Input
2959
+ }], triggerClass: [{
2960
+ type: Input
2961
+ }], panelClass: [{
2962
+ type: Input
2963
+ }], valueChange: [{
2964
+ type: Output
2965
+ }], searchChange: [{
2966
+ type: Output
2967
+ }] } });
2968
+
2969
+ class InputSlugControlComponent {
2970
+ placeholder;
2971
+ name;
2972
+ id;
2973
+ autocomplete = 'off';
2974
+ disabled = false;
2975
+ value = '';
2976
+ valueChange = new EventEmitter();
2977
+ touched = new EventEmitter();
2978
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSlugControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2979
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputSlugControlComponent, isStandalone: true, selector: "input-slug-control", inputs: { placeholder: "placeholder", name: "name", id: "id", autocomplete: "autocomplete", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange", touched: "touched" }, ngImport: i0, template: `<input type="text" class="w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200" [attr.placeholder]="placeholder" [attr.name]="name" [attr.autocomplete]="autocomplete" [disabled]="disabled" [attr.id]="id" [value]="value" (input)="valueChange.emit($any($event.target).value)" (blur)="touched.emit()" />`, isInline: true });
2980
+ }
2981
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSlugControlComponent, decorators: [{
2982
+ type: Component,
2983
+ args: [{
2984
+ selector: 'input-slug-control',
2985
+ standalone: true,
2986
+ template: `<input type="text" class="w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200" [attr.placeholder]="placeholder" [attr.name]="name" [attr.autocomplete]="autocomplete" [disabled]="disabled" [attr.id]="id" [value]="value" (input)="valueChange.emit($any($event.target).value)" (blur)="touched.emit()" />`,
2987
+ }]
2988
+ }], propDecorators: { placeholder: [{
2989
+ type: Input
2990
+ }], name: [{
2991
+ type: Input
2992
+ }], id: [{
2993
+ type: Input
2994
+ }], autocomplete: [{
2995
+ type: Input
2996
+ }], disabled: [{
2997
+ type: Input
2998
+ }], value: [{
2999
+ type: Input
3000
+ }], valueChange: [{
3001
+ type: Output
3002
+ }], touched: [{
3003
+ type: Output
3004
+ }] } });
3005
+
2316
3006
  class InputSlugComponent {
2317
3007
  label;
2318
3008
  hint;
@@ -2320,7 +3010,6 @@ class InputSlugComponent {
2320
3010
  name;
2321
3011
  id;
2322
3012
  required = false;
2323
- fullWidth = false;
2324
3013
  autocomplete = 'off';
2325
3014
  error;
2326
3015
  invalidInput = new EventEmitter();
@@ -2355,6 +3044,13 @@ class InputSlugComponent {
2355
3044
  this.value = normalized;
2356
3045
  this.onChange(normalized);
2357
3046
  }
3047
+ handleValueChange(value) {
3048
+ const normalized = this.normalizeSlug(value);
3049
+ if (value !== normalized)
3050
+ this.invalidInput.emit();
3051
+ this.value = normalized;
3052
+ this.onChange(normalized);
3053
+ }
2358
3054
  normalizeSlug(value) {
2359
3055
  return (value ?? '')
2360
3056
  .toLowerCase()
@@ -2362,25 +3058,25 @@ class InputSlugComponent {
2362
3058
  .replace(/[^a-z0-9_]/g, '');
2363
3059
  }
2364
3060
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSlugComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2365
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputSlugComponent, isStandalone: true, selector: "input-slug", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", required: "required", fullWidth: "fullWidth", autocomplete: "autocomplete", error: "error" }, outputs: { invalidInput: "invalidInput" }, providers: [
3061
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputSlugComponent, isStandalone: true, selector: "input-slug", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", required: "required", autocomplete: "autocomplete", error: "error" }, outputs: { invalidInput: "invalidInput" }, providers: [
2366
3062
  {
2367
3063
  provide: NG_VALUE_ACCESSOR,
2368
3064
  useExisting: forwardRef(() => InputSlugComponent),
2369
3065
  multi: true,
2370
3066
  },
2371
3067
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSlugComponent), multi: true },
2372
- ], ngImport: i0, template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <input\n type=\"text\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [disabled]=\"disabled\"\n class=\"rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target))\"\n (blur)=\"onTouched()\"\n />\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3068
+ ], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\">\n <input-slug-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [autocomplete]=\"autocomplete\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"handleValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputSlugControlComponent, selector: "input-slug-control", inputs: ["placeholder", "name", "id", "autocomplete", "disabled", "value"], outputs: ["valueChange", "touched"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2373
3069
  }
2374
3070
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSlugComponent, decorators: [{
2375
3071
  type: Component,
2376
- args: [{ selector: 'input-slug', standalone: true, imports: [CommonModule, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
3072
+ args: [{ selector: 'input-slug', standalone: true, imports: [CommonModule, FormsModule, FieldFrameComponent, InputSlugControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
2377
3073
  {
2378
3074
  provide: NG_VALUE_ACCESSOR,
2379
3075
  useExisting: forwardRef(() => InputSlugComponent),
2380
3076
  multi: true,
2381
3077
  },
2382
3078
  { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSlugComponent), multi: true },
2383
- ], template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <input\n type=\"text\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [disabled]=\"disabled\"\n class=\"rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target))\"\n (blur)=\"onTouched()\"\n />\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n" }]
3079
+ ], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\">\n <input-slug-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [autocomplete]=\"autocomplete\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"handleValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n" }]
2384
3080
  }], propDecorators: { label: [{
2385
3081
  type: Input
2386
3082
  }], hint: [{
@@ -2393,8 +3089,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
2393
3089
  type: Input
2394
3090
  }], required: [{
2395
3091
  type: Input
2396
- }], fullWidth: [{
2397
- type: Input
2398
3092
  }], autocomplete: [{
2399
3093
  type: Input
2400
3094
  }], error: [{
@@ -2425,7 +3119,7 @@ class FieldInputComponent {
2425
3119
  value;
2426
3120
  error = '';
2427
3121
  disabled = false;
2428
- actions = [];
3122
+ visibleActions = [];
2429
3123
  valueChange = new EventEmitter();
2430
3124
  actionTriggered = new EventEmitter();
2431
3125
  lookupRequested = new EventEmitter();
@@ -2476,11 +3170,17 @@ class FieldInputComponent {
2476
3170
  if (!this.componentRef || !this.field)
2477
3171
  return;
2478
3172
  const field = this.field;
3173
+ const config = field;
2479
3174
  const setInput = (name, value) => this.componentRef?.setInput(name, value);
2480
3175
  const inputType = String(field.type || 'text');
2481
- const fullWidth = field.fullWidth !== false;
2482
- // Common presentation belongs to this wrapper, not to the concrete control.
2483
- // Only pass inputs that are actually declared by the selected component.
3176
+ if (field.label !== undefined)
3177
+ setInput('label', field.label);
3178
+ if (field.hint !== undefined)
3179
+ setInput('hint', field.hint);
3180
+ setInput('error', this.error);
3181
+ if (typeof field.required === 'boolean')
3182
+ setInput('required', field.required);
3183
+ // FieldInput only adapts the dynamic field to the selected control.
2484
3184
  switch (inputType) {
2485
3185
  case 'text':
2486
3186
  case 'number':
@@ -2489,57 +3189,44 @@ class FieldInputComponent {
2489
3189
  case 'email':
2490
3190
  case 'password':
2491
3191
  case 'textarea':
3192
+ setInput('actions', this.visibleActions);
2492
3193
  setInput('name', field.id);
2493
3194
  setInput('id', field.id);
2494
3195
  setInput('placeholder', field.placeholder);
2495
- setInput('pattern', field.pattern);
2496
- setInput('rows', field.rows ?? 4);
2497
- setInput('fullWidth', fullWidth);
3196
+ setInput('pattern', config['pattern']);
3197
+ setInput('rows', config['rows'] ?? 4);
2498
3198
  break;
2499
3199
  case 'slug':
2500
3200
  setInput('name', field.id);
2501
3201
  setInput('id', field.id);
2502
3202
  setInput('placeholder', field.placeholder);
2503
- setInput('fullWidth', fullWidth);
2504
3203
  break;
2505
3204
  case 'select':
2506
3205
  setInput('name', field.id);
2507
3206
  setInput('placeholder', field.placeholder);
2508
- setInput('data', field.data);
2509
- setInput('options', field.options || []);
2510
- setInput('multiple', !!field.multiple);
2511
- setInput('searchable', field.searchable ?? !!field.data);
2512
- setInput('remoteSearch', !!field.data);
2513
- setInput('searchPlaceholder', field.placeholder || 'Пошук...');
2514
- setInput('fullWidth', fullWidth);
3207
+ setInput('data', config['data']);
3208
+ setInput('options', config['options'] || []);
3209
+ setInput('multiple', !!config['multiple']);
3210
+ if (typeof config['searchable'] === 'boolean') {
3211
+ setInput('searchable', config['searchable']);
3212
+ }
2515
3213
  break;
2516
3214
  case 'color':
2517
3215
  setInput('name', field.id);
2518
3216
  setInput('id', field.id);
2519
- setInput('required', false);
2520
- setInput('fullWidth', fullWidth);
2521
3217
  break;
2522
3218
  case 'file':
2523
- setInput('accept', field.accept);
2524
- setInput('multiple', !!field.multiple);
3219
+ setInput('accept', config['accept']);
3220
+ setInput('multiple', !!config['multiple']);
2525
3221
  break;
2526
3222
  case 'photo':
2527
- setInput('accept', field.accept || 'image/*');
2528
- setInput('multiple', !!field.multiple);
2529
- setInput('limit', field.limit ?? 0);
3223
+ setInput('accept', config['accept'] || 'image/*');
3224
+ setInput('multiple', !!config['multiple']);
3225
+ setInput('limit', config['limit'] ?? 0);
2530
3226
  break;
2531
3227
  case 'barcode':
2532
- setInput('label', '');
2533
- setInput('hint', '');
2534
3228
  setInput('placeholder', field.placeholder);
2535
3229
  setInput('disabled', this.disabled);
2536
- setInput('autoOpen', field.autoOpen !== false);
2537
- setInput('searchButtonLabel', field.searchButtonLabel || 'Знайти');
2538
- setInput('openCameraLabel', field.openCameraLabel || 'Відкрити камеру');
2539
- setInput('closeCameraLabel', field.closeCameraLabel || 'Закрити камеру');
2540
- setInput('cameraTitle', field.cameraTitle || 'Камера');
2541
- setInput('cameraStartingLabel', field.cameraStartingLabel || 'Запускаємо камеру…');
2542
- setInput('cameraHint', field.cameraHint || 'Наведіть камеру на штрихкод. Після зчитування код підставиться автоматично.');
2543
3230
  setInput('value', this.value ?? '');
2544
3231
  break;
2545
3232
  case 'checkbox':
@@ -2550,84 +3237,18 @@ class FieldInputComponent {
2550
3237
  instance.setDisabledState?.(this.disabled);
2551
3238
  }
2552
3239
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FieldInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2553
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FieldInputComponent, isStandalone: true, selector: "field-input", inputs: { field: "field", value: "value", error: "error", disabled: "disabled", actions: "actions" }, outputs: { valueChange: "valueChange", actionTriggered: "actionTriggered", lookupRequested: "lookupRequested", barcodeScanned: "barcodeScanned", filesChange: "filesChange", uploadingChange: "uploadingChange" }, viewQueries: [{ propertyName: "inputContainer", first: true, predicate: ["inputContainer"], descendants: true, read: ViewContainerRef }], usesOnChanges: true, ngImport: i0, template: `
2554
- <div class="flex flex-col gap-1" [class.w-full]="field.fullWidth !== false">
2555
- @if (field.label) {
2556
- <label class="text-[0.7rem] font-semibold uppercase tracking-[0.2em] text-slate-600" [attr.for]="field.id">
2557
- {{ field.label }}
2558
- @if (field.required) { <span aria-hidden="true" class="text-rose-500"> *</span> }
2559
- </label>
2560
- }
2561
-
2562
- <div class="flex items-start gap-2" [class.rounded-xl]="!!error" [class.ring-2]="!!error" [class.ring-rose-200]="!!error">
2563
- <div class="min-w-0 w-full flex-1">
2564
- <ng-container #inputContainer></ng-container>
2565
- </div>
2566
-
2567
- @if (actions.length) {
2568
- <div class="flex flex-none items-center gap-2 pt-0.5">
2569
- @for (action of actions; track action.id) {
2570
- <ui-button
2571
- type="button"
2572
- [variant]="action.variant ?? 'secondary'"
2573
- [icon]="action.icon || ''"
2574
- [size]="action.size || 'sm'"
2575
- [disabled]="disabled"
2576
- (click)="actionTriggered.emit(action)"
2577
- >
2578
- {{ action.label }}
2579
- </ui-button>
2580
- }
2581
- </div>
2582
- }
2583
- </div>
2584
-
2585
- @if (field.hint) { <p class="text-[0.5rem] text-slate-500">{{ field.hint }}</p> }
2586
- @if (error) { <p class="text-[0.65rem] text-rose-500">{{ error }}</p> }
2587
- </div>
2588
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }] });
3240
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: FieldInputComponent, isStandalone: true, selector: "field-input", inputs: { field: "field", value: "value", error: "error", disabled: "disabled", visibleActions: "visibleActions" }, outputs: { valueChange: "valueChange", actionTriggered: "actionTriggered", lookupRequested: "lookupRequested", barcodeScanned: "barcodeScanned", filesChange: "filesChange", uploadingChange: "uploadingChange" }, viewQueries: [{ propertyName: "inputContainer", first: true, predicate: ["inputContainer"], descendants: true, read: ViewContainerRef }], usesOnChanges: true, ngImport: i0, template: `
3241
+ <ng-container #inputContainer></ng-container>
3242
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2589
3243
  }
2590
3244
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FieldInputComponent, decorators: [{
2591
3245
  type: Component,
2592
3246
  args: [{
2593
3247
  selector: 'field-input',
2594
3248
  standalone: true,
2595
- imports: [CommonModule, UiButtonComponent],
3249
+ imports: [CommonModule],
2596
3250
  template: `
2597
- <div class="flex flex-col gap-1" [class.w-full]="field.fullWidth !== false">
2598
- @if (field.label) {
2599
- <label class="text-[0.7rem] font-semibold uppercase tracking-[0.2em] text-slate-600" [attr.for]="field.id">
2600
- {{ field.label }}
2601
- @if (field.required) { <span aria-hidden="true" class="text-rose-500"> *</span> }
2602
- </label>
2603
- }
2604
-
2605
- <div class="flex items-start gap-2" [class.rounded-xl]="!!error" [class.ring-2]="!!error" [class.ring-rose-200]="!!error">
2606
- <div class="min-w-0 w-full flex-1">
2607
- <ng-container #inputContainer></ng-container>
2608
- </div>
2609
-
2610
- @if (actions.length) {
2611
- <div class="flex flex-none items-center gap-2 pt-0.5">
2612
- @for (action of actions; track action.id) {
2613
- <ui-button
2614
- type="button"
2615
- [variant]="action.variant ?? 'secondary'"
2616
- [icon]="action.icon || ''"
2617
- [size]="action.size || 'sm'"
2618
- [disabled]="disabled"
2619
- (click)="actionTriggered.emit(action)"
2620
- >
2621
- {{ action.label }}
2622
- </ui-button>
2623
- }
2624
- </div>
2625
- }
2626
- </div>
2627
-
2628
- @if (field.hint) { <p class="text-[0.5rem] text-slate-500">{{ field.hint }}</p> }
2629
- @if (error) { <p class="text-[0.65rem] text-rose-500">{{ error }}</p> }
2630
- </div>
3251
+ <ng-container #inputContainer></ng-container>
2631
3252
  `,
2632
3253
  }]
2633
3254
  }], propDecorators: { field: [{
@@ -2638,7 +3259,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
2638
3259
  type: Input
2639
3260
  }], disabled: [{
2640
3261
  type: Input
2641
- }], actions: [{
3262
+ }], visibleActions: [{
2642
3263
  type: Input
2643
3264
  }], valueChange: [{
2644
3265
  type: Output
@@ -2898,6 +3519,8 @@ class DynamicFormComponent {
2898
3519
  this.runFieldLookup(field);
2899
3520
  }
2900
3521
  runFieldLookup(field, overrideValue) {
3522
+ if (!isDynamicFormLookupField(field))
3523
+ return;
2901
3524
  const api = String(field.lookupApi || '').trim();
2902
3525
  if (!api)
2903
3526
  return;
@@ -3047,7 +3670,7 @@ class DynamicFormComponent {
3047
3670
  ].filter(Boolean).join(' ');
3048
3671
  }
3049
3672
  getContainerContentClass(field) {
3050
- const className = String(field.contentClassName || '').trim();
3673
+ const className = String(this.isContainerField(field) ? field.contentClassName || '' : '').trim();
3051
3674
  const layout = this.isContainerField(field) ? field.layout : undefined;
3052
3675
  return [
3053
3676
  layout === 'stack' ? 'space-y-4' : 'grid grid-cols-1 gap-4 md:grid-cols-12',
@@ -3090,6 +3713,8 @@ class DynamicFormComponent {
3090
3713
  return classes[span] || classes[12];
3091
3714
  }
3092
3715
  applyLookupResponse(field, response) {
3716
+ if (!isDynamicFormLookupField(field))
3717
+ return;
3093
3718
  const lookupMap = field.lookupMap || {};
3094
3719
  const mapped = Object.entries(lookupMap).reduce((acc, [targetField, sourcePath]) => {
3095
3720
  acc[targetField] = this.resolveTemplateValue(response, sourcePath);
@@ -3121,11 +3746,11 @@ class DynamicFormComponent {
3121
3746
  return this.getFlatFields().filter((field) => field.type !== 'container' && field.type !== 'template');
3122
3747
  }
3123
3748
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: DynamicFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3124
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: DynamicFormComponent, isStandalone: true, selector: "dynamic-form", inputs: { fields: "fields", value: "value", errors: "errors", submitLabel: "submitLabel", showSubmit: "showSubmit", disabled: "disabled" }, outputs: { valueChange: "valueChange", lookupCompleted: "lookupCompleted", submitted: "submitted", filesChange: "filesChange", uploadingChange: "uploadingChange", fieldActionTriggered: "fieldActionTriggered" }, providers: [UiFormContext, UiFormLookupService], usesOnChanges: true, ngImport: i0, template: "<form class=\"grid grid-cols-1 gap-4 md:grid-cols-12\" (ngSubmit)=\"submitForm()\" #dynamicForm=\"ngForm\">\n <ng-template #renderField let-field>\n @if (isFieldVisible(field)) {\n @if (isContainerField(field)) {\n @if (getContainerTag(field) === 'aside') {\n <aside [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <p class=\"text-xs font-semibold uppercase tracking-[0.22em] text-slate-500\">\n {{ field.label || field.title }}\n </p>\n }\n @if (field.hint) {\n <h3 class=\"mt-1 text-lg font-semibold text-slate-900\">{{ field.hint }}</h3>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </aside>\n } @else if (getContainerTag(field) === 'fieldset') {\n <fieldset [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <legend class=\"text-lg font-semibold text-slate-900\">{{ field.label || field.title }}</legend>\n }\n @if (field.hint) {\n <p class=\"mt-1 text-sm text-slate-500\">{{ field.hint }}</p>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </fieldset>\n } @else {\n <section [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <h3 class=\"text-lg font-semibold text-slate-900\">{{ field.label || field.title }}</h3>\n }\n @if (field.hint) {\n <p class=\"mt-1 text-sm text-slate-500\">{{ field.hint }}</p>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </section>\n }\n } @else if (isTemplateField(field)) {\n <div [ngClass]=\"resolveClassName(field.className)\" [innerHTML]=\"renderTemplate(field)\"></div>\n } @else {\n <div [ngClass]=\"getFieldClass(field)\">\n <field-input\n [field]=\"field\"\n [value]=\"modelSignal()[field.id]\"\n [disabled]=\"disabled || isFieldDisabled(field) || isLookupLoading(field.id)\"\n [error]=\"errors[field.id] ?? getLookupError(field.id)\"\n [actions]=\"resolveFieldActions(field)\"\n (valueChange)=\"onFieldValueChange(field, $event)\"\n (actionTriggered)=\"onFieldActionTriggered(field.id, $event)\"\n (lookupRequested)=\"runBarcodeLookup(field)\"\n (barcodeScanned)=\"onBarcodeScanned(field, $event)\"\n (filesChange)=\"filesChange.emit({ fieldId: field.id, files: $event })\"\n (uploadingChange)=\"uploadingChange.emit($event)\"\n ></field-input>\n </div>\n }\n }\n </ng-template>\n\n @for (field of fields; track field.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: field }\"></ng-container>\n }\n\n @if (showSubmit) {\n <div class=\"flex justify-end md:col-span-12\">\n <button\n type=\"submit\"\n class=\"inline-flex items-center rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-slate-800 disabled:opacity-50\"\n [disabled]=\"disabled || !dynamicForm.form.valid\"\n >\n {{ submitLabel }}\n </button>\n </div>\n }\n</form>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }, { kind: "component", type: FieldInputComponent, selector: "field-input", inputs: ["field", "value", "error", "disabled", "actions"], outputs: ["valueChange", "actionTriggered", "lookupRequested", "barcodeScanned", "filesChange", "uploadingChange"] }] });
3749
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: DynamicFormComponent, isStandalone: true, selector: "dynamic-form", inputs: { fields: "fields", value: "value", errors: "errors", submitLabel: "submitLabel", showSubmit: "showSubmit", disabled: "disabled" }, outputs: { valueChange: "valueChange", lookupCompleted: "lookupCompleted", submitted: "submitted", filesChange: "filesChange", uploadingChange: "uploadingChange", fieldActionTriggered: "fieldActionTriggered" }, providers: [UiFormContext, UiFormLookupService], usesOnChanges: true, ngImport: i0, template: "<form class=\"grid grid-cols-1 gap-4 md:grid-cols-12\" (ngSubmit)=\"submitForm()\" #dynamicForm=\"ngForm\">\n <ng-template #renderField let-field>\n @if (isFieldVisible(field)) {\n @if (isContainerField(field)) {\n @if (getContainerTag(field) === 'aside') {\n <aside [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <p class=\"text-xs font-semibold uppercase tracking-[0.22em] text-slate-500\">\n {{ field.label || field.title }}\n </p>\n }\n @if (field.hint) {\n <h3 class=\"mt-1 text-lg font-semibold text-slate-900\">{{ field.hint }}</h3>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </aside>\n } @else if (getContainerTag(field) === 'fieldset') {\n <fieldset [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <legend class=\"text-lg font-semibold text-slate-900\">{{ field.label || field.title }}</legend>\n }\n @if (field.hint) {\n <p class=\"mt-1 text-sm text-slate-500\">{{ field.hint }}</p>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </fieldset>\n } @else {\n <section [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <h3 class=\"text-lg font-semibold text-slate-900\">{{ field.label || field.title }}</h3>\n }\n @if (field.hint) {\n <p class=\"mt-1 text-sm text-slate-500\">{{ field.hint }}</p>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </section>\n }\n } @else if (isTemplateField(field)) {\n <div [ngClass]=\"resolveClassName(field.className)\" [innerHTML]=\"renderTemplate(field)\"></div>\n } @else {\n <div [ngClass]=\"getFieldClass(field)\">\n <field-input\n [field]=\"field\"\n [value]=\"modelSignal()[field.id]\"\n [disabled]=\"disabled || isFieldDisabled(field) || isLookupLoading(field.id)\"\n [error]=\"errors[field.id] ?? getLookupError(field.id)\"\n [visibleActions]=\"resolveFieldActions(field)\"\n (valueChange)=\"onFieldValueChange(field, $event)\"\n (actionTriggered)=\"onFieldActionTriggered(field.id, $event)\"\n (lookupRequested)=\"runBarcodeLookup(field)\"\n (barcodeScanned)=\"onBarcodeScanned(field, $event)\"\n (filesChange)=\"filesChange.emit({ fieldId: field.id, files: $event })\"\n (uploadingChange)=\"uploadingChange.emit($event)\"\n ></field-input>\n </div>\n }\n }\n </ng-template>\n\n @for (field of fields; track field.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: field }\"></ng-container>\n }\n\n @if (showSubmit) {\n <div class=\"flex justify-end md:col-span-12\">\n <button\n type=\"submit\"\n class=\"inline-flex items-center rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-slate-800 disabled:opacity-50\"\n [disabled]=\"disabled || !dynamicForm.form.valid\"\n >\n {{ submitLabel }}\n </button>\n </div>\n }\n</form>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }, { kind: "component", type: FieldInputComponent, selector: "field-input", inputs: ["field", "value", "error", "disabled", "visibleActions"], outputs: ["valueChange", "actionTriggered", "lookupRequested", "barcodeScanned", "filesChange", "uploadingChange"] }] });
3125
3750
  }
3126
3751
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: DynamicFormComponent, decorators: [{
3127
3752
  type: Component,
3128
- args: [{ selector: 'dynamic-form', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, FieldInputComponent], providers: [UiFormContext, UiFormLookupService], template: "<form class=\"grid grid-cols-1 gap-4 md:grid-cols-12\" (ngSubmit)=\"submitForm()\" #dynamicForm=\"ngForm\">\n <ng-template #renderField let-field>\n @if (isFieldVisible(field)) {\n @if (isContainerField(field)) {\n @if (getContainerTag(field) === 'aside') {\n <aside [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <p class=\"text-xs font-semibold uppercase tracking-[0.22em] text-slate-500\">\n {{ field.label || field.title }}\n </p>\n }\n @if (field.hint) {\n <h3 class=\"mt-1 text-lg font-semibold text-slate-900\">{{ field.hint }}</h3>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </aside>\n } @else if (getContainerTag(field) === 'fieldset') {\n <fieldset [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <legend class=\"text-lg font-semibold text-slate-900\">{{ field.label || field.title }}</legend>\n }\n @if (field.hint) {\n <p class=\"mt-1 text-sm text-slate-500\">{{ field.hint }}</p>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </fieldset>\n } @else {\n <section [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <h3 class=\"text-lg font-semibold text-slate-900\">{{ field.label || field.title }}</h3>\n }\n @if (field.hint) {\n <p class=\"mt-1 text-sm text-slate-500\">{{ field.hint }}</p>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </section>\n }\n } @else if (isTemplateField(field)) {\n <div [ngClass]=\"resolveClassName(field.className)\" [innerHTML]=\"renderTemplate(field)\"></div>\n } @else {\n <div [ngClass]=\"getFieldClass(field)\">\n <field-input\n [field]=\"field\"\n [value]=\"modelSignal()[field.id]\"\n [disabled]=\"disabled || isFieldDisabled(field) || isLookupLoading(field.id)\"\n [error]=\"errors[field.id] ?? getLookupError(field.id)\"\n [actions]=\"resolveFieldActions(field)\"\n (valueChange)=\"onFieldValueChange(field, $event)\"\n (actionTriggered)=\"onFieldActionTriggered(field.id, $event)\"\n (lookupRequested)=\"runBarcodeLookup(field)\"\n (barcodeScanned)=\"onBarcodeScanned(field, $event)\"\n (filesChange)=\"filesChange.emit({ fieldId: field.id, files: $event })\"\n (uploadingChange)=\"uploadingChange.emit($event)\"\n ></field-input>\n </div>\n }\n }\n </ng-template>\n\n @for (field of fields; track field.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: field }\"></ng-container>\n }\n\n @if (showSubmit) {\n <div class=\"flex justify-end md:col-span-12\">\n <button\n type=\"submit\"\n class=\"inline-flex items-center rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-slate-800 disabled:opacity-50\"\n [disabled]=\"disabled || !dynamicForm.form.valid\"\n >\n {{ submitLabel }}\n </button>\n </div>\n }\n</form>\n" }]
3753
+ args: [{ selector: 'dynamic-form', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, FieldInputComponent], providers: [UiFormContext, UiFormLookupService], template: "<form class=\"grid grid-cols-1 gap-4 md:grid-cols-12\" (ngSubmit)=\"submitForm()\" #dynamicForm=\"ngForm\">\n <ng-template #renderField let-field>\n @if (isFieldVisible(field)) {\n @if (isContainerField(field)) {\n @if (getContainerTag(field) === 'aside') {\n <aside [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <p class=\"text-xs font-semibold uppercase tracking-[0.22em] text-slate-500\">\n {{ field.label || field.title }}\n </p>\n }\n @if (field.hint) {\n <h3 class=\"mt-1 text-lg font-semibold text-slate-900\">{{ field.hint }}</h3>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </aside>\n } @else if (getContainerTag(field) === 'fieldset') {\n <fieldset [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <legend class=\"text-lg font-semibold text-slate-900\">{{ field.label || field.title }}</legend>\n }\n @if (field.hint) {\n <p class=\"mt-1 text-sm text-slate-500\">{{ field.hint }}</p>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </fieldset>\n } @else {\n <section [ngClass]=\"getContainerClass(field)\">\n @if (field.label || field.title || getActions(field).length || field.hint) {\n <header class=\"flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n <div class=\"min-w-0\">\n @if (field.label || field.title) {\n <h3 class=\"text-lg font-semibold text-slate-900\">{{ field.label || field.title }}</h3>\n }\n @if (field.hint) {\n <p class=\"mt-1 text-sm text-slate-500\">{{ field.hint }}</p>\n }\n </div>\n\n @if (getActions(field).length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of getActions(field); track action.id; let i = $index) {\n <ui-button\n [variant]=\"action.variant || (i === 0 ? 'primary' : 'ghost')\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"onFieldActionTriggered(field.id, action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n\n <div [ngClass]=\"getContainerContentClass(field)\">\n @for (child of field.fields; track child.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: child }\"></ng-container>\n }\n </div>\n </section>\n }\n } @else if (isTemplateField(field)) {\n <div [ngClass]=\"resolveClassName(field.className)\" [innerHTML]=\"renderTemplate(field)\"></div>\n } @else {\n <div [ngClass]=\"getFieldClass(field)\">\n <field-input\n [field]=\"field\"\n [value]=\"modelSignal()[field.id]\"\n [disabled]=\"disabled || isFieldDisabled(field) || isLookupLoading(field.id)\"\n [error]=\"errors[field.id] ?? getLookupError(field.id)\"\n [visibleActions]=\"resolveFieldActions(field)\"\n (valueChange)=\"onFieldValueChange(field, $event)\"\n (actionTriggered)=\"onFieldActionTriggered(field.id, $event)\"\n (lookupRequested)=\"runBarcodeLookup(field)\"\n (barcodeScanned)=\"onBarcodeScanned(field, $event)\"\n (filesChange)=\"filesChange.emit({ fieldId: field.id, files: $event })\"\n (uploadingChange)=\"uploadingChange.emit($event)\"\n ></field-input>\n </div>\n }\n }\n </ng-template>\n\n @for (field of fields; track field.id) {\n <ng-container *ngTemplateOutlet=\"renderField; context: { $implicit: field }\"></ng-container>\n }\n\n @if (showSubmit) {\n <div class=\"flex justify-end md:col-span-12\">\n <button\n type=\"submit\"\n class=\"inline-flex items-center rounded-xl bg-slate-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-slate-800 disabled:opacity-50\"\n [disabled]=\"disabled || !dynamicForm.form.valid\"\n >\n {{ submitLabel }}\n </button>\n </div>\n }\n</form>\n" }]
3129
3754
  }], propDecorators: { fields: [{
3130
3755
  type: Input
3131
3756
  }], value: [{
@@ -3353,6 +3978,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3353
3978
  args: [{ selector: 'form-modal', standalone: true, imports: [CommonModule, UiButtonComponent, DynamicFormComponent], template: "@if (isOpen()) {\n <div class=\"fixed inset-0 z-50 flex min-h-full items-center justify-center overflow-y-auto bg-slate-950/40 p-4 sm:p-6\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"my-0 flex min-h-0 max-h-[calc(100vh-2rem)] w-full flex-col overflow-hidden rounded-2xl bg-slate-50 shadow-2xl sm:max-h-[calc(100vh-3rem)]\" [ngClass]=\"panelClass()\">\n <div class=\"flex shrink-0 items-center justify-between border-b border-slate-200 bg-white px-5 py-4 sm:px-6\">\n <h2 class=\"text-lg font-semibold\">{{ formConfig()?.title || 'Form' }}</h2>\n <button type=\"button\" class=\"text-xl text-slate-400 hover:text-slate-900\" aria-label=\"Close\" (click)=\"close()\">\u00D7</button>\n </div>\n <div class=\"min-h-0 flex-1 overflow-y-auto p-4 sm:p-6\">\n @if (isLoading()) {\n <p class=\"text-sm text-slate-500\">Loading form\u2026</p>\n } @else {\n <dynamic-form [fields]=\"fields()\" [value]=\"formValue()\" [disabled]=\"isLoading() || isSubmitting() || isActionRunning()\" [showSubmit]=\"false\" (valueChange)=\"onValueChange($event)\" (lookupCompleted)=\"onLookupCompleted($event)\" (uploadingChange)=\"onUploadingChange($event)\" (fieldActionTriggered)=\"onFieldActionTriggered($event)\"></dynamic-form>\n }\n </div>\n <div class=\"relative z-10 flex shrink-0 justify-end gap-3 border-t border-slate-200 bg-white px-5 py-4 sm:px-6\">\n <ui-button variant=\"ghost\" (click)=\"close()\">{{ cancelLabel() }}</ui-button>\n @if (submitAction()) { <ui-button variant=\"primary\" [disabled]=\"isLoading() || isSubmitting() || isUploading()\" (click)=\"confirm()\">{{ submitLabel() }}</ui-button> }\n </div>\n </div>\n </div>\n}\n" }]
3354
3979
  }], propDecorators: { formId: [{ type: i0.Input, args: [{ isSignal: true, alias: "formId", required: false }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], initialValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialValue", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], completed: [{ type: i0.Output, args: ["completed"] }] } });
3355
3980
 
3981
+ class InputBaseControlComponent {
3982
+ placeholder;
3983
+ name;
3984
+ id;
3985
+ type = 'text';
3986
+ element = 'input';
3987
+ rows = 4;
3988
+ autocomplete;
3989
+ pattern;
3990
+ disabled = false;
3991
+ value = '';
3992
+ valueChange = new EventEmitter();
3993
+ touched = new EventEmitter();
3994
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBaseControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3995
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputBaseControlComponent, isStandalone: true, selector: "input-base-control", inputs: { placeholder: "placeholder", name: "name", id: "id", type: "type", element: "element", rows: "rows", autocomplete: "autocomplete", pattern: "pattern", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange", touched: "touched" }, ngImport: i0, template: `<input-text-control [placeholder]="placeholder" [name]="name" [id]="id" [type]="type" [element]="element" [rows]="rows" [autocomplete]="autocomplete" [pattern]="pattern" [disabled]="disabled" [value]="value" (valueChange)="valueChange.emit($event)" (touched)="touched.emit()" />`, isInline: true, dependencies: [{ kind: "component", type: InputTextControlComponent, selector: "input-text-control", inputs: ["placeholder", "name", "id", "type", "element", "rows", "autocomplete", "pattern", "disabled", "value"], outputs: ["valueChange", "touched"] }] });
3996
+ }
3997
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBaseControlComponent, decorators: [{
3998
+ type: Component,
3999
+ args: [{ selector: 'input-base-control', standalone: true, imports: [InputTextControlComponent], template: `<input-text-control [placeholder]="placeholder" [name]="name" [id]="id" [type]="type" [element]="element" [rows]="rows" [autocomplete]="autocomplete" [pattern]="pattern" [disabled]="disabled" [value]="value" (valueChange)="valueChange.emit($event)" (touched)="touched.emit()" />` }]
4000
+ }], propDecorators: { placeholder: [{
4001
+ type: Input
4002
+ }], name: [{
4003
+ type: Input
4004
+ }], id: [{
4005
+ type: Input
4006
+ }], type: [{
4007
+ type: Input
4008
+ }], element: [{
4009
+ type: Input
4010
+ }], rows: [{
4011
+ type: Input
4012
+ }], autocomplete: [{
4013
+ type: Input
4014
+ }], pattern: [{
4015
+ type: Input
4016
+ }], disabled: [{
4017
+ type: Input
4018
+ }], value: [{
4019
+ type: Input
4020
+ }], valueChange: [{
4021
+ type: Output
4022
+ }], touched: [{
4023
+ type: Output
4024
+ }] } });
4025
+
3356
4026
  class InputComponent {
3357
4027
  cdr = inject(ChangeDetectorRef);
3358
4028
  label;
@@ -3364,7 +4034,6 @@ class InputComponent {
3364
4034
  element = 'input';
3365
4035
  rows = 4;
3366
4036
  required = false;
3367
- fullWidth = false;
3368
4037
  autocomplete;
3369
4038
  pattern;
3370
4039
  error;
@@ -3402,23 +4071,23 @@ class InputComponent {
3402
4071
  this.actionTriggered.emit(action);
3403
4072
  }
3404
4073
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3405
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputComponent, isStandalone: true, selector: "input-base", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", type: "type", element: "element", rows: "rows", required: "required", fullWidth: "fullWidth", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
4074
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputComponent, isStandalone: true, selector: "input-base", inputs: { label: "label", hint: "hint", placeholder: "placeholder", name: "name", id: "id", type: "type", element: "element", rows: "rows", required: "required", autocomplete: "autocomplete", pattern: "pattern", error: "error", actions: "actions" }, outputs: { actionTriggered: "actionTriggered" }, providers: [
3406
4075
  {
3407
4076
  provide: NG_VALUE_ACCESSOR,
3408
4077
  useExisting: forwardRef(() => InputComponent),
3409
4078
  multi: true,
3410
4079
  },
3411
- ], ngImport: i0, template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4080
+ ], ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"triggerAction($event)\">\n <input-base-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [type]=\"type\" [element]=\"element\" [rows]=\"rows\" [autocomplete]=\"autocomplete\" [pattern]=\"pattern\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"onValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }, { kind: "component", type: InputBaseControlComponent, selector: "input-base-control", inputs: ["placeholder", "name", "id", "type", "element", "rows", "autocomplete", "pattern", "disabled", "value"], outputs: ["valueChange", "touched"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3412
4081
  }
3413
4082
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputComponent, decorators: [{
3414
4083
  type: Component,
3415
- args: [{ selector: 'input-base', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
4084
+ args: [{ selector: 'input-base', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, FieldFrameComponent, InputBaseControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
3416
4085
  {
3417
4086
  provide: NG_VALUE_ACCESSOR,
3418
4087
  useExisting: forwardRef(() => InputComponent),
3419
4088
  multi: true,
3420
4089
  },
3421
- ], template: "<label\n class=\"flex flex-col gap-1 text-[0.65rem] uppercase tracking-[0.3em] text-slate-500\"\n [class.w-full]=\"fullWidth\"\n [attr.for]=\"controlId\"\n>\n @if (label) {\n <span class=\"text-[0.7rem] font-semibold text-slate-600\">\n {{ label }}\n @if (required) {\n <span aria-hidden=\"true\" class=\"text-rose-500\"> *</span>\n }\n </span>\n }\n\n <div class=\"flex items-end gap-2\">\n <div class=\"min-w-0 flex-1\">\n @if (element === 'textarea') {\n <textarea\n [attr.rows]=\"rows\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.id]=\"controlId\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n ></textarea>\n } @else {\n <input\n [attr.type]=\"type\"\n [attr.placeholder]=\"placeholder\"\n [attr.name]=\"name\"\n [attr.autocomplete]=\"autocomplete\"\n [attr.pattern]=\"pattern\"\n [disabled]=\"disabled\"\n class=\"w-full rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm text-slate-900 placeholder:text-slate-400 transition focus:border-slate-900 focus:outline-none focus:ring-2 focus:ring-slate-200\"\n [attr.id]=\"controlId\"\n [value]=\"value\"\n (input)=\"onValueChange($any($event.target).value)\"\n (blur)=\"onTouched()\"\n />\n }\n </div>\n\n @if (actions.length) {\n <div class=\"flex flex-none items-center gap-2 pb-0.5\">\n @for (action of actions; track action.id) {\n <ui-button\n type=\"button\"\n [variant]=\"action.variant ?? 'secondary'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"disabled\"\n (click)=\"triggerAction(action)\"\n >\n {{ action.label }}\n </ui-button>\n }\n </div>\n }\n </div>\n\n @if (hint) {\n <p class=\"text-[0.5rem] text-slate-500\">{{ hint }}</p>\n }\n @if (error) {\n <p class=\"text-[0.5rem] text-rose-400\">{{ error }}</p>\n }\n</label>\n\n\n" }]
4090
+ ], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\" [forId]=\"controlId\" [disabled]=\"disabled\" [actions]=\"actions\" (actionTriggered)=\"triggerAction($event)\">\n <input-base-control [placeholder]=\"placeholder\" [name]=\"name\" [id]=\"controlId\" [type]=\"type\" [element]=\"element\" [rows]=\"rows\" [autocomplete]=\"autocomplete\" [pattern]=\"pattern\" [disabled]=\"disabled\" [value]=\"value\" (valueChange)=\"onValueChange($event)\" (touched)=\"onTouched()\" />\n</field-frame>\n" }]
3422
4091
  }], propDecorators: { label: [{
3423
4092
  type: Input
3424
4093
  }], hint: [{
@@ -3437,8 +4106,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3437
4106
  type: Input
3438
4107
  }], required: [{
3439
4108
  type: Input
3440
- }], fullWidth: [{
3441
- type: Input
3442
4109
  }], autocomplete: [{
3443
4110
  type: Input
3444
4111
  }], pattern: [{
@@ -3454,6 +4121,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3454
4121
  class InputPhotoGalleryComponent {
3455
4122
  http = inject(HttpClient);
3456
4123
  label = 'Фото';
4124
+ hint;
4125
+ error;
4126
+ required = false;
3457
4127
  orderLabel = 'Порядок фото (перетягуй)';
3458
4128
  imageIds = [];
3459
4129
  disabled = false;
@@ -3594,13 +4264,19 @@ class InputPhotoGalleryComponent {
3594
4264
  return this.http.post('/api/files/', formData).pipe(map((response) => ({ imageId: response.id })));
3595
4265
  }
3596
4266
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPhotoGalleryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3597
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputPhotoGalleryComponent, isStandalone: true, selector: "input-photo-gallery", inputs: { label: "label", orderLabel: "orderLabel", imageIds: "imageIds", disabled: "disabled", afterUpload: "afterUpload", resolveImageUrl: "resolveImageUrl", notify: "notify" }, outputs: { imageIdsChange: "imageIdsChange" }, usesOnChanges: true, ngImport: i0, template: "<label class=\"flex flex-col gap-1 md:col-span-12\">\n <span class=\"text-sm\">{{ label }}</span>\n <div class=\"text-xs text-slate-500\">\u041A\u0456\u043B\u044C\u043A\u0456\u0441\u0442\u044C \u0444\u043E\u0442\u043E: {{ photoCount }}</div>\n</label>\n\n<div class=\"md:col-span-12\">\n <p class=\"mb-2 text-xs uppercase tracking-[0.2em] text-slate-500\">{{ orderLabel }}</p>\n <div class=\"grid gap-3 md:grid-cols-4\" cdkDropList [cdkDropListData]=\"entries\" (cdkDropListDropped)=\"onDrop($event)\">\n @for (entry of entries; let i = $index; track entry.clientKey + '-' + i) {\n <div class=\"image-drag-item relative overflow-hidden rounded-xl border border-slate-200 bg-white\" cdkDrag [cdkDragDisabled]=\"disabled || (!entry.imageId && !entry.file)\">\n <label class=\"block h-full w-full cursor-pointer\">\n @if (entry.preview) {\n <img [src]=\"entry.preview\" alt=\"photo\" class=\"h-48 w-full object-cover\" />\n } @else {\n <button type=\"button\" class=\"flex h-48 w-full items-center justify-center bg-slate-50 text-xs text-slate-500\" (click)=\"openFilePicker(fileInput, $event)\">+ \u0414\u043E\u0434\u0430\u0442\u0438 \u0444\u043E\u0442\u043E</button>\n }\n <input #fileInput type=\"file\" class=\"hidden\" [disabled]=\"disabled\" multiple (change)=\"onFilesChange($event, i)\" />\n </label>\n @if (entry.imageId || entry.file) {\n @if (i === 0) {\n <span class=\"absolute left-2 top-2 rounded-full border border-slate-200 bg-white/95 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.15em] text-slate-700\">\u041E\u0431\u043A\u043B\u0430\u0434\u0438\u043D\u043A\u0430</span>\n }\n <span class=\"absolute left-2 bottom-2 rounded-full bg-slate-900/85 px-2 py-1 text-[10px] font-semibold text-white\">#{{ i + 1 }}</span>\n <button type=\"button\" class=\"absolute right-2 top-2 rounded-full bg-white/95 px-2 py-1 text-xs text-rose-600 shadow\" [disabled]=\"disabled\" (click)=\"removeAt(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n }\n </div>\n }\n </div>\n</div>\n", styles: [".image-drag-item.cdk-drag-preview{transform:scale(1.03) rotate(.6deg);box-shadow:0 28px 60px #0f172a66;border-radius:.9rem}.image-drag-item.cdk-drag-placeholder{opacity:.4;outline:2px dashed #94a3b8;outline-offset:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }] });
4267
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InputPhotoGalleryComponent, isStandalone: true, selector: "input-photo-gallery", inputs: { label: "label", hint: "hint", error: "error", required: "required", orderLabel: "orderLabel", imageIds: "imageIds", disabled: "disabled", afterUpload: "afterUpload", resolveImageUrl: "resolveImageUrl", notify: "notify" }, outputs: { imageIdsChange: "imageIdsChange" }, usesOnChanges: true, ngImport: i0, template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\">\n <p class=\"mb-2 text-xs uppercase tracking-[0.2em] text-slate-500\">{{ orderLabel }}</p>\n <div class=\"grid gap-3 md:grid-cols-4\" cdkDropList [cdkDropListData]=\"entries\" (cdkDropListDropped)=\"onDrop($event)\">\n @for (entry of entries; let i = $index; track entry.clientKey + '-' + i) {\n <div class=\"image-drag-item relative overflow-hidden rounded-xl border border-slate-200 bg-white\" cdkDrag [cdkDragDisabled]=\"disabled || (!entry.imageId && !entry.file)\">\n <label class=\"block h-full w-full cursor-pointer\">\n @if (entry.preview) {\n <img [src]=\"entry.preview\" alt=\"photo\" class=\"h-48 w-full object-cover\" />\n } @else {\n <button type=\"button\" class=\"flex h-48 w-full items-center justify-center bg-slate-50 text-xs text-slate-500\" (click)=\"openFilePicker(fileInput, $event)\">+ \u0414\u043E\u0434\u0430\u0442\u0438 \u0444\u043E\u0442\u043E</button>\n }\n <input #fileInput type=\"file\" class=\"hidden\" [disabled]=\"disabled\" multiple (change)=\"onFilesChange($event, i)\" />\n </label>\n @if (entry.imageId || entry.file) {\n @if (i === 0) {\n <span class=\"absolute left-2 top-2 rounded-full border border-slate-200 bg-white/95 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.15em] text-slate-700\">\u041E\u0431\u043A\u043B\u0430\u0434\u0438\u043D\u043A\u0430</span>\n }\n <span class=\"absolute left-2 bottom-2 rounded-full bg-slate-900/85 px-2 py-1 text-[10px] font-semibold text-white\">#{{ i + 1 }}</span>\n <button type=\"button\" class=\"absolute right-2 top-2 rounded-full bg-white/95 px-2 py-1 text-xs text-rose-600 shadow\" [disabled]=\"disabled\" (click)=\"removeAt(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n }\n </div>\n }\n </div>\n <div class=\"mt-1 text-xs text-slate-500\">\u041A\u0456\u043B\u044C\u043A\u0456\u0441\u0442\u044C \u0444\u043E\u0442\u043E: {{ photoCount }}</div>\n</field-frame>\n", styles: [".image-drag-item.cdk-drag-preview{transform:scale(1.03) rotate(.6deg);box-shadow:0 28px 60px #0f172a66;border-radius:.9rem}.image-drag-item.cdk-drag-placeholder{opacity:.4;outline:2px dashed #94a3b8;outline-offset:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }] });
3598
4268
  }
3599
4269
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPhotoGalleryComponent, decorators: [{
3600
4270
  type: Component,
3601
- args: [{ selector: 'input-photo-gallery', standalone: true, imports: [CommonModule, DragDropModule], template: "<label class=\"flex flex-col gap-1 md:col-span-12\">\n <span class=\"text-sm\">{{ label }}</span>\n <div class=\"text-xs text-slate-500\">\u041A\u0456\u043B\u044C\u043A\u0456\u0441\u0442\u044C \u0444\u043E\u0442\u043E: {{ photoCount }}</div>\n</label>\n\n<div class=\"md:col-span-12\">\n <p class=\"mb-2 text-xs uppercase tracking-[0.2em] text-slate-500\">{{ orderLabel }}</p>\n <div class=\"grid gap-3 md:grid-cols-4\" cdkDropList [cdkDropListData]=\"entries\" (cdkDropListDropped)=\"onDrop($event)\">\n @for (entry of entries; let i = $index; track entry.clientKey + '-' + i) {\n <div class=\"image-drag-item relative overflow-hidden rounded-xl border border-slate-200 bg-white\" cdkDrag [cdkDragDisabled]=\"disabled || (!entry.imageId && !entry.file)\">\n <label class=\"block h-full w-full cursor-pointer\">\n @if (entry.preview) {\n <img [src]=\"entry.preview\" alt=\"photo\" class=\"h-48 w-full object-cover\" />\n } @else {\n <button type=\"button\" class=\"flex h-48 w-full items-center justify-center bg-slate-50 text-xs text-slate-500\" (click)=\"openFilePicker(fileInput, $event)\">+ \u0414\u043E\u0434\u0430\u0442\u0438 \u0444\u043E\u0442\u043E</button>\n }\n <input #fileInput type=\"file\" class=\"hidden\" [disabled]=\"disabled\" multiple (change)=\"onFilesChange($event, i)\" />\n </label>\n @if (entry.imageId || entry.file) {\n @if (i === 0) {\n <span class=\"absolute left-2 top-2 rounded-full border border-slate-200 bg-white/95 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.15em] text-slate-700\">\u041E\u0431\u043A\u043B\u0430\u0434\u0438\u043D\u043A\u0430</span>\n }\n <span class=\"absolute left-2 bottom-2 rounded-full bg-slate-900/85 px-2 py-1 text-[10px] font-semibold text-white\">#{{ i + 1 }}</span>\n <button type=\"button\" class=\"absolute right-2 top-2 rounded-full bg-white/95 px-2 py-1 text-xs text-rose-600 shadow\" [disabled]=\"disabled\" (click)=\"removeAt(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n }\n </div>\n }\n </div>\n</div>\n", styles: [".image-drag-item.cdk-drag-preview{transform:scale(1.03) rotate(.6deg);box-shadow:0 28px 60px #0f172a66;border-radius:.9rem}.image-drag-item.cdk-drag-placeholder{opacity:.4;outline:2px dashed #94a3b8;outline-offset:4px}\n"] }]
4271
+ args: [{ selector: 'input-photo-gallery', standalone: true, imports: [CommonModule, DragDropModule, FieldFrameComponent], template: "<field-frame [label]=\"label\" [hint]=\"hint\" [error]=\"error\" [required]=\"required\">\n <p class=\"mb-2 text-xs uppercase tracking-[0.2em] text-slate-500\">{{ orderLabel }}</p>\n <div class=\"grid gap-3 md:grid-cols-4\" cdkDropList [cdkDropListData]=\"entries\" (cdkDropListDropped)=\"onDrop($event)\">\n @for (entry of entries; let i = $index; track entry.clientKey + '-' + i) {\n <div class=\"image-drag-item relative overflow-hidden rounded-xl border border-slate-200 bg-white\" cdkDrag [cdkDragDisabled]=\"disabled || (!entry.imageId && !entry.file)\">\n <label class=\"block h-full w-full cursor-pointer\">\n @if (entry.preview) {\n <img [src]=\"entry.preview\" alt=\"photo\" class=\"h-48 w-full object-cover\" />\n } @else {\n <button type=\"button\" class=\"flex h-48 w-full items-center justify-center bg-slate-50 text-xs text-slate-500\" (click)=\"openFilePicker(fileInput, $event)\">+ \u0414\u043E\u0434\u0430\u0442\u0438 \u0444\u043E\u0442\u043E</button>\n }\n <input #fileInput type=\"file\" class=\"hidden\" [disabled]=\"disabled\" multiple (change)=\"onFilesChange($event, i)\" />\n </label>\n @if (entry.imageId || entry.file) {\n @if (i === 0) {\n <span class=\"absolute left-2 top-2 rounded-full border border-slate-200 bg-white/95 px-2 py-1 text-[10px] font-semibold uppercase tracking-[0.15em] text-slate-700\">\u041E\u0431\u043A\u043B\u0430\u0434\u0438\u043D\u043A\u0430</span>\n }\n <span class=\"absolute left-2 bottom-2 rounded-full bg-slate-900/85 px-2 py-1 text-[10px] font-semibold text-white\">#{{ i + 1 }}</span>\n <button type=\"button\" class=\"absolute right-2 top-2 rounded-full bg-white/95 px-2 py-1 text-xs text-rose-600 shadow\" [disabled]=\"disabled\" (click)=\"removeAt(i)\">\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438</button>\n }\n </div>\n }\n </div>\n <div class=\"mt-1 text-xs text-slate-500\">\u041A\u0456\u043B\u044C\u043A\u0456\u0441\u0442\u044C \u0444\u043E\u0442\u043E: {{ photoCount }}</div>\n</field-frame>\n", styles: [".image-drag-item.cdk-drag-preview{transform:scale(1.03) rotate(.6deg);box-shadow:0 28px 60px #0f172a66;border-radius:.9rem}.image-drag-item.cdk-drag-placeholder{opacity:.4;outline:2px dashed #94a3b8;outline-offset:4px}\n"] }]
3602
4272
  }], propDecorators: { label: [{
3603
4273
  type: Input
4274
+ }], hint: [{
4275
+ type: Input
4276
+ }], error: [{
4277
+ type: Input
4278
+ }], required: [{
4279
+ type: Input
3604
4280
  }], orderLabel: [{
3605
4281
  type: Input
3606
4282
  }], imageIds: [{
@@ -3618,6 +4294,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3618
4294
  }] } });
3619
4295
 
3620
4296
  class InputRangeSliderComponent {
4297
+ label;
4298
+ hint;
4299
+ error;
4300
+ required = false;
3621
4301
  min = 0;
3622
4302
  max = 100;
3623
4303
  step = 1;
@@ -3707,7 +4387,8 @@ class InputRangeSliderComponent {
3707
4387
  input.value = String(value);
3708
4388
  }
3709
4389
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputRangeSliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3710
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputRangeSliderComponent, isStandalone: true, selector: "input-range-slider", inputs: { min: "min", max: "max", step: "step", startValue: "startValue", endValue: "endValue", startLabel: "startLabel", endLabel: "endLabel" }, outputs: { startValueChange: "startValueChange", endValueChange: "endValueChange", interactionEnd: "interactionEnd" }, viewQueries: [{ propertyName: "startInput", first: true, predicate: ["startInput"], descendants: true }, { propertyName: "endInput", first: true, predicate: ["endInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
4390
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: InputRangeSliderComponent, isStandalone: true, selector: "input-range-slider", inputs: { label: "label", hint: "hint", error: "error", required: "required", min: "min", max: "max", step: "step", startValue: "startValue", endValue: "endValue", startLabel: "startLabel", endLabel: "endLabel" }, outputs: { startValueChange: "startValueChange", endValueChange: "endValueChange", interactionEnd: "interactionEnd" }, viewQueries: [{ propertyName: "startInput", first: true, predicate: ["startInput"], descendants: true }, { propertyName: "endInput", first: true, predicate: ["endInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
4391
+ <field-frame [label]="label" [hint]="hint" [error]="error" [required]="required">
3711
4392
  <div class="range-slider">
3712
4393
  <div class="range-slider__track"></div>
3713
4394
  <div
@@ -3740,11 +4421,13 @@ class InputRangeSliderComponent {
3740
4421
  (input)="onEndInput($event)"
3741
4422
  (change)="interactionEnd.emit()">
3742
4423
  </div>
3743
- `, isInline: true, styles: [".range-slider{position:relative;height:2rem;width:100%;display:flex;align-items:center}.range-slider__track,.range-slider__range{position:absolute;left:0;right:0;height:6px;border-radius:999px}.range-slider__track{background:#dbe1ea}.range-slider__range{background:linear-gradient(90deg,#334155,#0f172a)}.range-slider__input{pointer-events:none;position:absolute;left:0;width:100%;height:2rem;margin:0;background:transparent;appearance:none;-webkit-appearance:none}.range-slider__input::-webkit-slider-runnable-track{height:6px;background:transparent}.range-slider__input::-moz-range-track{height:6px;background:transparent}.range-slider__input::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;pointer-events:auto;width:20px;height:20px;margin-top:-7px;border-radius:50%;border:2px solid #ffffff;background:#0f172a;box-shadow:0 8px 22px #0f172a38;cursor:pointer}.range-slider__input::-moz-range-thumb{pointer-events:auto;width:20px;height:20px;border-radius:50%;border:2px solid #ffffff;background:#0f172a;box-shadow:0 8px 22px #0f172a38;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
4424
+ </field-frame>
4425
+ `, isInline: true, styles: [".range-slider{position:relative;height:2rem;width:100%;display:flex;align-items:center}.range-slider__track,.range-slider__range{position:absolute;left:0;right:0;height:6px;border-radius:999px}.range-slider__track{background:#dbe1ea}.range-slider__range{background:linear-gradient(90deg,#334155,#0f172a)}.range-slider__input{pointer-events:none;position:absolute;left:0;width:100%;height:2rem;margin:0;background:transparent;appearance:none;-webkit-appearance:none}.range-slider__input::-webkit-slider-runnable-track{height:6px;background:transparent}.range-slider__input::-moz-range-track{height:6px;background:transparent}.range-slider__input::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;pointer-events:auto;width:20px;height:20px;margin-top:-7px;border-radius:50%;border:2px solid #ffffff;background:#0f172a;box-shadow:0 8px 22px #0f172a38;cursor:pointer}.range-slider__input::-moz-range-thumb{pointer-events:auto;width:20px;height:20px;border-radius:50%;border:2px solid #ffffff;background:#0f172a;box-shadow:0 8px 22px #0f172a38;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FieldFrameComponent, selector: "field-frame", inputs: ["label", "hint", "error", "required", "forId", "disabled", "actions"], outputs: ["actionTriggered"] }] });
3744
4426
  }
3745
4427
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputRangeSliderComponent, decorators: [{
3746
4428
  type: Component,
3747
- args: [{ selector: 'input-range-slider', standalone: true, imports: [CommonModule], template: `
4429
+ args: [{ selector: 'input-range-slider', standalone: true, imports: [CommonModule, FieldFrameComponent], template: `
4430
+ <field-frame [label]="label" [hint]="hint" [error]="error" [required]="required">
3748
4431
  <div class="range-slider">
3749
4432
  <div class="range-slider__track"></div>
3750
4433
  <div
@@ -3777,8 +4460,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3777
4460
  (input)="onEndInput($event)"
3778
4461
  (change)="interactionEnd.emit()">
3779
4462
  </div>
4463
+ </field-frame>
3780
4464
  `, styles: [".range-slider{position:relative;height:2rem;width:100%;display:flex;align-items:center}.range-slider__track,.range-slider__range{position:absolute;left:0;right:0;height:6px;border-radius:999px}.range-slider__track{background:#dbe1ea}.range-slider__range{background:linear-gradient(90deg,#334155,#0f172a)}.range-slider__input{pointer-events:none;position:absolute;left:0;width:100%;height:2rem;margin:0;background:transparent;appearance:none;-webkit-appearance:none}.range-slider__input::-webkit-slider-runnable-track{height:6px;background:transparent}.range-slider__input::-moz-range-track{height:6px;background:transparent}.range-slider__input::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;pointer-events:auto;width:20px;height:20px;margin-top:-7px;border-radius:50%;border:2px solid #ffffff;background:#0f172a;box-shadow:0 8px 22px #0f172a38;cursor:pointer}.range-slider__input::-moz-range-thumb{pointer-events:auto;width:20px;height:20px;border-radius:50%;border:2px solid #ffffff;background:#0f172a;box-shadow:0 8px 22px #0f172a38;cursor:pointer}\n"] }]
3781
- }], propDecorators: { min: [{
4465
+ }], propDecorators: { label: [{
4466
+ type: Input
4467
+ }], hint: [{
4468
+ type: Input
4469
+ }], error: [{
4470
+ type: Input
4471
+ }], required: [{
4472
+ type: Input
4473
+ }], min: [{
3782
4474
  type: Input
3783
4475
  }], max: [{
3784
4476
  type: Input
@@ -3810,5 +4502,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3810
4502
  * Generated bundle index. Do not edit.
3811
4503
  */
3812
4504
 
3813
- export { ACTION_API, ACTION_FORM, ACTION_PRINT, ACTION_SUBMIT, DynamicFormComponent, FieldInputComponent, FormModalComponent, InputBarcodeComponent, InputCheckboxComponent, InputColorComponent, InputComponent, InputDateComponent, InputDatetimeComponent, InputEmailComponent, InputFileComponent, InputNumberComponent, InputPasswordComponent, InputPhotoComponent, InputPhotoGalleryComponent, InputRangeSliderComponent, InputSelectComponent, InputSlugComponent, InputTextComponent, InputTextareaComponent, MaroFormsApiService, UI_FORM_INPUT_REGISTRY, UiFormContext, UiFormLookupService, flattenDynamicFormFields, isDynamicFormContainerField, isDynamicFormTemplateField };
4505
+ export { ACTION_API, ACTION_FORM, ACTION_PRINT, ACTION_SUBMIT, DynamicFormComponent, FieldFrameComponent, FieldInputComponent, FormModalComponent, InputBarcodeComponent, InputBarcodeControlComponent, InputBaseControlComponent, InputCheckboxComponent, InputCheckboxControlComponent, InputColorComponent, InputColorControlComponent, InputComponent, InputDateComponent, InputDateControlComponent, InputDatetimeComponent, InputDatetimeControlComponent, InputEmailComponent, InputEmailControlComponent, InputFileComponent, InputFileControlComponent, InputNumberComponent, InputNumberControlComponent, InputPasswordComponent, InputPasswordControlComponent, InputPhotoComponent, InputPhotoControlComponent, InputPhotoGalleryComponent, InputRangeSliderComponent, InputSelectComponent, InputSelectControlComponent, InputSlugComponent, InputSlugControlComponent, InputTextComponent, InputTextControlComponent, InputTextareaComponent, InputTextareaControlComponent, MaroFormsApiService, UI_FORM_INPUT_REGISTRY, UiFormContext, UiFormLookupService, flattenDynamicFormFields, isDynamicFormContainerField, isDynamicFormLookupField, isDynamicFormTemplateField };
3814
4506
  //# sourceMappingURL=maro-tech-form.mjs.map