@spezutil/hijri-datepicker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/index.cjs +580 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +120 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.js +577 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
import { createCalendar, translitMonthNames, weekdayNames, formatHijri } from '@spezutil/hijri-core';
|
|
2
|
+
|
|
3
|
+
// src/hijri-datepicker.ts
|
|
4
|
+
|
|
5
|
+
// src/render.ts
|
|
6
|
+
function sameHijri(a, b) {
|
|
7
|
+
return a.year === b.year && a.month === b.month && a.day === b.day;
|
|
8
|
+
}
|
|
9
|
+
function addDaysUtc(date, days) {
|
|
10
|
+
return new Date(date.getTime() + days * 864e5);
|
|
11
|
+
}
|
|
12
|
+
function buildMonthModel(cal, view, opts) {
|
|
13
|
+
const firstGreg = cal.hijriToGregorian({ year: view.year, month: view.month, day: 1 });
|
|
14
|
+
const startOffset = firstGreg.getUTCDay();
|
|
15
|
+
const gridStart = addDaysUtc(firstGreg, -startOffset);
|
|
16
|
+
const todayHijri = opts.today ? cal.gregorianToHijri(opts.today) : null;
|
|
17
|
+
const startT = opts.rangeStart ? cal.hijriToGregorian(opts.rangeStart).getTime() : null;
|
|
18
|
+
const endT = opts.rangeEnd ? cal.hijriToGregorian(opts.rangeEnd).getTime() : null;
|
|
19
|
+
const lo = startT !== null && endT !== null ? Math.min(startT, endT) : null;
|
|
20
|
+
const hi = startT !== null && endT !== null ? Math.max(startT, endT) : null;
|
|
21
|
+
const weeks = [];
|
|
22
|
+
for (let w = 0; w < 6; w++) {
|
|
23
|
+
const week = [];
|
|
24
|
+
for (let d = 0; d < 7; d++) {
|
|
25
|
+
const g = addDaysUtc(gridStart, w * 7 + d);
|
|
26
|
+
const t = g.getTime();
|
|
27
|
+
const hijri = cal.gregorianToHijri(g);
|
|
28
|
+
const selected = (opts.selected ? sameHijri(hijri, opts.selected) : false) || (opts.selectedList ? opts.selectedList.some((s) => sameHijri(hijri, s)) : false);
|
|
29
|
+
week.push({
|
|
30
|
+
hijri,
|
|
31
|
+
gregorian: g,
|
|
32
|
+
inCurrentMonth: hijri.year === view.year && hijri.month === view.month,
|
|
33
|
+
selected,
|
|
34
|
+
disabled: opts.isDisabled ? opts.isDisabled(hijri, g) : false,
|
|
35
|
+
isToday: todayHijri ? sameHijri(hijri, todayHijri) : false,
|
|
36
|
+
rangeStart: startT !== null && t === startT,
|
|
37
|
+
rangeEnd: endT !== null && t === endT,
|
|
38
|
+
inRange: lo !== null && hi !== null && t > lo && t < hi
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
weeks.push(week);
|
|
42
|
+
}
|
|
43
|
+
return { year: view.year, month: view.month, weeks };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/selection.ts
|
|
47
|
+
var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
48
|
+
function isMode(v) {
|
|
49
|
+
return v === "range" || v === "multiple" ? v : "single";
|
|
50
|
+
}
|
|
51
|
+
function parseIsoList(v) {
|
|
52
|
+
if (!v) return [];
|
|
53
|
+
return v.split(",").map((s) => s.trim()).filter((s) => ISO_DATE.test(s));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/time.ts
|
|
57
|
+
function pad2(n) {
|
|
58
|
+
return String(n).padStart(2, "0");
|
|
59
|
+
}
|
|
60
|
+
function parseTime(s) {
|
|
61
|
+
if (!s) return null;
|
|
62
|
+
const m = /^(\d{1,2}):(\d{2})$/.exec(s.trim());
|
|
63
|
+
if (!m) return null;
|
|
64
|
+
const hour = Number(m[1]);
|
|
65
|
+
const minute = Number(m[2]);
|
|
66
|
+
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
|
|
67
|
+
return { hour, minute };
|
|
68
|
+
}
|
|
69
|
+
function formatTime24(t) {
|
|
70
|
+
return `${pad2(t.hour)}:${pad2(t.minute)}`;
|
|
71
|
+
}
|
|
72
|
+
function to12(hour) {
|
|
73
|
+
const meridiem = hour < 12 ? "AM" : "PM";
|
|
74
|
+
let h = hour % 12;
|
|
75
|
+
if (h === 0) h = 12;
|
|
76
|
+
return { hour12: h, meridiem };
|
|
77
|
+
}
|
|
78
|
+
function from12(hour12, meridiem) {
|
|
79
|
+
let h = hour12 % 12;
|
|
80
|
+
if (meridiem === "PM") h += 12;
|
|
81
|
+
return h;
|
|
82
|
+
}
|
|
83
|
+
function combineDateTime(dateIso, t) {
|
|
84
|
+
return `${dateIso}T${formatTime24(t)}`;
|
|
85
|
+
}
|
|
86
|
+
function splitDateTime(value) {
|
|
87
|
+
if (!value) return { date: null, time: null };
|
|
88
|
+
const [d, tt] = value.split("T");
|
|
89
|
+
return { date: d ?? null, time: tt ? parseTime(tt) : null };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/styles.ts
|
|
93
|
+
var styles = `
|
|
94
|
+
:host {
|
|
95
|
+
--dtp-bg: #fff;
|
|
96
|
+
--dtp-fg: #1a1a1a;
|
|
97
|
+
--dtp-muted: #9aa0a6;
|
|
98
|
+
--dtp-accent: #0b7d3e;
|
|
99
|
+
--dtp-accent-fg: #fff;
|
|
100
|
+
--dtp-radius: 8px;
|
|
101
|
+
display: inline-block;
|
|
102
|
+
font-family: system-ui, sans-serif;
|
|
103
|
+
color: var(--dtp-fg);
|
|
104
|
+
}
|
|
105
|
+
.cal { background: var(--dtp-bg); border: 1px solid #e0e0e0; border-radius: var(--dtp-radius); padding: 8px; width: 280px; }
|
|
106
|
+
.header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
|
|
107
|
+
.header button { background: none; border: none; cursor: pointer; font-size: 18px; padding: 4px 8px; border-radius: 6px; color: var(--dtp-fg); }
|
|
108
|
+
.header button:hover { background: #f0f0f0; }
|
|
109
|
+
.title { font-weight: 600; text-align: center; }
|
|
110
|
+
.title small { display: block; font-weight: 400; color: var(--dtp-muted); font-size: 11px; }
|
|
111
|
+
.grid { display: flex; flex-direction: column; gap: 2px; }
|
|
112
|
+
.dow-row, .week { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
|
|
113
|
+
.dow { text-align: center; font-size: 11px; color: var(--dtp-muted); padding: 4px 0; }
|
|
114
|
+
.cell { aspect-ratio: 1; border: none; background: none; cursor: pointer; border-radius: 6px; display: flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1.1; color: var(--dtp-fg); }
|
|
115
|
+
.cell:hover:not([disabled]) { background: #f0f0f0; }
|
|
116
|
+
.cell .greg { font-size: 9px; color: var(--dtp-muted); }
|
|
117
|
+
.cell.out { color: var(--dtp-muted); opacity: 0.5; }
|
|
118
|
+
.cell.today { outline: 1px solid var(--dtp-accent); }
|
|
119
|
+
.cell[aria-selected="true"] { background: var(--dtp-accent); color: var(--dtp-accent-fg); }
|
|
120
|
+
.cell[aria-selected="true"] .greg { color: var(--dtp-accent-fg); }
|
|
121
|
+
.cell[disabled] { cursor: not-allowed; opacity: 0.3; }
|
|
122
|
+
.cell.in-range { background: color-mix(in srgb, var(--dtp-accent) 16%, transparent); border-radius: 0; }
|
|
123
|
+
.cell.range-start { background: var(--dtp-accent); color: var(--dtp-accent-fg); border-top-right-radius: 0; border-bottom-right-radius: 0; }
|
|
124
|
+
.cell.range-end { background: var(--dtp-accent); color: var(--dtp-accent-fg); border-top-left-radius: 0; border-bottom-left-radius: 0; }
|
|
125
|
+
.cell.range-start .greg, .cell.range-end .greg { color: var(--dtp-accent-fg); }
|
|
126
|
+
.time-row { display: flex; align-items: center; gap: 4px; margin-top: 8px; justify-content: center; }
|
|
127
|
+
.time-row input { width: 44px; text-align: center; padding: 4px; border: 1px solid #e0e0e0; border-radius: 6px; font: inherit; }
|
|
128
|
+
.time-row button { border: 1px solid var(--dtp-accent); background: none; color: var(--dtp-accent); border-radius: 6px; padding: 4px 8px; cursor: pointer; font: inherit; }
|
|
129
|
+
.time-row button[aria-pressed="true"] { background: var(--dtp-accent); color: var(--dtp-accent-fg); }
|
|
130
|
+
:host([dir="rtl"]) .cal { direction: rtl; }
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
// src/hijri-datepicker.ts
|
|
134
|
+
var ISO = /^\d{4}-\d{2}-\d{2}$/;
|
|
135
|
+
function parseIsoUtc(s) {
|
|
136
|
+
if (!s || !ISO.test(s)) return null;
|
|
137
|
+
const [y, m, d] = s.split("-").map(Number);
|
|
138
|
+
return new Date(Date.UTC(y, m - 1, d));
|
|
139
|
+
}
|
|
140
|
+
function toIso(date) {
|
|
141
|
+
return date.toISOString().slice(0, 10);
|
|
142
|
+
}
|
|
143
|
+
var HijriDatepicker = class extends HTMLElement {
|
|
144
|
+
constructor() {
|
|
145
|
+
super();
|
|
146
|
+
this.cal = createCalendar();
|
|
147
|
+
this._mode = "single";
|
|
148
|
+
this.selected = null;
|
|
149
|
+
// single
|
|
150
|
+
this.selectedList = [];
|
|
151
|
+
// multiple
|
|
152
|
+
this.rangeStart = null;
|
|
153
|
+
this.rangeEnd = null;
|
|
154
|
+
this.hoverDate = null;
|
|
155
|
+
this.time = null;
|
|
156
|
+
// single + enable-time
|
|
157
|
+
this.lastCells = [];
|
|
158
|
+
this.suppress = false;
|
|
159
|
+
this.root = this.attachShadow({ mode: "open" });
|
|
160
|
+
const todayHijri = this.cal.gregorianToHijri(/* @__PURE__ */ new Date());
|
|
161
|
+
this.view = { year: todayHijri.year, month: todayHijri.month };
|
|
162
|
+
}
|
|
163
|
+
static get observedAttributes() {
|
|
164
|
+
return [
|
|
165
|
+
"value",
|
|
166
|
+
"start",
|
|
167
|
+
"end",
|
|
168
|
+
"min",
|
|
169
|
+
"max",
|
|
170
|
+
"dir",
|
|
171
|
+
"disabled-weekdays",
|
|
172
|
+
"mode",
|
|
173
|
+
"enable-time",
|
|
174
|
+
"time-format"
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
reflect(name, v) {
|
|
178
|
+
if (v === null) this.removeAttribute(name);
|
|
179
|
+
else this.setAttribute(name, v);
|
|
180
|
+
}
|
|
181
|
+
get value() {
|
|
182
|
+
return this.getAttribute("value");
|
|
183
|
+
}
|
|
184
|
+
set value(v) {
|
|
185
|
+
this.reflect("value", v);
|
|
186
|
+
}
|
|
187
|
+
get start() {
|
|
188
|
+
return this.getAttribute("start");
|
|
189
|
+
}
|
|
190
|
+
set start(v) {
|
|
191
|
+
this.reflect("start", v);
|
|
192
|
+
}
|
|
193
|
+
get end() {
|
|
194
|
+
return this.getAttribute("end");
|
|
195
|
+
}
|
|
196
|
+
set end(v) {
|
|
197
|
+
this.reflect("end", v);
|
|
198
|
+
}
|
|
199
|
+
get mode() {
|
|
200
|
+
return this.getAttribute("mode");
|
|
201
|
+
}
|
|
202
|
+
set mode(v) {
|
|
203
|
+
this.reflect("mode", v);
|
|
204
|
+
}
|
|
205
|
+
get min() {
|
|
206
|
+
return this.getAttribute("min");
|
|
207
|
+
}
|
|
208
|
+
set min(v) {
|
|
209
|
+
this.reflect("min", v);
|
|
210
|
+
}
|
|
211
|
+
get max() {
|
|
212
|
+
return this.getAttribute("max");
|
|
213
|
+
}
|
|
214
|
+
set max(v) {
|
|
215
|
+
this.reflect("max", v);
|
|
216
|
+
}
|
|
217
|
+
get timeFormat() {
|
|
218
|
+
return this.getAttribute("time-format");
|
|
219
|
+
}
|
|
220
|
+
set timeFormat(v) {
|
|
221
|
+
this.reflect("time-format", v);
|
|
222
|
+
}
|
|
223
|
+
get disabledWeekdays() {
|
|
224
|
+
return this.getAttribute("disabled-weekdays");
|
|
225
|
+
}
|
|
226
|
+
set disabledWeekdays(v) {
|
|
227
|
+
this.reflect("disabled-weekdays", v);
|
|
228
|
+
}
|
|
229
|
+
get enableTime() {
|
|
230
|
+
return this.hasAttribute("enable-time");
|
|
231
|
+
}
|
|
232
|
+
set enableTime(v) {
|
|
233
|
+
if (v) this.setAttribute("enable-time", "");
|
|
234
|
+
else this.removeAttribute("enable-time");
|
|
235
|
+
}
|
|
236
|
+
connectedCallback() {
|
|
237
|
+
this.syncFromAttrs();
|
|
238
|
+
this.render();
|
|
239
|
+
}
|
|
240
|
+
attributeChangedCallback() {
|
|
241
|
+
if (!this.root || this.suppress) return;
|
|
242
|
+
this.syncFromAttrs();
|
|
243
|
+
this.render();
|
|
244
|
+
}
|
|
245
|
+
g2h(d) {
|
|
246
|
+
return this.cal.gregorianToHijri(d);
|
|
247
|
+
}
|
|
248
|
+
h2iso(h) {
|
|
249
|
+
return toIso(this.cal.hijriToGregorian(h));
|
|
250
|
+
}
|
|
251
|
+
applyAttrs(fn) {
|
|
252
|
+
this.suppress = true;
|
|
253
|
+
try {
|
|
254
|
+
fn();
|
|
255
|
+
} finally {
|
|
256
|
+
this.suppress = false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
syncFromAttrs() {
|
|
260
|
+
this._mode = isMode(this.getAttribute("mode"));
|
|
261
|
+
if (this._mode === "range") {
|
|
262
|
+
const s = parseIsoUtc(this.getAttribute("start"));
|
|
263
|
+
const e = parseIsoUtc(this.getAttribute("end"));
|
|
264
|
+
this.rangeStart = s ? this.g2h(s) : null;
|
|
265
|
+
this.rangeEnd = e ? this.g2h(e) : null;
|
|
266
|
+
this.hoverDate = null;
|
|
267
|
+
if (this.rangeStart) this.view = { year: this.rangeStart.year, month: this.rangeStart.month };
|
|
268
|
+
} else if (this._mode === "multiple") {
|
|
269
|
+
const isos = parseIsoList(this.getAttribute("value"));
|
|
270
|
+
this.selectedList = isos.map((iso) => parseIsoUtc(iso)).filter((d) => d !== null).map((d) => this.g2h(d));
|
|
271
|
+
const first = this.selectedList[0];
|
|
272
|
+
if (first) this.view = { year: first.year, month: first.month };
|
|
273
|
+
} else {
|
|
274
|
+
const { date, time } = splitDateTime(this.getAttribute("value"));
|
|
275
|
+
const d = parseIsoUtc(date);
|
|
276
|
+
this.selected = d ? this.g2h(d) : null;
|
|
277
|
+
this.time = time ?? this.time;
|
|
278
|
+
if (this.selected) this.view = { year: this.selected.year, month: this.selected.month };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
buildDisabledFn() {
|
|
282
|
+
const min = parseIsoUtc(this.getAttribute("min"));
|
|
283
|
+
const max = parseIsoUtc(this.getAttribute("max"));
|
|
284
|
+
const dowAttr = this.getAttribute("disabled-weekdays");
|
|
285
|
+
const disabledDows = dowAttr ? dowAttr.split(",").map((n) => Number(n.trim())) : [];
|
|
286
|
+
return (h, g) => {
|
|
287
|
+
if (min && g.getTime() < min.getTime()) return true;
|
|
288
|
+
if (max && g.getTime() > max.getTime()) return true;
|
|
289
|
+
if (disabledDows.includes(g.getUTCDay())) return true;
|
|
290
|
+
if (this.isDateDisabled && this.isDateDisabled(h, g)) return true;
|
|
291
|
+
return false;
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
navigate(delta) {
|
|
295
|
+
let { year, month } = this.view;
|
|
296
|
+
month += delta;
|
|
297
|
+
if (month < 1) {
|
|
298
|
+
month = 12;
|
|
299
|
+
year -= 1;
|
|
300
|
+
} else if (month > 12) {
|
|
301
|
+
month = 1;
|
|
302
|
+
year += 1;
|
|
303
|
+
}
|
|
304
|
+
this.view = { year, month };
|
|
305
|
+
this.render();
|
|
306
|
+
}
|
|
307
|
+
emit(detail) {
|
|
308
|
+
this.dispatchEvent(
|
|
309
|
+
new CustomEvent("change", { bubbles: true, composed: true, detail })
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
select(cell) {
|
|
313
|
+
if (cell.disabled) return;
|
|
314
|
+
if (this._mode === "range") return this.selectRange(cell);
|
|
315
|
+
if (this._mode === "multiple") return this.selectMultiple(cell);
|
|
316
|
+
return this.selectSingle(cell);
|
|
317
|
+
}
|
|
318
|
+
selectSingle(cell) {
|
|
319
|
+
this.selected = cell.hijri;
|
|
320
|
+
const iso = toIso(cell.gregorian);
|
|
321
|
+
const value = this.time ? combineDateTime(iso, this.time) : iso;
|
|
322
|
+
this.applyAttrs(() => this.setAttribute("value", value));
|
|
323
|
+
this.render();
|
|
324
|
+
this.emit({
|
|
325
|
+
mode: "single",
|
|
326
|
+
hijri: cell.hijri,
|
|
327
|
+
gregorian: value,
|
|
328
|
+
...this.time ? { time: this.time } : {}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
selectMultiple(cell) {
|
|
332
|
+
const idx = this.selectedList.findIndex((h) => sameHijri(h, cell.hijri));
|
|
333
|
+
if (idx >= 0) this.selectedList.splice(idx, 1);
|
|
334
|
+
else this.selectedList.push(cell.hijri);
|
|
335
|
+
this.selectedList.sort(
|
|
336
|
+
(a, b) => this.cal.hijriToGregorian(a).getTime() - this.cal.hijriToGregorian(b).getTime()
|
|
337
|
+
);
|
|
338
|
+
const isos = this.selectedList.map((h) => this.h2iso(h));
|
|
339
|
+
this.applyAttrs(() => {
|
|
340
|
+
if (isos.length) this.setAttribute("value", isos.join(","));
|
|
341
|
+
else this.removeAttribute("value");
|
|
342
|
+
});
|
|
343
|
+
this.render();
|
|
344
|
+
this.emit({ mode: "multiple", hijri: this.selectedList.slice(), gregorian: isos });
|
|
345
|
+
}
|
|
346
|
+
selectRange(cell) {
|
|
347
|
+
const startingNew = !this.rangeStart || this.rangeStart && this.rangeEnd;
|
|
348
|
+
if (startingNew) {
|
|
349
|
+
this.rangeStart = cell.hijri;
|
|
350
|
+
this.rangeEnd = null;
|
|
351
|
+
this.hoverDate = null;
|
|
352
|
+
this.applyAttrs(() => {
|
|
353
|
+
this.setAttribute("start", toIso(cell.gregorian));
|
|
354
|
+
this.removeAttribute("end");
|
|
355
|
+
});
|
|
356
|
+
this.render();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const startG = this.cal.hijriToGregorian(this.rangeStart).getTime();
|
|
360
|
+
if (cell.gregorian.getTime() < startG) {
|
|
361
|
+
this.rangeEnd = this.rangeStart;
|
|
362
|
+
this.rangeStart = cell.hijri;
|
|
363
|
+
} else {
|
|
364
|
+
this.rangeEnd = cell.hijri;
|
|
365
|
+
}
|
|
366
|
+
const startIso = this.h2iso(this.rangeStart);
|
|
367
|
+
const endIso = this.h2iso(this.rangeEnd);
|
|
368
|
+
this.applyAttrs(() => {
|
|
369
|
+
this.setAttribute("start", startIso);
|
|
370
|
+
this.setAttribute("end", endIso);
|
|
371
|
+
});
|
|
372
|
+
this.render();
|
|
373
|
+
this.emit({
|
|
374
|
+
mode: "range",
|
|
375
|
+
start: { hijri: this.rangeStart, gregorian: startIso },
|
|
376
|
+
end: { hijri: this.rangeEnd, gregorian: endIso }
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
setTime(t) {
|
|
380
|
+
this.time = t;
|
|
381
|
+
if (this.selected) {
|
|
382
|
+
const iso = this.h2iso(this.selected);
|
|
383
|
+
const value = combineDateTime(iso, t);
|
|
384
|
+
this.applyAttrs(() => this.setAttribute("value", value));
|
|
385
|
+
this.emit({ mode: "single", hijri: this.selected, gregorian: value, time: t });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
paintRange(hoverHijri) {
|
|
389
|
+
if (this._mode !== "range" || !this.rangeStart || this.rangeEnd) return;
|
|
390
|
+
const startT = this.cal.hijriToGregorian(this.rangeStart).getTime();
|
|
391
|
+
const endT = hoverHijri ? this.cal.hijriToGregorian(hoverHijri).getTime() : startT;
|
|
392
|
+
const lo = Math.min(startT, endT);
|
|
393
|
+
const hi = Math.max(startT, endT);
|
|
394
|
+
this.root.querySelectorAll("[data-i]").forEach((btn) => {
|
|
395
|
+
const cell = this.lastCells[Number(btn.dataset.i)];
|
|
396
|
+
if (!cell) return;
|
|
397
|
+
const t = cell.gregorian.getTime();
|
|
398
|
+
btn.classList.toggle("in-range", t > lo && t < hi);
|
|
399
|
+
btn.classList.toggle("range-end", hoverHijri ? t === hi && t !== startT : false);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
renderTimeRow() {
|
|
403
|
+
if (this._mode !== "single" || !this.hasAttribute("enable-time")) return "";
|
|
404
|
+
const t = this.time ?? { hour: 0, minute: 0 };
|
|
405
|
+
const is12 = this.getAttribute("time-format") === "12";
|
|
406
|
+
if (is12) {
|
|
407
|
+
const { hour12, meridiem } = to12(t.hour);
|
|
408
|
+
return `<div class="time-row" part="time">
|
|
409
|
+
<input data-time="hour" type="number" min="1" max="12" value="${hour12}" aria-label="Hour" />
|
|
410
|
+
<span>:</span>
|
|
411
|
+
<input data-time="minute" type="number" min="0" max="59" value="${String(t.minute).padStart(2, "0")}" aria-label="Minute" />
|
|
412
|
+
<button type="button" data-time="meridiem" aria-pressed="${meridiem === "PM"}" aria-label="Toggle AM/PM">${meridiem}</button>
|
|
413
|
+
</div>`;
|
|
414
|
+
}
|
|
415
|
+
return `<div class="time-row" part="time">
|
|
416
|
+
<input data-time="hour" type="number" min="0" max="23" value="${String(t.hour).padStart(2, "0")}" aria-label="Hour" />
|
|
417
|
+
<span>:</span>
|
|
418
|
+
<input data-time="minute" type="number" min="0" max="59" value="${String(t.minute).padStart(2, "0")}" aria-label="Minute" />
|
|
419
|
+
</div>`;
|
|
420
|
+
}
|
|
421
|
+
wireTimeRow() {
|
|
422
|
+
const is12 = this.getAttribute("time-format") === "12";
|
|
423
|
+
const hourEl = this.root.querySelector('[data-time="hour"]');
|
|
424
|
+
const minEl = this.root.querySelector('[data-time="minute"]');
|
|
425
|
+
const ampmEl = this.root.querySelector('[data-time="meridiem"]');
|
|
426
|
+
if (!hourEl || !minEl) return;
|
|
427
|
+
const commit = () => {
|
|
428
|
+
const minute = Math.max(0, Math.min(59, Number(minEl.value) || 0));
|
|
429
|
+
let hour;
|
|
430
|
+
if (is12) {
|
|
431
|
+
const h12 = Math.max(1, Math.min(12, Number(hourEl.value) || 12));
|
|
432
|
+
const meridiem = ampmEl?.getAttribute("aria-pressed") === "true" ? "PM" : "AM";
|
|
433
|
+
hour = from12(h12, meridiem);
|
|
434
|
+
} else {
|
|
435
|
+
hour = Math.max(0, Math.min(23, Number(hourEl.value) || 0));
|
|
436
|
+
}
|
|
437
|
+
this.setTime({ hour, minute });
|
|
438
|
+
};
|
|
439
|
+
hourEl.addEventListener("change", commit);
|
|
440
|
+
minEl.addEventListener("change", commit);
|
|
441
|
+
ampmEl?.addEventListener("click", () => {
|
|
442
|
+
const isPm = ampmEl.getAttribute("aria-pressed") === "true";
|
|
443
|
+
ampmEl.setAttribute("aria-pressed", String(!isPm));
|
|
444
|
+
ampmEl.textContent = !isPm ? "PM" : "AM";
|
|
445
|
+
commit();
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
render() {
|
|
449
|
+
const model = buildMonthModel(this.cal, this.view, {
|
|
450
|
+
selected: this._mode === "single" ? this.selected : null,
|
|
451
|
+
selectedList: this._mode === "multiple" ? this.selectedList : void 0,
|
|
452
|
+
rangeStart: this._mode === "range" ? this.rangeStart : null,
|
|
453
|
+
rangeEnd: this._mode === "range" ? this.rangeEnd ?? this.hoverDate : null,
|
|
454
|
+
isDisabled: this.buildDisabledFn(),
|
|
455
|
+
today: /* @__PURE__ */ new Date()
|
|
456
|
+
});
|
|
457
|
+
this.lastCells = model.weeks.flat();
|
|
458
|
+
const headerGreg = this.cal.hijriToGregorian({
|
|
459
|
+
year: this.view.year,
|
|
460
|
+
month: this.view.month,
|
|
461
|
+
day: 1
|
|
462
|
+
});
|
|
463
|
+
const title = `${translitMonthNames[this.view.month - 1] ?? ""} ${this.view.year}`;
|
|
464
|
+
const subtitle = headerGreg.toLocaleDateString("en-US", {
|
|
465
|
+
month: "long",
|
|
466
|
+
year: "numeric",
|
|
467
|
+
timeZone: "UTC"
|
|
468
|
+
});
|
|
469
|
+
this.root.innerHTML = `<style>${styles}</style>
|
|
470
|
+
<div class="cal" role="application" aria-label="Hijri date picker">
|
|
471
|
+
<div class="header">
|
|
472
|
+
<button type="button" part="nav-prev" aria-label="Previous month" data-nav="-1">\u2039</button>
|
|
473
|
+
<div class="title">${title}<small>${subtitle}</small></div>
|
|
474
|
+
<button type="button" part="nav-next" aria-label="Next month" data-nav="1">\u203A</button>
|
|
475
|
+
</div>
|
|
476
|
+
<div class="grid" role="grid">
|
|
477
|
+
<div class="dow-row" role="row">
|
|
478
|
+
${weekdayNames.map((d) => `<div class="dow" role="columnheader">${d.slice(0, 2)}</div>`).join("")}
|
|
479
|
+
</div>
|
|
480
|
+
${model.weeks.map((week, w) => {
|
|
481
|
+
const cells = week.map((cell, d) => {
|
|
482
|
+
const i = w * 7 + d;
|
|
483
|
+
const cls = [
|
|
484
|
+
"cell",
|
|
485
|
+
cell.inCurrentMonth ? "" : "out",
|
|
486
|
+
cell.isToday ? "today" : "",
|
|
487
|
+
cell.rangeStart ? "range-start" : "",
|
|
488
|
+
cell.rangeEnd ? "range-end" : "",
|
|
489
|
+
cell.inRange ? "in-range" : ""
|
|
490
|
+
].filter(Boolean).join(" ");
|
|
491
|
+
const ariaSelected = cell.selected || cell.rangeStart || cell.rangeEnd;
|
|
492
|
+
const label = `${formatHijri(cell.hijri, "D MMMM YYYY")} (${toIso(cell.gregorian)})`;
|
|
493
|
+
return `<button type="button" part="day" class="${cls}" role="gridcell"
|
|
494
|
+
data-i="${i}" aria-selected="${ariaSelected}" aria-label="${label}"
|
|
495
|
+
tabindex="-1" ${cell.disabled ? "disabled" : ""}>
|
|
496
|
+
<span class="hijri">${cell.hijri.day}</span>
|
|
497
|
+
<span class="greg">${cell.gregorian.getUTCDate()}</span>
|
|
498
|
+
</button>`;
|
|
499
|
+
}).join("");
|
|
500
|
+
return `<div class="week" role="row">${cells}</div>`;
|
|
501
|
+
}).join("")}
|
|
502
|
+
</div>
|
|
503
|
+
${this.renderTimeRow()}
|
|
504
|
+
</div>`;
|
|
505
|
+
const flat = this.lastCells;
|
|
506
|
+
this.root.querySelectorAll("[data-nav]").forEach((btn) => {
|
|
507
|
+
btn.addEventListener("click", () => this.navigate(Number(btn.dataset.nav)));
|
|
508
|
+
});
|
|
509
|
+
this.root.querySelectorAll("[data-i]").forEach((btn) => {
|
|
510
|
+
const cell = flat[Number(btn.dataset.i)];
|
|
511
|
+
btn.addEventListener("click", () => this.select(cell));
|
|
512
|
+
if (this._mode === "range" && this.rangeStart && !this.rangeEnd) {
|
|
513
|
+
btn.addEventListener("mouseenter", () => {
|
|
514
|
+
this.hoverDate = cell.hijri;
|
|
515
|
+
this.paintRange(cell.hijri);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
this.wireTimeRow();
|
|
520
|
+
const dayButtons = Array.from(
|
|
521
|
+
this.root.querySelectorAll("[data-i]")
|
|
522
|
+
);
|
|
523
|
+
const selectedIdx = dayButtons.findIndex(
|
|
524
|
+
(b) => b.getAttribute("aria-selected") === "true"
|
|
525
|
+
);
|
|
526
|
+
const initialIdx = selectedIdx !== -1 ? selectedIdx : dayButtons.findIndex((b) => !b.disabled);
|
|
527
|
+
dayButtons.forEach((b, i) => b.tabIndex = i === initialIdx ? 0 : -1);
|
|
528
|
+
const moveFocus = (from, delta) => {
|
|
529
|
+
let i = from + delta;
|
|
530
|
+
while (i >= 0 && i < dayButtons.length && dayButtons[i]?.disabled) i += delta;
|
|
531
|
+
const target = dayButtons[i];
|
|
532
|
+
if (target) {
|
|
533
|
+
dayButtons.forEach((b) => b.tabIndex = -1);
|
|
534
|
+
target.tabIndex = 0;
|
|
535
|
+
target.focus();
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
const grid = this.root.querySelector(".grid");
|
|
539
|
+
grid.addEventListener("keydown", (e) => {
|
|
540
|
+
const active = this.root.activeElement;
|
|
541
|
+
const fromEl = active && active.matches?.("[data-i]") ? active : e.target?.closest?.("[data-i]") ?? null;
|
|
542
|
+
const idx = fromEl ? dayButtons.indexOf(fromEl) : -1;
|
|
543
|
+
const deltas = {
|
|
544
|
+
ArrowRight: 1,
|
|
545
|
+
ArrowLeft: -1,
|
|
546
|
+
ArrowDown: 7,
|
|
547
|
+
ArrowUp: -7
|
|
548
|
+
};
|
|
549
|
+
if (e.key in deltas) {
|
|
550
|
+
e.preventDefault();
|
|
551
|
+
moveFocus(idx === -1 ? initialIdx : idx, deltas[e.key] ?? 0);
|
|
552
|
+
} else if (e.key === "Enter" || e.key === " ") {
|
|
553
|
+
e.preventDefault();
|
|
554
|
+
const useIdx = idx === -1 ? initialIdx : idx;
|
|
555
|
+
if (useIdx >= 0) {
|
|
556
|
+
const cell = flat[Number(dayButtons[useIdx]?.dataset.i)];
|
|
557
|
+
if (cell) this.select(cell);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
grid.addEventListener("mouseleave", () => {
|
|
562
|
+
if (this._mode === "range" && this.rangeStart && !this.rangeEnd) {
|
|
563
|
+
this.hoverDate = null;
|
|
564
|
+
this.paintRange(null);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// src/index.ts
|
|
571
|
+
if (typeof customElements !== "undefined" && !customElements.get("hijri-datepicker")) {
|
|
572
|
+
customElements.define("hijri-datepicker", HijriDatepicker);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export { HijriDatepicker, buildMonthModel };
|
|
576
|
+
//# sourceMappingURL=index.js.map
|
|
577
|
+
//# sourceMappingURL=index.js.map
|