@tolle_/tolle-ui 0.0.27-beta → 0.0.29-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.
- package/esm2022/lib/calendar.component.mjs +278 -81
- package/esm2022/lib/card.component.mjs +3 -3
- package/esm2022/lib/data-table.component.mjs +3 -3
- package/esm2022/lib/date-picker.component.mjs +126 -29
- package/esm2022/lib/date-range-picker.component.mjs +76 -24
- package/esm2022/lib/multi-select.component.mjs +220 -26
- package/esm2022/lib/pagination.component.mjs +1 -1
- package/esm2022/lib/select-item.component.mjs +98 -14
- package/esm2022/lib/select.component.mjs +29 -36
- package/fesm2022/tolle-ui.mjs +821 -205
- package/fesm2022/tolle-ui.mjs.map +1 -1
- package/lib/button.component.d.ts +1 -1
- package/lib/calendar.component.d.ts +23 -2
- package/lib/date-picker.component.d.ts +16 -2
- package/lib/date-range-picker.component.d.ts +3 -1
- package/lib/multi-select.component.d.ts +13 -1
- package/lib/select-item.component.d.ts +4 -1
- package/package.json +1 -1
- package/theme.css +1 -1
package/fesm2022/tolle-ui.mjs
CHANGED
|
@@ -7,9 +7,9 @@ import { CommonModule, isPlatformBrowser, DOCUMENT, NgIf, NgTemplateOutlet } fro
|
|
|
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,
|
|
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';
|
|
@@ -403,7 +403,7 @@ class CardComponent {
|
|
|
403
403
|
cn = cn;
|
|
404
404
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
405
405
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: CardComponent, isStandalone: true, selector: "tolle-card", inputs: { class: "class" }, ngImport: i0, template: `
|
|
406
|
-
<div [class]="cn('rounded-md border border-border
|
|
406
|
+
<div [class]="cn('rounded-md border border-border text-card-foreground shadow', class)">
|
|
407
407
|
<ng-content></ng-content>
|
|
408
408
|
</div>
|
|
409
409
|
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
@@ -415,7 +415,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
415
415
|
standalone: true,
|
|
416
416
|
imports: [CommonModule],
|
|
417
417
|
template: `
|
|
418
|
-
<div [class]="cn('rounded-md border border-border
|
|
418
|
+
<div [class]="cn('rounded-md border border-border text-card-foreground shadow', class)">
|
|
419
419
|
<ng-content></ng-content>
|
|
420
420
|
</div>
|
|
421
421
|
`,
|
|
@@ -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.
|
|
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
|
-
|
|
530
|
-
|
|
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
|
|
541
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
563
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: [
|
|
697
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
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: [
|
|
1908
|
-
|
|
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
|
|
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]);
|
|
2073
|
+
this.onChange([...this.value]);
|
|
1922
2074
|
}
|
|
1923
2075
|
selectAll() {
|
|
1924
|
-
|
|
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) {
|
|
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
|
|
1977
|
-
|
|
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
|
|
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"
|
|
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"
|
|
1998
|
-
|
|
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">
|
|
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
|
|
2023
|
-
|
|
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
|
|
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"
|
|
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"
|
|
2044
|
-
|
|
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">
|
|
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,222 @@ 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
|
-
|
|
2106
|
-
|
|
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.
|
|
2117
|
-
|
|
2118
|
-
|
|
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.
|
|
2121
|
-
this.viewDate = subYears(this.viewDate,
|
|
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.
|
|
2125
|
-
|
|
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.
|
|
2130
|
-
|
|
2131
|
-
|
|
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.
|
|
2134
|
-
this.viewDate = addYears(this.viewDate,
|
|
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.
|
|
2138
|
-
|
|
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
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
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
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
if (this.maxDate && isBefore(this.maxDate, date)) {
|
|
2567
|
+
return true;
|
|
2568
|
+
}
|
|
2569
|
+
return false;
|
|
2570
|
+
}
|
|
2571
|
+
isTodayDisabled() {
|
|
2572
|
+
return this.isDateDisabled(new Date());
|
|
2573
|
+
}
|
|
2574
|
+
selectToday() {
|
|
2575
|
+
if (!this.isTodayDisabled()) {
|
|
2576
|
+
this.selectDate(new Date());
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
clear() {
|
|
2580
|
+
this.selectedDate = null;
|
|
2581
|
+
this.onChange(null);
|
|
2582
|
+
this.onTouched();
|
|
2583
|
+
this.dateSelect.emit(null);
|
|
2171
2584
|
}
|
|
2172
2585
|
// CVA Implementation
|
|
2173
2586
|
writeValue(obj) {
|
|
@@ -2184,27 +2597,26 @@ class CalendarComponent {
|
|
|
2184
2597
|
registerOnChange(fn) { this.onChange = fn; }
|
|
2185
2598
|
registerOnTouched(fn) { this.onTouched = fn; }
|
|
2186
2599
|
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: [
|
|
2600
|
+
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
2601
|
{
|
|
2189
2602
|
provide: NG_VALUE_ACCESSOR,
|
|
2190
2603
|
useExisting: forwardRef(() => CalendarComponent),
|
|
2191
2604
|
multi: true
|
|
2192
2605
|
}
|
|
2193
2606
|
], 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
|
-
|
|
2607
|
+
<div [class]="cn('p-3 border rounded-md bg-background text-popover-foreground shadow-sm inline-block min-w-fit', class)">
|
|
2608
|
+
<!-- Header with Navigation -->
|
|
2196
2609
|
<div class="flex items-center justify-between pt-1 pb-4 gap-2">
|
|
2197
|
-
|
|
2610
|
+
<!-- View Selector -->
|
|
2198
2611
|
<div class="flex items-center gap-1">
|
|
2199
|
-
<button
|
|
2612
|
+
<button *ngIf="mode !== 'year'"
|
|
2200
2613
|
type="button"
|
|
2201
2614
|
(click)="setView('month')"
|
|
2202
2615
|
[class]="cn(
|
|
2203
2616
|
'text-sm font-semibold px-2 py-1 rounded transition-colors',
|
|
2204
2617
|
currentView === 'month' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
|
|
2205
|
-
)"
|
|
2206
|
-
|
|
2207
|
-
{{ viewDate | date: 'MMMM' }}
|
|
2618
|
+
)">
|
|
2619
|
+
{{ formatMonthYear(viewDate, 'month') }}
|
|
2208
2620
|
</button>
|
|
2209
2621
|
|
|
2210
2622
|
<button
|
|
@@ -2213,12 +2625,12 @@ class CalendarComponent {
|
|
|
2213
2625
|
[class]="cn(
|
|
2214
2626
|
'text-sm font-semibold px-2 py-1 rounded transition-colors',
|
|
2215
2627
|
currentView === 'year' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
|
|
2216
|
-
)"
|
|
2217
|
-
|
|
2218
|
-
{{ viewDate | date: 'yyyy' }}
|
|
2628
|
+
)">
|
|
2629
|
+
{{ formatMonthYear(viewDate, 'year') }}
|
|
2219
2630
|
</button>
|
|
2220
2631
|
</div>
|
|
2221
2632
|
|
|
2633
|
+
<!-- Navigation Buttons -->
|
|
2222
2634
|
<div class="flex items-center space-x-1">
|
|
2223
2635
|
<button type="button" (click)="prev()" [class]="navBtnClass">
|
|
2224
2636
|
<i class="ri-arrow-left-s-line text-lg"></i>
|
|
@@ -2229,7 +2641,8 @@ class CalendarComponent {
|
|
|
2229
2641
|
</div>
|
|
2230
2642
|
</div>
|
|
2231
2643
|
|
|
2232
|
-
|
|
2644
|
+
<!-- DATE MODE -->
|
|
2645
|
+
<div *ngIf="currentView === 'date' && mode === 'date'" class="space-y-2 animate-in fade-in zoom-in-95 duration-200">
|
|
2233
2646
|
<div class="grid grid-cols-7 gap-1 w-full">
|
|
2234
2647
|
<span *ngFor="let day of weekDays" class="text-[0.8rem] text-muted-foreground font-normal text-center w-9">
|
|
2235
2648
|
{{ day }}
|
|
@@ -2243,47 +2656,66 @@ class CalendarComponent {
|
|
|
2243
2656
|
[disabled]="isDateDisabled(date)"
|
|
2244
2657
|
[class]="getDayClass(date)"
|
|
2245
2658
|
>
|
|
2246
|
-
{{ date
|
|
2659
|
+
{{ formatDate(date, 'day') }}
|
|
2247
2660
|
</button>
|
|
2248
2661
|
</div>
|
|
2249
2662
|
</div>
|
|
2250
2663
|
|
|
2251
|
-
|
|
2664
|
+
<!-- MONTH SELECTOR (for date mode and month mode) -->
|
|
2665
|
+
<div *ngIf="(currentView === 'month')"
|
|
2666
|
+
class="grid grid-cols-3 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
2252
2667
|
<button
|
|
2253
2668
|
*ngFor="let month of months; let i = index"
|
|
2254
2669
|
type="button"
|
|
2255
2670
|
(click)="selectMonth(i)"
|
|
2256
|
-
[class]="
|
|
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
|
-
)"
|
|
2671
|
+
[class]="getMonthClass(i)"
|
|
2260
2672
|
>
|
|
2261
2673
|
{{ month }}
|
|
2262
2674
|
</button>
|
|
2263
2675
|
</div>
|
|
2264
2676
|
|
|
2265
|
-
|
|
2677
|
+
<!-- YEAR SELECTOR (for date mode and year mode) -->
|
|
2678
|
+
<div *ngIf="(currentView === 'year') "
|
|
2679
|
+
class="grid grid-cols-4 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
2266
2680
|
<button
|
|
2267
2681
|
*ngFor="let year of years"
|
|
2268
2682
|
type="button"
|
|
2269
2683
|
(click)="selectYear(year)"
|
|
2270
|
-
[class]="
|
|
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
|
-
)"
|
|
2684
|
+
[class]="getYearClass(year)"
|
|
2274
2685
|
>
|
|
2275
2686
|
{{ year }}
|
|
2276
2687
|
</button>
|
|
2277
2688
|
</div>
|
|
2689
|
+
|
|
2690
|
+
<!-- Quick Actions -->
|
|
2691
|
+
<div *ngIf="showQuickActions" class="border-t pt-3 mt-3">
|
|
2692
|
+
<div class="flex items-center justify-between gap-2">
|
|
2693
|
+
<button
|
|
2694
|
+
type="button"
|
|
2695
|
+
(click)="selectToday()"
|
|
2696
|
+
[class]="quickActionBtnClass"
|
|
2697
|
+
[disabled]="isTodayDisabled()"
|
|
2698
|
+
>
|
|
2699
|
+
Today
|
|
2700
|
+
</button>
|
|
2701
|
+
<button
|
|
2702
|
+
type="button"
|
|
2703
|
+
(click)="clear()"
|
|
2704
|
+
[class]="quickActionBtnClass"
|
|
2705
|
+
>
|
|
2706
|
+
Clear
|
|
2707
|
+
</button>
|
|
2708
|
+
</div>
|
|
2709
|
+
</div>
|
|
2278
2710
|
</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: "
|
|
2711
|
+
`, 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
2712
|
}
|
|
2281
2713
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CalendarComponent, decorators: [{
|
|
2282
2714
|
type: Component,
|
|
2283
2715
|
args: [{
|
|
2284
2716
|
selector: 'tolle-calendar',
|
|
2285
2717
|
standalone: true,
|
|
2286
|
-
imports: [CommonModule],
|
|
2718
|
+
imports: [CommonModule, FormsModule],
|
|
2287
2719
|
providers: [
|
|
2288
2720
|
{
|
|
2289
2721
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -2292,20 +2724,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2292
2724
|
}
|
|
2293
2725
|
],
|
|
2294
2726
|
template: `
|
|
2295
|
-
<div [class]="cn('p-3 border rounded-md bg-background text-popover-foreground shadow-sm inline-block w-fit', class)">
|
|
2296
|
-
|
|
2727
|
+
<div [class]="cn('p-3 border rounded-md bg-background text-popover-foreground shadow-sm inline-block min-w-fit', class)">
|
|
2728
|
+
<!-- Header with Navigation -->
|
|
2297
2729
|
<div class="flex items-center justify-between pt-1 pb-4 gap-2">
|
|
2298
|
-
|
|
2730
|
+
<!-- View Selector -->
|
|
2299
2731
|
<div class="flex items-center gap-1">
|
|
2300
|
-
<button
|
|
2732
|
+
<button *ngIf="mode !== 'year'"
|
|
2301
2733
|
type="button"
|
|
2302
2734
|
(click)="setView('month')"
|
|
2303
2735
|
[class]="cn(
|
|
2304
2736
|
'text-sm font-semibold px-2 py-1 rounded transition-colors',
|
|
2305
2737
|
currentView === 'month' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
|
|
2306
|
-
)"
|
|
2307
|
-
|
|
2308
|
-
{{ viewDate | date: 'MMMM' }}
|
|
2738
|
+
)">
|
|
2739
|
+
{{ formatMonthYear(viewDate, 'month') }}
|
|
2309
2740
|
</button>
|
|
2310
2741
|
|
|
2311
2742
|
<button
|
|
@@ -2314,12 +2745,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2314
2745
|
[class]="cn(
|
|
2315
2746
|
'text-sm font-semibold px-2 py-1 rounded transition-colors',
|
|
2316
2747
|
currentView === 'year' ? 'bg-secondary text-secondary-foreground' : 'hover:bg-accent hover:text-accent-foreground'
|
|
2317
|
-
)"
|
|
2318
|
-
|
|
2319
|
-
{{ viewDate | date: 'yyyy' }}
|
|
2748
|
+
)">
|
|
2749
|
+
{{ formatMonthYear(viewDate, 'year') }}
|
|
2320
2750
|
</button>
|
|
2321
2751
|
</div>
|
|
2322
2752
|
|
|
2753
|
+
<!-- Navigation Buttons -->
|
|
2323
2754
|
<div class="flex items-center space-x-1">
|
|
2324
2755
|
<button type="button" (click)="prev()" [class]="navBtnClass">
|
|
2325
2756
|
<i class="ri-arrow-left-s-line text-lg"></i>
|
|
@@ -2330,7 +2761,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2330
2761
|
</div>
|
|
2331
2762
|
</div>
|
|
2332
2763
|
|
|
2333
|
-
|
|
2764
|
+
<!-- DATE MODE -->
|
|
2765
|
+
<div *ngIf="currentView === 'date' && mode === 'date'" class="space-y-2 animate-in fade-in zoom-in-95 duration-200">
|
|
2334
2766
|
<div class="grid grid-cols-7 gap-1 w-full">
|
|
2335
2767
|
<span *ngFor="let day of weekDays" class="text-[0.8rem] text-muted-foreground font-normal text-center w-9">
|
|
2336
2768
|
{{ day }}
|
|
@@ -2344,45 +2776,80 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2344
2776
|
[disabled]="isDateDisabled(date)"
|
|
2345
2777
|
[class]="getDayClass(date)"
|
|
2346
2778
|
>
|
|
2347
|
-
{{ date
|
|
2779
|
+
{{ formatDate(date, 'day') }}
|
|
2348
2780
|
</button>
|
|
2349
2781
|
</div>
|
|
2350
2782
|
</div>
|
|
2351
2783
|
|
|
2352
|
-
|
|
2784
|
+
<!-- MONTH SELECTOR (for date mode and month mode) -->
|
|
2785
|
+
<div *ngIf="(currentView === 'month')"
|
|
2786
|
+
class="grid grid-cols-3 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
2353
2787
|
<button
|
|
2354
2788
|
*ngFor="let month of months; let i = index"
|
|
2355
2789
|
type="button"
|
|
2356
2790
|
(click)="selectMonth(i)"
|
|
2357
|
-
[class]="
|
|
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
|
-
)"
|
|
2791
|
+
[class]="getMonthClass(i)"
|
|
2361
2792
|
>
|
|
2362
2793
|
{{ month }}
|
|
2363
2794
|
</button>
|
|
2364
2795
|
</div>
|
|
2365
2796
|
|
|
2366
|
-
|
|
2797
|
+
<!-- YEAR SELECTOR (for date mode and year mode) -->
|
|
2798
|
+
<div *ngIf="(currentView === 'year') "
|
|
2799
|
+
class="grid grid-cols-4 gap-2 w-64 animate-in fade-in zoom-in-95 duration-200">
|
|
2367
2800
|
<button
|
|
2368
2801
|
*ngFor="let year of years"
|
|
2369
2802
|
type="button"
|
|
2370
2803
|
(click)="selectYear(year)"
|
|
2371
|
-
[class]="
|
|
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
|
-
)"
|
|
2804
|
+
[class]="getYearClass(year)"
|
|
2375
2805
|
>
|
|
2376
2806
|
{{ year }}
|
|
2377
2807
|
</button>
|
|
2378
2808
|
</div>
|
|
2809
|
+
|
|
2810
|
+
<!-- Quick Actions -->
|
|
2811
|
+
<div *ngIf="showQuickActions" class="border-t pt-3 mt-3">
|
|
2812
|
+
<div class="flex items-center justify-between gap-2">
|
|
2813
|
+
<button
|
|
2814
|
+
type="button"
|
|
2815
|
+
(click)="selectToday()"
|
|
2816
|
+
[class]="quickActionBtnClass"
|
|
2817
|
+
[disabled]="isTodayDisabled()"
|
|
2818
|
+
>
|
|
2819
|
+
Today
|
|
2820
|
+
</button>
|
|
2821
|
+
<button
|
|
2822
|
+
type="button"
|
|
2823
|
+
(click)="clear()"
|
|
2824
|
+
[class]="quickActionBtnClass"
|
|
2825
|
+
>
|
|
2826
|
+
Clear
|
|
2827
|
+
</button>
|
|
2828
|
+
</div>
|
|
2829
|
+
</div>
|
|
2379
2830
|
</div>
|
|
2380
2831
|
`
|
|
2381
2832
|
}]
|
|
2382
|
-
}], propDecorators: { class: [{
|
|
2833
|
+
}], ctorParameters: () => [], propDecorators: { class: [{
|
|
2834
|
+
type: Input
|
|
2835
|
+
}], mode: [{
|
|
2383
2836
|
type: Input
|
|
2384
2837
|
}], disablePastDates: [{
|
|
2385
2838
|
type: Input
|
|
2839
|
+
}], showQuickActions: [{
|
|
2840
|
+
type: Input
|
|
2841
|
+
}], minDate: [{
|
|
2842
|
+
type: Input
|
|
2843
|
+
}], maxDate: [{
|
|
2844
|
+
type: Input
|
|
2845
|
+
}], formatMonthFn: [{
|
|
2846
|
+
type: Input
|
|
2847
|
+
}], formatYearFn: [{
|
|
2848
|
+
type: Input
|
|
2849
|
+
}], formatDateFn: [{
|
|
2850
|
+
type: Input
|
|
2851
|
+
}], dateSelect: [{
|
|
2852
|
+
type: Output
|
|
2386
2853
|
}] } });
|
|
2387
2854
|
|
|
2388
2855
|
class MaskedInputComponent {
|
|
@@ -2728,6 +3195,15 @@ class DatePickerComponent {
|
|
|
2728
3195
|
disabled = false;
|
|
2729
3196
|
class = '';
|
|
2730
3197
|
disablePastDates = false;
|
|
3198
|
+
showClear = true;
|
|
3199
|
+
showQuickActions = true;
|
|
3200
|
+
minDate;
|
|
3201
|
+
maxDate;
|
|
3202
|
+
mode = 'date';
|
|
3203
|
+
formatMonthFn;
|
|
3204
|
+
formatYearFn;
|
|
3205
|
+
// Format functions for display
|
|
3206
|
+
displayFormat;
|
|
2731
3207
|
triggerContainer;
|
|
2732
3208
|
popover;
|
|
2733
3209
|
value = null;
|
|
@@ -2737,12 +3213,56 @@ class DatePickerComponent {
|
|
|
2737
3213
|
constructor(cdr) {
|
|
2738
3214
|
this.cdr = cdr;
|
|
2739
3215
|
}
|
|
2740
|
-
|
|
3216
|
+
getMask() {
|
|
3217
|
+
switch (this.mode) {
|
|
3218
|
+
case 'date': return '00/00/0000';
|
|
3219
|
+
case 'month': return '00/0000';
|
|
3220
|
+
case 'year': return '0000';
|
|
3221
|
+
default: return '00/00/0000';
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
getPlaceholder() {
|
|
3225
|
+
switch (this.mode) {
|
|
3226
|
+
case 'date': return 'MM/DD/YYYY';
|
|
3227
|
+
case 'month': return 'MM/YYYY';
|
|
3228
|
+
case 'year': return 'YYYY';
|
|
3229
|
+
default: return 'MM/DD/YYYY';
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
getFormatString() {
|
|
3233
|
+
switch (this.mode) {
|
|
3234
|
+
case 'date': return 'MM/dd/yyyy';
|
|
3235
|
+
case 'month': return 'MM/yyyy';
|
|
3236
|
+
case 'year': return 'yyyy';
|
|
3237
|
+
default: return 'MM/dd/yyyy';
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
formatDate(date) {
|
|
3241
|
+
if (this.displayFormat) {
|
|
3242
|
+
return this.displayFormat(date, this.mode);
|
|
3243
|
+
}
|
|
3244
|
+
switch (this.mode) {
|
|
3245
|
+
case 'date': return format(date, 'MM/dd/yyyy');
|
|
3246
|
+
case 'month': return format(date, 'MM/yyyy');
|
|
3247
|
+
case 'year': return format(date, 'yyyy');
|
|
3248
|
+
default: return format(date, 'MM/dd/yyyy');
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
parseDate(str) {
|
|
3252
|
+
try {
|
|
3253
|
+
const parsed = parse(str, this.getFormatString(), new Date());
|
|
3254
|
+
return isValid(parsed) ? startOfDay(parsed) : null;
|
|
3255
|
+
}
|
|
3256
|
+
catch {
|
|
3257
|
+
return null;
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
2741
3260
|
onInputChange(str) {
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
3261
|
+
const expectedLength = this.getFormatString().replace(/[^0]/g, '').length;
|
|
3262
|
+
if (str?.length === expectedLength) {
|
|
3263
|
+
const parsed = this.parseDate(str);
|
|
3264
|
+
if (parsed) {
|
|
3265
|
+
this.value = parsed;
|
|
2746
3266
|
this.onChange(this.value);
|
|
2747
3267
|
}
|
|
2748
3268
|
}
|
|
@@ -2753,12 +3273,17 @@ class DatePickerComponent {
|
|
|
2753
3273
|
}
|
|
2754
3274
|
onCalendarChange(date) {
|
|
2755
3275
|
this.value = date;
|
|
2756
|
-
|
|
3276
|
+
if (date) {
|
|
3277
|
+
this.inputValue = this.formatDate(date);
|
|
3278
|
+
}
|
|
3279
|
+
else {
|
|
3280
|
+
this.inputValue = '';
|
|
3281
|
+
}
|
|
2757
3282
|
this.onChange(this.value);
|
|
2758
3283
|
this.close();
|
|
2759
3284
|
}
|
|
2760
3285
|
togglePopover(event) {
|
|
2761
|
-
event.stopPropagation();
|
|
3286
|
+
event.stopPropagation();
|
|
2762
3287
|
if (this.disabled)
|
|
2763
3288
|
return;
|
|
2764
3289
|
this.isOpen ? this.close() : this.open();
|
|
@@ -2773,22 +3298,27 @@ class DatePickerComponent {
|
|
|
2773
3298
|
this.cleanupAutoUpdate();
|
|
2774
3299
|
}
|
|
2775
3300
|
clear(event) {
|
|
2776
|
-
event.stopPropagation();
|
|
3301
|
+
event.stopPropagation();
|
|
2777
3302
|
this.value = null;
|
|
2778
3303
|
this.inputValue = '';
|
|
2779
3304
|
this.onChange(null);
|
|
2780
3305
|
this.cdr.markForCheck();
|
|
2781
3306
|
}
|
|
2782
|
-
// --- Positioning ---
|
|
2783
3307
|
updatePosition() {
|
|
2784
3308
|
if (!this.triggerContainer || !this.popover)
|
|
2785
3309
|
return;
|
|
2786
3310
|
this.cleanupAutoUpdate = autoUpdate(this.triggerContainer.nativeElement, this.popover.nativeElement, () => {
|
|
2787
3311
|
computePosition(this.triggerContainer.nativeElement, this.popover.nativeElement, {
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
3312
|
+
strategy: 'fixed', // ADDED: Fixed strategy
|
|
3313
|
+
placement: 'bottom-start', // Changed to bottom-start to align with input left edge
|
|
3314
|
+
middleware: [
|
|
3315
|
+
offset(4),
|
|
3316
|
+
flip(),
|
|
3317
|
+
shift({ padding: 8 })
|
|
3318
|
+
],
|
|
3319
|
+
}).then(({ x, y, strategy }) => {
|
|
2791
3320
|
Object.assign(this.popover.nativeElement.style, {
|
|
3321
|
+
position: strategy,
|
|
2792
3322
|
left: `${x}px`,
|
|
2793
3323
|
top: `${y}px`,
|
|
2794
3324
|
visibility: 'visible',
|
|
@@ -2803,7 +3333,7 @@ class DatePickerComponent {
|
|
|
2803
3333
|
this.close();
|
|
2804
3334
|
}
|
|
2805
3335
|
}
|
|
2806
|
-
//
|
|
3336
|
+
// CVA Implementation
|
|
2807
3337
|
onChange = () => { };
|
|
2808
3338
|
onTouched = () => { };
|
|
2809
3339
|
writeValue(val) {
|
|
@@ -2811,7 +3341,7 @@ class DatePickerComponent {
|
|
|
2811
3341
|
const date = new Date(val);
|
|
2812
3342
|
if (isValid(date)) {
|
|
2813
3343
|
this.value = startOfDay(date);
|
|
2814
|
-
this.inputValue =
|
|
3344
|
+
this.inputValue = this.formatDate(this.value);
|
|
2815
3345
|
}
|
|
2816
3346
|
}
|
|
2817
3347
|
else {
|
|
@@ -2825,7 +3355,7 @@ class DatePickerComponent {
|
|
|
2825
3355
|
setDisabledState(isDisabled) { this.disabled = isDisabled; }
|
|
2826
3356
|
cn = cn;
|
|
2827
3357
|
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: [
|
|
3358
|
+
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
3359
|
{
|
|
2830
3360
|
provide: NG_VALUE_ACCESSOR,
|
|
2831
3361
|
useExisting: forwardRef(() => DatePickerComponent),
|
|
@@ -2835,8 +3365,8 @@ class DatePickerComponent {
|
|
|
2835
3365
|
<div class="relative w-full" #triggerContainer>
|
|
2836
3366
|
<tolle-masked-input
|
|
2837
3367
|
#maskInput
|
|
2838
|
-
[mask]="
|
|
2839
|
-
[placeholder]="
|
|
3368
|
+
[mask]="getMask()"
|
|
3369
|
+
[placeholder]="getPlaceholder()"
|
|
2840
3370
|
[disabled]="disabled"
|
|
2841
3371
|
[(ngModel)]="inputValue"
|
|
2842
3372
|
(ngModelChange)="onInputChange($event)"
|
|
@@ -2844,14 +3374,17 @@ class DatePickerComponent {
|
|
|
2844
3374
|
>
|
|
2845
3375
|
<div suffix class="flex items-center gap-1.5 cursor-pointer">
|
|
2846
3376
|
<i
|
|
2847
|
-
*ngIf="value && !disabled"
|
|
3377
|
+
*ngIf="value && !disabled && showClear"
|
|
2848
3378
|
(click)="clear($event)"
|
|
2849
3379
|
class="ri-close-line cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
|
|
2850
3380
|
></i>
|
|
2851
3381
|
|
|
2852
3382
|
<i
|
|
2853
3383
|
(click)="togglePopover($event)"
|
|
2854
|
-
class="
|
|
3384
|
+
[class]="cn(
|
|
3385
|
+
'cursor-pointer text-muted-foreground transition-colors',
|
|
3386
|
+
'ri-calendar-line'
|
|
3387
|
+
)"
|
|
2855
3388
|
></i>
|
|
2856
3389
|
</div>
|
|
2857
3390
|
</tolle-masked-input>
|
|
@@ -2859,17 +3392,23 @@ class DatePickerComponent {
|
|
|
2859
3392
|
<div
|
|
2860
3393
|
#popover
|
|
2861
3394
|
*ngIf="isOpen"
|
|
2862
|
-
class="
|
|
3395
|
+
class="fixed z-[50]"
|
|
2863
3396
|
style="visibility: hidden; top: 0; left: 0;"
|
|
2864
3397
|
>
|
|
2865
|
-
<tolle-calendar
|
|
3398
|
+
<tolle-calendar class="shadow-lg"
|
|
2866
3399
|
[(ngModel)]="value"
|
|
2867
3400
|
(ngModelChange)="onCalendarChange($event)"
|
|
3401
|
+
[mode]="mode"
|
|
2868
3402
|
[disablePastDates]="disablePastDates"
|
|
3403
|
+
[minDate]="minDate"
|
|
3404
|
+
[maxDate]="maxDate"
|
|
3405
|
+
[showQuickActions]="showQuickActions"
|
|
3406
|
+
[formatMonthFn]="formatMonthFn"
|
|
3407
|
+
[formatYearFn]="formatYearFn"
|
|
2869
3408
|
></tolle-calendar>
|
|
2870
3409
|
</div>
|
|
2871
3410
|
</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"] }] });
|
|
3411
|
+
`, 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
3412
|
}
|
|
2874
3413
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DatePickerComponent, decorators: [{
|
|
2875
3414
|
type: Component,
|
|
@@ -2888,8 +3427,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2888
3427
|
<div class="relative w-full" #triggerContainer>
|
|
2889
3428
|
<tolle-masked-input
|
|
2890
3429
|
#maskInput
|
|
2891
|
-
[mask]="
|
|
2892
|
-
[placeholder]="
|
|
3430
|
+
[mask]="getMask()"
|
|
3431
|
+
[placeholder]="getPlaceholder()"
|
|
2893
3432
|
[disabled]="disabled"
|
|
2894
3433
|
[(ngModel)]="inputValue"
|
|
2895
3434
|
(ngModelChange)="onInputChange($event)"
|
|
@@ -2897,14 +3436,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2897
3436
|
>
|
|
2898
3437
|
<div suffix class="flex items-center gap-1.5 cursor-pointer">
|
|
2899
3438
|
<i
|
|
2900
|
-
*ngIf="value && !disabled"
|
|
3439
|
+
*ngIf="value && !disabled && showClear"
|
|
2901
3440
|
(click)="clear($event)"
|
|
2902
3441
|
class="ri-close-line cursor-pointer text-muted-foreground hover:text-foreground transition-colors"
|
|
2903
3442
|
></i>
|
|
2904
3443
|
|
|
2905
3444
|
<i
|
|
2906
3445
|
(click)="togglePopover($event)"
|
|
2907
|
-
class="
|
|
3446
|
+
[class]="cn(
|
|
3447
|
+
'cursor-pointer text-muted-foreground transition-colors',
|
|
3448
|
+
'ri-calendar-line'
|
|
3449
|
+
)"
|
|
2908
3450
|
></i>
|
|
2909
3451
|
</div>
|
|
2910
3452
|
</tolle-masked-input>
|
|
@@ -2912,13 +3454,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2912
3454
|
<div
|
|
2913
3455
|
#popover
|
|
2914
3456
|
*ngIf="isOpen"
|
|
2915
|
-
class="
|
|
3457
|
+
class="fixed z-[50]"
|
|
2916
3458
|
style="visibility: hidden; top: 0; left: 0;"
|
|
2917
3459
|
>
|
|
2918
|
-
<tolle-calendar
|
|
3460
|
+
<tolle-calendar class="shadow-lg"
|
|
2919
3461
|
[(ngModel)]="value"
|
|
2920
3462
|
(ngModelChange)="onCalendarChange($event)"
|
|
3463
|
+
[mode]="mode"
|
|
2921
3464
|
[disablePastDates]="disablePastDates"
|
|
3465
|
+
[minDate]="minDate"
|
|
3466
|
+
[maxDate]="maxDate"
|
|
3467
|
+
[showQuickActions]="showQuickActions"
|
|
3468
|
+
[formatMonthFn]="formatMonthFn"
|
|
3469
|
+
[formatYearFn]="formatYearFn"
|
|
2922
3470
|
></tolle-calendar>
|
|
2923
3471
|
</div>
|
|
2924
3472
|
</div>
|
|
@@ -2932,6 +3480,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2932
3480
|
type: Input
|
|
2933
3481
|
}], disablePastDates: [{
|
|
2934
3482
|
type: Input
|
|
3483
|
+
}], showClear: [{
|
|
3484
|
+
type: Input
|
|
3485
|
+
}], showQuickActions: [{
|
|
3486
|
+
type: Input
|
|
3487
|
+
}], minDate: [{
|
|
3488
|
+
type: Input
|
|
3489
|
+
}], maxDate: [{
|
|
3490
|
+
type: Input
|
|
3491
|
+
}], mode: [{
|
|
3492
|
+
type: Input
|
|
3493
|
+
}], formatMonthFn: [{
|
|
3494
|
+
type: Input
|
|
3495
|
+
}], formatYearFn: [{
|
|
3496
|
+
type: Input
|
|
3497
|
+
}], displayFormat: [{
|
|
3498
|
+
type: Input
|
|
2935
3499
|
}], triggerContainer: [{
|
|
2936
3500
|
type: ViewChild,
|
|
2937
3501
|
args: ['triggerContainer']
|
|
@@ -3113,7 +3677,7 @@ class PaginationComponent {
|
|
|
3113
3677
|
</div>
|
|
3114
3678
|
</div>
|
|
3115
3679
|
</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 });
|
|
3680
|
+
`, 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
3681
|
}
|
|
3118
3682
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PaginationComponent, decorators: [{
|
|
3119
3683
|
type: Component,
|
|
@@ -3386,7 +3950,7 @@ class DataTableComponent {
|
|
|
3386
3950
|
</tolle-input>
|
|
3387
3951
|
</div>
|
|
3388
3952
|
|
|
3389
|
-
<div class="rounded-md border border-border
|
|
3953
|
+
<div class="rounded-md border border-border overflow-hidden shadow-sm">
|
|
3390
3954
|
<table class="w-full text-sm">
|
|
3391
3955
|
<thead class="border-b bg-muted/30">
|
|
3392
3956
|
<tr>
|
|
@@ -3482,7 +4046,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3482
4046
|
</tolle-input>
|
|
3483
4047
|
</div>
|
|
3484
4048
|
|
|
3485
|
-
<div class="rounded-md border border-border
|
|
4049
|
+
<div class="rounded-md border border-border overflow-hidden shadow-sm">
|
|
3486
4050
|
<table class="w-full text-sm">
|
|
3487
4051
|
<thead class="border-b bg-muted/30">
|
|
3488
4052
|
<tr>
|
|
@@ -4248,7 +4812,6 @@ class DateRangePickerComponent {
|
|
|
4248
4812
|
placeholder = 'Pick a date range';
|
|
4249
4813
|
class = '';
|
|
4250
4814
|
disablePastDates = false;
|
|
4251
|
-
// Standardized Sizes
|
|
4252
4815
|
size = 'default';
|
|
4253
4816
|
triggerContainer;
|
|
4254
4817
|
popover;
|
|
@@ -4261,7 +4824,7 @@ class DateRangePickerComponent {
|
|
|
4261
4824
|
get displayValue() {
|
|
4262
4825
|
if (!this.value.start)
|
|
4263
4826
|
return '';
|
|
4264
|
-
const startStr = format(this.value.start, 'MMM dd, yyyy');
|
|
4827
|
+
const startStr = format(this.value.start, 'MMM dd, yyyy');
|
|
4265
4828
|
if (!this.value.end)
|
|
4266
4829
|
return startStr;
|
|
4267
4830
|
const endStr = format(this.value.end, 'MMM dd, yyyy');
|
|
@@ -4272,12 +4835,11 @@ class DateRangePickerComponent {
|
|
|
4272
4835
|
this.onChange(this.value);
|
|
4273
4836
|
// Close only if range is complete
|
|
4274
4837
|
if (range.start && range.end) {
|
|
4275
|
-
this.onChange(this.value);
|
|
4276
|
-
// Small delay for UX
|
|
4277
4838
|
setTimeout(() => this.close(), 150);
|
|
4278
4839
|
}
|
|
4279
4840
|
}
|
|
4280
|
-
togglePopover(
|
|
4841
|
+
togglePopover(event) {
|
|
4842
|
+
event.stopPropagation();
|
|
4281
4843
|
if (this.disabled)
|
|
4282
4844
|
return;
|
|
4283
4845
|
this.isOpen ? this.close() : this.open();
|
|
@@ -4288,39 +4850,75 @@ class DateRangePickerComponent {
|
|
|
4288
4850
|
}
|
|
4289
4851
|
close() {
|
|
4290
4852
|
this.isOpen = false;
|
|
4291
|
-
if (this.cleanupAutoUpdate)
|
|
4853
|
+
if (this.cleanupAutoUpdate) {
|
|
4292
4854
|
this.cleanupAutoUpdate();
|
|
4855
|
+
this.cleanupAutoUpdate = undefined;
|
|
4856
|
+
}
|
|
4293
4857
|
}
|
|
4294
4858
|
clear(event) {
|
|
4295
|
-
event.stopPropagation();
|
|
4859
|
+
event.stopPropagation();
|
|
4296
4860
|
this.value = { start: null, end: null };
|
|
4297
4861
|
this.onChange(this.value);
|
|
4862
|
+
this.cdr.markForCheck();
|
|
4298
4863
|
}
|
|
4299
|
-
// --- Floating UI Positioning ---
|
|
4864
|
+
// --- Floating UI Positioning with Fixed Strategy ---
|
|
4300
4865
|
updatePosition() {
|
|
4301
4866
|
if (!this.triggerContainer || !this.popover)
|
|
4302
4867
|
return;
|
|
4303
4868
|
this.cleanupAutoUpdate = autoUpdate(this.triggerContainer.nativeElement, this.popover.nativeElement, () => {
|
|
4304
4869
|
computePosition(this.triggerContainer.nativeElement, this.popover.nativeElement, {
|
|
4305
|
-
placement: 'bottom-
|
|
4306
|
-
|
|
4307
|
-
|
|
4870
|
+
placement: 'bottom-end',
|
|
4871
|
+
strategy: 'fixed', // Use fixed to escape column layout
|
|
4872
|
+
middleware: [
|
|
4873
|
+
offset(4),
|
|
4874
|
+
flip({
|
|
4875
|
+
fallbackAxisSideDirection: 'start',
|
|
4876
|
+
padding: 8
|
|
4877
|
+
}),
|
|
4878
|
+
shift({ padding: 8 }),
|
|
4879
|
+
size({
|
|
4880
|
+
apply({ rects, elements, availableHeight }) {
|
|
4881
|
+
// Constrain popover to available space
|
|
4882
|
+
Object.assign(elements.floating.style, {
|
|
4883
|
+
maxHeight: `${Math.min(400, availableHeight)}px`,
|
|
4884
|
+
minWidth: `${Math.max(rects.reference.width, 320)}px`, // Calendar minimum width
|
|
4885
|
+
});
|
|
4886
|
+
}
|
|
4887
|
+
})
|
|
4888
|
+
],
|
|
4889
|
+
}).then(({ x, y, placement }) => {
|
|
4308
4890
|
Object.assign(this.popover.nativeElement.style, {
|
|
4309
4891
|
left: `${x}px`,
|
|
4310
4892
|
top: `${y}px`,
|
|
4311
4893
|
visibility: 'visible',
|
|
4312
4894
|
});
|
|
4895
|
+
// Optional: Add placement class for styling
|
|
4896
|
+
this.popover.nativeElement.classList.remove('calendar-top', 'calendar-bottom');
|
|
4897
|
+
if (placement.includes('top')) {
|
|
4898
|
+
this.popover.nativeElement.classList.add('calendar-top');
|
|
4899
|
+
}
|
|
4900
|
+
else {
|
|
4901
|
+
this.popover.nativeElement.classList.add('calendar-bottom');
|
|
4902
|
+
}
|
|
4313
4903
|
});
|
|
4314
4904
|
});
|
|
4315
4905
|
}
|
|
4316
4906
|
onClickOutside(event) {
|
|
4317
4907
|
if (this.isOpen &&
|
|
4318
4908
|
!this.triggerContainer.nativeElement.contains(event.target) &&
|
|
4319
|
-
!this.popover
|
|
4909
|
+
!this.popover?.nativeElement.contains(event.target)) {
|
|
4320
4910
|
this.close();
|
|
4321
4911
|
}
|
|
4322
4912
|
}
|
|
4323
|
-
|
|
4913
|
+
onWindowResize() {
|
|
4914
|
+
if (this.isOpen) {
|
|
4915
|
+
this.close(); // Close on resize for simplicity
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
onWindowScroll() {
|
|
4919
|
+
// Floating-UI's autoUpdate handles scroll repositioning
|
|
4920
|
+
}
|
|
4921
|
+
// --- Control Value Accessor ---
|
|
4324
4922
|
onChange = () => { };
|
|
4325
4923
|
onTouched = () => { };
|
|
4326
4924
|
writeValue(val) {
|
|
@@ -4337,7 +4935,7 @@ class DateRangePickerComponent {
|
|
|
4337
4935
|
setDisabledState(isDisabled) { this.disabled = isDisabled; }
|
|
4338
4936
|
cn = cn;
|
|
4339
4937
|
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: [
|
|
4938
|
+
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
4939
|
{
|
|
4342
4940
|
provide: NG_VALUE_ACCESSOR,
|
|
4343
4941
|
useExisting: forwardRef(() => DateRangePickerComponent),
|
|
@@ -4345,7 +4943,12 @@ class DateRangePickerComponent {
|
|
|
4345
4943
|
}
|
|
4346
4944
|
], viewQueries: [{ propertyName: "triggerContainer", first: true, predicate: ["triggerContainer"], descendants: true }, { propertyName: "popover", first: true, predicate: ["popover"], descendants: true }], ngImport: i0, template: `
|
|
4347
4945
|
<div class="relative w-full" #triggerContainer>
|
|
4348
|
-
<tolle-input
|
|
4946
|
+
<tolle-input
|
|
4947
|
+
[placeholder]="placeholder"
|
|
4948
|
+
[disabled]="disabled"
|
|
4949
|
+
[ngModel]="displayValue"
|
|
4950
|
+
[class]="class"
|
|
4951
|
+
>
|
|
4349
4952
|
<div suffix class="flex items-center gap-1.5 cursor-pointer">
|
|
4350
4953
|
<i
|
|
4351
4954
|
*ngIf="(value.start || value.end) && !disabled"
|
|
@@ -4359,13 +4962,14 @@ class DateRangePickerComponent {
|
|
|
4359
4962
|
></i>
|
|
4360
4963
|
</div>
|
|
4361
4964
|
</tolle-input>
|
|
4965
|
+
|
|
4362
4966
|
<div
|
|
4363
4967
|
#popover
|
|
4364
4968
|
*ngIf="isOpen"
|
|
4365
|
-
class="
|
|
4366
|
-
style="visibility: hidden;
|
|
4969
|
+
class="fixed z-50"
|
|
4970
|
+
style="visibility: hidden;"
|
|
4367
4971
|
>
|
|
4368
|
-
<tolle-range-calendar
|
|
4972
|
+
<tolle-range-calendar class="shadow-lg"
|
|
4369
4973
|
[ngModel]="value"
|
|
4370
4974
|
(rangeSelect)="onCalendarSelect($event)"
|
|
4371
4975
|
[disablePastDates]="disablePastDates"
|
|
@@ -4389,7 +4993,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4389
4993
|
],
|
|
4390
4994
|
template: `
|
|
4391
4995
|
<div class="relative w-full" #triggerContainer>
|
|
4392
|
-
<tolle-input
|
|
4996
|
+
<tolle-input
|
|
4997
|
+
[placeholder]="placeholder"
|
|
4998
|
+
[disabled]="disabled"
|
|
4999
|
+
[ngModel]="displayValue"
|
|
5000
|
+
[class]="class"
|
|
5001
|
+
>
|
|
4393
5002
|
<div suffix class="flex items-center gap-1.5 cursor-pointer">
|
|
4394
5003
|
<i
|
|
4395
5004
|
*ngIf="(value.start || value.end) && !disabled"
|
|
@@ -4403,13 +5012,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4403
5012
|
></i>
|
|
4404
5013
|
</div>
|
|
4405
5014
|
</tolle-input>
|
|
5015
|
+
|
|
4406
5016
|
<div
|
|
4407
5017
|
#popover
|
|
4408
5018
|
*ngIf="isOpen"
|
|
4409
|
-
class="
|
|
4410
|
-
style="visibility: hidden;
|
|
5019
|
+
class="fixed z-50"
|
|
5020
|
+
style="visibility: hidden;"
|
|
4411
5021
|
>
|
|
4412
|
-
<tolle-range-calendar
|
|
5022
|
+
<tolle-range-calendar class="shadow-lg"
|
|
4413
5023
|
[ngModel]="value"
|
|
4414
5024
|
(rangeSelect)="onCalendarSelect($event)"
|
|
4415
5025
|
[disablePastDates]="disablePastDates"
|
|
@@ -4437,6 +5047,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4437
5047
|
}], onClickOutside: [{
|
|
4438
5048
|
type: HostListener,
|
|
4439
5049
|
args: ['document:mousedown', ['$event']]
|
|
5050
|
+
}], onWindowResize: [{
|
|
5051
|
+
type: HostListener,
|
|
5052
|
+
args: ['window:resize']
|
|
5053
|
+
}], onWindowScroll: [{
|
|
5054
|
+
type: HostListener,
|
|
5055
|
+
args: ['window:scroll']
|
|
4440
5056
|
}] } });
|
|
4441
5057
|
|
|
4442
5058
|
class DropdownItemComponent {
|