@devjuliovilla/jv-ui 1.5.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,3064 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, Component, forwardRef, inject, output, signal, Injectable, effect, HostListener, InjectionToken, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, viewChild } from '@angular/core';
3
+ import * as i1 from '@angular/forms';
4
+ import { NG_VALUE_ACCESSOR, FormBuilder, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
5
+ import { CommonModule, DOCUMENT } from '@angular/common';
6
+ import { Router, RouterLink } from '@angular/router';
7
+
8
+ const JV_FALLBACK_ICON_NAME = 'circle-alert';
9
+ const JV_LUCIDE_ICON_REGISTRY = {
10
+ bell: {
11
+ viewBox: '0 0 24 24',
12
+ paths: ['M10.268 21a2 2 0 0 0 3.464 0', 'M3.262 15.326A2 2 0 0 0 4 16.95h16a2 2 0 0 0 .74-1.624C19.41 13.44 18 11.116 18 8A6 6 0 0 0 6 8c0 3.116-1.41 5.44-2.738 7.326'],
13
+ },
14
+ check: {
15
+ viewBox: '0 0 24 24',
16
+ paths: ['M20 6 9 17l-5-5'],
17
+ },
18
+ 'chevron-down': {
19
+ viewBox: '0 0 24 24',
20
+ paths: ['m6 9 6 6 6-6'],
21
+ },
22
+ 'chevron-up': {
23
+ viewBox: '0 0 24 24',
24
+ paths: ['m18 15-6-6-6 6'],
25
+ },
26
+ 'circle-alert': {
27
+ viewBox: '0 0 24 24',
28
+ paths: ['M12 8v4', 'M12 16h.01', 'M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2Z'],
29
+ },
30
+ info: {
31
+ viewBox: '0 0 24 24',
32
+ paths: ['M12 8h.01', 'M11 12h1v4h1', 'M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2Z'],
33
+ },
34
+ menu: {
35
+ viewBox: '0 0 24 24',
36
+ paths: ['M4 12h16', 'M4 6h16', 'M4 18h16'],
37
+ },
38
+ monitor: {
39
+ viewBox: '0 0 24 24',
40
+ paths: ['M12 17v4', 'M8 21h8', 'M5 3h14a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z'],
41
+ },
42
+ moon: {
43
+ viewBox: '0 0 24 24',
44
+ paths: ['M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z'],
45
+ },
46
+ 'panel-left-close': {
47
+ viewBox: '0 0 24 24',
48
+ paths: ['M16 3H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8', 'M10 9l-3 3 3 3', 'M22 3v18'],
49
+ },
50
+ search: {
51
+ viewBox: '0 0 24 24',
52
+ paths: ['m21 21-4.34-4.34', 'M11 19a8 8 0 1 1 0-16 8 8 0 0 1 0 16Z'],
53
+ },
54
+ settings: {
55
+ viewBox: '0 0 24 24',
56
+ paths: ['M12.22 2h-.44a2 2 0 0 0-1.94 1.52l-.19.8a2 2 0 0 1-1.34 1.42l-.76.26a2 2 0 0 1-1.87-.24l-.7-.43a2 2 0 0 0-2.55.3l-.31.31a2 2 0 0 0-.3 2.55l.43.7a2 2 0 0 1 .24 1.87l-.26.76a2 2 0 0 1-1.42 1.34l-.8.19A2 2 0 0 0 2 11.78v.44a2 2 0 0 0 1.52 1.94l.8.19a2 2 0 0 1 1.42 1.34l.26.76a2 2 0 0 1-.24 1.87l-.43.7a2 2 0 0 0 .3 2.55l.31.31a2 2 0 0 0 2.55.3l.7-.43a2 2 0 0 1 1.87-.24l.76.26a2 2 0 0 1 1.34 1.42l.19.8A2 2 0 0 0 11.78 22h.44a2 2 0 0 0 1.94-1.52l.19-.8a2 2 0 0 1 1.34-1.42l.76-.26a2 2 0 0 1 1.87.24l.7.43a2 2 0 0 0 2.55-.3l.31-.31a2 2 0 0 0 .3-2.55l-.43-.7a2 2 0 0 1-.24-1.87l.26-.76a2 2 0 0 1 1.42-1.34l.8-.19A2 2 0 0 0 22 12.22v-.44a2 2 0 0 0-1.52-1.94l-.8-.19a2 2 0 0 1-1.42-1.34l-.26-.76a2 2 0 0 1 .24-1.87l.43-.7a2 2 0 0 0-.3-2.55l-.31-.31a2 2 0 0 0-2.55-.3l-.7.43a2 2 0 0 1-1.87.24l-.76-.26a2 2 0 0 1-1.34-1.42l-.19-.8A2 2 0 0 0 12.22 2Z', 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z'],
57
+ },
58
+ sun: {
59
+ viewBox: '0 0 24 24',
60
+ paths: ['M12 2v2', 'M12 20v2', 'm4.93 4.93 1.41 1.41', 'm17.66 17.66-1.41-1.41', 'M2 12h2', 'M20 12h2', 'm6.34 17.66-1.41-1.41', 'm19.07 4.93-1.41 1.41', 'M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10Z'],
61
+ },
62
+ sparkles: {
63
+ viewBox: '0 0 24 24',
64
+ paths: ['m12 3-1.9 5.1L5 10l5.1 1.9L12 17l1.9-5.1L19 10l-5.1-1.9L12 3Z', 'M5 3v4', 'M19 17v4', 'M3 5h4', 'M17 19h4'],
65
+ },
66
+ 'triangle-alert': {
67
+ viewBox: '0 0 24 24',
68
+ paths: ['m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3', 'M12 9v4', 'M12 17h.01'],
69
+ },
70
+ user: {
71
+ viewBox: '0 0 24 24',
72
+ paths: ['M19 21a7 7 0 0 0-14 0', 'M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z'],
73
+ },
74
+ x: {
75
+ viewBox: '0 0 24 24',
76
+ paths: ['M18 6 6 18', 'M6 6l12 12'],
77
+ },
78
+ };
79
+
80
+ class JvIconComponent {
81
+ warnedNames = new Set();
82
+ name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
83
+ size = input(18, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
84
+ strokeWidth = input(2, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : /* istanbul ignore next */ []));
85
+ decorative = input(true, ...(ngDevMode ? [{ debugName: "decorative" }] : /* istanbul ignore next */ []));
86
+ ariaLabel = input(...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
87
+ normalizedName = computed(() => this.name().trim().toLowerCase(), ...(ngDevMode ? [{ debugName: "normalizedName" }] : /* istanbul ignore next */ []));
88
+ iconDefinition = computed(() => {
89
+ const icon = JV_LUCIDE_ICON_REGISTRY[this.normalizedName()];
90
+ if (icon) {
91
+ return icon;
92
+ }
93
+ if (!this.warnedNames.has(this.normalizedName())) {
94
+ this.warnedNames.add(this.normalizedName());
95
+ console.warn(`[jv-icon] Icon not found: ${this.normalizedName()}`);
96
+ }
97
+ return JV_LUCIDE_ICON_REGISTRY[JV_FALLBACK_ICON_NAME];
98
+ }, ...(ngDevMode ? [{ debugName: "iconDefinition" }] : /* istanbul ignore next */ []));
99
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
100
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvIconComponent, isStandalone: true, selector: "jv-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, decorative: { classPropertyName: "decorative", publicName: "decorative", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
101
+ <svg
102
+ class="jv-icon"
103
+ [attr.viewBox]="iconDefinition().viewBox"
104
+ [attr.width]="size()"
105
+ [attr.height]="size()"
106
+ [attr.stroke-width]="strokeWidth()"
107
+ [attr.role]="decorative() ? 'presentation' : 'img'"
108
+ [attr.aria-hidden]="decorative() ? 'true' : null"
109
+ [attr.aria-label]="decorative() ? null : ariaLabel() || normalizedName()"
110
+ fill="none"
111
+ stroke="currentColor"
112
+ stroke-linecap="round"
113
+ stroke-linejoin="round"
114
+ >
115
+ @for (path of iconDefinition().paths; track path) {
116
+ <path [attr.d]="path" />
117
+ }
118
+ </svg>
119
+ `, isInline: true });
120
+ }
121
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvIconComponent, decorators: [{
122
+ type: Component,
123
+ args: [{
124
+ selector: 'jv-icon',
125
+ standalone: true,
126
+ template: `
127
+ <svg
128
+ class="jv-icon"
129
+ [attr.viewBox]="iconDefinition().viewBox"
130
+ [attr.width]="size()"
131
+ [attr.height]="size()"
132
+ [attr.stroke-width]="strokeWidth()"
133
+ [attr.role]="decorative() ? 'presentation' : 'img'"
134
+ [attr.aria-hidden]="decorative() ? 'true' : null"
135
+ [attr.aria-label]="decorative() ? null : ariaLabel() || normalizedName()"
136
+ fill="none"
137
+ stroke="currentColor"
138
+ stroke-linecap="round"
139
+ stroke-linejoin="round"
140
+ >
141
+ @for (path of iconDefinition().paths; track path) {
142
+ <path [attr.d]="path" />
143
+ }
144
+ </svg>
145
+ `,
146
+ }]
147
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], decorative: [{ type: i0.Input, args: [{ isSignal: true, alias: "decorative", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
148
+
149
+ class JvButtonComponent {
150
+ variant = input('primary', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
151
+ icon = input(null, ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
152
+ iconPosition = input('start', ...(ngDevMode ? [{ debugName: "iconPosition" }] : /* istanbul ignore next */ []));
153
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
154
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
155
+ buttonClass = computed(() => `jv-button--${this.variant()}`, ...(ngDevMode ? [{ debugName: "buttonClass" }] : /* istanbul ignore next */ []));
156
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
157
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvButtonComponent, isStandalone: true, selector: "jv-button", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconPosition: { classPropertyName: "iconPosition", publicName: "iconPosition", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
158
+ <button
159
+ type="button"
160
+ class="jv-button"
161
+ [class]="buttonClass()"
162
+ [attr.aria-busy]="loading() ? 'true' : null"
163
+ [disabled]="disabled() || loading()"
164
+ >
165
+ @if (loading()) {
166
+ <span class="jv-button-spinner" aria-hidden="true"></span>
167
+ }
168
+
169
+ <span class="jv-button-content" [class.is-hidden]="loading()">
170
+ @if (icon() && iconPosition() === 'start') {
171
+ <jv-icon [name]="icon()!" [size]="18" />
172
+ }
173
+
174
+ <span class="jv-button-label"><ng-content></ng-content></span>
175
+
176
+ @if (icon() && iconPosition() === 'end') {
177
+ <jv-icon [name]="icon()!" [size]="18" />
178
+ }
179
+ </span>
180
+ </button>
181
+ `, isInline: true, styles: [".jv-button{position:relative;display:inline-flex;align-items:center;justify-content:center;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);font-weight:600;transition:background-color .16s ease,border-color .16s ease,color .16s ease,box-shadow .16s ease;cursor:pointer}.jv-button:disabled{opacity:.6;cursor:not-allowed}.jv-button-content{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-button-content.is-hidden{visibility:hidden}.jv-button-spinner{position:absolute;width:1rem;height:1rem;border:2px solid currentColor;border-right-color:transparent;border-radius:999px;animation:jv-spin .8s linear infinite}.jv-button--primary{background:var(--jv-color-primary);color:#fff}.jv-button--secondary{background:var(--jv-color-secondary);color:#fff}.jv-button--outline{border-color:var(--jv-color-border);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-button--ghost{background:transparent;color:var(--jv-color-foreground)}.jv-button--danger{background:var(--jv-color-danger);color:#fff}.jv-button--success{background:var(--jv-color-success);color:#fff}.jv-button--warning{background:var(--jv-color-warning);color:#111827}.jv-button--link{min-height:auto;padding:0;background:transparent;color:var(--jv-color-primary);text-decoration:underline;text-underline-offset:.2em}.jv-button--outline:hover,.jv-button--ghost:hover{background:var(--jv-color-surface-muted)}.jv-button--primary:hover,.jv-button--secondary:hover,.jv-button--danger:hover,.jv-button--success:hover,.jv-button--warning:hover{filter:brightness(.96)}@keyframes jv-spin{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
182
+ }
183
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvButtonComponent, decorators: [{
184
+ type: Component,
185
+ args: [{ selector: 'jv-button', standalone: true, imports: [JvIconComponent], template: `
186
+ <button
187
+ type="button"
188
+ class="jv-button"
189
+ [class]="buttonClass()"
190
+ [attr.aria-busy]="loading() ? 'true' : null"
191
+ [disabled]="disabled() || loading()"
192
+ >
193
+ @if (loading()) {
194
+ <span class="jv-button-spinner" aria-hidden="true"></span>
195
+ }
196
+
197
+ <span class="jv-button-content" [class.is-hidden]="loading()">
198
+ @if (icon() && iconPosition() === 'start') {
199
+ <jv-icon [name]="icon()!" [size]="18" />
200
+ }
201
+
202
+ <span class="jv-button-label"><ng-content></ng-content></span>
203
+
204
+ @if (icon() && iconPosition() === 'end') {
205
+ <jv-icon [name]="icon()!" [size]="18" />
206
+ }
207
+ </span>
208
+ </button>
209
+ `, styles: [".jv-button{position:relative;display:inline-flex;align-items:center;justify-content:center;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);font-weight:600;transition:background-color .16s ease,border-color .16s ease,color .16s ease,box-shadow .16s ease;cursor:pointer}.jv-button:disabled{opacity:.6;cursor:not-allowed}.jv-button-content{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-button-content.is-hidden{visibility:hidden}.jv-button-spinner{position:absolute;width:1rem;height:1rem;border:2px solid currentColor;border-right-color:transparent;border-radius:999px;animation:jv-spin .8s linear infinite}.jv-button--primary{background:var(--jv-color-primary);color:#fff}.jv-button--secondary{background:var(--jv-color-secondary);color:#fff}.jv-button--outline{border-color:var(--jv-color-border);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-button--ghost{background:transparent;color:var(--jv-color-foreground)}.jv-button--danger{background:var(--jv-color-danger);color:#fff}.jv-button--success{background:var(--jv-color-success);color:#fff}.jv-button--warning{background:var(--jv-color-warning);color:#111827}.jv-button--link{min-height:auto;padding:0;background:transparent;color:var(--jv-color-primary);text-decoration:underline;text-underline-offset:.2em}.jv-button--outline:hover,.jv-button--ghost:hover{background:var(--jv-color-surface-muted)}.jv-button--primary:hover,.jv-button--secondary:hover,.jv-button--danger:hover,.jv-button--success:hover,.jv-button--warning:hover{filter:brightness(.96)}@keyframes jv-spin{to{transform:rotate(360deg)}}\n"] }]
210
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconPosition", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
211
+
212
+ class JvButtonGroupComponent {
213
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvButtonGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
214
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: JvButtonGroupComponent, isStandalone: true, selector: "jv-button-group", ngImport: i0, template: `<div class="jv-button-group" role="group"><ng-content></ng-content></div>`, isInline: true, styles: [".jv-button-group{display:inline-flex;flex-wrap:wrap;gap:var(--jv-spacing-sm);align-items:center}\n"] });
215
+ }
216
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvButtonGroupComponent, decorators: [{
217
+ type: Component,
218
+ args: [{ selector: 'jv-button-group', standalone: true, template: `<div class="jv-button-group" role="group"><ng-content></ng-content></div>`, styles: [".jv-button-group{display:inline-flex;flex-wrap:wrap;gap:var(--jv-spacing-sm);align-items:center}\n"] }]
219
+ }] });
220
+
221
+ class JvIconButtonComponent {
222
+ icon = input.required(...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
223
+ ariaLabel = input.required(...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
224
+ variant = input('ghost', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
225
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
226
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvIconButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
227
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvIconButtonComponent, isStandalone: true, selector: "jv-icon-button", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: true, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
228
+ <button
229
+ type="button"
230
+ class="jv-icon-button"
231
+ [class]="'jv-icon-button--' + variant()"
232
+ [attr.aria-label]="ariaLabel()"
233
+ [disabled]="disabled()"
234
+ >
235
+ <jv-icon [name]="icon()" [size]="18" />
236
+ </button>
237
+ `, isInline: true, styles: [".jv-icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid transparent;border-radius:999px;cursor:pointer;transition:background-color .16s ease,color .16s ease,border-color .16s ease}.jv-icon-button:disabled{opacity:.6;cursor:not-allowed}.jv-icon-button--primary{background:var(--jv-color-primary);color:#fff}.jv-icon-button--secondary{background:var(--jv-color-secondary);color:#fff}.jv-icon-button--outline{border-color:var(--jv-color-border);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-icon-button--ghost,.jv-icon-button--link{background:transparent;color:var(--jv-color-foreground)}.jv-icon-button--danger{background:var(--jv-color-danger);color:#fff}.jv-icon-button--success{background:var(--jv-color-success);color:#fff}.jv-icon-button--warning{background:var(--jv-color-warning);color:#111827}.jv-icon-button--outline:hover,.jv-icon-button--ghost:hover,.jv-icon-button--link:hover{background:var(--jv-color-surface-muted)}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
238
+ }
239
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvIconButtonComponent, decorators: [{
240
+ type: Component,
241
+ args: [{ selector: 'jv-icon-button', standalone: true, imports: [JvIconComponent], template: `
242
+ <button
243
+ type="button"
244
+ class="jv-icon-button"
245
+ [class]="'jv-icon-button--' + variant()"
246
+ [attr.aria-label]="ariaLabel()"
247
+ [disabled]="disabled()"
248
+ >
249
+ <jv-icon [name]="icon()" [size]="18" />
250
+ </button>
251
+ `, styles: [".jv-icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border:1px solid transparent;border-radius:999px;cursor:pointer;transition:background-color .16s ease,color .16s ease,border-color .16s ease}.jv-icon-button:disabled{opacity:.6;cursor:not-allowed}.jv-icon-button--primary{background:var(--jv-color-primary);color:#fff}.jv-icon-button--secondary{background:var(--jv-color-secondary);color:#fff}.jv-icon-button--outline{border-color:var(--jv-color-border);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-icon-button--ghost,.jv-icon-button--link{background:transparent;color:var(--jv-color-foreground)}.jv-icon-button--danger{background:var(--jv-color-danger);color:#fff}.jv-icon-button--success{background:var(--jv-color-success);color:#fff}.jv-icon-button--warning{background:var(--jv-color-warning);color:#111827}.jv-icon-button--outline:hover,.jv-icon-button--ghost:hover,.jv-icon-button--link:hover{background:var(--jv-color-surface-muted)}\n"] }]
252
+ }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
253
+
254
+ class JvAlertComponent {
255
+ tone = input('info', ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
256
+ title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
257
+ role = input('status', ...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
258
+ alertClass = computed(() => `jv-alert--${this.tone()}`, ...(ngDevMode ? [{ debugName: "alertClass" }] : /* istanbul ignore next */ []));
259
+ iconName = computed(() => {
260
+ switch (this.tone()) {
261
+ case 'success':
262
+ return 'check';
263
+ case 'warning':
264
+ return 'triangle-alert';
265
+ case 'danger':
266
+ return 'circle-alert';
267
+ case 'primary':
268
+ return 'sparkles';
269
+ case 'secondary':
270
+ case 'neutral':
271
+ return 'info';
272
+ default:
273
+ return 'info';
274
+ }
275
+ }, ...(ngDevMode ? [{ debugName: "iconName" }] : /* istanbul ignore next */ []));
276
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvAlertComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
277
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvAlertComponent, isStandalone: true, selector: "jv-alert", inputs: { tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
278
+ <div class="jv-alert" [class]="alertClass()" [attr.role]="role()">
279
+ <div class="jv-alert-icon">
280
+ <jv-icon [name]="iconName()" [size]="18" />
281
+ </div>
282
+ <div class="jv-alert-content">
283
+ @if (title()) {
284
+ <strong>{{ title() }}</strong>
285
+ }
286
+ <div><ng-content></ng-content></div>
287
+ </div>
288
+ </div>
289
+ `, isInline: true, styles: [".jv-alert{display:grid;grid-template-columns:auto 1fr;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md)}.jv-alert-content{display:grid;gap:var(--jv-spacing-xs)}.jv-alert--primary{background:color-mix(in srgb,var(--jv-color-primary) 10%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-alert--secondary,.jv-alert--neutral{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-alert--success{background:color-mix(in srgb,var(--jv-color-success) 12%,var(--jv-color-surface))}.jv-alert--warning{background:color-mix(in srgb,var(--jv-color-warning) 16%,var(--jv-color-surface))}.jv-alert--danger{background:color-mix(in srgb,var(--jv-color-danger) 12%,var(--jv-color-surface))}.jv-alert--info{background:color-mix(in srgb,var(--jv-color-info) 12%,var(--jv-color-surface))}strong{font-size:.95rem}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
290
+ }
291
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvAlertComponent, decorators: [{
292
+ type: Component,
293
+ args: [{ selector: 'jv-alert', standalone: true, imports: [JvIconComponent], template: `
294
+ <div class="jv-alert" [class]="alertClass()" [attr.role]="role()">
295
+ <div class="jv-alert-icon">
296
+ <jv-icon [name]="iconName()" [size]="18" />
297
+ </div>
298
+ <div class="jv-alert-content">
299
+ @if (title()) {
300
+ <strong>{{ title() }}</strong>
301
+ }
302
+ <div><ng-content></ng-content></div>
303
+ </div>
304
+ </div>
305
+ `, styles: [".jv-alert{display:grid;grid-template-columns:auto 1fr;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md)}.jv-alert-content{display:grid;gap:var(--jv-spacing-xs)}.jv-alert--primary{background:color-mix(in srgb,var(--jv-color-primary) 10%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-alert--secondary,.jv-alert--neutral{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-alert--success{background:color-mix(in srgb,var(--jv-color-success) 12%,var(--jv-color-surface))}.jv-alert--warning{background:color-mix(in srgb,var(--jv-color-warning) 16%,var(--jv-color-surface))}.jv-alert--danger{background:color-mix(in srgb,var(--jv-color-danger) 12%,var(--jv-color-surface))}.jv-alert--info{background:color-mix(in srgb,var(--jv-color-info) 12%,var(--jv-color-surface))}strong{font-size:.95rem}\n"] }]
306
+ }], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], role: [{ type: i0.Input, args: [{ isSignal: true, alias: "role", required: false }] }] } });
307
+
308
+ class JvCardComponent {
309
+ elevated = input(true, ...(ngDevMode ? [{ debugName: "elevated" }] : /* istanbul ignore next */ []));
310
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
311
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvCardComponent, isStandalone: true, selector: "jv-card", inputs: { elevated: { classPropertyName: "elevated", publicName: "elevated", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
312
+ <article class="jv-card" [class]="elevated() ? 'jv-card--elevated' : 'jv-card--flat'">
313
+ <ng-content></ng-content>
314
+ </article>
315
+ `, isInline: true, styles: [".jv-card{padding:var(--jv-spacing-lg);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface)}.jv-card--elevated{box-shadow:var(--jv-shadow-md)}\n"] });
316
+ }
317
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvCardComponent, decorators: [{
318
+ type: Component,
319
+ args: [{ selector: 'jv-card', standalone: true, template: `
320
+ <article class="jv-card" [class]="elevated() ? 'jv-card--elevated' : 'jv-card--flat'">
321
+ <ng-content></ng-content>
322
+ </article>
323
+ `, styles: [".jv-card{padding:var(--jv-spacing-lg);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface)}.jv-card--elevated{box-shadow:var(--jv-shadow-md)}\n"] }]
324
+ }], propDecorators: { elevated: [{ type: i0.Input, args: [{ isSignal: true, alias: "elevated", required: false }] }] } });
325
+
326
+ class JvFormContainerComponent {
327
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
328
+ hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
329
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
330
+ inputId = input('', ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
331
+ hintId = input('', ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
332
+ errorId = input('', ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
333
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
334
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
335
+ resolvedHintId = computed(() => this.hintId() || (this.inputId() ? `${this.inputId()}-hint` : ''), ...(ngDevMode ? [{ debugName: "resolvedHintId" }] : /* istanbul ignore next */ []));
336
+ resolvedErrorId = computed(() => this.errorId() || (this.inputId() ? `${this.inputId()}-error` : ''), ...(ngDevMode ? [{ debugName: "resolvedErrorId" }] : /* istanbul ignore next */ []));
337
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFormContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
338
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvFormContainerComponent, isStandalone: true, selector: "jv-form-container", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, hintId: { classPropertyName: "hintId", publicName: "hintId", isSignal: true, isRequired: false, transformFunction: null }, errorId: { classPropertyName: "errorId", publicName: "errorId", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
339
+ <div class="jv-form-container" [class.is-invalid]="invalid()">
340
+ @if (label()) {
341
+ <label class="jv-form-label" [attr.for]="inputId() || null">
342
+ <span>{{ label() }}</span>
343
+ @if (required()) {
344
+ <span class="required-indicator" aria-hidden="true">*</span>
345
+ }
346
+ </label>
347
+ }
348
+
349
+ <ng-content></ng-content>
350
+
351
+ @if (hint() && !invalid()) {
352
+ <p class="jv-form-hint" [id]="resolvedHintId() || null">{{ hint() }}</p>
353
+ }
354
+
355
+ @if (error() && invalid()) {
356
+ <p class="jv-form-error" [id]="resolvedErrorId() || null" role="alert" aria-live="assertive">{{ error() }}</p>
357
+ }
358
+ </div>
359
+ `, isInline: true, styles: [".jv-form-container{display:grid;gap:var(--jv-spacing-xs)}.jv-form-label{display:inline-flex;align-items:center;gap:.35rem;font-weight:600;color:var(--jv-color-foreground)}.required-indicator,.jv-form-error{color:var(--jv-color-danger)}.jv-form-hint,.jv-form-error{margin:0;font-size:.875rem}.jv-form-hint{color:var(--jv-color-foreground-muted)}\n"] });
360
+ }
361
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvFormContainerComponent, decorators: [{
362
+ type: Component,
363
+ args: [{ selector: 'jv-form-container', standalone: true, template: `
364
+ <div class="jv-form-container" [class.is-invalid]="invalid()">
365
+ @if (label()) {
366
+ <label class="jv-form-label" [attr.for]="inputId() || null">
367
+ <span>{{ label() }}</span>
368
+ @if (required()) {
369
+ <span class="required-indicator" aria-hidden="true">*</span>
370
+ }
371
+ </label>
372
+ }
373
+
374
+ <ng-content></ng-content>
375
+
376
+ @if (hint() && !invalid()) {
377
+ <p class="jv-form-hint" [id]="resolvedHintId() || null">{{ hint() }}</p>
378
+ }
379
+
380
+ @if (error() && invalid()) {
381
+ <p class="jv-form-error" [id]="resolvedErrorId() || null" role="alert" aria-live="assertive">{{ error() }}</p>
382
+ }
383
+ </div>
384
+ `, styles: [".jv-form-container{display:grid;gap:var(--jv-spacing-xs)}.jv-form-label{display:inline-flex;align-items:center;gap:.35rem;font-weight:600;color:var(--jv-color-foreground)}.required-indicator,.jv-form-error{color:var(--jv-color-danger)}.jv-form-hint,.jv-form-error{margin:0;font-size:.875rem}.jv-form-hint{color:var(--jv-color-foreground-muted)}\n"] }]
385
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], hintId: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintId", required: false }] }], errorId: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorId", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }] } });
386
+
387
+ let inputIdSequence = 0;
388
+ class JvInputComponent {
389
+ type = input('text', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
390
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
391
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
392
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
393
+ describedBy = input('', ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
394
+ inputId = input(`jv-input-${++inputIdSequence}`, ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
395
+ value = '';
396
+ disabled = false;
397
+ onChange = () => undefined;
398
+ onTouched = () => undefined;
399
+ writeValue(value) {
400
+ this.value = value ?? '';
401
+ }
402
+ registerOnChange(fn) {
403
+ this.onChange = fn;
404
+ }
405
+ registerOnTouched(fn) {
406
+ this.onTouched = fn;
407
+ }
408
+ setDisabledState(isDisabled) {
409
+ this.disabled = isDisabled;
410
+ }
411
+ handleInput(event) {
412
+ const nextValue = event.target.value;
413
+ this.value = nextValue;
414
+ this.onChange(nextValue);
415
+ }
416
+ handleBlur() {
417
+ this.onTouched();
418
+ }
419
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
420
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvInputComponent, isStandalone: true, selector: "jv-input", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, describedBy: { classPropertyName: "describedBy", publicName: "describedBy", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
421
+ {
422
+ provide: NG_VALUE_ACCESSOR,
423
+ useExisting: forwardRef(() => JvInputComponent),
424
+ multi: true,
425
+ },
426
+ ], ngImport: i0, template: `
427
+ <input
428
+ class="jv-input"
429
+ [class.is-invalid]="invalid()"
430
+ [id]="inputId()"
431
+ [type]="type()"
432
+ [value]="value"
433
+ [placeholder]="placeholder()"
434
+ [required]="required()"
435
+ [disabled]="disabled"
436
+ [attr.aria-invalid]="invalid() ? 'true' : null"
437
+ [attr.aria-describedby]="describedBy() || null"
438
+ (input)="handleInput($event)"
439
+ (blur)="handleBlur()"
440
+ />
441
+ `, isInline: true, styles: [".jv-input{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-input::placeholder{color:var(--jv-color-foreground-muted)}.jv-input.is-invalid{border-color:var(--jv-color-danger)}.jv-input:disabled{opacity:.7;cursor:not-allowed}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
442
+ }
443
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvInputComponent, decorators: [{
444
+ type: Component,
445
+ args: [{ selector: 'jv-input', standalone: true, imports: [CommonModule], providers: [
446
+ {
447
+ provide: NG_VALUE_ACCESSOR,
448
+ useExisting: forwardRef(() => JvInputComponent),
449
+ multi: true,
450
+ },
451
+ ], template: `
452
+ <input
453
+ class="jv-input"
454
+ [class.is-invalid]="invalid()"
455
+ [id]="inputId()"
456
+ [type]="type()"
457
+ [value]="value"
458
+ [placeholder]="placeholder()"
459
+ [required]="required()"
460
+ [disabled]="disabled"
461
+ [attr.aria-invalid]="invalid() ? 'true' : null"
462
+ [attr.aria-describedby]="describedBy() || null"
463
+ (input)="handleInput($event)"
464
+ (blur)="handleBlur()"
465
+ />
466
+ `, styles: [".jv-input{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-input::placeholder{color:var(--jv-color-foreground-muted)}.jv-input.is-invalid{border-color:var(--jv-color-danger)}.jv-input:disabled{opacity:.7;cursor:not-allowed}\n"] }]
467
+ }], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], describedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "describedBy", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }] } });
468
+
469
+ class JvSectionComponent {
470
+ title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
471
+ description = input('', ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
472
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
473
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvSectionComponent, isStandalone: true, selector: "jv-section", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
474
+ <section class="jv-section">
475
+ @if (title() || description()) {
476
+ <header class="jv-section-header">
477
+ @if (title()) {
478
+ <h2>{{ title() }}</h2>
479
+ }
480
+ @if (description()) {
481
+ <p>{{ description() }}</p>
482
+ }
483
+ </header>
484
+ }
485
+ <div class="jv-section-content"><ng-content></ng-content></div>
486
+ </section>
487
+ `, isInline: true, styles: [".jv-section{display:grid;gap:var(--jv-spacing-md)}.jv-section-header{display:grid;gap:var(--jv-spacing-xs)}h2,p{margin:0}p{color:var(--jv-color-foreground-muted)}\n"] });
488
+ }
489
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSectionComponent, decorators: [{
490
+ type: Component,
491
+ args: [{ selector: 'jv-section', standalone: true, template: `
492
+ <section class="jv-section">
493
+ @if (title() || description()) {
494
+ <header class="jv-section-header">
495
+ @if (title()) {
496
+ <h2>{{ title() }}</h2>
497
+ }
498
+ @if (description()) {
499
+ <p>{{ description() }}</p>
500
+ }
501
+ </header>
502
+ }
503
+ <div class="jv-section-content"><ng-content></ng-content></div>
504
+ </section>
505
+ `, styles: [".jv-section{display:grid;gap:var(--jv-spacing-md)}.jv-section-header{display:grid;gap:var(--jv-spacing-xs)}h2,p{margin:0}p{color:var(--jv-color-foreground-muted)}\n"] }]
506
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }] } });
507
+
508
+ function passwordMatchValidator(control) {
509
+ const newPassword = control.get('newPassword')?.value;
510
+ const confirmPassword = control.get('confirmPassword')?.value;
511
+ if (!newPassword || !confirmPassword) {
512
+ return null;
513
+ }
514
+ return newPassword === confirmPassword ? null : { passwordMismatch: true };
515
+ }
516
+ class JvChangePasswordPageComponent {
517
+ formBuilder = inject(FormBuilder);
518
+ title = input('Change password', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
519
+ description = input('Update your password and keep your account secure.', ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
520
+ submitLabel = input('Update password', ...(ngDevMode ? [{ debugName: "submitLabel" }] : /* istanbul ignore next */ []));
521
+ cancelLabel = input('Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
522
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
523
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
524
+ changePasswordSubmit = output();
525
+ cancel = output();
526
+ form = this.formBuilder.group({
527
+ currentPassword: ['', Validators.required],
528
+ newPassword: ['', Validators.required],
529
+ confirmPassword: ['', Validators.required],
530
+ }, { validators: passwordMatchValidator });
531
+ currentInvalid = computed(() => {
532
+ const control = this.form.controls.currentPassword;
533
+ return control.invalid && (control.touched || control.dirty);
534
+ }, ...(ngDevMode ? [{ debugName: "currentInvalid" }] : /* istanbul ignore next */ []));
535
+ newInvalid = computed(() => {
536
+ const control = this.form.controls.newPassword;
537
+ return control.invalid && (control.touched || control.dirty);
538
+ }, ...(ngDevMode ? [{ debugName: "newInvalid" }] : /* istanbul ignore next */ []));
539
+ confirmInvalid = computed(() => {
540
+ const control = this.form.controls.confirmPassword;
541
+ return (control.invalid && (control.touched || control.dirty)) || ((control.touched || control.dirty) && this.form.hasError('passwordMismatch'));
542
+ }, ...(ngDevMode ? [{ debugName: "confirmInvalid" }] : /* istanbul ignore next */ []));
543
+ submit() {
544
+ if (this.form.invalid) {
545
+ this.form.markAllAsTouched();
546
+ return;
547
+ }
548
+ this.changePasswordSubmit.emit(this.form.getRawValue());
549
+ }
550
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvChangePasswordPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
551
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvChangePasswordPageComponent, isStandalone: true, selector: "jv-change-password-page", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, submitLabel: { classPropertyName: "submitLabel", publicName: "submitLabel", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changePasswordSubmit: "changePasswordSubmit", cancel: "cancel" }, ngImport: i0, template: `
552
+ <jv-card>
553
+ <jv-section [title]="title()" [description]="description()">
554
+ @if (error()) {
555
+ <jv-alert tone="danger" title="Password change error" role="alert">{{ error() }}</jv-alert>
556
+ }
557
+
558
+ <form class="auth-form" [formGroup]="form" (ngSubmit)="submit()">
559
+ <jv-form-container label="Current password" inputId="jv-current-password" error="Current password is required" [required]="true" [invalid]="currentInvalid()">
560
+ <jv-input inputId="jv-current-password" type="password" formControlName="currentPassword" [invalid]="currentInvalid()" />
561
+ </jv-form-container>
562
+
563
+ <jv-form-container label="New password" inputId="jv-new-password" error="New password is required" [required]="true" [invalid]="newInvalid()">
564
+ <jv-input inputId="jv-new-password" type="password" formControlName="newPassword" [invalid]="newInvalid()" />
565
+ </jv-form-container>
566
+
567
+ <jv-form-container label="Confirm new password" inputId="jv-confirm-password" error="Passwords must match" [required]="true" [invalid]="confirmInvalid()">
568
+ <jv-input inputId="jv-confirm-password" type="password" formControlName="confirmPassword" [invalid]="confirmInvalid()" />
569
+ </jv-form-container>
570
+
571
+ <div class="auth-actions">
572
+ <jv-button variant="outline" (click)="cancel.emit()">{{ cancelLabel() }}</jv-button>
573
+ <jv-button variant="primary" icon="check" [loading]="loading()">{{ submitLabel() }}</jv-button>
574
+ </div>
575
+ </form>
576
+ </jv-section>
577
+ </jv-card>
578
+ `, isInline: true, styles: [".auth-form{display:grid;gap:var(--jv-spacing-md)}.auth-actions{display:flex;gap:var(--jv-spacing-sm);flex-wrap:wrap}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: JvAlertComponent, selector: "jv-alert", inputs: ["tone", "title", "role"] }, { kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvCardComponent, selector: "jv-card", inputs: ["elevated"] }, { kind: "component", type: JvFormContainerComponent, selector: "jv-form-container", inputs: ["label", "hint", "error", "inputId", "hintId", "errorId", "required", "invalid"] }, { kind: "component", type: JvInputComponent, selector: "jv-input", inputs: ["type", "placeholder", "required", "invalid", "describedBy", "inputId"] }, { kind: "component", type: JvSectionComponent, selector: "jv-section", inputs: ["title", "description"] }] });
579
+ }
580
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvChangePasswordPageComponent, decorators: [{
581
+ type: Component,
582
+ args: [{ selector: 'jv-change-password-page', standalone: true, imports: [ReactiveFormsModule, JvAlertComponent, JvButtonComponent, JvCardComponent, JvFormContainerComponent, JvInputComponent, JvSectionComponent], template: `
583
+ <jv-card>
584
+ <jv-section [title]="title()" [description]="description()">
585
+ @if (error()) {
586
+ <jv-alert tone="danger" title="Password change error" role="alert">{{ error() }}</jv-alert>
587
+ }
588
+
589
+ <form class="auth-form" [formGroup]="form" (ngSubmit)="submit()">
590
+ <jv-form-container label="Current password" inputId="jv-current-password" error="Current password is required" [required]="true" [invalid]="currentInvalid()">
591
+ <jv-input inputId="jv-current-password" type="password" formControlName="currentPassword" [invalid]="currentInvalid()" />
592
+ </jv-form-container>
593
+
594
+ <jv-form-container label="New password" inputId="jv-new-password" error="New password is required" [required]="true" [invalid]="newInvalid()">
595
+ <jv-input inputId="jv-new-password" type="password" formControlName="newPassword" [invalid]="newInvalid()" />
596
+ </jv-form-container>
597
+
598
+ <jv-form-container label="Confirm new password" inputId="jv-confirm-password" error="Passwords must match" [required]="true" [invalid]="confirmInvalid()">
599
+ <jv-input inputId="jv-confirm-password" type="password" formControlName="confirmPassword" [invalid]="confirmInvalid()" />
600
+ </jv-form-container>
601
+
602
+ <div class="auth-actions">
603
+ <jv-button variant="outline" (click)="cancel.emit()">{{ cancelLabel() }}</jv-button>
604
+ <jv-button variant="primary" icon="check" [loading]="loading()">{{ submitLabel() }}</jv-button>
605
+ </div>
606
+ </form>
607
+ </jv-section>
608
+ </jv-card>
609
+ `, styles: [".auth-form{display:grid;gap:var(--jv-spacing-md)}.auth-actions{display:flex;gap:var(--jv-spacing-sm);flex-wrap:wrap}\n"] }]
610
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], submitLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitLabel", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], changePasswordSubmit: [{ type: i0.Output, args: ["changePasswordSubmit"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
611
+
612
+ class JvForgotPasswordPageComponent {
613
+ formBuilder = inject(FormBuilder);
614
+ title = input('Reset your password', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
615
+ description = input('We will send reset instructions to your email address.', ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
616
+ submitLabel = input('Send reset link', ...(ngDevMode ? [{ debugName: "submitLabel" }] : /* istanbul ignore next */ []));
617
+ backLabel = input('Back to login', ...(ngDevMode ? [{ debugName: "backLabel" }] : /* istanbul ignore next */ []));
618
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
619
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
620
+ forgotPasswordSubmit = output();
621
+ backToLogin = output();
622
+ form = this.formBuilder.group({
623
+ email: ['', [Validators.required, Validators.email]],
624
+ });
625
+ emailInvalid = computed(() => {
626
+ const control = this.form.controls.email;
627
+ return control.invalid && (control.touched || control.dirty);
628
+ }, ...(ngDevMode ? [{ debugName: "emailInvalid" }] : /* istanbul ignore next */ []));
629
+ submit() {
630
+ if (this.form.invalid) {
631
+ this.form.markAllAsTouched();
632
+ return;
633
+ }
634
+ this.forgotPasswordSubmit.emit(this.form.getRawValue());
635
+ }
636
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvForgotPasswordPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
637
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvForgotPasswordPageComponent, isStandalone: true, selector: "jv-forgot-password-page", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, submitLabel: { classPropertyName: "submitLabel", publicName: "submitLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { forgotPasswordSubmit: "forgotPasswordSubmit", backToLogin: "backToLogin" }, ngImport: i0, template: `
638
+ <jv-card>
639
+ <jv-section [title]="title()" [description]="description()">
640
+ @if (error()) {
641
+ <jv-alert tone="danger" title="Reset error" role="alert">{{ error() }}</jv-alert>
642
+ }
643
+
644
+ <form class="auth-form" [formGroup]="form" (ngSubmit)="submit()">
645
+ <jv-form-container label="Email" inputId="jv-forgot-email" error="Enter a valid email" [required]="true" [invalid]="emailInvalid()">
646
+ <jv-input inputId="jv-forgot-email" type="email" formControlName="email" [invalid]="emailInvalid()" placeholder="you@example.com" />
647
+ </jv-form-container>
648
+
649
+ <div class="auth-actions">
650
+ <jv-button variant="outline" (click)="backToLogin.emit()">{{ backLabel() }}</jv-button>
651
+ <jv-button variant="primary" icon="check" [loading]="loading()">{{ submitLabel() }}</jv-button>
652
+ </div>
653
+ </form>
654
+ </jv-section>
655
+ </jv-card>
656
+ `, isInline: true, styles: [".auth-form{display:grid;gap:var(--jv-spacing-md)}.auth-actions{display:flex;gap:var(--jv-spacing-sm);flex-wrap:wrap}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: JvAlertComponent, selector: "jv-alert", inputs: ["tone", "title", "role"] }, { kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvCardComponent, selector: "jv-card", inputs: ["elevated"] }, { kind: "component", type: JvFormContainerComponent, selector: "jv-form-container", inputs: ["label", "hint", "error", "inputId", "hintId", "errorId", "required", "invalid"] }, { kind: "component", type: JvInputComponent, selector: "jv-input", inputs: ["type", "placeholder", "required", "invalid", "describedBy", "inputId"] }, { kind: "component", type: JvSectionComponent, selector: "jv-section", inputs: ["title", "description"] }] });
657
+ }
658
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvForgotPasswordPageComponent, decorators: [{
659
+ type: Component,
660
+ args: [{ selector: 'jv-forgot-password-page', standalone: true, imports: [ReactiveFormsModule, JvAlertComponent, JvButtonComponent, JvCardComponent, JvFormContainerComponent, JvInputComponent, JvSectionComponent], template: `
661
+ <jv-card>
662
+ <jv-section [title]="title()" [description]="description()">
663
+ @if (error()) {
664
+ <jv-alert tone="danger" title="Reset error" role="alert">{{ error() }}</jv-alert>
665
+ }
666
+
667
+ <form class="auth-form" [formGroup]="form" (ngSubmit)="submit()">
668
+ <jv-form-container label="Email" inputId="jv-forgot-email" error="Enter a valid email" [required]="true" [invalid]="emailInvalid()">
669
+ <jv-input inputId="jv-forgot-email" type="email" formControlName="email" [invalid]="emailInvalid()" placeholder="you@example.com" />
670
+ </jv-form-container>
671
+
672
+ <div class="auth-actions">
673
+ <jv-button variant="outline" (click)="backToLogin.emit()">{{ backLabel() }}</jv-button>
674
+ <jv-button variant="primary" icon="check" [loading]="loading()">{{ submitLabel() }}</jv-button>
675
+ </div>
676
+ </form>
677
+ </jv-section>
678
+ </jv-card>
679
+ `, styles: [".auth-form{display:grid;gap:var(--jv-spacing-md)}.auth-actions{display:flex;gap:var(--jv-spacing-sm);flex-wrap:wrap}\n"] }]
680
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], submitLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitLabel", required: false }] }], backLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "backLabel", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], forgotPasswordSubmit: [{ type: i0.Output, args: ["forgotPasswordSubmit"] }], backToLogin: [{ type: i0.Output, args: ["backToLogin"] }] } });
681
+
682
+ let checkboxIdSequence = 0;
683
+ class JvCheckboxComponent {
684
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
685
+ describedBy = input('', ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
686
+ inputId = input(`jv-checkbox-${++checkboxIdSequence}`, ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
687
+ value = false;
688
+ disabled = false;
689
+ onChange = () => undefined;
690
+ onTouched = () => undefined;
691
+ writeValue(value) {
692
+ this.value = value ?? false;
693
+ }
694
+ registerOnChange(fn) {
695
+ this.onChange = fn;
696
+ }
697
+ registerOnTouched(fn) {
698
+ this.onTouched = fn;
699
+ }
700
+ setDisabledState(isDisabled) {
701
+ this.disabled = isDisabled;
702
+ }
703
+ handleChange(event) {
704
+ const nextValue = event.target.checked;
705
+ this.value = nextValue;
706
+ this.onChange(nextValue);
707
+ }
708
+ handleBlur() {
709
+ this.onTouched();
710
+ }
711
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvCheckboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
712
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvCheckboxComponent, isStandalone: true, selector: "jv-checkbox", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, describedBy: { classPropertyName: "describedBy", publicName: "describedBy", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
713
+ {
714
+ provide: NG_VALUE_ACCESSOR,
715
+ useExisting: forwardRef(() => JvCheckboxComponent),
716
+ multi: true,
717
+ },
718
+ ], ngImport: i0, template: `
719
+ <label class="jv-checkbox" [class.is-disabled]="disabled">
720
+ <input
721
+ type="checkbox"
722
+ [id]="inputId()"
723
+ [checked]="value"
724
+ [disabled]="disabled"
725
+ [attr.aria-describedby]="describedBy() || null"
726
+ (change)="handleChange($event)"
727
+ (blur)="handleBlur()"
728
+ />
729
+ <span>{{ label() }}</span>
730
+ </label>
731
+ `, isInline: true, styles: [".jv-checkbox{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);min-height:var(--jv-density-control-height);color:var(--jv-color-foreground)}.jv-checkbox.is-disabled{opacity:.7}\n"] });
732
+ }
733
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvCheckboxComponent, decorators: [{
734
+ type: Component,
735
+ args: [{ selector: 'jv-checkbox', standalone: true, providers: [
736
+ {
737
+ provide: NG_VALUE_ACCESSOR,
738
+ useExisting: forwardRef(() => JvCheckboxComponent),
739
+ multi: true,
740
+ },
741
+ ], template: `
742
+ <label class="jv-checkbox" [class.is-disabled]="disabled">
743
+ <input
744
+ type="checkbox"
745
+ [id]="inputId()"
746
+ [checked]="value"
747
+ [disabled]="disabled"
748
+ [attr.aria-describedby]="describedBy() || null"
749
+ (change)="handleChange($event)"
750
+ (blur)="handleBlur()"
751
+ />
752
+ <span>{{ label() }}</span>
753
+ </label>
754
+ `, styles: [".jv-checkbox{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);min-height:var(--jv-density-control-height);color:var(--jv-color-foreground)}.jv-checkbox.is-disabled{opacity:.7}\n"] }]
755
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], describedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "describedBy", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }] } });
756
+
757
+ class JvLoginPageComponent {
758
+ formBuilder = inject(FormBuilder);
759
+ title = input('Welcome back', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
760
+ description = input('Use your account credentials to continue.', ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
761
+ submitLabel = input('Sign in', ...(ngDevMode ? [{ debugName: "submitLabel" }] : /* istanbul ignore next */ []));
762
+ rememberMeLabel = input('Remember me', ...(ngDevMode ? [{ debugName: "rememberMeLabel" }] : /* istanbul ignore next */ []));
763
+ forgotPasswordLabel = input('Forgot password?', ...(ngDevMode ? [{ debugName: "forgotPasswordLabel" }] : /* istanbul ignore next */ []));
764
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
765
+ error = input('', ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
766
+ loginSubmit = output();
767
+ forgotPasswordClick = output();
768
+ form = this.formBuilder.group({
769
+ identifier: ['', Validators.required],
770
+ password: ['', Validators.required],
771
+ rememberMe: [false],
772
+ });
773
+ identifierInvalid = computed(() => {
774
+ const control = this.form.controls.identifier;
775
+ return control.invalid && (control.touched || control.dirty);
776
+ }, ...(ngDevMode ? [{ debugName: "identifierInvalid" }] : /* istanbul ignore next */ []));
777
+ passwordInvalid = computed(() => {
778
+ const control = this.form.controls.password;
779
+ return control.invalid && (control.touched || control.dirty);
780
+ }, ...(ngDevMode ? [{ debugName: "passwordInvalid" }] : /* istanbul ignore next */ []));
781
+ submit() {
782
+ if (this.form.invalid) {
783
+ this.form.markAllAsTouched();
784
+ return;
785
+ }
786
+ this.loginSubmit.emit(this.form.getRawValue());
787
+ }
788
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvLoginPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
789
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvLoginPageComponent, isStandalone: true, selector: "jv-login-page", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, submitLabel: { classPropertyName: "submitLabel", publicName: "submitLabel", isSignal: true, isRequired: false, transformFunction: null }, rememberMeLabel: { classPropertyName: "rememberMeLabel", publicName: "rememberMeLabel", isSignal: true, isRequired: false, transformFunction: null }, forgotPasswordLabel: { classPropertyName: "forgotPasswordLabel", publicName: "forgotPasswordLabel", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loginSubmit: "loginSubmit", forgotPasswordClick: "forgotPasswordClick" }, ngImport: i0, template: `
790
+ <jv-card>
791
+ <jv-section [title]="title()" [description]="description()">
792
+ @if (error()) {
793
+ <jv-alert tone="danger" title="Sign-in error" role="alert">{{ error() }}</jv-alert>
794
+ }
795
+
796
+ <form class="auth-form" [formGroup]="form" (ngSubmit)="submit()">
797
+ <jv-form-container label="Email or username" inputId="jv-login-identifier" error="This field is required" [required]="true" [invalid]="identifierInvalid()">
798
+ <jv-input inputId="jv-login-identifier" formControlName="identifier" [invalid]="identifierInvalid()" placeholder="you@example.com" />
799
+ </jv-form-container>
800
+
801
+ <jv-form-container label="Password" inputId="jv-login-password" error="Password is required" [required]="true" [invalid]="passwordInvalid()">
802
+ <jv-input inputId="jv-login-password" type="password" formControlName="password" [invalid]="passwordInvalid()" placeholder="Enter your password" />
803
+ </jv-form-container>
804
+
805
+ <div class="auth-inline-row">
806
+ <jv-checkbox formControlName="rememberMe" label="{{ rememberMeLabel() }}" />
807
+ <button type="button" class="auth-link" (click)="forgotPasswordClick.emit()">{{ forgotPasswordLabel() }}</button>
808
+ </div>
809
+
810
+ <jv-button variant="primary" icon="check" [loading]="loading()">{{ submitLabel() }}</jv-button>
811
+ </form>
812
+ </jv-section>
813
+ </jv-card>
814
+ `, isInline: true, styles: [".auth-form{display:grid;gap:var(--jv-spacing-md)}.auth-inline-row{display:flex;justify-content:space-between;align-items:center;gap:var(--jv-spacing-md);flex-wrap:wrap}.auth-link{border:0;padding:0;background:transparent;color:var(--jv-color-primary);text-decoration:underline;text-underline-offset:.18em}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: JvAlertComponent, selector: "jv-alert", inputs: ["tone", "title", "role"] }, { kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvCardComponent, selector: "jv-card", inputs: ["elevated"] }, { kind: "component", type: JvCheckboxComponent, selector: "jv-checkbox", inputs: ["label", "describedBy", "inputId"] }, { kind: "component", type: JvFormContainerComponent, selector: "jv-form-container", inputs: ["label", "hint", "error", "inputId", "hintId", "errorId", "required", "invalid"] }, { kind: "component", type: JvInputComponent, selector: "jv-input", inputs: ["type", "placeholder", "required", "invalid", "describedBy", "inputId"] }, { kind: "component", type: JvSectionComponent, selector: "jv-section", inputs: ["title", "description"] }] });
815
+ }
816
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvLoginPageComponent, decorators: [{
817
+ type: Component,
818
+ args: [{ selector: 'jv-login-page', standalone: true, imports: [ReactiveFormsModule, JvAlertComponent, JvButtonComponent, JvCardComponent, JvCheckboxComponent, JvFormContainerComponent, JvInputComponent, JvSectionComponent], template: `
819
+ <jv-card>
820
+ <jv-section [title]="title()" [description]="description()">
821
+ @if (error()) {
822
+ <jv-alert tone="danger" title="Sign-in error" role="alert">{{ error() }}</jv-alert>
823
+ }
824
+
825
+ <form class="auth-form" [formGroup]="form" (ngSubmit)="submit()">
826
+ <jv-form-container label="Email or username" inputId="jv-login-identifier" error="This field is required" [required]="true" [invalid]="identifierInvalid()">
827
+ <jv-input inputId="jv-login-identifier" formControlName="identifier" [invalid]="identifierInvalid()" placeholder="you@example.com" />
828
+ </jv-form-container>
829
+
830
+ <jv-form-container label="Password" inputId="jv-login-password" error="Password is required" [required]="true" [invalid]="passwordInvalid()">
831
+ <jv-input inputId="jv-login-password" type="password" formControlName="password" [invalid]="passwordInvalid()" placeholder="Enter your password" />
832
+ </jv-form-container>
833
+
834
+ <div class="auth-inline-row">
835
+ <jv-checkbox formControlName="rememberMe" label="{{ rememberMeLabel() }}" />
836
+ <button type="button" class="auth-link" (click)="forgotPasswordClick.emit()">{{ forgotPasswordLabel() }}</button>
837
+ </div>
838
+
839
+ <jv-button variant="primary" icon="check" [loading]="loading()">{{ submitLabel() }}</jv-button>
840
+ </form>
841
+ </jv-section>
842
+ </jv-card>
843
+ `, styles: [".auth-form{display:grid;gap:var(--jv-spacing-md)}.auth-inline-row{display:flex;justify-content:space-between;align-items:center;gap:var(--jv-spacing-md);flex-wrap:wrap}.auth-link{border:0;padding:0;background:transparent;color:var(--jv-color-primary);text-decoration:underline;text-underline-offset:.18em}\n"] }]
844
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], submitLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitLabel", required: false }] }], rememberMeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rememberMeLabel", required: false }] }], forgotPasswordLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "forgotPasswordLabel", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], loginSubmit: [{ type: i0.Output, args: ["loginSubmit"] }], forgotPasswordClick: [{ type: i0.Output, args: ["forgotPasswordClick"] }] } });
845
+
846
+ class JvDividerComponent {
847
+ vertical = input(false, ...(ngDevMode ? [{ debugName: "vertical" }] : /* istanbul ignore next */ []));
848
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDividerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
849
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvDividerComponent, isStandalone: true, selector: "jv-divider", inputs: { vertical: { classPropertyName: "vertical", publicName: "vertical", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
850
+ <div
851
+ class="jv-divider"
852
+ [class.jv-divider--vertical]="vertical()"
853
+ [attr.role]="'separator'"
854
+ [attr.aria-orientation]="vertical() ? 'vertical' : 'horizontal'"
855
+ ></div>
856
+ `, isInline: true, styles: [".jv-divider{width:100%;height:1px;background:var(--jv-color-border)}.jv-divider--vertical{width:1px;height:100%;min-height:1.5rem}\n"] });
857
+ }
858
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDividerComponent, decorators: [{
859
+ type: Component,
860
+ args: [{ selector: 'jv-divider', standalone: true, template: `
861
+ <div
862
+ class="jv-divider"
863
+ [class.jv-divider--vertical]="vertical()"
864
+ [attr.role]="'separator'"
865
+ [attr.aria-orientation]="vertical() ? 'vertical' : 'horizontal'"
866
+ ></div>
867
+ `, styles: [".jv-divider{width:100%;height:1px;background:var(--jv-color-border)}.jv-divider--vertical{width:1px;height:100%;min-height:1.5rem}\n"] }]
868
+ }], propDecorators: { vertical: [{ type: i0.Input, args: [{ isSignal: true, alias: "vertical", required: false }] }] } });
869
+
870
+ class JvBadgeComponent {
871
+ tone = input('primary', ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
872
+ badgeClass = computed(() => `jv-badge--${this.tone()}`, ...(ngDevMode ? [{ debugName: "badgeClass" }] : /* istanbul ignore next */ []));
873
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
874
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvBadgeComponent, isStandalone: true, selector: "jv-badge", inputs: { tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `<span class="jv-badge" [class]="badgeClass()"><ng-content></ng-content></span>`, isInline: true, styles: [".jv-badge{display:inline-flex;align-items:center;justify-content:center;min-height:1.75rem;padding:0 var(--jv-spacing-sm);border-radius:999px;font-size:.8rem;font-weight:700;line-height:1;white-space:nowrap}.jv-badge--primary{background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface));color:var(--jv-color-primary)}.jv-badge--secondary,.jv-badge--neutral{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-badge--success{background:color-mix(in srgb,var(--jv-color-success) 16%,var(--jv-color-surface));color:var(--jv-color-success)}.jv-badge--warning{background:color-mix(in srgb,var(--jv-color-warning) 20%,var(--jv-color-surface));color:#8a4b00}.jv-badge--danger{background:color-mix(in srgb,var(--jv-color-danger) 16%,var(--jv-color-surface));color:var(--jv-color-danger)}.jv-badge--info{background:color-mix(in srgb,var(--jv-color-info) 16%,var(--jv-color-surface));color:var(--jv-color-info)}\n"] });
875
+ }
876
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvBadgeComponent, decorators: [{
877
+ type: Component,
878
+ args: [{ selector: 'jv-badge', standalone: true, template: `<span class="jv-badge" [class]="badgeClass()"><ng-content></ng-content></span>`, styles: [".jv-badge{display:inline-flex;align-items:center;justify-content:center;min-height:1.75rem;padding:0 var(--jv-spacing-sm);border-radius:999px;font-size:.8rem;font-weight:700;line-height:1;white-space:nowrap}.jv-badge--primary{background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface));color:var(--jv-color-primary)}.jv-badge--secondary,.jv-badge--neutral{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-badge--success{background:color-mix(in srgb,var(--jv-color-success) 16%,var(--jv-color-surface));color:var(--jv-color-success)}.jv-badge--warning{background:color-mix(in srgb,var(--jv-color-warning) 20%,var(--jv-color-surface));color:#8a4b00}.jv-badge--danger{background:color-mix(in srgb,var(--jv-color-danger) 16%,var(--jv-color-surface));color:var(--jv-color-danger)}.jv-badge--info{background:color-mix(in srgb,var(--jv-color-info) 16%,var(--jv-color-surface));color:var(--jv-color-info)}\n"] }]
879
+ }], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }] } });
880
+
881
+ class JvLoaderService {
882
+ counter = signal(0, ...(ngDevMode ? [{ debugName: "counter" }] : /* istanbul ignore next */ []));
883
+ visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
884
+ message = signal('Loading', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
885
+ delayMs = 300;
886
+ showTimeout = null;
887
+ activeCount = this.counter.asReadonly();
888
+ isVisible = this.visible.asReadonly();
889
+ liveMessage = this.message.asReadonly();
890
+ show(message = 'Loading') {
891
+ this.message.set(message);
892
+ this.counter.update((count) => count + 1);
893
+ if (this.counter() === 1) {
894
+ this.scheduleVisibility();
895
+ }
896
+ }
897
+ hide() {
898
+ this.counter.update((count) => Math.max(0, count - 1));
899
+ if (this.counter() === 0) {
900
+ this.clearTimer();
901
+ this.visible.set(false);
902
+ }
903
+ }
904
+ scheduleVisibility() {
905
+ this.clearTimer();
906
+ this.showTimeout = setTimeout(() => {
907
+ if (this.counter() > 0) {
908
+ this.visible.set(true);
909
+ }
910
+ this.showTimeout = null;
911
+ }, this.delayMs);
912
+ }
913
+ clearTimer() {
914
+ if (this.showTimeout) {
915
+ clearTimeout(this.showTimeout);
916
+ this.showTimeout = null;
917
+ }
918
+ }
919
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
920
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvLoaderService, providedIn: 'root' });
921
+ }
922
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvLoaderService, decorators: [{
923
+ type: Injectable,
924
+ args: [{ providedIn: 'root' }]
925
+ }] });
926
+
927
+ class JvLoaderComponent {
928
+ document = inject(DOCUMENT);
929
+ loaderService = inject(JvLoaderService);
930
+ constructor() {
931
+ effect(() => {
932
+ const locked = this.loaderService.isVisible();
933
+ const body = this.document.body;
934
+ const root = this.document.documentElement;
935
+ body.style.overflow = locked ? 'hidden' : '';
936
+ root.style.overflow = locked ? 'hidden' : '';
937
+ });
938
+ }
939
+ handleWheel(event) {
940
+ if (this.loaderService.isVisible()) {
941
+ event.preventDefault();
942
+ }
943
+ }
944
+ handleTouchMove(event) {
945
+ if (this.loaderService.isVisible()) {
946
+ event.preventDefault();
947
+ }
948
+ }
949
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvLoaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
950
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvLoaderComponent, isStandalone: true, selector: "jv-loader", host: { listeners: { "document:wheel": "handleWheel($event)", "document:touchmove": "handleTouchMove($event)" } }, ngImport: i0, template: `
951
+ @if (loaderService.isVisible()) {
952
+ <div class="jv-loader-backdrop" aria-live="polite" aria-atomic="true">
953
+ <div class="jv-loader-panel" role="status">
954
+ <span class="spinner" aria-hidden="true"></span>
955
+ <span>{{ loaderService.liveMessage() }}</span>
956
+ </div>
957
+ </div>
958
+ }
959
+ `, isInline: true, styles: [".jv-loader-backdrop{position:fixed;inset:0;z-index:1050;display:grid;place-items:center;background:#0f172a2e;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.jv-loader-panel{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-md) var(--jv-spacing-lg);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg);color:var(--jv-color-foreground)}.spinner{width:1rem;height:1rem;border:2px solid currentColor;border-right-color:transparent;border-radius:999px;animation:jv-loader-spin .8s linear infinite}@keyframes jv-loader-spin{to{transform:rotate(360deg)}}\n"] });
960
+ }
961
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvLoaderComponent, decorators: [{
962
+ type: Component,
963
+ args: [{ selector: 'jv-loader', standalone: true, template: `
964
+ @if (loaderService.isVisible()) {
965
+ <div class="jv-loader-backdrop" aria-live="polite" aria-atomic="true">
966
+ <div class="jv-loader-panel" role="status">
967
+ <span class="spinner" aria-hidden="true"></span>
968
+ <span>{{ loaderService.liveMessage() }}</span>
969
+ </div>
970
+ </div>
971
+ }
972
+ `, styles: [".jv-loader-backdrop{position:fixed;inset:0;z-index:1050;display:grid;place-items:center;background:#0f172a2e;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.jv-loader-panel{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-md) var(--jv-spacing-lg);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg);color:var(--jv-color-foreground)}.spinner{width:1rem;height:1rem;border:2px solid currentColor;border-right-color:transparent;border-radius:999px;animation:jv-loader-spin .8s linear infinite}@keyframes jv-loader-spin{to{transform:rotate(360deg)}}\n"] }]
973
+ }], ctorParameters: () => [], propDecorators: { handleWheel: [{
974
+ type: HostListener,
975
+ args: ['document:wheel', ['$event']]
976
+ }], handleTouchMove: [{
977
+ type: HostListener,
978
+ args: ['document:touchmove', ['$event']]
979
+ }] } });
980
+
981
+ class JvAnnouncementService {
982
+ document = inject(DOCUMENT);
983
+ region = null;
984
+ announce(message, politeness = 'polite') {
985
+ if (!message.trim()) {
986
+ return;
987
+ }
988
+ const region = this.ensureRegion(politeness);
989
+ region.textContent = '';
990
+ // Clear and reinsert so NVDA reliably reads repeated updates.
991
+ setTimeout(() => {
992
+ region.textContent = message;
993
+ }, 20);
994
+ }
995
+ ensureRegion(politeness) {
996
+ if (!this.region) {
997
+ const region = this.document.createElement('div');
998
+ region.className = 'sr-only';
999
+ region.setAttribute('role', politeness === 'assertive' ? 'alert' : 'status');
1000
+ region.setAttribute('aria-live', politeness);
1001
+ region.setAttribute('aria-atomic', 'true');
1002
+ this.document.body.appendChild(region);
1003
+ this.region = region;
1004
+ return region;
1005
+ }
1006
+ this.region.setAttribute('role', politeness === 'assertive' ? 'alert' : 'status');
1007
+ this.region.setAttribute('aria-live', politeness);
1008
+ return this.region;
1009
+ }
1010
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvAnnouncementService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1011
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvAnnouncementService, providedIn: 'root' });
1012
+ }
1013
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvAnnouncementService, decorators: [{
1014
+ type: Injectable,
1015
+ args: [{ providedIn: 'root' }]
1016
+ }] });
1017
+
1018
+ const JV_UI_DEFAULT_CONFIG = {
1019
+ theme: 'light',
1020
+ density: 'normal',
1021
+ language: 'es-MX',
1022
+ locale: 'es',
1023
+ toastPosition: 'bottom-right',
1024
+ };
1025
+
1026
+ class JvThemeService {
1027
+ document = inject(DOCUMENT);
1028
+ config = inject(JvUiConfigService);
1029
+ storageKey = 'jv-ui-theme';
1030
+ theme = signal(this.config.snapshot.theme, ...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
1031
+ density = signal(this.config.snapshot.density, ...(ngDevMode ? [{ debugName: "density" }] : /* istanbul ignore next */ []));
1032
+ initialize() {
1033
+ const initialConfig = this.config.snapshot;
1034
+ const theme = this.resolveInitialTheme(initialConfig.theme);
1035
+ this.applyTheme(theme);
1036
+ this.applyDensity(initialConfig.density);
1037
+ this.applyLanguage(initialConfig.language);
1038
+ }
1039
+ setTheme(theme) {
1040
+ this.applyTheme(theme);
1041
+ try {
1042
+ this.document.defaultView?.localStorage.setItem(this.storageKey, theme);
1043
+ }
1044
+ catch {
1045
+ // Ignore storage failures in restricted environments.
1046
+ }
1047
+ }
1048
+ setDensity(density) {
1049
+ this.applyDensity(density);
1050
+ }
1051
+ applyTheme(theme) {
1052
+ this.theme.set(theme);
1053
+ const root = this.document.documentElement;
1054
+ root.dataset['theme'] = theme;
1055
+ root.style.colorScheme = theme === 'dark' ? 'dark' : 'light';
1056
+ }
1057
+ applyDensity(density) {
1058
+ this.density.set(density);
1059
+ this.document.documentElement.dataset['density'] = density;
1060
+ }
1061
+ applyLanguage(language) {
1062
+ this.document.documentElement.lang = language;
1063
+ }
1064
+ resolveInitialTheme(fallbackTheme) {
1065
+ const savedTheme = this.readSavedTheme();
1066
+ if (savedTheme) {
1067
+ return savedTheme;
1068
+ }
1069
+ if (fallbackTheme === 'high-contrast') {
1070
+ return fallbackTheme;
1071
+ }
1072
+ return this.prefersDarkMode() ? 'dark' : fallbackTheme;
1073
+ }
1074
+ readSavedTheme() {
1075
+ try {
1076
+ const saved = this.document.defaultView?.localStorage.getItem(this.storageKey);
1077
+ return this.isTheme(saved) ? saved : null;
1078
+ }
1079
+ catch {
1080
+ return null;
1081
+ }
1082
+ }
1083
+ prefersDarkMode() {
1084
+ return this.document.defaultView?.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
1085
+ }
1086
+ isTheme(value) {
1087
+ return value === 'light' || value === 'dark' || value === 'high-contrast';
1088
+ }
1089
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1090
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvThemeService, providedIn: 'root' });
1091
+ }
1092
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvThemeService, decorators: [{
1093
+ type: Injectable,
1094
+ args: [{ providedIn: 'root' }]
1095
+ }] });
1096
+
1097
+ const ES = {
1098
+ 'grid.emptyMessage': 'No hay registros',
1099
+ 'grid.noResults': 'No se encontraron resultados',
1100
+ 'grid.searchPlaceholder': 'Buscar...',
1101
+ 'grid.loading': 'Cargando datos...',
1102
+ 'grid.pageOf': 'de',
1103
+ 'grid.itemsPerPage': 'Registros por página',
1104
+ 'grid.selected': 'seleccionados',
1105
+ 'grid.selectAll': 'Seleccionar todo',
1106
+ 'grid.deselectAll': 'Deseleccionar todo',
1107
+ 'grid.sortAsc': 'Orden ascendente',
1108
+ 'grid.sortDesc': 'Orden descendente',
1109
+ 'grid.sortNone': 'Sin orden',
1110
+ 'grid.pageFirst': 'Primera página',
1111
+ 'grid.pagePrev': 'Página anterior',
1112
+ 'grid.pageNext': 'Página siguiente',
1113
+ 'grid.pageLast': 'Última página',
1114
+ 'grid.pageOfTotal': 'Página {page} de {total}',
1115
+ 'grid.itemsTotal': '{total} registros',
1116
+ 'grid.itemsShowing': 'Mostrando {start}-{end} de {total}',
1117
+ };
1118
+
1119
+ const EN = {
1120
+ 'grid.emptyMessage': 'No records found',
1121
+ 'grid.noResults': 'No results found',
1122
+ 'grid.searchPlaceholder': 'Search...',
1123
+ 'grid.loading': 'Loading data...',
1124
+ 'grid.pageOf': 'of',
1125
+ 'grid.itemsPerPage': 'Items per page',
1126
+ 'grid.selected': 'selected',
1127
+ 'grid.selectAll': 'Select all',
1128
+ 'grid.deselectAll': 'Deselect all',
1129
+ 'grid.sortAsc': 'Sort ascending',
1130
+ 'grid.sortDesc': 'Sort descending',
1131
+ 'grid.sortNone': 'No sort',
1132
+ 'grid.pageFirst': 'First page',
1133
+ 'grid.pagePrev': 'Previous page',
1134
+ 'grid.pageNext': 'Next page',
1135
+ 'grid.pageLast': 'Last page',
1136
+ 'grid.pageOfTotal': 'Page {page} of {total}',
1137
+ 'grid.itemsTotal': '{total} items',
1138
+ 'grid.itemsShowing': 'Showing {start}-{end} of {total}',
1139
+ };
1140
+
1141
+ const JV_LOCALE_DICTIONARIES = {
1142
+ es: ES,
1143
+ en: EN,
1144
+ };
1145
+ const JV_DEFAULT_LOCALE = 'es';
1146
+
1147
+ class JvTranslationService {
1148
+ config = inject(JvUiConfigService);
1149
+ locale = signal(this.config.snapshot.locale, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
1150
+ initialize() {
1151
+ const locale = this.resolveLocale(this.config.snapshot.locale);
1152
+ this.locale.set(locale);
1153
+ }
1154
+ setLocale(locale) {
1155
+ this.locale.set(locale);
1156
+ }
1157
+ translate(key, params) {
1158
+ const dict = JV_LOCALE_DICTIONARIES[this.locale()] ?? JV_LOCALE_DICTIONARIES[JV_DEFAULT_LOCALE];
1159
+ let value = dict[key];
1160
+ if (value === undefined) {
1161
+ value = JV_LOCALE_DICTIONARIES[JV_DEFAULT_LOCALE][key] ?? key;
1162
+ }
1163
+ if (params) {
1164
+ for (const [k, v] of Object.entries(params)) {
1165
+ value = value.replace(`{${k}}`, String(v));
1166
+ }
1167
+ }
1168
+ return value;
1169
+ }
1170
+ resolveLocale(locale) {
1171
+ if (locale === 'es' || locale === 'en')
1172
+ return locale;
1173
+ return locale.startsWith('es') ? 'es' : 'en';
1174
+ }
1175
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTranslationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1176
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTranslationService, providedIn: 'root' });
1177
+ }
1178
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTranslationService, decorators: [{
1179
+ type: Injectable,
1180
+ args: [{ providedIn: 'root' }]
1181
+ }] });
1182
+
1183
+ const JV_UI_CONFIG = new InjectionToken('JV_UI_CONFIG');
1184
+ const JV_UI_CONFIG_DEFAULTS = new InjectionToken('JV_UI_CONFIG_DEFAULTS', {
1185
+ providedIn: 'root',
1186
+ factory: () => JV_UI_DEFAULT_CONFIG,
1187
+ });
1188
+ function provideJvUi(config = {}) {
1189
+ const configProvider = {
1190
+ provide: JV_UI_CONFIG,
1191
+ useValue: config,
1192
+ multi: true,
1193
+ };
1194
+ return makeEnvironmentProviders([
1195
+ configProvider,
1196
+ {
1197
+ provide: ENVIRONMENT_INITIALIZER,
1198
+ multi: true,
1199
+ useValue: () => {
1200
+ inject(JvThemeService).initialize();
1201
+ inject(JvTranslationService).initialize();
1202
+ },
1203
+ },
1204
+ ]);
1205
+ }
1206
+
1207
+ class JvUiConfigService {
1208
+ defaults = inject(JV_UI_CONFIG_DEFAULTS);
1209
+ configLayers = inject(JV_UI_CONFIG, { optional: true }) ?? [];
1210
+ get snapshot() {
1211
+ return this.configLayers.reduce((merged, layer) => ({ ...merged, ...layer }), { ...this.defaults });
1212
+ }
1213
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvUiConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1214
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvUiConfigService, providedIn: 'root' });
1215
+ }
1216
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvUiConfigService, decorators: [{
1217
+ type: Injectable,
1218
+ args: [{ providedIn: 'root' }]
1219
+ }] });
1220
+
1221
+ class JvToastService {
1222
+ announcementService = inject(JvAnnouncementService);
1223
+ config = inject(JvUiConfigService);
1224
+ items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
1225
+ positionOverride = signal(null, ...(ngDevMode ? [{ debugName: "positionOverride" }] : /* istanbul ignore next */ []));
1226
+ nextId = 0;
1227
+ toasts = computed(() => this.items(), ...(ngDevMode ? [{ debugName: "toasts" }] : /* istanbul ignore next */ []));
1228
+ position = computed(() => this.positionOverride() ?? this.config.snapshot.toastPosition, ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
1229
+ show(message, tone = 'info', options = {}) {
1230
+ const id = ++this.nextId;
1231
+ const toast = {
1232
+ id,
1233
+ title: options.title,
1234
+ message,
1235
+ tone,
1236
+ duration: options.duration ?? 4000,
1237
+ };
1238
+ this.items.update((current) => [...current, toast]);
1239
+ this.announcementService.announce(toast.title ? `${toast.title}. ${toast.message}` : toast.message, toast.tone === 'danger' ? 'assertive' : 'polite');
1240
+ if (toast.duration > 0) {
1241
+ setTimeout(() => this.dismiss(id), toast.duration);
1242
+ }
1243
+ return id;
1244
+ }
1245
+ dismiss(id) {
1246
+ this.items.update((current) => current.filter((item) => item.id !== id));
1247
+ }
1248
+ setPosition(position) {
1249
+ this.positionOverride.set(position);
1250
+ }
1251
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1252
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvToastService, providedIn: 'root' });
1253
+ }
1254
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvToastService, decorators: [{
1255
+ type: Injectable,
1256
+ args: [{ providedIn: 'root' }]
1257
+ }] });
1258
+
1259
+ class JvToastComponent {
1260
+ toastService = inject(JvToastService);
1261
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvToastComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1262
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvToastComponent, isStandalone: true, selector: "jv-toast", ngImport: i0, template: `
1263
+ @if (toastService.toasts().length > 0) {
1264
+ <div class="jv-toast-viewport" [class]="'is-' + toastService.position()" aria-live="polite" aria-atomic="false">
1265
+ @for (toast of toastService.toasts(); track toast.id) {
1266
+ <article class="jv-toast-item" [class]="'jv-toast--' + toast.tone" [attr.role]="toast.tone === 'danger' ? 'alert' : 'status'" aria-atomic="true">
1267
+ <div class="jv-toast-copy">
1268
+ @if (toast.title) {
1269
+ <strong>{{ toast.title }}</strong>
1270
+ }
1271
+ <p>{{ toast.message }}</p>
1272
+ </div>
1273
+
1274
+ <button type="button" class="jv-toast-close" (click)="toastService.dismiss(toast.id)" aria-label="Dismiss notification">
1275
+ <jv-icon name="x" [size]="16" />
1276
+ </button>
1277
+ </article>
1278
+ }
1279
+ </div>
1280
+ }
1281
+ `, isInline: true, styles: [".jv-toast-viewport{position:fixed;z-index:1100;display:grid;gap:var(--jv-spacing-sm);width:min(360px,calc(100vw - 2rem))}.is-top-left{top:1rem;left:1rem}.is-top-center{top:1rem;left:50%;transform:translate(-50%)}.is-top-right{top:1rem;right:1rem}.is-bottom-left{bottom:1rem;left:1rem}.is-bottom-center{bottom:1rem;left:50%;transform:translate(-50%)}.is-bottom-right{bottom:1rem;right:1rem}.jv-toast-item{display:grid;grid-template-columns:1fr auto;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg)}.jv-toast-copy strong,.jv-toast-copy p{margin:0}.jv-toast-copy{display:grid;gap:.25rem}.jv-toast-copy p{color:var(--jv-color-foreground-muted)}.jv-toast-close{display:inline-grid;place-items:center;width:2rem;height:2rem;border:0;border-radius:999px;background:transparent;color:inherit}.jv-toast--success{border-color:color-mix(in srgb,var(--jv-color-success) 35%,var(--jv-color-border))}.jv-toast--warning{border-color:color-mix(in srgb,var(--jv-color-warning) 35%,var(--jv-color-border))}.jv-toast--danger{border-color:color-mix(in srgb,var(--jv-color-danger) 35%,var(--jv-color-border))}.jv-toast--info,.jv-toast--primary{border-color:color-mix(in srgb,var(--jv-color-primary) 35%,var(--jv-color-border))}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
1282
+ }
1283
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvToastComponent, decorators: [{
1284
+ type: Component,
1285
+ args: [{ selector: 'jv-toast', standalone: true, imports: [JvIconComponent], template: `
1286
+ @if (toastService.toasts().length > 0) {
1287
+ <div class="jv-toast-viewport" [class]="'is-' + toastService.position()" aria-live="polite" aria-atomic="false">
1288
+ @for (toast of toastService.toasts(); track toast.id) {
1289
+ <article class="jv-toast-item" [class]="'jv-toast--' + toast.tone" [attr.role]="toast.tone === 'danger' ? 'alert' : 'status'" aria-atomic="true">
1290
+ <div class="jv-toast-copy">
1291
+ @if (toast.title) {
1292
+ <strong>{{ toast.title }}</strong>
1293
+ }
1294
+ <p>{{ toast.message }}</p>
1295
+ </div>
1296
+
1297
+ <button type="button" class="jv-toast-close" (click)="toastService.dismiss(toast.id)" aria-label="Dismiss notification">
1298
+ <jv-icon name="x" [size]="16" />
1299
+ </button>
1300
+ </article>
1301
+ }
1302
+ </div>
1303
+ }
1304
+ `, styles: [".jv-toast-viewport{position:fixed;z-index:1100;display:grid;gap:var(--jv-spacing-sm);width:min(360px,calc(100vw - 2rem))}.is-top-left{top:1rem;left:1rem}.is-top-center{top:1rem;left:50%;transform:translate(-50%)}.is-top-right{top:1rem;right:1rem}.is-bottom-left{bottom:1rem;left:1rem}.is-bottom-center{bottom:1rem;left:50%;transform:translate(-50%)}.is-bottom-right{bottom:1rem;right:1rem}.jv-toast-item{display:grid;grid-template-columns:1fr auto;gap:var(--jv-spacing-sm);padding:var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg)}.jv-toast-copy strong,.jv-toast-copy p{margin:0}.jv-toast-copy{display:grid;gap:.25rem}.jv-toast-copy p{color:var(--jv-color-foreground-muted)}.jv-toast-close{display:inline-grid;place-items:center;width:2rem;height:2rem;border:0;border-radius:999px;background:transparent;color:inherit}.jv-toast--success{border-color:color-mix(in srgb,var(--jv-color-success) 35%,var(--jv-color-border))}.jv-toast--warning{border-color:color-mix(in srgb,var(--jv-color-warning) 35%,var(--jv-color-border))}.jv-toast--danger{border-color:color-mix(in srgb,var(--jv-color-danger) 35%,var(--jv-color-border))}.jv-toast--info,.jv-toast--primary{border-color:color-mix(in srgb,var(--jv-color-primary) 35%,var(--jv-color-border))}\n"] }]
1305
+ }] });
1306
+
1307
+ let radioIdSequence = 0;
1308
+ class JvRadioComponent {
1309
+ name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
1310
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1311
+ radioValue = input.required(...(ngDevMode ? [{ debugName: "radioValue" }] : /* istanbul ignore next */ []));
1312
+ describedBy = input('', ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
1313
+ inputId = input(`jv-radio-${++radioIdSequence}`, ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
1314
+ value = '';
1315
+ disabled = false;
1316
+ onChange = () => undefined;
1317
+ onTouched = () => undefined;
1318
+ writeValue(value) {
1319
+ this.value = value ?? '';
1320
+ }
1321
+ registerOnChange(fn) {
1322
+ this.onChange = fn;
1323
+ }
1324
+ registerOnTouched(fn) {
1325
+ this.onTouched = fn;
1326
+ }
1327
+ setDisabledState(isDisabled) {
1328
+ this.disabled = isDisabled;
1329
+ }
1330
+ handleChange() {
1331
+ const nextValue = this.radioValue();
1332
+ this.value = nextValue;
1333
+ this.onChange(nextValue);
1334
+ }
1335
+ handleBlur() {
1336
+ this.onTouched();
1337
+ }
1338
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvRadioComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1339
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvRadioComponent, isStandalone: true, selector: "jv-radio", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, radioValue: { classPropertyName: "radioValue", publicName: "radioValue", isSignal: true, isRequired: true, transformFunction: null }, describedBy: { classPropertyName: "describedBy", publicName: "describedBy", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
1340
+ {
1341
+ provide: NG_VALUE_ACCESSOR,
1342
+ useExisting: forwardRef(() => JvRadioComponent),
1343
+ multi: true,
1344
+ },
1345
+ ], ngImport: i0, template: `
1346
+ <label class="jv-radio" [class.is-disabled]="disabled">
1347
+ <input
1348
+ type="radio"
1349
+ [id]="inputId()"
1350
+ [name]="name()"
1351
+ [checked]="value === radioValue()"
1352
+ [disabled]="disabled"
1353
+ [attr.aria-describedby]="describedBy() || null"
1354
+ (change)="handleChange()"
1355
+ (blur)="handleBlur()"
1356
+ />
1357
+ <span>{{ label() }}</span>
1358
+ </label>
1359
+ `, isInline: true, styles: [".jv-radio{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);min-height:var(--jv-density-control-height);color:var(--jv-color-foreground)}.jv-radio.is-disabled{opacity:.7}\n"] });
1360
+ }
1361
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvRadioComponent, decorators: [{
1362
+ type: Component,
1363
+ args: [{ selector: 'jv-radio', standalone: true, providers: [
1364
+ {
1365
+ provide: NG_VALUE_ACCESSOR,
1366
+ useExisting: forwardRef(() => JvRadioComponent),
1367
+ multi: true,
1368
+ },
1369
+ ], template: `
1370
+ <label class="jv-radio" [class.is-disabled]="disabled">
1371
+ <input
1372
+ type="radio"
1373
+ [id]="inputId()"
1374
+ [name]="name()"
1375
+ [checked]="value === radioValue()"
1376
+ [disabled]="disabled"
1377
+ [attr.aria-describedby]="describedBy() || null"
1378
+ (change)="handleChange()"
1379
+ (blur)="handleBlur()"
1380
+ />
1381
+ <span>{{ label() }}</span>
1382
+ </label>
1383
+ `, styles: [".jv-radio{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);min-height:var(--jv-density-control-height);color:var(--jv-color-foreground)}.jv-radio.is-disabled{opacity:.7}\n"] }]
1384
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], radioValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "radioValue", required: true }] }], describedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "describedBy", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }] } });
1385
+
1386
+ let selectIdSequence = 0;
1387
+ class JvSelectComponent {
1388
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
1389
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
1390
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
1391
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
1392
+ describedBy = input('', ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
1393
+ inputId = input(`jv-select-${++selectIdSequence}`, ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
1394
+ modelValue = input('', ...(ngDevMode ? [{ debugName: "modelValue" }] : /* istanbul ignore next */ []));
1395
+ selectionChange = output();
1396
+ value = '';
1397
+ disabled = false;
1398
+ onChange = () => undefined;
1399
+ onTouched = () => undefined;
1400
+ currentValue = computed(() => this.modelValue() || this.value, ...(ngDevMode ? [{ debugName: "currentValue" }] : /* istanbul ignore next */ []));
1401
+ writeValue(value) {
1402
+ this.value = value ?? '';
1403
+ }
1404
+ registerOnChange(fn) {
1405
+ this.onChange = fn;
1406
+ }
1407
+ registerOnTouched(fn) {
1408
+ this.onTouched = fn;
1409
+ }
1410
+ setDisabledState(isDisabled) {
1411
+ this.disabled = isDisabled;
1412
+ }
1413
+ handleChange(event) {
1414
+ const nextValue = event.target.value;
1415
+ this.value = nextValue;
1416
+ this.onChange(nextValue);
1417
+ this.selectionChange.emit(nextValue);
1418
+ }
1419
+ handleBlur() {
1420
+ this.onTouched();
1421
+ }
1422
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1423
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvSelectComponent, isStandalone: true, selector: "jv-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, describedBy: { classPropertyName: "describedBy", publicName: "describedBy", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, modelValue: { classPropertyName: "modelValue", publicName: "modelValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, providers: [
1424
+ {
1425
+ provide: NG_VALUE_ACCESSOR,
1426
+ useExisting: forwardRef(() => JvSelectComponent),
1427
+ multi: true,
1428
+ },
1429
+ ], ngImport: i0, template: `
1430
+ <select
1431
+ class="jv-select"
1432
+ [class.is-invalid]="invalid()"
1433
+ [id]="inputId()"
1434
+ [disabled]="disabled"
1435
+ [required]="required()"
1436
+ [attr.aria-invalid]="invalid() ? 'true' : null"
1437
+ [attr.aria-describedby]="describedBy() || null"
1438
+ [value]="currentValue()"
1439
+ (change)="handleChange($event)"
1440
+ (blur)="handleBlur()"
1441
+ >
1442
+ @if (placeholder()) {
1443
+ <option value="" [disabled]="required()">{{ placeholder() }}</option>
1444
+ }
1445
+
1446
+ @for (option of options(); track option.label + ':' + option.value) {
1447
+ <option [value]="option.value" [disabled]="option.disabled ?? false">{{ option.label }}</option>
1448
+ }
1449
+ </select>
1450
+ `, isInline: true, styles: [".jv-select{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-select.is-invalid{border-color:var(--jv-color-danger)}\n"] });
1451
+ }
1452
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSelectComponent, decorators: [{
1453
+ type: Component,
1454
+ args: [{ selector: 'jv-select', standalone: true, providers: [
1455
+ {
1456
+ provide: NG_VALUE_ACCESSOR,
1457
+ useExisting: forwardRef(() => JvSelectComponent),
1458
+ multi: true,
1459
+ },
1460
+ ], template: `
1461
+ <select
1462
+ class="jv-select"
1463
+ [class.is-invalid]="invalid()"
1464
+ [id]="inputId()"
1465
+ [disabled]="disabled"
1466
+ [required]="required()"
1467
+ [attr.aria-invalid]="invalid() ? 'true' : null"
1468
+ [attr.aria-describedby]="describedBy() || null"
1469
+ [value]="currentValue()"
1470
+ (change)="handleChange($event)"
1471
+ (blur)="handleBlur()"
1472
+ >
1473
+ @if (placeholder()) {
1474
+ <option value="" [disabled]="required()">{{ placeholder() }}</option>
1475
+ }
1476
+
1477
+ @for (option of options(); track option.label + ':' + option.value) {
1478
+ <option [value]="option.value" [disabled]="option.disabled ?? false">{{ option.label }}</option>
1479
+ }
1480
+ </select>
1481
+ `, styles: [".jv-select{width:100%;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md);background:var(--jv-color-surface);color:var(--jv-color-foreground)}.jv-select.is-invalid{border-color:var(--jv-color-danger)}\n"] }]
1482
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], describedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "describedBy", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], modelValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelValue", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
1483
+
1484
+ let switchIdSequence = 0;
1485
+ class JvSwitchComponent {
1486
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1487
+ describedBy = input('', ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
1488
+ inputId = input(`jv-switch-${++switchIdSequence}`, ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
1489
+ value = false;
1490
+ disabled = false;
1491
+ onChange = () => undefined;
1492
+ onTouched = () => undefined;
1493
+ writeValue(value) {
1494
+ this.value = value ?? false;
1495
+ }
1496
+ registerOnChange(fn) {
1497
+ this.onChange = fn;
1498
+ }
1499
+ registerOnTouched(fn) {
1500
+ this.onTouched = fn;
1501
+ }
1502
+ setDisabledState(isDisabled) {
1503
+ this.disabled = isDisabled;
1504
+ }
1505
+ handleChange(event) {
1506
+ const nextValue = event.target.checked;
1507
+ this.value = nextValue;
1508
+ this.onChange(nextValue);
1509
+ }
1510
+ handleBlur() {
1511
+ this.onTouched();
1512
+ }
1513
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSwitchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1514
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvSwitchComponent, isStandalone: true, selector: "jv-switch", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, describedBy: { classPropertyName: "describedBy", publicName: "describedBy", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
1515
+ {
1516
+ provide: NG_VALUE_ACCESSOR,
1517
+ useExisting: forwardRef(() => JvSwitchComponent),
1518
+ multi: true,
1519
+ },
1520
+ ], ngImport: i0, template: `
1521
+ <label class="jv-switch" [class.is-disabled]="disabled">
1522
+ <input
1523
+ class="jv-switch-input"
1524
+ type="checkbox"
1525
+ role="switch"
1526
+ [id]="inputId()"
1527
+ [checked]="value"
1528
+ [disabled]="disabled"
1529
+ [attr.aria-describedby]="describedBy() || null"
1530
+ (change)="handleChange($event)"
1531
+ (blur)="handleBlur()"
1532
+ />
1533
+ <span class="jv-switch-track" aria-hidden="true">
1534
+ <span class="jv-switch-thumb"></span>
1535
+ </span>
1536
+ <span>{{ label() }}</span>
1537
+ </label>
1538
+ `, isInline: true, styles: [".jv-switch{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);min-height:var(--jv-density-control-height);color:var(--jv-color-foreground)}.jv-switch.is-disabled{opacity:.7}.jv-switch-input{position:absolute;opacity:0;pointer-events:none}.jv-switch-track{position:relative;display:inline-flex;align-items:center;width:2.75rem;height:1.6rem;padding:.15rem;border-radius:999px;background:var(--jv-color-border);transition:background-color .16s ease}.jv-switch-thumb{width:1.25rem;height:1.25rem;border-radius:999px;background:#fff;box-shadow:var(--jv-shadow-sm);transition:transform .16s ease}.jv-switch-input:checked+.jv-switch-track{background:var(--jv-color-primary)}.jv-switch-input:checked+.jv-switch-track .jv-switch-thumb{transform:translate(1.1rem)}\n"] });
1539
+ }
1540
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSwitchComponent, decorators: [{
1541
+ type: Component,
1542
+ args: [{ selector: 'jv-switch', standalone: true, providers: [
1543
+ {
1544
+ provide: NG_VALUE_ACCESSOR,
1545
+ useExisting: forwardRef(() => JvSwitchComponent),
1546
+ multi: true,
1547
+ },
1548
+ ], template: `
1549
+ <label class="jv-switch" [class.is-disabled]="disabled">
1550
+ <input
1551
+ class="jv-switch-input"
1552
+ type="checkbox"
1553
+ role="switch"
1554
+ [id]="inputId()"
1555
+ [checked]="value"
1556
+ [disabled]="disabled"
1557
+ [attr.aria-describedby]="describedBy() || null"
1558
+ (change)="handleChange($event)"
1559
+ (blur)="handleBlur()"
1560
+ />
1561
+ <span class="jv-switch-track" aria-hidden="true">
1562
+ <span class="jv-switch-thumb"></span>
1563
+ </span>
1564
+ <span>{{ label() }}</span>
1565
+ </label>
1566
+ `, styles: [".jv-switch{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm);min-height:var(--jv-density-control-height);color:var(--jv-color-foreground)}.jv-switch.is-disabled{opacity:.7}.jv-switch-input{position:absolute;opacity:0;pointer-events:none}.jv-switch-track{position:relative;display:inline-flex;align-items:center;width:2.75rem;height:1.6rem;padding:.15rem;border-radius:999px;background:var(--jv-color-border);transition:background-color .16s ease}.jv-switch-thumb{width:1.25rem;height:1.25rem;border-radius:999px;background:#fff;box-shadow:var(--jv-shadow-sm);transition:transform .16s ease}.jv-switch-input:checked+.jv-switch-track{background:var(--jv-color-primary)}.jv-switch-input:checked+.jv-switch-track .jv-switch-thumb{transform:translate(1.1rem)}\n"] }]
1567
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], describedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "describedBy", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }] } });
1568
+
1569
+ class JvDialogService {
1570
+ announcementService = inject(JvAnnouncementService);
1571
+ document = inject(DOCUMENT);
1572
+ activeDialogSignal = signal(null, ...(ngDevMode ? [{ debugName: "activeDialogSignal" }] : /* istanbul ignore next */ []));
1573
+ nextId = 0;
1574
+ resolver = null;
1575
+ restoreTarget = null;
1576
+ activeDialog = this.activeDialogSignal.asReadonly();
1577
+ confirm(options) {
1578
+ const id = ++this.nextId;
1579
+ this.restoreTarget = this.document.activeElement instanceof HTMLElement ? this.document.activeElement : null;
1580
+ this.activeDialogSignal.set({
1581
+ id,
1582
+ title: options.title,
1583
+ message: options.message,
1584
+ confirmLabel: options.confirmLabel ?? 'Confirm',
1585
+ cancelLabel: options.cancelLabel ?? 'Cancel',
1586
+ tone: options.tone ?? 'primary',
1587
+ });
1588
+ this.announcementService.announce(`${options.title}. ${options.message}`, 'assertive');
1589
+ return new Promise((resolve) => {
1590
+ this.resolver = resolve;
1591
+ });
1592
+ }
1593
+ resolve(result) {
1594
+ this.activeDialogSignal.set(null);
1595
+ this.resolver?.(result);
1596
+ this.resolver = null;
1597
+ this.restoreTarget?.focus();
1598
+ this.restoreTarget = null;
1599
+ }
1600
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1601
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDialogService, providedIn: 'root' });
1602
+ }
1603
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDialogService, decorators: [{
1604
+ type: Injectable,
1605
+ args: [{ providedIn: 'root' }]
1606
+ }] });
1607
+
1608
+ class JvDialogComponent {
1609
+ document = inject(DOCUMENT);
1610
+ panelRef = viewChild('panel', ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
1611
+ open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
1612
+ title = input('Dialog', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
1613
+ closed = output();
1614
+ titleId = `jv-dialog-title-${Math.random().toString(36).slice(2, 8)}`;
1615
+ descriptionId = `jv-dialog-description-${Math.random().toString(36).slice(2, 8)}`;
1616
+ constructor() {
1617
+ effect(() => {
1618
+ if (this.open()) {
1619
+ queueMicrotask(() => this.focusInitialElement());
1620
+ }
1621
+ });
1622
+ }
1623
+ handleDocumentKeydown(event) {
1624
+ if (!this.open()) {
1625
+ return;
1626
+ }
1627
+ if (event.key === 'Escape') {
1628
+ event.preventDefault();
1629
+ this.closed.emit();
1630
+ return;
1631
+ }
1632
+ if (event.key === 'Tab') {
1633
+ this.trapFocus(event);
1634
+ }
1635
+ }
1636
+ focusInitialElement() {
1637
+ const panel = this.panelRef()?.nativeElement;
1638
+ if (!panel) {
1639
+ return;
1640
+ }
1641
+ const firstFocusable = this.getFocusableElements()[0];
1642
+ (firstFocusable ?? panel).focus();
1643
+ }
1644
+ backdropClose() {
1645
+ this.closed.emit();
1646
+ }
1647
+ trapFocus(event) {
1648
+ const focusable = this.getFocusableElements();
1649
+ const panel = this.panelRef()?.nativeElement;
1650
+ if (!panel || focusable.length === 0) {
1651
+ panel?.focus();
1652
+ event.preventDefault();
1653
+ return;
1654
+ }
1655
+ const first = focusable[0];
1656
+ const last = focusable[focusable.length - 1];
1657
+ const active = this.document.activeElement;
1658
+ if (event.shiftKey && active === first) {
1659
+ last.focus();
1660
+ event.preventDefault();
1661
+ }
1662
+ else if (!event.shiftKey && active === last) {
1663
+ first.focus();
1664
+ event.preventDefault();
1665
+ }
1666
+ }
1667
+ getFocusableElements() {
1668
+ const panel = this.panelRef()?.nativeElement;
1669
+ if (!panel) {
1670
+ return [];
1671
+ }
1672
+ return Array.from(panel.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'));
1673
+ }
1674
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1675
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvDialogComponent, isStandalone: true, selector: "jv-dialog", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { listeners: { "document:keydown": "handleDocumentKeydown($event)" } }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: `
1676
+ @if (open()) {
1677
+ <div class="jv-dialog-backdrop" (click)="backdropClose()">
1678
+ <div class="jv-dialog-panel" role="dialog" aria-modal="true" tabindex="-1" [attr.aria-labelledby]="titleId" [attr.aria-describedby]="descriptionId" (click)="$event.stopPropagation()" #panel>
1679
+ <header class="jv-dialog-header">
1680
+ <h2 [id]="titleId">{{ title() }}</h2>
1681
+ </header>
1682
+
1683
+ <div class="jv-dialog-body" [id]="descriptionId">
1684
+ <ng-content></ng-content>
1685
+ </div>
1686
+
1687
+ <footer class="jv-dialog-footer">
1688
+ <ng-content select="[dialog-actions]"></ng-content>
1689
+ </footer>
1690
+ </div>
1691
+ </div>
1692
+ }
1693
+ `, isInline: true, styles: [".jv-dialog-backdrop{position:fixed;inset:0;z-index:1200;display:grid;place-items:center;padding:1rem;background:#0f172a80}.jv-dialog-panel{width:min(32rem,100%);max-height:calc(100vh - 2rem);overflow:auto;padding:var(--jv-spacing-lg);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg)}.jv-dialog-header,.jv-dialog-body,.jv-dialog-footer{display:grid;gap:var(--jv-spacing-sm)}.jv-dialog-footer{margin-top:var(--jv-spacing-lg)}h2,p{margin:0}\n"] });
1694
+ }
1695
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDialogComponent, decorators: [{
1696
+ type: Component,
1697
+ args: [{ selector: 'jv-dialog', standalone: true, template: `
1698
+ @if (open()) {
1699
+ <div class="jv-dialog-backdrop" (click)="backdropClose()">
1700
+ <div class="jv-dialog-panel" role="dialog" aria-modal="true" tabindex="-1" [attr.aria-labelledby]="titleId" [attr.aria-describedby]="descriptionId" (click)="$event.stopPropagation()" #panel>
1701
+ <header class="jv-dialog-header">
1702
+ <h2 [id]="titleId">{{ title() }}</h2>
1703
+ </header>
1704
+
1705
+ <div class="jv-dialog-body" [id]="descriptionId">
1706
+ <ng-content></ng-content>
1707
+ </div>
1708
+
1709
+ <footer class="jv-dialog-footer">
1710
+ <ng-content select="[dialog-actions]"></ng-content>
1711
+ </footer>
1712
+ </div>
1713
+ </div>
1714
+ }
1715
+ `, styles: [".jv-dialog-backdrop{position:fixed;inset:0;z-index:1200;display:grid;place-items:center;padding:1rem;background:#0f172a80}.jv-dialog-panel{width:min(32rem,100%);max-height:calc(100vh - 2rem);overflow:auto;padding:var(--jv-spacing-lg);border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-lg);background:var(--jv-color-surface);box-shadow:var(--jv-shadow-lg)}.jv-dialog-header,.jv-dialog-body,.jv-dialog-footer{display:grid;gap:var(--jv-spacing-sm)}.jv-dialog-footer{margin-top:var(--jv-spacing-lg)}h2,p{margin:0}\n"] }]
1716
+ }], ctorParameters: () => [], propDecorators: { panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], handleDocumentKeydown: [{
1717
+ type: HostListener,
1718
+ args: ['document:keydown', ['$event']]
1719
+ }] } });
1720
+
1721
+ class JvConfirmDialogComponent {
1722
+ dialogService = inject(JvDialogService);
1723
+ activeDialog = this.dialogService.activeDialog;
1724
+ isOpen = computed(() => this.activeDialog() !== null, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
1725
+ dialogTitle = computed(() => this.activeDialog()?.title ?? 'Confirm', ...(ngDevMode ? [{ debugName: "dialogTitle" }] : /* istanbul ignore next */ []));
1726
+ dialogMessage = computed(() => this.activeDialog()?.message ?? '', ...(ngDevMode ? [{ debugName: "dialogMessage" }] : /* istanbul ignore next */ []));
1727
+ cancelLabel = computed(() => this.activeDialog()?.cancelLabel ?? 'Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
1728
+ confirmLabel = computed(() => this.activeDialog()?.confirmLabel ?? 'Confirm', ...(ngDevMode ? [{ debugName: "confirmLabel" }] : /* istanbul ignore next */ []));
1729
+ confirmVariant = computed(() => {
1730
+ const tone = this.activeDialog()?.tone ?? 'primary';
1731
+ return tone === 'info' ? 'primary' : tone;
1732
+ }, ...(ngDevMode ? [{ debugName: "confirmVariant" }] : /* istanbul ignore next */ []));
1733
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvConfirmDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1734
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: JvConfirmDialogComponent, isStandalone: true, selector: "jv-confirm-dialog", ngImport: i0, template: `
1735
+ <jv-dialog
1736
+ [open]="isOpen()"
1737
+ [title]="dialogTitle()"
1738
+ (closed)="dialogService.resolve(false)"
1739
+ >
1740
+ <p>{{ dialogMessage() }}</p>
1741
+
1742
+ <div dialog-actions class="actions">
1743
+ <jv-button variant="ghost" (click)="dialogService.resolve(false)">{{ cancelLabel() }}</jv-button>
1744
+ <jv-button [variant]="confirmVariant()" (click)="dialogService.resolve(true)">{{ confirmLabel() }}</jv-button>
1745
+ </div>
1746
+ </jv-dialog>
1747
+ `, isInline: true, styles: [".actions{display:flex;justify-content:flex-end;gap:var(--jv-spacing-sm);flex-wrap:wrap}p{margin:0;color:var(--jv-color-foreground-muted)}\n"], dependencies: [{ kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvDialogComponent, selector: "jv-dialog", inputs: ["open", "title"], outputs: ["closed"] }] });
1748
+ }
1749
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvConfirmDialogComponent, decorators: [{
1750
+ type: Component,
1751
+ args: [{ selector: 'jv-confirm-dialog', standalone: true, imports: [JvButtonComponent, JvDialogComponent], template: `
1752
+ <jv-dialog
1753
+ [open]="isOpen()"
1754
+ [title]="dialogTitle()"
1755
+ (closed)="dialogService.resolve(false)"
1756
+ >
1757
+ <p>{{ dialogMessage() }}</p>
1758
+
1759
+ <div dialog-actions class="actions">
1760
+ <jv-button variant="ghost" (click)="dialogService.resolve(false)">{{ cancelLabel() }}</jv-button>
1761
+ <jv-button [variant]="confirmVariant()" (click)="dialogService.resolve(true)">{{ confirmLabel() }}</jv-button>
1762
+ </div>
1763
+ </jv-dialog>
1764
+ `, styles: [".actions{display:flex;justify-content:flex-end;gap:var(--jv-spacing-sm);flex-wrap:wrap}p{margin:0;color:var(--jv-color-foreground-muted)}\n"] }]
1765
+ }] });
1766
+
1767
+ class JvBreadcrumbComponent {
1768
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
1769
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvBreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1770
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvBreadcrumbComponent, isStandalone: true, selector: "jv-breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1771
+ @if (items().length > 0) {
1772
+ <nav aria-label="Breadcrumb" class="jv-breadcrumb">
1773
+ <ol>
1774
+ @for (item of items(); track item.label; let isLast = $last) {
1775
+ <li>
1776
+ @if (item.href && !item.active && !isLast) {
1777
+ <a [href]="item.href">{{ item.label }}</a>
1778
+ } @else {
1779
+ <span [attr.aria-current]="item.active || isLast ? 'page' : null">{{ item.label }}</span>
1780
+ }
1781
+
1782
+ @if (!isLast) {
1783
+ <span aria-hidden="true" class="separator">/</span>
1784
+ }
1785
+ </li>
1786
+ }
1787
+ </ol>
1788
+ </nav>
1789
+ }
1790
+ `, isInline: true, styles: [".jv-breadcrumb ol{display:flex;flex-wrap:wrap;gap:var(--jv-spacing-xs);margin:0;padding:0;list-style:none;color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-breadcrumb li{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-breadcrumb a{color:inherit;text-decoration:none}.jv-breadcrumb a:hover{color:var(--jv-color-foreground);text-decoration:underline}.separator{opacity:.7}\n"] });
1791
+ }
1792
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvBreadcrumbComponent, decorators: [{
1793
+ type: Component,
1794
+ args: [{ selector: 'jv-breadcrumb', standalone: true, template: `
1795
+ @if (items().length > 0) {
1796
+ <nav aria-label="Breadcrumb" class="jv-breadcrumb">
1797
+ <ol>
1798
+ @for (item of items(); track item.label; let isLast = $last) {
1799
+ <li>
1800
+ @if (item.href && !item.active && !isLast) {
1801
+ <a [href]="item.href">{{ item.label }}</a>
1802
+ } @else {
1803
+ <span [attr.aria-current]="item.active || isLast ? 'page' : null">{{ item.label }}</span>
1804
+ }
1805
+
1806
+ @if (!isLast) {
1807
+ <span aria-hidden="true" class="separator">/</span>
1808
+ }
1809
+ </li>
1810
+ }
1811
+ </ol>
1812
+ </nav>
1813
+ }
1814
+ `, styles: [".jv-breadcrumb ol{display:flex;flex-wrap:wrap;gap:var(--jv-spacing-xs);margin:0;padding:0;list-style:none;color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-breadcrumb li{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-breadcrumb a{color:inherit;text-decoration:none}.jv-breadcrumb a:hover{color:var(--jv-color-foreground);text-decoration:underline}.separator{opacity:.7}\n"] }]
1815
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }] } });
1816
+
1817
+ class JvSidebarComponent {
1818
+ router = inject(Router);
1819
+ expandedSections = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedSections" }] : /* istanbul ignore next */ []));
1820
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
1821
+ subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
1822
+ brandIcon = input('monitor', ...(ngDevMode ? [{ debugName: "brandIcon" }] : /* istanbul ignore next */ []));
1823
+ items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
1824
+ collapsed = input(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
1825
+ mobileOpen = input(false, ...(ngDevMode ? [{ debugName: "mobileOpen" }] : /* istanbul ignore next */ []));
1826
+ ariaLabel = input('Sidebar navigation', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
1827
+ collapseToggle = output();
1828
+ activeChildParentIds = computed(() => {
1829
+ const url = this.router.url;
1830
+ const ids = new Set();
1831
+ for (const item of this.items()) {
1832
+ for (const child of item.children ?? []) {
1833
+ if (child.href && url.startsWith(child.href)) {
1834
+ ids.add(item.id);
1835
+ }
1836
+ }
1837
+ }
1838
+ return ids;
1839
+ }, ...(ngDevMode ? [{ debugName: "activeChildParentIds" }] : /* istanbul ignore next */ []));
1840
+ normalizedItems = computed(() => this.items().map((item) => ({
1841
+ ...item,
1842
+ children: item.children ?? [],
1843
+ })), ...(ngDevMode ? [{ debugName: "normalizedItems" }] : /* istanbul ignore next */ []));
1844
+ isExpanded(id) {
1845
+ return this.expandedSections().has(id) || this.activeChildParentIds().has(id);
1846
+ }
1847
+ toggleSection(id) {
1848
+ this.expandedSections.update((current) => {
1849
+ const next = new Set(current);
1850
+ if (next.has(id)) {
1851
+ next.delete(id);
1852
+ }
1853
+ else {
1854
+ next.add(id);
1855
+ }
1856
+ return next;
1857
+ });
1858
+ }
1859
+ isHashLink(href) {
1860
+ return !!href && href.startsWith('#');
1861
+ }
1862
+ navigateTo(href) {
1863
+ if (!href) {
1864
+ return;
1865
+ }
1866
+ const target = document.querySelector(href);
1867
+ target?.scrollIntoView({ block: 'start', behavior: 'smooth' });
1868
+ }
1869
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSidebarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1870
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvSidebarComponent, isStandalone: true, selector: "jv-sidebar", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, brandIcon: { classPropertyName: "brandIcon", publicName: "brandIcon", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, mobileOpen: { classPropertyName: "mobileOpen", publicName: "mobileOpen", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapseToggle: "collapseToggle" }, ngImport: i0, template: `
1871
+ <aside class="jv-sidebar" [class.is-collapsed]="collapsed()" [class.is-mobile-open]="mobileOpen()">
1872
+ <div class="jv-sidebar-brand">
1873
+ <div class="brand-mark">
1874
+ <jv-icon [name]="brandIcon()" [size]="18" />
1875
+ </div>
1876
+
1877
+ @if (!collapsed()) {
1878
+ <div class="brand-copy">
1879
+ <strong>{{ title() }}</strong>
1880
+ @if (subtitle()) {
1881
+ <span>{{ subtitle() }}</span>
1882
+ }
1883
+ </div>
1884
+ }
1885
+ </div>
1886
+
1887
+ <nav class="jv-sidebar-nav" [attr.aria-label]="ariaLabel()">
1888
+ <ul class="jv-sidebar-list">
1889
+ @for (item of normalizedItems(); track item.id) {
1890
+ <li class="jv-sidebar-item">
1891
+ @if (item.children.length > 0) {
1892
+ <button
1893
+ type="button"
1894
+ class="jv-sidebar-link"
1895
+ [class.is-active]="item.active"
1896
+ [attr.aria-label]="item.label"
1897
+ [attr.aria-expanded]="isExpanded(item.id) ? 'true' : 'false'"
1898
+ [attr.aria-controls]="'jv-sidebar-section-' + item.id"
1899
+ [attr.title]="item.label"
1900
+ (click)="toggleSection(item.id)"
1901
+ >
1902
+ <span class="jv-sidebar-link-main">
1903
+ @if (item.icon) {
1904
+ <jv-icon [name]="item.icon" [size]="18" />
1905
+ }
1906
+
1907
+ @if (!collapsed()) {
1908
+ <span>{{ item.label }}</span>
1909
+ }
1910
+ </span>
1911
+
1912
+ @if (!collapsed()) {
1913
+ <jv-icon [name]="isExpanded(item.id) ? 'chevron-up' : 'chevron-down'" [size]="16" />
1914
+ }
1915
+ </button>
1916
+ } @else {
1917
+ @if (isHashLink(item.href)) {
1918
+ <button
1919
+ type="button"
1920
+ class="jv-sidebar-link"
1921
+ [class.is-active]="item.active"
1922
+ [attr.aria-label]="item.label"
1923
+ [attr.aria-current]="item.active ? 'page' : null"
1924
+ [attr.title]="item.label"
1925
+ (click)="navigateTo(item.href)"
1926
+ >
1927
+ <span class="jv-sidebar-link-main">
1928
+ @if (item.icon) {
1929
+ <jv-icon [name]="item.icon" [size]="18" />
1930
+ }
1931
+
1932
+ @if (!collapsed()) {
1933
+ <span>{{ item.label }}</span>
1934
+ }
1935
+ </span>
1936
+ </button>
1937
+ } @else {
1938
+ <a
1939
+ class="jv-sidebar-link"
1940
+ [class.is-active]="item.active"
1941
+ [attr.aria-label]="item.label"
1942
+ [attr.aria-current]="item.active ? 'page' : null"
1943
+ [routerLink]="item.href"
1944
+ [attr.title]="item.label"
1945
+ >
1946
+ <span class="jv-sidebar-link-main">
1947
+ @if (item.icon) {
1948
+ <jv-icon [name]="item.icon" [size]="18" />
1949
+ }
1950
+
1951
+ @if (!collapsed()) {
1952
+ <span>{{ item.label }}</span>
1953
+ }
1954
+ </span>
1955
+ </a>
1956
+ }
1957
+ }
1958
+
1959
+ @if (!collapsed() && item.children.length > 0 && isExpanded(item.id)) {
1960
+ <div class="jv-sidebar-children" [id]="'jv-sidebar-section-' + item.id">
1961
+ @for (child of item.children; track child.id) {
1962
+ @if (isHashLink(child.href)) {
1963
+ <button type="button" class="jv-sidebar-child-link" [class.is-active]="child.active" [attr.aria-label]="child.label" [attr.aria-current]="child.active ? 'page' : null" [attr.title]="child.label" (click)="navigateTo(child.href)">
1964
+ {{ child.label }}
1965
+ </button>
1966
+ } @else {
1967
+ <a class="jv-sidebar-child-link" [class.is-active]="child.active" [attr.aria-label]="child.label" [attr.aria-current]="child.active ? 'page' : null" [routerLink]="child.href" [attr.title]="child.label">
1968
+ {{ child.label }}
1969
+ </a>
1970
+ }
1971
+ }
1972
+ </div>
1973
+ }
1974
+ </li>
1975
+ }
1976
+ </ul>
1977
+ </nav>
1978
+
1979
+ <div class="jv-sidebar-footer">
1980
+ <button type="button" class="jv-sidebar-toggle" (click)="collapseToggle.emit()">
1981
+ <jv-icon [name]="collapsed() ? 'menu' : 'panel-left-close'" [size]="18" />
1982
+ @if (!collapsed()) {
1983
+ <span>Collapse</span>
1984
+ }
1985
+ </button>
1986
+ </div>
1987
+ </aside>
1988
+ `, isInline: true, styles: [".jv-sidebar{display:grid;grid-template-rows:auto 1fr auto;gap:var(--jv-spacing-lg);width:280px;min-width:280px;height:100vh;padding:var(--jv-spacing-lg);overflow-y:auto;border-right:1px solid var(--jv-color-border);background:var(--jv-color-surface);scrollbar-width:none;-ms-overflow-style:none}.jv-sidebar::-webkit-scrollbar{width:0;height:0;display:none}.jv-sidebar.is-collapsed{width:96px;min-width:96px;padding-inline:var(--jv-spacing-md)}.jv-sidebar-brand,.jv-sidebar-link,.jv-sidebar-toggle{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.brand-mark{display:inline-grid;place-items:center;width:2.5rem;height:2.5rem;border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary)}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-sidebar-item{display:grid;gap:var(--jv-spacing-xs);list-style:none}.jv-sidebar-list{display:grid;gap:var(--jv-spacing-xs);margin:0;padding:0}.jv-sidebar-nav{display:grid;gap:var(--jv-spacing-xs);align-content:start}.jv-sidebar-link,.jv-sidebar-child-link,.jv-sidebar-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground-muted);text-decoration:none;cursor:pointer}.jv-sidebar-link-main{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-sidebar-link:hover,.jv-sidebar-child-link:hover,.jv-sidebar-toggle:hover{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-sidebar-link.is-active,.jv-sidebar-child-link.is-active{border-color:color-mix(in srgb,var(--jv-color-primary) 25%,var(--jv-color-border));background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-sidebar-children{display:grid;gap:var(--jv-spacing-xs);margin-left:var(--jv-spacing-xl);padding-left:var(--jv-spacing-sm);border-left:1px solid var(--jv-color-border)}.jv-sidebar-child-link{display:flex;align-items:center;min-height:2.25rem;font-size:.9rem}.jv-sidebar-toggle{width:100%;background:var(--jv-color-surface-muted);cursor:pointer}@media(max-width:900px){.jv-sidebar{width:100%;min-width:0;height:auto;max-height:100vh;border-right:0;border-bottom:1px solid var(--jv-color-border)}.jv-sidebar:not(.is-mobile-open){display:none}.jv-sidebar.is-collapsed{width:100%;min-width:0}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
1989
+ }
1990
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvSidebarComponent, decorators: [{
1991
+ type: Component,
1992
+ args: [{ selector: 'jv-sidebar', standalone: true, imports: [JvIconComponent, RouterLink], template: `
1993
+ <aside class="jv-sidebar" [class.is-collapsed]="collapsed()" [class.is-mobile-open]="mobileOpen()">
1994
+ <div class="jv-sidebar-brand">
1995
+ <div class="brand-mark">
1996
+ <jv-icon [name]="brandIcon()" [size]="18" />
1997
+ </div>
1998
+
1999
+ @if (!collapsed()) {
2000
+ <div class="brand-copy">
2001
+ <strong>{{ title() }}</strong>
2002
+ @if (subtitle()) {
2003
+ <span>{{ subtitle() }}</span>
2004
+ }
2005
+ </div>
2006
+ }
2007
+ </div>
2008
+
2009
+ <nav class="jv-sidebar-nav" [attr.aria-label]="ariaLabel()">
2010
+ <ul class="jv-sidebar-list">
2011
+ @for (item of normalizedItems(); track item.id) {
2012
+ <li class="jv-sidebar-item">
2013
+ @if (item.children.length > 0) {
2014
+ <button
2015
+ type="button"
2016
+ class="jv-sidebar-link"
2017
+ [class.is-active]="item.active"
2018
+ [attr.aria-label]="item.label"
2019
+ [attr.aria-expanded]="isExpanded(item.id) ? 'true' : 'false'"
2020
+ [attr.aria-controls]="'jv-sidebar-section-' + item.id"
2021
+ [attr.title]="item.label"
2022
+ (click)="toggleSection(item.id)"
2023
+ >
2024
+ <span class="jv-sidebar-link-main">
2025
+ @if (item.icon) {
2026
+ <jv-icon [name]="item.icon" [size]="18" />
2027
+ }
2028
+
2029
+ @if (!collapsed()) {
2030
+ <span>{{ item.label }}</span>
2031
+ }
2032
+ </span>
2033
+
2034
+ @if (!collapsed()) {
2035
+ <jv-icon [name]="isExpanded(item.id) ? 'chevron-up' : 'chevron-down'" [size]="16" />
2036
+ }
2037
+ </button>
2038
+ } @else {
2039
+ @if (isHashLink(item.href)) {
2040
+ <button
2041
+ type="button"
2042
+ class="jv-sidebar-link"
2043
+ [class.is-active]="item.active"
2044
+ [attr.aria-label]="item.label"
2045
+ [attr.aria-current]="item.active ? 'page' : null"
2046
+ [attr.title]="item.label"
2047
+ (click)="navigateTo(item.href)"
2048
+ >
2049
+ <span class="jv-sidebar-link-main">
2050
+ @if (item.icon) {
2051
+ <jv-icon [name]="item.icon" [size]="18" />
2052
+ }
2053
+
2054
+ @if (!collapsed()) {
2055
+ <span>{{ item.label }}</span>
2056
+ }
2057
+ </span>
2058
+ </button>
2059
+ } @else {
2060
+ <a
2061
+ class="jv-sidebar-link"
2062
+ [class.is-active]="item.active"
2063
+ [attr.aria-label]="item.label"
2064
+ [attr.aria-current]="item.active ? 'page' : null"
2065
+ [routerLink]="item.href"
2066
+ [attr.title]="item.label"
2067
+ >
2068
+ <span class="jv-sidebar-link-main">
2069
+ @if (item.icon) {
2070
+ <jv-icon [name]="item.icon" [size]="18" />
2071
+ }
2072
+
2073
+ @if (!collapsed()) {
2074
+ <span>{{ item.label }}</span>
2075
+ }
2076
+ </span>
2077
+ </a>
2078
+ }
2079
+ }
2080
+
2081
+ @if (!collapsed() && item.children.length > 0 && isExpanded(item.id)) {
2082
+ <div class="jv-sidebar-children" [id]="'jv-sidebar-section-' + item.id">
2083
+ @for (child of item.children; track child.id) {
2084
+ @if (isHashLink(child.href)) {
2085
+ <button type="button" class="jv-sidebar-child-link" [class.is-active]="child.active" [attr.aria-label]="child.label" [attr.aria-current]="child.active ? 'page' : null" [attr.title]="child.label" (click)="navigateTo(child.href)">
2086
+ {{ child.label }}
2087
+ </button>
2088
+ } @else {
2089
+ <a class="jv-sidebar-child-link" [class.is-active]="child.active" [attr.aria-label]="child.label" [attr.aria-current]="child.active ? 'page' : null" [routerLink]="child.href" [attr.title]="child.label">
2090
+ {{ child.label }}
2091
+ </a>
2092
+ }
2093
+ }
2094
+ </div>
2095
+ }
2096
+ </li>
2097
+ }
2098
+ </ul>
2099
+ </nav>
2100
+
2101
+ <div class="jv-sidebar-footer">
2102
+ <button type="button" class="jv-sidebar-toggle" (click)="collapseToggle.emit()">
2103
+ <jv-icon [name]="collapsed() ? 'menu' : 'panel-left-close'" [size]="18" />
2104
+ @if (!collapsed()) {
2105
+ <span>Collapse</span>
2106
+ }
2107
+ </button>
2108
+ </div>
2109
+ </aside>
2110
+ `, styles: [".jv-sidebar{display:grid;grid-template-rows:auto 1fr auto;gap:var(--jv-spacing-lg);width:280px;min-width:280px;height:100vh;padding:var(--jv-spacing-lg);overflow-y:auto;border-right:1px solid var(--jv-color-border);background:var(--jv-color-surface);scrollbar-width:none;-ms-overflow-style:none}.jv-sidebar::-webkit-scrollbar{width:0;height:0;display:none}.jv-sidebar.is-collapsed{width:96px;min-width:96px;padding-inline:var(--jv-spacing-md)}.jv-sidebar-brand,.jv-sidebar-link,.jv-sidebar-toggle{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.brand-mark{display:inline-grid;place-items:center;width:2.5rem;height:2.5rem;border-radius:var(--jv-radius-md);background:color-mix(in srgb,var(--jv-color-primary) 16%,var(--jv-color-surface));color:var(--jv-color-primary)}.brand-copy{display:grid;gap:.1rem}.brand-copy span{color:var(--jv-color-foreground-muted);font-size:.875rem}.jv-sidebar-item{display:grid;gap:var(--jv-spacing-xs);list-style:none}.jv-sidebar-list{display:grid;gap:var(--jv-spacing-xs);margin:0;padding:0}.jv-sidebar-nav{display:grid;gap:var(--jv-spacing-xs);align-content:start}.jv-sidebar-link,.jv-sidebar-child-link,.jv-sidebar-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border:1px solid transparent;border-radius:var(--jv-radius-md);background:transparent;color:var(--jv-color-foreground-muted);text-decoration:none;cursor:pointer}.jv-sidebar-link-main{display:inline-flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-sidebar-link:hover,.jv-sidebar-child-link:hover,.jv-sidebar-toggle:hover{background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.jv-sidebar-link.is-active,.jv-sidebar-child-link.is-active{border-color:color-mix(in srgb,var(--jv-color-primary) 25%,var(--jv-color-border));background:color-mix(in srgb,var(--jv-color-primary) 12%,var(--jv-color-surface));color:var(--jv-color-foreground)}.jv-sidebar-children{display:grid;gap:var(--jv-spacing-xs);margin-left:var(--jv-spacing-xl);padding-left:var(--jv-spacing-sm);border-left:1px solid var(--jv-color-border)}.jv-sidebar-child-link{display:flex;align-items:center;min-height:2.25rem;font-size:.9rem}.jv-sidebar-toggle{width:100%;background:var(--jv-color-surface-muted);cursor:pointer}@media(max-width:900px){.jv-sidebar{width:100%;min-width:0;height:auto;max-height:100vh;border-right:0;border-bottom:1px solid var(--jv-color-border)}.jv-sidebar:not(.is-mobile-open){display:none}.jv-sidebar.is-collapsed{width:100%;min-width:0}}\n"] }]
2111
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], brandIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandIcon", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], mobileOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileOpen", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], collapseToggle: [{ type: i0.Output, args: ["collapseToggle"] }] } });
2112
+
2113
+ class JvTopbarComponent {
2114
+ title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
2115
+ subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
2116
+ currentTheme = input('light', ...(ngDevMode ? [{ debugName: "currentTheme" }] : /* istanbul ignore next */ []));
2117
+ themeOptions = input([], ...(ngDevMode ? [{ debugName: "themeOptions" }] : /* istanbul ignore next */ []));
2118
+ actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
2119
+ showThemeSelector = input(true, ...(ngDevMode ? [{ debugName: "showThemeSelector" }] : /* istanbul ignore next */ []));
2120
+ menuClick = output();
2121
+ themeChange = output();
2122
+ actionClick = output();
2123
+ visibleActions = () => this.actions();
2124
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTopbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2125
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvTopbarComponent, isStandalone: true, selector: "jv-topbar", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, currentTheme: { classPropertyName: "currentTheme", publicName: "currentTheme", isSignal: true, isRequired: false, transformFunction: null }, themeOptions: { classPropertyName: "themeOptions", publicName: "themeOptions", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, showThemeSelector: { classPropertyName: "showThemeSelector", publicName: "showThemeSelector", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuClick: "menuClick", themeChange: "themeChange", actionClick: "actionClick" }, ngImport: i0, template: `
2126
+ <header class="jv-topbar">
2127
+ <div class="jv-topbar-start">
2128
+ <button type="button" class="icon-button" (click)="menuClick.emit()" aria-label="Toggle navigation menu">
2129
+ <jv-icon name="menu" [size]="18" />
2130
+ </button>
2131
+
2132
+ @if (title()) {
2133
+ <div class="title-block">
2134
+ <strong>{{ title() }}</strong>
2135
+ @if (subtitle()) {
2136
+ <span>{{ subtitle() }}</span>
2137
+ }
2138
+ </div>
2139
+ }
2140
+ </div>
2141
+
2142
+ <div class="jv-topbar-end">
2143
+ <ng-content select="[topbar-actions]"></ng-content>
2144
+
2145
+ @if (showThemeSelector()) {
2146
+ <div class="theme-group" aria-label="Theme selector">
2147
+ @for (option of themeOptions(); track option.value) {
2148
+ <button
2149
+ type="button"
2150
+ class="theme-pill"
2151
+ [class.is-active]="currentTheme() === option.value"
2152
+ (click)="themeChange.emit(option.value)"
2153
+ >
2154
+ @if (option.icon) {
2155
+ <jv-icon [name]="option.icon" [size]="16" />
2156
+ }
2157
+ <span>{{ option.label }}</span>
2158
+ </button>
2159
+ }
2160
+ </div>
2161
+ }
2162
+
2163
+ @for (action of visibleActions(); track action.id) {
2164
+ <button type="button" class="icon-button" [attr.aria-label]="action.label" (click)="actionClick.emit(action.id)">
2165
+ <jv-icon [name]="action.icon" [size]="18" />
2166
+ </button>
2167
+ }
2168
+ </div>
2169
+ </header>
2170
+ `, isInline: true, styles: [".jv-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md) var(--jv-spacing-lg);border-bottom:1px solid var(--jv-color-border);background:var(--jv-color-surface)}.jv-topbar-start,.jv-topbar-end,.theme-group{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-topbar-end{flex-wrap:wrap;justify-content:flex-end}.title-block{display:grid;gap:.1rem}.title-block span{color:var(--jv-color-foreground-muted);font-size:.875rem}.icon-button,.theme-pill{border:1px solid var(--jv-color-border);background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:999px}.theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border-radius:999px}.theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}@media(max-width:900px){.jv-topbar{align-items:flex-start;flex-direction:column}.jv-topbar-end{width:100%;justify-content:flex-start}.theme-group{flex-wrap:wrap}}\n"], dependencies: [{ kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }] });
2171
+ }
2172
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvTopbarComponent, decorators: [{
2173
+ type: Component,
2174
+ args: [{ selector: 'jv-topbar', standalone: true, imports: [JvIconComponent], template: `
2175
+ <header class="jv-topbar">
2176
+ <div class="jv-topbar-start">
2177
+ <button type="button" class="icon-button" (click)="menuClick.emit()" aria-label="Toggle navigation menu">
2178
+ <jv-icon name="menu" [size]="18" />
2179
+ </button>
2180
+
2181
+ @if (title()) {
2182
+ <div class="title-block">
2183
+ <strong>{{ title() }}</strong>
2184
+ @if (subtitle()) {
2185
+ <span>{{ subtitle() }}</span>
2186
+ }
2187
+ </div>
2188
+ }
2189
+ </div>
2190
+
2191
+ <div class="jv-topbar-end">
2192
+ <ng-content select="[topbar-actions]"></ng-content>
2193
+
2194
+ @if (showThemeSelector()) {
2195
+ <div class="theme-group" aria-label="Theme selector">
2196
+ @for (option of themeOptions(); track option.value) {
2197
+ <button
2198
+ type="button"
2199
+ class="theme-pill"
2200
+ [class.is-active]="currentTheme() === option.value"
2201
+ (click)="themeChange.emit(option.value)"
2202
+ >
2203
+ @if (option.icon) {
2204
+ <jv-icon [name]="option.icon" [size]="16" />
2205
+ }
2206
+ <span>{{ option.label }}</span>
2207
+ </button>
2208
+ }
2209
+ </div>
2210
+ }
2211
+
2212
+ @for (action of visibleActions(); track action.id) {
2213
+ <button type="button" class="icon-button" [attr.aria-label]="action.label" (click)="actionClick.emit(action.id)">
2214
+ <jv-icon [name]="action.icon" [size]="18" />
2215
+ </button>
2216
+ }
2217
+ </div>
2218
+ </header>
2219
+ `, styles: [".jv-topbar{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);padding:var(--jv-spacing-md) var(--jv-spacing-lg);border-bottom:1px solid var(--jv-color-border);background:var(--jv-color-surface)}.jv-topbar-start,.jv-topbar-end,.theme-group{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-topbar-end{flex-wrap:wrap;justify-content:flex-end}.title-block{display:grid;gap:.1rem}.title-block span{color:var(--jv-color-foreground-muted);font-size:.875rem}.icon-button,.theme-pill{border:1px solid var(--jv-color-border);background:var(--jv-color-surface-muted);color:var(--jv-color-foreground)}.icon-button{display:inline-grid;place-items:center;width:var(--jv-density-control-height);height:var(--jv-density-control-height);border-radius:999px}.theme-pill{display:inline-flex;align-items:center;gap:var(--jv-spacing-xs);min-height:var(--jv-density-control-height);padding:0 var(--jv-spacing-md);border-radius:999px}.theme-pill.is-active{border-color:var(--jv-color-primary);background:color-mix(in srgb,var(--jv-color-primary) 14%,var(--jv-color-surface))}@media(max-width:900px){.jv-topbar{align-items:flex-start;flex-direction:column}.jv-topbar-end{width:100%;justify-content:flex-start}.theme-group{flex-wrap:wrap}}\n"] }]
2220
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], currentTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTheme", required: false }] }], themeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeOptions", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], showThemeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThemeSelector", required: false }] }], menuClick: [{ type: i0.Output, args: ["menuClick"] }], themeChange: [{ type: i0.Output, args: ["themeChange"] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
2221
+
2222
+ class JvDashboardShellComponent {
2223
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
2224
+ sidebarSubtitle = input('', ...(ngDevMode ? [{ debugName: "sidebarSubtitle" }] : /* istanbul ignore next */ []));
2225
+ brandIcon = input('monitor', ...(ngDevMode ? [{ debugName: "brandIcon" }] : /* istanbul ignore next */ []));
2226
+ navItems = input([], ...(ngDevMode ? [{ debugName: "navItems" }] : /* istanbul ignore next */ []));
2227
+ sidebarCollapsed = input(false, ...(ngDevMode ? [{ debugName: "sidebarCollapsed" }] : /* istanbul ignore next */ []));
2228
+ mobileSidebarOpen = input(false, ...(ngDevMode ? [{ debugName: "mobileSidebarOpen" }] : /* istanbul ignore next */ []));
2229
+ topbarTitle = input('', ...(ngDevMode ? [{ debugName: "topbarTitle" }] : /* istanbul ignore next */ []));
2230
+ topbarSubtitle = input('', ...(ngDevMode ? [{ debugName: "topbarSubtitle" }] : /* istanbul ignore next */ []));
2231
+ currentTheme = input('light', ...(ngDevMode ? [{ debugName: "currentTheme" }] : /* istanbul ignore next */ []));
2232
+ themeOptions = input([], ...(ngDevMode ? [{ debugName: "themeOptions" }] : /* istanbul ignore next */ []));
2233
+ topbarActions = input([], ...(ngDevMode ? [{ debugName: "topbarActions" }] : /* istanbul ignore next */ []));
2234
+ showThemeSelector = input(true, ...(ngDevMode ? [{ debugName: "showThemeSelector" }] : /* istanbul ignore next */ []));
2235
+ menuClick = output();
2236
+ sidebarCollapseToggle = output();
2237
+ themeChange = output();
2238
+ topbarActionClick = output();
2239
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDashboardShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2240
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: JvDashboardShellComponent, isStandalone: true, selector: "jv-dashboard-shell", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, sidebarSubtitle: { classPropertyName: "sidebarSubtitle", publicName: "sidebarSubtitle", isSignal: true, isRequired: false, transformFunction: null }, brandIcon: { classPropertyName: "brandIcon", publicName: "brandIcon", isSignal: true, isRequired: false, transformFunction: null }, navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, sidebarCollapsed: { classPropertyName: "sidebarCollapsed", publicName: "sidebarCollapsed", isSignal: true, isRequired: false, transformFunction: null }, mobileSidebarOpen: { classPropertyName: "mobileSidebarOpen", publicName: "mobileSidebarOpen", isSignal: true, isRequired: false, transformFunction: null }, topbarTitle: { classPropertyName: "topbarTitle", publicName: "topbarTitle", isSignal: true, isRequired: false, transformFunction: null }, topbarSubtitle: { classPropertyName: "topbarSubtitle", publicName: "topbarSubtitle", isSignal: true, isRequired: false, transformFunction: null }, currentTheme: { classPropertyName: "currentTheme", publicName: "currentTheme", isSignal: true, isRequired: false, transformFunction: null }, themeOptions: { classPropertyName: "themeOptions", publicName: "themeOptions", isSignal: true, isRequired: false, transformFunction: null }, topbarActions: { classPropertyName: "topbarActions", publicName: "topbarActions", isSignal: true, isRequired: false, transformFunction: null }, showThemeSelector: { classPropertyName: "showThemeSelector", publicName: "showThemeSelector", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { menuClick: "menuClick", sidebarCollapseToggle: "sidebarCollapseToggle", themeChange: "themeChange", topbarActionClick: "topbarActionClick" }, ngImport: i0, template: `
2241
+ <div class="jv-dashboard-shell" [class.is-sidebar-collapsed]="sidebarCollapsed()">
2242
+ <jv-sidebar
2243
+ [title]="title()"
2244
+ [subtitle]="sidebarSubtitle()"
2245
+ [brandIcon]="brandIcon()"
2246
+ [items]="navItems()"
2247
+ [collapsed]="sidebarCollapsed()"
2248
+ [mobileOpen]="mobileSidebarOpen()"
2249
+ (collapseToggle)="sidebarCollapseToggle.emit()"
2250
+ />
2251
+
2252
+ <div class="jv-dashboard-main">
2253
+ <jv-topbar
2254
+ [title]="topbarTitle()"
2255
+ [subtitle]="topbarSubtitle()"
2256
+ [currentTheme]="currentTheme()"
2257
+ [themeOptions]="themeOptions()"
2258
+ [actions]="topbarActions()"
2259
+ [showThemeSelector]="showThemeSelector()"
2260
+ (menuClick)="menuClick.emit()"
2261
+ (themeChange)="themeChange.emit($event)"
2262
+ (actionClick)="topbarActionClick.emit($event)"
2263
+ >
2264
+ <ng-content select="[topbar-actions]" topbar-actions></ng-content>
2265
+ </jv-topbar>
2266
+
2267
+ <main class="jv-dashboard-content">
2268
+ <ng-content></ng-content>
2269
+ </main>
2270
+ </div>
2271
+ </div>
2272
+ `, isInline: true, styles: [":host{display:block;height:100dvh;overflow:hidden}.jv-dashboard-shell{display:grid;height:100%;grid-template-columns:auto 1fr;background:var(--jv-color-background);overflow:hidden}.jv-dashboard-main{display:grid;grid-template-rows:auto 1fr;min-width:0;min-height:0;overflow:hidden}.jv-dashboard-content{overflow:auto;min-width:0;min-height:0;padding:var(--jv-spacing-xl);scrollbar-width:none;-ms-overflow-style:none}.jv-dashboard-content::-webkit-scrollbar{width:0;height:0;display:none}@media(max-width:900px){.jv-dashboard-shell{grid-template-columns:1fr}.jv-dashboard-content{padding:var(--jv-spacing-lg)}}\n"], dependencies: [{ kind: "component", type: JvSidebarComponent, selector: "jv-sidebar", inputs: ["title", "subtitle", "brandIcon", "items", "collapsed", "mobileOpen", "ariaLabel"], outputs: ["collapseToggle"] }, { kind: "component", type: JvTopbarComponent, selector: "jv-topbar", inputs: ["title", "subtitle", "currentTheme", "themeOptions", "actions", "showThemeSelector"], outputs: ["menuClick", "themeChange", "actionClick"] }] });
2273
+ }
2274
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvDashboardShellComponent, decorators: [{
2275
+ type: Component,
2276
+ args: [{ selector: 'jv-dashboard-shell', standalone: true, imports: [JvSidebarComponent, JvTopbarComponent], template: `
2277
+ <div class="jv-dashboard-shell" [class.is-sidebar-collapsed]="sidebarCollapsed()">
2278
+ <jv-sidebar
2279
+ [title]="title()"
2280
+ [subtitle]="sidebarSubtitle()"
2281
+ [brandIcon]="brandIcon()"
2282
+ [items]="navItems()"
2283
+ [collapsed]="sidebarCollapsed()"
2284
+ [mobileOpen]="mobileSidebarOpen()"
2285
+ (collapseToggle)="sidebarCollapseToggle.emit()"
2286
+ />
2287
+
2288
+ <div class="jv-dashboard-main">
2289
+ <jv-topbar
2290
+ [title]="topbarTitle()"
2291
+ [subtitle]="topbarSubtitle()"
2292
+ [currentTheme]="currentTheme()"
2293
+ [themeOptions]="themeOptions()"
2294
+ [actions]="topbarActions()"
2295
+ [showThemeSelector]="showThemeSelector()"
2296
+ (menuClick)="menuClick.emit()"
2297
+ (themeChange)="themeChange.emit($event)"
2298
+ (actionClick)="topbarActionClick.emit($event)"
2299
+ >
2300
+ <ng-content select="[topbar-actions]" topbar-actions></ng-content>
2301
+ </jv-topbar>
2302
+
2303
+ <main class="jv-dashboard-content">
2304
+ <ng-content></ng-content>
2305
+ </main>
2306
+ </div>
2307
+ </div>
2308
+ `, styles: [":host{display:block;height:100dvh;overflow:hidden}.jv-dashboard-shell{display:grid;height:100%;grid-template-columns:auto 1fr;background:var(--jv-color-background);overflow:hidden}.jv-dashboard-main{display:grid;grid-template-rows:auto 1fr;min-width:0;min-height:0;overflow:hidden}.jv-dashboard-content{overflow:auto;min-width:0;min-height:0;padding:var(--jv-spacing-xl);scrollbar-width:none;-ms-overflow-style:none}.jv-dashboard-content::-webkit-scrollbar{width:0;height:0;display:none}@media(max-width:900px){.jv-dashboard-shell{grid-template-columns:1fr}.jv-dashboard-content{padding:var(--jv-spacing-lg)}}\n"] }]
2309
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], sidebarSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarSubtitle", required: false }] }], brandIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandIcon", required: false }] }], navItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "navItems", required: false }] }], sidebarCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidebarCollapsed", required: false }] }], mobileSidebarOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileSidebarOpen", required: false }] }], topbarTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "topbarTitle", required: false }] }], topbarSubtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "topbarSubtitle", required: false }] }], currentTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTheme", required: false }] }], themeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "themeOptions", required: false }] }], topbarActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "topbarActions", required: false }] }], showThemeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThemeSelector", required: false }] }], menuClick: [{ type: i0.Output, args: ["menuClick"] }], sidebarCollapseToggle: [{ type: i0.Output, args: ["sidebarCollapseToggle"] }], themeChange: [{ type: i0.Output, args: ["themeChange"] }], topbarActionClick: [{ type: i0.Output, args: ["topbarActionClick"] }] } });
2310
+
2311
+ class JvPageComponent {
2312
+ eyebrow = input('', ...(ngDevMode ? [{ debugName: "eyebrow" }] : /* istanbul ignore next */ []));
2313
+ title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
2314
+ description = input('', ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
2315
+ breadcrumbs = input([], ...(ngDevMode ? [{ debugName: "breadcrumbs" }] : /* istanbul ignore next */ []));
2316
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2317
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvPageComponent, isStandalone: true, selector: "jv-page", inputs: { eyebrow: { classPropertyName: "eyebrow", publicName: "eyebrow", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, breadcrumbs: { classPropertyName: "breadcrumbs", publicName: "breadcrumbs", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2318
+ <section class="jv-page">
2319
+ @if (eyebrow() || title() || description() || breadcrumbs().length > 0) {
2320
+ <header class="jv-page-header">
2321
+ <jv-breadcrumb [items]="breadcrumbs()" />
2322
+
2323
+ @if (eyebrow()) {
2324
+ <p class="eyebrow">{{ eyebrow() }}</p>
2325
+ }
2326
+
2327
+ @if (title()) {
2328
+ <h1>{{ title() }}</h1>
2329
+ }
2330
+
2331
+ @if (description()) {
2332
+ <p class="description">{{ description() }}</p>
2333
+ }
2334
+
2335
+ <ng-content select="[page-actions]"></ng-content>
2336
+ </header>
2337
+ }
2338
+
2339
+ <div class="jv-page-content">
2340
+ <ng-content></ng-content>
2341
+ </div>
2342
+ </section>
2343
+ `, isInline: true, styles: [".jv-page{display:grid;gap:var(--jv-spacing-lg);min-width:0}.jv-page-header{display:grid;gap:var(--jv-spacing-sm)}.jv-page-content{min-width:0}.eyebrow{margin:0;color:var(--jv-color-primary);font-size:.75rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase}h1,.description{margin:0}.description{color:var(--jv-color-foreground-muted);max-width:72ch}\n"], dependencies: [{ kind: "component", type: JvBreadcrumbComponent, selector: "jv-breadcrumb", inputs: ["items"] }] });
2344
+ }
2345
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvPageComponent, decorators: [{
2346
+ type: Component,
2347
+ args: [{ selector: 'jv-page', standalone: true, imports: [JvBreadcrumbComponent], template: `
2348
+ <section class="jv-page">
2349
+ @if (eyebrow() || title() || description() || breadcrumbs().length > 0) {
2350
+ <header class="jv-page-header">
2351
+ <jv-breadcrumb [items]="breadcrumbs()" />
2352
+
2353
+ @if (eyebrow()) {
2354
+ <p class="eyebrow">{{ eyebrow() }}</p>
2355
+ }
2356
+
2357
+ @if (title()) {
2358
+ <h1>{{ title() }}</h1>
2359
+ }
2360
+
2361
+ @if (description()) {
2362
+ <p class="description">{{ description() }}</p>
2363
+ }
2364
+
2365
+ <ng-content select="[page-actions]"></ng-content>
2366
+ </header>
2367
+ }
2368
+
2369
+ <div class="jv-page-content">
2370
+ <ng-content></ng-content>
2371
+ </div>
2372
+ </section>
2373
+ `, styles: [".jv-page{display:grid;gap:var(--jv-spacing-lg);min-width:0}.jv-page-header{display:grid;gap:var(--jv-spacing-sm)}.jv-page-content{min-width:0}.eyebrow{margin:0;color:var(--jv-color-primary);font-size:.75rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase}h1,.description{margin:0}.description{color:var(--jv-color-foreground-muted);max-width:72ch}\n"] }]
2374
+ }], propDecorators: { eyebrow: [{ type: i0.Input, args: [{ isSignal: true, alias: "eyebrow", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], breadcrumbs: [{ type: i0.Input, args: [{ isSignal: true, alias: "breadcrumbs", required: false }] }] } });
2375
+
2376
+ const JV_GRID_DEFAULT_OPTIONS = {
2377
+ searchable: true,
2378
+ sortable: true,
2379
+ pageable: true,
2380
+ selectable: false,
2381
+ pageSize: 20,
2382
+ pageSizeOptions: [10, 20, 50, 100],
2383
+ density: 'normal',
2384
+ loading: false,
2385
+ emptyMessage: '',
2386
+ noResultsMessage: '',
2387
+ searchPlaceholder: '',
2388
+ ariaLabel: '',
2389
+ };
2390
+
2391
+ let gridIdSequence = 0;
2392
+ class JvGridComponent {
2393
+ gridId = `jv-grid-${++gridIdSequence}`;
2394
+ translationService = inject(JvTranslationService);
2395
+ data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
2396
+ columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
2397
+ actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
2398
+ options = input({}, ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
2399
+ trackBy = input(null, ...(ngDevMode ? [{ debugName: "trackBy" }] : /* istanbul ignore next */ []));
2400
+ selectedIds = input([], ...(ngDevMode ? [{ debugName: "selectedIds" }] : /* istanbul ignore next */ []));
2401
+ rowClick = output();
2402
+ actionClick = output();
2403
+ selectionChange = output();
2404
+ pageChange = output();
2405
+ searchChange = output();
2406
+ sortChange = output();
2407
+ effectiveOptions = computed(() => ({ ...JV_GRID_DEFAULT_OPTIONS, ...this.options() }), ...(ngDevMode ? [{ debugName: "effectiveOptions" }] : /* istanbul ignore next */ []));
2408
+ idKey = computed(() => {
2409
+ const tb = this.trackBy();
2410
+ if (typeof tb === 'string')
2411
+ return tb;
2412
+ return null;
2413
+ }, ...(ngDevMode ? [{ debugName: "idKey" }] : /* istanbul ignore next */ []));
2414
+ resolvedOptions = this.effectiveOptions;
2415
+ gridLabel = computed(() => this.resolvedOptions().ariaLabel || `Grid ${this.gridId}`, ...(ngDevMode ? [{ debugName: "gridLabel" }] : /* istanbul ignore next */ []));
2416
+ visibleColumns = computed(() => this.columns().filter(col => !col.hidden), ...(ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []));
2417
+ internalSelectedIds = signal([], ...(ngDevMode ? [{ debugName: "internalSelectedIds" }] : /* istanbul ignore next */ []));
2418
+ searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
2419
+ sortState = signal({ columnKey: '', direction: null }, ...(ngDevMode ? [{ debugName: "sortState" }] : /* istanbul ignore next */ []));
2420
+ pageIndex = signal(0, ...(ngDevMode ? [{ debugName: "pageIndex" }] : /* istanbul ignore next */ []));
2421
+ pageSize = computed(() => this.effectiveOptions().pageSize ?? 20, ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
2422
+ searchPageResetter = effect(() => {
2423
+ this.searchTerm();
2424
+ this.pageIndex.set(0);
2425
+ }, ...(ngDevMode ? [{ debugName: "searchPageResetter" }] : /* istanbul ignore next */ []));
2426
+ selectedIdSet = computed(() => {
2427
+ const fromInput = this.selectedIds();
2428
+ const fromInternal = this.internalSelectedIds();
2429
+ const ids = fromInput.length > 0 ? fromInput : fromInternal;
2430
+ return new Set(ids);
2431
+ }, ...(ngDevMode ? [{ debugName: "selectedIdSet" }] : /* istanbul ignore next */ []));
2432
+ filteredData = computed(() => {
2433
+ let items = this.data();
2434
+ const term = this.searchTerm().trim().toLowerCase();
2435
+ if (!term)
2436
+ return items;
2437
+ return items.filter(row => {
2438
+ return this.visibleColumns().some(col => {
2439
+ if (col.searchable === false)
2440
+ return false;
2441
+ const value = this.getRawCellValue(row, col);
2442
+ if (value == null)
2443
+ return false;
2444
+ return String(value).toLowerCase().includes(term);
2445
+ });
2446
+ });
2447
+ }, ...(ngDevMode ? [{ debugName: "filteredData" }] : /* istanbul ignore next */ []));
2448
+ sortedData = computed(() => {
2449
+ let items = this.filteredData();
2450
+ const { columnKey, direction } = this.sortState();
2451
+ if (!columnKey || !direction)
2452
+ return items;
2453
+ return [...items].sort((a, b) => {
2454
+ const va = this.getRawCellValue(a, this.findColumn(columnKey));
2455
+ const vb = this.getRawCellValue(b, this.findColumn(columnKey));
2456
+ if (va == null)
2457
+ return 1;
2458
+ if (vb == null)
2459
+ return -1;
2460
+ const cmp = va < vb ? -1 : va > vb ? 1 : 0;
2461
+ return direction === 'asc' ? cmp : -cmp;
2462
+ });
2463
+ }, ...(ngDevMode ? [{ debugName: "sortedData" }] : /* istanbul ignore next */ []));
2464
+ totalItems = computed(() => this.sortedData().length, ...(ngDevMode ? [{ debugName: "totalItems" }] : /* istanbul ignore next */ []));
2465
+ totalPages = computed(() => Math.max(1, Math.ceil(this.totalItems() / this.pageSize())), ...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
2466
+ pagedData = computed(() => {
2467
+ const items = this.sortedData();
2468
+ const ps = this.pageSize();
2469
+ const page = Math.min(this.pageIndex(), this.totalPages() - 1);
2470
+ return items.slice(page * ps, page * ps + ps);
2471
+ }, ...(ngDevMode ? [{ debugName: "pagedData" }] : /* istanbul ignore next */ []));
2472
+ pageStart = computed(() => this.totalItems() === 0 ? 0 : this.pageIndex() * this.pageSize() + 1, ...(ngDevMode ? [{ debugName: "pageStart" }] : /* istanbul ignore next */ []));
2473
+ pageEnd = computed(() => Math.min((this.pageIndex() + 1) * this.pageSize(), this.totalItems()), ...(ngDevMode ? [{ debugName: "pageEnd" }] : /* istanbul ignore next */ []));
2474
+ pageRange = computed(() => {
2475
+ const total = this.totalPages();
2476
+ const current = this.pageIndex();
2477
+ const range = [];
2478
+ const start = Math.max(0, current - 2);
2479
+ const end = Math.min(total, start + 5);
2480
+ for (let i = start; i < end; i++)
2481
+ range.push(i);
2482
+ return range;
2483
+ }, ...(ngDevMode ? [{ debugName: "pageRange" }] : /* istanbul ignore next */ []));
2484
+ allSelected = computed(() => {
2485
+ const rows = this.pagedData();
2486
+ return rows.length > 0 && rows.every(r => this.selectedIdSet().has(this.getRowId(r)));
2487
+ }, ...(ngDevMode ? [{ debugName: "allSelected" }] : /* istanbul ignore next */ []));
2488
+ someSelected = computed(() => {
2489
+ const rows = this.pagedData();
2490
+ return rows.some(r => this.selectedIdSet().has(this.getRowId(r))) && !this.allSelected();
2491
+ }, ...(ngDevMode ? [{ debugName: "someSelected" }] : /* istanbul ignore next */ []));
2492
+ colspan = computed(() => {
2493
+ let count = this.visibleColumns().length;
2494
+ if (this.resolvedOptions().selectable)
2495
+ count++;
2496
+ if (this.actions().length > 0)
2497
+ count++;
2498
+ return count;
2499
+ }, ...(ngDevMode ? [{ debugName: "colspan" }] : /* istanbul ignore next */ []));
2500
+ densityClass = computed(() => `jv-grid-density--${this.resolvedOptions().density ?? 'normal'}`, ...(ngDevMode ? [{ debugName: "densityClass" }] : /* istanbul ignore next */ []));
2501
+ t = (key, params) => this.translationService.translate(key, params);
2502
+ getRowId(row, index) {
2503
+ const idKey = this.idKey();
2504
+ if (idKey)
2505
+ return row[idKey];
2506
+ const tb = this.trackBy();
2507
+ if (typeof tb === 'function')
2508
+ return tb(row, index ?? 0);
2509
+ if (index !== undefined)
2510
+ return index;
2511
+ return this.data().indexOf(row);
2512
+ }
2513
+ colKey(col) {
2514
+ return String(col.key);
2515
+ }
2516
+ getRawCellValue(row, col) {
2517
+ return row[col.key];
2518
+ }
2519
+ getCellValue(row, col) {
2520
+ return this.getRawCellValue(row, col);
2521
+ }
2522
+ formatValue(value, col) {
2523
+ if (value == null)
2524
+ return '';
2525
+ if (col.type === 'date' && value instanceof Date) {
2526
+ return value.toLocaleDateString();
2527
+ }
2528
+ return String(value);
2529
+ }
2530
+ getAriaSort(col) {
2531
+ if (col.sortable === false || !this.resolvedOptions().sortable)
2532
+ return null;
2533
+ if (this.sortState().columnKey !== this.colKey(col))
2534
+ return 'none';
2535
+ if (this.sortState().direction === 'asc')
2536
+ return 'ascending';
2537
+ if (this.sortState().direction === 'desc')
2538
+ return 'descending';
2539
+ return 'none';
2540
+ }
2541
+ getSortLabel(col) {
2542
+ const ariaSort = this.getAriaSort(col);
2543
+ if (ariaSort === 'ascending')
2544
+ return `${col.header} - ${this.t('grid.sortAsc')}`;
2545
+ if (ariaSort === 'descending')
2546
+ return `${col.header} - ${this.t('grid.sortDesc')}`;
2547
+ return `${col.header} - ${this.t('grid.sortAsc')} ${this.t('grid.sortDesc')}`;
2548
+ }
2549
+ findColumn(key) {
2550
+ return this.columns().find(c => this.colKey(c) === key) ?? { key, header: key };
2551
+ }
2552
+ toggleSort(col, event) {
2553
+ if (!event || !(event instanceof KeyboardEvent && event.key === ' ')) {
2554
+ if (col.sortable === false || !this.resolvedOptions().sortable)
2555
+ return;
2556
+ if (event instanceof KeyboardEvent)
2557
+ event.preventDefault();
2558
+ const key = this.colKey(col);
2559
+ const current = this.sortState();
2560
+ let direction;
2561
+ if (current.columnKey !== key) {
2562
+ direction = 'asc';
2563
+ }
2564
+ else if (current.direction === 'asc') {
2565
+ direction = 'desc';
2566
+ }
2567
+ else {
2568
+ direction = null;
2569
+ }
2570
+ this.sortState.set({ columnKey: direction ? key : '', direction });
2571
+ this.pageIndex.set(0);
2572
+ this.sortChange.emit({ columnKey: direction ? key : '', direction });
2573
+ }
2574
+ }
2575
+ actionVariant = (v) => v === 'primary' ? 'primary' : v === 'danger' ? 'danger' : 'ghost';
2576
+ onSearchInput(value) {
2577
+ this.searchTerm.set(value);
2578
+ this.searchChange.emit(value);
2579
+ }
2580
+ goToPage(page) {
2581
+ if (page >= 0 && page < this.totalPages()) {
2582
+ this.pageIndex.set(page);
2583
+ this.pageChange.emit({ pageIndex: page, pageSize: this.pageSize() });
2584
+ }
2585
+ }
2586
+ isSelected(row) {
2587
+ return this.selectedIdSet().has(this.getRowId(row));
2588
+ }
2589
+ toggleRow(row) {
2590
+ const id = this.getRowId(row);
2591
+ this.internalSelectedIds.update(ids => {
2592
+ const set = new Set(ids);
2593
+ if (set.has(id))
2594
+ set.delete(id);
2595
+ else
2596
+ set.add(id);
2597
+ return Array.from(set);
2598
+ });
2599
+ this.selectionChange.emit(this.internalSelectedIds());
2600
+ }
2601
+ toggleSelectAll(checked) {
2602
+ this.internalSelectedIds.update(ids => {
2603
+ const set = new Set(ids);
2604
+ const rows = this.pagedData();
2605
+ rows.forEach(r => {
2606
+ if (checked)
2607
+ set.add(this.getRowId(r));
2608
+ else
2609
+ set.delete(this.getRowId(r));
2610
+ });
2611
+ return Array.from(set);
2612
+ });
2613
+ this.selectionChange.emit(this.internalSelectedIds());
2614
+ }
2615
+ onRowClick(event, row) {
2616
+ if (event instanceof Event) {
2617
+ event.preventDefault();
2618
+ this.rowClick.emit(row);
2619
+ }
2620
+ else {
2621
+ this.rowClick.emit(event);
2622
+ }
2623
+ }
2624
+ onActionClick(action, row) {
2625
+ this.actionClick.emit({ actionId: action.id, row });
2626
+ }
2627
+ getRowLabel(row, index) {
2628
+ const label = this.visibleColumns()
2629
+ .slice(0, 2)
2630
+ .map(col => this.getCellValue(row, col))
2631
+ .filter(v => v != null)
2632
+ .join(' - ');
2633
+ return `${label}${this.isSelected(row) ? ` (${this.t('grid.selected')})` : ''}`;
2634
+ }
2635
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2636
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JvGridComponent, isStandalone: true, selector: "jv-grid", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null }, selectedIds: { classPropertyName: "selectedIds", publicName: "selectedIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClick: "rowClick", actionClick: "actionClick", selectionChange: "selectionChange", pageChange: "pageChange", searchChange: "searchChange", sortChange: "sortChange" }, ngImport: i0, template: `
2637
+ <div class="jv-grid" role="region" [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()">
2638
+ @if (resolvedOptions().searchable) {
2639
+ <div class="jv-grid-toolbar">
2640
+ <div class="jv-grid-search">
2641
+ <jv-icon name="search" [size]="16" />
2642
+ <jv-input
2643
+ inputId="grid-search"
2644
+ [placeholder]="t('grid.searchPlaceholder')"
2645
+ [ngModel]="searchTerm()"
2646
+ (ngModelChange)="onSearchInput($event)"
2647
+ />
2648
+ </div>
2649
+ </div>
2650
+ }
2651
+
2652
+ @if (resolvedOptions().loading) {
2653
+ <div class="jv-grid-loading" role="status" aria-live="polite">
2654
+ <span class="jv-grid-spinner" aria-hidden="true"></span>
2655
+ <span>{{ t('grid.loading') }}</span>
2656
+ </div>
2657
+ } @else {
2658
+ <div class="jv-grid-table-wrap">
2659
+ <table
2660
+ class="jv-grid-table"
2661
+ role="grid"
2662
+ [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
2663
+ [class]="densityClass()"
2664
+ >
2665
+ <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
2666
+ <thead class="jv-grid-thead">
2667
+ <tr>
2668
+ @if (resolvedOptions().selectable) {
2669
+ <th class="jv-grid-th jv-grid-th-select" scope="col">
2670
+ <input
2671
+ type="checkbox"
2672
+ class="jv-grid-checkbox"
2673
+ [checked]="allSelected()"
2674
+ (change)="toggleSelectAll($any($event.target).checked)"
2675
+ [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
2676
+ [indeterminate]="someSelected()"
2677
+ />
2678
+ </th>
2679
+ }
2680
+ @for (col of visibleColumns(); track colKey(col)) {
2681
+ <th
2682
+ class="jv-grid-th"
2683
+ [class]="col.headerClass || ''"
2684
+ [class.jv-grid-th-sortable]="col.sortable !== false && resolvedOptions().sortable"
2685
+ [style.text-align]="col.align ?? 'start'"
2686
+ [style.width]="col.width || undefined"
2687
+ scope="col"
2688
+ [attr.aria-sort]="getAriaSort(col)"
2689
+ [attr.aria-label]="getSortLabel(col)"
2690
+ [tabindex]="col.sortable !== false && resolvedOptions().sortable ? 0 : -1"
2691
+ (click)="toggleSort(col)"
2692
+ (keydown.enter)="toggleSort(col)"
2693
+ (keydown.space)="toggleSort(col, $event)"
2694
+ >
2695
+ <div class="jv-grid-th-content">
2696
+ <span>{{ col.header }}</span>
2697
+ @if (col.sortable !== false && resolvedOptions().sortable) {
2698
+ <span class="jv-grid-sort-icon" aria-hidden="true">
2699
+ @if (sortState().columnKey === colKey(col)) {
2700
+ @if (sortState().direction === 'asc') { ▲ }
2701
+ @if (sortState().direction === 'desc') { ▼ }
2702
+ }
2703
+ </span>
2704
+ }
2705
+ </div>
2706
+ </th>
2707
+ }
2708
+ @if (actions().length > 0) {
2709
+ <th class="jv-grid-th jv-grid-th-actions" scope="col">
2710
+ <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
2711
+ </th>
2712
+ }
2713
+ </tr>
2714
+ </thead>
2715
+ <tbody class="jv-grid-tbody">
2716
+ @if (pagedData().length === 0 && !searchTerm().trim()) {
2717
+ <tr>
2718
+ <td
2719
+ class="jv-grid-empty"
2720
+ [attr.colspan]="colspan()"
2721
+ >
2722
+ {{ resolvedOptions().emptyMessage || t('grid.emptyMessage') }}
2723
+ </td>
2724
+ </tr>
2725
+ } @else if (pagedData().length === 0 && searchTerm().trim()) {
2726
+ <tr>
2727
+ <td
2728
+ class="jv-grid-empty"
2729
+ [attr.colspan]="colspan()"
2730
+ >
2731
+ {{ resolvedOptions().noResultsMessage || t('grid.noResults') }}
2732
+ </td>
2733
+ </tr>
2734
+ } @else {
2735
+ @for (row of pagedData(); track getRowId(row, $index)) {
2736
+ <tr
2737
+ class="jv-grid-tr"
2738
+ (click)="onRowClick(row)"
2739
+ (keydown.enter)="onRowClick(row)"
2740
+ (keydown.space)="onRowClick($event, row)"
2741
+ [tabindex]="0"
2742
+ >
2743
+ @if (resolvedOptions().selectable) {
2744
+ <td class="jv-grid-td jv-grid-td-select">
2745
+ <input
2746
+ type="checkbox"
2747
+ class="jv-grid-checkbox"
2748
+ [checked]="isSelected(row)"
2749
+ (change)="toggleRow(row); $event.stopPropagation()"
2750
+ (keydown.space)="$event.stopPropagation()"
2751
+ [attr.aria-label]="getRowLabel(row, $index)"
2752
+ />
2753
+ </td>
2754
+ }
2755
+ @for (col of visibleColumns(); track colKey(col)) {
2756
+ <td
2757
+ class="jv-grid-td"
2758
+ [class]="col.cellClass || ''"
2759
+ [style.text-align]="col.align ?? 'start'"
2760
+ >
2761
+ @let value = getCellValue(row, col);
2762
+ @if (col.type === 'boolean') {
2763
+ @if (value) {
2764
+ <span class="jv-grid-boolean jv-grid-boolean-true" aria-label="true">✓</span>
2765
+ } @else {
2766
+ <span class="jv-grid-boolean jv-grid-boolean-false" aria-label="false">✗</span>
2767
+ }
2768
+ } @else if (col.format) {
2769
+ {{ col.format(value, row) }}
2770
+ } @else {
2771
+ {{ formatValue(value, col) }}
2772
+ }
2773
+ </td>
2774
+ }
2775
+ @if (actions().length > 0) {
2776
+ <td class="jv-grid-td jv-grid-td-actions">
2777
+ <div class="jv-grid-actions-group">
2778
+ @for (action of actions(); track action.id) {
2779
+ <jv-button
2780
+ [variant]="actionVariant(action.variant)"
2781
+ [icon]="action.icon ?? null"
2782
+ [disabled]="action.disabled?.(row) ?? false"
2783
+ [attr.aria-label]="action.label"
2784
+ (click)="onActionClick(action, row); $event.stopPropagation()"
2785
+ >{{ action.label }}</jv-button>
2786
+ }
2787
+ </div>
2788
+ </td>
2789
+ }
2790
+ </tr>
2791
+ }
2792
+ }
2793
+ </tbody>
2794
+ </table>
2795
+ </div>
2796
+ }
2797
+
2798
+ @if (resolvedOptions().pageable && totalPages() > 1 && !resolvedOptions().loading) {
2799
+ <nav class="jv-grid-pagination" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
2800
+ <div class="jv-grid-pagination-info">
2801
+ {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: totalItems() }) }}
2802
+ </div>
2803
+ <div class="jv-grid-pagination-controls">
2804
+ <jv-button
2805
+ variant="outline"
2806
+ icon="chevrons-left"
2807
+ [disabled]="pageIndex() === 0"
2808
+ (click)="goToPage(0)"
2809
+ [attr.aria-label]="t('grid.pageFirst')"
2810
+ ></jv-button>
2811
+ <jv-button
2812
+ variant="outline"
2813
+ icon="chevron-left"
2814
+ [disabled]="pageIndex() === 0"
2815
+ (click)="goToPage(pageIndex() - 1)"
2816
+ [attr.aria-label]="t('grid.pagePrev')"
2817
+ ></jv-button>
2818
+ @for (p of pageRange(); track p) {
2819
+ <jv-button
2820
+ [variant]="p === pageIndex() ? 'primary' : 'outline'"
2821
+ [attr.aria-current]="p === pageIndex() ? 'page' : null"
2822
+ [attr.aria-label]="t('grid.pageOfTotal', { page: p + 1, total: totalPages() })"
2823
+ (click)="goToPage(p)"
2824
+ >{{ p + 1 }}</jv-button>
2825
+ }
2826
+ <jv-button
2827
+ variant="outline"
2828
+ icon="chevron-right"
2829
+ [disabled]="pageIndex() >= totalPages() - 1"
2830
+ (click)="goToPage(pageIndex() + 1)"
2831
+ [attr.aria-label]="t('grid.pageNext')"
2832
+ ></jv-button>
2833
+ <jv-button
2834
+ variant="outline"
2835
+ icon="chevrons-right"
2836
+ [disabled]="pageIndex() >= totalPages() - 1"
2837
+ (click)="goToPage(totalPages() - 1)"
2838
+ [attr.aria-label]="t('grid.pageLast')"
2839
+ ></jv-button>
2840
+ </div>
2841
+ </nav>
2842
+ }
2843
+ </div>
2844
+ `, isInline: true, styles: [".jv-grid{display:flex;flex-direction:column;gap:var(--jv-spacing-md)}.jv-grid-toolbar{display:flex;align-items:center;gap:var(--jv-spacing-md)}.jv-grid-search{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-grid-table-wrap{overflow-x:auto;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md)}.jv-grid-table{width:100%;border-collapse:collapse;background:var(--jv-color-surface)}.jv-grid-thead{background:var(--jv-color-surface-muted)}.jv-grid-th{padding:var(--jv-spacing-sm) var(--jv-spacing-md);font-weight:600;font-size:.875rem;color:var(--jv-color-foreground-muted);text-align:start;white-space:nowrap;border-bottom:1px solid var(--jv-color-border);-webkit-user-select:none;user-select:none}.jv-grid-th-sortable{cursor:pointer}.jv-grid-th-sortable:hover{background:var(--jv-color-surface)}.jv-grid-th-content{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-sort-icon{font-size:.75rem;line-height:1}.jv-grid-th-select,.jv-grid-td-select{width:3rem;text-align:center}.jv-grid-th-actions,.jv-grid-td-actions{width:1%;white-space:nowrap}.jv-grid-td{padding:var(--jv-spacing-sm) var(--jv-spacing-md);color:var(--jv-color-foreground);font-size:var(--jv-density-font-size);border-bottom:1px solid var(--jv-color-border)}.jv-grid-tr{transition:background-color .12s ease}.jv-grid-tr:hover{background:var(--jv-color-surface-muted)}.jv-grid-tr:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-checkbox{width:1rem;height:1rem;accent-color:var(--jv-color-primary);cursor:pointer}.jv-grid-boolean{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:999px;font-size:.75rem;font-weight:700}.jv-grid-boolean-true{background:var(--jv-color-success);color:#fff}.jv-grid-boolean-false{background:var(--jv-color-danger);color:#fff}.jv-grid-empty{padding:var(--jv-spacing-xl) var(--jv-spacing-md);text-align:center;color:var(--jv-color-foreground-muted);font-style:italic}.jv-grid-actions-group{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-loading{display:flex;align-items:center;justify-content:center;gap:var(--jv-spacing-md);padding:var(--jv-spacing-xl);color:var(--jv-color-foreground-muted)}.jv-grid-spinner{width:1.25rem;height:1.25rem;border:2px solid var(--jv-color-border);border-right-color:var(--jv-color-primary);border-radius:999px;animation:jv-grid-spin .8s linear infinite}@keyframes jv-grid-spin{to{transform:rotate(360deg)}}.jv-grid-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-grid-pagination-info{font-size:.875rem;color:var(--jv-color-foreground-muted)}.jv-grid-pagination-controls{display:flex;align-items:center;gap:var(--jv-spacing-xs)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: JvButtonComponent, selector: "jv-button", inputs: ["variant", "icon", "iconPosition", "loading", "disabled"] }, { kind: "component", type: JvIconComponent, selector: "jv-icon", inputs: ["name", "size", "strokeWidth", "decorative", "ariaLabel"] }, { kind: "component", type: JvInputComponent, selector: "jv-input", inputs: ["type", "placeholder", "required", "invalid", "describedBy", "inputId"] }] });
2845
+ }
2846
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JvGridComponent, decorators: [{
2847
+ type: Component,
2848
+ args: [{ selector: 'jv-grid', standalone: true, imports: [FormsModule, JvButtonComponent, JvIconComponent, JvInputComponent], template: `
2849
+ <div class="jv-grid" role="region" [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()">
2850
+ @if (resolvedOptions().searchable) {
2851
+ <div class="jv-grid-toolbar">
2852
+ <div class="jv-grid-search">
2853
+ <jv-icon name="search" [size]="16" />
2854
+ <jv-input
2855
+ inputId="grid-search"
2856
+ [placeholder]="t('grid.searchPlaceholder')"
2857
+ [ngModel]="searchTerm()"
2858
+ (ngModelChange)="onSearchInput($event)"
2859
+ />
2860
+ </div>
2861
+ </div>
2862
+ }
2863
+
2864
+ @if (resolvedOptions().loading) {
2865
+ <div class="jv-grid-loading" role="status" aria-live="polite">
2866
+ <span class="jv-grid-spinner" aria-hidden="true"></span>
2867
+ <span>{{ t('grid.loading') }}</span>
2868
+ </div>
2869
+ } @else {
2870
+ <div class="jv-grid-table-wrap">
2871
+ <table
2872
+ class="jv-grid-table"
2873
+ role="grid"
2874
+ [attr.aria-label]="resolvedOptions().ariaLabel || gridLabel()"
2875
+ [class]="densityClass()"
2876
+ >
2877
+ <caption class="sr-only">{{ resolvedOptions().ariaLabel || gridLabel() }}</caption>
2878
+ <thead class="jv-grid-thead">
2879
+ <tr>
2880
+ @if (resolvedOptions().selectable) {
2881
+ <th class="jv-grid-th jv-grid-th-select" scope="col">
2882
+ <input
2883
+ type="checkbox"
2884
+ class="jv-grid-checkbox"
2885
+ [checked]="allSelected()"
2886
+ (change)="toggleSelectAll($any($event.target).checked)"
2887
+ [attr.aria-label]="allSelected() ? t('grid.deselectAll') : t('grid.selectAll')"
2888
+ [indeterminate]="someSelected()"
2889
+ />
2890
+ </th>
2891
+ }
2892
+ @for (col of visibleColumns(); track colKey(col)) {
2893
+ <th
2894
+ class="jv-grid-th"
2895
+ [class]="col.headerClass || ''"
2896
+ [class.jv-grid-th-sortable]="col.sortable !== false && resolvedOptions().sortable"
2897
+ [style.text-align]="col.align ?? 'start'"
2898
+ [style.width]="col.width || undefined"
2899
+ scope="col"
2900
+ [attr.aria-sort]="getAriaSort(col)"
2901
+ [attr.aria-label]="getSortLabel(col)"
2902
+ [tabindex]="col.sortable !== false && resolvedOptions().sortable ? 0 : -1"
2903
+ (click)="toggleSort(col)"
2904
+ (keydown.enter)="toggleSort(col)"
2905
+ (keydown.space)="toggleSort(col, $event)"
2906
+ >
2907
+ <div class="jv-grid-th-content">
2908
+ <span>{{ col.header }}</span>
2909
+ @if (col.sortable !== false && resolvedOptions().sortable) {
2910
+ <span class="jv-grid-sort-icon" aria-hidden="true">
2911
+ @if (sortState().columnKey === colKey(col)) {
2912
+ @if (sortState().direction === 'asc') { ▲ }
2913
+ @if (sortState().direction === 'desc') { ▼ }
2914
+ }
2915
+ </span>
2916
+ }
2917
+ </div>
2918
+ </th>
2919
+ }
2920
+ @if (actions().length > 0) {
2921
+ <th class="jv-grid-th jv-grid-th-actions" scope="col">
2922
+ <span class="sr-only">{{ t('grid.actionsLabel', { label: '' }) }}</span>
2923
+ </th>
2924
+ }
2925
+ </tr>
2926
+ </thead>
2927
+ <tbody class="jv-grid-tbody">
2928
+ @if (pagedData().length === 0 && !searchTerm().trim()) {
2929
+ <tr>
2930
+ <td
2931
+ class="jv-grid-empty"
2932
+ [attr.colspan]="colspan()"
2933
+ >
2934
+ {{ resolvedOptions().emptyMessage || t('grid.emptyMessage') }}
2935
+ </td>
2936
+ </tr>
2937
+ } @else if (pagedData().length === 0 && searchTerm().trim()) {
2938
+ <tr>
2939
+ <td
2940
+ class="jv-grid-empty"
2941
+ [attr.colspan]="colspan()"
2942
+ >
2943
+ {{ resolvedOptions().noResultsMessage || t('grid.noResults') }}
2944
+ </td>
2945
+ </tr>
2946
+ } @else {
2947
+ @for (row of pagedData(); track getRowId(row, $index)) {
2948
+ <tr
2949
+ class="jv-grid-tr"
2950
+ (click)="onRowClick(row)"
2951
+ (keydown.enter)="onRowClick(row)"
2952
+ (keydown.space)="onRowClick($event, row)"
2953
+ [tabindex]="0"
2954
+ >
2955
+ @if (resolvedOptions().selectable) {
2956
+ <td class="jv-grid-td jv-grid-td-select">
2957
+ <input
2958
+ type="checkbox"
2959
+ class="jv-grid-checkbox"
2960
+ [checked]="isSelected(row)"
2961
+ (change)="toggleRow(row); $event.stopPropagation()"
2962
+ (keydown.space)="$event.stopPropagation()"
2963
+ [attr.aria-label]="getRowLabel(row, $index)"
2964
+ />
2965
+ </td>
2966
+ }
2967
+ @for (col of visibleColumns(); track colKey(col)) {
2968
+ <td
2969
+ class="jv-grid-td"
2970
+ [class]="col.cellClass || ''"
2971
+ [style.text-align]="col.align ?? 'start'"
2972
+ >
2973
+ @let value = getCellValue(row, col);
2974
+ @if (col.type === 'boolean') {
2975
+ @if (value) {
2976
+ <span class="jv-grid-boolean jv-grid-boolean-true" aria-label="true">✓</span>
2977
+ } @else {
2978
+ <span class="jv-grid-boolean jv-grid-boolean-false" aria-label="false">✗</span>
2979
+ }
2980
+ } @else if (col.format) {
2981
+ {{ col.format(value, row) }}
2982
+ } @else {
2983
+ {{ formatValue(value, col) }}
2984
+ }
2985
+ </td>
2986
+ }
2987
+ @if (actions().length > 0) {
2988
+ <td class="jv-grid-td jv-grid-td-actions">
2989
+ <div class="jv-grid-actions-group">
2990
+ @for (action of actions(); track action.id) {
2991
+ <jv-button
2992
+ [variant]="actionVariant(action.variant)"
2993
+ [icon]="action.icon ?? null"
2994
+ [disabled]="action.disabled?.(row) ?? false"
2995
+ [attr.aria-label]="action.label"
2996
+ (click)="onActionClick(action, row); $event.stopPropagation()"
2997
+ >{{ action.label }}</jv-button>
2998
+ }
2999
+ </div>
3000
+ </td>
3001
+ }
3002
+ </tr>
3003
+ }
3004
+ }
3005
+ </tbody>
3006
+ </table>
3007
+ </div>
3008
+ }
3009
+
3010
+ @if (resolvedOptions().pageable && totalPages() > 1 && !resolvedOptions().loading) {
3011
+ <nav class="jv-grid-pagination" [attr.aria-label]="t('grid.paginationLabel', { label: '' })">
3012
+ <div class="jv-grid-pagination-info">
3013
+ {{ t('grid.itemsShowing', { start: pageStart(), end: pageEnd(), total: totalItems() }) }}
3014
+ </div>
3015
+ <div class="jv-grid-pagination-controls">
3016
+ <jv-button
3017
+ variant="outline"
3018
+ icon="chevrons-left"
3019
+ [disabled]="pageIndex() === 0"
3020
+ (click)="goToPage(0)"
3021
+ [attr.aria-label]="t('grid.pageFirst')"
3022
+ ></jv-button>
3023
+ <jv-button
3024
+ variant="outline"
3025
+ icon="chevron-left"
3026
+ [disabled]="pageIndex() === 0"
3027
+ (click)="goToPage(pageIndex() - 1)"
3028
+ [attr.aria-label]="t('grid.pagePrev')"
3029
+ ></jv-button>
3030
+ @for (p of pageRange(); track p) {
3031
+ <jv-button
3032
+ [variant]="p === pageIndex() ? 'primary' : 'outline'"
3033
+ [attr.aria-current]="p === pageIndex() ? 'page' : null"
3034
+ [attr.aria-label]="t('grid.pageOfTotal', { page: p + 1, total: totalPages() })"
3035
+ (click)="goToPage(p)"
3036
+ >{{ p + 1 }}</jv-button>
3037
+ }
3038
+ <jv-button
3039
+ variant="outline"
3040
+ icon="chevron-right"
3041
+ [disabled]="pageIndex() >= totalPages() - 1"
3042
+ (click)="goToPage(pageIndex() + 1)"
3043
+ [attr.aria-label]="t('grid.pageNext')"
3044
+ ></jv-button>
3045
+ <jv-button
3046
+ variant="outline"
3047
+ icon="chevrons-right"
3048
+ [disabled]="pageIndex() >= totalPages() - 1"
3049
+ (click)="goToPage(totalPages() - 1)"
3050
+ [attr.aria-label]="t('grid.pageLast')"
3051
+ ></jv-button>
3052
+ </div>
3053
+ </nav>
3054
+ }
3055
+ </div>
3056
+ `, styles: [".jv-grid{display:flex;flex-direction:column;gap:var(--jv-spacing-md)}.jv-grid-toolbar{display:flex;align-items:center;gap:var(--jv-spacing-md)}.jv-grid-search{display:flex;align-items:center;gap:var(--jv-spacing-sm)}.jv-grid-table-wrap{overflow-x:auto;border:1px solid var(--jv-color-border);border-radius:var(--jv-radius-md)}.jv-grid-table{width:100%;border-collapse:collapse;background:var(--jv-color-surface)}.jv-grid-thead{background:var(--jv-color-surface-muted)}.jv-grid-th{padding:var(--jv-spacing-sm) var(--jv-spacing-md);font-weight:600;font-size:.875rem;color:var(--jv-color-foreground-muted);text-align:start;white-space:nowrap;border-bottom:1px solid var(--jv-color-border);-webkit-user-select:none;user-select:none}.jv-grid-th-sortable{cursor:pointer}.jv-grid-th-sortable:hover{background:var(--jv-color-surface)}.jv-grid-th-content{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-sort-icon{font-size:.75rem;line-height:1}.jv-grid-th-select,.jv-grid-td-select{width:3rem;text-align:center}.jv-grid-th-actions,.jv-grid-td-actions{width:1%;white-space:nowrap}.jv-grid-td{padding:var(--jv-spacing-sm) var(--jv-spacing-md);color:var(--jv-color-foreground);font-size:var(--jv-density-font-size);border-bottom:1px solid var(--jv-color-border)}.jv-grid-tr{transition:background-color .12s ease}.jv-grid-tr:hover{background:var(--jv-color-surface-muted)}.jv-grid-tr:focus-visible{outline:2px solid var(--jv-color-primary);outline-offset:-2px}.jv-grid-checkbox{width:1rem;height:1rem;accent-color:var(--jv-color-primary);cursor:pointer}.jv-grid-boolean{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:999px;font-size:.75rem;font-weight:700}.jv-grid-boolean-true{background:var(--jv-color-success);color:#fff}.jv-grid-boolean-false{background:var(--jv-color-danger);color:#fff}.jv-grid-empty{padding:var(--jv-spacing-xl) var(--jv-spacing-md);text-align:center;color:var(--jv-color-foreground-muted);font-style:italic}.jv-grid-actions-group{display:flex;align-items:center;gap:var(--jv-spacing-xs)}.jv-grid-loading{display:flex;align-items:center;justify-content:center;gap:var(--jv-spacing-md);padding:var(--jv-spacing-xl);color:var(--jv-color-foreground-muted)}.jv-grid-spinner{width:1.25rem;height:1.25rem;border:2px solid var(--jv-color-border);border-right-color:var(--jv-color-primary);border-radius:999px;animation:jv-grid-spin .8s linear infinite}@keyframes jv-grid-spin{to{transform:rotate(360deg)}}.jv-grid-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--jv-spacing-md);flex-wrap:wrap}.jv-grid-pagination-info{font-size:.875rem;color:var(--jv-color-foreground-muted)}.jv-grid-pagination-controls{display:flex;align-items:center;gap:var(--jv-spacing-xs)}\n"] }]
3057
+ }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], trackBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackBy", required: false }] }], selectedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIds", required: false }] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }] } });
3058
+
3059
+ /**
3060
+ * Generated bundle index. Do not edit.
3061
+ */
3062
+
3063
+ export { EN, ES, JV_DEFAULT_LOCALE, JV_GRID_DEFAULT_OPTIONS, JV_LOCALE_DICTIONARIES, JV_UI_CONFIG, JV_UI_CONFIG_DEFAULTS, JV_UI_DEFAULT_CONFIG, JvAlertComponent, JvAnnouncementService, JvBadgeComponent, JvBreadcrumbComponent, JvButtonComponent, JvButtonGroupComponent, JvCardComponent, JvChangePasswordPageComponent, JvCheckboxComponent, JvConfirmDialogComponent, JvDashboardShellComponent, JvDialogComponent, JvDialogService, JvDividerComponent, JvForgotPasswordPageComponent, JvFormContainerComponent, JvGridComponent, JvIconButtonComponent, JvIconComponent, JvInputComponent, JvLoaderComponent, JvLoaderService, JvLoginPageComponent, JvPageComponent, JvRadioComponent, JvSectionComponent, JvSelectComponent, JvSidebarComponent, JvSwitchComponent, JvThemeService, JvToastComponent, JvToastService, JvTopbarComponent, JvTranslationService, provideJvUi };
3064
+ //# sourceMappingURL=devjuliovilla-jv-ui.mjs.map