@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.
@@ -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
+ }