sugarcraft / candy-forms
Foundation lib for form primitives (TextInput, TextArea, ItemList, Viewport, FilePicker, Field interface, Confirm, Form) extracted from sugar-bits and sugar-prompt.
Requires
- php: ^8.3
- sugarcraft/candy-async: dev-master
- sugarcraft/candy-buffer: dev-master
- sugarcraft/candy-core: dev-master
- sugarcraft/candy-fuzzy: dev-master
- sugarcraft/candy-layout: dev-master
- sugarcraft/candy-sprinkles: dev-master
Requires (Dev)
- phpunit/phpunit: ^10.5
- sugarcraft/candy-testing: dev-master
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 primitives —
TextInput,TextArea,ItemList,Viewport,FilePicker,Cursor,Scrollbar,Spinner. - Form engine —
Fieldinterface +Field\{Input,Text,Confirm,Select, MultiSelect,Note,FilePicker},Form,Group,KeyMap,Theme,Fuzzy\FuzzyMatcher, andValidator\{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-layout —
LayoutSolver+ constraint types (Constraint::length,Constraint::fill, etc.).Form::withConstraints()routes through the solver when an explicit constraint set is provided. - candy-testing —
Assertions::assertGoldenAnsiandAssertions::assertCellGridsnapshot helpers. Render fixtures live intests/fixtures/; setUPDATE_GOLDENS=1to regenerate them. - candy-fuzzy —
SmithWatermanMatcherscores. Select / MultiSelect filter delegates to it internally; the existing publicwithFilter(callable)injection point is preserved for custom filters. - candy-async —
Async\Await+CancelledExceptionfor async suggestion fetchers.withAsyncSuggestionsstores aCancellationSourceon the model and cancels the previous timer before scheduling the next, so rapid keystrokes only fire one network request. - Vim keybindings —
VimKeyHandlerprovides 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 runtimesugarcraft/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