@lit-material/calendar 0.0.0-init → 1.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.
@@ -0,0 +1,1076 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html, isServer, LitElement, nothing, svg } from "lit";
8
+ import { customElement, property, state } from "lit/decorators.js";
9
+ import { cssProps } from "@lit-material/core/css-props.js";
10
+ import { LocalizeController, isRtl } from "@lit-material/core/localize.js";
11
+ import "@lit-material/segmented-button";
12
+ import { DAY_MINUTES, MINUTE, addDays, addMonths, coversDay, daysBetween, formatISODate, fromDate, lastDay, layoutDay, layoutSpans, localeFirstDayOfWeek, minutesInto, normalizeEvents, parseISODate, sameDate, startOfWeek, toDate, today, } from "./calendar-utils.js";
13
+ import { dayNumber, format, range } from "./calendar-format.js";
14
+ import { styles } from "./calendar-styles.js";
15
+ const VIEWS = ["month", "week", "day", "agenda"];
16
+ // Material icons chevron_left / chevron_right (Apache 2.0); mirrored in right-to-left layouts by CSS.
17
+ const icon = (d) => html `<svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true">${svg `<path d=${d}></path>`}</svg>`;
18
+ const previousIcon = icon("M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z");
19
+ const nextIcon = icon("M10 6 8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z");
20
+ const closest = (path, selector, stop) => {
21
+ for (const node of path) {
22
+ if (node === stop)
23
+ return undefined;
24
+ if (node instanceof HTMLElement && node.matches(selector))
25
+ return node;
26
+ }
27
+ return undefined;
28
+ };
29
+ const shift = (date, days, minutes) => {
30
+ const next = new Date(date.getTime());
31
+ if (days)
32
+ next.setDate(next.getDate() + days);
33
+ return new Date(next.getTime() + minutes * MINUTE);
34
+ };
35
+ /**
36
+ * Material Design 3 styled calendar and scheduler: a month grid, week and day time grids, and an agenda
37
+ * list, with a toolbar (previous / today / next, the period's title, and a view switch).
38
+ *
39
+ * Events are controlled: the element never moves an event itself. Dragging, resizing or Alt+arrow keys
40
+ * fire a cancelable `event-change` with the new times; update `events` to accept it.
41
+ *
42
+ * Times are in the browser's (or server's) local time zone. ISO strings without an offset are read as
43
+ * local wall-clock time; date-only strings are local days. `slot-click` and `event-change` hand back
44
+ * local `Date`s.
45
+ *
46
+ * The month view is an ARIA grid of days. The week and day views are one ARIA grid too: a header row of
47
+ * days, an "All day" row, then a row per time slot, each with a time row header. One cell is in the tab
48
+ * order (arrow keys move it); Tab goes on to the events in that cell, each a button named by its title
49
+ * and time.
50
+ *
51
+ * @element lit-material-calendar
52
+ *
53
+ * @fires event-click - `detail: { event }` when an event is clicked or activated with Enter/Space.
54
+ * @fires slot-click - `detail: { start, end, allDay }` when an empty day, all-day cell or time slot is
55
+ * clicked or activated, or a time range is dragged out (pointer) or extended with Shift+arrows.
56
+ * @fires event-change - Cancelable. `detail: { event, start, end, allDay }` when an event is dragged,
57
+ * resized or moved with the keyboard. The app updates `events`.
58
+ * @fires navigate - `detail: { view, date, start, end }` when the toolbar, keyboard or a "+N more" link
59
+ * changes the view or the period shown.
60
+ *
61
+ * @csspart toolbar - The row with the navigation buttons, title and view switch.
62
+ * @csspart title - The period's title.
63
+ * @csspart views - The view switch (a segmented button group).
64
+ * @csspart grid - The month grid or the time grid.
65
+ * @csspart agenda - The agenda list.
66
+ * @csspart event - Each event chip.
67
+ *
68
+ * @cssprop --lit-material-calendar-slot-height - Height of one time slot in the week and day views. Default 24px.
69
+ * @cssprop --lit-material-calendar-height - Maximum height of the scrolling time grid. Default 640px.
70
+ */
71
+ let LitMaterialCalendar = class LitMaterialCalendar extends LitElement {
72
+ constructor() {
73
+ super(...arguments);
74
+ /** Which view is shown. */
75
+ this.view = "month";
76
+ /** The events: `{ id, title, start, end?, allDay?, color?, editable?, ...data }`. Property or JSON attribute. */
77
+ this.events = [];
78
+ /** Minutes per row of the week and day views; also the step for keyboard moves. */
79
+ this.slotMinutes = 30;
80
+ /** The hour the week and day views scroll to when they open. */
81
+ this.scrollToHour = 8;
82
+ /** Event rows per day in the month view before "+N more". */
83
+ this.dayMaxEvents = 3;
84
+ /** Days the agenda view lists, from `date`. */
85
+ this.agendaDays = 7;
86
+ /** No dragging, resizing or keyboard moves (clicks still fire). */
87
+ this.readonly = false;
88
+ /** Slot in focus in the time grid (`-1`: the all-day row). */
89
+ this.focusSlot = -1;
90
+ this.selection = null;
91
+ this.preview = null;
92
+ this.agendaIndex = 0;
93
+ this.announcement = "";
94
+ this.localize = new LocalizeController(this);
95
+ this.normalized = [];
96
+ this.byKey = new Map();
97
+ this.focusAfterUpdate = null;
98
+ this.pendingEventFocus = null;
99
+ this.drag = null;
100
+ this.suppressClick = false;
101
+ this.scrolledView = null;
102
+ // ---- toolbar ----
103
+ this.handleToday = () => this.goToToday();
104
+ this.handlePrevious = () => this.previous();
105
+ this.handleNext = () => this.next();
106
+ this.handleViewChange = (event) => {
107
+ event.stopPropagation();
108
+ const buttons = [...this.renderRoot.querySelectorAll("lit-material-segmented-button")];
109
+ const selected = buttons.find((button) => button.selected);
110
+ const view = selected?.dataset.view;
111
+ if (view && VIEWS.includes(view) && view !== this.view)
112
+ this.setDate(this.anchor, view);
113
+ };
114
+ this.handleGridClick = (e) => {
115
+ if (this.suppressClick) {
116
+ this.suppressClick = false;
117
+ return;
118
+ }
119
+ const grid = e.currentTarget;
120
+ const path = e.composedPath();
121
+ const chip = closest(path, ".event", grid);
122
+ if (chip) {
123
+ const event = this.byKey.get(chip.dataset.key ?? "");
124
+ if (event)
125
+ this.emitEventClick(event);
126
+ return;
127
+ }
128
+ const more = closest(path, ".more", grid);
129
+ if (more) {
130
+ const date = parseISODate(more.dataset.date);
131
+ if (date)
132
+ this.setDate(date, "day");
133
+ return;
134
+ }
135
+ const cell = closest(path, "[data-cell]", grid);
136
+ const info = cell && this.cellInfo(cell);
137
+ if (!info)
138
+ return;
139
+ if (this.inPeriod(info.date)) {
140
+ if (info.slot !== null)
141
+ this.focusSlot = info.slot;
142
+ this.date = formatISODate(info.date);
143
+ }
144
+ this.selection = null;
145
+ this.emitSlot(this.slotTimes(info));
146
+ };
147
+ /** Focus that lands on a cell (a click, or a chip's cell) makes it the roving tab stop. */
148
+ this.handleFocusIn = (e) => {
149
+ const grid = e.currentTarget;
150
+ const cell = closest(e.composedPath(), "[data-cell]", grid);
151
+ const info = cell && this.cellInfo(cell);
152
+ if (!info || !this.inPeriod(info.date))
153
+ return;
154
+ if (info.slot !== null)
155
+ this.focusSlot = info.slot;
156
+ if (!sameDate(info.date, this.anchor))
157
+ this.date = formatISODate(info.date);
158
+ };
159
+ this.handleGridKeydown = (e) => {
160
+ const grid = e.currentTarget;
161
+ const path = e.composedPath();
162
+ const chip = closest(path, ".event", grid);
163
+ const more = closest(path, ".more", grid);
164
+ this.pendingEventFocus = null;
165
+ if (chip) {
166
+ if (this.handleEventKeydown(e, chip))
167
+ return;
168
+ if (e.key === "Enter" || e.key === " ")
169
+ return; // the button's own click
170
+ if (e.key === "Escape") {
171
+ e.preventDefault();
172
+ this.renderRoot.querySelector('[data-cell][tabindex="0"]')?.focus();
173
+ return;
174
+ }
175
+ }
176
+ if (more && (e.key === "Enter" || e.key === " "))
177
+ return;
178
+ if (e.altKey || e.metaKey)
179
+ return;
180
+ const rtl = isRtl(this);
181
+ const forward = rtl ? -1 : 1;
182
+ const anchor = this.anchor;
183
+ const time = this.timeGrid;
184
+ const ctrl = e.ctrlKey;
185
+ let date;
186
+ let slot = this.focusSlot;
187
+ switch (e.key) {
188
+ case "ArrowLeft":
189
+ case "ArrowRight": {
190
+ if (ctrl)
191
+ return;
192
+ date = addDays(anchor, e.key === "ArrowRight" ? forward : -forward);
193
+ this.selection = null;
194
+ break;
195
+ }
196
+ case "ArrowUp":
197
+ case "ArrowDown": {
198
+ if (ctrl)
199
+ return;
200
+ const down = e.key === "ArrowDown";
201
+ if (!time) {
202
+ date = addDays(anchor, down ? 7 : -7);
203
+ break;
204
+ }
205
+ slot = Math.max(-1, Math.min(this.slotCount - 1, slot + (down ? 1 : -1)));
206
+ date = anchor;
207
+ if (e.shiftKey && slot >= 0 && this.focusSlot >= 0) {
208
+ const iso = formatISODate(anchor);
209
+ const base = this.selection?.date === iso ? this.selection.anchor : this.focusSlot;
210
+ this.selection = { date: iso, anchor: base, from: Math.min(base, slot), to: Math.max(base, slot) };
211
+ }
212
+ else {
213
+ this.selection = null;
214
+ }
215
+ break;
216
+ }
217
+ case "Home":
218
+ case "End": {
219
+ const end = e.key === "End";
220
+ if (time && ctrl) {
221
+ slot = end ? this.slotCount - 1 : 0;
222
+ date = anchor;
223
+ }
224
+ else if (time) {
225
+ const { first, days } = this.rangeOf(this.view, anchor);
226
+ date = end ? addDays(first, days - 1) : first;
227
+ }
228
+ else {
229
+ const first = startOfWeek(anchor, this.weekStart);
230
+ date = end ? addDays(first, 6) : first;
231
+ }
232
+ this.selection = null;
233
+ break;
234
+ }
235
+ case "PageUp":
236
+ case "PageDown": {
237
+ const direction = e.key === "PageDown" ? 1 : -1;
238
+ if (this.view === "month")
239
+ date = addMonths(anchor, e.shiftKey ? direction * 12 : direction);
240
+ else
241
+ date = addDays(anchor, direction * (this.view === "week" ? 7 : 1));
242
+ this.selection = null;
243
+ break;
244
+ }
245
+ case "t":
246
+ case "T":
247
+ if (ctrl)
248
+ return;
249
+ date = today();
250
+ this.selection = null;
251
+ break;
252
+ case "Enter":
253
+ case " ": {
254
+ const cell = closest(path, "[data-cell]", grid);
255
+ const info = cell && this.cellInfo(cell);
256
+ if (!info)
257
+ return;
258
+ e.preventDefault();
259
+ const sel = this.selection;
260
+ if (sel && sel.date === formatISODate(info.date) && info.slot !== null && info.slot >= sel.from && info.slot <= sel.to) {
261
+ this.emitSlot(this.slotTimes({ date: info.date, slot: sel.from }, sel.to));
262
+ }
263
+ else {
264
+ this.emitSlot(this.slotTimes(info));
265
+ }
266
+ return;
267
+ }
268
+ default:
269
+ return;
270
+ }
271
+ e.preventDefault();
272
+ this.focusSlot = slot;
273
+ this.setDate(date);
274
+ this.focusCellLater();
275
+ };
276
+ this.handlePointerDown = (e) => {
277
+ if (e.button !== 0 || this.drag)
278
+ return;
279
+ const grid = e.currentTarget;
280
+ const path = e.composedPath();
281
+ const chip = closest(path, ".event", grid);
282
+ const origin = this.cellAt(e.clientX, e.clientY) ?? (() => {
283
+ const cell = closest(path, "[data-cell]", grid);
284
+ return cell ? this.cellInfo(cell) : undefined;
285
+ })();
286
+ if (!origin)
287
+ return;
288
+ let drag = null;
289
+ if (chip) {
290
+ const event = this.byKey.get(chip.dataset.key ?? "");
291
+ if (!event?.editable)
292
+ return;
293
+ const resize = !!closest(path, "[data-resize]", grid);
294
+ drag = { mode: resize ? "resize" : "move", pointerId: e.pointerId, x: e.clientX, y: e.clientY, origin, event, moved: false };
295
+ }
296
+ else if (this.timeGrid && origin.slot !== null && origin.slot >= 0 && e.pointerType !== "touch") {
297
+ drag = { mode: "create", pointerId: e.pointerId, x: e.clientX, y: e.clientY, origin, moved: false };
298
+ }
299
+ if (!drag)
300
+ return;
301
+ this.drag = drag;
302
+ this.suppressClick = false;
303
+ try {
304
+ grid.setPointerCapture(e.pointerId);
305
+ }
306
+ catch {
307
+ // a synthetic pointer: listeners on the host still see its events
308
+ }
309
+ this.addEventListener("pointermove", this.handlePointerMove);
310
+ this.addEventListener("pointerup", this.handlePointerUp);
311
+ this.addEventListener("pointercancel", this.handlePointerCancel);
312
+ };
313
+ this.handlePointerMove = (e) => {
314
+ const drag = this.drag;
315
+ if (!drag || e.pointerId !== drag.pointerId)
316
+ return;
317
+ if (!drag.moved && Math.hypot(e.clientX - drag.x, e.clientY - drag.y) < 4)
318
+ return;
319
+ drag.moved = true;
320
+ const cell = this.cellAt(e.clientX, e.clientY);
321
+ if (!cell)
322
+ return;
323
+ if (drag.mode === "create") {
324
+ if (!sameDate(cell.date, drag.origin.date) || cell.slot === null || cell.slot < 0)
325
+ return;
326
+ const from = drag.origin.slot;
327
+ this.selection = { date: formatISODate(cell.date), anchor: from, from: Math.min(from, cell.slot), to: Math.max(from, cell.slot) };
328
+ return;
329
+ }
330
+ const event = drag.event;
331
+ const days = daysBetween(drag.origin.date, cell.date);
332
+ const timed = !event.allDay && drag.origin.slot !== null && drag.origin.slot >= 0;
333
+ // A timed event stays in the time rows and an all-day one in the all-day row (or the month grid).
334
+ if (timed && (cell.slot === null || cell.slot < 0))
335
+ return;
336
+ if (!timed && cell.slot !== null && cell.slot >= 0)
337
+ return;
338
+ const minutes = timed ? (cell.slot - drag.origin.slot) * this.slotLength : 0;
339
+ if (drag.mode === "move") {
340
+ this.preview = { key: event.key, start: shift(event.start, days, minutes), end: shift(event.end, days, minutes) };
341
+ }
342
+ else {
343
+ let end = shift(event.end, days, minutes);
344
+ const min = event.allDay ? toDate(addDays(fromDate(event.start), 1)) : new Date(event.start.getTime() + this.slotLength * MINUTE);
345
+ if (end.getTime() < min.getTime())
346
+ end = min;
347
+ this.preview = { key: event.key, start: event.start, end };
348
+ }
349
+ };
350
+ this.handlePointerUp = (e) => {
351
+ const drag = this.drag;
352
+ if (!drag || e.pointerId !== drag.pointerId)
353
+ return;
354
+ const preview = this.preview;
355
+ this.endDrag();
356
+ if (!drag.moved)
357
+ return;
358
+ this.suppressClick = true;
359
+ setTimeout(() => (this.suppressClick = false));
360
+ if (drag.mode === "create") {
361
+ const selection = this.selection;
362
+ this.selection = null;
363
+ if (selection)
364
+ this.emitSlot(this.slotTimes({ date: drag.origin.date, slot: selection.from }, selection.to));
365
+ return;
366
+ }
367
+ if (preview && drag.event)
368
+ this.requestChange(drag.event, preview.start, preview.end, false);
369
+ };
370
+ this.handlePointerCancel = () => {
371
+ if (this.drag?.mode === "create")
372
+ this.selection = null;
373
+ this.endDrag();
374
+ };
375
+ // ---- agenda ----
376
+ this.handleAgendaClick = (e) => {
377
+ const chip = closest(e.composedPath(), ".agenda-event", e.currentTarget);
378
+ if (!chip)
379
+ return;
380
+ this.agendaIndex = Number(chip.dataset.index) || 0;
381
+ const event = this.byKey.get(chip.dataset.key ?? "");
382
+ if (event)
383
+ this.emitEventClick(event);
384
+ };
385
+ this.handleAgendaKeydown = (e) => {
386
+ if (e.altKey || e.metaKey || e.ctrlKey)
387
+ return;
388
+ const count = this.agendaItems().length;
389
+ const index = Math.min(this.agendaIndex, Math.max(0, count - 1));
390
+ let next;
391
+ switch (e.key) {
392
+ case "ArrowDown":
393
+ next = Math.min(count - 1, index + 1);
394
+ break;
395
+ case "ArrowUp":
396
+ next = Math.max(0, index - 1);
397
+ break;
398
+ case "Home":
399
+ next = 0;
400
+ break;
401
+ case "End":
402
+ next = count - 1;
403
+ break;
404
+ case "PageUp":
405
+ case "PageDown":
406
+ e.preventDefault();
407
+ this.step(e.key === "PageDown" ? 1 : -1);
408
+ this.focusAfterUpdate = "agenda";
409
+ return;
410
+ case "t":
411
+ case "T":
412
+ e.preventDefault();
413
+ this.goToToday();
414
+ this.focusAfterUpdate = "agenda";
415
+ return;
416
+ default:
417
+ return;
418
+ }
419
+ e.preventDefault();
420
+ if (next === undefined || next < 0)
421
+ return;
422
+ this.agendaIndex = next;
423
+ this.focusAfterUpdate = "agenda";
424
+ };
425
+ }
426
+ static { this.styles = styles; }
427
+ connectedCallback() {
428
+ super.connectedCallback();
429
+ // After the first render, which on a server-rendered page has to match the server's markup.
430
+ if (!isServer)
431
+ void this.updateComplete.then(() => this.isConnected && this.startClock());
432
+ }
433
+ startClock() {
434
+ clearInterval(this.timer);
435
+ this.now = new Date();
436
+ this.timer = setInterval(() => (this.now = new Date()), 60_000);
437
+ }
438
+ disconnectedCallback() {
439
+ super.disconnectedCallback();
440
+ clearInterval(this.timer);
441
+ this.endDrag();
442
+ }
443
+ // ---- derived values ----
444
+ get slotLength() {
445
+ const value = Number(this.slotMinutes);
446
+ return Number.isFinite(value) && value >= 5 ? Math.min(240, Math.round(value)) : 30;
447
+ }
448
+ get slotCount() {
449
+ return Math.ceil(DAY_MINUTES / this.slotLength);
450
+ }
451
+ get anchor() {
452
+ return parseISODate(this.date) ?? today();
453
+ }
454
+ get weekStart() {
455
+ const value = this.firstDayOfWeek;
456
+ return typeof value === "number" && Number.isFinite(value) ? ((Math.round(value) % 7) + 7) % 7 : localeFirstDayOfWeek(this.localize.lang);
457
+ }
458
+ get agendaLength() {
459
+ const value = Math.round(Number(this.agendaDays));
460
+ return Number.isFinite(value) && value > 0 ? Math.min(value, 366) : 7;
461
+ }
462
+ /** The first day shown and how many days, for a view around `anchor`. */
463
+ rangeOf(view, anchor) {
464
+ if (view === "week")
465
+ return { first: startOfWeek(anchor, this.weekStart), days: 7 };
466
+ if (view === "day")
467
+ return { first: anchor, days: 1 };
468
+ if (view === "agenda")
469
+ return { first: anchor, days: this.agendaLength };
470
+ const first = startOfWeek({ year: anchor.year, month: anchor.month, day: 1 }, this.weekStart);
471
+ const last = { year: anchor.year, month: anchor.month, day: new Date(anchor.year, anchor.month + 1, 0).getDate() };
472
+ return { first, days: Math.ceil((daysBetween(first, last) + 1) / 7) * 7 };
473
+ }
474
+ /** The first and (exclusive) last instant on screen. */
475
+ get visibleRange() {
476
+ const { first, days } = this.rangeOf(this.view, this.anchor);
477
+ return { start: toDate(first), end: toDate(addDays(first, days)) };
478
+ }
479
+ inPeriod(date) {
480
+ if (this.view === "month")
481
+ return date.year === this.anchor.year && date.month === this.anchor.month;
482
+ const { first, days } = this.rangeOf(this.view, this.anchor);
483
+ const offset = daysBetween(first, date);
484
+ return offset >= 0 && offset < days;
485
+ }
486
+ get timeGrid() {
487
+ return this.view === "week" || this.view === "day";
488
+ }
489
+ // ---- lifecycle ----
490
+ willUpdate(changed) {
491
+ if (changed.has("events") || changed.has("readonly") || !this.hasUpdated) {
492
+ this.normalized = normalizeEvents(this.events, this.readonly);
493
+ this.byKey = new Map();
494
+ for (const event of this.normalized)
495
+ if (!this.byKey.has(event.key))
496
+ this.byKey.set(event.key, event);
497
+ if (this.pendingEventFocus)
498
+ this.followMovedEvent(this.pendingEventFocus);
499
+ }
500
+ if (!this.hasUpdated || changed.has("slotMinutes")) {
501
+ if (this.focusSlot >= this.slotCount || !this.hasUpdated)
502
+ this.focusSlot = this.defaultSlot();
503
+ }
504
+ }
505
+ defaultSlot() {
506
+ const hour = Math.max(0, Math.min(23, Math.round(Number(this.scrollToHour) || 0)));
507
+ return Math.min(this.slotCount - 1, Math.floor((hour * 60) / this.slotLength));
508
+ }
509
+ updated() {
510
+ if (this.timeGrid && this.scrolledView !== this.view) {
511
+ this.scrolledView = this.view;
512
+ this.scrollToDefaultHour();
513
+ }
514
+ else if (!this.timeGrid) {
515
+ this.scrolledView = null;
516
+ }
517
+ const target = this.focusAfterUpdate;
518
+ this.focusAfterUpdate = null;
519
+ if (!target)
520
+ return;
521
+ if (target === "cell")
522
+ this.renderRoot.querySelector('[data-cell][tabindex="0"]')?.focus();
523
+ else if (target === "agenda")
524
+ this.renderRoot.querySelector('.agenda-event[tabindex="0"]')?.focus();
525
+ else {
526
+ const chips = [...this.renderRoot.querySelectorAll(".event")].filter((chip) => chip.dataset.key === target.key);
527
+ (chips.find((chip) => chip.tabIndex === 0) ?? chips[0])?.focus();
528
+ }
529
+ }
530
+ scrollToDefaultHour() {
531
+ const grid = this.renderRoot.querySelector(".time-grid");
532
+ const row = this.renderRoot.querySelector(`[data-row="${this.defaultSlot()}"]`);
533
+ const head = this.renderRoot.querySelector(".head");
534
+ if (grid && row)
535
+ grid.scrollTop = row.offsetTop - (head?.offsetHeight ?? 0) - 8;
536
+ }
537
+ /** After an `event-change` the app accepted: focus and announce the event where it is now. */
538
+ followMovedEvent(key) {
539
+ this.pendingEventFocus = null;
540
+ const event = this.byKey.get(key);
541
+ if (!event)
542
+ return;
543
+ const day = fromDate(event.start);
544
+ this.focusSlot = event.allDay ? -1 : Math.floor(minutesInto(day, event.start) / this.slotLength);
545
+ this.setDate(day);
546
+ this.announcement = this.eventLabel(event);
547
+ this.focusAfterUpdate = { key };
548
+ }
549
+ // ---- public methods ----
550
+ /** Shows the next month, week, day or agenda period. */
551
+ next() {
552
+ this.step(1);
553
+ }
554
+ /** Shows the previous period. */
555
+ previous() {
556
+ this.step(-1);
557
+ }
558
+ /** Shows the period with today in it. */
559
+ goToToday() {
560
+ this.setDate(today());
561
+ }
562
+ step(direction) {
563
+ const anchor = this.anchor;
564
+ if (this.view === "month")
565
+ this.setDate(addMonths(anchor, direction));
566
+ else
567
+ this.setDate(addDays(anchor, direction * (this.view === "week" ? 7 : this.view === "day" ? 1 : this.agendaLength)));
568
+ }
569
+ /** Moves the focused day, firing `navigate` when the period on screen changes. */
570
+ setDate(date, view = this.view) {
571
+ const before = this.visibleRange;
572
+ const viewChanged = view !== this.view;
573
+ this.view = view;
574
+ this.date = formatISODate(date);
575
+ this.agendaIndex = 0;
576
+ const after = this.visibleRange;
577
+ if (viewChanged || before.start.getTime() !== after.start.getTime() || before.end.getTime() !== after.end.getTime()) {
578
+ this.dispatchEvent(new CustomEvent("navigate", {
579
+ detail: { view, date: this.date, ...after },
580
+ bubbles: true,
581
+ composed: true,
582
+ }));
583
+ }
584
+ }
585
+ // ---- labels ----
586
+ periodTitle() {
587
+ const lang = this.localize.lang;
588
+ const { first, days } = this.rangeOf(this.view, this.anchor);
589
+ if (this.view === "month")
590
+ return format(lang, "monthTitle", this.anchor);
591
+ if (this.view === "day")
592
+ return format(lang, "dayTitle", first);
593
+ return range(lang, "rangeTitle", first, addDays(first, days - 1));
594
+ }
595
+ eventTime(event) {
596
+ const lang = this.localize.lang;
597
+ if (event.allDay) {
598
+ const dates = range(lang, "eventDate", event.start, toDate(lastDay(event)));
599
+ return this.localize.term("calendar.allDayRange", "{dates}, all day", { dates });
600
+ }
601
+ return range(lang, "eventDateTime", event.start, event.end);
602
+ }
603
+ eventLabel(event) {
604
+ const title = event.title || this.localize.term("calendar.untitled", "(No title)");
605
+ return this.localize.term("calendar.eventLabel", "{title}, {time}", { title, time: this.eventTime(event) });
606
+ }
607
+ dayLabel(date, count) {
608
+ if (!count)
609
+ return date;
610
+ return this.localize.term("calendar.dayWithEvents", { one: "{date}, {count} event", other: "{date}, {count} events" }, { date, count });
611
+ }
612
+ navLabel(direction) {
613
+ const t = this.localize;
614
+ const previous = direction === "previous";
615
+ switch (this.view) {
616
+ case "week":
617
+ return previous ? t.term("calendar.previousWeek", "Previous week") : t.term("calendar.nextWeek", "Next week");
618
+ case "day":
619
+ return previous ? t.term("calendar.previousDay", "Previous day") : t.term("calendar.nextDay", "Next day");
620
+ case "agenda":
621
+ return previous ? t.term("calendar.previousPeriod", "Previous period") : t.term("calendar.nextPeriod", "Next period");
622
+ default:
623
+ return previous ? t.term("calendar.previousMonth", "Previous month") : t.term("calendar.nextMonth", "Next month");
624
+ }
625
+ }
626
+ viewLabel(view) {
627
+ const t = this.localize;
628
+ if (view === "week")
629
+ return t.term("calendar.week", "Week");
630
+ if (view === "day")
631
+ return t.term("calendar.day", "Day");
632
+ if (view === "agenda")
633
+ return t.term("calendar.agenda", "Agenda");
634
+ return t.term("calendar.month", "Month");
635
+ }
636
+ // ---- rendering ----
637
+ render() {
638
+ const title = this.periodTitle();
639
+ let body;
640
+ if (this.view === "agenda")
641
+ body = this.renderAgenda(title);
642
+ else if (this.timeGrid)
643
+ body = this.renderTimeGrid(title);
644
+ else
645
+ body = this.renderMonth(title);
646
+ return html `
647
+ ${this.renderToolbar(title)} ${body}
648
+ <div class="visually-hidden" role="status">${this.announcement}</div>
649
+ `;
650
+ }
651
+ renderToolbar(title) {
652
+ const t = this.localize;
653
+ return html `
654
+ <div class="toolbar" part="toolbar">
655
+ <div class="nav">
656
+ <button class="today-button" type="button" @click=${this.handleToday}>${t.term("calendar.today", "Today")}</button>
657
+ <button class="icon-button" type="button" data-nav="previous" aria-label=${this.navLabel("previous")} @click=${this.handlePrevious}>
658
+ ${previousIcon}
659
+ </button>
660
+ <button class="icon-button" type="button" data-nav="next" aria-label=${this.navLabel("next")} @click=${this.handleNext}>${nextIcon}</button>
661
+ </div>
662
+ <h2 class="title" part="title" aria-live="polite">${title}</h2>
663
+ <lit-material-segmented-button-group class="views" part="views" aria-label=${t.term("calendar.view", "Calendar view")} @change=${this.handleViewChange}>
664
+ ${VIEWS.map((view) => html `<lit-material-segmented-button data-view=${view} ?selected=${view === this.view}>${this.viewLabel(view)}</lit-material-segmented-button>`)}
665
+ </lit-material-segmented-button-group>
666
+ </div>
667
+ `;
668
+ }
669
+ /** The events to lay out, with a drag preview in place of the event being dragged. */
670
+ get displayed() {
671
+ const preview = this.preview;
672
+ if (!preview)
673
+ return this.normalized;
674
+ return this.normalized.map((event) => (event.key === preview.key ? { ...event, start: preview.start, end: preview.end } : event));
675
+ }
676
+ renderChip(event, focusable, classes, props, content) {
677
+ const dragging = this.preview?.key === event.key;
678
+ return html `<button
679
+ class="event ${classes} ${dragging ? "dragging" : ""} ${event.editable ? "editable" : ""}"
680
+ part="event"
681
+ type="button"
682
+ data-key=${event.key}
683
+ tabindex=${focusable ? "0" : "-1"}
684
+ aria-label=${this.eventLabel(event)}
685
+ style=${cssProps({ ...props, "--event-color": event.color })}
686
+ >
687
+ ${content}
688
+ </button>`;
689
+ }
690
+ renderMonth(title) {
691
+ const lang = this.localize.lang;
692
+ const anchor = this.anchor;
693
+ const { first, days } = this.rangeOf("month", anchor);
694
+ const now = today();
695
+ const maxLanes = Math.max(1, Math.round(Number(this.dayMaxEvents)) || 3);
696
+ const events = this.displayed;
697
+ const weeks = [];
698
+ for (let i = 0; i < days; i += 7)
699
+ weeks.push(Array.from({ length: 7 }, (_, d) => addDays(first, i + d)));
700
+ const header = weeks[0];
701
+ return html `
702
+ <div
703
+ class="month"
704
+ part="grid"
705
+ role="grid"
706
+ aria-label=${title}
707
+ style=${cssProps({ "--max-lanes": maxLanes })}
708
+ @keydown=${this.handleGridKeydown}
709
+ @click=${this.handleGridClick}
710
+ @pointerdown=${this.handlePointerDown}
711
+ @focusin=${this.handleFocusIn}
712
+ >
713
+ <div class="row weekdays" role="row">
714
+ ${header.map((day) => html `<div class="weekday" role="columnheader" aria-label=${format(lang, "weekdayLong", day)}>${format(lang, "weekdayShort", day)}</div>`)}
715
+ </div>
716
+ ${weeks.map((week) => {
717
+ const segments = layoutSpans(events, week[0], 7);
718
+ return html `<div class="row week" role="row">
719
+ ${week.map((day, column) => {
720
+ const iso = formatISODate(day);
721
+ const focused = sameDate(day, anchor);
722
+ const covering = segments.filter((s) => s.startColumn <= column && s.endColumn >= column);
723
+ const hidden = covering.filter((s) => s.lane >= maxLanes).length;
724
+ const label = this.dayLabel(format(lang, "fullDate", day), covering.length);
725
+ const outside = day.month !== anchor.month;
726
+ return html `<div
727
+ class="day ${outside ? "outside" : ""} ${sameDate(day, now) ? "today" : ""}"
728
+ role="gridcell"
729
+ data-cell
730
+ data-date=${iso}
731
+ tabindex=${focused ? "0" : "-1"}
732
+ aria-label=${label}
733
+ aria-current=${sameDate(day, now) ? "date" : nothing}
734
+ >
735
+ <span class="day-number" aria-hidden="true">${dayNumber(lang, day.day)}</span>
736
+ ${segments
737
+ .filter((s) => s.startColumn === column && s.lane < maxLanes)
738
+ .map((s) => {
739
+ const bar = s.item.allDay || s.endColumn > s.startColumn || s.continuesAfter;
740
+ const classes = `${bar ? "bar" : "timed"} ${s.continuesBefore ? "continues-before" : ""} ${s.continuesAfter ? "continues-after" : ""}`;
741
+ return this.renderChip(s.item, focused, classes, { "--lane": s.lane, "--span": s.endColumn - s.startColumn + 1 }, html `${bar || s.item.allDay ? nothing : html `<span class="time">${format(lang, "time", s.item.start)}</span>`}<span class="event-title"
742
+ >${s.item.title || this.localize.term("calendar.untitled", "(No title)")}</span
743
+ >`);
744
+ })}
745
+ ${hidden
746
+ ? html `<button
747
+ class="more"
748
+ type="button"
749
+ data-date=${iso}
750
+ tabindex=${focused ? "0" : "-1"}
751
+ aria-label=${this.localize.term("calendar.moreLabel", { one: "{count} more event, {date}", other: "{count} more events, {date}" }, { count: hidden, date: format(lang, "fullDate", day) })}
752
+ >
753
+ ${this.localize.term("calendar.more", { one: "+{count} more", other: "+{count} more" }, { count: hidden })}
754
+ </button>`
755
+ : nothing}
756
+ </div>`;
757
+ })}
758
+ </div>`;
759
+ })}
760
+ </div>
761
+ `;
762
+ }
763
+ renderTimeGrid(title) {
764
+ const lang = this.localize.lang;
765
+ const t = this.localize;
766
+ const anchor = this.anchor;
767
+ const { first, days } = this.rangeOf(this.view, anchor);
768
+ const dayList = Array.from({ length: days }, (_, i) => addDays(first, i));
769
+ const now = today();
770
+ const slot = this.slotLength;
771
+ const events = this.displayed;
772
+ const allDayEvents = events.filter((e) => e.allDay);
773
+ const timedEvents = events.filter((e) => !e.allDay);
774
+ const spans = layoutSpans(allDayEvents, first, days);
775
+ const lanes = spans.reduce((max, s) => Math.max(max, s.lane + 1), 1);
776
+ // Timed segments by "day:slot" of the slot each starts in.
777
+ const bySlot = new Map();
778
+ const countBySlot = new Map();
779
+ dayList.forEach((day, d) => {
780
+ for (const segment of layoutDay(timedEvents, day)) {
781
+ const index = Math.min(this.slotCount - 1, Math.floor(segment.top / slot));
782
+ const key = `${d}:${index}`;
783
+ bySlot.set(key, [...(bySlot.get(key) ?? []), segment]);
784
+ const last = Math.min(this.slotCount - 1, Math.ceil(segment.bottom / slot) - 1);
785
+ for (let s = index; s <= last; s++)
786
+ countBySlot.set(`${d}:${s}`, (countBySlot.get(`${d}:${s}`) ?? 0) + 1);
787
+ }
788
+ });
789
+ const nowMinutes = this.now ? this.now.getHours() * 60 + this.now.getMinutes() : -1;
790
+ const nowDay = this.now ? fromDate(this.now) : undefined;
791
+ const selection = this.selection;
792
+ const fullDates = dayList.map((day) => format(lang, "fullDate", day));
793
+ const allDayLabel = t.term("calendar.allDay", "All day");
794
+ const slots = Array.from({ length: this.slotCount }, (_, s) => s);
795
+ return html `
796
+ <div
797
+ class="time-grid"
798
+ part="grid"
799
+ role="grid"
800
+ aria-label=${title}
801
+ style=${cssProps({ "--days": days })}
802
+ @keydown=${this.handleGridKeydown}
803
+ @click=${this.handleGridClick}
804
+ @pointerdown=${this.handlePointerDown}
805
+ @focusin=${this.handleFocusIn}
806
+ >
807
+ <div class="head" role="rowgroup">
808
+ <div class="row day-headers" role="row">
809
+ <div class="corner" role="columnheader"><span class="visually-hidden">${t.term("calendar.time", "Time")}</span></div>
810
+ ${dayList.map((day, d) => html `<div class="day-header ${sameDate(day, now) ? "today" : ""}" role="columnheader" aria-label=${fullDates[d]}>
811
+ <span class="weekday" aria-hidden="true">${format(lang, "weekdayShort", day)}</span>
812
+ <span class="date-number" aria-hidden="true">${dayNumber(lang, day.day)}</span>
813
+ </div>`)}
814
+ </div>
815
+ <div class="row all-day" role="row" style=${cssProps({ "--lanes": lanes })}>
816
+ <div class="time-label all-day-label" role="rowheader">${allDayLabel}</div>
817
+ ${dayList.map((day, d) => {
818
+ const focused = sameDate(day, anchor) && this.focusSlot === -1;
819
+ const covering = spans.filter((s) => s.startColumn <= d && s.endColumn >= d).length;
820
+ return html `<div
821
+ class="cell all-day-cell ${sameDate(day, now) ? "today" : ""}"
822
+ role="gridcell"
823
+ data-cell
824
+ data-date=${formatISODate(day)}
825
+ data-slot="-1"
826
+ tabindex=${focused ? "0" : "-1"}
827
+ aria-label=${this.dayLabel(t.term("calendar.dateTime", "{date}, {time}", { date: fullDates[d], time: allDayLabel }), covering)}
828
+ >
829
+ ${spans
830
+ .filter((s) => s.startColumn === d)
831
+ .map((s) => this.renderChip(s.item, focused, `bar ${s.continuesBefore ? "continues-before" : ""} ${s.continuesAfter ? "continues-after" : ""}`, { "--lane": s.lane, "--span": s.endColumn - s.startColumn + 1 }, html `<span class="event-title">${s.item.title || t.term("calendar.untitled", "(No title)")}</span>`))}
832
+ </div>`;
833
+ })}
834
+ </div>
835
+ </div>
836
+ <div class="body" role="rowgroup">
837
+ ${slots.map((s) => {
838
+ const minutes = s * slot;
839
+ const hourStart = minutes % 60 === 0;
840
+ const time = format(lang, "time", toDate(first, minutes));
841
+ return html `<div class="row slot-row ${hourStart ? "hour" : ""}" role="row" data-row=${s}>
842
+ <div class="time-label" role="rowheader">
843
+ ${hourStart ? html `<span aria-hidden="true">${format(lang, "hour", toDate(first, minutes))}</span>` : nothing}
844
+ <span class="visually-hidden">${time}</span>
845
+ </div>
846
+ ${dayList.map((day, d) => {
847
+ const iso = formatISODate(day);
848
+ const focused = sameDate(day, anchor) && this.focusSlot === s;
849
+ const selected = !!selection && selection.date === iso && s >= selection.from && s <= selection.to;
850
+ const segments = bySlot.get(`${d}:${s}`) ?? [];
851
+ const showNow = !!nowDay && sameDate(day, nowDay) && nowMinutes >= minutes && nowMinutes < minutes + slot;
852
+ return html `<div
853
+ class="cell slot ${selected ? "selected" : ""} ${sameDate(day, now) ? "today" : ""}"
854
+ role="gridcell"
855
+ data-cell
856
+ data-date=${iso}
857
+ data-slot=${s}
858
+ tabindex=${focused ? "0" : "-1"}
859
+ aria-selected=${selected ? "true" : nothing}
860
+ aria-label=${this.dayLabel(t.term("calendar.dateTime", "{date}, {time}", { date: fullDates[d], time }), countBySlot.get(`${d}:${s}`) ?? 0)}
861
+ >
862
+ ${segments.map((segment) => this.renderChip(segment.item, focused, `timed-block ${segment.continuesBefore ? "continues-before" : ""} ${segment.continuesAfter ? "continues-after" : ""} ${segment.bottom - segment.top <= slot ? "short" : ""}`, {
863
+ "--offset": (segment.top - s * slot) / slot,
864
+ "--length": (segment.bottom - segment.top) / slot,
865
+ "--col": segment.column,
866
+ "--cols": segment.columns,
867
+ }, html `<span class="event-title">${segment.item.title || t.term("calendar.untitled", "(No title)")}</span>
868
+ <span class="time">${range(lang, "time", segment.item.start, segment.item.end)}</span>
869
+ ${segment.item.editable && !segment.continuesAfter ? html `<span class="resize" data-resize aria-hidden="true"></span>` : nothing}`))}
870
+ ${showNow ? html `<div class="now" aria-hidden="true" style=${cssProps({ "--offset": (nowMinutes - minutes) / slot })}></div>` : nothing}
871
+ </div>`;
872
+ })}
873
+ </div>`;
874
+ })}
875
+ </div>
876
+ </div>
877
+ `;
878
+ }
879
+ agendaItems() {
880
+ const { first, days } = this.rangeOf("agenda", this.anchor);
881
+ const items = [];
882
+ for (let i = 0; i < days; i++) {
883
+ const day = addDays(first, i);
884
+ const onDay = this.normalized.filter((event) => coversDay(event, day));
885
+ onDay.sort((a, b) => Number(b.allDay) - Number(a.allDay) || a.start.getTime() - b.start.getTime() || a.end.getTime() - b.end.getTime());
886
+ for (const event of onDay)
887
+ items.push({ day, event });
888
+ }
889
+ return items;
890
+ }
891
+ renderAgenda(title) {
892
+ const lang = this.localize.lang;
893
+ const t = this.localize;
894
+ const items = this.agendaItems();
895
+ const active = Math.min(this.agendaIndex, items.length - 1);
896
+ const groups = [];
897
+ items.forEach(({ day, event }, index) => {
898
+ const last = groups[groups.length - 1];
899
+ if (last && sameDate(last.day, day))
900
+ last.entries.push({ event, index });
901
+ else
902
+ groups.push({ day, entries: [{ event, index }] });
903
+ });
904
+ const now = today();
905
+ return html `
906
+ <section class="agenda" part="agenda" aria-label=${title} @keydown=${this.handleAgendaKeydown} @click=${this.handleAgendaClick}>
907
+ ${items.length
908
+ ? groups.map(({ day, entries }) => {
909
+ const id = `agenda-${formatISODate(day)}`;
910
+ return html `<div class="agenda-day ${sameDate(day, now) ? "today" : ""}">
911
+ <h3 class="agenda-date" id=${id} aria-label=${format(lang, "fullDate", day)}>${format(lang, "agendaDate", day)}</h3>
912
+ <ul aria-labelledby=${id}>
913
+ ${entries.map(({ event, index }) => html `<li>
914
+ <button
915
+ class="event agenda-event"
916
+ part="event"
917
+ type="button"
918
+ data-key=${event.key}
919
+ data-index=${index}
920
+ tabindex=${index === active ? "0" : "-1"}
921
+ aria-label=${this.eventLabel(event)}
922
+ style=${cssProps({ "--event-color": event.color })}
923
+ >
924
+ <span class="agenda-time">${event.allDay ? t.term("calendar.allDay", "All day") : range(lang, "time", event.start, event.end)}</span>
925
+ <span class="dot" aria-hidden="true"></span>
926
+ <span class="event-title">${event.title || t.term("calendar.untitled", "(No title)")}</span>
927
+ </button>
928
+ </li>`)}
929
+ </ul>
930
+ </div>`;
931
+ })
932
+ : html `<p class="empty">${t.term("calendar.noEvents", "No events")}</p>`}
933
+ </section>
934
+ `;
935
+ }
936
+ // ---- grid interaction ----
937
+ cellInfo(cell) {
938
+ const date = parseISODate(cell.dataset.date);
939
+ if (!date)
940
+ return undefined;
941
+ return { date, slot: cell.dataset.slot === undefined ? null : Number(cell.dataset.slot) };
942
+ }
943
+ slotTimes(info, toSlot = info.slot) {
944
+ if (info.slot === null || info.slot < 0)
945
+ return { start: toDate(info.date), end: toDate(addDays(info.date, 1)), allDay: true };
946
+ const from = Math.min(info.slot, toSlot ?? info.slot);
947
+ const to = Math.max(info.slot, toSlot ?? info.slot);
948
+ return {
949
+ start: toDate(info.date, from * this.slotLength),
950
+ end: toDate(info.date, Math.min(DAY_MINUTES, (to + 1) * this.slotLength)),
951
+ allDay: false,
952
+ };
953
+ }
954
+ emitSlot(detail) {
955
+ this.dispatchEvent(new CustomEvent("slot-click", { detail, bubbles: true, composed: true }));
956
+ }
957
+ emitEventClick(event) {
958
+ this.dispatchEvent(new CustomEvent("event-click", { detail: { event: event.source }, bubbles: true, composed: true }));
959
+ }
960
+ focusCellLater() {
961
+ this.focusAfterUpdate = "cell";
962
+ }
963
+ /** Alt+arrows move an event, Alt+Shift+arrows change its end. Returns whether the key was handled. */
964
+ handleEventKeydown(e, chip) {
965
+ if (!e.altKey || e.ctrlKey || e.metaKey)
966
+ return false;
967
+ if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(e.key))
968
+ return false;
969
+ e.preventDefault();
970
+ const event = this.byKey.get(chip.dataset.key ?? "");
971
+ if (!event || !event.editable)
972
+ return true;
973
+ const forward = isRtl(this) ? -1 : 1;
974
+ let days = 0;
975
+ let minutes = 0;
976
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight")
977
+ days = e.key === "ArrowRight" ? forward : -forward;
978
+ else {
979
+ const down = e.key === "ArrowDown" ? 1 : -1;
980
+ if (this.timeGrid && !event.allDay)
981
+ minutes = down * this.slotLength;
982
+ else
983
+ days = down * 7;
984
+ }
985
+ let { start, end } = event;
986
+ if (e.shiftKey) {
987
+ end = shift(end, days, minutes);
988
+ const min = event.allDay ? toDate(addDays(fromDate(start), 1)) : new Date(start.getTime() + Math.min(this.slotLength, 15) * MINUTE);
989
+ if (end.getTime() < min.getTime())
990
+ end = min;
991
+ }
992
+ else {
993
+ start = shift(start, days, minutes);
994
+ end = shift(end, days, minutes);
995
+ }
996
+ this.requestChange(event, start, end, true);
997
+ return true;
998
+ }
999
+ requestChange(event, start, end, keyboard) {
1000
+ if (start.getTime() === event.start.getTime() && end.getTime() === event.end.getTime())
1001
+ return false;
1002
+ const accepted = this.dispatchEvent(new CustomEvent("event-change", {
1003
+ detail: { event: event.source, start, end, allDay: event.allDay },
1004
+ bubbles: true,
1005
+ composed: true,
1006
+ cancelable: true,
1007
+ }));
1008
+ if (accepted && keyboard)
1009
+ this.pendingEventFocus = event.key;
1010
+ return accepted;
1011
+ }
1012
+ // ---- pointer: drag to create, move and resize ----
1013
+ cellAt(x, y) {
1014
+ const root = this.renderRoot;
1015
+ const elements = typeof root.elementsFromPoint === "function" ? root.elementsFromPoint(x, y) : [];
1016
+ const cell = elements.find((el) => el instanceof HTMLElement && el.hasAttribute("data-cell"));
1017
+ return cell ? this.cellInfo(cell) : undefined;
1018
+ }
1019
+ endDrag() {
1020
+ this.drag = null;
1021
+ this.preview = null;
1022
+ this.removeEventListener("pointermove", this.handlePointerMove);
1023
+ this.removeEventListener("pointerup", this.handlePointerUp);
1024
+ this.removeEventListener("pointercancel", this.handlePointerCancel);
1025
+ }
1026
+ };
1027
+ __decorate([
1028
+ property({ reflect: true })
1029
+ ], LitMaterialCalendar.prototype, "view", void 0);
1030
+ __decorate([
1031
+ property()
1032
+ ], LitMaterialCalendar.prototype, "date", void 0);
1033
+ __decorate([
1034
+ property({ type: Array })
1035
+ ], LitMaterialCalendar.prototype, "events", void 0);
1036
+ __decorate([
1037
+ property({ type: Number, attribute: "first-day-of-week" })
1038
+ ], LitMaterialCalendar.prototype, "firstDayOfWeek", void 0);
1039
+ __decorate([
1040
+ property({ type: Number, attribute: "slot-minutes" })
1041
+ ], LitMaterialCalendar.prototype, "slotMinutes", void 0);
1042
+ __decorate([
1043
+ property({ type: Number, attribute: "scroll-to-hour" })
1044
+ ], LitMaterialCalendar.prototype, "scrollToHour", void 0);
1045
+ __decorate([
1046
+ property({ type: Number, attribute: "day-max-events" })
1047
+ ], LitMaterialCalendar.prototype, "dayMaxEvents", void 0);
1048
+ __decorate([
1049
+ property({ type: Number, attribute: "agenda-days" })
1050
+ ], LitMaterialCalendar.prototype, "agendaDays", void 0);
1051
+ __decorate([
1052
+ property({ type: Boolean, reflect: true })
1053
+ ], LitMaterialCalendar.prototype, "readonly", void 0);
1054
+ __decorate([
1055
+ state()
1056
+ ], LitMaterialCalendar.prototype, "focusSlot", void 0);
1057
+ __decorate([
1058
+ state()
1059
+ ], LitMaterialCalendar.prototype, "selection", void 0);
1060
+ __decorate([
1061
+ state()
1062
+ ], LitMaterialCalendar.prototype, "preview", void 0);
1063
+ __decorate([
1064
+ state()
1065
+ ], LitMaterialCalendar.prototype, "agendaIndex", void 0);
1066
+ __decorate([
1067
+ state()
1068
+ ], LitMaterialCalendar.prototype, "announcement", void 0);
1069
+ __decorate([
1070
+ state()
1071
+ ], LitMaterialCalendar.prototype, "now", void 0);
1072
+ LitMaterialCalendar = __decorate([
1073
+ customElement("lit-material-calendar")
1074
+ ], LitMaterialCalendar);
1075
+ export { LitMaterialCalendar };
1076
+ //# sourceMappingURL=calendar.js.map