django-npdatetime 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,1381 @@
1
+ import init, { NepaliDate } from "./pkg/npdatetime_wasm.js";
2
+
3
+ export class NepaliDatePicker {
4
+ static initialized = false;
5
+ static instances = new Map();
6
+ static daysCache = new Map();
7
+
8
+ constructor(element, options = {}) {
9
+ if (typeof element === "string") {
10
+ element = document.querySelector(element);
11
+ }
12
+
13
+ if (!element) {
14
+ throw new Error("Invalid element provided to NepaliDatePicker");
15
+ }
16
+
17
+ this.input = element;
18
+ this.id = `npd-${Math.random().toString(36).substr(2, 9)}`;
19
+
20
+ this.options = {
21
+ mode: (element.dataset.mode || options.mode || "BS").toUpperCase(),
22
+ language: (
23
+ element.dataset.language ||
24
+ options.language ||
25
+ "en"
26
+ ).toLowerCase(),
27
+ format: options.format || "%Y-%m-%d",
28
+ minDate: options.minDate || null,
29
+ maxDate: options.maxDate || null,
30
+ disabledDates: options.disabledDates || [],
31
+ theme: element.dataset.theme || options.theme || "auto",
32
+ position: options.position || "auto",
33
+ closeOnSelect: options.closeOnSelect !== false,
34
+ showTodayButton: options.showTodayButton !== false,
35
+ showClearButton: options.showClearButton !== false,
36
+ onChange: options.onChange || null,
37
+ onOpen: options.onOpen || null,
38
+ onClose: options.onClose || null,
39
+ ...options,
40
+ };
41
+
42
+ this.selectedDate = null;
43
+ this.rangeStart = null;
44
+ this.rangeEnd = null;
45
+
46
+ const now = new Date();
47
+ this.selectedTime = {
48
+ hour:
49
+ options.defaultHour !== undefined
50
+ ? options.defaultHour
51
+ : now.getHours(),
52
+ minute:
53
+ options.defaultMinute !== undefined
54
+ ? options.defaultMinute
55
+ : now.getMinutes(),
56
+ };
57
+ this.viewDate = { year: 2081, month: 1 };
58
+ this.viewMode = "days";
59
+ this.isOpen = false;
60
+ this.switchRequest = null;
61
+ this.renderRequest = null;
62
+
63
+ this.init();
64
+ NepaliDatePicker.instances.set(element, this);
65
+ }
66
+
67
+ async init() {
68
+ if (!NepaliDatePicker.initialized) {
69
+ await init();
70
+ NepaliDatePicker.initialized = true;
71
+ }
72
+
73
+ this.setupInput();
74
+ this.createPicker();
75
+ this.attachEvents();
76
+ this.parseInitialValue();
77
+
78
+ if (!this.selectedDate) {
79
+ this.setDetailsFromToday();
80
+ }
81
+ }
82
+
83
+ setupInput() {
84
+ this.input.setAttribute("autocomplete", "off");
85
+ this.input.setAttribute("data-npd-id", this.id);
86
+ this.input.classList.add("npd-input");
87
+
88
+ if (!this.input.placeholder) {
89
+ this.input.placeholder =
90
+ this.options.mode === "BS" ? "Select Nepali Date" : "Select Date";
91
+ }
92
+ }
93
+
94
+ createPicker() {
95
+ const picker = document.createElement("div");
96
+ picker.className = "npd-picker";
97
+ picker.id = this.id;
98
+ picker.setAttribute("role", "dialog");
99
+ picker.setAttribute("aria-modal", "true");
100
+ picker.innerHTML = `
101
+ <div class="npd-header">
102
+ <button type="button" class="npd-title" aria-label="Change view">
103
+ <span class="npd-title-text"></span>
104
+ <svg class="npd-title-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
105
+ <polyline points="6 9 12 15 18 9"></polyline>
106
+ </svg>
107
+ </button>
108
+ <div class="npd-nav">
109
+ <button type="button" class="npd-nav-btn npd-prev" aria-label="Previous">
110
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
111
+ <polyline points="15 18 9 12 15 6"></polyline>
112
+ </svg>
113
+ </button>
114
+ <button type="button" class="npd-nav-btn npd-next" aria-label="Next">
115
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
116
+ <polyline points="9 18 15 12 9 6"></polyline>
117
+ </svg>
118
+ </button>
119
+ </div>
120
+ </div>
121
+
122
+ <div class="npd-body">
123
+ <div class="npd-view npd-view-days"></div>
124
+ <div class="npd-view npd-view-months"></div>
125
+ <div class="npd-view npd-view-years"></div>
126
+ </div>
127
+
128
+ <div class="npd-footer">
129
+ <div class="npd-mode-toggle">
130
+ <button type="button" class="npd-mode-btn ${this.options.mode === "BS" ? "active" : ""}" data-mode="BS">
131
+ <span>BS</span>
132
+ </button>
133
+ <button type="button" class="npd-mode-btn ${this.options.mode === "AD" ? "active" : ""}" data-mode="AD">
134
+ <span>AD</span>
135
+ </button>
136
+ </div>
137
+ <div class="npd-actions">
138
+ ${this.options.showClearButton ? '<button type="button" class="npd-btn npd-clear">Clear</button>' : ""}
139
+ <button type="button" class="npd-btn npd-yesterday">Yesterday</button>
140
+ ${this.options.showTodayButton ? '<button type="button" class="npd-btn npd-today">Today</button>' : ""}
141
+ <button type="button" class="npd-btn npd-tomorrow">Tomorrow</button>
142
+ </div>
143
+
144
+ <div class="npd-time-picker">
145
+ <div class="npd-time-field">
146
+ <label>Time</label>
147
+ <div class="npd-time-inputs">
148
+ <input type="number" class="npd-time-input npd-hour" min="0" max="23" value="${this.selectedTime.hour}" placeholder="HH">
149
+ <span>:</span>
150
+ <input type="number" class="npd-time-input npd-minute" min="0" max="59" value="${this.selectedTime.minute}" placeholder="mm">
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ `;
156
+
157
+ document.body.appendChild(picker);
158
+ this.picker = picker;
159
+ this.elements = {
160
+ title: picker.querySelector(".npd-title-text"),
161
+ daysView: picker.querySelector(".npd-view-days"),
162
+ monthsView: picker.querySelector(".npd-view-months"),
163
+ yearsView: picker.querySelector(".npd-view-years"),
164
+ };
165
+
166
+ this.createTooltip(); // Create tooltip element
167
+ }
168
+
169
+ createTooltip() {
170
+ this.tooltip = document.createElement("div");
171
+ this.tooltip.className = "npd-tooltip";
172
+ this.picker.appendChild(this.tooltip);
173
+ this.longPressTimer = null;
174
+ }
175
+
176
+ showTooltip(element, text) {
177
+ if (!this.tooltip || !text) return;
178
+ this.tooltip.textContent = text;
179
+
180
+ // Position tooltip
181
+ const rect = element.getBoundingClientRect();
182
+ const pickerRect = this.picker.getBoundingClientRect();
183
+
184
+ // Calculate relative position within picker
185
+ const top = rect.top - pickerRect.top - this.tooltip.offsetHeight - 8;
186
+ const left =
187
+ rect.left - pickerRect.left + (rect.width - this.tooltip.offsetWidth) / 2;
188
+
189
+ this.tooltip.style.top = `${top}px`;
190
+ this.tooltip.style.left = `${left}px`;
191
+ this.tooltip.classList.add("active");
192
+ }
193
+
194
+ hideTooltip() {
195
+ if (this.tooltip) {
196
+ this.tooltip.classList.remove("active");
197
+ }
198
+ }
199
+
200
+ attachEvents() {
201
+ this.input.addEventListener("focus", () => this.open());
202
+ this.input.addEventListener("click", () => this.open());
203
+ this.input.addEventListener("keydown", (e) => this.handleInputKeydown(e));
204
+ this.input.addEventListener("input", (e) => this.handleInputChange(e));
205
+ this.input.addEventListener("blur", () => this.handleInputBlur());
206
+
207
+ this.picker
208
+ .querySelector(".npd-title")
209
+ .addEventListener("click", () => this.changeViewMode());
210
+ this.picker
211
+ .querySelector(".npd-prev")
212
+ .addEventListener("click", () => this.navigate(-1));
213
+ this.picker
214
+ .querySelector(".npd-next")
215
+ .addEventListener("click", () => this.navigate(1));
216
+
217
+ this.picker.querySelectorAll(".npd-mode-btn").forEach((btn) => {
218
+ btn.addEventListener("click", () => this.switchMode(btn.dataset.mode));
219
+ });
220
+
221
+ if (this.options.showTodayButton) {
222
+ this.picker
223
+ .querySelector(".npd-today")
224
+ .addEventListener("click", () => this.selectToday());
225
+ }
226
+
227
+ if (this.options.showClearButton) {
228
+ this.picker
229
+ .querySelector(".npd-clear")
230
+ .addEventListener("click", () => this.clear());
231
+ }
232
+
233
+ this.picker
234
+ .querySelector(".npd-yesterday")
235
+ .addEventListener("click", () => this.selectYesterday());
236
+ this.picker
237
+ .querySelector(".npd-tomorrow")
238
+ .addEventListener("click", () => this.selectTomorrow());
239
+
240
+ this.picker.querySelector(".npd-hour").addEventListener("input", (e) => {
241
+ let val = parseInt(e.target.value);
242
+ if (isNaN(val)) val = 0;
243
+ if (val < 0) val = 0;
244
+ if (val > 23) val = 23;
245
+ this.selectedTime.hour = val;
246
+ this.updateInput();
247
+ });
248
+
249
+ this.picker.querySelector(".npd-minute").addEventListener("input", (e) => {
250
+ let val = parseInt(e.target.value);
251
+ if (isNaN(val)) val = 0;
252
+ if (val < 0) val = 0;
253
+ if (val > 59) val = 59;
254
+ this.selectedTime.minute = val;
255
+ this.updateInput();
256
+ });
257
+
258
+ document.addEventListener("click", (e) => {
259
+ if (!this.picker.contains(e.target) && e.target !== this.input) {
260
+ this.close();
261
+ }
262
+ });
263
+
264
+ window.addEventListener("resize", () => {
265
+ if (this.isOpen) this.position();
266
+ });
267
+
268
+ window.addEventListener(
269
+ "scroll",
270
+ () => {
271
+ if (this.isOpen) this.position();
272
+ },
273
+ true,
274
+ );
275
+ }
276
+
277
+ handleInputKeydown(e) {
278
+ if (e.key === "Escape") {
279
+ this.close();
280
+ } else if (e.key === "Enter") {
281
+ e.preventDefault();
282
+ this.open();
283
+ }
284
+ }
285
+
286
+ handleInputChange(e) {
287
+ // Prevent infinite loop if the event was triggered by our own updateInput
288
+ if (e.isTrusted === false && e.detail?.origin === "datepicker") return;
289
+
290
+ const value = e.target.value.trim();
291
+ if (!value) {
292
+ this.selectedDate = null;
293
+ this.render();
294
+ return;
295
+ }
296
+
297
+ try {
298
+ const [datePart, timePart] = value.split(" ");
299
+ let year, month, day;
300
+
301
+ // Try YYYY-MM-DD
302
+ if (datePart.includes("-")) {
303
+ [year, month, day] = datePart.split("-").map(Number);
304
+ } else if (datePart.length === 10) {
305
+ // Handle 2081/01/01 if user types slashes, though format option usually controls this
306
+ // For now let's assume standard format matches options.
307
+ // But simple split is safest for demo.
308
+ // If manual typing, we should be forgiving or strict.
309
+ // Let's reuse the parsing logic slightly.
310
+ [year, month, day] = datePart
311
+ .replace(/\//g, "-")
312
+ .split("-")
313
+ .map(Number);
314
+ }
315
+
316
+ if (!year || !month || !day) return; // Incomplete
317
+
318
+ // Parse Time
319
+ if (timePart) {
320
+ const [h, m] = timePart.split(":").map(Number);
321
+ if (!isNaN(h)) this.selectedTime.hour = h;
322
+ if (!isNaN(m)) this.selectedTime.minute = m;
323
+ }
324
+
325
+ let newDate;
326
+ if (this.options.mode === "BS") {
327
+ newDate = new NepaliDate(year, month, day);
328
+ } else {
329
+ newDate = NepaliDate.fromGregorian(year, month, day);
330
+ }
331
+
332
+ this.selectedDate = newDate;
333
+ this.viewDate = {
334
+ year: this.selectedDate.year,
335
+ month: this.selectedDate.month,
336
+ };
337
+
338
+ // Update time inputs in picker UI
339
+ const hourInput = this.picker.querySelector(".npd-hour");
340
+ const minInput = this.picker.querySelector(".npd-minute");
341
+ if (hourInput) hourInput.value = this.selectedTime.hour;
342
+ if (minInput) minInput.value = this.selectedTime.minute;
343
+
344
+ this.render();
345
+ } catch (err) {
346
+ // incomplete or invalid date, ignore until valid
347
+ }
348
+ }
349
+
350
+ parseInitialValue() {
351
+ const value = this.input.value.trim();
352
+ if (!value) return;
353
+
354
+ try {
355
+ // Split date and time
356
+ const [datePart, timePart] = value.split(" ");
357
+
358
+ let year, month, day;
359
+ if (datePart.includes("-")) {
360
+ [year, month, day] = datePart.split("-").map(Number);
361
+ } else {
362
+ // fallback or other format? assume YYYY-MM-DD for now
363
+ [year, month, day] = datePart.split("-").map(Number);
364
+ }
365
+
366
+ if (timePart) {
367
+ const [h, m] = timePart.split(":").map(Number);
368
+ this.selectedTime = {
369
+ hour: isNaN(h) ? 0 : h,
370
+ minute: isNaN(m) ? 0 : m,
371
+ };
372
+ }
373
+
374
+ if (this.options.mode === "BS") {
375
+ this.selectedDate = new NepaliDate(year, month, day);
376
+ } else {
377
+ this.selectedDate = NepaliDate.fromGregorian(year, month, day);
378
+ }
379
+ this.viewDate = {
380
+ year: this.selectedDate.year,
381
+ month: this.selectedDate.month,
382
+ };
383
+ } catch (e) {
384
+ console.warn("Invalid initial date value:", value);
385
+ }
386
+ }
387
+
388
+ setDetailsFromToday() {
389
+ try {
390
+ const today = NepaliDate.today();
391
+ if (this.options.mode === "BS") {
392
+ this.viewDate = { year: today.year, month: today.month };
393
+ } else {
394
+ const [y, m] = today.toGregorian();
395
+ this.viewDate = { year: y, month: m };
396
+ }
397
+ } catch (e) {
398
+ console.error("Failed to set default today date:", e);
399
+ }
400
+ }
401
+
402
+ open() {
403
+ if (this.isOpen) return;
404
+
405
+ this.isOpen = true;
406
+ this.picker.classList.add("active");
407
+ this.position();
408
+ this.render();
409
+
410
+ if (this.options.onOpen) {
411
+ this.options.onOpen(this);
412
+ }
413
+ }
414
+
415
+ close() {
416
+ if (!this.isOpen) return;
417
+
418
+ this.isOpen = false;
419
+ this.picker.classList.remove("active");
420
+
421
+ if (this.options.onClose) {
422
+ this.options.onClose(this);
423
+ }
424
+ }
425
+
426
+ position() {
427
+ const inputRect = this.input.getBoundingClientRect();
428
+ const pickerHeight = this.picker.offsetHeight || 400;
429
+ const spaceBelow = window.innerHeight - inputRect.bottom;
430
+ const spaceAbove = inputRect.top;
431
+
432
+ this.picker.style.left = `${inputRect.left}px`;
433
+ // Set picker width with a reasonable max-width instead of matching input width
434
+ // Min: 320px, Max: 400px to prevent overly wide pickers on full-width inputs
435
+ this.picker.style.width = `${Math.min(Math.max(inputRect.width, 320), 400)}px`;
436
+
437
+ if (
438
+ this.options.position === "top" ||
439
+ (this.options.position === "auto" &&
440
+ spaceBelow < pickerHeight &&
441
+ spaceAbove > spaceBelow)
442
+ ) {
443
+ this.picker.style.bottom = `${window.innerHeight - inputRect.top + 8}px`;
444
+ this.picker.style.top = "auto";
445
+ this.picker.classList.add("npd-position-top");
446
+ } else {
447
+ this.picker.style.top = `${inputRect.bottom + 8}px`;
448
+ this.picker.style.bottom = "auto";
449
+ this.picker.classList.remove("npd-position-top");
450
+ }
451
+ }
452
+
453
+ switchMode(mode) {
454
+ if (this.options.mode === mode) return;
455
+
456
+ // pending switch
457
+ if (this.switchRequest) {
458
+ cancelAnimationFrame(this.switchRequest);
459
+ }
460
+
461
+ this.switchRequest = requestAnimationFrame(() => {
462
+ this._performSwitchMode(mode);
463
+ this.switchRequest = null;
464
+ });
465
+ }
466
+
467
+ _performSwitchMode(mode) {
468
+ if (this.options.mode === mode) return;
469
+
470
+ this.options.mode = mode;
471
+ this.picker.querySelectorAll(".npd-mode-btn").forEach((btn) => {
472
+ btn.classList.toggle("active", btn.dataset.mode === mode);
473
+ });
474
+
475
+ try {
476
+ if (this.selectedDate) {
477
+ if (mode === "AD") {
478
+ const [y, m] = this.selectedDate.toGregorian();
479
+ this.viewDate = { year: y, month: m };
480
+ } else {
481
+ this.viewDate = {
482
+ year: this.selectedDate.year,
483
+ month: this.selectedDate.month,
484
+ };
485
+ }
486
+ } else {
487
+ // If no date is selected, convert the current view date
488
+ try {
489
+ if (mode === "AD") {
490
+ // BS -> AD: Use Day 15 to avoid backward drift (Day 1 usually maps to previous month)
491
+ const bsDate = new NepaliDate(
492
+ this.viewDate.year,
493
+ this.viewDate.month,
494
+ 15,
495
+ );
496
+ const [y, m] = bsDate.toGregorian();
497
+ this.viewDate = { year: y, month: m };
498
+ } else {
499
+ // AD -> BS: Use Day 15
500
+ const bsDate = NepaliDate.fromGregorian(
501
+ this.viewDate.year,
502
+ this.viewDate.month,
503
+ 15,
504
+ );
505
+ this.viewDate = { year: bsDate.year, month: bsDate.month };
506
+ }
507
+ } catch (e) {
508
+ console.error("Failed to convert view date on mode switch:", e);
509
+ this.setDetailsFromToday();
510
+ }
511
+ }
512
+ } catch (err) {
513
+ console.error("Switch mode error:", err);
514
+ this.setDetailsFromToday();
515
+ }
516
+
517
+ this.render();
518
+ }
519
+
520
+ changeViewMode() {
521
+ const modes = ["days", "months", "years"];
522
+ const currentIndex = modes.indexOf(this.viewMode);
523
+ this.viewMode = modes[(currentIndex + 1) % modes.length];
524
+ this.render();
525
+ }
526
+
527
+ navigate(direction) {
528
+ if (this.viewMode === "days") {
529
+ this.viewDate.month += direction;
530
+ if (this.viewDate.month > 12) {
531
+ this.viewDate.month = 1;
532
+ this.viewDate.year++;
533
+ } else if (this.viewDate.month < 1) {
534
+ this.viewDate.month = 12;
535
+ this.viewDate.year--;
536
+ }
537
+ } else if (this.viewMode === "months") {
538
+ this.viewDate.year += direction;
539
+ } else {
540
+ this.viewDate.year += direction * 12;
541
+ }
542
+ this.render();
543
+ }
544
+
545
+ render() {
546
+ if (this.renderRequest) {
547
+ cancelAnimationFrame(this.renderRequest);
548
+ }
549
+
550
+ this.renderRequest = requestAnimationFrame(() => {
551
+ this._performRender();
552
+ this.renderRequest = null;
553
+ });
554
+ }
555
+
556
+ _performRender() {
557
+ this.picker
558
+ .querySelectorAll(".npd-view")
559
+ .forEach((v) => v.classList.remove("active"));
560
+
561
+ if (this.viewMode === "days") {
562
+ this.renderDays();
563
+ } else if (this.viewMode === "months") {
564
+ this.renderMonths();
565
+ } else {
566
+ this.renderYears();
567
+ }
568
+ }
569
+
570
+ renderDays() {
571
+ let months;
572
+ if (this.options.mode === "BS") {
573
+ months =
574
+ this.options.language === "np"
575
+ ? [
576
+ "बैशाख",
577
+ "जेठ",
578
+ "असार",
579
+ "साउन",
580
+ "भदौ",
581
+ "असोज",
582
+ "कात्तिक",
583
+ "मंसिर",
584
+ "पुस",
585
+ "माघ",
586
+ "फागुन",
587
+ "चैत",
588
+ ]
589
+ : [
590
+ "Baisakh",
591
+ "Jestha",
592
+ "Ashadh",
593
+ "Shrawan",
594
+ "Bhadra",
595
+ "Ashwin",
596
+ "Kartik",
597
+ "Mangshir",
598
+ "Poush",
599
+ "Magh",
600
+ "Falgun",
601
+ "Chaitra",
602
+ ];
603
+ } else {
604
+ months = [
605
+ "January",
606
+ "February",
607
+ "March",
608
+ "April",
609
+ "May",
610
+ "June",
611
+ "July",
612
+ "August",
613
+ "September",
614
+ "October",
615
+ "November",
616
+ "December",
617
+ ];
618
+ }
619
+
620
+ const weekdays =
621
+ this.options.language === "np"
622
+ ? ["आइत", "सोम", "मंगल", "बुध", "बिहि", "शुक्र", "शनि"]
623
+ : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
624
+
625
+ if (this.options.mode === "BS") {
626
+ const year =
627
+ this.options.language === "np"
628
+ ? this.toNepaliNum(this.viewDate.year)
629
+ : this.viewDate.year;
630
+ this.elements.title.textContent = `${months[this.viewDate.month - 1]} ${year}`;
631
+ } else {
632
+ const date = new Date(this.viewDate.year, this.viewDate.month - 1);
633
+ this.elements.title.textContent = date.toLocaleDateString("en-US", {
634
+ month: "long",
635
+ year: "numeric",
636
+ });
637
+ }
638
+
639
+ let html = '<div class="npd-days-grid">';
640
+ weekdays.forEach((day, index) => {
641
+ let isHoliday = false;
642
+ if (this.options.mode === "BS") {
643
+ isHoliday = index === 6; // Saturday
644
+ } else {
645
+ isHoliday = index === 0; // Sunday
646
+ }
647
+ html += `<div class="npd-weekday ${isHoliday ? "holiday" : ""}">${day}</div>`;
648
+ });
649
+
650
+ if (this.options.mode === "BS") {
651
+ const firstDate = new NepaliDate(
652
+ this.viewDate.year,
653
+ this.viewDate.month,
654
+ 1,
655
+ );
656
+ const [gy, gm, gd] = firstDate.toGregorian();
657
+ const startWeekday = new Date(gy, gm - 1, gd).getDay();
658
+ const daysInMonth = this.getDaysInMonth(
659
+ this.viewDate.year,
660
+ this.viewDate.month,
661
+ );
662
+
663
+ const prevMonth =
664
+ this.viewDate.month === 1 ? 12 : this.viewDate.month - 1;
665
+ const prevYear =
666
+ this.viewDate.month === 1 ? this.viewDate.year - 1 : this.viewDate.year;
667
+ const daysInPrevMonth = this.getDaysInMonth(prevYear, prevMonth);
668
+
669
+ for (let i = startWeekday - 1; i >= 0; i--) {
670
+ const day = daysInPrevMonth - i;
671
+ const currentWeekday = (startWeekday - 1 - i) % 7;
672
+ // Actually, we are counting down.
673
+ // startWeekday is the first day of CURRENT month.
674
+ // So the day before is startWeekday-1.
675
+ // If startWeekday is 0 (Sunday), loops runs for i=-1 which is false? No startWeekday=0 means loop doesn't run.
676
+ // If startWeekday is 3 (Wed), i runs 2,1,0.
677
+ // i=2: day before start
678
+ // Wait, startWeekday is 0..6.
679
+ // The cell index for "day before start" is `startWeekday - 1`.
680
+ // The cell index relative to 0 is `startWeekday - 1 - i`?
681
+ // Let's count forward positions in the grid.
682
+ // The grid fills 0 to startWeekday-1 with prev month.
683
+ // So cell position is `startWeekday - 1 - i`.
684
+ // If startWeekday is 3 (Wed, index 3). i=2. 3-1-2 = 0 (Sunday). Correct.
685
+ // i=1. 3-1-1 = 1 (Monday).
686
+ // i=0. 3-1-0 = 2 (Tuesday).
687
+
688
+ // Wait, startWeekday comes from .getDay(). 0=Sun, 6=Sat.
689
+ // In BS mode, holiday is 6 (Sat).
690
+ // So we just check if the cell position % 7 === 6.
691
+
692
+ const cellIndex = startWeekday - 1 - i;
693
+ const isHoliday = cellIndex % 7 === 6;
694
+
695
+ const dayText =
696
+ this.options.language === "np" ? this.toNepaliNum(day) : day;
697
+ html += `<button type="button" class="npd-day npd-overflow ${isHoliday ? "holiday" : ""}" data-day="${day}" data-month-offset="-1">${dayText}</button>`;
698
+ }
699
+
700
+ const todayBS = NepaliDate.today();
701
+ const isCurrentYear = this.viewDate.year === todayBS.year;
702
+ const isCurrentMonth = this.viewDate.month === todayBS.month;
703
+
704
+ for (let day = 1; day <= daysInMonth; day++) {
705
+ let isSelected = false;
706
+ let isRangeStart = false;
707
+ let isRangeEnd = false;
708
+ let isInRange = false;
709
+
710
+ const currentDayDate = new NepaliDate(
711
+ this.viewDate.year,
712
+ this.viewDate.month,
713
+ day,
714
+ );
715
+
716
+ if (this.options.isRange) {
717
+ isRangeStart = this.isSameDate(currentDayDate, this.rangeStart);
718
+ isRangeEnd = this.isSameDate(currentDayDate, this.rangeEnd);
719
+ isInRange = this.isDateInRange(currentDayDate);
720
+ isSelected = isRangeStart || isRangeEnd;
721
+ } else {
722
+ isSelected =
723
+ this.selectedDate?.year === this.viewDate.year &&
724
+ this.selectedDate?.month === this.viewDate.month &&
725
+ this.selectedDate?.day === day;
726
+ }
727
+
728
+ const isToday = isCurrentYear && isCurrentMonth && day === todayBS.day;
729
+
730
+ const currentWeekday = (startWeekday + day - 1) % 7;
731
+ const isHoliday = currentWeekday === 6; // Saturday in Nepal
732
+
733
+ const dayText =
734
+ this.options.language === "np" ? this.toNepaliNum(day) : day;
735
+
736
+ let classes = `npd-day ${isSelected ? "selected" : ""} ${isToday ? "today" : ""} ${isHoliday ? "holiday" : ""}`;
737
+ if (this.options.isRange) {
738
+ if (isRangeStart) classes += " range-start";
739
+ if (isRangeEnd) classes += " range-end";
740
+ if (isInRange) classes += " in-range";
741
+ if (isRangeStart && !this.rangeEnd) classes += " range-only";
742
+ }
743
+
744
+ const fullDate =
745
+ this.options.mode == "BS"
746
+ ? `${currentDayDate.year}-${String(currentDayDate.month).padStart(2, "0")}-${String(currentDayDate.day).padStart(2, "0")}`
747
+ : `${currentDayDate.year}-${String(currentDayDate.month).padStart(2, "0")}-${String(currentDayDate.day).padStart(2, "0")}`;
748
+
749
+ let details = fullDate;
750
+ try {
751
+ if (typeof currentDayDate.tithi === "function") {
752
+ const tithi = currentDayDate.tithi();
753
+ if (tithi) {
754
+ details += `\n${tithi}`;
755
+ }
756
+ }
757
+ } catch (e) {
758
+ // Tithi calculation failed or feature disabled
759
+ }
760
+
761
+ html += `<button type="button" class="${classes}" data-day="${day}" data-month-offset="0" data-details="${details}">${dayText}</button>`;
762
+ }
763
+
764
+ // Next month overflow
765
+ const totalCells = 42;
766
+ const currentCells = startWeekday + daysInMonth;
767
+ for (let day = 1; day <= totalCells - currentCells; day++) {
768
+ const cellIndex = currentCells + day - 1;
769
+ const isHoliday = cellIndex % 7 === 6;
770
+
771
+ const dayText =
772
+ this.options.language === "np" ? this.toNepaliNum(day) : day;
773
+ html += `<button type="button" class="npd-day npd-overflow ${isHoliday ? "holiday" : ""}" data-day="${day}" data-month-offset="1">${dayText}</button>`;
774
+ }
775
+ } else {
776
+ const date = new Date(this.viewDate.year, this.viewDate.month - 1, 1);
777
+ const startWeekday = date.getDay();
778
+ const daysInMonth = new Date(
779
+ this.viewDate.year,
780
+ this.viewDate.month,
781
+ 0,
782
+ ).getDate();
783
+
784
+ const daysInPrevMonth = new Date(
785
+ this.viewDate.year,
786
+ this.viewDate.month - 1,
787
+ 0,
788
+ ).getDate();
789
+
790
+ for (let i = startWeekday - 1; i >= 0; i--) {
791
+ const cellIndex = startWeekday - 1 - i;
792
+ const isHoliday = cellIndex % 7 === 0; // Sunday logic for AD
793
+ const day = daysInPrevMonth - i;
794
+ html += `<button type="button" class="npd-day npd-overflow ${isHoliday ? "holiday" : ""}" data-day="${day}" data-month-offset="-1">${day}</button>`;
795
+ }
796
+
797
+ const [selY, selM, selD] = this.selectedDate
798
+ ? this.selectedDate.toGregorian()
799
+ : [null, null, null];
800
+
801
+ const today = new Date();
802
+ const isCurrentYear = this.viewDate.year === today.getFullYear();
803
+ const isCurrentMonth = this.viewDate.month === today.getMonth() + 1;
804
+
805
+ for (let day = 1; day <= daysInMonth; day++) {
806
+ let isSelected = false;
807
+ let isRangeStart = false;
808
+ let isRangeEnd = false;
809
+ let isInRange = false;
810
+
811
+ // Construct comparable object for AD (using existing helper logic or simple object)
812
+ // Since we store rangeStart/End as NepaliDate objects or wrappers?
813
+ // Wait, rangeStart is a NepaliDate object from selectDate.
814
+ // But in AD mode, selectDate creates a NepaliDate wrapper around the AD date via .fromGregorian().
815
+ // So comparison logic `isSameDate` works if `currentDayDate` is also a NepaliDate.
816
+
817
+ const currentAdDate = new Date(
818
+ this.viewDate.year,
819
+ this.viewDate.month - 1,
820
+ day,
821
+ );
822
+ const currentDayDate = NepaliDate.fromGregorian(
823
+ currentAdDate.getFullYear(),
824
+ currentAdDate.getMonth() + 1,
825
+ currentAdDate.getDate(),
826
+ );
827
+
828
+ if (this.options.isRange) {
829
+ isRangeStart = this.isSameDate(currentDayDate, this.rangeStart);
830
+ isRangeEnd = this.isSameDate(currentDayDate, this.rangeEnd);
831
+ isInRange = this.isDateInRange(currentDayDate);
832
+ isSelected = isRangeStart || isRangeEnd;
833
+ } else {
834
+ isSelected =
835
+ selY === this.viewDate.year &&
836
+ selM === this.viewDate.month &&
837
+ selD === day;
838
+ }
839
+
840
+ const isToday =
841
+ isCurrentYear && isCurrentMonth && day === today.getDate();
842
+
843
+ const currentWeekday = (startWeekday + day - 1) % 7;
844
+ const isHoliday = currentWeekday === 0; // Sunday for AD
845
+
846
+ let classes = `npd-day ${isSelected ? "selected" : ""} ${isToday ? "today" : ""} ${isHoliday ? "holiday" : ""}`;
847
+ if (this.options.isRange) {
848
+ if (isRangeStart) classes += " range-start";
849
+ if (isRangeEnd) classes += " range-end";
850
+ if (isInRange) classes += " in-range";
851
+ }
852
+
853
+ html += `<button type="button" class="${classes}" data-day="${day}" data-month-offset="0">${day}</button>`;
854
+ }
855
+
856
+ // Next month overflow
857
+ const totalCells = 42;
858
+ const currentCells = startWeekday + daysInMonth;
859
+ for (let day = 1; day <= totalCells - currentCells; day++) {
860
+ const cellIndex = currentCells + day - 1;
861
+ const isHoliday = cellIndex % 7 === 0; // Sunday for AD
862
+ html += `<button type="button" class="npd-day npd-overflow ${isHoliday ? "holiday" : ""}" data-day="${day}" data-month-offset="1">${day}</button>`;
863
+ }
864
+ }
865
+
866
+ html += "</div>";
867
+ this.elements.daysView.innerHTML = html;
868
+ this.elements.daysView.classList.add("active");
869
+
870
+ this.elements.daysView
871
+ .querySelectorAll(".npd-day[data-day]")
872
+ .forEach((btn) => {
873
+ btn.addEventListener("click", (e) => {
874
+ e.stopPropagation();
875
+ const day = parseInt(btn.dataset.day);
876
+ const offset = parseInt(btn.dataset.monthOffset || "0");
877
+
878
+ if (offset !== 0) {
879
+ let newMonth = this.viewDate.month + offset;
880
+ let newYear = this.viewDate.year;
881
+
882
+ if (newMonth > 12) {
883
+ newMonth = 1;
884
+ newYear++;
885
+ } else if (newMonth < 1) {
886
+ newMonth = 12;
887
+ newYear--;
888
+ }
889
+
890
+ this.viewDate = { year: newYear, month: newMonth };
891
+ this.render();
892
+ } else {
893
+ this.selectDate(day);
894
+ }
895
+ });
896
+
897
+ // Tooltip Events
898
+ this.elements.daysView
899
+ .querySelectorAll(".npd-day[data-details]")
900
+ .forEach((btn) => {
901
+ // Desktop Hover
902
+ btn.addEventListener("mouseenter", () => {
903
+ this.showTooltip(btn, btn.dataset.details);
904
+ });
905
+
906
+ btn.addEventListener("mouseleave", () => {
907
+ this.hideTooltip();
908
+ });
909
+
910
+ // Mobile Long Press
911
+ btn.addEventListener(
912
+ "touchstart",
913
+ (e) => {
914
+ // e.preventDefault(); // Don't block click
915
+ this.longPressTimer = setTimeout(() => {
916
+ this.showTooltip(btn, btn.dataset.details);
917
+ }, 500); // 500ms long press
918
+ },
919
+ { passive: true },
920
+ );
921
+
922
+ btn.addEventListener("touchend", () => {
923
+ if (this.longPressTimer) clearTimeout(this.longPressTimer);
924
+ this.hideTooltip();
925
+ });
926
+
927
+ btn.addEventListener("touchmove", () => {
928
+ if (this.longPressTimer) clearTimeout(this.longPressTimer);
929
+ this.hideTooltip();
930
+ });
931
+ });
932
+ });
933
+ }
934
+
935
+ renderMonths() {
936
+ let months;
937
+ if (this.options.mode === "BS") {
938
+ months =
939
+ this.options.language === "np"
940
+ ? [
941
+ "बैशाख",
942
+ "जेठ",
943
+ "असार",
944
+ "साउन",
945
+ "भदौ",
946
+ "असोज",
947
+ "कात्तिक",
948
+ "मंसिर",
949
+ "पुस",
950
+ "माघ",
951
+ "फागुन",
952
+ "चैत",
953
+ ]
954
+ : [
955
+ "Baisakh",
956
+ "Jestha",
957
+ "Ashadh",
958
+ "Shrawan",
959
+ "Bhadra",
960
+ "Ashwin",
961
+ "Kartik",
962
+ "Mangshir",
963
+ "Poush",
964
+ "Magh",
965
+ "Falgun",
966
+ "Chaitra",
967
+ ];
968
+ } else {
969
+ months = [
970
+ "January",
971
+ "February",
972
+ "March",
973
+ "April",
974
+ "May",
975
+ "June",
976
+ "July",
977
+ "August",
978
+ "September",
979
+ "October",
980
+ "November",
981
+ "December",
982
+ ];
983
+ }
984
+
985
+ const year =
986
+ this.options.language === "np"
987
+ ? this.toNepaliNum(this.viewDate.year)
988
+ : this.viewDate.year;
989
+ this.elements.title.textContent = year;
990
+
991
+ let html = '<div class="npd-months-grid">';
992
+ months.forEach((month, index) => {
993
+ const isSelected =
994
+ this.selectedDate?.year === this.viewDate.year &&
995
+ this.selectedDate?.month === index + 1;
996
+ html += `<button type="button" class="npd-month ${isSelected ? "selected" : ""}" data-month="${index + 1}">${month}</button>`;
997
+ });
998
+ html += "</div>";
999
+
1000
+ this.elements.monthsView.innerHTML = html;
1001
+ this.elements.monthsView.classList.add("active");
1002
+
1003
+ this.elements.monthsView.querySelectorAll(".npd-month").forEach((btn) => {
1004
+ btn.addEventListener("click", () => {
1005
+ this.viewDate.month = parseInt(btn.dataset.month);
1006
+ this.viewMode = "days";
1007
+ this.render();
1008
+ });
1009
+ });
1010
+ }
1011
+
1012
+ renderYears() {
1013
+ const startYear = Math.floor(this.viewDate.year / 12) * 12;
1014
+ this.elements.title.textContent = `${startYear} - ${startYear + 11}`;
1015
+
1016
+ let html = '<div class="npd-years-grid">';
1017
+ for (let i = 0; i < 12; i++) {
1018
+ const year = startYear + i;
1019
+ const isSelected = this.selectedDate?.year === year;
1020
+ const yearText =
1021
+ this.options.language === "np" ? this.toNepaliNum(year) : year;
1022
+ html += `<button type="button" class="npd-year ${isSelected ? "selected" : ""}" data-year="${year}">${yearText}</button>`;
1023
+ }
1024
+ html += "</div>";
1025
+
1026
+ this.elements.yearsView.innerHTML = html;
1027
+ this.elements.yearsView.classList.add("active");
1028
+
1029
+ this.elements.yearsView.querySelectorAll(".npd-year").forEach((btn) => {
1030
+ btn.addEventListener("click", () => {
1031
+ this.viewDate.year = parseInt(btn.dataset.year);
1032
+ this.viewMode = "months";
1033
+ this.render();
1034
+ });
1035
+ });
1036
+ }
1037
+
1038
+ isSameDate(d1, d2) {
1039
+ if (!d1 || !d2) return false;
1040
+ return d1.year === d2.year && d1.month === d2.month && d1.day === d2.day;
1041
+ }
1042
+
1043
+ compareDates(d1, d2) {
1044
+ if (!d1 || !d2) return 0;
1045
+ if (d1.year !== d2.year) return d1.year - d2.year;
1046
+ if (d1.month !== d2.month) return d1.month - d2.month;
1047
+ return d1.day - d2.day;
1048
+ }
1049
+
1050
+ isDateInRange(date) {
1051
+ if (!this.rangeStart || !this.rangeEnd || !date) return false;
1052
+ return (
1053
+ this.compareDates(date, this.rangeStart) > 0 &&
1054
+ this.compareDates(date, this.rangeEnd) < 0
1055
+ );
1056
+ }
1057
+
1058
+ selectDate(day) {
1059
+ try {
1060
+ let selected;
1061
+ if (this.options.mode === "BS") {
1062
+ selected = new NepaliDate(this.viewDate.year, this.viewDate.month, day);
1063
+ } else {
1064
+ selected = NepaliDate.fromGregorian(
1065
+ this.viewDate.year,
1066
+ this.viewDate.month,
1067
+ day,
1068
+ );
1069
+ }
1070
+
1071
+ if (this.options.isRange) {
1072
+ if (!this.rangeStart || (this.rangeStart && this.rangeEnd)) {
1073
+ this.rangeStart = selected;
1074
+ this.rangeEnd = null;
1075
+ } else {
1076
+ if (this.compareDates(selected, this.rangeStart) < 0) {
1077
+ this.rangeEnd = this.rangeStart;
1078
+ this.rangeStart = selected;
1079
+ } else {
1080
+ this.rangeEnd = selected;
1081
+ }
1082
+ if (this.options.closeOnSelect) {
1083
+ this.close();
1084
+ }
1085
+ }
1086
+
1087
+ // Update view to maintain visibility if range spans months?
1088
+ // Actually typically we don't jump view on second click unless necessary.
1089
+
1090
+ this.updateInput();
1091
+ this.render();
1092
+
1093
+ if (this.options.onChange) {
1094
+ this.options.onChange(
1095
+ { start: this.rangeStart, end: this.rangeEnd },
1096
+ this,
1097
+ );
1098
+ }
1099
+ } else {
1100
+ this.selectedDate = selected;
1101
+ this.updateInput();
1102
+ if (this.options.closeOnSelect) {
1103
+ this.close();
1104
+ } else {
1105
+ this.render();
1106
+ }
1107
+
1108
+ if (this.options.onChange) {
1109
+ this.options.onChange(this.selectedDate, this);
1110
+ }
1111
+ }
1112
+ } catch (e) {
1113
+ console.error("Invalid date selection:", e);
1114
+ }
1115
+ }
1116
+
1117
+ selectToday() {
1118
+ this.selectedDate = NepaliDate.today();
1119
+ this.viewDate = {
1120
+ year: this.selectedDate.year,
1121
+ month: this.selectedDate.month,
1122
+ };
1123
+ this.updateInput();
1124
+ this.close();
1125
+
1126
+ if (this.options.onChange) {
1127
+ this.options.onChange(this.selectedDate, this);
1128
+ }
1129
+ }
1130
+
1131
+ selectYesterday() {
1132
+ const today = NepaliDate.today();
1133
+ this.selectedDate = today.addDays(-1);
1134
+ this.viewDate = {
1135
+ year: this.selectedDate.year,
1136
+ month: this.selectedDate.month,
1137
+ };
1138
+ this.updateInput();
1139
+ this.close();
1140
+
1141
+ if (this.options.onChange) {
1142
+ this.options.onChange(this.selectedDate, this);
1143
+ }
1144
+ }
1145
+
1146
+ selectTomorrow() {
1147
+ const today = NepaliDate.today();
1148
+ this.selectedDate = today.addDays(1);
1149
+ this.viewDate = {
1150
+ year: this.selectedDate.year,
1151
+ month: this.selectedDate.month,
1152
+ };
1153
+ this.updateInput();
1154
+ this.close();
1155
+
1156
+ if (this.options.onChange) {
1157
+ this.options.onChange(this.selectedDate, this);
1158
+ }
1159
+ }
1160
+
1161
+ clear() {
1162
+ this.selectedDate = null;
1163
+ this.input.value = "";
1164
+ this.close();
1165
+
1166
+ if (this.options.onChange) {
1167
+ this.options.onChange(null, this);
1168
+ }
1169
+ }
1170
+
1171
+ updateInput() {
1172
+ if (this.options.isRange) {
1173
+ if (!this.rangeStart) {
1174
+ this.input.value = "";
1175
+ return;
1176
+ }
1177
+
1178
+ let startStr = "";
1179
+ let endStr = "";
1180
+
1181
+ if (this.options.mode === "BS") {
1182
+ startStr = this.rangeStart.format(this.options.format);
1183
+ if (this.rangeEnd) endStr = this.rangeEnd.format(this.options.format);
1184
+ } else {
1185
+ const [y1, m1, d1] = this.rangeStart.toGregorian();
1186
+ startStr = `${y1}-${String(m1).padStart(2, "0")}-${String(d1).padStart(2, "0")}`;
1187
+
1188
+ if (this.rangeEnd) {
1189
+ const [y2, m2, d2] = this.rangeEnd.toGregorian();
1190
+ endStr = `${y2}-${String(m2).padStart(2, "0")}-${String(d2).padStart(2, "0")}`;
1191
+ }
1192
+ }
1193
+
1194
+ this.input.value = endStr ? `${startStr} to ${endStr}` : startStr;
1195
+ } else {
1196
+ if (!this.selectedDate) {
1197
+ this.input.value = "";
1198
+ return;
1199
+ }
1200
+
1201
+ const timeStr = `${String(this.selectedTime.hour).padStart(2, "0")}:${String(this.selectedTime.minute).padStart(2, "0")}`;
1202
+
1203
+ if (this.options.mode === "BS") {
1204
+ let value = this.selectedDate.format(this.options.format);
1205
+ // Always append time for now if not present, because our format engine is simple
1206
+ // and user expects time if they see the picker
1207
+ if (!value.match(/\d{2}:\d{2}/)) {
1208
+ value += ` ${timeStr}`;
1209
+ }
1210
+ this.input.value = value;
1211
+ } else {
1212
+ const [y, m, d] = this.selectedDate.toGregorian();
1213
+ this.input.value = `${y}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")} ${timeStr}`;
1214
+ }
1215
+ }
1216
+
1217
+ this.input.dispatchEvent(
1218
+ new CustomEvent("change", {
1219
+ bubbles: true,
1220
+ detail: { origin: "datepicker" },
1221
+ }),
1222
+ );
1223
+ this.input.dispatchEvent(
1224
+ new CustomEvent("input", {
1225
+ bubbles: true,
1226
+ detail: { origin: "datepicker" },
1227
+ }),
1228
+ );
1229
+ }
1230
+
1231
+ getDaysInMonth(year, month) {
1232
+ const key = `${year}-${month}`;
1233
+ if (NepaliDatePicker.daysCache.has(key)) {
1234
+ return NepaliDatePicker.daysCache.get(key);
1235
+ }
1236
+
1237
+ try {
1238
+ for (let d = 32; d >= 27; d--) {
1239
+ try {
1240
+ new NepaliDate(year, month, d);
1241
+ NepaliDatePicker.daysCache.set(key, d);
1242
+ return d;
1243
+ } catch (e) {}
1244
+ }
1245
+ } catch (e) {}
1246
+ return 30;
1247
+ }
1248
+
1249
+ toNepaliNum(num) {
1250
+ const map = ["०", "१", "२", "३", "४", "५", "६", "७", "८", "९"];
1251
+ return String(num).replace(/\d/g, (d) => map[+d]);
1252
+ }
1253
+
1254
+ destroy() {
1255
+ this.close();
1256
+ this.picker.remove();
1257
+ this.input.classList.remove("npd-input");
1258
+ this.input.removeAttribute("data-npd-id");
1259
+ NepaliDatePicker.instances.delete(this.input);
1260
+ }
1261
+
1262
+ handleInputBlur() {
1263
+ const value = this.input.value.trim();
1264
+ if (!value) {
1265
+ this.updateInput();
1266
+ return;
1267
+ }
1268
+
1269
+ try {
1270
+ const [datePart, timePart] = value.split(" ");
1271
+ let year, month, day;
1272
+
1273
+ if (datePart.includes("-")) {
1274
+ [year, month, day] = datePart.split("-").map(Number);
1275
+ } else {
1276
+ [year, month, day] = datePart
1277
+ .replace(/\//g, "-")
1278
+ .split("-")
1279
+ .map(Number);
1280
+ }
1281
+
1282
+ if (!year || !month || !day) {
1283
+ this.updateInput();
1284
+ return;
1285
+ }
1286
+
1287
+ // Smart Rollover Logic
1288
+ let yearOffset = 0;
1289
+ let monthOffset = 0;
1290
+
1291
+ // Handle Month Overflow first (e.g. Month 13 -> Year + 1, Month 1)
1292
+ while (month > 12) {
1293
+ month -= 12;
1294
+ yearOffset++;
1295
+ }
1296
+ while (month < 1) {
1297
+ month += 12;
1298
+ yearOffset--;
1299
+ }
1300
+ year += yearOffset;
1301
+
1302
+ // Handle Day Overflow
1303
+ // We need loop because adding days might push us through multiple months (e.g. day 90)
1304
+ // But for simple "32", one check is usually enough. Let's do a robust loop.
1305
+ let maxDays = 32; // safe upper bound to start checking
1306
+ let safety = 0;
1307
+
1308
+ while (true && safety < 12) {
1309
+ // limit 1 year rollover to prevent infinite loops
1310
+ // Get max days for current Year/Month
1311
+ if (this.options.mode === "BS") {
1312
+ maxDays = this.getDaysInMonth(year, month);
1313
+ } else {
1314
+ maxDays = new Date(year, month, 0).getDate();
1315
+ }
1316
+
1317
+ if (day <= maxDays) break;
1318
+
1319
+ day -= maxDays;
1320
+ month++;
1321
+ if (month > 12) {
1322
+ month = 1;
1323
+ year++;
1324
+ }
1325
+ safety++;
1326
+ }
1327
+
1328
+ // Parse Time (preserve existing logic)
1329
+ if (timePart) {
1330
+ const [h, m] = timePart.split(":").map(Number);
1331
+ if (!isNaN(h)) this.selectedTime.hour = h;
1332
+ if (!isNaN(m)) this.selectedTime.minute = m;
1333
+ }
1334
+
1335
+ let newDate;
1336
+ if (this.options.mode === "BS") {
1337
+ newDate = new NepaliDate(year, month, day);
1338
+ } else {
1339
+ newDate = NepaliDate.fromGregorian(year, month, day);
1340
+ }
1341
+
1342
+ this.selectedDate = newDate;
1343
+ this.viewDate = {
1344
+ year: this.selectedDate.year,
1345
+ month: this.selectedDate.month,
1346
+ };
1347
+ this.render();
1348
+ this.updateInput(); // Format it nicely
1349
+ } catch (e) {
1350
+ // If totally invalid, revert
1351
+ this.updateInput();
1352
+ }
1353
+ }
1354
+
1355
+ static init(
1356
+ selector = 'input[type="npdate"], input[data-npdate]',
1357
+ options = {},
1358
+ ) {
1359
+ const inputs = document.querySelectorAll(selector);
1360
+ inputs.forEach((input) => {
1361
+ if (!NepaliDatePicker.instances.has(input)) {
1362
+ new NepaliDatePicker(input, options);
1363
+ }
1364
+ });
1365
+ }
1366
+ }
1367
+
1368
+ if (typeof document !== "undefined") {
1369
+ if (document.readyState === "loading") {
1370
+ document.addEventListener("DOMContentLoaded", () =>
1371
+ NepaliDatePicker.init(),
1372
+ );
1373
+ } else {
1374
+ NepaliDatePicker.init();
1375
+ }
1376
+ }
1377
+
1378
+ export default NepaliDatePicker;
1379
+
1380
+
1381
+