mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Fix iOS keyboard dismissal issue (#484)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
parent
e9a1ce0555
commit
32935878d8
9 changed files with 332 additions and 84 deletions
|
|
@ -48,8 +48,8 @@ export class ModalWrapper extends LitElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus management
|
// Focus management - but not if we have a special attribute to prevent it
|
||||||
if (changedProperties.has('visible') && this.visible) {
|
if (changedProperties.has('visible') && this.visible && !this.hasAttribute('no-autofocus')) {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const focusable = this.querySelector(
|
const focusable = this.querySelector(
|
||||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,9 @@ export class SessionView extends LitElement {
|
||||||
setShowQuickKeys: (value: boolean) => this.directKeyboardManager.setShowQuickKeys(value),
|
setShowQuickKeys: (value: boolean) => this.directKeyboardManager.setShowQuickKeys(value),
|
||||||
ensureHiddenInputVisible: () => this.directKeyboardManager.ensureHiddenInputVisible(),
|
ensureHiddenInputVisible: () => this.directKeyboardManager.ensureHiddenInputVisible(),
|
||||||
cleanup: () => this.directKeyboardManager.cleanup(),
|
cleanup: () => this.directKeyboardManager.cleanup(),
|
||||||
|
getKeyboardMode: () => this.directKeyboardManager.getKeyboardMode(),
|
||||||
|
isRecentlyEnteredKeyboardMode: () =>
|
||||||
|
this.directKeyboardManager.isRecentlyEnteredKeyboardMode(),
|
||||||
}),
|
}),
|
||||||
setShowQuickKeys: (value: boolean) => {
|
setShowQuickKeys: (value: boolean) => {
|
||||||
this.uiStateManager.setShowQuickKeys(value);
|
this.uiStateManager.setShowQuickKeys(value);
|
||||||
|
|
@ -778,9 +781,13 @@ export class SessionView extends LitElement {
|
||||||
this.uiStateManager.clearCtrlSequence();
|
this.uiStateManager.clearCtrlSequence();
|
||||||
this.uiStateManager.setShowCtrlAlpha(false);
|
this.uiStateManager.setShowCtrlAlpha(false);
|
||||||
|
|
||||||
// Refocus the hidden input
|
// Refocus the hidden input and restart focus retention
|
||||||
if (this.directKeyboardManager.shouldRefocusHiddenInput()) {
|
if (this.directKeyboardManager.shouldRefocusHiddenInput()) {
|
||||||
this.directKeyboardManager.refocusHiddenInput();
|
// Use a small delay to ensure the modal is fully closed first
|
||||||
|
setTimeout(() => {
|
||||||
|
this.directKeyboardManager.refocusHiddenInput();
|
||||||
|
this.directKeyboardManager.startFocusRetentionPublic();
|
||||||
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -792,9 +799,13 @@ export class SessionView extends LitElement {
|
||||||
this.uiStateManager.setShowCtrlAlpha(false);
|
this.uiStateManager.setShowCtrlAlpha(false);
|
||||||
this.uiStateManager.clearCtrlSequence();
|
this.uiStateManager.clearCtrlSequence();
|
||||||
|
|
||||||
// Refocus the hidden input
|
// Refocus the hidden input and restart focus retention
|
||||||
if (this.directKeyboardManager.shouldRefocusHiddenInput()) {
|
if (this.directKeyboardManager.shouldRefocusHiddenInput()) {
|
||||||
this.directKeyboardManager.refocusHiddenInput();
|
// Use a small delay to ensure the modal is fully closed first
|
||||||
|
setTimeout(() => {
|
||||||
|
this.directKeyboardManager.refocusHiddenInput();
|
||||||
|
this.directKeyboardManager.startFocusRetentionPublic();
|
||||||
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -889,6 +900,12 @@ export class SessionView extends LitElement {
|
||||||
if ('scrollToBottom' in terminal) {
|
if ('scrollToBottom' in terminal) {
|
||||||
terminal.scrollToBottom();
|
terminal.scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also ensure the terminal content is scrolled within its container
|
||||||
|
const terminalArea = this.querySelector('.terminal-area');
|
||||||
|
if (terminalArea) {
|
||||||
|
terminalArea.scrollTop = terminalArea.scrollHeight;
|
||||||
|
}
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1019,10 +1036,23 @@ export class SessionView extends LitElement {
|
||||||
contain: layout style paint; /* Isolate terminal updates */
|
contain: layout style paint; /* Isolate terminal updates */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add padding to terminal when quick keys are visible */
|
/* Make terminal content 50px larger to prevent clipping */
|
||||||
|
.terminal-area vibe-terminal,
|
||||||
|
.terminal-area vibe-terminal-binary {
|
||||||
|
height: calc(100% + 50px) !important;
|
||||||
|
margin-bottom: -50px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Transform terminal up when quick keys are visible */
|
||||||
|
.terminal-area[data-quickkeys-visible="true"] {
|
||||||
|
transform: translateY(-110px);
|
||||||
|
transition: transform 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add padding to terminal content when keyboard is visible */
|
||||||
.terminal-area[data-quickkeys-visible="true"] vibe-terminal,
|
.terminal-area[data-quickkeys-visible="true"] vibe-terminal,
|
||||||
.terminal-area[data-quickkeys-visible="true"] vibe-terminal-binary {
|
.terminal-area[data-quickkeys-visible="true"] vibe-terminal-binary {
|
||||||
padding-bottom: 120px !important;
|
padding-bottom: 70px !important;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1123,7 +1153,7 @@ export class SessionView extends LitElement {
|
||||||
// Add safe area padding for landscape mode on mobile to handle notch
|
// Add safe area padding for landscape mode on mobile to handle notch
|
||||||
uiState.isMobile && uiState.isLandscape ? 'safe-area-left safe-area-right' : ''
|
uiState.isMobile && uiState.isLandscape ? 'safe-area-left safe-area-right' : ''
|
||||||
}"
|
}"
|
||||||
data-quickkeys-visible="${uiState.showQuickKeys && uiState.keyboardHeight > 0}"
|
data-quickkeys-visible="${uiState.showQuickKeys}"
|
||||||
>
|
>
|
||||||
${
|
${
|
||||||
this.loadingAnimationManager.isLoading()
|
this.loadingAnimationManager.isLoading()
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
*/
|
*/
|
||||||
import { html, LitElement } from 'lit';
|
import { html, LitElement } from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
import '../modal-wrapper.js';
|
|
||||||
|
|
||||||
@customElement('ctrl-alpha-overlay')
|
@customElement('ctrl-alpha-overlay')
|
||||||
export class CtrlAlphaOverlay extends LitElement {
|
export class CtrlAlphaOverlay extends LitElement {
|
||||||
|
|
@ -28,24 +27,26 @@ export class CtrlAlphaOverlay extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
console.log('[CtrlAlphaOverlay] render called, visible:', this.visible);
|
||||||
if (!this.visible) return null;
|
if (!this.visible) return null;
|
||||||
|
|
||||||
|
// Render directly without modal-wrapper to debug the issue
|
||||||
return html`
|
return html`
|
||||||
<modal-wrapper
|
<!-- Direct backdrop -->
|
||||||
.visible=${this.visible}
|
<div
|
||||||
modalClass="" /* Use modal-wrapper's default z-index */
|
class="fixed inset-0 bg-bg/80 flex items-center justify-center p-4"
|
||||||
contentClass="fixed inset-0 flex flex-col" /* Use modal-wrapper's default z-index */
|
style="z-index: 1000; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);"
|
||||||
ariaLabel="Ctrl key sequence builder"
|
@click=${(e: Event) => {
|
||||||
@close=${() => this.onCancel?.()}
|
if (e.target === e.currentTarget) {
|
||||||
.closeOnBackdrop=${true}
|
this.onCancel?.();
|
||||||
.closeOnEscape=${false}
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<!-- Spacer to push content up above keyboard -->
|
<!-- Modal content -->
|
||||||
<div class="flex-1"></div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="font-mono text-sm mx-4 max-w-sm w-full self-center bg-bg border border-primary rounded-lg p-2.5"
|
class="bg-surface border-2 border-primary rounded-lg p-4 shadow-xl relative"
|
||||||
style="margin-bottom: ${this.keyboardHeight > 0 ? `${this.keyboardHeight}px` : 'env(keyboard-inset-height, 0px)'};"
|
style="z-index: 1001; background-color: rgb(var(--color-bg-secondary)); max-height: 80vh; overflow-y: auto; max-width: 24rem; width: 100%;"
|
||||||
|
@click=${(e: Event) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div class="text-primary text-center mb-2 font-bold">Ctrl + Key</div>
|
<div class="text-primary text-center mb-2 font-bold">Ctrl + Key</div>
|
||||||
|
|
||||||
|
|
@ -142,7 +143,7 @@ export class CtrlAlphaOverlay extends LitElement {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modal-wrapper>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
import { Z_INDEX } from '../../utils/constants.js';
|
import { Z_INDEX } from '../../utils/constants.js';
|
||||||
import { createLogger } from '../../utils/logger.js';
|
import { createLogger } from '../../utils/logger.js';
|
||||||
import type { InputManager } from './input-manager.js';
|
import type { InputManager } from './input-manager.js';
|
||||||
|
import { ManagerEventEmitter } from './interfaces.js';
|
||||||
|
|
||||||
const logger = createLogger('direct-keyboard-manager');
|
const logger = createLogger('direct-keyboard-manager');
|
||||||
|
|
||||||
|
|
@ -47,7 +48,7 @@ export interface DirectKeyboardCallbacks {
|
||||||
clearCtrlSequence(): void;
|
clearCtrlSequence(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DirectKeyboardManager {
|
export class DirectKeyboardManager extends ManagerEventEmitter {
|
||||||
private hiddenInput: HTMLInputElement | null = null;
|
private hiddenInput: HTMLInputElement | null = null;
|
||||||
private focusRetentionInterval: number | null = null;
|
private focusRetentionInterval: number | null = null;
|
||||||
private inputManager: InputManager | null = null;
|
private inputManager: InputManager | null = null;
|
||||||
|
|
@ -67,16 +68,17 @@ export class DirectKeyboardManager {
|
||||||
private instanceId: string;
|
private instanceId: string;
|
||||||
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used for focus state management
|
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used for focus state management
|
||||||
private hiddenInputFocused = false;
|
private hiddenInputFocused = false;
|
||||||
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used for keyboard mode timing
|
|
||||||
private keyboardModeTimestamp = 0;
|
private keyboardModeTimestamp = 0;
|
||||||
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used for IME composition
|
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used for IME composition
|
||||||
private compositionBuffer = '';
|
private compositionBuffer = '';
|
||||||
|
|
||||||
constructor(instanceId: string) {
|
constructor(instanceId: string) {
|
||||||
|
super();
|
||||||
this.instanceId = instanceId;
|
this.instanceId = instanceId;
|
||||||
|
|
||||||
// Add global paste listener for environments where Clipboard API doesn't work
|
// Add global paste listener for environments where Clipboard API doesn't work
|
||||||
this.setupGlobalPasteListener();
|
this.setupGlobalPasteListener();
|
||||||
|
this.ensureHiddenInputVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
setInputManager(inputManager: InputManager): void {
|
setInputManager(inputManager: InputManager): void {
|
||||||
|
|
@ -119,10 +121,10 @@ export class DirectKeyboardManager {
|
||||||
|
|
||||||
focusHiddenInput(): void {
|
focusHiddenInput(): void {
|
||||||
logger.log('Entering keyboard mode');
|
logger.log('Entering keyboard mode');
|
||||||
|
|
||||||
// Enter keyboard mode
|
// Enter keyboard mode
|
||||||
this.keyboardMode = true;
|
this.keyboardMode = true;
|
||||||
this.keyboardModeTimestamp = Date.now();
|
this.keyboardModeTimestamp = Date.now();
|
||||||
this.updateHiddenInputPosition();
|
|
||||||
|
|
||||||
// Add capture phase click handler to prevent any clicks from stealing focus
|
// Add capture phase click handler to prevent any clicks from stealing focus
|
||||||
if (!this.captureClickHandler) {
|
if (!this.captureClickHandler) {
|
||||||
|
|
@ -183,6 +185,11 @@ export class DirectKeyboardManager {
|
||||||
ensureHiddenInputVisible(): void {
|
ensureHiddenInputVisible(): void {
|
||||||
if (!this.hiddenInput) {
|
if (!this.hiddenInput) {
|
||||||
this.createHiddenInput();
|
this.createHiddenInput();
|
||||||
|
} else {
|
||||||
|
// Make sure it's in the DOM
|
||||||
|
if (!this.hiddenInput.parentNode) {
|
||||||
|
document.body.appendChild(this.hiddenInput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show quick keys immediately when entering keyboard mode
|
// Show quick keys immediately when entering keyboard mode
|
||||||
|
|
@ -224,6 +231,8 @@ export class DirectKeyboardManager {
|
||||||
this.hiddenInput = document.createElement('input');
|
this.hiddenInput = document.createElement('input');
|
||||||
this.hiddenInput.type = 'text';
|
this.hiddenInput.type = 'text';
|
||||||
this.hiddenInput.style.position = 'absolute';
|
this.hiddenInput.style.position = 'absolute';
|
||||||
|
|
||||||
|
// Hidden input that receives keyboard focus
|
||||||
this.hiddenInput.style.opacity = '0.01'; // iOS needs non-zero opacity
|
this.hiddenInput.style.opacity = '0.01'; // iOS needs non-zero opacity
|
||||||
this.hiddenInput.style.fontSize = '16px'; // Prevent zoom on iOS
|
this.hiddenInput.style.fontSize = '16px'; // Prevent zoom on iOS
|
||||||
this.hiddenInput.style.border = 'none';
|
this.hiddenInput.style.border = 'none';
|
||||||
|
|
@ -233,6 +242,7 @@ export class DirectKeyboardManager {
|
||||||
this.hiddenInput.style.caretColor = 'transparent';
|
this.hiddenInput.style.caretColor = 'transparent';
|
||||||
this.hiddenInput.style.cursor = 'default';
|
this.hiddenInput.style.cursor = 'default';
|
||||||
this.hiddenInput.style.pointerEvents = 'none'; // Start with pointer events disabled
|
this.hiddenInput.style.pointerEvents = 'none'; // Start with pointer events disabled
|
||||||
|
this.hiddenInput.placeholder = '';
|
||||||
this.hiddenInput.style.webkitUserSelect = 'text'; // iOS specific
|
this.hiddenInput.style.webkitUserSelect = 'text'; // iOS specific
|
||||||
this.hiddenInput.autocapitalize = 'none'; // More explicit than 'off'
|
this.hiddenInput.autocapitalize = 'none'; // More explicit than 'off'
|
||||||
this.hiddenInput.autocomplete = 'off';
|
this.hiddenInput.autocomplete = 'off';
|
||||||
|
|
@ -391,8 +401,9 @@ export class DirectKeyboardManager {
|
||||||
if (this.keyboardMode) {
|
if (this.keyboardMode) {
|
||||||
logger.log('In keyboard mode - maintaining focus');
|
logger.log('In keyboard mode - maintaining focus');
|
||||||
|
|
||||||
// Immediately try to refocus
|
// Add a small delay to allow Done button to exit keyboard mode first
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
// Re-check keyboard mode after delay - Done button might have exited it
|
||||||
if (
|
if (
|
||||||
this.keyboardMode &&
|
this.keyboardMode &&
|
||||||
this.hiddenInput &&
|
this.hiddenInput &&
|
||||||
|
|
@ -401,7 +412,7 @@ export class DirectKeyboardManager {
|
||||||
logger.log('Refocusing hidden input to maintain keyboard');
|
logger.log('Refocusing hidden input to maintain keyboard');
|
||||||
this.hiddenInput.focus();
|
this.hiddenInput.focus();
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 50); // 50ms delay to allow Done button processing
|
||||||
|
|
||||||
// Don't exit keyboard mode or hide quick keys
|
// Don't exit keyboard mode or hide quick keys
|
||||||
return;
|
return;
|
||||||
|
|
@ -437,11 +448,8 @@ export class DirectKeyboardManager {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add to the terminal container
|
// Add to the body for debugging (so it's always visible)
|
||||||
const terminalContainer = this.sessionViewElement?.querySelector('#terminal-container');
|
document.body.appendChild(this.hiddenInput);
|
||||||
if (terminalContainer) {
|
|
||||||
terminalContainer.appendChild(this.hiddenInput);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQuickKeyPress = async (
|
handleQuickKeyPress = async (
|
||||||
|
|
@ -458,6 +466,7 @@ export class DirectKeyboardManager {
|
||||||
if (isSpecial && key === 'Done') {
|
if (isSpecial && key === 'Done') {
|
||||||
// Dismiss the keyboard
|
// Dismiss the keyboard
|
||||||
logger.log('Done button pressed - dismissing keyboard');
|
logger.log('Done button pressed - dismissing keyboard');
|
||||||
|
// Set a flag to prevent refocus attempts
|
||||||
this.dismissKeyboard();
|
this.dismissKeyboard();
|
||||||
return;
|
return;
|
||||||
} else if (isModifier && key === 'Control') {
|
} else if (isModifier && key === 'Control') {
|
||||||
|
|
@ -466,22 +475,17 @@ export class DirectKeyboardManager {
|
||||||
return;
|
return;
|
||||||
} else if (key === 'CtrlFull') {
|
} else if (key === 'CtrlFull') {
|
||||||
// Toggle the full Ctrl+Alpha overlay
|
// Toggle the full Ctrl+Alpha overlay
|
||||||
|
console.log('[DirectKeyboardManager] CtrlFull pressed, toggling Ctrl+Alpha overlay');
|
||||||
if (this.callbacks) {
|
if (this.callbacks) {
|
||||||
this.callbacks.toggleCtrlAlpha();
|
this.callbacks.toggleCtrlAlpha();
|
||||||
}
|
}
|
||||||
|
|
||||||
const showCtrlAlpha = this.callbacks?.getShowCtrlAlpha() ?? false;
|
const showCtrlAlpha = this.callbacks?.getShowCtrlAlpha() ?? false;
|
||||||
|
console.log('[DirectKeyboardManager] showCtrlAlpha after toggle:', showCtrlAlpha);
|
||||||
if (showCtrlAlpha) {
|
if (showCtrlAlpha) {
|
||||||
// Stop focus retention when showing Ctrl overlay
|
// Keep focus retention running - we want the keyboard to stay visible
|
||||||
if (this.focusRetentionInterval) {
|
// The Ctrl+Alpha overlay should show above the keyboard
|
||||||
clearInterval(this.focusRetentionInterval);
|
// Don't stop focus retention or blur the input
|
||||||
this.focusRetentionInterval = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blur the hidden input to prevent it from capturing input
|
|
||||||
if (this.hiddenInput) {
|
|
||||||
this.hiddenInput.blur();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Clear the Ctrl sequence when closing
|
// Clear the Ctrl sequence when closing
|
||||||
if (this.callbacks) {
|
if (this.callbacks) {
|
||||||
|
|
@ -581,6 +585,10 @@ export class DirectKeyboardManager {
|
||||||
} else if (key === 'Delete') {
|
} else if (key === 'Delete') {
|
||||||
// Send delete key
|
// Send delete key
|
||||||
this.inputManager.sendInput('delete');
|
this.inputManager.sendInput('delete');
|
||||||
|
} else if (key === 'Done') {
|
||||||
|
// Safety check - Done should have been handled earlier
|
||||||
|
this.dismissKeyboard();
|
||||||
|
return;
|
||||||
} else if (key.startsWith('F')) {
|
} else if (key.startsWith('F')) {
|
||||||
// Handle function keys F1-F12
|
// Handle function keys F1-F12
|
||||||
const fNum = Number.parseInt(key.substring(1));
|
const fNum = Number.parseInt(key.substring(1));
|
||||||
|
|
@ -613,7 +621,13 @@ export class DirectKeyboardManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the key to terminal
|
// Send the key to terminal
|
||||||
this.inputManager.sendInput(keyToSend.toLowerCase());
|
// For single character keys, send as text
|
||||||
|
if (keyToSend.length === 1) {
|
||||||
|
this.inputManager.sendInputText(keyToSend);
|
||||||
|
} else {
|
||||||
|
// For special keys, send as input command
|
||||||
|
this.inputManager.sendInput(keyToSend.toLowerCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always keep focus on hidden input after any key press (except Done)
|
// Always keep focus on hidden input after any key press (except Done)
|
||||||
|
|
@ -689,14 +703,15 @@ export class DirectKeyboardManager {
|
||||||
if (!this.hiddenInput) return;
|
if (!this.hiddenInput) return;
|
||||||
|
|
||||||
if (this.keyboardMode) {
|
if (this.keyboardMode) {
|
||||||
// In keyboard mode: cover the terminal to receive input
|
// In keyboard mode: position at bottom center but invisible
|
||||||
this.hiddenInput.style.position = 'absolute';
|
this.hiddenInput.style.position = 'fixed';
|
||||||
this.hiddenInput.style.top = '0';
|
this.hiddenInput.style.bottom = '50px'; // Above quick keys
|
||||||
this.hiddenInput.style.left = '0';
|
this.hiddenInput.style.left = '50%';
|
||||||
this.hiddenInput.style.width = '100%';
|
this.hiddenInput.style.transform = 'translateX(-50%)';
|
||||||
|
this.hiddenInput.style.width = '1px';
|
||||||
this.hiddenInput.style.height = '1px';
|
this.hiddenInput.style.height = '1px';
|
||||||
this.hiddenInput.style.zIndex = String(Z_INDEX.TERMINAL_OVERLAY);
|
this.hiddenInput.style.zIndex = String(Z_INDEX.TERMINAL_OVERLAY + 100);
|
||||||
this.hiddenInput.style.pointerEvents = 'none';
|
this.hiddenInput.style.pointerEvents = 'auto'; // Allow focus
|
||||||
} else {
|
} else {
|
||||||
// In scroll mode: position off-screen
|
// In scroll mode: position off-screen
|
||||||
this.hiddenInput.style.position = 'fixed';
|
this.hiddenInput.style.position = 'fixed';
|
||||||
|
|
@ -897,4 +912,61 @@ export class DirectKeyboardManager {
|
||||||
this.hiddenInput = null;
|
this.hiddenInput = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getKeyboardMode(): boolean {
|
||||||
|
return this.keyboardMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
isRecentlyEnteredKeyboardMode(): boolean {
|
||||||
|
// Check if we entered keyboard mode within the last 2 seconds
|
||||||
|
// This helps prevent iOS keyboard animation from being interrupted
|
||||||
|
if (!this.keyboardMode) return false;
|
||||||
|
|
||||||
|
const timeSinceEntry = Date.now() - this.keyboardModeTimestamp;
|
||||||
|
return timeSinceEntry < 2000; // 2 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
showVisibleInputForKeyboard(): void {
|
||||||
|
// Prevent multiple inputs
|
||||||
|
if (document.getElementById('vibe-visible-keyboard-input')) return;
|
||||||
|
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'text';
|
||||||
|
input.id = 'vibe-visible-keyboard-input';
|
||||||
|
input.placeholder = 'Type here...';
|
||||||
|
input.style.position = 'fixed';
|
||||||
|
input.style.bottom = '80px'; // Just above your "Show Keyboard" button
|
||||||
|
input.style.left = '50%';
|
||||||
|
input.style.transform = 'translateX(-50%)';
|
||||||
|
input.style.zIndex = '9999';
|
||||||
|
input.style.fontSize = '18px';
|
||||||
|
input.style.padding = '0.5em';
|
||||||
|
input.style.background = '#fff';
|
||||||
|
input.style.color = '#000';
|
||||||
|
input.style.border = '1px solid #ccc';
|
||||||
|
input.style.borderRadius = '6px';
|
||||||
|
|
||||||
|
document.body.appendChild(input);
|
||||||
|
|
||||||
|
// Add a slight delay before focusing
|
||||||
|
setTimeout(() => {
|
||||||
|
input.focus();
|
||||||
|
console.log('Input focused:', document.activeElement === input);
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
// On blur or enter, remove input and send text
|
||||||
|
const cleanup = () => {
|
||||||
|
if (input.value && this.inputManager) {
|
||||||
|
this.inputManager.sendInputText(input.value);
|
||||||
|
}
|
||||||
|
input.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
input.addEventListener('blur', cleanup);
|
||||||
|
input.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,8 @@ export interface ManagerAccessCallbacks {
|
||||||
setShowQuickKeys?(value: boolean): void;
|
setShowQuickKeys?(value: boolean): void;
|
||||||
ensureHiddenInputVisible(): void;
|
ensureHiddenInputVisible(): void;
|
||||||
cleanup(): void;
|
cleanup(): void;
|
||||||
|
getKeyboardMode(): boolean;
|
||||||
|
isRecentlyEnteredKeyboardMode(): boolean;
|
||||||
};
|
};
|
||||||
getInputManager(): { isKeyboardShortcut(e: KeyboardEvent): boolean } | null;
|
getInputManager(): { isKeyboardShortcut(e: KeyboardEvent): boolean } | null;
|
||||||
getTerminalLifecycleManager(): {
|
getTerminalLifecycleManager(): {
|
||||||
|
|
|
||||||
|
|
@ -413,6 +413,18 @@ export class LifecycleEventManager extends ManagerEventEmitter {
|
||||||
directKeyboardManager &&
|
directKeyboardManager &&
|
||||||
directKeyboardManager.getShowQuickKeys()
|
directKeyboardManager.getShowQuickKeys()
|
||||||
) {
|
) {
|
||||||
|
// Check if we recently entered keyboard mode (within last 2 seconds)
|
||||||
|
// This prevents iOS keyboard animation from being interrupted
|
||||||
|
const isRecentlyEntered =
|
||||||
|
directKeyboardManager.isRecentlyEnteredKeyboardMode?.() ?? false;
|
||||||
|
|
||||||
|
if (isRecentlyEntered) {
|
||||||
|
logger.log(
|
||||||
|
'Ignoring keyboard dismissal - recently entered keyboard mode, likely iOS animation'
|
||||||
|
);
|
||||||
|
return; // Don't hide quick keys during iOS keyboard animation
|
||||||
|
}
|
||||||
|
|
||||||
// Force hide quick keys when keyboard dismisses
|
// Force hide quick keys when keyboard dismisses
|
||||||
this.callbacks.setShowQuickKeys(false);
|
this.callbacks.setShowQuickKeys(false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,15 +107,30 @@ export class OverlaysContainer extends LitElement {
|
||||||
></mobile-input-overlay>
|
></mobile-input-overlay>
|
||||||
|
|
||||||
<!-- Ctrl+Alpha Overlay -->
|
<!-- Ctrl+Alpha Overlay -->
|
||||||
<ctrl-alpha-overlay
|
${(() => {
|
||||||
.visible=${this.uiState.isMobile && this.uiState.showCtrlAlpha}
|
const visible = this.uiState.isMobile && this.uiState.showCtrlAlpha;
|
||||||
.ctrlSequence=${this.uiState.ctrlSequence}
|
console.log(
|
||||||
.keyboardHeight=${this.uiState.keyboardHeight}
|
'[OverlaysContainer] Ctrl+Alpha visible:',
|
||||||
.onCtrlKey=${this.callbacks.onCtrlKey}
|
visible,
|
||||||
.onSendSequence=${this.callbacks.onSendCtrlSequence}
|
'isMobile:',
|
||||||
.onClearSequence=${this.callbacks.onClearCtrlSequence}
|
this.uiState.isMobile,
|
||||||
.onCancel=${this.callbacks.onCtrlAlphaCancel}
|
'showCtrlAlpha:',
|
||||||
></ctrl-alpha-overlay>
|
this.uiState.showCtrlAlpha,
|
||||||
|
'z-index should be above',
|
||||||
|
Z_INDEX.TERMINAL_QUICK_KEYS
|
||||||
|
);
|
||||||
|
return html`
|
||||||
|
<ctrl-alpha-overlay
|
||||||
|
.visible=${visible}
|
||||||
|
.ctrlSequence=${this.uiState.ctrlSequence}
|
||||||
|
.keyboardHeight=${this.uiState.keyboardHeight}
|
||||||
|
.onCtrlKey=${this.callbacks.onCtrlKey}
|
||||||
|
.onSendSequence=${this.callbacks.onSendCtrlSequence}
|
||||||
|
.onClearSequence=${this.callbacks.onClearCtrlSequence}
|
||||||
|
.onCancel=${this.callbacks.onCtrlAlphaCancel}
|
||||||
|
></ctrl-alpha-overlay>
|
||||||
|
`;
|
||||||
|
})()}
|
||||||
|
|
||||||
<!-- Floating Keyboard Button (for direct keyboard mode on mobile) -->
|
<!-- Floating Keyboard Button (for direct keyboard mode on mobile) -->
|
||||||
${
|
${
|
||||||
|
|
@ -126,10 +141,6 @@ export class OverlaysContainer extends LitElement {
|
||||||
@pointerdown=${(e: PointerEvent) => {
|
@pointerdown=${(e: PointerEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
|
||||||
@click=${(e: MouseEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.callbacks?.onKeyboardButtonClick();
|
this.callbacks?.onKeyboardButtonClick();
|
||||||
}}
|
}}
|
||||||
title="Show keyboard"
|
title="Show keyboard"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { html, LitElement, type PropertyValues } from 'lit';
|
import { html, LitElement, type PropertyValues } from 'lit';
|
||||||
import { customElement, property, state } from 'lit/decorators.js';
|
import { customElement, property, state } from 'lit/decorators.js';
|
||||||
|
import { Z_INDEX } from '../utils/constants.js';
|
||||||
|
|
||||||
// Terminal-specific quick keys for mobile use
|
// Terminal-specific quick keys for mobile use
|
||||||
const TERMINAL_QUICK_KEYS = [
|
const TERMINAL_QUICK_KEYS = [
|
||||||
|
|
@ -27,7 +28,6 @@ const TERMINAL_QUICK_KEYS = [
|
||||||
{ key: '/', label: '/', row: 2 },
|
{ key: '/', label: '/', row: 2 },
|
||||||
{ key: '\\', label: '\\', row: 2 },
|
{ key: '\\', label: '\\', row: 2 },
|
||||||
{ key: '-', label: '-', row: 2 },
|
{ key: '-', label: '-', row: 2 },
|
||||||
{ key: 'Done', label: 'Done', special: true, row: 2 },
|
|
||||||
// Third row - additional special characters
|
// Third row - additional special characters
|
||||||
{ key: 'Option', label: '⌥', modifier: true, row: 3 },
|
{ key: 'Option', label: '⌥', modifier: true, row: 3 },
|
||||||
{ key: 'Command', label: '⌘', modifier: true, row: 3 },
|
{ key: 'Command', label: '⌘', modifier: true, row: 3 },
|
||||||
|
|
@ -63,6 +63,9 @@ const FUNCTION_KEYS = Array.from({ length: 12 }, (_, i) => ({
|
||||||
func: true,
|
func: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Done button - always visible
|
||||||
|
const DONE_BUTTON = { key: 'Done', label: 'Done', special: true };
|
||||||
|
|
||||||
@customElement('terminal-quick-keys')
|
@customElement('terminal-quick-keys')
|
||||||
export class TerminalQuickKeys extends LitElement {
|
export class TerminalQuickKeys extends LitElement {
|
||||||
createRenderRoot() {
|
createRenderRoot() {
|
||||||
|
|
@ -110,8 +113,8 @@ export class TerminalQuickKeys extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getButtonSizeClass(_label: string): string {
|
private getButtonSizeClass(_label: string): string {
|
||||||
// Use flexible sizing without constraining width
|
// Use minimal padding to fit more buttons
|
||||||
return this.isLandscape ? 'px-1 py-1' : 'px-1.5 py-1.5';
|
return this.isLandscape ? 'px-0.5 py-1' : 'px-1 py-1.5';
|
||||||
}
|
}
|
||||||
|
|
||||||
private getButtonFontClass(label: string): string {
|
private getButtonFontClass(label: string): string {
|
||||||
|
|
@ -201,6 +204,7 @@ export class TerminalQuickKeys extends LitElement {
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always pass the key press to the handler - let it decide what to do with special keys
|
||||||
if (this.onKeyPress) {
|
if (this.onKeyPress) {
|
||||||
this.onKeyPress(key, isModifier, isSpecial, isToggle);
|
this.onKeyPress(key, isModifier, isSpecial, isToggle);
|
||||||
}
|
}
|
||||||
|
|
@ -272,21 +276,35 @@ export class TerminalQuickKeys extends LitElement {
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: 999999;
|
z-index: ${Z_INDEX.TERMINAL_QUICK_KEYS};
|
||||||
background-color: rgb(var(--color-bg-secondary));
|
background-color: rgb(var(--color-bg-secondary) / 0.98);
|
||||||
width: 100%;
|
backdrop-filter: blur(20px);
|
||||||
/* Properly handle safe areas */
|
-webkit-backdrop-filter: blur(20px);
|
||||||
padding-left: env(safe-area-inset-left);
|
width: 100vw;
|
||||||
padding-right: env(safe-area-inset-right);
|
max-width: 100vw;
|
||||||
|
/* No safe areas needed when above keyboard */
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The actual bar with buttons */
|
/* The actual bar with buttons */
|
||||||
.quick-keys-bar {
|
.quick-keys-bar {
|
||||||
background: rgb(var(--color-bg-secondary));
|
background: transparent;
|
||||||
border-top: 1px solid rgb(var(--color-border-base));
|
border-top: 1px solid rgb(var(--color-border-base) / 0.5);
|
||||||
padding: 0.25rem 0;
|
padding: 0.25rem 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button rows - ensure full width */
|
||||||
|
.quick-keys-bar > div {
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 0.125rem;
|
||||||
|
padding-right: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Quick key buttons */
|
/* Quick key buttons */
|
||||||
|
|
@ -295,6 +313,8 @@ export class TerminalQuickKeys extends LitElement {
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modifier key styling */
|
/* Modifier key styling */
|
||||||
|
|
@ -366,6 +386,25 @@ export class TerminalQuickKeys extends LitElement {
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollable row styling */
|
||||||
|
.scrollable-row {
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar but keep functionality */
|
||||||
|
.scrollable-row::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-row {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toggle button styling */
|
/* Toggle button styling */
|
||||||
|
|
@ -394,6 +433,8 @@ export class TerminalQuickKeys extends LitElement {
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -412,7 +453,7 @@ export class TerminalQuickKeys extends LitElement {
|
||||||
>
|
>
|
||||||
<div class="quick-keys-bar">
|
<div class="quick-keys-bar">
|
||||||
<!-- Row 1 -->
|
<!-- Row 1 -->
|
||||||
<div class="flex gap-0.5 mb-0.5 overflow-x-auto scrollbar-hide px-0.5">
|
<div class="flex gap-0.5 mb-0.5">
|
||||||
${TERMINAL_QUICK_KEYS.filter((k) => k.row === 1).map(
|
${TERMINAL_QUICK_KEYS.filter((k) => k.row === 1).map(
|
||||||
({ key, label, modifier, arrow, toggle }) => html`
|
({ key, label, modifier, arrow, toggle }) => html`
|
||||||
<button
|
<button
|
||||||
|
|
@ -459,12 +500,12 @@ export class TerminalQuickKeys extends LitElement {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Row 2 or Function Keys or Ctrl Shortcuts -->
|
<!-- Row 2 or Function Keys or Ctrl Shortcuts (with Done button always visible) -->
|
||||||
${
|
${
|
||||||
this.showCtrlKeys
|
this.showCtrlKeys
|
||||||
? html`
|
? html`
|
||||||
<!-- Ctrl shortcuts row -->
|
<!-- Ctrl shortcuts row with Done button -->
|
||||||
<div class="flex gap-0.5 mb-0.5 overflow-x-auto scrollbar-hide px-0.5">
|
<div class="flex gap-0.5 mb-0.5">
|
||||||
${CTRL_SHORTCUTS.map(
|
${CTRL_SHORTCUTS.map(
|
||||||
({ key, label, combo, special }) => html`
|
({ key, label, combo, special }) => html`
|
||||||
<button
|
<button
|
||||||
|
|
@ -494,12 +535,38 @@ export class TerminalQuickKeys extends LitElement {
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
|
<!-- Done button -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
tabindex="-1"
|
||||||
|
class="quick-key-btn ${this.getButtonFontClass(DONE_BUTTON.label)} min-w-0 ${this.getButtonSizeClass(DONE_BUTTON.label)} bg-bg-tertiary text-primary font-mono rounded border border-border hover:bg-surface hover:border-primary transition-all whitespace-nowrap special-key"
|
||||||
|
@mousedown=${(e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
@touchstart=${(e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
@touchend=${(e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.handleKeyPress(DONE_BUTTON.key, false, DONE_BUTTON.special, false, e);
|
||||||
|
}}
|
||||||
|
@click=${(e: MouseEvent) => {
|
||||||
|
if (e.detail !== 0) {
|
||||||
|
this.handleKeyPress(DONE_BUTTON.key, false, DONE_BUTTON.special, false, e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${DONE_BUTTON.label}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: this.showFunctionKeys
|
: this.showFunctionKeys
|
||||||
? html`
|
? html`
|
||||||
<!-- Function keys row -->
|
<!-- Function keys row with Done button -->
|
||||||
<div class="flex gap-0.5 mb-0.5 overflow-x-auto scrollbar-hide px-0.5">
|
<div class="flex gap-0.5 mb-0.5">
|
||||||
${FUNCTION_KEYS.map(
|
${FUNCTION_KEYS.map(
|
||||||
({ key, label }) => html`
|
({ key, label }) => html`
|
||||||
<button
|
<button
|
||||||
|
|
@ -529,11 +596,37 @@ export class TerminalQuickKeys extends LitElement {
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
|
<!-- Done button -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
tabindex="-1"
|
||||||
|
class="quick-key-btn ${this.getButtonFontClass(DONE_BUTTON.label)} min-w-0 ${this.getButtonSizeClass(DONE_BUTTON.label)} bg-bg-tertiary text-primary font-mono rounded border border-border hover:bg-surface hover:border-primary transition-all whitespace-nowrap special-key"
|
||||||
|
@mousedown=${(e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
@touchstart=${(e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
@touchend=${(e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.handleKeyPress(DONE_BUTTON.key, false, DONE_BUTTON.special, false, e);
|
||||||
|
}}
|
||||||
|
@click=${(e: MouseEvent) => {
|
||||||
|
if (e.detail !== 0) {
|
||||||
|
this.handleKeyPress(DONE_BUTTON.key, false, DONE_BUTTON.special, false, e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${DONE_BUTTON.label}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<!-- Regular row 2 -->
|
<!-- Regular row 2 -->
|
||||||
<div class="flex gap-0.5 mb-0.5 overflow-x-auto scrollbar-hide px-0.5">
|
<div class="flex gap-0.5 mb-0.5 ">
|
||||||
${TERMINAL_QUICK_KEYS.filter((k) => k.row === 2).map(
|
${TERMINAL_QUICK_KEYS.filter((k) => k.row === 2).map(
|
||||||
({ key, label, modifier, combo, special, toggle }) => html`
|
({ key, label, modifier, combo, special, toggle }) => html`
|
||||||
<button
|
<button
|
||||||
|
|
@ -567,12 +660,38 @@ export class TerminalQuickKeys extends LitElement {
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
|
<!-- Done button (in regular row 2) -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
tabindex="-1"
|
||||||
|
class="quick-key-btn ${this.getButtonFontClass(DONE_BUTTON.label)} min-w-0 ${this.getButtonSizeClass(DONE_BUTTON.label)} bg-bg-tertiary text-primary font-mono rounded border border-border hover:bg-surface hover:border-primary transition-all whitespace-nowrap special-key"
|
||||||
|
@mousedown=${(e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
@touchstart=${(e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
@touchend=${(e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.handleKeyPress(DONE_BUTTON.key, false, DONE_BUTTON.special, false, e);
|
||||||
|
}}
|
||||||
|
@click=${(e: MouseEvent) => {
|
||||||
|
if (e.detail !== 0) {
|
||||||
|
this.handleKeyPress(DONE_BUTTON.key, false, DONE_BUTTON.special, false, e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${DONE_BUTTON.label}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Row 3 - Additional special characters (always visible) -->
|
<!-- Row 3 - Additional special characters (always visible) -->
|
||||||
<div class="flex gap-0.5 overflow-x-auto scrollbar-hide px-0.5">
|
<div class="flex gap-0.5 ">
|
||||||
${TERMINAL_QUICK_KEYS.filter((k) => k.row === 3).map(
|
${TERMINAL_QUICK_KEYS.filter((k) => k.row === 3).map(
|
||||||
({ key, label, modifier, combo, special }) => html`
|
({ key, label, modifier, combo, special }) => html`
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ export const Z_INDEX = {
|
||||||
SIDEBAR_MOBILE: 30,
|
SIDEBAR_MOBILE: 30,
|
||||||
MOBILE_INPUT_OVERLAY: 40,
|
MOBILE_INPUT_OVERLAY: 40,
|
||||||
CTRL_ALPHA_OVERLAY: 45,
|
CTRL_ALPHA_OVERLAY: 45,
|
||||||
|
TERMINAL_QUICK_KEYS: 48,
|
||||||
|
|
||||||
// Dropdowns and popovers (50-99)
|
// Dropdowns and popovers (50-99)
|
||||||
WIDTH_SELECTOR_DROPDOWN: 60,
|
WIDTH_SELECTOR_DROPDOWN: 60,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue