ui: derive group checkbox state in useToolsPanel

Moves the mixed-state derivation out of the submenu and sheet
snippets into one getGroupCheckState accessor; the snippets just
consume checked and indeterminate.

Assisted-by: pi
This commit is contained in:
Aleksander Grygier
2026-08-27 09:54:09 +02:00
committed by GitHub
parent cbace8af70
commit e415366eba
3 changed files with 25 additions and 14 deletions
@@ -250,11 +250,8 @@
</div>
{#snippet sheetGroupRow(group: ToolGroup)}
{@const checked = toolsPanel.isGroupChecked(group)}
{@const checkState = toolsPanel.getGroupCheckState(group)}
{@const enabledCount = toolsPanel.getEnabledToolCount(group)}
<!-- mixed state: parent on but nothing or only part of it enabled -->
{@const indeterminate =
group.tools.length > 0 && (enabledCount === 0 ? checked : enabledCount < group.tools.length)}
{@const favicon = toolsPanel.getFavicon(group)}
{@const groupDisabled = toolsPanel.isGroupDisabled(group)}
@@ -281,9 +278,9 @@
</span>
<Checkbox
checked={checked && !indeterminate}
checked={checkState.checked}
class="{ICON_CLASS_DEFAULT} shrink-0"
{indeterminate}
indeterminate={checkState.indeterminate}
onCheckedChange={() => toolsPanel.toggleGroupByKey(group.key)}
onclick={(e) => e.stopPropagation()}
/>
@@ -77,11 +77,7 @@
{#snippet groupRow(group: ToolGroup)}
{@const isExpanded = toolsPanel.expandedGroups.has(group.key)}
{@const checked = toolsPanel.isGroupChecked(group)}
{@const enabledCount = toolsPanel.getEnabledToolCount(group)}
<!-- mixed state: parent on but nothing or only part of it enabled -->
{@const indeterminate =
group.tools.length > 0 && (enabledCount === 0 ? checked : enabledCount < group.tools.length)}
{@const checkState = toolsPanel.getGroupCheckState(group)}
{@const favicon = toolsPanel.getFavicon(group)}
{@const groupDisabled = toolsPanel.isGroupDisabled(group)}
@@ -124,9 +120,9 @@
{#snippet child({ props })}
<Checkbox
{...props}
checked={checked && !indeterminate}
checked={checkState.checked}
class="mr-2 {ICON_CLASS_DEFAULT} shrink-0"
{indeterminate}
indeterminate={checkState.indeterminate}
onCheckedChange={() => toolsPanel.toggleGroupByKey(group.key)}
/>
{/snippet}
@@ -134,7 +130,7 @@
<Tooltip.Content side="right">
<p>
{checked ? 'Disable' : 'Enable'}
{checkState.checked ? 'Disable' : 'Enable'}
{group.tools.length} tool{group.tools.length !== 1 ? 's' : ''}
</p>
</Tooltip.Content>
@@ -12,6 +12,7 @@ export interface UseToolsPanelReturn {
readonly noToolsInfoMessage: string | null;
isGroupChecked(group: ToolGroup): boolean;
getEnabledToolCount(group: ToolGroup): number;
getGroupCheckState(group: ToolGroup): { checked: boolean; indeterminate: boolean };
getFavicon(group: ToolGroup): string | null;
isGroupDisabled(group: ToolGroup): boolean;
isToolEnabled(entry: ToolEntry): boolean;
@@ -65,6 +66,22 @@ export function useToolsPanel(): UseToolsPanelReturn {
return group.tools.filter((tool) => conversationsStore.preferences.isToolActive(tool)).length;
}
/**
* Group checkbox state: checked is the parent flag (category on, or the
* server key on for MCP groups); indeterminate marks the mixed case where
* the parent is on but nothing or only part of the group is enabled.
* isToolActive folds the parent gates into the count, so a disabled parent
* always yields plain unchecked.
*/
function getGroupCheckState(group: ToolGroup): { checked: boolean; indeterminate: boolean } {
const checked = isGroupChecked(group);
const enabledCount = getEnabledToolCount(group);
const indeterminate =
group.tools.length > 0 && (enabledCount === 0 ? checked : enabledCount < group.tools.length);
return { checked, indeterminate };
}
function getFavicon(group: ToolGroup): string | null {
if (group.source !== ToolSource.MCP || !group.serverId) return null;
@@ -123,6 +140,7 @@ export function useToolsPanel(): UseToolsPanelReturn {
expandedGroups,
getEnabledToolCount,
getFavicon,
getGroupCheckState,
handleOpen,
isGroupChecked,
isGroupDisabled,