fix: teams selection not sticking in workspace settings (#8309)

Fix portal class mismatch in clickOutside that caused premature dropdown
closing, and simplify TeamSelector/ChannelSelector state sync to use
getter/setter bindings instead of bidirectional $effect chains.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-03-10 18:25:09 +00:00
committed by GitHub
parent cb349cb3d1
commit fefc8c62a0
3 changed files with 40 additions and 38 deletions
@@ -26,7 +26,7 @@
let {
disabled = false,
placeholder = 'Select channel',
selectedChannel = $bindable(undefined),
selectedChannel = $bindable(),
containerClass = 'w-64',
minWidth = '160px',
channels = undefined,
@@ -40,8 +40,6 @@
let loadedChannels = $state<ChannelItem[]>([])
let loadedForTeamId = $state<string | undefined>(undefined)
let selectedChannelId = $state<string | undefined>(selectedChannel?.channel_id)
const searchMode = $derived(!channels && !!teamId)
let displayChannels = $derived.by(() => {
@@ -55,21 +53,17 @@
return baseChannels
})
$effect(() => {
const newChannel = selectedChannelId
? displayChannels.find((c) => c.channel_id === selectedChannelId)
: undefined
if (newChannel?.channel_id !== selectedChannel?.channel_id) {
selectedChannel = newChannel
// Single setter to bridge Select's string value -> selectedChannel object.
function setSelectedChannelById(newId: string | undefined) {
if (newId) {
const channel = displayChannels.find((c) => c.channel_id === newId)
if (channel && channel.channel_id !== selectedChannel?.channel_id) {
selectedChannel = channel
}
} else if (selectedChannel !== undefined) {
selectedChannel = undefined
}
})
$effect(() => {
if (selectedChannel?.channel_id !== selectedChannelId) {
selectedChannelId = selectedChannel?.channel_id
}
})
}
let previousChannelId = $state<string | undefined>(undefined)
@@ -136,7 +130,10 @@
clearable
disabled={disabled || !teamId}
loading={isFetching}
bind:value={selectedChannelId}
bind:value={
() => selectedChannel?.channel_id,
(newId) => setSelectedChannelById(newId)
}
/>
{:else}
<Select
@@ -150,7 +147,10 @@
{placeholder}
clearable
disabled={disabled || displayChannels.length === 0}
bind:value={selectedChannelId}
bind:value={
() => selectedChannel?.channel_id,
(newId) => setSelectedChannelById(newId)
}
/>
{/if}
</div>
+20 -18
View File
@@ -45,8 +45,6 @@
let preSearchNextLink = $state<string | null>(null)
let preSearchTotalCount = $state(0)
let selectedTeamId = $state<string | undefined>(selectedTeam?.team_id)
const searchMode = $derived(!teams)
// Check if there are more teams to load (based on next_link presence)
@@ -62,21 +60,18 @@
return baseTeams
})
$effect(() => {
const newTeam = selectedTeamId
? displayTeams.find((t) => t.team_id === selectedTeamId)
: undefined
if (newTeam?.team_id !== selectedTeam?.team_id) {
selectedTeam = newTeam
// Single getter/setter to bridge Select's string value ↔ selectedTeam object.
// This replaces the previous two bidirectional $effect sync blocks.
function setSelectedTeamById(newId: string | undefined) {
if (newId) {
const team = displayTeams.find((t) => t.team_id === newId)
if (team && team.team_id !== selectedTeam?.team_id) {
selectedTeam = team
}
} else if (selectedTeam !== undefined) {
selectedTeam = undefined
}
})
$effect(() => {
if (selectedTeam?.team_id !== selectedTeamId) {
selectedTeamId = selectedTeam?.team_id
}
})
}
let previousTeamId = $state<string | undefined>(undefined)
@@ -120,6 +115,7 @@
})
function restorePreSearchState() {
debouncedSearch.clearDebounce()
searchRequestId++ // Invalidate any in-flight search
if (preSearchTeams !== null) {
// Restore the accumulated teams from before the search
@@ -262,7 +258,10 @@
disabled={disabled || isFetching}
loading={isFetching}
bind:filterText={searchFilterText}
bind:value={selectedTeamId}
bind:value={
() => selectedTeam?.team_id,
(newId) => setSelectedTeamById(newId)
}
/>
{:else}
<Select
@@ -274,7 +273,10 @@
placeholder="Select a team"
clearable
disabled={disabled || isFetching}
bind:value={selectedTeamId}
bind:value={
() => selectedTeam?.team_id,
(newId) => setSelectedTeamById(newId)
}
/>
{/if}
</div>
+1 -1
View File
@@ -293,7 +293,7 @@ export function validatePassword(password: string): boolean {
return re.test(password)
}
const portalDivs = ['#app-editor-select', '.select-dropdown-portal', '[data-context-menu]']
const portalDivs = ['#app-editor-select', '.dropdown-portal', '[data-context-menu]']
interface ClickOutsideOptions {
capture?: boolean