@event-calendar/core 1.5.1 → 2.1.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.1",
3
+ "version": "2.1.0",
4
4
  "title": "Event Calendar Core package",
5
5
  "description": "Full-sized drag & drop event calendar with resource view",
6
6
  "keywords": [
@@ -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
 
@@ -27,14 +27,32 @@
27
27
 
28
28
  {#each buttons as button}
29
29
  {#if button == 'title'}
30
- <h2 class="{$theme.title}">{$_viewTitle}</h2>
30
+ <!-- svelte-ignore a11y-missing-content -->
31
+ <h2 class="{$theme.title}" use:setContent={$_viewTitle}></h2>
31
32
  {:else if button == 'prev'}
32
- <button class="{$theme.button} ec-{button}" aria-label={$buttonText.prev} on:click={prev}><i class="{$theme.icon} ec-{button}"></i></button>
33
+ <button
34
+ class="{$theme.button} ec-{button}"
35
+ aria-label={$buttonText.prev}
36
+ title={$buttonText.prev}
37
+ on:click={prev}
38
+ ><i class="{$theme.icon} ec-{button}"></i></button>
33
39
  {:else if button == 'next'}
34
- <button class="{$theme.button} ec-{button}" aria-label={$buttonText.next} on:click={next}><i class="{$theme.icon} ec-{button}"></i></button>
40
+ <button
41
+ class="{$theme.button} ec-{button}"
42
+ aria-label={$buttonText.next}
43
+ title={$buttonText.next}
44
+ on:click={next}
45
+ ><i class="{$theme.icon} ec-{button}"></i></button>
35
46
  {:else if button == 'today'}
36
- <button class="{$theme.button} ec-{button}" on:click={() => $date = cloneDate(today)} disabled={isToday}>{$buttonText[button]}</button>
47
+ <button
48
+ class="{$theme.button} ec-{button}"
49
+ on:click={() => $date = cloneDate(today)}
50
+ disabled={isToday}
51
+ >{$buttonText[button]}</button>
37
52
  {:else if button != ''}
38
- <button class="{$theme.button}{$view === button ? ' ' + $theme.active : ''} ec-{button}" on:click={() => $view = button}>{$buttonText[button]}</button>
53
+ <button
54
+ class="{$theme.button}{$view === button ? ' ' + $theme.active : ''} ec-{button}"
55
+ on:click={() => $view = button}
56
+ >{$buttonText[button]}</button>
39
57
  {/if}
40
58
  {/each}
@@ -26,7 +26,7 @@
26
26
  let state = new State(plugins, options);
27
27
  setContext('state', state);
28
28
 
29
- let {_viewComponent, _viewClass, _bodyEl, _interaction, _iClass, _events, _queue, _scrollable,
29
+ let {_viewComponent, _bodyEl, _interaction, _iClass, _events, _queue, _scrollable,
30
30
  events, eventSources, height, theme} = state;
31
31
 
32
32
  // Reactively update options that did change
@@ -130,7 +130,7 @@
130
130
  </script>
131
131
 
132
132
  <div
133
- class="{$theme.calendar}{$_viewClass ? ' ' + $theme[$_viewClass] : ''}{$_scrollable ? ' ' + $theme.withScroll : ''}{$_iClass ? ' ' + $theme[$_iClass] : ''}"
133
+ class="{$theme.calendar} {$theme.view}{$_scrollable ? ' ' + $theme.withScroll : ''}{$_iClass ? ' ' + $theme[$_iClass] : ''}"
134
134
  style="height: {$height}"
135
135
  >
136
136
  <Toolbar/>
package/src/index.scss CHANGED
@@ -184,7 +184,7 @@
184
184
  border-top: none;
185
185
  }
186
186
 
187
- .ec-month & {
187
+ .ec-day-grid & {
188
188
  flex: 1 1 auto;
189
189
  }
190
190
  }
@@ -215,12 +215,12 @@
215
215
  .ec-content {
216
216
  display: flex;
217
217
 
218
- .ec-month & {
218
+ .ec-day-grid & {
219
219
  flex-direction: column;
220
220
  height: 100%;
221
221
  }
222
222
 
223
- .ec-month .ec-uniform & {
223
+ .ec-day-grid .ec-uniform & {
224
224
  overflow: hidden; // remove scrolling due to hidden events
225
225
  }
226
226
 
@@ -241,12 +241,12 @@
241
241
  border-bottom: none;
242
242
  }
243
243
 
244
- .ec-month &,
244
+ .ec-day-grid &,
245
245
  .ec-resource & {
246
246
  flex: 1 0 auto;
247
247
  }
248
248
 
249
- .ec-month .ec-uniform & {
249
+ .ec-day-grid .ec-uniform & {
250
250
  flex: 1 1 0%; // % is required to work properly for both auto and fixed calendar height
251
251
  min-height: 0;
252
252
  }
@@ -263,16 +263,16 @@
263
263
  background-color: #e5f7fe;
264
264
  }
265
265
 
266
- .ec-month .ec-body & {
266
+ .ec-day-grid .ec-body & {
267
267
  min-height: 5em;
268
268
  position: relative;
269
269
  }
270
270
 
271
- .ec-month .ec-uniform & {
271
+ .ec-day-grid .ec-uniform & {
272
272
  min-height: 0;
273
273
  }
274
274
 
275
- .ec-month &:first-child {
275
+ .ec-day-grid &:first-child {
276
276
  border-left: none;
277
277
  }
278
278
 
@@ -296,7 +296,7 @@
296
296
  }
297
297
  }
298
298
 
299
- .ec-month {
299
+ .ec-day-grid {
300
300
  .ec-day-head {
301
301
  text-align: right;
302
302
  padding: 4px 4px 3px;
@@ -328,7 +328,7 @@
328
328
  .ec-events {
329
329
  margin: 0 6px 0 0;
330
330
 
331
- .ec-week &,
331
+ .ec-time-grid &,
332
332
  &.ec-preview {
333
333
  position: relative;
334
334
  }
@@ -346,12 +346,12 @@
346
346
  line-height: 1.5;
347
347
  z-index: 1; // put it above the pointer event (for multi-day events in month view)
348
348
 
349
- .ec-month &,
349
+ .ec-day-grid &,
350
350
  .ec-all-day & {
351
351
  position: relative;
352
352
  }
353
353
 
354
- .ec-week .ec-body & {
354
+ .ec-time-grid .ec-body & {
355
355
  position: absolute;
356
356
  }
357
357
 
@@ -390,7 +390,7 @@
390
390
  flex-direction: column;
391
391
  width: 100%;
392
392
 
393
- .ec-month &,
393
+ .ec-day-grid &,
394
394
  .ec-all-day & {
395
395
  flex-direction: row;
396
396
  }
@@ -408,7 +408,7 @@
408
408
  margin: 0 0 1px 0;
409
409
  flex-shrink: 0;
410
410
 
411
- .ec-month & {
411
+ .ec-day-grid & {
412
412
  margin: 0 3px 0 0;
413
413
  max-width: 100%;
414
414
  text-overflow: ellipsis;
@@ -418,14 +418,14 @@
418
418
  .ec-event-title {
419
419
  overflow: hidden;
420
420
 
421
- .ec-month &,
421
+ .ec-day-grid &,
422
422
  .ec-all-day & {
423
423
  min-height: 1.5em;
424
424
  white-space: nowrap;
425
425
  text-overflow: ellipsis;
426
426
  }
427
427
 
428
- .ec-week .ec-body & {
428
+ .ec-time-grid .ec-body & {
429
429
  position: sticky;
430
430
  top: 0;
431
431
  }
@@ -557,7 +557,7 @@
557
557
  position: absolute;
558
558
  user-select: none;
559
559
 
560
- .ec-month &,
560
+ .ec-day-grid &,
561
561
  .ec-all-day & {
562
562
  top: 0;
563
563
  right: 0;
@@ -567,7 +567,7 @@
567
567
  cursor: ew-resize;
568
568
  }
569
569
 
570
- .ec-week .ec-body & {
570
+ .ec-time-grid .ec-body & {
571
571
  left: 0;
572
572
  right: 0;
573
573
  bottom: 0;
package/src/lib/a11y.js CHANGED
@@ -4,27 +4,3 @@ export function keyEnter(fn) {
4
4
  return e.key === 'Enter' || e.key === ' ' ? fn.call(this, e) : undefined;
5
5
  };
6
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
  }
@@ -0,0 +1,28 @@
1
+
2
+ export function btnTextDay(text) {
3
+ return btnText(text, 'day');
4
+ }
5
+
6
+ export function btnTextWeek(text) {
7
+ return btnText(text, 'week');
8
+ }
9
+
10
+ export function btnTextMonth(text) {
11
+ return btnText(text, 'month');
12
+ }
13
+
14
+ export function btnTextYear(text) {
15
+ return btnText(text, 'year');
16
+ }
17
+
18
+ function btnText(text, period) {
19
+ return {
20
+ ...text,
21
+ next: 'Next ' + period,
22
+ prev: 'Previous ' + period
23
+ };
24
+ }
25
+
26
+ export function themeView(view) {
27
+ return theme => ({...theme, view});
28
+ }
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
@@ -4,6 +4,7 @@ export * from './lib/date';
4
4
  export * from './lib/debounce';
5
5
  export * from './lib/dom';
6
6
  export * from './lib/events';
7
+ export * from './lib/options';
7
8
  export * from './lib/stores';
8
9
  export * from './lib/utils';
9
10
  export * from './lib/view';
@@ -1,5 +1,4 @@
1
1
  import {assign, createDate, createDuration, setMidnight, createEvents, createEventSources} from '../lib.js';
2
- import {is_function} from 'svelte/internal';
3
2
 
4
3
  export function createOptions(plugins) {
5
4
  let options = {
@@ -92,7 +91,7 @@ export function createOptions(plugins) {
92
91
  time: 'ec-time',
93
92
  title: 'ec-title',
94
93
  toolbar: 'ec-toolbar',
95
- week: 'ec-week',
94
+ view: '',
96
95
  withScroll: 'ec-with-scroll'
97
96
  },
98
97
  titleFormat: {
@@ -112,9 +111,8 @@ export function createOptions(plugins) {
112
111
  return options;
113
112
  }
114
113
 
115
- export function createParsers(options, plugins) {
114
+ export function createParsers(plugins) {
116
115
  let parsers = {
117
- buttonText: input => is_function(input) ? input(options.buttonText) : input,
118
116
  date: date => setMidnight(createDate(date)),
119
117
  duration: createDuration,
120
118
  events: createEvents,
@@ -124,12 +122,11 @@ export function createParsers(options, plugins) {
124
122
  scrollTime: createDuration,
125
123
  slotDuration: createDuration,
126
124
  slotMaxTime: createDuration,
127
- slotMinTime: createDuration,
128
- theme: input => is_function(input) ? input(options.theme) : input
125
+ slotMinTime: createDuration
129
126
  };
130
127
 
131
128
  for (let plugin of plugins) {
132
- plugin.createParsers?.(parsers, options);
129
+ plugin.createParsers?.(parsers);
133
130
  }
134
131
 
135
132
  return parsers;
@@ -4,15 +4,15 @@ import {createOptions, createParsers} from './options';
4
4
  import {
5
5
  activeRange,
6
6
  currentRange,
7
+ dayGrid,
7
8
  events,
8
- monthMode,
9
9
  now,
10
10
  today,
11
11
  viewDates,
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 {keys, writable2, intl, intlRange} from '../lib.js';
16
16
 
17
17
  export default class {
18
18
  constructor(plugins, input) {
@@ -20,7 +20,11 @@ export default class {
20
20
 
21
21
  // Create options
22
22
  let options = createOptions(plugins);
23
- let parsers = createParsers(options, plugins);
23
+ let parsers = createParsers(plugins);
24
+
25
+ // Parse options
26
+ options = parseOpts(options, parsers);
27
+ input = parseOpts(input, parsers);
24
28
 
25
29
  // Create stores for options
26
30
  for (let [option, value] of Object.entries(options)) {
@@ -30,23 +34,22 @@ export default class {
30
34
  // Private stores
31
35
  this._queue = writable(new Map()); // debounce queue
32
36
  this._auxiliary = writable([]); // auxiliary components
33
- this._monthMode = monthMode(this);
37
+ this._dayGrid = dayGrid(this);
34
38
  this._currentRange = currentRange(this);
35
39
  this._activeRange = activeRange(this);
36
40
  this._fetchedRange = writable({start: undefined, end: undefined});
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);
47
51
  this._viewDates = viewDates(this);
48
52
  this._view = view2(this);
49
- this._viewClass = writable(undefined);
50
53
  this._viewComponent = writable(undefined);
51
54
  // Resources
52
55
  this._resBgColor = writable(noop);
@@ -68,13 +71,9 @@ export default class {
68
71
  }
69
72
 
70
73
  // 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 || {})]);
74
+ let views = new Set([...keys(options.views), ...keys(input.views ?? {})]);
74
75
  for (let view of views) {
75
- let viewOpts = assign({}, options.views[view] ?? {}, input.views?.[view] ?? {});
76
- parseOpts(viewOpts, this);
77
- let opts = assign({}, commonOpts, viewOpts);
76
+ let opts = mergeOpts(options, options.views[view] ?? {}, input, input.views?.[view] ?? {});
78
77
  // Change view component when view changes
79
78
  this.view.subscribe(newView => {
80
79
  if (newView === view) {
@@ -85,7 +84,7 @@ export default class {
85
84
  }
86
85
  });
87
86
  // Process options
88
- for (let key of Object.keys(opts)) {
87
+ for (let key of keys(opts)) {
89
88
  if (this.hasOwnProperty(key) && key[0] !== '_') {
90
89
  let {set, _set, ...rest} = this[key];
91
90
 
@@ -113,12 +112,26 @@ export default class {
113
112
  }
114
113
  }
115
114
 
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
- }
115
+ function parseOpts(opts, parsers) {
116
+ let result = {};
117
+ for (let key of keys(opts)) {
118
+ result[key] = parsers[key] ? parsers[key](opts[key]) : opts[key];
119
+ }
120
+ if (opts.views) {
121
+ for (let view of keys(opts.views)) {
122
+ result.views[view] = parseOpts(opts.views[view], parsers);
123
+ }
124
+ }
125
+ return result;
126
+ }
127
+
128
+ function mergeOpts(...args) {
129
+ let mergable = ['buttonText', 'theme'];
130
+ let result = {};
131
+ for (let opts of args) {
132
+ for (let key of keys(opts)) {
133
+ result[key] = mergable.includes(key) && is_function(opts[key]) ? opts[key](result[key]) : opts[key];
122
134
  }
123
135
  }
136
+ return result;
124
137
  }