UNPKG

ngx-persian-datepicker-element

Version:

Angular wrapper for Persian DatePicker Web Component with signal-based inputs (English/Persian docs)

491 lines (484 loc) 20.2 kB
import 'persian-datepicker-element/dist/persian-datepicker-element.min.js'; import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { inject, ElementRef, NgZone, Injector, signal, input, EventEmitter, effect, forwardRef, ViewChild, Output, CUSTOM_ELEMENTS_SCHEMA, Component, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; /** * Angular wrapper for the Persian DatePicker Web Component * * This component provides Angular bindings for the native Persian DatePicker web component, * allowing it to be used with Angular forms (both reactive and template-driven) and * custom event handling. * * Note: The component automatically imports and registers the persian-datepicker-element * web component, so you don't need to add any scripts to your angular.json file. * * @example * Basic usage: * ```html * <ngx-persian-datepicker-element * placeholderInput="انتخاب تاریخ" * formatInput="YYYY/MM/DD" * [showEventsInput]="true" * (dateChange)="onDateChange($event)"> * </ngx-persian-datepicker-element> * ``` * * With Angular forms: * ```html * <form [formGroup]="myForm"> * <ngx-persian-datepicker-element formControlName="date"></ngx-persian-datepicker-element> * </form> * ``` */ class NgxPersianDatepickerComponent { // Dependency injection using inject() function elRef = inject(ElementRef); zone = inject(NgZone); injector = inject(Injector); // Web component reference - expose it to be accessed from parent components elementSignal = signal(null); // Form control implementation as signals onChangeSignal = signal(() => { }); onTouchSignal = signal(() => { }); valueSignal = signal(null); disabledSignal = signal(false); // #region Input Signals placeholder = input(undefined); format = input(undefined); showEvents = input(undefined); eventTypes = input(undefined); rtl = input(undefined); minDate = input(undefined); maxDate = input(undefined); disabledDates = input(undefined); rangeMode = input(undefined); rangeStart = input(undefined); rangeEnd = input(undefined); defaultDate = input(undefined); // #endregion // #region Outputs dateChange = new EventEmitter(); // #endregion /** Reference to the container where the web component will be attached */ containerRef; constructor() { // Create an effect to respond to changes in the element signal effect(() => { const element = this.elementSignal(); if (element) { // Apply initial attributes and styles when the element is created this.setInitialAttributes(); } }); // Create effects for each input to update attributes when they change this.setupInputEffects(); } /** * Apply theme variables to the datepicker element * @param variables Object containing CSS variable names and values */ applyThemeVariables(variables) { const element = this.elementSignal(); if (!element) return; for (const [prop, value] of Object.entries(variables)) { element.style.setProperty(prop, value); } } /** * Set up effects for all inputs to update attributes/styles when they change */ setupInputEffects() { // Placeholder effect(() => { const value = this.placeholder(); if (value !== undefined) { this.updateAttribute('placeholder', value); } }); // Format effect(() => { const value = this.format(); if (value !== undefined) { this.updateAttribute('format', value); } }); // Show Holidays effect(() => { const value = this.showEvents(); if (value !== undefined) { this.updateAttribute('show-holidays', String(value)); } }); // Holiday Types effect(() => { const value = this.eventTypes(); if (value !== undefined) { this.updateeventTypesAttribute(value); } }); // RTL effect(() => { const value = this.rtl(); if (value !== undefined) { this.updateAttribute('rtl', String(value)); } }); // Range picker effects effect(() => { const value = this.rangeMode(); if (value !== undefined) { this.updateAttribute('range-mode', String(value)); } }); effect(() => { const value = this.rangeStart(); if (value !== undefined) { this.updateAttribute('range-start', JSON.stringify(value)); } }); effect(() => { const value = this.rangeEnd(); if (value !== undefined) { this.updateAttribute('range-end', JSON.stringify(value)); } }); // Add effects for new inputs effect(() => { const value = this.minDate(); if (value !== undefined) { this.updateAttribute('min-date', JSON.stringify(value)); } }); effect(() => { const value = this.maxDate(); if (value !== undefined) { this.updateAttribute('max-date', JSON.stringify(value)); } }); effect(() => { const value = this.disabledDates(); if (value !== undefined) { if (typeof value === 'function') { const element = this.elementSignal(); if (element && 'setDisabledDatesFn' in element) { element.setDisabledDatesFn(value); } } else { this.updateAttribute('disabled-dates', value); } } }); effect(() => { const value = this.defaultDate(); if (value !== undefined) { this.updateAttribute('default-date', JSON.stringify(value)); } }); } ngOnInit() { // Create the web component instance this.createWebComponent(); } /** * Create the web component instance */ createWebComponent() { // Create the element this.zone.runOutsideAngular(() => { try { // Make sure the web component is registered // This will typically happen from the import, but we check just in case if (typeof window !== 'undefined' && !customElements.get('persian-datepicker-element')) { console.warn('persian-datepicker-element not found in custom elements registry. The import should have registered it.'); } // Create the web component const element = document.createElement('persian-datepicker-element'); // Add the change event listener element.addEventListener('change', this.handleChangeEvent); // Store the element in the signal this.elementSignal.set(element); // Append to the container if (this.containerRef && this.containerRef.nativeElement) { this.containerRef.nativeElement.appendChild(element); } else { this.elRef.nativeElement.appendChild(element); } } catch (error) { console.error('Error creating persian-datepicker-element:', error); } }); } // Helper method to update attributes updateAttribute(name, value) { const element = this.elementSignal(); if (element && value !== undefined) { element.setAttribute(name, value); } } // Helper method specifically for holiday types updateeventTypesAttribute(value) { const element = this.elementSignal(); if (element && value !== undefined) { const formattedValue = Array.isArray(value) ? value.join(',') : value; element.setAttribute('holiday-types', formattedValue); } } ngOnDestroy() { // Clean up event listeners const element = this.elementSignal(); if (element) { element.removeEventListener('change', this.handleChangeEvent); } } /** * Handle change events from the web component */ handleChangeEvent = (event) => { // Use NgZone to ensure the change is detected by Angular this.zone.run(() => { // Get the selected date from the event detail const { jalali, gregorian, isHoliday, events, isRange, range } = event.detail; // Emit the dateChange event this.dateChange.emit({ jalali, gregorian, isHoliday, events, isRange, range }); // Update form control value this.valueSignal.set(jalali); this.onChangeSignal()(jalali); this.onTouchSignal()(); }); }; /** * Set initial attributes on the web component based on the signals */ setInitialAttributes() { const element = this.elementSignal(); if (!element) return; // Set attributes based on input signals const placeholder = this.placeholder(); if (placeholder !== undefined) { element.setAttribute('placeholder', placeholder); } const format = this.format(); if (format !== undefined) { element.setAttribute('format', format); } const showEvents = this.showEvents(); if (showEvents !== undefined) { element.setAttribute('show-holidays', String(showEvents)); } const eventTypes = this.eventTypes(); if (eventTypes !== undefined) { this.updateeventTypesAttribute(eventTypes); } const rtl = this.rtl(); if (rtl !== undefined) { element.setAttribute('rtl', String(rtl)); } const minDate = this.minDate(); if (minDate !== undefined) { element.setAttribute('min-date', JSON.stringify(minDate)); } const maxDate = this.maxDate(); if (maxDate !== undefined) { element.setAttribute('max-date', JSON.stringify(maxDate)); } const disabledDates = this.disabledDates(); if (disabledDates !== undefined) { if (typeof disabledDates === 'function') { element.setDisabledDatesFn(disabledDates); } else { element.setAttribute('disabled-dates', disabledDates); } } const rangeMode = this.rangeMode(); if (rangeMode !== undefined) { element.setAttribute('range-mode', String(rangeMode)); } const rangeStart = this.rangeStart(); if (rangeStart !== undefined) { element.setAttribute('range-start', JSON.stringify(rangeStart)); } const rangeEnd = this.rangeEnd(); if (rangeEnd !== undefined) { element.setAttribute('range-end', JSON.stringify(rangeEnd)); } const defaultDate = this.defaultDate(); if (defaultDate !== undefined) { element.setAttribute('default-date', JSON.stringify(defaultDate)); } // Set disabled state if (this.disabledSignal()) { element.setAttribute('disabled', ''); } // Set initial value if needed if (this.valueSignal()) { this.writeValue(this.valueSignal()); } } // #region ControlValueAccessor Implementation /** * Write value to the component (used by Angular forms) */ writeValue(value) { this.valueSignal.set(value); const element = this.elementSignal(); if (element && value) { // Set the value on the web component const [year, month, day] = value; element.setValue(year, month, day); } else if (element) { // Clear the value element.clear(); } } /** * Register change callback (used by Angular forms) */ registerOnChange(fn) { this.onChangeSignal.set(fn); } /** * Register touch callback (used by Angular forms) */ registerOnTouched(fn) { this.onTouchSignal.set(fn); } /** * Set disabled state (used by Angular forms) */ setDisabledState(isDisabled) { this.disabledSignal.set(isDisabled); const element = this.elementSignal(); if (element) { if (isDisabled) { element.setAttribute('disabled', ''); } else { element.removeAttribute('disabled'); } } } // #endregion // Add range picker methods setRange(start, end) { const element = this.elementSignal(); if (element) { element.setRange(start, end); } } getRange() { const element = this.elementSignal(); if (element) { return element.getRange() || { start: null, end: null }; } return { start: null, end: null }; } clear() { const element = this.elementSignal(); if (element) { element.clear(); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxPersianDatepickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.15", type: NgxPersianDatepickerComponent, isStandalone: true, selector: "ngx-persian-datepicker-element", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, showEvents: { classPropertyName: "showEvents", publicName: "showEvents", isSignal: true, isRequired: false, transformFunction: null }, eventTypes: { classPropertyName: "eventTypes", publicName: "eventTypes", isSignal: true, isRequired: false, transformFunction: null }, rtl: { classPropertyName: "rtl", publicName: "rtl", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, rangeMode: { classPropertyName: "rangeMode", publicName: "rangeMode", isSignal: true, isRequired: false, transformFunction: null }, rangeStart: { classPropertyName: "rangeStart", publicName: "rangeStart", isSignal: true, isRequired: false, transformFunction: null }, rangeEnd: { classPropertyName: "rangeEnd", publicName: "rangeEnd", isSignal: true, isRequired: false, transformFunction: null }, defaultDate: { classPropertyName: "defaultDate", publicName: "defaultDate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dateChange: "dateChange" }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgxPersianDatepickerComponent), multi: true } ], viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }], ngImport: i0, template: '<div #container></div>', isInline: true, styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxPersianDatepickerComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-persian-datepicker-element', standalone: true, imports: [CommonModule, FormsModule, ReactiveFormsModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], template: '<div #container></div>', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgxPersianDatepickerComponent), multi: true } ], styles: [":host{display:block;width:100%}\n"] }] }], ctorParameters: () => [], propDecorators: { dateChange: [{ type: Output }], containerRef: [{ type: ViewChild, args: ['container', { static: true }] }] } }); // Import is handled by index.ts to ensure it's only registered once // The comment is kept for documentation purposes /** * Module for the Persian DatePicker Angular wrapper component. * * This module can be imported in traditional Angular applications that use modules. * For standalone components, you can import the NgxPersianDatepickerComponent directly. * * The persian-datepicker web component is bundled directly with this package, so you * don't need to add any extra scripts to your angular.json file. * * @example * ```typescript * // In your app.module.ts or feature module * import { NgxPersianDatepickerModule } from 'ngx-persian-datepicker-element'; * * @NgModule({ * imports: [ * // ... other imports * NgxPersianDatepickerModule * ], * // ... declarations, providers, etc. * }) * export class AppModule { } * ``` */ class NgxPersianDatepickerModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxPersianDatepickerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.15", ngImport: i0, type: NgxPersianDatepickerModule, imports: [CommonModule, FormsModule, ReactiveFormsModule, NgxPersianDatepickerComponent // Import as a standalone component ], exports: [NgxPersianDatepickerComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxPersianDatepickerModule, imports: [CommonModule, FormsModule, ReactiveFormsModule, NgxPersianDatepickerComponent // Import as a standalone component ] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: NgxPersianDatepickerModule, decorators: [{ type: NgModule, args: [{ declarations: [], imports: [ CommonModule, FormsModule, ReactiveFormsModule, NgxPersianDatepickerComponent // Import as a standalone component ], exports: [ NgxPersianDatepickerComponent ], schemas: [CUSTOM_ELEMENTS_SCHEMA] // Required for using custom elements in Angular templates }] }] }); // Register the web component first // Use a more explicit import path to help bundlers resolve the package /* * Public API Surface of ngx-persian-datepicker-element */ /** * Generated bundle index. Do not edit. */ export { NgxPersianDatepickerComponent, NgxPersianDatepickerModule }; //# sourceMappingURL=ngx-persian-datepicker-element.mjs.map