npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ng-vui/date-picker

v1.0.0

Published

Angular Date Picker Component for UI Library

Readme

@ng-vui/date-picker

A fully-featured Angular date picker component with calendar view, date ranges, localization, and accessibility support.

📚 Quick Navigation

| Section | Description | Perfect For | |---------|-------------|------------| | 🚀 Features | Complete feature overview | Understanding capabilities | | 📦 Installation | Setup and installation | Getting started | | ⚡ Quick Start | Configuration examples | Initial setup | | 🎯 Basic Usage | Simple implementation | First-time setup | | 🔧 Configuration | Settings and options | Customization | | ✨ Advanced Features | Complex use cases | Advanced implementation | | 🎨 Styling & Theming | Visual customization | UI/UX design | | 🌍 Internationalization | Multi-language support | Global applications | | 🧪 Testing | Unit testing examples | Quality assurance | | 🚀 Performance | Optimization strategies | Large applications |


🎯 Use Cases & Examples

Single Date Selection

Perfect for birthdays, deadlines, and appointment scheduling.

<my-date-picker [(ngModel)]="birthday" [options]="{dateFormat: 'dd/mm/yyyy'}"></my-date-picker>

Date Range Selection

Ideal for booking systems, reporting periods, and date filters.

<my-date-picker [(ngModel)]="dateRange" [options]="{dateRange: true, rangeSeparator: ' - '}"></my-date-picker>

Form Integration

Seamless integration with Angular reactive forms and validation.

<my-date-picker [formControl]="dateControl" [options]="formDateOptions"></my-date-picker>

Localization

Support for multiple languages and regional date formats.

<my-date-picker [options]="{dayLabels: germanDayLabels, monthLabels: germanMonthLabels}"></my-date-picker>

Validation & Restrictions

Min/max dates, disabled dates, and custom validation rules.

<my-date-picker [options]="{disableDates: weekends, minYear: 2020, maxYear: 2025}"></my-date-picker>

🚀 Features

  • Date Selection - Single date, date range, and multiple dates
  • Calendar Views - Month, year, and decade navigation
  • Internationalization - Multiple locales and date formats
  • Accessibility - Full ARIA support and keyboard navigation
  • Validation - Min/max dates, disabled dates, and custom validators
  • Reactive Forms - Angular Forms integration
  • Customizable - Custom templates and styling
  • Mobile Friendly - Touch-optimized interface
  • TypeScript - Full type safety

📦 Installation

npm install @ng-vui/date-picker

Quick Start

1. Basic Date Picker

const basicOptions: IMyDpOptions = {
  dateFormat: 'dd/mm/yyyy',
  markCurrentDay: true,
  todayBtnTxt: 'Today',
  clearBtnTxt: 'Clear'
};

2. Date Range Picker

const rangeOptions: IMyDpOptions = {
  dateRange: true,
  rangeSeparator: ' - ',
  dateFormat: 'dd/mm/yyyy',
  markCurrentDay: true,
  showWeekNumbers: true
};

3. Localized Date Picker

const localizedOptions: IMyDpOptions = {
  dateFormat: 'dd.mm.yyyy',
  firstDayOfWeek: 'mo',
  monthLabels: {
    1: 'Januar', 2: 'Februar', 3: 'März', 4: 'April',
    5: 'Mai', 6: 'Juni', 7: 'Juli', 8: 'August',
    9: 'September', 10: 'Oktober', 11: 'November', 12: 'Dezember'
  },
  dayLabels: { su: 'So', mo: 'Mo', tu: 'Di', we: 'Mi', th: 'Do', fr: 'Fr', sa: 'Sa' }
};

4. Validated Date Picker

const validatedOptions: IMyDpOptions = {
  dateFormat: 'yyyy-mm-dd',
  minYear: 1900,
  maxYear: 2030,
  disableUntil: { year: 2020, month: 1, day: 1 },
  disableSince: { year: 2025, month: 12, day: 31 },
  disableWeekends: false
};

🎯 Basic Usage

Import

import { MyDatePickerModule } from '@ng-vui/date-picker';

// For modules
@NgModule({
  imports: [MyDatePickerModule],
  // ... module config
})

// For standalone components
import { MyDatePickerComponent } from '@ng-vui/date-picker';

@Component({
  standalone: true,
  imports: [MyDatePickerComponent],
  // ... component config
})

Simple Date Picker

@Component({
  selector: 'app-basic-example',
  template: `
    <my-date-picker 
      [options]="myDatePickerOptions" 
      [(ngModel)]="selectedDate"
      placeholder="Select date">
    </my-date-picker>
    
    <div class="mt-3" *ngIf="selectedDate">
      Selected: {{ selectedDate | json }}
    </div>
  `
})
export class BasicExampleComponent {
  selectedDate: any = null;
  
  myDatePickerOptions: IMyDpOptions = {
    dateFormat: 'dd.mm.yyyy',
    firstDayOfWeek: 'mo',
    sunHighlight: true,
    markCurrentDay: true,
    markCurrentMonth: true,
    markCurrentYear: true,
    monthLabels: { 
      1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
      7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec' 
    },
    dayLabels: { su: 'Sun', mo: 'Mon', tu: 'Tue', we: 'Wed', th: 'Thu', fr: 'Fri', sa: 'Sat' },
    todayBtnTxt: 'Today',
    clearBtnTxt: 'Clear',
    closeBtnTxt: 'Close'
  };
}

With Reactive Forms and Validation

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, AbstractControl, ValidationErrors, ReactiveFormsModule } from '@angular/forms';
import { MyDatePickerComponent } from '@ng-vui/date-picker';

@Component({
  standalone: true,
  imports: [ReactiveFormsModule, MyDatePickerComponent],
  template: `
    <form [formGroup]="userForm" (ngSubmit)="onSubmit()">
      <div class="space-y-4">
        <div class="form-group">
          <label class="block text-sm font-medium mb-1">Birth Date *</label>
          <my-date-picker 
            formControlName="birthDate"
            [options]="birthDateOptions"
            placeholder="Select your birth date"
            class="w-full">
          </my-date-picker>
          
          <div *ngIf="getBirthDateError()" class="mt-1 text-sm text-red-600">
            {{ getBirthDateError() }}
          </div>
        </div>

        <div class="form-group">
          <label class="block text-sm font-medium mb-1">Appointment Date *</label>
          <my-date-picker 
            formControlName="appointmentDate"
            [options]="appointmentOptions"
            placeholder="Select appointment date"
            class="w-full">
          </my-date-picker>
          
          <div *ngIf="getAppointmentError()" class="mt-1 text-sm text-red-600">
            {{ getAppointmentError() }}
          </div>
        </div>

        <button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
          Schedule Appointment
        </button>
      </div>
    </form>
  `
})
export class ReactiveFormComponent implements OnInit {
  userForm!: FormGroup;
  isSubmitted = false;

  birthDateOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disableFuture: true, // Disable future dates for birth date
    minYear: 1900,
    maxYear: new Date().getFullYear(),
    showTodayBtn: false,
    markCurrentDay: true
  };

  appointmentOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true, // Disable past dates for appointments
    disableWeekends: true, // No weekend appointments
    showTodayBtn: true,
    markCurrentDay: true,
    todayBtnTxt: 'Today',
    clearBtnTxt: 'Clear'
  };

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.userForm = this.fb.group({
      birthDate: ['', [
        Validators.required,
        this.birthDateValidator.bind(this)
      ]],
      appointmentDate: ['', [
        Validators.required,
        this.appointmentDateValidator.bind(this)
      ]]
    });
  }

  // Birth date validation
  birthDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const selectedDate = new Date(control.value.jsdate);
    const today = new Date();
    const minDate = new Date('1900-01-01');
    
    if (selectedDate > today) {
      return { futureDate: true };
    }
    
    if (selectedDate < minDate) {
      return { tooOld: true };
    }
    
    // Check if person is under 18
    const age = today.getFullYear() - selectedDate.getFullYear();
    if (age < 18) {
      return { underAge: true };
    }
    
    return null;
  }

  // Appointment date validation
  appointmentDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const selectedDate = new Date(control.value.jsdate);
    const today = new Date();
    const dayOfWeek = selectedDate.getDay();
    
    // Check if it's in the past
    if (selectedDate < today) {
      return { pastDate: true };
    }
    
    // Check if it's a weekend
    if (dayOfWeek === 0 || dayOfWeek === 6) {
      return { weekendNotAllowed: true };
    }
    
    // Check if it's too far in the future (max 6 months)
    const maxDate = new Date(today);
    maxDate.setMonth(today.getMonth() + 6);
    
    if (selectedDate > maxDate) {
      return { tooFarInFuture: true };
    }
    
    return null;
  }

  getBirthDateError(): string | null {
    const control = this.userForm.get('birthDate');
    
    if (!control || (!control.touched && !this.isSubmitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Birth date is required';
    }
    
    if (control.errors?.['futureDate']) {
      return 'Birth date cannot be in the future';
    }
    
    if (control.errors?.['tooOld']) {
      return 'Please enter a valid birth date';
    }
    
    if (control.errors?.['underAge']) {
      return 'You must be at least 18 years old';
    }
    
    return null;
  }

  getAppointmentError(): string | null {
    const control = this.userForm.get('appointmentDate');
    
    if (!control || (!control.touched && !this.isSubmitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Appointment date is required';
    }
    
    if (control.errors?.['pastDate']) {
      return 'Appointment date cannot be in the past';
    }
    
    if (control.errors?.['weekendNotAllowed']) {
      return 'Weekend appointments are not available';
    }
    
    if (control.errors?.['tooFarInFuture']) {
      return 'Appointments can only be scheduled up to 6 months in advance';
    }
    
    return null;
  }

  onSubmit() {
    this.isSubmitted = true;
    if (this.userForm.valid) {
      console.log('Form submitted:', this.userForm.value);
    }
  }
}

🚨 Error Handling & Validation

Built-in Validation Support

The VUI date picker component integrates seamlessly with Angular's reactive forms and provides comprehensive validation capabilities for date inputs:

Required Validation

@Component({
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <div class="form-group">
        <label>Event Date *</label>
        <my-date-picker 
          formControlName="eventDate"
          [options]="eventOptions"
          placeholder="Select event date">
        </my-date-picker>
        
        <div *ngIf="getEventDateError()" class="mt-1 text-sm text-red-600">
          {{ getEventDateError() }}
        </div>
      </div>
    </form>
  `
})
export class RequiredValidationExample {
  form = this.fb.group({
    eventDate: ['', Validators.required] // Required date selection
  });
  
  eventOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    markCurrentDay: true
  };

  submitted = false;

  getEventDateError(): string | null {
    const control = this.form.get('eventDate');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Event date is required for registration';
    }
    
    return null;
  }

  onSubmit() {
    this.submitted = true;
    if (this.form.valid) {
      console.log('Event scheduled:', this.form.value);
    }
  }
}

Date Range Validation

@Component({
  template: `
    <form [formGroup]="travelForm" (ngSubmit)="onSubmit()">
      <div class="grid grid-cols-2 gap-4">
        <div>
          <label>Departure Date *</label>
          <my-date-picker 
            formControlName="departureDate"
            [options]="departureDateOptions"
            placeholder="Select departure"
            (dateChanged)="onDepartureDateChange($event)">
          </my-date-picker>
          
          <div *ngIf="getDepartureDateError()" class="mt-1 text-sm text-red-600">
            {{ getDepartureDateError() }}
          </div>
        </div>

        <div>
          <label>Return Date *</label>
          <my-date-picker 
            formControlName="returnDate"
            [options]="returnDateOptions"
            placeholder="Select return">
          </my-date-picker>
          
          <div *ngIf="getReturnDateError()" class="mt-1 text-sm text-red-600">
            {{ getReturnDateError() }}
          </div>
        </div>
      </div>
    </form>
  `
})
export class DateRangeValidationExample implements OnInit {
  travelForm = this.fb.group({
    departureDate: ['', [Validators.required, this.departureDateValidator.bind(this)]],
    returnDate: ['', [Validators.required, this.returnDateValidator.bind(this)]]
  });

  departureDateOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true,
    markCurrentDay: true
  };

  returnDateOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true,
    markCurrentDay: true
  };

  submitted = false;

  ngOnInit() {
    // Watch for departure date changes to update return date restrictions
    this.travelForm.get('departureDate')?.valueChanges.subscribe(() => {
      this.updateReturnDateOptions();
      this.travelForm.get('returnDate')?.updateValueAndValidity();
    });
  }

  departureDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const selectedDate = new Date(control.value.jsdate);
    const today = new Date();
    
    // Must be at least tomorrow
    const tomorrow = new Date(today);
    tomorrow.setDate(today.getDate() + 1);
    
    if (selectedDate < tomorrow) {
      return { tooSoon: true };
    }
    
    // Cannot be more than 1 year in advance
    const maxDate = new Date(today);
    maxDate.setFullYear(today.getFullYear() + 1);
    
    if (selectedDate > maxDate) {
      return { tooFarAhead: true };
    }
    
    return null;
  }

  returnDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const returnDate = new Date(control.value.jsdate);
    const departureControl = this.travelForm?.get('departureDate');
    
    if (departureControl?.value) {
      const departureDate = new Date(departureControl.value.jsdate);
      
      // Return date must be after departure date
      if (returnDate <= departureDate) {
        return { beforeDeparture: true };
      }
      
      // Trip cannot exceed 30 days
      const diffTime = returnDate.getTime() - departureDate.getTime();
      const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
      
      if (diffDays > 30) {
        return { tripTooLong: true };
      }
    }
    
    return null;
  }

  onDepartureDateChange(event: any) {
    this.updateReturnDateOptions();
  }

  private updateReturnDateOptions() {
    const departureDate = this.travelForm.get('departureDate')?.value;
    
    if (departureDate) {
      this.returnDateOptions = {
        ...this.returnDateOptions,
        disableUntil: {
          year: departureDate.date.year,
          month: departureDate.date.month,
          day: departureDate.date.day
        }
      };
    }
  }

  getDepartureDateError(): string | null {
    const control = this.travelForm.get('departureDate');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Departure date is required';
    }
    
    if (control.errors?.['tooSoon']) {
      return 'Departure must be at least 1 day in advance';
    }
    
    if (control.errors?.['tooFarAhead']) {
      return 'Cannot book more than 1 year in advance';
    }
    
    return null;
  }

  getReturnDateError(): string | null {
    const control = this.travelForm.get('returnDate');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Return date is required';
    }
    
    if (control.errors?.['beforeDeparture']) {
      return 'Return date must be after departure date';
    }
    
    if (control.errors?.['tripTooLong']) {
      return 'Trip duration cannot exceed 30 days';
    }
    
    return null;
  }

  onSubmit() {
    this.submitted = true;
    if (this.travelForm.valid) {
      console.log('Travel booked:', this.travelForm.value);
    }
  }
}

Business Rules Validation

@Component({
  template: `
    <form [formGroup]="businessForm">
      <div class="space-y-4">
        <div>
          <label>Meeting Date *</label>
          <my-date-picker 
            formControlName="meetingDate"
            [options]="businessDateOptions"
            placeholder="Select meeting date">
          </my-date-picker>
          
          <div *ngIf="getMeetingDateError()" class="mt-1 text-sm text-red-600">
            {{ getMeetingDateError() }}
          </div>
        </div>

        <div>
          <label>Project Deadline *</label>
          <my-date-picker 
            formControlName="deadline"
            [options]="deadlineOptions"
            placeholder="Select deadline">
          </my-date-picker>
          
          <div *ngIf="getDeadlineError()" class="mt-1 text-sm text-red-600">
            {{ getDeadlineError() }}
          </div>
        </div>
      </div>
    </form>
  `
})
export class BusinessRulesValidationExample {
  businessForm = this.fb.group({
    meetingDate: ['', [Validators.required, this.businessDateValidator.bind(this)]],
    deadline: ['', [Validators.required, this.deadlineValidator.bind(this)]]
  });

  // Company holidays
  private holidays: IMyDate[] = [
    { year: 2024, month: 1, day: 1 },   // New Year
    { year: 2024, month: 7, day: 4 },   // Independence Day  
    { year: 2024, month: 11, day: 28 }, // Thanksgiving
    { year: 2024, month: 12, day: 25 }  // Christmas
  ];

  businessDateOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true,
    disableWeekends: true,
    disableDates: this.holidays,
    markCurrentDay: true
  };

  deadlineOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true,
    markCurrentDay: true,
    showTodayBtn: false
  };

  submitted = false;

  businessDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const selectedDate = new Date(control.value.jsdate);
    const dayOfWeek = selectedDate.getDay();
    
    // Check for weekends (already disabled in UI, but validate)
    if (dayOfWeek === 0 || dayOfWeek === 6) {
      return { weekendNotAllowed: true };
    }
    
    // Check for holidays
    const isHoliday = this.holidays.some(holiday => 
      holiday.year === control.value.date.year &&
      holiday.month === control.value.date.month &&
      holiday.day === control.value.date.day
    );
    
    if (isHoliday) {
      return { holidayNotAllowed: true };
    }
    
    // Must be at least 2 business days in advance
    const today = new Date();
    const businessDaysAhead = this.calculateBusinessDays(today, selectedDate);
    
    if (businessDaysAhead < 2) {
      return { insufficientAdvanceNotice: true };
    }
    
    return null;
  }

  deadlineValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const deadline = new Date(control.value.jsdate);
    const today = new Date();
    
    // Minimum 1 week for project completion
    const minDeadline = new Date(today);
    minDeadline.setDate(today.getDate() + 7);
    
    if (deadline < minDeadline) {
      return { deadlineTooSoon: true };
    }
    
    // Maximum 1 year for project planning
    const maxDeadline = new Date(today);
    maxDeadline.setFullYear(today.getFullYear() + 1);
    
    if (deadline > maxDeadline) {
      return { deadlineTooFar: true };
    }
    
    return null;
  }

  private calculateBusinessDays(startDate: Date, endDate: Date): number {
    let count = 0;
    const current = new Date(startDate);
    
    while (current < endDate) {
      const dayOfWeek = current.getDay();
      if (dayOfWeek !== 0 && dayOfWeek !== 6) { // Not weekend
        // Check if it's not a holiday
        const isHoliday = this.holidays.some(holiday => 
          holiday.year === current.getFullYear() &&
          holiday.month === current.getMonth() + 1 &&
          holiday.day === current.getDate()
        );
        
        if (!isHoliday) {
          count++;
        }
      }
      current.setDate(current.getDate() + 1);
    }
    
    return count;
  }

  getMeetingDateError(): string | null {
    const control = this.businessForm.get('meetingDate');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Meeting date is required';
    }
    
    if (control.errors?.['weekendNotAllowed']) {
      return 'Meetings cannot be scheduled on weekends';
    }
    
    if (control.errors?.['holidayNotAllowed']) {
      return 'Meetings cannot be scheduled on company holidays';
    }
    
    if (control.errors?.['insufficientAdvanceNotice']) {
      return 'Meetings must be scheduled at least 2 business days in advance';
    }
    
    return null;
  }

  getDeadlineError(): string | null {
    const control = this.businessForm.get('deadline');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Project deadline is required';
    }
    
    if (control.errors?.['deadlineTooSoon']) {
      return 'Project deadline must be at least 1 week from now';
    }
    
    if (control.errors?.['deadlineTooFar']) {
      return 'Project deadline cannot be more than 1 year away';
    }
    
    return null;
  }
}

Age and Legal Date Validation

@Component({
  template: `
    <form [formGroup]="registrationForm">
      <div class="space-y-4">
        <div>
          <label>Birth Date *</label>
          <my-date-picker 
            formControlName="birthDate"
            [options]="birthDateOptions"
            placeholder="Enter your birth date">
          </my-date-picker>
          
          <div *ngIf="getBirthDateError()" class="mt-1 text-sm text-red-600">
            {{ getBirthDateError() }}
          </div>
          
          <div *ngIf="getAge() > 0" class="mt-1 text-sm text-gray-600">
            Age: {{ getAge() }} years old
          </div>
        </div>

        <div>
          <label>License Expiry Date *</label>
          <my-date-picker 
            formControlName="licenseExpiry"
            [options]="licenseOptions"
            placeholder="License expiry date">
          </my-date-picker>
          
          <div *ngIf="getLicenseError()" class="mt-1 text-sm text-red-600">
            {{ getLicenseError() }}
          </div>
        </div>
      </div>
    </form>
  `
})
export class AgeLegalValidationExample {
  registrationForm = this.fb.group({
    birthDate: ['', [Validators.required, this.birthDateValidator.bind(this)]],
    licenseExpiry: ['', [Validators.required, this.licenseExpiryValidator.bind(this)]]
  });

  birthDateOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disableFuture: true,
    maxYear: new Date().getFullYear(),
    minYear: 1920,
    markCurrentDay: true
  };

  licenseOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    disablePast: true,
    markCurrentDay: true
  };

  submitted = false;

  birthDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const birthDate = new Date(control.value.jsdate);
    const today = new Date();
    
    // Future date check
    if (birthDate > today) {
      return { futureDate: true };
    }
    
    // Age calculation
    let age = today.getFullYear() - birthDate.getFullYear();
    const monthDiff = today.getMonth() - birthDate.getMonth();
    
    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
      age--;
    }
    
    // Minimum age validation (13 for social media, 18 for adult services, etc.)
    if (age < 13) {
      return { tooYoung: true };
    }
    
    // Maximum reasonable age
    if (age > 120) {
      return { unrealisticAge: true };
    }
    
    // Driving age validation (if this is for a driving-related service)
    if (age < 16) {
      return { belowDrivingAge: true };
    }
    
    return null;
  }

  licenseExpiryValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const expiryDate = new Date(control.value.jsdate);
    const today = new Date();
    
    // Must be valid (not expired)
    if (expiryDate <= today) {
      return { licenseExpired: true };
    }
    
    // Should have at least 30 days remaining
    const daysUntilExpiry = Math.ceil((expiryDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
    
    if (daysUntilExpiry < 30) {
      return { expiringSoon: true };
    }
    
    // Reasonable maximum (licenses don't last more than 10 years typically)
    const maxDate = new Date(today);
    maxDate.setFullYear(today.getFullYear() + 10);
    
    if (expiryDate > maxDate) {
      return { unrealisticExpiry: true };
    }
    
    return null;
  }

  getAge(): number {
    const birthDateControl = this.registrationForm.get('birthDate');
    
    if (!birthDateControl?.value) return 0;
    
    const birthDate = new Date(birthDateControl.value.jsdate);
    const today = new Date();
    
    let age = today.getFullYear() - birthDate.getFullYear();
    const monthDiff = today.getMonth() - birthDate.getMonth();
    
    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
      age--;
    }
    
    return age;
  }

  getBirthDateError(): string | null {
    const control = this.registrationForm.get('birthDate');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'Birth date is required';
    }
    
    if (control.errors?.['futureDate']) {
      return 'Birth date cannot be in the future';
    }
    
    if (control.errors?.['tooYoung']) {
      return 'You must be at least 13 years old to register';
    }
    
    if (control.errors?.['belowDrivingAge']) {
      return 'You must be at least 16 years old for this service';
    }
    
    if (control.errors?.['unrealisticAge']) {
      return 'Please enter a valid birth date';
    }
    
    return null;
  }

  getLicenseError(): string | null {
    const control = this.registrationForm.get('licenseExpiry');
    
    if (!control || (!control.touched && !this.submitted)) return null;
    
    if (control.errors?.['required']) {
      return 'License expiry date is required';
    }
    
    if (control.errors?.['licenseExpired']) {
      return 'License has already expired';
    }
    
    if (control.errors?.['expiringSoon']) {
      return 'License expires too soon (minimum 30 days required)';
    }
    
    if (control.errors?.['unrealisticExpiry']) {
      return 'Please enter a valid license expiry date';
    }
    
    return null;
  }
}

Common Validation Patterns

Cross-Field Date Validation

// Validator that compares two date fields
export function dateRangeValidator(startFieldName: string, endFieldName: string) {
  return (formGroup: AbstractControl): ValidationErrors | null => {
    const startControl = formGroup.get(startFieldName);
    const endControl = formGroup.get(endFieldName);
    
    if (!startControl?.value || !endControl?.value) {
      return null;
    }
    
    const startDate = new Date(startControl.value.jsdate);
    const endDate = new Date(endControl.value.jsdate);
    
    if (startDate >= endDate) {
      return { dateRangeInvalid: { startField: startFieldName, endField: endFieldName } };
    }
    
    return null;
  };
}

// Usage in form group
this.form = this.fb.group({
  startDate: ['', Validators.required],
  endDate: ['', Validators.required]
}, {
  validators: [dateRangeValidator('startDate', 'endDate')]
});

Dynamic Date Restrictions

// Method to dynamically update date picker options
updateDateRestrictions(baseDate: Date, component: 'start' | 'end') {
  if (component === 'end') {
    this.endDateOptions = {
      ...this.endDateOptions,
      disableUntil: {
        year: baseDate.getFullYear(),
        month: baseDate.getMonth() + 1,
        day: baseDate.getDate() - 1
      }
    };
  }
}

🔧 API Reference

MyDatePickerComponent

Inputs

| Property | Type | Default | Description | |----------|------|---------|-------------| | options | IMyDpOptions | {} | Configuration options | | locale | string | 'en' | Locale for date formatting | | defaultMonth | string | - | Default month to show (yyyy-mm) | | placeholder | string | - | Input placeholder text | | selector | string | - | CSS selector for input styling |

Outputs

| Event | Type | Description | |-------|------|-------------| | dateChanged | EventEmitter<IMyDateModel> | Date selection changed | | inputFieldChanged | EventEmitter<IMyInputFieldChanged> | Input field value changed | | calendarViewChanged | EventEmitter<IMyCalendarViewChanged> | Calendar view changed | | calendarToggle | EventEmitter<number> | Calendar opened/closed | | inputFocusBlur | EventEmitter<IMyInputFocusBlur> | Input focus/blur events |

IMyDpOptions Interface

interface IMyDpOptions {
  // Date format and display
  dateFormat?: string;              // Date format (e.g., 'dd.mm.yyyy', 'yyyy-mm-dd')
  monthLabels?: IMyMonthLabels;     // Month labels
  dayLabels?: IMyDayLabels;         // Day labels
  dayLabelsFull?: IMyDayLabels;     // Full day labels
  
  // Calendar behavior
  firstDayOfWeek?: string;          // First day of week ('su', 'mo', etc.)
  satHighlight?: boolean;           // Highlight Saturdays
  sunHighlight?: boolean;           // Highlight Sundays
  highlightDates?: IMyDate[];       // Dates to highlight
  markCurrentDay?: boolean;         // Mark current day
  markCurrentMonth?: boolean;       // Mark current month
  markCurrentYear?: boolean;        // Mark current year
  
  // Date restrictions
  disableUntil?: IMyDate;          // Disable dates until
  disableSince?: IMyDate;          // Disable dates since
  disableDates?: IMyDate[];        // Specific dates to disable
  disableDateRanges?: IMyDateRange[]; // Date ranges to disable
  disableWeekends?: boolean;        // Disable weekends
  disableFuture?: boolean;         // Disable future dates
  disablePast?: boolean;           // Disable past dates
  
  // Year and month limits
  minYear?: number;                // Minimum selectable year
  maxYear?: number;                // Maximum selectable year
  
  // UI elements
  showTodayBtn?: boolean;          // Show today button
  showClearBtn?: boolean;          // Show clear button
  showCloseBtn?: boolean;          // Show close button
  todayBtnTxt?: string;            // Today button text
  clearBtnTxt?: string;            // Clear button text
  closeBtnTxt?: string;            // Close button text
  
  // Calendar size and position
  width?: string;                  // Calendar width
  height?: string;                 // Calendar height
  selectorHeight?: string;         // Input height
  selectorWidth?: string;          // Input width
  
  // Animation and timing
  openSelectorTopOfInput?: boolean; // Open calendar above input
  showSelectorArrow?: boolean;     // Show calendar arrow
  alignSelectorRight?: boolean;    // Align calendar to right
  
  // Inline mode
  inline?: boolean;                // Display as inline calendar
  showDateFormatPlaceholder?: boolean; // Show format placeholder
  
  // Validation
  allowDeselectDate?: boolean;     // Allow deselecting current date
  
  // Styling
  stylesData?: IMyStyles;          // Custom styles
  divHostElement?: IMyDivHostElement; // Host element styling
}

Date Model Interface

interface IMyDateModel {
  date: IMyDate;          // Selected date object
  jsdate?: Date;          // JavaScript Date object
  formatted: string;      // Formatted date string
  epoc: number;           // Unix timestamp
}

interface IMyDate {
  year: number;           // Year (e.g., 2024)
  month: number;          // Month (1-12)
  day: number;            // Day (1-31)
}

🎯 Advanced Examples

Date Range Selection

@Component({
  template: `
    <div class="form-row">
      <div class="col-md-6">
        <label>Start Date</label>
        <my-date-picker 
          [(ngModel)]="startDate"
          [options]="startDateOptions"
          placeholder="Select start date">
        </my-date-picker>
      </div>
      
      <div class="col-md-6">
        <label>End Date</label>
        <my-date-picker 
          [(ngModel)]="endDate"
          [options]="endDateOptions"
          placeholder="Select end date">
        </my-date-picker>
      </div>
    </div>
    
    <div class="mt-3" *ngIf="startDate && endDate">
      Duration: {{ calculateDuration() }} days
    </div>
  `
})
export class DateRangeComponent {
  startDate: any = null;
  endDate: any = null;
  
  startDateOptions: IMyDpOptions = {
    dateFormat: 'yyyy-mm-dd',
    disablePast: true,
    showTodayBtn: true
  };
  
  endDateOptions: IMyDpOptions = {
    dateFormat: 'yyyy-mm-dd',
    disablePast: true,
    showTodayBtn: true
  };
  
  ngOnInit() {
    // Update end date options when start date changes
    this.updateEndDateRestrictions();
  }
  
  onStartDateChanged() {
    this.updateEndDateRestrictions();
    // Clear end date if it's before start date
    if (this.endDate && this.startDate && 
        new Date(this.endDate.jsdate) < new Date(this.startDate.jsdate)) {
      this.endDate = null;
    }
  }
  
  private updateEndDateRestrictions() {
    if (this.startDate) {
      this.endDateOptions = {
        ...this.endDateOptions,
        disableUntil: {
          year: this.startDate.date.year,
          month: this.startDate.date.month,
          day: this.startDate.date.day - 1
        }
      };
    }
  }
  
  calculateDuration(): number {
    if (!this.startDate || !this.endDate) return 0;
    
    const start = new Date(this.startDate.jsdate);
    const end = new Date(this.endDate.jsdate);
    const diffTime = Math.abs(end.getTime() - start.getTime());
    return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
  }
}

Custom Styling and Themes

@Component({
  template: `
    <my-date-picker 
      [options]="customOptions"
      [(ngModel)]="selectedDate"
      placeholder="Select date">
    </my-date-picker>
  `
})
export class CustomStyledComponent {
  selectedDate: any = null;
  
  customOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    firstDayOfWeek: 'mo',
    sunHighlight: true,
    markCurrentDay: true,
    stylesData: {
      selector: 'dp1',
      styles: `
        .dp1 .myDpSelector {
          border: 2px solid #007bff;
          border-radius: 8px;
          box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        
        .dp1 .myDpSelectorArrow:after {
          border-bottom-color: #007bff;
        }
        
        .dp1 .myDpDayCurrMonth:hover,
        .dp1 .myDpMonthLabel:hover,
        .dp1 .myDpYearLabel:hover {
          background-color: #e3f2fd;
          color: #1976d2;
        }
        
        .dp1 .myDpSelectedDay {
          background-color: #007bff;
          color: white;
          border-radius: 50%;
        }
        
        .dp1 .myDpHeaderBtn {
          background-color: #007bff;
          color: white;
          border-radius: 4px;
        }
        
        .dp1 .myDpFooterBtn {
          background-color: #6c757d;
          color: white;
          border-radius: 4px;
          margin: 2px;
        }
        
        .dp1 .myDpFooterBtn:hover {
          background-color: #495057;
        }
      `
    }
  };
}

Localization Example

@Component({
  template: `
    <div class="form-group">
      <label>Language</label>
      <select [(ngModel)]="currentLocale" (change)="changeLocale()" class="form-control">
        <option value="en">English</option>
        <option value="es">Español</option>
        <option value="fr">Français</option>
        <option value="de">Deutsch</option>
      </select>
    </div>
    
    <div class="form-group">
      <label>{{ getLabel('selectDate') }}</label>
      <my-date-picker 
        [options]="localizedOptions"
        [(ngModel)]="selectedDate"
        [placeholder]="getLabel('placeholder')">
      </my-date-picker>
    </div>
  `
})
export class LocalizationComponent {
  selectedDate: any = null;
  currentLocale = 'en';
  localizedOptions: IMyDpOptions = {};
  
  private locales = {
    en: {
      monthLabels: { 1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
                    7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec' },
      dayLabels: { su: 'Sun', mo: 'Mon', tu: 'Tue', we: 'Wed', th: 'Thu', fr: 'Fri', sa: 'Sat' },
      todayBtnTxt: 'Today',
      clearBtnTxt: 'Clear',
      closeBtnTxt: 'Close',
      selectDate: 'Select Date',
      placeholder: 'Click to select date'
    },
    es: {
      monthLabels: { 1: 'Ene', 2: 'Feb', 3: 'Mar', 4: 'Abr', 5: 'May', 6: 'Jun',
                    7: 'Jul', 8: 'Ago', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dic' },
      dayLabels: { su: 'Dom', mo: 'Lun', tu: 'Mar', we: 'Mié', th: 'Jue', fr: 'Vie', sa: 'Sáb' },
      todayBtnTxt: 'Hoy',
      clearBtnTxt: 'Limpiar',
      closeBtnTxt: 'Cerrar',
      selectDate: 'Seleccionar Fecha',
      placeholder: 'Haz clic para seleccionar fecha'
    },
    // Add more locales...
  };
  
  ngOnInit() {
    this.changeLocale();
  }
  
  changeLocale() {
    const locale = this.locales[this.currentLocale as keyof typeof this.locales];
    
    this.localizedOptions = {
      dateFormat: 'dd/mm/yyyy',
      firstDayOfWeek: 'mo',
      markCurrentDay: true,
      monthLabels: locale.monthLabels,
      dayLabels: locale.dayLabels,
      todayBtnTxt: locale.todayBtnTxt,
      clearBtnTxt: locale.clearBtnTxt,
      closeBtnTxt: locale.closeBtnTxt
    };
  }
  
  getLabel(key: string): string {
    const locale = this.locales[this.currentLocale as keyof typeof this.locales];
    return locale[key as keyof typeof locale];
  }
}

Disabled Dates and Validation

@Component({
  template: `
    <form [formGroup]="form">
      <div class="form-group">
        <label>Appointment Date</label>
        <my-date-picker 
          formControlName="appointmentDate"
          [options]="appointmentOptions"
          placeholder="Select appointment date">
        </my-date-picker>
        
        <div *ngIf="form.get('appointmentDate')?.invalid && form.get('appointmentDate')?.touched" 
             class="text-danger">
          <div *ngIf="form.get('appointmentDate')?.hasError('required')">
            Appointment date is required
          </div>
          <div *ngIf="form.get('appointmentDate')?.hasError('weekendNotAllowed')">
            Weekend appointments are not available
          </div>
          <div *ngIf="form.get('appointmentDate')?.hasError('holidayNotAllowed')">
            Holiday appointments are not available
          </div>
        </div>
      </div>
      
      <small class="form-text text-muted">
        * Appointments are available Monday-Friday, excluding holidays
      </small>
    </form>
  `
})
export class DisabledDatesComponent {
  form = this.fb.group({
    appointmentDate: ['', [Validators.required, this.appointmentDateValidator.bind(this)]]
  });

  // Define holidays
  private holidays: IMyDate[] = [
    { year: 2024, month: 1, day: 1 },   // New Year
    { year: 2024, month: 7, day: 4 },   // Independence Day
    { year: 2024, month: 12, day: 25 }  // Christmas
  ];

  appointmentOptions: IMyDpOptions = {
    dateFormat: 'dd/mm/yyyy',
    firstDayOfWeek: 'mo',
    disablePast: true,
    disableWeekends: true,
    disableDates: this.holidays,
    markCurrentDay: true,
    showTodayBtn: false,
    sunHighlight: false,
    satHighlight: false
  };

  constructor(private fb: FormBuilder) {}

  appointmentDateValidator(control: AbstractControl): ValidationErrors | null {
    if (!control.value) return null;
    
    const selectedDate = control.value;
    const jsDate = new Date(selectedDate.jsdate);
    
    // Check if it's a weekend
    if (jsDate.getDay() === 0 || jsDate.getDay() === 6) {
      return { weekendNotAllowed: true };
    }
    
    // Check if it's a holiday
    const isHoliday = this.holidays.some(holiday => 
      holiday.year === selectedDate.date.year &&
      holiday.month === selectedDate.date.month &&
      holiday.day === selectedDate.date.day
    );
    
    if (isHoliday) {
      return { holidayNotAllowed: true };
    }
    
    return null;
  }
}

Inline Calendar

@Component({
  template: `
    <div class="row">
      <div class="col-md-6">
        <h5>Inline Calendar</h5>
        <my-date-picker 
          [options]="inlineOptions"
          [(ngModel)]="selectedDate">
        </my-date-picker>
      </div>
      
      <div class="col-md-6">
        <h5>Selected Date Information</h5>
        <div *ngIf="selectedDate" class="card">
          <div class="card-body">
            <p><strong>Date:</strong> {{ selectedDate.formatted }}</p>
            <p><strong>Day of Week:</strong> {{ getDayOfWeek() }}</p>
            <p><strong>Week Number:</strong> {{ getWeekNumber() }}</p>
            <p><strong>Days from Today:</strong> {{ getDaysFromToday() }}</p>
          </div>
        </div>
        
        <div *ngIf="!selectedDate" class="alert alert-info">
          Please select a date from the calendar
        </div>
      </div>
    </div>
  `
})
export class InlineCalendarComponent {
  selectedDate: any = null;
  
  inlineOptions: IMyDpOptions = {
    dateFormat: 'dd.mm.yyyy',
    firstDayOfWeek: 'mo',
    inline: true,
    showTodayBtn: true,
    showClearBtn: true,
    markCurrentDay: true,
    sunHighlight: true,
    width: '100%'
  };
  
  getDayOfWeek(): string {
    if (!this.selectedDate) return '';
    const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    return days[new Date(this.selectedDate.jsdate).getDay()];
  }
  
  getWeekNumber(): number {
    if (!this.selectedDate) return 0;
    const date = new Date(this.selectedDate.jsdate);
    const firstDay = new Date(date.getFullYear(), 0, 1);
    return Math.ceil(((date.getTime() - firstDay.getTime()) / 86400000 + firstDay.getDay() + 1) / 7);
  }
  
  getDaysFromToday(): number {
    if (!this.selectedDate) return 0;
    const today = new Date();
    const selected = new Date(this.selectedDate.jsdate);
    const diffTime = selected.getTime() - today.getTime();
    return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
  }
}

🎨 Styling & Theming

CSS Classes

// Custom date picker styles
.my-date-picker {
  .myDpSelector {
    border: 2px solid #007bff;
    border-radius: 8px;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    
    &:focus {
      outline: none;
      box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
    }
  }
  
  .myDpCalendar {
    border-radius: 8px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    overflow: hidden;
    
    .myDpHeader {
      background: linear-gradient(135deg, #007bff, #0056b3);
      color: white;
      
      .myDpHeaderBtn {
        color: white;
        background: transparent;
        
        &:hover {
          background: rgba(255, 255, 255, 0.2);
        }
      }
    }
    
    .myDpWeekDays {
      background: #f8f9fa;
      font-weight: 600;
      text-transform: uppercase;
      font-size: 0.75rem;
      color: #6c757d;
    }
    
    .myDpDays {
      .myDpDayCurrMonth {
        transition: all 0.2s ease;
        
        &:hover {
          background: #e3f2fd;
          color: #1976d2;
          transform: scale(1.1);
        }
      }
      
      .myDpSelectedDay {
        background: #007bff !important;
        color: white !important;
        border-radius: 50%;
        font-weight: bold;
      }
      
      .myDpCurrDay {
        background: #ffc107;
        color: #212529;
        border-radius: 50%;
        font-weight: bold;
      }
      
      .myDpSunHighlight {
        color: #dc3545;
      }
      
      .myDpSatHighlight {
        color: #fd7e14;
      }
    }
    
    .myDpFooter {
      background: #f8f9fa;
      border-top: 1px solid #dee2e6;
      
      .myDpFooterBtn {
        background: #6c757d;
        color: white;
        border-radius: 4px;
        transition: background 0.2s ease;
        
        &:hover {
          background: #495057;
        }
        
        &.today-btn {
          background: #28a745;
          
          &:hover {
            background: #1e7e34;
          }
        }
        
        &.clear-btn {
          background: #dc3545;
          
          &:hover {
            background: #c82333;
          }
        }
      }
    }
  }
}

Dark Theme

.dark-theme .my-date-picker {
  .myDpSelector {
    background: #343a40;
    border-color: #6c757d;
    color: #ffffff;
  }
  
  .myDpCalendar {
    background: #495057;
    color: #ffffff;
    
    .myDpHeader {
      background: linear-gradient(135deg, #6f42c1, #563d7c);
    }
    
    .myDpWeekDays {
      background: #6c757d;
      color: #adb5bd;
    }
    
    .myDpDays {
      .myDpDayCurrMonth {
        &:hover {
          background: #6f42c1;
          color: white;
        }
      }
      
      .myDpSelectedDay {
        background: #6f42c1 !important;
      }
    }
    
    .myDpFooter {
      background: #6c757d;
      border-top-color: #495057;
    }
  }
}

🧪 Testing

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyDatePickerModule } from '@ng-vui/date-picker';
import { FormsModule } from '@angular/forms';

describe('MyDatePickerComponent', () => {
  let component: TestComponent;
  let fixture: ComponentFixture<TestComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [TestComponent],
      imports: [MyDatePickerModule, FormsModule]
    });
    
    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
  });

  it('should render date picker', () => {
    fixture.detectChanges();
    
    const datePicker = fixture.nativeElement.querySelector('my-date-picker');
    expect(datePicker).toBeTruthy();
  });

  it('should open calendar on input click', () => {
    fixture.detectChanges();
    
    const input = fixture.nativeElement.querySelector('.myDpSelector');
    input.click();
    
    fixture.detectChanges();
    
    const calendar = fixture.nativeElement.querySelector('.myDpCalendar');
    expect(calendar).toBeTruthy();
  });

  it('should select date', () => {
    spyOn(component, 'onDateChanged');
    
    fixture.detectChanges();
    
    // Open calendar and click on a date
    const input = fixture.nativeElement.querySelector('.myDpSelector');
    input.click();
    fixture.detectChanges();
    
    const dateCell = fixture.nativeElement.querySelector('.myDpDayCurrMonth');
    dateCell.click();
    
    expect(component.onDateChanged).toHaveBeenCalled();
  });
});

@Component({
  template: `
    <my-date-picker 
      [options]="options" 
      [(ngModel)]="selectedDate"
      (dateChanged)="onDateChanged($event)">
    </my-date-picker>
  `
})
class TestComponent {
  selectedDate: any = null;
  options = { dateFormat: 'dd.mm.yyyy' };
  
  onDateChanged(event: any) {
    console.log('Date changed:', event);
  }
}

🚀 Performance Tips

  1. Use OnPush change detection for better performance:
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})
  1. Limit date ranges for better calendar rendering:
options = {
  minYear: 2020,
  maxYear: 2030
};
  1. Use trackBy functions in templates with date arrays

🤝 Contributing

  1. Fork the repository
  2. Create your feature branch: git checkout -b feature/my-feature
  3. Commit your changes: git commit -am 'Add my feature'
  4. Push to the branch: git push origin feature/my-feature
  5. Submit a pull request

📄 License

MIT © VUI

🔗 Related Packages

  • ng-vui-select-input - Single select dropdown
  • ng-vui-grid - Data grid component
  • ng-vui-multi-select - Multi-select dropdown component
  • ng-vui-text-input - Text input component
  • ng-vui-textarea - Textarea component
  • ng-vui-auto-complete - Auto complete component