fix: reset Custom Style draft on reopen
The modal is kept mounted by its parent (only `open` toggles), so Radix onOpenChange never fires for a parent-driven open and the draft-reset branch was dead code — abandoned edits leaked into the next open. Reset on the `open` prop edge via effect instead. Also pass radix 10 to parseInt for the size inputs. Adds a reopen-after-cancel regression test. ha-relevant: yes
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Fragment, useState, useCallback } from 'react'
|
import { Fragment, useState, useEffect, useCallback } from 'react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import {
|
import {
|
||||||
Globe, Router, Network, Server, Layers, Box, Container, HardDrive,
|
Globe, Router, Network, Server, Layers, Box, Container, HardDrive,
|
||||||
@@ -160,7 +160,7 @@ function NodeEditor({ nodeType, style, onChange, onApplyToExisting }: NodeEditor
|
|||||||
min={0}
|
min={0}
|
||||||
step={10}
|
step={10}
|
||||||
value={style.width}
|
value={style.width}
|
||||||
onChange={(e) => set('width', parseInt(e.target.value) || 0)}
|
onChange={(e) => set('width', parseInt(e.target.value, 10) || 0)}
|
||||||
className="w-20 h-7 text-xs bg-[#0d1117] border border-[#30363d] rounded px-2 text-[#e6edf3]"
|
className="w-20 h-7 text-xs bg-[#0d1117] border border-[#30363d] rounded px-2 text-[#e6edf3]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,7 +171,7 @@ function NodeEditor({ nodeType, style, onChange, onApplyToExisting }: NodeEditor
|
|||||||
min={0}
|
min={0}
|
||||||
step={10}
|
step={10}
|
||||||
value={style.height}
|
value={style.height}
|
||||||
onChange={(e) => set('height', parseInt(e.target.value) || 0)}
|
onChange={(e) => set('height', parseInt(e.target.value, 10) || 0)}
|
||||||
className="w-20 h-7 text-xs bg-[#0d1117] border border-[#30363d] rounded px-2 text-[#e6edf3]"
|
className="w-20 h-7 text-xs bg-[#0d1117] border border-[#30363d] rounded px-2 text-[#e6edf3]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -285,14 +285,22 @@ export function CustomStyleModal({ open, onClose }: CustomStyleModalProps) {
|
|||||||
edges: { ...customStyle.edges },
|
edges: { ...customStyle.edges },
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const handleOpen = (isOpen: boolean) => {
|
// Reset the draft to the saved customStyle whenever the modal is (re)opened.
|
||||||
if (isOpen) {
|
// The parent keeps this component mounted and only toggles `open`, so Radix's
|
||||||
// Reset draft to current saved customStyle on open
|
// onOpenChange never fires for a parent-driven open — we key off the prop edge
|
||||||
|
// instead. Without this, abandoned edits (Cancel) would leak into the next open.
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
setDraft({ nodes: { ...customStyle.nodes }, edges: { ...customStyle.edges } })
|
setDraft({ nodes: { ...customStyle.nodes }, edges: { ...customStyle.edges } })
|
||||||
setSelection(null)
|
setSelection(null)
|
||||||
} else {
|
|
||||||
onClose()
|
|
||||||
}
|
}
|
||||||
|
// Intentional snapshot-on-open: we don't want live customStyle changes to
|
||||||
|
// clobber an in-progress edit, only a fresh open should reset.
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [open])
|
||||||
|
|
||||||
|
const handleOpen = (isOpen: boolean) => {
|
||||||
|
if (!isOpen) onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNodeStyle = (t: NodeType): NodeTypeStyle =>
|
const getNodeStyle = (t: NodeType): NodeTypeStyle =>
|
||||||
|
|||||||
@@ -134,4 +134,23 @@ describe('CustomStyleModal', () => {
|
|||||||
fireEvent.change(widthInputs[0], { target: { value: '250' } })
|
fireEvent.change(widthInputs[0], { target: { value: '250' } })
|
||||||
expect((widthInputs[0] as HTMLInputElement).value).toBe('250')
|
expect((widthInputs[0] as HTMLInputElement).value).toBe('250')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('resets abandoned edits when reopened after cancel (mounted parent)', () => {
|
||||||
|
// Parent keeps the modal mounted and only toggles `open`, so the reset must
|
||||||
|
// happen on the open-prop edge, not via Radix onOpenChange.
|
||||||
|
const { rerender } = render(<CustomStyleModal open onClose={vi.fn()} />)
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Router' }))
|
||||||
|
const widthInput = screen.getAllByRole('spinbutton')[0]
|
||||||
|
fireEvent.change(widthInput, { target: { value: '250' } })
|
||||||
|
expect((widthInput as HTMLInputElement).value).toBe('250')
|
||||||
|
|
||||||
|
// Cancel = parent flips open → false, then later → true again.
|
||||||
|
rerender(<CustomStyleModal open={false} onClose={vi.fn()} />)
|
||||||
|
rerender(<CustomStyleModal open onClose={vi.fn()} />)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Router' }))
|
||||||
|
const reopenedWidth = screen.getAllByRole('spinbutton')[0]
|
||||||
|
// Draft was reset to saved style (default width 0) — edit did not leak.
|
||||||
|
expect((reopenedWidth as HTMLInputElement).value).toBe('0')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user