@sonny-ui/core 0.1.0-alpha.13 → 0.1.0-alpha.15

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.
@@ -3,8 +3,8 @@ import { twMerge } from 'tailwind-merge';
3
3
  import { cva } from 'class-variance-authority';
4
4
  export { cva } from 'class-variance-authority';
5
5
  import * as i0 from '@angular/core';
6
- import { inject, PLATFORM_ID, signal, computed, Injectable, InjectionToken, makeEnvironmentProviders, provideEnvironmentInitializer, input, Directive, ChangeDetectionStrategy, Component, ElementRef, model, viewChild, forwardRef, HostListener, Injector, afterNextRender, effect, Renderer2, output, contentChildren } from '@angular/core';
7
- import { DOCUMENT, isPlatformBrowser } from '@angular/common';
6
+ import { inject, PLATFORM_ID, signal, computed, Injectable, InjectionToken, makeEnvironmentProviders, provideEnvironmentInitializer, input, Directive, ChangeDetectionStrategy, Component, ElementRef, model, viewChild, forwardRef, HostListener, TemplateRef, output, contentChildren, contentChild, effect, untracked, Injector, afterNextRender, Renderer2, linkedSignal } from '@angular/core';
7
+ import { DOCUMENT, isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
8
8
  import { Dialog, DialogRef } from '@angular/cdk/dialog';
9
9
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
10
10
  import { createGlobalPositionStrategy } from '@angular/cdk/overlay';
@@ -2417,6 +2417,1146 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
2417
2417
  }]
2418
2418
  }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
2419
2419
 
2420
+ const paginationItemVariants = cva('inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', {
2421
+ variants: {
2422
+ variant: {
2423
+ default: 'bg-background hover:bg-accent hover:text-accent-foreground',
2424
+ outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
2425
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
2426
+ },
2427
+ size: {
2428
+ sm: 'h-8 w-8 text-xs',
2429
+ md: 'h-9 w-9',
2430
+ lg: 'h-10 w-10',
2431
+ },
2432
+ active: {
2433
+ true: 'bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground',
2434
+ false: '',
2435
+ },
2436
+ },
2437
+ defaultVariants: {
2438
+ variant: 'default',
2439
+ size: 'md',
2440
+ active: false,
2441
+ },
2442
+ });
2443
+
2444
+ function computePageRange(totalPages, currentPage, siblingCount, boundaryCount) {
2445
+ const range = (start, end) => Array.from({ length: end - start + 1 }, (_, i) => start + i);
2446
+ const startPages = range(1, Math.min(boundaryCount, totalPages));
2447
+ const endPages = range(Math.max(totalPages - boundaryCount + 1, boundaryCount + 1), totalPages);
2448
+ const siblingsStart = Math.max(Math.min(currentPage - siblingCount, totalPages - boundaryCount - siblingCount * 2 - 1), boundaryCount + 2);
2449
+ const siblingsEnd = Math.min(Math.max(currentPage + siblingCount, boundaryCount + siblingCount * 2 + 2), endPages.length > 0 ? endPages[0] - 2 : totalPages - 1);
2450
+ const result = [...startPages];
2451
+ if (siblingsStart > boundaryCount + 2) {
2452
+ result.push('ellipsis');
2453
+ }
2454
+ else if (boundaryCount + 1 < totalPages - boundaryCount) {
2455
+ result.push(boundaryCount + 1);
2456
+ }
2457
+ result.push(...range(siblingsStart, siblingsEnd));
2458
+ if (siblingsEnd < totalPages - boundaryCount - 1) {
2459
+ result.push('ellipsis');
2460
+ }
2461
+ else if (totalPages - boundaryCount > boundaryCount) {
2462
+ result.push(totalPages - boundaryCount);
2463
+ }
2464
+ result.push(...endPages);
2465
+ return [...new Set(result)].sort((a, b) => {
2466
+ if (a === 'ellipsis')
2467
+ return 0;
2468
+ if (b === 'ellipsis')
2469
+ return 0;
2470
+ return a - b;
2471
+ });
2472
+ }
2473
+ class SnyPaginationComponent {
2474
+ currentPage = model(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
2475
+ totalPages = input.required(...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
2476
+ siblingCount = input(1, ...(ngDevMode ? [{ debugName: "siblingCount" }] : /* istanbul ignore next */ []));
2477
+ boundaryCount = input(1, ...(ngDevMode ? [{ debugName: "boundaryCount" }] : /* istanbul ignore next */ []));
2478
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
2479
+ variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
2480
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
2481
+ pages = computed(() => computePageRange(this.totalPages(), this.currentPage(), this.siblingCount(), this.boundaryCount()), ...(ngDevMode ? [{ debugName: "pages" }] : /* istanbul ignore next */ []));
2482
+ hasPrev = computed(() => this.currentPage() > 1, ...(ngDevMode ? [{ debugName: "hasPrev" }] : /* istanbul ignore next */ []));
2483
+ hasNext = computed(() => this.currentPage() < this.totalPages(), ...(ngDevMode ? [{ debugName: "hasNext" }] : /* istanbul ignore next */ []));
2484
+ goToPage(page) {
2485
+ if (page === 'ellipsis')
2486
+ return;
2487
+ this.currentPage.set(page);
2488
+ }
2489
+ prev() {
2490
+ if (this.hasPrev())
2491
+ this.currentPage.update((p) => p - 1);
2492
+ }
2493
+ next() {
2494
+ if (this.hasNext())
2495
+ this.currentPage.update((p) => p + 1);
2496
+ }
2497
+ pageClass(page) {
2498
+ return cn(paginationItemVariants({
2499
+ variant: this.variant(),
2500
+ size: this.size(),
2501
+ active: page === this.currentPage(),
2502
+ }));
2503
+ }
2504
+ navBtnClass() {
2505
+ return cn(paginationItemVariants({ variant: this.variant(), size: this.size(), active: false }));
2506
+ }
2507
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyPaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2508
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyPaginationComponent, isStandalone: true, selector: "sny-pagination", inputs: { currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, totalPages: { classPropertyName: "totalPages", publicName: "totalPages", isSignal: true, isRequired: true, transformFunction: null }, siblingCount: { classPropertyName: "siblingCount", publicName: "siblingCount", isSignal: true, isRequired: false, transformFunction: null }, boundaryCount: { classPropertyName: "boundaryCount", publicName: "boundaryCount", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange" }, host: { attributes: { "role": "navigation", "aria-label": "Pagination" } }, ngImport: i0, template: `
2509
+ <div class="flex items-center gap-1">
2510
+ <button
2511
+ [class]="navBtnClass()"
2512
+ [disabled]="!hasPrev()"
2513
+ [attr.aria-label]="'Go to previous page'"
2514
+ (click)="prev()"
2515
+ >
2516
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
2517
+ </button>
2518
+
2519
+ @for (page of pages(); track $index) {
2520
+ @if (page === 'ellipsis') {
2521
+ <span class="flex h-9 w-9 items-center justify-center" aria-hidden="true">...</span>
2522
+ } @else {
2523
+ <button
2524
+ [class]="pageClass(page)"
2525
+ [attr.aria-label]="'Page ' + page"
2526
+ [attr.aria-current]="page === currentPage() ? 'page' : null"
2527
+ (click)="goToPage(page)"
2528
+ >
2529
+ {{ page }}
2530
+ </button>
2531
+ }
2532
+ }
2533
+
2534
+ <button
2535
+ [class]="navBtnClass()"
2536
+ [disabled]="!hasNext()"
2537
+ [attr.aria-label]="'Go to next page'"
2538
+ (click)="next()"
2539
+ >
2540
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
2541
+ </button>
2542
+ </div>
2543
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
2544
+ }
2545
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyPaginationComponent, decorators: [{
2546
+ type: Component,
2547
+ args: [{
2548
+ selector: 'sny-pagination',
2549
+ standalone: true,
2550
+ changeDetection: ChangeDetectionStrategy.OnPush,
2551
+ host: {
2552
+ 'role': 'navigation',
2553
+ 'aria-label': 'Pagination',
2554
+ },
2555
+ template: `
2556
+ <div class="flex items-center gap-1">
2557
+ <button
2558
+ [class]="navBtnClass()"
2559
+ [disabled]="!hasPrev()"
2560
+ [attr.aria-label]="'Go to previous page'"
2561
+ (click)="prev()"
2562
+ >
2563
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
2564
+ </button>
2565
+
2566
+ @for (page of pages(); track $index) {
2567
+ @if (page === 'ellipsis') {
2568
+ <span class="flex h-9 w-9 items-center justify-center" aria-hidden="true">...</span>
2569
+ } @else {
2570
+ <button
2571
+ [class]="pageClass(page)"
2572
+ [attr.aria-label]="'Page ' + page"
2573
+ [attr.aria-current]="page === currentPage() ? 'page' : null"
2574
+ (click)="goToPage(page)"
2575
+ >
2576
+ {{ page }}
2577
+ </button>
2578
+ }
2579
+ }
2580
+
2581
+ <button
2582
+ [class]="navBtnClass()"
2583
+ [disabled]="!hasNext()"
2584
+ [attr.aria-label]="'Go to next page'"
2585
+ (click)="next()"
2586
+ >
2587
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
2588
+ </button>
2589
+ </div>
2590
+ `,
2591
+ }]
2592
+ }], propDecorators: { currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: false }] }, { type: i0.Output, args: ["currentPageChange"] }], totalPages: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalPages", required: true }] }], siblingCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "siblingCount", required: false }] }], boundaryCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "boundaryCount", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
2593
+
2594
+ const dropdownContentVariants = cva('z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md', {
2595
+ variants: {},
2596
+ defaultVariants: {},
2597
+ });
2598
+ const dropdownItemVariants = cva('relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[active]:bg-accent data-[active]:text-accent-foreground', {
2599
+ variants: {
2600
+ variant: {
2601
+ default: '',
2602
+ destructive: 'text-destructive data-[active]:bg-destructive/10 data-[active]:text-destructive',
2603
+ },
2604
+ },
2605
+ defaultVariants: {
2606
+ variant: 'default',
2607
+ },
2608
+ });
2609
+
2610
+ const SNY_DROPDOWN = new InjectionToken('SnyDropdown');
2611
+ class SnyDropdownDirective {
2612
+ elementRef = inject(ElementRef);
2613
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
2614
+ toggle() { this.isOpen.update((v) => !v); }
2615
+ open() { this.isOpen.set(true); }
2616
+ close() { this.isOpen.set(false); }
2617
+ onDocumentClick(event) {
2618
+ if (!this.elementRef.nativeElement.contains(event.target)) {
2619
+ this.close();
2620
+ }
2621
+ }
2622
+ onEscape() {
2623
+ this.close();
2624
+ }
2625
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2626
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: SnyDropdownDirective, isStandalone: true, selector: "[snyDropdown]", host: { listeners: { "document:click": "onDocumentClick($event)", "keydown.escape": "onEscape()" }, properties: { "class": "\"relative inline-block\"" } }, providers: [{ provide: SNY_DROPDOWN, useExisting: SnyDropdownDirective }], exportAs: ["snyDropdown"], ngImport: i0 });
2627
+ }
2628
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownDirective, decorators: [{
2629
+ type: Directive,
2630
+ args: [{
2631
+ selector: '[snyDropdown]',
2632
+ standalone: true,
2633
+ exportAs: 'snyDropdown',
2634
+ providers: [{ provide: SNY_DROPDOWN, useExisting: SnyDropdownDirective }],
2635
+ host: {
2636
+ '[class]': '"relative inline-block"',
2637
+ },
2638
+ }]
2639
+ }], propDecorators: { onDocumentClick: [{
2640
+ type: HostListener,
2641
+ args: ['document:click', ['$event']]
2642
+ }], onEscape: [{
2643
+ type: HostListener,
2644
+ args: ['keydown.escape']
2645
+ }] } });
2646
+ class SnyDropdownTriggerDirective {
2647
+ dropdown = inject(SNY_DROPDOWN);
2648
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2649
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: SnyDropdownTriggerDirective, isStandalone: true, selector: "[snyDropdownTrigger]", host: { listeners: { "click": "dropdown.toggle()" }, properties: { "attr.aria-expanded": "dropdown.isOpen()", "attr.aria-haspopup": "\"menu\"" } }, ngImport: i0 });
2650
+ }
2651
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownTriggerDirective, decorators: [{
2652
+ type: Directive,
2653
+ args: [{
2654
+ selector: '[snyDropdownTrigger]',
2655
+ standalone: true,
2656
+ host: {
2657
+ '(click)': 'dropdown.toggle()',
2658
+ '[attr.aria-expanded]': 'dropdown.isOpen()',
2659
+ '[attr.aria-haspopup]': '"menu"',
2660
+ },
2661
+ }]
2662
+ }] });
2663
+ class SnyDropdownContentDirective {
2664
+ dropdown = inject(SNY_DROPDOWN);
2665
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
2666
+ computedClass = computed(() => cn(dropdownContentVariants(), 'absolute mt-1 left-0 animate-in fade-in-0 zoom-in-95', this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
2667
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2668
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyDropdownContentDirective, isStandalone: true, selector: "[snyDropdownContent]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menu" }, properties: { "class": "computedClass()", "style.display": "dropdown.isOpen() ? \"\" : \"none\"" } }, ngImport: i0 });
2669
+ }
2670
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownContentDirective, decorators: [{
2671
+ type: Directive,
2672
+ args: [{
2673
+ selector: '[snyDropdownContent]',
2674
+ standalone: true,
2675
+ host: {
2676
+ 'role': 'menu',
2677
+ '[class]': 'computedClass()',
2678
+ '[style.display]': 'dropdown.isOpen() ? "" : "none"',
2679
+ },
2680
+ }]
2681
+ }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
2682
+ class SnyMenuContentDirective {
2683
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
2684
+ computedClass = computed(() => cn(dropdownContentVariants(), this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
2685
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2686
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyMenuContentDirective, isStandalone: true, selector: "[snyMenuContent]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "computedClass()" } }, ngImport: i0 });
2687
+ }
2688
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuContentDirective, decorators: [{
2689
+ type: Directive,
2690
+ args: [{
2691
+ selector: '[snyMenuContent]',
2692
+ standalone: true,
2693
+ host: {
2694
+ '[class]': 'computedClass()',
2695
+ },
2696
+ }]
2697
+ }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
2698
+ class SnyMenuItemDirective {
2699
+ dropdown = inject(SNY_DROPDOWN, { optional: true });
2700
+ variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
2701
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
2702
+ computedClass = computed(() => cn(dropdownItemVariants({ variant: this.variant() }), 'cursor-pointer hover:bg-accent hover:text-accent-foreground', this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
2703
+ onClick() {
2704
+ this.dropdown?.close();
2705
+ }
2706
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2707
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyMenuItemDirective, isStandalone: true, selector: "[snyMenuItem]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menuitem" }, listeners: { "click": "onClick()" }, properties: { "class": "computedClass()" } }, ngImport: i0 });
2708
+ }
2709
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuItemDirective, decorators: [{
2710
+ type: Directive,
2711
+ args: [{
2712
+ selector: '[snyMenuItem]',
2713
+ standalone: true,
2714
+ host: {
2715
+ 'role': 'menuitem',
2716
+ '[class]': 'computedClass()',
2717
+ '(click)': 'onClick()',
2718
+ },
2719
+ }]
2720
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
2721
+ class SnyMenuSeparatorDirective {
2722
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
2723
+ computedClass = computed(() => cn('-mx-1 my-1 h-px bg-muted', this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
2724
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuSeparatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2725
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyMenuSeparatorDirective, isStandalone: true, selector: "[snyMenuSeparator]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "separator" }, properties: { "class": "computedClass()" } }, ngImport: i0 });
2726
+ }
2727
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuSeparatorDirective, decorators: [{
2728
+ type: Directive,
2729
+ args: [{
2730
+ selector: '[snyMenuSeparator]',
2731
+ standalone: true,
2732
+ host: {
2733
+ 'role': 'separator',
2734
+ '[class]': 'computedClass()',
2735
+ },
2736
+ }]
2737
+ }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
2738
+ class SnyMenuLabelDirective {
2739
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
2740
+ computedClass = computed(() => cn('px-2 py-1.5 text-sm font-semibold', this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
2741
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuLabelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2742
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyMenuLabelDirective, isStandalone: true, selector: "[snyMenuLabel]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "computedClass()" } }, ngImport: i0 });
2743
+ }
2744
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuLabelDirective, decorators: [{
2745
+ type: Directive,
2746
+ args: [{
2747
+ selector: '[snyMenuLabel]',
2748
+ standalone: true,
2749
+ host: {
2750
+ '[class]': 'computedClass()',
2751
+ },
2752
+ }]
2753
+ }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
2754
+
2755
+ class SnyCellDefDirective {
2756
+ snyCell = input.required(...(ngDevMode ? [{ debugName: "snyCell" }] : /* istanbul ignore next */ []));
2757
+ template = inject(TemplateRef);
2758
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCellDefDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2759
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyCellDefDirective, isStandalone: true, selector: "[snyCell]", inputs: { snyCell: { classPropertyName: "snyCell", publicName: "snyCell", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
2760
+ }
2761
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCellDefDirective, decorators: [{
2762
+ type: Directive,
2763
+ args: [{
2764
+ selector: '[snyCell]',
2765
+ standalone: true,
2766
+ }]
2767
+ }], propDecorators: { snyCell: [{ type: i0.Input, args: [{ isSignal: true, alias: "snyCell", required: true }] }] } });
2768
+ class SnyHeaderCellDefDirective {
2769
+ snyHeaderCell = input.required(...(ngDevMode ? [{ debugName: "snyHeaderCell" }] : /* istanbul ignore next */ []));
2770
+ template = inject(TemplateRef);
2771
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyHeaderCellDefDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2772
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyHeaderCellDefDirective, isStandalone: true, selector: "[snyHeaderCell]", inputs: { snyHeaderCell: { classPropertyName: "snyHeaderCell", publicName: "snyHeaderCell", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
2773
+ }
2774
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyHeaderCellDefDirective, decorators: [{
2775
+ type: Directive,
2776
+ args: [{
2777
+ selector: '[snyHeaderCell]',
2778
+ standalone: true,
2779
+ }]
2780
+ }], propDecorators: { snyHeaderCell: [{ type: i0.Input, args: [{ isSignal: true, alias: "snyHeaderCell", required: true }] }] } });
2781
+ class SnyBulkActionsDefDirective {
2782
+ template = inject(TemplateRef);
2783
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyBulkActionsDefDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2784
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: SnyBulkActionsDefDirective, isStandalone: true, selector: "[snyBulkActions]", ngImport: i0 });
2785
+ }
2786
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyBulkActionsDefDirective, decorators: [{
2787
+ type: Directive,
2788
+ args: [{
2789
+ selector: '[snyBulkActions]',
2790
+ standalone: true,
2791
+ }]
2792
+ }] });
2793
+ class SnyRowExpandDefDirective {
2794
+ template = inject(TemplateRef);
2795
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyRowExpandDefDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2796
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: SnyRowExpandDefDirective, isStandalone: true, selector: "[snyRowExpand]", ngImport: i0 });
2797
+ }
2798
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyRowExpandDefDirective, decorators: [{
2799
+ type: Directive,
2800
+ args: [{
2801
+ selector: '[snyRowExpand]',
2802
+ standalone: true,
2803
+ }]
2804
+ }] });
2805
+
2806
+ const DEFAULT_PAGINATION = {
2807
+ pageSize: 10,
2808
+ pageSizeOptions: [5, 10, 25, 50],
2809
+ };
2810
+ class SnyDataTableComponent {
2811
+ // Inputs
2812
+ columns = input.required(...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
2813
+ data = input.required(...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
2814
+ variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
2815
+ density = input('normal', ...(ngDevMode ? [{ debugName: "density" }] : /* istanbul ignore next */ []));
2816
+ hoverable = input(true, ...(ngDevMode ? [{ debugName: "hoverable" }] : /* istanbul ignore next */ []));
2817
+ stickyHeader = input(false, ...(ngDevMode ? [{ debugName: "stickyHeader" }] : /* istanbul ignore next */ []));
2818
+ selectable = input(false, ...(ngDevMode ? [{ debugName: "selectable" }] : /* istanbul ignore next */ []));
2819
+ paginated = input(true, ...(ngDevMode ? [{ debugName: "paginated" }] : /* istanbul ignore next */ []));
2820
+ filterable = input(true, ...(ngDevMode ? [{ debugName: "filterable" }] : /* istanbul ignore next */ []));
2821
+ showExport = input(false, ...(ngDevMode ? [{ debugName: "showExport" }] : /* istanbul ignore next */ []));
2822
+ showColumnToggle = input(false, ...(ngDevMode ? [{ debugName: "showColumnToggle" }] : /* istanbul ignore next */ []));
2823
+ expandable = input(false, ...(ngDevMode ? [{ debugName: "expandable" }] : /* istanbul ignore next */ []));
2824
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
2825
+ loadingRows = input(5, ...(ngDevMode ? [{ debugName: "loadingRows" }] : /* istanbul ignore next */ []));
2826
+ paginationConfig = input(DEFAULT_PAGINATION, ...(ngDevMode ? [{ debugName: "paginationConfig" }] : /* istanbul ignore next */ []));
2827
+ trackBy = input('', ...(ngDevMode ? [{ debugName: "trackBy" }] : /* istanbul ignore next */ []));
2828
+ noDataText = input('No data available', ...(ngDevMode ? [{ debugName: "noDataText" }] : /* istanbul ignore next */ []));
2829
+ // Model
2830
+ selectedRows = model([], ...(ngDevMode ? [{ debugName: "selectedRows" }] : /* istanbul ignore next */ []));
2831
+ // Outputs
2832
+ sortChanged = output();
2833
+ rowClicked = output();
2834
+ dataExported = output();
2835
+ // Content queries
2836
+ cellDefs = contentChildren(SnyCellDefDirective, ...(ngDevMode ? [{ debugName: "cellDefs" }] : /* istanbul ignore next */ []));
2837
+ headerCellDefs = contentChildren(SnyHeaderCellDefDirective, ...(ngDevMode ? [{ debugName: "headerCellDefs" }] : /* istanbul ignore next */ []));
2838
+ bulkActionsDef = contentChild(SnyBulkActionsDefDirective, ...(ngDevMode ? [{ debugName: "bulkActionsDef" }] : /* istanbul ignore next */ []));
2839
+ rowExpandDef = contentChild(SnyRowExpandDefDirective, ...(ngDevMode ? [{ debugName: "rowExpandDef" }] : /* istanbul ignore next */ []));
2840
+ // Internal state
2841
+ sortState = signal({ key: '', direction: null }, ...(ngDevMode ? [{ debugName: "sortState" }] : /* istanbul ignore next */ []));
2842
+ filterText = signal('', ...(ngDevMode ? [{ debugName: "filterText" }] : /* istanbul ignore next */ []));
2843
+ currentPage = signal(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
2844
+ pageSize = signal(10, ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
2845
+ hiddenColumns = signal(new Set(), ...(ngDevMode ? [{ debugName: "hiddenColumns" }] : /* istanbul ignore next */ []));
2846
+ expandedRows = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedRows" }] : /* istanbul ignore next */ []));
2847
+ // Template def maps
2848
+ cellDefMap = computed(() => {
2849
+ const map = new Map();
2850
+ for (const def of this.cellDefs()) {
2851
+ map.set(def.snyCell(), def.template);
2852
+ }
2853
+ return map;
2854
+ }, ...(ngDevMode ? [{ debugName: "cellDefMap" }] : /* istanbul ignore next */ []));
2855
+ headerCellDefMap = computed(() => {
2856
+ const map = new Map();
2857
+ for (const def of this.headerCellDefs()) {
2858
+ map.set(def.snyHeaderCell(), def.template);
2859
+ }
2860
+ return map;
2861
+ }, ...(ngDevMode ? [{ debugName: "headerCellDefMap" }] : /* istanbul ignore next */ []));
2862
+ // Visible columns
2863
+ visibleColumns = computed(() => this.columns().filter((col) => col.visible !== false && !this.hiddenColumns().has(col.key)), ...(ngDevMode ? [{ debugName: "visibleColumns" }] : /* istanbul ignore next */ []));
2864
+ // Page size options
2865
+ pageSizeOptions = computed(() => this.paginationConfig().pageSizeOptions.map((n) => ({
2866
+ value: String(n),
2867
+ label: String(n),
2868
+ })), ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : /* istanbul ignore next */ []));
2869
+ pageSizeValue = computed(() => String(this.pageSize()), ...(ngDevMode ? [{ debugName: "pageSizeValue" }] : /* istanbul ignore next */ []));
2870
+ // Skeleton rows
2871
+ skeletonRows = computed(() => Array.from({ length: this.loadingRows() }, (_, i) => i), ...(ngDevMode ? [{ debugName: "skeletonRows" }] : /* istanbul ignore next */ []));
2872
+ // Bulk actions visibility
2873
+ showBulkActions = computed(() => this.selectable() &&
2874
+ this.selectedRows().length > 0 &&
2875
+ this.bulkActionsDef() != null, ...(ngDevMode ? [{ debugName: "showBulkActions" }] : /* istanbul ignore next */ []));
2876
+ // Data pipeline (filter uses all columns, not just visible)
2877
+ filteredData = computed(() => {
2878
+ const text = this.filterText().toLowerCase().trim();
2879
+ const rows = this.data();
2880
+ if (!text)
2881
+ return rows;
2882
+ const cols = this.columns().filter((c) => c.filterable !== false);
2883
+ return rows.filter((row) => cols.some((col) => String(row[col.key] ?? '').toLowerCase().includes(text)));
2884
+ }, ...(ngDevMode ? [{ debugName: "filteredData" }] : /* istanbul ignore next */ []));
2885
+ sortedData = computed(() => {
2886
+ const { key, direction } = this.sortState();
2887
+ const rows = this.filteredData();
2888
+ if (!key || !direction)
2889
+ return rows;
2890
+ return [...rows].sort((a, b) => {
2891
+ const aVal = a[key];
2892
+ const bVal = b[key];
2893
+ if (aVal == null && bVal == null)
2894
+ return 0;
2895
+ if (aVal == null)
2896
+ return direction === 'asc' ? -1 : 1;
2897
+ if (bVal == null)
2898
+ return direction === 'asc' ? 1 : -1;
2899
+ if (typeof aVal === 'number' && typeof bVal === 'number') {
2900
+ return direction === 'asc' ? aVal - bVal : bVal - aVal;
2901
+ }
2902
+ const cmp = String(aVal).localeCompare(String(bVal));
2903
+ return direction === 'asc' ? cmp : -cmp;
2904
+ });
2905
+ }, ...(ngDevMode ? [{ debugName: "sortedData" }] : /* istanbul ignore next */ []));
2906
+ totalPages = computed(() => Math.max(1, Math.ceil(this.filteredData().length / this.pageSize())), ...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
2907
+ paginatedData = computed(() => {
2908
+ if (!this.paginated())
2909
+ return this.sortedData();
2910
+ const start = (this.currentPage() - 1) * this.pageSize();
2911
+ return this.sortedData().slice(start, start + this.pageSize());
2912
+ }, ...(ngDevMode ? [{ debugName: "paginatedData" }] : /* istanbul ignore next */ []));
2913
+ totalColSpan = computed(() => this.visibleColumns().length +
2914
+ (this.selectable() ? 1 : 0) +
2915
+ (this.expandable() ? 1 : 0), ...(ngDevMode ? [{ debugName: "totalColSpan" }] : /* istanbul ignore next */ []));
2916
+ // Selection computed
2917
+ allSelected = computed(() => {
2918
+ const page = this.paginatedData();
2919
+ if (page.length === 0)
2920
+ return false;
2921
+ const selected = this.selectedRows();
2922
+ return page.every((row) => this.isRowInList(row, selected));
2923
+ }, ...(ngDevMode ? [{ debugName: "allSelected" }] : /* istanbul ignore next */ []));
2924
+ someSelected = computed(() => {
2925
+ const page = this.paginatedData();
2926
+ const selected = this.selectedRows();
2927
+ return page.some((row) => this.isRowInList(row, selected));
2928
+ }, ...(ngDevMode ? [{ debugName: "someSelected" }] : /* istanbul ignore next */ []));
2929
+ constructor() {
2930
+ effect(() => {
2931
+ const config = this.paginationConfig();
2932
+ untracked(() => this.pageSize.set(config.pageSize));
2933
+ });
2934
+ effect(() => {
2935
+ this.filterText();
2936
+ this.pageSize();
2937
+ this.data();
2938
+ untracked(() => this.currentPage.set(1));
2939
+ });
2940
+ }
2941
+ // Sort
2942
+ toggleSort(key) {
2943
+ const current = this.sortState();
2944
+ let direction;
2945
+ if (current.key !== key) {
2946
+ direction = 'asc';
2947
+ }
2948
+ else if (current.direction === 'asc') {
2949
+ direction = 'desc';
2950
+ }
2951
+ else if (current.direction === 'desc') {
2952
+ direction = null;
2953
+ }
2954
+ else {
2955
+ direction = 'asc';
2956
+ }
2957
+ const next = { key: direction ? key : '', direction };
2958
+ this.sortState.set(next);
2959
+ this.sortChanged.emit(next);
2960
+ }
2961
+ // Filter
2962
+ onFilterInput(event) {
2963
+ this.filterText.set(event.target.value);
2964
+ }
2965
+ // Page size
2966
+ onPageSizeChange(value) {
2967
+ this.pageSize.set(Number(value));
2968
+ }
2969
+ // Selection
2970
+ toggleSelectAll() {
2971
+ if (this.allSelected()) {
2972
+ const page = this.paginatedData();
2973
+ this.selectedRows.update((sel) => sel.filter((r) => !page.some((p) => this.rowsEqual(r, p))));
2974
+ }
2975
+ else {
2976
+ const page = this.paginatedData();
2977
+ this.selectedRows.update((sel) => {
2978
+ const newSel = [...sel];
2979
+ for (const row of page) {
2980
+ if (!this.isRowInList(row, newSel))
2981
+ newSel.push(row);
2982
+ }
2983
+ return newSel;
2984
+ });
2985
+ }
2986
+ }
2987
+ toggleRowSelection(row) {
2988
+ this.selectedRows.update((sel) => this.isRowInList(row, sel)
2989
+ ? sel.filter((r) => !this.rowsEqual(r, row))
2990
+ : [...sel, row]);
2991
+ }
2992
+ // Row click
2993
+ onRowClick(row) {
2994
+ this.rowClicked.emit(row);
2995
+ }
2996
+ // Export
2997
+ onExport() {
2998
+ this.dataExported.emit(this.filteredData());
2999
+ }
3000
+ // Column visibility
3001
+ toggleColumnVisibility(key) {
3002
+ this.hiddenColumns.update((set) => {
3003
+ const next = new Set(set);
3004
+ if (next.has(key))
3005
+ next.delete(key);
3006
+ else
3007
+ next.add(key);
3008
+ return next;
3009
+ });
3010
+ }
3011
+ // Expansion
3012
+ toggleRowExpansion(row) {
3013
+ const key = this.trackBy() ? row[this.trackBy()] : row;
3014
+ this.expandedRows.update((set) => {
3015
+ const next = new Set(set);
3016
+ if (next.has(key))
3017
+ next.delete(key);
3018
+ else
3019
+ next.add(key);
3020
+ return next;
3021
+ });
3022
+ }
3023
+ isExpanded(row) {
3024
+ const key = this.trackBy() ? row[this.trackBy()] : row;
3025
+ return this.expandedRows().has(key);
3026
+ }
3027
+ // Helpers
3028
+ isSelected(row) {
3029
+ return this.isRowInList(row, this.selectedRows());
3030
+ }
3031
+ trackByFn(row, index) {
3032
+ const key = this.trackBy();
3033
+ return key ? row[key] : index;
3034
+ }
3035
+ isRowInList(row, list) {
3036
+ return list.some((r) => this.rowsEqual(r, row));
3037
+ }
3038
+ rowsEqual(a, b) {
3039
+ const key = this.trackBy();
3040
+ if (key)
3041
+ return a[key] === b[key];
3042
+ return a === b;
3043
+ }
3044
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3045
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyDataTableComponent, isStandalone: true, selector: "sny-data-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, hoverable: { classPropertyName: "hoverable", publicName: "hoverable", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, paginated: { classPropertyName: "paginated", publicName: "paginated", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, showExport: { classPropertyName: "showExport", publicName: "showExport", isSignal: true, isRequired: false, transformFunction: null }, showColumnToggle: { classPropertyName: "showColumnToggle", publicName: "showColumnToggle", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, loadingRows: { classPropertyName: "loadingRows", publicName: "loadingRows", isSignal: true, isRequired: false, transformFunction: null }, paginationConfig: { classPropertyName: "paginationConfig", publicName: "paginationConfig", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null }, noDataText: { classPropertyName: "noDataText", publicName: "noDataText", isSignal: true, isRequired: false, transformFunction: null }, selectedRows: { classPropertyName: "selectedRows", publicName: "selectedRows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedRows: "selectedRowsChange", sortChanged: "sortChanged", rowClicked: "rowClicked", dataExported: "dataExported" }, queries: [{ propertyName: "cellDefs", predicate: SnyCellDefDirective, isSignal: true }, { propertyName: "headerCellDefs", predicate: SnyHeaderCellDefDirective, isSignal: true }, { propertyName: "bulkActionsDef", first: true, predicate: SnyBulkActionsDefDirective, descendants: true, isSignal: true }, { propertyName: "rowExpandDef", first: true, predicate: SnyRowExpandDefDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
3046
+ <!-- Toolbar -->
3047
+ @if (filterable() || showExport() || showColumnToggle()) {
3048
+ <div class="flex items-center justify-between gap-4 mb-4 flex-wrap">
3049
+ @if (filterable()) {
3050
+ <input
3051
+ snyInput
3052
+ [value]="filterText()"
3053
+ (input)="onFilterInput($event)"
3054
+ placeholder="Filter..."
3055
+ class="w-full sm:max-w-sm"
3056
+ />
3057
+ }
3058
+ <div class="flex items-center gap-2">
3059
+ @if (showExport()) {
3060
+ <button snyBtn variant="outline" size="sm" (click)="onExport()">
3061
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sm:mr-2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="M14 2v6h6"/><path d="M12 18v-6"/><path d="m9 15 3-3 3 3"/></svg>
3062
+ <span class="hidden sm:inline">Export</span>
3063
+ </button>
3064
+ }
3065
+ @if (showColumnToggle()) {
3066
+ <div snyDropdown class="relative">
3067
+ <button snyBtn variant="outline" size="sm" snyDropdownTrigger>
3068
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sm:mr-2"><path d="M12 3v18"/><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/></svg>
3069
+ <span class="hidden sm:inline">Columns</span>
3070
+ </button>
3071
+ <div snyDropdownContent class="w-48 right-0 left-auto">
3072
+ @for (col of columns(); track col.key) {
3073
+ <label snyMenuItem class="flex items-center gap-2 cursor-pointer">
3074
+ <input
3075
+ type="checkbox"
3076
+ snyCheckbox
3077
+ [checked]="!hiddenColumns().has(col.key)"
3078
+ (change)="toggleColumnVisibility(col.key)"
3079
+ (click)="$event.stopPropagation()"
3080
+ />
3081
+ {{ col.label }}
3082
+ </label>
3083
+ }
3084
+ </div>
3085
+ </div>
3086
+ }
3087
+ </div>
3088
+ </div>
3089
+ }
3090
+
3091
+ <!-- Bulk Actions Bar -->
3092
+ @if (showBulkActions()) {
3093
+ @let selected = selectedRows();
3094
+ <div class="flex items-center gap-2 mb-4 p-3 bg-muted/50 rounded-sm border border-border flex-wrap">
3095
+ <span class="text-sm font-medium text-muted-foreground mr-2">
3096
+ {{ selected.length }} selected
3097
+ </span>
3098
+ <ng-container
3099
+ [ngTemplateOutlet]="bulkActionsDef()!.template"
3100
+ [ngTemplateOutletContext]="{ $implicit: selected }"
3101
+ />
3102
+ <button
3103
+ snyBtn variant="ghost" size="sm" class="ml-auto"
3104
+ (click)="selectedRows.set([])"
3105
+ >
3106
+ Clear
3107
+ </button>
3108
+ </div>
3109
+ }
3110
+
3111
+ <!-- Table -->
3112
+ <div class="overflow-auto border border-border rounded-sm">
3113
+ <table
3114
+ snyTable
3115
+ [variant]="variant()"
3116
+ [density]="density()"
3117
+ [hoverable]="hoverable()"
3118
+ [stickyHeader]="stickyHeader()"
3119
+ >
3120
+ <thead snyTableHeader>
3121
+ <tr snyTableRow>
3122
+ @if (selectable()) {
3123
+ <th snyTableHead class="w-12">
3124
+ <input
3125
+ type="checkbox"
3126
+ snyCheckbox
3127
+ [checked]="allSelected()"
3128
+ [indeterminate]="someSelected() && !allSelected()"
3129
+ (change)="toggleSelectAll()"
3130
+ />
3131
+ </th>
3132
+ }
3133
+ @if (expandable()) {
3134
+ <th snyTableHead class="w-10"></th>
3135
+ }
3136
+ @let sort = sortState();
3137
+ @let headerDefs = headerCellDefMap();
3138
+ @for (col of visibleColumns(); track col.key) {
3139
+ <th
3140
+ snyTableHead
3141
+ [style.width]="col.width ?? null"
3142
+ [class]="col.sortable ? 'cursor-pointer select-none' : ''"
3143
+ (click)="col.sortable ? toggleSort(col.key) : null"
3144
+ >
3145
+ @if (headerDefs.has(col.key)) {
3146
+ <ng-container
3147
+ [ngTemplateOutlet]="headerDefs.get(col.key)!"
3148
+ [ngTemplateOutletContext]="{ $implicit: col }"
3149
+ />
3150
+ } @else {
3151
+ <div class="flex items-center gap-1">
3152
+ <span>{{ col.label }}</span>
3153
+ @if (col.sortable) {
3154
+ @let isActive = sort.key === col.key;
3155
+ @if (isActive && sort.direction === 'asc') {
3156
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12 7-7 7 7"/></svg>
3157
+ } @else if (isActive && sort.direction === 'desc') {
3158
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m19 12-7 7-7-7"/></svg>
3159
+ } @else {
3160
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-30"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
3161
+ }
3162
+ }
3163
+ </div>
3164
+ }
3165
+ </th>
3166
+ }
3167
+ </tr>
3168
+ </thead>
3169
+ <tbody snyTableBody>
3170
+ @if (loading()) {
3171
+ @for (i of skeletonRows(); track i) {
3172
+ <tr snyTableRow>
3173
+ @if (selectable()) {
3174
+ <td snyTableCell class="w-12"><div snySkeleton class="w-4 h-4 rounded"></div></td>
3175
+ }
3176
+ @if (expandable()) {
3177
+ <td snyTableCell class="w-10"><div snySkeleton class="w-4 h-4 rounded"></div></td>
3178
+ }
3179
+ @for (col of visibleColumns(); track col.key) {
3180
+ <td snyTableCell [style.width]="col.width ?? null">
3181
+ <div snySkeleton class="w-full h-4 rounded"></div>
3182
+ </td>
3183
+ }
3184
+ </tr>
3185
+ }
3186
+ } @else if (paginatedData().length === 0) {
3187
+ <tr snyTableRow>
3188
+ <td
3189
+ snyTableCell
3190
+ [attr.colspan]="totalColSpan()"
3191
+ class="text-center text-muted-foreground py-8"
3192
+ >
3193
+ {{ noDataText() }}
3194
+ </td>
3195
+ </tr>
3196
+ } @else {
3197
+ @let cellDefs = cellDefMap();
3198
+ @let cols = visibleColumns();
3199
+ @let expandTpl = rowExpandDef();
3200
+ @for (row of paginatedData(); track trackByFn(row, $index)) {
3201
+ <tr
3202
+ snyTableRow
3203
+ [attr.data-state]="isSelected(row) ? 'selected' : null"
3204
+ (click)="onRowClick(row)"
3205
+ class="cursor-pointer"
3206
+ >
3207
+ @if (selectable()) {
3208
+ <td snyTableCell class="w-12">
3209
+ <input
3210
+ type="checkbox"
3211
+ snyCheckbox
3212
+ [checked]="isSelected(row)"
3213
+ (change)="toggleRowSelection(row)"
3214
+ (click)="$event.stopPropagation()"
3215
+ />
3216
+ </td>
3217
+ }
3218
+ @if (expandable()) {
3219
+ <td snyTableCell class="w-10">
3220
+ <button
3221
+ class="p-0.5 rounded hover:bg-accent transition-transform duration-150"
3222
+ [class.rotate-90]="isExpanded(row)"
3223
+ (click)="toggleRowExpansion(row); $event.stopPropagation()"
3224
+ >
3225
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
3226
+ </button>
3227
+ </td>
3228
+ }
3229
+ @for (col of cols; track col.key) {
3230
+ <td snyTableCell [style.width]="col.width ?? null">
3231
+ @if (cellDefs.has(col.key)) {
3232
+ <ng-container
3233
+ [ngTemplateOutlet]="cellDefs.get(col.key)!"
3234
+ [ngTemplateOutletContext]="{ $implicit: row[col.key], row: row }"
3235
+ />
3236
+ } @else {
3237
+ {{ row[col.key] }}
3238
+ }
3239
+ </td>
3240
+ }
3241
+ </tr>
3242
+ @if (expandable() && isExpanded(row) && expandTpl) {
3243
+ <tr snyTableRow>
3244
+ <td snyTableCell [attr.colspan]="totalColSpan()" class="bg-muted/30">
3245
+ <ng-container
3246
+ [ngTemplateOutlet]="expandTpl.template"
3247
+ [ngTemplateOutletContext]="{ $implicit: row }"
3248
+ />
3249
+ </td>
3250
+ </tr>
3251
+ }
3252
+ }
3253
+ }
3254
+ </tbody>
3255
+ </table>
3256
+ </div>
3257
+
3258
+ <!-- Footer -->
3259
+ @if (paginated()) {
3260
+ <div class="flex flex-col sm:flex-row items-start sm:items-center justify-between mt-4 gap-3 sm:gap-4">
3261
+ <span class="text-sm text-muted-foreground">
3262
+ @if (selectable()) {
3263
+ {{ selectedRows().length }} of {{ filteredData().length }} row(s) selected
3264
+ } @else {
3265
+ {{ filteredData().length }} row(s)
3266
+ }
3267
+ </span>
3268
+ <div class="flex items-center gap-3 sm:gap-4 flex-wrap">
3269
+ <div class="flex items-center gap-2">
3270
+ <span class="hidden sm:inline text-sm text-muted-foreground whitespace-nowrap">Rows per page</span>
3271
+ <sny-select
3272
+ [options]="pageSizeOptions()"
3273
+ [value]="pageSizeValue()"
3274
+ (valueChange)="onPageSizeChange($event)"
3275
+ size="sm"
3276
+ class="w-20"
3277
+ />
3278
+ </div>
3279
+ <sny-pagination
3280
+ [currentPage]="currentPage()"
3281
+ (currentPageChange)="currentPage.set($event)"
3282
+ [totalPages]="totalPages()"
3283
+ />
3284
+ </div>
3285
+ </div>
3286
+ }
3287
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: SnyTableDirective, selector: "table[snyTable]", inputs: ["variant", "density", "hoverable", "stickyHeader", "class"] }, { kind: "directive", type: SnyTableHeaderDirective, selector: "thead[snyTableHeader]", inputs: ["class"] }, { kind: "directive", type: SnyTableBodyDirective, selector: "tbody[snyTableBody]", inputs: ["class"] }, { kind: "directive", type: SnyTableRowDirective, selector: "tr[snyTableRow]", inputs: ["class"] }, { kind: "directive", type: SnyTableHeadDirective, selector: "th[snyTableHead]", inputs: ["class"] }, { kind: "directive", type: SnyTableCellDirective, selector: "td[snyTableCell]", inputs: ["class"] }, { kind: "component", type: SnyPaginationComponent, selector: "sny-pagination", inputs: ["currentPage", "totalPages", "siblingCount", "boundaryCount", "size", "variant", "class"], outputs: ["currentPageChange"] }, { kind: "directive", type: SnyCheckboxDirective, selector: "input[type=\"checkbox\"][snyCheckbox]", inputs: ["size", "class"] }, { kind: "directive", type: SnyInputDirective, selector: "input[snyInput], textarea[snyInput]", inputs: ["variant", "inputSize", "class", "ariaDescribedBy"] }, { kind: "directive", type: SnyButtonDirective, selector: "button[snyBtn], a[snyBtn]", inputs: ["variant", "size", "disabled", "loading", "class"] }, { kind: "component", type: SnySelectComponent, selector: "sny-select", inputs: ["options", "placeholder", "size", "disabled", "class", "value"], outputs: ["valueChange"] }, { kind: "directive", type: SnySkeletonDirective, selector: "[snySkeleton]", inputs: ["variant", "size", "class"] }, { kind: "directive", type: SnyDropdownDirective, selector: "[snyDropdown]", exportAs: ["snyDropdown"] }, { kind: "directive", type: SnyDropdownTriggerDirective, selector: "[snyDropdownTrigger]" }, { kind: "directive", type: SnyDropdownContentDirective, selector: "[snyDropdownContent]", inputs: ["class"] }, { kind: "directive", type: SnyMenuItemDirective, selector: "[snyMenuItem]", inputs: ["variant", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3288
+ }
3289
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDataTableComponent, decorators: [{
3290
+ type: Component,
3291
+ args: [{
3292
+ selector: 'sny-data-table',
3293
+ standalone: true,
3294
+ changeDetection: ChangeDetectionStrategy.OnPush,
3295
+ imports: [
3296
+ NgTemplateOutlet,
3297
+ SnyTableDirective,
3298
+ SnyTableHeaderDirective,
3299
+ SnyTableBodyDirective,
3300
+ SnyTableRowDirective,
3301
+ SnyTableHeadDirective,
3302
+ SnyTableCellDirective,
3303
+ SnyPaginationComponent,
3304
+ SnyCheckboxDirective,
3305
+ SnyInputDirective,
3306
+ SnyButtonDirective,
3307
+ SnySelectComponent,
3308
+ SnySkeletonDirective,
3309
+ SnyDropdownDirective,
3310
+ SnyDropdownTriggerDirective,
3311
+ SnyDropdownContentDirective,
3312
+ SnyMenuItemDirective,
3313
+ ],
3314
+ template: `
3315
+ <!-- Toolbar -->
3316
+ @if (filterable() || showExport() || showColumnToggle()) {
3317
+ <div class="flex items-center justify-between gap-4 mb-4 flex-wrap">
3318
+ @if (filterable()) {
3319
+ <input
3320
+ snyInput
3321
+ [value]="filterText()"
3322
+ (input)="onFilterInput($event)"
3323
+ placeholder="Filter..."
3324
+ class="w-full sm:max-w-sm"
3325
+ />
3326
+ }
3327
+ <div class="flex items-center gap-2">
3328
+ @if (showExport()) {
3329
+ <button snyBtn variant="outline" size="sm" (click)="onExport()">
3330
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sm:mr-2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="M14 2v6h6"/><path d="M12 18v-6"/><path d="m9 15 3-3 3 3"/></svg>
3331
+ <span class="hidden sm:inline">Export</span>
3332
+ </button>
3333
+ }
3334
+ @if (showColumnToggle()) {
3335
+ <div snyDropdown class="relative">
3336
+ <button snyBtn variant="outline" size="sm" snyDropdownTrigger>
3337
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sm:mr-2"><path d="M12 3v18"/><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/></svg>
3338
+ <span class="hidden sm:inline">Columns</span>
3339
+ </button>
3340
+ <div snyDropdownContent class="w-48 right-0 left-auto">
3341
+ @for (col of columns(); track col.key) {
3342
+ <label snyMenuItem class="flex items-center gap-2 cursor-pointer">
3343
+ <input
3344
+ type="checkbox"
3345
+ snyCheckbox
3346
+ [checked]="!hiddenColumns().has(col.key)"
3347
+ (change)="toggleColumnVisibility(col.key)"
3348
+ (click)="$event.stopPropagation()"
3349
+ />
3350
+ {{ col.label }}
3351
+ </label>
3352
+ }
3353
+ </div>
3354
+ </div>
3355
+ }
3356
+ </div>
3357
+ </div>
3358
+ }
3359
+
3360
+ <!-- Bulk Actions Bar -->
3361
+ @if (showBulkActions()) {
3362
+ @let selected = selectedRows();
3363
+ <div class="flex items-center gap-2 mb-4 p-3 bg-muted/50 rounded-sm border border-border flex-wrap">
3364
+ <span class="text-sm font-medium text-muted-foreground mr-2">
3365
+ {{ selected.length }} selected
3366
+ </span>
3367
+ <ng-container
3368
+ [ngTemplateOutlet]="bulkActionsDef()!.template"
3369
+ [ngTemplateOutletContext]="{ $implicit: selected }"
3370
+ />
3371
+ <button
3372
+ snyBtn variant="ghost" size="sm" class="ml-auto"
3373
+ (click)="selectedRows.set([])"
3374
+ >
3375
+ Clear
3376
+ </button>
3377
+ </div>
3378
+ }
3379
+
3380
+ <!-- Table -->
3381
+ <div class="overflow-auto border border-border rounded-sm">
3382
+ <table
3383
+ snyTable
3384
+ [variant]="variant()"
3385
+ [density]="density()"
3386
+ [hoverable]="hoverable()"
3387
+ [stickyHeader]="stickyHeader()"
3388
+ >
3389
+ <thead snyTableHeader>
3390
+ <tr snyTableRow>
3391
+ @if (selectable()) {
3392
+ <th snyTableHead class="w-12">
3393
+ <input
3394
+ type="checkbox"
3395
+ snyCheckbox
3396
+ [checked]="allSelected()"
3397
+ [indeterminate]="someSelected() && !allSelected()"
3398
+ (change)="toggleSelectAll()"
3399
+ />
3400
+ </th>
3401
+ }
3402
+ @if (expandable()) {
3403
+ <th snyTableHead class="w-10"></th>
3404
+ }
3405
+ @let sort = sortState();
3406
+ @let headerDefs = headerCellDefMap();
3407
+ @for (col of visibleColumns(); track col.key) {
3408
+ <th
3409
+ snyTableHead
3410
+ [style.width]="col.width ?? null"
3411
+ [class]="col.sortable ? 'cursor-pointer select-none' : ''"
3412
+ (click)="col.sortable ? toggleSort(col.key) : null"
3413
+ >
3414
+ @if (headerDefs.has(col.key)) {
3415
+ <ng-container
3416
+ [ngTemplateOutlet]="headerDefs.get(col.key)!"
3417
+ [ngTemplateOutletContext]="{ $implicit: col }"
3418
+ />
3419
+ } @else {
3420
+ <div class="flex items-center gap-1">
3421
+ <span>{{ col.label }}</span>
3422
+ @if (col.sortable) {
3423
+ @let isActive = sort.key === col.key;
3424
+ @if (isActive && sort.direction === 'asc') {
3425
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12 7-7 7 7"/></svg>
3426
+ } @else if (isActive && sort.direction === 'desc') {
3427
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m19 12-7 7-7-7"/></svg>
3428
+ } @else {
3429
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-30"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
3430
+ }
3431
+ }
3432
+ </div>
3433
+ }
3434
+ </th>
3435
+ }
3436
+ </tr>
3437
+ </thead>
3438
+ <tbody snyTableBody>
3439
+ @if (loading()) {
3440
+ @for (i of skeletonRows(); track i) {
3441
+ <tr snyTableRow>
3442
+ @if (selectable()) {
3443
+ <td snyTableCell class="w-12"><div snySkeleton class="w-4 h-4 rounded"></div></td>
3444
+ }
3445
+ @if (expandable()) {
3446
+ <td snyTableCell class="w-10"><div snySkeleton class="w-4 h-4 rounded"></div></td>
3447
+ }
3448
+ @for (col of visibleColumns(); track col.key) {
3449
+ <td snyTableCell [style.width]="col.width ?? null">
3450
+ <div snySkeleton class="w-full h-4 rounded"></div>
3451
+ </td>
3452
+ }
3453
+ </tr>
3454
+ }
3455
+ } @else if (paginatedData().length === 0) {
3456
+ <tr snyTableRow>
3457
+ <td
3458
+ snyTableCell
3459
+ [attr.colspan]="totalColSpan()"
3460
+ class="text-center text-muted-foreground py-8"
3461
+ >
3462
+ {{ noDataText() }}
3463
+ </td>
3464
+ </tr>
3465
+ } @else {
3466
+ @let cellDefs = cellDefMap();
3467
+ @let cols = visibleColumns();
3468
+ @let expandTpl = rowExpandDef();
3469
+ @for (row of paginatedData(); track trackByFn(row, $index)) {
3470
+ <tr
3471
+ snyTableRow
3472
+ [attr.data-state]="isSelected(row) ? 'selected' : null"
3473
+ (click)="onRowClick(row)"
3474
+ class="cursor-pointer"
3475
+ >
3476
+ @if (selectable()) {
3477
+ <td snyTableCell class="w-12">
3478
+ <input
3479
+ type="checkbox"
3480
+ snyCheckbox
3481
+ [checked]="isSelected(row)"
3482
+ (change)="toggleRowSelection(row)"
3483
+ (click)="$event.stopPropagation()"
3484
+ />
3485
+ </td>
3486
+ }
3487
+ @if (expandable()) {
3488
+ <td snyTableCell class="w-10">
3489
+ <button
3490
+ class="p-0.5 rounded hover:bg-accent transition-transform duration-150"
3491
+ [class.rotate-90]="isExpanded(row)"
3492
+ (click)="toggleRowExpansion(row); $event.stopPropagation()"
3493
+ >
3494
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
3495
+ </button>
3496
+ </td>
3497
+ }
3498
+ @for (col of cols; track col.key) {
3499
+ <td snyTableCell [style.width]="col.width ?? null">
3500
+ @if (cellDefs.has(col.key)) {
3501
+ <ng-container
3502
+ [ngTemplateOutlet]="cellDefs.get(col.key)!"
3503
+ [ngTemplateOutletContext]="{ $implicit: row[col.key], row: row }"
3504
+ />
3505
+ } @else {
3506
+ {{ row[col.key] }}
3507
+ }
3508
+ </td>
3509
+ }
3510
+ </tr>
3511
+ @if (expandable() && isExpanded(row) && expandTpl) {
3512
+ <tr snyTableRow>
3513
+ <td snyTableCell [attr.colspan]="totalColSpan()" class="bg-muted/30">
3514
+ <ng-container
3515
+ [ngTemplateOutlet]="expandTpl.template"
3516
+ [ngTemplateOutletContext]="{ $implicit: row }"
3517
+ />
3518
+ </td>
3519
+ </tr>
3520
+ }
3521
+ }
3522
+ }
3523
+ </tbody>
3524
+ </table>
3525
+ </div>
3526
+
3527
+ <!-- Footer -->
3528
+ @if (paginated()) {
3529
+ <div class="flex flex-col sm:flex-row items-start sm:items-center justify-between mt-4 gap-3 sm:gap-4">
3530
+ <span class="text-sm text-muted-foreground">
3531
+ @if (selectable()) {
3532
+ {{ selectedRows().length }} of {{ filteredData().length }} row(s) selected
3533
+ } @else {
3534
+ {{ filteredData().length }} row(s)
3535
+ }
3536
+ </span>
3537
+ <div class="flex items-center gap-3 sm:gap-4 flex-wrap">
3538
+ <div class="flex items-center gap-2">
3539
+ <span class="hidden sm:inline text-sm text-muted-foreground whitespace-nowrap">Rows per page</span>
3540
+ <sny-select
3541
+ [options]="pageSizeOptions()"
3542
+ [value]="pageSizeValue()"
3543
+ (valueChange)="onPageSizeChange($event)"
3544
+ size="sm"
3545
+ class="w-20"
3546
+ />
3547
+ </div>
3548
+ <sny-pagination
3549
+ [currentPage]="currentPage()"
3550
+ (currentPageChange)="currentPage.set($event)"
3551
+ [totalPages]="totalPages()"
3552
+ />
3553
+ </div>
3554
+ </div>
3555
+ }
3556
+ `,
3557
+ }]
3558
+ }], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], hoverable: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverable", required: false }] }], stickyHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyHeader", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], paginated: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginated", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], showExport: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExport", required: false }] }], showColumnToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showColumnToggle", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], loadingRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingRows", required: false }] }], paginationConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginationConfig", required: false }] }], trackBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackBy", required: false }] }], noDataText: [{ type: i0.Input, args: [{ isSignal: true, alias: "noDataText", required: false }] }], selectedRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedRows", required: false }] }, { type: i0.Output, args: ["selectedRowsChange"] }], sortChanged: [{ type: i0.Output, args: ["sortChanged"] }], rowClicked: [{ type: i0.Output, args: ["rowClicked"] }], dataExported: [{ type: i0.Output, args: ["dataExported"] }], cellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SnyCellDefDirective), { isSignal: true }] }], headerCellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SnyHeaderCellDefDirective), { isSignal: true }] }], bulkActionsDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SnyBulkActionsDefDirective), { isSignal: true }] }], rowExpandDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SnyRowExpandDefDirective), { isSignal: true }] }] } });
3559
+
2420
3560
  class SnySheetRef {
2421
3561
  cdkRef;
2422
3562
  constructor(cdkRef) {
@@ -2896,226 +4036,65 @@ class SnyTooltipDirective {
2896
4036
  tooltip.setAttribute('role', 'tooltip');
2897
4037
  tooltip.className = cn(tooltipVariants({ position: this.tooltipPosition() }), 'fixed pointer-events-none', this.class());
2898
4038
  tooltip.textContent = this.snyTooltip();
2899
- this.renderer.appendChild(document.body, tooltip);
2900
- this.tooltipEl = tooltip;
2901
- this.positionTooltip();
2902
- }
2903
- positionTooltip() {
2904
- if (!this.tooltipEl)
2905
- return;
2906
- const hostRect = this.el.nativeElement.getBoundingClientRect();
2907
- const tooltipRect = this.tooltipEl.getBoundingClientRect();
2908
- const position = this.tooltipPosition();
2909
- const gap = 8;
2910
- let top = 0;
2911
- let left = 0;
2912
- switch (position) {
2913
- case 'top':
2914
- top = hostRect.top - tooltipRect.height - gap;
2915
- left = hostRect.left + (hostRect.width - tooltipRect.width) / 2;
2916
- break;
2917
- case 'bottom':
2918
- top = hostRect.bottom + gap;
2919
- left = hostRect.left + (hostRect.width - tooltipRect.width) / 2;
2920
- break;
2921
- case 'left':
2922
- top = hostRect.top + (hostRect.height - tooltipRect.height) / 2;
2923
- left = hostRect.left - tooltipRect.width - gap;
2924
- break;
2925
- case 'right':
2926
- top = hostRect.top + (hostRect.height - tooltipRect.height) / 2;
2927
- left = hostRect.right + gap;
2928
- break;
2929
- }
2930
- this.tooltipEl.style.top = `${top}px`;
2931
- this.tooltipEl.style.left = `${left}px`;
2932
- }
2933
- destroyTooltip() {
2934
- if (this.tooltipEl) {
2935
- this.tooltipEl.remove();
2936
- this.tooltipEl = null;
2937
- }
2938
- }
2939
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2940
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyTooltipDirective, isStandalone: true, selector: "[snyTooltip]", inputs: { snyTooltip: { classPropertyName: "snyTooltip", publicName: "snyTooltip", isSignal: true, isRequired: true, transformFunction: null }, tooltipPosition: { classPropertyName: "tooltipPosition", publicName: "tooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, tooltipDelay: { classPropertyName: "tooltipDelay", publicName: "tooltipDelay", isSignal: true, isRequired: false, transformFunction: null }, tooltipDisabled: { classPropertyName: "tooltipDisabled", publicName: "tooltipDisabled", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "show()", "mouseleave": "hide()", "focus": "show()", "blur": "hide()", "keydown.escape": "hide()" }, properties: { "attr.aria-describedby": "isOpen() ? tooltipId : null" } }, exportAs: ["snyTooltip"], ngImport: i0 });
2941
- }
2942
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyTooltipDirective, decorators: [{
2943
- type: Directive,
2944
- args: [{
2945
- selector: '[snyTooltip]',
2946
- standalone: true,
2947
- exportAs: 'snyTooltip',
2948
- host: {
2949
- '(mouseenter)': 'show()',
2950
- '(mouseleave)': 'hide()',
2951
- '(focus)': 'show()',
2952
- '(blur)': 'hide()',
2953
- '(keydown.escape)': 'hide()',
2954
- '[attr.aria-describedby]': 'isOpen() ? tooltipId : null',
2955
- },
2956
- }]
2957
- }], ctorParameters: () => [], propDecorators: { snyTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "snyTooltip", required: true }] }], tooltipPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipPosition", required: false }] }], tooltipDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipDelay", required: false }] }], tooltipDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipDisabled", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
2958
-
2959
- const dropdownContentVariants = cva('z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md', {
2960
- variants: {},
2961
- defaultVariants: {},
2962
- });
2963
- const dropdownItemVariants = cva('relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[active]:bg-accent data-[active]:text-accent-foreground', {
2964
- variants: {
2965
- variant: {
2966
- default: '',
2967
- destructive: 'text-destructive data-[active]:bg-destructive/10 data-[active]:text-destructive',
2968
- },
2969
- },
2970
- defaultVariants: {
2971
- variant: 'default',
2972
- },
2973
- });
2974
-
2975
- const SNY_DROPDOWN = new InjectionToken('SnyDropdown');
2976
- class SnyDropdownDirective {
2977
- elementRef = inject(ElementRef);
2978
- isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
2979
- toggle() { this.isOpen.update((v) => !v); }
2980
- open() { this.isOpen.set(true); }
2981
- close() { this.isOpen.set(false); }
2982
- onDocumentClick(event) {
2983
- if (!this.elementRef.nativeElement.contains(event.target)) {
2984
- this.close();
2985
- }
2986
- }
2987
- onEscape() {
2988
- this.close();
2989
- }
2990
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2991
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: SnyDropdownDirective, isStandalone: true, selector: "[snyDropdown]", host: { listeners: { "document:click": "onDocumentClick($event)", "keydown.escape": "onEscape()" }, properties: { "class": "\"relative inline-block\"" } }, providers: [{ provide: SNY_DROPDOWN, useExisting: SnyDropdownDirective }], exportAs: ["snyDropdown"], ngImport: i0 });
2992
- }
2993
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownDirective, decorators: [{
2994
- type: Directive,
2995
- args: [{
2996
- selector: '[snyDropdown]',
2997
- standalone: true,
2998
- exportAs: 'snyDropdown',
2999
- providers: [{ provide: SNY_DROPDOWN, useExisting: SnyDropdownDirective }],
3000
- host: {
3001
- '[class]': '"relative inline-block"',
3002
- },
3003
- }]
3004
- }], propDecorators: { onDocumentClick: [{
3005
- type: HostListener,
3006
- args: ['document:click', ['$event']]
3007
- }], onEscape: [{
3008
- type: HostListener,
3009
- args: ['keydown.escape']
3010
- }] } });
3011
- class SnyDropdownTriggerDirective {
3012
- dropdown = inject(SNY_DROPDOWN);
3013
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3014
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: SnyDropdownTriggerDirective, isStandalone: true, selector: "[snyDropdownTrigger]", host: { listeners: { "click": "dropdown.toggle()" }, properties: { "attr.aria-expanded": "dropdown.isOpen()", "attr.aria-haspopup": "\"menu\"" } }, ngImport: i0 });
3015
- }
3016
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownTriggerDirective, decorators: [{
3017
- type: Directive,
3018
- args: [{
3019
- selector: '[snyDropdownTrigger]',
3020
- standalone: true,
3021
- host: {
3022
- '(click)': 'dropdown.toggle()',
3023
- '[attr.aria-expanded]': 'dropdown.isOpen()',
3024
- '[attr.aria-haspopup]': '"menu"',
3025
- },
3026
- }]
3027
- }] });
3028
- class SnyDropdownContentDirective {
3029
- dropdown = inject(SNY_DROPDOWN);
3030
- class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
3031
- computedClass = computed(() => cn(dropdownContentVariants(), 'absolute mt-1 left-0 animate-in fade-in-0 zoom-in-95', this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
3032
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3033
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyDropdownContentDirective, isStandalone: true, selector: "[snyDropdownContent]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menu" }, properties: { "class": "computedClass()", "style.display": "dropdown.isOpen() ? \"\" : \"none\"" } }, ngImport: i0 });
3034
- }
3035
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDropdownContentDirective, decorators: [{
3036
- type: Directive,
3037
- args: [{
3038
- selector: '[snyDropdownContent]',
3039
- standalone: true,
3040
- host: {
3041
- 'role': 'menu',
3042
- '[class]': 'computedClass()',
3043
- '[style.display]': 'dropdown.isOpen() ? "" : "none"',
3044
- },
3045
- }]
3046
- }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3047
- class SnyMenuContentDirective {
3048
- class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
3049
- computedClass = computed(() => cn(dropdownContentVariants(), this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
3050
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3051
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyMenuContentDirective, isStandalone: true, selector: "[snyMenuContent]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "computedClass()" } }, ngImport: i0 });
3052
- }
3053
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuContentDirective, decorators: [{
3054
- type: Directive,
3055
- args: [{
3056
- selector: '[snyMenuContent]',
3057
- standalone: true,
3058
- host: {
3059
- '[class]': 'computedClass()',
3060
- },
3061
- }]
3062
- }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3063
- class SnyMenuItemDirective {
3064
- dropdown = inject(SNY_DROPDOWN, { optional: true });
3065
- variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
3066
- class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
3067
- computedClass = computed(() => cn(dropdownItemVariants({ variant: this.variant() }), 'cursor-pointer hover:bg-accent hover:text-accent-foreground', this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
3068
- onClick() {
3069
- this.dropdown?.close();
4039
+ this.renderer.appendChild(document.body, tooltip);
4040
+ this.tooltipEl = tooltip;
4041
+ this.positionTooltip();
3070
4042
  }
3071
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3072
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyMenuItemDirective, isStandalone: true, selector: "[snyMenuItem]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menuitem" }, listeners: { "click": "onClick()" }, properties: { "class": "computedClass()" } }, ngImport: i0 });
3073
- }
3074
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuItemDirective, decorators: [{
3075
- type: Directive,
3076
- args: [{
3077
- selector: '[snyMenuItem]',
3078
- standalone: true,
3079
- host: {
3080
- 'role': 'menuitem',
3081
- '[class]': 'computedClass()',
3082
- '(click)': 'onClick()',
3083
- },
3084
- }]
3085
- }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3086
- class SnyMenuSeparatorDirective {
3087
- class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
3088
- computedClass = computed(() => cn('-mx-1 my-1 h-px bg-muted', this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
3089
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuSeparatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3090
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyMenuSeparatorDirective, isStandalone: true, selector: "[snyMenuSeparator]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "separator" }, properties: { "class": "computedClass()" } }, ngImport: i0 });
3091
- }
3092
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuSeparatorDirective, decorators: [{
3093
- type: Directive,
3094
- args: [{
3095
- selector: '[snyMenuSeparator]',
3096
- standalone: true,
3097
- host: {
3098
- 'role': 'separator',
3099
- '[class]': 'computedClass()',
3100
- },
3101
- }]
3102
- }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3103
- class SnyMenuLabelDirective {
3104
- class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
3105
- computedClass = computed(() => cn('px-2 py-1.5 text-sm font-semibold', this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
3106
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuLabelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3107
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyMenuLabelDirective, isStandalone: true, selector: "[snyMenuLabel]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "computedClass()" } }, ngImport: i0 });
4043
+ positionTooltip() {
4044
+ if (!this.tooltipEl)
4045
+ return;
4046
+ const hostRect = this.el.nativeElement.getBoundingClientRect();
4047
+ const tooltipRect = this.tooltipEl.getBoundingClientRect();
4048
+ const position = this.tooltipPosition();
4049
+ const gap = 8;
4050
+ let top = 0;
4051
+ let left = 0;
4052
+ switch (position) {
4053
+ case 'top':
4054
+ top = hostRect.top - tooltipRect.height - gap;
4055
+ left = hostRect.left + (hostRect.width - tooltipRect.width) / 2;
4056
+ break;
4057
+ case 'bottom':
4058
+ top = hostRect.bottom + gap;
4059
+ left = hostRect.left + (hostRect.width - tooltipRect.width) / 2;
4060
+ break;
4061
+ case 'left':
4062
+ top = hostRect.top + (hostRect.height - tooltipRect.height) / 2;
4063
+ left = hostRect.left - tooltipRect.width - gap;
4064
+ break;
4065
+ case 'right':
4066
+ top = hostRect.top + (hostRect.height - tooltipRect.height) / 2;
4067
+ left = hostRect.right + gap;
4068
+ break;
4069
+ }
4070
+ this.tooltipEl.style.top = `${top}px`;
4071
+ this.tooltipEl.style.left = `${left}px`;
4072
+ }
4073
+ destroyTooltip() {
4074
+ if (this.tooltipEl) {
4075
+ this.tooltipEl.remove();
4076
+ this.tooltipEl = null;
4077
+ }
4078
+ }
4079
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4080
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyTooltipDirective, isStandalone: true, selector: "[snyTooltip]", inputs: { snyTooltip: { classPropertyName: "snyTooltip", publicName: "snyTooltip", isSignal: true, isRequired: true, transformFunction: null }, tooltipPosition: { classPropertyName: "tooltipPosition", publicName: "tooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, tooltipDelay: { classPropertyName: "tooltipDelay", publicName: "tooltipDelay", isSignal: true, isRequired: false, transformFunction: null }, tooltipDisabled: { classPropertyName: "tooltipDisabled", publicName: "tooltipDisabled", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "show()", "mouseleave": "hide()", "focus": "show()", "blur": "hide()", "keydown.escape": "hide()" }, properties: { "attr.aria-describedby": "isOpen() ? tooltipId : null" } }, exportAs: ["snyTooltip"], ngImport: i0 });
3108
4081
  }
3109
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyMenuLabelDirective, decorators: [{
4082
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyTooltipDirective, decorators: [{
3110
4083
  type: Directive,
3111
4084
  args: [{
3112
- selector: '[snyMenuLabel]',
4085
+ selector: '[snyTooltip]',
3113
4086
  standalone: true,
4087
+ exportAs: 'snyTooltip',
3114
4088
  host: {
3115
- '[class]': 'computedClass()',
4089
+ '(mouseenter)': 'show()',
4090
+ '(mouseleave)': 'hide()',
4091
+ '(focus)': 'show()',
4092
+ '(blur)': 'hide()',
4093
+ '(keydown.escape)': 'hide()',
4094
+ '[attr.aria-describedby]': 'isOpen() ? tooltipId : null',
3116
4095
  },
3117
4096
  }]
3118
- }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
4097
+ }], ctorParameters: () => [], propDecorators: { snyTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "snyTooltip", required: true }] }], tooltipPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipPosition", required: false }] }], tooltipDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipDelay", required: false }] }], tooltipDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipDisabled", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3119
4098
 
3120
4099
  const fileInputVariants = cva('flex w-full cursor-pointer items-center gap-2 rounded-md border bg-background px-3 py-2 text-sm ring-offset-background transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', {
3121
4100
  variants: {
@@ -3644,219 +4623,45 @@ class SnyDrawerContentDirective {
3644
4623
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyDrawerContentDirective, isStandalone: true, selector: "[snyDrawerContent]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "computedClass()" } }, ngImport: i0 });
3645
4624
  }
3646
4625
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDrawerContentDirective, decorators: [{
3647
- type: Directive,
3648
- args: [{
3649
- selector: '[snyDrawerContent]',
3650
- standalone: true,
3651
- host: {
3652
- '[class]': 'computedClass()',
3653
- },
3654
- }]
3655
- }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3656
- class SnyDrawerSideDirective {
3657
- drawer = inject(SNY_DRAWER);
3658
- side = input('left', ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
3659
- class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
3660
- computedClass = computed(() => {
3661
- const isOpen = this.drawer.isOpen();
3662
- const s = this.side();
3663
- const base = 'fixed inset-y-0 z-40 w-64 bg-background border-r border-border transition-transform duration-300 ease-in-out';
3664
- const sideClass = s === 'left' ? 'left-0' : 'right-0 border-l border-r-0';
3665
- const transformClass = isOpen
3666
- ? 'translate-x-0'
3667
- : s === 'left' ? '-translate-x-full' : 'translate-x-full';
3668
- return cn(base, sideClass, transformClass, this.class());
3669
- }, ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
3670
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDrawerSideDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3671
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyDrawerSideDirective, isStandalone: true, selector: "[snyDrawerSide]", inputs: { side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "navigation", "aria-label": "Sidebar navigation" }, properties: { "attr.aria-modal": "drawer.overlay() && drawer.isOpen() || null", "class": "computedClass()" } }, ngImport: i0 });
3672
- }
3673
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDrawerSideDirective, decorators: [{
3674
- type: Directive,
3675
- args: [{
3676
- selector: '[snyDrawerSide]',
3677
- standalone: true,
3678
- host: {
3679
- 'role': 'navigation',
3680
- 'aria-label': 'Sidebar navigation',
3681
- '[attr.aria-modal]': 'drawer.overlay() && drawer.isOpen() || null',
3682
- '[class]': 'computedClass()',
3683
- },
3684
- }]
3685
- }], propDecorators: { side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3686
-
3687
- const paginationItemVariants = cva('inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', {
3688
- variants: {
3689
- variant: {
3690
- default: 'bg-background hover:bg-accent hover:text-accent-foreground',
3691
- outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
3692
- ghost: 'hover:bg-accent hover:text-accent-foreground',
3693
- },
3694
- size: {
3695
- sm: 'h-8 w-8 text-xs',
3696
- md: 'h-9 w-9',
3697
- lg: 'h-10 w-10',
3698
- },
3699
- active: {
3700
- true: 'bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground',
3701
- false: '',
3702
- },
3703
- },
3704
- defaultVariants: {
3705
- variant: 'default',
3706
- size: 'md',
3707
- active: false,
3708
- },
3709
- });
3710
-
3711
- function computePageRange(totalPages, currentPage, siblingCount, boundaryCount) {
3712
- const range = (start, end) => Array.from({ length: end - start + 1 }, (_, i) => start + i);
3713
- const startPages = range(1, Math.min(boundaryCount, totalPages));
3714
- const endPages = range(Math.max(totalPages - boundaryCount + 1, boundaryCount + 1), totalPages);
3715
- const siblingsStart = Math.max(Math.min(currentPage - siblingCount, totalPages - boundaryCount - siblingCount * 2 - 1), boundaryCount + 2);
3716
- const siblingsEnd = Math.min(Math.max(currentPage + siblingCount, boundaryCount + siblingCount * 2 + 2), endPages.length > 0 ? endPages[0] - 2 : totalPages - 1);
3717
- const result = [...startPages];
3718
- if (siblingsStart > boundaryCount + 2) {
3719
- result.push('ellipsis');
3720
- }
3721
- else if (boundaryCount + 1 < totalPages - boundaryCount) {
3722
- result.push(boundaryCount + 1);
3723
- }
3724
- result.push(...range(siblingsStart, siblingsEnd));
3725
- if (siblingsEnd < totalPages - boundaryCount - 1) {
3726
- result.push('ellipsis');
3727
- }
3728
- else if (totalPages - boundaryCount > boundaryCount) {
3729
- result.push(totalPages - boundaryCount);
3730
- }
3731
- result.push(...endPages);
3732
- return [...new Set(result)].sort((a, b) => {
3733
- if (a === 'ellipsis')
3734
- return 0;
3735
- if (b === 'ellipsis')
3736
- return 0;
3737
- return a - b;
3738
- });
3739
- }
3740
- class SnyPaginationComponent {
3741
- currentPage = model(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
3742
- totalPages = input.required(...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
3743
- siblingCount = input(1, ...(ngDevMode ? [{ debugName: "siblingCount" }] : /* istanbul ignore next */ []));
3744
- boundaryCount = input(1, ...(ngDevMode ? [{ debugName: "boundaryCount" }] : /* istanbul ignore next */ []));
3745
- size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
3746
- variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
3747
- class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
3748
- pages = computed(() => computePageRange(this.totalPages(), this.currentPage(), this.siblingCount(), this.boundaryCount()), ...(ngDevMode ? [{ debugName: "pages" }] : /* istanbul ignore next */ []));
3749
- hasPrev = computed(() => this.currentPage() > 1, ...(ngDevMode ? [{ debugName: "hasPrev" }] : /* istanbul ignore next */ []));
3750
- hasNext = computed(() => this.currentPage() < this.totalPages(), ...(ngDevMode ? [{ debugName: "hasNext" }] : /* istanbul ignore next */ []));
3751
- goToPage(page) {
3752
- if (page === 'ellipsis')
3753
- return;
3754
- this.currentPage.set(page);
3755
- }
3756
- prev() {
3757
- if (this.hasPrev())
3758
- this.currentPage.update((p) => p - 1);
3759
- }
3760
- next() {
3761
- if (this.hasNext())
3762
- this.currentPage.update((p) => p + 1);
3763
- }
3764
- pageClass(page) {
3765
- return cn(paginationItemVariants({
3766
- variant: this.variant(),
3767
- size: this.size(),
3768
- active: page === this.currentPage(),
3769
- }));
3770
- }
3771
- navBtnClass() {
3772
- return cn(paginationItemVariants({ variant: this.variant(), size: this.size(), active: false }));
3773
- }
3774
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyPaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3775
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyPaginationComponent, isStandalone: true, selector: "sny-pagination", inputs: { currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, totalPages: { classPropertyName: "totalPages", publicName: "totalPages", isSignal: true, isRequired: true, transformFunction: null }, siblingCount: { classPropertyName: "siblingCount", publicName: "siblingCount", isSignal: true, isRequired: false, transformFunction: null }, boundaryCount: { classPropertyName: "boundaryCount", publicName: "boundaryCount", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange" }, host: { attributes: { "role": "navigation", "aria-label": "Pagination" } }, ngImport: i0, template: `
3776
- <div class="flex items-center gap-1">
3777
- <button
3778
- [class]="navBtnClass()"
3779
- [disabled]="!hasPrev()"
3780
- [attr.aria-label]="'Go to previous page'"
3781
- (click)="prev()"
3782
- >
3783
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
3784
- </button>
3785
-
3786
- @for (page of pages(); track $index) {
3787
- @if (page === 'ellipsis') {
3788
- <span class="flex h-9 w-9 items-center justify-center" aria-hidden="true">...</span>
3789
- } @else {
3790
- <button
3791
- [class]="pageClass(page)"
3792
- [attr.aria-label]="'Page ' + page"
3793
- [attr.aria-current]="page === currentPage() ? 'page' : null"
3794
- (click)="goToPage(page)"
3795
- >
3796
- {{ page }}
3797
- </button>
3798
- }
3799
- }
3800
-
3801
- <button
3802
- [class]="navBtnClass()"
3803
- [disabled]="!hasNext()"
3804
- [attr.aria-label]="'Go to next page'"
3805
- (click)="next()"
3806
- >
3807
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
3808
- </button>
3809
- </div>
3810
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
4626
+ type: Directive,
4627
+ args: [{
4628
+ selector: '[snyDrawerContent]',
4629
+ standalone: true,
4630
+ host: {
4631
+ '[class]': 'computedClass()',
4632
+ },
4633
+ }]
4634
+ }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
4635
+ class SnyDrawerSideDirective {
4636
+ drawer = inject(SNY_DRAWER);
4637
+ side = input('left', ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
4638
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
4639
+ computedClass = computed(() => {
4640
+ const isOpen = this.drawer.isOpen();
4641
+ const s = this.side();
4642
+ const base = 'fixed inset-y-0 z-40 w-64 bg-background border-r border-border transition-transform duration-300 ease-in-out';
4643
+ const sideClass = s === 'left' ? 'left-0' : 'right-0 border-l border-r-0';
4644
+ const transformClass = isOpen
4645
+ ? 'translate-x-0'
4646
+ : s === 'left' ? '-translate-x-full' : 'translate-x-full';
4647
+ return cn(base, sideClass, transformClass, this.class());
4648
+ }, ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
4649
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDrawerSideDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4650
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyDrawerSideDirective, isStandalone: true, selector: "[snyDrawerSide]", inputs: { side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "navigation", "aria-label": "Sidebar navigation" }, properties: { "attr.aria-modal": "drawer.overlay() && drawer.isOpen() || null", "class": "computedClass()" } }, ngImport: i0 });
3811
4651
  }
3812
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyPaginationComponent, decorators: [{
3813
- type: Component,
4652
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDrawerSideDirective, decorators: [{
4653
+ type: Directive,
3814
4654
  args: [{
3815
- selector: 'sny-pagination',
4655
+ selector: '[snyDrawerSide]',
3816
4656
  standalone: true,
3817
- changeDetection: ChangeDetectionStrategy.OnPush,
3818
4657
  host: {
3819
4658
  'role': 'navigation',
3820
- 'aria-label': 'Pagination',
4659
+ 'aria-label': 'Sidebar navigation',
4660
+ '[attr.aria-modal]': 'drawer.overlay() && drawer.isOpen() || null',
4661
+ '[class]': 'computedClass()',
3821
4662
  },
3822
- template: `
3823
- <div class="flex items-center gap-1">
3824
- <button
3825
- [class]="navBtnClass()"
3826
- [disabled]="!hasPrev()"
3827
- [attr.aria-label]="'Go to previous page'"
3828
- (click)="prev()"
3829
- >
3830
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
3831
- </button>
3832
-
3833
- @for (page of pages(); track $index) {
3834
- @if (page === 'ellipsis') {
3835
- <span class="flex h-9 w-9 items-center justify-center" aria-hidden="true">...</span>
3836
- } @else {
3837
- <button
3838
- [class]="pageClass(page)"
3839
- [attr.aria-label]="'Page ' + page"
3840
- [attr.aria-current]="page === currentPage() ? 'page' : null"
3841
- (click)="goToPage(page)"
3842
- >
3843
- {{ page }}
3844
- </button>
3845
- }
3846
- }
3847
-
3848
- <button
3849
- [class]="navBtnClass()"
3850
- [disabled]="!hasNext()"
3851
- [attr.aria-label]="'Go to next page'"
3852
- (click)="next()"
3853
- >
3854
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
3855
- </button>
3856
- </div>
3857
- `,
3858
4663
  }]
3859
- }], propDecorators: { currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: false }] }, { type: i0.Output, args: ["currentPageChange"] }], totalPages: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalPages", required: true }] }], siblingCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "siblingCount", required: false }] }], boundaryCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "boundaryCount", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
4664
+ }], propDecorators: { side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3860
4665
 
3861
4666
  const dividerVariants = cva('shrink-0 bg-border', {
3862
4667
  variants: {
@@ -4961,93 +5766,714 @@ class SnyDiffComponent {
4961
5766
  </div>
4962
5767
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
4963
5768
  }
4964
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDiffComponent, decorators: [{
5769
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDiffComponent, decorators: [{
5770
+ type: Component,
5771
+ args: [{
5772
+ selector: 'sny-diff',
5773
+ standalone: true,
5774
+ changeDetection: ChangeDetectionStrategy.OnPush,
5775
+ host: {
5776
+ '[class]': '"relative overflow-hidden select-none w-full"',
5777
+ '(pointerdown)': 'onPointerDown($event)',
5778
+ '(pointermove)': 'onPointerMove($event)',
5779
+ '(pointerup)': 'onPointerUp()',
5780
+ '(keydown)': 'onKeydown($event)',
5781
+ },
5782
+ template: `
5783
+ <div class="relative w-full" [style.aspect-ratio]="'16/9'">
5784
+ <div class="absolute inset-0">
5785
+ <ng-content select="[snyDiffAfter]" />
5786
+ </div>
5787
+ <div class="absolute inset-0 overflow-hidden" [style.width.%]="value()">
5788
+ <ng-content select="[snyDiffBefore]" />
5789
+ </div>
5790
+ <div
5791
+ class="absolute top-0 bottom-0 w-1 bg-foreground cursor-col-resize z-10"
5792
+ [style.left.%]="value()"
5793
+ role="slider"
5794
+ tabindex="0"
5795
+ [attr.aria-valuenow]="value()"
5796
+ aria-valuemin="0"
5797
+ aria-valuemax="100"
5798
+ aria-label="Comparison slider"
5799
+ >
5800
+ <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 rounded-full bg-foreground/80 flex items-center justify-center text-background text-xs">
5801
+
5802
+ </div>
5803
+ </div>
5804
+ </div>
5805
+ `,
5806
+ }]
5807
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
5808
+
5809
+ const linkVariants = cva('inline-flex items-center gap-1 underline-offset-4 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded', {
5810
+ variants: {
5811
+ variant: {
5812
+ default: 'text-foreground underline hover:text-foreground/80',
5813
+ primary: 'text-primary underline hover:text-primary/80',
5814
+ secondary: 'text-muted-foreground underline hover:text-foreground',
5815
+ hover: 'text-foreground no-underline hover:underline',
5816
+ },
5817
+ },
5818
+ defaultVariants: {
5819
+ variant: 'default',
5820
+ },
5821
+ });
5822
+
5823
+ class SnyLinkDirective {
5824
+ variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
5825
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
5826
+ computedClass = computed(() => cn(linkVariants({ variant: this.variant() }), this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
5827
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyLinkDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5828
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyLinkDirective, isStandalone: true, selector: "a[snyLink]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "computedClass()" } }, ngImport: i0 });
5829
+ }
5830
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyLinkDirective, decorators: [{
5831
+ type: Directive,
5832
+ args: [{
5833
+ selector: 'a[snyLink]',
5834
+ standalone: true,
5835
+ host: {
5836
+ '[class]': 'computedClass()',
5837
+ },
5838
+ }]
5839
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
5840
+
5841
+ class SnyCalendarComponent {
5842
+ // Existing inputs (backwards compatible)
5843
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
5844
+ min = input(undefined, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
5845
+ max = input(undefined, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
5846
+ locale = input('en-US', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
5847
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
5848
+ // Range mode inputs
5849
+ mode = input('single', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
5850
+ rangeValue = model(null, ...(ngDevMode ? [{ debugName: "rangeValue" }] : /* istanbul ignore next */ []));
5851
+ showNavigation = input(true, ...(ngDevMode ? [{ debugName: "showNavigation" }] : /* istanbul ignore next */ []));
5852
+ initialViewDate = input(undefined, ...(ngDevMode ? [{ debugName: "initialViewDate" }] : /* istanbul ignore next */ []));
5853
+ borderless = input(false, ...(ngDevMode ? [{ debugName: "borderless" }] : /* istanbul ignore next */ []));
5854
+ hostClass = computed(() => this.borderless()
5855
+ ? 'inline-block p-3 bg-background'
5856
+ : 'inline-block p-4 rounded-md border border-border bg-background', ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
5857
+ // Internal state
5858
+ _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
5859
+ hoveredDate = signal(null, ...(ngDevMode ? [{ debugName: "hoveredDate" }] : /* istanbul ignore next */ []));
5860
+ viewDate = linkedSignal(() => this.initialViewDate() ?? new Date(), ...(ngDevMode ? [{ debugName: "viewDate" }] : /* istanbul ignore next */ []));
5861
+ weekDays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
5862
+ // CVA
5863
+ _onChange = () => { };
5864
+ onTouched = () => { };
5865
+ writeValue(val) {
5866
+ if (this.mode() === 'range') {
5867
+ this.rangeValue.set(val ?? null);
5868
+ const range = val;
5869
+ if (range?.start) {
5870
+ this.viewDate.set(new Date(range.start.getFullYear(), range.start.getMonth(), 1));
5871
+ }
5872
+ }
5873
+ else {
5874
+ this.value.set(val ?? null);
5875
+ if (val) {
5876
+ const d = val;
5877
+ this.viewDate.set(new Date(d.getFullYear(), d.getMonth(), 1));
5878
+ }
5879
+ }
5880
+ }
5881
+ registerOnChange(fn) {
5882
+ this._onChange = fn;
5883
+ }
5884
+ registerOnTouched(fn) {
5885
+ this.onTouched = fn;
5886
+ }
5887
+ setDisabledState(isDisabled) {
5888
+ this._disabledByCva.set(isDisabled);
5889
+ }
5890
+ // Computed
5891
+ monthYearLabel = computed(() => {
5892
+ const d = this.viewDate();
5893
+ return d.toLocaleDateString(this.locale(), { month: 'long', year: 'numeric' });
5894
+ }, ...(ngDevMode ? [{ debugName: "monthYearLabel" }] : /* istanbul ignore next */ []));
5895
+ days = computed(() => {
5896
+ const view = this.viewDate();
5897
+ const year = view.getFullYear();
5898
+ const month = view.getMonth();
5899
+ const today = new Date();
5900
+ const selected = this.value();
5901
+ const rangeVal = this.mode() === 'range' ? this.rangeValue() : null;
5902
+ const hovered = this.hoveredDate();
5903
+ const minDate = this.min();
5904
+ const maxDate = this.max();
5905
+ const firstDay = new Date(year, month, 1);
5906
+ const startDay = firstDay.getDay();
5907
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
5908
+ const daysInPrevMonth = new Date(year, month, 0).getDate();
5909
+ const days = [];
5910
+ for (let i = startDay - 1; i >= 0; i--) {
5911
+ const date = new Date(year, month - 1, daysInPrevMonth - i);
5912
+ days.push(this.createDay(date, false, today, selected, rangeVal, hovered, minDate, maxDate));
5913
+ }
5914
+ for (let d = 1; d <= daysInMonth; d++) {
5915
+ const date = new Date(year, month, d);
5916
+ days.push(this.createDay(date, true, today, selected, rangeVal, hovered, minDate, maxDate));
5917
+ }
5918
+ const remaining = 42 - days.length;
5919
+ for (let d = 1; d <= remaining; d++) {
5920
+ const date = new Date(year, month + 1, d);
5921
+ days.push(this.createDay(date, false, today, selected, rangeVal, hovered, minDate, maxDate));
5922
+ }
5923
+ return days;
5924
+ }, ...(ngDevMode ? [{ debugName: "days" }] : /* istanbul ignore next */ []));
5925
+ // Navigation
5926
+ prevMonth() {
5927
+ this.viewDate.set(new Date(this.viewDate().getFullYear(), this.viewDate().getMonth() - 1, 1));
5928
+ }
5929
+ nextMonth() {
5930
+ this.viewDate.set(new Date(this.viewDate().getFullYear(), this.viewDate().getMonth() + 1, 1));
5931
+ }
5932
+ // Click handler
5933
+ onDayClick(date) {
5934
+ if (this.mode() === 'single') {
5935
+ this.value.set(date);
5936
+ this._onChange(date);
5937
+ this.onTouched();
5938
+ return;
5939
+ }
5940
+ // Range mode
5941
+ const current = this.rangeValue();
5942
+ if (!current?.start || (current.start && current.end)) {
5943
+ this.rangeValue.set({ start: date, end: null });
5944
+ }
5945
+ else {
5946
+ const start = current.start;
5947
+ if (date < start) {
5948
+ this.rangeValue.set({ start: date, end: start });
5949
+ }
5950
+ else if (this.isSameDay(date, start)) {
5951
+ this.rangeValue.set({ start: date, end: date });
5952
+ }
5953
+ else {
5954
+ this.rangeValue.set({ start, end: date });
5955
+ }
5956
+ }
5957
+ this._onChange(this.rangeValue());
5958
+ this.onTouched();
5959
+ }
5960
+ // Hover handler
5961
+ onDayHover(date) {
5962
+ if (this.mode() === 'range') {
5963
+ this.hoveredDate.set(date);
5964
+ }
5965
+ }
5966
+ // Keyboard
5967
+ onKeydown(event) {
5968
+ switch (event.key) {
5969
+ case 'ArrowLeft':
5970
+ event.preventDefault();
5971
+ this.navigateDays(-1);
5972
+ break;
5973
+ case 'ArrowRight':
5974
+ event.preventDefault();
5975
+ this.navigateDays(1);
5976
+ break;
5977
+ case 'ArrowUp':
5978
+ event.preventDefault();
5979
+ this.navigateDays(-7);
5980
+ break;
5981
+ case 'ArrowDown':
5982
+ event.preventDefault();
5983
+ this.navigateDays(7);
5984
+ break;
5985
+ }
5986
+ }
5987
+ // Styling
5988
+ dayClass(day) {
5989
+ const isEndpoint = day.isRangeStart || day.isRangeEnd;
5990
+ return cn('inline-flex items-center justify-center text-sm h-9 w-9 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
5991
+ // Shape
5992
+ day.isRangeStart && !day.isRangeEnd ? 'rounded-l-md rounded-r-none' :
5993
+ day.isRangeEnd && !day.isRangeStart ? 'rounded-r-md rounded-l-none' :
5994
+ day.isInRange || day.isRangePreview ? 'rounded-none' :
5995
+ 'rounded-md',
5996
+ // Base text color
5997
+ !day.isCurrentMonth && 'text-muted-foreground/40', day.isCurrentMonth && !day.isSelected && !isEndpoint && 'text-foreground',
5998
+ // Today indicator
5999
+ day.isToday && !day.isSelected && !isEndpoint && 'bg-accent text-accent-foreground font-semibold',
6000
+ // Single selected
6001
+ day.isSelected && this.mode() === 'single' && 'bg-primary text-primary-foreground font-semibold shadow-sm',
6002
+ // Range endpoints
6003
+ isEndpoint && 'bg-primary text-primary-foreground font-semibold shadow-sm',
6004
+ // Range band
6005
+ day.isInRange && 'bg-primary/10 text-foreground', day.isRangePreview && 'bg-primary/5 text-foreground',
6006
+ // States
6007
+ day.isDisabled && 'opacity-40 cursor-not-allowed pointer-events-none', !day.isDisabled && !day.isSelected && !isEndpoint && 'hover:bg-accent hover:text-accent-foreground cursor-pointer');
6008
+ }
6009
+ // Private helpers
6010
+ navigateDays(offset) {
6011
+ const current = this.value() ?? new Date();
6012
+ const next = new Date(current);
6013
+ next.setDate(next.getDate() + offset);
6014
+ this.value.set(next);
6015
+ this._onChange(next);
6016
+ this.viewDate.set(new Date(next.getFullYear(), next.getMonth(), 1));
6017
+ }
6018
+ createDay(date, isCurrentMonth, today, selected, rangeVal, hoveredDate, minDate, maxDate) {
6019
+ const isToday = this.isSameDay(date, today);
6020
+ const isSelected = selected ? this.isSameDay(date, selected) : false;
6021
+ const isDisabled = this._disabledByCva() ||
6022
+ (minDate ? date < minDate : false) ||
6023
+ (maxDate ? date > maxDate : false);
6024
+ let isRangeStart = false;
6025
+ let isRangeEnd = false;
6026
+ let isInRange = false;
6027
+ let isRangePreview = false;
6028
+ if (rangeVal) {
6029
+ const { start, end } = rangeVal;
6030
+ if (start)
6031
+ isRangeStart = this.isSameDay(date, start);
6032
+ if (end)
6033
+ isRangeEnd = this.isSameDay(date, end);
6034
+ if (start && end) {
6035
+ isInRange = date > start && date < end && !isRangeStart && !isRangeEnd;
6036
+ }
6037
+ // Preview: start set, no end yet, user hovering
6038
+ if (start && !end && hoveredDate && !this.isSameDay(hoveredDate, start)) {
6039
+ const previewStart = hoveredDate > start ? start : hoveredDate;
6040
+ const previewEnd = hoveredDate > start ? hoveredDate : start;
6041
+ if (date > previewStart && date < previewEnd) {
6042
+ isRangePreview = true;
6043
+ }
6044
+ if (this.isSameDay(date, hoveredDate) && !isRangeStart) {
6045
+ isRangePreview = true;
6046
+ }
6047
+ }
6048
+ }
6049
+ return {
6050
+ date, day: date.getDate(), isCurrentMonth, isToday, isSelected, isDisabled,
6051
+ isRangeStart, isRangeEnd, isInRange, isRangePreview,
6052
+ };
6053
+ }
6054
+ isSameDay(a, b) {
6055
+ return (a.getFullYear() === b.getFullYear() &&
6056
+ a.getMonth() === b.getMonth() &&
6057
+ a.getDate() === b.getDate());
6058
+ }
6059
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6060
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyCalendarComponent, isStandalone: true, selector: "sny-calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, rangeValue: { classPropertyName: "rangeValue", publicName: "rangeValue", isSignal: true, isRequired: false, transformFunction: null }, showNavigation: { classPropertyName: "showNavigation", publicName: "showNavigation", isSignal: true, isRequired: false, transformFunction: null }, initialViewDate: { classPropertyName: "initialViewDate", publicName: "initialViewDate", isSignal: true, isRequired: false, transformFunction: null }, borderless: { classPropertyName: "borderless", publicName: "borderless", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", rangeValue: "rangeValueChange" }, host: { attributes: { "role": "application", "aria-label": "Calendar" }, listeners: { "keydown": "onKeydown($event)" }, properties: { "class": "hostClass()" } }, providers: [
6061
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyCalendarComponent), multi: true },
6062
+ ], ngImport: i0, template: `
6063
+ @if (showNavigation()) {
6064
+ <div class="flex items-center justify-between mb-3">
6065
+ <button
6066
+ type="button"
6067
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6068
+ (click)="prevMonth()"
6069
+ aria-label="Previous month"
6070
+ >
6071
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
6072
+ </button>
6073
+ <span class="text-sm font-semibold tracking-tight">{{ monthYearLabel() }}</span>
6074
+ <button
6075
+ type="button"
6076
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6077
+ (click)="nextMonth()"
6078
+ aria-label="Next month"
6079
+ >
6080
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
6081
+ </button>
6082
+ </div>
6083
+ }
6084
+
6085
+ <div role="grid" class="grid grid-cols-7 gap-1">
6086
+ @for (dayName of weekDays; track dayName) {
6087
+ <div class="text-center text-xs text-muted-foreground font-medium h-9 flex items-center justify-center" role="columnheader">{{ dayName }}</div>
6088
+ }
6089
+ @for (day of days(); track day.date.getTime()) {
6090
+ <button
6091
+ type="button"
6092
+ [class]="dayClass(day)"
6093
+ [disabled]="day.isDisabled"
6094
+ [attr.aria-selected]="day.isSelected || day.isRangeStart || day.isRangeEnd || null"
6095
+ [attr.aria-current]="day.isToday ? 'date' : null"
6096
+ [attr.aria-disabled]="day.isDisabled || null"
6097
+ [attr.aria-label]="day.date.toLocaleDateString(locale(), { month: 'long', day: 'numeric', year: 'numeric' })"
6098
+ role="gridcell"
6099
+ (click)="onDayClick(day.date)"
6100
+ (mouseenter)="onDayHover(day.date)"
6101
+ (mouseleave)="onDayHover(null)"
6102
+ >
6103
+ {{ day.day }}
6104
+ </button>
6105
+ }
6106
+ </div>
6107
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
6108
+ }
6109
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCalendarComponent, decorators: [{
4965
6110
  type: Component,
4966
6111
  args: [{
4967
- selector: 'sny-diff',
6112
+ selector: 'sny-calendar',
4968
6113
  standalone: true,
4969
6114
  changeDetection: ChangeDetectionStrategy.OnPush,
4970
6115
  host: {
4971
- '[class]': '"relative overflow-hidden select-none w-full"',
4972
- '(pointerdown)': 'onPointerDown($event)',
4973
- '(pointermove)': 'onPointerMove($event)',
4974
- '(pointerup)': 'onPointerUp()',
6116
+ '[class]': 'hostClass()',
4975
6117
  '(keydown)': 'onKeydown($event)',
6118
+ 'role': 'application',
6119
+ 'aria-label': 'Calendar',
4976
6120
  },
6121
+ providers: [
6122
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyCalendarComponent), multi: true },
6123
+ ],
4977
6124
  template: `
4978
- <div class="relative w-full" [style.aspect-ratio]="'16/9'">
4979
- <div class="absolute inset-0">
4980
- <ng-content select="[snyDiffAfter]" />
4981
- </div>
4982
- <div class="absolute inset-0 overflow-hidden" [style.width.%]="value()">
4983
- <ng-content select="[snyDiffBefore]" />
4984
- </div>
4985
- <div
4986
- class="absolute top-0 bottom-0 w-1 bg-foreground cursor-col-resize z-10"
4987
- [style.left.%]="value()"
4988
- role="slider"
4989
- tabindex="0"
4990
- [attr.aria-valuenow]="value()"
4991
- aria-valuemin="0"
4992
- aria-valuemax="100"
4993
- aria-label="Comparison slider"
4994
- >
4995
- <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 rounded-full bg-foreground/80 flex items-center justify-center text-background text-xs">
4996
-
4997
- </div>
6125
+ @if (showNavigation()) {
6126
+ <div class="flex items-center justify-between mb-3">
6127
+ <button
6128
+ type="button"
6129
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6130
+ (click)="prevMonth()"
6131
+ aria-label="Previous month"
6132
+ >
6133
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
6134
+ </button>
6135
+ <span class="text-sm font-semibold tracking-tight">{{ monthYearLabel() }}</span>
6136
+ <button
6137
+ type="button"
6138
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6139
+ (click)="nextMonth()"
6140
+ aria-label="Next month"
6141
+ >
6142
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
6143
+ </button>
4998
6144
  </div>
6145
+ }
6146
+
6147
+ <div role="grid" class="grid grid-cols-7 gap-1">
6148
+ @for (dayName of weekDays; track dayName) {
6149
+ <div class="text-center text-xs text-muted-foreground font-medium h-9 flex items-center justify-center" role="columnheader">{{ dayName }}</div>
6150
+ }
6151
+ @for (day of days(); track day.date.getTime()) {
6152
+ <button
6153
+ type="button"
6154
+ [class]="dayClass(day)"
6155
+ [disabled]="day.isDisabled"
6156
+ [attr.aria-selected]="day.isSelected || day.isRangeStart || day.isRangeEnd || null"
6157
+ [attr.aria-current]="day.isToday ? 'date' : null"
6158
+ [attr.aria-disabled]="day.isDisabled || null"
6159
+ [attr.aria-label]="day.date.toLocaleDateString(locale(), { month: 'long', day: 'numeric', year: 'numeric' })"
6160
+ role="gridcell"
6161
+ (click)="onDayClick(day.date)"
6162
+ (mouseenter)="onDayHover(day.date)"
6163
+ (mouseleave)="onDayHover(null)"
6164
+ >
6165
+ {{ day.day }}
6166
+ </button>
6167
+ }
4999
6168
  </div>
5000
6169
  `,
5001
6170
  }]
5002
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
6171
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], rangeValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeValue", required: false }] }, { type: i0.Output, args: ["rangeValueChange"] }], showNavigation: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNavigation", required: false }] }], initialViewDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialViewDate", required: false }] }], borderless: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderless", required: false }] }] } });
5003
6172
 
5004
- const linkVariants = cva('inline-flex items-center gap-1 underline-offset-4 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded', {
6173
+ const datePickerTriggerVariants = cva('inline-flex w-full items-center justify-between whitespace-nowrap rounded-sm border border-border bg-background px-3 py-2 text-sm ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', {
5005
6174
  variants: {
5006
- variant: {
5007
- default: 'text-foreground underline hover:text-foreground/80',
5008
- primary: 'text-primary underline hover:text-primary/80',
5009
- secondary: 'text-muted-foreground underline hover:text-foreground',
5010
- hover: 'text-foreground no-underline hover:underline',
6175
+ size: {
6176
+ sm: 'h-9 text-xs',
6177
+ md: 'h-10 text-sm',
6178
+ lg: 'h-11 text-base',
5011
6179
  },
5012
6180
  },
5013
- defaultVariants: {
5014
- variant: 'default',
5015
- },
6181
+ defaultVariants: { size: 'md' },
5016
6182
  });
5017
6183
 
5018
- class SnyLinkDirective {
5019
- variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
6184
+ class SnyDatePickerComponent {
6185
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
6186
+ placeholder = input('Pick a date...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
6187
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
6188
+ locale = input('en-US', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
6189
+ dateFormat = input({
6190
+ month: 'short',
6191
+ day: 'numeric',
6192
+ year: 'numeric',
6193
+ }, ...(ngDevMode ? [{ debugName: "dateFormat" }] : /* istanbul ignore next */ []));
6194
+ min = input(undefined, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
6195
+ max = input(undefined, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
6196
+ clearable = input(true, ...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
6197
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
5020
6198
  class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
5021
- computedClass = computed(() => cn(linkVariants({ variant: this.variant() }), this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
5022
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyLinkDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5023
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyLinkDirective, isStandalone: true, selector: "a[snyLink]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "computedClass()" } }, ngImport: i0 });
6199
+ open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
6200
+ internalValue = signal(null, ...(ngDevMode ? [{ debugName: "internalValue" }] : /* istanbul ignore next */ []));
6201
+ _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
6202
+ isDisabled = computed(() => this.disabled() || this._disabledByCva(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
6203
+ triggerRef = viewChild('triggerEl', ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
6204
+ dropdownRef = viewChild('dropdownEl', ...(ngDevMode ? [{ debugName: "dropdownRef" }] : /* istanbul ignore next */ []));
6205
+ elRef = inject(ElementRef);
6206
+ scrollHandler = null;
6207
+ resizeHandler = null;
6208
+ _onChange = () => { };
6209
+ onTouched = () => { };
6210
+ // CVA
6211
+ writeValue(val) {
6212
+ this.value.set(val ?? null);
6213
+ this.internalValue.set(val ?? null);
6214
+ }
6215
+ registerOnChange(fn) {
6216
+ this._onChange = fn;
6217
+ }
6218
+ registerOnTouched(fn) {
6219
+ this.onTouched = fn;
6220
+ }
6221
+ setDisabledState(isDisabled) {
6222
+ this._disabledByCva.set(isDisabled);
6223
+ }
6224
+ // Display
6225
+ displayValue = computed(() => {
6226
+ const d = this.value();
6227
+ if (!d)
6228
+ return '';
6229
+ return d.toLocaleDateString(this.locale(), this.dateFormat());
6230
+ }, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
6231
+ triggerClass = computed(() => cn(datePickerTriggerVariants({ size: this.size() }), this.class()), ...(ngDevMode ? [{ debugName: "triggerClass" }] : /* istanbul ignore next */ []));
6232
+ // Actions
6233
+ onDateSelected(date) {
6234
+ this.value.set(date);
6235
+ this._onChange(date);
6236
+ this.close();
6237
+ }
6238
+ clear(event) {
6239
+ event.stopPropagation();
6240
+ this.value.set(null);
6241
+ this.internalValue.set(null);
6242
+ this._onChange(null);
6243
+ }
6244
+ toggle() {
6245
+ if (this.open()) {
6246
+ this.close();
6247
+ }
6248
+ else {
6249
+ this.internalValue.set(this.value());
6250
+ this.updateDropdownPosition();
6251
+ this.open.set(true);
6252
+ this.addGlobalListeners();
6253
+ setTimeout(() => this.updateDropdownPosition());
6254
+ }
6255
+ }
6256
+ close() {
6257
+ this.open.set(false);
6258
+ this.removeGlobalListeners();
6259
+ }
6260
+ // Positioning (combobox pattern)
6261
+ updateDropdownPosition() {
6262
+ const trigger = this.triggerRef()?.nativeElement;
6263
+ if (!trigger)
6264
+ return;
6265
+ const rect = trigger.getBoundingClientRect();
6266
+ const dropdown = this.dropdownRef()?.nativeElement;
6267
+ if (dropdown) {
6268
+ dropdown.style.top = `${rect.bottom + 4}px`;
6269
+ dropdown.style.left = `${rect.left}px`;
6270
+ }
6271
+ }
6272
+ addGlobalListeners() {
6273
+ this.removeGlobalListeners();
6274
+ this.scrollHandler = () => {
6275
+ requestAnimationFrame(() => this.updateDropdownPosition());
6276
+ };
6277
+ this.resizeHandler = () => {
6278
+ requestAnimationFrame(() => this.updateDropdownPosition());
6279
+ };
6280
+ document.addEventListener('scroll', this.scrollHandler, { capture: true, passive: true });
6281
+ window.addEventListener('resize', this.resizeHandler, { passive: true });
6282
+ }
6283
+ removeGlobalListeners() {
6284
+ if (this.scrollHandler) {
6285
+ document.removeEventListener('scroll', this.scrollHandler, { capture: true });
6286
+ this.scrollHandler = null;
6287
+ }
6288
+ if (this.resizeHandler) {
6289
+ window.removeEventListener('resize', this.resizeHandler);
6290
+ this.resizeHandler = null;
6291
+ }
6292
+ }
6293
+ ngOnDestroy() {
6294
+ this.removeGlobalListeners();
6295
+ }
6296
+ onDocumentClick(event) {
6297
+ if (!this.elRef.nativeElement.contains(event.target)) {
6298
+ this.close();
6299
+ }
6300
+ }
6301
+ onEscape() {
6302
+ this.close();
6303
+ }
6304
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6305
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyDatePickerComponent, isStandalone: true, selector: "sny-date-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, dateFormat: { classPropertyName: "dateFormat", publicName: "dateFormat", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "document:click": "onDocumentClick($event)", "keydown.escape": "onEscape()" }, classAttribute: "relative inline-block w-full" }, providers: [
6306
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyDatePickerComponent), multi: true },
6307
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerEl"], descendants: true, isSignal: true }, { propertyName: "dropdownRef", first: true, predicate: ["dropdownEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
6308
+ <button
6309
+ #triggerEl
6310
+ type="button"
6311
+ role="combobox"
6312
+ [attr.aria-expanded]="open()"
6313
+ aria-haspopup="dialog"
6314
+ [disabled]="isDisabled()"
6315
+ [class]="triggerClass()"
6316
+ (click)="toggle()"
6317
+ (blur)="onTouched()"
6318
+ >
6319
+ <span [class]="displayValue() ? 'truncate' : 'text-muted-foreground truncate'">
6320
+ {{ displayValue() || placeholder() }}
6321
+ </span>
6322
+ <div class="flex items-center gap-1 shrink-0">
6323
+ @if (clearable() && value()) {
6324
+ <button
6325
+ type="button"
6326
+ class="rounded-sm p-0.5 hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
6327
+ (click)="clear($event)"
6328
+ aria-label="Clear date"
6329
+ >
6330
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
6331
+ </button>
6332
+ }
6333
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-muted-foreground"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/></svg>
6334
+ </div>
6335
+ </button>
6336
+
6337
+ @if (open()) {
6338
+ <div
6339
+ #dropdownEl
6340
+ role="dialog"
6341
+ aria-modal="true"
6342
+ aria-label="Choose date"
6343
+ class="fixed z-50 rounded-md border border-border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95"
6344
+ >
6345
+ <sny-calendar
6346
+ [(value)]="internalValue"
6347
+ [min]="min()"
6348
+ [max]="max()"
6349
+ [locale]="locale()"
6350
+ (valueChange)="onDateSelected($event)"
6351
+ />
6352
+ </div>
6353
+ }
6354
+ `, isInline: true, dependencies: [{ kind: "component", type: SnyCalendarComponent, selector: "sny-calendar", inputs: ["value", "min", "max", "locale", "class", "mode", "rangeValue", "showNavigation", "initialViewDate", "borderless"], outputs: ["valueChange", "rangeValueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5024
6355
  }
5025
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyLinkDirective, decorators: [{
5026
- type: Directive,
6356
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDatePickerComponent, decorators: [{
6357
+ type: Component,
5027
6358
  args: [{
5028
- selector: 'a[snyLink]',
6359
+ selector: 'sny-date-picker',
5029
6360
  standalone: true,
5030
- host: {
5031
- '[class]': 'computedClass()',
5032
- },
6361
+ changeDetection: ChangeDetectionStrategy.OnPush,
6362
+ imports: [SnyCalendarComponent],
6363
+ host: { class: 'relative inline-block w-full' },
6364
+ providers: [
6365
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyDatePickerComponent), multi: true },
6366
+ ],
6367
+ template: `
6368
+ <button
6369
+ #triggerEl
6370
+ type="button"
6371
+ role="combobox"
6372
+ [attr.aria-expanded]="open()"
6373
+ aria-haspopup="dialog"
6374
+ [disabled]="isDisabled()"
6375
+ [class]="triggerClass()"
6376
+ (click)="toggle()"
6377
+ (blur)="onTouched()"
6378
+ >
6379
+ <span [class]="displayValue() ? 'truncate' : 'text-muted-foreground truncate'">
6380
+ {{ displayValue() || placeholder() }}
6381
+ </span>
6382
+ <div class="flex items-center gap-1 shrink-0">
6383
+ @if (clearable() && value()) {
6384
+ <button
6385
+ type="button"
6386
+ class="rounded-sm p-0.5 hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
6387
+ (click)="clear($event)"
6388
+ aria-label="Clear date"
6389
+ >
6390
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
6391
+ </button>
6392
+ }
6393
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-muted-foreground"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/></svg>
6394
+ </div>
6395
+ </button>
6396
+
6397
+ @if (open()) {
6398
+ <div
6399
+ #dropdownEl
6400
+ role="dialog"
6401
+ aria-modal="true"
6402
+ aria-label="Choose date"
6403
+ class="fixed z-50 rounded-md border border-border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95"
6404
+ >
6405
+ <sny-calendar
6406
+ [(value)]="internalValue"
6407
+ [min]="min()"
6408
+ [max]="max()"
6409
+ [locale]="locale()"
6410
+ (valueChange)="onDateSelected($event)"
6411
+ />
6412
+ </div>
6413
+ }
6414
+ `,
5033
6415
  }]
5034
- }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
6416
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], triggerRef: [{ type: i0.ViewChild, args: ['triggerEl', { isSignal: true }] }], dropdownRef: [{ type: i0.ViewChild, args: ['dropdownEl', { isSignal: true }] }], onDocumentClick: [{
6417
+ type: HostListener,
6418
+ args: ['document:click', ['$event']]
6419
+ }], onEscape: [{
6420
+ type: HostListener,
6421
+ args: ['keydown.escape']
6422
+ }] } });
5035
6423
 
5036
- class SnyCalendarComponent {
6424
+ class SnyDateRangePickerComponent {
5037
6425
  value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
6426
+ placeholder = input('Pick a date range...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
6427
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
6428
+ locale = input('en-US', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
6429
+ dateFormat = input({
6430
+ month: 'short',
6431
+ day: 'numeric',
6432
+ year: 'numeric',
6433
+ }, ...(ngDevMode ? [{ debugName: "dateFormat" }] : /* istanbul ignore next */ []));
6434
+ separator = input(' \u2014 ', ...(ngDevMode ? [{ debugName: "separator" }] : /* istanbul ignore next */ []));
6435
+ dualCalendar = input(false, ...(ngDevMode ? [{ debugName: "dualCalendar" }] : /* istanbul ignore next */ []));
6436
+ presets = input([], ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
5038
6437
  min = input(undefined, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
5039
6438
  max = input(undefined, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
5040
- locale = input('en-US', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
6439
+ clearable = input(true, ...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
6440
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
5041
6441
  class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
6442
+ open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
6443
+ internalRange = signal(null, ...(ngDevMode ? [{ debugName: "internalRange" }] : /* istanbul ignore next */ []));
6444
+ leftViewDate = signal(new Date(), ...(ngDevMode ? [{ debugName: "leftViewDate" }] : /* istanbul ignore next */ []));
5042
6445
  _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
5043
- viewDate = signal(new Date(), ...(ngDevMode ? [{ debugName: "viewDate" }] : /* istanbul ignore next */ []));
5044
- weekDays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
6446
+ isDisabled = computed(() => this.disabled() || this._disabledByCva(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
6447
+ triggerRef = viewChild('triggerEl', ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
6448
+ dropdownRef = viewChild('dropdownEl', ...(ngDevMode ? [{ debugName: "dropdownRef" }] : /* istanbul ignore next */ []));
6449
+ elRef = inject(ElementRef);
6450
+ scrollHandler = null;
6451
+ resizeHandler = null;
5045
6452
  _onChange = () => { };
5046
6453
  onTouched = () => { };
6454
+ // Computed
6455
+ rightViewDate = computed(() => {
6456
+ const d = this.leftViewDate();
6457
+ return new Date(d.getFullYear(), d.getMonth() + 1, 1);
6458
+ }, ...(ngDevMode ? [{ debugName: "rightViewDate" }] : /* istanbul ignore next */ []));
6459
+ leftMonthLabel = computed(() => this.leftViewDate().toLocaleDateString(this.locale(), { month: 'long', year: 'numeric' }), ...(ngDevMode ? [{ debugName: "leftMonthLabel" }] : /* istanbul ignore next */ []));
6460
+ rightMonthLabel = computed(() => this.rightViewDate().toLocaleDateString(this.locale(), { month: 'long', year: 'numeric' }), ...(ngDevMode ? [{ debugName: "rightMonthLabel" }] : /* istanbul ignore next */ []));
6461
+ displayValue = computed(() => {
6462
+ const r = this.value();
6463
+ if (!r?.start)
6464
+ return '';
6465
+ const fmt = (d) => d.toLocaleDateString(this.locale(), this.dateFormat());
6466
+ if (!r.end)
6467
+ return fmt(r.start) + this.separator() + '...';
6468
+ return fmt(r.start) + this.separator() + fmt(r.end);
6469
+ }, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
6470
+ triggerClass = computed(() => cn(datePickerTriggerVariants({ size: this.size() }), this.class()), ...(ngDevMode ? [{ debugName: "triggerClass" }] : /* istanbul ignore next */ []));
6471
+ // CVA
5047
6472
  writeValue(val) {
5048
6473
  this.value.set(val ?? null);
5049
- if (val) {
5050
- this.viewDate.set(new Date(val.getFullYear(), val.getMonth(), 1));
6474
+ this.internalRange.set(val ?? null);
6475
+ if (val?.start) {
6476
+ this.leftViewDate.set(new Date(val.start.getFullYear(), val.start.getMonth(), 1));
5051
6477
  }
5052
6478
  }
5053
6479
  registerOnChange(fn) {
@@ -5059,189 +6485,370 @@ class SnyCalendarComponent {
5059
6485
  setDisabledState(isDisabled) {
5060
6486
  this._disabledByCva.set(isDisabled);
5061
6487
  }
5062
- monthYearLabel = computed(() => {
5063
- const d = this.viewDate();
5064
- return d.toLocaleDateString(this.locale(), { month: 'long', year: 'numeric' });
5065
- }, ...(ngDevMode ? [{ debugName: "monthYearLabel" }] : /* istanbul ignore next */ []));
5066
- days = computed(() => {
5067
- const view = this.viewDate();
5068
- const year = view.getFullYear();
5069
- const month = view.getMonth();
5070
- const today = new Date();
5071
- const selected = this.value();
5072
- const minDate = this.min();
5073
- const maxDate = this.max();
5074
- const firstDay = new Date(year, month, 1);
5075
- const startDay = firstDay.getDay();
5076
- const daysInMonth = new Date(year, month + 1, 0).getDate();
5077
- const daysInPrevMonth = new Date(year, month, 0).getDate();
5078
- const days = [];
5079
- // Previous month
5080
- for (let i = startDay - 1; i >= 0; i--) {
5081
- const date = new Date(year, month - 1, daysInPrevMonth - i);
5082
- days.push(this.createDay(date, false, today, selected, minDate, maxDate));
5083
- }
5084
- // Current month
5085
- for (let d = 1; d <= daysInMonth; d++) {
5086
- const date = new Date(year, month, d);
5087
- days.push(this.createDay(date, true, today, selected, minDate, maxDate));
6488
+ // Actions
6489
+ onRangeChanged(range) {
6490
+ this.internalRange.set(range);
6491
+ if (range?.start && range?.end) {
6492
+ this.value.set(range);
6493
+ this._onChange(range);
6494
+ setTimeout(() => this.close(), 150);
5088
6495
  }
5089
- // Next month fill
5090
- const remaining = 42 - days.length;
5091
- for (let d = 1; d <= remaining; d++) {
5092
- const date = new Date(year, month + 1, d);
5093
- days.push(this.createDay(date, false, today, selected, minDate, maxDate));
5094
- }
5095
- return days;
5096
- }, ...(ngDevMode ? [{ debugName: "days" }] : /* istanbul ignore next */ []));
6496
+ }
6497
+ selectPreset(preset) {
6498
+ this.value.set(preset.range);
6499
+ this.internalRange.set(preset.range);
6500
+ this._onChange(preset.range);
6501
+ this.close();
6502
+ }
6503
+ clear(event) {
6504
+ event.stopPropagation();
6505
+ this.value.set(null);
6506
+ this.internalRange.set(null);
6507
+ this._onChange(null);
6508
+ }
5097
6509
  prevMonth() {
5098
- this.viewDate.update((d) => new Date(d.getFullYear(), d.getMonth() - 1, 1));
6510
+ this.leftViewDate.update((d) => new Date(d.getFullYear(), d.getMonth() - 1, 1));
5099
6511
  }
5100
6512
  nextMonth() {
5101
- this.viewDate.update((d) => new Date(d.getFullYear(), d.getMonth() + 1, 1));
6513
+ this.leftViewDate.update((d) => new Date(d.getFullYear(), d.getMonth() + 1, 1));
5102
6514
  }
5103
- selectDate(date) {
5104
- this.value.set(date);
5105
- this._onChange(date);
5106
- this.onTouched();
6515
+ toggle() {
6516
+ if (this.open()) {
6517
+ this.close();
6518
+ }
6519
+ else {
6520
+ this.internalRange.set(this.value());
6521
+ this.updateDropdownPosition();
6522
+ this.open.set(true);
6523
+ this.addGlobalListeners();
6524
+ setTimeout(() => this.updateDropdownPosition());
6525
+ }
5107
6526
  }
5108
- onKeydown(event) {
5109
- // Simplified keyboard navigation
5110
- switch (event.key) {
5111
- case 'ArrowLeft':
5112
- event.preventDefault();
5113
- this.navigateDays(-1);
5114
- break;
5115
- case 'ArrowRight':
5116
- event.preventDefault();
5117
- this.navigateDays(1);
5118
- break;
5119
- case 'ArrowUp':
5120
- event.preventDefault();
5121
- this.navigateDays(-7);
5122
- break;
5123
- case 'ArrowDown':
5124
- event.preventDefault();
5125
- this.navigateDays(7);
5126
- break;
6527
+ close() {
6528
+ this.open.set(false);
6529
+ this.removeGlobalListeners();
6530
+ }
6531
+ // Positioning
6532
+ updateDropdownPosition() {
6533
+ const trigger = this.triggerRef()?.nativeElement;
6534
+ if (!trigger)
6535
+ return;
6536
+ const rect = trigger.getBoundingClientRect();
6537
+ const dropdown = this.dropdownRef()?.nativeElement;
6538
+ if (dropdown) {
6539
+ dropdown.style.top = `${rect.bottom + 4}px`;
6540
+ dropdown.style.left = `${rect.left}px`;
5127
6541
  }
5128
6542
  }
5129
- dayClass(day) {
5130
- return cn('inline-flex items-center justify-center rounded-md text-sm h-8 w-8 transition-colors', day.isCurrentMonth ? 'text-foreground' : 'text-muted-foreground/50', day.isToday && !day.isSelected && 'bg-accent font-bold', day.isSelected && 'bg-primary text-primary-foreground', day.isDisabled && 'opacity-50 cursor-not-allowed', !day.isDisabled && !day.isSelected && 'hover:bg-accent cursor-pointer');
6543
+ addGlobalListeners() {
6544
+ this.removeGlobalListeners();
6545
+ this.scrollHandler = () => {
6546
+ requestAnimationFrame(() => this.updateDropdownPosition());
6547
+ };
6548
+ this.resizeHandler = () => {
6549
+ requestAnimationFrame(() => this.updateDropdownPosition());
6550
+ };
6551
+ document.addEventListener('scroll', this.scrollHandler, { capture: true, passive: true });
6552
+ window.addEventListener('resize', this.resizeHandler, { passive: true });
5131
6553
  }
5132
- navigateDays(offset) {
5133
- const current = this.value() ?? new Date();
5134
- const next = new Date(current);
5135
- next.setDate(next.getDate() + offset);
5136
- this.value.set(next);
5137
- this._onChange(next);
5138
- this.viewDate.set(new Date(next.getFullYear(), next.getMonth(), 1));
6554
+ removeGlobalListeners() {
6555
+ if (this.scrollHandler) {
6556
+ document.removeEventListener('scroll', this.scrollHandler, { capture: true });
6557
+ this.scrollHandler = null;
6558
+ }
6559
+ if (this.resizeHandler) {
6560
+ window.removeEventListener('resize', this.resizeHandler);
6561
+ this.resizeHandler = null;
6562
+ }
5139
6563
  }
5140
- createDay(date, isCurrentMonth, today, selected, minDate, maxDate) {
5141
- const isToday = this.isSameDay(date, today);
5142
- const isSelected = selected ? this.isSameDay(date, selected) : false;
5143
- const isDisabled = this._disabledByCva() ||
5144
- (minDate ? date < minDate : false) || (maxDate ? date > maxDate : false);
5145
- return { date, day: date.getDate(), isCurrentMonth, isToday, isSelected, isDisabled };
6564
+ ngOnDestroy() {
6565
+ this.removeGlobalListeners();
5146
6566
  }
5147
- isSameDay(a, b) {
5148
- return (a.getFullYear() === b.getFullYear() &&
5149
- a.getMonth() === b.getMonth() &&
5150
- a.getDate() === b.getDate());
6567
+ onDocumentClick(event) {
6568
+ if (!this.elRef.nativeElement.contains(event.target)) {
6569
+ this.close();
6570
+ }
5151
6571
  }
5152
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5153
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyCalendarComponent, isStandalone: true, selector: "sny-calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "class": "\"inline-block p-3 rounded-md border bg-background\"" } }, providers: [
5154
- { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyCalendarComponent), multi: true },
5155
- ], ngImport: i0, template: `
5156
- <div class="flex items-center justify-between mb-4">
5157
- <button
5158
- class="inline-flex items-center justify-center rounded-md text-sm h-7 w-7 hover:bg-accent"
5159
- (click)="prevMonth()"
5160
- aria-label="Previous month"
5161
- >
5162
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m15 18-6-6 6-6"/></svg>
5163
- </button>
5164
- <span class="text-sm font-medium">{{ monthYearLabel() }}</span>
5165
- <button
5166
- class="inline-flex items-center justify-center rounded-md text-sm h-7 w-7 hover:bg-accent"
5167
- (click)="nextMonth()"
5168
- aria-label="Next month"
6572
+ onEscape() {
6573
+ this.close();
6574
+ }
6575
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDateRangePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6576
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyDateRangePickerComponent, isStandalone: true, selector: "sny-date-range-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, dateFormat: { classPropertyName: "dateFormat", publicName: "dateFormat", isSignal: true, isRequired: false, transformFunction: null }, separator: { classPropertyName: "separator", publicName: "separator", isSignal: true, isRequired: false, transformFunction: null }, dualCalendar: { classPropertyName: "dualCalendar", publicName: "dualCalendar", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "document:click": "onDocumentClick($event)", "keydown.escape": "onEscape()" }, classAttribute: "relative inline-block w-full" }, providers: [
6577
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyDateRangePickerComponent), multi: true },
6578
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerEl"], descendants: true, isSignal: true }, { propertyName: "dropdownRef", first: true, predicate: ["dropdownEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
6579
+ <button
6580
+ #triggerEl
6581
+ type="button"
6582
+ role="combobox"
6583
+ [attr.aria-expanded]="open()"
6584
+ aria-haspopup="dialog"
6585
+ [disabled]="isDisabled()"
6586
+ [class]="triggerClass()"
6587
+ (click)="toggle()"
6588
+ (blur)="onTouched()"
6589
+ >
6590
+ <span [class]="displayValue() ? 'truncate' : 'text-muted-foreground truncate'">
6591
+ {{ displayValue() || placeholder() }}
6592
+ </span>
6593
+ <div class="flex items-center gap-1 shrink-0">
6594
+ @if (clearable() && value()?.start) {
6595
+ <button
6596
+ type="button"
6597
+ class="rounded-sm p-0.5 hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
6598
+ (click)="clear($event)"
6599
+ aria-label="Clear date range"
6600
+ >
6601
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
6602
+ </button>
6603
+ }
6604
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-muted-foreground"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/></svg>
6605
+ </div>
6606
+ </button>
6607
+
6608
+ @if (open()) {
6609
+ <div
6610
+ #dropdownEl
6611
+ role="dialog"
6612
+ aria-modal="true"
6613
+ aria-label="Choose date range"
6614
+ class="fixed z-50 rounded-md border border-border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95"
5169
6615
  >
5170
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg>
5171
- </button>
5172
- </div>
6616
+ <div class="flex flex-col sm:flex-row">
6617
+ <!-- Presets sidebar -->
6618
+ @if (presets().length > 0) {
6619
+ <div class="border-b sm:border-b-0 sm:border-r border-border p-3 space-y-0.5 sm:min-w-[150px]">
6620
+ <p class="px-3 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">Presets</p>
6621
+ @for (preset of presets(); track preset.label) {
6622
+ <button
6623
+ type="button"
6624
+ class="w-full text-left px-3 py-2 text-sm rounded-md hover:bg-accent hover:text-accent-foreground transition-colors cursor-pointer"
6625
+ (mousedown)="selectPreset(preset); $event.preventDefault()"
6626
+ >
6627
+ {{ preset.label }}
6628
+ </button>
6629
+ }
6630
+ </div>
6631
+ }
5173
6632
 
5174
- <div role="grid" aria-label="Calendar" class="grid grid-cols-7 gap-0">
5175
- @for (dayName of weekDays; track dayName) {
5176
- <div class="text-center text-xs text-muted-foreground font-medium py-1">{{ dayName }}</div>
5177
- }
5178
- @for (day of days(); track day.date.getTime()) {
5179
- <button
5180
- [class]="dayClass(day)"
5181
- [disabled]="day.isDisabled"
5182
- [attr.aria-selected]="day.isSelected || null"
5183
- [attr.aria-current]="day.isToday ? 'date' : null"
5184
- [attr.aria-disabled]="day.isDisabled || null"
5185
- (click)="selectDate(day.date)"
5186
- >
5187
- {{ day.day }}
5188
- </button>
5189
- }
5190
- </div>
5191
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
6633
+ <!-- Calendar(s) -->
6634
+ <div class="flex flex-col sm:flex-row">
6635
+ @if (dualCalendar()) {
6636
+ <!-- Left calendar -->
6637
+ <div class="p-1">
6638
+ <div class="flex items-center justify-between px-3 py-2">
6639
+ <button
6640
+ type="button"
6641
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6642
+ (click)="prevMonth()"
6643
+ aria-label="Previous month"
6644
+ >
6645
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
6646
+ </button>
6647
+ <span class="text-sm font-semibold tracking-tight">{{ leftMonthLabel() }}</span>
6648
+ <div class="w-8"></div>
6649
+ </div>
6650
+ <sny-calendar
6651
+ mode="range"
6652
+ [(rangeValue)]="internalRange"
6653
+ [min]="min()"
6654
+ [max]="max()"
6655
+ [locale]="locale()"
6656
+ [showNavigation]="false"
6657
+ [borderless]="true"
6658
+ [initialViewDate]="leftViewDate()"
6659
+ (rangeValueChange)="onRangeChanged($event)"
6660
+ />
6661
+ </div>
6662
+ <div class="border-t sm:border-t-0 sm:border-l border-border"></div>
6663
+ <!-- Right calendar -->
6664
+ <div class="p-1">
6665
+ <div class="flex items-center justify-between px-3 py-2">
6666
+ <div class="w-8"></div>
6667
+ <span class="text-sm font-semibold tracking-tight">{{ rightMonthLabel() }}</span>
6668
+ <button
6669
+ type="button"
6670
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6671
+ (click)="nextMonth()"
6672
+ aria-label="Next month"
6673
+ >
6674
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
6675
+ </button>
6676
+ </div>
6677
+ <sny-calendar
6678
+ mode="range"
6679
+ [(rangeValue)]="internalRange"
6680
+ [min]="min()"
6681
+ [max]="max()"
6682
+ [locale]="locale()"
6683
+ [showNavigation]="false"
6684
+ [borderless]="true"
6685
+ [initialViewDate]="rightViewDate()"
6686
+ (rangeValueChange)="onRangeChanged($event)"
6687
+ />
6688
+ </div>
6689
+ } @else {
6690
+ <!-- Single calendar -->
6691
+ <sny-calendar
6692
+ mode="range"
6693
+ [(rangeValue)]="internalRange"
6694
+ [min]="min()"
6695
+ [max]="max()"
6696
+ [locale]="locale()"
6697
+ (rangeValueChange)="onRangeChanged($event)"
6698
+ />
6699
+ }
6700
+ </div>
6701
+ </div>
6702
+ </div>
6703
+ }
6704
+ `, isInline: true, dependencies: [{ kind: "component", type: SnyCalendarComponent, selector: "sny-calendar", inputs: ["value", "min", "max", "locale", "class", "mode", "rangeValue", "showNavigation", "initialViewDate", "borderless"], outputs: ["valueChange", "rangeValueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5192
6705
  }
5193
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCalendarComponent, decorators: [{
6706
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDateRangePickerComponent, decorators: [{
5194
6707
  type: Component,
5195
6708
  args: [{
5196
- selector: 'sny-calendar',
6709
+ selector: 'sny-date-range-picker',
5197
6710
  standalone: true,
5198
6711
  changeDetection: ChangeDetectionStrategy.OnPush,
5199
- host: {
5200
- '[class]': '"inline-block p-3 rounded-md border bg-background"',
5201
- '(keydown)': 'onKeydown($event)',
5202
- },
6712
+ imports: [SnyCalendarComponent],
6713
+ host: { class: 'relative inline-block w-full' },
5203
6714
  providers: [
5204
- { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyCalendarComponent), multi: true },
6715
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyDateRangePickerComponent), multi: true },
5205
6716
  ],
5206
6717
  template: `
5207
- <div class="flex items-center justify-between mb-4">
5208
- <button
5209
- class="inline-flex items-center justify-center rounded-md text-sm h-7 w-7 hover:bg-accent"
5210
- (click)="prevMonth()"
5211
- aria-label="Previous month"
5212
- >
5213
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m15 18-6-6 6-6"/></svg>
5214
- </button>
5215
- <span class="text-sm font-medium">{{ monthYearLabel() }}</span>
5216
- <button
5217
- class="inline-flex items-center justify-center rounded-md text-sm h-7 w-7 hover:bg-accent"
5218
- (click)="nextMonth()"
5219
- aria-label="Next month"
6718
+ <button
6719
+ #triggerEl
6720
+ type="button"
6721
+ role="combobox"
6722
+ [attr.aria-expanded]="open()"
6723
+ aria-haspopup="dialog"
6724
+ [disabled]="isDisabled()"
6725
+ [class]="triggerClass()"
6726
+ (click)="toggle()"
6727
+ (blur)="onTouched()"
6728
+ >
6729
+ <span [class]="displayValue() ? 'truncate' : 'text-muted-foreground truncate'">
6730
+ {{ displayValue() || placeholder() }}
6731
+ </span>
6732
+ <div class="flex items-center gap-1 shrink-0">
6733
+ @if (clearable() && value()?.start) {
6734
+ <button
6735
+ type="button"
6736
+ class="rounded-sm p-0.5 hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
6737
+ (click)="clear($event)"
6738
+ aria-label="Clear date range"
6739
+ >
6740
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
6741
+ </button>
6742
+ }
6743
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-muted-foreground"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/></svg>
6744
+ </div>
6745
+ </button>
6746
+
6747
+ @if (open()) {
6748
+ <div
6749
+ #dropdownEl
6750
+ role="dialog"
6751
+ aria-modal="true"
6752
+ aria-label="Choose date range"
6753
+ class="fixed z-50 rounded-md border border-border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95"
5220
6754
  >
5221
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg>
5222
- </button>
5223
- </div>
6755
+ <div class="flex flex-col sm:flex-row">
6756
+ <!-- Presets sidebar -->
6757
+ @if (presets().length > 0) {
6758
+ <div class="border-b sm:border-b-0 sm:border-r border-border p-3 space-y-0.5 sm:min-w-[150px]">
6759
+ <p class="px-3 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">Presets</p>
6760
+ @for (preset of presets(); track preset.label) {
6761
+ <button
6762
+ type="button"
6763
+ class="w-full text-left px-3 py-2 text-sm rounded-md hover:bg-accent hover:text-accent-foreground transition-colors cursor-pointer"
6764
+ (mousedown)="selectPreset(preset); $event.preventDefault()"
6765
+ >
6766
+ {{ preset.label }}
6767
+ </button>
6768
+ }
6769
+ </div>
6770
+ }
5224
6771
 
5225
- <div role="grid" aria-label="Calendar" class="grid grid-cols-7 gap-0">
5226
- @for (dayName of weekDays; track dayName) {
5227
- <div class="text-center text-xs text-muted-foreground font-medium py-1">{{ dayName }}</div>
5228
- }
5229
- @for (day of days(); track day.date.getTime()) {
5230
- <button
5231
- [class]="dayClass(day)"
5232
- [disabled]="day.isDisabled"
5233
- [attr.aria-selected]="day.isSelected || null"
5234
- [attr.aria-current]="day.isToday ? 'date' : null"
5235
- [attr.aria-disabled]="day.isDisabled || null"
5236
- (click)="selectDate(day.date)"
5237
- >
5238
- {{ day.day }}
5239
- </button>
5240
- }
5241
- </div>
6772
+ <!-- Calendar(s) -->
6773
+ <div class="flex flex-col sm:flex-row">
6774
+ @if (dualCalendar()) {
6775
+ <!-- Left calendar -->
6776
+ <div class="p-1">
6777
+ <div class="flex items-center justify-between px-3 py-2">
6778
+ <button
6779
+ type="button"
6780
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6781
+ (click)="prevMonth()"
6782
+ aria-label="Previous month"
6783
+ >
6784
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
6785
+ </button>
6786
+ <span class="text-sm font-semibold tracking-tight">{{ leftMonthLabel() }}</span>
6787
+ <div class="w-8"></div>
6788
+ </div>
6789
+ <sny-calendar
6790
+ mode="range"
6791
+ [(rangeValue)]="internalRange"
6792
+ [min]="min()"
6793
+ [max]="max()"
6794
+ [locale]="locale()"
6795
+ [showNavigation]="false"
6796
+ [borderless]="true"
6797
+ [initialViewDate]="leftViewDate()"
6798
+ (rangeValueChange)="onRangeChanged($event)"
6799
+ />
6800
+ </div>
6801
+ <div class="border-t sm:border-t-0 sm:border-l border-border"></div>
6802
+ <!-- Right calendar -->
6803
+ <div class="p-1">
6804
+ <div class="flex items-center justify-between px-3 py-2">
6805
+ <div class="w-8"></div>
6806
+ <span class="text-sm font-semibold tracking-tight">{{ rightMonthLabel() }}</span>
6807
+ <button
6808
+ type="button"
6809
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6810
+ (click)="nextMonth()"
6811
+ aria-label="Next month"
6812
+ >
6813
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
6814
+ </button>
6815
+ </div>
6816
+ <sny-calendar
6817
+ mode="range"
6818
+ [(rangeValue)]="internalRange"
6819
+ [min]="min()"
6820
+ [max]="max()"
6821
+ [locale]="locale()"
6822
+ [showNavigation]="false"
6823
+ [borderless]="true"
6824
+ [initialViewDate]="rightViewDate()"
6825
+ (rangeValueChange)="onRangeChanged($event)"
6826
+ />
6827
+ </div>
6828
+ } @else {
6829
+ <!-- Single calendar -->
6830
+ <sny-calendar
6831
+ mode="range"
6832
+ [(rangeValue)]="internalRange"
6833
+ [min]="min()"
6834
+ [max]="max()"
6835
+ [locale]="locale()"
6836
+ (rangeValueChange)="onRangeChanged($event)"
6837
+ />
6838
+ }
6839
+ </div>
6840
+ </div>
6841
+ </div>
6842
+ }
5242
6843
  `,
5243
6844
  }]
5244
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
6845
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], separator: [{ type: i0.Input, args: [{ isSignal: true, alias: "separator", required: false }] }], dualCalendar: [{ type: i0.Input, args: [{ isSignal: true, alias: "dualCalendar", required: false }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], triggerRef: [{ type: i0.ViewChild, args: ['triggerEl', { isSignal: true }] }], dropdownRef: [{ type: i0.ViewChild, args: ['dropdownEl', { isSignal: true }] }], onDocumentClick: [{
6846
+ type: HostListener,
6847
+ args: ['document:click', ['$event']]
6848
+ }], onEscape: [{
6849
+ type: HostListener,
6850
+ args: ['keydown.escape']
6851
+ }] } });
5245
6852
 
5246
6853
  class SnyValidatorDirective {
5247
6854
  control = input(null, ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
@@ -5302,5 +6909,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
5302
6909
  * Generated bundle index. Do not edit.
5303
6910
  */
5304
6911
 
5305
- export { SNY_ACCORDION, SNY_ACCORDION_ITEM, SNY_CAROUSEL, SNY_CHAT_BUBBLE, SNY_CONFIG, SNY_DIALOG_DATA, SNY_DRAWER, SNY_DROPDOWN, SNY_FAB, SNY_SHEET_DATA, SNY_STEPS, SNY_TABLE, SNY_TABS, SNY_TIMELINE, SnyAccordionContentDirective, SnyAccordionDirective, SnyAccordionItemDirective, SnyAccordionTriggerDirective, SnyAlertDescriptionDirective, SnyAlertDirective, SnyAlertTitleDirective, SnyAvatarComponent, SnyBadgeDirective, SnyBreadcrumbDirective, SnyBreadcrumbItemDirective, SnyBreadcrumbLinkDirective, SnyBreadcrumbListDirective, SnyBreadcrumbPageDirective, SnyBreadcrumbSeparatorDirective, SnyButtonDirective, SnyButtonGroupDirective, SnyCalendarComponent, SnyCardContentDirective, SnyCardDescriptionDirective, SnyCardDirective, SnyCardFooterDirective, SnyCardHeaderDirective, SnyCardTitleDirective, SnyCarouselContentDirective, SnyCarouselDirective, SnyCarouselItemDirective, SnyCarouselNextDirective, SnyCarouselPrevDirective, SnyChatBubbleAvatarDirective, SnyChatBubbleBodyDirective, SnyChatBubbleContentDirective, SnyChatBubbleDirective, SnyChatBubbleFooterDirective, SnyChatBubbleHeaderDirective, SnyCheckboxDirective, SnyComboboxComponent, SnyDialogCloseDirective, SnyDialogContentDirective, SnyDialogDescriptionDirective, SnyDialogFooterDirective, SnyDialogHeaderDirective, SnyDialogRef, SnyDialogService, SnyDialogTitleDirective, SnyDiffComponent, SnyDividerComponent, SnyDockDirective, SnyDockItemDirective, SnyDrawerContentDirective, SnyDrawerLayoutComponent, SnyDrawerLayoutDirective, SnyDrawerSideDirective, SnyDropdownContentDirective, SnyDropdownDirective, SnyDropdownTriggerDirective, SnyFabActionDirective, SnyFabDirective, SnyFabTriggerDirective, SnyFieldsetContentDirective, SnyFieldsetDirective, SnyFieldsetLegendDirective, SnyFileInputComponent, SnyIndicatorBadgeDirective, SnyIndicatorDirective, SnyInputDirective, SnyKbdDirective, SnyLabelDirective, SnyLinkDirective, SnyListDirective, SnyListItemActionDirective, SnyListItemContentDirective, SnyListItemDirective, SnyListItemIconDirective, SnyLoaderComponent, SnyMenuContentDirective, SnyMenuItemDirective, SnyMenuLabelDirective, SnyMenuSeparatorDirective, SnyNavbarBrandDirective, SnyNavbarContentDirective, SnyNavbarDirective, SnyNavbarEndDirective, SnyPaginationComponent, SnyProgressComponent, SnyRadialProgressComponent, SnyRadioDirective, SnyRatingComponent, SnySelectComponent, SnySheetCloseDirective, SnySheetContentDirective, SnySheetDescriptionDirective, SnySheetHeaderDirective, SnySheetRef, SnySheetService, SnySheetTitleDirective, SnySkeletonDirective, SnySliderComponent, SnyStatDescriptionDirective, SnyStatDirective, SnyStatFigureDirective, SnyStatTitleDirective, SnyStatValueDirective, SnyStatusDirective, SnyStepDirective, SnyStepsDirective, SnySwitchComponent, SnyTableBodyDirective, SnyTableCaptionDirective, SnyTableCellDirective, SnyTableDirective, SnyTableFooterDirective, SnyTableHeadDirective, SnyTableHeaderDirective, SnyTableRowDirective, SnyTabsContentDirective, SnyTabsDirective, SnyTabsListDirective, SnyTabsTriggerDirective, SnyTextareaDirective, SnyTimelineDirective, SnyTimelineEndDirective, SnyTimelineItemDirective, SnyTimelineMiddleDirective, SnyTimelineStartDirective, SnyToastService, SnyToasterComponent, SnyToggleDirective, SnyTooltipDirective, SnyValidatorDirective, SnyValidatorHintDirective, ThemeService, alertVariants, avatarVariants, badgeVariants, buttonGroupVariants, buttonVariants, cardVariants, checkboxVariants, cn, comboboxTriggerVariants, dividerVariants, dropdownContentVariants, dropdownItemVariants, fieldsetVariants, fileInputVariants, inputVariants, kbdVariants, labelVariants, linkVariants, loaderVariants, paginationItemVariants, progressBarVariants, progressTrackVariants, provideSonnyUI, radioVariants, ratingVariants, selectTriggerVariants, skeletonVariants, sliderTrackVariants, statusVariants, switchTrackVariants, tableCellVariants, tableVariants, tabsListVariants, tabsTriggerVariants, textareaVariants, toastVariants, toggleVariants, tooltipVariants };
6912
+ export { SNY_ACCORDION, SNY_ACCORDION_ITEM, SNY_CAROUSEL, SNY_CHAT_BUBBLE, SNY_CONFIG, SNY_DIALOG_DATA, SNY_DRAWER, SNY_DROPDOWN, SNY_FAB, SNY_SHEET_DATA, SNY_STEPS, SNY_TABLE, SNY_TABS, SNY_TIMELINE, SnyAccordionContentDirective, SnyAccordionDirective, SnyAccordionItemDirective, SnyAccordionTriggerDirective, SnyAlertDescriptionDirective, SnyAlertDirective, SnyAlertTitleDirective, SnyAvatarComponent, SnyBadgeDirective, SnyBreadcrumbDirective, SnyBreadcrumbItemDirective, SnyBreadcrumbLinkDirective, SnyBreadcrumbListDirective, SnyBreadcrumbPageDirective, SnyBreadcrumbSeparatorDirective, SnyBulkActionsDefDirective, SnyButtonDirective, SnyButtonGroupDirective, SnyCalendarComponent, SnyCardContentDirective, SnyCardDescriptionDirective, SnyCardDirective, SnyCardFooterDirective, SnyCardHeaderDirective, SnyCardTitleDirective, SnyCarouselContentDirective, SnyCarouselDirective, SnyCarouselItemDirective, SnyCarouselNextDirective, SnyCarouselPrevDirective, SnyCellDefDirective, SnyChatBubbleAvatarDirective, SnyChatBubbleBodyDirective, SnyChatBubbleContentDirective, SnyChatBubbleDirective, SnyChatBubbleFooterDirective, SnyChatBubbleHeaderDirective, SnyCheckboxDirective, SnyComboboxComponent, SnyDataTableComponent, SnyDatePickerComponent, SnyDateRangePickerComponent, SnyDialogCloseDirective, SnyDialogContentDirective, SnyDialogDescriptionDirective, SnyDialogFooterDirective, SnyDialogHeaderDirective, SnyDialogRef, SnyDialogService, SnyDialogTitleDirective, SnyDiffComponent, SnyDividerComponent, SnyDockDirective, SnyDockItemDirective, SnyDrawerContentDirective, SnyDrawerLayoutComponent, SnyDrawerLayoutDirective, SnyDrawerSideDirective, SnyDropdownContentDirective, SnyDropdownDirective, SnyDropdownTriggerDirective, SnyFabActionDirective, SnyFabDirective, SnyFabTriggerDirective, SnyFieldsetContentDirective, SnyFieldsetDirective, SnyFieldsetLegendDirective, SnyFileInputComponent, SnyHeaderCellDefDirective, SnyIndicatorBadgeDirective, SnyIndicatorDirective, SnyInputDirective, SnyKbdDirective, SnyLabelDirective, SnyLinkDirective, SnyListDirective, SnyListItemActionDirective, SnyListItemContentDirective, SnyListItemDirective, SnyListItemIconDirective, SnyLoaderComponent, SnyMenuContentDirective, SnyMenuItemDirective, SnyMenuLabelDirective, SnyMenuSeparatorDirective, SnyNavbarBrandDirective, SnyNavbarContentDirective, SnyNavbarDirective, SnyNavbarEndDirective, SnyPaginationComponent, SnyProgressComponent, SnyRadialProgressComponent, SnyRadioDirective, SnyRatingComponent, SnyRowExpandDefDirective, SnySelectComponent, SnySheetCloseDirective, SnySheetContentDirective, SnySheetDescriptionDirective, SnySheetHeaderDirective, SnySheetRef, SnySheetService, SnySheetTitleDirective, SnySkeletonDirective, SnySliderComponent, SnyStatDescriptionDirective, SnyStatDirective, SnyStatFigureDirective, SnyStatTitleDirective, SnyStatValueDirective, SnyStatusDirective, SnyStepDirective, SnyStepsDirective, SnySwitchComponent, SnyTableBodyDirective, SnyTableCaptionDirective, SnyTableCellDirective, SnyTableDirective, SnyTableFooterDirective, SnyTableHeadDirective, SnyTableHeaderDirective, SnyTableRowDirective, SnyTabsContentDirective, SnyTabsDirective, SnyTabsListDirective, SnyTabsTriggerDirective, SnyTextareaDirective, SnyTimelineDirective, SnyTimelineEndDirective, SnyTimelineItemDirective, SnyTimelineMiddleDirective, SnyTimelineStartDirective, SnyToastService, SnyToasterComponent, SnyToggleDirective, SnyTooltipDirective, SnyValidatorDirective, SnyValidatorHintDirective, ThemeService, alertVariants, avatarVariants, badgeVariants, buttonGroupVariants, buttonVariants, cardVariants, checkboxVariants, cn, comboboxTriggerVariants, datePickerTriggerVariants, dividerVariants, dropdownContentVariants, dropdownItemVariants, fieldsetVariants, fileInputVariants, inputVariants, kbdVariants, labelVariants, linkVariants, loaderVariants, paginationItemVariants, progressBarVariants, progressTrackVariants, provideSonnyUI, radioVariants, ratingVariants, selectTriggerVariants, skeletonVariants, sliderTrackVariants, statusVariants, switchTrackVariants, tableCellVariants, tableVariants, tabsListVariants, tabsTriggerVariants, textareaVariants, toastVariants, toggleVariants, tooltipVariants };
5306
6913
  //# sourceMappingURL=sonny-ui-core.mjs.map