fixed: arrow navigation

This commit is contained in:
daboe01
2025-07-02 19:54:52 +02:00
parent 266965f506
commit 6b2494ede4
3 changed files with 78 additions and 44 deletions
+21 -3
View File
@@ -70,6 +70,7 @@ var _CPEventPeriodicEventPeriod = 0,
BOOL _isARepeat;
unsigned _keyCode;
DOMEvent _DOMEvent;
BOOL _isActionKey;
int _data1;
int _data2;
short _subtype;
@@ -110,17 +111,18 @@ var _CPEventPeriodicEventPeriod = 0,
@param unmodCharacters the string of keys pressed without the presence of any modifiers other than Shift
@param repeatKey \c YES if this is caused by the system repeat as opposed to the user pressing the key again
@param code a number associated with the keyboard key of this event
@param isAnActionKey a BOOL indicating whether this key is an action key (e.g. a function key)
@throws CPInternalInconsistencyException if \c anEventType is not a CPKeyDown,
CPKeyUp or CPFlagsChanged
@return the keyboard event
*/
+ (CPEvent)keyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code isActionKey:(BOOL)isAnActionKey
{
return [[self alloc] _initKeyEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code];
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code isActionKey:isAnActionKey];
}
/*!
@@ -252,7 +254,7 @@ var _CPEventPeriodicEventPeriod = 0,
/* @ignore */
- (id)_initKeyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code isActionKey:(BOOL)isAnActionKey
{
if (self = [self _initWithType:anEventType])
{
@@ -264,6 +266,7 @@ var _CPEventPeriodicEventPeriod = 0,
_charactersIgnoringModifiers = unmodCharacters;
_isARepeat = isARepeat;
_keyCode = code;
_isActionKey = isAnActionKey;
_windowNumber = aWindowNumber;
}
@@ -571,6 +574,21 @@ var _CPEventPeriodicEventPeriod = 0,
return !firstResponderIsText;
}
- (BOOL)_isActionOrCommandEvent
{
// This method is now platform-agnostic. It checks for abstract properties
// of the event, including the _isActionKey flag that was set at creation time.
return (
// Is it a command shortcut?
(_modifierFlags & (CPCommandKeyMask | CPControlKeyMask | CPAlternateKeyMask)) ||
// Is it a key that doesn't produce a character?
([_characters length] === 0) ||
// Was it identified as an action key by the platform-specific layer?
_isActionKey
);
}
/*!
Return YES if this event is a part of processing a browser controlled cut or paste event
where the browser will go ahead and do the work of cutting or pasting within the input
+14 -9
View File
@@ -1022,16 +1022,18 @@ Sets the selection to a range of characters in response to user action.
- (void)keyDown:(CPEvent)event
{
[[_window platformWindow] _propagateCurrentDOMEvent:YES];
[[_window platformWindow] _propagateCurrentDOMEvent:YES]; // for the _CPNativeInputManager (necessary at least on FF and chrome)
// Only call interpretKeyEvents for non-printable keys (navigation, commands).
// Printable characters are handled exclusively by _CPNativeInputManager.
var key = event.key;
if (key && (key.length > 1 || (event.modifierFlags & (CPCommandKeyMask | CPAlternateKeyMask | CPControlKeyMask))))
if ([event _isActionOrCommandEvent])
{
// This is a navigation key, action key, or command shortcut.
// Let the Cappuccino framework's key binding system handle it.
[self interpretKeyEvents:[event]];
}
// This is a normal printable character ('a', '1', '$', 'é').
// We do nothing, preventing the double-insertion bug. The _CPNativeInputManager
// will capture it from the hidden input field and insert it correctly.
[_caret setPermanentlyVisible:YES];
}
@@ -2661,7 +2663,10 @@ var _CPCopyPlaceholder = '-';
var currentFirstResponder = [[CPApp keyWindow] firstResponder];
if (currentFirstResponder && [currentFirstResponder respondsToSelector:@selector(insertText:)])
[currentFirstResponder insertText:textToInsert];
// setTimeout to prevent flickering
setTimeout(function(){
[currentFirstResponder insertText:textToInsert]
}, 20);
// CRUCIAL: Clear the field immediately after grabbing its content.
_CPNativeInputField.innerHTML = '';
+43 -32
View File
@@ -725,16 +725,13 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
// With a few exceptions, all key events are blocked from propagating to
// the browser. The logic here allows browser shortcuts (Cmd/Ctrl keys)
// and function keys (F1-F12) to pass through, unless explicitly blacklisted.
StopDOMEventPropagation = YES;
StopDOMEventPropagation = YES;
var keyCodeForPropagationCheck = aDOMEvent.keyCode || 0;
var charForPropagationCheck = String.fromCharCode(keyCodeForPropagationCheck).toLowerCase();
// Make sure it is not in the blacklists.
if (!(CharacterKeysToPrevent[charForPropagationCheck] || KeyCodesToPrevent[keyCodeForPropagationCheck]))
{
// It is not in the blacklist, let it through if the ctrl/cmd key is
// also down or it's in the whitelist.
if ((modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) || KeyCodesToAllow[keyCodeForPropagationCheck])
StopDOMEventPropagation = NO;
}
@@ -742,57 +739,75 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
var characters = @"",
charactersIgnoringModifiers = @"";
// Grab and store the keyCode for mapping and compatibility.
// This property is deprecated but necessary for the key maps.
var keyCode = aDOMEvent.keyCode;
if (keyCode in MozKeyCodeToKeyCodeMap)
keyCode = MozKeyCodeToKeyCodeMap[keyCode];
var isActionKey;
var key = aDOMEvent.key;
// Modern Browser Path (use event.key)
if (key) {
isActionKey =
key === 'Enter' ||
key === 'Backspace' ||
key === 'Tab' ||
key === 'Escape' ||
key === 'Delete' ||
key.startsWith('Arrow') ||
key === 'Home' ||
key === 'End' ||
key === 'PageUp' ||
key === 'PageDown';
}
// Legacy Browser Fallback (use event.keyCode)
else
{
isActionKey =
(keyCode === 13) || // Enter
(keyCode === 8) || // Backspace
(keyCode === 9) || // Tab
(keyCode === 27) || // Escape
(keyCode === 46) || // Delete
(keyCode >= 37 && keyCode <= 40); // Arrow keys
}
switch (aDOMEvent.type)
{
case "keydown":
// For modifier keys, we create a CPFlagsChanged event and stop processing.
// For modifier keys, create a CPFlagsChanged event and stop processing.
// These are always considered "action keys".
if ([ModifierKeyCodes containsObject:keyCode])
{
event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags
timestamp:timestamp windowNumber:windowNumber context:nil
characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode];
characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode isActionKey:YES];
break;
}
// Determine if the event is a repeat. Use the modern `event.repeat` property
// with a fallback to our manual state tracking for older browsers.
var isARepeat = !!aDOMEvent.repeat || (_charCodes[keyCode] != nil);
_charCodes[keyCode] = YES; // Mark the key as down for fallback repeat detection.
_charCodes[keyCode] = YES;
// Determine the character for the event.
// Priority 1: Use the modern `event.key` property. It's the most reliable.
if (aDOMEvent.key)
{
if (aDOMEvent.key.length === 1)
{
// This is a printing character (e.g., "a", "P", "#").
characters = aDOMEvent.key;
}
else
{
// This is a named, non-printing key (e.g., "Enter", "ArrowLeft").
// Map the standard key name to our framework's internal constant.
characters = KeyNameToUnicodeMap[aDOMEvent.key] || aDOMEvent.key;
}
}
// Priority 2: Fallback for older browsers without `event.key`.
else
{
// First, check if it's a known non-printing key in our legacy map.
characters = KeyCodesToUnicodeMap[keyCode];
// If not, it's likely a printing character. Use the deprecated fromCharCode.
// This is less reliable for international layouts but is the best fallback.
if (!characters)
{
characters = String.fromCharCode(keyCode);
// Manually handle capitalization for this fallback path.
if (modifierFlags & CPShiftKeyMask || _capsLockActive)
characters = characters.toUpperCase();
else
@@ -800,24 +815,21 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
}
}
// This is a simplification; a fully correct implementation would require extensive mapping.
charactersIgnoringModifiers = characters.toLowerCase();
// Pass the determined `isActionKey` flag.
event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags
timestamp:timestamp windowNumber:windowNumber context:nil
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:keyCode];
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:keyCode isActionKey:isActionKey];
break;
case "keyup":
// Clear the key's state for our fallback repeat detection.
_charCodes[keyCode] = nil;
// Handle the toggling of Caps Lock state.
if (keyCode === CPKeyCodes.CAPS_LOCK)
{
_capsLockActive = !_capsLockActive;
// Update modifierFlags to reflect the new state for this event.
if (_capsLockActive)
modifierFlags |= CPAlphaShiftKeyMask;
else
@@ -825,16 +837,15 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
}
// For modifier keys, create a CPFlagsChanged event and stop.
// These are always considered "action keys".
if ([ModifierKeyCodes containsObject:keyCode])
{
event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags
timestamp:timestamp windowNumber:windowNumber context:nil
characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode];
characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode isActionKey:YES];
break;
}
// Determine the character for the keyup event using the same logic as keydown
// for consistency, as this event no longer has access to `keypress` state.
if (aDOMEvent.key)
{
if (aDOMEvent.key.length === 1)
@@ -852,9 +863,10 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive)
characters = charactersIgnoringModifiers;
// Pass the determined `isActionKey` flag for keyup as well.
event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags
timestamp: timestamp windowNumber:windowNumber context:nil
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode];
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode isActionKey:isActionKey];
break;
}
@@ -867,12 +879,11 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
if (event && ![_platformPasteboard windowShouldSuppressKeyEvent])
{
[CPApp sendEvent:event];
[_platformPasteboard windowDidSendKeyEvent:event];
}
var didStop = NO;
// Platform pasteboard can overrule the decision to stop propagation.
if ([_platformPasteboard windowShouldStopPropagation] || (StopDOMEventPropagation && ![_platformPasteboard windowShouldNotStopPropagation]))
{
didStop = YES;