@lemonadejs/calendar 2.0.0 → 3.0.3

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,81 @@
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
+ | type | string | |
64
+
65
+ ### Calendar Events
66
+
67
+ | Event | Type | Description |
68
+ | -------- | ---- | ----------- |
69
+ | onopen? | () => void | Called when modal opens. |
70
+ | onclose? | () => void | Called when modal closes. |
71
+ | onupdate? | (instance.value) => void | Called when value updates. |
72
+ | onchange? | (instance.value) => void | Called when some state inside the component changes. |
73
+
74
+ ## License
75
+
76
+ The [LemonadeJS](https://lemonadejs.net) Calendar is released under the MIT.
77
+
78
+ ## Other Tools
79
+
80
+ - [jSuites](https://jsuites.net/v4/)
81
+ - [Jspreadsheet](https://jspreadsheet.com)
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Official Type definitions for LemonadeJS plugins
3
+ * https://lemonadejs.net
4
+ * Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5
+ */
6
+
7
+ interface Calendar {
8
+ (): any
9
+ [key: string]: any
10
+ }
11
+
12
+ interface options {
13
+ range?: boolean;
14
+ type?: 'default' | 'inline';
15
+ value?: number | string;
16
+ numeric?: boolean;
17
+ input?: HTMLElement;
18
+ }
19
+
20
+ interface instance {
21
+
22
+ }
23
+
24
+ export declare function Calendar(el: HTMLElement, options?: options): instance;
package/dist/index.js CHANGED
@@ -1,50 +1,791 @@
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
- const lemonade = require('lemonadejs');
11
- } else if (window.lemonade) {
12
- const lemonade = window.lemonade;
13
- }
14
- }
15
-
16
- // Load LemonadeJS
17
- if (typeof(jSuites) == 'undefined') {
18
- if (typeof(require) === 'function') {
19
- const jSuites = require('jsuites');
20
- } else if (window.jSuites) {
21
- const jSuites = window.jSuites;
22
- }
23
- }
24
-
25
- return function() {
26
- const 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
- const template = `<input type="text" @ready="self.create(this)" name="{{self.name}}" value="{{self.value}}" />`;
44
- } else {
45
- const 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('@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
+ }
50
791
  })));
package/dist/react.js ADDED
@@ -0,0 +1,31 @@
1
+ // @ts-nocheck
2
+ import React, { useRef, useEffect } from "react";
3
+ import Component from './index';
4
+
5
+ import "./style.css";
6
+ import "@lemonadejs/modal/dist/style.css"
7
+
8
+ // @ts-ignore
9
+ const Calendar = React.forwardRef((props, mainReference) => {
10
+ // Dom element
11
+ const Ref = useRef(null);
12
+
13
+ // Get the properties for the spreadsheet
14
+ let options = { ...props };
15
+
16
+ useEffect(() => {
17
+ // @ts-ignore
18
+ if (! Ref.current.innerHTML) {
19
+ mainReference.current = Component(Ref.current, options);
20
+ }
21
+ }, []);
22
+
23
+ let prop = {
24
+ ref: Ref,
25
+ style: { height: '100%', width: '100%' }
26
+ };
27
+
28
+ return React.createElement("div", prop);
29
+ })
30
+
31
+ export default Calendar;
package/dist/style.css ADDED
@@ -0,0 +1,231 @@
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
+ font-weight: bold;
23
+ }
24
+
25
+ .lm-calendar-header {
26
+ display: flex;
27
+ flex-direction: column;
28
+ }
29
+
30
+ .lm-calendar-header > div:first-child {
31
+ display: flex;
32
+ align-items: center;
33
+ padding: 10px;
34
+ flex: 1;
35
+ user-select: none;
36
+ }
37
+
38
+ .lm-calendar-header .lm-calendar-labels {
39
+ display: flex;
40
+ flex: 1;
41
+ cursor: pointer;
42
+ padding-left: 5px;
43
+ }
44
+
45
+ .lm-calendar-header .lm-calendar-labels > button {
46
+ font-size: 1.2em;
47
+ border: 0;
48
+ padding: 2px;
49
+ margin: 2px;
50
+ background-color: #fff;
51
+ }
52
+
53
+ .lm-calendar-navigation button {
54
+ cursor: pointer;
55
+ padding: 5px;
56
+ border: 0;
57
+ border-radius: 24px;
58
+ }
59
+
60
+ .lm-calendar-navigation i:hover {
61
+ background-color: #eee;
62
+ }
63
+
64
+ .lm-calendar-weekdays {
65
+ display: none;
66
+ grid-template-columns: repeat(7, 1fr);
67
+ grid-gap: 2px;
68
+ padding: 0 10px 0 10px;
69
+ font-size: 0.8em;
70
+ }
71
+
72
+ .lm-calendar-container[data-view="days"] .lm-calendar-weekdays {
73
+ display: grid;
74
+ }
75
+
76
+ .lm-calendar-weekdays > div {
77
+ display: inline-block;
78
+ padding: 10px;
79
+ box-sizing: border-box;
80
+ text-align: center;
81
+ font-weight: bold;
82
+ }
83
+
84
+ .lm-calendar-content {
85
+ display: grid;
86
+ grid-template-columns: repeat(7, 1fr);
87
+ grid-gap: 0;
88
+ padding: 8px;
89
+ font-size: 0.8em;
90
+ outline: none;
91
+ }
92
+
93
+ .lm-calendar-content > div {
94
+ box-sizing: border-box;
95
+ text-align: center;
96
+ aspect-ratio: 1 / 1;
97
+ display: flex;
98
+ flex-direction: column;
99
+ justify-content: center;
100
+ align-items: center;
101
+ padding: 10px;
102
+ cursor: pointer;
103
+ border-radius: 100px;
104
+ background-origin: padding-box;
105
+ }
106
+
107
+ .lm-calendar-container[data-view="months"] .lm-calendar-content {
108
+ grid-template-columns: repeat(4, 1fr);
109
+ }
110
+ .lm-calendar-container[data-view="years"] .lm-calendar-content {
111
+ grid-template-columns: repeat(4, 1fr);
112
+ }
113
+
114
+ .lm-calendar-content > div[data-grey="true"] {
115
+ color: #ccc;
116
+ }
117
+
118
+ .lm-calendar-content > div[data-bold="true"] {
119
+ font-weight: bold;
120
+ }
121
+
122
+ .lm-calendar-content > div[data-event="true"]::before {
123
+ content: '';
124
+ position: absolute;
125
+ margin-top: 22px;
126
+ width: 3px;
127
+ height: 3px;
128
+ border-radius: 3px;
129
+ background-color: red;
130
+ }
131
+
132
+ .lm-calendar-content > div[data-selected="true"] {
133
+ font-weight: bold;
134
+ background-color: #eee;
135
+ }
136
+
137
+ .lm-calendar-content:focus > div[data-selected="true"] {
138
+ outline: 2px solid black;
139
+ outline-offset: -2px;
140
+ }
141
+
142
+ .lm-calendar-content > div:hover {
143
+ background-color: #eee;
144
+ }
145
+
146
+ .lm-calendar-content > div[data-range="true"] {
147
+ position: relative;
148
+ }
149
+
150
+ .lm-calendar-content > div[data-start="true"],
151
+ .lm-calendar-content > div[data-end="true"] {
152
+ background-color: #78D350;
153
+ }
154
+
155
+ .lm-calendar-content > div[data-range="true"]::before {
156
+ content: "";
157
+ position: absolute;
158
+ left: 0;
159
+ right: 0;
160
+ top: 8px;
161
+ bottom: 8px;
162
+ background-color: #78D35050;
163
+ }
164
+
165
+ .lm-calendar-content > div[data-start="true"]::before {
166
+ left: 32px;
167
+ }
168
+
169
+ .lm-calendar-content > div[data-end="true"]::before {
170
+ right: 32px;
171
+ }
172
+
173
+ .lm-calendar-footer > div {
174
+ flex: 1;
175
+ }
176
+
177
+ .lm-calendar-footer {
178
+ display: flex;
179
+ margin: 0 10px 0 10px;
180
+ padding: 8px 0 8px 0;
181
+ line-height: 34px;
182
+ border-top: 1px solid #eee;
183
+ }
184
+
185
+ .lm-calendar-footer select {
186
+ border: 0;
187
+ background-color: transparent;
188
+ padding: 6px;
189
+ -moz-appearance: none;
190
+ -webkit-appearance: none;
191
+ margin: 2px;
192
+ border-radius: 32px;
193
+ font-size: 1.1em;
194
+ }
195
+
196
+ .lm-calendar-footer select:focus {
197
+ background-color: #eee;
198
+ }
199
+
200
+ .lm-calendar-footer select:hover {
201
+ background-color: #eee;
202
+ }
203
+
204
+ .lm-calendar-footer input {
205
+ border: transparent;
206
+ padding: 8px;
207
+ background-color: #eee;
208
+ width: 100%;
209
+ cursor: pointer;
210
+ }
211
+
212
+ .lm-calendar-input {
213
+ padding-right: 24px !important;
214
+ box-sizing: border-box;
215
+ 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;
216
+ }
217
+
218
+ .ripple {
219
+ background-position: center;
220
+ transition: background 0.8s;
221
+ }
222
+
223
+ .ripple:hover {
224
+ background: #E0E0E0 radial-gradient(circle, transparent 1%, #E0E0E0 1%) center/15000%;
225
+ }
226
+
227
+ .ripple:active {
228
+ background-color: #E0E0E0;
229
+ background-size: 100%;
230
+ transition: background 0s;
231
+ }
package/dist/vue.js ADDED
@@ -0,0 +1,32 @@
1
+ import { h, getCurrentInstance } from 'vue';
2
+ import component from "./index";
3
+
4
+ import "./style.css";
5
+ import "@lemonadejs/modal/dist/style.css"
6
+
7
+ export const Calendar = {
8
+ inheritAttrs: false,
9
+ mounted() {
10
+ const { attrs } = getCurrentInstance();
11
+
12
+ let options = {
13
+ ...attrs
14
+ };
15
+
16
+ this.el = this.$refs.container;
17
+
18
+ this.current = component(this.$refs.container, options);
19
+ },
20
+ setup() {
21
+ let containerProps = {
22
+ ref: 'container',
23
+ style: {
24
+ width: '100%',
25
+ height: '100%',
26
+ }
27
+ };
28
+ return () => h('div', containerProps);
29
+ }
30
+ }
31
+
32
+ export default Calendar;
package/package.json CHANGED
@@ -1,22 +1,23 @@
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": "^3.0.3",
18
- "jsuites": "^4.17.7"
19
- },
20
- "main": "dist/index.js",
21
- "version": "2.0.0"
22
- }
1
+ {
2
+ "name": "@lemonadejs/calendar",
3
+ "title": "LemonadeJS calendar",
4
+ "description": "LemonadeJS reactive JavaScript calendar plugin",
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": "^4.0.1",
18
+ "@lemonadejs/modal": "^2.3.3"
19
+ },
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "version": "3.0.3"
23
+ }