wavesurfer.js 7.11.0 → 7.12.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 (91) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/memory-leaks.test.d.ts +7 -0
  3. package/dist/__tests__/memory-leaks.test.js +184 -0
  4. package/dist/__tests__/player.test.js +107 -0
  5. package/dist/__tests__/renderer-utils.test.d.ts +1 -0
  6. package/dist/__tests__/renderer-utils.test.js +373 -0
  7. package/dist/decoder.js +12 -4
  8. package/dist/draggable.d.ts +4 -0
  9. package/dist/draggable.js +20 -5
  10. package/dist/fetcher.js +14 -23
  11. package/dist/player.d.ts +21 -0
  12. package/dist/player.js +86 -0
  13. package/dist/plugins/envelope.cjs +1 -1
  14. package/dist/plugins/envelope.esm.js +1 -1
  15. package/dist/plugins/envelope.js +1 -1
  16. package/dist/plugins/envelope.min.js +1 -1
  17. package/dist/plugins/hover.cjs +1 -1
  18. package/dist/plugins/hover.d.ts +0 -3
  19. package/dist/plugins/hover.esm.js +1 -1
  20. package/dist/plugins/hover.js +1 -1
  21. package/dist/plugins/hover.min.js +1 -1
  22. package/dist/plugins/minimap.cjs +1 -1
  23. package/dist/plugins/minimap.esm.js +1 -1
  24. package/dist/plugins/minimap.js +1 -1
  25. package/dist/plugins/minimap.min.js +1 -1
  26. package/dist/plugins/record.cjs +1 -1
  27. package/dist/plugins/record.esm.js +1 -1
  28. package/dist/plugins/record.js +1 -1
  29. package/dist/plugins/record.min.js +1 -1
  30. package/dist/plugins/regions.cjs +1 -1
  31. package/dist/plugins/regions.esm.js +1 -1
  32. package/dist/plugins/regions.js +1 -1
  33. package/dist/plugins/regions.min.js +1 -1
  34. package/dist/plugins/timeline.cjs +1 -1
  35. package/dist/plugins/timeline.d.ts +1 -1
  36. package/dist/plugins/timeline.esm.js +1 -1
  37. package/dist/plugins/timeline.js +1 -1
  38. package/dist/plugins/timeline.min.js +1 -1
  39. package/dist/plugins/zoom.cjs +1 -1
  40. package/dist/plugins/zoom.d.ts +10 -1
  41. package/dist/plugins/zoom.esm.js +1 -1
  42. package/dist/plugins/zoom.js +1 -1
  43. package/dist/plugins/zoom.min.js +1 -1
  44. package/dist/reactive/__tests__/drag-stream.test.d.ts +1 -0
  45. package/dist/reactive/__tests__/drag-stream.test.js +199 -0
  46. package/dist/reactive/__tests__/event-stream-emitter.test.d.ts +1 -0
  47. package/dist/reactive/__tests__/event-stream-emitter.test.js +230 -0
  48. package/dist/reactive/__tests__/event-streams.test.d.ts +1 -0
  49. package/dist/reactive/__tests__/event-streams.test.js +278 -0
  50. package/dist/reactive/__tests__/media-event-bridge.test.d.ts +1 -0
  51. package/dist/reactive/__tests__/media-event-bridge.test.js +202 -0
  52. package/dist/reactive/__tests__/render-scheduler.test.d.ts +1 -0
  53. package/dist/reactive/__tests__/render-scheduler.test.js +216 -0
  54. package/dist/reactive/__tests__/scroll-stream.test.d.ts +1 -0
  55. package/dist/reactive/__tests__/scroll-stream.test.js +191 -0
  56. package/dist/reactive/__tests__/state-event-emitter.test.d.ts +1 -0
  57. package/dist/reactive/__tests__/state-event-emitter.test.js +234 -0
  58. package/dist/reactive/__tests__/store.test.d.ts +1 -0
  59. package/dist/reactive/__tests__/store.test.js +271 -0
  60. package/dist/reactive/drag-stream.d.ts +48 -0
  61. package/dist/reactive/drag-stream.js +146 -0
  62. package/dist/reactive/event-stream-emitter.d.ts +104 -0
  63. package/dist/reactive/event-stream-emitter.js +151 -0
  64. package/dist/reactive/event-streams.d.ts +65 -0
  65. package/dist/reactive/event-streams.js +146 -0
  66. package/dist/reactive/media-event-bridge.d.ts +51 -0
  67. package/dist/reactive/media-event-bridge.js +152 -0
  68. package/dist/reactive/render-scheduler.d.ts +43 -0
  69. package/dist/reactive/render-scheduler.js +75 -0
  70. package/dist/reactive/scroll-stream.d.ts +93 -0
  71. package/dist/reactive/scroll-stream.js +131 -0
  72. package/dist/reactive/state-event-emitter.d.ts +105 -0
  73. package/dist/reactive/state-event-emitter.js +213 -0
  74. package/dist/reactive/store.d.ts +66 -0
  75. package/dist/reactive/store.js +115 -0
  76. package/dist/renderer-utils.d.ts +126 -0
  77. package/dist/renderer-utils.js +252 -0
  78. package/dist/renderer.d.ts +4 -4
  79. package/dist/renderer.js +133 -197
  80. package/dist/state/__tests__/wavesurfer-state.test.d.ts +1 -0
  81. package/dist/state/__tests__/wavesurfer-state.test.js +268 -0
  82. package/dist/state/wavesurfer-state.d.ts +90 -0
  83. package/dist/state/wavesurfer-state.js +124 -0
  84. package/dist/types.d.ts +167 -1
  85. package/dist/wavesurfer.cjs +1 -1
  86. package/dist/wavesurfer.d.ts +18 -0
  87. package/dist/wavesurfer.esm.js +1 -1
  88. package/dist/wavesurfer.js +46 -8
  89. package/dist/wavesurfer.min.js +1 -1
  90. package/dist/webaudio.js +4 -0
  91. package/package.json +6 -6
@@ -1 +1 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):((t="undefined"!=typeof globalThis?globalThis:t||self).WaveSurfer=t.WaveSurfer||{},t.WaveSurfer.Zoom=e())}(this,(function(){"use strict";class t{constructor(){this.listeners={}}on(t,e,s){if(this.listeners[t]||(this.listeners[t]=new Set),null==s?void 0:s.once){const s=(...i)=>{this.un(t,s),e(...i)};return this.listeners[t].add(s),()=>this.un(t,s)}return this.listeners[t].add(e),()=>this.un(t,e)}un(t,e){var s;null===(s=this.listeners[t])||void 0===s||s.delete(e)}once(t,e){return this.on(t,e,{once:!0})}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}}class e extends t{constructor(t){super(),this.subscriptions=[],this.isDestroyed=!1,this.options=t}onInit(){}_init(t){this.isDestroyed&&(this.subscriptions=[],this.isDestroyed=!1),this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t())),this.subscriptions=[],this.isDestroyed=!0,this.wavesurfer=void 0}}const s={scale:.5,deltaThreshold:5,exponentialZooming:!1,iterations:20};class i extends e{constructor(t){super(t||{}),this.wrapper=void 0,this.container=null,this.accumulatedDelta=0,this.pointerTime=0,this.oldX=0,this.endZoom=0,this.startZoom=0,this.onWheel=t=>{if(this.wavesurfer&&this.container&&!(Math.abs(t.deltaX)>=Math.abs(t.deltaY))&&(t.preventDefault(),this.accumulatedDelta+=-t.deltaY,0===this.startZoom&&this.options.exponentialZooming&&(this.startZoom=this.wavesurfer.getWrapper().clientWidth/this.wavesurfer.getDuration()),0===this.options.deltaThreshold||Math.abs(this.accumulatedDelta)>=this.options.deltaThreshold)){const e=this.wavesurfer.getDuration(),s=0===this.wavesurfer.options.minPxPerSec?this.wavesurfer.getWrapper().scrollWidth/e:this.wavesurfer.options.minPxPerSec,i=t.clientX-this.container.getBoundingClientRect().left,o=this.container.clientWidth,n=this.wavesurfer.getScroll();i===this.oldX&&0!==this.oldX||(this.pointerTime=(n+i)/s),this.oldX=i;const r=this.calculateNewZoom(s,this.accumulatedDelta),h=o/r*(i/o);r*e<o?(this.wavesurfer.zoom(o/e),this.container.scrollLeft=0):(this.wavesurfer.zoom(r),this.container.scrollLeft=(this.pointerTime-h)*r),this.accumulatedDelta=0}},this.calculateNewZoom=(t,e)=>{let s;if(this.options.exponentialZooming){const i=e>0?Math.pow(this.endZoom/this.startZoom,1/(this.options.iterations-1)):Math.pow(this.startZoom/this.endZoom,1/(this.options.iterations-1));s=Math.max(0,t*i)}else s=Math.max(0,t+e*this.options.scale);return Math.min(s,this.options.maxZoom)},this.options=Object.assign({},s,t)}static create(t){return new i(t)}onInit(){var t;this.wrapper=null===(t=this.wavesurfer)||void 0===t?void 0:t.getWrapper(),this.wrapper&&(this.container=this.wrapper.parentElement,this.container.addEventListener("wheel",this.onWheel),void 0===this.options.maxZoom&&(this.options.maxZoom=this.container.clientWidth),this.endZoom=this.options.maxZoom)}destroy(){this.container&&this.container.removeEventListener("wheel",this.onWheel),super.destroy()}}return i}));
1
+ !function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):((t="undefined"!=typeof globalThis?globalThis:t||self).WaveSurfer=t.WaveSurfer||{},t.WaveSurfer.Zoom=i())}(this,(function(){"use strict";class t{constructor(){this.listeners={}}on(t,i,s){if(this.listeners[t]||(this.listeners[t]=new Set),null==s?void 0:s.once){const s=(...e)=>{this.un(t,s),i(...e)};return this.listeners[t].add(s),()=>this.un(t,s)}return this.listeners[t].add(i),()=>this.un(t,i)}un(t,i){var s;null===(s=this.listeners[t])||void 0===s||s.delete(i)}once(t,i){return this.on(t,i,{once:!0})}unAll(){this.listeners={}}emit(t,...i){this.listeners[t]&&this.listeners[t].forEach((t=>t(...i)))}}class i extends t{constructor(t){super(),this.subscriptions=[],this.isDestroyed=!1,this.options=t}onInit(){}_init(t){this.isDestroyed&&(this.subscriptions=[],this.isDestroyed=!1),this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t())),this.subscriptions=[],this.isDestroyed=!0,this.wavesurfer=void 0}}function s(t,i){let s;const e=()=>{s&&(s(),s=void 0),s=t()},o=i.map((t=>t.subscribe(e)));return e(),()=>{s&&(s(),s=void 0),o.forEach((t=>t()))}}function e(t,i){const s=function(t){let i=t;const s=new Set;return{get value(){return i},set(t){Object.is(i,t)||(i=t,s.forEach((t=>t(i))))},update(t){this.set(t(i))},subscribe:t=>(s.add(t),()=>s.delete(t))}}(null),e=t=>{s.set(t)};return t.addEventListener(i,e),s._cleanup=()=>{t.removeEventListener(i,e)},s}const o={scale:.5,deltaThreshold:5,exponentialZooming:!1,iterations:20};class n extends i{constructor(t){super(t||{}),this.wrapper=void 0,this.container=null,this.accumulatedDelta=0,this.pointerTime=0,this.oldX=0,this.endZoom=0,this.startZoom=0,this.isPinching=!1,this.initialPinchDistance=0,this.initialZoom=0,this.onWheel=t=>{if(this.wavesurfer&&this.container&&!(Math.abs(t.deltaX)>=Math.abs(t.deltaY))&&(t.preventDefault(),this.accumulatedDelta+=-t.deltaY,0===this.startZoom&&this.options.exponentialZooming&&(this.startZoom=this.wavesurfer.getWrapper().clientWidth/this.wavesurfer.getDuration()),0===this.options.deltaThreshold||Math.abs(this.accumulatedDelta)>=this.options.deltaThreshold)){const i=this.wavesurfer.getDuration(),s=0===this.wavesurfer.options.minPxPerSec?this.wavesurfer.getWrapper().scrollWidth/i:this.wavesurfer.options.minPxPerSec,e=t.clientX-this.container.getBoundingClientRect().left,o=this.container.clientWidth,n=this.wavesurfer.getScroll();e===this.oldX&&0!==this.oldX||(this.pointerTime=(n+e)/s),this.oldX=e;const h=this.calculateNewZoom(s,this.accumulatedDelta),r=o/h*(e/o);h*i<o?(this.wavesurfer.zoom(o/i),this.container.scrollLeft=0):(this.wavesurfer.zoom(h),this.container.scrollLeft=(this.pointerTime-r)*h),this.accumulatedDelta=0}},this.calculateNewZoom=(t,i)=>{let s;if(this.options.exponentialZooming){const e=i>0?Math.pow(this.endZoom/this.startZoom,1/(this.options.iterations-1)):Math.pow(this.startZoom/this.endZoom,1/(this.options.iterations-1));s=Math.max(0,t*e)}else s=Math.max(0,t+i*this.options.scale);return Math.min(s,this.options.maxZoom)},this.onTouchStart=t=>{if(this.wavesurfer&&this.container&&2===t.touches.length){t.preventDefault(),this.isPinching=!0,this.initialPinchDistance=this.getTouchDistance(t);const i=this.wavesurfer.getDuration();this.initialZoom=0===this.wavesurfer.options.minPxPerSec?this.wavesurfer.getWrapper().scrollWidth/i:this.wavesurfer.options.minPxPerSec;const s=this.getTouchCenterX(t)-this.container.getBoundingClientRect().left,e=this.wavesurfer.getScroll();this.pointerTime=(e+s)/this.initialZoom,this.oldX=s}},this.onTouchMove=t=>{if(!this.isPinching||2!==t.touches.length||!this.wavesurfer||!this.container)return;t.preventDefault();const i=this.getTouchDistance(t)/this.initialPinchDistance;let s=this.initialZoom*i;s=Math.min(s,this.options.maxZoom);const e=this.wavesurfer.getDuration(),o=this.container.clientWidth,n=o/e;s<n&&(s=n);const h=o/s*(this.oldX/o);s===n?(this.wavesurfer.zoom(n),this.container.scrollLeft=0):(this.wavesurfer.zoom(s),this.container.scrollLeft=(this.pointerTime-h)*s)},this.onTouchEnd=t=>{this.isPinching&&t.touches.length<2&&(this.isPinching=!1,this.initialPinchDistance=0,this.initialZoom=0)},this.options=Object.assign({},o,t)}static create(t){return new n(t)}onInit(){var t,i;if(this.wrapper=null===(t=this.wavesurfer)||void 0===t?void 0:t.getWrapper(),!this.wrapper)return;this.container=this.wrapper.parentElement,void 0===this.options.maxZoom&&(this.options.maxZoom=this.container.clientWidth),this.endZoom=this.options.maxZoom;const o=null===(i=this.wavesurfer)||void 0===i?void 0:i.getState();o&&this.subscriptions.push(s((()=>{if(o.zoom.value>0&&0===this.startZoom&&this.options.exponentialZooming){const t=o.duration.value;t>0&&this.container&&(this.startZoom=this.container.clientWidth/t)}}),[o.zoom,o.duration]));const n=e(this.container,"wheel"),h=e(this.container,"touchstart"),r=e(this.container,"touchmove"),a=e(this.container,"touchend"),c=e(this.container,"touchcancel");this.subscriptions.push(s((()=>{const t=n.value;t&&this.onWheel(t)}),[n])),this.subscriptions.push(s((()=>{const t=h.value;t&&this.onTouchStart(t)}),[h])),this.subscriptions.push(s((()=>{const t=r.value;t&&this.onTouchMove(t)}),[r])),this.subscriptions.push(s((()=>{const t=a.value;t&&this.onTouchEnd(t)}),[a])),this.subscriptions.push(s((()=>{const t=c.value;t&&this.onTouchEnd(t)}),[c]))}getTouchDistance(t){const i=t.touches[0],s=t.touches[1];return Math.sqrt(Math.pow(s.clientX-i.clientX,2)+Math.pow(s.clientY-i.clientY,2))}getTouchCenterX(t){const i=t.touches[0],s=t.touches[1];return(i.clientX+s.clientX)/2}destroy(){super.destroy()}}return n}));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,199 @@
1
+ import { createDragStream } from '../drag-stream';
2
+ describe('createDragStream', () => {
3
+ beforeAll(() => {
4
+ // Mock matchMedia
5
+ Object.defineProperty(window, 'matchMedia', {
6
+ writable: true,
7
+ value: jest.fn().mockReturnValue({
8
+ matches: false,
9
+ addListener: jest.fn(),
10
+ removeListener: jest.fn(),
11
+ }),
12
+ });
13
+ // Polyfill PointerEvent for jsdom
14
+ if (typeof window.PointerEvent === 'undefined') {
15
+ class FakePointerEvent extends MouseEvent {
16
+ constructor(type, props) {
17
+ super(type, props);
18
+ }
19
+ }
20
+ // @ts-expect-error - Polyfill PointerEvent for jsdom test environment
21
+ window.PointerEvent = FakePointerEvent;
22
+ // @ts-expect-error - Polyfill PointerEvent for jsdom test environment
23
+ global.PointerEvent = FakePointerEvent;
24
+ }
25
+ });
26
+ let element;
27
+ let events;
28
+ beforeEach(() => {
29
+ element = document.createElement('div');
30
+ document.body.appendChild(element);
31
+ element.getBoundingClientRect = jest.fn(() => ({
32
+ left: 0,
33
+ top: 0,
34
+ right: 100,
35
+ bottom: 100,
36
+ width: 100,
37
+ height: 100,
38
+ x: 0,
39
+ y: 0,
40
+ toJSON: () => ({}),
41
+ }));
42
+ events = [];
43
+ });
44
+ afterEach(() => {
45
+ document.body.removeChild(element);
46
+ });
47
+ it('should create a drag signal', () => {
48
+ const { signal, cleanup } = createDragStream(element);
49
+ expect(signal).toBeDefined();
50
+ expect(signal.value).toBeNull();
51
+ cleanup();
52
+ });
53
+ it('should emit start event on drag', () => {
54
+ var _a;
55
+ const { signal, cleanup } = createDragStream(element, { threshold: 0 });
56
+ signal.subscribe((event) => {
57
+ if (event)
58
+ events.push(event);
59
+ });
60
+ // Simulate drag
61
+ const pointerDown = new PointerEvent('pointerdown', { clientX: 10, clientY: 10, button: 0 });
62
+ element.dispatchEvent(pointerDown);
63
+ const pointerMove = new PointerEvent('pointermove', { clientX: 20, clientY: 20 });
64
+ document.dispatchEvent(pointerMove);
65
+ expect(events.length).toBeGreaterThan(0);
66
+ expect((_a = events[0]) === null || _a === void 0 ? void 0 : _a.type).toBe('start');
67
+ cleanup();
68
+ });
69
+ it('should emit move events with deltas', () => {
70
+ const { signal, cleanup } = createDragStream(element, { threshold: 0 });
71
+ signal.subscribe((event) => {
72
+ if (event)
73
+ events.push(event);
74
+ });
75
+ // Simulate drag
76
+ const pointerDown = new PointerEvent('pointerdown', { clientX: 10, clientY: 10, button: 0 });
77
+ element.dispatchEvent(pointerDown);
78
+ const pointerMove = new PointerEvent('pointermove', { clientX: 20, clientY: 30 });
79
+ document.dispatchEvent(pointerMove);
80
+ const moveEvent = events.find((e) => e.type === 'move');
81
+ expect(moveEvent).toBeDefined();
82
+ expect(moveEvent === null || moveEvent === void 0 ? void 0 : moveEvent.deltaX).toBe(10);
83
+ expect(moveEvent === null || moveEvent === void 0 ? void 0 : moveEvent.deltaY).toBe(20);
84
+ cleanup();
85
+ });
86
+ it('should emit end event on pointer up', () => {
87
+ const { signal, cleanup } = createDragStream(element, { threshold: 0 });
88
+ signal.subscribe((event) => {
89
+ if (event)
90
+ events.push(event);
91
+ });
92
+ // Simulate drag
93
+ const pointerDown = new PointerEvent('pointerdown', { clientX: 10, clientY: 10, button: 0 });
94
+ element.dispatchEvent(pointerDown);
95
+ const pointerMove = new PointerEvent('pointermove', { clientX: 20, clientY: 20 });
96
+ document.dispatchEvent(pointerMove);
97
+ const pointerUp = new PointerEvent('pointerup', { clientX: 20, clientY: 20 });
98
+ document.dispatchEvent(pointerUp);
99
+ expect(events.some((e) => e.type === 'end')).toBe(true);
100
+ cleanup();
101
+ });
102
+ it('should respect threshold', () => {
103
+ const { signal, cleanup } = createDragStream(element, { threshold: 10 });
104
+ signal.subscribe((event) => {
105
+ if (event)
106
+ events.push(event);
107
+ });
108
+ // Simulate small drag (below threshold)
109
+ const pointerDown = new PointerEvent('pointerdown', { clientX: 10, clientY: 10, button: 0 });
110
+ element.dispatchEvent(pointerDown);
111
+ const pointerMove = new PointerEvent('pointermove', { clientX: 15, clientY: 15 });
112
+ document.dispatchEvent(pointerMove);
113
+ // Should not emit events yet
114
+ expect(events.length).toBe(0);
115
+ // Simulate larger drag (above threshold)
116
+ const pointerMove2 = new PointerEvent('pointermove', { clientX: 25, clientY: 25 });
117
+ document.dispatchEvent(pointerMove2);
118
+ // Should now emit events
119
+ expect(events.length).toBeGreaterThan(0);
120
+ cleanup();
121
+ });
122
+ it('should cleanup event listeners', () => {
123
+ const { cleanup } = createDragStream(element);
124
+ const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
125
+ const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener');
126
+ // Trigger drag to attach document listeners
127
+ const pointerDown = new PointerEvent('pointerdown', { clientX: 10, clientY: 10, button: 0 });
128
+ element.dispatchEvent(pointerDown);
129
+ expect(addEventListenerSpy).toHaveBeenCalled();
130
+ cleanup();
131
+ expect(removeEventListenerSpy).toHaveBeenCalled();
132
+ addEventListenerSpy.mockRestore();
133
+ removeEventListenerSpy.mockRestore();
134
+ });
135
+ it('should ignore non-primary mouse buttons', () => {
136
+ const { signal, cleanup } = createDragStream(element);
137
+ signal.subscribe((event) => {
138
+ if (event)
139
+ events.push(event);
140
+ });
141
+ // Simulate right-click drag
142
+ const pointerDown = new PointerEvent('pointerdown', { clientX: 10, clientY: 10, button: 2 });
143
+ element.dispatchEvent(pointerDown);
144
+ const pointerMove = new PointerEvent('pointermove', { clientX: 20, clientY: 20 });
145
+ document.dispatchEvent(pointerMove);
146
+ // Should not emit any events
147
+ expect(events.length).toBe(0);
148
+ cleanup();
149
+ });
150
+ it('should stop propagation during drag', () => {
151
+ const { signal, cleanup } = createDragStream(element, { threshold: 0 });
152
+ signal.subscribe((event) => {
153
+ if (event)
154
+ events.push(event);
155
+ });
156
+ // Start drag
157
+ const pointerDown = new PointerEvent('pointerdown', { clientX: 10, clientY: 10, button: 0 });
158
+ element.dispatchEvent(pointerDown);
159
+ // Move - should prevent click
160
+ const pointerMove = new PointerEvent('pointermove', { clientX: 20, clientY: 20 });
161
+ document.dispatchEvent(pointerMove);
162
+ // End drag
163
+ const pointerUp = new PointerEvent('pointerup', { clientX: 20, clientY: 20 });
164
+ document.dispatchEvent(pointerUp);
165
+ // Simulate click after drag
166
+ const clickHandler = jest.fn();
167
+ document.addEventListener('click', clickHandler, { capture: true });
168
+ const click = new MouseEvent('click', { bubbles: true });
169
+ Object.defineProperty(click, 'stopPropagation', { value: jest.fn() });
170
+ Object.defineProperty(click, 'preventDefault', { value: jest.fn() });
171
+ document.dispatchEvent(click);
172
+ document.removeEventListener('click', clickHandler, { capture: true });
173
+ cleanup();
174
+ });
175
+ it('should handle pointer leave events', () => {
176
+ const { signal, cleanup } = createDragStream(element, { threshold: 0 });
177
+ signal.subscribe((event) => {
178
+ if (event)
179
+ events.push(event);
180
+ });
181
+ // Start drag
182
+ const pointerDown = new PointerEvent('pointerdown', { clientX: 10, clientY: 10, button: 0, pointerId: 1 });
183
+ element.dispatchEvent(pointerDown);
184
+ // Move to start dragging
185
+ const pointerMove = new PointerEvent('pointermove', { clientX: 20, clientY: 20, pointerId: 1 });
186
+ document.dispatchEvent(pointerMove);
187
+ // Pointer leaves document
188
+ const pointerOut = new PointerEvent('pointerout', {
189
+ clientX: 20,
190
+ clientY: 20,
191
+ pointerId: 1,
192
+ relatedTarget: document.documentElement,
193
+ });
194
+ document.dispatchEvent(pointerOut);
195
+ // Should emit end event
196
+ expect(events.some((e) => e.type === 'end')).toBe(true);
197
+ cleanup();
198
+ });
199
+ });
@@ -0,0 +1,230 @@
1
+ import { toStream, toStreams, mergeStreams, mapStream, filterStream } from '../event-stream-emitter';
2
+ import EventEmitter from '../../event-emitter';
3
+ // Test class that exposes emit for testing
4
+ class TestEmitter extends EventEmitter {
5
+ emit(event, ...args) {
6
+ super.emit(event, ...args);
7
+ }
8
+ }
9
+ describe('event-stream-emitter', () => {
10
+ let emitter;
11
+ beforeEach(() => {
12
+ emitter = new TestEmitter();
13
+ });
14
+ describe('toStream', () => {
15
+ it('should create a stream from event', () => {
16
+ const { stream, cleanup } = toStream(emitter, 'play');
17
+ expect(stream.value).toBeNull();
18
+ const values = [];
19
+ stream.subscribe((value) => values.push(value));
20
+ emitter.emit('play');
21
+ expect(stream.value).toEqual([]);
22
+ // Values includes initial null and then the event
23
+ expect(values.length).toBeGreaterThanOrEqual(1);
24
+ expect(values[values.length - 1]).toEqual([]);
25
+ cleanup();
26
+ });
27
+ it('should update stream with event arguments', () => {
28
+ const { stream, cleanup } = toStream(emitter, 'timeupdate');
29
+ emitter.emit('timeupdate', 42);
30
+ expect(stream.value).toEqual([42]);
31
+ emitter.emit('timeupdate', 100);
32
+ expect(stream.value).toEqual([100]);
33
+ cleanup();
34
+ });
35
+ it('should handle multiple arguments', () => {
36
+ const { stream, cleanup } = toStream(emitter, 'customEvent');
37
+ emitter.emit('customEvent', 'hello', 5);
38
+ expect(stream.value).toEqual(['hello', 5]);
39
+ cleanup();
40
+ });
41
+ it('should cleanup event listener', () => {
42
+ const { stream, cleanup } = toStream(emitter, 'play');
43
+ emitter.emit('play');
44
+ expect(stream.value).toEqual([]);
45
+ cleanup();
46
+ // After cleanup, event should not update stream
47
+ const oldValue = stream.value;
48
+ emitter.emit('play');
49
+ expect(stream.value).toBe(oldValue); // Value should not change after cleanup
50
+ });
51
+ it('should allow multiple subscriptions to stream', () => {
52
+ const { stream, cleanup } = toStream(emitter, 'timeupdate');
53
+ const values1 = [];
54
+ const values2 = [];
55
+ stream.subscribe((value) => values1.push(value));
56
+ stream.subscribe((value) => values2.push(value));
57
+ emitter.emit('timeupdate', 10);
58
+ emitter.emit('timeupdate', 20);
59
+ expect(values1).toContainEqual([10]);
60
+ expect(values1).toContainEqual([20]);
61
+ expect(values2).toContainEqual([10]);
62
+ expect(values2).toContainEqual([20]);
63
+ cleanup();
64
+ });
65
+ });
66
+ describe('toStreams', () => {
67
+ it('should create multiple streams', () => {
68
+ const streams = toStreams(emitter, ['play', 'pause', 'timeupdate']);
69
+ expect(streams.play).toBeDefined();
70
+ expect(streams.pause).toBeDefined();
71
+ expect(streams.timeupdate).toBeDefined();
72
+ emitter.emit('play');
73
+ expect(streams.play.value).toEqual([]);
74
+ emitter.emit('pause');
75
+ expect(streams.pause.value).toEqual([]);
76
+ emitter.emit('timeupdate', 50);
77
+ expect(streams.timeupdate.value).toEqual([50]);
78
+ streams.cleanup();
79
+ });
80
+ it('should cleanup all streams', () => {
81
+ const streams = toStreams(emitter, ['play', 'pause']);
82
+ streams.cleanup();
83
+ emitter.emit('play');
84
+ emitter.emit('pause');
85
+ // Streams should not update after cleanup
86
+ expect(streams.play.value).toBeNull();
87
+ expect(streams.pause.value).toBeNull();
88
+ });
89
+ it('should handle empty event list', () => {
90
+ const streams = toStreams(emitter, []);
91
+ expect(streams.cleanup).toBeDefined();
92
+ expect(typeof streams.cleanup).toBe('function');
93
+ streams.cleanup(); // Should not throw
94
+ });
95
+ });
96
+ describe('mergeStreams', () => {
97
+ it('should merge multiple events into one stream', () => {
98
+ const { stream, cleanup } = mergeStreams(emitter, ['play', 'pause']);
99
+ expect(stream.value).toBeNull();
100
+ const values = [];
101
+ stream.subscribe((value) => values.push(value));
102
+ emitter.emit('play');
103
+ expect(stream.value).toEqual({ event: 'play', args: [] });
104
+ emitter.emit('pause');
105
+ expect(stream.value).toEqual({ event: 'pause', args: [] });
106
+ expect(values).toContainEqual({ event: 'play', args: [] });
107
+ expect(values).toContainEqual({ event: 'pause', args: [] });
108
+ cleanup();
109
+ });
110
+ it('should include event arguments in merged stream', () => {
111
+ const { stream, cleanup } = mergeStreams(emitter, ['timeupdate', 'error']);
112
+ emitter.emit('timeupdate', 42);
113
+ expect(stream.value).toEqual({ event: 'timeupdate', args: [42] });
114
+ const error = new Error('Test error');
115
+ emitter.emit('error', error);
116
+ expect(stream.value).toEqual({ event: 'error', args: [error] });
117
+ cleanup();
118
+ });
119
+ it('should cleanup all merged event listeners', () => {
120
+ const { stream, cleanup } = mergeStreams(emitter, ['play', 'pause']);
121
+ cleanup();
122
+ emitter.emit('play');
123
+ emitter.emit('pause');
124
+ // Stream should not update after cleanup
125
+ expect(stream.value).toBeNull();
126
+ });
127
+ it('should handle single event', () => {
128
+ const { stream, cleanup } = mergeStreams(emitter, ['play']);
129
+ emitter.emit('play');
130
+ expect(stream.value).toEqual({ event: 'play', args: [] });
131
+ cleanup();
132
+ });
133
+ });
134
+ describe('mapStream', () => {
135
+ it('should transform stream values', () => {
136
+ const { stream: timeStream, cleanup } = toStream(emitter, 'timeupdate');
137
+ const seconds = mapStream(timeStream, (value) => (value ? Math.floor(value[0]) : 0));
138
+ expect(seconds.value).toBe(0);
139
+ emitter.emit('timeupdate', 42.7);
140
+ expect(seconds.value).toBe(42);
141
+ emitter.emit('timeupdate', 99.9);
142
+ expect(seconds.value).toBe(99);
143
+ cleanup();
144
+ });
145
+ it('should work with multiple subscriptions', () => {
146
+ const { stream: timeStream, cleanup } = toStream(emitter, 'timeupdate');
147
+ const doubled = mapStream(timeStream, (value) => (value ? value[0] * 2 : 0));
148
+ const values = [];
149
+ doubled.subscribe((value) => values.push(value));
150
+ emitter.emit('timeupdate', 5);
151
+ emitter.emit('timeupdate', 10);
152
+ expect(values).toContain(10);
153
+ expect(values).toContain(20);
154
+ cleanup();
155
+ });
156
+ it('should handle null values', () => {
157
+ const { stream, cleanup } = toStream(emitter, 'play');
158
+ const mapped = mapStream(stream, (value) => (value ? 'playing' : 'not playing'));
159
+ expect(mapped.value).toBe('not playing');
160
+ emitter.emit('play');
161
+ expect(mapped.value).toBe('playing');
162
+ cleanup();
163
+ });
164
+ });
165
+ describe('filterStream', () => {
166
+ it('should filter stream values', () => {
167
+ const { stream: timeStream, cleanup } = toStream(emitter, 'timeupdate');
168
+ const afterTen = filterStream(timeStream, (value) => (value ? value[0] > 10 : false));
169
+ expect(afterTen.value).toBeNull();
170
+ emitter.emit('timeupdate', 5);
171
+ expect(afterTen.value).toBeNull();
172
+ emitter.emit('timeupdate', 15);
173
+ expect(afterTen.value).toEqual([15]);
174
+ emitter.emit('timeupdate', 7);
175
+ expect(afterTen.value).toBeNull();
176
+ cleanup();
177
+ });
178
+ it('should work with subscriptions', () => {
179
+ const { stream: timeStream, cleanup } = toStream(emitter, 'timeupdate');
180
+ const evenSeconds = filterStream(timeStream, (value) => value !== null && Math.floor(value[0]) % 2 === 0);
181
+ const values = [];
182
+ evenSeconds.subscribe((value) => values.push(value));
183
+ emitter.emit('timeupdate', 1);
184
+ emitter.emit('timeupdate', 2);
185
+ emitter.emit('timeupdate', 3);
186
+ emitter.emit('timeupdate', 4);
187
+ expect(values.filter((v) => v !== null)).toEqual([[2], [4]]);
188
+ cleanup();
189
+ });
190
+ it('should pass through values that match predicate', () => {
191
+ const { stream, cleanup } = toStream(emitter, 'customEvent');
192
+ const onlyHello = filterStream(stream, (value) => value !== null && value[0] === 'hello');
193
+ emitter.emit('customEvent', 'hello', 1);
194
+ expect(onlyHello.value).toEqual(['hello', 1]);
195
+ emitter.emit('customEvent', 'world', 2);
196
+ expect(onlyHello.value).toBeNull();
197
+ cleanup();
198
+ });
199
+ });
200
+ describe('integration', () => {
201
+ it('should allow chaining stream operations', () => {
202
+ const { stream: timeStream, cleanup } = toStream(emitter, 'timeupdate');
203
+ // Filter for times > 10, then map to seconds
204
+ const filtered = filterStream(timeStream, (value) => (value ? value[0] > 10 : false));
205
+ const seconds = mapStream(filtered, (value) => (value ? Math.floor(value[0]) : 0));
206
+ const values = [];
207
+ seconds.subscribe((value) => values.push(value));
208
+ emitter.emit('timeupdate', 5);
209
+ emitter.emit('timeupdate', 15.7);
210
+ emitter.emit('timeupdate', 20.3);
211
+ expect(values).toContain(15);
212
+ expect(values).toContain(20);
213
+ expect(values).not.toContain(5);
214
+ cleanup();
215
+ });
216
+ it('should work with merged streams', () => {
217
+ const { stream: mergedStream, cleanup } = mergeStreams(emitter, ['play', 'pause']);
218
+ const eventNames = mapStream(mergedStream, (value) => (value ? value.event : null));
219
+ const names = [];
220
+ eventNames.subscribe((name) => names.push(name));
221
+ emitter.emit('play');
222
+ emitter.emit('pause');
223
+ emitter.emit('play');
224
+ expect(names).toContain('play');
225
+ expect(names).toContain('pause');
226
+ expect(names.filter((n) => n === 'play')).toHaveLength(2);
227
+ cleanup();
228
+ });
229
+ });
230
+ });
@@ -0,0 +1 @@
1
+ export {};