voyage-lib 0.0.1

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,1290 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, Injectable, inject, ViewEncapsulation, Component, input, computed, output, Renderer2, effect, HostListener, ElementRef, PLATFORM_ID, Directive, Pipe, InjectionToken } from '@angular/core';
3
+ import { TitleCasePipe, isPlatformBrowser } from '@angular/common';
4
+ import { HttpClient } from '@angular/common/http';
5
+ import { map } from 'rxjs/operators';
6
+
7
+ class SnackbarService {
8
+ items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
9
+ show(config) {
10
+ const id = crypto.randomUUID();
11
+ const item = {
12
+ id,
13
+ message: config.message,
14
+ type: config.type ?? 'info',
15
+ title: config.title ?? '',
16
+ duration: config.duration ?? 4000,
17
+ dismissible: config.dismissible ?? true,
18
+ action: config.action,
19
+ exiting: false,
20
+ };
21
+ this.items.update(prev => [...prev, item]);
22
+ if (item.duration > 0) {
23
+ setTimeout(() => this.dismiss(id), item.duration);
24
+ }
25
+ return id;
26
+ }
27
+ dismiss(id) {
28
+ this.items.update(prev => prev.map(i => (i.id === id ? { ...i, exiting: true } : i)));
29
+ setTimeout(() => {
30
+ this.items.update(prev => prev.filter(i => i.id !== id));
31
+ }, 380);
32
+ }
33
+ success(message, config) {
34
+ return this.show({ ...config, message, type: 'success' });
35
+ }
36
+ error(message, config) {
37
+ return this.show({ ...config, message, type: 'error' });
38
+ }
39
+ warning(message, config) {
40
+ return this.show({ ...config, message, type: 'warning' });
41
+ }
42
+ info(message, config) {
43
+ return this.show({ ...config, message, type: 'info' });
44
+ }
45
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnackbarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
46
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnackbarService, providedIn: 'root' });
47
+ }
48
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnackbarService, decorators: [{
49
+ type: Injectable,
50
+ args: [{ providedIn: 'root' }]
51
+ }] });
52
+
53
+ class SnackbarComponent {
54
+ svc = inject(SnackbarService);
55
+ handleAction(item) {
56
+ item.action?.handler();
57
+ this.svc.dismiss(item.id);
58
+ }
59
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnackbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
60
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnackbarComponent, isStandalone: true, selector: "vl-snackbar", ngImport: i0, template: "<div class=\"vl-snackbar-host\" aria-live=\"polite\" aria-atomic=\"false\">\n @for (item of svc.items(); track item.id) {\n <div\n class=\"vl-snackbar-item\"\n [class]=\"'vl-snackbar-item vl-snackbar-' + item.type\"\n [class.vl-snackbar-exiting]=\"item.exiting\"\n role=\"alert\">\n\n <span class=\"vl-snackbar-accent\" aria-hidden=\"true\"></span>\n\n <span class=\"vl-snackbar-icon\" aria-hidden=\"true\">\n @switch (item.type) {\n @case ('success') {\n <svg viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5z\" clip-rule=\"evenodd\" />\n </svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z\" clip-rule=\"evenodd\" />\n </svg>\n }\n @default {\n <svg viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0zm-7-4a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM9 9a.75.75 0 0 0 0 1.5h.253a.25.25 0 0 1 .244.304l-.459 2.066A1.75 1.75 0 0 0 10.747 15H11a.75.75 0 0 0 0-1.5h-.253a.25.25 0 0 1-.244-.304l.459-2.066A1.75 1.75 0 0 0 9.253 9H9z\" clip-rule=\"evenodd\" />\n </svg>\n }\n }\n </span>\n\n <div class=\"vl-snackbar-body\">\n @if (item.title) {\n <p class=\"vl-snackbar-title\">{{ item.title }}</p>\n }\n <p class=\"vl-snackbar-message\">{{ item.message }}</p>\n @if (item.action) {\n <button class=\"vl-snackbar-action-btn\" (click)=\"handleAction(item)\">\n {{ item.action.label }}\n </button>\n }\n </div>\n\n @if (item.dismissible) {\n <button class=\"vl-snackbar-close\" aria-label=\"Dismiss\" (click)=\"svc.dismiss(item.id)\">\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06z\"/>\n </svg>\n </button>\n }\n\n @if (item.duration > 0) {\n <span\n class=\"vl-snackbar-progress\"\n [style.animation-duration.ms]=\"item.duration\"\n aria-hidden=\"true\">\n </span>\n }\n </div>\n }\n</div>\n", styles: [".vl-snackbar-host{position:fixed;bottom:1.25rem;right:1.25rem;z-index:9999;display:flex;flex-direction:column;gap:.5rem;width:21rem;max-width:calc(100vw - 2rem);pointer-events:none}.vl-snackbar-item{position:relative;display:flex;align-items:flex-start;gap:.625rem;padding:.875rem .875rem 1.125rem 1rem;border-radius:.75rem;overflow:hidden;pointer-events:all;background-color:var(--color-card, #ffffff);border:1px solid var(--color-border, hsl(210 15% 85%));box-shadow:0 1px 3px #0000000f,0 6px 20px #00000017;font-family:var(--font-family-sans, \"Inter\", sans-serif);animation:vl-snackbar-enter .34s cubic-bezier(.34,1.3,.64,1) forwards}.vl-snackbar-success{--vl-sb-color: var(--color-accent, hsl(150 60% 35%));--vl-sb-color-bg: color-mix(in srgb, var(--color-accent, hsl(150 60% 35%)) 8%, transparent)}.vl-snackbar-error{--vl-sb-color: var(--color-destructive, hsl(0 70% 50%));--vl-sb-color-bg: color-mix(in srgb, var(--color-destructive, hsl(0 70% 50%)) 8%, transparent)}.vl-snackbar-warning{--vl-sb-color: var(--color-secondary, hsl(15 90% 55%));--vl-sb-color-bg: color-mix(in srgb, var(--color-secondary, hsl(15 90% 55%)) 8%, transparent)}.vl-snackbar-info{--vl-sb-color: var(--color-primary, hsl(215 80% 25%));--vl-sb-color-bg: color-mix(in srgb, var(--color-primary, hsl(215 80% 25%)) 8%, transparent)}.vl-snackbar-item:before{content:\"\";position:absolute;inset:0;background:var(--vl-sb-color-bg);pointer-events:none}.vl-snackbar-accent{position:absolute;left:0;top:.625rem;bottom:.625rem;width:3px;border-radius:0 2px 2px 0;background-color:var(--vl-sb-color)}.vl-snackbar-icon{flex-shrink:0;width:1.125rem;height:1.125rem;margin-top:.125rem;color:var(--vl-sb-color);position:relative}.vl-snackbar-icon svg{width:100%;height:100%;display:block}.vl-snackbar-body{flex:1;min-width:0;position:relative}.vl-snackbar-title{margin:0 0 .1875rem;font-size:.8125rem;font-weight:600;line-height:1.4;letter-spacing:-.01em;color:var(--color-card-foreground, hsl(210 15% 20%))}.vl-snackbar-message{margin:0;font-size:.8125rem;line-height:1.5;color:var(--color-muted-foreground, hsl(210 10% 50%))}.vl-snackbar-action-btn{display:inline-flex;align-items:center;margin-top:.5rem;padding:.25rem .625rem;font-size:.75rem;font-weight:500;border-radius:calc(var(--radius, .5rem));color:var(--vl-sb-color);background:color-mix(in srgb,var(--vl-sb-color) 10%,transparent);border:none;cursor:pointer;transition:background .15s ease}.vl-snackbar-action-btn:hover{background:color-mix(in srgb,var(--vl-sb-color) 18%,transparent)}.vl-snackbar-close{flex-shrink:0;width:1.5rem;height:1.5rem;display:flex;align-items:center;justify-content:center;border-radius:calc(var(--radius, .5rem));border:none;background:transparent;color:var(--color-muted-foreground, hsl(210 10% 50%));cursor:pointer;position:relative;transition:background .15s ease,color .15s ease;margin-top:-.0625rem}.vl-snackbar-close svg{width:.8125rem;height:.8125rem;display:block}.vl-snackbar-close:hover{background:var(--color-muted, hsl(210 10% 95%));color:var(--color-card-foreground, hsl(210 15% 20%))}.vl-snackbar-progress{position:absolute;bottom:0;left:0;height:2px;width:100%;background-color:color-mix(in srgb,var(--vl-sb-color) 45%,transparent);transform-origin:left center;animation:vl-snackbar-progress linear forwards}@keyframes vl-snackbar-enter{0%{opacity:0;transform:translate(calc(100% + 1.25rem))}to{opacity:1;transform:translate(0)}}@keyframes vl-snackbar-progress{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.vl-snackbar-exiting{animation:vl-snackbar-exit .3s ease-in forwards!important}@keyframes vl-snackbar-exit{0%{opacity:1;transform:translate(0);max-height:10rem;margin-bottom:0}to{opacity:0;transform:translate(calc(100% + 1.25rem));max-height:0;margin-bottom:-.5rem}}@media(max-width:480px){.vl-snackbar-host{bottom:.75rem;right:.75rem;left:.75rem;width:auto}}\n"], encapsulation: i0.ViewEncapsulation.None });
61
+ }
62
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnackbarComponent, decorators: [{
63
+ type: Component,
64
+ args: [{ selector: 'vl-snackbar', standalone: true, encapsulation: ViewEncapsulation.None, template: "<div class=\"vl-snackbar-host\" aria-live=\"polite\" aria-atomic=\"false\">\n @for (item of svc.items(); track item.id) {\n <div\n class=\"vl-snackbar-item\"\n [class]=\"'vl-snackbar-item vl-snackbar-' + item.type\"\n [class.vl-snackbar-exiting]=\"item.exiting\"\n role=\"alert\">\n\n <span class=\"vl-snackbar-accent\" aria-hidden=\"true\"></span>\n\n <span class=\"vl-snackbar-icon\" aria-hidden=\"true\">\n @switch (item.type) {\n @case ('success') {\n <svg viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5z\" clip-rule=\"evenodd\" />\n </svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z\" clip-rule=\"evenodd\" />\n </svg>\n }\n @default {\n <svg viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0zm-7-4a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM9 9a.75.75 0 0 0 0 1.5h.253a.25.25 0 0 1 .244.304l-.459 2.066A1.75 1.75 0 0 0 10.747 15H11a.75.75 0 0 0 0-1.5h-.253a.25.25 0 0 1-.244-.304l.459-2.066A1.75 1.75 0 0 0 9.253 9H9z\" clip-rule=\"evenodd\" />\n </svg>\n }\n }\n </span>\n\n <div class=\"vl-snackbar-body\">\n @if (item.title) {\n <p class=\"vl-snackbar-title\">{{ item.title }}</p>\n }\n <p class=\"vl-snackbar-message\">{{ item.message }}</p>\n @if (item.action) {\n <button class=\"vl-snackbar-action-btn\" (click)=\"handleAction(item)\">\n {{ item.action.label }}\n </button>\n }\n </div>\n\n @if (item.dismissible) {\n <button class=\"vl-snackbar-close\" aria-label=\"Dismiss\" (click)=\"svc.dismiss(item.id)\">\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06z\"/>\n </svg>\n </button>\n }\n\n @if (item.duration > 0) {\n <span\n class=\"vl-snackbar-progress\"\n [style.animation-duration.ms]=\"item.duration\"\n aria-hidden=\"true\">\n </span>\n }\n </div>\n }\n</div>\n", styles: [".vl-snackbar-host{position:fixed;bottom:1.25rem;right:1.25rem;z-index:9999;display:flex;flex-direction:column;gap:.5rem;width:21rem;max-width:calc(100vw - 2rem);pointer-events:none}.vl-snackbar-item{position:relative;display:flex;align-items:flex-start;gap:.625rem;padding:.875rem .875rem 1.125rem 1rem;border-radius:.75rem;overflow:hidden;pointer-events:all;background-color:var(--color-card, #ffffff);border:1px solid var(--color-border, hsl(210 15% 85%));box-shadow:0 1px 3px #0000000f,0 6px 20px #00000017;font-family:var(--font-family-sans, \"Inter\", sans-serif);animation:vl-snackbar-enter .34s cubic-bezier(.34,1.3,.64,1) forwards}.vl-snackbar-success{--vl-sb-color: var(--color-accent, hsl(150 60% 35%));--vl-sb-color-bg: color-mix(in srgb, var(--color-accent, hsl(150 60% 35%)) 8%, transparent)}.vl-snackbar-error{--vl-sb-color: var(--color-destructive, hsl(0 70% 50%));--vl-sb-color-bg: color-mix(in srgb, var(--color-destructive, hsl(0 70% 50%)) 8%, transparent)}.vl-snackbar-warning{--vl-sb-color: var(--color-secondary, hsl(15 90% 55%));--vl-sb-color-bg: color-mix(in srgb, var(--color-secondary, hsl(15 90% 55%)) 8%, transparent)}.vl-snackbar-info{--vl-sb-color: var(--color-primary, hsl(215 80% 25%));--vl-sb-color-bg: color-mix(in srgb, var(--color-primary, hsl(215 80% 25%)) 8%, transparent)}.vl-snackbar-item:before{content:\"\";position:absolute;inset:0;background:var(--vl-sb-color-bg);pointer-events:none}.vl-snackbar-accent{position:absolute;left:0;top:.625rem;bottom:.625rem;width:3px;border-radius:0 2px 2px 0;background-color:var(--vl-sb-color)}.vl-snackbar-icon{flex-shrink:0;width:1.125rem;height:1.125rem;margin-top:.125rem;color:var(--vl-sb-color);position:relative}.vl-snackbar-icon svg{width:100%;height:100%;display:block}.vl-snackbar-body{flex:1;min-width:0;position:relative}.vl-snackbar-title{margin:0 0 .1875rem;font-size:.8125rem;font-weight:600;line-height:1.4;letter-spacing:-.01em;color:var(--color-card-foreground, hsl(210 15% 20%))}.vl-snackbar-message{margin:0;font-size:.8125rem;line-height:1.5;color:var(--color-muted-foreground, hsl(210 10% 50%))}.vl-snackbar-action-btn{display:inline-flex;align-items:center;margin-top:.5rem;padding:.25rem .625rem;font-size:.75rem;font-weight:500;border-radius:calc(var(--radius, .5rem));color:var(--vl-sb-color);background:color-mix(in srgb,var(--vl-sb-color) 10%,transparent);border:none;cursor:pointer;transition:background .15s ease}.vl-snackbar-action-btn:hover{background:color-mix(in srgb,var(--vl-sb-color) 18%,transparent)}.vl-snackbar-close{flex-shrink:0;width:1.5rem;height:1.5rem;display:flex;align-items:center;justify-content:center;border-radius:calc(var(--radius, .5rem));border:none;background:transparent;color:var(--color-muted-foreground, hsl(210 10% 50%));cursor:pointer;position:relative;transition:background .15s ease,color .15s ease;margin-top:-.0625rem}.vl-snackbar-close svg{width:.8125rem;height:.8125rem;display:block}.vl-snackbar-close:hover{background:var(--color-muted, hsl(210 10% 95%));color:var(--color-card-foreground, hsl(210 15% 20%))}.vl-snackbar-progress{position:absolute;bottom:0;left:0;height:2px;width:100%;background-color:color-mix(in srgb,var(--vl-sb-color) 45%,transparent);transform-origin:left center;animation:vl-snackbar-progress linear forwards}@keyframes vl-snackbar-enter{0%{opacity:0;transform:translate(calc(100% + 1.25rem))}to{opacity:1;transform:translate(0)}}@keyframes vl-snackbar-progress{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.vl-snackbar-exiting{animation:vl-snackbar-exit .3s ease-in forwards!important}@keyframes vl-snackbar-exit{0%{opacity:1;transform:translate(0);max-height:10rem;margin-bottom:0}to{opacity:0;transform:translate(calc(100% + 1.25rem));max-height:0;margin-bottom:-.5rem}}@media(max-width:480px){.vl-snackbar-host{bottom:.75rem;right:.75rem;left:.75rem;width:auto}}\n"] }]
65
+ }] });
66
+
67
+ /**
68
+ * User avatar — shows image when available, falls back to initials.
69
+ *
70
+ * Usage:
71
+ * <vl-avatar name="Pranesh T" />
72
+ * <vl-avatar name="Pranesh T" [src]="user.photoUrl" size="lg" />
73
+ */
74
+ class AvatarComponent {
75
+ name = input('', ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
76
+ src = input(null, ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
77
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
78
+ imgError = false;
79
+ initials = computed(() => {
80
+ const parts = this.name().trim().split(/\s+/).filter(Boolean);
81
+ if (!parts.length)
82
+ return '?';
83
+ if (parts.length === 1)
84
+ return parts[0].slice(0, 2).toUpperCase();
85
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
86
+ }, ...(ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []));
87
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
88
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: AvatarComponent, isStandalone: true, selector: "vl-avatar", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
89
+ <div class="vl-avatar" [class]="'vl-avatar-' + size()">
90
+ @if (src() && !imgError) {
91
+ <img [src]="src()" [alt]="name()" (error)="imgError = true" />
92
+ } @else {
93
+ <span class="vl-avatar-initials">{{ initials() }}</span>
94
+ }
95
+ </div>
96
+ `, isInline: true, styles: [".vl-avatar{display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;overflow:hidden;background-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 15%,var(--color-muted, hsl(210 10% 95%)));border:1px solid color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 20%,transparent);flex-shrink:0}.vl-avatar img{width:100%;height:100%;object-fit:cover}.vl-avatar-initials{font-weight:600;color:var(--color-primary, hsl(215 80% 25%));font-family:var(--font-family-sans, inherit);line-height:1;-webkit-user-select:none;user-select:none}.vl-avatar-xs{width:1.5rem;height:1.5rem}.vl-avatar-xs .vl-avatar-initials{font-size:.5625rem}.vl-avatar-sm{width:2rem;height:2rem}.vl-avatar-sm .vl-avatar-initials{font-size:.6875rem}.vl-avatar-md{width:2.5rem;height:2.5rem}.vl-avatar-md .vl-avatar-initials{font-size:.875rem}.vl-avatar-lg{width:3rem;height:3rem}.vl-avatar-lg .vl-avatar-initials{font-size:1rem}.vl-avatar-xl{width:4rem;height:4rem}.vl-avatar-xl .vl-avatar-initials{font-size:1.25rem}\n"], encapsulation: i0.ViewEncapsulation.None });
97
+ }
98
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AvatarComponent, decorators: [{
99
+ type: Component,
100
+ args: [{ selector: 'vl-avatar', standalone: true, template: `
101
+ <div class="vl-avatar" [class]="'vl-avatar-' + size()">
102
+ @if (src() && !imgError) {
103
+ <img [src]="src()" [alt]="name()" (error)="imgError = true" />
104
+ } @else {
105
+ <span class="vl-avatar-initials">{{ initials() }}</span>
106
+ }
107
+ </div>
108
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-avatar{display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;overflow:hidden;background-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 15%,var(--color-muted, hsl(210 10% 95%)));border:1px solid color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 20%,transparent);flex-shrink:0}.vl-avatar img{width:100%;height:100%;object-fit:cover}.vl-avatar-initials{font-weight:600;color:var(--color-primary, hsl(215 80% 25%));font-family:var(--font-family-sans, inherit);line-height:1;-webkit-user-select:none;user-select:none}.vl-avatar-xs{width:1.5rem;height:1.5rem}.vl-avatar-xs .vl-avatar-initials{font-size:.5625rem}.vl-avatar-sm{width:2rem;height:2rem}.vl-avatar-sm .vl-avatar-initials{font-size:.6875rem}.vl-avatar-md{width:2.5rem;height:2.5rem}.vl-avatar-md .vl-avatar-initials{font-size:.875rem}.vl-avatar-lg{width:3rem;height:3rem}.vl-avatar-lg .vl-avatar-initials{font-size:1rem}.vl-avatar-xl{width:4rem;height:4rem}.vl-avatar-xl .vl-avatar-initials{font-size:1.25rem}\n"] }]
109
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
110
+
111
+ /**
112
+ * Colored pill badge with optional leading dot.
113
+ *
114
+ * Usage:
115
+ * <vl-badge variant="ongoing">Ongoing</vl-badge>
116
+ * <vl-badge [dot]="true" variant="planning">Planning</vl-badge>
117
+ * <vl-badge [color]="categoryColor" [bgColor]="categoryBg">Food</vl-badge>
118
+ */
119
+ class BadgeComponent {
120
+ variant = input('neutral', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
121
+ dot = input(false, ...(ngDevMode ? [{ debugName: "dot" }] : /* istanbul ignore next */ []));
122
+ /** Override color (text + dot). Takes precedence over variant. */
123
+ color = input('', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
124
+ /** Override background color. Takes precedence over variant. */
125
+ bgColor = input('', ...(ngDevMode ? [{ debugName: "bgColor" }] : /* istanbul ignore next */ []));
126
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: BadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
127
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: BadgeComponent, isStandalone: true, selector: "vl-badge", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, dot: { classPropertyName: "dot", publicName: "dot", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, bgColor: { classPropertyName: "bgColor", publicName: "bgColor", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
128
+ <span class="vl-badge" [class]="'vl-badge-' + variant()"
129
+ [style.color]="color() || null"
130
+ [style.background-color]="bgColor() || null"
131
+ [style.border-color]="color() ? color() + '33' : null">
132
+ @if (dot()) {
133
+ <span class="vl-badge-dot"
134
+ [style.background-color]="color() || null"></span>
135
+ }
136
+ <ng-content />
137
+ </span>
138
+ `, isInline: true, styles: [".vl-badge{display:inline-flex;align-items:center;gap:.375rem;padding:.25rem .625rem;border-radius:9999px;font-size:.75rem;font-weight:600;line-height:1;white-space:nowrap;border:1px solid transparent;font-family:var(--font-family-sans, inherit)}.vl-badge-dot{width:.375rem;height:.375rem;border-radius:9999px;flex-shrink:0}.vl-badge-planning{color:#1458b8;background-color:#1458b81a;border-color:#1458b833}.vl-badge-ongoing{color:#22774d;background-color:#22774d1a;border-color:#22774d33}.vl-badge-completed{color:#5c6670;background-color:#5c66701a;border-color:#5c667033}.vl-badge-success{color:var(--color-accent, hsl(150 60% 35%));background-color:color-mix(in srgb,var(--color-accent, hsl(150 60% 35%)) 10%,transparent);border-color:color-mix(in srgb,var(--color-accent, hsl(150 60% 35%)) 20%,transparent)}.vl-badge-warning{color:var(--color-secondary, hsl(15 90% 50%));background-color:color-mix(in srgb,var(--color-secondary, hsl(15 90% 50%)) 10%,transparent);border-color:color-mix(in srgb,var(--color-secondary, hsl(15 90% 50%)) 20%,transparent)}.vl-badge-danger{color:var(--color-destructive, hsl(0 70% 50%));background-color:color-mix(in srgb,var(--color-destructive, hsl(0 70% 50%)) 10%,transparent);border-color:color-mix(in srgb,var(--color-destructive, hsl(0 70% 50%)) 20%,transparent)}.vl-badge-info{color:var(--color-primary, hsl(215 80% 35%));background-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 35%)) 10%,transparent);border-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 35%)) 20%,transparent)}.vl-badge-neutral{color:var(--color-muted-foreground, hsl(210 10% 50%));background-color:var(--color-muted, hsl(210 10% 95%));border-color:var(--color-border, hsl(210 15% 85%))}\n"], encapsulation: i0.ViewEncapsulation.None });
139
+ }
140
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: BadgeComponent, decorators: [{
141
+ type: Component,
142
+ args: [{ selector: 'vl-badge', standalone: true, template: `
143
+ <span class="vl-badge" [class]="'vl-badge-' + variant()"
144
+ [style.color]="color() || null"
145
+ [style.background-color]="bgColor() || null"
146
+ [style.border-color]="color() ? color() + '33' : null">
147
+ @if (dot()) {
148
+ <span class="vl-badge-dot"
149
+ [style.background-color]="color() || null"></span>
150
+ }
151
+ <ng-content />
152
+ </span>
153
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-badge{display:inline-flex;align-items:center;gap:.375rem;padding:.25rem .625rem;border-radius:9999px;font-size:.75rem;font-weight:600;line-height:1;white-space:nowrap;border:1px solid transparent;font-family:var(--font-family-sans, inherit)}.vl-badge-dot{width:.375rem;height:.375rem;border-radius:9999px;flex-shrink:0}.vl-badge-planning{color:#1458b8;background-color:#1458b81a;border-color:#1458b833}.vl-badge-ongoing{color:#22774d;background-color:#22774d1a;border-color:#22774d33}.vl-badge-completed{color:#5c6670;background-color:#5c66701a;border-color:#5c667033}.vl-badge-success{color:var(--color-accent, hsl(150 60% 35%));background-color:color-mix(in srgb,var(--color-accent, hsl(150 60% 35%)) 10%,transparent);border-color:color-mix(in srgb,var(--color-accent, hsl(150 60% 35%)) 20%,transparent)}.vl-badge-warning{color:var(--color-secondary, hsl(15 90% 50%));background-color:color-mix(in srgb,var(--color-secondary, hsl(15 90% 50%)) 10%,transparent);border-color:color-mix(in srgb,var(--color-secondary, hsl(15 90% 50%)) 20%,transparent)}.vl-badge-danger{color:var(--color-destructive, hsl(0 70% 50%));background-color:color-mix(in srgb,var(--color-destructive, hsl(0 70% 50%)) 10%,transparent);border-color:color-mix(in srgb,var(--color-destructive, hsl(0 70% 50%)) 20%,transparent)}.vl-badge-info{color:var(--color-primary, hsl(215 80% 35%));background-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 35%)) 10%,transparent);border-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 35%)) 20%,transparent)}.vl-badge-neutral{color:var(--color-muted-foreground, hsl(210 10% 50%));background-color:var(--color-muted, hsl(210 10% 95%));border-color:var(--color-border, hsl(210 15% 85%))}\n"] }]
154
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], bgColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "bgColor", required: false }] }] } });
155
+
156
+ class ButtonComponent {
157
+ variant = input('primary', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
158
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
159
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
160
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
161
+ variantClass() {
162
+ return `vl-btn-${this.variant()}`;
163
+ }
164
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
165
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ButtonComponent, isStandalone: true, selector: "vl-button", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", 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: `
166
+ <button
167
+ class="vl-btn"
168
+ [class]="variantClass()"
169
+ [class.vl-btn-sm]="size() === 'sm'"
170
+ [class.vl-btn-md]="size() === 'md'"
171
+ [class.vl-btn-lg]="size() === 'lg'"
172
+ [class.vl-btn-loading]="loading()"
173
+ [disabled]="disabled() || loading()">
174
+
175
+ @if (loading()) {
176
+ <span class="vl-btn-spinner" aria-hidden="true">
177
+ <svg viewBox="0 0 24 24" fill="none">
178
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2.5" opacity="0.25"/>
179
+ <path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/>
180
+ </svg>
181
+ </span>
182
+ }
183
+
184
+ <ng-content />
185
+ </button>
186
+ `, isInline: true, styles: [".vl-btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;font-weight:500;border-radius:var(--radius, .5rem);border:1px solid transparent;cursor:pointer;transition:background-color .15s,color .15s,border-color .15s,opacity .15s,box-shadow .15s;white-space:nowrap;font-family:var(--font-family-sans, inherit);line-height:1}.vl-btn-sm{padding:.375rem .75rem;font-size:.8125rem}.vl-btn-md{padding:.5rem 1rem;font-size:.875rem}.vl-btn-lg{padding:.625rem 1.25rem;font-size:1rem}.vl-btn-primary{background-color:var(--color-primary, hsl(215 80% 25%));color:#fff}.vl-btn-primary:hover:not(:disabled){background-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 85%,#000)}.vl-btn-secondary{background-color:var(--color-secondary, hsl(15 90% 55%));color:#fff}.vl-btn-secondary:hover:not(:disabled){background-color:color-mix(in srgb,var(--color-secondary, hsl(15 90% 55%)) 85%,#000)}.vl-btn-danger{background-color:var(--color-destructive, hsl(0 70% 50%));color:#fff}.vl-btn-danger:hover:not(:disabled){background-color:color-mix(in srgb,var(--color-destructive, hsl(0 70% 50%)) 85%,#000)}.vl-btn-ghost{background-color:transparent;color:var(--color-foreground, #1f2937)}.vl-btn-ghost:hover:not(:disabled){background-color:var(--color-muted, hsl(210 10% 95%))}.vl-btn-outline{background-color:transparent;color:var(--color-primary, hsl(215 80% 25%));border-color:var(--color-primary, hsl(215 80% 25%))}.vl-btn-outline:hover:not(:disabled){background-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 8%,transparent)}.vl-btn:disabled{opacity:.5;cursor:not-allowed}.vl-btn-loading{cursor:wait}.vl-btn-spinner{display:inline-flex;width:1em;height:1em;animation:vl-btn-spin .8s linear infinite}.vl-btn-spinner svg{width:100%;height:100%}@keyframes vl-btn-spin{to{transform:rotate(360deg)}}\n"], encapsulation: i0.ViewEncapsulation.None });
187
+ }
188
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ButtonComponent, decorators: [{
189
+ type: Component,
190
+ args: [{ selector: 'vl-button', standalone: true, template: `
191
+ <button
192
+ class="vl-btn"
193
+ [class]="variantClass()"
194
+ [class.vl-btn-sm]="size() === 'sm'"
195
+ [class.vl-btn-md]="size() === 'md'"
196
+ [class.vl-btn-lg]="size() === 'lg'"
197
+ [class.vl-btn-loading]="loading()"
198
+ [disabled]="disabled() || loading()">
199
+
200
+ @if (loading()) {
201
+ <span class="vl-btn-spinner" aria-hidden="true">
202
+ <svg viewBox="0 0 24 24" fill="none">
203
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2.5" opacity="0.25"/>
204
+ <path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/>
205
+ </svg>
206
+ </span>
207
+ }
208
+
209
+ <ng-content />
210
+ </button>
211
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;font-weight:500;border-radius:var(--radius, .5rem);border:1px solid transparent;cursor:pointer;transition:background-color .15s,color .15s,border-color .15s,opacity .15s,box-shadow .15s;white-space:nowrap;font-family:var(--font-family-sans, inherit);line-height:1}.vl-btn-sm{padding:.375rem .75rem;font-size:.8125rem}.vl-btn-md{padding:.5rem 1rem;font-size:.875rem}.vl-btn-lg{padding:.625rem 1.25rem;font-size:1rem}.vl-btn-primary{background-color:var(--color-primary, hsl(215 80% 25%));color:#fff}.vl-btn-primary:hover:not(:disabled){background-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 85%,#000)}.vl-btn-secondary{background-color:var(--color-secondary, hsl(15 90% 55%));color:#fff}.vl-btn-secondary:hover:not(:disabled){background-color:color-mix(in srgb,var(--color-secondary, hsl(15 90% 55%)) 85%,#000)}.vl-btn-danger{background-color:var(--color-destructive, hsl(0 70% 50%));color:#fff}.vl-btn-danger:hover:not(:disabled){background-color:color-mix(in srgb,var(--color-destructive, hsl(0 70% 50%)) 85%,#000)}.vl-btn-ghost{background-color:transparent;color:var(--color-foreground, #1f2937)}.vl-btn-ghost:hover:not(:disabled){background-color:var(--color-muted, hsl(210 10% 95%))}.vl-btn-outline{background-color:transparent;color:var(--color-primary, hsl(215 80% 25%));border-color:var(--color-primary, hsl(215 80% 25%))}.vl-btn-outline:hover:not(:disabled){background-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 8%,transparent)}.vl-btn:disabled{opacity:.5;cursor:not-allowed}.vl-btn-loading{cursor:wait}.vl-btn-spinner{display:inline-flex;width:1em;height:1em;animation:vl-btn-spin .8s linear infinite}.vl-btn-spinner svg{width:100%;height:100%}@keyframes vl-btn-spin{to{transform:rotate(360deg)}}\n"] }]
212
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
213
+
214
+ /**
215
+ * Standard card wrapper. Optionally includes a section header.
216
+ *
217
+ * Usage:
218
+ * <vl-card>content</vl-card>
219
+ * <vl-card sectionTitle="Basic Info">content</vl-card>
220
+ * <vl-card [hover]="true" padding="none">content</vl-card>
221
+ */
222
+ class CardComponent {
223
+ padding = input('md', ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
224
+ hover = input(false, ...(ngDevMode ? [{ debugName: "hover" }] : /* istanbul ignore next */ []));
225
+ sectionTitle = input('', ...(ngDevMode ? [{ debugName: "sectionTitle" }] : /* istanbul ignore next */ []));
226
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
227
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: CardComponent, isStandalone: true, selector: "vl-card", inputs: { padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, hover: { classPropertyName: "hover", publicName: "hover", isSignal: true, isRequired: false, transformFunction: null }, sectionTitle: { classPropertyName: "sectionTitle", publicName: "sectionTitle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
228
+ <div class="vl-card"
229
+ [class.vl-card-hover]="hover()"
230
+ [class.vl-card-p-none]="padding() === 'none'"
231
+ [class.vl-card-p-sm]="padding() === 'sm'"
232
+ [class.vl-card-p-md]="padding() === 'md'"
233
+ [class.vl-card-p-lg]="padding() === 'lg'">
234
+
235
+ @if (sectionTitle()) {
236
+ <div class="vl-card-section-header">
237
+ <span>{{ sectionTitle() }}</span>
238
+ </div>
239
+ }
240
+
241
+ <ng-content />
242
+ </div>
243
+ `, isInline: true, styles: [".vl-card{background-color:var(--color-card, #fff);border:1px solid var(--color-border, hsl(210 15% 85%));border-radius:var(--radius-xl, .75rem);overflow:hidden;font-family:var(--font-family-sans, inherit)}.vl-card-hover{transition:box-shadow .2s,border-color .2s}.vl-card-hover:hover{box-shadow:0 1px 4px #0000000f,0 4px 16px #00000012;border-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 30%,var(--color-border, hsl(210 15% 85%)))}.vl-card-p-none{padding:0}.vl-card-p-sm{padding:.75rem}.vl-card-p-md{padding:1.25rem}.vl-card-p-lg{padding:1.5rem}.vl-card-section-header{padding:.75rem 1.5rem;border-bottom:1px solid var(--color-border, hsl(210 15% 85%));background-color:color-mix(in srgb,var(--color-muted, hsl(210 10% 95%)) 30%,transparent)}.vl-card-section-header span{font-size:.6875rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--color-muted-foreground, hsl(210 10% 50%))}\n"], encapsulation: i0.ViewEncapsulation.None });
244
+ }
245
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: CardComponent, decorators: [{
246
+ type: Component,
247
+ args: [{ selector: 'vl-card', standalone: true, template: `
248
+ <div class="vl-card"
249
+ [class.vl-card-hover]="hover()"
250
+ [class.vl-card-p-none]="padding() === 'none'"
251
+ [class.vl-card-p-sm]="padding() === 'sm'"
252
+ [class.vl-card-p-md]="padding() === 'md'"
253
+ [class.vl-card-p-lg]="padding() === 'lg'">
254
+
255
+ @if (sectionTitle()) {
256
+ <div class="vl-card-section-header">
257
+ <span>{{ sectionTitle() }}</span>
258
+ </div>
259
+ }
260
+
261
+ <ng-content />
262
+ </div>
263
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-card{background-color:var(--color-card, #fff);border:1px solid var(--color-border, hsl(210 15% 85%));border-radius:var(--radius-xl, .75rem);overflow:hidden;font-family:var(--font-family-sans, inherit)}.vl-card-hover{transition:box-shadow .2s,border-color .2s}.vl-card-hover:hover{box-shadow:0 1px 4px #0000000f,0 4px 16px #00000012;border-color:color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 30%,var(--color-border, hsl(210 15% 85%)))}.vl-card-p-none{padding:0}.vl-card-p-sm{padding:.75rem}.vl-card-p-md{padding:1.25rem}.vl-card-p-lg{padding:1.5rem}.vl-card-section-header{padding:.75rem 1.5rem;border-bottom:1px solid var(--color-border, hsl(210 15% 85%));background-color:color-mix(in srgb,var(--color-muted, hsl(210 10% 95%)) 30%,transparent)}.vl-card-section-header span{font-size:.6875rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--color-muted-foreground, hsl(210 10% 50%))}\n"] }]
264
+ }], propDecorators: { padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], hover: [{ type: i0.Input, args: [{ isSignal: true, alias: "hover", required: false }] }], sectionTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionTitle", required: false }] }] } });
265
+
266
+ class DialogComponent {
267
+ isOpen = input(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
268
+ title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
269
+ subtitle = input(null, ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
270
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
271
+ hasFooter = input(false, ...(ngDevMode ? [{ debugName: "hasFooter" }] : /* istanbul ignore next */ []));
272
+ closed = output();
273
+ renderer = inject(Renderer2);
274
+ constructor() {
275
+ effect(() => {
276
+ if (this.isOpen()) {
277
+ this.renderer.addClass(document.body, 'overflow-hidden');
278
+ }
279
+ else {
280
+ this.renderer.removeClass(document.body, 'overflow-hidden');
281
+ }
282
+ });
283
+ }
284
+ onEscape() {
285
+ if (this.isOpen())
286
+ this.closed.emit();
287
+ }
288
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
289
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DialogComponent, isStandalone: true, selector: "vl-dialog", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, hasFooter: { classPropertyName: "hasFooter", publicName: "hasFooter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (isOpen()) {\n <div class=\"vl-dialog-overlay\" role=\"dialog\" aria-modal=\"true\">\n\n <div class=\"vl-dialog-backdrop\" (click)=\"closed.emit()\"></div>\n\n <div class=\"vl-dialog-panel\"\n [class.vl-dialog-panel-sm]=\"size() === 'sm'\"\n [class.vl-dialog-panel-md]=\"size() === 'md'\"\n [class.vl-dialog-panel-lg]=\"size() === 'lg'\">\n\n <div class=\"vl-dialog-header\">\n <div>\n <h2>{{ title() }}</h2>\n @if (subtitle()) {\n <p>{{ subtitle() }}</p>\n }\n </div>\n <button (click)=\"closed.emit()\" class=\"vl-dialog-close\" aria-label=\"Close\">\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n </div>\n\n <div class=\"vl-dialog-body\">\n <ng-content />\n </div>\n\n @if (hasFooter()) {\n <div class=\"vl-dialog-footer\">\n <ng-content select=\"[dialog-footer]\" />\n </div>\n }\n\n </div>\n </div>\n}\n", styles: [".vl-dialog-overlay{position:fixed;inset:0;z-index:50;display:flex;align-items:flex-end;justify-content:center;padding:0}.vl-dialog-backdrop{position:absolute;inset:0;background-color:#0006;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.vl-dialog-panel{position:relative;z-index:10;width:100%;max-height:92dvh;display:flex;flex-direction:column;background-color:var(--color-background, #fff);border-radius:1rem 1rem 0 0;box-shadow:0 25px 50px -12px #00000040;border:1px solid var(--color-border, #d1d5db);overflow:hidden}.vl-dialog-panel-sm{max-width:32rem}.vl-dialog-panel-md{max-width:42rem}.vl-dialog-panel-lg{max-width:48rem}.vl-dialog-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.5rem;border-bottom:1px solid var(--color-border, #d1d5db);flex-shrink:0}.vl-dialog-header h2{font-size:1rem;font-weight:600;color:var(--color-foreground, #1f2937)}.vl-dialog-header p{font-size:.75rem;color:var(--color-muted-foreground, #6b7280);margin-top:.125rem}.vl-dialog-close{width:2rem;height:2rem;display:flex;align-items:center;justify-content:center;border-radius:.5rem;color:var(--color-muted-foreground, #6b7280);background:transparent;border:none;cursor:pointer;transition:background-color .15s,color .15s}.vl-dialog-close:hover{background-color:var(--color-muted, #f3f4f6);color:var(--color-foreground, #1f2937)}.vl-dialog-body{flex:1;overflow-y:auto}.vl-dialog-footer{flex-shrink:0;border-top:1px solid var(--color-border, #d1d5db);background-color:color-mix(in srgb,var(--color-muted, #f3f4f6) 20%,transparent)}@media(min-width:640px){.vl-dialog-overlay{align-items:center;padding:1rem}.vl-dialog-panel{border-radius:1rem;max-height:88vh}}\n"], encapsulation: i0.ViewEncapsulation.None });
290
+ }
291
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DialogComponent, decorators: [{
292
+ type: Component,
293
+ args: [{ selector: 'vl-dialog', standalone: true, encapsulation: ViewEncapsulation.None, template: "@if (isOpen()) {\n <div class=\"vl-dialog-overlay\" role=\"dialog\" aria-modal=\"true\">\n\n <div class=\"vl-dialog-backdrop\" (click)=\"closed.emit()\"></div>\n\n <div class=\"vl-dialog-panel\"\n [class.vl-dialog-panel-sm]=\"size() === 'sm'\"\n [class.vl-dialog-panel-md]=\"size() === 'md'\"\n [class.vl-dialog-panel-lg]=\"size() === 'lg'\">\n\n <div class=\"vl-dialog-header\">\n <div>\n <h2>{{ title() }}</h2>\n @if (subtitle()) {\n <p>{{ subtitle() }}</p>\n }\n </div>\n <button (click)=\"closed.emit()\" class=\"vl-dialog-close\" aria-label=\"Close\">\n <svg class=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n </div>\n\n <div class=\"vl-dialog-body\">\n <ng-content />\n </div>\n\n @if (hasFooter()) {\n <div class=\"vl-dialog-footer\">\n <ng-content select=\"[dialog-footer]\" />\n </div>\n }\n\n </div>\n </div>\n}\n", styles: [".vl-dialog-overlay{position:fixed;inset:0;z-index:50;display:flex;align-items:flex-end;justify-content:center;padding:0}.vl-dialog-backdrop{position:absolute;inset:0;background-color:#0006;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.vl-dialog-panel{position:relative;z-index:10;width:100%;max-height:92dvh;display:flex;flex-direction:column;background-color:var(--color-background, #fff);border-radius:1rem 1rem 0 0;box-shadow:0 25px 50px -12px #00000040;border:1px solid var(--color-border, #d1d5db);overflow:hidden}.vl-dialog-panel-sm{max-width:32rem}.vl-dialog-panel-md{max-width:42rem}.vl-dialog-panel-lg{max-width:48rem}.vl-dialog-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.5rem;border-bottom:1px solid var(--color-border, #d1d5db);flex-shrink:0}.vl-dialog-header h2{font-size:1rem;font-weight:600;color:var(--color-foreground, #1f2937)}.vl-dialog-header p{font-size:.75rem;color:var(--color-muted-foreground, #6b7280);margin-top:.125rem}.vl-dialog-close{width:2rem;height:2rem;display:flex;align-items:center;justify-content:center;border-radius:.5rem;color:var(--color-muted-foreground, #6b7280);background:transparent;border:none;cursor:pointer;transition:background-color .15s,color .15s}.vl-dialog-close:hover{background-color:var(--color-muted, #f3f4f6);color:var(--color-foreground, #1f2937)}.vl-dialog-body{flex:1;overflow-y:auto}.vl-dialog-footer{flex-shrink:0;border-top:1px solid var(--color-border, #d1d5db);background-color:color-mix(in srgb,var(--color-muted, #f3f4f6) 20%,transparent)}@media(min-width:640px){.vl-dialog-overlay{align-items:center;padding:1rem}.vl-dialog-panel{border-radius:1rem;max-height:88vh}}\n"] }]
294
+ }], ctorParameters: () => [], propDecorators: { isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], hasFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasFooter", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], onEscape: [{
295
+ type: HostListener,
296
+ args: ['document:keydown.escape']
297
+ }] } });
298
+
299
+ /**
300
+ * Pre-built confirmation dialog (wraps vl-dialog).
301
+ *
302
+ * Usage:
303
+ * <vl-confirm-dialog
304
+ * [isOpen]="showDelete()"
305
+ * title="Delete Expense"
306
+ * message="This action cannot be undone."
307
+ * confirmLabel="Delete"
308
+ * confirmVariant="danger"
309
+ * [loading]="isDeleting()"
310
+ * (confirmed)="onDelete()"
311
+ * (cancelled)="showDelete.set(false)" />
312
+ */
313
+ class ConfirmDialogComponent {
314
+ isOpen = input(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
315
+ title = input('Are you sure?', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
316
+ subtitle = input(null, ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
317
+ message = input('', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
318
+ confirmLabel = input('Confirm', ...(ngDevMode ? [{ debugName: "confirmLabel" }] : /* istanbul ignore next */ []));
319
+ cancelLabel = input('Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
320
+ confirmVariant = input('danger', ...(ngDevMode ? [{ debugName: "confirmVariant" }] : /* istanbul ignore next */ []));
321
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
322
+ confirmed = output();
323
+ cancelled = output();
324
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ConfirmDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
325
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.5", type: ConfirmDialogComponent, isStandalone: true, selector: "vl-confirm-dialog", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, confirmLabel: { classPropertyName: "confirmLabel", publicName: "confirmLabel", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, confirmVariant: { classPropertyName: "confirmVariant", publicName: "confirmVariant", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { confirmed: "confirmed", cancelled: "cancelled" }, ngImport: i0, template: `
326
+ <vl-dialog
327
+ [isOpen]="isOpen()"
328
+ [title]="title()"
329
+ [subtitle]="subtitle()"
330
+ size="sm"
331
+ [hasFooter]="true"
332
+ (closed)="cancelled.emit()">
333
+
334
+ <div class="vl-confirm-body">
335
+ <p class="vl-confirm-message">{{ message() }}</p>
336
+ <ng-content />
337
+ </div>
338
+
339
+ <div dialog-footer class="vl-confirm-footer">
340
+ <vl-button variant="ghost" [disabled]="loading()" (click)="cancelled.emit()">
341
+ {{ cancelLabel() }}
342
+ </vl-button>
343
+ <vl-button [variant]="confirmVariant()" [loading]="loading()" (click)="confirmed.emit()">
344
+ {{ confirmLabel() }}
345
+ </vl-button>
346
+ </div>
347
+ </vl-dialog>
348
+ `, isInline: true, styles: [".vl-confirm-body{padding:1.5rem}.vl-confirm-message{font-size:.875rem;color:var(--color-muted-foreground, hsl(210 10% 50%));line-height:1.6;margin:0;font-family:var(--font-family-sans, inherit)}.vl-confirm-footer{display:flex;align-items:center;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem}\n"], dependencies: [{ kind: "component", type: DialogComponent, selector: "vl-dialog", inputs: ["isOpen", "title", "subtitle", "size", "hasFooter"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "vl-button", inputs: ["variant", "size", "loading", "disabled"] }], encapsulation: i0.ViewEncapsulation.None });
349
+ }
350
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ConfirmDialogComponent, decorators: [{
351
+ type: Component,
352
+ args: [{ selector: 'vl-confirm-dialog', standalone: true, imports: [DialogComponent, ButtonComponent], template: `
353
+ <vl-dialog
354
+ [isOpen]="isOpen()"
355
+ [title]="title()"
356
+ [subtitle]="subtitle()"
357
+ size="sm"
358
+ [hasFooter]="true"
359
+ (closed)="cancelled.emit()">
360
+
361
+ <div class="vl-confirm-body">
362
+ <p class="vl-confirm-message">{{ message() }}</p>
363
+ <ng-content />
364
+ </div>
365
+
366
+ <div dialog-footer class="vl-confirm-footer">
367
+ <vl-button variant="ghost" [disabled]="loading()" (click)="cancelled.emit()">
368
+ {{ cancelLabel() }}
369
+ </vl-button>
370
+ <vl-button [variant]="confirmVariant()" [loading]="loading()" (click)="confirmed.emit()">
371
+ {{ confirmLabel() }}
372
+ </vl-button>
373
+ </div>
374
+ </vl-dialog>
375
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-confirm-body{padding:1.5rem}.vl-confirm-message{font-size:.875rem;color:var(--color-muted-foreground, hsl(210 10% 50%));line-height:1.6;margin:0;font-family:var(--font-family-sans, inherit)}.vl-confirm-footer{display:flex;align-items:center;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem}\n"] }]
376
+ }], propDecorators: { isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], confirmLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmLabel", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], confirmVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmVariant", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
377
+
378
+ /**
379
+ * Dashed-border empty state card. Project an action button into the default slot.
380
+ *
381
+ * Usage:
382
+ * <vl-empty-state
383
+ * icon="M15 10.5a3 3 0 11-6 0..."
384
+ * title="No trips yet"
385
+ * description="Create your first trip to get started.">
386
+ * <button (click)="create()">Create Trip</button>
387
+ * </vl-empty-state>
388
+ */
389
+ class EmptyStateComponent {
390
+ /** SVG path `d` attribute for the icon. Or project via <svg empty-icon>. */
391
+ icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
392
+ title = input('Nothing here yet', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
393
+ description = input('', ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
394
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: EmptyStateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
395
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: EmptyStateComponent, isStandalone: true, selector: "vl-empty-state", inputs: { icon: { classPropertyName: "icon", publicName: "icon", 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 } }, ngImport: i0, template: `
396
+ <div class="vl-empty-state">
397
+ <div class="vl-empty-state-icon">
398
+ @if (icon()) {
399
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
400
+ stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
401
+ <path [attr.d]="icon()" />
402
+ </svg>
403
+ } @else {
404
+ <ng-content select="[empty-icon]" />
405
+ }
406
+ </div>
407
+ <h3 class="vl-empty-state-title">{{ title() }}</h3>
408
+ @if (description()) {
409
+ <p class="vl-empty-state-description">{{ description() }}</p>
410
+ }
411
+ <div class="vl-empty-state-action">
412
+ <ng-content />
413
+ </div>
414
+ </div>
415
+ `, isInline: true, styles: [".vl-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:4rem 1.5rem;background-color:var(--color-card, #fff);border:1px dashed var(--color-border, hsl(210 15% 85%));border-radius:var(--radius-xl, .75rem);text-align:center;font-family:var(--font-family-sans, inherit)}.vl-empty-state-icon{width:3rem;height:3rem;border-radius:9999px;background-color:var(--color-muted, hsl(210 10% 95%));display:flex;align-items:center;justify-content:center;margin-bottom:1rem}.vl-empty-state-icon svg{width:1.5rem;height:1.5rem;color:var(--color-muted-foreground, hsl(210 10% 50%))}.vl-empty-state-title{font-size:.9375rem;font-weight:600;color:var(--color-foreground, #1f2937);margin:0 0 .375rem}.vl-empty-state-description{font-size:.875rem;color:var(--color-muted-foreground, hsl(210 10% 50%));margin:0;max-width:20rem;line-height:1.5}.vl-empty-state-action{margin-top:1.5rem;display:flex;gap:.75rem;flex-wrap:wrap;justify-content:center}.vl-empty-state-action:empty{display:none}\n"], encapsulation: i0.ViewEncapsulation.None });
416
+ }
417
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: EmptyStateComponent, decorators: [{
418
+ type: Component,
419
+ args: [{ selector: 'vl-empty-state', standalone: true, template: `
420
+ <div class="vl-empty-state">
421
+ <div class="vl-empty-state-icon">
422
+ @if (icon()) {
423
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
424
+ stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
425
+ <path [attr.d]="icon()" />
426
+ </svg>
427
+ } @else {
428
+ <ng-content select="[empty-icon]" />
429
+ }
430
+ </div>
431
+ <h3 class="vl-empty-state-title">{{ title() }}</h3>
432
+ @if (description()) {
433
+ <p class="vl-empty-state-description">{{ description() }}</p>
434
+ }
435
+ <div class="vl-empty-state-action">
436
+ <ng-content />
437
+ </div>
438
+ </div>
439
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:4rem 1.5rem;background-color:var(--color-card, #fff);border:1px dashed var(--color-border, hsl(210 15% 85%));border-radius:var(--radius-xl, .75rem);text-align:center;font-family:var(--font-family-sans, inherit)}.vl-empty-state-icon{width:3rem;height:3rem;border-radius:9999px;background-color:var(--color-muted, hsl(210 10% 95%));display:flex;align-items:center;justify-content:center;margin-bottom:1rem}.vl-empty-state-icon svg{width:1.5rem;height:1.5rem;color:var(--color-muted-foreground, hsl(210 10% 50%))}.vl-empty-state-title{font-size:.9375rem;font-weight:600;color:var(--color-foreground, #1f2937);margin:0 0 .375rem}.vl-empty-state-description{font-size:.875rem;color:var(--color-muted-foreground, hsl(210 10% 50%));margin:0;max-width:20rem;line-height:1.5}.vl-empty-state-action{margin-top:1.5rem;display:flex;gap:.75rem;flex-wrap:wrap;justify-content:center}.vl-empty-state-action:empty{display:none}\n"] }]
440
+ }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }] } });
441
+
442
+ /**
443
+ * Label + error wrapper for native inputs. Use content projection for the
444
+ * actual <input> so reactive forms work without ControlValueAccessor.
445
+ *
446
+ * Usage:
447
+ * <vl-input label="Trip Name" [error]="nameControl.errors && nameControl.touched ? 'Required' : null">
448
+ * <input type="text" formControlName="name" placeholder="Paris Weekend" />
449
+ * </vl-input>
450
+ */
451
+ class InputComponent {
452
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
453
+ error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
454
+ hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
455
+ prefix = input('', ...(ngDevMode ? [{ debugName: "prefix" }] : /* istanbul ignore next */ []));
456
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
457
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: InputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
458
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: InputComponent, isStandalone: true, selector: "vl-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, prefix: { classPropertyName: "prefix", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
459
+ <div class="vl-input-root">
460
+ @if (label()) {
461
+ <label class="vl-input-label" [class.vl-input-label-required]="required()">
462
+ {{ label() }}
463
+ </label>
464
+ }
465
+ <div class="vl-input-field" [class.vl-input-field-error]="!!error()">
466
+ @if (prefix()) {
467
+ <span class="vl-input-prefix">{{ prefix() }}</span>
468
+ }
469
+ <ng-content />
470
+ </div>
471
+ @if (error()) {
472
+ <p class="vl-input-error">{{ error() }}</p>
473
+ } @else if (hint()) {
474
+ <p class="vl-input-hint">{{ hint() }}</p>
475
+ }
476
+ </div>
477
+ `, isInline: true, styles: [".vl-input-root{display:flex;flex-direction:column;gap:.375rem;font-family:var(--font-family-sans, inherit)}.vl-input-label{display:block;font-size:.875rem;font-weight:500;color:var(--color-foreground, #1f2937)}.vl-input-label-required:after{content:\" *\";color:var(--color-destructive, hsl(0 70% 50%))}.vl-input-field{display:flex;align-items:center;border:1px solid var(--color-border, hsl(210 15% 85%));border-radius:var(--radius, .5rem);background-color:var(--color-background, #fff);transition:border-color .15s,box-shadow .15s}.vl-input-field:focus-within{border-color:var(--color-primary, hsl(215 80% 25%));box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 15%,transparent)}.vl-input-field-error{border-color:var(--color-destructive, hsl(0 70% 50%))}.vl-input-field-error:focus-within{border-color:var(--color-destructive, hsl(0 70% 50%));box-shadow:0 0 0 3px color-mix(in srgb,var(--color-destructive, hsl(0 70% 50%)) 15%,transparent)}.vl-input-prefix{padding-left:.875rem;font-size:.875rem;font-weight:600;color:var(--color-muted-foreground, hsl(210 10% 50%));-webkit-user-select:none;user-select:none;flex-shrink:0}.vl-input-field ::slotted(input),.vl-input-field input{width:100%;padding:.625rem .875rem;font-size:.875rem;color:var(--color-foreground, #1f2937);background:transparent;border:none;outline:none;font-family:inherit;min-width:0}.vl-input-field input::placeholder{color:color-mix(in srgb,var(--color-muted-foreground, hsl(210 10% 50%)) 60%,transparent)}.vl-input-error{font-size:.75rem;color:var(--color-destructive, hsl(0 70% 50%));margin:0}.vl-input-hint{font-size:.75rem;color:var(--color-muted-foreground, hsl(210 10% 50%));margin:0}\n"], encapsulation: i0.ViewEncapsulation.None });
478
+ }
479
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: InputComponent, decorators: [{
480
+ type: Component,
481
+ args: [{ selector: 'vl-input', standalone: true, template: `
482
+ <div class="vl-input-root">
483
+ @if (label()) {
484
+ <label class="vl-input-label" [class.vl-input-label-required]="required()">
485
+ {{ label() }}
486
+ </label>
487
+ }
488
+ <div class="vl-input-field" [class.vl-input-field-error]="!!error()">
489
+ @if (prefix()) {
490
+ <span class="vl-input-prefix">{{ prefix() }}</span>
491
+ }
492
+ <ng-content />
493
+ </div>
494
+ @if (error()) {
495
+ <p class="vl-input-error">{{ error() }}</p>
496
+ } @else if (hint()) {
497
+ <p class="vl-input-hint">{{ hint() }}</p>
498
+ }
499
+ </div>
500
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-input-root{display:flex;flex-direction:column;gap:.375rem;font-family:var(--font-family-sans, inherit)}.vl-input-label{display:block;font-size:.875rem;font-weight:500;color:var(--color-foreground, #1f2937)}.vl-input-label-required:after{content:\" *\";color:var(--color-destructive, hsl(0 70% 50%))}.vl-input-field{display:flex;align-items:center;border:1px solid var(--color-border, hsl(210 15% 85%));border-radius:var(--radius, .5rem);background-color:var(--color-background, #fff);transition:border-color .15s,box-shadow .15s}.vl-input-field:focus-within{border-color:var(--color-primary, hsl(215 80% 25%));box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 15%,transparent)}.vl-input-field-error{border-color:var(--color-destructive, hsl(0 70% 50%))}.vl-input-field-error:focus-within{border-color:var(--color-destructive, hsl(0 70% 50%));box-shadow:0 0 0 3px color-mix(in srgb,var(--color-destructive, hsl(0 70% 50%)) 15%,transparent)}.vl-input-prefix{padding-left:.875rem;font-size:.875rem;font-weight:600;color:var(--color-muted-foreground, hsl(210 10% 50%));-webkit-user-select:none;user-select:none;flex-shrink:0}.vl-input-field ::slotted(input),.vl-input-field input{width:100%;padding:.625rem .875rem;font-size:.875rem;color:var(--color-foreground, #1f2937);background:transparent;border:none;outline:none;font-family:inherit;min-width:0}.vl-input-field input::placeholder{color:color-mix(in srgb,var(--color-muted-foreground, hsl(210 10% 50%)) 60%,transparent)}.vl-input-error{font-size:.75rem;color:var(--color-destructive, hsl(0 70% 50%));margin:0}.vl-input-hint{font-size:.75rem;color:var(--color-muted-foreground, hsl(210 10% 50%));margin:0}\n"] }]
501
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], prefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefix", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }] } });
502
+
503
+ class LoaderComponent {
504
+ message = input('', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
505
+ fullPage = input(false, ...(ngDevMode ? [{ debugName: "fullPage" }] : /* istanbul ignore next */ []));
506
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: LoaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
507
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: LoaderComponent, isStandalone: true, selector: "vl-loader", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, fullPage: { classPropertyName: "fullPage", publicName: "fullPage", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
508
+ <div class="vl-loader" [class.vl-loader-fullpage]="fullPage()">
509
+ <div class="vl-loader-spinner">
510
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
511
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2.5" opacity="0.2" />
512
+ <path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" />
513
+ </svg>
514
+ </div>
515
+ @if (message()) {
516
+ <p class="vl-loader-message">{{ message() }}</p>
517
+ }
518
+ </div>
519
+ `, isInline: true, styles: [".vl-loader{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;padding:3rem 1.5rem}.vl-loader-fullpage{min-height:16rem}.vl-loader-spinner{width:2rem;height:2rem;color:var(--color-primary, #6366f1);animation:vl-spin .8s linear infinite}.vl-loader-spinner svg{width:100%;height:100%}.vl-loader-message{font-size:.875rem;color:var(--color-muted-foreground, #6b7280);margin:0}@keyframes vl-spin{to{transform:rotate(360deg)}}\n"], encapsulation: i0.ViewEncapsulation.None });
520
+ }
521
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: LoaderComponent, decorators: [{
522
+ type: Component,
523
+ args: [{ selector: 'vl-loader', standalone: true, template: `
524
+ <div class="vl-loader" [class.vl-loader-fullpage]="fullPage()">
525
+ <div class="vl-loader-spinner">
526
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
527
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2.5" opacity="0.2" />
528
+ <path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" />
529
+ </svg>
530
+ </div>
531
+ @if (message()) {
532
+ <p class="vl-loader-message">{{ message() }}</p>
533
+ }
534
+ </div>
535
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-loader{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;padding:3rem 1.5rem}.vl-loader-fullpage{min-height:16rem}.vl-loader-spinner{width:2rem;height:2rem;color:var(--color-primary, #6366f1);animation:vl-spin .8s linear infinite}.vl-loader-spinner svg{width:100%;height:100%}.vl-loader-message{font-size:.875rem;color:var(--color-muted-foreground, #6b7280);margin:0}@keyframes vl-spin{to{transform:rotate(360deg)}}\n"] }]
536
+ }], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], fullPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullPage", required: false }] }] } });
537
+
538
+ /**
539
+ * Three-segment password strength indicator.
540
+ *
541
+ * Usage:
542
+ * <vl-password-strength [strength]="passwordStrength()" />
543
+ */
544
+ class PasswordStrengthComponent {
545
+ strength = input(null, ...(ngDevMode ? [{ debugName: "strength" }] : /* istanbul ignore next */ []));
546
+ showLabel = input(true, ...(ngDevMode ? [{ debugName: "showLabel" }] : /* istanbul ignore next */ []));
547
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: PasswordStrengthComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
548
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: PasswordStrengthComponent, isStandalone: true, selector: "vl-password-strength", inputs: { strength: { classPropertyName: "strength", publicName: "strength", isSignal: true, isRequired: false, transformFunction: null }, showLabel: { classPropertyName: "showLabel", publicName: "showLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
549
+ <div class="vl-pw-root">
550
+ <div class="vl-pw-bars">
551
+ <div class="vl-pw-bar"
552
+ [class.vl-pw-bar-weak]="strength() === 'weak'"
553
+ [class.vl-pw-bar-inactive]="!strength()"></div>
554
+ <div class="vl-pw-bar"
555
+ [class.vl-pw-bar-medium]="strength() === 'medium'"
556
+ [class.vl-pw-bar-strong]="strength() === 'strong'"
557
+ [class.vl-pw-bar-inactive]="strength() !== 'medium' && strength() !== 'strong'"></div>
558
+ <div class="vl-pw-bar"
559
+ [class.vl-pw-bar-strong]="strength() === 'strong'"
560
+ [class.vl-pw-bar-inactive]="strength() !== 'strong'"></div>
561
+ </div>
562
+ @if (showLabel() && strength()) {
563
+ <span class="vl-pw-label" [class]="'vl-pw-label-' + strength()">
564
+ {{ strength() | titlecase }}
565
+ </span>
566
+ }
567
+ </div>
568
+ `, isInline: true, styles: [".vl-pw-root{display:flex;align-items:center;gap:.625rem;font-family:var(--font-family-sans, inherit)}.vl-pw-bars{display:flex;flex:1;gap:.25rem;height:.375rem}.vl-pw-bar{flex:1;border-radius:9999px;transition:background-color .3s ease;background-color:var(--color-border, hsl(210 15% 85%))}.vl-pw-bar-inactive{background-color:var(--color-border, hsl(210 15% 85%))}.vl-pw-bar-weak{background-color:var(--color-destructive, hsl(0 70% 50%))}.vl-pw-bar-medium{background-color:#f2b90d}.vl-pw-bar-strong{background-color:var(--color-accent, hsl(150 60% 35%))}.vl-pw-label{font-size:.6875rem;font-weight:600;white-space:nowrap}.vl-pw-label-weak{color:var(--color-destructive, hsl(0 70% 50%))}.vl-pw-label-medium{color:#c2940a}.vl-pw-label-strong{color:var(--color-accent, hsl(150 60% 35%))}\n"], dependencies: [{ kind: "pipe", type: TitleCasePipe, name: "titlecase" }], encapsulation: i0.ViewEncapsulation.None });
569
+ }
570
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: PasswordStrengthComponent, decorators: [{
571
+ type: Component,
572
+ args: [{ selector: 'vl-password-strength', standalone: true, imports: [TitleCasePipe], template: `
573
+ <div class="vl-pw-root">
574
+ <div class="vl-pw-bars">
575
+ <div class="vl-pw-bar"
576
+ [class.vl-pw-bar-weak]="strength() === 'weak'"
577
+ [class.vl-pw-bar-inactive]="!strength()"></div>
578
+ <div class="vl-pw-bar"
579
+ [class.vl-pw-bar-medium]="strength() === 'medium'"
580
+ [class.vl-pw-bar-strong]="strength() === 'strong'"
581
+ [class.vl-pw-bar-inactive]="strength() !== 'medium' && strength() !== 'strong'"></div>
582
+ <div class="vl-pw-bar"
583
+ [class.vl-pw-bar-strong]="strength() === 'strong'"
584
+ [class.vl-pw-bar-inactive]="strength() !== 'strong'"></div>
585
+ </div>
586
+ @if (showLabel() && strength()) {
587
+ <span class="vl-pw-label" [class]="'vl-pw-label-' + strength()">
588
+ {{ strength() | titlecase }}
589
+ </span>
590
+ }
591
+ </div>
592
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-pw-root{display:flex;align-items:center;gap:.625rem;font-family:var(--font-family-sans, inherit)}.vl-pw-bars{display:flex;flex:1;gap:.25rem;height:.375rem}.vl-pw-bar{flex:1;border-radius:9999px;transition:background-color .3s ease;background-color:var(--color-border, hsl(210 15% 85%))}.vl-pw-bar-inactive{background-color:var(--color-border, hsl(210 15% 85%))}.vl-pw-bar-weak{background-color:var(--color-destructive, hsl(0 70% 50%))}.vl-pw-bar-medium{background-color:#f2b90d}.vl-pw-bar-strong{background-color:var(--color-accent, hsl(150 60% 35%))}.vl-pw-label{font-size:.6875rem;font-weight:600;white-space:nowrap}.vl-pw-label-weak{color:var(--color-destructive, hsl(0 70% 50%))}.vl-pw-label-medium{color:#c2940a}.vl-pw-label-strong{color:var(--color-accent, hsl(150 60% 35%))}\n"] }]
593
+ }], propDecorators: { strength: [{ type: i0.Input, args: [{ isSignal: true, alias: "strength", required: false }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }] } });
594
+
595
+ /**
596
+ * Horizontal progress bar with optional label and percentage text.
597
+ *
598
+ * Usage:
599
+ * <vl-progress-bar [value]="75" />
600
+ * <vl-progress-bar [value]="budgetUsed" variant="auto" [showLabel]="true" />
601
+ */
602
+ class ProgressBarComponent {
603
+ value = input(0, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
604
+ variant = input('auto', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
605
+ showLabel = input(false, ...(ngDevMode ? [{ debugName: "showLabel" }] : /* istanbul ignore next */ []));
606
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
607
+ clampedValue = computed(() => Math.min(100, Math.max(0, this.value())), ...(ngDevMode ? [{ debugName: "clampedValue" }] : /* istanbul ignore next */ []));
608
+ resolvedVariant = computed(() => {
609
+ if (this.variant() !== 'auto')
610
+ return this.variant();
611
+ const v = this.clampedValue();
612
+ if (v >= 90)
613
+ return 'danger';
614
+ if (v >= 75)
615
+ return 'warning';
616
+ return 'primary';
617
+ }, ...(ngDevMode ? [{ debugName: "resolvedVariant" }] : /* istanbul ignore next */ []));
618
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ProgressBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
619
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ProgressBarComponent, isStandalone: true, selector: "vl-progress-bar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, showLabel: { classPropertyName: "showLabel", publicName: "showLabel", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
620
+ <div class="vl-progress-root">
621
+ @if (label()) {
622
+ <div class="vl-progress-header">
623
+ <span class="vl-progress-label">{{ label() }}</span>
624
+ @if (showLabel()) {
625
+ <span class="vl-progress-pct">{{ clampedValue() }}%</span>
626
+ }
627
+ </div>
628
+ } @else if (showLabel()) {
629
+ <div class="vl-progress-header vl-progress-header-end">
630
+ <span class="vl-progress-pct">{{ clampedValue() }}%</span>
631
+ </div>
632
+ }
633
+ <div class="vl-progress-track">
634
+ <div class="vl-progress-fill"
635
+ [class]="'vl-progress-fill-' + resolvedVariant()"
636
+ [style.width.%]="clampedValue()">
637
+ </div>
638
+ </div>
639
+ </div>
640
+ `, isInline: true, styles: [".vl-progress-root{display:flex;flex-direction:column;gap:.375rem}.vl-progress-header{display:flex;align-items:center;justify-content:space-between}.vl-progress-header-end{justify-content:flex-end}.vl-progress-label{font-size:.75rem;font-weight:500;color:var(--color-muted-foreground, hsl(210 10% 50%));font-family:var(--font-family-sans, inherit)}.vl-progress-pct{font-size:.6875rem;font-weight:500;color:var(--color-muted-foreground, hsl(210 10% 50%));font-family:var(--font-family-sans, inherit)}.vl-progress-track{height:.375rem;background-color:var(--color-border, hsl(210 15% 85%));border-radius:9999px;overflow:hidden}.vl-progress-fill{height:100%;border-radius:9999px;transition:width .5s ease,background-color .3s ease}.vl-progress-fill-primary{background-color:var(--color-primary, hsl(215 80% 25%))}.vl-progress-fill-warning{background-color:var(--color-secondary, hsl(15 90% 55%))}.vl-progress-fill-danger{background-color:var(--color-destructive, hsl(0 70% 50%))}\n"], encapsulation: i0.ViewEncapsulation.None });
641
+ }
642
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ProgressBarComponent, decorators: [{
643
+ type: Component,
644
+ args: [{ selector: 'vl-progress-bar', standalone: true, template: `
645
+ <div class="vl-progress-root">
646
+ @if (label()) {
647
+ <div class="vl-progress-header">
648
+ <span class="vl-progress-label">{{ label() }}</span>
649
+ @if (showLabel()) {
650
+ <span class="vl-progress-pct">{{ clampedValue() }}%</span>
651
+ }
652
+ </div>
653
+ } @else if (showLabel()) {
654
+ <div class="vl-progress-header vl-progress-header-end">
655
+ <span class="vl-progress-pct">{{ clampedValue() }}%</span>
656
+ </div>
657
+ }
658
+ <div class="vl-progress-track">
659
+ <div class="vl-progress-fill"
660
+ [class]="'vl-progress-fill-' + resolvedVariant()"
661
+ [style.width.%]="clampedValue()">
662
+ </div>
663
+ </div>
664
+ </div>
665
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-progress-root{display:flex;flex-direction:column;gap:.375rem}.vl-progress-header{display:flex;align-items:center;justify-content:space-between}.vl-progress-header-end{justify-content:flex-end}.vl-progress-label{font-size:.75rem;font-weight:500;color:var(--color-muted-foreground, hsl(210 10% 50%));font-family:var(--font-family-sans, inherit)}.vl-progress-pct{font-size:.6875rem;font-weight:500;color:var(--color-muted-foreground, hsl(210 10% 50%));font-family:var(--font-family-sans, inherit)}.vl-progress-track{height:.375rem;background-color:var(--color-border, hsl(210 15% 85%));border-radius:9999px;overflow:hidden}.vl-progress-fill{height:100%;border-radius:9999px;transition:width .5s ease,background-color .3s ease}.vl-progress-fill-primary{background-color:var(--color-primary, hsl(215 80% 25%))}.vl-progress-fill-warning{background-color:var(--color-secondary, hsl(15 90% 55%))}.vl-progress-fill-danger{background-color:var(--color-destructive, hsl(0 70% 50%))}\n"] }]
666
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
667
+
668
+ /**
669
+ * Label + error wrapper for native <select> elements.
670
+ *
671
+ * Usage:
672
+ * <vl-select label="Currency" [error]="currencyControl.invalid && currencyControl.touched ? 'Required' : null">
673
+ * <select formControlName="currency">
674
+ * <option value="USD">USD</option>
675
+ * </select>
676
+ * </vl-select>
677
+ */
678
+ class SelectComponent {
679
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
680
+ error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
681
+ hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
682
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
683
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
684
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SelectComponent, isStandalone: true, selector: "vl-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
685
+ <div class="vl-select-root">
686
+ @if (label()) {
687
+ <label class="vl-select-label" [class.vl-select-label-required]="required()">
688
+ {{ label() }}
689
+ </label>
690
+ }
691
+ <div class="vl-select-field" [class.vl-select-field-error]="!!error()">
692
+ <ng-content />
693
+ <span class="vl-select-caret" aria-hidden="true">
694
+ <svg viewBox="0 0 16 16" fill="currentColor">
695
+ <path fill-rule="evenodd" d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06z"/>
696
+ </svg>
697
+ </span>
698
+ </div>
699
+ @if (error()) {
700
+ <p class="vl-select-error">{{ error() }}</p>
701
+ } @else if (hint()) {
702
+ <p class="vl-select-hint">{{ hint() }}</p>
703
+ }
704
+ </div>
705
+ `, isInline: true, styles: [".vl-select-root{display:flex;flex-direction:column;gap:.375rem;font-family:var(--font-family-sans, inherit)}.vl-select-label{display:block;font-size:.875rem;font-weight:500;color:var(--color-foreground, #1f2937)}.vl-select-label-required:after{content:\" *\";color:var(--color-destructive, hsl(0 70% 50%))}.vl-select-field{position:relative;display:flex;align-items:center;border:1px solid var(--color-border, hsl(210 15% 85%));border-radius:var(--radius, .5rem);background-color:var(--color-background, #fff);transition:border-color .15s,box-shadow .15s}.vl-select-field:focus-within{border-color:var(--color-primary, hsl(215 80% 25%));box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 15%,transparent)}.vl-select-field-error{border-color:var(--color-destructive, hsl(0 70% 50%))}.vl-select-field-error:focus-within{border-color:var(--color-destructive, hsl(0 70% 50%));box-shadow:0 0 0 3px color-mix(in srgb,var(--color-destructive, hsl(0 70% 50%)) 15%,transparent)}.vl-select-field select{width:100%;padding:.625rem 2.25rem .625rem .875rem;font-size:.875rem;color:var(--color-foreground, #1f2937);background:transparent;border:none;outline:none;appearance:none;-webkit-appearance:none;cursor:pointer;font-family:inherit}.vl-select-caret{position:absolute;right:.75rem;pointer-events:none;color:var(--color-muted-foreground, hsl(210 10% 50%));display:flex;align-items:center}.vl-select-caret svg{width:1rem;height:1rem}.vl-select-error{font-size:.75rem;color:var(--color-destructive, hsl(0 70% 50%));margin:0}.vl-select-hint{font-size:.75rem;color:var(--color-muted-foreground, hsl(210 10% 50%));margin:0}\n"], encapsulation: i0.ViewEncapsulation.None });
706
+ }
707
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SelectComponent, decorators: [{
708
+ type: Component,
709
+ args: [{ selector: 'vl-select', standalone: true, template: `
710
+ <div class="vl-select-root">
711
+ @if (label()) {
712
+ <label class="vl-select-label" [class.vl-select-label-required]="required()">
713
+ {{ label() }}
714
+ </label>
715
+ }
716
+ <div class="vl-select-field" [class.vl-select-field-error]="!!error()">
717
+ <ng-content />
718
+ <span class="vl-select-caret" aria-hidden="true">
719
+ <svg viewBox="0 0 16 16" fill="currentColor">
720
+ <path fill-rule="evenodd" d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06z"/>
721
+ </svg>
722
+ </span>
723
+ </div>
724
+ @if (error()) {
725
+ <p class="vl-select-error">{{ error() }}</p>
726
+ } @else if (hint()) {
727
+ <p class="vl-select-hint">{{ hint() }}</p>
728
+ }
729
+ </div>
730
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-select-root{display:flex;flex-direction:column;gap:.375rem;font-family:var(--font-family-sans, inherit)}.vl-select-label{display:block;font-size:.875rem;font-weight:500;color:var(--color-foreground, #1f2937)}.vl-select-label-required:after{content:\" *\";color:var(--color-destructive, hsl(0 70% 50%))}.vl-select-field{position:relative;display:flex;align-items:center;border:1px solid var(--color-border, hsl(210 15% 85%));border-radius:var(--radius, .5rem);background-color:var(--color-background, #fff);transition:border-color .15s,box-shadow .15s}.vl-select-field:focus-within{border-color:var(--color-primary, hsl(215 80% 25%));box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary, hsl(215 80% 25%)) 15%,transparent)}.vl-select-field-error{border-color:var(--color-destructive, hsl(0 70% 50%))}.vl-select-field-error:focus-within{border-color:var(--color-destructive, hsl(0 70% 50%));box-shadow:0 0 0 3px color-mix(in srgb,var(--color-destructive, hsl(0 70% 50%)) 15%,transparent)}.vl-select-field select{width:100%;padding:.625rem 2.25rem .625rem .875rem;font-size:.875rem;color:var(--color-foreground, #1f2937);background:transparent;border:none;outline:none;appearance:none;-webkit-appearance:none;cursor:pointer;font-family:inherit}.vl-select-caret{position:absolute;right:.75rem;pointer-events:none;color:var(--color-muted-foreground, hsl(210 10% 50%));display:flex;align-items:center}.vl-select-caret svg{width:1rem;height:1rem}.vl-select-error{font-size:.75rem;color:var(--color-destructive, hsl(0 70% 50%));margin:0}.vl-select-hint{font-size:.75rem;color:var(--color-muted-foreground, hsl(210 10% 50%));margin:0}\n"] }]
731
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }] } });
732
+
733
+ /**
734
+ * Shimmer placeholder for loading states.
735
+ *
736
+ * Usage:
737
+ * <vl-skeleton /> — single line, full width
738
+ * <vl-skeleton width="60%" height="1rem" />
739
+ * <vl-skeleton shape="circle" width="3rem" height="3rem" />
740
+ * <vl-skeleton [lines]="3" /> — stacked paragraph lines
741
+ */
742
+ class SkeletonComponent {
743
+ width = input('100%', ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
744
+ height = input('1rem', ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
745
+ shape = input('rect', ...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
746
+ lines = input(1, ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
747
+ lineArr() {
748
+ return Array.from({ length: this.lines() }, (_, i) => i);
749
+ }
750
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
751
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SkeletonComponent, isStandalone: true, selector: "vl-skeleton", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
752
+ @if (lines() > 1) {
753
+ <div class="vl-skeleton-group">
754
+ @for (i of lineArr(); track i; let last = $last) {
755
+ <div class="vl-skeleton"
756
+ [class.vl-skeleton-circle]="shape() === 'circle'"
757
+ [style.width]="last ? '60%' : '100%'"
758
+ [style.height]="height()">
759
+ </div>
760
+ }
761
+ </div>
762
+ } @else {
763
+ <div class="vl-skeleton"
764
+ [class.vl-skeleton-circle]="shape() === 'circle'"
765
+ [style.width]="width()"
766
+ [style.height]="height()">
767
+ </div>
768
+ }
769
+ `, isInline: true, styles: [".vl-skeleton{display:block;background:linear-gradient(90deg,var(--color-muted, hsl(210 10% 93%)) 25%,color-mix(in srgb,var(--color-muted, hsl(210 10% 93%)) 60%,white) 50%,var(--color-muted, hsl(210 10% 93%)) 75%);background-size:200% 100%;border-radius:calc(var(--radius, .5rem) * .5);animation:vl-shimmer 1.5s infinite linear}.vl-skeleton-circle{border-radius:9999px}.vl-skeleton-group{display:flex;flex-direction:column;gap:.5rem}@keyframes vl-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"], encapsulation: i0.ViewEncapsulation.None });
770
+ }
771
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SkeletonComponent, decorators: [{
772
+ type: Component,
773
+ args: [{ selector: 'vl-skeleton', standalone: true, template: `
774
+ @if (lines() > 1) {
775
+ <div class="vl-skeleton-group">
776
+ @for (i of lineArr(); track i; let last = $last) {
777
+ <div class="vl-skeleton"
778
+ [class.vl-skeleton-circle]="shape() === 'circle'"
779
+ [style.width]="last ? '60%' : '100%'"
780
+ [style.height]="height()">
781
+ </div>
782
+ }
783
+ </div>
784
+ } @else {
785
+ <div class="vl-skeleton"
786
+ [class.vl-skeleton-circle]="shape() === 'circle'"
787
+ [style.width]="width()"
788
+ [style.height]="height()">
789
+ </div>
790
+ }
791
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-skeleton{display:block;background:linear-gradient(90deg,var(--color-muted, hsl(210 10% 93%)) 25%,color-mix(in srgb,var(--color-muted, hsl(210 10% 93%)) 60%,white) 50%,var(--color-muted, hsl(210 10% 93%)) 75%);background-size:200% 100%;border-radius:calc(var(--radius, .5rem) * .5);animation:vl-shimmer 1.5s infinite linear}.vl-skeleton-circle{border-radius:9999px}.vl-skeleton-group{display:flex;flex-direction:column;gap:.5rem}@keyframes vl-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"] }]
792
+ }], propDecorators: { width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }] } });
793
+
794
+ /**
795
+ * Compact labeled metric card.
796
+ *
797
+ * Usage:
798
+ * <vl-stat-card label="Budget" value="$2,400" />
799
+ * <vl-stat-card label="Spent" value="$1,200" valueVariant="secondary" />
800
+ */
801
+ class StatCardComponent {
802
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
803
+ value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
804
+ subtext = input('', ...(ngDevMode ? [{ debugName: "subtext" }] : /* istanbul ignore next */ []));
805
+ valueVariant = input('default', ...(ngDevMode ? [{ debugName: "valueVariant" }] : /* istanbul ignore next */ []));
806
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: StatCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
807
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: StatCardComponent, isStandalone: true, selector: "vl-stat-card", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, subtext: { classPropertyName: "subtext", publicName: "subtext", isSignal: true, isRequired: false, transformFunction: null }, valueVariant: { classPropertyName: "valueVariant", publicName: "valueVariant", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
808
+ <div class="vl-stat-card">
809
+ <p class="vl-stat-label">{{ label() }}</p>
810
+ <p class="vl-stat-value" [class]="'vl-stat-value-' + valueVariant()">{{ value() }}</p>
811
+ @if (subtext()) {
812
+ <p class="vl-stat-subtext">{{ subtext() }}</p>
813
+ }
814
+ </div>
815
+ `, isInline: true, styles: [".vl-stat-card{background-color:var(--color-card, #fff);border:1px solid var(--color-border, hsl(210 15% 85%));border-radius:var(--radius-xl, .75rem);padding:1.25rem;text-align:center;font-family:var(--font-family-sans, inherit)}.vl-stat-label{font-size:.6875rem;font-weight:500;letter-spacing:.06em;text-transform:uppercase;color:var(--color-muted-foreground, hsl(210 10% 50%));margin:0 0 .5rem}.vl-stat-value{font-size:1.5rem;font-weight:700;margin:0;line-height:1.2}.vl-stat-value-default{color:var(--color-foreground, #1f2937)}.vl-stat-value-primary{color:var(--color-primary, hsl(215 80% 25%))}.vl-stat-value-secondary{color:var(--color-secondary, hsl(15 90% 55%))}.vl-stat-value-accent{color:var(--color-accent, hsl(150 60% 35%))}.vl-stat-value-destructive{color:var(--color-destructive, hsl(0 70% 50%))}.vl-stat-subtext{font-size:.75rem;color:var(--color-muted-foreground, hsl(210 10% 50%));margin:.375rem 0 0}\n"], encapsulation: i0.ViewEncapsulation.None });
816
+ }
817
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: StatCardComponent, decorators: [{
818
+ type: Component,
819
+ args: [{ selector: 'vl-stat-card', standalone: true, template: `
820
+ <div class="vl-stat-card">
821
+ <p class="vl-stat-label">{{ label() }}</p>
822
+ <p class="vl-stat-value" [class]="'vl-stat-value-' + valueVariant()">{{ value() }}</p>
823
+ @if (subtext()) {
824
+ <p class="vl-stat-subtext">{{ subtext() }}</p>
825
+ }
826
+ </div>
827
+ `, encapsulation: ViewEncapsulation.None, styles: [".vl-stat-card{background-color:var(--color-card, #fff);border:1px solid var(--color-border, hsl(210 15% 85%));border-radius:var(--radius-xl, .75rem);padding:1.25rem;text-align:center;font-family:var(--font-family-sans, inherit)}.vl-stat-label{font-size:.6875rem;font-weight:500;letter-spacing:.06em;text-transform:uppercase;color:var(--color-muted-foreground, hsl(210 10% 50%));margin:0 0 .5rem}.vl-stat-value{font-size:1.5rem;font-weight:700;margin:0;line-height:1.2}.vl-stat-value-default{color:var(--color-foreground, #1f2937)}.vl-stat-value-primary{color:var(--color-primary, hsl(215 80% 25%))}.vl-stat-value-secondary{color:var(--color-secondary, hsl(15 90% 55%))}.vl-stat-value-accent{color:var(--color-accent, hsl(150 60% 35%))}.vl-stat-value-destructive{color:var(--color-destructive, hsl(0 70% 50%))}.vl-stat-subtext{font-size:.75rem;color:var(--color-muted-foreground, hsl(210 10% 50%));margin:.375rem 0 0}\n"] }]
828
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], subtext: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtext", required: false }] }], valueVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueVariant", required: false }] }] } });
829
+
830
+ /**
831
+ * Attribute directive that renders an accessible tooltip near the host element.
832
+ *
833
+ * Usage:
834
+ * <button vlTooltip="Save changes">Save</button>
835
+ * <span [vlTooltip]="label" vlTooltipPosition="bottom">...</span>
836
+ */
837
+ class TooltipDirective {
838
+ vlTooltip = input('', ...(ngDevMode ? [{ debugName: "vlTooltip" }] : /* istanbul ignore next */ []));
839
+ vlTooltipPosition = input('top', ...(ngDevMode ? [{ debugName: "vlTooltipPosition" }] : /* istanbul ignore next */ []));
840
+ vlTooltipDelay = input(200, ...(ngDevMode ? [{ debugName: "vlTooltipDelay" }] : /* istanbul ignore next */ []));
841
+ tooltipId = `vl-tooltip-${Math.random().toString(36).slice(2, 8)}`;
842
+ el = inject((ElementRef));
843
+ platformId = inject(PLATFORM_ID);
844
+ tooltipEl = null;
845
+ showTimer = null;
846
+ show() {
847
+ if (!isPlatformBrowser(this.platformId) || !this.vlTooltip())
848
+ return;
849
+ this.showTimer = setTimeout(() => this.create(), this.vlTooltipDelay());
850
+ }
851
+ hide() {
852
+ if (this.showTimer) {
853
+ clearTimeout(this.showTimer);
854
+ this.showTimer = null;
855
+ }
856
+ this.destroy();
857
+ }
858
+ ngOnDestroy() {
859
+ this.hide();
860
+ }
861
+ create() {
862
+ if (this.tooltipEl)
863
+ return;
864
+ const tip = document.createElement('div');
865
+ tip.id = this.tooltipId;
866
+ tip.role = 'tooltip';
867
+ tip.className = 'vl-tooltip';
868
+ tip.textContent = this.vlTooltip();
869
+ tip.setAttribute('data-position', this.vlTooltipPosition());
870
+ document.body.appendChild(tip);
871
+ this.tooltipEl = tip;
872
+ this.injectStyles();
873
+ this.position(tip);
874
+ // Trigger enter animation on next frame so the transition fires
875
+ requestAnimationFrame(() => tip.classList.add('vl-tooltip-visible'));
876
+ }
877
+ destroy() {
878
+ if (!this.tooltipEl)
879
+ return;
880
+ const tip = this.tooltipEl;
881
+ tip.classList.remove('vl-tooltip-visible');
882
+ // Wait for the fade-out transition before removing from DOM
883
+ tip.addEventListener('transitionend', () => tip.remove(), { once: true });
884
+ this.tooltipEl = null;
885
+ }
886
+ position(tip) {
887
+ const hostRect = this.el.nativeElement.getBoundingClientRect();
888
+ const pos = this.vlTooltipPosition();
889
+ const GAP = 8;
890
+ // Temporarily make visible to measure size
891
+ tip.style.visibility = 'hidden';
892
+ tip.style.display = 'block';
893
+ const tipRect = tip.getBoundingClientRect();
894
+ tip.style.visibility = '';
895
+ tip.style.display = '';
896
+ let top;
897
+ let left;
898
+ switch (pos) {
899
+ case 'bottom':
900
+ top = hostRect.bottom + window.scrollY + GAP;
901
+ left = hostRect.left + window.scrollX + hostRect.width / 2 - tipRect.width / 2;
902
+ break;
903
+ case 'left':
904
+ top = hostRect.top + window.scrollY + hostRect.height / 2 - tipRect.height / 2;
905
+ left = hostRect.left + window.scrollX - tipRect.width - GAP;
906
+ break;
907
+ case 'right':
908
+ top = hostRect.top + window.scrollY + hostRect.height / 2 - tipRect.height / 2;
909
+ left = hostRect.right + window.scrollX + GAP;
910
+ break;
911
+ default: // top
912
+ top = hostRect.top + window.scrollY - tipRect.height - GAP;
913
+ left = hostRect.left + window.scrollX + hostRect.width / 2 - tipRect.width / 2;
914
+ break;
915
+ }
916
+ // Keep within horizontal viewport bounds
917
+ left = Math.max(8, Math.min(left, window.innerWidth - tipRect.width - 8));
918
+ tip.style.top = `${top}px`;
919
+ tip.style.left = `${left}px`;
920
+ }
921
+ injectStyles() {
922
+ const STYLE_ID = 'vl-tooltip-styles';
923
+ if (document.getElementById(STYLE_ID))
924
+ return;
925
+ const style = document.createElement('style');
926
+ style.id = STYLE_ID;
927
+ style.textContent = `
928
+ .vl-tooltip {
929
+ position: absolute;
930
+ z-index: 10000;
931
+ padding: 0.3125rem 0.625rem;
932
+ background-color: var(--color-foreground, #1f2937);
933
+ color: var(--color-background, #fff);
934
+ font-size: 0.75rem;
935
+ font-family: var(--font-family-sans, inherit);
936
+ line-height: 1.4;
937
+ border-radius: calc(var(--radius, 0.5rem) * 0.75);
938
+ white-space: nowrap;
939
+ pointer-events: none;
940
+ max-width: 16rem;
941
+ white-space: normal;
942
+ text-align: center;
943
+ opacity: 0;
944
+ transform: scale(0.95);
945
+ transition: opacity 0.15s ease, transform 0.15s ease;
946
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
947
+ }
948
+ .vl-tooltip-visible {
949
+ opacity: 1;
950
+ transform: scale(1);
951
+ }
952
+ `;
953
+ document.head.appendChild(style);
954
+ }
955
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
956
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: TooltipDirective, isStandalone: true, selector: "[vlTooltip]", inputs: { vlTooltip: { classPropertyName: "vlTooltip", publicName: "vlTooltip", isSignal: true, isRequired: false, transformFunction: null }, vlTooltipPosition: { classPropertyName: "vlTooltipPosition", publicName: "vlTooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, vlTooltipDelay: { classPropertyName: "vlTooltipDelay", publicName: "vlTooltipDelay", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "show()", "focusin": "show()", "mouseleave": "hide()", "focusout": "hide()" }, properties: { "attr.aria-describedby": "tooltipId" } }, ngImport: i0 });
957
+ }
958
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TooltipDirective, decorators: [{
959
+ type: Directive,
960
+ args: [{
961
+ selector: '[vlTooltip]',
962
+ standalone: true,
963
+ host: {
964
+ '[attr.aria-describedby]': 'tooltipId',
965
+ },
966
+ }]
967
+ }], propDecorators: { vlTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "vlTooltip", required: false }] }], vlTooltipPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "vlTooltipPosition", required: false }] }], vlTooltipDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "vlTooltipDelay", required: false }] }], show: [{
968
+ type: HostListener,
969
+ args: ['mouseenter']
970
+ }, {
971
+ type: HostListener,
972
+ args: ['focusin']
973
+ }], hide: [{
974
+ type: HostListener,
975
+ args: ['mouseleave']
976
+ }, {
977
+ type: HostListener,
978
+ args: ['focusout']
979
+ }] } });
980
+
981
+ /**
982
+ * Formats a number as a currency string using Intl.NumberFormat.
983
+ *
984
+ * Usage:
985
+ * {{ trip.budget | vlCurrency }} → "$2,400"
986
+ * {{ expense.amount | vlCurrency:'EUR' }} → "€1,200"
987
+ * {{ amount | vlCurrency:'INR':'symbol-narrow':'1.2-2' }}
988
+ */
989
+ class CurrencyFormatPipe {
990
+ transform(value, currencyCode = 'USD', display = 'symbol', locale = 'en-US') {
991
+ if (value == null)
992
+ return '';
993
+ try {
994
+ return new Intl.NumberFormat(locale, {
995
+ style: 'currency',
996
+ currency: currencyCode,
997
+ currencyDisplay: display === 'symbol-narrow' ? 'narrowSymbol' : display,
998
+ maximumFractionDigits: Number.isInteger(value) ? 0 : 2,
999
+ }).format(value);
1000
+ }
1001
+ catch {
1002
+ return `${currencyCode} ${value}`;
1003
+ }
1004
+ }
1005
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: CurrencyFormatPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1006
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.5", ngImport: i0, type: CurrencyFormatPipe, isStandalone: true, name: "vlCurrency" });
1007
+ }
1008
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: CurrencyFormatPipe, decorators: [{
1009
+ type: Pipe,
1010
+ args: [{ name: 'vlCurrency', standalone: true, pure: true }]
1011
+ }] });
1012
+
1013
+ /**
1014
+ * Formats a Date, ISO string, or timestamp using Intl.DateTimeFormat.
1015
+ *
1016
+ * Presets:
1017
+ * short → "Jun 28" (no year if current year)
1018
+ * medium → "Jun 28, 2026"
1019
+ * long → "June 28, 2026"
1020
+ * full → "Sunday, June 28, 2026"
1021
+ * time → "2:30 PM"
1022
+ * datetime → "Jun 28, 2026, 2:30 PM"
1023
+ * relative → "2 days ago" / "in 3 hours" (uses Intl.RelativeTimeFormat)
1024
+ *
1025
+ * Usage:
1026
+ * {{ trip.startDate | vlDateFormat }}
1027
+ * {{ trip.startDate | vlDateFormat:'long' }}
1028
+ * {{ trip.startDate | vlDateFormat:'relative' }}
1029
+ */
1030
+ class DateFormatPipe {
1031
+ transform(value, preset = 'medium', locale = 'en-US') {
1032
+ if (value == null || value === '')
1033
+ return '';
1034
+ const date = value instanceof Date ? value : new Date(value);
1035
+ if (isNaN(date.getTime()))
1036
+ return String(value);
1037
+ if (preset === 'relative')
1038
+ return this.toRelative(date, locale);
1039
+ return new Intl.DateTimeFormat(locale, this.options(preset, date)).format(date);
1040
+ }
1041
+ options(preset, date) {
1042
+ const isCurrentYear = date.getFullYear() === new Date().getFullYear();
1043
+ switch (preset) {
1044
+ case 'short':
1045
+ return isCurrentYear
1046
+ ? { month: 'short', day: 'numeric' }
1047
+ : { month: 'short', day: 'numeric', year: 'numeric' };
1048
+ case 'medium':
1049
+ return { month: 'short', day: 'numeric', year: 'numeric' };
1050
+ case 'long':
1051
+ return { month: 'long', day: 'numeric', year: 'numeric' };
1052
+ case 'full':
1053
+ return { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' };
1054
+ case 'time':
1055
+ return { hour: 'numeric', minute: '2-digit', hour12: true };
1056
+ case 'datetime':
1057
+ return { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true };
1058
+ default:
1059
+ return { month: 'short', day: 'numeric', year: 'numeric' };
1060
+ }
1061
+ }
1062
+ toRelative(date, locale) {
1063
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
1064
+ const diffMs = date.getTime() - Date.now();
1065
+ const diffS = Math.round(diffMs / 1000);
1066
+ const diffM = Math.round(diffS / 60);
1067
+ const diffH = Math.round(diffM / 60);
1068
+ const diffD = Math.round(diffH / 24);
1069
+ const diffW = Math.round(diffD / 7);
1070
+ const diffMo = Math.round(diffD / 30);
1071
+ const diffY = Math.round(diffD / 365);
1072
+ if (Math.abs(diffS) < 60)
1073
+ return rtf.format(diffS, 'second');
1074
+ if (Math.abs(diffM) < 60)
1075
+ return rtf.format(diffM, 'minute');
1076
+ if (Math.abs(diffH) < 24)
1077
+ return rtf.format(diffH, 'hour');
1078
+ if (Math.abs(diffD) < 7)
1079
+ return rtf.format(diffD, 'day');
1080
+ if (Math.abs(diffW) < 5)
1081
+ return rtf.format(diffW, 'week');
1082
+ if (Math.abs(diffMo) < 12)
1083
+ return rtf.format(diffMo, 'month');
1084
+ return rtf.format(diffY, 'year');
1085
+ }
1086
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DateFormatPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1087
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.5", ngImport: i0, type: DateFormatPipe, isStandalone: true, name: "vlDateFormat" });
1088
+ }
1089
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DateFormatPipe, decorators: [{
1090
+ type: Pipe,
1091
+ args: [{ name: 'vlDateFormat', standalone: true, pure: true }]
1092
+ }] });
1093
+
1094
+ /**
1095
+ * Returns a human-readable trip duration string.
1096
+ *
1097
+ * Usage:
1098
+ * {{ trip.startDate | vlTripDuration:trip.endDate }} → "6 days"
1099
+ */
1100
+ class TripDurationPipe {
1101
+ transform(startDate, endDate) {
1102
+ if (!startDate || !endDate)
1103
+ return '';
1104
+ const start = startDate instanceof Date ? startDate : new Date(startDate);
1105
+ const end = endDate instanceof Date ? endDate : new Date(endDate);
1106
+ const days = Math.ceil(Math.abs(end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
1107
+ return `${days} day${days !== 1 ? 's' : ''}`;
1108
+ }
1109
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TripDurationPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1110
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.5", ngImport: i0, type: TripDurationPipe, isStandalone: true, name: "vlTripDuration" });
1111
+ }
1112
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TripDurationPipe, decorators: [{
1113
+ type: Pipe,
1114
+ args: [{ name: 'vlTripDuration', standalone: true, pure: true }]
1115
+ }] });
1116
+
1117
+ const CATEGORY_ICONS = {
1118
+ food: '🍔',
1119
+ accommodation: '🏨',
1120
+ transport: '🚗',
1121
+ activities: '🎭',
1122
+ souvenirs: '🎁',
1123
+ other: '💰'
1124
+ };
1125
+ const CATEGORY_COLORS = {
1126
+ food: '#FF6B6B',
1127
+ accommodation: '#4ECDC4',
1128
+ transport: '#FFE66D',
1129
+ activities: '#95E1D3',
1130
+ souvenirs: '#C7CEEA',
1131
+ other: '#B4B4B4'
1132
+ };
1133
+
1134
+ /**
1135
+ * Shared HTTP service for micro-frontends.
1136
+ *
1137
+ * Services call with relative paths: `sdk.get('/trips')`.
1138
+ * The host application's HTTP interceptor resolves the full API URL.
1139
+ */
1140
+ class SharedHttpService {
1141
+ http = inject(HttpClient);
1142
+ get(path, options) {
1143
+ return this.http.get(path, { ...options, responseType: 'json' });
1144
+ }
1145
+ post(path, body, options) {
1146
+ return this.http.post(path, body, { ...options, responseType: 'json' });
1147
+ }
1148
+ put(path, body, options) {
1149
+ return this.http.put(path, body, { ...options, responseType: 'json' });
1150
+ }
1151
+ delete(path, options) {
1152
+ return this.http.delete(path, { ...options, body: options?.body, responseType: 'json' });
1153
+ }
1154
+ patch(path, body, options) {
1155
+ return this.http.patch(path, body, { ...options, responseType: 'json' });
1156
+ }
1157
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SharedHttpService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1158
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SharedHttpService, providedIn: 'root' });
1159
+ }
1160
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SharedHttpService, decorators: [{
1161
+ type: Injectable,
1162
+ args: [{
1163
+ providedIn: 'root',
1164
+ }]
1165
+ }] });
1166
+
1167
+ class TripService {
1168
+ httpService = inject(SharedHttpService);
1169
+ getAllTrips() {
1170
+ return this.httpService.get('/trips').pipe(map(res => (res?.data?.trips ?? []).map(dto => this.dtoToTrip(dto))));
1171
+ }
1172
+ getTripById(id) {
1173
+ return this.httpService.get(`/trips/${id}`).pipe(map(res => res?.data ? this.dtoToTrip(res.data) : undefined));
1174
+ }
1175
+ dtoToTrip(dto) {
1176
+ return {
1177
+ ...dto,
1178
+ startDate: new Date(dto.startDate),
1179
+ endDate: new Date(dto.endDate)
1180
+ };
1181
+ }
1182
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TripService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1183
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TripService, providedIn: 'root' });
1184
+ }
1185
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: TripService, decorators: [{
1186
+ type: Injectable,
1187
+ args: [{
1188
+ providedIn: 'root',
1189
+ }]
1190
+ }] });
1191
+
1192
+ class ExpenseService {
1193
+ httpService = inject(SharedHttpService);
1194
+ getExpensesByTripId(tripId) {
1195
+ return this.httpService.get(`/trips/${tripId}/expenses`).pipe(map(res => (res?.data ?? []).map(dto => this.dtoToExpense(dto))));
1196
+ }
1197
+ addExpense(expense) {
1198
+ const dto = this.expenseToDTO(expense);
1199
+ return this.httpService.post('/expenses', dto).pipe(map(res => this.dtoToExpense(res.data)));
1200
+ }
1201
+ getExpensesByCategory(tripId) {
1202
+ return this.getExpensesByTripId(tripId).pipe(map(expenses => this.calculateCategorySpending(expenses)));
1203
+ }
1204
+ getDailyExpenses(tripId) {
1205
+ return this.getExpensesByTripId(tripId).pipe(map(expenses => this.groupExpensesByDay(expenses)));
1206
+ }
1207
+ calculateCategorySpending(expenses) {
1208
+ const total = expenses.reduce((sum, exp) => sum + exp.amount, 0);
1209
+ const categoryMap = new Map();
1210
+ expenses.forEach(exp => {
1211
+ const current = categoryMap.get(exp.category) || 0;
1212
+ categoryMap.set(exp.category, current + exp.amount);
1213
+ });
1214
+ return Array.from(categoryMap.entries()).map(([category, amount]) => ({
1215
+ category,
1216
+ amount,
1217
+ percentage: total > 0 ? Math.round((amount / total) * 100) : 0,
1218
+ icon: CATEGORY_ICONS[category]
1219
+ })).sort((a, b) => b.amount - a.amount);
1220
+ }
1221
+ groupExpensesByDay(expenses) {
1222
+ const dayMap = new Map();
1223
+ expenses.forEach(exp => {
1224
+ const dateKey = exp.date.toISOString().split('T')[0];
1225
+ const dayExpenses = dayMap.get(dateKey) || [];
1226
+ dayExpenses.push(exp);
1227
+ dayMap.set(dateKey, dayExpenses);
1228
+ });
1229
+ return Array.from(dayMap.entries())
1230
+ .map(([dateKey, dayExpenses]) => ({
1231
+ date: new Date(dateKey),
1232
+ expenses: dayExpenses.sort((a, b) => a.date.getTime() - b.date.getTime()),
1233
+ total: dayExpenses.reduce((sum, exp) => sum + exp.amount, 0)
1234
+ }))
1235
+ .sort((a, b) => a.date.getTime() - b.date.getTime());
1236
+ }
1237
+ dtoToExpense(dto) {
1238
+ return {
1239
+ ...dto,
1240
+ date: new Date(dto.date)
1241
+ };
1242
+ }
1243
+ expenseToDTO(expense) {
1244
+ return {
1245
+ ...expense,
1246
+ date: expense.date.toISOString()
1247
+ };
1248
+ }
1249
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ExpenseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1250
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ExpenseService, providedIn: 'root' });
1251
+ }
1252
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ExpenseService, decorators: [{
1253
+ type: Injectable,
1254
+ args: [{
1255
+ providedIn: 'root'
1256
+ }]
1257
+ }] });
1258
+
1259
+ const EnvironmentToken = new InjectionToken('environment');
1260
+
1261
+ const CURRENCIES = [
1262
+ { code: 'USD', symbol: '$', name: 'US Dollar' },
1263
+ { code: 'EUR', symbol: '€', name: 'Euro' },
1264
+ { code: 'GBP', symbol: '£', name: 'British Pound' },
1265
+ { code: 'INR', symbol: '₹', name: 'Indian Rupee' },
1266
+ { code: 'JPY', symbol: '¥', name: 'Japanese Yen' },
1267
+ { code: 'AUD', symbol: 'A$', name: 'Australian Dollar' },
1268
+ { code: 'CAD', symbol: 'C$', name: 'Canadian Dollar' },
1269
+ ];
1270
+ function getCurrency(code) {
1271
+ return CURRENCIES.find(c => c.code === code);
1272
+ }
1273
+ function formatCurrency(amount, currencyCode = 'USD') {
1274
+ const currency = getCurrency(currencyCode);
1275
+ const symbol = currency?.symbol || '$';
1276
+ return `${symbol}${amount.toLocaleString()}`;
1277
+ }
1278
+
1279
+ // Components
1280
+
1281
+ /*
1282
+ * Public API Surface of voyage-lib
1283
+ */
1284
+
1285
+ /**
1286
+ * Generated bundle index. Do not edit.
1287
+ */
1288
+
1289
+ export { AvatarComponent, BadgeComponent, ButtonComponent, CATEGORY_COLORS, CATEGORY_ICONS, CURRENCIES, CardComponent, ConfirmDialogComponent, CurrencyFormatPipe, DateFormatPipe, DialogComponent, EmptyStateComponent, EnvironmentToken, ExpenseService, InputComponent, LoaderComponent, PasswordStrengthComponent, ProgressBarComponent, SelectComponent, SharedHttpService, SkeletonComponent, SnackbarComponent, SnackbarService, StatCardComponent, TooltipDirective, TripDurationPipe, TripService, formatCurrency, getCurrency };
1290
+ //# sourceMappingURL=voyage-lib.mjs.map