Nested Groups Fixes

This commit is contained in:
2025-11-29 19:05:54 +00:00
parent 757386f611
commit 9c7d9c5406
6 changed files with 73 additions and 39 deletions
+2
View File
@@ -26,6 +26,8 @@
style="--btn-color: {getButtonStyle(line.style)}"
on:click={() => onClick(index)}
on:mouseenter={() => onHover(index)}
data-href={line.href || ''}
data-external={isExternal ? 'true' : 'false'}
>
<span class="btn-indicator">{selected ? '▶' : ' '}</span>
{#if line.icon}
+5 -1
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import Icon from '@iconify/svelte';
import { getSegmentStyle, getLinePrefix, getSegmentsUpToChar, parseColorText, getPlainText } from './utils';
import { handleNavigation } from './terminal-keyboard';
import { user } from '$lib/config';
import { themeColors } from '$lib/stores/theme';
import TuiButton from './TuiButton.svelte';
@@ -10,6 +11,7 @@
import TuiInput from './TuiInput.svelte';
import TuiCheckbox from './TuiCheckbox.svelte';
import TuiToggle from './TuiToggle.svelte';
import TuiGroup from './TuiGroup.svelte';
import type { TerminalLine } from './types';
interface Props {
@@ -83,7 +85,7 @@
{/if}
</div>
{:else if child.type === 'button'}
<TuiButton line={child} index={idx} selected={false} onClick={onButtonClick} onHover={onHoverButton} inline={childInline} />
<TuiButton line={child} index={idx} selected={false} onClick={() => handleNavigation(child)} onHover={() => {}} inline={childInline} />
{:else if child.type === 'link'}
<TuiLink line={child} onClick={() => onLinkClick(idx)} />
{:else if child.type === 'tooltip'}
@@ -96,6 +98,8 @@
<TuiCheckbox line={child} inline={childInline} />
{:else if child.type === 'toggle'}
<TuiToggle line={child} inline={childInline} />
{:else if child.type === 'group'}
<TuiGroup line={child} inline={childInline} {onButtonClick} {onHoverButton} {onLinkClick} />
{:else if child.type === 'header'}
<span class="content header-text">
<Icon icon="mdi:pound" width="14" class="header-icon" />
+38 -23
View File
@@ -6,6 +6,7 @@
import type { DisplayedLine } from './types';
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
export interface KeyboardHandlerOptions {
getIsTyping: () => boolean;
@@ -31,14 +32,9 @@ export function scrollToSelected(
): void {
if (!bodyElement || selectedIndex < 0) return;
const buttons = bodyElement.querySelectorAll('.tui-button');
const btnIndices = displayedLines
.map((item, idx) => item.parsed.line.type === 'button' ? idx : -1)
.filter(idx => idx !== -1);
const selectedButton = Array.from(buttons).find((_, i) => {
return btnIndices[i] === selectedIndex;
}) as HTMLElement | undefined;
// Get all buttons in DOM order (including nested ones in groups)
const allButtons = bodyElement.querySelectorAll('.tui-button');
const selectedButton = allButtons[selectedIndex] as HTMLElement | undefined;
if (selectedButton && bodyElement) {
const containerRect = bodyElement.getBoundingClientRect();
@@ -67,7 +63,8 @@ export function handleNavigation(line: { action?: () => void; href?: string; ext
if (isExternal) {
window.open(line.href, '_blank', 'noopener,noreferrer');
} else {
window.location.href = line.href;
// Use SvelteKit's goto for client-side navigation (no page reload)
goto(line.href);
}
}
}
@@ -99,30 +96,48 @@ export function createKeyboardHandler(options: KeyboardHandlerOptions) {
if (!getInteractive() || !getIsComplete()) return;
const buttonIndices = getButtonIndices();
if (buttonIndices.length === 0) return;
const bodyElement = getBodyElement();
if (!bodyElement) return;
// Get all buttons from the DOM (including nested ones in groups)
const allButtons = bodyElement.querySelectorAll('.tui-button');
const buttonCount = allButtons.length;
if (buttonCount === 0) return;
const selectedIndex = getSelectedIndex();
const currentButtonIdx = buttonIndices.indexOf(selectedIndex);
const displayedLines = getDisplayedLines();
const bodyElement = getBodyElement();
if (event.key === 'ArrowDown' || event.key === 'j') {
event.preventDefault();
const nextIdx = (currentButtonIdx + 1) % buttonIndices.length;
const newSelectedIndex = buttonIndices[nextIdx];
setSelectedIndex(newSelectedIndex);
scrollToSelected(bodyElement, newSelectedIndex, displayedLines, scrollMargin);
const nextIdx = selectedIndex < 0 ? 0 : (selectedIndex + 1) % buttonCount;
setSelectedIndex(nextIdx);
scrollToSelected(bodyElement, nextIdx, displayedLines, scrollMargin);
// Update visual selection on buttons
allButtons.forEach((btn, i) => btn.classList.toggle('selected', i === nextIdx));
} else if (event.key === 'ArrowUp' || event.key === 'k') {
event.preventDefault();
const prevIdx = (currentButtonIdx - 1 + buttonIndices.length) % buttonIndices.length;
const newSelectedIndex = buttonIndices[prevIdx];
setSelectedIndex(newSelectedIndex);
scrollToSelected(bodyElement, newSelectedIndex, displayedLines, scrollMargin);
const prevIdx = selectedIndex < 0 ? buttonCount - 1 : (selectedIndex - 1 + buttonCount) % buttonCount;
setSelectedIndex(prevIdx);
scrollToSelected(bodyElement, prevIdx, displayedLines, scrollMargin);
// Update visual selection on buttons
allButtons.forEach((btn, i) => btn.classList.toggle('selected', i === prevIdx));
} else if (event.key === 'Enter') {
event.preventDefault();
const selectedLine = displayedLines[selectedIndex]?.parsed.line;
handleNavigation(selectedLine);
if (selectedIndex >= 0 && selectedIndex < buttonCount) {
const selectedButton = allButtons[selectedIndex] as HTMLElement;
if (selectedButton) {
// Read navigation data from button data attributes
const href = selectedButton.dataset.href;
const isExternal = selectedButton.dataset.external === 'true';
if (href) {
handleNavigation({ href, external: isExternal });
} else {
// Fallback to click for buttons with actions (non-navigational)
selectedButton.click();
}
}
}
}
};
}