@memberjunction/ng-timeline 2.122.1 → 2.122.2

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.
@@ -1,200 +1,1972 @@
1
- import { Component, Input } from '@angular/core';
2
- import { RunView } from '@memberjunction/core';
1
+ /**
2
+ * @fileoverview MJ Timeline Component - A flexible, responsive timeline for Angular.
3
+ *
4
+ * This component displays chronological data in a timeline format with support for:
5
+ * - Multiple data sources (MemberJunction entities or plain objects)
6
+ * - Virtual scrolling for large datasets
7
+ * - Collapsible time segments
8
+ * - Rich event system with BeforeX/AfterX pattern
9
+ * - Full keyboard navigation and accessibility
10
+ * - Responsive design for all screen sizes
11
+ *
12
+ * @module @memberjunction/ng-timeline
13
+ */
14
+ import { Component, Input, Output, EventEmitter, ViewChild, ContentChild, ChangeDetectionStrategy } from '@angular/core';
15
+ import { Subject } from 'rxjs';
16
+ import { takeUntil, debounceTime } from 'rxjs/operators';
17
+ import { DEFAULT_CARD_CONFIG, DEFAULT_VIRTUAL_SCROLL_CONFIG, DEFAULT_VIRTUAL_SCROLL_STATE } from '../types';
18
+ import { getFieldValue } from '../timeline-group';
3
19
  import * as i0 from "@angular/core";
4
- import * as i1 from "@progress/kendo-angular-layout";
20
+ import * as i1 from "@angular/common";
21
+ const _c0 = ["cardTemplate"];
22
+ const _c1 = ["headerTemplate"];
23
+ const _c2 = ["bodyTemplate"];
24
+ const _c3 = ["actionsTemplate"];
25
+ const _c4 = ["segmentHeaderTemplate"];
26
+ const _c5 = ["emptyTemplate"];
27
+ const _c6 = ["loadingTemplate"];
28
+ const _c7 = ["scrollContainer"];
29
+ const _c8 = a0 => ({ segment: a0 });
30
+ const _c9 = (a0, a1, a2, a3) => ({ event: a0, index: a1, isOdd: a2, globalIndex: a3 });
31
+ const _c10 = (a0, a1) => ({ event: a0, group: a1 });
32
+ const _c11 = a0 => ({ event: a0 });
33
+ const _c12 = (a0, a1) => ({ event: a0, actions: a1 });
34
+ function TimelineComponent_ng_container_2_ng_container_1_ng_container_1_Template(rf, ctx) { if (rf & 1) {
35
+ i0.ɵɵelementContainer(0);
36
+ } }
37
+ function TimelineComponent_ng_container_2_ng_container_1_Template(rf, ctx) { if (rf & 1) {
38
+ i0.ɵɵelementContainerStart(0);
39
+ i0.ɵɵtemplate(1, TimelineComponent_ng_container_2_ng_container_1_ng_container_1_Template, 1, 0, "ng-container", 15);
40
+ i0.ɵɵelementContainerEnd();
41
+ } if (rf & 2) {
42
+ const ctx_r1 = i0.ɵɵnextContext(2);
43
+ i0.ɵɵadvance();
44
+ i0.ɵɵproperty("ngTemplateOutlet", ctx_r1.loadingTemplate);
45
+ } }
46
+ function TimelineComponent_ng_container_2_ng_template_2_Template(rf, ctx) { if (rf & 1) {
47
+ i0.ɵɵelementStart(0, "div", 16);
48
+ i0.ɵɵelement(1, "div", 17);
49
+ i0.ɵɵelementStart(2, "span", 18);
50
+ i0.ɵɵtext(3);
51
+ i0.ɵɵelementEnd()();
52
+ } if (rf & 2) {
53
+ const ctx_r1 = i0.ɵɵnextContext(2);
54
+ i0.ɵɵadvance(3);
55
+ i0.ɵɵtextInterpolate(ctx_r1.loadingMessage);
56
+ } }
57
+ function TimelineComponent_ng_container_2_Template(rf, ctx) { if (rf & 1) {
58
+ i0.ɵɵelementContainerStart(0);
59
+ i0.ɵɵtemplate(1, TimelineComponent_ng_container_2_ng_container_1_Template, 2, 1, "ng-container", 14)(2, TimelineComponent_ng_container_2_ng_template_2_Template, 4, 1, "ng-template", null, 2, i0.ɵɵtemplateRefExtractor);
60
+ i0.ɵɵelementContainerEnd();
61
+ } if (rf & 2) {
62
+ const defaultLoading_r3 = i0.ɵɵreference(3);
63
+ const ctx_r1 = i0.ɵɵnextContext();
64
+ i0.ɵɵadvance();
65
+ i0.ɵɵproperty("ngIf", ctx_r1.loadingTemplate)("ngIfElse", defaultLoading_r3);
66
+ } }
67
+ function TimelineComponent_ng_container_3_ng_container_1_ng_container_1_Template(rf, ctx) { if (rf & 1) {
68
+ i0.ɵɵelementContainer(0);
69
+ } }
70
+ function TimelineComponent_ng_container_3_ng_container_1_Template(rf, ctx) { if (rf & 1) {
71
+ i0.ɵɵelementContainerStart(0);
72
+ i0.ɵɵtemplate(1, TimelineComponent_ng_container_3_ng_container_1_ng_container_1_Template, 1, 0, "ng-container", 15);
73
+ i0.ɵɵelementContainerEnd();
74
+ } if (rf & 2) {
75
+ const ctx_r1 = i0.ɵɵnextContext(2);
76
+ i0.ɵɵadvance();
77
+ i0.ɵɵproperty("ngTemplateOutlet", ctx_r1.emptyTemplate);
78
+ } }
79
+ function TimelineComponent_ng_container_3_ng_template_2_Template(rf, ctx) { if (rf & 1) {
80
+ i0.ɵɵelementStart(0, "div", 19);
81
+ i0.ɵɵelement(1, "i", 20);
82
+ i0.ɵɵelementStart(2, "span", 21);
83
+ i0.ɵɵtext(3);
84
+ i0.ɵɵelementEnd()();
85
+ } if (rf & 2) {
86
+ const ctx_r1 = i0.ɵɵnextContext(2);
87
+ i0.ɵɵadvance();
88
+ i0.ɵɵclassMap(ctx_r1.emptyIcon);
89
+ i0.ɵɵadvance(2);
90
+ i0.ɵɵtextInterpolate(ctx_r1.emptyMessage);
91
+ } }
92
+ function TimelineComponent_ng_container_3_Template(rf, ctx) { if (rf & 1) {
93
+ i0.ɵɵelementContainerStart(0);
94
+ i0.ɵɵtemplate(1, TimelineComponent_ng_container_3_ng_container_1_Template, 2, 1, "ng-container", 14)(2, TimelineComponent_ng_container_3_ng_template_2_Template, 4, 3, "ng-template", null, 3, i0.ɵɵtemplateRefExtractor);
95
+ i0.ɵɵelementContainerEnd();
96
+ } if (rf & 2) {
97
+ const defaultEmpty_r4 = i0.ɵɵreference(3);
98
+ const ctx_r1 = i0.ɵɵnextContext();
99
+ i0.ɵɵadvance();
100
+ i0.ɵɵproperty("ngIf", ctx_r1.emptyTemplate)("ngIfElse", defaultEmpty_r4);
101
+ } }
102
+ function TimelineComponent_ng_container_4_ng_container_1_div_1_ng_container_2_ng_container_1_Template(rf, ctx) { if (rf & 1) {
103
+ i0.ɵɵelementContainer(0);
104
+ } }
105
+ function TimelineComponent_ng_container_4_ng_container_1_div_1_ng_container_2_Template(rf, ctx) { if (rf & 1) {
106
+ i0.ɵɵelementContainerStart(0);
107
+ i0.ɵɵtemplate(1, TimelineComponent_ng_container_4_ng_container_1_div_1_ng_container_2_ng_container_1_Template, 1, 0, "ng-container", 28);
108
+ i0.ɵɵelementContainerEnd();
109
+ } if (rf & 2) {
110
+ const segment_r6 = i0.ɵɵnextContext().$implicit;
111
+ const ctx_r1 = i0.ɵɵnextContext(3);
112
+ i0.ɵɵadvance();
113
+ i0.ɵɵproperty("ngTemplateOutlet", ctx_r1.segmentHeaderTemplate)("ngTemplateOutletContext", i0.ɵɵpureFunction1(2, _c8, segment_r6));
114
+ } }
115
+ function TimelineComponent_ng_container_4_ng_container_1_div_1_ng_template_3_span_0_Template(rf, ctx) { if (rf & 1) {
116
+ i0.ɵɵelementStart(0, "span", 33);
117
+ i0.ɵɵelement(1, "i");
118
+ i0.ɵɵelementEnd();
119
+ } if (rf & 2) {
120
+ const segment_r6 = i0.ɵɵnextContext(2).$implicit;
121
+ i0.ɵɵadvance();
122
+ i0.ɵɵclassMap(segment_r6.isExpanded ? "fa-solid fa-chevron-down" : "fa-solid fa-chevron-right");
123
+ } }
124
+ function TimelineComponent_ng_container_4_ng_container_1_div_1_ng_template_3_Template(rf, ctx) { if (rf & 1) {
125
+ i0.ɵɵtemplate(0, TimelineComponent_ng_container_4_ng_container_1_div_1_ng_template_3_span_0_Template, 2, 2, "span", 29);
126
+ i0.ɵɵelementStart(1, "span", 30);
127
+ i0.ɵɵtext(2);
128
+ i0.ɵɵelementEnd();
129
+ i0.ɵɵelementStart(3, "span", 31);
130
+ i0.ɵɵtext(4);
131
+ i0.ɵɵelementEnd();
132
+ i0.ɵɵelement(5, "span", 32);
133
+ } if (rf & 2) {
134
+ const segment_r6 = i0.ɵɵnextContext().$implicit;
135
+ const ctx_r1 = i0.ɵɵnextContext(3);
136
+ i0.ɵɵproperty("ngIf", ctx_r1.segmentsCollapsible);
137
+ i0.ɵɵadvance(2);
138
+ i0.ɵɵtextInterpolate(segment_r6.label);
139
+ i0.ɵɵadvance(2);
140
+ i0.ɵɵtextInterpolate2("(", segment_r6.eventCount, " ", segment_r6.eventCount === 1 ? "event" : "events", ")");
141
+ } }
142
+ function TimelineComponent_ng_container_4_ng_container_1_div_1_ng_container_7_ng_container_1_Template(rf, ctx) { if (rf & 1) {
143
+ i0.ɵɵelementContainer(0);
144
+ } }
145
+ function TimelineComponent_ng_container_4_ng_container_1_div_1_ng_container_7_Template(rf, ctx) { if (rf & 1) {
146
+ i0.ɵɵelementContainerStart(0);
147
+ i0.ɵɵtemplate(1, TimelineComponent_ng_container_4_ng_container_1_div_1_ng_container_7_ng_container_1_Template, 1, 0, "ng-container", 28);
148
+ i0.ɵɵelementContainerEnd();
149
+ } if (rf & 2) {
150
+ const event_r7 = ctx.$implicit;
151
+ const eventIndex_r8 = ctx.index;
152
+ const isOdd_r9 = ctx.odd;
153
+ const ctx_r1 = i0.ɵɵnextContext(4);
154
+ const eventCard_r10 = i0.ɵɵreference(8);
155
+ i0.ɵɵadvance();
156
+ i0.ɵɵproperty("ngTemplateOutlet", eventCard_r10)("ngTemplateOutletContext", i0.ɵɵpureFunction4(2, _c9, event_r7, eventIndex_r8, isOdd_r9, ctx_r1.getGlobalIndex(event_r7)));
157
+ } }
158
+ function TimelineComponent_ng_container_4_ng_container_1_div_1_Template(rf, ctx) { if (rf & 1) {
159
+ const _r5 = i0.ɵɵgetCurrentView();
160
+ i0.ɵɵelementStart(0, "div", 23)(1, "div", 24);
161
+ i0.ɵɵlistener("click", function TimelineComponent_ng_container_4_ng_container_1_div_1_Template_div_click_1_listener() { const segment_r6 = i0.ɵɵrestoreView(_r5).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.onSegmentClick(segment_r6)); });
162
+ i0.ɵɵtemplate(2, TimelineComponent_ng_container_4_ng_container_1_div_1_ng_container_2_Template, 2, 4, "ng-container", 14)(3, TimelineComponent_ng_container_4_ng_container_1_div_1_ng_template_3_Template, 6, 4, "ng-template", null, 4, i0.ɵɵtemplateRefExtractor);
163
+ i0.ɵɵelementEnd();
164
+ i0.ɵɵelementStart(5, "div", 25)(6, "div", 26);
165
+ i0.ɵɵtemplate(7, TimelineComponent_ng_container_4_ng_container_1_div_1_ng_container_7_Template, 2, 7, "ng-container", 27);
166
+ i0.ɵɵelementEnd()()();
167
+ } if (rf & 2) {
168
+ const segment_r6 = ctx.$implicit;
169
+ const segmentIndex_r11 = ctx.index;
170
+ const defaultSegmentHeader_r12 = i0.ɵɵreference(4);
171
+ const ctx_r1 = i0.ɵɵnextContext(3);
172
+ i0.ɵɵclassProp("mj-timeline__segment--collapsed", !segment_r6.isExpanded);
173
+ i0.ɵɵattribute("data-segment-label", segment_r6.label);
174
+ i0.ɵɵadvance();
175
+ i0.ɵɵclassProp("mj-timeline__segment-header--clickable", ctx_r1.segmentsCollapsible);
176
+ i0.ɵɵattribute("aria-expanded", segment_r6.isExpanded)("aria-controls", "segment-content-" + segmentIndex_r11);
177
+ i0.ɵɵadvance();
178
+ i0.ɵɵproperty("ngIf", ctx_r1.segmentHeaderTemplate)("ngIfElse", defaultSegmentHeader_r12);
179
+ i0.ɵɵadvance(3);
180
+ i0.ɵɵclassProp("mj-timeline__segment-content--hidden", !segment_r6.isExpanded);
181
+ i0.ɵɵproperty("id", "segment-content-" + segmentIndex_r11);
182
+ i0.ɵɵadvance(2);
183
+ i0.ɵɵproperty("ngForOf", segment_r6.events)("ngForTrackBy", ctx_r1.trackByEventId);
184
+ } }
185
+ function TimelineComponent_ng_container_4_ng_container_1_Template(rf, ctx) { if (rf & 1) {
186
+ i0.ɵɵelementContainerStart(0);
187
+ i0.ɵɵtemplate(1, TimelineComponent_ng_container_4_ng_container_1_div_1_Template, 8, 14, "div", 22);
188
+ i0.ɵɵelementContainerEnd();
189
+ } if (rf & 2) {
190
+ const ctx_r1 = i0.ɵɵnextContext(2);
191
+ i0.ɵɵadvance();
192
+ i0.ɵɵproperty("ngForOf", ctx_r1.segments)("ngForTrackBy", ctx_r1.trackBySegmentLabel);
193
+ } }
194
+ function TimelineComponent_ng_container_4_ng_container_2_ng_container_2_ng_container_1_Template(rf, ctx) { if (rf & 1) {
195
+ i0.ɵɵelementContainer(0);
196
+ } }
197
+ function TimelineComponent_ng_container_4_ng_container_2_ng_container_2_Template(rf, ctx) { if (rf & 1) {
198
+ i0.ɵɵelementContainerStart(0);
199
+ i0.ɵɵtemplate(1, TimelineComponent_ng_container_4_ng_container_2_ng_container_2_ng_container_1_Template, 1, 0, "ng-container", 28);
200
+ i0.ɵɵelementContainerEnd();
201
+ } if (rf & 2) {
202
+ const event_r13 = ctx.$implicit;
203
+ const eventIndex_r14 = ctx.index;
204
+ const isOdd_r15 = ctx.odd;
205
+ i0.ɵɵnextContext(3);
206
+ const eventCard_r10 = i0.ɵɵreference(8);
207
+ i0.ɵɵadvance();
208
+ i0.ɵɵproperty("ngTemplateOutlet", eventCard_r10)("ngTemplateOutletContext", i0.ɵɵpureFunction4(2, _c9, event_r13, eventIndex_r14, isOdd_r15, eventIndex_r14));
209
+ } }
210
+ function TimelineComponent_ng_container_4_ng_container_2_Template(rf, ctx) { if (rf & 1) {
211
+ i0.ɵɵelementContainerStart(0);
212
+ i0.ɵɵelementStart(1, "div", 26);
213
+ i0.ɵɵtemplate(2, TimelineComponent_ng_container_4_ng_container_2_ng_container_2_Template, 2, 7, "ng-container", 27);
214
+ i0.ɵɵelementEnd();
215
+ i0.ɵɵelementContainerEnd();
216
+ } if (rf & 2) {
217
+ const ctx_r1 = i0.ɵɵnextContext(2);
218
+ i0.ɵɵadvance(2);
219
+ i0.ɵɵproperty("ngForOf", ctx_r1.allEvents)("ngForTrackBy", ctx_r1.trackByEventId);
220
+ } }
221
+ function TimelineComponent_ng_container_4_Template(rf, ctx) { if (rf & 1) {
222
+ i0.ɵɵelementContainerStart(0);
223
+ i0.ɵɵtemplate(1, TimelineComponent_ng_container_4_ng_container_1_Template, 2, 2, "ng-container", 11)(2, TimelineComponent_ng_container_4_ng_container_2_Template, 3, 2, "ng-container", 11);
224
+ i0.ɵɵelementContainerEnd();
225
+ } if (rf & 2) {
226
+ const ctx_r1 = i0.ɵɵnextContext();
227
+ i0.ɵɵadvance();
228
+ i0.ɵɵproperty("ngIf", ctx_r1.segmentGrouping !== "none");
229
+ i0.ɵɵadvance();
230
+ i0.ɵɵproperty("ngIf", ctx_r1.segmentGrouping === "none");
231
+ } }
232
+ function TimelineComponent_div_5_Template(rf, ctx) { if (rf & 1) {
233
+ i0.ɵɵelement(0, "div", 34);
234
+ } }
235
+ function TimelineComponent_div_6_Template(rf, ctx) { if (rf & 1) {
236
+ i0.ɵɵelementStart(0, "div", 35);
237
+ i0.ɵɵelement(1, "div", 36);
238
+ i0.ɵɵelementStart(2, "span");
239
+ i0.ɵɵtext(3);
240
+ i0.ɵɵelementEnd()();
241
+ } if (rf & 2) {
242
+ const ctx_r1 = i0.ɵɵnextContext();
243
+ i0.ɵɵadvance(3);
244
+ i0.ɵɵtextInterpolate(ctx_r1.virtualScroll.loadingMessage);
245
+ } }
246
+ function TimelineComponent_ng_template_7_div_4_Template(rf, ctx) { if (rf & 1) {
247
+ i0.ɵɵelementStart(0, "div", 43);
248
+ i0.ɵɵtext(1);
249
+ i0.ɵɵelementEnd();
250
+ } if (rf & 2) {
251
+ const event_r17 = i0.ɵɵnextContext().event;
252
+ const ctx_r1 = i0.ɵɵnextContext();
253
+ i0.ɵɵadvance();
254
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.formatDate(event_r17.date), " ");
255
+ } }
256
+ function TimelineComponent_ng_template_7_ng_container_6_ng_container_1_Template(rf, ctx) { if (rf & 1) {
257
+ i0.ɵɵelementContainer(0);
258
+ } }
259
+ function TimelineComponent_ng_template_7_ng_container_6_Template(rf, ctx) { if (rf & 1) {
260
+ i0.ɵɵelementContainerStart(0);
261
+ i0.ɵɵtemplate(1, TimelineComponent_ng_template_7_ng_container_6_ng_container_1_Template, 1, 0, "ng-container", 28);
262
+ i0.ɵɵelementContainerEnd();
263
+ } if (rf & 2) {
264
+ const event_r17 = i0.ɵɵnextContext().event;
265
+ const ctx_r1 = i0.ɵɵnextContext();
266
+ i0.ɵɵadvance();
267
+ i0.ɵɵproperty("ngTemplateOutlet", ctx_r1.cardTemplate)("ngTemplateOutletContext", i0.ɵɵpureFunction2(2, _c10, event_r17, ctx_r1.groups[event_r17.groupIndex]));
268
+ } }
269
+ function TimelineComponent_ng_template_7_ng_template_7_div_1_Template(rf, ctx) { if (rf & 1) {
270
+ i0.ɵɵelementStart(0, "div", 50);
271
+ i0.ɵɵelement(1, "img", 51);
272
+ i0.ɵɵelementEnd();
273
+ } if (rf & 2) {
274
+ const event_r17 = i0.ɵɵnextContext(2).event;
275
+ const ctx_r1 = i0.ɵɵnextContext();
276
+ i0.ɵɵclassProp("mj-timeline__card-image--small", ctx_r1.getEffectiveCardConfig(event_r17).imageSize === "small")("mj-timeline__card-image--medium", ctx_r1.getEffectiveCardConfig(event_r17).imageSize === "medium")("mj-timeline__card-image--large", ctx_r1.getEffectiveCardConfig(event_r17).imageSize === "large");
277
+ i0.ɵɵadvance();
278
+ i0.ɵɵproperty("src", event_r17.imageUrl, i0.ɵɵsanitizeUrl)("alt", event_r17.title);
279
+ } }
280
+ function TimelineComponent_ng_template_7_ng_template_7_ng_container_3_ng_container_1_Template(rf, ctx) { if (rf & 1) {
281
+ i0.ɵɵelementContainer(0);
282
+ } }
283
+ function TimelineComponent_ng_template_7_ng_template_7_ng_container_3_Template(rf, ctx) { if (rf & 1) {
284
+ i0.ɵɵelementContainerStart(0);
285
+ i0.ɵɵtemplate(1, TimelineComponent_ng_template_7_ng_template_7_ng_container_3_ng_container_1_Template, 1, 0, "ng-container", 28);
286
+ i0.ɵɵelementContainerEnd();
287
+ } if (rf & 2) {
288
+ const event_r17 = i0.ɵɵnextContext(2).event;
289
+ const ctx_r1 = i0.ɵɵnextContext();
290
+ i0.ɵɵadvance();
291
+ i0.ɵɵproperty("ngTemplateOutlet", ctx_r1.headerTemplate)("ngTemplateOutletContext", i0.ɵɵpureFunction1(2, _c11, event_r17));
292
+ } }
293
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_4_span_0_Template(rf, ctx) { if (rf & 1) {
294
+ i0.ɵɵelementStart(0, "span", 58);
295
+ i0.ɵɵelement(1, "i");
296
+ i0.ɵɵelementEnd();
297
+ } if (rf & 2) {
298
+ const event_r17 = i0.ɵɵnextContext(3).event;
299
+ const ctx_r1 = i0.ɵɵnextContext();
300
+ i0.ɵɵstyleProp("color", ctx_r1.getColor(event_r17));
301
+ i0.ɵɵadvance();
302
+ i0.ɵɵclassMap(ctx_r1.getIcon(event_r17));
303
+ } }
304
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_4_span_4_Template(rf, ctx) { if (rf & 1) {
305
+ i0.ɵɵelementStart(0, "span", 59);
306
+ i0.ɵɵtext(1);
307
+ i0.ɵɵelementEnd();
308
+ } if (rf & 2) {
309
+ const event_r17 = i0.ɵɵnextContext(3).event;
310
+ i0.ɵɵadvance();
311
+ i0.ɵɵtextInterpolate1(" ", event_r17.subtitle, " ");
312
+ } }
313
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_4_span_5_Template(rf, ctx) { if (rf & 1) {
314
+ i0.ɵɵelementStart(0, "span", 60);
315
+ i0.ɵɵtext(1);
316
+ i0.ɵɵelementEnd();
317
+ } if (rf & 2) {
318
+ const event_r17 = i0.ɵɵnextContext(3).event;
319
+ const ctx_r1 = i0.ɵɵnextContext();
320
+ i0.ɵɵadvance();
321
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.formatDate(event_r17.date, ctx_r1.getEffectiveCardConfig(event_r17).dateFormat), " ");
322
+ } }
323
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_4_button_6_Template(rf, ctx) { if (rf & 1) {
324
+ const _r22 = i0.ɵɵgetCurrentView();
325
+ i0.ɵɵelementStart(0, "button", 61);
326
+ i0.ɵɵlistener("click", function TimelineComponent_ng_template_7_ng_template_7_ng_template_4_button_6_Template_button_click_0_listener($event) { i0.ɵɵrestoreView(_r22); const ctx_r22 = i0.ɵɵnextContext(3); const event_r17 = ctx_r22.event; const globalIndex_r19 = ctx_r22.globalIndex; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onToggleExpand(event_r17, globalIndex_r19, $event)); });
327
+ i0.ɵɵelement(1, "i");
328
+ i0.ɵɵelementEnd();
329
+ } if (rf & 2) {
330
+ const event_r17 = i0.ɵɵnextContext(3).event;
331
+ i0.ɵɵattribute("aria-label", event_r17.isExpanded ? "Collapse" : "Expand");
332
+ i0.ɵɵadvance();
333
+ i0.ɵɵclassMap(event_r17.isExpanded ? "fa-solid fa-chevron-up" : "fa-solid fa-chevron-down");
334
+ } }
335
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_4_Template(rf, ctx) { if (rf & 1) {
336
+ i0.ɵɵtemplate(0, TimelineComponent_ng_template_7_ng_template_7_ng_template_4_span_0_Template, 2, 4, "span", 52);
337
+ i0.ɵɵelementStart(1, "div", 53)(2, "h4", 54);
338
+ i0.ɵɵtext(3);
339
+ i0.ɵɵelementEnd();
340
+ i0.ɵɵtemplate(4, TimelineComponent_ng_template_7_ng_template_7_ng_template_4_span_4_Template, 2, 1, "span", 55)(5, TimelineComponent_ng_template_7_ng_template_7_ng_template_4_span_5_Template, 2, 1, "span", 56);
341
+ i0.ɵɵelementEnd();
342
+ i0.ɵɵtemplate(6, TimelineComponent_ng_template_7_ng_template_7_ng_template_4_button_6_Template, 2, 3, "button", 57);
343
+ } if (rf & 2) {
344
+ const event_r17 = i0.ɵɵnextContext(2).event;
345
+ const ctx_r1 = i0.ɵɵnextContext();
346
+ i0.ɵɵproperty("ngIf", ctx_r1.getEffectiveCardConfig(event_r17).showIcon);
347
+ i0.ɵɵadvance(3);
348
+ i0.ɵɵtextInterpolate(event_r17.title);
349
+ i0.ɵɵadvance();
350
+ i0.ɵɵproperty("ngIf", event_r17.subtitle && ctx_r1.getEffectiveCardConfig(event_r17).showSubtitle);
351
+ i0.ɵɵadvance();
352
+ i0.ɵɵproperty("ngIf", ctx_r1.getEffectiveCardConfig(event_r17).showDate && ctx_r1.layout !== "alternating");
353
+ i0.ɵɵadvance();
354
+ i0.ɵɵproperty("ngIf", ctx_r1.getEffectiveCardConfig(event_r17).collapsible);
355
+ } }
356
+ function TimelineComponent_ng_template_7_ng_template_7_div_6_Template(rf, ctx) { if (rf & 1) {
357
+ i0.ɵɵelementStart(0, "div", 62);
358
+ i0.ɵɵelement(1, "img", 51);
359
+ i0.ɵɵelementEnd();
360
+ } if (rf & 2) {
361
+ const event_r17 = i0.ɵɵnextContext(2).event;
362
+ i0.ɵɵadvance();
363
+ i0.ɵɵproperty("src", event_r17.imageUrl, i0.ɵɵsanitizeUrl)("alt", event_r17.title);
364
+ } }
365
+ function TimelineComponent_ng_template_7_ng_template_7_ng_container_8_ng_container_1_Template(rf, ctx) { if (rf & 1) {
366
+ i0.ɵɵelementContainer(0);
367
+ } }
368
+ function TimelineComponent_ng_template_7_ng_template_7_ng_container_8_Template(rf, ctx) { if (rf & 1) {
369
+ i0.ɵɵelementContainerStart(0);
370
+ i0.ɵɵtemplate(1, TimelineComponent_ng_template_7_ng_template_7_ng_container_8_ng_container_1_Template, 1, 0, "ng-container", 28);
371
+ i0.ɵɵelementContainerEnd();
372
+ } if (rf & 2) {
373
+ const event_r17 = i0.ɵɵnextContext(2).event;
374
+ const ctx_r1 = i0.ɵɵnextContext();
375
+ i0.ɵɵadvance();
376
+ i0.ɵɵproperty("ngTemplateOutlet", ctx_r1.bodyTemplate)("ngTemplateOutletContext", i0.ɵɵpureFunction1(2, _c11, event_r17));
377
+ } }
378
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_0_ng_container_1_i_2_Template(rf, ctx) { if (rf & 1) {
379
+ i0.ɵɵelement(0, "i", 72);
380
+ } if (rf & 2) {
381
+ const field_r24 = i0.ɵɵnextContext().$implicit;
382
+ i0.ɵɵclassMap(field_r24.icon);
383
+ } }
384
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_0_ng_container_1_span_3_Template(rf, ctx) { if (rf & 1) {
385
+ i0.ɵɵelementStart(0, "span", 73);
386
+ i0.ɵɵtext(1);
387
+ i0.ɵɵelementEnd();
388
+ } if (rf & 2) {
389
+ const field_r24 = i0.ɵɵnextContext().$implicit;
390
+ i0.ɵɵadvance();
391
+ i0.ɵɵtextInterpolate1("", field_r24.label, ":");
392
+ } }
393
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_0_ng_container_1_Template(rf, ctx) { if (rf & 1) {
394
+ i0.ɵɵelementContainerStart(0);
395
+ i0.ɵɵelementStart(1, "div", 68);
396
+ i0.ɵɵtemplate(2, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_0_ng_container_1_i_2_Template, 1, 2, "i", 69)(3, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_0_ng_container_1_span_3_Template, 2, 1, "span", 70);
397
+ i0.ɵɵelementStart(4, "span", 71);
398
+ i0.ɵɵtext(5);
399
+ i0.ɵɵelementEnd()();
400
+ i0.ɵɵelementContainerEnd();
401
+ } if (rf & 2) {
402
+ const field_r24 = ctx.$implicit;
403
+ const event_r17 = i0.ɵɵnextContext(4).event;
404
+ const ctx_r1 = i0.ɵɵnextContext();
405
+ i0.ɵɵadvance();
406
+ i0.ɵɵclassMap(field_r24.cssClass);
407
+ i0.ɵɵadvance();
408
+ i0.ɵɵproperty("ngIf", field_r24.icon);
409
+ i0.ɵɵadvance();
410
+ i0.ɵɵproperty("ngIf", !field_r24.hideLabel && field_r24.label);
411
+ i0.ɵɵadvance(2);
412
+ i0.ɵɵtextInterpolate(ctx_r1.getFieldValue(event_r17, field_r24));
413
+ } }
414
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_0_Template(rf, ctx) { if (rf & 1) {
415
+ i0.ɵɵelementStart(0, "div", 66);
416
+ i0.ɵɵtemplate(1, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_0_ng_container_1_Template, 6, 5, "ng-container", 67);
417
+ i0.ɵɵelementEnd();
418
+ } if (rf & 2) {
419
+ const event_r17 = i0.ɵɵnextContext(3).event;
420
+ const ctx_r1 = i0.ɵɵnextContext();
421
+ i0.ɵɵadvance();
422
+ i0.ɵɵproperty("ngForOf", ctx_r1.getEffectiveCardConfig(event_r17).summaryFields);
423
+ } }
424
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_1_ng_container_1_Template(rf, ctx) { if (rf & 1) {
425
+ i0.ɵɵelementContainerStart(0);
426
+ i0.ɵɵelement(1, "div", 75);
427
+ i0.ɵɵelementContainerEnd();
428
+ } if (rf & 2) {
429
+ const event_r17 = i0.ɵɵnextContext(4).event;
430
+ i0.ɵɵadvance();
431
+ i0.ɵɵproperty("innerHTML", event_r17.description, i0.ɵɵsanitizeHtml);
432
+ } }
433
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_1_ng_template_2_Template(rf, ctx) { if (rf & 1) {
434
+ i0.ɵɵtext(0);
435
+ } if (rf & 2) {
436
+ const event_r17 = i0.ɵɵnextContext(4).event;
437
+ i0.ɵɵtextInterpolate1(" ", event_r17.description, " ");
438
+ } }
439
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_1_Template(rf, ctx) { if (rf & 1) {
440
+ i0.ɵɵelementStart(0, "div", 74);
441
+ i0.ɵɵtemplate(1, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_1_ng_container_1_Template, 2, 1, "ng-container", 14)(2, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_1_ng_template_2_Template, 1, 1, "ng-template", null, 8, i0.ɵɵtemplateRefExtractor);
442
+ i0.ɵɵelementEnd();
443
+ } if (rf & 2) {
444
+ let tmp_15_0;
445
+ const plainDescription_r25 = i0.ɵɵreference(3);
446
+ const event_r17 = i0.ɵɵnextContext(3).event;
447
+ const ctx_r1 = i0.ɵɵnextContext();
448
+ i0.ɵɵstyleProp("-webkit-line-clamp", ctx_r1.getEffectiveCardConfig(event_r17).descriptionMaxLines || null);
449
+ i0.ɵɵclassProp("mj-timeline__card-description--clamped", ((tmp_15_0 = ctx_r1.getEffectiveCardConfig(event_r17).descriptionMaxLines) !== null && tmp_15_0 !== undefined ? tmp_15_0 : 0) > 0);
450
+ i0.ɵɵadvance();
451
+ i0.ɵɵproperty("ngIf", ctx_r1.getEffectiveCardConfig(event_r17).allowHtmlDescription)("ngIfElse", plainDescription_r25);
452
+ } }
453
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_2_ng_container_1_i_2_Template(rf, ctx) { if (rf & 1) {
454
+ i0.ɵɵelement(0, "i", 72);
455
+ } if (rf & 2) {
456
+ const field_r26 = i0.ɵɵnextContext().$implicit;
457
+ i0.ɵɵclassMap(field_r26.icon);
458
+ } }
459
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_2_ng_container_1_span_3_Template(rf, ctx) { if (rf & 1) {
460
+ i0.ɵɵelementStart(0, "span", 73);
461
+ i0.ɵɵtext(1);
462
+ i0.ɵɵelementEnd();
463
+ } if (rf & 2) {
464
+ const field_r26 = i0.ɵɵnextContext().$implicit;
465
+ i0.ɵɵadvance();
466
+ i0.ɵɵtextInterpolate1("", field_r26.label || field_r26.fieldName, ":");
467
+ } }
468
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_2_ng_container_1_Template(rf, ctx) { if (rf & 1) {
469
+ i0.ɵɵelementContainerStart(0);
470
+ i0.ɵɵelementStart(1, "div", 68);
471
+ i0.ɵɵtemplate(2, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_2_ng_container_1_i_2_Template, 1, 2, "i", 69)(3, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_2_ng_container_1_span_3_Template, 2, 1, "span", 70);
472
+ i0.ɵɵelementStart(4, "span", 71);
473
+ i0.ɵɵtext(5);
474
+ i0.ɵɵelementEnd()();
475
+ i0.ɵɵelementContainerEnd();
476
+ } if (rf & 2) {
477
+ const field_r26 = ctx.$implicit;
478
+ const event_r17 = i0.ɵɵnextContext(4).event;
479
+ const ctx_r1 = i0.ɵɵnextContext();
480
+ i0.ɵɵadvance();
481
+ i0.ɵɵclassMap(field_r26.cssClass);
482
+ i0.ɵɵadvance();
483
+ i0.ɵɵproperty("ngIf", field_r26.icon);
484
+ i0.ɵɵadvance();
485
+ i0.ɵɵproperty("ngIf", !field_r26.hideLabel);
486
+ i0.ɵɵadvance(2);
487
+ i0.ɵɵtextInterpolate(ctx_r1.getFieldValue(event_r17, field_r26));
488
+ } }
489
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_2_Template(rf, ctx) { if (rf & 1) {
490
+ i0.ɵɵelementStart(0, "div", 76);
491
+ i0.ɵɵtemplate(1, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_2_ng_container_1_Template, 6, 5, "ng-container", 67);
492
+ i0.ɵɵelementEnd();
493
+ } if (rf & 2) {
494
+ const event_r17 = i0.ɵɵnextContext(3).event;
495
+ const ctx_r1 = i0.ɵɵnextContext();
496
+ i0.ɵɵadvance();
497
+ i0.ɵɵproperty("ngForOf", ctx_r1.getEffectiveCardConfig(event_r17).expandedFields);
498
+ } }
499
+ function TimelineComponent_ng_template_7_ng_template_7_ng_template_9_Template(rf, ctx) { if (rf & 1) {
500
+ i0.ɵɵtemplate(0, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_0_Template, 2, 1, "div", 63)(1, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_1_Template, 4, 6, "div", 64)(2, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_div_2_Template, 2, 1, "div", 65);
501
+ } if (rf & 2) {
502
+ let tmp_12_0;
503
+ let tmp_14_0;
504
+ const event_r17 = i0.ɵɵnextContext(2).event;
505
+ const ctx_r1 = i0.ɵɵnextContext();
506
+ i0.ɵɵproperty("ngIf", (tmp_12_0 = ctx_r1.getEffectiveCardConfig(event_r17).summaryFields) == null ? null : tmp_12_0.length);
507
+ i0.ɵɵadvance();
508
+ i0.ɵɵproperty("ngIf", event_r17.description && event_r17.isExpanded);
509
+ i0.ɵɵadvance();
510
+ i0.ɵɵproperty("ngIf", event_r17.isExpanded && ((tmp_14_0 = ctx_r1.getEffectiveCardConfig(event_r17).expandedFields) == null ? null : tmp_14_0.length));
511
+ } }
512
+ function TimelineComponent_ng_template_7_ng_template_7_div_11_ng_container_1_ng_container_1_Template(rf, ctx) { if (rf & 1) {
513
+ i0.ɵɵelementContainer(0);
514
+ } }
515
+ function TimelineComponent_ng_template_7_ng_template_7_div_11_ng_container_1_Template(rf, ctx) { if (rf & 1) {
516
+ i0.ɵɵelementContainerStart(0);
517
+ i0.ɵɵtemplate(1, TimelineComponent_ng_template_7_ng_template_7_div_11_ng_container_1_ng_container_1_Template, 1, 0, "ng-container", 28);
518
+ i0.ɵɵelementContainerEnd();
519
+ } if (rf & 2) {
520
+ const event_r17 = i0.ɵɵnextContext(3).event;
521
+ const ctx_r1 = i0.ɵɵnextContext();
522
+ i0.ɵɵadvance();
523
+ i0.ɵɵproperty("ngTemplateOutlet", ctx_r1.actionsTemplate)("ngTemplateOutletContext", i0.ɵɵpureFunction2(2, _c12, event_r17, ctx_r1.getActions(event_r17)));
524
+ } }
525
+ function TimelineComponent_ng_template_7_ng_template_7_div_11_ng_template_2_button_0_i_1_Template(rf, ctx) { if (rf & 1) {
526
+ i0.ɵɵelement(0, "i");
527
+ } if (rf & 2) {
528
+ const action_r28 = i0.ɵɵnextContext().$implicit;
529
+ i0.ɵɵclassMap(action_r28.icon);
530
+ } }
531
+ function TimelineComponent_ng_template_7_ng_template_7_div_11_ng_template_2_button_0_Template(rf, ctx) { if (rf & 1) {
532
+ const _r27 = i0.ɵɵgetCurrentView();
533
+ i0.ɵɵelementStart(0, "button", 79);
534
+ i0.ɵɵlistener("click", function TimelineComponent_ng_template_7_ng_template_7_div_11_ng_template_2_button_0_Template_button_click_0_listener($event) { const action_r28 = i0.ɵɵrestoreView(_r27).$implicit; const ctx_r22 = i0.ɵɵnextContext(4); const event_r17 = ctx_r22.event; const globalIndex_r19 = ctx_r22.globalIndex; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onActionClick(event_r17, action_r28, globalIndex_r19, $event)); });
535
+ i0.ɵɵtemplate(1, TimelineComponent_ng_template_7_ng_template_7_div_11_ng_template_2_button_0_i_1_Template, 1, 2, "i", 80);
536
+ i0.ɵɵelementStart(2, "span");
537
+ i0.ɵɵtext(3);
538
+ i0.ɵɵelementEnd()();
539
+ } if (rf & 2) {
540
+ const action_r28 = ctx.$implicit;
541
+ i0.ɵɵclassMap(action_r28.cssClass);
542
+ i0.ɵɵclassProp("mj-timeline__action--primary", action_r28.variant === "primary")("mj-timeline__action--secondary", action_r28.variant === "secondary" || !action_r28.variant)("mj-timeline__action--danger", action_r28.variant === "danger")("mj-timeline__action--link", action_r28.variant === "link");
543
+ i0.ɵɵproperty("disabled", action_r28.disabled)("title", action_r28.tooltip || "");
544
+ i0.ɵɵadvance();
545
+ i0.ɵɵproperty("ngIf", action_r28.icon);
546
+ i0.ɵɵadvance(2);
547
+ i0.ɵɵtextInterpolate(action_r28.label);
548
+ } }
549
+ function TimelineComponent_ng_template_7_ng_template_7_div_11_ng_template_2_Template(rf, ctx) { if (rf & 1) {
550
+ i0.ɵɵtemplate(0, TimelineComponent_ng_template_7_ng_template_7_div_11_ng_template_2_button_0_Template, 4, 14, "button", 78);
551
+ } if (rf & 2) {
552
+ const event_r17 = i0.ɵɵnextContext(3).event;
553
+ const ctx_r1 = i0.ɵɵnextContext();
554
+ i0.ɵɵproperty("ngForOf", ctx_r1.getActions(event_r17));
555
+ } }
556
+ function TimelineComponent_ng_template_7_ng_template_7_div_11_Template(rf, ctx) { if (rf & 1) {
557
+ i0.ɵɵelementStart(0, "div", 77);
558
+ i0.ɵɵtemplate(1, TimelineComponent_ng_template_7_ng_template_7_div_11_ng_container_1_Template, 2, 5, "ng-container", 14)(2, TimelineComponent_ng_template_7_ng_template_7_div_11_ng_template_2_Template, 1, 1, "ng-template", null, 9, i0.ɵɵtemplateRefExtractor);
559
+ i0.ɵɵelementEnd();
560
+ } if (rf & 2) {
561
+ const defaultActions_r29 = i0.ɵɵreference(3);
562
+ const event_r17 = i0.ɵɵnextContext(2).event;
563
+ const ctx_r1 = i0.ɵɵnextContext();
564
+ i0.ɵɵclassProp("mj-timeline__card-actions--hover-only", ctx_r1.getEffectiveCardConfig(event_r17).actionsOnHover);
565
+ i0.ɵɵadvance();
566
+ i0.ɵɵproperty("ngIf", ctx_r1.actionsTemplate)("ngIfElse", defaultActions_r29);
567
+ } }
568
+ function TimelineComponent_ng_template_7_ng_template_7_Template(rf, ctx) { if (rf & 1) {
569
+ i0.ɵɵelementStart(0, "div", 44);
570
+ i0.ɵɵtemplate(1, TimelineComponent_ng_template_7_ng_template_7_div_1_Template, 2, 8, "div", 45);
571
+ i0.ɵɵelementStart(2, "div", 46);
572
+ i0.ɵɵtemplate(3, TimelineComponent_ng_template_7_ng_template_7_ng_container_3_Template, 2, 4, "ng-container", 14)(4, TimelineComponent_ng_template_7_ng_template_7_ng_template_4_Template, 7, 5, "ng-template", null, 6, i0.ɵɵtemplateRefExtractor);
573
+ i0.ɵɵelementEnd()();
574
+ i0.ɵɵtemplate(6, TimelineComponent_ng_template_7_ng_template_7_div_6_Template, 2, 2, "div", 47);
575
+ i0.ɵɵelementStart(7, "div", 48);
576
+ i0.ɵɵtemplate(8, TimelineComponent_ng_template_7_ng_template_7_ng_container_8_Template, 2, 4, "ng-container", 14)(9, TimelineComponent_ng_template_7_ng_template_7_ng_template_9_Template, 3, 3, "ng-template", null, 7, i0.ɵɵtemplateRefExtractor);
577
+ i0.ɵɵelementEnd();
578
+ i0.ɵɵtemplate(11, TimelineComponent_ng_template_7_ng_template_7_div_11_Template, 4, 4, "div", 49);
579
+ } if (rf & 2) {
580
+ const defaultHeader_r30 = i0.ɵɵreference(5);
581
+ const defaultBody_r31 = i0.ɵɵreference(10);
582
+ const event_r17 = i0.ɵɵnextContext().event;
583
+ const ctx_r1 = i0.ɵɵnextContext();
584
+ i0.ɵɵadvance();
585
+ i0.ɵɵproperty("ngIf", event_r17.imageUrl && ctx_r1.getEffectiveCardConfig(event_r17).imagePosition === "left");
586
+ i0.ɵɵadvance(2);
587
+ i0.ɵɵproperty("ngIf", ctx_r1.headerTemplate)("ngIfElse", defaultHeader_r30);
588
+ i0.ɵɵadvance(3);
589
+ i0.ɵɵproperty("ngIf", event_r17.imageUrl && ctx_r1.getEffectiveCardConfig(event_r17).imagePosition === "top");
590
+ i0.ɵɵadvance();
591
+ i0.ɵɵclassProp("mj-timeline__card-body--collapsed", !event_r17.isExpanded && ctx_r1.getEffectiveCardConfig(event_r17).collapsible);
592
+ i0.ɵɵadvance();
593
+ i0.ɵɵproperty("ngIf", ctx_r1.bodyTemplate)("ngIfElse", defaultBody_r31);
594
+ i0.ɵɵadvance(3);
595
+ i0.ɵɵproperty("ngIf", ctx_r1.getActions(event_r17).length > 0);
596
+ } }
597
+ function TimelineComponent_ng_template_7_Template(rf, ctx) { if (rf & 1) {
598
+ const _r16 = i0.ɵɵgetCurrentView();
599
+ i0.ɵɵelementStart(0, "div", 37)(1, "div", 38);
600
+ i0.ɵɵelement(2, "i", 39);
601
+ i0.ɵɵelementEnd();
602
+ i0.ɵɵelement(3, "div", 40);
603
+ i0.ɵɵtemplate(4, TimelineComponent_ng_template_7_div_4_Template, 2, 1, "div", 41);
604
+ i0.ɵɵelementStart(5, "div", 42);
605
+ i0.ɵɵlistener("click", function TimelineComponent_ng_template_7_Template_div_click_5_listener($event) { const ctx_r17 = i0.ɵɵrestoreView(_r16); const event_r17 = ctx_r17.event; const globalIndex_r19 = ctx_r17.globalIndex; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onEventClick(event_r17, globalIndex_r19, $event)); })("mouseenter", function TimelineComponent_ng_template_7_Template_div_mouseenter_5_listener($event) { const ctx_r19 = i0.ɵɵrestoreView(_r16); const event_r17 = ctx_r19.event; const globalIndex_r19 = ctx_r19.globalIndex; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onEventMouseEnter(event_r17, globalIndex_r19, $event)); })("mouseleave", function TimelineComponent_ng_template_7_Template_div_mouseleave_5_listener($event) { const ctx_r20 = i0.ɵɵrestoreView(_r16); const event_r17 = ctx_r20.event; const globalIndex_r19 = ctx_r20.globalIndex; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onEventMouseLeave(event_r17, globalIndex_r19, $event)); });
606
+ i0.ɵɵtemplate(6, TimelineComponent_ng_template_7_ng_container_6_Template, 2, 5, "ng-container", 14)(7, TimelineComponent_ng_template_7_ng_template_7_Template, 12, 9, "ng-template", null, 5, i0.ɵɵtemplateRefExtractor);
607
+ i0.ɵɵelementEnd()();
608
+ } if (rf & 2) {
609
+ const event_r17 = ctx.event;
610
+ const isOdd_r32 = ctx.isOdd;
611
+ const globalIndex_r19 = ctx.globalIndex;
612
+ const defaultCard_r33 = i0.ɵɵreference(8);
613
+ const ctx_r1 = i0.ɵɵnextContext();
614
+ i0.ɵɵclassProp("mj-timeline__event--odd", isOdd_r32 && ctx_r1.layout === "alternating")("mj-timeline__event--even", !isOdd_r32 && ctx_r1.layout === "alternating")("mj-timeline__event--expanded", event_r17.isExpanded)("mj-timeline__event--focused", ctx_r1.focusedEventIndex === globalIndex_r19);
615
+ i0.ɵɵattribute("data-event-id", event_r17.id)("aria-expanded", event_r17.isExpanded);
616
+ i0.ɵɵadvance();
617
+ i0.ɵɵstyleProp("background-color", ctx_r1.getColor(event_r17));
618
+ i0.ɵɵadvance();
619
+ i0.ɵɵclassMap(ctx_r1.getIcon(event_r17));
620
+ i0.ɵɵadvance();
621
+ i0.ɵɵstyleProp("background-color", ctx_r1.getColor(event_r17));
622
+ i0.ɵɵadvance();
623
+ i0.ɵɵproperty("ngIf", ctx_r1.layout === "alternating");
624
+ i0.ɵɵadvance();
625
+ i0.ɵɵclassMap(ctx_r1.getEffectiveCardConfig(event_r17).cssClass);
626
+ i0.ɵɵstyleProp("max-width", ctx_r1.getEffectiveCardConfig(event_r17).maxWidth)("min-width", ctx_r1.getEffectiveCardConfig(event_r17).minWidth)("border-left-color", ctx_r1.getColor(event_r17));
627
+ i0.ɵɵadvance();
628
+ i0.ɵɵproperty("ngIf", ctx_r1.cardTemplate)("ngIfElse", defaultCard_r33);
629
+ } }
630
+ // ============================================================================
631
+ // AUTO-ASSIGNED COLORS FOR GROUPS
632
+ // ============================================================================
633
+ const AUTO_COLORS = [
634
+ '#1976d2', // Blue
635
+ '#388e3c', // Green
636
+ '#f57c00', // Orange
637
+ '#7b1fa2', // Purple
638
+ '#c2185b', // Pink
639
+ '#00796b', // Teal
640
+ '#5d4037', // Brown
641
+ '#455a64', // Blue Grey
642
+ '#d32f2f', // Red
643
+ '#0097a7' // Cyan
644
+ ];
645
+ const DEFAULT_ICONS = [
646
+ 'fa-solid fa-circle',
647
+ 'fa-solid fa-square',
648
+ 'fa-solid fa-diamond',
649
+ 'fa-solid fa-star',
650
+ 'fa-solid fa-heart'
651
+ ];
652
+ // ============================================================================
653
+ // TIMELINE COMPONENT
654
+ // ============================================================================
5
655
  /**
656
+ * MJ Timeline Component - Displays chronological data in a rich, interactive timeline.
657
+ *
658
+ * The timeline component supports multiple data groups, virtual scrolling for large
659
+ * datasets, collapsible time segments, and a comprehensive event system that allows
660
+ * container components to intercept and modify behavior.
6
661
  *
662
+ * @example Basic usage
663
+ * ```html
664
+ * <mj-timeline [groups]="myGroups"></mj-timeline>
665
+ * ```
666
+ *
667
+ * @example Full configuration
668
+ * ```html
669
+ * <mj-timeline
670
+ * [groups]="groups"
671
+ * orientation="vertical"
672
+ * layout="alternating"
673
+ * sortOrder="desc"
674
+ * segmentGrouping="month"
675
+ * [segmentsCollapsible]="true"
676
+ * [virtualScroll]="{ enabled: true, batchSize: 25 }"
677
+ * (beforeEventClick)="onBeforeClick($event)"
678
+ * (afterActionClick)="onAction($event)">
679
+ *
680
+ * <ng-template #emptyTemplate>
681
+ * <div class="custom-empty">No events found</div>
682
+ * </ng-template>
683
+ *
684
+ * </mj-timeline>
685
+ * ```
7
686
  */
8
- export class TimelineGroup {
687
+ export class TimelineComponent {
688
+ cdr;
689
+ elementRef;
690
+ ngZone;
691
+ // ============================================================================
692
+ // INPUTS - DATA
693
+ // ============================================================================
9
694
  /**
10
- * Entity name for the type of records to be displayed in this group
695
+ * Array of timeline groups to display.
696
+ * Each group defines a data source and display configuration.
11
697
  */
12
- EntityName;
698
+ get groups() {
699
+ return this._groups;
700
+ }
701
+ set groups(value) {
702
+ this._groups = value || [];
703
+ if (this._initialized && this.allowLoad) {
704
+ this.refresh();
705
+ }
706
+ }
707
+ _groups = [];
13
708
  /**
14
- * Specifies if the data will come from a provided array of BaseEntity objects - the EntityObjects array, or alternatively, from this object running its own view against the provided EntityName, optionally with a provided Filter (or without).
709
+ * Controls whether data loading is allowed.
710
+ * Set to false to defer loading until ready.
711
+ * @default true
15
712
  */
16
- DataSourceType = 'entity';
713
+ get allowLoad() {
714
+ return this._allowLoad;
715
+ }
716
+ set allowLoad(value) {
717
+ const wasDisabled = !this._allowLoad;
718
+ this._allowLoad = value;
719
+ if (value && wasDisabled && this._initialized && !this._hasLoaded) {
720
+ this.refresh();
721
+ }
722
+ }
723
+ _allowLoad = true;
724
+ // ============================================================================
725
+ // INPUTS - LAYOUT
726
+ // ============================================================================
727
+ /**
728
+ * Timeline orientation.
729
+ * - `vertical`: Events displayed top-to-bottom
730
+ * - `horizontal`: Events displayed left-to-right
731
+ * @default 'vertical'
732
+ */
733
+ orientation = 'vertical';
734
+ /**
735
+ * Layout mode for vertical timeline.
736
+ * - `single`: All cards on one side
737
+ * - `alternating`: Cards alternate sides
738
+ * @default 'single'
739
+ */
740
+ layout = 'single';
17
741
  /**
18
- * An optional filter that will be applied to the entity specified to reduce the number of records displayed
742
+ * Sort order for events.
743
+ * - `desc`: Newest first
744
+ * - `asc`: Oldest first
745
+ * @default 'desc'
19
746
  */
20
- Filter;
747
+ sortOrder = 'desc';
21
748
  /**
22
- * The actual data you want displayed in this group. This is an array of BaseEntity objects that will be displayed in the timeline. You can populate this array from a view or any other source.
749
+ * How to group events into time segments.
750
+ * @default 'month'
23
751
  */
24
- EntityObjects = [];
752
+ segmentGrouping = 'month';
753
+ // ============================================================================
754
+ // INPUTS - CARD DEFAULTS
755
+ // ============================================================================
25
756
  /**
26
- * The name of the field in the entity that contains the title of the record that will be displayed in the timeline
757
+ * Default card configuration applied to all groups.
758
+ * Individual groups can override these settings.
27
759
  */
28
- TitleFieldName;
760
+ defaultCardConfig = { ...DEFAULT_CARD_CONFIG };
761
+ // ============================================================================
762
+ // INPUTS - VIRTUAL SCROLLING
763
+ // ============================================================================
29
764
  /**
30
- * The name of the field in the entity that contains the date that will be used for the ordering in the timeline
765
+ * Virtual scrolling configuration.
31
766
  */
32
- DateFieldName;
767
+ virtualScroll = { ...DEFAULT_VIRTUAL_SCROLL_CONFIG };
768
+ // ============================================================================
769
+ // INPUTS - SEGMENTS
770
+ // ============================================================================
33
771
  /**
34
- * Use standard or custom icons, if custom is specified, the DisplayIcon property must be set
772
+ * Whether time segments can be collapsed.
773
+ * @default true
35
774
  */
36
- DisplayIconMode = 'standard';
775
+ segmentsCollapsible = true;
37
776
  /**
38
- * Only used if DisplayIconMode is set to custom, the CSS class name to use from Font Awesome (or any other library that has styles pre-loaded), for the span that will be shown
777
+ * Whether segments start expanded.
778
+ * @default true
39
779
  */
40
- DisplayIcon;
780
+ segmentsDefaultExpanded = true;
781
+ // ============================================================================
782
+ // INPUTS - EMPTY & LOADING STATES
783
+ // ============================================================================
41
784
  /**
42
- * Color mode for items in this group, defaults to auto-selected in which case the color will be determined by the system automatically based on the # of groups
785
+ * Message shown when no events exist.
786
+ * @default 'No events to display'
43
787
  */
44
- DisplayColorMode = 'auto';
788
+ emptyMessage = 'No events to display';
45
789
  /**
46
- * Only used if DisplayColorMode is set to manual, the color to use for the items in this group. Any valid color string that can be set into the element style via CSS is valid here.
790
+ * Icon shown with empty message.
791
+ * @default 'fa-regular fa-calendar-xmark'
47
792
  */
48
- DisplayColor;
793
+ emptyIcon = 'fa-regular fa-calendar-xmark';
49
794
  /**
50
- * When set to field, the SummaryFieldName will be used to display detailed information about the record in the timeline. If set to custom, you need to provide
51
- * a function for the SummaryFunction property. If set to none, no summary will be displayed.
795
+ * Message shown while loading.
796
+ * @default 'Loading timeline...'
52
797
  */
53
- SummaryMode = 'field';
798
+ loadingMessage = 'Loading timeline...';
799
+ // ============================================================================
800
+ // INPUTS - ACCESSIBILITY
801
+ // ============================================================================
54
802
  /**
55
- * When SummaryMode is set to 'custom', this function will be used to generate the summary for the record. The function should take a single parameter, the BaseEntity object and will return a string.
56
- * The string returned can be plain text or HTML and will be displayed in the timeline.
803
+ * ARIA label for the timeline container.
804
+ * @default 'Timeline'
57
805
  */
58
- SummaryFunction;
806
+ ariaLabel = 'Timeline';
59
807
  /**
60
- * Creates a new instance of the TimelineGroup class using the information from the RunViewParams provided.
61
- * After receiving back the new object, you can set other properties of the new instance as appropriate.
62
- * @param params
63
- * @returns
808
+ * Enable keyboard navigation.
809
+ * @default true
64
810
  */
65
- static async FromView(params) {
66
- const group = new TimelineGroup();
67
- const rv = new RunView();
68
- params.ResultType = 'entity_object'; // this might be already set but we want to make sure of it
69
- const result = await rv.RunView(params);
70
- if (result && result.Success) {
71
- group.EntityName = await RunView.GetEntityNameFromRunViewParams(params);
72
- group.EntityObjects = result.Results;
811
+ enableKeyboardNavigation = true;
812
+ // ============================================================================
813
+ // OUTPUTS - BEFORE EVENTS (with cancel support)
814
+ // ============================================================================
815
+ /** Emitted before an event card is clicked. Set `cancel = true` to prevent. */
816
+ beforeEventClick = new EventEmitter();
817
+ /** Emitted before an event card expands. Set `cancel = true` to prevent. */
818
+ beforeEventExpand = new EventEmitter();
819
+ /** Emitted before an event card collapses. Set `cancel = true` to prevent. */
820
+ beforeEventCollapse = new EventEmitter();
821
+ /** Emitted before hover state changes. Set `cancel = true` to prevent. */
822
+ beforeEventHover = new EventEmitter();
823
+ /** Emitted before an action button is clicked. Set `cancel = true` to prevent. */
824
+ beforeActionClick = new EventEmitter();
825
+ /** Emitted before a time segment expands. Set `cancel = true` to prevent. */
826
+ beforeSegmentExpand = new EventEmitter();
827
+ /** Emitted before a time segment collapses. Set `cancel = true` to prevent. */
828
+ beforeSegmentCollapse = new EventEmitter();
829
+ /** Emitted before data loading begins. Set `cancel = true` to prevent. */
830
+ beforeLoad = new EventEmitter();
831
+ // ============================================================================
832
+ // OUTPUTS - AFTER EVENTS
833
+ // ============================================================================
834
+ /** Emitted after an event card is clicked. */
835
+ afterEventClick = new EventEmitter();
836
+ /** Emitted after an event card expands. */
837
+ afterEventExpand = new EventEmitter();
838
+ /** Emitted after an event card collapses. */
839
+ afterEventCollapse = new EventEmitter();
840
+ /** Emitted after hover state changes. */
841
+ afterEventHover = new EventEmitter();
842
+ /** Emitted after an action button is clicked. */
843
+ afterActionClick = new EventEmitter();
844
+ /** Emitted after a time segment expands. */
845
+ afterSegmentExpand = new EventEmitter();
846
+ /** Emitted after a time segment collapses. */
847
+ afterSegmentCollapse = new EventEmitter();
848
+ /** Emitted after data loading completes. */
849
+ afterLoad = new EventEmitter();
850
+ // ============================================================================
851
+ // CONTENT CHILDREN - OPTIONAL TEMPLATES
852
+ // ============================================================================
853
+ /** Custom template for entire card. Context: { event, group } */
854
+ cardTemplate;
855
+ /** Custom template for card header. Context: { event } */
856
+ headerTemplate;
857
+ /** Custom template for card body. Context: { event } */
858
+ bodyTemplate;
859
+ /** Custom template for card actions. Context: { event, actions } */
860
+ actionsTemplate;
861
+ /** Custom template for segment header. Context: { segment } */
862
+ segmentHeaderTemplate;
863
+ /** Custom template for empty state. */
864
+ emptyTemplate;
865
+ /** Custom template for loading state. */
866
+ loadingTemplate;
867
+ // ============================================================================
868
+ // VIEW CHILDREN
869
+ // ============================================================================
870
+ scrollContainer;
871
+ // ============================================================================
872
+ // PUBLIC PROPERTIES
873
+ // ============================================================================
874
+ /** Current time segments with events. */
875
+ segments = [];
876
+ /** All flattened events (for non-segmented display). */
877
+ allEvents = [];
878
+ /** Virtual scroll state. */
879
+ scrollState = { ...DEFAULT_VIRTUAL_SCROLL_STATE };
880
+ /** Whether initial load is complete. */
881
+ isInitialized = false;
882
+ /** Whether currently loading data. */
883
+ isLoading = false;
884
+ /** Index of currently focused event (for keyboard navigation). */
885
+ focusedEventIndex = -1;
886
+ // ============================================================================
887
+ // PRIVATE PROPERTIES
888
+ // ============================================================================
889
+ _initialized = false;
890
+ _hasLoaded = false;
891
+ _destroy$ = new Subject();
892
+ _scroll$ = new Subject();
893
+ _intersectionObserver;
894
+ // ============================================================================
895
+ // CONSTRUCTOR
896
+ // ============================================================================
897
+ constructor(cdr, elementRef, ngZone) {
898
+ this.cdr = cdr;
899
+ this.elementRef = elementRef;
900
+ this.ngZone = ngZone;
901
+ }
902
+ // ============================================================================
903
+ // LIFECYCLE HOOKS
904
+ // ============================================================================
905
+ ngOnInit() {
906
+ this._initialized = true;
907
+ // Set up scroll listener for virtual scrolling
908
+ this._scroll$
909
+ .pipe(debounceTime(100), takeUntil(this._destroy$))
910
+ .subscribe(() => this.onScrollCheck());
911
+ }
912
+ ngAfterViewInit() {
913
+ if (this.allowLoad && !this._hasLoaded) {
914
+ this.refresh();
73
915
  }
74
- return group;
916
+ // Set up intersection observer for virtual scroll trigger
917
+ this.setupIntersectionObserver();
75
918
  }
76
- }
77
- /**
78
- * Displays data on a timeline UI so that information can see a chronolgoical display of the provided data.
79
- */
80
- export class TimelineComponent {
81
- _groups = [];
82
- DisplayOrientation = 'vertical';
919
+ ngOnDestroy() {
920
+ this._destroy$.next();
921
+ this._destroy$.complete();
922
+ if (this._intersectionObserver) {
923
+ this._intersectionObserver.disconnect();
924
+ }
925
+ }
926
+ // ============================================================================
927
+ // PUBLIC METHODS
928
+ // ============================================================================
83
929
  /**
84
- * Provide an array of one or more TimelineGroup objects to display the data in the timeline. Each group will be displayed in a different color and icon.
930
+ * Refreshes all data from the configured groups.
931
+ * Clears existing data and reloads from sources.
85
932
  */
86
- get Groups() {
87
- return this._groups;
933
+ async refresh() {
934
+ const startTime = Date.now();
935
+ // Emit before event
936
+ const beforeArgs = {
937
+ cancel: false,
938
+ groups: this._groups,
939
+ isIncremental: false
940
+ };
941
+ this.beforeLoad.emit(beforeArgs);
942
+ if (beforeArgs.cancel) {
943
+ return;
944
+ }
945
+ this.isLoading = true;
946
+ this.cdr.markForCheck();
947
+ try {
948
+ // Clear existing data
949
+ this.allEvents = [];
950
+ this.segments = [];
951
+ this.scrollState = { ...DEFAULT_VIRTUAL_SCROLL_STATE };
952
+ // Load data from all groups
953
+ await this.loadAllGroups();
954
+ // Group into segments
955
+ this.buildSegments();
956
+ this._hasLoaded = true;
957
+ this.isInitialized = true;
958
+ // Emit after event
959
+ const afterArgs = {
960
+ success: true,
961
+ eventsLoaded: this.allEvents.length,
962
+ totalEvents: this.allEvents.length,
963
+ loadTimeMs: Date.now() - startTime,
964
+ hasMore: this.scrollState.hasMore
965
+ };
966
+ this.afterLoad.emit(afterArgs);
967
+ }
968
+ catch (error) {
969
+ console.error('Timeline: Error loading data', error);
970
+ const afterArgs = {
971
+ success: false,
972
+ errorMessage: error instanceof Error ? error.message : 'Unknown error',
973
+ eventsLoaded: 0,
974
+ totalEvents: 0,
975
+ loadTimeMs: Date.now() - startTime,
976
+ hasMore: false
977
+ };
978
+ this.afterLoad.emit(afterArgs);
979
+ }
980
+ finally {
981
+ this.isLoading = false;
982
+ this.cdr.markForCheck();
983
+ }
88
984
  }
89
- set Groups(value) {
90
- this._groups = value;
91
- if (this.AllowLoad)
92
- this.Refresh();
985
+ /**
986
+ * Loads more events (for virtual scrolling).
987
+ */
988
+ async loadMore() {
989
+ if (!this.virtualScroll.enabled || this.scrollState.isLoading || !this.scrollState.hasMore) {
990
+ return;
991
+ }
992
+ const startTime = Date.now();
993
+ // Emit before event
994
+ const beforeArgs = {
995
+ cancel: false,
996
+ groups: this._groups,
997
+ isIncremental: true,
998
+ offset: this.scrollState.loadedCount,
999
+ batchSize: this.virtualScroll.batchSize
1000
+ };
1001
+ this.beforeLoad.emit(beforeArgs);
1002
+ if (beforeArgs.cancel) {
1003
+ return;
1004
+ }
1005
+ this.scrollState.isLoading = true;
1006
+ this.cdr.markForCheck();
1007
+ try {
1008
+ const previousCount = this.allEvents.length;
1009
+ // Load next batch from all groups
1010
+ await this.loadNextBatch();
1011
+ // Rebuild segments with new data
1012
+ this.buildSegments();
1013
+ const newCount = this.allEvents.length - previousCount;
1014
+ // Emit after event
1015
+ const afterArgs = {
1016
+ success: true,
1017
+ eventsLoaded: newCount,
1018
+ totalEvents: this.allEvents.length,
1019
+ loadTimeMs: Date.now() - startTime,
1020
+ hasMore: this.scrollState.hasMore
1021
+ };
1022
+ this.afterLoad.emit(afterArgs);
1023
+ }
1024
+ catch (error) {
1025
+ console.error('Timeline: Error loading more data', error);
1026
+ }
1027
+ finally {
1028
+ this.scrollState.isLoading = false;
1029
+ this.cdr.markForCheck();
1030
+ }
93
1031
  }
94
- _deferLoadCount = 0;
95
- _allowLoad = true;
96
1032
  /**
97
- * Set this property to false to prevent the timeline from loading the data. This is useful when you want to load the data yourself and then set this property to true to display the data.
1033
+ * Expands all event cards.
98
1034
  */
99
- get AllowLoad() {
100
- return this._allowLoad;
1035
+ expandAllEvents() {
1036
+ for (const event of this.allEvents) {
1037
+ if (!event.isExpanded) {
1038
+ this.setEventExpanded(event, true);
1039
+ }
1040
+ }
1041
+ this.cdr.markForCheck();
101
1042
  }
102
- set AllowLoad(value) {
103
- this._allowLoad = value;
104
- if (value === true && this._deferLoadCount === 0) {
105
- this._deferLoadCount++; // only do this one time
106
- this.Refresh();
1043
+ /**
1044
+ * Collapses all event cards.
1045
+ */
1046
+ collapseAllEvents() {
1047
+ for (const event of this.allEvents) {
1048
+ if (event.isExpanded) {
1049
+ this.setEventExpanded(event, false);
1050
+ }
107
1051
  }
1052
+ this.cdr.markForCheck();
108
1053
  }
109
- /*
110
- * events is the array of timeline events that gets updated on each call of LoadSingleGroup.
111
- * timelineGroupEvents is the array of total timeline events that will get called used by the timeline component.
112
- */
113
- events = [];
114
- async ngAfterViewInit() {
115
- if (this.AllowLoad)
116
- await this.Refresh();
1054
+ /**
1055
+ * Expands all time segments.
1056
+ */
1057
+ expandAllSegments() {
1058
+ for (const segment of this.segments) {
1059
+ if (!segment.isExpanded) {
1060
+ this.setSegmentExpanded(segment, true);
1061
+ }
1062
+ }
1063
+ this.cdr.markForCheck();
117
1064
  }
118
1065
  /**
119
- * This method refreshes the timeline with the data from the provided parameters.
1066
+ * Collapses all time segments.
120
1067
  */
121
- async Refresh() {
122
- this.events.splice(0, this.events.length); // clear out what we have
123
- if (this.Groups && this.Groups.length > 0) {
124
- for (const g of this.Groups) {
125
- await this.LoadSingleGroup(g);
1068
+ collapseAllSegments() {
1069
+ for (const segment of this.segments) {
1070
+ if (segment.isExpanded) {
1071
+ this.setSegmentExpanded(segment, false);
126
1072
  }
127
1073
  }
1074
+ this.cdr.markForCheck();
1075
+ }
1076
+ /**
1077
+ * Expands a specific event by ID.
1078
+ */
1079
+ expandEvent(eventId) {
1080
+ const event = this.getEvent(eventId);
1081
+ if (event && !event.isExpanded) {
1082
+ this.setEventExpanded(event, true);
1083
+ this.cdr.markForCheck();
1084
+ }
128
1085
  }
129
1086
  /**
130
- * This method loads the data for a single group and adds it to the timelineGroupEvents array.
131
- * @param group
1087
+ * Collapses a specific event by ID.
132
1088
  */
133
- async LoadSingleGroup(group) {
134
- // load up the events for the specified group into the events array
135
- let newItems = [];
136
- switch (group.DataSourceType) {
137
- case 'array':
138
- // use the provided array
139
- if (!group.EntityObjects)
140
- throw new Error("No EntityObjects provided for group");
141
- newItems = this.mapEntityObjectsToEvents(group, group.EntityObjects);
1089
+ collapseEvent(eventId) {
1090
+ const event = this.getEvent(eventId);
1091
+ if (event && event.isExpanded) {
1092
+ this.setEventExpanded(event, false);
1093
+ this.cdr.markForCheck();
1094
+ }
1095
+ }
1096
+ /**
1097
+ * Scrolls to a specific event.
1098
+ */
1099
+ scrollToEvent(eventId, behavior = 'smooth') {
1100
+ const element = this.elementRef.nativeElement.querySelector(`[data-event-id="${eventId}"]`);
1101
+ if (element) {
1102
+ element.scrollIntoView({ behavior, block: 'center' });
1103
+ }
1104
+ }
1105
+ /**
1106
+ * Scrolls to a specific date.
1107
+ */
1108
+ scrollToDate(date, behavior = 'smooth') {
1109
+ // Find the segment containing this date
1110
+ const segment = this.segments.find(s => date >= s.startDate && date < s.endDate);
1111
+ if (segment) {
1112
+ const element = this.elementRef.nativeElement.querySelector(`[data-segment-label="${segment.label}"]`);
1113
+ if (element) {
1114
+ element.scrollIntoView({ behavior, block: 'start' });
1115
+ }
1116
+ }
1117
+ }
1118
+ /**
1119
+ * Gets an event by ID.
1120
+ */
1121
+ getEvent(eventId) {
1122
+ return this.allEvents.find(e => e.id === eventId);
1123
+ }
1124
+ /**
1125
+ * Gets all events (flattened).
1126
+ */
1127
+ getAllEvents() {
1128
+ return [...this.allEvents];
1129
+ }
1130
+ // ============================================================================
1131
+ // EVENT HANDLERS (Template bindings)
1132
+ // ============================================================================
1133
+ /**
1134
+ * Handles click on an event card.
1135
+ */
1136
+ onEventClick(event, index, domEvent) {
1137
+ const group = this._groups[event.groupIndex];
1138
+ // Emit before event
1139
+ const beforeArgs = {
1140
+ cancel: false,
1141
+ event,
1142
+ group,
1143
+ index,
1144
+ domEvent
1145
+ };
1146
+ this.beforeEventClick.emit(beforeArgs);
1147
+ if (beforeArgs.cancel) {
1148
+ return;
1149
+ }
1150
+ // Default behavior: toggle expand/collapse if collapsible
1151
+ const cardConfig = this.getEffectiveCardConfig(event);
1152
+ if (cardConfig.collapsible) {
1153
+ this.toggleEventExpanded(event, index, domEvent);
1154
+ }
1155
+ // Emit after event
1156
+ const afterArgs = {
1157
+ success: true,
1158
+ event,
1159
+ group,
1160
+ index,
1161
+ domEvent
1162
+ };
1163
+ this.afterEventClick.emit(afterArgs);
1164
+ }
1165
+ /**
1166
+ * Handles expand/collapse toggle on an event.
1167
+ */
1168
+ onToggleExpand(event, index, domEvent) {
1169
+ domEvent.stopPropagation();
1170
+ this.toggleEventExpanded(event, index, domEvent);
1171
+ }
1172
+ /**
1173
+ * Handles mouse enter on an event card.
1174
+ */
1175
+ onEventMouseEnter(event, index, domEvent) {
1176
+ const group = this._groups[event.groupIndex];
1177
+ const beforeArgs = {
1178
+ cancel: false,
1179
+ event,
1180
+ group,
1181
+ index,
1182
+ domEvent,
1183
+ hoverState: 'enter'
1184
+ };
1185
+ this.beforeEventHover.emit(beforeArgs);
1186
+ if (!beforeArgs.cancel) {
1187
+ const afterArgs = {
1188
+ success: true,
1189
+ event,
1190
+ group,
1191
+ index,
1192
+ domEvent,
1193
+ hoverState: 'enter'
1194
+ };
1195
+ this.afterEventHover.emit(afterArgs);
1196
+ }
1197
+ }
1198
+ /**
1199
+ * Handles mouse leave on an event card.
1200
+ */
1201
+ onEventMouseLeave(event, index, domEvent) {
1202
+ const group = this._groups[event.groupIndex];
1203
+ const beforeArgs = {
1204
+ cancel: false,
1205
+ event,
1206
+ group,
1207
+ index,
1208
+ domEvent,
1209
+ hoverState: 'leave'
1210
+ };
1211
+ this.beforeEventHover.emit(beforeArgs);
1212
+ if (!beforeArgs.cancel) {
1213
+ const afterArgs = {
1214
+ success: true,
1215
+ event,
1216
+ group,
1217
+ index,
1218
+ domEvent,
1219
+ hoverState: 'leave'
1220
+ };
1221
+ this.afterEventHover.emit(afterArgs);
1222
+ }
1223
+ }
1224
+ /**
1225
+ * Handles action button click.
1226
+ */
1227
+ onActionClick(event, action, index, domEvent) {
1228
+ domEvent.stopPropagation();
1229
+ if (action.disabled) {
1230
+ return;
1231
+ }
1232
+ const group = this._groups[event.groupIndex];
1233
+ const beforeArgs = {
1234
+ cancel: false,
1235
+ event,
1236
+ group,
1237
+ index,
1238
+ domEvent,
1239
+ action
1240
+ };
1241
+ this.beforeActionClick.emit(beforeArgs);
1242
+ if (!beforeArgs.cancel) {
1243
+ const afterArgs = {
1244
+ success: true,
1245
+ event,
1246
+ group,
1247
+ index,
1248
+ domEvent,
1249
+ action
1250
+ };
1251
+ this.afterActionClick.emit(afterArgs);
1252
+ }
1253
+ }
1254
+ /**
1255
+ * Handles segment header click.
1256
+ */
1257
+ onSegmentClick(segment) {
1258
+ if (!this.segmentsCollapsible) {
1259
+ return;
1260
+ }
1261
+ if (segment.isExpanded) {
1262
+ this.collapseSegment(segment);
1263
+ }
1264
+ else {
1265
+ this.expandSegment(segment);
1266
+ }
1267
+ }
1268
+ /**
1269
+ * Handles scroll events for virtual scrolling.
1270
+ */
1271
+ onScroll(event) {
1272
+ this._scroll$.next(event);
1273
+ }
1274
+ /**
1275
+ * Handles keyboard navigation.
1276
+ */
1277
+ onKeyDown(event) {
1278
+ if (!this.enableKeyboardNavigation) {
1279
+ return;
1280
+ }
1281
+ switch (event.key) {
1282
+ case 'ArrowDown':
1283
+ case 'ArrowRight':
1284
+ event.preventDefault();
1285
+ this.focusNextEvent();
142
1286
  break;
143
- case 'entity':
144
- // use run view
145
- const rv = new RunView();
146
- const result = await rv.RunView({
147
- EntityName: group.EntityName,
148
- ExtraFilter: group.Filter,
149
- ResultType: 'entity_object'
150
- });
151
- if (result && result.Success)
152
- newItems = this.mapEntityObjectsToEvents(group, result.Results);
1287
+ case 'ArrowUp':
1288
+ case 'ArrowLeft':
1289
+ event.preventDefault();
1290
+ this.focusPreviousEvent();
1291
+ break;
1292
+ case 'Enter':
1293
+ case ' ':
1294
+ event.preventDefault();
1295
+ this.activateFocusedEvent();
1296
+ break;
1297
+ case 'Escape':
1298
+ event.preventDefault();
1299
+ this.collapseFocusedEvent();
1300
+ break;
1301
+ case 'Home':
1302
+ event.preventDefault();
1303
+ this.focusFirstEvent();
153
1304
  break;
1305
+ case 'End':
1306
+ event.preventDefault();
1307
+ this.focusLastEvent();
1308
+ break;
1309
+ }
1310
+ }
1311
+ // ============================================================================
1312
+ // TEMPLATE HELPERS
1313
+ // ============================================================================
1314
+ /**
1315
+ * Gets the effective card config for an event.
1316
+ */
1317
+ getEffectiveCardConfig(event) {
1318
+ const group = this._groups[event.groupIndex];
1319
+ return {
1320
+ ...this.defaultCardConfig,
1321
+ ...group?.CardConfig,
1322
+ ...this.mapEventConfigToCardConfig(event.config)
1323
+ };
1324
+ }
1325
+ /**
1326
+ * Gets the color for a group/event.
1327
+ */
1328
+ getColor(event) {
1329
+ if (event.config?.color) {
1330
+ return event.config.color;
154
1331
  }
155
- this.events.push(...newItems);
1332
+ const group = this._groups[event.groupIndex];
1333
+ if (group?.DisplayColorMode === 'manual' && group.DisplayColor) {
1334
+ return group.DisplayColor;
1335
+ }
1336
+ return AUTO_COLORS[event.groupIndex % AUTO_COLORS.length];
156
1337
  }
157
- mapEntityObjectsToEvents(group, entityObjects) {
158
- const ret = entityObjects.map(e => {
159
- let date = new Date(e.Get(group.DateFieldName));
160
- let title = e.Get(group.TitleFieldName);
161
- let summary = "";
162
- if (group.SummaryMode == 'field') {
163
- summary = e.Get(group.TitleFieldName);
1338
+ /**
1339
+ * Gets the icon for a group/event.
1340
+ */
1341
+ getIcon(event) {
1342
+ if (event.config?.icon) {
1343
+ return event.config.icon;
1344
+ }
1345
+ const group = this._groups[event.groupIndex];
1346
+ if (group?.DisplayIconMode === 'custom' && group.DisplayIcon) {
1347
+ return group.DisplayIcon;
1348
+ }
1349
+ return DEFAULT_ICONS[event.groupIndex % DEFAULT_ICONS.length];
1350
+ }
1351
+ /**
1352
+ * Gets the actions for an event.
1353
+ */
1354
+ getActions(event) {
1355
+ if (event.config?.actions) {
1356
+ return event.config.actions;
1357
+ }
1358
+ const cardConfig = this.getEffectiveCardConfig(event);
1359
+ return cardConfig.actions || [];
1360
+ }
1361
+ /**
1362
+ * Formats a date for display.
1363
+ */
1364
+ formatDate(date, format) {
1365
+ const fmt = format || this.defaultCardConfig.dateFormat || 'MMM d, yyyy';
1366
+ return this.formatDateInternal(date, fmt);
1367
+ }
1368
+ /**
1369
+ * Gets the value of a display field from an event.
1370
+ */
1371
+ getFieldValue(event, field) {
1372
+ const value = getFieldValue(event.entity, field.fieldName);
1373
+ if (field.formatter) {
1374
+ return field.formatter(value);
1375
+ }
1376
+ if (value == null) {
1377
+ return '';
1378
+ }
1379
+ if (field.format && value instanceof Date) {
1380
+ return this.formatDateInternal(value, field.format);
1381
+ }
1382
+ return String(value);
1383
+ }
1384
+ /**
1385
+ * Track by function for ngFor.
1386
+ */
1387
+ trackByEventId(_index, event) {
1388
+ return event.id;
1389
+ }
1390
+ /**
1391
+ * Track by function for segments.
1392
+ */
1393
+ trackBySegmentLabel(_index, segment) {
1394
+ return segment.label;
1395
+ }
1396
+ /**
1397
+ * Gets the global index of an event in the allEvents array.
1398
+ */
1399
+ getGlobalIndex(event) {
1400
+ return this.allEvents.indexOf(event);
1401
+ }
1402
+ // ============================================================================
1403
+ // PRIVATE METHODS - DATA LOADING
1404
+ // ============================================================================
1405
+ /**
1406
+ * Loads data from all groups.
1407
+ */
1408
+ async loadAllGroups() {
1409
+ for (let groupIndex = 0; groupIndex < this._groups.length; groupIndex++) {
1410
+ const group = this._groups[groupIndex];
1411
+ await this.loadGroup(group, groupIndex);
1412
+ }
1413
+ // Sort all events by date
1414
+ this.sortEvents();
1415
+ // Update scroll state
1416
+ this.scrollState.loadedCount = this.allEvents.length;
1417
+ this.scrollState.hasMore = false; // TODO: Implement proper pagination detection
1418
+ }
1419
+ /**
1420
+ * Loads data from a single group.
1421
+ */
1422
+ async loadGroup(group, groupIndex) {
1423
+ let records = [];
1424
+ if (group.DataSourceType === 'array') {
1425
+ records = group.EntityObjects || [];
1426
+ }
1427
+ else if (group.DataSourceType === 'entity' && group.EntityName) {
1428
+ records = await this.loadFromEntity(group);
1429
+ }
1430
+ // Convert records to timeline events
1431
+ for (const record of records) {
1432
+ const event = this.createTimelineEvent(record, group, groupIndex);
1433
+ this.allEvents.push(event);
1434
+ }
1435
+ }
1436
+ /**
1437
+ * Loads data from MemberJunction entity.
1438
+ */
1439
+ async loadFromEntity(group) {
1440
+ try {
1441
+ const { RunView } = await import('@memberjunction/core');
1442
+ const rv = new RunView();
1443
+ const result = await rv.RunView({
1444
+ EntityName: group.EntityName,
1445
+ ExtraFilter: group.Filter,
1446
+ OrderBy: group.OrderBy,
1447
+ MaxRows: group.MaxRecords,
1448
+ ResultType: 'entity_object'
1449
+ });
1450
+ if (result?.Success) {
1451
+ return result.Results;
164
1452
  }
165
- else if (group.SummaryMode == 'custom') {
166
- summary = group.SummaryFunction ? group.SummaryFunction(e) : "";
1453
+ }
1454
+ catch (error) {
1455
+ console.warn('Timeline: Could not load from entity. Is @memberjunction/core available?', error);
1456
+ }
1457
+ return [];
1458
+ }
1459
+ /**
1460
+ * Loads the next batch for virtual scrolling.
1461
+ */
1462
+ async loadNextBatch() {
1463
+ // TODO: Implement proper batch loading with offset/limit
1464
+ // For now, this is a placeholder
1465
+ }
1466
+ /**
1467
+ * Creates a timeline event from a source record.
1468
+ */
1469
+ createTimelineEvent(record, group, groupIndex) {
1470
+ const cardConfig = group.getEffectiveCardConfig();
1471
+ return {
1472
+ id: group.getId(record),
1473
+ entity: record,
1474
+ title: group.getTitle(record),
1475
+ date: group.getDate(record),
1476
+ subtitle: group.getSubtitle(record),
1477
+ description: group.getDescription(record),
1478
+ imageUrl: group.getImageUrl(record),
1479
+ config: group.getEventConfig(record),
1480
+ groupIndex,
1481
+ isExpanded: cardConfig.defaultExpanded ?? false
1482
+ };
1483
+ }
1484
+ /**
1485
+ * Sorts events by date according to sortOrder.
1486
+ */
1487
+ sortEvents() {
1488
+ this.allEvents.sort((a, b) => {
1489
+ const diff = a.date.getTime() - b.date.getTime();
1490
+ return this.sortOrder === 'asc' ? diff : -diff;
1491
+ });
1492
+ }
1493
+ // ============================================================================
1494
+ // PRIVATE METHODS - SEGMENTATION
1495
+ // ============================================================================
1496
+ /**
1497
+ * Builds time segments from events.
1498
+ */
1499
+ buildSegments() {
1500
+ if (this.segmentGrouping === 'none') {
1501
+ this.segments = [];
1502
+ return;
1503
+ }
1504
+ const segmentMap = new Map();
1505
+ for (const event of this.allEvents) {
1506
+ const { label, startDate, endDate } = this.getSegmentInfo(event.date);
1507
+ if (!segmentMap.has(label)) {
1508
+ segmentMap.set(label, {
1509
+ label,
1510
+ startDate,
1511
+ endDate,
1512
+ events: [],
1513
+ isExpanded: this.segmentsDefaultExpanded,
1514
+ eventCount: 0
1515
+ });
167
1516
  }
168
- return {
169
- description: summary,
170
- date: date,
171
- title: title,
172
- subtitle: date.toDateString(),
173
- images: [],
174
- actions: [],
175
- };
1517
+ const segment = segmentMap.get(label);
1518
+ segment.events.push(event);
1519
+ segment.eventCount++;
1520
+ }
1521
+ // Convert to array and sort
1522
+ this.segments = Array.from(segmentMap.values());
1523
+ this.segments.sort((a, b) => {
1524
+ const diff = a.startDate.getTime() - b.startDate.getTime();
1525
+ return this.sortOrder === 'asc' ? diff : -diff;
176
1526
  });
177
- return ret;
178
1527
  }
179
- static ɵfac = function TimelineComponent_Factory(t) { return new (t || TimelineComponent)(); };
180
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TimelineComponent, selectors: [["mj-timeline"]], inputs: { DisplayOrientation: "DisplayOrientation", Groups: "Groups", AllowLoad: "AllowLoad" }, decls: 2, vars: 4, consts: [[1, "wrapper"], [3, "events", "orientation", "collapsibleEvents", "alterMode"]], template: function TimelineComponent_Template(rf, ctx) { if (rf & 1) {
181
- i0.ɵɵelementStart(0, "div", 0);
182
- i0.ɵɵelement(1, "kendo-timeline", 1);
1528
+ /**
1529
+ * Gets segment information for a date.
1530
+ */
1531
+ getSegmentInfo(date) {
1532
+ const year = date.getFullYear();
1533
+ const month = date.getMonth();
1534
+ const day = date.getDate();
1535
+ switch (this.segmentGrouping) {
1536
+ case 'day':
1537
+ return {
1538
+ label: this.formatDateInternal(date, 'MMMM d, yyyy'),
1539
+ startDate: new Date(year, month, day),
1540
+ endDate: new Date(year, month, day + 1)
1541
+ };
1542
+ case 'week':
1543
+ const weekStart = new Date(date);
1544
+ weekStart.setDate(day - date.getDay());
1545
+ const weekEnd = new Date(weekStart);
1546
+ weekEnd.setDate(weekStart.getDate() + 7);
1547
+ return {
1548
+ label: `Week of ${this.formatDateInternal(weekStart, 'MMM d, yyyy')}`,
1549
+ startDate: weekStart,
1550
+ endDate: weekEnd
1551
+ };
1552
+ case 'month':
1553
+ return {
1554
+ label: this.formatDateInternal(new Date(year, month, 1), 'MMMM yyyy'),
1555
+ startDate: new Date(year, month, 1),
1556
+ endDate: new Date(year, month + 1, 1)
1557
+ };
1558
+ case 'quarter':
1559
+ const quarter = Math.floor(month / 3);
1560
+ return {
1561
+ label: `Q${quarter + 1} ${year}`,
1562
+ startDate: new Date(year, quarter * 3, 1),
1563
+ endDate: new Date(year, (quarter + 1) * 3, 1)
1564
+ };
1565
+ case 'year':
1566
+ return {
1567
+ label: String(year),
1568
+ startDate: new Date(year, 0, 1),
1569
+ endDate: new Date(year + 1, 0, 1)
1570
+ };
1571
+ default:
1572
+ return {
1573
+ label: this.formatDateInternal(date, 'MMMM yyyy'),
1574
+ startDate: new Date(year, month, 1),
1575
+ endDate: new Date(year, month + 1, 1)
1576
+ };
1577
+ }
1578
+ }
1579
+ // ============================================================================
1580
+ // PRIVATE METHODS - EXPAND/COLLAPSE
1581
+ // ============================================================================
1582
+ /**
1583
+ * Toggles event expanded state.
1584
+ */
1585
+ toggleEventExpanded(event, index, domEvent) {
1586
+ if (event.isExpanded) {
1587
+ this.collapseEventInternal(event, index, domEvent);
1588
+ }
1589
+ else {
1590
+ this.expandEventInternal(event, index, domEvent);
1591
+ }
1592
+ }
1593
+ /**
1594
+ * Sets event expanded state.
1595
+ */
1596
+ setEventExpanded(event, expanded) {
1597
+ event.isExpanded = expanded;
1598
+ }
1599
+ /**
1600
+ * Expands an event with events.
1601
+ */
1602
+ expandEventInternal(event, index, domEvent) {
1603
+ const group = this._groups[event.groupIndex];
1604
+ const beforeArgs = {
1605
+ cancel: false,
1606
+ event,
1607
+ group,
1608
+ index,
1609
+ domEvent
1610
+ };
1611
+ this.beforeEventExpand.emit(beforeArgs);
1612
+ if (beforeArgs.cancel) {
1613
+ return;
1614
+ }
1615
+ event.isExpanded = true;
1616
+ this.cdr.markForCheck();
1617
+ const afterArgs = {
1618
+ success: true,
1619
+ event,
1620
+ group,
1621
+ index,
1622
+ domEvent
1623
+ };
1624
+ this.afterEventExpand.emit(afterArgs);
1625
+ }
1626
+ /**
1627
+ * Collapses an event with events.
1628
+ */
1629
+ collapseEventInternal(event, index, domEvent) {
1630
+ const group = this._groups[event.groupIndex];
1631
+ const beforeArgs = {
1632
+ cancel: false,
1633
+ event,
1634
+ group,
1635
+ index,
1636
+ domEvent
1637
+ };
1638
+ this.beforeEventCollapse.emit(beforeArgs);
1639
+ if (beforeArgs.cancel) {
1640
+ return;
1641
+ }
1642
+ event.isExpanded = false;
1643
+ this.cdr.markForCheck();
1644
+ const afterArgs = {
1645
+ success: true,
1646
+ event,
1647
+ group,
1648
+ index,
1649
+ domEvent
1650
+ };
1651
+ this.afterEventCollapse.emit(afterArgs);
1652
+ }
1653
+ /**
1654
+ * Expands a segment.
1655
+ */
1656
+ expandSegment(segment) {
1657
+ const beforeArgs = {
1658
+ cancel: false,
1659
+ segment,
1660
+ label: segment.label,
1661
+ startDate: segment.startDate,
1662
+ endDate: segment.endDate,
1663
+ eventCount: segment.eventCount
1664
+ };
1665
+ this.beforeSegmentExpand.emit(beforeArgs);
1666
+ if (beforeArgs.cancel) {
1667
+ return;
1668
+ }
1669
+ segment.isExpanded = true;
1670
+ this.cdr.markForCheck();
1671
+ const afterArgs = {
1672
+ success: true,
1673
+ segment,
1674
+ label: segment.label,
1675
+ startDate: segment.startDate,
1676
+ endDate: segment.endDate,
1677
+ eventCount: segment.eventCount
1678
+ };
1679
+ this.afterSegmentExpand.emit(afterArgs);
1680
+ }
1681
+ /**
1682
+ * Collapses a segment.
1683
+ */
1684
+ collapseSegment(segment) {
1685
+ const beforeArgs = {
1686
+ cancel: false,
1687
+ segment,
1688
+ label: segment.label,
1689
+ startDate: segment.startDate,
1690
+ endDate: segment.endDate,
1691
+ eventCount: segment.eventCount
1692
+ };
1693
+ this.beforeSegmentCollapse.emit(beforeArgs);
1694
+ if (beforeArgs.cancel) {
1695
+ return;
1696
+ }
1697
+ segment.isExpanded = false;
1698
+ this.cdr.markForCheck();
1699
+ const afterArgs = {
1700
+ success: true,
1701
+ segment,
1702
+ label: segment.label,
1703
+ startDate: segment.startDate,
1704
+ endDate: segment.endDate,
1705
+ eventCount: segment.eventCount
1706
+ };
1707
+ this.afterSegmentCollapse.emit(afterArgs);
1708
+ }
1709
+ /**
1710
+ * Sets segment expanded state without events.
1711
+ */
1712
+ setSegmentExpanded(segment, expanded) {
1713
+ segment.isExpanded = expanded;
1714
+ }
1715
+ // ============================================================================
1716
+ // PRIVATE METHODS - KEYBOARD NAVIGATION
1717
+ // ============================================================================
1718
+ focusNextEvent() {
1719
+ if (this.allEvents.length === 0)
1720
+ return;
1721
+ this.focusedEventIndex = Math.min(this.focusedEventIndex + 1, this.allEvents.length - 1);
1722
+ this.scrollToFocusedEvent();
1723
+ }
1724
+ focusPreviousEvent() {
1725
+ if (this.allEvents.length === 0)
1726
+ return;
1727
+ this.focusedEventIndex = Math.max(this.focusedEventIndex - 1, 0);
1728
+ this.scrollToFocusedEvent();
1729
+ }
1730
+ focusFirstEvent() {
1731
+ if (this.allEvents.length === 0)
1732
+ return;
1733
+ this.focusedEventIndex = 0;
1734
+ this.scrollToFocusedEvent();
1735
+ }
1736
+ focusLastEvent() {
1737
+ if (this.allEvents.length === 0)
1738
+ return;
1739
+ this.focusedEventIndex = this.allEvents.length - 1;
1740
+ this.scrollToFocusedEvent();
1741
+ }
1742
+ activateFocusedEvent() {
1743
+ if (this.focusedEventIndex >= 0 && this.focusedEventIndex < this.allEvents.length) {
1744
+ const event = this.allEvents[this.focusedEventIndex];
1745
+ this.toggleEventExpanded(event, this.focusedEventIndex);
1746
+ }
1747
+ }
1748
+ collapseFocusedEvent() {
1749
+ if (this.focusedEventIndex >= 0 && this.focusedEventIndex < this.allEvents.length) {
1750
+ const event = this.allEvents[this.focusedEventIndex];
1751
+ if (event.isExpanded) {
1752
+ this.collapseEventInternal(event, this.focusedEventIndex);
1753
+ }
1754
+ }
1755
+ }
1756
+ scrollToFocusedEvent() {
1757
+ if (this.focusedEventIndex >= 0 && this.focusedEventIndex < this.allEvents.length) {
1758
+ const event = this.allEvents[this.focusedEventIndex];
1759
+ this.scrollToEvent(event.id, 'smooth');
1760
+ }
1761
+ this.cdr.markForCheck();
1762
+ }
1763
+ // ============================================================================
1764
+ // PRIVATE METHODS - VIRTUAL SCROLLING
1765
+ // ============================================================================
1766
+ setupIntersectionObserver() {
1767
+ if (!this.virtualScroll.enabled)
1768
+ return;
1769
+ // Use requestAnimationFrame to ensure DOM is ready
1770
+ requestAnimationFrame(() => {
1771
+ const sentinel = this.elementRef.nativeElement.querySelector('.mj-timeline-scroll-sentinel');
1772
+ if (!sentinel)
1773
+ return;
1774
+ this._intersectionObserver = new IntersectionObserver((entries) => {
1775
+ const entry = entries[0];
1776
+ if (entry?.isIntersecting && !this.scrollState.isLoading && this.scrollState.hasMore) {
1777
+ this.ngZone.run(() => this.loadMore());
1778
+ }
1779
+ }, {
1780
+ root: this.scrollContainer?.nativeElement,
1781
+ threshold: 0,
1782
+ rootMargin: `${this.virtualScroll.loadThreshold}px`
1783
+ });
1784
+ this._intersectionObserver.observe(sentinel);
1785
+ });
1786
+ }
1787
+ onScrollCheck() {
1788
+ if (!this.scrollContainer?.nativeElement || !this.virtualScroll.enabled) {
1789
+ return;
1790
+ }
1791
+ const el = this.scrollContainer.nativeElement;
1792
+ this.scrollState.scrollOffset = el.scrollTop;
1793
+ const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
1794
+ if (distanceFromBottom < this.virtualScroll.loadThreshold && this.scrollState.hasMore) {
1795
+ this.loadMore();
1796
+ }
1797
+ }
1798
+ // ============================================================================
1799
+ // PRIVATE METHODS - UTILITIES
1800
+ // ============================================================================
1801
+ /**
1802
+ * Maps event config to card config properties.
1803
+ */
1804
+ mapEventConfigToCardConfig(config = {}) {
1805
+ return {
1806
+ collapsible: config.collapsible,
1807
+ defaultExpanded: config.defaultExpanded,
1808
+ actions: config.actions
1809
+ };
1810
+ }
1811
+ /**
1812
+ * Simple date formatter (replaces Angular DatePipe for standalone use).
1813
+ */
1814
+ formatDateInternal(date, format) {
1815
+ const months = ['January', 'February', 'March', 'April', 'May', 'June',
1816
+ 'July', 'August', 'September', 'October', 'November', 'December'];
1817
+ const monthsShort = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
1818
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
1819
+ const year = date.getFullYear();
1820
+ const month = date.getMonth();
1821
+ const day = date.getDate();
1822
+ const hours = date.getHours();
1823
+ const minutes = date.getMinutes();
1824
+ return format
1825
+ .replace('yyyy', String(year))
1826
+ .replace('MMMM', months[month])
1827
+ .replace('MMM', monthsShort[month])
1828
+ .replace('MM', String(month + 1).padStart(2, '0'))
1829
+ .replace('dd', String(day).padStart(2, '0'))
1830
+ .replace('d', String(day))
1831
+ .replace('HH', String(hours).padStart(2, '0'))
1832
+ .replace('h', String(hours % 12 || 12))
1833
+ .replace('mm', String(minutes).padStart(2, '0'))
1834
+ .replace('a', hours >= 12 ? 'PM' : 'AM');
1835
+ }
1836
+ static ɵfac = function TimelineComponent_Factory(t) { return new (t || TimelineComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i0.NgZone)); };
1837
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TimelineComponent, selectors: [["mj-timeline"]], contentQueries: function TimelineComponent_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) {
1838
+ i0.ɵɵcontentQuery(dirIndex, _c0, 5);
1839
+ i0.ɵɵcontentQuery(dirIndex, _c1, 5);
1840
+ i0.ɵɵcontentQuery(dirIndex, _c2, 5);
1841
+ i0.ɵɵcontentQuery(dirIndex, _c3, 5);
1842
+ i0.ɵɵcontentQuery(dirIndex, _c4, 5);
1843
+ i0.ɵɵcontentQuery(dirIndex, _c5, 5);
1844
+ i0.ɵɵcontentQuery(dirIndex, _c6, 5);
1845
+ } if (rf & 2) {
1846
+ let _t;
1847
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.cardTemplate = _t.first);
1848
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.headerTemplate = _t.first);
1849
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.bodyTemplate = _t.first);
1850
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.actionsTemplate = _t.first);
1851
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.segmentHeaderTemplate = _t.first);
1852
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.emptyTemplate = _t.first);
1853
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.loadingTemplate = _t.first);
1854
+ } }, viewQuery: function TimelineComponent_Query(rf, ctx) { if (rf & 1) {
1855
+ i0.ɵɵviewQuery(_c7, 5);
1856
+ } if (rf & 2) {
1857
+ let _t;
1858
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.scrollContainer = _t.first);
1859
+ } }, inputs: { groups: "groups", allowLoad: "allowLoad", orientation: "orientation", layout: "layout", sortOrder: "sortOrder", segmentGrouping: "segmentGrouping", defaultCardConfig: "defaultCardConfig", virtualScroll: "virtualScroll", segmentsCollapsible: "segmentsCollapsible", segmentsDefaultExpanded: "segmentsDefaultExpanded", emptyMessage: "emptyMessage", emptyIcon: "emptyIcon", loadingMessage: "loadingMessage", ariaLabel: "ariaLabel", enableKeyboardNavigation: "enableKeyboardNavigation" }, outputs: { beforeEventClick: "beforeEventClick", beforeEventExpand: "beforeEventExpand", beforeEventCollapse: "beforeEventCollapse", beforeEventHover: "beforeEventHover", beforeActionClick: "beforeActionClick", beforeSegmentExpand: "beforeSegmentExpand", beforeSegmentCollapse: "beforeSegmentCollapse", beforeLoad: "beforeLoad", afterEventClick: "afterEventClick", afterEventExpand: "afterEventExpand", afterEventCollapse: "afterEventCollapse", afterEventHover: "afterEventHover", afterActionClick: "afterActionClick", afterSegmentExpand: "afterSegmentExpand", afterSegmentCollapse: "afterSegmentCollapse", afterLoad: "afterLoad" }, decls: 9, vars: 14, consts: [["scrollContainer", ""], ["eventCard", ""], ["defaultLoading", ""], ["defaultEmpty", ""], ["defaultSegmentHeader", ""], ["defaultCard", ""], ["defaultHeader", ""], ["defaultBody", ""], ["plainDescription", ""], ["defaultActions", ""], ["role", "list", "tabindex", "0", 1, "mj-timeline", 3, "keydown", "scroll"], [4, "ngIf"], ["class", "mj-timeline-scroll-sentinel", 4, "ngIf"], ["class", "mj-timeline__loading-more", 4, "ngIf"], [4, "ngIf", "ngIfElse"], [4, "ngTemplateOutlet"], [1, "mj-timeline__loading"], [1, "mj-timeline__loading-spinner"], [1, "mj-timeline__loading-text"], [1, "mj-timeline__empty"], [1, "mj-timeline__empty-icon"], [1, "mj-timeline__empty-text"], ["class", "mj-timeline__segment", 3, "mj-timeline__segment--collapsed", 4, "ngFor", "ngForOf", "ngForTrackBy"], [1, "mj-timeline__segment"], ["role", "button", 1, "mj-timeline__segment-header", 3, "click"], [1, "mj-timeline__segment-content", 3, "id"], [1, "mj-timeline__axis"], [4, "ngFor", "ngForOf", "ngForTrackBy"], [4, "ngTemplateOutlet", "ngTemplateOutletContext"], ["class", "mj-timeline__segment-toggle", 4, "ngIf"], [1, "mj-timeline__segment-label"], [1, "mj-timeline__segment-count"], [1, "mj-timeline__segment-line"], [1, "mj-timeline__segment-toggle"], [1, "mj-timeline-scroll-sentinel"], [1, "mj-timeline__loading-more"], [1, "mj-timeline__loading-spinner", "mj-timeline__loading-spinner--small"], ["role", "listitem", 1, "mj-timeline__event"], [1, "mj-timeline__marker"], [1, "mj-timeline__marker-icon"], [1, "mj-timeline__connector"], ["class", "mj-timeline__date-label", 4, "ngIf"], [1, "mj-timeline__card", 3, "click", "mouseenter", "mouseleave"], [1, "mj-timeline__date-label"], [1, "mj-timeline__card-header"], ["class", "mj-timeline__card-image mj-timeline__card-image--left", 3, "mj-timeline__card-image--small", "mj-timeline__card-image--medium", "mj-timeline__card-image--large", 4, "ngIf"], [1, "mj-timeline__card-header-content"], ["class", "mj-timeline__card-image mj-timeline__card-image--top", 4, "ngIf"], [1, "mj-timeline__card-body"], ["class", "mj-timeline__card-actions", 3, "mj-timeline__card-actions--hover-only", 4, "ngIf"], [1, "mj-timeline__card-image", "mj-timeline__card-image--left"], [3, "src", "alt"], ["class", "mj-timeline__card-icon", 3, "color", 4, "ngIf"], [1, "mj-timeline__card-titles"], [1, "mj-timeline__card-title"], ["class", "mj-timeline__card-subtitle", 4, "ngIf"], ["class", "mj-timeline__card-date", 4, "ngIf"], ["class", "mj-timeline__card-toggle", "type", "button", 3, "click", 4, "ngIf"], [1, "mj-timeline__card-icon"], [1, "mj-timeline__card-subtitle"], [1, "mj-timeline__card-date"], ["type", "button", 1, "mj-timeline__card-toggle", 3, "click"], [1, "mj-timeline__card-image", "mj-timeline__card-image--top"], ["class", "mj-timeline__card-fields mj-timeline__card-fields--summary", 4, "ngIf"], ["class", "mj-timeline__card-description", 3, "mj-timeline__card-description--clamped", "-webkit-line-clamp", 4, "ngIf"], ["class", "mj-timeline__card-fields mj-timeline__card-fields--expanded", 4, "ngIf"], [1, "mj-timeline__card-fields", "mj-timeline__card-fields--summary"], [4, "ngFor", "ngForOf"], [1, "mj-timeline__card-field"], ["class", "mj-timeline__card-field-icon", 3, "class", 4, "ngIf"], ["class", "mj-timeline__card-field-label", 4, "ngIf"], [1, "mj-timeline__card-field-value"], [1, "mj-timeline__card-field-icon"], [1, "mj-timeline__card-field-label"], [1, "mj-timeline__card-description"], [3, "innerHTML"], [1, "mj-timeline__card-fields", "mj-timeline__card-fields--expanded"], [1, "mj-timeline__card-actions"], ["class", "mj-timeline__action", "type", "button", 3, "mj-timeline__action--primary", "mj-timeline__action--secondary", "mj-timeline__action--danger", "mj-timeline__action--link", "class", "disabled", "title", "click", 4, "ngFor", "ngForOf"], ["type", "button", 1, "mj-timeline__action", 3, "click", "disabled", "title"], [3, "class", 4, "ngIf"]], template: function TimelineComponent_Template(rf, ctx) { if (rf & 1) {
1860
+ const _r1 = i0.ɵɵgetCurrentView();
1861
+ i0.ɵɵelementStart(0, "div", 10, 0);
1862
+ i0.ɵɵlistener("keydown", function TimelineComponent_Template_div_keydown_0_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onKeyDown($event)); })("scroll", function TimelineComponent_Template_div_scroll_0_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onScroll($event)); });
1863
+ i0.ɵɵtemplate(2, TimelineComponent_ng_container_2_Template, 4, 2, "ng-container", 11)(3, TimelineComponent_ng_container_3_Template, 4, 2, "ng-container", 11)(4, TimelineComponent_ng_container_4_Template, 3, 2, "ng-container", 11)(5, TimelineComponent_div_5_Template, 1, 0, "div", 12)(6, TimelineComponent_div_6_Template, 4, 1, "div", 13);
183
1864
  i0.ɵɵelementEnd();
1865
+ i0.ɵɵtemplate(7, TimelineComponent_ng_template_7_Template, 9, 27, "ng-template", null, 1, i0.ɵɵtemplateRefExtractor);
184
1866
  } if (rf & 2) {
1867
+ i0.ɵɵclassProp("mj-timeline--vertical", ctx.orientation === "vertical")("mj-timeline--horizontal", ctx.orientation === "horizontal")("mj-timeline--single", ctx.layout === "single")("mj-timeline--alternating", ctx.layout === "alternating");
1868
+ i0.ɵɵattribute("aria-label", ctx.ariaLabel);
1869
+ i0.ɵɵadvance(2);
1870
+ i0.ɵɵproperty("ngIf", ctx.isLoading && !ctx.isInitialized);
1871
+ i0.ɵɵadvance();
1872
+ i0.ɵɵproperty("ngIf", ctx.isInitialized && ctx.allEvents.length === 0 && !ctx.isLoading);
1873
+ i0.ɵɵadvance();
1874
+ i0.ɵɵproperty("ngIf", ctx.isInitialized && ctx.allEvents.length > 0);
1875
+ i0.ɵɵadvance();
1876
+ i0.ɵɵproperty("ngIf", ctx.virtualScroll.enabled && ctx.scrollState.hasMore);
185
1877
  i0.ɵɵadvance();
186
- i0.ɵɵproperty("events", ctx.events)("orientation", ctx.DisplayOrientation)("collapsibleEvents", true)("alterMode", true);
187
- } }, dependencies: [i1.TimelineComponent] });
1878
+ i0.ɵɵproperty("ngIf", ctx.scrollState.isLoading && ctx.virtualScroll.showLoadingIndicator);
1879
+ } }, dependencies: [i1.NgForOf, i1.NgIf, i1.NgTemplateOutlet], styles: ["\n\n\n\n\n\n\n\n\n// ============================================================================\n// CSS VARIABLES (Theming)\n// ============================================================================\n\n[_nghost-%COMP%] {\n // Colors\n --mj-timeline-bg: transparent;\n --mj-timeline-line-color: #e0e0e0;\n --mj-timeline-marker-bg: #1976d2;\n --mj-timeline-marker-border: #ffffff;\n --mj-timeline-card-bg: #ffffff;\n --mj-timeline-card-border: #e0e0e0;\n --mj-timeline-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n --mj-timeline-card-shadow-hover: 0 4px 16px rgba(0, 0, 0, 0.15);\n --mj-timeline-card-radius: 8px;\n --mj-timeline-text-primary: #212121;\n --mj-timeline-text-secondary: #757575;\n --mj-timeline-text-muted: #9e9e9e;\n --mj-timeline-accent: #1976d2;\n --mj-timeline-segment-bg: #f5f5f5;\n --mj-timeline-focus-ring: 0 0 0 3px rgba(25, 118, 210, 0.3);\n\n // Sizing\n --mj-timeline-line-width: 2px;\n --mj-timeline-marker-size: 14px;\n --mj-timeline-marker-icon-size: 8px;\n --mj-timeline-card-padding: 16px;\n --mj-timeline-card-max-width: 400px;\n --mj-timeline-card-min-width: 250px;\n --mj-timeline-gap: 16px;\n --mj-timeline-segment-gap: 24px;\n --mj-timeline-axis-offset: 60px;\n\n // Animation\n --mj-timeline-transition: 0.25s ease;\n\n display: block;\n width: 100%;\n}\n\n// Dark mode support\n.dark-theme[_nghost-%COMP%], .dark-theme [_nghost-%COMP%], \n[data-theme=\"dark\"][_nghost-%COMP%], [data-theme=\"dark\"] [_nghost-%COMP%] {\n --mj-timeline-line-color: #424242;\n --mj-timeline-card-bg: #1e1e1e;\n --mj-timeline-card-border: #424242;\n --mj-timeline-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n --mj-timeline-text-primary: #ffffff;\n --mj-timeline-text-secondary: #b0b0b0;\n --mj-timeline-text-muted: #757575;\n --mj-timeline-segment-bg: #2d2d2d;\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] MAIN[_ngcontent-%COMP%] CONTAINER\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline[_ngcontent-%COMP%] {\n position: relative;\n width: 100%;\n padding: var(--mj-timeline-gap);\n background: var(--mj-timeline-bg);\n outline: none;\n\n &:focus-visible {\n box-shadow: var(--mj-timeline-focus-ring);\n border-radius: 4px;\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] LOADING[_ngcontent-%COMP%] STATE\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__loading[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 48px 24px;\n gap: 16px;\n}\n\n.mj-timeline__loading-spinner[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border: 3px solid var(--mj-timeline-line-color);\n border-top-color: var(--mj-timeline-accent);\n border-radius: 50%;\n animation: _ngcontent-%COMP%_mj-timeline-spin 0.8s linear infinite;\n\n &--small {\n width: 20px;\n height: 20px;\n border-width: 2px;\n }\n}\n\n.mj-timeline__loading-text[_ngcontent-%COMP%] {\n color: var(--mj-timeline-text-secondary);\n font-size: 14px;\n}\n\n.mj-timeline__loading-more[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n gap: 8px;\n color: var(--mj-timeline-text-secondary);\n font-size: 13px;\n}\n\n@keyframes _ngcontent-%COMP%_mj-timeline-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] EMPTY[_ngcontent-%COMP%] STATE\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__empty[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 64px 24px;\n gap: 16px;\n text-align: center;\n}\n\n.mj-timeline__empty-icon[_ngcontent-%COMP%] {\n font-size: 48px;\n color: var(--mj-timeline-text-muted);\n}\n\n.mj-timeline__empty-text[_ngcontent-%COMP%] {\n color: var(--mj-timeline-text-secondary);\n font-size: 16px;\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] TIME[_ngcontent-%COMP%] SEGMENTS\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__segment[_ngcontent-%COMP%] {\n margin-bottom: var(--mj-timeline-segment-gap);\n\n &:last-child {\n margin-bottom: 0;\n }\n}\n\n.mj-timeline__segment-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n margin-bottom: var(--mj-timeline-gap);\n background: var(--mj-timeline-segment-bg);\n border-radius: 6px;\n font-weight: 600;\n color: var(--mj-timeline-text-primary);\n user-select: none;\n\n &--clickable {\n cursor: pointer;\n transition: background-color var(--mj-timeline-transition);\n\n &:hover {\n background: darken(#f5f5f5, 5%);\n }\n\n .dark-theme-shadowcsshost-no-combinator &:hover, .dark-theme -shadowcsshost-no-combinator &:hover,\n [data-theme=\"dark\"]-shadowcsshost-no-combinator &:hover , [data-theme=\"dark\"] -shadowcsshost-no-combinator &:hover {\n background: lighten(#2d2d2d, 5%);\n }\n }\n}\n\n.mj-timeline__segment-toggle[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n color: var(--mj-timeline-text-secondary);\n font-size: 12px;\n transition: transform var(--mj-timeline-transition);\n}\n\n.mj-timeline__segment-label[_ngcontent-%COMP%] {\n font-size: 15px;\n}\n\n.mj-timeline__segment-count[_ngcontent-%COMP%] {\n color: var(--mj-timeline-text-secondary);\n font-size: 13px;\n font-weight: 400;\n}\n\n.mj-timeline__segment-line[_ngcontent-%COMP%] {\n flex: 1;\n height: 1px;\n background: var(--mj-timeline-line-color);\n margin-left: 8px;\n}\n\n.mj-timeline__segment-content[_ngcontent-%COMP%] {\n overflow: hidden;\n transition: max-height var(--mj-timeline-transition), opacity var(--mj-timeline-transition);\n\n &--hidden {\n max-height: 0;\n opacity: 0;\n pointer-events: none;\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] TIMELINE[_ngcontent-%COMP%] AXIS\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__axis[_ngcontent-%COMP%] {\n position: relative;\n padding-left: var(--mj-timeline-axis-offset);\n\n // Vertical line\n &::before {\n content: '';\n position: absolute;\n left: calc(var(--mj-timeline-marker-size) / 2);\n top: 0;\n bottom: 0;\n width: var(--mj-timeline-line-width);\n background: var(--mj-timeline-line-color);\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] TIMELINE[_ngcontent-%COMP%] EVENT\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__event[_ngcontent-%COMP%] {\n position: relative;\n display: flex;\n align-items: flex-start;\n margin-bottom: var(--mj-timeline-gap);\n\n &:last-child {\n margin-bottom: 0;\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] MARKER\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__marker[_ngcontent-%COMP%] {\n position: absolute;\n left: 0;\n top: 16px;\n width: var(--mj-timeline-marker-size);\n height: var(--mj-timeline-marker-size);\n border-radius: 50%;\n background: var(--mj-timeline-marker-bg);\n border: 2px solid var(--mj-timeline-marker-border);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1;\n}\n\n.mj-timeline__marker-icon[_ngcontent-%COMP%] {\n font-size: var(--mj-timeline-marker-icon-size);\n color: var(--mj-timeline-marker-border);\n display: none; // Hidden by default, shown in larger markers\n}\n\n.mj-timeline__connector[_ngcontent-%COMP%] {\n display: none; // Hidden by default, used in horizontal mode\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] DATE[_ngcontent-%COMP%] LABEL[_ngcontent-%COMP%] (Alternating[_ngcontent-%COMP%] Layout)\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__date-label[_ngcontent-%COMP%] {\n display: none; // Hidden by default\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] EVENT[_ngcontent-%COMP%] CARD\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__card[_ngcontent-%COMP%] {\n flex: 1;\n background: var(--mj-timeline-card-bg);\n border: 1px solid var(--mj-timeline-card-border);\n border-left-width: 4px;\n border-radius: var(--mj-timeline-card-radius);\n box-shadow: var(--mj-timeline-card-shadow);\n overflow: hidden;\n transition: box-shadow var(--mj-timeline-transition), transform var(--mj-timeline-transition);\n cursor: pointer;\n max-width: var(--mj-timeline-card-max-width);\n min-width: var(--mj-timeline-card-min-width);\n\n &:hover {\n box-shadow: var(--mj-timeline-card-shadow-hover);\n transform: translateY(-1px);\n }\n\n .mj-timeline__event--focused & {\n box-shadow: var(--mj-timeline-focus-ring), var(--mj-timeline-card-shadow);\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] CARD[_ngcontent-%COMP%] HEADER\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__card-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n padding: var(--mj-timeline-card-padding);\n gap: 12px;\n}\n\n.mj-timeline__card-header-content[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0; // Enable text truncation\n}\n\n.mj-timeline__card-icon[_ngcontent-%COMP%] {\n flex-shrink: 0;\n font-size: 18px;\n line-height: 1;\n margin-top: 2px;\n}\n\n.mj-timeline__card-titles[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.mj-timeline__card-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-timeline-text-primary);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mj-timeline__card-subtitle[_ngcontent-%COMP%] {\n display: block;\n font-size: 13px;\n color: var(--mj-timeline-text-secondary);\n margin-top: 2px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mj-timeline__card-date[_ngcontent-%COMP%] {\n display: block;\n font-size: 12px;\n color: var(--mj-timeline-text-muted);\n margin-top: 4px;\n}\n\n.mj-timeline__card-toggle[_ngcontent-%COMP%] {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n color: var(--mj-timeline-text-secondary);\n border-radius: 4px;\n cursor: pointer;\n transition: background-color var(--mj-timeline-transition), color var(--mj-timeline-transition);\n\n &:hover {\n background: var(--mj-timeline-segment-bg);\n color: var(--mj-timeline-text-primary);\n }\n\n &:focus-visible {\n box-shadow: var(--mj-timeline-focus-ring);\n outline: none;\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] CARD[_ngcontent-%COMP%] IMAGE\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__card-image[_ngcontent-%COMP%] {\n overflow: hidden;\n border-radius: 4px;\n flex-shrink: 0;\n\n img {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n &--left {\n &.mj-timeline__card-image--small {\n width: 48px;\n height: 48px;\n }\n\n &.mj-timeline__card-image--medium {\n width: 80px;\n height: 80px;\n }\n\n &.mj-timeline__card-image--large {\n width: 120px;\n height: 120px;\n }\n }\n\n &--top {\n width: 100%;\n max-height: 200px;\n margin: 0 var(--mj-timeline-card-padding);\n margin-top: 0;\n border-radius: 4px;\n\n img {\n width: calc(100% - 2 * var(--mj-timeline-card-padding));\n height: auto;\n }\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] CARD[_ngcontent-%COMP%] BODY\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__card-body[_ngcontent-%COMP%] {\n padding: 0 var(--mj-timeline-card-padding);\n padding-bottom: var(--mj-timeline-card-padding);\n\n &--collapsed {\n display: none;\n }\n}\n\n.mj-timeline__card-description[_ngcontent-%COMP%] {\n font-size: 14px;\n line-height: 1.6;\n color: var(--mj-timeline-text-secondary);\n margin-bottom: 12px;\n\n &--clamped {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n\n &:last-child {\n margin-bottom: 0;\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] CARD[_ngcontent-%COMP%] FIELDS\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__card-fields[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px 16px;\n\n &--summary {\n padding-top: 8px;\n border-top: 1px solid var(--mj-timeline-line-color);\n margin-top: 8px;\n }\n\n &--expanded {\n margin-top: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--mj-timeline-line-color);\n }\n}\n\n.mj-timeline__card-field[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n}\n\n.mj-timeline__card-field-icon[_ngcontent-%COMP%] {\n color: var(--mj-timeline-text-muted);\n font-size: 12px;\n}\n\n.mj-timeline__card-field-label[_ngcontent-%COMP%] {\n color: var(--mj-timeline-text-muted);\n}\n\n.mj-timeline__card-field-value[_ngcontent-%COMP%] {\n color: var(--mj-timeline-text-secondary);\n font-weight: 500;\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] CARD[_ngcontent-%COMP%] ACTIONS\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline__card-actions[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n padding: var(--mj-timeline-card-padding);\n padding-top: 0;\n justify-content: flex-end;\n\n &--hover-only {\n opacity: 0;\n transition: opacity var(--mj-timeline-transition);\n\n .mj-timeline__card:hover &,\n .mj-timeline__card:focus-within & {\n opacity: 1;\n }\n }\n}\n\n.mj-timeline__action[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n font-size: 13px;\n font-weight: 500;\n border: 1px solid;\n border-radius: 4px;\n cursor: pointer;\n transition: all var(--mj-timeline-transition);\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n &:focus-visible {\n box-shadow: var(--mj-timeline-focus-ring);\n outline: none;\n }\n\n i {\n font-size: 12px;\n }\n\n // Variants\n &--primary {\n background: var(--mj-timeline-accent);\n border-color: var(--mj-timeline-accent);\n color: #ffffff;\n\n &:hover:not(:disabled) {\n background: darken(#1976d2, 8%);\n border-color: darken(#1976d2, 8%);\n }\n }\n\n &--secondary {\n background: transparent;\n border-color: var(--mj-timeline-card-border);\n color: var(--mj-timeline-text-primary);\n\n &:hover:not(:disabled) {\n background: var(--mj-timeline-segment-bg);\n }\n }\n\n &--danger {\n background: #f44336;\n border-color: #f44336;\n color: #ffffff;\n\n &:hover:not(:disabled) {\n background: darken(#f44336, 8%);\n border-color: darken(#f44336, 8%);\n }\n }\n\n &--link {\n background: transparent;\n border-color: transparent;\n color: var(--mj-timeline-accent);\n padding: 6px 8px;\n\n &:hover:not(:disabled) {\n background: rgba(25, 118, 210, 0.08);\n }\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] VIRTUAL[_ngcontent-%COMP%] SCROLL[_ngcontent-%COMP%] SENTINEL\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline-scroll-sentinel[_ngcontent-%COMP%] {\n height: 1px;\n width: 100%;\n visibility: hidden;\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] ALTERNATING[_ngcontent-%COMP%] LAYOUT\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline--alternating[_ngcontent-%COMP%] {\n .mj-timeline__axis {\n padding-left: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n\n &::before {\n left: 50%;\n transform: translateX(-50%);\n }\n }\n\n .mj-timeline__event {\n width: 100%;\n justify-content: flex-start;\n padding-right: calc(50% + var(--mj-timeline-gap));\n\n &--odd {\n justify-content: flex-end;\n padding-right: 0;\n padding-left: calc(50% + var(--mj-timeline-gap));\n flex-direction: row-reverse;\n }\n }\n\n .mj-timeline__marker {\n left: 50%;\n transform: translateX(-50%);\n }\n\n .mj-timeline__date-label {\n display: block;\n position: absolute;\n top: 12px;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-timeline-accent);\n white-space: nowrap;\n\n .mj-timeline__event:not(.mj-timeline__event--odd) & {\n right: calc(50% + var(--mj-timeline-gap) + var(--mj-timeline-marker-size));\n text-align: right;\n }\n\n .mj-timeline__event--odd & {\n left: calc(50% + var(--mj-timeline-gap) + var(--mj-timeline-marker-size));\n text-align: left;\n }\n }\n\n .mj-timeline__card {\n max-width: calc(50% - var(--mj-timeline-gap) - var(--mj-timeline-marker-size));\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] HORIZONTAL[_ngcontent-%COMP%] LAYOUT\n//[_ngcontent-%COMP%] ============================================================================\n\n.mj-timeline--horizontal[_ngcontent-%COMP%] {\n overflow-x: auto;\n overflow-y: hidden;\n -webkit-overflow-scrolling: touch;\n\n .mj-timeline__axis {\n display: flex;\n flex-direction: row;\n align-items: flex-start;\n padding-left: 0;\n padding-top: var(--mj-timeline-gap);\n min-width: max-content;\n\n &::before {\n left: 0;\n right: 0;\n top: calc(var(--mj-timeline-marker-size) / 2 + var(--mj-timeline-gap));\n bottom: auto;\n width: auto;\n height: var(--mj-timeline-line-width);\n }\n }\n\n .mj-timeline__segment-content {\n .mj-timeline__axis {\n flex-direction: row;\n }\n }\n\n .mj-timeline__event {\n flex-direction: column;\n align-items: center;\n margin-bottom: 0;\n margin-right: var(--mj-timeline-gap);\n min-width: 280px;\n max-width: 320px;\n\n &:last-child {\n margin-right: 0;\n }\n }\n\n .mj-timeline__marker {\n position: relative;\n left: auto;\n top: auto;\n margin-bottom: var(--mj-timeline-gap);\n }\n\n .mj-timeline__card {\n max-width: none;\n width: 100%;\n }\n\n .mj-timeline__date-label {\n display: block;\n margin-bottom: 8px;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-timeline-accent);\n }\n}\n\n//[_ngcontent-%COMP%] ============================================================================\n//[_ngcontent-%COMP%] RESPONSIVE[_ngcontent-%COMP%] STYLES\n//[_ngcontent-%COMP%] ============================================================================\n\n//[_ngcontent-%COMP%] Tablet\n@media[_ngcontent-%COMP%] (max-width[_ngcontent-%COMP%]: 1024px)[_ngcontent-%COMP%] {\n -shadowcsshost-no-combinator {\n --mj-timeline-card-max-width: 350px;\n --mj-timeline-axis-offset: 50px;\n }\n\n // Force single layout on tablet\n .mj-timeline--alternating {\n .mj-timeline__axis {\n align-items: flex-start;\n padding-left: var(--mj-timeline-axis-offset);\n\n &::before {\n left: calc(var(--mj-timeline-marker-size) / 2);\n transform: none;\n }\n }\n\n .mj-timeline__event {\n padding-right: 0;\n padding-left: 0;\n justify-content: flex-start;\n flex-direction: row;\n\n &--odd {\n padding-left: 0;\n justify-content: flex-start;\n flex-direction: row;\n }\n }\n\n .mj-timeline__marker {\n left: 0;\n transform: none;\n }\n\n .mj-timeline__date-label {\n display: none;\n }\n\n .mj-timeline__card {\n max-width: var(--mj-timeline-card-max-width);\n }\n }\n}\n\n//[_ngcontent-%COMP%] Mobile\n@media[_ngcontent-%COMP%] (max-width[_ngcontent-%COMP%]: 767px)[_ngcontent-%COMP%] {\n -shadowcsshost-no-combinator {\n --mj-timeline-card-max-width: 100%;\n --mj-timeline-card-min-width: 0;\n --mj-timeline-axis-offset: 40px;\n --mj-timeline-marker-size: 12px;\n --mj-timeline-card-padding: 12px;\n --mj-timeline-gap: 12px;\n }\n\n .mj-timeline {\n padding: var(--mj-timeline-gap);\n }\n\n .mj-timeline__segment-header {\n padding: 10px 12px;\n font-size: 14px;\n }\n\n .mj-timeline__segment-line {\n display: none;\n }\n\n .mj-timeline__card {\n max-width: none;\n min-width: 0;\n }\n\n .mj-timeline__card-title {\n font-size: 14px;\n white-space: normal;\n }\n\n .mj-timeline__card-actions {\n flex-direction: column;\n\n .mj-timeline__action {\n width: 100%;\n justify-content: center;\n }\n }\n\n // Horizontal mode on mobile\n .mj-timeline--horizontal {\n .mj-timeline__event {\n min-width: 240px;\n max-width: 280px;\n }\n }\n}\n\n//[_ngcontent-%COMP%] Touch[_ngcontent-%COMP%] device[_ngcontent-%COMP%] optimizations\n@media[_ngcontent-%COMP%] (hover[_ngcontent-%COMP%]: none)[_ngcontent-%COMP%] and[_ngcontent-%COMP%] (pointer[_ngcontent-%COMP%]: coarse)[_ngcontent-%COMP%] {\n .mj-timeline__card-actions--hover-only {\n opacity: 1;\n }\n\n .mj-timeline__action {\n min-height: 44px;\n min-width: 44px;\n }\n}"], changeDetection: 0 });
188
1880
  }
189
1881
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TimelineComponent, [{
190
1882
  type: Component,
191
- args: [{ selector: 'mj-timeline', template: "<div class=\"wrapper\"> \n <kendo-timeline\n [events]=\"events\"\n [orientation]=\"DisplayOrientation\"\n [collapsibleEvents]=\"true\"\n [alterMode]=\"true\"\n >\n </kendo-timeline>\n</div> " }]
192
- }], null, { DisplayOrientation: [{
1883
+ args: [{ selector: 'mj-timeline', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n MJ Timeline Component Template\n\n Structure:\n - Loading state\n - Empty state\n - Timeline container (vertical/horizontal)\n - Time segments (collapsible)\n - Segment header\n - Event cards within segment\n - Or flat list of events (when segmentGrouping='none')\n - Virtual scroll sentinel\n-->\n\n<div\n class=\"mj-timeline\"\n [class.mj-timeline--vertical]=\"orientation === 'vertical'\"\n [class.mj-timeline--horizontal]=\"orientation === 'horizontal'\"\n [class.mj-timeline--single]=\"layout === 'single'\"\n [class.mj-timeline--alternating]=\"layout === 'alternating'\"\n [attr.aria-label]=\"ariaLabel\"\n role=\"list\"\n tabindex=\"0\"\n (keydown)=\"onKeyDown($event)\"\n #scrollContainer\n (scroll)=\"onScroll($event)\">\n\n <!-- Loading State -->\n <ng-container *ngIf=\"isLoading && !isInitialized\">\n <ng-container *ngIf=\"loadingTemplate; else defaultLoading\">\n <ng-container *ngTemplateOutlet=\"loadingTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultLoading>\n <div class=\"mj-timeline__loading\">\n <div class=\"mj-timeline__loading-spinner\"></div>\n <span class=\"mj-timeline__loading-text\">{{ loadingMessage }}</span>\n </div>\n </ng-template>\n </ng-container>\n\n <!-- Empty State -->\n <ng-container *ngIf=\"isInitialized && allEvents.length === 0 && !isLoading\">\n <ng-container *ngIf=\"emptyTemplate; else defaultEmpty\">\n <ng-container *ngTemplateOutlet=\"emptyTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultEmpty>\n <div class=\"mj-timeline__empty\">\n <i [class]=\"emptyIcon\" class=\"mj-timeline__empty-icon\"></i>\n <span class=\"mj-timeline__empty-text\">{{ emptyMessage }}</span>\n </div>\n </ng-template>\n </ng-container>\n\n <!-- Timeline Content -->\n <ng-container *ngIf=\"isInitialized && allEvents.length > 0\">\n\n <!-- Segmented View -->\n <ng-container *ngIf=\"segmentGrouping !== 'none'\">\n <div\n *ngFor=\"let segment of segments; trackBy: trackBySegmentLabel; let segmentIndex = index\"\n class=\"mj-timeline__segment\"\n [class.mj-timeline__segment--collapsed]=\"!segment.isExpanded\"\n [attr.data-segment-label]=\"segment.label\">\n\n <!-- Segment Header -->\n <div\n class=\"mj-timeline__segment-header\"\n [class.mj-timeline__segment-header--clickable]=\"segmentsCollapsible\"\n (click)=\"onSegmentClick(segment)\"\n role=\"button\"\n [attr.aria-expanded]=\"segment.isExpanded\"\n [attr.aria-controls]=\"'segment-content-' + segmentIndex\">\n\n <ng-container *ngIf=\"segmentHeaderTemplate; else defaultSegmentHeader\">\n <ng-container *ngTemplateOutlet=\"segmentHeaderTemplate; context: { segment: segment }\"></ng-container>\n </ng-container>\n\n <ng-template #defaultSegmentHeader>\n <span class=\"mj-timeline__segment-toggle\" *ngIf=\"segmentsCollapsible\">\n <i [class]=\"segment.isExpanded ? 'fa-solid fa-chevron-down' : 'fa-solid fa-chevron-right'\"></i>\n </span>\n <span class=\"mj-timeline__segment-label\">{{ segment.label }}</span>\n <span class=\"mj-timeline__segment-count\">({{ segment.eventCount }} {{ segment.eventCount === 1 ? 'event' : 'events' }})</span>\n <span class=\"mj-timeline__segment-line\"></span>\n </ng-template>\n </div>\n\n <!-- Segment Content -->\n <div\n class=\"mj-timeline__segment-content\"\n [id]=\"'segment-content-' + segmentIndex\"\n [class.mj-timeline__segment-content--hidden]=\"!segment.isExpanded\">\n\n <div class=\"mj-timeline__axis\">\n <!-- Events in Segment -->\n <ng-container *ngFor=\"let event of segment.events; trackBy: trackByEventId; let eventIndex = index; let isOdd = odd\">\n <ng-container *ngTemplateOutlet=\"eventCard; context: {\n event: event,\n index: eventIndex,\n isOdd: isOdd,\n globalIndex: getGlobalIndex(event)\n }\"></ng-container>\n </ng-container>\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Flat View (no segments) -->\n <ng-container *ngIf=\"segmentGrouping === 'none'\">\n <div class=\"mj-timeline__axis\">\n <ng-container *ngFor=\"let event of allEvents; trackBy: trackByEventId; let eventIndex = index; let isOdd = odd\">\n <ng-container *ngTemplateOutlet=\"eventCard; context: {\n event: event,\n index: eventIndex,\n isOdd: isOdd,\n globalIndex: eventIndex\n }\"></ng-container>\n </ng-container>\n </div>\n </ng-container>\n\n </ng-container>\n\n <!-- Virtual Scroll Sentinel -->\n <div\n class=\"mj-timeline-scroll-sentinel\"\n *ngIf=\"virtualScroll.enabled && scrollState.hasMore\">\n </div>\n\n <!-- Loading More Indicator -->\n <div\n class=\"mj-timeline__loading-more\"\n *ngIf=\"scrollState.isLoading && virtualScroll.showLoadingIndicator\">\n <div class=\"mj-timeline__loading-spinner mj-timeline__loading-spinner--small\"></div>\n <span>{{ virtualScroll.loadingMessage }}</span>\n </div>\n\n</div>\n\n<!-- Event Card Template -->\n<ng-template #eventCard let-event=\"event\" let-index=\"index\" let-isOdd=\"isOdd\" let-globalIndex=\"globalIndex\">\n <div\n class=\"mj-timeline__event\"\n [class.mj-timeline__event--odd]=\"isOdd && layout === 'alternating'\"\n [class.mj-timeline__event--even]=\"!isOdd && layout === 'alternating'\"\n [class.mj-timeline__event--expanded]=\"event.isExpanded\"\n [class.mj-timeline__event--focused]=\"focusedEventIndex === globalIndex\"\n [attr.data-event-id]=\"event.id\"\n role=\"listitem\"\n [attr.aria-expanded]=\"event.isExpanded\">\n\n <!-- Timeline Marker -->\n <div class=\"mj-timeline__marker\" [style.background-color]=\"getColor(event)\">\n <i [class]=\"getIcon(event)\" class=\"mj-timeline__marker-icon\"></i>\n </div>\n\n <!-- Connector Line -->\n <div class=\"mj-timeline__connector\" [style.background-color]=\"getColor(event)\"></div>\n\n <!-- Date Label (for alternating layout) -->\n <div class=\"mj-timeline__date-label\" *ngIf=\"layout === 'alternating'\">\n {{ formatDate(event.date) }}\n </div>\n\n <!-- Event Card -->\n <div\n class=\"mj-timeline__card\"\n [class]=\"getEffectiveCardConfig(event).cssClass\"\n [style.max-width]=\"getEffectiveCardConfig(event).maxWidth\"\n [style.min-width]=\"getEffectiveCardConfig(event).minWidth\"\n [style.border-left-color]=\"getColor(event)\"\n (click)=\"onEventClick(event, globalIndex, $event)\"\n (mouseenter)=\"onEventMouseEnter(event, globalIndex, $event)\"\n (mouseleave)=\"onEventMouseLeave(event, globalIndex, $event)\">\n\n <!-- Custom Card Template -->\n <ng-container *ngIf=\"cardTemplate; else defaultCard\">\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: {\n event: event,\n group: groups[event.groupIndex]\n }\"></ng-container>\n </ng-container>\n\n <!-- Default Card Template -->\n <ng-template #defaultCard>\n <!-- Card Header -->\n <div class=\"mj-timeline__card-header\">\n <!-- Image (left position) -->\n <div\n class=\"mj-timeline__card-image mj-timeline__card-image--left\"\n *ngIf=\"event.imageUrl && getEffectiveCardConfig(event).imagePosition === 'left'\"\n [class.mj-timeline__card-image--small]=\"getEffectiveCardConfig(event).imageSize === 'small'\"\n [class.mj-timeline__card-image--medium]=\"getEffectiveCardConfig(event).imageSize === 'medium'\"\n [class.mj-timeline__card-image--large]=\"getEffectiveCardConfig(event).imageSize === 'large'\">\n <img [src]=\"event.imageUrl\" [alt]=\"event.title\" />\n </div>\n\n <div class=\"mj-timeline__card-header-content\">\n <!-- Custom Header Template -->\n <ng-container *ngIf=\"headerTemplate; else defaultHeader\">\n <ng-container *ngTemplateOutlet=\"headerTemplate; context: { event: event }\"></ng-container>\n </ng-container>\n\n <ng-template #defaultHeader>\n <!-- Icon -->\n <span\n class=\"mj-timeline__card-icon\"\n *ngIf=\"getEffectiveCardConfig(event).showIcon\"\n [style.color]=\"getColor(event)\">\n <i [class]=\"getIcon(event)\"></i>\n </span>\n\n <!-- Title & Subtitle -->\n <div class=\"mj-timeline__card-titles\">\n <h4 class=\"mj-timeline__card-title\">{{ event.title }}</h4>\n <span\n class=\"mj-timeline__card-subtitle\"\n *ngIf=\"event.subtitle && getEffectiveCardConfig(event).showSubtitle\">\n {{ event.subtitle }}\n </span>\n <span\n class=\"mj-timeline__card-date\"\n *ngIf=\"getEffectiveCardConfig(event).showDate && layout !== 'alternating'\">\n {{ formatDate(event.date, getEffectiveCardConfig(event).dateFormat) }}\n </span>\n </div>\n\n <!-- Expand/Collapse Toggle -->\n <button\n class=\"mj-timeline__card-toggle\"\n *ngIf=\"getEffectiveCardConfig(event).collapsible\"\n (click)=\"onToggleExpand(event, globalIndex, $event)\"\n [attr.aria-label]=\"event.isExpanded ? 'Collapse' : 'Expand'\"\n type=\"button\">\n <i [class]=\"event.isExpanded ? 'fa-solid fa-chevron-up' : 'fa-solid fa-chevron-down'\"></i>\n </button>\n </ng-template>\n </div>\n </div>\n\n <!-- Image (top position) -->\n <div\n class=\"mj-timeline__card-image mj-timeline__card-image--top\"\n *ngIf=\"event.imageUrl && getEffectiveCardConfig(event).imagePosition === 'top'\">\n <img [src]=\"event.imageUrl\" [alt]=\"event.title\" />\n </div>\n\n <!-- Card Body -->\n <div\n class=\"mj-timeline__card-body\"\n [class.mj-timeline__card-body--collapsed]=\"!event.isExpanded && getEffectiveCardConfig(event).collapsible\">\n\n <!-- Custom Body Template -->\n <ng-container *ngIf=\"bodyTemplate; else defaultBody\">\n <ng-container *ngTemplateOutlet=\"bodyTemplate; context: { event: event }\"></ng-container>\n </ng-container>\n\n <ng-template #defaultBody>\n <!-- Summary Fields (always visible) -->\n <div\n class=\"mj-timeline__card-fields mj-timeline__card-fields--summary\"\n *ngIf=\"getEffectiveCardConfig(event).summaryFields?.length\">\n <ng-container *ngFor=\"let field of getEffectiveCardConfig(event).summaryFields\">\n <div class=\"mj-timeline__card-field\" [class]=\"field.cssClass\">\n <i *ngIf=\"field.icon\" [class]=\"field.icon\" class=\"mj-timeline__card-field-icon\"></i>\n <span *ngIf=\"!field.hideLabel && field.label\" class=\"mj-timeline__card-field-label\">{{ field.label }}:</span>\n <span class=\"mj-timeline__card-field-value\">{{ getFieldValue(event, field) }}</span>\n </div>\n </ng-container>\n </div>\n\n <!-- Description (expanded view) -->\n <div\n class=\"mj-timeline__card-description\"\n *ngIf=\"event.description && event.isExpanded\"\n [class.mj-timeline__card-description--clamped]=\"(getEffectiveCardConfig(event).descriptionMaxLines ?? 0) > 0\"\n [style.-webkit-line-clamp]=\"getEffectiveCardConfig(event).descriptionMaxLines || null\">\n <ng-container *ngIf=\"getEffectiveCardConfig(event).allowHtmlDescription; else plainDescription\">\n <div [innerHTML]=\"event.description\"></div>\n </ng-container>\n <ng-template #plainDescription>\n {{ event.description }}\n </ng-template>\n </div>\n\n <!-- Expanded Fields -->\n <div\n class=\"mj-timeline__card-fields mj-timeline__card-fields--expanded\"\n *ngIf=\"event.isExpanded && getEffectiveCardConfig(event).expandedFields?.length\">\n <ng-container *ngFor=\"let field of getEffectiveCardConfig(event).expandedFields\">\n <div class=\"mj-timeline__card-field\" [class]=\"field.cssClass\">\n <i *ngIf=\"field.icon\" [class]=\"field.icon\" class=\"mj-timeline__card-field-icon\"></i>\n <span *ngIf=\"!field.hideLabel\" class=\"mj-timeline__card-field-label\">{{ field.label || field.fieldName }}:</span>\n <span class=\"mj-timeline__card-field-value\">{{ getFieldValue(event, field) }}</span>\n </div>\n </ng-container>\n </div>\n </ng-template>\n </div>\n\n <!-- Card Actions -->\n <div\n class=\"mj-timeline__card-actions\"\n [class.mj-timeline__card-actions--hover-only]=\"getEffectiveCardConfig(event).actionsOnHover\"\n *ngIf=\"getActions(event).length > 0\">\n\n <!-- Custom Actions Template -->\n <ng-container *ngIf=\"actionsTemplate; else defaultActions\">\n <ng-container *ngTemplateOutlet=\"actionsTemplate; context: {\n event: event,\n actions: getActions(event)\n }\"></ng-container>\n </ng-container>\n\n <ng-template #defaultActions>\n <button\n *ngFor=\"let action of getActions(event)\"\n class=\"mj-timeline__action\"\n [class.mj-timeline__action--primary]=\"action.variant === 'primary'\"\n [class.mj-timeline__action--secondary]=\"action.variant === 'secondary' || !action.variant\"\n [class.mj-timeline__action--danger]=\"action.variant === 'danger'\"\n [class.mj-timeline__action--link]=\"action.variant === 'link'\"\n [class]=\"action.cssClass\"\n [disabled]=\"action.disabled\"\n [title]=\"action.tooltip || ''\"\n (click)=\"onActionClick(event, action, globalIndex, $event)\"\n type=\"button\">\n <i *ngIf=\"action.icon\" [class]=\"action.icon\"></i>\n <span>{{ action.label }}</span>\n </button>\n </ng-template>\n </div>\n </ng-template>\n </div>\n </div>\n</ng-template>\n", styles: ["/**\n * MJ Timeline Component Styles\n *\n * A responsive, themeable timeline component using CSS variables.\n * Supports vertical/horizontal orientations, single/alternating layouts,\n * and fully responsive mobile design.\n */\n\n// ============================================================================\n// CSS VARIABLES (Theming)\n// ============================================================================\n\n:host {\n // Colors\n --mj-timeline-bg: transparent;\n --mj-timeline-line-color: #e0e0e0;\n --mj-timeline-marker-bg: #1976d2;\n --mj-timeline-marker-border: #ffffff;\n --mj-timeline-card-bg: #ffffff;\n --mj-timeline-card-border: #e0e0e0;\n --mj-timeline-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n --mj-timeline-card-shadow-hover: 0 4px 16px rgba(0, 0, 0, 0.15);\n --mj-timeline-card-radius: 8px;\n --mj-timeline-text-primary: #212121;\n --mj-timeline-text-secondary: #757575;\n --mj-timeline-text-muted: #9e9e9e;\n --mj-timeline-accent: #1976d2;\n --mj-timeline-segment-bg: #f5f5f5;\n --mj-timeline-focus-ring: 0 0 0 3px rgba(25, 118, 210, 0.3);\n\n // Sizing\n --mj-timeline-line-width: 2px;\n --mj-timeline-marker-size: 14px;\n --mj-timeline-marker-icon-size: 8px;\n --mj-timeline-card-padding: 16px;\n --mj-timeline-card-max-width: 400px;\n --mj-timeline-card-min-width: 250px;\n --mj-timeline-gap: 16px;\n --mj-timeline-segment-gap: 24px;\n --mj-timeline-axis-offset: 60px;\n\n // Animation\n --mj-timeline-transition: 0.25s ease;\n\n display: block;\n width: 100%;\n}\n\n// Dark mode support\n:host-context(.dark-theme),\n:host-context([data-theme=\"dark\"]) {\n --mj-timeline-line-color: #424242;\n --mj-timeline-card-bg: #1e1e1e;\n --mj-timeline-card-border: #424242;\n --mj-timeline-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n --mj-timeline-text-primary: #ffffff;\n --mj-timeline-text-secondary: #b0b0b0;\n --mj-timeline-text-muted: #757575;\n --mj-timeline-segment-bg: #2d2d2d;\n}\n\n// ============================================================================\n// MAIN CONTAINER\n// ============================================================================\n\n.mj-timeline {\n position: relative;\n width: 100%;\n padding: var(--mj-timeline-gap);\n background: var(--mj-timeline-bg);\n outline: none;\n\n &:focus-visible {\n box-shadow: var(--mj-timeline-focus-ring);\n border-radius: 4px;\n }\n}\n\n// ============================================================================\n// LOADING STATE\n// ============================================================================\n\n.mj-timeline__loading {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 48px 24px;\n gap: 16px;\n}\n\n.mj-timeline__loading-spinner {\n width: 40px;\n height: 40px;\n border: 3px solid var(--mj-timeline-line-color);\n border-top-color: var(--mj-timeline-accent);\n border-radius: 50%;\n animation: mj-timeline-spin 0.8s linear infinite;\n\n &--small {\n width: 20px;\n height: 20px;\n border-width: 2px;\n }\n}\n\n.mj-timeline__loading-text {\n color: var(--mj-timeline-text-secondary);\n font-size: 14px;\n}\n\n.mj-timeline__loading-more {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n gap: 8px;\n color: var(--mj-timeline-text-secondary);\n font-size: 13px;\n}\n\n@keyframes mj-timeline-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n// ============================================================================\n// EMPTY STATE\n// ============================================================================\n\n.mj-timeline__empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 64px 24px;\n gap: 16px;\n text-align: center;\n}\n\n.mj-timeline__empty-icon {\n font-size: 48px;\n color: var(--mj-timeline-text-muted);\n}\n\n.mj-timeline__empty-text {\n color: var(--mj-timeline-text-secondary);\n font-size: 16px;\n}\n\n// ============================================================================\n// TIME SEGMENTS\n// ============================================================================\n\n.mj-timeline__segment {\n margin-bottom: var(--mj-timeline-segment-gap);\n\n &:last-child {\n margin-bottom: 0;\n }\n}\n\n.mj-timeline__segment-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n margin-bottom: var(--mj-timeline-gap);\n background: var(--mj-timeline-segment-bg);\n border-radius: 6px;\n font-weight: 600;\n color: var(--mj-timeline-text-primary);\n user-select: none;\n\n &--clickable {\n cursor: pointer;\n transition: background-color var(--mj-timeline-transition);\n\n &:hover {\n background: darken(#f5f5f5, 5%);\n }\n\n :host-context(.dark-theme) &:hover,\n :host-context([data-theme=\"dark\"]) &:hover {\n background: lighten(#2d2d2d, 5%);\n }\n }\n}\n\n.mj-timeline__segment-toggle {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n color: var(--mj-timeline-text-secondary);\n font-size: 12px;\n transition: transform var(--mj-timeline-transition);\n}\n\n.mj-timeline__segment-label {\n font-size: 15px;\n}\n\n.mj-timeline__segment-count {\n color: var(--mj-timeline-text-secondary);\n font-size: 13px;\n font-weight: 400;\n}\n\n.mj-timeline__segment-line {\n flex: 1;\n height: 1px;\n background: var(--mj-timeline-line-color);\n margin-left: 8px;\n}\n\n.mj-timeline__segment-content {\n overflow: hidden;\n transition: max-height var(--mj-timeline-transition), opacity var(--mj-timeline-transition);\n\n &--hidden {\n max-height: 0;\n opacity: 0;\n pointer-events: none;\n }\n}\n\n// ============================================================================\n// TIMELINE AXIS\n// ============================================================================\n\n.mj-timeline__axis {\n position: relative;\n padding-left: var(--mj-timeline-axis-offset);\n\n // Vertical line\n &::before {\n content: '';\n position: absolute;\n left: calc(var(--mj-timeline-marker-size) / 2);\n top: 0;\n bottom: 0;\n width: var(--mj-timeline-line-width);\n background: var(--mj-timeline-line-color);\n }\n}\n\n// ============================================================================\n// TIMELINE EVENT\n// ============================================================================\n\n.mj-timeline__event {\n position: relative;\n display: flex;\n align-items: flex-start;\n margin-bottom: var(--mj-timeline-gap);\n\n &:last-child {\n margin-bottom: 0;\n }\n}\n\n// ============================================================================\n// MARKER\n// ============================================================================\n\n.mj-timeline__marker {\n position: absolute;\n left: 0;\n top: 16px;\n width: var(--mj-timeline-marker-size);\n height: var(--mj-timeline-marker-size);\n border-radius: 50%;\n background: var(--mj-timeline-marker-bg);\n border: 2px solid var(--mj-timeline-marker-border);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1;\n}\n\n.mj-timeline__marker-icon {\n font-size: var(--mj-timeline-marker-icon-size);\n color: var(--mj-timeline-marker-border);\n display: none; // Hidden by default, shown in larger markers\n}\n\n.mj-timeline__connector {\n display: none; // Hidden by default, used in horizontal mode\n}\n\n// ============================================================================\n// DATE LABEL (Alternating Layout)\n// ============================================================================\n\n.mj-timeline__date-label {\n display: none; // Hidden by default\n}\n\n// ============================================================================\n// EVENT CARD\n// ============================================================================\n\n.mj-timeline__card {\n flex: 1;\n background: var(--mj-timeline-card-bg);\n border: 1px solid var(--mj-timeline-card-border);\n border-left-width: 4px;\n border-radius: var(--mj-timeline-card-radius);\n box-shadow: var(--mj-timeline-card-shadow);\n overflow: hidden;\n transition: box-shadow var(--mj-timeline-transition), transform var(--mj-timeline-transition);\n cursor: pointer;\n max-width: var(--mj-timeline-card-max-width);\n min-width: var(--mj-timeline-card-min-width);\n\n &:hover {\n box-shadow: var(--mj-timeline-card-shadow-hover);\n transform: translateY(-1px);\n }\n\n .mj-timeline__event--focused & {\n box-shadow: var(--mj-timeline-focus-ring), var(--mj-timeline-card-shadow);\n }\n}\n\n// ============================================================================\n// CARD HEADER\n// ============================================================================\n\n.mj-timeline__card-header {\n display: flex;\n align-items: flex-start;\n padding: var(--mj-timeline-card-padding);\n gap: 12px;\n}\n\n.mj-timeline__card-header-content {\n flex: 1;\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0; // Enable text truncation\n}\n\n.mj-timeline__card-icon {\n flex-shrink: 0;\n font-size: 18px;\n line-height: 1;\n margin-top: 2px;\n}\n\n.mj-timeline__card-titles {\n flex: 1;\n min-width: 0;\n}\n\n.mj-timeline__card-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-timeline-text-primary);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mj-timeline__card-subtitle {\n display: block;\n font-size: 13px;\n color: var(--mj-timeline-text-secondary);\n margin-top: 2px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mj-timeline__card-date {\n display: block;\n font-size: 12px;\n color: var(--mj-timeline-text-muted);\n margin-top: 4px;\n}\n\n.mj-timeline__card-toggle {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n color: var(--mj-timeline-text-secondary);\n border-radius: 4px;\n cursor: pointer;\n transition: background-color var(--mj-timeline-transition), color var(--mj-timeline-transition);\n\n &:hover {\n background: var(--mj-timeline-segment-bg);\n color: var(--mj-timeline-text-primary);\n }\n\n &:focus-visible {\n box-shadow: var(--mj-timeline-focus-ring);\n outline: none;\n }\n}\n\n// ============================================================================\n// CARD IMAGE\n// ============================================================================\n\n.mj-timeline__card-image {\n overflow: hidden;\n border-radius: 4px;\n flex-shrink: 0;\n\n img {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n &--left {\n &.mj-timeline__card-image--small {\n width: 48px;\n height: 48px;\n }\n\n &.mj-timeline__card-image--medium {\n width: 80px;\n height: 80px;\n }\n\n &.mj-timeline__card-image--large {\n width: 120px;\n height: 120px;\n }\n }\n\n &--top {\n width: 100%;\n max-height: 200px;\n margin: 0 var(--mj-timeline-card-padding);\n margin-top: 0;\n border-radius: 4px;\n\n img {\n width: calc(100% - 2 * var(--mj-timeline-card-padding));\n height: auto;\n }\n }\n}\n\n// ============================================================================\n// CARD BODY\n// ============================================================================\n\n.mj-timeline__card-body {\n padding: 0 var(--mj-timeline-card-padding);\n padding-bottom: var(--mj-timeline-card-padding);\n\n &--collapsed {\n display: none;\n }\n}\n\n.mj-timeline__card-description {\n font-size: 14px;\n line-height: 1.6;\n color: var(--mj-timeline-text-secondary);\n margin-bottom: 12px;\n\n &--clamped {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n\n &:last-child {\n margin-bottom: 0;\n }\n}\n\n// ============================================================================\n// CARD FIELDS\n// ============================================================================\n\n.mj-timeline__card-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 8px 16px;\n\n &--summary {\n padding-top: 8px;\n border-top: 1px solid var(--mj-timeline-line-color);\n margin-top: 8px;\n }\n\n &--expanded {\n margin-top: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--mj-timeline-line-color);\n }\n}\n\n.mj-timeline__card-field {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n}\n\n.mj-timeline__card-field-icon {\n color: var(--mj-timeline-text-muted);\n font-size: 12px;\n}\n\n.mj-timeline__card-field-label {\n color: var(--mj-timeline-text-muted);\n}\n\n.mj-timeline__card-field-value {\n color: var(--mj-timeline-text-secondary);\n font-weight: 500;\n}\n\n// ============================================================================\n// CARD ACTIONS\n// ============================================================================\n\n.mj-timeline__card-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n padding: var(--mj-timeline-card-padding);\n padding-top: 0;\n justify-content: flex-end;\n\n &--hover-only {\n opacity: 0;\n transition: opacity var(--mj-timeline-transition);\n\n .mj-timeline__card:hover &,\n .mj-timeline__card:focus-within & {\n opacity: 1;\n }\n }\n}\n\n.mj-timeline__action {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n font-size: 13px;\n font-weight: 500;\n border: 1px solid;\n border-radius: 4px;\n cursor: pointer;\n transition: all var(--mj-timeline-transition);\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n &:focus-visible {\n box-shadow: var(--mj-timeline-focus-ring);\n outline: none;\n }\n\n i {\n font-size: 12px;\n }\n\n // Variants\n &--primary {\n background: var(--mj-timeline-accent);\n border-color: var(--mj-timeline-accent);\n color: #ffffff;\n\n &:hover:not(:disabled) {\n background: darken(#1976d2, 8%);\n border-color: darken(#1976d2, 8%);\n }\n }\n\n &--secondary {\n background: transparent;\n border-color: var(--mj-timeline-card-border);\n color: var(--mj-timeline-text-primary);\n\n &:hover:not(:disabled) {\n background: var(--mj-timeline-segment-bg);\n }\n }\n\n &--danger {\n background: #f44336;\n border-color: #f44336;\n color: #ffffff;\n\n &:hover:not(:disabled) {\n background: darken(#f44336, 8%);\n border-color: darken(#f44336, 8%);\n }\n }\n\n &--link {\n background: transparent;\n border-color: transparent;\n color: var(--mj-timeline-accent);\n padding: 6px 8px;\n\n &:hover:not(:disabled) {\n background: rgba(25, 118, 210, 0.08);\n }\n }\n}\n\n// ============================================================================\n// VIRTUAL SCROLL SENTINEL\n// ============================================================================\n\n.mj-timeline-scroll-sentinel {\n height: 1px;\n width: 100%;\n visibility: hidden;\n}\n\n// ============================================================================\n// ALTERNATING LAYOUT\n// ============================================================================\n\n.mj-timeline--alternating {\n .mj-timeline__axis {\n padding-left: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n\n &::before {\n left: 50%;\n transform: translateX(-50%);\n }\n }\n\n .mj-timeline__event {\n width: 100%;\n justify-content: flex-start;\n padding-right: calc(50% + var(--mj-timeline-gap));\n\n &--odd {\n justify-content: flex-end;\n padding-right: 0;\n padding-left: calc(50% + var(--mj-timeline-gap));\n flex-direction: row-reverse;\n }\n }\n\n .mj-timeline__marker {\n left: 50%;\n transform: translateX(-50%);\n }\n\n .mj-timeline__date-label {\n display: block;\n position: absolute;\n top: 12px;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-timeline-accent);\n white-space: nowrap;\n\n .mj-timeline__event:not(.mj-timeline__event--odd) & {\n right: calc(50% + var(--mj-timeline-gap) + var(--mj-timeline-marker-size));\n text-align: right;\n }\n\n .mj-timeline__event--odd & {\n left: calc(50% + var(--mj-timeline-gap) + var(--mj-timeline-marker-size));\n text-align: left;\n }\n }\n\n .mj-timeline__card {\n max-width: calc(50% - var(--mj-timeline-gap) - var(--mj-timeline-marker-size));\n }\n}\n\n// ============================================================================\n// HORIZONTAL LAYOUT\n// ============================================================================\n\n.mj-timeline--horizontal {\n overflow-x: auto;\n overflow-y: hidden;\n -webkit-overflow-scrolling: touch;\n\n .mj-timeline__axis {\n display: flex;\n flex-direction: row;\n align-items: flex-start;\n padding-left: 0;\n padding-top: var(--mj-timeline-gap);\n min-width: max-content;\n\n &::before {\n left: 0;\n right: 0;\n top: calc(var(--mj-timeline-marker-size) / 2 + var(--mj-timeline-gap));\n bottom: auto;\n width: auto;\n height: var(--mj-timeline-line-width);\n }\n }\n\n .mj-timeline__segment-content {\n .mj-timeline__axis {\n flex-direction: row;\n }\n }\n\n .mj-timeline__event {\n flex-direction: column;\n align-items: center;\n margin-bottom: 0;\n margin-right: var(--mj-timeline-gap);\n min-width: 280px;\n max-width: 320px;\n\n &:last-child {\n margin-right: 0;\n }\n }\n\n .mj-timeline__marker {\n position: relative;\n left: auto;\n top: auto;\n margin-bottom: var(--mj-timeline-gap);\n }\n\n .mj-timeline__card {\n max-width: none;\n width: 100%;\n }\n\n .mj-timeline__date-label {\n display: block;\n margin-bottom: 8px;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-timeline-accent);\n }\n}\n\n// ============================================================================\n// RESPONSIVE STYLES\n// ============================================================================\n\n// Tablet\n@media (max-width: 1024px) {\n :host {\n --mj-timeline-card-max-width: 350px;\n --mj-timeline-axis-offset: 50px;\n }\n\n // Force single layout on tablet\n .mj-timeline--alternating {\n .mj-timeline__axis {\n align-items: flex-start;\n padding-left: var(--mj-timeline-axis-offset);\n\n &::before {\n left: calc(var(--mj-timeline-marker-size) / 2);\n transform: none;\n }\n }\n\n .mj-timeline__event {\n padding-right: 0;\n padding-left: 0;\n justify-content: flex-start;\n flex-direction: row;\n\n &--odd {\n padding-left: 0;\n justify-content: flex-start;\n flex-direction: row;\n }\n }\n\n .mj-timeline__marker {\n left: 0;\n transform: none;\n }\n\n .mj-timeline__date-label {\n display: none;\n }\n\n .mj-timeline__card {\n max-width: var(--mj-timeline-card-max-width);\n }\n }\n}\n\n// Mobile\n@media (max-width: 767px) {\n :host {\n --mj-timeline-card-max-width: 100%;\n --mj-timeline-card-min-width: 0;\n --mj-timeline-axis-offset: 40px;\n --mj-timeline-marker-size: 12px;\n --mj-timeline-card-padding: 12px;\n --mj-timeline-gap: 12px;\n }\n\n .mj-timeline {\n padding: var(--mj-timeline-gap);\n }\n\n .mj-timeline__segment-header {\n padding: 10px 12px;\n font-size: 14px;\n }\n\n .mj-timeline__segment-line {\n display: none;\n }\n\n .mj-timeline__card {\n max-width: none;\n min-width: 0;\n }\n\n .mj-timeline__card-title {\n font-size: 14px;\n white-space: normal;\n }\n\n .mj-timeline__card-actions {\n flex-direction: column;\n\n .mj-timeline__action {\n width: 100%;\n justify-content: center;\n }\n }\n\n // Horizontal mode on mobile\n .mj-timeline--horizontal {\n .mj-timeline__event {\n min-width: 240px;\n max-width: 280px;\n }\n }\n}\n\n// Touch device optimizations\n@media (hover: none) and (pointer: coarse) {\n .mj-timeline__card-actions--hover-only {\n opacity: 1;\n }\n\n .mj-timeline__action {\n min-height: 44px;\n min-width: 44px;\n }\n}\n"] }]
1884
+ }], () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }], { groups: [{
1885
+ type: Input
1886
+ }], allowLoad: [{
1887
+ type: Input
1888
+ }], orientation: [{
1889
+ type: Input
1890
+ }], layout: [{
1891
+ type: Input
1892
+ }], sortOrder: [{
1893
+ type: Input
1894
+ }], segmentGrouping: [{
1895
+ type: Input
1896
+ }], defaultCardConfig: [{
1897
+ type: Input
1898
+ }], virtualScroll: [{
1899
+ type: Input
1900
+ }], segmentsCollapsible: [{
1901
+ type: Input
1902
+ }], segmentsDefaultExpanded: [{
1903
+ type: Input
1904
+ }], emptyMessage: [{
1905
+ type: Input
1906
+ }], emptyIcon: [{
1907
+ type: Input
1908
+ }], loadingMessage: [{
193
1909
  type: Input
194
- }], Groups: [{
1910
+ }], ariaLabel: [{
195
1911
  type: Input
196
- }], AllowLoad: [{
1912
+ }], enableKeyboardNavigation: [{
197
1913
  type: Input
1914
+ }], beforeEventClick: [{
1915
+ type: Output
1916
+ }], beforeEventExpand: [{
1917
+ type: Output
1918
+ }], beforeEventCollapse: [{
1919
+ type: Output
1920
+ }], beforeEventHover: [{
1921
+ type: Output
1922
+ }], beforeActionClick: [{
1923
+ type: Output
1924
+ }], beforeSegmentExpand: [{
1925
+ type: Output
1926
+ }], beforeSegmentCollapse: [{
1927
+ type: Output
1928
+ }], beforeLoad: [{
1929
+ type: Output
1930
+ }], afterEventClick: [{
1931
+ type: Output
1932
+ }], afterEventExpand: [{
1933
+ type: Output
1934
+ }], afterEventCollapse: [{
1935
+ type: Output
1936
+ }], afterEventHover: [{
1937
+ type: Output
1938
+ }], afterActionClick: [{
1939
+ type: Output
1940
+ }], afterSegmentExpand: [{
1941
+ type: Output
1942
+ }], afterSegmentCollapse: [{
1943
+ type: Output
1944
+ }], afterLoad: [{
1945
+ type: Output
1946
+ }], cardTemplate: [{
1947
+ type: ContentChild,
1948
+ args: ['cardTemplate']
1949
+ }], headerTemplate: [{
1950
+ type: ContentChild,
1951
+ args: ['headerTemplate']
1952
+ }], bodyTemplate: [{
1953
+ type: ContentChild,
1954
+ args: ['bodyTemplate']
1955
+ }], actionsTemplate: [{
1956
+ type: ContentChild,
1957
+ args: ['actionsTemplate']
1958
+ }], segmentHeaderTemplate: [{
1959
+ type: ContentChild,
1960
+ args: ['segmentHeaderTemplate']
1961
+ }], emptyTemplate: [{
1962
+ type: ContentChild,
1963
+ args: ['emptyTemplate']
1964
+ }], loadingTemplate: [{
1965
+ type: ContentChild,
1966
+ args: ['loadingTemplate']
1967
+ }], scrollContainer: [{
1968
+ type: ViewChild,
1969
+ args: ['scrollContainer']
198
1970
  }] }); })();
199
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TimelineComponent, { className: "TimelineComponent", filePath: "src/lib/component/timeline.component.ts", lineNumber: 90 }); })();
1971
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TimelineComponent, { className: "TimelineComponent", filePath: "src/lib/component/timeline.component.ts", lineNumber: 141 }); })();
200
1972
  //# sourceMappingURL=timeline.component.js.map