@maro-tech/form 0.1.1 → 0.1.3

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, HostBinding, 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
- import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
8
+ import { NG_VALUE_ACCESSOR, FormsModule, 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,103 @@ 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
- }
106
+ class FieldFrameComponent {
107
+ label;
108
+ hint;
109
+ error;
110
+ required = false;
111
+ forId;
112
+ disabled = false;
113
+ actions = [];
114
+ get hasError() {
115
+ return !!String(this.error || '').trim();
98
116
  }
99
- return result;
100
- };
117
+ actionTriggered = new EventEmitter();
118
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FieldFrameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
119
+ 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" }, host: { properties: { "class.field-error": "this.hasError" } }, 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-red-500\">{{ error }}</p>\n }\n</div>\n", styles: [":host(.field-error) ::ng-deep [data-field-control]{border-color:#fca5a5;box-shadow:0 0 0 2px #fee2e2}\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 });
120
+ }
121
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FieldFrameComponent, decorators: [{
122
+ type: Component,
123
+ 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-red-500\">{{ error }}</p>\n }\n</div>\n", styles: [":host(.field-error) ::ng-deep [data-field-control]{border-color:#fca5a5;box-shadow:0 0 0 2px #fee2e2}\n"] }]
124
+ }], propDecorators: { label: [{
125
+ type: Input
126
+ }], hint: [{
127
+ type: Input
128
+ }], error: [{
129
+ type: Input
130
+ }], required: [{
131
+ type: Input
132
+ }], forId: [{
133
+ type: Input
134
+ }], disabled: [{
135
+ type: Input
136
+ }], actions: [{
137
+ type: Input
138
+ }], hasError: [{
139
+ type: HostBinding,
140
+ args: ['class.field-error']
141
+ }], actionTriggered: [{
142
+ type: Output
143
+ }] } });
101
144
 
102
- class InputBarcodeComponent {
145
+ class InputTextControlComponent {
146
+ placeholder;
147
+ name;
148
+ id;
149
+ type = 'text';
150
+ element = 'input';
151
+ rows = 4;
152
+ autocomplete;
153
+ pattern;
154
+ disabled = false;
155
+ value = '';
156
+ valueChange = new EventEmitter();
157
+ touched = new EventEmitter();
158
+ enterPressed = new EventEmitter();
159
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
160
+ 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", enterPressed: "enterPressed" }, 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 data-field-control\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 (keydown.enter)=\"enterPressed.emit()\"\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 data-field-control\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 (keydown.enter)=\"enterPressed.emit()\"\n (blur)=\"touched.emit()\"\n />\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
161
+ }
162
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextControlComponent, decorators: [{
163
+ type: Component,
164
+ 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 data-field-control\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 (keydown.enter)=\"enterPressed.emit()\"\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 data-field-control\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 (keydown.enter)=\"enterPressed.emit()\"\n (blur)=\"touched.emit()\"\n />\n}\n" }]
165
+ }], propDecorators: { placeholder: [{
166
+ type: Input
167
+ }], name: [{
168
+ type: Input
169
+ }], id: [{
170
+ type: Input
171
+ }], type: [{
172
+ type: Input
173
+ }], element: [{
174
+ type: Input
175
+ }], rows: [{
176
+ type: Input
177
+ }], autocomplete: [{
178
+ type: Input
179
+ }], pattern: [{
180
+ type: Input
181
+ }], disabled: [{
182
+ type: Input
183
+ }], value: [{
184
+ type: Input
185
+ }], valueChange: [{
186
+ type: Output
187
+ }], touched: [{
188
+ type: Output
189
+ }], enterPressed: [{
190
+ type: Output
191
+ }] } });
192
+
193
+ class InputBarcodeControlComponent {
194
+ cameraIcon = Camera;
195
+ cameraOffIcon = CameraOff;
103
196
  value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
104
197
  label = input('Штрихкод', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
105
198
  placeholder = input('Відскануйте або введіть штрихкод', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
106
199
  hint = input('Сканер може просто ввести код і натиснути Enter.', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
200
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
201
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
107
202
  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
203
  valueChange = output();
116
204
  scanned = output();
117
205
  searchRequested = output();
@@ -138,9 +226,8 @@ class InputBarcodeComponent {
138
226
  this.autoOpenQueued = false;
139
227
  return;
140
228
  }
141
- if (changes['autoOpen'] && this.autoOpen() && !this.disabled()) {
229
+ if (!this.disabled())
142
230
  this.scheduleAutoOpen();
143
- }
144
231
  }
145
232
  ngAfterViewInit() {
146
233
  this.scheduleAutoOpen();
@@ -210,13 +297,13 @@ class InputBarcodeComponent {
210
297
  this.stopCamera();
211
298
  }
212
299
  scheduleAutoOpen() {
213
- if (typeof window === 'undefined' || !this.autoOpen() || this.disabled() || this.isCameraOpen() || this.isCameraStarting() || this.autoOpenQueued) {
300
+ if (typeof window === 'undefined' || this.disabled() || this.isCameraOpen() || this.isCameraStarting() || this.autoOpenQueued) {
214
301
  return;
215
302
  }
216
303
  this.autoOpenQueued = true;
217
304
  window.requestAnimationFrame(() => {
218
305
  this.autoOpenQueued = false;
219
- if (!this.autoOpen() || this.disabled() || this.isCameraOpen() || this.isCameraStarting()) {
306
+ if (this.disabled() || this.isCameraOpen() || this.isCameraStarting()) {
220
307
  return;
221
308
  }
222
309
  void this.openCamera();
@@ -363,40 +450,24 @@ class InputBarcodeComponent {
363
450
  return null;
364
451
  }
365
452
  }
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
- }
453
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBarcodeControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
454
+ 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
455
 
377
- <div class="flex items-start gap-2">
456
+ <div class="relative flex items-start gap-2">
378
457
  <div class="min-w-0 flex-1">
379
- <input
380
- #barcodeInput
458
+ <input-text-control
381
459
  id="barcode-scanner-input"
382
460
  type="text"
383
461
  autocomplete="off"
384
- spellcheck="false"
385
462
  [disabled]="disabled()"
386
- [ngModel]="value()"
387
- (ngModelChange)="onValueChange($event)"
388
- (keydown.enter)="requestSearch()"
389
- 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"
463
+ [value]="value()"
390
464
  [placeholder]="placeholder()"
465
+ (valueChange)="onValueChange($event)"
466
+ (enterPressed)="requestSearch()"
391
467
  />
392
- @if (hint()) {
393
- <p class="mt-2 text-[0.5rem] text-slate-500">
394
- {{ hint() }}
395
- </p>
396
- }
397
468
  </div>
398
469
 
399
- <div class="mt-0.5 flex flex-col gap-2">
470
+ <div class="mt-0.5 flex flex-row flex-wrap items-center gap-2">
400
471
  <ui-button
401
472
  type="button"
402
473
  variant="secondary"
@@ -404,31 +475,35 @@ class InputBarcodeComponent {
404
475
  [disabled]="disabled()"
405
476
  (click)="requestSearch()"
406
477
  >
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() }}
478
+ Знайти
417
479
  </ui-button>
480
+ <ui-button
481
+ type="button"
482
+ variant="ghost"
483
+ size="sm"
484
+ [iconOnly]="true"
485
+ [icon]="isCameraOpen() ? cameraOffIcon : cameraIcon"
486
+ [ariaLabel]="isCameraOpen() ? 'Закрити камеру' : 'Відкрити камеру'"
487
+ [disabled]="disabled() || isCameraStarting()"
488
+ (click)="isCameraOpen() ? closeCamera() : openCamera()"
489
+ ></ui-button>
418
490
  </div>
419
491
  </div>
420
492
 
493
+ @if (hint()) {
494
+ <p class="mt-2 text-[0.5rem] text-slate-500">{{ hint() }}</p>
495
+ }
421
496
  @if (isCameraStarting()) {
422
497
  <div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-500">
423
- {{ cameraStartingLabel() }}
498
+ Запускаємо камеру…
424
499
  </div>
425
500
  }
426
501
 
427
502
  @if (isCameraOpen()) {
428
- <section class="space-y-3 rounded-3xl border border-slate-200 bg-slate-950 p-3 text-white">
503
+ <section class="mt-3 space-y-3 rounded-3xl border border-slate-200 bg-slate-950 p-3 text-white">
429
504
  <div class="flex items-center justify-between gap-3">
430
505
  <div>
431
- <p class="text-xs font-semibold uppercase tracking-[0.22em] text-slate-300">{{ cameraTitle() }}</p>
506
+ <p class="text-xs font-semibold uppercase tracking-[0.22em] text-slate-300">Камера</p>
432
507
  <p class="text-sm text-slate-200">{{ cameraStatus() }}</p>
433
508
  </div>
434
509
  <button
@@ -436,7 +511,7 @@ class InputBarcodeComponent {
436
511
  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
512
  (click)="closeCamera()"
438
513
  >
439
- {{ closeCameraLabel() }}
514
+ Закрити камеру
440
515
  </button>
441
516
  </div>
442
517
 
@@ -445,58 +520,42 @@ class InputBarcodeComponent {
445
520
  </div>
446
521
 
447
522
  <p class="text-xs text-slate-300">
448
- {{ cameraHint() }}
523
+ Наведіть камеру на штрихкод.
449
524
  </p>
450
525
  </section>
451
526
  }
452
527
 
453
528
  @if (cameraError()) {
454
- <div class="rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
529
+ <div class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
455
530
  {{ cameraError() }}
456
531
  </div>
457
532
  }
458
- </div>
459
- `, 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"] }] });
533
+
534
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }, { kind: "component", type: InputTextControlComponent, selector: "input-text-control", inputs: ["placeholder", "name", "id", "type", "element", "rows", "autocomplete", "pattern", "disabled", "value"], outputs: ["valueChange", "touched", "enterPressed"] }] });
460
535
  }
461
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBarcodeComponent, decorators: [{
536
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBarcodeControlComponent, decorators: [{
462
537
  type: Component,
463
538
  args: [{
464
- selector: 'input-barcode',
539
+ selector: 'input-barcode-control',
465
540
  standalone: true,
466
- imports: [CommonModule, FormsModule, UiButtonComponent],
541
+ imports: [CommonModule, UiButtonComponent, InputTextControlComponent],
467
542
  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
543
 
477
- <div class="flex items-start gap-2">
544
+ <div class="relative flex items-start gap-2">
478
545
  <div class="min-w-0 flex-1">
479
- <input
480
- #barcodeInput
546
+ <input-text-control
481
547
  id="barcode-scanner-input"
482
548
  type="text"
483
549
  autocomplete="off"
484
- spellcheck="false"
485
550
  [disabled]="disabled()"
486
- [ngModel]="value()"
487
- (ngModelChange)="onValueChange($event)"
488
- (keydown.enter)="requestSearch()"
489
- 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"
551
+ [value]="value()"
490
552
  [placeholder]="placeholder()"
553
+ (valueChange)="onValueChange($event)"
554
+ (enterPressed)="requestSearch()"
491
555
  />
492
- @if (hint()) {
493
- <p class="mt-2 text-[0.5rem] text-slate-500">
494
- {{ hint() }}
495
- </p>
496
- }
497
556
  </div>
498
557
 
499
- <div class="mt-0.5 flex flex-col gap-2">
558
+ <div class="mt-0.5 flex flex-row flex-wrap items-center gap-2">
500
559
  <ui-button
501
560
  type="button"
502
561
  variant="secondary"
@@ -504,31 +563,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
504
563
  [disabled]="disabled()"
505
564
  (click)="requestSearch()"
506
565
  >
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() }}
566
+ Знайти
517
567
  </ui-button>
568
+ <ui-button
569
+ type="button"
570
+ variant="ghost"
571
+ size="sm"
572
+ [iconOnly]="true"
573
+ [icon]="isCameraOpen() ? cameraOffIcon : cameraIcon"
574
+ [ariaLabel]="isCameraOpen() ? 'Закрити камеру' : 'Відкрити камеру'"
575
+ [disabled]="disabled() || isCameraStarting()"
576
+ (click)="isCameraOpen() ? closeCamera() : openCamera()"
577
+ ></ui-button>
518
578
  </div>
519
579
  </div>
520
580
 
581
+ @if (hint()) {
582
+ <p class="mt-2 text-[0.5rem] text-slate-500">{{ hint() }}</p>
583
+ }
521
584
  @if (isCameraStarting()) {
522
585
  <div class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-500">
523
- {{ cameraStartingLabel() }}
586
+ Запускаємо камеру…
524
587
  </div>
525
588
  }
526
589
 
527
590
  @if (isCameraOpen()) {
528
- <section class="space-y-3 rounded-3xl border border-slate-200 bg-slate-950 p-3 text-white">
591
+ <section class="mt-3 space-y-3 rounded-3xl border border-slate-200 bg-slate-950 p-3 text-white">
529
592
  <div class="flex items-center justify-between gap-3">
530
593
  <div>
531
- <p class="text-xs font-semibold uppercase tracking-[0.22em] text-slate-300">{{ cameraTitle() }}</p>
594
+ <p class="text-xs font-semibold uppercase tracking-[0.22em] text-slate-300">Камера</p>
532
595
  <p class="text-sm text-slate-200">{{ cameraStatus() }}</p>
533
596
  </div>
534
597
  <button
@@ -536,7 +599,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
536
599
  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
600
  (click)="closeCamera()"
538
601
  >
539
- {{ closeCameraLabel() }}
602
+ Закрити камеру
540
603
  </button>
541
604
  </div>
542
605
 
@@ -545,29 +608,115 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
545
608
  </div>
546
609
 
547
610
  <p class="text-xs text-slate-300">
548
- {{ cameraHint() }}
611
+ Наведіть камеру на штрихкод.
549
612
  </p>
550
613
  </section>
551
614
  }
552
615
 
553
616
  @if (cameraError()) {
554
- <div class="rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
617
+ <div class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
555
618
  {{ cameraError() }}
556
619
  </div>
557
620
  }
558
- </div>
621
+
559
622
  `,
560
623
  }]
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: [{
624
+ }], 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
625
  type: ViewChild,
563
626
  args: ['cameraVideo']
564
627
  }] } });
565
628
 
629
+ class InputBarcodeComponent {
630
+ value = '';
631
+ label = 'Штрихкод';
632
+ placeholder = 'Відскануйте або введіть штрихкод';
633
+ hint = 'Сканер може просто ввести код і натиснути Enter.';
634
+ error = '';
635
+ required = false;
636
+ disabled = false;
637
+ valueChange = new EventEmitter();
638
+ scanned = new EventEmitter();
639
+ searchRequested = new EventEmitter();
640
+ cameraStateChange = new EventEmitter();
641
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBarcodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
642
+ 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: `
643
+ <field-frame [label]="label" [error]="error" [required]="required" [forId]="'barcode-scanner-input'" [disabled]="disabled">
644
+ <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)" />
645
+ </field-frame>
646
+ `, 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 });
647
+ }
648
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBarcodeComponent, decorators: [{
649
+ type: Component,
650
+ args: [{
651
+ selector: 'input-barcode',
652
+ standalone: true,
653
+ imports: [FieldFrameComponent, InputBarcodeControlComponent],
654
+ template: `
655
+ <field-frame [label]="label" [error]="error" [required]="required" [forId]="'barcode-scanner-input'" [disabled]="disabled">
656
+ <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)" />
657
+ </field-frame>
658
+ `,
659
+ changeDetection: ChangeDetectionStrategy.OnPush,
660
+ }]
661
+ }], propDecorators: { value: [{
662
+ type: Input
663
+ }], label: [{
664
+ type: Input
665
+ }], placeholder: [{
666
+ type: Input
667
+ }], hint: [{
668
+ type: Input
669
+ }], error: [{
670
+ type: Input
671
+ }], required: [{
672
+ type: Input
673
+ }], disabled: [{
674
+ type: Input
675
+ }], valueChange: [{
676
+ type: Output
677
+ }], scanned: [{
678
+ type: Output
679
+ }], searchRequested: [{
680
+ type: Output
681
+ }], cameraStateChange: [{
682
+ type: Output
683
+ }] } });
684
+
685
+ class InputCheckboxControlComponent {
686
+ value = false;
687
+ disabled = false;
688
+ valueChange = new EventEmitter();
689
+ touched = new EventEmitter();
690
+ onValueChange(event) {
691
+ this.valueChange.emit(event.target.checked);
692
+ }
693
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputCheckboxControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
694
+ 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 data-field-control 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 });
695
+ }
696
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputCheckboxControlComponent, decorators: [{
697
+ type: Component,
698
+ args: [{
699
+ selector: 'input-checkbox-control',
700
+ standalone: true,
701
+ template: `<input data-field-control 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()" />`,
702
+ }]
703
+ }], propDecorators: { value: [{
704
+ type: Input
705
+ }], disabled: [{
706
+ type: Input
707
+ }], valueChange: [{
708
+ type: Output
709
+ }], touched: [{
710
+ type: Output
711
+ }] } });
712
+
566
713
  class InputCheckboxComponent {
567
714
  label;
715
+ hint;
716
+ error;
717
+ required = false;
568
718
  disabled = false;
569
719
  value = false;
570
- valueChange = new EventEmitter();
571
720
  onChange = () => undefined;
572
721
  onTouched = () => undefined;
573
722
  writeValue(value) { this.value = !!value; }
@@ -578,22 +727,27 @@ class InputCheckboxComponent {
578
727
  const value = event.target.checked;
579
728
  this.value = value;
580
729
  this.onChange(value);
581
- this.valueChange.emit(value);
730
+ }
731
+ handleValueChange(value) {
732
+ this.value = value;
733
+ this.onChange(value);
582
734
  }
583
735
  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 }] });
736
+ 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
737
  }
586
738
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputCheckboxComponent, decorators: [{
587
739
  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" }]
740
+ 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
741
  }], propDecorators: { label: [{
590
742
  type: Input
591
- }], disabled: [{
743
+ }], hint: [{
592
744
  type: Input
593
- }], value: [{
745
+ }], error: [{
746
+ type: Input
747
+ }], required: [{
748
+ type: Input
749
+ }], disabled: [{
594
750
  type: Input
595
- }], valueChange: [{
596
- type: Output
597
751
  }] } });
598
752
 
599
753
  const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -618,6 +772,56 @@ const validateFormControl = (control, required, pattern, email = false) => {
618
772
  return null;
619
773
  };
620
774
 
775
+ class InputColorControlComponent {
776
+ id;
777
+ name;
778
+ disabled = false;
779
+ value = '#000000';
780
+ pickerValue = '#000000';
781
+ valueChange = new EventEmitter();
782
+ touched = new EventEmitter();
783
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputColorControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
784
+ 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: `
785
+ <div class="flex items-center gap-3">
786
+ <label data-field-control 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">
787
+ <span class="block h-9 w-9 rounded-full border border-black/10" [style.backgroundColor]="pickerValue"></span>
788
+ <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()" />
789
+ </label>
790
+ <input data-field-control 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()" />
791
+ </div>
792
+ `, isInline: true });
793
+ }
794
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputColorControlComponent, decorators: [{
795
+ type: Component,
796
+ args: [{
797
+ selector: 'input-color-control',
798
+ standalone: true,
799
+ template: `
800
+ <div class="flex items-center gap-3">
801
+ <label data-field-control 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">
802
+ <span class="block h-9 w-9 rounded-full border border-black/10" [style.backgroundColor]="pickerValue"></span>
803
+ <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()" />
804
+ </label>
805
+ <input data-field-control 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()" />
806
+ </div>
807
+ `,
808
+ }]
809
+ }], propDecorators: { id: [{
810
+ type: Input
811
+ }], name: [{
812
+ type: Input
813
+ }], disabled: [{
814
+ type: Input
815
+ }], value: [{
816
+ type: Input
817
+ }], pickerValue: [{
818
+ type: Input
819
+ }], valueChange: [{
820
+ type: Output
821
+ }], touched: [{
822
+ type: Output
823
+ }] } });
824
+
621
825
  class InputColorComponent {
622
826
  cdr = inject(ChangeDetectorRef);
623
827
  label;
@@ -625,7 +829,6 @@ class InputColorComponent {
625
829
  name;
626
830
  id;
627
831
  required = false;
628
- fullWidth = false;
629
832
  error;
630
833
  disabled = false;
631
834
  value = '#000000';
@@ -662,6 +865,10 @@ class InputColorComponent {
662
865
  this.value = this.normalizeHexInput(value);
663
866
  this.onChange(this.value);
664
867
  }
868
+ handleValueChange(value) {
869
+ this.value = value;
870
+ this.onChange(value);
871
+ }
665
872
  markTouched() {
666
873
  this.onTouched();
667
874
  }
@@ -676,25 +883,25 @@ class InputColorComponent {
676
883
  return /^#([0-9a-fA-F]{6})$/.test(value.trim());
677
884
  }
678
885
  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: [
886
+ 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
887
  {
681
888
  provide: NG_VALUE_ACCESSOR,
682
889
  useExisting: forwardRef(() => InputColorComponent),
683
890
  multi: true,
684
891
  },
685
892
  { 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 });
893
+ ], 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
894
  }
688
895
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputColorComponent, decorators: [{
689
896
  type: Component,
690
- args: [{ selector: 'input-color', standalone: true, imports: [CommonModule, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
897
+ args: [{ selector: 'input-color', standalone: true, imports: [CommonModule, FormsModule, FieldFrameComponent, InputColorControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
691
898
  {
692
899
  provide: NG_VALUE_ACCESSOR,
693
900
  useExisting: forwardRef(() => InputColorComponent),
694
901
  multi: true,
695
902
  },
696
903
  { 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" }]
904
+ ], 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
905
  }], propDecorators: { label: [{
699
906
  type: Input
700
907
  }], hint: [{
@@ -705,8 +912,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
705
912
  type: Input
706
913
  }], required: [{
707
914
  type: Input
708
- }], fullWidth: [{
709
- type: Input
710
915
  }], error: [{
711
916
  type: Input
712
917
  }] } });
@@ -733,7 +938,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
733
938
  args: [{ providedIn: 'root' }]
734
939
  }] });
735
940
 
736
- class InputDateComponent {
941
+ class InputDateControlComponent {
737
942
  cdr = inject(ChangeDetectorRef);
738
943
  popupService = inject(UiPopupService);
739
944
  closeActivePopup = () => this.closeCalendar();
@@ -749,7 +954,6 @@ class InputDateComponent {
749
954
  nextMonthIcon = ChevronRight;
750
955
  rows = 4;
751
956
  required = false;
752
- fullWidth = false;
753
957
  autocomplete = 'off';
754
958
  pattern;
755
959
  error;
@@ -757,7 +961,7 @@ class InputDateComponent {
757
961
  disabled = false;
758
962
  value = '';
759
963
  static nextId = 0;
760
- generatedId = `input-date-${InputDateComponent.nextId++}`;
964
+ generatedId = `input-date-${InputDateControlComponent.nextId++}`;
761
965
  visibleMonth = this.startOfMonth(new Date());
762
966
  calendarView = 'days';
763
967
  isCalendarOpen = false;
@@ -966,26 +1170,26 @@ class InputDateComponent {
966
1170
  const day = String(date.getDate()).padStart(2, '0');
967
1171
  return `${year}-${month}-${day}`;
968
1172
  }
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: [
1173
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDateControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1174
+ 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
1175
  {
972
1176
  provide: NG_VALUE_ACCESSOR,
973
- useExisting: forwardRef(() => InputDateComponent),
1177
+ useExisting: forwardRef(() => InputDateControlComponent),
974
1178
  multi: true,
975
1179
  },
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 });
1180
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDateControlComponent), multi: true },
1181
+ ], 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 data-field-control\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
1182
  }
979
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDateComponent, decorators: [{
1183
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDateControlComponent, decorators: [{
980
1184
  type: Component,
981
- args: [{ selector: 'input-date', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1185
+ args: [{ selector: 'input-date-control', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
982
1186
  {
983
1187
  provide: NG_VALUE_ACCESSOR,
984
- useExisting: forwardRef(() => InputDateComponent),
1188
+ useExisting: forwardRef(() => InputDateControlComponent),
985
1189
  multi: true,
986
1190
  },
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" }]
1191
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDateControlComponent), multi: true },
1192
+ ], 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 data-field-control\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
1193
  }], propDecorators: { label: [{
990
1194
  type: Input
991
1195
  }], hint: [{
@@ -1000,8 +1204,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1000
1204
  type: Input
1001
1205
  }], required: [{
1002
1206
  type: Input
1003
- }], fullWidth: [{
1004
- type: Input
1005
1207
  }], autocomplete: [{
1006
1208
  type: Input
1007
1209
  }], pattern: [{
@@ -1010,6 +1212,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1010
1212
  type: Input
1011
1213
  }], actions: [{
1012
1214
  type: Input
1215
+ }], disabled: [{
1216
+ type: Input
1013
1217
  }], actionTriggered: [{
1014
1218
  type: Output
1015
1219
  }], onDocumentClick: [{
@@ -1020,7 +1224,68 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1020
1224
  args: ['document:keydown.escape']
1021
1225
  }] } });
1022
1226
 
1023
- class InputDatetimeComponent {
1227
+ class InputDateComponent {
1228
+ control;
1229
+ label;
1230
+ hint;
1231
+ error;
1232
+ placeholder;
1233
+ name;
1234
+ id;
1235
+ required = false;
1236
+ disabled = false;
1237
+ autocomplete = 'off';
1238
+ pattern;
1239
+ rows = 4;
1240
+ actions = [];
1241
+ actionTriggered = new EventEmitter();
1242
+ pendingValue = '';
1243
+ onChange = () => undefined;
1244
+ onTouched = () => undefined;
1245
+ ngAfterViewInit() { this.control?.registerOnChange((value) => this.onChange(value)); this.control?.registerOnTouched(() => this.onTouched()); this.control?.writeValue(this.pendingValue); }
1246
+ writeValue(value) { this.pendingValue = value; this.control?.writeValue(value); }
1247
+ registerOnChange(fn) { this.onChange = fn; }
1248
+ registerOnTouched(fn) { this.onTouched = fn; }
1249
+ setDisabledState(value) { this.disabled = value; }
1250
+ validate(control) { return validateFormControl(control, this.required, this.pattern); }
1251
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1252
+ 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 });
1253
+ }
1254
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDateComponent, decorators: [{
1255
+ type: Component,
1256
+ 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" }]
1257
+ }], propDecorators: { control: [{
1258
+ type: ViewChild,
1259
+ args: [InputDateControlComponent]
1260
+ }], label: [{
1261
+ type: Input
1262
+ }], hint: [{
1263
+ type: Input
1264
+ }], error: [{
1265
+ type: Input
1266
+ }], placeholder: [{
1267
+ type: Input
1268
+ }], name: [{
1269
+ type: Input
1270
+ }], id: [{
1271
+ type: Input
1272
+ }], required: [{
1273
+ type: Input
1274
+ }], disabled: [{
1275
+ type: Input
1276
+ }], autocomplete: [{
1277
+ type: Input
1278
+ }], pattern: [{
1279
+ type: Input
1280
+ }], rows: [{
1281
+ type: Input
1282
+ }], actions: [{
1283
+ type: Input
1284
+ }], actionTriggered: [{
1285
+ type: Output
1286
+ }] } });
1287
+
1288
+ class InputDatetimeControlComponent {
1024
1289
  cdr = inject(ChangeDetectorRef);
1025
1290
  popupService = inject(UiPopupService);
1026
1291
  closeActivePopup = () => this.closeCalendar();
@@ -1031,7 +1296,6 @@ class InputDatetimeComponent {
1031
1296
  id;
1032
1297
  rows = 4;
1033
1298
  required = false;
1034
- fullWidth = false;
1035
1299
  autocomplete = 'off';
1036
1300
  pattern;
1037
1301
  error;
@@ -1048,7 +1312,7 @@ class InputDatetimeComponent {
1048
1312
  selectedHour = 12;
1049
1313
  selectedMinute = 0;
1050
1314
  static nextId = 0;
1051
- generatedId = `input-datetime-${InputDatetimeComponent.nextId++}`;
1315
+ generatedId = `input-datetime-${InputDatetimeControlComponent.nextId++}`;
1052
1316
  actionTriggered = new EventEmitter();
1053
1317
  onChange = () => undefined;
1054
1318
  onTouched = () => undefined;
@@ -1118,18 +1382,18 @@ class InputDatetimeComponent {
1118
1382
  startOfMonth(date) { return new Date(date.getFullYear(), date.getMonth(), 1); }
1119
1383
  parseDate(value) { const [year, month, day] = value.split('-').map(Number); return year && month && day ? new Date(year, month - 1, day) : null; }
1120
1384
  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 });
1385
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDatetimeControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1386
+ 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: [
1387
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputDatetimeControlComponent), multi: true },
1388
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDatetimeControlComponent), multi: true },
1389
+ ], ngImport: i0, template: " <div class=\"flex items-end gap-2\"><div class=\"min-w-0 flex-1\"><div class=\"relative\">\n <input data-field-control 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
1390
  }
1127
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDatetimeComponent, decorators: [{
1391
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDatetimeControlComponent, decorators: [{
1128
1392
  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" }]
1393
+ args: [{ selector: 'input-datetime-control', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1394
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputDatetimeControlComponent), multi: true },
1395
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputDatetimeControlComponent), multi: true },
1396
+ ], template: " <div class=\"flex items-end gap-2\"><div class=\"min-w-0 flex-1\"><div class=\"relative\">\n <input data-field-control 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
1397
  }], propDecorators: { label: [{
1134
1398
  type: Input
1135
1399
  }], hint: [{
@@ -1144,8 +1408,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1144
1408
  type: Input
1145
1409
  }], required: [{
1146
1410
  type: Input
1147
- }], fullWidth: [{
1148
- type: Input
1149
1411
  }], autocomplete: [{
1150
1412
  type: Input
1151
1413
  }], pattern: [{
@@ -1154,6 +1416,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1154
1416
  type: Input
1155
1417
  }], actions: [{
1156
1418
  type: Input
1419
+ }], disabled: [{
1420
+ type: Input
1157
1421
  }], actionTriggered: [{
1158
1422
  type: Output
1159
1423
  }], onDocumentClick: [{
@@ -1164,6 +1428,103 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1164
1428
  args: ['document:keydown.escape']
1165
1429
  }] } });
1166
1430
 
1431
+ class InputDatetimeComponent {
1432
+ control;
1433
+ label;
1434
+ hint;
1435
+ error;
1436
+ placeholder;
1437
+ name;
1438
+ id;
1439
+ required = false;
1440
+ disabled = false;
1441
+ autocomplete = 'off';
1442
+ pattern;
1443
+ rows = 4;
1444
+ actions = [];
1445
+ actionTriggered = new EventEmitter();
1446
+ pendingValue = '';
1447
+ onChange = () => undefined;
1448
+ onTouched = () => undefined;
1449
+ ngAfterViewInit() { this.control?.registerOnChange((value) => this.onChange(value)); this.control?.registerOnTouched(() => this.onTouched()); this.control?.writeValue(this.pendingValue); }
1450
+ writeValue(value) { this.pendingValue = value; this.control?.writeValue(value); }
1451
+ registerOnChange(fn) { this.onChange = fn; }
1452
+ registerOnTouched(fn) { this.onTouched = fn; }
1453
+ setDisabledState(value) { this.disabled = value; }
1454
+ validate(control) { return validateFormControl(control, this.required, this.pattern); }
1455
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDatetimeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1456
+ 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 });
1457
+ }
1458
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputDatetimeComponent, decorators: [{
1459
+ type: Component,
1460
+ 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" }]
1461
+ }], propDecorators: { control: [{
1462
+ type: ViewChild,
1463
+ args: [InputDatetimeControlComponent]
1464
+ }], label: [{
1465
+ type: Input
1466
+ }], hint: [{
1467
+ type: Input
1468
+ }], error: [{
1469
+ type: Input
1470
+ }], placeholder: [{
1471
+ type: Input
1472
+ }], name: [{
1473
+ type: Input
1474
+ }], id: [{
1475
+ type: Input
1476
+ }], required: [{
1477
+ type: Input
1478
+ }], disabled: [{
1479
+ type: Input
1480
+ }], autocomplete: [{
1481
+ type: Input
1482
+ }], pattern: [{
1483
+ type: Input
1484
+ }], rows: [{
1485
+ type: Input
1486
+ }], actions: [{
1487
+ type: Input
1488
+ }], actionTriggered: [{
1489
+ type: Output
1490
+ }] } });
1491
+
1492
+ class InputEmailControlComponent {
1493
+ placeholder;
1494
+ name;
1495
+ id;
1496
+ autocomplete;
1497
+ pattern;
1498
+ disabled = false;
1499
+ value = '';
1500
+ valueChange = new EventEmitter();
1501
+ touched = new EventEmitter();
1502
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputEmailControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1503
+ 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", "enterPressed"] }] });
1504
+ }
1505
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputEmailControlComponent, decorators: [{
1506
+ type: Component,
1507
+ 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()" />` }]
1508
+ }], propDecorators: { placeholder: [{
1509
+ type: Input
1510
+ }], name: [{
1511
+ type: Input
1512
+ }], id: [{
1513
+ type: Input
1514
+ }], autocomplete: [{
1515
+ type: Input
1516
+ }], pattern: [{
1517
+ type: Input
1518
+ }], disabled: [{
1519
+ type: Input
1520
+ }], value: [{
1521
+ type: Input
1522
+ }], valueChange: [{
1523
+ type: Output
1524
+ }], touched: [{
1525
+ type: Output
1526
+ }] } });
1527
+
1167
1528
  class InputEmailComponent {
1168
1529
  cdr = inject(ChangeDetectorRef);
1169
1530
  label;
@@ -1175,7 +1536,6 @@ class InputEmailComponent {
1175
1536
  element = 'input';
1176
1537
  rows = 4;
1177
1538
  required = false;
1178
- fullWidth = false;
1179
1539
  autocomplete;
1180
1540
  pattern;
1181
1541
  error;
@@ -1214,25 +1574,25 @@ class InputEmailComponent {
1214
1574
  this.actionTriggered.emit(action);
1215
1575
  }
1216
1576
  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: [
1577
+ 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
1578
  {
1219
1579
  provide: NG_VALUE_ACCESSOR,
1220
1580
  useExisting: forwardRef(() => InputEmailComponent),
1221
1581
  multi: true,
1222
1582
  },
1223
1583
  { 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 });
1584
+ ], 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
1585
  }
1226
1586
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputEmailComponent, decorators: [{
1227
1587
  type: Component,
1228
- args: [{ selector: 'input-email', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1588
+ args: [{ selector: 'input-email', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, FieldFrameComponent, InputEmailControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1229
1589
  {
1230
1590
  provide: NG_VALUE_ACCESSOR,
1231
1591
  useExisting: forwardRef(() => InputEmailComponent),
1232
1592
  multi: true,
1233
1593
  },
1234
1594
  { 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" }]
1595
+ ], 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
1596
  }], propDecorators: { label: [{
1237
1597
  type: Input
1238
1598
  }], hint: [{
@@ -1247,8 +1607,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1247
1607
  type: Input
1248
1608
  }], required: [{
1249
1609
  type: Input
1250
- }], fullWidth: [{
1251
- type: Input
1252
1610
  }], autocomplete: [{
1253
1611
  type: Input
1254
1612
  }], pattern: [{
@@ -1261,6 +1619,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1261
1619
  type: Output
1262
1620
  }] } });
1263
1621
 
1622
+ class InputNumberControlComponent {
1623
+ placeholder;
1624
+ name;
1625
+ id;
1626
+ disabled = false;
1627
+ value = '';
1628
+ valueChange = new EventEmitter();
1629
+ touched = new EventEmitter();
1630
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputNumberControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1631
+ 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", "enterPressed"] }] });
1632
+ }
1633
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputNumberControlComponent, decorators: [{
1634
+ type: Component,
1635
+ 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()" />` }]
1636
+ }], propDecorators: { placeholder: [{
1637
+ type: Input
1638
+ }], name: [{
1639
+ type: Input
1640
+ }], id: [{
1641
+ type: Input
1642
+ }], disabled: [{
1643
+ type: Input
1644
+ }], value: [{
1645
+ type: Input
1646
+ }], valueChange: [{
1647
+ type: Output
1648
+ }], touched: [{
1649
+ type: Output
1650
+ }] } });
1651
+
1264
1652
  class InputNumberComponent {
1265
1653
  cdr = inject(ChangeDetectorRef);
1266
1654
  label;
@@ -1272,7 +1660,6 @@ class InputNumberComponent {
1272
1660
  element = 'input';
1273
1661
  rows = 4;
1274
1662
  required = false;
1275
- fullWidth = false;
1276
1663
  autocomplete;
1277
1664
  disabled = false;
1278
1665
  pattern;
@@ -1314,25 +1701,25 @@ class InputNumberComponent {
1314
1701
  this.actionTriggered.emit(action);
1315
1702
  }
1316
1703
  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: [
1704
+ 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
1705
  {
1319
1706
  provide: NG_VALUE_ACCESSOR,
1320
1707
  useExisting: forwardRef(() => InputNumberComponent),
1321
1708
  multi: true,
1322
1709
  },
1323
1710
  { 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 });
1711
+ ], 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
1712
  }
1326
1713
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputNumberComponent, decorators: [{
1327
1714
  type: Component,
1328
- args: [{ selector: 'input-number', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1715
+ args: [{ selector: 'input-number', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, FieldFrameComponent, InputNumberControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1329
1716
  {
1330
1717
  provide: NG_VALUE_ACCESSOR,
1331
1718
  useExisting: forwardRef(() => InputNumberComponent),
1332
1719
  multi: true,
1333
1720
  },
1334
1721
  { 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"] }]
1722
+ ], 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
1723
  }], propDecorators: { label: [{
1337
1724
  type: Input
1338
1725
  }], hint: [{
@@ -1347,8 +1734,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1347
1734
  type: Input
1348
1735
  }], required: [{
1349
1736
  type: Input
1350
- }], fullWidth: [{
1351
- type: Input
1352
1737
  }], autocomplete: [{
1353
1738
  type: Input
1354
1739
  }], disabled: [{
@@ -1363,6 +1748,64 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1363
1748
  type: Output
1364
1749
  }] } });
1365
1750
 
1751
+ class InputPasswordControlComponent {
1752
+ showIcon = Eye;
1753
+ hideIcon = EyeOff;
1754
+ placeholder;
1755
+ name;
1756
+ id;
1757
+ autocomplete;
1758
+ pattern;
1759
+ disabled = false;
1760
+ value = '';
1761
+ valueChange = new EventEmitter();
1762
+ touched = new EventEmitter();
1763
+ visible = false;
1764
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPasswordControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1765
+ 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: `
1766
+ <div class="relative">
1767
+ <input data-field-control [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()" />
1768
+ <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">
1769
+ <lucide-icon [img]="visible ? hideIcon : showIcon" class="h-4 w-4" aria-hidden="true"></lucide-icon>
1770
+ </button>
1771
+ </div>
1772
+ `, 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"] }] });
1773
+ }
1774
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPasswordControlComponent, decorators: [{
1775
+ type: Component,
1776
+ args: [{
1777
+ selector: 'input-password-control',
1778
+ standalone: true,
1779
+ imports: [CommonModule, LucideAngularModule],
1780
+ template: `
1781
+ <div class="relative">
1782
+ <input data-field-control [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()" />
1783
+ <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">
1784
+ <lucide-icon [img]="visible ? hideIcon : showIcon" class="h-4 w-4" aria-hidden="true"></lucide-icon>
1785
+ </button>
1786
+ </div>
1787
+ `,
1788
+ }]
1789
+ }], propDecorators: { placeholder: [{
1790
+ type: Input
1791
+ }], name: [{
1792
+ type: Input
1793
+ }], id: [{
1794
+ type: Input
1795
+ }], autocomplete: [{
1796
+ type: Input
1797
+ }], pattern: [{
1798
+ type: Input
1799
+ }], disabled: [{
1800
+ type: Input
1801
+ }], value: [{
1802
+ type: Input
1803
+ }], valueChange: [{
1804
+ type: Output
1805
+ }], touched: [{
1806
+ type: Output
1807
+ }] } });
1808
+
1366
1809
  class InputPasswordComponent {
1367
1810
  cdr = inject(ChangeDetectorRef);
1368
1811
  label;
@@ -1376,7 +1819,6 @@ class InputPasswordComponent {
1376
1819
  element = 'input';
1377
1820
  rows = 4;
1378
1821
  required = false;
1379
- fullWidth = false;
1380
1822
  autocomplete;
1381
1823
  pattern;
1382
1824
  error;
@@ -1421,25 +1863,25 @@ class InputPasswordComponent {
1421
1863
  this.actionTriggered.emit(action);
1422
1864
  }
1423
1865
  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: [
1866
+ 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
1867
  {
1426
1868
  provide: NG_VALUE_ACCESSOR,
1427
1869
  useExisting: forwardRef(() => InputPasswordComponent),
1428
1870
  multi: true,
1429
1871
  },
1430
1872
  { 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 });
1873
+ ], 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
1874
  }
1433
1875
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPasswordComponent, decorators: [{
1434
1876
  type: Component,
1435
- args: [{ selector: 'input-password', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, LucideAngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1877
+ args: [{ selector: 'input-password', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, LucideAngularModule, FieldFrameComponent, InputPasswordControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1436
1878
  {
1437
1879
  provide: NG_VALUE_ACCESSOR,
1438
1880
  useExisting: forwardRef(() => InputPasswordComponent),
1439
1881
  multi: true,
1440
1882
  },
1441
1883
  { 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" }]
1884
+ ], 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
1885
  }], propDecorators: { label: [{
1444
1886
  type: Input
1445
1887
  }], hint: [{
@@ -1454,8 +1896,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1454
1896
  type: Input
1455
1897
  }], required: [{
1456
1898
  type: Input
1457
- }], fullWidth: [{
1458
- type: Input
1459
1899
  }], autocomplete: [{
1460
1900
  type: Input
1461
1901
  }], pattern: [{
@@ -1479,7 +1919,6 @@ class InputTextComponent {
1479
1919
  element = 'input';
1480
1920
  rows = 4;
1481
1921
  required = false;
1482
- fullWidth = false;
1483
1922
  autocomplete;
1484
1923
  pattern;
1485
1924
  error;
@@ -1518,25 +1957,25 @@ class InputTextComponent {
1518
1957
  this.actionTriggered.emit(action);
1519
1958
  }
1520
1959
  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: [
1960
+ 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
1961
  {
1523
1962
  provide: NG_VALUE_ACCESSOR,
1524
1963
  useExisting: forwardRef(() => InputTextComponent),
1525
1964
  multi: true,
1526
1965
  },
1527
1966
  { 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 });
1967
+ ], 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", "enterPressed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1529
1968
  }
1530
1969
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextComponent, decorators: [{
1531
1970
  type: Component,
1532
- args: [{ selector: 'input-text', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1971
+ args: [{ selector: 'input-text', standalone: true, imports: [CommonModule, FormsModule, FieldFrameComponent, InputTextControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1533
1972
  {
1534
1973
  provide: NG_VALUE_ACCESSOR,
1535
1974
  useExisting: forwardRef(() => InputTextComponent),
1536
1975
  multi: true,
1537
1976
  },
1538
1977
  { 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" }]
1978
+ ], 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
1979
  }], propDecorators: { label: [{
1541
1980
  type: Input
1542
1981
  }], hint: [{
@@ -1551,8 +1990,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1551
1990
  type: Input
1552
1991
  }], required: [{
1553
1992
  type: Input
1554
- }], fullWidth: [{
1555
- type: Input
1556
1993
  }], autocomplete: [{
1557
1994
  type: Input
1558
1995
  }], pattern: [{
@@ -1565,6 +2002,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1565
2002
  type: Output
1566
2003
  }] } });
1567
2004
 
2005
+ class InputTextareaControlComponent {
2006
+ placeholder;
2007
+ name;
2008
+ id;
2009
+ rows = 4;
2010
+ disabled = false;
2011
+ value = '';
2012
+ valueChange = new EventEmitter();
2013
+ touched = new EventEmitter();
2014
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextareaControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2015
+ 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", "enterPressed"] }] });
2016
+ }
2017
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextareaControlComponent, decorators: [{
2018
+ type: Component,
2019
+ 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()" />` }]
2020
+ }], propDecorators: { placeholder: [{
2021
+ type: Input
2022
+ }], name: [{
2023
+ type: Input
2024
+ }], id: [{
2025
+ type: Input
2026
+ }], rows: [{
2027
+ type: Input
2028
+ }], disabled: [{
2029
+ type: Input
2030
+ }], value: [{
2031
+ type: Input
2032
+ }], valueChange: [{
2033
+ type: Output
2034
+ }], touched: [{
2035
+ type: Output
2036
+ }] } });
2037
+
1568
2038
  class InputTextareaComponent {
1569
2039
  cdr = inject(ChangeDetectorRef);
1570
2040
  label;
@@ -1576,7 +2046,6 @@ class InputTextareaComponent {
1576
2046
  element = 'textarea';
1577
2047
  rows = 4;
1578
2048
  required = false;
1579
- fullWidth = false;
1580
2049
  autocomplete;
1581
2050
  pattern;
1582
2051
  error;
@@ -1615,25 +2084,25 @@ class InputTextareaComponent {
1615
2084
  this.actionTriggered.emit(action);
1616
2085
  }
1617
2086
  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: [
2087
+ 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
2088
  {
1620
2089
  provide: NG_VALUE_ACCESSOR,
1621
2090
  useExisting: forwardRef(() => InputTextareaComponent),
1622
2091
  multi: true,
1623
2092
  },
1624
2093
  { 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 });
2094
+ ], 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
2095
  }
1627
2096
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputTextareaComponent, decorators: [{
1628
2097
  type: Component,
1629
- args: [{ selector: 'input-textarea', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
2098
+ args: [{ selector: 'input-textarea', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, FieldFrameComponent, InputTextareaControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
1630
2099
  {
1631
2100
  provide: NG_VALUE_ACCESSOR,
1632
2101
  useExisting: forwardRef(() => InputTextareaComponent),
1633
2102
  multi: true,
1634
2103
  },
1635
2104
  { 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" }]
2105
+ ], 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
2106
  }], propDecorators: { label: [{
1638
2107
  type: Input
1639
2108
  }], hint: [{
@@ -1648,8 +2117,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1648
2117
  type: Input
1649
2118
  }], required: [{
1650
2119
  type: Input
1651
- }], fullWidth: [{
1652
- type: Input
1653
2120
  }], autocomplete: [{
1654
2121
  type: Input
1655
2122
  }], pattern: [{
@@ -1662,15 +2129,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1662
2129
  type: Output
1663
2130
  }] } });
1664
2131
 
2132
+ class InputFileControlComponent {
2133
+ accept;
2134
+ multiple = false;
2135
+ disabled = false;
2136
+ selectedFiles = [];
2137
+ filesSelected = new EventEmitter();
2138
+ removeFile = new EventEmitter();
2139
+ touched = new EventEmitter();
2140
+ onFilesSelected(event) {
2141
+ const input = event.target;
2142
+ this.filesSelected.emit(Array.from(input.files ?? []));
2143
+ input.value = '';
2144
+ }
2145
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputFileControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2146
+ 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 data-field-control 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 }] });
2147
+ }
2148
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputFileControlComponent, decorators: [{
2149
+ type: Component,
2150
+ args: [{ selector: 'input-file-control', standalone: true, imports: [CommonModule], template: "<input data-field-control 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" }]
2151
+ }], propDecorators: { accept: [{
2152
+ type: Input
2153
+ }], multiple: [{
2154
+ type: Input
2155
+ }], disabled: [{
2156
+ type: Input
2157
+ }], selectedFiles: [{
2158
+ type: Input
2159
+ }], filesSelected: [{
2160
+ type: Output
2161
+ }], removeFile: [{
2162
+ type: Output
2163
+ }], touched: [{
2164
+ type: Output
2165
+ }] } });
2166
+
1665
2167
  class InputFileComponent {
1666
2168
  label;
1667
2169
  hint;
2170
+ error;
2171
+ required = false;
1668
2172
  accept;
1669
2173
  multiple = false;
1670
2174
  disabled = false;
1671
- value = null;
1672
- valueChange = new EventEmitter();
1673
2175
  filesChange = new EventEmitter();
2176
+ value = null;
1674
2177
  onChange = () => undefined;
1675
2178
  onTouched = () => undefined;
1676
2179
  writeValue(value) { this.value = value; }
@@ -1688,39 +2191,85 @@ class InputFileComponent {
1688
2191
  const value = this.multiple ? files : (files[0] ?? null);
1689
2192
  this.value = value;
1690
2193
  this.onChange(value);
1691
- this.valueChange.emit(value);
1692
2194
  this.filesChange.emit(files);
1693
- input.value = '';
2195
+ input.value = '';
2196
+ }
2197
+ handleFilesSelected(files) {
2198
+ const value = this.multiple ? files : (files[0] ?? null);
2199
+ this.value = value;
2200
+ this.onChange(value);
2201
+ this.filesChange.emit(files);
1694
2202
  }
1695
2203
  remove(index) {
1696
2204
  const files = this.selectedFiles.filter((_file, fileIndex) => fileIndex !== index);
1697
2205
  const value = this.multiple ? files : (files[0] ?? null);
1698
2206
  this.value = value;
1699
2207
  this.onChange(value);
1700
- this.valueChange.emit(value);
1701
2208
  this.filesChange.emit(files);
1702
2209
  }
1703
2210
  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 }] });
2211
+ 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
2212
  }
1706
2213
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputFileComponent, decorators: [{
1707
2214
  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" }]
2215
+ 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
2216
  }], propDecorators: { label: [{
1710
2217
  type: Input
1711
2218
  }], hint: [{
1712
2219
  type: Input
2220
+ }], error: [{
2221
+ type: Input
2222
+ }], required: [{
2223
+ type: Input
1713
2224
  }], accept: [{
1714
2225
  type: Input
1715
2226
  }], multiple: [{
1716
2227
  type: Input
1717
2228
  }], disabled: [{
1718
2229
  type: Input
1719
- }], value: [{
2230
+ }], filesChange: [{
2231
+ type: Output
2232
+ }] } });
2233
+
2234
+ class InputPhotoControlComponent {
2235
+ accept = 'image/*';
2236
+ multiple = false;
2237
+ disabled = false;
2238
+ canSelectMore = true;
2239
+ previews = [];
2240
+ filesSelected = new EventEmitter();
2241
+ removed = new EventEmitter();
2242
+ dropped = new EventEmitter();
2243
+ touched = new EventEmitter();
2244
+ selectFiles(event) {
2245
+ const input = event.target;
2246
+ this.filesSelected.emit(Array.from(input.files ?? []));
2247
+ input.value = '';
2248
+ this.touched.emit();
2249
+ }
2250
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPhotoControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2251
+ 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 data-field-control 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"] }] });
2252
+ }
2253
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPhotoControlComponent, decorators: [{
2254
+ type: Component,
2255
+ args: [{ selector: 'input-photo-control', standalone: true, imports: [CommonModule, DragDropModule], template: "<button data-field-control 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" }]
2256
+ }], propDecorators: { accept: [{
1720
2257
  type: Input
1721
- }], valueChange: [{
2258
+ }], multiple: [{
2259
+ type: Input
2260
+ }], disabled: [{
2261
+ type: Input
2262
+ }], canSelectMore: [{
2263
+ type: Input
2264
+ }], previews: [{
2265
+ type: Input
2266
+ }], filesSelected: [{
1722
2267
  type: Output
1723
- }], filesChange: [{
2268
+ }], removed: [{
2269
+ type: Output
2270
+ }], dropped: [{
2271
+ type: Output
2272
+ }], touched: [{
1724
2273
  type: Output
1725
2274
  }] } });
1726
2275
 
@@ -1728,14 +2277,15 @@ class InputPhotoComponent {
1728
2277
  http = inject(HttpClient);
1729
2278
  label;
1730
2279
  hint;
2280
+ error;
2281
+ required = false;
1731
2282
  accept = 'image/*';
1732
2283
  multiple = false;
1733
2284
  limit = Number.MAX_SAFE_INTEGER;
1734
2285
  disabled = false;
1735
- value = null;
1736
- valueChange = new EventEmitter();
1737
2286
  filesChange = new EventEmitter();
1738
2287
  uploadingChange = new EventEmitter();
2288
+ value = null;
1739
2289
  pendingFiles = [];
1740
2290
  previews = [];
1741
2291
  uploading = false;
@@ -1750,7 +2300,7 @@ class InputPhotoComponent {
1750
2300
  registerOnTouched(fn) { this.onTouched = fn; }
1751
2301
  setDisabledState(isDisabled) { this.disabled = isDisabled; }
1752
2302
  ngOnChanges(changes) {
1753
- if (changes['value'] || changes['multiple'])
2303
+ if (changes['multiple'])
1754
2304
  this.rebuildPreviews();
1755
2305
  }
1756
2306
  ngOnDestroy() {
@@ -1771,6 +2321,15 @@ class InputPhotoComponent {
1771
2321
  this.rebuildPreviews();
1772
2322
  this.uploadFiles(accepted);
1773
2323
  }
2324
+ handleFilesSelected(files) {
2325
+ if (!files.length || this.disabled)
2326
+ return;
2327
+ const accepted = this.multiple ? files.slice(0, Math.max(0, this.normalizedLimit - this.ids.length - this.pendingFiles.length)) : [files[0]];
2328
+ this.pendingFiles = this.multiple ? [...this.pendingFiles, ...accepted] : [accepted[0]];
2329
+ this.filesChange.emit(this.pendingFiles);
2330
+ this.rebuildPreviews();
2331
+ this.uploadFiles(accepted);
2332
+ }
1774
2333
  remove(index) {
1775
2334
  const ids = [...this.ids];
1776
2335
  if (index < ids.length) {
@@ -1801,7 +2360,6 @@ class InputPhotoComponent {
1801
2360
  emitValue(value) {
1802
2361
  this.value = value;
1803
2362
  this.onChange(value);
1804
- this.valueChange.emit(value);
1805
2363
  this.rebuildPreviews(value);
1806
2364
  }
1807
2365
  uploadFiles(files) {
@@ -1862,15 +2420,19 @@ class InputPhotoComponent {
1862
2420
  return normalized.startsWith('image/') || normalized === 'application/pdf';
1863
2421
  }
1864
2422
  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"] }] });
2423
+ 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
2424
  }
1867
2425
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPhotoComponent, decorators: [{
1868
2426
  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" }]
2427
+ 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
2428
  }], propDecorators: { label: [{
1871
2429
  type: Input
1872
2430
  }], hint: [{
1873
2431
  type: Input
2432
+ }], error: [{
2433
+ type: Input
2434
+ }], required: [{
2435
+ type: Input
1874
2436
  }], accept: [{
1875
2437
  type: Input
1876
2438
  }], multiple: [{
@@ -1879,17 +2441,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1879
2441
  type: Input
1880
2442
  }], disabled: [{
1881
2443
  type: Input
1882
- }], value: [{
1883
- type: Input
1884
- }], valueChange: [{
1885
- type: Output
1886
2444
  }], filesChange: [{
1887
2445
  type: Output
1888
2446
  }], uploadingChange: [{
1889
2447
  type: Output
1890
2448
  }] } });
1891
2449
 
1892
- class InputSelectComponent {
2450
+ class InputSelectControlComponent {
1893
2451
  closeIcon = X;
1894
2452
  chevronDownIcon = ChevronDown;
1895
2453
  checkIcon = Check;
@@ -1902,16 +2460,14 @@ class InputSelectComponent {
1902
2460
  placeholder;
1903
2461
  name;
1904
2462
  data;
2463
+ dataParams = {};
1905
2464
  required = false;
1906
- fullWidth = false;
1907
2465
  disabled = false;
1908
2466
  multiple = false;
1909
2467
  includeBlankOption = false;
1910
2468
  blankOptionLabel = 'Оберіть…';
1911
2469
  emptyStateLabel = 'Немає варіантів для вибору';
1912
2470
  searchable = true;
1913
- remoteSearch = false;
1914
- searchPlaceholder = 'Пошук...';
1915
2471
  _options = [];
1916
2472
  set options(value) {
1917
2473
  this._options = value ?? [];
@@ -1975,7 +2531,7 @@ class InputSelectComponent {
1975
2531
  });
1976
2532
  }
1977
2533
  ngOnChanges(changes) {
1978
- if ('data' in changes) {
2534
+ if ('data' in changes || 'dataParams' in changes) {
1979
2535
  this.reloadDictionaryOptions();
1980
2536
  }
1981
2537
  }
@@ -2086,7 +2642,7 @@ class InputSelectComponent {
2086
2642
  }
2087
2643
  get visibleOptions() {
2088
2644
  const options = this.getRenderOptions();
2089
- if (!this.searchable || this.remoteSearch || !this.searchTerm.trim()) {
2645
+ if (!this.searchable || this.getDictionaryName() || !this.searchTerm.trim()) {
2090
2646
  return options;
2091
2647
  }
2092
2648
  const search = this.searchTerm.trim().toLowerCase();
@@ -2233,32 +2789,37 @@ class InputSelectComponent {
2233
2789
  const params = new URLSearchParams();
2234
2790
  if (query?.trim())
2235
2791
  params.set('q', query.trim());
2792
+ for (const [key, value] of Object.entries(this.dataParams || {})) {
2793
+ if (value !== null && value !== undefined && String(value).trim()) {
2794
+ params.set(key, String(value));
2795
+ }
2796
+ }
2236
2797
  const suffix = params.toString() ? `?${params.toString()}` : '';
2237
2798
  return this.http.get(`/api/suggest/${encodeURIComponent(name)}${suffix}`);
2238
2799
  }
2239
2800
  loadSuggestItem(name, key) {
2240
2801
  return this.http.get(`/api/suggest/${encodeURIComponent(name)}/${encodeURIComponent(key)}`);
2241
2802
  }
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: [
2803
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSelectControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2804
+ 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", dataParams: "dataParams", 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
2805
  {
2245
2806
  provide: NG_VALUE_ACCESSOR,
2246
- useExisting: forwardRef(() => InputSelectComponent),
2807
+ useExisting: forwardRef(() => InputSelectControlComponent),
2247
2808
  multi: true,
2248
2809
  },
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 });
2810
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSelectControlComponent), multi: true },
2811
+ ], 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 data-field-control\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
2812
  }
2252
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSelectComponent, decorators: [{
2813
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSelectControlComponent, decorators: [{
2253
2814
  type: Component,
2254
- args: [{ selector: 'input-select', standalone: true, imports: [LucideAngularModule, CommonModule, FormsModule, CdkConnectedOverlay, CdkOverlayOrigin], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
2815
+ args: [{ selector: 'input-select-control', standalone: true, imports: [LucideAngularModule, CommonModule, FormsModule, CdkConnectedOverlay, CdkOverlayOrigin], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
2255
2816
  {
2256
2817
  provide: NG_VALUE_ACCESSOR,
2257
- useExisting: forwardRef(() => InputSelectComponent),
2818
+ useExisting: forwardRef(() => InputSelectControlComponent),
2258
2819
  multi: true,
2259
2820
  },
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" }]
2821
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSelectControlComponent), multi: true },
2822
+ ], template: " <div class=\"relative w-full\">\n\n <button\n data-field-control\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
2823
  }], ctorParameters: () => [], propDecorators: { label: [{
2263
2824
  type: Input
2264
2825
  }], placeholder: [{
@@ -2267,9 +2828,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
2267
2828
  type: Input
2268
2829
  }], data: [{
2269
2830
  type: Input
2270
- }], required: [{
2831
+ }], dataParams: [{
2271
2832
  type: Input
2272
- }], fullWidth: [{
2833
+ }], required: [{
2273
2834
  type: Input
2274
2835
  }], disabled: [{
2275
2836
  type: Input
@@ -2284,10 +2845,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
2284
2845
  type: Input
2285
2846
  }], searchable: [{
2286
2847
  type: Input
2287
- }], remoteSearch: [{
2288
- type: Input
2289
- }], searchPlaceholder: [{
2290
- type: Input
2291
2848
  }], options: [{
2292
2849
  type: Input
2293
2850
  }], error: [{
@@ -2313,6 +2870,139 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
2313
2870
  args: ['document:keydown.escape']
2314
2871
  }] } });
2315
2872
 
2873
+ class InputSelectComponent {
2874
+ control;
2875
+ label;
2876
+ hint;
2877
+ error;
2878
+ placeholder;
2879
+ name;
2880
+ data;
2881
+ dataParams = {};
2882
+ required = false;
2883
+ disabled = false;
2884
+ multiple = false;
2885
+ includeBlankOption = false;
2886
+ blankOptionLabel = 'Оберіть…';
2887
+ emptyStateLabel = 'Немає варіантів для вибору';
2888
+ searchable = true;
2889
+ options = [];
2890
+ triggerClass = '';
2891
+ panelClass = '';
2892
+ valueChange = new EventEmitter();
2893
+ searchChange = new EventEmitter();
2894
+ pendingValue = null;
2895
+ onChange = () => undefined;
2896
+ onTouched = () => undefined;
2897
+ ngAfterViewInit() {
2898
+ this.control?.registerOnChange((value) => {
2899
+ this.onChange(value);
2900
+ this.valueChange.emit(value);
2901
+ });
2902
+ this.control?.registerOnTouched(() => this.onTouched());
2903
+ this.control?.writeValue(this.pendingValue);
2904
+ }
2905
+ writeValue(value) {
2906
+ this.pendingValue = value;
2907
+ this.control?.writeValue(value);
2908
+ }
2909
+ registerOnChange(fn) { this.onChange = fn; }
2910
+ registerOnTouched(fn) { this.onTouched = fn; }
2911
+ setDisabledState(isDisabled) { this.disabled = isDisabled; }
2912
+ validate(control) { return validateFormControl(control, this.required); }
2913
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2914
+ 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", dataParams: "dataParams", 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: [
2915
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputSelectComponent), multi: true },
2916
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSelectComponent), multi: true },
2917
+ ], 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 [dataParams]=\"dataParams\"\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", "dataParams", "required", "disabled", "multiple", "includeBlankOption", "blankOptionLabel", "emptyStateLabel", "searchable", "options", "error", "hint", "triggerClass", "panelClass"], outputs: ["valueChange", "searchChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2918
+ }
2919
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputSelectComponent, decorators: [{
2920
+ type: Component,
2921
+ args: [{ selector: 'input-select', standalone: true, imports: [FieldFrameComponent, InputSelectControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
2922
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputSelectComponent), multi: true },
2923
+ { provide: NG_VALIDATORS, useExisting: forwardRef(() => InputSelectComponent), multi: true },
2924
+ ], 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 [dataParams]=\"dataParams\"\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" }]
2925
+ }], propDecorators: { control: [{
2926
+ type: ViewChild,
2927
+ args: [InputSelectControlComponent]
2928
+ }], label: [{
2929
+ type: Input
2930
+ }], hint: [{
2931
+ type: Input
2932
+ }], error: [{
2933
+ type: Input
2934
+ }], placeholder: [{
2935
+ type: Input
2936
+ }], name: [{
2937
+ type: Input
2938
+ }], data: [{
2939
+ type: Input
2940
+ }], dataParams: [{
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 data-field-control 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 data-field-control 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: [{
@@ -2423,9 +3117,10 @@ const UI_FORM_INPUT_REGISTRY = {
2423
3117
  class FieldInputComponent {
2424
3118
  field;
2425
3119
  value;
3120
+ model = {};
2426
3121
  error = '';
2427
3122
  disabled = false;
2428
- actions = [];
3123
+ visibleActions = [];
2429
3124
  valueChange = new EventEmitter();
2430
3125
  actionTriggered = new EventEmitter();
2431
3126
  lookupRequested = new EventEmitter();
@@ -2476,11 +3171,17 @@ class FieldInputComponent {
2476
3171
  if (!this.componentRef || !this.field)
2477
3172
  return;
2478
3173
  const field = this.field;
3174
+ const config = field;
2479
3175
  const setInput = (name, value) => this.componentRef?.setInput(name, value);
2480
3176
  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.
3177
+ if (field.label !== undefined)
3178
+ setInput('label', field.label);
3179
+ if (field.hint !== undefined)
3180
+ setInput('hint', field.hint);
3181
+ setInput('error', this.error);
3182
+ if (typeof field.required === 'boolean')
3183
+ setInput('required', field.required);
3184
+ // FieldInput only adapts the dynamic field to the selected control.
2484
3185
  switch (inputType) {
2485
3186
  case 'text':
2486
3187
  case 'number':
@@ -2489,57 +3190,48 @@ class FieldInputComponent {
2489
3190
  case 'email':
2490
3191
  case 'password':
2491
3192
  case 'textarea':
3193
+ setInput('actions', this.visibleActions);
2492
3194
  setInput('name', field.id);
2493
3195
  setInput('id', field.id);
2494
3196
  setInput('placeholder', field.placeholder);
2495
- setInput('pattern', field.pattern);
2496
- setInput('rows', field.rows ?? 4);
2497
- setInput('fullWidth', fullWidth);
3197
+ setInput('pattern', config['pattern']);
3198
+ setInput('rows', config['rows'] ?? 4);
2498
3199
  break;
2499
3200
  case 'slug':
2500
3201
  setInput('name', field.id);
2501
3202
  setInput('id', field.id);
2502
3203
  setInput('placeholder', field.placeholder);
2503
- setInput('fullWidth', fullWidth);
2504
3204
  break;
2505
3205
  case 'select':
2506
3206
  setInput('name', field.id);
2507
3207
  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);
3208
+ setInput('data', config['data']);
3209
+ const dataParams = config['dataParams'];
3210
+ setInput('dataParams', Object.fromEntries(Object.entries(dataParams || {})
3211
+ .map(([key, fieldId]) => [key, this.model[fieldId]])
3212
+ .filter(([, value]) => value !== undefined && value !== null)));
3213
+ setInput('options', config['options'] || []);
3214
+ setInput('multiple', !!config['multiple']);
3215
+ if (typeof config['searchable'] === 'boolean') {
3216
+ setInput('searchable', config['searchable']);
3217
+ }
2515
3218
  break;
2516
3219
  case 'color':
2517
3220
  setInput('name', field.id);
2518
3221
  setInput('id', field.id);
2519
- setInput('required', false);
2520
- setInput('fullWidth', fullWidth);
2521
3222
  break;
2522
3223
  case 'file':
2523
- setInput('accept', field.accept);
2524
- setInput('multiple', !!field.multiple);
3224
+ setInput('accept', config['accept']);
3225
+ setInput('multiple', !!config['multiple']);
2525
3226
  break;
2526
3227
  case 'photo':
2527
- setInput('accept', field.accept || 'image/*');
2528
- setInput('multiple', !!field.multiple);
2529
- setInput('limit', field.limit ?? 0);
3228
+ setInput('accept', config['accept'] || 'image/*');
3229
+ setInput('multiple', !!config['multiple']);
3230
+ setInput('limit', config['limit'] ?? 0);
2530
3231
  break;
2531
3232
  case 'barcode':
2532
- setInput('label', '');
2533
- setInput('hint', '');
2534
3233
  setInput('placeholder', field.placeholder);
2535
3234
  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
3235
  setInput('value', this.value ?? '');
2544
3236
  break;
2545
3237
  case 'checkbox':
@@ -2550,95 +3242,31 @@ class FieldInputComponent {
2550
3242
  instance.setDisabledState?.(this.disabled);
2551
3243
  }
2552
3244
  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"] }] });
3245
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.19", type: FieldInputComponent, isStandalone: true, selector: "field-input", inputs: { field: "field", value: "value", model: "model", 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: `
3246
+ <ng-container #inputContainer></ng-container>
3247
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2589
3248
  }
2590
3249
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FieldInputComponent, decorators: [{
2591
3250
  type: Component,
2592
3251
  args: [{
2593
3252
  selector: 'field-input',
2594
3253
  standalone: true,
2595
- imports: [CommonModule, UiButtonComponent],
3254
+ imports: [CommonModule],
2596
3255
  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>
3256
+ <ng-container #inputContainer></ng-container>
2631
3257
  `,
2632
3258
  }]
2633
3259
  }], propDecorators: { field: [{
2634
3260
  type: Input
2635
3261
  }], value: [{
2636
3262
  type: Input
3263
+ }], model: [{
3264
+ type: Input
2637
3265
  }], error: [{
2638
3266
  type: Input
2639
3267
  }], disabled: [{
2640
3268
  type: Input
2641
- }], actions: [{
3269
+ }], visibleActions: [{
2642
3270
  type: Input
2643
3271
  }], valueChange: [{
2644
3272
  type: Output
@@ -2898,6 +3526,8 @@ class DynamicFormComponent {
2898
3526
  this.runFieldLookup(field);
2899
3527
  }
2900
3528
  runFieldLookup(field, overrideValue) {
3529
+ if (!isDynamicFormLookupField(field))
3530
+ return;
2901
3531
  const api = String(field.lookupApi || '').trim();
2902
3532
  if (!api)
2903
3533
  return;
@@ -3047,7 +3677,7 @@ class DynamicFormComponent {
3047
3677
  ].filter(Boolean).join(' ');
3048
3678
  }
3049
3679
  getContainerContentClass(field) {
3050
- const className = String(field.contentClassName || '').trim();
3680
+ const className = String(this.isContainerField(field) ? field.contentClassName || '' : '').trim();
3051
3681
  const layout = this.isContainerField(field) ? field.layout : undefined;
3052
3682
  return [
3053
3683
  layout === 'stack' ? 'space-y-4' : 'grid grid-cols-1 gap-4 md:grid-cols-12',
@@ -3090,6 +3720,8 @@ class DynamicFormComponent {
3090
3720
  return classes[span] || classes[12];
3091
3721
  }
3092
3722
  applyLookupResponse(field, response) {
3723
+ if (!isDynamicFormLookupField(field))
3724
+ return;
3093
3725
  const lookupMap = field.lookupMap || {};
3094
3726
  const mapped = Object.entries(lookupMap).reduce((acc, [targetField, sourcePath]) => {
3095
3727
  acc[targetField] = this.resolveTemplateValue(response, sourcePath);
@@ -3121,11 +3753,11 @@ class DynamicFormComponent {
3121
3753
  return this.getFlatFields().filter((field) => field.type !== 'container' && field.type !== 'template');
3122
3754
  }
3123
3755
  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"] }] });
3756
+ 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 [model]=\"modelSignal()\"\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", "model", "error", "disabled", "visibleActions"], outputs: ["valueChange", "actionTriggered", "lookupRequested", "barcodeScanned", "filesChange", "uploadingChange"] }] });
3125
3757
  }
3126
3758
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: DynamicFormComponent, decorators: [{
3127
3759
  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" }]
3760
+ 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 [model]=\"modelSignal()\"\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
3761
  }], propDecorators: { fields: [{
3130
3762
  type: Input
3131
3763
  }], value: [{
@@ -3353,6 +3985,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3353
3985
  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
3986
  }], 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
3987
 
3988
+ class InputBaseControlComponent {
3989
+ placeholder;
3990
+ name;
3991
+ id;
3992
+ type = 'text';
3993
+ element = 'input';
3994
+ rows = 4;
3995
+ autocomplete;
3996
+ pattern;
3997
+ disabled = false;
3998
+ value = '';
3999
+ valueChange = new EventEmitter();
4000
+ touched = new EventEmitter();
4001
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBaseControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4002
+ 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", "enterPressed"] }] });
4003
+ }
4004
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputBaseControlComponent, decorators: [{
4005
+ type: Component,
4006
+ 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()" />` }]
4007
+ }], propDecorators: { placeholder: [{
4008
+ type: Input
4009
+ }], name: [{
4010
+ type: Input
4011
+ }], id: [{
4012
+ type: Input
4013
+ }], type: [{
4014
+ type: Input
4015
+ }], element: [{
4016
+ type: Input
4017
+ }], rows: [{
4018
+ type: Input
4019
+ }], autocomplete: [{
4020
+ type: Input
4021
+ }], pattern: [{
4022
+ type: Input
4023
+ }], disabled: [{
4024
+ type: Input
4025
+ }], value: [{
4026
+ type: Input
4027
+ }], valueChange: [{
4028
+ type: Output
4029
+ }], touched: [{
4030
+ type: Output
4031
+ }] } });
4032
+
3356
4033
  class InputComponent {
3357
4034
  cdr = inject(ChangeDetectorRef);
3358
4035
  label;
@@ -3364,7 +4041,6 @@ class InputComponent {
3364
4041
  element = 'input';
3365
4042
  rows = 4;
3366
4043
  required = false;
3367
- fullWidth = false;
3368
4044
  autocomplete;
3369
4045
  pattern;
3370
4046
  error;
@@ -3402,23 +4078,23 @@ class InputComponent {
3402
4078
  this.actionTriggered.emit(action);
3403
4079
  }
3404
4080
  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: [
4081
+ 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
4082
  {
3407
4083
  provide: NG_VALUE_ACCESSOR,
3408
4084
  useExisting: forwardRef(() => InputComponent),
3409
4085
  multi: true,
3410
4086
  },
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 });
4087
+ ], 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
4088
  }
3413
4089
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputComponent, decorators: [{
3414
4090
  type: Component,
3415
- args: [{ selector: 'input-base', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
4091
+ args: [{ selector: 'input-base', standalone: true, imports: [CommonModule, FormsModule, UiButtonComponent, FieldFrameComponent, InputBaseControlComponent], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
3416
4092
  {
3417
4093
  provide: NG_VALUE_ACCESSOR,
3418
4094
  useExisting: forwardRef(() => InputComponent),
3419
4095
  multi: true,
3420
4096
  },
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" }]
4097
+ ], 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
4098
  }], propDecorators: { label: [{
3423
4099
  type: Input
3424
4100
  }], hint: [{
@@ -3437,8 +4113,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3437
4113
  type: Input
3438
4114
  }], required: [{
3439
4115
  type: Input
3440
- }], fullWidth: [{
3441
- type: Input
3442
4116
  }], autocomplete: [{
3443
4117
  type: Input
3444
4118
  }], pattern: [{
@@ -3454,6 +4128,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3454
4128
  class InputPhotoGalleryComponent {
3455
4129
  http = inject(HttpClient);
3456
4130
  label = 'Фото';
4131
+ hint;
4132
+ error;
4133
+ required = false;
3457
4134
  orderLabel = 'Порядок фото (перетягуй)';
3458
4135
  imageIds = [];
3459
4136
  disabled = false;
@@ -3594,13 +4271,19 @@ class InputPhotoGalleryComponent {
3594
4271
  return this.http.post('/api/files/', formData).pipe(map((response) => ({ imageId: response.id })));
3595
4272
  }
3596
4273
  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"] }] });
4274
+ 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 data-field-control 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
4275
  }
3599
4276
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputPhotoGalleryComponent, decorators: [{
3600
4277
  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"] }]
4278
+ 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 data-field-control 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
4279
  }], propDecorators: { label: [{
3603
4280
  type: Input
4281
+ }], hint: [{
4282
+ type: Input
4283
+ }], error: [{
4284
+ type: Input
4285
+ }], required: [{
4286
+ type: Input
3604
4287
  }], orderLabel: [{
3605
4288
  type: Input
3606
4289
  }], imageIds: [{
@@ -3618,6 +4301,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3618
4301
  }] } });
3619
4302
 
3620
4303
  class InputRangeSliderComponent {
4304
+ label;
4305
+ hint;
4306
+ error;
4307
+ required = false;
3621
4308
  min = 0;
3622
4309
  max = 100;
3623
4310
  step = 1;
@@ -3707,7 +4394,8 @@ class InputRangeSliderComponent {
3707
4394
  input.value = String(value);
3708
4395
  }
3709
4396
  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: `
4397
+ 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: `
4398
+ <field-frame [label]="label" [hint]="hint" [error]="error" [required]="required">
3711
4399
  <div class="range-slider">
3712
4400
  <div class="range-slider__track"></div>
3713
4401
  <div
@@ -3718,6 +4406,7 @@ class InputRangeSliderComponent {
3718
4406
 
3719
4407
  <input
3720
4408
  #startInput
4409
+ data-field-control
3721
4410
  class="range-slider__input"
3722
4411
  type="range"
3723
4412
  [min]="safeMin"
@@ -3730,6 +4419,7 @@ class InputRangeSliderComponent {
3730
4419
 
3731
4420
  <input
3732
4421
  #endInput
4422
+ data-field-control
3733
4423
  class="range-slider__input"
3734
4424
  type="range"
3735
4425
  [min]="safeMin"
@@ -3740,11 +4430,13 @@ class InputRangeSliderComponent {
3740
4430
  (input)="onEndInput($event)"
3741
4431
  (change)="interactionEnd.emit()">
3742
4432
  </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 }] });
4433
+ </field-frame>
4434
+ `, 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
4435
  }
3745
4436
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InputRangeSliderComponent, decorators: [{
3746
4437
  type: Component,
3747
- args: [{ selector: 'input-range-slider', standalone: true, imports: [CommonModule], template: `
4438
+ args: [{ selector: 'input-range-slider', standalone: true, imports: [CommonModule, FieldFrameComponent], template: `
4439
+ <field-frame [label]="label" [hint]="hint" [error]="error" [required]="required">
3748
4440
  <div class="range-slider">
3749
4441
  <div class="range-slider__track"></div>
3750
4442
  <div
@@ -3755,6 +4447,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3755
4447
 
3756
4448
  <input
3757
4449
  #startInput
4450
+ data-field-control
3758
4451
  class="range-slider__input"
3759
4452
  type="range"
3760
4453
  [min]="safeMin"
@@ -3767,6 +4460,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3767
4460
 
3768
4461
  <input
3769
4462
  #endInput
4463
+ data-field-control
3770
4464
  class="range-slider__input"
3771
4465
  type="range"
3772
4466
  [min]="safeMin"
@@ -3777,8 +4471,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3777
4471
  (input)="onEndInput($event)"
3778
4472
  (change)="interactionEnd.emit()">
3779
4473
  </div>
4474
+ </field-frame>
3780
4475
  `, 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: [{
4476
+ }], propDecorators: { label: [{
4477
+ type: Input
4478
+ }], hint: [{
4479
+ type: Input
4480
+ }], error: [{
4481
+ type: Input
4482
+ }], required: [{
4483
+ type: Input
4484
+ }], min: [{
3782
4485
  type: Input
3783
4486
  }], max: [{
3784
4487
  type: Input
@@ -3810,5 +4513,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
3810
4513
  * Generated bundle index. Do not edit.
3811
4514
  */
3812
4515
 
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 };
4516
+ 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
4517
  //# sourceMappingURL=maro-tech-form.mjs.map