@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 +385 -245
- package/dist/lib/component/timeline.component.d.ts +377 -60
- package/dist/lib/component/timeline.component.d.ts.map +1 -1
- package/dist/lib/component/timeline.component.js +1904 -132
- package/dist/lib/component/timeline.component.js.map +1 -1
- package/dist/lib/events.d.ts +440 -0
- package/dist/lib/events.d.ts.map +1 -0
- package/dist/lib/events.js +11 -0
- package/dist/lib/events.js.map +1 -0
- package/dist/lib/module.d.ts +14 -6
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/module.js +24 -26
- package/dist/lib/module.js.map +1 -1
- package/dist/lib/timeline-group.d.ts +387 -0
- package/dist/lib/timeline-group.d.ts.map +1 -0
- package/dist/lib/timeline-group.js +523 -0
- package/dist/lib/timeline-group.js.map +1 -0
- package/dist/lib/types.d.ts +491 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +49 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/public-api.d.ts +12 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +15 -2
- package/dist/public-api.js.map +1 -1
- package/package.json +19 -17
package/README.md
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# @memberjunction/ng-timeline
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
57
|
+
## Quick Start
|
|
34
58
|
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
###
|
|
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 {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
196
|
+
## TimelineGroup Configuration
|
|
156
197
|
|
|
157
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
279
|
+
## Event Handling
|
|
181
280
|
|
|
182
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
##
|
|
315
|
+
## Custom Templates
|
|
218
316
|
|
|
219
|
-
|
|
317
|
+
Override any part of the card rendering:
|
|
220
318
|
|
|
221
|
-
|
|
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
|
-
|
|
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
|
-
|
|
348
|
+
## Theming
|
|
230
349
|
|
|
231
|
-
|
|
232
|
-
|------|------------|-------------|-------------|
|
|
233
|
-
| `Refresh` | None | `Promise<void>` | Refreshes the timeline with current group data |
|
|
350
|
+
Customize via CSS variables:
|
|
234
351
|
|
|
235
|
-
|
|
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
|
-
|
|
364
|
+
// Sizing
|
|
365
|
+
--mj-timeline-card-max-width: 400px;
|
|
366
|
+
--mj-timeline-card-padding: 16px;
|
|
367
|
+
--mj-timeline-marker-size: 14px;
|
|
238
368
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
258
|
-
|------|------------|-------------|-------------|
|
|
259
|
-
| `FromView` | `RunViewParams` | `Promise<TimelineGroup>` | Creates a TimelineGroup from RunViewParams |
|
|
381
|
+
## Visual Layout
|
|
260
382
|
|
|
261
|
-
|
|
383
|
+
### Vertical Timeline (Single)
|
|
262
384
|
|
|
263
|
-
|
|
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
|
-
|
|
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
|
-
|
|
404
|
+
```
|
|
405
|
+
┌────────────────┐
|
|
406
|
+
│ Task Done │────●─── Dec 2
|
|
407
|
+
└────────────────┘ │
|
|
408
|
+
│
|
|
409
|
+
Nov 28 ───●────┤
|
|
410
|
+
│ │
|
|
411
|
+
└────┼────┌────────────────┐
|
|
412
|
+
│ │ Released │
|
|
413
|
+
│ └────────────────┘
|
|
414
|
+
```
|
|
271
415
|
|
|
272
|
-
|
|
273
|
-
/* Custom timeline styling */
|
|
274
|
-
mj-timeline .wrapper {
|
|
275
|
-
height: 100%;
|
|
276
|
-
padding: 20px;
|
|
277
|
-
}
|
|
416
|
+
### Time Segments
|
|
278
417
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
418
|
+
```
|
|
419
|
+
▼ December 2025 (3 events) ────────────────────
|
|
420
|
+
│
|
|
421
|
+
│ [Events in December...]
|
|
422
|
+
│
|
|
423
|
+
► November 2025 (12 events) ─────────────────── ← Collapsed
|
|
283
424
|
```
|
|
284
425
|
|
|
285
|
-
##
|
|
426
|
+
## Keyboard Navigation
|
|
286
427
|
|
|
287
|
-
|
|
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
|
-
|
|
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
|
-
|
|
439
|
+
Configure for large datasets:
|
|
296
440
|
|
|
297
|
-
|
|
298
|
-
-
|
|
299
|
-
|
|
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
|
-
##
|
|
454
|
+
## Browser Support
|
|
302
455
|
|
|
303
|
-
|
|
304
|
-
-
|
|
305
|
-
-
|
|
306
|
-
-
|
|
307
|
-
-
|
|
308
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
472
|
+
## Building
|
|
325
473
|
|
|
326
474
|
```bash
|
|
327
|
-
# From
|
|
475
|
+
# From package directory
|
|
328
476
|
npm run build
|
|
329
477
|
|
|
330
|
-
# From
|
|
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
|