Commit Graph
263 Commits
Author SHA1 Message Date
phranck 0dedddca59 Refactor: Remove maxVisibleRows from List and Table (not in SwiftUI API)
List and Table now always fill available height like SwiftUI.
The TUI-specific maxVisibleRows parameter was removed for API parity.
2026-02-10 12:46:09 +01:00
phranck e2acc069fc Fix: List now fills available height like SwiftUI (greedy layout) 2026-02-10 12:41:26 +01:00
phranck f193fd45c8 Fix: Remove maxVisibleRows from SplitView demo Lists for full height 2026-02-10 12:39:44 +01:00
phranck 4bca440acf Fix: NavigationSplitView uses space separator and fixed column widths
TUI-specific changes:
- Separator is now a space instead of vertical line to avoid double borders
- Sidebar (20 chars) and content (30 chars) have fixed widths
- Only the rightmost column adapts to terminal width changes
- Column content is padded to fill the full column width
2026-02-10 12:35:54 +01:00
phranck c7ef9ac004 Fix: NavigationSplitView focus indicator and improve demo with List components 2026-02-10 12:05:49 +01:00
phranck b3a8cb7e5a Feat: Add NavigationSplitView demo to example app 2026-02-10 12:00:29 +01:00
phranck 9b7454c903 Fix: Remove force casts in StepperHandler using type constraints and .zero 2026-02-10 04:10:21 +01:00
phranck f8881f2bc1 Feat: Add clipboard and undo support to TextField
- Fix escape sequence parsing for fast key repeat (read byte-by-byte)
- Ctrl+A: Select all text
- Ctrl+C: Copy selection to system clipboard (pbcopy/xclip)
- Ctrl+X: Cut selection to clipboard
- Ctrl+V: Paste from clipboard (pbpaste/xsel)
- Ctrl+Z: Undo last change (up to 50 states)
- 12 new tests (998 total)
2026-02-10 04:03:16 +01:00
phranck 19586e8ae7 Feat: Add NavigationSplitView with two/three-column layouts
- NavigationSplitView with sidebar/content/detail columns
- NavigationSplitViewVisibility for programmatic column control
- NavigationSplitViewColumn for column identification
- NavigationSplitViewStyle protocol with automatic/balanced/prominentDetail
- Focus section integration: Tab cycles columns, Up/Down within
- Column width modifier: .navigationSplitViewColumnWidth(_:)
- Style modifier: .navigationSplitViewStyle(_:)
- 39 tests across 8 suites (986 total tests)
2026-02-10 02:59:51 +01:00
phranck c1bda23c92 Feat: Add text selection support to TextField and SecureField
- Parse CSI modifier codes (Shift/Alt/Ctrl) in escape sequences
- Add selection state to TextFieldHandler (anchor, range, helpers)
- Implement Shift+Arrow selection keyboard handling
- Render selection with accent background highlight
- Support delete/replace selected text with Backspace/Delete/typing
- Add 41 new tests (14 KeyEvent + 27 TextFieldHandler)
2026-02-09 22:57:22 +01:00
phranck 91aadd40a1 Feat: Add SecureField component for password input
- SecureField with masked display using ● (U+25CF) bullets
- Reuses TextFieldHandler for key input handling
- SwiftUI API parity: init(_:text:) and init(_:text:prompt:)
- Supports .onSubmit() and .disabled() modifiers
- Focus indicator with pulsing vertical bars
- 15 tests for masking behavior and rendering
- Example app page with password validation demo
2026-02-09 22:20:43 +01:00
phranck bc9841cdc9 Feat: Add Slider and Stepper components with TrackStyle refactor
- Slider: Interactive track control with keyboard navigation (arrow keys, +/-, Home/End)
- Stepper: Increment/decrement control with value or custom callbacks
- TrackStyle: Renamed from ProgressBarStyle, shared between ProgressView and Slider
- TrackRenderer: Extracted utility for track rendering
- 59 new tests (892 total), Example app demo pages for both components
2026-02-09 21:52:37 +01:00
phranck 339e5123a1 Refactor: Improve TextField rendering and input responsiveness
- Remove textInputAutocapitalization (not relevant for TUI)
- TextField now has fixed width with horizontal scrolling
- New visual style: background color + focus indicator bars
- Increase frame rate to 30 FPS and process up to 5 events/frame
- Fix Space key handling in ActionHandler (.space instead of .character)
2026-02-09 21:03:21 +01:00
phranck d607fdc831 Feat: Add TextField<Label> generic, ViewBuilder init, and textInputAutocapitalization 2026-02-09 19:15:56 +01:00
phranck b877cb58ae Feat: Add TextField demo page to Example app 2026-02-09 19:05:30 +01:00
phranck c4ca87f70e Feat: Add TextField component with text input, cursor navigation, and onSubmit 2026-02-09 19:01:28 +01:00
phranck f288eb71a4 Chore: Remove unused ansiRegex (dead code after manual parsing optimization) 2026-02-09 18:33:30 +01:00
phranck 2f8b351bfb Perf: Optimize ANSI string operations
Replace regex-based strippedLength and stripped with manual parsing.

Results:
- strippedLength: 2-5x faster
- stripped: 5-12x faster
- padToVisibleWidth: 1.8-2.2x faster
- Menu render: 34% faster
- RadioButtonGroup render: 22% faster
2026-02-09 18:33:26 +01:00
phranck bfda38ff0d Perf: Optimize FrameBuffer and Stack rendering
- Add FrameBuffer init with pre-computed width
- Optimize appendVertically/appendHorizontally to avoid redundant width computation
- Single-pass computation in VStack/HStack (was 4+ array traversals)
- Add performance benchmark tests (21 tests)

Results:
- VStack (10 children): 3x faster
- Stack nesting depth 10: 2.3x faster
- Nesting overhead: ~0.002ms per level
2026-02-09 18:19:13 +01:00
phranck 26e667f2dc Feat: Add LazyVStack and LazyHStack with lazy rendering 2026-02-09 17:57:27 +01:00
phranck 7e7ad673ca Refactor: Convert VStack, HStack, ZStack to body: some View pattern 2026-02-09 17:55:22 +01:00
phranck 65418a4d5e Feat: Complete View Architecture Refactor - Phase 6 verification
- Add 13 modifier propagation tests
- All 757 tests pass
- SwiftLint clean (no serious violations)
- Move plan to done/
2026-02-09 17:49:24 +01:00
phranck 4e59a36ba4 Refactor: Convert RadioButtonGroup to body: some View pattern 2026-02-09 12:32:16 +01:00
phranck d9010bab39 Refactor: Convert Button, Toggle, Menu to body: some View pattern 2026-02-09 12:32:16 +01:00
phranck 6a4f6880bc Feat: Add preview button to CodePreview with terminal screenshot overlay
- VStack centers children relative to available width when spacers present
- CodePreview: Add Preview button that shows terminal screenshot
- Smooth fade-in animation with blur backdrop
- ESC or click outside to close
- Caption overlay on image
2026-02-09 02:57:48 +01:00
phranck c0c42e8349 Fix: frame(width:) now properly constrains container views like List
- Add hasExplicitWidth flag to RenderContext
- FlexibleFrameView sets flag when width constraints are specified
- ContainerView expands to fill available width when flag is set
- List calculates row width based on frame constraints
- InsetGroupedListStyle: disable alternating row colors by default
- InsetGroupedListStyle: move row padding into renderRow() so backgrounds extend to borders
2026-02-09 02:26:45 +01:00
phranck 2a67398589 Fix: ContainerView respects frame width constraints 2026-02-09 01:28:28 +01:00
phranck cef2483dbf Feat: Add statusBarSystemItems modifier for theme/appearance toggles 2026-02-09 01:12:06 +01:00
phranck 90017af892 Fix: Remove stderr usage for Linux concurrency safety
The fputs(stderr) call was not concurrency-safe on Linux.
Removed the warning entirely since stub modifiers should be silent.
2026-02-08 23:49:45 +01:00
phranck 12e2645969 Feat: Add selectionDisabled and listRowSeparator modifiers
Phase 4 & 6 of List SwiftUI API Parity:
- .selectionDisabled() prevents row selection in List
- .listRowSeparator() stub with warning (not supported in TUI)
- SelectionDisabledKey environment key
- Visibility and VerticalEdge.Set types for API compatibility
- 11 new tests (738 total)
2026-02-08 23:43:59 +01:00
phranck 4937de4748 Fix: Rename count to intValue to avoid SwiftLint false positive
SwiftLint interpreted `count == 0` as empty_count violation,
but count was an Int variable, not a collection property.
2026-02-08 23:04:32 +01:00
phranck 506c7621e9 Feat: Integrate Section into List rendering (Phase 2c3)
- List now uses SelectableListRow<ID> instead of ListRow<ID> internally
- Section headers/footers are non-selectable (.header/.footer types)
- extractRows() detects SectionRowExtractor and flattens to typed rows
- selectableIndices populated for focus navigation skip logic
- renderRow() skips focus/selection background for headers/footers
- Alternating colors restart per section (sectionContentIndex)
- Added Sendable constraint to List<SelectionValue> for Swift 6
- Section conforms to ListRowExtractor (delegates to content ForEach)
- 10 new integration tests (732 total)
2026-02-08 23:00:59 +01:00
phranck 91ca964d93 Feat: Add Section Integration Foundation (Phase 2c1-2c2)
Foundation for integrating Section headers/footers with List row rendering:

- Create SelectableListRow.swift with ListRowType enum (header/content/footer)
  and SelectableListRow struct for type-safe row classification
- Add FrameBuffer Sendable + Equatable conformance (required for SelectableListRow)
- Enhance ItemListHandler with selectableIndices property to track which rows
  can be selected/focused (for skipping non-selectable headers/footers)
- Implement focus skip-logic in moveFocus() to jump over non-selectable rows
- Update Home/End keys to navigate to first/last selectable row
- Add comprehensive documentation to all new types

This foundation enables Phase 2c3 (List flattening and per-section styling).

All tests passing (722). Build + swiftlint clean.
2026-02-08 23:00:59 +01:00
phranck f3b1b52254 Feat: Implement ListStyle system (List SwiftUI API Parity Phase 2b)
- Add ListStyle protocol with showsBorder, rowPadding, groupingStyle, alternatingRowColors
- Implement PlainListStyle (no borders, minimal appearance)
- Implement InsetGroupedListStyle (bordered, inset padding, alternating rows)
- Add ListStyleKey environment key with InsetGroupedListStyle default
- Create .listStyle() modifier extension for semantic API
- Integrate into List._ListCore.renderToBuffer() and renderRow()
- Apply alternating row colors (even/odd rows with accent opacity)
- Respect style.showsBorder in ContainerConfig for border rendering
- Add 20 comprehensive ListStyle tests (protocol, styles, environment, instantiation)

Total tests: 722 (20 new ListStyle tests all passing)
Previous: 702 (Badge 2a tests)

Behavior matches SwiftUI:
- PlainListStyle: minimal, no visual containers
- InsetGroupedListStyle: bordered, padded, alternating row backgrounds
- Even rows: accent.opacity(0.15), Odd rows: no background (unless focused/selected)
- Focus/Selection state overrides alternating colors (priority: focus+selected > focused > selected > alternating)
2026-02-08 23:00:59 +01:00
phranck e118871503 Feat: Implement Badge modifier (List SwiftUI API Parity Phase 2a)
- Add BadgeValue enum (Int, String variants) with displayText and isHidden logic
- Implement BadgeModifier view wrapper with environment-based propagation
- Add BadgeKey to Environment.swift for badge value storage
- Create three .badge() overload extensions: badge(Int), badge(String), badge<S>(S?)
- Integrate badge rendering in List.renderRow(): right-aligned, dimmed foreground
- Add 20 comprehensive test cases for BadgeValue, environment, and edge cases
- Create implementation plan for Phase 2 (Badge + ListStyle + alternating rows)

Total tests: 702 (20 new Badge tests all passing)
2026-02-08 23:00:59 +01:00
phranck f4d70d15cc Feat: Add Section view and List SwiftUI API Parity plan
- Implement SwiftUI-conformant Section with header/content/footer
- Add SectionRowExtractor for integration with List
- SectionTests with 14 test cases covering layout and behavior
- List SwiftUI API Parity plan: Section (phase 1), Badge/ListStyle (phase 2)
- Add ROADMAP documentation
- Add .vscode editor configuration
2026-02-08 23:00:59 +01:00
phranck ac5e63eb84 Feat: Add ButtonRole, horizontal Alert buttons, ESC dismiss, arrow key navigation
- Add ButtonRole (.cancel, .destructive) with SwiftUI-conformant API
- Alert renders buttons horizontally, sorted by role (cancel left)
- Alert max width capped at 60 characters for readability
- ESC key dismisses alerts via AlertPresentationModifier handler
- FocusManager: Left/Right arrows navigate within sections (like Up/Down)
- Move completed plans to done/ directory
- Update example pages with minor adjustments
2026-02-08 23:00:59 +01:00
phranck 989b90effa Refactor: Rename .foregroundColor() to .foregroundStyle() for SwiftUI API parity 2026-02-08 16:36:53 +01:00
phranck f2df17de30 Lots of changes 2026-02-08 14:34:42 +01:00
phranck 7b62fba8ac Docs: Add List and Table documentation
- Create ListAndTable.md DocC article with usage examples
- Document keyboard navigation, selection modes, visual states
- Add padding modifier documentation (1 unit = 1 line/1 char)
- Update plan checklist to reflect implementation status
- Add List, Table, TableColumn to TUIkit.md topics
2026-02-08 00:42:26 +01:00
phranck 80553547c1 Feat: List/Table focus bar, F-keys support, StatusBar defaults
List & Table:
- Replace focus indicator with full-width background bar (focusBackground)
- Change selection indicator from symbol to subtle background color
- Add Footer generic parameter and ViewBuilder initializers to List
- Environment values now propagate to child views

Keyboard:
- Add F1-F12 function key support (VT and SS3 escape sequences)
- Add Key.space case (previously parsed as .character(" "))
- Add Shortcut.f1 through Shortcut.f12

StatusBar:
- Change default style from .compact to .bordered
- Disable showAppearanceItem and showThemeItem by default
- Split StatusBarTests into 4 focused test files

Palette:
- Add focusBackground color for focus indication
2026-02-08 00:35:12 +01:00
phranck e3438f2c68 Fix: Prevent content jumping on first frame with app header
Add measurement pass on first frame to determine actual header height
before outputting anything. This prevents visible content shift when
the estimated header height differs from the actual height.

- Add isFirstFrame flag to RenderLoop
- Perform silent measurement render on first frame
- Use actual header height for correct centering from the start
2026-02-08 00:26:07 +01:00
phranck 44fa7b3c74 Refactor: Convert simple controls to body: some View
Phase 2 of View Architecture Refactor:

- ProgressView: Real body returning _ProgressViewCore
  - Labels inherit environment (foregroundColor propagates)

- Spinner: Real body returning _SpinnerCore
  - Respects environment.foregroundColor if no explicit color

- ButtonRow: Real body returning _ButtonRowCore
  - Buttons inside inherit environment

All controls now follow the pattern:
- Public struct with body: some View
- Internal _Core struct with Renderable for rendering

656 tests pass.
2026-02-07 19:58:21 +01:00
phranck 032e0bcfa9 Feat: Add foregroundColor environment propagation
Phase 1 of View Architecture Refactor:

- Add ForegroundColorKey environment key
- Add .foregroundColor() modifier on View (sets environment)
- Text reads environment.foregroundColor if no explicit color set
- Environment values propagate through body: some View

This enables:
  VStack { Text("Hello") }.foregroundColor(.red)
  // Text is now red!

7 new tests verify propagation through view hierarchy.
2026-02-07 19:44:47 +01:00
phranck 816f1958e1 Refactor: List and Table use ContainerView with proper body
- List: Real View with body returning ContainerView
  - Optional title displayed in container border
  - Horizontal padding (1 char) for items
  - Items rendered inside bordered container

- Table: Real View with body returning ContainerView
  - Column headers inside container with separator
  - Data rows with proper padding
  - Scroll indicators inside container

- Updated example pages to use new title parameters
- Fixed tests for new container structure

This follows the SwiftUI pattern: public View with body,
internal _Core struct handles Renderable logic.
2026-02-07 19:29:57 +01:00
phranck 84fdb7ace2 Feat: Add Table component with column support
- TableColumn: Column definition with title, alignment, width modes
  - Key path and closure-based value extraction
  - Width modes: .fixed(Int), .flexible, .ratio(Double)
  - Alignment: .leading, .center, .trailing
  - Chainable modifiers: .alignment(), .width()

- Table: SwiftUI-compatible table with column headers
  - Single selection: Table(data, selection: Binding<ID?>)
  - Multi-selection: Table(data, selection: Binding<Set<ID>>)
  - Reuses ItemListHandler for navigation/selection
  - ANSI-aware column alignment
  - Header row with column titles
  - Space-only separators (no vertical lines)
  - Scroll indicators when content overflows
  - Empty state placeholder
  - .disabled() modifier support

- TableColumnBuilder: Result builder for column DSL
- TablePage: Example with file browser style demo
- 21 new tests for Table and TableColumn

Completes Phase 2 of List & Table plan.
2026-02-07 19:08:19 +01:00
phranck 9b8548bdbc Feat: Add List component with ItemListHandler
- ItemListHandler: Shared navigation/selection logic for List and Table
  - Keyboard navigation (Up/Down/Home/End/PageUp/PageDown)
  - Single and multi-selection modes via Binding
  - Scroll offset management with auto-scroll
  - Focus lifecycle hooks (onFocusLost/onFocusReceived)

- List: SwiftUI-compatible scrollable list component
  - Single selection: List(selection: Binding<ID?>)
  - Multi-selection: List(selection: Binding<Set<ID>>)
  - ForEach content via ListRowExtractor protocol
  - Multi-line row support
  - Visual states (focused/selected with pulsing accent)
  - Scroll indicators when content overflows
  - Empty state placeholder
  - .disabled() modifier support

- ListPage: Example with single and multi-selection demos
- 32 new tests (24 handler + 8 list rendering)
2026-02-07 18:57:51 +01:00
phranck b7781d58ae Refactor: Remove implicitly unwrapped optionals from AppRunner (Phase 6)
- Move InputHandler, RenderLoop, PulseTimer creation into run()
- These are now local variables instead of IUO stored properties
- Removes swiftlint:disable comments and potential crash risk
- 590 tests passing
2026-02-07 16:28:20 +01:00
phranck d0a66c28b3 Refactor: Consolidate ButtonHandler and ToggleHandler into ActionHandler (Phase 5)
- Create reusable ActionHandler for simple action-triggering views
- Button and Toggle now use ActionHandler instead of dedicated handlers
- RadioButtonGroupHandler unchanged (complex navigation logic)
- Update tests: ActionHandlerTests with custom trigger key test
- 590 tests passing
2026-02-07 16:28:20 +01:00
phranck 21b3ebc2b8 Docs: Improve ForEach.body documentation (Phase 4)
- Add Important callout warning about runtime crash
- @available(*, unavailable) not possible on protocol requirements
- Existing fatalError solution is already the best approach
2026-02-07 16:28:20 +01:00