ngwr 10.0.0 → 10.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -8
- package/action-sheet/styles/_index.scss +6 -1
- package/badge/styles/_index.scss +1 -1
- package/badge/styles/_tag.scss +2 -2
- package/border-glow/styles/_index.scss +5 -0
- package/button/styles/_index.scss +6 -6
- package/calendar/styles/_index.scss +9 -1
- package/circular-text/styles/_index.scss +6 -1
- package/density/styles/_index.scss +3 -1
- package/dialog/styles/_index.scss +1 -2
- package/drag-drop/styles/_index.scss +20 -7
- package/event-calendar/styles/_index.scss +284 -0
- package/falling-text/styles/_index.scss +5 -0
- package/fesm2022/ngwr-alert.mjs +8 -3
- package/fesm2022/ngwr-alert.mjs.map +1 -1
- package/fesm2022/ngwr-back-top.mjs +6 -4
- package/fesm2022/ngwr-back-top.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow.mjs +12 -9
- package/fesm2022/ngwr-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-breadcrumbs.mjs +2 -2
- package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
- package/fesm2022/ngwr-calendar.mjs +5 -3
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-card.mjs +2 -2
- package/fesm2022/ngwr-card.mjs.map +1 -1
- package/fesm2022/ngwr-carousel.mjs +17 -4
- package/fesm2022/ngwr-carousel.mjs.map +1 -1
- package/fesm2022/ngwr-cascader.mjs +31 -4
- package/fesm2022/ngwr-cascader.mjs.map +1 -1
- package/fesm2022/ngwr-checkbox.mjs +12 -3
- package/fesm2022/ngwr-checkbox.mjs.map +1 -1
- package/fesm2022/ngwr-circular-text.mjs +2 -2
- package/fesm2022/ngwr-circular-text.mjs.map +1 -1
- package/fesm2022/ngwr-command-palette.mjs +7 -3
- package/fesm2022/ngwr-command-palette.mjs.map +1 -1
- package/fesm2022/ngwr-counter.mjs +9 -1
- package/fesm2022/ngwr-counter.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker.mjs +23 -6
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/ngwr-descriptions.mjs +5 -4
- package/fesm2022/ngwr-descriptions.mjs.map +1 -1
- package/fesm2022/ngwr-drag-drop.mjs +2 -2
- package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +4 -0
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar.mjs +723 -0
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -0
- package/fesm2022/ngwr-falling-text.mjs +2 -2
- package/fesm2022/ngwr-falling-text.mjs.map +1 -1
- package/fesm2022/ngwr-file-upload.mjs +2 -2
- package/fesm2022/ngwr-file-upload.mjs.map +1 -1
- package/fesm2022/ngwr-form.mjs +258 -42
- package/fesm2022/ngwr-form.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text.mjs +15 -1
- package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-gauge.mjs +8 -3
- package/fesm2022/ngwr-gauge.mjs.map +1 -1
- package/fesm2022/ngwr-glitch-text.mjs +2 -2
- package/fesm2022/ngwr-glitch-text.mjs.map +1 -1
- package/fesm2022/ngwr-gradient-text.mjs +2 -2
- package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +73 -5
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +73 -5
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n.mjs +20 -2
- package/fesm2022/ngwr-i18n.mjs.map +1 -1
- package/fesm2022/ngwr-icon.mjs +5 -0
- package/fesm2022/ngwr-icon.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +19 -3
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-input.mjs +48 -6
- package/fesm2022/ngwr-input.mjs.map +1 -1
- package/fesm2022/ngwr-knob.mjs +8 -3
- package/fesm2022/ngwr-knob.mjs.map +1 -1
- package/fesm2022/ngwr-lightbox.mjs +32 -3
- package/fesm2022/ngwr-lightbox.mjs.map +1 -1
- package/fesm2022/ngwr-line-chart.mjs +3 -3
- package/fesm2022/ngwr-line-chart.mjs.map +1 -1
- package/fesm2022/ngwr-list.mjs +2 -2
- package/fesm2022/ngwr-list.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar.mjs +2 -2
- package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
- package/fesm2022/ngwr-marquee.mjs +2 -2
- package/fesm2022/ngwr-marquee.mjs.map +1 -1
- package/fesm2022/ngwr-media.mjs +3 -0
- package/fesm2022/ngwr-media.mjs.map +1 -1
- package/fesm2022/ngwr-meter-group.mjs +14 -3
- package/fesm2022/ngwr-meter-group.mjs.map +1 -1
- package/fesm2022/ngwr-page-header.mjs +2 -2
- package/fesm2022/ngwr-page-header.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +1 -1
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-progress.mjs +8 -2
- package/fesm2022/ngwr-progress.mjs.map +1 -1
- package/fesm2022/ngwr-qr.mjs.map +1 -1
- package/fesm2022/ngwr-rating.mjs +6 -4
- package/fesm2022/ngwr-rating.mjs.map +1 -1
- package/fesm2022/ngwr-result.mjs +19 -12
- package/fesm2022/ngwr-result.mjs.map +1 -1
- package/fesm2022/ngwr-segmented.mjs +2 -2
- package/fesm2022/ngwr-segmented.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +28 -8
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text.mjs +4 -6
- package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
- package/fesm2022/ngwr-speed-dial.mjs +11 -3
- package/fesm2022/ngwr-speed-dial.mjs.map +1 -1
- package/fesm2022/ngwr-spotlight-card.mjs +2 -2
- package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
- package/fesm2022/ngwr-squircle.mjs +1 -1
- package/fesm2022/ngwr-squircle.mjs.map +1 -1
- package/fesm2022/ngwr-star-border.mjs +2 -2
- package/fesm2022/ngwr-star-border.mjs.map +1 -1
- package/fesm2022/ngwr-switch.mjs +10 -3
- package/fesm2022/ngwr-switch.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +233 -19
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs.mjs +21 -4
- package/fesm2022/ngwr-tabs.mjs.map +1 -1
- package/fesm2022/ngwr-textarea.mjs +20 -4
- package/fesm2022/ngwr-textarea.mjs.map +1 -1
- package/fesm2022/ngwr-tilt-card.mjs +2 -2
- package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
- package/fesm2022/ngwr-toolbar.mjs +2 -2
- package/fesm2022/ngwr-toolbar.mjs.map +1 -1
- package/fesm2022/ngwr-tour.mjs +264 -0
- package/fesm2022/ngwr-tour.mjs.map +1 -0
- package/fesm2022/ngwr-transfer.mjs +184 -0
- package/fesm2022/ngwr-transfer.mjs.map +1 -0
- package/fesm2022/ngwr-tree.mjs +23 -6
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-typewriter.mjs +2 -2
- package/fesm2022/ngwr-typewriter.mjs.map +1 -1
- package/fesm2022/ngwr-validators.mjs +118 -2
- package/fesm2022/ngwr-validators.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +2 -2
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/fesm2022/ngwr-virtual-scroll.mjs +2 -2
- package/fesm2022/ngwr-virtual-scroll.mjs.map +1 -1
- package/fesm2022/ngwr-waves.mjs +2 -2
- package/fesm2022/ngwr-waves.mjs.map +1 -1
- package/form/styles/_index.scss +8 -4
- package/glitch-text/styles/_index.scss +5 -0
- package/gradient-text/styles/_index.scss +5 -0
- package/i18n/en.json +74 -6
- package/i18n/ru.json +74 -6
- package/icon/styles/_index.scss +5 -0
- package/keyboard/styles/_index.scss +5 -0
- package/llms-full.txt +123 -62
- package/llms.txt +29 -8
- package/loading-bar/styles/_index.scss +5 -0
- package/marquee/styles/_index.scss +7 -2
- package/overlay/styles/_index.scss +5 -0
- package/package.json +13 -1
- package/popover/styles/_index.scss +48 -12
- package/pull-to-refresh/styles/_index.scss +5 -0
- package/result/styles/_index.scss +3 -3
- package/schematics/icon-set/index.js +80 -34
- package/schematics/icon-set/schema.json +8 -3
- package/schematics/ng-add/index.js +5 -2
- package/schematics/page/templates.js +17 -21
- package/schematics/provider/index.js +31 -13
- package/schematics/provider/schema.json +40 -24
- package/schematics/use/index.js +5 -1
- package/schematics/use/schema.json +8 -3
- package/schematics/use/symbol-map.json +6 -177
- package/segmented/styles/_index.scss +1 -1
- package/select/styles/_index.scss +7 -3
- package/shiny-text/styles/_index.scss +5 -0
- package/speed-dial/styles/_index.scss +12 -1
- package/spotlight-card/styles/_index.scss +5 -0
- package/squircle/styles/_index.scss +5 -0
- package/star-border/styles/_index.scss +5 -0
- package/statistic/styles/_index.scss +2 -2
- package/styles/_animations.scss +6 -0
- package/styles.scss +3 -0
- package/table/styles/_index.scss +100 -2
- package/theme/styles/_colors.scss +43 -1
- package/theme/styles/_dark.scss +18 -6
- package/theme/styles/_focus.scss +2 -8
- package/tilt-card/styles/_index.scss +5 -0
- package/toast/styles/_index.scss +6 -3
- package/tour/styles/_index.scss +63 -0
- package/transfer/styles/_index.scss +105 -0
- package/types/ngwr-alert.d.ts +4 -1
- package/types/ngwr-avatar.d.ts +1 -1
- package/types/ngwr-back-top.d.ts +3 -2
- package/types/ngwr-badge.d.ts +1 -1
- package/types/ngwr-border-glow.d.ts +0 -1
- package/types/ngwr-carousel.d.ts +10 -1
- package/types/ngwr-cascader.d.ts +12 -1
- package/types/ngwr-checkbox.d.ts +9 -1
- package/types/ngwr-command-palette.d.ts +4 -1
- package/types/ngwr-date-picker.d.ts +9 -1
- package/types/ngwr-density.d.ts +1 -1
- package/types/ngwr-divider.d.ts +1 -1
- package/types/ngwr-event-calendar.d.ts +358 -0
- package/types/ngwr-form.d.ts +177 -20
- package/types/ngwr-fuzzy-text.d.ts +8 -0
- package/types/ngwr-gauge.d.ts +4 -1
- package/types/ngwr-i18n.d.ts +15 -0
- package/types/ngwr-input-number.d.ts +8 -1
- package/types/ngwr-input.d.ts +50 -20
- package/types/ngwr-knob.d.ts +4 -1
- package/types/ngwr-lightbox.d.ts +7 -1
- package/types/ngwr-line-chart.d.ts +1 -1
- package/types/ngwr-media.d.ts +1 -1
- package/types/ngwr-meter-group.d.ts +9 -1
- package/types/ngwr-page-header.d.ts +2 -2
- package/types/ngwr-pagination.d.ts +1 -1
- package/types/ngwr-progress.d.ts +4 -1
- package/types/ngwr-rating.d.ts +3 -2
- package/types/ngwr-result.d.ts +12 -6
- package/types/ngwr-segmented.d.ts +6 -0
- package/types/ngwr-select.d.ts +13 -7
- package/types/ngwr-shiny-text.d.ts +2 -4
- package/types/ngwr-speed-dial.d.ts +7 -1
- package/types/ngwr-squircle.d.ts +1 -1
- package/types/ngwr-switch.d.ts +7 -1
- package/types/ngwr-table.d.ts +126 -6
- package/types/ngwr-tabs.d.ts +1 -4
- package/types/ngwr-textarea.d.ts +8 -1
- package/types/ngwr-toolbar.d.ts +2 -2
- package/types/ngwr-tour.d.ts +132 -0
- package/types/ngwr-transfer.d.ts +118 -0
- package/types/ngwr-tree.d.ts +10 -2
- package/types/ngwr-validators.d.ts +62 -2
- package/types/ngwr-version.d.ts +2 -2
- package/typewriter/styles/_index.scss +5 -0
- package/typography/styles/_index.scss +3 -3
- package/virtual-scroll/styles/_index.scss +6 -1
- package/waves/styles/_index.scss +5 -0
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
|
2
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { inject, TemplateRef, Directive, input, model, output, DOCUMENT, contentChild, signal, computed, ViewEncapsulation, Component } from '@angular/core';
|
|
5
|
+
import { WrButton, WrButtonGroup } from 'ngwr/button';
|
|
6
|
+
import { WrDateAdapter } from 'ngwr/date-adapter';
|
|
7
|
+
import { readI18nText, useI18nFormatter, useI18nText } from 'ngwr/i18n';
|
|
8
|
+
import { numAttr } from 'ngwr/utils';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @license
|
|
12
|
+
*
|
|
13
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
14
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Replace the chip's contents. The event is the implicit context; the chip
|
|
18
|
+
* itself — its button, its position and its drag handling — stays with the
|
|
19
|
+
* calendar, so a custom template cannot break the geometry.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```html
|
|
23
|
+
* <wr-event-calendar [events]="events">
|
|
24
|
+
* <ng-template wrCalendarEvent let-event>
|
|
25
|
+
* <strong>{{ event.title }}</strong> — {{ event.data.room }}
|
|
26
|
+
* </ng-template>
|
|
27
|
+
* </wr-event-calendar>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
class WrCalendarEventTemplate {
|
|
31
|
+
template = inject(TemplateRef);
|
|
32
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrCalendarEventTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
33
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.0", type: WrCalendarEventTemplate, isStandalone: true, selector: "ng-template[wrCalendarEvent]", ngImport: i0 });
|
|
34
|
+
}
|
|
35
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrCalendarEventTemplate, decorators: [{
|
|
36
|
+
type: Directive,
|
|
37
|
+
args: [{
|
|
38
|
+
selector: 'ng-template[wrCalendarEvent]',
|
|
39
|
+
}]
|
|
40
|
+
}] });
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @license
|
|
44
|
+
*
|
|
45
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
46
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
47
|
+
*/
|
|
48
|
+
const MINUTES_PER_DAY = 24 * 60;
|
|
49
|
+
const DAYS_PER_WEEK = 7;
|
|
50
|
+
const MONTH_ROWS = 6;
|
|
51
|
+
/**
|
|
52
|
+
* Month / week / day event calendar with drag to move and resize.
|
|
53
|
+
*
|
|
54
|
+
* `events` is an INPUT, never mutated: a drag emits `eventChange` describing
|
|
55
|
+
* where the event would land and stops there. Apply it to your own state and
|
|
56
|
+
* the calendar re-renders — so an unhandled output is a cancelled drag, an
|
|
57
|
+
* optimistic update is one `set`, and a rejected server write needs no rollback
|
|
58
|
+
* path inside the component.
|
|
59
|
+
*
|
|
60
|
+
* Every chip lives inside the `role="gridcell"` where it starts and reaches out
|
|
61
|
+
* with `calc()` widths or percentage heights. That is what keeps the ARIA grid
|
|
62
|
+
* valid — an events layer floating over the grid would leave rows owning
|
|
63
|
+
* something other than cells — and it means no pixel measurement anywhere.
|
|
64
|
+
*
|
|
65
|
+
* **Keyboard.** The grid is one tab stop with a roving cursor: arrows move by
|
|
66
|
+
* day (left / right) and by week or slot (up / down), `Home` / `End` jump to
|
|
67
|
+
* the ends of the row, `Enter` on an empty cell emits `slotClick`, and `Enter`
|
|
68
|
+
* on a cell holding events focuses the first chip (`Escape` returns). On a
|
|
69
|
+
* focused chip, `Alt` + arrows move the event — the keyboard equivalent of the
|
|
70
|
+
* drag, emitting the same `eventChange`.
|
|
71
|
+
*
|
|
72
|
+
* Date math and formatting go through {@link WrDateAdapter}; register one with
|
|
73
|
+
* `provideWrDateAdapter(...)` at bootstrap.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```html
|
|
77
|
+
* <wr-event-calendar
|
|
78
|
+
* editable
|
|
79
|
+
* [events]="events()"
|
|
80
|
+
* [(view)]="view"
|
|
81
|
+
* [(date)]="anchor"
|
|
82
|
+
* (eventChange)="apply($event)"
|
|
83
|
+
* (slotClick)="compose($event)"
|
|
84
|
+
* />
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* @see https://ngwr.dev/reference/components/event-calendar
|
|
88
|
+
*/
|
|
89
|
+
class WrEventCalendar {
|
|
90
|
+
/** Everything to render. Never mutated — see `eventChange`. */
|
|
91
|
+
events = input([], /* @ts-ignore */
|
|
92
|
+
...(ngDevMode ? [{ debugName: "events" }] : /* istanbul ignore next */ []));
|
|
93
|
+
/** Which span is shown. Two-way bindable. @default 'month' */
|
|
94
|
+
view = model('month', /* @ts-ignore */
|
|
95
|
+
...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
|
|
96
|
+
/** Any date inside the shown span. Two-way bindable. */
|
|
97
|
+
date = model(new Date(), /* @ts-ignore */
|
|
98
|
+
...(ngDevMode ? [{ debugName: "date" }] : /* istanbul ignore next */ []));
|
|
99
|
+
/** Which buttons the view switcher offers. Empty hides it. */
|
|
100
|
+
views = input(['month', 'week', 'day'], /* @ts-ignore */
|
|
101
|
+
...(ngDevMode ? [{ debugName: "views" }] : /* istanbul ignore next */ []));
|
|
102
|
+
/** Allow dragging chips to move and resize them. @default false */
|
|
103
|
+
editable = input(false, { ...(ngDevMode ? { debugName: "editable" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
104
|
+
/** Minutes per row in the time views — also the drag snap. @default 30 */
|
|
105
|
+
slotMinutes = input(30, { ...(ngDevMode ? { debugName: "slotMinutes" } : /* istanbul ignore next */ {}), transform: numAttr(30) });
|
|
106
|
+
/** First hour the time views show. @default 0 */
|
|
107
|
+
dayStartHour = input(0, { ...(ngDevMode ? { debugName: "dayStartHour" } : /* istanbul ignore next */ {}), transform: numAttr(0) });
|
|
108
|
+
/** First hour they do NOT show, exclusive. @default 24 */
|
|
109
|
+
dayEndHour = input(24, { ...(ngDevMode ? { debugName: "dayEndHour" } : /* istanbul ignore next */ {}), transform: numAttr(24) });
|
|
110
|
+
/** Lanes a month cell shows before collapsing the rest into “+N more”. @default 3 */
|
|
111
|
+
maxLanes = input(3, { ...(ngDevMode ? { debugName: "maxLanes" } : /* istanbul ignore next */ {}), transform: numAttr(3) });
|
|
112
|
+
/** Hide the built-in header — supply your own navigation instead. @default false */
|
|
113
|
+
hideHeader = input(false, { ...(ngDevMode ? { debugName: "hideHeader" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
114
|
+
/** A chip was activated. */
|
|
115
|
+
eventClick = output();
|
|
116
|
+
/** Empty space was activated — the slot the user pointed at. */
|
|
117
|
+
slotClick = output();
|
|
118
|
+
/** A drag or an `Alt` + arrow finished. Apply it yourself; nothing moves until you do. */
|
|
119
|
+
eventChange = output();
|
|
120
|
+
adapter = inject(WrDateAdapter);
|
|
121
|
+
document = inject(DOCUMENT);
|
|
122
|
+
chipTpl = contentChild(WrCalendarEventTemplate, /* @ts-ignore */
|
|
123
|
+
...(ngDevMode ? [{ debugName: "chipTpl" }] : /* istanbul ignore next */ []));
|
|
124
|
+
todayLabel = readI18nText('eventCalendar.today', 'Today');
|
|
125
|
+
previousLabel = readI18nText('eventCalendar.previous', 'Previous');
|
|
126
|
+
nextLabel = readI18nText('eventCalendar.next', 'Next');
|
|
127
|
+
allDayLabel = readI18nText('eventCalendar.allDay', 'All day');
|
|
128
|
+
timeLabel = readI18nText('eventCalendar.time', 'Time');
|
|
129
|
+
moreLabel = useI18nFormatter('eventCalendar.more', '+{{count}} more');
|
|
130
|
+
viewLabels = {
|
|
131
|
+
month: readI18nText('eventCalendar.month', 'Month'),
|
|
132
|
+
week: readI18nText('eventCalendar.week', 'Week'),
|
|
133
|
+
day: readI18nText('eventCalendar.day', 'Day'),
|
|
134
|
+
};
|
|
135
|
+
/** Accessible name of the grid; overridable for a page with several. */
|
|
136
|
+
ariaLabel = input(null, /* @ts-ignore */
|
|
137
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
138
|
+
resolvedAriaLabel = useI18nText(this.ariaLabel, 'eventCalendar.label', 'Calendar');
|
|
139
|
+
/**
|
|
140
|
+
* `[day, minutes]` of the roving cursor — `minutes === -1` means a month cell
|
|
141
|
+
* or the all-day band. Raw, so it survives a view switch; read
|
|
142
|
+
* {@link cursorCell}, which clamps it to what the current view actually has.
|
|
143
|
+
*/
|
|
144
|
+
cursor = signal([0, -1], /* @ts-ignore */
|
|
145
|
+
...(ngDevMode ? [{ debugName: "cursor" }] : /* istanbul ignore next */ []));
|
|
146
|
+
/**
|
|
147
|
+
* The cursor as the current view can honour it. Exactly one cell may carry
|
|
148
|
+
* `tabindex="0"`, so a stale coordinate — the 09:30 row after switching to
|
|
149
|
+
* month, day 34 after switching to week — must fall back to a real cell
|
|
150
|
+
* rather than leave the grid with no tab stop at all.
|
|
151
|
+
*/
|
|
152
|
+
cursorCell = computed(() => {
|
|
153
|
+
const [day, minutes] = this.cursor();
|
|
154
|
+
const columns = Math.max(1, this.days().length);
|
|
155
|
+
const clamped = Math.min(columns - 1, Math.max(0, day));
|
|
156
|
+
if (this.view() === 'month')
|
|
157
|
+
return [clamped, -1];
|
|
158
|
+
if (minutes === -1 && this.hasAllDay())
|
|
159
|
+
return [clamped, -1];
|
|
160
|
+
const rows = this.rows();
|
|
161
|
+
const known = rows.some(row => row.minutes === minutes);
|
|
162
|
+
return [clamped, known ? minutes : (rows[0]?.minutes ?? 0)];
|
|
163
|
+
}, /* @ts-ignore */
|
|
164
|
+
...(ngDevMode ? [{ debugName: "cursorCell" }] : /* istanbul ignore next */ []));
|
|
165
|
+
/** Non-null only while a pointer is down on a chip. @internal */
|
|
166
|
+
drag = signal(null, /* @ts-ignore */
|
|
167
|
+
...(ngDevMode ? [{ debugName: "drag" }] : /* istanbul ignore next */ []));
|
|
168
|
+
dragging = computed(() => this.drag()?.event.id ?? null, /* @ts-ignore */
|
|
169
|
+
...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
|
|
170
|
+
chipTemplate = computed(() => this.chipTpl()?.template ?? null, /* @ts-ignore */
|
|
171
|
+
...(ngDevMode ? [{ debugName: "chipTemplate" }] : /* istanbul ignore next */ []));
|
|
172
|
+
classes = computed(() => {
|
|
173
|
+
const parts = ['wr-event-calendar', `wr-event-calendar--${this.view()}`];
|
|
174
|
+
if (this.editable())
|
|
175
|
+
parts.push('wr-event-calendar--editable');
|
|
176
|
+
if (this.drag())
|
|
177
|
+
parts.push('wr-event-calendar--dragging');
|
|
178
|
+
return parts.join(' ');
|
|
179
|
+
}, /* @ts-ignore */
|
|
180
|
+
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
181
|
+
// ---------------------------------------------------------------- ranges
|
|
182
|
+
/** Inclusive-exclusive window the current view covers. */
|
|
183
|
+
range = computed(() => {
|
|
184
|
+
const anchor = this.startOfDay(this.date());
|
|
185
|
+
switch (this.view()) {
|
|
186
|
+
case 'day':
|
|
187
|
+
return [anchor, this.adapter.addDays(anchor, 1)];
|
|
188
|
+
case 'week': {
|
|
189
|
+
const start = this.startOfWeek(anchor);
|
|
190
|
+
return [start, this.adapter.addDays(start, DAYS_PER_WEEK)];
|
|
191
|
+
}
|
|
192
|
+
default: {
|
|
193
|
+
const first = this.adapter.createDate(this.adapter.getYear(anchor), this.adapter.getMonth(anchor), 1);
|
|
194
|
+
const start = this.startOfWeek(first);
|
|
195
|
+
return [start, this.adapter.addDays(start, MONTH_ROWS * DAYS_PER_WEEK)];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}, /* @ts-ignore */
|
|
199
|
+
...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
|
|
200
|
+
/** Every day the view shows, left to right. */
|
|
201
|
+
days = computed(() => {
|
|
202
|
+
const [start, end] = this.range();
|
|
203
|
+
const count = Math.round((end.getTime() - start.getTime()) / (MINUTES_PER_DAY * 60_000));
|
|
204
|
+
return Array.from({ length: count }, (_, i) => this.adapter.addDays(start, i));
|
|
205
|
+
}, /* @ts-ignore */
|
|
206
|
+
...(ngDevMode ? [{ debugName: "days" }] : /* istanbul ignore next */ []));
|
|
207
|
+
/** Only what overlaps the window — everything downstream works off this. */
|
|
208
|
+
visible = computed(() => {
|
|
209
|
+
const [start, end] = this.range();
|
|
210
|
+
return [...this.events()]
|
|
211
|
+
.filter(e => e.end.getTime() > start.getTime() && e.start.getTime() < end.getTime())
|
|
212
|
+
.sort((a, b) => {
|
|
213
|
+
const byStart = a.start.getTime() - b.start.getTime();
|
|
214
|
+
// Ties go to the longer event, so a week-long band claims its lane
|
|
215
|
+
// before the one-day events sitting alongside it.
|
|
216
|
+
return byStart !== 0 ? byStart : b.end.getTime() - a.end.getTime();
|
|
217
|
+
});
|
|
218
|
+
}, /* @ts-ignore */
|
|
219
|
+
...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
220
|
+
title = computed(() => {
|
|
221
|
+
const anchor = this.date();
|
|
222
|
+
const months = this.adapter.getMonthNames('long');
|
|
223
|
+
switch (this.view()) {
|
|
224
|
+
case 'day':
|
|
225
|
+
return this.adapter.format(anchor, 'longDate');
|
|
226
|
+
case 'week': {
|
|
227
|
+
const [start] = this.range();
|
|
228
|
+
const last = this.adapter.addDays(start, DAYS_PER_WEEK - 1);
|
|
229
|
+
return `${this.adapter.format(start, 'mediumDate')} – ${this.adapter.format(last, 'mediumDate')}`;
|
|
230
|
+
}
|
|
231
|
+
default:
|
|
232
|
+
return `${months[this.adapter.getMonth(anchor)]} ${this.adapter.getYear(anchor)}`;
|
|
233
|
+
}
|
|
234
|
+
}, /* @ts-ignore */
|
|
235
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
236
|
+
weekdayNames = computed(() => {
|
|
237
|
+
const names = this.adapter.getDayOfWeekNames('short');
|
|
238
|
+
const first = this.adapter.getFirstDayOfWeek();
|
|
239
|
+
return Array.from({ length: DAYS_PER_WEEK }, (_, i) => names[(first + i) % DAYS_PER_WEEK]);
|
|
240
|
+
}, /* @ts-ignore */
|
|
241
|
+
...(ngDevMode ? [{ debugName: "weekdayNames" }] : /* istanbul ignore next */ []));
|
|
242
|
+
switcher = computed(() => this.views().map(view => ({ view, label: this.viewLabels[view] })), /* @ts-ignore */
|
|
243
|
+
...(ngDevMode ? [{ debugName: "switcher" }] : /* istanbul ignore next */ []));
|
|
244
|
+
// ----------------------------------------------------------- month model
|
|
245
|
+
weeks = computed(() => {
|
|
246
|
+
const days = this.days();
|
|
247
|
+
const events = this.visible();
|
|
248
|
+
const weeks = [];
|
|
249
|
+
const month = this.adapter.getMonth(this.date());
|
|
250
|
+
for (let w = 0; w * DAYS_PER_WEEK < days.length; w++) {
|
|
251
|
+
const row = days.slice(w * DAYS_PER_WEEK, (w + 1) * DAYS_PER_WEEK);
|
|
252
|
+
const { chips, hidden } = this.bandLayout(events, row, this.maxLanes());
|
|
253
|
+
weeks.push({
|
|
254
|
+
key: this.iso(row[0]),
|
|
255
|
+
days: row.map((date, col) => ({
|
|
256
|
+
date,
|
|
257
|
+
key: this.iso(date),
|
|
258
|
+
day: String(this.adapter.getDate(date)),
|
|
259
|
+
label: this.adapter.format(date, 'longDate'),
|
|
260
|
+
inMonth: this.adapter.getMonth(date) === month,
|
|
261
|
+
isToday: this.adapter.isSameDay(date, this.adapter.today()),
|
|
262
|
+
bands: chips.filter(chip => chip.column === col),
|
|
263
|
+
hidden: hidden[col],
|
|
264
|
+
})),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return weeks;
|
|
268
|
+
}, /* @ts-ignore */
|
|
269
|
+
...(ngDevMode ? [{ debugName: "weeks" }] : /* istanbul ignore next */ []));
|
|
270
|
+
// ------------------------------------------------------------ time model
|
|
271
|
+
columns = computed(() => {
|
|
272
|
+
const weekdays = this.adapter.getDayOfWeekNames('short');
|
|
273
|
+
return this.days().map(date => ({
|
|
274
|
+
key: this.iso(date),
|
|
275
|
+
date,
|
|
276
|
+
weekday: weekdays[this.adapter.getDayOfWeek(date)],
|
|
277
|
+
day: String(this.adapter.getDate(date)),
|
|
278
|
+
label: this.adapter.format(date, 'longDate'),
|
|
279
|
+
isToday: this.adapter.isSameDay(date, this.adapter.today()),
|
|
280
|
+
}));
|
|
281
|
+
}, /* @ts-ignore */
|
|
282
|
+
...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
283
|
+
/** The all-day band above the time grid — same layout as a month week. */
|
|
284
|
+
allDayBands = computed(() => {
|
|
285
|
+
const days = this.days();
|
|
286
|
+
const events = this.visible().filter(e => e.allDay === true || this.spansWholeDay(e));
|
|
287
|
+
return this.bandLayout(events, days, Number.POSITIVE_INFINITY).chips;
|
|
288
|
+
}, /* @ts-ignore */
|
|
289
|
+
...(ngDevMode ? [{ debugName: "allDayBands" }] : /* istanbul ignore next */ []));
|
|
290
|
+
allDayCells = computed(() => {
|
|
291
|
+
const days = this.days();
|
|
292
|
+
const chips = this.allDayBands();
|
|
293
|
+
return days.map((date, col) => ({
|
|
294
|
+
key: this.iso(date),
|
|
295
|
+
date,
|
|
296
|
+
label: `${this.allDayLabel} — ${this.adapter.format(date, 'longDate')}`,
|
|
297
|
+
bands: chips.filter(chip => chip.column === col),
|
|
298
|
+
}));
|
|
299
|
+
}, /* @ts-ignore */
|
|
300
|
+
...(ngDevMode ? [{ debugName: "allDayCells" }] : /* istanbul ignore next */ []));
|
|
301
|
+
hasAllDay = computed(() => this.allDayBands().length > 0, /* @ts-ignore */
|
|
302
|
+
...(ngDevMode ? [{ debugName: "hasAllDay" }] : /* istanbul ignore next */ []));
|
|
303
|
+
rows = computed(() => {
|
|
304
|
+
const slot = Math.max(5, this.slotMinutes());
|
|
305
|
+
const from = Math.max(0, Math.min(23, this.dayStartHour())) * 60;
|
|
306
|
+
const to = Math.max(from + slot, Math.min(24, this.dayEndHour()) * 60);
|
|
307
|
+
const days = this.days();
|
|
308
|
+
const placed = this.timeLayout(days, slot);
|
|
309
|
+
const rows = [];
|
|
310
|
+
for (let minutes = from; minutes < to; minutes += slot) {
|
|
311
|
+
rows.push({
|
|
312
|
+
key: String(minutes),
|
|
313
|
+
minutes,
|
|
314
|
+
major: minutes % 60 === 0,
|
|
315
|
+
label: minutes % 60 === 0 ? this.clock(minutes) : '',
|
|
316
|
+
cells: days.map((date, col) => ({
|
|
317
|
+
key: `${this.iso(date)}:${minutes}`,
|
|
318
|
+
date,
|
|
319
|
+
label: `${this.clock(minutes)} — ${this.adapter.format(date, 'longDate')}`,
|
|
320
|
+
chips: placed[col].filter(chip => chip.slotMinutes === minutes).map(chip => chip.chip),
|
|
321
|
+
})),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return rows;
|
|
325
|
+
}, /* @ts-ignore */
|
|
326
|
+
...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
327
|
+
// ------------------------------------------------------------------ nav
|
|
328
|
+
step(direction) {
|
|
329
|
+
const anchor = this.date();
|
|
330
|
+
switch (this.view()) {
|
|
331
|
+
case 'day':
|
|
332
|
+
this.date.set(this.adapter.addDays(anchor, direction));
|
|
333
|
+
break;
|
|
334
|
+
case 'week':
|
|
335
|
+
this.date.set(this.adapter.addDays(anchor, direction * DAYS_PER_WEEK));
|
|
336
|
+
break;
|
|
337
|
+
default:
|
|
338
|
+
this.date.set(this.adapter.addMonths(anchor, direction));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
goToday() {
|
|
342
|
+
this.date.set(this.adapter.today());
|
|
343
|
+
}
|
|
344
|
+
setView(view) {
|
|
345
|
+
this.view.set(view);
|
|
346
|
+
}
|
|
347
|
+
/** “+N more” opens that day — the overflow is only ever a month-view problem. */
|
|
348
|
+
openDay(date) {
|
|
349
|
+
this.date.set(date);
|
|
350
|
+
this.view.set('day');
|
|
351
|
+
}
|
|
352
|
+
// -------------------------------------------------------------- pointer
|
|
353
|
+
onChipPointerDown(event, chipEvent, kind) {
|
|
354
|
+
if (!this.canDrag(chipEvent) || event.button !== 0)
|
|
355
|
+
return;
|
|
356
|
+
const cell = this.cellUnder(event.clientX, event.clientY);
|
|
357
|
+
if (!cell)
|
|
358
|
+
return;
|
|
359
|
+
// The chip must stop swallowing hit-tests, or `elementFromPoint` reports
|
|
360
|
+
// the chip itself for the whole drag and the target never changes.
|
|
361
|
+
try {
|
|
362
|
+
event.target.setPointerCapture?.(event.pointerId);
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
// Capture is an optimisation — it keeps the release on this element even
|
|
366
|
+
// when the pointer leaves it. A pointer that is already gone throws here,
|
|
367
|
+
// and losing capture is not a reason to lose the drag.
|
|
368
|
+
}
|
|
369
|
+
event.preventDefault();
|
|
370
|
+
event.stopPropagation();
|
|
371
|
+
this.drag.set({
|
|
372
|
+
event: chipEvent,
|
|
373
|
+
kind,
|
|
374
|
+
originDate: cell.date,
|
|
375
|
+
originMinutes: cell.minutes,
|
|
376
|
+
pointerId: event.pointerId,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
onPointerUp(event) {
|
|
380
|
+
const state = this.drag();
|
|
381
|
+
this.drag.set(null);
|
|
382
|
+
if (state?.pointerId !== event.pointerId)
|
|
383
|
+
return;
|
|
384
|
+
const cell = this.cellUnder(event.clientX, event.clientY);
|
|
385
|
+
if (!cell)
|
|
386
|
+
return;
|
|
387
|
+
const dayDelta = this.daysBetween(state.originDate, cell.date);
|
|
388
|
+
const minuteDelta = cell.minutes < 0 || state.originMinutes < 0 ? 0 : cell.minutes - state.originMinutes;
|
|
389
|
+
if (dayDelta === 0 && minuteDelta === 0)
|
|
390
|
+
return;
|
|
391
|
+
const { event: source, kind } = state;
|
|
392
|
+
if (kind === 'resize') {
|
|
393
|
+
const end = this.shift(source.end, dayDelta, minuteDelta);
|
|
394
|
+
// A resize that would end at or before the start is a no-op, not an
|
|
395
|
+
// inverted event.
|
|
396
|
+
if (end.getTime() <= source.start.getTime())
|
|
397
|
+
return;
|
|
398
|
+
this.eventChange.emit({ event: source, start: source.start, end, kind });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
this.eventChange.emit({
|
|
402
|
+
event: source,
|
|
403
|
+
start: this.shift(source.start, dayDelta, minuteDelta),
|
|
404
|
+
end: this.shift(source.end, dayDelta, minuteDelta),
|
|
405
|
+
kind,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
onChipClick(event) {
|
|
409
|
+
this.eventClick.emit(event);
|
|
410
|
+
}
|
|
411
|
+
onCellClick(date, minutes) {
|
|
412
|
+
this.slotClick.emit(this.slotAt(date, minutes));
|
|
413
|
+
}
|
|
414
|
+
// ------------------------------------------------------------- keyboard
|
|
415
|
+
onChipKeyDown(event, chipEvent) {
|
|
416
|
+
if (event.key === 'Escape') {
|
|
417
|
+
this.focusCell(event);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (!event.altKey || !this.canDrag(chipEvent))
|
|
421
|
+
return;
|
|
422
|
+
const slot = Math.max(5, this.slotMinutes());
|
|
423
|
+
const inTime = this.view() !== 'month';
|
|
424
|
+
let days = 0;
|
|
425
|
+
let minutes = 0;
|
|
426
|
+
switch (event.key) {
|
|
427
|
+
case 'ArrowLeft':
|
|
428
|
+
days = -1;
|
|
429
|
+
break;
|
|
430
|
+
case 'ArrowRight':
|
|
431
|
+
days = 1;
|
|
432
|
+
break;
|
|
433
|
+
case 'ArrowUp':
|
|
434
|
+
if (inTime)
|
|
435
|
+
minutes = -slot;
|
|
436
|
+
else
|
|
437
|
+
days = -DAYS_PER_WEEK;
|
|
438
|
+
break;
|
|
439
|
+
case 'ArrowDown':
|
|
440
|
+
if (inTime)
|
|
441
|
+
minutes = slot;
|
|
442
|
+
else
|
|
443
|
+
days = DAYS_PER_WEEK;
|
|
444
|
+
break;
|
|
445
|
+
default:
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
event.preventDefault();
|
|
449
|
+
this.eventChange.emit({
|
|
450
|
+
event: chipEvent,
|
|
451
|
+
start: this.shift(chipEvent.start, days, minutes),
|
|
452
|
+
end: this.shift(chipEvent.end, days, minutes),
|
|
453
|
+
kind: 'move',
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
onGridKeyDown(event) {
|
|
457
|
+
// Handled per cell, not on the grid: the element carrying the keyboard
|
|
458
|
+
// should be the one that actually holds focus. Chips sit inside cells and
|
|
459
|
+
// own their keys, so let anything originating in one bubble straight past.
|
|
460
|
+
if (event.target !== event.currentTarget)
|
|
461
|
+
return;
|
|
462
|
+
const [day, minutes] = this.cursorCell();
|
|
463
|
+
const columns = this.days().length;
|
|
464
|
+
const rows = this.rows();
|
|
465
|
+
const inTime = this.view() !== 'month';
|
|
466
|
+
let next;
|
|
467
|
+
switch (event.key) {
|
|
468
|
+
case 'ArrowLeft':
|
|
469
|
+
next = [Math.max(0, day - 1), minutes];
|
|
470
|
+
break;
|
|
471
|
+
case 'ArrowRight':
|
|
472
|
+
next = [Math.min(columns - 1, day + 1), minutes];
|
|
473
|
+
break;
|
|
474
|
+
case 'ArrowUp':
|
|
475
|
+
next = inTime ? [day, this.stepRow(minutes, -1, rows)] : [Math.max(0, day - DAYS_PER_WEEK), minutes];
|
|
476
|
+
break;
|
|
477
|
+
case 'ArrowDown':
|
|
478
|
+
next = inTime
|
|
479
|
+
? [day, this.stepRow(minutes, 1, rows)]
|
|
480
|
+
: [day + DAYS_PER_WEEK < columns ? day + DAYS_PER_WEEK : day, minutes];
|
|
481
|
+
break;
|
|
482
|
+
case 'Home':
|
|
483
|
+
next = [day - (day % DAYS_PER_WEEK), minutes];
|
|
484
|
+
break;
|
|
485
|
+
case 'End':
|
|
486
|
+
next = [Math.min(columns - 1, day - (day % DAYS_PER_WEEK) + DAYS_PER_WEEK - 1), minutes];
|
|
487
|
+
break;
|
|
488
|
+
case 'Enter':
|
|
489
|
+
case ' ': {
|
|
490
|
+
event.preventDefault();
|
|
491
|
+
const cell = event.target;
|
|
492
|
+
const chip = cell.querySelector('.wr-event-calendar__chip');
|
|
493
|
+
if (chip)
|
|
494
|
+
chip.focus();
|
|
495
|
+
else
|
|
496
|
+
this.onCellClick(this.days()[day], minutes);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
default:
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
event.preventDefault();
|
|
503
|
+
this.cursor.set(next);
|
|
504
|
+
this.focusCursor(event.currentTarget);
|
|
505
|
+
}
|
|
506
|
+
isCursor(day, minutes) {
|
|
507
|
+
const [d, m] = this.cursorCell();
|
|
508
|
+
return d === day && m === minutes;
|
|
509
|
+
}
|
|
510
|
+
// --------------------------------------------------------------- layout
|
|
511
|
+
/**
|
|
512
|
+
* Greedy lane packing for day-spanning chips. Sorted by start then by length,
|
|
513
|
+
* each segment takes the first lane its columns are free in — the classic
|
|
514
|
+
* interval-graph colouring, and the reason a long event keeps one lane across
|
|
515
|
+
* the whole week instead of stair-stepping.
|
|
516
|
+
*
|
|
517
|
+
* `limit` caps the visible lanes; anything past it is reported per column so
|
|
518
|
+
* the cell can say how much it is hiding.
|
|
519
|
+
*/
|
|
520
|
+
bandLayout(events, days, limit) {
|
|
521
|
+
const first = days[0];
|
|
522
|
+
const last = this.adapter.addDays(days[days.length - 1], 1);
|
|
523
|
+
const lanes = [];
|
|
524
|
+
const chips = [];
|
|
525
|
+
const hidden = days.map(() => 0);
|
|
526
|
+
for (const event of events) {
|
|
527
|
+
if (event.end.getTime() <= first.getTime() || event.start.getTime() >= last.getTime())
|
|
528
|
+
continue;
|
|
529
|
+
const from = Math.max(0, this.daysBetween(first, this.startOfDay(event.start)));
|
|
530
|
+
// An exclusive end landing exactly on midnight belongs to the previous
|
|
531
|
+
// day — otherwise every all-day event paints one column too wide.
|
|
532
|
+
const endDay = this.startOfDay(new Date(event.end.getTime() - 1));
|
|
533
|
+
const to = Math.min(days.length - 1, this.daysBetween(first, endDay));
|
|
534
|
+
if (to < from)
|
|
535
|
+
continue;
|
|
536
|
+
let lane = lanes.findIndex(taken => taken.every(col => col < from || col > to));
|
|
537
|
+
if (lane === -1) {
|
|
538
|
+
lane = lanes.length;
|
|
539
|
+
lanes.push([]);
|
|
540
|
+
}
|
|
541
|
+
for (let col = from; col <= to; col++)
|
|
542
|
+
lanes[lane].push(col);
|
|
543
|
+
if (lane >= limit) {
|
|
544
|
+
for (let col = from; col <= to; col++)
|
|
545
|
+
hidden[col]++;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
chips.push({
|
|
549
|
+
key: `${String(event.id)}:${from}`,
|
|
550
|
+
event,
|
|
551
|
+
column: from,
|
|
552
|
+
span: to - from + 1,
|
|
553
|
+
lane,
|
|
554
|
+
continuesBefore: event.start.getTime() < first.getTime(),
|
|
555
|
+
continuesAfter: event.end.getTime() > last.getTime(),
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
return { chips, hidden };
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Column layout inside one day: events that overlap in time share the width.
|
|
562
|
+
* Clusters are built by a running high-water mark — a new cluster starts the
|
|
563
|
+
* moment an event begins after everything before it has ended.
|
|
564
|
+
*/
|
|
565
|
+
timeLayout(days, slot) {
|
|
566
|
+
const from = Math.max(0, Math.min(23, this.dayStartHour())) * 60;
|
|
567
|
+
const to = Math.max(from + slot, Math.min(24, this.dayEndHour()) * 60);
|
|
568
|
+
const timed = this.visible().filter(e => !e.allDay && !this.spansWholeDay(e));
|
|
569
|
+
return days.map(day => {
|
|
570
|
+
const dayStart = this.startOfDay(day);
|
|
571
|
+
const windowStart = dayStart.getTime() + from * 60_000;
|
|
572
|
+
const windowEnd = dayStart.getTime() + to * 60_000;
|
|
573
|
+
const onDay = timed
|
|
574
|
+
.filter(e => e.end.getTime() > windowStart && e.start.getTime() < windowEnd)
|
|
575
|
+
.map(event => ({
|
|
576
|
+
event,
|
|
577
|
+
from: Math.max(windowStart, event.start.getTime()),
|
|
578
|
+
to: Math.min(windowEnd, event.end.getTime()),
|
|
579
|
+
}))
|
|
580
|
+
.sort((a, b) => {
|
|
581
|
+
const byStart = a.from - b.from;
|
|
582
|
+
return byStart !== 0 ? byStart : b.to - a.to;
|
|
583
|
+
});
|
|
584
|
+
const out = [];
|
|
585
|
+
let cluster = [];
|
|
586
|
+
let highWater = -1;
|
|
587
|
+
const flush = () => {
|
|
588
|
+
for (const [index, item] of cluster.entries()) {
|
|
589
|
+
const minutes = Math.floor((item.from - dayStart.getTime()) / 60_000);
|
|
590
|
+
const slotStart = from + Math.floor((minutes - from) / slot) * slot;
|
|
591
|
+
const length = (item.to - item.from) / 60_000;
|
|
592
|
+
out.push({
|
|
593
|
+
slotMinutes: slotStart,
|
|
594
|
+
chip: {
|
|
595
|
+
key: `${String(item.event.id)}:${minutes}`,
|
|
596
|
+
event: item.event,
|
|
597
|
+
top: ((minutes - slotStart) / slot) * 100,
|
|
598
|
+
height: (length / slot) * 100,
|
|
599
|
+
left: (index / cluster.length) * 100,
|
|
600
|
+
width: (1 / cluster.length) * 100,
|
|
601
|
+
continuesBefore: item.event.start.getTime() < windowStart,
|
|
602
|
+
continuesAfter: item.event.end.getTime() > windowEnd,
|
|
603
|
+
},
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
cluster = [];
|
|
607
|
+
};
|
|
608
|
+
for (const item of onDay) {
|
|
609
|
+
if (cluster.length > 0 && item.from >= highWater)
|
|
610
|
+
flush();
|
|
611
|
+
cluster.push(item);
|
|
612
|
+
highWater = Math.max(highWater, item.to);
|
|
613
|
+
}
|
|
614
|
+
flush();
|
|
615
|
+
return out;
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
// --------------------------------------------------------------- chrome
|
|
619
|
+
chipLabel(event) {
|
|
620
|
+
const time = event.allDay ? this.allDayLabel : this.adapter.format(event.start, 'time');
|
|
621
|
+
return `${event.title}, ${time}`;
|
|
622
|
+
}
|
|
623
|
+
chipTime(event) {
|
|
624
|
+
return event.allDay ? this.allDayLabel : this.adapter.format(event.start, 'time');
|
|
625
|
+
}
|
|
626
|
+
canDrag(event) {
|
|
627
|
+
return this.editable() && event.editable !== false;
|
|
628
|
+
}
|
|
629
|
+
// --------------------------------------------------------------- helpers
|
|
630
|
+
slotAt(date, minutes) {
|
|
631
|
+
if (minutes < 0) {
|
|
632
|
+
const start = this.startOfDay(date);
|
|
633
|
+
return { start, end: this.adapter.addDays(start, 1), allDay: true };
|
|
634
|
+
}
|
|
635
|
+
const start = new Date(this.startOfDay(date).getTime() + minutes * 60_000);
|
|
636
|
+
return { start, end: new Date(start.getTime() + Math.max(5, this.slotMinutes()) * 60_000), allDay: false };
|
|
637
|
+
}
|
|
638
|
+
/** The `[data-cell]` under the pointer, or `null` outside the grid. */
|
|
639
|
+
cellUnder(x, y) {
|
|
640
|
+
const el = this.document.elementFromPoint(x, y)?.closest('[data-cell-date]');
|
|
641
|
+
if (!el)
|
|
642
|
+
return null;
|
|
643
|
+
// Epoch millis rather than a formatted date: `new Date('2026-08-08')` is
|
|
644
|
+
// parsed as UTC and lands on the previous day west of Greenwich.
|
|
645
|
+
const stamp = Number(el.dataset['cellDate']);
|
|
646
|
+
if (!Number.isFinite(stamp))
|
|
647
|
+
return null;
|
|
648
|
+
return { date: new Date(stamp), minutes: Number(el.dataset['cellMinutes'] ?? -1) };
|
|
649
|
+
}
|
|
650
|
+
/** Value of a cell's `data-cell-date`. @internal */
|
|
651
|
+
stamp(date) {
|
|
652
|
+
return this.startOfDay(date).getTime();
|
|
653
|
+
}
|
|
654
|
+
focusCursor(from) {
|
|
655
|
+
const [day, minutes] = this.cursorCell();
|
|
656
|
+
from.closest('[role="grid"]')?.querySelector(`[data-cell-index="${day}:${minutes}"]`)?.focus();
|
|
657
|
+
}
|
|
658
|
+
focusCell(event) {
|
|
659
|
+
event.preventDefault();
|
|
660
|
+
event.target.closest('[data-cell-date]')?.focus();
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Move one time row, treating the all-day band as the row above the first —
|
|
664
|
+
* otherwise the band is mouse-only, which is the kind of gap that makes a
|
|
665
|
+
* keyboard user give up on the whole grid.
|
|
666
|
+
*/
|
|
667
|
+
stepRow(minutes, direction, rows) {
|
|
668
|
+
if (minutes === -1)
|
|
669
|
+
return direction === 1 ? (rows[0]?.minutes ?? -1) : -1;
|
|
670
|
+
const index = rows.findIndex(row => row.minutes === minutes);
|
|
671
|
+
if (index === -1)
|
|
672
|
+
return rows[0]?.minutes ?? -1;
|
|
673
|
+
if (index === 0 && direction === -1)
|
|
674
|
+
return this.hasAllDay() ? -1 : minutes;
|
|
675
|
+
return rows[Math.min(rows.length - 1, index + direction)].minutes;
|
|
676
|
+
}
|
|
677
|
+
shift(date, days, minutes) {
|
|
678
|
+
return new Date(this.adapter.addDays(date, days).getTime() + minutes * 60_000);
|
|
679
|
+
}
|
|
680
|
+
startOfDay(date) {
|
|
681
|
+
return this.adapter.setTime(date, 0, 0, 0);
|
|
682
|
+
}
|
|
683
|
+
startOfWeek(date) {
|
|
684
|
+
const first = this.adapter.getFirstDayOfWeek();
|
|
685
|
+
const offset = (this.adapter.getDayOfWeek(date) - first + DAYS_PER_WEEK) % DAYS_PER_WEEK;
|
|
686
|
+
return this.startOfDay(this.adapter.addDays(date, -offset));
|
|
687
|
+
}
|
|
688
|
+
/** Whole days apart — computed on the day floor, so DST cannot round it off. */
|
|
689
|
+
daysBetween(from, to) {
|
|
690
|
+
const a = this.startOfDay(from);
|
|
691
|
+
const b = this.startOfDay(to);
|
|
692
|
+
return Math.round((b.getTime() - a.getTime()) / (MINUTES_PER_DAY * 60_000));
|
|
693
|
+
}
|
|
694
|
+
spansWholeDay(event) {
|
|
695
|
+
return event.end.getTime() - event.start.getTime() >= MINUTES_PER_DAY * 60_000;
|
|
696
|
+
}
|
|
697
|
+
clock(minutes) {
|
|
698
|
+
const date = this.adapter.setTime(this.adapter.today(), Math.floor(minutes / 60), minutes % 60, 0);
|
|
699
|
+
return this.adapter.format(date, 'time');
|
|
700
|
+
}
|
|
701
|
+
iso(date) {
|
|
702
|
+
return `${this.adapter.getYear(date)}-${this.adapter.getMonth(date) + 1}-${this.adapter.getDate(date)}`;
|
|
703
|
+
}
|
|
704
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrEventCalendar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
705
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: WrEventCalendar, isStandalone: true, selector: "wr-event-calendar", inputs: { events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, slotMinutes: { classPropertyName: "slotMinutes", publicName: "slotMinutes", isSignal: true, isRequired: false, transformFunction: null }, dayStartHour: { classPropertyName: "dayStartHour", publicName: "dayStartHour", isSignal: true, isRequired: false, transformFunction: null }, dayEndHour: { classPropertyName: "dayEndHour", publicName: "dayEndHour", isSignal: true, isRequired: false, transformFunction: null }, maxLanes: { classPropertyName: "maxLanes", publicName: "maxLanes", isSignal: true, isRequired: false, transformFunction: null }, hideHeader: { classPropertyName: "hideHeader", publicName: "hideHeader", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { view: "viewChange", date: "dateChange", eventClick: "eventClick", slotClick: "slotClick", eventChange: "eventChange" }, host: { listeners: { "pointerup": "onPointerUp($event)", "pointercancel": "drag.set(null)" }, properties: { "class": "classes()" } }, queries: [{ propertyName: "chipTpl", first: true, predicate: WrCalendarEventTemplate, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n Two grids, one rule: a chip always lives inside the `role=\"gridcell\"` where it\n STARTS, and reaches out from there \u2014 `calc()` widths for day-spanning bands,\n percentage heights for timed chips. A floating events layer would read better\n in the template and would leave `role=\"row\"` owning something other than\n cells, which is exactly what the axe gate rejects.\n-->\n@if (!hideHeader()) {\n <div class=\"wr-event-calendar__header\">\n <div class=\"wr-event-calendar__nav\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-event-calendar__step\"\n [attr.aria-label]=\"previousLabel\"\n (click)=\"step(-1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <button wr-btn type=\"button\" size=\"sm\" outlined (click)=\"goToday()\">{{ todayLabel }}</button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-event-calendar__step\"\n [attr.aria-label]=\"nextLabel\"\n (click)=\"step(1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n </div>\n\n <h2 class=\"wr-event-calendar__title\">{{ title() }}</h2>\n\n @if (switcher().length > 1) {\n <wr-btn-group>\n @for (item of switcher(); track item.view) {\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n [outlined]=\"view() !== item.view\"\n [color]=\"view() === item.view ? 'primary' : null\"\n [attr.aria-pressed]=\"view() === item.view\"\n (click)=\"setView(item.view)\"\n >\n {{ item.label }}\n </button>\n }\n </wr-btn-group>\n }\n </div>\n}\n\n@if (view() === 'month') {\n <div\n role=\"grid\"\n class=\"wr-event-calendar__grid wr-event-calendar__grid--month\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n >\n <div role=\"row\" class=\"wr-event-calendar__row\">\n @for (name of weekdayNames(); track $index) {\n <div role=\"columnheader\" class=\"wr-event-calendar__weekday\">{{ name }}</div>\n }\n </div>\n\n @for (week of weeks(); track week.key; let w = $index) {\n <div role=\"row\" class=\"wr-event-calendar__row\">\n @for (cell of week.days; track cell.key; let c = $index) {\n @let index = w * 7 + c;\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--day\"\n [class.wr-event-calendar__cell--muted]=\"!cell.inMonth\"\n [class.wr-event-calendar__cell--today]=\"cell.isToday\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(index, -1) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"-1\"\n [attr.data-cell-index]=\"`${index}:-1`\"\n (click)=\"onCellClick(cell.date, -1)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n <span class=\"wr-event-calendar__daynum\" aria-hidden=\"true\">{{ cell.day }}</span>\n\n @for (chip of cell.bands; track chip.key) {\n <ng-container\n [ngTemplateOutlet]=\"band\"\n [ngTemplateOutletContext]=\"{ $implicit: chip, offset: '1.75rem' }\"\n />\n }\n\n @if (cell.hidden > 0) {\n <button\n type=\"button\"\n class=\"wr-event-calendar__more\"\n [style.--wr-event-calendar-lane]=\"maxLanes()\"\n (click)=\"openDay(cell.date); $event.stopPropagation()\"\n >\n {{ moreLabel({ count: cell.hidden }) }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n} @else {\n <div\n role=\"grid\"\n class=\"wr-event-calendar__grid wr-event-calendar__grid--time\"\n [style.--wr-event-calendar-cols]=\"columns().length\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n >\n <div role=\"row\" class=\"wr-event-calendar__row\">\n <div role=\"columnheader\" class=\"wr-event-calendar__gutter\" [attr.aria-label]=\"timeLabel\"></div>\n @for (column of columns(); track column.key) {\n <div\n role=\"columnheader\"\n class=\"wr-event-calendar__colhead\"\n [class.wr-event-calendar__colhead--today]=\"column.isToday\"\n >\n <span class=\"wr-event-calendar__colhead-weekday\">{{ column.weekday }}</span>\n <span class=\"wr-event-calendar__colhead-day\">{{ column.day }}</span>\n </div>\n }\n </div>\n\n @if (hasAllDay()) {\n <div role=\"row\" class=\"wr-event-calendar__row wr-event-calendar__row--allday\">\n <div role=\"rowheader\" class=\"wr-event-calendar__gutter\">{{ allDayLabel }}</div>\n @for (cell of allDayCells(); track cell.key; let c = $index) {\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--allday\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(c, -1) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"-1\"\n [attr.data-cell-index]=\"`${c}:-1`\"\n (click)=\"onCellClick(cell.date, -1)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n @for (chip of cell.bands; track chip.key) {\n <ng-container [ngTemplateOutlet]=\"band\" [ngTemplateOutletContext]=\"{ $implicit: chip, offset: '0rem' }\" />\n }\n </div>\n }\n </div>\n }\n\n @for (row of rows(); track row.key) {\n <div role=\"row\" class=\"wr-event-calendar__row\" [class.wr-event-calendar__row--major]=\"row.major\">\n <div role=\"rowheader\" class=\"wr-event-calendar__gutter\">{{ row.label }}</div>\n @for (cell of row.cells; track cell.key; let c = $index) {\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--slot\"\n [class.wr-event-calendar__cell--major]=\"row.major\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(c, row.minutes) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"row.minutes\"\n [attr.data-cell-index]=\"`${c}:${row.minutes}`\"\n (click)=\"onCellClick(cell.date, row.minutes)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n @for (chip of cell.chips; track chip.key) {\n <button\n type=\"button\"\n class=\"wr-event-calendar__chip wr-event-calendar__chip--time\"\n [class.wr-event-calendar__chip--dragging]=\"dragging() === chip.event.id\"\n [class.wr-event-calendar__chip--continues-before]=\"chip.continuesBefore\"\n [class.wr-event-calendar__chip--continues-after]=\"chip.continuesAfter\"\n [style.--wr-event-calendar-color]=\"`var(--wr-color-${chip.event.color ?? 'primary'})`\"\n [style.top.%]=\"chip.top\"\n [style.height.%]=\"chip.height\"\n [style.left.%]=\"chip.left\"\n [style.width.%]=\"chip.width\"\n [attr.aria-label]=\"chipLabel(chip.event)\"\n (click)=\"onChipClick(chip.event); $event.stopPropagation()\"\n (keydown)=\"onChipKeyDown($event, chip.event)\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'move')\"\n >\n <span class=\"wr-event-calendar__chip-time\" aria-hidden=\"true\">{{ chipTime(chip.event) }}</span>\n @if (chipTemplate(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"{ $implicit: chip.event }\" />\n } @else {\n <span class=\"wr-event-calendar__chip-title\" aria-hidden=\"true\">{{ chip.event.title }}</span>\n }\n\n @if (canDrag(chip.event) && !chip.continuesAfter) {\n <!--\n A grab area, not a control: the same resize is on the keyboard\n as `Alt` + arrows on the chip itself, so exposing a second\n focusable thing here would add a tab stop that leads nowhere.\n -->\n <span\n class=\"wr-event-calendar__resize\"\n aria-hidden=\"true\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'resize')\"\n ></span>\n }\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n\n<!-- One chip shape for both day-spanning cases: month cells and the all-day band. -->\n<ng-template #band let-chip let-offset=\"offset\">\n <button\n type=\"button\"\n class=\"wr-event-calendar__chip wr-event-calendar__chip--band\"\n [class.wr-event-calendar__chip--dragging]=\"dragging() === chip.event.id\"\n [class.wr-event-calendar__chip--continues-before]=\"chip.continuesBefore\"\n [class.wr-event-calendar__chip--continues-after]=\"chip.continuesAfter\"\n [style.--wr-event-calendar-color]=\"`var(--wr-color-${chip.event.color ?? 'primary'})`\"\n [style.--wr-event-calendar-span]=\"chip.span\"\n [style.--wr-event-calendar-lane]=\"chip.lane\"\n [style.--wr-event-calendar-offset]=\"offset\"\n [attr.aria-label]=\"chipLabel(chip.event)\"\n (click)=\"onChipClick(chip.event); $event.stopPropagation()\"\n (keydown)=\"onChipKeyDown($event, chip.event)\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'move')\"\n >\n @if (chipTemplate(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"{ $implicit: chip.event }\" />\n } @else {\n <span class=\"wr-event-calendar__chip-title\" aria-hidden=\"true\">{{ chip.event.title }}</span>\n }\n </button>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: WrButton, selector: "wr-btn, button[wr-btn], a[wr-btn]", inputs: ["color", "size", "shape", "icon", "iconPosition", "disabled", "outlined", "block", "loading", "isDisabledWhenLoading"] }, { kind: "component", type: WrButtonGroup, selector: "wr-btn-group", inputs: ["shape"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
706
|
+
}
|
|
707
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WrEventCalendar, decorators: [{
|
|
708
|
+
type: Component,
|
|
709
|
+
args: [{ selector: 'wr-event-calendar', encapsulation: ViewEncapsulation.None, host: {
|
|
710
|
+
'[class]': 'classes()',
|
|
711
|
+
// A drag has to end wherever the pointer is released, including outside the
|
|
712
|
+
// chip it started on — so the host owns the release, not the chip.
|
|
713
|
+
'(pointerup)': 'onPointerUp($event)',
|
|
714
|
+
'(pointercancel)': 'drag.set(null)',
|
|
715
|
+
}, imports: [NgTemplateOutlet, WrButton, WrButtonGroup], template: "<!--\n Two grids, one rule: a chip always lives inside the `role=\"gridcell\"` where it\n STARTS, and reaches out from there \u2014 `calc()` widths for day-spanning bands,\n percentage heights for timed chips. A floating events layer would read better\n in the template and would leave `role=\"row\"` owning something other than\n cells, which is exactly what the axe gate rejects.\n-->\n@if (!hideHeader()) {\n <div class=\"wr-event-calendar__header\">\n <div class=\"wr-event-calendar__nav\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-event-calendar__step\"\n [attr.aria-label]=\"previousLabel\"\n (click)=\"step(-1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <button wr-btn type=\"button\" size=\"sm\" outlined (click)=\"goToday()\">{{ todayLabel }}</button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-event-calendar__step\"\n [attr.aria-label]=\"nextLabel\"\n (click)=\"step(1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n </div>\n\n <h2 class=\"wr-event-calendar__title\">{{ title() }}</h2>\n\n @if (switcher().length > 1) {\n <wr-btn-group>\n @for (item of switcher(); track item.view) {\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n [outlined]=\"view() !== item.view\"\n [color]=\"view() === item.view ? 'primary' : null\"\n [attr.aria-pressed]=\"view() === item.view\"\n (click)=\"setView(item.view)\"\n >\n {{ item.label }}\n </button>\n }\n </wr-btn-group>\n }\n </div>\n}\n\n@if (view() === 'month') {\n <div\n role=\"grid\"\n class=\"wr-event-calendar__grid wr-event-calendar__grid--month\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n >\n <div role=\"row\" class=\"wr-event-calendar__row\">\n @for (name of weekdayNames(); track $index) {\n <div role=\"columnheader\" class=\"wr-event-calendar__weekday\">{{ name }}</div>\n }\n </div>\n\n @for (week of weeks(); track week.key; let w = $index) {\n <div role=\"row\" class=\"wr-event-calendar__row\">\n @for (cell of week.days; track cell.key; let c = $index) {\n @let index = w * 7 + c;\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--day\"\n [class.wr-event-calendar__cell--muted]=\"!cell.inMonth\"\n [class.wr-event-calendar__cell--today]=\"cell.isToday\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(index, -1) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"-1\"\n [attr.data-cell-index]=\"`${index}:-1`\"\n (click)=\"onCellClick(cell.date, -1)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n <span class=\"wr-event-calendar__daynum\" aria-hidden=\"true\">{{ cell.day }}</span>\n\n @for (chip of cell.bands; track chip.key) {\n <ng-container\n [ngTemplateOutlet]=\"band\"\n [ngTemplateOutletContext]=\"{ $implicit: chip, offset: '1.75rem' }\"\n />\n }\n\n @if (cell.hidden > 0) {\n <button\n type=\"button\"\n class=\"wr-event-calendar__more\"\n [style.--wr-event-calendar-lane]=\"maxLanes()\"\n (click)=\"openDay(cell.date); $event.stopPropagation()\"\n >\n {{ moreLabel({ count: cell.hidden }) }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n} @else {\n <div\n role=\"grid\"\n class=\"wr-event-calendar__grid wr-event-calendar__grid--time\"\n [style.--wr-event-calendar-cols]=\"columns().length\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n >\n <div role=\"row\" class=\"wr-event-calendar__row\">\n <div role=\"columnheader\" class=\"wr-event-calendar__gutter\" [attr.aria-label]=\"timeLabel\"></div>\n @for (column of columns(); track column.key) {\n <div\n role=\"columnheader\"\n class=\"wr-event-calendar__colhead\"\n [class.wr-event-calendar__colhead--today]=\"column.isToday\"\n >\n <span class=\"wr-event-calendar__colhead-weekday\">{{ column.weekday }}</span>\n <span class=\"wr-event-calendar__colhead-day\">{{ column.day }}</span>\n </div>\n }\n </div>\n\n @if (hasAllDay()) {\n <div role=\"row\" class=\"wr-event-calendar__row wr-event-calendar__row--allday\">\n <div role=\"rowheader\" class=\"wr-event-calendar__gutter\">{{ allDayLabel }}</div>\n @for (cell of allDayCells(); track cell.key; let c = $index) {\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--allday\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(c, -1) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"-1\"\n [attr.data-cell-index]=\"`${c}:-1`\"\n (click)=\"onCellClick(cell.date, -1)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n @for (chip of cell.bands; track chip.key) {\n <ng-container [ngTemplateOutlet]=\"band\" [ngTemplateOutletContext]=\"{ $implicit: chip, offset: '0rem' }\" />\n }\n </div>\n }\n </div>\n }\n\n @for (row of rows(); track row.key) {\n <div role=\"row\" class=\"wr-event-calendar__row\" [class.wr-event-calendar__row--major]=\"row.major\">\n <div role=\"rowheader\" class=\"wr-event-calendar__gutter\">{{ row.label }}</div>\n @for (cell of row.cells; track cell.key; let c = $index) {\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--slot\"\n [class.wr-event-calendar__cell--major]=\"row.major\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(c, row.minutes) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"row.minutes\"\n [attr.data-cell-index]=\"`${c}:${row.minutes}`\"\n (click)=\"onCellClick(cell.date, row.minutes)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n @for (chip of cell.chips; track chip.key) {\n <button\n type=\"button\"\n class=\"wr-event-calendar__chip wr-event-calendar__chip--time\"\n [class.wr-event-calendar__chip--dragging]=\"dragging() === chip.event.id\"\n [class.wr-event-calendar__chip--continues-before]=\"chip.continuesBefore\"\n [class.wr-event-calendar__chip--continues-after]=\"chip.continuesAfter\"\n [style.--wr-event-calendar-color]=\"`var(--wr-color-${chip.event.color ?? 'primary'})`\"\n [style.top.%]=\"chip.top\"\n [style.height.%]=\"chip.height\"\n [style.left.%]=\"chip.left\"\n [style.width.%]=\"chip.width\"\n [attr.aria-label]=\"chipLabel(chip.event)\"\n (click)=\"onChipClick(chip.event); $event.stopPropagation()\"\n (keydown)=\"onChipKeyDown($event, chip.event)\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'move')\"\n >\n <span class=\"wr-event-calendar__chip-time\" aria-hidden=\"true\">{{ chipTime(chip.event) }}</span>\n @if (chipTemplate(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"{ $implicit: chip.event }\" />\n } @else {\n <span class=\"wr-event-calendar__chip-title\" aria-hidden=\"true\">{{ chip.event.title }}</span>\n }\n\n @if (canDrag(chip.event) && !chip.continuesAfter) {\n <!--\n A grab area, not a control: the same resize is on the keyboard\n as `Alt` + arrows on the chip itself, so exposing a second\n focusable thing here would add a tab stop that leads nowhere.\n -->\n <span\n class=\"wr-event-calendar__resize\"\n aria-hidden=\"true\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'resize')\"\n ></span>\n }\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n\n<!-- One chip shape for both day-spanning cases: month cells and the all-day band. -->\n<ng-template #band let-chip let-offset=\"offset\">\n <button\n type=\"button\"\n class=\"wr-event-calendar__chip wr-event-calendar__chip--band\"\n [class.wr-event-calendar__chip--dragging]=\"dragging() === chip.event.id\"\n [class.wr-event-calendar__chip--continues-before]=\"chip.continuesBefore\"\n [class.wr-event-calendar__chip--continues-after]=\"chip.continuesAfter\"\n [style.--wr-event-calendar-color]=\"`var(--wr-color-${chip.event.color ?? 'primary'})`\"\n [style.--wr-event-calendar-span]=\"chip.span\"\n [style.--wr-event-calendar-lane]=\"chip.lane\"\n [style.--wr-event-calendar-offset]=\"offset\"\n [attr.aria-label]=\"chipLabel(chip.event)\"\n (click)=\"onChipClick(chip.event); $event.stopPropagation()\"\n (keydown)=\"onChipKeyDown($event, chip.event)\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'move')\"\n >\n @if (chipTemplate(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"{ $implicit: chip.event }\" />\n } @else {\n <span class=\"wr-event-calendar__chip-title\" aria-hidden=\"true\">{{ chip.event.title }}</span>\n }\n </button>\n</ng-template>\n" }]
|
|
716
|
+
}], propDecorators: { events: [{ type: i0.Input, args: [{ isSignal: true, alias: "events", required: false }] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }, { type: i0.Output, args: ["viewChange"] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }, { type: i0.Output, args: ["dateChange"] }], views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], slotMinutes: [{ type: i0.Input, args: [{ isSignal: true, alias: "slotMinutes", required: false }] }], dayStartHour: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayStartHour", required: false }] }], dayEndHour: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayEndHour", required: false }] }], maxLanes: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLanes", required: false }] }], hideHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideHeader", required: false }] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }], slotClick: [{ type: i0.Output, args: ["slotClick"] }], eventChange: [{ type: i0.Output, args: ["eventChange"] }], chipTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => WrCalendarEventTemplate), { isSignal: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Generated bundle index. Do not edit.
|
|
720
|
+
*/
|
|
721
|
+
|
|
722
|
+
export { WrCalendarEventTemplate, WrEventCalendar };
|
|
723
|
+
//# sourceMappingURL=ngwr-event-calendar.mjs.map
|