@lemonadejs/calendar 3.0.3 → 3.1.0
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/README.md +78 -81
- package/dist/index.d.ts +35 -24
- package/dist/index.js +793 -790
- package/dist/react.d.ts +13 -0
- package/dist/react.js +37 -31
- package/dist/style.css +230 -230
- package/dist/vue.d.ts +15 -0
- package/dist/vue.js +45 -32
- package/package.json +23 -23
package/dist/index.js
CHANGED
|
@@ -1,791 +1,794 @@
|
|
|
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
|
|
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
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
self.input.
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
<
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
<div
|
|
772
|
-
</div>
|
|
773
|
-
<div class="lm-calendar-
|
|
774
|
-
<div
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
+
position: 'absolute',
|
|
687
|
+
'auto-adjust': true,
|
|
688
|
+
'auto-close': false,
|
|
689
|
+
};
|
|
690
|
+
// Generate modal
|
|
691
|
+
Modal(self.el, self.modal);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Create input controls
|
|
695
|
+
if (self.input) {
|
|
696
|
+
self.input.classList.add('lm-calendar-input');
|
|
697
|
+
self.input.addEventListener('focus', self.open);
|
|
698
|
+
self.input.addEventListener('click', self.open);
|
|
699
|
+
self.input.addEventListener('blur', blur);
|
|
700
|
+
|
|
701
|
+
// Retrieve the value
|
|
702
|
+
if (self.value) {
|
|
703
|
+
self.input.value = self.value;
|
|
704
|
+
} else if (self.input.value) {
|
|
705
|
+
self.value = self.input.value;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Handler keyboard
|
|
711
|
+
* @param {object} e - event
|
|
712
|
+
*/
|
|
713
|
+
self.content.addEventListener('keydown', function(e){
|
|
714
|
+
if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
|
|
715
|
+
self.prev(e);
|
|
716
|
+
} else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
|
717
|
+
self.next(e);
|
|
718
|
+
} else if (e.key === 'Enter') {
|
|
719
|
+
// Current view
|
|
720
|
+
let view = self.view;
|
|
721
|
+
// Select
|
|
722
|
+
self.select(e, self.options[self.cursor.index]);
|
|
723
|
+
// If is range do something diferent
|
|
724
|
+
if (view === 'days' && ! self.range) {
|
|
725
|
+
self.update();
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Mouse wheel handler
|
|
732
|
+
* @param {object} e - mouse event
|
|
733
|
+
*/
|
|
734
|
+
self.content.addEventListener('wheel', function(e){
|
|
735
|
+
if (e.deltaY < 0) {
|
|
736
|
+
self.prev();
|
|
737
|
+
} else {
|
|
738
|
+
self.next();
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Range handler
|
|
744
|
+
* @param {object} e - mouse event
|
|
745
|
+
*/
|
|
746
|
+
self.content.addEventListener('mouseover', function(e){
|
|
747
|
+
if (e.target.lemon) {
|
|
748
|
+
updateRange(e.target.lemon.self);
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
// Create event for focus out
|
|
753
|
+
self.el.addEventListener("focusout", (e) => {
|
|
754
|
+
if (e.relatedTarget !== self.input && ! self.el.contains(e.relatedTarget)) {
|
|
755
|
+
self.close();
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return `<div class="lm-calendar">
|
|
761
|
+
<div class="lm-calendar-options">
|
|
762
|
+
<button onclick="self.reset">Reset</button>
|
|
763
|
+
<button onclick="self.update">Done</button>
|
|
764
|
+
</div>
|
|
765
|
+
<div class="lm-calendar-container" data-view="{{self.view}}">
|
|
766
|
+
<div class="lm-calendar-header">
|
|
767
|
+
<div>
|
|
768
|
+
<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>
|
|
769
|
+
<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>
|
|
770
|
+
</div>
|
|
771
|
+
<div class="lm-calendar-weekdays" :loop="self.weekdays"><div>{{self.title}}</div></div>
|
|
772
|
+
</div>
|
|
773
|
+
<div class="lm-calendar-content" :loop="self.options" tabindex="0" :ref="self.content">
|
|
774
|
+
<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>
|
|
775
|
+
</div>
|
|
776
|
+
<div class="lm-calendar-footer">
|
|
777
|
+
<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>
|
|
778
|
+
<div><input type="button" value="Update" onclick="self.update" class="ripple"></div>
|
|
779
|
+
</div>
|
|
780
|
+
</div>
|
|
781
|
+
</div>`
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
lemonade.setComponents({ Calendar: Calendar });
|
|
785
|
+
|
|
786
|
+
return function (root, options) {
|
|
787
|
+
if (typeof (root) === 'object') {
|
|
788
|
+
lemonade.render(Calendar, root, options)
|
|
789
|
+
return options;
|
|
790
|
+
} else {
|
|
791
|
+
return Calendar.call(this, root)
|
|
792
|
+
}
|
|
793
|
+
}
|
|
791
794
|
})));
|