@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/css/components-ext.css +58 -0
- package/dist/css/components-ext.css +58 -0
- package/dist/kupola.cjs.js +21 -21
- package/dist/kupola.cjs.js.map +1 -1
- package/dist/kupola.esm.js +1110 -970
- package/dist/kupola.esm.js.map +1 -1
- package/dist/kupola.umd.js +26 -26
- package/dist/kupola.umd.js.map +1 -1
- package/dist/types/kupola.d.ts +135 -0
- package/js/datepicker.js +177 -106
- package/js/dropdown.js +186 -98
- package/js/kupola-config.js +64 -36
- package/js/message.js +66 -53
- package/js/modal.js +62 -59
- package/js/notification.js +63 -60
- package/js/security.js +52 -0
- package/js/select.js +88 -3
- package/js/tooltip.js +300 -297
- package/js/validation.js +155 -140
- package/package.json +2 -1
- package/types/kupola.d.ts +135 -0
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
|
|
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 (
|
|
162
|
-
this.calendarEl.style.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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;
|