@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.mjs
CHANGED
|
@@ -2179,6 +2179,13 @@ var ClipPointerHandler = class {
|
|
|
2179
2179
|
this._isDragging = false;
|
|
2180
2180
|
this._lastDeltaPx = 0;
|
|
2181
2181
|
this._cumulativeDeltaSamples = 0;
|
|
2182
|
+
if (this._host.engine) {
|
|
2183
|
+
this._host.engine.beginTransaction();
|
|
2184
|
+
} else {
|
|
2185
|
+
console.warn(
|
|
2186
|
+
"[dawcore] beginDrag: engine unavailable, drag mutations will not be grouped for undo"
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2182
2189
|
if (mode === "trim-left" || mode === "trim-right") {
|
|
2183
2190
|
const container = this._host.shadowRoot?.querySelector(
|
|
2184
2191
|
`.clip-container[data-clip-id="${clipId}"]`
|
|
@@ -2217,8 +2224,8 @@ var ClipPointerHandler = class {
|
|
|
2217
2224
|
const incrementalDeltaPx = totalDeltaPx - this._lastDeltaPx;
|
|
2218
2225
|
this._lastDeltaPx = totalDeltaPx;
|
|
2219
2226
|
const incrementalDeltaSamples = Math.round(incrementalDeltaPx * this._host.samplesPerPixel);
|
|
2220
|
-
this.
|
|
2221
|
-
|
|
2227
|
+
const applied = engine.moveClip(this._trackId, this._clipId, incrementalDeltaSamples, true);
|
|
2228
|
+
this._cumulativeDeltaSamples += applied;
|
|
2222
2229
|
} else {
|
|
2223
2230
|
const boundary = this._mode === "trim-left" ? "left" : "right";
|
|
2224
2231
|
const rawDeltaSamples = Math.round(totalDeltaPx * this._host.samplesPerPixel);
|
|
@@ -2307,9 +2314,18 @@ var ClipPointerHandler = class {
|
|
|
2307
2314
|
}
|
|
2308
2315
|
})
|
|
2309
2316
|
);
|
|
2317
|
+
} else {
|
|
2318
|
+
console.warn(
|
|
2319
|
+
"[dawcore] engine unavailable at trim drop \u2014 trim not applied for clip " + this._clipId
|
|
2320
|
+
);
|
|
2310
2321
|
}
|
|
2311
2322
|
}
|
|
2312
2323
|
} finally {
|
|
2324
|
+
if (this._isDragging && this._cumulativeDeltaSamples !== 0) {
|
|
2325
|
+
this._host.engine?.commitTransaction();
|
|
2326
|
+
} else {
|
|
2327
|
+
this._host.engine?.abortTransaction();
|
|
2328
|
+
}
|
|
2313
2329
|
this._reset();
|
|
2314
2330
|
}
|
|
2315
2331
|
}
|
|
@@ -2545,6 +2561,35 @@ function addRecordedClip(host, trackId, buf, startSample, durSamples, offsetSamp
|
|
|
2545
2561
|
|
|
2546
2562
|
// src/interactions/split-handler.ts
|
|
2547
2563
|
function splitAtPlayhead(host) {
|
|
2564
|
+
const wasPlaying = host.isPlaying;
|
|
2565
|
+
const time = host.currentTime;
|
|
2566
|
+
if (!canSplitAtTime(host, time)) return false;
|
|
2567
|
+
if (wasPlaying) {
|
|
2568
|
+
host.stop();
|
|
2569
|
+
}
|
|
2570
|
+
let result;
|
|
2571
|
+
try {
|
|
2572
|
+
result = performSplit(host, time);
|
|
2573
|
+
} catch (err) {
|
|
2574
|
+
console.warn("[dawcore] splitAtPlayhead failed: " + String(err));
|
|
2575
|
+
result = false;
|
|
2576
|
+
}
|
|
2577
|
+
if (wasPlaying) {
|
|
2578
|
+
host.play(time);
|
|
2579
|
+
}
|
|
2580
|
+
return result;
|
|
2581
|
+
}
|
|
2582
|
+
function canSplitAtTime(host, time) {
|
|
2583
|
+
const { engine } = host;
|
|
2584
|
+
if (!engine) return false;
|
|
2585
|
+
const state5 = engine.getState();
|
|
2586
|
+
if (!state5.selectedTrackId) return false;
|
|
2587
|
+
const track = state5.tracks.find((t) => t.id === state5.selectedTrackId);
|
|
2588
|
+
if (!track) return false;
|
|
2589
|
+
const atSample = Math.round(time * host.effectiveSampleRate);
|
|
2590
|
+
return !!findClipAtSample(track.clips, atSample);
|
|
2591
|
+
}
|
|
2592
|
+
function performSplit(host, time) {
|
|
2548
2593
|
const { engine } = host;
|
|
2549
2594
|
if (!engine) return false;
|
|
2550
2595
|
const stateBefore = engine.getState();
|
|
@@ -2552,7 +2597,7 @@ function splitAtPlayhead(host) {
|
|
|
2552
2597
|
if (!selectedTrackId) return false;
|
|
2553
2598
|
const track = tracks.find((t) => t.id === selectedTrackId);
|
|
2554
2599
|
if (!track) return false;
|
|
2555
|
-
const atSample = Math.round(
|
|
2600
|
+
const atSample = Math.round(time * host.effectiveSampleRate);
|
|
2556
2601
|
const clip = findClipAtSample(track.clips, atSample);
|
|
2557
2602
|
if (!clip) return false;
|
|
2558
2603
|
const originalClipId = clip.id;
|
|
@@ -2759,6 +2804,19 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
2759
2804
|
this._onTrackControl = (e) => {
|
|
2760
2805
|
const { trackId, prop, value } = e.detail ?? {};
|
|
2761
2806
|
if (!trackId || !prop || !DawEditorElement._CONTROL_PROPS.has(prop)) return;
|
|
2807
|
+
if (this._selectedTrackId !== trackId) {
|
|
2808
|
+
this._setSelectedTrackId(trackId);
|
|
2809
|
+
if (this._engine) {
|
|
2810
|
+
this._engine.selectTrack(trackId);
|
|
2811
|
+
}
|
|
2812
|
+
this.dispatchEvent(
|
|
2813
|
+
new CustomEvent("daw-track-select", {
|
|
2814
|
+
bubbles: true,
|
|
2815
|
+
composed: true,
|
|
2816
|
+
detail: { trackId }
|
|
2817
|
+
})
|
|
2818
|
+
);
|
|
2819
|
+
}
|
|
2762
2820
|
const oldDescriptor = this._tracks.get(trackId);
|
|
2763
2821
|
if (oldDescriptor) {
|
|
2764
2822
|
const descriptor = { ...oldDescriptor, [prop]: value };
|
|
@@ -2816,17 +2874,6 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
2816
2874
|
);
|
|
2817
2875
|
}
|
|
2818
2876
|
};
|
|
2819
|
-
this._onKeyDown = (e) => {
|
|
2820
|
-
if (!this.interactiveClips) return;
|
|
2821
|
-
if (e.key === "s" || e.key === "S") {
|
|
2822
|
-
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
2823
|
-
const tag = e.target?.tagName;
|
|
2824
|
-
if (tag === "INPUT" || tag === "TEXTAREA") return;
|
|
2825
|
-
if (e.target?.isContentEditable) return;
|
|
2826
|
-
e.preventDefault();
|
|
2827
|
-
this.splitAtPlayhead();
|
|
2828
|
-
}
|
|
2829
|
-
};
|
|
2830
2877
|
// --- Recording ---
|
|
2831
2878
|
this.recordingStream = null;
|
|
2832
2879
|
}
|
|
@@ -2892,10 +2939,6 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
2892
2939
|
// --- Lifecycle ---
|
|
2893
2940
|
connectedCallback() {
|
|
2894
2941
|
super.connectedCallback();
|
|
2895
|
-
if (!this.hasAttribute("tabindex")) {
|
|
2896
|
-
this.setAttribute("tabindex", "0");
|
|
2897
|
-
}
|
|
2898
|
-
this.addEventListener("keydown", this._onKeyDown);
|
|
2899
2942
|
this.addEventListener("daw-track-connected", this._onTrackConnected);
|
|
2900
2943
|
this.addEventListener("daw-track-update", this._onTrackUpdate);
|
|
2901
2944
|
this.addEventListener("daw-track-control", this._onTrackControl);
|
|
@@ -2921,7 +2964,6 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
2921
2964
|
}
|
|
2922
2965
|
disconnectedCallback() {
|
|
2923
2966
|
super.disconnectedCallback();
|
|
2924
|
-
this.removeEventListener("keydown", this._onKeyDown);
|
|
2925
2967
|
this.removeEventListener("daw-track-connected", this._onTrackConnected);
|
|
2926
2968
|
this.removeEventListener("daw-track-update", this._onTrackUpdate);
|
|
2927
2969
|
this.removeEventListener("daw-track-control", this._onTrackControl);
|
|
@@ -3213,18 +3255,71 @@ var DawEditorElement = class extends LitElement8 {
|
|
|
3213
3255
|
this._stopPlayhead();
|
|
3214
3256
|
this.dispatchEvent(new CustomEvent("daw-stop", { bubbles: true, composed: true }));
|
|
3215
3257
|
}
|
|
3258
|
+
/** Toggle between play and pause. */
|
|
3259
|
+
togglePlayPause() {
|
|
3260
|
+
if (this._isPlaying) {
|
|
3261
|
+
this.pause();
|
|
3262
|
+
} else {
|
|
3263
|
+
this.play();
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3216
3266
|
seekTo(time) {
|
|
3217
|
-
if (!this._engine)
|
|
3218
|
-
|
|
3219
|
-
|
|
3267
|
+
if (!this._engine) {
|
|
3268
|
+
console.warn("[dawcore] seekTo: engine not ready, call ignored");
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
if (this._isPlaying) {
|
|
3272
|
+
this.stop();
|
|
3273
|
+
this.play(time);
|
|
3274
|
+
} else {
|
|
3275
|
+
this._engine.seek(time);
|
|
3276
|
+
this._currentTime = time;
|
|
3277
|
+
this._stopPlayhead();
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
/** Undo the last structural edit. */
|
|
3281
|
+
undo() {
|
|
3282
|
+
if (!this._engine) {
|
|
3283
|
+
console.warn("[dawcore] undo: engine not ready, call ignored");
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
this._engine.undo();
|
|
3287
|
+
}
|
|
3288
|
+
/** Redo the last undone edit. */
|
|
3289
|
+
redo() {
|
|
3290
|
+
if (!this._engine) {
|
|
3291
|
+
console.warn("[dawcore] redo: engine not ready, call ignored");
|
|
3292
|
+
return;
|
|
3293
|
+
}
|
|
3294
|
+
this._engine.redo();
|
|
3295
|
+
}
|
|
3296
|
+
/** Whether undo is available. */
|
|
3297
|
+
get canUndo() {
|
|
3298
|
+
return this._engine?.canUndo ?? false;
|
|
3299
|
+
}
|
|
3300
|
+
/** Whether redo is available. */
|
|
3301
|
+
get canRedo() {
|
|
3302
|
+
return this._engine?.canRedo ?? false;
|
|
3220
3303
|
}
|
|
3221
3304
|
/** Split the clip under the playhead on the selected track. */
|
|
3222
3305
|
splitAtPlayhead() {
|
|
3223
3306
|
return splitAtPlayhead({
|
|
3224
3307
|
effectiveSampleRate: this.effectiveSampleRate,
|
|
3225
3308
|
currentTime: this._currentTime,
|
|
3309
|
+
isPlaying: this._isPlaying,
|
|
3226
3310
|
engine: this._engine,
|
|
3227
|
-
dispatchEvent: (e) => this.dispatchEvent(e)
|
|
3311
|
+
dispatchEvent: (e) => this.dispatchEvent(e),
|
|
3312
|
+
stop: () => {
|
|
3313
|
+
this._engine?.stop();
|
|
3314
|
+
this._stopPlayhead();
|
|
3315
|
+
},
|
|
3316
|
+
// Call engine.play directly (synchronous) — not the async editor play()
|
|
3317
|
+
// which yields to microtask queue via await engine.init(). Engine is
|
|
3318
|
+
// already initialized at split time; the async gap causes audio desync.
|
|
3319
|
+
play: (time) => {
|
|
3320
|
+
this._engine?.play(time);
|
|
3321
|
+
this._startPlayhead();
|
|
3322
|
+
}
|
|
3228
3323
|
});
|
|
3229
3324
|
}
|
|
3230
3325
|
get currentTime() {
|
|
@@ -3824,11 +3919,182 @@ __decorateClass([
|
|
|
3824
3919
|
DawRecordButtonElement = __decorateClass([
|
|
3825
3920
|
customElement13("daw-record-button")
|
|
3826
3921
|
], DawRecordButtonElement);
|
|
3922
|
+
|
|
3923
|
+
// src/elements/daw-keyboard-shortcuts.ts
|
|
3924
|
+
import { LitElement as LitElement11 } from "lit";
|
|
3925
|
+
import { customElement as customElement14, property as property9 } from "lit/decorators.js";
|
|
3926
|
+
import { handleKeyboardEvent } from "@waveform-playlist/core";
|
|
3927
|
+
var DawKeyboardShortcutsElement = class extends LitElement11 {
|
|
3928
|
+
constructor() {
|
|
3929
|
+
super(...arguments);
|
|
3930
|
+
this.playback = false;
|
|
3931
|
+
this.splitting = false;
|
|
3932
|
+
this.undo = false;
|
|
3933
|
+
// --- JS properties for remapping ---
|
|
3934
|
+
this.playbackShortcuts = null;
|
|
3935
|
+
this.splittingShortcuts = null;
|
|
3936
|
+
this.undoShortcuts = null;
|
|
3937
|
+
/** Additional custom shortcuts. */
|
|
3938
|
+
this.customShortcuts = [];
|
|
3939
|
+
this._editor = null;
|
|
3940
|
+
this._cachedShortcuts = null;
|
|
3941
|
+
// --- Event handler ---
|
|
3942
|
+
this._onKeyDown = (e) => {
|
|
3943
|
+
const shortcuts = this.shortcuts;
|
|
3944
|
+
if (shortcuts.length === 0) return;
|
|
3945
|
+
try {
|
|
3946
|
+
handleKeyboardEvent(e, shortcuts, true);
|
|
3947
|
+
} catch (err) {
|
|
3948
|
+
console.warn("[dawcore] Keyboard shortcut failed (key=" + e.key + "): " + String(err));
|
|
3949
|
+
const target = this._editor ?? this;
|
|
3950
|
+
target.dispatchEvent(
|
|
3951
|
+
new CustomEvent("daw-error", {
|
|
3952
|
+
bubbles: true,
|
|
3953
|
+
composed: true,
|
|
3954
|
+
detail: { operation: "keyboard-shortcut", key: e.key, error: err }
|
|
3955
|
+
})
|
|
3956
|
+
);
|
|
3957
|
+
}
|
|
3958
|
+
};
|
|
3959
|
+
}
|
|
3960
|
+
/** All active shortcuts (read-only, cached). */
|
|
3961
|
+
get shortcuts() {
|
|
3962
|
+
if (!this._cachedShortcuts) {
|
|
3963
|
+
this._cachedShortcuts = this._buildShortcuts();
|
|
3964
|
+
}
|
|
3965
|
+
return this._cachedShortcuts;
|
|
3966
|
+
}
|
|
3967
|
+
/** Invalidate cached shortcuts when Lit properties change. */
|
|
3968
|
+
updated() {
|
|
3969
|
+
this._cachedShortcuts = null;
|
|
3970
|
+
}
|
|
3971
|
+
// --- Lifecycle ---
|
|
3972
|
+
connectedCallback() {
|
|
3973
|
+
super.connectedCallback();
|
|
3974
|
+
this._editor = this.closest("daw-editor");
|
|
3975
|
+
if (!this._editor) {
|
|
3976
|
+
console.warn(
|
|
3977
|
+
"[dawcore] <daw-keyboard-shortcuts> must be placed inside a <daw-editor>. Preset shortcuts (playback, splitting, undo) will be inactive; only customShortcuts will fire."
|
|
3978
|
+
);
|
|
3979
|
+
}
|
|
3980
|
+
document.addEventListener("keydown", this._onKeyDown);
|
|
3981
|
+
}
|
|
3982
|
+
disconnectedCallback() {
|
|
3983
|
+
super.disconnectedCallback();
|
|
3984
|
+
document.removeEventListener("keydown", this._onKeyDown);
|
|
3985
|
+
this._editor = null;
|
|
3986
|
+
}
|
|
3987
|
+
// No shadow DOM — render-less element
|
|
3988
|
+
createRenderRoot() {
|
|
3989
|
+
return this;
|
|
3990
|
+
}
|
|
3991
|
+
// --- Shortcut building ---
|
|
3992
|
+
_buildShortcuts() {
|
|
3993
|
+
const editor = this._editor;
|
|
3994
|
+
if (!editor) return this.customShortcuts;
|
|
3995
|
+
const result = [];
|
|
3996
|
+
if (this.playback) {
|
|
3997
|
+
const map = this.playbackShortcuts;
|
|
3998
|
+
result.push(
|
|
3999
|
+
this._makeShortcut(
|
|
4000
|
+
map?.playPause ?? { key: " ", ctrlKey: false, metaKey: false },
|
|
4001
|
+
() => editor.togglePlayPause(),
|
|
4002
|
+
"Play/Pause"
|
|
4003
|
+
),
|
|
4004
|
+
this._makeShortcut(
|
|
4005
|
+
map?.stop ?? { key: "Escape", ctrlKey: false, metaKey: false },
|
|
4006
|
+
() => editor.stop(),
|
|
4007
|
+
"Stop"
|
|
4008
|
+
),
|
|
4009
|
+
this._makeShortcut(
|
|
4010
|
+
map?.rewindToStart ?? { key: "0", ctrlKey: false, metaKey: false },
|
|
4011
|
+
() => editor.seekTo(0),
|
|
4012
|
+
"Rewind to start"
|
|
4013
|
+
)
|
|
4014
|
+
);
|
|
4015
|
+
}
|
|
4016
|
+
if (this.splitting) {
|
|
4017
|
+
const map = this.splittingShortcuts;
|
|
4018
|
+
const binding = map?.splitAtPlayhead ?? {
|
|
4019
|
+
key: "s",
|
|
4020
|
+
ctrlKey: false,
|
|
4021
|
+
metaKey: false,
|
|
4022
|
+
altKey: false
|
|
4023
|
+
};
|
|
4024
|
+
result.push(this._makeShortcut(binding, () => editor.splitAtPlayhead(), "Split at playhead"));
|
|
4025
|
+
}
|
|
4026
|
+
if (this.undo) {
|
|
4027
|
+
const map = this.undoShortcuts;
|
|
4028
|
+
const undoBinding = map?.undo ?? { key: "z" };
|
|
4029
|
+
const redoBinding = map?.redo ?? { key: "z", shiftKey: true };
|
|
4030
|
+
if (undoBinding.ctrlKey === void 0 && undoBinding.metaKey === void 0) {
|
|
4031
|
+
const undoShift = undoBinding.shiftKey === void 0 ? { shiftKey: false } : {};
|
|
4032
|
+
result.push(
|
|
4033
|
+
this._makeShortcut(
|
|
4034
|
+
{ ...undoBinding, ctrlKey: true, ...undoShift },
|
|
4035
|
+
() => editor.undo(),
|
|
4036
|
+
"Undo"
|
|
4037
|
+
),
|
|
4038
|
+
this._makeShortcut(
|
|
4039
|
+
{ ...undoBinding, metaKey: true, ...undoShift },
|
|
4040
|
+
() => editor.undo(),
|
|
4041
|
+
"Undo"
|
|
4042
|
+
)
|
|
4043
|
+
);
|
|
4044
|
+
} else {
|
|
4045
|
+
result.push(this._makeShortcut(undoBinding, () => editor.undo(), "Undo"));
|
|
4046
|
+
}
|
|
4047
|
+
if (redoBinding.ctrlKey === void 0 && redoBinding.metaKey === void 0) {
|
|
4048
|
+
const redoShift = redoBinding.shiftKey === void 0 ? { shiftKey: true } : {};
|
|
4049
|
+
result.push(
|
|
4050
|
+
this._makeShortcut(
|
|
4051
|
+
{ ...redoBinding, ctrlKey: true, ...redoShift },
|
|
4052
|
+
() => editor.redo(),
|
|
4053
|
+
"Redo"
|
|
4054
|
+
),
|
|
4055
|
+
this._makeShortcut(
|
|
4056
|
+
{ ...redoBinding, metaKey: true, ...redoShift },
|
|
4057
|
+
() => editor.redo(),
|
|
4058
|
+
"Redo"
|
|
4059
|
+
)
|
|
4060
|
+
);
|
|
4061
|
+
} else {
|
|
4062
|
+
result.push(this._makeShortcut(redoBinding, () => editor.redo(), "Redo"));
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
result.push(...this.customShortcuts);
|
|
4066
|
+
return result;
|
|
4067
|
+
}
|
|
4068
|
+
_makeShortcut(binding, action, description) {
|
|
4069
|
+
return {
|
|
4070
|
+
key: binding.key,
|
|
4071
|
+
...binding.ctrlKey !== void 0 && { ctrlKey: binding.ctrlKey },
|
|
4072
|
+
...binding.shiftKey !== void 0 && { shiftKey: binding.shiftKey },
|
|
4073
|
+
...binding.metaKey !== void 0 && { metaKey: binding.metaKey },
|
|
4074
|
+
...binding.altKey !== void 0 && { altKey: binding.altKey },
|
|
4075
|
+
action,
|
|
4076
|
+
description
|
|
4077
|
+
};
|
|
4078
|
+
}
|
|
4079
|
+
};
|
|
4080
|
+
__decorateClass([
|
|
4081
|
+
property9({ type: Boolean })
|
|
4082
|
+
], DawKeyboardShortcutsElement.prototype, "playback", 2);
|
|
4083
|
+
__decorateClass([
|
|
4084
|
+
property9({ type: Boolean })
|
|
4085
|
+
], DawKeyboardShortcutsElement.prototype, "splitting", 2);
|
|
4086
|
+
__decorateClass([
|
|
4087
|
+
property9({ type: Boolean })
|
|
4088
|
+
], DawKeyboardShortcutsElement.prototype, "undo", 2);
|
|
4089
|
+
DawKeyboardShortcutsElement = __decorateClass([
|
|
4090
|
+
customElement14("daw-keyboard-shortcuts")
|
|
4091
|
+
], DawKeyboardShortcutsElement);
|
|
3827
4092
|
export {
|
|
3828
4093
|
AudioResumeController,
|
|
3829
4094
|
ClipPointerHandler,
|
|
3830
4095
|
DawClipElement,
|
|
3831
4096
|
DawEditorElement,
|
|
4097
|
+
DawKeyboardShortcutsElement,
|
|
3832
4098
|
DawPauseButtonElement,
|
|
3833
4099
|
DawPlayButtonElement,
|
|
3834
4100
|
DawPlayheadElement,
|