@event-calendar/core 1.1.1 → 1.2.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.
@@ -1,7 +1,7 @@
1
1
  <script>
2
2
  import {getContext} from 'svelte';
3
3
  import {is_function} from 'svelte/internal';
4
- import {debounce, toISOString, toLocalDate, toViewWithLocalDates} from '@event-calendar/common';
4
+ import {debounce, toISOString, toLocalDate, toViewWithLocalDates} from './lib.js';
5
5
 
6
6
  let {datesSet, _auxiliary, _activeRange, _queue, _view} = getContext('state');
7
7
 
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  import {getContext} from 'svelte';
3
- import {createDate, cloneDate, subtractDay, addDuration, subtractDuration, setMidnight} from '@event-calendar/common';
3
+ import {createDate, cloneDate, subtractDay, addDuration, subtractDuration, setMidnight} from './lib.js';
4
4
 
5
5
  export let buttons;
6
6
 
@@ -16,7 +16,7 @@
16
16
  getPayload,
17
17
  flushDebounce,
18
18
  hasYScroll
19
- } from '@event-calendar/common';
19
+ } from './lib.js';
20
20
 
21
21
  export let plugins = [];
22
22
  export let options = {};
package/src/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  import Calendar from './Calendar.svelte';
2
2
 
3
- export {Calendar as default};
3
+ export {Calendar as default};
4
+ export * from './lib.js';
@@ -0,0 +1,43 @@
1
+ import {isObject} from './utils.js';
2
+
3
+ export function setContent(node, content) {
4
+ let actions = {
5
+ update(content) {
6
+ while (node.firstChild) {
7
+ node.removeChild(node.lastChild);
8
+ }
9
+ if (!isObject(content)) {
10
+ node.innerText = content;
11
+ } else if (content.domNodes) {
12
+ for (let child of content.domNodes) {
13
+ node.appendChild(child);
14
+ }
15
+ } else if (content.html) {
16
+ node.innerHTML = content.html;
17
+ }
18
+ }
19
+ };
20
+ actions.update(content);
21
+
22
+ return actions;
23
+ }
24
+
25
+ /** Dispatch event occurred outside of node */
26
+ export function outsideEvent(node, type) {
27
+
28
+ const handlePointerDown = jsEvent => {
29
+ if (node && !node.contains(jsEvent.target)) {
30
+ node.dispatchEvent(
31
+ new CustomEvent(type + 'outside', {detail: {jsEvent}})
32
+ );
33
+ }
34
+ };
35
+
36
+ document.addEventListener(type, handlePointerDown, true);
37
+
38
+ return {
39
+ destroy() {
40
+ document.removeEventListener(type, handlePointerDown, true);
41
+ }
42
+ };
43
+ }
@@ -0,0 +1,219 @@
1
+ export const DAY_IN_SECONDS = 86400;
2
+
3
+ export function createDate(input = undefined) {
4
+ if (input !== undefined) {
5
+ return input instanceof Date ? _fromLocalDate(input) : _fromISOString(input);
6
+ }
7
+
8
+ return _fromLocalDate(new Date());
9
+ }
10
+
11
+ export function createDuration(input) {
12
+ if (typeof input === 'number') {
13
+ input = {seconds: input};
14
+ } else if (typeof input === 'string') {
15
+ // Expected format hh[:mm[:ss]]
16
+ let seconds = 0, exp = 2;
17
+ for (let part of input.split(':', 3)) {
18
+ seconds += parseInt(part, 10) * Math.pow(60, exp--);
19
+ }
20
+ input = {seconds};
21
+ } else if (input instanceof Date) {
22
+ input = {hours: input.getUTCHours(), minutes: input.getUTCMinutes(), seconds: input.getUTCSeconds()};
23
+ }
24
+
25
+ let weeks = input.weeks || input.week || 0;
26
+
27
+ return {
28
+ years: input.years || input.year || 0,
29
+ months: input.months || input.month || 0,
30
+ days: weeks * 7 + (input.days || input.day || 0),
31
+ seconds: (input.hours || input.hour || 0) * 60 * 60 +
32
+ (input.minutes || input.minute || 0) * 60 +
33
+ (input.seconds || input.second || 0),
34
+ inWeeks: !!weeks
35
+ };
36
+ }
37
+
38
+ export function cloneDate(date) {
39
+ return new Date(date.getTime());
40
+ }
41
+
42
+ export function addDuration(date, duration, x = 1) {
43
+ date.setUTCFullYear(date.getUTCFullYear() + x * duration.years);
44
+ let month = date.getUTCMonth() + x * duration.months;
45
+ date.setUTCMonth(month);
46
+ month %= 12;
47
+ if (month < 0) {
48
+ month += 12;
49
+ }
50
+ while (date.getUTCMonth() !== month) {
51
+ subtractDay(date);
52
+ }
53
+ date.setUTCDate(date.getUTCDate() + x * duration.days);
54
+ date.setUTCSeconds(date.getUTCSeconds() + x * duration.seconds);
55
+
56
+ return date;
57
+ }
58
+
59
+ export function subtractDuration(date, duration, x = 1) {
60
+ return addDuration(date, duration, -x);
61
+ }
62
+
63
+ export function addDay(date, x = 1) {
64
+ date.setUTCDate(date.getUTCDate() + x);
65
+
66
+ return date;
67
+ }
68
+
69
+ export function subtractDay(date, x = 1) {
70
+ return addDay(date, -x);
71
+ }
72
+
73
+ export function setMidnight(date) {
74
+ date.setUTCHours(0, 0, 0, 0);
75
+
76
+ return date;
77
+ }
78
+
79
+ export function toLocalDate(date) {
80
+ return new Date(
81
+ date.getUTCFullYear(),
82
+ date.getUTCMonth(),
83
+ date.getUTCDate(),
84
+ date.getUTCHours(),
85
+ date.getUTCMinutes(),
86
+ date.getUTCSeconds()
87
+ );
88
+ }
89
+
90
+ export function toISOString(date) {
91
+ return date.toISOString().substring(0, 19);
92
+ }
93
+
94
+ export function formatRange(start, end, intl) {
95
+ if (start.getFullYear() !== end.getFullYear()) {
96
+ return intl.format(start) + ' - ' + intl.format(end);
97
+ }
98
+
99
+ let diff = [];
100
+ if (start.getMonth() !== end.getMonth()) {
101
+ diff.push('month');
102
+ }
103
+ if (start.getDate() !== end.getDate()) {
104
+ diff.push('day');
105
+ }
106
+
107
+ if (!diff.length) {
108
+ return intl.format(start);
109
+ }
110
+
111
+ let opts1 = intl.resolvedOptions();
112
+ let opts2 = {};
113
+ for (let key of diff) {
114
+ opts2[key] = opts1[key];
115
+ }
116
+ let intl2 = new Intl.DateTimeFormat(opts1.locale, opts2);
117
+
118
+ let full1 = intl.format(start);
119
+ let full2 = intl.format(end);
120
+ let part1 = intl2.format(start);
121
+ let part2 = intl2.format(end);
122
+
123
+ let common = _commonChunks(full1, part1, full2, part2);
124
+ if (common) {
125
+ return common.head + part1 + ' - ' + part2 + common.tail;
126
+ }
127
+
128
+ return full1 + ' - ' + full2;
129
+ }
130
+
131
+ export function datesEqual(date1, ...dates2) {
132
+ return dates2.every(date2 => date1.getTime() === date2.getTime());
133
+ }
134
+
135
+ export function nextClosestDay(date, day) {
136
+ let diff = day - date.getUTCDay();
137
+ date.setUTCDate(date.getUTCDate() + (diff >= 0 ? diff : diff + 7));
138
+ return date;
139
+ }
140
+
141
+ export function prevClosestDay(date, day) {
142
+ let diff = day - date.getUTCDay();
143
+ date.setUTCDate(date.getUTCDate() + (diff <= 0 ? diff : diff - 7));
144
+ return date;
145
+ }
146
+
147
+ /**
148
+ * Check whether given date is string which contains no time part
149
+ */
150
+ export function noTimePart(date) {
151
+ return typeof date === 'string' && date.length <= 10;
152
+ }
153
+
154
+ /**
155
+ * Private functions
156
+ */
157
+
158
+ function _fromLocalDate(date) {
159
+ return new Date(Date.UTC(
160
+ date.getFullYear(),
161
+ date.getMonth(),
162
+ date.getDate(),
163
+ date.getHours(),
164
+ date.getMinutes(),
165
+ date.getSeconds()
166
+ ));
167
+ }
168
+
169
+ function _fromISOString(str) {
170
+ const parts = str.match(/\d+/g);
171
+ return new Date(Date.UTC(
172
+ Number(parts[0]),
173
+ Number(parts[1]) - 1,
174
+ Number(parts[2]),
175
+ Number(parts[3] || 0),
176
+ Number(parts[4] || 0),
177
+ Number(parts[5] || 0)
178
+ ));
179
+ }
180
+
181
+ function _commonChunks(str1, substr1, str2, substr2) {
182
+ let i = 0;
183
+ while (i < str1.length) {
184
+ let res1;
185
+ [i, res1] = _cut(str1, substr1, i);
186
+ if (!res1) {
187
+ break;
188
+ }
189
+
190
+ let j = 0;
191
+ while (j < str2.length) {
192
+ let res2;
193
+ [j, res2] = _cut(str2, substr2, j);
194
+ if (!res2) {
195
+ break;
196
+ }
197
+
198
+ if (res1.head === res2.head && res1.tail === res2.tail) {
199
+ return res1;
200
+ }
201
+ }
202
+ }
203
+
204
+ return null
205
+ }
206
+
207
+ function _cut(str, substr, from) {
208
+ let start = str.indexOf(substr, from);
209
+ if (start >= 0) {
210
+ let end = start + substr.length;
211
+
212
+ return [end, {
213
+ head: str.substr(0, start),
214
+ tail: str.substr(end)
215
+ }];
216
+ }
217
+
218
+ return [-1, null];
219
+ }
@@ -0,0 +1,10 @@
1
+ import {run_all} from 'svelte/internal';
2
+
3
+ export function debounce(fn, handle, queueStore) {
4
+ queueStore.update(queue => queue.set(handle, fn));
5
+ }
6
+
7
+ export function flushDebounce(queue) {
8
+ run_all(queue);
9
+ queue.clear();
10
+ }
package/src/lib/dom.js ADDED
@@ -0,0 +1,53 @@
1
+ import {symbol} from './utils.js';
2
+
3
+ export function createElement(tag, className, html, text) {
4
+ let el = document.createElement(tag);
5
+ el.className = className;
6
+ if (html) {
7
+ el.innerHTML = html;
8
+ } else if (text) {
9
+ el.innerText = text;
10
+ }
11
+ return el;
12
+ }
13
+
14
+ export function hasYScroll(el) {
15
+ return el.scrollHeight > el.clientHeight;
16
+ }
17
+
18
+ export function rect(el) {
19
+ return el.getBoundingClientRect();
20
+ }
21
+
22
+ export function ancestor(el, up) {
23
+ while (up--) {
24
+ el = el.parentElement;
25
+ }
26
+ return el;
27
+ }
28
+
29
+ export function height(el) {
30
+ return rect(el).height;
31
+ }
32
+
33
+ let payloadProp = symbol();
34
+ export function setPayload(el, payload) {
35
+ el[payloadProp] = payload;
36
+ }
37
+
38
+ export function hasPayload(el) {
39
+ return !!el?.[payloadProp];
40
+ }
41
+
42
+ export function getPayload(el) {
43
+ return el[payloadProp];
44
+ }
45
+
46
+ export function getElementWithPayload(x, y) {
47
+ for (let el of document.elementsFromPoint(x, y)) {
48
+ if (hasPayload(el)) {
49
+ return el;
50
+ }
51
+ }
52
+ return null;
53
+ }
@@ -0,0 +1,224 @@
1
+ import {addDay, datesEqual, createDate, cloneDate, setMidnight, toLocalDate, noTimePart} from './date';
2
+ import {createElement} from './dom';
3
+ import {assign} from './utils';
4
+ import {toViewWithLocalDates} from './view';
5
+ import {is_function} from 'svelte/internal';
6
+
7
+ let eventId = 1;
8
+ export function createEvents(input) {
9
+ return input.map(event => ({
10
+ id: 'id' in event ? String(event.id) : `{generated-${eventId++}}`,
11
+ resourceIds: Array.isArray(event.resourceIds)
12
+ ? event.resourceIds.map(String)
13
+ : ('resourceId' in event ? [String(event.resourceId)] : []),
14
+ allDay: event.allDay ?? (noTimePart(event.start) && noTimePart(event.end)),
15
+ start: createDate(event.start),
16
+ end: createDate(event.end),
17
+ title: event.title || '',
18
+ titleHTML: event.titleHTML || '',
19
+ editable: event.editable,
20
+ startEditable: event.startEditable,
21
+ durationEditable: event.durationEditable,
22
+ display: event.display || 'auto',
23
+ extendedProps: event.extendedProps || {},
24
+ backgroundColor: event.backgroundColor || event.color,
25
+ textColor: event.textColor
26
+ }));
27
+ }
28
+
29
+ export function createEventSources(input) {
30
+ return input.map(source => ({
31
+ events: source.events,
32
+ url: (source.url && source.url.trimEnd('&')) || '',
33
+ method: (source.method && source.method.toUpperCase()) || 'GET',
34
+ extraParams: source.extraParams || {}
35
+ }));
36
+ }
37
+
38
+ export function createEventChunk(event, start, end) {
39
+ return {
40
+ start: event.start > start ? event.start : start,
41
+ end: event.end < end ? event.end : end,
42
+ event
43
+ };
44
+ }
45
+
46
+ export function sortEventChunks(chunks) {
47
+ // Sort by start date
48
+ chunks.sort((a, b) => {
49
+ if (a.start < b.start) {
50
+ return -1;
51
+ }
52
+ if (a.start > b.start) {
53
+ return 1;
54
+ }
55
+ return 0;
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Prepare event chunks for month view and all-day slot in week view
61
+ */
62
+ export function prepareEventChunks(chunks, hiddenDays) {
63
+ let longChunks = {};
64
+
65
+ if (chunks.length) {
66
+ sortEventChunks(chunks);
67
+
68
+ let prevChunk;
69
+ for (let chunk of chunks) {
70
+ let dates = [];
71
+ let date = setMidnight(cloneDate(chunk.start));
72
+ while (chunk.end > date) {
73
+ if (!hiddenDays.includes(date.getUTCDay())) {
74
+ dates.push(cloneDate(date));
75
+ if (dates.length > 1) {
76
+ let key = date.getTime();
77
+ if (longChunks[key]) {
78
+ longChunks[key].chunks.push(chunk);
79
+ } else {
80
+ longChunks[key] = {
81
+ sorted: false,
82
+ chunks: [chunk]
83
+ };
84
+ }
85
+ }
86
+ }
87
+ addDay(date);
88
+ }
89
+ if (dates.length) {
90
+ chunk.date = dates[0];
91
+ chunk.days = dates.length;
92
+ chunk.dates = dates;
93
+ if (chunk.start < dates[0]) {
94
+ chunk.start = dates[0];
95
+ }
96
+ if (setMidnight(cloneDate(chunk.end)) > dates[dates.length - 1]) {
97
+ chunk.end = dates[dates.length - 1];
98
+ }
99
+ } else {
100
+ chunk.date = setMidnight(cloneDate(chunk.start));
101
+ chunk.days = 1;
102
+ chunk.dates = [chunk.date];
103
+ }
104
+
105
+ if (prevChunk && datesEqual(prevChunk.date, chunk.date)) {
106
+ chunk.prev = prevChunk;
107
+ }
108
+ prevChunk = chunk;
109
+ }
110
+ }
111
+
112
+ return longChunks;
113
+ }
114
+
115
+ export function repositionEvent(chunk, longChunks, height) {
116
+ chunk.top = 0;
117
+ if (chunk.prev) {
118
+ chunk.top = chunk.prev.bottom + 1;
119
+ }
120
+ chunk.bottom = chunk.top + height;
121
+ let margin = 1;
122
+ let key = chunk.date.getTime();
123
+ if (longChunks[key]) {
124
+ if (!longChunks[key].sorted) {
125
+ longChunks[key].chunks.sort((a, b) => a.top - b.top);
126
+ longChunks[key].sorted = true;
127
+ }
128
+ for (let longChunk of longChunks[key].chunks) {
129
+ if (chunk.top < longChunk.bottom && chunk.bottom > longChunk.top) {
130
+ let offset = longChunk.bottom - chunk.top + 1;
131
+ margin += offset;
132
+ chunk.top += offset;
133
+ chunk.bottom += offset;
134
+ }
135
+ }
136
+ }
137
+
138
+ return margin;
139
+ }
140
+
141
+ export function createEventContent(chunk, displayEventEnd, eventContent, theme, _intlEventTime, _view) {
142
+ let timeText = _intlEventTime.format(chunk.start), content;
143
+ if (displayEventEnd && chunk.event.display !== 'pointer') {
144
+ timeText += ` - ${_intlEventTime.format(chunk.end)}`;
145
+ }
146
+ if (eventContent) {
147
+ content = is_function(eventContent)
148
+ ? eventContent({
149
+ event: toEventWithLocalDates(chunk.event),
150
+ timeText,
151
+ view: toViewWithLocalDates(_view)
152
+ })
153
+ : eventContent;
154
+ } else {
155
+ switch (chunk.event.display) {
156
+ case 'background':
157
+ content = '';
158
+ break;
159
+ case 'pointer':
160
+ content = {
161
+ domNodes: [createElement('div', theme.eventTime, null, timeText)]
162
+ };
163
+ break;
164
+ default:
165
+ content = {
166
+ domNodes: [
167
+ createElement('div', theme.eventTime, null, timeText),
168
+ createElement('div', theme.eventTitle, chunk.event.titleHTML, chunk.event.title)
169
+ ]
170
+ };
171
+ }
172
+ }
173
+
174
+ return [timeText, content];
175
+ }
176
+
177
+ export function toEventWithLocalDates(event) {
178
+ return _cloneEvent(event, toLocalDate);
179
+ }
180
+
181
+ export function cloneEvent(event) {
182
+ return _cloneEvent(event, cloneDate);
183
+ }
184
+
185
+ function _cloneEvent(event, dateFn) {
186
+ event = assign({}, event);
187
+ event.start = dateFn(event.start);
188
+ event.end = dateFn(event.end);
189
+
190
+ return event;
191
+ }
192
+
193
+ /**
194
+ * Check whether the event intersects with the given date range and resource
195
+ * @param event
196
+ * @param start
197
+ * @param end
198
+ * @param [resource]
199
+ * @param [timeMode] Zero-length events should be allowed (@see https://github.com/vkurko/calendar/issues/50), except in time mode
200
+ * @return boolean
201
+ */
202
+ export function eventIntersects(event, start, end, resource, timeMode) {
203
+ return (
204
+ event.start < end && event.end > start || !timeMode && datesEqual(event.start, event.end, start)
205
+ ) && (
206
+ resource === undefined || event.resourceIds.includes(resource.id)
207
+ );
208
+ }
209
+
210
+ export function helperEvent(display) {
211
+ return display === 'preview' || display === 'ghost' || display === 'pointer';
212
+ }
213
+
214
+ export function bgEvent(display) {
215
+ return display === 'background';
216
+ }
217
+
218
+ export function previewEvent(display) {
219
+ return display === 'preview';
220
+ }
221
+
222
+ export function ghostEvent(display) {
223
+ return display === 'ghost';
224
+ }
@@ -0,0 +1,54 @@
1
+ import {derived, writable, get} from 'svelte/store';
2
+ import {is_function} from 'svelte/internal';
3
+ import {toLocalDate, formatRange} from './date';
4
+
5
+ export function writable2(value, parser, start) {
6
+ return {
7
+ ...writable(parser ? parser(value) : value, start),
8
+ parse: parser
9
+ };
10
+ }
11
+
12
+ export function derived2(stores, fn, initValue) {
13
+ let storeValue = initValue;
14
+ let hasSubscribers = false;
15
+ let auto = fn.length < 2;
16
+ let fn2 = (_, set) => {
17
+ hasSubscribers = true;
18
+ if (auto) {
19
+ storeValue = fn(_, set);
20
+ set(storeValue);
21
+ } else {
22
+ fn(_, value => {storeValue = value; set(value);});
23
+ }
24
+ return () => {hasSubscribers = false;};
25
+ };
26
+ let store = derived(stores, fn2, storeValue);
27
+ return {
28
+ ...store,
29
+ get: () => hasSubscribers ? storeValue : get(store)
30
+ };
31
+ }
32
+
33
+ export function intl(locale, format) {
34
+ return derived([locale, format], ([$locale, $format]) => {
35
+ let intl = is_function($format)
36
+ ? {format: $format}
37
+ : new Intl.DateTimeFormat($locale, $format);
38
+ return {
39
+ format: date => intl.format(toLocalDate(date))
40
+ };
41
+ });
42
+ }
43
+
44
+ export function intlRange(locale, format) {
45
+ return derived([locale, format], ([$locale, $format]) => {
46
+ if (is_function($format)) {
47
+ return {format: (start, end) => $format(toLocalDate(start), toLocalDate(end))};
48
+ }
49
+ let intl = new Intl.DateTimeFormat($locale, $format);
50
+ return {
51
+ format: (start, end) => formatRange(toLocalDate(start), toLocalDate(end), intl)
52
+ };
53
+ });
54
+ }
@@ -0,0 +1,23 @@
1
+ export function assign(...args) {
2
+ return Object.assign(...args);
3
+ }
4
+
5
+ export function floor(value) {
6
+ return Math.floor(value);
7
+ }
8
+
9
+ export function min(...args) {
10
+ return Math.min(...args);
11
+ }
12
+
13
+ export function max(...args) {
14
+ return Math.max(...args);
15
+ }
16
+
17
+ export function isObject(test) {
18
+ return typeof test === 'object' && test !== null;
19
+ }
20
+
21
+ export function symbol() {
22
+ return Symbol('ec');
23
+ }
@@ -0,0 +1,24 @@
1
+ import {assign} from './utils';
2
+ import {toLocalDate} from './date';
3
+
4
+ export function createView(view, _viewTitle, _currentRange, _activeRange) {
5
+ return {
6
+ type: view,
7
+ title: _viewTitle,
8
+ currentStart: _currentRange.start,
9
+ currentEnd: _currentRange.end,
10
+ activeStart: _activeRange.start,
11
+ activeEnd: _activeRange.end,
12
+ calendar: undefined
13
+ };
14
+ }
15
+
16
+ export function toViewWithLocalDates(view) {
17
+ view = assign({}, view);
18
+ view.currentStart = toLocalDate(view.currentStart);
19
+ view.currentEnd = toLocalDate(view.currentEnd);
20
+ view.activeStart = toLocalDate(view.activeStart);
21
+ view.activeEnd = toLocalDate(view.activeEnd);
22
+
23
+ return view;
24
+ }
package/src/lib.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from './lib/actions';
2
+ export * from './lib/date';
3
+ export * from './lib/debounce';
4
+ export * from './lib/dom';
5
+ export * from './lib/events';
6
+ export * from './lib/stores';
7
+ export * from './lib/utils';
8
+ export * from './lib/view';