diff --git a/src/index.ts b/src/index.ts index aca221d..dc82778 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ export * from './Dashboard'; export * from './widgets'; -export * from './interactions'; \ No newline at end of file +export * from './interactions'; +export * from './types/blessed-extensions'; \ No newline at end of file diff --git a/src/interactions/InteractionManager.ts b/src/interactions/InteractionManager.ts index 45594c9..03db074 100644 --- a/src/interactions/InteractionManager.ts +++ b/src/interactions/InteractionManager.ts @@ -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(); diff --git a/src/types/blessed-extensions.ts b/src/types/blessed-extensions.ts index 2dda091..245e6b5 100644 --- a/src/types/blessed-extensions.ts +++ b/src/types/blessed-extensions.ts @@ -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 + ) } diff --git a/src/widgets/HealthMonitor.ts b/src/widgets/HealthMonitor.ts index 2147caf..bb52291 100644 --- a/src/widgets/HealthMonitor.ts +++ b/src/widgets/HealthMonitor.ts @@ -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, diff --git a/src/widgets/LogViewer.ts b/src/widgets/LogViewer.ts index 6309e38..c7961e4 100644 --- a/src/widgets/LogViewer.ts +++ b/src/widgets/LogViewer.ts @@ -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, diff --git a/src/widgets/ProgressPanel.ts b/src/widgets/ProgressPanel.ts index 3fff332..39e8699 100644 --- a/src/widgets/ProgressPanel.ts +++ b/src/widgets/ProgressPanel.ts @@ -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, diff --git a/src/widgets/RoadmapPanel.ts b/src/widgets/RoadmapPanel.ts index 80f174e..faf8306 100644 --- a/src/widgets/RoadmapPanel.ts +++ b/src/widgets/RoadmapPanel.ts @@ -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, diff --git a/src/widgets/ServiceList.ts b/src/widgets/ServiceList.ts index 2b56267..cdc0de5 100644 --- a/src/widgets/ServiceList.ts +++ b/src/widgets/ServiceList.ts @@ -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,