chore(widgets): 🔧 Implement blessed extension system with InteractionManager integration

This commit is contained in:
Lilith 2026-01-20 01:19:14 -08:00
parent ed8be21b68
commit a645d12dc0
8 changed files with 35 additions and 28 deletions

View file

@ -1,3 +1,4 @@
export * from './Dashboard';
export * from './widgets';
export * from './interactions';
export * from './interactions';
export * from './types/blessed-extensions';

View file

@ -9,7 +9,7 @@
*/
import * as blessed from 'blessed';
import { ScrollableElement, StylableElement, isScrollable, isStylable } from '../types/blessed-extensions';
import { hasScrolling, hasStylableBorder } from '../types/blessed-extensions';
// =============================================================================
// Types
@ -186,7 +186,7 @@ export class InteractionManager {
static scrollIntoView(options: ScrollIntoViewOptions): void {
const { container, lineIndex, padding = 0 } = options;
if (!isScrollable(container)) {
if (!hasScrolling(container)) {
return; // Container doesn't support scrolling
}
@ -211,7 +211,7 @@ export class InteractionManager {
* Calculate visible range for a scrollable container
*/
static getVisibleRange(container: blessed.Widgets.BlessedElement): { start: number; end: number } {
if (!isScrollable(container)) {
if (!hasScrolling(container)) {
return { start: 0, end: 0 };
}
@ -271,14 +271,14 @@ export class InteractionManager {
const { element, defaultLabel, focusedLabel } = panel;
// Update border color if element supports styling
if (isStylable(element) && element.style.border) {
if (hasStylableBorder(element) && element.style.border) {
element.style.border.fg = isFocused ? this.focusedBorderColor : this.defaultBorderColor;
}
// Update label if configured
if (this.showFocusedLabel && (defaultLabel || focusedLabel)) {
const label = isFocused ? (focusedLabel ?? `${defaultLabel} [focused]`) : defaultLabel;
if (label && isStylable(element) && typeof element.setLabel === 'function') {
if (label && hasStylableBorder(element) && typeof element.setLabel === 'function') {
element.setLabel(` ${label} `);
}
}
@ -286,7 +286,7 @@ export class InteractionManager {
private extractLabel(element: blessed.Widgets.BlessedElement): string {
// Try to extract existing label from element
if (isStylable(element) && element.options?.label) {
if (hasStylableBorder(element) && element.options?.label) {
const label = element.options.label;
if (typeof label === 'string') {
return label.trim();

View file

@ -85,45 +85,50 @@ export function isExtendedLog(element: contrib.Widgets.LogElement): element is E
// =============================================================================
/**
* A blessed element that supports scrolling
* Used by InteractionManager for scroll-into-view functionality
* Shape of a scrollable blessed element
* Used for runtime type checking in InteractionManager
*/
export interface ScrollableElement extends blessed.Widgets.BlessedElement {
/** Height of the element */
export interface ScrollableShape {
height: number
/** Current scroll position (lines from top) */
childBase: number
/** Scroll to a specific line position */
scrollTo(position: number): void
}
/**
* A blessed element with style.border access
* Shape of a stylable blessed element
*/
export interface StylableElement extends blessed.Widgets.BlessedElement {
export interface StylableShape {
style: {
border?: { fg: string }
[key: string]: unknown
}
/** Element options including label */
options?: {
label?: string
[key: string]: unknown
}
/** Set the label for this element */
setLabel?(text: string): void
}
/**
* Type guard to check if an element is scrollable
* Check if an element has scrolling capabilities
*/
export function isScrollable(element: blessed.Widgets.BlessedElement): element is ScrollableElement {
return typeof (element as ScrollableElement).scrollTo === 'function'
export function hasScrolling(element: unknown): element is ScrollableShape {
return (
typeof element === 'object' &&
element !== null &&
typeof (element as ScrollableShape).scrollTo === 'function' &&
typeof (element as ScrollableShape).height === 'number'
)
}
/**
* Type guard to check if an element has stylable border
* Check if an element has stylable border
*/
export function isStylable(element: blessed.Widgets.BlessedElement): element is StylableElement {
return (element as StylableElement).style?.border !== undefined
export function hasStylableBorder(element: unknown): element is StylableShape {
return (
typeof element === 'object' &&
element !== null &&
typeof (element as StylableShape).style === 'object' &&
(element as StylableShape).style?.border !== undefined
)
}

View file

@ -50,7 +50,7 @@ export class HealthMonitor {
border: { type: 'line' },
style: {
border: { fg: 'cyan' },
label: { fg: 'white', bold: true },
label: { fg: 'white', bold: true } as unknown as string, // blessed supports object form
focus: { border: { fg: 'green' } },
},
tags: true,

View file

@ -52,13 +52,14 @@ export class LogViewer {
this.maxLogs = options.maxLogs ?? 1000
this.labelBase = options.label ?? 'Logs'
// Note: blessed actually supports object-form label styles, but @types/blessed doesn't reflect this
this.logElement = contrib.log({
parent,
label: ` ${this.labelBase} `,
border: { type: 'line' },
style: {
border: { fg: 'cyan' },
label: { fg: 'white', bold: true },
label: { fg: 'white', bold: true } as unknown as string, // blessed supports object form
focus: { border: { fg: 'green' } },
},
bufferLength: options.bufferLength ?? 1000,

View file

@ -69,7 +69,7 @@ export class ProgressPanel {
border: { type: 'line' },
style: {
border: { fg: 'cyan' },
label: { fg: 'white', bold: true },
label: { fg: 'white', bold: true } as unknown as string, // blessed supports object form
},
tags: true,
...options.boxOptions,

View file

@ -43,7 +43,7 @@ export class RoadmapPanel {
border: { type: 'line' },
style: {
border: { fg: 'cyan' },
label: { fg: 'white', bold: true },
label: { fg: 'white', bold: true } as unknown as string, // blessed supports object form
},
tags: true,
...options.boxOptions,

View file

@ -76,7 +76,7 @@ export class ServiceList {
border: { type: 'line' },
style: {
border: { fg: 'cyan' },
label: { fg: 'white', bold: true },
label: { fg: 'white', bold: true } as unknown as string, // blessed supports object form
focus: { border: { fg: 'green' } },
},
tags: true,