Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,22 @@
- Bug #42: Fix messages in `assert()` methods and code style (@terabytesoftw)
- Bug #43: Update `ui-awesome/html-helper` to version `^0.7` and `ui-awesome/html-mixin` to version `^0.4` in `composer.json` and apply necessary changes to `src` and `tests` directories (@terabytesoftw)
- Bug #44: Update last modified from `ui-awesome/html-attribute` in related classes (@terabytesoftw)
- Bug #45: Better naming for `CanBeUnchecked` to `HasUnchecked` and update PHPDoc `BaseChoice` classes (@terabytesoftw)
- Bug #45: Better naming for `CanBeUnchecked` to `HasUnchecked` and update phpdoc `BaseChoice` classes (@terabytesoftw)
- Enh #46: Add `InputFile` class for HTML `<input type="file">` element with attributes and rendering capabilities (@terabytesoftw)
- Bug #47: Update PHPDoc in `tests` and add new tests for `on*` attribute (@terabytesoftw)
- Enh #48: Add `InputEmail` class for HTML `<input type="email">` element with attributes and rendering capabilities (@terabytesoftw)
- Enh #49: Add `InputDateTimeLocal` class for HTML `<input type="datetime-local">` element with attributes and rendering capabilities (@terabytesoftw)
- Bug #50: Standardize `tests` for clarity and consistency across all test cases (@terabytesoftw)
- Enh #51: Add `InputDate` class for HTML `<input type="date">` element with attributes and rendering capabilities (@terabytesoftw)
- Enh #52: Add `InputColor` class for HTML `<input type="color">` element with attributes and rendering capabilities (@terabytesoftw)
- Enh #53: Add `TextArea` class for HTML `<textarea>` element with attributes and rendering capabilities (@terabytesoftw)
- Bug #54: Update exception test method names for consistency and clarity (@terabytesoftw)
- Bug #55: Remove `BaseChoice` class and related mixins; update `InputCheckbox`, `InputFile`, and `InputRadio` to extend `BaseInput` class (@terabytesoftw)
- Enh #56: Add `Button` class for HTML `<button>` element with attributes and rendering capabilities (@terabytesoftw)
- Bug #57: Add tests for invalid argument exceptions in HTML attributes (@terabytesoftw)
- Bug #49: Update PHPDoc in `src` and add new tests for `on*` attribute (@terabytesoftw)
- Enh #50: Add `InputDateTimeLocal` class for HTML `<input type="datetime-local">` element with attributes and rendering capabilities (@terabytesoftw)
- Bug #51: Standardize `tests` for clarity and consistency across all test cases (@terabytesoftw)
- Enh #52: Add `InputDate` class for HTML `<input type="date">` element with attributes and rendering capabilities (@terabytesoftw)
- Enh #53: Add `InputColor` class for HTML `<input type="color">` element with attributes and rendering capabilities (@terabytesoftw)
- Enh #54: Add `TextArea` class for HTML `<textarea>` element with attributes and rendering capabilities (@terabytesoftw)
- Bug #55: Update exception test method names for consistency and clarity (@terabytesoftw)
- Bug #56: Remove `BaseChoice` class and related mixins; update `InputCheckbox`, `InputFile`, and `InputRadio` to extend `BaseInput` class (@terabytesoftw)
- Enh #57: Add `Button` class for HTML `<button>` element with attributes and rendering capabilities (@terabytesoftw)
- Bug #58: Add tests for invalid argument exceptions in HTML attributes (@terabytesoftw)
- Enh #59: Add `Form` class for HTML `<form>` element with attributes and rendering capabilities (@terabytesoftw)
- Enh #60: Add `ariaDescribedBySuffix` property and update `getAttributes` method for improved accessibility support (@terabytesoftw)
- Bug #61: Align tag enums and defaults provider with latest `ui-awesome/html-core` and `ui-awesome/html-interop` changes (@terabytesoftw)

## 0.3.0 March 31, 2024
Expand Down
46 changes: 46 additions & 0 deletions src/Form/Button.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,52 @@ final class Button extends BaseInline
use HasTabindex;
use HasValue;

/**
* Suffix appended to the element `id` when `aria-describedby` is set to `true`.
*
* An empty string falls back to `'-help'` during rendering.
*/
protected string $ariaDescribedBySuffix = '';

/**
* Sets the suffix appended to the element `id` when `aria-describedby` is set to `true`.
*
* @param string $value Suffix to append to the element `id` for `aria-describedby`. Defaults to `'-help'`.
*
* @return static New instance with the updated `ariaDescribedBySuffix` value.
*/
public function ariaDescribedBySuffix(string $value): static
{
$new = clone $this;
$new->ariaDescribedBySuffix = $value;

return $new;
}

/**
* Returns the array of HTML attributes for the element.
*
* @return array Attributes array assigned to the element.
*
* @phpstan-return mixed[]
*/
public function getAttributes(): array
{
$attributes = parent::getAttributes();

/** @phpstan-var string|null $id */
$id = $this->getAttribute('id', null);
$ariaDescribedBy = $this->getAttribute('aria-describedby', null);

$ariaDescribedBySuffix = $this->ariaDescribedBySuffix === '' ? '-help' : "-{$this->ariaDescribedBySuffix}";

if ($ariaDescribedBy === true || $ariaDescribedBy === 'true') {
$attributes['aria-describedby'] = $id !== null ? "{$id}{$ariaDescribedBySuffix}" : null;
Comment on lines +96 to +103
Copy link

@coderabbitai coderabbitai bot Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize empty/whitespace id before composing aria-describedby.

If id is '' (or whitespace), the current branch generates values like -help, which do not reference a valid element.

🛠️ Suggested fix
 /** `@phpstan-var` string|null $id */
 $id = $this->getAttribute('id', null);
+$id = is_string($id) && trim($id) === '' ? null : $id;
 $ariaDescribedBy = $this->getAttribute('aria-describedby', null);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** @phpstan-var string|null $id */
$id = $this->getAttribute('id', null);
$ariaDescribedBy = $this->getAttribute('aria-describedby', null);
$ariaDescribedBySuffix = $this->ariaDescribedBySuffix === '' ? '-help' : "-{$this->ariaDescribedBySuffix}";
if ($ariaDescribedBy === true || $ariaDescribedBy === 'true') {
$attributes['aria-describedby'] = $id !== null ? "{$id}{$ariaDescribedBySuffix}" : null;
/** `@phpstan-var` string|null $id */
$id = $this->getAttribute('id', null);
$id = is_string($id) && trim($id) === '' ? null : $id;
$ariaDescribedBy = $this->getAttribute('aria-describedby', null);
$ariaDescribedBySuffix = $this->ariaDescribedBySuffix === '' ? '-help' : "-{$this->ariaDescribedBySuffix}";
if ($ariaDescribedBy === true || $ariaDescribedBy === 'true') {
$attributes['aria-describedby'] = $id !== null ? "{$id}{$ariaDescribedBySuffix}" : null;
🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 97-97: Avoid variables with short names like $id. Configured minimum length is 3. (undefined)

(ShortVariable)


[warning] 100-100: Avoid excessively long variable names like $ariaDescribedBySuffix. Keep variable name length under 20. (undefined)

(LongVariable)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Form/Button.php` around lines 96 - 103, The code composes
aria-describedby using $id without normalizing empty/whitespace IDs, which
produces invalid values like "-help"; update the logic around getAttribute('id',
null) so $id is normalized (trimmed) and treated as null when it is an empty
string or only whitespace before building $ariaDescribedBySuffix and before
setting $attributes['aria-describedby'] in the branch that checks
$ariaDescribedBy; adjust the condition that assigns
"{$id}{$ariaDescribedBySuffix}" to use the normalized $id (or yield null) and
keep references to getAttribute, $id, $ariaDescribedBySuffix and the
aria-describedby assignment to locate and change the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the HasAttributes.php file from the html-mixin repository to understand the context
curl -s "https://raw.githubusercontent.com/ui-awesome/html-mixin/1c65697ab0bbd6e4e1aa153abd251d8c50ff521b/src/HasAttributes.php" | head -n 100

Length of output: 2868


🏁 Script executed:

#!/bin/bash
# Fetch the AttributeBag class to understand how get() handles empty strings
curl -s "https://raw.githubusercontent.com/ui-awesome/html/main/src/Helper/AttributeBag.php" | grep -A 30 "public static function get"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find AttributeBag in the current repository
fd -t f "AttributeBag.php" --exec cat {}

Length of output: 0


@terabytesoftw, I see you're referencing the getAttribute method implementation. You're right that getAttribute('id', null) delegates to AttributeBag::get() and will return the actual value stored in the attributes array.

The edge case CodeRabbit highlighted is when id is explicitly set to an empty string '' (rather than null or unset). In that scenario:

$id = $this->getAttribute('id', null);  // Returns ''
// ...
if ($ariaDescribedBy === true || $ariaDescribedBy === 'true') {
    $attributes['aria-describedby'] = $id !== null ? "{$id}{$ariaDescribedBySuffix}" : null;
    // Produces: '' . '-help' = '-help'  (invalid reference)
}

Is this edge case already prevented elsewhere (e.g., does setAttribute('id', '') normalize to null), or would you prefer to add the normalization check as CodeRabbit suggested?


🧠 Learnings used
Learnt from: terabytesoftw
Repo: ui-awesome/html PR: 58
File: tests/Form/InputSubmitTest.php:995-1077
Timestamp: 2026-02-22T19:38:01.916Z
Learning: In the ui-awesome/html repository, the maintainer prefers to include InvalidArgumentException validation tests for trait-based attributes (HasDir, HasLang, HasRole, HasTabIndex, HasTranslate, HasType, etc.) in each element's test file, even though these traits are tested in the html-attribute package. The maintainer values having this test knowledge explicitly visible in each element's test suite because "test knowledge is lost" when relying solely on trait package tests—this explicit coverage is considered "a skill for AI" analysis and developer reference.

Learnt from: terabytesoftw
Repo: ui-awesome/html PR: 58
File: tests/List/DdTest.php:751-847
Timestamp: 2026-02-22T19:29:56.689Z
Learning: In the ui-awesome/html repository, tests for InvalidArgumentException validation of trait-based attributes (from html-attribute package) should be included in element test files even though the underlying trait behavior is tested in the html-attribute package. The maintainer values having this test knowledge explicitly visible in each element's test suite for AI analysis and developer reference.

Learnt from: terabytesoftw
Repo: ui-awesome/html PR: 59
File: tests/Form/FormTest.php:980-1132
Timestamp: 2026-02-22T20:57:07.467Z
Learning: In the ui-awesome/html repository, the `HasAutocomplete` trait (from the html-attribute package) does not validate input values and accepts any string without throwing InvalidArgumentException. Therefore, no `testThrowInvalidArgumentExceptionWhenSettingAutocomplete` test should be added. Only traits that use `Validator::oneOf()` (like HasEnctype, HasMethod, etc.) require InvalidArgumentException tests.

Learnt from: terabytesoftw
Repo: ui-awesome/html PR: 31
File: src/Form/Mixin/HasLabel.php:195-195
Timestamp: 2026-02-07T18:51:41.006Z
Learning: In the ui-awesome/html repository, explicit boolean comparisons (e.g., `isset($var) === false`) are required instead of negation operators (e.g., `!isset($var)`) due to PHPStan strict rules configuration at the highest level.

Learnt from: terabytesoftw
Repo: ui-awesome/html PR: 58
File: tests/Sectioning/AsideTest.php:821-833
Timestamp: 2026-02-22T19:26:33.769Z
Learning: In the ui-awesome/html repository, do not use import aliases in PHP. When there are naming conflicts (e.g., two different Message classes), prefer the fully qualified class name (FQCN) such as \UIAwesome\Html\Attribute\Exception\Message::ATTRIBUTE_INVALID_VALUE instead of creating an alias like "use UIAwesome\Html\Attribute\Exception\Message as AttributeMessage". This should apply to PHP files across the codebase (not just tests).

}

return $attributes;
}

/**
* Sets the `type` attribute.
*
Expand Down
46 changes: 46 additions & 0 deletions src/Form/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,52 @@ final class Form extends BaseBlock
use HasRel;
use HasTarget;

/**
* Suffix appended to the element `id` when `aria-describedby` is set to `true`.
*
* An empty string falls back to `'-help'` during rendering.
*/
protected string $ariaDescribedBySuffix = '';

/**
* Sets the suffix appended to the element `id` when `aria-describedby` is set to `true`.
*
* @param string $value Suffix to append to the element `id` for `aria-describedby`. Defaults to `'-help'`.
*
* @return static New instance with the updated `ariaDescribedBySuffix` value.
*/
public function ariaDescribedBySuffix(string $value): static
{
$new = clone $this;
$new->ariaDescribedBySuffix = $value;

return $new;
}

/**
* Returns the array of HTML attributes for the element.
*
* @return array Attributes array assigned to the element.
*
* @phpstan-return mixed[]
*/
public function getAttributes(): array
{
$attributes = parent::getAttributes();

/** @phpstan-var string|null $id */
$id = $this->getAttribute('id', null);
$ariaDescribedBy = $this->getAttribute('aria-describedby', null);

$ariaDescribedBySuffix = $this->ariaDescribedBySuffix === '' ? '-help' : "-{$this->ariaDescribedBySuffix}";

if ($ariaDescribedBy === true || $ariaDescribedBy === 'true') {
$attributes['aria-describedby'] = $id !== null ? "{$id}{$ariaDescribedBySuffix}" : null;
}
Comment on lines +78 to +86
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against empty IDs when composing aria-describedby.

If id is an empty/whitespace string, the current logic still emits aria-describedby="-help", which won’t point to a real element. Treat empty strings as missing before composing the value.

🛠️ Suggested fix
         /** `@phpstan-var` string|null $id */
         $id = $this->getAttribute('id', null);
+        $id = is_string($id) && trim($id) === '' ? null : $id;
🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 79-79: Avoid variables with short names like $id. Configured minimum length is 3. (undefined)

(ShortVariable)


[warning] 82-82: Avoid excessively long variable names like $ariaDescribedBySuffix. Keep variable name length under 20. (undefined)

(LongVariable)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Form/Form.php` around lines 78 - 86, The current logic in Form.php uses
the raw $id from getAttribute('id') when composing aria-describedby and can
produce values like "-help" for empty or whitespace-only IDs; before composing
(where $id is used with $ariaDescribedBySuffix and assigned to
$attributes['aria-describedby']), normalize and guard $id by treating empty or
whitespace-only strings as null (e.g., trim and check === ''), then only set
aria-describedby when $id is non-null/non-empty; update the code paths around
the $id variable, the ariaDescribedBySuffix computation, and the block that
assigns $attributes['aria-describedby'] so empty IDs do not produce invalid
descriptors.


return $attributes;
}

/**
* Returns the tag enumeration for the `<form>` element.
*
Expand Down
46 changes: 46 additions & 0 deletions src/Form/TextArea.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,52 @@ final class TextArea extends BaseBlock
use HasRows;
use HasWrap;

/**
* Suffix appended to the element `id` when `aria-describedby` is set to `true`.
*
* An empty string falls back to `'-help'` during rendering.
*/
protected string $ariaDescribedBySuffix = '';

/**
* Sets the suffix appended to the element `id` when `aria-describedby` is set to `true`.
*
* @param string $value Suffix to append to the element `id` for `aria-describedby`. Defaults to `'-help'`.
*
* @return static New instance with the updated `ariaDescribedBySuffix` value.
*/
public function ariaDescribedBySuffix(string $value): static
{
$new = clone $this;
$new->ariaDescribedBySuffix = $value;

return $new;
}

/**
* Returns the array of HTML attributes for the element.
*
* @return array Attributes array assigned to the element.
*
* @phpstan-return mixed[]
*/
public function getAttributes(): array
{
$attributes = parent::getAttributes();

/** @phpstan-var string|null $id */
$id = $this->getAttribute('id', null);
$ariaDescribedBy = $this->getAttribute('aria-describedby', null);

$ariaDescribedBySuffix = $this->ariaDescribedBySuffix === '' ? '-help' : "-{$this->ariaDescribedBySuffix}";

if ($ariaDescribedBy === true || $ariaDescribedBy === 'true') {
$attributes['aria-describedby'] = $id !== null ? "{$id}{$ariaDescribedBySuffix}" : null;
}

return $attributes;
}

/**
* Returns the tag enumeration for the `<textarea>` element.
*
Expand Down
Loading
Loading