diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml
index afa74ac1310..bfd7f2523fc 100644
--- a/.github/workflows/unit-tests.yaml
+++ b/.github/workflows/unit-tests.yaml
@@ -65,7 +65,7 @@ jobs:
# Exclude deprecated packages when testing against lowest dependencies
if [ "${{ matrix.dependency-version }}" = "lowest" ]; then
- EXCLUDED_PACKAGES="$EXCLUDED_PACKAGES|LazyImage"
+ EXCLUDED_PACKAGES="$EXCLUDED_PACKAGES|LazyImage|TogglePassword"
fi
PACKAGES=$(find src/ -mindepth 2 -type f -name composer.json -not -path "*/vendor/*" -printf '%h\n' | sed 's/^src\///' | grep -Ev "$EXCLUDED_PACKAGES" | sort | tr '\n' ' ')
diff --git a/src/TogglePassword/CHANGELOG.md b/src/TogglePassword/CHANGELOG.md
index ae644812580..ee855b97a5e 100644
--- a/src/TogglePassword/CHANGELOG.md
+++ b/src/TogglePassword/CHANGELOG.md
@@ -1,5 +1,9 @@
# CHANGELOG
+## 2.29.0
+
+- Deprecate the package
+
## 2.13.2
- Revert "Change JavaScript package to `type: module`"
diff --git a/src/TogglePassword/README.md b/src/TogglePassword/README.md
index 90945a31069..e2311852b7f 100644
--- a/src/TogglePassword/README.md
+++ b/src/TogglePassword/README.md
@@ -1,5 +1,231 @@
# Symfony UX TogglePassword
+> [!WARNING]
+> **Deprecated**: This package has been **deprecated** in 2.x and will be removed in the next major version.
+
+To keep the same functionality in your Symfony application, follow these migration steps:
+
+1. Remove the `symfony/ux-toggle-password` package from your project:
+```bash
+composer remove symfony/ux-toggle-password
+```
+
+2. Create the following files in your project:
+
+> [!NOTE]
+> These files are provided as a reference.
+> You can customize them to fit your needs, and even simplify the implementation if you don't need all the features.
+
+ - `src/Form/Extension/TogglePasswordTypeExtension.php`
+```php
+setDefaults([
+ 'toggle' => false,
+ 'hidden_label' => 'Hide',
+ 'visible_label' => 'Show',
+ 'hidden_icon' => 'Default',
+ 'visible_icon' => 'Default',
+ 'button_classes' => ['toggle-password-button'],
+ 'toggle_container_classes' => ['toggle-password-container'],
+ 'toggle_translation_domain' => null,
+ 'use_toggle_form_theme' => true,
+ ]);
+
+ $resolver->setNormalizer(
+ 'toggle_translation_domain',
+ static fn (Options $options, $labelTranslationDomain) => $labelTranslationDomain ?? $options['translation_domain'],
+ );
+
+ $resolver->setAllowedTypes('toggle', ['bool']);
+ $resolver->setAllowedTypes('hidden_label', ['string', TranslatableMessage::class, 'null']);
+ $resolver->setAllowedTypes('visible_label', ['string', TranslatableMessage::class, 'null']);
+ $resolver->setAllowedTypes('hidden_icon', ['string', 'null']);
+ $resolver->setAllowedTypes('visible_icon', ['string', 'null']);
+ $resolver->setAllowedTypes('button_classes', ['string[]']);
+ $resolver->setAllowedTypes('toggle_container_classes', ['string[]']);
+ $resolver->setAllowedTypes('toggle_translation_domain', ['string', 'bool', 'null']);
+ $resolver->setAllowedTypes('use_toggle_form_theme', ['bool']);
+ }
+
+ public function buildView(FormView $view, FormInterface $form, array $options): void
+ {
+ $view->vars['toggle'] = $options['toggle'];
+
+ if (!$options['toggle']) {
+ return;
+ }
+
+ if ($options['use_toggle_form_theme']) {
+ array_splice($view->vars['block_prefixes'], -1, 0, 'toggle_password');
+ }
+
+ $controllerName = 'toggle-password';
+ $view->vars['attr']['data-controller'] = trim(\sprintf('%s %s', $view->vars['attr']['data-controller'] ?? '', $controllerName));
+
+ if (false !== $options['toggle_translation_domain']) {
+ $controllerValues['hidden-label'] = $this->translateLabel($options['hidden_label'], $options['toggle_translation_domain']);
+ $controllerValues['visible-label'] = $this->translateLabel($options['visible_label'], $options['toggle_translation_domain']);
+ } else {
+ $controllerValues['hidden-label'] = $options['hidden_label'];
+ $controllerValues['visible-label'] = $options['visible_label'];
+ }
+
+ $controllerValues['hidden-icon'] = $options['hidden_icon'];
+ $controllerValues['visible-icon'] = $options['visible_icon'];
+ $controllerValues['button-classes'] = json_encode($options['button_classes'], \JSON_THROW_ON_ERROR);
+
+ foreach ($controllerValues as $name => $value) {
+ $view->vars['attr'][\sprintf('data-%s-%s-value', $controllerName, $name)] = $value;
+ }
+
+ $view->vars['toggle_container_classes'] = $options['toggle_container_classes'];
+ }
+
+ private function translateLabel(string|TranslatableMessage|null $label, ?string $translationDomain): ?string
+ {
+ if (null === $this->translator || null === $label) {
+ return $label;
+ }
+
+ if ($label instanceof TranslatableMessage) {
+ return $label->trans($this->translator);
+ }
+
+ return $this->translator->trans($label, domain: $translationDomain);
+ }
+}
+```
+ - `template/form_theme.html.twig`, and follow the [How to work with form themes](https://symfony.com/doc/current/form/form_themes.html) documentation to register it.
+```twig
+{%- block toggle_password_widget -%}
+
{{ block('password_widget') }}
+{%- endblock toggle_password_widget -%}
+```
+ - `assets/controllers/toggle_password_controller.js`
+```javascript
+import { Controller } from '@hotwired/stimulus';
+import '../styles/toggle_password.css';
+
+export default class extends Controller {
+ static values = {
+ visibleLabel: { type: String, default: 'Show' },
+ visibleIcon: { type: String, default: 'Default' },
+ hiddenLabel: { type: String, default: 'Hide' },
+ hiddenIcon: { type: String, default: 'Default' },
+ buttonClasses: Array,
+ };
+
+ isDisplayed = false;
+ visibleIcon = ``;
+ hiddenIcon = ``;
+
+ connect() {
+ if (this.visibleIconValue !== 'Default') {
+ this.visibleIcon = this.visibleIconValue;
+ }
+
+ if (this.hiddenIconValue !== 'Default') {
+ this.hiddenIcon = this.hiddenIconValue;
+ }
+
+ const button = this.createButton();
+
+ this.element.insertAdjacentElement('afterend', button);
+ this.dispatchEvent('connect', { element: this.element, button });
+ }
+
+ /**
+ * @returns {HTMLButtonElement}
+ */
+ createButton() {
+ const button = document.createElement('button');
+ button.type = 'button';
+ button.classList.add(...this.buttonClassesValue);
+ button.setAttribute('tabindex', '-1');
+ button.addEventListener('click', this.toggle.bind(this));
+ button.innerHTML = `${this.visibleIcon} ${this.visibleLabelValue}`;
+ return button;
+ }
+
+ /**
+ * Toggle input type between "text" or "password" and update label accordingly
+ */
+ toggle(event) {
+ this.isDisplayed = !this.isDisplayed;
+ const toggleButtonElement = event.currentTarget;
+ toggleButtonElement.innerHTML = this.isDisplayed
+ ? `${this.hiddenIcon} ${this.hiddenLabelValue}`
+ : `${this.visibleIcon} ${this.visibleLabelValue}`;
+ this.element.setAttribute('type', this.isDisplayed ? 'text' : 'password');
+ this.dispatchEvent(this.isDisplayed ? 'show' : 'hide', { element: this.element, button: toggleButtonElement });
+ }
+
+ dispatchEvent(name, payload) {
+ this.dispatch(name, { detail: payload, prefix: 'toggle-password' });
+ }
+}
+```
+ - `assets/styles/toggle_password.css`
+```css
+.toggle-password-container {
+ position: relative;
+}
+.toggle-password-icon {
+ height: 1rem;
+ width: 1rem;
+}
+.toggle-password-button {
+ align-items: center;
+ background-color: transparent;
+ border: none;
+ column-gap: 0.25rem;
+ display: flex;
+ flex-direction: row;
+ font-size: 0.875rem;
+ justify-items: center;
+ height: 1rem;
+ line-height: 1.25rem;
+ position: absolute;
+ right: 0.5rem;
+ top: -1.25rem;
+}
+```
+
+You're done!
+
+---
+
Symfony UX TogglePassword is a Symfony bundle providing visibility toggle for password inputs
in Symfony Forms. It is part of [the Symfony UX initiative](https://ux.symfony.com/).
diff --git a/src/TogglePassword/doc/index.rst b/src/TogglePassword/doc/index.rst
index 51d79800d49..111e3db33e9 100644
--- a/src/TogglePassword/doc/index.rst
+++ b/src/TogglePassword/doc/index.rst
@@ -1,6 +1,13 @@
Symfony UX TogglePassword
=========================
+.. warning::
+
+ **Deprecated: This package has been deprecated in 2.x and will be removed in the next major version.**
+
+ To keep the same functionality in your Symfony application, please follow the migration steps
+ from the `Symfony UX TogglePassword README.md`_.
+
Symfony UX TogglePassword is a Symfony bundle providing visibility toggle for password inputs
in Symfony Forms. It is part of `the Symfony UX initiative`_.
@@ -306,3 +313,4 @@ https://symfony.com/doc/current/contributing/code/bc.html
.. _StimulusBundle configured in your app: https://symfony.com/bundles/StimulusBundle/current/index.html
.. _Heroicons: https://heroicons.com/
.. _`@symfony/ux-toggle-password npm package`: https://www.npmjs.com/package/@symfony/ux-toggle-password
+.. _`Symfony UX TogglePassword README.md`: https://github.com/symfony/ux/tree/2.x/src/TogglePassword/README.md
diff --git a/src/TogglePassword/phpunit.xml.dist b/src/TogglePassword/phpunit.xml.dist
index 56293693f2c..77cf27958b1 100644
--- a/src/TogglePassword/phpunit.xml.dist
+++ b/src/TogglePassword/phpunit.xml.dist
@@ -11,7 +11,7 @@
-
+
diff --git a/src/TogglePassword/src/DependencyInjection/TogglePasswordExtension.php b/src/TogglePassword/src/DependencyInjection/TogglePasswordExtension.php
index 48d111ad389..c88e4ea7ccb 100644
--- a/src/TogglePassword/src/DependencyInjection/TogglePasswordExtension.php
+++ b/src/TogglePassword/src/DependencyInjection/TogglePasswordExtension.php
@@ -20,6 +20,8 @@
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\UX\TogglePassword\Form\TogglePasswordTypeExtension;
+trigger_deprecation('symfony/ux-toggle-password', '2.29.0', 'The package is deprecated and will be removed in 3.0. Follow the migration steps in https://github.com/symfony/ux/tree/2.x/src/TogglePassword to keep using TogglePassword in your Symfony application.');
+
/**
* @author Félix Eymonot
*/
diff --git a/src/TogglePassword/src/Form/TogglePasswordTypeExtension.php b/src/TogglePassword/src/Form/TogglePasswordTypeExtension.php
index 75f02912793..a78679bd1d7 100644
--- a/src/TogglePassword/src/Form/TogglePasswordTypeExtension.php
+++ b/src/TogglePassword/src/Form/TogglePasswordTypeExtension.php
@@ -20,6 +20,8 @@
use Symfony\Component\Translation\TranslatableMessage;
use Symfony\Contracts\Translation\TranslatorInterface;
+trigger_deprecation('symfony/ux-toggle-password', '2.29.0', 'The package is deprecated and will be removed in 3.0. Follow the migration steps in https://github.com/symfony/ux/tree/2.x/src/TogglePassword to keep using TogglePassword in your Symfony application.');
+
/**
* @author Félix Eymonot
*/
diff --git a/src/TogglePassword/src/TogglePasswordBundle.php b/src/TogglePassword/src/TogglePasswordBundle.php
index 8c35c4da849..fbfb00ede56 100644
--- a/src/TogglePassword/src/TogglePasswordBundle.php
+++ b/src/TogglePassword/src/TogglePasswordBundle.php
@@ -13,6 +13,8 @@
use Symfony\Component\HttpKernel\Bundle\Bundle;
+trigger_deprecation('symfony/ux-toggle-password', '2.29.0', 'The package is deprecated and will be removed in 3.0. Follow the migration steps in https://github.com/symfony/ux/tree/2.x/src/TogglePassword to keep using TogglePassword in your Symfony application.');
+
/**
* @author Félix Eymonot
*/
diff --git a/src/TogglePassword/tests/baseline-ignore b/src/TogglePassword/tests/baseline-ignore
new file mode 100644
index 00000000000..3321cdbc818
--- /dev/null
+++ b/src/TogglePassword/tests/baseline-ignore
@@ -0,0 +1 @@
+%Since symfony/ux-toggle-password 2\.29\.0: The package is deprecated and will be removed in 3\.0\.%
diff --git a/ux.symfony.com/tests/baseline-ignore b/ux.symfony.com/tests/baseline-ignore
index bb9ff61b48d..6297f114800 100644
--- a/ux.symfony.com/tests/baseline-ignore
+++ b/ux.symfony.com/tests/baseline-ignore
@@ -1,3 +1,4 @@
%Since symfony/ux-typed 2\.27\.0: The package is deprecated and will be removed in 3\.0\.%
%Since symfony/ux-lazy-image 2\.27\.0: The package is deprecated and will be removed in 3\.0\.%
%Since symfony/ux-swup 2\.27\.0: The package is deprecated and will be removed in 3\.0\.%
+%Since symfony/ux-toggle-password 2\.29\.0: The package is deprecated and will be removed in 3\.0\.%