diff --git a/CHANGELOG.md b/CHANGELOG.md index 0989da0b..796b512c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ## 4.1.1 under development -- no changes in this release. +- New #276: Add `beforeInput()` and `afterInput()` methods to abstract `BooleanInputTag`, extended by `Radio` + and `Checkbox` (@vjik) ## 4.1.0 May 19, 2026 diff --git a/src/Tag/Base/BooleanInputTag.php b/src/Tag/Base/BooleanInputTag.php index 966b1c51..f0bc987d 100644 --- a/src/Tag/Base/BooleanInputTag.php +++ b/src/Tag/Base/BooleanInputTag.php @@ -18,6 +18,8 @@ abstract class BooleanInputTag extends InputTag private array $labelAttributes = []; private bool $labelWrap = true; private bool $labelEncode = true; + private string|Stringable $beforeInput = ''; + private string|Stringable $afterInput = ''; /** * @link https://www.w3.org/TR/html52/sec-forms.html#element-attrdef-input-checked @@ -86,6 +88,26 @@ final public function uncheckValue(bool|float|int|string|Stringable|null $value) return $new; } + /** + * @param string|Stringable $content Content to be rendered before the input. + */ + final public function beforeInput(string|Stringable $content): static + { + $new = clone $this; + $new->beforeInput = $content; + return $new; + } + + /** + * @param string|Stringable $content Content to be rendered after the input. + */ + final public function afterInput(string|Stringable $content): static + { + $new = clone $this; + $new->afterInput = $content; + return $new; + } + final protected function prepareAttributes(): void { $this->attributes['type'] = $this->getType(); @@ -97,23 +119,32 @@ final protected function before(): string ? null : Html::generateId(); - return $this->renderUncheckInput() - . ($this->labelWrap ? $this->renderLabelOpenTag($this->labelAttributes) : ''); + $html = $this->renderUncheckInput(); + + if ($this->labelWrap) { + $html .= $this->renderLabelOpenTag($this->labelAttributes); + } + + $html .= $this->beforeInput; + + return $html; } final protected function after(): string { + $html = (string) $this->afterInput; + if ($this->label === null) { - return ''; + return $html; } if ($this->labelWrap) { - $html = $this->label === '' ? '' : ' '; + $html .= $this->label === '' ? '' : ' '; } else { $labelAttributes = array_merge($this->labelAttributes, [ 'for' => $this->attributes['id'], ]); - $html = ' ' . $this->renderLabelOpenTag($labelAttributes); + $html .= ' ' . $this->renderLabelOpenTag($labelAttributes); } $html .= $this->labelEncode ? Html::encode($this->label) : $this->label; diff --git a/tests/Tag/Base/BooleanInputTagTest.php b/tests/Tag/Base/BooleanInputTagTest.php index 981143dd..7495c080 100644 --- a/tests/Tag/Base/BooleanInputTagTest.php +++ b/tests/Tag/Base/BooleanInputTagTest.php @@ -6,6 +6,9 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +use Stringable; +use Yiisoft\Html\IdGenerator; +use Yiisoft\Html\Tests\Objects\StringableObject; use Yiisoft\Html\Tests\Objects\TestBooleanInputTag; final class BooleanInputTagTest extends TestCase @@ -226,6 +229,128 @@ public function testUncheckValueWithLabel(): void ); } + public static function dataBeforeInput(): iterable + { + yield [ + 'Before', + 'Before', + ]; + yield [ + 'Before', + 'Before', + ]; + yield [ + 'Before', + new StringableObject('Before'), + ]; + yield [ + 'Before', + new StringableObject('Before'), + ]; + } + + #[DataProvider('dataBeforeInput')] + public function testBeforeInput(string $expectedBefore, string|Stringable $content): void + { + $input = (new TestBooleanInputTag())->beforeInput($content); + $this->assertSame( + $expectedBefore . '', + (string) $input, + ); + } + + public function testBeforeInputWithLabel(): void + { + $input = (new TestBooleanInputTag()) + ->label('One') + ->beforeInput('Before'); + $this->assertSame( + '', + (string) $input, + ); + } + + public function testBeforeInputWithSideLabel(): void + { + IdGenerator\disableSeed(); + IdGenerator\reset(); + + try { + $result = (new TestBooleanInputTag()) + ->sideLabel('One') + ->beforeInput('Before') + ->render(); + } finally { + IdGenerator\enableSeed(); + } + + $this->assertSame( + 'Before ', + $result, + ); + } + + public static function dataAfterInput(): iterable + { + yield [ + 'After', + 'After', + ]; + yield [ + 'After', + 'After', + ]; + yield [ + 'After', + new StringableObject('After'), + ]; + yield [ + 'After', + new StringableObject('After'), + ]; + } + + #[DataProvider('dataAfterInput')] + public function testAfterInput(string $expectedAfter, string|Stringable $content): void + { + $input = (new TestBooleanInputTag())->AfterInput($content); + $this->assertSame( + '' . $expectedAfter, + (string) $input, + ); + } + + public function testAfterInputWithLabel(): void + { + $input = (new TestBooleanInputTag()) + ->label('One') + ->afterInput('After'); + $this->assertSame( + '', + (string) $input, + ); + } + + public function testAfterInputWithSideLabel(): void + { + IdGenerator\disableSeed(); + IdGenerator\reset(); + + try { + $result = (new TestBooleanInputTag()) + ->sideLabel('One') + ->afterInput('After') + ->render(); + } finally { + IdGenerator\enableSeed(); + } + + $this->assertSame( + 'After ', + $result, + ); + } + public function testImmutability(): void { $input = new TestBooleanInputTag(); @@ -234,5 +359,7 @@ public function testImmutability(): void $this->assertNotSame($input, $input->sideLabel('')); $this->assertNotSame($input, $input->labelEncode(true)); $this->assertNotSame($input, $input->uncheckValue(null)); + $this->assertNotSame($input, $input->beforeInput('')); + $this->assertNotSame($input, $input->afterInput('')); } }