@checksub_team/peaks_timeline 2.2.1 → 2.3.0-alpha.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.
@@ -6,12 +6,22 @@
6
6
  * @module invoker
7
7
  */
8
8
 
9
- define([
9
+ define([
10
10
  ], function() {
11
11
  'use strict';
12
12
 
13
+ // Use requestAnimationFrame for smoother animations when available
14
+ var raf = (typeof window !== 'undefined' && window.requestAnimationFrame) ||
15
+ function(fn) {
16
+ return setTimeout(fn, 16);
17
+ };
18
+ var caf = (typeof window !== 'undefined' && window.cancelAnimationFrame) ||
19
+ function(id) {
20
+ clearTimeout(id);
21
+ };
22
+
13
23
  /**
14
- * An invoker class for throttling.
24
+ * An invoker class for throttling and performance optimization.
15
25
  *
16
26
  * @class
17
27
  * @alias Invoker
@@ -19,8 +29,14 @@
19
29
 
20
30
  function Invoker() {
21
31
  this._throttledFunc = {};
32
+ this._rafThrottled = {};
33
+ this._frameCallbacks = [];
34
+ this._frameScheduled = false;
22
35
  }
23
36
 
37
+ /**
38
+ * Throttle using setInterval - good for consistent timing
39
+ */
24
40
  Invoker.prototype.throttle = function(id, func, wait) {
25
41
  var self = this;
26
42
 
@@ -50,6 +66,78 @@
50
66
  }
51
67
  };
52
68
 
69
+ /**
70
+ * Throttle using requestAnimationFrame - ideal for visual updates
71
+ * Ensures function runs at most once per animation frame
72
+ */
73
+ Invoker.prototype.throttleRAF = function(id, func) {
74
+ var self = this;
75
+
76
+ if (this._rafThrottled[id]) {
77
+ // Update the function but don't schedule again
78
+ this._rafThrottled[id].func = func;
79
+ this._rafThrottled[id].pending = true;
80
+ return;
81
+ }
82
+
83
+ this._rafThrottled[id] = {
84
+ func: func,
85
+ pending: true,
86
+ rafId: null
87
+ };
88
+
89
+ function frame() {
90
+ if (self._rafThrottled[id] && self._rafThrottled[id].pending) {
91
+ self._rafThrottled[id].pending = false;
92
+ self._rafThrottled[id].func();
93
+ self._rafThrottled[id].rafId = raf(frame);
94
+ }
95
+ else if (self._rafThrottled[id]) {
96
+ delete self._rafThrottled[id];
97
+ }
98
+ }
99
+
100
+ this._rafThrottled[id].rafId = raf(frame);
101
+ };
102
+
103
+ /**
104
+ * Cancel a RAF-throttled function
105
+ */
106
+ Invoker.prototype.cancelThrottleRAF = function(id) {
107
+ if (this._rafThrottled[id]) {
108
+ if (this._rafThrottled[id].rafId) {
109
+ caf(this._rafThrottled[id].rafId);
110
+ }
111
+ delete this._rafThrottled[id];
112
+ }
113
+ };
114
+
115
+ /**
116
+ * Schedule a callback for the next animation frame
117
+ * Multiple callbacks are batched together
118
+ */
119
+ Invoker.prototype.scheduleFrame = function(callback) {
120
+ var self = this;
121
+
122
+ this._frameCallbacks.push(callback);
123
+
124
+ if (!this._frameScheduled) {
125
+ this._frameScheduled = true;
126
+ raf(function() {
127
+ self._frameScheduled = false;
128
+ var callbacks = self._frameCallbacks;
129
+
130
+ self._frameCallbacks = [];
131
+ for (var i = 0; i < callbacks.length; i++) {
132
+ callbacks[i]();
133
+ }
134
+ });
135
+ }
136
+ };
137
+
138
+ /**
139
+ * Creates a debounced function
140
+ */
53
141
  Invoker.prototype.debounce = function(func, wait, immediate) {
54
142
  var timeout;
55
143
 
@@ -77,5 +165,133 @@
77
165
  };
78
166
  };
79
167
 
168
+ /**
169
+ * Creates a leading-edge throttled function using RAF
170
+ * Executes immediately, then throttles subsequent calls
171
+ */
172
+ Invoker.prototype.throttleLeading = function(func) {
173
+ var scheduled = false;
174
+ var savedArgs = null;
175
+ var savedThis = null;
176
+
177
+ return function throttled() {
178
+ if (scheduled) {
179
+ savedArgs = arguments;
180
+ // eslint-disable-next-line consistent-this
181
+ savedThis = this;
182
+ return;
183
+ }
184
+
185
+ func.apply(this, arguments);
186
+ scheduled = true;
187
+
188
+ raf(function() {
189
+ scheduled = false;
190
+ if (savedArgs) {
191
+ func.apply(savedThis, savedArgs);
192
+ savedArgs = null;
193
+ savedThis = null;
194
+ }
195
+ });
196
+ };
197
+ };
198
+
199
+ /**
200
+ * Creates a trailing-edge throttled function using RAF
201
+ * Collects calls and executes only the last one per frame
202
+ */
203
+ Invoker.prototype.throttleTrailing = function(func) {
204
+ var rafId = null;
205
+ var savedArgs = null;
206
+ var savedThis = null;
207
+
208
+ return function throttled() {
209
+ savedArgs = arguments;
210
+ // eslint-disable-next-line consistent-this
211
+ savedThis = this;
212
+
213
+ if (rafId === null) {
214
+ rafId = raf(function() {
215
+ rafId = null;
216
+ func.apply(savedThis, savedArgs);
217
+ });
218
+ }
219
+ };
220
+ };
221
+
222
+ /**
223
+ * Batch multiple function calls into a single execution
224
+ * Useful for accumulating updates before rendering
225
+ */
226
+ Invoker.prototype.createBatcher = function(processFn, options) {
227
+ options = options || {};
228
+ var maxWait = options.maxWait || 100;
229
+ var items = [];
230
+ var timeout = null;
231
+ var lastFlush = 0;
232
+
233
+ function flush() {
234
+ if (items.length === 0) {
235
+ return;
236
+ }
237
+ var batch = items;
238
+
239
+ items = [];
240
+ timeout = null;
241
+ lastFlush = Date.now();
242
+ processFn(batch);
243
+ }
244
+
245
+ return {
246
+ add: function(item) {
247
+ items.push(item);
248
+
249
+ if (timeout === null) {
250
+ var timeSinceLastFlush = Date.now() - lastFlush;
251
+
252
+ if (timeSinceLastFlush >= maxWait) {
253
+ raf(flush);
254
+ }
255
+ else {
256
+ timeout = setTimeout(flush, Math.min(16, maxWait - timeSinceLastFlush));
257
+ }
258
+ }
259
+ },
260
+ flush: flush,
261
+ size: function() {
262
+ return items.length;
263
+ }
264
+ };
265
+ };
266
+
267
+ /**
268
+ * Destroy all pending operations
269
+ */
270
+ Invoker.prototype.destroy = function() {
271
+ var id;
272
+
273
+ // Clear interval-based throttles
274
+ for (id in this._throttledFunc) {
275
+ if (Object.prototype.hasOwnProperty.call(this._throttledFunc, id)) {
276
+ clearInterval(this._throttledFunc[id].timer);
277
+ }
278
+ }
279
+ this._throttledFunc = {};
280
+
281
+ // Clear RAF-based throttles
282
+ for (id in this._rafThrottled) {
283
+ if (Object.prototype.hasOwnProperty.call(this._rafThrottled, id)) {
284
+ if (this._rafThrottled[id].rafId) {
285
+ caf(this._rafThrottled[id].rafId);
286
+ }
287
+ }
288
+ }
289
+ this._rafThrottled = {};
290
+
291
+ // Clear frame callbacks
292
+ this._frameCallbacks = [];
293
+ this._frameScheduled = false;
294
+ };
295
+
80
296
  return Invoker;
81
297
  });
@@ -195,8 +195,12 @@ define([
195
195
  LineGroup.prototype.addSource = function(source, sourceGroup, sourcesAround) {
196
196
  if (sourceGroup) {
197
197
  this._sourcesGroup[source.id] = sourceGroup;
198
- if (!sourceGroup.getParent() || !sourceGroup.isDescendantOf(this._group)) {
199
- sourceGroup.moveTo(this._group);
198
+ // Only move to this group if not currently being dragged
199
+ // (during drag, source stays in drag layer for z-order)
200
+ if (!sourceGroup.isActive()) {
201
+ if (!sourceGroup.getParent() || !sourceGroup.isDescendantOf(this._group)) {
202
+ sourceGroup.moveTo(this._group);
203
+ }
200
204
  }
201
205
  }
202
206
 
@@ -342,10 +346,6 @@ define([
342
346
 
343
347
  delete this._sourcesGroup[source.id];
344
348
 
345
- if (sourceGroup) {
346
- sourceGroup.hideButKeepFocus();
347
- }
348
-
349
349
  return sourceGroup;
350
350
  };
351
351
 
@@ -30,6 +30,7 @@ define([
30
30
 
31
31
  this._automaticallyCreatedLineId = null;
32
32
  this._automaticLineCreationPosition = null;
33
+ this._automaticLineCreationMouseY = null;
33
34
  this._automaticLineCreationTimeout = null;
34
35
 
35
36
  this._segmentsGroups = {};
@@ -141,7 +142,6 @@ define([
141
142
 
142
143
  LineGroups.prototype._onLineHeightChanged = function() {
143
144
  this.refreshLineYs();
144
- this._view.updateTimeline();
145
145
  };
146
146
 
147
147
  LineGroups.prototype._onSourcesWrappingChanged = function(sources) {
@@ -282,10 +282,11 @@ define([
282
282
  }
283
283
  this._automaticLineCreationTimeout = null;
284
284
  this._automaticLineCreationPosition = null;
285
+ this._automaticLineCreationMouseY = null;
285
286
  this._automaticallyCreatedLineId = null;
286
287
  };
287
288
 
288
- LineGroups.prototype.manageAutomaticLineCreation = function(newLinePosition, initialPosition, sources) {
289
+ LineGroups.prototype.manageAutomaticLineCreation = function(newLinePosition, initialPosition, sources, mouseY) {
289
290
  if (!Utils.isNullOrUndefined(this._automaticallyCreatedLineId)) {
290
291
  return;
291
292
  }
@@ -301,6 +302,7 @@ define([
301
302
  }
302
303
 
303
304
  this._automaticLineCreationPosition = newLinePosition;
305
+ this._automaticLineCreationMouseY = mouseY;
304
306
  this._automaticLineCreationTimeout = setTimeout(function() {
305
307
  this._automaticLineCreationTimeout = null;
306
308
 
@@ -344,9 +346,44 @@ define([
344
346
 
345
347
  this._automaticallyCreatedLineId = automaticallyCreatedLineGroup.getId();
346
348
  this._moveSourcesToPositionIfPossible(sources, newLinePosition);
349
+
350
+ // After creating and moving to the new line, nudge the vertical scroll so
351
+ // the pointer is closer to the center of that line.
352
+ this._nudgeFrameOffsetYToLineCenter(newLinePosition, this._automaticLineCreationMouseY);
353
+
354
+ // Notify that sources have been moved to a new line (for ghost preview updates)
355
+ this._peaks.emit('sources.delayedLineChange', sources);
347
356
  }.bind(this), this._peaks.options.automaticLineCreationDelay);
348
357
  };
349
358
 
359
+ LineGroups.prototype._nudgeFrameOffsetYToLineCenter = function(linePosition, mouseY) {
360
+ if (!this._peaks.options.enableVerticalScrolling) {
361
+ return;
362
+ }
363
+
364
+ if (Utils.isNullOrUndefined(mouseY)) {
365
+ return;
366
+ }
367
+
368
+ const lineGroup = this._lineGroupsByPosition[linePosition];
369
+
370
+ if (!lineGroup) {
371
+ return;
372
+ }
373
+
374
+ const targetCenterY = lineGroup.y() + (lineGroup.lineHeight() / 2);
375
+ const deltaY = targetCenterY - mouseY;
376
+
377
+ if (deltaY === 0) {
378
+ return;
379
+ }
380
+
381
+ const maxOffsetY = Math.max(0, this._view.getFullHeight() - this._view.getHeight());
382
+ const nextOffsetY = Utils.clamp(this._view.getFrameOffsetY() + deltaY, 0, maxOffsetY);
383
+
384
+ this._view.updateTimeline(null, nextOffsetY);
385
+ };
386
+
350
387
  LineGroups.prototype.manageVerticalPosition = function(sources, startTime, endTime, mouseX, mouseY) {
351
388
  if (Utils.isNullOrUndefined(mouseX)) {
352
389
  return;
@@ -361,7 +398,7 @@ define([
361
398
  }
362
399
 
363
400
  if (linePos[0] !== linePos[1]) {
364
- this.manageAutomaticLineCreation(linePos[0] + 1, position, sources);
401
+ this.manageAutomaticLineCreation(linePos[0] + 1, position, sources, mouseY);
365
402
  }
366
403
  else {
367
404
  this.stopAutomaticLineCreation();