@lemonadejs/calendar 3.0.1 → 3.0.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.
package/dist/index.js CHANGED
@@ -1,702 +1,791 @@
1
- /**
2
- * render: ()
3
- * valid-ranges: []
4
- * disabled
5
- * dateToNum UTC
6
- * navigation with icons Enter key
7
- */
8
- if (! lemonade && typeof (require) === 'function') {
9
- var lemonade = require('lemonadejs');
10
- }
11
-
12
- if (! Modal && typeof (require) === 'function') {
13
- var Modal = require('../../modal/dist/index');
14
- }
15
-
16
- ; (function (global, factory) {
17
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
18
- typeof define === 'function' && define.amd ? define(factory) :
19
- global.Calendar = factory();
20
- }(this, (function () {
21
-
22
- // Weekdays
23
- const Weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
24
-
25
- // Months
26
- const Months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
27
-
28
- // Excel like dates
29
- const excelInitialTime = Date.UTC(1900, 0, 0);
30
- const excelLeapYearBug = Date.UTC(1900, 1, 29);
31
- const millisecondsPerDay = 86400000;
32
-
33
- /**
34
- * Date to number
35
- */
36
- const dateToNum = function (jsDate) {
37
- if (typeof(jsDate) === 'string') {
38
- jsDate = new Date(jsDate + ' GMT+0');
39
- }
40
- let jsDateInMilliseconds = jsDate.getTime();
41
- if (jsDateInMilliseconds >= excelLeapYearBug) {
42
- jsDateInMilliseconds += millisecondsPerDay;
43
- }
44
- jsDateInMilliseconds -= excelInitialTime;
45
-
46
- return jsDateInMilliseconds / millisecondsPerDay;
47
- }
48
-
49
- const numToDate = function (excelSerialNumber) {
50
- let jsDateInMilliseconds = excelInitialTime + excelSerialNumber * millisecondsPerDay;
51
- if (jsDateInMilliseconds >= excelLeapYearBug) {
52
- jsDateInMilliseconds -= millisecondsPerDay;
53
- }
54
-
55
- const d = new Date(jsDateInMilliseconds);
56
-
57
- return [
58
- d.getUTCFullYear(),
59
- Two(d.getUTCMonth() + 1),
60
- Two(d.getUTCDate()),
61
- Two(d.getUTCHours()),
62
- Two(d.getUTCMinutes()),
63
- Two(d.getUTCSeconds()),
64
- ];
65
- }
66
-
67
- /**
68
- * Create a data calendar object based on the view
69
- */
70
- const views = {
71
- years: function(date) {
72
- let year = date.getFullYear();
73
- let result = [];
74
- let start = year % 16;
75
- let complement = 16 - start;
76
-
77
- for (let i = year-start; i < year+complement; i++) {
78
- let item = {
79
- title: i,
80
- value: i
81
- };
82
- result.push(item);
83
- // Select cursor
84
- if (this.cursor.y === i) {
85
- // Select item
86
- item.selected = true;
87
- // Cursor
88
- this.cursor.index = result.length - 1;
89
- }
90
- }
91
- return result;
92
- },
93
- months: function(date) {
94
- let year = date.getFullYear();
95
- let result = [];
96
- for (let i = 0; i < 12; i++) {
97
- let item = {
98
- title: Months[i].substring(0,3),
99
- value: i
100
- }
101
- // Add the item to the data
102
- result.push(item);
103
- // Select cursor
104
- if (this.cursor.y === year && this.cursor.m === i) {
105
- // Select item
106
- item.selected = true;
107
- // Cursor
108
- this.cursor.index = result.length - 1;
109
- }
110
- }
111
-
112
- return result;
113
- },
114
- days: function(date) {
115
- let year = date.getFullYear();
116
- let month = date.getMonth();
117
- let data = filterData.call(this, year, month);
118
-
119
- // First day
120
- let tmp = new Date(year, month, 1, 0, 0, 0);
121
- let firstDay = tmp.getDay();
122
-
123
- let result = [];
124
- for (let i = 1-firstDay; i <= 42-firstDay; i++) {
125
- // Get the day
126
- tmp = new Date(year, month, i, 0, 0, 0);
127
- // Day
128
- let day = tmp.getDate();
129
- // Create the item
130
- let item = {
131
- title: day,
132
- value: i,
133
- number: dateToNum(tmp.toString())
134
- }
135
- // Add the item to the date
136
- result.push(item);
137
- // Check selections
138
- if (tmp.getMonth() !== month) {
139
- // Days are not in the current month
140
- item.grey = true;
141
- } else {
142
- // Check for data
143
- let d = [ year, Two(month+1), Two(day)].join('-');
144
- if (data && data[d]) {
145
- item.data = data[d];
146
- }
147
- // Select cursor
148
- if (this.cursor.y === year && this.cursor.m === month && this.cursor.d === day) {
149
- // Select item
150
- item.selected = true;
151
- // Cursor
152
- this.cursor.index = result.length - 1;
153
- }
154
- }
155
-
156
- // Select range
157
- if (this.type === 'range' && this.range) {
158
- // Mark the start and end points
159
- if (this.range[0] === item.number) {
160
- item.range = true;
161
- item.start = true;
162
- }
163
- if (this.range[1] === item.number) {
164
- item.range = true;
165
- item.end = true;
166
- }
167
- // Re-recreate teh range
168
- if (this.range[0] && this.range[1]) {
169
- if (this.range[0] <= item.number && this.range[1] >= item.number) {
170
- item.range = true;
171
- }
172
- }
173
- }
174
- }
175
-
176
- return result;
177
- },
178
- hours: function() {
179
- let result = [];
180
- for (let i = 0; i < 24; i++) {
181
- let item = {
182
- title: Two(i),
183
- value: i
184
- };
185
- result.push(item);
186
- }
187
- return result;
188
- },
189
- minutes: function() {
190
- let result = [];
191
- for (let i = 0; i < 60; i=i+5) {
192
- let item = {
193
- title: Two(i),
194
- value: i
195
- };
196
- result.push(item);
197
- }
198
- return result;
199
- }
200
- }
201
-
202
- const filterData = function(year, month) {
203
- // Data for the month
204
- let data = {};
205
- if (Array.isArray(this.data)) {
206
- this.data.map(function (v) {
207
- let d = year + '-' + Two(month + 1);
208
- if (v.date.substring(0, 7) === d) {
209
- if (!data[v.date]) {
210
- data[v.date] = [];
211
- }
212
- data[v.date].push(v);
213
- }
214
- });
215
- }
216
- return data;
217
- }
218
- // Get the short weekdays name
219
- const getWeekdays = function() {
220
- return Weekdays.map(w => {
221
- return { title: w.substring(0, 1) };
222
- })
223
- }
224
-
225
- // Define the hump based on the view
226
- const getJump = function(e) {
227
- if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
228
- return this.view === 'days' ? 7 : 4;
229
- }
230
-
231
- return 1;
232
- }
233
-
234
- // Get the position of the data based on the view
235
- const getPosition = function() {
236
- let position = 2;
237
- if (this.view === 'years') {
238
- position = 0;
239
- } else if (this.view === 'months') {
240
- position = 1;
241
- }
242
- return position;
243
- }
244
-
245
- // Transform in two digits
246
- const Two = function(value) {
247
- value = '' + value;
248
- if (value.length == 1) {
249
- value = '0' + value;
250
- }
251
- return value;
252
- }
253
-
254
- const Calendar = function() {
255
- let self = this;
256
-
257
- // Weekdays
258
- self.weekdays = getWeekdays();
259
-
260
- // Cursor
261
- self.cursor = {};
262
-
263
- // Calendar date
264
- let date = new Date();
265
-
266
- // Range
267
- self.range = null;
268
-
269
- /**
270
- * Update the internal date
271
- * @param {Date|string|number[]} d instance of Date
272
- *
273
- */
274
- const setDate = function(d) {
275
- if (Array.isArray(d)) {
276
- d = new Date(Date.UTC(...d));
277
- } else if (typeof(d) === 'string') {
278
- d = new Date(d);
279
- }
280
- // Update internal date
281
- date = d;
282
- // Update the headers of the calendar
283
- let value = d.toISOString().substring(0,10).split('-');
284
- // Update the month label
285
- self.month = Months[parseInt(value[1])-1];
286
- // Update the year label
287
- self.year = parseInt(value[0]);
288
- // Load data
289
- if (! self.view) {
290
- // Start on the days view will start the data
291
- self.view = 'days';
292
- } else {
293
- // Reload the data for the same view
294
- self.options = views[self.view].call(self, date);
295
- }
296
- }
297
-
298
- /**
299
- * Set the internal cursor
300
- * @param {object} s
301
- */
302
- const setCursor = function(s) {
303
- // Remove selection from the current object
304
- let item = self.options[self.cursor.index];
305
- if (typeof(item) !== 'undefined') {
306
- item.selected = false;
307
- }
308
- // Update the date based on the click
309
- let v = updateDate(s.value, getPosition.call(self));
310
- let d = new Date(Date.UTC(...v));
311
- // Update cursor controller
312
- self.cursor = {
313
- y: d.getFullYear(),
314
- m: d.getMonth(),
315
- d: d.getDate(),
316
- };
317
- // Update cursor based on the object position
318
- if (s) {
319
- // Update selected property
320
- s.selected = true;
321
- // New cursor
322
- self.cursor.index = self.options.indexOf(s);
323
- }
324
- return d;
325
- }
326
-
327
- /**
328
- * Update the current date
329
- * @param {number} v new value for year, month or day
330
- * @param {number} position (0,1,2 - year,month,day)
331
- * @returns {number[]}
332
- */
333
- const updateDate = function(v, position) {
334
- // Current internal date
335
- let value = [date.getFullYear(), date.getMonth(), date.getDate(),0,0,0];
336
- // Update internal date
337
- value[position] = v;
338
- // Return new value
339
- return value;
340
- }
341
-
342
- /**
343
- * This method move the data from the view up or down
344
- * @param direction
345
- */
346
- const move = function(direction) {
347
- let value;
348
-
349
- // Update the new internal date
350
- if (self.view === 'days') {
351
- // Select the new internal date
352
- value = updateDate(date.getMonth()+direction, 1);
353
- } else if (self.view === 'months') {
354
- // Select the new internal date
355
- value = updateDate(date.getFullYear()+direction, 0);
356
- } else if (self.view === 'years') {
357
- // Select the new internal date
358
- value = updateDate(date.getFullYear()+(direction*16), 0);
359
- }
360
-
361
- // Update view
362
- if (value) {
363
- setDate(value);
364
- }
365
- }
366
-
367
- /**
368
- * Keyboard handler
369
- * @param {number} direction of the action
370
- * @param {object} e keyboard event
371
- */
372
- const moveCursor = function(direction, e) {
373
- direction = direction * getJump.call(self, e);
374
- // Remove the selected from the current selection
375
- let s = self.options[self.cursor.index];
376
- // If the selection is going outside the viewport
377
- if (typeof(s) === 'undefined' || ! s.selected) {
378
- // Go back to the view
379
- setDate([ self.cursor.y, self.cursor.m, self.cursor.d ]);
380
- }
381
-
382
- // Jump to the index
383
- let index = self.cursor.index + direction;
384
-
385
- // See if the new position is in the viewport
386
- if (typeof(self.options[index]) === 'undefined') {
387
- // Adjust the index for next collection of data
388
- if (self.view === 'days') {
389
- if (index < 0) {
390
- index = 42 + index;
391
- } else {
392
- index = index - 42;
393
- }
394
- } else if (self.view === 'years') {
395
- if (index < 0) {
396
- index = 4 + index;
397
- } else {
398
- index = index - 4;
399
- }
400
- } else if (self.view === 'months') {
401
- if (index < 0) {
402
- index = 12 + index;
403
- } else {
404
- index = index - 12;
405
- }
406
- }
407
-
408
- // Move the data up or down
409
- move(direction > 0 ? 1 : -1);
410
- }
411
-
412
- // Update the date based on the click
413
- setCursor(self.options[index]);
414
-
415
- // Update ranges
416
- updateRange(self.options[index])
417
- }
418
-
419
- /**
420
- * Handler blur
421
- * @param e
422
- */
423
- const blur = function(e) {
424
- if (self.modal) {
425
- if (!(e.relatedTarget && self.modal.el.contains(e.relatedTarget))) {
426
- if (self.modal.closed === false) {
427
- // self.modal.closed = true
428
- }
429
- }
430
- }
431
- }
432
-
433
- /**
434
- * Set the limits of a range
435
- * @param s
436
- */
437
- const setRange = function(s) {
438
- if (self.view === 'days' && self.type === 'range') {
439
- let d = self.getValue();
440
- // Date to number
441
- let number = dateToNum(d);
442
- // Start a new range
443
- if (self.range && (self.range[0] > number || self.range[1])) {
444
- destroyRange();
445
- }
446
- // Range
447
- s.range = true;
448
- // Update range
449
- if (! self.range) {
450
- s.start = true;
451
- self.range = [number, null];
452
- } else {
453
- s.end = true;
454
- self.range[1] = number;
455
- }
456
- }
457
- }
458
-
459
- /**
460
- * Update the visible range
461
- * @param s
462
- */
463
- const updateRange = function(s) {
464
- if (self.type === 'range' && self.view === 'days' && self.range) {
465
- // Creating a range
466
- if (self.range[0] && ! self.range[1]) {
467
- let number = s.number;
468
- if (number) {
469
- // Update range properties
470
- for (let i = 0; i < self.options.length; i++) {
471
- // Item number
472
- let v = self.options[i].number;
473
- // Update property condition
474
- self.options[i].range = v >= self.range[0] && v <= number;
475
- }
476
- }
477
- }
478
- }
479
- }
480
-
481
- /**
482
- * Destroy the range
483
- */
484
- const destroyRange = function() {
485
- for (let i = 0; i < self.options.length; i++) {
486
- self.options[i].range = false;
487
- self.options[i].start = false;
488
- self.options[i].end = false;
489
- }
490
- self.range = null;
491
- }
492
-
493
- /**
494
- * Select an item with the enter or mouse
495
- * @param {object} item - selected cell
496
- */
497
- self.select = function(item) {
498
- // Update cursor generic
499
- let value = setCursor(item);
500
- // Update range
501
- setRange(item);
502
- // Based where was the click
503
- if (! (self.view === 'days' && ! item.grey)) {
504
- // Update the internal date
505
- setDate(value);
506
- }
507
- // Go back to the view of days
508
- if (self.view !== 'days') {
509
- self.view = 'days';
510
- }
511
- }
512
-
513
- /**
514
- * Next handler
515
- * @param {object} e mouse event
516
- */
517
- self.next = function(e) {
518
- if (! e) {
519
- // Icon click
520
- move(1);
521
- } else {
522
- // Keyboard handler
523
- moveCursor(1, e);
524
- }
525
- }
526
-
527
- /**
528
- * Next handler
529
- * @param {object} e mouse event
530
- */
531
- self.prev = function(e) {
532
- if (! e) {
533
- // Icon click
534
- move(-1);
535
- } else {
536
- // Keyboard handler
537
- moveCursor(-1, e);
538
- }
539
- }
540
-
541
- /**
542
- * Open the modal
543
- */
544
- self.open = function() {
545
- if (self.modal) {
546
- self.modal.closed = false;
547
- // Set the focus
548
- self.content.focus();
549
- }
550
- }
551
-
552
- /**
553
- * Close the modal
554
- */
555
- self.close = function() {
556
- if (self.modal && self.modal.closed === false) {
557
- self.modal.closed = true;
558
- }
559
- }
560
-
561
- /**
562
- * Get value from cursor
563
- * @returns {string}
564
- */
565
- self.getValue = function() {
566
- let v = [ self.cursor.y, self.cursor.m, self.cursor.d ];
567
- let d = new Date(Date.UTC(...v));
568
- // Update the headers of the calendar
569
- return d.toISOString().substring(0,10);
570
- }
571
-
572
- self.onchange = function(prop) {
573
- if (prop === 'view') {
574
- if (typeof(views[self.view]) === 'function') {
575
- // When change the view update the data
576
- self.options = views[self.view].call(self, date);
577
- }
578
- } else if (prop === 'value') {
579
- if (typeof(self.onupdate) === 'function') {
580
- self.onupdate.call(self, self.value);
581
- }
582
- } else if (prop === 'options') {
583
- self.content.focus();
584
- }
585
- }
586
-
587
- self.onload = function() {
588
- let d;
589
- if (self.value) {
590
- d = new Date(self.value);
591
- } else {
592
- d = new Date();
593
- }
594
- // Update my index
595
- self.cursor = {
596
- y: d.getFullYear(),
597
- m: d.getMonth(),
598
- d: d.getDate(),
599
- };
600
- // Populate components
601
- self.hours = views.hours();
602
- self.minutes = views.minutes();
603
-
604
- // Update the internal calendar date
605
- setDate(d);
606
-
607
- if (self.type !== "inline") {
608
- // Create modal instance
609
- self.modal = {
610
- width: 300,
611
- closed: true,
612
- 'auto-close': true,
613
- };
614
- // Generate modal
615
- Modal(self.el, self.modal);
616
- }
617
-
618
- // Create input controls
619
- if (self.input) {
620
- self.input.classList.add('lm-calendar-input');
621
- self.input.addEventListener('focus', self.open);
622
- self.input.addEventListener('click', self.open);
623
- self.input.addEventListener('blur', blur);
624
- }
625
-
626
- /**
627
- * Handler keyboard
628
- * @param e
629
- */
630
- self.content.addEventListener('keydown', function(e){
631
- if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
632
- self.prev(e);
633
- } else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
634
- self.next(e);
635
- } else if (e.key === 'Enter') {
636
- // Select
637
- self.select(self.options[self.cursor.index]);
638
- // If is range do something different
639
- if (self.type !== 'range') {
640
- self.value = self.getValue();
641
- self.close();
642
- }
643
- }
644
- });
645
-
646
- /**
647
- * Mouse wheel handler
648
- */
649
- self.content.addEventListener('wheel', function(e){
650
- if (e.deltaY < 0) {
651
- self.prev();
652
- } else {
653
- self.next();
654
- }
655
- });
656
-
657
- /**
658
- * Range handler
659
- * @param e
660
- */
661
- self.content.addEventListener('mouseover', function(e){
662
- if (e.target.lemon) {
663
- updateRange(e.target.lemon.self);
664
- }
665
- });
666
- }
667
-
668
- return `<div class="lm-calendar">
669
- <div class="lm-calendar-options">
670
- <button onclick="self.value = ''; self.close();">Reset</button>
671
- <button onclick="self.value = self.getValue(); self.close();">Done</button>
672
- </div>
673
- <div class="lm-calendar-container" data-view="{{self.view}}">
674
- <div class="lm-calendar-header">
675
- <div>
676
- <div class="lm-calendar-labels"><div onclick="self.view = 'months'">{{self.month}}</div> <div onclick="self.view = 'years'">{{self.year}}</div></div>
677
- <div class="lm-calendar-navigation"><i class="material-icons ripple" onclick="self.prev()" tabindex="0">arrow_drop_up</i> <i class="material-icons ripple" onclick="self.next()" tabindex="0">arrow_drop_down</i></div>
678
- </div>
679
- <div class="lm-calendar-weekdays" :loop="self.weekdays"><div>{{self.title}}</div></div>
680
- </div>
681
- <div class="lm-calendar-content" :loop="self.options" tabindex="0" :ref="self.content">
682
- <div data-start="{{self.start}}" data-end="{{self.end}}" data-range="{{self.range}}" data-event="{{self.data}}" data-grey="{{self.grey}}" data-bold="{{self.bold}}" data-selected="{{self.selected}}" onclick="self.parent.select(self)">{{self.title}}</div>
683
- </div>
684
- <div class="lm-calendar-footer">
685
- <div><select :loop="self.hours"><option value="{{self.value}}">{{self.title}}</option></select>:<select :loop="self.minutes"><option value="{{self.value}}">{{self.title}}</option></select></div>
686
- <div><input type="button" value="Update" onclick="self.value = self.getValue(); self.close();" class="ripple"></div>
687
- </div>
688
- </div>
689
- </div>`
690
- }
691
-
692
- lemonade.setComponents({ Calendar: Calendar });
693
-
694
- return function (root, options) {
695
- if (typeof (root) === 'object') {
696
- lemonade.render(Calendar, root, options)
697
- return options;
698
- } else {
699
- return Calendar.call(this, root)
700
- }
701
- }
1
+ /**
2
+ * render: ()
3
+ * valid-ranges: []
4
+ * disabled
5
+ * dateToNum UTC
6
+ * navigation with icons Enter key
7
+ */
8
+ if (! lemonade && typeof (require) === 'function') {
9
+ var lemonade = require('lemonadejs');
10
+ }
11
+
12
+ if (! Modal && typeof (require) === 'function') {
13
+ var Modal = require('@lemonadejs/modal');
14
+ }
15
+
16
+ ; (function (global, factory) {
17
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
18
+ typeof define === 'function' && define.amd ? define(factory) :
19
+ global.Calendar = factory();
20
+ }(this, (function () {
21
+
22
+ // Weekdays
23
+ const Weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
24
+
25
+ // Months
26
+ const Months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
27
+
28
+ // Excel like dates
29
+ const excelInitialTime = Date.UTC(1900, 0, 0);
30
+ const excelLeapYearBug = Date.UTC(1900, 1, 29);
31
+ const millisecondsPerDay = 86400000;
32
+
33
+ function isValidDate(d) {
34
+ return d instanceof Date && !isNaN(d.getTime());
35
+ }
36
+
37
+ /**
38
+ * Date to number
39
+ */
40
+ const dateToNum = function (jsDate) {
41
+ if (typeof(jsDate) === 'string') {
42
+ jsDate = new Date(jsDate + ' GMT+0');
43
+ }
44
+ let jsDateInMilliseconds = jsDate.getTime();
45
+ if (jsDateInMilliseconds >= excelLeapYearBug) {
46
+ jsDateInMilliseconds += millisecondsPerDay;
47
+ }
48
+ jsDateInMilliseconds -= excelInitialTime;
49
+
50
+ return jsDateInMilliseconds / millisecondsPerDay;
51
+ }
52
+
53
+ const numToDate = function (excelSerialNumber, asString) {
54
+ if (! excelSerialNumber) {
55
+ return '';
56
+ }
57
+
58
+ let jsDateInMilliseconds = excelInitialTime + excelSerialNumber * millisecondsPerDay;
59
+ if (jsDateInMilliseconds >= excelLeapYearBug) {
60
+ jsDateInMilliseconds -= millisecondsPerDay;
61
+ }
62
+
63
+ const d = new Date(jsDateInMilliseconds);
64
+
65
+ let arr = [
66
+ d.getUTCFullYear(),
67
+ Two(d.getUTCMonth() + 1),
68
+ Two(d.getUTCDate()),
69
+ Two(d.getUTCHours()),
70
+ Two(d.getUTCMinutes()),
71
+ Two(d.getUTCSeconds()),
72
+ ];
73
+
74
+ if (asString) {
75
+ return arrayToStringDate(arr);
76
+ } else {
77
+ return arr;
78
+ }
79
+ }
80
+
81
+ const arrayToStringDate = function(arr) {
82
+ return [arr[0],Two(arr[1]),Two(arr[2])].join('-');
83
+ }
84
+
85
+ /**
86
+ * Create a data calendar object based on the view
87
+ */
88
+ const views = {
89
+ years: function(date) {
90
+ let year = date.getFullYear();
91
+ let result = [];
92
+ let start = year % 16;
93
+ let complement = 16 - start;
94
+
95
+ for (let i = year-start; i < year+complement; i++) {
96
+ let item = {
97
+ title: i,
98
+ value: i
99
+ };
100
+ result.push(item);
101
+ // Select cursor
102
+ if (this.cursor.y === i) {
103
+ // Select item
104
+ item.selected = true;
105
+ // Cursor
106
+ this.cursor.index = result.length - 1;
107
+ }
108
+ }
109
+ return result;
110
+ },
111
+ months: function(date) {
112
+ let year = date.getFullYear();
113
+ let result = [];
114
+ for (let i = 0; i < 12; i++) {
115
+ let item = {
116
+ title: Months[i].substring(0,3),
117
+ value: i
118
+ }
119
+ // Add the item to the data
120
+ result.push(item);
121
+ // Select cursor
122
+ if (this.cursor.y === year && this.cursor.m === i) {
123
+ // Select item
124
+ item.selected = true;
125
+ // Cursor
126
+ this.cursor.index = result.length - 1;
127
+ }
128
+ }
129
+
130
+ return result;
131
+ },
132
+ days: function(date) {
133
+ let year = date.getFullYear();
134
+ let month = date.getMonth();
135
+ let data = filterData.call(this, year, month);
136
+
137
+ // First day
138
+ let tmp = new Date(year, month, 1, 0, 0, 0);
139
+ let firstDay = tmp.getDay();
140
+
141
+ let result = [];
142
+ for (let i = 1-firstDay; i <= 42-firstDay; i++) {
143
+ // Get the day
144
+ tmp = new Date(year, month, i, 0, 0, 0);
145
+ // Day
146
+ let day = tmp.getDate();
147
+ // Create the item
148
+ let item = {
149
+ title: day,
150
+ value: i,
151
+ number: dateToNum(tmp.toString())
152
+ }
153
+ // Add the item to the date
154
+ result.push(item);
155
+ // Check selections
156
+ if (tmp.getMonth() !== month) {
157
+ // Days are not in the current month
158
+ item.grey = true;
159
+ } else {
160
+ // Check for data
161
+ let d = [ year, Two(month+1), Two(day)].join('-');
162
+ if (data && data[d]) {
163
+ item.data = data[d];
164
+ }
165
+ // Select cursor
166
+ if (this.cursor.y === year && this.cursor.m === month && this.cursor.d === day) {
167
+ // Select item
168
+ item.selected = true;
169
+ // Cursor
170
+ this.cursor.index = result.length - 1;
171
+ }
172
+ }
173
+
174
+ // Select range
175
+ if (this.range && this.rangeValues) {
176
+ // Mark the start and end points
177
+ if (this.rangeValues[0] === item.number) {
178
+ item.range = true;
179
+ item.start = true;
180
+ }
181
+ if (this.rangeValues[1] === item.number) {
182
+ item.range = true;
183
+ item.end = true;
184
+ }
185
+ // Re-recreate teh range
186
+ if (this.rangeValues[0] && this.rangeValues[1]) {
187
+ if (this.rangeValues[0] <= item.number && this.rangeValues[1] >= item.number) {
188
+ item.range = true;
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ return result;
195
+ },
196
+ hours: function() {
197
+ let result = [];
198
+ for (let i = 0; i < 24; i++) {
199
+ let item = {
200
+ title: Two(i),
201
+ value: i
202
+ };
203
+ result.push(item);
204
+ }
205
+ return result;
206
+ },
207
+ minutes: function() {
208
+ let result = [];
209
+ for (let i = 0; i < 60; i=i+5) {
210
+ let item = {
211
+ title: Two(i),
212
+ value: i
213
+ };
214
+ result.push(item);
215
+ }
216
+ return result;
217
+ }
218
+ }
219
+
220
+ const filterData = function(year, month) {
221
+ // Data for the month
222
+ let data = {};
223
+ if (Array.isArray(this.data)) {
224
+ this.data.map(function (v) {
225
+ let d = year + '-' + Two(month + 1);
226
+ if (v.date.substring(0, 7) === d) {
227
+ if (!data[v.date]) {
228
+ data[v.date] = [];
229
+ }
230
+ data[v.date].push(v);
231
+ }
232
+ });
233
+ }
234
+ return data;
235
+ }
236
+ // Get the short weekdays name
237
+ const getWeekdays = function() {
238
+ return Weekdays.map(w => {
239
+ return { title: w.substring(0, 1) };
240
+ })
241
+ }
242
+
243
+ // Define the hump based on the view
244
+ const getJump = function(e) {
245
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
246
+ return this.view === 'days' ? 7 : 4;
247
+ }
248
+
249
+ return 1;
250
+ }
251
+
252
+ // Get the position of the data based on the view
253
+ const getPosition = function() {
254
+ let position = 2;
255
+ if (this.view === 'years') {
256
+ position = 0;
257
+ } else if (this.view === 'months') {
258
+ position = 1;
259
+ }
260
+ return position;
261
+ }
262
+
263
+ // Transform in two digits
264
+ const Two = function(value) {
265
+ value = '' + value;
266
+ if (value.length === 1) {
267
+ value = '0' + value;
268
+ }
269
+ return value;
270
+ }
271
+
272
+ const Calendar = function() {
273
+ let self = this;
274
+
275
+ // Weekdays
276
+ self.weekdays = getWeekdays();
277
+
278
+ // Cursor
279
+ self.cursor = {};
280
+
281
+ // Calendar date
282
+ let date = new Date();
283
+
284
+ // Range
285
+ self.rangeValues = null;
286
+
287
+ /**
288
+ * Update the internal date
289
+ * @param {Date|string|number[]} d instance of Date
290
+ *
291
+ */
292
+ const setDate = function(d) {
293
+ if (Array.isArray(d)) {
294
+ d = new Date(Date.UTC(...d));
295
+ } else if (typeof(d) === 'string') {
296
+ d = new Date(d);
297
+ }
298
+ // Update internal date
299
+ date = d;
300
+ // Update the headers of the calendar
301
+ let value = d.toISOString().substring(0,10).split('-');
302
+ // Update the month label
303
+ self.month = Months[parseInt(value[1])-1];
304
+ // Update the year label
305
+ self.year = parseInt(value[0]);
306
+ // Load data
307
+ if (! self.view) {
308
+ // Start on the days view will start the data
309
+ self.view = 'days';
310
+ } else {
311
+ // Reload the data for the same view
312
+ self.options = views[self.view].call(self, date);
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Set the internal cursor
318
+ * @param {object} s
319
+ */
320
+ const setCursor = function(s) {
321
+ // Remove selection from the current object
322
+ let item = self.options[self.cursor.index];
323
+ if (typeof(item) !== 'undefined') {
324
+ item.selected = false;
325
+ }
326
+ // Update the date based on the click
327
+ let v = updateDate(s.value, getPosition.call(self));
328
+ let d = new Date(Date.UTC(...v));
329
+ // Update cursor controller
330
+ self.cursor = {
331
+ y: d.getFullYear(),
332
+ m: d.getMonth(),
333
+ d: d.getDate(),
334
+ };
335
+ // Update cursor based on the object position
336
+ if (s) {
337
+ // Update selected property
338
+ s.selected = true;
339
+ // New cursor
340
+ self.cursor.index = self.options.indexOf(s);
341
+ }
342
+ return d;
343
+ }
344
+
345
+ /**
346
+ * Update the current date
347
+ * @param {number} v new value for year, month or day
348
+ * @param {number} position (0,1,2 - year,month,day)
349
+ * @returns {number[]}
350
+ */
351
+ const updateDate = function(v, position) {
352
+ // Current internal date
353
+ let value = [date.getFullYear(), date.getMonth(), date.getDate(),0,0,0];
354
+ // Update internal date
355
+ value[position] = v;
356
+ // Return new value
357
+ return value;
358
+ }
359
+
360
+ /**
361
+ * This method move the data from the view up or down
362
+ * @param direction
363
+ */
364
+ const move = function(direction) {
365
+ let value;
366
+
367
+ // Update the new internal date
368
+ if (self.view === 'days') {
369
+ // Select the new internal date
370
+ value = updateDate(date.getMonth()+direction, 1);
371
+ } else if (self.view === 'months') {
372
+ // Select the new internal date
373
+ value = updateDate(date.getFullYear()+direction, 0);
374
+ } else if (self.view === 'years') {
375
+ // Select the new internal date
376
+ value = updateDate(date.getFullYear()+(direction*16), 0);
377
+ }
378
+
379
+ // Update view
380
+ if (value) {
381
+ setDate(value);
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Keyboard handler
387
+ * @param {number} direction of the action
388
+ * @param {object} e keyboard event
389
+ */
390
+ const moveCursor = function(direction, e) {
391
+ direction = direction * getJump.call(self, e);
392
+ // Remove the selected from the current selection
393
+ let s = self.options[self.cursor.index];
394
+ // If the selection is going outside the viewport
395
+ if (typeof(s) === 'undefined' || ! s.selected) {
396
+ // Go back to the view
397
+ setDate([ self.cursor.y, self.cursor.m, self.cursor.d ]);
398
+ }
399
+
400
+ // Jump to the index
401
+ let index = self.cursor.index + direction;
402
+
403
+ // See if the new position is in the viewport
404
+ if (typeof(self.options[index]) === 'undefined') {
405
+ // Adjust the index for next collection of data
406
+ if (self.view === 'days') {
407
+ if (index < 0) {
408
+ index = 42 + index;
409
+ } else {
410
+ index = index - 42;
411
+ }
412
+ } else if (self.view === 'years') {
413
+ if (index < 0) {
414
+ index = 4 + index;
415
+ } else {
416
+ index = index - 4;
417
+ }
418
+ } else if (self.view === 'months') {
419
+ if (index < 0) {
420
+ index = 12 + index;
421
+ } else {
422
+ index = index - 12;
423
+ }
424
+ }
425
+
426
+ // Move the data up or down
427
+ move(direction > 0 ? 1 : -1);
428
+ }
429
+
430
+ // Update the date based on the click
431
+ setCursor(self.options[index]);
432
+
433
+ // Update ranges
434
+ updateRange(self.options[index])
435
+ }
436
+
437
+ /**
438
+ * Handler blur
439
+ * @param e
440
+ */
441
+ const blur = function(e) {
442
+ if (self.modal) {
443
+ if (!(e.relatedTarget && self.modal.el.contains(e.relatedTarget))) {
444
+ if (self.modal.closed === false) {
445
+ self.modal.closed = true
446
+ }
447
+ }
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Set the limits of a range
453
+ * @param s
454
+ */
455
+ const setRange = function(s) {
456
+ if (self.view === 'days' && self.range) {
457
+ let d = self.getValue();
458
+ // Date to number
459
+ let number = dateToNum(d);
460
+ // Start a new range
461
+ if (self.rangeValues && (self.rangeValues[0] > number || self.rangeValues[1])) {
462
+ destroyRange();
463
+ }
464
+ // Range
465
+ s.range = true;
466
+ // Update range
467
+ if (! self.rangeValues) {
468
+ s.start = true;
469
+ self.rangeValues = [number, null];
470
+ } else {
471
+ s.end = true;
472
+ self.rangeValues[1] = number;
473
+ }
474
+ }
475
+ }
476
+
477
+ /**
478
+ * Update the visible range
479
+ * @param s
480
+ */
481
+ const updateRange = function(s) {
482
+ if (self.range && self.view === 'days' && self.rangeValues) {
483
+ // Creating a range
484
+ if (self.rangeValues[0] && ! self.rangeValues[1]) {
485
+ let number = s.number;
486
+ if (number) {
487
+ // Update range properties
488
+ for (let i = 0; i < self.options.length; i++) {
489
+ // Item number
490
+ let v = self.options[i].number;
491
+ // Update property condition
492
+ self.options[i].range = v > self.rangeValues[0] && v <= number;
493
+ }
494
+ }
495
+ }
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Destroy the range
501
+ */
502
+ const destroyRange = function() {
503
+ for (let i = 0; i < self.options.length; i++) {
504
+ self.options[i].range = false;
505
+ self.options[i].start = false;
506
+ self.options[i].end = false;
507
+ }
508
+ self.rangeValues = null;
509
+ }
510
+
511
+ const setValue = function(reset) {
512
+ if (reset) {
513
+ self.value = '';
514
+ self.input.value = '';
515
+ destroyRange();
516
+ } else {
517
+ if (self.range) {
518
+ self.value = self.rangeValues[0];
519
+ if (self.input) {
520
+ if (self.numeric) {
521
+ self.input.value = self.rangeValues;
522
+ } else {
523
+ self.input.value = [numToDate(self.rangeValues[0], true), numToDate(self.rangeValues[1], true)];
524
+ }
525
+ }
526
+ } else {
527
+ self.value = self.getValue();
528
+ if (self.input) {
529
+ if (self.numeric) {
530
+ self.input.value = dateToNum(self.value);
531
+ } else {
532
+ self.input.value = self.value;
533
+ }
534
+ }
535
+ }
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Select an item with the enter or mouse
541
+ * @param {object} e - mouse event
542
+ * @param {object} item - selected cell
543
+ */
544
+ self.select = function(e, item) {
545
+ // Update cursor generic
546
+ let value = setCursor(item);
547
+ // Update range
548
+ setRange(item);
549
+ // Based where was the click
550
+ if (! (self.view === 'days' && ! item.grey)) {
551
+ // Update the internal date
552
+ setDate(value);
553
+ }
554
+ // Go back to the view of days
555
+ if (self.view !== 'days') {
556
+ self.view = 'days';
557
+ }
558
+ }
559
+
560
+ /**
561
+ * Next handler
562
+ * @param {object?} e mouse event
563
+ */
564
+ self.next = function(e) {
565
+ if (! e || e.type === 'click') {
566
+ // Icon click
567
+ move(1);
568
+ } else {
569
+ // Keyboard handler
570
+ moveCursor(1, e);
571
+ }
572
+ }
573
+
574
+ /**
575
+ * Next handler
576
+ * @param {object?} e mouse event
577
+ */
578
+ self.prev = function(e) {
579
+ if (! e || e.type === 'click') {
580
+ // Icon click
581
+ move(-1);
582
+ } else {
583
+ // Keyboard handler
584
+ moveCursor(-1, e);
585
+ }
586
+ }
587
+
588
+ /**
589
+ * Open the modal
590
+ */
591
+ self.open = function(e) {
592
+ if (self.modal && self.modal.closed) {
593
+ // Open modal
594
+ self.modal.closed = false;
595
+ // Set the focus on the content to use the keyboard
596
+ if (! (self.input && e.target.getAttribute('readonly') === null)) {
597
+ self.content.focus();
598
+ }
599
+ }
600
+ }
601
+
602
+ /**
603
+ * Close the modal
604
+ */
605
+ self.close = function() {
606
+ if (self.modal && self.modal.closed === false) {
607
+ // Close modal
608
+ self.modal.closed = true;
609
+ }
610
+ }
611
+
612
+ self.reset = function() {
613
+ setValue(true);
614
+ self.close();
615
+ }
616
+
617
+ self.update = function() {
618
+ setValue();
619
+ self.close();
620
+ }
621
+
622
+ /**
623
+ * Change the view
624
+ */
625
+ self.setView = function() {
626
+ let v = this.getAttribute('data-view');
627
+ if (v) {
628
+ self.view = v;
629
+ }
630
+ }
631
+
632
+ /**
633
+ * Get value from cursor
634
+ * @returns {string}
635
+ */
636
+ self.getValue = function() {
637
+ let v = [ self.cursor.y, self.cursor.m, self.cursor.d ];
638
+ let d = new Date(Date.UTC(...v));
639
+ // Update the headers of the calendar
640
+ return d.toISOString().substring(0,10);
641
+ }
642
+
643
+ self.onchange = function(prop) {
644
+ if (prop === 'view') {
645
+ if (typeof(views[self.view]) === 'function') {
646
+ // When change the view update the data
647
+ self.options = views[self.view].call(self, date);
648
+ }
649
+ } else if (prop === 'value') {
650
+ if (typeof(self.onupdate) === 'function') {
651
+ self.onupdate.call(self, self.value);
652
+ }
653
+ } else if (prop === 'options') {
654
+ self.content.focus();
655
+ }
656
+ }
657
+
658
+ self.onload = function() {
659
+ let d;
660
+ if (self.value) {
661
+ d = new Date(self.value);
662
+ }
663
+ // if no date is defined
664
+ if (! isValidDate(d)) {
665
+ d = new Date();
666
+ }
667
+ // Update my index
668
+ self.cursor = {
669
+ y: d.getFullYear(),
670
+ m: d.getMonth(),
671
+ d: d.getDate(),
672
+ };
673
+ // Populate components
674
+ self.hours = views.hours();
675
+ self.minutes = views.minutes();
676
+
677
+ // Update the internal calendar date
678
+ setDate(d);
679
+
680
+ if (self.type !== "inline") {
681
+ // Create modal instance
682
+ self.modal = {
683
+ width: 300,
684
+ closed: true,
685
+ focus: false,
686
+ };
687
+ // Generate modal
688
+ Modal(self.el, self.modal);
689
+ }
690
+
691
+ // Create input controls
692
+ if (self.input) {
693
+ self.input.classList.add('lm-calendar-input');
694
+ self.input.addEventListener('focus', self.open);
695
+ self.input.addEventListener('click', self.open);
696
+ self.input.addEventListener('blur', blur);
697
+
698
+ // Retrieve the value
699
+ if (self.value) {
700
+ self.input.value = self.value;
701
+ } else if (self.input.value) {
702
+ self.value = self.input.value;
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Handler keyboard
708
+ * @param {object} e - event
709
+ */
710
+ self.content.addEventListener('keydown', function(e){
711
+ if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
712
+ self.prev(e);
713
+ } else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
714
+ self.next(e);
715
+ } else if (e.key === 'Enter') {
716
+ // Current view
717
+ let view = self.view;
718
+ // Select
719
+ self.select(e, self.options[self.cursor.index]);
720
+ // If is range do something diferent
721
+ if (view === 'days' && ! self.range) {
722
+ self.update();
723
+ }
724
+ }
725
+ });
726
+
727
+ /**
728
+ * Mouse wheel handler
729
+ * @param {object} e - mouse event
730
+ */
731
+ self.content.addEventListener('wheel', function(e){
732
+ if (e.deltaY < 0) {
733
+ self.prev();
734
+ } else {
735
+ self.next();
736
+ }
737
+ });
738
+
739
+ /**
740
+ * Range handler
741
+ * @param {object} e - mouse event
742
+ */
743
+ self.content.addEventListener('mouseover', function(e){
744
+ if (e.target.lemon) {
745
+ updateRange(e.target.lemon.self);
746
+ }
747
+ });
748
+
749
+ // Create event for focus out
750
+ self.el.addEventListener("focusout", (e) => {
751
+ if (e.relatedTarget !== self.input && ! self.el.contains(e.relatedTarget)) {
752
+ self.close();
753
+ }
754
+ });
755
+ }
756
+
757
+ return `<div class="lm-calendar">
758
+ <div class="lm-calendar-options">
759
+ <button onclick="self.reset">Reset</button>
760
+ <button onclick="self.update">Done</button>
761
+ </div>
762
+ <div class="lm-calendar-container" data-view="{{self.view}}">
763
+ <div class="lm-calendar-header">
764
+ <div>
765
+ <div class="lm-calendar-labels"><button onclick="self.setView" data-view="months">{{self.month}}</button> <button onclick="self.setView" data-view="years">{{self.year}}</button></div>
766
+ <div class="lm-calendar-navigation"><button type="button" class="material-icons ripple" onclick="self.prev" tabindex="0">arrow_drop_up</button> <button type="button" class="material-icons ripple" onclick="self.next" tabindex="0">arrow_drop_down</button></div>
767
+ </div>
768
+ <div class="lm-calendar-weekdays" :loop="self.weekdays"><div>{{self.title}}</div></div>
769
+ </div>
770
+ <div class="lm-calendar-content" :loop="self.options" tabindex="0" :ref="self.content">
771
+ <div data-start="{{self.start}}" data-end="{{self.end}}" data-range="{{self.range}}" data-event="{{self.data}}" data-grey="{{self.grey}}" data-bold="{{self.bold}}" data-selected="{{self.selected}}" onclick="self.parent.select">{{self.title}}</div>
772
+ </div>
773
+ <div class="lm-calendar-footer">
774
+ <div><select :loop="self.hours"><option value="{{self.value}}">{{self.title}}</option></select>:<select :loop="self.minutes"><option value="{{self.value}}">{{self.title}}</option></select></div>
775
+ <div><input type="button" value="Update" onclick="self.update" class="ripple"></div>
776
+ </div>
777
+ </div>
778
+ </div>`
779
+ }
780
+
781
+ lemonade.setComponents({ Calendar: Calendar });
782
+
783
+ return function (root, options) {
784
+ if (typeof (root) === 'object') {
785
+ lemonade.render(Calendar, root, options)
786
+ return options;
787
+ } else {
788
+ return Calendar.call(this, root)
789
+ }
790
+ }
702
791
  })));