@event-calendar/core 5.1.3 → 5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event-calendar/core",
3
- "version": "5.1.3",
3
+ "version": "5.2.0",
4
4
  "title": "Event Calendar Core package",
5
5
  "description": "Full-sized drag & drop event calendar with resource & timeline views",
6
6
  "keywords": [
@@ -2,7 +2,7 @@
2
2
  import './styles/index.css';
3
3
  import {setContext, untrack} from 'svelte';
4
4
  import {
5
- assign, cloneDate, createEvents, getElementWithPayload, getPayload, nextDate,
5
+ assign, cloneDate, createEvents, getElementWithPayload, getPayload, isArray, isDate, isPlainObject, nextDate,
6
6
  prevDate, toEventWithLocalDates, toLocalDate, toViewWithLocalDates
7
7
  } from '#lib';
8
8
  import MainState from './storage/state.svelte.js';
@@ -33,13 +33,19 @@
33
33
  });
34
34
 
35
35
  export function setOption(name, value) {
36
+ if (isPlainObject(value)) {
37
+ value = {...value};
38
+ }
39
+ if (isArray(value)) {
40
+ value = [...value];
41
+ }
36
42
  mainState.setOption(name, value, false);
37
43
  return this;
38
44
  }
39
45
 
40
46
  export function getOption(name) {
41
47
  let value = mainState.options[name];
42
- return value instanceof Date ? toLocalDate(value) : value;
48
+ return isDate(value) ? toLocalDate(value) : value;
43
49
  }
44
50
 
45
51
  export function refetchEvents() {
package/src/lib/chunks.js CHANGED
@@ -2,12 +2,17 @@ import {datesEqual} from './date.js';
2
2
  import {eventIntersects} from './events.js';
3
3
  import {assign} from './utils.js';
4
4
 
5
+ // Storage of unique event identifiers for generating chunk ids
6
+ const ids = new WeakMap();
7
+ let idCounter = 1;
8
+
5
9
  /**
6
10
  * @returns {{
11
+ * id: String, // this can be used as key in Svelte keyed each block
7
12
  * start: Date,
8
13
  * end: Date,
9
14
  * event: Object,
10
- * zeroDuration?: boolean,
15
+ * zeroDuration: boolean,
11
16
  * gridColumn?: Number,
12
17
  * gridRow?: Number,
13
18
  * group?: Object,
@@ -25,14 +30,25 @@ import {assign} from './utils.js';
25
30
  * }}
26
31
  */
27
32
  export function createEventChunk(event, start, end) {
28
- let chunk = {
29
- start: event.start > start ? event.start : start,
30
- end: event.end < end ? event.end : end,
31
- event
32
- };
33
- chunk.zeroDuration = datesEqual(chunk.start, chunk.end);
33
+ start = event.start > start ? event.start : start;
34
+ end = event.end < end ? event.end : end;
34
35
 
35
- return chunk;
36
+ return {
37
+ start,
38
+ end,
39
+ event,
40
+ get id() {
41
+ let id = ids.get(event);
42
+ if (!id) {
43
+ id = idCounter++;
44
+ ids.set(event, id);
45
+ }
46
+ delete this.id;
47
+ this.id = `${id}-${start.getTime()}`;
48
+ return this.id;
49
+ },
50
+ zeroDuration: datesEqual(start, end)
51
+ };
36
52
  }
37
53
 
38
54
  /**
@@ -92,7 +108,7 @@ export function prepareAllDayChunks(chunks) {
92
108
  }
93
109
  }
94
110
 
95
- export function repositionEvent(chunk, height, top = 0) {
111
+ export function repositionEvent(chunk, height, top = 1) {
96
112
  if (chunk.prev) {
97
113
  top = chunk.prev.bottom + 1;
98
114
  }
package/src/lib/date.js CHANGED
@@ -1,8 +1,10 @@
1
+ import {isDate} from './utils.js';
2
+
1
3
  export const DAY_IN_SECONDS = 86400;
2
4
 
3
5
  export function createDate(input = undefined) {
4
6
  if (input !== undefined) {
5
- return input instanceof Date ? _fromLocalDate(input) : _fromISOString(input);
7
+ return isDate(input) ? _fromLocalDate(input) : _fromISOString(input);
6
8
  }
7
9
 
8
10
  return _fromLocalDate(new Date());
@@ -18,7 +20,7 @@ export function createDuration(input) {
18
20
  seconds += parseInt(part, 10) * Math.pow(60, exp--);
19
21
  }
20
22
  input = {seconds};
21
- } else if (input instanceof Date) {
23
+ } else if (isDate(input)) {
22
24
  input = {hours: input.getUTCHours(), minutes: input.getUTCMinutes(), seconds: input.getUTCSeconds()};
23
25
  }
24
26
 
package/src/lib/utils.js CHANGED
@@ -10,10 +10,6 @@ export function entries(object) {
10
10
  return Object.entries(object);
11
11
  }
12
12
 
13
- export function hasOwn(object, property) {
14
- return Object.hasOwn(object, property);
15
- }
16
-
17
13
  export function floor(value) {
18
14
  return Math.floor(value);
19
15
  }
@@ -42,6 +38,18 @@ export function isFunction(value) {
42
38
  return typeof value === 'function';
43
39
  }
44
40
 
41
+ export function isPlainObject(value) {
42
+ if (typeof value !== 'object' || value === null) {
43
+ return false;
44
+ }
45
+ const prototype = Object.getPrototypeOf(value);
46
+ return prototype === null || prototype === Object.prototype;
47
+ }
48
+
49
+ export function isDate(value) {
50
+ return value instanceof Date;
51
+ }
52
+
45
53
  export function run(fn) {
46
54
  return fn();
47
55
  }
@@ -10,7 +10,7 @@
10
10
  let mainState = getContext('state');
11
11
  let viewState = getContext('view-state');
12
12
 
13
- let {options: {date, firstDay, moreLinkContent, theme, weekNumbers, weekNumberContent}} = $derived(mainState);
13
+ let {features, options: {date, firstDay, moreLinkContent, theme, weekNumbers, weekNumberContent}} = $derived(mainState);
14
14
  let {hiddenChunks, intlDayCell} = $derived(viewState);
15
15
 
16
16
  let {dayStart, disabled, highlight} = $derived(day);
@@ -59,10 +59,12 @@
59
59
 
60
60
  <BaseDay date={dayStart} allDay {classes} {disabled} {highlight} {noIeb} {noBeb}>
61
61
  <div class="{theme.dayHead}">
62
- <time
63
- datetime="{toISOString(dayStart, 10)}"
64
- {@attach contentFrom(intlDayCell.format(dayStart))}
65
- ></time>
62
+ {#if features.includes('dayNumber')}
63
+ <time
64
+ datetime="{toISOString(dayStart, 10)}"
65
+ {@attach contentFrom(intlDayCell.format(dayStart))}
66
+ ></time>
67
+ {/if}
66
68
  {#if showWeekNumber}
67
69
  <span
68
70
  class="{theme.weekNumber}"
@@ -9,7 +9,7 @@
9
9
  let {colsCount, gridEl, hiddenChunks, popupDay} = $derived(getContext('view-state'));
10
10
 
11
11
  let el = $state();
12
- let margin = $state(0);
12
+ let margin = $state(1);
13
13
  let hidden = $state(false);
14
14
 
15
15
  let event = $derived(chunk.event);
@@ -18,7 +18,7 @@
18
18
 
19
19
  $effect(() => {
20
20
  if (!inPopup) {
21
- margin = height(dayEl.firstElementChild);
21
+ margin = height(dayEl.firstElementChild) || 1;
22
22
  }
23
23
  });
24
24
 
@@ -44,7 +44,7 @@
44
44
  });
45
45
 
46
46
  export function reposition() {
47
- margin = repositionEvent(chunk, height(el), height(dayEl.firstElementChild));
47
+ margin = repositionEvent(chunk, height(el), height(dayEl.firstElementChild) || 1);
48
48
  }
49
49
 
50
50
  export function hide() {
@@ -62,8 +62,7 @@
62
62
  let top;
63
63
  if (popupRect.height >= gridRect.height) {
64
64
  top = gridRect.top - dayRect.top;
65
- let bottom = dayRect.bottom - gridRect.bottom;
66
- style += `inset-block-end:${bottom}px;`;
65
+ style += `block-size:${gridRect.height}px;`;
67
66
  } else {
68
67
  top = (dayRect.height - popupRect.height) / 2;
69
68
  if (dayRect.top + top < gridRect.top) {
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import {getContext, setContext} from 'svelte';
2
+ import {getContext, setContext, tick} from 'svelte';
3
3
  import {contentFrom, resizeObserver, runReposition} from '#lib';
4
4
  import ViewState from './state.svelte.js';
5
5
  import Day from './Day.svelte';
@@ -16,14 +16,16 @@
16
16
  // Events reposition
17
17
  let refs = [];
18
18
  function reposition() {
19
- hiddenChunks.clear();
20
19
  runReposition(refs, chunks);
20
+ hiddenChunks.clear();
21
+ tick().then(hide);
21
22
  }
22
- $effect(reposition);
23
- $effect(() => {
24
- chunks;
23
+ function hide() {
24
+ hiddenChunks.size;
25
25
  refs.forEach(ref => ref.hide());
26
- });
26
+ }
27
+ $effect(reposition);
28
+ $effect(hide);
27
29
  </script>
28
30
 
29
31
  <section
@@ -59,11 +61,11 @@
59
61
  {/each}
60
62
  </div>
61
63
  <div class="{theme.events}">
62
- {#each chunks as chunk, i}
64
+ {#each chunks as chunk, i (chunk.id)}
63
65
  <!-- svelte-ignore binding_property_non_reactive -->
64
66
  <Event bind:this={refs[i]} {chunk}/>
65
67
  {/each}
66
- {#each bgChunks as chunk}
68
+ {#each bgChunks as chunk (chunk.id)}
67
69
  <Event {chunk}/>
68
70
  {/each}
69
71
  {#each iChunks as chunk}
@@ -4,11 +4,11 @@ import {addDay, bgEvent, cloneDate, createAllDayChunks, datesEqual, outsideRange
4
4
  export function colsCount(mainState) {
5
5
  return () => {
6
6
  // Dependencies
7
- let {options: {hiddenDays}} = mainState;
7
+ let {viewDates, options: {duration, hiddenDays}} = mainState;
8
8
 
9
9
  let count;
10
10
 
11
- untrack(() => count = 7 - hiddenDays.length);
11
+ untrack(() => count = duration.months || duration.inWeeks ? 7 - hiddenDays.length : viewDates.length);
12
12
 
13
13
  return count;
14
14
  };
@@ -1,4 +1,4 @@
1
- import {assign, btnTextMonth, nextClosestDay, prevClosestDay, themeView} from '#lib';
1
+ import {assign, btnTextDay, btnTextMonth, btnTextWeek, nextClosestDay, prevClosestDay, themeView} from '#lib';
2
2
  import View from './View.svelte';
3
3
 
4
4
  export default {
@@ -14,7 +14,9 @@ export default {
14
14
  view: 'dayGridMonth'
15
15
  });
16
16
  assign(options.buttonText, {
17
+ dayGridDay: 'day',
17
18
  dayGridMonth: 'month',
19
+ dayGridWeek: 'week',
18
20
  close: 'Close'
19
21
  });
20
22
  assign(options.theme, {
@@ -25,9 +27,23 @@ export default {
25
27
  weekNumber: 'ec-week-number'
26
28
  });
27
29
  assign(options.views, {
30
+ dayGridDay: {
31
+ buttonText: btnTextDay,
32
+ component: () => View,
33
+ dayHeaderFormat: {weekday: 'long'},
34
+ displayEventEnd: false,
35
+ duration: {days: 1},
36
+ theme: themeView('ec-day-grid ec-day-view')
37
+ },
38
+ dayGridWeek: {
39
+ buttonText: btnTextWeek,
40
+ component: () => View,
41
+ displayEventEnd: false,
42
+ theme: themeView('ec-day-grid ec-week-view')
43
+ },
28
44
  dayGridMonth: {
29
45
  buttonText: btnTextMonth,
30
- component: initViewComponent,
46
+ component: initMonthViewComponent,
31
47
  dayHeaderFormat: {weekday: 'short'},
32
48
  dayHeaderAriaLabelFormat: {weekday: 'long'},
33
49
  displayEventEnd: false,
@@ -39,8 +55,8 @@ export default {
39
55
  }
40
56
  }
41
57
 
42
- function initViewComponent(mainState) {
43
- mainState.features = ['day-grid'];
58
+ function initMonthViewComponent(mainState) {
59
+ mainState.features = ['dayNumber'];
44
60
  mainState.extensions.activeRange = (start, end) => {
45
61
  // Dependencies
46
62
  let {options: {firstDay}} = mainState;
@@ -38,7 +38,7 @@
38
38
  <time {datetime} {@attach contentFrom(intlListDay.format(date))}></time>
39
39
  <time class="{theme.daySide}" {datetime} {@attach contentFrom(intlListDaySide.format(date))}></time>
40
40
  </h4>
41
- {#each chunks as chunk (chunk.event)}
41
+ {#each chunks as chunk (chunk.id)}
42
42
  <Event {chunk}/>
43
43
  {/each}
44
44
  </BaseDay>
@@ -27,24 +27,33 @@
27
27
  tick().then(scrollToTime);
28
28
  });
29
29
  function scrollToTime() {
30
- if (monthView) {
31
- return;
32
- }
33
30
  let scrollLeft = 0;
34
31
  let todayOutOfView = today < viewDates[0] || today > viewDates.at(-1);
35
- for (let date of viewDates) {
36
- let slotTimeLimits = getSlotTimeLimits(dayTimeLimits, date);
37
- if (todayOutOfView || datesEqual(date, today)) {
38
- scrollLeft += max(
39
- min(toSeconds(scrollTime), toSeconds(slotTimeLimits.max)) - toSeconds(slotTimeLimits.min),
40
- 0
41
- );
42
- break;
43
- } else {
44
- scrollLeft += toSeconds(slotTimeLimits.max) - toSeconds(slotTimeLimits.min);
32
+ if (monthView) {
33
+ if (!todayOutOfView) {
34
+ let days = grid[0];
35
+ for (let day of days) {
36
+ if (datesEqual(day.dayStart, today)) {
37
+ mainEl.scrollLeft = (mainEl.scrollWidth - sidebarWidth) / days.length * (day.gridColumn - 1) * (isRtl() ? -1 : 1);
38
+ break;
39
+ }
40
+ }
41
+ }
42
+ } else {
43
+ for (let date of viewDates) {
44
+ let slotTimeLimits = getSlotTimeLimits(dayTimeLimits, date);
45
+ if (todayOutOfView || datesEqual(date, today)) {
46
+ scrollLeft += max(
47
+ min(toSeconds(scrollTime), toSeconds(slotTimeLimits.max)) - toSeconds(slotTimeLimits.min),
48
+ 0
49
+ );
50
+ break;
51
+ } else {
52
+ scrollLeft += toSeconds(slotTimeLimits.max) - toSeconds(slotTimeLimits.min);
53
+ }
45
54
  }
55
+ mainEl.scrollLeft = scrollLeft / toSeconds(slotDuration) * slotWidth * (isRtl() ? -1 : 1);
46
56
  }
47
- mainEl.scrollLeft = scrollLeft / toSeconds(slotDuration) * slotWidth * (isRtl() ? -1 : 1);
48
57
  }
49
58
 
50
59
  // Events reposition
@@ -115,11 +124,11 @@
115
124
  {/each}
116
125
  </div>
117
126
  <div class="{theme.events}">
118
- {#each chunks as chunk, i}
127
+ {#each chunks as chunk, i (chunk.id)}
119
128
  <!-- svelte-ignore binding_property_non_reactive -->
120
129
  <Event bind:this={refs[i]} {chunk}/>
121
130
  {/each}
122
- {#each bgChunks as chunk}
131
+ {#each bgChunks as chunk (chunk.id)}
123
132
  <Event {chunk}/>
124
133
  {/each}
125
134
  {#each iChunks as chunk}
@@ -46,7 +46,7 @@ export function eventChunks(mainState, viewState) {
46
46
  return () => {
47
47
  // Dependencies
48
48
  let {filteredEvents} = mainState;
49
- let {grid} = viewState;
49
+ let {grid, monthView} = viewState;
50
50
 
51
51
  let chunks = [];
52
52
  let bgChunks = [];
@@ -55,9 +55,11 @@ export function eventChunks(mainState, viewState) {
55
55
  for (let event of filteredEvents) {
56
56
  for (let days of grid) {
57
57
  if (bgEvent(event.display)) {
58
- bgChunks = bgChunks.concat(createChunks(event, days));
58
+ if (!monthView || event.allDay) {
59
+ bgChunks = bgChunks.concat(createChunks(event, days, monthView));
60
+ }
59
61
  } else {
60
- chunks = chunks.concat(createChunks(event, days));
62
+ chunks = chunks.concat(createChunks(event, days, monthView));
61
63
  }
62
64
  }
63
65
  }
@@ -1,6 +1,6 @@
1
1
  import {assign, createDuration, createEventChunk, eventIntersects, max, min} from '#lib';
2
2
 
3
- export function createChunks(event, days) {
3
+ export function createChunks(event, days, monthView) {
4
4
  let dates = [];
5
5
  let firstStart;
6
6
  let lastEnd;
@@ -8,17 +8,31 @@ export function createChunks(event, days) {
8
8
  let gridRow;
9
9
  let left;
10
10
  let width = 0;
11
- for (let {gridColumn: column, gridRow: row, resource, dayStart, start, end, disabled} of days) {
12
- if (!disabled && eventIntersects(event, start, end, resource)) {
13
- if (!dates.length) {
14
- firstStart = start;
15
- gridColumn = column;
16
- gridRow = row;
17
- left = max(event.start - start, 0) / 1000;
11
+ for (let {gridColumn: column, gridRow: row, resource, dayStart, dayEnd, start, end, disabled} of days) {
12
+ if (!disabled) {
13
+ if (monthView) {
14
+ if (eventIntersects(event, dayStart, dayEnd, resource)) {
15
+ if (!dates.length) {
16
+ firstStart = dayStart;
17
+ gridColumn = column;
18
+ gridRow = row;
19
+ }
20
+ dates.push(dayStart);
21
+ lastEnd = end;
22
+ }
23
+ } else {
24
+ if (eventIntersects(event, start, end, resource)) {
25
+ if (!dates.length) {
26
+ firstStart = start;
27
+ gridColumn = column;
28
+ gridRow = row;
29
+ left = max(event.start - start, 0) / 1000;
30
+ }
31
+ dates.push(dayStart);
32
+ lastEnd = end;
33
+ width += (min(end, event.end) - max(start, event.start)) / 1000;
34
+ }
18
35
  }
19
- dates.push(dayStart);
20
- lastEnd = end;
21
- width += (min(end, event.end) - max(start, event.start)) / 1000;
22
36
  }
23
37
  }
24
38
  if (dates.length) {
@@ -8,11 +8,11 @@ export default class ViewState extends RRState(TRRState()) {
8
8
  this.dayTimeLimits = $derived.by(dayTimeLimits(mainState)); // flexible time limits per day
9
9
  this.daySlots = $derived.by(daySlots(mainState, this));
10
10
  this.grid = $derived.by(grid(mainState, this));
11
+ this.monthView = $derived.by(monthView(mainState));
11
12
  let {chunks, bgChunks} = $derived.by(eventChunks(mainState, this));
12
13
  this.chunks = $derived(chunks);
13
14
  this.bgChunks = $derived(bgChunks);
14
15
  this.iChunks = $derived.by(iEventChunks(mainState, this));
15
- this.monthView = $derived.by(monthView(mainState));
16
16
  this.nestedResources = $derived.by(nestedResources(mainState));
17
17
  }
18
18
  }
@@ -84,11 +84,11 @@
84
84
  {/each}
85
85
  </div>
86
86
  <div class="{theme.events}">
87
- {#each allDayChunks as chunk, i}
87
+ {#each allDayChunks as chunk, i (chunk.id)}
88
88
  <!-- svelte-ignore binding_property_non_reactive -->
89
89
  <AllDayEvent bind:this={refs[i]} {chunk}/>
90
90
  {/each}
91
- {#each allDayBgChunks as chunk}
91
+ {#each allDayBgChunks as chunk (chunk.id)}
92
92
  <AllDayEvent {chunk}/>
93
93
  {/each}
94
94
  {#each allDayIChunks as chunk}
@@ -121,10 +121,10 @@
121
121
  {/each}
122
122
  </div>
123
123
  <div class="{theme.events}">
124
- {#each chunks as chunk}
124
+ {#each chunks as chunk (chunk.id)}
125
125
  <Event {chunk}/>
126
126
  {/each}
127
- {#each bgChunks as chunk}
127
+ {#each bgChunks as chunk (chunk.id)}
128
128
  <Event {chunk}/>
129
129
  {/each}
130
130
  {#each iChunks as chunk}
@@ -1,6 +1,6 @@
1
1
  import {untrack} from 'svelte';
2
2
  import {
3
- addDay, addDuration, cloneDate, createView, isFunction, prevClosestDay, setMidnight, subtractDay,
3
+ addDay, addDuration, cloneDate, createView, isFunction, prevClosestDay, subtractDay,
4
4
  toEventWithLocalDates, toViewWithLocalDates
5
5
  } from '#lib';
6
6
 
@@ -91,8 +91,8 @@ export function viewDates(mainState) {
91
91
  let dates = [];
92
92
 
93
93
  untrack(() => {
94
- let date = setMidnight(cloneDate(activeRange.start));
95
- let end = setMidnight(cloneDate(activeRange.end));
94
+ let date = cloneDate(activeRange.start);
95
+ let end = cloneDate(activeRange.end);
96
96
  while (date < end) {
97
97
  if (!hiddenDays.includes(date.getUTCDay())) {
98
98
  dates.push(cloneDate(date));
@@ -114,14 +114,12 @@ export function viewDates(mainState) {
114
114
  export function viewTitle(mainState) {
115
115
  return () => {
116
116
  // Dependencies
117
- let {activeRange, intlTitle, features, options: {date}} = mainState;
117
+ let {currentRange, intlTitle} = mainState;
118
118
 
119
119
  let title;
120
120
 
121
121
  untrack(() => {
122
- title = features.includes('day-grid')
123
- ? intlTitle.formatRange(date, date)
124
- : intlTitle.formatRange(activeRange.start, subtractDay(cloneDate(activeRange.end)));
122
+ title = intlTitle.formatRange(currentRange.start, subtractDay(cloneDate(currentRange.end)));
125
123
  });
126
124
 
127
125
  return title;
@@ -143,7 +143,7 @@ export function runEventAllUpdated(mainState) {
143
143
  export function runViewDidMount(mainState) {
144
144
  return () => {
145
145
  // Dependencies
146
- let {viewComponent, options: {viewDidMount}} = mainState;
146
+ let {options: {view, viewDidMount}} = mainState;
147
147
 
148
148
  untrack(() => {
149
149
  if (isFunction(viewDidMount)) {
@@ -81,7 +81,10 @@
81
81
  display: flex;
82
82
  flex-direction: row-reverse;
83
83
  justify-content: space-between;
84
- padding: .375rem;
84
+
85
+ .ec-day-grid.ec-month-view & {
86
+ padding: .375rem;
87
+ }
85
88
 
86
89
  .ec-day.ec-other-month & time {
87
90
  opacity: .3;
@@ -2,10 +2,11 @@
2
2
  position: relative;
3
3
  display: flex;
4
4
  flex-direction: column;
5
+ box-sizing: border-box;
5
6
  block-size: max-content;
6
- inline-size: 110%;
7
- min-block-size: 6em;
8
- min-inline-size: 10em;
7
+ inline-size: 125%;
8
+ min-block-size: 8em;
9
+ min-inline-size: 12em;
9
10
  padding: .375rem .75rem .75rem;
10
11
  background-color: var(--ec-popup-bg-color);
11
12
  border: 1px solid var(--ec-border-color);
@@ -26,5 +27,7 @@
26
27
  .ec-events {
27
28
  --ec-event-col-gap: 0;
28
29
  display: block;
30
+ overflow-y: auto;
31
+ pointer-events: auto;
29
32
  }
30
33
  }