diff --git a/README.md b/README.md index 7668435..0886f1f 100644 --- a/README.md +++ b/README.md @@ -156,9 +156,9 @@ field name exactly. For PHP array submissions, supply a name such as `categories[]` yourself. Checkbox groups retain the existing convention of appending `[]` unless the name already ends with it. -`wrapperProps()` targets the dropdown's `
` or the group's `
`. +`containerProps()` targets the dropdown's `
` or the group's `
`. For native group-wide disabling, use -`->checkbox()->wrapperProps('disabled disabled')` (or `radio()`); the browser +`->checkbox()->containerProps('disabled disabled')` (or `radio()`); the browser disables descendants through fieldset semantics, without rewriting the inputs. Disabling a dropdown instead requires `inputProps('disabled disabled')`. `labelProps()` targets the dropdown's label or the group's legend. @@ -175,6 +175,14 @@ argument and explicit mode methods instead. A warning forces warning, caller-supplied guidance and invalid-state properties remain unchanged. Other repeated properties use the element builder's normal last-value behavior. +### Migrating from wrapperProps + +In 5.0, use `containerProps()` instead of `wrapperProps()` on all form +components. This is a rename, not a change in property handling or target +elements. `wrapperProps()` remains a forwarding alias throughout 5.x and is +marked with PHP's native `#[Deprecated]` attribute (supported since PHP 8.4). +Calling it emits `E_USER_DEPRECATED`; removal is deferred until at least 6.0. + ## Performance The `/comparisons` folder can be used to run performance tests locally to compare the same output using: diff --git a/src/Forms/EmailInput.php b/src/Forms/EmailInput.php index b501f6e..1419ce8 100644 --- a/src/Forms/EmailInput.php +++ b/src/Forms/EmailInput.php @@ -15,7 +15,7 @@ class EmailInput implements Stringable /** * @var list */ - private array $wrapperProperties = []; + private array $containerProperties = []; /** * @var list @@ -59,12 +59,18 @@ public function id(string $id): self /** * Sets properties on this component's outermost div, not the input. */ - public function wrapperProps(string ...$properties): self + public function containerProps(string ...$properties): self { - $this->wrapperProperties = array_values($properties); + $this->containerProperties = array_values($properties); return $this; } + #[\Deprecated(message: 'Use containerProps() instead.', since: '5.0')] + public function wrapperProps(string ...$properties): self + { + return $this->containerProps(...$properties); + } + public function labelProps(string ...$properties): self { $this->labelProperties = array_values($properties); @@ -135,6 +141,6 @@ public function __toString(): string ), $input, ...[...$this->content, $warning] - )->props(...$this->wrapperProperties); + )->props(...$this->containerProperties); } } diff --git a/src/Forms/Fieldset.php b/src/Forms/Fieldset.php index aaf3238..289a542 100644 --- a/src/Forms/Fieldset.php +++ b/src/Forms/Fieldset.php @@ -21,7 +21,7 @@ class Fieldset implements Stringable /** * @var string[] */ - private array $wrapperProperties = []; + private array $containerProperties = []; /** * @var string[] @@ -61,12 +61,18 @@ public function elements(string|Stringable ...$elements): self /** * Sets properties on this component's outermost element: the fieldset. */ - public function wrapperProps(string ...$properties): self + public function containerProps(string ...$properties): self { - $this->wrapperProperties = $properties; + $this->containerProperties = $properties; return $this; } + #[\Deprecated(message: 'Use containerProps() instead.', since: '5.0')] + public function wrapperProps(string ...$properties): self + { + return $this->containerProps(...$properties); + } + public function labelProps(string ...$properties): self { $this->labelProperties = $properties; @@ -96,6 +102,6 @@ public function __toString(): string return (string) Element::fieldset( Element::legend($this->label)->props(...$this->labelProperties), ...$elements - )->props(...$this->wrapperProperties); + )->props(...$this->containerProperties); } } diff --git a/src/Forms/Password.php b/src/Forms/Password.php index fe3f984..813c42b 100644 --- a/src/Forms/Password.php +++ b/src/Forms/Password.php @@ -17,7 +17,7 @@ class Password implements Stringable /** * @var list */ - private array $wrapperProperties = []; + private array $containerProperties = []; /** * @var list @@ -61,12 +61,18 @@ public function id(string $id): self /** * Sets properties on this component's outermost div, not the input. */ - public function wrapperProps(string ...$properties): self + public function containerProps(string ...$properties): self { - $this->wrapperProperties = array_values($properties); + $this->containerProperties = array_values($properties); return $this; } + #[\Deprecated(message: 'Use containerProps() instead.', since: '5.0')] + public function wrapperProps(string ...$properties): self + { + return $this->containerProps(...$properties); + } + public function labelProps(string ...$properties): self { $this->labelProperties = array_values($properties); @@ -153,6 +159,6 @@ public function __toString(): string ]), $input, ...[...$this->content, $warning] - )->props(...$this->wrapperProperties); + )->props(...$this->containerProperties); } } diff --git a/src/Forms/Select.php b/src/Forms/Select.php index fdba016..0ebeaf0 100644 --- a/src/Forms/Select.php +++ b/src/Forms/Select.php @@ -19,7 +19,7 @@ class Select implements Stringable /** * @var list */ - private array $wrapperProperties = []; + private array $containerProperties = []; /** * @var list @@ -76,12 +76,18 @@ public function id(string $id): self * Sets properties on the outermost div in dropdown/multiple modes, * or the fieldset in radio/checkbox modes, not on member controls. */ - public function wrapperProps(string ...$properties): self + public function containerProps(string ...$properties): self { - $this->wrapperProperties = array_values($properties); + $this->containerProperties = array_values($properties); return $this; } + #[\Deprecated(message: 'Use containerProps() instead.', since: '5.0')] + public function wrapperProps(string ...$properties): self + { + return $this->containerProps(...$properties); + } + public function labelProps(string ...$properties): self { $this->labelProperties = array_values($properties); @@ -239,7 +245,7 @@ private function selectDropdown( ]), $select, ...[...$this->content, $warning] - )->props(...$this->wrapperProperties); + )->props(...$this->containerProperties); } /** @@ -288,6 +294,6 @@ private function selectGroup( return Element::fieldset( Element::legend($this->label)->props(...$this->labelProperties), ...[...$members, ...$this->content, $warning] - )->props(...$this->wrapperProperties); + )->props(...$this->containerProperties); } } diff --git a/src/Forms/TextArea.php b/src/Forms/TextArea.php index a2d69e3..858f6df 100644 --- a/src/Forms/TextArea.php +++ b/src/Forms/TextArea.php @@ -15,7 +15,7 @@ class TextArea implements Stringable /** * @var list */ - private array $wrapperProperties = []; + private array $containerProperties = []; /** * @var list @@ -62,12 +62,18 @@ public function id(string $id): self /** * Sets properties on this component's outermost div, not the textarea. */ - public function wrapperProps(string ...$properties): self + public function containerProps(string ...$properties): self { - $this->wrapperProperties = array_values($properties); + $this->containerProperties = array_values($properties); return $this; } + #[\Deprecated(message: 'Use containerProps() instead.', since: '5.0')] + public function wrapperProps(string ...$properties): self + { + return $this->containerProps(...$properties); + } + public function labelProps(string ...$properties): self { $this->labelProperties = array_values($properties); @@ -136,6 +142,6 @@ public function __toString(): string ]), $textarea, ...[...$this->content, $warning] - )->props(...$this->wrapperProperties); + )->props(...$this->containerProperties); } } diff --git a/src/Forms/TextInput.php b/src/Forms/TextInput.php index 1d81ab0..4b01b3a 100644 --- a/src/Forms/TextInput.php +++ b/src/Forms/TextInput.php @@ -15,7 +15,7 @@ class TextInput implements Stringable /** * @var list */ - private array $wrapperProperties = []; + private array $containerProperties = []; /** * @var list @@ -59,12 +59,18 @@ public function id(string $id): self /** * Sets properties on this component's outermost div, not the input. */ - public function wrapperProps(string ...$properties): self + public function containerProps(string ...$properties): self { - $this->wrapperProperties = array_values($properties); + $this->containerProperties = array_values($properties); return $this; } + #[\Deprecated(message: 'Use containerProps() instead.', since: '5.0')] + public function wrapperProps(string ...$properties): self + { + return $this->containerProps(...$properties); + } + public function labelProps(string ...$properties): self { $this->labelProperties = array_values($properties); @@ -133,6 +139,6 @@ public function __toString(): string ]), $input, ...[...$this->content, $warning] - )->props(...$this->wrapperProperties); + )->props(...$this->containerProperties); } } diff --git a/tests/Forms/ContainerPropsTest.php b/tests/Forms/ContainerPropsTest.php new file mode 100644 index 0000000..1bab6a8 --- /dev/null +++ b/tests/Forms/ContainerPropsTest.php @@ -0,0 +1,68 @@ + */ + public static function components(): iterable + { + foreach (['text', 'email', 'password', 'textarea', 'fieldset', 'dropdown', 'multiple', 'radio', 'checkbox'] as $mode) { + yield $mode => [$mode]; + } + } + + #[Test] + #[DataProvider('components')] + public function deprecated_alias_preserves_fluent_return_and_rendered_output(string $mode): void + { + $component = match ($mode) { + 'text' => TextInput::create('Field', 'field'), + 'email' => EmailInput::create('Field', 'field'), + 'password' => Password::create('Field', 'field'), + 'textarea' => TextArea::create('Field', 'field'), + 'fieldset' => Fieldset::create('Field', 'field'), + default => Select::create('Field', 'field', ['a' => 'A'])->$mode() + }; + $properties = ['class container', 'data-example preserved']; + $modern = clone $component; + parent::assertSame($modern, $modern->containerProps(...$properties)); + $attribute = new ReflectionMethod($component, 'wrapperProps') + ->getAttributes(Deprecated::class)[0]->newInstance(); + parent::assertSame('Use containerProps() instead.', $attribute->message); + parent::assertSame('5.0', $attribute->since); + + $deprecations = []; + set_error_handler(static function (int $severity, string $message) use (&$deprecations): bool { + if ($severity !== E_USER_DEPRECATED) { + return false; + } + $deprecations[] = $message; + return true; + }); + try { + $result = $component->wrapperProps(...$properties); + } finally { + restore_error_handler(); + } + + parent::assertSame($component, $result, 'The deprecated alias must return containerProps() results.'); + parent::assertSame((string) $modern, (string) $component, 'Renaming must not change markup.'); + parent::assertCount(1, $deprecations, 'The native attribute must emit one deprecation per call.'); + parent::assertStringContainsString('Use containerProps() instead.', $deprecations[0]); + } +} diff --git a/tests/Forms/EmailInputTest.php b/tests/Forms/EmailInputTest.php index e37d1fc..3eab6ad 100644 --- a/tests/Forms/EmailInputTest.php +++ b/tests/Forms/EmailInputTest.php @@ -25,7 +25,7 @@ public function can_set_custom_id(): void // phpcs:ignore warningMessage: 'Invalid value' )->id('email-input-id')->content( Element::p('Helpful text')->props('id email-input-id-help') - )->wrapperProps('is form-control'); + )->containerProps('is form-control'); parent::assertSame($expected, $result); } diff --git a/tests/Forms/SelectTest.php b/tests/Forms/SelectTest.php index fa71f59..69c645e 100644 --- a/tests/Forms/SelectTest.php +++ b/tests/Forms/SelectTest.php @@ -102,7 +102,7 @@ public function native_attributes_target_controls_and_preserve_generated_identit 'aria-describedby category-help', ...($isGroup ? [] : ['size 4']) )->content(Element::p('Help')->props('id category-help')) - ->wrapperProps('class wrapper')->labelProps('class caption') + ->containerProps('class wrapper')->labelProps('class caption') ); $controls = $xpath->query('//*[local-name()="input" or local-name()="select"]'); parent::assertNotFalse($controls); @@ -193,7 +193,7 @@ public function group_disabling_and_required_attributes_follow_native_semantics( { $control = Select::create('Categories', 'categories', ['a' => 'A', 'b' => 'B']); $xpath = self::controlXpath( - $control->$mode()->wrapperProps('disabled disabled')->inputProps('required required') + $control->$mode()->containerProps('disabled disabled')->inputProps('required required') ); $isGroup = in_array($mode, ['radio', 'checkbox'], true); parent::assertSame($isGroup ? 1 : 0, $xpath->query('//*[local-name()="fieldset" and @disabled]')->length); @@ -437,7 +437,7 @@ public function can_add_properties_to_wrapper(): void // phpcs: ignore [ 'value' => 'display' ] - )->wrapperProps('id some-id', 'class some-token'); + )->containerProps('id some-id', 'class some-token'); parent::assertSame($expected, $result); } diff --git a/tests/Forms/TextAreaTest.php b/tests/Forms/TextAreaTest.php index c33d5a1..8d11449 100644 --- a/tests/Forms/TextAreaTest.php +++ b/tests/Forms/TextAreaTest.php @@ -124,7 +124,7 @@ public function native_properties_and_custom_identity_target_the_textarea(): voi 'required required', 'readonly readonly', 'disabled disabled' - )->wrapperProps('class notes-wrapper')->labelProps('class notes-label'); + )->containerProps('class notes-wrapper')->labelProps('class notes-label'); $xpath = self::xpath($html); $textarea = $xpath->query('//*[local-name()="textarea"]')->item(0); parent::assertInstanceOf(DomElement::class, $textarea); diff --git a/tests/Forms/TextInputTest.php b/tests/Forms/TextInputTest.php index 6f8fba6..4f4c9b4 100644 --- a/tests/Forms/TextInputTest.php +++ b/tests/Forms/TextInputTest.php @@ -25,7 +25,7 @@ public function can_set_custom_id(): void // phpcs:ignore warningMessage: 'Invalid value' )->id('text-input-id')->content( Element::p('Helpful text')->props('id text-input-id-help') - )->wrapperProps('is form-control'); + )->containerProps('is form-control'); parent::assertSame($expected, $result); }