shirkasoft-ui-components 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,2748 @@
|
|
|
1
|
+
import * as i1 from '@angular/common';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { inject, ChangeDetectorRef, input, output, viewChild, signal, computed, DestroyRef, effect, forwardRef, ChangeDetectionStrategy, Component, Injectable, model, ElementRef, HostListener, ApplicationRef, EnvironmentInjector, createComponent, ViewContainerRef, untracked } from '@angular/core';
|
|
5
|
+
import * as i2 from '@angular/forms';
|
|
6
|
+
import { ReactiveFormsModule, NG_VALUE_ACCESSOR, FormsModule, FormControl, FormGroup, FormBuilder } from '@angular/forms';
|
|
7
|
+
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
8
|
+
import * as i1$1 from '@jsverse/transloco';
|
|
9
|
+
import { TranslocoService, TranslocoModule } from '@jsverse/transloco';
|
|
10
|
+
import { LucideEyeOff, LucideEye, LucideSearch, LucideDynamicIcon, LucideX, LucideTriangleAlert, LucideInfo, LucideCircleX, LucideCheck, LucideLoader, LucideTrash2, LucideLoaderCircle, LucideMinimize2, LucideMaximize2, LucideChevronRight, LucideChevronLeft, LucideChevronDown, LucideFile, LucideUpload, LucideList, LucideWarehouse, LucideUserCog, LucideShieldCheck, LucideCalculator, LucidePercent, LucideDollarSign, LucideLineChart, LucideBot, LucideReceipt, LucideUsers, LucideTruck, LucideTag, LucideTags, LucideBox, LucidePackage, LucideLandmark, LucideBuilding, LucideUserPlus, LucideUserRoundX, LucideUserCheck, LucideUser, LucideLogOut, LucideSettings, LucideClock, LucideStar, LucideVideo, LucidePlay, LucideShare2, LucideCopy, LucideCircleCheck, LucideArchive, LucideFileText, LucidePresentation, LucideFileSpreadsheet, LucideRefreshCw, LucideCirclePlus, LucidePlus, LucideDownload, LucideToggleLeft, LucideToggleRight, LucideUserPen, LucideKey, LucidePencil, LucideFunnel, LucideInbox, LucideArrowUpDown, LucideArrowDown, LucideArrowUp, LucideChevronsRight, LucideChevronsLeft } from '@lucide/angular';
|
|
11
|
+
import { Router } from '@angular/router';
|
|
12
|
+
import { Subject, debounceTime } from 'rxjs';
|
|
13
|
+
|
|
14
|
+
const icons$6 = {
|
|
15
|
+
search: LucideSearch,
|
|
16
|
+
eye: LucideEye,
|
|
17
|
+
'eye-off': LucideEyeOff,
|
|
18
|
+
};
|
|
19
|
+
class TextFieldComponent {
|
|
20
|
+
cdr = inject(ChangeDetectorRef);
|
|
21
|
+
id = input('', ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
22
|
+
formatCard = input(false, ...(ngDevMode ? [{ debugName: "formatCard" }] : /* istanbul ignore next */ []));
|
|
23
|
+
preventNegative = input(false, ...(ngDevMode ? [{ debugName: "preventNegative" }] : /* istanbul ignore next */ []));
|
|
24
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
25
|
+
type = input('text', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
26
|
+
numbersOnly = input(false, ...(ngDevMode ? [{ debugName: "numbersOnly" }] : /* istanbul ignore next */ []));
|
|
27
|
+
allowDecimals = input(false, ...(ngDevMode ? [{ debugName: "allowDecimals" }] : /* istanbul ignore next */ []));
|
|
28
|
+
maxLength = input(undefined, ...(ngDevMode ? [{ debugName: "maxLength" }] : /* istanbul ignore next */ []));
|
|
29
|
+
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
30
|
+
errorMessages = input({}, ...(ngDevMode ? [{ debugName: "errorMessages" }] : /* istanbul ignore next */ []));
|
|
31
|
+
formGroup = input(undefined, ...(ngDevMode ? [{ debugName: "formGroup" }] : /* istanbul ignore next */ []));
|
|
32
|
+
control = input(undefined, ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
|
|
33
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
34
|
+
errorMessage = input('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
35
|
+
isTextArea = input(false, ...(ngDevMode ? [{ debugName: "isTextArea" }] : /* istanbul ignore next */ []));
|
|
36
|
+
textAreaHeight = input('h-28', ...(ngDevMode ? [{ debugName: "textAreaHeight" }] : /* istanbul ignore next */ []));
|
|
37
|
+
searchMode = input(false, ...(ngDevMode ? [{ debugName: "searchMode" }] : /* istanbul ignore next */ []));
|
|
38
|
+
displayValue = input('', ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
39
|
+
searchButtonClick = output();
|
|
40
|
+
getIcon = (name) => icons$6[name] ?? LucideEye;
|
|
41
|
+
inputElement = viewChild('input', ...(ngDevMode ? [{ debugName: "inputElement" }] : /* istanbul ignore next */ []));
|
|
42
|
+
textareaElement = viewChild('textarea', ...(ngDevMode ? [{ debugName: "textareaElement" }] : /* istanbul ignore next */ []));
|
|
43
|
+
_value = signal('', ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
|
|
44
|
+
_disabledState = signal(false, ...(ngDevMode ? [{ debugName: "_disabledState" }] : /* istanbul ignore next */ []));
|
|
45
|
+
showPassword = signal(false, ...(ngDevMode ? [{ debugName: "showPassword" }] : /* istanbul ignore next */ []));
|
|
46
|
+
hadError = signal(false, ...(ngDevMode ? [{ debugName: "hadError" }] : /* istanbul ignore next */ []));
|
|
47
|
+
errorTracker = signal(0, ...(ngDevMode ? [{ debugName: "errorTracker" }] : /* istanbul ignore next */ []));
|
|
48
|
+
displayLabel = computed(() => {
|
|
49
|
+
if (this.searchMode() && this.displayValue()) {
|
|
50
|
+
return this.displayValue();
|
|
51
|
+
}
|
|
52
|
+
return '';
|
|
53
|
+
}, ...(ngDevMode ? [{ debugName: "displayLabel" }] : /* istanbul ignore next */ []));
|
|
54
|
+
subscribed = signal(false, ...(ngDevMode ? [{ debugName: "subscribed" }] : /* istanbul ignore next */ []));
|
|
55
|
+
isDisabled = computed(() => this.disabled() || this._disabledState(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
56
|
+
value = computed(() => this._value(), ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
57
|
+
actualControl = computed(() => {
|
|
58
|
+
const ctrl = this.control();
|
|
59
|
+
if (ctrl)
|
|
60
|
+
return ctrl;
|
|
61
|
+
const fg = this.formGroup();
|
|
62
|
+
const lbl = this.label();
|
|
63
|
+
if (fg && lbl) {
|
|
64
|
+
return fg.get(lbl);
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}, ...(ngDevMode ? [{ debugName: "actualControl" }] : /* istanbul ignore next */ []));
|
|
68
|
+
shouldShowError = computed(() => {
|
|
69
|
+
this.errorTracker();
|
|
70
|
+
const ctrl = this.actualControl();
|
|
71
|
+
return ctrl?.invalid && (ctrl?.dirty || ctrl?.touched);
|
|
72
|
+
}, ...(ngDevMode ? [{ debugName: "shouldShowError" }] : /* istanbul ignore next */ []));
|
|
73
|
+
wasFixedError = computed(() => {
|
|
74
|
+
this.errorTracker();
|
|
75
|
+
const ctrl = this.actualControl();
|
|
76
|
+
return this.hadError() && ctrl?.valid && ctrl?.touched;
|
|
77
|
+
}, ...(ngDevMode ? [{ debugName: "wasFixedError" }] : /* istanbul ignore next */ []));
|
|
78
|
+
getErrorMessages = computed(() => {
|
|
79
|
+
this.errorTracker();
|
|
80
|
+
const customError = this.errorMessage();
|
|
81
|
+
if (customError) {
|
|
82
|
+
return [customError];
|
|
83
|
+
}
|
|
84
|
+
const ctrl = this.actualControl();
|
|
85
|
+
if (!ctrl?.errors)
|
|
86
|
+
return [];
|
|
87
|
+
const customErrors = this.errorMessages();
|
|
88
|
+
const t = this.transloco;
|
|
89
|
+
return Object.keys(ctrl.errors).map((errorKey) => {
|
|
90
|
+
if (customErrors[errorKey])
|
|
91
|
+
return customErrors[errorKey];
|
|
92
|
+
switch (errorKey) {
|
|
93
|
+
case 'required':
|
|
94
|
+
return t.translate('validation.required');
|
|
95
|
+
case 'email':
|
|
96
|
+
return t.translate('validation.email');
|
|
97
|
+
case 'minlength':
|
|
98
|
+
return t.translate('validation.minlength', {
|
|
99
|
+
min: ctrl.errors['minlength'].requiredLength,
|
|
100
|
+
});
|
|
101
|
+
case 'maxlength':
|
|
102
|
+
return t.translate('validation.maxlength', {
|
|
103
|
+
max: ctrl.errors['maxlength'].requiredLength,
|
|
104
|
+
});
|
|
105
|
+
case 'pattern':
|
|
106
|
+
return t.translate('validation.pattern');
|
|
107
|
+
default:
|
|
108
|
+
return t.translate('validation.invalid');
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}, ...(ngDevMode ? [{ debugName: "getErrorMessages" }] : /* istanbul ignore next */ []));
|
|
112
|
+
effectiveType = computed(() => {
|
|
113
|
+
if (this.isTextArea())
|
|
114
|
+
return 'text';
|
|
115
|
+
if (this.type() === 'password' && this.showPassword())
|
|
116
|
+
return 'text';
|
|
117
|
+
return this.type();
|
|
118
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveType" }] : /* istanbul ignore next */ []));
|
|
119
|
+
transloco = inject(TranslocoService);
|
|
120
|
+
onChange = () => { };
|
|
121
|
+
onTouched = () => { };
|
|
122
|
+
destroyRef = inject(DestroyRef);
|
|
123
|
+
constructor() {
|
|
124
|
+
effect(() => {
|
|
125
|
+
const ctrl = this.actualControl();
|
|
126
|
+
const shouldDisable = this.isDisabled();
|
|
127
|
+
if (ctrl && shouldDisable) {
|
|
128
|
+
Promise.resolve().then(() => {
|
|
129
|
+
ctrl.disable();
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
effect(() => {
|
|
134
|
+
const ctrl = this.actualControl();
|
|
135
|
+
if (!ctrl) {
|
|
136
|
+
this.subscribed.set(false);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (this.subscribed())
|
|
140
|
+
return;
|
|
141
|
+
if (ctrl.invalid && ctrl.touched) {
|
|
142
|
+
this.hadError.set(true);
|
|
143
|
+
}
|
|
144
|
+
ctrl.statusChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
|
145
|
+
this.errorTracker.update((v) => v + 1);
|
|
146
|
+
if (ctrl.invalid && ctrl.touched) {
|
|
147
|
+
this.hadError.set(true);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
ctrl.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
|
151
|
+
this.errorTracker.update((v) => v + 1);
|
|
152
|
+
});
|
|
153
|
+
this.subscribed.set(true);
|
|
154
|
+
});
|
|
155
|
+
this.transloco.langChanges$
|
|
156
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
157
|
+
.subscribe(() => {
|
|
158
|
+
this.errorTracker.update((v) => v + 1);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
numberValidation(event) {
|
|
162
|
+
const allowedKeys = [
|
|
163
|
+
'Backspace',
|
|
164
|
+
'Tab',
|
|
165
|
+
'End',
|
|
166
|
+
'Home',
|
|
167
|
+
'ArrowLeft',
|
|
168
|
+
'ArrowRight',
|
|
169
|
+
'Delete',
|
|
170
|
+
];
|
|
171
|
+
if (this.numbersOnly()) {
|
|
172
|
+
const isNumber = /^\d$/.test(event.key);
|
|
173
|
+
if (!isNumber && !allowedKeys.includes(event.key)) {
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (this.allowDecimals() || this.type() === 'number') {
|
|
179
|
+
allowedKeys.push('.');
|
|
180
|
+
}
|
|
181
|
+
const isDecimal = event.key === '.' || event.key === ',';
|
|
182
|
+
const isNumber = !isNaN(Number(event.key));
|
|
183
|
+
if (allowedKeys.includes(event.key)) {
|
|
184
|
+
if (isDecimal) {
|
|
185
|
+
const input = event.target;
|
|
186
|
+
const currentValue = input.value;
|
|
187
|
+
const hasDecimalSeparator = currentValue.includes('.') || currentValue.includes(',');
|
|
188
|
+
if (hasDecimalSeparator) {
|
|
189
|
+
event.preventDefault();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (isNumber) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
event.preventDefault();
|
|
198
|
+
}
|
|
199
|
+
onBlur() {
|
|
200
|
+
this.onTouched();
|
|
201
|
+
this.errorTracker.update((v) => v + 1);
|
|
202
|
+
const ctrl = this.actualControl();
|
|
203
|
+
if (ctrl?.invalid && ctrl?.touched) {
|
|
204
|
+
this.hadError.set(true);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
onCardInput(event) {
|
|
208
|
+
const input = event.target;
|
|
209
|
+
let value = input.value.replace(/[^0-9]/g, '');
|
|
210
|
+
if (value.length > 16) {
|
|
211
|
+
value = value.substring(0, 16);
|
|
212
|
+
}
|
|
213
|
+
let formatted = '';
|
|
214
|
+
for (let i = 0; i < value.length; i++) {
|
|
215
|
+
if (i > 0 && i % 4 === 0) {
|
|
216
|
+
formatted += '-';
|
|
217
|
+
}
|
|
218
|
+
formatted += value[i];
|
|
219
|
+
}
|
|
220
|
+
input.value = formatted;
|
|
221
|
+
this.onChange(formatted);
|
|
222
|
+
}
|
|
223
|
+
onInput(event) {
|
|
224
|
+
const input = event.target;
|
|
225
|
+
let newValue = input.value;
|
|
226
|
+
if (this.allowDecimals()) {
|
|
227
|
+
newValue = newValue.replace(/,/g, '.');
|
|
228
|
+
const parts = newValue.split('.');
|
|
229
|
+
if (parts.length > 2) {
|
|
230
|
+
newValue = parts[0] + '.' + parts.slice(1).join('');
|
|
231
|
+
}
|
|
232
|
+
if (parts.length > 1 && parts[1].length > 2) {
|
|
233
|
+
newValue = parts[0] + '.' + parts[1].substring(0, 2);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (this.numbersOnly()) {
|
|
237
|
+
newValue = newValue.replace(/[^0-9]/g, '');
|
|
238
|
+
}
|
|
239
|
+
const maxLen = this.maxLength();
|
|
240
|
+
if (maxLen && newValue.length > maxLen) {
|
|
241
|
+
newValue = newValue.substring(0, maxLen);
|
|
242
|
+
input.value = newValue;
|
|
243
|
+
const ctrl = this.actualControl();
|
|
244
|
+
setTimeout(() => {
|
|
245
|
+
ctrl?.updateValueAndValidity();
|
|
246
|
+
this.errorTracker.update((v) => v + 1);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if (this.formatCard()) {
|
|
250
|
+
this.onCardInput(event);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
this.onChange(newValue);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
togglePasswordVisibility() {
|
|
257
|
+
this.showPassword.update((v) => !v);
|
|
258
|
+
}
|
|
259
|
+
writeValue(value) {
|
|
260
|
+
let processedValue = value;
|
|
261
|
+
if (this.allowDecimals() && value) {
|
|
262
|
+
processedValue = String(value).replace(/,/g, '.');
|
|
263
|
+
}
|
|
264
|
+
if (this.numbersOnly() && value) {
|
|
265
|
+
processedValue = value.toString().replace(/[^0-9]/g, '');
|
|
266
|
+
}
|
|
267
|
+
const maxLen = this.maxLength();
|
|
268
|
+
if (maxLen && processedValue?.length > maxLen) {
|
|
269
|
+
processedValue = processedValue.substring(0, maxLen);
|
|
270
|
+
}
|
|
271
|
+
if (this.formatCard() && processedValue) {
|
|
272
|
+
const cleaned = processedValue.replace(/[^0-9]/g, '');
|
|
273
|
+
let formatted = '';
|
|
274
|
+
for (let i = 0; i < cleaned.length; i++) {
|
|
275
|
+
if (i > 0 && i % 4 === 0)
|
|
276
|
+
formatted += '-';
|
|
277
|
+
formatted += cleaned[i];
|
|
278
|
+
}
|
|
279
|
+
this._value.set(formatted);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
this._value.set(processedValue);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
registerOnChange(fn) {
|
|
286
|
+
this.onChange = fn;
|
|
287
|
+
}
|
|
288
|
+
registerOnTouched(fn) {
|
|
289
|
+
this.onTouched = fn;
|
|
290
|
+
}
|
|
291
|
+
setDisabledState(isDisabled) {
|
|
292
|
+
this._disabledState.set(isDisabled);
|
|
293
|
+
this.cdr.markForCheck();
|
|
294
|
+
}
|
|
295
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TextFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
296
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: TextFieldComponent, isStandalone: true, selector: "shk-text-field", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, formatCard: { classPropertyName: "formatCard", publicName: "formatCard", isSignal: true, isRequired: false, transformFunction: null }, preventNegative: { classPropertyName: "preventNegative", publicName: "preventNegative", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, numbersOnly: { classPropertyName: "numbersOnly", publicName: "numbersOnly", isSignal: true, isRequired: false, transformFunction: null }, allowDecimals: { classPropertyName: "allowDecimals", publicName: "allowDecimals", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, errorMessages: { classPropertyName: "errorMessages", publicName: "errorMessages", isSignal: true, isRequired: false, transformFunction: null }, formGroup: { classPropertyName: "formGroup", publicName: "formGroup", isSignal: true, isRequired: false, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, isTextArea: { classPropertyName: "isTextArea", publicName: "isTextArea", isSignal: true, isRequired: false, transformFunction: null }, textAreaHeight: { classPropertyName: "textAreaHeight", publicName: "textAreaHeight", isSignal: true, isRequired: false, transformFunction: null }, searchMode: { classPropertyName: "searchMode", publicName: "searchMode", isSignal: true, isRequired: false, transformFunction: null }, displayValue: { classPropertyName: "displayValue", publicName: "displayValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { searchButtonClick: "searchButtonClick" }, providers: [
|
|
297
|
+
{
|
|
298
|
+
provide: NG_VALUE_ACCESSOR,
|
|
299
|
+
useExisting: forwardRef(() => TextFieldComponent),
|
|
300
|
+
multi: true,
|
|
301
|
+
},
|
|
302
|
+
], viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["input"], descendants: true, isSignal: true }, { propertyName: "textareaElement", first: true, predicate: ["textarea"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"relative mb-4\">\n @if (label()) {\n <div class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-300)] mb-1\">\n {{ label() }}\n </div>\n }\n\n <div class=\"relative\">\n @if (!isTextArea()) {\n <div class=\"flex\">\n @if (searchMode()) {\n @let isDisabledSearch = actualControl()?.disabled || isDisabled();\n <input\n #input\n [id]=\"id()\"\n type=\"text\"\n [value]=\"displayValue() || ''\"\n [placeholder]=\"placeholder() || label()\"\n [readOnly]=\"true\"\n [disabled]=\"isDisabledSearch\"\n (click)=\"!isDisabledSearch && searchButtonClick.emit()\"\n (keydown.enter)=\"!isDisabledSearch && searchButtonClick.emit()\"\n (blur)=\"onBlur()\"\n autocomplete=\"off\"\n [class.cursor-pointer]=\"!isDisabledSearch\"\n [class.cursor-not-allowed]=\"isDisabledSearch\"\n [class._shk-bg-surface-50]=\"isDisabledSearch\"\n [class.dark:[background-color:var(--shk-surface-800)]]=\"isDisabledSearch\"\n [class._shk-text-surface-400]=\"isDisabledSearch\"\n [class.dark:[color:var(--shk-surface-500)]]=\"isDisabledSearch\"\n class=\"block w-full px-3 py-2 _shk-text-surface-700 dark:[color:var(--shk-surface-200)] _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border rounded-l-lg transition-colors placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] focus:[background-color:var(--shk-surface-50)] dark:focus:[background-color:var(--shk-surface-800)] sm:text-sm\"\n [ngClass]=\"{\n 'border-red-500': shouldShowError() && !isDisabledSearch,\n '_shk-border dark:[border-color:var(--shk-surface-700)]': !shouldShowError() || isDisabledSearch,\n 'cursor-pointer': !isDisabledSearch,\n 'cursor-not-allowed _shk-text-surface-400 dark:[color:var(--shk-surface-500)]': isDisabledSearch\n }\"\n />\n <button\n type=\"button\"\n (click)=\"searchButtonClick.emit()\"\n tabindex=\"-1\"\n [disabled]=\"isDisabledSearch\"\n class=\"px-3 flex items-center justify-center shrink-0 _shk-bg-primary hover:[background-color:var(--shk-primary-700)] text-white border _shk-border-primary rounded-r-lg transition-colors duration-200 disabled:[background-color:var(--shk-surface-300)] dark:disabled:[background-color:var(--shk-surface-700)] disabled:[border-color:var(--shk-surface-300)] dark:disabled:[border-color:var(--shk-surface-700)] disabled:cursor-not-allowed\"\n >\n <svg [lucideIcon]=\"getIcon('search')\" class=\"w-4 h-4\"></svg>\n </button>\n }\n\n @if (!searchMode()) {\n <input\n #input\n [id]=\"id()\"\n [maxlength]=\"maxLength() || (formatCard() ? 19 : null)\"\n [min]=\"type() === 'number' && preventNegative() ? '0' : null\"\n (keypress)=\"type() === 'number' || numbersOnly() ? numberValidation($event) : null\"\n [type]=\"effectiveType()\"\n [formControl]=\"$any(actualControl())\"\n [placeholder]=\"placeholder() || label()\"\n (input)=\"onInput($event)\"\n autocomplete=\"new-password\"\n (blur)=\"onBlur()\"\n class=\"block w-full px-3 py-2 _shk-text-surface-700 dark:[color:var(--shk-surface-200)] _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border rounded-lg transition-colors placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] focus:outline-none focus:[background-color:var(--shk-surface-50)] dark:focus:[background-color:var(--shk-surface-800)] focus:ring-1 disabled:[background-color:var(--shk-surface-50)] dark:disabled:[background-color:var(--shk-surface-800)] disabled:cursor-not-allowed sm:text-sm\"\n [ngClass]=\"{\n 'border-red-500 focus:border-red-500 focus:ring-red-500': shouldShowError(),\n 'border-green-500 focus:border-green-500 focus:ring-green-500': wasFixedError(),\n '_shk-border dark:[border-color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:[--tw-ring-color:var(--shk-primary-500)]': !shouldShowError() && !wasFixedError()\n }\"\n [class.pr-8]=\"type() === 'password' && actualControl()?.value?.length > 0\"\n [class.pr-3]=\"type() !== 'password' || !actualControl()?.value?.length\"\n />\n\n @if (type() === 'password' && actualControl()?.value?.length > 0) {\n <button\n type=\"button\"\n (click)=\"togglePasswordVisibility()\"\n [disabled]=\"actualControl()?.disabled\"\n class=\"absolute right-3 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] focus:outline-none\"\n >\n @if (showPassword()) {\n <svg [lucideIcon]=\"getIcon('eye-off')\" class=\"w-4 h-4\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('eye')\" class=\"w-4 h-4\"></svg>\n }\n </button>\n }\n }\n </div>\n }\n\n @if (isTextArea()) {\n <textarea\n #textarea\n [id]=\"id()\"\n [formControl]=\"$any(actualControl())\"\n [placeholder]=\"placeholder() || label()\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n [class]=\"isTextArea() ? textAreaHeight() : 'h-28'\"\n class=\"block w-full px-3 py-2 _shk-text-surface-700 dark:[color:var(--shk-surface-200)] _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border rounded-lg transition-colors placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] focus:outline-none focus:[background-color:var(--shk-surface-50)] dark:focus:[background-color:var(--shk-surface-800)] focus:ring-1 resize-none disabled:[background-color:var(--shk-surface-50)] dark:disabled:[background-color:var(--shk-surface-800)] disabled:cursor-not-allowed disabled:[color:var(--shk-surface-400)] dark:disabled:[color:var(--shk-surface-500)] sm:text-sm\"\n [ngClass]=\"{\n 'border-red-500 focus:border-red-500 focus:ring-red-500': shouldShowError(),\n 'border-green-500 focus:border-green-500 focus:ring-green-500': wasFixedError(),\n '_shk-border dark:[border-color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:[--tw-ring-color:var(--shk-primary-500)]': !shouldShowError() && !wasFixedError()\n }\"\n ></textarea>\n }\n </div>\n\n @if (shouldShowError()) {\n <div class=\"mt-1 text-xs text-red-500 max-h-20 overflow-y-auto\" tabindex=\"-1\">\n <ul class=\"list-disc pl-4 space-y-1\">\n @for (error of getErrorMessages(); track error) {\n <li>{{ error }}</li>\n }\n </ul>\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
303
|
+
}
|
|
304
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TextFieldComponent, decorators: [{
|
|
305
|
+
type: Component,
|
|
306
|
+
args: [{ selector: 'shk-text-field', standalone: true, imports: [CommonModule, ReactiveFormsModule, LucideDynamicIcon], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
307
|
+
{
|
|
308
|
+
provide: NG_VALUE_ACCESSOR,
|
|
309
|
+
useExisting: forwardRef(() => TextFieldComponent),
|
|
310
|
+
multi: true,
|
|
311
|
+
},
|
|
312
|
+
], template: "<div class=\"relative mb-4\">\n @if (label()) {\n <div class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-300)] mb-1\">\n {{ label() }}\n </div>\n }\n\n <div class=\"relative\">\n @if (!isTextArea()) {\n <div class=\"flex\">\n @if (searchMode()) {\n @let isDisabledSearch = actualControl()?.disabled || isDisabled();\n <input\n #input\n [id]=\"id()\"\n type=\"text\"\n [value]=\"displayValue() || ''\"\n [placeholder]=\"placeholder() || label()\"\n [readOnly]=\"true\"\n [disabled]=\"isDisabledSearch\"\n (click)=\"!isDisabledSearch && searchButtonClick.emit()\"\n (keydown.enter)=\"!isDisabledSearch && searchButtonClick.emit()\"\n (blur)=\"onBlur()\"\n autocomplete=\"off\"\n [class.cursor-pointer]=\"!isDisabledSearch\"\n [class.cursor-not-allowed]=\"isDisabledSearch\"\n [class._shk-bg-surface-50]=\"isDisabledSearch\"\n [class.dark:[background-color:var(--shk-surface-800)]]=\"isDisabledSearch\"\n [class._shk-text-surface-400]=\"isDisabledSearch\"\n [class.dark:[color:var(--shk-surface-500)]]=\"isDisabledSearch\"\n class=\"block w-full px-3 py-2 _shk-text-surface-700 dark:[color:var(--shk-surface-200)] _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border rounded-l-lg transition-colors placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] focus:[background-color:var(--shk-surface-50)] dark:focus:[background-color:var(--shk-surface-800)] sm:text-sm\"\n [ngClass]=\"{\n 'border-red-500': shouldShowError() && !isDisabledSearch,\n '_shk-border dark:[border-color:var(--shk-surface-700)]': !shouldShowError() || isDisabledSearch,\n 'cursor-pointer': !isDisabledSearch,\n 'cursor-not-allowed _shk-text-surface-400 dark:[color:var(--shk-surface-500)]': isDisabledSearch\n }\"\n />\n <button\n type=\"button\"\n (click)=\"searchButtonClick.emit()\"\n tabindex=\"-1\"\n [disabled]=\"isDisabledSearch\"\n class=\"px-3 flex items-center justify-center shrink-0 _shk-bg-primary hover:[background-color:var(--shk-primary-700)] text-white border _shk-border-primary rounded-r-lg transition-colors duration-200 disabled:[background-color:var(--shk-surface-300)] dark:disabled:[background-color:var(--shk-surface-700)] disabled:[border-color:var(--shk-surface-300)] dark:disabled:[border-color:var(--shk-surface-700)] disabled:cursor-not-allowed\"\n >\n <svg [lucideIcon]=\"getIcon('search')\" class=\"w-4 h-4\"></svg>\n </button>\n }\n\n @if (!searchMode()) {\n <input\n #input\n [id]=\"id()\"\n [maxlength]=\"maxLength() || (formatCard() ? 19 : null)\"\n [min]=\"type() === 'number' && preventNegative() ? '0' : null\"\n (keypress)=\"type() === 'number' || numbersOnly() ? numberValidation($event) : null\"\n [type]=\"effectiveType()\"\n [formControl]=\"$any(actualControl())\"\n [placeholder]=\"placeholder() || label()\"\n (input)=\"onInput($event)\"\n autocomplete=\"new-password\"\n (blur)=\"onBlur()\"\n class=\"block w-full px-3 py-2 _shk-text-surface-700 dark:[color:var(--shk-surface-200)] _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border rounded-lg transition-colors placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] focus:outline-none focus:[background-color:var(--shk-surface-50)] dark:focus:[background-color:var(--shk-surface-800)] focus:ring-1 disabled:[background-color:var(--shk-surface-50)] dark:disabled:[background-color:var(--shk-surface-800)] disabled:cursor-not-allowed sm:text-sm\"\n [ngClass]=\"{\n 'border-red-500 focus:border-red-500 focus:ring-red-500': shouldShowError(),\n 'border-green-500 focus:border-green-500 focus:ring-green-500': wasFixedError(),\n '_shk-border dark:[border-color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:[--tw-ring-color:var(--shk-primary-500)]': !shouldShowError() && !wasFixedError()\n }\"\n [class.pr-8]=\"type() === 'password' && actualControl()?.value?.length > 0\"\n [class.pr-3]=\"type() !== 'password' || !actualControl()?.value?.length\"\n />\n\n @if (type() === 'password' && actualControl()?.value?.length > 0) {\n <button\n type=\"button\"\n (click)=\"togglePasswordVisibility()\"\n [disabled]=\"actualControl()?.disabled\"\n class=\"absolute right-3 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] focus:outline-none\"\n >\n @if (showPassword()) {\n <svg [lucideIcon]=\"getIcon('eye-off')\" class=\"w-4 h-4\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('eye')\" class=\"w-4 h-4\"></svg>\n }\n </button>\n }\n }\n </div>\n }\n\n @if (isTextArea()) {\n <textarea\n #textarea\n [id]=\"id()\"\n [formControl]=\"$any(actualControl())\"\n [placeholder]=\"placeholder() || label()\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n [class]=\"isTextArea() ? textAreaHeight() : 'h-28'\"\n class=\"block w-full px-3 py-2 _shk-text-surface-700 dark:[color:var(--shk-surface-200)] _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border rounded-lg transition-colors placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] focus:outline-none focus:[background-color:var(--shk-surface-50)] dark:focus:[background-color:var(--shk-surface-800)] focus:ring-1 resize-none disabled:[background-color:var(--shk-surface-50)] dark:disabled:[background-color:var(--shk-surface-800)] disabled:cursor-not-allowed disabled:[color:var(--shk-surface-400)] dark:disabled:[color:var(--shk-surface-500)] sm:text-sm\"\n [ngClass]=\"{\n 'border-red-500 focus:border-red-500 focus:ring-red-500': shouldShowError(),\n 'border-green-500 focus:border-green-500 focus:ring-green-500': wasFixedError(),\n '_shk-border dark:[border-color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:[--tw-ring-color:var(--shk-primary-500)]': !shouldShowError() && !wasFixedError()\n }\"\n ></textarea>\n }\n </div>\n\n @if (shouldShowError()) {\n <div class=\"mt-1 text-xs text-red-500 max-h-20 overflow-y-auto\" tabindex=\"-1\">\n <ul class=\"list-disc pl-4 space-y-1\">\n @for (error of getErrorMessages(); track error) {\n <li>{{ error }}</li>\n }\n </ul>\n </div>\n }\n</div>\n" }]
|
|
313
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], formatCard: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatCard", required: false }] }], preventNegative: [{ type: i0.Input, args: [{ isSignal: true, alias: "preventNegative", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], numbersOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "numbersOnly", required: false }] }], allowDecimals: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDecimals", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], errorMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessages", required: false }] }], formGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "formGroup", required: false }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], isTextArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "isTextArea", required: false }] }], textAreaHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "textAreaHeight", required: false }] }], searchMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchMode", required: false }] }], displayValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayValue", required: false }] }], searchButtonClick: [{ type: i0.Output, args: ["searchButtonClick"] }], inputElement: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], textareaElement: [{ type: i0.ViewChild, args: ['textarea', { isSignal: true }] }] } });
|
|
314
|
+
|
|
315
|
+
class NotificationService {
|
|
316
|
+
notificationsSignal = signal([], ...(ngDevMode ? [{ debugName: "notificationsSignal" }] : /* istanbul ignore next */ []));
|
|
317
|
+
currentId = 0;
|
|
318
|
+
notifications = this.notificationsSignal.asReadonly();
|
|
319
|
+
addNotification(message, type = 'info', showProgress = false, duration = 5000) {
|
|
320
|
+
const id = (++this.currentId).toString();
|
|
321
|
+
const notification = {
|
|
322
|
+
id,
|
|
323
|
+
message,
|
|
324
|
+
type,
|
|
325
|
+
progress: showProgress ? 0 : undefined,
|
|
326
|
+
showProgress,
|
|
327
|
+
};
|
|
328
|
+
this.notificationsSignal.update((notifications) => [
|
|
329
|
+
...notifications,
|
|
330
|
+
notification,
|
|
331
|
+
]);
|
|
332
|
+
if (duration > 0 && !showProgress) {
|
|
333
|
+
setTimeout(() => this.removeNotificationById(id), duration);
|
|
334
|
+
}
|
|
335
|
+
return id;
|
|
336
|
+
}
|
|
337
|
+
updateProgress(notificationId, progress) {
|
|
338
|
+
const currentNotifications = this.notificationsSignal();
|
|
339
|
+
const index = currentNotifications.findIndex((n) => n.id === notificationId);
|
|
340
|
+
if (index > -1 && currentNotifications[index].showProgress) {
|
|
341
|
+
const updatedProgress = Math.min(100, Math.max(0, progress));
|
|
342
|
+
this.notificationsSignal.update((notifications) => notifications.map((n, i) => i === index ? { ...n, progress: updatedProgress } : n));
|
|
343
|
+
if (progress >= 100) {
|
|
344
|
+
setTimeout(() => this.removeNotificationById(notificationId), 2000);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
removeNotification(notification) {
|
|
349
|
+
this.removeNotificationById(notification.id);
|
|
350
|
+
}
|
|
351
|
+
removeNotificationById(notificationId) {
|
|
352
|
+
this.notificationsSignal.update((notifications) => notifications.filter((n) => n.id !== notificationId));
|
|
353
|
+
}
|
|
354
|
+
clearAll() {
|
|
355
|
+
this.notificationsSignal.set([]);
|
|
356
|
+
}
|
|
357
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
358
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: NotificationService, providedIn: 'root' });
|
|
359
|
+
}
|
|
360
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: NotificationService, decorators: [{
|
|
361
|
+
type: Injectable,
|
|
362
|
+
args: [{
|
|
363
|
+
providedIn: 'root',
|
|
364
|
+
}]
|
|
365
|
+
}] });
|
|
366
|
+
|
|
367
|
+
const icons$5 = {
|
|
368
|
+
check: LucideCheck,
|
|
369
|
+
'circle-x': LucideCircleX,
|
|
370
|
+
info: LucideInfo,
|
|
371
|
+
'triangle-alert': LucideTriangleAlert,
|
|
372
|
+
x: LucideX,
|
|
373
|
+
};
|
|
374
|
+
class NotificationComponent {
|
|
375
|
+
position = input('center-top', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
|
|
376
|
+
notificationService = inject(NotificationService);
|
|
377
|
+
getIcon = (name) => icons$5[name] ?? LucideInfo;
|
|
378
|
+
iconMap = {
|
|
379
|
+
success: 'check',
|
|
380
|
+
error: 'circle-x',
|
|
381
|
+
info: 'info',
|
|
382
|
+
warning: 'triangle-alert',
|
|
383
|
+
};
|
|
384
|
+
getNotificationIcon(type) {
|
|
385
|
+
return this.iconMap[type] || 'info';
|
|
386
|
+
}
|
|
387
|
+
notifications = this.notificationService.notifications;
|
|
388
|
+
sortedNotifications = computed(() => [...this.notifications()].reverse(), ...(ngDevMode ? [{ debugName: "sortedNotifications" }] : /* istanbul ignore next */ []));
|
|
389
|
+
removeNotification(notification) {
|
|
390
|
+
this.notificationService.removeNotification(notification);
|
|
391
|
+
}
|
|
392
|
+
roundProgress(progress) {
|
|
393
|
+
return Math.round(progress || 0);
|
|
394
|
+
}
|
|
395
|
+
hasNotifications = computed(() => this.notifications().length > 0, ...(ngDevMode ? [{ debugName: "hasNotifications" }] : /* istanbul ignore next */ []));
|
|
396
|
+
positionClass = computed(() => {
|
|
397
|
+
const base = 'fixed top-4 z-[9999] space-y-4 sm:space-y-6 sm:top-6 lg:top-8';
|
|
398
|
+
switch (this.position()) {
|
|
399
|
+
case 'right-top':
|
|
400
|
+
return `${base} right-4`;
|
|
401
|
+
case 'left-top':
|
|
402
|
+
return `${base} left-4`;
|
|
403
|
+
default:
|
|
404
|
+
return `${base} left-1/2 -translate-x-1/2`;
|
|
405
|
+
}
|
|
406
|
+
}, ...(ngDevMode ? [{ debugName: "positionClass" }] : /* istanbul ignore next */ []));
|
|
407
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: NotificationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
408
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: NotificationComponent, isStandalone: true, selector: "shk-notification", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div [class]=\"positionClass()\">\n @for (notification of sortedNotifications(); track notification.id) {\n <div\n [ngClass]=\"{\n '_shk-bg-green-soft _shk-dark-bg-green-soft _shk-border-green-400 dark:[border-color:var(--shk-success-600)] _shk-text-green-800 dark:[color:var(--shk-success-800)]':\n notification.type === 'success',\n '_shk-bg-red-soft _shk-dark-bg-red-soft _shk-border-red-400 dark:[border-color:var(--shk-error-600)] _shk-text-red-800 dark:[color:var(--shk-error-800)]':\n notification.type === 'error',\n '_shk-bg-blue-soft _shk-dark-bg-blue-soft _shk-border-blue-400 dark:[border-color:var(--shk-info-600)] _shk-text-blue-800 dark:[color:var(--shk-info-800)]':\n notification.type === 'info',\n '_shk-bg-yellow-soft _shk-dark-bg-yellow-soft _shk-border-yellow-400 dark:[border-color:var(--shk-warning-600)] _shk-text-yellow-800 dark:[color:var(--shk-warning-800)]':\n notification.type === 'warning',\n }\"\n class=\"flex items-center w-full min-w-[320px] max-w-xs sm:max-w-sm lg:max-w-md p-4 mb-4 border-l-4 rounded-lg shadow-lg backdrop-blur-sm transition-all duration-300 transform hover:scale-105 hover:shadow-xl\"\n role=\"alert\"\n >\n <div\n [ngClass]=\"{\n '_shk-text-green-600': notification.type === 'success',\n '_shk-text-red-600': notification.type === 'error',\n '_shk-text-blue-600': notification.type === 'info',\n '_shk-text-yellow-600': notification.type === 'warning',\n }\"\n class=\"inline-flex items-center justify-center flex-shrink-0 w-10 h-10 rounded-lg bg-opacity-10\"\n >\n <svg [lucideIcon]=\"getIcon(getNotificationIcon(notification.type))\" class=\"w-6 h-6\"></svg>\n </div>\n <div class=\"ms-4 flex-1 text-sm font-medium break-words\">\n {{ notification.message }}\n </div>\n <button\n type=\"button\"\n (click)=\"removeNotification(notification)\"\n aria-label=\"Close\"\n [ngClass]=\"{\n '_shk-text-green-600 hover:[color:var(--shk-green-800)]':\n notification.type === 'success',\n '_shk-text-red-600 hover:[color:var(--shk-error-800)]': notification.type === 'error',\n '_shk-text-blue-600 hover:[color:var(--shk-info-800)]': notification.type === 'info',\n '_shk-text-yellow-600 hover:[color:var(--shk-warning-800)]':\n notification.type === 'warning',\n }\"\n class=\"mx-2 -my-1.5 bg-transparent focus:outline-none\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
409
|
+
}
|
|
410
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: NotificationComponent, decorators: [{
|
|
411
|
+
type: Component,
|
|
412
|
+
args: [{ selector: 'shk-notification', standalone: true, imports: [CommonModule, LucideDynamicIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [class]=\"positionClass()\">\n @for (notification of sortedNotifications(); track notification.id) {\n <div\n [ngClass]=\"{\n '_shk-bg-green-soft _shk-dark-bg-green-soft _shk-border-green-400 dark:[border-color:var(--shk-success-600)] _shk-text-green-800 dark:[color:var(--shk-success-800)]':\n notification.type === 'success',\n '_shk-bg-red-soft _shk-dark-bg-red-soft _shk-border-red-400 dark:[border-color:var(--shk-error-600)] _shk-text-red-800 dark:[color:var(--shk-error-800)]':\n notification.type === 'error',\n '_shk-bg-blue-soft _shk-dark-bg-blue-soft _shk-border-blue-400 dark:[border-color:var(--shk-info-600)] _shk-text-blue-800 dark:[color:var(--shk-info-800)]':\n notification.type === 'info',\n '_shk-bg-yellow-soft _shk-dark-bg-yellow-soft _shk-border-yellow-400 dark:[border-color:var(--shk-warning-600)] _shk-text-yellow-800 dark:[color:var(--shk-warning-800)]':\n notification.type === 'warning',\n }\"\n class=\"flex items-center w-full min-w-[320px] max-w-xs sm:max-w-sm lg:max-w-md p-4 mb-4 border-l-4 rounded-lg shadow-lg backdrop-blur-sm transition-all duration-300 transform hover:scale-105 hover:shadow-xl\"\n role=\"alert\"\n >\n <div\n [ngClass]=\"{\n '_shk-text-green-600': notification.type === 'success',\n '_shk-text-red-600': notification.type === 'error',\n '_shk-text-blue-600': notification.type === 'info',\n '_shk-text-yellow-600': notification.type === 'warning',\n }\"\n class=\"inline-flex items-center justify-center flex-shrink-0 w-10 h-10 rounded-lg bg-opacity-10\"\n >\n <svg [lucideIcon]=\"getIcon(getNotificationIcon(notification.type))\" class=\"w-6 h-6\"></svg>\n </div>\n <div class=\"ms-4 flex-1 text-sm font-medium break-words\">\n {{ notification.message }}\n </div>\n <button\n type=\"button\"\n (click)=\"removeNotification(notification)\"\n aria-label=\"Close\"\n [ngClass]=\"{\n '_shk-text-green-600 hover:[color:var(--shk-green-800)]':\n notification.type === 'success',\n '_shk-text-red-600 hover:[color:var(--shk-error-800)]': notification.type === 'error',\n '_shk-text-blue-600 hover:[color:var(--shk-info-800)]': notification.type === 'info',\n '_shk-text-yellow-600 hover:[color:var(--shk-warning-800)]':\n notification.type === 'warning',\n }\"\n class=\"mx-2 -my-1.5 bg-transparent focus:outline-none\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n }\n</div>\n" }]
|
|
413
|
+
}], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }] } });
|
|
414
|
+
|
|
415
|
+
class ToggleComponent {
|
|
416
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
417
|
+
mode = input('toggle', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
418
|
+
checked = input(undefined, ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
|
|
419
|
+
checkedChange = output();
|
|
420
|
+
_value = signal(false, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
|
|
421
|
+
_disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
|
|
422
|
+
onChange = () => { };
|
|
423
|
+
onTouched = () => { };
|
|
424
|
+
constructor() {
|
|
425
|
+
effect(() => {
|
|
426
|
+
const manual = this.checked();
|
|
427
|
+
if (manual !== undefined) {
|
|
428
|
+
this._value.set(manual);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
writeValue(value) {
|
|
433
|
+
this._value.set(!!value);
|
|
434
|
+
}
|
|
435
|
+
registerOnChange(fn) {
|
|
436
|
+
this.onChange = fn;
|
|
437
|
+
}
|
|
438
|
+
registerOnTouched(fn) {
|
|
439
|
+
this.onTouched = fn;
|
|
440
|
+
}
|
|
441
|
+
setDisabledState(isDisabled) {
|
|
442
|
+
this._disabled.set(isDisabled);
|
|
443
|
+
}
|
|
444
|
+
toggle() {
|
|
445
|
+
if (this._disabled())
|
|
446
|
+
return;
|
|
447
|
+
const newValue = !this._value();
|
|
448
|
+
this._value.set(newValue);
|
|
449
|
+
this.onChange(newValue);
|
|
450
|
+
this.onTouched();
|
|
451
|
+
this.checkedChange.emit(newValue);
|
|
452
|
+
}
|
|
453
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ToggleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
454
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: ToggleComponent, isStandalone: true, selector: "shk-toggle", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checkedChange: "checkedChange" }, providers: [
|
|
455
|
+
{
|
|
456
|
+
provide: NG_VALUE_ACCESSOR,
|
|
457
|
+
useExisting: forwardRef(() => ToggleComponent),
|
|
458
|
+
multi: true,
|
|
459
|
+
},
|
|
460
|
+
], ngImport: i0, template: `
|
|
461
|
+
<div class="relative">
|
|
462
|
+
<label
|
|
463
|
+
class="inline-flex items-center gap-3 cursor-pointer select-none"
|
|
464
|
+
[class.opacity-50]="_disabled()"
|
|
465
|
+
[class.cursor-not-allowed]="_disabled()"
|
|
466
|
+
>
|
|
467
|
+
@if (mode() === 'toggle') {
|
|
468
|
+
<div class="relative flex-shrink-0" (click)="toggle()">
|
|
469
|
+
<div
|
|
470
|
+
class="w-10 h-6 rounded-full transition-colors duration-200"
|
|
471
|
+
[class._shk-bg-primary]="_value()"
|
|
472
|
+
[class._shk-bg-surface-300]="!_value()"
|
|
473
|
+
[class._shk-dark-bg-surface-600]="!_value()"
|
|
474
|
+
></div>
|
|
475
|
+
<div
|
|
476
|
+
class="absolute top-0.5 left-0.5 w-5 h-5 _shk-bg-white rounded-full shadow transition-transform duration-200"
|
|
477
|
+
[class.translate-x-4]="_value()"
|
|
478
|
+
></div>
|
|
479
|
+
</div>
|
|
480
|
+
} @else {
|
|
481
|
+
<div
|
|
482
|
+
class="flex-shrink-0 w-4 h-4 rounded border-2 transition-colors duration-200 flex items-center justify-center"
|
|
483
|
+
[class._shk-bg-primary]="_value()"
|
|
484
|
+
[class._shk-border-primary]="_value()"
|
|
485
|
+
[class._shk-border-surface-300]="!_value()"
|
|
486
|
+
[class._shk-dark-border-surface-600]="!_value()"
|
|
487
|
+
(click)="toggle()"
|
|
488
|
+
>
|
|
489
|
+
@if (_value()) {
|
|
490
|
+
<svg class="w-3 h-3 text-white" viewBox="0 0 12 12" fill="none">
|
|
491
|
+
<path d="M2 6l3 3 5-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
492
|
+
</svg>
|
|
493
|
+
}
|
|
494
|
+
</div>
|
|
495
|
+
}
|
|
496
|
+
@if (label()) {
|
|
497
|
+
<span class="text-sm _shk-text-surface-700 _shk-dark-text-surface-300">
|
|
498
|
+
{{ label() }}
|
|
499
|
+
</span>
|
|
500
|
+
}
|
|
501
|
+
</label>
|
|
502
|
+
</div>
|
|
503
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
504
|
+
}
|
|
505
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ToggleComponent, decorators: [{
|
|
506
|
+
type: Component,
|
|
507
|
+
args: [{
|
|
508
|
+
selector: 'shk-toggle',
|
|
509
|
+
standalone: true,
|
|
510
|
+
imports: [CommonModule],
|
|
511
|
+
template: `
|
|
512
|
+
<div class="relative">
|
|
513
|
+
<label
|
|
514
|
+
class="inline-flex items-center gap-3 cursor-pointer select-none"
|
|
515
|
+
[class.opacity-50]="_disabled()"
|
|
516
|
+
[class.cursor-not-allowed]="_disabled()"
|
|
517
|
+
>
|
|
518
|
+
@if (mode() === 'toggle') {
|
|
519
|
+
<div class="relative flex-shrink-0" (click)="toggle()">
|
|
520
|
+
<div
|
|
521
|
+
class="w-10 h-6 rounded-full transition-colors duration-200"
|
|
522
|
+
[class._shk-bg-primary]="_value()"
|
|
523
|
+
[class._shk-bg-surface-300]="!_value()"
|
|
524
|
+
[class._shk-dark-bg-surface-600]="!_value()"
|
|
525
|
+
></div>
|
|
526
|
+
<div
|
|
527
|
+
class="absolute top-0.5 left-0.5 w-5 h-5 _shk-bg-white rounded-full shadow transition-transform duration-200"
|
|
528
|
+
[class.translate-x-4]="_value()"
|
|
529
|
+
></div>
|
|
530
|
+
</div>
|
|
531
|
+
} @else {
|
|
532
|
+
<div
|
|
533
|
+
class="flex-shrink-0 w-4 h-4 rounded border-2 transition-colors duration-200 flex items-center justify-center"
|
|
534
|
+
[class._shk-bg-primary]="_value()"
|
|
535
|
+
[class._shk-border-primary]="_value()"
|
|
536
|
+
[class._shk-border-surface-300]="!_value()"
|
|
537
|
+
[class._shk-dark-border-surface-600]="!_value()"
|
|
538
|
+
(click)="toggle()"
|
|
539
|
+
>
|
|
540
|
+
@if (_value()) {
|
|
541
|
+
<svg class="w-3 h-3 text-white" viewBox="0 0 12 12" fill="none">
|
|
542
|
+
<path d="M2 6l3 3 5-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
543
|
+
</svg>
|
|
544
|
+
}
|
|
545
|
+
</div>
|
|
546
|
+
}
|
|
547
|
+
@if (label()) {
|
|
548
|
+
<span class="text-sm _shk-text-surface-700 _shk-dark-text-surface-300">
|
|
549
|
+
{{ label() }}
|
|
550
|
+
</span>
|
|
551
|
+
}
|
|
552
|
+
</label>
|
|
553
|
+
</div>
|
|
554
|
+
`,
|
|
555
|
+
providers: [
|
|
556
|
+
{
|
|
557
|
+
provide: NG_VALUE_ACCESSOR,
|
|
558
|
+
useExisting: forwardRef(() => ToggleComponent),
|
|
559
|
+
multi: true,
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
}]
|
|
563
|
+
}], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }], checkedChange: [{ type: i0.Output, args: ["checkedChange"] }] } });
|
|
564
|
+
|
|
565
|
+
class PriceInputComponent {
|
|
566
|
+
value = model.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
567
|
+
isPercentage = model(false, ...(ngDevMode ? [{ debugName: "isPercentage" }] : /* istanbul ignore next */ []));
|
|
568
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
569
|
+
inputId = input('', ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
|
|
570
|
+
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
571
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
572
|
+
min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
573
|
+
max = input(Infinity, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
574
|
+
showError = input(false, ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
|
|
575
|
+
errorMessage = input('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
576
|
+
toggleMode() {
|
|
577
|
+
if (this.disabled())
|
|
578
|
+
return;
|
|
579
|
+
this.isPercentage.update(v => !v);
|
|
580
|
+
}
|
|
581
|
+
onInput(event) {
|
|
582
|
+
const input = event.target;
|
|
583
|
+
this.value.set(parseFloat(input.value) || 0);
|
|
584
|
+
}
|
|
585
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: PriceInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
586
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: PriceInputComponent, isStandalone: true, selector: "shk-price-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, isPercentage: { classPropertyName: "isPercentage", publicName: "isPercentage", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", 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 }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, showError: { classPropertyName: "showError", publicName: "showError", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", isPercentage: "isPercentageChange" }, ngImport: i0, template: "<div>\n @if (label()) {\n <div class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-300)] mb-1\">\n {{ label() }}\n @if (required()) {\n <span class=\"text-red-400\">*</span>\n }\n </div>\n }\n <div class=\"flex\">\n <input\n [id]=\"inputId()\"\n type=\"number\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"value.set($event ?? 0)\"\n (input)=\"onInput($event)\"\n [disabled]=\"disabled()\"\n [min]=\"min()\"\n [max]=\"max()\"\n step=\"0.01\"\n class=\"block w-full px-3 py-2 _shk-text-surface-700 dark:[color:var(--shk-surface-200)] _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border rounded-l-lg transition-colors placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] focus:outline-none focus:[background-color:var(--shk-surface-50)] dark:focus:[background-color:var(--shk-surface-800)] focus:ring-1 disabled:[background-color:var(--shk-surface-50)] dark:disabled:[background-color:var(--shk-surface-800)] disabled:cursor-not-allowed sm:text-sm\"\n [ngClass]=\"{\n 'border-red-500 focus:border-red-500 focus:ring-red-500': showError(),\n '_shk-border dark:[border-color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:[--tw-ring-color:var(--shk-primary-500)]': !showError()\n }\"\n />\n <button\n type=\"button\"\n (click)=\"toggleMode()\"\n [disabled]=\"disabled()\"\n class=\"px-3 py-2 text-sm font-medium border rounded-r-lg transition-colors shrink-0\"\n [ngClass]=\"{\n '_shk-bg-primary-soft _shk-border-primary-200 _shk-text-primary-700': isPercentage(),\n '_shk-bg-white _shk-border _shk-text-surface-500 dark:[background-color:var(--shk-surface-800)] dark:[border-color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)]': !isPercentage(),\n 'cursor-not-allowed opacity-50': disabled()\n }\"\n >\n {{ isPercentage() ? '%' : '$' }}\n </button>\n </div>\n @if (showError()) {\n <div class=\"text-xs _shk-text-red-500 mt-1\">\n {{ errorMessage() }}\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
587
|
+
}
|
|
588
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: PriceInputComponent, decorators: [{
|
|
589
|
+
type: Component,
|
|
590
|
+
args: [{ selector: 'shk-price-input', standalone: true, imports: [CommonModule, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div>\n @if (label()) {\n <div class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-300)] mb-1\">\n {{ label() }}\n @if (required()) {\n <span class=\"text-red-400\">*</span>\n }\n </div>\n }\n <div class=\"flex\">\n <input\n [id]=\"inputId()\"\n type=\"number\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"value.set($event ?? 0)\"\n (input)=\"onInput($event)\"\n [disabled]=\"disabled()\"\n [min]=\"min()\"\n [max]=\"max()\"\n step=\"0.01\"\n class=\"block w-full px-3 py-2 _shk-text-surface-700 dark:[color:var(--shk-surface-200)] _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border rounded-l-lg transition-colors placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)] focus:outline-none focus:[background-color:var(--shk-surface-50)] dark:focus:[background-color:var(--shk-surface-800)] focus:ring-1 disabled:[background-color:var(--shk-surface-50)] dark:disabled:[background-color:var(--shk-surface-800)] disabled:cursor-not-allowed sm:text-sm\"\n [ngClass]=\"{\n 'border-red-500 focus:border-red-500 focus:ring-red-500': showError(),\n '_shk-border dark:[border-color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:[--tw-ring-color:var(--shk-primary-500)]': !showError()\n }\"\n />\n <button\n type=\"button\"\n (click)=\"toggleMode()\"\n [disabled]=\"disabled()\"\n class=\"px-3 py-2 text-sm font-medium border rounded-r-lg transition-colors shrink-0\"\n [ngClass]=\"{\n '_shk-bg-primary-soft _shk-border-primary-200 _shk-text-primary-700': isPercentage(),\n '_shk-bg-white _shk-border _shk-text-surface-500 dark:[background-color:var(--shk-surface-800)] dark:[border-color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)]': !isPercentage(),\n 'cursor-not-allowed opacity-50': disabled()\n }\"\n >\n {{ isPercentage() ? '%' : '$' }}\n </button>\n </div>\n @if (showError()) {\n <div class=\"text-xs _shk-text-red-500 mt-1\">\n {{ errorMessage() }}\n </div>\n }\n</div>\n" }]
|
|
591
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }, { type: i0.Output, args: ["valueChange"] }], isPercentage: [{ type: i0.Input, args: [{ isSignal: true, alias: "isPercentage", required: false }] }, { type: i0.Output, args: ["isPercentageChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], showError: [{ type: i0.Input, args: [{ isSignal: true, alias: "showError", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }] } });
|
|
592
|
+
|
|
593
|
+
class DatePickerComponent {
|
|
594
|
+
elementRef = inject(ElementRef);
|
|
595
|
+
cdr = inject(ChangeDetectorRef);
|
|
596
|
+
destroyRef = inject(DestroyRef);
|
|
597
|
+
transloco = inject(TranslocoService);
|
|
598
|
+
id = input('', ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
599
|
+
view = input('date', ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
|
|
600
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
601
|
+
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
602
|
+
formGroup = input(undefined, ...(ngDevMode ? [{ debugName: "formGroup" }] : /* istanbul ignore next */ []));
|
|
603
|
+
control = input(undefined, ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
|
|
604
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
605
|
+
errorMessage = input('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
606
|
+
errorMessages = input({}, ...(ngDevMode ? [{ debugName: "errorMessages" }] : /* istanbul ignore next */ []));
|
|
607
|
+
minValue = input('', ...(ngDevMode ? [{ debugName: "minValue" }] : /* istanbul ignore next */ []));
|
|
608
|
+
maxValue = input('', ...(ngDevMode ? [{ debugName: "maxValue" }] : /* istanbul ignore next */ []));
|
|
609
|
+
locale = input('es', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
610
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
611
|
+
viewYear = signal(new Date().getFullYear(), ...(ngDevMode ? [{ debugName: "viewYear" }] : /* istanbul ignore next */ []));
|
|
612
|
+
viewMonth = signal(new Date().getMonth(), ...(ngDevMode ? [{ debugName: "viewMonth" }] : /* istanbul ignore next */ []));
|
|
613
|
+
viewDecade = signal(Math.floor(new Date().getFullYear() / 10) * 10, ...(ngDevMode ? [{ debugName: "viewDecade" }] : /* istanbul ignore next */ []));
|
|
614
|
+
_value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
|
|
615
|
+
_disabledState = signal(false, ...(ngDevMode ? [{ debugName: "_disabledState" }] : /* istanbul ignore next */ []));
|
|
616
|
+
subscribed = signal(false, ...(ngDevMode ? [{ debugName: "subscribed" }] : /* istanbul ignore next */ []));
|
|
617
|
+
valueSubscribed = signal(false, ...(ngDevMode ? [{ debugName: "valueSubscribed" }] : /* istanbul ignore next */ []));
|
|
618
|
+
errorTracker = signal(0, ...(ngDevMode ? [{ debugName: "errorTracker" }] : /* istanbul ignore next */ []));
|
|
619
|
+
hadError = signal(false, ...(ngDevMode ? [{ debugName: "hadError" }] : /* istanbul ignore next */ []));
|
|
620
|
+
onChange = () => { };
|
|
621
|
+
onTouched = () => { };
|
|
622
|
+
value = computed(() => this._value(), ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
623
|
+
isDisabled = computed(() => this.disabled() || this._disabledState(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
624
|
+
actualControl = computed(() => {
|
|
625
|
+
const ctrl = this.control();
|
|
626
|
+
if (ctrl)
|
|
627
|
+
return ctrl;
|
|
628
|
+
const fg = this.formGroup();
|
|
629
|
+
const lbl = this.label();
|
|
630
|
+
if (fg && lbl)
|
|
631
|
+
return fg.get(lbl);
|
|
632
|
+
return undefined;
|
|
633
|
+
}, ...(ngDevMode ? [{ debugName: "actualControl" }] : /* istanbul ignore next */ []));
|
|
634
|
+
shouldShowError = computed(() => {
|
|
635
|
+
this.errorTracker();
|
|
636
|
+
const ctrl = this.actualControl();
|
|
637
|
+
return ctrl?.invalid && (ctrl?.dirty || ctrl?.touched);
|
|
638
|
+
}, ...(ngDevMode ? [{ debugName: "shouldShowError" }] : /* istanbul ignore next */ []));
|
|
639
|
+
wasFixedError = computed(() => {
|
|
640
|
+
this.errorTracker();
|
|
641
|
+
const ctrl = this.actualControl();
|
|
642
|
+
return this.hadError() && ctrl?.valid && ctrl?.touched;
|
|
643
|
+
}, ...(ngDevMode ? [{ debugName: "wasFixedError" }] : /* istanbul ignore next */ []));
|
|
644
|
+
getErrorMessages = computed(() => {
|
|
645
|
+
this.errorTracker();
|
|
646
|
+
const customError = this.errorMessage();
|
|
647
|
+
if (customError)
|
|
648
|
+
return [customError];
|
|
649
|
+
const ctrl = this.actualControl();
|
|
650
|
+
if (!ctrl?.errors)
|
|
651
|
+
return [];
|
|
652
|
+
const customErrors = this.errorMessages();
|
|
653
|
+
const t = this.transloco;
|
|
654
|
+
return Object.keys(ctrl.errors).map((key) => {
|
|
655
|
+
if (customErrors[key])
|
|
656
|
+
return customErrors[key];
|
|
657
|
+
switch (key) {
|
|
658
|
+
case 'required':
|
|
659
|
+
return t.translate('validation.required');
|
|
660
|
+
default:
|
|
661
|
+
return t.translate('validation.invalid');
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
}, ...(ngDevMode ? [{ debugName: "getErrorMessages" }] : /* istanbul ignore next */ []));
|
|
665
|
+
displayLabel = computed(() => {
|
|
666
|
+
const value = this._value();
|
|
667
|
+
if (!value)
|
|
668
|
+
return '';
|
|
669
|
+
const v = this.view();
|
|
670
|
+
if (v === 'year') {
|
|
671
|
+
return value;
|
|
672
|
+
}
|
|
673
|
+
if (v === 'month') {
|
|
674
|
+
const [y, m] = value.split('-').map(Number);
|
|
675
|
+
const fmt = new Intl.DateTimeFormat(this.locale(), {
|
|
676
|
+
month: 'long',
|
|
677
|
+
year: 'numeric',
|
|
678
|
+
});
|
|
679
|
+
const label = fmt.format(new Date(y, m - 1, 1));
|
|
680
|
+
return label.charAt(0).toUpperCase() + label.slice(1);
|
|
681
|
+
}
|
|
682
|
+
const fmt = new Intl.DateTimeFormat(this.locale(), {
|
|
683
|
+
dateStyle: 'long',
|
|
684
|
+
});
|
|
685
|
+
return fmt.format(new Date(value + 'T12:00:00'));
|
|
686
|
+
}, ...(ngDevMode ? [{ debugName: "displayLabel" }] : /* istanbul ignore next */ []));
|
|
687
|
+
monthNames = computed(() => {
|
|
688
|
+
const fmt = new Intl.DateTimeFormat(this.locale(), { month: 'short' });
|
|
689
|
+
return Array.from({ length: 12 }, (_, i) => {
|
|
690
|
+
const name = fmt.format(new Date(2000, i, 1)).replace('.', '');
|
|
691
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
692
|
+
});
|
|
693
|
+
}, ...(ngDevMode ? [{ debugName: "monthNames" }] : /* istanbul ignore next */ []));
|
|
694
|
+
dayNames = computed(() => {
|
|
695
|
+
const fmt = new Intl.DateTimeFormat(this.locale(), { weekday: 'short' });
|
|
696
|
+
return Array.from({ length: 7 }, (_, i) => {
|
|
697
|
+
const name = fmt.format(new Date(2024, 0, i + 1));
|
|
698
|
+
return name.charAt(0).toUpperCase() + name.slice(1, 3);
|
|
699
|
+
});
|
|
700
|
+
}, ...(ngDevMode ? [{ debugName: "dayNames" }] : /* istanbul ignore next */ []));
|
|
701
|
+
daysInMonth = computed(() => {
|
|
702
|
+
return new Date(this.viewYear(), this.viewMonth() + 1, 0).getDate();
|
|
703
|
+
}, ...(ngDevMode ? [{ debugName: "daysInMonth" }] : /* istanbul ignore next */ []));
|
|
704
|
+
firstDayOfMonth = computed(() => {
|
|
705
|
+
return new Date(this.viewYear(), this.viewMonth(), 1).getDay();
|
|
706
|
+
}, ...(ngDevMode ? [{ debugName: "firstDayOfMonth" }] : /* istanbul ignore next */ []));
|
|
707
|
+
calendarDays = computed(() => {
|
|
708
|
+
const totalDays = this.daysInMonth();
|
|
709
|
+
const firstDay = this.firstDayOfMonth();
|
|
710
|
+
const days = [];
|
|
711
|
+
for (let i = 0; i < firstDay; i++)
|
|
712
|
+
days.push(null);
|
|
713
|
+
for (let i = 1; i <= totalDays; i++)
|
|
714
|
+
days.push(i);
|
|
715
|
+
return days;
|
|
716
|
+
}, ...(ngDevMode ? [{ debugName: "calendarDays" }] : /* istanbul ignore next */ []));
|
|
717
|
+
canGoPrev = computed(() => {
|
|
718
|
+
const v = this.view();
|
|
719
|
+
const min = this.minValue();
|
|
720
|
+
if (v === 'date') {
|
|
721
|
+
if (!min)
|
|
722
|
+
return true;
|
|
723
|
+
const prev = new Date(this.viewYear(), this.viewMonth() - 1, 1);
|
|
724
|
+
return prev >= new Date(min + 'T12:00:00');
|
|
725
|
+
}
|
|
726
|
+
if (v === 'month') {
|
|
727
|
+
const minY = min ? Number(min.split('-')[0]) : undefined;
|
|
728
|
+
return minY === undefined || this.viewYear() > minY;
|
|
729
|
+
}
|
|
730
|
+
const minY = min ? Number(min) : undefined;
|
|
731
|
+
return minY === undefined || this.viewYear() > minY;
|
|
732
|
+
}, ...(ngDevMode ? [{ debugName: "canGoPrev" }] : /* istanbul ignore next */ []));
|
|
733
|
+
canGoNext = computed(() => {
|
|
734
|
+
const v = this.view();
|
|
735
|
+
const max = this.maxValue();
|
|
736
|
+
if (v === 'date') {
|
|
737
|
+
if (!max)
|
|
738
|
+
return true;
|
|
739
|
+
const next = new Date(this.viewYear(), this.viewMonth() + 1, 1);
|
|
740
|
+
return next <= new Date(max + 'T12:00:00');
|
|
741
|
+
}
|
|
742
|
+
if (v === 'month') {
|
|
743
|
+
const maxY = max ? Number(max.split('-')[0]) : undefined;
|
|
744
|
+
return maxY === undefined || this.viewYear() < maxY;
|
|
745
|
+
}
|
|
746
|
+
const maxY = max ? Number(max) : undefined;
|
|
747
|
+
return maxY === undefined || this.viewYear() < maxY;
|
|
748
|
+
}, ...(ngDevMode ? [{ debugName: "canGoNext" }] : /* istanbul ignore next */ []));
|
|
749
|
+
selectedYear = computed(() => {
|
|
750
|
+
const value = this._value();
|
|
751
|
+
return value ? Number(value.split('-')[0]) : null;
|
|
752
|
+
}, ...(ngDevMode ? [{ debugName: "selectedYear" }] : /* istanbul ignore next */ []));
|
|
753
|
+
selectedMonth = computed(() => {
|
|
754
|
+
const value = this._value();
|
|
755
|
+
return value ? Number(value.split('-')[1]) - 1 : null;
|
|
756
|
+
}, ...(ngDevMode ? [{ debugName: "selectedMonth" }] : /* istanbul ignore next */ []));
|
|
757
|
+
selectedDay = computed(() => {
|
|
758
|
+
const value = this._value();
|
|
759
|
+
return value && this.view() === 'date' ? Number(value.split('-')[2]) : null;
|
|
760
|
+
}, ...(ngDevMode ? [{ debugName: "selectedDay" }] : /* istanbul ignore next */ []));
|
|
761
|
+
decadeYears = computed(() => {
|
|
762
|
+
const start = this.viewDecade();
|
|
763
|
+
return Array.from({ length: 12 }, (_, i) => start + i);
|
|
764
|
+
}, ...(ngDevMode ? [{ debugName: "decadeYears" }] : /* istanbul ignore next */ []));
|
|
765
|
+
years = [];
|
|
766
|
+
constructor() {
|
|
767
|
+
effect(() => {
|
|
768
|
+
const ctrl = this.actualControl();
|
|
769
|
+
if (!ctrl) {
|
|
770
|
+
this.subscribed.set(false);
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
if (this.subscribed())
|
|
774
|
+
return;
|
|
775
|
+
if (ctrl.invalid && ctrl.touched)
|
|
776
|
+
this.hadError.set(true);
|
|
777
|
+
ctrl.statusChanges
|
|
778
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
779
|
+
.subscribe(() => {
|
|
780
|
+
this.errorTracker.update((v) => v + 1);
|
|
781
|
+
if (ctrl.invalid && ctrl.touched)
|
|
782
|
+
this.hadError.set(true);
|
|
783
|
+
});
|
|
784
|
+
this.subscribed.set(true);
|
|
785
|
+
});
|
|
786
|
+
effect(() => {
|
|
787
|
+
const ctrl = this.actualControl();
|
|
788
|
+
if (!ctrl)
|
|
789
|
+
return;
|
|
790
|
+
if (!this.valueSubscribed()) {
|
|
791
|
+
this.writeValue(ctrl.value);
|
|
792
|
+
ctrl.valueChanges
|
|
793
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
794
|
+
.subscribe((value) => {
|
|
795
|
+
this.writeValue(value);
|
|
796
|
+
});
|
|
797
|
+
this.valueSubscribed.set(true);
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
onDocumentClick(event) {
|
|
802
|
+
if (!this.isOpen())
|
|
803
|
+
return;
|
|
804
|
+
if (!this.elementRef.nativeElement.contains(event.target)) {
|
|
805
|
+
this.close();
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
onEscape() {
|
|
809
|
+
this.close();
|
|
810
|
+
}
|
|
811
|
+
toggle() {
|
|
812
|
+
if (this.isDisabled())
|
|
813
|
+
return;
|
|
814
|
+
if (this.isOpen()) {
|
|
815
|
+
this.close();
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
this.syncViewToValue();
|
|
819
|
+
this.isOpen.set(true);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
syncViewToValue() {
|
|
823
|
+
const val = this._value();
|
|
824
|
+
const v = this.view();
|
|
825
|
+
if (!val) {
|
|
826
|
+
const now = new Date();
|
|
827
|
+
this.viewYear.set(now.getFullYear());
|
|
828
|
+
this.viewMonth.set(now.getMonth());
|
|
829
|
+
this.viewDecade.set(Math.floor(now.getFullYear() / 10) * 10);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
if (v === 'year') {
|
|
833
|
+
this.viewYear.set(Number(val));
|
|
834
|
+
}
|
|
835
|
+
else if (v === 'month') {
|
|
836
|
+
this.viewYear.set(Number(val.split('-')[0]));
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
const d = new Date(val + 'T12:00:00');
|
|
840
|
+
this.viewYear.set(d.getFullYear());
|
|
841
|
+
this.viewMonth.set(d.getMonth());
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
close() {
|
|
845
|
+
if (!this.isOpen())
|
|
846
|
+
return;
|
|
847
|
+
this.isOpen.set(false);
|
|
848
|
+
this.onTouched();
|
|
849
|
+
const ctrl = this.actualControl();
|
|
850
|
+
if (ctrl)
|
|
851
|
+
ctrl.markAsTouched();
|
|
852
|
+
this.errorTracker.update((v) => v + 1);
|
|
853
|
+
if (ctrl?.invalid && ctrl?.touched)
|
|
854
|
+
this.hadError.set(true);
|
|
855
|
+
}
|
|
856
|
+
prev() {
|
|
857
|
+
const v = this.view();
|
|
858
|
+
if (v === 'year') {
|
|
859
|
+
this.viewDecade.update((d) => d - 12);
|
|
860
|
+
this.viewYear.update((y) => y - 12);
|
|
861
|
+
}
|
|
862
|
+
else if (v === 'month') {
|
|
863
|
+
this.viewYear.update((y) => y - 1);
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
this.viewMonth.update((m) => {
|
|
867
|
+
if (m === 0) {
|
|
868
|
+
this.viewYear.update((y) => y - 1);
|
|
869
|
+
return 11;
|
|
870
|
+
}
|
|
871
|
+
return m - 1;
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
next() {
|
|
876
|
+
const v = this.view();
|
|
877
|
+
if (v === 'year') {
|
|
878
|
+
this.viewDecade.update((d) => d + 12);
|
|
879
|
+
this.viewYear.update((y) => y + 12);
|
|
880
|
+
}
|
|
881
|
+
else if (v === 'month') {
|
|
882
|
+
this.viewYear.update((y) => y + 1);
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
this.viewMonth.update((m) => {
|
|
886
|
+
if (m === 11) {
|
|
887
|
+
this.viewYear.update((y) => y + 1);
|
|
888
|
+
return 0;
|
|
889
|
+
}
|
|
890
|
+
return m + 1;
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
selectDay(day) {
|
|
895
|
+
const y = this.viewYear();
|
|
896
|
+
const m = String(this.viewMonth() + 1).padStart(2, '0');
|
|
897
|
+
const d = String(day).padStart(2, '0');
|
|
898
|
+
const value = `${y}-${m}-${d}`;
|
|
899
|
+
this.setValue(value);
|
|
900
|
+
}
|
|
901
|
+
selectMonth(monthIndex) {
|
|
902
|
+
const value = `${this.viewYear()}-${String(monthIndex + 1).padStart(2, '0')}`;
|
|
903
|
+
this.setValue(value);
|
|
904
|
+
}
|
|
905
|
+
selectYear(year) {
|
|
906
|
+
const value = String(year);
|
|
907
|
+
this.setValue(value);
|
|
908
|
+
}
|
|
909
|
+
setValue(value) {
|
|
910
|
+
this._value.set(value);
|
|
911
|
+
this.onChange(value);
|
|
912
|
+
const ctrl = this.actualControl();
|
|
913
|
+
if (ctrl) {
|
|
914
|
+
ctrl.setValue(value);
|
|
915
|
+
ctrl.markAsDirty();
|
|
916
|
+
}
|
|
917
|
+
this.close();
|
|
918
|
+
}
|
|
919
|
+
isSelectedDay(day) {
|
|
920
|
+
return (this.selectedYear() === this.viewYear() &&
|
|
921
|
+
this.selectedMonth() === this.viewMonth() &&
|
|
922
|
+
this.selectedDay() === day);
|
|
923
|
+
}
|
|
924
|
+
isToday(day) {
|
|
925
|
+
const now = new Date();
|
|
926
|
+
return (this.viewYear() === now.getFullYear() &&
|
|
927
|
+
this.viewMonth() === now.getMonth() &&
|
|
928
|
+
day === now.getDate());
|
|
929
|
+
}
|
|
930
|
+
isSelectedMonth(monthIndex) {
|
|
931
|
+
return (this.selectedYear() === this.viewYear() &&
|
|
932
|
+
this.selectedMonth() === monthIndex);
|
|
933
|
+
}
|
|
934
|
+
isCurrentMonth(monthIndex) {
|
|
935
|
+
const now = new Date();
|
|
936
|
+
return (this.viewYear() === now.getFullYear() && monthIndex === now.getMonth());
|
|
937
|
+
}
|
|
938
|
+
isSelectedYear(year) {
|
|
939
|
+
return this.selectedYear() === year;
|
|
940
|
+
}
|
|
941
|
+
isCurrentYear(year) {
|
|
942
|
+
return new Date().getFullYear() === year;
|
|
943
|
+
}
|
|
944
|
+
writeValue(value) {
|
|
945
|
+
this._value.set(value || null);
|
|
946
|
+
}
|
|
947
|
+
registerOnChange(fn) {
|
|
948
|
+
this.onChange = fn;
|
|
949
|
+
}
|
|
950
|
+
registerOnTouched(fn) {
|
|
951
|
+
this.onTouched = fn;
|
|
952
|
+
}
|
|
953
|
+
setDisabledState(isDisabled) {
|
|
954
|
+
this._disabledState.set(isDisabled);
|
|
955
|
+
this.cdr.markForCheck();
|
|
956
|
+
}
|
|
957
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
958
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: DatePickerComponent, isStandalone: true, selector: "shk-date-picker", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", 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 }, formGroup: { classPropertyName: "formGroup", publicName: "formGroup", isSignal: true, isRequired: false, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, errorMessages: { classPropertyName: "errorMessages", publicName: "errorMessages", isSignal: true, isRequired: false, transformFunction: null }, minValue: { classPropertyName: "minValue", publicName: "minValue", isSignal: true, isRequired: false, transformFunction: null }, maxValue: { classPropertyName: "maxValue", publicName: "maxValue", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()" } }, providers: [
|
|
959
|
+
{
|
|
960
|
+
provide: NG_VALUE_ACCESSOR,
|
|
961
|
+
useExisting: forwardRef(() => DatePickerComponent),
|
|
962
|
+
multi: true,
|
|
963
|
+
},
|
|
964
|
+
], ngImport: i0, template: "<div class=\"relative mb-4\">\n @if (label()) {\n <div class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-300)] mb-1\">\n {{ label() }}\n </div>\n }\n\n <div class=\"relative\">\n <button\n type=\"button\"\n [id]=\"id()\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n class=\"flex items-center justify-between w-full px-3 py-2 text-left _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border rounded-lg transition-colors sm:text-sm focus:outline-none focus:ring-1 disabled:cursor-not-allowed disabled:[color:var(--shk-surface-400)] dark:disabled:[color:var(--shk-surface-500)]\"\n [ngClass]=\"{\n 'border-red-500 focus:border-red-500 focus:ring-red-500': shouldShowError(),\n 'border-green-500 focus:border-green-500 focus:ring-green-500': wasFixedError(),\n '_shk-border dark:[border-color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:[--tw-ring-color:var(--shk-primary-500)]': !shouldShowError() && !wasFixedError(),\n }\"\n >\n <span\n [ngClass]=\"value() ? '_shk-text-surface-700 dark:[color:var(--shk-surface-200)]' : '_shk-text-surface-400 dark:[color:var(--shk-surface-500)]'\"\n >\n {{ displayLabel() || placeholder() || label() }}\n </span>\n <svg\n class=\"w-4 h-4 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] shrink-0\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\"></rect>\n <path d=\"M16 2v4M8 2v4M3 10h18\"></path>\n </svg>\n </button>\n\n @if (isOpen()) {\n <div class=\"fixed inset-0 z-40 bg-black/30\" (click)=\"close()\"></div>\n <div\n class=\"fixed z-50 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-72 p-4 bg-white dark:[background-color:var(--shk-surface-800)] _shk-border dark:[border-color:var(--shk-surface-700)] rounded-lg shadow-xl\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (view() === 'date') {\n <div class=\"flex items-center justify-between mb-3\">\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"!canGoPrev()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M15 18l-6-6 6-6\"></path>\n </svg>\n </button>\n <span class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\">\n {{ monthNames()[viewMonth()] }} {{ viewYear() }}\n </span>\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"!canGoNext()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M9 18l6-6-6-6\"></path>\n </svg>\n </button>\n </div>\n\n <div class=\"grid grid-cols-7 gap-0 mb-1\">\n @for (name of dayNames(); track $index) {\n <div class=\"text-center text-xs font-medium _shk-text-surface-400 dark:[color:var(--shk-surface-500)] py-1\">\n {{ name }}\n </div>\n }\n </div>\n\n <div class=\"grid grid-cols-7 gap-0\">\n @for (day of calendarDays(); track $index) {\n @if (day !== null) {\n <button\n type=\"button\"\n (click)=\"selectDay(day)\"\n class=\"py-1.5 text-sm rounded transition-colors\"\n [ngClass]=\"{\n '_shk-bg-primary _shk-text-white hover:[background-color:var(--shk-primary-700)]': isSelectedDay(day),\n 'ring-1 [--tw-ring-color:var(--shk-primary-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)]': isToday(day) && !isSelectedDay(day),\n '_shk-text-surface-700 dark:[color:var(--shk-surface-200)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)]': !isSelectedDay(day) && !isToday(day),\n }\"\n >\n {{ day }}\n </button>\n } @else {\n <div></div>\n }\n }\n </div>\n }\n\n @if (view() === 'month') {\n <div class=\"flex items-center justify-between mb-3\">\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"!canGoPrev()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M15 18l-6-6 6-6\"></path>\n </svg>\n </button>\n <span class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\">{{ viewYear() }}</span>\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"!canGoNext()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M9 18l6-6-6-6\"></path>\n </svg>\n </button>\n </div>\n\n <div class=\"grid grid-cols-3 gap-1\">\n @for (name of monthNames(); track $index) {\n <button\n type=\"button\"\n (click)=\"selectMonth($index)\"\n class=\"py-2 text-sm rounded transition-colors\"\n [ngClass]=\"{\n '_shk-bg-primary _shk-text-white hover:[background-color:var(--shk-primary-700)]': isSelectedMonth($index),\n 'ring-1 [--tw-ring-color:var(--shk-primary-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)]': isCurrentMonth($index) && !isSelectedMonth($index),\n '_shk-text-surface-700 dark:[color:var(--shk-surface-200)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)]': !isSelectedMonth($index) && !isCurrentMonth($index),\n }\"\n >\n {{ name }}\n </button>\n }\n </div>\n }\n\n @if (view() === 'year') {\n <div class=\"flex items-center justify-between mb-3\">\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"!canGoPrev()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M15 18l-6-6 6-6\"></path>\n </svg>\n </button>\n <span class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\">\n {{ viewDecade() }} - {{ viewDecade() + 11 }}\n </span>\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"!canGoNext()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M9 18l6-6-6-6\"></path>\n </svg>\n </button>\n </div>\n\n <div class=\"grid grid-cols-3 gap-1\">\n @for (year of decadeYears(); track year) {\n <button\n type=\"button\"\n (click)=\"selectYear(year)\"\n class=\"py-2 text-sm rounded transition-colors\"\n [ngClass]=\"{\n '_shk-bg-primary _shk-text-white hover:[background-color:var(--shk-primary-700)]': isSelectedYear(year),\n 'ring-1 [--tw-ring-color:var(--shk-primary-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)]': isCurrentYear(year) && !isSelectedYear(year),\n '_shk-text-surface-700 dark:[color:var(--shk-surface-200)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)]': !isSelectedYear(year) && !isCurrentYear(year),\n }\"\n >\n {{ year }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n\n @if (shouldShowError()) {\n <div class=\"mt-1 text-xs text-red-500 max-h-20 overflow-y-auto\">\n <ul class=\"list-disc pl-4 space-y-1\">\n @for (error of getErrorMessages(); track error) {\n <li>{{ error }}</li>\n }\n </ul>\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
965
|
+
}
|
|
966
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: DatePickerComponent, decorators: [{
|
|
967
|
+
type: Component,
|
|
968
|
+
args: [{ selector: 'shk-date-picker', standalone: true, imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
969
|
+
{
|
|
970
|
+
provide: NG_VALUE_ACCESSOR,
|
|
971
|
+
useExisting: forwardRef(() => DatePickerComponent),
|
|
972
|
+
multi: true,
|
|
973
|
+
},
|
|
974
|
+
], template: "<div class=\"relative mb-4\">\n @if (label()) {\n <div class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-300)] mb-1\">\n {{ label() }}\n </div>\n }\n\n <div class=\"relative\">\n <button\n type=\"button\"\n [id]=\"id()\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n class=\"flex items-center justify-between w-full px-3 py-2 text-left _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border rounded-lg transition-colors sm:text-sm focus:outline-none focus:ring-1 disabled:cursor-not-allowed disabled:[color:var(--shk-surface-400)] dark:disabled:[color:var(--shk-surface-500)]\"\n [ngClass]=\"{\n 'border-red-500 focus:border-red-500 focus:ring-red-500': shouldShowError(),\n 'border-green-500 focus:border-green-500 focus:ring-green-500': wasFixedError(),\n '_shk-border dark:[border-color:var(--shk-surface-700)] focus:[border-color:var(--shk-primary-500)] focus:[--tw-ring-color:var(--shk-primary-500)]': !shouldShowError() && !wasFixedError(),\n }\"\n >\n <span\n [ngClass]=\"value() ? '_shk-text-surface-700 dark:[color:var(--shk-surface-200)]' : '_shk-text-surface-400 dark:[color:var(--shk-surface-500)]'\"\n >\n {{ displayLabel() || placeholder() || label() }}\n </span>\n <svg\n class=\"w-4 h-4 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] shrink-0\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\"></rect>\n <path d=\"M16 2v4M8 2v4M3 10h18\"></path>\n </svg>\n </button>\n\n @if (isOpen()) {\n <div class=\"fixed inset-0 z-40 bg-black/30\" (click)=\"close()\"></div>\n <div\n class=\"fixed z-50 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-72 p-4 bg-white dark:[background-color:var(--shk-surface-800)] _shk-border dark:[border-color:var(--shk-surface-700)] rounded-lg shadow-xl\"\n (click)=\"$event.stopPropagation()\"\n >\n @if (view() === 'date') {\n <div class=\"flex items-center justify-between mb-3\">\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"!canGoPrev()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M15 18l-6-6 6-6\"></path>\n </svg>\n </button>\n <span class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\">\n {{ monthNames()[viewMonth()] }} {{ viewYear() }}\n </span>\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"!canGoNext()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M9 18l6-6-6-6\"></path>\n </svg>\n </button>\n </div>\n\n <div class=\"grid grid-cols-7 gap-0 mb-1\">\n @for (name of dayNames(); track $index) {\n <div class=\"text-center text-xs font-medium _shk-text-surface-400 dark:[color:var(--shk-surface-500)] py-1\">\n {{ name }}\n </div>\n }\n </div>\n\n <div class=\"grid grid-cols-7 gap-0\">\n @for (day of calendarDays(); track $index) {\n @if (day !== null) {\n <button\n type=\"button\"\n (click)=\"selectDay(day)\"\n class=\"py-1.5 text-sm rounded transition-colors\"\n [ngClass]=\"{\n '_shk-bg-primary _shk-text-white hover:[background-color:var(--shk-primary-700)]': isSelectedDay(day),\n 'ring-1 [--tw-ring-color:var(--shk-primary-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)]': isToday(day) && !isSelectedDay(day),\n '_shk-text-surface-700 dark:[color:var(--shk-surface-200)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)]': !isSelectedDay(day) && !isToday(day),\n }\"\n >\n {{ day }}\n </button>\n } @else {\n <div></div>\n }\n }\n </div>\n }\n\n @if (view() === 'month') {\n <div class=\"flex items-center justify-between mb-3\">\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"!canGoPrev()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M15 18l-6-6 6-6\"></path>\n </svg>\n </button>\n <span class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\">{{ viewYear() }}</span>\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"!canGoNext()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M9 18l6-6-6-6\"></path>\n </svg>\n </button>\n </div>\n\n <div class=\"grid grid-cols-3 gap-1\">\n @for (name of monthNames(); track $index) {\n <button\n type=\"button\"\n (click)=\"selectMonth($index)\"\n class=\"py-2 text-sm rounded transition-colors\"\n [ngClass]=\"{\n '_shk-bg-primary _shk-text-white hover:[background-color:var(--shk-primary-700)]': isSelectedMonth($index),\n 'ring-1 [--tw-ring-color:var(--shk-primary-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)]': isCurrentMonth($index) && !isSelectedMonth($index),\n '_shk-text-surface-700 dark:[color:var(--shk-surface-200)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)]': !isSelectedMonth($index) && !isCurrentMonth($index),\n }\"\n >\n {{ name }}\n </button>\n }\n </div>\n }\n\n @if (view() === 'year') {\n <div class=\"flex items-center justify-between mb-3\">\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"!canGoPrev()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M15 18l-6-6 6-6\"></path>\n </svg>\n </button>\n <span class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\">\n {{ viewDecade() }} - {{ viewDecade() + 11 }}\n </span>\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"!canGoNext()\"\n class=\"p-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-30 disabled:cursor-not-allowed\"\n >\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M9 18l6-6-6-6\"></path>\n </svg>\n </button>\n </div>\n\n <div class=\"grid grid-cols-3 gap-1\">\n @for (year of decadeYears(); track year) {\n <button\n type=\"button\"\n (click)=\"selectYear(year)\"\n class=\"py-2 text-sm rounded transition-colors\"\n [ngClass]=\"{\n '_shk-bg-primary _shk-text-white hover:[background-color:var(--shk-primary-700)]': isSelectedYear(year),\n 'ring-1 [--tw-ring-color:var(--shk-primary-500)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)]': isCurrentYear(year) && !isSelectedYear(year),\n '_shk-text-surface-700 dark:[color:var(--shk-surface-200)] hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)]': !isSelectedYear(year) && !isCurrentYear(year),\n }\"\n >\n {{ year }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n\n @if (shouldShowError()) {\n <div class=\"mt-1 text-xs text-red-500 max-h-20 overflow-y-auto\">\n <ul class=\"list-disc pl-4 space-y-1\">\n @for (error of getErrorMessages(); track error) {\n <li>{{ error }}</li>\n }\n </ul>\n </div>\n }\n</div>\n" }]
|
|
975
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], formGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "formGroup", required: false }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], errorMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessages", required: false }] }], minValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "minValue", required: false }] }], maxValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxValue", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], onDocumentClick: [{
|
|
976
|
+
type: HostListener,
|
|
977
|
+
args: ['document:click', ['$event']]
|
|
978
|
+
}], onEscape: [{
|
|
979
|
+
type: HostListener,
|
|
980
|
+
args: ['document:keydown.escape']
|
|
981
|
+
}] } });
|
|
982
|
+
|
|
983
|
+
const icons$4 = {
|
|
984
|
+
info: LucideInfo,
|
|
985
|
+
'triangle-alert': LucideTriangleAlert,
|
|
986
|
+
x: LucideX,
|
|
987
|
+
'trash-2': LucideTrash2,
|
|
988
|
+
loader: LucideLoader,
|
|
989
|
+
};
|
|
990
|
+
class ConfirmDialogComponent {
|
|
991
|
+
getIcon = (name) => icons$4[name] ?? LucideTriangleAlert;
|
|
992
|
+
title = input('Confirmar acción', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
993
|
+
message = input('¿Está seguro de realizar esta acción?', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
994
|
+
confirmLabel = input('Confirmar', ...(ngDevMode ? [{ debugName: "confirmLabel" }] : /* istanbul ignore next */ []));
|
|
995
|
+
cancelLabel = input('Cancelar', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
|
|
996
|
+
loadingText = input('Procesando…', ...(ngDevMode ? [{ debugName: "loadingText" }] : /* istanbul ignore next */ []));
|
|
997
|
+
type = input('danger', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
998
|
+
loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
999
|
+
showCancel = input(true, ...(ngDevMode ? [{ debugName: "showCancel" }] : /* istanbul ignore next */ []));
|
|
1000
|
+
visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
1001
|
+
resolveRef = signal(null, ...(ngDevMode ? [{ debugName: "resolveRef" }] : /* istanbul ignore next */ []));
|
|
1002
|
+
closed = output();
|
|
1003
|
+
show() {
|
|
1004
|
+
this.visible.set(true);
|
|
1005
|
+
return new Promise((resolve) => {
|
|
1006
|
+
this.resolveRef.set(resolve);
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
onConfirm() {
|
|
1010
|
+
this.visible.set(false);
|
|
1011
|
+
const resolve = this.resolveRef();
|
|
1012
|
+
if (resolve) {
|
|
1013
|
+
resolve(true);
|
|
1014
|
+
this.resolveRef.set(null);
|
|
1015
|
+
}
|
|
1016
|
+
this.closed.emit();
|
|
1017
|
+
}
|
|
1018
|
+
onCancel() {
|
|
1019
|
+
this.visible.set(false);
|
|
1020
|
+
const resolve = this.resolveRef();
|
|
1021
|
+
if (resolve) {
|
|
1022
|
+
resolve(false);
|
|
1023
|
+
this.resolveRef.set(null);
|
|
1024
|
+
}
|
|
1025
|
+
this.closed.emit();
|
|
1026
|
+
}
|
|
1027
|
+
ngOnDestroy() {
|
|
1028
|
+
this.resolveRef.set(null);
|
|
1029
|
+
}
|
|
1030
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ConfirmDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1031
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: ConfirmDialogComponent, isStandalone: true, selector: "shk-confirm-dialog", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, confirmLabel: { classPropertyName: "confirmLabel", publicName: "confirmLabel", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, showCancel: { classPropertyName: "showCancel", publicName: "showCancel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, ngImport: i0, template: "@if (visible()) {\n <div\n class=\"fixed inset-0 bg-black/40 backdrop-blur-sm flex items-center justify-center z-[9999] p-4\"\n (click)=\"onCancel()\"\n >\n <div\n class=\"bg-white dark:[background-color:var(--shk-surface-800)] rounded-2xl shadow-2xl max-w-sm w-full animate-in fade-in zoom-in duration-200 overflow-hidden\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"h-1.5 w-full\"\n [class._shk-gradient-red]=\"type() === 'danger'\"\n [class._shk-gradient-blue]=\"type() === 'info'\"\n [class._shk-bg-amber-400]=\"type() === 'warning'\"\n ></div>\n <div class=\"p-6\">\n <div class=\"flex items-start gap-4 mb-4\">\n <div\n class=\"w-12 h-12 rounded-full flex items-center justify-center shrink-0\"\n [ngClass]=\"{\n '_shk-bg-red-soft _shk-dark-bg-red-soft _shk-text-red-600 dark:[color:var(--shk-error-400)]': type() === 'danger',\n '_shk-bg-blue-soft _shk-dark-bg-blue-soft _shk-text-blue-600 dark:[color:var(--shk-info-400)]': type() === 'info',\n '_shk-bg-amber-soft _shk-dark-bg-amber-soft _shk-text-amber-600 dark:[color:var(--shk-amber-400)]': type() === 'warning',\n }\"\n >\n @if (type() === 'info') {\n <svg [lucideIcon]=\"getIcon('info')\" class=\"w-6 h-6\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('triangle-alert')\" class=\"w-6 h-6\"></svg>\n }\n </div>\n <div>\n <h3 class=\"text-lg font-bold _shk-text-surface-800 dark:[color:var(--shk-surface-100)]\">{{ title() }}</h3>\n <p class=\"_shk-text-surface-500 dark:[color:var(--shk-surface-400)] text-sm leading-relaxed mb-6\">{{ message() }}</p>\n </div>\n </div>\n <div class=\"flex gap-3\">\n @if (showCancel()) {\n <button\n type=\"button\"\n (click)=\"onCancel()\"\n [disabled]=\"loading()\"\n class=\"flex-1 px-4 py-2 text-sm font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)] _shk-bg-surface-100 dark:[background-color:var(--shk-surface-700)] hover:[background-color:var(--shk-surface-200)] dark:hover:[background-color:var(--shk-surface-600)] rounded-lg transition-colors flex items-center justify-center gap-2\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-4 h-4\"></svg>\n {{ cancelLabel() }}\n </button>\n }\n <button\n type=\"button\"\n (click)=\"onConfirm()\"\n [disabled]=\"loading()\"\n class=\"flex-1 px-4 py-2 text-sm font-semibold text-white rounded-lg shadow-md transition-all flex items-center justify-center gap-2\"\n [class._shk-bg-red-600]=\"type() === 'danger'\"\n [class.hover:[background-color:var(--shk-error-700)]]=\"type() === 'danger'\"\n [class.[box-shadow:0_0_0_1px_var(--shk-error-500)_inset]]=\"type() === 'danger'\"\n [class._shk-bg-primary]=\"type() === 'info'\"\n [class.hover:[background-color:var(--shk-primary-700)]]=\"type() === 'info'\"\n [class.[box-shadow:0_0_0_1px_var(--shk-primary-500)_inset]]=\"type() === 'info'\"\n [class._shk-bg-amber-600]=\"type() === 'warning'\"\n [class.hover:[background-color:var(--shk-amber-700)]]=\"type() === 'warning'\"\n [class.[box-shadow:0_0_0_1px_var(--shk-amber-500)_inset]]=\"type() === 'warning'\"\n >\n @if (loading()) {\n <svg [lucideIcon]=\"getIcon('loader')\" class=\"w-4 h-4 animate-spin\"></svg>\n } @else if (type() === 'danger') {\n <svg [lucideIcon]=\"getIcon('trash-2')\" class=\"w-4 h-4\"></svg>\n }\n {{ loading() ? loadingText() : confirmLabel() }}\n </button>\n </div>\n </div>\n </div>\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1032
|
+
}
|
|
1033
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ConfirmDialogComponent, decorators: [{
|
|
1034
|
+
type: Component,
|
|
1035
|
+
args: [{ selector: 'shk-confirm-dialog', standalone: true, imports: [CommonModule, LucideDynamicIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible()) {\n <div\n class=\"fixed inset-0 bg-black/40 backdrop-blur-sm flex items-center justify-center z-[9999] p-4\"\n (click)=\"onCancel()\"\n >\n <div\n class=\"bg-white dark:[background-color:var(--shk-surface-800)] rounded-2xl shadow-2xl max-w-sm w-full animate-in fade-in zoom-in duration-200 overflow-hidden\"\n (click)=\"$event.stopPropagation()\"\n >\n <div\n class=\"h-1.5 w-full\"\n [class._shk-gradient-red]=\"type() === 'danger'\"\n [class._shk-gradient-blue]=\"type() === 'info'\"\n [class._shk-bg-amber-400]=\"type() === 'warning'\"\n ></div>\n <div class=\"p-6\">\n <div class=\"flex items-start gap-4 mb-4\">\n <div\n class=\"w-12 h-12 rounded-full flex items-center justify-center shrink-0\"\n [ngClass]=\"{\n '_shk-bg-red-soft _shk-dark-bg-red-soft _shk-text-red-600 dark:[color:var(--shk-error-400)]': type() === 'danger',\n '_shk-bg-blue-soft _shk-dark-bg-blue-soft _shk-text-blue-600 dark:[color:var(--shk-info-400)]': type() === 'info',\n '_shk-bg-amber-soft _shk-dark-bg-amber-soft _shk-text-amber-600 dark:[color:var(--shk-amber-400)]': type() === 'warning',\n }\"\n >\n @if (type() === 'info') {\n <svg [lucideIcon]=\"getIcon('info')\" class=\"w-6 h-6\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('triangle-alert')\" class=\"w-6 h-6\"></svg>\n }\n </div>\n <div>\n <h3 class=\"text-lg font-bold _shk-text-surface-800 dark:[color:var(--shk-surface-100)]\">{{ title() }}</h3>\n <p class=\"_shk-text-surface-500 dark:[color:var(--shk-surface-400)] text-sm leading-relaxed mb-6\">{{ message() }}</p>\n </div>\n </div>\n <div class=\"flex gap-3\">\n @if (showCancel()) {\n <button\n type=\"button\"\n (click)=\"onCancel()\"\n [disabled]=\"loading()\"\n class=\"flex-1 px-4 py-2 text-sm font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)] _shk-bg-surface-100 dark:[background-color:var(--shk-surface-700)] hover:[background-color:var(--shk-surface-200)] dark:hover:[background-color:var(--shk-surface-600)] rounded-lg transition-colors flex items-center justify-center gap-2\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-4 h-4\"></svg>\n {{ cancelLabel() }}\n </button>\n }\n <button\n type=\"button\"\n (click)=\"onConfirm()\"\n [disabled]=\"loading()\"\n class=\"flex-1 px-4 py-2 text-sm font-semibold text-white rounded-lg shadow-md transition-all flex items-center justify-center gap-2\"\n [class._shk-bg-red-600]=\"type() === 'danger'\"\n [class.hover:[background-color:var(--shk-error-700)]]=\"type() === 'danger'\"\n [class.[box-shadow:0_0_0_1px_var(--shk-error-500)_inset]]=\"type() === 'danger'\"\n [class._shk-bg-primary]=\"type() === 'info'\"\n [class.hover:[background-color:var(--shk-primary-700)]]=\"type() === 'info'\"\n [class.[box-shadow:0_0_0_1px_var(--shk-primary-500)_inset]]=\"type() === 'info'\"\n [class._shk-bg-amber-600]=\"type() === 'warning'\"\n [class.hover:[background-color:var(--shk-amber-700)]]=\"type() === 'warning'\"\n [class.[box-shadow:0_0_0_1px_var(--shk-amber-500)_inset]]=\"type() === 'warning'\"\n >\n @if (loading()) {\n <svg [lucideIcon]=\"getIcon('loader')\" class=\"w-4 h-4 animate-spin\"></svg>\n } @else if (type() === 'danger') {\n <svg [lucideIcon]=\"getIcon('trash-2')\" class=\"w-4 h-4\"></svg>\n }\n {{ loading() ? loadingText() : confirmLabel() }}\n </button>\n </div>\n </div>\n </div>\n </div>\n}\n" }]
|
|
1036
|
+
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], confirmLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmLabel", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], showCancel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCancel", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
|
|
1037
|
+
|
|
1038
|
+
class ConfirmDialogService {
|
|
1039
|
+
dialogRef = null;
|
|
1040
|
+
destroyRef = inject(DestroyRef);
|
|
1041
|
+
appRef = inject(ApplicationRef);
|
|
1042
|
+
injector = inject(EnvironmentInjector);
|
|
1043
|
+
constructor() {
|
|
1044
|
+
this.destroyRef.onDestroy(() => this.cleanupDialog());
|
|
1045
|
+
}
|
|
1046
|
+
async confirm(config) {
|
|
1047
|
+
if (this.dialogRef)
|
|
1048
|
+
this.cleanupDialog();
|
|
1049
|
+
this.dialogRef = createComponent(ConfirmDialogComponent, {
|
|
1050
|
+
environmentInjector: this.injector,
|
|
1051
|
+
});
|
|
1052
|
+
this.dialogRef.setInput('title', config.title ?? 'Confirmar acción');
|
|
1053
|
+
this.dialogRef.setInput('message', config.message);
|
|
1054
|
+
this.dialogRef.setInput('confirmLabel', config.confirmLabel ?? 'Confirmar');
|
|
1055
|
+
this.dialogRef.setInput('cancelLabel', config.cancelLabel ?? 'Cancelar');
|
|
1056
|
+
this.dialogRef.setInput('loadingText', config.loadingText ?? 'Procesando…');
|
|
1057
|
+
this.dialogRef.setInput('type', config.type ?? 'danger');
|
|
1058
|
+
this.dialogRef.setInput('showCancel', config.showCancel ?? true);
|
|
1059
|
+
this.dialogRef.setInput('loading', config.loading ?? false);
|
|
1060
|
+
document.body.appendChild(this.dialogRef.location.nativeElement);
|
|
1061
|
+
this.appRef.attachView(this.dialogRef.hostView);
|
|
1062
|
+
try {
|
|
1063
|
+
return await this.dialogRef.instance.show();
|
|
1064
|
+
}
|
|
1065
|
+
finally {
|
|
1066
|
+
this.cleanupDialog();
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
cleanupDialog() {
|
|
1070
|
+
if (this.dialogRef) {
|
|
1071
|
+
this.appRef.detachView(this.dialogRef.hostView);
|
|
1072
|
+
this.dialogRef.destroy();
|
|
1073
|
+
this.dialogRef = null;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ConfirmDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1077
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ConfirmDialogService, providedIn: 'root' });
|
|
1078
|
+
}
|
|
1079
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ConfirmDialogService, decorators: [{
|
|
1080
|
+
type: Injectable,
|
|
1081
|
+
args: [{ providedIn: 'root' }]
|
|
1082
|
+
}], ctorParameters: () => [] });
|
|
1083
|
+
|
|
1084
|
+
class ModalService {
|
|
1085
|
+
stackSignal = signal([], ...(ngDevMode ? [{ debugName: "stackSignal" }] : /* istanbul ignore next */ []));
|
|
1086
|
+
closeRequestSignal = signal(0, ...(ngDevMode ? [{ debugName: "closeRequestSignal" }] : /* istanbul ignore next */ []));
|
|
1087
|
+
router = inject(Router);
|
|
1088
|
+
destroyRef = inject(DestroyRef);
|
|
1089
|
+
constructor() {
|
|
1090
|
+
this.router.events.subscribe(() => {
|
|
1091
|
+
this.stackSignal.set([]);
|
|
1092
|
+
});
|
|
1093
|
+
this.destroyRef.onDestroy(() => this.stackSignal.set([]));
|
|
1094
|
+
}
|
|
1095
|
+
modalStack = computed(() => this.stackSignal(), ...(ngDevMode ? [{ debugName: "modalStack" }] : /* istanbul ignore next */ []));
|
|
1096
|
+
currentModal = computed(() => {
|
|
1097
|
+
const stack = this.stackSignal();
|
|
1098
|
+
return stack.length > 0 ? stack[stack.length - 0] : null;
|
|
1099
|
+
}, ...(ngDevMode ? [{ debugName: "currentModal" }] : /* istanbul ignore next */ []));
|
|
1100
|
+
closeRequest = computed(() => this.closeRequestSignal(), ...(ngDevMode ? [{ debugName: "closeRequest" }] : /* istanbul ignore next */ []));
|
|
1101
|
+
open(config) {
|
|
1102
|
+
this.stackSignal.update((stack) => [...stack, config]);
|
|
1103
|
+
}
|
|
1104
|
+
accept() {
|
|
1105
|
+
this.close();
|
|
1106
|
+
}
|
|
1107
|
+
close() {
|
|
1108
|
+
const stack = this.stackSignal();
|
|
1109
|
+
if (stack.length > 0) {
|
|
1110
|
+
stack[stack.length - 1]?.onClose?.();
|
|
1111
|
+
this.stackSignal.set(stack.slice(0, -1));
|
|
1112
|
+
this.closeRequestSignal.update((v) => v + 1);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
clear() {
|
|
1116
|
+
this.stackSignal.set([]);
|
|
1117
|
+
}
|
|
1118
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ModalService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1119
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ModalService, providedIn: 'root' });
|
|
1120
|
+
}
|
|
1121
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ModalService, decorators: [{
|
|
1122
|
+
type: Injectable,
|
|
1123
|
+
args: [{ providedIn: 'root' }]
|
|
1124
|
+
}], ctorParameters: () => [] });
|
|
1125
|
+
|
|
1126
|
+
const icons$3 = {
|
|
1127
|
+
x: LucideX,
|
|
1128
|
+
check: LucideCheck,
|
|
1129
|
+
'maximize-2': LucideMaximize2,
|
|
1130
|
+
'minimize-2': LucideMinimize2,
|
|
1131
|
+
'loader-circle': LucideLoaderCircle,
|
|
1132
|
+
};
|
|
1133
|
+
class ModalComponent {
|
|
1134
|
+
getIcon = (name) => icons$3[name] ?? LucideX;
|
|
1135
|
+
notificationSrv = inject(NotificationService);
|
|
1136
|
+
destroyRef = inject(DestroyRef);
|
|
1137
|
+
modalSrv = inject(ModalService);
|
|
1138
|
+
transloco = inject(TranslocoService);
|
|
1139
|
+
container = viewChild('dynamicContent', { ...(ngDevMode ? { debugName: "container" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
|
|
1140
|
+
currentConfig = signal(null, ...(ngDevMode ? [{ debugName: "currentConfig" }] : /* istanbul ignore next */ []));
|
|
1141
|
+
title = signal('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
1142
|
+
visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
1143
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
1144
|
+
isProcessing = signal(false, ...(ngDevMode ? [{ debugName: "isProcessing" }] : /* istanbul ignore next */ []));
|
|
1145
|
+
isExpanded = signal(false, ...(ngDevMode ? [{ debugName: "isExpanded" }] : /* istanbul ignore next */ []));
|
|
1146
|
+
isFormValid = signal(false, ...(ngDevMode ? [{ debugName: "isFormValid" }] : /* istanbul ignore next */ []));
|
|
1147
|
+
componentRef = null;
|
|
1148
|
+
subscriptions = [];
|
|
1149
|
+
computedModalWidth = computed(() => {
|
|
1150
|
+
return this.isExpanded() ? '100vw' : (this.currentConfig()?.width ?? '560px');
|
|
1151
|
+
}, ...(ngDevMode ? [{ debugName: "computedModalWidth" }] : /* istanbul ignore next */ []));
|
|
1152
|
+
modalButtonsVisible = computed(() => this.currentConfig()?.showButtons ?? true, ...(ngDevMode ? [{ debugName: "modalButtonsVisible" }] : /* istanbul ignore next */ []));
|
|
1153
|
+
showExpandButton = computed(() => this.currentConfig()?.showExpandButton ?? false, ...(ngDevMode ? [{ debugName: "showExpandButton" }] : /* istanbul ignore next */ []));
|
|
1154
|
+
cancelLabel = computed(() => this.currentConfig()?.cancelLabel || 'Cancelar', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
|
|
1155
|
+
acceptLabel = computed(() => this.currentConfig()?.acceptLabel || 'Aceptar', ...(ngDevMode ? [{ debugName: "acceptLabel" }] : /* istanbul ignore next */ []));
|
|
1156
|
+
constructor() {
|
|
1157
|
+
effect(() => {
|
|
1158
|
+
const config = this.modalSrv.currentModal();
|
|
1159
|
+
if (config) {
|
|
1160
|
+
this.currentConfig.set(config);
|
|
1161
|
+
this.title.set(config.title);
|
|
1162
|
+
this.isExpanded.set(false);
|
|
1163
|
+
this.visible.set(true);
|
|
1164
|
+
setTimeout(() => {
|
|
1165
|
+
untracked(() => this.loadComponent(config));
|
|
1166
|
+
});
|
|
1167
|
+
this.isProcessing.set(false);
|
|
1168
|
+
this.loading.set(false);
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
this.visible.set(false);
|
|
1172
|
+
this.currentConfig.set(null);
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
this.destroyRef.onDestroy(() => this.cleanup());
|
|
1176
|
+
}
|
|
1177
|
+
onVisibleChange(show) {
|
|
1178
|
+
this.visible.set(show);
|
|
1179
|
+
if (!show) {
|
|
1180
|
+
this.closeModal();
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
loadComponent(config) {
|
|
1184
|
+
const containerRef = this.container();
|
|
1185
|
+
if (!containerRef)
|
|
1186
|
+
return;
|
|
1187
|
+
this.cleanup();
|
|
1188
|
+
containerRef.clear();
|
|
1189
|
+
this.componentRef = containerRef.createComponent(config.component);
|
|
1190
|
+
if (config.data) {
|
|
1191
|
+
Object.entries(config.data).forEach(([key, value]) => {
|
|
1192
|
+
this.componentRef?.setInput(key, value);
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
const instance = this.componentRef.instance;
|
|
1196
|
+
this.subscribeToOutput(instance.formValid, (valid) => this.isFormValid.set(valid));
|
|
1197
|
+
this.subscribeToOutput(instance.submitSuccess, () => this.handleSubmit(true));
|
|
1198
|
+
this.subscribeToOutput(instance.submitError, () => this.handleSubmit(false));
|
|
1199
|
+
this.componentRef.changeDetectorRef.markForCheck();
|
|
1200
|
+
}
|
|
1201
|
+
subscribeToOutput(output, callback) {
|
|
1202
|
+
if (output?.subscribe) {
|
|
1203
|
+
this.subscriptions.push(output.subscribe(callback));
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
handleSubmit(success) {
|
|
1207
|
+
this.isProcessing.set(false);
|
|
1208
|
+
this.loading.set(false);
|
|
1209
|
+
if (success) {
|
|
1210
|
+
untracked(() => this.modalSrv.accept());
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
closeModal() {
|
|
1214
|
+
if (this.componentRef?.instance?.handleCancel) {
|
|
1215
|
+
this.componentRef.instance.handleCancel();
|
|
1216
|
+
}
|
|
1217
|
+
this.doClose();
|
|
1218
|
+
}
|
|
1219
|
+
doClose() {
|
|
1220
|
+
this.visible.set(false);
|
|
1221
|
+
this.isProcessing.set(false);
|
|
1222
|
+
this.loading.set(false);
|
|
1223
|
+
if (this.isExpanded()) {
|
|
1224
|
+
this.isExpanded.set(false);
|
|
1225
|
+
}
|
|
1226
|
+
setTimeout(() => {
|
|
1227
|
+
this.cleanup();
|
|
1228
|
+
this.modalSrv.close();
|
|
1229
|
+
}, 100);
|
|
1230
|
+
}
|
|
1231
|
+
toggleExpand() {
|
|
1232
|
+
this.isExpanded.update((v) => !v);
|
|
1233
|
+
}
|
|
1234
|
+
onAccept() {
|
|
1235
|
+
if (!this.componentRef || !this.isDynamicComponent(this.componentRef.instance))
|
|
1236
|
+
return;
|
|
1237
|
+
if (this.isProcessing())
|
|
1238
|
+
return;
|
|
1239
|
+
const instance = this.componentRef.instance;
|
|
1240
|
+
const form = this.getForm(instance);
|
|
1241
|
+
if (form) {
|
|
1242
|
+
this.markAllAsTouched(form);
|
|
1243
|
+
this.componentRef.changeDetectorRef.markForCheck();
|
|
1244
|
+
if (this.hasRealErrors(form)) {
|
|
1245
|
+
this.notificationSrv.addNotification(this.transloco.translate('modal.formInvalid'), 'warning');
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
this.isProcessing.set(true);
|
|
1250
|
+
this.loading.set(true);
|
|
1251
|
+
instance.onSubmit();
|
|
1252
|
+
}
|
|
1253
|
+
getForm(instance) {
|
|
1254
|
+
if (!instance['form'])
|
|
1255
|
+
return null;
|
|
1256
|
+
return typeof instance['form'] === 'function'
|
|
1257
|
+
? instance['form']()
|
|
1258
|
+
: instance['form'];
|
|
1259
|
+
}
|
|
1260
|
+
hasRealErrors(control) {
|
|
1261
|
+
if (control instanceof FormControl) {
|
|
1262
|
+
const errors = control.errors;
|
|
1263
|
+
return errors
|
|
1264
|
+
? Object.keys(errors).some((key) => key !== 'warning')
|
|
1265
|
+
: false;
|
|
1266
|
+
}
|
|
1267
|
+
const groupErrors = control.errors;
|
|
1268
|
+
if (groupErrors &&
|
|
1269
|
+
Object.keys(groupErrors).some((key) => key !== 'warning')) {
|
|
1270
|
+
return true;
|
|
1271
|
+
}
|
|
1272
|
+
return Object.values(control.controls).some((child) => this.hasRealErrors(child));
|
|
1273
|
+
}
|
|
1274
|
+
markAllAsTouched(control) {
|
|
1275
|
+
control.markAsTouched();
|
|
1276
|
+
control.updateValueAndValidity({ onlySelf: true, emitEvent: true });
|
|
1277
|
+
if (control instanceof FormGroup) {
|
|
1278
|
+
Object.values(control.controls).forEach((child) => this.markAllAsTouched(child));
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
isDynamicComponent(instance) {
|
|
1282
|
+
return typeof instance?.onSubmit === 'function' && instance?.form !== undefined;
|
|
1283
|
+
}
|
|
1284
|
+
cleanup() {
|
|
1285
|
+
this.subscriptions.forEach((sub) => sub.unsubscribe());
|
|
1286
|
+
this.subscriptions = [];
|
|
1287
|
+
if (this.componentRef) {
|
|
1288
|
+
this.componentRef.destroy();
|
|
1289
|
+
this.componentRef = null;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1293
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: ModalComponent, isStandalone: true, selector: "shk-modal", viewQueries: [{ propertyName: "container", first: true, predicate: ["dynamicContent"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "@if (visible()) {\n <div\n class=\"fixed inset-0 bg-black/40 backdrop-blur-sm flex items-start justify-center z-40 p-4 pt-[5vh] sm:pt-[10vh] overflow-y-auto\"\n (click)=\"closeModal()\"\n >\n <div\n class=\"bg-white dark:[background-color:var(--shk-surface-800)] rounded-xl shadow-2xl overflow-hidden flex flex-col animate-in fade-in zoom-in duration-200\"\n [class.w-[95vw]]=\"isExpanded()\"\n [class.h-[90vh]]=\"isExpanded()\"\n [class.w-full]=\"!isExpanded()\"\n [style.max-width]=\"isExpanded() ? undefined : computedModalWidth()\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"h-1.5 w-full _shk-gradient-emerald shrink-0\"></div>\n\n <div class=\"flex items-center justify-between px-6 pt-4 pb-2 shrink-0\">\n <span class=\"font-semibold text-lg _shk-text-surface-800 dark:[color:var(--shk-surface-100)]\">{{ title() }}</span>\n <div class=\"flex items-center gap-2\">\n @if (showExpandButton()) {\n <button\n type=\"button\"\n (click)=\"toggleExpand()\"\n class=\"p-1.5 rounded-lg hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] _shk-text-surface-500 dark:[color:var(--shk-surface-400)] transition-colors\"\n >\n @if (isExpanded()) {\n <svg [lucideIcon]=\"getIcon('minimize-2')\" class=\"w-4 h-4\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('maximize-2')\" class=\"w-4 h-4\"></svg>\n }\n </button>\n }\n <button\n type=\"button\"\n (click)=\"closeModal()\"\n class=\"p-1.5 rounded-lg hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] _shk-text-surface-500 dark:[color:var(--shk-surface-400)] transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n </div>\n\n <div class=\"overflow-y-auto px-6 py-4 flex-1 max-h-[70vh]\">\n <ng-container #dynamicContent></ng-container>\n </div>\n\n @if (modalButtonsVisible()) {\n <hr class=\"border-t _shk-border dark:[border-color:var(--shk-surface-700)] shrink-0\" />\n <div class=\"flex justify-end gap-3 px-6 py-4 shrink-0\">\n <button\n type=\"button\"\n (click)=\"closeModal()\"\n [disabled]=\"loading()\"\n class=\"px-4 py-2 text-sm font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)] _shk-bg-surface-100 dark:[background-color:var(--shk-surface-700)] hover:[background-color:var(--shk-surface-200)] dark:hover:[background-color:var(--shk-surface-600)] rounded-lg transition-colors flex items-center gap-2 disabled:opacity-50\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-4 h-4\"></svg>\n {{ cancelLabel() }}\n </button>\n <button\n type=\"button\"\n (click)=\"onAccept()\"\n [disabled]=\"loading()\"\n class=\"px-4 py-2 text-sm font-semibold _shk-text-white _shk-bg-primary hover:[background-color:var(--shk-primary-700)] rounded-lg shadow-md transition-all flex items-center gap-2 disabled:opacity-50\"\n >\n @if (loading()) {\n <svg [lucideIcon]=\"getIcon('loader-circle')\" class=\"w-4 h-4 animate-spin\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('check')\" class=\"w-4 h-4\"></svg>\n }\n {{ acceptLabel() }}\n </button>\n </div>\n }\n </div>\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1294
|
+
}
|
|
1295
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: ModalComponent, decorators: [{
|
|
1296
|
+
type: Component,
|
|
1297
|
+
args: [{ selector: 'shk-modal', standalone: true, imports: [CommonModule, LucideDynamicIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible()) {\n <div\n class=\"fixed inset-0 bg-black/40 backdrop-blur-sm flex items-start justify-center z-40 p-4 pt-[5vh] sm:pt-[10vh] overflow-y-auto\"\n (click)=\"closeModal()\"\n >\n <div\n class=\"bg-white dark:[background-color:var(--shk-surface-800)] rounded-xl shadow-2xl overflow-hidden flex flex-col animate-in fade-in zoom-in duration-200\"\n [class.w-[95vw]]=\"isExpanded()\"\n [class.h-[90vh]]=\"isExpanded()\"\n [class.w-full]=\"!isExpanded()\"\n [style.max-width]=\"isExpanded() ? undefined : computedModalWidth()\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"h-1.5 w-full _shk-gradient-emerald shrink-0\"></div>\n\n <div class=\"flex items-center justify-between px-6 pt-4 pb-2 shrink-0\">\n <span class=\"font-semibold text-lg _shk-text-surface-800 dark:[color:var(--shk-surface-100)]\">{{ title() }}</span>\n <div class=\"flex items-center gap-2\">\n @if (showExpandButton()) {\n <button\n type=\"button\"\n (click)=\"toggleExpand()\"\n class=\"p-1.5 rounded-lg hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] _shk-text-surface-500 dark:[color:var(--shk-surface-400)] transition-colors\"\n >\n @if (isExpanded()) {\n <svg [lucideIcon]=\"getIcon('minimize-2')\" class=\"w-4 h-4\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('maximize-2')\" class=\"w-4 h-4\"></svg>\n }\n </button>\n }\n <button\n type=\"button\"\n (click)=\"closeModal()\"\n class=\"p-1.5 rounded-lg hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] _shk-text-surface-500 dark:[color:var(--shk-surface-400)] transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n </div>\n\n <div class=\"overflow-y-auto px-6 py-4 flex-1 max-h-[70vh]\">\n <ng-container #dynamicContent></ng-container>\n </div>\n\n @if (modalButtonsVisible()) {\n <hr class=\"border-t _shk-border dark:[border-color:var(--shk-surface-700)] shrink-0\" />\n <div class=\"flex justify-end gap-3 px-6 py-4 shrink-0\">\n <button\n type=\"button\"\n (click)=\"closeModal()\"\n [disabled]=\"loading()\"\n class=\"px-4 py-2 text-sm font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)] _shk-bg-surface-100 dark:[background-color:var(--shk-surface-700)] hover:[background-color:var(--shk-surface-200)] dark:hover:[background-color:var(--shk-surface-600)] rounded-lg transition-colors flex items-center gap-2 disabled:opacity-50\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-4 h-4\"></svg>\n {{ cancelLabel() }}\n </button>\n <button\n type=\"button\"\n (click)=\"onAccept()\"\n [disabled]=\"loading()\"\n class=\"px-4 py-2 text-sm font-semibold _shk-text-white _shk-bg-primary hover:[background-color:var(--shk-primary-700)] rounded-lg shadow-md transition-all flex items-center gap-2 disabled:opacity-50\"\n >\n @if (loading()) {\n <svg [lucideIcon]=\"getIcon('loader-circle')\" class=\"w-4 h-4 animate-spin\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('check')\" class=\"w-4 h-4\"></svg>\n }\n {{ acceptLabel() }}\n </button>\n </div>\n }\n </div>\n </div>\n}\n" }]
|
|
1298
|
+
}], ctorParameters: () => [], propDecorators: { container: [{ type: i0.ViewChild, args: ['dynamicContent', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
|
|
1299
|
+
|
|
1300
|
+
const icons$2 = {
|
|
1301
|
+
'chevron-down': LucideChevronDown,
|
|
1302
|
+
'chevron-left': LucideChevronLeft,
|
|
1303
|
+
'chevron-right': LucideChevronRight,
|
|
1304
|
+
};
|
|
1305
|
+
class SelectComponent {
|
|
1306
|
+
elementRef;
|
|
1307
|
+
getIcon = (name) => icons$2[name] ?? LucideChevronDown;
|
|
1308
|
+
options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
1309
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
|
|
1310
|
+
validCombinations = input(undefined, ...(ngDevMode ? [{ debugName: "validCombinations" }] : /* istanbul ignore next */ []));
|
|
1311
|
+
placeholder = input('Seleccionar...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
1312
|
+
label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1313
|
+
isLoading = input(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
1314
|
+
isAllDataLoaded = input(false, ...(ngDevMode ? [{ debugName: "isAllDataLoaded" }] : /* istanbul ignore next */ []));
|
|
1315
|
+
allowCustomEntries = input(false, ...(ngDevMode ? [{ debugName: "allowCustomEntries" }] : /* istanbul ignore next */ []));
|
|
1316
|
+
formGroup = input(undefined, ...(ngDevMode ? [{ debugName: "formGroup" }] : /* istanbul ignore next */ []));
|
|
1317
|
+
isSearchable = input(false, ...(ngDevMode ? [{ debugName: "isSearchable" }] : /* istanbul ignore next */ []));
|
|
1318
|
+
itemsPerPage = input(10, ...(ngDevMode ? [{ debugName: "itemsPerPage" }] : /* istanbul ignore next */ []));
|
|
1319
|
+
control = input(undefined, ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
|
|
1320
|
+
usePagination = input(false, ...(ngDevMode ? [{ debugName: "usePagination" }] : /* istanbul ignore next */ []));
|
|
1321
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
1322
|
+
errorMessages = input({
|
|
1323
|
+
required: 'Este campo es requerido',
|
|
1324
|
+
}, ...(ngDevMode ? [{ debugName: "errorMessages" }] : /* istanbul ignore next */ []));
|
|
1325
|
+
preserveSearchOnLoad = input(false, ...(ngDevMode ? [{ debugName: "preserveSearchOnLoad" }] : /* istanbul ignore next */ []));
|
|
1326
|
+
emptyMessageKey = input('common.no_records', ...(ngDevMode ? [{ debugName: "emptyMessageKey" }] : /* istanbul ignore next */ []));
|
|
1327
|
+
dropdownUpward = input(false, ...(ngDevMode ? [{ debugName: "dropdownUpward" }] : /* istanbul ignore next */ []));
|
|
1328
|
+
showEmptyOption = input(true, ...(ngDevMode ? [{ debugName: "showEmptyOption" }] : /* istanbul ignore next */ []));
|
|
1329
|
+
selectionChange = output();
|
|
1330
|
+
search = output();
|
|
1331
|
+
filterInput = viewChild('filterInput', ...(ngDevMode ? [{ debugName: "filterInput" }] : /* istanbul ignore next */ []));
|
|
1332
|
+
selectContainer = viewChild('selectContainer', ...(ngDevMode ? [{ debugName: "selectContainer" }] : /* istanbul ignore next */ []));
|
|
1333
|
+
selectTrigger = viewChild('selectTrigger', ...(ngDevMode ? [{ debugName: "selectTrigger" }] : /* istanbul ignore next */ []));
|
|
1334
|
+
dropdownContainer = viewChild('dropdownContainer', ...(ngDevMode ? [{ debugName: "dropdownContainer" }] : /* istanbul ignore next */ []));
|
|
1335
|
+
_value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
|
|
1336
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
1337
|
+
focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : /* istanbul ignore next */ []));
|
|
1338
|
+
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
|
|
1339
|
+
currentPage = signal(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
|
|
1340
|
+
searchTermChanged = signal(false, ...(ngDevMode ? [{ debugName: "searchTermChanged" }] : /* istanbul ignore next */ []));
|
|
1341
|
+
_isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_isDisabled" }] : /* istanbul ignore next */ []));
|
|
1342
|
+
debounceTimer = null;
|
|
1343
|
+
errorTracker = signal(0, ...(ngDevMode ? [{ debugName: "errorTracker" }] : /* istanbul ignore next */ []));
|
|
1344
|
+
hadError = signal(false, ...(ngDevMode ? [{ debugName: "hadError" }] : /* istanbul ignore next */ []));
|
|
1345
|
+
subscribed = signal(false, ...(ngDevMode ? [{ debugName: "subscribed" }] : /* istanbul ignore next */ []));
|
|
1346
|
+
valueSubscribed = signal(false, ...(ngDevMode ? [{ debugName: "valueSubscribed" }] : /* istanbul ignore next */ []));
|
|
1347
|
+
value = computed(() => this._value(), ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1348
|
+
isDisabled = computed(() => this.disabled() || this._isDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
1349
|
+
actualControl = computed(() => {
|
|
1350
|
+
const ctrl = this.control();
|
|
1351
|
+
if (ctrl)
|
|
1352
|
+
return ctrl;
|
|
1353
|
+
const fg = this.formGroup();
|
|
1354
|
+
const lbl = this.label();
|
|
1355
|
+
if (fg && lbl) {
|
|
1356
|
+
return fg.get(lbl);
|
|
1357
|
+
}
|
|
1358
|
+
return undefined;
|
|
1359
|
+
}, ...(ngDevMode ? [{ debugName: "actualControl" }] : /* istanbul ignore next */ []));
|
|
1360
|
+
shouldShowError = computed(() => {
|
|
1361
|
+
this.errorTracker();
|
|
1362
|
+
const ctrl = this.actualControl();
|
|
1363
|
+
return ctrl?.invalid && (ctrl?.dirty || ctrl?.touched);
|
|
1364
|
+
}, ...(ngDevMode ? [{ debugName: "shouldShowError" }] : /* istanbul ignore next */ []));
|
|
1365
|
+
wasFixedError = computed(() => {
|
|
1366
|
+
this.errorTracker();
|
|
1367
|
+
const ctrl = this.actualControl();
|
|
1368
|
+
return this.hadError() && ctrl?.valid && ctrl?.touched;
|
|
1369
|
+
}, ...(ngDevMode ? [{ debugName: "wasFixedError" }] : /* istanbul ignore next */ []));
|
|
1370
|
+
displayValue = computed(() => {
|
|
1371
|
+
const val = this._value();
|
|
1372
|
+
const opts = this.options();
|
|
1373
|
+
const isMultiple = this.multiple();
|
|
1374
|
+
const ph = this.placeholder();
|
|
1375
|
+
if (!val || (Array.isArray(val) && val.length === 0)) {
|
|
1376
|
+
return ph;
|
|
1377
|
+
}
|
|
1378
|
+
if (isMultiple) {
|
|
1379
|
+
const values = Array.isArray(val) ? val : [];
|
|
1380
|
+
const selectedLabels = opts
|
|
1381
|
+
.filter((option) => values.includes(option.value))
|
|
1382
|
+
.map((option) => option.label);
|
|
1383
|
+
if (selectedLabels.length === 0) {
|
|
1384
|
+
return values.toString();
|
|
1385
|
+
}
|
|
1386
|
+
return selectedLabels.join(', ');
|
|
1387
|
+
}
|
|
1388
|
+
const selectedOption = opts.find((opt) => opt.value === val);
|
|
1389
|
+
return selectedOption ? selectedOption.label : val;
|
|
1390
|
+
}, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
1391
|
+
getErrorMessages = computed(() => {
|
|
1392
|
+
this.errorTracker();
|
|
1393
|
+
const ctrl = this.actualControl();
|
|
1394
|
+
if (!ctrl?.errors)
|
|
1395
|
+
return [];
|
|
1396
|
+
const customErrors = this.errorMessages();
|
|
1397
|
+
const t = this.transloco;
|
|
1398
|
+
return Object.keys(ctrl.errors).map((errorKey) => {
|
|
1399
|
+
if (customErrors[errorKey])
|
|
1400
|
+
return customErrors[errorKey];
|
|
1401
|
+
switch (errorKey) {
|
|
1402
|
+
case 'required':
|
|
1403
|
+
return t.translate('validation.required');
|
|
1404
|
+
case 'email':
|
|
1405
|
+
return t.translate('validation.email');
|
|
1406
|
+
case 'minlength':
|
|
1407
|
+
return t.translate('validation.minlength', {
|
|
1408
|
+
min: ctrl.errors['minlength'].requiredLength,
|
|
1409
|
+
});
|
|
1410
|
+
case 'maxlength':
|
|
1411
|
+
return t.translate('validation.maxlength', {
|
|
1412
|
+
max: ctrl.errors['maxlength'].requiredLength,
|
|
1413
|
+
});
|
|
1414
|
+
case 'pattern':
|
|
1415
|
+
return t.translate('validation.pattern');
|
|
1416
|
+
default:
|
|
1417
|
+
return t.translate('validation.invalid');
|
|
1418
|
+
}
|
|
1419
|
+
});
|
|
1420
|
+
}, ...(ngDevMode ? [{ debugName: "getErrorMessages" }] : /* istanbul ignore next */ []));
|
|
1421
|
+
filteredOptions = computed(() => {
|
|
1422
|
+
const opts = this.options();
|
|
1423
|
+
const term = this.searchTerm();
|
|
1424
|
+
if (!term)
|
|
1425
|
+
return opts;
|
|
1426
|
+
const normalizedSearchTerm = this.normalizeText(term);
|
|
1427
|
+
const matched = opts.filter((option) => this.normalizeText(option.label).includes(normalizedSearchTerm));
|
|
1428
|
+
return matched.sort((a, b) => {
|
|
1429
|
+
const aLbl = this.normalizeText(a.label);
|
|
1430
|
+
const bLbl = this.normalizeText(b.label);
|
|
1431
|
+
const aStarts = aLbl.startsWith(normalizedSearchTerm);
|
|
1432
|
+
const bStarts = bLbl.startsWith(normalizedSearchTerm);
|
|
1433
|
+
if (aStarts && !bStarts)
|
|
1434
|
+
return -1;
|
|
1435
|
+
if (!aStarts && bStarts)
|
|
1436
|
+
return 1;
|
|
1437
|
+
return 0;
|
|
1438
|
+
});
|
|
1439
|
+
}, ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
|
|
1440
|
+
paginatedOptions = computed(() => {
|
|
1441
|
+
let filtered = this.filteredOptions();
|
|
1442
|
+
const term = this.searchTerm();
|
|
1443
|
+
const allowCustom = this.allowCustomEntries();
|
|
1444
|
+
if (allowCustom && term && !filtered.some((o) => o.value === term)) {
|
|
1445
|
+
filtered = [
|
|
1446
|
+
{
|
|
1447
|
+
label: `${term} (nuevo)`,
|
|
1448
|
+
value: term,
|
|
1449
|
+
custom: true,
|
|
1450
|
+
},
|
|
1451
|
+
...filtered,
|
|
1452
|
+
];
|
|
1453
|
+
}
|
|
1454
|
+
if (!this.usePagination()) {
|
|
1455
|
+
return filtered;
|
|
1456
|
+
}
|
|
1457
|
+
const page = this.currentPage();
|
|
1458
|
+
const perPage = this.itemsPerPage();
|
|
1459
|
+
const startIndex = (page - 1) * perPage;
|
|
1460
|
+
const endIndex = startIndex + perPage;
|
|
1461
|
+
return filtered.slice(startIndex, endIndex);
|
|
1462
|
+
}, ...(ngDevMode ? [{ debugName: "paginatedOptions" }] : /* istanbul ignore next */ []));
|
|
1463
|
+
regularOptions = computed(() => {
|
|
1464
|
+
const opts = this.paginatedOptions().filter((option) => !option.custom);
|
|
1465
|
+
if (!this.multiple() && this.showEmptyOption()) {
|
|
1466
|
+
return [{ label: '- Sin selección -', value: null }, ...opts];
|
|
1467
|
+
}
|
|
1468
|
+
return opts;
|
|
1469
|
+
}, ...(ngDevMode ? [{ debugName: "regularOptions" }] : /* istanbul ignore next */ []));
|
|
1470
|
+
totalPages = computed(() => {
|
|
1471
|
+
if (!this.usePagination()) {
|
|
1472
|
+
return 1;
|
|
1473
|
+
}
|
|
1474
|
+
const filtered = this.filteredOptions();
|
|
1475
|
+
const perPage = this.itemsPerPage();
|
|
1476
|
+
return Math.ceil(filtered.length / perPage);
|
|
1477
|
+
}, ...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
|
|
1478
|
+
hasSelectedValues = computed(() => {
|
|
1479
|
+
const val = this._value();
|
|
1480
|
+
if (this.multiple()) {
|
|
1481
|
+
return val && val.length > 0;
|
|
1482
|
+
}
|
|
1483
|
+
return !!val;
|
|
1484
|
+
}, ...(ngDevMode ? [{ debugName: "hasSelectedValues" }] : /* istanbul ignore next */ []));
|
|
1485
|
+
showCustomOption = computed(() => {
|
|
1486
|
+
const term = this.searchTerm();
|
|
1487
|
+
const opts = this.options();
|
|
1488
|
+
return (this.allowCustomEntries() &&
|
|
1489
|
+
term !== '' &&
|
|
1490
|
+
!opts.some((o) => o.value === term));
|
|
1491
|
+
}, ...(ngDevMode ? [{ debugName: "showCustomOption" }] : /* istanbul ignore next */ []));
|
|
1492
|
+
transloco = inject(TranslocoService);
|
|
1493
|
+
onChange = (_) => { };
|
|
1494
|
+
onTouched = () => { };
|
|
1495
|
+
destroyRef = inject(DestroyRef);
|
|
1496
|
+
constructor(elementRef) {
|
|
1497
|
+
this.elementRef = elementRef;
|
|
1498
|
+
effect(() => {
|
|
1499
|
+
const term = this.searchTerm();
|
|
1500
|
+
if (term !== '' && this.isSearchable()) {
|
|
1501
|
+
if (this.debounceTimer) {
|
|
1502
|
+
clearTimeout(this.debounceTimer);
|
|
1503
|
+
}
|
|
1504
|
+
this.debounceTimer = setTimeout(() => {
|
|
1505
|
+
this.search.emit(term);
|
|
1506
|
+
}, 300);
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
effect(() => {
|
|
1510
|
+
const isMultiple = this.multiple();
|
|
1511
|
+
if (this._value() === null || this._value() === undefined) {
|
|
1512
|
+
this._value.set(isMultiple ? [] : null);
|
|
1513
|
+
}
|
|
1514
|
+
});
|
|
1515
|
+
effect(() => {
|
|
1516
|
+
this.options();
|
|
1517
|
+
this.currentPage.set(1);
|
|
1518
|
+
if (!this.preserveSearchOnLoad() || !this.searchTermChanged()) {
|
|
1519
|
+
this.searchTerm.set('');
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
effect(() => {
|
|
1523
|
+
const ctrl = this.actualControl();
|
|
1524
|
+
if (!ctrl) {
|
|
1525
|
+
this.subscribed.set(false);
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
if (this.subscribed())
|
|
1529
|
+
return;
|
|
1530
|
+
if (ctrl.invalid && ctrl.touched) {
|
|
1531
|
+
this.hadError.set(true);
|
|
1532
|
+
}
|
|
1533
|
+
ctrl.statusChanges
|
|
1534
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1535
|
+
.subscribe(() => {
|
|
1536
|
+
this.errorTracker.update((v) => v + 1);
|
|
1537
|
+
if (ctrl.invalid && ctrl.touched) {
|
|
1538
|
+
this.hadError.set(true);
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
ctrl.valueChanges
|
|
1542
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1543
|
+
.subscribe(() => {
|
|
1544
|
+
this.errorTracker.update((v) => v + 1);
|
|
1545
|
+
});
|
|
1546
|
+
this.subscribed.set(true);
|
|
1547
|
+
});
|
|
1548
|
+
this.transloco.langChanges$
|
|
1549
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
1550
|
+
.subscribe(() => {
|
|
1551
|
+
this.errorTracker.update((v) => v + 1);
|
|
1552
|
+
});
|
|
1553
|
+
effect(() => {
|
|
1554
|
+
const ctrl = this.actualControl();
|
|
1555
|
+
if (!ctrl)
|
|
1556
|
+
return;
|
|
1557
|
+
if (!this.valueSubscribed()) {
|
|
1558
|
+
this.writeValue(ctrl.value);
|
|
1559
|
+
ctrl.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value) => {
|
|
1560
|
+
this.writeValue(value);
|
|
1561
|
+
});
|
|
1562
|
+
this.valueSubscribed.set(true);
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
setDisabledState(isDisabled) {
|
|
1567
|
+
this._isDisabled.set(isDisabled);
|
|
1568
|
+
}
|
|
1569
|
+
markAsTouchedAndUpdate() {
|
|
1570
|
+
const ctrl = this.actualControl();
|
|
1571
|
+
if (ctrl) {
|
|
1572
|
+
ctrl.markAsTouched();
|
|
1573
|
+
ctrl.updateValueAndValidity({ emitEvent: false });
|
|
1574
|
+
}
|
|
1575
|
+
this.onTouched();
|
|
1576
|
+
this.errorTracker.update((v) => v + 1);
|
|
1577
|
+
if (ctrl?.invalid) {
|
|
1578
|
+
this.hadError.set(true);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
normalizeText(text) {
|
|
1582
|
+
return text
|
|
1583
|
+
.normalize('NFD')
|
|
1584
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
1585
|
+
.toLowerCase();
|
|
1586
|
+
}
|
|
1587
|
+
onKeydown(event) {
|
|
1588
|
+
if (this.isDisabled())
|
|
1589
|
+
return;
|
|
1590
|
+
switch (event.key) {
|
|
1591
|
+
case 'Enter':
|
|
1592
|
+
case ' ':
|
|
1593
|
+
event.preventDefault();
|
|
1594
|
+
if (this.isOpen()) {
|
|
1595
|
+
if (this.focusedIndex() >= 0) {
|
|
1596
|
+
const options = this.regularOptions();
|
|
1597
|
+
const selectedOption = options[this.focusedIndex()];
|
|
1598
|
+
if (selectedOption && !this.isOptionDisabled(selectedOption)) {
|
|
1599
|
+
this.selectOption(selectedOption);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
else {
|
|
1604
|
+
this.toggleDropdown();
|
|
1605
|
+
}
|
|
1606
|
+
break;
|
|
1607
|
+
case 'Escape':
|
|
1608
|
+
if (this.isOpen()) {
|
|
1609
|
+
this.isOpen.set(false);
|
|
1610
|
+
this.searchTerm.set('');
|
|
1611
|
+
this.focusedIndex.set(-1);
|
|
1612
|
+
this.selectTrigger()?.nativeElement.focus();
|
|
1613
|
+
}
|
|
1614
|
+
break;
|
|
1615
|
+
case 'ArrowDown':
|
|
1616
|
+
event.preventDefault();
|
|
1617
|
+
if (!this.isOpen()) {
|
|
1618
|
+
this.toggleDropdown();
|
|
1619
|
+
}
|
|
1620
|
+
else {
|
|
1621
|
+
this.moveFocus(1);
|
|
1622
|
+
}
|
|
1623
|
+
break;
|
|
1624
|
+
case 'ArrowUp':
|
|
1625
|
+
event.preventDefault();
|
|
1626
|
+
if (this.isOpen()) {
|
|
1627
|
+
this.moveFocus(-1);
|
|
1628
|
+
}
|
|
1629
|
+
break;
|
|
1630
|
+
case 'Tab':
|
|
1631
|
+
if (this.isOpen()) {
|
|
1632
|
+
this.isOpen.set(false);
|
|
1633
|
+
this.searchTerm.set('');
|
|
1634
|
+
this.focusedIndex.set(-1);
|
|
1635
|
+
}
|
|
1636
|
+
break;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
handleKeyDown(event) {
|
|
1640
|
+
switch (event.key) {
|
|
1641
|
+
case 'Enter':
|
|
1642
|
+
event.preventDefault();
|
|
1643
|
+
event.stopPropagation();
|
|
1644
|
+
if (this.focusedIndex() >= 0) {
|
|
1645
|
+
const options = this.regularOptions();
|
|
1646
|
+
const option = options[this.focusedIndex()];
|
|
1647
|
+
if (option && !this.isOptionDisabled(option)) {
|
|
1648
|
+
this.selectOption(option);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
else {
|
|
1652
|
+
const filtered = this.filteredOptions();
|
|
1653
|
+
if (filtered.length > 0) {
|
|
1654
|
+
this.selectOption(filtered[0]);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
break;
|
|
1658
|
+
case 'ArrowDown':
|
|
1659
|
+
event.preventDefault();
|
|
1660
|
+
event.stopPropagation();
|
|
1661
|
+
this.moveFocus(1);
|
|
1662
|
+
break;
|
|
1663
|
+
case 'ArrowUp':
|
|
1664
|
+
event.preventDefault();
|
|
1665
|
+
event.stopPropagation();
|
|
1666
|
+
this.moveFocus(-1);
|
|
1667
|
+
break;
|
|
1668
|
+
case 'Escape':
|
|
1669
|
+
event.stopPropagation();
|
|
1670
|
+
event.preventDefault();
|
|
1671
|
+
this.isOpen.set(false);
|
|
1672
|
+
this.searchTerm.set('');
|
|
1673
|
+
this.focusedIndex.set(-1);
|
|
1674
|
+
this.selectTrigger()?.nativeElement.focus();
|
|
1675
|
+
break;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
moveFocus(direction) {
|
|
1679
|
+
const options = this.regularOptions();
|
|
1680
|
+
if (!options.length)
|
|
1681
|
+
return;
|
|
1682
|
+
let newIndex = this.focusedIndex();
|
|
1683
|
+
do {
|
|
1684
|
+
newIndex = newIndex + direction;
|
|
1685
|
+
if (newIndex < 0)
|
|
1686
|
+
newIndex = options.length - 1;
|
|
1687
|
+
if (newIndex >= options.length)
|
|
1688
|
+
newIndex = 0;
|
|
1689
|
+
} while (this.isOptionDisabled(options[newIndex]) && options.length > 1);
|
|
1690
|
+
this.focusedIndex.set(newIndex);
|
|
1691
|
+
setTimeout(() => {
|
|
1692
|
+
const container = this.dropdownContainer()?.nativeElement;
|
|
1693
|
+
const activeItem = container?.querySelector('.select-option-active');
|
|
1694
|
+
if (activeItem && container) {
|
|
1695
|
+
const itemTop = activeItem.offsetTop;
|
|
1696
|
+
const itemBottom = itemTop + activeItem.offsetHeight;
|
|
1697
|
+
const containerScrollTop = container.scrollTop;
|
|
1698
|
+
const containerHeight = container.clientHeight;
|
|
1699
|
+
if (itemTop < containerScrollTop) {
|
|
1700
|
+
container.scrollTop = itemTop;
|
|
1701
|
+
}
|
|
1702
|
+
else if (itemBottom > containerScrollTop + containerHeight) {
|
|
1703
|
+
container.scrollTop = itemBottom - containerHeight;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
getSelectedIndex() {
|
|
1709
|
+
const currentVal = this._value();
|
|
1710
|
+
const options = this.regularOptions();
|
|
1711
|
+
if (currentVal === null || currentVal === undefined)
|
|
1712
|
+
return -1;
|
|
1713
|
+
const index = options.findIndex((opt) => opt.value === currentVal);
|
|
1714
|
+
return index >= 0 ? index : -1;
|
|
1715
|
+
}
|
|
1716
|
+
isOptionDisabled(option) {
|
|
1717
|
+
if (option.value === null)
|
|
1718
|
+
return false;
|
|
1719
|
+
const validCombos = this.validCombinations();
|
|
1720
|
+
if (!validCombos || !this.multiple()) {
|
|
1721
|
+
return false;
|
|
1722
|
+
}
|
|
1723
|
+
const currentSelection = Array.isArray(this._value()) ? this._value() : [];
|
|
1724
|
+
if (currentSelection.length === 0) {
|
|
1725
|
+
return false;
|
|
1726
|
+
}
|
|
1727
|
+
if (currentSelection.includes(option.value)) {
|
|
1728
|
+
return false;
|
|
1729
|
+
}
|
|
1730
|
+
const hypotheticalSelection = [...currentSelection, option.value];
|
|
1731
|
+
return !validCombos.some((combination) => {
|
|
1732
|
+
const allSelected = hypotheticalSelection.every((selected) => combination.includes(selected));
|
|
1733
|
+
const withinLimit = hypotheticalSelection.length <= combination.length;
|
|
1734
|
+
return allSelected && withinLimit;
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
isSelected(option) {
|
|
1738
|
+
const val = this._value();
|
|
1739
|
+
if (this.multiple()) {
|
|
1740
|
+
return Array.isArray(val) && val.includes(option.value);
|
|
1741
|
+
}
|
|
1742
|
+
return val === option.value;
|
|
1743
|
+
}
|
|
1744
|
+
nextPage() {
|
|
1745
|
+
const total = this.totalPages();
|
|
1746
|
+
const current = this.currentPage();
|
|
1747
|
+
if (current < total) {
|
|
1748
|
+
this.currentPage.set(current + 1);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
previousPage() {
|
|
1752
|
+
const current = this.currentPage();
|
|
1753
|
+
if (current > 1) {
|
|
1754
|
+
this.currentPage.set(current - 1);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
onClickOutside(event) {
|
|
1758
|
+
const clickedInside = this.elementRef.nativeElement.contains(event.target);
|
|
1759
|
+
if (!clickedInside && this.isOpen()) {
|
|
1760
|
+
this.markAsTouchedAndUpdate();
|
|
1761
|
+
this.isOpen.set(false);
|
|
1762
|
+
this.searchTerm.set('');
|
|
1763
|
+
this.focusedIndex.set(-1);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
onCustomOptionClick() {
|
|
1767
|
+
const term = this.searchTerm();
|
|
1768
|
+
const customOption = {
|
|
1769
|
+
label: term,
|
|
1770
|
+
value: term,
|
|
1771
|
+
};
|
|
1772
|
+
this._value.set(customOption.value);
|
|
1773
|
+
this.onChange(customOption.value);
|
|
1774
|
+
this.selectionChange.emit(customOption.value);
|
|
1775
|
+
this.isOpen.set(false);
|
|
1776
|
+
this.searchTerm.set('');
|
|
1777
|
+
const ctrl = this.actualControl();
|
|
1778
|
+
if (ctrl) {
|
|
1779
|
+
ctrl.setValue(customOption.value);
|
|
1780
|
+
}
|
|
1781
|
+
this.markAsTouchedAndUpdate();
|
|
1782
|
+
}
|
|
1783
|
+
onInputChange(event) {
|
|
1784
|
+
const previousSearch = this.searchTerm();
|
|
1785
|
+
const target = event.target;
|
|
1786
|
+
this.searchTerm.set(target.value);
|
|
1787
|
+
this.searchTermChanged.set(true);
|
|
1788
|
+
if (previousSearch !== this.searchTerm()) {
|
|
1789
|
+
this.search.emit(this.searchTerm());
|
|
1790
|
+
if (this.allowCustomEntries() && this.searchTerm()) {
|
|
1791
|
+
this._value.set(this.searchTerm());
|
|
1792
|
+
this.onChange(this.searchTerm());
|
|
1793
|
+
this.selectionChange.emit(this.searchTerm());
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
this.currentPage.set(1);
|
|
1797
|
+
}
|
|
1798
|
+
onOptionClick(option) {
|
|
1799
|
+
if (this.isDisabled())
|
|
1800
|
+
return;
|
|
1801
|
+
if (this.allowCustomEntries() && option.value === this.searchTerm()) {
|
|
1802
|
+
const customOption = {
|
|
1803
|
+
label: this.searchTerm(),
|
|
1804
|
+
value: this.searchTerm(),
|
|
1805
|
+
};
|
|
1806
|
+
this._value.set(customOption.value);
|
|
1807
|
+
this.onChange(customOption.value);
|
|
1808
|
+
this.selectionChange.emit(customOption.value);
|
|
1809
|
+
this.isOpen.set(false);
|
|
1810
|
+
this.searchTerm.set('');
|
|
1811
|
+
const ctrl = this.actualControl();
|
|
1812
|
+
if (ctrl) {
|
|
1813
|
+
ctrl.setValue(customOption.value);
|
|
1814
|
+
}
|
|
1815
|
+
this.markAsTouchedAndUpdate();
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
if (this.multiple()) {
|
|
1819
|
+
this.toggleOption(option);
|
|
1820
|
+
}
|
|
1821
|
+
else {
|
|
1822
|
+
this.selectOption(option);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
selectOption(option) {
|
|
1826
|
+
if (this.multiple()) {
|
|
1827
|
+
this.toggleOption(option);
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
this._value.set(option.value);
|
|
1831
|
+
this.onChange(option.value);
|
|
1832
|
+
this.onTouched();
|
|
1833
|
+
this.selectionChange.emit(option.value);
|
|
1834
|
+
this.isOpen.set(false);
|
|
1835
|
+
const ctrl = this.actualControl();
|
|
1836
|
+
if (ctrl) {
|
|
1837
|
+
ctrl.setValue(option.value);
|
|
1838
|
+
}
|
|
1839
|
+
this.markAsTouchedAndUpdate();
|
|
1840
|
+
this.focusedIndex.set(-1);
|
|
1841
|
+
this.selectTrigger()?.nativeElement.focus();
|
|
1842
|
+
}
|
|
1843
|
+
toggleOption(option) {
|
|
1844
|
+
if (this.isOptionDisabled(option))
|
|
1845
|
+
return;
|
|
1846
|
+
if (!this.multiple())
|
|
1847
|
+
return;
|
|
1848
|
+
let currentValue = this._value();
|
|
1849
|
+
if (!Array.isArray(currentValue)) {
|
|
1850
|
+
currentValue = [];
|
|
1851
|
+
}
|
|
1852
|
+
const index = currentValue.indexOf(option.value);
|
|
1853
|
+
let newValue;
|
|
1854
|
+
if (index === -1) {
|
|
1855
|
+
newValue = [...currentValue, option.value];
|
|
1856
|
+
}
|
|
1857
|
+
else {
|
|
1858
|
+
newValue = currentValue.filter((v) => v !== option.value);
|
|
1859
|
+
}
|
|
1860
|
+
this._value.set(newValue);
|
|
1861
|
+
this.onChange(newValue);
|
|
1862
|
+
this.onTouched();
|
|
1863
|
+
this.selectionChange.emit(newValue);
|
|
1864
|
+
this.markAsTouchedAndUpdate();
|
|
1865
|
+
}
|
|
1866
|
+
toggleDropdown() {
|
|
1867
|
+
if (this.isDisabled())
|
|
1868
|
+
return;
|
|
1869
|
+
const wasOpen = this.isOpen();
|
|
1870
|
+
this.isOpen.update((v) => !v);
|
|
1871
|
+
if (!wasOpen && this.isOpen()) {
|
|
1872
|
+
const selectedIdx = this.getSelectedIndex();
|
|
1873
|
+
this.focusedIndex.set(selectedIdx);
|
|
1874
|
+
}
|
|
1875
|
+
else if (wasOpen && !this.isOpen()) {
|
|
1876
|
+
this.searchTerm.set('');
|
|
1877
|
+
this.focusedIndex.set(-1);
|
|
1878
|
+
this.selectTrigger()?.nativeElement.focus();
|
|
1879
|
+
}
|
|
1880
|
+
if (this.isOpen() && this.isSearchable()) {
|
|
1881
|
+
setTimeout(() => {
|
|
1882
|
+
this.filterInput()?.nativeElement?.focus();
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
this.onTouched();
|
|
1886
|
+
}
|
|
1887
|
+
trackByFn(index, option) {
|
|
1888
|
+
return option.value;
|
|
1889
|
+
}
|
|
1890
|
+
writeValue(value) {
|
|
1891
|
+
if (value === null ||
|
|
1892
|
+
value === undefined ||
|
|
1893
|
+
value === '' ||
|
|
1894
|
+
(Array.isArray(value) && value.length === 0)) {
|
|
1895
|
+
this._value.set(this.multiple() ? [] : null);
|
|
1896
|
+
}
|
|
1897
|
+
else {
|
|
1898
|
+
this._value.set(value);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
registerOnChange(fn) {
|
|
1902
|
+
this.onChange = fn;
|
|
1903
|
+
}
|
|
1904
|
+
registerOnTouched(fn) {
|
|
1905
|
+
this.onTouched = fn;
|
|
1906
|
+
}
|
|
1907
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SelectComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1908
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: SelectComponent, isStandalone: true, selector: "shk-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, validCombinations: { classPropertyName: "validCombinations", publicName: "validCombinations", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, isAllDataLoaded: { classPropertyName: "isAllDataLoaded", publicName: "isAllDataLoaded", isSignal: true, isRequired: false, transformFunction: null }, allowCustomEntries: { classPropertyName: "allowCustomEntries", publicName: "allowCustomEntries", isSignal: true, isRequired: false, transformFunction: null }, formGroup: { classPropertyName: "formGroup", publicName: "formGroup", isSignal: true, isRequired: false, transformFunction: null }, isSearchable: { classPropertyName: "isSearchable", publicName: "isSearchable", isSignal: true, isRequired: false, transformFunction: null }, itemsPerPage: { classPropertyName: "itemsPerPage", publicName: "itemsPerPage", isSignal: true, isRequired: false, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, usePagination: { classPropertyName: "usePagination", publicName: "usePagination", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, errorMessages: { classPropertyName: "errorMessages", publicName: "errorMessages", isSignal: true, isRequired: false, transformFunction: null }, preserveSearchOnLoad: { classPropertyName: "preserveSearchOnLoad", publicName: "preserveSearchOnLoad", isSignal: true, isRequired: false, transformFunction: null }, emptyMessageKey: { classPropertyName: "emptyMessageKey", publicName: "emptyMessageKey", isSignal: true, isRequired: false, transformFunction: null }, dropdownUpward: { classPropertyName: "dropdownUpward", publicName: "dropdownUpward", isSignal: true, isRequired: false, transformFunction: null }, showEmptyOption: { classPropertyName: "showEmptyOption", publicName: "showEmptyOption", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", search: "search" }, host: { listeners: { "document:click": "onClickOutside($event)" } }, providers: [
|
|
1909
|
+
{
|
|
1910
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1911
|
+
useExisting: forwardRef(() => SelectComponent),
|
|
1912
|
+
multi: true,
|
|
1913
|
+
},
|
|
1914
|
+
], viewQueries: [{ propertyName: "filterInput", first: true, predicate: ["filterInput"], descendants: true, isSignal: true }, { propertyName: "selectContainer", first: true, predicate: ["selectContainer"], descendants: true, isSignal: true }, { propertyName: "selectTrigger", first: true, predicate: ["selectTrigger"], descendants: true, isSignal: true }, { propertyName: "dropdownContainer", first: true, predicate: ["dropdownContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #selectContainer\n class=\"relative w-full font-sans outline-none\"\n>\n @if (label()) {\n <div class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-300)] mb-1\">\n {{ label() }}\n </div>\n }\n\n <div\n #selectTrigger\n class=\"flex justify-between items-center w-full px-3 py-2 border _shk-border dark:[border-color:var(--shk-surface-700)] rounded-lg _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] transition-colors duration-150 cursor-pointer min-h-[2.5rem] _shk-text-surface-700 dark:[color:var(--shk-surface-200)] focus-within:outline-none focus-within:[background-color:var(--shk-surface-50)] dark:focus-within:[background-color:var(--shk-surface-800)] focus-within:ring-1 focus-within:[--tw-ring-color:var(--shk-primary-500)] focus-within:[border-color:var(--shk-primary-500)] focus:outline-none focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] focus:[border-color:var(--shk-primary-500)] sm:text-sm\"\n [attr.tabindex]=\"isDisabled() ? -1 : 0\"\n (keydown)=\"onKeydown($event)\"\n (click)=\"toggleDropdown()\"\n [class.open]=\"isOpen()\"\n [class.disabled-select]=\"isDisabled()\"\n [class._shk-border-red]=\"shouldShowError()\"\n >\n <div class=\"selected-value truncate\">\n @if (isSearchable() && isOpen()) {\n <input\n #filterInput\n type=\"text\"\n class=\"w-full bg-transparent border-0 outline-none p-0 _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\"\n placeholder=\"Buscar...\"\n [value]=\"searchTerm()\"\n (input)=\"onInputChange($event)\"\n (keydown)=\"handleKeyDown($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n } @else {\n <span\n [class._shk-text-surface-400]=\"!hasSelectedValues()\"\n [class._shk-text-surface-700]=\"hasSelectedValues()\"\n >\n {{ displayValue() }}\n </span>\n }\n </div>\n <div\n class=\"text-xs transition-transform duration-200 shrink-0 ml-2\"\n [class.rotate-180]=\"isOpen()\"\n >\n <svg\n [lucideIcon]=\"getIcon('chevron-down')\"\n class=\"w-4 h-4 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n </div>\n\n @if (isOpen()) {\n <div\n #dropdownContainer\n (keydown)=\"onKeydown($event)\"\n class=\"absolute left-0 right-0 _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border _shk-border dark:[border-color:var(--shk-surface-700)] rounded-lg max-h-[240px] overflow-y-auto z-50 shadow-lg\"\n [class.top-full]=\"!dropdownUpward()\"\n [class.bottom-full]=\"dropdownUpward()\"\n [class.mt-1]=\"!dropdownUpward()\"\n [class.mb-1]=\"dropdownUpward()\"\n >\n @if (showCustomOption()) {\n <div\n class=\"flex items-center gap-2 px-3 py-2 cursor-pointer hover:[background-color:var(--shk-surface-50)] dark:hover:[background-color:var(--shk-surface-700)] text-sm\"\n (click)=\"onCustomOptionClick()\"\n >\n <span class=\"font-semibold _shk-text-primary\">Crear nuevo:</span>\n <span class=\"_shk-text-surface-700 dark:[color:var(--shk-surface-200)] ml-1\">{{ searchTerm() }}</span>\n </div>\n }\n\n @for (option of regularOptions(); track trackByFn($index, option); let i = $index) {\n <div\n class=\"flex items-center gap-2 px-3 py-2 cursor-pointer hover:[background-color:var(--shk-surface-50)] dark:hover:[background-color:var(--shk-surface-700)] text-sm _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\"\n [class.opacity-50]=\"isOptionDisabled(option)\"\n [class.select-option-active]=\"focusedIndex() === i\"\n [class._shk-text-surface-400]=\"option.value === null\"\n (click)=\"!isOptionDisabled(option) && onOptionClick(option)\"\n >\n @if (multiple()) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n [disabled]=\"isOptionDisabled(option)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"!isOptionDisabled(option) && toggleOption(option)\"\n class=\"flex-shrink-0 [accent-color:var(--shk-primary-600)]\"\n />\n {{ option.label }}\n } @else {\n {{ option.label }}\n }\n </div>\n }\n\n <div class=\"px-3 py-2 text-center\">\n @if (isLoading() && regularOptions().length === 0) {\n <div class=\"flex items-center justify-center gap-2 text-sm _shk-text-surface-500 dark:[color:var(--shk-surface-400)]\">\n <svg class=\"spinner\" viewBox=\"0 0 50 50\">\n <circle\n class=\"path\"\n cx=\"25\"\n cy=\"25\"\n r=\"20\"\n fill=\"none\"\n stroke-width=\"5\"\n ></circle>\n </svg>\n Cargando m\u00E1s resultados...\n </div>\n }\n\n @if (!isLoading() && isAllDataLoaded() && regularOptions().length === 0) {\n <div class=\"text-sm _shk-text-surface-400 dark:[color:var(--shk-surface-500)] italic\">\n No se encontraron coincidencias\n </div>\n }\n\n @if (options().length === 0) {\n <div class=\"text-sm _shk-text-surface-400 dark:[color:var(--shk-surface-500)] italic\">\n {{ emptyMessageKey() | transloco }}\n </div>\n }\n </div>\n\n @if (usePagination() && totalPages() > 1) {\n <div class=\"flex justify-between items-center px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-700)]\">\n <button\n class=\"bg-transparent border-0 cursor-pointer px-2 py-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-50 disabled:cursor-not-allowed _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\"\n [disabled]=\"currentPage() === 1\"\n (click)=\"$event.stopPropagation(); previousPage()\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span class=\"text-sm _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\">\n {{ currentPage() }} de {{ totalPages() }}\n </span>\n <button\n class=\"bg-transparent border-0 cursor-pointer px-2 py-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-50 disabled:cursor-not-allowed _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\"\n [disabled]=\"currentPage() === totalPages()\"\n (click)=\"$event.stopPropagation(); nextPage()\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n }\n </div>\n }\n\n @if (shouldShowError()) {\n <div class=\"mt-1 text-xs text-red-500 max-h-20 overflow-y-auto\" tabindex=\"-1\">\n <ul class=\"list-disc pl-4 space-y-1\">\n @for (error of getErrorMessages(); track error) {\n <li>{{ error }}</li>\n }\n </ul>\n </div>\n }\n</div>", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }, { kind: "pipe", type: i1$1.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1915
|
+
}
|
|
1916
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SelectComponent, decorators: [{
|
|
1917
|
+
type: Component,
|
|
1918
|
+
args: [{ selector: 'shk-select', standalone: true, imports: [
|
|
1919
|
+
CommonModule,
|
|
1920
|
+
ReactiveFormsModule,
|
|
1921
|
+
FormsModule,
|
|
1922
|
+
TranslocoModule,
|
|
1923
|
+
LucideDynamicIcon,
|
|
1924
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
1925
|
+
{
|
|
1926
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1927
|
+
useExisting: forwardRef(() => SelectComponent),
|
|
1928
|
+
multi: true,
|
|
1929
|
+
},
|
|
1930
|
+
], template: "<div\n #selectContainer\n class=\"relative w-full font-sans outline-none\"\n>\n @if (label()) {\n <div class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-300)] mb-1\">\n {{ label() }}\n </div>\n }\n\n <div\n #selectTrigger\n class=\"flex justify-between items-center w-full px-3 py-2 border _shk-border dark:[border-color:var(--shk-surface-700)] rounded-lg _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] transition-colors duration-150 cursor-pointer min-h-[2.5rem] _shk-text-surface-700 dark:[color:var(--shk-surface-200)] focus-within:outline-none focus-within:[background-color:var(--shk-surface-50)] dark:focus-within:[background-color:var(--shk-surface-800)] focus-within:ring-1 focus-within:[--tw-ring-color:var(--shk-primary-500)] focus-within:[border-color:var(--shk-primary-500)] focus:outline-none focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] focus:[border-color:var(--shk-primary-500)] sm:text-sm\"\n [attr.tabindex]=\"isDisabled() ? -1 : 0\"\n (keydown)=\"onKeydown($event)\"\n (click)=\"toggleDropdown()\"\n [class.open]=\"isOpen()\"\n [class.disabled-select]=\"isDisabled()\"\n [class._shk-border-red]=\"shouldShowError()\"\n >\n <div class=\"selected-value truncate\">\n @if (isSearchable() && isOpen()) {\n <input\n #filterInput\n type=\"text\"\n class=\"w-full bg-transparent border-0 outline-none p-0 _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\"\n placeholder=\"Buscar...\"\n [value]=\"searchTerm()\"\n (input)=\"onInputChange($event)\"\n (keydown)=\"handleKeyDown($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n } @else {\n <span\n [class._shk-text-surface-400]=\"!hasSelectedValues()\"\n [class._shk-text-surface-700]=\"hasSelectedValues()\"\n >\n {{ displayValue() }}\n </span>\n }\n </div>\n <div\n class=\"text-xs transition-transform duration-200 shrink-0 ml-2\"\n [class.rotate-180]=\"isOpen()\"\n >\n <svg\n [lucideIcon]=\"getIcon('chevron-down')\"\n class=\"w-4 h-4 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"\n ></svg>\n </div>\n </div>\n\n @if (isOpen()) {\n <div\n #dropdownContainer\n (keydown)=\"onKeydown($event)\"\n class=\"absolute left-0 right-0 _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border _shk-border dark:[border-color:var(--shk-surface-700)] rounded-lg max-h-[240px] overflow-y-auto z-50 shadow-lg\"\n [class.top-full]=\"!dropdownUpward()\"\n [class.bottom-full]=\"dropdownUpward()\"\n [class.mt-1]=\"!dropdownUpward()\"\n [class.mb-1]=\"dropdownUpward()\"\n >\n @if (showCustomOption()) {\n <div\n class=\"flex items-center gap-2 px-3 py-2 cursor-pointer hover:[background-color:var(--shk-surface-50)] dark:hover:[background-color:var(--shk-surface-700)] text-sm\"\n (click)=\"onCustomOptionClick()\"\n >\n <span class=\"font-semibold _shk-text-primary\">Crear nuevo:</span>\n <span class=\"_shk-text-surface-700 dark:[color:var(--shk-surface-200)] ml-1\">{{ searchTerm() }}</span>\n </div>\n }\n\n @for (option of regularOptions(); track trackByFn($index, option); let i = $index) {\n <div\n class=\"flex items-center gap-2 px-3 py-2 cursor-pointer hover:[background-color:var(--shk-surface-50)] dark:hover:[background-color:var(--shk-surface-700)] text-sm _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\"\n [class.opacity-50]=\"isOptionDisabled(option)\"\n [class.select-option-active]=\"focusedIndex() === i\"\n [class._shk-text-surface-400]=\"option.value === null\"\n (click)=\"!isOptionDisabled(option) && onOptionClick(option)\"\n >\n @if (multiple()) {\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(option)\"\n [disabled]=\"isOptionDisabled(option)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"!isOptionDisabled(option) && toggleOption(option)\"\n class=\"flex-shrink-0 [accent-color:var(--shk-primary-600)]\"\n />\n {{ option.label }}\n } @else {\n {{ option.label }}\n }\n </div>\n }\n\n <div class=\"px-3 py-2 text-center\">\n @if (isLoading() && regularOptions().length === 0) {\n <div class=\"flex items-center justify-center gap-2 text-sm _shk-text-surface-500 dark:[color:var(--shk-surface-400)]\">\n <svg class=\"spinner\" viewBox=\"0 0 50 50\">\n <circle\n class=\"path\"\n cx=\"25\"\n cy=\"25\"\n r=\"20\"\n fill=\"none\"\n stroke-width=\"5\"\n ></circle>\n </svg>\n Cargando m\u00E1s resultados...\n </div>\n }\n\n @if (!isLoading() && isAllDataLoaded() && regularOptions().length === 0) {\n <div class=\"text-sm _shk-text-surface-400 dark:[color:var(--shk-surface-500)] italic\">\n No se encontraron coincidencias\n </div>\n }\n\n @if (options().length === 0) {\n <div class=\"text-sm _shk-text-surface-400 dark:[color:var(--shk-surface-500)] italic\">\n {{ emptyMessageKey() | transloco }}\n </div>\n }\n </div>\n\n @if (usePagination() && totalPages() > 1) {\n <div class=\"flex justify-between items-center px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-700)]\">\n <button\n class=\"bg-transparent border-0 cursor-pointer px-2 py-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-50 disabled:cursor-not-allowed _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\"\n [disabled]=\"currentPage() === 1\"\n (click)=\"$event.stopPropagation(); previousPage()\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span class=\"text-sm _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\">\n {{ currentPage() }} de {{ totalPages() }}\n </span>\n <button\n class=\"bg-transparent border-0 cursor-pointer px-2 py-1 rounded hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] disabled:opacity-50 disabled:cursor-not-allowed _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\"\n [disabled]=\"currentPage() === totalPages()\"\n (click)=\"$event.stopPropagation(); nextPage()\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n }\n </div>\n }\n\n @if (shouldShowError()) {\n <div class=\"mt-1 text-xs text-red-500 max-h-20 overflow-y-auto\" tabindex=\"-1\">\n <ul class=\"list-disc pl-4 space-y-1\">\n @for (error of getErrorMessages(); track error) {\n <li>{{ error }}</li>\n }\n </ul>\n </div>\n }\n</div>" }]
|
|
1931
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], validCombinations: [{ type: i0.Input, args: [{ isSignal: true, alias: "validCombinations", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], isAllDataLoaded: [{ type: i0.Input, args: [{ isSignal: true, alias: "isAllDataLoaded", required: false }] }], allowCustomEntries: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowCustomEntries", required: false }] }], formGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "formGroup", required: false }] }], isSearchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isSearchable", required: false }] }], itemsPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemsPerPage", required: false }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], usePagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "usePagination", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], errorMessages: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessages", required: false }] }], preserveSearchOnLoad: [{ type: i0.Input, args: [{ isSignal: true, alias: "preserveSearchOnLoad", required: false }] }], emptyMessageKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessageKey", required: false }] }], dropdownUpward: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropdownUpward", required: false }] }], showEmptyOption: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEmptyOption", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], search: [{ type: i0.Output, args: ["search"] }], filterInput: [{ type: i0.ViewChild, args: ['filterInput', { isSignal: true }] }], selectContainer: [{ type: i0.ViewChild, args: ['selectContainer', { isSignal: true }] }], selectTrigger: [{ type: i0.ViewChild, args: ['selectTrigger', { isSignal: true }] }], dropdownContainer: [{ type: i0.ViewChild, args: ['dropdownContainer', { isSignal: true }] }], onClickOutside: [{
|
|
1932
|
+
type: HostListener,
|
|
1933
|
+
args: ['document:click', ['$event']]
|
|
1934
|
+
}] } });
|
|
1935
|
+
|
|
1936
|
+
const icons$1 = {
|
|
1937
|
+
upload: LucideUpload,
|
|
1938
|
+
file: LucideFile,
|
|
1939
|
+
x: LucideX,
|
|
1940
|
+
};
|
|
1941
|
+
class FileUploadComponent {
|
|
1942
|
+
getIcon = (name) => icons$1[name] ?? LucideUpload;
|
|
1943
|
+
transloco = inject(TranslocoService);
|
|
1944
|
+
label = input('Cargar archivo', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1945
|
+
accept = input('image/*', ...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
|
|
1946
|
+
maxFileSize = input(2 * 1024 * 1024, ...(ngDevMode ? [{ debugName: "maxFileSize" }] : /* istanbul ignore next */ []));
|
|
1947
|
+
maxFiles = input(0, ...(ngDevMode ? [{ debugName: "maxFiles" }] : /* istanbul ignore next */ []));
|
|
1948
|
+
fileUploadText = input('Seleccionar archivo', ...(ngDevMode ? [{ debugName: "fileUploadText" }] : /* istanbul ignore next */ []));
|
|
1949
|
+
fileRecommendation = input('', ...(ngDevMode ? [{ debugName: "fileRecommendation" }] : /* istanbul ignore next */ []));
|
|
1950
|
+
changeFilesText = input('Cambiar archivos', ...(ngDevMode ? [{ debugName: "changeFilesText" }] : /* istanbul ignore next */ []));
|
|
1951
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
|
|
1952
|
+
fileSelected = output();
|
|
1953
|
+
fileRemoved = output();
|
|
1954
|
+
fileError = output();
|
|
1955
|
+
fileWarning = output();
|
|
1956
|
+
selectedFiles = signal([], ...(ngDevMode ? [{ debugName: "selectedFiles" }] : /* istanbul ignore next */ []));
|
|
1957
|
+
errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
1958
|
+
isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : /* istanbul ignore next */ []));
|
|
1959
|
+
previewUrls = new Map();
|
|
1960
|
+
canAddMore = computed(() => {
|
|
1961
|
+
if (!this.multiple())
|
|
1962
|
+
return this.selectedFiles().length === 0;
|
|
1963
|
+
const max = this.maxFiles();
|
|
1964
|
+
if (max <= 0)
|
|
1965
|
+
return true;
|
|
1966
|
+
return this.selectedFiles().length < max;
|
|
1967
|
+
}, ...(ngDevMode ? [{ debugName: "canAddMore" }] : /* istanbul ignore next */ []));
|
|
1968
|
+
maxReached = computed(() => {
|
|
1969
|
+
const max = this.maxFiles();
|
|
1970
|
+
if (max <= 0)
|
|
1971
|
+
return false;
|
|
1972
|
+
return this.selectedFiles().length >= max;
|
|
1973
|
+
}, ...(ngDevMode ? [{ debugName: "maxReached" }] : /* istanbul ignore next */ []));
|
|
1974
|
+
isImageFile(file) {
|
|
1975
|
+
return file.type.startsWith('image/');
|
|
1976
|
+
}
|
|
1977
|
+
previewUrl(file) {
|
|
1978
|
+
if (!this.previewUrls.has(file)) {
|
|
1979
|
+
this.previewUrls.set(file, URL.createObjectURL(file));
|
|
1980
|
+
}
|
|
1981
|
+
return this.previewUrls.get(file) ?? '';
|
|
1982
|
+
}
|
|
1983
|
+
onDragOver(event) {
|
|
1984
|
+
event.preventDefault();
|
|
1985
|
+
event.stopPropagation();
|
|
1986
|
+
this.isDragOver.set(true);
|
|
1987
|
+
}
|
|
1988
|
+
onDragLeave(event) {
|
|
1989
|
+
event.preventDefault();
|
|
1990
|
+
event.stopPropagation();
|
|
1991
|
+
this.isDragOver.set(false);
|
|
1992
|
+
}
|
|
1993
|
+
onDrop(event) {
|
|
1994
|
+
event.preventDefault();
|
|
1995
|
+
event.stopPropagation();
|
|
1996
|
+
this.isDragOver.set(false);
|
|
1997
|
+
const files = event.dataTransfer?.files;
|
|
1998
|
+
if (files && files.length > 0) {
|
|
1999
|
+
this.processFiles(Array.from(files));
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
onFileChange(event) {
|
|
2003
|
+
const input = event.target;
|
|
2004
|
+
const files = input.files;
|
|
2005
|
+
if (files && files.length > 0) {
|
|
2006
|
+
this.processFiles(Array.from(files));
|
|
2007
|
+
}
|
|
2008
|
+
input.value = '';
|
|
2009
|
+
}
|
|
2010
|
+
processFiles(files) {
|
|
2011
|
+
this.errorMessage.set('');
|
|
2012
|
+
const validFiles = [];
|
|
2013
|
+
for (const file of files) {
|
|
2014
|
+
const error = this.validateFile(file);
|
|
2015
|
+
if (error) {
|
|
2016
|
+
this.fileError.emit(error);
|
|
2017
|
+
this.errorMessage.set(error.message);
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
const warning = this.checkSizeWarning(file);
|
|
2021
|
+
if (warning) {
|
|
2022
|
+
this.fileWarning.emit(warning);
|
|
2023
|
+
}
|
|
2024
|
+
validFiles.push(file);
|
|
2025
|
+
}
|
|
2026
|
+
const max = this.maxFiles();
|
|
2027
|
+
if (max > 0) {
|
|
2028
|
+
const currentCount = this.selectedFiles().length;
|
|
2029
|
+
const allowed = max - currentCount;
|
|
2030
|
+
if (validFiles.length > allowed) {
|
|
2031
|
+
const errorMsg = this.transloco.translate('fileUpload.maxFiles', undefined, `Máximo ${max} archivos permitidos`);
|
|
2032
|
+
this.errorMessage.set(errorMsg);
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
if (this.multiple()) {
|
|
2037
|
+
this.selectedFiles.set([...this.selectedFiles(), ...validFiles]);
|
|
2038
|
+
}
|
|
2039
|
+
else {
|
|
2040
|
+
this.clearPreviews();
|
|
2041
|
+
this.selectedFiles.set(validFiles);
|
|
2042
|
+
}
|
|
2043
|
+
this.fileSelected.emit(this.selectedFiles());
|
|
2044
|
+
}
|
|
2045
|
+
validateFile(file) {
|
|
2046
|
+
const acceptStr = this.accept();
|
|
2047
|
+
if (acceptStr && acceptStr !== '*') {
|
|
2048
|
+
const acceptTypes = acceptStr.split(',').map((t) => t.trim());
|
|
2049
|
+
const valid = acceptTypes.some((type) => {
|
|
2050
|
+
if (type.endsWith('/*')) {
|
|
2051
|
+
const category = type.replace('/*', '');
|
|
2052
|
+
return file.type.startsWith(category);
|
|
2053
|
+
}
|
|
2054
|
+
if (type.startsWith('.')) {
|
|
2055
|
+
const ext = type.slice(1).toLowerCase();
|
|
2056
|
+
return file.name.toLowerCase().endsWith('.' + ext);
|
|
2057
|
+
}
|
|
2058
|
+
if (type.includes('/')) {
|
|
2059
|
+
return file.type === type;
|
|
2060
|
+
}
|
|
2061
|
+
return false;
|
|
2062
|
+
});
|
|
2063
|
+
if (!valid) {
|
|
2064
|
+
return {
|
|
2065
|
+
type: 'type',
|
|
2066
|
+
message: this.transloco.translate('fileUpload.invalidType', { accept: acceptStr }, `Tipo de archivo no válido. Formatos aceptados: ${acceptStr}`),
|
|
2067
|
+
file,
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
if (this.maxFileSize() > 0 && file.size > this.maxFileSize()) {
|
|
2072
|
+
return {
|
|
2073
|
+
type: 'size',
|
|
2074
|
+
message: this.transloco.translate('fileUpload.fileTooLarge', { size: this.formatFileSize(this.maxFileSize()) }, `El archivo excede el tamaño máximo de ${this.formatFileSize(this.maxFileSize())}`),
|
|
2075
|
+
file,
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
return null;
|
|
2079
|
+
}
|
|
2080
|
+
checkSizeWarning(file) {
|
|
2081
|
+
if (this.maxFileSize() > 0 && file.size > this.maxFileSize() * 0.9) {
|
|
2082
|
+
return {
|
|
2083
|
+
type: 'size',
|
|
2084
|
+
message: this.transloco.translate('fileUpload.sizeWarning', { size: this.formatFileSize(this.maxFileSize()) }, `El archivo está cerca del límite de tamaño (${this.formatFileSize(this.maxFileSize())})`),
|
|
2085
|
+
file,
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
return null;
|
|
2089
|
+
}
|
|
2090
|
+
removeFile(file) {
|
|
2091
|
+
const url = this.previewUrls.get(file);
|
|
2092
|
+
if (url) {
|
|
2093
|
+
URL.revokeObjectURL(url);
|
|
2094
|
+
this.previewUrls.delete(file);
|
|
2095
|
+
}
|
|
2096
|
+
this.selectedFiles.update((files) => files.filter((f) => f !== file));
|
|
2097
|
+
this.errorMessage.set('');
|
|
2098
|
+
if (this.selectedFiles().length === 0) {
|
|
2099
|
+
this.fileRemoved.emit();
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
changeFiles() {
|
|
2103
|
+
this.clearPreviews();
|
|
2104
|
+
this.selectedFiles.set([]);
|
|
2105
|
+
this.errorMessage.set('');
|
|
2106
|
+
}
|
|
2107
|
+
clearPreviews() {
|
|
2108
|
+
for (const [file, url] of this.previewUrls) {
|
|
2109
|
+
URL.revokeObjectURL(url);
|
|
2110
|
+
}
|
|
2111
|
+
this.previewUrls.clear();
|
|
2112
|
+
}
|
|
2113
|
+
formatFileSize(bytes) {
|
|
2114
|
+
if (bytes < 1024)
|
|
2115
|
+
return bytes + ' B';
|
|
2116
|
+
if (bytes < 1024 * 1024)
|
|
2117
|
+
return (bytes / 1024).toFixed(1) + ' KB';
|
|
2118
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
2119
|
+
}
|
|
2120
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: FileUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2121
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: FileUploadComponent, isStandalone: true, selector: "shk-file-upload", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, fileUploadText: { classPropertyName: "fileUploadText", publicName: "fileUploadText", isSignal: true, isRequired: false, transformFunction: null }, fileRecommendation: { classPropertyName: "fileRecommendation", publicName: "fileRecommendation", isSignal: true, isRequired: false, transformFunction: null }, changeFilesText: { classPropertyName: "changeFilesText", publicName: "changeFilesText", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileSelected: "fileSelected", fileRemoved: "fileRemoved", fileError: "fileError", fileWarning: "fileWarning" }, ngImport: i0, template: "<div class=\"flex flex-col gap-2\">\n @if (label()) {\n <label class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-300)]\">\n {{ label() }}\n </label>\n }\n\n @if (canAddMore()) {\n <div\n class=\"relative border-2 border-dashed _shk-border dark:[border-color:var(--shk-surface-700)] rounded-lg p-4 transition-colors hover:[border-color:var(--shk-primary-400)] dark:hover:[border-color:var(--shk-primary-500)]\"\n [class.border-red-400]=\"errorMessage()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n >\n <input\n #fileInput\n type=\"file\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n (change)=\"onFileChange($event)\"\n class=\"hidden\"\n />\n\n <div\n class=\"flex flex-col items-center gap-2 cursor-pointer\"\n (click)=\"fileInput.click()\"\n [class.drag-over]=\"isDragOver()\"\n >\n <svg [lucideIcon]=\"getIcon('upload')\" class=\"w-8 h-8 _shk-icon-subtle dark:[color:var(--shk-surface-600)]\"></svg>\n <span class=\"text-sm _shk-text-surface-500 dark:[color:var(--shk-surface-400)]\">\n {{ fileUploadText() }}\n </span>\n @if (fileRecommendation()) {\n <span class=\"text-xs _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\">\n {{ fileRecommendation() }}\n </span>\n }\n </div>\n </div>\n }\n\n @if (maxReached()) {\n <div class=\"text-xs _shk-text-surface-400 dark:[color:var(--shk-surface-500)] text-center py-2\">\n M\u00E1ximo {{ maxFiles() }} archivos alcanzado\n </div>\n }\n\n @if (selectedFiles().length > 0) {\n <div class=\"flex flex-wrap gap-3\">\n @for (file of selectedFiles(); track file.name + file.size + file.lastModified) {\n <div class=\"relative group\">\n @if (isImageFile(file)) {\n <img\n [src]=\"previewUrl(file)\"\n [alt]=\"file.name\"\n class=\"w-20 h-20 object-cover rounded-lg _shk-border dark:[border-color:var(--shk-surface-700)]\"\n />\n } @else {\n <div class=\"w-20 h-20 rounded-lg _shk-border dark:[border-color:var(--shk-surface-700)] flex flex-col items-center justify-center _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] gap-1\">\n <svg [lucideIcon]=\"getIcon('file')\" class=\"w-6 h-6 _shk-text-surface-400\"></svg>\n <span class=\"text-[10px] _shk-text-surface-400 truncate max-w-[70px] px-1 text-center\">{{ file.name }}</span>\n </div>\n }\n <button\n type=\"button\"\n (click)=\"removeFile(file)\"\n class=\"absolute -top-1.5 -right-1.5 w-5 h-5 bg-red-500 text-white rounded-full text-xs flex items-center justify-center opacity-0 group-hover:opacity-100 transition shadow\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3 h-3\"></svg>\n </button>\n </div>\n }\n </div>\n @if (!multiple()) {\n <button\n type=\"button\"\n (click)=\"changeFiles()\"\n class=\"text-xs _shk-text-primary hover:[color:var(--shk-primary-700)] dark:[color:var(--shk-primary-400)] dark:hover:[color:var(--shk-primary-300)] underline cursor-pointer bg-transparent border-0\"\n >\n {{ changeFilesText() }}\n </button>\n }\n }\n\n @if (errorMessage()) {\n <p class=\"text-xs _shk-text-red-500\">{{ errorMessage() }}</p>\n }\n</div>", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2122
|
+
}
|
|
2123
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: FileUploadComponent, decorators: [{
|
|
2124
|
+
type: Component,
|
|
2125
|
+
args: [{ selector: 'shk-file-upload', standalone: true, imports: [CommonModule, LucideDynamicIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex flex-col gap-2\">\n @if (label()) {\n <label class=\"text-sm font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-300)]\">\n {{ label() }}\n </label>\n }\n\n @if (canAddMore()) {\n <div\n class=\"relative border-2 border-dashed _shk-border dark:[border-color:var(--shk-surface-700)] rounded-lg p-4 transition-colors hover:[border-color:var(--shk-primary-400)] dark:hover:[border-color:var(--shk-primary-500)]\"\n [class.border-red-400]=\"errorMessage()\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n >\n <input\n #fileInput\n type=\"file\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n (change)=\"onFileChange($event)\"\n class=\"hidden\"\n />\n\n <div\n class=\"flex flex-col items-center gap-2 cursor-pointer\"\n (click)=\"fileInput.click()\"\n [class.drag-over]=\"isDragOver()\"\n >\n <svg [lucideIcon]=\"getIcon('upload')\" class=\"w-8 h-8 _shk-icon-subtle dark:[color:var(--shk-surface-600)]\"></svg>\n <span class=\"text-sm _shk-text-surface-500 dark:[color:var(--shk-surface-400)]\">\n {{ fileUploadText() }}\n </span>\n @if (fileRecommendation()) {\n <span class=\"text-xs _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\">\n {{ fileRecommendation() }}\n </span>\n }\n </div>\n </div>\n }\n\n @if (maxReached()) {\n <div class=\"text-xs _shk-text-surface-400 dark:[color:var(--shk-surface-500)] text-center py-2\">\n M\u00E1ximo {{ maxFiles() }} archivos alcanzado\n </div>\n }\n\n @if (selectedFiles().length > 0) {\n <div class=\"flex flex-wrap gap-3\">\n @for (file of selectedFiles(); track file.name + file.size + file.lastModified) {\n <div class=\"relative group\">\n @if (isImageFile(file)) {\n <img\n [src]=\"previewUrl(file)\"\n [alt]=\"file.name\"\n class=\"w-20 h-20 object-cover rounded-lg _shk-border dark:[border-color:var(--shk-surface-700)]\"\n />\n } @else {\n <div class=\"w-20 h-20 rounded-lg _shk-border dark:[border-color:var(--shk-surface-700)] flex flex-col items-center justify-center _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] gap-1\">\n <svg [lucideIcon]=\"getIcon('file')\" class=\"w-6 h-6 _shk-text-surface-400\"></svg>\n <span class=\"text-[10px] _shk-text-surface-400 truncate max-w-[70px] px-1 text-center\">{{ file.name }}</span>\n </div>\n }\n <button\n type=\"button\"\n (click)=\"removeFile(file)\"\n class=\"absolute -top-1.5 -right-1.5 w-5 h-5 bg-red-500 text-white rounded-full text-xs flex items-center justify-center opacity-0 group-hover:opacity-100 transition shadow\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3 h-3\"></svg>\n </button>\n </div>\n }\n </div>\n @if (!multiple()) {\n <button\n type=\"button\"\n (click)=\"changeFiles()\"\n class=\"text-xs _shk-text-primary hover:[color:var(--shk-primary-700)] dark:[color:var(--shk-primary-400)] dark:hover:[color:var(--shk-primary-300)] underline cursor-pointer bg-transparent border-0\"\n >\n {{ changeFilesText() }}\n </button>\n }\n }\n\n @if (errorMessage()) {\n <p class=\"text-xs _shk-text-red-500\">{{ errorMessage() }}</p>\n }\n</div>" }]
|
|
2126
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], fileUploadText: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileUploadText", required: false }] }], fileRecommendation: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileRecommendation", required: false }] }], changeFilesText: [{ type: i0.Input, args: [{ isSignal: true, alias: "changeFilesText", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }], fileRemoved: [{ type: i0.Output, args: ["fileRemoved"] }], fileError: [{ type: i0.Output, args: ["fileError"] }], fileWarning: [{ type: i0.Output, args: ["fileWarning"] }] } });
|
|
2127
|
+
|
|
2128
|
+
const icons = {
|
|
2129
|
+
'chevrons-left': LucideChevronsLeft,
|
|
2130
|
+
'chevrons-right': LucideChevronsRight,
|
|
2131
|
+
'chevron-left': LucideChevronLeft,
|
|
2132
|
+
'chevron-right': LucideChevronRight,
|
|
2133
|
+
'arrow-up': LucideArrowUp,
|
|
2134
|
+
'arrow-down': LucideArrowDown,
|
|
2135
|
+
'arrow-up-down': LucideArrowUpDown,
|
|
2136
|
+
search: LucideSearch,
|
|
2137
|
+
x: LucideX,
|
|
2138
|
+
loader: LucideLoader,
|
|
2139
|
+
inbox: LucideInbox,
|
|
2140
|
+
filter: LucideFunnel,
|
|
2141
|
+
funnel: LucideFunnel,
|
|
2142
|
+
pencil: LucidePencil,
|
|
2143
|
+
edit: LucidePencil,
|
|
2144
|
+
'trash-2': LucideTrash2,
|
|
2145
|
+
delete: LucideTrash2,
|
|
2146
|
+
eye: LucideEye,
|
|
2147
|
+
view: LucideEye,
|
|
2148
|
+
key: LucideKey,
|
|
2149
|
+
'change-password': LucideKey,
|
|
2150
|
+
'user-pen': LucideUserPen,
|
|
2151
|
+
'edit-user': LucideUserPen,
|
|
2152
|
+
'toggle-right': LucideToggleRight,
|
|
2153
|
+
activate: LucideToggleRight,
|
|
2154
|
+
'toggle-left': LucideToggleLeft,
|
|
2155
|
+
deactivate: LucideToggleLeft,
|
|
2156
|
+
download: LucideDownload,
|
|
2157
|
+
export: LucideDownload,
|
|
2158
|
+
upload: LucideUpload,
|
|
2159
|
+
import: LucideUpload,
|
|
2160
|
+
plus: LucidePlus,
|
|
2161
|
+
add: LucidePlus,
|
|
2162
|
+
'circle-plus': LucideCirclePlus,
|
|
2163
|
+
'eye-off': LucideEyeOff,
|
|
2164
|
+
'refresh-cw': LucideRefreshCw,
|
|
2165
|
+
'file-spreadsheet': LucideFileSpreadsheet,
|
|
2166
|
+
presentation: LucidePresentation,
|
|
2167
|
+
file: LucideFile,
|
|
2168
|
+
'file-text': LucideFileText,
|
|
2169
|
+
archive: LucideArchive,
|
|
2170
|
+
'circle-check': LucideCircleCheck,
|
|
2171
|
+
'circle-x': LucideCircleX,
|
|
2172
|
+
check: LucideCheck,
|
|
2173
|
+
copy: LucideCopy,
|
|
2174
|
+
'share-2': LucideShare2,
|
|
2175
|
+
play: LucidePlay,
|
|
2176
|
+
video: LucideVideo,
|
|
2177
|
+
star: LucideStar,
|
|
2178
|
+
clock: LucideClock,
|
|
2179
|
+
settings: LucideSettings,
|
|
2180
|
+
'log-out': LucideLogOut,
|
|
2181
|
+
logout: LucideLogOut,
|
|
2182
|
+
user: LucideUser,
|
|
2183
|
+
'user-check': LucideUserCheck,
|
|
2184
|
+
'user-round-x': LucideUserRoundX,
|
|
2185
|
+
'user-plus': LucideUserPlus,
|
|
2186
|
+
building: LucideBuilding,
|
|
2187
|
+
landmark: LucideLandmark,
|
|
2188
|
+
package: LucidePackage,
|
|
2189
|
+
box: LucideBox,
|
|
2190
|
+
tags: LucideTags,
|
|
2191
|
+
tag: LucideTag,
|
|
2192
|
+
truck: LucideTruck,
|
|
2193
|
+
users: LucideUsers,
|
|
2194
|
+
receipt: LucideReceipt,
|
|
2195
|
+
bot: LucideBot,
|
|
2196
|
+
'line-chart': LucideLineChart,
|
|
2197
|
+
'dollar-sign': LucideDollarSign,
|
|
2198
|
+
percent: LucidePercent,
|
|
2199
|
+
calculator: LucideCalculator,
|
|
2200
|
+
'shield-check': LucideShieldCheck,
|
|
2201
|
+
'user-cog': LucideUserCog,
|
|
2202
|
+
warehouse: LucideWarehouse,
|
|
2203
|
+
list: LucideList,
|
|
2204
|
+
};
|
|
2205
|
+
function getIcon(name) {
|
|
2206
|
+
return icons[name] ?? LucidePlus;
|
|
2207
|
+
}
|
|
2208
|
+
class TableComponent {
|
|
2209
|
+
fb = inject(FormBuilder);
|
|
2210
|
+
cdr = inject(ChangeDetectorRef);
|
|
2211
|
+
transloco = inject(TranslocoService);
|
|
2212
|
+
getIcon = getIcon;
|
|
2213
|
+
actionsHeaderLabel = toSignal(this.transloco.selectTranslate('common.actions'), { initialValue: 'Acciones' });
|
|
2214
|
+
data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
2215
|
+
columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
2216
|
+
rowsPerPage = input(10, ...(ngDevMode ? [{ debugName: "rowsPerPage" }] : /* istanbul ignore next */ []));
|
|
2217
|
+
rowsPerPageOptions = input([10, 25, 50], ...(ngDevMode ? [{ debugName: "rowsPerPageOptions" }] : /* istanbul ignore next */ []));
|
|
2218
|
+
loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
2219
|
+
showActionRow = input(true, ...(ngDevMode ? [{ debugName: "showActionRow" }] : /* istanbul ignore next */ []));
|
|
2220
|
+
customTemplates = input({}, ...(ngDevMode ? [{ debugName: "customTemplates" }] : /* istanbul ignore next */ []));
|
|
2221
|
+
headerActions = input([], ...(ngDevMode ? [{ debugName: "headerActions" }] : /* istanbul ignore next */ []));
|
|
2222
|
+
rowActions = input([], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
|
|
2223
|
+
hasShadow = input(true, ...(ngDevMode ? [{ debugName: "hasShadow" }] : /* istanbul ignore next */ []));
|
|
2224
|
+
defaultSortField = input('', ...(ngDevMode ? [{ debugName: "defaultSortField" }] : /* istanbul ignore next */ []));
|
|
2225
|
+
defaultSortOrder = input(1, ...(ngDevMode ? [{ debugName: "defaultSortOrder" }] : /* istanbul ignore next */ []));
|
|
2226
|
+
showSearch = input(true, ...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
|
|
2227
|
+
searchPlaceholder = input('', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
|
|
2228
|
+
emptyMessage = input('', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
|
|
2229
|
+
serverSide = input(false, ...(ngDevMode ? [{ debugName: "serverSide" }] : /* istanbul ignore next */ []));
|
|
2230
|
+
totalRecords = input(0, ...(ngDevMode ? [{ debugName: "totalRecords" }] : /* istanbul ignore next */ []));
|
|
2231
|
+
filters = input([], ...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
|
|
2232
|
+
activeFilter = input('', ...(ngDevMode ? [{ debugName: "activeFilter" }] : /* istanbul ignore next */ []));
|
|
2233
|
+
refresh = output();
|
|
2234
|
+
pageChange = output();
|
|
2235
|
+
filterChange = output();
|
|
2236
|
+
searchChange = output();
|
|
2237
|
+
filterClick = output();
|
|
2238
|
+
originalData = signal([], ...(ngDevMode ? [{ debugName: "originalData" }] : /* istanbul ignore next */ []));
|
|
2239
|
+
searchedData = signal([], ...(ngDevMode ? [{ debugName: "searchedData" }] : /* istanbul ignore next */ []));
|
|
2240
|
+
filteredData = signal([], ...(ngDevMode ? [{ debugName: "filteredData" }] : /* istanbul ignore next */ []));
|
|
2241
|
+
displayedData = signal([], ...(ngDevMode ? [{ debugName: "displayedData" }] : /* istanbul ignore next */ []));
|
|
2242
|
+
localTotalRecords = signal(0, ...(ngDevMode ? [{ debugName: "localTotalRecords" }] : /* istanbul ignore next */ []));
|
|
2243
|
+
first = signal(0, ...(ngDevMode ? [{ debugName: "first" }] : /* istanbul ignore next */ []));
|
|
2244
|
+
showFilterInput = signal({}, ...(ngDevMode ? [{ debugName: "showFilterInput" }] : /* istanbul ignore next */ []));
|
|
2245
|
+
searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
|
|
2246
|
+
sortField = signal('', ...(ngDevMode ? [{ debugName: "sortField" }] : /* istanbul ignore next */ []));
|
|
2247
|
+
sortOrder = signal(1, ...(ngDevMode ? [{ debugName: "sortOrder" }] : /* istanbul ignore next */ []));
|
|
2248
|
+
rowsPerPageLocal = signal(10, ...(ngDevMode ? [{ debugName: "rowsPerPageLocal" }] : /* istanbul ignore next */ []));
|
|
2249
|
+
totalPages = computed(() => Math.max(1, Math.ceil(this.effectiveTotalRecords() / this.rowsPerPageLocal())), ...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
|
|
2250
|
+
currentPage = computed(() => Math.floor(this.first() / this.rowsPerPageLocal()) + 1, ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
|
|
2251
|
+
columnFiltersForm;
|
|
2252
|
+
filterSubject = new Subject();
|
|
2253
|
+
columnsWithActions = computed(() => {
|
|
2254
|
+
const columnsArray = this.columns();
|
|
2255
|
+
const showAction = this.showActionRow();
|
|
2256
|
+
const rowActionsArray = this.rowActions();
|
|
2257
|
+
const translatedHeader = this.actionsHeaderLabel();
|
|
2258
|
+
if (showAction && rowActionsArray.length > 0) {
|
|
2259
|
+
const actionsWidth = rowActionsArray.length <= 2
|
|
2260
|
+
? '110px'
|
|
2261
|
+
: rowActionsArray.length <= 3
|
|
2262
|
+
? '130px'
|
|
2263
|
+
: '170px';
|
|
2264
|
+
return [
|
|
2265
|
+
{
|
|
2266
|
+
field: 'actions',
|
|
2267
|
+
header: translatedHeader || 'Acciones',
|
|
2268
|
+
width: actionsWidth,
|
|
2269
|
+
defaultSort: true,
|
|
2270
|
+
},
|
|
2271
|
+
...columnsArray,
|
|
2272
|
+
];
|
|
2273
|
+
}
|
|
2274
|
+
return [...columnsArray];
|
|
2275
|
+
}, ...(ngDevMode ? [{ debugName: "columnsWithActions" }] : /* istanbul ignore next */ []));
|
|
2276
|
+
effectiveTotalRecords = computed(() => {
|
|
2277
|
+
return this.serverSide() ? this.totalRecords() : this.localTotalRecords();
|
|
2278
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveTotalRecords" }] : /* istanbul ignore next */ []));
|
|
2279
|
+
effectiveDisplayedData = computed(() => {
|
|
2280
|
+
return this.serverSide() ? this.data() : this.displayedData();
|
|
2281
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveDisplayedData" }] : /* istanbul ignore next */ []));
|
|
2282
|
+
activeFilters = computed(() => {
|
|
2283
|
+
const form = this.columnFiltersForm;
|
|
2284
|
+
if (!form)
|
|
2285
|
+
return {};
|
|
2286
|
+
const active = {};
|
|
2287
|
+
Object.keys(form.controls).forEach((field) => {
|
|
2288
|
+
const value = form.get(field)?.value;
|
|
2289
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
2290
|
+
active[field] = value;
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2293
|
+
return active;
|
|
2294
|
+
}, ...(ngDevMode ? [{ debugName: "activeFilters" }] : /* istanbul ignore next */ []));
|
|
2295
|
+
constructor() {
|
|
2296
|
+
this.columnFiltersForm = this.fb.group({});
|
|
2297
|
+
this.filterSubject.pipe(debounceTime(300)).subscribe(() => {
|
|
2298
|
+
if (this.serverSide()) {
|
|
2299
|
+
this.emitFilterChange();
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
effect(() => {
|
|
2303
|
+
const newData = this.data();
|
|
2304
|
+
untracked(() => {
|
|
2305
|
+
this.originalData.set([...newData]);
|
|
2306
|
+
if (!this.serverSide()) {
|
|
2307
|
+
this.applyFilters();
|
|
2308
|
+
}
|
|
2309
|
+
});
|
|
2310
|
+
});
|
|
2311
|
+
effect(() => {
|
|
2312
|
+
const cols = this.columns();
|
|
2313
|
+
untracked(() => {
|
|
2314
|
+
this.setupColumnFilters(cols);
|
|
2315
|
+
});
|
|
2316
|
+
});
|
|
2317
|
+
effect(() => {
|
|
2318
|
+
const field = this.defaultSortField();
|
|
2319
|
+
const order = this.defaultSortOrder();
|
|
2320
|
+
if (field) {
|
|
2321
|
+
untracked(() => {
|
|
2322
|
+
this.sortField.set(field);
|
|
2323
|
+
this.sortOrder.set(order);
|
|
2324
|
+
if (!this.serverSide()) {
|
|
2325
|
+
this.applySort();
|
|
2326
|
+
this.updateDisplayedData();
|
|
2327
|
+
}
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
});
|
|
2331
|
+
effect(() => {
|
|
2332
|
+
const activeFilters = this.activeFilters();
|
|
2333
|
+
untracked(() => {
|
|
2334
|
+
if (!this.serverSide()) {
|
|
2335
|
+
this.applyFilters();
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
});
|
|
2339
|
+
effect(() => {
|
|
2340
|
+
const query = this.searchQuery();
|
|
2341
|
+
untracked(() => {
|
|
2342
|
+
if (!this.serverSide()) {
|
|
2343
|
+
this.applySearch();
|
|
2344
|
+
}
|
|
2345
|
+
});
|
|
2346
|
+
});
|
|
2347
|
+
effect(() => {
|
|
2348
|
+
this.rowsPerPageLocal.set(this.rowsPerPage());
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
setupColumnFilters(cols) {
|
|
2352
|
+
const filterControls = {};
|
|
2353
|
+
const filterVisibility = {};
|
|
2354
|
+
cols.forEach((col) => {
|
|
2355
|
+
if (col.filter) {
|
|
2356
|
+
filterControls[col.field] = [col.filterType === 'select' ? null : ''];
|
|
2357
|
+
filterVisibility[col.field] = false;
|
|
2358
|
+
}
|
|
2359
|
+
});
|
|
2360
|
+
this.columnFiltersForm = this.fb.group(filterControls);
|
|
2361
|
+
this.showFilterInput.set(filterVisibility);
|
|
2362
|
+
Object.keys(this.columnFiltersForm.controls).forEach((field) => {
|
|
2363
|
+
this.columnFiltersForm.get(field)?.valueChanges.subscribe(() => {
|
|
2364
|
+
this.first.set(0);
|
|
2365
|
+
if (this.serverSide()) {
|
|
2366
|
+
this.filterSubject.next();
|
|
2367
|
+
}
|
|
2368
|
+
else {
|
|
2369
|
+
this.applyFilters();
|
|
2370
|
+
}
|
|
2371
|
+
this.cdr.markForCheck();
|
|
2372
|
+
});
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
2375
|
+
emitFilterChange() {
|
|
2376
|
+
const filters = {};
|
|
2377
|
+
Object.keys(this.columnFiltersForm.controls).forEach((field) => {
|
|
2378
|
+
const value = this.columnFiltersForm.get(field)?.value;
|
|
2379
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
2380
|
+
filters[field] = value;
|
|
2381
|
+
}
|
|
2382
|
+
});
|
|
2383
|
+
this.filterChange.emit({ filters });
|
|
2384
|
+
}
|
|
2385
|
+
applySearch() {
|
|
2386
|
+
const original = this.originalData();
|
|
2387
|
+
const query = this.searchQuery().toLowerCase();
|
|
2388
|
+
if (!query) {
|
|
2389
|
+
this.searchedData.set([...original]);
|
|
2390
|
+
}
|
|
2391
|
+
else {
|
|
2392
|
+
const filtered = original.filter((item) => {
|
|
2393
|
+
return Object.values(item).some((val) => {
|
|
2394
|
+
if (val === null || val === undefined)
|
|
2395
|
+
return false;
|
|
2396
|
+
return String(val).toLowerCase().includes(query);
|
|
2397
|
+
});
|
|
2398
|
+
});
|
|
2399
|
+
this.searchedData.set(filtered);
|
|
2400
|
+
}
|
|
2401
|
+
this.applyColumnFilters();
|
|
2402
|
+
}
|
|
2403
|
+
applyFilters() {
|
|
2404
|
+
this.applySearch();
|
|
2405
|
+
}
|
|
2406
|
+
applyColumnFilters() {
|
|
2407
|
+
const data = this.searchedData();
|
|
2408
|
+
let filtered = [...data];
|
|
2409
|
+
this.columns().forEach((col) => {
|
|
2410
|
+
if (col.filter) {
|
|
2411
|
+
const filterValue = this.columnFiltersForm.get(col.field)?.value;
|
|
2412
|
+
if (filterValue !== null && filterValue !== undefined && filterValue !== '') {
|
|
2413
|
+
if (col.filterType === 'select') {
|
|
2414
|
+
filtered = filtered.filter((item) => item[col.field] == filterValue);
|
|
2415
|
+
}
|
|
2416
|
+
else if (col.filterType === 'exact') {
|
|
2417
|
+
const searchTerm = String(filterValue).toLowerCase();
|
|
2418
|
+
filtered = filtered.filter((item) => String(item[col.field]).toLowerCase() === searchTerm);
|
|
2419
|
+
}
|
|
2420
|
+
else {
|
|
2421
|
+
const searchTerm = String(filterValue).toLowerCase();
|
|
2422
|
+
filtered = filtered.filter((item) => String(item[col.field]).toLowerCase().includes(searchTerm));
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
});
|
|
2427
|
+
this.filteredData.set(filtered);
|
|
2428
|
+
this.localTotalRecords.set(filtered.length);
|
|
2429
|
+
this.applySort();
|
|
2430
|
+
this.updateDisplayedData();
|
|
2431
|
+
}
|
|
2432
|
+
updateDisplayedData() {
|
|
2433
|
+
const filtered = this.filteredData();
|
|
2434
|
+
const firstIndex = this.first();
|
|
2435
|
+
const rows = this.rowsPerPageLocal();
|
|
2436
|
+
const displayed = filtered.slice(firstIndex, firstIndex + rows);
|
|
2437
|
+
this.displayedData.set(displayed);
|
|
2438
|
+
}
|
|
2439
|
+
onFilterClick(value) {
|
|
2440
|
+
this.filterClick.emit(value);
|
|
2441
|
+
}
|
|
2442
|
+
toggleSort(field) {
|
|
2443
|
+
if (this.sortField() === field) {
|
|
2444
|
+
this.sortOrder.set(this.sortOrder() * -1);
|
|
2445
|
+
}
|
|
2446
|
+
else {
|
|
2447
|
+
this.sortField.set(field);
|
|
2448
|
+
this.sortOrder.set(1);
|
|
2449
|
+
}
|
|
2450
|
+
this.first.set(0);
|
|
2451
|
+
if (!this.serverSide()) {
|
|
2452
|
+
this.applySort();
|
|
2453
|
+
this.updateDisplayedData();
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
applySort() {
|
|
2457
|
+
const field = this.sortField();
|
|
2458
|
+
const order = this.sortOrder();
|
|
2459
|
+
if (!field)
|
|
2460
|
+
return;
|
|
2461
|
+
const data = [...this.filteredData()];
|
|
2462
|
+
data.sort((a, b) => {
|
|
2463
|
+
const va = a[field];
|
|
2464
|
+
const vb = b[field];
|
|
2465
|
+
if (va == null)
|
|
2466
|
+
return 1;
|
|
2467
|
+
if (vb == null)
|
|
2468
|
+
return -1;
|
|
2469
|
+
if (va < vb)
|
|
2470
|
+
return -1 * order;
|
|
2471
|
+
if (va > vb)
|
|
2472
|
+
return 1 * order;
|
|
2473
|
+
return 0;
|
|
2474
|
+
});
|
|
2475
|
+
this.filteredData.set(data);
|
|
2476
|
+
}
|
|
2477
|
+
onSearchInput(value) {
|
|
2478
|
+
this.searchQuery.set(value);
|
|
2479
|
+
this.first.set(0);
|
|
2480
|
+
if (this.serverSide()) {
|
|
2481
|
+
this.searchChange.emit(value);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
clearFilter(field) {
|
|
2485
|
+
const column = this.columns().find((col) => col.field === field);
|
|
2486
|
+
const value = column?.filterType === 'select' ? null : '';
|
|
2487
|
+
this.columnFiltersForm.get(field)?.setValue(value);
|
|
2488
|
+
const visibility = this.showFilterInput();
|
|
2489
|
+
this.showFilterInput.set({ ...visibility, [field]: false });
|
|
2490
|
+
this.cdr.markForCheck();
|
|
2491
|
+
}
|
|
2492
|
+
toggleFilter(field) {
|
|
2493
|
+
const visibility = this.showFilterInput();
|
|
2494
|
+
this.showFilterInput.set({ ...visibility, [field]: !visibility[field] });
|
|
2495
|
+
this.cdr.markForCheck();
|
|
2496
|
+
}
|
|
2497
|
+
getPageReport() {
|
|
2498
|
+
const total = this.effectiveTotalRecords();
|
|
2499
|
+
if (total === 0)
|
|
2500
|
+
return this.transloco.translate('table.pageReport', {
|
|
2501
|
+
from: 0,
|
|
2502
|
+
to: 0,
|
|
2503
|
+
total: 0,
|
|
2504
|
+
});
|
|
2505
|
+
const from = this.first() + 1;
|
|
2506
|
+
const to = Math.min(this.first() + this.rowsPerPageLocal(), total);
|
|
2507
|
+
return this.transloco.translate('table.pageReport', { from, to, total });
|
|
2508
|
+
}
|
|
2509
|
+
goToFirst() {
|
|
2510
|
+
const rows = this.rowsPerPageLocal();
|
|
2511
|
+
this.first.set(0);
|
|
2512
|
+
if (this.serverSide()) {
|
|
2513
|
+
this.pageChange.emit({
|
|
2514
|
+
first: 0,
|
|
2515
|
+
rows,
|
|
2516
|
+
page: 0,
|
|
2517
|
+
pageCount: Math.ceil(this.totalRecords() / rows),
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
else {
|
|
2521
|
+
this.updateDisplayedData();
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
goToLast() {
|
|
2525
|
+
const total = this.serverSide()
|
|
2526
|
+
? this.totalRecords()
|
|
2527
|
+
: this.filteredData().length;
|
|
2528
|
+
const rows = this.rowsPerPageLocal();
|
|
2529
|
+
const lastFirst = Math.max(0, Math.ceil(total / rows) * rows - rows);
|
|
2530
|
+
this.first.set(lastFirst);
|
|
2531
|
+
if (this.serverSide()) {
|
|
2532
|
+
const page = Math.floor(lastFirst / rows);
|
|
2533
|
+
this.pageChange.emit({
|
|
2534
|
+
first: lastFirst,
|
|
2535
|
+
rows,
|
|
2536
|
+
page,
|
|
2537
|
+
pageCount: Math.ceil(total / rows),
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
else {
|
|
2541
|
+
this.updateDisplayedData();
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
onRowsPerPageChange(rows) {
|
|
2545
|
+
rows = Number(rows);
|
|
2546
|
+
this.rowsPerPageLocal.set(rows);
|
|
2547
|
+
this.first.set(0);
|
|
2548
|
+
if (this.serverSide()) {
|
|
2549
|
+
this.pageChange.emit({
|
|
2550
|
+
first: 0,
|
|
2551
|
+
rows,
|
|
2552
|
+
page: 0,
|
|
2553
|
+
pageCount: Math.ceil(this.totalRecords() / rows),
|
|
2554
|
+
});
|
|
2555
|
+
}
|
|
2556
|
+
else {
|
|
2557
|
+
this.updateDisplayedData();
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
refreshData() {
|
|
2561
|
+
this.refresh.emit();
|
|
2562
|
+
}
|
|
2563
|
+
isFirstPage() {
|
|
2564
|
+
return this.first() === 0;
|
|
2565
|
+
}
|
|
2566
|
+
isLastPage() {
|
|
2567
|
+
const total = this.effectiveTotalRecords();
|
|
2568
|
+
return this.first() + this.rowsPerPageLocal() >= total;
|
|
2569
|
+
}
|
|
2570
|
+
next() {
|
|
2571
|
+
const total = this.serverSide()
|
|
2572
|
+
? this.totalRecords()
|
|
2573
|
+
: this.filteredData().length;
|
|
2574
|
+
const rows = this.rowsPerPageLocal();
|
|
2575
|
+
let first = this.first() + rows;
|
|
2576
|
+
if (first >= total) {
|
|
2577
|
+
first = total - rows;
|
|
2578
|
+
}
|
|
2579
|
+
this.first.set(first);
|
|
2580
|
+
if (this.serverSide()) {
|
|
2581
|
+
const page = Math.floor(first / rows);
|
|
2582
|
+
this.pageChange.emit({
|
|
2583
|
+
first,
|
|
2584
|
+
rows,
|
|
2585
|
+
page,
|
|
2586
|
+
pageCount: Math.ceil(total / rows),
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
else {
|
|
2590
|
+
this.updateDisplayedData();
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
prev() {
|
|
2594
|
+
const rows = this.rowsPerPageLocal();
|
|
2595
|
+
let first = this.first() - rows;
|
|
2596
|
+
const total = this.serverSide()
|
|
2597
|
+
? this.totalRecords()
|
|
2598
|
+
: this.filteredData().length;
|
|
2599
|
+
if (first < 0) {
|
|
2600
|
+
first = 0;
|
|
2601
|
+
}
|
|
2602
|
+
this.first.set(first);
|
|
2603
|
+
if (this.serverSide()) {
|
|
2604
|
+
const page = Math.floor(first / rows);
|
|
2605
|
+
this.pageChange.emit({
|
|
2606
|
+
first,
|
|
2607
|
+
rows,
|
|
2608
|
+
page,
|
|
2609
|
+
pageCount: Math.ceil(total / rows),
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
else {
|
|
2613
|
+
this.updateDisplayedData();
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
reset() {
|
|
2617
|
+
this.goToFirst();
|
|
2618
|
+
}
|
|
2619
|
+
getRowActionLabel(action, rowData) {
|
|
2620
|
+
return typeof action.label === 'function'
|
|
2621
|
+
? action.label(rowData)
|
|
2622
|
+
: action.label;
|
|
2623
|
+
}
|
|
2624
|
+
getRowActionIconName(action, rowData) {
|
|
2625
|
+
return typeof action.icon === 'function'
|
|
2626
|
+
? action.icon(rowData)
|
|
2627
|
+
: action.icon;
|
|
2628
|
+
}
|
|
2629
|
+
getRowActionClass(action, rowData) {
|
|
2630
|
+
if (!action.class)
|
|
2631
|
+
return '';
|
|
2632
|
+
return typeof action.class === 'function'
|
|
2633
|
+
? action.class(rowData)
|
|
2634
|
+
: action.class;
|
|
2635
|
+
}
|
|
2636
|
+
isHeaderActionDisabled(action) {
|
|
2637
|
+
return action.isDisabled ? action.isDisabled() : false;
|
|
2638
|
+
}
|
|
2639
|
+
isHeaderActionVisible(action) {
|
|
2640
|
+
return action.isVisible ? action.isVisible() : true;
|
|
2641
|
+
}
|
|
2642
|
+
isRowActionDisabled(action, rowData) {
|
|
2643
|
+
return action.isDisabled ? action.isDisabled(rowData) : false;
|
|
2644
|
+
}
|
|
2645
|
+
isRowActionVisible(action, rowData) {
|
|
2646
|
+
return action.isVisible ? action.isVisible(rowData) : true;
|
|
2647
|
+
}
|
|
2648
|
+
truncate(value, limit = 30) {
|
|
2649
|
+
if (value === null || value === undefined)
|
|
2650
|
+
return '';
|
|
2651
|
+
const str = String(value);
|
|
2652
|
+
return str.length > limit ? str.slice(0, limit) + '\u2026' : str;
|
|
2653
|
+
}
|
|
2654
|
+
getTagClass(severity) {
|
|
2655
|
+
switch (severity) {
|
|
2656
|
+
case 'success':
|
|
2657
|
+
return '_shk-bg-green-soft _shk-text-green-600';
|
|
2658
|
+
case 'danger':
|
|
2659
|
+
return '_shk-bg-red-soft _shk-text-red-600';
|
|
2660
|
+
case 'warn':
|
|
2661
|
+
return '_shk-bg-yellow-soft _shk-text-yellow-600';
|
|
2662
|
+
case 'info':
|
|
2663
|
+
return '_shk-bg-blue-soft _shk-text-blue-600';
|
|
2664
|
+
default:
|
|
2665
|
+
return '_shk-bg-surface-100 _shk-text-surface-700 dark:[background-color:var(--shk-surface-700)] dark:[color:var(--shk-surface-300)]';
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2669
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: TableComponent, isStandalone: true, selector: "shk-table", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPage: { classPropertyName: "rowsPerPage", publicName: "rowsPerPage", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPageOptions: { classPropertyName: "rowsPerPageOptions", publicName: "rowsPerPageOptions", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, showActionRow: { classPropertyName: "showActionRow", publicName: "showActionRow", isSignal: true, isRequired: false, transformFunction: null }, customTemplates: { classPropertyName: "customTemplates", publicName: "customTemplates", isSignal: true, isRequired: false, transformFunction: null }, headerActions: { classPropertyName: "headerActions", publicName: "headerActions", isSignal: true, isRequired: false, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, hasShadow: { classPropertyName: "hasShadow", publicName: "hasShadow", isSignal: true, isRequired: false, transformFunction: null }, defaultSortField: { classPropertyName: "defaultSortField", publicName: "defaultSortField", isSignal: true, isRequired: false, transformFunction: null }, defaultSortOrder: { classPropertyName: "defaultSortOrder", publicName: "defaultSortOrder", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, serverSide: { classPropertyName: "serverSide", publicName: "serverSide", isSignal: true, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, activeFilter: { classPropertyName: "activeFilter", publicName: "activeFilter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { refresh: "refresh", pageChange: "pageChange", filterChange: "filterChange", searchChange: "searchChange", filterClick: "filterClick" }, ngImport: i0, template: "<div class=\"space-y-2 min-[1440px]:space-y-3 mt-2 min-[1440px]:mt-3 animate-fade-in pb-8\">\n\n @if (showSearch() || filters().length > 0 || headerActions().length > 0) {\n <div class=\"card p-2 min-[1440px]:p-2.5 flex flex-row items-center justify-between gap-2\">\n\n <div class=\"flex items-center gap-2 min-[1440px]:gap-3 flex-1 min-w-0\">\n @if (showSearch()) {\n <div class=\"relative w-full md:w-52 min-[1440px]:w-56\">\n <div class=\"absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none\">\n <svg [lucideIcon]=\"getIcon('search')\" class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n </div>\n <input\n type=\"text\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($any($event.target).value)\"\n [placeholder]=\"searchPlaceholder() || ('common.search_placeholder' | transloco)\"\n class=\"block w-full pl-8 pr-3 py-1.5 text-xs border _shk-border dark:[border-color:var(--shk-surface-700)] rounded-md leading-5\n _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\n _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\n focus:outline-none focus:bg-white dark:focus:[background-color:var(--shk-surface-800)] focus:[border-color:var(--shk-primary-500)] focus:ring-1\n focus:[--tw-ring-color:var(--shk-primary-500)]\"\n />\n </div>\n }\n\n @if (filters().length > 0) {\n <div class=\"flex _shk-bg-surface-100 dark:[background-color:var(--shk-surface-800)] p-0.5 rounded-md ml-auto\">\n @for (f of filters(); track f.value) {\n <button\n type=\"button\"\n (click)=\"onFilterClick(f.value)\"\n class=\"px-2.5 min-[1440px]:px-3 py-1 text-xs font-medium rounded _shk-text-surface-700 dark:[color:var(--shk-surface-200)] transition-all\"\n [class]=\"f.value === activeFilter() ? 'bg-white shadow-sm dark:[background-color:var(--shk-surface-700)]' : ''\"\n >\n {{ f.label }}\n </button>\n }\n </div>\n }\n </div>\n\n @if (headerActions().length > 0) {\n <div class=\"flex items-center gap-1.5 shrink-0\">\n @for (action of headerActions(); track $index) {\n @if (isHeaderActionVisible(action)) {\n <button\n type=\"button\"\n [class]=\"action.class || 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed'\"\n [disabled]=\"isHeaderActionDisabled(action)\"\n (click)=\"action.onClick()\"\n >\n @if (action.icon) {\n <svg\n [lucideIcon]=\"getIcon(action.icon)\"\n class=\"w-3.5 h-3.5\"\n ></svg>\n }\n {{ action.label }}\n </button>\n }\n }\n </div>\n }\n\n </div>\n }\n\n <div\n class=\"card p-0 overflow-hidden\"\n [class.shadow-lg]=\"hasShadow()\"\n >\n\n <div class=\"table-scroll-wrapper _shk-scrollbar\">\n <table class=\"w-full\">\n <thead>\n <tr>\n @for (col of columnsWithActions(); track col.field) {\n <th\n (click)=\"col.field !== 'actions' && col.sortable && toggleSort(col.field)\"\n [style.cursor]=\"col.field !== 'actions' && col.sortable ? 'pointer' : 'default'\"\n [style.width]=\"col.width || 'auto'\"\n [style.min-width]=\"col.field === 'actions' ? '130px' : 'auto'\"\n [style.max-width]=\"col.field === 'actions' ? 'none' : (col.width || '300px')\"\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-left text-[10px] min-[1440px]:text-xs font-bold _shk-text-surface-500 dark:[color:var(--shk-surface-400)] uppercase tracking-wider\n _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border-b _shk-border dark:[border-color:var(--shk-surface-700)]\"\n >\n @if (col.field !== 'actions') {\n <div class=\"flex items-center justify-between gap-2\">\n <span>{{ col.header }}</span>\n <div class=\"flex items-center gap-1\">\n @if (col.sortable) {\n <div class=\"relative group\">\n @if (sortField() === col.field) {\n <svg [lucideIcon]=\"getIcon(sortOrder() === 1 ? 'arrow-up' : 'arrow-down')\"\n class=\"w-3.5 h-3.5 _shk-text-primary\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('arrow-up-down')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n }\n <span class=\"absolute top-full left-1/2 -translate-x-1/2 mt-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ 'table.tooltips.sort' | transloco }}\n </span>\n </div>\n }\n @if (col.filter) {\n <div class=\"relative group\">\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleFilter(col.field)\"\n class=\"_shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] transition-colors\"\n >\n <svg\n [lucideIcon]=\"getIcon('filter')\"\n class=\"w-3.5 h-3.5\"\n [class._shk-text-primary]=\"columnFiltersForm.get(col.field)?.value\"\n ></svg>\n <span class=\"absolute top-full left-1/2 -translate-x-1/2 mt-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ 'table.tooltips.filter' | transloco }}\n </span>\n </button>\n </div>\n }\n </div>\n </div>\n\n @if (col.filter && showFilterInput()[col.field]) {\n <div\n class=\"mt-2\"\n [formGroup]=\"columnFiltersForm\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"relative\">\n @if (col.filterType !== 'select') {\n <input\n type=\"text\"\n [formControlName]=\"col.field\"\n class=\"w-full px-3 py-1.5 text-sm rounded-md border _shk-border dark:[border-color:var(--shk-surface-700)] bg-white dark:[background-color:var(--shk-surface-800)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\n focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)]\n placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\"\n [placeholder]=\"col.filterPlaceholder || ('common.search_placeholder' | transloco)\"\n />\n }\n @if (col.filterType === 'select') {\n <select\n [formControlName]=\"col.field\"\n class=\"w-full px-2 py-1 text-xs rounded-md border _shk-border dark:[border-color:var(--shk-surface-700)] bg-white dark:[background-color:var(--shk-surface-800)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] appearance-none\"\n >\n <option [ngValue]=\"null\">A/D</option>\n @for (opt of col.filterOptions || []; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @if (columnFiltersForm.get(col.field)?.value && col.filterType !== 'select') {\n <button\n type=\"button\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)]\"\n (click)=\"clearFilter(col.field)\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <div class=\"flex items-center justify-center\">\n <span>{{ col.header }}</span>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n\n @if (loading()) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-800)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex items-center justify-center\">\n <svg [lucideIcon]=\"getIcon('loader')\" class=\"w-6 h-6 animate-spin _shk-text-primary\"></svg>\n </div>\n </td>\n </tr>\n </tbody>\n } @else if (effectiveDisplayedData().length === 0) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-800)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex flex-col items-center\">\n <div class=\"w-16 h-16 _shk-bg-surface-100 dark:[background-color:var(--shk-surface-800)] rounded-full flex items-center justify-center mb-4\">\n <svg [lucideIcon]=\"getIcon('inbox')\" class=\"w-8 h-8 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n </div>\n <p class=\"text-lg font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\">\n {{ emptyMessage() || ('common.no_records' | transloco) }}\n </p>\n </div>\n </td>\n </tr>\n </tbody>\n } @else {\n <tbody>\n @for (rowData of effectiveDisplayedData(); track $index) {\n <tr class=\"hover:[background-color:color-mix(in srgb,var(--shk-primary-50)30%,transparent)] dark:hover:[background-color:color-mix(in srgb,var(--shk-primary-900)10%,transparent)] transition-colors duration-200 border-b _shk-border dark:[border-color:var(--shk-surface-800)]\">\n @for (col of columnsWithActions(); track col.field) {\n <td\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-200)] whitespace-nowrap\"\n [style.max-width]=\"col.width || '300px'\"\n [style.overflow]=\"col.field === 'actions' ? 'visible' : 'hidden'\"\n [style.text-overflow]=\"col.field === 'actions' ? 'clip' : 'ellipsis'\"\n >\n @if (col.field !== 'actions') {\n @if (col.template === 'tag') {\n <span\n class=\"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium\"\n [class]=\"getTagClass(col.tagSeverity ? col.tagSeverity(rowData) : undefined)\"\n [style]=\"col.tagStyle ? col.tagStyle(rowData) : undefined\"\n >\n {{ col.tagValue ? col.tagValue(rowData) : rowData[col.field] }}\n </span>\n } @else if (col.format) {\n <span>{{ col.format(rowData) }}</span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n customTemplates()[col.field] || defaultTemplate;\n context: { $implicit: rowData, field: col.field }\n \"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-start gap-px\">\n @for (action of rowActions(); track $index) {\n @if (isRowActionVisible(action, rowData)) {\n <div class=\"relative group\">\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData)\"\n [class]=\"'table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] rounded-md ' + getRowActionClass(action, rowData)\"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n <span class=\"absolute bottom-full left-1/2 -translate-x-1/2 mb-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ getRowActionLabel(action, rowData) }}\n </span>\n </div>\n }\n }\n </div>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n }\n </table>\n\n <ng-template #defaultTemplate let-rowData let-field=\"field\">\n {{ truncate(rowData[field], 30) }}\n </ng-template>\n </div>\n\n <div class=\"flex items-center justify-between gap-4 px-2 min-[1440px]:px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-800)]\">\n <div class=\"flex items-center gap-1.5 text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">\n <span>{{ 'table.rows' | transloco }}</span>\n <select\n [value]=\"rowsPerPageLocal()\"\n (change)=\"onRowsPerPageChange($any($event.target).value)\"\n class=\"border _shk-border dark:[border-color:var(--shk-surface-700)] rounded px-2 py-1 text-xs bg-white dark:[background-color:var(--shk-surface-800)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)] focus:outline-none focus:[border-color:var(--shk-primary-500)]\"\n >\n @for (opt of rowsPerPageOptions(); track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n <div class=\"text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">{{ getPageReport() }}</div>\n <div class=\"flex items-center gap-0.5\">\n <button\n type=\"button\"\n (click)=\"goToFirst()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span class=\"px-2 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\">{{ currentPage() }} / {{ totalPages() }}</span>\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"goToLast()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n </div>\n </div>\n\n</div>\n", styles: ["input:focus{outline:none}input[type=text]{font-weight:400!important}table th{white-space:nowrap}table td{white-space:nowrap;text-overflow:ellipsis}td button svg{width:1.125rem!important;height:1.125rem!important}td button:disabled{opacity:.4;cursor:not-allowed}.table-action-btn svg{width:1rem!important;height:1rem!important;min-width:1rem!important;min-height:1rem!important}.table-action-btn{padding:.5rem!important;min-width:2rem!important;min-height:2rem!important;display:inline-flex!important;align-items:center!important;justify-content:center!important}.table-scroll-wrapper{width:100%;overflow:auto}.table-scroll-wrapper table{table-layout:fixed}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { 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.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }, { kind: "pipe", type: i1$1.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2670
|
+
}
|
|
2671
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TableComponent, decorators: [{
|
|
2672
|
+
type: Component,
|
|
2673
|
+
args: [{ selector: 'shk-table', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
2674
|
+
CommonModule,
|
|
2675
|
+
ReactiveFormsModule,
|
|
2676
|
+
TranslocoModule,
|
|
2677
|
+
LucideDynamicIcon,
|
|
2678
|
+
], template: "<div class=\"space-y-2 min-[1440px]:space-y-3 mt-2 min-[1440px]:mt-3 animate-fade-in pb-8\">\n\n @if (showSearch() || filters().length > 0 || headerActions().length > 0) {\n <div class=\"card p-2 min-[1440px]:p-2.5 flex flex-row items-center justify-between gap-2\">\n\n <div class=\"flex items-center gap-2 min-[1440px]:gap-3 flex-1 min-w-0\">\n @if (showSearch()) {\n <div class=\"relative w-full md:w-52 min-[1440px]:w-56\">\n <div class=\"absolute inset-y-0 left-0 pl-2.5 flex items-center pointer-events-none\">\n <svg [lucideIcon]=\"getIcon('search')\" class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n </div>\n <input\n type=\"text\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($any($event.target).value)\"\n [placeholder]=\"searchPlaceholder() || ('common.search_placeholder' | transloco)\"\n class=\"block w-full pl-8 pr-3 py-1.5 text-xs border _shk-border dark:[border-color:var(--shk-surface-700)] rounded-md leading-5\n _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\n _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\n focus:outline-none focus:bg-white dark:focus:[background-color:var(--shk-surface-800)] focus:[border-color:var(--shk-primary-500)] focus:ring-1\n focus:[--tw-ring-color:var(--shk-primary-500)]\"\n />\n </div>\n }\n\n @if (filters().length > 0) {\n <div class=\"flex _shk-bg-surface-100 dark:[background-color:var(--shk-surface-800)] p-0.5 rounded-md ml-auto\">\n @for (f of filters(); track f.value) {\n <button\n type=\"button\"\n (click)=\"onFilterClick(f.value)\"\n class=\"px-2.5 min-[1440px]:px-3 py-1 text-xs font-medium rounded _shk-text-surface-700 dark:[color:var(--shk-surface-200)] transition-all\"\n [class]=\"f.value === activeFilter() ? 'bg-white shadow-sm dark:[background-color:var(--shk-surface-700)]' : ''\"\n >\n {{ f.label }}\n </button>\n }\n </div>\n }\n </div>\n\n @if (headerActions().length > 0) {\n <div class=\"flex items-center gap-1.5 shrink-0\">\n @for (action of headerActions(); track $index) {\n @if (isHeaderActionVisible(action)) {\n <button\n type=\"button\"\n [class]=\"action.class || 'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md _shk-bg-primary text-white hover:[background-color:var(--shk-primary-700)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed'\"\n [disabled]=\"isHeaderActionDisabled(action)\"\n (click)=\"action.onClick()\"\n >\n @if (action.icon) {\n <svg\n [lucideIcon]=\"getIcon(action.icon)\"\n class=\"w-3.5 h-3.5\"\n ></svg>\n }\n {{ action.label }}\n </button>\n }\n }\n </div>\n }\n\n </div>\n }\n\n <div\n class=\"card p-0 overflow-hidden\"\n [class.shadow-lg]=\"hasShadow()\"\n >\n\n <div class=\"table-scroll-wrapper _shk-scrollbar\">\n <table class=\"w-full\">\n <thead>\n <tr>\n @for (col of columnsWithActions(); track col.field) {\n <th\n (click)=\"col.field !== 'actions' && col.sortable && toggleSort(col.field)\"\n [style.cursor]=\"col.field !== 'actions' && col.sortable ? 'pointer' : 'default'\"\n [style.width]=\"col.width || 'auto'\"\n [style.min-width]=\"col.field === 'actions' ? '130px' : 'auto'\"\n [style.max-width]=\"col.field === 'actions' ? 'none' : (col.width || '300px')\"\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-left text-[10px] min-[1440px]:text-xs font-bold _shk-text-surface-500 dark:[color:var(--shk-surface-400)] uppercase tracking-wider\n _shk-bg-surface-50 dark:[background-color:var(--shk-surface-800)] border-b _shk-border dark:[border-color:var(--shk-surface-700)]\"\n >\n @if (col.field !== 'actions') {\n <div class=\"flex items-center justify-between gap-2\">\n <span>{{ col.header }}</span>\n <div class=\"flex items-center gap-1\">\n @if (col.sortable) {\n <div class=\"relative group\">\n @if (sortField() === col.field) {\n <svg [lucideIcon]=\"getIcon(sortOrder() === 1 ? 'arrow-up' : 'arrow-down')\"\n class=\"w-3.5 h-3.5 _shk-text-primary\"></svg>\n } @else {\n <svg [lucideIcon]=\"getIcon('arrow-up-down')\"\n class=\"w-3.5 h-3.5 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n }\n <span class=\"absolute top-full left-1/2 -translate-x-1/2 mt-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ 'table.tooltips.sort' | transloco }}\n </span>\n </div>\n }\n @if (col.filter) {\n <div class=\"relative group\">\n <button\n type=\"button\"\n (click)=\"$event.stopPropagation(); toggleFilter(col.field)\"\n class=\"_shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)] transition-colors\"\n >\n <svg\n [lucideIcon]=\"getIcon('filter')\"\n class=\"w-3.5 h-3.5\"\n [class._shk-text-primary]=\"columnFiltersForm.get(col.field)?.value\"\n ></svg>\n <span class=\"absolute top-full left-1/2 -translate-x-1/2 mt-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ 'table.tooltips.filter' | transloco }}\n </span>\n </button>\n </div>\n }\n </div>\n </div>\n\n @if (col.filter && showFilterInput()[col.field]) {\n <div\n class=\"mt-2\"\n [formGroup]=\"columnFiltersForm\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"relative\">\n @if (col.filterType !== 'select') {\n <input\n type=\"text\"\n [formControlName]=\"col.field\"\n class=\"w-full px-3 py-1.5 text-sm rounded-md border _shk-border dark:[border-color:var(--shk-surface-700)] bg-white dark:[background-color:var(--shk-surface-800)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\n focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)]\n placeholder:[color:var(--shk-surface-400)] dark:placeholder:[color:var(--shk-surface-500)]\"\n [placeholder]=\"col.filterPlaceholder || ('common.search_placeholder' | transloco)\"\n />\n }\n @if (col.filterType === 'select') {\n <select\n [formControlName]=\"col.field\"\n class=\"w-full px-2 py-1 text-xs rounded-md border _shk-border dark:[border-color:var(--shk-surface-700)] bg-white dark:[background-color:var(--shk-surface-800)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)] focus:[border-color:var(--shk-primary-500)] focus:ring-1 focus:[--tw-ring-color:var(--shk-primary-500)] appearance-none\"\n >\n <option [ngValue]=\"null\">A/D</option>\n @for (opt of col.filterOptions || []; track opt.value) {\n <option [ngValue]=\"opt.value\">{{ opt.label }}</option>\n }\n </select>\n }\n @if (columnFiltersForm.get(col.field)?.value && col.filterType !== 'select') {\n <button\n type=\"button\"\n class=\"absolute right-2 top-1/2 -translate-y-1/2 _shk-text-surface-400 dark:[color:var(--shk-surface-500)] hover:[color:var(--shk-surface-600)] dark:hover:[color:var(--shk-surface-300)]\"\n (click)=\"clearFilter(col.field)\"\n >\n <svg [lucideIcon]=\"getIcon('x')\" class=\"w-3.5 h-3.5\"></svg>\n </button>\n }\n </div>\n </div>\n }\n } @else {\n <div class=\"flex items-center justify-center\">\n <span>{{ col.header }}</span>\n </div>\n }\n </th>\n }\n </tr>\n </thead>\n\n @if (loading()) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-800)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex items-center justify-center\">\n <svg [lucideIcon]=\"getIcon('loader')\" class=\"w-6 h-6 animate-spin _shk-text-primary\"></svg>\n </div>\n </td>\n </tr>\n </tbody>\n } @else if (effectiveDisplayedData().length === 0) {\n <tbody class=\"border-b _shk-border dark:[border-color:var(--shk-surface-800)]\">\n <tr>\n <td [attr.colspan]=\"columnsWithActions().length\" class=\"text-center py-12\">\n <div class=\"flex flex-col items-center\">\n <div class=\"w-16 h-16 _shk-bg-surface-100 dark:[background-color:var(--shk-surface-800)] rounded-full flex items-center justify-center mb-4\">\n <svg [lucideIcon]=\"getIcon('inbox')\" class=\"w-8 h-8 _shk-text-surface-400 dark:[color:var(--shk-surface-500)]\"></svg>\n </div>\n <p class=\"text-lg font-semibold _shk-text-surface-600 dark:[color:var(--shk-surface-300)]\">\n {{ emptyMessage() || ('common.no_records' | transloco) }}\n </p>\n </div>\n </td>\n </tr>\n </tbody>\n } @else {\n <tbody>\n @for (rowData of effectiveDisplayedData(); track $index) {\n <tr class=\"hover:[background-color:color-mix(in srgb,var(--shk-primary-50)30%,transparent)] dark:hover:[background-color:color-mix(in srgb,var(--shk-primary-900)10%,transparent)] transition-colors duration-200 border-b _shk-border dark:[border-color:var(--shk-surface-800)]\">\n @for (col of columnsWithActions(); track col.field) {\n <td\n class=\"px-2 min-[1440px]:px-3 py-2 min-[1440px]:py-2.5 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-200)] whitespace-nowrap\"\n [style.max-width]=\"col.width || '300px'\"\n [style.overflow]=\"col.field === 'actions' ? 'visible' : 'hidden'\"\n [style.text-overflow]=\"col.field === 'actions' ? 'clip' : 'ellipsis'\"\n >\n @if (col.field !== 'actions') {\n @if (col.template === 'tag') {\n <span\n class=\"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium\"\n [class]=\"getTagClass(col.tagSeverity ? col.tagSeverity(rowData) : undefined)\"\n [style]=\"col.tagStyle ? col.tagStyle(rowData) : undefined\"\n >\n {{ col.tagValue ? col.tagValue(rowData) : rowData[col.field] }}\n </span>\n } @else if (col.format) {\n <span>{{ col.format(rowData) }}</span>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n customTemplates()[col.field] || defaultTemplate;\n context: { $implicit: rowData, field: col.field }\n \"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-start gap-px\">\n @for (action of rowActions(); track $index) {\n @if (isRowActionVisible(action, rowData)) {\n <div class=\"relative group\">\n <button\n type=\"button\"\n (click)=\"action.onClick(rowData)\"\n [class]=\"'table-action-btn hover:[background-color:var(--shk-surface-100)] dark:hover:[background-color:var(--shk-surface-700)] rounded-md ' + getRowActionClass(action, rowData)\"\n [disabled]=\"isRowActionDisabled(action, rowData) || false\"\n >\n <svg\n [lucideIcon]=\"getIcon(getRowActionIconName(action, rowData))\"\n class=\"w-4 h-4\"\n ></svg>\n </button>\n <span class=\"absolute bottom-full left-1/2 -translate-x-1/2 mb-1 px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg\">\n {{ getRowActionLabel(action, rowData) }}\n </span>\n </div>\n }\n }\n </div>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n }\n </table>\n\n <ng-template #defaultTemplate let-rowData let-field=\"field\">\n {{ truncate(rowData[field], 30) }}\n </ng-template>\n </div>\n\n <div class=\"flex items-center justify-between gap-4 px-2 min-[1440px]:px-3 py-2 border-t _shk-border dark:[border-color:var(--shk-surface-800)]\">\n <div class=\"flex items-center gap-1.5 text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">\n <span>{{ 'table.rows' | transloco }}</span>\n <select\n [value]=\"rowsPerPageLocal()\"\n (change)=\"onRowsPerPageChange($any($event.target).value)\"\n class=\"border _shk-border dark:[border-color:var(--shk-surface-700)] rounded px-2 py-1 text-xs bg-white dark:[background-color:var(--shk-surface-800)] _shk-text-surface-700 dark:[color:var(--shk-surface-200)] focus:outline-none focus:[border-color:var(--shk-primary-500)]\"\n >\n @for (opt of rowsPerPageOptions(); track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n <div class=\"text-xs _shk-text-surface-600 dark:[color:var(--shk-surface-400)]\">{{ getPageReport() }}</div>\n <div class=\"flex items-center gap-0.5\">\n <button\n type=\"button\"\n (click)=\"goToFirst()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"prev()\"\n [disabled]=\"isFirstPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-left')\" class=\"w-4 h-4\"></svg>\n </button>\n <span class=\"px-2 text-xs font-medium _shk-text-surface-700 dark:[color:var(--shk-surface-200)]\">{{ currentPage() }} / {{ totalPages() }}</span>\n <button\n type=\"button\"\n (click)=\"next()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevron-right')\" class=\"w-4 h-4\"></svg>\n </button>\n <button\n type=\"button\"\n (click)=\"goToLast()\"\n [disabled]=\"isLastPage()\"\n class=\"p-1 _shk-text-surface-500 hover:[color:var(--shk-surface-700)] dark:[color:var(--shk-surface-400)] dark:hover:[color:var(--shk-surface-200)] disabled:opacity-30 disabled:cursor-not-allowed rounded transition-colors\"\n >\n <svg [lucideIcon]=\"getIcon('chevrons-right')\" class=\"w-4 h-4\"></svg>\n </button>\n </div>\n </div>\n </div>\n\n</div>\n", styles: ["input:focus{outline:none}input[type=text]{font-weight:400!important}table th{white-space:nowrap}table td{white-space:nowrap;text-overflow:ellipsis}td button svg{width:1.125rem!important;height:1.125rem!important}td button:disabled{opacity:.4;cursor:not-allowed}.table-action-btn svg{width:1rem!important;height:1rem!important;min-width:1rem!important;min-height:1rem!important}.table-action-btn{padding:.5rem!important;min-width:2rem!important;min-height:2rem!important;display:inline-flex!important;align-items:center!important;justify-content:center!important}.table-scroll-wrapper{width:100%;overflow:auto}.table-scroll-wrapper table{table-layout:fixed}\n"] }]
|
|
2679
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], rowsPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPage", required: false }] }], rowsPerPageOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowsPerPageOptions", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], showActionRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActionRow", required: false }] }], customTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "customTemplates", required: false }] }], headerActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerActions", required: false }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], hasShadow: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasShadow", required: false }] }], defaultSortField: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSortField", required: false }] }], defaultSortOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultSortOrder", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], serverSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "serverSide", required: false }] }], totalRecords: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalRecords", required: false }] }], filters: [{ type: i0.Input, args: [{ isSignal: true, alias: "filters", required: false }] }], activeFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeFilter", required: false }] }], refresh: [{ type: i0.Output, args: ["refresh"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], filterClick: [{ type: i0.Output, args: ["filterClick"] }] } });
|
|
2680
|
+
|
|
2681
|
+
class TooltipComponent {
|
|
2682
|
+
text = input('', ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
2683
|
+
position = input('bottom', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
|
|
2684
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2685
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: TooltipComponent, isStandalone: true, selector: "shk-tooltip", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
2686
|
+
<div class="relative group inline-flex">
|
|
2687
|
+
<ng-content></ng-content>
|
|
2688
|
+
@if (text()) {
|
|
2689
|
+
<span
|
|
2690
|
+
class="absolute px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg"
|
|
2691
|
+
[class.top-full]="position() === 'bottom'"
|
|
2692
|
+
[class.bottom-full]="position() === 'top'"
|
|
2693
|
+
[class.left-full]="position() === 'right'"
|
|
2694
|
+
[class.mt-1]="position() === 'bottom'"
|
|
2695
|
+
[class.mb-1]="position() === 'top'"
|
|
2696
|
+
[class.ml-1]="position() === 'right'"
|
|
2697
|
+
[class.left-1/2]="position() !== 'right'"
|
|
2698
|
+
[class.-translate-x-1/2]="position() !== 'right'"
|
|
2699
|
+
[class.top-1/2]="position() === 'right'"
|
|
2700
|
+
[class.-translate-y-1/2]="position() === 'right'"
|
|
2701
|
+
>
|
|
2702
|
+
{{ text() }}
|
|
2703
|
+
</span>
|
|
2704
|
+
}
|
|
2705
|
+
</div>
|
|
2706
|
+
`, isInline: true });
|
|
2707
|
+
}
|
|
2708
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: TooltipComponent, decorators: [{
|
|
2709
|
+
type: Component,
|
|
2710
|
+
args: [{
|
|
2711
|
+
selector: 'shk-tooltip',
|
|
2712
|
+
standalone: true,
|
|
2713
|
+
template: `
|
|
2714
|
+
<div class="relative group inline-flex">
|
|
2715
|
+
<ng-content></ng-content>
|
|
2716
|
+
@if (text()) {
|
|
2717
|
+
<span
|
|
2718
|
+
class="absolute px-2 py-1 text-xs _shk-bg-tooltip _shk-text-tooltip rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition pointer-events-none z-[9999] shadow-lg"
|
|
2719
|
+
[class.top-full]="position() === 'bottom'"
|
|
2720
|
+
[class.bottom-full]="position() === 'top'"
|
|
2721
|
+
[class.left-full]="position() === 'right'"
|
|
2722
|
+
[class.mt-1]="position() === 'bottom'"
|
|
2723
|
+
[class.mb-1]="position() === 'top'"
|
|
2724
|
+
[class.ml-1]="position() === 'right'"
|
|
2725
|
+
[class.left-1/2]="position() !== 'right'"
|
|
2726
|
+
[class.-translate-x-1/2]="position() !== 'right'"
|
|
2727
|
+
[class.top-1/2]="position() === 'right'"
|
|
2728
|
+
[class.-translate-y-1/2]="position() === 'right'"
|
|
2729
|
+
>
|
|
2730
|
+
{{ text() }}
|
|
2731
|
+
</span>
|
|
2732
|
+
}
|
|
2733
|
+
</div>
|
|
2734
|
+
`,
|
|
2735
|
+
}]
|
|
2736
|
+
}], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }] } });
|
|
2737
|
+
|
|
2738
|
+
/* Theme — import theme.css in your global styles.css:
|
|
2739
|
+
@import '@shirkasoft/ui-components/theme.css';
|
|
2740
|
+
Override --shk-* CSS variables at :root or .dark to theme.
|
|
2741
|
+
*/
|
|
2742
|
+
|
|
2743
|
+
/**
|
|
2744
|
+
* Generated bundle index. Do not edit.
|
|
2745
|
+
*/
|
|
2746
|
+
|
|
2747
|
+
export { ConfirmDialogComponent, ConfirmDialogService, DatePickerComponent, FileUploadComponent, ModalComponent, ModalService, NotificationComponent, NotificationService, PriceInputComponent, SelectComponent, TableComponent, TextFieldComponent, ToggleComponent, TooltipComponent };
|
|
2748
|
+
//# sourceMappingURL=shirkasoft-ui-components.mjs.map
|