@kteneyck/cesium-timeline-angular 0.9.0 → 0.11.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); }
@@ -57,8 +67,8 @@ class TimelineControlsComponent {
57
67
  ngOnDestroy() {
58
68
  this.ro?.disconnect();
59
69
  }
60
- 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: `
70
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TimelineControlsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
71
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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()"
@@ -250,7 +319,7 @@ class TimelineControlsComponent {
250
319
  </div>
251
320
  `, isInline: true, styles: [":host{display:block}.ct-btn{background:none;border:1px solid transparent;cursor:pointer;font-size:16px;padding:0;display:flex;align-items:center;justify-content:center;min-width:32px;width:32px;height:32px;border-radius:4px;transition:background-color .15s,color .15s;font-family:system-ui,-apple-system,sans-serif;flex-shrink:0;line-height:1}.ct-btn:hover{background-color:#ffffff1a}.ct-btn-wide{width:64px;min-width:64px;gap:3px}.ct-btn-play{font-size:18px;width:40px;min-width:40px;height:40px;border-radius:50%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
252
321
  }
253
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: TimelineControlsComponent, decorators: [{
322
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TimelineControlsComponent, decorators: [{
254
323
  type: Component,
255
324
  args: [{ selector: 'ct-timeline-controls', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
256
325
  <div
@@ -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;
@@ -832,6 +973,22 @@ class TimelineCanvasComponent {
832
973
  const needleX = ((this.curMs - this.startMs) / (this.endMs - this.startMs)) * rect.width;
833
974
  const nearNeedle = Math.abs(x - needleX) <= 10;
834
975
  const inTickArea = y >= rect.height - TICK_AREA_HEIGHT;
976
+ if (this.disableNeedleDrag) {
977
+ if (!nearNeedle && inTickArea) {
978
+ // In live mode: allow range-select zoom in the tick area; needle scrub is still disabled.
979
+ this.mouseMode = 'rangeSelectPending';
980
+ this.rangeAnchorX = x;
981
+ this.rangeAnchorMs = this.startMs + (x / rect.width) * (this.endMs - this.startMs);
982
+ canvas.style.cursor = 'crosshair';
983
+ this.ngZone.run(() => this.dragStart.emit());
984
+ }
985
+ else {
986
+ // Outside tick area — left-click becomes a pan.
987
+ this.mouseMode = 'slide';
988
+ this.mouseX = e.clientX;
989
+ }
990
+ return;
991
+ }
835
992
  if (!nearNeedle && inTickArea) {
836
993
  this.mouseMode = 'rangeSelectPending';
837
994
  this.rangeAnchorX = x;
@@ -982,13 +1139,38 @@ class TimelineCanvasComponent {
982
1139
  const sel = this.rangeSelection;
983
1140
  this.rangeSelection = null;
984
1141
  if (sel) {
985
- const selStart = Math.min(sel.startMs, sel.endMs);
986
- const selEnd = Math.max(sel.startMs, sel.endMs);
987
- this.startMs = selStart;
988
- this.endMs = selEnd;
989
- const startJd = Cesium.JulianDate.fromDate(new Date(selStart));
990
- const endJd = Cesium.JulianDate.fromDate(new Date(selEnd));
991
- this.ngZone.run(() => this.rangeSelect.emit({ start: startJd, end: endJd }));
1142
+ let selStart = Math.min(sel.startMs, sel.endMs);
1143
+ let selEnd = Math.max(sel.startMs, sel.endMs);
1144
+ if (this.disableNeedleDrag) {
1145
+ // In live mode: don't move the needle. Expand the selection to keep
1146
+ // the needle (current time) visible on screen.
1147
+ selStart = Math.min(selStart, this.curMs);
1148
+ selEnd = Math.max(selEnd, this.curMs);
1149
+ this.startMs = selStart;
1150
+ this.endMs = selEnd;
1151
+ const startJd = Cesium.JulianDate.fromDate(new Date(selStart));
1152
+ const endJd = Cesium.JulianDate.fromDate(new Date(selEnd));
1153
+ this.ngZone.run(() => this.rangeSelect.emit({ start: startJd, end: endJd }));
1154
+ }
1155
+ else {
1156
+ this.startMs = selStart;
1157
+ this.endMs = selEnd;
1158
+ // If the needle is outside the selected range, clamp it to the nearest
1159
+ // edge so the clock-tick auto-scroll doesn't immediately override the zoom.
1160
+ const clampedMs = Math.max(selStart, Math.min(selEnd, this.curMs));
1161
+ const needleMoved = clampedMs !== this.curMs;
1162
+ if (needleMoved) {
1163
+ this.curMs = clampedMs;
1164
+ }
1165
+ const startJd = Cesium.JulianDate.fromDate(new Date(selStart));
1166
+ const endJd = Cesium.JulianDate.fromDate(new Date(selEnd));
1167
+ this.ngZone.run(() => {
1168
+ this.rangeSelect.emit({ start: startJd, end: endJd });
1169
+ if (needleMoved) {
1170
+ this.timeChange.emit(Cesium.JulianDate.fromDate(new Date(clampedMs)));
1171
+ }
1172
+ });
1173
+ }
992
1174
  }
993
1175
  this.mouseMode = 'none';
994
1176
  const canvas = this.canvasRef?.nativeElement;
@@ -1005,14 +1187,25 @@ class TimelineCanvasComponent {
1005
1187
  this.ngZone.run(() => this.dragEnd.emit());
1006
1188
  }
1007
1189
  onCanvasMouseMove(e) {
1008
- if (this.mouseMode !== 'none')
1009
- return;
1010
1190
  const canvas = this.canvasRef.nativeElement;
1011
1191
  const rect = canvas.getBoundingClientRect();
1012
1192
  const x = e.clientX - rect.left;
1013
1193
  const y = e.clientY - rect.top;
1194
+ // Update ghost needle while idle
1195
+ if (this.mouseMode === 'none') {
1196
+ if (!this.disableNeedleDrag) {
1197
+ this.hoverMs = this.startMs + (Math.max(0, Math.min(rect.width, x)) / rect.width) * (this.endMs - this.startMs);
1198
+ }
1199
+ else if (this.hoverMs !== null) {
1200
+ this.hoverMs = null;
1201
+ }
1202
+ }
1203
+ else {
1204
+ this.hoverMs = null;
1205
+ return;
1206
+ }
1014
1207
  const needleX = ((this.curMs - this.startMs) / (this.endMs - this.startMs)) * rect.width;
1015
- const nearNeedle = Math.abs(x - needleX) <= 10;
1208
+ const nearNeedle = !this.disableNeedleDrag && Math.abs(x - needleX) <= 10;
1016
1209
  if (this.isInSwimLaneRegion(y, rect.height)) {
1017
1210
  const hit = this.hitTestSwimLane(x, y, rect.width, rect.height);
1018
1211
  const prev = this.hoveredItem;
@@ -1021,14 +1214,12 @@ class TimelineCanvasComponent {
1021
1214
  if (!prev || prev.item.id !== hit.item.id || prev.lane.id !== hit.lane.id) {
1022
1215
  this.hoveredItem = hit;
1023
1216
  this.ngZone.run(() => this.swimLaneItemHover.emit({ laneId: hit.lane.id, item: hit.item, originalEvent: e }));
1024
- this.draw();
1025
1217
  }
1026
1218
  }
1027
1219
  else {
1028
1220
  if (prev) {
1029
1221
  this.hoveredItem = null;
1030
1222
  this.ngZone.run(() => this.swimLaneItemHover.emit(null));
1031
- this.draw();
1032
1223
  }
1033
1224
  if (nearNeedle) {
1034
1225
  canvas.style.cursor = 'grab';
@@ -1038,12 +1229,12 @@ class TimelineCanvasComponent {
1038
1229
  canvas.style.cursor = labelLane && this.swimLaneReorder.observed ? 'grab' : 'default';
1039
1230
  }
1040
1231
  }
1232
+ this.draw();
1041
1233
  return;
1042
1234
  }
1043
1235
  if (this.hoveredItem) {
1044
1236
  this.hoveredItem = null;
1045
1237
  this.ngZone.run(() => this.swimLaneItemHover.emit(null));
1046
- this.draw();
1047
1238
  }
1048
1239
  if (nearNeedle) {
1049
1240
  canvas.style.cursor = 'grab';
@@ -1054,6 +1245,7 @@ class TimelineCanvasComponent {
1054
1245
  else {
1055
1246
  canvas.style.cursor = 'default';
1056
1247
  }
1248
+ this.draw();
1057
1249
  }
1058
1250
  onCanvasClick(e) {
1059
1251
  const elapsed = performance.now() - this.swimLaneDownTime;
@@ -1093,11 +1285,12 @@ class TimelineCanvasComponent {
1093
1285
  if (this.hoveredItem) {
1094
1286
  this.hoveredItem = null;
1095
1287
  this.ngZone.run(() => this.swimLaneItemHover.emit(null));
1096
- this.draw();
1097
1288
  }
1289
+ this.hoverMs = null;
1098
1290
  const canvas = this.canvasRef?.nativeElement;
1099
1291
  if (this.mouseMode === 'none' && canvas)
1100
1292
  canvas.style.cursor = 'default';
1293
+ this.draw();
1101
1294
  }
1102
1295
  // ── Wheel handler ──────────────────────────────────────────────────────
1103
1296
  onWheel(e) {
@@ -1118,7 +1311,7 @@ class TimelineCanvasComponent {
1118
1311
  return;
1119
1312
  }
1120
1313
  }
1121
- this.zoomFrom(Math.pow(1.05, e.deltaY > 0 ? -1 : 1));
1314
+ this.zoomFrom(Math.pow(1.05, e.deltaY > 0 === this.invertScrollZoom ? -1 : 1));
1122
1315
  }
1123
1316
  // ── Touch handlers ─────────────────────────────────────────────────────
1124
1317
  onTouchStart(e) {
@@ -1130,15 +1323,22 @@ class TimelineCanvasComponent {
1130
1323
  const cx = Math.max(0, Math.min(rect.width, x));
1131
1324
  const ms = this.startMs + (cx / rect.width) * (this.endMs - this.startMs);
1132
1325
  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
- });
1326
+ if (this.disableNeedleDrag) {
1327
+ // In live mode single-finger becomes a pan, not a scrub.
1328
+ this.touchMode = 'slide';
1329
+ this.touchX = e.touches[0].clientX;
1330
+ }
1331
+ else {
1332
+ this.touchMode = 'scrub';
1333
+ this.touchX = e.touches[0].clientX;
1334
+ this.scrubClientX = e.touches[0].clientX;
1335
+ this.curMs = ms;
1336
+ this.draw();
1337
+ this.ngZone.run(() => {
1338
+ this.dragStart.emit();
1339
+ this.timeChange.emit(Cesium.JulianDate.fromDate(new Date(ms)));
1340
+ });
1341
+ }
1142
1342
  }
1143
1343
  else if (e.touches.length >= 2) {
1144
1344
  // If we were scrubbing, undo the needle move — pinch-zoom should not
@@ -1213,8 +1413,8 @@ class TimelineCanvasComponent {
1213
1413
  this.touchX = e.touches[0].clientX;
1214
1414
  }
1215
1415
  }
1216
- 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
1416
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TimelineCanvasComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
1417
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", 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
1418
  style="width:100%;flex:1;min-height:0;display:block;cursor:default"
1219
1419
  (mousedown)="onCanvasMouseDown($event)"
1220
1420
  (mousemove)="onCanvasMouseMove($event)"
@@ -1224,7 +1424,7 @@ class TimelineCanvasComponent {
1224
1424
  (mouseleave)="onCanvasMouseLeave()"
1225
1425
  ></canvas>`, isInline: true, styles: [":host{display:flex;flex:1;min-height:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1226
1426
  }
1227
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: TimelineCanvasComponent, decorators: [{
1427
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TimelineCanvasComponent, decorators: [{
1228
1428
  type: Component,
1229
1429
  args: [{ selector: 'ct-timeline-canvas', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `<canvas #canvas
1230
1430
  style="width:100%;flex:1;min-height:0;display:block;cursor:default"
@@ -1255,6 +1455,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1255
1455
  type: Input
1256
1456
  }], showSwimLanes: [{
1257
1457
  type: Input
1458
+ }], disableNeedleDrag: [{
1459
+ type: Input
1460
+ }], invertScrollZoom: [{
1461
+ type: Input
1258
1462
  }], timeChange: [{
1259
1463
  type: Output
1260
1464
  }], dragStart: [{
@@ -1306,6 +1510,13 @@ class TimelineComponent {
1306
1510
  swimLaneTransition = 'animated';
1307
1511
  /** Overrides for control-bar labels and tooltips (i18n / custom verbiage). */
1308
1512
  labels;
1513
+ /** @see TimelineBaseProps.liveButtonSize */
1514
+ liveButtonSize;
1515
+ /** @see TimelineBaseProps.liveButtonPosition */
1516
+ liveButtonPosition;
1517
+ /** @see TimelineBaseProps.live */
1518
+ live = false;
1519
+ invertScrollZoom = false;
1309
1520
  // ── Outputs ────────────────────────────────────────────────────────────
1310
1521
  timeChange = new EventEmitter();
1311
1522
  playPause = new EventEmitter();
@@ -1342,7 +1553,7 @@ class TimelineComponent {
1342
1553
  return this.swimLanes != null && this.swimLanes.length > 0;
1343
1554
  }
1344
1555
  get isLive() {
1345
- return Math.abs(Cesium.JulianDate.toDate(this.currentTimeState).getTime() - Date.now()) < 10_000;
1556
+ return Math.abs(Cesium.JulianDate.toDate(this.currentTimeState).getTime() - Date.now()) < 2_000;
1346
1557
  }
1347
1558
  get isCollapsed() {
1348
1559
  return this.hasSwimLanes && !this.swimLanesExpanded;
@@ -1393,7 +1604,7 @@ class TimelineComponent {
1393
1604
  this.cleanupClockSync();
1394
1605
  this.setupClockSync();
1395
1606
  }
1396
- if (changes['jumpToTime'] && this.jumpToTime) {
1607
+ if (changes['jumpToTime'] && this.jumpToTime && !this.live) {
1397
1608
  const t = toJulianDate(this.jumpToTime);
1398
1609
  this.handleTimeChange(t);
1399
1610
  if (this.canvasComp) {
@@ -1403,6 +1614,20 @@ class TimelineComponent {
1403
1614
  this.canvasComp.zoomTo(newMs - span / 2, newMs + span / 2);
1404
1615
  }
1405
1616
  }
1617
+ if ((changes['startTime'] && !changes['startTime'].firstChange) ||
1618
+ (changes['endTime'] && !changes['endTime'].firstChange)) {
1619
+ const now = Date.now();
1620
+ this.defaultStartMs = this.startTime
1621
+ ? Cesium.JulianDate.toDate(toJulianDate(this.startTime)).getTime()
1622
+ : now - 12 * 3600 * 1000;
1623
+ this.defaultEndMs = this.endTime
1624
+ ? Cesium.JulianDate.toDate(toJulianDate(this.endTime)).getTime()
1625
+ : now + 12 * 3600 * 1000;
1626
+ if (this.startTime != null && this.endTime != null) {
1627
+ this.canvasComp?.zoomTo(this.defaultStartMs, this.defaultEndMs);
1628
+ }
1629
+ this.cdr.markForCheck();
1630
+ }
1406
1631
  }
1407
1632
  ngOnDestroy() {
1408
1633
  this.cleanupClockSync();
@@ -1546,8 +1771,8 @@ class TimelineComponent {
1546
1771
  this.multiplierChange.emit(m);
1547
1772
  this.cdr.markForCheck();
1548
1773
  }
1549
- 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: `
1774
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TimelineComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
1775
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", 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
1776
  <div
1552
1777
  [class]="cssClass"
1553
1778
  [style.width]="'100%'"
@@ -1583,11 +1808,14 @@ class TimelineComponent {
1583
1808
  (dateTimeClick)="dateTimeClick.emit()"
1584
1809
  (toggleSwimLanes)="handleToggleSwimLanes()"
1585
1810
  [labels]="labels"
1811
+ [liveButtonSize]="liveButtonSize"
1812
+ [liveButtonPosition]="liveButtonPosition"
1813
+ [live]="live"
1586
1814
  />
1587
1815
  </div>
1588
1816
  }
1589
1817
 
1590
- @if (enableDrag !== false) {
1818
+ @if (enableDrag !== false || live) {
1591
1819
  <ct-timeline-canvas
1592
1820
  [currentTime]="currentTimeState"
1593
1821
  [defaultStartMs]="defaultStartMs"
@@ -1599,6 +1827,8 @@ class TimelineComponent {
1599
1827
  [months]="labels?.months"
1600
1828
  [swimLanes]="swimLanes"
1601
1829
  [showSwimLanes]="swimLanesExpanded"
1830
+ [disableNeedleDrag]="live"
1831
+ [invertScrollZoom]="invertScrollZoom"
1602
1832
  (timeChange)="handleTimeChange($event)"
1603
1833
  (dragStart)="isDragging = true"
1604
1834
  (dragEnd)="isDragging = false"
@@ -1611,9 +1841,9 @@ class TimelineComponent {
1611
1841
  />
1612
1842
  }
1613
1843
  </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 });
1844
+ `, 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
1845
  }
1616
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: TimelineComponent, decorators: [{
1846
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TimelineComponent, decorators: [{
1617
1847
  type: Component,
1618
1848
  args: [{ selector: 'ct-timeline', standalone: true, imports: [TimelineControlsComponent, TimelineCanvasComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1619
1849
  <div
@@ -1651,11 +1881,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1651
1881
  (dateTimeClick)="dateTimeClick.emit()"
1652
1882
  (toggleSwimLanes)="handleToggleSwimLanes()"
1653
1883
  [labels]="labels"
1884
+ [liveButtonSize]="liveButtonSize"
1885
+ [liveButtonPosition]="liveButtonPosition"
1886
+ [live]="live"
1654
1887
  />
1655
1888
  </div>
1656
1889
  }
1657
1890
 
1658
- @if (enableDrag !== false) {
1891
+ @if (enableDrag !== false || live) {
1659
1892
  <ct-timeline-canvas
1660
1893
  [currentTime]="currentTimeState"
1661
1894
  [defaultStartMs]="defaultStartMs"
@@ -1667,6 +1900,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1667
1900
  [months]="labels?.months"
1668
1901
  [swimLanes]="swimLanes"
1669
1902
  [showSwimLanes]="swimLanesExpanded"
1903
+ [disableNeedleDrag]="live"
1904
+ [invertScrollZoom]="invertScrollZoom"
1670
1905
  (timeChange)="handleTimeChange($event)"
1671
1906
  (dragStart)="isDragging = true"
1672
1907
  (dragEnd)="isDragging = false"
@@ -1722,6 +1957,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1722
1957
  type: Input
1723
1958
  }], labels: [{
1724
1959
  type: Input
1960
+ }], liveButtonSize: [{
1961
+ type: Input
1962
+ }], liveButtonPosition: [{
1963
+ type: Input
1964
+ }], live: [{
1965
+ type: Input
1966
+ }], invertScrollZoom: [{
1967
+ type: Input
1725
1968
  }], timeChange: [{
1726
1969
  type: Output
1727
1970
  }], playPause: [{