@event-calendar/core 1.5.0 → 2.0.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": "1.5.0",
3
+ "version": "2.0.0",
4
4
  "title": "Event Calendar Core package",
5
5
  "description": "Full-sized drag & drop event calendar with resource view",
6
6
  "keywords": [
@@ -27,6 +27,6 @@
27
27
  "./package.json": "./package.json"
28
28
  },
29
29
  "dependencies": {
30
- "svelte": "^3.59.1"
30
+ "svelte": "^4.1.1"
31
31
  }
32
32
  }
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  import {getContext} from 'svelte';
3
- import {createDate, cloneDate, subtractDay, addDuration, subtractDuration, setMidnight} from './lib.js';
3
+ import {createDate, cloneDate, subtractDay, addDuration, setContent, subtractDuration, setMidnight} from './lib.js';
4
4
 
5
5
  export let buttons;
6
6
 
@@ -26,16 +26,16 @@
26
26
  </script>
27
27
 
28
28
  {#each buttons as button}
29
- {#if button == ''}
30
- {:else if button == 'title'}
31
- <h2 class="{$theme.title}">{$_viewTitle}</h2>
29
+ {#if button == 'title'}
30
+ <!-- svelte-ignore a11y-missing-content -->
31
+ <h2 class="{$theme.title}" use:setContent={$_viewTitle}></h2>
32
32
  {:else if button == 'prev'}
33
- <button class="{$theme.button} ec-{button}" on:click={prev}><i class="{$theme.icon} ec-{button}"></i></button>
34
- {:else if button === 'next'}
35
- <button class="{$theme.button} ec-{button}" on:click={next}><i class="{$theme.icon} ec-{button}"></i></button>
36
- {:else if button === 'today'}
33
+ <button class="{$theme.button} ec-{button}" aria-label={$buttonText.prev} on:click={prev}><i class="{$theme.icon} ec-{button}"></i></button>
34
+ {:else if button == 'next'}
35
+ <button class="{$theme.button} ec-{button}" aria-label={$buttonText.next} on:click={next}><i class="{$theme.icon} ec-{button}"></i></button>
36
+ {:else if button == 'today'}
37
37
  <button class="{$theme.button} ec-{button}" on:click={() => $date = cloneDate(today)} disabled={isToday}>{$buttonText[button]}</button>
38
- {:else}
38
+ {:else if button != ''}
39
39
  <button class="{$theme.button}{$view === button ? ' ' + $theme.active : ''} ec-{button}" on:click={() => $view = button}>{$buttonText[button]}</button>
40
40
  {/if}
41
41
  {/each}
@@ -0,0 +1,30 @@
1
+
2
+ export function keyEnter(fn) {
3
+ return function (e) {
4
+ return e.key === 'Enter' || e.key === ' ' ? fn.call(this, e) : undefined;
5
+ };
6
+ }
7
+
8
+ export function btnTextDay(text) {
9
+ return btnText(text, 'day');
10
+ }
11
+
12
+ export function btnTextWeek(text) {
13
+ return btnText(text, 'week');
14
+ }
15
+
16
+ export function btnTextMonth(text) {
17
+ return btnText(text, 'month');
18
+ }
19
+
20
+ export function btnTextYear(text) {
21
+ return btnText(text, 'year');
22
+ }
23
+
24
+ function btnText(text, period) {
25
+ return {
26
+ ...text,
27
+ next: 'Next ' + period,
28
+ prev: 'Previous ' + period
29
+ };
30
+ }
@@ -1,18 +1,12 @@
1
- import {isObject} from './utils.js';
2
1
 
3
2
  export function setContent(node, content) {
4
3
  let actions = {
5
4
  update(content) {
6
- while (node.firstChild) {
7
- node.removeChild(node.lastChild);
8
- }
9
- if (!isObject(content)) {
5
+ if (typeof content == 'string') {
10
6
  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) {
7
+ } else if (content?.domNodes) {
8
+ node.replaceChildren(...content.domNodes);
9
+ } else if (content?.html) {
16
10
  node.innerHTML = content.html;
17
11
  }
18
12
  }
package/src/lib/date.js CHANGED
@@ -91,43 +91,6 @@ export function toISOString(date) {
91
91
  return date.toISOString().substring(0, 19);
92
92
  }
93
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
94
  export function datesEqual(date1, ...dates2) {
132
95
  return dates2.every(date2 => date1.getTime() === date2.getTime());
133
96
  }
@@ -151,6 +114,15 @@ export function noTimePart(date) {
151
114
  return typeof date === 'string' && date.length <= 10;
152
115
  }
153
116
 
117
+ /**
118
+ * Copy time from one date to another
119
+ */
120
+ export function copyTime(toDate, fromDate) {
121
+ toDate.setUTCHours(fromDate.getUTCHours(), fromDate.getUTCMinutes(), fromDate.getUTCSeconds(), 0);
122
+
123
+ return toDate;
124
+ }
125
+
154
126
  /**
155
127
  * Private functions
156
128
  */
@@ -177,43 +149,3 @@ function _fromISOString(str) {
177
149
  Number(parts[5] || 0)
178
150
  ));
179
151
  }
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
- }
package/src/lib/dom.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import {symbol} from './utils.js';
2
2
 
3
- export function createElement(tag, className, html, text) {
3
+ export function createElement(tag, className, content) {
4
4
  let el = document.createElement(tag);
5
5
  el.className = className;
6
- if (html) {
7
- el.innerHTML = html;
8
- } else if (text) {
9
- el.innerText = text;
6
+ if (typeof content == 'string') {
7
+ el.innerText = content;
8
+ } else if (content.domNodes) {
9
+ el.replaceChildren(...content.domNodes);
10
+ } else if (content.html) {
11
+ el.innerHTML = content.html;
10
12
  }
11
13
  return el;
12
14
  }
package/src/lib/events.js CHANGED
@@ -1,4 +1,4 @@
1
- import {addDay, datesEqual, createDate, cloneDate, setMidnight, toLocalDate, noTimePart} from './date';
1
+ import {addDay, datesEqual, createDate, cloneDate, setMidnight, toLocalDate, noTimePart, copyTime} from './date';
2
2
  import {createElement} from './dom';
3
3
  import {assign} from './utils';
4
4
  import {toViewWithLocalDates} from './view';
@@ -131,10 +131,14 @@ export function repositionEvent(chunk, longChunks, height) {
131
131
  }
132
132
 
133
133
  export function createEventContent(chunk, displayEventEnd, eventContent, theme, _intlEventTime, _view) {
134
- let timeText = _intlEventTime.format(chunk.start), content;
135
- if (displayEventEnd && chunk.event.display !== 'pointer') {
136
- timeText += ` - ${_intlEventTime.format(chunk.end)}`;
137
- }
134
+ let timeText = _intlEventTime.formatRange(
135
+ chunk.start,
136
+ displayEventEnd && chunk.event.display !== 'pointer'
137
+ ? copyTime(cloneDate(chunk.start), chunk.end) // make Intl.formatRange output only the time part
138
+ : chunk.start
139
+ );
140
+ let content;
141
+
138
142
  if (eventContent) {
139
143
  content = is_function(eventContent)
140
144
  ? eventContent({
@@ -150,14 +154,14 @@ export function createEventContent(chunk, displayEventEnd, eventContent, theme,
150
154
  break;
151
155
  case 'pointer':
152
156
  content = {
153
- domNodes: [createElement('div', theme.eventTime, null, timeText)]
157
+ domNodes: [createElement('div', theme.eventTime, timeText)]
154
158
  };
155
159
  break;
156
160
  default:
157
161
  content = {
158
162
  domNodes: [
159
- ...chunk.event.allDay ? [] : [createElement('div', theme.eventTime, null, timeText)],
160
- createElement('div', theme.eventTitle, chunk.event.titleHTML, chunk.event.title)
163
+ ...chunk.event.allDay ? [] : [createElement('div', theme.eventTime, timeText)],
164
+ createElement('div', theme.eventTitle, chunk.event.title)
161
165
  ]
162
166
  };
163
167
  }
package/src/lib/stores.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {derived, writable, get} from 'svelte/store';
2
2
  import {is_function} from 'svelte/internal';
3
- import {toLocalDate, formatRange} from './date';
3
+ import {toLocalDate} from './date';
4
4
 
5
5
  export function writable2(value, parser, start) {
6
6
  return {
7
- ...writable(parser ? parser(value) : value, start),
7
+ ...writable(value, start),
8
8
  parse: parser
9
9
  };
10
10
  }
@@ -43,12 +43,11 @@ export function intl(locale, format) {
43
43
 
44
44
  export function intlRange(locale, format) {
45
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);
46
+ let intl = is_function($format)
47
+ ? {formatRange: $format}
48
+ : new Intl.DateTimeFormat($locale, $format);
50
49
  return {
51
- format: (start, end) => formatRange(toLocalDate(start), toLocalDate(end), intl)
50
+ formatRange: (start, end) => intl.formatRange(toLocalDate(start), toLocalDate(end))
52
51
  };
53
52
  });
54
53
  }
package/src/lib/utils.js CHANGED
@@ -2,6 +2,10 @@ export function assign(...args) {
2
2
  return Object.assign(...args);
3
3
  }
4
4
 
5
+ export function keys(object) {
6
+ return Object.keys(object);
7
+ }
8
+
5
9
  export function floor(value) {
6
10
  return Math.floor(value);
7
11
  }
@@ -14,10 +18,6 @@ export function max(...args) {
14
18
  return Math.max(...args);
15
19
  }
16
20
 
17
- export function isObject(test) {
18
- return typeof test === 'object' && test !== null;
19
- }
20
-
21
21
  export function symbol() {
22
22
  return Symbol('ec');
23
23
  }
package/src/lib.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './lib/a11y.js';
1
2
  export * from './lib/actions';
2
3
  export * from './lib/date';
3
4
  export * from './lib/debounce';
@@ -12,16 +12,20 @@ import {
12
12
  viewTitle,
13
13
  view as view2 // hack to avoid a runtime error in SvelteKit dev mode (ReferenceError: view is not defined)
14
14
  } from './stores';
15
- import {assign, writable2, intl, intlRange} from '../lib.js';
15
+ import {assign, keys, writable2, intl, intlRange} from '../lib.js';
16
16
 
17
17
  export default class {
18
18
  constructor(plugins, input) {
19
19
  plugins = plugins || [];
20
20
 
21
21
  // Create options
22
- let options = createOptions(plugins);
22
+ let options= createOptions(plugins);
23
23
  let parsers = createParsers(options, plugins);
24
24
 
25
+ // Parse options
26
+ options = parseOpts(options, parsers);
27
+ input = parseOpts(input, parsers);
28
+
25
29
  // Create stores for options
26
30
  for (let [option, value] of Object.entries(options)) {
27
31
  this[option] = writable2(value, parsers[option]);
@@ -37,10 +41,10 @@ export default class {
37
41
  this._events = events(this);
38
42
  this._now = now();
39
43
  this._today = today(this);
40
- this._intlEventTime = intl(this.locale, this.eventTimeFormat);
44
+ this._intlEventTime = intlRange(this.locale, this.eventTimeFormat);
41
45
  this._intlSlotLabel = intl(this.locale, this.slotLabelFormat);
42
46
  this._intlDayHeader = intl(this.locale, this.dayHeaderFormat);
43
- this._titleIntlRange = intlRange(this.locale, this.titleFormat);
47
+ this._intlTitle = intlRange(this.locale, this.titleFormat);
44
48
  this._bodyEl = writable(undefined);
45
49
  this._scrollable = writable(false);
46
50
  this._viewTitle = viewTitle(this);
@@ -68,13 +72,9 @@ export default class {
68
72
  }
69
73
 
70
74
  // Set options for each view
71
- let commonOpts = assign({}, options, input);
72
- parseOpts(commonOpts, this);
73
- let views = new Set([...Object.keys(options.views), ...Object.keys(input.views || {})]);
75
+ let views = new Set([...keys(options.views), ...keys(input.views ?? {})]);
74
76
  for (let view of views) {
75
- let viewOpts = assign({}, options.views[view] || {}, input.views && input.views[view] || {});
76
- parseOpts(viewOpts, this);
77
- let opts = assign({}, commonOpts, viewOpts);
77
+ let opts = assign({}, options, options.views[view] ?? {}, input, input.views?.[view] ?? {});
78
78
  // Change view component when view changes
79
79
  this.view.subscribe(newView => {
80
80
  if (newView === view) {
@@ -85,7 +85,7 @@ export default class {
85
85
  }
86
86
  });
87
87
  // Process options
88
- for (let key of Object.keys(opts)) {
88
+ for (let key of keys(opts)) {
89
89
  if (this.hasOwnProperty(key) && key[0] !== '_') {
90
90
  let {set, _set, ...rest} = this[key];
91
91
 
@@ -113,12 +113,15 @@ export default class {
113
113
  }
114
114
  }
115
115
 
116
- function parseOpts(opts, state) {
117
- for (let key of Object.keys(opts)) {
118
- if (state.hasOwnProperty(key) && key[0] !== '_') {
119
- if (state[key].parse) {
120
- opts[key] = state[key].parse(opts[key]);
121
- }
116
+ function parseOpts(opts, parsers) {
117
+ let result = {};
118
+ for (let key of keys(opts)) {
119
+ result[key] = parsers[key] ? parsers[key](opts[key]) : opts[key];
120
+ }
121
+ if (opts.views) {
122
+ for (let view of keys(opts.views)) {
123
+ result.views[view] = parseOpts(opts.views[view], parsers);
122
124
  }
123
125
  }
126
+ return result;
124
127
  }
@@ -20,7 +20,7 @@ import {
20
20
  } from '../lib.js';
21
21
 
22
22
  export function monthMode(state) {
23
- return derived(state.view, $view => $view.startsWith?.('dayGrid'));
23
+ return derived(state.view, $view => $view?.startsWith('dayGrid'));
24
24
  }
25
25
 
26
26
  export function activeRange(state) {
@@ -93,11 +93,11 @@ export function viewDates(state) {
93
93
 
94
94
  export function viewTitle(state) {
95
95
  return derived(
96
- [state.date, state._activeRange, state._titleIntlRange, state._monthMode],
97
- ([$date, $_activeRange, $_titleIntlRange, $_monthMode]) => {
96
+ [state.date, state._activeRange, state._intlTitle, state._monthMode],
97
+ ([$date, $_activeRange, $_intlTitle, $_monthMode]) => {
98
98
  return $_monthMode
99
- ? $_titleIntlRange.format($date, $date)
100
- : $_titleIntlRange.format($_activeRange.start, subtractDay(cloneDate($_activeRange.end)));
99
+ ? $_intlTitle.formatRange($date, $date)
100
+ : $_intlTitle.formatRange($_activeRange.start, subtractDay(cloneDate($_activeRange.end)));
101
101
  }
102
102
  );
103
103
  }