A comprehensive form library for PocketMine-MP that simplifies the creation and management of Minecraft Bedrock Edition forms, supporting class-based definitions, fluent dynamic builders, pagination, form chaining, conditional elements, async data loading, timeouts, and ready-made presets.
- Three form types: Long forms (button lists), modal dialogs (yes/no), and custom forms (inputs, toggles, sliders, dropdowns).
- Class-based and dynamic approaches: Extend abstract classes for reusable form definitions, or use fluent method chaining for quick one-off forms.
- FormBuilder shorthand: Ultra-concise form creation through
FormBuilder::long(),FormBuilder::modal(), andFormBuilder::custom(). - Pagination: Automatically paginate large button lists with
PaginatedLongForm. - Dynamic buttons: Generate buttons at runtime from data sources with
DynamicButtonForm. - Form chaining: Build multi-step wizards with
FormChain, supporting conditional step skipping. - Conditional elements: Show or hide form elements based on trigger values with
ConditionalForm. - Async data loading: Load form content asynchronously with
AsyncFormData, displaying a loading screen while data is fetched. - Form history: Stack-based back-button navigation with
FormHistory. - Form timeouts: Automatically close forms after a set duration with
FormTimeout. - Presets: Common form patterns (confirm dialog, player selector, color picker, number input, text input) via
FormPresets. - Server-side validation: Validate text inputs with regex, length constraints, or custom closures using
ValidatedInput. - Searchable dropdowns: Filter dropdown options at runtime with
SearchableDropdown. - State management: Share data between forms using the array-accessible
FormDatacontainer. - Flow control: Return
FormResult::CLOSEorFormResult::KEEPto control whether a form closes or re-opens after interaction.
Add LibForm to your project with Composer:
composer require imperazim/libformThen import classes under the imperazim\form namespace. All form types require PHP 8.2+ and PocketMine-MP API 5.0.0.
Form-- abstract base class implementingpocketmine\form\Form. ProvidessendTo(),sendWithHistory(), and JSON serialization.FormResult-- enum withCLOSEandKEEPcases to control post-interaction behavior.FormData-- array-accessible data container (ArrayAccess,IteratorAggregate,Countable) for sharing state between forms.LibForm-- plugin entry point for standalone usage.
use imperazim\form\FormData;
use imperazim\form\FormResult;
// Create a shared data container
$formData = new FormData([
'playerName' => $player->getName(),
'score' => 1000,
]);
// Access values
$name = $formData->get('playerName');
$score = $formData['score'];
// Check existence
$formData->has('playerName'); // true
// Flow control in form callbacks
return FormResult::CLOSE; // close the form
return FormResult::KEEP; // re-send the formTextElement-- abstract base for text-based elements, implementsJsonSerializable.Title-- represents a form title. ExtendsTextElement.Content-- represents form body text. ExtendsTextElement.
use imperazim\form\base\elements\Title;
use imperazim\form\base\elements\Content;
$title = new Title('My Form');
$content = new Content('Select an option below:');
$title->getText(); // "My Form"
$content->getText(); // "Select an option below:"LongForm-- abstract class for scrollable button-list forms. Extend and overridetitle(),content(), andbuttons(). Passtrueas the third constructor argument to auto-send.DynamicLongForm-- fluent builder for long forms. Create withDynamicLongForm::create()orDynamicLongForm::build(), chain methods, then callsendTo().DynamicButtonForm-- long form whose buttons are generated from a factory closure at send time. Supports optional caching and cache invalidation.PaginatedLongForm-- automatically splits a largeButtonCollectioninto pages with "Previous" / "Next" navigation buttons.
Class-based LongForm:
use pocketmine\player\Player;
use imperazim\form\FormData;
use imperazim\form\FormResult;
use imperazim\form\long\LongForm;
use imperazim\form\base\elements\Title;
use imperazim\form\base\elements\Content;
use imperazim\form\long\elements\Button;
use imperazim\form\long\elements\ButtonTexture;
use imperazim\form\long\elements\ButtonCollection;
use imperazim\form\long\response\ButtonResponse;
class MyListForm extends LongForm {
protected function title(Player $player, FormData $data): Title {
return new Title('Main Menu');
}
protected function content(Player $player, FormData $data): Content {
return new Content('Select an option:');
}
protected function buttons(Player $player, FormData $data): ButtonCollection {
return ButtonCollection::fromArray([
new Button(
'Play',
new ButtonTexture('textures/ui/play', ButtonTexture::PATH),
new ButtonResponse(function(Player $player): FormResult {
$player->sendMessage("Let's go!");
return FormResult::CLOSE;
})
),
new Button(
'Settings',
new ButtonTexture('', ButtonTexture::PATH),
new ButtonResponse(fn(Player $p) => FormResult::CLOSE)
),
]);
}
}
// Auto-send on construction:
new MyListForm($player, new FormData(), true);DynamicLongForm (fluent):
use imperazim\form\FormResult;
use imperazim\form\long\DynamicLongForm;
DynamicLongForm::create("Quick Menu")
->setContent("What do you want to do?")
->addButton("Status", 'textures/blocks/diamond_block', function(Player $p): FormResult {
$p->sendMessage("Status chosen!");
return FormResult::CLOSE;
})
->addButton("Settings", null, fn(Player $p) => FormResult::CLOSE)
->setOnClose(fn(Player $p) => FormResult::CLOSE)
->sendTo($player);DynamicButtonForm (runtime-generated buttons):
use pocketmine\Server;
use imperazim\form\FormResult;
use imperazim\form\long\DynamicButtonForm;
use imperazim\form\long\elements\Button;
use imperazim\form\long\elements\ButtonTexture;
use imperazim\form\long\elements\ButtonCollection;
use imperazim\form\long\response\ButtonResponse;
DynamicButtonForm::send(
$player,
"Online Players",
"Select a player:",
function(): ButtonCollection {
$buttons = new ButtonCollection();
foreach (Server::getInstance()->getOnlinePlayers() as $p) {
$buttons->add(new Button(
$p->getName(),
new ButtonTexture('', ButtonTexture::PATH),
new ButtonResponse(fn(Player $pl) => FormResult::CLOSE)
));
}
return $buttons;
},
fn(Player $p, Button $btn, int $idx) => FormResult::CLOSE
);PaginatedLongForm:
use imperazim\form\FormResult;
use imperazim\form\long\PaginatedLongForm;
use imperazim\form\long\elements\Button;
use imperazim\form\long\elements\ButtonCollection;
$allButtons = new ButtonCollection();
for ($i = 1; $i <= 50; $i++) {
$allButtons->add(new Button(
"Item #$i",
new ButtonTexture('', ButtonTexture::PATH),
new ButtonResponse(fn(Player $p) => FormResult::CLOSE)
));
}
$form = new PaginatedLongForm(
player: $player,
title: "Shop Items",
content: "Select an item to buy:",
buttons: $allButtons,
onSelect: fn(Player $p, Button $btn, int $globalIndex) => FormResult::CLOSE,
perPage: 8
);
$form->sendTo($player);Button-- represents a form button with text, aButtonTexture, and aButtonResponsecallback. ImplementsJsonSerializable.ButtonTexture-- defines a button icon source. UseButtonTexture::PATHfor resource pack paths orButtonTexture::URLfor remote URLs.ButtonCollection-- immutable, countable, iterable collection ofButtonobjects. SupportsArrayAccess,fromArray(), andgetByIndex().
use imperazim\form\long\elements\Button;
use imperazim\form\long\elements\ButtonTexture;
use imperazim\form\long\elements\ButtonCollection;
use imperazim\form\long\response\ButtonResponse;
use imperazim\form\FormResult;
// Create a button with a path-based texture
$button = new Button(
'Diamond Sword',
new ButtonTexture('textures/items/diamond_sword', ButtonTexture::PATH),
new ButtonResponse(fn(Player $p) => FormResult::CLOSE)
);
$button->getText(); // "Diamond Sword"
$button->getTexture(); // ButtonTexture instance
$button->getResponse(); // ButtonResponse instance
// Create a button with a URL-based texture
$urlButton = new Button(
'Website',
new ButtonTexture('https://example.com/icon.png', ButtonTexture::URL),
new ButtonResponse(fn(Player $p) => FormResult::CLOSE)
);
// Build a collection
$collection = ButtonCollection::fromArray([$button, $urlButton]);
$collection->count(); // 2
$collection->getByIndex(0); // first ButtonButtonResponse-- wraps aClosurethat handles button clicks. The closure receives aPlayerand must return aFormResult.
use pocketmine\player\Player;
use imperazim\form\FormResult;
use imperazim\form\long\response\ButtonResponse;
$response = new ButtonResponse(function(Player $player): FormResult {
$player->sendMessage("Button clicked!");
return FormResult::CLOSE;
});
$result = $response->handle($player); // FormResult::CLOSEModalForm-- abstract class for two-button confirmation dialogs. Extend and overridetitle(),content(),button1(),button2(),onConfirm(), andonCancel().DynamicModalForm-- fluent builder for modal forms. SupportssetContent(),setButton1(),setButton2(),setOnClose(), and the staticbuild()configurator pattern.
Class-based ModalForm:
use pocketmine\player\Player;
use imperazim\form\FormData;
use imperazim\form\FormResult;
use imperazim\form\modal\ModalForm;
use imperazim\form\modal\elements\ModalButton;
use imperazim\form\base\elements\Title;
use imperazim\form\base\elements\Content;
class DeleteConfirmForm extends ModalForm {
protected function title(Player $player, FormData $data): Title {
return new Title('Delete Item');
}
protected function content(Player $player, FormData $data): Content {
return new Content('Are you sure you want to delete this item?');
}
protected function button1(Player $player, FormData $data): ModalButton {
return ModalButton::confirm('Yes, delete');
}
protected function button2(Player $player, FormData $data): ModalButton {
return ModalButton::cancel('Cancel');
}
protected function onConfirm(Player $player, FormData $data): FormResult {
$player->sendMessage('Item deleted!');
return FormResult::CLOSE;
}
protected function onCancel(Player $player, FormData $data): FormResult {
return FormResult::CLOSE;
}
}
new DeleteConfirmForm($player, new FormData(), true);DynamicModalForm (fluent):
use pocketmine\player\Player;
use imperazim\form\FormResult;
use imperazim\form\modal\DynamicModalForm;
use imperazim\form\modal\response\ModalResponse;
DynamicModalForm::create("Save Changes")
->setContent("Do you want to save your changes?")
->setButton1("Save", function(Player $p, ModalResponse $r): FormResult {
$p->sendMessage('Changes saved!');
return FormResult::CLOSE;
})
->setButton2("Discard", function(Player $p, ModalResponse $r): FormResult {
$p->sendMessage('Changes discarded.');
return FormResult::CLOSE;
})
->sendTo($player);ModalButton-- represents a modal dialog button. Created via named constructorsModalButton::confirm($label)andModalButton::cancel($label). Associates with aModalResulttype.
use imperazim\form\modal\elements\ModalButton;
$yes = ModalButton::confirm('Yes');
$no = ModalButton::cancel('No');
$yes->getText(); // "Yes"
$yes->getType(); // ModalResult::CONFIRMModalResponse-- value object wrapping aModalResult. ProvidesisConfirmed(),isCanceled(), andgetResult().ModalResult-- enum withCONFIRMandCANCELcases.
use imperazim\form\modal\response\ModalResponse;
use imperazim\form\modal\result\ModalResult;
$response = ModalResponse::from(true);
$response->isConfirmed(); // true
$response->isCanceled(); // false
$response->getResult(); // ModalResult::CONFIRMCustomForm-- abstract class for forms with multiple input elements. Extend and overridetitle(),elements(),onSubmit(), andonClose(). SupportsValidatedInputserver-side validation.DynamicCustomForm-- fluent builder for custom forms. UseaddElement(),setOnSubmit(),setOnClose(), and the staticbuild()configurator pattern.
Class-based CustomForm:
use pocketmine\player\Player;
use imperazim\form\FormData;
use imperazim\form\FormResult;
use imperazim\form\custom\CustomForm;
use imperazim\form\custom\elements\ElementCollection;
use imperazim\form\custom\elements\Input;
use imperazim\form\custom\elements\Toggle;
use imperazim\form\custom\elements\Slider;
use imperazim\form\custom\elements\Dropdown;
use imperazim\form\custom\elements\Option;
use imperazim\form\custom\response\CustomResponse;
use imperazim\form\base\elements\Title;
class SettingsForm extends CustomForm {
protected function title(Player $player, FormData $data): Title {
return new Title('Settings');
}
protected function elements(Player $player, FormData $data): ElementCollection {
return ElementCollection::fromArray([
new Input('username', 'Username:', 'Type your name', $player->getName()),
new Toggle('notifications', 'Enable notifications?', true),
new Slider('volume', 'Volume', 0, 100, 1, 50),
new Dropdown('language', 'Language', [
new Option('en', 'English'),
new Option('es', 'Spanish'),
], 'en'),
]);
}
protected function onSubmit(Player $player, CustomResponse $response): FormResult {
$name = $response->getInput('username');
$volume = $response->getSlider('volume');
$lang = $response->getDropdown('language');
$player->sendMessage("Saved: $name, vol=$volume, lang={$lang->getId()}");
return FormResult::CLOSE;
}
protected function onClose(Player $player, FormData $data): FormResult {
return FormResult::CLOSE;
}
}
new SettingsForm($player, new FormData(), true);DynamicCustomForm (fluent):
use pocketmine\player\Player;
use imperazim\form\FormResult;
use imperazim\form\custom\DynamicCustomForm;
use imperazim\form\custom\elements\Input;
use imperazim\form\custom\elements\Toggle;
use imperazim\form\custom\response\CustomResponse;
DynamicCustomForm::create("Player Profile")
->addElement(new Input('nickname', 'Nickname:', 'Enter nickname...', $player->getName()))
->addElement(new Toggle('pvp', 'Enable PvP?', false))
->setOnSubmit(function(Player $p, CustomResponse $response): FormResult {
$nick = $response->getInput('nickname');
$pvp = $response->getToggle('pvp') ? 'enabled' : 'disabled';
$p->sendMessage("Nickname: $nick | PvP: $pvp");
return FormResult::CLOSE;
})
->sendTo($player);Element-- abstract base class for all custom form elements. ProvidesgetId(),hasId(), andhasValue().ElementCollection-- immutable, countable, iterable collection ofElementobjects. SupportsArrayAccess,fromArray(), andgetByIndex().Input-- text input field with ID, label, placeholder, and default value.ValidatedInput-- extendsElementwith server-side validation: regex pattern, min/max length, custom validator closure, and configurable error message.Toggle-- on/off boolean switch.Slider-- numeric range selector with min, max, step, and default. Validates that min < max and step > 0.Dropdown-- single-select option list. UsesOptionNormalizerTraitfor flexible option input formats.StepSlider-- visual step selector, similar toDropdown. ProvidesgetStepByIndex()andgetSelectedStep()aliases.SearchableDropdown-- dropdown with runtime filtering viasearch()andsetOptions(). Supports a placeholder text.Label-- non-interactive text element. ReturnsfalsefromhasValue().Option-- value object representing a selectable option with anidand displaytext.OptionNormalizerTrait-- shared trait used byDropdown,StepSlider, andSearchableDropdown. Normalizes raw arrays intoOption[]and resolves default indices.
use imperazim\form\custom\elements\Input;
use imperazim\form\custom\elements\ValidatedInput;
use imperazim\form\custom\elements\Toggle;
use imperazim\form\custom\elements\Slider;
use imperazim\form\custom\elements\Dropdown;
use imperazim\form\custom\elements\StepSlider;
use imperazim\form\custom\elements\SearchableDropdown;
use imperazim\form\custom\elements\Label;
use imperazim\form\custom\elements\Option;
use imperazim\form\custom\elements\ElementCollection;
// Input
$input = new Input('name', 'Your Name:', 'Enter name...', 'Steve');
// ValidatedInput with regex and length constraints
$email = new ValidatedInput(
'email', 'Email:', 'user@example.com', '',
pattern: '/^[^@]+@[^@]+\.[a-z]+$/i',
minLength: 5,
maxLength: 64,
errorMessage: 'Invalid email format'
);
// ValidatedInput with custom closure
$age = new ValidatedInput(
'age', 'Age:', '', '',
validator: fn(string $v) => is_numeric($v) && (int)$v >= 13,
errorMessage: 'You must be at least 13'
);
// Toggle
$toggle = new Toggle('pvp', 'Enable PvP?', false);
// Slider
$slider = new Slider('volume', 'Volume', 0, 100, 5, 50);
// Dropdown with Option objects
$dropdown = new Dropdown('color', 'Favorite Color', [
new Option('red', 'Red'),
new Option('blue', 'Blue'),
new Option('green', 'Green'),
], 'red');
// Dropdown with simple string array
$dropdown2 = new Dropdown('mode', 'Game Mode', ['Survival', 'Creative', 'Adventure']);
// StepSlider
$stepper = new StepSlider('difficulty', 'Difficulty', [
new Option('easy', 'Easy'),
new Option('normal', 'Normal'),
new Option('hard', 'Hard'),
], 'normal');
// SearchableDropdown
$searchable = new SearchableDropdown('kit', 'Select Kit:', [
new Option('warrior', 'Warrior'),
new Option('archer', 'Archer'),
new Option('mage', 'Mage'),
], placeholder: 'Type to search...');
$results = $searchable->search('war'); // filters matching options
// Label (non-interactive)
$label = new Label('Welcome to the settings menu!');
// Build a collection
$collection = ElementCollection::fromArray([
$label, $input, $toggle, $slider, $dropdown,
]);CustomResponse-- encapsulates submitted form data. Provides typed getters:getInput(),getToggle(),getSlider(),getDropdown(),getStepSlider(), as well as genericgetString(),getInt(),getBool(),getFloat(),getArray(). Also exposesgetFormData(),getElement(),getElementsRaw(), andhas().SelectedOption-- value object returned by dropdown/step slider selections. ProvidesgetIndex(),getId(), andgetText().
use imperazim\form\custom\response\CustomResponse;
use imperazim\form\custom\response\SelectedOption;
// Inside an onSubmit callback:
$name = $response->getInput('username'); // string
$enabled = $response->getToggle('notifications'); // bool
$volume = $response->getSlider('volume'); // float
$language = $response->getDropdown('language'); // SelectedOption
$language->getId(); // "en"
$language->getText(); // "English"
$language->getIndex(); // 0
// Generic access
$response->getString('username');
$response->getInt('score', 0);
$response->getBool('pvp', false);
$response->has('username'); // trueConfirmForm-- a ready-madeModalFormsubclass for quick yes/no confirmation dialogs. Accepts title, content, confirm/cancel callbacks, and custom button labels. UseConfirmForm::send()for a one-liner.
use pocketmine\player\Player;
use imperazim\form\FormResult;
use imperazim\form\confirm\ConfirmForm;
// One-liner static send
ConfirmForm::send(
$player,
"Delete Warp",
"Are you sure you want to delete this warp?",
function(Player $p): FormResult {
$p->sendMessage("Warp deleted!");
return FormResult::CLOSE;
},
function(Player $p): FormResult {
$p->sendMessage("Cancelled.");
return FormResult::CLOSE;
},
confirmLabel: 'Yes, delete',
cancelLabel: 'Keep it'
);FormHistory-- static stack-based form history for back-button navigation. Stores forms per player usingSplStack. Providespush(),back(),hasHistory(),clear(), andcleanup().
use imperazim\form\history\FormHistory;
// Push current form before navigating to a new one
FormHistory::push($player, $currentForm);
$nextForm->sendTo($player);
// Go back to the previous form
$previousForm = FormHistory::back($player); // re-sends automatically
// Check if back navigation is available
if (FormHistory::hasHistory($player)) {
// Show a "Back" button
}
// Clean up on player quit
FormHistory::cleanup($player);FormBuilder-- static entry point withcustom(),long(), andmodal()methods returning specialized builders.LongFormBuilder-- fluent builder wrappingDynamicLongForm. Methods:content(),button(),onClose(),build(),send().ModalFormBuilder-- fluent builder wrappingDynamicModalForm. Methods:content(),accept(),deny(),onClose(),build(),send().CustomFormBuilder-- fluent builder wrappingDynamicCustomForm. Methods:input(),toggle(),slider(),dropdown(),stepSlider(),label(),onSubmit(),onClose(),build(),send().
use pocketmine\player\Player;
use imperazim\form\FormResult;
use imperazim\form\builder\FormBuilder;
// Long form
FormBuilder::long("Menu")
->content("Choose an option:")
->button("Play", fn(Player $p) => FormResult::CLOSE)
->button("Shop", fn(Player $p) => FormResult::CLOSE, "textures/items/diamond")
->send($player);
// Modal form
FormBuilder::modal("Confirm")
->content("Are you sure?")
->accept("Yes", fn(Player $p, $r) => FormResult::CLOSE)
->deny("No", fn(Player $p, $r) => FormResult::CLOSE)
->send($player);
// Custom form
FormBuilder::custom("Settings")
->label("Configure your preferences:")
->input("name", "Player Name", placeholder: "Enter name")
->toggle("pvp", "Enable PvP", default: true)
->slider("volume", "Volume", 0, 100, 1, 50)
->dropdown("mode", "Game Mode", ["Survival", "Creative"])
->stepSlider("difficulty", "Difficulty", ["Easy", "Normal", "Hard"])
->onSubmit(fn(Player $p, $r) => FormResult::CLOSE)
->send($player);FormPresets-- static utility class providing ready-made form templates:confirm()-- yes/no confirmation dialog.numberInput()-- slider-based numeric input with validation.playerSelector()-- button list of all online players (optionally excluding the sender).colorPicker()-- Minecraft color code selector.textInput()-- single text input form.
use pocketmine\player\Player;
use imperazim\form\preset\FormPresets;
// Confirm dialog
FormPresets::confirm($player, "Delete", "Are you sure?", function(Player $p) {
$p->sendMessage("Deleted!");
});
// Number input
FormPresets::numberInput($player, "Set Level", "Choose level:", function(Player $p, $value) {
$p->sendMessage("Level set to $value");
}, min: 1, max: 50, step: 1, default: 10);
// Player selector
FormPresets::playerSelector($player, "Choose Player", function(Player $sender, Player $target) {
$sender->sendMessage("Selected: " . $target->getName());
});
// Color picker
FormPresets::colorPicker($player, function(Player $p, string $colorCode) {
$p->sendMessage($colorCode . "You picked this color!");
});
// Text input
FormPresets::textInput($player, "Rename", "New name:", function(Player $p, string $text) {
$p->sendMessage("Renamed to: $text");
}, placeholder: "Enter new name...");AsyncFormData-- populates form elements via async callbacks. Shows a "Loading..." form while data is being fetched, then re-sends with the loaded content on the next tick. Provides:customForm()-- async custom form with dynamically loaded elements.longForm()-- async long form with dynamically loaded buttons.
use Closure;
use pocketmine\player\Player;
use imperazim\form\FormResult;
use imperazim\form\async\AsyncFormData;
use imperazim\form\custom\elements\Dropdown;
use imperazim\form\custom\elements\Option;
// Async custom form
AsyncFormData::customForm(
$plugin,
$player,
"Shop",
function(Closure $resolve) {
// Simulate async operation (DB query, HTTP request, etc.)
$resolve([
new Dropdown("item", "Choose item", [
new Option("sword", "Sword"),
new Option("shield", "Shield"),
]),
]);
},
function(Player $p, $response): FormResult {
$item = $response->getDropdown('item');
$p->sendMessage("You chose: " . $item->getText());
return FormResult::CLOSE;
}
);
// Async long form
AsyncFormData::longForm(
$plugin,
$player,
"Warps",
"Select a warp:",
function(Closure $resolve) {
$resolve(["Spawn" => null, "PvP" => null, "Shop" => null]);
},
function(Player $p, string $buttonText): FormResult {
$p->sendMessage("Teleporting to $buttonText...");
return FormResult::CLOSE;
}
);ConditionalForm-- form with elements that appear or disappear based on trigger element values. When a trigger changes, the form is automatically rebuilt and re-sent with the updated element set.
use pocketmine\player\Player;
use imperazim\form\FormResult;
use imperazim\form\conditional\ConditionalForm;
use imperazim\form\custom\elements\Toggle;
use imperazim\form\custom\elements\Input;
use imperazim\form\custom\elements\Slider;
$form = new ConditionalForm("Settings");
// Base elements (always shown)
$form->addElement(new Toggle("advanced", "Advanced Mode"));
$form->addElement(new Input("name", "Display Name:", "Enter name..."));
// Conditional elements (shown only when "advanced" is true)
$form->when("advanced", true, function() {
return [
new Input("customPrefix", "Custom Prefix:", "[Prefix]"),
new Slider("particleCount", "Particle Count", 0, 100, 1, 10),
];
});
$form->onSubmit(function(Player $p, $response): FormResult {
$p->sendMessage("Settings saved!");
return FormResult::CLOSE;
});
$form->send($player);FormChain-- orchestrates a sequence of forms shown one after another (wizard/stepper pattern). Each step receives accumulated data from previous steps. Supports conditional step skipping, completion callbacks, and cancellation handling.
use pocketmine\player\Player;
use imperazim\form\FormData;
use imperazim\form\chain\FormChain;
FormChain::create($player)
->step(fn(Player $p, FormData $data) => new NameForm($p, $data))
->step(fn(Player $p, FormData $data) => new AgeForm($p, $data))
->step(
fn(Player $p, FormData $data) => new ConfirmForm($p, $data),
skip: fn(FormData $d) => $d['skipConfirm'] ?? false
)
->onComplete(function(Player $p, FormData $data) {
$p->sendMessage("Registration complete! Name: " . $data['name']);
})
->onCancel(function(Player $p, FormData $data, int $step) {
$p->sendMessage("Cancelled at step $step.");
})
->start();
// Inside each step form, call $chain->advance(['key' => 'value']) to proceed
// or $chain->cancel() to abort the chain.FormTimeout-- wraps any form with an automatic timeout that closes it after a specified number of seconds. Manages active timeout tasks per player, automatically cancelling previous timeouts. Providessend(),cancel(), andcancelAll().
use pocketmine\player\Player;
use imperazim\form\timeout\FormTimeout;
// Send a form that auto-closes after 30 seconds
$form = new MyLongForm($player, $data);
FormTimeout::send($plugin, $player, $form, seconds: 30, onTimeout: function(Player $p) {
$p->sendMessage("Form timed out! Please try again.");
});
// Manually cancel a timeout
FormTimeout::cancel($player->getId());
// Cancel all active timeouts (e.g. on plugin disable)
FormTimeout::cancelAll();This project is licensed under MIT. Please see the LICENSE file for details.