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.
- package/README.md +1 -1
- package/dist/__tests__/memory-leaks.test.d.ts +7 -0
- package/dist/__tests__/memory-leaks.test.js +184 -0
- package/dist/__tests__/player.test.js +107 -0
- package/dist/__tests__/renderer-utils.test.d.ts +1 -0
- package/dist/__tests__/renderer-utils.test.js +373 -0
- package/dist/decoder.js +12 -4
- package/dist/draggable.d.ts +4 -0
- package/dist/draggable.js +20 -5
- package/dist/fetcher.js +14 -23
- package/dist/player.d.ts +21 -0
- package/dist/player.js +86 -0
- package/dist/plugins/envelope.cjs +1 -1
- package/dist/plugins/envelope.esm.js +1 -1
- package/dist/plugins/envelope.js +1 -1
- package/dist/plugins/envelope.min.js +1 -1
- package/dist/plugins/hover.cjs +1 -1
- package/dist/plugins/hover.d.ts +0 -3
- package/dist/plugins/hover.esm.js +1 -1
- package/dist/plugins/hover.js +1 -1
- package/dist/plugins/hover.min.js +1 -1
- package/dist/plugins/minimap.cjs +1 -1
- package/dist/plugins/minimap.esm.js +1 -1
- package/dist/plugins/minimap.js +1 -1
- package/dist/plugins/minimap.min.js +1 -1
- package/dist/plugins/record.cjs +1 -1
- package/dist/plugins/record.esm.js +1 -1
- package/dist/plugins/record.js +1 -1
- package/dist/plugins/record.min.js +1 -1
- package/dist/plugins/regions.cjs +1 -1
- package/dist/plugins/regions.esm.js +1 -1
- package/dist/plugins/regions.js +1 -1
- package/dist/plugins/regions.min.js +1 -1
- package/dist/plugins/timeline.cjs +1 -1
- package/dist/plugins/timeline.d.ts +1 -1
- package/dist/plugins/timeline.esm.js +1 -1
- package/dist/plugins/timeline.js +1 -1
- package/dist/plugins/timeline.min.js +1 -1
- package/dist/plugins/zoom.cjs +1 -1
- package/dist/plugins/zoom.d.ts +10 -1
- package/dist/plugins/zoom.esm.js +1 -1
- package/dist/plugins/zoom.js +1 -1
- package/dist/plugins/zoom.min.js +1 -1
- package/dist/reactive/__tests__/drag-stream.test.d.ts +1 -0
- package/dist/reactive/__tests__/drag-stream.test.js +199 -0
- package/dist/reactive/__tests__/event-stream-emitter.test.d.ts +1 -0
- package/dist/reactive/__tests__/event-stream-emitter.test.js +230 -0
- package/dist/reactive/__tests__/event-streams.test.d.ts +1 -0
- package/dist/reactive/__tests__/event-streams.test.js +278 -0
- package/dist/reactive/__tests__/media-event-bridge.test.d.ts +1 -0
- package/dist/reactive/__tests__/media-event-bridge.test.js +202 -0
- package/dist/reactive/__tests__/render-scheduler.test.d.ts +1 -0
- package/dist/reactive/__tests__/render-scheduler.test.js +216 -0
- package/dist/reactive/__tests__/scroll-stream.test.d.ts +1 -0
- package/dist/reactive/__tests__/scroll-stream.test.js +191 -0
- package/dist/reactive/__tests__/state-event-emitter.test.d.ts +1 -0
- package/dist/reactive/__tests__/state-event-emitter.test.js +234 -0
- package/dist/reactive/__tests__/store.test.d.ts +1 -0
- package/dist/reactive/__tests__/store.test.js +271 -0
- package/dist/reactive/drag-stream.d.ts +48 -0
- package/dist/reactive/drag-stream.js +146 -0
- package/dist/reactive/event-stream-emitter.d.ts +104 -0
- package/dist/reactive/event-stream-emitter.js +151 -0
- package/dist/reactive/event-streams.d.ts +65 -0
- package/dist/reactive/event-streams.js +146 -0
- package/dist/reactive/media-event-bridge.d.ts +51 -0
- package/dist/reactive/media-event-bridge.js +152 -0
- package/dist/reactive/render-scheduler.d.ts +43 -0
- package/dist/reactive/render-scheduler.js +75 -0
- package/dist/reactive/scroll-stream.d.ts +93 -0
- package/dist/reactive/scroll-stream.js +131 -0
- package/dist/reactive/state-event-emitter.d.ts +105 -0
- package/dist/reactive/state-event-emitter.js +213 -0
- package/dist/reactive/store.d.ts +66 -0
- package/dist/reactive/store.js +115 -0
- package/dist/renderer-utils.d.ts +126 -0
- package/dist/renderer-utils.js +252 -0
- package/dist/renderer.d.ts +4 -4
- package/dist/renderer.js +133 -197
- package/dist/state/__tests__/wavesurfer-state.test.d.ts +1 -0
- package/dist/state/__tests__/wavesurfer-state.test.js +268 -0
- package/dist/state/wavesurfer-state.d.ts +90 -0
- package/dist/state/wavesurfer-state.js +124 -0
- package/dist/types.d.ts +167 -1
- package/dist/wavesurfer.cjs +1 -1
- package/dist/wavesurfer.d.ts +18 -0
- package/dist/wavesurfer.esm.js +1 -1
- package/dist/wavesurfer.js +46 -8
- package/dist/wavesurfer.min.js +1 -1
- package/dist/webaudio.js +4 -0
- package/package.json +6 -6
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive scroll stream utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides declarative scroll handling using reactive streams.
|
|
5
|
+
* Automatically handles scroll event optimization and cleanup.
|
|
6
|
+
*/
|
|
7
|
+
import { type Signal } from './store.js';
|
|
8
|
+
export interface ScrollData {
|
|
9
|
+
/** Current scroll position in pixels */
|
|
10
|
+
scrollLeft: number;
|
|
11
|
+
/** Total scrollable width in pixels */
|
|
12
|
+
scrollWidth: number;
|
|
13
|
+
/** Visible viewport width in pixels */
|
|
14
|
+
clientWidth: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ScrollPercentages {
|
|
17
|
+
/** Start position as percentage (0-1) */
|
|
18
|
+
startX: number;
|
|
19
|
+
/** End position as percentage (0-1) */
|
|
20
|
+
endX: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Calculate visible percentages from scroll data
|
|
24
|
+
* Pure function - no side effects
|
|
25
|
+
*
|
|
26
|
+
* @param scrollData - Current scroll dimensions
|
|
27
|
+
* @returns Start and end positions as percentages (0-1)
|
|
28
|
+
*/
|
|
29
|
+
export declare function calculateScrollPercentages(scrollData: ScrollData): ScrollPercentages;
|
|
30
|
+
/**
|
|
31
|
+
* Calculate scroll bounds in pixels
|
|
32
|
+
* Pure function - no side effects
|
|
33
|
+
*
|
|
34
|
+
* @param scrollData - Current scroll dimensions
|
|
35
|
+
* @returns Left and right scroll bounds in pixels
|
|
36
|
+
*/
|
|
37
|
+
export declare function calculateScrollBounds(scrollData: ScrollData): {
|
|
38
|
+
left: number;
|
|
39
|
+
right: number;
|
|
40
|
+
};
|
|
41
|
+
export interface ScrollStream {
|
|
42
|
+
/** Signal containing current scroll data */
|
|
43
|
+
scrollData: Signal<ScrollData>;
|
|
44
|
+
/** Computed signal with visible percentages */
|
|
45
|
+
percentages: Signal<ScrollPercentages>;
|
|
46
|
+
/** Computed signal with scroll bounds */
|
|
47
|
+
bounds: Signal<{
|
|
48
|
+
left: number;
|
|
49
|
+
right: number;
|
|
50
|
+
}>;
|
|
51
|
+
/** Cleanup function to remove listeners */
|
|
52
|
+
cleanup: () => void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create a reactive scroll stream from an element
|
|
56
|
+
*
|
|
57
|
+
* Emits scroll data as the user scrolls the element.
|
|
58
|
+
* Automatically computes derived values (percentages, bounds).
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const scrollStream = createScrollStream(container)
|
|
63
|
+
*
|
|
64
|
+
* effect(() => {
|
|
65
|
+
* const { startX, endX } = scrollStream.percentages.value
|
|
66
|
+
* console.log('Visible:', startX, 'to', endX)
|
|
67
|
+
* }, [scrollStream.percentages])
|
|
68
|
+
*
|
|
69
|
+
* scrollStream.cleanup()
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @param element - Scrollable element
|
|
73
|
+
* @returns Scroll stream with signals and cleanup
|
|
74
|
+
*/
|
|
75
|
+
export declare function createScrollStream(element: HTMLElement): ScrollStream;
|
|
76
|
+
/**
|
|
77
|
+
* Create a scroll stream that automatically updates external state
|
|
78
|
+
*
|
|
79
|
+
* This is a convenience wrapper that connects scroll events to a state action.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* const scrollStream = createScrollStreamWithAction(
|
|
84
|
+
* container,
|
|
85
|
+
* (scrollLeft) => actions.setScrollPosition(scrollLeft)
|
|
86
|
+
* )
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @param element - Scrollable element
|
|
90
|
+
* @param onScrollChange - Action to call when scroll changes
|
|
91
|
+
* @returns Scroll stream with signals and cleanup
|
|
92
|
+
*/
|
|
93
|
+
export declare function createScrollStreamWithAction(element: HTMLElement, onScrollChange: (scrollLeft: number) => void): ScrollStream;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive scroll stream utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides declarative scroll handling using reactive streams.
|
|
5
|
+
* Automatically handles scroll event optimization and cleanup.
|
|
6
|
+
*/
|
|
7
|
+
import { signal, computed, effect } from './store.js';
|
|
8
|
+
import { cleanup } from './event-streams.js';
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Pure Scroll Calculation Functions
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* Calculate visible percentages from scroll data
|
|
14
|
+
* Pure function - no side effects
|
|
15
|
+
*
|
|
16
|
+
* @param scrollData - Current scroll dimensions
|
|
17
|
+
* @returns Start and end positions as percentages (0-1)
|
|
18
|
+
*/
|
|
19
|
+
export function calculateScrollPercentages(scrollData) {
|
|
20
|
+
const { scrollLeft, scrollWidth, clientWidth } = scrollData;
|
|
21
|
+
if (scrollWidth === 0) {
|
|
22
|
+
return { startX: 0, endX: 1 };
|
|
23
|
+
}
|
|
24
|
+
const startX = scrollLeft / scrollWidth;
|
|
25
|
+
const endX = (scrollLeft + clientWidth) / scrollWidth;
|
|
26
|
+
return {
|
|
27
|
+
startX: Math.max(0, Math.min(1, startX)),
|
|
28
|
+
endX: Math.max(0, Math.min(1, endX)),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Calculate scroll bounds in pixels
|
|
33
|
+
* Pure function - no side effects
|
|
34
|
+
*
|
|
35
|
+
* @param scrollData - Current scroll dimensions
|
|
36
|
+
* @returns Left and right scroll bounds in pixels
|
|
37
|
+
*/
|
|
38
|
+
export function calculateScrollBounds(scrollData) {
|
|
39
|
+
return {
|
|
40
|
+
left: scrollData.scrollLeft,
|
|
41
|
+
right: scrollData.scrollLeft + scrollData.clientWidth,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a reactive scroll stream from an element
|
|
46
|
+
*
|
|
47
|
+
* Emits scroll data as the user scrolls the element.
|
|
48
|
+
* Automatically computes derived values (percentages, bounds).
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const scrollStream = createScrollStream(container)
|
|
53
|
+
*
|
|
54
|
+
* effect(() => {
|
|
55
|
+
* const { startX, endX } = scrollStream.percentages.value
|
|
56
|
+
* console.log('Visible:', startX, 'to', endX)
|
|
57
|
+
* }, [scrollStream.percentages])
|
|
58
|
+
*
|
|
59
|
+
* scrollStream.cleanup()
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @param element - Scrollable element
|
|
63
|
+
* @returns Scroll stream with signals and cleanup
|
|
64
|
+
*/
|
|
65
|
+
export function createScrollStream(element) {
|
|
66
|
+
// Create signals
|
|
67
|
+
const scrollData = signal({
|
|
68
|
+
scrollLeft: element.scrollLeft,
|
|
69
|
+
scrollWidth: element.scrollWidth,
|
|
70
|
+
clientWidth: element.clientWidth,
|
|
71
|
+
});
|
|
72
|
+
// Computed derived values
|
|
73
|
+
const percentages = computed(() => {
|
|
74
|
+
return calculateScrollPercentages(scrollData.value);
|
|
75
|
+
}, [scrollData]);
|
|
76
|
+
const bounds = computed(() => {
|
|
77
|
+
return calculateScrollBounds(scrollData.value);
|
|
78
|
+
}, [scrollData]);
|
|
79
|
+
// Update scroll data on scroll event
|
|
80
|
+
const onScroll = () => {
|
|
81
|
+
scrollData.set({
|
|
82
|
+
scrollLeft: element.scrollLeft,
|
|
83
|
+
scrollWidth: element.scrollWidth,
|
|
84
|
+
clientWidth: element.clientWidth,
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
// Attach scroll listener
|
|
88
|
+
element.addEventListener('scroll', onScroll, { passive: true });
|
|
89
|
+
// Cleanup function
|
|
90
|
+
const cleanupFn = () => {
|
|
91
|
+
element.removeEventListener('scroll', onScroll);
|
|
92
|
+
cleanup(scrollData);
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
scrollData,
|
|
96
|
+
percentages,
|
|
97
|
+
bounds,
|
|
98
|
+
cleanup: cleanupFn,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Create a scroll stream that automatically updates external state
|
|
103
|
+
*
|
|
104
|
+
* This is a convenience wrapper that connects scroll events to a state action.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const scrollStream = createScrollStreamWithAction(
|
|
109
|
+
* container,
|
|
110
|
+
* (scrollLeft) => actions.setScrollPosition(scrollLeft)
|
|
111
|
+
* )
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @param element - Scrollable element
|
|
115
|
+
* @param onScrollChange - Action to call when scroll changes
|
|
116
|
+
* @returns Scroll stream with signals and cleanup
|
|
117
|
+
*/
|
|
118
|
+
export function createScrollStreamWithAction(element, onScrollChange) {
|
|
119
|
+
const stream = createScrollStream(element);
|
|
120
|
+
// Effect to update external state
|
|
121
|
+
const unsubscribe = effect(() => {
|
|
122
|
+
onScrollChange(stream.scrollData.value.scrollLeft);
|
|
123
|
+
}, [stream.scrollData]);
|
|
124
|
+
// Wrap cleanup to include effect cleanup
|
|
125
|
+
const originalCleanup = stream.cleanup;
|
|
126
|
+
stream.cleanup = () => {
|
|
127
|
+
unsubscribe();
|
|
128
|
+
originalCleanup();
|
|
129
|
+
};
|
|
130
|
+
return stream;
|
|
131
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State-driven event emission utilities
|
|
3
|
+
*
|
|
4
|
+
* Automatically emit events when reactive state changes.
|
|
5
|
+
* Ensures events are always in sync with state and removes manual emit() calls.
|
|
6
|
+
*/
|
|
7
|
+
import { type Signal } from './store.js';
|
|
8
|
+
import type { WaveSurferState } from '../state/wavesurfer-state.js';
|
|
9
|
+
export type EventEmitter = {
|
|
10
|
+
emit(event: string, ...args: any[]): void;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Setup automatic event emission from state changes
|
|
14
|
+
*
|
|
15
|
+
* This function subscribes to all relevant state signals and automatically
|
|
16
|
+
* emits corresponding events when state changes. This ensures:
|
|
17
|
+
* - Events are always in sync with state
|
|
18
|
+
* - No manual emit() calls needed
|
|
19
|
+
* - Can't forget to emit an event
|
|
20
|
+
* - Clear event sources (state changes)
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const { state } = createWaveSurferState()
|
|
25
|
+
* const wavesurfer = new WaveSurfer()
|
|
26
|
+
*
|
|
27
|
+
* const cleanup = setupStateEventEmission(state, wavesurfer)
|
|
28
|
+
*
|
|
29
|
+
* // Now state changes automatically emit events
|
|
30
|
+
* state.isPlaying.set(true) // → wavesurfer.emit('play')
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @param state - Reactive state to observe
|
|
34
|
+
* @param emitter - Event emitter to emit events on
|
|
35
|
+
* @returns Cleanup function that removes all subscriptions
|
|
36
|
+
*/
|
|
37
|
+
export declare function setupStateEventEmission(state: WaveSurferState, emitter: EventEmitter): () => void;
|
|
38
|
+
/**
|
|
39
|
+
* Setup custom event emission from signal changes
|
|
40
|
+
*
|
|
41
|
+
* This is a lower-level utility for setting up custom event emission
|
|
42
|
+
* from any signal. Useful when you need more control over event emission logic.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const volumeSignal = signal(1)
|
|
47
|
+
*
|
|
48
|
+
* const cleanup = setupSignalEventEmission(
|
|
49
|
+
* volumeSignal,
|
|
50
|
+
* emitter,
|
|
51
|
+
* (volume) => ['volume', volume]
|
|
52
|
+
* )
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @param signal - Signal to observe
|
|
56
|
+
* @param emitter - Event emitter
|
|
57
|
+
* @param getEventData - Function that returns [eventName, ...args]
|
|
58
|
+
* @returns Cleanup function
|
|
59
|
+
*/
|
|
60
|
+
export declare function setupSignalEventEmission<T>(signal: Signal<T>, emitter: EventEmitter, getEventData: (value: T) => [string, ...any[]]): () => void;
|
|
61
|
+
/**
|
|
62
|
+
* Setup event emission with debouncing
|
|
63
|
+
*
|
|
64
|
+
* Useful for high-frequency events like scroll or timeupdate.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const cleanup = setupDebouncedEventEmission(
|
|
69
|
+
* state.scrollPosition,
|
|
70
|
+
* emitter,
|
|
71
|
+
* (pos) => ['scroll', pos],
|
|
72
|
+
* 100 // debounce 100ms
|
|
73
|
+
* )
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @param signal - Signal to observe
|
|
77
|
+
* @param emitter - Event emitter
|
|
78
|
+
* @param getEventData - Function that returns [eventName, ...args]
|
|
79
|
+
* @param debounceMs - Debounce delay in milliseconds
|
|
80
|
+
* @returns Cleanup function
|
|
81
|
+
*/
|
|
82
|
+
export declare function setupDebouncedEventEmission<T>(signal: Signal<T>, emitter: EventEmitter, getEventData: (value: T) => [string, ...any[]], debounceMs: number): () => void;
|
|
83
|
+
/**
|
|
84
|
+
* Setup conditional event emission
|
|
85
|
+
*
|
|
86
|
+
* Only emit events when a condition is met.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* // Only emit finish event when playing stops at end
|
|
91
|
+
* const cleanup = setupConditionalEventEmission(
|
|
92
|
+
* state.isPlaying,
|
|
93
|
+
* emitter,
|
|
94
|
+
* (isPlaying) => !isPlaying && state.currentTime.value >= state.duration.value,
|
|
95
|
+
* () => ['finish']
|
|
96
|
+
* )
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* @param signal - Signal to observe
|
|
100
|
+
* @param emitter - Event emitter
|
|
101
|
+
* @param condition - Function that returns true when event should emit
|
|
102
|
+
* @param getEventData - Function that returns [eventName, ...args]
|
|
103
|
+
* @returns Cleanup function
|
|
104
|
+
*/
|
|
105
|
+
export declare function setupConditionalEventEmission<T>(signal: Signal<T>, emitter: EventEmitter, condition: (value: T) => boolean, getEventData: (value: T) => [string, ...any[]]): () => void;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State-driven event emission utilities
|
|
3
|
+
*
|
|
4
|
+
* Automatically emit events when reactive state changes.
|
|
5
|
+
* Ensures events are always in sync with state and removes manual emit() calls.
|
|
6
|
+
*/
|
|
7
|
+
import { effect } from './store.js';
|
|
8
|
+
/**
|
|
9
|
+
* Setup automatic event emission from state changes
|
|
10
|
+
*
|
|
11
|
+
* This function subscribes to all relevant state signals and automatically
|
|
12
|
+
* emits corresponding events when state changes. This ensures:
|
|
13
|
+
* - Events are always in sync with state
|
|
14
|
+
* - No manual emit() calls needed
|
|
15
|
+
* - Can't forget to emit an event
|
|
16
|
+
* - Clear event sources (state changes)
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const { state } = createWaveSurferState()
|
|
21
|
+
* const wavesurfer = new WaveSurfer()
|
|
22
|
+
*
|
|
23
|
+
* const cleanup = setupStateEventEmission(state, wavesurfer)
|
|
24
|
+
*
|
|
25
|
+
* // Now state changes automatically emit events
|
|
26
|
+
* state.isPlaying.set(true) // → wavesurfer.emit('play')
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @param state - Reactive state to observe
|
|
30
|
+
* @param emitter - Event emitter to emit events on
|
|
31
|
+
* @returns Cleanup function that removes all subscriptions
|
|
32
|
+
*/
|
|
33
|
+
export function setupStateEventEmission(state, emitter) {
|
|
34
|
+
const cleanups = [];
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Play/Pause Events
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Emit play/pause events when playing state changes
|
|
39
|
+
cleanups.push(effect(() => {
|
|
40
|
+
const isPlaying = state.isPlaying.value;
|
|
41
|
+
emitter.emit(isPlaying ? 'play' : 'pause');
|
|
42
|
+
}, [state.isPlaying]));
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Time Update Events
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Emit timeupdate when current time changes
|
|
47
|
+
cleanups.push(effect(() => {
|
|
48
|
+
const currentTime = state.currentTime.value;
|
|
49
|
+
emitter.emit('timeupdate', currentTime);
|
|
50
|
+
// Also emit audioprocess when playing
|
|
51
|
+
if (state.isPlaying.value) {
|
|
52
|
+
emitter.emit('audioprocess', currentTime);
|
|
53
|
+
}
|
|
54
|
+
}, [state.currentTime, state.isPlaying]));
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Seeking Events
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// Emit seeking event when seeking state changes to true
|
|
59
|
+
cleanups.push(effect(() => {
|
|
60
|
+
const isSeeking = state.isSeeking.value;
|
|
61
|
+
if (isSeeking) {
|
|
62
|
+
emitter.emit('seeking', state.currentTime.value);
|
|
63
|
+
}
|
|
64
|
+
}, [state.isSeeking, state.currentTime]));
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Ready Event
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// Emit ready when state becomes ready
|
|
69
|
+
let wasReady = false;
|
|
70
|
+
cleanups.push(effect(() => {
|
|
71
|
+
const isReady = state.isReady.value;
|
|
72
|
+
if (isReady && !wasReady) {
|
|
73
|
+
wasReady = true;
|
|
74
|
+
emitter.emit('ready', state.duration.value);
|
|
75
|
+
}
|
|
76
|
+
}, [state.isReady, state.duration]));
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// Finish Event
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Emit finish when playback ends (reached duration and stopped)
|
|
81
|
+
let wasPlayingAtEnd = false;
|
|
82
|
+
cleanups.push(effect(() => {
|
|
83
|
+
const isPlaying = state.isPlaying.value;
|
|
84
|
+
const currentTime = state.currentTime.value;
|
|
85
|
+
const duration = state.duration.value;
|
|
86
|
+
// Check if we're at the end
|
|
87
|
+
const isAtEnd = duration > 0 && currentTime >= duration;
|
|
88
|
+
// Emit finish when we were playing at end and now stopped
|
|
89
|
+
if (wasPlayingAtEnd && !isPlaying && isAtEnd) {
|
|
90
|
+
emitter.emit('finish');
|
|
91
|
+
}
|
|
92
|
+
// Track if we're playing at the end
|
|
93
|
+
wasPlayingAtEnd = isPlaying && isAtEnd;
|
|
94
|
+
}, [state.isPlaying, state.currentTime, state.duration]));
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Zoom Events
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// Emit zoom when zoom level changes
|
|
99
|
+
cleanups.push(effect(() => {
|
|
100
|
+
const zoom = state.zoom.value;
|
|
101
|
+
if (zoom > 0) {
|
|
102
|
+
emitter.emit('zoom', zoom);
|
|
103
|
+
}
|
|
104
|
+
}, [state.zoom]));
|
|
105
|
+
// Return cleanup function
|
|
106
|
+
return () => {
|
|
107
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Setup custom event emission from signal changes
|
|
112
|
+
*
|
|
113
|
+
* This is a lower-level utility for setting up custom event emission
|
|
114
|
+
* from any signal. Useful when you need more control over event emission logic.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const volumeSignal = signal(1)
|
|
119
|
+
*
|
|
120
|
+
* const cleanup = setupSignalEventEmission(
|
|
121
|
+
* volumeSignal,
|
|
122
|
+
* emitter,
|
|
123
|
+
* (volume) => ['volume', volume]
|
|
124
|
+
* )
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* @param signal - Signal to observe
|
|
128
|
+
* @param emitter - Event emitter
|
|
129
|
+
* @param getEventData - Function that returns [eventName, ...args]
|
|
130
|
+
* @returns Cleanup function
|
|
131
|
+
*/
|
|
132
|
+
export function setupSignalEventEmission(signal, emitter, getEventData) {
|
|
133
|
+
return effect(() => {
|
|
134
|
+
const value = signal.value;
|
|
135
|
+
const [eventName, ...args] = getEventData(value);
|
|
136
|
+
emitter.emit(eventName, ...args);
|
|
137
|
+
}, [signal]);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Setup event emission with debouncing
|
|
141
|
+
*
|
|
142
|
+
* Useful for high-frequency events like scroll or timeupdate.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* const cleanup = setupDebouncedEventEmission(
|
|
147
|
+
* state.scrollPosition,
|
|
148
|
+
* emitter,
|
|
149
|
+
* (pos) => ['scroll', pos],
|
|
150
|
+
* 100 // debounce 100ms
|
|
151
|
+
* )
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* @param signal - Signal to observe
|
|
155
|
+
* @param emitter - Event emitter
|
|
156
|
+
* @param getEventData - Function that returns [eventName, ...args]
|
|
157
|
+
* @param debounceMs - Debounce delay in milliseconds
|
|
158
|
+
* @returns Cleanup function
|
|
159
|
+
*/
|
|
160
|
+
export function setupDebouncedEventEmission(signal, emitter, getEventData, debounceMs) {
|
|
161
|
+
let timeoutId = null;
|
|
162
|
+
const cleanup = effect(() => {
|
|
163
|
+
const value = signal.value;
|
|
164
|
+
// Clear previous timeout
|
|
165
|
+
if (timeoutId !== null) {
|
|
166
|
+
clearTimeout(timeoutId);
|
|
167
|
+
}
|
|
168
|
+
// Set new timeout
|
|
169
|
+
timeoutId = setTimeout(() => {
|
|
170
|
+
const [eventName, ...args] = getEventData(value);
|
|
171
|
+
emitter.emit(eventName, ...args);
|
|
172
|
+
timeoutId = null;
|
|
173
|
+
}, debounceMs);
|
|
174
|
+
}, [signal]);
|
|
175
|
+
// Return cleanup that also clears pending timeout
|
|
176
|
+
return () => {
|
|
177
|
+
if (timeoutId !== null) {
|
|
178
|
+
clearTimeout(timeoutId);
|
|
179
|
+
}
|
|
180
|
+
cleanup();
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Setup conditional event emission
|
|
185
|
+
*
|
|
186
|
+
* Only emit events when a condition is met.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```typescript
|
|
190
|
+
* // Only emit finish event when playing stops at end
|
|
191
|
+
* const cleanup = setupConditionalEventEmission(
|
|
192
|
+
* state.isPlaying,
|
|
193
|
+
* emitter,
|
|
194
|
+
* (isPlaying) => !isPlaying && state.currentTime.value >= state.duration.value,
|
|
195
|
+
* () => ['finish']
|
|
196
|
+
* )
|
|
197
|
+
* ```
|
|
198
|
+
*
|
|
199
|
+
* @param signal - Signal to observe
|
|
200
|
+
* @param emitter - Event emitter
|
|
201
|
+
* @param condition - Function that returns true when event should emit
|
|
202
|
+
* @param getEventData - Function that returns [eventName, ...args]
|
|
203
|
+
* @returns Cleanup function
|
|
204
|
+
*/
|
|
205
|
+
export function setupConditionalEventEmission(signal, emitter, condition, getEventData) {
|
|
206
|
+
return effect(() => {
|
|
207
|
+
const value = signal.value;
|
|
208
|
+
if (condition(value)) {
|
|
209
|
+
const [eventName, ...args] = getEventData(value);
|
|
210
|
+
emitter.emit(eventName, ...args);
|
|
211
|
+
}
|
|
212
|
+
}, [signal]);
|
|
213
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive primitives for managing state in WaveSurfer
|
|
3
|
+
*
|
|
4
|
+
* This module provides signal-based reactivity similar to SolidJS signals.
|
|
5
|
+
* Signals are reactive values that notify subscribers when they change.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* A reactive value that can be read and subscribed to
|
|
9
|
+
*/
|
|
10
|
+
export interface Signal<T> {
|
|
11
|
+
/** Get the current value */
|
|
12
|
+
get value(): T;
|
|
13
|
+
/** Subscribe to changes. Returns an unsubscribe function. */
|
|
14
|
+
subscribe(callback: (value: T) => void): () => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A writable reactive value that can be updated
|
|
18
|
+
*/
|
|
19
|
+
export interface WritableSignal<T> extends Signal<T> {
|
|
20
|
+
/** Set a new value. Only notifies if value changed. */
|
|
21
|
+
set(value: T): void;
|
|
22
|
+
/** Update value using a function. */
|
|
23
|
+
update(fn: (current: T) => T): void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a reactive signal that notifies subscribers when its value changes
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const count = signal(0)
|
|
31
|
+
* count.subscribe(val => console.log('Count:', val))
|
|
32
|
+
* count.set(5) // Logs: Count: 5
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function signal<T>(initialValue: T): WritableSignal<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Create a computed value that automatically updates when its dependencies change
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const count = signal(0)
|
|
42
|
+
* const doubled = computed(() => count.value * 2, [count])
|
|
43
|
+
* console.log(doubled.value) // 0
|
|
44
|
+
* count.set(5)
|
|
45
|
+
* console.log(doubled.value) // 10
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function computed<T>(fn: () => T, dependencies: Signal<any>[]): Signal<T>;
|
|
49
|
+
/**
|
|
50
|
+
* Run a side effect automatically when dependencies change
|
|
51
|
+
*
|
|
52
|
+
* @param fn - Effect function. Can return a cleanup function.
|
|
53
|
+
* @param dependencies - Signals that trigger the effect when they change
|
|
54
|
+
* @returns Unsubscribe function that stops the effect and runs cleanup
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const count = signal(0)
|
|
59
|
+
* effect(() => {
|
|
60
|
+
* console.log('Count is:', count.value)
|
|
61
|
+
* return () => console.log('Cleanup')
|
|
62
|
+
* }, [count])
|
|
63
|
+
* count.set(5) // Logs: Cleanup, Count is: 5
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function effect(fn: () => void | (() => void), dependencies: Signal<any>[]): () => void;
|