@lemonadejs/calendar 1.1.4 → 3.0.1

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 ADDED
@@ -0,0 +1,80 @@
1
+ # LemonadeJS Calendar
2
+
3
+ [Official website and documentation is here](https://lemonadejs.net/components/calendar)
4
+
5
+ Compatible with Vanilla JavaScript, LemonadeJS, React, Vue or Angular.
6
+
7
+ The LemonadeJS Calendar Component is a lightweight and agile calendar solution that empowers developers with efficient date management capabilities. With seamless navigation between months and years, intuitive day selection, and the ability to attach values and events, this highly customizable component provides a versatile foundation for scheduling applications, booking systems, and more. Its optimized codebase ensures fast performance, while its responsive design guarantees a consistent user experience across devices. Streamline your date management with the LemonadeJS Calendar Component and unlock enhanced productivity for your users.
8
+
9
+ ## Features
10
+
11
+ - Lightweight: The JavaScript Calendar is only about 2 KBytes;
12
+ - Integration: It can be used as a standalone library or integrated with any modern framework;
13
+
14
+ ## Getting Started
15
+
16
+ You can install using NPM or using directly from a CDN.
17
+
18
+ ### npm Installation
19
+
20
+ To install it in your project using npm, run the following command:
21
+
22
+ ```bash
23
+ $ npm install @lemonadejs/calendar
24
+ ```
25
+
26
+ ### CDN
27
+
28
+ To use calendar via a CDN, include the following script tags in your HTML file:
29
+
30
+ ```html
31
+ <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
32
+ <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@lemonadejs/calendar/dist/index.min.js"></script>
33
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/calendar/dist/style.min.css" />
34
+ ```
35
+
36
+ ### Usage
37
+
38
+ Quick example with Lemonade
39
+
40
+ ```javascript
41
+ import Calendar from "@lemonadejs/calendar";
42
+ import "@lemonadejs/calendar/dist/style.css"
43
+
44
+ export default function App() {
45
+ const self = this;
46
+
47
+ return `<Calendar />`;
48
+ }
49
+ ```
50
+
51
+ ### Configuration
52
+
53
+ You can configure things such as calendar starting date, calendar events, and customize functions.
54
+
55
+ #### Calendar Properties
56
+
57
+ | Property | Type | Description |
58
+ | -------- | ---- | ----------- |
59
+ | value | date | The value currently attached to the calendar. |
60
+ | range | array | Defines a restricted range of selectable dates within the calendar. Example: ['2023-06-20', '2023-06-25']. |
61
+ | closed | boolean | Control when the calendar modal is open or closed. |
62
+ | time | boolean | Enables time selection into the calendar. |
63
+
64
+ ### Calendar Events
65
+
66
+ | Event | Type | Description |
67
+ | -------- | ---- | ----------- |
68
+ | onopen? | () => void | Called when modal opens. |
69
+ | onclose? | () => void | Called when modal closes. |
70
+ | onupdate? | (instance.value) => void | Called when value updates. |
71
+ | onchange? | (instance.value) => void | Called when some state inside the component changes. |
72
+
73
+ ## License
74
+
75
+ The [LemonadeJS](https://lemonadejs.net) Calendar is released under the MIT.
76
+
77
+ ## Other Tools
78
+
79
+ - [jSuites](https://jsuites.net/v4/)
80
+ - [Jspreadsheet](https://jspreadsheet.com)
@@ -0,0 +1,11 @@
1
+ <html>
2
+ <script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
3
+ <script src="./index.js"></script>
4
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/calendar/dist/style.min.css" />
5
+ <div id='root'></div>
6
+
7
+ <script>
8
+ console.log(Calendar)
9
+ Calendar(document.getElementById('root'), {});
10
+ </script>
11
+ </html>
package/dist/index.js CHANGED
@@ -1,50 +1,702 @@
1
- ;(function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
- typeof define === 'function' && define.amd ? define(factory) :
4
- global.Calendar = factory();
5
- }(this, (function () {
6
-
7
- // Load LemonadeJS
8
- if (typeof(lemonade) == 'undefined') {
9
- if (typeof(require) === 'function') {
10
- var lemonade = require('lemonadejs');
11
- } else if (window.lemonade) {
12
- var lemonade = window.lemonade;
13
- }
14
- }
15
-
16
- // Load LemonadeJS
17
- if (typeof(jSuites) == 'undefined') {
18
- if (typeof(require) === 'function') {
19
- var jSuites = require('jsuites');
20
- } else if (window.jSuites) {
21
- var jSuites = window.jSuites;
22
- }
23
- }
24
-
25
- return function() {
26
- var self = this;
27
-
28
- self.onchange = function(prop) {
29
- if (self.instance && prop === 'value') {
30
- self.instance.setValue(self.value);
31
- }
32
- }
33
-
34
- self.create = function(o) {
35
- if (this.time || this.time == 'true') {
36
- this.time = true;
37
- }
38
-
39
- self.instance = jSuites.calendar(o, this);
40
- }
41
-
42
- if (self.type == 'input') {
43
- var template = `<input type="text" @ready="self.create(this)" name="{{self.name}}" value="{{self.value}}" />`;
44
- } else {
45
- var template = `<div @ready="self.create(this)" name="{{self.name}}" value="{{self.value}}"></div>`;
46
- }
47
-
48
- return lemonade.element(template, self);
49
- }
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
+ }
50
702
  })));
package/dist/style.css ADDED
@@ -0,0 +1,229 @@
1
+ .lm-calendar .lm-modal {
2
+ min-width: initial;
3
+ min-height: initial;
4
+ }
5
+
6
+ .lm-calendar-options {
7
+ display: none;
8
+ }
9
+
10
+ .lm-modal .lm-calendar-options {
11
+ display: flex;
12
+ justify-content: space-between;
13
+ border-bottom: 1px solid #ddd;
14
+ }
15
+
16
+ .lm-modal .lm-calendar-options button {
17
+ border: 0;
18
+ background-color: transparent;
19
+ text-transform: uppercase;
20
+ cursor: pointer;
21
+ padding: 15px;
22
+ }
23
+
24
+ .lm-calendar-header {
25
+ display: flex;
26
+ flex-direction: column;
27
+ }
28
+
29
+ .lm-calendar-header > div:first-child {
30
+ display: flex;
31
+ align-items: center;
32
+ padding: 10px;
33
+ flex: 1;
34
+ user-select: none;
35
+ }
36
+
37
+ .lm-calendar-header .lm-calendar-labels {
38
+ font-size: 1.4em;
39
+ display: flex;
40
+ flex: 1;
41
+ cursor: pointer;
42
+ padding-left: 5px;
43
+ }
44
+
45
+ .lm-calendar-header .lm-calendar-labels > div {
46
+ margin: 2px;
47
+ }
48
+
49
+ .lm-calendar-navigation {
50
+ cursor: pointer;
51
+ }
52
+
53
+ .lm-calendar-navigation i {
54
+ padding: 5px;
55
+ border-radius: 24px;
56
+ }
57
+
58
+ .lm-calendar-navigation i:hover {
59
+ background-color: #eee;
60
+ }
61
+
62
+ .lm-calendar-weekdays {
63
+ display: none;
64
+ grid-template-columns: repeat(7, 1fr);
65
+ grid-gap: 2px;
66
+ padding: 0 10px 0 10px;
67
+ font-size: 0.8em;
68
+ }
69
+
70
+ .lm-calendar-container[data-view="days"] .lm-calendar-weekdays {
71
+ display: grid;
72
+ }
73
+
74
+ .lm-calendar-weekdays > div {
75
+ display: inline-block;
76
+ padding: 10px;
77
+ box-sizing: border-box;
78
+ text-align: center;
79
+ font-weight: bold;
80
+ }
81
+
82
+ .lm-calendar-content {
83
+ display: grid;
84
+ grid-template-columns: repeat(7, 1fr);
85
+ grid-gap: 0;
86
+ padding: 8px;
87
+ font-size: 0.8em;
88
+ outline: none;
89
+ }
90
+
91
+ .lm-calendar-content > div {
92
+ box-sizing: border-box;
93
+ text-align: center;
94
+ aspect-ratio: 1 / 1;
95
+ display: flex;
96
+ flex-direction: column;
97
+ justify-content: center;
98
+ align-items: center;
99
+ padding: 10px;
100
+ cursor: pointer;
101
+ border-radius: 100px;
102
+ background-origin: padding-box;
103
+ }
104
+
105
+ .lm-calendar-container[data-view="months"] .lm-calendar-content {
106
+ grid-template-columns: repeat(4, 1fr);
107
+ }
108
+ .lm-calendar-container[data-view="years"] .lm-calendar-content {
109
+ grid-template-columns: repeat(4, 1fr);
110
+ }
111
+
112
+ .lm-calendar-content > div[data-grey="true"] {
113
+ color: #ccc;
114
+ }
115
+
116
+ .lm-calendar-content > div[data-bold="true"] {
117
+ font-weight: bold;
118
+ }
119
+
120
+ .lm-calendar-content > div[data-event="true"]::before {
121
+ content: '';
122
+ position: absolute;
123
+ margin-top: 22px;
124
+ width: 3px;
125
+ height: 3px;
126
+ border-radius: 3px;
127
+ background-color: red;
128
+ }
129
+
130
+ .lm-calendar-content > div[data-selected="true"] {
131
+ font-weight: bold;
132
+ background-color: #eee;
133
+ }
134
+
135
+ .lm-calendar-content:focus > div[data-selected="true"] {
136
+ outline: 2px solid black;
137
+ outline-offset: -2px;
138
+ }
139
+
140
+ .lm-calendar-content > div:hover {
141
+ background-color: #eee;
142
+ }
143
+
144
+ .lm-calendar-content > div[data-range="true"] {
145
+ position: relative;
146
+ }
147
+
148
+ .lm-calendar-content > div[data-start="true"],
149
+ .lm-calendar-content > div[data-end="true"] {
150
+ background-color: #78D350;
151
+ }
152
+
153
+ .lm-calendar-content > div[data-range="true"]::before {
154
+ content: "";
155
+ position: absolute;
156
+ left: 0;
157
+ right: 0;
158
+ top: 8px;
159
+ bottom: 8px;
160
+ background-color: #78D35050;
161
+ }
162
+
163
+ .lm-calendar-content > div[data-start="true"]::before {
164
+ left: 32px;
165
+ }
166
+
167
+ .lm-calendar-content > div[data-end="true"]::before {
168
+ right: 32px;
169
+ }
170
+
171
+ .lm-calendar-footer > div {
172
+ flex: 1;
173
+ }
174
+
175
+ .lm-calendar-footer {
176
+ display: flex;
177
+ margin: 0 10px 0 10px;
178
+ padding: 8px 0 8px 0;
179
+ line-height: 34px;
180
+ border-top: 1px solid #eee;
181
+ }
182
+
183
+ .lm-calendar-footer select {
184
+ border: 0;
185
+ background-color: transparent;
186
+ padding: 6px;
187
+ -moz-appearance: none;
188
+ -webkit-appearance: none;
189
+ margin: 2px;
190
+ border-radius: 32px;
191
+ font-size: 1.1em;
192
+ }
193
+
194
+ .lm-calendar-footer select:focus {
195
+ background-color: #eee;
196
+ }
197
+
198
+ .lm-calendar-footer select:hover {
199
+ background-color: #eee;
200
+ }
201
+
202
+ .lm-calendar-footer input {
203
+ border: transparent;
204
+ padding: 8px;
205
+ background-color: #eee;
206
+ width: 100%;
207
+ cursor: pointer;
208
+ }
209
+
210
+ .lm-calendar-input {
211
+ padding-right: 24px !important;
212
+ box-sizing: border-box;
213
+ background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-80q-33 0-56.5-23.5T120-160v-560q0-33 23.5-56.5T200-800h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840-720v560q0 33-23.5 56.5T760-80H200Zm0-80h560v-400H200v400Zm0-480h560v-80H200v80Zm0 0v-80 80Zm280 240q-17 0-28.5-11.5T440-440q0-17 11.5-28.5T480-480q17 0 28.5 11.5T520-440q0 17-11.5 28.5T480-400Zm-160 0q-17 0-28.5-11.5T280-440q0-17 11.5-28.5T320-480q17 0 28.5 11.5T360-440q0 17-11.5 28.5T320-400Zm320 0q-17 0-28.5-11.5T600-440q0-17 11.5-28.5T640-480q17 0 28.5 11.5T680-440q0 17-11.5 28.5T640-400ZM480-240q-17 0-28.5-11.5T440-280q0-17 11.5-28.5T480-320q17 0 28.5 11.5T520-280q0 17-11.5 28.5T480-240Zm-160 0q-17 0-28.5-11.5T280-280q0-17 11.5-28.5T320-320q17 0 28.5 11.5T360-280q0 17-11.5 28.5T320-240Zm320 0q-17 0-28.5-11.5T600-280q0-17 11.5-28.5T640-320q17 0 28.5 11.5T680-280q0 17-11.5 28.5T640-240Z"/></svg>') top 50% right 0 no-repeat, content-box;
214
+ }
215
+
216
+ .ripple {
217
+ background-position: center;
218
+ transition: background 0.8s;
219
+ }
220
+
221
+ .ripple:hover {
222
+ background: #E0E0E0 radial-gradient(circle, transparent 1%, #E0E0E0 1%) center/15000%;
223
+ }
224
+
225
+ .ripple:active {
226
+ background-color: #E0E0E0;
227
+ background-size: 100%;
228
+ transition: background 0s;
229
+ }
package/package.json CHANGED
@@ -1,22 +1,24 @@
1
- {
2
- "name": "@lemonadejs/calendar",
3
- "title": "LemonadeJS calendar",
4
- "description": "LemonadeJS calendar integration.",
5
- "author": {
6
- "name": "Contact <contact@lemonadejs.net>",
7
- "url": "https://lemonadejs.net"
8
- },
9
- "keywords": [
10
- "javascript calendar",
11
- "lemonadejs plugins",
12
- "js",
13
- "library",
14
- "javascript plugins"
15
- ],
16
- "dependencies": {
17
- "lemonadejs": "^2.6.3",
18
- "jsuites": "^4.15.1"
19
- },
20
- "main": "dist/index.js",
21
- "version": "1.1.4"
22
- }
1
+ {
2
+ "name": "@lemonadejs/calendar",
3
+ "title": "LemonadeJS calendar",
4
+ "description": "LemonadeJS Calendar Component.",
5
+ "author": {
6
+ "name": "Contact <contact@lemonadejs.net>",
7
+ "url": "https://lemonadejs.net"
8
+ },
9
+ "keywords": [
10
+ "javascript calendar",
11
+ "lemonadejs plugins",
12
+ "js",
13
+ "library",
14
+ "javascript plugins"
15
+ ],
16
+ "scripts": {
17
+ "build": "webpack --config webpack.config.js"
18
+ },
19
+ "dependencies": {
20
+ "lemonadejs": "^3.3.2"
21
+ },
22
+ "main": "dist/index.js",
23
+ "version": "3.0.1"
24
+ }