Skip to content

Commit

Permalink
Add aria-describedby support to input elements. (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
terabytesoftw committed Dec 21, 2023
1 parent c6d2484 commit e548561
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 26 deletions.
4 changes: 2 additions & 2 deletions src/Attribute/Aria/HasAriaDescribedBy.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ trait HasAriaDescribedBy
* descriptive elements. This helps screen readers and other assistive technologies provide additional context
* about the element.
*
* @param string $value IDs of the descriptive element(s) separated by spaces.
* @param bool|string $value IDs of the descriptive element(s) separated by spaces.
*
* @return static A new instance or clone of the current object with the aria-describedby attribute set.
*
* @link https://www.w3.org/TR/wai-aria-1.1/#aria-describedby
*/
public function ariaDescribedBy(string $value): static
public function ariaDescribedBy(string|bool $value = true): static
{
$new = clone $this;
$new->attributes['aria-describedby'] = $value;
Expand Down
22 changes: 16 additions & 6 deletions src/Input/Base/AbstractButton.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

abstract class AbstractButton extends Element implements InputInterface
{
use Attribute\Aria\HasAriaDescribedBy;
use Attribute\Aria\HasAriaLabel;
use Attribute\CanBeHidden;
use Attribute\Custom\HasAttributes;
use Attribute\Custom\HasContainer;
Expand All @@ -38,7 +40,8 @@ abstract class AbstractButton extends Element implements InputInterface

protected function run(): string
{
$buttonInput = Input::widget()->attributes($this->attributes)->id($this->id);
$attributes = $this->attributes;
$type = $this->attributes['type'] ?? 'button';
$value = $this->attributes['value'] ?? null;

/**
Expand All @@ -50,13 +53,20 @@ protected function run(): string
);
}

if (array_key_exists('type', $this->attributes) === false) {
$buttonInput = $buttonInput->type('button');
$id = $this->generateId("$type-");

$ariaDescribedBy = $this->attributes['aria-describedby'] ?? null;

if ($ariaDescribedBy === true) {

Check warning on line 60 in src/Input/Base/AbstractButton.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "TrueValue": --- Original +++ New @@ @@ } $id = $this->generateId("{$type}-"); $ariaDescribedBy = $this->attributes['aria-describedby'] ?? null; - if ($ariaDescribedBy === true) { + if ($ariaDescribedBy === false) { $attributes['aria-describedby'] = "{$id}-help"; } $buttonInput = Input::widget()->attributes($attributes)->id($id)->type($type)->value($value);
$attributes['aria-describedby'] = "$id-help";
}

$buttonInput = $buttonInput->value($value);
$labelFor = isset($this->labelAttributes['for']) && is_string($this->labelAttributes['for'])
? $this->labelAttributes['for'] : $this->id;
$buttonInput = Input::widget()->attributes($attributes)->id($id)->type($type)->value($value);
$labelFor = $id;

if (array_key_exists('for', $this->labelAttributes)) {
$labelFor = (string) $this->labelAttributes['for'];
}

$buttonTag = match ($this->labelContent !== '') {
true => $buttonInput
Expand Down
42 changes: 28 additions & 14 deletions src/Input/Base/AbstractChoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ abstract class AbstractChoice extends Element implements InputInterface, LabelIn
protected function run(): string
{
$attributes = $this->attributes;
$uncheckTag = '';
$value = $attributes['value'] ?? null;

/**
Expand All @@ -67,28 +66,43 @@ protected function run(): string
}

$value = is_bool($value) ? (int) $value : $value;

unset($attributes['value']);

if ($this->uncheckValue !== null) {
$uncheckTag = Hidden::widget()
->attributes($this->uncheckAttributes)
->id(null)
->value($this->uncheckValue)
->render();
}
$id = $this->generateId("$this->type-");

$attributes['checked'] = match (empty($this->checkedValue)) {
true => $this->checked,
default => $value === $this->checkedValue,
};

$inputCheckboxTag = $this->renderInputCheckboxTag($attributes, $uncheckTag, $value);
$ariaDescribedBy = $this->attributes['aria-describedby'] ?? null;

if ($ariaDescribedBy === true) {
$attributes['aria-describedby'] = "$id-help";
}

$inputCheckboxTag = $this->renderInputCheckboxTag($attributes, $id, $this->generateUncheckTag(), $value);

if ($this->isNotLabel() === false) {
$inputCheckboxTag = $this->renderLabelTag($inputCheckboxTag);
}

return $this->renderContainerTag($inputCheckboxTag);
}

private function generateUncheckTag(): string
{
if ($this->uncheckValue === null) {
return '';
}

return Hidden::widget()
->attributes($this->uncheckAttributes)
->id(null)
->value($this->uncheckValue)
->render();
}

private function renderContainerTag(Label|Tag $inputCheckboxTag): string
{
return match ($this->container) {
true => Tag::widget()
->attributes($this->containerAttributes)
Expand All @@ -111,11 +125,11 @@ private function renderLabelTag(Tag $inputCheckboxTag): Label|Tag
->for($inputCheckboxTag->getId());
}

private function renderInputCheckboxTag(array $attributes, string $uncheckTag, mixed $value): Tag
private function renderInputCheckboxTag(array $attributes, string|null $id, string $uncheckTag, mixed $value): Tag
{
return Tag::widget()
->attributes($attributes)
->id($this->generateId("$this->type-"))
->id($id)
->prefix($this->prefix, $uncheckTag)
->prefixContainer($this->prefixContainer)
->prefixContainerAttributes($this->prefixContainerAttributes)
Expand Down
12 changes: 10 additions & 2 deletions src/Input/Base/AbstractInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,19 @@ abstract class AbstractInput extends Element implements InputInterface, Placehol

protected function run(): string
{
$attributes = $this->attributes;
$type = $this->attributes['type'] ?? $this->type;

$ariaDescribedBy = $attributes['aria-describedby'] ?? null;
$id = $this->generateId("$this->type-");

if ($ariaDescribedBy === true) {
$attributes['aria-describedby'] = "$id-help";
}

return Tag::widget()
->attributes($this->attributes)
->id($this->generateId("$this->type-"))
->attributes($attributes)
->id($id)
->prefix($this->prefix)
->prefixContainer($this->prefixContainer)
->prefixContainerAttributes($this->prefixContainerAttributes)
Expand Down
78 changes: 76 additions & 2 deletions src/Input/InputInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,93 @@

namespace PHPForge\Html\Input;

/**
* Provide methods for handling HTML input-related attributes and properties.
*/
interface InputInterface
{
/**
* Set the aria-describedby attribute, which identifies the element(s) that describe the current element.
*
* The aria-describedby attribute is used in WAI-ARIA to provide a relationship between an element and its
* descriptive elements. This helps screen readers and other assistive technologies provide additional context
* about the element.
*
* @param string $value IDs of the descriptive element(s) separated by spaces.
*
* @return static A new instance or clone of the current object with the aria-describedby attribute set.
*
* @link https://www.w3.org/TR/wai-aria-1.1/#aria-describedby
*/
public function ariaDescribedBy(string $value): static;

/**
* Set the `HTML` attributes.
*
* @param array $values Attribute values indexed by attribute names.
*
* @return static A new instance of the current class with the specified attributes.
*/
public function attributes(array $values): static;

public function class(string $value): static;
/**
* Set the `CSS` `HTML` class attribute.
*
* @param string $value The `CSS` attribute of the widget.
* @param bool $override If `true` the value will be overridden.
*
* @return static A new instance of the current class with the specified class value.
*
* @link https://html.spec.whatwg.org/#classes
*/
public function class(string $value, bool $override = false): static;

/**
* Get the ID of the widget.
*
* @return string|null The ID of the widget.
*/
public function getId(): string|null;

/**
* Set the ID of the widget.
*
* @param string|null $value The ID of the widget.
*
* @return static A new instance of the current class with the specified ID value.
*
* @link https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute
*/
public function id(string|null $value): static;

public function name(string $value): static;
/**
* Set the name part of the name/value pair associated with this element for the purposes of form submission.
*
* @param string|null $value The name of the widget.
*
* @return static A new instance of the current class with the specified name.
*
* @link https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-name
*/
public function name(string|null $value): static;

/**
* Executes the widget.
*
* This method is responsible for executing the widget and returning the result of the execution as a string.
*
* @return string The result of widget execution to be outputted.
*/
public function render(): string;

/**
* set the value content attribute gives the default value of the field.
*
* @param mixed $value The value of the widget.
*
* @return static A new instance of the current class with the specified value.
*
* @link https://html.spec.whatwg.org/multipage/input.html#attr-input-value
*/
public function value(mixed $value): static;
}
3 changes: 3 additions & 0 deletions src/Input/LabelInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

use PHPForge\Widget\ElementInterface;

/**
* Provide methods for handling HTML label-related attributes and properties.
*/
interface LabelInterface
{
/**
Expand Down
18 changes: 18 additions & 0 deletions src/Input/PlaceholderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,25 @@

namespace PHPForge\Html\Input;

/**
* Provide methods for handling HTML placeholder-related attributes and properties.
*/
interface PlaceholderInterface
{
/**
* Set the placeholder attribute valid for text, search, url, tel, email, password, and number, provides a brief
* hint to the user as to what kind of information is expected in the field.
*
* It should be a word or short phrase that provides a hint as to the expected type of data, rather than an
* explanation or prompt.
*
* The text must not include carriage returns or line feeds.
*
* @param string $value The placeholder text.
*
* @return static A new instance of the current class with the specified placeholder text.
*
* @link https://html.spec.whatwg.org/multipage/input.html#the-placeholder-attribute
*/
public function placeholder(string $value): static;
}
36 changes: 36 additions & 0 deletions tests/Input/Button/RenderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@
*/
final class RenderTest extends TestCase
{
public function testAriaDescribedBy(): void
{
Assert::equalsWithoutLE(
<<<HTML
<div>
<input id="text-6582f2d099e8b" type="button" aria-describedby="MyWidget">
</div>
HTML,
Button::widget()->ariaDescribedBy('MyWidget')->id('text-6582f2d099e8b')->render()
);
}

public function testAriaLabel(): void
{
Assert::equalsWithoutLE(
<<<HTML
<div>
<input id="text-6582f2d099e8b" type="button" aria-label="MyWidget">
</div>
HTML,
Button::widget()->ariaLabel('MyWidget')->id('text-6582f2d099e8b')->render()
);
}

public function testAttribute(): void
{
Assert::equalsWithoutLE(
Expand Down Expand Up @@ -105,6 +129,18 @@ public function testForm(): void
);
}

public function testGenerateAriaDescribeBy(): void
{
Assert::equalsWithoutLE(
<<<HTML
<div>
<input id="text-6582f2d099e8b" type="button" aria-describedby="text-6582f2d099e8b-help">
</div>
HTML,
Button::widget()->ariaDescribedBy()->id('text-6582f2d099e8b')->render()
);
}

public function testHidden(): void
{
Assert::equalsWithoutLE(
Expand Down
10 changes: 10 additions & 0 deletions tests/Input/Checkbox/RenderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ public function testForm(): void
);
}

public function testGenerateAriaDescribeBy(): void
{
Assert::equalsWithoutLE(
<<<HTML
<input id="text-6582f2d099e8b" type="checkbox" aria-describedby="text-6582f2d099e8b-help">
HTML,
Checkbox::widget()->ariaDescribedBy()->id('text-6582f2d099e8b')->render()
);
}

public function testHidden(): void
{
Assert::equalsWithoutLE(
Expand Down
10 changes: 10 additions & 0 deletions tests/Input/RenderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ public function testForm(): void
);
}

public function testGenerateAriaDescribeBy(): void
{
Assert::equalsWithoutLE(
<<<HTML
<input id="text-6582f2d099e8b" type="text" aria-describedby="text-6582f2d099e8b-help">
HTML,
Input::widget()->ariaDescribedBy()->id('text-6582f2d099e8b')->render()
);
}

public function testHidden(): void
{
Assert::equalsWithoutLE(
Expand Down

0 comments on commit e548561

Please sign in to comment.