@event-calendar/core 5.6.1 → 5.7.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.
- package/README.md +52 -2
- package/dist/index.css +1 -1
- package/dist/index.js +112 -32
- package/package.json +2 -2
- package/src/Calendar.svelte +3 -3
- package/src/lib/date.js +71 -27
- package/src/lib/events.js +3 -3
- package/src/lib/utils.js +4 -0
- package/src/plugins/time-grid/derived.js +8 -2
- package/src/storage/derived.js +18 -1
- package/src/storage/effects.js +66 -11
- package/src/storage/options.js +2 -3
- package/src/storage/state.svelte.js +8 -3
package/README.md
CHANGED
|
@@ -136,6 +136,7 @@ Inspired by [FullCalendar](https://fullcalendar.io/), it implements similar opti
|
|
|
136
136
|
- [slotWidth](#slotwidth)
|
|
137
137
|
- [snapDuration](#snapduration)
|
|
138
138
|
- [theme](#theme)
|
|
139
|
+
- [timeZone](#timezone)
|
|
139
140
|
- [titleFormat](#titleformat)
|
|
140
141
|
- [unselect](#unselect)
|
|
141
142
|
- [unselectAuto](#unselectauto)
|
|
@@ -260,8 +261,8 @@ This bundle contains a version of the calendar that includes all plugins and is
|
|
|
260
261
|
|
|
261
262
|
The first step is to include the following lines of code in the `<head>` section of your page:
|
|
262
263
|
```html
|
|
263
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@event-calendar/build@5.
|
|
264
|
-
<script src="https://cdn.jsdelivr.net/npm/@event-calendar/build@5.
|
|
264
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@event-calendar/build@5.7.0/dist/event-calendar.min.css">
|
|
265
|
+
<script src="https://cdn.jsdelivr.net/npm/@event-calendar/build@5.7.0/dist/event-calendar.min.js"></script>
|
|
265
266
|
```
|
|
266
267
|
|
|
267
268
|
<details>
|
|
@@ -1652,6 +1653,15 @@ Start date of the range the calendar needs events for
|
|
|
1652
1653
|
End date of the range the calendar needs events for
|
|
1653
1654
|
</td>
|
|
1654
1655
|
</tr>
|
|
1656
|
+
<tr>
|
|
1657
|
+
<td>
|
|
1658
|
+
|
|
1659
|
+
`timeZone`
|
|
1660
|
+
</td>
|
|
1661
|
+
<td>
|
|
1662
|
+
The value of the calendar's [timeZone](#timezone) option. Sent only when `timeZone` is not `'local'`
|
|
1663
|
+
</td>
|
|
1664
|
+
</tr>
|
|
1655
1665
|
</table>
|
|
1656
1666
|
</td>
|
|
1657
1667
|
</tr>
|
|
@@ -1721,6 +1731,13 @@ function(fetchInfo, successCallback, failureCallback) { }
|
|
|
1721
1731
|
</td>
|
|
1722
1732
|
<td>ISO8601 string representation of the end date</td>
|
|
1723
1733
|
</tr>
|
|
1734
|
+
<tr>
|
|
1735
|
+
<td>
|
|
1736
|
+
|
|
1737
|
+
`timeZone`
|
|
1738
|
+
</td>
|
|
1739
|
+
<td>The value of the calendar's [timeZone](#timezone) option</td>
|
|
1740
|
+
</tr>
|
|
1724
1741
|
</table>
|
|
1725
1742
|
|
|
1726
1743
|
The `successCallback` function must be called by the custom function with an array of [parsable](#parsing-event-from-a-plain-object) [Event](#event-object) objects.
|
|
@@ -2640,6 +2657,39 @@ function (theme) {
|
|
|
2640
2657
|
</tr>
|
|
2641
2658
|
</table>
|
|
2642
2659
|
|
|
2660
|
+
### timeZone
|
|
2661
|
+
- Type `string`
|
|
2662
|
+
- Default `'local'`
|
|
2663
|
+
|
|
2664
|
+
The time zone the calendar uses to display dates and times.
|
|
2665
|
+
|
|
2666
|
+
The following values are accepted:
|
|
2667
|
+
- `'local'` — uses the browser's local time zone
|
|
2668
|
+
- `'UTC'` — uses UTC (zero offset)
|
|
2669
|
+
- A UTC offset string in the form `'±HH:MM'`, e.g. `'+05:30'` or `'-06:00'`
|
|
2670
|
+
|
|
2671
|
+
Event dates that contain an explicit timezone offset in their ISO string (e.g. `'2028-06-01T10:00:00+02:00'`) will be shifted to the calendar's timezone. Event dates without timezone info (e.g. `'2028-06-01T10:00:00'`) are treated as floating — they display their wall-clock time as-is and will be interpreted in the calendar's timezone from that point forward.
|
|
2672
|
+
|
|
2673
|
+
When the `timeZone` option changes at runtime, all already-loaded events and the current `date` option are automatically shifted to the new timezone. Events from [eventSources](#eventsources) are re-fetched automatically.
|
|
2674
|
+
|
|
2675
|
+
```js
|
|
2676
|
+
let ec = new EventCalendar(document.getElementById('ec'), {
|
|
2677
|
+
timeZone: '+02:00',
|
|
2678
|
+
events: [
|
|
2679
|
+
{
|
|
2680
|
+
start: '2028-06-01T10:00:00', // floating — displayed as 10:00
|
|
2681
|
+
end: '2028-06-01T12:00:00',
|
|
2682
|
+
title: 'Meeting'
|
|
2683
|
+
},
|
|
2684
|
+
{
|
|
2685
|
+
start: '2028-06-01T10:00:00+00:00', // UTC — displayed as 12:00 in +02:00
|
|
2686
|
+
end: '2028-06-01T12:00:00+00:00',
|
|
2687
|
+
title: 'Call'
|
|
2688
|
+
}
|
|
2689
|
+
]
|
|
2690
|
+
});
|
|
2691
|
+
```
|
|
2692
|
+
|
|
2643
2693
|
### titleFormat
|
|
2644
2694
|
- Type `object` or `function`
|
|
2645
2695
|
- Default `{year: 'numeric', month: 'short', day: 'numeric'}`
|
package/dist/index.css
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* EventCalendar v5.
|
|
2
|
+
* EventCalendar v5.7.0
|
|
3
3
|
* https://github.com/vkurko/calendar
|
|
4
4
|
*/
|
|
5
5
|
import { getAbortSignal, getContext, mount, onMount, setContext, tick, unmount, untrack } from "svelte";
|
|
@@ -90,6 +90,9 @@ function length(array) {
|
|
|
90
90
|
function empty(array) {
|
|
91
91
|
return !length(array);
|
|
92
92
|
}
|
|
93
|
+
function tzOffset(date = /* @__PURE__ */ new Date()) {
|
|
94
|
+
return -date.getTimezoneOffset();
|
|
95
|
+
}
|
|
93
96
|
function isArray(value) {
|
|
94
97
|
return Array.isArray(value);
|
|
95
98
|
}
|
|
@@ -121,9 +124,8 @@ function undefinedOr(fn) {
|
|
|
121
124
|
//#endregion
|
|
122
125
|
//#region packages/core/src/lib/date.js
|
|
123
126
|
var DAY_IN_SECONDS = 86400;
|
|
124
|
-
function createDate(input = void 0) {
|
|
125
|
-
|
|
126
|
-
return _fromLocalDate(/* @__PURE__ */ new Date());
|
|
127
|
+
function createDate(input = /* @__PURE__ */ new Date(), offset = void 0) {
|
|
128
|
+
return isDate(input) ? _fromLocalDate(input, offset) : _fromISOString(input, offset);
|
|
127
129
|
}
|
|
128
130
|
function createDuration(input) {
|
|
129
131
|
if (typeof input === "number") input = { seconds: input };
|
|
@@ -146,7 +148,9 @@ function createDuration(input) {
|
|
|
146
148
|
};
|
|
147
149
|
}
|
|
148
150
|
function cloneDate(date) {
|
|
149
|
-
|
|
151
|
+
let result = new Date(date.getTime());
|
|
152
|
+
setOffset(result, getOffset(date));
|
|
153
|
+
return result;
|
|
150
154
|
}
|
|
151
155
|
function addDuration(date, duration, x = 1) {
|
|
152
156
|
date.setUTCFullYear(date.getUTCFullYear() + x * duration.years);
|
|
@@ -227,9 +231,6 @@ function prevDate(date, duration, hiddenDays) {
|
|
|
227
231
|
_skipHiddenDays(date, hiddenDays, subtractDay);
|
|
228
232
|
return date;
|
|
229
233
|
}
|
|
230
|
-
function _skipHiddenDays(date, hiddenDays, dateFn) {
|
|
231
|
-
if (hiddenDays.length && hiddenDays.length < 7) while (hiddenDays.includes(date.getUTCDay())) dateFn(date);
|
|
232
|
-
}
|
|
233
234
|
/**
|
|
234
235
|
* For a given date, get its week number
|
|
235
236
|
* - ISO @see https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php
|
|
@@ -249,15 +250,49 @@ function createWeekNumberContent(week, weekNumberContent, date) {
|
|
|
249
250
|
}) : weekNumberContent;
|
|
250
251
|
return "W" + String(week).padStart(2, "0");
|
|
251
252
|
}
|
|
253
|
+
function parseOffset(str, match = {}) {
|
|
254
|
+
let parts = str.match(/([+-])(\d{2}):(\d{2})$/);
|
|
255
|
+
if (parts) {
|
|
256
|
+
assign(match, parts);
|
|
257
|
+
return +(parts[1] + "1") * (+parts[2] * 60 + +parts[3]);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Apply timezone offset difference in minutes to a date
|
|
262
|
+
*/
|
|
263
|
+
function applyOffsetDiff(date, offsetDiff) {
|
|
264
|
+
if (offsetDiff) date.setUTCMinutes(date.getUTCMinutes() + offsetDiff);
|
|
265
|
+
return date;
|
|
266
|
+
}
|
|
267
|
+
var offsetSymbol = Symbol("ec");
|
|
268
|
+
function setOffset(date, offset) {
|
|
269
|
+
date[offsetSymbol] = offset;
|
|
270
|
+
return date;
|
|
271
|
+
}
|
|
272
|
+
function getOffset(date) {
|
|
273
|
+
return date[offsetSymbol];
|
|
274
|
+
}
|
|
252
275
|
/**
|
|
253
276
|
* Private functions
|
|
254
277
|
*/
|
|
255
|
-
function _fromLocalDate(date) {
|
|
256
|
-
|
|
278
|
+
function _fromLocalDate(date, offset = void 0) {
|
|
279
|
+
let result = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()));
|
|
280
|
+
applyOffsetDiff(result, offset ? offset - tzOffset(result) : 0);
|
|
281
|
+
setOffset(result, offset ?? tzOffset(result));
|
|
282
|
+
return result;
|
|
257
283
|
}
|
|
258
|
-
function _fromISOString(str) {
|
|
259
|
-
|
|
260
|
-
|
|
284
|
+
function _fromISOString(str, offset = void 0) {
|
|
285
|
+
let match = {};
|
|
286
|
+
let inputOffset = parseOffset(str, match);
|
|
287
|
+
if (inputOffset !== void 0) str = str.substring(0, match.index);
|
|
288
|
+
let parts = str.match(/\d+/g);
|
|
289
|
+
let result = new Date(Date.UTC(+parts[0], +parts[1] - 1, +parts[2], +parts[3] || 0, +parts[4] || 0, +parts[5] || 0));
|
|
290
|
+
if (offset !== void 0 && inputOffset !== void 0) applyOffsetDiff(result, offset - inputOffset);
|
|
291
|
+
setOffset(result, offset ?? inputOffset);
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
function _skipHiddenDays(date, hiddenDays, dateFn) {
|
|
295
|
+
if (hiddenDays.length && hiddenDays.length < 7) while (hiddenDays.includes(date.getUTCDay())) dateFn(date);
|
|
261
296
|
}
|
|
262
297
|
//#endregion
|
|
263
298
|
//#region packages/core/src/lib/payload.js
|
|
@@ -338,14 +373,14 @@ function toViewWithLocalDates(view) {
|
|
|
338
373
|
//#endregion
|
|
339
374
|
//#region packages/core/src/lib/events.js
|
|
340
375
|
var eventId = 1;
|
|
341
|
-
function createEvents(input) {
|
|
376
|
+
function createEvents(input, offset = void 0) {
|
|
342
377
|
return input.map((event) => {
|
|
343
378
|
let result = {
|
|
344
379
|
id: "id" in event ? String(event.id) : `{generated-${eventId++}}`,
|
|
345
380
|
resourceIds: toArrayProp(event, "resourceId").map(String),
|
|
346
381
|
allDay: event.allDay ?? (noTimePart(event.start) && noTimePart(event.end)),
|
|
347
|
-
start: createDate(event.start),
|
|
348
|
-
end: createDate(event.end),
|
|
382
|
+
start: createDate(event.start, offset),
|
|
383
|
+
end: createDate(event.end, offset),
|
|
349
384
|
title: event.title ?? "",
|
|
350
385
|
editable: event.editable,
|
|
351
386
|
startEditable: event.startEditable,
|
|
@@ -886,6 +921,7 @@ function createOptions(plugins) {
|
|
|
886
921
|
],
|
|
887
922
|
weekNumber: "ec-week-number"
|
|
888
923
|
},
|
|
924
|
+
timeZone: "local",
|
|
889
925
|
titleFormat: {
|
|
890
926
|
year: "numeric",
|
|
891
927
|
month: "short",
|
|
@@ -941,7 +977,7 @@ function optionsState(plugins, userOptions) {
|
|
|
941
977
|
let component = extractOption(opts, "component");
|
|
942
978
|
delete opts.view;
|
|
943
979
|
for (let key of keys(opts)) if (hasOwn(options, key)) {
|
|
944
|
-
|
|
980
|
+
setters[key] ??= [];
|
|
945
981
|
setters[key].push(specialOptions.includes(key) ? (value) => opts[key] = isFunction(value) ? value(defOpts[key]) : value : (value) => opts[key] = value);
|
|
946
982
|
} else delete opts[key];
|
|
947
983
|
viewOptions[view] = opts;
|
|
@@ -1014,27 +1050,27 @@ function switchView(mainState) {
|
|
|
1014
1050
|
}
|
|
1015
1051
|
function loadEvents(mainState, loadingInvoker) {
|
|
1016
1052
|
return () => {
|
|
1017
|
-
let { activeRange, fetchedRange: { events: fetchedRange }, viewDates, options: { events, eventSources, lazyFetching } } = mainState;
|
|
1053
|
+
let { activeRange, fetchedRange: { events: fetchedRange }, offset, viewDates, options: { events, eventSources, lazyFetching, timeZone } } = mainState;
|
|
1018
1054
|
untrack(() => {
|
|
1019
|
-
load(eventSources.map((source) => isFunction(source.events) ? source.events : source), events, createEvents, (result) => mainState.events = arrayProxy(result), activeRange, fetchedRange, viewDates, true, lazyFetching, loadingInvoker);
|
|
1055
|
+
load(eventSources.map((source) => isFunction(source.events) ? source.events : source), events, (input) => createEvents(input, offset), (result) => mainState.events = arrayProxy(result), timeZone, activeRange, fetchedRange, viewDates, true, lazyFetching, loadingInvoker);
|
|
1020
1056
|
});
|
|
1021
1057
|
};
|
|
1022
1058
|
}
|
|
1023
1059
|
function loadResources(mainState, loadingInvoker) {
|
|
1024
1060
|
return () => {
|
|
1025
|
-
let { activeRange, fetchedRange: { resources: fetchedRange }, viewDates, options: { lazyFetching, refetchResourcesOnNavigate, resources } } = mainState;
|
|
1061
|
+
let { activeRange, fetchedRange: { resources: fetchedRange }, viewDates, options: { lazyFetching, refetchResourcesOnNavigate, resources, timeZone } } = mainState;
|
|
1026
1062
|
untrack(() => {
|
|
1027
|
-
load(isArray(resources) ? [] : [resources], resources, createResources, (result) => mainState.resources = arrayProxy(result), activeRange, fetchedRange, viewDates, refetchResourcesOnNavigate, lazyFetching, loadingInvoker);
|
|
1063
|
+
load(isArray(resources) ? [] : [resources], resources, createResources, (result) => mainState.resources = arrayProxy(result), timeZone, activeRange, fetchedRange, viewDates, refetchResourcesOnNavigate, lazyFetching, loadingInvoker);
|
|
1028
1064
|
});
|
|
1029
1065
|
};
|
|
1030
1066
|
}
|
|
1031
|
-
function load(sources, defaultResult, parseResult, applyResult, activeRange, fetchedRange, viewDates, refetchOnNavigate, lazyFetching, loading) {
|
|
1067
|
+
function load(sources, defaultResult, parseResult, applyResult, timeZone, activeRange, fetchedRange, viewDates, refetchOnNavigate, lazyFetching, loading) {
|
|
1032
1068
|
if (empty(viewDates)) return;
|
|
1033
1069
|
if (empty(sources)) {
|
|
1034
1070
|
applyResult(defaultResult);
|
|
1035
1071
|
return;
|
|
1036
1072
|
}
|
|
1037
|
-
if ((refetchOnNavigate || !fetchedRange.start) && (!lazyFetching || !fetchedRange.start || fetchedRange.start > activeRange.start || fetchedRange.end < activeRange.end)) {
|
|
1073
|
+
if ((refetchOnNavigate || !fetchedRange.start) && (!lazyFetching || !fetchedRange.start || fetchedRange.start > activeRange.start || fetchedRange.end < activeRange.end || fetchedRange.timeZone !== timeZone)) {
|
|
1038
1074
|
let result = [];
|
|
1039
1075
|
let failure = (e) => loading.stop();
|
|
1040
1076
|
let success = (data) => {
|
|
@@ -1051,7 +1087,8 @@ function load(sources, defaultResult, parseResult, applyResult, activeRange, fet
|
|
|
1051
1087
|
start: toLocalDate(activeRange.start),
|
|
1052
1088
|
end: toLocalDate(activeRange.end),
|
|
1053
1089
|
startStr,
|
|
1054
|
-
endStr
|
|
1090
|
+
endStr,
|
|
1091
|
+
timeZone
|
|
1055
1092
|
} : {}, success, failure);
|
|
1056
1093
|
if (result !== void 0) Promise.resolve(result).then(success, failure);
|
|
1057
1094
|
} else {
|
|
@@ -1059,6 +1096,7 @@ function load(sources, defaultResult, parseResult, applyResult, activeRange, fet
|
|
|
1059
1096
|
if (refetchOnNavigate) {
|
|
1060
1097
|
params.start = startStr;
|
|
1061
1098
|
params.end = endStr;
|
|
1099
|
+
if (timeZone !== "local") params.timeZone = timeZone;
|
|
1062
1100
|
}
|
|
1063
1101
|
params = new URLSearchParams(params);
|
|
1064
1102
|
let url = source.url, headers = {}, body;
|
|
@@ -1076,7 +1114,10 @@ function load(sources, defaultResult, parseResult, applyResult, activeRange, fet
|
|
|
1076
1114
|
}).then((response) => response.json()).then(success).catch(failure);
|
|
1077
1115
|
}
|
|
1078
1116
|
}
|
|
1079
|
-
assign(fetchedRange,
|
|
1117
|
+
assign(fetchedRange, {
|
|
1118
|
+
...activeRange,
|
|
1119
|
+
timeZone
|
|
1120
|
+
});
|
|
1080
1121
|
}
|
|
1081
1122
|
}
|
|
1082
1123
|
function createLoadingInvoker(mainState) {
|
|
@@ -1092,8 +1133,9 @@ function createLoadingInvoker(mainState) {
|
|
|
1092
1133
|
}
|
|
1093
1134
|
function setNowAndToday(mainState) {
|
|
1094
1135
|
return () => {
|
|
1136
|
+
let { offset } = mainState;
|
|
1095
1137
|
let interval = setInterval(() => {
|
|
1096
|
-
let now = createDate();
|
|
1138
|
+
let now = createDate(void 0, offset);
|
|
1097
1139
|
let today = setMidnight(cloneDate(now));
|
|
1098
1140
|
mainState.now = now;
|
|
1099
1141
|
if (!datesEqual(mainState.today, today)) mainState.today = today;
|
|
@@ -1101,6 +1143,25 @@ function setNowAndToday(mainState) {
|
|
|
1101
1143
|
return () => clearInterval(interval);
|
|
1102
1144
|
};
|
|
1103
1145
|
}
|
|
1146
|
+
function handleTimeZoneChange(mainState) {
|
|
1147
|
+
return () => {
|
|
1148
|
+
let { offset, options } = mainState;
|
|
1149
|
+
untrack(() => {
|
|
1150
|
+
for (let event of mainState.events) if (!event.allDay) for (let prop of ["start", "end"]) {
|
|
1151
|
+
let dateOffset = getOffset(event[prop]);
|
|
1152
|
+
if (dateOffset !== void 0) applyOffsetDiff(event[prop], offset - dateOffset);
|
|
1153
|
+
setOffset(event[prop], offset);
|
|
1154
|
+
}
|
|
1155
|
+
let dateOffset = getOffset(options.date);
|
|
1156
|
+
if (dateOffset !== void 0) {
|
|
1157
|
+
let diff = createDate(void 0, offset).getUTCDay() - createDate(void 0, dateOffset).getUTCDay();
|
|
1158
|
+
let date = addDay(cloneDate(options.date), diff);
|
|
1159
|
+
mainState.setOption("date", date);
|
|
1160
|
+
}
|
|
1161
|
+
setOffset(options.date, offset);
|
|
1162
|
+
});
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1104
1165
|
function runDatesSet(mainState) {
|
|
1105
1166
|
return () => {
|
|
1106
1167
|
let { activeRange, options: { datesSet } } = mainState;
|
|
@@ -1194,6 +1255,16 @@ function filteredEvents(mainState) {
|
|
|
1194
1255
|
return result;
|
|
1195
1256
|
};
|
|
1196
1257
|
}
|
|
1258
|
+
function offset(mainState) {
|
|
1259
|
+
return () => {
|
|
1260
|
+
let { options: { timeZone } } = mainState;
|
|
1261
|
+
let offset;
|
|
1262
|
+
untrack(() => {
|
|
1263
|
+
offset = timeZone === "local" ? tzOffset() : timeZone === "UTC" ? 0 : parseOffset(timeZone) ?? tzOffset();
|
|
1264
|
+
});
|
|
1265
|
+
return offset;
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1197
1268
|
function viewDates(mainState) {
|
|
1198
1269
|
return () => {
|
|
1199
1270
|
let { options, activeRange } = mainState;
|
|
@@ -1246,6 +1317,13 @@ var State = class {
|
|
|
1246
1317
|
set auxComponents(value) {
|
|
1247
1318
|
$.set(this.#auxComponents, value, true);
|
|
1248
1319
|
}
|
|
1320
|
+
#offset;
|
|
1321
|
+
get offset() {
|
|
1322
|
+
return $.get(this.#offset);
|
|
1323
|
+
}
|
|
1324
|
+
set offset(value) {
|
|
1325
|
+
$.set(this.#offset, value);
|
|
1326
|
+
}
|
|
1249
1327
|
#currentRange;
|
|
1250
1328
|
get currentRange() {
|
|
1251
1329
|
return $.get(this.#currentRange);
|
|
@@ -1406,6 +1484,7 @@ var State = class {
|
|
|
1406
1484
|
constructor(plugins, options) {
|
|
1407
1485
|
[this.options, this.setOption, this.setViewOptions] = optionsState(plugins, options);
|
|
1408
1486
|
this.#auxComponents = $.state($.proxy([]));
|
|
1487
|
+
this.#offset = $.derived(offset(this));
|
|
1409
1488
|
this.#currentRange = $.derived(currentRange(this));
|
|
1410
1489
|
this.#activeRange = $.derived(activeRange(this));
|
|
1411
1490
|
this.#fetchedRange = $.state($.proxy({
|
|
@@ -1415,7 +1494,7 @@ var State = class {
|
|
|
1415
1494
|
this.#events = $.state(arrayProxy(this.options.events));
|
|
1416
1495
|
this.#filteredEvents = $.derived(filteredEvents(this));
|
|
1417
1496
|
this.#mainEl = $.state();
|
|
1418
|
-
this.#now = $.state($.proxy(createDate()));
|
|
1497
|
+
this.#now = $.state($.proxy(createDate(void 0, this.offset)));
|
|
1419
1498
|
this.#resources = $.state(arrayProxy(isArray(this.options.resources) ? this.options.resources : []));
|
|
1420
1499
|
this.#today = $.state($.proxy(setMidnight(cloneDate(this.now))));
|
|
1421
1500
|
this.#intlEventTime = $.derived(intlRange(this, "eventTimeFormat"));
|
|
@@ -1438,6 +1517,7 @@ var State = class {
|
|
|
1438
1517
|
#initEffects() {
|
|
1439
1518
|
let loading = createLoadingInvoker(this);
|
|
1440
1519
|
$.user_pre_effect(switchView(this));
|
|
1520
|
+
$.user_pre_effect(handleTimeZoneChange(this));
|
|
1441
1521
|
$.user_pre_effect(setNowAndToday(this));
|
|
1442
1522
|
$.user_effect(loadEvents(this, loading));
|
|
1443
1523
|
$.user_effect(loadResources(this, loading));
|
|
@@ -1654,7 +1734,7 @@ function Calendar($$anchor, $$props) {
|
|
|
1654
1734
|
let plugins = $.prop($$props, "plugins", 19, () => []), options = $.prop($$props, "options", 19, () => ({}));
|
|
1655
1735
|
let mainState = new State(plugins(), options());
|
|
1656
1736
|
setContext("state", mainState);
|
|
1657
|
-
let auxComponents = $.derived(() => mainState.auxComponents), features = $.derived(() => mainState.features), events = $.derived(() => mainState.events), interaction = $.derived(() => mainState.interaction), iClass = $.derived(() => mainState.iClass), view = $.derived(() => mainState.view), View = $.derived(() => mainState.viewComponent), date = $.derived(() => mainState.options.date), dateIncrement = $.derived(() => mainState.options.dateIncrement), duration = $.derived(() => mainState.options.duration), height = $.derived(() => mainState.options.height), hiddenDays = $.derived(() => mainState.options.hiddenDays), customScrollbars = $.derived(() => mainState.options.customScrollbars), theme = $.derived(() => mainState.options.theme);
|
|
1737
|
+
let auxComponents = $.derived(() => mainState.auxComponents), features = $.derived(() => mainState.features), events = $.derived(() => mainState.events), interaction = $.derived(() => mainState.interaction), iClass = $.derived(() => mainState.iClass), offset = $.derived(() => mainState.offset), view = $.derived(() => mainState.view), View = $.derived(() => mainState.viewComponent), date = $.derived(() => mainState.options.date), dateIncrement = $.derived(() => mainState.options.dateIncrement), duration = $.derived(() => mainState.options.duration), height = $.derived(() => mainState.options.height), hiddenDays = $.derived(() => mainState.options.hiddenDays), customScrollbars = $.derived(() => mainState.options.customScrollbars), theme = $.derived(() => mainState.options.theme);
|
|
1658
1738
|
let prevOptions = { ...options() };
|
|
1659
1739
|
$.user_pre_effect(() => {
|
|
1660
1740
|
for (let [name, value] of diff(options(), prevOptions)) untrack(() => {
|
|
@@ -1687,7 +1767,7 @@ function Calendar($$anchor, $$props) {
|
|
|
1687
1767
|
return null;
|
|
1688
1768
|
}
|
|
1689
1769
|
function addEvent(event) {
|
|
1690
|
-
event = createEvents([event])[0];
|
|
1770
|
+
event = createEvents([event], $.get(offset))[0];
|
|
1691
1771
|
$.get(events).push(event);
|
|
1692
1772
|
return toEventWithLocalDates(event);
|
|
1693
1773
|
}
|
|
@@ -1695,7 +1775,7 @@ function Calendar($$anchor, $$props) {
|
|
|
1695
1775
|
let id = String(event.id);
|
|
1696
1776
|
let idx = $.get(events).findIndex((event) => event.id === id);
|
|
1697
1777
|
if (idx >= 0) {
|
|
1698
|
-
event = createEvents([event])[0];
|
|
1778
|
+
event = createEvents([event], $.get(offset))[0];
|
|
1699
1779
|
$.get(events)[idx] = event;
|
|
1700
1780
|
return toEventWithLocalDates(event);
|
|
1701
1781
|
}
|
|
@@ -3877,11 +3957,11 @@ function slotLabelPeriodicity(mainState) {
|
|
|
3877
3957
|
}
|
|
3878
3958
|
function slots(mainState, viewState) {
|
|
3879
3959
|
return () => {
|
|
3880
|
-
let { options: { slotDuration } } = mainState;
|
|
3960
|
+
let { offset, options: { slotDuration } } = mainState;
|
|
3881
3961
|
let { intlSlotLabel, slotLabelPeriodicity, slotTimeLimits } = viewState;
|
|
3882
3962
|
let slots;
|
|
3883
3963
|
untrack(() => {
|
|
3884
|
-
slots = createSlots(setMidnight(createDate()), slotDuration, slotLabelPeriodicity, slotTimeLimits, intlSlotLabel);
|
|
3964
|
+
slots = createSlots(setMidnight(createDate(void 0, offset)), slotDuration, slotLabelPeriodicity, slotTimeLimits, intlSlotLabel);
|
|
3885
3965
|
});
|
|
3886
3966
|
return slots;
|
|
3887
3967
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event-calendar/core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.7.0",
|
|
4
4
|
"title": "Event Calendar Core package",
|
|
5
5
|
"description": "Full-sized drag & drop event calendar with resource & timeline views",
|
|
6
6
|
"keywords": [
|
|
@@ -32,6 +32,6 @@
|
|
|
32
32
|
"#components": "./src/lib/components/index.js"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"svelte": "^5.55.
|
|
35
|
+
"svelte": "^5.55.4"
|
|
36
36
|
}
|
|
37
37
|
}
|
package/src/Calendar.svelte
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
setContext('state', mainState);
|
|
17
17
|
|
|
18
18
|
let {
|
|
19
|
-
auxComponents, features, events, interaction, iClass, view, viewComponent: View,
|
|
19
|
+
auxComponents, features, events, interaction, iClass, offset, view, viewComponent: View,
|
|
20
20
|
options: {date, dateIncrement, duration, height, hiddenDays, customScrollbars, theme}
|
|
21
21
|
} = $derived(mainState);
|
|
22
22
|
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export function addEvent(event) {
|
|
70
|
-
event = createEvents([event])[0];
|
|
70
|
+
event = createEvents([event], offset)[0];
|
|
71
71
|
events.push(event);
|
|
72
72
|
return toEventWithLocalDates(event);
|
|
73
73
|
}
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
let id = String(event.id);
|
|
77
77
|
let idx = events.findIndex(event => event.id === id);
|
|
78
78
|
if (idx >= 0) {
|
|
79
|
-
event = createEvents([event])[0];
|
|
79
|
+
event = createEvents([event], offset)[0];
|
|
80
80
|
events[idx] = event;
|
|
81
81
|
return toEventWithLocalDates(event);
|
|
82
82
|
}
|
package/src/lib/date.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import {isDate, isFunction} from './utils.js';
|
|
1
|
+
import {assign, isDate, isFunction, tzOffset} from './utils.js';
|
|
2
2
|
|
|
3
3
|
export const DAY_IN_SECONDS = 86400;
|
|
4
4
|
|
|
5
|
-
export function createDate(input = undefined) {
|
|
6
|
-
|
|
7
|
-
return isDate(input) ? _fromLocalDate(input) : _fromISOString(input);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
return _fromLocalDate(new Date());
|
|
5
|
+
export function createDate(input = new Date(), offset = undefined) {
|
|
6
|
+
return isDate(input) ? _fromLocalDate(input, offset) : _fromISOString(input, offset);
|
|
11
7
|
}
|
|
12
8
|
|
|
13
9
|
export function createDuration(input) {
|
|
@@ -38,7 +34,10 @@ export function createDuration(input) {
|
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
export function cloneDate(date) {
|
|
41
|
-
|
|
37
|
+
let result = new Date(date.getTime());
|
|
38
|
+
setOffset(result, getOffset(date));
|
|
39
|
+
|
|
40
|
+
return result;
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
export function addDuration(date, duration, x = 1) {
|
|
@@ -152,14 +151,6 @@ export function prevDate(date, duration, hiddenDays) {
|
|
|
152
151
|
return date;
|
|
153
152
|
}
|
|
154
153
|
|
|
155
|
-
function _skipHiddenDays(date, hiddenDays, dateFn) {
|
|
156
|
-
if (hiddenDays.length && hiddenDays.length < 7) {
|
|
157
|
-
while (hiddenDays.includes(date.getUTCDay())) {
|
|
158
|
-
dateFn(date);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
154
|
/**
|
|
164
155
|
* For a given date, get its week number
|
|
165
156
|
* - ISO @see https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php
|
|
@@ -192,12 +183,42 @@ export function createWeekNumberContent(week, weekNumberContent, date) {
|
|
|
192
183
|
return 'W' + String(week).padStart(2, '0');
|
|
193
184
|
}
|
|
194
185
|
|
|
186
|
+
export function parseOffset(str, match = {}) {
|
|
187
|
+
let parts = str.match(/([+-])(\d{2}):(\d{2})$/);
|
|
188
|
+
if (parts) {
|
|
189
|
+
assign(match, parts);
|
|
190
|
+
return +(parts[1] + '1') * (+parts[2] * 60 + +parts[3]);
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Apply timezone offset difference in minutes to a date
|
|
197
|
+
*/
|
|
198
|
+
export function applyOffsetDiff(date, offsetDiff) {
|
|
199
|
+
if (offsetDiff) {
|
|
200
|
+
date.setUTCMinutes(date.getUTCMinutes() + offsetDiff);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return date;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let offsetSymbol = Symbol('ec');
|
|
207
|
+
export function setOffset(date, offset) {
|
|
208
|
+
date[offsetSymbol] = offset;
|
|
209
|
+
return date;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function getOffset(date) {
|
|
213
|
+
return date[offsetSymbol];
|
|
214
|
+
}
|
|
215
|
+
|
|
195
216
|
/**
|
|
196
217
|
* Private functions
|
|
197
218
|
*/
|
|
198
219
|
|
|
199
|
-
function _fromLocalDate(date) {
|
|
200
|
-
|
|
220
|
+
function _fromLocalDate(date, offset = undefined) {
|
|
221
|
+
let result = new Date(Date.UTC(
|
|
201
222
|
date.getFullYear(),
|
|
202
223
|
date.getMonth(),
|
|
203
224
|
date.getDate(),
|
|
@@ -205,16 +226,39 @@ function _fromLocalDate(date) {
|
|
|
205
226
|
date.getMinutes(),
|
|
206
227
|
date.getSeconds()
|
|
207
228
|
));
|
|
229
|
+
applyOffsetDiff(result, offset ? offset - tzOffset(result) : 0);
|
|
230
|
+
setOffset(result, offset ?? tzOffset(result));
|
|
231
|
+
|
|
232
|
+
return result;
|
|
208
233
|
}
|
|
209
234
|
|
|
210
|
-
function _fromISOString(str) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
235
|
+
function _fromISOString(str, offset = undefined) {
|
|
236
|
+
let match = {};
|
|
237
|
+
let inputOffset = parseOffset(str, match);
|
|
238
|
+
if (inputOffset !== undefined) {
|
|
239
|
+
str = str.substring(0, match.index);
|
|
240
|
+
}
|
|
241
|
+
let parts = str.match(/\d+/g);
|
|
242
|
+
let result = new Date(Date.UTC(
|
|
243
|
+
+parts[0],
|
|
244
|
+
+parts[1] - 1,
|
|
245
|
+
+parts[2],
|
|
246
|
+
+parts[3] || 0,
|
|
247
|
+
+parts[4] || 0,
|
|
248
|
+
+parts[5] || 0
|
|
219
249
|
));
|
|
250
|
+
if (offset !== undefined && inputOffset !== undefined) {
|
|
251
|
+
applyOffsetDiff(result, offset - inputOffset);
|
|
252
|
+
}
|
|
253
|
+
setOffset(result, offset ?? inputOffset);
|
|
254
|
+
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function _skipHiddenDays(date, hiddenDays, dateFn) {
|
|
259
|
+
if (hiddenDays.length && hiddenDays.length < 7) {
|
|
260
|
+
while (hiddenDays.includes(date.getUTCDay())) {
|
|
261
|
+
dateFn(date);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
220
264
|
}
|
package/src/lib/events.js
CHANGED
|
@@ -6,14 +6,14 @@ import {assign, isArray, isFunction} from './utils.js';
|
|
|
6
6
|
import {toViewWithLocalDates} from './view.js';
|
|
7
7
|
|
|
8
8
|
let eventId = 1;
|
|
9
|
-
export function createEvents(input) {
|
|
9
|
+
export function createEvents(input, offset = undefined) {
|
|
10
10
|
return input.map(event => {
|
|
11
11
|
let result = {
|
|
12
12
|
id: 'id' in event ? String(event.id) : `{generated-${eventId++}}`,
|
|
13
13
|
resourceIds: toArrayProp(event, 'resourceId').map(String),
|
|
14
14
|
allDay: event.allDay ?? (noTimePart(event.start) && noTimePart(event.end)),
|
|
15
|
-
start: createDate(event.start),
|
|
16
|
-
end: createDate(event.end),
|
|
15
|
+
start: createDate(event.start, offset),
|
|
16
|
+
end: createDate(event.end, offset),
|
|
17
17
|
title: event.title ?? '',
|
|
18
18
|
editable: event.editable,
|
|
19
19
|
startEditable: event.startEditable,
|
package/src/lib/utils.js
CHANGED
|
@@ -134,13 +134,19 @@ export function slotLabelPeriodicity(mainState) {
|
|
|
134
134
|
export function slots(mainState, viewState) {
|
|
135
135
|
return () => {
|
|
136
136
|
// Dependencies
|
|
137
|
-
let {options: {slotDuration}} = mainState;
|
|
137
|
+
let {offset, options: {slotDuration}} = mainState;
|
|
138
138
|
let {intlSlotLabel, slotLabelPeriodicity, slotTimeLimits} = viewState;
|
|
139
139
|
|
|
140
140
|
let slots;
|
|
141
141
|
|
|
142
142
|
untrack(() => {
|
|
143
|
-
slots = createSlots(
|
|
143
|
+
slots = createSlots(
|
|
144
|
+
setMidnight(createDate(undefined, offset)),
|
|
145
|
+
slotDuration,
|
|
146
|
+
slotLabelPeriodicity,
|
|
147
|
+
slotTimeLimits,
|
|
148
|
+
intlSlotLabel
|
|
149
|
+
);
|
|
144
150
|
});
|
|
145
151
|
|
|
146
152
|
return slots;
|
package/src/storage/derived.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {tick, untrack} from 'svelte';
|
|
2
2
|
import {
|
|
3
3
|
addDay, addDuration, cloneDate, createView, isFunction, prevClosestDay, setMidnight, subtractDay,
|
|
4
|
-
toEventWithLocalDates, toViewWithLocalDates
|
|
4
|
+
toEventWithLocalDates, toViewWithLocalDates, parseOffset, tzOffset, applyOffsetDiff
|
|
5
5
|
} from '#lib';
|
|
6
6
|
|
|
7
7
|
export function currentRange(mainState) {
|
|
@@ -85,6 +85,23 @@ export function filteredEvents(mainState) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
export function offset(mainState) {
|
|
89
|
+
return () => {
|
|
90
|
+
// Dependencies
|
|
91
|
+
let {options: {timeZone}} = mainState;
|
|
92
|
+
|
|
93
|
+
let offset;
|
|
94
|
+
|
|
95
|
+
untrack(() => {
|
|
96
|
+
offset = timeZone === 'local' ? tzOffset() : (
|
|
97
|
+
timeZone === 'UTC' ? 0 : (parseOffset(timeZone) ?? tzOffset())
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return offset;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
88
105
|
export function viewDates(mainState) {
|
|
89
106
|
return () => {
|
|
90
107
|
// Dependencies
|
package/src/storage/effects.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {getAbortSignal, tick, untrack} from 'svelte';
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
addDay,
|
|
4
|
+
applyOffsetDiff, assign, cloneDate, createDate, createEvents, createResources, datesEqual, empty, getOffset, isArray,
|
|
5
|
+
isFunction, setMidnight, setOffset, toISOString, toLocalDate, toViewWithLocalDates
|
|
5
6
|
} from '#lib';
|
|
6
7
|
import {arrayProxy} from './proxy.svelte.js';
|
|
7
8
|
|
|
@@ -22,15 +23,16 @@ export function switchView(mainState) {
|
|
|
22
23
|
export function loadEvents(mainState, loadingInvoker) {
|
|
23
24
|
return () => {
|
|
24
25
|
// Dependencies
|
|
25
|
-
let {activeRange, fetchedRange: {events: fetchedRange}, viewDates,
|
|
26
|
-
options: {events, eventSources, lazyFetching}} = mainState;
|
|
26
|
+
let {activeRange, fetchedRange: {events: fetchedRange}, offset, viewDates,
|
|
27
|
+
options: {events, eventSources, lazyFetching, timeZone}} = mainState;
|
|
27
28
|
|
|
28
29
|
untrack(() => {
|
|
29
30
|
load(
|
|
30
31
|
eventSources.map(source => isFunction(source.events) ? source.events : source),
|
|
31
32
|
events,
|
|
32
|
-
createEvents,
|
|
33
|
+
input => createEvents(input, offset),
|
|
33
34
|
result => mainState.events = arrayProxy(result),
|
|
35
|
+
timeZone,
|
|
34
36
|
activeRange,
|
|
35
37
|
fetchedRange,
|
|
36
38
|
viewDates,
|
|
@@ -46,7 +48,7 @@ export function loadResources(mainState, loadingInvoker) {
|
|
|
46
48
|
return () => {
|
|
47
49
|
// Dependencies
|
|
48
50
|
let {activeRange, fetchedRange: {resources: fetchedRange}, viewDates,
|
|
49
|
-
options: {lazyFetching, refetchResourcesOnNavigate, resources}} = mainState;
|
|
51
|
+
options: {lazyFetching, refetchResourcesOnNavigate, resources, timeZone}} = mainState;
|
|
50
52
|
|
|
51
53
|
untrack(() => {
|
|
52
54
|
load(
|
|
@@ -54,6 +56,7 @@ export function loadResources(mainState, loadingInvoker) {
|
|
|
54
56
|
resources,
|
|
55
57
|
createResources,
|
|
56
58
|
result => mainState.resources = arrayProxy(result),
|
|
59
|
+
timeZone,
|
|
57
60
|
activeRange,
|
|
58
61
|
fetchedRange,
|
|
59
62
|
viewDates,
|
|
@@ -65,7 +68,19 @@ export function loadResources(mainState, loadingInvoker) {
|
|
|
65
68
|
};
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
function load(
|
|
71
|
+
function load(
|
|
72
|
+
sources,
|
|
73
|
+
defaultResult,
|
|
74
|
+
parseResult,
|
|
75
|
+
applyResult,
|
|
76
|
+
timeZone,
|
|
77
|
+
activeRange,
|
|
78
|
+
fetchedRange,
|
|
79
|
+
viewDates,
|
|
80
|
+
refetchOnNavigate,
|
|
81
|
+
lazyFetching,
|
|
82
|
+
loading
|
|
83
|
+
) {
|
|
69
84
|
if (empty(viewDates)) {
|
|
70
85
|
return;
|
|
71
86
|
}
|
|
@@ -80,7 +95,8 @@ function load(sources, defaultResult, parseResult, applyResult, activeRange, fet
|
|
|
80
95
|
!lazyFetching ||
|
|
81
96
|
!fetchedRange.start ||
|
|
82
97
|
fetchedRange.start > activeRange.start ||
|
|
83
|
-
fetchedRange.end < activeRange.end
|
|
98
|
+
fetchedRange.end < activeRange.end ||
|
|
99
|
+
fetchedRange.timeZone !== timeZone
|
|
84
100
|
)
|
|
85
101
|
) {
|
|
86
102
|
let result = [];
|
|
@@ -103,7 +119,8 @@ function load(sources, defaultResult, parseResult, applyResult, activeRange, fet
|
|
|
103
119
|
start: toLocalDate(activeRange.start),
|
|
104
120
|
end: toLocalDate(activeRange.end),
|
|
105
121
|
startStr,
|
|
106
|
-
endStr
|
|
122
|
+
endStr,
|
|
123
|
+
timeZone
|
|
107
124
|
} : {}, success, failure);
|
|
108
125
|
if (result !== undefined) {
|
|
109
126
|
Promise.resolve(result).then(success, failure);
|
|
@@ -115,6 +132,9 @@ function load(sources, defaultResult, parseResult, applyResult, activeRange, fet
|
|
|
115
132
|
if (refetchOnNavigate) {
|
|
116
133
|
params.start = startStr;
|
|
117
134
|
params.end = endStr;
|
|
135
|
+
if (timeZone !== 'local') {
|
|
136
|
+
params.timeZone = timeZone;
|
|
137
|
+
}
|
|
118
138
|
}
|
|
119
139
|
params = new URLSearchParams(params);
|
|
120
140
|
// Prepare fetch
|
|
@@ -135,7 +155,7 @@ function load(sources, defaultResult, parseResult, applyResult, activeRange, fet
|
|
|
135
155
|
}
|
|
136
156
|
}
|
|
137
157
|
// Save current range for future requests
|
|
138
|
-
assign(fetchedRange, activeRange);
|
|
158
|
+
assign(fetchedRange, {...activeRange, timeZone});
|
|
139
159
|
}
|
|
140
160
|
}
|
|
141
161
|
|
|
@@ -155,9 +175,12 @@ export function createLoadingInvoker(mainState) {
|
|
|
155
175
|
|
|
156
176
|
export function setNowAndToday(mainState) {
|
|
157
177
|
return () => {
|
|
178
|
+
// Dependencies
|
|
179
|
+
let {offset} = mainState;
|
|
180
|
+
|
|
158
181
|
// Now and today
|
|
159
182
|
let interval = setInterval(() => {
|
|
160
|
-
let now = createDate();
|
|
183
|
+
let now = createDate(undefined, offset);
|
|
161
184
|
let today = setMidnight(cloneDate(now));
|
|
162
185
|
mainState.now = now;
|
|
163
186
|
if (!datesEqual(mainState.today, today)) {
|
|
@@ -169,6 +192,38 @@ export function setNowAndToday(mainState) {
|
|
|
169
192
|
}
|
|
170
193
|
}
|
|
171
194
|
|
|
195
|
+
export function handleTimeZoneChange(mainState) {
|
|
196
|
+
return () => {
|
|
197
|
+
// Dependencies
|
|
198
|
+
let {offset, options} = mainState;
|
|
199
|
+
|
|
200
|
+
untrack(() => {
|
|
201
|
+
// Update events
|
|
202
|
+
for (let event of mainState.events) {
|
|
203
|
+
if (!event.allDay) {
|
|
204
|
+
for (let prop of ['start', 'end']) {
|
|
205
|
+
let dateOffset = getOffset(event[prop]);
|
|
206
|
+
// Dates parsed from strings with no timezone info have dateOffset === undefined;
|
|
207
|
+
// they are treated as floating and only get branded with the new offset, not shifted
|
|
208
|
+
if (dateOffset !== undefined) {
|
|
209
|
+
applyOffsetDiff(event[prop], offset - dateOffset);
|
|
210
|
+
}
|
|
211
|
+
setOffset(event[prop], offset);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Update date option
|
|
216
|
+
let dateOffset = getOffset(options.date);
|
|
217
|
+
if (dateOffset !== undefined) {
|
|
218
|
+
let diff = createDate(undefined, offset).getUTCDay() - createDate(undefined, dateOffset).getUTCDay();
|
|
219
|
+
let date = addDay(cloneDate(options.date), diff);
|
|
220
|
+
mainState.setOption('date', date);
|
|
221
|
+
}
|
|
222
|
+
setOffset(options.date, offset);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
172
227
|
export function runDatesSet(mainState) {
|
|
173
228
|
return () => {
|
|
174
229
|
// Dependencies
|
package/src/storage/options.js
CHANGED
|
@@ -94,6 +94,7 @@ function createOptions(plugins) {
|
|
|
94
94
|
weekdays: ['ec-sun', 'ec-mon', 'ec-tue', 'ec-wed', 'ec-thu', 'ec-fri', 'ec-sat'],
|
|
95
95
|
weekNumber: 'ec-week-number'
|
|
96
96
|
},
|
|
97
|
+
timeZone: 'local',
|
|
97
98
|
titleFormat: {
|
|
98
99
|
year: 'numeric',
|
|
99
100
|
month: 'short',
|
|
@@ -173,9 +174,7 @@ export function optionsState(plugins, userOptions) {
|
|
|
173
174
|
// Set up option setters and delete unknown options
|
|
174
175
|
for (let key of keys(opts)) {
|
|
175
176
|
if (hasOwn(options, key)) {
|
|
176
|
-
|
|
177
|
-
setters[key] = [];
|
|
178
|
-
}
|
|
177
|
+
setters[key] ??= [];
|
|
179
178
|
setters[key].push(
|
|
180
179
|
specialOptions.includes(key)
|
|
181
180
|
? value => opts[key] = isFunction(value) ? value(defOpts[key]) : value
|
|
@@ -2,10 +2,13 @@ import {SvelteMap} from 'svelte/reactivity';
|
|
|
2
2
|
import {cloneDate, createDate, identity, intl, intlRange, isArray, setMidnight} from '#lib';
|
|
3
3
|
import {optionsState} from './options.js';
|
|
4
4
|
import {
|
|
5
|
-
createLoadingInvoker, loadEvents, loadResources, runDatesSet, runEventAllUpdated,
|
|
5
|
+
createLoadingInvoker, handleTimeZoneChange, loadEvents, loadResources, runDatesSet, runEventAllUpdated,
|
|
6
|
+
runViewDidMount, setNowAndToday,
|
|
6
7
|
switchView
|
|
7
8
|
} from './effects.js';
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
activeRange, currentRange, filteredEvents, offset, view, viewDates, viewTitle
|
|
11
|
+
} from './derived.js';
|
|
9
12
|
import {arrayProxy} from './proxy.svelte.js';
|
|
10
13
|
|
|
11
14
|
export default class State {
|
|
@@ -20,13 +23,14 @@ export default class State {
|
|
|
20
23
|
|
|
21
24
|
// Create other states
|
|
22
25
|
this.auxComponents = $state([]);
|
|
26
|
+
this.offset = $derived.by(offset(this));
|
|
23
27
|
this.currentRange = $derived.by(currentRange(this));
|
|
24
28
|
this.activeRange = $derived.by(activeRange(this));
|
|
25
29
|
this.fetchedRange = $state({events: {}, resources: {}});
|
|
26
30
|
this.events = $state.raw(arrayProxy(this.options.events));
|
|
27
31
|
this.filteredEvents = $derived.by(filteredEvents(this));
|
|
28
32
|
this.mainEl = $state();
|
|
29
|
-
this.now = $state(createDate());
|
|
33
|
+
this.now = $state(createDate(undefined, this.offset));
|
|
30
34
|
this.resources = $state.raw(arrayProxy(isArray(this.options.resources) ? this.options.resources : []));
|
|
31
35
|
this.today = $state(setMidnight(cloneDate(this.now)));
|
|
32
36
|
this.intlEventTime = $derived.by(intlRange(this, 'eventTimeFormat'));
|
|
@@ -56,6 +60,7 @@ export default class State {
|
|
|
56
60
|
#initEffects() {
|
|
57
61
|
let loading = createLoadingInvoker(this);
|
|
58
62
|
$effect.pre(switchView(this));
|
|
63
|
+
$effect.pre(handleTimeZoneChange(this));
|
|
59
64
|
$effect.pre(setNowAndToday(this));
|
|
60
65
|
$effect(loadEvents(this, loading));
|
|
61
66
|
$effect(loadResources(this, loading));
|