Files
phranck 5ad97132b8 Feat: Add @Observable support with Observation framework
- Replace custom Observable protocol and @Published with Apple's @Observable macro
- Add withObservationTracking in renderToBuffer for automatic per-property dependency tracking
- Add type-based @Environment(Type.self) and .environment(object) for observable objects
- Add ObjectEnvironmentModifier for injecting observable objects into the environment
- Add needsCacheClear flag to AppState for thread-safe cache invalidation
- Add cross-platform test script (scripts/test-linux.sh) for Docker-based Linux verification
- Add DemoAppHeader with system info display (OS, version, architecture)
- Consolidate Example App: extract ImageDemoHelpers, KeyboardHelpSection, ValueDisplayRow
- Add pre-push verification rule to CLAUDE.md
- Verified on both macOS and Linux (swift:6.0 container), 1155 tests passing
2026-02-15 23:49:34 +01:00

145 lines
5.0 KiB
Swift

// TUIKit - Terminal UI Kit for Swift
// TextFieldPage.swift
//
// Created by LAYERED.work
// License: MIT
import TUIkit
/// TextField demo page.
///
/// Shows interactive text field features including:
/// - Basic text input with cursor
/// - Cursor styles (block, bar, underscore)
/// - Cursor animations (none, blink, pulse)
/// - Cursor speeds (slow, regular, fast)
/// - Cursor navigation (left/right/home/end)
/// - Text editing (insert, backspace, delete)
/// - onSubmit action
/// - Disabled state
struct TextFieldPage: View {
@State var demoText: String = ""
@State var searchQuery: String = ""
@State var disabledText: String = "Cannot edit"
@State var submittedValue: String = ""
@State var cursorShapeIndex: Int = 0
@State var cursorAnimationIndex: Int = 0
@State var cursorSpeedIndex: Int = 1 // Start at regular
private let shapes: [TextCursorStyle.Shape] = [.block, .bar, .underscore]
private let animations: [TextCursorStyle.Animation] = [.none, .blink, .pulse]
private let speeds: [TextCursorStyle.Speed] = [.slow, .regular, .fast]
private var currentShape: TextCursorStyle.Shape {
shapes[cursorShapeIndex]
}
private var currentAnimation: TextCursorStyle.Animation {
animations[cursorAnimationIndex]
}
private var currentSpeed: TextCursorStyle.Speed {
speeds[cursorSpeedIndex]
}
private var shapeLabel: String {
switch currentShape {
case .block: "█ Block"
case .bar: "│ Bar"
case .underscore: "▁ Underscore"
}
}
private var animationLabel: String {
switch currentAnimation {
case .none: "Static"
case .blink: "Blink"
case .pulse: "Pulse"
}
}
private var speedLabel: String {
switch currentSpeed {
case .slow: "Slow"
case .regular: "Regular"
case .fast: "Fast"
}
}
var body: some View {
VStack(alignment: .leading, spacing: 1) {
DemoSection("Cursor Demo") {
VStack(alignment: .leading, spacing: 1) {
HStack(spacing: 1) {
Text("Input:").foregroundStyle(.palette.foregroundSecondary)
TextField("Input", text: $demoText, prompt: Text("Type here..."))
}
HStack(spacing: 1) {
Text("Search:").foregroundStyle(.palette.foregroundSecondary)
TextField("Search", text: $searchQuery, prompt: Text("Enter search term..."))
.onSubmit { submittedValue = searchQuery }
}
if !submittedValue.isEmpty {
HStack(spacing: 1) {
Text("Submitted:").foregroundStyle(.palette.foregroundSecondary)
Text(submittedValue).foregroundStyle(.palette.success)
}
}
Text("Cursor style set on container, inherited by all fields").dim()
}
}
DemoSection("Disabled TextField") {
VStack(alignment: .leading, spacing: 1) {
HStack(spacing: 1) {
Text("Disabled:").foregroundStyle(.palette.foregroundSecondary)
TextField("Disabled", text: $disabledText, prompt: Text("Cannot edit"))
.disabled()
}
}
}
HStack(alignment: .top, spacing: 3) {
KeyboardHelpSection(shortcuts: [
"[←] [→] Move cursor left/right",
"[Home] [End] Jump to start/end",
"[Backspace] Delete before cursor",
"[Delete] Delete at cursor",
"[Enter] Submit (triggers onSubmit)",
"[Tab] Move to next field",
])
KeyboardHelpSection("Cursor Settings", shortcuts: [
"[F1] Shape: Block, Bar, Underscore",
"[F2] Animation: Static, Blink, Pulse",
"[F3] Speed: Slow, Regular, Fast",
])
}
Spacer()
}
.padding(.horizontal, 1)
.textCursor(currentShape, animation: currentAnimation, speed: currentSpeed)
.statusBarItems(cursorStatusBarItems)
.appHeader {
DemoAppHeader("TextField Demo")
}
}
private var cursorStatusBarItems: [any StatusBarItemProtocol] {
[
StatusBarItem(shortcut: Shortcut.f1, label: shapeLabel) {
cursorShapeIndex = (cursorShapeIndex + 1) % shapes.count
},
StatusBarItem(shortcut: Shortcut.f2, label: animationLabel) {
cursorAnimationIndex = (cursorAnimationIndex + 1) % animations.count
},
StatusBarItem(shortcut: Shortcut.f3, label: speedLabel) {
cursorSpeedIndex = (cursorSpeedIndex + 1) % speeds.count
},
]
}
}