@dawcore/components 0.0.2 → 0.0.3
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/dist/index.d.mts +78 -5
- package/dist/index.d.ts +78 -5
- package/dist/index.js +290 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +289 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as lit from 'lit';
|
|
2
2
|
import { LitElement, PropertyValues, ReactiveController, ReactiveControllerHost } from 'lit';
|
|
3
|
-
import { Peaks, Bits, FadeType, PeakData, ClipTrack } from '@waveform-playlist/core';
|
|
3
|
+
import { Peaks, Bits, FadeType, PeakData, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
|
|
4
4
|
import { PlaylistEngine } from '@waveform-playlist/engine';
|
|
5
5
|
|
|
6
6
|
declare class DawClipElement extends LitElement {
|
|
@@ -335,13 +335,19 @@ interface ClipBounds {
|
|
|
335
335
|
}
|
|
336
336
|
/** Narrow engine contract for clip move/trim interactions. */
|
|
337
337
|
interface ClipEngineContract {
|
|
338
|
-
moveClip(trackId: string, clipId: string, deltaSamples: number, skipAdapter?: boolean):
|
|
338
|
+
moveClip(trackId: string, clipId: string, deltaSamples: number, skipAdapter?: boolean): number;
|
|
339
339
|
trimClip(trackId: string, clipId: string, boundary: 'left' | 'right', deltaSamples: number, skipAdapter?: boolean): void;
|
|
340
340
|
updateTrack(trackId: string): void;
|
|
341
341
|
/** Get a clip's full bounds for trim constraint computation. */
|
|
342
342
|
getClipBounds(trackId: string, clipId: string): ClipBounds | null;
|
|
343
343
|
/** Constrain a trim delta using the engine's collision/bounds logic. */
|
|
344
344
|
constrainTrimDelta(trackId: string, clipId: string, boundary: 'left' | 'right', deltaSamples: number): number;
|
|
345
|
+
/** Begin a transaction — groups mutations into one undo step. */
|
|
346
|
+
beginTransaction(): void;
|
|
347
|
+
/** Commit the transaction — pushes one undo step for all grouped mutations. */
|
|
348
|
+
commitTransaction(): void;
|
|
349
|
+
/** Abort the transaction — restores pre-transaction state without pushing to undo. */
|
|
350
|
+
abortTransaction(): void;
|
|
345
351
|
}
|
|
346
352
|
/** Peak data returned by reextractClipPeaks for imperative waveform updates. */
|
|
347
353
|
interface ClipPeakSlice {
|
|
@@ -593,10 +599,19 @@ declare class DawEditorElement extends LitElement {
|
|
|
593
599
|
play(startTime?: number): Promise<void>;
|
|
594
600
|
pause(): void;
|
|
595
601
|
stop(): void;
|
|
602
|
+
/** Toggle between play and pause. */
|
|
603
|
+
togglePlayPause(): void;
|
|
596
604
|
seekTo(time: number): void;
|
|
605
|
+
/** Undo the last structural edit. */
|
|
606
|
+
undo(): void;
|
|
607
|
+
/** Redo the last undone edit. */
|
|
608
|
+
redo(): void;
|
|
609
|
+
/** Whether undo is available. */
|
|
610
|
+
get canUndo(): boolean;
|
|
611
|
+
/** Whether redo is available. */
|
|
612
|
+
get canRedo(): boolean;
|
|
597
613
|
/** Split the clip under the playhead on the selected track. */
|
|
598
614
|
splitAtPlayhead(): boolean;
|
|
599
|
-
private _onKeyDown;
|
|
600
615
|
recordingStream: MediaStream | null;
|
|
601
616
|
get currentTime(): number;
|
|
602
617
|
get isRecording(): boolean;
|
|
@@ -668,6 +683,59 @@ declare global {
|
|
|
668
683
|
}
|
|
669
684
|
}
|
|
670
685
|
|
|
686
|
+
/** Key binding for remapping — derived from KeyboardShortcut to stay in sync. */
|
|
687
|
+
type KeyBinding = Pick<KeyboardShortcut, 'key' | 'ctrlKey' | 'shiftKey' | 'metaKey' | 'altKey'>;
|
|
688
|
+
interface PlaybackShortcutMap {
|
|
689
|
+
playPause?: KeyBinding;
|
|
690
|
+
stop?: KeyBinding;
|
|
691
|
+
rewindToStart?: KeyBinding;
|
|
692
|
+
}
|
|
693
|
+
interface SplittingShortcutMap {
|
|
694
|
+
splitAtPlayhead?: KeyBinding;
|
|
695
|
+
}
|
|
696
|
+
interface UndoShortcutMap {
|
|
697
|
+
undo?: KeyBinding;
|
|
698
|
+
redo?: KeyBinding;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Render-less element that enables keyboard shortcuts for a parent <daw-editor>.
|
|
702
|
+
* Place inside the editor element. Boolean attributes enable preset categories;
|
|
703
|
+
* JS properties allow remapping and custom shortcuts.
|
|
704
|
+
*
|
|
705
|
+
* ```html
|
|
706
|
+
* <daw-editor>
|
|
707
|
+
* <daw-keyboard-shortcuts playback splitting undo></daw-keyboard-shortcuts>
|
|
708
|
+
* </daw-editor>
|
|
709
|
+
* ```
|
|
710
|
+
*/
|
|
711
|
+
declare class DawKeyboardShortcutsElement extends LitElement {
|
|
712
|
+
playback: boolean;
|
|
713
|
+
splitting: boolean;
|
|
714
|
+
undo: boolean;
|
|
715
|
+
playbackShortcuts: PlaybackShortcutMap | null;
|
|
716
|
+
splittingShortcuts: SplittingShortcutMap | null;
|
|
717
|
+
undoShortcuts: UndoShortcutMap | null;
|
|
718
|
+
/** Additional custom shortcuts. */
|
|
719
|
+
customShortcuts: KeyboardShortcut[];
|
|
720
|
+
private _editor;
|
|
721
|
+
private _cachedShortcuts;
|
|
722
|
+
/** All active shortcuts (read-only, cached). */
|
|
723
|
+
get shortcuts(): KeyboardShortcut[];
|
|
724
|
+
/** Invalidate cached shortcuts when Lit properties change. */
|
|
725
|
+
updated(): void;
|
|
726
|
+
connectedCallback(): void;
|
|
727
|
+
disconnectedCallback(): void;
|
|
728
|
+
createRenderRoot(): this;
|
|
729
|
+
private _buildShortcuts;
|
|
730
|
+
private _makeShortcut;
|
|
731
|
+
private _onKeyDown;
|
|
732
|
+
}
|
|
733
|
+
declare global {
|
|
734
|
+
interface HTMLElementTagNameMap {
|
|
735
|
+
'daw-keyboard-shortcuts': DawKeyboardShortcutsElement;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
671
739
|
declare class AudioResumeController implements ReactiveController {
|
|
672
740
|
private _host;
|
|
673
741
|
private _target;
|
|
@@ -704,15 +772,20 @@ interface SplitEngineContract {
|
|
|
704
772
|
interface SplitHost {
|
|
705
773
|
readonly effectiveSampleRate: number;
|
|
706
774
|
readonly currentTime: number;
|
|
775
|
+
readonly isPlaying: boolean;
|
|
707
776
|
readonly engine: SplitEngineContract | null;
|
|
708
777
|
dispatchEvent(event: Event): boolean;
|
|
778
|
+
stop(): void;
|
|
779
|
+
play(time: number): void;
|
|
709
780
|
}
|
|
710
781
|
/**
|
|
711
|
-
*
|
|
782
|
+
* Split the clip under the playhead on the selected track.
|
|
783
|
+
* Stops playback before split and resumes after to avoid duplicate audio
|
|
784
|
+
* from Transport rescheduling during playback.
|
|
712
785
|
*
|
|
713
786
|
* Returns true if the split occurred and dispatched a daw-clip-split event.
|
|
714
787
|
* Returns false for any guard failure or engine no-op.
|
|
715
788
|
*/
|
|
716
789
|
declare function splitAtPlayhead(host: SplitHost): boolean;
|
|
717
790
|
|
|
718
|
-
export { AudioResumeController, type ClipDescriptor, type ClipEngineContract, ClipPointerHandler, type ClipPointerHost, DawClipElement, type DawClipMoveDetail, type DawClipSplitDetail, type DawClipTrimDetail, DawEditorElement, type DawErrorDetail, type DawEvent, type DawEventMap, type DawFilesLoadErrorDetail, DawPauseButtonElement, DawPlayButtonElement, DawPlayheadElement, DawRecordButtonElement, type DawRecordingCompleteDetail, type DawRecordingErrorDetail, type DawRecordingStartDetail, DawRulerElement, type DawSeekDetail, type DawSelectionDetail, DawSelectionElement, DawStopButtonElement, type DawTrackConnectedDetail, type DawTrackControlDetail, DawTrackControlsElement, DawTrackElement, type DawTrackErrorDetail, type DawTrackIdDetail, type DawTrackRemoveDetail, type DawTrackSelectDetail, DawTransportButton, DawTransportElement, DawWaveformElement, type LoadFilesResult, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type SplitEngineContract, type SplitHost, type TrackDescriptor, splitAtPlayhead };
|
|
791
|
+
export { AudioResumeController, type ClipDescriptor, type ClipEngineContract, ClipPointerHandler, type ClipPointerHost, DawClipElement, type DawClipMoveDetail, type DawClipSplitDetail, type DawClipTrimDetail, DawEditorElement, type DawErrorDetail, type DawEvent, type DawEventMap, type DawFilesLoadErrorDetail, DawKeyboardShortcutsElement, DawPauseButtonElement, DawPlayButtonElement, DawPlayheadElement, DawRecordButtonElement, type DawRecordingCompleteDetail, type DawRecordingErrorDetail, type DawRecordingStartDetail, DawRulerElement, type DawSeekDetail, type DawSelectionDetail, DawSelectionElement, DawStopButtonElement, type DawTrackConnectedDetail, type DawTrackControlDetail, DawTrackControlsElement, DawTrackElement, type DawTrackErrorDetail, type DawTrackIdDetail, type DawTrackRemoveDetail, type DawTrackSelectDetail, DawTransportButton, DawTransportElement, DawWaveformElement, type KeyBinding, type LoadFilesResult, type PlaybackShortcutMap, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type SplitEngineContract, type SplitHost, type SplittingShortcutMap, type TrackDescriptor, type UndoShortcutMap, splitAtPlayhead };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as lit from 'lit';
|
|
2
2
|
import { LitElement, PropertyValues, ReactiveController, ReactiveControllerHost } from 'lit';
|
|
3
|
-
import { Peaks, Bits, FadeType, PeakData, ClipTrack } from '@waveform-playlist/core';
|
|
3
|
+
import { Peaks, Bits, FadeType, PeakData, ClipTrack, KeyboardShortcut } from '@waveform-playlist/core';
|
|
4
4
|
import { PlaylistEngine } from '@waveform-playlist/engine';
|
|
5
5
|
|
|
6
6
|
declare class DawClipElement extends LitElement {
|
|
@@ -335,13 +335,19 @@ interface ClipBounds {
|
|
|
335
335
|
}
|
|
336
336
|
/** Narrow engine contract for clip move/trim interactions. */
|
|
337
337
|
interface ClipEngineContract {
|
|
338
|
-
moveClip(trackId: string, clipId: string, deltaSamples: number, skipAdapter?: boolean):
|
|
338
|
+
moveClip(trackId: string, clipId: string, deltaSamples: number, skipAdapter?: boolean): number;
|
|
339
339
|
trimClip(trackId: string, clipId: string, boundary: 'left' | 'right', deltaSamples: number, skipAdapter?: boolean): void;
|
|
340
340
|
updateTrack(trackId: string): void;
|
|
341
341
|
/** Get a clip's full bounds for trim constraint computation. */
|
|
342
342
|
getClipBounds(trackId: string, clipId: string): ClipBounds | null;
|
|
343
343
|
/** Constrain a trim delta using the engine's collision/bounds logic. */
|
|
344
344
|
constrainTrimDelta(trackId: string, clipId: string, boundary: 'left' | 'right', deltaSamples: number): number;
|
|
345
|
+
/** Begin a transaction — groups mutations into one undo step. */
|
|
346
|
+
beginTransaction(): void;
|
|
347
|
+
/** Commit the transaction — pushes one undo step for all grouped mutations. */
|
|
348
|
+
commitTransaction(): void;
|
|
349
|
+
/** Abort the transaction — restores pre-transaction state without pushing to undo. */
|
|
350
|
+
abortTransaction(): void;
|
|
345
351
|
}
|
|
346
352
|
/** Peak data returned by reextractClipPeaks for imperative waveform updates. */
|
|
347
353
|
interface ClipPeakSlice {
|
|
@@ -593,10 +599,19 @@ declare class DawEditorElement extends LitElement {
|
|
|
593
599
|
play(startTime?: number): Promise<void>;
|
|
594
600
|
pause(): void;
|
|
595
601
|
stop(): void;
|
|
602
|
+
/** Toggle between play and pause. */
|
|
603
|
+
togglePlayPause(): void;
|
|
596
604
|
seekTo(time: number): void;
|
|
605
|
+
/** Undo the last structural edit. */
|
|
606
|
+
undo(): void;
|
|
607
|
+
/** Redo the last undone edit. */
|
|
608
|
+
redo(): void;
|
|
609
|
+
/** Whether undo is available. */
|
|
610
|
+
get canUndo(): boolean;
|
|
611
|
+
/** Whether redo is available. */
|
|
612
|
+
get canRedo(): boolean;
|
|
597
613
|
/** Split the clip under the playhead on the selected track. */
|
|
598
614
|
splitAtPlayhead(): boolean;
|
|
599
|
-
private _onKeyDown;
|
|
600
615
|
recordingStream: MediaStream | null;
|
|
601
616
|
get currentTime(): number;
|
|
602
617
|
get isRecording(): boolean;
|
|
@@ -668,6 +683,59 @@ declare global {
|
|
|
668
683
|
}
|
|
669
684
|
}
|
|
670
685
|
|
|
686
|
+
/** Key binding for remapping — derived from KeyboardShortcut to stay in sync. */
|
|
687
|
+
type KeyBinding = Pick<KeyboardShortcut, 'key' | 'ctrlKey' | 'shiftKey' | 'metaKey' | 'altKey'>;
|
|
688
|
+
interface PlaybackShortcutMap {
|
|
689
|
+
playPause?: KeyBinding;
|
|
690
|
+
stop?: KeyBinding;
|
|
691
|
+
rewindToStart?: KeyBinding;
|
|
692
|
+
}
|
|
693
|
+
interface SplittingShortcutMap {
|
|
694
|
+
splitAtPlayhead?: KeyBinding;
|
|
695
|
+
}
|
|
696
|
+
interface UndoShortcutMap {
|
|
697
|
+
undo?: KeyBinding;
|
|
698
|
+
redo?: KeyBinding;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Render-less element that enables keyboard shortcuts for a parent <daw-editor>.
|
|
702
|
+
* Place inside the editor element. Boolean attributes enable preset categories;
|
|
703
|
+
* JS properties allow remapping and custom shortcuts.
|
|
704
|
+
*
|
|
705
|
+
* ```html
|
|
706
|
+
* <daw-editor>
|
|
707
|
+
* <daw-keyboard-shortcuts playback splitting undo></daw-keyboard-shortcuts>
|
|
708
|
+
* </daw-editor>
|
|
709
|
+
* ```
|
|
710
|
+
*/
|
|
711
|
+
declare class DawKeyboardShortcutsElement extends LitElement {
|
|
712
|
+
playback: boolean;
|
|
713
|
+
splitting: boolean;
|
|
714
|
+
undo: boolean;
|
|
715
|
+
playbackShortcuts: PlaybackShortcutMap | null;
|
|
716
|
+
splittingShortcuts: SplittingShortcutMap | null;
|
|
717
|
+
undoShortcuts: UndoShortcutMap | null;
|
|
718
|
+
/** Additional custom shortcuts. */
|
|
719
|
+
customShortcuts: KeyboardShortcut[];
|
|
720
|
+
private _editor;
|
|
721
|
+
private _cachedShortcuts;
|
|
722
|
+
/** All active shortcuts (read-only, cached). */
|
|
723
|
+
get shortcuts(): KeyboardShortcut[];
|
|
724
|
+
/** Invalidate cached shortcuts when Lit properties change. */
|
|
725
|
+
updated(): void;
|
|
726
|
+
connectedCallback(): void;
|
|
727
|
+
disconnectedCallback(): void;
|
|
728
|
+
createRenderRoot(): this;
|
|
729
|
+
private _buildShortcuts;
|
|
730
|
+
private _makeShortcut;
|
|
731
|
+
private _onKeyDown;
|
|
732
|
+
}
|
|
733
|
+
declare global {
|
|
734
|
+
interface HTMLElementTagNameMap {
|
|
735
|
+
'daw-keyboard-shortcuts': DawKeyboardShortcutsElement;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
671
739
|
declare class AudioResumeController implements ReactiveController {
|
|
672
740
|
private _host;
|
|
673
741
|
private _target;
|
|
@@ -704,15 +772,20 @@ interface SplitEngineContract {
|
|
|
704
772
|
interface SplitHost {
|
|
705
773
|
readonly effectiveSampleRate: number;
|
|
706
774
|
readonly currentTime: number;
|
|
775
|
+
readonly isPlaying: boolean;
|
|
707
776
|
readonly engine: SplitEngineContract | null;
|
|
708
777
|
dispatchEvent(event: Event): boolean;
|
|
778
|
+
stop(): void;
|
|
779
|
+
play(time: number): void;
|
|
709
780
|
}
|
|
710
781
|
/**
|
|
711
|
-
*
|
|
782
|
+
* Split the clip under the playhead on the selected track.
|
|
783
|
+
* Stops playback before split and resumes after to avoid duplicate audio
|
|
784
|
+
* from Transport rescheduling during playback.
|
|
712
785
|
*
|
|
713
786
|
* Returns true if the split occurred and dispatched a daw-clip-split event.
|
|
714
787
|
* Returns false for any guard failure or engine no-op.
|
|
715
788
|
*/
|
|
716
789
|
declare function splitAtPlayhead(host: SplitHost): boolean;
|
|
717
790
|
|
|
718
|
-
export { AudioResumeController, type ClipDescriptor, type ClipEngineContract, ClipPointerHandler, type ClipPointerHost, DawClipElement, type DawClipMoveDetail, type DawClipSplitDetail, type DawClipTrimDetail, DawEditorElement, type DawErrorDetail, type DawEvent, type DawEventMap, type DawFilesLoadErrorDetail, DawPauseButtonElement, DawPlayButtonElement, DawPlayheadElement, DawRecordButtonElement, type DawRecordingCompleteDetail, type DawRecordingErrorDetail, type DawRecordingStartDetail, DawRulerElement, type DawSeekDetail, type DawSelectionDetail, DawSelectionElement, DawStopButtonElement, type DawTrackConnectedDetail, type DawTrackControlDetail, DawTrackControlsElement, DawTrackElement, type DawTrackErrorDetail, type DawTrackIdDetail, type DawTrackRemoveDetail, type DawTrackSelectDetail, DawTransportButton, DawTransportElement, DawWaveformElement, type LoadFilesResult, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type SplitEngineContract, type SplitHost, type TrackDescriptor, splitAtPlayhead };
|
|
791
|
+
export { AudioResumeController, type ClipDescriptor, type ClipEngineContract, ClipPointerHandler, type ClipPointerHost, DawClipElement, type DawClipMoveDetail, type DawClipSplitDetail, type DawClipTrimDetail, DawEditorElement, type DawErrorDetail, type DawEvent, type DawEventMap, type DawFilesLoadErrorDetail, DawKeyboardShortcutsElement, DawPauseButtonElement, DawPlayButtonElement, DawPlayheadElement, DawRecordButtonElement, type DawRecordingCompleteDetail, type DawRecordingErrorDetail, type DawRecordingStartDetail, DawRulerElement, type DawSeekDetail, type DawSelectionDetail, DawSelectionElement, DawStopButtonElement, type DawTrackConnectedDetail, type DawTrackControlDetail, DawTrackControlsElement, DawTrackElement, type DawTrackErrorDetail, type DawTrackIdDetail, type DawTrackRemoveDetail, type DawTrackSelectDetail, DawTransportButton, DawTransportElement, DawWaveformElement, type KeyBinding, type LoadFilesResult, type PlaybackShortcutMap, type PointerEngineContract, RecordingController, type RecordingOptions, type RecordingSession, type SplitEngineContract, type SplitHost, type SplittingShortcutMap, type TrackDescriptor, type UndoShortcutMap, splitAtPlayhead };
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,7 @@ __export(index_exports, {
|
|
|
42
42
|
ClipPointerHandler: () => ClipPointerHandler,
|
|
43
43
|
DawClipElement: () => DawClipElement,
|
|
44
44
|
DawEditorElement: () => DawEditorElement,
|
|
45
|
+
DawKeyboardShortcutsElement: () => DawKeyboardShortcutsElement,
|
|
45
46
|
DawPauseButtonElement: () => DawPauseButtonElement,
|
|
46
47
|
DawPlayButtonElement: () => DawPlayButtonElement,
|
|
47
48
|
DawPlayheadElement: () => DawPlayheadElement,
|
|
@@ -2229,6 +2230,13 @@ var ClipPointerHandler = class {
|
|
|
2229
2230
|
this._isDragging = false;
|
|
2230
2231
|
this._lastDeltaPx = 0;
|
|
2231
2232
|
this._cumulativeDeltaSamples = 0;
|
|
2233
|
+
if (this._host.engine) {
|
|
2234
|
+
this._host.engine.beginTransaction();
|
|
2235
|
+
} else {
|
|
2236
|
+
console.warn(
|
|
2237
|
+
"[dawcore] beginDrag: engine unavailable, drag mutations will not be grouped for undo"
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2232
2240
|
if (mode === "trim-left" || mode === "trim-right") {
|
|
2233
2241
|
const container = this._host.shadowRoot?.querySelector(
|
|
2234
2242
|
`.clip-container[data-clip-id="${clipId}"]`
|
|
@@ -2267,8 +2275,8 @@ var ClipPointerHandler = class {
|
|
|
2267
2275
|
const incrementalDeltaPx = totalDeltaPx - this._lastDeltaPx;
|
|
2268
2276
|
this._lastDeltaPx = totalDeltaPx;
|
|
2269
2277
|
const incrementalDeltaSamples = Math.round(incrementalDeltaPx * this._host.samplesPerPixel);
|
|
2270
|
-
this.
|
|
2271
|
-
|
|
2278
|
+
const applied = engine.moveClip(this._trackId, this._clipId, incrementalDeltaSamples, true);
|
|
2279
|
+
this._cumulativeDeltaSamples += applied;
|
|
2272
2280
|
} else {
|
|
2273
2281
|
const boundary = this._mode === "trim-left" ? "left" : "right";
|
|
2274
2282
|
const rawDeltaSamples = Math.round(totalDeltaPx * this._host.samplesPerPixel);
|
|
@@ -2357,9 +2365,18 @@ var ClipPointerHandler = class {
|
|
|
2357
2365
|
}
|
|
2358
2366
|
})
|
|
2359
2367
|
);
|
|
2368
|
+
} else {
|
|
2369
|
+
console.warn(
|
|
2370
|
+
"[dawcore] engine unavailable at trim drop \u2014 trim not applied for clip " + this._clipId
|
|
2371
|
+
);
|
|
2360
2372
|
}
|
|
2361
2373
|
}
|
|
2362
2374
|
} finally {
|
|
2375
|
+
if (this._isDragging && this._cumulativeDeltaSamples !== 0) {
|
|
2376
|
+
this._host.engine?.commitTransaction();
|
|
2377
|
+
} else {
|
|
2378
|
+
this._host.engine?.abortTransaction();
|
|
2379
|
+
}
|
|
2363
2380
|
this._reset();
|
|
2364
2381
|
}
|
|
2365
2382
|
}
|
|
@@ -2595,6 +2612,35 @@ function addRecordedClip(host, trackId, buf, startSample, durSamples, offsetSamp
|
|
|
2595
2612
|
|
|
2596
2613
|
// src/interactions/split-handler.ts
|
|
2597
2614
|
function splitAtPlayhead(host) {
|
|
2615
|
+
const wasPlaying = host.isPlaying;
|
|
2616
|
+
const time = host.currentTime;
|
|
2617
|
+
if (!canSplitAtTime(host, time)) return false;
|
|
2618
|
+
if (wasPlaying) {
|
|
2619
|
+
host.stop();
|
|
2620
|
+
}
|
|
2621
|
+
let result;
|
|
2622
|
+
try {
|
|
2623
|
+
result = performSplit(host, time);
|
|
2624
|
+
} catch (err) {
|
|
2625
|
+
console.warn("[dawcore] splitAtPlayhead failed: " + String(err));
|
|
2626
|
+
result = false;
|
|
2627
|
+
}
|
|
2628
|
+
if (wasPlaying) {
|
|
2629
|
+
host.play(time);
|
|
2630
|
+
}
|
|
2631
|
+
return result;
|
|
2632
|
+
}
|
|
2633
|
+
function canSplitAtTime(host, time) {
|
|
2634
|
+
const { engine } = host;
|
|
2635
|
+
if (!engine) return false;
|
|
2636
|
+
const state5 = engine.getState();
|
|
2637
|
+
if (!state5.selectedTrackId) return false;
|
|
2638
|
+
const track = state5.tracks.find((t) => t.id === state5.selectedTrackId);
|
|
2639
|
+
if (!track) return false;
|
|
2640
|
+
const atSample = Math.round(time * host.effectiveSampleRate);
|
|
2641
|
+
return !!findClipAtSample(track.clips, atSample);
|
|
2642
|
+
}
|
|
2643
|
+
function performSplit(host, time) {
|
|
2598
2644
|
const { engine } = host;
|
|
2599
2645
|
if (!engine) return false;
|
|
2600
2646
|
const stateBefore = engine.getState();
|
|
@@ -2602,7 +2648,7 @@ function splitAtPlayhead(host) {
|
|
|
2602
2648
|
if (!selectedTrackId) return false;
|
|
2603
2649
|
const track = tracks.find((t) => t.id === selectedTrackId);
|
|
2604
2650
|
if (!track) return false;
|
|
2605
|
-
const atSample = Math.round(
|
|
2651
|
+
const atSample = Math.round(time * host.effectiveSampleRate);
|
|
2606
2652
|
const clip = findClipAtSample(track.clips, atSample);
|
|
2607
2653
|
if (!clip) return false;
|
|
2608
2654
|
const originalClipId = clip.id;
|
|
@@ -2809,6 +2855,19 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2809
2855
|
this._onTrackControl = (e) => {
|
|
2810
2856
|
const { trackId, prop, value } = e.detail ?? {};
|
|
2811
2857
|
if (!trackId || !prop || !DawEditorElement._CONTROL_PROPS.has(prop)) return;
|
|
2858
|
+
if (this._selectedTrackId !== trackId) {
|
|
2859
|
+
this._setSelectedTrackId(trackId);
|
|
2860
|
+
if (this._engine) {
|
|
2861
|
+
this._engine.selectTrack(trackId);
|
|
2862
|
+
}
|
|
2863
|
+
this.dispatchEvent(
|
|
2864
|
+
new CustomEvent("daw-track-select", {
|
|
2865
|
+
bubbles: true,
|
|
2866
|
+
composed: true,
|
|
2867
|
+
detail: { trackId }
|
|
2868
|
+
})
|
|
2869
|
+
);
|
|
2870
|
+
}
|
|
2812
2871
|
const oldDescriptor = this._tracks.get(trackId);
|
|
2813
2872
|
if (oldDescriptor) {
|
|
2814
2873
|
const descriptor = { ...oldDescriptor, [prop]: value };
|
|
@@ -2866,17 +2925,6 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2866
2925
|
);
|
|
2867
2926
|
}
|
|
2868
2927
|
};
|
|
2869
|
-
this._onKeyDown = (e) => {
|
|
2870
|
-
if (!this.interactiveClips) return;
|
|
2871
|
-
if (e.key === "s" || e.key === "S") {
|
|
2872
|
-
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
2873
|
-
const tag = e.target?.tagName;
|
|
2874
|
-
if (tag === "INPUT" || tag === "TEXTAREA") return;
|
|
2875
|
-
if (e.target?.isContentEditable) return;
|
|
2876
|
-
e.preventDefault();
|
|
2877
|
-
this.splitAtPlayhead();
|
|
2878
|
-
}
|
|
2879
|
-
};
|
|
2880
2928
|
// --- Recording ---
|
|
2881
2929
|
this.recordingStream = null;
|
|
2882
2930
|
}
|
|
@@ -2942,10 +2990,6 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2942
2990
|
// --- Lifecycle ---
|
|
2943
2991
|
connectedCallback() {
|
|
2944
2992
|
super.connectedCallback();
|
|
2945
|
-
if (!this.hasAttribute("tabindex")) {
|
|
2946
|
-
this.setAttribute("tabindex", "0");
|
|
2947
|
-
}
|
|
2948
|
-
this.addEventListener("keydown", this._onKeyDown);
|
|
2949
2993
|
this.addEventListener("daw-track-connected", this._onTrackConnected);
|
|
2950
2994
|
this.addEventListener("daw-track-update", this._onTrackUpdate);
|
|
2951
2995
|
this.addEventListener("daw-track-control", this._onTrackControl);
|
|
@@ -2971,7 +3015,6 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
2971
3015
|
}
|
|
2972
3016
|
disconnectedCallback() {
|
|
2973
3017
|
super.disconnectedCallback();
|
|
2974
|
-
this.removeEventListener("keydown", this._onKeyDown);
|
|
2975
3018
|
this.removeEventListener("daw-track-connected", this._onTrackConnected);
|
|
2976
3019
|
this.removeEventListener("daw-track-update", this._onTrackUpdate);
|
|
2977
3020
|
this.removeEventListener("daw-track-control", this._onTrackControl);
|
|
@@ -3263,18 +3306,71 @@ var DawEditorElement = class extends import_lit12.LitElement {
|
|
|
3263
3306
|
this._stopPlayhead();
|
|
3264
3307
|
this.dispatchEvent(new CustomEvent("daw-stop", { bubbles: true, composed: true }));
|
|
3265
3308
|
}
|
|
3309
|
+
/** Toggle between play and pause. */
|
|
3310
|
+
togglePlayPause() {
|
|
3311
|
+
if (this._isPlaying) {
|
|
3312
|
+
this.pause();
|
|
3313
|
+
} else {
|
|
3314
|
+
this.play();
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3266
3317
|
seekTo(time) {
|
|
3267
|
-
if (!this._engine)
|
|
3268
|
-
|
|
3269
|
-
|
|
3318
|
+
if (!this._engine) {
|
|
3319
|
+
console.warn("[dawcore] seekTo: engine not ready, call ignored");
|
|
3320
|
+
return;
|
|
3321
|
+
}
|
|
3322
|
+
if (this._isPlaying) {
|
|
3323
|
+
this.stop();
|
|
3324
|
+
this.play(time);
|
|
3325
|
+
} else {
|
|
3326
|
+
this._engine.seek(time);
|
|
3327
|
+
this._currentTime = time;
|
|
3328
|
+
this._stopPlayhead();
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
/** Undo the last structural edit. */
|
|
3332
|
+
undo() {
|
|
3333
|
+
if (!this._engine) {
|
|
3334
|
+
console.warn("[dawcore] undo: engine not ready, call ignored");
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
this._engine.undo();
|
|
3338
|
+
}
|
|
3339
|
+
/** Redo the last undone edit. */
|
|
3340
|
+
redo() {
|
|
3341
|
+
if (!this._engine) {
|
|
3342
|
+
console.warn("[dawcore] redo: engine not ready, call ignored");
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
this._engine.redo();
|
|
3346
|
+
}
|
|
3347
|
+
/** Whether undo is available. */
|
|
3348
|
+
get canUndo() {
|
|
3349
|
+
return this._engine?.canUndo ?? false;
|
|
3350
|
+
}
|
|
3351
|
+
/** Whether redo is available. */
|
|
3352
|
+
get canRedo() {
|
|
3353
|
+
return this._engine?.canRedo ?? false;
|
|
3270
3354
|
}
|
|
3271
3355
|
/** Split the clip under the playhead on the selected track. */
|
|
3272
3356
|
splitAtPlayhead() {
|
|
3273
3357
|
return splitAtPlayhead({
|
|
3274
3358
|
effectiveSampleRate: this.effectiveSampleRate,
|
|
3275
3359
|
currentTime: this._currentTime,
|
|
3360
|
+
isPlaying: this._isPlaying,
|
|
3276
3361
|
engine: this._engine,
|
|
3277
|
-
dispatchEvent: (e) => this.dispatchEvent(e)
|
|
3362
|
+
dispatchEvent: (e) => this.dispatchEvent(e),
|
|
3363
|
+
stop: () => {
|
|
3364
|
+
this._engine?.stop();
|
|
3365
|
+
this._stopPlayhead();
|
|
3366
|
+
},
|
|
3367
|
+
// Call engine.play directly (synchronous) — not the async editor play()
|
|
3368
|
+
// which yields to microtask queue via await engine.init(). Engine is
|
|
3369
|
+
// already initialized at split time; the async gap causes audio desync.
|
|
3370
|
+
play: (time) => {
|
|
3371
|
+
this._engine?.play(time);
|
|
3372
|
+
this._startPlayhead();
|
|
3373
|
+
}
|
|
3278
3374
|
});
|
|
3279
3375
|
}
|
|
3280
3376
|
get currentTime() {
|
|
@@ -3874,12 +3970,183 @@ __decorateClass([
|
|
|
3874
3970
|
DawRecordButtonElement = __decorateClass([
|
|
3875
3971
|
(0, import_decorators13.customElement)("daw-record-button")
|
|
3876
3972
|
], DawRecordButtonElement);
|
|
3973
|
+
|
|
3974
|
+
// src/elements/daw-keyboard-shortcuts.ts
|
|
3975
|
+
var import_lit16 = require("lit");
|
|
3976
|
+
var import_decorators14 = require("lit/decorators.js");
|
|
3977
|
+
var import_core5 = require("@waveform-playlist/core");
|
|
3978
|
+
var DawKeyboardShortcutsElement = class extends import_lit16.LitElement {
|
|
3979
|
+
constructor() {
|
|
3980
|
+
super(...arguments);
|
|
3981
|
+
this.playback = false;
|
|
3982
|
+
this.splitting = false;
|
|
3983
|
+
this.undo = false;
|
|
3984
|
+
// --- JS properties for remapping ---
|
|
3985
|
+
this.playbackShortcuts = null;
|
|
3986
|
+
this.splittingShortcuts = null;
|
|
3987
|
+
this.undoShortcuts = null;
|
|
3988
|
+
/** Additional custom shortcuts. */
|
|
3989
|
+
this.customShortcuts = [];
|
|
3990
|
+
this._editor = null;
|
|
3991
|
+
this._cachedShortcuts = null;
|
|
3992
|
+
// --- Event handler ---
|
|
3993
|
+
this._onKeyDown = (e) => {
|
|
3994
|
+
const shortcuts = this.shortcuts;
|
|
3995
|
+
if (shortcuts.length === 0) return;
|
|
3996
|
+
try {
|
|
3997
|
+
(0, import_core5.handleKeyboardEvent)(e, shortcuts, true);
|
|
3998
|
+
} catch (err) {
|
|
3999
|
+
console.warn("[dawcore] Keyboard shortcut failed (key=" + e.key + "): " + String(err));
|
|
4000
|
+
const target = this._editor ?? this;
|
|
4001
|
+
target.dispatchEvent(
|
|
4002
|
+
new CustomEvent("daw-error", {
|
|
4003
|
+
bubbles: true,
|
|
4004
|
+
composed: true,
|
|
4005
|
+
detail: { operation: "keyboard-shortcut", key: e.key, error: err }
|
|
4006
|
+
})
|
|
4007
|
+
);
|
|
4008
|
+
}
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
/** All active shortcuts (read-only, cached). */
|
|
4012
|
+
get shortcuts() {
|
|
4013
|
+
if (!this._cachedShortcuts) {
|
|
4014
|
+
this._cachedShortcuts = this._buildShortcuts();
|
|
4015
|
+
}
|
|
4016
|
+
return this._cachedShortcuts;
|
|
4017
|
+
}
|
|
4018
|
+
/** Invalidate cached shortcuts when Lit properties change. */
|
|
4019
|
+
updated() {
|
|
4020
|
+
this._cachedShortcuts = null;
|
|
4021
|
+
}
|
|
4022
|
+
// --- Lifecycle ---
|
|
4023
|
+
connectedCallback() {
|
|
4024
|
+
super.connectedCallback();
|
|
4025
|
+
this._editor = this.closest("daw-editor");
|
|
4026
|
+
if (!this._editor) {
|
|
4027
|
+
console.warn(
|
|
4028
|
+
"[dawcore] <daw-keyboard-shortcuts> must be placed inside a <daw-editor>. Preset shortcuts (playback, splitting, undo) will be inactive; only customShortcuts will fire."
|
|
4029
|
+
);
|
|
4030
|
+
}
|
|
4031
|
+
document.addEventListener("keydown", this._onKeyDown);
|
|
4032
|
+
}
|
|
4033
|
+
disconnectedCallback() {
|
|
4034
|
+
super.disconnectedCallback();
|
|
4035
|
+
document.removeEventListener("keydown", this._onKeyDown);
|
|
4036
|
+
this._editor = null;
|
|
4037
|
+
}
|
|
4038
|
+
// No shadow DOM — render-less element
|
|
4039
|
+
createRenderRoot() {
|
|
4040
|
+
return this;
|
|
4041
|
+
}
|
|
4042
|
+
// --- Shortcut building ---
|
|
4043
|
+
_buildShortcuts() {
|
|
4044
|
+
const editor = this._editor;
|
|
4045
|
+
if (!editor) return this.customShortcuts;
|
|
4046
|
+
const result = [];
|
|
4047
|
+
if (this.playback) {
|
|
4048
|
+
const map = this.playbackShortcuts;
|
|
4049
|
+
result.push(
|
|
4050
|
+
this._makeShortcut(
|
|
4051
|
+
map?.playPause ?? { key: " ", ctrlKey: false, metaKey: false },
|
|
4052
|
+
() => editor.togglePlayPause(),
|
|
4053
|
+
"Play/Pause"
|
|
4054
|
+
),
|
|
4055
|
+
this._makeShortcut(
|
|
4056
|
+
map?.stop ?? { key: "Escape", ctrlKey: false, metaKey: false },
|
|
4057
|
+
() => editor.stop(),
|
|
4058
|
+
"Stop"
|
|
4059
|
+
),
|
|
4060
|
+
this._makeShortcut(
|
|
4061
|
+
map?.rewindToStart ?? { key: "0", ctrlKey: false, metaKey: false },
|
|
4062
|
+
() => editor.seekTo(0),
|
|
4063
|
+
"Rewind to start"
|
|
4064
|
+
)
|
|
4065
|
+
);
|
|
4066
|
+
}
|
|
4067
|
+
if (this.splitting) {
|
|
4068
|
+
const map = this.splittingShortcuts;
|
|
4069
|
+
const binding = map?.splitAtPlayhead ?? {
|
|
4070
|
+
key: "s",
|
|
4071
|
+
ctrlKey: false,
|
|
4072
|
+
metaKey: false,
|
|
4073
|
+
altKey: false
|
|
4074
|
+
};
|
|
4075
|
+
result.push(this._makeShortcut(binding, () => editor.splitAtPlayhead(), "Split at playhead"));
|
|
4076
|
+
}
|
|
4077
|
+
if (this.undo) {
|
|
4078
|
+
const map = this.undoShortcuts;
|
|
4079
|
+
const undoBinding = map?.undo ?? { key: "z" };
|
|
4080
|
+
const redoBinding = map?.redo ?? { key: "z", shiftKey: true };
|
|
4081
|
+
if (undoBinding.ctrlKey === void 0 && undoBinding.metaKey === void 0) {
|
|
4082
|
+
const undoShift = undoBinding.shiftKey === void 0 ? { shiftKey: false } : {};
|
|
4083
|
+
result.push(
|
|
4084
|
+
this._makeShortcut(
|
|
4085
|
+
{ ...undoBinding, ctrlKey: true, ...undoShift },
|
|
4086
|
+
() => editor.undo(),
|
|
4087
|
+
"Undo"
|
|
4088
|
+
),
|
|
4089
|
+
this._makeShortcut(
|
|
4090
|
+
{ ...undoBinding, metaKey: true, ...undoShift },
|
|
4091
|
+
() => editor.undo(),
|
|
4092
|
+
"Undo"
|
|
4093
|
+
)
|
|
4094
|
+
);
|
|
4095
|
+
} else {
|
|
4096
|
+
result.push(this._makeShortcut(undoBinding, () => editor.undo(), "Undo"));
|
|
4097
|
+
}
|
|
4098
|
+
if (redoBinding.ctrlKey === void 0 && redoBinding.metaKey === void 0) {
|
|
4099
|
+
const redoShift = redoBinding.shiftKey === void 0 ? { shiftKey: true } : {};
|
|
4100
|
+
result.push(
|
|
4101
|
+
this._makeShortcut(
|
|
4102
|
+
{ ...redoBinding, ctrlKey: true, ...redoShift },
|
|
4103
|
+
() => editor.redo(),
|
|
4104
|
+
"Redo"
|
|
4105
|
+
),
|
|
4106
|
+
this._makeShortcut(
|
|
4107
|
+
{ ...redoBinding, metaKey: true, ...redoShift },
|
|
4108
|
+
() => editor.redo(),
|
|
4109
|
+
"Redo"
|
|
4110
|
+
)
|
|
4111
|
+
);
|
|
4112
|
+
} else {
|
|
4113
|
+
result.push(this._makeShortcut(redoBinding, () => editor.redo(), "Redo"));
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
result.push(...this.customShortcuts);
|
|
4117
|
+
return result;
|
|
4118
|
+
}
|
|
4119
|
+
_makeShortcut(binding, action, description) {
|
|
4120
|
+
return {
|
|
4121
|
+
key: binding.key,
|
|
4122
|
+
...binding.ctrlKey !== void 0 && { ctrlKey: binding.ctrlKey },
|
|
4123
|
+
...binding.shiftKey !== void 0 && { shiftKey: binding.shiftKey },
|
|
4124
|
+
...binding.metaKey !== void 0 && { metaKey: binding.metaKey },
|
|
4125
|
+
...binding.altKey !== void 0 && { altKey: binding.altKey },
|
|
4126
|
+
action,
|
|
4127
|
+
description
|
|
4128
|
+
};
|
|
4129
|
+
}
|
|
4130
|
+
};
|
|
4131
|
+
__decorateClass([
|
|
4132
|
+
(0, import_decorators14.property)({ type: Boolean })
|
|
4133
|
+
], DawKeyboardShortcutsElement.prototype, "playback", 2);
|
|
4134
|
+
__decorateClass([
|
|
4135
|
+
(0, import_decorators14.property)({ type: Boolean })
|
|
4136
|
+
], DawKeyboardShortcutsElement.prototype, "splitting", 2);
|
|
4137
|
+
__decorateClass([
|
|
4138
|
+
(0, import_decorators14.property)({ type: Boolean })
|
|
4139
|
+
], DawKeyboardShortcutsElement.prototype, "undo", 2);
|
|
4140
|
+
DawKeyboardShortcutsElement = __decorateClass([
|
|
4141
|
+
(0, import_decorators14.customElement)("daw-keyboard-shortcuts")
|
|
4142
|
+
], DawKeyboardShortcutsElement);
|
|
3877
4143
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3878
4144
|
0 && (module.exports = {
|
|
3879
4145
|
AudioResumeController,
|
|
3880
4146
|
ClipPointerHandler,
|
|
3881
4147
|
DawClipElement,
|
|
3882
4148
|
DawEditorElement,
|
|
4149
|
+
DawKeyboardShortcutsElement,
|
|
3883
4150
|
DawPauseButtonElement,
|
|
3884
4151
|
DawPlayButtonElement,
|
|
3885
4152
|
DawPlayheadElement,
|