@rushdi94/hijri-datepicker 1.0.1

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,749 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.HijriDatePickerLib = {}));
5
+ })(this, (function (exports) { 'use strict';
6
+
7
+ // Tabular Islamic Calendar (arithmetic algorithm)
8
+ // Verified against known dates: 30 Dhu al-Qi'dah 1447 = 2026-05-17
9
+ const HIJRI_MONTHS_AR = [
10
+ 'محرم', 'صفر', 'ربيع الأول', 'ربيع الآخر',
11
+ 'جمادى الأولى', 'جمادى الآخرة', 'رجب', 'شعبان',
12
+ 'رمضان', 'شوال', 'ذو القعدة', 'ذو الحجة',
13
+ ];
14
+ // Sunday → Saturday in Arabic
15
+ const WEEKDAY_AR = ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'];
16
+ // ── calendar arithmetic ──────────────────────────────────────────────────────
17
+ function gToJDN(y, m, d) {
18
+ const a = Math.floor((14 - m) / 12);
19
+ const yr = y + 4800 - a;
20
+ const mo = m + 12 * a - 3;
21
+ return (d +
22
+ Math.floor((153 * mo + 2) / 5) +
23
+ 365 * yr +
24
+ Math.floor(yr / 4) -
25
+ Math.floor(yr / 100) +
26
+ Math.floor(yr / 400) -
27
+ 32045);
28
+ }
29
+ function jdnToG(jdn) {
30
+ const a = jdn + 32044;
31
+ const b = Math.floor((4 * a + 3) / 146097);
32
+ const c = a - Math.floor((b * 146097) / 4);
33
+ const dd = Math.floor((4 * c + 3) / 1461);
34
+ const e = c - Math.floor((1461 * dd) / 4);
35
+ const mo = Math.floor((5 * e + 2) / 153);
36
+ return [
37
+ b * 100 + dd - 4800 + Math.floor(mo / 10),
38
+ mo + 3 - 12 * Math.floor(mo / 10),
39
+ e - Math.floor((153 * mo + 2) / 5) + 1,
40
+ ];
41
+ }
42
+ function hToJDN(y, m, d) {
43
+ return (Math.floor((11 * y + 3) / 30) +
44
+ 354 * y +
45
+ 30 * m -
46
+ Math.floor((m - 1) / 2) +
47
+ d +
48
+ 1948440 -
49
+ 385);
50
+ }
51
+ function jdnToH(jdn) {
52
+ const l = jdn - 1948440 + 10632;
53
+ const n = Math.floor((l - 1) / 10631);
54
+ const ll = l - 10631 * n + 354;
55
+ const j = Math.floor((10985 - ll) / 5316) * Math.floor((50 * ll) / 17719) +
56
+ Math.floor(ll / 5670) * Math.floor((43 * ll) / 15238);
57
+ const lll = ll -
58
+ Math.floor((30 - j) / 15) * Math.floor((17719 * j) / 50) -
59
+ Math.floor(j / 16) * Math.floor((15238 * j) / 43) +
60
+ 29;
61
+ const month = Math.floor((24 * lll) / 709);
62
+ const day = lll - Math.floor((709 * month) / 24);
63
+ const year = 30 * n + j - 30;
64
+ return [year, month, day];
65
+ }
66
+ // ── public helpers ───────────────────────────────────────────────────────────
67
+ function toHijri(date) {
68
+ const [year, month, day] = jdnToH(gToJDN(date.getFullYear(), date.getMonth() + 1, date.getDate()));
69
+ return { year, month, day };
70
+ }
71
+ function toGregorian(year, month, day) {
72
+ const [gy, gm, gd] = jdnToG(hToJDN(year, month, day));
73
+ return new Date(gy, gm - 1, gd);
74
+ }
75
+ function daysInMonth(year, month) {
76
+ const nm = month === 12 ? 1 : month + 1;
77
+ const ny = month === 12 ? year + 1 : year;
78
+ return hToJDN(ny, nm, 1) - hToJDN(year, month, 1);
79
+ }
80
+ function firstWeekday(year, month) {
81
+ return toGregorian(year, month, 1).getDay(); // 0 = Sunday
82
+ }
83
+ function todayHijri() {
84
+ return toHijri(new Date());
85
+ }
86
+ function formatHijri(h, fmt = 'DD/MM/YYYY') {
87
+ return fmt
88
+ .replace('YYYY', String(h.year))
89
+ .replace('MM', String(h.month).padStart(2, '0'))
90
+ .replace('DD', String(h.day).padStart(2, '0'))
91
+ .replace('M', String(h.month))
92
+ .replace('D', String(h.day));
93
+ }
94
+ function parseHijri(value, fmt = 'DD/MM/YYYY') {
95
+ try {
96
+ const sep = fmt.replace(/[DMY]/g, '').charAt(0);
97
+ const parts = value.split(sep).map(Number);
98
+ const fmtParts = fmt.split(sep);
99
+ const di = fmtParts.findIndex((p) => p.startsWith('D'));
100
+ const mi = fmtParts.findIndex((p) => p.startsWith('M'));
101
+ const yi = fmtParts.findIndex((p) => p.startsWith('Y'));
102
+ const d = parts[di], m = parts[mi], y = parts[yi];
103
+ if (!d || !m || !y || m < 1 || m > 12 || d < 1 || d > daysInMonth(y, m))
104
+ return null;
105
+ return { year: y, month: m, day: d };
106
+ }
107
+ catch (_a) {
108
+ return null;
109
+ }
110
+ }
111
+
112
+ const CSS = /* css */ `
113
+ /* ── HijriDatePicker ─────────────────────────────────────────────────────── */
114
+ .hdp,
115
+ .hdp *,
116
+ .hdp *::before,
117
+ .hdp *::after {
118
+ box-sizing: border-box;
119
+ font-family: system-ui, -apple-system, 'Segoe UI', Tahoma, Arial, sans-serif;
120
+ }
121
+
122
+ /* wrapper */
123
+ .hdp-wrapper { position: relative; display: inline-block; }
124
+
125
+ /* input */
126
+ .hdp-input-wrap { position: relative; display: block; }
127
+ .hdp-input {
128
+ width: 100%;
129
+ padding: 9px 14px 9px 38px;
130
+ border: 1.5px solid #d1d5db;
131
+ border-radius: 8px;
132
+ font-size: 14px;
133
+ cursor: pointer;
134
+ background: #fff;
135
+ outline: none;
136
+ color: #333;
137
+ min-width: 210px;
138
+ transition: border-color .2s, box-shadow .2s;
139
+ caret-color: transparent;
140
+ text-align: inherit;
141
+ }
142
+ [dir="rtl"] .hdp-input { padding: 9px 14px 9px 38px; text-align: right; }
143
+ [dir="ltr"] .hdp-input { padding: 9px 38px 9px 14px; text-align: left; }
144
+ .hdp-input::placeholder { color: #aaa; }
145
+ .hdp-input:focus,
146
+ .hdp-input.open {
147
+ border-color: var(--hdp-primary, #8b1a2e);
148
+ box-shadow: 0 0 0 3px rgba(139,26,46,.14);
149
+ }
150
+ .hdp-input-icon {
151
+ position: absolute;
152
+ top: 50%;
153
+ transform: translateY(-50%);
154
+ pointer-events: none;
155
+ color: #9ca3af;
156
+ width: 18px;
157
+ height: 18px;
158
+ }
159
+ [dir="rtl"] .hdp-input-icon { left: 10px; right: auto; }
160
+ [dir="ltr"] .hdp-input-icon { right: 10px; left: auto; }
161
+
162
+ /* ── popup ───────────────────────────────────────────────────────────────── */
163
+ .hdp-popup {
164
+ position: absolute;
165
+ top: calc(100% + 6px);
166
+ z-index: 99999;
167
+ }
168
+ [dir="rtl"] .hdp-popup { right: 0; left: auto; }
169
+ [dir="ltr"] .hdp-popup { left: 0; right: auto; }
170
+ .hdp-popup {
171
+ width: 312px;
172
+ border-radius: 12px;
173
+ overflow: visible;
174
+ box-shadow: 0 8px 32px rgba(0,0,0,.18), 0 2px 8px rgba(0,0,0,.08);
175
+ background: var(--hdp-body-bg, #fff);
176
+ animation: _hdpIn .15s cubic-bezier(.2,.8,.4,1);
177
+ }
178
+
179
+ /* upward-pointing caret connecting popup to the input */
180
+ .hdp-popup:not(.hdp-inline)::before {
181
+ content: '';
182
+ position: absolute;
183
+ top: -8px;
184
+ width: 0; height: 0;
185
+ border-left: 9px solid transparent;
186
+ border-right: 9px solid transparent;
187
+ border-bottom: 9px solid var(--hdp-primary, #8b1a2e);
188
+ pointer-events: none;
189
+ }
190
+ [dir="rtl"] .hdp-popup:not(.hdp-inline)::before { right: 18px; left: auto; }
191
+ [dir="ltr"] .hdp-popup:not(.hdp-inline)::before { left: 18px; right: auto; }
192
+ @keyframes _hdpIn {
193
+ from { opacity: 0; transform: translateY(-8px) scale(.97); }
194
+ to { opacity: 1; transform: translateY(0) scale(1); }
195
+ }
196
+ .hdp-popup.hdp-inline {
197
+ position: static;
198
+ box-shadow: 0 2px 16px rgba(0,0,0,.1);
199
+ animation: none;
200
+ }
201
+
202
+ /* ── header ──────────────────────────────────────────────────────────────── */
203
+ .hdp-header {
204
+ background: var(--hdp-primary, #8b1a2e);
205
+ color: var(--hdp-on-primary, #fff);
206
+ display: flex;
207
+ flex-direction: row; /* always LTR regardless of dir attribute */
208
+ align-items: stretch;
209
+ height: 44px;
210
+ user-select: none;
211
+ }
212
+
213
+ /* prev / next month arrows */
214
+ .hdp-nav {
215
+ flex-shrink: 0;
216
+ width: 36px;
217
+ border: none;
218
+ background: transparent;
219
+ color: var(--hdp-on-primary, #fff);
220
+ cursor: pointer;
221
+ display: flex; align-items: center; justify-content: center;
222
+ font-size: 18px;
223
+ transition: background .15s;
224
+ line-height: 1;
225
+ }
226
+ .hdp-nav:hover { background: rgba(255,255,255,.18); }
227
+
228
+ /* ── month half (left 50%) ───────────────────────────────────────────────── */
229
+ .hdp-month-half {
230
+ flex: 1;
231
+ display: flex;
232
+ align-items: center;
233
+ justify-content: center;
234
+ border-right: 1px solid rgba(255,255,255,.2);
235
+ padding: 0 4px;
236
+ min-width: 0;
237
+ }
238
+
239
+ .hdp-month-select {
240
+ width: 100%;
241
+ appearance: none;
242
+ -webkit-appearance: none;
243
+ background: transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23ffffff' stroke-width='1.8' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right 8px center;
244
+ border: none;
245
+ padding: 6px 26px 6px 10px;
246
+ font-size: 14px; font-weight: 700;
247
+ color: var(--hdp-on-primary, #fff);
248
+ cursor: pointer;
249
+ outline: none;
250
+ text-align: center;
251
+ text-align-last: center;
252
+ letter-spacing: .2px;
253
+ }
254
+ [dir="rtl"] .hdp-month-select {
255
+ background-position: left 8px center;
256
+ padding: 6px 10px 6px 26px;
257
+ }
258
+ .hdp-month-select:hover { background-color: rgba(255,255,255,.12); border-radius: 6px; }
259
+ .hdp-month-select option { background: #fff; color: #111; font-weight: 600; }
260
+
261
+ /* ── year half (right 50%) ───────────────────────────────────────────────── */
262
+ .hdp-year-half {
263
+ flex: 1;
264
+ display: flex;
265
+ align-items: center;
266
+ justify-content: center;
267
+ padding: 0 6px;
268
+ min-width: 0;
269
+ }
270
+
271
+ /* inner wrapper: year number + arrows in a row */
272
+ .hdp-year-inner {
273
+ display: flex;
274
+ align-items: center;
275
+ gap: 3px;
276
+ }
277
+
278
+ .hdp-year-display {
279
+ font-size: 15px; font-weight: 800;
280
+ color: var(--hdp-on-primary, #fff);
281
+ letter-spacing: .4px;
282
+ line-height: 1;
283
+ white-space: nowrap;
284
+ }
285
+
286
+ /* arrows: collapsed (width 0) when not hovered, slide in on hover */
287
+ .hdp-year-arrows {
288
+ display: flex;
289
+ flex-direction: column;
290
+ gap: 2px;
291
+ max-width: 0;
292
+ overflow: hidden;
293
+ opacity: 0;
294
+ pointer-events: none;
295
+ transition: max-width .18s ease, opacity .18s ease;
296
+ }
297
+ .hdp-year-half:hover .hdp-year-arrows {
298
+ max-width: 22px;
299
+ opacity: 1;
300
+ pointer-events: auto;
301
+ }
302
+
303
+ .hdp-year-arrow {
304
+ background: none;
305
+ border: none;
306
+ color: var(--hdp-on-primary, #fff);
307
+ cursor: pointer;
308
+ width: 18px; height: 13px;
309
+ display: flex; align-items: center; justify-content: center;
310
+ font-size: 7px;
311
+ border-radius: 3px;
312
+ padding: 0;
313
+ line-height: 1;
314
+ flex-shrink: 0;
315
+ transition: background .12s;
316
+ }
317
+ .hdp-year-arrow:hover { background: rgba(255,255,255,.25); }
318
+
319
+ /* ── weekday row ─────────────────────────────────────────────────────────── */
320
+ .hdp-weekdays {
321
+ display: grid;
322
+ grid-template-columns: repeat(7, 1fr);
323
+ background: var(--hdp-primary, #8b1a2e);
324
+ padding: 0 10px 10px;
325
+ }
326
+ .hdp-wday {
327
+ text-align: center;
328
+ font-size: 11.5px;
329
+ color: var(--hdp-on-primary, #fff);
330
+ opacity: .82;
331
+ font-weight: 500;
332
+ padding: 3px 0;
333
+ }
334
+
335
+ /* ── days ────────────────────────────────────────────────────────────────── */
336
+ .hdp-days {
337
+ display: grid;
338
+ grid-template-columns: repeat(7, 1fr);
339
+ padding: 8px 8px 12px;
340
+ gap: 2px;
341
+ border-radius: 0 0 12px 12px;
342
+ background: var(--hdp-body-bg, #fff);
343
+ }
344
+ .hdp-day {
345
+ aspect-ratio: 1;
346
+ display: flex; align-items: center; justify-content: center;
347
+ border-radius: 50%;
348
+ font-size: 13.5px;
349
+ cursor: pointer;
350
+ color: var(--hdp-day-color, #1f2937);
351
+ transition: background .12s, color .12s, transform .08s;
352
+ font-weight: 400;
353
+ border: 2px solid transparent;
354
+ }
355
+ .hdp-day:not(.hdp-empty):hover {
356
+ background: var(--hdp-hover-bg, rgba(139,26,46,.1));
357
+ transform: scale(1.08);
358
+ }
359
+ .hdp-day.hdp-today:not(.hdp-selected) {
360
+ border-color: var(--hdp-primary, #8b1a2e);
361
+ color: var(--hdp-primary, #8b1a2e);
362
+ font-weight: 700;
363
+ }
364
+ .hdp-day.hdp-selected {
365
+ background: var(--hdp-primary, #8b1a2e) !important;
366
+ color: var(--hdp-on-primary, #fff) !important;
367
+ font-weight: 700;
368
+ border-color: transparent !important;
369
+ transform: scale(1.1);
370
+ }
371
+ .hdp-day.hdp-empty {
372
+ cursor: default;
373
+ pointer-events: none;
374
+ }
375
+ `;
376
+
377
+ // ── style injection (once per page) ─────────────────────────────────────────
378
+ let _stylesInjected = false;
379
+ function ensureStyles() {
380
+ if (_stylesInjected || typeof document === 'undefined')
381
+ return;
382
+ _stylesInjected = true;
383
+ const el = document.createElement('style');
384
+ el.dataset.hdpStyles = '1';
385
+ el.textContent = CSS;
386
+ document.head.appendChild(el);
387
+ }
388
+ // ── component ────────────────────────────────────────────────────────────────
389
+ const DEFAULT_WEEKDAYS_AR = [...WEEKDAY_AR];
390
+ const DEFAULT_WEEKDAYS_EN = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
391
+ class HijriDatePicker {
392
+ // ── constructor ────────────────────────────────────────────────────────────
393
+ constructor(options) {
394
+ var _a;
395
+ this.selected = null;
396
+ // ── event handlers ─────────────────────────────────────────────────────────
397
+ this._onDocClick = (e) => {
398
+ if (!this.root.contains(e.target))
399
+ this._close();
400
+ };
401
+ this._onKeyDown = (e) => {
402
+ if (e.key === 'Escape')
403
+ this._close();
404
+ };
405
+ if (!options.container)
406
+ throw new Error('HijriDatePicker: "container" is required');
407
+ const today = todayHijri();
408
+ const dir = (_a = options.dir) !== null && _a !== void 0 ? _a : 'rtl';
409
+ const defaults = {
410
+ inline: false,
411
+ dir,
412
+ placeholder: dir === 'rtl' ? 'اختر تاريخاً هجرياً' : 'Select Hijri date',
413
+ weekdayLabels: dir === 'rtl' ? DEFAULT_WEEKDAYS_AR : DEFAULT_WEEKDAYS_EN,
414
+ monthLabels: [...HIJRI_MONTHS_AR],
415
+ primaryColor: '#8b1a2e',
416
+ onPrimaryColor: '#ffffff',
417
+ bodyBg: '#ffffff',
418
+ dayColor: '#1f2937',
419
+ format: 'DD/MM/YYYY',
420
+ initialDate: today,
421
+ editable: false,
422
+ container: options.container,
423
+ onSelect: () => undefined,
424
+ onOpen: () => undefined,
425
+ onClose: () => undefined,
426
+ };
427
+ this.opts = { ...defaults, ...options, dir };
428
+ this.isRTL = this.opts.dir === 'rtl';
429
+ this.viewYear = this.opts.initialDate.year;
430
+ this.viewMonth = this.opts.initialDate.month;
431
+ ensureStyles();
432
+ this._mount();
433
+ }
434
+ // ── mount ──────────────────────────────────────────────────────────────────
435
+ _mount() {
436
+ const containerEl = typeof this.opts.container === 'string'
437
+ ? document.querySelector(this.opts.container)
438
+ : this.opts.container;
439
+ if (!containerEl)
440
+ throw new Error('HijriDatePicker: container element not found');
441
+ this.root = document.createElement('div');
442
+ this.root.className = 'hdp hdp-wrapper';
443
+ this.root.dir = this.opts.dir;
444
+ this._applyVars(this.root);
445
+ if (!this.opts.inline) {
446
+ this.root.appendChild(this._buildInput());
447
+ }
448
+ this.popup = this._buildPopup();
449
+ if (!this.opts.inline)
450
+ this.popup.style.display = 'none';
451
+ this.root.appendChild(this.popup);
452
+ containerEl.appendChild(this.root);
453
+ if (!this.opts.inline) {
454
+ document.addEventListener('click', this._onDocClick, true);
455
+ document.addEventListener('keydown', this._onKeyDown);
456
+ }
457
+ }
458
+ _applyVars(el) {
459
+ const { primaryColor, onPrimaryColor, bodyBg, dayColor, primaryColor: p } = this.opts;
460
+ el.style.setProperty('--hdp-primary', primaryColor);
461
+ el.style.setProperty('--hdp-on-primary', onPrimaryColor);
462
+ el.style.setProperty('--hdp-body-bg', bodyBg);
463
+ el.style.setProperty('--hdp-day-color', dayColor);
464
+ el.style.setProperty('--hdp-hover-bg', `${p}1a`);
465
+ }
466
+ // ── input ──────────────────────────────────────────────────────────────────
467
+ _buildInput() {
468
+ const wrap = document.createElement('div');
469
+ wrap.className = 'hdp-input-wrap';
470
+ this.inputEl = document.createElement('input');
471
+ this.inputEl.type = 'text';
472
+ this.inputEl.className = 'hdp-input';
473
+ this.inputEl.placeholder = this.opts.placeholder;
474
+ this.inputEl.readOnly = !this.opts.editable;
475
+ this.inputEl.setAttribute('autocomplete', 'off');
476
+ this.inputEl.addEventListener('click', () => this._open());
477
+ if (this.opts.editable) {
478
+ this.inputEl.addEventListener('input', () => {
479
+ const parsed = parseHijri(this.inputEl.value, this.opts.format);
480
+ if (parsed)
481
+ this._applySelection(parsed, false);
482
+ });
483
+ }
484
+ const icon = document.createElement('span');
485
+ icon.className = 'hdp-input-icon';
486
+ icon.innerHTML =
487
+ `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">` +
488
+ `<rect x="3" y="4" width="18" height="18" rx="2"/>` +
489
+ `<line x1="16" y1="2" x2="16" y2="6"/>` +
490
+ `<line x1="8" y1="2" x2="8" y2="6"/>` +
491
+ `<line x1="3" y1="10" x2="21" y2="10"/>` +
492
+ `</svg>`;
493
+ wrap.appendChild(this.inputEl);
494
+ wrap.appendChild(icon);
495
+ return wrap;
496
+ }
497
+ // ── popup ──────────────────────────────────────────────────────────────────
498
+ _buildPopup() {
499
+ const popup = document.createElement('div');
500
+ popup.className = this.opts.inline ? 'hdp-popup hdp-inline' : 'hdp-popup';
501
+ popup.dir = this.opts.dir;
502
+ if (!this.isRTL && !this.opts.inline) {
503
+ popup.style.right = 'auto';
504
+ popup.style.left = '0';
505
+ }
506
+ popup.setAttribute('role', 'dialog');
507
+ popup.setAttribute('aria-label', this.isRTL ? 'منتقي التاريخ الهجري' : 'Hijri Date Picker');
508
+ popup.appendChild(this._buildHeader());
509
+ popup.appendChild(this._buildWeekdays());
510
+ this.daysGrid = this._buildDaysGrid();
511
+ popup.appendChild(this.daysGrid);
512
+ return popup;
513
+ }
514
+ // ── header ─────────────────────────────────────────────────────────────────
515
+ _buildHeader() {
516
+ const hdr = document.createElement('div');
517
+ hdr.className = 'hdp-header';
518
+ // ── Month half (left) ─────────────────────────────────────────
519
+ const monthHalf = document.createElement('div');
520
+ monthHalf.className = 'hdp-month-half';
521
+ this.monthSelectEl = document.createElement('select');
522
+ this.monthSelectEl.className = 'hdp-month-select';
523
+ this.monthSelectEl.setAttribute('aria-label', this.isRTL ? 'الشهر' : 'Month');
524
+ this.opts.monthLabels.forEach((name, i) => {
525
+ const opt = document.createElement('option');
526
+ opt.value = String(i + 1);
527
+ opt.textContent = name;
528
+ this.monthSelectEl.appendChild(opt);
529
+ });
530
+ this.monthSelectEl.addEventListener('change', () => {
531
+ this.viewMonth = Number(this.monthSelectEl.value);
532
+ this._renderDays();
533
+ });
534
+ monthHalf.appendChild(this.monthSelectEl);
535
+ // ── Year half (right) ─────────────────────────────────────────
536
+ const yearHalf = document.createElement('div');
537
+ yearHalf.className = 'hdp-year-half';
538
+ // Inner group: year number + arrows side by side
539
+ const yearInner = document.createElement('div');
540
+ yearInner.className = 'hdp-year-inner';
541
+ this.yearDisplayEl = document.createElement('span');
542
+ this.yearDisplayEl.className = 'hdp-year-display';
543
+ const yearArrows = document.createElement('div');
544
+ yearArrows.className = 'hdp-year-arrows';
545
+ const yearUp = document.createElement('button');
546
+ yearUp.className = 'hdp-year-arrow';
547
+ yearUp.innerHTML = '▲';
548
+ yearUp.setAttribute('aria-label', this.isRTL ? 'السنة التالية' : 'Next year');
549
+ yearUp.addEventListener('click', () => {
550
+ this.viewYear++;
551
+ this._syncHeader();
552
+ this._renderDays();
553
+ });
554
+ const yearDown = document.createElement('button');
555
+ yearDown.className = 'hdp-year-arrow';
556
+ yearDown.innerHTML = '▼';
557
+ yearDown.setAttribute('aria-label', this.isRTL ? 'السنة السابقة' : 'Previous year');
558
+ yearDown.addEventListener('click', () => {
559
+ this.viewYear--;
560
+ this._syncHeader();
561
+ this._renderDays();
562
+ });
563
+ yearArrows.appendChild(yearUp);
564
+ yearArrows.appendChild(yearDown);
565
+ yearInner.appendChild(this.yearDisplayEl);
566
+ yearInner.appendChild(yearArrows);
567
+ yearHalf.appendChild(yearInner);
568
+ // ── Assemble: [‹] [month | year] [›] (always LTR order) ─────
569
+ hdr.appendChild(this._navBtn('‹', -1));
570
+ hdr.appendChild(monthHalf);
571
+ hdr.appendChild(yearHalf);
572
+ hdr.appendChild(this._navBtn('›', 1));
573
+ this._syncHeader();
574
+ return hdr;
575
+ }
576
+ _navBtn(symbol, delta) {
577
+ const btn = document.createElement('button');
578
+ btn.className = 'hdp-nav';
579
+ btn.innerHTML = symbol;
580
+ btn.setAttribute('aria-label', delta > 0 ? 'الشهر التالي' : 'الشهر السابق');
581
+ btn.addEventListener('click', () => this._navigate(delta));
582
+ return btn;
583
+ }
584
+ _syncHeader() {
585
+ this.monthSelectEl.value = String(this.viewMonth);
586
+ this.yearDisplayEl.textContent = String(this.viewYear);
587
+ }
588
+ // ── weekdays ───────────────────────────────────────────────────────────────
589
+ _buildWeekdays() {
590
+ const row = document.createElement('div');
591
+ row.className = 'hdp-weekdays';
592
+ this.opts.weekdayLabels.forEach((name) => {
593
+ const cell = document.createElement('div');
594
+ cell.className = 'hdp-wday';
595
+ cell.textContent = name;
596
+ row.appendChild(cell);
597
+ });
598
+ return row;
599
+ }
600
+ // ── days grid ──────────────────────────────────────────────────────────────
601
+ _buildDaysGrid() {
602
+ const grid = document.createElement('div');
603
+ grid.className = 'hdp-days';
604
+ this._renderDays(grid);
605
+ return grid;
606
+ }
607
+ _renderDays(grid) {
608
+ const g = grid !== null && grid !== void 0 ? grid : this.daysGrid;
609
+ g.innerHTML = '';
610
+ const today = todayHijri();
611
+ const total = daysInMonth(this.viewYear, this.viewMonth);
612
+ const start = firstWeekday(this.viewYear, this.viewMonth);
613
+ for (let i = 0; i < start; i++) {
614
+ const blank = document.createElement('div');
615
+ blank.className = 'hdp-day hdp-empty';
616
+ g.appendChild(blank);
617
+ }
618
+ for (let d = 1; d <= total; d++) {
619
+ const cell = document.createElement('div');
620
+ cell.className = 'hdp-day';
621
+ cell.textContent = String(d);
622
+ cell.setAttribute('role', 'button');
623
+ cell.setAttribute('tabindex', '0');
624
+ cell.setAttribute('aria-label', `${d} ${this.opts.monthLabels[this.viewMonth - 1]} ${this.viewYear}`);
625
+ const isToday = d === today.day && this.viewMonth === today.month && this.viewYear === today.year;
626
+ const isSel = this.selected !== null &&
627
+ d === this.selected.day &&
628
+ this.viewMonth === this.selected.month &&
629
+ this.viewYear === this.selected.year;
630
+ if (isToday)
631
+ cell.classList.add('hdp-today');
632
+ if (isSel)
633
+ cell.classList.add('hdp-selected');
634
+ cell.addEventListener('click', () => this._pickDay(d));
635
+ cell.addEventListener('keydown', (e) => {
636
+ if (e.key === 'Enter' || e.key === ' ') {
637
+ e.preventDefault();
638
+ this._pickDay(d);
639
+ }
640
+ });
641
+ g.appendChild(cell);
642
+ }
643
+ }
644
+ // ── navigation ─────────────────────────────────────────────────────────────
645
+ _navigate(delta) {
646
+ this.viewMonth += delta;
647
+ if (this.viewMonth > 12) {
648
+ this.viewMonth = 1;
649
+ this.viewYear++;
650
+ }
651
+ if (this.viewMonth < 1) {
652
+ this.viewMonth = 12;
653
+ this.viewYear--;
654
+ }
655
+ this._syncHeader();
656
+ this._renderDays();
657
+ }
658
+ // ── selection ──────────────────────────────────────────────────────────────
659
+ _pickDay(day) {
660
+ this._applySelection({ year: this.viewYear, month: this.viewMonth, day }, true);
661
+ }
662
+ _applySelection(date, closeAfter) {
663
+ this.selected = { ...date };
664
+ this.viewYear = date.year;
665
+ this.viewMonth = date.month;
666
+ const fmt = formatHijri(date, this.opts.format);
667
+ if (this.inputEl)
668
+ this.inputEl.value = fmt;
669
+ this._syncHeader();
670
+ this._renderDays();
671
+ this.opts.onSelect(date, fmt);
672
+ if (closeAfter && !this.opts.inline)
673
+ this._close();
674
+ }
675
+ // ── open / close ───────────────────────────────────────────────────────────
676
+ _open() {
677
+ var _a;
678
+ if (this.popup.style.display !== 'none')
679
+ return;
680
+ this.popup.style.display = 'block';
681
+ (_a = this.inputEl) === null || _a === void 0 ? void 0 : _a.classList.add('open');
682
+ this.opts.onOpen();
683
+ }
684
+ _close() {
685
+ var _a;
686
+ if (this.popup.style.display === 'none')
687
+ return;
688
+ this.popup.style.display = 'none';
689
+ (_a = this.inputEl) === null || _a === void 0 ? void 0 : _a.classList.remove('open');
690
+ this.opts.onClose();
691
+ }
692
+ // ── public API ─────────────────────────────────────────────────────────────
693
+ /** Get the currently selected Hijri date (or null) */
694
+ getValue() {
695
+ return this.selected ? { ...this.selected } : null;
696
+ }
697
+ /** Programmatically select a date */
698
+ setValue(date) {
699
+ this._applySelection(date, false);
700
+ }
701
+ /** Clear the selected date */
702
+ clear() {
703
+ this.selected = null;
704
+ if (this.inputEl)
705
+ this.inputEl.value = '';
706
+ this._renderDays();
707
+ }
708
+ /** Navigate the calendar view to a specific month */
709
+ goTo(year, month) {
710
+ this.viewYear = year;
711
+ this.viewMonth = month;
712
+ this._syncHeader();
713
+ this._renderDays();
714
+ }
715
+ /** Update colors after instantiation */
716
+ setColors(opts) {
717
+ Object.assign(this.opts, opts);
718
+ this._applyVars(this.root);
719
+ this._renderDays();
720
+ }
721
+ /** Open the picker programmatically */
722
+ open() {
723
+ this._open();
724
+ }
725
+ /** Close the picker programmatically */
726
+ close() {
727
+ this._close();
728
+ }
729
+ /** Remove the picker from the DOM and clean up listeners */
730
+ destroy() {
731
+ document.removeEventListener('click', this._onDocClick, true);
732
+ document.removeEventListener('keydown', this._onKeyDown);
733
+ this.root.remove();
734
+ }
735
+ }
736
+
737
+ exports.HIJRI_MONTHS_AR = HIJRI_MONTHS_AR;
738
+ exports.HijriDatePicker = HijriDatePicker;
739
+ exports.WEEKDAY_AR = WEEKDAY_AR;
740
+ exports.daysInMonth = daysInMonth;
741
+ exports.firstWeekday = firstWeekday;
742
+ exports.formatHijri = formatHijri;
743
+ exports.parseHijri = parseHijri;
744
+ exports.toGregorian = toGregorian;
745
+ exports.toHijri = toHijri;
746
+ exports.todayHijri = todayHijri;
747
+
748
+ }));
749
+ //# sourceMappingURL=hijri-datepicker.umd.js.map