sugarcraft/candy-forms

Foundation lib for form primitives (TextInput, TextArea, ItemList, Viewport, FilePicker, Field interface, Confirm, Form) extracted from sugar-bits and sugar-prompt.

Maintainers

Package info

github.com/sugarcraft/candy-forms

Documentation

pkg:composer/sugarcraft/candy-forms

Statistics

Installs: 4

Dependents: 6

Suggesters: 0

Stars: 0

Open Issues: 0

dev-master 2026-06-02 08:21 UTC

This package is not auto-updated.

Last update: 2026-06-02 12:40:46 UTC


README

Foundation lib for form and input primitives — the raw component implementations that sugar-bits and sugar-prompt re-export. Extracted out of those two leaf libs so the form/prompt engine can ship as a single standalone dependency.

Install

composer require sugarcraft/candy-forms

Role

CandyForms owns the full primitive + form surface:

  • Input primitivesTextInput, TextArea, ItemList, Viewport, FilePicker, Cursor, Scrollbar, Spinner.
  • Form engineField interface + Field\{Input,Text,Confirm,Select, MultiSelect,Note,FilePicker}, Form, Group, KeyMap, Theme, Fuzzy\FuzzyMatcher, and Validator\{Required,Email,MinLength,MaxLength,Pattern}.

sugar-bits (SugarCraft\Bits\*) and sugar-prompt (SugarCraft\Prompt\*) now expose these classes as thin class_alias shims pointing back here, so existing imports keep working while the canonical implementation lives in SugarCraft\Forms\*.

Quickstart

A single-field form:

<?php

use SugarCraft\Forms\Field\Input;
use SugarCraft\Forms\Form;
use SugarCraft\Forms\Validator\Required;

$form = Form::new(
    Input::new('name')
        ->withTitle('What is your name?')
        ->withValidator(new Required())
);

echo $form->view();

Standalone spinner (now lives here, was in sugar-bits):

<?php

use SugarCraft\Forms\Spinner\Spinner;
use SugarCraft\Forms\Spinner\Style;

$spinner = Spinner::new(Style::dot());
// in your model's init():   return $spinner->init();
// in your model's update():  [$spinner, $cmd] = $spinner->update($msg);
// in your model's view():    "loading {$spinner->view()}"

Shared foundations

candy-forms is built on five shared packages:

  • candy-buffer — Cell-grid model + Buffer::toAnsi() for rendering glyphs and styles to ANSI bytes. TextInput / TextArea use the buffer path internally; their snapshot tests pin byte-for-byte output.
  • candy-layoutLayoutSolver + constraint types (Constraint::length, Constraint::fill, etc.). Form::withConstraints() routes through the solver when an explicit constraint set is provided.
  • candy-testingAssertions::assertGoldenAnsi and Assertions::assertCellGrid snapshot helpers. Render fixtures live in tests/fixtures/; set UPDATE_GOLDENS=1 to regenerate them.
  • candy-fuzzySmithWatermanMatcher scores. Select / MultiSelect filter delegates to it internally; the existing public withFilter(callable) injection point is preserved for custom filters.
  • candy-asyncAsync\Await + CancelledException for async suggestion fetchers. withAsyncSuggestions stores a CancellationSource on the model and cancels the previous timer before scheduling the next, so rapid keystrokes only fire one network request.
  • Vim keybindingsVimKeyHandler provides a shared vim mode handler (VimState: Insert/Normal/Visual/VisualLine; VimAction: CursorLeft, CursorRight, DeleteChar, YankLine, …). sugar-prompt, sugar-bits, and sugar-readline delegate to it — new bindings benefit all 4 libs at once.

The legacy SugarCraft\Forms\Fuzzy\FuzzyMatcher (step-07 back-compat shim) remains as a deprecated alias — it delegates to SugarCraft\Fuzzy\Matcher\SmithWatermanMatcher with no behavioural divergence.

Dependencies

  • sugarcraft/candy-core — Elm-architecture TUI runtime
  • sugarcraft/candy-sprinkles — Declarative styling

Snapshot tests

Render output is covered by golden-file snapshot tests. Fixture files live in tests/fixtures/ with a .golden extension and are compared against actual ANSI byte output via SugarCraft\Testing\Snapshot\Assertions::assertGoldenAnsi(). To re-record fixtures after intentional output changes:

UPDATE_GOLDENS=1 vendor/bin/phpunit

License

MIT