Skip to content

Commit f06f07e

Browse files
authored
Merge pull request #9478 from gitbutlerapp/fix-clickoutside-handling-spacing
fix(web/ui): improve clickOutside handling and fix spacing in chat
2 parents 0325b2a + 04b016a commit f06f07e

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)