// ============================================================================ // SVELTUI MOUNT FUNCTION // Sets up terminal, input, layout, and rendering // ============================================================================ import { terminalSize, setTerminalSize } from './core/state/engine.svelte.ts' import { initializeLayout } from './core/layout/layout.svelte.ts' import { initializeRenderer } from './core/render/renderer.svelte.ts' import { keyboard } from './input/keyboard.svelte.ts' import { cursor, type CursorOptions, type CursorVisibility } from './input/cursor.svelte.ts' import * as ANSI from './utils/ansi-codes.ts' import { writeStdout } from './utils/bun-output.ts' export type MountOptions = { fullscreen?: boolean append?: boolean // Append mode: content flows down, terminal scrolls naturally /** Cursor configuration - controls native terminal cursor */ cursor?: CursorVisibility | CursorOptions } // ============================================================================ // MOUNT FUNCTION // ============================================================================ export function mount(rootComponent: any, options: MountOptions = {}) { // Setup terminal dimensions const cols = process.stdout.columns || 80 const rows = process.stdout.rows || 24 setTerminalSize(cols, rows) terminalSize.fullscreen = options.fullscreen || false terminalSize.append = options.append || false // Enter fullscreen if requested (mutually exclusive with append mode) if (options.fullscreen) { writeStdout(ANSI.ENTER_ALT_SCREEN) writeStdout(ANSI.CLEAR_SCREEN) } // Configure cursor based on options if (options.cursor !== undefined) { // Explicit cursor config provided if (typeof options.cursor === 'string') { // Simple visibility: 'visible' or 'hidden' if (options.cursor === 'hidden') { cursor.hide() } else { cursor.show() } } else { // Full cursor options object cursor.configure(options.cursor) } } else { // Default behavior: hide cursor in fullscreen, show in append mode if (!options.append) { cursor.hide() } } writeStdout(ANSI.ENABLE_MOUSE) // Set up keyboard - using the existing keyboard module keyboard.initialize() keyboard.onKey('Tab', () => keyboard.focusNext()) keyboard.onKey('Shift+Tab', () => keyboard.focusPrevious()) keyboard.onKey('Escape', () => keyboard.clearFocus()) // Error handling // Error handling with detailed output const handleError = (error: Error) => { // Exit fullscreen mode first if (options?.fullscreen) { writeStdout(ANSI.EXIT_ALT_SCREEN) } // Reset cursor cursor.reset() writeStdout(ANSI.CLEAR_SCREEN) writeStdout(ANSI.DISABLE_MOUSE) writeStdout(ANSI.RESET) // Clear screen for clean error display console.clear() // Display error message console.error('\x1b[41m\x1b[37m\x1b[1m SVELTUI RUNTIME ERROR \x1b[0m\n') console.error( '\x1b[31m\x1b[1mError:\x1b[0m \x1b[37m' + error.message + '\x1b[0m\n' ) // Show stack trace with formatting if (error.stack) { console.error('\x1b[33m\x1b[1mStack Trace:\x1b[0m') const stackLines = error.stack.split('\n').slice(1) // Skip first line (error message) stackLines.forEach((line) => { if (line.includes('node_modules')) { // Dim lines from node_modules console.error('\x1b[90m' + line + '\x1b[0m') } else if (line.includes('at ')) { // Highlight app code console.error('\x1b[36m' + line + '\x1b[0m') } else { console.error(line) } }) } console.error('\n\x1b[33m💡 Tips:\x1b[0m') console.error(' • Check that all components are imported correctly') console.error(' • Ensure Happy DOM is registered before Svelte imports') console.error(" • Verify you're using Svelte 5 rune syntax") console.error('\n─'.repeat(60) + '\n') process.exit(1) } process.on('uncaughtException', handleError) process.on('unhandledRejection', handleError) // Initialize systems inside effect root const effectCleanup = $effect.root(() => { // Initialize simplified layout system (calculates positions) const layoutCleanup = initializeLayout() // Initialize our new optimized renderer const rendererCleanup = initializeRenderer() rootComponent() // Handle resize const handleResize = () => { const newCols = process.stdout.columns || 80 const newRows = process.stdout.rows || 24 setTerminalSize(newCols, newRows) } process.stdout.on('resize', handleResize) // Return cleanup return () => { process.stdout.off('resize', handleResize) process.off('SIGINT', handleExit) process.off('SIGTERM', handleExit) process.off('uncaughtException', handleError) process.off('unhandledRejection', handleError) rendererCleanup() layoutCleanup() } }) // Cleanup function const cleanup = () => { effectCleanup() keyboard.cleanup() writeStdout(ANSI.DISABLE_MOUSE) if (options.fullscreen) { writeStdout(ANSI.EXIT_ALT_SCREEN) } // Always reset cursor to default on cleanup cursor.reset() writeStdout(ANSI.RESET) if (options.append) { // In append mode, ensure we end on a new line writeStdout('\n') } } // Handle exit const handleExit = () => { cleanup() process.exit(0) } process.on('SIGINT', handleExit) process.on('SIGTERM', handleExit) // Return cleanup for manual unmount return { cleanup, unmount: cleanup, } }