wavesurfer.js 4.5.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGES.md +36 -1
  2. package/README.md +6 -3
  3. package/dist/plugin/wavesurfer.cursor.js +39 -34
  4. package/dist/plugin/wavesurfer.cursor.js.map +1 -1
  5. package/dist/plugin/wavesurfer.cursor.min.js +2 -2
  6. package/dist/plugin/wavesurfer.cursor.min.js.map +1 -1
  7. package/dist/plugin/wavesurfer.elan.js +29 -27
  8. package/dist/plugin/wavesurfer.elan.js.map +1 -1
  9. package/dist/plugin/wavesurfer.elan.min.js +2 -2
  10. package/dist/plugin/wavesurfer.elan.min.js.map +1 -1
  11. package/dist/plugin/wavesurfer.markers.js +381 -0
  12. package/dist/plugin/wavesurfer.markers.js.map +1 -0
  13. package/dist/plugin/wavesurfer.markers.min.js +7 -0
  14. package/dist/plugin/wavesurfer.markers.min.js.map +1 -0
  15. package/dist/plugin/wavesurfer.mediasession.js +29 -27
  16. package/dist/plugin/wavesurfer.mediasession.js.map +1 -1
  17. package/dist/plugin/wavesurfer.mediasession.min.js +2 -2
  18. package/dist/plugin/wavesurfer.mediasession.min.js.map +1 -1
  19. package/dist/plugin/wavesurfer.microphone.js +29 -27
  20. package/dist/plugin/wavesurfer.microphone.js.map +1 -1
  21. package/dist/plugin/wavesurfer.microphone.min.js +2 -2
  22. package/dist/plugin/wavesurfer.microphone.min.js.map +1 -1
  23. package/dist/plugin/wavesurfer.minimap.js +30 -28
  24. package/dist/plugin/wavesurfer.minimap.js.map +1 -1
  25. package/dist/plugin/wavesurfer.minimap.min.js +2 -2
  26. package/dist/plugin/wavesurfer.minimap.min.js.map +1 -1
  27. package/dist/plugin/wavesurfer.playhead.js +324 -0
  28. package/dist/plugin/wavesurfer.playhead.js.map +1 -0
  29. package/dist/plugin/wavesurfer.playhead.min.js +7 -0
  30. package/dist/plugin/wavesurfer.playhead.min.js.map +1 -0
  31. package/dist/plugin/wavesurfer.regions.js +152 -126
  32. package/dist/plugin/wavesurfer.regions.js.map +1 -1
  33. package/dist/plugin/wavesurfer.regions.min.js +2 -2
  34. package/dist/plugin/wavesurfer.regions.min.js.map +1 -1
  35. package/dist/plugin/wavesurfer.spectrogram.js +97 -89
  36. package/dist/plugin/wavesurfer.spectrogram.js.map +1 -1
  37. package/dist/plugin/wavesurfer.spectrogram.min.js +2 -2
  38. package/dist/plugin/wavesurfer.spectrogram.min.js.map +1 -1
  39. package/dist/plugin/wavesurfer.timeline.js +46 -32
  40. package/dist/plugin/wavesurfer.timeline.js.map +1 -1
  41. package/dist/plugin/wavesurfer.timeline.min.js +2 -2
  42. package/dist/plugin/wavesurfer.timeline.min.js.map +1 -1
  43. package/dist/wavesurfer-html-init.js +9 -5
  44. package/dist/wavesurfer-html-init.js.map +1 -1
  45. package/dist/wavesurfer-html-init.min.js +2 -2
  46. package/dist/wavesurfer-html-init.min.js.map +1 -1
  47. package/dist/wavesurfer.js +350 -166
  48. package/dist/wavesurfer.js.map +1 -1
  49. package/dist/wavesurfer.min.js +2 -2
  50. package/dist/wavesurfer.min.js.map +1 -1
  51. package/package.json +19 -16
  52. package/src/drawer.canvasentry.js +16 -0
  53. package/src/drawer.js +30 -21
  54. package/src/drawer.multicanvas.js +63 -39
  55. package/src/plugin/cursor/index.js +3 -1
  56. package/src/plugin/markers/index.js +282 -0
  57. package/src/plugin/playhead/index.js +226 -0
  58. package/src/plugin/regions/index.js +28 -11
  59. package/src/plugin/regions/region.js +83 -70
  60. package/src/plugin/spectrogram/index.js +65 -61
  61. package/src/plugin/timeline/index.js +20 -11
  62. package/src/util/index.js +1 -0
  63. package/src/util/orientation.js +98 -0
  64. package/src/wavesurfer.js +18 -9
  65. package/src/webaudio.js +10 -9
@@ -15,6 +15,7 @@ export class Region {
15
15
  this.util = ws.util;
16
16
  this.style = this.util.style;
17
17
  this.regionsUtil = regionsUtils;
18
+ this.vertical = ws.drawer.params.vertical;
18
19
 
19
20
  this.id = params.id == null ? ws.util.getId() : params.id;
20
21
  this.start = Number(params.start) || 0;
@@ -42,6 +43,7 @@ export class Region {
42
43
  this.handleRightEl = null;
43
44
  this.data = params.data || {};
44
45
  this.attributes = params.attributes || {};
46
+ this.showTooltip = params.showTooltip ?? true;
45
47
 
46
48
  this.maxLength = params.maxLength;
47
49
  // It assumes the minLength parameter value, or the regionsMinLength parameter value, if the first one not provided
@@ -129,7 +131,7 @@ export class Region {
129
131
  /* Remove a single region. */
130
132
  remove() {
131
133
  if (this.element) {
132
- this.wrapper.removeChild(this.element);
134
+ this.wrapper.removeChild(this.element.domElement);
133
135
  this.element = null;
134
136
  this.fireEvent('remove');
135
137
  this.wavesurfer.un('zoom', this._onRedraw);
@@ -168,20 +170,25 @@ export class Region {
168
170
 
169
171
  /* Render a region as a DOM element. */
170
172
  render() {
171
- const regionEl = document.createElement('region');
173
+ this.element = this.util.withOrientation(
174
+ this.wrapper.appendChild(document.createElement('region')),
175
+ this.vertical
176
+ );
172
177
 
173
- regionEl.className = 'wavesurfer-region';
174
- regionEl.title = this.formatTime(this.start, this.end);
175
- regionEl.setAttribute('data-id', this.id);
178
+ this.element.className = 'wavesurfer-region';
179
+ if (this.showTooltip) {
180
+ this.element.title = this.formatTime(this.start, this.end);
181
+ }
182
+ this.element.setAttribute('data-id', this.id);
176
183
 
177
184
  for (const attrname in this.attributes) {
178
- regionEl.setAttribute(
185
+ this.element.setAttribute(
179
186
  'data-region-' + attrname,
180
187
  this.attributes[attrname]
181
188
  );
182
189
  }
183
190
 
184
- this.style(regionEl, {
191
+ this.style(this.element, {
185
192
  position: 'absolute',
186
193
  zIndex: 2,
187
194
  height: this.regionHeight,
@@ -190,11 +197,13 @@ export class Region {
190
197
 
191
198
  /* Resize handles */
192
199
  if (this.resize) {
193
- this.handleLeftEl = regionEl.appendChild(
194
- document.createElement('handle')
200
+ this.handleLeftEl = this.util.withOrientation(
201
+ this.element.appendChild(document.createElement('handle')),
202
+ this.vertical
195
203
  );
196
- this.handleRightEl = regionEl.appendChild(
197
- document.createElement('handle')
204
+ this.handleRightEl = this.util.withOrientation(
205
+ this.element.appendChild(document.createElement('handle')),
206
+ this.vertical
198
207
  );
199
208
 
200
209
  this.handleLeftEl.className = 'wavesurfer-handle wavesurfer-handle-start';
@@ -202,7 +211,7 @@ export class Region {
202
211
 
203
212
  // Default CSS properties for both handles.
204
213
  const css = {
205
- cursor: 'col-resize',
214
+ cursor: this.vertical ? 'row-resize' : 'col-resize',
206
215
  position: 'absolute',
207
216
  top: '0px',
208
217
  width: '2px',
@@ -213,7 +222,11 @@ export class Region {
213
222
  // Merge CSS properties per handle.
214
223
  const handleLeftCss =
215
224
  this.handleStyle.left !== 'none'
216
- ? Object.assign({ left: '0px' }, css, this.handleStyle.left)
225
+ ? Object.assign(
226
+ { left: '0px' },
227
+ css,
228
+ this.handleStyle.left
229
+ )
217
230
  : null;
218
231
  const handleRightCss =
219
232
  this.handleStyle.right !== 'none'
@@ -233,9 +246,8 @@ export class Region {
233
246
  }
234
247
  }
235
248
 
236
- this.element = this.wrapper.appendChild(regionEl);
237
249
  this.updateRender();
238
- this.bindEvents(regionEl);
250
+ this.bindEvents();
239
251
  }
240
252
 
241
253
  formatTime(start, end) {
@@ -301,7 +313,9 @@ export class Region {
301
313
  );
302
314
  }
303
315
 
304
- this.element.title = this.formatTime(this.start, this.end);
316
+ if (this.showTooltip) {
317
+ this.element.title = this.formatTime(this.start, this.end);
318
+ }
305
319
  }
306
320
  }
307
321
 
@@ -407,20 +421,21 @@ export class Region {
407
421
  let regionRightHalfTime;
408
422
 
409
423
  // Scroll when the user is dragging within the threshold
410
- const edgeScroll = (e) => {
424
+ const edgeScroll = (event) => {
425
+ let orientedEvent = this.util.withOrientation(event, this.vertical);
411
426
  const duration = this.wavesurfer.getDuration();
412
427
  if (!scrollDirection || (!drag && !resize)) {
413
428
  return;
414
429
  }
415
430
 
416
- const x = e.clientX;
431
+ const x = orientedEvent.clientX;
417
432
  let distanceBetweenCursorAndWrapperEdge = 0;
418
433
  let regionHalfTimeWidth = 0;
419
434
  let adjustment = 0;
420
435
 
421
436
  // Get the currently selected time according to the mouse position
422
437
  let time = this.regionsUtil.getRegionSnapToGridValue(
423
- this.wavesurfer.drawer.handleEvent(e) * duration
438
+ this.wavesurfer.drawer.handleEvent(event) * duration
424
439
  );
425
440
 
426
441
  if (drag) {
@@ -461,26 +476,28 @@ export class Region {
461
476
  }
462
477
 
463
478
  // Don't edgescroll if region has reached min or max limit
479
+ const wrapperScrollLeft = this.wrapper.scrollLeft;
480
+
464
481
  if (scrollDirection === -1) {
465
- if (Math.round(this.wrapper.scrollLeft) === 0) {
482
+ if (Math.round(wrapperScrollLeft) === 0) {
466
483
  return;
467
484
  }
468
485
 
469
- if (Math.round(this.wrapper.scrollLeft - regionHalfTimeWidth + distanceBetweenCursorAndWrapperEdge) <= 0) {
486
+ if (Math.round(wrapperScrollLeft - regionHalfTimeWidth + distanceBetweenCursorAndWrapperEdge) <= 0) {
470
487
  return;
471
488
  }
472
489
  } else {
473
- if (Math.round(this.wrapper.scrollLeft) === maxScroll) {
490
+ if (Math.round(wrapperScrollLeft) === maxScroll) {
474
491
  return;
475
492
  }
476
493
 
477
- if (Math.round(this.wrapper.scrollLeft + regionHalfTimeWidth - distanceBetweenCursorAndWrapperEdge) >= maxScroll) {
494
+ if (Math.round(wrapperScrollLeft + regionHalfTimeWidth - distanceBetweenCursorAndWrapperEdge) >= maxScroll) {
478
495
  return;
479
496
  }
480
497
  }
481
498
 
482
499
  // Update scroll position
483
- let scrollLeft = this.wrapper.scrollLeft - adjustment + scrollSpeed * scrollDirection;
500
+ let scrollLeft = wrapperScrollLeft - adjustment + scrollSpeed * scrollDirection;
484
501
 
485
502
  if (scrollDirection === -1) {
486
503
  const calculatedLeft = Math.max(0 + regionHalfTimeWidth - distanceBetweenCursorAndWrapperEdge, scrollLeft);
@@ -498,26 +515,26 @@ export class Region {
498
515
 
499
516
  // Repeat
500
517
  window.requestAnimationFrame(() => {
501
- edgeScroll(e);
518
+ edgeScroll(event);
502
519
  });
503
520
  };
504
521
 
505
- const onDown = (e) => {
522
+ const onDown = (event) => {
506
523
  const duration = this.wavesurfer.getDuration();
507
- if (e.touches && e.touches.length > 1) {
524
+ if (event.touches && event.touches.length > 1) {
508
525
  return;
509
526
  }
510
- touchId = e.targetTouches ? e.targetTouches[0].identifier : null;
527
+ touchId = event.targetTouches ? event.targetTouches[0].identifier : null;
511
528
 
512
529
  // stop the event propagation, if this region is resizable or draggable
513
530
  // and the event is therefore handled here.
514
531
  if (this.drag || this.resize) {
515
- e.stopPropagation();
532
+ event.stopPropagation();
516
533
  }
517
534
 
518
535
  // Store the selected startTime we begun dragging or resizing
519
536
  startTime = this.regionsUtil.getRegionSnapToGridValue(
520
- this.wavesurfer.drawer.handleEvent(e, true) * duration
537
+ this.wavesurfer.drawer.handleEvent(event, true) * duration
521
538
  );
522
539
 
523
540
  // Store the selected point of contact when we begin dragging
@@ -526,13 +543,17 @@ export class Region {
526
543
 
527
544
  // Store for scroll calculations
528
545
  maxScroll = this.wrapper.scrollWidth - this.wrapper.clientWidth;
529
- wrapperRect = this.wrapper.getBoundingClientRect();
546
+
547
+ wrapperRect = this.util.withOrientation(
548
+ this.wrapper.getBoundingClientRect(),
549
+ this.vertical
550
+ );
530
551
 
531
552
  this.isResizing = false;
532
553
  this.isDragging = false;
533
- if (e.target.tagName.toLowerCase() === 'handle') {
554
+ if (event.target.tagName.toLowerCase() === 'handle') {
534
555
  this.isResizing = true;
535
- resize = e.target.classList.contains('wavesurfer-handle-start')
556
+ resize = event.target.classList.contains('wavesurfer-handle-start')
536
557
  ? 'start'
537
558
  : 'end';
538
559
  } else {
@@ -541,8 +562,8 @@ export class Region {
541
562
  resize = false;
542
563
  }
543
564
  };
544
- const onUp = (e) => {
545
- if (e.touches && e.touches.length > 1) {
565
+ const onUp = (event) => {
566
+ if (event.touches && event.touches.length > 1) {
546
567
  return;
547
568
  }
548
569
 
@@ -557,17 +578,18 @@ export class Region {
557
578
  if (updated) {
558
579
  updated = false;
559
580
  this.util.preventClick();
560
- this.fireEvent('update-end', e);
561
- this.wavesurfer.fireEvent('region-update-end', this, e);
581
+ this.fireEvent('update-end', event);
582
+ this.wavesurfer.fireEvent('region-update-end', this, event);
562
583
  }
563
584
  };
564
- const onMove = (e) => {
585
+ const onMove = (event) => {
565
586
  const duration = this.wavesurfer.getDuration();
587
+ let orientedEvent = this.util.withOrientation(event, this.vertical);
566
588
 
567
- if (e.touches && e.touches.length > 1) {
589
+ if (event.touches && event.touches.length > 1) {
568
590
  return;
569
591
  }
570
- if (e.targetTouches && e.targetTouches[0].identifier != touchId) {
592
+ if (event.targetTouches && event.targetTouches[0].identifier != touchId) {
571
593
  return;
572
594
  }
573
595
  if (!drag && !resize) {
@@ -576,7 +598,7 @@ export class Region {
576
598
 
577
599
  const oldTime = startTime;
578
600
  let time = this.regionsUtil.getRegionSnapToGridValue(
579
- this.wavesurfer.drawer.handleEvent(e) * duration
601
+ this.wavesurfer.drawer.handleEvent(event) * duration
580
602
  );
581
603
 
582
604
  if (drag) {
@@ -634,36 +656,22 @@ export class Region {
634
656
  }
635
657
 
636
658
  if (
637
- this.scroll &&
638
- container.clientWidth < this.wrapper.scrollWidth
659
+ this.scroll && container.clientWidth < this.wrapper.scrollWidth
639
660
  ) {
640
661
  // Triggering edgescroll from within edgeScrollWidth
641
- if (drag) {
642
- let x = e.clientX;
643
-
644
- // Check direction
645
- if (x < wrapperRect.left + this.edgeScrollWidth) {
646
- scrollDirection = -1;
647
- } else if (x > wrapperRect.right - this.edgeScrollWidth) {
648
- scrollDirection = 1;
649
- } else {
650
- scrollDirection = null;
651
- }
662
+ let x = orientedEvent.clientX;
663
+
664
+ // Check direction
665
+ if (x < wrapperRect.left + this.edgeScrollWidth) {
666
+ scrollDirection = -1;
667
+ } else if (x > wrapperRect.right - this.edgeScrollWidth) {
668
+ scrollDirection = 1;
652
669
  } else {
653
- let x = e.clientX;
654
-
655
- // Check direction
656
- if (x < wrapperRect.left + this.edgeScrollWidth) {
657
- scrollDirection = -1;
658
- } else if (x > wrapperRect.right - this.edgeScrollWidth) {
659
- scrollDirection = 1;
660
- } else {
661
- scrollDirection = null;
662
- }
670
+ scrollDirection = null;
663
671
  }
664
672
 
665
673
  if (scrollDirection) {
666
- edgeScroll(e);
674
+ edgeScroll(event);
667
675
  }
668
676
  }
669
677
  };
@@ -672,20 +680,20 @@ export class Region {
672
680
  this.element.addEventListener('touchstart', onDown);
673
681
 
674
682
  document.body.addEventListener('mousemove', onMove);
675
- document.body.addEventListener('touchmove', onMove);
683
+ document.body.addEventListener('touchmove', onMove, {passive: false});
676
684
 
677
- document.body.addEventListener('mouseup', onUp);
685
+ document.addEventListener('mouseup', onUp);
678
686
  document.body.addEventListener('touchend', onUp);
679
687
 
680
688
  this.on('remove', () => {
681
- document.body.removeEventListener('mouseup', onUp);
689
+ document.removeEventListener('mouseup', onUp);
682
690
  document.body.removeEventListener('touchend', onUp);
683
691
  document.body.removeEventListener('mousemove', onMove);
684
692
  document.body.removeEventListener('touchmove', onMove);
685
693
  });
686
694
 
687
695
  this.wavesurfer.on('destroy', () => {
688
- document.body.removeEventListener('mouseup', onUp);
696
+ document.removeEventListener('mouseup', onUp);
689
697
  document.body.removeEventListener('touchend', onUp);
690
698
  });
691
699
  }
@@ -750,7 +758,12 @@ export class Region {
750
758
  }
751
759
 
752
760
  updateHandlesResize(resize) {
753
- const cursorStyle = resize ? 'col-resize' : 'auto';
761
+ let cursorStyle;
762
+ if (resize) {
763
+ cursorStyle = this.vertical ? 'row-resize' : 'col-resize';
764
+ } else {
765
+ cursorStyle = 'auto';
766
+ }
754
767
 
755
768
  this.handleLeftEl && this.style(this.handleLeftEl, { cursor: cursorStyle });
756
769
  this.handleRightEl && this.style(this.handleRightEl, { cursor: cursorStyle });
@@ -254,30 +254,32 @@ export default class SpectrogramPlugin {
254
254
  const width = my.width;
255
255
  const pixels = my.resample(frequenciesData);
256
256
  const heightFactor = my.buffer ? 2 / my.buffer.numberOfChannels : 1;
257
- const imageData = spectrCc.createImageData(width, height);
258
- let i;
259
- let j;
260
- let k;
261
-
262
- for (i = 0; i < pixels.length; i++) {
263
- for (j = 0; j < pixels[i].length; j++) {
264
- const colorMap = my.colorMap[pixels[i][j]];
265
- /* eslint-disable max-depth */
266
- for (k = 0; k < heightFactor; k++) {
267
- let y = height - j * heightFactor;
268
- if (heightFactor === 2 && k === 1) {
269
- y--;
257
+ if (spectrCc) {
258
+ const imageData = spectrCc.createImageData(width, height);
259
+ let i;
260
+ let j;
261
+ let k;
262
+
263
+ for (i = 0; i < pixels.length; i++) {
264
+ for (j = 0; j < pixels[i].length; j++) {
265
+ const colorMap = my.colorMap[pixels[i][j]];
266
+ /* eslint-disable max-depth */
267
+ for (k = 0; k < heightFactor; k++) {
268
+ let y = height - j * heightFactor;
269
+ if (heightFactor === 2 && k === 1) {
270
+ y--;
271
+ }
272
+ const redIndex = y * (width * 4) + i * 4;
273
+ imageData.data[redIndex] = colorMap[0] * 255;
274
+ imageData.data[redIndex + 1] = colorMap[1] * 255;
275
+ imageData.data[redIndex + 2] = colorMap[2] * 255;
276
+ imageData.data[redIndex + 3] = colorMap[3] * 255;
270
277
  }
271
- const redIndex = y * (width * 4) + i * 4;
272
- imageData.data[redIndex] = colorMap[0] * 255;
273
- imageData.data[redIndex + 1] = colorMap[1] * 255;
274
- imageData.data[redIndex + 2] = colorMap[2] * 255;
275
- imageData.data[redIndex + 3] = colorMap[3] * 255;
278
+ /* eslint-enable max-depth */
276
279
  }
277
- /* eslint-enable max-depth */
278
280
  }
281
+ spectrCc.putImageData(imageData, 0, 0);
279
282
  }
280
- spectrCc.putImageData(imageData, 0, 0);
281
283
  }
282
284
 
283
285
  getFrequencies(callback) {
@@ -378,47 +380,49 @@ export default class SpectrogramPlugin {
378
380
  this.labelsEl.height = this.height;
379
381
  this.labelsEl.width = bgWidth;
380
382
 
381
- // fill background
382
- ctx.fillStyle = bgFill;
383
- ctx.fillRect(0, 0, bgWidth, getMaxY);
384
- ctx.fill();
385
- let i;
386
-
387
- // render labels
388
- for (i = 0; i <= labelIndex; i++) {
389
- ctx.textAlign = textAlign;
390
- ctx.textBaseline = 'middle';
391
-
392
- const freq = freqStart + step * i;
393
- const index = Math.round(
394
- (freq / (this.sampleRate / 2)) * this.fftSamples
395
- );
396
- const label = this.freqType(freq);
397
- const units = this.unitType(freq);
398
- const yLabelOffset = 2;
399
- const x = 16;
400
- let y;
401
-
402
- if (i == 0) {
403
- y = getMaxY + i - 10;
404
- // unit label
405
- ctx.fillStyle = textColorUnit;
406
- ctx.font = fontSizeUnit + ' ' + fontType;
407
- ctx.fillText(units, x + 24, y);
408
- // freq label
409
- ctx.fillStyle = textColorFreq;
410
- ctx.font = fontSizeFreq + ' ' + fontType;
411
- ctx.fillText(label, x, y);
412
- } else {
413
- y = getMaxY - i * 50 + yLabelOffset;
414
- // unit label
415
- ctx.fillStyle = textColorUnit;
416
- ctx.font = fontSizeUnit + ' ' + fontType;
417
- ctx.fillText(units, x + 24, y);
418
- // freq label
419
- ctx.fillStyle = textColorFreq;
420
- ctx.font = fontSizeFreq + ' ' + fontType;
421
- ctx.fillText(label, x, y);
383
+ if (ctx) {
384
+ // fill background
385
+ ctx.fillStyle = bgFill;
386
+ ctx.fillRect(0, 0, bgWidth, getMaxY);
387
+ ctx.fill();
388
+ let i;
389
+
390
+ // render labels
391
+ for (i = 0; i <= labelIndex; i++) {
392
+ ctx.textAlign = textAlign;
393
+ ctx.textBaseline = 'middle';
394
+
395
+ const freq = freqStart + step * i;
396
+ const index = Math.round(
397
+ (freq / (this.sampleRate / 2)) * this.fftSamples
398
+ );
399
+ const label = this.freqType(freq);
400
+ const units = this.unitType(freq);
401
+ const yLabelOffset = 2;
402
+ const x = 16;
403
+ let y;
404
+
405
+ if (i == 0) {
406
+ y = getMaxY + i - 10;
407
+ // unit label
408
+ ctx.fillStyle = textColorUnit;
409
+ ctx.font = fontSizeUnit + ' ' + fontType;
410
+ ctx.fillText(units, x + 24, y);
411
+ // freq label
412
+ ctx.fillStyle = textColorFreq;
413
+ ctx.font = fontSizeFreq + ' ' + fontType;
414
+ ctx.fillText(label, x, y);
415
+ } else {
416
+ y = getMaxY - i * 50 + yLabelOffset;
417
+ // unit label
418
+ ctx.fillStyle = textColorUnit;
419
+ ctx.font = fontSizeUnit + ' ' + fontType;
420
+ ctx.fillText(units, x + 24, y);
421
+ // freq label
422
+ ctx.fillStyle = textColorFreq;
423
+ ctx.font = fontSizeFreq + ' ' + fontType;
424
+ ctx.fillText(label, x, y);
425
+ }
422
426
  }
423
427
  }
424
428
  }
@@ -434,7 +434,10 @@ export default class TimelinePlugin {
434
434
  */
435
435
  setFillStyles(fillStyle) {
436
436
  this.canvases.forEach(canvas => {
437
- canvas.getContext('2d').fillStyle = fillStyle;
437
+ const context = canvas.getContext('2d');
438
+ if (context) {
439
+ context.fillStyle = fillStyle;
440
+ }
438
441
  });
439
442
  }
440
443
 
@@ -445,7 +448,10 @@ export default class TimelinePlugin {
445
448
  */
446
449
  setFonts(font) {
447
450
  this.canvases.forEach(canvas => {
448
- canvas.getContext('2d').font = font;
451
+ const context = canvas.getContext('2d');
452
+ if (context) {
453
+ context.font = font;
454
+ }
449
455
  });
450
456
  }
451
457
 
@@ -471,14 +477,17 @@ export default class TimelinePlugin {
471
477
  };
472
478
 
473
479
  if (intersection.x1 < intersection.x2) {
474
- canvas
475
- .getContext('2d')
476
- .fillRect(
477
- intersection.x1 - leftOffset,
478
- intersection.y1,
479
- intersection.x2 - intersection.x1,
480
- intersection.y2 - intersection.y1
481
- );
480
+ const context = canvas
481
+ .getContext('2d');
482
+ if (context) {
483
+ context
484
+ .fillRect(
485
+ intersection.x1 - leftOffset,
486
+ intersection.y1,
487
+ intersection.x2 - intersection.x1,
488
+ intersection.y2 - intersection.y1
489
+ );
490
+ }
482
491
  }
483
492
  });
484
493
  }
@@ -502,7 +511,7 @@ export default class TimelinePlugin {
502
511
  return;
503
512
  }
504
513
 
505
- if (xOffset + canvasWidth > x) {
514
+ if (xOffset + canvasWidth > x && context) {
506
515
  textWidth = context.measureText(text).width;
507
516
  context.fillText(text, x - xOffset, y);
508
517
  }
package/src/util/index.js CHANGED
@@ -10,3 +10,4 @@ export { default as debounce } from 'debounce';
10
10
  export { default as preventClick } from './prevent-click';
11
11
  export { default as fetchFile } from './fetch';
12
12
  export { default as clamp } from './clamp';
13
+ export { default as withOrientation } from './orientation';
@@ -0,0 +1,98 @@
1
+ const verticalPropMap = {
2
+ width: 'height',
3
+ height: 'width',
4
+
5
+ overflowX: 'overflowY',
6
+ overflowY: 'overflowX',
7
+
8
+ clientWidth: 'clientHeight',
9
+ clientHeight: 'clientWidth',
10
+
11
+ clientX: 'clientY',
12
+ clientY: 'clientX',
13
+
14
+ scrollWidth: 'scrollHeight',
15
+ scrollLeft: 'scrollTop',
16
+
17
+ offsetLeft: 'offsetTop',
18
+ offsetTop: 'offsetLeft',
19
+ offsetHeight: 'offsetWidth',
20
+ offsetWidth: 'offsetHeight',
21
+
22
+ left: 'top',
23
+ right: 'bottom',
24
+ top: 'left',
25
+ bottom: 'right',
26
+
27
+ borderRightStyle: 'borderBottomStyle',
28
+ borderRightWidth: 'borderBottomWidth',
29
+ borderRightColor: 'borderBottomColor'
30
+ };
31
+
32
+ /**
33
+ * Convert a horizontally-oriented property name to a vertical one.
34
+ *
35
+ * @param {string} prop A property name
36
+ * @param {bool} vertical Whether the element is oriented vertically
37
+ * @returns {string} prop, converted appropriately
38
+ */
39
+ function mapProp(prop, vertical) {
40
+ if (Object.prototype.hasOwnProperty.call(verticalPropMap, prop)) {
41
+ return vertical ? verticalPropMap[prop] : prop;
42
+ } else {
43
+ return prop;
44
+ }
45
+ }
46
+
47
+ const isProxy = Symbol("isProxy");
48
+
49
+ /**
50
+ * Returns an appropriately oriented object based on vertical.
51
+ * If vertical is true, attribute getting and setting will be mapped through
52
+ * verticalPropMap, so that e.g. getting the object's .width will give its
53
+ * .height instead.
54
+ * Certain methods of an oriented object will return oriented objects as well.
55
+ * Oriented objects can't be added to the DOM directly since they are Proxy objects
56
+ * and thus fail typechecks. Use domElement to get the actual element for this.
57
+ *
58
+ * @param {object} target The object to be wrapped and oriented
59
+ * @param {bool} vertical Whether the element is oriented vertically
60
+ * @returns {Proxy} An oriented object with attr translation via verticalAttrMap
61
+ * @since 5.0.0
62
+ */
63
+ export default function withOrientation(target, vertical) {
64
+ if (target[isProxy]) {
65
+ return target;
66
+ } else {
67
+ return new Proxy(
68
+ target, {
69
+ get: function(obj, prop, receiver) {
70
+ if (prop === isProxy) {
71
+ return true;
72
+ } else if (prop === 'domElement') {
73
+ return obj;
74
+ } else if (prop === 'style') {
75
+ return withOrientation(obj.style, vertical);
76
+ } else if (prop === 'canvas') {
77
+ return withOrientation(obj.canvas, vertical);
78
+ } else if (prop === 'getBoundingClientRect') {
79
+ return function(...args) {
80
+ return withOrientation(obj.getBoundingClientRect(...args), vertical);
81
+ };
82
+ } else if (prop === 'getContext') {
83
+ return function(...args) {
84
+ return withOrientation(obj.getContext(...args), vertical);
85
+ };
86
+ } else {
87
+ let value = obj[mapProp(prop, vertical)];
88
+ return typeof value == 'function' ? value.bind(obj) : value;
89
+ }
90
+ },
91
+ set: function(obj, prop, value) {
92
+ obj[mapProp(prop, vertical)] = value;
93
+ return true;
94
+ }
95
+ }
96
+ );
97
+ }
98
+ }