@haloduck/ui 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2857 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, Input, Component, inject, ChangeDetectorRef, forwardRef, ViewChild, signal, EventEmitter, Output, ViewContainerRef, NgZone, isDevMode, ChangeDetectionStrategy } from '@angular/core';
3
+ import { signIn, confirmSignIn, resetPassword, confirmResetPassword } from 'aws-amplify/auth';
4
+ import * as i1$1 from '@angular/forms';
5
+ import { NG_VALUE_ACCESSOR, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
6
+ import { BehaviorSubject, zip, Subject, takeUntil, tap, combineLatest, switchMap, of, Observable, distinctUntilChanged, map, take } from 'rxjs';
7
+ import { ulid } from 'ulid';
8
+ import * as i1 from '@angular/common';
9
+ import { CommonModule, DecimalPipe, DatePipe } from '@angular/common';
10
+ import * as i1$2 from '@jsverse/transloco';
11
+ import { provideTranslocoScope, TranslocoModule, TranslocoService } from '@jsverse/transloco';
12
+ import * as i2 from '@angular/common/http';
13
+ import * as i1$3 from '@angular/cdk/overlay';
14
+ import { Overlay } from '@angular/cdk/overlay';
15
+ import { ComponentPortal } from '@angular/cdk/portal';
16
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
17
+ import { CoreService } from '@haloduck/core';
18
+ import * as i1$5 from '@angular/router';
19
+ import { Router, NavigationEnd, RouterLink } from '@angular/router';
20
+ import * as i1$4 from '@angular/platform-browser';
21
+ import { Title } from '@angular/platform-browser';
22
+ import * as THREE from 'three';
23
+ import { STLLoader, OrbitControls } from 'three-stdlib';
24
+ import { download } from '@haloduck/util';
25
+ import { filter } from 'rxjs/operators';
26
+
27
+ class NotificationService {
28
+ listNotification$ = new BehaviorSubject([]);
29
+ listNotification = [];
30
+ showNotification(title, body, payload = null, timeout) {
31
+ if (window.FlutterApp) {
32
+ window.FlutterApp.postMessage(JSON.stringify({
33
+ action: 'showNotification',
34
+ title: title,
35
+ body: body,
36
+ payload: payload,
37
+ timeout: timeout,
38
+ }));
39
+ }
40
+ else if ('Notification' in window &&
41
+ Notification.permission === 'granted') {
42
+ // 웹브라우저에서 실행될 때는 기본 브라우저 알림 사용
43
+ new Notification(title, { body: body });
44
+ }
45
+ else {
46
+ // 기본 내부 알림 시스템 사용
47
+ this._showNotification('info', body, timeout ? timeout : undefined);
48
+ }
49
+ }
50
+ _showNotification(type, message, timeOut) {
51
+ const id = ulid();
52
+ const notification = { id, type, message, timeOut };
53
+ this.listNotification.push(notification);
54
+ this.listNotification$.next(this.listNotification);
55
+ if (timeOut) {
56
+ setTimeout(() => {
57
+ this.listNotification = this.listNotification.filter((n) => n.id !== id);
58
+ this.listNotification$.next(this.listNotification);
59
+ }, timeOut);
60
+ }
61
+ return id;
62
+ }
63
+ getListNotification() {
64
+ return this.listNotification$.asObservable();
65
+ }
66
+ removeNotificationById(id) {
67
+ this.listNotification = this.listNotification.filter((n) => n.id !== id);
68
+ this.listNotification$.next(this.listNotification);
69
+ }
70
+ constructor() { }
71
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
72
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: NotificationService, providedIn: 'root' });
73
+ }
74
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: NotificationService, decorators: [{
75
+ type: Injectable,
76
+ args: [{
77
+ providedIn: 'root',
78
+ }]
79
+ }], ctorParameters: () => [] });
80
+
81
+ class ButtonComponent {
82
+ disabled = false;
83
+ variant = 'primary';
84
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
85
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: ButtonComponent, isStandalone: true, selector: "haloduck-button", inputs: { disabled: "disabled", variant: "variant" }, ngImport: i0, template: "<button type=\"button\"\n [disabled]=\"disabled\"\n [ngClass]=\"{\n 'bg-light-primary text-light-on-primary dark:bg-dark-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'bg-light-primary-light text-light-on-primary-light dark:bg-dark-primary-light dark:text-dark-on-primary-light': variant === 'secondary' && !disabled,\n 'bg-light-danger text-light-on-danger dark:bg-dark-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n 'bg-light-background text-light-on-background dark:bg-dark-background dark:text-dark-on-background border border-light-on-background dark:border-dark-on-background' : variant === 'none' && !disabled,\n 'bg-light-inactive text-light-on-inactive dark:bg-dark-inactive dark:text-dark-on-inactive active:animate-bounce hover:cursor-not-allowed': disabled,\n 'active:scale-95 transition-transform': !disabled,\n 'hover:cursor-pointer': !disabled\n }\"\n class=\"px-4 py-1.5 rounded-lg text-sm/6 w-full text-nowrap\">\n <ng-content></ng-content>\n</button>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
86
+ }
87
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ButtonComponent, decorators: [{
88
+ type: Component,
89
+ args: [{ selector: 'haloduck-button', imports: [CommonModule], template: "<button type=\"button\"\n [disabled]=\"disabled\"\n [ngClass]=\"{\n 'bg-light-primary text-light-on-primary dark:bg-dark-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'bg-light-primary-light text-light-on-primary-light dark:bg-dark-primary-light dark:text-dark-on-primary-light': variant === 'secondary' && !disabled,\n 'bg-light-danger text-light-on-danger dark:bg-dark-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n 'bg-light-background text-light-on-background dark:bg-dark-background dark:text-dark-on-background border border-light-on-background dark:border-dark-on-background' : variant === 'none' && !disabled,\n 'bg-light-inactive text-light-on-inactive dark:bg-dark-inactive dark:text-dark-on-inactive active:animate-bounce hover:cursor-not-allowed': disabled,\n 'active:scale-95 transition-transform': !disabled,\n 'hover:cursor-pointer': !disabled\n }\"\n class=\"px-4 py-1.5 rounded-lg text-sm/6 w-full text-nowrap\">\n <ng-content></ng-content>\n</button>\n" }]
90
+ }], propDecorators: { disabled: [{
91
+ type: Input
92
+ }], variant: [{
93
+ type: Input
94
+ }] } });
95
+
96
+ class InputComponent {
97
+ cdr = inject(ChangeDetectorRef);
98
+ label;
99
+ inputElement;
100
+ placeholder = '';
101
+ type = 'text';
102
+ disabled = false;
103
+ rows = 1;
104
+ autofocus = false;
105
+ value = '';
106
+ onChange = (value) => { };
107
+ onTouched = () => { };
108
+ writeValue(value) {
109
+ this.value = value;
110
+ }
111
+ registerOnChange(fn) {
112
+ this.onChange = fn;
113
+ }
114
+ registerOnTouched(fn) {
115
+ this.onTouched = fn;
116
+ }
117
+ setDisabledState(isDisabled) {
118
+ this.disabled = isDisabled;
119
+ }
120
+ focus() {
121
+ this.inputElement?.nativeElement?.focus();
122
+ }
123
+ onInput(event) {
124
+ const input = event.target;
125
+ this.value = input.value;
126
+ this.onChange(this.value);
127
+ this.onTouched();
128
+ if (this.rows > 1) {
129
+ input.style.height = 'auto';
130
+ input.style.height = input.scrollHeight + 'px';
131
+ }
132
+ }
133
+ onKeydown($event) {
134
+ if (this.rows === 1 && $event.key === 'Enter') {
135
+ $event.preventDefault();
136
+ $event.stopPropagation();
137
+ }
138
+ }
139
+ ngAfterViewInit() {
140
+ // hide label if no content.
141
+ if (!this.label.nativeElement.innerText.trim()) {
142
+ this.label.nativeElement.style.display = 'none';
143
+ }
144
+ if (this.autofocus) {
145
+ // Ensure change detection is complete and view is stable
146
+ this.cdr.detectChanges();
147
+ setTimeout(() => {
148
+ if (this.inputElement?.nativeElement instanceof HTMLElement) {
149
+ this.inputElement.nativeElement.focus();
150
+ }
151
+ }, 100);
152
+ }
153
+ }
154
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: InputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
155
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: InputComponent, isStandalone: true, selector: "haloduck-input", inputs: { placeholder: "placeholder", type: "type", disabled: "disabled", rows: "rows", autofocus: "autofocus", value: "value" }, providers: [
156
+ {
157
+ provide: NG_VALUE_ACCESSOR,
158
+ useExisting: forwardRef(() => InputComponent),
159
+ multi: true,
160
+ },
161
+ provideTranslocoScope('haloduck'),
162
+ ], viewQueries: [{ propertyName: "label", first: true, predicate: ["label"], descendants: true }, { propertyName: "inputElement", first: true, predicate: ["input"], descendants: true }], ngImport: i0, template: "<div class=\"flex flex-col gap-2\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control text-left\">\n <ng-content></ng-content>\n </label>\n @if (rows > 1) {\n <textarea #input\n [value]=\"value\"\n (input)=\"onInput($event)\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n [rows]=\"rows\"\n (keydown)=\"onKeydown($event)\"\n class=\"block w-full rounded-md bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/80 px-3 py-1.5 text-base text-light-on-control dark:text-dark-on-control disabled:cursor-not-allowed disabled:text-light-on-control/60 dark:disabled:text-dark-on-control/80 outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary sm:text-sm/6\"></textarea>\n }\n @else {\n <input #input\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"onInput($event)\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n (keydown)=\"onKeydown($event)\"\n class=\"block w-full rounded-md bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/80 px-3 py-1.5 text-base text-light-on-control dark:text-dark-on-control disabled:cursor-not-allowed disabled:text-light-on-control/60 dark:disabled:text-dark-on-control/80 outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary sm:text-sm/6\">\n }\n</div>\n", styles: [""] });
163
+ }
164
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: InputComponent, decorators: [{
165
+ type: Component,
166
+ args: [{ selector: 'haloduck-input', providers: [
167
+ {
168
+ provide: NG_VALUE_ACCESSOR,
169
+ useExisting: forwardRef(() => InputComponent),
170
+ multi: true,
171
+ },
172
+ provideTranslocoScope('haloduck'),
173
+ ], template: "<div class=\"flex flex-col gap-2\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control text-left\">\n <ng-content></ng-content>\n </label>\n @if (rows > 1) {\n <textarea #input\n [value]=\"value\"\n (input)=\"onInput($event)\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n [rows]=\"rows\"\n (keydown)=\"onKeydown($event)\"\n class=\"block w-full rounded-md bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/80 px-3 py-1.5 text-base text-light-on-control dark:text-dark-on-control disabled:cursor-not-allowed disabled:text-light-on-control/60 dark:disabled:text-dark-on-control/80 outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary sm:text-sm/6\"></textarea>\n }\n @else {\n <input #input\n [type]=\"type\"\n [value]=\"value\"\n (input)=\"onInput($event)\"\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n (keydown)=\"onKeydown($event)\"\n class=\"block w-full rounded-md bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/80 px-3 py-1.5 text-base text-light-on-control dark:text-dark-on-control disabled:cursor-not-allowed disabled:text-light-on-control/60 dark:disabled:text-dark-on-control/80 outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary sm:text-sm/6\">\n }\n</div>\n" }]
174
+ }], propDecorators: { label: [{
175
+ type: ViewChild,
176
+ args: ['label']
177
+ }], inputElement: [{
178
+ type: ViewChild,
179
+ args: ['input']
180
+ }], placeholder: [{
181
+ type: Input
182
+ }], type: [{
183
+ type: Input
184
+ }], disabled: [{
185
+ type: Input
186
+ }], rows: [{
187
+ type: Input
188
+ }], autofocus: [{
189
+ type: Input
190
+ }], value: [{
191
+ type: Input
192
+ }] } });
193
+
194
+ // auth.component.ts
195
+ class AuthenticateComponent {
196
+ fb;
197
+ http;
198
+ notificationService = inject(NotificationService);
199
+ loginForm;
200
+ signupForm;
201
+ resetForm;
202
+ newPasswordForm;
203
+ otpForm;
204
+ stage = 'login';
205
+ emailForReset = '';
206
+ constructor(fb, http) {
207
+ this.fb = fb;
208
+ this.http = http;
209
+ this.loginForm = fb.group({
210
+ email: ['', [Validators.required, Validators.email]],
211
+ password: ['', Validators.required],
212
+ });
213
+ this.signupForm = fb.group({
214
+ email: ['', [Validators.required, Validators.email]],
215
+ password: ['', Validators.required],
216
+ });
217
+ this.resetForm = fb.group({
218
+ email: ['', [Validators.required, Validators.email]],
219
+ });
220
+ this.newPasswordForm = fb.group({
221
+ newPassword: ['', Validators.required],
222
+ });
223
+ this.otpForm = fb.group({
224
+ code: ['', Validators.required],
225
+ newPassword: ['', Validators.required],
226
+ });
227
+ }
228
+ switchStage(stage) {
229
+ this.stage = stage;
230
+ }
231
+ login() {
232
+ signIn({
233
+ username: this.loginForm.value.email,
234
+ password: this.loginForm.value.password,
235
+ options: {
236
+ authFlowType: 'USER_PASSWORD_AUTH',
237
+ },
238
+ })
239
+ .then((res) => {
240
+ console.log('res', res);
241
+ console.log('res.nextStep.signInStep', res.nextStep.signInStep);
242
+ if (res.nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED') {
243
+ this.switchStage('newPassword');
244
+ }
245
+ else {
246
+ this.notificationService.showNotification('success', 'Successfully signed in.', 3000);
247
+ }
248
+ })
249
+ .catch((err) => {
250
+ if (err.code === 'NotAuthorizedException') {
251
+ this.switchStage('reset');
252
+ }
253
+ else {
254
+ this.notificationService.showNotification('error', 'Failed to sign in.');
255
+ }
256
+ });
257
+ }
258
+ confirmNewPassword() {
259
+ confirmSignIn({
260
+ challengeResponse: this.newPasswordForm.value.newPassword,
261
+ })
262
+ .then(() => {
263
+ this.notificationService.showNotification('success', 'Successfully signed in.', 3000);
264
+ })
265
+ .catch((err) => {
266
+ console.log('err', JSON.stringify(err));
267
+ if (err.code === 'NotAuthorizedException') {
268
+ this.notificationService.showNotification('error', 'Failed to sign in.');
269
+ }
270
+ else if (err.code === 'InvalidPasswordException') {
271
+ this.notificationService.showNotification('error', 'Invalid password.');
272
+ }
273
+ else {
274
+ this.notificationService.showNotification('error', 'Failed to confirm new password.');
275
+ }
276
+ });
277
+ }
278
+ signup() {
279
+ this.http.post('/api/signup', this.signupForm.value).subscribe(console.log);
280
+ }
281
+ reset() {
282
+ resetPassword({ username: this.resetForm.value.email })
283
+ .then(() => {
284
+ this.switchStage('otpVerify');
285
+ })
286
+ .catch((err) => {
287
+ this.notificationService.showNotification('error', 'Failed to request verification code.');
288
+ });
289
+ }
290
+ verifyOtp() {
291
+ confirmResetPassword({
292
+ username: this.resetForm.value.email,
293
+ confirmationCode: this.otpForm.value.code,
294
+ newPassword: this.otpForm.value.newPassword,
295
+ })
296
+ .then(() => {
297
+ this.notificationService.showNotification('success', 'Successfully reset password.', 3000);
298
+ this.switchStage('login');
299
+ })
300
+ .catch((err) => {
301
+ this.notificationService.showNotification('error', 'Failed to reset password.');
302
+ });
303
+ }
304
+ get passwordValue() {
305
+ return this.newPasswordForm.get('newPassword')?.value || '';
306
+ }
307
+ get isMinLength() {
308
+ return this.passwordValue.length >= 8;
309
+ }
310
+ get hasUppercase() {
311
+ return /[A-Z]/.test(this.passwordValue);
312
+ }
313
+ get hasLowercase() {
314
+ return /[a-z]/.test(this.passwordValue);
315
+ }
316
+ get hasNumber() {
317
+ return /[0-9]/.test(this.passwordValue);
318
+ }
319
+ get hasSpecialChar() {
320
+ return /[^A-Za-z0-9]/.test(this.passwordValue);
321
+ }
322
+ get isPasswordValid() {
323
+ return this.isMinLength && this.hasUppercase && this.hasLowercase && this.hasNumber && this.hasSpecialChar;
324
+ }
325
+ get passwordRules() {
326
+ return [
327
+ { label: 'minimum of 8 characters', valid: this.isMinLength },
328
+ { label: 'one uppercase letter', valid: this.hasUppercase },
329
+ { label: 'one lowercase letter', valid: this.hasLowercase },
330
+ { label: 'one numeric digit', valid: this.hasNumber },
331
+ { label: 'one special character', valid: this.hasSpecialChar },
332
+ ];
333
+ }
334
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthenticateComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
335
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: AuthenticateComponent, isStandalone: true, selector: "haloduck-authenticate", ngImport: i0, template: "<div class=\"flex flex-col items-center justify-center h-full\">\n <!-- auth.component.html -->\n <div class=\"w-full max-w-md mx-auto mt-20 p-8 shadow-lg rounded-2xl bg-light-background dark:bg-dark-background\">\n\n @switch (stage) {\n @case ('login') {\n <!-- \uB85C\uADF8\uC778 -->\n <form [formGroup]=\"loginForm\"\n (ngSubmit)=\"login()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-login-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-input data-testid=\"authenticate-login-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\" />\n <haloduck-button data-testid=\"authenticate-login-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"login()\">Sign In</haloduck-button>\n </form>\n }\n @case ('signup') {\n <!-- \uD68C\uC6D0\uAC00\uC785 -->\n <form [formGroup]=\"signupForm\"\n (ngSubmit)=\"signup()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-signup-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-input data-testid=\"authenticate-signup-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\" />\n <haloduck-button data-testid=\"authenticate-signup-submit\"\n type=\"submit\"\n variant=\"primary\">Sign Up</haloduck-button>\n </form>\n }\n @case ('reset') {\n <!-- \uBE44\uBC00\uBC88\uD638 \uC7AC\uC124\uC815 \uC694\uCCAD -->\n <form [formGroup]=\"resetForm\"\n (ngSubmit)=\"reset()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-reset-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-button data-testid=\"authenticate-reset-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"reset()\">Send Verification Code</haloduck-button>\n </form>\n }\n @case ('otpVerify') {\n <!-- OTP \uC778\uC99D \uD6C4 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uC785\uB825 -->\n <form [formGroup]=\"otpForm\"\n (ngSubmit)=\"verifyOtp()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-otp-code\"\n type=\"text\"\n formControlName=\"code\"\n placeholder=\"Verification Code\" />\n <haloduck-input data-testid=\"authenticate-otp-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\" />\n <haloduck-button data-testid=\"authenticate-otp-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"verifyOtp()\">Reset Password</haloduck-button>\n </form>\n\n }\n @case ('newPassword') {\n <!-- \uC784\uC2DC \uBE44\uBC00\uBC88\uD638 \u2192 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD -->\n <form [formGroup]=\"newPasswordForm\"\n (ngSubmit)=\"confirmNewPassword()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-new-password-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\">\n </haloduck-input>\n <div class=\"text-sm flex flex-col\">\n @for (rule of passwordRules; track rule.label) {\n <span [class.text-light-secondary]=\"rule.valid && passwordValue !== ''\"\n [class.line-through]=\"rule.valid && passwordValue !== ''\"\n [class.text-light-danger]=\"!rule.valid && passwordValue !== ''\"\n [class.text-light-inactive]=\"passwordValue === ''\">\n {{ rule.valid ? '\u2714' : '\u2717' }} {{ rule.label }}\n </span>\n }\n </div>\n <haloduck-button data-testid=\"authenticate-new-password-submit\"\n type=\"submit\"\n (click)=\"confirmNewPassword()\"\n variant=\"primary\"\n [disabled]=\"!isPasswordValid\">Change Password</haloduck-button>\n </form>\n }\n }\n\n <!-- \uB2E8\uACC4 \uC804\uD658 \uD0ED -->\n <div class=\"flex justify-center mt-6 gap-4\">\n @if (stage !== 'login') {\n <haloduck-button data-testid=\"authenticate-to-signin\"\n variant=\"none\"\n (click)=\"switchStage('login')\">to Sign In</haloduck-button>\n }\n <!-- <haloduck-button class=\"text-sm text-blue-600 hover:underline\"\n (click)=\"switchStage('signup')\">\uD68C\uC6D0\uAC00\uC785</haloduck-button> -->\n @if (stage !== 'reset' && stage !== 'otpVerify' && stage !== 'newPassword') {\n <haloduck-button data-testid=\"authenticate-to-reset-password\"\n variant=\"secondary\"\n (click)=\"switchStage('reset')\">Reset Password</haloduck-button>\n }\n </div>\n\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: InputComponent, selector: "haloduck-input", inputs: ["placeholder", "type", "disabled", "rows", "autofocus", "value"] }, { kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }] });
336
+ }
337
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthenticateComponent, decorators: [{
338
+ type: Component,
339
+ args: [{ selector: 'haloduck-authenticate', imports: [FormsModule, ReactiveFormsModule, InputComponent, ButtonComponent], template: "<div class=\"flex flex-col items-center justify-center h-full\">\n <!-- auth.component.html -->\n <div class=\"w-full max-w-md mx-auto mt-20 p-8 shadow-lg rounded-2xl bg-light-background dark:bg-dark-background\">\n\n @switch (stage) {\n @case ('login') {\n <!-- \uB85C\uADF8\uC778 -->\n <form [formGroup]=\"loginForm\"\n (ngSubmit)=\"login()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-login-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-input data-testid=\"authenticate-login-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\" />\n <haloduck-button data-testid=\"authenticate-login-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"login()\">Sign In</haloduck-button>\n </form>\n }\n @case ('signup') {\n <!-- \uD68C\uC6D0\uAC00\uC785 -->\n <form [formGroup]=\"signupForm\"\n (ngSubmit)=\"signup()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-signup-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-input data-testid=\"authenticate-signup-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\" />\n <haloduck-button data-testid=\"authenticate-signup-submit\"\n type=\"submit\"\n variant=\"primary\">Sign Up</haloduck-button>\n </form>\n }\n @case ('reset') {\n <!-- \uBE44\uBC00\uBC88\uD638 \uC7AC\uC124\uC815 \uC694\uCCAD -->\n <form [formGroup]=\"resetForm\"\n (ngSubmit)=\"reset()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-reset-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\" />\n <haloduck-button data-testid=\"authenticate-reset-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"reset()\">Send Verification Code</haloduck-button>\n </form>\n }\n @case ('otpVerify') {\n <!-- OTP \uC778\uC99D \uD6C4 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uC785\uB825 -->\n <form [formGroup]=\"otpForm\"\n (ngSubmit)=\"verifyOtp()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-otp-code\"\n type=\"text\"\n formControlName=\"code\"\n placeholder=\"Verification Code\" />\n <haloduck-input data-testid=\"authenticate-otp-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\" />\n <haloduck-button data-testid=\"authenticate-otp-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"verifyOtp()\">Reset Password</haloduck-button>\n </form>\n\n }\n @case ('newPassword') {\n <!-- \uC784\uC2DC \uBE44\uBC00\uBC88\uD638 \u2192 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD -->\n <form [formGroup]=\"newPasswordForm\"\n (ngSubmit)=\"confirmNewPassword()\"\n class=\"flex flex-col gap-4\">\n <haloduck-input data-testid=\"authenticate-new-password-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\">\n </haloduck-input>\n <div class=\"text-sm flex flex-col\">\n @for (rule of passwordRules; track rule.label) {\n <span [class.text-light-secondary]=\"rule.valid && passwordValue !== ''\"\n [class.line-through]=\"rule.valid && passwordValue !== ''\"\n [class.text-light-danger]=\"!rule.valid && passwordValue !== ''\"\n [class.text-light-inactive]=\"passwordValue === ''\">\n {{ rule.valid ? '\u2714' : '\u2717' }} {{ rule.label }}\n </span>\n }\n </div>\n <haloduck-button data-testid=\"authenticate-new-password-submit\"\n type=\"submit\"\n (click)=\"confirmNewPassword()\"\n variant=\"primary\"\n [disabled]=\"!isPasswordValid\">Change Password</haloduck-button>\n </form>\n }\n }\n\n <!-- \uB2E8\uACC4 \uC804\uD658 \uD0ED -->\n <div class=\"flex justify-center mt-6 gap-4\">\n @if (stage !== 'login') {\n <haloduck-button data-testid=\"authenticate-to-signin\"\n variant=\"none\"\n (click)=\"switchStage('login')\">to Sign In</haloduck-button>\n }\n <!-- <haloduck-button class=\"text-sm text-blue-600 hover:underline\"\n (click)=\"switchStage('signup')\">\uD68C\uC6D0\uAC00\uC785</haloduck-button> -->\n @if (stage !== 'reset' && stage !== 'otpVerify' && stage !== 'newPassword') {\n <haloduck-button data-testid=\"authenticate-to-reset-password\"\n variant=\"secondary\"\n (click)=\"switchStage('reset')\">Reset Password</haloduck-button>\n }\n </div>\n\n </div>\n</div>\n" }]
340
+ }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i2.HttpClient }] });
341
+
342
+ class SelectDropdownComponent {
343
+ _filteredOptions = signal([], ...(ngDevMode ? [{ debugName: "_filteredOptions" }] : []));
344
+ _options = [];
345
+ _selectedOptionIds = [];
346
+ selectedChange = new EventEmitter();
347
+ useFilter = false;
348
+ multiselect = true;
349
+ atLeastOne = false;
350
+ asButton = false;
351
+ set options(value) {
352
+ this._options = value || [];
353
+ this._filteredOptions.set(this._options);
354
+ }
355
+ get options() {
356
+ return this._filteredOptions();
357
+ }
358
+ set selectedOptionIds(value) {
359
+ this._selectedOptionIds = value || [];
360
+ }
361
+ get selectedOptions() {
362
+ return this._selectedOptionIds;
363
+ }
364
+ onFilterInput(event) {
365
+ const value = event.target.value.toLowerCase();
366
+ this._filteredOptions.set(this._options.filter((opt) => opt.value.toLowerCase().includes(value)));
367
+ }
368
+ onToggleOption(option) {
369
+ if (this.asButton) {
370
+ this._selectedOptionIds = [option.id || option.value];
371
+ }
372
+ else {
373
+ if (this.multiselect) {
374
+ if (!this._selectedOptionIds.includes(option.id || option.value)) {
375
+ this._selectedOptionIds.push(option.id || option.value);
376
+ }
377
+ else {
378
+ this._selectedOptionIds.splice(this._selectedOptionIds.indexOf(option.id || option.value), 1);
379
+ }
380
+ }
381
+ else {
382
+ if (this.atLeastOne) {
383
+ this._selectedOptionIds = [option.id || option.value];
384
+ }
385
+ else {
386
+ if (this._selectedOptionIds.includes(option.id || option.value)) {
387
+ this._selectedOptionIds = [];
388
+ }
389
+ else {
390
+ this._selectedOptionIds = [option.id || option.value];
391
+ }
392
+ }
393
+ }
394
+ }
395
+ // prevent [null]
396
+ this._selectedOptionIds = this._selectedOptionIds.filter((id) => id !== null);
397
+ this.selectedChange.emit(this._selectedOptionIds);
398
+ }
399
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
400
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SelectDropdownComponent, isStandalone: true, selector: "haloduck-select-dropdown", inputs: { useFilter: "useFilter", multiselect: "multiselect", atLeastOne: "atLeastOne", asButton: "asButton", options: "options", selectedOptionIds: "selectedOptionIds" }, outputs: { selectedChange: "selectedChange" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<div id=\"dropdown\"\n class=\"max-w-full mt-2 absolute z-40 bg-light-background dark:bg-dark-background text-light-on-background dark:text-dark-on-background border border-light-inactive dark:border-dark-inactive rounded max-h-60 flex flex-col gap-2\">\n @if (useFilter && _options.length >= 5) {\n <input #inputFilter\n id=\"inputFilter\"\n type=\"text\"\n [placeholder]=\"'haloduck.ui.select.Keyword...' | transloco\"\n (input)=\"onFilterInput($event)\"\n class=\"text-light-inactive dark:text-dark-inactive rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary px-3 py-1.5 text-sm/6 bg-light-control dark:bg-dark-control m-2\" />\n }\n <div class=\"overflow-y-auto\">\n @for ( option of _filteredOptions(); track (option.id) ? option.id : option.value) {\n <div class=\"px-3 py-2 text-sm/6 cursor-pointer hover:bg-light-secondary/60 dark:hover:bg-dark-secondary/60 flex items-center justify-start whitespace-nowrap\"\n (click)=\"onToggleOption(option)\">\n @if(!asButton) {\n @if( _selectedOptionIds.includes(option.id || option.value)) {\n <svg class=\"w-4 h-4 text-light-primary dark:text-dark-primary inline-block mr-2\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M16.707 5.293a1 1 0 00-1.414 0L8 12.586 4.707 9.293a1 1 0 00-1.414 1.414l4 4a1 1 0 001.414 0l8-8a1 1 0 000-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <div class=\"w-4 h-4 inline-block mr-2\"></div>\n }\n }\n {{ option.value }}\n </div>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
401
+ }
402
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectDropdownComponent, decorators: [{
403
+ type: Component,
404
+ args: [{ selector: 'haloduck-select-dropdown', imports: [TranslocoModule], providers: [provideTranslocoScope('haloduck')], template: "<div id=\"dropdown\"\n class=\"max-w-full mt-2 absolute z-40 bg-light-background dark:bg-dark-background text-light-on-background dark:text-dark-on-background border border-light-inactive dark:border-dark-inactive rounded max-h-60 flex flex-col gap-2\">\n @if (useFilter && _options.length >= 5) {\n <input #inputFilter\n id=\"inputFilter\"\n type=\"text\"\n [placeholder]=\"'haloduck.ui.select.Keyword...' | transloco\"\n (input)=\"onFilterInput($event)\"\n class=\"text-light-inactive dark:text-dark-inactive rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary px-3 py-1.5 text-sm/6 bg-light-control dark:bg-dark-control m-2\" />\n }\n <div class=\"overflow-y-auto\">\n @for ( option of _filteredOptions(); track (option.id) ? option.id : option.value) {\n <div class=\"px-3 py-2 text-sm/6 cursor-pointer hover:bg-light-secondary/60 dark:hover:bg-dark-secondary/60 flex items-center justify-start whitespace-nowrap\"\n (click)=\"onToggleOption(option)\">\n @if(!asButton) {\n @if( _selectedOptionIds.includes(option.id || option.value)) {\n <svg class=\"w-4 h-4 text-light-primary dark:text-dark-primary inline-block mr-2\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M16.707 5.293a1 1 0 00-1.414 0L8 12.586 4.707 9.293a1 1 0 00-1.414 1.414l4 4a1 1 0 001.414 0l8-8a1 1 0 000-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <div class=\"w-4 h-4 inline-block mr-2\"></div>\n }\n }\n {{ option.value }}\n </div>\n }\n </div>\n</div>\n" }]
405
+ }], propDecorators: { selectedChange: [{
406
+ type: Output
407
+ }], useFilter: [{
408
+ type: Input
409
+ }], multiselect: [{
410
+ type: Input
411
+ }], atLeastOne: [{
412
+ type: Input
413
+ }], asButton: [{
414
+ type: Input
415
+ }], options: [{
416
+ type: Input
417
+ }], selectedOptionIds: [{
418
+ type: Input
419
+ }] } });
420
+
421
+ class SelectComponent {
422
+ translateService = inject(TranslocoService);
423
+ overlay = inject(Overlay);
424
+ viewContainerRef = inject(ViewContainerRef);
425
+ overlayRef = null;
426
+ selectedChange = new EventEmitter();
427
+ disabled = false;
428
+ loading = false;
429
+ variant = 'none';
430
+ asButton = false;
431
+ useIcon = false;
432
+ useFilter = false;
433
+ multiselect = true;
434
+ placeholder = '';
435
+ atLeastOne = false;
436
+ showAll = false;
437
+ options = null;
438
+ set value(value) {
439
+ this.writeValue(value);
440
+ this.onChange(this.getSelectedValue());
441
+ }
442
+ get value() {
443
+ if (this.multiselect) {
444
+ return this.selectedOptionIds;
445
+ }
446
+ else {
447
+ if (this.selectedOptionIds.length === 0) {
448
+ return null;
449
+ }
450
+ return this.selectedOptionIds[0];
451
+ }
452
+ }
453
+ origin;
454
+ label;
455
+ isDropdownOpen = signal(false, ...(ngDevMode ? [{ debugName: "isDropdownOpen" }] : []));
456
+ selectedOptionIds = [];
457
+ get selectedOptions() {
458
+ if (this.options) {
459
+ return this.options.filter((option) => this.selectedOptionIds.includes(option.id || option.value));
460
+ }
461
+ return [];
462
+ }
463
+ onClick(event) {
464
+ if (this.disabled) {
465
+ return;
466
+ }
467
+ const originWidth = this.origin?.nativeElement?.offsetWidth || 0;
468
+ if (!this.overlayRef) {
469
+ this.overlayRef = this.overlay.create({
470
+ positionStrategy: this.overlay
471
+ .position()
472
+ .flexibleConnectedTo(this.origin.nativeElement)
473
+ .withPositions([
474
+ {
475
+ originX: 'start',
476
+ originY: 'bottom',
477
+ overlayX: 'start',
478
+ overlayY: 'top',
479
+ },
480
+ {
481
+ originX: 'start',
482
+ originY: 'top',
483
+ overlayX: 'start',
484
+ overlayY: 'bottom',
485
+ },
486
+ ]),
487
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
488
+ hasBackdrop: true,
489
+ backdropClass: 'cdk-overlay-transparent-backdrop',
490
+ minWidth: originWidth,
491
+ });
492
+ this.overlayRef.backdropClick().subscribe(() => {
493
+ this.isDropdownOpen.set(false);
494
+ this.overlayRef?.detach();
495
+ });
496
+ }
497
+ if (!this.overlayRef.hasAttached()) {
498
+ const portal = new ComponentPortal(SelectDropdownComponent, this.viewContainerRef);
499
+ const componentRef = this.overlayRef.attach(portal);
500
+ componentRef.instance.options = this.options;
501
+ componentRef.instance.selectedOptionIds = this.selectedOptionIds;
502
+ componentRef.instance.useFilter = this.useFilter;
503
+ componentRef.instance.multiselect = this.multiselect;
504
+ componentRef.instance.atLeastOne = this.atLeastOne;
505
+ componentRef.instance.asButton = this.asButton;
506
+ componentRef.instance.selectedChange.subscribe((selectedOptionIds) => {
507
+ this.selectedChange.emit(selectedOptionIds);
508
+ this.selectedOptionIds = selectedOptionIds;
509
+ this.onChange(this.getSelectedValue());
510
+ if (!this.multiselect) {
511
+ this.isDropdownOpen.set(false);
512
+ this.overlayRef?.detach();
513
+ }
514
+ });
515
+ this.isDropdownOpen.set(true);
516
+ }
517
+ else {
518
+ this.isDropdownOpen.set(false);
519
+ this.overlayRef.detach();
520
+ }
521
+ }
522
+ onKeyDown(event) {
523
+ if (this.disabled) {
524
+ return;
525
+ }
526
+ if (event.key === 'Enter') {
527
+ }
528
+ }
529
+ onDeselectOption(event, option) {
530
+ if (this.disabled) {
531
+ return;
532
+ }
533
+ event.stopPropagation();
534
+ event.preventDefault();
535
+ this.selectedOptionIds = this.selectedOptionIds.filter((selected) => selected !== (option.id || option.value));
536
+ this.onChange(this.getSelectedValue());
537
+ this.selectedChange.emit(this.selectedOptionIds);
538
+ this.isDropdownOpen.set(false);
539
+ }
540
+ getSelectedValue() {
541
+ if (!this.multiselect) {
542
+ if (this.selectedOptionIds.length > 0) {
543
+ return this.selectedOptionIds[0];
544
+ }
545
+ else {
546
+ return null;
547
+ }
548
+ }
549
+ else {
550
+ return this.selectedOptionIds;
551
+ }
552
+ }
553
+ onChange = (value) => { };
554
+ onTouched = () => { };
555
+ writeValue(value) {
556
+ if (Array.isArray(value)) {
557
+ this.selectedOptionIds = value;
558
+ }
559
+ else {
560
+ this.selectedOptionIds = [value];
561
+ }
562
+ }
563
+ registerOnChange(fn) {
564
+ this.onChange = fn;
565
+ }
566
+ registerOnTouched(fn) {
567
+ this.onTouched = fn;
568
+ }
569
+ setDisabledState(isDisabled) {
570
+ this.disabled = isDisabled;
571
+ }
572
+ ngAfterViewInit() {
573
+ // hide label if no content.
574
+ if (!this.label.nativeElement.innerText.trim()) {
575
+ this.label.nativeElement.style.display = 'none';
576
+ }
577
+ }
578
+ constructor() {
579
+ this.translateService
580
+ .selectTranslate('ui.select.Select...', {}, 'haloduck')
581
+ .subscribe((translation) => {
582
+ if (!this.placeholder || this.placeholder === '') {
583
+ this.placeholder = translation;
584
+ }
585
+ });
586
+ }
587
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
588
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SelectComponent, isStandalone: true, selector: "haloduck-select", inputs: { disabled: "disabled", loading: "loading", variant: "variant", asButton: "asButton", useIcon: "useIcon", useFilter: "useFilter", multiselect: "multiselect", placeholder: "placeholder", atLeastOne: "atLeastOne", showAll: "showAll", options: "options", value: "value" }, outputs: { selectedChange: "selectedChange" }, providers: [
589
+ {
590
+ provide: NG_VALUE_ACCESSOR,
591
+ useExisting: forwardRef(() => SelectComponent),
592
+ multi: true,
593
+ },
594
+ provideTranslocoScope('haloduck'),
595
+ ], viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }, { propertyName: "label", first: true, predicate: ["label"], descendants: true }], ngImport: i0, template: "<div class=\"flex flex-col gap-2 items-start w-full\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control w-full\">\n <ng-content></ng-content>\n </label>\n <div #origin\n class=\"w-full flex-1 relative overflow-visible rounded-md outline outline-light-inactive dark:outline-dark-inactive text-sm/6\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary focus:dark:outline-dark-primary': !disabled,\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary' : variant === 'primary' && !disabled,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary' : variant === 'secondary' && !disabled,\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger' : variant === 'danger' && !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick($event)\"\n (keydown)=\"onKeyDown($event)\">\n <div class=\"px-3 py-1.5 text-sm/6 cursor-pointer flex flex-nowrap items-center justify-between overflow-hidden\">\n @if(loading){\n <div class=\"flex-1 flex items-center gap-2 w-full h-3\">\n <div class=\"animate-pulse bg-light-inactive dark:bg-dark-inactive rounded-md h-5 w-full\">&nbsp;</div>\n </div>\n }\n @else{\n <div class=\"flex-1\">\n @if (!asButton && selectedOptions && selectedOptions.length > 0) {\n @if (multiselect) {\n <div class=\"flex flex-wrap gap-x-2 gap-y-1 items-start\">\n @for (option of selectedOptions; track option; let i = $index) {\n @if (showAll || i === 0) {\n <span class=\"bg-light-secondary dark:bg-dark-secondary rounded-md flex items-center text-xs/6 text-light-on-secondary dark:text-dark-on-secondary\"\n [ngClass]=\"{'w-full h-full px-4': !multiselect, 'px-1': multiselect}\">\n {{ option.value }}\n @if (showAll || i === 0) {\n <div (click)=\"onDeselectOption($event, option)\"\n class=\"ml-2 text-light-danger dark:text-dark-danger hover:cursor-pointer\">\n <svg class=\"w-3 h-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M10 8.586l-2.293-2.293a1 1 0 00-1.414 1.414L8.586 10l-2.293 2.293a1 1 0 001.414 1.414L10 11.414l2.293 2.293a1 1 0 001.414-1.414L11.414 10l2.293-2.293a1 1 0 00-1.414-1.414L10 8.586z\"\n clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </span>\n @if (!showAll && selectedOptions.length > 1) {\n <span class=\"text-light-on-control/80 dark:text-dark-on-control/80 text-xs/6\">\n +{{ selectedOptions.length - 1 }}\n </span>\n }\n }\n }\n </div>\n } @else {\n <span class=\"text-light-on-control dark:text-dark-on-control overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'text-light-on-secondary dark:text-dark-on-secondary': variant === 'secondary' && !disabled,\n 'text-light-on-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n }\">{{ selectedOptions[0].value }}</span>\n }\n } @else {\n <span class=\"text-inactive overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (useIcon) {\n <ng-content selector=\"buttonIcon\"></ng-content>\n } @else {\n {{ placeholder }}\n }\n </span>\n }\n </div>\n }\n <div class=\"text-light-on-control dark:text-dark-on-control\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (isDropdownOpen()) {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 011.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
596
+ }
597
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectComponent, decorators: [{
598
+ type: Component,
599
+ args: [{ selector: 'haloduck-select', imports: [CommonModule], providers: [
600
+ {
601
+ provide: NG_VALUE_ACCESSOR,
602
+ useExisting: forwardRef(() => SelectComponent),
603
+ multi: true,
604
+ },
605
+ provideTranslocoScope('haloduck'),
606
+ ], template: "<div class=\"flex flex-col gap-2 items-start w-full\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control w-full\">\n <ng-content></ng-content>\n </label>\n <div #origin\n class=\"w-full flex-1 relative overflow-visible rounded-md outline outline-light-inactive dark:outline-dark-inactive text-sm/6\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary focus:dark:outline-dark-primary': !disabled,\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary' : variant === 'primary' && !disabled,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary' : variant === 'secondary' && !disabled,\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger' : variant === 'danger' && !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick($event)\"\n (keydown)=\"onKeyDown($event)\">\n <div class=\"px-3 py-1.5 text-sm/6 cursor-pointer flex flex-nowrap items-center justify-between overflow-hidden\">\n @if(loading){\n <div class=\"flex-1 flex items-center gap-2 w-full h-3\">\n <div class=\"animate-pulse bg-light-inactive dark:bg-dark-inactive rounded-md h-5 w-full\">&nbsp;</div>\n </div>\n }\n @else{\n <div class=\"flex-1\">\n @if (!asButton && selectedOptions && selectedOptions.length > 0) {\n @if (multiselect) {\n <div class=\"flex flex-wrap gap-x-2 gap-y-1 items-start\">\n @for (option of selectedOptions; track option; let i = $index) {\n @if (showAll || i === 0) {\n <span class=\"bg-light-secondary dark:bg-dark-secondary rounded-md flex items-center text-xs/6 text-light-on-secondary dark:text-dark-on-secondary\"\n [ngClass]=\"{'w-full h-full px-4': !multiselect, 'px-1': multiselect}\">\n {{ option.value }}\n @if (showAll || i === 0) {\n <div (click)=\"onDeselectOption($event, option)\"\n class=\"ml-2 text-light-danger dark:text-dark-danger hover:cursor-pointer\">\n <svg class=\"w-3 h-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M10 8.586l-2.293-2.293a1 1 0 00-1.414 1.414L8.586 10l-2.293 2.293a1 1 0 001.414 1.414L10 11.414l2.293 2.293a1 1 0 001.414-1.414L11.414 10l2.293-2.293a1 1 0 00-1.414-1.414L10 8.586z\"\n clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </span>\n @if (!showAll && selectedOptions.length > 1) {\n <span class=\"text-light-on-control/80 dark:text-dark-on-control/80 text-xs/6\">\n +{{ selectedOptions.length - 1 }}\n </span>\n }\n }\n }\n </div>\n } @else {\n <span class=\"text-light-on-control dark:text-dark-on-control overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'text-light-on-secondary dark:text-dark-on-secondary': variant === 'secondary' && !disabled,\n 'text-light-on-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n }\">{{ selectedOptions[0].value }}</span>\n }\n } @else {\n <span class=\"text-inactive overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (useIcon) {\n <ng-content selector=\"buttonIcon\"></ng-content>\n } @else {\n {{ placeholder }}\n }\n </span>\n }\n </div>\n }\n <div class=\"text-light-on-control dark:text-dark-on-control\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (isDropdownOpen()) {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 011.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n </div>\n </div>\n </div>\n</div>\n" }]
607
+ }], ctorParameters: () => [], propDecorators: { selectedChange: [{
608
+ type: Output
609
+ }], disabled: [{
610
+ type: Input
611
+ }], loading: [{
612
+ type: Input
613
+ }], variant: [{
614
+ type: Input
615
+ }], asButton: [{
616
+ type: Input
617
+ }], useIcon: [{
618
+ type: Input
619
+ }], useFilter: [{
620
+ type: Input
621
+ }], multiselect: [{
622
+ type: Input
623
+ }], placeholder: [{
624
+ type: Input
625
+ }], atLeastOne: [{
626
+ type: Input
627
+ }], showAll: [{
628
+ type: Input
629
+ }], options: [{
630
+ type: Input
631
+ }], value: [{
632
+ type: Input
633
+ }], origin: [{
634
+ type: ViewChild,
635
+ args: ['origin', { static: false }]
636
+ }], label: [{
637
+ type: ViewChild,
638
+ args: ['label']
639
+ }] } });
640
+
641
+ class CalendarComponent {
642
+ translateService = inject(TranslocoService);
643
+ monthSelect;
644
+ singleDate = false;
645
+ firstDayOfWeek = 1; // monday
646
+ selectedDate;
647
+ selectedDateRange;
648
+ dateChange = new EventEmitter();
649
+ dateRangeChange = new EventEmitter();
650
+ currentYear = new Date().getFullYear();
651
+ currentMonth = new Date().getMonth();
652
+ listCalendarDate = [];
653
+ isFromSelected = false;
654
+ yearList = [];
655
+ monthList = [];
656
+ onAdjustYear(amount) {
657
+ this.currentYear += amount;
658
+ this.calculateDateRangeDropdown();
659
+ }
660
+ onAdjustMonth(amount) {
661
+ this.currentMonth += amount;
662
+ if (this.currentMonth < 0) {
663
+ this.currentYear--;
664
+ this.currentMonth = 11;
665
+ }
666
+ else if (this.currentMonth > 11) {
667
+ this.currentYear++;
668
+ this.currentMonth = 0;
669
+ }
670
+ this.calculateDateRangeDropdown();
671
+ }
672
+ onSelectCalendarDate(date) {
673
+ if (this.singleDate) {
674
+ this.dateChange.emit(date);
675
+ }
676
+ else {
677
+ if (!this.isFromSelected) {
678
+ this.selectedDateRange = {
679
+ from: date.datetime,
680
+ to: date.datetime,
681
+ };
682
+ this.isFromSelected = true;
683
+ }
684
+ else {
685
+ if (this.selectedDateRange?.from) {
686
+ const from = this.selectedDateRange.from < date.datetime
687
+ ? this.selectedDateRange.from
688
+ : date.datetime;
689
+ const to = this.selectedDateRange.from < date.datetime
690
+ ? date.datetime
691
+ : this.selectedDateRange.from;
692
+ this.dateRangeChange.emit({
693
+ from,
694
+ to,
695
+ });
696
+ this.isFromSelected = false;
697
+ }
698
+ }
699
+ }
700
+ }
701
+ onCalenderOpen(value) {
702
+ // const currentDate = (this.singleDate) ? () : ( value && value.from && value.to ? value.from :dateToString(new Date()));
703
+ let currentDate;
704
+ if (this.singleDate) {
705
+ const date = value;
706
+ currentDate = date ? date : dateToString(new Date());
707
+ }
708
+ else {
709
+ const dateRange = value;
710
+ currentDate =
711
+ dateRange && dateRange.from && dateRange.to
712
+ ? dateRange.from
713
+ : dateToString(new Date());
714
+ }
715
+ this.currentYear = parseInt(currentDate.slice(0, 4), 10);
716
+ this.currentMonth = parseInt(currentDate.slice(5, 7), 10) - 1;
717
+ this.calculateDateRangeDropdown();
718
+ }
719
+ calculateDateRangeDropdown() {
720
+ const weekdayOfFirstOfMonth = new Date(this.currentYear, this.currentMonth, 1).getDay();
721
+ const firstOfCalendar = new Date(this.currentYear, this.currentMonth, 1 - weekdayOfFirstOfMonth + this.firstDayOfWeek);
722
+ if (firstOfCalendar > new Date(this.currentYear, this.currentMonth, 1)) {
723
+ firstOfCalendar.setDate(firstOfCalendar.getDate() - 7);
724
+ }
725
+ const lastOfCalendar = new Date(firstOfCalendar);
726
+ lastOfCalendar.setDate(lastOfCalendar.getDate() + 41);
727
+ if (lastOfCalendar.getDate() > 6) {
728
+ lastOfCalendar.setDate(lastOfCalendar.getDate() - 7);
729
+ }
730
+ const dateDiff = Math.ceil((lastOfCalendar.getTime() - firstOfCalendar.getTime()) /
731
+ (1000 * 60 * 60 * 24)) + 1;
732
+ this.listCalendarDate = [];
733
+ for (let i = 0; i < dateDiff; i++) {
734
+ const datetime = dateToString(firstOfCalendar);
735
+ const date = firstOfCalendar.getDate().toString();
736
+ const isToday = firstOfCalendar.toDateString() === new Date().toDateString();
737
+ const isCurrentMonth = firstOfCalendar.getMonth() === this.currentMonth;
738
+ const isTheDate = (this.singleDate && this.selectedDate
739
+ ? datetime === this.selectedDate
740
+ : this.selectedDateRange &&
741
+ this.selectedDateRange.from &&
742
+ this.selectedDateRange.to &&
743
+ datetime >= this.selectedDateRange.from &&
744
+ datetime <= this.selectedDateRange.to) || false;
745
+ this.listCalendarDate.push({
746
+ datetime,
747
+ date,
748
+ isToday,
749
+ isTheDate,
750
+ isCurrentMonth,
751
+ });
752
+ firstOfCalendar.setDate(firstOfCalendar.getDate() + 1);
753
+ }
754
+ }
755
+ onSelectYear(year) {
756
+ year = year;
757
+ this.currentYear = parseInt(year, 10);
758
+ this.calculateDateRangeDropdown();
759
+ }
760
+ onSelectMonth(month) {
761
+ month = month;
762
+ this.currentMonth = parseInt(month, 10);
763
+ this.calculateDateRangeDropdown();
764
+ }
765
+ ngOnInit() {
766
+ if (this.singleDate) {
767
+ this.onCalenderOpen(this.selectedDate ?? dateToString(new Date()));
768
+ }
769
+ else {
770
+ this.onCalenderOpen(this.selectedDateRange ?? {
771
+ from: dateToString(new Date()),
772
+ to: dateToString(new Date()),
773
+ });
774
+ }
775
+ }
776
+ constructor() {
777
+ const currentYear = new Date().getFullYear();
778
+ this.translateService
779
+ .selectTranslate('ui.calendar._year_', { year: '_year_' }, 'haloduck')
780
+ .pipe(takeUntilDestroyed())
781
+ .subscribe((translation) => {
782
+ this.yearList = [];
783
+ for (let i = currentYear; i >= currentYear - 150; i--) {
784
+ this.yearList.push({
785
+ id: i.toString(),
786
+ value: translation.replace('_year_', i.toString()),
787
+ });
788
+ }
789
+ });
790
+ zip(this.translateService.selectTranslate('ui.calendar.January', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.February', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.March', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.April', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.May', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.June', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.July', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.August', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.September', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.October', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.November', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.December', {}, 'haloduck'))
791
+ .pipe(takeUntilDestroyed())
792
+ .subscribe((translations) => {
793
+ this.monthList = [
794
+ {
795
+ id: '0',
796
+ value: translations[0],
797
+ },
798
+ {
799
+ id: '1',
800
+ value: translations[1],
801
+ },
802
+ {
803
+ id: '2',
804
+ value: translations[2],
805
+ },
806
+ {
807
+ id: '3',
808
+ value: translations[3],
809
+ },
810
+ {
811
+ id: '4',
812
+ value: translations[4],
813
+ },
814
+ {
815
+ id: '5',
816
+ value: translations[5],
817
+ },
818
+ {
819
+ id: '6',
820
+ value: translations[6],
821
+ },
822
+ {
823
+ id: '7',
824
+ value: translations[7],
825
+ },
826
+ {
827
+ id: '8',
828
+ value: translations[8],
829
+ },
830
+ {
831
+ id: '9',
832
+ value: translations[9],
833
+ },
834
+ {
835
+ id: '10',
836
+ value: translations[10],
837
+ },
838
+ {
839
+ id: '11',
840
+ value: translations[11],
841
+ },
842
+ ];
843
+ });
844
+ }
845
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
846
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: CalendarComponent, isStandalone: true, selector: "haloduck-calendar", inputs: { singleDate: "singleDate", firstDayOfWeek: "firstDayOfWeek", selectedDate: "selectedDate", selectedDateRange: "selectedDateRange" }, outputs: { dateChange: "dateChange", dateRangeChange: "dateRangeChange" }, providers: [provideTranslocoScope('haloduck')], viewQueries: [{ propertyName: "monthSelect", first: true, predicate: ["monthSelect"], descendants: true }], ngImport: i0, template: "<div id=\"dateRangeCalendar\"\n class=\"min-w-96 brightness-125 bg-light-background dark:bg-dark-background border border-light-inactive dark:border-dark-inactive rounded text-center p-4 flex flex-col gap-4\"\n tabindex=\"1\">\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(-1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"yearList\"\n [value]=\"currentYear + ''\"\n (selectedChange)=\"onSelectYear($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(-1)\">\n <span class=\"sr-only\">\uC774\uC804 \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"monthList\"\n [value]=\"currentMonth + ''\"\n (selectedChange)=\"onSelectMonth($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(1)\">\n <span class=\"sr-only\">\uB2E4\uC74C \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div>\n <div\n class=\"grid grid-cols-7 gap-[1px] text-xs/6 font-semibold text-light-on-background dark:text-dark-on-background\">\n <div>{{ 'haloduck.ui.calendar.Mon' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Tue' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Wed' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Thu' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Fri' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sat' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sun' | transloco }}</div>\n </div>\n\n <div class=\"isolate mt-2 grid grid-cols-7 gap-[1px] rounded-lg text-sm shadow overflow-hidden\">\n @for (date of listCalendarDate; track date.datetime) {\n <button type=\"button\"\n (click)=\"onSelectCalendarDate(date)\"\n class=\"py-1.5 hover:cursor-pointer bg-light-alternative dark:bg-dark-alternative text-light-on-alternative dark:text-dark-on-alternative\"\n [ngClass]=\"{'bg-light-control dark:bg-dark-control text-light-on-control dark:text-dark-on-control' : date.isCurrentMonth}\">\n <time [ngClass]=\"{'font-semibold bg-light-primary-light dark:bg-dark-primary-light text-on-light-primary-light dark:text-dark-on-primary-light': date.isToday && date.datetime !== selectedDateRange?.from,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary': date.datetime === selectedDateRange?.from,\n 'bg-gray-500 font-semibold text-white' : date.isTheDate}\"\n class=\"mx-auto flex size-7 items-center justify-center rounded-full hover:font-bold\">{{date.date}}</time>\n </button>\n }\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SelectComponent, selector: "haloduck-select", inputs: ["disabled", "loading", "variant", "asButton", "useIcon", "useFilter", "multiselect", "placeholder", "atLeastOne", "showAll", "options", "value"], outputs: ["selectedChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
847
+ }
848
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CalendarComponent, decorators: [{
849
+ type: Component,
850
+ args: [{ selector: 'haloduck-calendar', imports: [CommonModule, SelectComponent, FormsModule, TranslocoModule], providers: [provideTranslocoScope('haloduck')], template: "<div id=\"dateRangeCalendar\"\n class=\"min-w-96 brightness-125 bg-light-background dark:bg-dark-background border border-light-inactive dark:border-dark-inactive rounded text-center p-4 flex flex-col gap-4\"\n tabindex=\"1\">\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(-1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"yearList\"\n [value]=\"currentYear + ''\"\n (selectedChange)=\"onSelectYear($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(-1)\">\n <span class=\"sr-only\">\uC774\uC804 \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"monthList\"\n [value]=\"currentMonth + ''\"\n (selectedChange)=\"onSelectMonth($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(1)\">\n <span class=\"sr-only\">\uB2E4\uC74C \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div>\n <div\n class=\"grid grid-cols-7 gap-[1px] text-xs/6 font-semibold text-light-on-background dark:text-dark-on-background\">\n <div>{{ 'haloduck.ui.calendar.Mon' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Tue' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Wed' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Thu' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Fri' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sat' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sun' | transloco }}</div>\n </div>\n\n <div class=\"isolate mt-2 grid grid-cols-7 gap-[1px] rounded-lg text-sm shadow overflow-hidden\">\n @for (date of listCalendarDate; track date.datetime) {\n <button type=\"button\"\n (click)=\"onSelectCalendarDate(date)\"\n class=\"py-1.5 hover:cursor-pointer bg-light-alternative dark:bg-dark-alternative text-light-on-alternative dark:text-dark-on-alternative\"\n [ngClass]=\"{'bg-light-control dark:bg-dark-control text-light-on-control dark:text-dark-on-control' : date.isCurrentMonth}\">\n <time [ngClass]=\"{'font-semibold bg-light-primary-light dark:bg-dark-primary-light text-on-light-primary-light dark:text-dark-on-primary-light': date.isToday && date.datetime !== selectedDateRange?.from,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary': date.datetime === selectedDateRange?.from,\n 'bg-gray-500 font-semibold text-white' : date.isTheDate}\"\n class=\"mx-auto flex size-7 items-center justify-center rounded-full hover:font-bold\">{{date.date}}</time>\n </button>\n }\n </div>\n </div>\n</div>\n" }]
851
+ }], ctorParameters: () => [], propDecorators: { monthSelect: [{
852
+ type: ViewChild,
853
+ args: ['monthSelect', { static: false }]
854
+ }], singleDate: [{
855
+ type: Input
856
+ }], firstDayOfWeek: [{
857
+ type: Input
858
+ }], selectedDate: [{
859
+ type: Input
860
+ }], selectedDateRange: [{
861
+ type: Input
862
+ }], dateChange: [{
863
+ type: Output
864
+ }], dateRangeChange: [{
865
+ type: Output
866
+ }] } });
867
+ // return yyyy-mm-dd
868
+ function dateToString(date) {
869
+ return (date.getFullYear() +
870
+ '-' +
871
+ (date.getMonth() + 1).toString().padStart(2, '0') +
872
+ '-' +
873
+ date.getDate().toString().padStart(2, '0'));
874
+ }
875
+
876
+ class DialogContainerComponent {
877
+ childComponent;
878
+ data;
879
+ close = new EventEmitter();
880
+ dialogContainer;
881
+ ngAfterViewInit() {
882
+ if (this.childComponent) {
883
+ const componentRef = this.dialogContainer.createComponent(this.childComponent);
884
+ if (this.data) {
885
+ Object.assign(componentRef.instance, { context: this.data });
886
+ }
887
+ if (this.data.classList) {
888
+ this.data.classList.forEach((className) => {
889
+ componentRef.location.nativeElement.classList.add(className);
890
+ });
891
+ }
892
+ componentRef.location.nativeElement.classList.add('max-w-full');
893
+ componentRef.location.nativeElement.classList.add('max-h-full');
894
+ componentRef.location.nativeElement.classList.add('text-light-on-background');
895
+ componentRef.location.nativeElement.classList.add('dark:text-dark-on-background');
896
+ componentRef.location.nativeElement.classList.add('bg-light-background');
897
+ componentRef.location.nativeElement.classList.add('dark:bg-dark-background');
898
+ componentRef.location.nativeElement.classList.add('p-4');
899
+ componentRef.location.nativeElement.classList.add('rounded-lg');
900
+ componentRef.location.nativeElement.classList.add('overflow-auto');
901
+ }
902
+ }
903
+ ngOnInit() { }
904
+ closeDialog() {
905
+ this.close.emit();
906
+ }
907
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
908
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: DialogContainerComponent, isStandalone: true, selector: "haloduck-dialog-container", inputs: { childComponent: "childComponent", data: "data" }, outputs: { close: "close" }, viewQueries: [{ propertyName: "dialogContainer", first: true, predicate: ["dialogContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<div class=\"h-full w-full max-h-full max-w-full fixed inset-0 bg-black/50 flex items-center justify-center overflow-hidden\">\n <!-- <div class=\"w-full h-full max-w-full max-h-full bg-white p-5 rounded-lg overflow-y-auto\"\n (click)=\"$event.stopPropagation()\"> -->\n <ng-container id=\"dialogContainer\"\n #dialogContainer></ng-container>\n <!-- </div> -->\n</div>\n", styles: [".custom-dialog-panel{width:100%;max-width:100%;height:100%;max-height:100%;margin:0;padding:0}\n"] });
909
+ }
910
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogContainerComponent, decorators: [{
911
+ type: Component,
912
+ args: [{ selector: 'haloduck-dialog-container', template: "<div class=\"h-full w-full max-h-full max-w-full fixed inset-0 bg-black/50 flex items-center justify-center overflow-hidden\">\n <!-- <div class=\"w-full h-full max-w-full max-h-full bg-white p-5 rounded-lg overflow-y-auto\"\n (click)=\"$event.stopPropagation()\"> -->\n <ng-container id=\"dialogContainer\"\n #dialogContainer></ng-container>\n <!-- </div> -->\n</div>\n", styles: [".custom-dialog-panel{width:100%;max-width:100%;height:100%;max-height:100%;margin:0;padding:0}\n"] }]
913
+ }], propDecorators: { childComponent: [{
914
+ type: Input
915
+ }], data: [{
916
+ type: Input
917
+ }], close: [{
918
+ type: Output
919
+ }], dialogContainer: [{
920
+ type: ViewChild,
921
+ args: ['dialogContainer', { read: ViewContainerRef }]
922
+ }] } });
923
+
924
+ class DialogService {
925
+ overlay;
926
+ injector;
927
+ dialogStack = []; // 다이얼로그 스택
928
+ constructor(overlay, injector) {
929
+ this.overlay = overlay;
930
+ this.injector = injector;
931
+ }
932
+ open(component, data, classList) {
933
+ const overlayRef = this.overlay.create({
934
+ hasBackdrop: true,
935
+ panelClass: 'custom-dialog-panel',
936
+ scrollStrategy: this.overlay.scrollStrategies.block(),
937
+ });
938
+ const portal = new ComponentPortal(DialogContainerComponent);
939
+ const containerRef = overlayRef.attach(portal);
940
+ containerRef.instance.childComponent = component;
941
+ containerRef.instance.data = { ...data, overlayRef, classList };
942
+ containerRef.instance.close.subscribe(() => this.close(overlayRef));
943
+ this.dialogStack.push(overlayRef);
944
+ return overlayRef;
945
+ }
946
+ close(overlayRef) {
947
+ if (overlayRef) {
948
+ overlayRef.dispose();
949
+ this.dialogStack = this.dialogStack.filter(ref => ref !== overlayRef);
950
+ }
951
+ else {
952
+ // 마지막 다이얼로그 닫기
953
+ const lastOverlay = this.dialogStack.pop();
954
+ lastOverlay?.dispose();
955
+ }
956
+ }
957
+ closeAll() {
958
+ while (this.dialogStack.length) {
959
+ this.dialogStack.pop()?.dispose();
960
+ }
961
+ }
962
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, deps: [{ token: i1$3.Overlay }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable });
963
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, providedIn: 'root' });
964
+ }
965
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, decorators: [{
966
+ type: Injectable,
967
+ args: [{ providedIn: 'root' }]
968
+ }], ctorParameters: () => [{ type: i1$3.Overlay }, { type: i0.Injector }] });
969
+
970
+ class ConfirmDialogComponent {
971
+ dialogService = inject(DialogService);
972
+ context;
973
+ onButtonClicked(id) {
974
+ this.context.clickedButton$.next(id);
975
+ this.context.clickedButton$.complete();
976
+ this.dialogService.close(this.context.overlayRef);
977
+ }
978
+ ngOnInit() {
979
+ }
980
+ constructor() {
981
+ }
982
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
983
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: ConfirmDialogComponent, isStandalone: true, selector: "haloduck-confirm-dialog", inputs: { context: "context" }, ngImport: i0, template: "<div class=\"w-120 max-w-full flex flex-col gap-4 text-light-on-background dark:text-dark-on-background bg-light-background dark:bg-dark-background\">\n <div class=\"font-bold\">{{ context.title }}</div>\n <p>{{ context.message }}</p>\n <div class=\"flex items-center justify-center gap-4\">\n @for (button of context.buttons; track button.id) {\n <haloduck-button [variant]=\"button.variant\"\n (click)=\"onButtonClicked(button.id)\">{{ button.label }}</haloduck-button>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }] });
984
+ }
985
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogComponent, decorators: [{
986
+ type: Component,
987
+ args: [{ selector: 'haloduck-confirm-dialog', imports: [ButtonComponent], template: "<div class=\"w-120 max-w-full flex flex-col gap-4 text-light-on-background dark:text-dark-on-background bg-light-background dark:bg-dark-background\">\n <div class=\"font-bold\">{{ context.title }}</div>\n <p>{{ context.message }}</p>\n <div class=\"flex items-center justify-center gap-4\">\n @for (button of context.buttons; track button.id) {\n <haloduck-button [variant]=\"button.variant\"\n (click)=\"onButtonClicked(button.id)\">{{ button.label }}</haloduck-button>\n }\n </div>\n</div>\n" }]
988
+ }], ctorParameters: () => [], propDecorators: { context: [{
989
+ type: Input
990
+ }] } });
991
+
992
+ class ConfirmDialogService {
993
+ dialogService = inject(DialogService);
994
+ clickedButton$;
995
+ confirm(title, message, buttons) {
996
+ this.clickedButton$ = new Subject();
997
+ const dialogRef = this.dialogService.open(ConfirmDialogComponent, {
998
+ title,
999
+ message,
1000
+ buttons,
1001
+ clickedButton$: this.clickedButton$,
1002
+ });
1003
+ return this.clickedButton$.asObservable();
1004
+ }
1005
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1006
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogService, providedIn: 'root' });
1007
+ }
1008
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ConfirmDialogService, decorators: [{
1009
+ type: Injectable,
1010
+ args: [{
1011
+ providedIn: 'root',
1012
+ }]
1013
+ }] });
1014
+
1015
+ class CopyButtonComponent {
1016
+ text = '';
1017
+ isAnimating = false;
1018
+ copyToClipboard($event) {
1019
+ $event.preventDefault();
1020
+ $event.stopPropagation();
1021
+ this.isAnimating = true;
1022
+ setTimeout(() => {
1023
+ this.isAnimating = false;
1024
+ }, 500); // Animation duration
1025
+ if (navigator.clipboard && window.isSecureContext) {
1026
+ navigator.clipboard
1027
+ .writeText(this.text)
1028
+ .then(() => {
1029
+ console.log('Text copied to clipboard');
1030
+ })
1031
+ .catch((err) => {
1032
+ console.error('Failed to copy text: ', err);
1033
+ });
1034
+ }
1035
+ else {
1036
+ const textArea = document.createElement('textarea');
1037
+ textArea.value = this.text;
1038
+ textArea.style.position = 'fixed'; // Avoid scrolling to bottom
1039
+ document.body.appendChild(textArea);
1040
+ textArea.focus();
1041
+ textArea.select();
1042
+ try {
1043
+ document.execCommand('copy');
1044
+ console.log('Text copied to clipboard');
1045
+ }
1046
+ catch (err) {
1047
+ console.error('Failed to copy text: ', err);
1048
+ }
1049
+ document.body.removeChild(textArea);
1050
+ }
1051
+ }
1052
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CopyButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1053
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: CopyButtonComponent, isStandalone: true, selector: "haloduck-copy-button", inputs: { text: "text" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<ng-container *transloco=\"let _ts; read: 'haloduck'\">\n <button type=\"button\"\n class=\"group inline-flex items-center px-2 py-1 bg-light-alternative dark:bg-dark-alternative hover:brightness-125 rounded text-xs text-light-on-alternative dark:text-dark-on-alternative hover:cursor-pointer active:scale-95 transition-transform\"\n (click)=\"copyToClipboard($event)\"\n title=\"Copy to clipboard\">\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n [class]=\"'h-4 w-4 mr-1 transition-all duration-500 ' + (isAnimating ? 'scale-150 opacity-0' : 'scale-100 opacity-100')\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <rect x=\"9\"\n y=\"9\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n <rect x=\"3\"\n y=\"3\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n </svg>\n <span class=\"hidden group-hover:inline\">\n @if (isAnimating) {\n {{ _ts('ui.button.Copied') }}\n } @else {\n {{ _ts('ui.button.Copy') }}\n }\n </span>\n </button>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: TranslocoModule }, { kind: "directive", type: i1$2.TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
1054
+ }
1055
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CopyButtonComponent, decorators: [{
1056
+ type: Component,
1057
+ args: [{ selector: 'haloduck-copy-button', imports: [TranslocoModule], providers: [provideTranslocoScope('haloduck')], template: "<ng-container *transloco=\"let _ts; read: 'haloduck'\">\n <button type=\"button\"\n class=\"group inline-flex items-center px-2 py-1 bg-light-alternative dark:bg-dark-alternative hover:brightness-125 rounded text-xs text-light-on-alternative dark:text-dark-on-alternative hover:cursor-pointer active:scale-95 transition-transform\"\n (click)=\"copyToClipboard($event)\"\n title=\"Copy to clipboard\">\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n [class]=\"'h-4 w-4 mr-1 transition-all duration-500 ' + (isAnimating ? 'scale-150 opacity-0' : 'scale-100 opacity-100')\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <rect x=\"9\"\n y=\"9\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n <rect x=\"3\"\n y=\"3\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n </svg>\n <span class=\"hidden group-hover:inline\">\n @if (isAnimating) {\n {{ _ts('ui.button.Copied') }}\n } @else {\n {{ _ts('ui.button.Copy') }}\n }\n </span>\n </button>\n</ng-container>\n" }]
1058
+ }], propDecorators: { text: [{
1059
+ type: Input
1060
+ }] } });
1061
+
1062
+ class DatePickerComponent {
1063
+ destroy$ = new Subject();
1064
+ translateService = inject(TranslocoService);
1065
+ overlay = inject(Overlay);
1066
+ disabled = false;
1067
+ placeholder = '';
1068
+ firstDayOfWeek = 1;
1069
+ origin;
1070
+ selectedDate;
1071
+ overlayRef;
1072
+ onClick() {
1073
+ if (this.disabled) {
1074
+ return;
1075
+ }
1076
+ this.openCalendar();
1077
+ }
1078
+ onDateChange(date) {
1079
+ this.selectedDate = date.datetime;
1080
+ this.onChange(this.selectedDate);
1081
+ }
1082
+ openCalendar() {
1083
+ if (!this.overlayRef) {
1084
+ this.overlayRef = this.overlay.create({
1085
+ positionStrategy: this.overlay
1086
+ .position()
1087
+ .flexibleConnectedTo(this.origin.nativeElement)
1088
+ .withPositions([
1089
+ {
1090
+ originX: 'start',
1091
+ originY: 'bottom',
1092
+ overlayX: 'start',
1093
+ overlayY: 'top',
1094
+ },
1095
+ {
1096
+ originX: 'start',
1097
+ originY: 'top',
1098
+ overlayX: 'start',
1099
+ overlayY: 'bottom',
1100
+ },
1101
+ ]),
1102
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
1103
+ hasBackdrop: true,
1104
+ backdropClass: 'cdk-overlay-transparent-backdrop',
1105
+ });
1106
+ const calendarPortal = new ComponentPortal(CalendarComponent);
1107
+ const componentRef = this.overlayRef.attach(calendarPortal);
1108
+ // Pass values directly to the CalendarComponent instance
1109
+ componentRef.instance.firstDayOfWeek = this.firstDayOfWeek;
1110
+ componentRef.instance.singleDate = true;
1111
+ componentRef.instance.selectedDate = this.selectedDate;
1112
+ // Subscribe to events emitted by the CalendarComponent
1113
+ componentRef.instance.dateChange
1114
+ .pipe(takeUntil(this.destroy$))
1115
+ .subscribe((date) => {
1116
+ this.onDateChange(date);
1117
+ this.closeCalendar();
1118
+ });
1119
+ // Handle clicks outside the overlay
1120
+ this.overlayRef
1121
+ .backdropClick()
1122
+ .pipe(takeUntil(this.destroy$))
1123
+ .subscribe(() => {
1124
+ this.closeCalendar();
1125
+ });
1126
+ }
1127
+ }
1128
+ closeCalendar() {
1129
+ if (this.overlayRef) {
1130
+ this.overlayRef.detach();
1131
+ this.overlayRef = undefined;
1132
+ }
1133
+ }
1134
+ onChange = (value) => { };
1135
+ onTouched = () => { };
1136
+ writeValue(value) {
1137
+ this.selectedDate = value;
1138
+ }
1139
+ registerOnChange(fn) {
1140
+ this.onChange = fn;
1141
+ }
1142
+ registerOnTouched(fn) {
1143
+ this.onTouched = fn;
1144
+ }
1145
+ setDisabledState(isDisabled) {
1146
+ this.disabled = isDisabled;
1147
+ }
1148
+ ngOnInit() { }
1149
+ ngOnDestroy() {
1150
+ this.destroy$.next();
1151
+ this.destroy$.complete();
1152
+ }
1153
+ constructor() {
1154
+ this.translateService
1155
+ .selectTranslate('ui.calendar.Pick a date.', {}, 'haloduck')
1156
+ .pipe(takeUntilDestroyed())
1157
+ .subscribe((translation) => {
1158
+ if (!this.placeholder || this.placeholder === '') {
1159
+ this.placeholder = translation;
1160
+ }
1161
+ });
1162
+ }
1163
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1164
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: DatePickerComponent, isStandalone: true, selector: "haloduck-date-picker", inputs: { disabled: "disabled", placeholder: "placeholder", firstDayOfWeek: "firstDayOfWeek" }, providers: [
1165
+ {
1166
+ provide: NG_VALUE_ACCESSOR,
1167
+ useExisting: forwardRef(() => DatePickerComponent),
1168
+ multi: true,
1169
+ },
1170
+ provideTranslocoScope('haloduck'),
1171
+ ], viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }], ngImport: i0, template: "<label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n</label>\n<div #origin\n class=\"py-1.5 relative mt-2 gap-2 rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary': !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick()\">\n <div class=\"flex items-center\">\n <div class=\"flex-1 text-light-on-control dark:text-dark-on-control sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-inactive dark:text-dark-inactive': !selectedDate && !disabled,\n 'text-light-on-control dark:text-dark-on-control': selectedDate && !disabled,\n }\"> {{ selectedDate || placeholder }}</div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }] });
1172
+ }
1173
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DatePickerComponent, decorators: [{
1174
+ type: Component,
1175
+ args: [{ selector: 'haloduck-date-picker', imports: [CommonModule, FormsModule], providers: [
1176
+ {
1177
+ provide: NG_VALUE_ACCESSOR,
1178
+ useExisting: forwardRef(() => DatePickerComponent),
1179
+ multi: true,
1180
+ },
1181
+ provideTranslocoScope('haloduck'),
1182
+ ], template: "<label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n</label>\n<div #origin\n class=\"py-1.5 relative mt-2 gap-2 rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary': !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick()\">\n <div class=\"flex items-center\">\n <div class=\"flex-1 text-light-on-control dark:text-dark-on-control sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-inactive dark:text-dark-inactive': !selectedDate && !disabled,\n 'text-light-on-control dark:text-dark-on-control': selectedDate && !disabled,\n }\"> {{ selectedDate || placeholder }}</div>\n </div>\n</div>\n" }]
1183
+ }], ctorParameters: () => [], propDecorators: { disabled: [{
1184
+ type: Input
1185
+ }], placeholder: [{
1186
+ type: Input
1187
+ }], firstDayOfWeek: [{
1188
+ type: Input
1189
+ }], origin: [{
1190
+ type: ViewChild,
1191
+ args: ['origin', { static: false }]
1192
+ }] } });
1193
+
1194
+ class DateRangeComponent {
1195
+ destroy$ = new Subject();
1196
+ translateService = inject(TranslocoService);
1197
+ overlay = inject(Overlay);
1198
+ dateRangeOptions = [];
1199
+ disabled = false;
1200
+ placeholder = '';
1201
+ firstDayOfWeek = 1;
1202
+ origin;
1203
+ selectedDateRangeOptionId;
1204
+ dateRange = undefined;
1205
+ overlayRef;
1206
+ getSelectedDateRangeString() {
1207
+ return (this.dateRange &&
1208
+ this.dateRange.from &&
1209
+ this.dateRange.to &&
1210
+ this.dateRange.from + ' ~ ' + this.dateRange.to);
1211
+ }
1212
+ onClick() {
1213
+ if (this.disabled) {
1214
+ return;
1215
+ }
1216
+ this.openCalendar();
1217
+ }
1218
+ onDateRangeOptionChange(selectedOptionId) {
1219
+ this.selectedDateRangeOptionId = selectedOptionId;
1220
+ if (this.selectedDateRangeOptionId !== 'custom') {
1221
+ this.calculateDateRange();
1222
+ }
1223
+ else {
1224
+ this.openCalendar();
1225
+ }
1226
+ }
1227
+ onDateRangeChange(dateRange) {
1228
+ this.dateRange = dateRange;
1229
+ this.selectedDateRangeOptionId = 'custom';
1230
+ this.onChange(this.dateRange);
1231
+ }
1232
+ calculateDateRange() {
1233
+ if (!this.selectedDateRangeOptionId) {
1234
+ }
1235
+ else if (this.selectedDateRangeOptionId !== 'custom') {
1236
+ const unit = this.selectedDateRangeOptionId.charAt(0);
1237
+ const amount = parseInt(this.selectedDateRangeOptionId.slice(1), 10);
1238
+ let from = new Date();
1239
+ let to = new Date();
1240
+ switch (unit) {
1241
+ case 'd':
1242
+ from = new Date(to);
1243
+ from.setDate(to.getDate() - amount);
1244
+ break;
1245
+ case 'm':
1246
+ from = new Date(to);
1247
+ from.setMonth(to.getMonth() - amount);
1248
+ break;
1249
+ case 'y':
1250
+ from = new Date(to);
1251
+ from.setFullYear(to.getFullYear() - amount);
1252
+ break;
1253
+ default:
1254
+ break;
1255
+ }
1256
+ this.dateRange = {
1257
+ from: dateToString(from),
1258
+ to: dateToString(to),
1259
+ };
1260
+ this.onChange(this.dateRange);
1261
+ }
1262
+ }
1263
+ getDateRangeOptionId(dateRange) {
1264
+ const selectedOption = this.dateRangeOptions.find((option) => {
1265
+ const unit = (option.id || option.value).charAt(0);
1266
+ const amount = parseInt((option.id || option.value).slice(1), 10);
1267
+ let from = new Date();
1268
+ let to = new Date();
1269
+ switch (unit) {
1270
+ case 'd':
1271
+ from = new Date(to);
1272
+ from.setDate(to.getDate() - amount);
1273
+ break;
1274
+ case 'm':
1275
+ from = new Date(to);
1276
+ from.setMonth(to.getMonth() - amount);
1277
+ break;
1278
+ case 'y':
1279
+ from = new Date(to);
1280
+ from.setFullYear(to.getFullYear() - amount);
1281
+ break;
1282
+ default:
1283
+ break;
1284
+ }
1285
+ return (dateRange.from === dateToString(from) &&
1286
+ dateRange.to === dateToString(to));
1287
+ });
1288
+ if (selectedOption) {
1289
+ return selectedOption.id || selectedOption.value;
1290
+ }
1291
+ return 'custom';
1292
+ }
1293
+ openCalendar() {
1294
+ if (!this.overlayRef) {
1295
+ this.overlayRef = this.overlay.create({
1296
+ positionStrategy: this.overlay
1297
+ .position()
1298
+ .flexibleConnectedTo(this.origin.nativeElement)
1299
+ .withPositions([
1300
+ {
1301
+ originX: 'start',
1302
+ originY: 'bottom',
1303
+ overlayX: 'start',
1304
+ overlayY: 'top',
1305
+ },
1306
+ {
1307
+ originX: 'start',
1308
+ originY: 'top',
1309
+ overlayX: 'start',
1310
+ overlayY: 'bottom',
1311
+ },
1312
+ ]),
1313
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
1314
+ hasBackdrop: true,
1315
+ backdropClass: 'cdk-overlay-transparent-backdrop',
1316
+ });
1317
+ const calendarPortal = new ComponentPortal(CalendarComponent);
1318
+ const componentRef = this.overlayRef.attach(calendarPortal);
1319
+ componentRef.instance.firstDayOfWeek = this.firstDayOfWeek;
1320
+ componentRef.instance.selectedDateRange = this.dateRange;
1321
+ // Subscribe to events emitted by the CalendarComponent
1322
+ componentRef.instance.dateRangeChange
1323
+ .pipe(takeUntil(this.destroy$))
1324
+ .subscribe((date) => {
1325
+ this.onDateRangeChange(date);
1326
+ this.closeCalendar();
1327
+ });
1328
+ // Handle clicks outside the overlay
1329
+ this.overlayRef
1330
+ .backdropClick()
1331
+ .pipe(takeUntil(this.destroy$))
1332
+ .subscribe(() => {
1333
+ this.closeCalendar();
1334
+ });
1335
+ }
1336
+ }
1337
+ closeCalendar() {
1338
+ if (this.overlayRef) {
1339
+ this.overlayRef.detach();
1340
+ this.overlayRef = undefined;
1341
+ }
1342
+ }
1343
+ onChange = (value) => { };
1344
+ onTouched = () => { };
1345
+ writeValue(value) {
1346
+ this.dateRange = value;
1347
+ if (!value) {
1348
+ this.selectedDateRangeOptionId = undefined;
1349
+ }
1350
+ else {
1351
+ this.selectedDateRangeOptionId = this.getDateRangeOptionId(value);
1352
+ }
1353
+ }
1354
+ registerOnChange(fn) {
1355
+ this.onChange = fn;
1356
+ }
1357
+ registerOnTouched(fn) {
1358
+ this.onTouched = fn;
1359
+ }
1360
+ setDisabledState(isDisabled) {
1361
+ this.disabled = isDisabled;
1362
+ }
1363
+ ngOnInit() {
1364
+ console.log('DateRangeComponent ngOnInit', this.dateRangeOptions);
1365
+ }
1366
+ ngOnDestroy() {
1367
+ this.destroy$.next();
1368
+ this.destroy$.complete();
1369
+ }
1370
+ constructor() {
1371
+ this.translateService
1372
+ .selectTranslate('ui.calendar.Pick a period.', {}, 'haloduck')
1373
+ .pipe(takeUntilDestroyed())
1374
+ .subscribe((translation) => {
1375
+ if (!this.placeholder || this.placeholder === '') {
1376
+ this.placeholder = translation;
1377
+ }
1378
+ });
1379
+ console.log('DateRangeComponent constructor', this.dateRangeOptions);
1380
+ zip(this.translateService.selectTranslate('ui.calendar.today', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.yesterday', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.2 Days ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.3 Days ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.a week ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.15 Days ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.a month ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.2 months ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.3 months ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.6 months ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.a year ago', {}, 'haloduck'), this.translateService.selectTranslate('ui.calendar.pick manually', {}, 'haloduck'))
1381
+ .pipe(takeUntilDestroyed())
1382
+ .subscribe(([today, yesterday, twoDaysAgo, threeDaysAgo, aWeekAgo, fifteenDaysAgo, aMonthAgo, twoMonthsAgo, threeMonthsAgo, sixMonthsAgo, aYearAgo, pickManually,]) => {
1383
+ if (this.dateRangeOptions.length === 0) {
1384
+ this.dateRangeOptions = [
1385
+ {
1386
+ id: 'd0',
1387
+ value: today,
1388
+ },
1389
+ {
1390
+ id: 'd1',
1391
+ value: yesterday,
1392
+ },
1393
+ {
1394
+ id: 'd2',
1395
+ value: twoDaysAgo,
1396
+ },
1397
+ {
1398
+ id: 'd3',
1399
+ value: threeDaysAgo,
1400
+ },
1401
+ {
1402
+ id: 'd7',
1403
+ value: aWeekAgo,
1404
+ },
1405
+ {
1406
+ id: 'd15',
1407
+ value: fifteenDaysAgo,
1408
+ },
1409
+ {
1410
+ id: 'm1',
1411
+ value: aMonthAgo,
1412
+ },
1413
+ {
1414
+ id: 'm2',
1415
+ value: twoMonthsAgo,
1416
+ },
1417
+ {
1418
+ id: 'm3',
1419
+ value: threeMonthsAgo,
1420
+ },
1421
+ {
1422
+ id: 'm6',
1423
+ value: sixMonthsAgo,
1424
+ },
1425
+ {
1426
+ id: 'y1',
1427
+ value: aYearAgo,
1428
+ },
1429
+ {
1430
+ id: 'custom',
1431
+ value: pickManually,
1432
+ },
1433
+ ];
1434
+ }
1435
+ });
1436
+ }
1437
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DateRangeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1438
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: DateRangeComponent, isStandalone: true, selector: "haloduck-date-range", inputs: { dateRangeOptions: "dateRangeOptions", disabled: "disabled", placeholder: "placeholder", firstDayOfWeek: "firstDayOfWeek" }, providers: [
1439
+ {
1440
+ provide: NG_VALUE_ACCESSOR,
1441
+ useExisting: forwardRef(() => DateRangeComponent),
1442
+ multi: true,
1443
+ },
1444
+ provideTranslocoScope('haloduck'),
1445
+ ], viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }], ngImport: i0, template: "<label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n</label>\n<div #origin\n class=\"relative mt-2 gap-2 rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary': !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick()\">\n <div class=\"flex items-center\">\n <haloduck-select [multiselect]=\"false\"\n [disabled]=\"disabled\"\n [options]=\"dateRangeOptions\"\n [atLeastOne]=\"true\"\n (click)=\"$event.stopPropagation()\"\n variant=\"primary\"\n placeholder=\"{{ 'haloduck.ui.calendar.Period' | transloco }}\"\n [(ngModel)]=\"selectedDateRangeOptionId\"\n (ngModelChange)=\"onDateRangeOptionChange($event)\">\n </haloduck-select>\n @if (getSelectedDateRangeString()) {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-on-control dark:text-dark-on-control': !disabled,\n 'text-light-on-control/60 dark:text-light-on-control/60': disabled,\n }\"> {{ getSelectedDateRangeString() }}</div>\n } @else {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-inactive dark:text-dark-inactive': !disabled,\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n }\"> {{ placeholder }}</div>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: SelectComponent, selector: "haloduck-select", inputs: ["disabled", "loading", "variant", "asButton", "useIcon", "useFilter", "multiselect", "placeholder", "atLeastOne", "showAll", "options", "value"], outputs: ["selectedChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
1446
+ }
1447
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DateRangeComponent, decorators: [{
1448
+ type: Component,
1449
+ args: [{ selector: 'haloduck-date-range', imports: [SelectComponent, CommonModule, FormsModule, TranslocoModule], providers: [
1450
+ {
1451
+ provide: NG_VALUE_ACCESSOR,
1452
+ useExisting: forwardRef(() => DateRangeComponent),
1453
+ multi: true,
1454
+ },
1455
+ provideTranslocoScope('haloduck'),
1456
+ ], template: "<label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n</label>\n<div #origin\n class=\"relative mt-2 gap-2 rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary': !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick()\">\n <div class=\"flex items-center\">\n <haloduck-select [multiselect]=\"false\"\n [disabled]=\"disabled\"\n [options]=\"dateRangeOptions\"\n [atLeastOne]=\"true\"\n (click)=\"$event.stopPropagation()\"\n variant=\"primary\"\n placeholder=\"{{ 'haloduck.ui.calendar.Period' | transloco }}\"\n [(ngModel)]=\"selectedDateRangeOptionId\"\n (ngModelChange)=\"onDateRangeOptionChange($event)\">\n </haloduck-select>\n @if (getSelectedDateRangeString()) {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-on-control dark:text-dark-on-control': !disabled,\n 'text-light-on-control/60 dark:text-light-on-control/60': disabled,\n }\"> {{ getSelectedDateRangeString() }}</div>\n } @else {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-inactive dark:text-dark-inactive': !disabled,\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n }\"> {{ placeholder }}</div>\n }\n </div>\n</div>\n" }]
1457
+ }], ctorParameters: () => [], propDecorators: { dateRangeOptions: [{
1458
+ type: Input
1459
+ }], disabled: [{
1460
+ type: Input
1461
+ }], placeholder: [{
1462
+ type: Input
1463
+ }], firstDayOfWeek: [{
1464
+ type: Input
1465
+ }], origin: [{
1466
+ type: ViewChild,
1467
+ args: ['origin', { static: false }]
1468
+ }] } });
1469
+
1470
+ class DrawCanvasComponent {
1471
+ imagePath;
1472
+ lineColor = '#DD0000'; // Default color is black
1473
+ canvasRef;
1474
+ ctx;
1475
+ isDrawing = false;
1476
+ onChange = () => { };
1477
+ onTouched = () => { };
1478
+ ngAfterViewInit() {
1479
+ const canvas = this.canvasRef.nativeElement;
1480
+ this.ctx = canvas.getContext('2d');
1481
+ this.ctx.strokeStyle = this.lineColor; // Set the initial line color
1482
+ this.loadImage();
1483
+ this.setupCanvas();
1484
+ }
1485
+ loadImage() {
1486
+ console.log(this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height, this.canvasRef.nativeElement.getBoundingClientRect());
1487
+ const image = new Image();
1488
+ image.src = this.imagePath;
1489
+ image.onload = () => {
1490
+ this.ctx.drawImage(image, 0, 0, this.canvasRef.nativeElement.width, this.canvasRef.nativeElement.height);
1491
+ };
1492
+ }
1493
+ setupCanvas() {
1494
+ const canvas = this.canvasRef.nativeElement;
1495
+ // Mouse events
1496
+ canvas.addEventListener('mousedown', (event) => this.startMouseDrawing(event));
1497
+ canvas.addEventListener('mousemove', (event) => this.mouseDraw(event));
1498
+ canvas.addEventListener('mouseup', () => this.stopDrawing());
1499
+ canvas.addEventListener('mouseleave', () => this.stopDrawing());
1500
+ // Touch events
1501
+ canvas.addEventListener('touchstart', (event) => this.startTouchDrawing(event));
1502
+ canvas.addEventListener('touchmove', (event) => {
1503
+ event.preventDefault(); // Prevent scrolling
1504
+ this.touchDraw(event);
1505
+ });
1506
+ canvas.addEventListener('touchend', () => this.stopDrawing());
1507
+ canvas.addEventListener('touchcancel', () => this.stopDrawing());
1508
+ }
1509
+ startMouseDrawing(event) {
1510
+ const rect = this.canvasRef.nativeElement.getBoundingClientRect();
1511
+ this.isDrawing = true;
1512
+ this.ctx.beginPath();
1513
+ this.ctx.moveTo(event.offsetX * (this.canvasRef.nativeElement.width / rect.width), event.offsetY * (this.canvasRef.nativeElement.height / rect.height));
1514
+ }
1515
+ mouseDraw(event) {
1516
+ const rect = this.canvasRef.nativeElement.getBoundingClientRect();
1517
+ if (!this.isDrawing)
1518
+ return;
1519
+ this.ctx.lineTo(event.offsetX * (this.canvasRef.nativeElement.width / rect.width), event.offsetY * (this.canvasRef.nativeElement.height / rect.height));
1520
+ this.ctx.stroke();
1521
+ }
1522
+ startTouchDrawing(event) {
1523
+ this.isDrawing = true;
1524
+ const touch = event.touches[0];
1525
+ const rect = this.canvasRef.nativeElement.getBoundingClientRect();
1526
+ this.ctx.beginPath();
1527
+ this.ctx.moveTo((touch.clientX - rect.left) *
1528
+ (this.canvasRef.nativeElement.width / rect.width), (touch.clientY - rect.top) *
1529
+ (this.canvasRef.nativeElement.height / rect.height));
1530
+ }
1531
+ touchDraw(event) {
1532
+ if (!this.isDrawing)
1533
+ return;
1534
+ const touch = event.touches[0];
1535
+ const rect = this.canvasRef.nativeElement.getBoundingClientRect();
1536
+ this.ctx.lineTo((touch.clientX - rect.left) *
1537
+ (this.canvasRef.nativeElement.width / rect.width), (touch.clientY - rect.top) *
1538
+ (this.canvasRef.nativeElement.height / rect.height));
1539
+ this.ctx.stroke();
1540
+ }
1541
+ stopDrawing() {
1542
+ this.isDrawing = false;
1543
+ this.ctx.closePath();
1544
+ this.saveDrawing();
1545
+ }
1546
+ saveDrawing() {
1547
+ const canvas = this.canvasRef.nativeElement;
1548
+ const dataUrl = canvas.toDataURL('image/png');
1549
+ // Convert data URL to Blob
1550
+ const blob = this.dataURLToBlob(dataUrl);
1551
+ const file = new File([blob], 'teeth.png', { type: 'image/png' });
1552
+ // Emit the file value to the form control
1553
+ this.onChange(file);
1554
+ }
1555
+ dataURLToBlob(dataUrl) {
1556
+ const byteString = atob(dataUrl.split(',')[1]);
1557
+ const mimeString = dataUrl.split(',')[0].split(':')[1].split(';')[0];
1558
+ const buffer = new ArrayBuffer(byteString.length);
1559
+ const dataView = new Uint8Array(buffer);
1560
+ for (let i = 0; i < byteString.length; i++) {
1561
+ dataView[i] = byteString.charCodeAt(i);
1562
+ }
1563
+ return new Blob([buffer], { type: mimeString });
1564
+ }
1565
+ // ControlValueAccessor methods
1566
+ writeValue(file) {
1567
+ // No-op: The component does not need to set the file value directly
1568
+ }
1569
+ registerOnChange(fn) {
1570
+ this.onChange = fn;
1571
+ }
1572
+ registerOnTouched(fn) {
1573
+ this.onTouched = fn;
1574
+ }
1575
+ setDisabledState(isDisabled) {
1576
+ const canvas = this.canvasRef.nativeElement;
1577
+ canvas.style.pointerEvents = isDisabled ? 'none' : 'auto';
1578
+ }
1579
+ onReset() {
1580
+ this.loadImage();
1581
+ }
1582
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DrawCanvasComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1583
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: DrawCanvasComponent, isStandalone: true, selector: "haloduck-draw-canvas", inputs: { imagePath: "imagePath", lineColor: "lineColor" }, providers: [
1584
+ {
1585
+ provide: NG_VALUE_ACCESSOR,
1586
+ useExisting: forwardRef(() => DrawCanvasComponent),
1587
+ multi: true,
1588
+ },
1589
+ provideTranslocoScope('haloduck'),
1590
+ ], viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"draw-canvas-container flex flex-col gap-2\">\n <canvas #canvas width=\"800\" height=\"400\" class=\"max-w-full draw-canvas select-none\"></canvas>\n <haloduck-button variant=\"danger\" (click)=\"onReset()\">{{ 'haloduck.ui.draw.Clear' | transloco }}</haloduck-button>\n</div>\n", styles: [".draw-canvas-container{display:flex;flex-direction:column;align-items:center}.draw-canvas{border:1px solid #ccc;cursor:crosshair}\n"], dependencies: [{ kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
1591
+ }
1592
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DrawCanvasComponent, decorators: [{
1593
+ type: Component,
1594
+ args: [{ selector: 'haloduck-draw-canvas', providers: [
1595
+ {
1596
+ provide: NG_VALUE_ACCESSOR,
1597
+ useExisting: forwardRef(() => DrawCanvasComponent),
1598
+ multi: true,
1599
+ },
1600
+ provideTranslocoScope('haloduck'),
1601
+ ], imports: [ButtonComponent, TranslocoModule], template: "<div class=\"draw-canvas-container flex flex-col gap-2\">\n <canvas #canvas width=\"800\" height=\"400\" class=\"max-w-full draw-canvas select-none\"></canvas>\n <haloduck-button variant=\"danger\" (click)=\"onReset()\">{{ 'haloduck.ui.draw.Clear' | transloco }}</haloduck-button>\n</div>\n", styles: [".draw-canvas-container{display:flex;flex-direction:column;align-items:center}.draw-canvas{border:1px solid #ccc;cursor:crosshair}\n"] }]
1602
+ }], propDecorators: { imagePath: [{
1603
+ type: Input
1604
+ }], lineColor: [{
1605
+ type: Input
1606
+ }], canvasRef: [{
1607
+ type: ViewChild,
1608
+ args: ['canvas', { static: true }]
1609
+ }] } });
1610
+
1611
+ const ERROR_NOT_ACCEPTABLE_FILE_TYPE = 'NOT_ACCEPTABLE_FILE_TYPE';
1612
+ const ERROR_OVER_SIZE = 'OVER_SIZE';
1613
+ const ERROR_OVER_COUNT = 'OVER_COUNT';
1614
+ const ERROR_UPLOAD = 'UPLOAD';
1615
+ class FileUploaderComponent {
1616
+ disabled = false;
1617
+ urlPrefix;
1618
+ keyPrefix;
1619
+ uploadApi;
1620
+ multiple = true;
1621
+ maxCount = 0;
1622
+ maxSize = 0;
1623
+ accept = [''];
1624
+ filesAdded = new EventEmitter();
1625
+ fileRemoved = new EventEmitter();
1626
+ error = new EventEmitter();
1627
+ cdr = inject(ChangeDetectorRef);
1628
+ zone = inject(NgZone);
1629
+ files = [];
1630
+ isUploading = false;
1631
+ isDragOver = false;
1632
+ onChange = () => { };
1633
+ onTouched = () => { };
1634
+ writeValue(files) {
1635
+ this.files = files || [];
1636
+ }
1637
+ registerOnChange(fn) {
1638
+ this.onChange = fn;
1639
+ }
1640
+ registerOnTouched(fn) {
1641
+ this.onTouched = fn;
1642
+ }
1643
+ setDisabledState(isDisabled) {
1644
+ this.disabled = isDisabled;
1645
+ }
1646
+ onFileSelected(event) {
1647
+ const input = event.target;
1648
+ if (input.files) {
1649
+ const selectedFiles = this.multiple
1650
+ ? Array.from(input.files)
1651
+ : [input.files[0]];
1652
+ this.addFiles(selectedFiles);
1653
+ }
1654
+ this.onTouched();
1655
+ }
1656
+ removeFile(index) {
1657
+ const removedFile = this.files[index];
1658
+ this.files.splice(index, 1);
1659
+ this.fileRemoved.emit(removedFile);
1660
+ this.onChange(this.files);
1661
+ }
1662
+ onDragOver(event) {
1663
+ event.preventDefault();
1664
+ this.isDragOver = true;
1665
+ }
1666
+ onDragLeave(event) {
1667
+ event.preventDefault();
1668
+ this.isDragOver = false;
1669
+ }
1670
+ onDrop(event) {
1671
+ event.preventDefault();
1672
+ this.isDragOver = false;
1673
+ if (event.dataTransfer?.files) {
1674
+ const droppedFiles = this.multiple
1675
+ ? Array.from(event.dataTransfer.files)
1676
+ : [event.dataTransfer.files[0]];
1677
+ this.addFiles(droppedFiles);
1678
+ }
1679
+ this.onTouched();
1680
+ }
1681
+ getFilePreview(file) {
1682
+ return file.previewUrl || ''; // Return the precomputed preview URL
1683
+ }
1684
+ addFiles(addedFiles) {
1685
+ const notAcceptedFiles = [];
1686
+ let filteredFiles = addedFiles.filter((file) => {
1687
+ const acceptableFileType = this.isAcceptableFileType(file);
1688
+ const acceptableFileSize = this.maxSize > 0 ? file.size <= this.maxSize : true;
1689
+ if (!acceptableFileType) {
1690
+ notAcceptedFiles.push({ error: ERROR_NOT_ACCEPTABLE_FILE_TYPE, file });
1691
+ }
1692
+ if (!acceptableFileSize) {
1693
+ notAcceptedFiles.push({ error: ERROR_OVER_SIZE, file });
1694
+ }
1695
+ return acceptableFileType && acceptableFileSize;
1696
+ });
1697
+ if (this.maxCount > 0 &&
1698
+ this.files.length + filteredFiles.length > this.maxCount) {
1699
+ for (let i = this.maxCount; i < this.files.length + filteredFiles.length; i++) {
1700
+ notAcceptedFiles.push({
1701
+ error: ERROR_OVER_COUNT,
1702
+ file: filteredFiles[i - this.files.length],
1703
+ });
1704
+ }
1705
+ filteredFiles = filteredFiles.splice(this.files.length + filteredFiles.length - this.maxCount);
1706
+ }
1707
+ if (notAcceptedFiles.length > 0) {
1708
+ this.error.emit(notAcceptedFiles);
1709
+ }
1710
+ // Compute preview URLs for the new files
1711
+ filteredFiles.forEach((file) => {
1712
+ if (file.type.startsWith('image')) {
1713
+ file.previewUrl = URL.createObjectURL(file);
1714
+ }
1715
+ });
1716
+ this.files = this.files.concat(filteredFiles);
1717
+ this.filesAdded.emit(filteredFiles);
1718
+ this.onChange(this.files);
1719
+ }
1720
+ isAcceptableFileType(file) {
1721
+ // Check if the file type is acceptable
1722
+ return this.accept.some((type) => {
1723
+ if (type.startsWith('.')) {
1724
+ // Check if the file type matches any of the accepted types
1725
+ return file.name.endsWith(type);
1726
+ }
1727
+ else if (type.includes('/')) {
1728
+ if (type.includes('/*')) {
1729
+ // Check if the file type matches the wildcard MIME type
1730
+ return file.type.startsWith(type.split('/')[0]);
1731
+ }
1732
+ else {
1733
+ // Check if the file type matches the specific MIME type
1734
+ return file.type === type;
1735
+ }
1736
+ }
1737
+ else {
1738
+ return file.type === type;
1739
+ }
1740
+ });
1741
+ }
1742
+ startUpload() {
1743
+ // Placeholder for upload logic
1744
+ this.isUploading = true;
1745
+ const uploadTrigger$ = [];
1746
+ this.files.forEach((file) => {
1747
+ file.isUploading = true;
1748
+ file.isUploaded = false;
1749
+ uploadTrigger$.push(this.uploadApi(file, this.keyPrefix).pipe(
1750
+ // Handle the upload API response
1751
+ tap((key) => {
1752
+ file.isUploading = false;
1753
+ file.isUploaded = true;
1754
+ file.key = key;
1755
+ })));
1756
+ });
1757
+ // Wait for all uploads to complete
1758
+ return combineLatest(uploadTrigger$).pipe(switchMap((keys) => {
1759
+ this.files.forEach((file) => {
1760
+ if ('' === file.key) {
1761
+ this.isUploading = false;
1762
+ file.isUploaded = false;
1763
+ this.error.emit([{ error: ERROR_UPLOAD, file }]);
1764
+ }
1765
+ });
1766
+ this.onChange(this.files);
1767
+ this.onTouched();
1768
+ return of(this.files);
1769
+ }));
1770
+ }
1771
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FileUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1772
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: FileUploaderComponent, isStandalone: true, selector: "haloduck-file-uploader", inputs: { disabled: "disabled", urlPrefix: "urlPrefix", keyPrefix: "keyPrefix", uploadApi: "uploadApi", multiple: "multiple", maxCount: "maxCount", maxSize: "maxSize", accept: "accept" }, outputs: { filesAdded: "filesAdded", fileRemoved: "fileRemoved", error: "error" }, providers: [
1773
+ {
1774
+ provide: NG_VALUE_ACCESSOR,
1775
+ useExisting: forwardRef(() => FileUploaderComponent),
1776
+ multi: true,
1777
+ },
1778
+ provideTranslocoScope('haloduck'),
1779
+ ], ngImport: i0, template: "<div class=\"p-4 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n @if (!isUploading) {\n <label for=\"file-upload\"\n class=\"flex flex-col items-center justify-center w-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md cursor-pointer hover:border-light-secondary dark:border-dark-secondary p-4\">\n <span class=\"text-light-inactive dark:text-dark-inactive\">{{ 'haloduck.ui.file.Drag and drop files here, or click to select files' | transloco }}</span>\n <input id=\"file-upload\"\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"accept ? accept.join(',') : null\"\n [attr.multiple]=\"multiple ? '' : null\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n </label>\n }\n <!-- Display file list -->\n @if (files.length > 0) {\n <ul class=\"mt-4 space-y-2\">\n @for (file of files; track file.name; let i = $index) {\n <li class=\"flex items-center justify-between p-2 border border-light-inactive dark:border-dark-inactive rounded-md\">\n <!-- Check if the file is an image -->\n <div class=\"flex items-center space-x-4\">\n @if (file.previewUrl){\n <img [src]=\"file.previewUrl\"\n alt=\"{{ file.name }}\"\n class=\"w-12 h-12 object-cover rounded-md\" />\n }\n <span class=\"text-sm text-light-inactive dark:text-dark-inactive\">{{ file.name }}</span>\n </div>\n @if (isUploading) {\n @if (file.isUploaded) {\n <span class=\"text-sm text-light-secondary dark:text-dark-secondary\">{{ 'haloduck.ui.file.Uploaded' | transloco }}</span>\n }@else{\n <span class=\"text-sm text-light-primary dark:text-dark-primary\">{{ 'haloduck.ui.file.Uploading...' | transloco }}</span>\n }\n }@else{\n <button type=\"button\"\n class=\"text-light-danger dark:text-dark-danger hover:brightness-125\"\n (click)=\"removeFile(i)\">\n {{ 'haloduck.ui.file.Remove' | transloco }}\n </button>\n }\n </li>\n }\n </ul>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
1780
+ }
1781
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FileUploaderComponent, decorators: [{
1782
+ type: Component,
1783
+ args: [{ selector: 'haloduck-file-uploader', imports: [CommonModule, TranslocoModule], providers: [
1784
+ {
1785
+ provide: NG_VALUE_ACCESSOR,
1786
+ useExisting: forwardRef(() => FileUploaderComponent),
1787
+ multi: true,
1788
+ },
1789
+ provideTranslocoScope('haloduck'),
1790
+ ], template: "<div class=\"p-4 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n @if (!isUploading) {\n <label for=\"file-upload\"\n class=\"flex flex-col items-center justify-center w-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md cursor-pointer hover:border-light-secondary dark:border-dark-secondary p-4\">\n <span class=\"text-light-inactive dark:text-dark-inactive\">{{ 'haloduck.ui.file.Drag and drop files here, or click to select files' | transloco }}</span>\n <input id=\"file-upload\"\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"accept ? accept.join(',') : null\"\n [attr.multiple]=\"multiple ? '' : null\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n </label>\n }\n <!-- Display file list -->\n @if (files.length > 0) {\n <ul class=\"mt-4 space-y-2\">\n @for (file of files; track file.name; let i = $index) {\n <li class=\"flex items-center justify-between p-2 border border-light-inactive dark:border-dark-inactive rounded-md\">\n <!-- Check if the file is an image -->\n <div class=\"flex items-center space-x-4\">\n @if (file.previewUrl){\n <img [src]=\"file.previewUrl\"\n alt=\"{{ file.name }}\"\n class=\"w-12 h-12 object-cover rounded-md\" />\n }\n <span class=\"text-sm text-light-inactive dark:text-dark-inactive\">{{ file.name }}</span>\n </div>\n @if (isUploading) {\n @if (file.isUploaded) {\n <span class=\"text-sm text-light-secondary dark:text-dark-secondary\">{{ 'haloduck.ui.file.Uploaded' | transloco }}</span>\n }@else{\n <span class=\"text-sm text-light-primary dark:text-dark-primary\">{{ 'haloduck.ui.file.Uploading...' | transloco }}</span>\n }\n }@else{\n <button type=\"button\"\n class=\"text-light-danger dark:text-dark-danger hover:brightness-125\"\n (click)=\"removeFile(i)\">\n {{ 'haloduck.ui.file.Remove' | transloco }}\n </button>\n }\n </li>\n }\n </ul>\n }\n</div>\n" }]
1791
+ }], propDecorators: { disabled: [{
1792
+ type: Input
1793
+ }], urlPrefix: [{
1794
+ type: Input
1795
+ }], keyPrefix: [{
1796
+ type: Input
1797
+ }], uploadApi: [{
1798
+ type: Input
1799
+ }], multiple: [{
1800
+ type: Input
1801
+ }], maxCount: [{
1802
+ type: Input
1803
+ }], maxSize: [{
1804
+ type: Input
1805
+ }], accept: [{
1806
+ type: Input
1807
+ }], filesAdded: [{
1808
+ type: Output
1809
+ }], fileRemoved: [{
1810
+ type: Output
1811
+ }], error: [{
1812
+ type: Output
1813
+ }] } });
1814
+
1815
+ class FlipComponent {
1816
+ title = '';
1817
+ set isOpen(value) {
1818
+ this._isOpen.set(value);
1819
+ }
1820
+ _isOpen = signal(false, ...(ngDevMode ? [{ debugName: "_isOpen" }] : []));
1821
+ toggle() {
1822
+ this._isOpen.set(!this._isOpen());
1823
+ }
1824
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FlipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1825
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: FlipComponent, isStandalone: true, selector: "haloduck-flip", inputs: { title: "title", isOpen: "isOpen" }, ngImport: i0, template: "<div class=\"relative w-full shadow ring-1 ring-light-inactive/50 dark:ring-dark-inactive/80 sm:rounded-lg bg-light-inactive/20 dark:bg-dark-inactive/20 p-2 flex flex-col gap-2\">\n <label class=\"text-sm text-light-on-inactive dark:text-dark-on-inactive\">{{ title }}</label>\n @if(_isOpen()) {\n <ng-content></ng-content>\n }\n <button variant=\"secondary\"\n class=\"absolute rounded-md border border-inactive px-1 py-0.5 top-1 right-1 hover:cursor-pointer bg-white\"\n (click)=\"toggle()\">\n @if(_isOpen()) {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n class=\"h-4 w-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M18 15l-6-6-6 6\" />\n </svg>\n }\n @else {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n class=\"h-4 w-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M6 9l6 6 6-6\" />\n </svg>\n }\n </button>\n</div>\n", styles: [""] });
1826
+ }
1827
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FlipComponent, decorators: [{
1828
+ type: Component,
1829
+ args: [{ selector: 'haloduck-flip', imports: [], template: "<div class=\"relative w-full shadow ring-1 ring-light-inactive/50 dark:ring-dark-inactive/80 sm:rounded-lg bg-light-inactive/20 dark:bg-dark-inactive/20 p-2 flex flex-col gap-2\">\n <label class=\"text-sm text-light-on-inactive dark:text-dark-on-inactive\">{{ title }}</label>\n @if(_isOpen()) {\n <ng-content></ng-content>\n }\n <button variant=\"secondary\"\n class=\"absolute rounded-md border border-inactive px-1 py-0.5 top-1 right-1 hover:cursor-pointer bg-white\"\n (click)=\"toggle()\">\n @if(_isOpen()) {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n class=\"h-4 w-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M18 15l-6-6-6 6\" />\n </svg>\n }\n @else {\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n class=\"h-4 w-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M6 9l6 6 6-6\" />\n </svg>\n }\n </button>\n</div>\n" }]
1830
+ }], propDecorators: { title: [{
1831
+ type: Input
1832
+ }], isOpen: [{
1833
+ type: Input
1834
+ }] } });
1835
+
1836
+ class ImageViewerComponent {
1837
+ context;
1838
+ imageUrl = '';
1839
+ imageAlt = '';
1840
+ dialogService = inject(DialogService);
1841
+ onClose() {
1842
+ if (this.context?.overlayRef) {
1843
+ this.dialogService.close(this.context.overlayRef);
1844
+ }
1845
+ }
1846
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ImageViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1847
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: ImageViewerComponent, isStandalone: true, selector: "haloduck-image-viewer", inputs: { context: "context", imageUrl: "imageUrl", imageAlt: "imageAlt" }, ngImport: i0, template: "<img class=\"max-w-full max-h-full object-cover cursor-pointer\"\n [src]=\"context?.imageUrl\"\n [alt]=\"context?.imageAlt\"\n (click)=\"onClose()\" />", styles: [""] });
1848
+ }
1849
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ImageViewerComponent, decorators: [{
1850
+ type: Component,
1851
+ args: [{ selector: 'haloduck-image-viewer', imports: [], template: "<img class=\"max-w-full max-h-full object-cover cursor-pointer\"\n [src]=\"context?.imageUrl\"\n [alt]=\"context?.imageAlt\"\n (click)=\"onClose()\" />" }]
1852
+ }], propDecorators: { context: [{
1853
+ type: Input
1854
+ }], imageUrl: [{
1855
+ type: Input
1856
+ }], imageAlt: [{
1857
+ type: Input
1858
+ }] } });
1859
+
1860
+ // 전역 변수로 드래그 데이터를 임시 저장
1861
+ let draggedBase64File = null;
1862
+ let originDrag = null;
1863
+ class ImageUploaderComponent {
1864
+ disabled = false;
1865
+ id;
1866
+ alt;
1867
+ urlPrefix;
1868
+ keyPrefix;
1869
+ uploadApi;
1870
+ backgroundImage;
1871
+ shouldPassToSibling = false;
1872
+ passToSibling = new EventEmitter();
1873
+ fileAdded = new EventEmitter();
1874
+ fileRemoved = new EventEmitter();
1875
+ error = new EventEmitter();
1876
+ imageUrl = null;
1877
+ file = null;
1878
+ isDragOver = false;
1879
+ isUploading = false;
1880
+ dialogService = inject(DialogService);
1881
+ onChange = () => { };
1882
+ onTouched = () => { };
1883
+ writeValue(key) {
1884
+ if (!key) {
1885
+ this.imageUrl = null;
1886
+ return;
1887
+ }
1888
+ this.imageUrl = `${this.urlPrefix}/${key}`;
1889
+ this.file = null;
1890
+ }
1891
+ registerOnChange(fn) {
1892
+ this.onChange = fn;
1893
+ }
1894
+ registerOnTouched(fn) {
1895
+ this.onTouched = fn;
1896
+ }
1897
+ setDisabledState(isDisabled) {
1898
+ this.disabled = isDisabled;
1899
+ }
1900
+ startUpload() {
1901
+ // Placeholder for upload logic
1902
+ if (!this.file) {
1903
+ return new Observable();
1904
+ }
1905
+ this.isUploading = true;
1906
+ this.file.isUploading = true;
1907
+ this.file.isUploaded = false;
1908
+ return this.uploadApi(this.file, this.keyPrefix).pipe(switchMap((key) => {
1909
+ if (this.file) {
1910
+ this.file.isUploading = false;
1911
+ this.file.isUploaded = true;
1912
+ this.file.key = key;
1913
+ }
1914
+ return of(this.file);
1915
+ }));
1916
+ }
1917
+ onFileSelected(event) {
1918
+ const input = event.target;
1919
+ if (input.files && input.files[0]) {
1920
+ const listFile = Array.from(input.files);
1921
+ this.setFiles(listFile);
1922
+ }
1923
+ }
1924
+ onDrop(event) {
1925
+ event.preventDefault();
1926
+ this.isDragOver = false;
1927
+ if (event.dataTransfer?.files && event.dataTransfer.files[0]) {
1928
+ const listFile = Array.from(event.dataTransfer.files);
1929
+ this.setFiles(listFile);
1930
+ }
1931
+ else if (draggedBase64File) {
1932
+ if (this.file) {
1933
+ originDrag?.setFiles([this.file]); // 타켓에 파일이 있을 경우 원래 드래그한 컴포넌트에 파일 전달
1934
+ }
1935
+ else {
1936
+ originDrag?.removeImage();
1937
+ }
1938
+ fetch(draggedBase64File)
1939
+ .then((res) => res.blob())
1940
+ .then((blob) => {
1941
+ const file = new File([blob], 'dropped-file', { type: blob.type });
1942
+ this.setFiles([file]);
1943
+ });
1944
+ draggedBase64File = null; // 전역 변수 초기화
1945
+ }
1946
+ }
1947
+ onDragOver(event) {
1948
+ event.preventDefault();
1949
+ this.isDragOver = true;
1950
+ }
1951
+ onDragLeave(event) {
1952
+ event.preventDefault();
1953
+ this.isDragOver = false;
1954
+ }
1955
+ onDragStart(event) {
1956
+ if (event.target instanceof HTMLImageElement) {
1957
+ const img = event.target;
1958
+ if (img.id && this.file) {
1959
+ const reader = new FileReader();
1960
+ reader.onload = () => {
1961
+ const base64File = reader.result;
1962
+ draggedBase64File = base64File; // 전역 변수에 저장
1963
+ originDrag = this; // 드래그 시작한 컴포넌트 저장
1964
+ if (event.dataTransfer) {
1965
+ event.dataTransfer.setData('text/plain', 'dragged-file'); // 최소한의 데이터 설정
1966
+ }
1967
+ };
1968
+ reader.readAsDataURL(this.file);
1969
+ }
1970
+ }
1971
+ }
1972
+ onDragEnd(event) {
1973
+ draggedBase64File = null;
1974
+ originDrag = null;
1975
+ }
1976
+ removeImage() {
1977
+ const removedFile = this.file;
1978
+ this.imageUrl = null;
1979
+ this.file = null;
1980
+ this.fileRemoved.emit(removedFile);
1981
+ this.onChange(null); // Notify form control
1982
+ }
1983
+ setFiles(listFile) {
1984
+ listFile = listFile.filter((file) => {
1985
+ const isValid = file.type.startsWith('image/');
1986
+ if (!isValid) {
1987
+ console.error('Invalid file type:', file.type);
1988
+ }
1989
+ return isValid;
1990
+ });
1991
+ this.file = listFile[0];
1992
+ this.imageUrl = URL.createObjectURL(listFile[0]);
1993
+ if (listFile.length > 1 && this.shouldPassToSibling) {
1994
+ listFile.shift();
1995
+ this.passToSibling.emit(listFile);
1996
+ }
1997
+ this.fileAdded.emit(this.file);
1998
+ this.onChange(this.file.name);
1999
+ this.onTouched();
2000
+ }
2001
+ previewImage(imageUrl) {
2002
+ this.dialogService.open(ImageViewerComponent, {
2003
+ imageUrl,
2004
+ imageAlt: this.alt,
2005
+ });
2006
+ }
2007
+ ngOnInit() { }
2008
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ImageUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2009
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: ImageUploaderComponent, isStandalone: true, selector: "haloduck-image-uploader", inputs: { disabled: "disabled", id: "id", alt: "alt", urlPrefix: "urlPrefix", keyPrefix: "keyPrefix", uploadApi: "uploadApi", backgroundImage: "backgroundImage", shouldPassToSibling: "shouldPassToSibling" }, outputs: { passToSibling: "passToSibling", fileAdded: "fileAdded", fileRemoved: "fileRemoved", error: "error" }, providers: [
2010
+ {
2011
+ provide: NG_VALUE_ACCESSOR,
2012
+ useExisting: forwardRef(() => ImageUploaderComponent),
2013
+ multi: true,
2014
+ },
2015
+ provideTranslocoScope('haloduck'),
2016
+ ], ngImport: i0, template: "<div class=\"w-full h-full flex flex-col gap-1\">\n <label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n </label>\n <div class=\"h-full w-full p-2 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragstart)=\"onDragStart($event)\"\n (dragend)=\"onDragEnd($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <!-- Show image preview if available -->\n @if (imageUrl) {\n @if (!disabled) {\n <div class=\"relative\">\n <button type=\"button\"\n class=\"absolute -top-1 -right-1 bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger rounded-full px-1 text-xs\"\n (click)=\"removeImage()\">\n x\n </button>\n </div>\n }\n <img [src]=\"imageUrl\"\n [id]=\"id + '-image'\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\"\n [ngClass]=\"{\n 'filter grayscale': disabled,\n }\"\n (click)=\"previewImage(imageUrl)\" />\n } @else {\n <!-- Show upload area -->\n <label for=\"{{id + '-input'}}\"\n class=\"flex flex-col items-center justify-center w-full h-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md\"\n [ngClass]=\"{'cursor-pointer hover:border-light-primary dark:hover:border-dark-primary': !disabled}\">\n @if (backgroundImage) {\n <img [src]=\"backgroundImage\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\" />\n }@else {\n <span class=\"text-light-inactive dark:text-dark-inactive text-sm p-2\">{{ 'haloduck.ui.file.Drag and drop an image here, or click to select' | transloco }}</span>\n }\n\n @if(!disabled){\n <input [id]=\"id + '-input'\"\n type=\"file\"\n class=\"hidden\"\n accept=\"image/*\"\n [multiple]=\"passToSibling\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n }\n </label>\n }\n </div>\n</div>\n", styles: [".drag-over{border-color:#3b82f6}.relative{position:relative}button{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
2017
+ }
2018
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ImageUploaderComponent, decorators: [{
2019
+ type: Component,
2020
+ args: [{ selector: 'haloduck-image-uploader', imports: [CommonModule, TranslocoModule], providers: [
2021
+ {
2022
+ provide: NG_VALUE_ACCESSOR,
2023
+ useExisting: forwardRef(() => ImageUploaderComponent),
2024
+ multi: true,
2025
+ },
2026
+ provideTranslocoScope('haloduck'),
2027
+ ], template: "<div class=\"w-full h-full flex flex-col gap-1\">\n <label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n </label>\n <div class=\"h-full w-full p-2 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragstart)=\"onDragStart($event)\"\n (dragend)=\"onDragEnd($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <!-- Show image preview if available -->\n @if (imageUrl) {\n @if (!disabled) {\n <div class=\"relative\">\n <button type=\"button\"\n class=\"absolute -top-1 -right-1 bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger rounded-full px-1 text-xs\"\n (click)=\"removeImage()\">\n x\n </button>\n </div>\n }\n <img [src]=\"imageUrl\"\n [id]=\"id + '-image'\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\"\n [ngClass]=\"{\n 'filter grayscale': disabled,\n }\"\n (click)=\"previewImage(imageUrl)\" />\n } @else {\n <!-- Show upload area -->\n <label for=\"{{id + '-input'}}\"\n class=\"flex flex-col items-center justify-center w-full h-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md\"\n [ngClass]=\"{'cursor-pointer hover:border-light-primary dark:hover:border-dark-primary': !disabled}\">\n @if (backgroundImage) {\n <img [src]=\"backgroundImage\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\" />\n }@else {\n <span class=\"text-light-inactive dark:text-dark-inactive text-sm p-2\">{{ 'haloduck.ui.file.Drag and drop an image here, or click to select' | transloco }}</span>\n }\n\n @if(!disabled){\n <input [id]=\"id + '-input'\"\n type=\"file\"\n class=\"hidden\"\n accept=\"image/*\"\n [multiple]=\"passToSibling\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n }\n </label>\n }\n </div>\n</div>\n", styles: [".drag-over{border-color:#3b82f6}.relative{position:relative}button{cursor:pointer}\n"] }]
2028
+ }], propDecorators: { disabled: [{
2029
+ type: Input
2030
+ }], id: [{
2031
+ type: Input
2032
+ }], alt: [{
2033
+ type: Input
2034
+ }], urlPrefix: [{
2035
+ type: Input
2036
+ }], keyPrefix: [{
2037
+ type: Input
2038
+ }], uploadApi: [{
2039
+ type: Input
2040
+ }], backgroundImage: [{
2041
+ type: Input
2042
+ }], shouldPassToSibling: [{
2043
+ type: Input
2044
+ }], passToSibling: [{
2045
+ type: Output
2046
+ }], fileAdded: [{
2047
+ type: Output
2048
+ }], fileRemoved: [{
2049
+ type: Output
2050
+ }], error: [{
2051
+ type: Output
2052
+ }] } });
2053
+
2054
+ class LanguageSelectorOptionComponent {
2055
+ languageSelected = new EventEmitter();
2056
+ translateService = inject(TranslocoService);
2057
+ selectLanguage(language) {
2058
+ this.languageSelected.emit(language);
2059
+ }
2060
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: LanguageSelectorOptionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2061
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: LanguageSelectorOptionComponent, isStandalone: true, selector: "haloduck-language-selector-option", outputs: { languageSelected: "languageSelected" }, providers: [TranslocoService], ngImport: i0, template: "<div class=\"right-0 z-10 mt-2.5 origin-top-right rounded-md bg-light-background dark:bg-dark-background py-2 shadow-lg ring-1 ring-light-inactive dark:ring-dark-inactive\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n aria-labelledby=\"user-menu-button\"\n tabindex=\"0\">\n @for (language of translateService.getAvailableLangs(); track language) {\n <a href=\"javascript:void(0)\"\n class=\"px-3 py-2 flex items-center flex-nowrap gap-2\"\n role=\"menuitem\"\n tabindex=\"-1\"\n id=\"language-item-{{language}}\"\n (click)=\"selectLanguage(language)\">\n <img class=\"w-6 h-6\"\n [src]=\"'i18n/icons/' + language + '.png'\">\n </a>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
2062
+ }
2063
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: LanguageSelectorOptionComponent, decorators: [{
2064
+ type: Component,
2065
+ args: [{ selector: 'haloduck-language-selector-option', imports: [CommonModule], providers: [TranslocoService], template: "<div class=\"right-0 z-10 mt-2.5 origin-top-right rounded-md bg-light-background dark:bg-dark-background py-2 shadow-lg ring-1 ring-light-inactive dark:ring-dark-inactive\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n aria-labelledby=\"user-menu-button\"\n tabindex=\"0\">\n @for (language of translateService.getAvailableLangs(); track language) {\n <a href=\"javascript:void(0)\"\n class=\"px-3 py-2 flex items-center flex-nowrap gap-2\"\n role=\"menuitem\"\n tabindex=\"-1\"\n id=\"language-item-{{language}}\"\n (click)=\"selectLanguage(language)\">\n <img class=\"w-6 h-6\"\n [src]=\"'i18n/icons/' + language + '.png'\">\n </a>\n }\n</div>\n" }]
2066
+ }], propDecorators: { languageSelected: [{
2067
+ type: Output
2068
+ }] } });
2069
+
2070
+ class LanguageSelectorComponent {
2071
+ origin;
2072
+ destroy$ = new Subject();
2073
+ translateService = inject(TranslocoService);
2074
+ overlay = inject(Overlay);
2075
+ language = signal(this.translateService.getActiveLang(), ...(ngDevMode ? [{ debugName: "language" }] : []));
2076
+ overlayRef;
2077
+ showLanguageOptions() {
2078
+ if (!this.overlayRef) {
2079
+ this.overlayRef = this.overlay.create({
2080
+ positionStrategy: this.overlay
2081
+ .position()
2082
+ .flexibleConnectedTo(this.origin.nativeElement)
2083
+ .withPositions([
2084
+ {
2085
+ originX: 'start',
2086
+ originY: 'bottom',
2087
+ overlayX: 'start',
2088
+ overlayY: 'top',
2089
+ },
2090
+ {
2091
+ originX: 'start',
2092
+ originY: 'top',
2093
+ overlayX: 'start',
2094
+ overlayY: 'bottom',
2095
+ },
2096
+ ]),
2097
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
2098
+ hasBackdrop: true,
2099
+ backdropClass: 'cdk-overlay-transparent-backdrop',
2100
+ });
2101
+ const calendarPortal = new ComponentPortal(LanguageSelectorOptionComponent);
2102
+ const componentRef = this.overlayRef.attach(calendarPortal);
2103
+ // Pass values directly to the CalendarComponent instance
2104
+ // componentRef.instance.firstDayOfWeek = this.firstDayOfWeek;
2105
+ // componentRef.instance.singleDate = true;
2106
+ // componentRef.instance.selectedDate = this.selectedDate;
2107
+ // Subscribe to events emitted
2108
+ componentRef.instance.languageSelected
2109
+ .pipe(takeUntil(this.destroy$))
2110
+ .subscribe((language) => {
2111
+ this.translateService.setActiveLang(language);
2112
+ this.hideLanguageOptions();
2113
+ });
2114
+ // Handle clicks outside the overlay
2115
+ this.overlayRef
2116
+ .backdropClick()
2117
+ .pipe(takeUntil(this.destroy$))
2118
+ .subscribe(() => {
2119
+ this.hideLanguageOptions();
2120
+ });
2121
+ }
2122
+ }
2123
+ hideLanguageOptions() {
2124
+ if (this.overlayRef) {
2125
+ this.overlayRef.detach();
2126
+ this.overlayRef = undefined;
2127
+ }
2128
+ }
2129
+ ngOnInit() {
2130
+ this.translateService.langChanges$
2131
+ .pipe(distinctUntilChanged(), takeUntil(this.destroy$))
2132
+ .subscribe((lang) => {
2133
+ this.language.set(lang);
2134
+ });
2135
+ }
2136
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: LanguageSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2137
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: LanguageSelectorComponent, isStandalone: true, selector: "haloduck-language-selector", viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }], ngImport: i0, template: "<button #origin\n type=\"button\"\n (click)=\"showLanguageOptions()\"\n class=\"-m-1.5 flex items-center p-1.5 hover:cursor-pointer\"\n aria-expanded=\"false\"\n aria-haspopup=\"true\">\n <span class=\"max-lg:hidden flex lg:items-center\">\n <img class=\"w-6 h-6\"\n [src]=\"'i18n/icons/' + language() + '.png'\">\n </span>\n</button>\n", styles: [""] });
2138
+ }
2139
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: LanguageSelectorComponent, decorators: [{
2140
+ type: Component,
2141
+ args: [{ selector: 'haloduck-language-selector', imports: [], template: "<button #origin\n type=\"button\"\n (click)=\"showLanguageOptions()\"\n class=\"-m-1.5 flex items-center p-1.5 hover:cursor-pointer\"\n aria-expanded=\"false\"\n aria-haspopup=\"true\">\n <span class=\"max-lg:hidden flex lg:items-center\">\n <img class=\"w-6 h-6\"\n [src]=\"'i18n/icons/' + language() + '.png'\">\n </span>\n</button>\n" }]
2142
+ }], propDecorators: { origin: [{
2143
+ type: ViewChild,
2144
+ args: ['origin', { static: false }]
2145
+ }] } });
2146
+
2147
+ class MapToAddressComponent {
2148
+ coreService = inject(CoreService);
2149
+ mapId = this.coreService.getMapId();
2150
+ defaultLngLat = this.coreService.getDefaultLngLat();
2151
+ disabled = false;
2152
+ currentLngLat;
2153
+ _currentAddress;
2154
+ set currentAddress(value) {
2155
+ if (value) {
2156
+ this._currentAddress = value;
2157
+ this.setMapToAddress();
2158
+ }
2159
+ }
2160
+ locationChanged = new EventEmitter();
2161
+ map;
2162
+ marker;
2163
+ location;
2164
+ onChange = () => { };
2165
+ onTouched = () => { };
2166
+ async ngOnInit() {
2167
+ await this.initScript();
2168
+ this.initMap();
2169
+ }
2170
+ initScript() {
2171
+ return new Promise((resolve, reject) => {
2172
+ if (typeof google !== 'undefined') {
2173
+ resolve();
2174
+ return;
2175
+ }
2176
+ const script = document.createElement('script');
2177
+ script.src = `https://maps.googleapis.com/maps/api/js?key=${this.coreService.getGoogleApiKey()}&libraries=places,marker&loading=sync`;
2178
+ script.async = false;
2179
+ script.defer = true;
2180
+ script.onload = () => resolve();
2181
+ script.onerror = (error) => reject(error);
2182
+ document.head.appendChild(script);
2183
+ console.log('Google Maps script loaded', script);
2184
+ });
2185
+ }
2186
+ initMap() {
2187
+ if (this.currentLngLat) {
2188
+ isDevMode() &&
2189
+ console.log('Using provided location:', this.currentLngLat);
2190
+ this.initializeMap(this.currentLngLat);
2191
+ }
2192
+ else {
2193
+ navigator.geolocation.getCurrentPosition((position) => {
2194
+ const currentLocation = {
2195
+ lat: position.coords.latitude,
2196
+ lng: position.coords.longitude,
2197
+ };
2198
+ isDevMode() &&
2199
+ console.log('Using current location:', currentLocation);
2200
+ this.initializeMap(currentLocation);
2201
+ }, (error) => {
2202
+ if (error.code === error.PERMISSION_DENIED) {
2203
+ isDevMode() &&
2204
+ console.log('Location permission denied. Using default location.:', this.defaultLngLat);
2205
+ }
2206
+ else if (error.code === error.POSITION_UNAVAILABLE ||
2207
+ error.code === error.TIMEOUT) {
2208
+ isDevMode() &&
2209
+ console.log('Unable to determine location. Using default location.:', this.defaultLngLat);
2210
+ }
2211
+ this.initializeMap(this.defaultLngLat);
2212
+ });
2213
+ }
2214
+ }
2215
+ initializeMap(lngLat) {
2216
+ this.map = new google.maps.Map(document.getElementById('map'), {
2217
+ center: lngLat,
2218
+ zoom: 15,
2219
+ mapId: this.mapId,
2220
+ });
2221
+ this.marker = new google.maps.marker.AdvancedMarkerElement({
2222
+ position: lngLat,
2223
+ map: this.map,
2224
+ draggable: true,
2225
+ });
2226
+ // Listen for marker drag end to update address immediately
2227
+ this.marker.addListener('dragend', () => {
2228
+ const position = this.marker.position;
2229
+ if (position) {
2230
+ isDevMode() && console.log('Marker dragged to:', position.toJSON());
2231
+ this.getAddress(); // Fetch address after dragging
2232
+ }
2233
+ });
2234
+ this.map.addListener('click', (event) => this.onClick(event));
2235
+ if (!this.currentLngLat) {
2236
+ this.getAddress();
2237
+ }
2238
+ }
2239
+ onClick(event) {
2240
+ isDevMode() && console.log('Map clicked at:', event.latLng);
2241
+ if (event.latLng) {
2242
+ // Explicitly update marker position
2243
+ this.marker.position = event.latLng;
2244
+ this.marker.map = this.map; // Ensure marker is attached to the map
2245
+ isDevMode() &&
2246
+ console.log('Marker position updated to:', event.latLng.toJSON());
2247
+ this.getAddress(); // Fetch address immediately after click
2248
+ this.onTouched(); // Mark as touched
2249
+ }
2250
+ else {
2251
+ isDevMode() &&
2252
+ console.error('Failed to update marker position: event.latLng is undefined');
2253
+ }
2254
+ }
2255
+ setMapToAddress() {
2256
+ const geocoder = new google.maps.Geocoder();
2257
+ geocoder.geocode({ address: this._currentAddress }, (results, status) => {
2258
+ if (status === 'OK' && results[0]) {
2259
+ const location = results[0].geometry.location;
2260
+ isDevMode() && console.log('Geocoded location:', location.toJSON());
2261
+ this.initializeMap(location.toJSON());
2262
+ }
2263
+ else {
2264
+ isDevMode() && console.error('Geocoding failed:', status);
2265
+ }
2266
+ });
2267
+ }
2268
+ getAddress() {
2269
+ const geocoder = new google.maps.Geocoder();
2270
+ const position = this.marker.position;
2271
+ if (!position) {
2272
+ isDevMode() && console.error('Position update is unavailable');
2273
+ return;
2274
+ }
2275
+ geocoder.geocode({ location: position }, (results, status) => {
2276
+ isDevMode() && console.log('Geocoded results:', status, results);
2277
+ if (status === 'OK' && results[0]) {
2278
+ this.location = this.parseAddress(results);
2279
+ this.locationChanged.emit(this.location);
2280
+ this.onChange(this.location); // Notify the form control
2281
+ }
2282
+ });
2283
+ }
2284
+ parseAddress(results) {
2285
+ return {
2286
+ plusCode: this.getComponentLongName(results, 'plus_code'),
2287
+ postalCode: this.getComponentLongName(results, 'postal_code'),
2288
+ countryCode: this.getComponentShortName(results, 'country').toUpperCase(),
2289
+ country: this.getComponentLongName(results, 'country'),
2290
+ area1: this.getComponentLongName(results, 'administrative_area_level_1'),
2291
+ area2: this.getComponentLongName(results, 'sublocality_level_1'),
2292
+ area3: this.getComponentLongName(results, 'sublocality_level_2'),
2293
+ formatted: results[0]?.formatted_address,
2294
+ lat: results[0]?.geometry?.location?.lat(),
2295
+ lng: results[0]?.geometry?.location?.lng(),
2296
+ };
2297
+ }
2298
+ getComponentLongName(results, type) {
2299
+ for (let i = 0; i < results.length; i++) {
2300
+ const component = results[i].address_components.find((c) => c.types.includes(type));
2301
+ if (component) {
2302
+ return component.long_name;
2303
+ }
2304
+ }
2305
+ return '';
2306
+ }
2307
+ getComponentShortName(results, type) {
2308
+ for (let i = 0; i < results.length; i++) {
2309
+ const component = results[i].address_components.find((c) => c.types.includes(type));
2310
+ if (component) {
2311
+ return component.short_name;
2312
+ }
2313
+ }
2314
+ return '';
2315
+ }
2316
+ writeValue(value) {
2317
+ if (value) {
2318
+ this.location = value;
2319
+ this.currentLngLat = { lat: value.lat, lng: value.lng };
2320
+ this.initializeMap(this.currentLngLat);
2321
+ }
2322
+ }
2323
+ registerOnChange(fn) {
2324
+ this.onChange = fn;
2325
+ }
2326
+ registerOnTouched(fn) {
2327
+ this.onTouched = fn;
2328
+ }
2329
+ setDisabledState(isDisabled) {
2330
+ this.disabled = isDisabled;
2331
+ }
2332
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: MapToAddressComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2333
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: MapToAddressComponent, isStandalone: true, selector: "haloduck-map-to-address", inputs: { disabled: "disabled", currentLngLat: "currentLngLat", currentAddress: "currentAddress" }, outputs: { locationChanged: "locationChanged" }, providers: [
2334
+ {
2335
+ provide: NG_VALUE_ACCESSOR,
2336
+ useExisting: MapToAddressComponent,
2337
+ multi: true,
2338
+ },
2339
+ ], ngImport: i0, template: "<div id=\"map\" class=\"w-full h-full\"></div>", styles: ["#map{border:1px solid #ccc;margin-bottom:10px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
2340
+ }
2341
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: MapToAddressComponent, decorators: [{
2342
+ type: Component,
2343
+ args: [{ selector: 'haloduck-map-to-address', imports: [CommonModule, FormsModule], providers: [
2344
+ {
2345
+ provide: NG_VALUE_ACCESSOR,
2346
+ useExisting: MapToAddressComponent,
2347
+ multi: true,
2348
+ },
2349
+ ], template: "<div id=\"map\" class=\"w-full h-full\"></div>", styles: ["#map{border:1px solid #ccc;margin-bottom:10px}\n"] }]
2350
+ }], propDecorators: { disabled: [{
2351
+ type: Input
2352
+ }], currentLngLat: [{
2353
+ type: Input
2354
+ }], currentAddress: [{
2355
+ type: Input
2356
+ }], locationChanged: [{
2357
+ type: Output
2358
+ }] } });
2359
+
2360
+ class SideMenuItemComponent {
2361
+ sanitizer;
2362
+ router = inject(Router);
2363
+ menuItem;
2364
+ depth = 0;
2365
+ menuItemSelectHandler;
2366
+ iconSvg;
2367
+ constructor(sanitizer) {
2368
+ this.sanitizer = sanitizer;
2369
+ }
2370
+ ngOnInit() {
2371
+ if (this.menuItem.iconType === 'svg') {
2372
+ fetch(this.menuItem.icon || '')
2373
+ .then(response => { return response.ok ? response : Promise.reject(response); })
2374
+ .then(response => response.text())
2375
+ .then(svg => this.iconSvg = this.sanitizer.bypassSecurityTrustHtml(svg));
2376
+ }
2377
+ }
2378
+ onMenuItemSelected(menuItem) {
2379
+ if (this.menuItem.link) {
2380
+ this.navigateTo([this.menuItem.link]);
2381
+ }
2382
+ if (this.menuItemSelectHandler) {
2383
+ this.menuItemSelectHandler.emit(this.menuItem);
2384
+ }
2385
+ }
2386
+ navigateTo(link) {
2387
+ this.router.navigate(link);
2388
+ }
2389
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SideMenuItemComponent, deps: [{ token: i1$4.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
2390
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SideMenuItemComponent, isStandalone: true, selector: "haloduck-side-menu-item", inputs: { menuItem: "menuItem", depth: "depth", menuItemSelectHandler: "menuItemSelectHandler" }, ngImport: i0, template: "<a href=\"javascript:void(0)\"\n (click)=\"onMenuItemSelected(menuItem)\"\n class=\"flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:text-light-on-primary dark:hover:text-dark-on-primary hover:bg-light-primary dark:hover:bg-dark-primary hover:cursor-pointer\"\n [ngClass]=\"{\n 'font-bold text-light-on-secondary dark:bg-dark-on-secondary bg-light-secondary dark:bg-dark-secondary': menuItem.isSelected,\n }\">\n @if (menuItem.iconType === 'svg') {\n <div [innerHTML]=\"iconSvg\"></div>\n } @else {\n @if (menuItem.icon) {\n <img class=\"w-5 h-5\"\n [src]=\"menuItem.icon\"\n [alt]=\"menuItem.title\"\n [title]=\"menuItem.title\"\n [loading]=\"'lazy'\" />\n }\n }\n <span class=\"overflow-hidden text-ellipsis whitespace-nowrap\"\n title=\"{{ menuItem.title }}\"\n [ngStyle]=\"{'margin-left.px': depth ? depth * 16 : 0}\">\n {{ menuItem.title }}\n </span>\n</a>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
2391
+ }
2392
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SideMenuItemComponent, decorators: [{
2393
+ type: Component,
2394
+ args: [{ selector: 'haloduck-side-menu-item', imports: [CommonModule], template: "<a href=\"javascript:void(0)\"\n (click)=\"onMenuItemSelected(menuItem)\"\n class=\"flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:text-light-on-primary dark:hover:text-dark-on-primary hover:bg-light-primary dark:hover:bg-dark-primary hover:cursor-pointer\"\n [ngClass]=\"{\n 'font-bold text-light-on-secondary dark:bg-dark-on-secondary bg-light-secondary dark:bg-dark-secondary': menuItem.isSelected,\n }\">\n @if (menuItem.iconType === 'svg') {\n <div [innerHTML]=\"iconSvg\"></div>\n } @else {\n @if (menuItem.icon) {\n <img class=\"w-5 h-5\"\n [src]=\"menuItem.icon\"\n [alt]=\"menuItem.title\"\n [title]=\"menuItem.title\"\n [loading]=\"'lazy'\" />\n }\n }\n <span class=\"overflow-hidden text-ellipsis whitespace-nowrap\"\n title=\"{{ menuItem.title }}\"\n [ngStyle]=\"{'margin-left.px': depth ? depth * 16 : 0}\">\n {{ menuItem.title }}\n </span>\n</a>\n" }]
2395
+ }], ctorParameters: () => [{ type: i1$4.DomSanitizer }], propDecorators: { menuItem: [{
2396
+ type: Input
2397
+ }], depth: [{
2398
+ type: Input
2399
+ }], menuItemSelectHandler: [{
2400
+ type: Input
2401
+ }] } });
2402
+
2403
+ class SideMenuComponent {
2404
+ menuItems = [];
2405
+ depth = 0;
2406
+ menuItemSelected = new EventEmitter();
2407
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SideMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2408
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SideMenuComponent, isStandalone: true, selector: "haloduck-side-menu", inputs: { menuItems: "menuItems", depth: "depth" }, outputs: { menuItemSelected: "menuItemSelected" }, ngImport: i0, template: "<div class=\"flex flex-col text-sm text-light-on-background dark:text-dark-on-background\">\n @for (menuItem of menuItems; track menuItem.link) {\n <haloduck-side-menu-item [menuItemSelectHandler]=\"menuItemSelected\"\n [menuItem]=\"menuItem\"\n [depth]=\"depth\"></haloduck-side-menu-item>\n @if (menuItem.children && menuItem.children.length > 0) {\n <haloduck-side-menu [menuItems]=\"menuItem.children\"\n [depth]=\"depth + 1\"\n (menuItemSelected)=\"menuItemSelected.emit($event)\"></haloduck-side-menu>\n }\n }\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: SideMenuComponent, selector: "haloduck-side-menu", inputs: ["menuItems", "depth"], outputs: ["menuItemSelected"] }, { kind: "component", type: SideMenuItemComponent, selector: "haloduck-side-menu-item", inputs: ["menuItem", "depth", "menuItemSelectHandler"] }] });
2409
+ }
2410
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SideMenuComponent, decorators: [{
2411
+ type: Component,
2412
+ args: [{ selector: 'haloduck-side-menu', imports: [SideMenuItemComponent], template: "<div class=\"flex flex-col text-sm text-light-on-background dark:text-dark-on-background\">\n @for (menuItem of menuItems; track menuItem.link) {\n <haloduck-side-menu-item [menuItemSelectHandler]=\"menuItemSelected\"\n [menuItem]=\"menuItem\"\n [depth]=\"depth\"></haloduck-side-menu-item>\n @if (menuItem.children && menuItem.children.length > 0) {\n <haloduck-side-menu [menuItems]=\"menuItem.children\"\n [depth]=\"depth + 1\"\n (menuItemSelected)=\"menuItemSelected.emit($event)\"></haloduck-side-menu>\n }\n }\n</div>\n" }]
2413
+ }], propDecorators: { menuItems: [{
2414
+ type: Input
2415
+ }], depth: [{
2416
+ type: Input
2417
+ }], menuItemSelected: [{
2418
+ type: Output
2419
+ }] } });
2420
+
2421
+ class StlViewerComponent {
2422
+ fileUrl;
2423
+ filename;
2424
+ context;
2425
+ containerRef;
2426
+ dialogService = inject(DialogService);
2427
+ isPopup = false;
2428
+ scene;
2429
+ camera;
2430
+ renderer;
2431
+ loader = new STLLoader();
2432
+ controls;
2433
+ loadingPercentage = 0;
2434
+ showStl() {
2435
+ if (this.isPopup) {
2436
+ return;
2437
+ }
2438
+ this.dialogService.open(StlViewerComponent, {
2439
+ fileUrl: this.fileUrl,
2440
+ filename: this.filename,
2441
+ }, ['w-full', 'h-full']);
2442
+ }
2443
+ download() {
2444
+ download(this.fileUrl);
2445
+ }
2446
+ closeDialog() {
2447
+ this.dialogService.close(this.context.overlayRef);
2448
+ }
2449
+ animate = () => {
2450
+ requestAnimationFrame(this.animate);
2451
+ this.controls.update();
2452
+ this.renderer.render(this.scene, this.camera);
2453
+ };
2454
+ ngAfterViewInit() {
2455
+ const container = this.containerRef.nativeElement;
2456
+ this.scene = new THREE.Scene();
2457
+ this.scene.background = new THREE.Color(0xffffff);
2458
+ this.camera = new THREE.PerspectiveCamera(50, container.clientWidth / container.clientHeight, 0.1, 1000);
2459
+ this.camera.position.z = 100;
2460
+ this.renderer = new THREE.WebGLRenderer({ antialias: true });
2461
+ this.renderer.setSize(container.clientWidth, container.clientHeight);
2462
+ container.appendChild(this.renderer.domElement);
2463
+ this.controls = new OrbitControls(this.camera, this.renderer.domElement);
2464
+ this.controls.enableDamping = true;
2465
+ this.controls.dampingFactor = 0.05;
2466
+ // Adjust lighting
2467
+ const light = new THREE.DirectionalLight(0xffffff, 1.5); // Increased intensity
2468
+ light.position.set(0, 0, 1).normalize();
2469
+ const light2 = new THREE.DirectionalLight(0xffffff, 1.5); // Increased intensity
2470
+ light2.position.set(0, 0, -1).normalize();
2471
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // Added ambient light
2472
+ this.scene.add(light, light2, ambientLight);
2473
+ this.loader.load(this.fileUrl || this.context?.fileUrl, (geometry) => {
2474
+ // Use MeshStandardMaterial with DoubleSide to render both sides
2475
+ const material = new THREE.MeshStandardMaterial({
2476
+ color: 0xffffff,
2477
+ metalness: 0.3,
2478
+ roughness: 0.5,
2479
+ side: THREE.DoubleSide, // Render both front and back faces
2480
+ });
2481
+ const mesh = new THREE.Mesh(geometry, material);
2482
+ geometry.computeBoundingBox();
2483
+ const boundingBox = geometry.boundingBox;
2484
+ const center = new THREE.Vector3();
2485
+ boundingBox?.getCenter(center);
2486
+ mesh.geometry.translate(-center.x, -center.y, -center.z);
2487
+ this.scene.add(mesh);
2488
+ const size = new THREE.Vector3();
2489
+ boundingBox?.getSize(size);
2490
+ const maxDim = Math.max(size.x, size.y, size.z);
2491
+ this.camera.position.z = maxDim * 2;
2492
+ }, (xhr) => {
2493
+ this.loadingPercentage = (xhr.loaded / xhr.total) * 100;
2494
+ isDevMode() && console.log(this.loadingPercentage + '% loaded');
2495
+ }, (error) => {
2496
+ isDevMode() && console.error('Error loading STL:', error);
2497
+ });
2498
+ this.animate();
2499
+ }
2500
+ ngOnDestroy() {
2501
+ this.renderer.dispose();
2502
+ this.controls.dispose();
2503
+ }
2504
+ ngOnInit() {
2505
+ if (this.context) {
2506
+ this.isPopup = true;
2507
+ this.fileUrl = this.context.fileUrl;
2508
+ this.filename = this.context.filename;
2509
+ }
2510
+ }
2511
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: StlViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2512
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: StlViewerComponent, isStandalone: true, selector: "haloduck-stl-viewer", inputs: { fileUrl: "fileUrl", filename: "filename", context: "context" }, providers: [provideTranslocoScope('haloduck')], viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["viewerContainer"], descendants: true }], ngImport: i0, template: "<div class=\"relative w-full h-full flex flex-col items-end\">\n <div class=\"w-full h-full flex items-start flex-col\">\n <div #viewerContainer\n class=\"w-full h-full rounded-md\"></div>\n @if (loadingPercentage < 100)\n {\n <div\n class=\"h-2 bg-light-primary-light dark:bg-dark-primary-light\"\n [ngStyle]=\"{ 'width': loadingPercentage + '%' }\">\n </div>\n }\n</div>\n<div class=\"absolute bottom-2 right-0 flex justify-end gap-2\">\n @if (context) {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"danger\"\n (click)=\"closeDialog()\">{{ 'haloduck.ui.button.Close' | transloco }}</haloduck-button>\n } @else {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"secondary\"\n (click)=\"showStl()\">{{ 'haloduck.ui.button.View' | transloco }}</haloduck-button>\n }\n</div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
2513
+ }
2514
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: StlViewerComponent, decorators: [{
2515
+ type: Component,
2516
+ args: [{ selector: 'haloduck-stl-viewer', imports: [CommonModule, ButtonComponent, TranslocoModule], providers: [provideTranslocoScope('haloduck')], template: "<div class=\"relative w-full h-full flex flex-col items-end\">\n <div class=\"w-full h-full flex items-start flex-col\">\n <div #viewerContainer\n class=\"w-full h-full rounded-md\"></div>\n @if (loadingPercentage < 100)\n {\n <div\n class=\"h-2 bg-light-primary-light dark:bg-dark-primary-light\"\n [ngStyle]=\"{ 'width': loadingPercentage + '%' }\">\n </div>\n }\n</div>\n<div class=\"absolute bottom-2 right-0 flex justify-end gap-2\">\n @if (context) {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"danger\"\n (click)=\"closeDialog()\">{{ 'haloduck.ui.button.Close' | transloco }}</haloduck-button>\n } @else {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"secondary\"\n (click)=\"showStl()\">{{ 'haloduck.ui.button.View' | transloco }}</haloduck-button>\n }\n</div>\n</div>\n" }]
2517
+ }], propDecorators: { fileUrl: [{
2518
+ type: Input
2519
+ }], filename: [{
2520
+ type: Input
2521
+ }], context: [{
2522
+ type: Input
2523
+ }], containerRef: [{
2524
+ type: ViewChild,
2525
+ args: ['viewerContainer']
2526
+ }] } });
2527
+
2528
+ class TableComponent {
2529
+ coreService = inject(CoreService);
2530
+ tableLayout = 'auto';
2531
+ showHeader = true;
2532
+ useLoadMore = true;
2533
+ columns = [];
2534
+ rows = new Observable();
2535
+ isLoading = new Observable();
2536
+ isPaging = new Observable();
2537
+ sort = new Observable();
2538
+ expandedTemplate; // TODO
2539
+ customTemplates = {}; // Add this line
2540
+ onSortChange = new EventEmitter();
2541
+ lastEvaluatedKey = new Observable();
2542
+ onLoadMore = new EventEmitter();
2543
+ onRowClick = new EventEmitter();
2544
+ onRowDblClick = new EventEmitter();
2545
+ getColumnValue(row, column) {
2546
+ const value = this.getColumnValueRaw(row, column);
2547
+ if (column.customRenderFn) {
2548
+ return column.customRenderFn(value);
2549
+ }
2550
+ switch (column.type) {
2551
+ case 'date':
2552
+ return this.getDate(value);
2553
+ case 'datetime':
2554
+ return this.getDateTime(value);
2555
+ case 'time':
2556
+ return this.getTime(value);
2557
+ default:
2558
+ return of(value);
2559
+ }
2560
+ }
2561
+ getColumnValueRaw(row, column) {
2562
+ if (Array.isArray(column.key)) {
2563
+ const value = this.getColumnValueRawWithArrayKey(row, column);
2564
+ return value;
2565
+ }
2566
+ else {
2567
+ return row[column.key];
2568
+ }
2569
+ }
2570
+ getColumnValueRawWithArrayKey(row, column) {
2571
+ let value = row;
2572
+ let invalidKey = false;
2573
+ for (const key of column.key) {
2574
+ if (Array.isArray(column.key) && column.key.length > 1) {
2575
+ const [first, ...remainingKeys] = column.key;
2576
+ return this.getColumnValueRawWithArrayKey(value[first], {
2577
+ ...column,
2578
+ key: remainingKeys,
2579
+ });
2580
+ }
2581
+ else {
2582
+ if (invalidKey || value[key] === undefined) {
2583
+ invalidKey = true;
2584
+ break;
2585
+ }
2586
+ value = value[key];
2587
+ }
2588
+ }
2589
+ if (invalidKey) {
2590
+ value = '-';
2591
+ }
2592
+ return value;
2593
+ }
2594
+ getNumber(data) {
2595
+ const numberPipe = new DecimalPipe('en-US');
2596
+ return of(numberPipe.transform(data) || '');
2597
+ }
2598
+ getDate(data) {
2599
+ const datePipe = new DatePipe('en-US');
2600
+ return of(datePipe.transform(data, 'shortDate') || '');
2601
+ }
2602
+ getDateTime(data) {
2603
+ const datePipe = new DatePipe('en-US');
2604
+ return of(datePipe.transform(data, this.coreService.getDateFormatLong()) || '');
2605
+ }
2606
+ getTime(data) {
2607
+ const datePipe = new DatePipe('en-US');
2608
+ return of(datePipe.transform(data, 'Time') || '');
2609
+ }
2610
+ onUpdateSort(field, direction) {
2611
+ this.onSortChange.emit({ field, direction });
2612
+ }
2613
+ getSortDirection(field) {
2614
+ return this.sort.pipe(map((sort) => {
2615
+ const sortField = sort.find((s) => s.field === field);
2616
+ return sortField?.direction;
2617
+ }));
2618
+ }
2619
+ onLoadMoreClicked() {
2620
+ this.lastEvaluatedKey.pipe(take(1)).subscribe((lastEvaluatedKey) => {
2621
+ this.onLoadMore.emit(lastEvaluatedKey);
2622
+ });
2623
+ }
2624
+ onRowClicked(row) {
2625
+ this.onRowClick.emit(row);
2626
+ }
2627
+ onRowDblClicked(row) {
2628
+ this.onRowDblClick.emit(row);
2629
+ }
2630
+ ngOnInit() { }
2631
+ constructor() { }
2632
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2633
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: TableComponent, isStandalone: true, selector: "haloduck-table", inputs: { tableLayout: "tableLayout", showHeader: "showHeader", useLoadMore: "useLoadMore", columns: "columns", rows: "rows", isLoading: "isLoading", isPaging: "isPaging", sort: "sort", expandedTemplate: "expandedTemplate", customTemplates: "customTemplates", lastEvaluatedKey: "lastEvaluatedKey" }, outputs: { onSortChange: "onSortChange", onLoadMore: "onLoadMore", onRowClick: "onRowClick", onRowDblClick: "onRowDblClick" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<div class=\"w-full h-full shadow border border-light-inactive dark:border-dark-inactive rounded-lg overflow-auto\">\n <table class=\"w-full h-full\"\n [ngClass]=\"{\n 'table-fixed': tableLayout === 'fixed',\n 'table-auto': tableLayout === 'auto',\n }\">\n <colgroup>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <col class=\"{{ column.width || '' }}\" />\n }\n }\n </colgroup>\n @if (showHeader) {\n <thead class=\"bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-tl-lg rounded-tr-lg\">\n <tr>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <th scope=\"col\"\n class=\"{{ column.align || '' }} py-3.5 pl-4 pr-3 text-sm font-semibold text-light-on-background dark:text-dark-on-background whitespace-nowrap\">\n <span class=\"group inline-flex\">\n {{ column.label }}\n @if (column.sortable) {\n @switch (getSortDirection(column.sort?.field || column.key) | async)\n {\n @case('desc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @case('asc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'desc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @default {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"invisible ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive group-hover:visible group-focus:visible\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n }\n }\n </span>\n </th>\n }\n }\n </tr>\n </thead>\n }\n <tbody class=\"overflow-scroll\">\n @for (row of rows | async; track row['id']; let lastRow = $last) {\n <tr class=\"border-t border-light-inactive dark:border-dark-inactive {{ row.bgColor || '' }}\"\n (click)=\"onRowClicked(row)\"\n (dblclick)=\"onRowDblClicked(row)\"\n [ngClass]=\"{'rounded-b-lg': lastRow && (lastEvaluatedKey | async) === null, 'even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background': !row.bgColor }\"\n [ngStyle]=\"{ 'background-color': row.bgColor || '' }\">\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <td class=\"relative overflow-visible {{ column.align || '' }} whitespace-nowrap text-left px-3 text-sm text-light-on-background dark:text-dark-on-background\"\n [ngClass]=\"{\n 'first:rounded-bl-lg last:rounded-br-lg': lastRow && (lastEvaluatedKey | async) === null,\n 'text-wrap': row.isExpanded && column.type !== 'custom',\n 'overflow-x-hidden text-ellipsis' : !row.isExpanded && column.type !== 'custom',\n 'py-2': column.type === 'custom',\n 'py-4': column.type !== 'custom'\n }\">\n @if (column.type === 'custom') {\n @if (column.customRenderTemplate) {\n <ng-container [ngTemplateOutlet]=\"customTemplates[column.customRenderTemplate]\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n }\n }\n @else {\n {{ getColumnValue(row, column) | async }}\n }\n </td>\n }\n }\n </tr>\n\n @if (row.isExpanded && expandedTemplate) {\n <tr>\n <td [attr.colspan]=\"columns.length\">\n </td>\n </tr>\n <tr class=\"even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 pb-4 text-sm text-center\">\n <ng-container [ngTemplateOutlet]=\"expandedTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n </td>\n </tr>\n }\n\n @if (lastRow && (lastEvaluatedKey | async) && !(isLoading | async) && useLoadMore) {\n <tr #loadMoreRow class=\"border-t border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n <div (click)=\"onLoadMoreClicked()\"\n class=\"cursor-pointer\">\n {{ 'haloduck.ui.table.Load More...' | transloco }}\n </div>\n </td>\n </tr>\n }\n\n } @empty {\n @if(!(isLoading | async)) {\n <tr>\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n {{ 'haloduck.ui.table.No data available.' | transloco }}\n </td>\n </tr>\n }\n }\n\n @if(isLoading | async) {\n @for ( i of [0,1,2,3,4]; track i; let lastRow = $last)\n {\n <tr class=\"bg-light-background dark:bg-dark-background border-t border-light-inactive/50 dark:border-dark-inactive/50\">\n @for (column of columns; track column.key) {\n <td class=\"whitespace-nowrap py-4 pl-4 pr-3 text-sm\"\n [ngClass]=\"{'first:rounded-bl-lg last:rounded-br-lg': lastRow}\">\n <div class=\"h-4 bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-md animate-pulse\"></div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2634
+ }
2635
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TableComponent, decorators: [{
2636
+ type: Component,
2637
+ args: [{ selector: 'haloduck-table', imports: [CommonModule, TranslocoModule], providers: [provideTranslocoScope('haloduck')], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"w-full h-full shadow border border-light-inactive dark:border-dark-inactive rounded-lg overflow-auto\">\n <table class=\"w-full h-full\"\n [ngClass]=\"{\n 'table-fixed': tableLayout === 'fixed',\n 'table-auto': tableLayout === 'auto',\n }\">\n <colgroup>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <col class=\"{{ column.width || '' }}\" />\n }\n }\n </colgroup>\n @if (showHeader) {\n <thead class=\"bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-tl-lg rounded-tr-lg\">\n <tr>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <th scope=\"col\"\n class=\"{{ column.align || '' }} py-3.5 pl-4 pr-3 text-sm font-semibold text-light-on-background dark:text-dark-on-background whitespace-nowrap\">\n <span class=\"group inline-flex\">\n {{ column.label }}\n @if (column.sortable) {\n @switch (getSortDirection(column.sort?.field || column.key) | async)\n {\n @case('desc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @case('asc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'desc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @default {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"invisible ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive group-hover:visible group-focus:visible\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n }\n }\n </span>\n </th>\n }\n }\n </tr>\n </thead>\n }\n <tbody class=\"overflow-scroll\">\n @for (row of rows | async; track row['id']; let lastRow = $last) {\n <tr class=\"border-t border-light-inactive dark:border-dark-inactive {{ row.bgColor || '' }}\"\n (click)=\"onRowClicked(row)\"\n (dblclick)=\"onRowDblClicked(row)\"\n [ngClass]=\"{'rounded-b-lg': lastRow && (lastEvaluatedKey | async) === null, 'even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background': !row.bgColor }\"\n [ngStyle]=\"{ 'background-color': row.bgColor || '' }\">\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <td class=\"relative overflow-visible {{ column.align || '' }} whitespace-nowrap text-left px-3 text-sm text-light-on-background dark:text-dark-on-background\"\n [ngClass]=\"{\n 'first:rounded-bl-lg last:rounded-br-lg': lastRow && (lastEvaluatedKey | async) === null,\n 'text-wrap': row.isExpanded && column.type !== 'custom',\n 'overflow-x-hidden text-ellipsis' : !row.isExpanded && column.type !== 'custom',\n 'py-2': column.type === 'custom',\n 'py-4': column.type !== 'custom'\n }\">\n @if (column.type === 'custom') {\n @if (column.customRenderTemplate) {\n <ng-container [ngTemplateOutlet]=\"customTemplates[column.customRenderTemplate]\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n }\n }\n @else {\n {{ getColumnValue(row, column) | async }}\n }\n </td>\n }\n }\n </tr>\n\n @if (row.isExpanded && expandedTemplate) {\n <tr>\n <td [attr.colspan]=\"columns.length\">\n </td>\n </tr>\n <tr class=\"even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 pb-4 text-sm text-center\">\n <ng-container [ngTemplateOutlet]=\"expandedTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n </td>\n </tr>\n }\n\n @if (lastRow && (lastEvaluatedKey | async) && !(isLoading | async) && useLoadMore) {\n <tr #loadMoreRow class=\"border-t border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n <div (click)=\"onLoadMoreClicked()\"\n class=\"cursor-pointer\">\n {{ 'haloduck.ui.table.Load More...' | transloco }}\n </div>\n </td>\n </tr>\n }\n\n } @empty {\n @if(!(isLoading | async)) {\n <tr>\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n {{ 'haloduck.ui.table.No data available.' | transloco }}\n </td>\n </tr>\n }\n }\n\n @if(isLoading | async) {\n @for ( i of [0,1,2,3,4]; track i; let lastRow = $last)\n {\n <tr class=\"bg-light-background dark:bg-dark-background border-t border-light-inactive/50 dark:border-dark-inactive/50\">\n @for (column of columns; track column.key) {\n <td class=\"whitespace-nowrap py-4 pl-4 pr-3 text-sm\"\n [ngClass]=\"{'first:rounded-bl-lg last:rounded-br-lg': lastRow}\">\n <div class=\"h-4 bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-md animate-pulse\"></div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n" }]
2638
+ }], ctorParameters: () => [], propDecorators: { tableLayout: [{
2639
+ type: Input
2640
+ }], showHeader: [{
2641
+ type: Input
2642
+ }], useLoadMore: [{
2643
+ type: Input
2644
+ }], columns: [{
2645
+ type: Input
2646
+ }], rows: [{
2647
+ type: Input
2648
+ }], isLoading: [{
2649
+ type: Input
2650
+ }], isPaging: [{
2651
+ type: Input
2652
+ }], sort: [{
2653
+ type: Input
2654
+ }], expandedTemplate: [{
2655
+ type: Input
2656
+ }], customTemplates: [{
2657
+ type: Input
2658
+ }], onSortChange: [{
2659
+ type: Output
2660
+ }], lastEvaluatedKey: [{
2661
+ type: Input
2662
+ }], onLoadMore: [{
2663
+ type: Output
2664
+ }], onRowClick: [{
2665
+ type: Output
2666
+ }], onRowDblClick: [{
2667
+ type: Output
2668
+ }] } });
2669
+
2670
+ class ToggleComponent {
2671
+ layout = 'vertical';
2672
+ value = false;
2673
+ disabled = false;
2674
+ nullable = false;
2675
+ toggled = new EventEmitter();
2676
+ label;
2677
+ onChange = (value) => { };
2678
+ onTouched = () => { };
2679
+ writeValue(value) {
2680
+ if (!this.nullable && (value === null || value === undefined)) {
2681
+ value = false;
2682
+ }
2683
+ this.value = value;
2684
+ }
2685
+ registerOnChange(fn) {
2686
+ this.onChange = fn;
2687
+ }
2688
+ registerOnTouched(fn) {
2689
+ this.onTouched = fn;
2690
+ }
2691
+ setDisabledState(isDisabled) {
2692
+ this.disabled = isDisabled;
2693
+ }
2694
+ onToggle() {
2695
+ if (this.nullable) {
2696
+ this.value = this.value ? false : this.value === null ? true : null;
2697
+ }
2698
+ else {
2699
+ this.value = !this.value;
2700
+ }
2701
+ this.onChange(this.value);
2702
+ this.onTouched();
2703
+ this.toggled.emit(this.value);
2704
+ }
2705
+ ngAfterViewInit() {
2706
+ // hide label if no content.
2707
+ if (!this.label.nativeElement.innerText.trim()) {
2708
+ this.label.nativeElement.style.display = 'none';
2709
+ }
2710
+ }
2711
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ToggleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2712
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: ToggleComponent, isStandalone: true, selector: "haloduck-toggle", inputs: { layout: "layout", value: "value", disabled: "disabled", nullable: "nullable" }, outputs: { toggled: "toggled" }, providers: [
2713
+ {
2714
+ provide: NG_VALUE_ACCESSOR,
2715
+ useExisting: forwardRef(() => ToggleComponent),
2716
+ multi: true,
2717
+ },
2718
+ ], viewQueries: [{ propertyName: "label", first: true, predicate: ["label"], descendants: true }], ngImport: i0, template: "<div class=\"flex gap-2\"\n [ngClass]=\"{'items-center justify-start': 'horizontal' === layout, 'flex-col items-start justify-center ': 'vertical' === layout}\">\n <label #label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control text-left\">\n <ng-content></ng-content>\n </label>\n <button class=\"w-12 p-1 rounded-l-full rounded-r-full outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-light-primary dark:foucs:outline-dark-primary bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/60 flex hover:cursor-pointer\"\n (click)=\"onToggle()\"\n [disabled]=\"disabled\"\n [ngClass]=\"{'justify-end': true === value, 'justify-center': null === value, 'justify-start': false === value}\">\n <div class=\"shrink-0 h-[1.5rem] rounded-l-full rounded-r-full\"\n [ngClass]=\"{'bg-light-primary dark:bg-dark-primary disabled:bg-light-primary/60 dark:disabled:bg-dark-primary/60': true === value, 'bg-light-inactive dark:bg-dark-inactive disabled:bg-light-inactive/60 dark:disabled:bg-dark-inactive/60': false === value,\n 'w-[1.5rem]': true === value || false === value, 'bg-light-primary-light dark:bg-dark-primary-light disabled:bg-light-primary-light/60 dark:disabled:bg-dark-primary-light/60 w-[2.4rem]': null === value}\">\n </div>\n </button>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
2719
+ }
2720
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ToggleComponent, decorators: [{
2721
+ type: Component,
2722
+ args: [{ selector: 'haloduck-toggle', imports: [CommonModule], providers: [
2723
+ {
2724
+ provide: NG_VALUE_ACCESSOR,
2725
+ useExisting: forwardRef(() => ToggleComponent),
2726
+ multi: true,
2727
+ },
2728
+ ], template: "<div class=\"flex gap-2\"\n [ngClass]=\"{'items-center justify-start': 'horizontal' === layout, 'flex-col items-start justify-center ': 'vertical' === layout}\">\n <label #label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control text-left\">\n <ng-content></ng-content>\n </label>\n <button class=\"w-12 p-1 rounded-l-full rounded-r-full outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-light-primary dark:foucs:outline-dark-primary bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/60 flex hover:cursor-pointer\"\n (click)=\"onToggle()\"\n [disabled]=\"disabled\"\n [ngClass]=\"{'justify-end': true === value, 'justify-center': null === value, 'justify-start': false === value}\">\n <div class=\"shrink-0 h-[1.5rem] rounded-l-full rounded-r-full\"\n [ngClass]=\"{'bg-light-primary dark:bg-dark-primary disabled:bg-light-primary/60 dark:disabled:bg-dark-primary/60': true === value, 'bg-light-inactive dark:bg-dark-inactive disabled:bg-light-inactive/60 dark:disabled:bg-dark-inactive/60': false === value,\n 'w-[1.5rem]': true === value || false === value, 'bg-light-primary-light dark:bg-dark-primary-light disabled:bg-light-primary-light/60 dark:disabled:bg-dark-primary-light/60 w-[2.4rem]': null === value}\">\n </div>\n </button>\n</div>\n" }]
2729
+ }], propDecorators: { layout: [{
2730
+ type: Input
2731
+ }], value: [{
2732
+ type: Input
2733
+ }], disabled: [{
2734
+ type: Input
2735
+ }], nullable: [{
2736
+ type: Input
2737
+ }], toggled: [{
2738
+ type: Output
2739
+ }], label: [{
2740
+ type: ViewChild,
2741
+ args: ['label']
2742
+ }] } });
2743
+
2744
+ class BreadcrumbService {
2745
+ router;
2746
+ coreService = inject(CoreService);
2747
+ breadcrumbs = new BehaviorSubject([]);
2748
+ breadcrumbs$ = this.breadcrumbs.asObservable();
2749
+ titleService = inject(Title);
2750
+ constructor(router) {
2751
+ this.router = router;
2752
+ this.router.events
2753
+ .pipe(filter((event) => event instanceof NavigationEnd))
2754
+ .subscribe(() => {
2755
+ const root = this.router.routerState.snapshot.root;
2756
+ const breadcrumbs = this.createBreadcrumbs(root);
2757
+ this.breadcrumbs.next(breadcrumbs);
2758
+ setTimeout(() => {
2759
+ const titlePrefix = this.coreService.getStage() === 'prod'
2760
+ ? ''
2761
+ : `[${this.coreService.getStage()}] ` +
2762
+ this.coreService.getAppName() +
2763
+ '::';
2764
+ const title = titlePrefix +
2765
+ breadcrumbs.reduce((acc, breadcrumb) => {
2766
+ return acc ? `${acc}>${breadcrumb.label}` : breadcrumb.label;
2767
+ }, '');
2768
+ this.titleService.setTitle(title || 'Default Title');
2769
+ });
2770
+ });
2771
+ }
2772
+ createBreadcrumbs(route, url = '', breadcrumbs = []) {
2773
+ const children = route.children;
2774
+ if (children.length === 0) {
2775
+ return breadcrumbs;
2776
+ }
2777
+ for (const child of children) {
2778
+ const routeURL = child.url
2779
+ .map((segment) => segment.path)
2780
+ .join('/');
2781
+ if (routeURL !== '') {
2782
+ url += `/${routeURL}`;
2783
+ }
2784
+ if (child.data['breadcrumb']) {
2785
+ breadcrumbs.push({ label: child.data['breadcrumb'], url });
2786
+ }
2787
+ return this.createBreadcrumbs(child, url, breadcrumbs);
2788
+ }
2789
+ return breadcrumbs;
2790
+ }
2791
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbService, deps: [{ token: i1$5.Router }], target: i0.ɵɵFactoryTarget.Injectable });
2792
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbService, providedIn: 'root' });
2793
+ }
2794
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbService, decorators: [{
2795
+ type: Injectable,
2796
+ args: [{ providedIn: 'root' }]
2797
+ }], ctorParameters: () => [{ type: i1$5.Router }] });
2798
+
2799
+ class BreadcrumbComponent {
2800
+ breadcrumbService = inject(BreadcrumbService);
2801
+ listBreadcrumb = [];
2802
+ ngOnInit() {
2803
+ this.breadcrumbService.breadcrumbs$.subscribe((listBreadcrumb) => {
2804
+ this.listBreadcrumb = listBreadcrumb;
2805
+ });
2806
+ }
2807
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2808
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: BreadcrumbComponent, isStandalone: true, selector: "haloduck-breadcrumb", ngImport: i0, template: "<nav class=\"flex border-b border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\"\n aria-label=\"Breadcrumb\">\n <ol role=\"list\"\n class=\"flex w-full max-w-(--breakpoint-xl) space-x-4 px-4 sm:px-6 lg:px-8\">\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <a routerLink=\"/\"\n class=\"text-light-on-background dark:text-dark-on-background hover:scale-105\">\n <svg class=\"size-5 shrink-0\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M9.293 2.293a1 1 0 0 1 1.414 0l7 7A1 1 0 0 1 17 11h-1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-3a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6H3a1 1 0 0 1-.707-1.707l7-7Z\"\n clip-rule=\"evenodd\" />\n </svg>\n <span class=\"sr-only\">Home</span>\n </a>\n </div>\n </li>\n @for (breadcrumb of listBreadcrumb; track breadcrumb.label; let last = $last) {\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <svg class=\"h-full w-6 shrink-0 text-light-inactive dark:text-dark-inactive\"\n viewBox=\"0 0 24 44\"\n preserveAspectRatio=\"none\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path d=\"M.293 0l22 22-22 22h1.414l22-22-22-22H.293z\" />\n </svg>\n @if (!last) {\n <a href=\"javascript:void(0)\"\n [routerLink]=\"breadcrumb.url\"\n class=\"ml-4 text-sm font-medium hover:font-bold text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</a>\n } @else {\n <span class=\"ml-4 text-sm font-medium text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</span>\n }\n </div>\n </li>\n }\n </ol>\n</nav>\n", styles: [""], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
2809
+ }
2810
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbComponent, decorators: [{
2811
+ type: Component,
2812
+ args: [{ selector: 'haloduck-breadcrumb', imports: [RouterLink, TranslocoModule], template: "<nav class=\"flex border-b border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\"\n aria-label=\"Breadcrumb\">\n <ol role=\"list\"\n class=\"flex w-full max-w-(--breakpoint-xl) space-x-4 px-4 sm:px-6 lg:px-8\">\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <a routerLink=\"/\"\n class=\"text-light-on-background dark:text-dark-on-background hover:scale-105\">\n <svg class=\"size-5 shrink-0\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M9.293 2.293a1 1 0 0 1 1.414 0l7 7A1 1 0 0 1 17 11h-1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-3a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6H3a1 1 0 0 1-.707-1.707l7-7Z\"\n clip-rule=\"evenodd\" />\n </svg>\n <span class=\"sr-only\">Home</span>\n </a>\n </div>\n </li>\n @for (breadcrumb of listBreadcrumb; track breadcrumb.label; let last = $last) {\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <svg class=\"h-full w-6 shrink-0 text-light-inactive dark:text-dark-inactive\"\n viewBox=\"0 0 24 44\"\n preserveAspectRatio=\"none\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path d=\"M.293 0l22 22-22 22h1.414l22-22-22-22H.293z\" />\n </svg>\n @if (!last) {\n <a href=\"javascript:void(0)\"\n [routerLink]=\"breadcrumb.url\"\n class=\"ml-4 text-sm font-medium hover:font-bold text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</a>\n } @else {\n <span class=\"ml-4 text-sm font-medium text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</span>\n }\n </div>\n </li>\n }\n </ol>\n</nav>\n" }]
2813
+ }] });
2814
+
2815
+ class NotificationComponent {
2816
+ notificationService = inject(NotificationService);
2817
+ listNotification$ = this.notificationService.getListNotification();
2818
+ removeNotification(id) {
2819
+ this.notificationService.removeNotificationById(id);
2820
+ }
2821
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: NotificationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2822
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: NotificationComponent, isStandalone: true, selector: "haloduck-notification", ngImport: i0, template: "@for (notification of listNotification$ |async; track notification.id) {\n<div class=\"relative rounded-lg m-4 px-8 py-4 bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary animate-fadeIn\"\n [ngClass]=\"{ 'bg-light-primary-dark dark:bg-dark-primary-dark text-light-on-primary-dark dark:text-dark-on-primary-dark': notification.type === 'info',\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary': notification.type === 'success',\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger': notification.type === 'error',\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary': notification.type === 'warning' }\">\n {{notification.message}}\n <div class=\"absolute top-0 right-0\">\n <button type=\"button\"\n (click)=\"removeNotification(notification.id)\">\n <span class=\"sr-only\">Close</span>\n <svg class=\"size-6 text-white\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n</div>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }] });
2823
+ }
2824
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: NotificationComponent, decorators: [{
2825
+ type: Component,
2826
+ args: [{ selector: 'haloduck-notification', standalone: true, imports: [CommonModule], template: "@for (notification of listNotification$ |async; track notification.id) {\n<div class=\"relative rounded-lg m-4 px-8 py-4 bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary animate-fadeIn\"\n [ngClass]=\"{ 'bg-light-primary-dark dark:bg-dark-primary-dark text-light-on-primary-dark dark:text-dark-on-primary-dark': notification.type === 'info',\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary': notification.type === 'success',\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger': notification.type === 'error',\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary': notification.type === 'warning' }\">\n {{notification.message}}\n <div class=\"absolute top-0 right-0\">\n <button type=\"button\"\n (click)=\"removeNotification(notification.id)\">\n <span class=\"sr-only\">Close</span>\n <svg class=\"size-6 text-white\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke-width=\"1.5\"\n stroke=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n</div>\n}\n" }]
2827
+ }] });
2828
+
2829
+ class PictureNameComponent {
2830
+ name = '';
2831
+ pictureKey;
2832
+ cdnUrl = '';
2833
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PictureNameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2834
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: PictureNameComponent, isStandalone: true, selector: "haloduck-picture-name", inputs: { name: "name", pictureKey: "pictureKey", cdnUrl: "cdnUrl" }, ngImport: i0, template: "<div class=\"flex items-center justify-start gap-2\">\n @if(pictureKey) {\n <img src=\"{{cdnUrl}}/{{pictureKey}}\"\n alt=\"\uC0AC\uC9C4\"\n class=\"w-8 h-8 rounded-full\" />\n } @else {\n <div class=\"w-8 h-8 bg-light-inactive dark:bg-dark-inactive rounded-full\"></div>\n }\n {{ name }}\n @if (name) {\n &nbsp;&nbsp;&nbsp;&nbsp;\n <!-- TODO -->\n }\n</div>\n", styles: [""] });
2835
+ }
2836
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PictureNameComponent, decorators: [{
2837
+ type: Component,
2838
+ args: [{ selector: 'haloduck-picture-name', imports: [], template: "<div class=\"flex items-center justify-start gap-2\">\n @if(pictureKey) {\n <img src=\"{{cdnUrl}}/{{pictureKey}}\"\n alt=\"\uC0AC\uC9C4\"\n class=\"w-8 h-8 rounded-full\" />\n } @else {\n <div class=\"w-8 h-8 bg-light-inactive dark:bg-dark-inactive rounded-full\"></div>\n }\n {{ name }}\n @if (name) {\n &nbsp;&nbsp;&nbsp;&nbsp;\n <!-- TODO -->\n }\n</div>\n" }]
2839
+ }], propDecorators: { name: [{
2840
+ type: Input
2841
+ }], pictureKey: [{
2842
+ type: Input
2843
+ }], cdnUrl: [{
2844
+ type: Input
2845
+ }] } });
2846
+
2847
+ const provideHaloduckTransloco = () => provideTranslocoScope({
2848
+ scope: 'haloduck',
2849
+ alias: 'haloduck',
2850
+ });
2851
+
2852
+ /**
2853
+ * Generated bundle index. Do not edit.
2854
+ */
2855
+
2856
+ export { AuthenticateComponent, BreadcrumbComponent, ButtonComponent, CalendarComponent, ConfirmDialogService, CopyButtonComponent, DatePickerComponent, DateRangeComponent, DialogService, DrawCanvasComponent, ERROR_NOT_ACCEPTABLE_FILE_TYPE, ERROR_OVER_COUNT, ERROR_OVER_SIZE, ERROR_UPLOAD, FileUploaderComponent, FlipComponent, ImageUploaderComponent, ImageViewerComponent, InputComponent, LanguageSelectorComponent, MapToAddressComponent, NotificationComponent, NotificationService, PictureNameComponent, SelectComponent, SelectDropdownComponent, SideMenuComponent, SideMenuItemComponent, StlViewerComponent, TableComponent, ToggleComponent, dateToString, provideHaloduckTransloco };
2857
+ //# sourceMappingURL=haloduck-ui.mjs.map