@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.
package/README.md CHANGED
@@ -1,17 +1,18 @@
1
1
  # @memberjunction/ng-timeline
2
2
 
3
- The `@memberjunction/ng-timeline` package provides a comprehensive Angular component for displaying chronological data from MemberJunction entities in a timeline format. Built on top of Kendo UI's Timeline component, it offers a flexible and intuitive way to visualize time-based data from multiple entity sources.
3
+ A powerful, flexible, and fully responsive Angular timeline component. Works with both MemberJunction entities and plain JavaScript objects. **No external dependencies** - pure HTML/CSS implementation.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Multiple Data Sources**: Display data from multiple MemberJunction entities on a unified timeline
8
- - **Flexible Orientation**: Support for both vertical and horizontal timeline layouts
9
- - **Data Source Options**: Load data via entity views with filters or provide pre-loaded entity arrays
10
- - **Customizable Display**: Configure title, date, and summary fields for each timeline item
11
- - **Visual Customization**: Support for custom icons and colors for different event groups
12
- - **Interactive UI**: Collapsible event details with alternating layout mode
13
- - **Dynamic Content**: Custom summary generation via callback functions
14
- - **Refresh Control**: Manual control over data loading and refresh operations
7
+ - **Universal Compatibility**: Works with MemberJunction BaseEntity objects OR plain JavaScript objects
8
+ - **Responsive Design**: Mobile-first approach with automatic layout adaptation
9
+ - **Virtual Scrolling**: Built-in support for large datasets with dynamic loading
10
+ - **Time Segment Collapsing**: Group events by day/week/month/quarter/year with collapsible sections
11
+ - **Rich Event System**: BeforeX/AfterX event pattern for full control from container components
12
+ - **Customizable Cards**: Configurable fields, images, actions, and custom templates
13
+ - **Keyboard Navigation**: Full accessibility with ARIA support
14
+ - **Theming**: CSS variables for easy customization including dark mode
15
+ - **Zero Dependencies**: No Kendo UI or other external libraries required
15
16
 
16
17
  ## Installation
17
18
 
@@ -21,324 +22,463 @@ npm install @memberjunction/ng-timeline
21
22
 
22
23
  ## Requirements
23
24
 
24
- - Angular 18.0.2 or higher
25
- - @memberjunction/core and related MemberJunction packages
26
- - Kendo UI Angular components:
27
- - @progress/kendo-angular-layout (v16.2.0)
28
- - @progress/kendo-angular-buttons (v16.2.0)
29
- - @progress/kendo-angular-indicators (v16.2.0)
30
-
31
- ## Usage
25
+ | Requirement | Version |
26
+ |------------|---------|
27
+ | Angular | 18+ |
28
+ | TypeScript | 5.0+ |
29
+ | @memberjunction/core | Optional (only for entity data sources) |
30
+
31
+ ## Architecture
32
+
33
+ ```mermaid
34
+ graph TB
35
+ subgraph "Data Layer"
36
+ A[TimelineGroup<T>] --> B[Plain Objects]
37
+ A --> C[BaseEntity Objects]
38
+ end
39
+
40
+ subgraph "Component Layer"
41
+ D[TimelineComponent] --> E[Segments]
42
+ E --> F[Event Cards]
43
+ D --> G[Virtual Scroll]
44
+ end
45
+
46
+ subgraph "Event System"
47
+ H[BeforeX Events] --> I{Cancel?}
48
+ I -->|No| J[Default Behavior]
49
+ I -->|Yes| K[Blocked]
50
+ J --> L[AfterX Events]
51
+ end
52
+
53
+ A --> D
54
+ D --> H
55
+ ```
32
56
 
33
- ### Basic Usage
57
+ ## Quick Start
34
58
 
35
- First, import the TimelineModule in your module:
59
+ ### Import the Module
36
60
 
37
61
  ```typescript
38
62
  import { TimelineModule } from '@memberjunction/ng-timeline';
39
63
 
40
64
  @NgModule({
41
- imports: [
42
- // other imports...
43
- TimelineModule
44
- ],
65
+ imports: [TimelineModule]
45
66
  })
46
67
  export class YourModule { }
47
68
  ```
48
69
 
49
- Then, use the component in your template:
50
-
51
- ```html
52
- <mj-timeline [Groups]="timelineGroups" [DisplayOrientation]="'vertical'"></mj-timeline>
53
- ```
54
-
55
- In your component file:
70
+ ### Basic Usage with Plain Objects
56
71
 
57
72
  ```typescript
58
73
  import { TimelineGroup } from '@memberjunction/ng-timeline';
59
74
 
60
- export class YourComponent {
61
- timelineGroups: TimelineGroup[] = [];
62
-
63
- constructor() {
64
- // Create a timeline group using entity data
65
- const tasksGroup = new TimelineGroup();
66
- tasksGroup.EntityName = 'Tasks';
67
- tasksGroup.DataSourceType = 'entity';
68
- tasksGroup.TitleFieldName = 'Title';
69
- tasksGroup.DateFieldName = 'DueDate';
70
- tasksGroup.Filter = "Status = 'Open'";
71
-
72
- // Add the group to the array
73
- this.timelineGroups.push(tasksGroup);
75
+ interface MyEvent {
76
+ id: string;
77
+ title: string;
78
+ eventDate: Date;
79
+ description: string;
80
+ }
81
+
82
+ @Component({
83
+ template: `<mj-timeline [groups]="groups"></mj-timeline>`
84
+ })
85
+ export class MyComponent {
86
+ groups: TimelineGroup<MyEvent>[] = [];
87
+
88
+ ngOnInit() {
89
+ const group = TimelineGroup.FromArray(this.myEvents, {
90
+ titleField: 'title',
91
+ dateField: 'eventDate',
92
+ descriptionField: 'description'
93
+ });
94
+ this.groups = [group];
74
95
  }
75
96
  }
76
97
  ```
77
98
 
78
- ### Advanced Usage
79
-
80
- #### Using Multiple Data Sources
81
-
82
- Display data from multiple entity types on the same timeline with different visual treatments:
99
+ ### Usage with MemberJunction Entities
83
100
 
84
101
  ```typescript
85
102
  import { TimelineGroup } from '@memberjunction/ng-timeline';
86
- import { BaseEntity } from '@memberjunction/core';
87
-
88
- export class YourComponent implements OnInit {
89
- timelineGroups: TimelineGroup[] = [];
90
-
91
- async ngOnInit() {
92
- // First group - Tasks with custom icon and color
93
- const tasksGroup = new TimelineGroup();
94
- tasksGroup.EntityName = 'Tasks';
95
- tasksGroup.DataSourceType = 'entity';
96
- tasksGroup.TitleFieldName = 'Title';
97
- tasksGroup.DateFieldName = 'DueDate';
98
- tasksGroup.Filter = "Status = 'Active'"; // Optional filter
99
- tasksGroup.DisplayIconMode = 'custom';
100
- tasksGroup.DisplayIcon = 'fa fa-tasks';
101
- tasksGroup.DisplayColorMode = 'manual';
102
- tasksGroup.DisplayColor = '#4287f5';
103
-
104
- // Second group - Meetings with custom summary
105
- const meetingsGroup = new TimelineGroup();
106
- meetingsGroup.EntityName = 'Meetings';
107
- meetingsGroup.DataSourceType = 'entity';
108
- meetingsGroup.TitleFieldName = 'Subject';
109
- meetingsGroup.DateFieldName = 'StartTime';
110
- meetingsGroup.SummaryMode = 'custom';
111
- meetingsGroup.SummaryFunction = (record: BaseEntity) => {
112
- return `<strong>Location:</strong> ${record.Get('Location')}<br/>
113
- <strong>Duration:</strong> ${record.Get('DurationMinutes')} minutes`;
114
- };
115
-
116
- // Add groups to array
117
- this.timelineGroups.push(tasksGroup, meetingsGroup);
103
+ import { TaskEntity } from '@memberjunction/core-entities';
104
+
105
+ @Component({
106
+ template: `<mj-timeline [groups]="groups"></mj-timeline>`
107
+ })
108
+ export class MyComponent {
109
+ groups: TimelineGroup<TaskEntity>[] = [];
110
+
111
+ ngOnInit() {
112
+ const group = new TimelineGroup<TaskEntity>();
113
+ group.EntityName = 'Tasks';
114
+ group.DataSourceType = 'entity';
115
+ group.Filter = "Status = 'Open'";
116
+ group.TitleFieldName = 'Name';
117
+ group.DateFieldName = 'DueDate';
118
+ this.groups = [group];
118
119
  }
119
120
  }
120
121
  ```
121
122
 
122
- #### Using RunView Parameters
123
-
124
- Create a TimelineGroup using pre-configured RunViewParams:
123
+ ## Component API
124
+
125
+ ### Inputs
126
+
127
+ | Input | Type | Default | Description |
128
+ |-------|------|---------|-------------|
129
+ | `groups` | `TimelineGroup<T>[]` | `[]` | Data groups to display |
130
+ | `allowLoad` | `boolean` | `true` | Control deferred loading |
131
+ | `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Timeline orientation |
132
+ | `layout` | `'single' \| 'alternating'` | `'single'` | Card layout (vertical only) |
133
+ | `sortOrder` | `'asc' \| 'desc'` | `'desc'` | Event sort order |
134
+ | `segmentGrouping` | `'none' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'` | `'month'` | Time segment grouping |
135
+ | `segmentsCollapsible` | `boolean` | `true` | Allow collapsing segments |
136
+ | `segmentsDefaultExpanded` | `boolean` | `true` | Segments start expanded |
137
+ | `defaultCardConfig` | `TimelineCardConfig` | (see below) | Default card configuration |
138
+ | `virtualScroll` | `VirtualScrollConfig` | (see below) | Virtual scroll settings |
139
+ | `emptyMessage` | `string` | `'No events to display'` | Empty state message |
140
+ | `emptyIcon` | `string` | `'fa-regular fa-calendar-xmark'` | Empty state icon |
141
+ | `enableKeyboardNavigation` | `boolean` | `true` | Enable keyboard navigation |
142
+
143
+ ### Outputs (Events)
144
+
145
+ The component uses a BeforeX/AfterX event pattern. BeforeX events include a `cancel` property - set it to `true` to prevent the default behavior.
146
+
147
+ | Event | Args Type | Description |
148
+ |-------|-----------|-------------|
149
+ | `beforeEventClick` | `BeforeEventClickArgs<T>` | Before card click |
150
+ | `afterEventClick` | `AfterEventClickArgs<T>` | After card click |
151
+ | `beforeEventExpand` | `BeforeEventExpandArgs<T>` | Before card expand |
152
+ | `afterEventExpand` | `AfterEventExpandArgs<T>` | After card expand |
153
+ | `beforeEventCollapse` | `BeforeEventCollapseArgs<T>` | Before card collapse |
154
+ | `afterEventCollapse` | `AfterEventCollapseArgs<T>` | After card collapse |
155
+ | `beforeEventHover` | `BeforeEventHoverArgs<T>` | Before hover state change |
156
+ | `afterEventHover` | `AfterEventHoverArgs<T>` | After hover state change |
157
+ | `beforeActionClick` | `BeforeActionClickArgs<T>` | Before action button click |
158
+ | `afterActionClick` | `AfterActionClickArgs<T>` | After action button click |
159
+ | `beforeSegmentExpand` | `BeforeSegmentExpandArgs` | Before segment expand |
160
+ | `afterSegmentExpand` | `AfterSegmentExpandArgs` | After segment expand |
161
+ | `beforeSegmentCollapse` | `BeforeSegmentCollapseArgs` | Before segment collapse |
162
+ | `afterSegmentCollapse` | `AfterSegmentCollapseArgs` | After segment collapse |
163
+ | `beforeLoad` | `BeforeLoadArgs` | Before data load |
164
+ | `afterLoad` | `AfterLoadArgs` | After data load |
165
+
166
+ ### Public Methods
125
167
 
126
168
  ```typescript
127
- import { TimelineGroup } from '@memberjunction/ng-timeline';
128
- import { RunViewParams } from '@memberjunction/core';
129
-
130
- export class YourComponent implements OnInit {
131
- timelineGroups: TimelineGroup[] = [];
132
-
133
- async ngOnInit() {
134
- // Configure RunViewParams for data retrieval
135
- const params: RunViewParams = {
136
- EntityName: 'Tasks',
137
- ExtraFilter: "Status = 'Open' AND Priority = 'High'",
138
- Skip: 0,
139
- Take: 50,
140
- OrderBy: 'DueDate DESC'
141
- };
142
-
143
- // Create TimelineGroup from view parameters
144
- const tasksGroup = await TimelineGroup.FromView(params);
145
- tasksGroup.TitleFieldName = 'Title';
146
- tasksGroup.DateFieldName = 'DueDate';
147
- tasksGroup.DisplayIconMode = 'custom';
148
- tasksGroup.DisplayIcon = 'fa fa-exclamation-circle';
149
-
150
- this.timelineGroups.push(tasksGroup);
151
- }
152
- }
169
+ // Refresh all data
170
+ await timeline.refresh();
171
+
172
+ // Load more (virtual scroll)
173
+ await timeline.loadMore();
174
+
175
+ // Expand/collapse all events
176
+ timeline.expandAllEvents();
177
+ timeline.collapseAllEvents();
178
+
179
+ // Expand/collapse all segments
180
+ timeline.expandAllSegments();
181
+ timeline.collapseAllSegments();
182
+
183
+ // Target specific events
184
+ timeline.expandEvent(eventId);
185
+ timeline.collapseEvent(eventId);
186
+
187
+ // Navigation
188
+ timeline.scrollToEvent(eventId, 'smooth');
189
+ timeline.scrollToDate(new Date(), 'smooth');
190
+
191
+ // Data access
192
+ const event = timeline.getEvent(eventId);
193
+ const allEvents = timeline.getAllEvents();
153
194
  ```
154
195
 
155
- #### Using Pre-loaded Entity Arrays
196
+ ## TimelineGroup Configuration
156
197
 
157
- For scenarios where you already have entity data loaded:
198
+ ```typescript
199
+ const group = new TimelineGroup<MyType>();
200
+
201
+ // Data Source
202
+ group.DataSourceType = 'array'; // or 'entity' for MJ
203
+ group.EntityObjects = myData; // For array mode
204
+ group.EntityName = 'Tasks'; // For entity mode
205
+ group.Filter = "Status='Open'"; // SQL filter (entity mode)
206
+ group.OrderBy = 'DueDate DESC'; // SQL order (entity mode)
207
+
208
+ // Field Mappings
209
+ group.TitleFieldName = 'title';
210
+ group.DateFieldName = 'eventDate';
211
+ group.SubtitleFieldName = 'category';
212
+ group.DescriptionFieldName = 'details';
213
+ group.ImageFieldName = 'thumbnailUrl';
214
+ group.IdFieldName = 'id'; // Defaults to 'ID' or 'id'
215
+
216
+ // Group Display
217
+ group.DisplayIconMode = 'custom';
218
+ group.DisplayIcon = 'fa-solid fa-check-circle';
219
+ group.DisplayColorMode = 'manual';
220
+ group.DisplayColor = '#4caf50';
221
+ group.GroupLabel = 'Completed Tasks';
222
+
223
+ // Card Configuration
224
+ group.CardConfig = {
225
+ collapsible: true,
226
+ defaultExpanded: false,
227
+ summaryFields: [
228
+ { fieldName: 'Status', icon: 'fa-solid fa-circle' }
229
+ ],
230
+ actions: [
231
+ { id: 'view', label: 'View', variant: 'primary' }
232
+ ]
233
+ };
234
+
235
+ // Custom Functions
236
+ group.SummaryFunction = (record) => `Priority: ${record.priority}`;
237
+ group.EventConfigFunction = (record) => ({
238
+ color: record.isUrgent ? '#f44336' : undefined
239
+ });
240
+ ```
241
+
242
+ ## Card Configuration
158
243
 
159
244
  ```typescript
160
- import { TimelineGroup } from '@memberjunction/ng-timeline';
161
- import { BaseEntity } from '@memberjunction/core';
162
-
163
- export class YourComponent {
164
- timelineGroups: TimelineGroup[] = [];
165
-
166
- displayLoadedData(entities: BaseEntity[]) {
167
- const group = new TimelineGroup();
168
- group.EntityName = 'Custom Events';
169
- group.DataSourceType = 'array'; // Use provided array instead of loading
170
- group.EntityObjects = entities;
171
- group.TitleFieldName = 'EventName';
172
- group.DateFieldName = 'EventDate';
173
- group.SummaryMode = 'field';
174
-
175
- this.timelineGroups = [group];
176
- }
245
+ interface TimelineCardConfig {
246
+ // Header
247
+ showIcon?: boolean; // Show icon in header
248
+ showDate?: boolean; // Show date in header
249
+ showSubtitle?: boolean; // Show subtitle
250
+ dateFormat?: string; // Angular date format
251
+
252
+ // Image
253
+ imageField?: string; // Field containing image URL
254
+ imagePosition?: 'left' | 'top' | 'none';
255
+ imageSize?: 'small' | 'medium' | 'large';
256
+
257
+ // Body
258
+ descriptionField?: string; // Description field name
259
+ descriptionMaxLines?: number; // Max lines before truncate
260
+ allowHtmlDescription?: boolean;
261
+
262
+ // Expansion
263
+ collapsible?: boolean; // Allow expand/collapse
264
+ defaultExpanded?: boolean; // Start expanded
265
+ expandedFields?: TimelineDisplayField[]; // Fields in expanded view
266
+ summaryFields?: TimelineDisplayField[]; // Always visible fields
267
+
268
+ // Actions
269
+ actions?: TimelineAction[]; // Action buttons
270
+ actionsOnHover?: boolean; // Show actions only on hover
271
+
272
+ // Styling
273
+ cssClass?: string;
274
+ minWidth?: string;
275
+ maxWidth?: string; // Default: '400px'
177
276
  }
178
277
  ```
179
278
 
180
- #### Deferred Loading
279
+ ## Event Handling
181
280
 
182
- Control when the timeline loads data using the `AllowLoad` property:
281
+ ### Preventing Default Behavior
183
282
 
184
283
  ```typescript
185
- import { Component, ViewChild } from '@angular/core';
186
- import { TimelineComponent, TimelineGroup } from '@memberjunction/ng-timeline';
187
-
188
284
  @Component({
189
285
  template: `
190
- <mj-timeline
191
- #timeline
192
- [Groups]="timelineGroups"
193
- [AllowLoad]="false">
286
+ <mj-timeline
287
+ [groups]="groups"
288
+ (beforeEventClick)="onBeforeClick($event)"
289
+ (afterActionClick)="onAction($event)">
194
290
  </mj-timeline>
195
- <button (click)="loadTimeline()">Load Timeline Data</button>
196
291
  `
197
292
  })
198
- export class YourComponent {
199
- @ViewChild('timeline') timeline!: TimelineComponent;
200
- timelineGroups: TimelineGroup[] = [];
201
-
202
- constructor() {
203
- // Configure groups but don't load yet
204
- const group = new TimelineGroup();
205
- group.EntityName = 'Events';
206
- group.TitleFieldName = 'Name';
207
- group.DateFieldName = 'EventDate';
208
- this.timelineGroups = [group];
293
+ export class MyComponent {
294
+ onBeforeClick(args: BeforeEventClickArgs<MyType>) {
295
+ // Prevent click on archived items
296
+ if (args.event.entity.status === 'archived') {
297
+ args.cancel = true;
298
+ this.showToast('Archived items cannot be opened');
299
+ }
209
300
  }
210
301
 
211
- async loadTimeline() {
212
- // Manually trigger data loading
213
- await this.timeline.Refresh();
302
+ onAction(args: AfterActionClickArgs<MyType>) {
303
+ switch (args.action.id) {
304
+ case 'view':
305
+ this.router.navigate(['/items', args.event.entity.id]);
306
+ break;
307
+ case 'edit':
308
+ this.openEditDialog(args.event.entity);
309
+ break;
310
+ }
214
311
  }
215
312
  }
313
+ ```
216
314
 
217
- ## API Reference
315
+ ## Custom Templates
218
316
 
219
- ### TimelineComponent
317
+ Override any part of the card rendering:
220
318
 
221
- #### Inputs
319
+ ```html
320
+ <mj-timeline [groups]="groups">
321
+
322
+ <!-- Full card override -->
323
+ <ng-template #cardTemplate let-event="event" let-group="group">
324
+ <div class="my-card">
325
+ <h3>{{ event.title }}</h3>
326
+ <p>{{ event.description }}</p>
327
+ </div>
328
+ </ng-template>
329
+
330
+ <!-- Just override actions -->
331
+ <ng-template #actionsTemplate let-event="event" let-actions="actions">
332
+ <my-action-bar [event]="event"></my-action-bar>
333
+ </ng-template>
334
+
335
+ <!-- Custom empty state -->
336
+ <ng-template #emptyTemplate>
337
+ <div class="no-data">
338
+ <img src="empty.svg" />
339
+ <p>Nothing here yet!</p>
340
+ </div>
341
+ </ng-template>
342
+
343
+ </mj-timeline>
344
+ ```
222
345
 
223
- | Name | Type | Default | Description |
224
- |------|------|---------|-------------|
225
- | `DisplayOrientation` | `'horizontal' \| 'vertical'` | `'vertical'` | Orientation of the timeline |
226
- | `Groups` | `TimelineGroup[]` | `[]` | Array of groups to display on the timeline |
227
- | `AllowLoad` | `boolean` | `true` | Whether to load data automatically |
346
+ Available templates: `cardTemplate`, `headerTemplate`, `bodyTemplate`, `actionsTemplate`, `segmentHeaderTemplate`, `emptyTemplate`, `loadingTemplate`
228
347
 
229
- #### Methods
348
+ ## Theming
230
349
 
231
- | Name | Parameters | Return Type | Description |
232
- |------|------------|-------------|-------------|
233
- | `Refresh` | None | `Promise<void>` | Refreshes the timeline with current group data |
350
+ Customize via CSS variables:
234
351
 
235
- ### TimelineGroup Class
352
+ ```scss
353
+ mj-timeline {
354
+ // Colors
355
+ --mj-timeline-line-color: #e0e0e0;
356
+ --mj-timeline-marker-bg: #1976d2;
357
+ --mj-timeline-card-bg: #ffffff;
358
+ --mj-timeline-card-border: #e0e0e0;
359
+ --mj-timeline-card-shadow: 0 2px 8px rgba(0,0,0,0.1);
360
+ --mj-timeline-text-primary: #212121;
361
+ --mj-timeline-text-secondary: #757575;
362
+ --mj-timeline-accent: #1976d2;
236
363
 
237
- #### Properties
364
+ // Sizing
365
+ --mj-timeline-card-max-width: 400px;
366
+ --mj-timeline-card-padding: 16px;
367
+ --mj-timeline-marker-size: 14px;
238
368
 
239
- | Name | Type | Default | Description |
240
- |------|------|---------|-------------|
241
- | `EntityName` | `string` | (required) | Entity name for the records to display |
242
- | `DataSourceType` | `'array' \| 'entity'` | `'entity'` | Specifies data source type |
243
- | `Filter` | `string` | (optional) | Filter to apply when loading entity data |
244
- | `EntityObjects` | `BaseEntity[]` | `[]` | Array of entities when using 'array' data source |
245
- | `TitleFieldName` | `string` | (required) | Field name for event titles |
246
- | `DateFieldName` | `string` | (required) | Field name for event dates |
247
- | `DisplayIconMode` | `'standard' \| 'custom'` | `'standard'` | Icon display mode |
248
- | `DisplayIcon` | `string` | (optional) | Custom icon class for events |
249
- | `DisplayColorMode` | `'auto' \| 'manual'` | `'auto'` | Color selection mode |
250
- | `DisplayColor` | `string` | (optional) | Manual color for events |
251
- | `SummaryMode` | `'field' \| 'custom' \| 'none'` | `'field'` | Mode for summary display |
252
- | `SummaryFieldName` | `string` | (optional) | Field name for summary when using 'field' mode (Note: Currently uses TitleFieldName) |
253
- | `SummaryFunction` | `(record: BaseEntity) => string` | (optional) | Function for custom summary generation |
369
+ // Animation
370
+ --mj-timeline-transition: 0.25s ease;
371
+ }
254
372
 
255
- #### Static Methods
373
+ // Dark mode
374
+ .dark-theme mj-timeline {
375
+ --mj-timeline-card-bg: #1e1e1e;
376
+ --mj-timeline-card-border: #424242;
377
+ --mj-timeline-text-primary: #ffffff;
378
+ }
379
+ ```
256
380
 
257
- | Name | Parameters | Return Type | Description |
258
- |------|------------|-------------|-------------|
259
- | `FromView` | `RunViewParams` | `Promise<TimelineGroup>` | Creates a TimelineGroup from RunViewParams |
381
+ ## Visual Layout
260
382
 
261
- ## Styling
383
+ ### Vertical Timeline (Single)
262
384
 
263
- The component uses Kendo UI's Timeline styling with additional customization options:
385
+ ```
386
+ ●─── Dec 2, 2025
387
+
388
+ │ ┌──────────────────────────┐
389
+ │ │ Task Completed │
390
+ │ │ Review timeline design │
391
+ │ │ [View] [Edit] │
392
+ │ └──────────────────────────┘
393
+
394
+ ●─── Nov 28, 2025
395
+
396
+ │ ┌──────────────────────────┐
397
+ │ │ Feature Released │
398
+ │ │ New dashboard v2.5 │
399
+ │ └──────────────────────────┘
400
+ ```
264
401
 
265
- - The timeline component automatically applies alternating layout mode for better visual separation
266
- - Events are displayed with collapsible details for better space utilization
267
- - Custom CSS can be applied by targeting the `.wrapper` class or Kendo UI elements
268
- - Icon and color customization per group allows for visual categorization
402
+ ### Vertical Timeline (Alternating)
269
403
 
270
- ### CSS Example
404
+ ```
405
+ ┌────────────────┐
406
+ │ Task Done │────●─── Dec 2
407
+ └────────────────┘ │
408
+
409
+ Nov 28 ───●────┤
410
+ │ │
411
+ └────┼────┌────────────────┐
412
+ │ │ Released │
413
+ │ └────────────────┘
414
+ ```
271
415
 
272
- ```css
273
- /* Custom timeline styling */
274
- mj-timeline .wrapper {
275
- height: 100%;
276
- padding: 20px;
277
- }
416
+ ### Time Segments
278
417
 
279
- /* Custom event styling */
280
- mj-timeline .k-timeline-event {
281
- /* Your custom styles */
282
- }
418
+ ```
419
+ December 2025 (3 events) ────────────────────
420
+
421
+ │ [Events in December...]
422
+
423
+ ► November 2025 (12 events) ─────────────────── ← Collapsed
283
424
  ```
284
425
 
285
- ## Integration with MemberJunction
426
+ ## Keyboard Navigation
286
427
 
287
- This component is designed to work seamlessly with the MemberJunction framework:
428
+ | Key | Action |
429
+ |-----|--------|
430
+ | `↓` / `→` | Move to next event |
431
+ | `↑` / `←` | Move to previous event |
432
+ | `Enter` / `Space` | Toggle expand/collapse |
433
+ | `Escape` | Collapse focused event |
434
+ | `Home` | Jump to first event |
435
+ | `End` | Jump to last event |
288
436
 
289
- - **Entity System**: Automatically loads and displays data from any MemberJunction entity
290
- - **Metadata Integration**: Leverages MJ's metadata system for entity field access
291
- - **View System**: Supports RunView parameters for flexible data retrieval
292
- - **Container Directives**: Uses `mjFillContainer` directive for responsive layouts
293
- - **Entity Form Dialog**: Can integrate with entity form dialogs for detailed views (future enhancement)
437
+ ## Virtual Scrolling
294
438
 
295
- ## Performance Considerations
439
+ Configure for large datasets:
296
440
 
297
- - **Data Loading**: The component loads all data at once. For large datasets, consider using filters or pagination
298
- - **Multiple Groups**: Each group triggers a separate data query. Consolidate groups when possible
299
- - **Refresh Operations**: The `Refresh()` method reloads all groups. Use judiciously for performance
441
+ ```typescript
442
+ <mj-timeline
443
+ [groups]="groups"
444
+ [virtualScroll]="{
445
+ enabled: true,
446
+ batchSize: 25,
447
+ loadThreshold: 200,
448
+ showLoadingIndicator: true,
449
+ loadingMessage: 'Loading more...'
450
+ }">
451
+ </mj-timeline>
452
+ ```
300
453
 
301
- ## Dependencies
454
+ ## Browser Support
302
455
 
303
- ### Production Dependencies
304
- - @memberjunction/core (^2.43.0)
305
- - @memberjunction/core-entities (^2.43.0)
306
- - @memberjunction/global (^2.43.0)
307
- - @memberjunction/ng-container-directives (^2.43.0)
308
- - @memberjunction/ng-entity-form-dialog (^2.43.0)
309
- - @memberjunction/ng-shared (^2.43.0)
310
- - @progress/kendo-angular-buttons (^16.2.0)
311
- - @progress/kendo-angular-layout (^16.2.0)
312
- - @progress/kendo-angular-indicators (^16.2.0)
313
- - @progress/kendo-angular-scheduler (^16.2.0)
314
- - tslib (^2.3.0)
456
+ - Chrome (last 2 versions)
457
+ - Firefox (last 2 versions)
458
+ - Safari (last 2 versions)
459
+ - Edge (last 2 versions)
460
+ - iOS Safari
461
+ - Android Chrome
315
462
 
316
- ### Peer Dependencies
317
- - @angular/common (^18.0.2)
318
- - @angular/core (^18.0.2)
319
- - @angular/forms (^18.0.2)
320
- - @angular/router (^18.0.2)
463
+ ## Migration from v1
321
464
 
322
- ## Building
465
+ If migrating from the Kendo-based version:
466
+
467
+ 1. **No Kendo dependencies** - Remove all `@progress/kendo-*` packages
468
+ 2. **Property names changed** - `Groups` → `groups`, `DisplayOrientation` → `orientation`
469
+ 3. **New event system** - Replace Kendo events with BeforeX/AfterX pattern
470
+ 4. **Segments are new** - Time-based grouping is now built-in
323
471
 
324
- This package is part of the MemberJunction monorepo. To build:
472
+ ## Building
325
473
 
326
474
  ```bash
327
- # From the package directory
475
+ # From package directory
328
476
  npm run build
329
477
 
330
- # From the monorepo root
478
+ # From monorepo root
331
479
  turbo build --filter="@memberjunction/ng-timeline"
332
480
  ```
333
481
 
334
- ## Future Enhancements
335
-
336
- - Support for pagination and virtual scrolling for large datasets
337
- - Integration with entity form dialogs for inline editing
338
- - Custom event templates for advanced display scenarios
339
- - Export functionality for timeline data
340
- - Real-time updates via entity change notifications
341
-
342
482
  ## License
343
483
 
344
- ISC
484
+ ISC