Element Bug Fixes
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { getSegmentsUpToChar } from "./utils";
|
||||
import { user } from "$lib/config";
|
||||
import TuiLine from "./TuiLine.svelte";
|
||||
import type { DisplayedLine } from "./types";
|
||||
import type { DisplayedLine, TerminalLine } from "./types";
|
||||
import "$lib/assets/css/tui-body.css";
|
||||
|
||||
interface Props {
|
||||
@@ -11,7 +11,7 @@
|
||||
isTyping?: boolean;
|
||||
selectedIndex?: number;
|
||||
ref?: HTMLDivElement | undefined;
|
||||
onButtonClick: (idx: number) => void;
|
||||
onButtonClick: (idx: number, line?: TerminalLine) => void;
|
||||
onHoverButton: (idx: number) => void;
|
||||
onLinkClick: (idx: number) => void;
|
||||
terminalSettings: { showCursor: boolean };
|
||||
@@ -92,6 +92,11 @@
|
||||
showImage={item.displayed.showImage}
|
||||
{selectedIndex}
|
||||
inline={true}
|
||||
showCursor={terminalSettings.showCursor &&
|
||||
item.index === currentLineIndex &&
|
||||
!item.displayed.complete &&
|
||||
isTyping &&
|
||||
item.displayed.parsed.line.type !== "image"}
|
||||
{onButtonClick}
|
||||
{onHoverButton}
|
||||
{onLinkClick}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script lang="ts">
|
||||
import Icon from '@iconify/svelte';
|
||||
import { getButtonStyle, parseColorText, getSegmentStyle } from './utils';
|
||||
import { themeColors } from '$lib/stores/theme';
|
||||
import type { ButtonLine } from './types';
|
||||
import '$lib/assets/css/tui-button.css';
|
||||
import Icon from "@iconify/svelte";
|
||||
import { getButtonStyle, parseColorText, getSegmentStyle } from "./utils";
|
||||
import { themeColors } from "$lib/stores/theme";
|
||||
import type { ButtonLine } from "./types";
|
||||
import "$lib/assets/css/tui-button.css";
|
||||
|
||||
interface Props {
|
||||
line: ButtonLine;
|
||||
index: number;
|
||||
selected: boolean;
|
||||
onClick: (idx: number) => void;
|
||||
onClick: (idx: number, line?: ButtonLine) => void;
|
||||
onHover: (idx: number) => void;
|
||||
inline?: boolean;
|
||||
}
|
||||
@@ -20,31 +20,39 @@
|
||||
selected,
|
||||
onClick,
|
||||
onHover,
|
||||
inline = false
|
||||
inline = false,
|
||||
}: Props = $props();
|
||||
|
||||
// Determine if this is an external link
|
||||
const isExternal = $derived(line.external || (line.href && (line.href.startsWith('http://') || line.href.startsWith('https://'))));
|
||||
|
||||
const isExternal = $derived(
|
||||
line.external ||
|
||||
(line.href &&
|
||||
(line.href.startsWith("http://") ||
|
||||
line.href.startsWith("https://"))),
|
||||
);
|
||||
|
||||
// Parse color formatting in content using theme colorMap
|
||||
const segments = $derived(parseColorText(line.content, $themeColors.colorMap));
|
||||
const segments = $derived(
|
||||
parseColorText(line.content, $themeColors.colorMap),
|
||||
);
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="tui-button"
|
||||
class:selected={selected}
|
||||
class:inline={inline}
|
||||
class:selected
|
||||
class:inline
|
||||
class:no-border={line.border === false}
|
||||
style="--btn-color: {getButtonStyle(line.style)}"
|
||||
onclick={() => onClick(index)}
|
||||
onclick={() => onClick(index, line)}
|
||||
onmouseenter={() => onHover(index)}
|
||||
data-href={line.href || ''}
|
||||
data-external={isExternal ? 'true' : 'false'}
|
||||
data-href={line.href || ""}
|
||||
data-external={isExternal ? "true" : "false"}
|
||||
>
|
||||
<span class="btn-indicator">{selected ? '▶' : ' '}</span>
|
||||
<span class="btn-indicator">{selected ? "▶" : " "}</span>
|
||||
{#if line.icon}
|
||||
<Icon icon={line.icon} width="16" />
|
||||
{/if}
|
||||
<span class="btn-text">
|
||||
<span class="btn-text" style:text-align={line.textStyle || "left"}>
|
||||
{#each segments as segment}
|
||||
{#if segment.icon}
|
||||
<Icon icon={segment.icon} width="14" class="inline-icon" />
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<script lang="ts">
|
||||
import Icon from "@iconify/svelte";
|
||||
import { getButtonStyle, parseColorText, getSegmentStyle } from "./utils";
|
||||
import {
|
||||
getButtonStyle,
|
||||
parseColorText,
|
||||
getSegmentStyle,
|
||||
parseDimension,
|
||||
} from "./utils";
|
||||
import type { CardLine } from "./types";
|
||||
import TuiLine from "./TuiLine.svelte";
|
||||
import "$lib/assets/css/tui-card.css";
|
||||
@@ -8,9 +13,18 @@
|
||||
interface Props {
|
||||
line: CardLine;
|
||||
inline?: boolean;
|
||||
onButtonClick?: (idx: number, line?: any) => void;
|
||||
onHoverButton?: (idx: number) => void;
|
||||
onLinkClick?: (idx: number) => void;
|
||||
}
|
||||
|
||||
let { line, inline = false }: Props = $props();
|
||||
let {
|
||||
line,
|
||||
inline = false,
|
||||
onButtonClick = () => {},
|
||||
onHoverButton = () => {},
|
||||
onLinkClick = () => {},
|
||||
}: Props = $props();
|
||||
|
||||
const segments = $derived(parseColorText(line.content));
|
||||
const titleSegments = $derived(
|
||||
@@ -19,13 +33,22 @@
|
||||
const footerSegments = $derived(
|
||||
line.cardFooter ? parseColorText(line.cardFooter) : [],
|
||||
);
|
||||
|
||||
const cardStyle = $derived(
|
||||
[
|
||||
`--card-color: ${getButtonStyle(line.style)}`,
|
||||
line.cardWidth ? `width: ${parseDimension(line.cardWidth)}` : "",
|
||||
line.cardHeight ? `height: ${parseDimension(line.cardHeight)}` : "",
|
||||
line.cardFloat === "start" ? "margin-right: auto" : "",
|
||||
line.cardFloat === "center" ? "margin-inline: auto" : "",
|
||||
line.cardFloat === "end" ? "margin-left: auto" : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("; "),
|
||||
);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="tui-card"
|
||||
class:inline
|
||||
style="--card-color: {getButtonStyle(line.style)}"
|
||||
>
|
||||
<div class="tui-card" class:inline style={cardStyle}>
|
||||
{#if line.cardTitle}
|
||||
<div class="card-header">
|
||||
{#if line.icon}
|
||||
@@ -77,6 +100,9 @@
|
||||
showImage={true}
|
||||
selectedIndex={-1}
|
||||
inline={false}
|
||||
{onButtonClick}
|
||||
{onHoverButton}
|
||||
{onLinkClick}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -153,7 +179,6 @@
|
||||
}
|
||||
|
||||
.card-children {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import { parseColorText, getPlainText } from "./utils";
|
||||
import { themeColors } from "$lib/stores/theme";
|
||||
import TuiLine from "./TuiLine.svelte";
|
||||
import type { GroupLine } from "./types";
|
||||
import type { GroupLine, TerminalLine } from "./types";
|
||||
|
||||
interface Props {
|
||||
line: GroupLine;
|
||||
inline?: boolean;
|
||||
onButtonClick?: (idx: number) => void;
|
||||
onButtonClick?: (idx: number, line?: TerminalLine) => void;
|
||||
onHoverButton?: (idx: number) => void;
|
||||
onLinkClick?: (idx: number) => void;
|
||||
}
|
||||
@@ -43,6 +43,7 @@
|
||||
start: "flex-start",
|
||||
center: "center",
|
||||
end: "flex-end",
|
||||
stretch: "stretch",
|
||||
};
|
||||
styles.push(
|
||||
`align-items: ${alignMap[line.groupAlign] || "flex-start"}`,
|
||||
@@ -59,6 +60,7 @@
|
||||
class:inline
|
||||
class:grid={line.display === "grid"}
|
||||
class:block={line.display === "block"}
|
||||
class:expand={line.groupExpand}
|
||||
style={groupStyle()}
|
||||
id={line.id}
|
||||
>
|
||||
@@ -102,6 +104,10 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tui-group.expand > :global(*) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@keyframes lineSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
selectedIndex?: number;
|
||||
inline?: boolean;
|
||||
showCursor?: boolean;
|
||||
onButtonClick?: (idx: number) => void;
|
||||
onButtonClick?: (idx: number, line?: TerminalLine) => void;
|
||||
onHoverButton?: (idx: number) => void;
|
||||
onLinkClick?: (idx: number) => void;
|
||||
}
|
||||
@@ -110,7 +110,7 @@
|
||||
<TuiTooltip {line} />
|
||||
</div>{/if}
|
||||
{:else if line.type === "card"}
|
||||
<TuiCard {line} {inline} />
|
||||
<TuiCard {line} {inline} {onButtonClick} {onHoverButton} {onLinkClick} />
|
||||
{:else if line.type === "cardgrid"}
|
||||
<TuiCardGrid {line} {inline} />
|
||||
{:else if line.type === "progress"}
|
||||
@@ -214,6 +214,7 @@
|
||||
/>{:else if getSegmentStyle(seg)}<span
|
||||
style={getSegmentStyle(seg)}>{seg.text}</span
|
||||
>{:else}{seg.text}{/if}{/each}
|
||||
{#if showCursor}<span class="cursor"></span>{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<div class="tui-line {line.type}" class:complete id={line.id}>
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
|
||||
let { line, inline = false }: Props = $props();
|
||||
|
||||
let containerWidth = $state(0);
|
||||
const charWidth = 9.6; // Approximate width of a character in monospace font (adjust as needed)
|
||||
|
||||
const progress = $derived(Math.min(100, Math.max(0, line.progress ?? 0)));
|
||||
const label = $derived(line.progressLabel || `${progress}%`);
|
||||
const contentSegments = $derived(
|
||||
@@ -20,12 +23,19 @@
|
||||
const labelSegments = $derived(
|
||||
parseColorText(label, $themeColors.colorMap),
|
||||
);
|
||||
|
||||
// Calculate how many characters fit in the bar
|
||||
const charCapacity = $derived(
|
||||
Math.max(10, Math.floor((containerWidth - 30) / charWidth)),
|
||||
);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="tui-progress"
|
||||
class:inline
|
||||
class:text-style={line.progressStyle === "text"}
|
||||
style="--progress-color: {getButtonStyle(line.style)}"
|
||||
bind:clientWidth={containerWidth}
|
||||
>
|
||||
{#if line.content}
|
||||
<div class="progress-label">
|
||||
@@ -40,17 +50,42 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: {progress}%">
|
||||
<span class="progress-glow"></span>
|
||||
|
||||
{#if line.progressStyle === "text"}
|
||||
<div class="progress-text-bar">
|
||||
<span class="bracket">[</span>
|
||||
<span class="bar-content">
|
||||
{#each Array(charCapacity) as _, i}
|
||||
<span class="char"
|
||||
>{i < Math.floor((progress / 100) * charCapacity)
|
||||
? "#"
|
||||
: "."}</span
|
||||
>
|
||||
{/each}
|
||||
</span>
|
||||
<span class="bracket">]</span>
|
||||
</div>
|
||||
<div class="progress-blocks">
|
||||
{#each Array(20) as _, i}
|
||||
<span class="block" class:filled={i < Math.floor(progress / 5)}
|
||||
></span>
|
||||
{/each}
|
||||
{:else}
|
||||
<div
|
||||
class="progress-bar"
|
||||
class:continuous={line.progressStyle === "continuous"}
|
||||
>
|
||||
<div class="progress-fill" style="width: {progress}%">
|
||||
<span class="progress-glow"></span>
|
||||
</div>
|
||||
{#if line.progressStyle !== "continuous"}
|
||||
<div class="progress-blocks">
|
||||
{#each Array(20) as _, i}
|
||||
<span
|
||||
class="block"
|
||||
class:filled={i < Math.floor(progress / 5)}
|
||||
></span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="progress-value">
|
||||
{#each labelSegments as segment}
|
||||
{#if segment.icon}
|
||||
@@ -198,4 +233,30 @@
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Continuous Style */
|
||||
.progress-bar.continuous .progress-blocks {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Text Style */
|
||||
.progress-text-bar {
|
||||
font-family: monospace;
|
||||
font-size: 1rem;
|
||||
color: var(--terminal-dim);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.progress-text-bar .char {
|
||||
color: var(--terminal-dim);
|
||||
}
|
||||
|
||||
.tui-progress:not(.text-style) .progress-text-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-text-bar .bracket {
|
||||
color: var(--terminal-text);
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -48,13 +48,17 @@ export function createTerminalAPI(options: TerminalAPIOptions): TerminalAPI {
|
||||
function refreshDisplayedLines() {
|
||||
const state = getState();
|
||||
const colorMap = getColorMap();
|
||||
const displayedLines = state.lines.map((line, i) => {
|
||||
// Only update lines that are already displayed to avoid forcing untyped lines to appear
|
||||
const displayedLines = state.displayedLines.map((displayed, i) => {
|
||||
const line = state.lines[i];
|
||||
if (!line) return displayed; // Should not happen if state is consistent
|
||||
|
||||
const parsed = parseLine(line, colorMap);
|
||||
return {
|
||||
parsed,
|
||||
charIndex: parsed.plainText.length,
|
||||
complete: true,
|
||||
showImage: state.displayedLines[i]?.showImage ?? (line.type === 'image')
|
||||
charIndex: displayed.complete ? parsed.plainText.length : displayed.charIndex,
|
||||
complete: displayed.complete,
|
||||
showImage: displayed.showImage ?? (line.type === 'image')
|
||||
};
|
||||
});
|
||||
setState({ displayedLines, lines: [...state.lines] });
|
||||
|
||||
@@ -77,6 +77,8 @@ export interface ButtonLine extends BaseTerminalLine {
|
||||
href?: string;
|
||||
icon?: string;
|
||||
external?: boolean;
|
||||
textStyle?: 'start' | 'center' | 'end';
|
||||
border?: boolean;
|
||||
}
|
||||
|
||||
export interface LinkLine extends BaseTerminalLine {
|
||||
@@ -101,12 +103,16 @@ export interface CardLine extends BaseTerminalLine {
|
||||
image?: string;
|
||||
imageAlt?: string;
|
||||
children?: TerminalLine[];
|
||||
cardWidth?: string | number;
|
||||
cardHeight?: string | number;
|
||||
cardFloat?: 'start' | 'center' | 'end';
|
||||
}
|
||||
|
||||
export interface ProgressLine extends BaseTerminalLine {
|
||||
type: 'progress';
|
||||
progress: number; // 0-100
|
||||
progressLabel?: string;
|
||||
progressStyle?: 'block' | 'continuous' | 'text';
|
||||
}
|
||||
|
||||
export interface AccordionLine extends BaseTerminalLine {
|
||||
@@ -195,8 +201,9 @@ export interface GroupLine extends BaseTerminalLine {
|
||||
type: 'group';
|
||||
children: TerminalLine[];
|
||||
groupDirection?: 'row' | 'column';
|
||||
groupAlign?: 'start' | 'center' | 'end';
|
||||
groupAlign?: 'start' | 'center' | 'end' | 'stretch';
|
||||
groupGap?: string;
|
||||
groupExpand?: boolean;
|
||||
}
|
||||
|
||||
// Discriminated union of all line types
|
||||
|
||||
@@ -169,7 +169,7 @@ export function getSegmentStyle(segment: TextSegment): string {
|
||||
if (segment.bold) styles.push('font-weight: bold');
|
||||
if (segment.dim) styles.push('opacity: 0.6');
|
||||
if (segment.italic) styles.push('font-style: italic');
|
||||
|
||||
|
||||
// Combine text decorations
|
||||
const decorations: string[] = [];
|
||||
if (segment.underline) decorations.push('underline');
|
||||
@@ -178,7 +178,7 @@ export function getSegmentStyle(segment: TextSegment): string {
|
||||
if (decorations.length > 0) {
|
||||
styles.push(`text-decoration: ${decorations.join(' ')}`);
|
||||
}
|
||||
|
||||
|
||||
return styles.join('; ');
|
||||
}
|
||||
|
||||
@@ -211,3 +211,26 @@ export function getButtonStyle(style?: string): string {
|
||||
return 'var(--terminal-text)';
|
||||
}
|
||||
}
|
||||
|
||||
export function parseDimension(value: string | number | undefined): string | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
if (typeof value === 'number') {
|
||||
// If it's a decimal between 0 and 1, treat as percentage
|
||||
if (value > 0 && value <= 1) {
|
||||
return `${value * 100}%`;
|
||||
}
|
||||
return `${value}px`;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
// Handle fractions like "1/2", "1/3"
|
||||
if (value.includes('/')) {
|
||||
const [num, den] = value.split('/').map(Number);
|
||||
if (!isNaN(num) && !isNaN(den) && den !== 0) {
|
||||
return `${(num / den) * 100}%`;
|
||||
}
|
||||
}
|
||||
// Return as is if it already has a unit or is just a number string
|
||||
return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user