@event-calendar/core 0.7.0 → 0.8.2
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 +12 -5
- package/index.css +1 -1
- package/index.js +70 -49
- package/package.json +4 -3
- package/src/Buttons.svelte +41 -0
- package/src/Calendar.svelte +91 -0
- package/src/Toolbar.svelte +34 -0
- package/src/index.js +3 -0
- package/src/index.scss +507 -0
- package/src/storage/options.js +150 -0
- package/src/storage/state.js +114 -0
- package/src/storage/stores.js +206 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {writable} from 'svelte/store';
|
|
2
|
+
import {is_function, tick, noop, identity} from 'svelte/internal';
|
|
3
|
+
import {createOptions, createParsers} from './options';
|
|
4
|
+
import {
|
|
5
|
+
activeRange,
|
|
6
|
+
currentRange,
|
|
7
|
+
events,
|
|
8
|
+
viewDates,
|
|
9
|
+
viewTitle,
|
|
10
|
+
view as view2 // hack to avoid a runtime error in SvelteKit dev mode (ReferenceError: view is not defined)
|
|
11
|
+
} from './stores';
|
|
12
|
+
import {writable2, intl, intlRange} from '@event-calendar/common';
|
|
13
|
+
import {assign} from '@event-calendar/common';
|
|
14
|
+
|
|
15
|
+
export default class {
|
|
16
|
+
constructor(plugins, input) {
|
|
17
|
+
plugins = plugins || [];
|
|
18
|
+
|
|
19
|
+
// Create options
|
|
20
|
+
let options = createOptions(plugins);
|
|
21
|
+
let parsers = createParsers(options, plugins);
|
|
22
|
+
|
|
23
|
+
// Create stores for options
|
|
24
|
+
for (let [option, value] of Object.entries(options)) {
|
|
25
|
+
this[option] = writable2(value, parsers[option]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Private stores
|
|
29
|
+
this._currentRange = currentRange(this);
|
|
30
|
+
this._activeRange = activeRange(this);
|
|
31
|
+
this._fetchedRange = writable({start: undefined, end: undefined});
|
|
32
|
+
this._events = events(this);
|
|
33
|
+
this._intlEventTime = intl(this.locale, this.eventTimeFormat);
|
|
34
|
+
this._intlSlotLabel = intl(this.locale, this.slotLabelFormat);
|
|
35
|
+
this._intlDayHeader = intl(this.locale, this.dayHeaderFormat);
|
|
36
|
+
this._titleIntlRange = intlRange(this.locale, this.titleFormat);
|
|
37
|
+
this._scrollable = writable(false);
|
|
38
|
+
this._viewTitle = viewTitle(this);
|
|
39
|
+
this._viewDates = viewDates(this);
|
|
40
|
+
this._view = view2(this);
|
|
41
|
+
this._viewComponent = writable(undefined);
|
|
42
|
+
// Interaction
|
|
43
|
+
this._interaction = writable({});
|
|
44
|
+
this._interactionEvents = writable([null, null]); // drag, pointer
|
|
45
|
+
this._draggable = writable(noop);
|
|
46
|
+
this._classes = writable(identity);
|
|
47
|
+
this._scroll = writable(undefined);
|
|
48
|
+
|
|
49
|
+
// Let plugins create their private stores
|
|
50
|
+
for (let plugin of plugins) {
|
|
51
|
+
if ('createStores' in plugin) {
|
|
52
|
+
plugin.createStores(this);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (input.view) {
|
|
57
|
+
// Set initial view based on input
|
|
58
|
+
this.view.set(input.view);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Set options for each view
|
|
62
|
+
let commonOpts = assign({}, options, input);
|
|
63
|
+
parseOpts(commonOpts, this);
|
|
64
|
+
let views = new Set([...Object.keys(options.views), ...Object.keys(input.views || {})]);
|
|
65
|
+
for (let view of views) {
|
|
66
|
+
let viewOpts = assign({}, options.views[view] || {}, input.views && input.views[view] || {});
|
|
67
|
+
parseOpts(viewOpts, this);
|
|
68
|
+
let opts = assign({}, commonOpts, viewOpts);
|
|
69
|
+
// Change view component when view changes
|
|
70
|
+
this.view.subscribe(newView => {
|
|
71
|
+
if (newView === view) {
|
|
72
|
+
this._viewComponent.set(opts.component);
|
|
73
|
+
if (is_function(opts.viewDidMount)) {
|
|
74
|
+
tick().then(() => opts.viewDidMount(this._view.get()));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
for (let key of Object.keys(opts)) {
|
|
79
|
+
if (this.hasOwnProperty(key) && key[0] !== '_') {
|
|
80
|
+
let {set, _set, ...rest} = this[key];
|
|
81
|
+
|
|
82
|
+
if (!_set) {
|
|
83
|
+
// Original set
|
|
84
|
+
_set = set;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this[key] = {
|
|
88
|
+
// Set value in all views
|
|
89
|
+
set: value => {opts[key] = value; set(value);},
|
|
90
|
+
_set,
|
|
91
|
+
...rest
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Change value when view changes
|
|
95
|
+
this.view.subscribe(newView => {
|
|
96
|
+
if (newView === view) {
|
|
97
|
+
_set(opts[key]);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseOpts(opts, state) {
|
|
107
|
+
for (let key of Object.keys(opts)) {
|
|
108
|
+
if (state.hasOwnProperty(key) && key[0] !== '_') {
|
|
109
|
+
if (state[key].parse) {
|
|
110
|
+
opts[key] = state[key].parse(opts[key]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {derived, writable} from 'svelte/store';
|
|
2
|
+
import {is_function, noop, tick} from 'svelte/internal';
|
|
3
|
+
import {
|
|
4
|
+
DAY_IN_SECONDS,
|
|
5
|
+
cloneDate,
|
|
6
|
+
addDuration,
|
|
7
|
+
addDay,
|
|
8
|
+
subtractDay,
|
|
9
|
+
toISOString,
|
|
10
|
+
nextClosestDay,
|
|
11
|
+
prevClosestDay,
|
|
12
|
+
setMidnight,
|
|
13
|
+
toLocalDate
|
|
14
|
+
} from '@event-calendar/common';
|
|
15
|
+
import {derived2} from '@event-calendar/common';
|
|
16
|
+
import {createEvents} from '@event-calendar/common';
|
|
17
|
+
import {createView} from '@event-calendar/common';
|
|
18
|
+
import {assign} from '@event-calendar/common';
|
|
19
|
+
|
|
20
|
+
export function activeRange(state) {
|
|
21
|
+
let _activeRange = derived(
|
|
22
|
+
[state._currentRange, state.firstDay, state.monthMode, state.slotMinTime, state.slotMaxTime],
|
|
23
|
+
([$_currentRange, $firstDay, $monthMode, $slotMinTime, $slotMaxTime]) => {
|
|
24
|
+
let start = cloneDate($_currentRange.start);
|
|
25
|
+
let end = cloneDate($_currentRange.end);
|
|
26
|
+
|
|
27
|
+
if ($monthMode) {
|
|
28
|
+
// First day of week
|
|
29
|
+
prevClosestDay(start, $firstDay);
|
|
30
|
+
nextClosestDay(end, $firstDay);
|
|
31
|
+
} else if ($slotMaxTime.days || $slotMaxTime.seconds > DAY_IN_SECONDS) {
|
|
32
|
+
addDuration(subtractDay(end), $slotMaxTime);
|
|
33
|
+
let start2 = subtractDay(cloneDate(end));
|
|
34
|
+
if (start2 < start) {
|
|
35
|
+
start = start2;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {start, end};
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
let debounce = 0;
|
|
44
|
+
derived([_activeRange, state.datesSet], ([$_activeRange, $datesSet]) => {
|
|
45
|
+
if ($datesSet && !debounce) {
|
|
46
|
+
++debounce;
|
|
47
|
+
tick().then(() => {
|
|
48
|
+
--debounce;
|
|
49
|
+
$datesSet({
|
|
50
|
+
start: toLocalDate($_activeRange.start),
|
|
51
|
+
end: toLocalDate($_activeRange.end),
|
|
52
|
+
startStr: toISOString($_activeRange.start),
|
|
53
|
+
endStr: toISOString($_activeRange.end)
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}).subscribe(noop);
|
|
58
|
+
|
|
59
|
+
return _activeRange;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function currentRange(state) {
|
|
63
|
+
return derived(
|
|
64
|
+
[state.date, state.duration, state.monthMode, state.firstDay],
|
|
65
|
+
([$date, $duration, $monthMode, $firstDay]) => {
|
|
66
|
+
let start = cloneDate($date), end;
|
|
67
|
+
if ($monthMode) {
|
|
68
|
+
start.setDate(1);
|
|
69
|
+
} else if ($duration.inWeeks) {
|
|
70
|
+
// First day of week
|
|
71
|
+
prevClosestDay(start, $firstDay);
|
|
72
|
+
}
|
|
73
|
+
end = addDuration(cloneDate(start), $duration);
|
|
74
|
+
|
|
75
|
+
return {start, end};
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function viewDates(state) {
|
|
81
|
+
return derived2([state._activeRange, state.hiddenDays], ([$_activeRange, $hiddenDays]) => {
|
|
82
|
+
let dates = [];
|
|
83
|
+
let date = setMidnight(cloneDate($_activeRange.start));
|
|
84
|
+
let end = setMidnight(cloneDate($_activeRange.end));
|
|
85
|
+
while (date < end) {
|
|
86
|
+
if (!$hiddenDays.includes(date.getUTCDay())) {
|
|
87
|
+
dates.push(cloneDate(date));
|
|
88
|
+
}
|
|
89
|
+
addDay(date);
|
|
90
|
+
}
|
|
91
|
+
if (!dates.length && $hiddenDays.length && $hiddenDays.length < 7) {
|
|
92
|
+
// Try to move the date
|
|
93
|
+
state.date.update(date => {
|
|
94
|
+
while ($hiddenDays.includes(date.getUTCDay())) {
|
|
95
|
+
addDay(date);
|
|
96
|
+
}
|
|
97
|
+
return date;
|
|
98
|
+
});
|
|
99
|
+
dates = state._viewDates.get();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return dates;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function viewTitle(state) {
|
|
107
|
+
return derived(
|
|
108
|
+
[state.date, state._activeRange, state._titleIntlRange, state.monthMode],
|
|
109
|
+
([$date, $_activeRange, $_titleIntlRange, $monthMode]) => {
|
|
110
|
+
return $monthMode
|
|
111
|
+
? $_titleIntlRange.format($date, $date)
|
|
112
|
+
: $_titleIntlRange.format($_activeRange.start, subtractDay(cloneDate($_activeRange.end)));
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function view(state) {
|
|
118
|
+
return derived2([state.view, state._viewTitle, state._currentRange, state._activeRange], args => createView(...args));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function events(state) {
|
|
122
|
+
let _events = writable([]);
|
|
123
|
+
let abortController;
|
|
124
|
+
let fetching = 0;
|
|
125
|
+
derived(
|
|
126
|
+
[state.events, state.eventSources, state._activeRange, state._fetchedRange, state.lazyFetching, state.loading],
|
|
127
|
+
(values, set) => tick().then(() => {
|
|
128
|
+
let [$events, $eventSources, $_activeRange, $_fetchedRange, $lazyFetching, $loading] = values;
|
|
129
|
+
if (!$eventSources.length) {
|
|
130
|
+
set($events);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Do not fetch if new range is within the previous one
|
|
134
|
+
if (!$_fetchedRange.start || $_fetchedRange.start > $_activeRange.start || $_fetchedRange.end < $_activeRange.end || !$lazyFetching) {
|
|
135
|
+
if (abortController) {
|
|
136
|
+
// Abort previous request
|
|
137
|
+
abortController.abort();
|
|
138
|
+
}
|
|
139
|
+
// Create new abort controller
|
|
140
|
+
abortController = new AbortController();
|
|
141
|
+
// Call loading hook
|
|
142
|
+
if (is_function($loading) && !fetching) {
|
|
143
|
+
$loading(true);
|
|
144
|
+
}
|
|
145
|
+
let stopLoading = () => {
|
|
146
|
+
if (--fetching === 0 && is_function($loading)) {
|
|
147
|
+
$loading(false);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
let events = [];
|
|
151
|
+
// Prepare handlers
|
|
152
|
+
let failure = e => stopLoading();
|
|
153
|
+
let success = data => {
|
|
154
|
+
events = events.concat(createEvents(data));
|
|
155
|
+
set(events);
|
|
156
|
+
stopLoading();
|
|
157
|
+
};
|
|
158
|
+
// Prepare other stuff
|
|
159
|
+
let startStr = toISOString($_activeRange.start)
|
|
160
|
+
let endStr = toISOString($_activeRange.end);
|
|
161
|
+
// Loop over event sources
|
|
162
|
+
for (let source of $eventSources) {
|
|
163
|
+
if (is_function(source.events)) {
|
|
164
|
+
// Events as a function
|
|
165
|
+
let result = source.events({
|
|
166
|
+
start: toLocalDate($_activeRange.start),
|
|
167
|
+
end: toLocalDate($_activeRange.end),
|
|
168
|
+
startStr,
|
|
169
|
+
endStr
|
|
170
|
+
}, success, failure);
|
|
171
|
+
if (result !== undefined) {
|
|
172
|
+
Promise.resolve(result).then(success, failure);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
// Events as a JSON feed
|
|
176
|
+
// Prepare params
|
|
177
|
+
let params = is_function(source.extraParams) ? source.extraParams() : assign({}, source.extraParams);
|
|
178
|
+
params.start = startStr;
|
|
179
|
+
params.end = endStr;
|
|
180
|
+
params = new URLSearchParams(params);
|
|
181
|
+
// Prepare fetch
|
|
182
|
+
let url = source.url, headers = {}, body;
|
|
183
|
+
if (['GET', 'HEAD'].includes(source.method)) {
|
|
184
|
+
url += (url.includes('?') ? '&' : '?') + params;
|
|
185
|
+
} else {
|
|
186
|
+
headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
|
|
187
|
+
body = String(params); // Safari 10.1 doesn't convert to string automatically
|
|
188
|
+
}
|
|
189
|
+
// Do the fetch
|
|
190
|
+
fetch(url, {method: source.method, headers, body, signal: abortController.signal, credentials: 'same-origin'})
|
|
191
|
+
.then(response => response.json())
|
|
192
|
+
.then(success)
|
|
193
|
+
.catch(failure);
|
|
194
|
+
}
|
|
195
|
+
++fetching;
|
|
196
|
+
}
|
|
197
|
+
// Save current range for future requests
|
|
198
|
+
$_fetchedRange.start = $_activeRange.start;
|
|
199
|
+
$_fetchedRange.end = $_activeRange.end;
|
|
200
|
+
}
|
|
201
|
+
}),
|
|
202
|
+
[]
|
|
203
|
+
).subscribe(_events.set);
|
|
204
|
+
|
|
205
|
+
return _events;
|
|
206
|
+
}
|