Skip to content

Commit dc864fa

Browse files
committed
Removed the deprecated symfony/ux-toggle-password package by replacing it by our own local versions
1 parent 6d495b3 commit dc864fa

File tree

11 files changed

+254
-104
lines changed

11 files changed

+254
-104
lines changed

assets/controllers.json

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
{
22
"controllers": {
3-
"@symfony/ux-toggle-password": {
4-
"toggle-password": {
5-
"enabled": true,
6-
"fetch": "eager",
7-
"autoimport": {
8-
"@symfony/ux-toggle-password/dist/style.min.css": true
9-
}
10-
}
11-
},
123
"@symfony/ux-turbo": {
134
"turbo-core": {
145
"enabled": true,
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
3+
*
4+
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published
8+
* by the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
import { Controller } from '@hotwired/stimulus';
21+
import '../css/components/toggle_password.css';
22+
23+
export default class extends Controller {
24+
static values = {
25+
visibleLabel: { type: String, default: 'Show' },
26+
visibleIcon: { type: String, default: 'Default' },
27+
hiddenLabel: { type: String, default: 'Hide' },
28+
hiddenIcon: { type: String, default: 'Default' },
29+
buttonClasses: Array,
30+
};
31+
32+
isDisplayed = false;
33+
visibleIcon = `<svg xmlns="http://www.w3.org/2000/svg" class="toggle-password-icon" viewBox="0 0 20 20" fill="currentColor">
34+
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
35+
<path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd" />
36+
</svg>`;
37+
hiddenIcon = `<svg xmlns="http://www.w3.org/2000/svg" class="toggle-password-icon" viewBox="0 0 20 20" fill="currentColor">
38+
<path fill-rule="evenodd" d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z" clip-rule="evenodd" />
39+
<path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z" />
40+
</svg>`;
41+
42+
connect() {
43+
if (this.visibleIconValue !== 'Default') {
44+
this.visibleIcon = this.visibleIconValue;
45+
}
46+
47+
if (this.hiddenIconValue !== 'Default') {
48+
this.hiddenIcon = this.hiddenIconValue;
49+
}
50+
51+
const button = this.createButton();
52+
53+
this.element.insertAdjacentElement('afterend', button);
54+
this.dispatchEvent('connect', { element: this.element, button });
55+
}
56+
57+
/**
58+
* @returns {HTMLButtonElement}
59+
*/
60+
createButton() {
61+
const button = document.createElement('button');
62+
button.type = 'button';
63+
button.classList.add(...this.buttonClassesValue);
64+
button.setAttribute('tabindex', '-1');
65+
button.addEventListener('click', this.toggle.bind(this));
66+
button.innerHTML = `${this.visibleIcon} ${this.visibleLabelValue}`;
67+
return button;
68+
}
69+
70+
/**
71+
* Toggle input type between "text" or "password" and update label accordingly
72+
*/
73+
toggle(event) {
74+
this.isDisplayed = !this.isDisplayed;
75+
const toggleButtonElement = event.currentTarget;
76+
toggleButtonElement.innerHTML = this.isDisplayed
77+
? `${this.hiddenIcon} ${this.hiddenLabelValue}`
78+
: `${this.visibleIcon} ${this.visibleLabelValue}`;
79+
this.element.setAttribute('type', this.isDisplayed ? 'text' : 'password');
80+
this.dispatchEvent(this.isDisplayed ? 'show' : 'hide', { element: this.element, button: toggleButtonElement });
81+
}
82+
83+
dispatchEvent(name, payload) {
84+
this.dispatch(name, { detail: payload, prefix: 'toggle-password' });
85+
}
86+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
3+
*
4+
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published
8+
* by the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
.toggle-password-container {
21+
position: relative;
22+
}
23+
.toggle-password-icon {
24+
height: 1rem;
25+
width: 1rem;
26+
}
27+
.toggle-password-button {
28+
align-items: center;
29+
background-color: transparent;
30+
border: none;
31+
column-gap: 0.25rem;
32+
display: flex;
33+
flex-direction: row;
34+
font-size: 0.875rem;
35+
justify-items: center;
36+
height: 1rem;
37+
line-height: 1.25rem;
38+
position: absolute;
39+
right: 0.5rem;
40+
top: -1.25rem;
41+
}

composer.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@
8080
"symfony/string": "7.3.*",
8181
"symfony/translation": "7.3.*",
8282
"symfony/twig-bundle": "7.3.*",
83-
"symfony/ux-toggle-password": "^2.29",
8483
"symfony/ux-translator": "^2.10",
8584
"symfony/ux-turbo": "^2.0",
8685
"symfony/validator": "7.3.*",

composer.lock

Lines changed: 1 addition & 85 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/bundles.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,4 @@
3333
Jbtronics\SettingsBundle\JbtronicsSettingsBundle::class => ['all' => true],
3434
Jbtronics\TranslationEditorBundle\JbtronicsTranslationEditorBundle::class => ['dev' => true],
3535
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
36-
Symfony\UX\TogglePassword\TogglePasswordBundle::class => ['all' => true],
3736
];

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"@hotwired/turbo": "^8.0.1",
88
"@popperjs/core": "^2.10.2",
99
"@symfony/stimulus-bridge": "^4.0.0",
10-
"@symfony/ux-toggle-password": "file:vendor/symfony/ux-toggle-password/assets",
1110
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
1211
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
1312
"@symfony/webpack-encore": "^5.0.0",
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
/*
3+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4+
*
5+
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as published
9+
* by the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace App\Form\Extension;
24+
25+
use Symfony\Component\Form\AbstractTypeExtension;
26+
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
27+
use Symfony\Component\Form\FormInterface;
28+
use Symfony\Component\Form\FormView;
29+
use Symfony\Component\OptionsResolver\Options;
30+
use Symfony\Component\OptionsResolver\OptionsResolver;
31+
use Symfony\Component\Translation\TranslatableMessage;
32+
use Symfony\Contracts\Translation\TranslatorInterface;
33+
34+
final class TogglePasswordTypeExtension extends AbstractTypeExtension
35+
{
36+
public function __construct(private readonly ?TranslatorInterface $translator)
37+
{
38+
}
39+
40+
public static function getExtendedTypes(): iterable
41+
{
42+
return [PasswordType::class];
43+
}
44+
45+
public function configureOptions(OptionsResolver $resolver): void
46+
{
47+
$resolver->setDefaults([
48+
'toggle' => false,
49+
'hidden_label' => 'Hide',
50+
'visible_label' => 'Show',
51+
'hidden_icon' => 'Default',
52+
'visible_icon' => 'Default',
53+
'button_classes' => ['toggle-password-button'],
54+
'toggle_container_classes' => ['toggle-password-container'],
55+
'toggle_translation_domain' => null,
56+
'use_toggle_form_theme' => true,
57+
]);
58+
59+
$resolver->setNormalizer(
60+
'toggle_translation_domain',
61+
static fn (Options $options, $labelTranslationDomain) => $labelTranslationDomain ?? $options['translation_domain'],
62+
);
63+
64+
$resolver->setAllowedTypes('toggle', ['bool']);
65+
$resolver->setAllowedTypes('hidden_label', ['string', TranslatableMessage::class, 'null']);
66+
$resolver->setAllowedTypes('visible_label', ['string', TranslatableMessage::class, 'null']);
67+
$resolver->setAllowedTypes('hidden_icon', ['string', 'null']);
68+
$resolver->setAllowedTypes('visible_icon', ['string', 'null']);
69+
$resolver->setAllowedTypes('button_classes', ['string[]']);
70+
$resolver->setAllowedTypes('toggle_container_classes', ['string[]']);
71+
$resolver->setAllowedTypes('toggle_translation_domain', ['string', 'bool', 'null']);
72+
$resolver->setAllowedTypes('use_toggle_form_theme', ['bool']);
73+
}
74+
75+
public function buildView(FormView $view, FormInterface $form, array $options): void
76+
{
77+
$view->vars['toggle'] = $options['toggle'];
78+
79+
if (!$options['toggle']) {
80+
return;
81+
}
82+
83+
if ($options['use_toggle_form_theme']) {
84+
array_splice($view->vars['block_prefixes'], -1, 0, 'toggle_password');
85+
}
86+
87+
$controllerName = 'toggle-password';
88+
$view->vars['attr']['data-controller'] = trim(\sprintf('%s %s', $view->vars['attr']['data-controller'] ?? '', $controllerName));
89+
90+
if (false !== $options['toggle_translation_domain']) {
91+
$controllerValues['hidden-label'] = $this->translateLabel($options['hidden_label'], $options['toggle_translation_domain']);
92+
$controllerValues['visible-label'] = $this->translateLabel($options['visible_label'], $options['toggle_translation_domain']);
93+
} else {
94+
$controllerValues['hidden-label'] = $options['hidden_label'];
95+
$controllerValues['visible-label'] = $options['visible_label'];
96+
}
97+
98+
$controllerValues['hidden-icon'] = $options['hidden_icon'];
99+
$controllerValues['visible-icon'] = $options['visible_icon'];
100+
$controllerValues['button-classes'] = json_encode($options['button_classes'], \JSON_THROW_ON_ERROR);
101+
102+
foreach ($controllerValues as $name => $value) {
103+
$view->vars['attr'][\sprintf('data-%s-%s-value', $controllerName, $name)] = $value;
104+
}
105+
106+
$view->vars['toggle_container_classes'] = $options['toggle_container_classes'];
107+
}
108+
109+
private function translateLabel(string|TranslatableMessage|null $label, ?string $translationDomain): ?string
110+
{
111+
if (null === $this->translator || null === $label) {
112+
return $label;
113+
}
114+
115+
if ($label instanceof TranslatableMessage) {
116+
return $label->trans($this->translator);
117+
}
118+
119+
return $this->translator->trans($label, domain: $translationDomain);
120+
}
121+
}

symfony.lock

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -729,9 +729,6 @@
729729
},
730730
"files": []
731731
},
732-
"symfony/ux-toggle-password": {
733-
"version": "v2.29.2"
734-
},
735732
"symfony/ux-translator": {
736733
"version": "2.9",
737734
"recipe": {

0 commit comments

Comments
 (0)