Skip to content

Commit 79828f4

Browse files
committed
fix(emcn): stop Enter double-submit and close registration gap
- stopPropagation on field Enter-submit so a parent onKeyDown can't re-fire the primary (double OAuth connect, etc.) - register primary via useLayoutEffect so it's set before paint (no null-Enter window) - drop now-redundant parent Enter handlers in connect-oauth/create-workspace/rename-document modals
1 parent 9ac301a commit 79828f4

4 files changed

Lines changed: 14 additions & 31 deletions

File tree

apps/sim/app/workspace/[workspaceId]/components/connect-oauth-modal/connect-oauth-modal.tsx

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { type ComponentType, type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react'
3+
import { type ComponentType, useEffect, useMemo, useRef, useState } from 'react'
44
import {
55
Badge,
66
ChipModal,
@@ -309,18 +309,6 @@ export function ConnectOAuthModal(props: ConnectOAuthModalProps) {
309309
? !displayName.trim() || isPending || Boolean(existingCredential)
310310
: isPending
311311

312-
/**
313-
* Submits the connect form on Enter, mirroring the Connect button's enabled
314-
* state and excluding the multi-line description. Restores the keyboard
315-
* affordance the pre-consolidation workflow modal provided.
316-
*/
317-
const handleBodyKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
318-
if (event.key !== 'Enter' || !isConnect || isDisabled) return
319-
if (event.target instanceof HTMLTextAreaElement) return
320-
event.preventDefault()
321-
void handleConnect()
322-
}
323-
324312
const displayNameError =
325313
validationError ??
326314
(existingCredential
@@ -334,7 +322,7 @@ export function ConnectOAuthModal(props: ConnectOAuthModalProps) {
334322
<ChipModalHeader icon={ProviderIcon} onClose={handleClose}>
335323
{title}
336324
</ChipModalHeader>
337-
<ChipModalBody onKeyDown={handleBodyKeyDown}>
325+
<ChipModalBody>
338326
{!isConnect && (
339327
<p className='text-[var(--text-tertiary)] text-caption'>
340328
The "{props.toolName}" tool requires access to your account.

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/rename-document-modal/rename-document-modal.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,10 @@ export function RenameDocumentModal({
7474
}
7575
}
7676

77-
const handleKeyDown = (e: React.KeyboardEvent) => {
78-
if (e.key === 'Enter') {
79-
e.preventDefault()
80-
void handleSubmit()
81-
}
82-
}
83-
8477
return (
8578
<ChipModal open={open} onOpenChange={onOpenChange} srTitle='Rename Document'>
8679
<ChipModalHeader onClose={() => onOpenChange(false)}>Rename Document</ChipModalHeader>
87-
<ChipModalBody onKeyDown={handleKeyDown}>
80+
<ChipModalBody>
8881
<ChipModalField
8982
type='input'
9083
title='Name'

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/create-workspace-modal/create-workspace-modal.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,6 @@ export function CreateWorkspaceModal({
6969
}
7070
}
7171

72-
const handleKeyDown = (e: React.KeyboardEvent) => {
73-
if (e.key === 'Enter') {
74-
e.preventDefault()
75-
void handleSubmit()
76-
}
77-
}
78-
7972
const handleNameChange = (value: string) => {
8073
setName(value)
8174
setError(null)
@@ -86,7 +79,7 @@ export function CreateWorkspaceModal({
8679
return (
8780
<ChipModal open={open} onOpenChange={onOpenChange} srTitle={copy.title}>
8881
<ChipModalHeader onClose={() => onOpenChange(false)}>{copy.title}</ChipModalHeader>
89-
<ChipModalBody onKeyDown={handleKeyDown}>
82+
<ChipModalBody>
9083
<p className='px-2 text-[var(--text-muted)] text-sm'>{copy.description}</p>
9184
<ChipModalField
9285
type='input'

packages/emcn/src/components/chip-modal/chip-modal.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,13 +646,17 @@ function renderChipModalControl(
646646
if (event.key !== 'Enter' || event.nativeEvent.isComposing) return
647647
if (onSubmit) {
648648
event.preventDefault()
649+
event.stopPropagation()
649650
onSubmit()
650651
return
651652
}
652653
if (props.submitOnEnter === false) return
653654
const submit = submitRef?.current
654655
if (submit && !submit.disabled) {
655656
event.preventDefault()
657+
// Stop bubbling so a parent Enter handler (e.g. a modal body that
658+
// also submits) can't fire the same primary action a second time.
659+
event.stopPropagation()
656660
submit.trigger()
657661
}
658662
}}
@@ -1095,13 +1099,18 @@ function ChipModalFooter({
10951099
* the modal on Enter without per-field wiring. Kept in a ref (updated each
10961100
* render) rather than state so fields read the latest handler at Enter-time.
10971101
*
1102+
* A layout effect (not a passive effect) so the ref is populated before the
1103+
* browser paints the modal — otherwise there is a window between first paint
1104+
* and effect commit where an enabled primary is visible but Enter does
1105+
* nothing because `submitRef.current` is still `null`.
1106+
*
10981107
* A `destructive` primary is deliberately NOT published: Enter must never
10991108
* trigger a destructive action from a text field. Destructive flows that DO
11001109
* want Enter (e.g. a guarded "change address") wire an explicit field-level
11011110
* `onSubmit`, which takes precedence over this fallback.
11021111
*/
11031112
const submitRef = React.useContext(ChipModalSubmitContext)
1104-
React.useEffect(() => {
1113+
React.useLayoutEffect(() => {
11051114
if (!submitRef) return
11061115
if (primaryAction.variant === 'destructive') {
11071116
submitRef.current = null

0 commit comments

Comments
 (0)