Skip to content

Commit 04b016a

Browse files
committed
fix(web/ui): improve clickOutside handling and fix spacing in chat
Refactor clickOutside utility to support multiple excluded elements, enabling better control over click event handling in complex UI parts. Update HunkDiff to wait for table wrapper mount before setting excluded elements for clickOutside, ensuring correct behavior. Fix prop names accordingly in HunkDiffBody. Also replace non-breaking space with normal space in MessageText for consistent spacing.
1 parent b9ea776 commit 04b016a

File tree

4 files changed

+56
-32
lines changed

4 files changed

+56
-32
lines changed

apps/web/src/lib/components/chat/MessageText.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
1313
const userMap = $derived(new Map(mentions.map((user) => [user.id, user])));
1414
const words = $derived(getChatMessageWords(text, userMap));
15+
const SPACE = ' ';
1516
</script>
1617

1718
<span>
@@ -21,6 +22,6 @@
2122
{:else}
2223
<Mention mention={word.mention} />
2324
{/if}
24-
&nbsp;
25+
{SPACE}
2526
{/each}
2627
</span>

packages/ui/src/lib/HunkDiff.svelte

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
type LineSelector,
1616
parseHunk
1717
} from '$lib/utils/diffParsing';
18+
import { isDefined } from '$lib/utils/typeguards';
1819
import type { ContextMenuParams } from '$lib/hunkDiff/HunkDiffRow.svelte';
1920
import type { Snippet } from 'svelte';
2021
interface Props {
@@ -69,8 +70,6 @@
6970
7071
const BORDER_WIDTH = 1;
7172
72-
let tableWidth = $state<number>(0);
73-
let tableHeight = $state<number>(0);
7473
let numberHeaderWidth = $state<number>(0);
7574
7675
const hunk = $derived(parseHunk(hunkStr));
@@ -85,11 +84,11 @@
8584
const showingCheckboxes = $derived(!hideCheckboxes && staged !== undefined);
8685
const hunkHasLocks = $derived(lineLocks && lineLocks.length > 0);
8786
const colspan = $derived(showingCheckboxes || hunkHasLocks ? 3 : 2);
87+
let tableWrapperElem = $state<HTMLElement>();
8888
</script>
8989

9090
<div
91-
bind:clientWidth={tableWidth}
92-
bind:clientHeight={tableHeight}
91+
bind:this={tableWrapperElem}
9392
class="table__wrapper hide-native-scrollbar contrast-{diffContrast}"
9493
style="--tab-size: {tabSize}; --diff-font: {diffFont};"
9594
style:font-variant-ligatures={diffLigatures ? 'common-ligatures' : 'none'}
@@ -134,28 +133,38 @@
134133
</tr>
135134
</thead>
136135

137-
<HunkDiffBody
138-
comment={hunk.comment}
139-
{filePath}
140-
content={hunk.contentSections}
141-
{onLineClick}
142-
clearLineSelection={() => clearLineSelection?.(filePath)}
143-
{wrapText}
144-
{tabSize}
145-
{diffFont}
146-
{inlineUnifiedDiffs}
147-
{selectedLines}
148-
{lineLocks}
149-
{numberHeaderWidth}
150-
onCopySelection={onCopySelection && handleCopySelection}
151-
{onQuoteSelection}
152-
{staged}
153-
{stagedLines}
154-
{hideCheckboxes}
155-
{handleLineContextMenu}
156-
{clickOutsideExcludeElement}
157-
{lockWarning}
158-
/>
136+
{#if tableWrapperElem}
137+
<!-- We need to await the table wrapper to be mounted in order to set the array of elements
138+
to ignore when clicking outside.
139+
This is the case because the clickOutside handler needs to know which elements to ignore
140+
at mount time. Reactive updates to the array will not work as expected. -->
141+
{@const elemetsToIgnoreInClickOutside = [
142+
clickOutsideExcludeElement,
143+
tableWrapperElem
144+
].filter(isDefined)}
145+
<HunkDiffBody
146+
comment={hunk.comment}
147+
{filePath}
148+
content={hunk.contentSections}
149+
{onLineClick}
150+
clearLineSelection={() => clearLineSelection?.(filePath)}
151+
{wrapText}
152+
{tabSize}
153+
{diffFont}
154+
{inlineUnifiedDiffs}
155+
{selectedLines}
156+
{lineLocks}
157+
{numberHeaderWidth}
158+
onCopySelection={onCopySelection && handleCopySelection}
159+
{onQuoteSelection}
160+
{staged}
161+
{stagedLines}
162+
{hideCheckboxes}
163+
{handleLineContextMenu}
164+
clickOutsideExcludeElements={elemetsToIgnoreInClickOutside}
165+
{lockWarning}
166+
/>
167+
{/if}
159168
</table>
160169
</ScrollableContainer>
161170
</div>

packages/ui/src/lib/hunkDiff/HunkDiffBody.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
stagedLines?: LineId[];
3737
hideCheckboxes?: boolean;
3838
handleLineContextMenu?: (params: ContextMenuParams) => void;
39-
clickOutsideExcludeElement?: HTMLElement;
39+
clickOutsideExcludeElements?: HTMLElement[];
4040
comment?: string;
4141
lockWarning?: Snippet<[DependencyLock[]]>;
4242
}
@@ -60,7 +60,7 @@
6060
stagedLines,
6161
hideCheckboxes,
6262
handleLineContextMenu,
63-
clickOutsideExcludeElement,
63+
clickOutsideExcludeElements,
6464
lockWarning
6565
}: Props = $props();
6666
@@ -240,7 +240,7 @@
240240
bind:clientHeight={chunkHeight[chunkIdx]}
241241
use:clickOutside={{
242242
handler: handleClearSelection,
243-
excludeElement: clickOutsideExcludeElement
243+
excludeElement: clickOutsideExcludeElements
244244
}}
245245
use:intersectionObserver={{
246246
callback: (entries) => {

packages/ui/src/lib/utils/clickOutside.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
1-
export type ClickOpts = { excludeElement?: Element; handler: (event: MouseEvent) => void };
1+
export type ClickOpts = {
2+
excludeElement?: Element | Element[];
3+
handler: (event: MouseEvent) => void;
4+
};
5+
6+
function elementShouldBeExcluded(
7+
element: Element | Element[] | undefined,
8+
target: HTMLElement
9+
): boolean {
10+
if (!element) return false;
11+
if (Array.isArray(element)) {
12+
return element.some((el) => el.contains(target));
13+
}
14+
return element.contains(target);
15+
}
216

317
export function clickOutside(node: HTMLElement, params: ClickOpts) {
418
function onClick(event: MouseEvent) {
519
if (
620
node &&
721
!node.contains(event.target as HTMLElement) &&
8-
!params.excludeElement?.contains(event.target as HTMLElement)
22+
!elementShouldBeExcluded(params.excludeElement, event.target as HTMLElement)
923
) {
1024
params.handler(event);
1125
}

0 commit comments

Comments
 (0)