@kupola/kupola 1.5.2 → 1.5.4

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/js/datepicker.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { globalEvents } from './global-events.js';
2
2
  import { kupolaInitializer } from './initializer.js';
3
+ import { getUiConfig } from './kupola-config.js';
3
4
 
4
5
  class Datepicker {
5
6
  constructor(element, options = {}) {
@@ -9,25 +10,29 @@ class Datepicker {
9
10
  this.icon = element.querySelector('.ds-datepicker__icon');
10
11
  this.calendarEl = element.querySelector('.ds-datepicker__calendar');
11
12
  this.scope = `datepicker-${Math.random().toString(36).substr(2, 9)}`;
12
-
13
+
13
14
  // Options
15
+ const uiConfig = getUiConfig();
16
+ const defaultWeekStart = uiConfig.datepicker?.weekStart !== undefined ? uiConfig.datepicker.weekStart : 1;
17
+
14
18
  this.format = options.format || element.getAttribute('data-datepicker-format') || 'YYYY-MM-DD';
15
19
  this.range = options.range || element.hasAttribute('data-datepicker-range');
16
20
  this.minDate = options.minDate || element.getAttribute('data-datepicker-min') || null;
17
21
  this.maxDate = options.maxDate || element.getAttribute('data-datepicker-max') || null;
18
22
  this.disabledDate = options.disabledDate || null; // function(date) => boolean
19
- this.weekStart = options.weekStart || parseInt(element.getAttribute('data-datepicker-week-start')) || 0; // 0=Sun, 1=Mon
23
+ this.weekStart = options.weekStart !== undefined ? options.weekStart : (parseInt(element.getAttribute('data-datepicker-week-start')) || defaultWeekStart); // 0=Sun, 1=Mon
24
+ this.appendToBody = options.appendToBody !== false;
20
25
  this.placeholder = options.placeholder || element.getAttribute('data-datepicker-placeholder') || '';
21
26
  this.showToday = options.showToday !== false;
22
27
  this.showWeekNumber = options.showWeekNumber || element.hasAttribute('data-datepicker-week-number');
23
28
  this.onChange = options.onChange || null;
24
-
29
+
25
30
  // i18n
26
- this.months = options.months || ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
27
- this.weekDays = options.weekDays || ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
31
+ this.months = options.months || [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
32
+ this.weekDays = options.weekDays || [ 'Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa' ];
28
33
  this.todayText = options.todayText || 'Today';
29
34
  this.clearText = options.clearText || 'Clear';
30
-
35
+
31
36
  // State
32
37
  this.currentDate = new Date();
33
38
  this.viewMode = 'days'; // days | months | years
@@ -35,11 +40,12 @@ class Datepicker {
35
40
  this.rangeStart = null;
36
41
  this.rangeEnd = null;
37
42
  this.isSelectingEnd = false;
38
-
43
+
39
44
  this._iconClickHandler = null;
40
45
  this._inputClickHandler = null;
41
46
  this._endInputClickHandler = null;
42
47
  this._documentClickHandler = null;
48
+ this._originalParent = null;
43
49
  this._documentClickListener = null;
44
50
  this._resizeHandler = null;
45
51
  this._resizeListener = null;
@@ -47,8 +53,8 @@ class Datepicker {
47
53
  }
48
54
 
49
55
  init() {
50
- if (!this.calendarEl) return;
51
- if (this.element.__kupolaInitialized) return;
56
+ if (!this.calendarEl) {return;}
57
+ if (this.element.__kupolaInitialized) {return;}
52
58
 
53
59
  // Parse initial value
54
60
  if (this.input && this.input.value) {
@@ -72,8 +78,8 @@ class Datepicker {
72
78
  this._iconClickHandler = (e) => this.toggleCalendar(e);
73
79
  this._inputClickHandler = (e) => this.toggleCalendar(e);
74
80
 
75
- if (this.icon) this.icon.addEventListener('click', this._iconClickHandler);
76
- if (this.input) this.input.addEventListener('click', this._inputClickHandler);
81
+ if (this.icon) {this.icon.addEventListener('click', this._iconClickHandler);}
82
+ if (this.input) {this.input.addEventListener('click', this._inputClickHandler);}
77
83
  if (this.endInput) {
78
84
  this._endInputClickHandler = (e) => {
79
85
  this.isSelectingEnd = true;
@@ -98,18 +104,18 @@ class Datepicker {
98
104
  }
99
105
 
100
106
  _parseDate(str) {
101
- if (!str) return null;
107
+ if (!str) {return null;}
102
108
  const parts = str.split('-');
103
- if (parts.length === 3) return new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
109
+ if (parts.length === 3) {return new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));}
104
110
  return null;
105
111
  }
106
112
 
107
113
  _formatDate(date) {
108
- if (!date) return '';
114
+ if (!date) {return '';}
109
115
  const y = date.getFullYear();
110
116
  const m = String(date.getMonth() + 1).padStart(2, '0');
111
117
  const d = String(date.getDate()).padStart(2, '0');
112
-
118
+
113
119
  return this.format
114
120
  .replace('YYYY', y)
115
121
  .replace('MM', m)
@@ -119,11 +125,11 @@ class Datepicker {
119
125
  _isDateDisabled(date) {
120
126
  if (this.minDate) {
121
127
  const min = typeof this.minDate === 'string' ? this._parseDate(this.minDate) : this.minDate;
122
- if (date < min) return true;
128
+ if (date < min) {return true;}
123
129
  }
124
130
  if (this.maxDate) {
125
131
  const max = typeof this.maxDate === 'string' ? this._parseDate(this.maxDate) : this.maxDate;
126
- if (date > max) return true;
132
+ if (date > max) {return true;}
127
133
  }
128
134
  if (this.disabledDate) {
129
135
  return this.disabledDate(date);
@@ -137,12 +143,12 @@ class Datepicker {
137
143
  }
138
144
 
139
145
  _isSameDay(d1, d2) {
140
- if (!d1 || !d2) return false;
146
+ if (!d1 || !d2) {return false;}
141
147
  return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate();
142
148
  }
143
149
 
144
150
  _isInRange(date) {
145
- if (!this.range || !this.rangeStart || !this.rangeEnd) return false;
151
+ if (!this.range || !this.rangeStart || !this.rangeEnd) {return false;}
146
152
  const time = date.getTime();
147
153
  const start = Math.min(this.rangeStart.getTime(), this.rangeEnd.getTime());
148
154
  const end = Math.max(this.rangeStart.getTime(), this.rangeEnd.getTime());
@@ -153,29 +159,44 @@ class Datepicker {
153
159
  const pickerRect = this.element.getBoundingClientRect();
154
160
  const calendarRect = this.calendarEl.getBoundingClientRect();
155
161
  const viewportHeight = window.innerHeight;
156
-
162
+
157
163
  const spaceBelow = viewportHeight - pickerRect.bottom;
158
164
  const spaceAbove = pickerRect.top;
159
165
  const calendarHeight = calendarRect.height || 320;
160
-
161
- if (spaceBelow >= calendarHeight) {
162
- this.calendarEl.style.top = 'calc(100% + 4px)';
163
- this.calendarEl.style.bottom = 'auto';
164
- } else if (spaceAbove >= calendarHeight) {
165
- this.calendarEl.style.top = 'auto';
166
- this.calendarEl.style.bottom = 'calc(100% + 4px)';
166
+
167
+ if (this.appendToBody) {
168
+ this.calendarEl.style.left = `${pickerRect.left}px`;
169
+
170
+ if (spaceBelow >= calendarHeight) {
171
+ this.calendarEl.style.top = `${pickerRect.bottom + 4}px`;
172
+ this.calendarEl.style.bottom = 'auto';
173
+ } else if (spaceAbove >= calendarHeight) {
174
+ this.calendarEl.style.top = `${pickerRect.top - calendarHeight - 4}px`;
175
+ this.calendarEl.style.bottom = 'auto';
176
+ } else {
177
+ this.calendarEl.style.top = `${pickerRect.bottom + 4}px`;
178
+ this.calendarEl.style.bottom = 'auto';
179
+ }
167
180
  } else {
168
- this.calendarEl.style.top = 'calc(100% + 4px)';
169
- this.calendarEl.style.bottom = 'auto';
181
+ if (spaceBelow >= calendarHeight) {
182
+ this.calendarEl.style.top = 'calc(100% + 4px)';
183
+ this.calendarEl.style.bottom = 'auto';
184
+ } else if (spaceAbove >= calendarHeight) {
185
+ this.calendarEl.style.top = 'auto';
186
+ this.calendarEl.style.bottom = 'calc(100% + 4px)';
187
+ } else {
188
+ this.calendarEl.style.top = 'calc(100% + 4px)';
189
+ this.calendarEl.style.bottom = 'auto';
190
+ }
170
191
  }
171
192
  }
172
193
 
173
194
  toggleCalendar(e) {
174
195
  e.preventDefault();
175
196
  e.stopPropagation();
176
-
197
+
177
198
  const isVisible = this.calendarEl.style.display === 'block';
178
-
199
+
179
200
  // Close all other datepickers
180
201
  document.querySelectorAll('.ds-datepicker__calendar').forEach(c => {
181
202
  if (c !== this.calendarEl) {
@@ -183,8 +204,12 @@ class Datepicker {
183
204
  c.setAttribute('hidden', '');
184
205
  }
185
206
  });
186
-
207
+
187
208
  if (!isVisible) {
209
+ if (this.appendToBody) {
210
+ this._appendCalendarToBody();
211
+ this._addScrollListener();
212
+ }
188
213
  this.calendarEl.style.display = 'block';
189
214
  this.calendarEl.removeAttribute('hidden');
190
215
  this.calculatePosition();
@@ -192,13 +217,55 @@ class Datepicker {
192
217
  }
193
218
 
194
219
  hideCalendar(e) {
195
- if (!this.element.contains(e.target)) {
220
+ if (!this.element.contains(e.target) && !this.calendarEl.contains(e.target)) {
196
221
  this.calendarEl.style.display = 'none';
197
222
  this.calendarEl.setAttribute('hidden', '');
198
223
  this.viewMode = 'days';
224
+ if (this.appendToBody) {
225
+ this._restoreCalendarFromBody();
226
+ this._removeScrollListener();
227
+ }
228
+ }
229
+ }
230
+
231
+ _addScrollListener() {
232
+ this._scrollHandler = () => {
233
+ this.hideCalendar({ target: document });
234
+ };
235
+ window.addEventListener('scroll', this._scrollHandler, true);
236
+ }
237
+
238
+ _removeScrollListener() {
239
+ if (this._scrollHandler) {
240
+ window.removeEventListener('scroll', this._scrollHandler, true);
241
+ this._scrollHandler = null;
199
242
  }
200
243
  }
201
244
 
245
+ _appendCalendarToBody() {
246
+ if (!this.calendarEl) return;
247
+ this._originalParent = this.calendarEl.parentNode;
248
+ this._originalPosition = this.calendarEl.style.position;
249
+ this._originalTop = this.calendarEl.style.top;
250
+ this._originalLeft = this.calendarEl.style.left;
251
+ this._originalWidth = this.calendarEl.style.width;
252
+
253
+ this.calendarEl.style.position = 'fixed';
254
+ this.calendarEl.style.zIndex = '9999';
255
+ document.body.appendChild(this.calendarEl);
256
+ }
257
+
258
+ _restoreCalendarFromBody() {
259
+ if (!this.calendarEl || !this._originalParent) return;
260
+ this._originalParent.appendChild(this.calendarEl);
261
+ this.calendarEl.style.position = this._originalPosition || '';
262
+ this.calendarEl.style.top = this._originalTop || '';
263
+ this.calendarEl.style.left = this._originalLeft || '';
264
+ this.calendarEl.style.width = this._originalWidth || '';
265
+ this.calendarEl.style.zIndex = '';
266
+ this._originalParent = null;
267
+ }
268
+
202
269
  resizeHandler() {
203
270
  if (this.calendarEl.style.display === 'block') {
204
271
  this.calculatePosition();
@@ -207,13 +274,13 @@ class Datepicker {
207
274
 
208
275
  _renderCalendar() {
209
276
  const calendar = this.calendarEl;
210
- if (!calendar) return;
211
-
277
+ if (!calendar) {return;}
278
+
212
279
  // Clean up old listeners
213
280
  calendar.querySelectorAll('.ds-datepicker__day').forEach(dayEl => {
214
- if (dayEl._dayClickHandler) dayEl.removeEventListener('click', dayEl._dayClickHandler);
281
+ if (dayEl._dayClickHandler) {dayEl.removeEventListener('click', dayEl._dayClickHandler);}
215
282
  });
216
-
283
+
217
284
  if (this.viewMode === 'years') {
218
285
  this._renderYearsView();
219
286
  return;
@@ -222,43 +289,43 @@ class Datepicker {
222
289
  this._renderMonthsView();
223
290
  return;
224
291
  }
225
-
292
+
226
293
  const year = this.currentDate.getFullYear();
227
294
  const month = this.currentDate.getMonth();
228
-
295
+
229
296
  calendar.innerHTML = '';
230
-
297
+
231
298
  // Header with navigation
232
299
  const header = document.createElement('div');
233
300
  header.className = 'ds-datepicker__header';
234
-
301
+
235
302
  const prevBtn = document.createElement('button');
236
303
  prevBtn.className = 'ds-datepicker__nav ds-datepicker__nav--prev';
237
304
  prevBtn.type = 'button';
238
305
  prevBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>';
239
306
  prevBtn.addEventListener('click', (e) => { e.stopPropagation(); this._prevMonth(); });
240
-
307
+
241
308
  const titleBtn = document.createElement('button');
242
309
  titleBtn.className = 'ds-datepicker__title';
243
310
  titleBtn.type = 'button';
244
311
  titleBtn.textContent = `${year} ${this.months[month]}`;
245
312
  titleBtn.addEventListener('click', (e) => { e.stopPropagation(); this.viewMode = 'months'; this._renderCalendar(); });
246
-
313
+
247
314
  const nextBtn = document.createElement('button');
248
315
  nextBtn.className = 'ds-datepicker__nav ds-datepicker__nav--next';
249
316
  nextBtn.type = 'button';
250
317
  nextBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>';
251
318
  nextBtn.addEventListener('click', (e) => { e.stopPropagation(); this._nextMonth(); });
252
-
319
+
253
320
  header.appendChild(prevBtn);
254
321
  header.appendChild(titleBtn);
255
322
  header.appendChild(nextBtn);
256
323
  calendar.appendChild(header);
257
-
324
+
258
325
  // Week day headers
259
326
  const weekHeader = document.createElement('div');
260
327
  weekHeader.className = 'ds-datepicker__weekdays';
261
- const orderedDays = [...this.weekDays.slice(this.weekStart), ...this.weekDays.slice(0, this.weekStart)];
328
+ const orderedDays = [ ...this.weekDays.slice(this.weekStart), ...this.weekDays.slice(0, this.weekStart) ];
262
329
  orderedDays.forEach(day => {
263
330
  const span = document.createElement('span');
264
331
  span.className = 'ds-datepicker__weekday';
@@ -266,22 +333,22 @@ class Datepicker {
266
333
  weekHeader.appendChild(span);
267
334
  });
268
335
  calendar.appendChild(weekHeader);
269
-
336
+
270
337
  // Days grid
271
338
  const daysEl = document.createElement('div');
272
339
  daysEl.className = 'ds-datepicker__days';
273
-
340
+
274
341
  const firstDay = new Date(year, month, 1).getDay();
275
342
  const daysInMonth = new Date(year, month + 1, 0).getDate();
276
343
  const offset = (firstDay - this.weekStart + 7) % 7;
277
-
344
+
278
345
  // Empty cells
279
346
  for (let i = 0; i < offset; i++) {
280
347
  const emptyDay = document.createElement('span');
281
348
  emptyDay.className = 'ds-datepicker__day ds-datepicker__day--empty';
282
349
  daysEl.appendChild(emptyDay);
283
350
  }
284
-
351
+
285
352
  // Day cells
286
353
  for (let day = 1; day <= daysInMonth; day++) {
287
354
  const date = new Date(year, month, day);
@@ -289,11 +356,11 @@ class Datepicker {
289
356
  dayEl.className = 'ds-datepicker__day';
290
357
  dayEl.type = 'button';
291
358
  dayEl.textContent = day;
292
-
359
+
293
360
  const dateStr = this._formatDate(date);
294
-
295
- if (this._isToday(date)) dayEl.classList.add('is-today');
296
-
361
+
362
+ if (this._isToday(date)) {dayEl.classList.add('is-today');}
363
+
297
364
  // Selected state
298
365
  if (this.range) {
299
366
  if (this._isSameDay(date, this.rangeStart) || this._isSameDay(date, this.rangeEnd)) {
@@ -307,39 +374,39 @@ class Datepicker {
307
374
  dayEl.classList.add('is-selected');
308
375
  }
309
376
  }
310
-
377
+
311
378
  // Disabled state
312
379
  if (this._isDateDisabled(date)) {
313
380
  dayEl.classList.add('is-disabled');
314
381
  dayEl.disabled = true;
315
382
  }
316
-
383
+
317
384
  const clickHandler = () => this._selectDate(date);
318
385
  dayEl.addEventListener('click', clickHandler);
319
386
  dayEl._dayClickHandler = clickHandler;
320
-
387
+
321
388
  daysEl.appendChild(dayEl);
322
389
  }
323
-
390
+
324
391
  calendar.appendChild(daysEl);
325
-
392
+
326
393
  // Footer with Today / Clear buttons
327
394
  if (this.showToday) {
328
395
  const footer = document.createElement('div');
329
396
  footer.className = 'ds-datepicker__footer';
330
-
397
+
331
398
  const todayBtn = document.createElement('button');
332
399
  todayBtn.className = 'ds-datepicker__today-btn';
333
400
  todayBtn.type = 'button';
334
401
  todayBtn.textContent = this.todayText;
335
402
  todayBtn.addEventListener('click', (e) => { e.stopPropagation(); this._goToToday(); });
336
-
403
+
337
404
  const clearBtn = document.createElement('button');
338
405
  clearBtn.className = 'ds-datepicker__clear-btn';
339
406
  clearBtn.type = 'button';
340
407
  clearBtn.textContent = this.clearText;
341
408
  clearBtn.addEventListener('click', (e) => { e.stopPropagation(); this._clearDate(); });
342
-
409
+
343
410
  footer.appendChild(todayBtn);
344
411
  footer.appendChild(clearBtn);
345
412
  calendar.appendChild(footer);
@@ -349,46 +416,46 @@ class Datepicker {
349
416
  _renderYearsView() {
350
417
  const calendar = this.calendarEl;
351
418
  calendar.innerHTML = '';
352
-
419
+
353
420
  const currentYear = this.currentDate.getFullYear();
354
421
  const startYear = currentYear - 6;
355
-
422
+
356
423
  const header = document.createElement('div');
357
424
  header.className = 'ds-datepicker__header';
358
-
425
+
359
426
  const prevBtn = document.createElement('button');
360
427
  prevBtn.className = 'ds-datepicker__nav ds-datepicker__nav--prev';
361
428
  prevBtn.type = 'button';
362
429
  prevBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>';
363
430
  prevBtn.addEventListener('click', (e) => { e.stopPropagation(); this.currentDate.setFullYear(this.currentDate.getFullYear() - 12); this._renderCalendar(); });
364
-
431
+
365
432
  const titleBtn = document.createElement('button');
366
433
  titleBtn.className = 'ds-datepicker__title';
367
434
  titleBtn.type = 'button';
368
435
  titleBtn.textContent = `${startYear} - ${startYear + 11}`;
369
436
  titleBtn.addEventListener('click', (e) => { e.stopPropagation(); });
370
-
437
+
371
438
  const nextBtn = document.createElement('button');
372
439
  nextBtn.className = 'ds-datepicker__nav ds-datepicker__nav--next';
373
440
  nextBtn.type = 'button';
374
441
  nextBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>';
375
442
  nextBtn.addEventListener('click', (e) => { e.stopPropagation(); this.currentDate.setFullYear(this.currentDate.getFullYear() + 12); this._renderCalendar(); });
376
-
443
+
377
444
  header.appendChild(prevBtn);
378
445
  header.appendChild(titleBtn);
379
446
  header.appendChild(nextBtn);
380
447
  calendar.appendChild(header);
381
-
448
+
382
449
  const grid = document.createElement('div');
383
450
  grid.className = 'ds-datepicker__years-grid';
384
-
451
+
385
452
  for (let i = 0; i < 12; i++) {
386
453
  const year = startYear + i;
387
454
  const btn = document.createElement('button');
388
455
  btn.className = 'ds-datepicker__year-cell';
389
456
  btn.type = 'button';
390
457
  btn.textContent = year;
391
- if (year === currentYear) btn.classList.add('is-selected');
458
+ if (year === currentYear) {btn.classList.add('is-selected');}
392
459
  btn.addEventListener('click', (e) => {
393
460
  e.stopPropagation();
394
461
  this.currentDate.setFullYear(year);
@@ -397,51 +464,51 @@ class Datepicker {
397
464
  });
398
465
  grid.appendChild(btn);
399
466
  }
400
-
467
+
401
468
  calendar.appendChild(grid);
402
469
  }
403
470
 
404
471
  _renderMonthsView() {
405
472
  const calendar = this.calendarEl;
406
473
  calendar.innerHTML = '';
407
-
474
+
408
475
  const year = this.currentDate.getFullYear();
409
-
476
+
410
477
  const header = document.createElement('div');
411
478
  header.className = 'ds-datepicker__header';
412
-
479
+
413
480
  const prevBtn = document.createElement('button');
414
481
  prevBtn.className = 'ds-datepicker__nav ds-datepicker__nav--prev';
415
482
  prevBtn.type = 'button';
416
483
  prevBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>';
417
484
  prevBtn.addEventListener('click', (e) => { e.stopPropagation(); this.currentDate.setFullYear(this.currentDate.getFullYear() - 1); this._renderCalendar(); });
418
-
485
+
419
486
  const titleBtn = document.createElement('button');
420
487
  titleBtn.className = 'ds-datepicker__title';
421
488
  titleBtn.type = 'button';
422
489
  titleBtn.textContent = year;
423
490
  titleBtn.addEventListener('click', (e) => { e.stopPropagation(); this.viewMode = 'years'; this._renderCalendar(); });
424
-
491
+
425
492
  const nextBtn = document.createElement('button');
426
493
  nextBtn.className = 'ds-datepicker__nav ds-datepicker__nav--next';
427
494
  nextBtn.type = 'button';
428
495
  nextBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>';
429
496
  nextBtn.addEventListener('click', (e) => { e.stopPropagation(); this.currentDate.setFullYear(this.currentDate.getFullYear() + 1); this._renderCalendar(); });
430
-
497
+
431
498
  header.appendChild(prevBtn);
432
499
  header.appendChild(titleBtn);
433
500
  header.appendChild(nextBtn);
434
501
  calendar.appendChild(header);
435
-
502
+
436
503
  const grid = document.createElement('div');
437
504
  grid.className = 'ds-datepicker__months-grid';
438
-
505
+
439
506
  this.months.forEach((monthName, idx) => {
440
507
  const btn = document.createElement('button');
441
508
  btn.className = 'ds-datepicker__month-cell';
442
509
  btn.type = 'button';
443
510
  btn.textContent = monthName;
444
- if (idx === this.currentDate.getMonth()) btn.classList.add('is-selected');
511
+ if (idx === this.currentDate.getMonth()) {btn.classList.add('is-selected');}
445
512
  btn.addEventListener('click', (e) => {
446
513
  e.stopPropagation();
447
514
  this.currentDate.setMonth(idx);
@@ -450,13 +517,13 @@ class Datepicker {
450
517
  });
451
518
  grid.appendChild(btn);
452
519
  });
453
-
520
+
454
521
  calendar.appendChild(grid);
455
522
  }
456
523
 
457
524
  _selectDate(date) {
458
- if (this._isDateDisabled(date)) return;
459
-
525
+ if (this._isDateDisabled(date)) {return;}
526
+
460
527
  if (this.range) {
461
528
  if (!this.isSelectingEnd || !this.rangeStart) {
462
529
  this.rangeStart = date;
@@ -466,13 +533,13 @@ class Datepicker {
466
533
  this.rangeEnd = date;
467
534
  // Ensure start <= end
468
535
  if (this.rangeEnd < this.rangeStart) {
469
- [this.rangeStart, this.rangeEnd] = [this.rangeEnd, this.rangeStart];
536
+ [ this.rangeStart, this.rangeEnd ] = [ this.rangeEnd, this.rangeStart ];
470
537
  }
471
538
  this.isSelectingEnd = false;
472
-
473
- if (this.input) this.input.value = this._formatDate(this.rangeStart);
474
- if (this.endInput) this.endInput.value = this._formatDate(this.rangeEnd);
475
-
539
+
540
+ if (this.input) {this.input.value = this._formatDate(this.rangeStart);}
541
+ if (this.endInput) {this.endInput.value = this._formatDate(this.rangeEnd);}
542
+
476
543
  this.calendarEl.style.display = 'none';
477
544
  this.calendarEl.setAttribute('hidden', '');
478
545
  this._fireChange();
@@ -488,7 +555,7 @@ class Datepicker {
488
555
  this._fireChange();
489
556
  return;
490
557
  }
491
-
558
+
492
559
  this._renderCalendar();
493
560
  }
494
561
 
@@ -496,7 +563,7 @@ class Datepicker {
496
563
  if (this.input) {
497
564
  this.input.dispatchEvent(new Event('change', { bubbles: true }));
498
565
  }
499
-
566
+
500
567
  if (this.onChange) {
501
568
  if (this.range) {
502
569
  this.onChange({ start: this.rangeStart, end: this.rangeEnd, startStr: this._formatDate(this.rangeStart), endStr: this._formatDate(this.rangeEnd) });
@@ -504,15 +571,15 @@ class Datepicker {
504
571
  this.onChange({ date: this.selectedDate, dateStr: this._formatDate(this.selectedDate) });
505
572
  }
506
573
  }
507
-
574
+
508
575
  this.element.dispatchEvent(new CustomEvent('kupola:datepicker-change', {
509
576
  detail: {
510
577
  date: this.selectedDate,
511
578
  dateStr: this._formatDate(this.selectedDate),
512
579
  rangeStart: this.rangeStart,
513
- rangeEnd: this.rangeEnd
580
+ rangeEnd: this.rangeEnd,
514
581
  },
515
- bubbles: true
582
+ bubbles: true,
516
583
  }));
517
584
  }
518
585
 
@@ -529,7 +596,7 @@ class Datepicker {
529
596
  _goToToday() {
530
597
  const today = new Date();
531
598
  this.currentDate = new Date(today);
532
-
599
+
533
600
  if (!this._isDateDisabled(today)) {
534
601
  this._selectDate(today);
535
602
  } else {
@@ -542,8 +609,8 @@ class Datepicker {
542
609
  this.rangeStart = null;
543
610
  this.rangeEnd = null;
544
611
  this.isSelectingEnd = false;
545
- if (this.input) this.input.value = '';
546
- if (this.endInput) this.endInput.value = '';
612
+ if (this.input) {this.input.value = '';}
613
+ if (this.endInput) {this.endInput.value = '';}
547
614
  this.calendarEl.style.display = 'none';
548
615
  this.calendarEl.setAttribute('hidden', '');
549
616
  this._fireChange();
@@ -555,7 +622,7 @@ class Datepicker {
555
622
  if (d) {
556
623
  this.selectedDate = d;
557
624
  this.currentDate = new Date(d);
558
- if (this.input) this.input.value = this._formatDate(d);
625
+ if (this.input) {this.input.value = this._formatDate(d);}
559
626
  this._renderCalendar();
560
627
  }
561
628
  }
@@ -567,18 +634,18 @@ class Datepicker {
567
634
  setRange(start, end) {
568
635
  this.rangeStart = typeof start === 'string' ? this._parseDate(start) : start;
569
636
  this.rangeEnd = typeof end === 'string' ? this._parseDate(end) : end;
570
- if (this.input) this.input.value = this._formatDate(this.rangeStart);
571
- if (this.endInput) this.endInput.value = this._formatDate(this.rangeEnd);
637
+ if (this.input) {this.input.value = this._formatDate(this.rangeStart);}
638
+ if (this.endInput) {this.endInput.value = this._formatDate(this.rangeEnd);}
572
639
  this._renderCalendar();
573
640
  }
574
641
 
575
642
  destroy() {
576
- if (!this.element.__kupolaInitialized) return;
643
+ if (!this.element.__kupolaInitialized) {return;}
577
644
 
578
- if (this.icon && this._iconClickHandler) this.icon.removeEventListener('click', this._iconClickHandler);
579
- if (this.input && this._inputClickHandler) this.input.removeEventListener('click', this._inputClickHandler);
580
- if (this.endInput && this._endInputClickHandler) this.endInput.removeEventListener('click', this._endInputClickHandler);
581
- if (this._keydownHandler) document.removeEventListener('keydown', this._keydownHandler);
645
+ if (this.icon && this._iconClickHandler) {this.icon.removeEventListener('click', this._iconClickHandler);}
646
+ if (this.input && this._inputClickHandler) {this.input.removeEventListener('click', this._inputClickHandler);}
647
+ if (this.endInput && this._endInputClickHandler) {this.endInput.removeEventListener('click', this._endInputClickHandler);}
648
+ if (this._keydownHandler) {document.removeEventListener('keydown', this._keydownHandler);}
582
649
 
583
650
  if (this._documentClickListener && this._documentClickListener.unsubscribe) {
584
651
  this._documentClickListener.unsubscribe();
@@ -594,10 +661,14 @@ class Datepicker {
594
661
 
595
662
  if (this.calendarEl) {
596
663
  this.calendarEl.querySelectorAll('.ds-datepicker__day').forEach(dayEl => {
597
- if (dayEl._dayClickHandler) dayEl.removeEventListener('click', dayEl._dayClickHandler);
664
+ if (dayEl._dayClickHandler) {dayEl.removeEventListener('click', dayEl._dayClickHandler);}
598
665
  });
599
666
  }
600
667
 
668
+ if (this.appendToBody && this._originalParent) {
669
+ this._restoreCalendarFromBody();
670
+ }
671
+
601
672
  this._documentClickHandler = null;
602
673
  this._resizeHandler = null;
603
674
  this._documentClickListener = null;