@ziadshalaby/ngx-zs-component 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2793 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, signal, Injectable, inject, input, computed, effect, output, viewChild, model, untracked, HostListener, ChangeDetectionStrategy } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i2 from '@angular/router';
6
+ import { RouterModule } from '@angular/router';
7
+ import { FormsModule } from '@angular/forms';
8
+
9
+ class NgxZsComponent {
10
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgxZsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: NgxZsComponent, isStandalone: true, selector: "lib-ngx-zs-component", ngImport: i0, template: `
12
+ <p>
13
+ ngx-zs-component works!
14
+ </p>
15
+ `, isInline: true, styles: [""] });
16
+ }
17
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgxZsComponent, decorators: [{
18
+ type: Component,
19
+ args: [{ selector: 'lib-ngx-zs-component', imports: [], template: `
20
+ <p>
21
+ ngx-zs-component works!
22
+ </p>
23
+ ` }]
24
+ }] });
25
+
26
+ // ==============================================
27
+ // Types
28
+ // ==============================================
29
+ // ==============================================
30
+ // Service
31
+ // ==============================================
32
+ class AlertService {
33
+ // ==============================================
34
+ // State
35
+ // ==============================================
36
+ alerts = signal([], ...(ngDevMode ? [{ debugName: "alerts" }] : []));
37
+ // ==============================================
38
+ // Public Methods
39
+ // ==============================================
40
+ addAlert(newAlert) {
41
+ const newAlertToAdd = {
42
+ ...newAlert,
43
+ id: crypto.randomUUID(),
44
+ };
45
+ this.alerts.update((alerts) => [...alerts, newAlertToAdd]);
46
+ }
47
+ bulkAlert(newAlerts, options) {
48
+ const alertsToAdd = newAlerts.map((message) => ({
49
+ ...options,
50
+ message,
51
+ id: crypto.randomUUID(),
52
+ }));
53
+ this.alerts.update((alerts) => [...alerts, ...alertsToAdd]);
54
+ }
55
+ onAlertClosed(id) {
56
+ this.alerts.update((alerts) => {
57
+ return alerts.filter(a => a.id !== id);
58
+ });
59
+ }
60
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AlertService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
61
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AlertService, providedIn: 'root' });
62
+ }
63
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: AlertService, decorators: [{
64
+ type: Injectable,
65
+ args: [{
66
+ providedIn: 'root'
67
+ }]
68
+ }] });
69
+
70
+ const zIndices = {
71
+ alert: 'zs:z-1600',
72
+ spinner: 'zs:z-1400',
73
+ modal: 'zs:z-1200',
74
+ themeToggle: 'zs:z-1000',
75
+ navbar: 'zs:z-800',
76
+ scrollToTop: 'zs:z-600',
77
+ navItemDropdown: 'zs:z-200',
78
+ selectDropdown: 'zs:z-200'
79
+ };
80
+
81
+ // ==============================================
82
+ // Types
83
+ // ==============================================
84
+ const ALERT_CONFIG = {
85
+ success: {
86
+ icon: 'fas fa-check-circle',
87
+ bgColor: 'zs:bg-green-100 zs:dark:bg-green-800',
88
+ textColor: 'zs:text-green-800 zs:dark:text-green-100',
89
+ borderColor: 'zs:border-green-500 zs:dark:border-green-300'
90
+ },
91
+ danger: {
92
+ icon: 'fas fa-exclamation-circle',
93
+ bgColor: 'zs:bg-red-100 zs:dark:bg-red-800',
94
+ textColor: 'zs:text-red-800 zs:dark:text-red-100',
95
+ borderColor: 'zs:border-red-500 zs:dark:border-red-300'
96
+ },
97
+ warning: {
98
+ icon: 'fas fa-exclamation-triangle',
99
+ bgColor: 'zs:bg-yellow-100 zs:dark:bg-yellow-800',
100
+ textColor: 'zs:text-yellow-800 zs:dark:text-yellow-100',
101
+ borderColor: 'zs:border-yellow-500 zs:dark:border-yellow-300'
102
+ },
103
+ info: {
104
+ icon: 'fas fa-info-circle',
105
+ bgColor: 'zs:bg-blue-100 zs:dark:bg-blue-800',
106
+ textColor: 'zs:text-blue-800 zs:dark:text-blue-100',
107
+ borderColor: 'zs:border-blue-500 zs:dark:border-blue-300'
108
+ }
109
+ };
110
+ const PositionClasses = {
111
+ 'top-left': 'zs:top-4 zs:left-4',
112
+ 'top-right': 'zs:top-4 zs:right-4',
113
+ 'bottom-left': 'zs:bottom-4 zs:left-4',
114
+ 'bottom-right': 'zs:bottom-4 zs:right-4',
115
+ };
116
+ // ==============================================
117
+ // Component Decorator
118
+ // ==============================================
119
+ class Alert {
120
+ // ==============================================
121
+ // Dependencies
122
+ // ==============================================
123
+ zIndices = zIndices;
124
+ alertService = inject(AlertService);
125
+ // ==============================================
126
+ // Inputs
127
+ // ==============================================
128
+ positionClass = input('top-right', ...(ngDevMode ? [{ debugName: "positionClass" }] : []));
129
+ defaultShowCloseButton = input(true, ...(ngDevMode ? [{ debugName: "defaultShowCloseButton" }] : []));
130
+ defaultAutoClose = input(true, ...(ngDevMode ? [{ debugName: "defaultAutoClose" }] : []));
131
+ defaultDuration = input(5000, ...(ngDevMode ? [{ debugName: "defaultDuration" }] : []));
132
+ // ==============================================
133
+ // Signals & Computed Properties
134
+ // ==============================================
135
+ oldAlerts = signal(new Set(), ...(ngDevMode ? [{ debugName: "oldAlerts" }] : []));
136
+ direction = computed(() => this.positionClass().startsWith('top') ? 'top' : 'bottom', ...(ngDevMode ? [{ debugName: "direction" }] : []));
137
+ positionClasses = computed(() => PositionClasses[this.positionClass()], ...(ngDevMode ? [{ debugName: "positionClasses" }] : []));
138
+ alerts = computed(() => {
139
+ const list = this.alertService.alerts();
140
+ return this.direction() === 'bottom' ? [...list].reverse() : list;
141
+ }, ...(ngDevMode ? [{ debugName: "alerts" }] : []));
142
+ alertConfig = computed(() => {
143
+ return this.alerts().map((alert) => {
144
+ const config = ALERT_CONFIG[alert.type] ?? ALERT_CONFIG.info;
145
+ return { ...alert, ...config };
146
+ });
147
+ }, ...(ngDevMode ? [{ debugName: "alertConfig" }] : []));
148
+ // ==============================================
149
+ // Private Properties
150
+ // ==============================================
151
+ activeIntervals = new Map();
152
+ // ==============================================
153
+ // Lifecycle & Effects
154
+ // ==============================================
155
+ constructor() {
156
+ effect(() => {
157
+ const alerts = this.alerts();
158
+ const oldIds = this.oldAlerts();
159
+ // استخرج التنبيهات الجديدة فقط
160
+ const newOnes = alerts.filter(a => !oldIds.has(a.id));
161
+ // سجّل كل تنبيه جديد
162
+ for (const alert of newOnes) {
163
+ this.registerAlert(alert);
164
+ }
165
+ });
166
+ }
167
+ ngOnDestroy() {
168
+ this.activeIntervals.forEach(clearInterval);
169
+ this.activeIntervals.clear();
170
+ }
171
+ // ==============================================
172
+ // Private Methods
173
+ // ==============================================
174
+ registerAlert(alert) {
175
+ // Mark alert as processed
176
+ const updatedSet = new Set(this.oldAlerts());
177
+ updatedSet.add(alert.id);
178
+ this.oldAlerts.set(updatedSet);
179
+ const autoClose = alert.autoClose ?? this.defaultAutoClose();
180
+ const duration = alert.duration ?? this.defaultDuration();
181
+ if (autoClose) {
182
+ let progress = 100;
183
+ const step = 100 / (duration / 100);
184
+ const interval = window.setInterval(() => {
185
+ progress = Math.max(0, progress - step);
186
+ this.alertService.alerts.update((all) => all.map((a) => a.id === alert.id ? { ...a, progress } : a));
187
+ if (progress <= 0) {
188
+ this.closeAlert(alert.id);
189
+ }
190
+ }, 100);
191
+ this.activeIntervals.set(alert.id, interval);
192
+ }
193
+ }
194
+ // ==============================================
195
+ // Public Methods
196
+ // ==============================================
197
+ closeAlert(id) {
198
+ // Clear active interval if exists
199
+ const interval = this.activeIntervals.get(id);
200
+ if (interval) {
201
+ clearInterval(interval);
202
+ this.activeIntervals.delete(id);
203
+ }
204
+ // Remove from processed alerts
205
+ const updatedSet = new Set(this.oldAlerts());
206
+ updatedSet.delete(id);
207
+ this.oldAlerts.set(updatedSet);
208
+ // Remove from service
209
+ this.alertService.onAlertClosed(id);
210
+ }
211
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Alert, deps: [], target: i0.ɵɵFactoryTarget.Component });
212
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Alert, isStandalone: true, selector: "ZS-alert", inputs: { positionClass: { classPropertyName: "positionClass", publicName: "positionClass", isSignal: true, isRequired: false, transformFunction: null }, defaultShowCloseButton: { classPropertyName: "defaultShowCloseButton", publicName: "defaultShowCloseButton", isSignal: true, isRequired: false, transformFunction: null }, defaultAutoClose: { classPropertyName: "defaultAutoClose", publicName: "defaultAutoClose", isSignal: true, isRequired: false, transformFunction: null }, defaultDuration: { classPropertyName: "defaultDuration", publicName: "defaultDuration", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!-- ========================= Alert Container ========================= -->\n<div\n class=\"zs:fixed zs:ml-4 zs:break-all {{ zIndices.alert }} zs:flex zs:flex-col \n zs:overflow-y-auto custom-scrollbar zs:p-2 zs:gap-3 zs:max-h-[calc(100vh-1rem)]\"\n [ngClass]=\"positionClasses()\"\n role=\"region\"\n aria-label=\"Notification area\"\n aria-live=\"polite\"\n>\n\n @for (alert of alertConfig(); track alert.id) {\n\n <!-- ========================= Alert Card ========================= -->\n <div\n class=\"zs:pointer-events-auto zs:max-w-sm zs:min-h-14 zs:rounded-lg zs:border zs:p-4 zs:shadow-md zs:dark:shadow-gray-400/30\n zs:transition-all zs:duration-300 zs:ease-in-out animate-fade-in zs:relative zs:overflow-hidden\"\n [class]=\"alert.bgColor + ' ' + alert.textColor + ' ' + alert.borderColor\"\n role=\"alert\"\n >\n\n <!-- ========================= Alert Content ========================= -->\n <div class=\"zs:flex zs:items-center\">\n\n <!-- Icon -->\n <i\n [class]=\"alert.icon + ' zs:mt-0.5 zs:me-3'\"\n aria-hidden=\"true\"\n ></i>\n\n <!-- Message -->\n <div class=\"zs:flex-1 zs:text-sm zs:font-medium\">\n {{ alert.message }}\n </div>\n\n <!-- Close Button -->\n @if (alert.showCloseButton ?? defaultShowCloseButton()) {\n <button\n type=\"button\"\n class=\"zs:ms-2 zs:inline-flex zs:h-6 zs:w-6 zs:items-center zs:justify-center zs:rounded-full\n zs:text-gray-500 zs:dark:text-gray-300\n zs:hover:bg-gray-200 zs:dark:hover:bg-gray-700\n zs:hover:text-gray-700 zs:dark:hover:text-gray-100\n zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-within:ring-indigo-500\"\n (click)=\"closeAlert(alert.id!)\"\n aria-label=\"Close alert\"\n >\n <i class=\"fas fa-times zs:text-xs\" aria-hidden=\"true\"></i>\n </button>\n }\n\n </div>\n\n <!-- ========================= Auto-Close Progress Bar ========================= -->\n @if (alert.autoClose ?? defaultAutoClose()) {\n <div\n class=\"zs:absolute zs:bottom-0 zs:left-0 zs:h-1 zs:bg-gray-300 zs:dark:bg-gray-700 zs:w-full\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"alert.progress ?? 100\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div\n class=\"zs:h-1 zs:bg-current zs:transition-[width] zs:duration-100\"\n [style.width.%]=\"alert.progress ?? 100\"\n ></div>\n </div>\n }\n\n </div>\n\n }\n\n</div>", styles: ["@keyframes fade-in{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.animate-fade-in{animation:fade-in .3s ease-out forwards}.custom-scrollbar::-webkit-scrollbar{width:8px}.custom-scrollbar::-webkit-scrollbar-track{background:#0000000d;border-radius:4px}.custom-scrollbar::-webkit-scrollbar-thumb{background-color:#64646480;border-radius:4px;border:2px solid transparent;background-clip:content-box}.custom-scrollbar:hover::-webkit-scrollbar-thumb{background-color:#646464cc}.custom-scrollbar{scrollbar-width:thin;scrollbar-color:rgba(100,100,100,.5) rgba(0,0,0,.05)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
213
+ }
214
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Alert, decorators: [{
215
+ type: Component,
216
+ args: [{ selector: 'ZS-alert', imports: [CommonModule], template: "<!-- ========================= Alert Container ========================= -->\n<div\n class=\"zs:fixed zs:ml-4 zs:break-all {{ zIndices.alert }} zs:flex zs:flex-col \n zs:overflow-y-auto custom-scrollbar zs:p-2 zs:gap-3 zs:max-h-[calc(100vh-1rem)]\"\n [ngClass]=\"positionClasses()\"\n role=\"region\"\n aria-label=\"Notification area\"\n aria-live=\"polite\"\n>\n\n @for (alert of alertConfig(); track alert.id) {\n\n <!-- ========================= Alert Card ========================= -->\n <div\n class=\"zs:pointer-events-auto zs:max-w-sm zs:min-h-14 zs:rounded-lg zs:border zs:p-4 zs:shadow-md zs:dark:shadow-gray-400/30\n zs:transition-all zs:duration-300 zs:ease-in-out animate-fade-in zs:relative zs:overflow-hidden\"\n [class]=\"alert.bgColor + ' ' + alert.textColor + ' ' + alert.borderColor\"\n role=\"alert\"\n >\n\n <!-- ========================= Alert Content ========================= -->\n <div class=\"zs:flex zs:items-center\">\n\n <!-- Icon -->\n <i\n [class]=\"alert.icon + ' zs:mt-0.5 zs:me-3'\"\n aria-hidden=\"true\"\n ></i>\n\n <!-- Message -->\n <div class=\"zs:flex-1 zs:text-sm zs:font-medium\">\n {{ alert.message }}\n </div>\n\n <!-- Close Button -->\n @if (alert.showCloseButton ?? defaultShowCloseButton()) {\n <button\n type=\"button\"\n class=\"zs:ms-2 zs:inline-flex zs:h-6 zs:w-6 zs:items-center zs:justify-center zs:rounded-full\n zs:text-gray-500 zs:dark:text-gray-300\n zs:hover:bg-gray-200 zs:dark:hover:bg-gray-700\n zs:hover:text-gray-700 zs:dark:hover:text-gray-100\n zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-within:ring-indigo-500\"\n (click)=\"closeAlert(alert.id!)\"\n aria-label=\"Close alert\"\n >\n <i class=\"fas fa-times zs:text-xs\" aria-hidden=\"true\"></i>\n </button>\n }\n\n </div>\n\n <!-- ========================= Auto-Close Progress Bar ========================= -->\n @if (alert.autoClose ?? defaultAutoClose()) {\n <div\n class=\"zs:absolute zs:bottom-0 zs:left-0 zs:h-1 zs:bg-gray-300 zs:dark:bg-gray-700 zs:w-full\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"alert.progress ?? 100\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div\n class=\"zs:h-1 zs:bg-current zs:transition-[width] zs:duration-100\"\n [style.width.%]=\"alert.progress ?? 100\"\n ></div>\n </div>\n }\n\n </div>\n\n }\n\n</div>", styles: ["@keyframes fade-in{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.animate-fade-in{animation:fade-in .3s ease-out forwards}.custom-scrollbar::-webkit-scrollbar{width:8px}.custom-scrollbar::-webkit-scrollbar-track{background:#0000000d;border-radius:4px}.custom-scrollbar::-webkit-scrollbar-thumb{background-color:#64646480;border-radius:4px;border:2px solid transparent;background-clip:content-box}.custom-scrollbar:hover::-webkit-scrollbar-thumb{background-color:#646464cc}.custom-scrollbar{scrollbar-width:thin;scrollbar-color:rgba(100,100,100,.5) rgba(0,0,0,.05)}\n"] }]
217
+ }], ctorParameters: () => [], propDecorators: { positionClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "positionClass", required: false }] }], defaultShowCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultShowCloseButton", required: false }] }], defaultAutoClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultAutoClose", required: false }] }], defaultDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultDuration", required: false }] }] } });
218
+
219
+ // ==============================================
220
+ // Imports
221
+ // ==============================================
222
+ // ==============================================
223
+ // Injectable Metadata
224
+ // ==============================================
225
+ class NavItemService {
226
+ // ==============================================
227
+ // State
228
+ // ==============================================
229
+ /**
230
+ * Map of collections, where each collection tracks:
231
+ * - openIndex: the currently open item's unique index
232
+ * - indexes: list of all registered item indexes in this collection
233
+ */
234
+ collections = signal(new Map(), ...(ngDevMode ? [{ debugName: "collections" }] : []));
235
+ // ==============================================
236
+ // Public Read API
237
+ // ==============================================
238
+ openIndex(collectionName) {
239
+ const entry = this.collections().get(collectionName);
240
+ return entry ? entry.openIndex : '';
241
+ }
242
+ // ==============================================
243
+ // Public Write API
244
+ // ==============================================
245
+ addItemInCollection(collectionName, index) {
246
+ this.collections.update((prev) => {
247
+ const updated = new Map(prev);
248
+ const entry = updated.get(collectionName);
249
+ if (entry) {
250
+ updated.set(collectionName, {
251
+ ...entry,
252
+ indexes: [...entry.indexes, index]
253
+ });
254
+ }
255
+ else {
256
+ updated.set(collectionName, {
257
+ openIndex: '',
258
+ indexes: [index]
259
+ });
260
+ }
261
+ return updated;
262
+ });
263
+ }
264
+ onOpenIndexChange(collectionName, index) {
265
+ this.collections.update((prev) => {
266
+ const updated = new Map(prev);
267
+ const entry = updated.get(collectionName);
268
+ if (entry) {
269
+ updated.set(collectionName, {
270
+ ...entry,
271
+ openIndex: index
272
+ });
273
+ }
274
+ else {
275
+ updated.set(collectionName, {
276
+ openIndex: index,
277
+ indexes: []
278
+ });
279
+ }
280
+ return updated;
281
+ });
282
+ }
283
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NavItemService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
284
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NavItemService, providedIn: 'root' });
285
+ }
286
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NavItemService, decorators: [{
287
+ type: Injectable,
288
+ args: [{
289
+ providedIn: 'root'
290
+ }]
291
+ }] });
292
+
293
+ // ==============================================
294
+ // Imports
295
+ // ==============================================
296
+ // ==============================================
297
+ // Component Metadata
298
+ // ==============================================
299
+ class NavItem {
300
+ zIndices = zIndices;
301
+ // ==============================================
302
+ // Injection & Services
303
+ // ==============================================
304
+ znavItemService = inject(NavItemService);
305
+ // ==============================================
306
+ // Inputs & Outputs
307
+ // ==============================================
308
+ item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
309
+ collectionName = input.required(...(ngDevMode ? [{ debugName: "collectionName" }] : []));
310
+ anyItemClickedEv = output();
311
+ // ==============================================
312
+ // Signals & Computed Properties
313
+ // ==============================================
314
+ index = signal(crypto.randomUUID(), ...(ngDevMode ? [{ debugName: "index" }] : []));
315
+ isOpen = computed(() => this.znavItemService.openIndex(this.collectionName()) === this.index(), ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
316
+ // ==============================================
317
+ // Lifecycle & Effects
318
+ // ==============================================
319
+ constructor() {
320
+ effect(() => {
321
+ const collection = this.collectionName();
322
+ if (collection) {
323
+ this.znavItemService.addItemInCollection(collection, this.index());
324
+ }
325
+ });
326
+ }
327
+ // ==============================================
328
+ // Event Handlers
329
+ // ==============================================
330
+ toggle() {
331
+ const currentOpen = this.znavItemService.openIndex(this.collectionName());
332
+ const myIndex = this.index();
333
+ if (currentOpen === myIndex) {
334
+ this.znavItemService.onOpenIndexChange(this.collectionName(), '');
335
+ }
336
+ else {
337
+ this.znavItemService.onOpenIndexChange(this.collectionName(), myIndex);
338
+ }
339
+ }
340
+ onItemClick() {
341
+ const item = this.item();
342
+ item.action?.();
343
+ if (this.item().closeMenuAfterClick) {
344
+ this.toggle();
345
+ }
346
+ this.anyItemClickedEv.emit(this.item());
347
+ }
348
+ handleChildClick(child) {
349
+ // أعد إرسال الحدث لأعلى (لو فيه levels أكثر)
350
+ this.anyItemClickedEv.emit(child);
351
+ // إن كان الطفل يريد غلق القائمة، أغلق نفسي
352
+ if (child.closeMenuAfterClick) {
353
+ this.toggle();
354
+ }
355
+ }
356
+ // ==============================================
357
+ // Helper Methods
358
+ // ==============================================
359
+ getItemClasses = (item) => {
360
+ const defaultTextClass = 'zs:text-gray-600 zs:dark:text-gray-300 zs:hover:text-gray-900 zs:dark:hover:text-gray-100';
361
+ const defaultBgClass = 'zs:hover:bg-gray-100 zs:dark:hover:bg-gray-700';
362
+ if (item.colorClass) {
363
+ return item.colorClass;
364
+ }
365
+ return item.useDefaultColorClass === 'bg'
366
+ ? defaultBgClass
367
+ : defaultTextClass;
368
+ };
369
+ labelLineClass(item) {
370
+ return item.label.length > 60 ? 'zs:text-xs'
371
+ : item.label.length > 40 ? 'zs:text-sm'
372
+ : '';
373
+ }
374
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NavItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
375
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: NavItem, isStandalone: true, selector: "ZS-nav-item", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, collectionName: { classPropertyName: "collectionName", publicName: "collectionName", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { anyItemClickedEv: "anyItemClickedEv" }, ngImport: i0, template: "<!-- ========================= Conditional Rendering ========================= -->\n@if (item()) {\n @if (item().children?.length) {\n\n <!-- ========================= Parent Item with Dropdown ========================= -->\n <div class=\"zs:relative zs:w-full\">\n\n <!-- ========================= Main Toggle Button ========================= -->\n <button\n type=\"button\"\n (click)=\"toggle()\"\n [ngClass]=\"getItemClasses(item())\"\n class=\"zs:flex zs:w-full zs:items-center zs:gap-2 zs:rounded-md zs:px-3 zs:py-2 zs:text-left \n zs:text-sm zs:font-medium zs:md:text-base\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"'submenu-' + index()\"\n >\n <div class=\"zs:flex zs:items-center\">\n @if (item().icon) {\n <i [ngClass]=\"[\n item().icon, \n item().iconClass ?? '',\n (item().icon && item().label) ? 'zs:mr-2' : ''\n ]\" aria-hidden=\"true\"></i>\n }\n <span class=\"zs:wrap-break-word\" [ngClass]=\"labelLineClass(item())\">{{ item().label }}</span>\n </div>\n <i class=\"fas fa-chevron-down zs:ml-auto zs:p-0.5 zs:text-xs\" aria-hidden=\"true\"></i>\n </button>\n\n <!-- ========================= Dropdown Menu ========================= -->\n <div\n [id]=\"'submenu-' + index()\"\n [ngClass]=\"{\n 'zs:block': isOpen(),\n 'zs:hidden': !isOpen(),\n 'zs:absolute zs:left-6 zs:w-50 zs:mt-1 zs:py-1 zs:bg-white zs:dark:bg-gray-800 \n zs:rounded-md shadow-md-all shadow-md-all-night': item().childrenOpenWindow,\n 'zs:px-4 zs:mt-1': !item().childrenOpenWindow\n }\"\n class=\"{{ zIndices.navItemDropdown }}\"\n role=\"menu\"\n [attr.aria-label]=\"item().label + ' submenu'\"\n >\n @for (child of item().children; track $index) {\n <ZS-nav-item\n [item]=\"child\"\n [collectionName]=\"collectionName() + '-' + index()\"\n (anyItemClickedEv)=\"handleChildClick($event)\"\n role=\"menuitem\"\n ></ZS-nav-item>\n }\n </div>\n\n </div>\n\n } @else {\n\n <!-- ========================= Leaf Item (No Children) ========================= -->\n <a\n [routerLink]=\"item().routerLink\"\n [routerLinkActive]=\"item().routerLinkActive ?? ''\"\n #rla=\"routerLinkActive\"\n [ngClass]=\"rla.isActive ? '' : getItemClasses(item())\"\n class=\"zs:flex zs:items-center zs:rounded-md zs:px-3 zs:py-2 \n zs:text-sm zs:font-medium zs:md:text-base\"\n (click)=\"onItemClick()\"\n role=\"menuitem\"\n >\n @if (item().icon) {\n <i [ngClass]=\"[\n item().icon,\n rla.isActive ? '' : item().iconClass ?? '',\n (item().icon && item().label) ? 'zs:mr-2' : ''\n ]\"\n aria-hidden=\"true\"></i>\n }\n <span class=\"zs:wrap-break-word zs:line-clamp-3\" [ngClass]=\"labelLineClass(item())\">{{ item().label }}</span>\n </a>\n }\n}", styles: [""], dependencies: [{ kind: "component", type: NavItem, selector: "ZS-nav-item", inputs: ["item", "collectionName"], outputs: ["anyItemClickedEv"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i2.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
376
+ }
377
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NavItem, decorators: [{
378
+ type: Component,
379
+ args: [{ selector: 'ZS-nav-item', imports: [CommonModule, RouterModule], template: "<!-- ========================= Conditional Rendering ========================= -->\n@if (item()) {\n @if (item().children?.length) {\n\n <!-- ========================= Parent Item with Dropdown ========================= -->\n <div class=\"zs:relative zs:w-full\">\n\n <!-- ========================= Main Toggle Button ========================= -->\n <button\n type=\"button\"\n (click)=\"toggle()\"\n [ngClass]=\"getItemClasses(item())\"\n class=\"zs:flex zs:w-full zs:items-center zs:gap-2 zs:rounded-md zs:px-3 zs:py-2 zs:text-left \n zs:text-sm zs:font-medium zs:md:text-base\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"'submenu-' + index()\"\n >\n <div class=\"zs:flex zs:items-center\">\n @if (item().icon) {\n <i [ngClass]=\"[\n item().icon, \n item().iconClass ?? '',\n (item().icon && item().label) ? 'zs:mr-2' : ''\n ]\" aria-hidden=\"true\"></i>\n }\n <span class=\"zs:wrap-break-word\" [ngClass]=\"labelLineClass(item())\">{{ item().label }}</span>\n </div>\n <i class=\"fas fa-chevron-down zs:ml-auto zs:p-0.5 zs:text-xs\" aria-hidden=\"true\"></i>\n </button>\n\n <!-- ========================= Dropdown Menu ========================= -->\n <div\n [id]=\"'submenu-' + index()\"\n [ngClass]=\"{\n 'zs:block': isOpen(),\n 'zs:hidden': !isOpen(),\n 'zs:absolute zs:left-6 zs:w-50 zs:mt-1 zs:py-1 zs:bg-white zs:dark:bg-gray-800 \n zs:rounded-md shadow-md-all shadow-md-all-night': item().childrenOpenWindow,\n 'zs:px-4 zs:mt-1': !item().childrenOpenWindow\n }\"\n class=\"{{ zIndices.navItemDropdown }}\"\n role=\"menu\"\n [attr.aria-label]=\"item().label + ' submenu'\"\n >\n @for (child of item().children; track $index) {\n <ZS-nav-item\n [item]=\"child\"\n [collectionName]=\"collectionName() + '-' + index()\"\n (anyItemClickedEv)=\"handleChildClick($event)\"\n role=\"menuitem\"\n ></ZS-nav-item>\n }\n </div>\n\n </div>\n\n } @else {\n\n <!-- ========================= Leaf Item (No Children) ========================= -->\n <a\n [routerLink]=\"item().routerLink\"\n [routerLinkActive]=\"item().routerLinkActive ?? ''\"\n #rla=\"routerLinkActive\"\n [ngClass]=\"rla.isActive ? '' : getItemClasses(item())\"\n class=\"zs:flex zs:items-center zs:rounded-md zs:px-3 zs:py-2 \n zs:text-sm zs:font-medium zs:md:text-base\"\n (click)=\"onItemClick()\"\n role=\"menuitem\"\n >\n @if (item().icon) {\n <i [ngClass]=\"[\n item().icon,\n rla.isActive ? '' : item().iconClass ?? '',\n (item().icon && item().label) ? 'zs:mr-2' : ''\n ]\"\n aria-hidden=\"true\"></i>\n }\n <span class=\"zs:wrap-break-word zs:line-clamp-3\" [ngClass]=\"labelLineClass(item())\">{{ item().label }}</span>\n </a>\n }\n}" }]
380
+ }], ctorParameters: () => [], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], collectionName: [{ type: i0.Input, args: [{ isSignal: true, alias: "collectionName", required: true }] }], anyItemClickedEv: [{ type: i0.Output, args: ["anyItemClickedEv"] }] } });
381
+
382
+ const FormPaletteMap = new Map([
383
+ [
384
+ 'secondary',
385
+ {
386
+ border: 'zs:border-slate-300 zs:dark:border-slate-600',
387
+ borderHover: 'zs:hover:border-slate-500 zs:dark:hover:border-slate-400',
388
+ inputBg: 'zs:bg-slate-50 zs:dark:bg-slate-900',
389
+ ring: 'zs:focus-within:ring-slate-400 zs:dark:focus-within:ring-slate-600',
390
+ bgSelect: 'zs:bg-slate-200 zs:dark:bg-slate-800',
391
+ text: 'zs:text-slate-800 zs:dark:text-slate-300',
392
+ textHover: 'zs:hover:text-slate-700 zs:dark:hover:text-slate-400',
393
+ btnBG: 'zs:bg-slate-500 zs:dark:bg-slate-700',
394
+ btnBGHover: 'zs:hover:bg-slate-600',
395
+ checkBoxText: 'zs:text-slate-500 zs:dark:text-slate-700',
396
+ checkBoxTextHover: 'zs:hover:text-slate-600',
397
+ },
398
+ ],
399
+ [
400
+ 'primary',
401
+ {
402
+ border: 'zs:border-blue-200 zs:dark:border-blue-700',
403
+ borderHover: 'zs:hover:border-blue-400 zs:dark:hover:border-blue-500',
404
+ inputBg: 'zs:bg-white zs:dark:bg-slate-900',
405
+ ring: 'zs:focus-within:ring-blue-400 zs:dark:focus-within:ring-blue-500',
406
+ bgSelect: 'zs:bg-blue-200 zs:dark:bg-blue-800',
407
+ text: 'zs:text-blue-900 zs:dark:text-blue-100',
408
+ textHover: 'zs:hover:text-blue-700 zs:dark:hover:text-blue-300',
409
+ btnBG: 'zs:bg-blue-500 zs:dark:bg-blue-700',
410
+ btnBGHover: 'zs:hover:bg-blue-600',
411
+ checkBoxText: 'zs:text-blue-500 zs:dark:text-blue-700',
412
+ checkBoxTextHover: 'zs:hover:text-blue-600',
413
+ },
414
+ ],
415
+ [
416
+ 'success',
417
+ {
418
+ border: 'zs:border-green-300 zs:dark:border-green-600',
419
+ borderHover: 'zs:hover:border-green-500 zs:dark:hover:border-green-400',
420
+ inputBg: 'zs:bg-white zs:dark:bg-slate-900',
421
+ ring: 'zs:focus-within:ring-green-400 zs:dark:focus-within:ring-green-600',
422
+ bgSelect: 'zs:bg-green-200 zs:dark:bg-green-800',
423
+ text: 'zs:text-green-800 zs:dark:text-green-300',
424
+ textHover: 'zs:hover:text-green-700 zs:dark:hover:text-green-400',
425
+ btnBG: 'zs:bg-green-500 zs:dark:bg-green-700',
426
+ btnBGHover: 'zs:hover:bg-green-600',
427
+ checkBoxText: 'zs:text-green-500 zs:dark:text-green-700',
428
+ checkBoxTextHover: 'zs:hover:text-green-600',
429
+ },
430
+ ],
431
+ [
432
+ 'danger',
433
+ {
434
+ border: 'zs:border-red-300 zs:dark:border-red-600',
435
+ borderHover: 'zs:hover:border-red-500 zs:dark:hover:border-red-400',
436
+ inputBg: 'zs:bg-white zs:dark:bg-slate-900',
437
+ ring: 'zs:focus-within:ring-red-400 zs:dark:focus-within:ring-red-600',
438
+ bgSelect: 'zs:bg-red-200 zs:dark:bg-red-800',
439
+ text: 'zs:text-red-800 zs:dark:text-red-300',
440
+ textHover: 'zs:hover:text-red-700 zs:dark:hover:text-red-400',
441
+ btnBG: 'zs:bg-red-500 zs:dark:bg-red-700',
442
+ btnBGHover: 'zs:hover:bg-red-600',
443
+ checkBoxText: 'zs:text-red-500 zs:dark:text-red-700',
444
+ checkBoxTextHover: 'zs:hover:text-red-600',
445
+ },
446
+ ],
447
+ [
448
+ 'warning',
449
+ {
450
+ border: 'zs:border-yellow-300 zs:dark:border-yellow-600',
451
+ borderHover: 'zs:hover:border-yellow-500 zs:dark:hover:border-yellow-400',
452
+ inputBg: 'zs:bg-white zs:dark:bg-slate-900',
453
+ ring: 'zs:focus-within:ring-yellow-400 zs:dark:focus-within:ring-yellow-600',
454
+ bgSelect: 'zs:bg-amber-200 zs:dark:bg-amber-800',
455
+ text: 'zs:text-amber-800 zs:dark:text-amber-300',
456
+ textHover: 'zs:hover:text-amber-700 zs:dark:hover:text-amber-400',
457
+ btnBG: 'zs:bg-amber-500 zs:dark:bg-amber-700',
458
+ btnBGHover: 'zs:hover:bg-amber-600',
459
+ checkBoxText: 'zs:text-amber-500 zs:dark:text-amber-700',
460
+ checkBoxTextHover: 'zs:hover:text-amber-600',
461
+ },
462
+ ],
463
+ [
464
+ 'info',
465
+ {
466
+ border: 'zs:border-cyan-300 zs:dark:border-cyan-600',
467
+ borderHover: 'zs:hover:border-cyan-500 zs:dark:hover:border-cyan-400',
468
+ inputBg: 'zs:bg-white zs:dark:bg-slate-900',
469
+ ring: 'zs:focus-within:ring-cyan-400 zs:dark:focus-within:ring-cyan-600',
470
+ bgSelect: 'zs:bg-cyan-200 zs:dark:bg-cyan-800',
471
+ text: 'zs:text-cyan-800 zs:dark:text-cyan-300',
472
+ textHover: 'zs:hover:text-cyan-700 zs:dark:hover:text-cyan-400',
473
+ btnBG: 'zs:bg-cyan-500 zs:dark:bg-cyan-700',
474
+ btnBGHover: 'zs:hover:bg-cyan-600',
475
+ checkBoxText: 'zs:text-cyan-500 zs:dark:text-cyan-700',
476
+ checkBoxTextHover: 'zs:hover:text-cyan-600',
477
+ },
478
+ ],
479
+ [
480
+ 'dark',
481
+ {
482
+ border: 'zs:border-slate-900 zs:dark:border-slate-700',
483
+ borderHover: 'zs:hover:border-gray-500 zs:dark:hover:border-slate-500',
484
+ inputBg: 'zs:bg-slate-300 zs:dark:bg-slate-900',
485
+ ring: 'zs:focus-within:ring-slate-700 zs:dark:focus-within:ring-slate-600',
486
+ bgSelect: 'zs:bg-slate-400 zs:dark:bg-slate-800',
487
+ text: 'zs:text-slate-900 zs:dark:text-slate-300',
488
+ textHover: 'zs:hover:text-slate-700 zs:dark:hover:text-slate-400',
489
+ btnBG: 'zs:bg-slate-900 zs:dark:bg-slate-700',
490
+ btnBGHover: 'zs:hover:bg-slate-800',
491
+ checkBoxText: 'zs:text-slate-900 zs:dark:text-slate-700',
492
+ checkBoxTextHover: 'zs:hover:text-slate-800'
493
+ },
494
+ ],
495
+ [
496
+ 'violet',
497
+ {
498
+ border: 'zs:border-violet-300 zs:dark:border-violet-600',
499
+ borderHover: 'zs:hover:border-violet-500 zs:dark:hover:border-violet-400',
500
+ inputBg: 'zs:bg-white zs:dark:bg-slate-900',
501
+ ring: 'zs:focus-within:ring-violet-400 zs:dark:focus-within:ring-violet-600',
502
+ bgSelect: 'zs:bg-violet-200 zs:dark:bg-violet-800',
503
+ text: 'zs:text-violet-800 zs:dark:text-violet-300',
504
+ textHover: 'zs:hover:text-violet-700 zs:dark:hover:text-violet-400',
505
+ btnBG: 'zs:bg-violet-500 zs:dark:bg-violet-700',
506
+ btnBGHover: 'zs:hover:bg-violet-600',
507
+ checkBoxText: 'zs:text-violet-500 zs:dark:text-violet-700',
508
+ checkBoxTextHover: 'zs:hover:text-violet-600',
509
+ },
510
+ ],
511
+ [
512
+ 'teal',
513
+ {
514
+ border: 'zs:border-teal-300 zs:dark:border-teal-600',
515
+ borderHover: 'zs:hover:border-teal-500 zs:dark:hover:border-teal-400',
516
+ inputBg: 'zs:bg-white zs:dark:bg-slate-900',
517
+ ring: 'zs:focus-within:ring-teal-400 zs:dark:focus-within:ring-teal-600',
518
+ bgSelect: 'zs:bg-teal-200 zs:dark:bg-teal-800',
519
+ text: 'zs:text-teal-800 zs:dark:text-teal-300',
520
+ textHover: 'zs:hover:text-teal-700 zs:dark:hover:text-teal-400',
521
+ btnBG: 'zs:bg-teal-500 zs:dark:bg-teal-700',
522
+ btnBGHover: 'zs:hover:bg-teal-600',
523
+ checkBoxText: 'zs:text-teal-500 zs:dark:text-teal-700',
524
+ checkBoxTextHover: 'zs:hover:text-teal-600',
525
+ },
526
+ ],
527
+ ]);
528
+ const ColorMapping = new Map([
529
+ ['slate', { text: 'zs:text-slate-600 zs:dark:text-slate-400', bg: 'zs:bg-slate-600 zs:dark:bg-slate-400', border: 'zs:border-slate-600 zs:dark:border-slate-400' }],
530
+ ['gray', { text: 'zs:text-gray-600 zs:dark:text-gray-400', bg: 'zs:bg-gray-600 zs:dark:bg-gray-400', border: 'zs:border-gray-600 zs:dark:border-gray-400' }],
531
+ ['zinc', { text: 'zs:text-zinc-600 zs:dark:text-zinc-400', bg: 'zs:bg-zinc-600 zs:dark:bg-zinc-400', border: 'zs:border-zinc-600 zs:dark:border-zinc-400' }],
532
+ ['neutral', { text: 'zs:text-neutral-600 zs:dark:text-neutral-400', bg: 'zs:bg-neutral-600 zs:dark:bg-neutral-400', border: 'zs:border-neutral-600 zs:dark:border-neutral-400' }],
533
+ ['stone', { text: 'zs:text-stone-600 zs:dark:text-stone-400', bg: 'zs:bg-stone-600 zs:dark:bg-stone-400', border: 'zs:border-stone-600 zs:dark:border-stone-400' }],
534
+ ['red', { text: 'zs:text-red-600 zs:dark:text-red-400', bg: 'zs:bg-red-600 zs:dark:bg-red-400', border: 'zs:border-red-600 zs:dark:border-red-400' }],
535
+ ['orange', { text: 'zs:text-orange-600 zs:dark:text-orange-400', bg: 'zs:bg-orange-600 zs:dark:bg-orange-400', border: 'zs:border-orange-600 zs:dark:border-orange-400' }],
536
+ ['amber', { text: 'zs:text-amber-600 zs:dark:text-amber-400', bg: 'zs:bg-amber-600 zs:dark:bg-amber-400', border: 'zs:border-amber-600 zs:dark:border-amber-400' }],
537
+ ['yellow', { text: 'zs:text-yellow-600 zs:dark:text-yellow-400', bg: 'zs:bg-yellow-600 zs:dark:bg-yellow-400', border: 'zs:border-yellow-600 zs:dark:border-yellow-400' }],
538
+ ['lime', { text: 'zs:text-lime-600 zs:dark:text-lime-400', bg: 'zs:bg-lime-600 zs:dark:bg-lime-400', border: 'zs:border-lime-600 zs:dark:border-lime-400' }],
539
+ ['green', { text: 'zs:text-green-600 zs:dark:text-green-400', bg: 'zs:bg-green-600 zs:dark:bg-green-400', border: 'zs:border-green-600 zs:dark:border-green-400' }],
540
+ ['emerald', { text: 'zs:text-emerald-600 zs:dark:text-emerald-400', bg: 'zs:bg-emerald-600 zs:dark:bg-emerald-400', border: 'zs:border-emerald-600 zs:dark:border-emerald-400' }],
541
+ ['teal', { text: 'zs:text-teal-600 zs:dark:text-teal-400', bg: 'zs:bg-teal-600 zs:dark:bg-teal-400', border: 'zs:border-teal-600 zs:dark:border-teal-400' }],
542
+ ['cyan', { text: 'zs:text-cyan-600 zs:dark:text-cyan-400', bg: 'zs:bg-cyan-600 zs:dark:bg-cyan-400', border: 'zs:border-cyan-600 zs:dark:border-cyan-400' }],
543
+ ['sky', { text: 'zs:text-sky-600 zs:dark:text-sky-400', bg: 'zs:bg-sky-600 zs:dark:bg-sky-400', border: 'zs:border-sky-600 zs:dark:border-sky-400' }],
544
+ ['blue', { text: 'zs:text-blue-600 zs:dark:text-blue-400', bg: 'zs:bg-blue-600 zs:dark:bg-blue-400', border: 'zs:border-blue-600 zs:dark:border-blue-400' }],
545
+ ['indigo', { text: 'zs:text-indigo-600 zs:dark:text-indigo-400', bg: 'zs:bg-indigo-600 zs:dark:bg-indigo-400', border: 'zs:border-indigo-600 zs:dark:border-indigo-400' }],
546
+ ['violet', { text: 'zs:text-violet-600 zs:dark:text-violet-400', bg: 'zs:bg-violet-600 zs:dark:bg-violet-400', border: 'zs:border-violet-600 zs:dark:border-violet-400' }],
547
+ ['purple', { text: 'zs:text-purple-600 zs:dark:text-purple-400', bg: 'zs:bg-purple-600 zs:dark:bg-purple-400', border: 'zs:border-purple-600 zs:dark:border-purple-400' }],
548
+ ['fuchsia', { text: 'zs:text-fuchsia-600 zs:dark:text-fuchsia-400', bg: 'zs:bg-fuchsia-600 zs:dark:bg-fuchsia-400', border: 'zs:border-fuchsia-600 zs:dark:border-fuchsia-400' }],
549
+ ['pink', { text: 'zs:text-pink-600 zs:dark:text-pink-400', bg: 'zs:bg-pink-600 zs:dark:bg-pink-400', border: 'zs:border-pink-600 zs:dark:border-pink-400' }],
550
+ ['rose', { text: 'zs:text-rose-600 zs:dark:text-rose-400', bg: 'zs:bg-rose-600 zs:dark:bg-rose-400', border: 'zs:border-rose-600 zs:dark:border-rose-400' }],
551
+ ]);
552
+
553
+ // ==============================================================================
554
+ // Component
555
+ // ==============================================================================
556
+ class Card {
557
+ // ==========================================================================
558
+ // Inputs
559
+ // ==========================================================================
560
+ cardStyle = input('primary', ...(ngDevMode ? [{ debugName: "cardStyle" }] : []));
561
+ variant = input(...(ngDevMode ? [undefined, { debugName: "variant" }] : []));
562
+ clickable = input(false, ...(ngDevMode ? [{ debugName: "clickable" }] : []));
563
+ animation = input('none', ...(ngDevMode ? [{ debugName: "animation" }] : []));
564
+ bodyClass = input('zs:bg-gray-100 zs:dark:bg-gray-800', ...(ngDevMode ? [{ debugName: "bodyClass" }] : []));
565
+ // ==========================================================================
566
+ // Local State
567
+ // ==========================================================================
568
+ cardRef = viewChild('cardRef', ...(ngDevMode ? [{ debugName: "cardRef" }] : []));
569
+ isVisible = signal(false, ...(ngDevMode ? [{ debugName: "isVisible" }] : []));
570
+ constructor() {
571
+ const observer = new IntersectionObserver((entries) => {
572
+ for (const entry of entries) {
573
+ if (entry.isIntersecting) {
574
+ this.isVisible.set(true);
575
+ observer.unobserve(entry.target);
576
+ }
577
+ }
578
+ }, { threshold: 0.2 });
579
+ queueMicrotask(() => {
580
+ const el = this.cardRef()?.nativeElement;
581
+ if (el)
582
+ observer.observe(el);
583
+ });
584
+ }
585
+ // ==========================================================================
586
+ // Computed Classes
587
+ // ==========================================================================
588
+ classList = computed(() => {
589
+ const style = this.cardStyle();
590
+ const variant = this.variant();
591
+ const clickable = this.clickable();
592
+ const animation = this.animation();
593
+ const visible = this.isVisible();
594
+ const bodyClass = this.bodyClass();
595
+ const palette = FormPaletteMap.get(style) ?? {
596
+ border: '',
597
+ borderHover: '',
598
+ ring: '',
599
+ };
600
+ const base = 'zs:w-full zs:h-full zs:overflow-hidden zs:flex zs:flex-col zs:gap-4 zs:rounded-lg zs:transition-all zs:duration-300';
601
+ // ---------------------
602
+ // Border Handling
603
+ // ---------------------
604
+ const border = (variant?.border ?? true) ? `zs:border ${palette.border}` : 'zs:border-0';
605
+ // ---------------------
606
+ // Shadow Handling
607
+ // ---------------------
608
+ const shadowClasses = (variant?.shadow ?? true)
609
+ ? 'zs:shadow-md zs:dark:shadow-slate-700/50 zs:hover:shadow-lg'
610
+ : 'zs:shadow-none';
611
+ // ---------------------
612
+ // Hover Border Handling
613
+ // ---------------------
614
+ const hoverBorder = (variant?.border_hover ?? true) ? palette.borderHover : '';
615
+ // ---------------------
616
+ // Clickable / Focus Effects
617
+ // ---------------------
618
+ const clickEffects = clickable
619
+ ? `zs:cursor-pointer zs:hover:scale-[1.02] zs:active:scale-[0.97] zs:focus-visible:ring-2 ${palette.ring}`
620
+ : '';
621
+ // ---------------------
622
+ // Animation Handling
623
+ // ---------------------
624
+ const animationClass = animation !== 'none' ? `animate-from-${animation}` : '';
625
+ const visibleClass = visible ? 'animate-visible' : '';
626
+ // ---------------------
627
+ // Combine Classes
628
+ // ---------------------
629
+ return [
630
+ base,
631
+ border,
632
+ hoverBorder,
633
+ bodyClass,
634
+ shadowClasses,
635
+ clickEffects,
636
+ animationClass,
637
+ visibleClass
638
+ ]
639
+ .filter(Boolean)
640
+ .join(' ');
641
+ }, ...(ngDevMode ? [{ debugName: "classList" }] : []));
642
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Card, deps: [], target: i0.ɵɵFactoryTarget.Component });
643
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.9", type: Card, isStandalone: true, selector: "ZS-card", inputs: { cardStyle: { classPropertyName: "cardStyle", publicName: "cardStyle", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, clickable: { classPropertyName: "clickable", publicName: "clickable", isSignal: true, isRequired: false, transformFunction: null }, animation: { classPropertyName: "animation", publicName: "animation", isSignal: true, isRequired: false, transformFunction: null }, bodyClass: { classPropertyName: "bodyClass", publicName: "bodyClass", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "cardRef", first: true, predicate: ["cardRef"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #cardRef [class]=\"classList()\"\nrole=\"group\"\ntabindex=\"0\"\n[attr.aria-label]=\"clickable() ? 'Interactive card' : 'Information card'\"\n[attr.aria-pressed]=\"clickable() ? 'false' : null\"\n>\n <ng-content></ng-content>\n</div>", styles: [":host{display:block}[class*=animate-from-]{opacity:0;transform:translate(0)}.animate-visible[class*=animate-from-]{animation:fadeInMove .5s ease-out forwards}.animate-from-top{transform:translateY(-30px)}.animate-from-bottom{transform:translateY(30px)}.animate-from-left{transform:translate(-30px)}.animate-from-right{transform:translate(30px)}.animate-from-top-left{transform:translate(-30px,-30px)}.animate-from-top-right{transform:translate(30px,-30px)}.animate-from-bottom-left{transform:translate(-30px,30px)}.animate-from-bottom-right{transform:translate(30px,30px)}@keyframes fadeInMove{to{opacity:1;transform:translate(0)}}\n"] });
644
+ }
645
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Card, decorators: [{
646
+ type: Component,
647
+ args: [{ selector: 'ZS-card', standalone: true, template: "<div #cardRef [class]=\"classList()\"\nrole=\"group\"\ntabindex=\"0\"\n[attr.aria-label]=\"clickable() ? 'Interactive card' : 'Information card'\"\n[attr.aria-pressed]=\"clickable() ? 'false' : null\"\n>\n <ng-content></ng-content>\n</div>", styles: [":host{display:block}[class*=animate-from-]{opacity:0;transform:translate(0)}.animate-visible[class*=animate-from-]{animation:fadeInMove .5s ease-out forwards}.animate-from-top{transform:translateY(-30px)}.animate-from-bottom{transform:translateY(30px)}.animate-from-left{transform:translate(-30px)}.animate-from-right{transform:translate(30px)}.animate-from-top-left{transform:translate(-30px,-30px)}.animate-from-top-right{transform:translate(30px,-30px)}.animate-from-bottom-left{transform:translate(-30px,30px)}.animate-from-bottom-right{transform:translate(30px,30px)}@keyframes fadeInMove{to{opacity:1;transform:translate(0)}}\n"] }]
648
+ }], ctorParameters: () => [], propDecorators: { cardStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardStyle", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], clickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickable", required: false }] }], animation: [{ type: i0.Input, args: [{ isSignal: true, alias: "animation", required: false }] }], bodyClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "bodyClass", required: false }] }], cardRef: [{ type: i0.ViewChild, args: ['cardRef', { isSignal: true }] }] } });
649
+
650
+ // ==============================================
651
+ // Component
652
+ // ==============================================
653
+ class Carousel {
654
+ // ==============================================
655
+ // Inputs
656
+ // ==============================================
657
+ itemsNumber = input.required(...(ngDevMode ? [{ debugName: "itemsNumber" }] : []));
658
+ arrows = input(true, ...(ngDevMode ? [{ debugName: "arrows" }] : [])); // Show/hide navigation arrows
659
+ arrowColor = input('gray', ...(ngDevMode ? [{ debugName: "arrowColor" }] : [])); // Arrow color CSS class
660
+ showIndicators = input(true, ...(ngDevMode ? [{ debugName: "showIndicators" }] : []));
661
+ autoPlay = input(true, ...(ngDevMode ? [{ debugName: "autoPlay" }] : []));
662
+ duration = input(3000, ...(ngDevMode ? [{ debugName: "duration" }] : []));
663
+ maxItemsPerBox = input(4, ...(ngDevMode ? [{ debugName: "maxItemsPerBox" }] : [])); // Maximum number of items visible in one box
664
+ itemMinWidth = input(200, ...(ngDevMode ? [{ debugName: "itemMinWidth" }] : []));
665
+ // ==============================================
666
+ // Outputs
667
+ // ==============================================
668
+ indexChangeEv = output();
669
+ // ==============================================
670
+ // Model
671
+ // ==============================================
672
+ currentIndex = model(0, ...(ngDevMode ? [{ debugName: "currentIndex" }] : []));
673
+ // ==============================================
674
+ // View Children
675
+ // ==============================================
676
+ carouselContainer = viewChild('carouselContainer', ...(ngDevMode ? [{ debugName: "carouselContainer" }] : []));
677
+ carouselTrack = viewChild('carouselTrack', ...(ngDevMode ? [{ debugName: "carouselTrack" }] : []));
678
+ // ==============================================
679
+ // Signals
680
+ // ==============================================
681
+ itemsPerBox = signal(1, ...(ngDevMode ? [{ debugName: "itemsPerBox" }] : []));
682
+ currentTranslate = signal(0, ...(ngDevMode ? [{ debugName: "currentTranslate" }] : []));
683
+ dragging = signal(false, ...(ngDevMode ? [{ debugName: "dragging" }] : []));
684
+ startX = signal(0, ...(ngDevMode ? [{ debugName: "startX" }] : []));
685
+ prevTranslate = signal(0, ...(ngDevMode ? [{ debugName: "prevTranslate" }] : []));
686
+ // ==============================================
687
+ // Computed Properties
688
+ // ==============================================
689
+ arrowColorClass = computed(() => ColorMapping.get(this.arrowColor())?.text ?? 'zs:text-gray-600', ...(ngDevMode ? [{ debugName: "arrowColorClass" }] : []));
690
+ itemsPerBoxWidth = computed(() => `${100 / this.itemsPerBox()}%`, ...(ngDevMode ? [{ debugName: "itemsPerBoxWidth" }] : []));
691
+ totalBoxes = computed(() => Math.ceil(this.itemsNumber() / this.itemsPerBox()), ...(ngDevMode ? [{ debugName: "totalBoxes" }] : []));
692
+ indicatorBoxes = computed(() => Array.from({ length: this.totalBoxes() }, (_, i) => i), ...(ngDevMode ? [{ debugName: "indicatorBoxes" }] : []));
693
+ // ==============================================
694
+ // Private Properties
695
+ // ==============================================
696
+ autoPlayTimer = null;
697
+ resizeObserver;
698
+ // ==============================================
699
+ // Lifecycle Hooks
700
+ // ==============================================
701
+ constructor() {
702
+ effect(() => {
703
+ this.autoPlay() ? this.startAutoPlay() : this.stopAutoPlay();
704
+ });
705
+ }
706
+ ngAfterViewInit() {
707
+ const el = this.carouselContainer()?.nativeElement;
708
+ if (el) {
709
+ this.resizeObserver = new ResizeObserver(() => {
710
+ this.updateItemsPerBox();
711
+ const containerWidth = el.offsetWidth;
712
+ const pos = -this.currentIndex() * containerWidth;
713
+ this.applyTranslate(pos, 'none');
714
+ });
715
+ this.resizeObserver.observe(el);
716
+ this.updateItemsPerBox();
717
+ const containerWidth = el.offsetWidth;
718
+ this.applyTranslate(-this.currentIndex() * containerWidth, 'none');
719
+ }
720
+ }
721
+ ngOnDestroy() {
722
+ this.stopAutoPlay();
723
+ if (this.resizeObserver) {
724
+ this.resizeObserver.disconnect();
725
+ }
726
+ }
727
+ // ==============================================
728
+ // Public Methods
729
+ // ==============================================
730
+ updateIndex(newIndex) {
731
+ const containerEl = this.carouselContainer();
732
+ if (!containerEl)
733
+ return;
734
+ this.currentIndex.set(newIndex);
735
+ this.indexChangeEv.emit(newIndex);
736
+ const containerWidth = containerEl.nativeElement.offsetWidth;
737
+ const newTranslate = -newIndex * containerWidth;
738
+ this.applyTranslate(newTranslate, 'transform 0.3s ease-out');
739
+ this.restartAutoPlay();
740
+ }
741
+ next() {
742
+ if (this.currentIndex() < this.totalBoxes() - 1) {
743
+ this.updateIndex(this.currentIndex() + 1);
744
+ }
745
+ else {
746
+ this.updateIndex(0);
747
+ }
748
+ }
749
+ previous() {
750
+ if (this.currentIndex() > 0) {
751
+ this.updateIndex(this.currentIndex() - 1);
752
+ }
753
+ else {
754
+ this.updateIndex(this.totalBoxes() - 1);
755
+ }
756
+ }
757
+ // ==============================================
758
+ // AutoPlay Methods
759
+ // ==============================================
760
+ startAutoPlay() {
761
+ if (this.autoPlayTimer)
762
+ return;
763
+ this.autoPlayTimer = setInterval(() => this.next(), this.duration());
764
+ }
765
+ stopAutoPlay() {
766
+ if (this.autoPlayTimer) {
767
+ clearInterval(this.autoPlayTimer);
768
+ this.autoPlayTimer = null;
769
+ }
770
+ }
771
+ restartAutoPlay() {
772
+ this.stopAutoPlay();
773
+ if (this.autoPlay()) {
774
+ this.startAutoPlay();
775
+ }
776
+ }
777
+ // ==============================================
778
+ // Resize Handling
779
+ // ==============================================
780
+ updateItemsPerBox() {
781
+ const containerWidth = this.carouselContainer()?.nativeElement.offsetWidth || 0;
782
+ const possibleCount = Math.floor(containerWidth / this.itemMinWidth());
783
+ this.itemsPerBox.set(Math.min(this.maxItemsPerBox(), Math.max(1, possibleCount)));
784
+ }
785
+ // ==============================================
786
+ // Drag Handling
787
+ // ==============================================
788
+ onDragStart(event) {
789
+ event.preventDefault();
790
+ const trackEl = this.carouselTrack();
791
+ const containerEl = this.carouselContainer();
792
+ if (!trackEl || !containerEl)
793
+ return;
794
+ this.dragging.set(true);
795
+ this.startX.set(event.clientX);
796
+ this.prevTranslate.set(-this.currentIndex() * containerEl.nativeElement.offsetWidth);
797
+ trackEl.nativeElement.style.transition = 'none';
798
+ this.stopAutoPlay();
799
+ }
800
+ onDragMove(event) {
801
+ if (!this.dragging())
802
+ return;
803
+ const delta = event.clientX - this.startX();
804
+ this.currentTranslate.set(this.prevTranslate() + delta);
805
+ }
806
+ onDragEnd() {
807
+ if (!this.dragging())
808
+ return;
809
+ this.dragging.set(false);
810
+ const containerEl = this.carouselContainer();
811
+ if (!containerEl)
812
+ return;
813
+ const containerWidth = containerEl.nativeElement.offsetWidth;
814
+ const movedSlides = Math.round(-this.currentTranslate() / containerWidth);
815
+ const newIndex = Math.max(0, Math.min(this.totalBoxes() - 1, movedSlides));
816
+ this.updateIndex(newIndex);
817
+ const finalTranslate = -this.currentIndex() * containerWidth;
818
+ this.applyTranslate(finalTranslate, 'transform 0.3s ease-out');
819
+ if (this.autoPlay()) {
820
+ this.startAutoPlay();
821
+ }
822
+ }
823
+ // ==============================================
824
+ // Helper Methods
825
+ // ==============================================
826
+ applyTranslate(value, transition = 'transform 0.3s ease-out') {
827
+ this.currentTranslate.set(value);
828
+ const trackEl = this.carouselTrack();
829
+ if (!trackEl)
830
+ return;
831
+ trackEl.nativeElement.style.transition = transition ?? 'none';
832
+ }
833
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Carousel, deps: [], target: i0.ɵɵFactoryTarget.Component });
834
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Carousel, isStandalone: true, selector: "ZS-carousel", inputs: { itemsNumber: { classPropertyName: "itemsNumber", publicName: "itemsNumber", isSignal: true, isRequired: true, transformFunction: null }, arrows: { classPropertyName: "arrows", publicName: "arrows", isSignal: true, isRequired: false, transformFunction: null }, arrowColor: { classPropertyName: "arrowColor", publicName: "arrowColor", isSignal: true, isRequired: false, transformFunction: null }, showIndicators: { classPropertyName: "showIndicators", publicName: "showIndicators", isSignal: true, isRequired: false, transformFunction: null }, autoPlay: { classPropertyName: "autoPlay", publicName: "autoPlay", isSignal: true, isRequired: false, transformFunction: null }, duration: { classPropertyName: "duration", publicName: "duration", isSignal: true, isRequired: false, transformFunction: null }, maxItemsPerBox: { classPropertyName: "maxItemsPerBox", publicName: "maxItemsPerBox", isSignal: true, isRequired: false, transformFunction: null }, itemMinWidth: { classPropertyName: "itemMinWidth", publicName: "itemMinWidth", isSignal: true, isRequired: false, transformFunction: null }, currentIndex: { classPropertyName: "currentIndex", publicName: "currentIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { indexChangeEv: "indexChangeEv", currentIndex: "currentIndexChange" }, viewQueries: [{ propertyName: "carouselContainer", first: true, predicate: ["carouselContainer"], descendants: true, isSignal: true }, { propertyName: "carouselTrack", first: true, predicate: ["carouselTrack"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- =================================== Main Container =================================== -->\n<div \n class=\"zs:relative zs:w-full zs:mt-6\"\n role=\"region\"\n aria-label=\"Image carousel\"\n aria-roledescription=\"carousel\"\n>\n\n <!-- =================================== Carousel Track =================================== -->\n <div \n carousel-container\n #carouselContainer\n class=\"zs:overflow-hidden zs:relative\"\n role=\"group\"\n aria-label=\"Carousel track\"\n (pointerdown)=\"onDragStart($event)\"\n (pointermove)=\"onDragMove($event)\"\n (pointerup)=\"onDragEnd()\"\n (pointerleave)=\"onDragEnd()\"\n (pointercancel)=\"onDragEnd()\"\n [class.dragging]=\"dragging()\"\n >\n <div \n #carouselTrack \n [style.--carousel-item-width]=\"itemsPerBoxWidth()\"\n class=\"zs:flex zs:transition-transform zs:duration-500 zs:ease-in-out\"\n [style.transform]=\"'translateX(' + currentTranslate() + 'px)'\"\n >\n <!-- Cards -->\n <ng-content select=\"[carousel-item]\"></ng-content>\n <!-- /Cards -->\n </div>\n </div>\n <!-- =================================== /Carousel Track =================================== -->\n\n <!-- =================================== Navigation Arrows =================================== -->\n @if (arrows()) {\n <button\n type=\"button\"\n class=\"zs:absolute zs:top-1/2 zs:left-3 zs:size-10 zs:-translate-y-1/2 zs:p-3 zs:flex zs:items-center zs:justify-center \n zs:rounded-full zs:bg-gray-100/50 zs:dark:bg-gray-500/50 zs:hover:bg-gray-100/70 \n zs:dark:hover:bg-gray-500/70 zs:transition shadow-lg-all shadow-lg-all-night\"\n (click)=\"previous()\"\n aria-label=\"Previous slide\"\n [attr.aria-controls]=\"carouselContainer?.id\"\n >\n <i [ngClass]=\"arrowColorClass()\" class=\"fas fa-chevron-left zs:text-xl\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"zs:absolute zs:top-1/2 zs:right-3 zs:size-10 zs:-translate-y-1/2 zs:p-3 zs:flex zs:items-center zs:justify-center \n zs:rounded-full zs:bg-gray-100/50 zs:dark:bg-gray-500/50 zs:hover:bg-gray-100/70 \n zs:dark:hover:bg-gray-500/70 zs:transition shadow-lg-all shadow-lg-all-night\"\n (click)=\"next()\"\n aria-label=\"Next slide\"\n [attr.aria-controls]=\"carouselContainer?.id\"\n >\n <i [ngClass]=\"arrowColorClass()\" class=\"fas fa-chevron-right zs:text-xl\" aria-hidden=\"true\"></i>\n </button>\n }\n <!-- =================================== /Navigation Arrows =================================== -->\n\n</div>\n<!-- =================================== /Main Container =================================== -->\n\n\n<!-- =================================== Indicators =================================== -->\n@if(showIndicators()) {\n <div class=\"zs:flex zs:justify-center zs:mt-4 zs:gap-2\" role=\"tablist\" aria-label=\"Carousel indicators\">\n @for (item of indicatorBoxes(); track $index; let i = $index) {\n <button\n type=\"button\"\n role=\"tab\"\n class=\"zs:w-3 zs:h-3 zs:rounded-full zs:transition\"\n [ngClass]=\"currentIndex() === i \n ? 'zs:bg-gray-800 zs:dark:bg-gray-200 zs:scale-110' \n : 'zs:bg-gray-400 zs:dark:bg-gray-600 zs:hover:bg-gray-600 zs:dark:hover:bg-gray-400'\"\n (click)=\"updateIndex(i)\"\n [attr.aria-label]=\"'Go to slide ' + (i + 1)\"\n [attr.aria-current]=\"currentIndex() === i ? 'true' : null\"\n tabindex=\"0\"\n ></button>\n }\n </div>\n}\n<!-- =================================== /Indicators =================================== -->", styles: [":host{display:block}[carousel-container]{touch-action:pan-y;cursor:grab}[carousel-container].dragging{cursor:grabbing!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
835
+ }
836
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Carousel, decorators: [{
837
+ type: Component,
838
+ args: [{ selector: 'ZS-carousel', imports: [CommonModule], template: "<!-- =================================== Main Container =================================== -->\n<div \n class=\"zs:relative zs:w-full zs:mt-6\"\n role=\"region\"\n aria-label=\"Image carousel\"\n aria-roledescription=\"carousel\"\n>\n\n <!-- =================================== Carousel Track =================================== -->\n <div \n carousel-container\n #carouselContainer\n class=\"zs:overflow-hidden zs:relative\"\n role=\"group\"\n aria-label=\"Carousel track\"\n (pointerdown)=\"onDragStart($event)\"\n (pointermove)=\"onDragMove($event)\"\n (pointerup)=\"onDragEnd()\"\n (pointerleave)=\"onDragEnd()\"\n (pointercancel)=\"onDragEnd()\"\n [class.dragging]=\"dragging()\"\n >\n <div \n #carouselTrack \n [style.--carousel-item-width]=\"itemsPerBoxWidth()\"\n class=\"zs:flex zs:transition-transform zs:duration-500 zs:ease-in-out\"\n [style.transform]=\"'translateX(' + currentTranslate() + 'px)'\"\n >\n <!-- Cards -->\n <ng-content select=\"[carousel-item]\"></ng-content>\n <!-- /Cards -->\n </div>\n </div>\n <!-- =================================== /Carousel Track =================================== -->\n\n <!-- =================================== Navigation Arrows =================================== -->\n @if (arrows()) {\n <button\n type=\"button\"\n class=\"zs:absolute zs:top-1/2 zs:left-3 zs:size-10 zs:-translate-y-1/2 zs:p-3 zs:flex zs:items-center zs:justify-center \n zs:rounded-full zs:bg-gray-100/50 zs:dark:bg-gray-500/50 zs:hover:bg-gray-100/70 \n zs:dark:hover:bg-gray-500/70 zs:transition shadow-lg-all shadow-lg-all-night\"\n (click)=\"previous()\"\n aria-label=\"Previous slide\"\n [attr.aria-controls]=\"carouselContainer?.id\"\n >\n <i [ngClass]=\"arrowColorClass()\" class=\"fas fa-chevron-left zs:text-xl\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"zs:absolute zs:top-1/2 zs:right-3 zs:size-10 zs:-translate-y-1/2 zs:p-3 zs:flex zs:items-center zs:justify-center \n zs:rounded-full zs:bg-gray-100/50 zs:dark:bg-gray-500/50 zs:hover:bg-gray-100/70 \n zs:dark:hover:bg-gray-500/70 zs:transition shadow-lg-all shadow-lg-all-night\"\n (click)=\"next()\"\n aria-label=\"Next slide\"\n [attr.aria-controls]=\"carouselContainer?.id\"\n >\n <i [ngClass]=\"arrowColorClass()\" class=\"fas fa-chevron-right zs:text-xl\" aria-hidden=\"true\"></i>\n </button>\n }\n <!-- =================================== /Navigation Arrows =================================== -->\n\n</div>\n<!-- =================================== /Main Container =================================== -->\n\n\n<!-- =================================== Indicators =================================== -->\n@if(showIndicators()) {\n <div class=\"zs:flex zs:justify-center zs:mt-4 zs:gap-2\" role=\"tablist\" aria-label=\"Carousel indicators\">\n @for (item of indicatorBoxes(); track $index; let i = $index) {\n <button\n type=\"button\"\n role=\"tab\"\n class=\"zs:w-3 zs:h-3 zs:rounded-full zs:transition\"\n [ngClass]=\"currentIndex() === i \n ? 'zs:bg-gray-800 zs:dark:bg-gray-200 zs:scale-110' \n : 'zs:bg-gray-400 zs:dark:bg-gray-600 zs:hover:bg-gray-600 zs:dark:hover:bg-gray-400'\"\n (click)=\"updateIndex(i)\"\n [attr.aria-label]=\"'Go to slide ' + (i + 1)\"\n [attr.aria-current]=\"currentIndex() === i ? 'true' : null\"\n tabindex=\"0\"\n ></button>\n }\n </div>\n}\n<!-- =================================== /Indicators =================================== -->", styles: [":host{display:block}[carousel-container]{touch-action:pan-y;cursor:grab}[carousel-container].dragging{cursor:grabbing!important}\n"] }]
839
+ }], ctorParameters: () => [], propDecorators: { itemsNumber: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemsNumber", required: true }] }], arrows: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrows", required: false }] }], arrowColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowColor", required: false }] }], showIndicators: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIndicators", required: false }] }], autoPlay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoPlay", required: false }] }], duration: [{ type: i0.Input, args: [{ isSignal: true, alias: "duration", required: false }] }], maxItemsPerBox: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxItemsPerBox", required: false }] }], itemMinWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemMinWidth", required: false }] }], indexChangeEv: [{ type: i0.Output, args: ["indexChangeEv"] }], currentIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentIndex", required: false }] }, { type: i0.Output, args: ["currentIndexChange"] }], carouselContainer: [{ type: i0.ViewChild, args: ['carouselContainer', { isSignal: true }] }], carouselTrack: [{ type: i0.ViewChild, args: ['carouselTrack', { isSignal: true }] }] } });
840
+
841
+ class Connection {
842
+ isOnline = signal(true, ...(ngDevMode ? [{ debugName: "isOnline" }] : []));
843
+ isOnlineEv = output();
844
+ onlineListener = () => this.isOnline.set(true);
845
+ offlineListener = () => this.isOnline.set(false);
846
+ constructor() {
847
+ this.checkOnline();
848
+ window.addEventListener('online', this.onlineListener);
849
+ window.addEventListener('offline', this.offlineListener);
850
+ effect(() => {
851
+ const status = this.isOnline();
852
+ this.isOnlineEv.emit(status);
853
+ });
854
+ }
855
+ checkOnline() {
856
+ if (navigator.onLine)
857
+ this.onlineListener();
858
+ else
859
+ this.offlineListener();
860
+ }
861
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Connection, deps: [], target: i0.ɵɵFactoryTarget.Component });
862
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Connection, isStandalone: true, selector: "ZS-connection", outputs: { isOnlineEv: "isOnlineEv" }, ngImport: i0, template: "<div tabindex=\"0\" \n class=\"zs:inline-flex zs:items-center zs:justify-center zs:gap-2 zs:rounded-full \n zs:text-sm zs:font-medium zs:px-3 zs:h-8 zs:w-fit zs:cursor-pointer\"\n [ngClass]=\"[\n isOnline() \n ? 'zs:bg-green-100 zs:text-green-800 zs:dark:bg-green-700 zs:dark:text-green-100'\n : 'zs:bg-red-100 zs:text-red-800 zs:dark:bg-red-700 zs:dark:text-red-100'\n ]\"\n>\n <i\n class=\"fa fa-circle zs:text-xs zs:leading-none\"\n [ngClass]=\"[\n isOnline() ? 'zs:text-green-500' : 'zs:text-red-500'\n ]\"\n aria-hidden=\"true\"\n ></i>\n <span>\n @if (isOnline()) {\n Online\n } @else {\n Offline\n }\n </span>\n</div>", styles: ["div{box-shadow:0 1px 2px #0000000d}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
863
+ }
864
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Connection, decorators: [{
865
+ type: Component,
866
+ args: [{ selector: 'ZS-connection', imports: [CommonModule], template: "<div tabindex=\"0\" \n class=\"zs:inline-flex zs:items-center zs:justify-center zs:gap-2 zs:rounded-full \n zs:text-sm zs:font-medium zs:px-3 zs:h-8 zs:w-fit zs:cursor-pointer\"\n [ngClass]=\"[\n isOnline() \n ? 'zs:bg-green-100 zs:text-green-800 zs:dark:bg-green-700 zs:dark:text-green-100'\n : 'zs:bg-red-100 zs:text-red-800 zs:dark:bg-red-700 zs:dark:text-red-100'\n ]\"\n>\n <i\n class=\"fa fa-circle zs:text-xs zs:leading-none\"\n [ngClass]=\"[\n isOnline() ? 'zs:text-green-500' : 'zs:text-red-500'\n ]\"\n aria-hidden=\"true\"\n ></i>\n <span>\n @if (isOnline()) {\n Online\n } @else {\n Offline\n }\n </span>\n</div>", styles: ["div{box-shadow:0 1px 2px #0000000d}\n"] }]
867
+ }], ctorParameters: () => [], propDecorators: { isOnlineEv: [{ type: i0.Output, args: ["isOnlineEv"] }] } });
868
+
869
+ class ExtractorService {
870
+ /**
871
+ * Extract all error messages recursively from any structure
872
+ * @param input - could be string | Array | object | | Error | null | undefined
873
+ * @returns string[] - flattened array of all messages
874
+ */
875
+ extract(input) {
876
+ const result = [];
877
+ const seen = new WeakSet();
878
+ const traverse = (value) => {
879
+ if (value == null)
880
+ return; // null or undefined
881
+ if (typeof value === 'string') {
882
+ result.push(String(value));
883
+ }
884
+ else if (Array.isArray(value)) {
885
+ for (const item of value)
886
+ traverse(item);
887
+ }
888
+ else if (value instanceof Error) {
889
+ result.push(value.message);
890
+ }
891
+ else if (typeof value === 'object') {
892
+ if (seen.has(value))
893
+ return;
894
+ seen.add(value);
895
+ for (const key of Object.keys(value)) {
896
+ traverse(value[key]);
897
+ }
898
+ }
899
+ };
900
+ traverse(input);
901
+ return result;
902
+ }
903
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ExtractorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
904
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ExtractorService, providedIn: 'root' });
905
+ }
906
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ExtractorService, decorators: [{
907
+ type: Injectable,
908
+ args: [{
909
+ providedIn: 'root'
910
+ }]
911
+ }] });
912
+
913
+ // ==============================================
914
+ // Types
915
+ // ==============================================
916
+ // ==============================================
917
+ // Zform Class
918
+ // ==============================================
919
+ class Form {
920
+ fields;
921
+ touched = signal(false, ...(ngDevMode ? [{ debugName: "touched" }] : []));
922
+ constructor(initial) {
923
+ this.fields = Object.keys(initial).reduce((acc, key) => {
924
+ acc[key] = signal({
925
+ value: initial[key],
926
+ valid: false,
927
+ });
928
+ return acc;
929
+ }, {});
930
+ }
931
+ // ==============================================
932
+ // Field Accessors
933
+ // ==============================================
934
+ set(key, value, valid = true) {
935
+ this.fields[key].set({ value, valid });
936
+ }
937
+ patch(key, partial) {
938
+ const current = this.fields[key]();
939
+ this.fields[key].set({ ...current, ...partial });
940
+ }
941
+ get(key) {
942
+ return this.fields[key]();
943
+ }
944
+ // ==============================================
945
+ // Form State & Validation
946
+ // ==============================================
947
+ allFilled = computed(() => {
948
+ return Object.values(this.fields).every(f => {
949
+ const v = f();
950
+ return v.value !== null && v.value !== '' && v.valid === true;
951
+ });
952
+ }, ...(ngDevMode ? [{ debugName: "allFilled" }] : []));
953
+ markAllTouched() {
954
+ this.touched.set(true);
955
+ }
956
+ // ==============================================
957
+ // Data Extraction & Submission
958
+ // ==============================================
959
+ getValues() {
960
+ const result = {};
961
+ for (const key in this.fields) {
962
+ if (this.fields.hasOwnProperty(key)) {
963
+ result[key] = this.fields[key]().value ?? undefined;
964
+ }
965
+ }
966
+ return result;
967
+ }
968
+ getValidations() {
969
+ const result = {};
970
+ for (const key in this.fields) {
971
+ if (this.fields.hasOwnProperty(key)) {
972
+ result[key] = this.fields[key]().valid;
973
+ }
974
+ }
975
+ return result;
976
+ }
977
+ submit(callback) {
978
+ this.markAllTouched();
979
+ const allFilled = this.allFilled();
980
+ const allValid = Object.values(this.getValidations()).every(v => v === true);
981
+ if (!allFilled || !allValid)
982
+ return;
983
+ callback(this.getValues());
984
+ }
985
+ }
986
+
987
+ // ==============================================
988
+ // Imports
989
+ // ==============================================
990
+ // ==============================================
991
+ // Component Metadata
992
+ // ==============================================
993
+ class Button {
994
+ // ==============================================
995
+ // Inputs
996
+ // ==============================================
997
+ Id = input(crypto.randomUUID(), ...(ngDevMode ? [{ debugName: "Id" }] : []));
998
+ btnStyle = input('primary', ...(ngDevMode ? [{ debugName: "btnStyle" }] : []));
999
+ variant = input('solid', ...(ngDevMode ? [{ debugName: "variant" }] : []));
1000
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
1001
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1002
+ icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : [])); // Optional FontAwesome icon class (e.g., "fa fa-plus")
1003
+ type = input('button', ...(ngDevMode ? [{ debugName: "type" }] : []));
1004
+ // ==============================================
1005
+ // Outputs
1006
+ // ==============================================
1007
+ clickedEv = output();
1008
+ // ==============================================
1009
+ // Computed Properties
1010
+ // ==============================================
1011
+ palette = computed(() => FormPaletteMap.get(this.btnStyle()), ...(ngDevMode ? [{ debugName: "palette" }] : []));
1012
+ solidTextColor = computed(() => {
1013
+ if (['primary', 'dark', 'violet', 'secondary'].includes(this.btnStyle()))
1014
+ return 'zs:text-slate-100';
1015
+ return 'zs:text-slate-800 zs:dark:text-slate-100';
1016
+ }, ...(ngDevMode ? [{ debugName: "solidTextColor" }] : []));
1017
+ baseClasses = computed(() => {
1018
+ const size = this.size();
1019
+ const variant = this.variant();
1020
+ const p = this.palette();
1021
+ const sizes = {
1022
+ xs: 'zs:text-[10px] zs:px-2 zs:py-1',
1023
+ sm: 'zs:text-xs zs:px-3.5 zs:py-1.75',
1024
+ md: 'zs:text-sm zs:px-5 zs:py-2.5',
1025
+ lg: 'zs:text-base zs:px-6.5 zs:py-3.25',
1026
+ xl: 'zs:text-lg zs:px-8 zs:py-4',
1027
+ };
1028
+ const solidClasses = this.join(p.btnBG, p.btnBGHover, 'zs:shadow-md zs:dark:shadow-slate-700/50', 'zs:hover:shadow-lg', 'zs:active:shadow-sm', ['dark'].includes(this.btnStyle()) ? 'zs:dark:hover:shadow-sm' : '', this.solidTextColor());
1029
+ const outlineClasses = this.join('zs:bg-transparent', 'zs:border', p.border, p.borderHover, p.text, p.textHover, 'zs:hover:shadow-sm');
1030
+ const stateClasses = this.disabled()
1031
+ ? 'zs:opacity-60 zs:cursor-not-allowed zs:shadow-none'
1032
+ : this.join('zs:hover:scale-[1.02]', 'zs:active:scale-[0.97]', 'zs:transition-[background-color,color,border-color,box-shadow,opacity]', 'zs:duration-200', 'zs:ease-in-out');
1033
+ return this.join('zs:inline-flex zs:items-center zs:justify-center', ['xl'].includes(size) ? 'zs:rounded-xl' : 'zs:rounded-lg', 'zs:focus-visible:ring-2', 'zs:select-none', 'zs:outline-hidden', sizes[size], variant === 'solid' ? solidClasses : outlineClasses, stateClasses, p.ring);
1034
+ }, ...(ngDevMode ? [{ debugName: "baseClasses" }] : []));
1035
+ // ==============================================
1036
+ // Methods
1037
+ // ==============================================
1038
+ join(...classes) {
1039
+ return classes.join(' ');
1040
+ }
1041
+ onClick(event) {
1042
+ if (!this.disabled()) {
1043
+ this.clickedEv.emit(event);
1044
+ }
1045
+ }
1046
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Button, deps: [], target: i0.ɵɵFactoryTarget.Component });
1047
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Button, isStandalone: true, selector: "ZS-button", inputs: { Id: { classPropertyName: "Id", publicName: "Id", isSignal: true, isRequired: false, transformFunction: null }, btnStyle: { classPropertyName: "btnStyle", publicName: "btnStyle", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clickedEv: "clickedEv" }, ngImport: i0, template: "<!-- ========================= Button Core Attributes ========================= -->\n<button \n [id]=\"Id()\"\n class=\"zs:w-full\"\n [attr.type]=\"type()\"\n [disabled]=\"disabled()\"\n [ngClass]=\"['zs:flex zs:gap-2', baseClasses()]\"\n\n [attr.aria-disabled]=\"disabled()\"\n [attr.aria-label]=\"icon() ? (icon() + ' button') : undefined\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n\n (click)=\"onClick($event)\"\n>\n <!-- ========================= Button Content ========================= -->\n @if (icon()) {\n <i [class]=\"icon()\" aria-hidden=\"true\"></i>\n }\n <ng-content></ng-content>\n</button>", styles: [":host{display:inline-flex}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
1048
+ }
1049
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Button, decorators: [{
1050
+ type: Component,
1051
+ args: [{ selector: 'ZS-button', imports: [CommonModule], template: "<!-- ========================= Button Core Attributes ========================= -->\n<button \n [id]=\"Id()\"\n class=\"zs:w-full\"\n [attr.type]=\"type()\"\n [disabled]=\"disabled()\"\n [ngClass]=\"['zs:flex zs:gap-2', baseClasses()]\"\n\n [attr.aria-disabled]=\"disabled()\"\n [attr.aria-label]=\"icon() ? (icon() + ' button') : undefined\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n\n (click)=\"onClick($event)\"\n>\n <!-- ========================= Button Content ========================= -->\n @if (icon()) {\n <i [class]=\"icon()\" aria-hidden=\"true\"></i>\n }\n <ng-content></ng-content>\n</button>", styles: [":host{display:inline-flex}\n"] }]
1052
+ }], propDecorators: { Id: [{ type: i0.Input, args: [{ isSignal: true, alias: "Id", required: false }] }], btnStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "btnStyle", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], clickedEv: [{ type: i0.Output, args: ["clickedEv"] }] } });
1053
+
1054
+ // ==============================================
1055
+ // Maps
1056
+ // ==============================================
1057
+ const positionMap = {
1058
+ 'left top': 'zs:justify-start zs:items-start',
1059
+ 'left bot': 'zs:justify-start zs:items-end',
1060
+ 'right top': 'zs:justify-end zs:items-start',
1061
+ 'right bot': 'zs:justify-end zs:items-end',
1062
+ 'top': 'zs:justify-center zs:items-start',
1063
+ 'bot': 'zs:justify-center zs:items-end',
1064
+ 'center': 'zs:justify-center zs:items-center',
1065
+ };
1066
+ // ==============================================
1067
+ // Component Metadata
1068
+ // ==============================================
1069
+ class Modal {
1070
+ zIndices = zIndices;
1071
+ // ==============================================
1072
+ // Model
1073
+ // ==============================================
1074
+ /** 🔹 Model لتحديد الفتح والإغلاق */
1075
+ open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
1076
+ // ==============================================
1077
+ // Inputs
1078
+ // ==============================================
1079
+ title = input('Modal Title', ...(ngDevMode ? [{ debugName: "title" }] : []));
1080
+ modalStyle = input('primary', ...(ngDevMode ? [{ debugName: "modalStyle" }] : []));
1081
+ showCancelIcon = input(true, ...(ngDevMode ? [{ debugName: "showCancelIcon" }] : []));
1082
+ showFooter = input(true, ...(ngDevMode ? [{ debugName: "showFooter" }] : []));
1083
+ cancelConfig = input(...(ngDevMode ? [undefined, { debugName: "cancelConfig" }] : []));
1084
+ confirmConfig = input(...(ngDevMode ? [undefined, { debugName: "confirmConfig" }] : []));
1085
+ position = input('center', ...(ngDevMode ? [{ debugName: "position" }] : []));
1086
+ closeOnOverlay = input(true, ...(ngDevMode ? [{ debugName: "closeOnOverlay" }] : []));
1087
+ // ==============================================
1088
+ // Defaults
1089
+ // ==============================================
1090
+ cancelConfigDefault = {
1091
+ show: true,
1092
+ text: 'Cancel',
1093
+ btnStyle: 'secondary',
1094
+ variant: 'solid',
1095
+ size: 'md',
1096
+ icon: null,
1097
+ disabled: false
1098
+ };
1099
+ confirmConfigDefault = {
1100
+ show: true,
1101
+ text: 'Confirm',
1102
+ btnStyle: 'primary',
1103
+ variant: 'solid',
1104
+ size: 'md',
1105
+ icon: null,
1106
+ disabled: false
1107
+ };
1108
+ cancelMerged = computed(() => ({
1109
+ ...this.cancelConfigDefault,
1110
+ ...(this.cancelConfig() ?? {})
1111
+ }), ...(ngDevMode ? [{ debugName: "cancelMerged" }] : []));
1112
+ confirmMerged = computed(() => ({
1113
+ ...this.confirmConfigDefault,
1114
+ ...(this.confirmConfig() ?? {})
1115
+ }), ...(ngDevMode ? [{ debugName: "confirmMerged" }] : []));
1116
+ // ==============================================
1117
+ // Outputs
1118
+ // ==============================================
1119
+ confirmEv = output();
1120
+ cancelEv = output();
1121
+ closedEv = output();
1122
+ // ==============================================
1123
+ // Computed Signals
1124
+ // ==============================================
1125
+ palette = computed(() => FormPaletteMap.get(this.modalStyle()), ...(ngDevMode ? [{ debugName: "palette" }] : []));
1126
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
1127
+ positionClass = computed(() => positionMap[this.position()], ...(ngDevMode ? [{ debugName: "positionClass" }] : []));
1128
+ setTimeId;
1129
+ constructor() {
1130
+ effect(() => {
1131
+ const state = this.open();
1132
+ if (state)
1133
+ this.isOpen.set(true);
1134
+ untracked(() => {
1135
+ clearTimeout(this.setTimeId);
1136
+ if (!state && this.isOpen()) {
1137
+ this.setTimeId = window.setTimeout(() => this.isOpen.set(false), 250);
1138
+ }
1139
+ });
1140
+ });
1141
+ }
1142
+ // ==============================================
1143
+ // Methods
1144
+ // ==============================================
1145
+ close() {
1146
+ this.open.set(false);
1147
+ this.closedEv.emit();
1148
+ }
1149
+ onOverlayClick(event) {
1150
+ if (this.closeOnOverlay()) {
1151
+ this.close();
1152
+ }
1153
+ }
1154
+ onEscape() {
1155
+ if (this.isOpen())
1156
+ this.close();
1157
+ }
1158
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Modal, deps: [], target: i0.ɵɵFactoryTarget.Component });
1159
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Modal, isStandalone: true, selector: "ZS-modal", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, modalStyle: { classPropertyName: "modalStyle", publicName: "modalStyle", isSignal: true, isRequired: false, transformFunction: null }, showCancelIcon: { classPropertyName: "showCancelIcon", publicName: "showCancelIcon", isSignal: true, isRequired: false, transformFunction: null }, showFooter: { classPropertyName: "showFooter", publicName: "showFooter", isSignal: true, isRequired: false, transformFunction: null }, cancelConfig: { classPropertyName: "cancelConfig", publicName: "cancelConfig", isSignal: true, isRequired: false, transformFunction: null }, confirmConfig: { classPropertyName: "confirmConfig", publicName: "confirmConfig", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, closeOnOverlay: { classPropertyName: "closeOnOverlay", publicName: "closeOnOverlay", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", confirmEv: "confirmEv", cancelEv: "cancelEv", closedEv: "closedEv" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "<!-- ========================= Overlay ========================= -->\n@if (isOpen()) {\n <div #modalRef\n class=\"zs:fixed zs:inset-0 zs:flex {{ zIndices.modal }} zs:bg-black/50 zs:dark:bg-black/70 zs:transition-all zs:duration-300\"\n [ngClass]=\"[positionClass(), open() ? 'animate-fadeIn' : 'animate-fadeOut']\"\n (click)=\"onOverlayClick($event)\"\n role=\"presentation\"\n [attr.inert]=\"isOpen() ? null : ''\"\n >\n\n <!-- ========================= Modal Container ========================= -->\n <div\n class=\"zs:relative zs:m-4 zs:bg-white zs:dark:bg-slate-900 zs:w-full zs:max-w-lg zs:mx-4 \n zs:rounded-2xl zs:shadow-lg zs:transition-all zs:duration-300 zs:outline-hidden\"\n [ngClass]=\"[\n 'zs:border', \n palette().border,\n palette().text,\n open() ? 'animate-modalIn' : 'animate-modalOut'\n ]\"\n (click)=\"$event.stopPropagation()\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"'modal-title'\"\n [attr.aria-describedby]=\"'modal-body'\"\n tabindex=\"0\"\n (keydown.escape)=\"close()\"\n >\n\n <!-- ========================= Modal Header ========================= -->\n <div\n class=\"zs:flex zs:items-center zs:justify-between zs:px-4 zs:py-3 zs:border-b\"\n [class]=\"palette().border\"\n >\n <h3 id=\"modal-title\" class=\"zs:font-semibold zs:text-lg\">\n {{ title() }}\n </h3>\n @if(showCancelIcon()) {\n <button \n class=\"zs:text-xl zs:hover:opacity-85 zs:cursor-pointer zs:transition-colors zs:duration-200 zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-offset-2 zs:focus-visible:ring-blue-500 zs:rounded-md\"\n [class]=\"palette().textHover\"\n (click)=\"close()\"\n aria-label=\"Close dialog\"\n >\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n\n <!-- ========================= Modal Body ========================= -->\n <div id=\"modal-body\" class=\"zs:p-4 scroll zs:max-h-[75vh] scroll-{{ modalStyle() }}\">\n <ng-content></ng-content>\n </div>\n\n <!-- ========================= Modal Footer ========================= -->\n @if (showFooter()) {\n <div\n class=\"zs:flex zs:justify-end zs:gap-3 zs:p-4 zs:border-t\"\n [class]=\"palette().border\"\n >\n @if (cancelMerged().show) {\n <ZS-button\n [btnStyle]=\"cancelMerged().btnStyle\"\n variant=\"solid\"\n type=\"button\"\n (clickedEv)=\"cancelEv.emit()\"\n [icon]=\"cancelMerged().icon\"\n [disabled]=\"cancelMerged().disabled\"\n [size]=\"cancelMerged().size\"\n [variant]=\"cancelMerged().variant\"\n aria-label=\"Cancel\"\n >\n {{ cancelMerged().text}}\n </ZS-button>\n }\n @if (confirmMerged().show) {\n <ZS-button\n type=\"button\"\n [btnStyle]=\"confirmMerged().btnStyle\"\n (clickedEv)=\"confirmEv.emit()\"\n [icon]=\"confirmMerged().icon\"\n [disabled]=\"confirmMerged().disabled\"\n [size]=\"confirmMerged().size\"\n [variant]=\"confirmMerged().variant\"\n aria-label=\"Confirm\"\n >\n {{ confirmMerged().text}}\n </ZS-button>\n }\n </div>\n }\n </div>\n </div>\n}", styles: ["@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes modalIn{0%{opacity:0;transform:scale(.9) translateY(10px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes modalOut{0%{opacity:1;transform:scale(1) translateY(0)}to{opacity:0;transform:scale(.9) translateY(10px)}}.animate-fadeIn{animation:fadeIn .25s ease-out forwards}.animate-modalIn{animation:modalIn .25s ease-out forwards}.animate-fadeOut{animation:fadeOut .25s ease-in forwards}.animate-modalOut{animation:modalOut .25s ease-in forwards}\n"], dependencies: [{ kind: "component", type: Button, selector: "ZS-button", inputs: ["Id", "btnStyle", "variant", "size", "disabled", "icon", "type"], outputs: ["clickedEv"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
1160
+ }
1161
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Modal, decorators: [{
1162
+ type: Component,
1163
+ args: [{ selector: 'ZS-modal', imports: [Button, CommonModule], template: "<!-- ========================= Overlay ========================= -->\n@if (isOpen()) {\n <div #modalRef\n class=\"zs:fixed zs:inset-0 zs:flex {{ zIndices.modal }} zs:bg-black/50 zs:dark:bg-black/70 zs:transition-all zs:duration-300\"\n [ngClass]=\"[positionClass(), open() ? 'animate-fadeIn' : 'animate-fadeOut']\"\n (click)=\"onOverlayClick($event)\"\n role=\"presentation\"\n [attr.inert]=\"isOpen() ? null : ''\"\n >\n\n <!-- ========================= Modal Container ========================= -->\n <div\n class=\"zs:relative zs:m-4 zs:bg-white zs:dark:bg-slate-900 zs:w-full zs:max-w-lg zs:mx-4 \n zs:rounded-2xl zs:shadow-lg zs:transition-all zs:duration-300 zs:outline-hidden\"\n [ngClass]=\"[\n 'zs:border', \n palette().border,\n palette().text,\n open() ? 'animate-modalIn' : 'animate-modalOut'\n ]\"\n (click)=\"$event.stopPropagation()\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"'modal-title'\"\n [attr.aria-describedby]=\"'modal-body'\"\n tabindex=\"0\"\n (keydown.escape)=\"close()\"\n >\n\n <!-- ========================= Modal Header ========================= -->\n <div\n class=\"zs:flex zs:items-center zs:justify-between zs:px-4 zs:py-3 zs:border-b\"\n [class]=\"palette().border\"\n >\n <h3 id=\"modal-title\" class=\"zs:font-semibold zs:text-lg\">\n {{ title() }}\n </h3>\n @if(showCancelIcon()) {\n <button \n class=\"zs:text-xl zs:hover:opacity-85 zs:cursor-pointer zs:transition-colors zs:duration-200 zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-offset-2 zs:focus-visible:ring-blue-500 zs:rounded-md\"\n [class]=\"palette().textHover\"\n (click)=\"close()\"\n aria-label=\"Close dialog\"\n >\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n\n <!-- ========================= Modal Body ========================= -->\n <div id=\"modal-body\" class=\"zs:p-4 scroll zs:max-h-[75vh] scroll-{{ modalStyle() }}\">\n <ng-content></ng-content>\n </div>\n\n <!-- ========================= Modal Footer ========================= -->\n @if (showFooter()) {\n <div\n class=\"zs:flex zs:justify-end zs:gap-3 zs:p-4 zs:border-t\"\n [class]=\"palette().border\"\n >\n @if (cancelMerged().show) {\n <ZS-button\n [btnStyle]=\"cancelMerged().btnStyle\"\n variant=\"solid\"\n type=\"button\"\n (clickedEv)=\"cancelEv.emit()\"\n [icon]=\"cancelMerged().icon\"\n [disabled]=\"cancelMerged().disabled\"\n [size]=\"cancelMerged().size\"\n [variant]=\"cancelMerged().variant\"\n aria-label=\"Cancel\"\n >\n {{ cancelMerged().text}}\n </ZS-button>\n }\n @if (confirmMerged().show) {\n <ZS-button\n type=\"button\"\n [btnStyle]=\"confirmMerged().btnStyle\"\n (clickedEv)=\"confirmEv.emit()\"\n [icon]=\"confirmMerged().icon\"\n [disabled]=\"confirmMerged().disabled\"\n [size]=\"confirmMerged().size\"\n [variant]=\"confirmMerged().variant\"\n aria-label=\"Confirm\"\n >\n {{ confirmMerged().text}}\n </ZS-button>\n }\n </div>\n }\n </div>\n </div>\n}", styles: ["@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes modalIn{0%{opacity:0;transform:scale(.9) translateY(10px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes modalOut{0%{opacity:1;transform:scale(1) translateY(0)}to{opacity:0;transform:scale(.9) translateY(10px)}}.animate-fadeIn{animation:fadeIn .25s ease-out forwards}.animate-modalIn{animation:modalIn .25s ease-out forwards}.animate-fadeOut{animation:fadeOut .25s ease-in forwards}.animate-modalOut{animation:modalOut .25s ease-in forwards}\n"] }]
1164
+ }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], modalStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalStyle", required: false }] }], showCancelIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCancelIcon", required: false }] }], showFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooter", required: false }] }], cancelConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelConfig", required: false }] }], confirmConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmConfig", required: false }] }], position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], closeOnOverlay: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnOverlay", required: false }] }], confirmEv: [{ type: i0.Output, args: ["confirmEv"] }], cancelEv: [{ type: i0.Output, args: ["cancelEv"] }], closedEv: [{ type: i0.Output, args: ["closedEv"] }], onEscape: [{
1165
+ type: HostListener,
1166
+ args: ['document:keydown.escape']
1167
+ }] } });
1168
+
1169
+ // ==============================================
1170
+ // Imports
1171
+ // ==============================================
1172
+ // ==============================================
1173
+ // Component Decorator
1174
+ // ==============================================
1175
+ class Navbar {
1176
+ zIndices = zIndices;
1177
+ // ==============================================
1178
+ // Inputs
1179
+ // ==============================================
1180
+ fixed = input(true, ...(ngDevMode ? [{ debugName: "fixed" }] : []));
1181
+ logoUrl = input(...(ngDevMode ? [undefined, { debugName: "logoUrl" }] : []));
1182
+ siteNameConfig = input(...(ngDevMode ? [undefined, { debugName: "siteNameConfig" }] : []));
1183
+ authButtons = input({ showAuthButtons: false }, ...(ngDevMode ? [{ debugName: "authButtons" }] : []));
1184
+ showUserSection = input(true, ...(ngDevMode ? [{ debugName: "showUserSection" }] : []));
1185
+ showSearchBar = input(false, ...(ngDevMode ? [{ debugName: "showSearchBar" }] : []));
1186
+ navItems = input(...(ngDevMode ? [undefined, { debugName: "navItems" }] : []));
1187
+ isLoggedIn = input(false, ...(ngDevMode ? [{ debugName: "isLoggedIn" }] : []));
1188
+ userProfile = input(...(ngDevMode ? [undefined, { debugName: "userProfile" }] : []));
1189
+ userMenuItems = input([], ...(ngDevMode ? [{ debugName: "userMenuItems" }] : []));
1190
+ searchPlaceholder = input('Search...', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : []));
1191
+ // ==============================================
1192
+ // Outputs
1193
+ // ==============================================
1194
+ loginClickedEv = output();
1195
+ signupClickedEv = output();
1196
+ searchSubmittedEv = output();
1197
+ anyItemClickedEv = output();
1198
+ // ==============================================
1199
+ // Models
1200
+ // ==============================================
1201
+ searchValue = model(null, ...(ngDevMode ? [{ debugName: "searchValue" }] : []));
1202
+ isMobileMenuOpen = model(false, ...(ngDevMode ? [{ debugName: "isMobileMenuOpen" }] : []));
1203
+ // ==============================================
1204
+ // Internal State (Signals)
1205
+ // ==============================================
1206
+ isUserMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "isUserMenuOpen" }] : []));
1207
+ isMoreOpen = signal(false, ...(ngDevMode ? [{ debugName: "isMoreOpen" }] : []));
1208
+ // ==============================================
1209
+ // Computed Properties
1210
+ // ==============================================
1211
+ visibleNavItems = computed(() => {
1212
+ const items = this.navItems()?.navItems ?? [];
1213
+ const limit = this.showSearchBar() ? 2 : 5;
1214
+ return items.slice(0, limit).map(item => this.toNavbarItem(item, true));
1215
+ }, ...(ngDevMode ? [{ debugName: "visibleNavItems" }] : []));
1216
+ moreNavItems = computed(() => {
1217
+ const items = this.navItems()?.navItems ?? [];
1218
+ const start = this.showSearchBar() ? 2 : 5;
1219
+ return items.slice(start).map(item => this.toNavbarItem(item, true));
1220
+ }, ...(ngDevMode ? [{ debugName: "moreNavItems" }] : []));
1221
+ mobileNavItems = computed(() => (this.navItems()?.navItems ?? []).map(item => this.toNavbarItem(item, false)), ...(ngDevMode ? [{ debugName: "mobileNavItems" }] : []));
1222
+ getUserMenuItems = computed(() => this.userMenuItems().map(item => this.toNavbarItem(item, false)), ...(ngDevMode ? [{ debugName: "getUserMenuItems" }] : []));
1223
+ // ==============================================
1224
+ // Private Helper Methods
1225
+ // ==============================================
1226
+ toNavbarItem(item, childrenOpenWindow = false) {
1227
+ const routerLinkActive = this.navItems()?.routerLinkActive;
1228
+ const colorClass = this.navItems()?.colorClass;
1229
+ return {
1230
+ ...item,
1231
+ colorClass: item.colorClass ?? colorClass,
1232
+ routerLinkActive: item.routerLinkActive ?? routerLinkActive,
1233
+ childrenOpenWindow,
1234
+ children: item.children?.map(child => this.toNavbarItem(child, childrenOpenWindow)) ?? []
1235
+ };
1236
+ }
1237
+ // ==============================================
1238
+ // Event Handlers
1239
+ // ==============================================
1240
+ onSearchSubmit() {
1241
+ this.searchSubmittedEv.emit(this.searchValue());
1242
+ }
1243
+ onLogin() {
1244
+ this.loginClickedEv.emit();
1245
+ }
1246
+ onSignup() {
1247
+ this.signupClickedEv.emit();
1248
+ }
1249
+ toggleMobileMenu() {
1250
+ this.isMobileMenuOpen.update(value => !value);
1251
+ }
1252
+ toggleUserMenu() {
1253
+ this.isUserMenuOpen.update(value => !value);
1254
+ }
1255
+ closeAllMenus() {
1256
+ this.isMobileMenuOpen.set(false);
1257
+ this.isUserMenuOpen.set(false);
1258
+ this.isMoreOpen.set(false);
1259
+ }
1260
+ itemClicked(event) {
1261
+ this.anyItemClickedEv.emit(event);
1262
+ this.closeAllMenus();
1263
+ }
1264
+ // ==============================================
1265
+ // Lifecycle Hooks
1266
+ // ==============================================
1267
+ resizeObserver;
1268
+ ngOnInit() {
1269
+ this.resizeObserver = new ResizeObserver(() => {
1270
+ if (window.innerWidth >= 1024) {
1271
+ this.isMobileMenuOpen.set(false);
1272
+ }
1273
+ });
1274
+ this.resizeObserver.observe(document.body);
1275
+ }
1276
+ ngOnDestroy() {
1277
+ this.resizeObserver.disconnect();
1278
+ }
1279
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Navbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
1280
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Navbar, isStandalone: true, selector: "ZS-navbar", inputs: { fixed: { classPropertyName: "fixed", publicName: "fixed", isSignal: true, isRequired: false, transformFunction: null }, logoUrl: { classPropertyName: "logoUrl", publicName: "logoUrl", isSignal: true, isRequired: false, transformFunction: null }, siteNameConfig: { classPropertyName: "siteNameConfig", publicName: "siteNameConfig", isSignal: true, isRequired: false, transformFunction: null }, authButtons: { classPropertyName: "authButtons", publicName: "authButtons", isSignal: true, isRequired: false, transformFunction: null }, showUserSection: { classPropertyName: "showUserSection", publicName: "showUserSection", isSignal: true, isRequired: false, transformFunction: null }, showSearchBar: { classPropertyName: "showSearchBar", publicName: "showSearchBar", isSignal: true, isRequired: false, transformFunction: null }, navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, isLoggedIn: { classPropertyName: "isLoggedIn", publicName: "isLoggedIn", isSignal: true, isRequired: false, transformFunction: null }, userProfile: { classPropertyName: "userProfile", publicName: "userProfile", isSignal: true, isRequired: false, transformFunction: null }, userMenuItems: { classPropertyName: "userMenuItems", publicName: "userMenuItems", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, searchValue: { classPropertyName: "searchValue", publicName: "searchValue", isSignal: true, isRequired: false, transformFunction: null }, isMobileMenuOpen: { classPropertyName: "isMobileMenuOpen", publicName: "isMobileMenuOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loginClickedEv: "loginClickedEv", signupClickedEv: "signupClickedEv", searchSubmittedEv: "searchSubmittedEv", anyItemClickedEv: "anyItemClickedEv", searchValue: "searchValueChange", isMobileMenuOpen: "isMobileMenuOpenChange" }, ngImport: i0, template: "<!-- ========================= Fixed Navbar Container ========================= -->\n\n<nav\n class=\"zs:bg-white zs:dark:bg-[#18202F] zs:top-0 zs:left-0 zs:right-0 {{ zIndices.navbar }} zs:shadow-md zs:dark:shadow-gray-400/30\"\n [ngClass]=\"fixed() ? 'zs:fixed' : 'zs:relative'\"\n role=\"navigation\"\n aria-label=\"Main Navigation\"\n>\n <div class=\"zs:max-w-7xl zs:mx-auto zs:px-4 zs:sm:px-6 zs:lg:px-8\">\n <div class=\"zs:flex zs:justify-between zs:h-16 zs:gap-4\">\n\n <!-- ========================= Logo & Site Name ========================= -->\n <div class=\"zs:flex zs:items-center\">\n @if (logoUrl() || siteNameConfig()) {\n <a\n [routerLink]=\"[siteNameConfig()?.routerLink ?? '/']\"\n class=\"zs:shrink-0 zs:flex zs:items-center zs:gap-2 zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-blue-500\"\n aria-label=\"Homepage\"\n >\n @if (logoUrl()) {\n <img\n [src]=\"logoUrl()\"\n alt=\"{{ siteNameConfig()?.siteName ?? 'Site Logo' }}\"\n class=\"zs:h-8 zs:w-auto\"\n />\n }\n @if (siteNameConfig()) {\n <span\n [class]=\"siteNameConfig()?.siteNameColorClass ?? 'zs:text-gray-800 zs:dark:text-gray-100'\"\n class=\"zs:font-bold zs:text-xl\"\n >\n {{ siteNameConfig()?.siteName }}\n </span>\n }\n </a>\n }\n </div>\n\n <!-- ========================= Desktop Navigation ========================= -->\n <div class=\"zs:hidden zs:lg:flex zs:items-center zs:gap-4\" role=\"menubar\">\n @for (item of visibleNavItems(); track $index) {\n <ZS-nav-item\n [item]=\"item\"\n [collectionName]=\"'navItems'\"\n (anyItemClickedEv)=\"itemClicked($event)\"\n role=\"menuitem\"\n />\n }\n\n @if (moreNavItems().length > 0) {\n <div class=\"zs:relative\" role=\"none\">\n <button\n (click)=\"isMoreOpen.set(!isMoreOpen())\"\n [attr.aria-expanded]=\"isMoreOpen()\"\n aria-haspopup=\"true\"\n aria-controls=\"more-menu\"\n class=\"zs:px-3 zs:py-2 zs:text-sm zs:font-bold zs:text-blue-600 zs:dark:text-blue-400 zs:hover:text-blue-700 zs:dark:hover:text-blue-300 zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-blue-500\"\n >\n More\n <i class=\"fas fa-chevron-down zs:ml-1 zs:text-xs zs:text-gray-500 zs:dark:text-gray-400\"></i>\n </button>\n <div\n id=\"more-menu\"\n [ngClass]=\"{ 'zs:block': isMoreOpen(), 'zs:hidden': !isMoreOpen() }\"\n class=\"zs:absolute zs:bg-white zs:dark:bg-[#18202F] shadow-md-all shadow-md-all-night zs:rounded-md zs:mt-1 zs:py-1 zs:min-w-48 z-200\"\n role=\"menu\"\n >\n @for (item of moreNavItems(); track $index) {\n <ZS-nav-item\n [item]=\"item\"\n [collectionName]=\"'navItems'\"\n (anyItemClickedEv)=\"itemClicked($event)\"\n role=\"menuitem\"\n />\n }\n </div>\n </div>\n }\n </div>\n\n <!-- ========================= Right Controls ========================= -->\n <div class=\"zs:flex zs:items-center zs:gap-3\">\n\n <!-- Search Bar (Desktop) -->\n @if (showSearchBar()) {\n <div class=\"zs:hidden zs:lg:block\" role=\"search\">\n <div class=\"zs:relative\">\n <label for=\"desktop-search\" class=\"sr-only\">Search</label>\n <input\n id=\"desktop-search\"\n type=\"text\"\n [value]=\"searchValue()\"\n (input)=\"searchValue.set($any($event.target).value)\"\n (keyup.enter)=\"onSearchSubmit()\"\n [placeholder]=\"searchPlaceholder()\"\n class=\"zs:bg-gray-100 zs:dark:bg-gray-700 zs:text-gray-800 zs:dark:text-gray-200 zs:rounded-full zs:py-2 zs:px-4 zs:w-64\n zs:transition-all zs:duration-200 zs:focus:outline-hidden zs:focus:ring-2 zs:focus:ring-blue-500\n zs:focus:bg-white zs:dark:focus:bg-gray-600 zs:shadow-sm zs:dark:shadow-gray-500/40\n zs:hover:shadow-md zs:dark:hover:shadow-gray-500/50\"\n />\n <button\n (click)=\"onSearchSubmit()\"\n class=\"zs:absolute zs:py-1 zs:px-2 zs:right-2 zs:top-1/2 zs:-translate-y-1/2 zs:text-gray-500 zs:dark:text-gray-400\n zs:hover:text-gray-700 zs:dark:hover:text-gray-300 zs:transition-colors zs:duration-200\"\n aria-label=\"Submit search\"\n >\n <i class=\"fas fa-search\"></i>\n </button>\n </div>\n </div>\n }\n\n <!-- User Profile Dropdown -->\n @if (isLoggedIn() && userProfile() && showUserSection()) {\n <div class=\"zs:relative\">\n <button\n (click)=\"toggleUserMenu()\"\n class=\"zs:flex zs:items-center zs:gap-2 zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-blue-500\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"isUserMenuOpen()\"\n aria-controls=\"user-menu\"\n aria-label=\"User menu\"\n >\n @if (userProfile()?.imageUrl) {\n <img\n [src]=\"userProfile()?.imageUrl\"\n alt=\"{{ userProfile()?.name ?? 'User profile image' }}\"\n class=\"zs:h-8 zs:w-8 zs:rounded-full zs:object-cover\"\n />\n } @else {\n <div\n class=\"zs:h-8 zs:w-8 zs:rounded-full zs:bg-blue-500 zs:flex zs:items-center zs:justify-center zs:text-white zs:font-semibold\"\n aria-hidden=\"true\"\n >\n {{ userProfile()?.name?.charAt(0) || 'U' }}\n </div>\n }\n <i class=\"fas fa-chevron-down zs:text-xs zs:text-gray-600 zs:dark:text-gray-400\n zs:hover:text-gray-700 zs:dark:hover:text-gray-300\"></i>\n </button>\n\n @if (isUserMenuOpen() && userMenuItems().length > 0) {\n <div\n id=\"user-menu\"\n class=\"zs:absolute zs:right-0 zs:mt-2.5 zs:min-w-56 zs:bg-white zs:dark:bg-gray-800 zs:rounded-md\n shadow-md-all shadow-md-all-night zs:pt-2 z-400\"\n role=\"menu\"\n >\n <!-- User Info Section -->\n <div class=\"zs:px-4 zs:py-2 zs:border-b zs:border-gray-200 zs:dark:border-gray-700\">\n @if (userProfile()?.name) {\n <div class=\"zs:font-semibold zs:text-gray-900 zs:dark:text-gray-100\">\n {{ userProfile()?.name }}\n </div>\n }\n @if (userProfile()?.username) {\n <div class=\"zs:text-sm zs:text-gray-600 zs:dark:text-gray-400\">\n @{{ userProfile()?.username }}\n </div>\n }\n @if (userProfile()?.email) {\n <div class=\"zs:text-sm zs:text-gray-500 zs:dark:text-gray-400\">\n {{ userProfile()?.email }}\n </div>\n }\n </div>\n\n <!-- Menu Items -->\n @if (userMenuItems().length > 0) {\n <div class=\"zs:py-1\">\n @for (item of getUserMenuItems(); track $index) {\n <ZS-nav-item\n [item]=\"item\"\n [collectionName]=\"'MenuItems'\"\n (anyItemClickedEv)=\"itemClicked($event)\"\n role=\"menuitem\"\n />\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Auth Buttons -->\n @else if (authButtons().showAuthButtons && !isLoggedIn()) {\n <div class=\"zs:hidden zs:lg:flex zs:items-center zs:gap-3\" role=\"group\" aria-label=\"Authentication\">\n <ZS-button\n [btnStyle]=\"authButtons().login?.btnStyle ?? 'secondary'\"\n [variant]=\"authButtons().login?.variant ?? 'outline'\"\n [size]=\"authButtons().login?.size ?? 'md'\"\n [icon]=\"authButtons().login?.icon ?? ''\"\n (clickedEv)=\"onLogin()\"\n >\n Login\n </ZS-button>\n\n <ZS-button\n [btnStyle]=\"authButtons().signup?.btnStyle ?? 'primary'\"\n [variant]=\"authButtons().signup?.variant ?? 'solid'\"\n [size]=\"authButtons().signup?.size ?? 'md'\"\n [icon]=\"authButtons().signup?.icon ?? ''\"\n (clickedEv)=\"onSignup()\"\n >\n Sign Up\n </ZS-button>\n </div>\n }\n\n <!-- Mobile Menu Toggle -->\n <div class=\"zs:lg:hidden\">\n <button\n (click)=\"toggleMobileMenu()\"\n class=\"zs:p-2 zs:rounded-md zs:text-gray-600 zs:dark:text-gray-300\n zs:hover:text-gray-900 zs:dark:hover:text-gray-100\n zs:focus:outline-hidden zs:focus:ring-2 zs:focus:ring-inset\n zs:focus:ring-gray-200 zs:dark:focus:ring-gray-700\"\n aria-controls=\"mobile-menu\"\n aria-expanded=\"{{ isMobileMenuOpen() }}\"\n aria-label=\"Toggle mobile menu\"\n >\n <i class=\"fas fa-bars zs:px-1\"></i>\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <!-- ========================= Mobile Menu ========================= -->\n @if (isMobileMenuOpen()) {\n <div\n id=\"mobile-menu\"\n class=\"zs:lg:hidden zs:bg-white zs:dark:bg-[#18202F] \n zs:[box-shadow:0_8px_0_0_#364153] zs:dark:[box-shadow:0_8px_0_0_black]\"\n role=\"menu\"\n aria-label=\"Mobile navigation\"\n >\n <div class=\"zs:px-2 zs:pt-2 zs:pb-3 zs:flex zs:flex-col zs:gap-1 zs:sm:px-3\">\n\n <!-- Navigation Items -->\n <div class=\"zs:max-h-96 zs:overflow-y-auto\">\n @for (item of mobileNavItems(); track $index) {\n <ZS-nav-item\n [item]=\"item\"\n [collectionName]=\"'navItems'\"\n (anyItemClickedEv)=\"itemClicked($event)\"\n role=\"menuitem\"\n />\n }\n </div>\n\n <!-- Search Bar (Mobile) -->\n @if (showSearchBar()) {\n <div class=\"zs:px-3 zs:py-2\" role=\"search\">\n <label for=\"mobile-search\" class=\"sr-only\">Search</label>\n <div class=\"zs:relative\">\n <input\n id=\"mobile-search\"\n type=\"text\"\n [value]=\"searchValue()\"\n (input)=\"searchValue.set($any($event.target).value)\"\n (keyup.enter)=\"onSearchSubmit()\"\n [placeholder]=\"searchPlaceholder()\"\n class=\"zs:w-full zs:bg-gray-100 zs:dark:bg-gray-700 zs:text-gray-800 zs:dark:text-gray-200\n zs:rounded-full zs:py-2 zs:px-4 zs:transition-all zs:duration-200 zs:focus:outline-hidden\n zs:focus:ring-2 zs:focus:ring-blue-500 zs:focus:bg-white zs:dark:focus:bg-gray-600\n zs:shadow-sm zs:dark:shadow-gray-500/40 zs:hover:shadow-md zs:dark:hover:shadow-gray-500/50\"\n />\n <button\n (click)=\"onSearchSubmit()\"\n class=\"zs:absolute zs:py-1 zs:px-2 zs:right-2 zs:top-1/2 zs:-translate-y-1/2 zs:text-gray-500 zs:dark:text-gray-400\n zs:hover:text-gray-700 zs:dark:hover:text-gray-300 zs:transition-colors zs:duration-200\"\n aria-label=\"Submit search\"\n >\n <i class=\"fas fa-search\"></i>\n </button>\n </div>\n </div>\n }\n\n <!-- Auth Buttons (Mobile) -->\n @if (authButtons().showAuthButtons && !isLoggedIn()) {\n <div\n class=\"zs:pt-3 zs:pb-4 zs:border-t zs:border-gray-300 zs:dark:border-gray-500\n zs:flex zs:flex-wrap zs:justify-start zs:items-center zs:gap-2\"\n role=\"group\"\n aria-label=\"Authentication (mobile)\"\n >\n <ZS-button\n [btnStyle]=\"authButtons().login?.btnStyle ?? 'secondary'\"\n [variant]=\"authButtons().login?.variant ?? 'outline'\"\n [size]=\"authButtons().login?.size ?? 'md'\"\n [icon]=\"authButtons().login?.icon ?? ''\"\n (clickedEv)=\"onLogin(); isMobileMenuOpen.set(false)\"\n >\n Login\n </ZS-button>\n\n <ZS-button\n [btnStyle]=\"authButtons().signup?.btnStyle ?? 'primary'\"\n [variant]=\"authButtons().signup?.variant ?? 'solid'\"\n [size]=\"authButtons().signup?.size ?? 'md'\"\n [icon]=\"authButtons().signup?.icon ?? ''\"\n (clickedEv)=\"onSignup(); isMobileMenuOpen.set(false)\"\n >\n Sign Up\n </ZS-button>\n </div>\n }\n </div>\n </div>\n }\n</nav>\n\n<!-- ========================= Spacer for Fixed Navbar ========================= -->\n@if (fixed()) {\n <div class=\"zs:h-16\" aria-hidden=\"true\"></div>\n}\n\n<!-- ========================= Overlay for Menu Closing ========================= -->\n@if (isMobileMenuOpen()) {\n <div\n class=\"zs:fixed zs:inset-0 zs:bg-gray-700 zs:dark:bg-black z-700\"\n (click)=\"closeAllMenus()\"\n aria-hidden=\"true\"\n ></div>\n}\n", styles: [":host{display:block}body{font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}a,button{transition:all .2s ease-in-out}img{object-fit:cover}.shadow-lg{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d}@media (max-width: 768px){.rtl\\:space-x-reverse{margin-right:0;margin-left:.75rem}}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: NavItem, selector: "ZS-nav-item", inputs: ["item", "collectionName"], outputs: ["anyItemClickedEv"] }, { kind: "component", type: Button, selector: "ZS-button", inputs: ["Id", "btnStyle", "variant", "size", "disabled", "icon", "type"], outputs: ["clickedEv"] }] });
1281
+ }
1282
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Navbar, decorators: [{
1283
+ type: Component,
1284
+ args: [{ selector: 'ZS-navbar', imports: [RouterModule, CommonModule, NavItem, Button], template: "<!-- ========================= Fixed Navbar Container ========================= -->\n\n<nav\n class=\"zs:bg-white zs:dark:bg-[#18202F] zs:top-0 zs:left-0 zs:right-0 {{ zIndices.navbar }} zs:shadow-md zs:dark:shadow-gray-400/30\"\n [ngClass]=\"fixed() ? 'zs:fixed' : 'zs:relative'\"\n role=\"navigation\"\n aria-label=\"Main Navigation\"\n>\n <div class=\"zs:max-w-7xl zs:mx-auto zs:px-4 zs:sm:px-6 zs:lg:px-8\">\n <div class=\"zs:flex zs:justify-between zs:h-16 zs:gap-4\">\n\n <!-- ========================= Logo & Site Name ========================= -->\n <div class=\"zs:flex zs:items-center\">\n @if (logoUrl() || siteNameConfig()) {\n <a\n [routerLink]=\"[siteNameConfig()?.routerLink ?? '/']\"\n class=\"zs:shrink-0 zs:flex zs:items-center zs:gap-2 zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-blue-500\"\n aria-label=\"Homepage\"\n >\n @if (logoUrl()) {\n <img\n [src]=\"logoUrl()\"\n alt=\"{{ siteNameConfig()?.siteName ?? 'Site Logo' }}\"\n class=\"zs:h-8 zs:w-auto\"\n />\n }\n @if (siteNameConfig()) {\n <span\n [class]=\"siteNameConfig()?.siteNameColorClass ?? 'zs:text-gray-800 zs:dark:text-gray-100'\"\n class=\"zs:font-bold zs:text-xl\"\n >\n {{ siteNameConfig()?.siteName }}\n </span>\n }\n </a>\n }\n </div>\n\n <!-- ========================= Desktop Navigation ========================= -->\n <div class=\"zs:hidden zs:lg:flex zs:items-center zs:gap-4\" role=\"menubar\">\n @for (item of visibleNavItems(); track $index) {\n <ZS-nav-item\n [item]=\"item\"\n [collectionName]=\"'navItems'\"\n (anyItemClickedEv)=\"itemClicked($event)\"\n role=\"menuitem\"\n />\n }\n\n @if (moreNavItems().length > 0) {\n <div class=\"zs:relative\" role=\"none\">\n <button\n (click)=\"isMoreOpen.set(!isMoreOpen())\"\n [attr.aria-expanded]=\"isMoreOpen()\"\n aria-haspopup=\"true\"\n aria-controls=\"more-menu\"\n class=\"zs:px-3 zs:py-2 zs:text-sm zs:font-bold zs:text-blue-600 zs:dark:text-blue-400 zs:hover:text-blue-700 zs:dark:hover:text-blue-300 zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-blue-500\"\n >\n More\n <i class=\"fas fa-chevron-down zs:ml-1 zs:text-xs zs:text-gray-500 zs:dark:text-gray-400\"></i>\n </button>\n <div\n id=\"more-menu\"\n [ngClass]=\"{ 'zs:block': isMoreOpen(), 'zs:hidden': !isMoreOpen() }\"\n class=\"zs:absolute zs:bg-white zs:dark:bg-[#18202F] shadow-md-all shadow-md-all-night zs:rounded-md zs:mt-1 zs:py-1 zs:min-w-48 z-200\"\n role=\"menu\"\n >\n @for (item of moreNavItems(); track $index) {\n <ZS-nav-item\n [item]=\"item\"\n [collectionName]=\"'navItems'\"\n (anyItemClickedEv)=\"itemClicked($event)\"\n role=\"menuitem\"\n />\n }\n </div>\n </div>\n }\n </div>\n\n <!-- ========================= Right Controls ========================= -->\n <div class=\"zs:flex zs:items-center zs:gap-3\">\n\n <!-- Search Bar (Desktop) -->\n @if (showSearchBar()) {\n <div class=\"zs:hidden zs:lg:block\" role=\"search\">\n <div class=\"zs:relative\">\n <label for=\"desktop-search\" class=\"sr-only\">Search</label>\n <input\n id=\"desktop-search\"\n type=\"text\"\n [value]=\"searchValue()\"\n (input)=\"searchValue.set($any($event.target).value)\"\n (keyup.enter)=\"onSearchSubmit()\"\n [placeholder]=\"searchPlaceholder()\"\n class=\"zs:bg-gray-100 zs:dark:bg-gray-700 zs:text-gray-800 zs:dark:text-gray-200 zs:rounded-full zs:py-2 zs:px-4 zs:w-64\n zs:transition-all zs:duration-200 zs:focus:outline-hidden zs:focus:ring-2 zs:focus:ring-blue-500\n zs:focus:bg-white zs:dark:focus:bg-gray-600 zs:shadow-sm zs:dark:shadow-gray-500/40\n zs:hover:shadow-md zs:dark:hover:shadow-gray-500/50\"\n />\n <button\n (click)=\"onSearchSubmit()\"\n class=\"zs:absolute zs:py-1 zs:px-2 zs:right-2 zs:top-1/2 zs:-translate-y-1/2 zs:text-gray-500 zs:dark:text-gray-400\n zs:hover:text-gray-700 zs:dark:hover:text-gray-300 zs:transition-colors zs:duration-200\"\n aria-label=\"Submit search\"\n >\n <i class=\"fas fa-search\"></i>\n </button>\n </div>\n </div>\n }\n\n <!-- User Profile Dropdown -->\n @if (isLoggedIn() && userProfile() && showUserSection()) {\n <div class=\"zs:relative\">\n <button\n (click)=\"toggleUserMenu()\"\n class=\"zs:flex zs:items-center zs:gap-2 zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-blue-500\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"isUserMenuOpen()\"\n aria-controls=\"user-menu\"\n aria-label=\"User menu\"\n >\n @if (userProfile()?.imageUrl) {\n <img\n [src]=\"userProfile()?.imageUrl\"\n alt=\"{{ userProfile()?.name ?? 'User profile image' }}\"\n class=\"zs:h-8 zs:w-8 zs:rounded-full zs:object-cover\"\n />\n } @else {\n <div\n class=\"zs:h-8 zs:w-8 zs:rounded-full zs:bg-blue-500 zs:flex zs:items-center zs:justify-center zs:text-white zs:font-semibold\"\n aria-hidden=\"true\"\n >\n {{ userProfile()?.name?.charAt(0) || 'U' }}\n </div>\n }\n <i class=\"fas fa-chevron-down zs:text-xs zs:text-gray-600 zs:dark:text-gray-400\n zs:hover:text-gray-700 zs:dark:hover:text-gray-300\"></i>\n </button>\n\n @if (isUserMenuOpen() && userMenuItems().length > 0) {\n <div\n id=\"user-menu\"\n class=\"zs:absolute zs:right-0 zs:mt-2.5 zs:min-w-56 zs:bg-white zs:dark:bg-gray-800 zs:rounded-md\n shadow-md-all shadow-md-all-night zs:pt-2 z-400\"\n role=\"menu\"\n >\n <!-- User Info Section -->\n <div class=\"zs:px-4 zs:py-2 zs:border-b zs:border-gray-200 zs:dark:border-gray-700\">\n @if (userProfile()?.name) {\n <div class=\"zs:font-semibold zs:text-gray-900 zs:dark:text-gray-100\">\n {{ userProfile()?.name }}\n </div>\n }\n @if (userProfile()?.username) {\n <div class=\"zs:text-sm zs:text-gray-600 zs:dark:text-gray-400\">\n @{{ userProfile()?.username }}\n </div>\n }\n @if (userProfile()?.email) {\n <div class=\"zs:text-sm zs:text-gray-500 zs:dark:text-gray-400\">\n {{ userProfile()?.email }}\n </div>\n }\n </div>\n\n <!-- Menu Items -->\n @if (userMenuItems().length > 0) {\n <div class=\"zs:py-1\">\n @for (item of getUserMenuItems(); track $index) {\n <ZS-nav-item\n [item]=\"item\"\n [collectionName]=\"'MenuItems'\"\n (anyItemClickedEv)=\"itemClicked($event)\"\n role=\"menuitem\"\n />\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Auth Buttons -->\n @else if (authButtons().showAuthButtons && !isLoggedIn()) {\n <div class=\"zs:hidden zs:lg:flex zs:items-center zs:gap-3\" role=\"group\" aria-label=\"Authentication\">\n <ZS-button\n [btnStyle]=\"authButtons().login?.btnStyle ?? 'secondary'\"\n [variant]=\"authButtons().login?.variant ?? 'outline'\"\n [size]=\"authButtons().login?.size ?? 'md'\"\n [icon]=\"authButtons().login?.icon ?? ''\"\n (clickedEv)=\"onLogin()\"\n >\n Login\n </ZS-button>\n\n <ZS-button\n [btnStyle]=\"authButtons().signup?.btnStyle ?? 'primary'\"\n [variant]=\"authButtons().signup?.variant ?? 'solid'\"\n [size]=\"authButtons().signup?.size ?? 'md'\"\n [icon]=\"authButtons().signup?.icon ?? ''\"\n (clickedEv)=\"onSignup()\"\n >\n Sign Up\n </ZS-button>\n </div>\n }\n\n <!-- Mobile Menu Toggle -->\n <div class=\"zs:lg:hidden\">\n <button\n (click)=\"toggleMobileMenu()\"\n class=\"zs:p-2 zs:rounded-md zs:text-gray-600 zs:dark:text-gray-300\n zs:hover:text-gray-900 zs:dark:hover:text-gray-100\n zs:focus:outline-hidden zs:focus:ring-2 zs:focus:ring-inset\n zs:focus:ring-gray-200 zs:dark:focus:ring-gray-700\"\n aria-controls=\"mobile-menu\"\n aria-expanded=\"{{ isMobileMenuOpen() }}\"\n aria-label=\"Toggle mobile menu\"\n >\n <i class=\"fas fa-bars zs:px-1\"></i>\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <!-- ========================= Mobile Menu ========================= -->\n @if (isMobileMenuOpen()) {\n <div\n id=\"mobile-menu\"\n class=\"zs:lg:hidden zs:bg-white zs:dark:bg-[#18202F] \n zs:[box-shadow:0_8px_0_0_#364153] zs:dark:[box-shadow:0_8px_0_0_black]\"\n role=\"menu\"\n aria-label=\"Mobile navigation\"\n >\n <div class=\"zs:px-2 zs:pt-2 zs:pb-3 zs:flex zs:flex-col zs:gap-1 zs:sm:px-3\">\n\n <!-- Navigation Items -->\n <div class=\"zs:max-h-96 zs:overflow-y-auto\">\n @for (item of mobileNavItems(); track $index) {\n <ZS-nav-item\n [item]=\"item\"\n [collectionName]=\"'navItems'\"\n (anyItemClickedEv)=\"itemClicked($event)\"\n role=\"menuitem\"\n />\n }\n </div>\n\n <!-- Search Bar (Mobile) -->\n @if (showSearchBar()) {\n <div class=\"zs:px-3 zs:py-2\" role=\"search\">\n <label for=\"mobile-search\" class=\"sr-only\">Search</label>\n <div class=\"zs:relative\">\n <input\n id=\"mobile-search\"\n type=\"text\"\n [value]=\"searchValue()\"\n (input)=\"searchValue.set($any($event.target).value)\"\n (keyup.enter)=\"onSearchSubmit()\"\n [placeholder]=\"searchPlaceholder()\"\n class=\"zs:w-full zs:bg-gray-100 zs:dark:bg-gray-700 zs:text-gray-800 zs:dark:text-gray-200\n zs:rounded-full zs:py-2 zs:px-4 zs:transition-all zs:duration-200 zs:focus:outline-hidden\n zs:focus:ring-2 zs:focus:ring-blue-500 zs:focus:bg-white zs:dark:focus:bg-gray-600\n zs:shadow-sm zs:dark:shadow-gray-500/40 zs:hover:shadow-md zs:dark:hover:shadow-gray-500/50\"\n />\n <button\n (click)=\"onSearchSubmit()\"\n class=\"zs:absolute zs:py-1 zs:px-2 zs:right-2 zs:top-1/2 zs:-translate-y-1/2 zs:text-gray-500 zs:dark:text-gray-400\n zs:hover:text-gray-700 zs:dark:hover:text-gray-300 zs:transition-colors zs:duration-200\"\n aria-label=\"Submit search\"\n >\n <i class=\"fas fa-search\"></i>\n </button>\n </div>\n </div>\n }\n\n <!-- Auth Buttons (Mobile) -->\n @if (authButtons().showAuthButtons && !isLoggedIn()) {\n <div\n class=\"zs:pt-3 zs:pb-4 zs:border-t zs:border-gray-300 zs:dark:border-gray-500\n zs:flex zs:flex-wrap zs:justify-start zs:items-center zs:gap-2\"\n role=\"group\"\n aria-label=\"Authentication (mobile)\"\n >\n <ZS-button\n [btnStyle]=\"authButtons().login?.btnStyle ?? 'secondary'\"\n [variant]=\"authButtons().login?.variant ?? 'outline'\"\n [size]=\"authButtons().login?.size ?? 'md'\"\n [icon]=\"authButtons().login?.icon ?? ''\"\n (clickedEv)=\"onLogin(); isMobileMenuOpen.set(false)\"\n >\n Login\n </ZS-button>\n\n <ZS-button\n [btnStyle]=\"authButtons().signup?.btnStyle ?? 'primary'\"\n [variant]=\"authButtons().signup?.variant ?? 'solid'\"\n [size]=\"authButtons().signup?.size ?? 'md'\"\n [icon]=\"authButtons().signup?.icon ?? ''\"\n (clickedEv)=\"onSignup(); isMobileMenuOpen.set(false)\"\n >\n Sign Up\n </ZS-button>\n </div>\n }\n </div>\n </div>\n }\n</nav>\n\n<!-- ========================= Spacer for Fixed Navbar ========================= -->\n@if (fixed()) {\n <div class=\"zs:h-16\" aria-hidden=\"true\"></div>\n}\n\n<!-- ========================= Overlay for Menu Closing ========================= -->\n@if (isMobileMenuOpen()) {\n <div\n class=\"zs:fixed zs:inset-0 zs:bg-gray-700 zs:dark:bg-black z-700\"\n (click)=\"closeAllMenus()\"\n aria-hidden=\"true\"\n ></div>\n}\n", styles: [":host{display:block}body{font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}a,button{transition:all .2s ease-in-out}img{object-fit:cover}.shadow-lg{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d}@media (max-width: 768px){.rtl\\:space-x-reverse{margin-right:0;margin-left:.75rem}}\n"] }]
1285
+ }], propDecorators: { fixed: [{ type: i0.Input, args: [{ isSignal: true, alias: "fixed", required: false }] }], logoUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "logoUrl", required: false }] }], siteNameConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "siteNameConfig", required: false }] }], authButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "authButtons", required: false }] }], showUserSection: [{ type: i0.Input, args: [{ isSignal: true, alias: "showUserSection", required: false }] }], showSearchBar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearchBar", required: false }] }], navItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "navItems", required: false }] }], isLoggedIn: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoggedIn", required: false }] }], userProfile: [{ type: i0.Input, args: [{ isSignal: true, alias: "userProfile", required: false }] }], userMenuItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "userMenuItems", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], loginClickedEv: [{ type: i0.Output, args: ["loginClickedEv"] }], signupClickedEv: [{ type: i0.Output, args: ["signupClickedEv"] }], searchSubmittedEv: [{ type: i0.Output, args: ["searchSubmittedEv"] }], anyItemClickedEv: [{ type: i0.Output, args: ["anyItemClickedEv"] }], searchValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchValue", required: false }] }, { type: i0.Output, args: ["searchValueChange"] }], isMobileMenuOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMobileMenuOpen", required: false }] }, { type: i0.Output, args: ["isMobileMenuOpenChange"] }] } });
1286
+
1287
+ class Page404 {
1288
+ // ========================================================================
1289
+ // Inputs
1290
+ // ========================================================================
1291
+ title = input('Page Not Found', ...(ngDevMode ? [{ debugName: "title" }] : []));
1292
+ message = input('Hmm… this page doesn’t seem to exist. Try checking the URL or going back home.', ...(ngDevMode ? [{ debugName: "message" }] : []));
1293
+ icon = input('fa-ghost', ...(ngDevMode ? [{ debugName: "icon" }] : []));
1294
+ showButton = input(true, ...(ngDevMode ? [{ debugName: "showButton" }] : []));
1295
+ buttonText = input('Go Home', ...(ngDevMode ? [{ debugName: "buttonText" }] : []));
1296
+ routerLink = input('/', ...(ngDevMode ? [{ debugName: "routerLink" }] : []));
1297
+ // ========================================================================
1298
+ // Outputs
1299
+ // ========================================================================
1300
+ onAction = output();
1301
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Page404, deps: [], target: i0.ɵɵFactoryTarget.Component });
1302
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Page404, isStandalone: true, selector: "ZS-page404", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, showButton: { classPropertyName: "showButton", publicName: "showButton", isSignal: true, isRequired: false, transformFunction: null }, buttonText: { classPropertyName: "buttonText", publicName: "buttonText", isSignal: true, isRequired: false, transformFunction: null }, routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onAction: "onAction" }, ngImport: i0, template: "<!-- ========================= Page 404 Container ========================= -->\n<div class=\"zs:flex zs:flex-col zs:items-center zs:justify-center zs:min-h-[90vh]\n zs:text-center zs:text-sky-600 zs:dark:text-sky-500\n zs:transition-colors zs:duration-500 zs:animate-fadeIn\">\n\n <!-- ========================= Icon ========================= --> \n <div class=\"zs:text-[136px] zs:mb-8 animate-float zs:opacity-90 zs:leading-none\">\n <i class=\"fa-solid\" [class]=\"icon()\"></i>\n </div>\n\n <!-- ========================= Title ========================= -->\n <h1 class=\"zs:text-6xl zs:font-black zs:tracking-widest zs:mb-2 \n zs:text-rose-600 zs:dark:text-rose-500 zs:select-none\n zs:drop-shadow-[0_0_8px_rgba(239,68,68,0.5)]\">\n 404\n </h1>\n\n\n <!-- ========================= Message ========================= -->\n <h2 class=\"zs:text-2xl zs:text-rose-600/80 zs:dark:text-rose-800/80 zs:font-semibold zs:mb-4\">\n {{ title() }}\n </h2>\n <p class=\"zs:text-base zs:max-w-md zs:text-gray-600 zs:dark:text-gray-400 zs:mb-8\">\n {{ message() }}\n </p>\n\n <!-- ========================= Button ========================= -->\n @if (showButton()) {\n <a\n [routerLink]=\"routerLink()\"\n (click)=\"onAction.emit()\"\n class=\"zs:px-6 zs:py-2 zs:rounded-full\n zs:bg-sky-500 zs:text-white zs:font-medium zs:shadow-md\n zs:hover:bg-sky-600 zs:hover:shadow-lg zs:flex zs:items-center \n zs:justify-center zs:transition-all zs:duration-300 zs:ease-out \n zs:gap-1.5 zs:dark:bg-sky-600 zs:dark:hover:bg-sky-500\"\n >\n <i class=\"fa-solid fa-house\"></i>\n {{ buttonText() }}\n </a>\n }\n</div>\n", styles: ["@keyframes float{0%{transform:translateY(0)}50%{transform:translateY(-10px)}to{transform:translateY(0)}}.animate-float{animation:float 3s ease-in-out infinite}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
1303
+ }
1304
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Page404, decorators: [{
1305
+ type: Component,
1306
+ args: [{ selector: 'ZS-page404', imports: [RouterModule], template: "<!-- ========================= Page 404 Container ========================= -->\n<div class=\"zs:flex zs:flex-col zs:items-center zs:justify-center zs:min-h-[90vh]\n zs:text-center zs:text-sky-600 zs:dark:text-sky-500\n zs:transition-colors zs:duration-500 zs:animate-fadeIn\">\n\n <!-- ========================= Icon ========================= --> \n <div class=\"zs:text-[136px] zs:mb-8 animate-float zs:opacity-90 zs:leading-none\">\n <i class=\"fa-solid\" [class]=\"icon()\"></i>\n </div>\n\n <!-- ========================= Title ========================= -->\n <h1 class=\"zs:text-6xl zs:font-black zs:tracking-widest zs:mb-2 \n zs:text-rose-600 zs:dark:text-rose-500 zs:select-none\n zs:drop-shadow-[0_0_8px_rgba(239,68,68,0.5)]\">\n 404\n </h1>\n\n\n <!-- ========================= Message ========================= -->\n <h2 class=\"zs:text-2xl zs:text-rose-600/80 zs:dark:text-rose-800/80 zs:font-semibold zs:mb-4\">\n {{ title() }}\n </h2>\n <p class=\"zs:text-base zs:max-w-md zs:text-gray-600 zs:dark:text-gray-400 zs:mb-8\">\n {{ message() }}\n </p>\n\n <!-- ========================= Button ========================= -->\n @if (showButton()) {\n <a\n [routerLink]=\"routerLink()\"\n (click)=\"onAction.emit()\"\n class=\"zs:px-6 zs:py-2 zs:rounded-full\n zs:bg-sky-500 zs:text-white zs:font-medium zs:shadow-md\n zs:hover:bg-sky-600 zs:hover:shadow-lg zs:flex zs:items-center \n zs:justify-center zs:transition-all zs:duration-300 zs:ease-out \n zs:gap-1.5 zs:dark:bg-sky-600 zs:dark:hover:bg-sky-500\"\n >\n <i class=\"fa-solid fa-house\"></i>\n {{ buttonText() }}\n </a>\n }\n</div>\n", styles: ["@keyframes float{0%{transform:translateY(0)}50%{transform:translateY(-10px)}to{transform:translateY(0)}}.animate-float{animation:float 3s ease-in-out infinite}\n"] }]
1307
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], showButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showButton", required: false }] }], buttonText: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonText", required: false }] }], routerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLink", required: false }] }], onAction: [{ type: i0.Output, args: ["onAction"] }] } });
1308
+
1309
+ // ========================================================================
1310
+ // Imports
1311
+ // ========================================================================
1312
+ // ========================================================================
1313
+ // Component Declaration
1314
+ // ========================================================================
1315
+ class Pagination {
1316
+ // ========================================================================
1317
+ // Inputs
1318
+ // ========================================================================
1319
+ /**
1320
+ * Total number of pages (required).
1321
+ */
1322
+ totalPages = input.required(...(ngDevMode ? [{ debugName: "totalPages" }] : []));
1323
+ /**
1324
+ * Current active page (required).
1325
+ */
1326
+ currentPage = input.required(...(ngDevMode ? [{ debugName: "currentPage" }] : []));
1327
+ /**
1328
+ * Whether to display the total items count.
1329
+ */
1330
+ showTotalItems = input(false, ...(ngDevMode ? [{ debugName: "showTotalItems" }] : []));
1331
+ /**
1332
+ * Message shown before the total items count.
1333
+ */
1334
+ totalItemsMessage = input('Total items:', ...(ngDevMode ? [{ debugName: "totalItemsMessage" }] : []));
1335
+ /**
1336
+ * Total number of items (used when `showTotalItems` is true).
1337
+ */
1338
+ totalItems = input(...(ngDevMode ? [undefined, { debugName: "totalItems" }] : []));
1339
+ // ========================================================================
1340
+ // Outputs
1341
+ // ========================================================================
1342
+ /**
1343
+ * Emits the new page number when the user navigates.
1344
+ */
1345
+ pageChangeEv = output();
1346
+ // ========================================================================
1347
+ // Computed Properties
1348
+ // ========================================================================
1349
+ /**
1350
+ * Generates an array of page numbers from 1 to `totalPages`.
1351
+ */
1352
+ pages = computed(() => Array.from({ length: this.totalPages() }, (_, i) => i + 1), ...(ngDevMode ? [{ debugName: "pages" }] : []));
1353
+ // ========================================================================
1354
+ // Event Handlers
1355
+ // ========================================================================
1356
+ /**
1357
+ * Navigates to the specified page if it's within valid range.
1358
+ */
1359
+ goToPage(page) {
1360
+ if (page < 1 || page > this.totalPages())
1361
+ return;
1362
+ this.pageChangeEv.emit(page);
1363
+ }
1364
+ /**
1365
+ * Navigates to the next page.
1366
+ */
1367
+ nextPage() {
1368
+ this.goToPage(this.currentPage() + 1);
1369
+ }
1370
+ /**
1371
+ * Navigates to the previous page.
1372
+ */
1373
+ prevPage() {
1374
+ this.goToPage(this.currentPage() - 1);
1375
+ }
1376
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Pagination, deps: [], target: i0.ɵɵFactoryTarget.Component });
1377
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Pagination, isStandalone: true, selector: "ZS-pagination", inputs: { totalPages: { classPropertyName: "totalPages", publicName: "totalPages", isSignal: true, isRequired: true, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: true, transformFunction: null }, showTotalItems: { classPropertyName: "showTotalItems", publicName: "showTotalItems", isSignal: true, isRequired: false, transformFunction: null }, totalItemsMessage: { classPropertyName: "totalItemsMessage", publicName: "totalItemsMessage", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChangeEv: "pageChangeEv" }, ngImport: i0, template: "<!-- ========================= Top Spacing ========================= -->\n<div class=\"zs:mt-6\"></div>\n\n<!-- ========================= Total Items Count ========================= -->\n@if (showTotalItems()) {\n <div\n class=\"zs:text-center zs:text-sm zs:text-gray-500 zs:dark:text-gray-300 zs:mb-2\"\n role=\"status\"\n aria-live=\"polite\"\n >\n {{ totalItemsMessage() }} {{ totalItems() }}\n </div>\n}\n\n<!-- ========================= Pagination Navigation ========================= -->\n<nav\n class=\"zs:flex zs:flex-wrap zs:justify-center zs:items-center zs:gap-1\"\n role=\"navigation\"\n aria-label=\"Pagination\"\n>\n <!-- ========================= Previous Button ========================= -->\n <button\n type=\"button\"\n class=\"zs:flex zs:items-center zs:justify-center zs:size-9 zs:sm:size-10 \n zs:rounded-lg zs:border zs:border-gray-300 zs:dark:border-gray-600 zs:hover:bg-blue-100 \n zs:dark:hover:bg-blue-700 zs:disabled:opacity-50 zs:disabled:cursor-not-allowed zs:transition-all \n zs:duration-200\"\n (click)=\"prevPage()\"\n [disabled]=\"currentPage() === 1\"\n [attr.aria-disabled]=\"currentPage() === 1\"\n aria-label=\"Go to previous page\"\n >\n <i class=\"fas fa-chevron-left zs:text-gray-600 zs:dark:text-gray-200\" aria-hidden=\"true\"></i>\n <span class=\"sr-only\">Previous</span>\n </button>\n\n <!-- ========================= Page Numbers ========================= -->\n @for (page of pages(); track page) {\n <button\n type=\"button\"\n (click)=\"goToPage(page)\"\n class=\"zs:flex zs:items-center zs:justify-center zs:size-9 zs:sm:size-10 zs:rounded-lg \n zs:border zs:border-gray-300 zs:dark:border-gray-600 zs:hover:bg-blue-100 zs:dark:hover:bg-blue-700 \n zs:transition-all zs:duration-200\"\n [ngClass]=\"{\n 'zs:bg-blue-500 zs:text-white zs:dark:bg-blue-400 zs:dark:text-black': page === currentPage()\n }\"\n [attr.aria-current]=\"page === currentPage() ? 'page' : null\"\n [attr.aria-label]=\"'Go to page ' + page\"\n >\n {{ page }}\n </button>\n }\n\n <!-- ========================= Next Button ========================= -->\n <button\n type=\"button\"\n class=\"zs:flex zs:items-center zs:justify-center zs:size-9 zs:sm:size-10 zs:rounded-lg zs:border \n zs:border-gray-300 zs:dark:border-gray-600 zs:hover:bg-blue-100 zs:dark:hover:bg-blue-700 \n zs:disabled:opacity-50 zs:disabled:cursor-not-allowed zs:transition-all zs:duration-200\"\n (click)=\"nextPage()\"\n [disabled]=\"currentPage() === totalPages()\"\n [attr.aria-disabled]=\"currentPage() === totalPages()\"\n aria-label=\"Go to next page\"\n >\n <i class=\"fas fa-chevron-right zs:text-gray-600 zs:dark:text-gray-200\" aria-hidden=\"true\"></i>\n <span class=\"sr-only\">Next</span>\n </button>\n</nav>", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
1378
+ }
1379
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Pagination, decorators: [{
1380
+ type: Component,
1381
+ args: [{ selector: 'ZS-pagination', imports: [CommonModule], template: "<!-- ========================= Top Spacing ========================= -->\n<div class=\"zs:mt-6\"></div>\n\n<!-- ========================= Total Items Count ========================= -->\n@if (showTotalItems()) {\n <div\n class=\"zs:text-center zs:text-sm zs:text-gray-500 zs:dark:text-gray-300 zs:mb-2\"\n role=\"status\"\n aria-live=\"polite\"\n >\n {{ totalItemsMessage() }} {{ totalItems() }}\n </div>\n}\n\n<!-- ========================= Pagination Navigation ========================= -->\n<nav\n class=\"zs:flex zs:flex-wrap zs:justify-center zs:items-center zs:gap-1\"\n role=\"navigation\"\n aria-label=\"Pagination\"\n>\n <!-- ========================= Previous Button ========================= -->\n <button\n type=\"button\"\n class=\"zs:flex zs:items-center zs:justify-center zs:size-9 zs:sm:size-10 \n zs:rounded-lg zs:border zs:border-gray-300 zs:dark:border-gray-600 zs:hover:bg-blue-100 \n zs:dark:hover:bg-blue-700 zs:disabled:opacity-50 zs:disabled:cursor-not-allowed zs:transition-all \n zs:duration-200\"\n (click)=\"prevPage()\"\n [disabled]=\"currentPage() === 1\"\n [attr.aria-disabled]=\"currentPage() === 1\"\n aria-label=\"Go to previous page\"\n >\n <i class=\"fas fa-chevron-left zs:text-gray-600 zs:dark:text-gray-200\" aria-hidden=\"true\"></i>\n <span class=\"sr-only\">Previous</span>\n </button>\n\n <!-- ========================= Page Numbers ========================= -->\n @for (page of pages(); track page) {\n <button\n type=\"button\"\n (click)=\"goToPage(page)\"\n class=\"zs:flex zs:items-center zs:justify-center zs:size-9 zs:sm:size-10 zs:rounded-lg \n zs:border zs:border-gray-300 zs:dark:border-gray-600 zs:hover:bg-blue-100 zs:dark:hover:bg-blue-700 \n zs:transition-all zs:duration-200\"\n [ngClass]=\"{\n 'zs:bg-blue-500 zs:text-white zs:dark:bg-blue-400 zs:dark:text-black': page === currentPage()\n }\"\n [attr.aria-current]=\"page === currentPage() ? 'page' : null\"\n [attr.aria-label]=\"'Go to page ' + page\"\n >\n {{ page }}\n </button>\n }\n\n <!-- ========================= Next Button ========================= -->\n <button\n type=\"button\"\n class=\"zs:flex zs:items-center zs:justify-center zs:size-9 zs:sm:size-10 zs:rounded-lg zs:border \n zs:border-gray-300 zs:dark:border-gray-600 zs:hover:bg-blue-100 zs:dark:hover:bg-blue-700 \n zs:disabled:opacity-50 zs:disabled:cursor-not-allowed zs:transition-all zs:duration-200\"\n (click)=\"nextPage()\"\n [disabled]=\"currentPage() === totalPages()\"\n [attr.aria-disabled]=\"currentPage() === totalPages()\"\n aria-label=\"Go to next page\"\n >\n <i class=\"fas fa-chevron-right zs:text-gray-600 zs:dark:text-gray-200\" aria-hidden=\"true\"></i>\n <span class=\"sr-only\">Next</span>\n </button>\n</nav>", styles: [":host{display:block}\n"] }]
1382
+ }], propDecorators: { totalPages: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalPages", required: true }] }], currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: true }] }], showTotalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTotalItems", required: false }] }], totalItemsMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItemsMessage", required: false }] }], totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: false }] }], pageChangeEv: [{ type: i0.Output, args: ["pageChangeEv"] }] } });
1383
+
1384
+ // ========================================================================
1385
+ // Imports
1386
+ // ========================================================================
1387
+ // ========================================================================
1388
+ // Component Declaration
1389
+ // ========================================================================
1390
+ class ScrollToTop {
1391
+ zIndices = zIndices;
1392
+ // ========================================================================
1393
+ // Inputs
1394
+ // ========================================================================
1395
+ /**
1396
+ * Determines the horizontal position of the button ('left' or 'right').
1397
+ * Default: 'right'
1398
+ */
1399
+ position = input('right', ...(ngDevMode ? [{ debugName: "position" }] : []));
1400
+ /**
1401
+ * Tailwind CSS class for the circle's color (background ring).
1402
+ */
1403
+ circleColorClass = input('zs:text-gray-400/60 zs:dark:text-gray-600/70 zs:group-hover:brightness-110', ...(ngDevMode ? [{ debugName: "circleColorClass" }] : []));
1404
+ /**
1405
+ * BaseColors class for the arrow and progress indicator color.
1406
+ */
1407
+ arrowProgressColor = input('blue', ...(ngDevMode ? [{ debugName: "arrowProgressColor" }] : []));
1408
+ // ========================================================================
1409
+ // Constants
1410
+ // ========================================================================
1411
+ circleRadius = 22;
1412
+ circleCircumference = 2 * Math.PI * this.circleRadius;
1413
+ // ========================================================================
1414
+ // Internal State
1415
+ // ========================================================================
1416
+ scrollY = signal(0, ...(ngDevMode ? [{ debugName: "scrollY" }] : []));
1417
+ // ========================================================================
1418
+ // Computed Properties
1419
+ // ========================================================================
1420
+ arrowProgressColorClass = computed(() => ColorMapping.get(this.arrowProgressColor())?.text ?? 'zs:text-blue-600', ...(ngDevMode ? [{ debugName: "arrowProgressColorClass" }] : []));
1421
+ /**
1422
+ * Computes the stroke-dashoffset for the progress circle based on scroll position.
1423
+ */
1424
+ progressOffset = computed(() => {
1425
+ const _ = this.scrollY();
1426
+ const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
1427
+ const progress = maxScroll > 0 ? this.scrollY() / maxScroll : 0;
1428
+ return this.circleCircumference * (1 - progress);
1429
+ }, ...(ngDevMode ? [{ debugName: "progressOffset" }] : []));
1430
+ /**
1431
+ * Returns Tailwind classes to position the button horizontally.
1432
+ */
1433
+ positionClass = computed(() => ({
1434
+ 'zs:right-4': this.position() === 'right',
1435
+ 'zs:left-4': this.position() === 'left',
1436
+ }), ...(ngDevMode ? [{ debugName: "positionClass" }] : []));
1437
+ // ========================================================================
1438
+ // Lifecycle Hooks
1439
+ // ========================================================================
1440
+ onScroll(event) {
1441
+ this.scrollY.set(window.scrollY);
1442
+ }
1443
+ // ========================================================================
1444
+ // Event Handlers
1445
+ // ========================================================================
1446
+ scrollToTop() {
1447
+ window.scrollTo({
1448
+ top: 0,
1449
+ behavior: 'smooth',
1450
+ });
1451
+ }
1452
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ScrollToTop, deps: [], target: i0.ɵɵFactoryTarget.Component });
1453
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.9", type: ScrollToTop, isStandalone: true, selector: "ZS-scroll-to-top", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, circleColorClass: { classPropertyName: "circleColorClass", publicName: "circleColorClass", isSignal: true, isRequired: false, transformFunction: null }, arrowProgressColor: { classPropertyName: "arrowProgressColor", publicName: "arrowProgressColor", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "window:scroll": "onScroll($event)" } }, ngImport: i0, template: "<!-- ========================= Scroll-to-Top Container ========================= -->\n<div\n [ngClass]=\"positionClass()\"\n class=\"zs:fixed zs:group zs:bottom-4 {{ zIndices.scrollToTop }} \n zs:transition-opacity zs:duration-300\"\n [style.opacity]=\"scrollY() > 200 ? 1 : 0\"\n>\n <!-- ========================= Scroll Button ========================= -->\n <button\n (click)=\"scrollToTop()\"\n class=\"zs:w-12 zs:h-12 zs:group-hover:scale-105 zs:rounded-full zs:flex zs:items-center \n zs:justify-center zs:relative zs:overflow-hidden zs:focus:outline-hidden\"\n type=\"button\"\n role=\"button\"\n aria-label=\"Scroll to top\"\n title=\"Scroll to top\"\n >\n <!-- ========================= Progress Ring (SVG) ========================= -->\n <svg\n class=\"zs:absolute zs:inset-0 zs:w-full zs:h-full zs:transform zs:-rotate-90\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <!-- Background Circle -->\n <circle\n cx=\"24\"\n cy=\"24\"\n r=\"22\"\n stroke=\"transparent\"\n stroke-width=\"5\"\n fill=\"currentColor\"\n [ngClass]=\"circleColorClass()\"\n />\n\n <!-- Animated Progress Circle -->\n <circle\n cx=\"24\"\n cy=\"24\"\n r=\"22\"\n stroke=\"currentColor\"\n stroke-width=\"5\"\n fill=\"transparent\"\n [attr.stroke-dasharray]=\"circleCircumference\"\n [attr.stroke-dashoffset]=\"progressOffset()\"\n [ngClass]=\"arrowProgressColorClass()\"\n class=\"zs:transition-all zs:duration-300 zs:ease-out\"\n stroke-linecap=\"round\"\n />\n </svg>\n\n <!-- ========================= Up Arrow Icon ========================= -->\n <i\n class=\"fas fa-arrow-up zs:relative zs:z-100 zs:text-lg\"\n [ngClass]=\"arrowProgressColorClass()\"\n aria-hidden=\"true\"\n ></i>\n </button>\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
1454
+ }
1455
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ScrollToTop, decorators: [{
1456
+ type: Component,
1457
+ args: [{ selector: 'ZS-scroll-to-top', imports: [CommonModule], template: "<!-- ========================= Scroll-to-Top Container ========================= -->\n<div\n [ngClass]=\"positionClass()\"\n class=\"zs:fixed zs:group zs:bottom-4 {{ zIndices.scrollToTop }} \n zs:transition-opacity zs:duration-300\"\n [style.opacity]=\"scrollY() > 200 ? 1 : 0\"\n>\n <!-- ========================= Scroll Button ========================= -->\n <button\n (click)=\"scrollToTop()\"\n class=\"zs:w-12 zs:h-12 zs:group-hover:scale-105 zs:rounded-full zs:flex zs:items-center \n zs:justify-center zs:relative zs:overflow-hidden zs:focus:outline-hidden\"\n type=\"button\"\n role=\"button\"\n aria-label=\"Scroll to top\"\n title=\"Scroll to top\"\n >\n <!-- ========================= Progress Ring (SVG) ========================= -->\n <svg\n class=\"zs:absolute zs:inset-0 zs:w-full zs:h-full zs:transform zs:-rotate-90\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <!-- Background Circle -->\n <circle\n cx=\"24\"\n cy=\"24\"\n r=\"22\"\n stroke=\"transparent\"\n stroke-width=\"5\"\n fill=\"currentColor\"\n [ngClass]=\"circleColorClass()\"\n />\n\n <!-- Animated Progress Circle -->\n <circle\n cx=\"24\"\n cy=\"24\"\n r=\"22\"\n stroke=\"currentColor\"\n stroke-width=\"5\"\n fill=\"transparent\"\n [attr.stroke-dasharray]=\"circleCircumference\"\n [attr.stroke-dashoffset]=\"progressOffset()\"\n [ngClass]=\"arrowProgressColorClass()\"\n class=\"zs:transition-all zs:duration-300 zs:ease-out\"\n stroke-linecap=\"round\"\n />\n </svg>\n\n <!-- ========================= Up Arrow Icon ========================= -->\n <i\n class=\"fas fa-arrow-up zs:relative zs:z-100 zs:text-lg\"\n [ngClass]=\"arrowProgressColorClass()\"\n aria-hidden=\"true\"\n ></i>\n </button>\n</div>" }]
1458
+ }], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], circleColorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "circleColorClass", required: false }] }], arrowProgressColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowProgressColor", required: false }] }], onScroll: [{
1459
+ type: HostListener,
1460
+ args: ['window:scroll', ['$event']]
1461
+ }] } });
1462
+
1463
+ // =================================================================================================
1464
+ // Component Definition
1465
+ // =================================================================================================
1466
+ class Spinner {
1467
+ zIndices = zIndices;
1468
+ // =================================================================================================
1469
+ // Inputs
1470
+ // =================================================================================================
1471
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
1472
+ isFloating = input(false, ...(ngDevMode ? [{ debugName: "isFloating" }] : []));
1473
+ color = input('blue', ...(ngDevMode ? [{ debugName: "color" }] : []));
1474
+ withBox = input(false, ...(ngDevMode ? [{ debugName: "withBox" }] : []));
1475
+ boxColorClass = input('zs:bg-gray-300/90 zs:dark:bg-gray-400/80', ...(ngDevMode ? [{ debugName: "boxColorClass" }] : []));
1476
+ type = input('spinner', ...(ngDevMode ? [{ debugName: "type" }] : []));
1477
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
1478
+ // =================================================================================================
1479
+ // Computed Properties
1480
+ // =================================================================================================
1481
+ wrapperClasses = computed(() => this.isFloating()
1482
+ ? `zs:fixed zs:inset-0 zs:flex zs:items-center zs:justify-center
1483
+ ${this.zIndices.spinner} zs:bg-black/50 zs:dark:bg-black/70`
1484
+ : 'zs:flex zs:items-center zs:justify-center', ...(ngDevMode ? [{ debugName: "wrapperClasses" }] : []));
1485
+ boxClasses = computed(() => this.withBox()
1486
+ ? `zs:p-5 zs:rounded-lg zs:shadow-md ${this.boxColorClass()}`
1487
+ : '', ...(ngDevMode ? [{ debugName: "boxClasses" }] : []));
1488
+ spinnerSizeTextClass = computed(() => {
1489
+ const sizes = {
1490
+ sm: 'zs:text-3xl',
1491
+ md: 'zs:text-5xl',
1492
+ lg: 'zs:text-7xl'
1493
+ };
1494
+ return sizes[this.size()];
1495
+ }, ...(ngDevMode ? [{ debugName: "spinnerSizeTextClass" }] : []));
1496
+ spinnerSizeDotsClass = computed(() => {
1497
+ const sizes = {
1498
+ sm: 'zs:size-2',
1499
+ md: 'zs:size-4',
1500
+ lg: 'zs:size-6'
1501
+ };
1502
+ return sizes[this.size()];
1503
+ }, ...(ngDevMode ? [{ debugName: "spinnerSizeDotsClass" }] : []));
1504
+ spinnerSizeBarsClass = (num) => {
1505
+ const sizes = {
1506
+ sm: ['zs:w-1 zs:h-3', 'zs:w-1 zs:h-3.5', 'zs:w-1 zs:h-4'],
1507
+ md: ['zs:w-1.5 zs:h-6', 'zs:w-1.5 zs:h-8', 'zs:w-1.5 zs:h-10'],
1508
+ lg: ['zs:w-2 zs:h-8', 'zs:w-2 zs:h-9', 'zs:w-2 zs:h-10']
1509
+ };
1510
+ return sizes[this.size()][num - 1];
1511
+ };
1512
+ spinnerSizeProClass = computed(() => {
1513
+ const sizes = {
1514
+ sm: 'zs:border-t-3 zs:border-b-3 zs:size-7',
1515
+ md: 'zs:border-t-5 zs:border-b-5 zs:size-12',
1516
+ lg: 'zs:border-t-7 zs:border-b-7 zs:size-18'
1517
+ };
1518
+ return sizes[this.size()];
1519
+ }, ...(ngDevMode ? [{ debugName: "spinnerSizeProClass" }] : []));
1520
+ spinnerSizePulseClass = computed(() => {
1521
+ const sizes = {
1522
+ sm: 'zs:border-3 zs:size-7',
1523
+ md: 'zs:border-5 zs:size-12',
1524
+ lg: 'zs:border-7 zs:size-18'
1525
+ };
1526
+ return sizes[this.size()];
1527
+ }, ...(ngDevMode ? [{ debugName: "spinnerSizePulseClass" }] : []));
1528
+ spinnerSizeDoubleClass = (num) => {
1529
+ const sizes = {
1530
+ sm: { 1: 'zs:border-3 zs:size-7', 2: 'zs:border-3 zs:size-5' },
1531
+ md: { 1: 'zs:border-5 zs:size-12', 2: 'zs:border-5 zs:size-8.5' },
1532
+ lg: { 1: 'zs:border-7 zs:size-18', 2: 'zs:border-7 zs:size-13' }
1533
+ };
1534
+ return sizes[this.size()][num];
1535
+ };
1536
+ bgColor = computed(() => ColorMapping.get(this.color())?.bg, ...(ngDevMode ? [{ debugName: "bgColor" }] : []));
1537
+ borderColor = computed(() => ColorMapping.get(this.color())?.border, ...(ngDevMode ? [{ debugName: "borderColor" }] : []));
1538
+ textColor = computed(() => ColorMapping.get(this.color())?.text, ...(ngDevMode ? [{ debugName: "textColor" }] : []));
1539
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Spinner, deps: [], target: i0.ɵɵFactoryTarget.Component });
1540
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Spinner, isStandalone: true, selector: "ZS-spinner", inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, isFloating: { classPropertyName: "isFloating", publicName: "isFloating", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, withBox: { classPropertyName: "withBox", publicName: "withBox", isSignal: true, isRequired: false, transformFunction: null }, boxColorClass: { classPropertyName: "boxColorClass", publicName: "boxColorClass", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!-- ========================= Loader Wrapper ========================= -->\n@if(loading()) {\n <div \n [class]=\"wrapperClasses()\" \n role=\"status\" \n aria-live=\"polite\" \n aria-busy=\"true\"\n >\n <div [class]=\"boxClasses()\">\n \n @switch (type()) {\n \n <!-- ========================= Spinner ========================= -->\n @case ('spinner') {\n <i \n [ngClass]=\"['fas fa-spinner fa-spin', spinnerSizeTextClass(), textColor()]\" \n aria-hidden=\"true\"\n ></i>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Pro Spinner ========================= -->\n @case ('pro') {\n <div \n [ngClass]=\"['zs:animate-spin zs:rounded-full zs:border-solid', spinnerSizeProClass(), borderColor()]\" \n aria-hidden=\"true\"\n ></div>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Gear ========================= -->\n @case ('gear') {\n <i \n [ngClass]=\"['fas fa-gear fa-spin', spinnerSizeTextClass(), textColor()]\" \n aria-hidden=\"true\"\n ></i>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Fan ========================= -->\n @case ('fan') {\n <i \n [ngClass]=\"['fas fa-fan fa-spin', spinnerSizeTextClass(), textColor()]\" \n aria-hidden=\"true\"\n ></i>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Pulse ========================= -->\n @case ('pulse') {\n <div \n [ngClass]=\"[\n 'zs:animate-spin zs:rounded-full zs:border-solid',\n spinnerSizePulseClass(),\n borderColor(),\n 'zs:border-t-transparent!'\n ]\" \n aria-hidden=\"true\"\n ></div>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Dots ========================= -->\n @case ('dots') {\n <div class=\"zs:flex zs:gap-2 zs:items-center\" aria-hidden=\"true\">\n <span \n class=\"zs:rounded-full zs:animate-bounce\" \n [ngClass]=\"[bgColor(), spinnerSizeDotsClass()]\" \n style=\"animation-delay: 0s\"\n ></span>\n <span \n class=\"zs:rounded-full zs:animate-bounce\" \n [ngClass]=\"[bgColor(), spinnerSizeDotsClass()]\" \n style=\"animation-delay: 0.2s\"\n ></span>\n <span \n class=\"zs:rounded-full zs:animate-bounce\" \n [ngClass]=\"[bgColor(), spinnerSizeDotsClass()]\" \n style=\"animation-delay: 0.4s\"\n ></span>\n </div>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Bars ========================= -->\n @case ('bars') {\n <div class=\"zs:flex zs:gap-1 zs:items-center zs:h-6\" aria-hidden=\"true\">\n <span \n class=\"zs:rounded-sm animate-bar-scale\" \n [ngClass]=\"[bgColor(), spinnerSizeBarsClass(1)]\" \n style=\"animation-delay: 0s\"\n ></span>\n <span \n class=\"zs:rounded-sm animate-bar-scale\" \n [ngClass]=\"[bgColor(), spinnerSizeBarsClass(2)]\" \n style=\"animation-delay: 0.2s\"\n ></span>\n <span \n class=\"zs:rounded-sm animate-bar-scale\" \n [ngClass]=\"[bgColor(), spinnerSizeBarsClass(3)]\" \n style=\"animation-delay: 0.4s\"\n ></span>\n </div>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Double Spinner ========================= -->\n @case ('double') {\n <div class=\"zs:relative\" aria-hidden=\"true\">\n <!-- Outer ring -->\n <div \n [ngClass]=\"[\n 'zs:animate-spin zs:rounded-full zs:border-solid',\n spinnerSizeDoubleClass(1),\n borderColor(), \n 'zs:border-t-transparent!'\n ]\"\n ></div>\n <!-- Inner ring (reversed animation) -->\n <div \n [ngClass]=\"[\n 'zs:animate-spin zs:opacity-75 zs:rounded-full zs:border-solid zs:absolute \n zs:top-1/2 zs:left-1/2 zs:-translate-x-1/2 zs:-translate-y-1/2',\n spinnerSizeDoubleClass(2),\n borderColor(), \n 'zs:border-b-transparent!'\n ]\"\n style=\"animation-direction: reverse;\"\n ></div>\n </div>\n <span class=\"sr-only\">Loading...</span>\n }\n \n }\n \n </div>\n </div>\n}", styles: ["@keyframes bar-scale{0%,to{transform:scaleY(.4)}50%{transform:scaleY(1)}}.animate-bar-scale{animation:bar-scale .9s ease-in-out infinite}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
1541
+ }
1542
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Spinner, decorators: [{
1543
+ type: Component,
1544
+ args: [{ selector: 'ZS-spinner', imports: [CommonModule], template: "<!-- ========================= Loader Wrapper ========================= -->\n@if(loading()) {\n <div \n [class]=\"wrapperClasses()\" \n role=\"status\" \n aria-live=\"polite\" \n aria-busy=\"true\"\n >\n <div [class]=\"boxClasses()\">\n \n @switch (type()) {\n \n <!-- ========================= Spinner ========================= -->\n @case ('spinner') {\n <i \n [ngClass]=\"['fas fa-spinner fa-spin', spinnerSizeTextClass(), textColor()]\" \n aria-hidden=\"true\"\n ></i>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Pro Spinner ========================= -->\n @case ('pro') {\n <div \n [ngClass]=\"['zs:animate-spin zs:rounded-full zs:border-solid', spinnerSizeProClass(), borderColor()]\" \n aria-hidden=\"true\"\n ></div>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Gear ========================= -->\n @case ('gear') {\n <i \n [ngClass]=\"['fas fa-gear fa-spin', spinnerSizeTextClass(), textColor()]\" \n aria-hidden=\"true\"\n ></i>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Fan ========================= -->\n @case ('fan') {\n <i \n [ngClass]=\"['fas fa-fan fa-spin', spinnerSizeTextClass(), textColor()]\" \n aria-hidden=\"true\"\n ></i>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Pulse ========================= -->\n @case ('pulse') {\n <div \n [ngClass]=\"[\n 'zs:animate-spin zs:rounded-full zs:border-solid',\n spinnerSizePulseClass(),\n borderColor(),\n 'zs:border-t-transparent!'\n ]\" \n aria-hidden=\"true\"\n ></div>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Dots ========================= -->\n @case ('dots') {\n <div class=\"zs:flex zs:gap-2 zs:items-center\" aria-hidden=\"true\">\n <span \n class=\"zs:rounded-full zs:animate-bounce\" \n [ngClass]=\"[bgColor(), spinnerSizeDotsClass()]\" \n style=\"animation-delay: 0s\"\n ></span>\n <span \n class=\"zs:rounded-full zs:animate-bounce\" \n [ngClass]=\"[bgColor(), spinnerSizeDotsClass()]\" \n style=\"animation-delay: 0.2s\"\n ></span>\n <span \n class=\"zs:rounded-full zs:animate-bounce\" \n [ngClass]=\"[bgColor(), spinnerSizeDotsClass()]\" \n style=\"animation-delay: 0.4s\"\n ></span>\n </div>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Bars ========================= -->\n @case ('bars') {\n <div class=\"zs:flex zs:gap-1 zs:items-center zs:h-6\" aria-hidden=\"true\">\n <span \n class=\"zs:rounded-sm animate-bar-scale\" \n [ngClass]=\"[bgColor(), spinnerSizeBarsClass(1)]\" \n style=\"animation-delay: 0s\"\n ></span>\n <span \n class=\"zs:rounded-sm animate-bar-scale\" \n [ngClass]=\"[bgColor(), spinnerSizeBarsClass(2)]\" \n style=\"animation-delay: 0.2s\"\n ></span>\n <span \n class=\"zs:rounded-sm animate-bar-scale\" \n [ngClass]=\"[bgColor(), spinnerSizeBarsClass(3)]\" \n style=\"animation-delay: 0.4s\"\n ></span>\n </div>\n <span class=\"sr-only\">Loading...</span>\n }\n \n <!-- ========================= Double Spinner ========================= -->\n @case ('double') {\n <div class=\"zs:relative\" aria-hidden=\"true\">\n <!-- Outer ring -->\n <div \n [ngClass]=\"[\n 'zs:animate-spin zs:rounded-full zs:border-solid',\n spinnerSizeDoubleClass(1),\n borderColor(), \n 'zs:border-t-transparent!'\n ]\"\n ></div>\n <!-- Inner ring (reversed animation) -->\n <div \n [ngClass]=\"[\n 'zs:animate-spin zs:opacity-75 zs:rounded-full zs:border-solid zs:absolute \n zs:top-1/2 zs:left-1/2 zs:-translate-x-1/2 zs:-translate-y-1/2',\n spinnerSizeDoubleClass(2),\n borderColor(), \n 'zs:border-b-transparent!'\n ]\"\n style=\"animation-direction: reverse;\"\n ></div>\n </div>\n <span class=\"sr-only\">Loading...</span>\n }\n \n }\n \n </div>\n </div>\n}", styles: ["@keyframes bar-scale{0%,to{transform:scaleY(.4)}50%{transform:scaleY(1)}}.animate-bar-scale{animation:bar-scale .9s ease-in-out infinite}\n"] }]
1545
+ }], propDecorators: { loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], isFloating: [{ type: i0.Input, args: [{ isSignal: true, alias: "isFloating", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], withBox: [{ type: i0.Input, args: [{ isSignal: true, alias: "withBox", required: false }] }], boxColorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "boxColorClass", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
1546
+
1547
+ // ==============================================
1548
+ // Component Metadata
1549
+ // ==============================================
1550
+ class ThemeToggle {
1551
+ zIndices = zIndices;
1552
+ // ==============================================
1553
+ // Signals
1554
+ // ==============================================
1555
+ currentTheme = signal('light', ...(ngDevMode ? [{ debugName: "currentTheme" }] : []));
1556
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
1557
+ userSelectedTheme = signal(false, ...(ngDevMode ? [{ debugName: "userSelectedTheme" }] : []));
1558
+ // ==============================================
1559
+ // Inputs
1560
+ // ==============================================
1561
+ bodyClass = input('zs:bg-white zs:dark:bg-gray-900 zs:text-gray-900 zs:dark:text-gray-100', ...(ngDevMode ? [{ debugName: "bodyClass" }] : []));
1562
+ // ==============================================
1563
+ // Outputs
1564
+ // ==============================================
1565
+ themeChangeEv = output();
1566
+ // ==============================================
1567
+ // Lifecycle & Side Effects
1568
+ // ==============================================
1569
+ constructor() {
1570
+ // ① تهيئة الثيم
1571
+ const savedTheme = localStorage.getItem('theme');
1572
+ const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)');
1573
+ if (savedTheme) {
1574
+ this.currentTheme.set(savedTheme);
1575
+ this.userSelectedTheme.set(true);
1576
+ }
1577
+ else {
1578
+ this.currentTheme.set(systemPrefersDark.matches ? 'dark' : 'light');
1579
+ }
1580
+ // ② مراقبة تغيّر ثيم النظام (لو المستخدم لم يختر يدويًا)
1581
+ effect((onCleanup) => {
1582
+ const listener = (e) => {
1583
+ if (!this.userSelectedTheme()) {
1584
+ this.currentTheme.set(e.matches ? 'dark' : 'light');
1585
+ }
1586
+ };
1587
+ systemPrefersDark.addEventListener('change', listener);
1588
+ onCleanup(() => systemPrefersDark.removeEventListener('change', listener));
1589
+ });
1590
+ // ③ مزامنة الثيم مع DOM و localStorage
1591
+ effect(() => {
1592
+ const theme = this.currentTheme();
1593
+ document.documentElement.classList.toggle('dark', theme === 'dark');
1594
+ document.body.className = this.bodyClass();
1595
+ if (this.userSelectedTheme()) {
1596
+ localStorage.setItem('theme', theme);
1597
+ }
1598
+ });
1599
+ }
1600
+ // ==============================================
1601
+ // Component Methods
1602
+ // ==============================================
1603
+ toggleOpen() {
1604
+ this.isOpen.set(!this.isOpen());
1605
+ }
1606
+ setTheme(theme) {
1607
+ this.currentTheme.set(theme);
1608
+ this.isOpen.set(false);
1609
+ this.themeChangeEv.emit(theme);
1610
+ this.userSelectedTheme.set(true);
1611
+ }
1612
+ // ==============================================
1613
+ // Host Listeners
1614
+ // ==============================================
1615
+ onDocumentClick(event) {
1616
+ const target = event.target;
1617
+ if (!target.closest('zs-theme-toggle') && this.isOpen()) {
1618
+ this.isOpen.set(false);
1619
+ }
1620
+ }
1621
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ThemeToggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
1622
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: ThemeToggle, isStandalone: true, selector: "ZS-theme-toggle", inputs: { bodyClass: { classPropertyName: "bodyClass", publicName: "bodyClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { themeChangeEv: "themeChangeEv" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, ngImport: i0, template: "<!-- ========================= Theme Switcher Panel ========================= -->\n<div \n class=\"zs:fixed zs:left-0 zs:top-1/6 {{ zIndices.themeToggle }} zs:transform \n zs:-translate-y-1/2 zs:transition-all zs:duration-300\"\n [ngClass]=\"!isOpen() ? 'zs:-translate-x-24' : ''\"\n role=\"complementary\"\n aria-label=\"Theme switcher panel\"\n>\n <!-- ========================= Theme Selection Buttons ========================= -->\n <div class=\"zs:flex zs:items-center\">\n <div \n class=\"zs:flex zs:overflow-hidden zs:transition-all zs:duration-300\"\n role=\"group\"\n aria-label=\"Theme selection\"\n >\n <!-- Light Theme Button -->\n <button\n (click)=\"setTheme('light')\"\n type=\"button\"\n class=\"zs:px-3 zs:py-2 zs:rounded-l-none zs:border-l-0 zs:border-y \n zs:transition-colors zs:shrink-0\n zs:bg-white zs:text-gray-900 zs:border-gray-300\n zs:dark:bg-gray-800 zs:dark:text-gray-100 zs:dark:border-gray-600\n zs:hover:bg-gray-100 zs:dark:hover:bg-gray-700 zs:w-12\n zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-offset-2 \n zs:focus-visible:ring-blue-500\"\n aria-label=\"Activate light theme\"\n [attr.aria-pressed]=\"currentTheme() === 'light'\"\n >\n <i class=\"fas fa-sun zs:text-yellow-500\" aria-hidden=\"true\"></i>\n </button>\n\n <!-- Dark Theme Button -->\n <button\n (click)=\"setTheme('dark')\"\n type=\"button\"\n class=\"zs:px-3 zs:py-2 zs:rounded-r-lg zs:border-y zs:border-r \n zs:transition-colors zs:shrink-0\n zs:bg-white zs:text-gray-900 zs:border-gray-300\n zs:dark:bg-gray-800 zs:dark:text-gray-100 zs:dark:border-gray-600\n zs:hover:bg-gray-100 zs:dark:hover:bg-gray-700 zs:w-12\n zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-offset-2 \n zs:focus-visible:ring-blue-500\"\n aria-label=\"Activate dark theme\"\n [attr.aria-pressed]=\"currentTheme() === 'dark'\"\n >\n <i class=\"fas fa-moon\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <!-- ========================= Toggle Button with Connector ========================= -->\n <div \n class=\"zs:relative\"\n [ngClass]=\"isOpen() ? 'zs:hidden' : ''\"\n >\n <!-- Connector Line -->\n <div\n class=\"zs:absolute zs:top-1/2 zs:right-0 zs:h-0.5 zs:w-3 zs:transform zs:-translate-y-1/2\n zs:bg-gray-300 zs:dark:bg-gray-600\"\n aria-hidden=\"true\"\n ></div>\n\n <!-- Toggle Button -->\n <button\n (click)=\"toggleOpen()\"\n type=\"button\"\n class=\"zs:relative zs:z-100 zs:w-12 zs:h-12 zs:rounded-full zs:rounded-l-none zs:border-l-0 \n zs:border-2\n zs:transition-all zs:duration-300 zs:flex zs:items-center zs:justify-center\n zs:bg-white zs:text-gray-900 zs:border-gray-300\n zs:dark:bg-gray-800 zs:dark:text-gray-100 zs:dark:border-gray-600\n zs:hover:bg-gray-100 zs:dark:hover:bg-gray-700\n zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-offset-2 \n zs:focus-visible:ring-blue-500\"\n aria-label=\"Toggle theme switcher\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-controls=\"theme-panel\"\n >\n @if (currentTheme() === 'dark') {\n <i class=\"fas fa-moon\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fas fa-sun zs:text-yellow-500\" aria-hidden=\"true\"></i>\n }\n </button>\n </div>\n </div>\n</div>", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
1623
+ }
1624
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: ThemeToggle, decorators: [{
1625
+ type: Component,
1626
+ args: [{ selector: 'ZS-theme-toggle', imports: [CommonModule], template: "<!-- ========================= Theme Switcher Panel ========================= -->\n<div \n class=\"zs:fixed zs:left-0 zs:top-1/6 {{ zIndices.themeToggle }} zs:transform \n zs:-translate-y-1/2 zs:transition-all zs:duration-300\"\n [ngClass]=\"!isOpen() ? 'zs:-translate-x-24' : ''\"\n role=\"complementary\"\n aria-label=\"Theme switcher panel\"\n>\n <!-- ========================= Theme Selection Buttons ========================= -->\n <div class=\"zs:flex zs:items-center\">\n <div \n class=\"zs:flex zs:overflow-hidden zs:transition-all zs:duration-300\"\n role=\"group\"\n aria-label=\"Theme selection\"\n >\n <!-- Light Theme Button -->\n <button\n (click)=\"setTheme('light')\"\n type=\"button\"\n class=\"zs:px-3 zs:py-2 zs:rounded-l-none zs:border-l-0 zs:border-y \n zs:transition-colors zs:shrink-0\n zs:bg-white zs:text-gray-900 zs:border-gray-300\n zs:dark:bg-gray-800 zs:dark:text-gray-100 zs:dark:border-gray-600\n zs:hover:bg-gray-100 zs:dark:hover:bg-gray-700 zs:w-12\n zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-offset-2 \n zs:focus-visible:ring-blue-500\"\n aria-label=\"Activate light theme\"\n [attr.aria-pressed]=\"currentTheme() === 'light'\"\n >\n <i class=\"fas fa-sun zs:text-yellow-500\" aria-hidden=\"true\"></i>\n </button>\n\n <!-- Dark Theme Button -->\n <button\n (click)=\"setTheme('dark')\"\n type=\"button\"\n class=\"zs:px-3 zs:py-2 zs:rounded-r-lg zs:border-y zs:border-r \n zs:transition-colors zs:shrink-0\n zs:bg-white zs:text-gray-900 zs:border-gray-300\n zs:dark:bg-gray-800 zs:dark:text-gray-100 zs:dark:border-gray-600\n zs:hover:bg-gray-100 zs:dark:hover:bg-gray-700 zs:w-12\n zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-offset-2 \n zs:focus-visible:ring-blue-500\"\n aria-label=\"Activate dark theme\"\n [attr.aria-pressed]=\"currentTheme() === 'dark'\"\n >\n <i class=\"fas fa-moon\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <!-- ========================= Toggle Button with Connector ========================= -->\n <div \n class=\"zs:relative\"\n [ngClass]=\"isOpen() ? 'zs:hidden' : ''\"\n >\n <!-- Connector Line -->\n <div\n class=\"zs:absolute zs:top-1/2 zs:right-0 zs:h-0.5 zs:w-3 zs:transform zs:-translate-y-1/2\n zs:bg-gray-300 zs:dark:bg-gray-600\"\n aria-hidden=\"true\"\n ></div>\n\n <!-- Toggle Button -->\n <button\n (click)=\"toggleOpen()\"\n type=\"button\"\n class=\"zs:relative zs:z-100 zs:w-12 zs:h-12 zs:rounded-full zs:rounded-l-none zs:border-l-0 \n zs:border-2\n zs:transition-all zs:duration-300 zs:flex zs:items-center zs:justify-center\n zs:bg-white zs:text-gray-900 zs:border-gray-300\n zs:dark:bg-gray-800 zs:dark:text-gray-100 zs:dark:border-gray-600\n zs:hover:bg-gray-100 zs:dark:hover:bg-gray-700\n zs:focus:outline-hidden zs:focus-visible:ring-2 zs:focus-visible:ring-offset-2 \n zs:focus-visible:ring-blue-500\"\n aria-label=\"Toggle theme switcher\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-controls=\"theme-panel\"\n >\n @if (currentTheme() === 'dark') {\n <i class=\"fas fa-moon\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fas fa-sun zs:text-yellow-500\" aria-hidden=\"true\"></i>\n }\n </button>\n </div>\n </div>\n</div>", styles: [":host{display:block}\n"] }]
1627
+ }], ctorParameters: () => [], propDecorators: { bodyClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "bodyClass", required: false }] }], themeChangeEv: [{ type: i0.Output, args: ["themeChangeEv"] }], onDocumentClick: [{
1628
+ type: HostListener,
1629
+ args: ['document:click', ['$event']]
1630
+ }] } });
1631
+
1632
+ // ==============================================
1633
+ // Component Metadata
1634
+ // ==============================================
1635
+ class Label {
1636
+ // ==============================================
1637
+ // Label & Hint Configuration
1638
+ // ==============================================
1639
+ label = input(null, ...(ngDevMode ? [{ debugName: "label" }] : []));
1640
+ hint = input(null, ...(ngDevMode ? [{ debugName: "hint" }] : []));
1641
+ hintId = input(null, ...(ngDevMode ? [{ debugName: "hintId" }] : []));
1642
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
1643
+ // ==============================================
1644
+ // Accessibility & State Inputs
1645
+ // ==============================================
1646
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
1647
+ for = input(null, ...(ngDevMode ? [{ debugName: "for" }] : []));
1648
+ // ==============================================
1649
+ // Computed Classes
1650
+ // ==============================================
1651
+ sizeClasses = computed(() => {
1652
+ const sizes = {
1653
+ sm: { label: 'zs:text-xs', hint: 'zs:text-[10px]' },
1654
+ md: { label: 'zs:text-sm', hint: 'zs:text-xs' },
1655
+ lg: { label: 'zs:text-base', hint: 'zs:text-sm' },
1656
+ };
1657
+ return sizes[this.size()];
1658
+ }, ...(ngDevMode ? [{ debugName: "sizeClasses" }] : []));
1659
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Label, deps: [], target: i0.ɵɵFactoryTarget.Component });
1660
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Label, isStandalone: true, selector: "ZS-label", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, hintId: { classPropertyName: "hintId", publicName: "hintId", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, for: { classPropertyName: "for", publicName: "for", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!-- ================= Label Wrapper ================= -->\n@if (label() || hint()) {\n <label \n [for]=\"for()\" \n class=\"zs:flex zs:items-center zs:gap-2\"\n >\n\n <!-- ========== Label Text ========== -->\n @if (label()) {\n <span class=\"zs:font-semibold zs:truncate\" [ngClass]=\"sizeClasses().label\">\n {{ label() }}\n\n <!-- Required Indicator -->\n @if (required()) {\n <span class=\"zs:text-red-500\" aria-hidden=\"true\">*</span>\n <span class=\"sr-only\">(required)</span>\n }\n </span>\n }\n\n <!-- ========== Hint Text ========== -->\n @if (hint()) {\n <small\n [id]=\"hintId()\"\n class=\"zs:text-slate-500 zs:dark:text-slate-400\" \n [ngClass]=\"sizeClasses().hint\"\n >\n {{ hint() }}\n </small>\n }\n <!-- ========== End Hint Text ========== -->\n\n </label>\n}\n<!-- ================= End Label Wrapper ================= -->", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
1661
+ }
1662
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Label, decorators: [{
1663
+ type: Component,
1664
+ args: [{ selector: 'ZS-label', imports: [CommonModule], template: "<!-- ================= Label Wrapper ================= -->\n@if (label() || hint()) {\n <label \n [for]=\"for()\" \n class=\"zs:flex zs:items-center zs:gap-2\"\n >\n\n <!-- ========== Label Text ========== -->\n @if (label()) {\n <span class=\"zs:font-semibold zs:truncate\" [ngClass]=\"sizeClasses().label\">\n {{ label() }}\n\n <!-- Required Indicator -->\n @if (required()) {\n <span class=\"zs:text-red-500\" aria-hidden=\"true\">*</span>\n <span class=\"sr-only\">(required)</span>\n }\n </span>\n }\n\n <!-- ========== Hint Text ========== -->\n @if (hint()) {\n <small\n [id]=\"hintId()\"\n class=\"zs:text-slate-500 zs:dark:text-slate-400\" \n [ngClass]=\"sizeClasses().hint\"\n >\n {{ hint() }}\n </small>\n }\n <!-- ========== End Hint Text ========== -->\n\n </label>\n}\n<!-- ================= End Label Wrapper ================= -->", styles: [":host{display:block}\n"] }]
1665
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], hintId: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintId", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], for: [{ type: i0.Input, args: [{ isSignal: true, alias: "for", required: false }] }] } });
1666
+
1667
+ // ==============================================
1668
+ // Class
1669
+ // ==============================================
1670
+ class Checkbox {
1671
+ // ==============================================
1672
+ // Inputs
1673
+ // ==============================================
1674
+ Id = input(crypto.randomUUID(), ...(ngDevMode ? [{ debugName: "Id" }] : []));
1675
+ label = input(null, ...(ngDevMode ? [{ debugName: "label" }] : []));
1676
+ hint = input(null, ...(ngDevMode ? [{ debugName: "hint" }] : []));
1677
+ inputStyle = input('secondary', ...(ngDevMode ? [{ debugName: "inputStyle" }] : []));
1678
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
1679
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1680
+ isReadonly = input(false, ...(ngDevMode ? [{ debugName: "isReadonly" }] : []));
1681
+ variant = input('regular', ...(ngDevMode ? [{ debugName: "variant" }] : []));
1682
+ shape = input('square', ...(ngDevMode ? [{ debugName: "shape" }] : []));
1683
+ // ==============================================
1684
+ // Model
1685
+ // ==============================================
1686
+ value = model(false, ...(ngDevMode ? [{ debugName: "value" }] : []));
1687
+ // ==============================================
1688
+ // Computed Signals
1689
+ // ==============================================
1690
+ palette = computed(() => FormPaletteMap.get(this.inputStyle()) ?? FormPaletteMap.get('secondary'), ...(ngDevMode ? [{ debugName: "palette" }] : []));
1691
+ iconClasses = computed(() => {
1692
+ const v = this.variant();
1693
+ const s = this.shape();
1694
+ const variantClass = {
1695
+ true: {
1696
+ solid: 'fa-solid',
1697
+ regular: 'fa-regular'
1698
+ },
1699
+ false: {
1700
+ solid: 'fa-regular',
1701
+ regular: 'fa-regular'
1702
+ }
1703
+ };
1704
+ const shapeClass = {
1705
+ true: {
1706
+ square: 'fa-square-check',
1707
+ circle: 'fa-circle-check'
1708
+ },
1709
+ false: {
1710
+ square: 'fa-square',
1711
+ circle: 'fa-circle'
1712
+ },
1713
+ };
1714
+ const disabledClass = this.disabled() ? 'zs:opacity-60' : '';
1715
+ const interactionClass = this.disabledOrReadonly() ? 'zs:cursor-not-allowed' : '';
1716
+ const state = this.value() ? 'true' : 'false';
1717
+ return [
1718
+ variantClass[state][v],
1719
+ shapeClass[state][s],
1720
+ disabledClass,
1721
+ interactionClass
1722
+ ].filter(Boolean).join(' ');
1723
+ }, ...(ngDevMode ? [{ debugName: "iconClasses" }] : []));
1724
+ sizeClass = computed(() => {
1725
+ const sizeClasses = {
1726
+ sm: 'zs:text-[20px]',
1727
+ md: 'zs:text-[30px]',
1728
+ lg: 'zs:text-[45px]'
1729
+ };
1730
+ return sizeClasses[this.size()];
1731
+ }, ...(ngDevMode ? [{ debugName: "sizeClass" }] : []));
1732
+ isChecked = computed(() => this.value(), ...(ngDevMode ? [{ debugName: "isChecked" }] : []));
1733
+ disabledOrReadonly = computed(() => this.disabled() || this.isReadonly(), ...(ngDevMode ? [{ debugName: "disabledOrReadonly" }] : []));
1734
+ // ==============================================
1735
+ // Handlers
1736
+ // ==============================================
1737
+ toggleChecked() {
1738
+ if (this.disabledOrReadonly())
1739
+ return;
1740
+ this.value.update(v => !v);
1741
+ }
1742
+ onKeyDown(event) {
1743
+ if (event.key === ' ' || event.key === 'Enter') {
1744
+ event.preventDefault();
1745
+ this.toggleChecked();
1746
+ }
1747
+ }
1748
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Checkbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
1749
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.9", type: Checkbox, isStandalone: true, selector: "ZS-checkbox", inputs: { Id: { classPropertyName: "Id", publicName: "Id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, inputStyle: { classPropertyName: "inputStyle", publicName: "inputStyle", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, isReadonly: { classPropertyName: "isReadonly", publicName: "isReadonly", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: "<div class=\"zs:flex zs:items-center zs:justify-start zs:w-full zs:select-none\">\n <!-- ===================== Checkbox Wrapper ===================== -->\n <div\n class=\"zs:cursor-pointer group leading-none\"\n >\n <!-- ===================== Hidden Input (Native for screen readers) ===================== -->\n <input\n type=\"checkbox\"\n class=\"zs:hidden\"\n [id]=\"Id()\"\n [checked]=\"isChecked()\"\n [disabled]=\"disabledOrReadonly()\"\n (change)=\"toggleChecked()\"\n />\n\n <!-- ===================== Custom Accessible Checkbox ===================== -->\n <span\n role=\"'checkbox'\"\n tabindex=\"0\"\n [attr.aria-checked]=\"isChecked()\"\n [attr.aria-disabled]=\"disabledOrReadonly()\"\n [attr.aria-labelledby]=\"Id() + '-label'\"\n [ngClass]=\"[\n sizeClass(),\n palette().checkBoxText,\n palette().checkBoxTextHover,\n palette().ring,\n 'zs:leading-none zs:inline-flex zs:items-center zs:justify-center zs:focus:outline-hidden zs:focus-visible:ring-2'\n ]\"\n (click)=\"toggleChecked()\"\n (keydown)=\"onKeyDown($event)\"\n >\n <i [class]=\"iconClasses()\"></i>\n </span>\n </div>\n\n <!-- ===================== Label (ZS-label component) ===================== -->\n <ZS-label [class]=\"(label() || hint()) ? 'zs:ml-1' : ''\"\n [id]=\"Id() + '-label'\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [hintId]=\"Id() + '-hint'\"\n [size]=\"size()\"\n [for]=\"Id()\"\n ></ZS-label>\n</div>", styles: [""], dependencies: [{ kind: "component", type: Label, selector: "ZS-label", inputs: ["label", "hint", "hintId", "size", "required", "for"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
1750
+ }
1751
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Checkbox, decorators: [{
1752
+ type: Component,
1753
+ args: [{ selector: 'ZS-checkbox', imports: [Label, CommonModule], template: "<div class=\"zs:flex zs:items-center zs:justify-start zs:w-full zs:select-none\">\n <!-- ===================== Checkbox Wrapper ===================== -->\n <div\n class=\"zs:cursor-pointer group leading-none\"\n >\n <!-- ===================== Hidden Input (Native for screen readers) ===================== -->\n <input\n type=\"checkbox\"\n class=\"zs:hidden\"\n [id]=\"Id()\"\n [checked]=\"isChecked()\"\n [disabled]=\"disabledOrReadonly()\"\n (change)=\"toggleChecked()\"\n />\n\n <!-- ===================== Custom Accessible Checkbox ===================== -->\n <span\n role=\"'checkbox'\"\n tabindex=\"0\"\n [attr.aria-checked]=\"isChecked()\"\n [attr.aria-disabled]=\"disabledOrReadonly()\"\n [attr.aria-labelledby]=\"Id() + '-label'\"\n [ngClass]=\"[\n sizeClass(),\n palette().checkBoxText,\n palette().checkBoxTextHover,\n palette().ring,\n 'zs:leading-none zs:inline-flex zs:items-center zs:justify-center zs:focus:outline-hidden zs:focus-visible:ring-2'\n ]\"\n (click)=\"toggleChecked()\"\n (keydown)=\"onKeyDown($event)\"\n >\n <i [class]=\"iconClasses()\"></i>\n </span>\n </div>\n\n <!-- ===================== Label (ZS-label component) ===================== -->\n <ZS-label [class]=\"(label() || hint()) ? 'zs:ml-1' : ''\"\n [id]=\"Id() + '-label'\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [hintId]=\"Id() + '-hint'\"\n [size]=\"size()\"\n [for]=\"Id()\"\n ></ZS-label>\n</div>" }]
1754
+ }], propDecorators: { Id: [{ type: i0.Input, args: [{ isSignal: true, alias: "Id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], inputStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputStyle", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], isReadonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "isReadonly", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
1755
+
1756
+ // ==============================================================================
1757
+ // Component Definition
1758
+ // ==============================================================================
1759
+ class InputErrors {
1760
+ // ==============================================================================
1761
+ // Service
1762
+ // ==============================================================================
1763
+ extractorService = inject(ExtractorService);
1764
+ // ==============================================================================
1765
+ // Inputs
1766
+ // ==============================================================================
1767
+ Id = input(crypto.randomUUID(), ...(ngDevMode ? [{ debugName: "Id" }] : []));
1768
+ errors = input([], ...(ngDevMode ? [{ debugName: "errors" }] : []));
1769
+ // ==============================================================================
1770
+ // Computed Signals
1771
+ // ==============================================================================
1772
+ extractedErrors = computed(() => this.extractorService.extract(this.errors()), ...(ngDevMode ? [{ debugName: "extractedErrors" }] : []));
1773
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: InputErrors, deps: [], target: i0.ɵɵFactoryTarget.Component });
1774
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: InputErrors, isStandalone: true, selector: "ZS-input-errors", inputs: { Id: { classPropertyName: "Id", publicName: "Id", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!-- ========================= Error Messages ========================= -->\n@if (extractedErrors().length) {\n <ul\n [id]=\"Id() + '-error'\"\n class=\"zs:mt-1 zs:text-xs zs:text-red-500 zs:flex zs:flex-col zs:gap-1\"\n role=\"status\"\n aria-live=\"polite\"\n >\n @for (err of extractedErrors(); track $index) {\n <li class=\"zs:flex zs:items-center zs:gap-2\">\n <i class=\"fa fa-circle zs:text-[6px]\" aria-hidden=\"true\"></i>\n <span>{{ err }}</span>\n </li>\n }\n </ul>\n}", styles: [""] });
1775
+ }
1776
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: InputErrors, decorators: [{
1777
+ type: Component,
1778
+ args: [{ selector: 'ZS-input-errors', imports: [], template: "<!-- ========================= Error Messages ========================= -->\n@if (extractedErrors().length) {\n <ul\n [id]=\"Id() + '-error'\"\n class=\"zs:mt-1 zs:text-xs zs:text-red-500 zs:flex zs:flex-col zs:gap-1\"\n role=\"status\"\n aria-live=\"polite\"\n >\n @for (err of extractedErrors(); track $index) {\n <li class=\"zs:flex zs:items-center zs:gap-2\">\n <i class=\"fa fa-circle zs:text-[6px]\" aria-hidden=\"true\"></i>\n <span>{{ err }}</span>\n </li>\n }\n </ul>\n}" }]
1779
+ }], propDecorators: { Id: [{ type: i0.Input, args: [{ isSignal: true, alias: "Id", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }] } });
1780
+
1781
+ // ==============================================================================
1782
+ // Imports
1783
+ // ==============================================================================
1784
+ // ==============================================================================
1785
+ // Component Metadata
1786
+ // ==============================================================================
1787
+ class FileInput {
1788
+ // ==============================================================================
1789
+ // Inputs
1790
+ // ==============================================================================
1791
+ Id = input(crypto.randomUUID(), ...(ngDevMode ? [{ debugName: "Id" }] : []));
1792
+ iName = input(null, ...(ngDevMode ? [{ debugName: "iName" }] : []));
1793
+ label = input(null, ...(ngDevMode ? [{ debugName: "label" }] : []));
1794
+ hint = input(null, ...(ngDevMode ? [{ debugName: "hint" }] : []));
1795
+ placeholder = input(null, ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
1796
+ inputStyle = input('secondary', ...(ngDevMode ? [{ debugName: "inputStyle" }] : []));
1797
+ autofocus = input(false, ...(ngDevMode ? [{ debugName: "autofocus" }] : []));
1798
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1799
+ isReadonly = input(false, ...(ngDevMode ? [{ debugName: "isReadonly" }] : []));
1800
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
1801
+ validateFns = input([], ...(ngDevMode ? [{ debugName: "validateFns" }] : []));
1802
+ accept = input('', ...(ngDevMode ? [{ debugName: "accept" }] : []));
1803
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
1804
+ maxSize = input(5 * 1024 * 1024, ...(ngDevMode ? [{ debugName: "maxSize" }] : [])); // 5MB
1805
+ allowPreview = input(true, ...(ngDevMode ? [{ debugName: "allowPreview" }] : []));
1806
+ maxFiles = input('infinity', ...(ngDevMode ? [{ debugName: "maxFiles" }] : []));
1807
+ // ==============================================================================
1808
+ // Outputs
1809
+ // ==============================================================================
1810
+ changeEv = output();
1811
+ // ==============================================================================
1812
+ // Model
1813
+ // ==============================================================================
1814
+ files = model(new Map(), ...(ngDevMode ? [{ debugName: "files" }] : []));
1815
+ touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : [])); // Tracks if the user has interacted with the input
1816
+ // ==============================================================================
1817
+ // ViewChild
1818
+ // ==============================================================================
1819
+ fileInputRef = viewChild('fileInput', ...(ngDevMode ? [{ debugName: "fileInputRef" }] : []));
1820
+ // ==============================================================================
1821
+ // Computed Properties
1822
+ // ==============================================================================
1823
+ palette = computed(() => FormPaletteMap.get(this.inputStyle()), ...(ngDevMode ? [{ debugName: "palette" }] : []));
1824
+ hasFiles = computed(() => this.files().size > 0, ...(ngDevMode ? [{ debugName: "hasFiles" }] : []));
1825
+ totalSize = computed(() => this.filesMapToList().reduce((sum, f) => sum + f.size, 0), ...(ngDevMode ? [{ debugName: "totalSize" }] : []));
1826
+ disabledOrReadonly = computed(() => this.disabled() || this.isReadonly(), ...(ngDevMode ? [{ debugName: "disabledOrReadonly" }] : []));
1827
+ error = computed(() => {
1828
+ const hasFiles = this.hasFiles();
1829
+ const files = this.files();
1830
+ const required = this.required();
1831
+ const totalSize = this.totalSize();
1832
+ const maxSize = this.maxSize();
1833
+ const maxFiles = this.maxFiles();
1834
+ const accept = this.accept();
1835
+ // Only validate after user interaction
1836
+ if (!this.touched())
1837
+ return [];
1838
+ const errors = [];
1839
+ // Required validation
1840
+ if (required && !hasFiles) {
1841
+ errors.push('This field is required');
1842
+ }
1843
+ // Max size validation
1844
+ if (totalSize > maxSize) {
1845
+ errors.push(`Total file size exceeds ${this.formatSize(maxSize)}`);
1846
+ }
1847
+ // Max files validation
1848
+ if (maxFiles !== 'infinity' && files.size > maxFiles) {
1849
+ errors.push(`Total number of files exceeds ${maxFiles}`);
1850
+ }
1851
+ // Accept (file type) validation
1852
+ const invalidFiles = this.filesMapToList().filter(f => !this.matchesAccept(f, accept));
1853
+ if (invalidFiles.length > 0) {
1854
+ const names = invalidFiles.map(f => f.name).join(', ');
1855
+ errors.push(`Some files have unsupported types: ${names}`);
1856
+ }
1857
+ // Custom validators
1858
+ for (const fn of this.validateFns()) {
1859
+ const result = fn(this.filesMapToList());
1860
+ if (Array.isArray(result))
1861
+ errors.push(...result);
1862
+ }
1863
+ return errors.length > 0 ? errors : [];
1864
+ }, ...(ngDevMode ? [{ debugName: "error" }] : []));
1865
+ filesMapToList = computed(() => {
1866
+ const files = this.files();
1867
+ return files.size ? Array.from(files.values()) : [];
1868
+ }, ...(ngDevMode ? [{ debugName: "filesMapToList" }] : []));
1869
+ // ==============================================================================
1870
+ // Event Handlers
1871
+ // ==============================================================================
1872
+ handleFileChange(event) {
1873
+ if (this.disabledOrReadonly())
1874
+ return;
1875
+ const input = event.target;
1876
+ if (!input.files)
1877
+ return;
1878
+ const selected = Array.from(input.files).map(f => ({
1879
+ name: f.name,
1880
+ size: f.size,
1881
+ type: f.type,
1882
+ url: this.allowPreview() ? URL.createObjectURL(f) : undefined,
1883
+ }));
1884
+ this.files.update((prev) => {
1885
+ const multiple = this.multiple();
1886
+ const next = multiple ? new Map(prev) : new Map();
1887
+ for (const nf of selected) {
1888
+ next.set(this.fileKey(nf), nf);
1889
+ }
1890
+ return next;
1891
+ });
1892
+ this.touched.set(true);
1893
+ this.emitChangeValue(this.filesMapToList(), false);
1894
+ // Reset native <input> value to allow re-selecting the same file
1895
+ input.value = '';
1896
+ }
1897
+ removeFile(id) {
1898
+ if (this.isReadonly() || this.disabled())
1899
+ return;
1900
+ this.files.update((prev) => {
1901
+ const next = new Map(prev);
1902
+ const file = prev.get(id);
1903
+ if (file?.url?.startsWith('blob:')) {
1904
+ URL.revokeObjectURL(file.url);
1905
+ }
1906
+ next.delete(id);
1907
+ return next;
1908
+ });
1909
+ this.emitChangeValue(this.filesMapToList(), false);
1910
+ // Reset native file input
1911
+ const inputEl = this.fileInputRef()?.nativeElement;
1912
+ if (inputEl) {
1913
+ inputEl.value = '';
1914
+ }
1915
+ }
1916
+ // ==============================================================================
1917
+ // Public Methods
1918
+ // ==============================================================================
1919
+ /** Forces the input to trigger a manual change event */
1920
+ forceChange(fromForce = true) {
1921
+ this.touched.set(true);
1922
+ this.emitChangeValue(this.filesMapToList(), fromForce);
1923
+ }
1924
+ // ==============================================================================
1925
+ // Private Helpers
1926
+ // ==============================================================================
1927
+ formatSize(size) {
1928
+ if (size < 1024)
1929
+ return `${size} B`;
1930
+ if (size < 1024 * 1024)
1931
+ return `${(size / 1024).toFixed(1)} KB`;
1932
+ return `${(size / 1024 / 1024).toFixed(1)} MB`;
1933
+ }
1934
+ preview(url) {
1935
+ if (!url)
1936
+ return;
1937
+ window.open(url, '_blank', 'noopener,noreferrer');
1938
+ }
1939
+ fileKey(f) {
1940
+ return `${f.name}_${f.size}_${f.type}`;
1941
+ }
1942
+ emitChangeValue(value, fromForce = true) {
1943
+ const valid = this.error().length === 0;
1944
+ this.changeEv.emit({ value, valid, fromForce });
1945
+ }
1946
+ matchesAccept(file, accept) {
1947
+ if (!accept)
1948
+ return true;
1949
+ const types = accept.split(',').map(t => t.trim().toLowerCase());
1950
+ return types.some(type => {
1951
+ if (type === '*/*')
1952
+ return true;
1953
+ // Wildcard MIME types (e.g., image/*)
1954
+ if (type.endsWith('/*')) {
1955
+ const baseType = type.split('/')[0];
1956
+ return file.type.startsWith(`${baseType}/`);
1957
+ }
1958
+ // Exact MIME type match
1959
+ if (type.includes('/') && !type.startsWith('.')) {
1960
+ return file.type === type;
1961
+ }
1962
+ // File extension match (e.g., .jpg)
1963
+ if (type.startsWith('.')) {
1964
+ return file.name.toLowerCase().endsWith(type);
1965
+ }
1966
+ // Fallback: partial match in MIME type
1967
+ return file.type.includes(type);
1968
+ });
1969
+ }
1970
+ // ==============================================================================
1971
+ // Lifecycle Hooks
1972
+ // ==============================================================================
1973
+ ngOnDestroy() {
1974
+ this.files().forEach(f => {
1975
+ if (f.url?.startsWith('blob:')) {
1976
+ URL.revokeObjectURL(f.url);
1977
+ }
1978
+ });
1979
+ }
1980
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: FileInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
1981
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: FileInput, isStandalone: true, selector: "ZS-file", inputs: { Id: { classPropertyName: "Id", publicName: "Id", isSignal: true, isRequired: false, transformFunction: null }, iName: { classPropertyName: "iName", publicName: "iName", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, inputStyle: { classPropertyName: "inputStyle", publicName: "inputStyle", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, isReadonly: { classPropertyName: "isReadonly", publicName: "isReadonly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, validateFns: { classPropertyName: "validateFns", publicName: "validateFns", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, allowPreview: { classPropertyName: "allowPreview", publicName: "allowPreview", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changeEv: "changeEv", files: "filesChange", touched: "touchedChange" }, viewQueries: [{ propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"zs:flex zs:flex-col zs:gap-2 zs:w-full\"\n [ngClass]=\"disabled() ? 'zs:opacity-60' : ''\"\n role=\"group\"\n [attr.aria-labelledby]=\"Id() + '-label'\"\n [attr.aria-describedby]=\"Id() + '-hint ' + Id() + '-error'\"\n>\n <!-- ========================= Label & Hint ========================= -->\n <ZS-label [class]=\"(label() || hint()) ? 'zs:mb-1' : ''\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [required]=\"required()\"\n [hintId]=\"Id() + '-hint'\"\n [for]=\"Id()\"\n [id]=\"Id() + '-label'\"\n >\n </ZS-label>\n\n <!-- ================= Input ================= -->\n <div\n [class]=\"[\n 'zs:relative zs:rounded-xl zs:flex zs:flex-col zs:justify-center zs:items-center zs:gap-2 zs:border-2 zs:border-dashed zs:transition-all zs:duration-200',\n palette().border,\n palette().ring,\n palette().borderHover,\n palette().inputBg\n ].join(' ')\"\n [ngClass]=\"disabledOrReadonly() ? 'cursor-not-allowed' : 'cursor-pointer'\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-disabled]=\"disabledOrReadonly()\"\n [attr.aria-label]=\"placeholder() || 'Upload file'\"\n (keydown.enter)=\"!disabledOrReadonly() && fileInput.click()\"\n (keydown.space)=\"!disabledOrReadonly() && fileInput.click()\"\n >\n <input\n #fileInput\n type=\"file\"\n [id]=\"Id()\"\n [name]=\"iName()\"\n [attr.accept]=\"accept()\"\n [attr.multiple]=\"multiple() ? '' : null\"\n [attr.autofocus]=\"autofocus() ? true : null\"\n class=\"zs:absolute zs:inset-0 zs:opacity-0 zs:cursor-pointer\"\n [disabled]=\"disabledOrReadonly()\"\n (change)=\"handleFileChange($event)\"\n [attr.aria-required]=\"required()\"\n [attr.aria-invalid]=\"!!error().length\"\n [attr.aria-describedby]=\"Id() + '-hint ' + Id() + '-error'\"\n />\n\n <div class=\"zs:flex zs:flex-col zs:items-center zs:justify-center zs:text-center zs:py-6 zs:pointer-events-none zs:select-none\">\n <i class=\"fa-solid fa-upload text-3xl mb-2\" [ngClass]=\"palette().text\" aria-hidden=\"true\"></i>\n <p class=\"zs:text-sm zs:text-gray-500 zs:dark:text-gray-400\">\n {{ placeholder() }}\n </p>\n </div>\n </div>\n\n <!-- ================= Files Preview ================= -->\n @if (hasFiles()) {\n <div\n class=\"zs:flex zs:flex-col zs:gap-2 zs:mt-3\"\n role=\"list\"\n aria-label=\"Uploaded files\"\n >\n @for (entry of files().entries(); track entry[0]) {\n <div\n class=\"zs:flex zs:flex-col zs:gap-1 zs:p-2 zs:rounded-lg zs:border zs:focus:outline-hidden zs:focus:ring-2 zs:focus:ring-offset-2\"\n [ngClass]=\"[palette().border, palette().inputBg]\"\n role=\"listitem\"\n tabindex=\"0\"\n >\n <span class=\"zs:block zs:text-sm zs:font-medium zs:truncate\">\n {{ entry[1].name }}\n </span>\n\n <div class=\"zs:flex zs:items-center zs:justify-between zs:gap-4\">\n <div class=\"zs:flex zs:items-center zs:gap-1\">\n <i class=\"text-lg fa-solid fa-file\" [ngClass]=\"palette().text\" aria-hidden=\"true\"></i>\n <span class=\"zs:text-xs zs:text-gray-500\">\n {{ formatSize(entry[1].size) }}\n </span>\n </div>\n\n <div class=\"zs:flex zs:gap-2\">\n @if (allowPreview() && entry[1].url?.startsWith('blob:')) {\n <ZS-button\n type=\"button\"\n (clickedEv)=\"preview(entry[1].url)\"\n [btnStyle]=\"inputStyle()\"\n size=\"sm\"\n aria-label=\"Preview {{ entry[1].name }}\"\n >\n Preview\n </ZS-button>\n }\n\n @if (!disabledOrReadonly()) {\n <ZS-button\n type=\"button\"\n variant=\"outline\"\n btnStyle=\"danger\"\n (clickedEv)=\"removeFile(entry[0])\"\n icon=\"fa-solid fa-trash\"\n aria-label=\"Remove {{ entry[1].name }}\"\n >\n </ZS-button>\n }\n </div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- ========================= Error Messages ========================= -->\n <ZS-input-errors\n [Id]=\"Id() + '-error'\"\n [errors]=\"[error()]\"\n aria-live=\"assertive\"\n role=\"alert\"\n >\n </ZS-input-errors>\n</div>\n", styles: [":host{@apply block w-full;}input[type=file]:disabled{@apply cursor-not-allowed opacity-60;}\n"], dependencies: [{ kind: "component", type: Label, selector: "ZS-label", inputs: ["label", "hint", "hintId", "size", "required", "for"] }, { kind: "component", type: InputErrors, selector: "ZS-input-errors", inputs: ["Id", "errors"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: Button, selector: "ZS-button", inputs: ["Id", "btnStyle", "variant", "size", "disabled", "icon", "type"], outputs: ["clickedEv"] }] });
1982
+ }
1983
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: FileInput, decorators: [{
1984
+ type: Component,
1985
+ args: [{ selector: 'ZS-file', imports: [Label, InputErrors, CommonModule, Button], template: "<div\n class=\"zs:flex zs:flex-col zs:gap-2 zs:w-full\"\n [ngClass]=\"disabled() ? 'zs:opacity-60' : ''\"\n role=\"group\"\n [attr.aria-labelledby]=\"Id() + '-label'\"\n [attr.aria-describedby]=\"Id() + '-hint ' + Id() + '-error'\"\n>\n <!-- ========================= Label & Hint ========================= -->\n <ZS-label [class]=\"(label() || hint()) ? 'zs:mb-1' : ''\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [required]=\"required()\"\n [hintId]=\"Id() + '-hint'\"\n [for]=\"Id()\"\n [id]=\"Id() + '-label'\"\n >\n </ZS-label>\n\n <!-- ================= Input ================= -->\n <div\n [class]=\"[\n 'zs:relative zs:rounded-xl zs:flex zs:flex-col zs:justify-center zs:items-center zs:gap-2 zs:border-2 zs:border-dashed zs:transition-all zs:duration-200',\n palette().border,\n palette().ring,\n palette().borderHover,\n palette().inputBg\n ].join(' ')\"\n [ngClass]=\"disabledOrReadonly() ? 'cursor-not-allowed' : 'cursor-pointer'\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-disabled]=\"disabledOrReadonly()\"\n [attr.aria-label]=\"placeholder() || 'Upload file'\"\n (keydown.enter)=\"!disabledOrReadonly() && fileInput.click()\"\n (keydown.space)=\"!disabledOrReadonly() && fileInput.click()\"\n >\n <input\n #fileInput\n type=\"file\"\n [id]=\"Id()\"\n [name]=\"iName()\"\n [attr.accept]=\"accept()\"\n [attr.multiple]=\"multiple() ? '' : null\"\n [attr.autofocus]=\"autofocus() ? true : null\"\n class=\"zs:absolute zs:inset-0 zs:opacity-0 zs:cursor-pointer\"\n [disabled]=\"disabledOrReadonly()\"\n (change)=\"handleFileChange($event)\"\n [attr.aria-required]=\"required()\"\n [attr.aria-invalid]=\"!!error().length\"\n [attr.aria-describedby]=\"Id() + '-hint ' + Id() + '-error'\"\n />\n\n <div class=\"zs:flex zs:flex-col zs:items-center zs:justify-center zs:text-center zs:py-6 zs:pointer-events-none zs:select-none\">\n <i class=\"fa-solid fa-upload text-3xl mb-2\" [ngClass]=\"palette().text\" aria-hidden=\"true\"></i>\n <p class=\"zs:text-sm zs:text-gray-500 zs:dark:text-gray-400\">\n {{ placeholder() }}\n </p>\n </div>\n </div>\n\n <!-- ================= Files Preview ================= -->\n @if (hasFiles()) {\n <div\n class=\"zs:flex zs:flex-col zs:gap-2 zs:mt-3\"\n role=\"list\"\n aria-label=\"Uploaded files\"\n >\n @for (entry of files().entries(); track entry[0]) {\n <div\n class=\"zs:flex zs:flex-col zs:gap-1 zs:p-2 zs:rounded-lg zs:border zs:focus:outline-hidden zs:focus:ring-2 zs:focus:ring-offset-2\"\n [ngClass]=\"[palette().border, palette().inputBg]\"\n role=\"listitem\"\n tabindex=\"0\"\n >\n <span class=\"zs:block zs:text-sm zs:font-medium zs:truncate\">\n {{ entry[1].name }}\n </span>\n\n <div class=\"zs:flex zs:items-center zs:justify-between zs:gap-4\">\n <div class=\"zs:flex zs:items-center zs:gap-1\">\n <i class=\"text-lg fa-solid fa-file\" [ngClass]=\"palette().text\" aria-hidden=\"true\"></i>\n <span class=\"zs:text-xs zs:text-gray-500\">\n {{ formatSize(entry[1].size) }}\n </span>\n </div>\n\n <div class=\"zs:flex zs:gap-2\">\n @if (allowPreview() && entry[1].url?.startsWith('blob:')) {\n <ZS-button\n type=\"button\"\n (clickedEv)=\"preview(entry[1].url)\"\n [btnStyle]=\"inputStyle()\"\n size=\"sm\"\n aria-label=\"Preview {{ entry[1].name }}\"\n >\n Preview\n </ZS-button>\n }\n\n @if (!disabledOrReadonly()) {\n <ZS-button\n type=\"button\"\n variant=\"outline\"\n btnStyle=\"danger\"\n (clickedEv)=\"removeFile(entry[0])\"\n icon=\"fa-solid fa-trash\"\n aria-label=\"Remove {{ entry[1].name }}\"\n >\n </ZS-button>\n }\n </div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- ========================= Error Messages ========================= -->\n <ZS-input-errors\n [Id]=\"Id() + '-error'\"\n [errors]=\"[error()]\"\n aria-live=\"assertive\"\n role=\"alert\"\n >\n </ZS-input-errors>\n</div>\n", styles: [":host{@apply block w-full;}input[type=file]:disabled{@apply cursor-not-allowed opacity-60;}\n"] }]
1986
+ }], propDecorators: { Id: [{ type: i0.Input, args: [{ isSignal: true, alias: "Id", required: false }] }], iName: [{ type: i0.Input, args: [{ isSignal: true, alias: "iName", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], inputStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputStyle", required: false }] }], autofocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autofocus", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], isReadonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "isReadonly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], validateFns: [{ type: i0.Input, args: [{ isSignal: true, alias: "validateFns", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], allowPreview: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowPreview", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], changeEv: [{ type: i0.Output, args: ["changeEv"] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }, { type: i0.Output, args: ["filesChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], fileInputRef: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
1987
+
1988
+ // ==============================================================================
1989
+ // Constants & Regex
1990
+ // ==============================================================================
1991
+ const SIZE_CLASSES_MAP = new Map([
1992
+ [
1993
+ 'container',
1994
+ {
1995
+ sm: 'zs:px-2 zs:py-1 zs:rounded-md',
1996
+ md: 'zs:px-3 zs:py-2 zs:rounded-lg',
1997
+ lg: 'zs:px-4 zs:py-3 zs:rounded-lg',
1998
+ },
1999
+ ],
2000
+ [
2001
+ 'field',
2002
+ {
2003
+ sm: 'zs:text-xs',
2004
+ md: 'zs:text-sm',
2005
+ lg: 'zs:text-base',
2006
+ },
2007
+ ],
2008
+ [
2009
+ 'leftIcon',
2010
+ {
2011
+ sm: 'zs:text-sm zs:mr-1.5',
2012
+ md: 'zs:text-base zs:mr-2',
2013
+ lg: 'zs:text-lg zs:mr-2.5',
2014
+ },
2015
+ ],
2016
+ [
2017
+ 'rightIcon',
2018
+ {
2019
+ sm: 'zs:text-xs',
2020
+ md: 'zs:text-sm',
2021
+ lg: 'zs:text-base',
2022
+ },
2023
+ ],
2024
+ ]);
2025
+ const DATE_ICON_MAP = {
2026
+ date: 'fas fa-calendar',
2027
+ 'datetime-local': 'fas fa-calendar',
2028
+ month: 'fas fa-calendar-days',
2029
+ week: 'fas fa-calendar-week',
2030
+ time: 'fas fa-clock',
2031
+ };
2032
+ const ICONS = {
2033
+ spinner: 'fas fa-spinner fa-spin',
2034
+ };
2035
+ const PHONE_REGEX = /^\+?[0-9\s\-()]{7,20}$/;
2036
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2037
+ // ==============================================================================
2038
+ // Component Definition
2039
+ // ==============================================================================
2040
+ class Input {
2041
+ // ==============================================================================
2042
+ // Inputs
2043
+ // ==============================================================================
2044
+ Id = input(crypto.randomUUID(), ...(ngDevMode ? [{ debugName: "Id" }] : []));
2045
+ iName = input(null, ...(ngDevMode ? [{ debugName: "iName" }] : []));
2046
+ label = input(null, ...(ngDevMode ? [{ debugName: "label" }] : []));
2047
+ hint = input(null, ...(ngDevMode ? [{ debugName: "hint" }] : []));
2048
+ placeholder = input(null, ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
2049
+ type = input('text', ...(ngDevMode ? [{ debugName: "type" }] : []));
2050
+ inputStyle = input('secondary', ...(ngDevMode ? [{ debugName: "inputStyle" }] : []));
2051
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2052
+ isReadonly = input(false, ...(ngDevMode ? [{ debugName: "isReadonly" }] : []));
2053
+ autocomplete = input('off', ...(ngDevMode ? [{ debugName: "autocomplete" }] : []));
2054
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
2055
+ inputmode = input(null, ...(ngDevMode ? [{ debugName: "inputmode" }] : []));
2056
+ icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : []));
2057
+ showSearchIcon = input(false, ...(ngDevMode ? [{ debugName: "showSearchIcon" }] : []));
2058
+ showLoaderIconOnSearchInput = input(false, ...(ngDevMode ? [{ debugName: "showLoaderIconOnSearchInput" }] : []));
2059
+ maxlength = input(null, ...(ngDevMode ? [{ debugName: "maxlength" }] : []));
2060
+ minlength = input(null, ...(ngDevMode ? [{ debugName: "minlength" }] : []));
2061
+ spellcheck = input(false, ...(ngDevMode ? [{ debugName: "spellcheck" }] : []));
2062
+ min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : []));
2063
+ max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : []));
2064
+ step = input(null, ...(ngDevMode ? [{ debugName: "step" }] : []));
2065
+ validateFns = input([], ...(ngDevMode ? [{ debugName: "validateFns" }] : []));
2066
+ formatFn = input((val) => val?.trim() ?? null, ...(ngDevMode ? [{ debugName: "formatFn" }] : []));
2067
+ autofocus = input(false, ...(ngDevMode ? [{ debugName: "autofocus" }] : []));
2068
+ searchDebounceDelay = input(300, ...(ngDevMode ? [{ debugName: "searchDebounceDelay" }] : []));
2069
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
2070
+ // ==============================================================================
2071
+ // ViewChild
2072
+ // ==============================================================================
2073
+ inputEl = viewChild('inputEl', ...(ngDevMode ? [{ debugName: "inputEl" }] : []));
2074
+ // ==============================================================================
2075
+ // Model
2076
+ // ==============================================================================
2077
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
2078
+ touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : [])); // Tracks if the user has interacted with the input
2079
+ // ==============================================================================
2080
+ // Outputs
2081
+ // ==============================================================================
2082
+ enterEv = output();
2083
+ focusEv = output();
2084
+ blurEv = output();
2085
+ changedEv = output();
2086
+ searchEv = output();
2087
+ clearedEv = output();
2088
+ keydownEv = output();
2089
+ // ==============================================================================
2090
+ // Internal State (Signals)
2091
+ // ==============================================================================
2092
+ showPassword = signal(false, ...(ngDevMode ? [{ debugName: "showPassword" }] : []));
2093
+ searchDebounceTimer;
2094
+ loaderIconOnSearchInput = signal(null, ...(ngDevMode ? [{ debugName: "loaderIconOnSearchInput" }] : [])); // Fixed typo: "Serach" → "Search"
2095
+ // ==============================================================================
2096
+ // Computed Properties
2097
+ // ==============================================================================
2098
+ disabledOrReadonly = computed(() => this.disabled() || this.isReadonly(), ...(ngDevMode ? [{ debugName: "disabledOrReadonly" }] : []));
2099
+ containerClasses = computed(() => {
2100
+ const baseClasses = 'zs:border zs:transition-all zs:duration-150 zs:focus-within:ring-2';
2101
+ const hasError = this.error().length;
2102
+ let styleConfig = FormPaletteMap.get(this.inputStyle()) ?? FormPaletteMap.get('secondary');
2103
+ if (hasError) {
2104
+ styleConfig = FormPaletteMap.get('danger');
2105
+ }
2106
+ const disabledClass = this.disabled() ? 'zs:opacity-60' : '';
2107
+ const interactionClass = this.disabledOrReadonly()
2108
+ ? 'zs:cursor-not-allowed'
2109
+ : 'zs:cursor-text';
2110
+ return [
2111
+ baseClasses,
2112
+ styleConfig.border,
2113
+ styleConfig.borderHover,
2114
+ styleConfig.inputBg,
2115
+ styleConfig.text,
2116
+ styleConfig.ring,
2117
+ disabledClass,
2118
+ interactionClass,
2119
+ ]
2120
+ .filter(Boolean)
2121
+ .join(' ');
2122
+ }, ...(ngDevMode ? [{ debugName: "containerClasses" }] : []));
2123
+ // Computed date icon
2124
+ isDate = computed(() => {
2125
+ const dateTypes = ['date', 'datetime-local', 'month', 'week', 'time'];
2126
+ return dateTypes.includes(this.type());
2127
+ }, ...(ngDevMode ? [{ debugName: "isDate" }] : []));
2128
+ dateIcon = computed(() => {
2129
+ if (this.icon())
2130
+ return '';
2131
+ return DATE_ICON_MAP[this.type()] || 'fas fa-calendar';
2132
+ }, ...(ngDevMode ? [{ debugName: "dateIcon" }] : []));
2133
+ showClear = computed(() => this.type() !== 'password' && !!this.value(), ...(ngDevMode ? [{ debugName: "showClear" }] : []));
2134
+ error = computed(() => {
2135
+ const val = this.value();
2136
+ const type = this.type();
2137
+ const required = this.required();
2138
+ const minlength = this.minlength();
2139
+ const maxlength = this.maxlength();
2140
+ const min = this.min();
2141
+ const max = this.max();
2142
+ // Only validate after user interaction
2143
+ if (!this.touched())
2144
+ return [];
2145
+ const errors = [];
2146
+ // Required validation
2147
+ if (required && !val) {
2148
+ errors.push('This field is required');
2149
+ }
2150
+ // Min length
2151
+ if (minlength !== null && val && val.length < minlength) {
2152
+ errors.push(`The value must be at least ${minlength} characters`);
2153
+ }
2154
+ // Max length
2155
+ if (maxlength !== null && val && val.length > maxlength) {
2156
+ errors.push(`The value must be at most ${maxlength} characters`);
2157
+ }
2158
+ // Email format
2159
+ if (type === 'email' && val && !EMAIL_REGEX.test(val)) {
2160
+ errors.push('Please enter a valid email address');
2161
+ }
2162
+ // Number range & validity
2163
+ if (type === 'number' && val) {
2164
+ const num = Number(val);
2165
+ if (Number.isNaN(num)) {
2166
+ errors.push('Please enter a valid number');
2167
+ }
2168
+ else {
2169
+ if (min !== null && num < Number(min)) {
2170
+ errors.push(`The value must be at least ${min}`);
2171
+ }
2172
+ if (max !== null && num > Number(max)) {
2173
+ errors.push(`The value must be at most ${max}`);
2174
+ }
2175
+ }
2176
+ }
2177
+ // Date/Time Range Validation
2178
+ if (this.isDate() && val) {
2179
+ const valueTime = new Date(val).getTime();
2180
+ const minDate = min ? new Date(min).getTime() : null;
2181
+ const maxDate = max ? new Date(max).getTime() : null;
2182
+ if (minDate !== null && valueTime < minDate) {
2183
+ errors.push(`The date must be on or after ${min}`);
2184
+ }
2185
+ if (maxDate !== null && valueTime > maxDate) {
2186
+ errors.push(`The date must be on or before ${max}`);
2187
+ }
2188
+ }
2189
+ // Phone format
2190
+ if (type === 'phone' && val && !PHONE_REGEX.test(val)) {
2191
+ errors.push('Please enter a valid phone number');
2192
+ }
2193
+ // URL validity
2194
+ if (type === 'url' && val) {
2195
+ try {
2196
+ new URL(val);
2197
+ }
2198
+ catch {
2199
+ errors.push('Please enter a valid URL');
2200
+ }
2201
+ }
2202
+ // Custom validator
2203
+ for (const fn of this.validateFns()) {
2204
+ const result = fn(val);
2205
+ if (Array.isArray(result))
2206
+ errors.push(...result);
2207
+ }
2208
+ return errors.length > 0 ? errors : [];
2209
+ }, ...(ngDevMode ? [{ debugName: "error" }] : []));
2210
+ supportsMinMaxStep = computed(() => {
2211
+ const t = this.type();
2212
+ return this.isDate() || ['number'].includes(t);
2213
+ }, ...(ngDevMode ? [{ debugName: "supportsMinMaxStep" }] : []));
2214
+ // ==============================================================================
2215
+ // Getters
2216
+ // ==============================================================================
2217
+ get actualType() {
2218
+ if (this.type() === 'phone')
2219
+ return 'tel';
2220
+ if (this.type() === 'search')
2221
+ return 'text';
2222
+ if (this.type() === 'password' && this.showPassword())
2223
+ return 'text';
2224
+ return this.type();
2225
+ }
2226
+ getSize(type) {
2227
+ return SIZE_CLASSES_MAP.get(type)?.[this.size()] ?? '';
2228
+ }
2229
+ // ==============================================================================
2230
+ // Lifecycle Hooks
2231
+ // ==============================================================================
2232
+ ngAfterViewInit() {
2233
+ if (this.autofocus()) {
2234
+ queueMicrotask(() => this.inputEl()?.nativeElement.focus());
2235
+ }
2236
+ }
2237
+ // ==============================================================================
2238
+ // Event Handlers
2239
+ // ==============================================================================
2240
+ focusInput(event) {
2241
+ const input = event.currentTarget.querySelector('input');
2242
+ input?.focus();
2243
+ }
2244
+ onInput(event) {
2245
+ if (this.disabledOrReadonly())
2246
+ return;
2247
+ const value = event.target.value;
2248
+ this.value.set(value);
2249
+ // Handle search input with debounce and loader
2250
+ if (this.type() === 'search') {
2251
+ if (this.showLoaderIconOnSearchInput()) {
2252
+ this.loaderIconOnSearchInput.set(ICONS.spinner);
2253
+ }
2254
+ if (this.searchDebounceTimer) {
2255
+ clearTimeout(this.searchDebounceTimer);
2256
+ }
2257
+ this.searchDebounceTimer = setTimeout(() => {
2258
+ this.searchEv.emit(this.value());
2259
+ this.loaderIconOnSearchInput.set(null);
2260
+ }, this.searchDebounceDelay());
2261
+ }
2262
+ }
2263
+ onEnter() {
2264
+ if (this.disabledOrReadonly())
2265
+ return;
2266
+ this.enterEv.emit();
2267
+ }
2268
+ onFocus() {
2269
+ this.focusEv.emit();
2270
+ }
2271
+ onBlur() {
2272
+ if (this.disabledOrReadonly())
2273
+ return;
2274
+ this.touched.set(true);
2275
+ this.value.set(this.formatFn()(this.value()));
2276
+ this.blurEv.emit();
2277
+ }
2278
+ onChange() {
2279
+ if (this.disabledOrReadonly())
2280
+ return;
2281
+ this.touched.set(true);
2282
+ this.emitChangeValue(this.value(), false);
2283
+ }
2284
+ onSearch() {
2285
+ if (this.disabledOrReadonly())
2286
+ return;
2287
+ this.searchEv.emit(this.value());
2288
+ }
2289
+ clear() {
2290
+ if (this.disabledOrReadonly())
2291
+ return;
2292
+ this.value.set(null);
2293
+ this.emitChangeValue(null, false);
2294
+ this.searchEv.emit(null);
2295
+ this.clearedEv.emit();
2296
+ }
2297
+ togglePassword() {
2298
+ if (this.disabledOrReadonly())
2299
+ return;
2300
+ this.showPassword.update((v) => !v);
2301
+ }
2302
+ onKeydown(event) {
2303
+ if (this.disabledOrReadonly())
2304
+ return;
2305
+ this.keydownEv.emit(event);
2306
+ }
2307
+ /** Forces the input to trigger a manual change event */
2308
+ forceChange(fromForce = true) {
2309
+ // Applies the same logic as natural change.
2310
+ this.touched.set(true);
2311
+ this.value.set(this.formatFn()(this.value()));
2312
+ this.emitChangeValue(this.value(), fromForce);
2313
+ }
2314
+ emitChangeValue(value, fromForce = true) {
2315
+ const valid = this.error().length === 0;
2316
+ this.changedEv.emit({ value, valid, fromForce });
2317
+ }
2318
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Input, deps: [], target: i0.ɵɵFactoryTarget.Component });
2319
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Input, isStandalone: true, selector: "ZS-input", inputs: { Id: { classPropertyName: "Id", publicName: "Id", isSignal: true, isRequired: false, transformFunction: null }, iName: { classPropertyName: "iName", publicName: "iName", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, inputStyle: { classPropertyName: "inputStyle", publicName: "inputStyle", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, isReadonly: { classPropertyName: "isReadonly", publicName: "isReadonly", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, showSearchIcon: { classPropertyName: "showSearchIcon", publicName: "showSearchIcon", isSignal: true, isRequired: false, transformFunction: null }, showLoaderIconOnSearchInput: { classPropertyName: "showLoaderIconOnSearchInput", publicName: "showLoaderIconOnSearchInput", isSignal: true, isRequired: false, transformFunction: null }, maxlength: { classPropertyName: "maxlength", publicName: "maxlength", isSignal: true, isRequired: false, transformFunction: null }, minlength: { classPropertyName: "minlength", publicName: "minlength", isSignal: true, isRequired: false, transformFunction: null }, spellcheck: { classPropertyName: "spellcheck", publicName: "spellcheck", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, validateFns: { classPropertyName: "validateFns", publicName: "validateFns", isSignal: true, isRequired: false, transformFunction: null }, formatFn: { classPropertyName: "formatFn", publicName: "formatFn", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null }, searchDebounceDelay: { classPropertyName: "searchDebounceDelay", publicName: "searchDebounceDelay", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", enterEv: "enterEv", focusEv: "focusEv", blurEv: "blurEv", changedEv: "changedEv", searchEv: "searchEv", clearedEv: "clearedEv", keydownEv: "keydownEv" }, viewQueries: [{ propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- ==============================================\n Input Field Component (ZS-Input)\n ============================================== -->\n\n<div class=\"zs:w-full\">\n\n <!-- ========================= Label & Hint ========================= -->\n <ZS-label [class]=\"(label() || hint()) ? 'zs:mb-1' : ''\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [required]=\"required()\"\n [hintId]=\"Id() + '-hint'\"\n [size]=\"size()\"\n [for]=\"Id()\"\n >\n </ZS-label>\n\n <!-- ========================= Input Container ========================= -->\n <div\n class=\"zs:relative zs:flex zs:items-center zs:w-full\"\n [class]=\"containerClasses() + ' ' + getSize('container')\"\n (click)=\"focusInput($event)\"\n >\n\n <!-- ========================= Left Icon ========================= -->\n @if (icon() || loaderIconOnSearchInput() || isDate()) {\n <i\n [ngClass]=\"(\n loaderIconOnSearchInput()\n ? loaderIconOnSearchInput()\n : \n icon() ? icon() : dateIcon()\n ) + ' ' + getSize('leftIcon')\"\n aria-hidden=\"true\"\n ></i>\n }\n\n <!-- ========================= Input Field ========================= -->\n <input\n #inputEl\n [id]=\"Id()\"\n [name]=\"iName()\"\n [type]=\"actualType\"\n [attr.placeholder]=\"placeholder()\"\n [value]=\"value()\"\n [disabled]=\"disabled() ? true : null\"\n [attr.min]=\"supportsMinMaxStep() ? min() : null\"\n [attr.max]=\"supportsMinMaxStep() ? max() : null\"\n [attr.step]=\"supportsMinMaxStep() ? step() : null\"\n [attr.required]=\"required() ? true : null\"\n (input)=\"onInput($event)\"\n (keydown.enter)=\"onEnter()\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (change)=\"onChange()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.maxlength]=\"maxlength()\"\n [attr.minlength]=\"minlength()\"\n [attr.autofocus]=\"autofocus() ? true : null\"\n [attr.readonly]=\"isReadonly() ? true : null\"\n [attr.aria-invalid]=\"!!error()\"\n [attr.inputmode]=\"inputmode()\"\n [spellcheck]=\"spellcheck()\"\n class=\"zs:flex-1 zs:bg-transparent zs:outline-hidden zs:caret-current zs:w-full\"\n [class]=\"getSize('field')\"\n [attr.aria-describedby]=\"\n (hint() ? Id() + '-hint' : '') +\n (hint() && error() ? ' ' : '') +\n (error() ? Id() + '-error' : '')\n \"\n />\n\n <!-- ========================= Spacer (Maintains Height) ========================= -->\n <div class=\"zs:h-6\"></div>\n\n <!-- ========================= Password Toggle ========================= -->\n @if (type() === 'password') {\n <button\n type=\"button\"\n (click)=\"togglePassword()\"\n [attr.aria-pressed]=\"showPassword()\"\n class=\"zs:ml-1 zs:px-1 zs:text-slate-500 zs:hover:text-slate-700 \n zs:dark:text-slate-400 zs:dark:hover:text-slate-200\"\n [attr.aria-label]=\"showPassword() ? 'Hide password' : 'Show password'\"\n >\n <i\n class=\"fa\"\n [class]=\"(showPassword() ? 'fa-eye-slash' : 'fa-eye') + ' ' + getSize('rightIcon')\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n\n <!-- ========================= Clear Button ========================= -->\n @if (showClear() && !disabledOrReadonly()) {\n <button\n type=\"button\"\n (click)=\"clear()\"\n class=\"zs:ml-1 zs:px-1 zs:text-slate-500 zs:hover:text-slate-700 \n zs:dark:text-slate-400 zs:dark:hover:text-slate-200\"\n [attr.aria-label]=\"'Clear ' + (label() || 'input')\"\n >\n <i\n class=\"fa fa-times\"\n [class]=\"getSize('rightIcon')\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n\n <!-- ========================= Search Icon ========================= -->\n @if (type() === 'search' || showSearchIcon()) {\n <button\n type=\"button\"\n (click)=\"onSearch()\"\n class=\"zs:ml-1 zs:px-1 zs:text-slate-500 zs:hover:text-slate-700 \n zs:dark:text-slate-400 zs:dark:hover:text-slate-200\"\n aria-label=\"Search\"\n >\n <i\n class=\"fa fa-search\"\n [class]=\"getSize('rightIcon')\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n\n </div>\n\n <!-- ========================= Error Messages ========================= -->\n <ZS-input-errors\n [Id]=\"Id()\"\n [errors]=\"[error()]\"\n >\n </ZS-input-errors>\n\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: Label, selector: "ZS-label", inputs: ["label", "hint", "hintId", "size", "required", "for"] }, { kind: "component", type: InputErrors, selector: "ZS-input-errors", inputs: ["Id", "errors"] }] });
2320
+ }
2321
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Input, decorators: [{
2322
+ type: Component,
2323
+ args: [{ selector: 'ZS-input', imports: [CommonModule, Label, InputErrors], template: "<!-- ==============================================\n Input Field Component (ZS-Input)\n ============================================== -->\n\n<div class=\"zs:w-full\">\n\n <!-- ========================= Label & Hint ========================= -->\n <ZS-label [class]=\"(label() || hint()) ? 'zs:mb-1' : ''\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [required]=\"required()\"\n [hintId]=\"Id() + '-hint'\"\n [size]=\"size()\"\n [for]=\"Id()\"\n >\n </ZS-label>\n\n <!-- ========================= Input Container ========================= -->\n <div\n class=\"zs:relative zs:flex zs:items-center zs:w-full\"\n [class]=\"containerClasses() + ' ' + getSize('container')\"\n (click)=\"focusInput($event)\"\n >\n\n <!-- ========================= Left Icon ========================= -->\n @if (icon() || loaderIconOnSearchInput() || isDate()) {\n <i\n [ngClass]=\"(\n loaderIconOnSearchInput()\n ? loaderIconOnSearchInput()\n : \n icon() ? icon() : dateIcon()\n ) + ' ' + getSize('leftIcon')\"\n aria-hidden=\"true\"\n ></i>\n }\n\n <!-- ========================= Input Field ========================= -->\n <input\n #inputEl\n [id]=\"Id()\"\n [name]=\"iName()\"\n [type]=\"actualType\"\n [attr.placeholder]=\"placeholder()\"\n [value]=\"value()\"\n [disabled]=\"disabled() ? true : null\"\n [attr.min]=\"supportsMinMaxStep() ? min() : null\"\n [attr.max]=\"supportsMinMaxStep() ? max() : null\"\n [attr.step]=\"supportsMinMaxStep() ? step() : null\"\n [attr.required]=\"required() ? true : null\"\n (input)=\"onInput($event)\"\n (keydown.enter)=\"onEnter()\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (change)=\"onChange()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.maxlength]=\"maxlength()\"\n [attr.minlength]=\"minlength()\"\n [attr.autofocus]=\"autofocus() ? true : null\"\n [attr.readonly]=\"isReadonly() ? true : null\"\n [attr.aria-invalid]=\"!!error()\"\n [attr.inputmode]=\"inputmode()\"\n [spellcheck]=\"spellcheck()\"\n class=\"zs:flex-1 zs:bg-transparent zs:outline-hidden zs:caret-current zs:w-full\"\n [class]=\"getSize('field')\"\n [attr.aria-describedby]=\"\n (hint() ? Id() + '-hint' : '') +\n (hint() && error() ? ' ' : '') +\n (error() ? Id() + '-error' : '')\n \"\n />\n\n <!-- ========================= Spacer (Maintains Height) ========================= -->\n <div class=\"zs:h-6\"></div>\n\n <!-- ========================= Password Toggle ========================= -->\n @if (type() === 'password') {\n <button\n type=\"button\"\n (click)=\"togglePassword()\"\n [attr.aria-pressed]=\"showPassword()\"\n class=\"zs:ml-1 zs:px-1 zs:text-slate-500 zs:hover:text-slate-700 \n zs:dark:text-slate-400 zs:dark:hover:text-slate-200\"\n [attr.aria-label]=\"showPassword() ? 'Hide password' : 'Show password'\"\n >\n <i\n class=\"fa\"\n [class]=\"(showPassword() ? 'fa-eye-slash' : 'fa-eye') + ' ' + getSize('rightIcon')\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n\n <!-- ========================= Clear Button ========================= -->\n @if (showClear() && !disabledOrReadonly()) {\n <button\n type=\"button\"\n (click)=\"clear()\"\n class=\"zs:ml-1 zs:px-1 zs:text-slate-500 zs:hover:text-slate-700 \n zs:dark:text-slate-400 zs:dark:hover:text-slate-200\"\n [attr.aria-label]=\"'Clear ' + (label() || 'input')\"\n >\n <i\n class=\"fa fa-times\"\n [class]=\"getSize('rightIcon')\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n\n <!-- ========================= Search Icon ========================= -->\n @if (type() === 'search' || showSearchIcon()) {\n <button\n type=\"button\"\n (click)=\"onSearch()\"\n class=\"zs:ml-1 zs:px-1 zs:text-slate-500 zs:hover:text-slate-700 \n zs:dark:text-slate-400 zs:dark:hover:text-slate-200\"\n aria-label=\"Search\"\n >\n <i\n class=\"fa fa-search\"\n [class]=\"getSize('rightIcon')\"\n aria-hidden=\"true\"\n ></i>\n </button>\n }\n\n </div>\n\n <!-- ========================= Error Messages ========================= -->\n <ZS-input-errors\n [Id]=\"Id()\"\n [errors]=\"[error()]\"\n >\n </ZS-input-errors>\n\n</div>" }]
2324
+ }], propDecorators: { Id: [{ type: i0.Input, args: [{ isSignal: true, alias: "Id", required: false }] }], iName: [{ type: i0.Input, args: [{ isSignal: true, alias: "iName", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], inputStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputStyle", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], isReadonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "isReadonly", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], showSearchIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearchIcon", required: false }] }], showLoaderIconOnSearchInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLoaderIconOnSearchInput", required: false }] }], maxlength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxlength", required: false }] }], minlength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minlength", required: false }] }], spellcheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "spellcheck", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], validateFns: [{ type: i0.Input, args: [{ isSignal: true, alias: "validateFns", required: false }] }], formatFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatFn", required: false }] }], autofocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autofocus", required: false }] }], searchDebounceDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchDebounceDelay", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], inputEl: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], enterEv: [{ type: i0.Output, args: ["enterEv"] }], focusEv: [{ type: i0.Output, args: ["focusEv"] }], blurEv: [{ type: i0.Output, args: ["blurEv"] }], changedEv: [{ type: i0.Output, args: ["changedEv"] }], searchEv: [{ type: i0.Output, args: ["searchEv"] }], clearedEv: [{ type: i0.Output, args: ["clearedEv"] }], keydownEv: [{ type: i0.Output, args: ["keydownEv"] }] } });
2325
+
2326
+ // ==============================================
2327
+ // Imports
2328
+ // ==============================================
2329
+ // ==============================================
2330
+ // Component Metadata
2331
+ // ==============================================
2332
+ class Range {
2333
+ // ==============================================
2334
+ // Inputs
2335
+ // ==============================================
2336
+ Id = input(crypto.randomUUID(), ...(ngDevMode ? [{ debugName: "Id" }] : []));
2337
+ label = input(null, ...(ngDevMode ? [{ debugName: "label" }] : []));
2338
+ hint = input(null, ...(ngDevMode ? [{ debugName: "hint" }] : []));
2339
+ min = input(10, ...(ngDevMode ? [{ debugName: "min" }] : []));
2340
+ max = input(400, ...(ngDevMode ? [{ debugName: "max" }] : []));
2341
+ step = input(10, ...(ngDevMode ? [{ debugName: "step" }] : []));
2342
+ inputStyle = input('secondary', ...(ngDevMode ? [{ debugName: "inputStyle" }] : []));
2343
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
2344
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2345
+ isReadonly = input(false, ...(ngDevMode ? [{ debugName: "isReadonly" }] : []));
2346
+ showValue = input(true, ...(ngDevMode ? [{ debugName: "showValue" }] : []));
2347
+ // ==============================================
2348
+ // Model
2349
+ // ==============================================
2350
+ value = model(200, ...(ngDevMode ? [{ debugName: "value" }] : []));
2351
+ // ==============================================
2352
+ // References & Internal State
2353
+ // ==============================================
2354
+ trackRef = viewChild('track', ...(ngDevMode ? [{ debugName: "trackRef" }] : []));
2355
+ dragging = signal(false, ...(ngDevMode ? [{ debugName: "dragging" }] : []));
2356
+ // ==============================================
2357
+ // Computed Properties
2358
+ // ==============================================
2359
+ disabledOrReadonly = computed(() => this.disabled() || this.isReadonly(), ...(ngDevMode ? [{ debugName: "disabledOrReadonly" }] : []));
2360
+ palette = computed(() => {
2361
+ return FormPaletteMap.get(this.inputStyle()) ?? FormPaletteMap.get('secondary');
2362
+ }, ...(ngDevMode ? [{ debugName: "palette" }] : []));
2363
+ percent = computed(() => {
2364
+ const range = this.max() - this.min();
2365
+ return ((this.value() - this.min()) / range) * 100;
2366
+ }, ...(ngDevMode ? [{ debugName: "percent" }] : []));
2367
+ rangeSizeClasses = (type) => {
2368
+ const sizeClasses = {
2369
+ height: {
2370
+ sm: 'zs:h-1.5',
2371
+ md: 'zs:h-2.5',
2372
+ lg: 'zs:h-3.5',
2373
+ },
2374
+ size: {
2375
+ sm: 'zs:size-3.5',
2376
+ md: 'zs:size-5',
2377
+ lg: 'zs:size-7',
2378
+ }
2379
+ };
2380
+ return sizeClasses[type][this.size()];
2381
+ };
2382
+ dotCLasses = computed(() => {
2383
+ const sizeClasses = {
2384
+ sm: 'zs:text-[6px]',
2385
+ md: 'zs:text-[10px]',
2386
+ lg: 'zs:text-[14px]',
2387
+ };
2388
+ return sizeClasses[this.size()];
2389
+ }, ...(ngDevMode ? [{ debugName: "dotCLasses" }] : []));
2390
+ gapCLasses = computed(() => {
2391
+ const sizeClasses = {
2392
+ sm: 'zs:gap-3.5',
2393
+ md: 'zs:gap-4',
2394
+ lg: 'zs:gap-6',
2395
+ };
2396
+ return sizeClasses[this.size()];
2397
+ }, ...(ngDevMode ? [{ debugName: "gapCLasses" }] : []));
2398
+ rangeClasses = computed(() => {
2399
+ const base = 'zs:relative zs:w-full zs:rounded-full zs:cursor-pointer';
2400
+ const sizeClasses = this.rangeSizeClasses('height');
2401
+ const disabledClass = this.disabled() ? 'zs:opacity-60' : '';
2402
+ const interactionClass = !this.disabledOrReadonly() ? 'zs:group' : '';
2403
+ return [
2404
+ sizeClasses,
2405
+ this.palette().border,
2406
+ this.palette().inputBg,
2407
+ this.palette().text,
2408
+ base,
2409
+ disabledClass,
2410
+ interactionClass
2411
+ ].join(' ');
2412
+ }, ...(ngDevMode ? [{ debugName: "rangeClasses" }] : []));
2413
+ ThumbClasses = computed(() => {
2414
+ return [
2415
+ this.palette().btnBG,
2416
+ this.palette().btnBGHover,
2417
+ this.dragging() ? 'zs:scale-110 zs:shadow-lg' : '',
2418
+ this.rangeSizeClasses('size')
2419
+ ].join(' ');
2420
+ }, ...(ngDevMode ? [{ debugName: "ThumbClasses" }] : []));
2421
+ // ==============================================
2422
+ // Event Handlers
2423
+ // ==============================================
2424
+ onMouseDown(event) {
2425
+ if (this.disabledOrReadonly())
2426
+ return;
2427
+ this.dragging.set(true);
2428
+ this.updateValueFromEvent(event);
2429
+ }
2430
+ onMouseMove(event) {
2431
+ if (this.disabledOrReadonly() || !this.dragging())
2432
+ return;
2433
+ this.updateValueFromEvent(event);
2434
+ }
2435
+ onMouseUp() {
2436
+ if (this.disabledOrReadonly())
2437
+ return;
2438
+ this.dragging.set(false);
2439
+ }
2440
+ // ==============================================
2441
+ // Private Helpers
2442
+ // ==============================================
2443
+ updateValueFromEvent(event) {
2444
+ if (this.disabledOrReadonly())
2445
+ return;
2446
+ const track = this.trackRef()?.nativeElement;
2447
+ if (!track)
2448
+ return;
2449
+ const rect = track.getBoundingClientRect();
2450
+ const x = Math.min(Math.max(event.clientX - rect.left, 0), rect.width);
2451
+ const percent = x / rect.width;
2452
+ const rawValue = this.min() + percent * (this.max() - this.min());
2453
+ const stepped = Math.round(rawValue / this.step()) * this.step();
2454
+ this.value.set(Math.min(this.max(), Math.max(this.min(), stepped)));
2455
+ }
2456
+ calcThumbPosition() {
2457
+ const p = this.percent();
2458
+ const track = this.trackRef()?.nativeElement;
2459
+ if (!track)
2460
+ return `${p}%`;
2461
+ const trackWidth = track.offsetWidth; // عرض الشريط بالبكسل
2462
+ const displacementSizes = {
2463
+ sm: 6,
2464
+ md: 10,
2465
+ lg: 14,
2466
+ };
2467
+ const displacementPx = displacementSizes[this.size()];
2468
+ const displacementPercent = (displacementPx / trackWidth) * 100;
2469
+ const displacement = p - displacementPercent;
2470
+ return `${displacement}%`;
2471
+ }
2472
+ // ==============================================
2473
+ // Lifecycle & Side Effects
2474
+ // ==============================================
2475
+ constructor() {
2476
+ const mouseUpHandler = this.onMouseUp.bind(this);
2477
+ const mouseMoveHandler = this.onMouseMove.bind(this);
2478
+ effect(() => {
2479
+ if (!this.dragging())
2480
+ return;
2481
+ window.addEventListener('mouseup', mouseUpHandler);
2482
+ window.addEventListener('mousemove', mouseMoveHandler);
2483
+ return () => {
2484
+ window.removeEventListener('mouseup', mouseUpHandler);
2485
+ window.removeEventListener('mousemove', mouseMoveHandler);
2486
+ };
2487
+ });
2488
+ }
2489
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Range, deps: [], target: i0.ɵɵFactoryTarget.Component });
2490
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Range, isStandalone: true, selector: "ZS-range", inputs: { Id: { classPropertyName: "Id", publicName: "Id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, inputStyle: { classPropertyName: "inputStyle", publicName: "inputStyle", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, isReadonly: { classPropertyName: "isReadonly", publicName: "isReadonly", isSignal: true, isRequired: false, transformFunction: null }, showValue: { classPropertyName: "showValue", publicName: "showValue", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, viewQueries: [{ propertyName: "trackRef", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- ========================= Label ========================= -->\n<div class=\"zs:flex zs:flex-col zs:w-full zs:select-none\">\n <ZS-label [class]=\"(label() || hint()) ? 'zs:mb-1' : ''\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [hintId]=\"Id() + '-hint'\"\n [size]=\"size()\"\n [for]=\"Id()\"\n ></ZS-label>\n\n <!-- ========================= Slider Container ========================= -->\n <div class=\"zs:flex zs:items-center\"\n [class]=\"gapCLasses()\">\n \n <!-- ========================= Track ========================= -->\n <div\n #track\n [class]=\"rangeClasses()\"\n (mousedown)=\"onMouseDown($event)\"\n >\n \n <!-- ========================= Track Background ========================= -->\n <div\n class=\"zs:absolute zs:inset-0 zs:rounded-full zs:bg-linear-to-r zs:from-gray-200 zs:to-gray-300 zs:transition-all zs:duration-300 zs:group-hover:brightness-110\"\n [ngClass]=\"[\n ['secondary', 'dark'].includes(inputStyle()) ? 'zs:dark:from-gray-500 zs:dark:to-gray-600' : 'zs:dark:from-gray-700 zs:dark:to-gray-800',\n disabledOrReadonly() ? 'zs:cursor-not-allowed' : ''\n ]\"\n ></div>\n\n <!-- ========================= Fill (Progress) ========================= -->\n <div\n class=\"zs:absolute zs:left-0 zs:rounded-full zs:top-0 zs:h-full zs:transition-all zs:duration-200 zs:ease-out zs:shadow-sm\"\n [style.width.%]=\"percent()\"\n [ngClass]=\"[\n disabledOrReadonly() ? 'zs:cursor-not-allowed' : '',\n palette().btnBG,\n palette().btnBGHover\n ]\"\n ></div>\n\n <!-- ========================= Thumb (Handle) ========================= -->\n <div\n class=\"zs:absolute zs:top-1/2 zs:rounded-full zs:flex zs:items-center zs:justify-center \n zs:-translate-y-1/2 zs:transition-all zs:duration-200 zs:ease-out zs:shadow-md \n zs:group-hover:scale-110\"\n [style.left]=\"calcThumbPosition()\"\n [ngClass]=\"[ThumbClasses(), disabledOrReadonly() ? 'zs:cursor-not-allowed' : '']\"\n role=\"slider\"\n tabindex=\"0\"\n [attr.id]=\"Id()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"value()\"\n [attr.aria-disabled]=\"disabledOrReadonly() || null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n [attr.aria-labelledby]=\"Id() + '-label'\"\n [attr.aria-describedby]=\"hint() ? Id() + '-hint' : null\"\n aria-orientation=\"horizontal\"\n >\n <i class=\"fa-solid fa-circle zs:text-white\" [ngClass]=\"dotCLasses()\"></i>\n </div>\n </div>\n\n <!-- ========================= Value Display ========================= -->\n @if (showValue()) {\n <div class=\"zs:text-sm zs:text-gray-600 zs:dark:text-gray-300 zs:font-medium\">\n {{ value() }}\n </div>\n }\n </div>\n</div>", styles: [".thumb-active{transition:transform .1s ease-in-out}\n"], dependencies: [{ kind: "component", type: Label, selector: "ZS-label", inputs: ["label", "hint", "hintId", "size", "required", "for"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2491
+ }
2492
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Range, decorators: [{
2493
+ type: Component,
2494
+ args: [{ selector: 'ZS-range', imports: [Label, CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- ========================= Label ========================= -->\n<div class=\"zs:flex zs:flex-col zs:w-full zs:select-none\">\n <ZS-label [class]=\"(label() || hint()) ? 'zs:mb-1' : ''\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [hintId]=\"Id() + '-hint'\"\n [size]=\"size()\"\n [for]=\"Id()\"\n ></ZS-label>\n\n <!-- ========================= Slider Container ========================= -->\n <div class=\"zs:flex zs:items-center\"\n [class]=\"gapCLasses()\">\n \n <!-- ========================= Track ========================= -->\n <div\n #track\n [class]=\"rangeClasses()\"\n (mousedown)=\"onMouseDown($event)\"\n >\n \n <!-- ========================= Track Background ========================= -->\n <div\n class=\"zs:absolute zs:inset-0 zs:rounded-full zs:bg-linear-to-r zs:from-gray-200 zs:to-gray-300 zs:transition-all zs:duration-300 zs:group-hover:brightness-110\"\n [ngClass]=\"[\n ['secondary', 'dark'].includes(inputStyle()) ? 'zs:dark:from-gray-500 zs:dark:to-gray-600' : 'zs:dark:from-gray-700 zs:dark:to-gray-800',\n disabledOrReadonly() ? 'zs:cursor-not-allowed' : ''\n ]\"\n ></div>\n\n <!-- ========================= Fill (Progress) ========================= -->\n <div\n class=\"zs:absolute zs:left-0 zs:rounded-full zs:top-0 zs:h-full zs:transition-all zs:duration-200 zs:ease-out zs:shadow-sm\"\n [style.width.%]=\"percent()\"\n [ngClass]=\"[\n disabledOrReadonly() ? 'zs:cursor-not-allowed' : '',\n palette().btnBG,\n palette().btnBGHover\n ]\"\n ></div>\n\n <!-- ========================= Thumb (Handle) ========================= -->\n <div\n class=\"zs:absolute zs:top-1/2 zs:rounded-full zs:flex zs:items-center zs:justify-center \n zs:-translate-y-1/2 zs:transition-all zs:duration-200 zs:ease-out zs:shadow-md \n zs:group-hover:scale-110\"\n [style.left]=\"calcThumbPosition()\"\n [ngClass]=\"[ThumbClasses(), disabledOrReadonly() ? 'zs:cursor-not-allowed' : '']\"\n role=\"slider\"\n tabindex=\"0\"\n [attr.id]=\"Id()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"value()\"\n [attr.aria-disabled]=\"disabledOrReadonly() || null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n [attr.aria-labelledby]=\"Id() + '-label'\"\n [attr.aria-describedby]=\"hint() ? Id() + '-hint' : null\"\n aria-orientation=\"horizontal\"\n >\n <i class=\"fa-solid fa-circle zs:text-white\" [ngClass]=\"dotCLasses()\"></i>\n </div>\n </div>\n\n <!-- ========================= Value Display ========================= -->\n @if (showValue()) {\n <div class=\"zs:text-sm zs:text-gray-600 zs:dark:text-gray-300 zs:font-medium\">\n {{ value() }}\n </div>\n }\n </div>\n</div>", styles: [".thumb-active{transition:transform .1s ease-in-out}\n"] }]
2495
+ }], ctorParameters: () => [], propDecorators: { Id: [{ type: i0.Input, args: [{ isSignal: true, alias: "Id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], inputStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputStyle", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], isReadonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "isReadonly", required: false }] }], showValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValue", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], trackRef: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
2496
+
2497
+ // =================================================================================================
2498
+ // Imports
2499
+ // =================================================================================================
2500
+ // =================================================================================================
2501
+ // Component Declaration
2502
+ // =================================================================================================
2503
+ class Select {
2504
+ zIndices = zIndices;
2505
+ // =================================================================================================
2506
+ // Inputs
2507
+ // =================================================================================================
2508
+ Id = input(crypto.randomUUID(), ...(ngDevMode ? [{ debugName: "Id" }] : []));
2509
+ items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
2510
+ label = input(null, ...(ngDevMode ? [{ debugName: "label" }] : []));
2511
+ hint = input(null, ...(ngDevMode ? [{ debugName: "hint" }] : []));
2512
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
2513
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2514
+ isReadonly = input(false, ...(ngDevMode ? [{ debugName: "isReadonly" }] : []));
2515
+ inputStyle = input('secondary', ...(ngDevMode ? [{ debugName: "inputStyle" }] : []));
2516
+ placeholder = input('Select an option...', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
2517
+ showSearch = input(true, ...(ngDevMode ? [{ debugName: "showSearch" }] : []));
2518
+ searchPlaceholder = input('Search...', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : []));
2519
+ noResultsText = input('No results found', ...(ngDevMode ? [{ debugName: "noResultsText" }] : []));
2520
+ showClearButton = input(true, ...(ngDevMode ? [{ debugName: "showClearButton" }] : []));
2521
+ searchDebounceDelay = input(300, ...(ngDevMode ? [{ debugName: "searchDebounceDelay" }] : []));
2522
+ showLoaderIconOnSearchInput = input(false, ...(ngDevMode ? [{ debugName: "showLoaderIconOnSearchInput" }] : []));
2523
+ preselectedIds = input([], ...(ngDevMode ? [{ debugName: "preselectedIds" }] : []));
2524
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
2525
+ validateFns = input([], ...(ngDevMode ? [{ debugName: "validateFns" }] : []));
2526
+ // =================================================================================================
2527
+ // Model (Two-way Binding)
2528
+ // =================================================================================================
2529
+ selectedItems = model([], ...(ngDevMode ? [{ debugName: "selectedItems" }] : []));
2530
+ touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : [])); // Tracks if the user has interacted with the input
2531
+ // =================================================================================================
2532
+ // Outputs
2533
+ // =================================================================================================
2534
+ selectedItemsEv = output();
2535
+ selectionClearedEv = output();
2536
+ // =================================================================================================
2537
+ // Local Signals
2538
+ // =================================================================================================
2539
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
2540
+ searchQuery = signal(null, ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
2541
+ // =================================================================================================
2542
+ // Computed Signals
2543
+ // =================================================================================================
2544
+ styleEntry = computed(() => {
2545
+ const hasError = this.error().length;
2546
+ let styleEntry = FormPaletteMap.get(this.inputStyle()) ?? FormPaletteMap.get('secondary');
2547
+ if (hasError) {
2548
+ styleEntry = FormPaletteMap.get('danger');
2549
+ }
2550
+ return styleEntry;
2551
+ }, ...(ngDevMode ? [{ debugName: "styleEntry" }] : []));
2552
+ disabledOrReadonly = computed(() => this.disabled() || this.isReadonly(), ...(ngDevMode ? [{ debugName: "disabledOrReadonly" }] : []));
2553
+ filteredItems = computed(() => {
2554
+ const query = this.searchQuery();
2555
+ if (!query)
2556
+ return this.items();
2557
+ const lowerQuery = query.toLowerCase();
2558
+ return this.items().filter(item => item.name.toLowerCase().includes(lowerQuery));
2559
+ }, ...(ngDevMode ? [{ debugName: "filteredItems" }] : []));
2560
+ containerClasses = computed(() => {
2561
+ const base = `
2562
+ zs:border zs:transition-all zs:duration-150
2563
+ zs:flex zs:items-center zs:justify-between
2564
+ zs:w-full zs:min-w-48 zs:px-3 zs:py-2
2565
+ zs:rounded-lg zs:shadow-sm
2566
+ `.trim();
2567
+ const disabledCls = this.disabled() ? 'zs:opacity-60' : '';
2568
+ const cursorCls = this.disabledOrReadonly()
2569
+ ? 'zs:cursor-not-allowed'
2570
+ : 'zs:cursor-text';
2571
+ return [
2572
+ base,
2573
+ this.styleEntry().border,
2574
+ this.styleEntry().inputBg,
2575
+ this.styleEntry().text,
2576
+ this.styleEntry().borderHover,
2577
+ disabledCls,
2578
+ cursorCls
2579
+ ].filter(Boolean).join(' ');
2580
+ }, ...(ngDevMode ? [{ debugName: "containerClasses" }] : []));
2581
+ clearClass = computed(() => {
2582
+ const base = 'zs:mt-2 zs:text-sm zs:flex zs:items-center zs:transition-colors';
2583
+ return [base, this.styleEntry().text, this.styleEntry().textHover].filter(Boolean).join(' ');
2584
+ }, ...(ngDevMode ? [{ debugName: "clearClass" }] : []));
2585
+ showItemsClass = computed(() => {
2586
+ return this.styleEntry()?.bgSelect ?? '';
2587
+ }, ...(ngDevMode ? [{ debugName: "showItemsClass" }] : []));
2588
+ error = computed(() => {
2589
+ const selectedItems = this.selectedItems();
2590
+ const required = this.required();
2591
+ // Only validate after user interaction
2592
+ if (!this.touched())
2593
+ return [];
2594
+ const errors = [];
2595
+ // Required validation
2596
+ if (required && !selectedItems.length) {
2597
+ errors.push('This field is required');
2598
+ }
2599
+ // Custom validator
2600
+ for (const fn of this.validateFns()) {
2601
+ const result = fn(selectedItems);
2602
+ if (Array.isArray(result))
2603
+ errors.push(...result);
2604
+ }
2605
+ return errors.length > 0 ? errors : [];
2606
+ }, ...(ngDevMode ? [{ debugName: "error" }] : []));
2607
+ // =================================================================================================
2608
+ // Utility Methods
2609
+ // =================================================================================================
2610
+ getBgSelectClasses = (selected) => {
2611
+ return selected
2612
+ ? `${this.styleEntry().bgSelect} zs:hover:opacity-80`
2613
+ : 'zs:hover:bg-gray-200/50 zs:dark:hover:bg-gray-600/40';
2614
+ };
2615
+ // =================================================================================================
2616
+ // Lifecycle & Effects
2617
+ // =================================================================================================
2618
+ constructor() {
2619
+ effect(() => {
2620
+ const ids = this.preselectedIds();
2621
+ const items = ids
2622
+ ?.map(id => this.items().find(item => item.id === id))
2623
+ .filter((item) => item !== undefined) ?? [];
2624
+ if (items.length > 0) {
2625
+ this.selectItem(items, true);
2626
+ }
2627
+ else if (ids.length === 0) {
2628
+ this.clearSelection();
2629
+ }
2630
+ });
2631
+ }
2632
+ // =================================================================================================
2633
+ // Public Methods
2634
+ // =================================================================================================
2635
+ toggleDropdown() {
2636
+ if (this.disabledOrReadonly())
2637
+ return;
2638
+ this.isOpen.set(!this.isOpen());
2639
+ if (this.isOpen()) {
2640
+ this.searchQuery.set(null);
2641
+ }
2642
+ if (!this.isOpen())
2643
+ this.touched.set(true);
2644
+ }
2645
+ selectItem(items, isPreselectedIds = false) {
2646
+ if (!items?.length || !items[0])
2647
+ return;
2648
+ if (this.multiple()) {
2649
+ this.selectedItems.update(current => {
2650
+ const existing = current ?? [];
2651
+ const clicked = items[0];
2652
+ const alreadySelected = existing.some(i => i?.id === clicked.id);
2653
+ if (alreadySelected) {
2654
+ return existing.filter(i => i?.id !== clicked.id);
2655
+ }
2656
+ else {
2657
+ return [...existing, clicked];
2658
+ }
2659
+ });
2660
+ }
2661
+ else {
2662
+ this.selectedItems.set([items[0]]);
2663
+ this.isOpen.set(false);
2664
+ this.searchQuery.set(null);
2665
+ }
2666
+ if (isPreselectedIds)
2667
+ return;
2668
+ this.touched.set(true);
2669
+ this.emitChangeValue(this.selectedItems(), false);
2670
+ }
2671
+ clearSelection() {
2672
+ if (this.disabledOrReadonly())
2673
+ return;
2674
+ this.selectedItems.set([]);
2675
+ this.selectionClearedEv.emit();
2676
+ }
2677
+ inSelectItems(item) {
2678
+ if (!item)
2679
+ return false;
2680
+ return this.selectedItems()?.some(i => i?.id === item.id) ?? false;
2681
+ }
2682
+ trackByFn(_index, item) {
2683
+ return item.id;
2684
+ }
2685
+ /** Forces the input to trigger a manual change event */
2686
+ forceChange(fromForce = true) {
2687
+ // Applies the same logic as natural change.
2688
+ this.touched.set(true);
2689
+ this.emitChangeValue(this.selectedItems(), fromForce);
2690
+ }
2691
+ emitChangeValue(value, fromForce = true) {
2692
+ const valid = this.error().length === 0;
2693
+ this.selectedItemsEv.emit({ value, valid, fromForce });
2694
+ }
2695
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Select, deps: [], target: i0.ɵɵFactoryTarget.Component });
2696
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: Select, isStandalone: true, selector: "ZS-select", inputs: { Id: { classPropertyName: "Id", publicName: "Id", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, isReadonly: { classPropertyName: "isReadonly", publicName: "isReadonly", isSignal: true, isRequired: false, transformFunction: null }, inputStyle: { classPropertyName: "inputStyle", publicName: "inputStyle", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, noResultsText: { classPropertyName: "noResultsText", publicName: "noResultsText", isSignal: true, isRequired: false, transformFunction: null }, showClearButton: { classPropertyName: "showClearButton", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null }, searchDebounceDelay: { classPropertyName: "searchDebounceDelay", publicName: "searchDebounceDelay", isSignal: true, isRequired: false, transformFunction: null }, showLoaderIconOnSearchInput: { classPropertyName: "showLoaderIconOnSearchInput", publicName: "showLoaderIconOnSearchInput", isSignal: true, isRequired: false, transformFunction: null }, preselectedIds: { classPropertyName: "preselectedIds", publicName: "preselectedIds", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, validateFns: { classPropertyName: "validateFns", publicName: "validateFns", isSignal: true, isRequired: false, transformFunction: null }, selectedItems: { classPropertyName: "selectedItems", publicName: "selectedItems", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedItems: "selectedItemsChange", touched: "touchedChange", selectedItemsEv: "selectedItemsEv", selectionClearedEv: "selectionClearedEv" }, ngImport: i0, template: "<!-- ========================= Label & Hint ========================= -->\n<div class=\"zs:relative zs:w-full\">\n <ZS-label [class]=\"(label() || hint()) ? 'zs:mb-1' : ''\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [required]=\"required()\"\n [hintId]=\"Id() + '-hint'\"\n [for]=\"Id() + '-trigger'\">\n </ZS-label>\n\n <!-- ========================= Trigger Button (Combobox) ========================= -->\n <button\n id=\"{{ Id() + '-trigger' }}\"\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"Id() + '-options'\"\n [attr.aria-describedby]=\"hint() ? Id() + '-hint' : null\"\n [disabled]=\"disabledOrReadonly()\"\n [class]=\"containerClasses()\"\n (click)=\"toggleDropdown()\">\n\n @if (selectedItems().length) {\n @if (!multiple()) {\n <!-- Single Selection -->\n <span class=\"zs:truncate\">{{ selectedItems()[0].name }}</span>\n } @else {\n <!-- Multiple Selection -->\n <div class=\"zs:flex zs:flex-wrap zs:gap-2 zs:items-center zs:max-w-full\">\n @for (item of selectedItems().slice(0, 6); track item.id) {\n <span\n [ngClass]=\"showItemsClass()\"\n class=\"zs:flex zs:gap-1 zs:justify-center zs:items-center zs:text-gray-900 \n zs:dark:text-gray-100 zs:pl-3 zs:pr-2 zs:py-1.5 zs:rounded-full zs:text-sm \n zs:max-w-[140px]\">\n <span class=\"zs:truncate\">{{ item.name }}</span>\n <button\n type=\"button\"\n [attr.aria-label]=\"'Remove ' + item.name\"\n class=\"zs:px-1 zs:text-gray-950 zs:hover:text-gray-600 zs:dark:text-gray-50 zs:dark:hover:text-gray-300\"\n (click)=\"selectItem([item]); $event.stopPropagation()\">\n <i class=\"fas fa-times text-xs\"></i>\n </button>\n </span>\n }\n\n @if (selectedItems().length > 6) {\n <!-- +More Indicator -->\n <span class=\"zs:text-gray-500 zs:text-sm\">\n +{{ selectedItems().length - 6 }} more\n </span>\n }\n </div>\n }\n } @else {\n <!-- Placeholder -->\n <span>{{ placeholder() }}</span>\n }\n\n @if (!disabledOrReadonly()) {\n <!-- Dropdown Toggle Icon -->\n <i class=\"fas zs:ml-2\"\n [ngClass]=\"[isOpen() ? 'fa-chevron-up' : 'fa-chevron-down']\"\n >\n </i>\n }\n </button>\n\n <!-- ========================= Dropdown Menu ========================= -->\n @if (isOpen()) {\n <div\n id=\"{{ Id() + '-options' }}\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple() ? 'true' : null\"\n class=\"zs:absolute {{ zIndices.selectDropdown }} zs:w-full zs:mt-1 zs:rounded-lg \n zs:shadow-lg zs:dark:shadow-gray-400/30 zs:overflow-hidden zs:bg-white zs:border \n zs:border-gray-300 zs:dark:bg-gray-900 zs:dark:border-gray-600 zs:dark:text-gray-100\">\n\n @if (showSearch()) {\n <!-- Search Input -->\n <div class=\"zs:relative zs:p-2 zs:border-b zs:border-gray-200 zs:dark:border-gray-700\">\n <ZS-input\n role=\"searchbox\"\n aria-label=\"Search options\"\n [iName]=\"'ZS-select-[' + Id() + ']'\"\n type=\"search\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchQuery()\"\n (searchEv)=\"searchQuery.set($event)\"\n (click)=\"$event.stopPropagation()\"\n [autofocus]=\"true\"\n [inputStyle]=\"this.error().length ? 'danger' : inputStyle()\"\n [searchDebounceDelay]=\"searchDebounceDelay()\"\n [showLoaderIconOnSearchInput]=\"showLoaderIconOnSearchInput()\">\n </ZS-input>\n </div>\n }\n\n <!-- Options List -->\n <div class=\"zs:max-h-60 zs:overflow-y-auto\">\n @for (item of filteredItems(); track trackByFn($index, item)) {\n <div\n role=\"option\"\n [attr.aria-selected]=\"inSelectItems(item)\"\n class=\"zs:px-4 zs:py-2 zs:cursor-pointer zs:flex zs:items-center zs:justify-between zs:transition-colors zs:text-gray-900 zs:dark:text-gray-100\"\n [ngClass]=\"getBgSelectClasses(inSelectItems(item))\"\n (click)=\"selectItem([item]); $event.stopPropagation()\">\n <span class=\"zs:truncate\">{{ item.name }}</span>\n @if (inSelectItems(item)) {\n <i class=\"fas fa-check zs:text-gray-950 zs:dark:text-gray-50 zs:ml-2\"></i>\n }\n </div>\n }\n\n @if (filteredItems().length === 0) {\n <!-- No Results Message -->\n <div class=\"zs:px-4 zs:py-3 zs:text-center zs:text-gray-500 zs:dark:text-gray-400\">\n {{ noResultsText() }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- ========================= Clear Selection Button ========================= -->\n @if (selectedItems().length && showClearButton() && !disabledOrReadonly()) {\n <button\n type=\"button\"\n aria-label=\"Clear all selections\"\n [class]=\"clearClass()\"\n (click)=\"clearSelection()\">\n <i class=\"fas fa-times zs:mr-1\"></i> Clear selection\n </button>\n }\n\n <!-- ========================= Error Messages ========================= -->\n <ZS-input-errors\n [Id]=\"Id()\"\n [errors]=\"[error()]\"\n >\n </ZS-input-errors>\n</div>", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: Input, selector: "ZS-input", inputs: ["Id", "iName", "label", "hint", "placeholder", "type", "inputStyle", "disabled", "isReadonly", "autocomplete", "required", "inputmode", "icon", "showSearchIcon", "showLoaderIconOnSearchInput", "maxlength", "minlength", "spellcheck", "min", "max", "step", "validateFns", "formatFn", "autofocus", "searchDebounceDelay", "size", "value", "touched"], outputs: ["valueChange", "touchedChange", "enterEv", "focusEv", "blurEv", "changedEv", "searchEv", "clearedEv", "keydownEv"] }, { kind: "component", type: Label, selector: "ZS-label", inputs: ["label", "hint", "hintId", "size", "required", "for"] }, { kind: "component", type: InputErrors, selector: "ZS-input-errors", inputs: ["Id", "errors"] }] });
2697
+ }
2698
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Select, decorators: [{
2699
+ type: Component,
2700
+ args: [{ selector: 'ZS-select', imports: [CommonModule, FormsModule, Input, Label, InputErrors], template: "<!-- ========================= Label & Hint ========================= -->\n<div class=\"zs:relative zs:w-full\">\n <ZS-label [class]=\"(label() || hint()) ? 'zs:mb-1' : ''\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [required]=\"required()\"\n [hintId]=\"Id() + '-hint'\"\n [for]=\"Id() + '-trigger'\">\n </ZS-label>\n\n <!-- ========================= Trigger Button (Combobox) ========================= -->\n <button\n id=\"{{ Id() + '-trigger' }}\"\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"Id() + '-options'\"\n [attr.aria-describedby]=\"hint() ? Id() + '-hint' : null\"\n [disabled]=\"disabledOrReadonly()\"\n [class]=\"containerClasses()\"\n (click)=\"toggleDropdown()\">\n\n @if (selectedItems().length) {\n @if (!multiple()) {\n <!-- Single Selection -->\n <span class=\"zs:truncate\">{{ selectedItems()[0].name }}</span>\n } @else {\n <!-- Multiple Selection -->\n <div class=\"zs:flex zs:flex-wrap zs:gap-2 zs:items-center zs:max-w-full\">\n @for (item of selectedItems().slice(0, 6); track item.id) {\n <span\n [ngClass]=\"showItemsClass()\"\n class=\"zs:flex zs:gap-1 zs:justify-center zs:items-center zs:text-gray-900 \n zs:dark:text-gray-100 zs:pl-3 zs:pr-2 zs:py-1.5 zs:rounded-full zs:text-sm \n zs:max-w-[140px]\">\n <span class=\"zs:truncate\">{{ item.name }}</span>\n <button\n type=\"button\"\n [attr.aria-label]=\"'Remove ' + item.name\"\n class=\"zs:px-1 zs:text-gray-950 zs:hover:text-gray-600 zs:dark:text-gray-50 zs:dark:hover:text-gray-300\"\n (click)=\"selectItem([item]); $event.stopPropagation()\">\n <i class=\"fas fa-times text-xs\"></i>\n </button>\n </span>\n }\n\n @if (selectedItems().length > 6) {\n <!-- +More Indicator -->\n <span class=\"zs:text-gray-500 zs:text-sm\">\n +{{ selectedItems().length - 6 }} more\n </span>\n }\n </div>\n }\n } @else {\n <!-- Placeholder -->\n <span>{{ placeholder() }}</span>\n }\n\n @if (!disabledOrReadonly()) {\n <!-- Dropdown Toggle Icon -->\n <i class=\"fas zs:ml-2\"\n [ngClass]=\"[isOpen() ? 'fa-chevron-up' : 'fa-chevron-down']\"\n >\n </i>\n }\n </button>\n\n <!-- ========================= Dropdown Menu ========================= -->\n @if (isOpen()) {\n <div\n id=\"{{ Id() + '-options' }}\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple() ? 'true' : null\"\n class=\"zs:absolute {{ zIndices.selectDropdown }} zs:w-full zs:mt-1 zs:rounded-lg \n zs:shadow-lg zs:dark:shadow-gray-400/30 zs:overflow-hidden zs:bg-white zs:border \n zs:border-gray-300 zs:dark:bg-gray-900 zs:dark:border-gray-600 zs:dark:text-gray-100\">\n\n @if (showSearch()) {\n <!-- Search Input -->\n <div class=\"zs:relative zs:p-2 zs:border-b zs:border-gray-200 zs:dark:border-gray-700\">\n <ZS-input\n role=\"searchbox\"\n aria-label=\"Search options\"\n [iName]=\"'ZS-select-[' + Id() + ']'\"\n type=\"search\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchQuery()\"\n (searchEv)=\"searchQuery.set($event)\"\n (click)=\"$event.stopPropagation()\"\n [autofocus]=\"true\"\n [inputStyle]=\"this.error().length ? 'danger' : inputStyle()\"\n [searchDebounceDelay]=\"searchDebounceDelay()\"\n [showLoaderIconOnSearchInput]=\"showLoaderIconOnSearchInput()\">\n </ZS-input>\n </div>\n }\n\n <!-- Options List -->\n <div class=\"zs:max-h-60 zs:overflow-y-auto\">\n @for (item of filteredItems(); track trackByFn($index, item)) {\n <div\n role=\"option\"\n [attr.aria-selected]=\"inSelectItems(item)\"\n class=\"zs:px-4 zs:py-2 zs:cursor-pointer zs:flex zs:items-center zs:justify-between zs:transition-colors zs:text-gray-900 zs:dark:text-gray-100\"\n [ngClass]=\"getBgSelectClasses(inSelectItems(item))\"\n (click)=\"selectItem([item]); $event.stopPropagation()\">\n <span class=\"zs:truncate\">{{ item.name }}</span>\n @if (inSelectItems(item)) {\n <i class=\"fas fa-check zs:text-gray-950 zs:dark:text-gray-50 zs:ml-2\"></i>\n }\n </div>\n }\n\n @if (filteredItems().length === 0) {\n <!-- No Results Message -->\n <div class=\"zs:px-4 zs:py-3 zs:text-center zs:text-gray-500 zs:dark:text-gray-400\">\n {{ noResultsText() }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- ========================= Clear Selection Button ========================= -->\n @if (selectedItems().length && showClearButton() && !disabledOrReadonly()) {\n <button\n type=\"button\"\n aria-label=\"Clear all selections\"\n [class]=\"clearClass()\"\n (click)=\"clearSelection()\">\n <i class=\"fas fa-times zs:mr-1\"></i> Clear selection\n </button>\n }\n\n <!-- ========================= Error Messages ========================= -->\n <ZS-input-errors\n [Id]=\"Id()\"\n [errors]=\"[error()]\"\n >\n </ZS-input-errors>\n</div>", styles: [":host{display:block}\n"] }]
2701
+ }], ctorParameters: () => [], propDecorators: { Id: [{ type: i0.Input, args: [{ isSignal: true, alias: "Id", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], isReadonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "isReadonly", required: false }] }], inputStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputStyle", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], noResultsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsText", required: false }] }], showClearButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], searchDebounceDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchDebounceDelay", required: false }] }], showLoaderIconOnSearchInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLoaderIconOnSearchInput", required: false }] }], preselectedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "preselectedIds", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], validateFns: [{ type: i0.Input, args: [{ isSignal: true, alias: "validateFns", required: false }] }], selectedItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedItems", required: false }] }, { type: i0.Output, args: ["selectedItemsChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], selectedItemsEv: [{ type: i0.Output, args: ["selectedItemsEv"] }], selectionClearedEv: [{ type: i0.Output, args: ["selectionClearedEv"] }] } });
2702
+
2703
+ class Toggle {
2704
+ // ==============================================
2705
+ // Inputs
2706
+ // ==============================================
2707
+ Id = input(crypto.randomUUID(), ...(ngDevMode ? [{ debugName: "Id" }] : []));
2708
+ label = input(null, ...(ngDevMode ? [{ debugName: "label" }] : []));
2709
+ hint = input(null, ...(ngDevMode ? [{ debugName: "hint" }] : []));
2710
+ color = input('blue', ...(ngDevMode ? [{ debugName: "color" }] : []));
2711
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2712
+ isReadonly = input(false, ...(ngDevMode ? [{ debugName: "isReadonly" }] : []));
2713
+ icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : []));
2714
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
2715
+ // ==============================================
2716
+ // Model
2717
+ // ==============================================
2718
+ value = model(false, ...(ngDevMode ? [{ debugName: "value" }] : []));
2719
+ // ==============================================
2720
+ // Computed Signals
2721
+ // ==============================================
2722
+ toggleClasses = computed(() => {
2723
+ const disabledClass = this.disabled() ? 'zs:opacity-60' : '';
2724
+ const interactionClass = this.disabledOrReadonly() ? 'zs:cursor-not-allowed' : '';
2725
+ return [
2726
+ disabledClass,
2727
+ interactionClass
2728
+ ].join(' ');
2729
+ }, ...(ngDevMode ? [{ debugName: "toggleClasses" }] : []));
2730
+ colorClasses = (type) => {
2731
+ const color = ColorMapping.get(this.color());
2732
+ if (!color)
2733
+ return '';
2734
+ return color[type];
2735
+ };
2736
+ btnSize = computed(() => {
2737
+ const sizes = {
2738
+ sm: 'zs:w-16 zs:h-8',
2739
+ md: 'zs:w-20 zs:h-10',
2740
+ lg: 'zs:w-24 zs:h-12'
2741
+ };
2742
+ return sizes[this.size()];
2743
+ }, ...(ngDevMode ? [{ debugName: "btnSize" }] : []));
2744
+ wrapperSize = computed(() => {
2745
+ const sizes = {
2746
+ sm: 'zs:size-6 zs:text-[10px]',
2747
+ md: 'zs:size-8 zs:text-sm',
2748
+ lg: 'zs:size-10 zs:text-base'
2749
+ };
2750
+ return sizes[this.size()];
2751
+ }, ...(ngDevMode ? [{ debugName: "wrapperSize" }] : []));
2752
+ transSize = computed(() => {
2753
+ const sizes = {
2754
+ sm: 'zs:translate-x-7',
2755
+ md: 'zs:translate-x-9',
2756
+ lg: 'zs:translate-x-11'
2757
+ };
2758
+ return sizes[this.size()];
2759
+ }, ...(ngDevMode ? [{ debugName: "transSize" }] : []));
2760
+ isChecked = computed(() => this.value(), ...(ngDevMode ? [{ debugName: "isChecked" }] : []));
2761
+ disabledOrReadonly = computed(() => this.disabled() || this.isReadonly(), ...(ngDevMode ? [{ debugName: "disabledOrReadonly" }] : []));
2762
+ // ==============================================
2763
+ // Handlers
2764
+ // ==============================================
2765
+ toggleChecked() {
2766
+ if (this.disabledOrReadonly())
2767
+ return;
2768
+ this.value.update(v => !v);
2769
+ }
2770
+ onKeyDown(event) {
2771
+ if (event.key === ' ' || event.key === 'Enter') {
2772
+ event.preventDefault();
2773
+ this.toggleChecked();
2774
+ }
2775
+ }
2776
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Toggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
2777
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.9", type: Toggle, isStandalone: true, selector: "ZS-toggle", inputs: { Id: { classPropertyName: "Id", publicName: "Id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, isReadonly: { classPropertyName: "isReadonly", publicName: "isReadonly", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: "<div class=\"zs:flex zs:items-center zs:justify-start zs:w-full zs:select-none\">\n <!-- ===================== Checkbox Wrapper ===================== -->\n <div\n class=\"zs:cursor-pointer zs:leading-none\"\n >\n <!-- ===================== Hidden Input (Native for screen readers) ===================== -->\n <input\n type=\"checkbox\"\n class=\"zs:hidden\"\n [id]=\"Id()\"\n [checked]=\"isChecked()\"\n [disabled]=\"disabledOrReadonly()\"\n (change)=\"toggleChecked()\"\n />\n\n <!-- ===================== Custom Accessible Toggle ===================== -->\n <button\n (click)=\"value.set(!value())\"\n class=\"zs:relative zs:rounded-full zs:border-2 zs:flex zs:items-center zs:transition-all zs:duration-500 zs:ease-in-out zs:overflow-hidden\"\n [ngClass]=\"[\n toggleClasses(),\n colorClasses('border'),\n value() ? colorClasses('bg') : '', \n value() ? '' : 'zs:bg-gray-100 zs:dark:bg-gray-700',\n btnSize(),\n ]\"\n >\n <div\n class=\"zs:absolute zs:left-1 zs:top-1/2 zs:-translate-1/2 zs:rounded-full zs:flex zs:items-center zs:justify-center zs:transition-all zs:duration-500 zs:ease-in-out zs:shadow-md\"\n [ngClass]=\"[\n value() ? '' : colorClasses('bg'),\n value() ? colorClasses('text') : 'zs:text-gray-100 zs:dark:text-gray-300',\n value() ? 'zs:bg-gray-100 zs:dark:bg-gray-300' : '',\n wrapperSize(),\n value() ? transSize() : 'zs:translate-x-0',\n ]\"\n >\n <i [class]=\"icon()\"></i>\n </div>\n </button>\n </div>\n\n <!-- ===================== Label (ZS-label component) ===================== -->\n <ZS-label [class]=\"(label() || hint()) ? 'zs:ml-1' : ''\"\n [id]=\"Id() + '-label'\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [size]=\"size()\"\n [hintId]=\"Id() + '-hint'\"\n [for]=\"Id()\"\n ></ZS-label>\n</div>", styles: [""], dependencies: [{ kind: "component", type: Label, selector: "ZS-label", inputs: ["label", "hint", "hintId", "size", "required", "for"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
2778
+ }
2779
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: Toggle, decorators: [{
2780
+ type: Component,
2781
+ args: [{ selector: 'ZS-toggle', imports: [Label, CommonModule], template: "<div class=\"zs:flex zs:items-center zs:justify-start zs:w-full zs:select-none\">\n <!-- ===================== Checkbox Wrapper ===================== -->\n <div\n class=\"zs:cursor-pointer zs:leading-none\"\n >\n <!-- ===================== Hidden Input (Native for screen readers) ===================== -->\n <input\n type=\"checkbox\"\n class=\"zs:hidden\"\n [id]=\"Id()\"\n [checked]=\"isChecked()\"\n [disabled]=\"disabledOrReadonly()\"\n (change)=\"toggleChecked()\"\n />\n\n <!-- ===================== Custom Accessible Toggle ===================== -->\n <button\n (click)=\"value.set(!value())\"\n class=\"zs:relative zs:rounded-full zs:border-2 zs:flex zs:items-center zs:transition-all zs:duration-500 zs:ease-in-out zs:overflow-hidden\"\n [ngClass]=\"[\n toggleClasses(),\n colorClasses('border'),\n value() ? colorClasses('bg') : '', \n value() ? '' : 'zs:bg-gray-100 zs:dark:bg-gray-700',\n btnSize(),\n ]\"\n >\n <div\n class=\"zs:absolute zs:left-1 zs:top-1/2 zs:-translate-1/2 zs:rounded-full zs:flex zs:items-center zs:justify-center zs:transition-all zs:duration-500 zs:ease-in-out zs:shadow-md\"\n [ngClass]=\"[\n value() ? '' : colorClasses('bg'),\n value() ? colorClasses('text') : 'zs:text-gray-100 zs:dark:text-gray-300',\n value() ? 'zs:bg-gray-100 zs:dark:bg-gray-300' : '',\n wrapperSize(),\n value() ? transSize() : 'zs:translate-x-0',\n ]\"\n >\n <i [class]=\"icon()\"></i>\n </div>\n </button>\n </div>\n\n <!-- ===================== Label (ZS-label component) ===================== -->\n <ZS-label [class]=\"(label() || hint()) ? 'zs:ml-1' : ''\"\n [id]=\"Id() + '-label'\"\n [label]=\"label()\"\n [hint]=\"hint()\"\n [size]=\"size()\"\n [hintId]=\"Id() + '-hint'\"\n [for]=\"Id()\"\n ></ZS-label>\n</div>" }]
2782
+ }], propDecorators: { Id: [{ type: i0.Input, args: [{ isSignal: true, alias: "Id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], isReadonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "isReadonly", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
2783
+
2784
+ /*
2785
+ * Public API Surface of ngx-zs-component
2786
+ */
2787
+
2788
+ /**
2789
+ * Generated bundle index. Do not edit.
2790
+ */
2791
+
2792
+ export { ALERT_CONFIG, Alert, AlertService, Button, Card, Carousel, Checkbox, ColorMapping, Connection, ExtractorService, FileInput, Form, FormPaletteMap, Input, InputErrors, Label, Modal, NavItem, NavItemService, Navbar, NgxZsComponent, Page404, Pagination, Range, ScrollToTop, Select, Spinner, ThemeToggle, Toggle };
2793
+ //# sourceMappingURL=ziadshalaby-ngx-zs-component.mjs.map