@memberjunction/ng-timeline 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -393
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
# @memberjunction/ng-timeline
|
|
2
2
|
|
|
3
|
-
A powerful, flexible
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
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
|
|
3
|
+
A powerful, flexible Angular timeline component with zero external dependencies. Works with both MemberJunction BaseEntity objects and plain JavaScript objects. Pure HTML/CSS implementation with virtual scrolling, time-segment grouping, and full keyboard accessibility.
|
|
16
4
|
|
|
17
5
|
## Installation
|
|
18
6
|
|
|
@@ -20,43 +8,40 @@ A powerful, flexible, and fully responsive Angular timeline component. Works wit
|
|
|
20
8
|
npm install @memberjunction/ng-timeline
|
|
21
9
|
```
|
|
22
10
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
| Requirement | Version |
|
|
26
|
-
|------------|---------|
|
|
27
|
-
| Angular | 18+ |
|
|
28
|
-
| TypeScript | 5.0+ |
|
|
29
|
-
| @memberjunction/core | Optional (only for entity data sources) |
|
|
11
|
+
## Overview
|
|
30
12
|
|
|
31
|
-
|
|
13
|
+
The timeline renders chronologically ordered events as cards along a vertical or horizontal axis. Events can be grouped by time segments (day, week, month, quarter, year) with collapsible sections. The component uses a BeforeX/AfterX event pattern that allows container components to intercept and cancel default behaviors.
|
|
32
14
|
|
|
33
15
|
```mermaid
|
|
34
|
-
|
|
35
|
-
subgraph "Data
|
|
36
|
-
A[
|
|
37
|
-
|
|
16
|
+
flowchart TD
|
|
17
|
+
subgraph Data["Data Sources"]
|
|
18
|
+
A["Plain JS Objects"]
|
|
19
|
+
B["MJ BaseEntity Objects"]
|
|
38
20
|
end
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
D[
|
|
42
|
-
E
|
|
43
|
-
D --> G[Virtual Scroll]
|
|
21
|
+
subgraph Config["TimelineGroup Config"]
|
|
22
|
+
C["Field Mappings"]
|
|
23
|
+
D["Display Options"]
|
|
24
|
+
E["Card Config"]
|
|
44
25
|
end
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
I
|
|
50
|
-
J --> L[AfterX Events]
|
|
26
|
+
subgraph Component["TimelineComponent"]
|
|
27
|
+
F["Time Segments"]
|
|
28
|
+
G["Event Cards"]
|
|
29
|
+
H["Virtual Scroll"]
|
|
30
|
+
I["Keyboard Nav"]
|
|
51
31
|
end
|
|
52
32
|
|
|
53
|
-
A -->
|
|
54
|
-
|
|
33
|
+
A --> Config
|
|
34
|
+
B --> Config
|
|
35
|
+
Config --> Component
|
|
36
|
+
|
|
37
|
+
style Data fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
38
|
+
style Config fill:#7c5295,stroke:#563a6b,color:#fff
|
|
39
|
+
style Component fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
55
40
|
```
|
|
56
41
|
|
|
57
|
-
##
|
|
42
|
+
## Usage
|
|
58
43
|
|
|
59
|
-
### Import
|
|
44
|
+
### Module Import
|
|
60
45
|
|
|
61
46
|
```typescript
|
|
62
47
|
import { TimelineModule } from '@memberjunction/ng-timeline';
|
|
@@ -64,10 +49,10 @@ import { TimelineModule } from '@memberjunction/ng-timeline';
|
|
|
64
49
|
@NgModule({
|
|
65
50
|
imports: [TimelineModule]
|
|
66
51
|
})
|
|
67
|
-
export class YourModule {
|
|
52
|
+
export class YourModule {}
|
|
68
53
|
```
|
|
69
54
|
|
|
70
|
-
###
|
|
55
|
+
### With Plain Objects
|
|
71
56
|
|
|
72
57
|
```typescript
|
|
73
58
|
import { TimelineGroup } from '@memberjunction/ng-timeline';
|
|
@@ -79,406 +64,107 @@ interface MyEvent {
|
|
|
79
64
|
description: string;
|
|
80
65
|
}
|
|
81
66
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
ngOnInit() {
|
|
89
|
-
const group = TimelineGroup.FromArray(this.myEvents, {
|
|
90
|
-
titleField: 'title',
|
|
91
|
-
dateField: 'eventDate',
|
|
92
|
-
descriptionField: 'description'
|
|
93
|
-
});
|
|
94
|
-
this.groups = [group];
|
|
95
|
-
}
|
|
96
|
-
}
|
|
67
|
+
const group = TimelineGroup.FromArray(myEvents, {
|
|
68
|
+
titleField: 'title',
|
|
69
|
+
dateField: 'eventDate',
|
|
70
|
+
descriptionField: 'description'
|
|
71
|
+
});
|
|
97
72
|
```
|
|
98
73
|
|
|
99
|
-
|
|
74
|
+
```html
|
|
75
|
+
<mj-timeline [groups]="[group]"></mj-timeline>
|
|
76
|
+
```
|
|
100
77
|
|
|
101
|
-
|
|
102
|
-
import { TimelineGroup } from '@memberjunction/ng-timeline';
|
|
103
|
-
import { TaskEntity } from '@memberjunction/core-entities';
|
|
78
|
+
### With MemberJunction Entities
|
|
104
79
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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];
|
|
119
|
-
}
|
|
120
|
-
}
|
|
80
|
+
```typescript
|
|
81
|
+
const group = new TimelineGroup<TaskEntity>();
|
|
82
|
+
group.EntityName = 'Tasks';
|
|
83
|
+
group.DataSourceType = 'entity';
|
|
84
|
+
group.Filter = "Status = 'Open'";
|
|
85
|
+
group.TitleFieldName = 'Name';
|
|
86
|
+
group.DateFieldName = 'DueDate';
|
|
121
87
|
```
|
|
122
88
|
|
|
123
|
-
##
|
|
124
|
-
|
|
125
|
-
### Inputs
|
|
89
|
+
## Key Inputs
|
|
126
90
|
|
|
127
91
|
| Input | Type | Default | Description |
|
|
128
92
|
|-------|------|---------|-------------|
|
|
129
93
|
| `groups` | `TimelineGroup<T>[]` | `[]` | Data groups to display |
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `layout` | `'single' \| 'alternating'` | `'single'` | Card layout (vertical only) |
|
|
94
|
+
| `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Timeline axis |
|
|
95
|
+
| `layout` | `'single' \| 'alternating'` | `'single'` | Card layout |
|
|
133
96
|
| `sortOrder` | `'asc' \| 'desc'` | `'desc'` | Event sort order |
|
|
134
|
-
| `segmentGrouping` | `'none' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'` | `'month'` | Time
|
|
97
|
+
| `segmentGrouping` | `'none' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'` | `'month'` | Time grouping |
|
|
135
98
|
| `segmentsCollapsible` | `boolean` | `true` | Allow collapsing segments |
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
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
|
|
99
|
+
| `enableKeyboardNavigation` | `boolean` | `true` | Keyboard support |
|
|
100
|
+
| `virtualScroll` | `VirtualScrollConfig` | -- | Virtual scroll settings |
|
|
167
101
|
|
|
168
|
-
|
|
169
|
-
// Refresh all data
|
|
170
|
-
await timeline.refresh();
|
|
102
|
+
## Event System (BeforeX/AfterX Pattern)
|
|
171
103
|
|
|
172
|
-
|
|
173
|
-
await timeline.loadMore();
|
|
104
|
+
Every user interaction emits a "before" event (cancelable) and an "after" event:
|
|
174
105
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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();
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
## TimelineGroup Configuration
|
|
106
|
+
| Before Event | After Event | Description |
|
|
107
|
+
|-------------|-------------|-------------|
|
|
108
|
+
| `beforeEventClick` | `afterEventClick` | Card clicked |
|
|
109
|
+
| `beforeEventExpand` | `afterEventExpand` | Card expanded |
|
|
110
|
+
| `beforeEventCollapse` | `afterEventCollapse` | Card collapsed |
|
|
111
|
+
| `beforeActionClick` | `afterActionClick` | Action button clicked |
|
|
112
|
+
| `beforeSegmentExpand` | `afterSegmentExpand` | Segment expanded |
|
|
113
|
+
| `beforeSegmentCollapse` | `afterSegmentCollapse` | Segment collapsed |
|
|
114
|
+
| `beforeLoad` | `afterLoad` | Data loaded |
|
|
197
115
|
|
|
198
116
|
```typescript
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
//
|
|
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
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
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'
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
## Event Handling
|
|
280
|
-
|
|
281
|
-
### Preventing Default Behavior
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
@Component({
|
|
285
|
-
template: `
|
|
286
|
-
<mj-timeline
|
|
287
|
-
[groups]="groups"
|
|
288
|
-
(beforeEventClick)="onBeforeClick($event)"
|
|
289
|
-
(afterActionClick)="onAction($event)">
|
|
290
|
-
</mj-timeline>
|
|
291
|
-
`
|
|
292
|
-
})
|
|
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
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
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
|
-
}
|
|
117
|
+
onBeforeClick(args: BeforeEventClickArgs<MyType>) {
|
|
118
|
+
if (args.event.entity.status === 'archived') {
|
|
119
|
+
args.cancel = true; // Prevent default behavior
|
|
311
120
|
}
|
|
312
121
|
}
|
|
313
122
|
```
|
|
314
123
|
|
|
315
124
|
## Custom Templates
|
|
316
125
|
|
|
317
|
-
Override
|
|
126
|
+
Override card rendering with Angular templates:
|
|
318
127
|
|
|
319
128
|
```html
|
|
320
129
|
<mj-timeline [groups]="groups">
|
|
321
|
-
|
|
322
|
-
|
|
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>
|
|
130
|
+
<ng-template #cardTemplate let-event="event">
|
|
131
|
+
<div class="custom-card">{{ event.title }}</div>
|
|
328
132
|
</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
133
|
<ng-template #emptyTemplate>
|
|
337
|
-
<
|
|
338
|
-
<img src="empty.svg" />
|
|
339
|
-
<p>Nothing here yet!</p>
|
|
340
|
-
</div>
|
|
134
|
+
<p>No events found.</p>
|
|
341
135
|
</ng-template>
|
|
342
|
-
|
|
343
136
|
</mj-timeline>
|
|
344
137
|
```
|
|
345
138
|
|
|
346
|
-
Available
|
|
347
|
-
|
|
348
|
-
## Theming
|
|
139
|
+
Available template refs: `cardTemplate`, `headerTemplate`, `bodyTemplate`, `actionsTemplate`, `segmentHeaderTemplate`, `emptyTemplate`, `loadingTemplate`
|
|
349
140
|
|
|
350
|
-
|
|
141
|
+
## Theming (CSS Variables)
|
|
351
142
|
|
|
352
|
-
```
|
|
143
|
+
```css
|
|
353
144
|
mj-timeline {
|
|
354
|
-
// Colors
|
|
355
145
|
--mj-timeline-line-color: #e0e0e0;
|
|
356
146
|
--mj-timeline-marker-bg: #1976d2;
|
|
357
147
|
--mj-timeline-card-bg: #ffffff;
|
|
358
148
|
--mj-timeline-card-border: #e0e0e0;
|
|
359
|
-
--mj-timeline-card-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
360
149
|
--mj-timeline-text-primary: #212121;
|
|
361
|
-
--mj-timeline-text-secondary: #757575;
|
|
362
150
|
--mj-timeline-accent: #1976d2;
|
|
363
|
-
|
|
364
|
-
// Sizing
|
|
365
151
|
--mj-timeline-card-max-width: 400px;
|
|
366
|
-
--mj-timeline-card-padding: 16px;
|
|
367
|
-
--mj-timeline-marker-size: 14px;
|
|
368
|
-
|
|
369
|
-
// Animation
|
|
370
|
-
--mj-timeline-transition: 0.25s ease;
|
|
371
|
-
}
|
|
372
|
-
|
|
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
152
|
}
|
|
379
153
|
```
|
|
380
154
|
|
|
381
|
-
## Visual Layout
|
|
382
|
-
|
|
383
|
-
### Vertical Timeline (Single)
|
|
384
|
-
|
|
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
|
-
```
|
|
401
|
-
|
|
402
|
-
### Vertical Timeline (Alternating)
|
|
403
|
-
|
|
404
|
-
```
|
|
405
|
-
┌────────────────┐
|
|
406
|
-
│ Task Done │────●─── Dec 2
|
|
407
|
-
└────────────────┘ │
|
|
408
|
-
│
|
|
409
|
-
Nov 28 ───●────┤
|
|
410
|
-
│ │
|
|
411
|
-
└────┼────┌────────────────┐
|
|
412
|
-
│ │ Released │
|
|
413
|
-
│ └────────────────┘
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
### Time Segments
|
|
417
|
-
|
|
418
|
-
```
|
|
419
|
-
▼ December 2025 (3 events) ────────────────────
|
|
420
|
-
│
|
|
421
|
-
│ [Events in December...]
|
|
422
|
-
│
|
|
423
|
-
► November 2025 (12 events) ─────────────────── ← Collapsed
|
|
424
|
-
```
|
|
425
|
-
|
|
426
155
|
## Keyboard Navigation
|
|
427
156
|
|
|
428
157
|
| Key | Action |
|
|
429
158
|
|-----|--------|
|
|
430
|
-
|
|
|
431
|
-
|
|
|
432
|
-
|
|
|
433
|
-
|
|
|
434
|
-
|
|
|
435
|
-
| `End` | Jump to last event |
|
|
436
|
-
|
|
437
|
-
## Virtual Scrolling
|
|
438
|
-
|
|
439
|
-
Configure for large datasets:
|
|
440
|
-
|
|
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
|
-
```
|
|
159
|
+
| Arrow Down/Right | Next event |
|
|
160
|
+
| Arrow Up/Left | Previous event |
|
|
161
|
+
| Enter/Space | Toggle expand/collapse |
|
|
162
|
+
| Escape | Collapse focused event |
|
|
163
|
+
| Home/End | Jump to first/last event |
|
|
453
164
|
|
|
454
|
-
##
|
|
455
|
-
|
|
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
|
|
462
|
-
|
|
463
|
-
## Migration from v1
|
|
464
|
-
|
|
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
|
|
471
|
-
|
|
472
|
-
## Building
|
|
473
|
-
|
|
474
|
-
```bash
|
|
475
|
-
# From package directory
|
|
476
|
-
npm run build
|
|
477
|
-
|
|
478
|
-
# From monorepo root
|
|
479
|
-
turbo build --filter="@memberjunction/ng-timeline"
|
|
480
|
-
```
|
|
165
|
+
## Dependencies
|
|
481
166
|
|
|
482
|
-
|
|
167
|
+
No external UI library dependencies. Pure Angular + HTML/CSS implementation.
|
|
483
168
|
|
|
484
|
-
|
|
169
|
+
- [@memberjunction/core](../../MJCore/README.md) -- Optional, for entity data sources
|
|
170
|
+
- [@memberjunction/core-entities](../../MJCoreEntities/README.md) -- Optional, for entity types
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-timeline",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "MemberJunction: Responsive timeline component for Angular. Works with MemberJunction entities or plain JavaScript objects. No external dependencies.",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@memberjunction/core": "4.
|
|
38
|
+
"@memberjunction/core": "4.1.0",
|
|
39
39
|
"rxjs": "~7.8.2",
|
|
40
40
|
"tslib": "^2.8.1"
|
|
41
41
|
},
|