├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── LICENSE ├── README.md ├── dist │ ├── controller.d.ts │ ├── controller.js │ └── style.min.css └── package.json ├── composer.json ├── src ├── DependencyInjection │ └── TogglePasswordExtension.php ├── Form │ └── TogglePasswordTypeExtension.php └── TogglePasswordBundle.php └── templates └── form_theme.html.twig /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 2.13.2 4 | 5 | - Revert "Change JavaScript package to `type: module`" 6 | 7 | ## 2.13.0 8 | 9 | - Add Symfony 7 support. 10 | - Change JavaScript package to `type: module` 11 | 12 | ## 2.12.0 13 | 14 | - Added default values for the Stimulus controller values. 15 | 16 | ## 2.11.0 17 | 18 | - Introduce component! 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symfony UX TogglePassword 2 | 3 | Symfony UX TogglePassword is a Symfony bundle providing visibility toggle for password inputs 4 | in Symfony Forms. It is part of [the Symfony UX initiative](https://ux.symfony.com/). 5 | 6 | It allows visitors to switch the type of password field to text and vice versa. 7 | 8 | **This repository is a READ-ONLY sub-tree split**. See 9 | https://github.com/symfony/ux to create issues or submit pull requests. 10 | 11 | ## Sponsor 12 | 13 | The Symfony UX packages are [backed][1] by [Mercure.rocks][2]. 14 | 15 | Create real-time experiences in minutes! Mercure.rocks provides a realtime API service 16 | that is tightly integrated with Symfony: create UIs that update in live with UX Turbo, 17 | send notifications with the Notifier component, expose async APIs with API Platform and 18 | create low level stuffs with the Mercure component. We maintain and scale the complex 19 | infrastructure for you! 20 | 21 | Help Symfony by [sponsoring][3] its development! 22 | 23 | ## Resources 24 | 25 | - [Documentation](https://symfony.com/bundles/ux-toggle-password/current/index.html) 26 | - [Report issues](https://github.com/symfony/ux/issues) and 27 | [send Pull Requests](https://github.com/symfony/ux/pulls) 28 | in the [main Symfony UX repository](https://github.com/symfony/ux) 29 | 30 | [1]: https://symfony.com/backers 31 | [2]: https://mercure.rocks 32 | [3]: https://symfony.com/sponsor 33 | -------------------------------------------------------------------------------- /assets/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # @symfony/ux-toggle-password 2 | 3 | JavaScript assets of the [symfony/ux-toggle-password](https://packagist.org/packages/symfony/ux-toggle-password) PHP package. 4 | 5 | ## Installation 6 | 7 | This npm package is **reserved for advanced users** who want to decouple their JavaScript dependencies from their PHP dependencies (e.g., when building Docker images, running JavaScript-only pipelines, etc.). 8 | 9 | We **strongly recommend not installing this package directly**, but instead install the PHP package [symfony/ux-toggle-password](https://packagist.org/packages/symfony/ux-toggle-password) in your Symfony application with [Flex](https://github.com/symfony/flex) enabled. 10 | 11 | If you still want to install this package directly, please make sure its version exactly matches [symfony/ux-toggle-password](https://packagist.org/packages/symfony/ux-toggle-password) PHP package version: 12 | ```shell 13 | composer require symfony/ux-toggle-password:2.23.0 14 | npm add @symfony/ux-toggle-password@2.23.0 15 | ``` 16 | 17 | **Tip:** Your `package.json` file will be automatically modified by [Flex](https://github.com/symfony/flex) when installing or upgrading a PHP package. To prevent this behavior, ensure to **use at least Flex 1.22.0 or 2.5.0**, and run `composer config extra.symfony.flex.synchronize_package_json false`. 18 | 19 | ## Resources 20 | 21 | - [Documentation](https://symfony.com/bundles/ux-toggle-password/current/index.html) 22 | - [Report issues](https://github.com/symfony/ux/issues) and 23 | [send Pull Requests](https://github.com/symfony/ux/pulls) 24 | in the [main Symfony UX repository](https://github.com/symfony/ux) 25 | -------------------------------------------------------------------------------- /assets/dist/controller.d.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@hotwired/stimulus'; 2 | export default class extends Controller { 3 | readonly visibleLabelValue: string; 4 | readonly visibleIconValue: string; 5 | readonly hiddenLabelValue: string; 6 | readonly hiddenIconValue: string; 7 | readonly buttonClassesValue: Array; 8 | static values: { 9 | visibleLabel: { 10 | type: StringConstructor; 11 | default: string; 12 | }; 13 | visibleIcon: { 14 | type: StringConstructor; 15 | default: string; 16 | }; 17 | hiddenLabel: { 18 | type: StringConstructor; 19 | default: string; 20 | }; 21 | hiddenIcon: { 22 | type: StringConstructor; 23 | default: string; 24 | }; 25 | buttonClasses: ArrayConstructor; 26 | }; 27 | isDisplayed: boolean; 28 | visibleIcon: string; 29 | hiddenIcon: string; 30 | connect(): void; 31 | private createButton; 32 | toggle(event: any): void; 33 | private dispatchEvent; 34 | } 35 | -------------------------------------------------------------------------------- /assets/dist/controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from '@hotwired/stimulus'; 2 | 3 | class default_1 extends Controller { 4 | constructor() { 5 | super(...arguments); 6 | this.isDisplayed = false; 7 | this.visibleIcon = ` 8 | 9 | 10 | `; 11 | this.hiddenIcon = ` 12 | 13 | 14 | `; 15 | } 16 | connect() { 17 | if (this.visibleIconValue !== 'Default') { 18 | this.visibleIcon = this.visibleIconValue; 19 | } 20 | if (this.hiddenIconValue !== 'Default') { 21 | this.hiddenIcon = this.hiddenIconValue; 22 | } 23 | const button = this.createButton(); 24 | this.element.insertAdjacentElement('afterend', button); 25 | this.dispatchEvent('connect', { element: this.element, button: button }); 26 | } 27 | createButton() { 28 | const button = document.createElement('button'); 29 | button.type = 'button'; 30 | button.classList.add(...this.buttonClassesValue); 31 | button.setAttribute('tabindex', '-1'); 32 | button.addEventListener('click', this.toggle.bind(this)); 33 | button.innerHTML = `${this.visibleIcon} ${this.visibleLabelValue}`; 34 | return button; 35 | } 36 | toggle(event) { 37 | this.isDisplayed = !this.isDisplayed; 38 | const toggleButtonElement = event.currentTarget; 39 | toggleButtonElement.innerHTML = this.isDisplayed 40 | ? `${this.hiddenIcon} ${this.hiddenLabelValue}` 41 | : `${this.visibleIcon} ${this.visibleLabelValue}`; 42 | this.element.setAttribute('type', this.isDisplayed ? 'text' : 'password'); 43 | this.dispatchEvent(this.isDisplayed ? 'show' : 'hide', { element: this.element, button: toggleButtonElement }); 44 | } 45 | dispatchEvent(name, payload) { 46 | this.dispatch(name, { detail: payload, prefix: 'toggle-password' }); 47 | } 48 | } 49 | default_1.values = { 50 | visibleLabel: { type: String, default: 'Show' }, 51 | visibleIcon: { type: String, default: 'Default' }, 52 | hiddenLabel: { type: String, default: 'Hide' }, 53 | hiddenIcon: { type: String, default: 'Default' }, 54 | buttonClasses: Array, 55 | }; 56 | 57 | export { default_1 as default }; 58 | -------------------------------------------------------------------------------- /assets/dist/style.min.css: -------------------------------------------------------------------------------- 1 | .toggle-password-container{position:relative}.toggle-password-icon{width:1rem;height:1rem}.toggle-password-button{background-color:#0000;border:none;flex-direction:row;place-items:center;column-gap:.25rem;height:1rem;font-size:.875rem;line-height:1.25rem;display:flex;position:absolute;top:-1.25rem;right:.5rem} -------------------------------------------------------------------------------- /assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@symfony/ux-toggle-password", 3 | "description": "Toggle visibility of password inputs for Symfony Forms", 4 | "license": "MIT", 5 | "version": "2.26.1", 6 | "keywords": [ 7 | "symfony-ux" 8 | ], 9 | "homepage": "https://ux.symfony.com/toggle-password", 10 | "repository": "https://github.com/symfony/ux-toggle-password", 11 | "type": "module", 12 | "files": [ 13 | "dist" 14 | ], 15 | "main": "dist/controller.js", 16 | "types": "dist/controller.d.ts", 17 | "config": { 18 | "css_source": "src/style.css" 19 | }, 20 | "scripts": { 21 | "build": "node ../../../bin/build_package.js .", 22 | "watch": "node ../../../bin/build_package.js . --watch", 23 | "test": "../../../bin/test_package.sh .", 24 | "check": "biome check", 25 | "ci": "biome ci" 26 | }, 27 | "symfony": { 28 | "controllers": { 29 | "toggle-password": { 30 | "main": "dist/controller.js", 31 | "fetch": "eager", 32 | "enabled": true, 33 | "autoimport": { 34 | "@symfony/ux-toggle-password/dist/style.min.css": true 35 | } 36 | } 37 | }, 38 | "importmap": { 39 | "@hotwired/stimulus": "^3.0.0" 40 | } 41 | }, 42 | "peerDependencies": { 43 | "@hotwired/stimulus": "^3.0.0" 44 | }, 45 | "devDependencies": { 46 | "@hotwired/stimulus": "^3.0.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/ux-toggle-password", 3 | "type": "symfony-bundle", 4 | "description": "Toggle visibility of password inputs for Symfony Forms", 5 | "keywords": [ 6 | "symfony-ux" 7 | ], 8 | "homepage": "https://symfony.com", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Félix Eymonot", 13 | "email": "felix.eymonot@alximy.io" 14 | }, 15 | { 16 | "name": "Symfony Community", 17 | "homepage": "https://symfony.com/contributors" 18 | } 19 | ], 20 | "autoload": { 21 | "psr-4": { 22 | "Symfony\\UX\\TogglePassword\\": "src/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "Symfony\\UX\\TogglePassword\\Tests\\": "tests/" 28 | } 29 | }, 30 | "require": { 31 | "php": ">=8.1", 32 | "symfony/config": "^5.4|^6.0|^7.0", 33 | "symfony/dependency-injection": "^5.4|^6.0|^7.0", 34 | "symfony/form": "^5.4|^6.0|^7.0", 35 | "symfony/http-kernel": "^5.4|^6.0|^7.0", 36 | "symfony/options-resolver": "^5.4|^6.0|^7.0", 37 | "symfony/translation": "^5.4|^6.0|^7.0" 38 | }, 39 | "require-dev": { 40 | "symfony/framework-bundle": "^5.4|^6.0|^7.0", 41 | "symfony/phpunit-bridge": "^5.4|^6.0|^7.0", 42 | "symfony/twig-bundle": "^5.4|^6.0|^7.0", 43 | "symfony/var-dumper": "^5.4|^6.0|^7.0", 44 | "twig/twig": "^2.14.7|^3.0.4" 45 | }, 46 | "extra": { 47 | "thanks": { 48 | "name": "symfony/ux", 49 | "url": "https://github.com/symfony/ux" 50 | } 51 | }, 52 | "minimum-stability": "dev" 53 | } 54 | -------------------------------------------------------------------------------- /src/DependencyInjection/TogglePasswordExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\UX\TogglePassword\DependencyInjection; 13 | 14 | use Symfony\Component\AssetMapper\AssetMapperInterface; 15 | use Symfony\Component\DependencyInjection\ContainerBuilder; 16 | use Symfony\Component\DependencyInjection\ContainerInterface; 17 | use Symfony\Component\DependencyInjection\Definition; 18 | use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; 19 | use Symfony\Component\DependencyInjection\Reference; 20 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 21 | use Symfony\UX\TogglePassword\Form\TogglePasswordTypeExtension; 22 | 23 | /** 24 | * @author Félix Eymonot 25 | */ 26 | final class TogglePasswordExtension extends Extension implements PrependExtensionInterface 27 | { 28 | public function prepend(ContainerBuilder $container): void 29 | { 30 | // Register the TogglePassword form theme if TwigBundle is available 31 | $bundles = $container->getParameter('kernel.bundles'); 32 | 33 | if (isset($bundles['TwigBundle'])) { 34 | $container->prependExtensionConfig('twig', ['form_themes' => ['@TogglePassword/form_theme.html.twig']]); 35 | } 36 | 37 | if ($this->isAssetMapperAvailable($container)) { 38 | $container->prependExtensionConfig('framework', [ 39 | 'asset_mapper' => [ 40 | 'paths' => [ 41 | __DIR__.'/../../assets/dist' => '@symfony/ux-toggle-password', 42 | ], 43 | ], 44 | ]); 45 | } 46 | } 47 | 48 | public function load(array $configs, ContainerBuilder $container): void 49 | { 50 | $container 51 | ->setDefinition('form.extension.toggle_password', new Definition(TogglePasswordTypeExtension::class)) 52 | ->setArguments([ 53 | new Reference('translator', ContainerInterface::IGNORE_ON_INVALID_REFERENCE), 54 | ]) 55 | ->addTag('form.type_extension') 56 | ->setPublic(false) 57 | ; 58 | } 59 | 60 | private function isAssetMapperAvailable(ContainerBuilder $container): bool 61 | { 62 | if (!interface_exists(AssetMapperInterface::class)) { 63 | return false; 64 | } 65 | 66 | // check that FrameworkBundle 6.3 or higher is installed 67 | $bundlesMetadata = $container->getParameter('kernel.bundles_metadata'); 68 | if (!isset($bundlesMetadata['FrameworkBundle'])) { 69 | return false; 70 | } 71 | 72 | return is_file($bundlesMetadata['FrameworkBundle']['path'].'/Resources/config/asset_mapper.php'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Form/TogglePasswordTypeExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\UX\TogglePassword\Form; 13 | 14 | use Symfony\Component\Form\AbstractTypeExtension; 15 | use Symfony\Component\Form\Extension\Core\Type\PasswordType; 16 | use Symfony\Component\Form\FormInterface; 17 | use Symfony\Component\Form\FormView; 18 | use Symfony\Component\OptionsResolver\Options; 19 | use Symfony\Component\OptionsResolver\OptionsResolver; 20 | use Symfony\Component\Translation\TranslatableMessage; 21 | use Symfony\Contracts\Translation\TranslatorInterface; 22 | 23 | /** 24 | * @author Félix Eymonot 25 | */ 26 | final class TogglePasswordTypeExtension extends AbstractTypeExtension 27 | { 28 | public function __construct(private readonly ?TranslatorInterface $translator) 29 | { 30 | } 31 | 32 | public static function getExtendedTypes(): iterable 33 | { 34 | return [PasswordType::class]; 35 | } 36 | 37 | public function configureOptions(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'toggle' => false, 41 | 'hidden_label' => 'Hide', 42 | 'visible_label' => 'Show', 43 | 'hidden_icon' => 'Default', 44 | 'visible_icon' => 'Default', 45 | 'button_classes' => ['toggle-password-button'], 46 | 'toggle_container_classes' => ['toggle-password-container'], 47 | 'toggle_translation_domain' => null, 48 | 'use_toggle_form_theme' => true, 49 | ]); 50 | 51 | $resolver->setNormalizer( 52 | 'toggle_translation_domain', 53 | static fn (Options $options, $labelTranslationDomain) => $labelTranslationDomain ?? $options['translation_domain'], 54 | ); 55 | 56 | $resolver->setAllowedTypes('toggle', ['bool']); 57 | $resolver->setAllowedTypes('hidden_label', ['string', TranslatableMessage::class, 'null']); 58 | $resolver->setAllowedTypes('visible_label', ['string', TranslatableMessage::class, 'null']); 59 | $resolver->setAllowedTypes('hidden_icon', ['string', 'null']); 60 | $resolver->setAllowedTypes('visible_icon', ['string', 'null']); 61 | $resolver->setAllowedTypes('button_classes', ['string[]']); 62 | $resolver->setAllowedTypes('toggle_container_classes', ['string[]']); 63 | $resolver->setAllowedTypes('toggle_translation_domain', ['string', 'bool', 'null']); 64 | $resolver->setAllowedTypes('use_toggle_form_theme', ['bool']); 65 | } 66 | 67 | public function buildView(FormView $view, FormInterface $form, array $options): void 68 | { 69 | $view->vars['toggle'] = $options['toggle']; 70 | 71 | if (!$options['toggle']) { 72 | return; 73 | } 74 | 75 | if ($options['use_toggle_form_theme']) { 76 | array_splice($view->vars['block_prefixes'], -1, 0, 'ux_toggle_password'); 77 | } 78 | 79 | $controllerName = 'symfony--ux-toggle-password--toggle-password'; 80 | $view->vars['attr']['data-controller'] = trim(\sprintf('%s %s', $view->vars['attr']['data-controller'] ?? '', $controllerName)); 81 | 82 | if (false !== $options['toggle_translation_domain']) { 83 | $controllerValues['hidden-label'] = $this->translateLabel($options['hidden_label'], $options['toggle_translation_domain']); 84 | $controllerValues['visible-label'] = $this->translateLabel($options['visible_label'], $options['toggle_translation_domain']); 85 | } else { 86 | $controllerValues['hidden-label'] = $options['hidden_label']; 87 | $controllerValues['visible-label'] = $options['visible_label']; 88 | } 89 | 90 | $controllerValues['hidden-icon'] = $options['hidden_icon']; 91 | $controllerValues['visible-icon'] = $options['visible_icon']; 92 | $controllerValues['button-classes'] = json_encode($options['button_classes'], \JSON_THROW_ON_ERROR); 93 | 94 | foreach ($controllerValues as $name => $value) { 95 | $view->vars['attr'][\sprintf('data-%s-%s-value', $controllerName, $name)] = $value; 96 | } 97 | 98 | $view->vars['toggle_container_classes'] = $options['toggle_container_classes']; 99 | } 100 | 101 | private function translateLabel(string|TranslatableMessage|null $label, ?string $translationDomain): ?string 102 | { 103 | if (null === $this->translator || null === $label) { 104 | return $label; 105 | } 106 | 107 | if ($label instanceof TranslatableMessage) { 108 | return $label->trans($this->translator); 109 | } 110 | 111 | return $this->translator->trans($label, domain: $translationDomain); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/TogglePasswordBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\UX\TogglePassword; 13 | 14 | use Symfony\Component\HttpKernel\Bundle\Bundle; 15 | 16 | /** 17 | * @author Félix Eymonot 18 | */ 19 | final class TogglePasswordBundle extends Bundle 20 | { 21 | public function getPath(): string 22 | { 23 | return \dirname(__DIR__); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /templates/form_theme.html.twig: -------------------------------------------------------------------------------- 1 | {%- block ux_toggle_password_widget -%} 2 |
{{ block('password_widget') }}
3 | {%- endblock ux_toggle_password_widget -%} 4 | --------------------------------------------------------------------------------