diff --git a/src/components/DrumPadWidget.jsx b/src/components/DrumPadWidget.jsx
index 5098b67..ed54575 100644
--- a/src/components/DrumPadWidget.jsx
+++ b/src/components/DrumPadWidget.jsx
@@ -1,7 +1,5 @@
-import React, { useCallback, useRef, useState } from 'react';
+import React, { useCallback, useState } from 'react';
import { triggerKeyboard } from '../engine/audioEngine.js';
-import { state } from '../engine/state.js';
-import { useIsMobile } from '../hooks/useIsMobile.js';
// 4x4 pad layout — each pad maps to a MIDI note
const PAD_NOTES = [
@@ -66,11 +64,8 @@ function FullscreenDrumPad({ moduleId, onClose }) {
);
}
-export default function DrumPadWidget({ moduleId }) {
- const isMobile = useIsMobile();
- const [fullscreen, setFullscreen] = useState(false);
+export default function DrumPadWidget({ moduleId, fullscreen, onCloseFullscreen }) {
const [activePad, setActivePad] = useState(-1);
- const lastTap = useRef(0);
const hitPad = useCallback((pad, idx) => {
triggerKeyboard(moduleId, midiToFreq(pad.note), true);
@@ -81,19 +76,9 @@ export default function DrumPadWidget({ moduleId }) {
}, 150);
}, [moduleId]);
- const handleDoubleTap = useCallback((e) => {
- const now = Date.now();
- if (now - lastTap.current < 350) {
- e.preventDefault();
- e.stopPropagation();
- setFullscreen(true);
- }
- lastTap.current = now;
- }, []);
-
return (
<>
-
+
{PAD_NOTES.map((pad, i) => (
- {isMobile ? 'Doble-tap: pantalla completa' : 'Tap pads to trigger'}
+ Tap pads to trigger
{fullscreen && (
-
setFullscreen(false)} />
+
)}
>
);
diff --git a/src/components/KeyboardWidget.jsx b/src/components/KeyboardWidget.jsx
index 99dcbda..8329fd4 100644
--- a/src/components/KeyboardWidget.jsx
+++ b/src/components/KeyboardWidget.jsx
@@ -1,7 +1,6 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { triggerKeyboard } from '../engine/audioEngine.js';
import { state } from '../engine/state.js';
-import { useIsMobile } from '../hooks/useIsMobile.js';
const NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
@@ -96,13 +95,10 @@ function FullscreenPiano({ moduleId, initialOctave, onClose }) {
);
}
-export default function KeyboardWidget({ moduleId }) {
+export default function KeyboardWidget({ moduleId, fullscreen, onCloseFullscreen }) {
const mod = state.modules.find(m => m.id === moduleId);
const octave = mod?.params?.octave ?? 4;
const activeKeys = useRef(new Set());
- const isMobile = useIsMobile();
- const [fullscreen, setFullscreen] = useState(false);
- const lastTap = useRef(0);
const playNote = useCallback((semitone) => {
const midi = (octave + 1) * 12 + semitone;
@@ -138,23 +134,13 @@ export default function KeyboardWidget({ moduleId }) {
};
}, [playNote, stopNote]);
- const handleDoubleTap = useCallback((e) => {
- const now = Date.now();
- if (now - lastTap.current < 350) {
- e.preventDefault();
- e.stopPropagation();
- setFullscreen(true);
- }
- lastTap.current = now;
- }, []);
-
// Mini keyboard (1 octave)
const whites = [0, 2, 4, 5, 7, 9, 11];
const blacks = [1, 3, -1, 6, 8, 10];
return (
<>
-
+
@@ -185,7 +171,7 @@ export default function KeyboardWidget({ moduleId }) {
setFullscreen(false)}
+ onClose={onCloseFullscreen}
/>
)}
>
diff --git a/src/components/ModuleNode.jsx b/src/components/ModuleNode.jsx
index 4f006ef..400dcb7 100644
--- a/src/components/ModuleNode.jsx
+++ b/src/components/ModuleNode.jsx
@@ -46,6 +46,7 @@ export default function ModuleNode({ mod, zoom, onStartConnect, onPortPosition }
if (!def) return null;
const isSelected = state.selectedModuleId === mod.id;
+ const [fullscreen, setFullscreen] = useState(false);
// Merge default params
const params = { ...Object.fromEntries(Object.entries(def.params).map(([k, v]) => [k, v.default])), ...mod.params };
@@ -177,6 +178,13 @@ export default function ModuleNode({ mod, zoom, onStartConnect, onPortPosition }
{def.icon}
{def.name}
+ {(mod.type === 'keyboard' || mod.type === 'drumpad') && (
+
+ )}
@@ -247,10 +255,10 @@ export default function ModuleNode({ mod, zoom, onStartConnect, onPortPosition }
{mod.type === 'scope' && }
{/* Keyboard widget */}
- {mod.type === 'keyboard' && }
+ {mod.type === 'keyboard' && setFullscreen(false)} />}
{/* Drum Pad widget */}
- {mod.type === 'drumpad' && }
+ {mod.type === 'drumpad' && setFullscreen(false)} />}
{/* Sequencer widget */}
{mod.type === 'sequencer' && }
diff --git a/src/index.css b/src/index.css
index 5e4f1dc..1a18d20 100644
--- a/src/index.css
+++ b/src/index.css
@@ -35,6 +35,9 @@ html, body, #root {
}
/* Block native browser zoom gestures globally */
html { touch-action: manipulation; }
+/* Prevent text selection globally (except inputs/textareas) */
+* { -webkit-user-select: none; user-select: none; }
+input, textarea, [contenteditable] { -webkit-user-select: text; user-select: text; }
/* ===== Layout ===== */
.app { display: flex; flex-direction: column; height: 100vh; }
@@ -121,6 +124,14 @@ html { touch-action: manipulation; }
}
.module-header .close-btn:hover { background: var(--red); color: #fff; }
+.module-header .expand-btn {
+ width: 18px; height: 18px; border: none; background: transparent;
+ color: var(--text2); cursor: pointer; font-size: 13px; border-radius: 3px;
+ display: flex; align-items: center; justify-content: center;
+ margin-left: auto;
+}
+.module-header .expand-btn:hover { background: var(--accent); color: #000; }
+
.module-body { padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; }
/* Ports */