@kteneyck/cesium-timeline-angular 0.9.0 → 0.10.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.
@@ -19,6 +19,10 @@ class TimelineControlsComponent {
19
19
  theme;
20
20
  swimLanesVisible;
21
21
  labels;
22
+ liveButtonSize = 'md';
23
+ liveButtonPosition = 'left';
24
+ /** @see TimelineBaseProps.live */
25
+ live = false;
22
26
  dateTimeClick = new EventEmitter();
23
27
  playPause = new EventEmitter();
24
28
  jumpToStart = new EventEmitter();
@@ -36,6 +40,12 @@ class TimelineControlsComponent {
36
40
  get isNormalSpeed() { return this.multiplier === 1; }
37
41
  get absMultiplier() { return Math.abs(this.multiplier); }
38
42
  get hasSwimLaneToggle() { return this.swimLanesVisible != null; }
43
+ static LIVE_SIZE_MAP = {
44
+ sm: { width: 44, height: 18, fontSize: '10px', dot: 5, borderRadius: '3px' },
45
+ md: { width: 56, height: 22, fontSize: '11px', dot: 6, borderRadius: '3px' },
46
+ lg: { width: 72, height: 30, fontSize: '13px', dot: 8, borderRadius: '4px' },
47
+ };
48
+ get liveSize() { return TimelineControlsComponent.LIVE_SIZE_MAP[this.liveButtonSize]; }
39
49
  get timeFormat() { return splitForDisplay(this.dateTimeFormat).timeFormat; }
40
50
  get dateFormat() { return splitForDisplay(this.dateTimeFormat).dateFormat; }
41
51
  get formattedTime() { return formatDateTime(this.currentTime, this.timeFormat, this.timezone); }
@@ -58,7 +68,7 @@ class TimelineControlsComponent {
58
68
  this.ro?.disconnect();
59
69
  }
60
70
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: TimelineControlsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
61
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: TimelineControlsComponent, isStandalone: true, selector: "ct-timeline-controls", inputs: { currentTime: "currentTime", isPlaying: "isPlaying", multiplier: "multiplier", dateTimeFormat: "dateTimeFormat", timezone: "timezone", isLive: "isLive", hasStartTime: "hasStartTime", hasEndTime: "hasEndTime", showJumpToStart: "showJumpToStart", showJumpToEnd: "showJumpToEnd", theme: "theme", swimLanesVisible: "swimLanesVisible", labels: "labels" }, outputs: { dateTimeClick: "dateTimeClick", playPause: "playPause", jumpToStart: "jumpToStart", rewind: "rewind", fastForward: "fastForward", jumpToEnd: "jumpToEnd", jumpToLive: "jumpToLive", resetSpeed: "resetSpeed", toggleSwimLanes: "toggleSwimLanes" }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true }], ngImport: i0, template: `
71
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: TimelineControlsComponent, isStandalone: true, selector: "ct-timeline-controls", inputs: { currentTime: "currentTime", isPlaying: "isPlaying", multiplier: "multiplier", dateTimeFormat: "dateTimeFormat", timezone: "timezone", isLive: "isLive", hasStartTime: "hasStartTime", hasEndTime: "hasEndTime", showJumpToStart: "showJumpToStart", showJumpToEnd: "showJumpToEnd", theme: "theme", swimLanesVisible: "swimLanesVisible", labels: "labels", liveButtonSize: "liveButtonSize", liveButtonPosition: "liveButtonPosition", live: "live" }, outputs: { dateTimeClick: "dateTimeClick", playPause: "playPause", jumpToStart: "jumpToStart", rewind: "rewind", fastForward: "fastForward", jumpToEnd: "jumpToEnd", jumpToLive: "jumpToLive", resetSpeed: "resetSpeed", toggleSwimLanes: "toggleSwimLanes" }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true }], ngImport: i0, template: `
62
72
  <div
63
73
  #container
64
74
  [style.display]="isNarrow ? 'flex' : 'grid'"
@@ -69,14 +79,14 @@ class TimelineControlsComponent {
69
79
  [style.border-bottom]="'1px solid ' + theme.controlBarBorder"
70
80
  [style.font-family]="'system-ui, -apple-system, sans-serif'"
71
81
  >
72
- <!-- Left: Datetime + LIVE + speed badge -->
82
+ <!-- Left: Datetime + LIVE (if position=left) -->
73
83
  <div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
74
84
  <div
75
- (click)="dateTimeClick.emit()"
76
- [title]="dateTimeClick.observed ? l.dateTimeClickTooltip : ''"
85
+ (click)="!live && dateTimeClick.emit()"
86
+ [title]="(!live && dateTimeClick.observed) ? l.dateTimeClickTooltip : ''"
77
87
  [style.color]="theme.labelColor"
78
88
  style="font-family:monospace;line-height:1.15;border-radius:4px;padding:2px 4px;transition:background 0.15s"
79
- [style.cursor]="dateTimeClick.observed ? 'pointer' : 'default'"
89
+ [style.cursor]="(!live && dateTimeClick.observed) ? 'pointer' : 'default'"
80
90
  >
81
91
  @if (timeFormat) {
82
92
  <div style="font-size:2em;font-weight:bold;letter-spacing:0.02em">
@@ -98,35 +108,47 @@ class TimelineControlsComponent {
98
108
  }
99
109
  </div>
100
110
 
101
- <div style="display:flex;flex-direction:column;gap:2px;justify-content:center">
102
- <!-- LIVE button -->
103
- <button
104
- (click)="jumpToLive.emit()"
105
- [style.color]="isLive ? theme.controlBarBackground : theme.buttonActiveColor"
106
- [style.background-color]="isLive ? theme.buttonActiveColor : 'transparent'"
107
- [style.border-color]="theme.buttonActiveColor"
108
- [style.opacity]="isLive ? 1 : 0.55"
109
- style="background:none;border:1px solid;cursor:pointer;font-size:11px;font-weight:bold;letter-spacing:0.05em;width:52px;min-width:52px;height:20px;border-radius:3px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:opacity 0.15s"
110
- [title]="isLive ? l.liveActiveTooltip : l.liveTooltip"
111
- >
112
- {{ isLive ? l.liveActiveLabel : l.liveLabel }}
113
- </button>
114
-
115
- <!-- Speed badge -->
116
- <div style="height:20px;display:flex;align-items:center">
117
- @if (!isNormalSpeed) {
111
+ @if (liveButtonPosition === 'left') {
112
+ <div style="display:flex;align-items:center;gap:4px">
113
+ <button
114
+ (click)="!live && jumpToLive.emit()"
115
+ [style.color]="(live || isLive) ? theme.controlBarBackground : theme.buttonActiveColor"
116
+ [style.background-color]="(live || isLive) ? theme.buttonActiveColor : 'transparent'"
117
+ [style.border-color]="theme.buttonActiveColor"
118
+ [style.opacity]="1"
119
+ [style.width.px]="liveSize.width"
120
+ [style.min-width.px]="liveSize.width"
121
+ [style.height.px]="liveSize.height"
122
+ [style.font-size]="liveSize.fontSize"
123
+ [style.border-radius]="liveSize.borderRadius"
124
+ [style.cursor]="live ? 'default' : 'pointer'"
125
+ style="background:none;border:1px solid;font-weight:bold;letter-spacing:0.05em;display:flex;align-items:center;justify-content:center;padding:0;gap:4px;font-family:system-ui,-apple-system,sans-serif;transition:opacity 0.15s"
126
+ [title]="(live || isLive) ? l.liveActiveTooltip : l.liveTooltip"
127
+ >
128
+ @if (live || isLive) {
129
+ <span
130
+ [style.width.px]="liveSize.dot"
131
+ [style.height.px]="liveSize.dot"
132
+ [style.background-color]="theme.liveDotColor"
133
+ style="border-radius:50%;display:inline-block;flex-shrink:0"
134
+ ></span>
135
+ }
136
+ {{ (live || isLive) ? l.liveActiveLabel : l.liveLabel }}
137
+ </button>
138
+ @if (!isNormalSpeed && !live) {
118
139
  <button
119
140
  (click)="resetSpeed.emit()"
120
141
  [style.color]="theme.buttonActiveColor"
121
142
  [style.border-color]="theme.buttonActiveColor + '44'"
122
- style="background:none;border:1px solid;cursor:pointer;font-size:11px;width:52px;min-width:52px;height:20px;border-radius:4px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:background-color 0.15s"
143
+ [style.width.px]="liveSize.width"
144
+ [style.min-width.px]="liveSize.width"
145
+ [style.height.px]="liveSize.height"
146
+ style="background:none;border:1px solid;cursor:pointer;font-size:11px;border-radius:4px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:background-color 0.15s"
123
147
  [title]="l.resetSpeedTooltip"
124
- >
125
- {{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}
126
- </button>
148
+ >{{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}</button>
127
149
  }
128
150
  </div>
129
- </div>
151
+ }
130
152
  </div>
131
153
 
132
154
  <!-- Center: Transport buttons -->
@@ -135,7 +157,7 @@ class TimelineControlsComponent {
135
157
  [style.flex]="isNarrow ? '1' : undefined"
136
158
  [style.justify-content]="isNarrow ? 'center' : undefined"
137
159
  >
138
- @if (showJumpToStart !== false) {
160
+ @if (!live && showJumpToStart !== false) {
139
161
  <button
140
162
  (click)="hasStartTime && jumpToStart.emit()"
141
163
  [disabled]="!hasStartTime"
@@ -147,53 +169,59 @@ class TimelineControlsComponent {
147
169
  >⏮</button>
148
170
  }
149
171
 
150
- <button
151
- (click)="rewind.emit()"
152
- [style.color]="isRewinding ? theme.buttonActiveColor : theme.buttonColor"
153
- [style.border-color]="isRewinding ? theme.buttonActiveColor + '33' : 'transparent'"
154
- class="ct-btn ct-btn-wide"
155
- [title]="isRewinding ? resolveRewindActive(absMultiplier) : l.rewindTooltip"
156
- >
157
- @if (isRewinding) {
158
- <span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>◀◀
159
- } @else {
160
- ◀◀
161
- }
162
- </button>
172
+ @if (!live) {
173
+ <button
174
+ (click)="rewind.emit()"
175
+ [style.color]="isRewinding ? theme.buttonActiveColor : theme.buttonColor"
176
+ [style.border-color]="isRewinding ? theme.buttonActiveColor + '33' : 'transparent'"
177
+ class="ct-btn ct-btn-wide"
178
+ [title]="isRewinding ? resolveRewindActive(absMultiplier) : l.rewindTooltip"
179
+ >
180
+ @if (isRewinding) {
181
+ <span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>◀◀
182
+ } @else {
183
+ ◀◀
184
+ }
185
+ </button>
186
+ }
163
187
 
164
- <button
165
- (click)="playPause.emit(!isPlaying)"
166
- [style.color]="theme.buttonActiveColor"
167
- [style.border-color]="theme.buttonActiveColor + '55'"
168
- [style.padding-left]="isPlaying ? '0' : '2px'"
169
- class="ct-btn ct-btn-play"
170
- [title]="isPlaying ? l.pauseTooltip : (isRewinding ? l.playFromRewindTooltip : l.playTooltip)"
171
- >
172
- @if (isPlaying) {
173
- <svg width="14" height="16" viewBox="0 0 14 16" fill="currentColor">
174
- <rect x="1" y="0" width="4" height="16" rx="1"/>
175
- <rect x="9" y="0" width="4" height="16" rx="1"/>
176
- </svg>
177
- } @else {
178
-
179
- }
180
- </button>
188
+ @if (!live) {
189
+ <button
190
+ (click)="playPause.emit(!isPlaying)"
191
+ [style.color]="theme.buttonActiveColor"
192
+ [style.border-color]="theme.buttonActiveColor + '55'"
193
+ [style.padding-left]="isPlaying ? '0' : '2px'"
194
+ class="ct-btn ct-btn-play"
195
+ [title]="isPlaying ? l.pauseTooltip : (isRewinding ? l.playFromRewindTooltip : l.playTooltip)"
196
+ >
197
+ @if (isPlaying) {
198
+ <svg width="14" height="16" viewBox="0 0 14 16" fill="currentColor">
199
+ <rect x="1" y="0" width="4" height="16" rx="1"/>
200
+ <rect x="9" y="0" width="4" height="16" rx="1"/>
201
+ </svg>
202
+ } @else {
203
+
204
+ }
205
+ </button>
206
+ }
181
207
 
182
- <button
183
- (click)="fastForward.emit()"
184
- [style.color]="isFastForward ? theme.buttonActiveColor : theme.buttonColor"
185
- [style.border-color]="isFastForward ? theme.buttonActiveColor + '33' : 'transparent'"
186
- class="ct-btn ct-btn-wide"
187
- [title]="isFastForward ? resolveFastForwardActive(absMultiplier) : l.fastForwardTooltip"
188
- >
189
- @if (isFastForward) {
190
- ▶▶<span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>
191
- } @else {
192
- ▶▶
193
- }
194
- </button>
208
+ @if (!live) {
209
+ <button
210
+ (click)="fastForward.emit()"
211
+ [style.color]="isFastForward ? theme.buttonActiveColor : theme.buttonColor"
212
+ [style.border-color]="isFastForward ? theme.buttonActiveColor + '33' : 'transparent'"
213
+ class="ct-btn ct-btn-wide"
214
+ [title]="isFastForward ? resolveFastForwardActive(absMultiplier) : l.fastForwardTooltip"
215
+ >
216
+ @if (isFastForward) {
217
+ ▶▶<span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>
218
+ } @else {
219
+ ▶▶
220
+ }
221
+ </button>
222
+ }
195
223
 
196
- @if (showJumpToEnd !== false) {
224
+ @if (!live && showJumpToEnd !== false) {
197
225
  <button
198
226
  (click)="hasEndTime && jumpToEnd.emit()"
199
227
  [disabled]="!hasEndTime"
@@ -206,9 +234,50 @@ class TimelineControlsComponent {
206
234
  }
207
235
  </div>
208
236
 
209
- <!-- Right: swim-lane toggle -->
237
+ <!-- Right: LIVE (if position=right) + swim-lane toggle -->
210
238
  @if (!isNarrow) {
211
- <div style="display:flex;justify-content:flex-end;align-items:center">
239
+ <div style="display:flex;justify-content:flex-end;align-items:center;gap:8px">
240
+ @if (liveButtonPosition === 'right') {
241
+ <div style="display:flex;align-items:center;gap:4px">
242
+ <button
243
+ (click)="!live && jumpToLive.emit()"
244
+ [style.color]="(live || isLive) ? theme.controlBarBackground : theme.buttonActiveColor"
245
+ [style.background-color]="(live || isLive) ? theme.buttonActiveColor : 'transparent'"
246
+ [style.border-color]="theme.buttonActiveColor"
247
+ [style.opacity]="1"
248
+ [style.width.px]="liveSize.width"
249
+ [style.min-width.px]="liveSize.width"
250
+ [style.height.px]="liveSize.height"
251
+ [style.font-size]="liveSize.fontSize"
252
+ [style.border-radius]="liveSize.borderRadius"
253
+ [style.cursor]="live ? 'default' : 'pointer'"
254
+ style="background:none;border:1px solid;font-weight:bold;letter-spacing:0.05em;display:flex;align-items:center;justify-content:center;padding:0;gap:4px;font-family:system-ui,-apple-system,sans-serif;transition:opacity 0.15s"
255
+ [title]="(live || isLive) ? l.liveActiveTooltip : l.liveTooltip"
256
+ >
257
+ @if (live || isLive) {
258
+ <span
259
+ [style.width.px]="liveSize.dot"
260
+ [style.height.px]="liveSize.dot"
261
+ [style.background-color]="theme.liveDotColor"
262
+ style="border-radius:50%;display:inline-block;flex-shrink:0"
263
+ ></span>
264
+ }
265
+ {{ (live || isLive) ? l.liveActiveLabel : l.liveLabel }}
266
+ </button>
267
+ @if (!isNormalSpeed && !live) {
268
+ <button
269
+ (click)="resetSpeed.emit()"
270
+ [style.color]="theme.buttonActiveColor"
271
+ [style.border-color]="theme.buttonActiveColor + '44'"
272
+ [style.width.px]="liveSize.width"
273
+ [style.min-width.px]="liveSize.width"
274
+ [style.height.px]="liveSize.height"
275
+ style="background:none;border:1px solid;cursor:pointer;font-size:11px;border-radius:4px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:background-color 0.15s"
276
+ [title]="l.resetSpeedTooltip"
277
+ >{{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}</button>
278
+ }
279
+ </div>
280
+ }
212
281
  @if (hasSwimLaneToggle) {
213
282
  <button
214
283
  (click)="toggleSwimLanes.emit()"
@@ -263,14 +332,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
263
332
  [style.border-bottom]="'1px solid ' + theme.controlBarBorder"
264
333
  [style.font-family]="'system-ui, -apple-system, sans-serif'"
265
334
  >
266
- <!-- Left: Datetime + LIVE + speed badge -->
335
+ <!-- Left: Datetime + LIVE (if position=left) -->
267
336
  <div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
268
337
  <div
269
- (click)="dateTimeClick.emit()"
270
- [title]="dateTimeClick.observed ? l.dateTimeClickTooltip : ''"
338
+ (click)="!live && dateTimeClick.emit()"
339
+ [title]="(!live && dateTimeClick.observed) ? l.dateTimeClickTooltip : ''"
271
340
  [style.color]="theme.labelColor"
272
341
  style="font-family:monospace;line-height:1.15;border-radius:4px;padding:2px 4px;transition:background 0.15s"
273
- [style.cursor]="dateTimeClick.observed ? 'pointer' : 'default'"
342
+ [style.cursor]="(!live && dateTimeClick.observed) ? 'pointer' : 'default'"
274
343
  >
275
344
  @if (timeFormat) {
276
345
  <div style="font-size:2em;font-weight:bold;letter-spacing:0.02em">
@@ -292,35 +361,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
292
361
  }
293
362
  </div>
294
363
 
295
- <div style="display:flex;flex-direction:column;gap:2px;justify-content:center">
296
- <!-- LIVE button -->
297
- <button
298
- (click)="jumpToLive.emit()"
299
- [style.color]="isLive ? theme.controlBarBackground : theme.buttonActiveColor"
300
- [style.background-color]="isLive ? theme.buttonActiveColor : 'transparent'"
301
- [style.border-color]="theme.buttonActiveColor"
302
- [style.opacity]="isLive ? 1 : 0.55"
303
- style="background:none;border:1px solid;cursor:pointer;font-size:11px;font-weight:bold;letter-spacing:0.05em;width:52px;min-width:52px;height:20px;border-radius:3px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:opacity 0.15s"
304
- [title]="isLive ? l.liveActiveTooltip : l.liveTooltip"
305
- >
306
- {{ isLive ? l.liveActiveLabel : l.liveLabel }}
307
- </button>
308
-
309
- <!-- Speed badge -->
310
- <div style="height:20px;display:flex;align-items:center">
311
- @if (!isNormalSpeed) {
364
+ @if (liveButtonPosition === 'left') {
365
+ <div style="display:flex;align-items:center;gap:4px">
366
+ <button
367
+ (click)="!live && jumpToLive.emit()"
368
+ [style.color]="(live || isLive) ? theme.controlBarBackground : theme.buttonActiveColor"
369
+ [style.background-color]="(live || isLive) ? theme.buttonActiveColor : 'transparent'"
370
+ [style.border-color]="theme.buttonActiveColor"
371
+ [style.opacity]="1"
372
+ [style.width.px]="liveSize.width"
373
+ [style.min-width.px]="liveSize.width"
374
+ [style.height.px]="liveSize.height"
375
+ [style.font-size]="liveSize.fontSize"
376
+ [style.border-radius]="liveSize.borderRadius"
377
+ [style.cursor]="live ? 'default' : 'pointer'"
378
+ style="background:none;border:1px solid;font-weight:bold;letter-spacing:0.05em;display:flex;align-items:center;justify-content:center;padding:0;gap:4px;font-family:system-ui,-apple-system,sans-serif;transition:opacity 0.15s"
379
+ [title]="(live || isLive) ? l.liveActiveTooltip : l.liveTooltip"
380
+ >
381
+ @if (live || isLive) {
382
+ <span
383
+ [style.width.px]="liveSize.dot"
384
+ [style.height.px]="liveSize.dot"
385
+ [style.background-color]="theme.liveDotColor"
386
+ style="border-radius:50%;display:inline-block;flex-shrink:0"
387
+ ></span>
388
+ }
389
+ {{ (live || isLive) ? l.liveActiveLabel : l.liveLabel }}
390
+ </button>
391
+ @if (!isNormalSpeed && !live) {
312
392
  <button
313
393
  (click)="resetSpeed.emit()"
314
394
  [style.color]="theme.buttonActiveColor"
315
395
  [style.border-color]="theme.buttonActiveColor + '44'"
316
- style="background:none;border:1px solid;cursor:pointer;font-size:11px;width:52px;min-width:52px;height:20px;border-radius:4px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:background-color 0.15s"
396
+ [style.width.px]="liveSize.width"
397
+ [style.min-width.px]="liveSize.width"
398
+ [style.height.px]="liveSize.height"
399
+ style="background:none;border:1px solid;cursor:pointer;font-size:11px;border-radius:4px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:background-color 0.15s"
317
400
  [title]="l.resetSpeedTooltip"
318
- >
319
- {{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}
320
- </button>
401
+ >{{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}</button>
321
402
  }
322
403
  </div>
323
- </div>
404
+ }
324
405
  </div>
325
406
 
326
407
  <!-- Center: Transport buttons -->
@@ -329,7 +410,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
329
410
  [style.flex]="isNarrow ? '1' : undefined"
330
411
  [style.justify-content]="isNarrow ? 'center' : undefined"
331
412
  >
332
- @if (showJumpToStart !== false) {
413
+ @if (!live && showJumpToStart !== false) {
333
414
  <button
334
415
  (click)="hasStartTime && jumpToStart.emit()"
335
416
  [disabled]="!hasStartTime"
@@ -341,53 +422,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
341
422
  >⏮</button>
342
423
  }
343
424
 
344
- <button
345
- (click)="rewind.emit()"
346
- [style.color]="isRewinding ? theme.buttonActiveColor : theme.buttonColor"
347
- [style.border-color]="isRewinding ? theme.buttonActiveColor + '33' : 'transparent'"
348
- class="ct-btn ct-btn-wide"
349
- [title]="isRewinding ? resolveRewindActive(absMultiplier) : l.rewindTooltip"
350
- >
351
- @if (isRewinding) {
352
- <span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>◀◀
353
- } @else {
354
- ◀◀
355
- }
356
- </button>
425
+ @if (!live) {
426
+ <button
427
+ (click)="rewind.emit()"
428
+ [style.color]="isRewinding ? theme.buttonActiveColor : theme.buttonColor"
429
+ [style.border-color]="isRewinding ? theme.buttonActiveColor + '33' : 'transparent'"
430
+ class="ct-btn ct-btn-wide"
431
+ [title]="isRewinding ? resolveRewindActive(absMultiplier) : l.rewindTooltip"
432
+ >
433
+ @if (isRewinding) {
434
+ <span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>◀◀
435
+ } @else {
436
+ ◀◀
437
+ }
438
+ </button>
439
+ }
357
440
 
358
- <button
359
- (click)="playPause.emit(!isPlaying)"
360
- [style.color]="theme.buttonActiveColor"
361
- [style.border-color]="theme.buttonActiveColor + '55'"
362
- [style.padding-left]="isPlaying ? '0' : '2px'"
363
- class="ct-btn ct-btn-play"
364
- [title]="isPlaying ? l.pauseTooltip : (isRewinding ? l.playFromRewindTooltip : l.playTooltip)"
365
- >
366
- @if (isPlaying) {
367
- <svg width="14" height="16" viewBox="0 0 14 16" fill="currentColor">
368
- <rect x="1" y="0" width="4" height="16" rx="1"/>
369
- <rect x="9" y="0" width="4" height="16" rx="1"/>
370
- </svg>
371
- } @else {
372
-
373
- }
374
- </button>
441
+ @if (!live) {
442
+ <button
443
+ (click)="playPause.emit(!isPlaying)"
444
+ [style.color]="theme.buttonActiveColor"
445
+ [style.border-color]="theme.buttonActiveColor + '55'"
446
+ [style.padding-left]="isPlaying ? '0' : '2px'"
447
+ class="ct-btn ct-btn-play"
448
+ [title]="isPlaying ? l.pauseTooltip : (isRewinding ? l.playFromRewindTooltip : l.playTooltip)"
449
+ >
450
+ @if (isPlaying) {
451
+ <svg width="14" height="16" viewBox="0 0 14 16" fill="currentColor">
452
+ <rect x="1" y="0" width="4" height="16" rx="1"/>
453
+ <rect x="9" y="0" width="4" height="16" rx="1"/>
454
+ </svg>
455
+ } @else {
456
+
457
+ }
458
+ </button>
459
+ }
375
460
 
376
- <button
377
- (click)="fastForward.emit()"
378
- [style.color]="isFastForward ? theme.buttonActiveColor : theme.buttonColor"
379
- [style.border-color]="isFastForward ? theme.buttonActiveColor + '33' : 'transparent'"
380
- class="ct-btn ct-btn-wide"
381
- [title]="isFastForward ? resolveFastForwardActive(absMultiplier) : l.fastForwardTooltip"
382
- >
383
- @if (isFastForward) {
384
- ▶▶<span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>
385
- } @else {
386
- ▶▶
387
- }
388
- </button>
461
+ @if (!live) {
462
+ <button
463
+ (click)="fastForward.emit()"
464
+ [style.color]="isFastForward ? theme.buttonActiveColor : theme.buttonColor"
465
+ [style.border-color]="isFastForward ? theme.buttonActiveColor + '33' : 'transparent'"
466
+ class="ct-btn ct-btn-wide"
467
+ [title]="isFastForward ? resolveFastForwardActive(absMultiplier) : l.fastForwardTooltip"
468
+ >
469
+ @if (isFastForward) {
470
+ ▶▶<span style="font-size:11px;font-weight:bold">{{ absMultiplier }}×</span>
471
+ } @else {
472
+ ▶▶
473
+ }
474
+ </button>
475
+ }
389
476
 
390
- @if (showJumpToEnd !== false) {
477
+ @if (!live && showJumpToEnd !== false) {
391
478
  <button
392
479
  (click)="hasEndTime && jumpToEnd.emit()"
393
480
  [disabled]="!hasEndTime"
@@ -400,9 +487,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
400
487
  }
401
488
  </div>
402
489
 
403
- <!-- Right: swim-lane toggle -->
490
+ <!-- Right: LIVE (if position=right) + swim-lane toggle -->
404
491
  @if (!isNarrow) {
405
- <div style="display:flex;justify-content:flex-end;align-items:center">
492
+ <div style="display:flex;justify-content:flex-end;align-items:center;gap:8px">
493
+ @if (liveButtonPosition === 'right') {
494
+ <div style="display:flex;align-items:center;gap:4px">
495
+ <button
496
+ (click)="!live && jumpToLive.emit()"
497
+ [style.color]="(live || isLive) ? theme.controlBarBackground : theme.buttonActiveColor"
498
+ [style.background-color]="(live || isLive) ? theme.buttonActiveColor : 'transparent'"
499
+ [style.border-color]="theme.buttonActiveColor"
500
+ [style.opacity]="1"
501
+ [style.width.px]="liveSize.width"
502
+ [style.min-width.px]="liveSize.width"
503
+ [style.height.px]="liveSize.height"
504
+ [style.font-size]="liveSize.fontSize"
505
+ [style.border-radius]="liveSize.borderRadius"
506
+ [style.cursor]="live ? 'default' : 'pointer'"
507
+ style="background:none;border:1px solid;font-weight:bold;letter-spacing:0.05em;display:flex;align-items:center;justify-content:center;padding:0;gap:4px;font-family:system-ui,-apple-system,sans-serif;transition:opacity 0.15s"
508
+ [title]="(live || isLive) ? l.liveActiveTooltip : l.liveTooltip"
509
+ >
510
+ @if (live || isLive) {
511
+ <span
512
+ [style.width.px]="liveSize.dot"
513
+ [style.height.px]="liveSize.dot"
514
+ [style.background-color]="theme.liveDotColor"
515
+ style="border-radius:50%;display:inline-block;flex-shrink:0"
516
+ ></span>
517
+ }
518
+ {{ (live || isLive) ? l.liveActiveLabel : l.liveLabel }}
519
+ </button>
520
+ @if (!isNormalSpeed && !live) {
521
+ <button
522
+ (click)="resetSpeed.emit()"
523
+ [style.color]="theme.buttonActiveColor"
524
+ [style.border-color]="theme.buttonActiveColor + '44'"
525
+ [style.width.px]="liveSize.width"
526
+ [style.min-width.px]="liveSize.width"
527
+ [style.height.px]="liveSize.height"
528
+ style="background:none;border:1px solid;cursor:pointer;font-size:11px;border-radius:4px;display:flex;align-items:center;justify-content:center;padding:0;font-family:system-ui,-apple-system,sans-serif;transition:background-color 0.15s"
529
+ [title]="l.resetSpeedTooltip"
530
+ >{{ isRewinding ? '◀ ' + absMultiplier + '×' : absMultiplier + '× ▶' }}</button>
531
+ }
532
+ </div>
533
+ }
406
534
  @if (hasSwimLaneToggle) {
407
535
  <button
408
536
  (click)="toggleSwimLanes.emit()"
@@ -469,6 +597,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
469
597
  type: Input
470
598
  }], labels: [{
471
599
  type: Input
600
+ }], liveButtonSize: [{
601
+ type: Input
602
+ }], liveButtonPosition: [{
603
+ type: Input
604
+ }], live: [{
605
+ type: Input
472
606
  }], dateTimeClick: [{
473
607
  type: Output
474
608
  }], playPause: [{
@@ -511,6 +645,10 @@ class TimelineCanvasComponent {
511
645
  months;
512
646
  swimLanes;
513
647
  showSwimLanes;
648
+ /** When true, needle scrub is disabled (left-click becomes pan). Zoom and pan remain active. */
649
+ disableNeedleDrag = false;
650
+ /** @see TimelineBaseProps.invertScrollZoom */
651
+ invertScrollZoom = false;
514
652
  timeChange = new EventEmitter();
515
653
  dragStart = new EventEmitter();
516
654
  dragEnd = new EventEmitter();
@@ -539,6 +677,8 @@ class TimelineCanvasComponent {
539
677
  rangeAnchorMs = 0;
540
678
  rangeAnchorX = 0;
541
679
  rangeSelection = null;
680
+ // Ghost needle (hover preview)
681
+ hoverMs = null;
542
682
  // Touch state
543
683
  touchMode = 'none';
544
684
  touchX = 0;
@@ -728,6 +868,7 @@ class TimelineCanvasComponent {
728
868
  scrollTop: this.scrollTop,
729
869
  reorderState: this.reorderState,
730
870
  rangeSelection: this.rangeSelection,
871
+ hoverMs: this.hoverMs,
731
872
  });
732
873
  if (clampedScrollTop !== this.scrollTop) {
733
874
  this.scrollTop = clampedScrollTop;
@@ -829,6 +970,12 @@ class TimelineCanvasComponent {
829
970
  }
830
971
  }
831
972
  if (e.button === 0) {
973
+ if (this.disableNeedleDrag) {
974
+ // In live mode left-click becomes a pan; needle scrub is disabled.
975
+ this.mouseMode = 'slide';
976
+ this.mouseX = e.clientX;
977
+ return;
978
+ }
832
979
  const needleX = ((this.curMs - this.startMs) / (this.endMs - this.startMs)) * rect.width;
833
980
  const nearNeedle = Math.abs(x - needleX) <= 10;
834
981
  const inTickArea = y >= rect.height - TICK_AREA_HEIGHT;
@@ -986,9 +1133,21 @@ class TimelineCanvasComponent {
986
1133
  const selEnd = Math.max(sel.startMs, sel.endMs);
987
1134
  this.startMs = selStart;
988
1135
  this.endMs = selEnd;
1136
+ // If the needle is outside the selected range, clamp it to the nearest
1137
+ // edge so the clock-tick auto-scroll doesn't immediately override the zoom.
1138
+ const clampedMs = Math.max(selStart, Math.min(selEnd, this.curMs));
1139
+ const needleMoved = clampedMs !== this.curMs;
1140
+ if (needleMoved) {
1141
+ this.curMs = clampedMs;
1142
+ }
989
1143
  const startJd = Cesium.JulianDate.fromDate(new Date(selStart));
990
1144
  const endJd = Cesium.JulianDate.fromDate(new Date(selEnd));
991
- this.ngZone.run(() => this.rangeSelect.emit({ start: startJd, end: endJd }));
1145
+ this.ngZone.run(() => {
1146
+ this.rangeSelect.emit({ start: startJd, end: endJd });
1147
+ if (needleMoved) {
1148
+ this.timeChange.emit(Cesium.JulianDate.fromDate(new Date(clampedMs)));
1149
+ }
1150
+ });
992
1151
  }
993
1152
  this.mouseMode = 'none';
994
1153
  const canvas = this.canvasRef?.nativeElement;
@@ -1005,14 +1164,25 @@ class TimelineCanvasComponent {
1005
1164
  this.ngZone.run(() => this.dragEnd.emit());
1006
1165
  }
1007
1166
  onCanvasMouseMove(e) {
1008
- if (this.mouseMode !== 'none')
1009
- return;
1010
1167
  const canvas = this.canvasRef.nativeElement;
1011
1168
  const rect = canvas.getBoundingClientRect();
1012
1169
  const x = e.clientX - rect.left;
1013
1170
  const y = e.clientY - rect.top;
1171
+ // Update ghost needle while idle
1172
+ if (this.mouseMode === 'none') {
1173
+ if (!this.disableNeedleDrag) {
1174
+ this.hoverMs = this.startMs + (Math.max(0, Math.min(rect.width, x)) / rect.width) * (this.endMs - this.startMs);
1175
+ }
1176
+ else if (this.hoverMs !== null) {
1177
+ this.hoverMs = null;
1178
+ }
1179
+ }
1180
+ else {
1181
+ this.hoverMs = null;
1182
+ return;
1183
+ }
1014
1184
  const needleX = ((this.curMs - this.startMs) / (this.endMs - this.startMs)) * rect.width;
1015
- const nearNeedle = Math.abs(x - needleX) <= 10;
1185
+ const nearNeedle = !this.disableNeedleDrag && Math.abs(x - needleX) <= 10;
1016
1186
  if (this.isInSwimLaneRegion(y, rect.height)) {
1017
1187
  const hit = this.hitTestSwimLane(x, y, rect.width, rect.height);
1018
1188
  const prev = this.hoveredItem;
@@ -1021,14 +1191,12 @@ class TimelineCanvasComponent {
1021
1191
  if (!prev || prev.item.id !== hit.item.id || prev.lane.id !== hit.lane.id) {
1022
1192
  this.hoveredItem = hit;
1023
1193
  this.ngZone.run(() => this.swimLaneItemHover.emit({ laneId: hit.lane.id, item: hit.item, originalEvent: e }));
1024
- this.draw();
1025
1194
  }
1026
1195
  }
1027
1196
  else {
1028
1197
  if (prev) {
1029
1198
  this.hoveredItem = null;
1030
1199
  this.ngZone.run(() => this.swimLaneItemHover.emit(null));
1031
- this.draw();
1032
1200
  }
1033
1201
  if (nearNeedle) {
1034
1202
  canvas.style.cursor = 'grab';
@@ -1038,22 +1206,23 @@ class TimelineCanvasComponent {
1038
1206
  canvas.style.cursor = labelLane && this.swimLaneReorder.observed ? 'grab' : 'default';
1039
1207
  }
1040
1208
  }
1209
+ this.draw();
1041
1210
  return;
1042
1211
  }
1043
1212
  if (this.hoveredItem) {
1044
1213
  this.hoveredItem = null;
1045
1214
  this.ngZone.run(() => this.swimLaneItemHover.emit(null));
1046
- this.draw();
1047
1215
  }
1048
1216
  if (nearNeedle) {
1049
1217
  canvas.style.cursor = 'grab';
1050
1218
  }
1051
- else if (y >= rect.height - TICK_AREA_HEIGHT) {
1219
+ else if (!this.disableNeedleDrag && y >= rect.height - TICK_AREA_HEIGHT) {
1052
1220
  canvas.style.cursor = 'crosshair';
1053
1221
  }
1054
1222
  else {
1055
1223
  canvas.style.cursor = 'default';
1056
1224
  }
1225
+ this.draw();
1057
1226
  }
1058
1227
  onCanvasClick(e) {
1059
1228
  const elapsed = performance.now() - this.swimLaneDownTime;
@@ -1093,11 +1262,12 @@ class TimelineCanvasComponent {
1093
1262
  if (this.hoveredItem) {
1094
1263
  this.hoveredItem = null;
1095
1264
  this.ngZone.run(() => this.swimLaneItemHover.emit(null));
1096
- this.draw();
1097
1265
  }
1266
+ this.hoverMs = null;
1098
1267
  const canvas = this.canvasRef?.nativeElement;
1099
1268
  if (this.mouseMode === 'none' && canvas)
1100
1269
  canvas.style.cursor = 'default';
1270
+ this.draw();
1101
1271
  }
1102
1272
  // ── Wheel handler ──────────────────────────────────────────────────────
1103
1273
  onWheel(e) {
@@ -1118,7 +1288,7 @@ class TimelineCanvasComponent {
1118
1288
  return;
1119
1289
  }
1120
1290
  }
1121
- this.zoomFrom(Math.pow(1.05, e.deltaY > 0 ? -1 : 1));
1291
+ this.zoomFrom(Math.pow(1.05, e.deltaY > 0 === this.invertScrollZoom ? -1 : 1));
1122
1292
  }
1123
1293
  // ── Touch handlers ─────────────────────────────────────────────────────
1124
1294
  onTouchStart(e) {
@@ -1130,15 +1300,22 @@ class TimelineCanvasComponent {
1130
1300
  const cx = Math.max(0, Math.min(rect.width, x));
1131
1301
  const ms = this.startMs + (cx / rect.width) * (this.endMs - this.startMs);
1132
1302
  this.prePinchCurMs = this.curMs;
1133
- this.touchMode = 'scrub';
1134
- this.touchX = e.touches[0].clientX;
1135
- this.scrubClientX = e.touches[0].clientX;
1136
- this.curMs = ms;
1137
- this.draw();
1138
- this.ngZone.run(() => {
1139
- this.dragStart.emit();
1140
- this.timeChange.emit(Cesium.JulianDate.fromDate(new Date(ms)));
1141
- });
1303
+ if (this.disableNeedleDrag) {
1304
+ // In live mode single-finger becomes a pan, not a scrub.
1305
+ this.touchMode = 'slide';
1306
+ this.touchX = e.touches[0].clientX;
1307
+ }
1308
+ else {
1309
+ this.touchMode = 'scrub';
1310
+ this.touchX = e.touches[0].clientX;
1311
+ this.scrubClientX = e.touches[0].clientX;
1312
+ this.curMs = ms;
1313
+ this.draw();
1314
+ this.ngZone.run(() => {
1315
+ this.dragStart.emit();
1316
+ this.timeChange.emit(Cesium.JulianDate.fromDate(new Date(ms)));
1317
+ });
1318
+ }
1142
1319
  }
1143
1320
  else if (e.touches.length >= 2) {
1144
1321
  // If we were scrubbing, undo the needle move — pinch-zoom should not
@@ -1214,7 +1391,7 @@ class TimelineCanvasComponent {
1214
1391
  }
1215
1392
  }
1216
1393
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: TimelineCanvasComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
1217
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.10", type: TimelineCanvasComponent, isStandalone: true, selector: "ct-timeline-canvas", inputs: { currentTime: "currentTime", defaultStartMs: "defaultStartMs", defaultEndMs: "defaultEndMs", theme: "theme", maxTicks: "maxTicks", timezone: "timezone", dateTimeFormat: "dateTimeFormat", months: "months", swimLanes: "swimLanes", showSwimLanes: "showSwimLanes" }, outputs: { timeChange: "timeChange", dragStart: "dragStart", dragEnd: "dragEnd", swimLaneItemClick: "swimLaneItemClick", swimLaneItemHover: "swimLaneItemHover", swimLaneItemDoubleClick: "swimLaneItemDoubleClick", swimLaneItemContextMenu: "swimLaneItemContextMenu", swimLaneReorder: "swimLaneReorder", rangeSelect: "rangeSelect" }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `<canvas #canvas
1394
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.10", type: TimelineCanvasComponent, isStandalone: true, selector: "ct-timeline-canvas", inputs: { currentTime: "currentTime", defaultStartMs: "defaultStartMs", defaultEndMs: "defaultEndMs", theme: "theme", maxTicks: "maxTicks", timezone: "timezone", dateTimeFormat: "dateTimeFormat", months: "months", swimLanes: "swimLanes", showSwimLanes: "showSwimLanes", disableNeedleDrag: "disableNeedleDrag", invertScrollZoom: "invertScrollZoom" }, outputs: { timeChange: "timeChange", dragStart: "dragStart", dragEnd: "dragEnd", swimLaneItemClick: "swimLaneItemClick", swimLaneItemHover: "swimLaneItemHover", swimLaneItemDoubleClick: "swimLaneItemDoubleClick", swimLaneItemContextMenu: "swimLaneItemContextMenu", swimLaneReorder: "swimLaneReorder", rangeSelect: "rangeSelect" }, viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `<canvas #canvas
1218
1395
  style="width:100%;flex:1;min-height:0;display:block;cursor:default"
1219
1396
  (mousedown)="onCanvasMouseDown($event)"
1220
1397
  (mousemove)="onCanvasMouseMove($event)"
@@ -1255,6 +1432,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1255
1432
  type: Input
1256
1433
  }], showSwimLanes: [{
1257
1434
  type: Input
1435
+ }], disableNeedleDrag: [{
1436
+ type: Input
1437
+ }], invertScrollZoom: [{
1438
+ type: Input
1258
1439
  }], timeChange: [{
1259
1440
  type: Output
1260
1441
  }], dragStart: [{
@@ -1306,6 +1487,13 @@ class TimelineComponent {
1306
1487
  swimLaneTransition = 'animated';
1307
1488
  /** Overrides for control-bar labels and tooltips (i18n / custom verbiage). */
1308
1489
  labels;
1490
+ /** @see TimelineBaseProps.liveButtonSize */
1491
+ liveButtonSize;
1492
+ /** @see TimelineBaseProps.liveButtonPosition */
1493
+ liveButtonPosition;
1494
+ /** @see TimelineBaseProps.live */
1495
+ live = false;
1496
+ invertScrollZoom = false;
1309
1497
  // ── Outputs ────────────────────────────────────────────────────────────
1310
1498
  timeChange = new EventEmitter();
1311
1499
  playPause = new EventEmitter();
@@ -1342,7 +1530,7 @@ class TimelineComponent {
1342
1530
  return this.swimLanes != null && this.swimLanes.length > 0;
1343
1531
  }
1344
1532
  get isLive() {
1345
- return Math.abs(Cesium.JulianDate.toDate(this.currentTimeState).getTime() - Date.now()) < 10_000;
1533
+ return Math.abs(Cesium.JulianDate.toDate(this.currentTimeState).getTime() - Date.now()) < 2_000;
1346
1534
  }
1347
1535
  get isCollapsed() {
1348
1536
  return this.hasSwimLanes && !this.swimLanesExpanded;
@@ -1393,7 +1581,7 @@ class TimelineComponent {
1393
1581
  this.cleanupClockSync();
1394
1582
  this.setupClockSync();
1395
1583
  }
1396
- if (changes['jumpToTime'] && this.jumpToTime) {
1584
+ if (changes['jumpToTime'] && this.jumpToTime && !this.live) {
1397
1585
  const t = toJulianDate(this.jumpToTime);
1398
1586
  this.handleTimeChange(t);
1399
1587
  if (this.canvasComp) {
@@ -1547,7 +1735,7 @@ class TimelineComponent {
1547
1735
  this.cdr.markForCheck();
1548
1736
  }
1549
1737
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: TimelineComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
1550
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: TimelineComponent, isStandalone: true, selector: "ct-timeline", inputs: { startTime: "startTime", endTime: "endTime", currentTime: "currentTime", clock: "clock", height: "height", showControls: "showControls", showJumpToStart: "showJumpToStart", showJumpToEnd: "showJumpToEnd", enableDrag: "enableDrag", dateTimeFormat: "dateTimeFormat", jumpToTime: "jumpToTime", maxTicks: "maxTicks", ffSpeeds: "ffSpeeds", rwSpeeds: "rwSpeeds", theme: "theme", cssClass: "cssClass", timezone: "timezone", swimLanes: "swimLanes", showSwimLanes: "showSwimLanes", swimLaneTransition: "swimLaneTransition", labels: "labels" }, outputs: { timeChange: "timeChange", playPause: "playPause", multiplierChange: "multiplierChange", dateTimeClick: "dateTimeClick", showSwimLanesChange: "showSwimLanesChange", swimLaneItemClick: "swimLaneItemClick", swimLaneItemHover: "swimLaneItemHover", swimLaneItemDoubleClick: "swimLaneItemDoubleClick", swimLaneItemContextMenu: "swimLaneItemContextMenu", swimLaneReorder: "swimLaneReorder", rangeSelect: "rangeSelect" }, viewQueries: [{ propertyName: "canvasComp", first: true, predicate: TimelineCanvasComponent, descendants: true }, { propertyName: "controlsRef", first: true, predicate: ["controlsEl"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
1738
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: TimelineComponent, isStandalone: true, selector: "ct-timeline", inputs: { startTime: "startTime", endTime: "endTime", currentTime: "currentTime", clock: "clock", height: "height", showControls: "showControls", showJumpToStart: "showJumpToStart", showJumpToEnd: "showJumpToEnd", enableDrag: "enableDrag", dateTimeFormat: "dateTimeFormat", jumpToTime: "jumpToTime", maxTicks: "maxTicks", ffSpeeds: "ffSpeeds", rwSpeeds: "rwSpeeds", theme: "theme", cssClass: "cssClass", timezone: "timezone", swimLanes: "swimLanes", showSwimLanes: "showSwimLanes", swimLaneTransition: "swimLaneTransition", labels: "labels", liveButtonSize: "liveButtonSize", liveButtonPosition: "liveButtonPosition", live: "live", invertScrollZoom: "invertScrollZoom" }, outputs: { timeChange: "timeChange", playPause: "playPause", multiplierChange: "multiplierChange", dateTimeClick: "dateTimeClick", showSwimLanesChange: "showSwimLanesChange", swimLaneItemClick: "swimLaneItemClick", swimLaneItemHover: "swimLaneItemHover", swimLaneItemDoubleClick: "swimLaneItemDoubleClick", swimLaneItemContextMenu: "swimLaneItemContextMenu", swimLaneReorder: "swimLaneReorder", rangeSelect: "rangeSelect" }, viewQueries: [{ propertyName: "canvasComp", first: true, predicate: TimelineCanvasComponent, descendants: true }, { propertyName: "controlsRef", first: true, predicate: ["controlsEl"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
1551
1739
  <div
1552
1740
  [class]="cssClass"
1553
1741
  [style.width]="'100%'"
@@ -1583,11 +1771,14 @@ class TimelineComponent {
1583
1771
  (dateTimeClick)="dateTimeClick.emit()"
1584
1772
  (toggleSwimLanes)="handleToggleSwimLanes()"
1585
1773
  [labels]="labels"
1774
+ [liveButtonSize]="liveButtonSize"
1775
+ [liveButtonPosition]="liveButtonPosition"
1776
+ [live]="live"
1586
1777
  />
1587
1778
  </div>
1588
1779
  }
1589
1780
 
1590
- @if (enableDrag !== false) {
1781
+ @if (enableDrag !== false || live) {
1591
1782
  <ct-timeline-canvas
1592
1783
  [currentTime]="currentTimeState"
1593
1784
  [defaultStartMs]="defaultStartMs"
@@ -1599,6 +1790,8 @@ class TimelineComponent {
1599
1790
  [months]="labels?.months"
1600
1791
  [swimLanes]="swimLanes"
1601
1792
  [showSwimLanes]="swimLanesExpanded"
1793
+ [disableNeedleDrag]="live"
1794
+ [invertScrollZoom]="invertScrollZoom"
1602
1795
  (timeChange)="handleTimeChange($event)"
1603
1796
  (dragStart)="isDragging = true"
1604
1797
  (dragEnd)="isDragging = false"
@@ -1611,7 +1804,7 @@ class TimelineComponent {
1611
1804
  />
1612
1805
  }
1613
1806
  </div>
1614
- `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: TimelineControlsComponent, selector: "ct-timeline-controls", inputs: ["currentTime", "isPlaying", "multiplier", "dateTimeFormat", "timezone", "isLive", "hasStartTime", "hasEndTime", "showJumpToStart", "showJumpToEnd", "theme", "swimLanesVisible", "labels"], outputs: ["dateTimeClick", "playPause", "jumpToStart", "rewind", "fastForward", "jumpToEnd", "jumpToLive", "resetSpeed", "toggleSwimLanes"] }, { kind: "component", type: TimelineCanvasComponent, selector: "ct-timeline-canvas", inputs: ["currentTime", "defaultStartMs", "defaultEndMs", "theme", "maxTicks", "timezone", "dateTimeFormat", "months", "swimLanes", "showSwimLanes"], outputs: ["timeChange", "dragStart", "dragEnd", "swimLaneItemClick", "swimLaneItemHover", "swimLaneItemDoubleClick", "swimLaneItemContextMenu", "swimLaneReorder", "rangeSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1807
+ `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: TimelineControlsComponent, selector: "ct-timeline-controls", inputs: ["currentTime", "isPlaying", "multiplier", "dateTimeFormat", "timezone", "isLive", "hasStartTime", "hasEndTime", "showJumpToStart", "showJumpToEnd", "theme", "swimLanesVisible", "labels", "liveButtonSize", "liveButtonPosition", "live"], outputs: ["dateTimeClick", "playPause", "jumpToStart", "rewind", "fastForward", "jumpToEnd", "jumpToLive", "resetSpeed", "toggleSwimLanes"] }, { kind: "component", type: TimelineCanvasComponent, selector: "ct-timeline-canvas", inputs: ["currentTime", "defaultStartMs", "defaultEndMs", "theme", "maxTicks", "timezone", "dateTimeFormat", "months", "swimLanes", "showSwimLanes", "disableNeedleDrag", "invertScrollZoom"], outputs: ["timeChange", "dragStart", "dragEnd", "swimLaneItemClick", "swimLaneItemHover", "swimLaneItemDoubleClick", "swimLaneItemContextMenu", "swimLaneReorder", "rangeSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1615
1808
  }
1616
1809
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: TimelineComponent, decorators: [{
1617
1810
  type: Component,
@@ -1651,11 +1844,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1651
1844
  (dateTimeClick)="dateTimeClick.emit()"
1652
1845
  (toggleSwimLanes)="handleToggleSwimLanes()"
1653
1846
  [labels]="labels"
1847
+ [liveButtonSize]="liveButtonSize"
1848
+ [liveButtonPosition]="liveButtonPosition"
1849
+ [live]="live"
1654
1850
  />
1655
1851
  </div>
1656
1852
  }
1657
1853
 
1658
- @if (enableDrag !== false) {
1854
+ @if (enableDrag !== false || live) {
1659
1855
  <ct-timeline-canvas
1660
1856
  [currentTime]="currentTimeState"
1661
1857
  [defaultStartMs]="defaultStartMs"
@@ -1667,6 +1863,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1667
1863
  [months]="labels?.months"
1668
1864
  [swimLanes]="swimLanes"
1669
1865
  [showSwimLanes]="swimLanesExpanded"
1866
+ [disableNeedleDrag]="live"
1867
+ [invertScrollZoom]="invertScrollZoom"
1670
1868
  (timeChange)="handleTimeChange($event)"
1671
1869
  (dragStart)="isDragging = true"
1672
1870
  (dragEnd)="isDragging = false"
@@ -1722,6 +1920,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1722
1920
  type: Input
1723
1921
  }], labels: [{
1724
1922
  type: Input
1923
+ }], liveButtonSize: [{
1924
+ type: Input
1925
+ }], liveButtonPosition: [{
1926
+ type: Input
1927
+ }], live: [{
1928
+ type: Input
1929
+ }], invertScrollZoom: [{
1930
+ type: Input
1725
1931
  }], timeChange: [{
1726
1932
  type: Output
1727
1933
  }], playPause: [{