Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/components/MorphemeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,19 @@ export function MorphemeBreakdownPopover({
inputRef.current?.select();
}, []);

/**
* Collapses leading/trailing and repeated internal whitespace to a single space.
*
* @param s - The string to normalize.
* @returns The string with surrounding whitespace trimmed and internal runs collapsed.
*/
const normalize = (s: string) => s.trim().replace(/\s+/g, ' ');

// Whether the draft matches the pre-filled value. Shared by the Done/Enter and outside-click
// commit paths so the two can never disagree about what counts as an edit.
const isUnedited = draft.trim() === initialValue.trim();
// commit paths so the two can never disagree about what counts as an edit. Whitespace is
// normalized because the save path splits on /\s+/, so differing spacing yields identical forms —
// comparing normalized text avoids a no-op persistence round-trip.
const isUnedited = normalize(draft) === normalize(initialValue);

/**
* Commits the current draft and closes the popover. Skips the save when the token already has a
Expand Down
5 changes: 5 additions & 0 deletions src/components/TokenChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ export function TokenChip({
// otherwise paint later segment rows over it. The popover is modal so interactions
// outside the panel are blocked while it is open. The popover component is mounted only
// while open so its draft state re-initializes from the current forms on every open.
//
// `onOpenChange` is intentionally omitted: this consumer owns every dismissal path
// (onEscapeKeyDown, onInteractOutside, explicit button clicks), so Radix's internal close
// requests aren't needed. Don't wire onOpenChange without also removing those, or closes
// would double-fire.
<Popover modal open={popoverOpen}>
<PopoverAnchor asChild>
<div className="tw:relative tw:flex tw:flex-col tw:items-center tw:w-full">
Expand Down