@event-calendar/core 5.3.2 → 5.4.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 CHANGED
@@ -5,7 +5,7 @@ See [demo](https://vkurko.github.io/calendar/) and [changelog](CHANGELOG.md).
5
5
  Full-sized drag & drop JavaScript event calendar with resource & timeline views:
6
6
 
7
7
  * Lightweight (35kb [br](https://en.wikipedia.org/wiki/Brotli) compressed)
8
- * 100% human-coded
8
+ * Feature-rich, performant, and with minimal DOM structure (thanks to [CSS Grid](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout))
9
9
  * Zero-dependency (standalone bundle)
10
10
  * Used on over 70,000 websites with [Bookly](https://wordpress.org/plugins/bookly-responsive-appointment-booking-tool/)
11
11
 
@@ -68,9 +68,9 @@ Inspired by [FullCalendar](https://fullcalendar.io/), it implements similar opti
68
68
  - [eventDragStart](#eventdragstart)
69
69
  - [eventDragStop](#eventdragstop)
70
70
  - [eventDrop](#eventdrop)
71
+ - [eventDurationEditable](#eventdurationeditable)
71
72
  </td><td>
72
73
 
73
- - [eventDurationEditable](#eventdurationeditable)
74
74
  - [eventFilter](#eventfilter)
75
75
  - [eventLongPressDelay](#eventlongpressdelay)
76
76
  - [eventMouseEnter](#eventmouseenter)
@@ -92,6 +92,7 @@ Inspired by [FullCalendar](https://fullcalendar.io/), it implements similar opti
92
92
  - [height](#height)
93
93
  - [hiddenDays](#hiddendays)
94
94
  - [highlightedDates](#highlighteddates)
95
+ - [icons](#icons)
95
96
  - [lazyFetching](#lazyfetching)
96
97
  - [listDayFormat](#listdayformat)
97
98
  - [listDaySideFormat](#listdaysideformat)
@@ -102,11 +103,12 @@ Inspired by [FullCalendar](https://fullcalendar.io/), it implements similar opti
102
103
  - [noEventsClick](#noeventsclick)
103
104
  - [noEventsContent](#noeventscontent)
104
105
  - [nowIndicator](#nowindicator)
106
+ - [pointer](#pointer)
105
107
  </td><td>
106
108
 
107
- - [pointer](#pointer)
108
109
  - [refetchResourcesOnNavigate](#refetchresourcesonnavigate)
109
110
  - [resizeConstraint](#resizeconstraint)
111
+ - [resourceExpand](#resourceexpand)
110
112
  - [resources](#resources)
111
113
  - [resourceLabelContent](#resourcelabelcontent)
112
114
  - [resourceLabelDidMount](#resourcelabeldidmount)
@@ -251,8 +253,8 @@ This bundle contains a version of the calendar that includes all plugins and is
251
253
 
252
254
  The first step is to include the following lines of code in the `<head>` section of your page:
253
255
  ```html
254
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@event-calendar/build@5.3.2/dist/event-calendar.min.css">
255
- <script src="https://cdn.jsdelivr.net/npm/@event-calendar/build@5.3.2/dist/event-calendar.min.js"></script>
256
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@event-calendar/build@5.4.0/dist/event-calendar.min.css">
257
+ <script src="https://cdn.jsdelivr.net/npm/@event-calendar/build@5.4.0/dist/event-calendar.min.js"></script>
256
258
  ```
257
259
 
258
260
  <details>
@@ -1829,6 +1831,31 @@ Array of dates that need to be highlighted in the calendar.
1829
1831
 
1830
1832
  Each date can be either an ISO8601 date string like `'2026-12-31'`, or a JavaScript Date object.
1831
1833
 
1834
+ ### icons
1835
+ - Type `object`
1836
+ - Default `{collapse: {html: '&minus;'}, expand: {html: '&plus;'}}`
1837
+
1838
+ Defines icons used in some buttons, such as those for expanding nested resources in `resourceTimeline` views.
1839
+
1840
+ Each icon is specified as a [Content](#content) value.
1841
+
1842
+ This option can be either a plain object with all necessary properties, or a callback function that receives default icons object and should return a new one:
1843
+
1844
+ ```js
1845
+ function (icons) {
1846
+ // return new icons object
1847
+ }
1848
+ ```
1849
+ <table>
1850
+ <tr>
1851
+ <td>
1852
+
1853
+ `icons`
1854
+ </td>
1855
+ <td>An object with default icons</td>
1856
+ </tr>
1857
+ </table>
1858
+
1832
1859
  ### lazyFetching
1833
1860
  - Type `boolean`
1834
1861
  - Default `true`
@@ -2029,6 +2056,47 @@ Callback function that limits the date/time range within which the event is allo
2029
2056
 
2030
2057
  The function is triggered during resizing for each cursor movement and takes the same parameters as [eventResize](#eventresize). The function should return `true` if the new size is allowed, and `false` otherwise.
2031
2058
 
2059
+ ### resourceExpand
2060
+ - Type `function`
2061
+ - Default `undefined`
2062
+
2063
+ Callback function that is triggered when a resource with nested children is expanded or collapsed in `resourceTimeline` views.
2064
+
2065
+
2066
+ ```js
2067
+ function (info) { }
2068
+ ```
2069
+ `info` is an object with the following properties:
2070
+ <table>
2071
+ <tr>
2072
+ <td>
2073
+
2074
+ `resource`
2075
+ </td>
2076
+ <td>
2077
+
2078
+ The associated [Resource](#resource-object) object
2079
+ </td>
2080
+ </tr>
2081
+ <tr>
2082
+ <td>
2083
+
2084
+ `jsEvent`
2085
+ </td>
2086
+ <td>JavaScript native event object with low-level information such as click coordinates</td>
2087
+ </tr>
2088
+ <tr>
2089
+ <td>
2090
+
2091
+ `view`
2092
+ </td>
2093
+ <td>
2094
+
2095
+ The current [View](#view-object) object
2096
+ </td>
2097
+ </tr>
2098
+ </table>
2099
+
2032
2100
  ### resources
2033
2101
  - Type `array`, `object` or `function`
2034
2102
  - Default `[]`
@@ -3263,6 +3331,16 @@ The title of the resource. See [Content](#content)
3263
3331
  <tr>
3264
3332
  <td>
3265
3333
 
3334
+ `expanded`
3335
+ </td>
3336
+ <td>
3337
+
3338
+ A flag indicating whether the resource is expanded or collapsed if it has nested children
3339
+ </td>
3340
+ </tr>
3341
+ <tr>
3342
+ <td>
3343
+
3266
3344
  `extendedProps`
3267
3345
  </td>
3268
3346
  <td>
@@ -3320,6 +3398,16 @@ Here are all admissible fields for the resource’s input object:
3320
3398
  <tr>
3321
3399
  <td>
3322
3400
 
3401
+ `expanded`
3402
+ </td>
3403
+ <td>
3404
+
3405
+ `boolean` Specifies whether the resource with nested children will be expanded or collapsed. Default `true`
3406
+ </td>
3407
+ </tr>
3408
+ <tr>
3409
+ <td>
3410
+
3323
3411
  `extendedProps`
3324
3412
  </td>
3325
3413
  <td>
@@ -3336,7 +3424,7 @@ Here are all admissible fields for the resource’s input object:
3336
3424
  </tr>
3337
3425
  </table>
3338
3426
 
3339
- The `timeline` views support displaying nested resources. Nested resources can be collapsed or expanded using an additional button that appears before the parent resource name. To pass nested resources, use the `children` field:
3427
+ The `resourceTimeline` views support displaying nested resources. Nested resources can be collapsed or expanded using an additional button that appears before the parent resource name. To pass nested resources, use the `children` field:
3340
3428
 
3341
3429
  ```js
3342
3430
  resources: [
package/dist/index.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * EventCalendar v5.3.2
2
+ * EventCalendar v5.4.0
3
3
  * https://github.com/vkurko/calendar
4
4
  */
5
5
  .ec {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * EventCalendar v5.3.2
2
+ * EventCalendar v5.4.0
3
3
  * https://github.com/vkurko/calendar
4
4
  */
5
5
  import { untrack, tick, getAbortSignal, getContext, setContext, onMount, mount, unmount } from "svelte";
@@ -679,10 +679,10 @@ function outsideRange(date, range) {
679
679
  }
680
680
  function createResources(input) {
681
681
  let result = [];
682
- _createResources(input, 0, result);
682
+ _createResources(input, 0, false, result);
683
683
  return result;
684
684
  }
685
- function _createResources(input, level, flat) {
685
+ function _createResources(input, level, hidden, flat) {
686
686
  let result = [];
687
687
  for (let item of input) {
688
688
  let resource = createResource(item);
@@ -691,12 +691,11 @@ function _createResources(input, level, flat) {
691
691
  let payload = {
692
692
  level,
693
693
  children: [],
694
- expanded: true,
695
- hidden: false
694
+ hidden
696
695
  };
697
696
  setPayload(resource, payload);
698
697
  if (item.children) {
699
- payload.children = _createResources(item.children, level + 1, flat);
698
+ payload.children = _createResources(item.children, level + 1, hidden || !resource.expanded, flat);
700
699
  }
701
700
  }
702
701
  return result;
@@ -707,6 +706,7 @@ function createResource(input) {
707
706
  title: input.title || "",
708
707
  eventBackgroundColor: eventBackgroundColor(input),
709
708
  eventTextColor: eventTextColor(input),
709
+ expanded: input.expanded ?? true,
710
710
  extendedProps: input.extendedProps ?? {}
711
711
  };
712
712
  }
@@ -793,24 +793,32 @@ function proxy(target, setDependency, hasEffect, invokeEffect) {
793
793
  },
794
794
  set(target2, prop, value, receiver) {
795
795
  let has = hasEffect(target2[prop], value);
796
- Reflect.set(target2, prop, value, receiver);
796
+ let result = Reflect.set(target2, prop, value, receiver);
797
797
  if (has) {
798
798
  invokeEffect(prop);
799
799
  }
800
- return true;
800
+ return result;
801
801
  }
802
802
  });
803
803
  }
804
804
  function createOptions(plugins) {
805
805
  let options = {
806
- buttonText: { today: "today" },
806
+ buttonText: {
807
+ today: "today"
808
+ },
807
809
  customButtons: {},
808
810
  customScrollbars: false,
809
811
  // ec option
810
812
  date: /* @__PURE__ */ new Date(),
811
813
  datesSet: void 0,
812
- dayHeaderFormat: { weekday: "short", month: "numeric", day: "numeric" },
813
- dayHeaderAriaLabelFormat: { dateStyle: "full" },
814
+ dayHeaderFormat: {
815
+ weekday: "short",
816
+ month: "numeric",
817
+ day: "numeric"
818
+ },
819
+ dayHeaderAriaLabelFormat: {
820
+ dateStyle: "full"
821
+ },
814
822
  displayEventEnd: true,
815
823
  duration: { weeks: 1 },
816
824
  events: [],
@@ -828,14 +836,23 @@ function createOptions(plugins) {
828
836
  eventOrder: void 0,
829
837
  eventSources: [],
830
838
  eventTextColor: void 0,
831
- eventTimeFormat: { hour: "numeric", minute: "2-digit" },
839
+ eventTimeFormat: {
840
+ hour: "numeric",
841
+ minute: "2-digit"
842
+ },
832
843
  filterEventsWithResources: false,
833
844
  firstDay: 0,
834
- headerToolbar: { start: "title", center: "", end: "today prev,next" },
845
+ headerToolbar: {
846
+ start: "title",
847
+ center: "",
848
+ end: "today prev,next"
849
+ },
835
850
  height: void 0,
836
851
  hiddenDays: [],
837
852
  highlightedDates: [],
838
853
  // ec option
854
+ icons: {},
855
+ // ec option
839
856
  lazyFetching: true,
840
857
  loading: void 0,
841
858
  locale: void 0,
@@ -874,17 +891,13 @@ function createOptions(plugins) {
874
891
  title: "ec-title",
875
892
  toolbar: "ec-toolbar",
876
893
  view: "",
877
- weekdays: [
878
- "ec-sun",
879
- "ec-mon",
880
- "ec-tue",
881
- "ec-wed",
882
- "ec-thu",
883
- "ec-fri",
884
- "ec-sat"
885
- ]
894
+ weekdays: ["ec-sun", "ec-mon", "ec-tue", "ec-wed", "ec-thu", "ec-fri", "ec-sat"]
895
+ },
896
+ titleFormat: {
897
+ year: "numeric",
898
+ month: "short",
899
+ day: "numeric"
886
900
  },
887
- titleFormat: { year: "numeric", month: "short", day: "numeric" },
888
901
  validRange: void 0,
889
902
  view: void 0,
890
903
  viewDidMount: void 0,
@@ -911,52 +924,48 @@ function createParsers(plugins) {
911
924
  }
912
925
  return parsers;
913
926
  }
914
- const specialOptions = ["buttonText", "customButtons", "theme"];
915
- function optionsState(mainState, plugins, userOptions) {
927
+ const specialOptions = ["buttonText", "customButtons", "icons", "theme"];
928
+ function optionsState(plugins, userOptions) {
916
929
  let defOptions = createOptions(plugins);
917
930
  let parsers = createParsers(plugins);
918
931
  defOptions = parseOptions(defOptions, parsers);
919
932
  userOptions = parseOptions(userOptions, parsers);
920
- let defViewsOptions = extractOption(defOptions, "views") ?? {};
921
- let userViewsOptions = extractOption(userOptions, "views") ?? {};
933
+ let defViews = extractOption(defOptions, "views") ?? {};
934
+ let userViews = extractOption(userOptions, "views") ?? {};
922
935
  let options = objectProxy({});
923
936
  assign(options, defOptions);
937
+ if (userOptions.view) {
938
+ options.view = userOptions.view;
939
+ }
924
940
  let setters = {};
925
- function initEffects() {
926
- if (userOptions.view) {
927
- options["view"] = userOptions.view;
928
- }
929
- let views = /* @__PURE__ */ new Set([...keys(defViewsOptions), ...keys(userViewsOptions)]);
930
- for (let view2 of views) {
931
- let userViewOptions = userViewsOptions[view2] ?? {};
932
- let defOpts = mergeOpts(defOptions, defViewsOptions[view2] ?? defViewsOptions[userViewOptions.type] ?? {});
933
- let opts = mergeOpts(defOpts, userOptions, userViewOptions);
934
- let component = extractOption(opts, "component");
935
- delete opts.view;
936
- for (let key of keys(opts)) {
937
- if (hasOwn(options, key)) {
938
- if (!setters[key]) {
939
- setters[key] = [];
940
- }
941
- setters[key].push(specialOptions.includes(key) ? (value) => opts[key] = isFunction(value) ? value(defOpts[key]) : value : (value) => opts[key] = value);
942
- } else {
943
- delete opts[key];
941
+ let viewOptions = {};
942
+ let viewComponents = {};
943
+ let views = /* @__PURE__ */ new Set([...keys(defViews), ...keys(userViews)]);
944
+ for (let view2 of views) {
945
+ let userViewOptions = userViews[view2] ?? {};
946
+ let defOpts = mergeOpts(defOptions, defViews[view2] ?? defViews[userViewOptions.type] ?? {});
947
+ let opts = mergeOpts(defOpts, userOptions, userViewOptions);
948
+ let component = extractOption(opts, "component");
949
+ delete opts.view;
950
+ for (let key of keys(opts)) {
951
+ if (hasOwn(options, key)) {
952
+ if (!setters[key]) {
953
+ setters[key] = [];
944
954
  }
955
+ setters[key].push(
956
+ specialOptions.includes(key) ? (value) => opts[key] = isFunction(value) ? value(defOpts[key]) : value : (value) => opts[key] = value
957
+ );
958
+ } else {
959
+ delete opts[key];
945
960
  }
946
- $.user_pre_effect(() => {
947
- let newView = options["view"];
948
- untrack(() => {
949
- if (newView === view2) {
950
- mainState.setViewComponent(component);
951
- assign(options, opts);
952
- }
953
- });
954
- });
955
961
  }
962
+ viewOptions[view2] = opts;
963
+ viewComponents[view2] = component;
956
964
  }
957
- return {
958
- state: options,
959
- setOption(key, value, parsed) {
965
+ assign(options, viewOptions[options.view]);
966
+ return [
967
+ options,
968
+ function setOption(key, value, parsed = true) {
960
969
  if (hasOwn(options, key)) {
961
970
  if (!parsed) {
962
971
  if (key in parsers) {
@@ -971,8 +980,11 @@ function optionsState(mainState, plugins, userOptions) {
971
980
  options[key] = value;
972
981
  }
973
982
  },
974
- initEffects
975
- };
983
+ function setViewOptions(view2) {
984
+ assign(options, viewOptions[view2]);
985
+ return viewComponents[view2];
986
+ }
987
+ ];
976
988
  }
977
989
  function parseOptions(opts, parsers) {
978
990
  let result = { ...opts };
@@ -1003,7 +1015,11 @@ function mergeOpts(...args) {
1003
1015
  override[key] = opts[key](result[key]);
1004
1016
  }
1005
1017
  }
1006
- result = { ...result, ...opts, ...override };
1018
+ result = {
1019
+ ...result,
1020
+ ...opts,
1021
+ ...override
1022
+ };
1007
1023
  }
1008
1024
  return result;
1009
1025
  }
@@ -1016,6 +1032,17 @@ function diff(options, prevOptions) {
1016
1032
  }
1017
1033
  return diff2;
1018
1034
  }
1035
+ function switchView(mainState) {
1036
+ return () => {
1037
+ let { options: { view: view2 } } = mainState;
1038
+ untrack(() => {
1039
+ let initComponent = mainState.setViewOptions(view2);
1040
+ mainState.extensions = {};
1041
+ mainState.features = [];
1042
+ mainState.viewComponent = initComponent(mainState);
1043
+ });
1044
+ };
1045
+ }
1019
1046
  function loadEvents(mainState, loadingInvoker) {
1020
1047
  return () => {
1021
1048
  let {
@@ -1120,10 +1147,10 @@ function load(sources, defaultResult, parseResult, applyResult, activeRange2, fe
1120
1147
  assign(fetchedRange, activeRange2);
1121
1148
  }
1122
1149
  }
1123
- function createLoadingInvoker(options) {
1150
+ function createLoadingInvoker(mainState) {
1124
1151
  let counter = 0;
1125
1152
  function invoke(value) {
1126
- let { loading } = options;
1153
+ let { options: { loading } } = mainState;
1127
1154
  if (isFunction(loading)) {
1128
1155
  loading(value);
1129
1156
  }
@@ -1455,20 +1482,20 @@ class State {
1455
1482
  set iClass(value) {
1456
1483
  $.set(this.#iClass, value, true);
1457
1484
  }
1458
- #setOption;
1485
+ options;
1486
+ setOption;
1487
+ setViewOptions;
1459
1488
  constructor(plugins, options) {
1460
- let { state, setOption, initEffects } = optionsState(this, plugins, options);
1461
- this.options = state;
1462
- this.#setOption = setOption;
1489
+ [this.options, this.setOption, this.setViewOptions] = optionsState(plugins, options);
1463
1490
  this.#auxComponents = $.state($.proxy([]));
1464
1491
  this.#currentRange = $.derived(currentRange(this));
1465
1492
  this.#activeRange = $.derived(activeRange(this));
1466
1493
  this.#fetchedRange = $.state($.proxy({ events: {}, resources: {} }));
1467
- this.#events = $.state(arrayProxy([]));
1494
+ this.#events = $.state(arrayProxy(this.options.events));
1468
1495
  this.#filteredEvents = $.derived(filteredEvents(this));
1469
1496
  this.#mainEl = $.state();
1470
1497
  this.#now = $.state($.proxy(createDate()));
1471
- this.#resources = $.state(arrayProxy([]));
1498
+ this.#resources = $.state(arrayProxy(this.options.resources));
1472
1499
  this.#today = $.state($.proxy(setMidnight(createDate())));
1473
1500
  this.#intlEventTime = $.derived(intlRange(this, "eventTimeFormat"));
1474
1501
  this.#intlDayHeader = $.derived(intl(this, "dayHeaderFormat"));
@@ -1487,11 +1514,11 @@ class State {
1487
1514
  for (let plugin of plugins) {
1488
1515
  plugin.initState?.(this);
1489
1516
  }
1490
- initEffects();
1491
1517
  this.#initEffects();
1492
1518
  }
1493
1519
  #initEffects() {
1494
- let loading = createLoadingInvoker(this.options);
1520
+ let loading = createLoadingInvoker(this);
1521
+ $.user_pre_effect(switchView(this));
1495
1522
  $.user_pre_effect(setNowAndToday(this));
1496
1523
  $.user_effect(loadEvents(this, loading));
1497
1524
  $.user_effect(loadResources(this, loading));
@@ -1499,14 +1526,6 @@ class State {
1499
1526
  $.user_effect(runEventAllUpdated(this));
1500
1527
  $.user_effect(runViewDidMount(this));
1501
1528
  }
1502
- setViewComponent(component) {
1503
- this.extensions = {};
1504
- this.features = [];
1505
- this.viewComponent = component(this);
1506
- }
1507
- setOption(name, value, parsed = true) {
1508
- this.#setOption(name, value, parsed);
1509
- }
1510
1529
  }
1511
1530
  var root_2$5 = $.from_html(`<h2></h2>`);
1512
1531
  var root_4$1 = $.from_html(`<button><i></i></button>`);
@@ -5558,29 +5577,36 @@ function Event($$anchor, $$props) {
5558
5577
  return $.pop($$exports);
5559
5578
  }
5560
5579
  var root_1$2 = $.from_html(`<span></span>`);
5561
- var root_2 = $.from_html(`<button><!></button>`);
5580
+ var root_2 = $.from_html(`<button></button>`);
5562
5581
  var root = $.from_html(`<!> <span><!></span>`, 1);
5563
5582
  function Expander($$anchor, $$props) {
5564
5583
  $.push($$props, true);
5565
- let $$d = $.derived(() => getContext("state")), resources = $.derived(() => $.get($$d).resources), theme = $.derived(() => $.get($$d).options.theme);
5584
+ let resource = $.prop($$props, "resource", 7);
5585
+ let $$d = $.derived(() => getContext("state")), resources = $.derived(() => $.get($$d).resources), view2 = $.derived(() => $.get($$d).view), buttonText = $.derived(() => $.get($$d).options.buttonText), icons = $.derived(() => $.get($$d).options.icons), resourceExpand = $.derived(() => $.get($$d).options.resourceExpand), theme = $.derived(() => $.get($$d).options.theme);
5566
5586
  let payload = $.state({});
5567
- let expanded = $.state(true);
5587
+ let expanded = $.derived(() => resource().expanded);
5588
+ let title = $.derived(() => $.get(buttonText)[$.get(expanded) ? "collapse" : "expand"]);
5568
5589
  $.user_pre_effect(() => {
5569
- $.set(payload, getPayload($$props.resource));
5570
- $.set(expanded, $.get(payload).expanded, true);
5590
+ $.set(payload, getPayload(resource()));
5571
5591
  });
5572
- function onclick() {
5573
- $.set(expanded, !$.get(expanded));
5574
- $.get(payload).expanded = $.get(expanded);
5575
- toggle($.get(payload).children, $.get(expanded));
5592
+ function onclick(jsEvent) {
5593
+ resource().expanded = $.set(expanded, !$.get(expanded));
5594
+ toggle($.get(payload).children);
5576
5595
  $.get(resources).length = $.get(resources).length;
5596
+ if (isFunction($.get(resourceExpand))) {
5597
+ $.get(resourceExpand)({
5598
+ resource: resource(),
5599
+ jsEvent,
5600
+ view: toViewWithLocalDates($.get(view2))
5601
+ });
5602
+ }
5577
5603
  }
5578
- function toggle(children, expand) {
5604
+ function toggle(children) {
5579
5605
  for (let child of children) {
5580
5606
  let payload2 = getPayload(child);
5581
- payload2.hidden = !expand;
5582
- if (payload2.expanded) {
5583
- toggle(payload2.children, expand);
5607
+ payload2.hidden = !$.get(expanded);
5608
+ if (child.expanded) {
5609
+ toggle(payload2.children);
5584
5610
  }
5585
5611
  }
5586
5612
  }
@@ -5594,30 +5620,19 @@ function Expander($$anchor, $$props) {
5594
5620
  var span_1 = $.sibling(node, 2);
5595
5621
  var node_1 = $.child(span_1);
5596
5622
  {
5597
- var consequent_1 = ($$anchor2) => {
5623
+ var consequent = ($$anchor2) => {
5598
5624
  var button = root_2();
5599
5625
  button.__click = onclick;
5600
- var node_2 = $.child(button);
5601
- {
5602
- var consequent = ($$anchor3) => {
5603
- var text = $.text("−");
5604
- $.append($$anchor3, text);
5605
- };
5606
- var alternate = ($$anchor3) => {
5607
- var text_1 = $.text("+");
5608
- $.append($$anchor3, text_1);
5609
- };
5610
- $.if(node_2, ($$render) => {
5611
- if ($.get(expanded)) $$render(consequent);
5612
- else $$render(alternate, false);
5613
- });
5614
- }
5615
- $.reset(button);
5616
- $.template_effect(() => $.set_class(button, 1, $.get(theme).button));
5626
+ $.attach(button, () => contentFrom($.get(icons)[$.get(expanded) ? "collapse" : "expand"]));
5627
+ $.template_effect(() => {
5628
+ $.set_class(button, 1, $.get(theme).button);
5629
+ $.set_attribute(button, "aria-label", $.get(title));
5630
+ $.set_attribute(button, "title", $.get(title));
5631
+ });
5617
5632
  $.append($$anchor2, button);
5618
5633
  };
5619
5634
  $.if(node_1, ($$render) => {
5620
- if ($.get(payload).children?.length) $$render(consequent_1);
5635
+ if ($.get(payload).children?.length) $$render(consequent);
5621
5636
  });
5622
5637
  }
5623
5638
  $.reset(span_1);
@@ -5949,43 +5964,59 @@ const index$1 = {
5949
5964
  createOptions(options) {
5950
5965
  createTRROptions(options);
5951
5966
  createRROptions(options);
5952
- options.slotWidth = 32;
5953
- options.buttonText.resourceTimelineDay = "timeline";
5954
- options.buttonText.resourceTimelineWeek = "timeline";
5955
- options.buttonText.resourceTimelineMonth = "timeline";
5956
- options.theme.expander = "ec-expander";
5957
- options.theme.rowHead = "ec-row-head";
5958
- options.theme.slots = "ec-slots";
5959
- options.view = "resourceTimelineWeek";
5960
- options.views.resourceTimelineDay = {
5961
- buttonText: btnTextDay,
5962
- component: initViewComponent$1,
5963
- displayEventEnd: false,
5964
- dayHeaderFormat: { weekday: "long" },
5965
- duration: { days: 1 },
5966
- theme: themeView("ec-resource ec-timeline ec-day-view"),
5967
- titleFormat: { year: "numeric", month: "long", day: "numeric" }
5968
- };
5969
- options.views.resourceTimelineWeek = {
5970
- buttonText: btnTextWeek,
5971
- component: initViewComponent$1,
5972
- displayEventEnd: false,
5973
- duration: { weeks: 1 },
5974
- theme: themeView("ec-resource ec-timeline ec-week-view")
5975
- };
5976
- options.views.resourceTimelineMonth = {
5977
- buttonText: btnTextMonth,
5978
- component: initMonthViewComponent,
5979
- displayEventEnd: false,
5980
- dayHeaderFormat: {
5981
- weekday: "short",
5982
- day: "numeric"
5967
+ assign(options, {
5968
+ resourceExpand: void 0,
5969
+ slotWidth: 32,
5970
+ // Common options
5971
+ view: "resourceTimelineWeek"
5972
+ });
5973
+ assign(options.buttonText, {
5974
+ expand: "Expand",
5975
+ collapse: "Collapse",
5976
+ resourceTimelineDay: "timeline",
5977
+ resourceTimelineWeek: "timeline",
5978
+ resourceTimelineMonth: "timeline"
5979
+ });
5980
+ assign(options.icons, {
5981
+ collapse: { html: "&minus;" },
5982
+ expand: { html: "&plus;" }
5983
+ });
5984
+ assign(options.theme, {
5985
+ expander: "ec-expander",
5986
+ rowHead: "ec-row-head",
5987
+ slots: "ec-slots"
5988
+ });
5989
+ assign(options.views, {
5990
+ resourceTimelineDay: {
5991
+ buttonText: btnTextDay,
5992
+ component: initViewComponent$1,
5993
+ displayEventEnd: false,
5994
+ dayHeaderFormat: { weekday: "long" },
5995
+ duration: { days: 1 },
5996
+ theme: themeView("ec-resource ec-timeline ec-day-view"),
5997
+ titleFormat: { year: "numeric", month: "long", day: "numeric" }
5983
5998
  },
5984
- duration: { months: 1 },
5985
- slotDuration: { days: 1 },
5986
- theme: themeView("ec-resource ec-timeline ec-month-view"),
5987
- titleFormat: { year: "numeric", month: "long" }
5988
- };
5999
+ resourceTimelineWeek: {
6000
+ buttonText: btnTextWeek,
6001
+ component: initViewComponent$1,
6002
+ displayEventEnd: false,
6003
+ duration: { weeks: 1 },
6004
+ theme: themeView("ec-resource ec-timeline ec-week-view")
6005
+ },
6006
+ resourceTimelineMonth: {
6007
+ buttonText: btnTextMonth,
6008
+ component: initMonthViewComponent,
6009
+ displayEventEnd: false,
6010
+ dayHeaderFormat: {
6011
+ weekday: "short",
6012
+ day: "numeric"
6013
+ },
6014
+ duration: { months: 1 },
6015
+ slotDuration: { days: 1 },
6016
+ theme: themeView("ec-resource ec-timeline ec-month-view"),
6017
+ titleFormat: { year: "numeric", month: "long" }
6018
+ }
6019
+ });
5989
6020
  },
5990
6021
  createParsers(parsers) {
5991
6022
  createTRRParsers(parsers);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event-calendar/core",
3
- "version": "5.3.2",
3
+ "version": "5.4.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.49.1"
35
+ "svelte": "^5.50.1"
36
36
  }
37
37
  }
@@ -6,7 +6,7 @@
6
6
  prevDate, toEventWithLocalDates, toLocalDate, toViewWithLocalDates
7
7
  } from '#lib';
8
8
  import MainState from './storage/state.svelte.js';
9
- import {diff} from './storage/options.svelte.js';
9
+ import {diff} from './storage/options.js';
10
10
  import Toolbar from './Toolbar.svelte';
11
11
 
12
12
  let {plugins = [], options = {}} = $props();
@@ -3,11 +3,11 @@ import {empty} from './utils.js';
3
3
 
4
4
  export function createResources(input) {
5
5
  let result = [];
6
- _createResources(input, 0, result);
6
+ _createResources(input, 0, false, result);
7
7
  return result;
8
8
  }
9
9
 
10
- function _createResources(input, level, flat) {
10
+ function _createResources(input, level, hidden, flat) {
11
11
  let result = [];
12
12
  for (let item of input) {
13
13
  let resource = createResource(item);
@@ -16,12 +16,11 @@ function _createResources(input, level, flat) {
16
16
  let payload = {
17
17
  level,
18
18
  children: [],
19
- expanded: true,
20
- hidden: false
19
+ hidden
21
20
  };
22
21
  setPayload(resource, payload);
23
22
  if (item.children) {
24
- payload.children = _createResources(item.children, level + 1, flat);
23
+ payload.children = _createResources(item.children, level + 1, hidden || !resource.expanded, flat);
25
24
  }
26
25
  }
27
26
  return result;
@@ -33,6 +32,7 @@ export function createResource(input) {
33
32
  title: input.title || '',
34
33
  eventBackgroundColor: eventBackgroundColor(input),
35
34
  eventTextColor: eventTextColor(input),
35
+ expanded: input.expanded ?? true,
36
36
  extendedProps: input.extendedProps ?? {}
37
37
  };
38
38
  }
@@ -1,32 +1,34 @@
1
1
  <script>
2
2
  import {getContext} from 'svelte';
3
- import {getPayload} from '#lib';
3
+ import {contentFrom, getPayload, isFunction, toViewWithLocalDates} from '#lib';
4
4
 
5
5
  let {resource} = $props();
6
6
 
7
- let {resources, options: {theme}} = $derived(getContext('state'));
7
+ let {resources, view, options: {buttonText, icons, resourceExpand, theme}} = $derived(getContext('state'));
8
8
 
9
9
  let payload = $state.raw({});
10
- let expanded = $state(true);
10
+ let expanded = $derived(resource.expanded);
11
+ let title = $derived(buttonText[expanded ? 'collapse' : 'expand']);
11
12
 
12
13
  $effect.pre(() => {
13
14
  payload = getPayload(resource);
14
- expanded = payload.expanded;
15
15
  });
16
16
 
17
- function onclick() {
18
- expanded = !expanded;
19
- payload.expanded = expanded;
20
- toggle(payload.children, expanded);
17
+ function onclick(jsEvent) {
18
+ resource.expanded = expanded = !expanded;
19
+ toggle(payload.children);
21
20
  resources.length = resources.length;
21
+ if (isFunction(resourceExpand)) {
22
+ resourceExpand({resource, jsEvent, view: toViewWithLocalDates(view)});
23
+ }
22
24
  }
23
25
 
24
- function toggle(children, expand) {
26
+ function toggle(children) {
25
27
  for (let child of children) {
26
28
  let payload = getPayload(child);
27
- payload.hidden = !expand;
28
- if (payload.expanded) {
29
- toggle(payload.children, expand);
29
+ payload.hidden = !expanded;
30
+ if (child.expanded) {
31
+ toggle(payload.children);
30
32
  }
31
33
  }
32
34
  }
@@ -38,8 +40,13 @@
38
40
 
39
41
  <span class="{theme.expander}">
40
42
  {#if payload.children?.length}
41
- <button class="{theme.button}" {onclick}>
42
- {#if expanded}&minus;{:else}&plus;{/if}
43
+ <button
44
+ class="{theme.button}"
45
+ aria-label="{title}"
46
+ title="{title}"
47
+ {onclick}
48
+ {@attach contentFrom(icons[expanded ? 'collapse' : 'expand'])}
49
+ >
43
50
  </button>
44
51
  {/if}
45
52
  </span>
@@ -1,4 +1,4 @@
1
- import {btnTextDay, btnTextMonth, btnTextWeek, getPayload, themeView} from '#lib';
1
+ import {assign, btnTextDay, btnTextMonth, btnTextWeek, getPayload, themeView} from '#lib';
2
2
  import {setExtensions} from '../time-grid/lib.js';
3
3
  import {createTRROptions, createTRRParsers} from '../time-grid/options.js';
4
4
  import {createRROptions} from '../resource-time-grid/options.js';
@@ -8,44 +8,59 @@ export default {
8
8
  createOptions(options) {
9
9
  createTRROptions(options);
10
10
  createRROptions(options);
11
- options.slotWidth = 32;
12
- // Common options
13
- options.buttonText.resourceTimelineDay = 'timeline';
14
- options.buttonText.resourceTimelineWeek = 'timeline';
15
- options.buttonText.resourceTimelineMonth = 'timeline';
16
- options.theme.expander = 'ec-expander';
17
- options.theme.rowHead = 'ec-row-head';
18
- options.theme.slots = 'ec-slots';
19
- options.view = 'resourceTimelineWeek';
20
- options.views.resourceTimelineDay = {
21
- buttonText: btnTextDay,
22
- component: initViewComponent,
23
- displayEventEnd: false,
24
- dayHeaderFormat: {weekday: 'long'},
25
- duration: {days: 1},
26
- theme: themeView('ec-resource ec-timeline ec-day-view'),
27
- titleFormat: {year: 'numeric', month: 'long', day: 'numeric'}
28
- };
29
- options.views.resourceTimelineWeek = {
30
- buttonText: btnTextWeek,
31
- component: initViewComponent,
32
- displayEventEnd: false,
33
- duration: {weeks: 1},
34
- theme: themeView('ec-resource ec-timeline ec-week-view')
35
- };
36
- options.views.resourceTimelineMonth = {
37
- buttonText: btnTextMonth,
38
- component: initMonthViewComponent,
39
- displayEventEnd: false,
40
- dayHeaderFormat: {
41
- weekday: 'short',
42
- day: 'numeric'
11
+ assign(options, {
12
+ resourceExpand: undefined,
13
+ slotWidth: 32,
14
+ // Common options
15
+ view: 'resourceTimelineWeek'
16
+ });
17
+ assign(options.buttonText, {
18
+ expand: 'Expand',
19
+ collapse: 'Collapse',
20
+ resourceTimelineDay: 'timeline',
21
+ resourceTimelineWeek: 'timeline',
22
+ resourceTimelineMonth: 'timeline'
23
+ });
24
+ assign(options.icons, {
25
+ collapse: {html: '&minus;'},
26
+ expand: {html: '&plus;'}
27
+ });
28
+ assign(options.theme, {
29
+ expander: 'ec-expander',
30
+ rowHead: 'ec-row-head',
31
+ slots: 'ec-slots'
32
+ });
33
+ assign(options.views, {
34
+ resourceTimelineDay: {
35
+ buttonText: btnTextDay,
36
+ component: initViewComponent,
37
+ displayEventEnd: false,
38
+ dayHeaderFormat: {weekday: 'long'},
39
+ duration: {days: 1},
40
+ theme: themeView('ec-resource ec-timeline ec-day-view'),
41
+ titleFormat: {year: 'numeric', month: 'long', day: 'numeric'}
43
42
  },
44
- duration: {months: 1},
45
- slotDuration: {days: 1},
46
- theme: themeView('ec-resource ec-timeline ec-month-view'),
47
- titleFormat: {year: 'numeric', month: 'long'}
48
- };
43
+ resourceTimelineWeek: {
44
+ buttonText: btnTextWeek,
45
+ component: initViewComponent,
46
+ displayEventEnd: false,
47
+ duration: {weeks: 1},
48
+ theme: themeView('ec-resource ec-timeline ec-week-view')
49
+ },
50
+ resourceTimelineMonth: {
51
+ buttonText: btnTextMonth,
52
+ component: initMonthViewComponent,
53
+ displayEventEnd: false,
54
+ dayHeaderFormat: {
55
+ weekday: 'short',
56
+ day: 'numeric'
57
+ },
58
+ duration: {months: 1},
59
+ slotDuration: {days: 1},
60
+ theme: themeView('ec-resource ec-timeline ec-month-view'),
61
+ titleFormat: {year: 'numeric', month: 'long'}
62
+ }
63
+ });
49
64
  },
50
65
 
51
66
  createParsers(parsers) {
@@ -5,6 +5,20 @@ import {
5
5
  } from '#lib';
6
6
  import {arrayProxy} from './proxy.svelte.js';
7
7
 
8
+ export function switchView(mainState) {
9
+ return () => {
10
+ // Dependencies
11
+ let {options: {view}} = mainState;
12
+
13
+ untrack(() => {
14
+ let initComponent = mainState.setViewOptions(view);
15
+ mainState.extensions = {};
16
+ mainState.features = [];
17
+ mainState.viewComponent = initComponent(mainState);
18
+ });
19
+ };
20
+ }
21
+
8
22
  export function loadEvents(mainState, loadingInvoker) {
9
23
  return () => {
10
24
  // Dependencies
@@ -125,10 +139,10 @@ function load(sources, defaultResult, parseResult, applyResult, activeRange, fet
125
139
  }
126
140
  }
127
141
 
128
- export function createLoadingInvoker(options) {
142
+ export function createLoadingInvoker(mainState) {
129
143
  let counter = 0;
130
144
  function invoke(value) {
131
- let {loading} = options;
145
+ let {options: {loading}} = mainState;
132
146
  if (isFunction(loading)) {
133
147
  loading(value);
134
148
  }
@@ -52,6 +52,7 @@ function createOptions(plugins) {
52
52
  height: undefined,
53
53
  hiddenDays: [],
54
54
  highlightedDates: [], // ec option
55
+ icons: {}, // ec option
55
56
  lazyFetching: true,
56
57
  loading: undefined,
57
58
  locale: undefined,
@@ -130,9 +131,9 @@ function createParsers(plugins) {
130
131
  }
131
132
 
132
133
  // Options where default value is passed to the function
133
- const specialOptions = ['buttonText', 'customButtons', 'theme'];
134
+ const specialOptions = ['buttonText', 'customButtons', 'icons', 'theme'];
134
135
 
135
- export function optionsState(mainState, plugins, userOptions) {
136
+ export function optionsState(plugins, userOptions) {
136
137
  // Create default options and parsers
137
138
  let defOptions = createOptions(plugins);
138
139
  let parsers = createParsers(plugins);
@@ -142,62 +143,53 @@ export function optionsState(mainState, plugins, userOptions) {
142
143
  userOptions = parseOptions(userOptions, parsers);
143
144
 
144
145
  // Extract view-specific options
145
- let defViewsOptions = extractOption(defOptions, 'views') ?? {};
146
- let userViewsOptions = extractOption(userOptions, 'views') ?? {};
146
+ let defViews = extractOption(defOptions, 'views') ?? {};
147
+ let userViews = extractOption(userOptions, 'views') ?? {};
147
148
 
148
149
  // Create options state
149
150
  let options = objectProxy({});
150
151
  assign(options, defOptions);
152
+ // Set initial view based on input
153
+ if (userOptions.view) {
154
+ options.view = userOptions.view;
155
+ }
151
156
 
157
+ // Set options for each view
152
158
  let setters = {};
153
-
154
- function initEffects() {
155
- // Set initial view based on input
156
- if (userOptions.view) {
157
- options['view'] = userOptions.view;
158
- }
159
- // Set options for each view
160
- let views = new Set([...keys(defViewsOptions), ...keys(userViewsOptions)]);
161
- for (let view of views) {
162
- let userViewOptions = userViewsOptions[view] ?? {};
163
- let defOpts = mergeOpts(defOptions, defViewsOptions[view] ?? defViewsOptions[userViewOptions.type] ?? {});
164
- let opts = mergeOpts(defOpts, userOptions, userViewOptions);
165
- let component = extractOption(opts, 'component');
166
- // View has been set
167
- delete opts.view;
168
- // Set up option setters and delete unknown options
169
- for (let key of keys(opts)) {
170
- if (hasOwn(options, key)) {
171
- if (!setters[key]) {
172
- setters[key] = [];
173
- }
174
- setters[key].push(
175
- specialOptions.includes(key)
176
- ? value => opts[key] = isFunction(value) ? value(defOpts[key]) : value
177
- : value => opts[key] = value
178
- );
179
- } else {
180
- delete opts[key];
159
+ let viewOptions = {};
160
+ let viewComponents = {};
161
+ let views = new Set([...keys(defViews), ...keys(userViews)]);
162
+ for (let view of views) {
163
+ let userViewOptions = userViews[view] ?? {};
164
+ let defOpts = mergeOpts(defOptions, defViews[view] ?? defViews[userViewOptions.type] ?? {});
165
+ let opts = mergeOpts(defOpts, userOptions, userViewOptions);
166
+ let component = extractOption(opts, 'component');
167
+ // View has been set
168
+ delete opts.view;
169
+ // Set up option setters and delete unknown options
170
+ for (let key of keys(opts)) {
171
+ if (hasOwn(options, key)) {
172
+ if (!setters[key]) {
173
+ setters[key] = [];
181
174
  }
175
+ setters[key].push(
176
+ specialOptions.includes(key)
177
+ ? value => opts[key] = isFunction(value) ? value(defOpts[key]) : value
178
+ : value => opts[key] = value
179
+ );
180
+ } else {
181
+ delete opts[key];
182
182
  }
183
- // When view changes...
184
- $effect.pre(() => {
185
- let newView = options['view'];
186
- untrack(() => {
187
- if (newView === view) {
188
- // ...switch view component
189
- mainState.setViewComponent(component);
190
- // ...and update options
191
- assign(options, opts);
192
- }
193
- });
194
- });
195
183
  }
184
+ viewOptions[view] = opts;
185
+ viewComponents[view] = component;
196
186
  }
197
187
 
198
- return {
199
- state: options,
200
- setOption(key, value, parsed) {
188
+ assign(options, viewOptions[options.view]);
189
+
190
+ return [
191
+ options,
192
+ function setOption(key, value, parsed = true) {
201
193
  if (hasOwn(options, key)) {
202
194
  if (!parsed) {
203
195
  if (key in parsers) {
@@ -213,8 +205,11 @@ export function optionsState(mainState, plugins, userOptions) {
213
205
  options[key] = value;
214
206
  }
215
207
  },
216
- initEffects
217
- };
208
+ function setViewOptions(view) {
209
+ assign(options, viewOptions[view]);
210
+ return viewComponents[view];
211
+ }
212
+ ];
218
213
  }
219
214
 
220
215
  function parseOptions(opts, parsers) {
@@ -38,11 +38,11 @@ function proxy(target, setDependency, hasEffect, invokeEffect) {
38
38
  },
39
39
  set(target, prop, value, receiver) {
40
40
  let has = hasEffect(target[prop], value);
41
- Reflect.set(target, prop, value, receiver);
41
+ let result = Reflect.set(target, prop, value, receiver);
42
42
  if (has) {
43
43
  invokeEffect(prop);
44
44
  }
45
- return true;
45
+ return result;
46
46
  }
47
47
  });
48
48
  }
@@ -1,32 +1,33 @@
1
1
  import {SvelteMap} from 'svelte/reactivity';
2
2
  import {createDate, identity, intl, intlRange, setMidnight} from '#lib';
3
- import {optionsState} from './options.svelte.js';
3
+ import {optionsState} from './options.js';
4
4
  import {
5
- createLoadingInvoker, loadEvents, loadResources, runDatesSet, runEventAllUpdated, runViewDidMount, setNowAndToday
5
+ createLoadingInvoker, loadEvents, loadResources, runDatesSet, runEventAllUpdated, runViewDidMount, setNowAndToday,
6
+ switchView
6
7
  } from './effects.js';
7
8
  import {activeRange, currentRange, filteredEvents, view, viewDates, viewTitle} from './derived.js';
8
9
  import {arrayProxy} from './proxy.svelte.js';
9
10
 
10
11
  export default class State {
11
12
 
12
- #setOption;
13
+ options;
14
+ setOption;
15
+ setViewOptions;
13
16
 
14
17
  constructor(plugins, options) {
15
18
  // Create options state
16
- let {state, setOption, initEffects} = optionsState(this, plugins, options);
17
- this.options = state;
18
- this.#setOption = setOption;
19
+ ([this.options, this.setOption, this.setViewOptions] = optionsState(plugins, options));
19
20
 
20
21
  // Create other states
21
22
  this.auxComponents = $state([]);
22
23
  this.currentRange = $derived.by(currentRange(this));
23
24
  this.activeRange = $derived.by(activeRange(this));
24
25
  this.fetchedRange = $state({events: {}, resources: {}});
25
- this.events = $state.raw(arrayProxy([]));
26
+ this.events = $state.raw(arrayProxy(this.options.events));
26
27
  this.filteredEvents = $derived.by(filteredEvents(this));
27
28
  this.mainEl = $state();
28
29
  this.now = $state(createDate());
29
- this.resources = $state.raw(arrayProxy([]));
30
+ this.resources = $state.raw(arrayProxy(this.options.resources));
30
31
  this.today = $state(setMidnight(createDate()));
31
32
  this.intlEventTime = $derived.by(intlRange(this, 'eventTimeFormat'));
32
33
  this.intlDayHeader = $derived.by(intl(this, 'dayHeaderFormat'));
@@ -49,12 +50,12 @@ export default class State {
49
50
  plugin.initState?.(this);
50
51
  }
51
52
 
52
- initEffects();
53
53
  this.#initEffects();
54
54
  }
55
55
 
56
56
  #initEffects() {
57
- let loading = createLoadingInvoker(this.options);
57
+ let loading = createLoadingInvoker(this);
58
+ $effect.pre(switchView(this));
58
59
  $effect.pre(setNowAndToday(this));
59
60
  $effect(loadEvents(this, loading));
60
61
  $effect(loadResources(this, loading));
@@ -62,14 +63,4 @@ export default class State {
62
63
  $effect(runEventAllUpdated(this));
63
64
  $effect(runViewDidMount(this));
64
65
  }
65
-
66
- setViewComponent(component) {
67
- this.extensions = {};
68
- this.features = [];
69
- this.viewComponent = component(this);
70
- }
71
-
72
- setOption(name, value, parsed = true) {
73
- this.#setOption(name, value, parsed);
74
- }
75
66
  }