@luftborn/custom-elements 2.8.4 → 2.8.6

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.
Files changed (19) hide show
  1. package/demo/index.js +178 -49
  2. package/demo/index.min.js +177 -48
  3. package/demo/index.min.js.map +1 -1
  4. package/dist/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepicker.d.ts +4 -0
  5. package/dist/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepicker.js +84 -32
  6. package/dist/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepicker.js.map +1 -1
  7. package/dist/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepickerStyles.d.ts +1 -1
  8. package/dist/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepickerStyles.js +1 -1
  9. package/dist/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepickerStyles.js.map +1 -1
  10. package/dist/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepickerUtils.d.ts +1 -0
  11. package/dist/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepickerUtils.js +90 -14
  12. package/dist/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepickerUtils.js.map +1 -1
  13. package/dist/elements/CustomFormatDateFieldElement/CustomFormatDateFieldElement.js +2 -1
  14. package/dist/elements/CustomFormatDateFieldElement/CustomFormatDateFieldElement.js.map +1 -1
  15. package/package.json +1 -1
  16. package/src/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepicker.ts +93 -35
  17. package/src/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepickerStyles.ts +23 -22
  18. package/src/elements/CustomFormatDateFieldElement/CustomDatepicker/CustomDatepickerUtils.ts +95 -15
  19. package/src/elements/CustomFormatDateFieldElement/CustomFormatDateFieldElement.ts +3 -1
@@ -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 = {
@@ -72,9 +72,10 @@ export default class CustomDatepicker {
72
72
 
73
73
  private createDatePickerElements() {
74
74
  this.datepicker = document.createElement('div');
75
- this.datepicker.classList.add('datepicker');
75
+ this.datepicker.id = this.input.id + '-datepicker';
76
+ this.datepicker.classList.add('custom-datepicker');
76
77
  this.header = document.createElement('div');
77
- this.header.classList.add('header');
78
+ this.header.classList.add('custom-datepicer-header');
78
79
  this.monthYear = document.createElement('span');
79
80
  this.monthYear.classList.add('month-year');
80
81
  this.monthYear.onclick = () => this.toggleSelectMonthYear();
@@ -118,11 +119,7 @@ export default class CustomDatepicker {
118
119
  today.onclick = () => this.setDateForToday();
119
120
  actions.appendChild(today);
120
121
 
121
- if (this.input.parentElement) {
122
- this.input.parentElement.appendChild(this.datepicker);
123
- } else {
124
- document.body.appendChild(this.datepicker);
125
- }
122
+ document.body.appendChild(this.datepicker);
126
123
 
127
124
  this.applyStyles();
128
125
  }
@@ -195,25 +192,34 @@ export default class CustomDatepicker {
195
192
  this.selectedYear = this.currentYear;
196
193
  moveMonthSteps = 1;
197
194
  }
198
- this.selectedDate = new Date(this.selectedYear, this.selectedMonth, this.selectedDay);
199
- let selectedDayElement = this.days.querySelector('.selected-day');
200
- if (selectedDayElement) {
201
- selectedDayElement.classList.remove('selected-day');
202
- }
203
- span.classList.add('selected-day');
204
-
205
- const formattedDate = formatDate(this.selectedDate, this.dateFormat, this.language);
206
- this.input.value = formattedDate;
207
-
208
- this.showPicker(false);
195
+ this.setSelectedDate(moveMonthSteps, span);
209
196
  };
210
197
  this.days.appendChild(span);
211
198
  }
212
-
213
199
  this.positionPicker();
214
200
  }
215
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
+
216
220
  private initSelectMonthYear() {
221
+ let tempCurrentYear = this.currentYear;
222
+
217
223
  this.selectMonthYear.style.display = 'none';
218
224
  this.selectMonthYear.style.height = '200px';
219
225
  if (isMobileDevice()) {
@@ -228,8 +234,8 @@ export default class CustomDatepicker {
228
234
  span.classList.add('select-month');
229
235
  monthsContainer.appendChild(span);
230
236
  span.onclick = () => {
231
- this.selectedMonth = this.months.findIndex(month => month.substring(0, 3) === span.innerHTML);
232
- 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;
233
239
  this.moveMonth(moveMonthSteps);
234
240
  this.hideSelectMonthYear();
235
241
  }
@@ -244,15 +250,13 @@ export default class CustomDatepicker {
244
250
  }
245
251
  }
246
252
  let years = this.selectMonthYear.querySelectorAll('.year');
247
- let tempSelectedYear = this.selectedYear;
248
253
  for (let i = 0; i < years.length; i++) {
249
254
  years[i].addEventListener('click', () => {
250
255
  let year = parseInt(years[i].innerHTML);
251
- if (year === tempSelectedYear) {
256
+ if (year === tempCurrentYear) {
252
257
  return;
253
258
  } else {
254
- tempSelectedYear = year;
255
- this.selectedYear = year;
259
+ tempCurrentYear = year;
256
260
  let monthsContainers = this.selectMonthYear.querySelectorAll('.months');
257
261
  for (let i = 0; i < monthsContainers.length; i++) {
258
262
  monthsContainers[i].remove();
@@ -288,15 +292,38 @@ export default class CustomDatepicker {
288
292
 
289
293
  private showPicker(showpicker: boolean = true) {
290
294
  if (showpicker) {
295
+ this.closeOtherDatepickers();
296
+ localStorage.setItem('shownDatepicker', this.datepicker.id);
297
+ localStorage.setItem('focusedInput', this.input.id);
291
298
  this.positionPicker();
292
299
  this.datepicker.style.display = 'block';
300
+ this.navigateToSelectedMonth();
293
301
  } else {
294
302
  this.datepicker.style.display = 'none';
303
+ this.closeOtherDatepickers();
295
304
  this.hideSelectMonthYear();
305
+ this.input.placeholder = this.dateFormat;
296
306
  return;
297
307
  }
298
308
  }
299
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
+
300
327
  private positionPicker() {
301
328
  this.datepicker.style.position = 'fixed';
302
329
 
@@ -335,17 +362,35 @@ export default class CustomDatepicker {
335
362
  this.showPicker(false);
336
363
  }
337
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
+ }
381
+ this.showPicker(false);
382
+ }
383
+
338
384
  private addEventListeners() {
339
385
  this.input.addEventListener('click', () => this.showPicker(true));
340
386
  this.input.addEventListener('focus', () => this.showPicker(true));
341
387
  this.input.addEventListener('change', () => {
342
- const date = new Date(this.input.value);
343
- if (date.toString() !== 'Invalid Date') {
344
- this.selectedDate = date;
345
- this.selectedMonth = date.getMonth();
346
- this.selectedYear = date.getFullYear();
347
- this.selectedDay = date.getDate();
348
- this.renderCalendar();
388
+ this.setDateUsingInputValue();
389
+ });
390
+
391
+ this.input.addEventListener('keydown', (event: KeyboardEvent) => {
392
+ if (event.key === 'Enter') {
393
+ this.setDateUsingInputValue();
349
394
  }
350
395
  });
351
396
 
@@ -354,9 +399,22 @@ export default class CustomDatepicker {
354
399
  });
355
400
 
356
401
  document.onclick = (event: Event) => {
357
- const elementsToNeglect = [this.input, this.datepicker, this.monthYear, this.weekdays, this.days, this.header, this.selectMonthYear];
358
402
  const element = event.target as HTMLElement;
359
- 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'
360
418
 
361
419
  if (!isNeglected){
362
420
  this.showPicker(false);
@@ -1,100 +1,101 @@
1
1
  export const CustomDatepickerStyles = `
2
- .datepicker {
2
+ .custom-datepicker {
3
3
  position: fixed;
4
4
  background-color: #fff;
5
5
  display: none;
6
6
  width: 200px;
7
7
  border: 1px solid #ccc;
8
8
  border-radius: 5px;
9
- padding: 10px;
9
+ padding: 20px;
10
10
  margin: 0 auto;
11
11
  box-shadow: 0 0 10px 0 #ccc;
12
12
  font: 12px Arial, sans-serif;
13
13
  }
14
14
 
15
- .header {
15
+ .custom-datepicker .custom-datepicer-header {
16
16
  display: flex;
17
17
  justify-content: space-between;
18
18
  align-items: center;
19
19
  margin-bottom: 10px;
20
20
  }
21
- .weekdays {
21
+ .custom-datepicker .weekdays {
22
22
  display: flex;
23
23
  justify-content: space-between;
24
+ margin-top: 20px;
24
25
  }
25
- .weekdays span {
26
+ .custom-datepicker .weekdays span {
26
27
  width: 14.28%;
27
28
  text-align: center;
28
29
  padding: 5px 0;
29
30
  }
30
- .days {
31
+ .custom-datepicker .days {
31
32
  display: flex;
32
33
  flex-wrap: wrap;
33
34
  }
34
- .days span {
35
+ .custom-datepicker .days span {
35
36
  width: 14.28%;
36
37
  text-align: center;
37
38
  padding: 5px 0;
38
39
  }
39
- .days span:hover {
40
+ .custom-datepicker .days span:hover {
40
41
  background-color: #B2d5ff;
41
42
  cursor: pointer;
42
43
  }
43
- .days span.today {
44
+ .custom-datepicker .days span.today {
44
45
  border: 1px solid #000;
45
46
  }
46
- .days span.selected-day {
47
+ .custom-datepicker .days span.selected-day {
47
48
  background-color: #0075ff;
48
49
  color: #fff;
49
50
  }
50
- .prev-month, .next-month {
51
+ .custom-datepicker .prev-month, .next-month {
51
52
  cursor: pointer;
52
53
  }
53
- .month-year {
54
+ .custom-datepicker .month-year {
54
55
  font-weight: bold;
55
56
  }
56
- .month-year:hover {
57
+ .custom-datepicker .month-year:hover {
57
58
  cursor: pointer;
58
59
  }
59
- .select-month-year {
60
+ .custom-datepicker .select-month-year {
60
61
  display: none;
61
62
  width: 100%;
62
63
  overflow-y: auto;
63
64
  }
64
- .select-month-year .year {
65
+ .custom-datepicker .select-month-year .year {
65
66
  text-align: center;
66
67
  background-color: #f0f0f0;
67
68
  border-bottom: 1px solid #3f3d3d;
68
69
  padding: 5px 0;
69
70
  cursor: pointer;
70
71
  }
71
- .select-month-year .year .months {
72
+ .custom-datepicker .select-month-year .year .months {
72
73
  display: flex;
73
74
  flex-wrap: wrap;
74
75
  }
75
- .select-month-year .year .months span {
76
+ .custom-datepicker .select-month-year .year .months span {
76
77
  width: 25%;
77
78
  text-align: center;
78
79
  padding: 10px 0;
79
80
  background-color: #fff;
80
81
  }
81
- .select-month-year .year .months span:hover {
82
+ .custom-datepicker .select-month-year .year .months span:hover {
82
83
  background-color: #B2d5ff;
83
84
  cursor: pointer;
84
85
  }
85
- .actions {
86
+ .custom-datepicker .actions {
86
87
  display: flex;
87
88
  justify-content: space-between;
88
- margin-top: 10px;
89
+ margin-top: 30px;
89
90
  }
90
- .actions input {
91
+ .custom-datepicker .actions input {
91
92
  color: #0075ff;
92
93
  border: none;
93
94
  background-color: transparent;
94
95
  padding: 5px;
95
96
  border: 1px solid transparent;
96
97
  }
97
- .actions input:hover {
98
+ .custom-datepicker .actions input:hover {
98
99
  background-color: #B2d5ff;
99
100
  border-color: #000;
100
101
  }
@@ -24,24 +24,104 @@ export function formatDate(date: Date, dateFormat: string, language:string = 'en
24
24
  const day = date.getDate().toString().padStart(2, '0');
25
25
  const monthName = GetMonths(language)[date.getMonth()];
26
26
  const capitalizedMonthName = monthName.charAt(0).toUpperCase() + monthName.slice(1);
27
-
28
- const dateFormats = {
29
- 'ddmmyyyy': `${day}${month}${year}`,
30
- 'mmddyyyy': `${month}${day}${year}`,
31
- 'dd/mm/yyyy': `${day}/${month}/${year}`,
32
- 'mm/dd/yyyy': `${month}/${day}/${year}`,
33
- 'dd-mm-yyyy': `${day}-${month}-${year}`,
34
- 'mm-dd-yyyy': `${month}-${day}-${year}`,
35
- 'yyyy-mm-dd': `${year}-${month}-${day}`,
36
- 'yyyy-dd-mm': `${year}-${day}-${month}`,
37
- 'Month dd, yyyy': `${capitalizedMonthName} ${day}, ${year}`,
38
- 'mm/dd/yy': `${month}/${day}/${year.toString().slice(-2)}`,
39
- 'dd/mm/yy': `${day}/${month}/${year.toString().slice(-2)}`,
40
- }
27
+
28
+ const dateFormats = supportedDateFormats.reduce((acc, format) => {
29
+ acc[format] = format.replace('yyyy', year.toString())
30
+ .replace('yy', year.toString().slice(-2))
31
+ .replace('mm', month)
32
+ .replace('dd', day)
33
+ .replace('Month', capitalizedMonthName);
34
+ return acc;
35
+ }, {});
41
36
 
42
37
  return dateFormats[dateFormat];
43
38
  }
44
39
 
45
40
  export function isMobileDevice() {
46
41
  return /Android|webOS|iPhone|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth < 480;
47
- }
42
+ }
43
+
44
+ export function convertStringToDate(dateString: string, dateFormat: string, language: string = 'en'): Date | null {
45
+ let year, month, day;
46
+
47
+ if (!validateDateString(dateString, dateFormat, language)) {
48
+ return null;
49
+ }
50
+
51
+ const getMonthIndex = (monthName: string) => {
52
+ const months = GetMonths(language).map(month => month.toLowerCase());
53
+ const englishMonths = GetMonths('en').map(month => month.toLowerCase());
54
+ const index = months.indexOf(monthName.toLowerCase());
55
+ if (index === -1) {
56
+ const englishMonthIndex = englishMonths.indexOf(monthName.toLowerCase());
57
+ return englishMonthIndex === -1 ? NaN : englishMonthIndex;
58
+ }
59
+
60
+ return index + 1;
61
+ };
62
+
63
+ switch (dateFormat) {
64
+ case 'ddmmyyyy':
65
+ day = parseInt(dateString.substring(0, 2));
66
+ month = parseInt(dateString.substring(2, 4));
67
+ year = parseInt(dateString.substring(4, 8));
68
+ break;
69
+ case 'mmddyyyy':
70
+ month = parseInt(dateString.substring(0, 2));
71
+ day = parseInt(dateString.substring(2, 4));
72
+ year = parseInt(dateString.substring(4, 8));
73
+ break;
74
+ case 'dd/mm/yyyy':
75
+ case 'dd-mm-yyyy':
76
+ [day, month, year] = dateString.split(/\/|-/).map(Number);
77
+ break;
78
+ case 'mm/dd/yyyy':
79
+ case 'mm-dd-yyyy':
80
+ [month, day, year] = dateString.split(/\/|-/).map(Number);
81
+ break;
82
+ case 'yyyy-mm-dd':
83
+ case 'yyyy-dd-mm':
84
+ [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);
85
+ break;
86
+ case 'Month dd, yyyy':
87
+ const parts = dateString.split(' ');
88
+ year = parseInt(parts[2]);
89
+ month = getMonthIndex(parts[0]);
90
+ day = parseInt(parts[1].replace(',', ''));
91
+ break;
92
+ case 'mm/dd/yy':
93
+ const [monthSt, daySt, yearSt] = dateString.split('/');
94
+ month = parseInt(monthSt);
95
+ day = parseInt(daySt);
96
+ year = parseInt(yearSt) + 2000;
97
+ break;
98
+ case 'dd/mm/yy':
99
+ const [dayStr, monthStr, yearStr] = dateString.split('/');
100
+ day = parseInt(dayStr);
101
+ month = parseInt(monthStr);
102
+ year = parseInt(yearStr) + 2000;
103
+ break;
104
+ default:
105
+ return null;
106
+ }
107
+
108
+ if (!isNaN(year) && !isNaN(month) && !isNaN(day)) {
109
+ return new Date(year, month - 1 , day);
110
+ } else {
111
+ return null;
112
+ }
113
+ }
114
+
115
+
116
+ function validateDateString(dateString: string, dateFormat: string, language: string ='en'): boolean {
117
+ const dateFormatsRegex = supportedDateFormats.reduce((acc, format) => {
118
+ acc[format] = new RegExp(`^${format.replace('yyyy', '\\d{4}')
119
+ .replace('yy', '\\d{2}')
120
+ .replace('mm', '(0[1-9]|1[0-2])')
121
+ .replace('dd', '(0[1-9]|[12][0-9]|3[01])')
122
+ .replace('Month', GetMonths(language).join('|') + '|' + GetMonths('en').join('|'))}$`, 'i');
123
+ return acc;
124
+ }, {});
125
+
126
+ return dateFormatsRegex[dateFormat].test(dateString);
127
+ }
@@ -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;