Manages text input and color parsing with error feedback.
Users type color values. They make mistakes—typos, invalid formats. The app needs to validate their input and show helpful error messages. Manually tracking input state, validation, and error messages across renders is cumbersome and error-prone.
This object holds the current input string. It validates by parsing. It stores errors and clears them when appropriate. It provides methods to manipulate the input (append, delete).
Use it to build text input forms where validation feedback matters.
Example
input = Input.new input.append_char("#") input.append_char("f") input.append_char("f") color = input.parse # => Color or nil puts input.error # => error message if parse failed
Source Code
# frozen_string_literal: true # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com> # SPDX-License-Identifier: AGPL-3.0-or-later require_relative "color" # Manages text input and color parsing with error feedback. # # Users type color values. They make mistakes—typos, invalid formats. The app # needs to validate their input and show helpful error messages. Manually # tracking input state, validation, and error messages across renders is # cumbersome and error-prone. # # This object holds the current input string. It validates by parsing. It stores # errors and clears them when appropriate. It provides methods to manipulate # the input (append, delete). # # Use it to build text input forms where validation feedback matters. # # === Example # # input = Input.new # input.append_char("#") # input.append_char("f") # input.append_char("f") # color = input.parse # => Color or nil # puts input.error # => error message if parse failed class Input PRINTABLE_PATTERN = /[\w#,().\s%]/ # Creates a new Input with an optional initial value. # # [initial_value] String initial color input (default: <tt>"#F96302"</tt>) def initialize(initial_value = "#F96302") @value = initial_value @error = "" end # Current input string. # # === Example # # input = Input.new # input.value # => "#F96302" def value @value end # Error message from the last failed parse, or empty string. # # === Example # # input.parse # => nil (invalid) # input.error # => "Invalid color format. Try: #ff0000, rgb(255,0,0), red" def error @error end # Clears the current error message. def clear_error @error = "" end # Appends a character to the input if it matches the printable pattern. # # Silently ignores non-printable characters. Valid characters include # letters, digits, hash, comma, parentheses, dot, space, and percent. # # [char] String single character def append_char(char) @value += char if char.length == 1 && char.match?(PRINTABLE_PATTERN) end # Removes the last character from the input. def delete_char @value = @value[0...-1] end # Replaces the entire input string. # # [text] String new input value def set(text) @value = text end # Parses the current input as a Color. # # Returns a Color if valid; nil otherwise. Sets the error message on failure. # Clears the error message on success. # # === Example # # input = Input.new("#FF0000") # color = input.parse # => Color # input.error # => "" def parse color = Color.parse(@value) if color clear_error color else @error = "Invalid color format. Try: #ff0000, rgb(255,0,0), red" nil end end # Renders the input widget for display in a TUI frame. # # Shows the current input value with a cursor. Displays the error message # in red if one is set. # # [tui] Session or TUI factory object # # === Example # # input = Input.new # widget = input.render(tui) # frame.render_widget(widget, area) def render(tui) input_lines = [ tui.text_line(spans: [ tui.text_span(content: @value), tui.text_span(content: "_", style: tui.style(modifiers: [:reversed])), ]), ] unless @error.empty? input_lines << tui.text_line(spans: [ tui.text_span(content: @error, style: tui.style(fg: :red)), ]) end tui.block( title: "Color Input", borders: [:all], children: [ tui.paragraph(text: input_lines), ] ) end end