wavesurfer.js 7.11.1 → 7.12.1

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 (86) 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 +281 -0
  4. package/dist/__tests__/player.test.js +107 -0
  5. package/dist/__tests__/renderer-utils.test.js +55 -2
  6. package/dist/draggable.d.ts +4 -0
  7. package/dist/draggable.js +4 -0
  8. package/dist/player.d.ts +21 -0
  9. package/dist/player.js +86 -0
  10. package/dist/plugins/envelope.cjs +1 -1
  11. package/dist/plugins/envelope.esm.js +1 -1
  12. package/dist/plugins/envelope.js +1 -1
  13. package/dist/plugins/envelope.min.js +1 -1
  14. package/dist/plugins/hover.cjs +1 -1
  15. package/dist/plugins/hover.d.ts +0 -3
  16. package/dist/plugins/hover.esm.js +1 -1
  17. package/dist/plugins/hover.js +1 -1
  18. package/dist/plugins/hover.min.js +1 -1
  19. package/dist/plugins/minimap.cjs +1 -1
  20. package/dist/plugins/minimap.esm.js +1 -1
  21. package/dist/plugins/minimap.js +1 -1
  22. package/dist/plugins/minimap.min.js +1 -1
  23. package/dist/plugins/record.cjs +1 -1
  24. package/dist/plugins/record.esm.js +1 -1
  25. package/dist/plugins/record.js +1 -1
  26. package/dist/plugins/record.min.js +1 -1
  27. package/dist/plugins/regions.cjs +1 -1
  28. package/dist/plugins/regions.esm.js +1 -1
  29. package/dist/plugins/regions.js +1 -1
  30. package/dist/plugins/regions.min.js +1 -1
  31. package/dist/plugins/timeline.cjs +1 -1
  32. package/dist/plugins/timeline.d.ts +1 -1
  33. package/dist/plugins/timeline.esm.js +1 -1
  34. package/dist/plugins/timeline.js +1 -1
  35. package/dist/plugins/timeline.min.js +1 -1
  36. package/dist/plugins/zoom.cjs +1 -1
  37. package/dist/plugins/zoom.esm.js +1 -1
  38. package/dist/plugins/zoom.js +1 -1
  39. package/dist/plugins/zoom.min.js +1 -1
  40. package/dist/reactive/__tests__/drag-stream.test.d.ts +1 -0
  41. package/dist/reactive/__tests__/drag-stream.test.js +199 -0
  42. package/dist/reactive/__tests__/event-stream-emitter.test.d.ts +1 -0
  43. package/dist/reactive/__tests__/event-stream-emitter.test.js +230 -0
  44. package/dist/reactive/__tests__/event-streams.test.d.ts +1 -0
  45. package/dist/reactive/__tests__/event-streams.test.js +278 -0
  46. package/dist/reactive/__tests__/media-event-bridge.test.d.ts +1 -0
  47. package/dist/reactive/__tests__/media-event-bridge.test.js +202 -0
  48. package/dist/reactive/__tests__/render-scheduler.test.d.ts +1 -0
  49. package/dist/reactive/__tests__/render-scheduler.test.js +216 -0
  50. package/dist/reactive/__tests__/scroll-stream.test.d.ts +1 -0
  51. package/dist/reactive/__tests__/scroll-stream.test.js +191 -0
  52. package/dist/reactive/__tests__/state-event-emitter.test.d.ts +1 -0
  53. package/dist/reactive/__tests__/state-event-emitter.test.js +234 -0
  54. package/dist/reactive/__tests__/store.test.d.ts +1 -0
  55. package/dist/reactive/__tests__/store.test.js +271 -0
  56. package/dist/reactive/drag-stream.d.ts +48 -0
  57. package/dist/reactive/drag-stream.js +146 -0
  58. package/dist/reactive/event-stream-emitter.d.ts +104 -0
  59. package/dist/reactive/event-stream-emitter.js +151 -0
  60. package/dist/reactive/event-streams.d.ts +65 -0
  61. package/dist/reactive/event-streams.js +146 -0
  62. package/dist/reactive/media-event-bridge.d.ts +51 -0
  63. package/dist/reactive/media-event-bridge.js +152 -0
  64. package/dist/reactive/render-scheduler.d.ts +43 -0
  65. package/dist/reactive/render-scheduler.js +75 -0
  66. package/dist/reactive/scroll-stream.d.ts +93 -0
  67. package/dist/reactive/scroll-stream.js +131 -0
  68. package/dist/reactive/state-event-emitter.d.ts +105 -0
  69. package/dist/reactive/state-event-emitter.js +213 -0
  70. package/dist/reactive/store.d.ts +66 -0
  71. package/dist/reactive/store.js +115 -0
  72. package/dist/renderer-utils.d.ts +13 -4
  73. package/dist/renderer-utils.js +35 -15
  74. package/dist/renderer.d.ts +2 -1
  75. package/dist/renderer.js +51 -48
  76. package/dist/state/__tests__/wavesurfer-state.test.d.ts +1 -0
  77. package/dist/state/__tests__/wavesurfer-state.test.js +268 -0
  78. package/dist/state/wavesurfer-state.d.ts +90 -0
  79. package/dist/state/wavesurfer-state.js +124 -0
  80. package/dist/types.d.ts +165 -1
  81. package/dist/wavesurfer.cjs +1 -1
  82. package/dist/wavesurfer.d.ts +16 -0
  83. package/dist/wavesurfer.esm.js +1 -1
  84. package/dist/wavesurfer.js +32 -0
  85. package/dist/wavesurfer.min.js +1 -1
  86. package/package.json +2 -3
@@ -0,0 +1,271 @@
1
+ import { signal, computed, effect } from '../store';
2
+ describe('signal', () => {
3
+ it('should create a signal with initial value', () => {
4
+ const count = signal(0);
5
+ expect(count.value).toBe(0);
6
+ });
7
+ it('should update value with set()', () => {
8
+ const count = signal(0);
9
+ count.set(5);
10
+ expect(count.value).toBe(5);
11
+ });
12
+ it('should update value with update()', () => {
13
+ const count = signal(0);
14
+ count.update((n) => n + 1);
15
+ expect(count.value).toBe(1);
16
+ });
17
+ it('should notify subscribers when value changes', () => {
18
+ const count = signal(0);
19
+ const callback = jest.fn();
20
+ count.subscribe(callback);
21
+ count.set(5);
22
+ expect(callback).toHaveBeenCalledWith(5);
23
+ expect(callback).toHaveBeenCalledTimes(1);
24
+ });
25
+ it('should not notify if value does not change', () => {
26
+ const count = signal(0);
27
+ const callback = jest.fn();
28
+ count.subscribe(callback);
29
+ count.set(0); // Same value
30
+ expect(callback).not.toHaveBeenCalled();
31
+ });
32
+ it('should support multiple subscribers', () => {
33
+ const count = signal(0);
34
+ const callback1 = jest.fn();
35
+ const callback2 = jest.fn();
36
+ count.subscribe(callback1);
37
+ count.subscribe(callback2);
38
+ count.set(5);
39
+ expect(callback1).toHaveBeenCalledWith(5);
40
+ expect(callback2).toHaveBeenCalledWith(5);
41
+ });
42
+ it('should unsubscribe correctly', () => {
43
+ const count = signal(0);
44
+ const callback = jest.fn();
45
+ const unsubscribe = count.subscribe(callback);
46
+ count.set(1);
47
+ expect(callback).toHaveBeenCalledTimes(1);
48
+ unsubscribe();
49
+ count.set(2);
50
+ expect(callback).toHaveBeenCalledTimes(1); // Should not be called again
51
+ });
52
+ it('should work with object values', () => {
53
+ const state = signal({ count: 0 });
54
+ const callback = jest.fn();
55
+ state.subscribe(callback);
56
+ state.set({ count: 1 });
57
+ expect(callback).toHaveBeenCalledWith({ count: 1 });
58
+ });
59
+ it('should detect reference equality for objects', () => {
60
+ const obj = { count: 0 };
61
+ const state = signal(obj);
62
+ const callback = jest.fn();
63
+ state.subscribe(callback);
64
+ state.set(obj); // Same reference
65
+ expect(callback).not.toHaveBeenCalled();
66
+ });
67
+ });
68
+ describe('computed', () => {
69
+ it('should compute initial value', () => {
70
+ const count = signal(5);
71
+ const doubled = computed(() => count.value * 2, [count]);
72
+ expect(doubled.value).toBe(10);
73
+ });
74
+ it('should recompute when dependency changes', () => {
75
+ const count = signal(5);
76
+ const doubled = computed(() => count.value * 2, [count]);
77
+ count.set(10);
78
+ expect(doubled.value).toBe(20);
79
+ });
80
+ it('should work with multiple dependencies', () => {
81
+ const a = signal(2);
82
+ const b = signal(3);
83
+ const sum = computed(() => a.value + b.value, [a, b]);
84
+ expect(sum.value).toBe(5);
85
+ a.set(5);
86
+ expect(sum.value).toBe(8);
87
+ b.set(7);
88
+ expect(sum.value).toBe(12);
89
+ });
90
+ it('should notify subscribers when computed value changes', () => {
91
+ const count = signal(5);
92
+ const doubled = computed(() => count.value * 2, [count]);
93
+ const callback = jest.fn();
94
+ doubled.subscribe(callback);
95
+ count.set(10);
96
+ expect(callback).toHaveBeenCalledWith(20);
97
+ });
98
+ it('should not notify if computed value does not change', () => {
99
+ const count = signal(5);
100
+ const isPositive = computed(() => count.value > 0, [count]);
101
+ const callback = jest.fn();
102
+ isPositive.subscribe(callback);
103
+ count.set(10); // Still positive
104
+ expect(callback).not.toHaveBeenCalled();
105
+ });
106
+ it('should support nested computed values', () => {
107
+ const count = signal(5);
108
+ const doubled = computed(() => count.value * 2, [count]);
109
+ const quadrupled = computed(() => doubled.value * 2, [doubled]);
110
+ expect(quadrupled.value).toBe(20);
111
+ count.set(10);
112
+ expect(quadrupled.value).toBe(40);
113
+ });
114
+ it('should cleanup subscriptions when computed is unsubscribed', () => {
115
+ const count = signal(5);
116
+ const doubled = computed(() => count.value * 2, [count]);
117
+ const callback = jest.fn();
118
+ const unsubscribe = doubled.subscribe(callback);
119
+ count.set(10);
120
+ expect(callback).toHaveBeenCalledTimes(1);
121
+ unsubscribe();
122
+ count.set(15);
123
+ expect(callback).toHaveBeenCalledTimes(1);
124
+ });
125
+ it('should be read-only (no set method)', () => {
126
+ const count = signal(5);
127
+ const doubled = computed(() => count.value * 2, [count]);
128
+ expect(doubled.set).toBeUndefined();
129
+ expect(doubled.update).toBeUndefined();
130
+ });
131
+ });
132
+ describe('effect', () => {
133
+ it('should run immediately', () => {
134
+ const fn = jest.fn();
135
+ const count = signal(0);
136
+ effect(fn, [count]);
137
+ expect(fn).toHaveBeenCalledTimes(1);
138
+ });
139
+ it('should run when dependency changes', () => {
140
+ const fn = jest.fn();
141
+ const count = signal(0);
142
+ effect(fn, [count]);
143
+ expect(fn).toHaveBeenCalledTimes(1);
144
+ count.set(1);
145
+ expect(fn).toHaveBeenCalledTimes(2);
146
+ count.set(2);
147
+ expect(fn).toHaveBeenCalledTimes(3);
148
+ });
149
+ it('should run with multiple dependencies', () => {
150
+ const fn = jest.fn();
151
+ const a = signal(0);
152
+ const b = signal(0);
153
+ effect(fn, [a, b]);
154
+ expect(fn).toHaveBeenCalledTimes(1);
155
+ a.set(1);
156
+ expect(fn).toHaveBeenCalledTimes(2);
157
+ b.set(1);
158
+ expect(fn).toHaveBeenCalledTimes(3);
159
+ });
160
+ it('should run cleanup before re-running effect', () => {
161
+ const cleanup = jest.fn();
162
+ const fn = jest.fn(() => cleanup);
163
+ const count = signal(0);
164
+ effect(fn, [count]);
165
+ expect(fn).toHaveBeenCalledTimes(1);
166
+ expect(cleanup).not.toHaveBeenCalled();
167
+ count.set(1);
168
+ expect(cleanup).toHaveBeenCalledTimes(1); // Cleanup from first run
169
+ expect(fn).toHaveBeenCalledTimes(2);
170
+ count.set(2);
171
+ expect(cleanup).toHaveBeenCalledTimes(2); // Cleanup from second run
172
+ expect(fn).toHaveBeenCalledTimes(3);
173
+ });
174
+ it('should run cleanup on unsubscribe', () => {
175
+ const cleanup = jest.fn();
176
+ const fn = jest.fn(() => cleanup);
177
+ const count = signal(0);
178
+ const unsubscribe = effect(fn, [count]);
179
+ expect(fn).toHaveBeenCalledTimes(1);
180
+ expect(cleanup).not.toHaveBeenCalled();
181
+ unsubscribe();
182
+ expect(cleanup).toHaveBeenCalledTimes(1);
183
+ });
184
+ it('should stop running after unsubscribe', () => {
185
+ const fn = jest.fn();
186
+ const count = signal(0);
187
+ const unsubscribe = effect(fn, [count]);
188
+ expect(fn).toHaveBeenCalledTimes(1);
189
+ count.set(1);
190
+ expect(fn).toHaveBeenCalledTimes(2);
191
+ unsubscribe();
192
+ count.set(2);
193
+ expect(fn).toHaveBeenCalledTimes(2); // Should not increase
194
+ });
195
+ it('should work with computed dependencies', () => {
196
+ const fn = jest.fn();
197
+ const count = signal(0);
198
+ const doubled = computed(() => count.value * 2, [count]);
199
+ effect(fn, [doubled]);
200
+ expect(fn).toHaveBeenCalledTimes(1);
201
+ count.set(1);
202
+ expect(fn).toHaveBeenCalledTimes(2);
203
+ });
204
+ it('should handle effects without cleanup', () => {
205
+ const fn = jest.fn();
206
+ const count = signal(0);
207
+ const unsubscribe = effect(fn, [count]);
208
+ count.set(1);
209
+ expect(() => unsubscribe()).not.toThrow();
210
+ expect(fn).toHaveBeenCalledTimes(2);
211
+ });
212
+ it('should handle accessing signal values in effect', () => {
213
+ const values = [];
214
+ const count = signal(0);
215
+ effect(() => {
216
+ values.push(count.value);
217
+ }, [count]);
218
+ count.set(1);
219
+ count.set(2);
220
+ expect(values).toEqual([0, 1, 2]);
221
+ });
222
+ });
223
+ describe('integration tests', () => {
224
+ it('should work with signal -> computed -> effect chain', () => {
225
+ const values = [];
226
+ const count = signal(0);
227
+ const doubled = computed(() => count.value * 2, [count]);
228
+ effect(() => {
229
+ values.push(doubled.value);
230
+ }, [doubled]);
231
+ count.set(5);
232
+ count.set(10);
233
+ expect(values).toEqual([0, 10, 20]);
234
+ });
235
+ it('should handle complex dependency graphs', () => {
236
+ const a = signal(1);
237
+ const b = signal(2);
238
+ const sum = computed(() => a.value + b.value, [a, b]);
239
+ const product = computed(() => a.value * b.value, [a, b]);
240
+ const combined = computed(() => sum.value + product.value, [sum, product]);
241
+ expect(combined.value).toBe(5); // (1+2) + (1*2) = 3 + 2 = 5
242
+ a.set(3);
243
+ expect(combined.value).toBe(11); // (3+2) + (3*2) = 5 + 6 = 11
244
+ b.set(4);
245
+ expect(combined.value).toBe(19); // (3+4) + (3*4) = 7 + 12 = 19
246
+ });
247
+ it('should not create memory leaks with many subscriptions', () => {
248
+ const count = signal(0);
249
+ const unsubscribes = [];
250
+ // Create 1000 subscriptions
251
+ for (let i = 0; i < 1000; i++) {
252
+ unsubscribes.push(count.subscribe(() => { }));
253
+ }
254
+ // Unsubscribe all
255
+ unsubscribes.forEach((unsub) => unsub());
256
+ // Signal should still work
257
+ count.set(5);
258
+ expect(count.value).toBe(5);
259
+ });
260
+ it('should handle rapid updates correctly', () => {
261
+ const count = signal(0);
262
+ const callback = jest.fn();
263
+ count.subscribe(callback);
264
+ // Rapid updates
265
+ for (let i = 1; i <= 100; i++) {
266
+ count.set(i);
267
+ }
268
+ expect(callback).toHaveBeenCalledTimes(100);
269
+ expect(count.value).toBe(100);
270
+ });
271
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Reactive drag stream utilities
3
+ *
4
+ * Provides declarative drag handling using reactive streams.
5
+ * Automatically handles mouseup cleanup and supports constraints.
6
+ */
7
+ import { type Signal } from './store.js';
8
+ export interface DragEvent {
9
+ type: 'start' | 'move' | 'end';
10
+ x: number;
11
+ y: number;
12
+ deltaX?: number;
13
+ deltaY?: number;
14
+ }
15
+ export interface DragStreamOptions {
16
+ /** Minimum distance to move before dragging starts (default: 3) */
17
+ threshold?: number;
18
+ /** Mouse button to listen for (default: 0 = left button) */
19
+ mouseButton?: number;
20
+ /** Delay before touch drag starts in ms (default: 100) */
21
+ touchDelay?: number;
22
+ }
23
+ /**
24
+ * Create a reactive drag stream from an element
25
+ *
26
+ * Emits drag events (start, move, end) as the user drags the element.
27
+ * Automatically handles pointer capture, multi-touch prevention, and cleanup.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const dragSignal = createDragStream(element)
32
+ *
33
+ * effect(() => {
34
+ * const drag = dragSignal.value
35
+ * if (drag?.type === 'move') {
36
+ * console.log('Dragging:', drag.deltaX, drag.deltaY)
37
+ * }
38
+ * }, [dragSignal])
39
+ * ```
40
+ *
41
+ * @param element - Element to make draggable
42
+ * @param options - Drag configuration options
43
+ * @returns Signal emitting drag events and cleanup function
44
+ */
45
+ export declare function createDragStream(element: HTMLElement, options?: DragStreamOptions): {
46
+ signal: Signal<DragEvent | null>;
47
+ cleanup: () => void;
48
+ };
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Reactive drag stream utilities
3
+ *
4
+ * Provides declarative drag handling using reactive streams.
5
+ * Automatically handles mouseup cleanup and supports constraints.
6
+ */
7
+ import { signal } from './store.js';
8
+ import { cleanup } from './event-streams.js';
9
+ /**
10
+ * Create a reactive drag stream from an element
11
+ *
12
+ * Emits drag events (start, move, end) as the user drags the element.
13
+ * Automatically handles pointer capture, multi-touch prevention, and cleanup.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const dragSignal = createDragStream(element)
18
+ *
19
+ * effect(() => {
20
+ * const drag = dragSignal.value
21
+ * if (drag?.type === 'move') {
22
+ * console.log('Dragging:', drag.deltaX, drag.deltaY)
23
+ * }
24
+ * }, [dragSignal])
25
+ * ```
26
+ *
27
+ * @param element - Element to make draggable
28
+ * @param options - Drag configuration options
29
+ * @returns Signal emitting drag events and cleanup function
30
+ */
31
+ export function createDragStream(element, options = {}) {
32
+ const { threshold = 3, mouseButton = 0, touchDelay = 100 } = options;
33
+ const dragSignal = signal(null);
34
+ const activePointers = new Map();
35
+ const isTouchDevice = matchMedia('(pointer: coarse)').matches;
36
+ let unsubscribeDocument = () => void 0;
37
+ const onPointerDown = (event) => {
38
+ if (event.button !== mouseButton)
39
+ return;
40
+ activePointers.set(event.pointerId, event);
41
+ if (activePointers.size > 1) {
42
+ return;
43
+ }
44
+ let startX = event.clientX;
45
+ let startY = event.clientY;
46
+ let isDragging = false;
47
+ const touchStartTime = Date.now();
48
+ const rect = element.getBoundingClientRect();
49
+ const { left, top } = rect;
50
+ const onPointerMove = (event) => {
51
+ if (event.defaultPrevented || activePointers.size > 1) {
52
+ return;
53
+ }
54
+ if (isTouchDevice && Date.now() - touchStartTime < touchDelay)
55
+ return;
56
+ const x = event.clientX;
57
+ const y = event.clientY;
58
+ const dx = x - startX;
59
+ const dy = y - startY;
60
+ if (isDragging || Math.abs(dx) > threshold || Math.abs(dy) > threshold) {
61
+ event.preventDefault();
62
+ event.stopPropagation();
63
+ if (!isDragging) {
64
+ // Emit start event
65
+ dragSignal.set({
66
+ type: 'start',
67
+ x: startX - left,
68
+ y: startY - top,
69
+ });
70
+ isDragging = true;
71
+ }
72
+ // Emit move event
73
+ dragSignal.set({
74
+ type: 'move',
75
+ x: x - left,
76
+ y: y - top,
77
+ deltaX: dx,
78
+ deltaY: dy,
79
+ });
80
+ startX = x;
81
+ startY = y;
82
+ }
83
+ };
84
+ const onPointerUp = (event) => {
85
+ activePointers.delete(event.pointerId);
86
+ if (isDragging) {
87
+ const x = event.clientX;
88
+ const y = event.clientY;
89
+ // Emit end event
90
+ dragSignal.set({
91
+ type: 'end',
92
+ x: x - left,
93
+ y: y - top,
94
+ });
95
+ }
96
+ unsubscribeDocument();
97
+ };
98
+ const onPointerLeave = (e) => {
99
+ activePointers.delete(e.pointerId);
100
+ if (!e.relatedTarget || e.relatedTarget === document.documentElement) {
101
+ onPointerUp(e);
102
+ }
103
+ };
104
+ const onClick = (event) => {
105
+ if (isDragging) {
106
+ event.stopPropagation();
107
+ event.preventDefault();
108
+ }
109
+ };
110
+ const onTouchMove = (event) => {
111
+ if (event.defaultPrevented || activePointers.size > 1) {
112
+ return;
113
+ }
114
+ if (isDragging) {
115
+ event.preventDefault();
116
+ }
117
+ };
118
+ document.addEventListener('pointermove', onPointerMove);
119
+ document.addEventListener('pointerup', onPointerUp);
120
+ document.addEventListener('pointerout', onPointerLeave);
121
+ document.addEventListener('pointercancel', onPointerLeave);
122
+ document.addEventListener('touchmove', onTouchMove, { passive: false });
123
+ document.addEventListener('click', onClick, { capture: true });
124
+ unsubscribeDocument = () => {
125
+ document.removeEventListener('pointermove', onPointerMove);
126
+ document.removeEventListener('pointerup', onPointerUp);
127
+ document.removeEventListener('pointerout', onPointerLeave);
128
+ document.removeEventListener('pointercancel', onPointerLeave);
129
+ document.removeEventListener('touchmove', onTouchMove);
130
+ setTimeout(() => {
131
+ document.removeEventListener('click', onClick, { capture: true });
132
+ }, 10);
133
+ };
134
+ };
135
+ element.addEventListener('pointerdown', onPointerDown);
136
+ const cleanupFn = () => {
137
+ unsubscribeDocument();
138
+ element.removeEventListener('pointerdown', onPointerDown);
139
+ activePointers.clear();
140
+ cleanup(dragSignal);
141
+ };
142
+ return {
143
+ signal: dragSignal,
144
+ cleanup: cleanupFn,
145
+ };
146
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Event stream emitter - bridges EventEmitter to reactive streams
3
+ *
4
+ * Provides reactive stream API on top of traditional EventEmitter.
5
+ * This allows users to choose between callback-based and stream-based APIs.
6
+ */
7
+ import { type Signal } from './store.js';
8
+ import type EventEmitter from '../event-emitter.js';
9
+ /**
10
+ * Convert an EventEmitter event to a reactive signal/stream
11
+ *
12
+ * Creates a signal that updates whenever the event is emitted.
13
+ * Returns both the signal (for reading values) and cleanup function.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const { stream, cleanup } = toStream(wavesurfer, 'play')
18
+ *
19
+ * // Subscribe to play events
20
+ * stream.subscribe(() => console.log('Playing!'))
21
+ *
22
+ * // Cleanup when done
23
+ * cleanup()
24
+ * ```
25
+ *
26
+ * @param emitter - EventEmitter instance
27
+ * @param eventName - Name of the event to stream
28
+ * @returns Object with stream signal and cleanup function
29
+ */
30
+ export declare function toStream<T extends Record<string, any[]>, K extends keyof T>(emitter: EventEmitter<T>, eventName: K): {
31
+ stream: Signal<T[K] | null>;
32
+ cleanup: () => void;
33
+ };
34
+ /**
35
+ * Create multiple event streams from an emitter
36
+ *
37
+ * Helper to create streams for multiple events at once.
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const streams = toStreams(wavesurfer, ['play', 'pause', 'timeupdate'])
42
+ *
43
+ * streams.play.subscribe(() => console.log('Play'))
44
+ * streams.pause.subscribe(() => console.log('Pause'))
45
+ * streams.timeupdate.subscribe(([time]) => console.log('Time:', time))
46
+ *
47
+ * // Cleanup all
48
+ * streams.cleanup()
49
+ * ```
50
+ *
51
+ * @param emitter - EventEmitter instance
52
+ * @param eventNames - Array of event names to stream
53
+ * @returns Object with streams for each event and cleanup function
54
+ */
55
+ export declare function toStreams<T extends Record<string, any[]>, K extends keyof T>(emitter: EventEmitter<T>, eventNames: K[]): {
56
+ [P in K]: Signal<T[P] | null>;
57
+ } & {
58
+ cleanup: () => void;
59
+ };
60
+ /**
61
+ * Create a stream that combines multiple events into one
62
+ *
63
+ * Useful when you want to react to any of several events.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * const { stream, cleanup } = mergeStreams(wavesurfer, ['play', 'pause'])
68
+ *
69
+ * stream.subscribe(({ event, args }) => {
70
+ * console.log(`Event ${event} fired with`, args)
71
+ * })
72
+ * ```
73
+ *
74
+ * @param emitter - EventEmitter instance
75
+ * @param eventNames - Array of event names to merge
76
+ * @returns Object with merged stream and cleanup function
77
+ */
78
+ export declare function mergeStreams<T extends Record<string, any[]>, K extends keyof T>(emitter: EventEmitter<T>, eventNames: K[]): {
79
+ stream: Signal<{
80
+ event: K;
81
+ args: T[K];
82
+ } | null>;
83
+ cleanup: () => void;
84
+ };
85
+ /**
86
+ * Helper to map event stream values
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const { stream: timeStream } = toStream(wavesurfer, 'timeupdate')
91
+ * const seconds = mapStream(timeStream, ([time]) => Math.floor(time))
92
+ * ```
93
+ */
94
+ export declare function mapStream<T, U>(source: Signal<T>, mapper: (value: T) => U): Signal<U>;
95
+ /**
96
+ * Helper to filter event stream values
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const { stream: timeStream } = toStream(wavesurfer, 'timeupdate')
101
+ * const afterTenSeconds = filterStream(timeStream, ([time]) => time > 10)
102
+ * ```
103
+ */
104
+ export declare function filterStream<T>(source: Signal<T>, predicate: (value: T) => boolean): Signal<T | null>;