@tolle_/tolle-ui 0.0.28-beta → 0.0.30-beta

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.
@@ -1,15 +1,15 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
3
  import * as i0 from '@angular/core';
4
- import { Component, Input, forwardRef, ViewChild, Injectable, Optional, HostListener, ContentChildren, EventEmitter, Output, Directive, inject, PLATFORM_ID, Inject, InjectionToken, APP_INITIALIZER, ChangeDetectorRef, ChangeDetectionStrategy, TemplateRef, Injector, HostBinding } from '@angular/core';
4
+ import { Component, Input, forwardRef, ViewChild, Injectable, Optional, HostListener, ContentChildren, EventEmitter, Output, Directive, inject, PLATFORM_ID, Inject, InjectionToken, APP_INITIALIZER, ChangeDetectorRef, ChangeDetectionStrategy, TemplateRef, Injector, HostBinding, ViewChildren } from '@angular/core';
5
5
  import * as i1 from '@angular/common';
6
6
  import { CommonModule, isPlatformBrowser, DOCUMENT, NgIf, NgTemplateOutlet } from '@angular/common';
7
7
  import { cva } from 'class-variance-authority';
8
8
  import * as i2 from '@angular/forms';
9
9
  import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
10
- import { autoUpdate, computePosition, offset, flip, shift } from '@floating-ui/dom';
10
+ import { autoUpdate, computePosition, offset, flip, shift, size } from '@floating-ui/dom';
11
11
  import { Subject, Subscription, BehaviorSubject } from 'rxjs';
12
- import { startOfWeek, startOfMonth, endOfWeek, endOfMonth, eachDayOfInterval, subMonths, subYears, addMonths, addYears, isSameMonth, setMonth, setYear, isSameDay, isToday, isBefore, startOfDay, parse, isValid, format, isWithinInterval } from 'date-fns';
12
+ import { format, startOfWeek, startOfMonth, endOfWeek, endOfMonth, eachDayOfInterval, subMonths, subYears, addMonths, addYears, setMonth, setYear, isSameDay, isToday, isSameMonth, isBefore, startOfDay, parse, isValid, isWithinInterval } from 'date-fns';
13
13
  import * as i1$1 from '@angular/cdk/overlay';
14
14
  import { OverlayConfig } from '@angular/cdk/overlay';
15
15
  import { ComponentPortal } from '@angular/cdk/portal';
@@ -512,6 +512,8 @@ class SelectItemComponent {
512
512
  value;
513
513
  class = '';
514
514
  selected = false;
515
+ disabled = false;
516
+ multiSelect = false; // Will be set by parent component
515
517
  hidden = false;
516
518
  constructor(selectService, el) {
517
519
  this.selectService = selectService;
@@ -519,33 +521,85 @@ class SelectItemComponent {
519
521
  }
520
522
  // Helper method for the parent to get the searchable text
521
523
  getLabel() {
522
- return this.el.nativeElement.innerText || '';
524
+ return this.el.nativeElement.textContent?.trim() || '';
525
+ }
526
+ getItemClasses() {
527
+ return cn(
528
+ // Base state
529
+ 'focus:bg-accent focus:text-accent-foreground',
530
+ // Hover states (only if not disabled)
531
+ !this.disabled && [
532
+ 'cursor-pointer',
533
+ 'hover:bg-accent hover:text-accent-foreground'
534
+ ],
535
+ // Selected state
536
+ this.selected && [
537
+ this.multiSelect
538
+ ? 'bg-primary/5 text-foreground'
539
+ : 'bg-accent text-accent-foreground'
540
+ ],
541
+ // Disabled state
542
+ this.disabled && [
543
+ 'opacity-50',
544
+ 'cursor-not-allowed',
545
+ 'hover:bg-transparent hover:text-foreground'
546
+ ]);
523
547
  }
524
548
  onClick(event) {
525
- if (this.hidden)
549
+ if (this.hidden || this.disabled)
526
550
  return;
527
551
  event.stopPropagation();
528
552
  if (this.selectService) {
529
- // Get the text content to show in the trigger button
530
- const label = this.el.nativeElement.innerText.trim();
553
+ const label = this.getLabel();
554
+ if (this.multiSelect) {
555
+ // For multi-select, toggle selection
556
+ this.selected = !this.selected;
557
+ }
558
+ // For both single and multi-select, notify parent
531
559
  this.selectService.registerClick(this.value, label);
532
560
  }
533
561
  }
534
562
  cn = cn;
535
563
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectItemComponent, deps: [{ token: SelectService, optional: true }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
536
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SelectItemComponent, isStandalone: true, selector: "tolle-select-item", inputs: { value: "value", class: "class", selected: "selected" }, host: { listeners: { "click": "onClick($event)" } }, ngImport: i0, template: `
564
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SelectItemComponent, isStandalone: true, selector: "tolle-select-item", inputs: { value: "value", class: "class", selected: "selected", disabled: "disabled", multiSelect: "multiSelect" }, host: { listeners: { "click": "onClick($event)" } }, ngImport: i0, template: `
537
565
  <div
538
566
  *ngIf="!hidden"
539
567
  [class]="cn(
540
- 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors',
541
- selected ? 'bg-accent text-accent-foreground' : '',
568
+ 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors',
569
+ getItemClasses(),
542
570
  class
543
571
  )"
572
+ [attr.aria-disabled]="disabled"
573
+ [attr.aria-selected]="selected"
574
+ role="option"
544
575
  >
545
- <span *ngIf="selected" class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
576
+ <!-- Single Select: Checkmark -->
577
+ <span *ngIf="!multiSelect && selected" class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
546
578
  <i class="ri-check-line text-primary"></i>
547
579
  </span>
548
- <ng-content></ng-content>
580
+
581
+ <!-- Multi-Select: Checkbox -->
582
+ <span *ngIf="multiSelect" class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
583
+ <div [class]="cn(
584
+ 'flex h-4 w-4 items-center justify-center rounded-sm border transition-all duration-200',
585
+ selected ? 'border-primary bg-primary text-primary-foreground' : 'border-input'
586
+ )">
587
+ <i [class]="cn(
588
+ 'ri-check-line text-xs transition-all duration-200',
589
+ selected ? 'opacity-100 scale-100' : 'opacity-0 scale-75'
590
+ )"></i>
591
+ </div>
592
+ </span>
593
+
594
+ <!-- Content -->
595
+ <span class="flex-1 truncate">
596
+ <ng-content></ng-content>
597
+ </span>
598
+
599
+ <!-- Disabled indicator -->
600
+ <span *ngIf="disabled && !selected" class="ml-2 text-xs text-muted-foreground/50">
601
+ <i class="ri-forbid-line"></i>
602
+ </span>
549
603
  </div>
550
604
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
551
605
  }
@@ -559,15 +613,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
559
613
  <div
560
614
  *ngIf="!hidden"
561
615
  [class]="cn(
562
- 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors',
563
- selected ? 'bg-accent text-accent-foreground' : '',
616
+ 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors',
617
+ getItemClasses(),
564
618
  class
565
619
  )"
620
+ [attr.aria-disabled]="disabled"
621
+ [attr.aria-selected]="selected"
622
+ role="option"
566
623
  >
567
- <span *ngIf="selected" class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
624
+ <!-- Single Select: Checkmark -->
625
+ <span *ngIf="!multiSelect && selected" class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
568
626
  <i class="ri-check-line text-primary"></i>
569
627
  </span>
570
- <ng-content></ng-content>
628
+
629
+ <!-- Multi-Select: Checkbox -->
630
+ <span *ngIf="multiSelect" class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
631
+ <div [class]="cn(
632
+ 'flex h-4 w-4 items-center justify-center rounded-sm border transition-all duration-200',
633
+ selected ? 'border-primary bg-primary text-primary-foreground' : 'border-input'
634
+ )">
635
+ <i [class]="cn(
636
+ 'ri-check-line text-xs transition-all duration-200',
637
+ selected ? 'opacity-100 scale-100' : 'opacity-0 scale-75'
638
+ )"></i>
639
+ </div>
640
+ </span>
641
+
642
+ <!-- Content -->
643
+ <span class="flex-1 truncate">
644
+ <ng-content></ng-content>
645
+ </span>
646
+
647
+ <!-- Disabled indicator -->
648
+ <span *ngIf="disabled && !selected" class="ml-2 text-xs text-muted-foreground/50">
649
+ <i class="ri-forbid-line"></i>
650
+ </span>
571
651
  </div>
572
652
  `,
573
653
  }]
@@ -579,6 +659,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
579
659
  type: Input
580
660
  }], selected: [{
581
661
  type: Input
662
+ }], disabled: [{
663
+ type: Input
664
+ }], multiSelect: [{
665
+ type: Input
582
666
  }], onClick: [{
583
667
  type: HostListener,
584
668
  args: ['click', ['$event']]
@@ -618,44 +702,25 @@ class SelectComponent {
618
702
  this.close();
619
703
  }));
620
704
  }
621
- // SIMPLIFIED: Zardui-inspired trigger styling
622
705
  get computedTriggerClass() {
623
- return cn(
624
- // Base styles
625
- 'flex w-full items-center justify-between rounded-md border transition-all duration-200', 'bg-background text-foreground',
626
- // Border and shadow
627
- 'border-input shadow-sm',
628
- // Sizing
629
- this.size === 'xs' && 'h-8 px-2 text-xs', this.size === 'sm' && 'h-9 px-3 text-sm', this.size === 'default' && 'h-10 px-3 text-sm', this.size === 'lg' && 'h-11 px-4 text-base',
630
- // Focus state - SIMPLE LIKE ZARDUI
631
- !(this.readonly || this.disabled) && [
706
+ return cn('flex w-full items-center justify-between rounded-md border transition-all duration-200', 'bg-background text-foreground', 'border-input shadow-sm', this.size === 'xs' && 'h-8 px-2 text-xs', this.size === 'sm' && 'h-9 px-3 text-sm', this.size === 'default' && 'h-10 px-3 text-sm', this.size === 'lg' && 'h-11 px-4 text-base', !(this.readonly || this.disabled) && [
632
707
  'focus:outline-none',
633
708
  'focus:ring-4',
634
709
  'focus:ring-ring/30',
635
710
  'focus:ring-offset-0',
636
711
  'focus:shadow-none',
637
- // Border darkens on focus automatically
638
712
  'focus:border-primary/80'
639
- ],
640
- // Hover state
641
- !(this.readonly || this.disabled) && 'hover:border-accent',
642
- // Disabled state
643
- this.disabled && [
713
+ ], !(this.readonly || this.disabled) && 'hover:border-accent', this.disabled && [
644
714
  'cursor-not-allowed opacity-50',
645
715
  'border-opacity-50'
646
- ],
647
- // Readonly state
648
- this.readonly && [
716
+ ], this.readonly && [
649
717
  'cursor-default',
650
718
  'border-dashed',
651
719
  !this.disabled && 'focus:ring-0 focus:border-opacity-100'
652
720
  ], this.class);
653
721
  }
654
- // UPDATED: Dynamic icon sizing relative to the trigger size
655
722
  get iconClass() {
656
- return cn('ri-arrow-down-s-line text-muted-foreground ml-2 transition-transform duration-200', this.isOpen ? 'rotate-180' : '', (this.size === 'xs' || this.size === 'sm') ? 'text-[14px]' : 'text-[18px]',
657
- // Hide or fade icon when interaction is blocked
658
- (this.disabled || this.readonly) && 'opacity-30');
723
+ return cn('ri-arrow-down-s-line text-muted-foreground ml-2 transition-transform duration-200', this.isOpen ? 'rotate-180' : '', (this.size === 'xs' || this.size === 'sm') ? 'text-[14px]' : 'text-[18px]', (this.disabled || this.readonly) && 'opacity-30');
659
724
  }
660
725
  ngAfterContentInit() {
661
726
  this.updateItemSelection();
@@ -675,9 +740,7 @@ class SelectComponent {
675
740
  }
676
741
  open() {
677
742
  this.isOpen = true;
678
- // Trigger focus on the button for focus styling
679
743
  this.trigger.nativeElement.focus();
680
- // Tick to ensure DOM is rendered before positioning
681
744
  setTimeout(() => this.updatePosition());
682
745
  }
683
746
  close() {
@@ -692,10 +755,25 @@ class SelectComponent {
692
755
  return;
693
756
  this.cleanupAutoUpdate = autoUpdate(this.trigger.nativeElement, this.popover.nativeElement, () => {
694
757
  computePosition(this.trigger.nativeElement, this.popover.nativeElement, {
758
+ strategy: 'fixed', // 3. Use fixed strategy
695
759
  placement: 'bottom-start',
696
- middleware: [offset(4), flip(), shift({ padding: 8 })],
697
- }).then(({ x, y }) => {
760
+ middleware: [
761
+ offset(4),
762
+ flip(),
763
+ shift({ padding: 8 }),
764
+ // 4. Use size middleware to sync width and handle constraints
765
+ size({
766
+ apply({ rects, elements, availableHeight }) {
767
+ Object.assign(elements.floating.style, {
768
+ width: `${rects.reference.width}px`,
769
+ maxHeight: `${availableHeight}px`
770
+ });
771
+ },
772
+ }),
773
+ ],
774
+ }).then(({ x, y, strategy }) => {
698
775
  Object.assign(this.popover.nativeElement.style, {
776
+ position: strategy, // 5. Apply strategy to style
699
777
  left: `${x}px`,
700
778
  top: `${y}px`,
701
779
  visibility: 'visible',
@@ -746,7 +824,7 @@ class SelectComponent {
746
824
  multi: true
747
825
  }
748
826
  ], queries: [{ propertyName: "items", predicate: SelectItemComponent, descendants: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "popover", first: true, predicate: ["popover"], descendants: true }, { propertyName: "container", first: true, predicate: ["container"], descendants: true }], ngImport: i0, template: `
749
- <div [class]="cn('w-full', 'size-' + size)" #container>
827
+ <div [class]="cn('relative w-full', 'size-' + size)" #container>
750
828
  <button
751
829
  type="button"
752
830
  #trigger
@@ -763,8 +841,7 @@ class SelectComponent {
763
841
  <div
764
842
  #popover
765
843
  *ngIf="isOpen"
766
- [ngStyle]="{minWidth: container.clientWidth+'px'}"
767
- class="absolute bg-popover z-50 min-w-full max-h-[300px] overflow-auto flex flex-col rounded-md border border-border text-popover-foreground shadow-md"
844
+ class="fixed bg-popover z-[999] overflow-auto flex flex-col rounded-md border border-border text-popover-foreground shadow-md"
768
845
  style="visibility: hidden; top: 0; left: 0;">
769
846
  <div *ngIf="searchable" class="p-2 border-b border-border bg-popover h-auto">
770
847
  <tolle-input
@@ -785,7 +862,7 @@ class SelectComponent {
785
862
  </div>
786
863
  </div>
787
864
  </div>
788
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error", "hideHintOnFocus"] }] });
865
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: InputComponent, selector: "tolle-input", inputs: ["id", "label", "hint", "errorMessage", "type", "placeholder", "size", "containerClass", "class", "disabled", "readonly", "error", "hideHintOnFocus"] }] });
789
866
  }
790
867
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SelectComponent, decorators: [{
791
868
  type: Component,
@@ -802,7 +879,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
802
879
  }
803
880
  ],
804
881
  template: `
805
- <div [class]="cn('w-full', 'size-' + size)" #container>
882
+ <div [class]="cn('relative w-full', 'size-' + size)" #container>
806
883
  <button
807
884
  type="button"
808
885
  #trigger
@@ -819,8 +896,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
819
896
  <div
820
897
  #popover
821
898
  *ngIf="isOpen"
822
- [ngStyle]="{minWidth: container.clientWidth+'px'}"
823
- class="absolute bg-popover z-50 min-w-full max-h-[300px] overflow-auto flex flex-col rounded-md border border-border text-popover-foreground shadow-md"
899
+ class="fixed bg-popover z-[999] overflow-auto flex flex-col rounded-md border border-border text-popover-foreground shadow-md"
824
900
  style="visibility: hidden; top: 0; left: 0;">
825
901
  <div *ngIf="searchable" class="p-2 border-b border-border bg-popover h-auto">
826
902
  <tolle-input
@@ -1866,6 +1942,9 @@ class MultiSelectComponent {
1866
1942
  searchable = false;
1867
1943
  disabled = false;
1868
1944
  class = '';
1945
+ maxSelections;
1946
+ maxDisplayItems = 3;
1947
+ error = false; // Added to support error styling
1869
1948
  trigger;
1870
1949
  popover;
1871
1950
  items;
@@ -1882,10 +1961,58 @@ class MultiSelectComponent {
1882
1961
  this.toggleValue(val);
1883
1962
  });
1884
1963
  }
1964
+ // NEW: Matches InputComponent styles exactly
1965
+ get computedTriggerClass() {
1966
+ return cn(
1967
+ // Base styles
1968
+ 'flex min-h-10 w-full items-center justify-between rounded-md border transition-all duration-200 h-auto', 'bg-background text-sm',
1969
+ // Border and shadow
1970
+ 'border-input shadow-sm',
1971
+ // Padding based on size (aligned with InputComponent logic)
1972
+ this.size === 'xs' && 'px-2 py-1', this.size === 'sm' && 'px-3 py-1.5', this.size === 'default' && 'px-3 py-2', this.size === 'lg' && 'px-4 py-2',
1973
+ // Focus state - ZARDUI STYLE (Soft ring, no offset)
1974
+ !this.disabled && [
1975
+ 'focus:outline-none',
1976
+ 'focus:ring-4',
1977
+ 'focus:ring-ring/30',
1978
+ 'focus:ring-offset-0',
1979
+ 'focus:shadow-none',
1980
+ 'focus:border-primary/80' // Darkens border on focus
1981
+ ],
1982
+ // Hover state
1983
+ !this.disabled && 'hover:border-accent',
1984
+ // Error state
1985
+ this.error && [
1986
+ 'border-destructive',
1987
+ !this.disabled && [
1988
+ 'focus:border-destructive/80',
1989
+ 'focus:ring-destructive/30'
1990
+ ]
1991
+ ],
1992
+ // Disabled state
1993
+ this.disabled && [
1994
+ 'cursor-not-allowed opacity-50',
1995
+ 'border-opacity-50'
1996
+ ], this.class);
1997
+ }
1885
1998
  ngAfterContentInit() {
1886
1999
  this.syncItems();
1887
2000
  this.items.changes.subscribe(() => this.syncItems());
1888
2001
  }
2002
+ get displayItems() {
2003
+ return this.selectedItems.slice(0, this.maxDisplayItems);
2004
+ }
2005
+ get exceedsDisplayLimit() {
2006
+ return this.value.length > this.maxDisplayItems;
2007
+ }
2008
+ get selectableItems() {
2009
+ return this.items ? this.items.filter(item => !item.disabled) : [];
2010
+ }
2011
+ get availableSelections() {
2012
+ if (!this.maxSelections)
2013
+ return Infinity;
2014
+ return Math.max(0, this.maxSelections - this.value.length);
2015
+ }
1889
2016
  toggle() {
1890
2017
  if (this.disabled)
1891
2018
  return;
@@ -1897,16 +2024,34 @@ class MultiSelectComponent {
1897
2024
  }
1898
2025
  close() {
1899
2026
  this.isOpen = false;
2027
+ this.searchQuery = '';
2028
+ this.onSearchChange('');
1900
2029
  if (this.cleanup)
1901
2030
  this.cleanup();
1902
2031
  }
1903
2032
  updatePosition() {
2033
+ if (!this.trigger || !this.popover)
2034
+ return;
1904
2035
  this.cleanup = autoUpdate(this.trigger.nativeElement, this.popover.nativeElement, () => {
1905
2036
  computePosition(this.trigger.nativeElement, this.popover.nativeElement, {
2037
+ strategy: 'fixed',
1906
2038
  placement: 'bottom-start',
1907
- middleware: [offset(4), flip(), shift({ padding: 8 })],
1908
- }).then(({ x, y }) => {
2039
+ middleware: [
2040
+ offset(4),
2041
+ flip(),
2042
+ shift({ padding: 8 }),
2043
+ size({
2044
+ apply({ rects, elements, availableHeight }) {
2045
+ Object.assign(elements.floating.style, {
2046
+ width: `${rects.reference.width}px`,
2047
+ maxHeight: `${availableHeight}px`
2048
+ });
2049
+ },
2050
+ }),
2051
+ ],
2052
+ }).then(({ x, y, strategy }) => {
1909
2053
  Object.assign(this.popover.nativeElement.style, {
2054
+ position: strategy,
1910
2055
  left: `${x}px`,
1911
2056
  top: `${y}px`,
1912
2057
  visibility: 'visible',
@@ -1916,12 +2061,32 @@ class MultiSelectComponent {
1916
2061
  }
1917
2062
  toggleValue(val) {
1918
2063
  const index = this.value.indexOf(val);
1919
- index > -1 ? this.value.splice(index, 1) : this.value.push(val);
2064
+ if (index > -1) {
2065
+ this.value.splice(index, 1);
2066
+ }
2067
+ else {
2068
+ if (this.maxSelections && this.value.length >= this.maxSelections)
2069
+ return;
2070
+ this.value.push(val);
2071
+ }
1920
2072
  this.syncItems();
1921
- this.onChange([...this.value]); // Return new array reference for Change Detection
2073
+ this.onChange([...this.value]);
1922
2074
  }
1923
2075
  selectAll() {
1924
- this.value = this.items.map(i => i.value);
2076
+ if (!this.items)
2077
+ return;
2078
+ let itemsToSelect = [];
2079
+ if (this.maxSelections) {
2080
+ const availableItems = this.items
2081
+ .filter(item => !item.disabled && !this.value.includes(item.value))
2082
+ .slice(0, this.availableSelections)
2083
+ .map(item => item.value);
2084
+ itemsToSelect = [...this.value, ...availableItems];
2085
+ }
2086
+ else {
2087
+ itemsToSelect = this.items.filter(item => !item.disabled).map(item => item.value);
2088
+ }
2089
+ this.value = itemsToSelect;
1925
2090
  this.syncItems();
1926
2091
  this.onChange([...this.value]);
1927
2092
  }
@@ -1940,8 +2105,15 @@ class MultiSelectComponent {
1940
2105
  this.selectedItems = [];
1941
2106
  this.items.forEach(item => {
1942
2107
  item.selected = this.value.includes(item.value);
1943
- if (item.selected)
2108
+ if (item.selected) {
1944
2109
  this.selectedItems.push({ label: item.getLabel(), value: item.value });
2110
+ }
2111
+ if (this.maxSelections && this.value.length >= this.maxSelections) {
2112
+ item.disabled = !this.value.includes(item.value);
2113
+ }
2114
+ else if (item.disabled) {
2115
+ item.disabled = false;
2116
+ }
1945
2117
  });
1946
2118
  }
1947
2119
  onSearchChange(q) {
@@ -1963,45 +2135,93 @@ class MultiSelectComponent {
1963
2135
  // ControlValueAccessor
1964
2136
  onChange = () => { };
1965
2137
  onTouched = () => { };
1966
- writeValue(v) { this.value = Array.isArray(v) ? v : []; this.syncItems(); }
2138
+ writeValue(v) {
2139
+ this.value = Array.isArray(v) ? v : [];
2140
+ this.syncItems();
2141
+ }
1967
2142
  registerOnChange(fn) { this.onChange = fn; }
1968
2143
  registerOnTouched(fn) { this.onTouched = fn; }
2144
+ setDisabledState(isDisabled) { this.disabled = isDisabled; }
1969
2145
  cn = cn;
1970
2146
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MultiSelectComponent, deps: [{ token: SelectService }], target: i0.ɵɵFactoryTarget.Component });
1971
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MultiSelectComponent, isStandalone: true, selector: "tolle-multi-select", inputs: { placeholder: "placeholder", size: "size", searchable: "searchable", disabled: "disabled", class: "class" }, host: { listeners: { "document:mousedown": "onClickOutside($event)" } }, providers: [
2147
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: MultiSelectComponent, isStandalone: true, selector: "tolle-multi-select", inputs: { placeholder: "placeholder", size: "size", searchable: "searchable", disabled: "disabled", class: "class", maxSelections: "maxSelections", maxDisplayItems: "maxDisplayItems", error: "error" }, host: { listeners: { "document:mousedown": "onClickOutside($event)" } }, providers: [
1972
2148
  SelectService,
1973
2149
  { provide: NG_VALUE_ACCESSOR, useExisting: MultiSelectComponent, multi: true }
1974
2150
  ], queries: [{ propertyName: "items", predicate: SelectItemComponent, descendants: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "popover", first: true, predicate: ["popover"], descendants: true }], ngImport: i0, template: `
1975
2151
  <div [class]="cn('relative w-full', 'size-' + size)" #container>
1976
- <button #trigger type="button" (click)="toggle()" [disabled]="disabled"
1977
- [class]="cn('flex min-h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm focus:ring-2 focus:ring-ring focus:ring-offset-2 transition-all h-auto', class)">
2152
+ <button
2153
+ #trigger
2154
+ type="button"
2155
+ (click)="toggle()"
2156
+ [disabled]="disabled"
2157
+ [class]="computedTriggerClass">
2158
+
1978
2159
  <div class="flex flex-wrap gap-1 items-center max-w-[95%]">
1979
2160
  <ng-container *ngIf="value?.length; else placeholderTpl">
1980
- <tolle-badge *ngFor="let item of selectedItems" size="xs" variant="secondary" [removable]="true" (onRemove)="removeValue($event, item.value)">
2161
+ <tolle-badge *ngFor="let item of displayItems" size="xs" variant="secondary" [removable]="true" (onRemove)="removeValue($event, item.value)">
1981
2162
  {{ item.label }}
1982
2163
  </tolle-badge>
2164
+ <span *ngIf="exceedsDisplayLimit" class="text-xs text-muted-foreground px-1">
2165
+ +{{ value.length - maxDisplayItems }} more
2166
+ </span>
2167
+ <span *ngIf="maxSelections && value.length >= maxSelections" class="text-xs text-muted-foreground px-1">
2168
+ (Max reached)
2169
+ </span>
1983
2170
  </ng-container>
1984
2171
  <ng-template #placeholderTpl><span class="text-muted-foreground">{{ placeholder }}</span></ng-template>
1985
2172
  </div>
1986
- <i [class]="cn('ri-arrow-down-s-line text-muted-foreground ml-2 transition-transform', isOpen ? 'rotate-180' : '')"></i>
2173
+ <i [class]="cn('ri-arrow-down-s-line text-muted-foreground ml-2 transition-transform duration-200', isOpen ? 'rotate-180' : '')"></i>
1987
2174
  </button>
1988
2175
 
1989
- <div #popover *ngIf="isOpen" class="absolute bg-popover z-50 min-w-full rounded-md border border-border shadow-md overflow-hidden" style="visibility: hidden;">
2176
+ <div #popover *ngIf="isOpen"
2177
+ class="fixed bg-popover z-[999] rounded-md border border-border shadow-md overflow-hidden"
2178
+ style="visibility: hidden; top: 0; left: 0;">
1990
2179
 
1991
2180
  <div class="p-2 border-b border-border space-y-2 bg-popover">
2181
+ <div class="flex items-center justify-between px-1 text-xs">
2182
+ <span class="text-muted-foreground">
2183
+ {{ value.length }} selected
2184
+ <span *ngIf="maxSelections">/ {{ maxSelections }} max</span>
2185
+ </span>
2186
+ <span *ngIf="maxSelections && value.length >= maxSelections" class="text-destructive text-xs font-medium">
2187
+ Maximum reached
2188
+ </span>
2189
+ </div>
2190
+
1992
2191
  <tolle-input *ngIf="searchable" size="xs" placeholder="Search..." [(ngModel)]="searchQuery" (ngModelChange)="onSearchChange($event)">
1993
2192
  <i prefix class="ri-search-line"></i>
1994
2193
  </tolle-input>
1995
2194
 
1996
2195
  <div class="flex items-center justify-between px-1">
1997
- <button type="button" (click)="selectAll()" class="text-[10px] font-bold uppercase text-primary hover:underline">Select All</button>
1998
- <button type="button" (click)="clearAll()" class="text-[10px] font-bold uppercase text-muted-foreground hover:underline">Clear</button>
2196
+ <button type="button"
2197
+ (click)="selectAll()"
2198
+ [disabled]="maxSelections && selectableItems.length > maxSelections"
2199
+ [class]="cn(
2200
+ 'text-[10px] font-bold uppercase transition-colors',
2201
+ maxSelections && selectableItems.length > maxSelections
2202
+ ? 'text-muted-foreground cursor-not-allowed'
2203
+ : 'text-primary hover:underline'
2204
+ )">
2205
+ Select All
2206
+ </button>
2207
+ <button type="button" (click)="clearAll()" class="text-[10px] font-bold uppercase text-muted-foreground hover:underline">
2208
+ Clear
2209
+ </button>
1999
2210
  </div>
2000
2211
  </div>
2001
2212
 
2002
2213
  <div class="p-1 max-h-60 overflow-y-auto">
2003
2214
  <ng-content></ng-content>
2004
- <div *ngIf="noResults" class="py-4 text-center text-xs text-muted-foreground">No results found for "{{searchQuery}}"</div>
2215
+ <div *ngIf="noResults" class="py-4 text-center text-xs text-muted-foreground">
2216
+ No results found for "{{searchQuery}}"
2217
+ </div>
2218
+ <div *ngIf="maxSelections && value.length >= maxSelections"
2219
+ class="p-2 text-center border-t border-border bg-muted/20">
2220
+ <span class="text-xs text-destructive">
2221
+ <i class="ri-alert-line mr-1"></i>
2222
+ Maximum selection limit reached ({{maxSelections}})
2223
+ </span>
2224
+ </div>
2005
2225
  </div>
2006
2226
  </div>
2007
2227
  </div>
@@ -2019,35 +2239,79 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2019
2239
  ],
2020
2240
  template: `
2021
2241
  <div [class]="cn('relative w-full', 'size-' + size)" #container>
2022
- <button #trigger type="button" (click)="toggle()" [disabled]="disabled"
2023
- [class]="cn('flex min-h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm focus:ring-2 focus:ring-ring focus:ring-offset-2 transition-all h-auto', class)">
2242
+ <button
2243
+ #trigger
2244
+ type="button"
2245
+ (click)="toggle()"
2246
+ [disabled]="disabled"
2247
+ [class]="computedTriggerClass">
2248
+
2024
2249
  <div class="flex flex-wrap gap-1 items-center max-w-[95%]">
2025
2250
  <ng-container *ngIf="value?.length; else placeholderTpl">
2026
- <tolle-badge *ngFor="let item of selectedItems" size="xs" variant="secondary" [removable]="true" (onRemove)="removeValue($event, item.value)">
2251
+ <tolle-badge *ngFor="let item of displayItems" size="xs" variant="secondary" [removable]="true" (onRemove)="removeValue($event, item.value)">
2027
2252
  {{ item.label }}
2028
2253
  </tolle-badge>
2254
+ <span *ngIf="exceedsDisplayLimit" class="text-xs text-muted-foreground px-1">
2255
+ +{{ value.length - maxDisplayItems }} more
2256
+ </span>
2257
+ <span *ngIf="maxSelections && value.length >= maxSelections" class="text-xs text-muted-foreground px-1">
2258
+ (Max reached)
2259
+ </span>
2029
2260
  </ng-container>
2030
2261
  <ng-template #placeholderTpl><span class="text-muted-foreground">{{ placeholder }}</span></ng-template>
2031
2262
  </div>
2032
- <i [class]="cn('ri-arrow-down-s-line text-muted-foreground ml-2 transition-transform', isOpen ? 'rotate-180' : '')"></i>
2263
+ <i [class]="cn('ri-arrow-down-s-line text-muted-foreground ml-2 transition-transform duration-200', isOpen ? 'rotate-180' : '')"></i>
2033
2264
  </button>
2034
2265
 
2035
- <div #popover *ngIf="isOpen" class="absolute bg-popover z-50 min-w-full rounded-md border border-border shadow-md overflow-hidden" style="visibility: hidden;">
2266
+ <div #popover *ngIf="isOpen"
2267
+ class="fixed bg-popover z-[999] rounded-md border border-border shadow-md overflow-hidden"
2268
+ style="visibility: hidden; top: 0; left: 0;">
2036
2269
 
2037
2270
  <div class="p-2 border-b border-border space-y-2 bg-popover">
2271
+ <div class="flex items-center justify-between px-1 text-xs">
2272
+ <span class="text-muted-foreground">
2273
+ {{ value.length }} selected
2274
+ <span *ngIf="maxSelections">/ {{ maxSelections }} max</span>
2275
+ </span>
2276
+ <span *ngIf="maxSelections && value.length >= maxSelections" class="text-destructive text-xs font-medium">
2277
+ Maximum reached
2278
+ </span>
2279
+ </div>
2280
+
2038
2281
  <tolle-input *ngIf="searchable" size="xs" placeholder="Search..." [(ngModel)]="searchQuery" (ngModelChange)="onSearchChange($event)">
2039
2282
  <i prefix class="ri-search-line"></i>
2040
2283
  </tolle-input>
2041
2284
 
2042
2285
  <div class="flex items-center justify-between px-1">
2043
- <button type="button" (click)="selectAll()" class="text-[10px] font-bold uppercase text-primary hover:underline">Select All</button>
2044
- <button type="button" (click)="clearAll()" class="text-[10px] font-bold uppercase text-muted-foreground hover:underline">Clear</button>
2286
+ <button type="button"
2287
+ (click)="selectAll()"
2288
+ [disabled]="maxSelections && selectableItems.length > maxSelections"
2289
+ [class]="cn(
2290
+ 'text-[10px] font-bold uppercase transition-colors',
2291
+ maxSelections && selectableItems.length > maxSelections
2292
+ ? 'text-muted-foreground cursor-not-allowed'
2293
+ : 'text-primary hover:underline'
2294
+ )">
2295
+ Select All
2296
+ </button>
2297
+ <button type="button" (click)="clearAll()" class="text-[10px] font-bold uppercase text-muted-foreground hover:underline">
2298
+ Clear
2299
+ </button>
2045
2300
  </div>
2046
2301
  </div>
2047
2302
 
2048
2303
  <div class="p-1 max-h-60 overflow-y-auto">
2049
2304
  <ng-content></ng-content>
2050
- <div *ngIf="noResults" class="py-4 text-center text-xs text-muted-foreground">No results found for "{{searchQuery}}"</div>
2305
+ <div *ngIf="noResults" class="py-4 text-center text-xs text-muted-foreground">
2306
+ No results found for "{{searchQuery}}"
2307
+ </div>
2308
+ <div *ngIf="maxSelections && value.length >= maxSelections"
2309
+ class="p-2 text-center border-t border-border bg-muted/20">
2310
+ <span class="text-xs text-destructive">
2311
+ <i class="ri-alert-line mr-1"></i>
2312
+ Maximum selection limit reached ({{maxSelections}})
2313
+ </span>
2314
+ </div>
2051
2315
  </div>
2052
2316
  </div>
2053
2317
  </div>
@@ -2063,6 +2327,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2063
2327
  type: Input
2064
2328
  }], class: [{
2065
2329
  type: Input
2330
+ }], maxSelections: [{
2331
+ type: Input
2332
+ }], maxDisplayItems: [{
2333
+ type: Input
2334
+ }], error: [{
2335
+ type: Input
2066
2336
  }], trigger: [{
2067
2337
  type: ViewChild,
2068
2338
  args: ['trigger']
@@ -2079,7 +2349,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2079
2349
 
2080
2350
  class CalendarComponent {
2081
2351
  class = '';
2352
+ mode = 'date';
2082
2353
  disablePastDates = false;
2354
+ showQuickActions = true;
2355
+ minDate;
2356
+ maxDate;
2357
+ formatMonthFn;
2358
+ formatYearFn;
2359
+ formatDateFn;
2360
+ dateSelect = new EventEmitter();
2083
2361
  currentView = 'date';
2084
2362
  viewDate = new Date();
2085
2363
  selectedDate = null;
@@ -2087,87 +2365,219 @@ class CalendarComponent {
2087
2365
  daysInMonth = [];
2088
2366
  months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
2089
2367
  years = [];
2368
+ yearRangeStart;
2090
2369
  navBtnClass = cn('h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 border border-input rounded-md flex items-center justify-center hover:bg-accent hover:text-accent-foreground transition-all');
2370
+ quickActionBtnClass = cn('px-3 py-1.5 text-sm rounded hover:bg-accent text-muted-foreground hover:text-foreground transition-colors');
2091
2371
  onTouched = () => { };
2092
2372
  onChange = () => { };
2093
2373
  cn = cn;
2374
+ constructor() {
2375
+ this.yearRangeStart = new Date().getFullYear() - 6;
2376
+ }
2094
2377
  ngOnInit() {
2378
+ // Initialize based on mode
2379
+ if (this.mode === 'month') {
2380
+ this.currentView = 'month';
2381
+ }
2382
+ else if (this.mode === 'year') {
2383
+ this.currentView = 'year';
2384
+ }
2095
2385
  this.generateDays();
2096
2386
  this.generateYears();
2097
2387
  }
2388
+ // Format helpers
2389
+ formatMonthYear(date, type) {
2390
+ if (type === 'month' && this.formatMonthFn) {
2391
+ return this.formatMonthFn(date);
2392
+ }
2393
+ if (type === 'year' && this.formatYearFn) {
2394
+ return this.formatYearFn(date);
2395
+ }
2396
+ return type === 'month' ? format(date, 'MMMM') : format(date, 'yyyy');
2397
+ }
2398
+ formatDate(date, type) {
2399
+ if (type === 'day' && this.formatDateFn) {
2400
+ return this.formatDateFn(date);
2401
+ }
2402
+ return format(date, type === 'day' ? 'd' : type === 'month' ? 'MMM' : 'yyyy');
2403
+ }
2098
2404
  generateDays() {
2405
+ if (this.mode !== 'date')
2406
+ return;
2099
2407
  const start = startOfWeek(startOfMonth(this.viewDate));
2100
2408
  const end = endOfWeek(endOfMonth(this.viewDate));
2101
2409
  this.daysInMonth = eachDayOfInterval({ start, end });
2102
2410
  }
2103
2411
  generateYears() {
2104
2412
  const currentYear = this.viewDate.getFullYear();
2105
- // Generates a 16-year window centered roughly on current view
2106
- this.years = Array.from({ length: 16 }, (_, i) => currentYear - 6 + i);
2413
+ if (this.mode === 'year') {
2414
+ // For year picker, show a 12-year grid
2415
+ this.years = Array.from({ length: 12 }, (_, i) => this.yearRangeStart + i);
2416
+ }
2417
+ else {
2418
+ // For date mode year selector, show 16 years centered on current
2419
+ this.years = Array.from({ length: 16 }, (_, i) => currentYear - 6 + i);
2420
+ }
2107
2421
  }
2108
2422
  setView(view) {
2109
2423
  this.currentView = view;
2110
- // If switching to year view, ensure the year grid is centered on current view year
2111
2424
  if (view === 'year') {
2112
2425
  this.generateYears();
2113
2426
  }
2114
2427
  }
2115
2428
  prev() {
2116
- if (this.currentView === 'date') {
2117
- this.viewDate = subMonths(this.viewDate, 1);
2118
- this.generateDays();
2429
+ if (this.mode === 'date') {
2430
+ if (this.currentView === 'date') {
2431
+ this.viewDate = subMonths(this.viewDate, 1);
2432
+ this.generateDays();
2433
+ }
2434
+ else if (this.currentView === 'year') {
2435
+ this.viewDate = subYears(this.viewDate, 16);
2436
+ this.generateYears();
2437
+ }
2438
+ else if (this.currentView === 'month') {
2439
+ this.viewDate = subYears(this.viewDate, 1);
2440
+ }
2119
2441
  }
2120
- else if (this.currentView === 'year') {
2121
- this.viewDate = subYears(this.viewDate, 16);
2442
+ else if (this.mode === 'month') {
2443
+ this.viewDate = subYears(this.viewDate, 1);
2444
+ }
2445
+ else if (this.mode === 'year') {
2446
+ this.yearRangeStart -= 12;
2122
2447
  this.generateYears();
2123
2448
  }
2124
- else if (this.currentView === 'month') {
2125
- this.viewDate = subYears(this.viewDate, 1);
2449
+ else if (this.mode === 'month-year') {
2450
+ if (this.currentView === 'month') {
2451
+ this.viewDate = subYears(this.viewDate, 1);
2452
+ }
2453
+ else {
2454
+ this.yearRangeStart -= 12;
2455
+ this.generateYears();
2456
+ }
2126
2457
  }
2127
2458
  }
2128
2459
  next() {
2129
- if (this.currentView === 'date') {
2130
- this.viewDate = addMonths(this.viewDate, 1);
2131
- this.generateDays();
2460
+ if (this.mode === 'date') {
2461
+ if (this.currentView === 'date') {
2462
+ this.viewDate = addMonths(this.viewDate, 1);
2463
+ this.generateDays();
2464
+ }
2465
+ else if (this.currentView === 'year') {
2466
+ this.viewDate = addYears(this.viewDate, 16);
2467
+ this.generateYears();
2468
+ }
2469
+ else if (this.currentView === 'month') {
2470
+ this.viewDate = addYears(this.viewDate, 1);
2471
+ }
2132
2472
  }
2133
- else if (this.currentView === 'year') {
2134
- this.viewDate = addYears(this.viewDate, 16);
2473
+ else if (this.mode === 'month') {
2474
+ this.viewDate = addYears(this.viewDate, 1);
2475
+ }
2476
+ else if (this.mode === 'year') {
2477
+ this.yearRangeStart += 12;
2135
2478
  this.generateYears();
2136
2479
  }
2137
- else if (this.currentView === 'month') {
2138
- this.viewDate = addYears(this.viewDate, 1);
2480
+ else if (this.mode === 'month-year') {
2481
+ if (this.currentView === 'month') {
2482
+ this.viewDate = addYears(this.viewDate, 1);
2483
+ }
2484
+ else {
2485
+ this.yearRangeStart += 12;
2486
+ this.generateYears();
2487
+ }
2139
2488
  }
2140
2489
  }
2490
+ prevYears() {
2491
+ this.yearRangeStart -= 12;
2492
+ this.generateYears();
2493
+ }
2494
+ nextYears() {
2495
+ this.yearRangeStart += 12;
2496
+ this.generateYears();
2497
+ }
2141
2498
  selectDate(date) {
2142
2499
  if (this.isDateDisabled(date))
2143
2500
  return;
2144
2501
  this.selectedDate = date;
2145
- if (!isSameMonth(date, this.viewDate)) {
2146
- this.viewDate = date;
2147
- this.generateDays();
2148
- }
2149
2502
  this.onChange(date);
2150
2503
  this.onTouched();
2504
+ this.dateSelect.emit(date);
2151
2505
  }
2152
2506
  selectMonth(monthIndex) {
2153
- this.viewDate = setMonth(this.viewDate, monthIndex);
2154
- this.currentView = 'date';
2155
- this.generateDays();
2507
+ if (this.mode === 'date') {
2508
+ this.viewDate = setMonth(this.viewDate, monthIndex);
2509
+ this.currentView = 'date';
2510
+ this.generateDays();
2511
+ }
2512
+ else if (this.mode === 'month') {
2513
+ this.viewDate = setMonth(this.viewDate, monthIndex);
2514
+ this.selectedDate = this.viewDate;
2515
+ this.onChange(this.viewDate);
2516
+ this.onTouched();
2517
+ this.dateSelect.emit(this.viewDate);
2518
+ }
2156
2519
  }
2157
2520
  selectYear(year) {
2158
- this.viewDate = setYear(this.viewDate, year);
2159
- this.currentView = 'date'; // Jump straight back to date view for efficiency
2160
- this.generateDays();
2521
+ if (this.mode === 'date') {
2522
+ this.viewDate = setYear(this.viewDate, year);
2523
+ this.currentView = 'date';
2524
+ this.generateDays();
2525
+ }
2526
+ else if (this.mode === 'year' || this.mode === 'month') {
2527
+ this.viewDate = setYear(this.viewDate, year);
2528
+ this.selectedDate = this.viewDate;
2529
+ this.onChange(this.viewDate);
2530
+ this.onTouched();
2531
+ this.dateSelect.emit(this.viewDate);
2532
+ }
2161
2533
  }
2162
2534
  getDayClass(date) {
2163
2535
  const isSelected = this.selectedDate && isSameDay(date, this.selectedDate);
2164
2536
  const isTodayDate = isToday(date);
2165
2537
  const isOutside = !isSameMonth(date, this.viewDate);
2166
2538
  const isDisabled = this.isDateDisabled(date);
2167
- return cn('h-9 w-9 p-0 font-normal text-sm rounded-md transition-all flex items-center justify-center', !isSelected && !isDisabled && 'hover:bg-accent hover:text-accent-foreground', isSelected && 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground', !isSelected && isTodayDate && 'bg-accent text-accent-foreground', (isOutside || isDisabled) && 'text-muted-foreground opacity-50', isDisabled && 'cursor-not-allowed');
2539
+ return cn('h-9 w-9 p-0 font-normal text-sm rounded-md transition-all flex items-center justify-center', !isSelected && !isDisabled && 'hover:bg-accent hover:text-accent-foreground', isSelected && 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground', !isSelected && isTodayDate && 'bg-accent text-accent-foreground', (isOutside || isDisabled) && 'text-muted-foreground opacity-50', isDisabled && 'cursor-not-allowed');
2540
+ }
2541
+ getMonthClass(monthIndex) {
2542
+ const isSelected = this.selectedDate &&
2543
+ this.selectedDate.getMonth() === monthIndex &&
2544
+ this.selectedDate.getFullYear() === this.viewDate.getFullYear();
2545
+ const isCurrent = new Date().getMonth() === monthIndex &&
2546
+ new Date().getFullYear() === this.viewDate.getFullYear();
2547
+ return cn('text-sm py-2.5 rounded-md transition-colors', isSelected ? 'bg-primary text-primary-foreground hover:bg-primary' :
2548
+ isCurrent ? 'border border-primary/30 text-primary' :
2549
+ 'hover:bg-accent hover:text-accent-foreground');
2550
+ }
2551
+ getYearClass(year) {
2552
+ const isSelected = this.selectedDate &&
2553
+ this.selectedDate.getFullYear() === year;
2554
+ const isCurrent = new Date().getFullYear() === year;
2555
+ return cn('text-sm py-2 rounded-md transition-colors', isSelected ? 'bg-primary text-primary-foreground hover:bg-primary' :
2556
+ isCurrent ? 'border border-primary/30 text-primary' :
2557
+ 'hover:bg-accent hover:text-accent-foreground');
2168
2558
  }
2169
2559
  isDateDisabled(date) {
2170
- return this.disablePastDates ? isBefore(date, startOfDay(new Date())) : false;
2560
+ if (this.disablePastDates && isBefore(date, startOfDay(new Date()))) {
2561
+ return true;
2562
+ }
2563
+ if (this.minDate && isBefore(date, this.minDate)) {
2564
+ return true;
2565
+ }
2566
+ return !!(this.maxDate && isBefore(this.maxDate, date));
2567
+ }
2568
+ isTodayDisabled() {
2569
+ return this.isDateDisabled(new Date());
2570
+ }
2571
+ selectToday() {
2572
+ if (!this.isTodayDisabled()) {
2573
+ this.selectDate(new Date());
2574
+ }
2575
+ }
2576
+ clear() {
2577
+ this.selectedDate = null;
2578
+ this.onChange(null);
2579
+ this.onTouched();
2580
+ this.dateSelect.emit(null);
2171
2581
  }
2172
2582
  // CVA Implementation
2173
2583
  writeValue(obj) {
@@ -2184,27 +2594,26 @@ class CalendarComponent {
2184
2594
  registerOnChange(fn) { this.onChange = fn; }
2185
2595
  registerOnTouched(fn) { this.onTouched = fn; }
2186
2596
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2187
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CalendarComponent, isStandalone: true, selector: "tolle-calendar", inputs: { class: "class", disablePastDates: "disablePastDates" }, providers: [
2597
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CalendarComponent, isStandalone: true, selector: "tolle-calendar", inputs: { class: "class", mode: "mode", disablePastDates: "disablePastDates", showQuickActions: "showQuickActions", minDate: "minDate", maxDate: "maxDate", formatMonthFn: "formatMonthFn", formatYearFn: "formatYearFn", formatDateFn: "formatDateFn" }, outputs: { dateSelect: "dateSelect" }, providers: [
2188
2598
  {
2189
2599
  provide: NG_VALUE_ACCESSOR,
2190
2600
  useExisting: forwardRef(() => CalendarComponent),
2191
2601
  multi: true
2192
2602
  }
2193
2603
  ], ngImport: i0, template: `
2194
- <div [class]="cn('p-3 border rounded-md bg-background text-popover-foreground shadow-sm inline-block w-fit', class)">
2195
-
2604
+ <div [class]="cn('p-3 border rounded-md bg-background text-popover-foreground shadow-sm inline-block min-w-fit', class)">
2605
+ <!-- Header with Navigation -->
2196
2606
  <div class="flex items-center justify-between pt-1 pb-4 gap-2">
2197
-
2607
+ <!-- View Selector -->
2198
2608
  <div class="flex items-center gap-1">
2199
- <button
2609
+ <button *ngIf="mode !== 'year'"
2200
2610
  type="button"
2201
2611
  (click)="setView('month')"
2202
2612
  [class]="cn(
2203
2613
  'text-sm font-semibold px-2 py-1 rounded transition-colors',
2204
2614
  currentView === 'month' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
2205
- )"
2206
- >
2207
- {{ viewDate | date: 'MMMM' }}
2615
+ )">
2616
+ {{ formatMonthYear(viewDate, 'month') }}
2208
2617
  </button>
2209
2618
 
2210
2619
  <button
@@ -2213,12 +2622,12 @@ class CalendarComponent {
2213
2622
  [class]="cn(
2214
2623
  'text-sm font-semibold px-2 py-1 rounded transition-colors',
2215
2624
  currentView === 'year' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
2216
- )"
2217
- >
2218
- {{ viewDate | date: 'yyyy' }}
2625
+ )">
2626
+ {{ formatMonthYear(viewDate, 'year') }}
2219
2627
  </button>
2220
2628
  </div>
2221
2629
 
2630
+ <!-- Navigation Buttons -->
2222
2631
  <div class="flex items-center space-x-1">
2223
2632
  <button type="button" (click)="prev()" [class]="navBtnClass">
2224
2633
  <i class="ri-arrow-left-s-line text-lg"></i>
@@ -2229,7 +2638,8 @@ class CalendarComponent {
2229
2638
  </div>
2230
2639
  </div>
2231
2640
 
2232
- <div *ngIf="currentView === 'date'" class="space-y-2 animate-in fade-in zoom-in-95 duration-200">
2641
+ <!-- DATE MODE -->
2642
+ <div *ngIf="currentView === 'date' && mode === 'date'" class="space-y-2 animate-in fade-in zoom-in-95 duration-200">
2233
2643
  <div class="grid grid-cols-7 gap-1 w-full">
2234
2644
  <span *ngFor="let day of weekDays" class="text-[0.8rem] text-muted-foreground font-normal text-center w-9">
2235
2645
  {{ day }}
@@ -2243,47 +2653,66 @@ class CalendarComponent {
2243
2653
  [disabled]="isDateDisabled(date)"
2244
2654
  [class]="getDayClass(date)"
2245
2655
  >
2246
- {{ date | date: 'd' }}
2656
+ {{ formatDate(date, 'day') }}
2247
2657
  </button>
2248
2658
  </div>
2249
2659
  </div>
2250
2660
 
2251
- <div *ngIf="currentView === 'month'" class="grid grid-cols-3 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
2661
+ <!-- MONTH SELECTOR (for date mode and month mode) -->
2662
+ <div *ngIf="(currentView === 'month')"
2663
+ class="grid grid-cols-3 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
2252
2664
  <button
2253
2665
  *ngFor="let month of months; let i = index"
2254
2666
  type="button"
2255
2667
  (click)="selectMonth(i)"
2256
- [class]="cn(
2257
- 'text-sm py-2.5 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors',
2258
- i === viewDate.getMonth() ? 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground' : ''
2259
- )"
2668
+ [class]="getMonthClass(i)"
2260
2669
  >
2261
2670
  {{ month }}
2262
2671
  </button>
2263
2672
  </div>
2264
2673
 
2265
- <div *ngIf="currentView === 'year'" class="grid grid-cols-4 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
2674
+ <!-- YEAR SELECTOR (for date mode and year mode) -->
2675
+ <div *ngIf="(currentView === 'year') "
2676
+ class="grid grid-cols-4 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
2266
2677
  <button
2267
2678
  *ngFor="let year of years"
2268
2679
  type="button"
2269
2680
  (click)="selectYear(year)"
2270
- [class]="cn(
2271
- 'text-sm py-2 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors',
2272
- year === viewDate.getFullYear() ? 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground' : ''
2273
- )"
2681
+ [class]="getYearClass(year)"
2274
2682
  >
2275
2683
  {{ year }}
2276
2684
  </button>
2277
2685
  </div>
2686
+
2687
+ <!-- Quick Actions -->
2688
+ <div *ngIf="showQuickActions" class="border-t pt-3 mt-3">
2689
+ <div class="flex items-center justify-between gap-2">
2690
+ <button
2691
+ type="button"
2692
+ (click)="selectToday()"
2693
+ [class]="quickActionBtnClass"
2694
+ [disabled]="isTodayDisabled()"
2695
+ >
2696
+ Today
2697
+ </button>
2698
+ <button
2699
+ type="button"
2700
+ (click)="clear()"
2701
+ [class]="quickActionBtnClass"
2702
+ >
2703
+ Clear
2704
+ </button>
2705
+ </div>
2706
+ </div>
2278
2707
  </div>
2279
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }] });
2708
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }] });
2280
2709
  }
2281
2710
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CalendarComponent, decorators: [{
2282
2711
  type: Component,
2283
2712
  args: [{
2284
2713
  selector: 'tolle-calendar',
2285
2714
  standalone: true,
2286
- imports: [CommonModule],
2715
+ imports: [CommonModule, FormsModule],
2287
2716
  providers: [
2288
2717
  {
2289
2718
  provide: NG_VALUE_ACCESSOR,
@@ -2292,20 +2721,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2292
2721
  }
2293
2722
  ],
2294
2723
  template: `
2295
- <div [class]="cn('p-3 border rounded-md bg-background text-popover-foreground shadow-sm inline-block w-fit', class)">
2296
-
2724
+ <div [class]="cn('p-3 border rounded-md bg-background text-popover-foreground shadow-sm inline-block min-w-fit', class)">
2725
+ <!-- Header with Navigation -->
2297
2726
  <div class="flex items-center justify-between pt-1 pb-4 gap-2">
2298
-
2727
+ <!-- View Selector -->
2299
2728
  <div class="flex items-center gap-1">
2300
- <button
2729
+ <button *ngIf="mode !== 'year'"
2301
2730
  type="button"
2302
2731
  (click)="setView('month')"
2303
2732
  [class]="cn(
2304
2733
  'text-sm font-semibold px-2 py-1 rounded transition-colors',
2305
2734
  currentView === 'month' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
2306
- )"
2307
- >
2308
- {{ viewDate | date: 'MMMM' }}
2735
+ )">
2736
+ {{ formatMonthYear(viewDate, 'month') }}
2309
2737
  </button>
2310
2738
 
2311
2739
  <button
@@ -2314,12 +2742,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2314
2742
  [class]="cn(
2315
2743
  'text-sm font-semibold px-2 py-1 rounded transition-colors',
2316
2744
  currentView === 'year' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
2317
- )"
2318
- >
2319
- {{ viewDate | date: 'yyyy' }}
2745
+ )">
2746
+ {{ formatMonthYear(viewDate, 'year') }}
2320
2747
  </button>
2321
2748
  </div>
2322
2749
 
2750
+ <!-- Navigation Buttons -->
2323
2751
  <div class="flex items-center space-x-1">
2324
2752
  <button type="button" (click)="prev()" [class]="navBtnClass">
2325
2753
  <i class="ri-arrow-left-s-line text-lg"></i>
@@ -2330,7 +2758,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2330
2758
  </div>
2331
2759
  </div>
2332
2760
 
2333
- <div *ngIf="currentView === 'date'" class="space-y-2 animate-in fade-in zoom-in-95 duration-200">
2761
+ <!-- DATE MODE -->
2762
+ <div *ngIf="currentView === 'date' && mode === 'date'" class="space-y-2 animate-in fade-in zoom-in-95 duration-200">
2334
2763
  <div class="grid grid-cols-7 gap-1 w-full">
2335
2764
  <span *ngFor="let day of weekDays" class="text-[0.8rem] text-muted-foreground font-normal text-center w-9">
2336
2765
  {{ day }}
@@ -2344,45 +2773,80 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2344
2773
  [disabled]="isDateDisabled(date)"
2345
2774
  [class]="getDayClass(date)"
2346
2775
  >
2347
- {{ date | date: 'd' }}
2776
+ {{ formatDate(date, 'day') }}
2348
2777
  </button>
2349
2778
  </div>
2350
2779
  </div>
2351
2780
 
2352
- <div *ngIf="currentView === 'month'" class="grid grid-cols-3 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
2781
+ <!-- MONTH SELECTOR (for date mode and month mode) -->
2782
+ <div *ngIf="(currentView === 'month')"
2783
+ class="grid grid-cols-3 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
2353
2784
  <button
2354
2785
  *ngFor="let month of months; let i = index"
2355
2786
  type="button"
2356
2787
  (click)="selectMonth(i)"
2357
- [class]="cn(
2358
- 'text-sm py-2.5 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors',
2359
- i === viewDate.getMonth() ? 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground' : ''
2360
- )"
2788
+ [class]="getMonthClass(i)"
2361
2789
  >
2362
2790
  {{ month }}
2363
2791
  </button>
2364
2792
  </div>
2365
2793
 
2366
- <div *ngIf="currentView === 'year'" class="grid grid-cols-4 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
2794
+ <!-- YEAR SELECTOR (for date mode and year mode) -->
2795
+ <div *ngIf="(currentView === 'year') "
2796
+ class="grid grid-cols-4 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
2367
2797
  <button
2368
2798
  *ngFor="let year of years"
2369
2799
  type="button"
2370
2800
  (click)="selectYear(year)"
2371
- [class]="cn(
2372
- 'text-sm py-2 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors',
2373
- year === viewDate.getFullYear() ? 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground' : ''
2374
- )"
2801
+ [class]="getYearClass(year)"
2375
2802
  >
2376
2803
  {{ year }}
2377
2804
  </button>
2378
2805
  </div>
2806
+
2807
+ <!-- Quick Actions -->
2808
+ <div *ngIf="showQuickActions" class="border-t pt-3 mt-3">
2809
+ <div class="flex items-center justify-between gap-2">
2810
+ <button
2811
+ type="button"
2812
+ (click)="selectToday()"
2813
+ [class]="quickActionBtnClass"
2814
+ [disabled]="isTodayDisabled()"
2815
+ >
2816
+ Today
2817
+ </button>
2818
+ <button
2819
+ type="button"
2820
+ (click)="clear()"
2821
+ [class]="quickActionBtnClass"
2822
+ >
2823
+ Clear
2824
+ </button>
2825
+ </div>
2826
+ </div>
2379
2827
  </div>
2380
2828
  `
2381
2829
  }]
2382
- }], propDecorators: { class: [{
2830
+ }], ctorParameters: () => [], propDecorators: { class: [{
2831
+ type: Input
2832
+ }], mode: [{
2383
2833
  type: Input
2384
2834
  }], disablePastDates: [{
2385
2835
  type: Input
2836
+ }], showQuickActions: [{
2837
+ type: Input
2838
+ }], minDate: [{
2839
+ type: Input
2840
+ }], maxDate: [{
2841
+ type: Input
2842
+ }], formatMonthFn: [{
2843
+ type: Input
2844
+ }], formatYearFn: [{
2845
+ type: Input
2846
+ }], formatDateFn: [{
2847
+ type: Input
2848
+ }], dateSelect: [{
2849
+ type: Output
2386
2850
  }] } });
2387
2851
 
2388
2852
  class MaskedInputComponent {
@@ -2728,6 +3192,15 @@ class DatePickerComponent {
2728
3192
  disabled = false;
2729
3193
  class = '';
2730
3194
  disablePastDates = false;
3195
+ showClear = true;
3196
+ showQuickActions = true;
3197
+ minDate;
3198
+ maxDate;
3199
+ mode = 'date';
3200
+ formatMonthFn;
3201
+ formatYearFn;
3202
+ // Format functions for display
3203
+ displayFormat;
2731
3204
  triggerContainer;
2732
3205
  popover;
2733
3206
  value = null;
@@ -2737,12 +3210,56 @@ class DatePickerComponent {
2737
3210
  constructor(cdr) {
2738
3211
  this.cdr = cdr;
2739
3212
  }
2740
- // --- Logic ---
3213
+ getMask() {
3214
+ switch (this.mode) {
3215
+ case 'date': return '00/00/0000';
3216
+ case 'month': return '00/0000';
3217
+ case 'year': return '0000';
3218
+ default: return '00/00/0000';
3219
+ }
3220
+ }
3221
+ getPlaceholder() {
3222
+ switch (this.mode) {
3223
+ case 'date': return 'MM/DD/YYYY';
3224
+ case 'month': return 'MM/YYYY';
3225
+ case 'year': return 'YYYY';
3226
+ default: return 'MM/DD/YYYY';
3227
+ }
3228
+ }
3229
+ getFormatString() {
3230
+ switch (this.mode) {
3231
+ case 'date': return 'MM/dd/yyyy';
3232
+ case 'month': return 'MM/yyyy';
3233
+ case 'year': return 'yyyy';
3234
+ default: return 'MM/dd/yyyy';
3235
+ }
3236
+ }
3237
+ formatDate(date) {
3238
+ if (this.displayFormat) {
3239
+ return this.displayFormat(date, this.mode);
3240
+ }
3241
+ switch (this.mode) {
3242
+ case 'date': return format(date, 'MM/dd/yyyy');
3243
+ case 'month': return format(date, 'MM/yyyy');
3244
+ case 'year': return format(date, 'yyyy');
3245
+ default: return format(date, 'MM/dd/yyyy');
3246
+ }
3247
+ }
3248
+ parseDate(str) {
3249
+ try {
3250
+ const parsed = parse(str, this.getFormatString(), new Date());
3251
+ return isValid(parsed) ? startOfDay(parsed) : null;
3252
+ }
3253
+ catch {
3254
+ return null;
3255
+ }
3256
+ }
2741
3257
  onInputChange(str) {
2742
- if (str?.length === 10) {
2743
- const parsed = parse(str, 'MM/dd/yyyy', new Date());
2744
- if (isValid(parsed)) {
2745
- this.value = startOfDay(parsed);
3258
+ const expectedLength = this.getFormatString().replace(/[^0]/g, '').length;
3259
+ if (str?.length === expectedLength) {
3260
+ const parsed = this.parseDate(str);
3261
+ if (parsed) {
3262
+ this.value = parsed;
2746
3263
  this.onChange(this.value);
2747
3264
  }
2748
3265
  }
@@ -2753,12 +3270,17 @@ class DatePickerComponent {
2753
3270
  }
2754
3271
  onCalendarChange(date) {
2755
3272
  this.value = date;
2756
- this.inputValue = format(date, 'MM/dd/yyyy');
3273
+ if (date) {
3274
+ this.inputValue = this.formatDate(date);
3275
+ }
3276
+ else {
3277
+ this.inputValue = '';
3278
+ }
2757
3279
  this.onChange(this.value);
2758
3280
  this.close();
2759
3281
  }
2760
3282
  togglePopover(event) {
2761
- event.stopPropagation(); // Prevent bubbling to document
3283
+ event.stopPropagation();
2762
3284
  if (this.disabled)
2763
3285
  return;
2764
3286
  this.isOpen ? this.close() : this.open();
@@ -2773,22 +3295,27 @@ class DatePickerComponent {
2773
3295
  this.cleanupAutoUpdate();
2774
3296
  }
2775
3297
  clear(event) {
2776
- event.stopPropagation(); // CRITICAL: Stop the calendar from opening
3298
+ event.stopPropagation();
2777
3299
  this.value = null;
2778
3300
  this.inputValue = '';
2779
3301
  this.onChange(null);
2780
3302
  this.cdr.markForCheck();
2781
3303
  }
2782
- // --- Positioning ---
2783
3304
  updatePosition() {
2784
3305
  if (!this.triggerContainer || !this.popover)
2785
3306
  return;
2786
3307
  this.cleanupAutoUpdate = autoUpdate(this.triggerContainer.nativeElement, this.popover.nativeElement, () => {
2787
3308
  computePosition(this.triggerContainer.nativeElement, this.popover.nativeElement, {
2788
- placement: 'bottom-end', // Aligned to the right where the icon is
2789
- middleware: [offset(4), flip(), shift({ padding: 8 })],
2790
- }).then(({ x, y }) => {
3309
+ strategy: 'fixed', // ADDED: Fixed strategy
3310
+ placement: 'bottom-start', // Changed to bottom-start to align with input left edge
3311
+ middleware: [
3312
+ offset(4),
3313
+ flip(),
3314
+ shift({ padding: 8 })
3315
+ ],
3316
+ }).then(({ x, y, strategy }) => {
2791
3317
  Object.assign(this.popover.nativeElement.style, {
3318
+ position: strategy,
2792
3319
  left: `${x}px`,
2793
3320
  top: `${y}px`,
2794
3321
  visibility: 'visible',
@@ -2803,7 +3330,7 @@ class DatePickerComponent {
2803
3330
  this.close();
2804
3331
  }
2805
3332
  }
2806
- // --- CVA ---
3333
+ // CVA Implementation
2807
3334
  onChange = () => { };
2808
3335
  onTouched = () => { };
2809
3336
  writeValue(val) {
@@ -2811,7 +3338,7 @@ class DatePickerComponent {
2811
3338
  const date = new Date(val);
2812
3339
  if (isValid(date)) {
2813
3340
  this.value = startOfDay(date);
2814
- this.inputValue = format(this.value, 'MM/dd/yyyy');
3341
+ this.inputValue = this.formatDate(this.value);
2815
3342
  }
2816
3343
  }
2817
3344
  else {
@@ -2825,7 +3352,7 @@ class DatePickerComponent {
2825
3352
  setDisabledState(isDisabled) { this.disabled = isDisabled; }
2826
3353
  cn = cn;
2827
3354
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatePickerComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
2828
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DatePickerComponent, isStandalone: true, selector: "tolle-date-picker", inputs: { placeholder: "placeholder", disabled: "disabled", class: "class", disablePastDates: "disablePastDates" }, host: { listeners: { "document:mousedown": "onClickOutside($event)" } }, providers: [
3355
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DatePickerComponent, isStandalone: true, selector: "tolle-date-picker", inputs: { placeholder: "placeholder", disabled: "disabled", class: "class", disablePastDates: "disablePastDates", showClear: "showClear", showQuickActions: "showQuickActions", minDate: "minDate", maxDate: "maxDate", mode: "mode", formatMonthFn: "formatMonthFn", formatYearFn: "formatYearFn", displayFormat: "displayFormat" }, host: { listeners: { "document:mousedown": "onClickOutside($event)" } }, providers: [
2829
3356
  {
2830
3357
  provide: NG_VALUE_ACCESSOR,
2831
3358
  useExisting: forwardRef(() => DatePickerComponent),
@@ -2835,8 +3362,8 @@ class DatePickerComponent {
2835
3362
  <div class="relative w-full" #triggerContainer>
2836
3363
  <tolle-masked-input
2837
3364
  #maskInput
2838
- [mask]="'00/00/0000'"
2839
- [placeholder]="placeholder"
3365
+ [mask]="getMask()"
3366
+ [placeholder]="getPlaceholder()"
2840
3367
  [disabled]="disabled"
2841
3368
  [(ngModel)]="inputValue"
2842
3369
  (ngModelChange)="onInputChange($event)"
@@ -2844,14 +3371,17 @@ class DatePickerComponent {
2844
3371
  >
2845
3372
  <div suffix class="flex items-center gap-1.5 cursor-pointer">
2846
3373
  <i
2847
- *ngIf="value && !disabled"
3374
+ *ngIf="value && !disabled && showClear"
2848
3375
  (click)="clear($event)"
2849
3376
  class="ri-close-line cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
2850
3377
  ></i>
2851
3378
 
2852
3379
  <i
2853
3380
  (click)="togglePopover($event)"
2854
- class="ri-calendar-line cursor-pointer text-muted-foreground hover:text-primary transition-colors"
3381
+ [class]="cn(
3382
+ 'cursor-pointer text-muted-foreground transition-colors',
3383
+ 'ri-calendar-line'
3384
+ )"
2855
3385
  ></i>
2856
3386
  </div>
2857
3387
  </tolle-masked-input>
@@ -2859,17 +3389,23 @@ class DatePickerComponent {
2859
3389
  <div
2860
3390
  #popover
2861
3391
  *ngIf="isOpen"
2862
- class="absolute z-50 max-w-max left-0 right-0 overflow-hidden rounded-md border border-border text-popover-foreground bg-background shadow-md"
3392
+ class="fixed z-[50]"
2863
3393
  style="visibility: hidden; top: 0; left: 0;"
2864
3394
  >
2865
- <tolle-calendar
3395
+ <tolle-calendar class="shadow-lg"
2866
3396
  [(ngModel)]="value"
2867
3397
  (ngModelChange)="onCalendarChange($event)"
3398
+ [mode]="mode"
2868
3399
  [disablePastDates]="disablePastDates"
3400
+ [minDate]="minDate"
3401
+ [maxDate]="maxDate"
3402
+ [showQuickActions]="showQuickActions"
3403
+ [formatMonthFn]="formatMonthFn"
3404
+ [formatYearFn]="formatYearFn"
2869
3405
  ></tolle-calendar>
2870
3406
  </div>
2871
3407
  </div>
2872
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MaskedInputComponent, selector: "tolle-masked-input", inputs: ["id", "label", "hint", "errorMessage", "mask", "placeholder", "type", "disabled", "readonly", "class", "containerClass", "error", "size", "returnRaw", "hideHintOnFocus"] }, { kind: "component", type: CalendarComponent, selector: "tolle-calendar", inputs: ["class", "disablePastDates"] }] });
3408
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MaskedInputComponent, selector: "tolle-masked-input", inputs: ["id", "label", "hint", "errorMessage", "mask", "placeholder", "type", "disabled", "readonly", "class", "containerClass", "error", "size", "returnRaw", "hideHintOnFocus"] }, { kind: "component", type: CalendarComponent, selector: "tolle-calendar", inputs: ["class", "mode", "disablePastDates", "showQuickActions", "minDate", "maxDate", "formatMonthFn", "formatYearFn", "formatDateFn"], outputs: ["dateSelect"] }] });
2873
3409
  }
2874
3410
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatePickerComponent, decorators: [{
2875
3411
  type: Component,
@@ -2888,8 +3424,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2888
3424
  <div class="relative w-full" #triggerContainer>
2889
3425
  <tolle-masked-input
2890
3426
  #maskInput
2891
- [mask]="'00/00/0000'"
2892
- [placeholder]="placeholder"
3427
+ [mask]="getMask()"
3428
+ [placeholder]="getPlaceholder()"
2893
3429
  [disabled]="disabled"
2894
3430
  [(ngModel)]="inputValue"
2895
3431
  (ngModelChange)="onInputChange($event)"
@@ -2897,14 +3433,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2897
3433
  >
2898
3434
  <div suffix class="flex items-center gap-1.5 cursor-pointer">
2899
3435
  <i
2900
- *ngIf="value && !disabled"
3436
+ *ngIf="value && !disabled && showClear"
2901
3437
  (click)="clear($event)"
2902
3438
  class="ri-close-line cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
2903
3439
  ></i>
2904
3440
 
2905
3441
  <i
2906
3442
  (click)="togglePopover($event)"
2907
- class="ri-calendar-line cursor-pointer text-muted-foreground hover:text-primary transition-colors"
3443
+ [class]="cn(
3444
+ 'cursor-pointer text-muted-foreground transition-colors',
3445
+ 'ri-calendar-line'
3446
+ )"
2908
3447
  ></i>
2909
3448
  </div>
2910
3449
  </tolle-masked-input>
@@ -2912,13 +3451,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2912
3451
  <div
2913
3452
  #popover
2914
3453
  *ngIf="isOpen"
2915
- class="absolute z-50 max-w-max left-0 right-0 overflow-hidden rounded-md border border-border text-popover-foreground bg-background shadow-md"
3454
+ class="fixed z-[50]"
2916
3455
  style="visibility: hidden; top: 0; left: 0;"
2917
3456
  >
2918
- <tolle-calendar
3457
+ <tolle-calendar class="shadow-lg"
2919
3458
  [(ngModel)]="value"
2920
3459
  (ngModelChange)="onCalendarChange($event)"
3460
+ [mode]="mode"
2921
3461
  [disablePastDates]="disablePastDates"
3462
+ [minDate]="minDate"
3463
+ [maxDate]="maxDate"
3464
+ [showQuickActions]="showQuickActions"
3465
+ [formatMonthFn]="formatMonthFn"
3466
+ [formatYearFn]="formatYearFn"
2922
3467
  ></tolle-calendar>
2923
3468
  </div>
2924
3469
  </div>
@@ -2932,6 +3477,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2932
3477
  type: Input
2933
3478
  }], disablePastDates: [{
2934
3479
  type: Input
3480
+ }], showClear: [{
3481
+ type: Input
3482
+ }], showQuickActions: [{
3483
+ type: Input
3484
+ }], minDate: [{
3485
+ type: Input
3486
+ }], maxDate: [{
3487
+ type: Input
3488
+ }], mode: [{
3489
+ type: Input
3490
+ }], formatMonthFn: [{
3491
+ type: Input
3492
+ }], formatYearFn: [{
3493
+ type: Input
3494
+ }], displayFormat: [{
3495
+ type: Input
2935
3496
  }], triggerContainer: [{
2936
3497
  type: ViewChild,
2937
3498
  args: ['triggerContainer']
@@ -3113,7 +3674,7 @@ class PaginationComponent {
3113
3674
  </div>
3114
3675
  </div>
3115
3676
  </div>
3116
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SelectComponent, selector: "tolle-select", inputs: ["placeholder", "class", "disabled", "searchable", "size", "readonly"] }, { kind: "component", type: SelectItemComponent, selector: "tolle-select-item", inputs: ["value", "class", "selected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3677
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SelectComponent, selector: "tolle-select", inputs: ["placeholder", "class", "disabled", "searchable", "size", "readonly"] }, { kind: "component", type: SelectItemComponent, selector: "tolle-select-item", inputs: ["value", "class", "selected", "disabled", "multiSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3117
3678
  }
3118
3679
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PaginationComponent, decorators: [{
3119
3680
  type: Component,
@@ -4248,7 +4809,6 @@ class DateRangePickerComponent {
4248
4809
  placeholder = 'Pick a date range';
4249
4810
  class = '';
4250
4811
  disablePastDates = false;
4251
- // Standardized Sizes
4252
4812
  size = 'default';
4253
4813
  triggerContainer;
4254
4814
  popover;
@@ -4261,7 +4821,7 @@ class DateRangePickerComponent {
4261
4821
  get displayValue() {
4262
4822
  if (!this.value.start)
4263
4823
  return '';
4264
- const startStr = format(this.value.start, 'MMM dd, yyyy'); // Using date-fns format
4824
+ const startStr = format(this.value.start, 'MMM dd, yyyy');
4265
4825
  if (!this.value.end)
4266
4826
  return startStr;
4267
4827
  const endStr = format(this.value.end, 'MMM dd, yyyy');
@@ -4272,12 +4832,11 @@ class DateRangePickerComponent {
4272
4832
  this.onChange(this.value);
4273
4833
  // Close only if range is complete
4274
4834
  if (range.start && range.end) {
4275
- this.onChange(this.value);
4276
- // Small delay for UX
4277
4835
  setTimeout(() => this.close(), 150);
4278
4836
  }
4279
4837
  }
4280
- togglePopover(_) {
4838
+ togglePopover(event) {
4839
+ event.stopPropagation();
4281
4840
  if (this.disabled)
4282
4841
  return;
4283
4842
  this.isOpen ? this.close() : this.open();
@@ -4288,39 +4847,75 @@ class DateRangePickerComponent {
4288
4847
  }
4289
4848
  close() {
4290
4849
  this.isOpen = false;
4291
- if (this.cleanupAutoUpdate)
4850
+ if (this.cleanupAutoUpdate) {
4292
4851
  this.cleanupAutoUpdate();
4852
+ this.cleanupAutoUpdate = undefined;
4853
+ }
4293
4854
  }
4294
4855
  clear(event) {
4295
- event.stopPropagation(); // Stop button click
4856
+ event.stopPropagation();
4296
4857
  this.value = { start: null, end: null };
4297
4858
  this.onChange(this.value);
4859
+ this.cdr.markForCheck();
4298
4860
  }
4299
- // --- Floating UI Positioning ---
4861
+ // --- Floating UI Positioning with Fixed Strategy ---
4300
4862
  updatePosition() {
4301
4863
  if (!this.triggerContainer || !this.popover)
4302
4864
  return;
4303
4865
  this.cleanupAutoUpdate = autoUpdate(this.triggerContainer.nativeElement, this.popover.nativeElement, () => {
4304
4866
  computePosition(this.triggerContainer.nativeElement, this.popover.nativeElement, {
4305
- placement: 'bottom-start', // Aligned to the right where the icon is
4306
- middleware: [offset(4), flip(), shift({ padding: 8 })],
4307
- }).then(({ x, y }) => {
4867
+ placement: 'bottom-end',
4868
+ strategy: 'fixed', // Use fixed to escape column layout
4869
+ middleware: [
4870
+ offset(4),
4871
+ flip({
4872
+ fallbackAxisSideDirection: 'start',
4873
+ padding: 8
4874
+ }),
4875
+ shift({ padding: 8 }),
4876
+ size({
4877
+ apply({ rects, elements, availableHeight }) {
4878
+ // Constrain popover to available space
4879
+ Object.assign(elements.floating.style, {
4880
+ maxHeight: `${Math.min(400, availableHeight)}px`,
4881
+ minWidth: `${Math.max(rects.reference.width, 320)}px`, // Calendar minimum width
4882
+ });
4883
+ }
4884
+ })
4885
+ ],
4886
+ }).then(({ x, y, placement }) => {
4308
4887
  Object.assign(this.popover.nativeElement.style, {
4309
4888
  left: `${x}px`,
4310
4889
  top: `${y}px`,
4311
4890
  visibility: 'visible',
4312
4891
  });
4892
+ // Optional: Add placement class for styling
4893
+ this.popover.nativeElement.classList.remove('calendar-top', 'calendar-bottom');
4894
+ if (placement.includes('top')) {
4895
+ this.popover.nativeElement.classList.add('calendar-top');
4896
+ }
4897
+ else {
4898
+ this.popover.nativeElement.classList.add('calendar-bottom');
4899
+ }
4313
4900
  });
4314
4901
  });
4315
4902
  }
4316
4903
  onClickOutside(event) {
4317
4904
  if (this.isOpen &&
4318
4905
  !this.triggerContainer.nativeElement.contains(event.target) &&
4319
- !this.popover.nativeElement.contains(event.target)) {
4906
+ !this.popover?.nativeElement.contains(event.target)) {
4320
4907
  this.close();
4321
4908
  }
4322
4909
  }
4323
- // CVA
4910
+ onWindowResize() {
4911
+ if (this.isOpen) {
4912
+ this.close(); // Close on resize for simplicity
4913
+ }
4914
+ }
4915
+ onWindowScroll() {
4916
+ // Floating-UI's autoUpdate handles scroll repositioning
4917
+ }
4918
+ // --- Control Value Accessor ---
4324
4919
  onChange = () => { };
4325
4920
  onTouched = () => { };
4326
4921
  writeValue(val) {
@@ -4337,7 +4932,7 @@ class DateRangePickerComponent {
4337
4932
  setDisabledState(isDisabled) { this.disabled = isDisabled; }
4338
4933
  cn = cn;
4339
4934
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DateRangePickerComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
4340
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DateRangePickerComponent, isStandalone: true, selector: "tolle-date-range-picker", inputs: { disabled: "disabled", placeholder: "placeholder", class: "class", disablePastDates: "disablePastDates", size: "size" }, host: { listeners: { "document:mousedown": "onClickOutside($event)" } }, providers: [
4935
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DateRangePickerComponent, isStandalone: true, selector: "tolle-date-range-picker", inputs: { disabled: "disabled", placeholder: "placeholder", class: "class", disablePastDates: "disablePastDates", size: "size" }, host: { listeners: { "document:mousedown": "onClickOutside($event)", "window:resize": "onWindowResize()", "window:scroll": "onWindowScroll()" } }, providers: [
4341
4936
  {
4342
4937
  provide: NG_VALUE_ACCESSOR,
4343
4938
  useExisting: forwardRef(() => DateRangePickerComponent),
@@ -4345,7 +4940,12 @@ class DateRangePickerComponent {
4345
4940
  }
4346
4941
  ], viewQueries: [{ propertyName: "triggerContainer", first: true, predicate: ["triggerContainer"], descendants: true }, { propertyName: "popover", first: true, predicate: ["popover"], descendants: true }], ngImport: i0, template: `
4347
4942
  <div class="relative w-full" #triggerContainer>
4348
- <tolle-input [placeholder]="placeholder" [disabled]="disabled" [ngModel]="displayValue">
4943
+ <tolle-input
4944
+ [placeholder]="placeholder"
4945
+ [disabled]="disabled"
4946
+ [ngModel]="displayValue"
4947
+ [class]="class"
4948
+ >
4349
4949
  <div suffix class="flex items-center gap-1.5 cursor-pointer">
4350
4950
  <i
4351
4951
  *ngIf="(value.start || value.end) && !disabled"
@@ -4359,13 +4959,14 @@ class DateRangePickerComponent {
4359
4959
  ></i>
4360
4960
  </div>
4361
4961
  </tolle-input>
4962
+
4362
4963
  <div
4363
4964
  #popover
4364
4965
  *ngIf="isOpen"
4365
- class="absolute z-50 min-w-72"
4366
- style="visibility: hidden; top: 0; left: 0;"
4966
+ class="fixed z-50"
4967
+ style="visibility: hidden;"
4367
4968
  >
4368
- <tolle-range-calendar
4969
+ <tolle-range-calendar class="shadow-lg"
4369
4970
  [ngModel]="value"
4370
4971
  (rangeSelect)="onCalendarSelect($event)"
4371
4972
  [disablePastDates]="disablePastDates"
@@ -4389,7 +4990,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4389
4990
  ],
4390
4991
  template: `
4391
4992
  <div class="relative w-full" #triggerContainer>
4392
- <tolle-input [placeholder]="placeholder" [disabled]="disabled" [ngModel]="displayValue">
4993
+ <tolle-input
4994
+ [placeholder]="placeholder"
4995
+ [disabled]="disabled"
4996
+ [ngModel]="displayValue"
4997
+ [class]="class"
4998
+ >
4393
4999
  <div suffix class="flex items-center gap-1.5 cursor-pointer">
4394
5000
  <i
4395
5001
  *ngIf="(value.start || value.end) && !disabled"
@@ -4403,13 +5009,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4403
5009
  ></i>
4404
5010
  </div>
4405
5011
  </tolle-input>
5012
+
4406
5013
  <div
4407
5014
  #popover
4408
5015
  *ngIf="isOpen"
4409
- class="absolute z-50 min-w-72"
4410
- style="visibility: hidden; top: 0; left: 0;"
5016
+ class="fixed z-50"
5017
+ style="visibility: hidden;"
4411
5018
  >
4412
- <tolle-range-calendar
5019
+ <tolle-range-calendar class="shadow-lg"
4413
5020
  [ngModel]="value"
4414
5021
  (rangeSelect)="onCalendarSelect($event)"
4415
5022
  [disablePastDates]="disablePastDates"
@@ -4437,6 +5044,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
4437
5044
  }], onClickOutside: [{
4438
5045
  type: HostListener,
4439
5046
  args: ['document:mousedown', ['$event']]
5047
+ }], onWindowResize: [{
5048
+ type: HostListener,
5049
+ args: ['window:resize']
5050
+ }], onWindowScroll: [{
5051
+ type: HostListener,
5052
+ args: ['window:scroll']
4440
5053
  }] } });
4441
5054
 
4442
5055
  class DropdownItemComponent {
@@ -5831,6 +6444,192 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5831
6444
  type: Input
5832
6445
  }] } });
5833
6446
 
6447
+ class SegmentedComponent {
6448
+ cdr;
6449
+ items = [];
6450
+ class = '';
6451
+ disabled = false;
6452
+ itemTemplate; // Allow custom content
6453
+ value = null;
6454
+ gliderLeft = 0;
6455
+ gliderWidth = 0;
6456
+ hasValue = false;
6457
+ itemElements;
6458
+ onChange = () => { };
6459
+ onTouched = () => { };
6460
+ constructor(cdr) {
6461
+ this.cdr = cdr;
6462
+ }
6463
+ ngAfterViewInit() {
6464
+ setTimeout(() => this.updateGlider());
6465
+ }
6466
+ ngOnChanges(changes) {
6467
+ // Recalculate if items change or if value changes externally
6468
+ if (changes['items'] && !changes['items'].firstChange) {
6469
+ setTimeout(() => this.updateGlider());
6470
+ }
6471
+ }
6472
+ select(val) {
6473
+ if (this.disabled)
6474
+ return;
6475
+ const item = this.items.find(i => i.value === val);
6476
+ if (item?.disabled)
6477
+ return;
6478
+ this.value = val;
6479
+ this.onChange(val);
6480
+ this.onTouched();
6481
+ this.updateGlider();
6482
+ }
6483
+ updateGlider() {
6484
+ if (!this.itemElements || !this.items.length)
6485
+ return;
6486
+ const index = this.items.findIndex(i => i.value === this.value);
6487
+ if (index === -1) {
6488
+ this.hasValue = false;
6489
+ this.gliderWidth = 0;
6490
+ return;
6491
+ }
6492
+ const activeElement = this.itemElements.get(index)?.nativeElement;
6493
+ if (activeElement) {
6494
+ this.hasValue = true;
6495
+ this.gliderLeft = activeElement.offsetLeft;
6496
+ this.gliderWidth = activeElement.offsetWidth;
6497
+ this.cdr.detectChanges();
6498
+ }
6499
+ }
6500
+ // CVA Implementation
6501
+ writeValue(val) {
6502
+ this.value = val;
6503
+ setTimeout(() => this.updateGlider());
6504
+ }
6505
+ registerOnChange(fn) { this.onChange = fn; }
6506
+ registerOnTouched(fn) { this.onTouched = fn; }
6507
+ setDisabledState(isDisabled) {
6508
+ this.disabled = isDisabled;
6509
+ this.cdr.markForCheck();
6510
+ }
6511
+ cn = cn;
6512
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SegmentedComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
6513
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: SegmentedComponent, isStandalone: true, selector: "tolle-segment", inputs: { items: "items", class: "class", disabled: "disabled", itemTemplate: "itemTemplate" }, providers: [
6514
+ {
6515
+ provide: NG_VALUE_ACCESSOR,
6516
+ useExisting: forwardRef(() => SegmentedComponent),
6517
+ multi: true
6518
+ }
6519
+ ], viewQueries: [{ propertyName: "itemElements", predicate: ["itemEls"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
6520
+ <div
6521
+ #container
6522
+ [class]="cn(
6523
+ 'relative flex items-center p-1 bg-muted rounded-lg select-none w-full gap-1',
6524
+ class
6525
+ )"
6526
+ role="tablist"
6527
+ >
6528
+ <div
6529
+ class="absolute top-1 bottom-1 bg-background shadow-sm rounded-md transition-all duration-300 ease-[cubic-bezier(0.2,0.0,0.2,1)]"
6530
+ [style.left.px]="gliderLeft"
6531
+ [style.width.px]="gliderWidth"
6532
+ [class.opacity-0]="!hasValue"
6533
+ ></div>
6534
+
6535
+ <button
6536
+ *ngFor="let item of items"
6537
+ #itemEls
6538
+ type="button"
6539
+ role="tab"
6540
+ [disabled]="item.disabled || disabled"
6541
+ [attr.aria-selected]="value === item.value"
6542
+ (click)="select(item.value)"
6543
+ [class]="cn(
6544
+ 'relative z-10 flex-1 px-3 py-1.5 text-sm font-medium transition-colors duration-200 rounded-md text-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
6545
+ 'flex items-center justify-center gap-2',
6546
+ value === item.value
6547
+ ? 'text-foreground'
6548
+ : 'text-muted-foreground hover:text-foreground/70',
6549
+ item.disabled && 'opacity-50 cursor-not-allowed',
6550
+ item.class
6551
+ )"
6552
+ >
6553
+ <ng-container *ngIf="itemTemplate; else defaultContent">
6554
+ <ng-container *ngTemplateOutlet="itemTemplate; context: { $implicit: item, selected: value === item.value }">
6555
+ </ng-container>
6556
+ </ng-container>
6557
+
6558
+ <ng-template #defaultContent>
6559
+ <i *ngIf="item.icon" [class]="item.icon"></i>
6560
+ <span class="truncate">{{ item.label }}</span>
6561
+ </ng-template>
6562
+ </button>
6563
+ </div>
6564
+ `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }] });
6565
+ }
6566
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SegmentedComponent, decorators: [{
6567
+ type: Component,
6568
+ args: [{ selector: 'tolle-segment', standalone: true, imports: [CommonModule, FormsModule], providers: [
6569
+ {
6570
+ provide: NG_VALUE_ACCESSOR,
6571
+ useExisting: forwardRef(() => SegmentedComponent),
6572
+ multi: true
6573
+ }
6574
+ ], template: `
6575
+ <div
6576
+ #container
6577
+ [class]="cn(
6578
+ 'relative flex items-center p-1 bg-muted rounded-lg select-none w-full gap-1',
6579
+ class
6580
+ )"
6581
+ role="tablist"
6582
+ >
6583
+ <div
6584
+ class="absolute top-1 bottom-1 bg-background shadow-sm rounded-md transition-all duration-300 ease-[cubic-bezier(0.2,0.0,0.2,1)]"
6585
+ [style.left.px]="gliderLeft"
6586
+ [style.width.px]="gliderWidth"
6587
+ [class.opacity-0]="!hasValue"
6588
+ ></div>
6589
+
6590
+ <button
6591
+ *ngFor="let item of items"
6592
+ #itemEls
6593
+ type="button"
6594
+ role="tab"
6595
+ [disabled]="item.disabled || disabled"
6596
+ [attr.aria-selected]="value === item.value"
6597
+ (click)="select(item.value)"
6598
+ [class]="cn(
6599
+ 'relative z-10 flex-1 px-3 py-1.5 text-sm font-medium transition-colors duration-200 rounded-md text-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
6600
+ 'flex items-center justify-center gap-2',
6601
+ value === item.value
6602
+ ? 'text-foreground'
6603
+ : 'text-muted-foreground hover:text-foreground/70',
6604
+ item.disabled && 'opacity-50 cursor-not-allowed',
6605
+ item.class
6606
+ )"
6607
+ >
6608
+ <ng-container *ngIf="itemTemplate; else defaultContent">
6609
+ <ng-container *ngTemplateOutlet="itemTemplate; context: { $implicit: item, selected: value === item.value }">
6610
+ </ng-container>
6611
+ </ng-container>
6612
+
6613
+ <ng-template #defaultContent>
6614
+ <i *ngIf="item.icon" [class]="item.icon"></i>
6615
+ <span class="truncate">{{ item.label }}</span>
6616
+ </ng-template>
6617
+ </button>
6618
+ </div>
6619
+ `, styles: [":host{display:block}\n"] }]
6620
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { items: [{
6621
+ type: Input
6622
+ }], class: [{
6623
+ type: Input
6624
+ }], disabled: [{
6625
+ type: Input
6626
+ }], itemTemplate: [{
6627
+ type: Input
6628
+ }], itemElements: [{
6629
+ type: ViewChildren,
6630
+ args: ['itemEls']
6631
+ }] } });
6632
+
5834
6633
  /*
5835
6634
  * Public API Surface of tolle
5836
6635
  */
@@ -5839,5 +6638,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5839
6638
  * Generated bundle index. Do not edit.
5840
6639
  */
5841
6640
 
5842
- export { AccordionComponent, AccordionItemComponent, AlertComponent, AvatarComponent, AvatarFallbackComponent, BadgeComponent, BreadcrumbComponent, BreadcrumbItemComponent, BreadcrumbLinkComponent, BreadcrumbSeparatorComponent, ButtonComponent, ButtonGroupComponent, CalendarComponent, CardComponent, CardContentComponent, CardFooterComponent, CardHeaderComponent, CardTitleComponent, CheckboxComponent, DataTableComponent, DatePickerComponent, DateRangePickerComponent, DropdownItemComponent, DropdownLabelComponent, DropdownMenuComponent, DropdownSeparatorComponent, DropdownTriggerDirective, EmptyStateComponent, InputComponent, MaskedInputComponent, Modal, ModalComponent, ModalRef, ModalService, ModalStackService, MultiSelectComponent, OtpComponent, OtpGroupComponent, OtpSlotComponent, PaginationComponent, PopoverComponent, PopoverContentComponent, RadioGroupComponent, RadioItemComponent, RangeCalendarComponent, SelectComponent, SelectGroupComponent, SelectItemComponent, SelectSeparatorComponent, SkeletonComponent, SwitchComponent, TOLLE_CONFIG, TextareaComponent, ThemeService, ToastContainerComponent, ToastService, TolleCellDirective, TooltipDirective, cn, provideTolleConfig };
6641
+ export { AccordionComponent, AccordionItemComponent, AlertComponent, AvatarComponent, AvatarFallbackComponent, BadgeComponent, BreadcrumbComponent, BreadcrumbItemComponent, BreadcrumbLinkComponent, BreadcrumbSeparatorComponent, ButtonComponent, ButtonGroupComponent, CalendarComponent, CardComponent, CardContentComponent, CardFooterComponent, CardHeaderComponent, CardTitleComponent, CheckboxComponent, DataTableComponent, DatePickerComponent, DateRangePickerComponent, DropdownItemComponent, DropdownLabelComponent, DropdownMenuComponent, DropdownSeparatorComponent, DropdownTriggerDirective, EmptyStateComponent, InputComponent, MaskedInputComponent, Modal, ModalComponent, ModalRef, ModalService, ModalStackService, MultiSelectComponent, OtpComponent, OtpGroupComponent, OtpSlotComponent, PaginationComponent, PopoverComponent, PopoverContentComponent, RadioGroupComponent, RadioItemComponent, RangeCalendarComponent, SegmentedComponent, SelectComponent, SelectGroupComponent, SelectItemComponent, SelectSeparatorComponent, SkeletonComponent, SwitchComponent, TOLLE_CONFIG, TextareaComponent, ThemeService, ToastContainerComponent, ToastService, TolleCellDirective, TooltipDirective, cn, provideTolleConfig };
5843
6642
  //# sourceMappingURL=tolle-ui.mjs.map