Month, week and agenda views over a flat table of event occurrences. It reports the date window it is drawing so the host loads exactly that window — delegable and bounded no matter how large the list is — and raises events when a day or a chip is tapped. It writes nothing.
This component requires Modern controls to be enabled. Settings → Updates → Preview → Modern controls and themes
Month, week and agenda views over a flat table of event occurrences. Reports the date window it is drawing so the host loads only that window, and raises events when a day or an event is tapped.
It writes nothing and it does not expand recurrence. A repeating series arrives already materialised, one row per occurrence. That constraint is the reason it works against a real list rather than only against a demo collection.
Most Canvas calendars either compute occurrences at render time, which means no occurrence has a row you can attach an approval or a comment to, or they load the whole list and filter client side, which stops working at the delegation limit. This one does neither. It tells you which forty two days it is about to draw, you load exactly those, and it draws them. The query stays delegable and bounded no matter how large the list behind it is.
| Property | Type | Default | Description |
|---|---|---|---|
Events | Table | two samples | Flat occurrences, one row per day an event appears on |
Channels | Table | five samples | Categories, with ChannelKey, Title, ColorHex |
FocusDate | DateAndTime | Today() | Which month opens |
View | Text | "month" | month, week or agenda. Read once, then the toolbar owns it |
Config | Record | see below | Layout and behaviour, including DarkMode |
Theme | Table | Dark and Light | One row per mode |
| Column | Type | Notes |
|---|---|---|
OccurrenceKey | Text | Unique per occurrence. For a series, SeriesKey plus the date |
OccurrenceDate | Date | The day this row draws on. Index this column |
Start | DateTime | |
End | DateTime | |
IsAllDay | Boolean | All day draws as a solid bar, timed as a tint |
Title | Text | |
ChannelKey | Text | Matches Channels. No match falls back to the accent colour |
A multi day event is several rows, one per day. See the contract for why.
| Key | Default | Description |
|---|---|---|
RowHeight | 118 | Height of a week row in month view |
ChipHeight | 20 | Height of one event chip |
ChipSlots | 3 | Chips per day before the overflow link |
ShowWeekNumbers | true | ISO week gutter |
FirstDayOfWeek | "Sunday" | Sunday or Monday |
HourStart | 7 | First hour drawn in week view |
HourEnd | 19 | Last hour drawn |
HourHeight | 52 | Height of one hour row |
MarkHolidays | true | Tint federal holidays in month view |
DarkMode | false | Picks a row of the Theme table |
Accent | "" | Hex string, blank uses the theme accent |
| Property | Type | Description |
|---|---|---|
RangeStart | DateAndTime | First date drawn |
RangeEnd | DateAndTime | Last date drawn |
ActiveView | Text | The view currently drawn |
SelectedDate | DateAndTime | The day last tapped |
SelectedEventKey | Text | The chip last tapped, cleared when a day is tapped |
VisibleCount | Number | Events inside the drawn range |
FederalHolidays | Table | Observed US federal dates for three years around the focus |
| Event | When | Read |
|---|---|---|
OnRangeChange | The grid moved or the view changed | RangeStart, RangeEnd |
OnViewChange | The view toggle was used | ActiveView, then reload |
OnSelectDay | A day or an overflow link was tapped | SelectedDate |
OnSelectEvent | A chip was tapped | SelectedEventKey, SelectedDate |
RangeStart and RangeEnd follow the view. Week draws seven days and month
draws forty two, so the toggle raises OnRangeChange as well as OnViewChange. A
host that listens only for the view change reloads nothing and shows a week
filtered to the month it was already holding.
One flat gallery per view, never nested. An inner gallery cannot read the outer
ThisItem, and a control reading through a container returns a frozen value. The
month grid is six rows of seven cells, each cell computing its own date from the
row's WeekStart.
Overflow opens a day panel, it does not grow the cell. Growing would need the row height to change and the cell to hold as many chip slots as the busiest day might ever have. The panel shows every event on the day whatever the count, with room for full titles.
Chips carry a transparent button for the tooltip. ModernText has no
Tooltip, so a title truncated to the cell width would be unreadable. The overlay
supplies the tooltip, the tap, and a real keyboard focus stop.
Inactive views produce no rows. Hiding a gallery stops it being laid out but
does not stop its Items evaluating, so each view's Items is gated on the active
view. Without that, every toggle paid for all three.
Holidays use observed dates. Independence Day on a Saturday is observed on the Friday, and the Friday is the day that gets tinted.
Colour comes from the Theme table. A timed chip is a tint of its channel colour, and the tint amount is a theme value rather than a constant, which is what keeps it legible in both modes.
| Situation | Notes |
|---|---|
| Panel and review scheduling | Channels per review type, week view for the day plan |
| Reporting calendar | Materialise the recurring due dates, agenda view for the list |
| Inspection or site visit schedule | All day events, month view |
| Leave and coverage | All day bars, one row per day of a leave block |
| Deadline overview across a programme | Agenda view, filtered by channel on the host |
| Training sessions with sign up | OnSelectEvent opens your own detail, not the form |
When not to use it. If you need resource columns, drag to reschedule, or overlapping events laid out side by side within an hour, this is not that component. It is a scheduling calendar, not a booking grid.
On every range change, load exactly the dates drawn — delegable and bounded at ~42 rows, whatever the list size.
OnRangeChange: |-
=ClearCollect(
colWindow,
Filter(
'Event Occurrences',
OccurrenceDate >= cmpCalendarPro1.RangeStart,
OccurrenceDate <= cmpCalendarPro1.RangeEnd
)
)
// then bind Events: =colWindowA day tap seeds a blank draft on that date and opens your Event Form. Reset is what loads it.
OnSelectDay: |-
=Set(gblFormEvent, {
OccurrenceKey: "", Title: "", ChannelKey: "review",
IsAllDay: false,
Start: cmpCalendarPro1.SelectedDate + Time(9, 0, 0),
End: cmpCalendarPro1.SelectedDate + Time(10, 0, 0) });
Set(gblFormOpen, true);
Reset(cmpEventForm1)Bind to something read-only and let OnSelectEvent set a variable. The calendar has no opinion about what a tap means.
OnSelectEvent: |-
=Set(
gblPicked,
LookUp(colWindow,
OccurrenceKey = cmpCalendarPro1.SelectedEventKey)
)Two lists. Series holds the rules; Occurrences holds the rows this component reads.
| Occurrences column | Type | Notes |
|---|---|---|
OccurrenceKey | Text, indexed | SeriesKey plus yyyymmdd, so a regeneration upserts |
SeriesKey | Text | Blank for a one off |
OccurrenceDate | Date, indexed | The column you filter on |
StartClock | Text | "09:30", not a datetime. See the contract for why |
IsAllDay | Yes/No | |
Title | Text | |
ChannelKey | Text | |
ExceptionType | Choice | None, Modified, Cancelled |
Index OccurrenceDate. A Flow materialises a rolling window of occurrences from
the series rules, and ExceptionType protects rows a user has edited from being
regenerated over.
The full model, including why the clock is stored as text and what the three edit scopes cost you as writes, is in the recurrence contract.
Set Width by hand. A pasted instance arrives at 600 regardless of the YAML.
Height comes through intact.
Pass every Config key, including the ones left at their defaults. A Record
input's Default does not reach a pasted instance.
The instance syntax is Control: CanvasComponent with a ComponentName
sibling, not Control: cmpCalendarPro.
Bind Events to a windowed collection, not to a list. Binding a whole
SharePoint list directly will appear to work on a small list and quietly truncate
on a large one.
One email when something new ships. Unsubscribe anytime.
Use the toolbar to format · or type markdown directly