├── .github └── workflows │ ├── deploy-docs.yml │ └── run-tests.yml ├── .styleci.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── UPGRADING.md ├── composer.json ├── docs ├── _index.md ├── about-us.md ├── advanced-usage │ ├── _index.md │ ├── prefixing-components.md │ ├── sharing-data-with-context.md │ └── transforming-data-with-view-models.md ├── basic-usage │ ├── _index.md │ ├── using-slots.md │ ├── using-variables.md │ └── writing-your-first-component.md ├── changelog.md ├── images │ └── header.jpg ├── installation-setup.md ├── introduction.md ├── postcardware.md ├── questions-issues.md ├── requirements.md ├── under-the-hood │ ├── _index.md │ └── from-bladex-to-blade.md └── upgrading.md ├── resources └── views │ └── context.blade.php └── src ├── BladeX.php ├── BladeXServiceProvider.php ├── ClosureViewModel.php ├── Compiler.php ├── Component.php ├── ComponentCollection.php ├── ComponentDirectory ├── ComponentDirectory.php ├── NamespacedDirectory.php └── RegularDirectory.php ├── ContextStack.php ├── Exceptions └── CouldNotRegisterComponent.php ├── Facades └── BladeX.php └── ViewModel.php /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: deploy-docs 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'docs/**' 7 | 8 | jobs: 9 | deploy: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | name: Deploy docs 14 | 15 | steps: 16 | - name: Netlify deploy 17 | uses: wei/curl@v1 18 | with: 19 | args: -X POST -d {} ${{ secrets.NETLIFY_DEPLOY_URL }}?trigger_title=laravel-blade-x 20 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | fail-fast: true 11 | matrix: 12 | os: [ubuntu-latest, windows-latest] 13 | php: [7.2, 7.3, 7.4] 14 | laravel: [5.8.*, 6.*] 15 | dependency-version: [prefer-lowest, prefer-stable] 16 | include: 17 | - laravel: 6.* 18 | testbench: 4.* 19 | - laravel: 5.8.* 20 | testbench: 3.8.* 21 | 22 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} 23 | 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v1 27 | 28 | - name: Cache dependencies 29 | uses: actions/cache@v1 30 | with: 31 | path: ~/.composer/cache/files 32 | key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 33 | 34 | - name: Setup PHP 35 | uses: shivammathur/setup-php@v1 36 | with: 37 | php-version: ${{ matrix.php }} 38 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick 39 | coverage: none 40 | 41 | - name: Install dependencies 42 | run: | 43 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 44 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest 45 | - name: Execute tests 46 | run: vendor/bin/phpunit 47 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | 3 | disabled: 4 | - single_class_element_per_statement 5 | - self_accessor 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-blade-x` will be documented in this file 4 | 5 | ## 2.6.0 - 2020-03-02 6 | 7 | - add attribute spread operator (#112) 8 | 9 | ## 2.5.0 - 2020-01-10 10 | 11 | - add support for namespaced attributes 12 | 13 | ## 2.4.1 - 2019-12-14 14 | 15 | - fix tag names for snake_case view files (#100 via #92) 16 | - cleaned up and documented some regular expressions 17 | 18 | ## 2.4.0 - 2019-10-17 19 | 20 | - add `**.*` as a wildcard for all files including sub-directories 21 | 22 | ## 2.3.0 - 2019-09-04 23 | 24 | - add support for Laravel 6.0 25 | - add support for prefixing single components and collections 26 | - allow to remove the namespace for a single component 27 | 28 | ## 2.2.3 - 2019-07-25 29 | 30 | - do not use deprecated Laravel helpers 31 | 32 | ## 2.2.2 - 2019-01-07 33 | 34 | - loading views moved from `register` method to `boot` 35 | 36 | ## 2.2.1 - 2019-06-28 37 | 38 | - fix multi-line closing tags triggering other components 39 | 40 | ## 2.2.0 - 2018-02-27 41 | 42 | - drop support for Laravel 5.7 and lower 43 | - drop support for PHP 7.1 and lower 44 | 45 | ## 2.1.2 - 2018-02-27 46 | 47 | - add support for Laravel 5.8 48 | 49 | ## 2.1.1 - 2018-02-01 50 | 51 | - use Arr:: and Str:: functions 52 | 53 | ## 2.1.0 - 2018-10-30 54 | 55 | - add support for namespaced subdirectories 56 | 57 | ## 2.0.3 - 2018-10-22 58 | 59 | - fix compiling empty tag attributes 60 | 61 | ## 2.0.2 - 2018-10-08 62 | 63 | - fix edge-case for self-closing tags with newlines 64 | 65 | ## 2.0.1 - 2018-10-08 66 | 67 | - fix edge-case for boolean attributes in opening tags 68 | 69 | ## 2.0.0 - 2018-10-08 70 | 71 | - simplified component registration 72 | - internal cleanup 73 | - some edge case fixes 74 | 75 | ## 1.2.3 - 2018-10-07 76 | 77 | - fix nested components and slots without spaces or on a single line 78 | - fix edge-case for slots with weird content 79 | 80 | ## 1.2.2 - 2018-10-04 81 | 82 | - remove unnecessary dependencies `symfony/css-selector` and `symfony/dom-crawler` 83 | 84 | ## 1.2.1 - 2018-10-04 85 | 86 | - fix test 87 | 88 | ## 1.2.0 - 2018-10-04 89 | 90 | - add support for context 91 | - add closure based view models 92 | - bugfixes for component attributes with weird characters 93 | 94 | ## 1.1.2 - 2018-10-02 95 | 96 | - make sure a component is registered only once 97 | - make sure kebab-cased props get passed to components camelCased 98 | 99 | ## 1.1.1 - 2018-10-01 100 | 101 | - add view models 102 | 103 | ## 1.0.0 - 2018-10-01 104 | 105 | - initial release 106 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | ## Procedure 26 | 27 | Before filing an issue: 28 | 29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. 30 | - Check to make sure your feature suggestion isn't already present within the project. 31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. 32 | - Check the pull requests tab to ensure that the feature isn't already in progress. 33 | 34 | Before submitting a pull request: 35 | 36 | - Check the codebase to ensure that your feature doesn't already exist. 37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. 38 | 39 | ## Requirements 40 | 41 | If the project maintainer has any additional requirements, you will find them listed here. 42 | 43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). 44 | 45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 46 | 47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 48 | 49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. 50 | 51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 52 | 53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 54 | 55 | **Happy coding**! 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notice 2 | 3 | We have abandoned this package because Laravel 7 introduced native support for Blade-X style components. 4 | 5 | Only use this package if you're on Laravel 6 or below. 6 | 7 | When upgrading to Laravel 7 you should convert your Blade X components to native Laravel Blade components 8 | 9 | # Supercharged Blade components 10 | 11 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-blade-x.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-blade-x) 12 | [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/spatie/laravel-blade-x/run-tests?label=tests)](https://github.com/spatie/laravel-blade-x/actions?query=workflow%3Arun-tests+branch%3Amaster) 13 | [![Quality Score](https://img.shields.io/scrutinizer/g/spatie/laravel-blade-x.svg?style=flat-square)](https://scrutinizer-ci.com/g/spatie/laravel-blade-x) 14 | [![StyleCI](https://github.styleci.io/repos/150733020/shield?branch=master)](https://github.styleci.io/repos/150733020) 15 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-blade-x.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-blade-x) 16 | 17 | This package provides an easy way to render custom HTML components in your Blade views. 18 | 19 | Here's an example. Instead of this 20 | 21 | ```blade 22 |

My view

23 | 24 | @include('myAlert', ['type' => 'error', 'message' => $message]) 25 | ``` 26 | 27 | you can write this: 28 | 29 | ```blade 30 |

My view

31 | 32 | 33 | ``` 34 | 35 | You can place the content of that alert in a simple Blade view that needs to be [registered](https://docs.spatie.be/laravel-blade-x/v2/basic-usage/writing-your-first-component) before using the `my-alert` component. 36 | 37 | ```blade 38 | {{-- resources/views/components/myAlert.blade.php --}} 39 | 40 |
41 | {{ $message }} 42 |
43 | ``` 44 | 45 | ## Installation 46 | 47 | You can install the package via Composer: 48 | 49 | ```bash 50 | composer require spatie/laravel-blade-x 51 | ``` 52 | 53 | The package will automatically register itself. 54 | 55 | ## Documentation 56 | 57 | You'll find the documentation on [https://docs.spatie.be/laravel-blade-x/v2/introduction](https://docs.spatie.be/laravel-blade-x/v2/introduction). 58 | 59 | Find yourself stuck using the package? Found a bug? Do you have general questions or suggestions for improving the media library? Feel free to [create an issue on GitHub](https://github.com/spatie/laravel-blade-x/issues), we'll try to address it as soon as possible. 60 | 61 | If you've found a bug regarding security please mail [freek@spatie.be](mailto:freek@spatie.be) instead of using the issue tracker. 62 | 63 | ## Upgrading major versions 64 | 65 | Please see [UPGRADING](UPGRADING.md) for more information on how to upgrade from one major version to the other. 66 | 67 | ## Testing 68 | 69 | ``` bash 70 | composer test 71 | ``` 72 | 73 | ## Changelog 74 | 75 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 76 | 77 | ## Contributing 78 | 79 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 80 | 81 | ## Postcardware 82 | 83 | You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 84 | 85 | Our address is: Spatie, Samberstraat 69D, 2060 Antwerp, Belgium. 86 | 87 | We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards). 88 | 89 | ## Credits 90 | 91 | - [Brent Roose](https://github.com/brendt) 92 | - [Alex Vanderbist](https://github.com/alexvanderbist) 93 | - [Ruben Van Assche](https://github.com/rubenvanassche) 94 | - [Sebastian De Deyne](https://github.com/sebdedeyne) 95 | - [Freek Van der Herten](https://github.com/freekmurze) 96 | - [All Contributors](../../contributors) 97 | 98 | ## Support us 99 | 100 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). 101 | 102 | Does your business depend on our contributions? Reach out and support us on [Patreon](https://www.patreon.com/spatie). 103 | All pledges will be dedicated to allocating workforce on maintenance and new awesome stuff. 104 | 105 | ## License 106 | 107 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 108 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | ## From v1 to v2 2 | 3 | The way components are registered is greatly simplified. 4 | 5 | In `v1` you could pass a custom tag of your component as a second parameter. In `v2` you'll need to use the `tag` method. 6 | 7 | ```php 8 | // v1 9 | BladeX::component('components.myAlert', 'my-custom-tag') 10 | 11 | // v2 12 | BladeX::component('components.myAlert')->tag('my-custom-tag'); 13 | ``` 14 | 15 | In v1 you could register an entire directory of components using the `components()` method. This method has now been deprecated. Use the `component` method and the `.*` notation to register a directory. 16 | 17 | ```php 18 | // v1 19 | BladeX::components('components'); 20 | 21 | // v2 22 | BladeX::component('components.*'); 23 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-blade-x", 3 | "description": "Supercharged Blade components", 4 | "keywords": [ 5 | "spatie", 6 | "blade", 7 | "components", 8 | "html", 9 | "laravel-blade-x" 10 | ], 11 | "homepage": "https://github.com/spatie/laravel-blade-x", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Sebastian De Deyne", 16 | "email": "sebastian@spatie.be", 17 | "homepage": "https://spatie.be", 18 | "role": "Developer" 19 | }, 20 | { 21 | "name": "Brent Roose", 22 | "email": "brent@spatie.be", 23 | "homepage": "https://spatie.be", 24 | "role": "Developer" 25 | }, 26 | { 27 | "name": "Ruben Van Assche", 28 | "email": "ruben@spatie.be", 29 | "homepage": "https://spatie.be", 30 | "role": "Developer" 31 | }, 32 | { 33 | "name": "Freek Van der Herten", 34 | "email": "freek@spatie.be", 35 | "homepage": "https://spatie.be", 36 | "role": "Developer" 37 | }, 38 | { 39 | "name": "Alex Vanderbist", 40 | "email": "alex@spatie.be", 41 | "homepage": "https://spatie.be", 42 | "role": "Developer" 43 | } 44 | ], 45 | "require": { 46 | "php": "^7.2", 47 | "illuminate/view": "~5.8.0|^6.0" 48 | }, 49 | "require-dev": { 50 | "orchestra/testbench": "~3.8.0|^4.0", 51 | "phpunit/phpunit": "^8.0", 52 | "spatie/phpunit-snapshot-assertions": "^2.1" 53 | }, 54 | "autoload": { 55 | "psr-4": { 56 | "Spatie\\BladeX\\": "src" 57 | } 58 | }, 59 | "autoload-dev": { 60 | "psr-4": { 61 | "Spatie\\BladeX\\Tests\\": "tests" 62 | } 63 | }, 64 | "scripts": { 65 | "test": "vendor/bin/phpunit", 66 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 67 | 68 | }, 69 | "config": { 70 | "sort-packages": true 71 | }, 72 | "extra": { 73 | "laravel": { 74 | "providers": [ 75 | "Spatie\\BladeX\\BladeXServiceProvider" 76 | ], 77 | "aliases": { 78 | "BladeX": "Spatie\\BladeX\\Facades\\BladeX" 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /docs/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: v2 3 | slogan: Supercharged Blade Components 4 | githubUrl: https://github.com/spatie/laravel-blade-x 5 | branch: master 6 | --- 7 | -------------------------------------------------------------------------------- /docs/about-us.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About us 3 | weight: 8 4 | --- 5 | 6 | [Spatie](https://spatie.be) is a webdesign agency based in Antwerp, Belgium. 7 | 8 | Open source software is used in all projects we deliver. Laravel, Nginx, Ubuntu are just a few 9 | of the free pieces of software we use every single day. For this, we are very grateful. 10 | When we feel we have solved a problem in a way that can help other developers, 11 | we release our code as open source software [on GitHub](https://spatie.be/open-source). 12 | 13 | Does your business depend on our contributions? Reach out and support us on [Patreon](https://www.patreon.com/spatie). 14 | All pledges will be dedicated to allocating workforce on maintenance and new awesome stuff. -------------------------------------------------------------------------------- /docs/advanced-usage/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Advanced usage 3 | weight: 2 4 | --- 5 | -------------------------------------------------------------------------------- /docs/advanced-usage/prefixing-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prefixing components 3 | weight: 3 4 | --- 5 | 6 | If you're using Vue components in combination with BladeX components, it might be worth prefixing your BladeX components to make them easily distinguishable from the rest. 7 | 8 | Setting a global prefix can easily be done before or after registering components: 9 | 10 | ```php 11 | BladeX::component('components.myAlert'); 12 | 13 | BladeX::prefix('x'); 14 | ``` 15 | 16 | All your registered components can now be used like this: 17 | 18 | ```html 19 | 20 | ``` -------------------------------------------------------------------------------- /docs/advanced-usage/sharing-data-with-context.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sharing data with context 3 | weight: 2 4 | --- 5 | 6 | By default BladeX components only have access to variables that are passed to them via attributes or via the view model. In some cases you might find yourself passing the same variables to multiple components. Take a look at this example where we are building a form using some BladeX components. 7 | 8 | ```html 9 | 10 | 11 | 12 | 13 | 14 | ``` 15 | 16 | You can avoid having to pass `$user` to each component separatly by using a special component called `context`. 17 | 18 | You can rewrite the above as 19 | 20 | ```html 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ``` 29 | 30 | **Note**: If you are using a custom component prefix (e.g. `BladeX::prefix('x')`, for more details see the following chapter), you will have to use the prefix for this special component as well: `` 31 | 32 | The only restriction of `context` is that you shouldn't use it around a slot. 33 | 34 | ```html 35 | 36 | {{-- Don't do this: the variables in `context` will not be passed to `$slot`. 37 | 38 | {{ $slot }} 39 | 40 | 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/advanced-usage/transforming-data-with-view-models.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Transforming data with view models 3 | weight: 1 4 | --- 5 | 6 | Before rendering a BladeX component you might want to transform the passed data, or add inject view data. You can do this using a view model. Let's take a look at an example where we render a `select` element with a list of countries. 7 | 8 | To make a BladeX component use a view model, pass a class name to the `viewModel` method. 9 | 10 | ```php 11 | BladeX::component('select-field')->viewModel(SelectFieldViewModel::class); 12 | ``` 13 | 14 | Before reviewing the contents of the component and the view model itself, let's take a look at the `select-field` component in use. 15 | 16 | ```blade 17 | @php 18 | // In a real app this data would probably come from a controller 19 | // or a view composer. 20 | $countries = [ 21 | 'be' => 'Belgium', 22 | 'fr' => 'France', 23 | 'nl' => 'The Netherlands', 24 | ]; 25 | @endphp 26 | 27 | 28 | ``` 29 | 30 | Next, let's take a look at the `SelectFieldViewModel::class`: 31 | 32 | ```php 33 | class SelectFieldViewModel extends ViewModel 34 | { 35 | /** @var string */ 36 | public $name; 37 | 38 | /** @var array */ 39 | public $options; 40 | 41 | /** @var string */ 42 | private $selected; 43 | 44 | public function __construct(string $name, array $options, string $selected = null) 45 | { 46 | $this->name = $name; 47 | 48 | $this->options = $options; 49 | 50 | $this->selected = old($name, $selected); 51 | } 52 | 53 | public function isSelected(string $optionName): bool 54 | { 55 | return $optionName === $this->selected; 56 | } 57 | } 58 | ``` 59 | 60 | Notice that this class extends `\Spatie\BladeX\ViewModel`. Every attribute on the `select-field` will be passed to its constructor. This happens based on the attribute names: the `name` attribute will be passed to the `$name` constructor argument, the `options` attribute will be passed to the `$options` argument and so on. Any other argument will be resolved out of Laravel's [IoC container](https://laravel.com/docs/5.6/container), so you can inject external dependencies. 61 | 62 | All public properties and methods on the view model will be passed to the Blade view that will render the `select-field` component. Public methods will be available in as a closure stored in the variable that is named after the public method in view model. This is what that view looks like. 63 | 64 | ```blade 65 | 70 | ``` 71 | 72 | When rendering the BladeX component, this is the output: 73 | 74 | ```html 75 |
76 | 81 |
82 | ``` 83 | -------------------------------------------------------------------------------- /docs/basic-usage/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Basic usage 3 | weight: 1 4 | --- 5 | -------------------------------------------------------------------------------- /docs/basic-usage/using-slots.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Using slots 3 | weight: 3 4 | --- 5 | 6 | 7 | BladeX support slots too. This layout component 8 | 9 | ```html 10 | {{-- resources/views/components/layout.blade.php --}} 11 | 12 |
13 |

{{ $title }}

14 |
15 |
16 | {{ $sidebar }} 17 |
18 |
19 | {!! $slot !!} 20 |
21 |
22 |
23 | {{ $footer }} 24 |
25 |
26 | ``` 27 | 28 | can be used in your views like this: 29 | 30 | ```html 31 | 32 | 33 | 34 |
    35 |
  • Home
  • 36 |
  • Contact
  • 37 |
38 |
39 | 40 | 41 |
Whose motorcycle is this?
42 | 43 | 44 | It's not a motorcycle honey, it's a chopper. 45 |
46 | ``` 47 | 48 | and will result in: 49 | 50 | ```html 51 |
52 |

Zed's chopper

53 |
54 |
55 |
    56 |
  • Home
  • 57 |
  • Contact
  • 58 |
59 |
60 |
61 |
Whose motorcycle is this?
62 |
63 |
64 |
65 | It's not a motorcycle honey, it's a chopper. 66 |
67 |
68 | ``` 69 | -------------------------------------------------------------------------------- /docs/basic-usage/using-variables.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Using variables 3 | weight: 2 4 | --- 5 | 6 | 7 | When using a BladeX component all attributes will be passed as variables to the underlying Blade view. 8 | 9 | ```html 10 | {{-- the `myAlert` view will receive a variable named `type` with a value of `error` --}} 11 | 12 | 13 | ``` 14 | 15 | If you want to pass on a PHP variable or something that needs to be evaluated you must prefix the attribute name with `:`. 16 | 17 | ```html 18 | {{-- the `myAlert` view will receive the contents of `$message` --}} 19 | 20 | 21 | {{-- the `myAlert` view will receive the uppercased contents of `$message` --}} 22 | 23 | ``` 24 | 25 | ## Spread operator for attributes 26 | 27 | Passing an array of component attributes to a BladeX component can be achieved using the spread operator: 28 | 29 | ```html 30 | 31 | ``` 32 | 33 | Combining multiple destructured arrays and normal attributes is supported too! Normal attributes will override attributes in the spreaded attributes array. 34 | 35 | ```html 36 | 42 | ``` 43 | 44 | ## Boolean attributes 45 | 46 | Boolean attributes (attributes without a value), e.g. `` will be passed to the component as variables evaluating to `true`. 47 | 48 | ```html 49 | {{-- the `checkboxInput` view will receive a `$checked` variable that evaluates as true --}} 50 | 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/basic-usage/writing-your-first-component.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Writing your first component 3 | weight: 1 4 | --- 5 | 6 | The contents of a component can be stored in a simple Blade view. 7 | 8 | ```html 9 | {{-- resources/views/components/myAlert.blade.php --}} 10 | 11 |
12 | {{ $message }} 13 |
14 | ``` 15 | 16 | Before using that component you must first register it. Typically you would do this in the `AppServiceProvider boot() method` or a service provider of your own 17 | 18 | ```php 19 | BladeX::component('components.myAlert'); 20 | ``` 21 | 22 | BladeX will automatically kebab-case your Blade view name and use that as the tag for your component. So for the example above the tag to use your component would be `my-alert`. 23 | 24 | If you want to use a custom tag name use the `tag`-method. 25 | 26 | ```php 27 | BladeX::component('components.myAlert')->tag('my-custom-tag'); 28 | ``` 29 | 30 | You can also register an entire directory like this. 31 | 32 | ```php 33 | // This will register all Blade views that are stored in `resources/views/components` 34 | 35 | BladeX::component('components.*'); 36 | ``` 37 | 38 | Or you can register multiple directories like this. 39 | 40 | ```php 41 | // This will register all Blade views that are stored in both `resources/views/components` and `resources/views/layouts` 42 | 43 | BladeX::component([ 44 | 'components.*', 45 | 'layouts.*', 46 | ]); 47 | ``` 48 | 49 | You can also register sub-directories like this. 50 | 51 | ```php 52 | // This will register all Blade views that are stored in both `resources/views/components` and `resources/views/layouts` 53 | 54 | BladeX::component( 55 | 'components.**.*', 56 | ); 57 | ``` 58 | 59 | In your Blade view you can now use the component using the kebab-cased name: 60 | 61 | ```html 62 |

My view

63 | 64 | 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Changelog 3 | weight: 6 4 | --- 5 | 6 | All notable changes to laravel-blade-x are documented [on GitHub](https://github.com/spatie/laravel-blade-x/blob/master/CHANGELOG.md) 7 | -------------------------------------------------------------------------------- /docs/images/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatie/laravel-blade-x/a6d43adefb5885642b4b42909405e1e5c4102c1a/docs/images/header.jpg -------------------------------------------------------------------------------- /docs/installation-setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation & setup 3 | weight: 4 4 | --- 5 | 6 | `laravel-blade-x` can be installed via composer: 7 | 8 | ```bash 9 | composer require spatie/laravel-blade-x:^2.0.0 10 | ``` 11 | 12 | The package will automatically register a service provider and a facade. -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | weight: 1 4 | --- 5 | 6 | # Notice 7 | 8 | We have abandoned this package because Laravel 7 introduced native support for Blade-X style components. 9 | 10 | Only use this package if you're on Laravel 6 or below. 11 | 12 | When upgrading to Laravel 7 you should convert your Blade X components to native Laravel Blade components 13 | 14 | # Introduction 15 | 16 | This package provides an easy way to render custom HTML components in your Blade views. 17 | 18 | Instead of this: 19 | 20 | ```blade 21 |

My view

22 | 23 | @include('myAlert', ['type' => 'error', 'message' => $message]) 24 | ``` 25 | 26 | you can write this 27 | 28 | ```blade 29 |

My view

30 | 31 | 32 | ``` 33 | 34 | You can place the content of that alert in a simple blade view that needs to be [registered](https://docs.spatie.be/laravel-blade-x/v2/basic-usage/writing-your-first-component) before using the `my-alert` component. 35 | 36 | ```blade 37 | {{-- resources/views/components/myAlert.blade.php --}} 38 | 39 |
40 | {{ $message }} 41 |
42 | ``` 43 | 44 | ### A note on performance 45 | 46 | Because our package operates before Blade compiles views there is no performance penalty. Blade can just cache all views. 47 | 48 | Because all the transformations are done serverside, there are no interop problems with a clientside framework such as Vue or React. 49 | 50 | For more information on how the transformation is done, checkout [the "From BladeX to Blade" section](https://docs.spatie.be/laravel-blade-x/v2/under-the-hood/from-bladex-to-blade). 51 | -------------------------------------------------------------------------------- /docs/postcardware.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Postcardware 3 | weight: 2 4 | --- 5 | 6 | You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 7 | 8 | Our address is: Spatie, Samberstraat 69D, 2060 Antwerp, Belgium. 9 | 10 | All postcards are published on the [open source section](https://spatie.be/open-source) on our website. 11 | 12 | Does your business depend on our contributions? Reach out and support us on [Patreon](https://www.patreon.com/spatie). 13 | All pledges will be dedicated to allocating workforce on maintenance and new awesome stuff. 14 | -------------------------------------------------------------------------------- /docs/questions-issues.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Questions and issues 3 | weight: 5 4 | --- 5 | 6 | Find yourself stuck using the package? Found a bug? Do you have general questions or suggestions for improving Laravel Blade X? Feel free to [create an issue on GitHub](https://github.com/spatie/laravel-blade-x/issues), we'll try to address it as soon as possible. 7 | 8 | If you've found a bug regarding security please mail [freek@spatie.be](mailto:freek@spatie.be) instead of using the issue tracker. 9 | -------------------------------------------------------------------------------- /docs/requirements.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Requirements 3 | weight: 3 4 | --- 5 | 6 | This package requires: 7 | - PHP 7.2 or higher 8 | - Laravel 5.8.0 or higher -------------------------------------------------------------------------------- /docs/under-the-hood/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Under the hood 3 | weight: 3 4 | --- 5 | -------------------------------------------------------------------------------- /docs/under-the-hood/from-bladex-to-blade.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: From BladeX to Blade 3 | weight: 1 4 | --- 5 | 6 | When you register a component 7 | 8 | ```php 9 | BladeX::component('components.myAlert') 10 | ``` 11 | with this HTML 12 | 13 | ```html 14 | {{-- resources/views/components/myAlert.blade.php --}} 15 |
16 | {{ $message }} 17 |
18 | ``` 19 | 20 | and use it in your Blade view like this, 21 | 22 | ```html 23 | 24 | ``` 25 | 26 | our package will replace that HTML in your view with this: 27 | 28 | ```html 29 | @component('components/myAlert', ['type' => 'error','message' => $message,])@endcomponent 30 | ``` 31 | 32 | After that conversion Blade will compile (and possibly cache) that view. 33 | 34 | Because all this happens before any HTML is sent to the browser, client side frameworks like Vue.js will never see the original html you wrote (with the custom tags). 35 | -------------------------------------------------------------------------------- /docs/upgrading.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Upgrading 3 | weight: 7 4 | --- 5 | 6 | Instructions on how to upgrade from an earlier major version of `laravel-blade-x` are available [on GitHub](https://github.com/spatie/laravel-blade-x/blob/master/UPGRADING.md) 7 | -------------------------------------------------------------------------------- /resources/views/context.blade.php: -------------------------------------------------------------------------------- 1 | {{-- This view serves no other purpose that to be able to hook into usage. --}} 2 | -------------------------------------------------------------------------------- /src/BladeX.php: -------------------------------------------------------------------------------- 1 | registerViews($view); 29 | 30 | return null; 31 | } 32 | 33 | if ($view instanceof Component) { 34 | $this->registeredComponents[] = $view; 35 | 36 | return $view; 37 | } 38 | 39 | if (! is_string($view)) { 40 | throw CouldNotRegisterComponent::invalidArgument(); 41 | } 42 | 43 | if (Str::endsWith($view, '*')) { 44 | $this->registerComponents($view); 45 | 46 | return null; 47 | } 48 | 49 | if (! view()->exists($view)) { 50 | throw CouldNotRegisterComponent::viewNotFound($view, $tag); 51 | } 52 | 53 | $component = new Component($view, $tag); 54 | 55 | $this->registeredComponents[] = $component; 56 | 57 | return $component; 58 | } 59 | 60 | /** 61 | * @param string|string[] $viewDirectory 62 | * 63 | * @return \Spatie\BladeX\ComponentCollection|\Spatie\BladeX\Component[] 64 | */ 65 | public function components($viewDirectory): ComponentCollection 66 | { 67 | if (is_iterable($viewDirectory)) { 68 | $components = new ComponentCollection(); 69 | 70 | foreach ($viewDirectory as $singleViewDirectory) { 71 | if (Str::endsWith($singleViewDirectory, '*')) { 72 | $components = $components->merge($this->registerComponents($singleViewDirectory)); 73 | } else { 74 | $components->push($this->component($singleViewDirectory)); 75 | } 76 | } 77 | 78 | return $components; 79 | } 80 | 81 | return $this->registerComponents($viewDirectory); 82 | } 83 | 84 | /** 85 | * @return \Spatie\BladeX\Component[] 86 | */ 87 | public function registeredComponents(): array 88 | { 89 | return collect($this->registeredComponents)->reverse()->unique(function (Component $component) { 90 | return $component->getTag(); 91 | })->reverse()->values()->all(); 92 | } 93 | 94 | public function prefix(string $prefix = ''): self 95 | { 96 | $this->prefix = $prefix; 97 | 98 | return $this; 99 | } 100 | 101 | public function getPrefix(): string 102 | { 103 | return empty($this->prefix) ? '' : Str::finish($this->prefix, '-'); 104 | } 105 | 106 | /** 107 | * @internal 108 | * 109 | * @param string $viewDirectory 110 | * 111 | * @return \Spatie\BladeX\ComponentCollection|\Spatie\BladeX\Component[] 112 | */ 113 | public function registerComponents(string $viewDirectory) 114 | { 115 | if (! Str::endsWith($viewDirectory, '*')) { 116 | throw CouldNotRegisterComponent::viewDirectoryWithoutWildcard($viewDirectory); 117 | } 118 | 119 | $includeSubdirectories = Str::endsWith($viewDirectory, '**.*'); 120 | 121 | $componentDirectory = Str::contains($viewDirectory, '::') 122 | ? new NamespacedDirectory($viewDirectory, $includeSubdirectories) 123 | : new RegularDirectory($viewDirectory, $includeSubdirectories); 124 | 125 | return $this->registerViews( 126 | ComponentCollection::make($componentDirectory->getFiles()) 127 | 128 | ->filter(function (SplFileInfo $file) { 129 | return Str::endsWith($file->getFilename(), '.blade.php'); 130 | }) 131 | ->map(function (SplFileInfo $file) use ($componentDirectory) { 132 | return $componentDirectory->getViewName($file); 133 | }) 134 | ); 135 | } 136 | 137 | /** 138 | * @param iterable|string[] $views 139 | * 140 | * @return \Spatie\BladeX\ComponentCollection|\Spatie\BladeX\Component[] 141 | */ 142 | protected function registerViews(iterable $views): ComponentCollection 143 | { 144 | return ComponentCollection::make($views)->map(function (string $viewName) { 145 | return $this->component($viewName); 146 | }); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/BladeXServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(BladeX::class); 12 | $this->app->singleton(ContextStack::class); 13 | 14 | $this->app->alias(BladeX::class, 'blade-x'); 15 | } 16 | 17 | public function boot() 18 | { 19 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'bladex'); 20 | 21 | $this->app['blade.compiler']->extend(function ($view) { 22 | return $this->app[Compiler::class]->compile($view); 23 | }); 24 | 25 | $this->app->make(BladeX::class)->component('bladex::context', 'context'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ClosureViewModel.php: -------------------------------------------------------------------------------- 1 | arguments = $arguments[0] ?? []; 19 | } 20 | 21 | public function withClosure(Closure $closure) 22 | { 23 | $this->closure = $closure; 24 | 25 | return $this; 26 | } 27 | 28 | public function toArray(): array 29 | { 30 | return app()->call($this->closure, $this->arguments); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Compiler.php: -------------------------------------------------------------------------------- 1 | bladeX = $bladeX; 15 | } 16 | 17 | public function compile(string $viewContents): string 18 | { 19 | return array_reduce( 20 | $this->bladeX->registeredComponents(), 21 | [$this, 'parseComponentHtml'], 22 | $viewContents 23 | ); 24 | } 25 | 26 | protected function parseComponentHtml(string $viewContents, Component $component) 27 | { 28 | $viewContents = $this->parseSlots($viewContents); 29 | 30 | $viewContents = $this->parseSelfClosingTags($viewContents, $component); 31 | 32 | $viewContents = $this->parseOpeningTags($viewContents, $component); 33 | 34 | $viewContents = $this->parseClosingTags($viewContents, $component); 35 | 36 | return $viewContents; 37 | } 38 | 39 | protected function parseSelfClosingTags(string $viewContents, Component $component): string 40 | { 41 | $pattern = "/ 42 | < 43 | \s* 44 | {$component->getTag()} 45 | \s* 46 | (? 47 | (?: 48 | \s+ 49 | [\w\-:.\$]+ 50 | ( 51 | = 52 | (?: 53 | \\\"[^\\\"]+\\\" 54 | | 55 | \'[^\']+\' 56 | | 57 | [^\'\\\"=<>]+ 58 | ) 59 | )? 60 | )* 61 | \s* 62 | ) 63 | \/> 64 | /x"; 65 | 66 | return preg_replace_callback($pattern, function (array $matches) use ($component) { 67 | $attributes = $this->getAttributesFromAttributeString($matches['attributes']); 68 | 69 | return $this->componentString($component, $attributes); 70 | }, $viewContents); 71 | } 72 | 73 | protected function parseOpeningTags(string $viewContents, Component $component): string 74 | { 75 | $pattern = "/ 76 | < 77 | \s* 78 | {$component->getTag()} 79 | (? 80 | (?: 81 | \s+ 82 | [\w\-:.\$]+ 83 | ( 84 | = 85 | (?: 86 | \\\"[^\\\"]*\\\" 87 | | 88 | \'[^\']*\' 89 | | 90 | [^\'\\\"=<>]+ 91 | ) 92 | ) 93 | ?)* 94 | \s* 95 | ) 96 | (? 98 | /x"; 99 | 100 | return preg_replace_callback($pattern, function (array $matches) use ($component) { 101 | $attributes = $this->getAttributesFromAttributeString($matches['attributes']); 102 | 103 | return $this->componentStartString($component, $attributes); 104 | }, $viewContents); 105 | } 106 | 107 | protected function parseClosingTags(string $viewContents, Component $component): string 108 | { 109 | $pattern = "/<\/\s*{$component->getTag()}\s*>/"; 110 | 111 | return preg_replace($pattern, $this->componentEndString($component), $viewContents); 112 | } 113 | 114 | protected function componentString(Component $component, array $attributes = []): string 115 | { 116 | return $this->componentStartString($component, $attributes).$this->componentEndString($component); 117 | } 118 | 119 | protected function componentStartString(Component $component, array $attributes = []): string 120 | { 121 | $attributesString = $this->attributesToString($attributes); 122 | 123 | if ($component->view === 'bladex::context') { 124 | return " @php(app(Spatie\BladeX\ContextStack::class)->push({$attributesString})) "; 125 | } 126 | 127 | if ($component->viewModel) { 128 | $attributesString = " 129 | array_merge( 130 | app(Spatie\BladeX\ContextStack::class)->read(), 131 | {$attributesString}, 132 | app( 133 | '{$component->viewModel}', 134 | array_merge( 135 | app(Spatie\BladeX\ContextStack::class)->read(), 136 | {$attributesString} 137 | ) 138 | )->toArray() 139 | )"; 140 | } 141 | 142 | return " @component( 143 | '{$component->view}', 144 | array_merge(app(Spatie\BladeX\ContextStack::class)->read(), 145 | {$attributesString}) 146 | ) "; 147 | } 148 | 149 | protected function componentEndString(Component $component): string 150 | { 151 | if ($component->view === 'bladex::context') { 152 | return "@php(app(Spatie\BladeX\ContextStack::class)->pop())"; 153 | } 154 | 155 | return ' @endcomponent '; 156 | } 157 | 158 | protected function getAttributesFromAttributeString(string $attributeString): array 159 | { 160 | $attributeString = $this->parseBindAttributes($attributeString); 161 | 162 | $pattern = '/ 163 | (?[\w\-:.\$]+) 164 | ( 165 | = 166 | (? 167 | ( 168 | \"[^\"]+\" 169 | | 170 | \\\'[^\\\']+\\\' 171 | | 172 | [^\s>]+ 173 | ) 174 | ) 175 | )? 176 | /x'; 177 | 178 | if (! preg_match_all($pattern, $attributeString, $matches, PREG_SET_ORDER)) { 179 | return []; 180 | } 181 | 182 | $namespaces = []; 183 | $attributes = collect($matches)->mapWithKeys(function ($match) use (&$namespaces) { 184 | $attribute = Str::camel($match['attribute']); 185 | $value = $match['value'] ?? null; 186 | 187 | if (is_null($value)) { 188 | $value = 'true'; 189 | $attribute = Str::start($attribute, 'bind:'); 190 | } 191 | 192 | $value = $this->stripQuotes($value); 193 | 194 | if (Str::startsWith($attribute, 'bind:')) { 195 | $attribute = Str::after($attribute, 'bind:'); 196 | } else { 197 | $value = str_replace("'", "\\'", $value); 198 | $value = "'{$value}'"; 199 | 200 | if (Str::contains($attribute, ':')) { 201 | $namespace = Str::before($attribute, ':'); 202 | $attribute = Str::after($attribute, ':'); 203 | 204 | data_set($namespaces, "{$namespace}.{$attribute}", $value); 205 | 206 | return []; 207 | } 208 | } 209 | 210 | return [$attribute => $value]; 211 | }); 212 | 213 | return $attributes->merge($namespaces)->toArray(); 214 | } 215 | 216 | protected function parseSlots(string $viewContents): string 217 | { 218 | $openingPattern = '/<\s*slot\s+name=(?(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+))\s*>/'; 219 | $viewContents = preg_replace_callback($openingPattern, function ($matches) { 220 | $name = $this->stripQuotes($matches['name']); 221 | 222 | return " @slot('{$name}') "; 223 | }, $viewContents); 224 | 225 | $closingPattern = '/<\/\s*slot[^>]*>/'; 226 | $viewContents = preg_replace($closingPattern, ' @endslot', $viewContents); 227 | 228 | return $viewContents; 229 | } 230 | 231 | /** 232 | * Adds the `bind:` prefix for all bound data attributes. 233 | * E.g. `foo=bar :name=alex` becomes `foo=bar bind:name=alex`. 234 | * 235 | * @param string $attributeString 236 | * 237 | * @return string 238 | */ 239 | protected function parseBindAttributes(string $attributeString): string 240 | { 241 | $pattern = "/ 242 | (?:^|\s+) # start of the string or whitespace between attributes 243 | : # attribute needs to start with a semicolon 244 | ([\w-]+) # match the actual attribute name 245 | = # only match attributes that have a value 246 | /xm"; 247 | 248 | return preg_replace($pattern, ' bind:$1=', $attributeString); 249 | } 250 | 251 | protected function attributesToString(array $attributes): string 252 | { 253 | $attributes = collect($attributes); 254 | 255 | $string = []; 256 | 257 | $plainAttributes = $attributes 258 | ->reject(function ($value, string $attribute) { 259 | return Str::startsWith($attribute, '...$'); 260 | }) 261 | ->map(function ($value, string $attribute) { 262 | if (is_array($value)) { 263 | $value = $this->attributesToString($value); 264 | } 265 | 266 | return "'{$attribute}' => {$value}"; 267 | }); 268 | 269 | $string['plain'] = '['.$plainAttributes->implode(',').']'; 270 | 271 | $string['spread'] = $attributes 272 | ->filter(function ($value, string $attribute) { 273 | return Str::startsWith($attribute, '...$'); 274 | }) 275 | ->map(function ($value, string $attribute) { 276 | $attribute = Str::after($attribute, '...'); 277 | 278 | return "{$attribute}"; 279 | }) 280 | ->implode(','); 281 | 282 | if (empty($string['spread'])) { 283 | return $string['plain']; 284 | } 285 | 286 | return 'array_merge('.$string['spread'].','.$string['plain'].')'; 287 | } 288 | 289 | protected function stripQuotes(string $string): string 290 | { 291 | if (Str::startsWith($string, ['"', '\''])) { 292 | return substr($string, 1, -1); 293 | } 294 | 295 | return $string; 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/Component.php: -------------------------------------------------------------------------------- 1 | view = $view; 41 | 42 | $this->tag = $tag; 43 | 44 | $this->prefix = $prefix; 45 | 46 | $this->withNamespace = $withNamespace; 47 | 48 | $this->bladeX = app(BladeX::class); 49 | } 50 | 51 | public function tag(string $tag) 52 | { 53 | $this->tag = $tag; 54 | 55 | return $this; 56 | } 57 | 58 | public function prefix(string $prefix) 59 | { 60 | $this->prefix = $prefix; 61 | 62 | return $this; 63 | } 64 | 65 | public function withoutNamespace() 66 | { 67 | $this->withNamespace = false; 68 | 69 | return $this; 70 | } 71 | 72 | public function viewModel(string $viewModel) 73 | { 74 | if (! class_exists($viewModel)) { 75 | throw CouldNotRegisterComponent::viewModelNotFound($this->tag, $viewModel); 76 | } 77 | 78 | if (! is_a($viewModel, Arrayable::class, true)) { 79 | throw CouldNotRegisterComponent::viewModelNotArrayable($this->tag, $viewModel); 80 | } 81 | 82 | $this->viewModel = $viewModel; 83 | 84 | return $this; 85 | } 86 | 87 | public function getTag(): string 88 | { 89 | $tag = empty($this->prefix) ? $this->bladeX->getPrefix() : Str::finish($this->prefix, '-'); 90 | 91 | $tag .= empty($this->tag) ? $this->determineDefaultTag() : $this->tag; 92 | 93 | return $tag; 94 | } 95 | 96 | protected function determineDefaultTag(): string 97 | { 98 | $baseComponentName = explode('.', $this->view); 99 | 100 | $tag = Str::kebab(end($baseComponentName)); 101 | 102 | $tag = str_replace('_', '-', $tag); 103 | 104 | if (Str::contains($this->view, '::') && ! Str::contains($tag, '::')) { 105 | $namespace = Str::before($this->view, '::'); 106 | $tag = "{$namespace}::{$tag}"; 107 | } 108 | 109 | if (! $this->withNamespace && Str::contains($tag, '::')) { 110 | $tag = Str::after($tag, '::'); 111 | } 112 | 113 | return $tag; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/ComponentCollection.php: -------------------------------------------------------------------------------- 1 | each->prefix($prefix); 15 | 16 | return $this; 17 | } 18 | 19 | public function withoutNamespace() 20 | { 21 | $this->each->withoutNamespace(); 22 | 23 | return $this; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ComponentDirectory/ComponentDirectory.php: -------------------------------------------------------------------------------- 1 | getRelativePath(); 22 | 23 | $view = Str::replaceLast('.blade.php', '', $viewFile->getFilename()); 24 | 25 | return implode('.', array_filter([ 26 | $this->viewDirectory, 27 | $subDirectory, 28 | $view, 29 | ])); 30 | } 31 | 32 | public function getFiles(): array 33 | { 34 | return $this->includeSubdirectories 35 | ? File::allFiles($this->getAbsoluteDirectory()) 36 | : File::files($this->getAbsoluteDirectory()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ComponentDirectory/NamespacedDirectory.php: -------------------------------------------------------------------------------- 1 | namespace, $viewDirectory] = explode('::', $viewDirectory); 18 | $this->viewDirectory = trim(Str::before($viewDirectory, '*'), '.'); 19 | $this->includeSubdirectories = $includeSubdirectories; 20 | } 21 | 22 | public function getAbsoluteDirectory(): string 23 | { 24 | $viewPath = str_replace('.', '/', $this->viewDirectory); 25 | 26 | $absoluteDirectory = View::getFinder()->getHints()[$this->namespace][0] ?? null; 27 | 28 | if (! $absoluteDirectory) { 29 | throw CouldNotRegisterComponent::viewPathNotFound($viewPath); 30 | } 31 | 32 | return $viewPath ? "{$absoluteDirectory}/{$viewPath}" : $absoluteDirectory; 33 | } 34 | 35 | public function getViewName(SplFileInfo $viewFile): string 36 | { 37 | return "{$this->namespace}::".parent::getViewName($viewFile); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ComponentDirectory/RegularDirectory.php: -------------------------------------------------------------------------------- 1 | viewDirectory = Str::before($viewDirectory, '.*'); 14 | $this->includeSubdirectories = $includeSubdirectories; 15 | } 16 | 17 | public function getAbsoluteDirectory(): string 18 | { 19 | $viewPath = str_replace('.', '/', $this->viewDirectory); 20 | 21 | $absoluteDirectory = collect(View::getFinder()->getPaths()) 22 | ->map(function (string $path) use ($viewPath) { 23 | return realpath($path.'/'.$viewPath); 24 | }) 25 | ->filter() 26 | ->first(); 27 | 28 | if (! $absoluteDirectory) { 29 | throw CouldNotRegisterComponent::viewPathNotFound($viewPath); 30 | } 31 | 32 | return $absoluteDirectory; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ContextStack.php: -------------------------------------------------------------------------------- 1 | stack[] = array_merge($this->read(), $data); 15 | } 16 | 17 | public function read(): array 18 | { 19 | return Arr::last($this->stack) ?? []; 20 | } 21 | 22 | public function pop() 23 | { 24 | array_pop($this->stack); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Exceptions/CouldNotRegisterComponent.php: -------------------------------------------------------------------------------- 1 | getProperties(ReflectionProperty::IS_PUBLIC)) 21 | ->reject(function (ReflectionProperty $property) { 22 | return $this->shouldIgnore($property->getName()); 23 | }) 24 | ->mapWithKeys(function (ReflectionProperty $property) { 25 | return [$property->getName() => $this->{$property->getName()}]; 26 | }); 27 | 28 | $publicMethods = collect($class->getMethods(ReflectionMethod::IS_PUBLIC)) 29 | ->reject(function (ReflectionMethod $method) { 30 | return $this->shouldIgnore($method->getName()); 31 | }) 32 | ->mapWithKeys(function (ReflectionMethod $method) { 33 | return [$method->getName() => $this->createVariableFromMethod($method)]; 34 | }); 35 | 36 | return $publicProperties->merge($publicMethods)->all(); 37 | } 38 | 39 | protected function shouldIgnore(string $methodName): bool 40 | { 41 | if (Str::startsWith($methodName, '__')) { 42 | return true; 43 | } 44 | 45 | return in_array($methodName, $this->ignoredMethods()); 46 | } 47 | 48 | protected function ignoredMethods(): array 49 | { 50 | return array_merge([ 51 | 'toArray', 52 | 'toResponse', 53 | 'view', 54 | ], $this->ignore); 55 | } 56 | 57 | protected function createVariableFromMethod(ReflectionMethod $method) 58 | { 59 | if ($method->getNumberOfParameters() === 0) { 60 | return $this->{$method->getName()}(); 61 | } 62 | 63 | return Closure::fromCallable([$this, $method->getName()]); 64 | } 65 | } 66 | --------------------------------------------------------------------------------