From c8a10a732b479743f2389065e32e389148556c90 Mon Sep 17 00:00:00 2001 From: matheo Date: Sat, 15 Apr 2023 14:08:08 +0200 Subject: [PATCH 01/24] add the ability to have static component --- src/TwigComponent/src/ComponentFactory.php | 27 +++++++++++++++++-- .../TwigComponentExtension.php | 1 + src/TwigComponent/src/StaticComponent.php | 7 +++++ .../render_static_component.html.twig | 1 + .../templates/static/button.html.twig | 1 + .../Integration/ComponentExtensionTest.php | 7 +++++ 6 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 src/TwigComponent/src/StaticComponent.php create mode 100644 src/TwigComponent/tests/Fixtures/templates/render_static_component.html.twig create mode 100644 src/TwigComponent/tests/Fixtures/templates/static/button.html.twig diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php index 8e0ccc3f860..59edf1eff74 100644 --- a/src/TwigComponent/src/ComponentFactory.php +++ b/src/TwigComponent/src/ComponentFactory.php @@ -17,6 +17,7 @@ use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; use Symfony\UX\TwigComponent\Event\PostMountEvent; use Symfony\UX\TwigComponent\Event\PreMountEvent; +use Twig\Environment; /** * @author Kevin Bond @@ -35,6 +36,7 @@ public function __construct( private EventDispatcherInterface $eventDispatcher, private array $config, private array $classMap, + private Environment $environment, ) { } @@ -43,7 +45,14 @@ public function metadataFor(string $name): ComponentMetadata $name = $this->classMap[$name] ?? $name; if (!$config = $this->config[$name] ?? null) { - $this->throwUnknownComponentException($name); + if ($this->isStaticComponent($name)) { + return new ComponentMetadata([ + 'key' => $name, + 'template' => $this->getTemplateFromName($name), + ]); + } + + throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s', $name, implode(', ', array_keys($this->config)))); } return new ComponentMetadata($config); @@ -149,7 +158,11 @@ private function getComponent(string $name): object $name = $this->classMap[$name] ?? $name; if (!$this->components->has($name)) { - $this->throwUnknownComponentException($name); + if ($this->isStaticComponent($name)) { + return new StaticComponent(); + } + + throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s', $name, implode(', ', array_keys($this->components->getProvidedServices())))); } return $this->components->get($name); @@ -189,6 +202,16 @@ private function postMount(object $component, array $data): array return $data; } + private function isStaticComponent(string $name): bool + { + return $this->environment->getLoader()->exists($this->getTemplateFromName($name)); + } + + private function getTemplateFromName(string $name): string + { + return str_replace('.', '/', $name).'.html.twig'; + } + /** * @return never */ diff --git a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php index 6a3820c5752..3f52601de13 100644 --- a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php +++ b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php @@ -53,6 +53,7 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in % new Reference('property_accessor'), new Reference('event_dispatcher'), class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %s.', TwigComponentPass::class)) : [], + new Reference('twig'), ]) ; diff --git a/src/TwigComponent/src/StaticComponent.php b/src/TwigComponent/src/StaticComponent.php new file mode 100644 index 00000000000..bcbe727aca6 --- /dev/null +++ b/src/TwigComponent/src/StaticComponent.php @@ -0,0 +1,7 @@ +assertStringContainsString('custom td (1)', $output); } + public function testCanRenderStaticComponent(): void + { + $output = self::getContainer()->get(Environment::class)->render('render_static_component.html.twig'); + + $this->assertStringContainsString('I am static', $output); + } + public function testComponentWithNamespace(): void { $output = $this->renderComponent('foo:bar:baz'); From 718f3a13d138804931fdeeea8eec3b216d0c131b Mon Sep 17 00:00:00 2001 From: matheo Date: Mon, 17 Apr 2023 23:38:14 +0200 Subject: [PATCH 02/24] add configuration for template extension --- src/TwigComponent/src/ComponentFactory.php | 7 +++--- .../src/DependencyInjection/Configuration.php | 22 +++++++++++++++++++ .../TwigComponentExtension.php | 4 ++++ 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 src/TwigComponent/src/DependencyInjection/Configuration.php diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php index 59edf1eff74..48f495e6b0c 100644 --- a/src/TwigComponent/src/ComponentFactory.php +++ b/src/TwigComponent/src/ComponentFactory.php @@ -37,6 +37,7 @@ public function __construct( private array $config, private array $classMap, private Environment $environment, + private string $twigExtension ) { } @@ -52,7 +53,7 @@ public function metadataFor(string $name): ComponentMetadata ]); } - throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s', $name, implode(', ', array_keys($this->config)))); + throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no template %s. founded', $name, implode(', ', array_keys($this->config)), $this->getTemplateFromName($name))); } return new ComponentMetadata($config); @@ -162,7 +163,7 @@ private function getComponent(string $name): object return new StaticComponent(); } - throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s', $name, implode(', ', array_keys($this->components->getProvidedServices())))); + throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no template %s. founded', $name, implode(', ', array_keys($this->components->getProvidedServices())), $this->getTemplateFromName($name))); } return $this->components->get($name); @@ -209,7 +210,7 @@ private function isStaticComponent(string $name): bool private function getTemplateFromName(string $name): string { - return str_replace('.', '/', $name).'.html.twig'; + return str_replace('.', '/', $name).$this->twigExtension; } /** diff --git a/src/TwigComponent/src/DependencyInjection/Configuration.php b/src/TwigComponent/src/DependencyInjection/Configuration.php new file mode 100644 index 00000000000..b0b8bacfc95 --- /dev/null +++ b/src/TwigComponent/src/DependencyInjection/Configuration.php @@ -0,0 +1,22 @@ +getRootNode(); + $rootNode + ->children() + ->scalarNode('twig_extension')->defaultValue('.html.twig')->end() + ->end() + ; + + return $treeBuilder; + } +} diff --git a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php index 3f52601de13..5ab767e95bc 100644 --- a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php +++ b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php @@ -40,6 +40,9 @@ public function load(array $configs, ContainerBuilder $container): void throw new LogicException('The TwigBundle is not registered in your application. Try running "composer require symfony/twig-bundle".'); } + $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); + $container->registerAttributeForAutoconfiguration( AsTwigComponent::class, static function (ChildDefinition $definition, AsTwigComponent $attribute) { @@ -54,6 +57,7 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in % new Reference('event_dispatcher'), class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %s.', TwigComponentPass::class)) : [], new Reference('twig'), + $config['twig_extension'], ]) ; From 1b7020e4df7da2e73b694b6aa96fb352b23d753c Mon Sep 17 00:00:00 2001 From: matheo Date: Mon, 17 Apr 2023 23:39:42 +0200 Subject: [PATCH 03/24] rename twig extension --- src/TwigComponent/src/DependencyInjection/Configuration.php | 2 +- .../src/DependencyInjection/TwigComponentExtension.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TwigComponent/src/DependencyInjection/Configuration.php b/src/TwigComponent/src/DependencyInjection/Configuration.php index b0b8bacfc95..6ee4f011db6 100644 --- a/src/TwigComponent/src/DependencyInjection/Configuration.php +++ b/src/TwigComponent/src/DependencyInjection/Configuration.php @@ -13,7 +13,7 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() - ->scalarNode('twig_extension')->defaultValue('.html.twig')->end() + ->scalarNode('template_extension')->defaultValue('.html.twig')->end() ->end() ; diff --git a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php index 5ab767e95bc..bb8645ea945 100644 --- a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php +++ b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php @@ -57,7 +57,7 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in % new Reference('event_dispatcher'), class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %s.', TwigComponentPass::class)) : [], new Reference('twig'), - $config['twig_extension'], + $config['template_extension'], ]) ; From 2bffe7c6611c0b240f709ba725895b7212c8ad9d Mon Sep 17 00:00:00 2001 From: Matheo Daninos Date: Sat, 1 Jul 2023 10:25:47 +0200 Subject: [PATCH 04/24] Render Statics Components and Props tags --- src/TwigComponent/src/ComponentFactory.php | 44 ++++++++++++++----- src/TwigComponent/src/ComponentRenderer.php | 1 + .../Compiler/TwigComponentPass.php | 6 +-- .../TwigComponentExtension.php | 6 +-- src/TwigComponent/src/StaticComponent.php | 16 +++++++ .../src/Twig/ComponentExtension.php | 1 + src/TwigComponent/src/Twig/PropsNode.php | 40 +++++++++++++++++ .../src/Twig/PropsTokenParser.php | 41 +++++++++++++++++ .../templates/anonymous_component.html.twig | 1 + ...nymous_component_overwrite_props.html.twig | 1 + .../templates/components/Button.html.twig | 5 +++ .../templates/static/button.html.twig | 1 - .../Integration/ComponentExtensionTest.php | 21 ++++++--- 13 files changed, 159 insertions(+), 25 deletions(-) create mode 100644 src/TwigComponent/src/Twig/PropsNode.php create mode 100644 src/TwigComponent/src/Twig/PropsTokenParser.php create mode 100644 src/TwigComponent/tests/Fixtures/templates/anonymous_component.html.twig create mode 100644 src/TwigComponent/tests/Fixtures/templates/anonymous_component_overwrite_props.html.twig create mode 100644 src/TwigComponent/tests/Fixtures/templates/components/Button.html.twig delete mode 100644 src/TwigComponent/tests/Fixtures/templates/static/button.html.twig diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php index 48f495e6b0c..2e76ca46aa7 100644 --- a/src/TwigComponent/src/ComponentFactory.php +++ b/src/TwigComponent/src/ComponentFactory.php @@ -31,13 +31,12 @@ final class ComponentFactory * @param array $classMap */ public function __construct( + private Environment $environment, private ServiceLocator $components, private PropertyAccessorInterface $propertyAccessor, private EventDispatcherInterface $eventDispatcher, private array $config, - private array $classMap, - private Environment $environment, - private string $twigExtension + private array $classMap ) { } @@ -46,14 +45,14 @@ public function metadataFor(string $name): ComponentMetadata $name = $this->classMap[$name] ?? $name; if (!$config = $this->config[$name] ?? null) { - if ($this->isStaticComponent($name)) { + if (($template = $this->findStaticComponentTemplate($name)) !== null) { return new ComponentMetadata([ 'key' => $name, - 'template' => $this->getTemplateFromName($name), + 'template' => $template, ]); } - throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no template %s. founded', $name, implode(', ', array_keys($this->config)), $this->getTemplateFromName($name))); + throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no template anonymous component founded', $name, implode(', ', array_keys($this->config)))); } return new ComponentMetadata($config); @@ -134,6 +133,12 @@ private function mount(object $component, array &$data): void return; } + if ($component instanceof StaticComponent) { + $component->mount($data); + + return; + } + $parameters = []; foreach ($method->getParameters() as $refParameter) { @@ -163,7 +168,7 @@ private function getComponent(string $name): object return new StaticComponent(); } - throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no template %s. founded', $name, implode(', ', array_keys($this->components->getProvidedServices())), $this->getTemplateFromName($name))); + throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no anonymous component founded', $name, implode(', ', array_keys($this->components->getProvidedServices())))); } return $this->components->get($name); @@ -205,12 +210,31 @@ private function postMount(object $component, array $data): array private function isStaticComponent(string $name): bool { - return $this->environment->getLoader()->exists($this->getTemplateFromName($name)); + return null !== $this->findStaticComponentTemplate($name); } - private function getTemplateFromName(string $name): string + public function findStaticComponentTemplate(string $name): ?string { - return str_replace('.', '/', $name).$this->twigExtension; + $loader = $this->environment->getLoader(); + $componentPath = rtrim(str_replace(':', '/', $name)); + + if ($loader->exists($componentPath)) { + return $componentPath; + } + + if ($loader->exists($componentPath.'.html.twig')) { + return $componentPath.'.html.twig'; + } + + if ($loader->exists('components/'.$componentPath)) { + return 'components/'.$componentPath; + } + + if ($loader->exists('/components/'.$componentPath.'.html.twig')) { + return '/components/'.$componentPath.'.html.twig'; + } + + return null; } /** diff --git a/src/TwigComponent/src/ComponentRenderer.php b/src/TwigComponent/src/ComponentRenderer.php index 253df6993df..5a2ec91db12 100644 --- a/src/TwigComponent/src/ComponentRenderer.php +++ b/src/TwigComponent/src/ComponentRenderer.php @@ -105,6 +105,7 @@ private function preRender(MountedComponent $mounted, array $context = []): PreR // expose public properties and properties marked with ExposeInTemplate attribute iterator_to_array($this->exposedVariables($component, $metadata->isPublicPropsExposed())), + $component instanceof StaticComponent ? $component->getProps() : [] ); $event = new PreRenderEvent($mounted, $metadata, $variables); diff --git a/src/TwigComponent/src/DependencyInjection/Compiler/TwigComponentPass.php b/src/TwigComponent/src/DependencyInjection/Compiler/TwigComponentPass.php index 8be8c553b6b..448d48d230c 100644 --- a/src/TwigComponent/src/DependencyInjection/Compiler/TwigComponentPass.php +++ b/src/TwigComponent/src/DependencyInjection/Compiler/TwigComponentPass.php @@ -58,8 +58,8 @@ public function process(ContainerBuilder $container): void } $factoryDefinition = $container->findDefinition('ux.twig_component.component_factory'); - $factoryDefinition->setArgument(0, ServiceLocatorTagPass::register($container, $componentReferences)); - $factoryDefinition->setArgument(3, $componentConfig); - $factoryDefinition->setArgument(4, $componentClassMap); + $factoryDefinition->setArgument(1, ServiceLocatorTagPass::register($container, $componentReferences)); + $factoryDefinition->setArgument(4, $componentConfig); + $factoryDefinition->setArgument(5, $componentClassMap); } } diff --git a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php index bb8645ea945..ba19d83c15e 100644 --- a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php +++ b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php @@ -40,9 +40,6 @@ public function load(array $configs, ContainerBuilder $container): void throw new LogicException('The TwigBundle is not registered in your application. Try running "composer require symfony/twig-bundle".'); } - $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); - $container->registerAttributeForAutoconfiguration( AsTwigComponent::class, static function (ChildDefinition $definition, AsTwigComponent $attribute) { @@ -52,12 +49,11 @@ static function (ChildDefinition $definition, AsTwigComponent $attribute) { $container->register('ux.twig_component.component_factory', ComponentFactory::class) ->setArguments([ + new Reference('twig'), class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %s.', TwigComponentPass::class)) : null, new Reference('property_accessor'), new Reference('event_dispatcher'), class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %s.', TwigComponentPass::class)) : [], - new Reference('twig'), - $config['template_extension'], ]) ; diff --git a/src/TwigComponent/src/StaticComponent.php b/src/TwigComponent/src/StaticComponent.php index bcbe727aca6..06e289d3188 100644 --- a/src/TwigComponent/src/StaticComponent.php +++ b/src/TwigComponent/src/StaticComponent.php @@ -4,4 +4,20 @@ class StaticComponent { + private array $props = []; + + public function setProps(array $props) + { + $this->props = $props; + } + + public function getProps(): array + { + return $this->props; + } + + public function mount(array $props = []) + { + $this->setProps($props); + } } diff --git a/src/TwigComponent/src/Twig/ComponentExtension.php b/src/TwigComponent/src/Twig/ComponentExtension.php index a9afea75102..ecc36ee7f05 100644 --- a/src/TwigComponent/src/Twig/ComponentExtension.php +++ b/src/TwigComponent/src/Twig/ComponentExtension.php @@ -49,6 +49,7 @@ public function getTokenParsers(): array { return [ new ComponentTokenParser(fn () => $this->container->get(ComponentFactory::class)), + new PropsTokenParser(), ]; } diff --git a/src/TwigComponent/src/Twig/PropsNode.php b/src/TwigComponent/src/Twig/PropsNode.php new file mode 100644 index 00000000000..6daec65ad01 --- /dev/null +++ b/src/TwigComponent/src/Twig/PropsNode.php @@ -0,0 +1,40 @@ + $propsNames], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + foreach ($this->getAttribute('names') as $name) { + $compiler + ->addDebugInfo($this) + ->write('if (!isset($context[\''.$name.'\'])) {') + ; + + if (!$this->hasNode($name)) { + $compiler + ->write('throw new \Exception("'.$name.' should be defined for component '.$this->getTemplateName().'");') + ->write('}') + ; + + continue; + } + + $compiler + ->write('$context[\''.$name.'\'] = ') + ->subcompile($this->getNode($name)) + ->raw(";\n") + ->write('}') + ; + } + } +} diff --git a/src/TwigComponent/src/Twig/PropsTokenParser.php b/src/TwigComponent/src/Twig/PropsTokenParser.php new file mode 100644 index 00000000000..92e603e9ea2 --- /dev/null +++ b/src/TwigComponent/src/Twig/PropsTokenParser.php @@ -0,0 +1,41 @@ +parser; + $stream = $parser->getStream(); + + $names = []; + $values = []; + while (!$stream->nextIf(Token::BLOCK_END_TYPE)) { + $name = $stream->expect(\Twig\Token::NAME_TYPE)->getValue(); + + if ($stream->nextIf(Token::OPERATOR_TYPE, '=')) { + $values[$name] = $parser->getExpressionParser()->parseExpression(); + } + + $names[] = $name; + + if (!$stream->nextIf(Token::PUNCTUATION_TYPE)) { + break; + } + } + + $stream->expect(\Twig\Token::BLOCK_END_TYPE); + + return new PropsNode($names, $values, $token->getLine(), $token->getValue()); + } + + public function getTag(): string + { + return 'props'; + } +} diff --git a/src/TwigComponent/tests/Fixtures/templates/anonymous_component.html.twig b/src/TwigComponent/tests/Fixtures/templates/anonymous_component.html.twig new file mode 100644 index 00000000000..b40ed083df9 --- /dev/null +++ b/src/TwigComponent/tests/Fixtures/templates/anonymous_component.html.twig @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/TwigComponent/tests/Fixtures/templates/anonymous_component_overwrite_props.html.twig b/src/TwigComponent/tests/Fixtures/templates/anonymous_component_overwrite_props.html.twig new file mode 100644 index 00000000000..784cbf514dc --- /dev/null +++ b/src/TwigComponent/tests/Fixtures/templates/anonymous_component_overwrite_props.html.twig @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/TwigComponent/tests/Fixtures/templates/components/Button.html.twig b/src/TwigComponent/tests/Fixtures/templates/components/Button.html.twig new file mode 100644 index 00000000000..775baaa125c --- /dev/null +++ b/src/TwigComponent/tests/Fixtures/templates/components/Button.html.twig @@ -0,0 +1,5 @@ +{% props label, primary = true %} + + \ No newline at end of file diff --git a/src/TwigComponent/tests/Fixtures/templates/static/button.html.twig b/src/TwigComponent/tests/Fixtures/templates/static/button.html.twig deleted file mode 100644 index c638a1d3773..00000000000 --- a/src/TwigComponent/tests/Fixtures/templates/static/button.html.twig +++ /dev/null @@ -1 +0,0 @@ -I am static \ No newline at end of file diff --git a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php index 8c4f0bd5c1c..7a951034cba 100644 --- a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php +++ b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php @@ -151,18 +151,27 @@ public function testCanRenderEmbeddedComponent(): void $this->assertStringContainsString('custom td (1)', $output); } - public function testCanRenderStaticComponent(): void + public function testComponentWithNamespace(): void { - $output = self::getContainer()->get(Environment::class)->render('render_static_component.html.twig'); + $output = $this->renderComponent('foo:bar:baz'); - $this->assertStringContainsString('I am static', $output); + $this->assertStringContainsString('Content...', $output); } - public function testComponentWithNamespace(): void + public function testRenderAnonymousComponent(): void { - $output = $this->renderComponent('foo:bar:baz'); + $output = self::getContainer()->get(Environment::class)->render('anonymous_component.html.twig'); - $this->assertStringContainsString('Content...', $output); + $this->assertStringContainsString('Click me', $output); + $this->assertStringContainsString('class="primary"', $output); + } + + public function testRenderAnonymousComponentOverwriteProps(): void + { + $output = self::getContainer()->get(Environment::class)->render('anonymous_component_overwrite_props.html.twig'); + + $this->assertStringContainsString('Click me', $output); + $this->assertStringContainsString('class="secondary"', $output); } private function renderComponent(string $name, array $data = []): string From 78a576d96b2f780814fa21852b678f44d99e068a Mon Sep 17 00:00:00 2001 From: matheo Date: Mon, 3 Jul 2023 14:49:56 +0200 Subject: [PATCH 05/24] remove unused files --- .../src/DependencyInjection/Configuration.php | 22 ------------------- .../render_static_component.html.twig | 1 - 2 files changed, 23 deletions(-) delete mode 100644 src/TwigComponent/src/DependencyInjection/Configuration.php delete mode 100644 src/TwigComponent/tests/Fixtures/templates/render_static_component.html.twig diff --git a/src/TwigComponent/src/DependencyInjection/Configuration.php b/src/TwigComponent/src/DependencyInjection/Configuration.php deleted file mode 100644 index 6ee4f011db6..00000000000 --- a/src/TwigComponent/src/DependencyInjection/Configuration.php +++ /dev/null @@ -1,22 +0,0 @@ -getRootNode(); - $rootNode - ->children() - ->scalarNode('template_extension')->defaultValue('.html.twig')->end() - ->end() - ; - - return $treeBuilder; - } -} diff --git a/src/TwigComponent/tests/Fixtures/templates/render_static_component.html.twig b/src/TwigComponent/tests/Fixtures/templates/render_static_component.html.twig deleted file mode 100644 index f082477ec72..00000000000 --- a/src/TwigComponent/tests/Fixtures/templates/render_static_component.html.twig +++ /dev/null @@ -1 +0,0 @@ -{{ component('static.button') }} \ No newline at end of file From 7f4e71e34f84ff1ad70e0b6b3142152ad7b88b8a Mon Sep 17 00:00:00 2001 From: matheo Date: Mon, 3 Jul 2023 14:59:38 +0200 Subject: [PATCH 06/24] rename static to anonymous --- ...{StaticComponent.php => AnonymousComponent.php} | 2 +- src/TwigComponent/src/ComponentFactory.php | 14 +++++++------- src/TwigComponent/src/ComponentRenderer.php | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) rename src/TwigComponent/src/{StaticComponent.php => AnonymousComponent.php} (93%) diff --git a/src/TwigComponent/src/StaticComponent.php b/src/TwigComponent/src/AnonymousComponent.php similarity index 93% rename from src/TwigComponent/src/StaticComponent.php rename to src/TwigComponent/src/AnonymousComponent.php index 06e289d3188..367b7f7a970 100644 --- a/src/TwigComponent/src/StaticComponent.php +++ b/src/TwigComponent/src/AnonymousComponent.php @@ -2,7 +2,7 @@ namespace Symfony\UX\TwigComponent; -class StaticComponent +class AnonymousComponent { private array $props = []; diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php index 2e76ca46aa7..2f972125c35 100644 --- a/src/TwigComponent/src/ComponentFactory.php +++ b/src/TwigComponent/src/ComponentFactory.php @@ -45,7 +45,7 @@ public function metadataFor(string $name): ComponentMetadata $name = $this->classMap[$name] ?? $name; if (!$config = $this->config[$name] ?? null) { - if (($template = $this->findStaticComponentTemplate($name)) !== null) { + if (($template = $this->findAnonymousComponentTemplate($name)) !== null) { return new ComponentMetadata([ 'key' => $name, 'template' => $template, @@ -133,7 +133,7 @@ private function mount(object $component, array &$data): void return; } - if ($component instanceof StaticComponent) { + if ($component instanceof AnonymousComponent) { $component->mount($data); return; @@ -164,8 +164,8 @@ private function getComponent(string $name): object $name = $this->classMap[$name] ?? $name; if (!$this->components->has($name)) { - if ($this->isStaticComponent($name)) { - return new StaticComponent(); + if ($this->isAnonymousComponent($name)) { + return new AnonymousComponent(); } throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no anonymous component founded', $name, implode(', ', array_keys($this->components->getProvidedServices())))); @@ -208,12 +208,12 @@ private function postMount(object $component, array $data): array return $data; } - private function isStaticComponent(string $name): bool + private function isAnonymousComponent(string $name): bool { - return null !== $this->findStaticComponentTemplate($name); + return null !== $this->findAnonymousComponentTemplate($name); } - public function findStaticComponentTemplate(string $name): ?string + public function findAnonymousComponentTemplate(string $name): ?string { $loader = $this->environment->getLoader(); $componentPath = rtrim(str_replace(':', '/', $name)); diff --git a/src/TwigComponent/src/ComponentRenderer.php b/src/TwigComponent/src/ComponentRenderer.php index 5a2ec91db12..2d1ade5b841 100644 --- a/src/TwigComponent/src/ComponentRenderer.php +++ b/src/TwigComponent/src/ComponentRenderer.php @@ -105,7 +105,7 @@ private function preRender(MountedComponent $mounted, array $context = []): PreR // expose public properties and properties marked with ExposeInTemplate attribute iterator_to_array($this->exposedVariables($component, $metadata->isPublicPropsExposed())), - $component instanceof StaticComponent ? $component->getProps() : [] + $component instanceof AnonymousComponent ? $component->getProps() : [] ); $event = new PreRenderEvent($mounted, $metadata, $variables); From 6906714c6f59f4ba89260b472326e7ec25576125 Mon Sep 17 00:00:00 2001 From: matheo Date: Mon, 3 Jul 2023 15:13:56 +0200 Subject: [PATCH 07/24] Add test for component in nested directory --- .../anonymous_component_nested_directory.html.twig | 1 + .../templates/components/Form/SubmitButton.html.twig | 5 +++++ .../tests/Integration/ComponentExtensionTest.php | 8 ++++++++ 3 files changed, 14 insertions(+) create mode 100644 src/TwigComponent/tests/Fixtures/templates/anonymous_component_nested_directory.html.twig create mode 100644 src/TwigComponent/tests/Fixtures/templates/components/Form/SubmitButton.html.twig diff --git a/src/TwigComponent/tests/Fixtures/templates/anonymous_component_nested_directory.html.twig b/src/TwigComponent/tests/Fixtures/templates/anonymous_component_nested_directory.html.twig new file mode 100644 index 00000000000..6f43eda5d3f --- /dev/null +++ b/src/TwigComponent/tests/Fixtures/templates/anonymous_component_nested_directory.html.twig @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/TwigComponent/tests/Fixtures/templates/components/Form/SubmitButton.html.twig b/src/TwigComponent/tests/Fixtures/templates/components/Form/SubmitButton.html.twig new file mode 100644 index 00000000000..1e1f153bffe --- /dev/null +++ b/src/TwigComponent/tests/Fixtures/templates/components/Form/SubmitButton.html.twig @@ -0,0 +1,5 @@ +{% props label, primary = true %} + + \ No newline at end of file diff --git a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php index 7a951034cba..5c4c71148db 100644 --- a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php +++ b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php @@ -174,6 +174,14 @@ public function testRenderAnonymousComponentOverwriteProps(): void $this->assertStringContainsString('class="secondary"', $output); } + public function testRenderAnonymousComponentInNestedDirectory(): void + { + $output = self::getContainer()->get(Environment::class)->render('anonymous_component_nested_directory.html.twig'); + + $this->assertStringContainsString('Submit', $output); + $this->assertStringContainsString('class="primary"', $output); + } + private function renderComponent(string $name, array $data = []): string { return self::getContainer()->get(Environment::class)->render('render_component.html.twig', [ From 6a162a191e702f82d5564c0a0dfb299c27fe0a2f Mon Sep 17 00:00:00 2001 From: matheo Date: Mon, 3 Jul 2023 15:24:03 +0200 Subject: [PATCH 08/24] add files header --- src/TwigComponent/src/AnonymousComponent.php | 14 ++++++++++++++ src/TwigComponent/src/Twig/PropsNode.php | 14 ++++++++++++++ src/TwigComponent/src/Twig/PropsTokenParser.php | 14 ++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/TwigComponent/src/AnonymousComponent.php b/src/TwigComponent/src/AnonymousComponent.php index 367b7f7a970..1fbc9fa048c 100644 --- a/src/TwigComponent/src/AnonymousComponent.php +++ b/src/TwigComponent/src/AnonymousComponent.php @@ -1,7 +1,21 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\UX\TwigComponent; +/** + * @author Matheo Daninos + * + * @internal + */ class AnonymousComponent { private array $props = []; diff --git a/src/TwigComponent/src/Twig/PropsNode.php b/src/TwigComponent/src/Twig/PropsNode.php index 6daec65ad01..adfc9e75b28 100644 --- a/src/TwigComponent/src/Twig/PropsNode.php +++ b/src/TwigComponent/src/Twig/PropsNode.php @@ -1,10 +1,24 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\UX\TwigComponent\Twig; use Twig\Compiler; use Twig\Node\Node; +/** + * @author Matheo Daninos + * + * @internal + */ class PropsNode extends Node { public function __construct(array $propsNames, array $values, $lineno = 0, string $tag = null) diff --git a/src/TwigComponent/src/Twig/PropsTokenParser.php b/src/TwigComponent/src/Twig/PropsTokenParser.php index 92e603e9ea2..3d65c4cb5e7 100644 --- a/src/TwigComponent/src/Twig/PropsTokenParser.php +++ b/src/TwigComponent/src/Twig/PropsTokenParser.php @@ -1,11 +1,25 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\UX\TwigComponent\Twig; use Twig\Node\Node; use Twig\Token; use Twig\TokenParser\AbstractTokenParser; +/** + * @author Matheo Daninos + * + * @internal + */ class PropsTokenParser extends AbstractTokenParser { public function parse(Token $token): Node From 1d5589196c7e600d455c60a2267ff2a5a45c11fb Mon Sep 17 00:00:00 2001 From: matheo Date: Thu, 6 Jul 2023 13:51:28 +0200 Subject: [PATCH 09/24] ComponentTemplateFinderInterface to isolate logic --- src/TwigComponent/src/ComponentFactory.php | 36 ++++--------------- .../src/ComponentTemplateFinder.php | 8 +++++ .../src/ComponentTemplateFinderInterface.php | 8 +++++ .../TwigComponentExtension.php | 14 ++++++-- 4 files changed, 34 insertions(+), 32 deletions(-) create mode 100644 src/TwigComponent/src/ComponentTemplateFinder.php create mode 100644 src/TwigComponent/src/ComponentTemplateFinderInterface.php diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php index 2f972125c35..e956432999c 100644 --- a/src/TwigComponent/src/ComponentFactory.php +++ b/src/TwigComponent/src/ComponentFactory.php @@ -31,7 +31,7 @@ final class ComponentFactory * @param array $classMap */ public function __construct( - private Environment $environment, + private ComponentTemplateFinderInterface $componentTemplateFinder, private ServiceLocator $components, private PropertyAccessorInterface $propertyAccessor, private EventDispatcherInterface $eventDispatcher, @@ -45,14 +45,14 @@ public function metadataFor(string $name): ComponentMetadata $name = $this->classMap[$name] ?? $name; if (!$config = $this->config[$name] ?? null) { - if (($template = $this->findAnonymousComponentTemplate($name)) !== null) { + if (($template = $this->componentTemplateFinder->findAnonymousComponentTemplate($name)) !== null) { return new ComponentMetadata([ 'key' => $name, 'template' => $template, ]); } - throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no template anonymous component founded', $name, implode(', ', array_keys($this->config)))); + $this->throwUnknownComponentException($name); } return new ComponentMetadata($config); @@ -168,7 +168,7 @@ private function getComponent(string $name): object return new AnonymousComponent(); } - throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no anonymous component founded', $name, implode(', ', array_keys($this->components->getProvidedServices())))); + $this->throwUnknownComponentException($name); } return $this->components->get($name); @@ -210,31 +210,7 @@ private function postMount(object $component, array $data): array private function isAnonymousComponent(string $name): bool { - return null !== $this->findAnonymousComponentTemplate($name); - } - - public function findAnonymousComponentTemplate(string $name): ?string - { - $loader = $this->environment->getLoader(); - $componentPath = rtrim(str_replace(':', '/', $name)); - - if ($loader->exists($componentPath)) { - return $componentPath; - } - - if ($loader->exists($componentPath.'.html.twig')) { - return $componentPath.'.html.twig'; - } - - if ($loader->exists('components/'.$componentPath)) { - return 'components/'.$componentPath; - } - - if ($loader->exists('/components/'.$componentPath.'.html.twig')) { - return '/components/'.$componentPath.'.html.twig'; - } - - return null; + return null !== $this->componentTemplateFinder->findAnonymousComponentTemplate($name); } /** @@ -242,6 +218,6 @@ public function findAnonymousComponentTemplate(string $name): ?string */ private function throwUnknownComponentException(string $name): void { - throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s', $name, implode(', ', array_keys($this->config)))); + throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no matching anonymous component template was found', $name, implode(', ', array_keys($this->config)))); } } diff --git a/src/TwigComponent/src/ComponentTemplateFinder.php b/src/TwigComponent/src/ComponentTemplateFinder.php new file mode 100644 index 00000000000..f4cdd2743c9 --- /dev/null +++ b/src/TwigComponent/src/ComponentTemplateFinder.php @@ -0,0 +1,8 @@ +register('ux.twig_component.component_template_finder', ComponentTemplateFinder::class) + ->setArguments([ + new Reference('twig') + ]) + ; + + $container->setAlias(ComponentRendererInterface::class, 'ux.twig_component.component_renderer'); + $container->registerAttributeForAutoconfiguration( AsTwigComponent::class, static function (ChildDefinition $definition, AsTwigComponent $attribute) { @@ -49,7 +59,7 @@ static function (ChildDefinition $definition, AsTwigComponent $attribute) { $container->register('ux.twig_component.component_factory', ComponentFactory::class) ->setArguments([ - new Reference('twig'), + new Reference('ux.twig_component.component_template_finder'), class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %s.', TwigComponentPass::class)) : null, new Reference('property_accessor'), new Reference('event_dispatcher'), @@ -69,7 +79,7 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in % ]) ; - $container->setAlias(ComponentRendererInterface::class, 'ux.twig_component.component_renderer'); + $container->setAlias(ComponentTemplateFinderInterface::class, 'ux.twig_component.component_template_finder'); $container->register('ux.twig_component.twig.component_extension', ComponentExtension::class) ->addTag('twig.extension') From 03504fc79d9dca241b0b63e745b50c06423c49af Mon Sep 17 00:00:00 2001 From: matheo Date: Thu, 6 Jul 2023 13:53:35 +0200 Subject: [PATCH 10/24] destruct option to ExposeInTemplate attribute --- src/TwigComponent/src/AnonymousComponent.php | 12 +++++------- src/TwigComponent/src/Attribute/ExposeInTemplate.php | 4 +++- src/TwigComponent/src/ComponentRenderer.php | 10 +++++++++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/TwigComponent/src/AnonymousComponent.php b/src/TwigComponent/src/AnonymousComponent.php index 1fbc9fa048c..916b4397bfd 100644 --- a/src/TwigComponent/src/AnonymousComponent.php +++ b/src/TwigComponent/src/AnonymousComponent.php @@ -11,6 +11,8 @@ namespace Symfony\UX\TwigComponent; +use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate; + /** * @author Matheo Daninos * @@ -18,20 +20,16 @@ */ class AnonymousComponent { - private array $props = []; + private array $props; - public function setProps(array $props) + public function mount($props = []): void { $this->props = $props; } + #[ExposeInTemplate(destruct: true)] public function getProps(): array { return $this->props; } - - public function mount(array $props = []) - { - $this->setProps($props); - } } diff --git a/src/TwigComponent/src/Attribute/ExposeInTemplate.php b/src/TwigComponent/src/Attribute/ExposeInTemplate.php index 0c62e4d0793..af238184a0c 100644 --- a/src/TwigComponent/src/Attribute/ExposeInTemplate.php +++ b/src/TwigComponent/src/Attribute/ExposeInTemplate.php @@ -26,8 +26,10 @@ final class ExposeInTemplate * to default to property name. * @param string|null $getter The getter method to use. Leave as null * to default to PropertyAccessor logic. + * @param bool $destruct The content should be used as array of variables + * names */ - public function __construct(public ?string $name = null, public ?string $getter = null) + public function __construct(public ?string $name = null, public ?string $getter = null, public bool $destruct = false) { } } diff --git a/src/TwigComponent/src/ComponentRenderer.php b/src/TwigComponent/src/ComponentRenderer.php index 2d1ade5b841..b3d6303630b 100644 --- a/src/TwigComponent/src/ComponentRenderer.php +++ b/src/TwigComponent/src/ComponentRenderer.php @@ -105,7 +105,6 @@ private function preRender(MountedComponent $mounted, array $context = []): PreR // expose public properties and properties marked with ExposeInTemplate attribute iterator_to_array($this->exposedVariables($component, $metadata->isPublicPropsExposed())), - $component instanceof AnonymousComponent ? $component->getProps() : [] ); $event = new PreRenderEvent($mounted, $metadata, $variables); @@ -149,6 +148,15 @@ private function exposedVariables(object $component, bool $exposePublicProps): \ throw new \LogicException(sprintf('Cannot use %s on methods with required parameters (%s::%s).', ExposeInTemplate::class, $component::class, $method->name)); } + + if ($attribute->destruct) { + foreach ($component->{$method->name}() as $prop => $value) { + yield $prop => $value; + } + + return; + } + yield $name => $component->{$method->name}(); } } From 34534685fe437ae6cee578b2fcde379289c1fb03 Mon Sep 17 00:00:00 2001 From: matheo Date: Thu, 6 Jul 2023 13:54:30 +0200 Subject: [PATCH 11/24] ComponentTemplateFinder --- .../src/ComponentTemplateFinder.php | 42 ++++++++++++++++++- .../src/ComponentTemplateFinderInterface.php | 14 ++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/TwigComponent/src/ComponentTemplateFinder.php b/src/TwigComponent/src/ComponentTemplateFinder.php index f4cdd2743c9..560f9a0c6db 100644 --- a/src/TwigComponent/src/ComponentTemplateFinder.php +++ b/src/TwigComponent/src/ComponentTemplateFinder.php @@ -1,8 +1,48 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\UX\TwigComponent; -interface ComponentTemplateFinder +use Twig\Environment; + +/** + * @author Matheo Daninos + */ +class ComponentTemplateFinder implements ComponentTemplateFinderInterface { + public function __construct( + private Environment $environment + ) {} + + public function findAnonymousComponentTemplate(string $name): ?string + { + $loader = $this->environment->getLoader(); + $componentPath = rtrim(str_replace(':', '/', $name)); + + if ($loader->exists($componentPath)) { + return $componentPath; + } + + if ($loader->exists($componentPath.'.html.twig')) { + return $componentPath.'.html.twig'; + } + + if ($loader->exists('components/'.$componentPath)) { + return 'components/'.$componentPath; + } + + if ($loader->exists('components/'.$componentPath.'.html.twig')) { + return 'components/'.$componentPath.'.html.twig'; + } + return null; + } } \ No newline at end of file diff --git a/src/TwigComponent/src/ComponentTemplateFinderInterface.php b/src/TwigComponent/src/ComponentTemplateFinderInterface.php index 85761fa2aed..eb995482d75 100644 --- a/src/TwigComponent/src/ComponentTemplateFinderInterface.php +++ b/src/TwigComponent/src/ComponentTemplateFinderInterface.php @@ -1,8 +1,20 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\UX\TwigComponent; +/** + * @author Matheo Daninos + */ interface ComponentTemplateFinderInterface { - + public function findAnonymousComponentTemplate(string $name): ?string; } \ No newline at end of file From 58c927a34cbeb25f8c8f7ccf6fc83d0807711f99 Mon Sep 17 00:00:00 2001 From: matheo Date: Thu, 6 Jul 2023 13:56:06 +0200 Subject: [PATCH 12/24] fix --- src/TwigComponent/src/Attribute/ExposeInTemplate.php | 12 ++++++------ src/TwigComponent/src/ComponentFactory.php | 1 - src/TwigComponent/src/ComponentRenderer.php | 1 - src/TwigComponent/src/ComponentTemplateFinder.php | 5 +++-- .../src/ComponentTemplateFinderInterface.php | 2 +- .../DependencyInjection/TwigComponentExtension.php | 2 +- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/TwigComponent/src/Attribute/ExposeInTemplate.php b/src/TwigComponent/src/Attribute/ExposeInTemplate.php index af238184a0c..6d62d782460 100644 --- a/src/TwigComponent/src/Attribute/ExposeInTemplate.php +++ b/src/TwigComponent/src/Attribute/ExposeInTemplate.php @@ -22,12 +22,12 @@ final class ExposeInTemplate { /** - * @param string|null $name The variable name to expose. Leave as null - * to default to property name. - * @param string|null $getter The getter method to use. Leave as null - * to default to PropertyAccessor logic. - * @param bool $destruct The content should be used as array of variables - * names + * @param string|null $name The variable name to expose. Leave as null + * to default to property name. + * @param string|null $getter The getter method to use. Leave as null + * to default to PropertyAccessor logic. + * @param bool $destruct The content should be used as array of variables + * names */ public function __construct(public ?string $name = null, public ?string $getter = null, public bool $destruct = false) { diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php index e956432999c..02cf0cecf14 100644 --- a/src/TwigComponent/src/ComponentFactory.php +++ b/src/TwigComponent/src/ComponentFactory.php @@ -17,7 +17,6 @@ use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; use Symfony\UX\TwigComponent\Event\PostMountEvent; use Symfony\UX\TwigComponent\Event\PreMountEvent; -use Twig\Environment; /** * @author Kevin Bond diff --git a/src/TwigComponent/src/ComponentRenderer.php b/src/TwigComponent/src/ComponentRenderer.php index b3d6303630b..ef43ff70c61 100644 --- a/src/TwigComponent/src/ComponentRenderer.php +++ b/src/TwigComponent/src/ComponentRenderer.php @@ -148,7 +148,6 @@ private function exposedVariables(object $component, bool $exposePublicProps): \ throw new \LogicException(sprintf('Cannot use %s on methods with required parameters (%s::%s).', ExposeInTemplate::class, $component::class, $method->name)); } - if ($attribute->destruct) { foreach ($component->{$method->name}() as $prop => $value) { yield $prop => $value; diff --git a/src/TwigComponent/src/ComponentTemplateFinder.php b/src/TwigComponent/src/ComponentTemplateFinder.php index 560f9a0c6db..9e60a3fe0f0 100644 --- a/src/TwigComponent/src/ComponentTemplateFinder.php +++ b/src/TwigComponent/src/ComponentTemplateFinder.php @@ -20,7 +20,8 @@ class ComponentTemplateFinder implements ComponentTemplateFinderInterface { public function __construct( private Environment $environment - ) {} + ) { + } public function findAnonymousComponentTemplate(string $name): ?string { @@ -45,4 +46,4 @@ public function findAnonymousComponentTemplate(string $name): ?string return null; } -} \ No newline at end of file +} diff --git a/src/TwigComponent/src/ComponentTemplateFinderInterface.php b/src/TwigComponent/src/ComponentTemplateFinderInterface.php index eb995482d75..1a76913ea51 100644 --- a/src/TwigComponent/src/ComponentTemplateFinderInterface.php +++ b/src/TwigComponent/src/ComponentTemplateFinderInterface.php @@ -17,4 +17,4 @@ interface ComponentTemplateFinderInterface { public function findAnonymousComponentTemplate(string $name): ?string; -} \ No newline at end of file +} diff --git a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php index cdabc10aaae..80a16875336 100644 --- a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php +++ b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php @@ -44,7 +44,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->register('ux.twig_component.component_template_finder', ComponentTemplateFinder::class) ->setArguments([ - new Reference('twig') + new Reference('twig'), ]) ; From 708ff5d607b583cd3a81f8d15c1738fd6118b1c5 Mon Sep 17 00:00:00 2001 From: matheo Date: Fri, 7 Jul 2023 10:17:32 +0200 Subject: [PATCH 13/24] use RuntimeExeception for prop errors --- src/TwigComponent/src/Twig/PropsNode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TwigComponent/src/Twig/PropsNode.php b/src/TwigComponent/src/Twig/PropsNode.php index adfc9e75b28..91208c5195e 100644 --- a/src/TwigComponent/src/Twig/PropsNode.php +++ b/src/TwigComponent/src/Twig/PropsNode.php @@ -36,7 +36,7 @@ public function compile(Compiler $compiler): void if (!$this->hasNode($name)) { $compiler - ->write('throw new \Exception("'.$name.' should be defined for component '.$this->getTemplateName().'");') + ->write('throw new \Twig\Error\RuntimeError("'.$name.' should be defined for component '.$this->getTemplateName().'");') ->write('}') ; From 8defd7a25c637f7979036a893a7279a17908a4da Mon Sep 17 00:00:00 2001 From: matheo Date: Thu, 20 Jul 2023 15:50:57 +0200 Subject: [PATCH 14/24] make class finals --- src/TwigComponent/src/AnonymousComponent.php | 2 +- src/TwigComponent/src/ComponentTemplateFinder.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TwigComponent/src/AnonymousComponent.php b/src/TwigComponent/src/AnonymousComponent.php index 916b4397bfd..59f07bd3f98 100644 --- a/src/TwigComponent/src/AnonymousComponent.php +++ b/src/TwigComponent/src/AnonymousComponent.php @@ -18,7 +18,7 @@ * * @internal */ -class AnonymousComponent +final class AnonymousComponent { private array $props; diff --git a/src/TwigComponent/src/ComponentTemplateFinder.php b/src/TwigComponent/src/ComponentTemplateFinder.php index 9e60a3fe0f0..889324f5090 100644 --- a/src/TwigComponent/src/ComponentTemplateFinder.php +++ b/src/TwigComponent/src/ComponentTemplateFinder.php @@ -16,7 +16,7 @@ /** * @author Matheo Daninos */ -class ComponentTemplateFinder implements ComponentTemplateFinderInterface +final class ComponentTemplateFinder implements ComponentTemplateFinderInterface { public function __construct( private Environment $environment From 5369c25ba11e9c0b09f700759cf18394fe2978b8 Mon Sep 17 00:00:00 2001 From: matheo Date: Thu, 20 Jul 2023 15:52:05 +0200 Subject: [PATCH 15/24] fixing syntax --- src/TwigComponent/src/Attribute/ExposeInTemplate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TwigComponent/src/Attribute/ExposeInTemplate.php b/src/TwigComponent/src/Attribute/ExposeInTemplate.php index 6d62d782460..dd6d993ae01 100644 --- a/src/TwigComponent/src/Attribute/ExposeInTemplate.php +++ b/src/TwigComponent/src/Attribute/ExposeInTemplate.php @@ -26,7 +26,7 @@ final class ExposeInTemplate * to default to property name. * @param string|null $getter The getter method to use. Leave as null * to default to PropertyAccessor logic. - * @param bool $destruct The content should be used as array of variables + * @param bool $destruct The content should be used as array of variable * names */ public function __construct(public ?string $name = null, public ?string $getter = null, public bool $destruct = false) From 226c3014f656c0e3ddd61ed162431a33d85fc609 Mon Sep 17 00:00:00 2001 From: matheo Date: Thu, 20 Jul 2023 15:59:45 +0200 Subject: [PATCH 16/24] remove tag for ComponentTemplateFinderInterface --- .../src/DependencyInjection/TwigComponentExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php index 80a16875336..415891028f3 100644 --- a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php +++ b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php @@ -79,7 +79,7 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in % ]) ; - $container->setAlias(ComponentTemplateFinderInterface::class, 'ux.twig_component.component_template_finder'); + $container->register(ComponentTemplateFinder::class, 'ux.twig_component.component_template_finder'); $container->register('ux.twig_component.twig.component_extension', ComponentExtension::class) ->addTag('twig.extension') From 82d9a9fcb2ef74e9101c2f3ca6086cfd206d8fe5 Mon Sep 17 00:00:00 2001 From: matheo Date: Thu, 20 Jul 2023 16:04:02 +0200 Subject: [PATCH 17/24] use attributes in template --- .../tests/Fixtures/templates/components/Button.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TwigComponent/tests/Fixtures/templates/components/Button.html.twig b/src/TwigComponent/tests/Fixtures/templates/components/Button.html.twig index 775baaa125c..e747c09b4d4 100644 --- a/src/TwigComponent/tests/Fixtures/templates/components/Button.html.twig +++ b/src/TwigComponent/tests/Fixtures/templates/components/Button.html.twig @@ -1,5 +1,5 @@ {% props label, primary = true %} - \ No newline at end of file From b19c71ae8c90a89d12bde8868786210d933dd84d Mon Sep 17 00:00:00 2001 From: matheo Date: Thu, 20 Jul 2023 16:31:49 +0200 Subject: [PATCH 18/24] destruct properties --- src/TwigComponent/doc/.idea/.gitignore | 0 src/TwigComponent/doc/.idea/doc.iml | 8 +++ src/TwigComponent/doc/.idea/modules.xml | 8 +++ src/TwigComponent/doc/.idea/php.xml | 19 +++++++ src/TwigComponent/doc/.idea/vcs.xml | 6 +++ src/TwigComponent/doc/.idea/workspace.xml | 51 +++++++++++++++++++ src/TwigComponent/src/ComponentRenderer.php | 6 +++ .../TwigComponentExtension.php | 1 - 8 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/TwigComponent/doc/.idea/.gitignore create mode 100644 src/TwigComponent/doc/.idea/doc.iml create mode 100644 src/TwigComponent/doc/.idea/modules.xml create mode 100644 src/TwigComponent/doc/.idea/php.xml create mode 100644 src/TwigComponent/doc/.idea/vcs.xml create mode 100644 src/TwigComponent/doc/.idea/workspace.xml diff --git a/src/TwigComponent/doc/.idea/.gitignore b/src/TwigComponent/doc/.idea/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/TwigComponent/doc/.idea/doc.iml b/src/TwigComponent/doc/.idea/doc.iml new file mode 100644 index 00000000000..c956989b29a --- /dev/null +++ b/src/TwigComponent/doc/.idea/doc.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/TwigComponent/doc/.idea/modules.xml b/src/TwigComponent/doc/.idea/modules.xml new file mode 100644 index 00000000000..989d897f563 --- /dev/null +++ b/src/TwigComponent/doc/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/TwigComponent/doc/.idea/php.xml b/src/TwigComponent/doc/.idea/php.xml new file mode 100644 index 00000000000..f324872a8ba --- /dev/null +++ b/src/TwigComponent/doc/.idea/php.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/TwigComponent/doc/.idea/vcs.xml b/src/TwigComponent/doc/.idea/vcs.xml new file mode 100644 index 00000000000..c2365ab11f9 --- /dev/null +++ b/src/TwigComponent/doc/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/TwigComponent/doc/.idea/workspace.xml b/src/TwigComponent/doc/.idea/workspace.xml new file mode 100644 index 00000000000..f3e44e2aacb --- /dev/null +++ b/src/TwigComponent/doc/.idea/workspace.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + 1681461976725 + + + + + + \ No newline at end of file diff --git a/src/TwigComponent/src/ComponentRenderer.php b/src/TwigComponent/src/ComponentRenderer.php index ef43ff70c61..deec3a40e5d 100644 --- a/src/TwigComponent/src/ComponentRenderer.php +++ b/src/TwigComponent/src/ComponentRenderer.php @@ -131,6 +131,12 @@ private function exposedVariables(object $component, bool $exposePublicProps): \ /** @var ExposeInTemplate $attribute */ $value = $attribute->getter ? $component->{rtrim($attribute->getter, '()')}() : $this->propertyAccessor->getValue($component, $property->name); + if ($attribute->destruct) { + foreach ($value as $key => $destructedValue) { + yield $key => $destructedValue; + } + } + yield $attribute->name ?? $property->name => $value; } diff --git a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php index 415891028f3..59b9789fe64 100644 --- a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php +++ b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php @@ -23,7 +23,6 @@ use Symfony\UX\TwigComponent\ComponentRendererInterface; use Symfony\UX\TwigComponent\ComponentStack; use Symfony\UX\TwigComponent\ComponentTemplateFinder; -use Symfony\UX\TwigComponent\ComponentTemplateFinderInterface; use Symfony\UX\TwigComponent\DependencyInjection\Compiler\TwigComponentPass; use Symfony\UX\TwigComponent\Twig\ComponentExtension; use Symfony\UX\TwigComponent\Twig\ComponentLexer; From 3453c00a96928987852bce74fca4d6fad19d8f22 Mon Sep 17 00:00:00 2001 From: Tomas Date: Wed, 26 Jul 2023 08:55:47 +0300 Subject: [PATCH 19/24] [TwigComponent] Support ...spread operator with html syntax --- src/TwigComponent/CHANGELOG.md | 4 +++ src/TwigComponent/doc/index.rst | 6 +++++ src/TwigComponent/src/Twig/TwigPreLexer.php | 13 +++++++++- .../tests/Unit/TwigPreLexerTest.php | 26 +++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/TwigComponent/CHANGELOG.md b/src/TwigComponent/CHANGELOG.md index 5813bd612e5..9ebf28ca5ce 100644 --- a/src/TwigComponent/CHANGELOG.md +++ b/src/TwigComponent/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 2.11.0 + +- Support ...spread operator with html syntax (requires Twig 3.7.0 or higher) + ## 2.9.0 - The `ComponentAttributes::defaults()` method now accepts any iterable argument. diff --git a/src/TwigComponent/doc/index.rst b/src/TwigComponent/doc/index.rst index 688fcca7acb..24b6b28460e 100644 --- a/src/TwigComponent/doc/index.rst +++ b/src/TwigComponent/doc/index.rst @@ -997,6 +997,12 @@ normal ``{{ }}`` syntax: // and pass object, or table, or anything you imagine +To forward attributes to another component, use `{{...}}` spread operator syntax: + +.. code-block:: html+twig + + + Passing Blocks to your Component ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/TwigComponent/src/Twig/TwigPreLexer.php b/src/TwigComponent/src/Twig/TwigPreLexer.php index 1022a38a5bc..1def6b708aa 100644 --- a/src/TwigComponent/src/Twig/TwigPreLexer.php +++ b/src/TwigComponent/src/Twig/TwigPreLexer.php @@ -22,7 +22,9 @@ class TwigPreLexer private int $length; private int $position = 0; private int $line; - /** @var array */ + /** + * @var array + */ private array $currentComponents = []; public function __construct(int $startingLine = 1) @@ -201,6 +203,15 @@ private function consumeAttributes(string $componentName): string break; } + if ($this->check('{{...') || $this->check('{{ ...')) { + $this->consume('{{...'); + $this->consume('{{ ...'); + $attributes[] = '...'.trim($this->consumeUntil('}}')); + $this->consume('}}'); + + continue; + } + $isAttributeDynamic = false; // :someProp="dynamicVar" diff --git a/src/TwigComponent/tests/Unit/TwigPreLexerTest.php b/src/TwigComponent/tests/Unit/TwigPreLexerTest.php index 37362ac9478..a100f09e45c 100644 --- a/src/TwigComponent/tests/Unit/TwigPreLexerTest.php +++ b/src/TwigComponent/tests/Unit/TwigPreLexerTest.php @@ -216,5 +216,31 @@ public function getLexTests(): iterable '{% verbatim %}{% endverbatim %}', '{% verbatim %}{% endverbatim %}', ]; + + yield 'component_attr_spreading_self_closing' => [ + '', + '{{ component(\'foobar\', { bar: \'baz\', ...attr }) }}', + ]; + yield 'component_attr_spreading_self_closing2' => [ + '', + '{{ component(\'foobar\', { bar: \'baz\', ...customAttrs }) }}', + ]; + yield 'component_attr_spreading_self_closing3' => [ + '', + '{{ component(\'foobar\', { bar: \'baz\', ...attr }) }}', + ]; + + yield 'component_attr_spreading_with_content1' => [ + 'content', + '{% component \'foobar\' with { bar: \'baz\', ...attr } %}{% block content %}content{% endblock %}{% endcomponent %}', + ]; + yield 'component_attr_spreading_with_content2' => [ + 'content', + '{% component \'foobar\' with { bar: \'baz\', ...customAttrs } %}{% block content %}content{% endblock %}{% endcomponent %}', + ]; + yield 'component_attr_spreading_with_content3' => [ + 'content', + '{% component \'foobar\' with { bar: \'baz\', ...attr } %}{% block content %}content{% endblock %}{% endcomponent %}', + ]; } } From 85e0322cbff11cc69af062de21e811e30df85bf5 Mon Sep 17 00:00:00 2001 From: John Ballinger Date: Sat, 5 Aug 2023 10:39:59 +1200 Subject: [PATCH 20/24] Fix javascript error - 2 extra closing brackets in _onPreConnect Code was not valid due to extra closing brackets. Removed extra ")", run code and tested. --- src/Typed/doc/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Typed/doc/index.rst b/src/Typed/doc/index.rst index 479d3419ff8..9878b69c06b 100644 --- a/src/Typed/doc/index.rst +++ b/src/Typed/doc/index.rst @@ -108,10 +108,10 @@ Stimulus controller: console.log(event.detail.options); // Options that will be used to initialize Typed event.detail.options.onBegin = (typed) => { console.log("Typed is ready to type cool messages!"); - }); + }; event.detail.options.onStop = (typed) => { console.log("OK. Enough is enough."); - }); + }; } _onConnect(event) { From 904a61347a652268b0b3d2041143008fe63b2cf0 Mon Sep 17 00:00:00 2001 From: matheo Date: Mon, 7 Aug 2023 16:44:01 +0200 Subject: [PATCH 21/24] remove .idea folder --- src/TwigComponent/doc/.idea/.gitignore | 0 src/TwigComponent/doc/.idea/doc.iml | 8 ---- src/TwigComponent/doc/.idea/modules.xml | 8 ---- src/TwigComponent/doc/.idea/php.xml | 19 --------- src/TwigComponent/doc/.idea/vcs.xml | 6 --- src/TwigComponent/doc/.idea/workspace.xml | 51 ----------------------- 6 files changed, 92 deletions(-) delete mode 100644 src/TwigComponent/doc/.idea/.gitignore delete mode 100644 src/TwigComponent/doc/.idea/doc.iml delete mode 100644 src/TwigComponent/doc/.idea/modules.xml delete mode 100644 src/TwigComponent/doc/.idea/php.xml delete mode 100644 src/TwigComponent/doc/.idea/vcs.xml delete mode 100644 src/TwigComponent/doc/.idea/workspace.xml diff --git a/src/TwigComponent/doc/.idea/.gitignore b/src/TwigComponent/doc/.idea/.gitignore deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/TwigComponent/doc/.idea/doc.iml b/src/TwigComponent/doc/.idea/doc.iml deleted file mode 100644 index c956989b29a..00000000000 --- a/src/TwigComponent/doc/.idea/doc.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/TwigComponent/doc/.idea/modules.xml b/src/TwigComponent/doc/.idea/modules.xml deleted file mode 100644 index 989d897f563..00000000000 --- a/src/TwigComponent/doc/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/TwigComponent/doc/.idea/php.xml b/src/TwigComponent/doc/.idea/php.xml deleted file mode 100644 index f324872a8ba..00000000000 --- a/src/TwigComponent/doc/.idea/php.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/TwigComponent/doc/.idea/vcs.xml b/src/TwigComponent/doc/.idea/vcs.xml deleted file mode 100644 index c2365ab11f9..00000000000 --- a/src/TwigComponent/doc/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/TwigComponent/doc/.idea/workspace.xml b/src/TwigComponent/doc/.idea/workspace.xml deleted file mode 100644 index f3e44e2aacb..00000000000 --- a/src/TwigComponent/doc/.idea/workspace.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - 1681461976725 - - - - - - \ No newline at end of file From 3c9ec5e69b53de05d1965966a41a859cc921c91f Mon Sep 17 00:00:00 2001 From: matheo Date: Mon, 7 Aug 2023 16:44:44 +0200 Subject: [PATCH 22/24] order checks to match best pratice first --- src/TwigComponent/src/ComponentTemplateFinder.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/TwigComponent/src/ComponentTemplateFinder.php b/src/TwigComponent/src/ComponentTemplateFinder.php index 889324f5090..452256a43f4 100644 --- a/src/TwigComponent/src/ComponentTemplateFinder.php +++ b/src/TwigComponent/src/ComponentTemplateFinder.php @@ -28,8 +28,8 @@ public function findAnonymousComponentTemplate(string $name): ?string $loader = $this->environment->getLoader(); $componentPath = rtrim(str_replace(':', '/', $name)); - if ($loader->exists($componentPath)) { - return $componentPath; + if ($loader->exists('components/'.$componentPath.'.html.twig')) { + return 'components/'.$componentPath.'.html.twig'; } if ($loader->exists($componentPath.'.html.twig')) { @@ -40,8 +40,8 @@ public function findAnonymousComponentTemplate(string $name): ?string return 'components/'.$componentPath; } - if ($loader->exists('components/'.$componentPath.'.html.twig')) { - return 'components/'.$componentPath.'.html.twig'; + if ($loader->exists($componentPath)) { + return $componentPath; } return null; From 805461714f81f325d627bdc66b81b6e56af7f9e3 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 7 Aug 2023 14:57:27 -0400 Subject: [PATCH 23/24] [TwigComponent] Adding note about min version of Twig for spread operator --- src/TwigComponent/doc/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/TwigComponent/doc/index.rst b/src/TwigComponent/doc/index.rst index 24b6b28460e..66dd8e2fb2d 100644 --- a/src/TwigComponent/doc/index.rst +++ b/src/TwigComponent/doc/index.rst @@ -997,7 +997,8 @@ normal ``{{ }}`` syntax: // and pass object, or table, or anything you imagine -To forward attributes to another component, use `{{...}}` spread operator syntax: +To forward attributes to another component, use `{{...}}` spread operator syntax. +This requires Twig 3.7.0 or higher: .. code-block:: html+twig From 18e49a96817bf849df78ed27691a9c1af8807943 Mon Sep 17 00:00:00 2001 From: Felix Eymonot Date: Thu, 3 Aug 2023 00:06:09 +0200 Subject: [PATCH 24/24] [TogglePassword] Add example page for ux.symfony.com --- ux.symfony.com/assets/controllers.json | 9 ++ .../assets/images/toggle-password.png | Bin 0 -> 1252 bytes ux.symfony.com/composer.json | 1 + ux.symfony.com/composer.lock | 84 +++++++++++++++++- ux.symfony.com/config/bundles.php | 1 + .../src/Controller/UxPackagesController.php | 9 ++ .../src/Form/TogglePasswordForm.php | 21 +++++ .../src/Service/PackageRepository.php | 8 ++ ux.symfony.com/symfony.lock | 3 + .../ux_packages/toggle_password.html.twig | 38 ++++++++ 10 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 ux.symfony.com/assets/images/toggle-password.png create mode 100644 ux.symfony.com/src/Form/TogglePasswordForm.php create mode 100644 ux.symfony.com/templates/ux_packages/toggle_password.html.twig diff --git a/ux.symfony.com/assets/controllers.json b/ux.symfony.com/assets/controllers.json index c0929e47cc5..3d1edb5d879 100644 --- a/ux.symfony.com/assets/controllers.json +++ b/ux.symfony.com/assets/controllers.json @@ -74,6 +74,15 @@ "fetch": "lazy" } }, + "@symfony/ux-toggle-password": { + "toggle-password": { + "enabled": true, + "fetch": "eager", + "autoimport": { + "@symfony/ux-toggle-password/dist/style.min.css": true + } + } + }, "@symfony/ux-turbo": { "turbo-core": { "enabled": true, diff --git a/ux.symfony.com/assets/images/toggle-password.png b/ux.symfony.com/assets/images/toggle-password.png new file mode 100644 index 0000000000000000000000000000000000000000..b8cfaaaed26c0ae7c1a02126acb0fac4077b0a52 GIT binary patch literal 1252 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAifV{wqX6XVU3I`u#fXMsm#F#`j) zFbFd;%$g$sRNx!n6XFV#{67k)6au_5=No_lnqLy+7tFveq!ryYNh-Pb=G#C2?@m!l zIQ=!ldg1%I#->ZR$*&Qpc=~I3t;w-}r*ydV&#{DkKOe~KdV;5pmqUE-=R@k3E3b+@ zlDpliapuOyW?Nb53o8^ickZ01lKy*6woUHzvS~*iSgLlbT|ZdWv}NB$)`A~5ZPI2l zFfeWPba4!+nDaI)I{KD_056lHVYa76P%cYLQv;U}Yf|RLSs|>hYrR~(^}p3!{{R2R zfwoC=V|_kk-!GC&KR0jg+`Bg=`-&ePYVH2de!k`U^XI=m{N5ux@Apa;wdGBhnSOC6 zRy{kIk`Oa>f<~(#bK=Xy44#=Ma;7Y8609|O9+39$V}>YC<-Nu;PG|gKu$obzWy-zsvkgHPjYZw{+?qkC|7UCQgae)^uLKX`iE) zDEI1|xkmF<4zCaKY>HxB{_M2Q+_TG7)`l&xTqr1EaI?nsXP#C}Y70ZL2Et7VxyJTQ|&15RS zgNH_i#g$B1M)gyVs>A!+)g-kwjh?)l&r@=0{Q~~Z8_svWYi{cl`FW;>^_1mPuSeml zP1hWgIu|@UX>qa4|JU57l)t`i@}6+b?|#J6PYrr{)qSg_o;qmCWV4+}&hC2Ve!1;0 zZ?3yd+@i^0veA{tnpW0p9@MeKlF`U%H^!=Tecp3){>!0q29K8tm7v~HEYSutX3EQ!ak>DgO|8Bym-_T`|j?x*X9T8w2B0-f`bpb>x!f!HP>J%fC*2V)64& zc+{G^r(!qUo#rL%;`O}acF+uiBuGf<@e$UcfHeP?3_-=-!LsCn< z>ztKeKJ2ay*54G;^>y~Pto`@6zFv^%&tI#w{aA&~x-QQCX`g>uZ))tjKmGDM%MH5= efJsgH596H`y-Ph;uHOvII}Dz#elF{r5}E)z!1V0^ literal 0 HcmV?d00001 diff --git a/ux.symfony.com/composer.json b/ux.symfony.com/composer.json index 1dee3df94e2..3249763a073 100644 --- a/ux.symfony.com/composer.json +++ b/ux.symfony.com/composer.json @@ -41,6 +41,7 @@ "symfony/ux-react": "2.x-dev", "symfony/ux-svelte": "2.x-dev", "symfony/ux-swup": "2.x-dev", + "symfony/ux-toggle-password": "@dev", "symfony/ux-translator": "2.x-dev", "symfony/ux-turbo": "2.x-dev", "symfony/ux-twig-component": "2.x-dev", diff --git a/ux.symfony.com/composer.lock b/ux.symfony.com/composer.lock index 96a06650b67..b9d2a7f9c27 100644 --- a/ux.symfony.com/composer.lock +++ b/ux.symfony.com/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6d7bacb3285da14665a59f381e0a7d3b", + "content-hash": "99023d3f81bb17d7ffaa098319d5d126", "packages": [ { "name": "babdev/pagerfanta-bundle", @@ -7901,6 +7901,87 @@ ], "time": "2023-06-26T17:45:00+00:00" }, + { + "name": "symfony/ux-toggle-password", + "version": "2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-toggle-password.git", + "reference": "5a5b21dfa753904c6633c7607480c9ecf8c54dbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-toggle-password/zipball/5a5b21dfa753904c6633c7607480c9ecf8c54dbc", + "reference": "5a5b21dfa753904c6633c7607480c9ecf8c54dbc", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/options-resolver": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0" + }, + "require-dev": { + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/twig-bundle": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0", + "twig/twig": "^2.14.7|^3.0.4" + }, + "default-branch": true, + "type": "symfony-bundle", + "extra": { + "thanks": { + "name": "symfony/ux", + "url": "https://github.com/symfony/ux" + } + }, + "autoload": { + "psr-4": { + "Symfony\\UX\\TogglePassword\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "FĂ©lix Eymonot", + "email": "felix.eymonot@alximy.io" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Toggle visibility of password inputs for Symfony Forms", + "homepage": "https://symfony.com", + "keywords": [ + "symfony-ux" + ], + "support": { + "source": "https://github.com/symfony/ux-toggle-password/tree/2.x" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-31T20:26:27+00:00" + }, { "name": "symfony/ux-translator", "version": "2.x-dev", @@ -11862,6 +11943,7 @@ "symfony/ux-react": 20, "symfony/ux-svelte": 20, "symfony/ux-swup": 20, + "symfony/ux-toggle-password": 20, "symfony/ux-translator": 20, "symfony/ux-turbo": 20, "symfony/ux-twig-component": 20, diff --git a/ux.symfony.com/config/bundles.php b/ux.symfony.com/config/bundles.php index 4fd55e6d87f..f34509b05e6 100644 --- a/ux.symfony.com/config/bundles.php +++ b/ux.symfony.com/config/bundles.php @@ -29,4 +29,5 @@ Symfony\UX\Typed\TypedBundle::class => ['all' => true], Symfony\UX\Swup\SwupBundle::class => ['all' => true], Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['dev' => true, 'test' => true], + Symfony\UX\TogglePassword\TogglePasswordBundle::class => ['all' => true], ]; diff --git a/ux.symfony.com/src/Controller/UxPackagesController.php b/ux.symfony.com/src/Controller/UxPackagesController.php index ad5795a1020..c0c035eba51 100644 --- a/ux.symfony.com/src/Controller/UxPackagesController.php +++ b/ux.symfony.com/src/Controller/UxPackagesController.php @@ -5,6 +5,7 @@ use App\Entity\Food; use App\Form\DropzoneForm; use App\Form\TimeForAMealForm; +use App\Form\TogglePasswordForm; use App\Service\PackageRepository; use Doctrine\Common\Collections\Collection; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -122,6 +123,14 @@ public function translator(): Response return $this->render('ux_packages/translator.html.twig'); } + #[Route('/toggle-password', name: 'app_toggle_password')] + public function togglePassword(): Response + { + return $this->render('ux_packages/toggle_password.html.twig', [ + 'form' => $this->createForm(TogglePasswordForm::class), + ]); + } + private function getDeliciousWord(): string { $words = ['delicious', 'scrumptious', 'mouth-watering', 'life-changing', 'world-beating', 'freshly-squeezed']; diff --git a/ux.symfony.com/src/Form/TogglePasswordForm.php b/ux.symfony.com/src/Form/TogglePasswordForm.php new file mode 100644 index 00000000000..31e23842982 --- /dev/null +++ b/ux.symfony.com/src/Form/TogglePasswordForm.php @@ -0,0 +1,21 @@ +add('email', EmailType::class) + ->add('password', PasswordType::class, [ + 'toggle' => true, + ]) + ; + } +} diff --git a/ux.symfony.com/src/Service/PackageRepository.php b/ux.symfony.com/src/Service/PackageRepository.php index 6189cfb3d0e..9ee86ba910c 100644 --- a/ux.symfony.com/src/Service/PackageRepository.php +++ b/ux.symfony.com/src/Service/PackageRepository.php @@ -134,6 +134,14 @@ public function findAll(string $query = null): array 'Trigger native browser notifications from inside PHP', 'I need to send browser notifications', ), + new Package( + 'toggle-password', + 'Toggle Password', + 'app_toggle_password', + 'linear-gradient(142.8deg, #FD963C -14.8%, #BE0404 95.43%)', + 'Switch the visibility of a password field', + 'I need to toggle the visibility of a password field', + ), (new Package( 'typed', 'Typed', diff --git a/ux.symfony.com/symfony.lock b/ux.symfony.com/symfony.lock index bfa20eb1056..8ef201a5b78 100644 --- a/ux.symfony.com/symfony.lock +++ b/ux.symfony.com/symfony.lock @@ -589,6 +589,9 @@ "symfony/ux-swup": { "version": "v2.1.1" }, + "symfony/ux-toggle-password": { + "version": "2.x-dev" + }, "symfony/ux-translator": { "version": "2.9999999", "recipe": { diff --git a/ux.symfony.com/templates/ux_packages/toggle_password.html.twig b/ux.symfony.com/templates/ux_packages/toggle_password.html.twig new file mode 100644 index 00000000000..4248af5ec9c --- /dev/null +++ b/ux.symfony.com/templates/ux_packages/toggle_password.html.twig @@ -0,0 +1,38 @@ +{% extends 'packageBase.html.twig' %} + +{% block component_header %} + {% component PackageHeader with { + package: 'toggle-password', + eyebrowText: 'Switch between password & text' + } %} + {% block title_header %} + From password to text + & vice versa + {% endblock %} + + {% block sub_content %} + Upgrade your password field to toggle between text and password. + {% endblock %} + {% endcomponent %} +{% endblock %} + +{% block code_block_left %} + +{% endblock %} + +{% block code_block_right %} + +{% endblock %} + +{% block demo_title %}UX Toggle Password{% endblock %} + +{% block demo_content %} + {{ form_start(form) }} + {{ form_row(form.email) }} + {{ form_row(form.password) }} + + {{ form_end(form) }} +{% endblock %}