diff --git a/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField ComponentView.swift b/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField ComponentView.swift index 4cde55fa..d674d854 100644 --- a/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField ComponentView.swift +++ b/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField ComponentView.swift @@ -30,6 +30,8 @@ extension TimecodeField { @Environment(\.timecodeHighlightStyle) private var timecodeHighlightStyle: Color? @Environment(\.timecodeSeparatorStyle) private var timecodeSeparatorStyle: Color? @Environment(\.timecodeValidationStyle) private var timecodeValidationStyle: Color? + @Environment(\.timecodeFieldReturnAction) private var timecodeFieldReturnAction: TimecodeField.FieldAction? + @Environment(\.timecodeFieldEscapeAction) private var timecodeFieldEscapeAction: TimecodeField.FieldAction? // MARK: - Internal State @@ -240,17 +242,33 @@ extension TimecodeField { return .handled case .escape: - endEditing() - return .handled + perform(fieldAction: timecodeFieldEscapeAction) + // pass through to any receivers that accept cancel action + return .ignored case .return: - endEditing() - return .ignored // pass through to any buttons that may have default action + perform(fieldAction: timecodeFieldReturnAction) + // pass through to any receivers that accept default action + return .ignored default: return .ignored } } + + private func perform(fieldAction: TimecodeField.FieldAction?) { + // a `nil` action does nothing. + guard let fieldAction else { return } + + switch fieldAction { + case .endEditing: + endEditing() + case let .resetComponentFocus(component): + let component = component ?? + Timecode.Component.first(excluding: invisibleComponents) + componentEditing = component + } + } } } diff --git a/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField Environment.swift b/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField Environment.swift index ef729dbb..6caff655 100644 --- a/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField Environment.swift +++ b/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField Environment.swift @@ -9,9 +9,55 @@ import SwiftUI import TimecodeKit +// MARK: - TimecodeFieldReturnAction + +/// Sets the `Return` key action for ``TimecodeField`` views. +@_documentation(visibility: internal) +@available(macOS 14, iOS 17, *) +@available(watchOS, unavailable) +@available(tvOS, unavailable) +struct TimecodeFieldReturnActionKey: EnvironmentKey { + public static var defaultValue: TimecodeField.FieldAction? = nil +} + +@_documentation(visibility: internal) +@available(macOS 14, iOS 17, *) +@available(watchOS, unavailable) +@available(tvOS, unavailable) +extension EnvironmentValues { + /// Sets the `Return` key action for ``TimecodeField`` views. + public var timecodeFieldReturnAction: TimecodeField.FieldAction? { + get { self[TimecodeFieldReturnActionKey.self] } + set { self[TimecodeFieldReturnActionKey.self] = newValue } + } +} + +// MARK: - TimecodeFieldEscapeAction + +/// Sets the `Escape` key action for ``TimecodeField`` views. +@_documentation(visibility: internal) +@available(macOS 14, iOS 17, *) +@available(watchOS, unavailable) +@available(tvOS, unavailable) +struct TimecodeFieldEscapeActionKey: EnvironmentKey { + public static var defaultValue: TimecodeField.FieldAction? = nil +} + +@_documentation(visibility: internal) +@available(macOS 14, iOS 17, *) +@available(watchOS, unavailable) +@available(tvOS, unavailable) +extension EnvironmentValues { + /// Sets the `Escape` key action for ``TimecodeField`` views. + public var timecodeFieldEscapeAction: TimecodeField.FieldAction? { + get { self[TimecodeFieldEscapeActionKey.self] } + set { self[TimecodeFieldEscapeActionKey.self] = newValue } + } +} + // MARK: - TimecodeFormat -/// Sets the timecode string format for ``TimecodeField`` and ``Text(timecode:)`` views. +/// Sets the timecode string format for ``TimecodeField`` and ``TimecodeText`` views. @_documentation(visibility: internal) @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) struct TimecodeFormatKey: EnvironmentKey { diff --git a/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField Types.swift b/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField Types.swift new file mode 100644 index 00000000..6fe28240 --- /dev/null +++ b/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField Types.swift @@ -0,0 +1,28 @@ +// +// TimecodeField Types.swift +// TimecodeKit • https://github.com/orchetect/TimecodeKit +// © 2020-2024 Steffan Andrews • Licensed under MIT License +// + +#if canImport(SwiftUI) && (os(macOS) || os(iOS) || os(visionOS)) + +import SwiftUI +import TimecodeKit + +@available(macOS 14, iOS 17, *) +@available(watchOS, unavailable) +@available(tvOS, unavailable) +extension TimecodeField { + /// An enum describing actions to perform in response to ``TimecodeField`` user input. + public enum FieldAction: Equatable, Hashable, Sendable { + /// End editing. + /// Removes focus from the timecode field. + case endEditing + + /// Resets component focus to the specified component. + /// If `nil`, focus is reset to the first visible component. + case resetComponentFocus(component: Timecode.Component? = nil) + } +} + +#endif diff --git a/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField View Modifiers.swift b/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField View Modifiers.swift index 5ad77d9f..777ab92f 100644 --- a/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField View Modifiers.swift +++ b/Sources/TimecodeKitUI/SwiftUI/TimecodeField/TimecodeField View Modifiers.swift @@ -9,8 +9,61 @@ import SwiftUI import TimecodeKit +// MARK: - TimecodeFieldReturnAction + +/// Sets the `Return` key action for ``TimecodeField`` views. +@available(macOS 14, iOS 17, *) +@available(watchOS, unavailable) +@available(tvOS, unavailable) +struct TimecodeFieldReturnActionViewModifier: ViewModifier { + let format: TimecodeField.FieldAction? + + func body(content: Content) -> some View { + content.environment(\.timecodeFieldReturnAction, format) + } +} + +@available(macOS 14, iOS 17, *) +@available(watchOS, unavailable) +@available(tvOS, unavailable) +extension View { + /// Sets the `Return` key action for ``TimecodeField`` views. + public func timecodeFieldReturnAction( + _ format: TimecodeField.FieldAction? + ) -> some View { + modifier(TimecodeFieldReturnActionViewModifier(format: format)) + } +} + +// MARK: - TimecodeFieldEscapeAction + +/// Sets the `Escape` key action for ``TimecodeField`` views. +@available(macOS 14, iOS 17, *) +@available(watchOS, unavailable) +@available(tvOS, unavailable) +struct TimecodeFieldEscapeActionViewModifier: ViewModifier { + let format: TimecodeField.FieldAction? + + func body(content: Content) -> some View { + content.environment(\.timecodeFieldEscapeAction, format) + } +} + +@available(macOS 14, iOS 17, *) +@available(watchOS, unavailable) +@available(tvOS, unavailable) +extension View { + /// Sets the `Escape` key action for ``TimecodeField`` views. + public func timecodeFieldEscapeAction( + _ format: TimecodeField.FieldAction? + ) -> some View { + modifier(TimecodeFieldEscapeActionViewModifier(format: format)) + } +} + // MARK: - TimecodeFormat +/// Sets the timecode string format for ``TimecodeField`` and ``TimecodeText`` views. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) struct TimecodeFormatViewModifier: ViewModifier { let format: Timecode.StringFormat @@ -32,6 +85,8 @@ extension View { // MARK: - TimecodeHighlightStyle +/// Sets the component highlight style for ``TimecodeField`` views. +/// By default, the application's `accentColor` is used. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) struct TimecodeHighlightStyleViewModifier: ViewModifier { let color: Color? @@ -54,6 +109,11 @@ extension View { // MARK: - TimecodeSeparatorStyle +/// Sets the text separator style for ``TimecodeField`` and ``TimecodeText`` views. +/// If `color` is nil, the foreground style is used. +/// +/// - Note: To set the default color of the component values, use `foregroundColor` or `foregroundStyle` view +/// modifiers. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) struct TimecodeSeparatorStyleViewModifier: ViewModifier { let color: Color? @@ -79,6 +139,12 @@ extension View { // MARK: - TimecodeValidationStyle +/// Sets timecode component validation style for ``TimecodeField`` and ``TimecodeText`` views. +/// +/// This foreground color will be used only for any timecode component values that are invalid for the given +/// properties (frame rate, subframes base, and upper limit). +/// +/// If `nil`, validation is disabled and invalid components will not be colorized. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) struct TimecodeValidationStyleViewModifier: ViewModifier { let color: Color?