@luftborn/custom-elements 2.8.3 → 2.8.5

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.
@@ -1,5 +1,5 @@
1
1
  import { CustomDatepickerStyles } from "./CustomDatepickerStyles";
2
- import { defaultDateFormat, formatDate, isMobileDevice, supportedDateFormats } from "./CustomDatepickerUtils";
2
+ import { convertStringToDate, defaultDateFormat, formatDate, isMobileDevice, supportedDateFormats } from "./CustomDatepickerUtils";
3
3
  import { GetMonths, GetWeekdays } from "./DatepickerTranslations";
4
4
 
5
5
  type CustomDatepickerOptions = {
@@ -35,6 +35,7 @@ export default class CustomDatepicker {
35
35
  private lastDayOfMonth: Date;
36
36
  private firstDay: number;
37
37
  private lastDay: number;
38
+ private language: string;
38
39
 
39
40
  private months: string[] = [];
40
41
  private daysOfWeek: string[] = [];
@@ -59,7 +60,8 @@ export default class CustomDatepicker {
59
60
  this.firstDay = this.firstDayOfMonth.getDay();
60
61
  this.lastDay = this.lastDayOfMonth.getDate();
61
62
 
62
- this.months = GetMonths(options.language);
63
+ this.language = options.language || 'en';
64
+ this.months = GetMonths(this.language);
63
65
  this.daysOfWeek = GetWeekdays(options.language, 'short');
64
66
 
65
67
  this.createDatePickerElements();
@@ -70,6 +72,7 @@ export default class CustomDatepicker {
70
72
 
71
73
  private createDatePickerElements() {
72
74
  this.datepicker = document.createElement('div');
75
+ this.datepicker.id = this.input.id + '-datepicker';
73
76
  this.datepicker.classList.add('datepicker');
74
77
  this.header = document.createElement('div');
75
78
  this.header.classList.add('header');
@@ -116,11 +119,7 @@ export default class CustomDatepicker {
116
119
  today.onclick = () => this.setDateForToday();
117
120
  actions.appendChild(today);
118
121
 
119
- if (this.input.parentElement) {
120
- this.input.parentElement.appendChild(this.datepicker);
121
- } else {
122
- document.body.appendChild(this.datepicker);
123
- }
122
+ document.body.appendChild(this.datepicker);
124
123
 
125
124
  this.applyStyles();
126
125
  }
@@ -151,7 +150,8 @@ export default class CustomDatepicker {
151
150
  }
152
151
 
153
152
  private renderCalendar() {
154
- this.monthYear.innerHTML = this.months[this.currentMonth] + ' ' + this.currentYear;
153
+ const monthName = this.months[this.currentMonth];
154
+ this.monthYear.innerHTML = monthName.charAt(0).toUpperCase() + monthName.slice(1) + ' ' + this.currentYear;
155
155
  this.weekdays.innerHTML = '';
156
156
  for (let i = 0; i < this.daysOfWeek.length; i++) {
157
157
  let span = document.createElement('span');
@@ -192,25 +192,34 @@ export default class CustomDatepicker {
192
192
  this.selectedYear = this.currentYear;
193
193
  moveMonthSteps = 1;
194
194
  }
195
- this.selectedDate = new Date(this.selectedYear, this.selectedMonth, this.selectedDay);
196
- let selectedDayElement = this.days.querySelector('.selected-day');
197
- if (selectedDayElement) {
198
- selectedDayElement.classList.remove('selected-day');
199
- }
200
- span.classList.add('selected-day');
201
-
202
- const formattedDate = formatDate(this.selectedDate, this.dateFormat);
203
- this.input.value = formattedDate;
204
-
205
- this.showPicker(false);
195
+ this.setSelectedDate(moveMonthSteps, span);
206
196
  };
207
197
  this.days.appendChild(span);
208
198
  }
209
-
210
199
  this.positionPicker();
211
200
  }
212
201
 
202
+ private setSelectedDate(moveMonthSteps: number = 0, span?: HTMLElement) {
203
+ this.selectedDate = new Date(this.selectedYear, this.selectedMonth, this.selectedDay);
204
+ let selectedDayElement = this.days.querySelector('.selected-day');
205
+ if (selectedDayElement) {
206
+ selectedDayElement.classList.remove('selected-day');
207
+ }
208
+ span.classList.add('selected-day');
209
+
210
+ const formattedDate = formatDate(this.selectedDate, this.dateFormat, this.language);
211
+ this.input.value = formattedDate;
212
+
213
+ this.showPicker(false);
214
+
215
+ if (moveMonthSteps !== 0) {
216
+ this.moveMonth(moveMonthSteps);
217
+ }
218
+ }
219
+
213
220
  private initSelectMonthYear() {
221
+ let tempCurrentYear = this.currentYear;
222
+
214
223
  this.selectMonthYear.style.display = 'none';
215
224
  this.selectMonthYear.style.height = '200px';
216
225
  if (isMobileDevice()) {
@@ -225,8 +234,8 @@ export default class CustomDatepicker {
225
234
  span.classList.add('select-month');
226
235
  monthsContainer.appendChild(span);
227
236
  span.onclick = () => {
228
- this.selectedMonth = this.months.findIndex(month => month.substring(0, 3) === span.innerHTML);
229
- let moveMonthSteps = (this.selectedYear - this.currentYear) * 12 + this.selectedMonth - this.currentMonth;
237
+ const tempCurrentMonth = this.months.findIndex(month => month.substring(0, 3) === span.innerHTML);
238
+ let moveMonthSteps = (tempCurrentYear - this.currentYear) * 12 + tempCurrentMonth - this.currentMonth;
230
239
  this.moveMonth(moveMonthSteps);
231
240
  this.hideSelectMonthYear();
232
241
  }
@@ -241,15 +250,13 @@ export default class CustomDatepicker {
241
250
  }
242
251
  }
243
252
  let years = this.selectMonthYear.querySelectorAll('.year');
244
- let tempSelectedYear = this.selectedYear;
245
253
  for (let i = 0; i < years.length; i++) {
246
254
  years[i].addEventListener('click', () => {
247
255
  let year = parseInt(years[i].innerHTML);
248
- if (year === tempSelectedYear) {
256
+ if (year === tempCurrentYear) {
249
257
  return;
250
258
  } else {
251
- tempSelectedYear = year;
252
- this.selectedYear = year;
259
+ tempCurrentYear = year;
253
260
  let monthsContainers = this.selectMonthYear.querySelectorAll('.months');
254
261
  for (let i = 0; i < monthsContainers.length; i++) {
255
262
  monthsContainers[i].remove();
@@ -285,15 +292,38 @@ export default class CustomDatepicker {
285
292
 
286
293
  private showPicker(showpicker: boolean = true) {
287
294
  if (showpicker) {
295
+ this.closeOtherDatepickers();
296
+ localStorage.setItem('shownDatepicker', this.datepicker.id);
297
+ localStorage.setItem('focusedInput', this.input.id);
288
298
  this.positionPicker();
289
299
  this.datepicker.style.display = 'block';
300
+ this.navigateToSelectedMonth();
290
301
  } else {
291
302
  this.datepicker.style.display = 'none';
303
+ this.closeOtherDatepickers();
292
304
  this.hideSelectMonthYear();
305
+ this.input.placeholder = this.dateFormat;
293
306
  return;
294
307
  }
295
308
  }
296
309
 
310
+ private closeOtherDatepickers() {
311
+ const shownDatepickerId = localStorage.getItem('shownDatepicker');
312
+ if (shownDatepickerId) {
313
+ const shownDatepicker = document.getElementById(shownDatepickerId);
314
+ if (shownDatepicker) {
315
+ shownDatepicker.style.display = 'none';
316
+ }
317
+ }
318
+ localStorage.removeItem('shownDatepicker');
319
+ localStorage.removeItem('focusedInput');
320
+ }
321
+
322
+ private navigateToSelectedMonth() {
323
+ let moveMonthSteps = (this.selectedYear - this.currentYear) * 12 + this.selectedMonth - this.currentMonth;
324
+ this.moveMonth(moveMonthSteps);
325
+ }
326
+
297
327
  private positionPicker() {
298
328
  this.datepicker.style.position = 'fixed';
299
329
 
@@ -328,7 +358,26 @@ export default class CustomDatepicker {
328
358
  }
329
359
 
330
360
  private setDateForToday() {
331
- this.input.value = formatDate(this.selectedDate, this.dateFormat);
361
+ this.input.value = formatDate(this.selectedDate, this.dateFormat, this.language);
362
+ this.showPicker(false);
363
+ }
364
+
365
+ private setDateUsingInputValue() {
366
+ const date = convertStringToDate(this.input.value, this.dateFormat, this.language);
367
+ if (date) {
368
+ this.selectedDate = date;
369
+ this.selectedMonth = date.getMonth();
370
+ this.selectedYear = date.getFullYear();
371
+ this.selectedDay = date.getDate();
372
+ this.renderCalendar();
373
+ this.input.value = formatDate(this.selectedDate, this.dateFormat, this.language);
374
+ this.input.blur();
375
+ } else {
376
+ this.input.value = 'Invalid Date';
377
+ setTimeout(() => {
378
+ this.input.value = '';
379
+ }, 300);
380
+ }
332
381
  this.showPicker(false);
333
382
  }
334
383
 
@@ -336,13 +385,12 @@ export default class CustomDatepicker {
336
385
  this.input.addEventListener('click', () => this.showPicker(true));
337
386
  this.input.addEventListener('focus', () => this.showPicker(true));
338
387
  this.input.addEventListener('change', () => {
339
- const date = new Date(this.input.value);
340
- if (date.toString() !== 'Invalid Date') {
341
- this.selectedDate = date;
342
- this.selectedMonth = date.getMonth();
343
- this.selectedYear = date.getFullYear();
344
- this.selectedDay = date.getDate();
345
- this.renderCalendar();
388
+ this.setDateUsingInputValue();
389
+ });
390
+
391
+ this.input.addEventListener('keydown', (event: KeyboardEvent) => {
392
+ if (event.key === 'Enter') {
393
+ this.setDateUsingInputValue();
346
394
  }
347
395
  });
348
396
 
@@ -351,9 +399,22 @@ export default class CustomDatepicker {
351
399
  });
352
400
 
353
401
  document.onclick = (event: Event) => {
354
- const elementsToNeglect = [this.input, this.datepicker, this.monthYear, this.weekdays, this.days, this.header, this.selectMonthYear];
355
402
  const element = event.target as HTMLElement;
356
- const isNeglected = elementsToNeglect.includes(element) || elementsToNeglect.includes(element.parentElement) || (element).classList.contains('select-month') || element === this.parentElement;
403
+
404
+ const shownDatepicker = document.getElementById(localStorage.getItem('shownDatepicker'));
405
+ if (shownDatepicker){
406
+ const elementInsideDatepicker = shownDatepicker.querySelector(element.tagName);
407
+ if (elementInsideDatepicker){
408
+ return;
409
+ }
410
+ }
411
+
412
+ const elementsToNeglect = [this.input, this.datepicker, this.monthYear, this.weekdays, this.days, this.header, this.selectMonthYear];
413
+ const isNeglected = elementsToNeglect.includes(element)
414
+ || elementsToNeglect.includes(element.parentElement)
415
+ || (element).classList.contains('select-month')
416
+ || element === this.parentElement
417
+ || element.tagName === 'CUSTOM-FORMAT-DATE-ELEMENT'
357
418
 
358
419
  if (!isNeglected){
359
420
  this.showPicker(false);
@@ -1,3 +1,5 @@
1
+ import { GetMonths } from "./DatepickerTranslations";
2
+
1
3
  export const months: string[] = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
2
4
  export const daysOfWeek: string[] = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
3
5
 
@@ -16,29 +18,112 @@ export const supportedDateFormats = [
16
18
  'dd/mm/yy',
17
19
  ];
18
20
 
19
- export function formatDate(date: Date, dateFormat: string): string {
21
+ export function formatDate(date: Date, dateFormat: string, language:string = 'en'): string {
22
+ debugger;
20
23
  const year = date.getFullYear();
21
24
  const month = (date.getMonth() + 1).toString().padStart(2, '0');
22
25
  const day = date.getDate().toString().padStart(2, '0');
23
- const monthName = date.toLocaleString('default', { month: 'long' });
24
-
25
- const dateFormats = {
26
- 'ddmmyyyy': `${day}${month}${year}`,
27
- 'mmddyyyy': `${month}${day}${year}`,
28
- 'dd/mm/yyyy': `${day}/${month}/${year}`,
29
- 'mm/dd/yyyy': `${month}/${day}/${year}`,
30
- 'dd-mm-yyyy': `${day}-${month}-${year}`,
31
- 'mm-dd-yyyy': `${month}-${day}-${year}`,
32
- 'yyyy-mm-dd': `${year}-${month}-${day}`,
33
- 'yyyy-dd-mm': `${year}-${day}-${month}`,
34
- 'Month dd, yyyy': `${monthName} ${day}, ${year}`,
35
- 'mm/dd/yy': `${month}/${day}/${year.toString().slice(-2)}`,
36
- 'dd/mm/yy': `${day}/${month}/${year.toString().slice(-2)}`,
37
- }
26
+ const monthName = GetMonths(language)[date.getMonth()];
27
+ const capitalizedMonthName = monthName.charAt(0).toUpperCase() + monthName.slice(1);
28
+
29
+ const dateFormats = supportedDateFormats.reduce((acc, format) => {
30
+ acc[format] = format.replace('yyyy', year.toString())
31
+ .replace('yy', year.toString().slice(-2))
32
+ .replace('mm', month)
33
+ .replace('dd', day)
34
+ .replace('Month', capitalizedMonthName);
35
+ return acc;
36
+ }, {});
38
37
 
39
38
  return dateFormats[dateFormat];
40
39
  }
41
40
 
42
41
  export function isMobileDevice() {
43
42
  return /Android|webOS|iPhone|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth < 480;
44
- }
43
+ }
44
+
45
+ export function convertStringToDate(dateString: string, dateFormat: string, language: string = 'en'): Date | null {
46
+ debugger;
47
+ let year, month, day;
48
+
49
+ if (!validateDateString(dateString, dateFormat, language)) {
50
+ return null;
51
+ }
52
+
53
+ const getMonthIndex = (monthName: string) => {
54
+ const months = GetMonths(language).map(month => month.toLowerCase());
55
+ const englishMonths = GetMonths('en').map(month => month.toLowerCase());
56
+ const index = months.indexOf(monthName.toLowerCase());
57
+ if (index === -1) {
58
+ const englishMonthIndex = englishMonths.indexOf(monthName.toLowerCase());
59
+ return englishMonthIndex === -1 ? NaN : englishMonthIndex;
60
+ }
61
+
62
+ return index + 1;
63
+ };
64
+
65
+ switch (dateFormat) {
66
+ case 'ddmmyyyy':
67
+ day = parseInt(dateString.substring(0, 2));
68
+ month = parseInt(dateString.substring(2, 4));
69
+ year = parseInt(dateString.substring(4, 8));
70
+ break;
71
+ case 'mmddyyyy':
72
+ month = parseInt(dateString.substring(0, 2));
73
+ day = parseInt(dateString.substring(2, 4));
74
+ year = parseInt(dateString.substring(4, 8));
75
+ break;
76
+ case 'dd/mm/yyyy':
77
+ case 'dd-mm-yyyy':
78
+ [day, month, year] = dateString.split(/\/|-/).map(Number);
79
+ break;
80
+ case 'mm/dd/yyyy':
81
+ case 'mm-dd-yyyy':
82
+ [month, day, year] = dateString.split(/\/|-/).map(Number);
83
+ break;
84
+ case 'yyyy-mm-dd':
85
+ case 'yyyy-dd-mm':
86
+ [year, month, day] = dateFormat === 'yyyy-mm-dd' ? dateString.split('-').map(Number) : [dateString.slice(0, 4), dateString.slice(8, 10), dateString.slice(5, 7)].map(Number);
87
+ break;
88
+ case 'Month dd, yyyy':
89
+ const parts = dateString.split(' ');
90
+ year = parseInt(parts[2]);
91
+ month = getMonthIndex(parts[0]);
92
+ day = parseInt(parts[1].replace(',', ''));
93
+ break;
94
+ case 'mm/dd/yy':
95
+ const [monthSt, daySt, yearSt] = dateString.split('/');
96
+ month = parseInt(monthSt);
97
+ day = parseInt(daySt);
98
+ year = parseInt(yearSt) + 2000;
99
+ break;
100
+ case 'dd/mm/yy':
101
+ const [dayStr, monthStr, yearStr] = dateString.split('/');
102
+ day = parseInt(dayStr);
103
+ month = parseInt(monthStr);
104
+ year = parseInt(yearStr) + 2000;
105
+ break;
106
+ default:
107
+ return null;
108
+ }
109
+
110
+ if (!isNaN(year) && !isNaN(month) && !isNaN(day)) {
111
+ return new Date(year, month - 1 , day);
112
+ } else {
113
+ return null;
114
+ }
115
+ }
116
+
117
+
118
+ function validateDateString(dateString: string, dateFormat: string, language: string ='en'): boolean {
119
+ const dateFormatsRegex = supportedDateFormats.reduce((acc, format) => {
120
+ acc[format] = new RegExp(`^${format.replace('yyyy', '\\d{4}')
121
+ .replace('yy', '\\d{2}')
122
+ .replace('mm', '(0[1-9]|1[0-2])')
123
+ .replace('dd', '(0[1-9]|[12][0-9]|3[01])')
124
+ .replace('Month', GetMonths(language).join('|') + '|' + GetMonths('en').join('|'))}$`, 'i');
125
+ return acc;
126
+ }, {});
127
+
128
+ return dateFormatsRegex[dateFormat].test(dateString);
129
+ }
@@ -8,7 +8,7 @@ import { defaultDateFormat, supportedDateFormats } from './CustomDatepicker/Cust
8
8
  selector: 'custom-format-date-element',
9
9
  template: `
10
10
  <div class="wrapper">
11
- <input type="text" id="date-field" readonly />
11
+ <input type="text" id="date-field" />
12
12
  <svg id="picker-trigger" data-toggle viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="4" width="20" height="18" rx="1" fill="#000"/><rect x="4" y="8" width="16" height="12" fill="white"/><path d="M4 10H20" stroke="#000" stroke-width="1"/><circle cx="16" cy="14" r="2" fill="#F44336"/><rect x="6" y="2" width="3" height="4" rx=".5" fill="#000"/><rect x="15" y="2" width="3" height="4" rx=".5" fill="#000"/></svg>
13
13
  </div>`,
14
14
  style: `
@@ -75,6 +75,8 @@ export class CustomFormatDateFieldElement extends CustomInputElement {
75
75
 
76
76
  initChildInputs() {
77
77
  this.date = super.getChildInput('#date-field');
78
+ this.date.id = `date-field-${Date.now()}`;
79
+
78
80
  this.pickerTrigger = this.getChildElement('#picker-trigger');
79
81
 
80
82
  this.dateFormat = supportedDateFormats.includes(this.dateFormat) ? this.dateFormat : defaultDateFormat;