diff --git a/composer.lock b/composer.lock index 93b697067d..6c2ce0286d 100644 --- a/composer.lock +++ b/composer.lock @@ -129,16 +129,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.298.4", + "version": "3.300.3", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "c61e8f8640352c4cad1ec364c0801b8adae8adb9" + "reference": "a1202d1a82593fc2cdad54d627e54c518a984738" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c61e8f8640352c4cad1ec364c0801b8adae8adb9", - "reference": "c61e8f8640352c4cad1ec364c0801b8adae8adb9", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a1202d1a82593fc2cdad54d627e54c518a984738", + "reference": "a1202d1a82593fc2cdad54d627e54c518a984738", "shasum": "" }, "require": { @@ -218,32 +218,32 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.298.4" + "source": "https://github.com/aws/aws-sdk-php/tree/3.300.3" }, - "time": "2024-02-06T19:09:25+00:00" + "time": "2024-02-22T19:20:21+00:00" }, { "name": "blade-ui-kit/blade-heroicons", - "version": "2.2.1", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/blade-ui-kit/blade-heroicons.git", - "reference": "bcf4be8f6bbde0bb4c23f2e3fb189b88dec1580a" + "reference": "a265dbcf2a098121aad05752d0bba9f59022e4ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/blade-ui-kit/blade-heroicons/zipball/bcf4be8f6bbde0bb4c23f2e3fb189b88dec1580a", - "reference": "bcf4be8f6bbde0bb4c23f2e3fb189b88dec1580a", + "url": "https://api.github.com/repos/blade-ui-kit/blade-heroicons/zipball/a265dbcf2a098121aad05752d0bba9f59022e4ba", + "reference": "a265dbcf2a098121aad05752d0bba9f59022e4ba", "shasum": "" }, "require": { - "blade-ui-kit/blade-icons": "^1.1", - "illuminate/support": "^9.0|^10.0", + "blade-ui-kit/blade-icons": "^1.6", + "illuminate/support": "^9.0|^10.0|^11.0", "php": "^8.0" }, "require-dev": { - "orchestra/testbench": "^7.0|^8.0", - "phpunit/phpunit": "^9.0" + "orchestra/testbench": "^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.0|^10.5|^11.0" }, "type": "library", "extra": { @@ -277,7 +277,7 @@ ], "support": { "issues": "https://github.com/blade-ui-kit/blade-heroicons/issues", - "source": "https://github.com/blade-ui-kit/blade-heroicons/tree/2.2.1" + "source": "https://github.com/blade-ui-kit/blade-heroicons/tree/2.3.0" }, "funding": [ { @@ -289,35 +289,35 @@ "type": "paypal" } ], - "time": "2023-12-18T20:44:03+00:00" + "time": "2024-02-07T16:33:46+00:00" }, { "name": "blade-ui-kit/blade-icons", - "version": "1.5.3", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/blade-ui-kit/blade-icons.git", - "reference": "b5e6603218e2347ac81cb780bc6f71c8c3b31f5b" + "reference": "89660d93f9897d231e9113ba203cd17f4c5efade" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/blade-ui-kit/blade-icons/zipball/b5e6603218e2347ac81cb780bc6f71c8c3b31f5b", - "reference": "b5e6603218e2347ac81cb780bc6f71c8c3b31f5b", + "url": "https://api.github.com/repos/blade-ui-kit/blade-icons/zipball/89660d93f9897d231e9113ba203cd17f4c5efade", + "reference": "89660d93f9897d231e9113ba203cd17f4c5efade", "shasum": "" }, "require": { - "illuminate/contracts": "^8.0|^9.0|^10.0", - "illuminate/filesystem": "^8.0|^9.0|^10.0", - "illuminate/support": "^8.0|^9.0|^10.0", - "illuminate/view": "^8.0|^9.0|^10.0", + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0", + "illuminate/filesystem": "^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "illuminate/view": "^8.0|^9.0|^10.0|^11.0", "php": "^7.4|^8.0", - "symfony/console": "^5.3|^6.0", - "symfony/finder": "^5.3|^6.0" + "symfony/console": "^5.3|^6.0|^7.0", + "symfony/finder": "^5.3|^6.0|^7.0" }, "require-dev": { - "mockery/mockery": "^1.3", - "orchestra/testbench": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^9.0" + "mockery/mockery": "^1.5.1", + "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.0|^10.5|^11.0" }, "bin": [ "bin/blade-icons-generate" @@ -370,20 +370,20 @@ "type": "paypal" } ], - "time": "2023-10-18T10:50:13+00:00" + "time": "2024-02-07T16:09:20+00:00" }, { "name": "brianium/paratest", - "version": "v7.4.1", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "b2830e330011d59a799c0002e118f5b4bbdb9604" + "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/b2830e330011d59a799c0002e118f5b4bbdb9604", - "reference": "b2830e330011d59a799c0002e118f5b4bbdb9604", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", + "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", "shasum": "" }, "require": { @@ -391,13 +391,13 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", - "fidry/cpu-core-counter": "^1.0.0", + "fidry/cpu-core-counter": "^1.1.0", "jean85/pretty-package-versions": "^2.0.5", "php": "~8.2.0 || ~8.3.0", "phpunit/php-code-coverage": "^10.1.11 || ^11.0.0", "phpunit/php-file-iterator": "^4.1.0 || ^5.0.0", "phpunit/php-timer": "^6.0.0 || ^7.0.0", - "phpunit/phpunit": "^10.5.9 || ^11.0.2", + "phpunit/phpunit": "^10.5.9 || ^11.0.3", "sebastian/environment": "^6.0.1 || ^7.0.0", "symfony/console": "^6.4.3 || ^7.0.3", "symfony/process": "^6.4.3 || ^7.0.3" @@ -406,11 +406,11 @@ "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "phpstan/phpstan": "^1.10.57", + "phpstan/phpstan": "^1.10.58", "phpstan/phpstan-deprecation-rules": "^1.1.4", "phpstan/phpstan-phpunit": "^1.3.15", "phpstan/phpstan-strict-rules": "^1.5.2", - "squizlabs/php_codesniffer": "^3.8.1", + "squizlabs/php_codesniffer": "^3.9.0", "symfony/filesystem": "^6.4.3 || ^7.0.3" }, "bin": [ @@ -452,7 +452,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.4.1" + "source": "https://github.com/paratestphp/paratest/tree/v7.4.3" }, "funding": [ { @@ -464,7 +464,7 @@ "type": "paypal" } ], - "time": "2024-02-06T13:50:28+00:00" + "time": "2024-02-20T07:24:02+00:00" }, { "name": "brick/math", @@ -895,16 +895,16 @@ }, { "name": "doctrine/dbal", - "version": "3.8.1", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c9ea252cdce4da324ede3d6c5913dd89f769afd2" + "reference": "a19a1d05ca211f41089dffcc387733a6875196cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c9ea252cdce4da324ede3d6c5913dd89f769afd2", - "reference": "c9ea252cdce4da324ede3d6c5913dd89f769afd2", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/a19a1d05ca211f41089dffcc387733a6875196cb", + "reference": "a19a1d05ca211f41089dffcc387733a6875196cb", "shasum": "" }, "require": { @@ -988,7 +988,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.8.1" + "source": "https://github.com/doctrine/dbal/tree/3.8.2" }, "funding": [ { @@ -1004,7 +1004,7 @@ "type": "tidelift" } ], - "time": "2024-02-03T17:33:49+00:00" + "time": "2024-02-12T18:36:36+00:00" }, { "name": "doctrine/deprecations", @@ -1146,16 +1146,16 @@ }, { "name": "doctrine/inflector", - "version": "2.0.9", + "version": "2.0.10", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "2930cd5ef353871c821d5c43ed030d39ac8cfe65" + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/2930cd5ef353871c821d5c43ed030d39ac8cfe65", - "reference": "2930cd5ef353871c821d5c43ed030d39ac8cfe65", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", "shasum": "" }, "require": { @@ -1217,7 +1217,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.9" + "source": "https://github.com/doctrine/inflector/tree/2.0.10" }, "funding": [ { @@ -1233,7 +1233,7 @@ "type": "tidelift" } ], - "time": "2024-01-15T18:05:13+00:00" + "time": "2024-02-18T20:23:39+00:00" }, { "name": "doctrine/lexer", @@ -1570,15 +1570,15 @@ "dist": { "type": "path", "url": "packages/support", - "reference": "70668ccdd8bce1e3df337ed3e483150a70207728" + "reference": "099ac9c8ddf7d2d6fdb9ccd8ed7d43782645bd4f" }, "require": { "blade-ui-kit/blade-heroicons": "^2.2.1", "doctrine/dbal": "^3.2", "ext-intl": "*", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0", - "illuminate/view": "^10.0", + "illuminate/contracts": "^10.45", + "illuminate/support": "^10.45", + "illuminate/view": "^10.45", "livewire/livewire": "^3.2.3", "php": "^8.1", "ryangjchandler/blade-capture-directive": "^0.2|^0.3", @@ -2343,27 +2343,27 @@ }, { "name": "kirschbaum-development/eloquent-power-joins", - "version": "3.4.0", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/kirschbaum-development/eloquent-power-joins.git", - "reference": "9238fcb53d777265ee9d8d139810e2cadecde079" + "reference": "13feb3692ab6c0475b2c05de131d5f5822fb250a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/9238fcb53d777265ee9d8d139810e2cadecde079", - "reference": "9238fcb53d777265ee9d8d139810e2cadecde079", + "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/13feb3692ab6c0475b2c05de131d5f5822fb250a", + "reference": "13feb3692ab6c0475b2c05de131d5f5822fb250a", "shasum": "" }, "require": { - "illuminate/database": "^8.0|^9.0|^10.0", - "illuminate/support": "^8.0|^9.0|^10.0", + "illuminate/database": "^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", "php": "^8.0" }, "require-dev": { "laravel/legacy-factories": "^1.0@dev", - "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0", - "phpunit/phpunit": "^8.0|^9.0" + "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^8.0|^9.0|^10.0" }, "type": "library", "extra": { @@ -2399,22 +2399,22 @@ ], "support": { "issues": "https://github.com/kirschbaum-development/eloquent-power-joins/issues", - "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/3.4.0" + "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/3.5.0" }, - "time": "2023-12-07T10:44:41+00:00" + "time": "2024-02-13T15:40:14+00:00" }, { "name": "larastan/larastan", - "version": "v2.8.1", + "version": "v2.9.0", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "b7cc6a29c457a7d4f3de90466392ae9ad3e17022" + "reference": "35fa9cbe1895e76215bbe74571a344f2705fbe01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/b7cc6a29c457a7d4f3de90466392ae9ad3e17022", - "reference": "b7cc6a29c457a7d4f3de90466392ae9ad3e17022", + "url": "https://api.github.com/repos/larastan/larastan/zipball/35fa9cbe1895e76215bbe74571a344f2705fbe01", + "reference": "35fa9cbe1895e76215bbe74571a344f2705fbe01", "shasum": "" }, "require": { @@ -2482,7 +2482,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v2.8.1" + "source": "https://github.com/larastan/larastan/tree/v2.9.0" }, "funding": [ { @@ -2502,7 +2502,7 @@ "type": "patreon" } ], - "time": "2024-01-08T09:11:17+00:00" + "time": "2024-02-13T11:49:22+00:00" }, { "name": "laravel/cashier", @@ -2593,16 +2593,16 @@ }, { "name": "laravel/framework", - "version": "v10.43.0", + "version": "v10.45.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "4f7802dfc9993cb57cf69615491ce1a7eb2e9529" + "reference": "dcf5d1d722b84ad38a5e053289130b6962f830bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/4f7802dfc9993cb57cf69615491ce1a7eb2e9529", - "reference": "4f7802dfc9993cb57cf69615491ce1a7eb2e9529", + "url": "https://api.github.com/repos/laravel/framework/zipball/dcf5d1d722b84ad38a5e053289130b6962f830bd", + "reference": "dcf5d1d722b84ad38a5e053289130b6962f830bd", "shasum": "" }, "require": { @@ -2650,6 +2650,7 @@ "conflict": { "carbonphp/carbon-doctrine-types": ">=3.0", "doctrine/dbal": ">=4.0", + "phpunit/phpunit": ">=11.0.0", "tightenco/collect": "<5.5.33" }, "provide": { @@ -2794,20 +2795,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-01-30T16:25:02+00:00" + "time": "2024-02-21T14:07:36+00:00" }, { "name": "laravel/pint", - "version": "v1.13.10", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "e2b5060885694ca30ac008c05dc9d47f10ed1abf" + "reference": "6b127276e3f263f7bb17d5077e9e0269e61b2a0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/e2b5060885694ca30ac008c05dc9d47f10ed1abf", - "reference": "e2b5060885694ca30ac008c05dc9d47f10ed1abf", + "url": "https://api.github.com/repos/laravel/pint/zipball/6b127276e3f263f7bb17d5077e9e0269e61b2a0e", + "reference": "6b127276e3f263f7bb17d5077e9e0269e61b2a0e", "shasum": "" }, "require": { @@ -2818,13 +2819,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.47.1", - "illuminate/view": "^10.41.0", + "friendsofphp/php-cs-fixer": "^3.49.0", + "illuminate/view": "^10.43.0", "larastan/larastan": "^2.8.1", "laravel-zero/framework": "^10.3.0", "mockery/mockery": "^1.6.7", "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.31.0" + "pestphp/pest": "^2.33.6" }, "bin": [ "builds/pint" @@ -2860,7 +2861,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-01-22T09:04:15+00:00" + "time": "2024-02-20T17:38:05+00:00" }, { "name": "laravel/prompts", @@ -3235,16 +3236,16 @@ }, { "name": "league/csv", - "version": "9.14.0", + "version": "9.15.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "34bf0df7340b60824b9449b5c526fcc3325070d5" + "reference": "fa7e2441c0bc9b2360f4314fd6c954f7ff40d435" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/34bf0df7340b60824b9449b5c526fcc3325070d5", - "reference": "34bf0df7340b60824b9449b5c526fcc3325070d5", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/fa7e2441c0bc9b2360f4314fd6c954f7ff40d435", + "reference": "fa7e2441c0bc9b2360f4314fd6c954f7ff40d435", "shasum": "" }, "require": { @@ -3259,12 +3260,12 @@ "ext-xdebug": "*", "friendsofphp/php-cs-fixer": "^v3.22.0", "phpbench/phpbench": "^1.2.15", - "phpstan/phpstan": "^1.10.50", + "phpstan/phpstan": "^1.10.57", "phpstan/phpstan-deprecation-rules": "^1.1.4", "phpstan/phpstan-phpunit": "^1.3.15", "phpstan/phpstan-strict-rules": "^1.5.2", - "phpunit/phpunit": "^10.5.3", - "symfony/var-dumper": "^6.4.0" + "phpunit/phpunit": "^10.5.9", + "symfony/var-dumper": "^6.4.2" }, "suggest": { "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", @@ -3320,7 +3321,7 @@ "type": "github" } ], - "time": "2023-12-29T07:34:53+00:00" + "time": "2024-02-20T20:00:00+00:00" }, { "name": "league/flysystem", @@ -3769,16 +3770,16 @@ }, { "name": "livewire/livewire", - "version": "v3.4.4", + "version": "v3.4.6", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "c0489d4a76382f6dcf6e2702112f86aa089d0c8d" + "reference": "7e7d638183b34fb61621455891869f5abfd55a82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/c0489d4a76382f6dcf6e2702112f86aa089d0c8d", - "reference": "c0489d4a76382f6dcf6e2702112f86aa089d0c8d", + "url": "https://api.github.com/repos/livewire/livewire/zipball/7e7d638183b34fb61621455891869f5abfd55a82", + "reference": "7e7d638183b34fb61621455891869f5abfd55a82", "shasum": "" }, "require": { @@ -3832,7 +3833,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.4.4" + "source": "https://github.com/livewire/livewire/tree/v3.4.6" }, "funding": [ { @@ -3840,7 +3841,7 @@ "type": "github" } ], - "time": "2024-01-28T19:07:11+00:00" + "time": "2024-02-20T14:04:25+00:00" }, { "name": "maennchen/zipstream-php", @@ -4075,41 +4076,41 @@ }, { "name": "moneyphp/money", - "version": "v4.4.0", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/moneyphp/money.git", - "reference": "5e60aebf09f709dd4ea16bf85e66d65301c0d172" + "reference": "a1daa7daf159b4044e3d0c34c41fe2be5860e850" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/moneyphp/money/zipball/5e60aebf09f709dd4ea16bf85e66d65301c0d172", - "reference": "5e60aebf09f709dd4ea16bf85e66d65301c0d172", + "url": "https://api.github.com/repos/moneyphp/money/zipball/a1daa7daf159b4044e3d0c34c41fe2be5860e850", + "reference": "a1daa7daf159b4044e3d0c34c41fe2be5860e850", "shasum": "" }, "require": { "ext-bcmath": "*", "ext-filter": "*", "ext-json": "*", - "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0" }, "require-dev": { "cache/taggable-cache": "^1.1.0", - "doctrine/coding-standard": "^9.0", - "doctrine/instantiator": "^1.4.0", + "doctrine/coding-standard": "^12.0", + "doctrine/instantiator": "^1.5.0 || ^2.0", "ext-gmp": "*", "ext-intl": "*", - "florianv/exchanger": "^2.6.3", + "florianv/exchanger": "^2.8.1", "florianv/swap": "^4.3.0", - "moneyphp/crypto-currencies": "^1.0.0", - "moneyphp/iso-currencies": "^3.2.1", - "php-http/message": "^1.11.0", - "php-http/mock-client": "^1.4.1", + "moneyphp/crypto-currencies": "^1.1.0", + "moneyphp/iso-currencies": "^3.4", + "php-http/message": "^1.16.0", + "php-http/mock-client": "^1.6.0", "phpbench/phpbench": "^1.2.5", - "phpunit/phpunit": "^9.5.4", + "phpunit/phpunit": "^10.5.9", "psalm/plugin-phpunit": "^0.18.4", "psr/cache": "^1.0.1 || ^2.0 || ^3.0", - "vimeo/psalm": "~5.15.0" + "vimeo/psalm": "~5.20.0" }, "suggest": { "ext-gmp": "Calculate without integer limits", @@ -4157,9 +4158,9 @@ ], "support": { "issues": "https://github.com/moneyphp/money/issues", - "source": "https://github.com/moneyphp/money/tree/v4.4.0" + "source": "https://github.com/moneyphp/money/tree/v4.5.0" }, - "time": "2024-01-24T08:29:16+00:00" + "time": "2024-02-15T19:47:21+00:00" }, { "name": "monolog/monolog", @@ -4644,16 +4645,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.0.0", + "version": "v5.0.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc" + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc", - "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/2218c2252c874a4624ab2f613d86ac32d227bc69", + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69", "shasum": "" }, "require": { @@ -4696,9 +4697,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.1" }, - "time": "2024-01-07T17:17:35+00:00" + "time": "2024-02-21T19:24:10+00:00" }, { "name": "nunomaduro/collision", @@ -5179,16 +5180,16 @@ }, { "name": "orchestra/testbench-core", - "version": "v8.21.1", + "version": "v8.22.1", "source": { "type": "git", "url": "https://github.com/orchestral/testbench-core.git", - "reference": "c6bf49a1b7ef4afe58e5bda676158c4de41f9a81" + "reference": "b0006c092694828f4b0fa409a369b798e5e26f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/c6bf49a1b7ef4afe58e5bda676158c4de41f9a81", - "reference": "c6bf49a1b7ef4afe58e5bda676158c4de41f9a81", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/b0006c092694828f4b0fa409a369b798e5e26f8d", + "reference": "b0006c092694828f4b0fa409a369b798e5e26f8d", "shasum": "" }, "require": { @@ -5267,7 +5268,7 @@ "issues": "https://github.com/orchestral/testbench/issues", "source": "https://github.com/orchestral/testbench-core" }, - "time": "2024-01-22T01:45:47+00:00" + "time": "2024-02-21T23:33:22+00:00" }, { "name": "orchestra/workbench", @@ -5341,16 +5342,16 @@ }, { "name": "pestphp/pest", - "version": "v2.33.4", + "version": "v2.34.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "4baf27911e088cd27c0114bd9b4ee579203f8810" + "reference": "602b696348efdf4da83c9719de3062462cc1d146" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/4baf27911e088cd27c0114bd9b4ee579203f8810", - "reference": "4baf27911e088cd27c0114bd9b4ee579203f8810", + "url": "https://api.github.com/repos/pestphp/pest/zipball/602b696348efdf4da83c9719de3062462cc1d146", + "reference": "602b696348efdf4da83c9719de3062462cc1d146", "shasum": "" }, "require": { @@ -5360,10 +5361,10 @@ "pestphp/pest-plugin": "^2.1.1", "pestphp/pest-plugin-arch": "^2.7.0", "php": "^8.1.0", - "phpunit/phpunit": "^10.5.9" + "phpunit/phpunit": "^10.5.10" }, "conflict": { - "phpunit/phpunit": ">10.5.9", + "phpunit/phpunit": ">10.5.10", "sebastian/exporter": "<5.1.0", "webmozart/assert": "<1.11.0" }, @@ -5433,7 +5434,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v2.33.4" + "source": "https://github.com/pestphp/pest/tree/v2.34.0" }, "funding": [ { @@ -5445,7 +5446,7 @@ "type": "github" } ], - "time": "2024-02-02T16:54:54+00:00" + "time": "2024-02-17T10:06:53+00:00" }, { "name": "pestphp/pest-plugin", @@ -5590,27 +5591,27 @@ }, { "name": "pestphp/pest-plugin-laravel", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest-plugin-laravel.git", - "reference": "77a2838c1d3b09d147211e76a48987ba9a758279" + "reference": "2f6ea6233bb74ec65d969ecdea56bdbd3d1e2f0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest-plugin-laravel/zipball/77a2838c1d3b09d147211e76a48987ba9a758279", - "reference": "77a2838c1d3b09d147211e76a48987ba9a758279", + "url": "https://api.github.com/repos/pestphp/pest-plugin-laravel/zipball/2f6ea6233bb74ec65d969ecdea56bdbd3d1e2f0e", + "reference": "2f6ea6233bb74ec65d969ecdea56bdbd3d1e2f0e", "shasum": "" }, "require": { - "laravel/framework": "^10.18.0|^11.0", - "pestphp/pest": "^2.13.0", + "laravel/framework": "^10.44.0|^11.0", + "pestphp/pest": "^2.33.6", "php": "^8.1.0" }, "require-dev": { - "laravel/dusk": "^7.9.3", - "orchestra/testbench": "^8.6.3", - "pestphp/pest-dev-tools": "^2.14.0" + "laravel/dusk": "^7.12.3", + "orchestra/testbench": "^8.21.1|^9.0.0", + "pestphp/pest-dev-tools": "^2.16.0" }, "type": "library", "extra": { @@ -5618,6 +5619,11 @@ "providers": [ "Pest\\Laravel\\PestServiceProvider" ] + }, + "pest": { + "plugins": [ + "Pest\\Laravel\\Plugin" + ] } }, "autoload": { @@ -5643,7 +5649,7 @@ "unit" ], "support": { - "source": "https://github.com/pestphp/pest-plugin-laravel/tree/v2.2.0" + "source": "https://github.com/pestphp/pest-plugin-laravel/tree/v2.3.0" }, "funding": [ { @@ -5655,7 +5661,7 @@ "type": "github" } ], - "time": "2023-08-10T15:37:09+00:00" + "time": "2024-02-17T10:04:08+00:00" }, { "name": "phar-io/manifest", @@ -5880,21 +5886,21 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.0", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc" + "reference": "153ae662783729388a584b4361f2545e4d841e3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fad452781b3d774e3337b0c0b245dd8e5a4455fc", - "reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", - "php": "^7.4 || ^8.0", + "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", "phpstan/phpdoc-parser": "^1.13" }, @@ -5932,9 +5938,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" }, - "time": "2024-01-11T11:49:22+00:00" + "time": "2024-02-23T11:10:43+00:00" }, { "name": "phpmyadmin/sql-parser", @@ -6575,16 +6581,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.9", + "version": "10.5.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0bd663704f0165c9e76fe4f06ffa6a1ca727fdbe" + "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0bd663704f0165c9e76fe4f06ffa6a1ca727fdbe", - "reference": "0bd663704f0165c9e76fe4f06ffa6a1ca727fdbe", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/50b8e314b6d0dd06521dc31d1abffa73f25f850c", + "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c", "shasum": "" }, "require": { @@ -6656,7 +6662,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.9" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.10" }, "funding": [ { @@ -6672,7 +6678,7 @@ "type": "tidelift" } ], - "time": "2024-01-22T14:35:40+00:00" + "time": "2024-02-04T09:07:51+00:00" }, { "name": "pimple/pimple", @@ -8738,16 +8744,16 @@ }, { "name": "spatie/image", - "version": "3.3.4", + "version": "3.3.5", "source": { "type": "git", "url": "https://github.com/spatie/image.git", - "reference": "6bd35eecd44a08f93fd0436f99e9adbe3ebc05da" + "reference": "e74f161b1e28e97752a70ef456f5ae7bad2277c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/image/zipball/6bd35eecd44a08f93fd0436f99e9adbe3ebc05da", - "reference": "6bd35eecd44a08f93fd0436f99e9adbe3ebc05da", + "url": "https://api.github.com/repos/spatie/image/zipball/e74f161b1e28e97752a70ef456f5ae7bad2277c1", + "reference": "e74f161b1e28e97752a70ef456f5ae7bad2277c1", "shasum": "" }, "require": { @@ -8794,7 +8800,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/image/tree/3.3.4" + "source": "https://github.com/spatie/image/tree/3.3.5" }, "funding": [ { @@ -8806,7 +8812,7 @@ "type": "github" } ], - "time": "2024-01-15T12:17:58+00:00" + "time": "2024-02-16T10:03:53+00:00" }, { "name": "spatie/image-optimizer", @@ -8924,28 +8930,28 @@ }, { "name": "spatie/laravel-medialibrary", - "version": "11.2.0", + "version": "11.4.4", "source": { "type": "git", "url": "https://github.com/spatie/laravel-medialibrary.git", - "reference": "3c4021c0ec6d1180b57f40c772b51da5d4e60162" + "reference": "bb29cacfe209856db304adc6c5ba8c05ff7a8802" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/3c4021c0ec6d1180b57f40c772b51da5d4e60162", - "reference": "3c4021c0ec6d1180b57f40c772b51da5d4e60162", + "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/bb29cacfe209856db304adc6c5ba8c05ff7a8802", + "reference": "bb29cacfe209856db304adc6c5ba8c05ff7a8802", "shasum": "" }, "require": { "ext-exif": "*", "ext-fileinfo": "*", "ext-json": "*", - "illuminate/bus": "^10.0", - "illuminate/conditionable": "^10.0", - "illuminate/console": "^10.0", - "illuminate/database": "^10.0", - "illuminate/pipeline": "^10.0", - "illuminate/support": "^10.0", + "illuminate/bus": "^10.0|^11.0", + "illuminate/conditionable": "^10.0|^11.0", + "illuminate/console": "^10.0|^11.0", + "illuminate/database": "^10.0|^11.0", + "illuminate/pipeline": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0", "maennchen/zipstream-php": "^3.1", "php": "^8.2", "spatie/image": "^3.3.2", @@ -8958,7 +8964,6 @@ }, "require-dev": { "aws/aws-sdk-php": "^3.293.10", - "doctrine/dbal": "^2.13.9", "ext-imagick": "*", "ext-pdo_sqlite": "*", "ext-zip": "*", @@ -8966,7 +8971,7 @@ "larastan/larastan": "^2.7", "league/flysystem-aws-s3-v3": "^3.22", "mockery/mockery": "^1.6.7", - "orchestra/testbench": "^7.0|^8.17", + "orchestra/testbench": "^7.0|^8.17|^9.0", "pestphp/pest": "^2.28", "phpstan/extension-installer": "^1.3.1", "spatie/laravel-ray": "^1.33", @@ -9017,7 +9022,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-medialibrary/issues", - "source": "https://github.com/spatie/laravel-medialibrary/tree/11.2.0" + "source": "https://github.com/spatie/laravel-medialibrary/tree/11.4.4" }, "funding": [ { @@ -9029,7 +9034,7 @@ "type": "github" } ], - "time": "2024-01-29T07:53:43+00:00" + "time": "2024-02-22T09:39:31+00:00" }, { "name": "spatie/laravel-package-tools", @@ -9178,20 +9183,20 @@ }, { "name": "spatie/laravel-tags", - "version": "4.5.2", + "version": "4.6.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-tags.git", - "reference": "627587878731650c52d79e2132cfd1410a18d428" + "reference": "ddcc82e1149c4720c0eaa02d171f50a1cdd1cb46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-tags/zipball/627587878731650c52d79e2132cfd1410a18d428", - "reference": "627587878731650c52d79e2132cfd1410a18d428", + "url": "https://api.github.com/repos/spatie/laravel-tags/zipball/ddcc82e1149c4720c0eaa02d171f50a1cdd1cb46", + "reference": "ddcc82e1149c4720c0eaa02d171f50a1cdd1cb46", "shasum": "" }, "require": { - "laravel/framework": "^8.67|^9.0|^10.0", + "laravel/framework": "^8.67|^9.0|^10.0|^11.0", "nesbot/carbon": "^2.63", "php": "^8.0", "spatie/eloquent-sortable": "^3.10|^4.0", @@ -9199,8 +9204,8 @@ "spatie/laravel-translatable": "^4.6|^5.0|^6.0" }, "require-dev": { - "orchestra/testbench": "^6.13|^7.0|^8.0", - "pestphp/pest": "^1.22", + "orchestra/testbench": "^6.13|^7.0|^8.0|^9.0", + "pestphp/pest": "^1.22|^2.0", "phpunit/phpunit": "^9.5.2" }, "type": "library", @@ -9236,7 +9241,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-tags/issues", - "source": "https://github.com/spatie/laravel-tags/tree/4.5.2" + "source": "https://github.com/spatie/laravel-tags/tree/4.6.0" }, "funding": [ { @@ -9244,7 +9249,7 @@ "type": "github" } ], - "time": "2024-01-19T22:10:13+00:00" + "time": "2024-02-21T12:13:18+00:00" }, { "name": "spatie/laravel-translatable", @@ -9586,16 +9591,16 @@ }, { "name": "staudenmeir/eloquent-has-many-deep", - "version": "v1.19.2", + "version": "v1.19.3", "source": { "type": "git", "url": "https://github.com/staudenmeir/eloquent-has-many-deep.git", - "reference": "4192ef405f74a52fbac47f515a40260b7c45e319" + "reference": "35e6a2f18a6605cbeda69573080c4ba008501971" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/staudenmeir/eloquent-has-many-deep/zipball/4192ef405f74a52fbac47f515a40260b7c45e319", - "reference": "4192ef405f74a52fbac47f515a40260b7c45e319", + "url": "https://api.github.com/repos/staudenmeir/eloquent-has-many-deep/zipball/35e6a2f18a6605cbeda69573080c4ba008501971", + "reference": "35e6a2f18a6605cbeda69573080c4ba008501971", "shasum": "" }, "require": { @@ -9642,7 +9647,7 @@ "description": "Laravel Eloquent HasManyThrough relationships with unlimited levels", "support": { "issues": "https://github.com/staudenmeir/eloquent-has-many-deep/issues", - "source": "https://github.com/staudenmeir/eloquent-has-many-deep/tree/v1.19.2" + "source": "https://github.com/staudenmeir/eloquent-has-many-deep/tree/v1.19.3" }, "funding": [ { @@ -9650,7 +9655,7 @@ "type": "custom" } ], - "time": "2023-12-19T15:44:14+00:00" + "time": "2024-02-07T21:23:00+00:00" }, { "name": "staudenmeir/eloquent-has-many-deep-contracts", diff --git a/docs-assets/app/public/js/filament/filament/app.js b/docs-assets/app/public/js/filament/filament/app.js index 6edaa63227..1ea8686be2 100644 --- a/docs-assets/app/public/js/filament/filament/app.js +++ b/docs-assets/app/public/js/filament/filament/app.js @@ -1 +1 @@ -(()=>{var Z=Object.create,L=Object.defineProperty,ee=Object.getPrototypeOf,te=Object.prototype.hasOwnProperty,re=Object.getOwnPropertyNames,ne=Object.getOwnPropertyDescriptor,ae=o=>L(o,"__esModule",{value:!0}),ie=(o,n)=>()=>(n||(n={exports:{}},o(n.exports,n)),n.exports),se=(o,n,p)=>{if(n&&typeof n=="object"||typeof n=="function")for(let d of re(n))!te.call(o,d)&&d!=="default"&&L(o,d,{get:()=>n[d],enumerable:!(p=ne(n,d))||p.enumerable});return o},oe=o=>se(ae(L(o!=null?Z(ee(o)):{},"default",o&&o.__esModule&&"default"in o?{get:()=>o.default,enumerable:!0}:{value:o,enumerable:!0})),o),fe=ie((o,n)=>{(function(p,d,P){if(!p)return;for(var h={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},y={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},g={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},q={option:"alt",command:"meta",return:"enter",escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},S,w=1;w<20;++w)h[111+w]="f"+w;for(w=0;w<=9;++w)h[w+96]=w.toString();function O(e,t,a){if(e.addEventListener){e.addEventListener(t,a,!1);return}e.attachEvent("on"+t,a)}function T(e){if(e.type=="keypress"){var t=String.fromCharCode(e.which);return e.shiftKey||(t=t.toLowerCase()),t}return h[e.which]?h[e.which]:y[e.which]?y[e.which]:String.fromCharCode(e.which).toLowerCase()}function $(e,t){return e.sort().join(",")===t.sort().join(",")}function B(e){var t=[];return e.shiftKey&&t.push("shift"),e.altKey&&t.push("alt"),e.ctrlKey&&t.push("ctrl"),e.metaKey&&t.push("meta"),t}function V(e){if(e.preventDefault){e.preventDefault();return}e.returnValue=!1}function H(e){if(e.stopPropagation){e.stopPropagation();return}e.cancelBubble=!0}function C(e){return e=="shift"||e=="ctrl"||e=="alt"||e=="meta"}function J(){if(!S){S={};for(var e in h)e>95&&e<112||h.hasOwnProperty(e)&&(S[h[e]]=e)}return S}function U(e,t,a){return a||(a=J()[e]?"keydown":"keypress"),a=="keypress"&&t.length&&(a="keydown"),a}function X(e){return e==="+"?["+"]:(e=e.replace(/\+{2}/g,"+plus"),e.split("+"))}function I(e,t){var a,c,b,M=[];for(a=X(e),b=0;b1){z(r,m,s,l);return}f=I(r,l),t._callbacks[f.key]=t._callbacks[f.key]||[],j(f.key,f.modifiers,{type:f.action},i,r,u),t._callbacks[f.key][i?"unshift":"push"]({callback:s,modifiers:f.modifiers,action:f.action,seq:i,level:u,combo:r})}t._bindMultiple=function(r,s,l){for(var i=0;i-1||D(t,a.target))return!1;if("composedPath"in e&&typeof e.composedPath=="function"){var c=e.composedPath()[0];c!==e.target&&(t=c)}return t.tagName=="INPUT"||t.tagName=="SELECT"||t.tagName=="TEXTAREA"||t.isContentEditable},v.prototype.handleKey=function(){var e=this;return e._handleKey.apply(e,arguments)},v.addKeycodes=function(e){for(var t in e)e.hasOwnProperty(t)&&(h[t]=e[t]);S=null},v.init=function(){var e=v(d);for(var t in e)t.charAt(0)!=="_"&&(v[t]=function(a){return function(){return e[a].apply(e,arguments)}}(t))},v.init(),p.Mousetrap=v,typeof n<"u"&&n.exports&&(n.exports=v),typeof define=="function"&&define.amd&&define(function(){return v})})(typeof window<"u"?window:null,typeof window<"u"?document:null)}),R=oe(fe());(function(o){if(o){var n={},p=o.prototype.stopCallback;o.prototype.stopCallback=function(d,P,h,y){var g=this;return g.paused?!0:n[h]||n[y]?!1:p.call(g,d,P,h)},o.prototype.bindGlobal=function(d,P,h){var y=this;if(y.bind(d,P,h),d instanceof Array){for(var g=0;g{o.directive("mousetrap",(n,{modifiers:p,expression:d},{evaluate:P})=>{let h=()=>d?P(d):n.click();p=p.map(y=>y.replace("-","+")),p.includes("global")&&(p=p.filter(y=>y!=="global"),R.default.bindGlobal(p,y=>{y.preventDefault(),h()})),R.default.bind(p,y=>{y.preventDefault(),h()})})},F=le;document.addEventListener("alpine:init",()=>{window.Alpine.plugin(F),window.Alpine.store("sidebar",{isOpen:window.Alpine.$persist(!0).as("isOpen"),collapsedGroups:window.Alpine.$persist(null).as("collapsedGroups"),groupIsCollapsed:function(n){return this.collapsedGroups.includes(n)},collapseGroup:function(n){this.collapsedGroups.includes(n)||(this.collapsedGroups=this.collapsedGroups.concat(n))},toggleCollapsedGroup:function(n){this.collapsedGroups=this.collapsedGroups.includes(n)?this.collapsedGroups.filter(p=>p!==n):this.collapsedGroups.concat(n)},close:function(){this.isOpen=!1},open:function(){this.isOpen=!0}});let o=localStorage.getItem("theme")??"system";window.Alpine.store("theme",o==="dark"||o==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.addEventListener("theme-changed",n=>{let p=n.detail;localStorage.setItem("theme",p),p==="system"&&(p=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.Alpine.store("theme",p)}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",n=>{localStorage.getItem("theme")==="system"&&window.Alpine.store("theme",n.matches?"dark":"light")}),window.Alpine.effect(()=>{window.Alpine.store("theme")==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")})});})(); +(()=>{var Z=Object.create,L=Object.defineProperty,ee=Object.getPrototypeOf,te=Object.prototype.hasOwnProperty,re=Object.getOwnPropertyNames,ne=Object.getOwnPropertyDescriptor,ae=s=>L(s,"__esModule",{value:!0}),ie=(s,n)=>()=>(n||(n={exports:{}},s(n.exports,n)),n.exports),oe=(s,n,p)=>{if(n&&typeof n=="object"||typeof n=="function")for(let d of re(n))!te.call(s,d)&&d!=="default"&&L(s,d,{get:()=>n[d],enumerable:!(p=ne(n,d))||p.enumerable});return s},se=s=>oe(ae(L(s!=null?Z(ee(s)):{},"default",s&&s.__esModule&&"default"in s?{get:()=>s.default,enumerable:!0}:{value:s,enumerable:!0})),s),fe=ie((s,n)=>{(function(p,d,M){if(!p)return;for(var h={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},y={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},g={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},q={option:"alt",command:"meta",return:"enter",escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},S,w=1;w<20;++w)h[111+w]="f"+w;for(w=0;w<=9;++w)h[w+96]=w.toString();function C(e,t,a){if(e.addEventListener){e.addEventListener(t,a,!1);return}e.attachEvent("on"+t,a)}function T(e){if(e.type=="keypress"){var t=String.fromCharCode(e.which);return e.shiftKey||(t=t.toLowerCase()),t}return h[e.which]?h[e.which]:y[e.which]?y[e.which]:String.fromCharCode(e.which).toLowerCase()}function V(e,t){return e.sort().join(",")===t.sort().join(",")}function $(e){var t=[];return e.shiftKey&&t.push("shift"),e.altKey&&t.push("alt"),e.ctrlKey&&t.push("ctrl"),e.metaKey&&t.push("meta"),t}function B(e){if(e.preventDefault){e.preventDefault();return}e.returnValue=!1}function H(e){if(e.stopPropagation){e.stopPropagation();return}e.cancelBubble=!0}function O(e){return e=="shift"||e=="ctrl"||e=="alt"||e=="meta"}function J(){if(!S){S={};for(var e in h)e>95&&e<112||h.hasOwnProperty(e)&&(S[h[e]]=e)}return S}function U(e,t,a){return a||(a=J()[e]?"keydown":"keypress"),a=="keypress"&&t.length&&(a="keydown"),a}function X(e){return e==="+"?["+"]:(e=e.replace(/\+{2}/g,"+plus"),e.split("+"))}function I(e,t){var a,c,b,P=[];for(a=X(e),b=0;b1){z(r,m,o,l);return}f=I(r,l),t._callbacks[f.key]=t._callbacks[f.key]||[],j(f.key,f.modifiers,{type:f.action},i,r,u),t._callbacks[f.key][i?"unshift":"push"]({callback:o,modifiers:f.modifiers,action:f.action,seq:i,level:u,combo:r})}t._bindMultiple=function(r,o,l){for(var i=0;i-1||D(t,a.target))return!1;if("composedPath"in e&&typeof e.composedPath=="function"){var c=e.composedPath()[0];c!==e.target&&(t=c)}return t.tagName=="INPUT"||t.tagName=="SELECT"||t.tagName=="TEXTAREA"||t.isContentEditable},v.prototype.handleKey=function(){var e=this;return e._handleKey.apply(e,arguments)},v.addKeycodes=function(e){for(var t in e)e.hasOwnProperty(t)&&(h[t]=e[t]);S=null},v.init=function(){var e=v(d);for(var t in e)t.charAt(0)!=="_"&&(v[t]=function(a){return function(){return e[a].apply(e,arguments)}}(t))},v.init(),p.Mousetrap=v,typeof n<"u"&&n.exports&&(n.exports=v),typeof define=="function"&&define.amd&&define(function(){return v})})(typeof window<"u"?window:null,typeof window<"u"?document:null)}),R=se(fe());(function(s){if(s){var n={},p=s.prototype.stopCallback;s.prototype.stopCallback=function(d,M,h,y){var g=this;return g.paused?!0:n[h]||n[y]?!1:p.call(g,d,M,h)},s.prototype.bindGlobal=function(d,M,h){var y=this;if(y.bind(d,M,h),d instanceof Array){for(var g=0;g{s.directive("mousetrap",(n,{modifiers:p,expression:d},{evaluate:M})=>{let h=()=>d?M(d):n.click();p=p.map(y=>y.replace("-","+")),p.includes("global")&&(p=p.filter(y=>y!=="global"),R.default.bindGlobal(p,y=>{y.preventDefault(),h()})),R.default.bind(p,y=>{y.preventDefault(),h()})})},F=le;document.addEventListener("alpine:init",()=>{window.Alpine.plugin(F),window.Alpine.store("sidebar",{isOpen:window.Alpine.$persist(!0).as("isOpen"),collapsedGroups:window.Alpine.$persist(null).as("collapsedGroups"),groupIsCollapsed:function(n){return this.collapsedGroups.includes(n)},collapseGroup:function(n){this.collapsedGroups.includes(n)||(this.collapsedGroups=this.collapsedGroups.concat(n))},toggleCollapsedGroup:function(n){this.collapsedGroups=this.collapsedGroups.includes(n)?this.collapsedGroups.filter(p=>p!==n):this.collapsedGroups.concat(n)},close:function(){this.isOpen=!1},open:function(){this.isOpen=!0}});let s=localStorage.getItem("theme")??getComputedStyle(document.documentElement).getPropertyValue("--default-theme-mode");window.Alpine.store("theme",s==="dark"||s==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.addEventListener("theme-changed",n=>{let p=n.detail;localStorage.setItem("theme",p),p==="system"&&(p=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.Alpine.store("theme",p)}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",n=>{localStorage.getItem("theme")==="system"&&window.Alpine.store("theme",n.matches?"dark":"light")}),window.Alpine.effect(()=>{window.Alpine.store("theme")==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")})});})(); diff --git a/packages/actions/composer.json b/packages/actions/composer.json index 49c37579b6..1d63bf645b 100644 --- a/packages/actions/composer.json +++ b/packages/actions/composer.json @@ -14,9 +14,9 @@ "filament/infolists": "self.version", "filament/notifications": "self.version", "filament/support": "self.version", - "illuminate/contracts": "^10.0", - "illuminate/database": "^10.0", - "illuminate/support": "^10.0", + "illuminate/contracts": "^10.45", + "illuminate/database": "^10.45", + "illuminate/support": "^10.45", "league/csv": "^9.14", "openspout/openspout": "^4.23", "spatie/laravel-package-tools": "^1.9" diff --git a/packages/actions/docs/04-modals.md b/packages/actions/docs/04-modals.md index 2161d957f3..8806d01708 100644 --- a/packages/actions/docs/04-modals.md +++ b/packages/actions/docs/04-modals.md @@ -293,7 +293,7 @@ Instead of opening in the center of the screen, the modal content will now slide ## Making the modal header sticky -The header of a modal scrolls out of view with the modal content when it overflows the modal size. However, slide-overs have a sticky modal that's always visible. You may control this behavior using `stickyModalHeader()`: +The header of a modal scrolls out of view with the modal content when it overflows the modal size. However, slide-overs have a sticky header that's always visible. You may control this behavior using `stickyModalHeader()`: ```php Action::make('updateAuthor') diff --git a/packages/actions/docs/07-prebuilt-actions/08-import.md b/packages/actions/docs/07-prebuilt-actions/08-import.md index 0486e60069..4290a2622b 100644 --- a/packages/actions/docs/07-prebuilt-actions/08-import.md +++ b/packages/actions/docs/07-prebuilt-actions/08-import.md @@ -320,6 +320,18 @@ ImportColumn::make('sku') }) ``` +### Adding helper text below the import column + +Sometimes, you may wish to provide extra information for the user before validation. You can do this by adding `helperText()` to a column, which gets displayed below the mapping select: + +```php +use Filament\Forms\Components\TextInput; + +ImportColumn::make('skus') + ->array(',') + ->helperText('A comma-separated list of SKUs.') +``` + ## Updating existing records when importing When generating an importer class, you will see this `resolveRecord()` method: diff --git a/packages/actions/docs/07-prebuilt-actions/09-export.md b/packages/actions/docs/07-prebuilt-actions/09-export.md index 8a63442b1e..8fc4e455c7 100644 --- a/packages/actions/docs/07-prebuilt-actions/09-export.md +++ b/packages/actions/docs/07-prebuilt-actions/09-export.md @@ -124,6 +124,18 @@ ExportColumn::make('description') ->enabledByDefault(false) ``` +### Disabling column selection + +By default, user will be asked which columns they would like to export. You can disable this functionality using `columnMapping(false)`: + +```php +use App\Filament\Exports\ProductExporter; + +ExportAction::make() + ->exporter(ProductExporter::class) + ->columnMapping(false) +``` + ### Calculated export column state Sometimes you need to calculate the state of a column, instead of directly reading it from a database column. diff --git a/packages/actions/resources/lang/fr/export.php b/packages/actions/resources/lang/fr/export.php new file mode 100644 index 0000000000..89bb1c959e --- /dev/null +++ b/packages/actions/resources/lang/fr/export.php @@ -0,0 +1,77 @@ + 'Exporter :label', + + 'modal' => [ + + 'heading' => 'Exporter :label', + + 'form' => [ + + 'columns' => [ + + 'label' => 'Colonnes', + + 'form' => [ + + 'is_enabled' => [ + 'label' => ':column sélectionnée', + ], + + 'label' => [ + 'label' => ':column label', + ], + + ], + + ], + + ], + + 'actions' => [ + + 'export' => [ + 'label' => 'Exporter', + ], + + ], + + ], + + 'notifications' => [ + + 'completed' => [ + + 'title' => 'Exportation terminée', + + 'actions' => [ + + 'download_csv' => [ + 'label' => 'Télécharger .csv', + ], + + 'download_xlsx' => [ + 'label' => 'Télécharger .xlsx', + ], + + ], + + ], + + 'max_rows' => [ + 'title' => 'L\'exportation est trop volumineuse', + 'body' => 'Vous ne pouvez pas exporter plus d’une ligne à la fois.|Vous ne pouvez pas exporter plus de :count lignes à la fois', + ], + + 'started' => [ + 'title' => 'L’exportation a commencé', + 'body' => 'Votre exportation a commencé et 1 ligne sera traitée en arrière-plan.|Votre exportation a commencé et :count lignes seront traitées en arrière-plan.', + ], + + ], + + 'file_name' => 'export-:export_id-:model', + +]; diff --git a/packages/actions/resources/lang/pl/export.php b/packages/actions/resources/lang/pl/export.php new file mode 100644 index 0000000000..5d12f1d820 --- /dev/null +++ b/packages/actions/resources/lang/pl/export.php @@ -0,0 +1,77 @@ + 'Eksportuj :label', + + 'modal' => [ + + 'heading' => 'Eksportuj :label', + + 'form' => [ + + 'columns' => [ + + 'label' => 'Kolumny', + + 'form' => [ + + 'is_enabled' => [ + 'label' => ':column włączony', + ], + + 'label' => [ + 'label' => 'Etykieta :column', + ], + + ], + + ], + + ], + + 'actions' => [ + + 'export' => [ + 'label' => 'Eksportuj', + ], + + ], + + ], + + 'notifications' => [ + + 'completed' => [ + + 'title' => 'Eksport zakończony', + + 'actions' => [ + + 'download_csv' => [ + 'label' => 'Pobierz .csv', + ], + + 'download_xlsx' => [ + 'label' => 'Pobierz .xlsx', + ], + + ], + + ], + + 'max_rows' => [ + 'title' => 'Zbyt dużo wierszy', + 'body' => 'Nie możesz wyeksportować więcej niż 1 wiersz na raz.|Nie możesz wyeksportować więcej niż :count wierszy na raz.', + ], + + 'started' => [ + 'title' => 'Eksport rozpoczęty', + 'body' => 'Rozpoczęto eksport i 1 wiersz zostanie przetworzony w tle.|Rozpoczęto eksport i :count wierszy zostanie przetworzonych w tle.', + ], + + ], + + 'file_name' => 'eksport-:export_id-:model', + +]; diff --git a/packages/actions/resources/lang/pl/import.php b/packages/actions/resources/lang/pl/import.php new file mode 100644 index 0000000000..461e28b216 --- /dev/null +++ b/packages/actions/resources/lang/pl/import.php @@ -0,0 +1,77 @@ + 'Importuj :label', + + 'modal' => [ + + 'heading' => 'Importuj :label', + + 'form' => [ + + 'file' => [ + 'label' => 'Plik', + 'placeholder' => 'Wybierz plik CSV', + ], + + 'columns' => [ + 'label' => 'Kolumny', + 'placeholder' => 'Wybierz kolumnę', + ], + + ], + + 'actions' => [ + + 'download_example' => [ + 'label' => 'Pobierz przykładowy plik CSV', + ], + + 'import' => [ + 'label' => 'Importuj', + ], + + ], + + ], + + 'notifications' => [ + + 'completed' => [ + + 'title' => 'Import zakończony', + + 'actions' => [ + + 'download_failed_rows_csv' => [ + 'label' => 'Pobierz informacje o niepowodzeniach', + ], + + ], + + ], + + 'max_rows' => [ + 'title' => 'Przesłany plik CSV jest zbyt duży', + 'body' => 'Nie możesz zaimportować więcej niż 1 wiersz na raz.|Nie możesz zaimportować więcej niż :count wierszy na raz.', + ], + + 'started' => [ + 'title' => 'Import rozpoczęty', + 'body' => 'Import rozpoczęty i 1 wiersz zostanie przetworzony w tle.|Import rozpoczęty i :count wierszy zostanie przetworzonych w tle.', + ], + + ], + + 'example_csv' => [ + 'file_name' => ':importer-example', + ], + + 'failure_csv' => [ + 'file_name' => 'import-:import_id-:csv_name-failed-rows', + 'error_header' => 'błąd', + 'system_error' => 'Błąd systemu, skontaktuj się z pomocą techniczną.', + ], + +]; diff --git a/packages/actions/resources/lang/ro/export.php b/packages/actions/resources/lang/ro/export.php new file mode 100644 index 0000000000..e1394adc0d --- /dev/null +++ b/packages/actions/resources/lang/ro/export.php @@ -0,0 +1,77 @@ + 'Exportă :label', + + 'modal' => [ + + 'heading' => 'Exportă :label', + + 'form' => [ + + 'columns' => [ + + 'label' => 'Coloane', + + 'form' => [ + + 'is_enabled' => [ + 'label' => ':column activat', + ], + + 'label' => [ + 'label' => 'Eticheta :column', + ], + + ], + + ], + + ], + + 'actions' => [ + + 'export' => [ + 'label' => 'Exportă', + ], + + ], + + ], + + 'notifications' => [ + + 'completed' => [ + + 'title' => 'Export complet', + + 'actions' => [ + + 'download_csv' => [ + 'label' => 'Descarcă .csv', + ], + + 'download_xlsx' => [ + 'label' => 'Descarcă .xlsx', + ], + + ], + + ], + + 'max_rows' => [ + 'title' => 'Exportul este prea mare', + 'body' => 'Nu puteți exporta mai mult de 1 rând odată.|Nu puteți exporta mai mult de :count rânduri odată', + ], + + 'started' => [ + 'title' => 'Exportul a început', + 'body' => 'Exportul dvs. a început și 1 rând va fi procesat în fundal.|Exportul dvs. a început și :count rânduri vor fi procesate în fundal.', + ], + + ], + + 'file_name' => 'export-:export_id-:model', + +]; diff --git a/packages/actions/resources/lang/ro/import.php b/packages/actions/resources/lang/ro/import.php new file mode 100644 index 0000000000..5e332656d9 --- /dev/null +++ b/packages/actions/resources/lang/ro/import.php @@ -0,0 +1,77 @@ + 'Importă :label', + + 'modal' => [ + + 'heading' => 'Importă :label', + + 'form' => [ + + 'file' => [ + 'label' => 'Fișier', + 'placeholder' => 'Încarcă un fișier CSV', + ], + + 'columns' => [ + 'label' => 'Coloane', + 'placeholder' => 'Alege Coloanele', + ], + + ], + + 'actions' => [ + + 'download_example' => [ + 'label' => 'Descarcă exemplu în format CSV', + ], + + 'import' => [ + 'label' => 'Importă', + ], + + ], + + ], + + 'notifications' => [ + + 'completed' => [ + + 'title' => 'Import finalizat', + + 'actions' => [ + + 'download_failed_rows_csv' => [ + 'label' => 'Descarcă informațiiler despre câmpul ce a avut eroaro|Descarcă informații despre câmpurile ce au auvt erori', + ], + + ], + + ], + + 'max_rows' => [ + 'title' => 'Fișierul CSV file este prea mare', + 'body' => 'Nu este permis importul a mai mult de o linile într-un singur fișier.|Nu este permis imporutl a mai mult de :count linii într-un singur fișier.', + ], + + 'started' => [ + 'title' => 'Importul a început', + 'body' => 'Importul dumneavostră a început, iar rândul va fi procesa în fundal.|Importul dumneavoastră a început, iar cele :count rânduri vor fi procesate în fundal.', + ], + + ], + + 'example_csv' => [ + 'file_name' => ':importer-example', + ], + + 'failure_csv' => [ + 'file_name' => 'import-:import_id-:csv_name-câmpuri-gresite', + 'error_header' => 'error', + 'system_error' => 'Eroare de sistem, vă rugăm contactați echipa de suport.', + ], + +]; diff --git a/packages/actions/resources/lang/tr/export.php b/packages/actions/resources/lang/tr/export.php new file mode 100644 index 0000000000..9dbadbd2ee --- /dev/null +++ b/packages/actions/resources/lang/tr/export.php @@ -0,0 +1,77 @@ + 'Dışa Aktar :label', + + 'modal' => [ + + 'heading' => 'Dışa Aktar :label', + + 'form' => [ + + 'columns' => [ + + 'label' => 'Sütunlar', + + 'form' => [ + + 'is_enabled' => [ + 'label' => ':column etkin', + ], + + 'label' => [ + 'label' => ':column etiketi', + ], + + ], + + ], + + ], + + 'actions' => [ + + 'export' => [ + 'label' => 'Dışa Aktar', + ], + + ], + + ], + + 'notifications' => [ + + 'completed' => [ + + 'title' => 'Dışa Aktarım Tamamlandı', + + 'actions' => [ + + 'download_csv' => [ + 'label' => '.csv Olarak İndir', + ], + + 'download_xlsx' => [ + 'label' => '.xlsx Olarak İndir', + ], + + ], + + ], + + 'max_rows' => [ + 'title' => 'Maksimum Satır Sayısı Aşıldı', + 'body' => 'Birden fazla satırı dışa aktaramazsınız.|:count satırı dışa aktaramazsınız.', + ], + + 'started' => [ + 'title' => 'Dışa Aktarım Başladı', + 'body' => 'Dışa aktarım başladı ve 1 satır arka planda işlenecek.|Dışa aktarım başladı ve :count satır arka planda işlenecek.', + ], + + ], + + 'file_name' => 'export-:export_id-:model', + +]; diff --git a/packages/actions/resources/lang/tr/import.php b/packages/actions/resources/lang/tr/import.php new file mode 100644 index 0000000000..fcd5b01167 --- /dev/null +++ b/packages/actions/resources/lang/tr/import.php @@ -0,0 +1,77 @@ + 'İçe Aktar: :label', + + 'modal' => [ + + 'heading' => 'İçe Aktar: :label', + + 'form' => [ + + 'file' => [ + 'label' => 'Dosya', + 'placeholder' => 'Bir CSV dosyası seçin', + ], + + 'columns' => [ + 'label' => 'Kolonlar', + 'placeholder' => 'Kolonları eşleştirin', + ], + + ], + + 'actions' => [ + + 'download_example' => [ + 'label' => 'Örnek CSV Dosyasını İndir', + ], + + 'import' => [ + 'label' => 'İçe Aktar', + ], + + ], + + ], + + 'notifications' => [ + + 'completed' => [ + + 'title' => 'İçe Aktarım Tamamlandı', + + 'actions' => [ + + 'download_failed_rows_csv' => [ + 'label' => 'Başarısız satır hakkında bilgileri indir|Başarısız satırlar hakkında bilgileri indir', + ], + + ], + + ], + + 'max_rows' => [ + 'title' => 'Yüklenen Dosya Çok Büyük', + 'body' => 'Aynı anda 1\'den fazla satır içeren dosyaları içe aktaramazsınız.|Aynı anda :count\'den fazla satır içeren dosyaları içe aktaramazsınız.', + ], + + 'started' => [ + 'title' => 'İçe Aktarım Başladı', + 'body' => 'İçe aktarım başladı ve 1 satır arka planda işlenecek.|İçe aktarım başladı ve :count satır arka planda işlenecek.', + ], + + ], + + 'example_csv' => [ + 'file_name' => ':importer-example', + ], + + 'failure_csv' => [ + 'file_name' => 'import-:import_id-:csv_name-failed-rows', + 'error_header' => 'hata', + 'system_error' => 'Sistem Hatası', + ], + +]; diff --git a/packages/actions/src/Concerns/CanExportRecords.php b/packages/actions/src/Concerns/CanExportRecords.php index 9293b3def6..ea56877d1e 100644 --- a/packages/actions/src/Concerns/CanExportRecords.php +++ b/packages/actions/src/Concerns/CanExportRecords.php @@ -25,11 +25,10 @@ use Illuminate\Foundation\Bus\PendingChain; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Bus; +use Illuminate\Support\Number; use Illuminate\Support\Str; use Livewire\Component; -use function Filament\Support\format_number; - trait CanExportRecords { /** @@ -61,6 +60,8 @@ trait CanExportRecords protected ?Closure $modifyQueryUsing = null; + protected bool | Closure $hasColumnMapping = true; + protected function setUp(): void { parent::setUp(); @@ -73,8 +74,8 @@ trait CanExportRecords $this->groupedIcon(FilamentIcon::resolve('actions::export-action.grouped') ?? 'heroicon-m-arrow-down-tray'); - $this->form(fn (ExportAction | ExportTableAction | ExportTableBulkAction $action): array => array_merge([ - Fieldset::make(__('filament-actions::export.modal.form.columns.label')) + $this->form(fn (ExportAction | ExportTableAction | ExportTableBulkAction $action): array => [ + ...($action->hasColumnMapping() ? [Fieldset::make(__('filament-actions::export.modal.form.columns.label')) ->columns(1) ->inlineLabel() ->schema(function () use ($action): array { @@ -99,8 +100,9 @@ trait CanExportRecords $action->getExporter()::getColumns(), ); }) - ->statePath('columnMap'), - ], $action->getExporter()::getOptionsFormComponents())); + ->statePath('columnMap')] : []), + ...$action->getExporter()::getOptionsFormComponents(), + ]); $this->action(function (ExportAction | ExportTableAction | ExportTableBulkAction $action, array $data, Component $livewire) { if ($livewire instanceof HasTable) { @@ -124,7 +126,7 @@ trait CanExportRecords Notification::make() ->title(__('filament-actions::export.notifications.max_rows.title')) ->body(trans_choice('filament-actions::export.notifications.max_rows.body', $maxRows, [ - 'count' => format_number($maxRows), + 'count' => Number::format($maxRows), ])) ->danger() ->send(); @@ -139,14 +141,20 @@ trait CanExportRecords Arr::except($data, ['columnMap']), ); - $columnMap = collect($data['columnMap']) - ->dot() - ->reduce(fn (Collection $carry, mixed $value, string $key): Collection => $carry->mergeRecursive([ - Str::beforeLast($key, '.') => [Str::afterLast($key, '.') => $value], - ]), collect()) - ->filter(fn (array $column): bool => $column['isEnabled'] ?? false) - ->mapWithKeys(fn (array $column, string $columnName): array => [$columnName => $column['label']]) - ->all(); + if ($action->hasColumnMapping()) { + $columnMap = collect($data['columnMap']) + ->dot() + ->reduce(fn (Collection $carry, mixed $value, string $key): Collection => $carry->mergeRecursive([ + Str::beforeLast($key, '.') => [Str::afterLast($key, '.') => $value], + ]), collect()) + ->filter(fn (array $column): bool => $column['isEnabled'] ?? false) + ->mapWithKeys(fn (array $column, string $columnName): array => [$columnName => $column['label']]) + ->all(); + } else { + $columnMap = collect($action->getExporter()::getColumns()) + ->mapWithKeys(fn (ExportColumn $column): array => [$column->getName() => $column->getLabel()]) + ->all(); + } $export = app(Export::class); $export->user()->associate($user); @@ -230,7 +238,7 @@ trait CanExportRecords Notification::make() ->title($action->getSuccessNotificationTitle()) ->body(trans_choice('filament-actions::export.notifications.started.body', $export->total_rows, [ - 'count' => format_number($export->total_rows), + 'count' => Number::format($export->total_rows), ])) ->success() ->send(); @@ -387,4 +395,16 @@ trait CanExportRecords return $this; } + + public function columnMapping(bool | Closure $condition = true): static + { + $this->hasColumnMapping = $condition; + + return $this; + } + + public function hasColumnMapping(): bool + { + return (bool) $this->evaluate($this->hasColumnMapping); + } } diff --git a/packages/actions/src/Concerns/CanImportRecords.php b/packages/actions/src/Concerns/CanImportRecords.php index 12e8fc7931..0897000f71 100644 --- a/packages/actions/src/Concerns/CanImportRecords.php +++ b/packages/actions/src/Concerns/CanImportRecords.php @@ -26,6 +26,7 @@ use Illuminate\Filesystem\AwsS3V3Adapter; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Number; use League\Csv\Info; use League\Csv\Reader as CsvReader; use League\Csv\Statement; @@ -34,8 +35,6 @@ use Livewire\Features\SupportFileUploads\TemporaryUploadedFile; use SplTempFileObject; use Symfony\Component\HttpFoundation\StreamedResponse; -use function Filament\Support\format_number; - trait CanImportRecords { /** @@ -181,7 +180,7 @@ trait CanImportRecords Notification::make() ->title(__('filament-actions::import.notifications.max_rows.title')) ->body(trans_choice('filament-actions::import.notifications.max_rows.body', $maxRows, [ - 'count' => format_number($maxRows), + 'count' => Number::format($maxRows), ])) ->danger() ->send(); @@ -271,7 +270,7 @@ trait CanImportRecords fn (Notification $notification) => $notification->actions([ NotificationAction::make('downloadFailedRowsCsv') ->label(trans_choice('filament-actions::import.notifications.completed.actions.download_failed_rows_csv.label', $failedRowsCount, [ - 'count' => format_number($failedRowsCount), + 'count' => Number::format($failedRowsCount), ])) ->color('danger') ->url(route('filament.imports.failed-rows.download', ['import' => $import], absolute: false), shouldOpenInNewTab: true) @@ -285,7 +284,7 @@ trait CanImportRecords Notification::make() ->title($action->getSuccessNotificationTitle()) ->body(trans_choice('filament-actions::import.notifications.started.body', $import->total_rows, [ - 'count' => format_number($import->total_rows), + 'count' => Number::format($import->total_rows), ])) ->success() ->send(); diff --git a/packages/actions/src/Concerns/CanUseDatabaseTransactions.php b/packages/actions/src/Concerns/CanUseDatabaseTransactions.php new file mode 100644 index 0000000000..4ea621ddf7 --- /dev/null +++ b/packages/actions/src/Concerns/CanUseDatabaseTransactions.php @@ -0,0 +1,50 @@ +hasDatabaseTransactions = $condition; + + return $this; + } + + public function hasDatabaseTransactions(): bool + { + return (bool) $this->evaluate($this->hasDatabaseTransactions); + } + + public function beginDatabaseTransaction(): void + { + if (! $this->hasDatabaseTransactions()) { + return; + } + + DB::beginTransaction(); + } + + public function commitDatabaseTransaction(): void + { + if (! $this->hasDatabaseTransactions()) { + return; + } + + DB::commit(); + } + + public function rollBackDatabaseTransaction(): void + { + if (! $this->hasDatabaseTransactions()) { + return; + } + + DB::rollBack(); + } +} diff --git a/packages/actions/src/Concerns/InteractsWithActions.php b/packages/actions/src/Concerns/InteractsWithActions.php index 80c417fe02..4af3dc1746 100644 --- a/packages/actions/src/Concerns/InteractsWithActions.php +++ b/packages/actions/src/Concerns/InteractsWithActions.php @@ -13,6 +13,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Validation\ValidationException; use InvalidArgumentException; use Livewire\Attributes\Url; +use Throwable; use function Livewire\store; @@ -79,6 +80,8 @@ trait InteractsWithActions $originallyMountedActions = $this->mountedActions; try { + $action->beginDatabaseTransaction(); + if ($this->mountedActionHasForm()) { $action->callBeforeFormValidated(); @@ -96,10 +99,21 @@ trait InteractsWithActions $result = $action->callAfter() ?? $result; $this->afterActionCalled(); + + $action->commitDatabaseTransaction(); } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + $action->rollBackDatabaseTransaction() : + $action->commitDatabaseTransaction(); + return null; } catch (Cancel $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + $action->rollBackDatabaseTransaction() : + $action->commitDatabaseTransaction(); } catch (ValidationException $exception) { + $action->rollBackDatabaseTransaction(); + if (! $this->mountedActionShouldOpenModal()) { $action->resetArguments(); $action->resetFormData(); @@ -107,6 +121,10 @@ trait InteractsWithActions $this->unmountAction(); } + throw $exception; + } catch (Throwable $exception) { + $action->rollBackDatabaseTransaction(); + throw $exception; } diff --git a/packages/actions/src/CreateAction.php b/packages/actions/src/CreateAction.php index 7d1887dde0..d06b0de9ad 100644 --- a/packages/actions/src/CreateAction.php +++ b/packages/actions/src/CreateAction.php @@ -47,6 +47,8 @@ class CreateAction extends Action $this->record(null); + $this->databaseTransaction(); + $this->action(function (array $arguments, Form $form): void { $model = $this->getModel(); diff --git a/packages/actions/src/EditAction.php b/packages/actions/src/EditAction.php index 3b19cbd0a7..090ab208c4 100644 --- a/packages/actions/src/EditAction.php +++ b/packages/actions/src/EditAction.php @@ -47,6 +47,8 @@ class EditAction extends Action return $data; }); + $this->databaseTransaction(); + $this->action(function (): void { $this->process(function (array $data, HasActions $livewire, Model $record) { if ($translatableContentDriver = $livewire->makeFilamentTranslatableContentDriver()) { diff --git a/packages/actions/src/Imports/ImportColumn.php b/packages/actions/src/Imports/ImportColumn.php index bc8d9418c1..bb2c795a86 100644 --- a/packages/actions/src/Imports/ImportColumn.php +++ b/packages/actions/src/Imports/ImportColumn.php @@ -5,6 +5,7 @@ namespace Filament\Actions\Imports; use Closure; use Filament\Forms\Components\Select; use Filament\Support\Components\Component; +use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\Relation; @@ -70,6 +71,8 @@ class ImportColumn extends Component protected string $evaluationIdentifier = 'column'; + protected string | Htmlable | Closure | null $helperText = null; + final public function __construct(string $name) { $this->name($name); @@ -88,7 +91,8 @@ class ImportColumn extends Component return Select::make($this->getName()) ->label($this->getLabel()) ->placeholder(__('filament-actions::import.modal.form.columns.placeholder')) - ->required($this->isMappingRequired()); + ->required($this->isMappingRequired()) + ->helperText($this->helperText); } public function name(string $name): static @@ -134,6 +138,13 @@ class ImportColumn extends Component return $this; } + public function helperText(string | Htmlable | Closure | null $text): static + { + $this->helperText = $text; + + return $this; + } + public function integer(bool | Closure $condition = true): static { $this->numeric($condition, decimalPlaces: 0); diff --git a/packages/actions/src/Imports/Importer.php b/packages/actions/src/Imports/Importer.php index 02a9cec9d2..69c45852e5 100644 --- a/packages/actions/src/Imports/Importer.php +++ b/packages/actions/src/Imports/Importer.php @@ -80,7 +80,12 @@ abstract class Importer foreach ($this->getCachedColumns() as $column) { $columnName = $column->getName(); - $rowColumnName = $this->columnMap[$columnName] ?? null; + + if (blank($this->columnMap[$columnName] ?? null)) { + continue; + } + + $rowColumnName = $this->columnMap[$columnName]; if (! array_key_exists($rowColumnName, $this->data)) { continue; @@ -141,6 +146,10 @@ abstract class Importer foreach ($this->getCachedColumns() as $column) { $columnName = $column->getName(); + if (blank($this->columnMap[$columnName] ?? null)) { + continue; + } + $rules[$columnName] = $column->getDataValidationRules(); if ( @@ -170,13 +179,19 @@ abstract class Importer $attributes = []; foreach ($this->getCachedColumns() as $column) { + $columnName = $column->getName(); + + if (blank($this->columnMap[$columnName] ?? null)) { + continue; + } + $validationAttribute = $column->getValidationAttribute(); if (blank($validationAttribute)) { continue; } - $attributes[$column->getName()] = $validationAttribute; + $attributes[$columnName] = $validationAttribute; } return $attributes; @@ -187,6 +202,10 @@ abstract class Importer foreach ($this->getCachedColumns() as $column) { $columnName = $column->getName(); + if (blank($this->columnMap[$columnName] ?? null)) { + continue; + } + if (! array_key_exists($columnName, $this->data)) { continue; } diff --git a/packages/actions/src/MountableAction.php b/packages/actions/src/MountableAction.php index 60ef7929f2..260378a452 100644 --- a/packages/actions/src/MountableAction.php +++ b/packages/actions/src/MountableAction.php @@ -15,6 +15,7 @@ abstract class MountableAction extends StaticAction implements HasLivewire use Concerns\CanOpenModal; use Concerns\CanRedirect; use Concerns\CanRequireConfirmation; + use Concerns\CanUseDatabaseTransactions; use Concerns\HasForm; use Concerns\HasInfolist; use Concerns\HasLifecycleHooks; @@ -39,14 +40,14 @@ abstract class MountableAction extends StaticAction implements HasLivewire return $this->evaluate($this->getActionFunction(), $parameters); } - public function cancel(): void + public function cancel(bool $shouldRollBackDatabaseTransaction = false): void { - throw new Cancel(); + throw (new Cancel())->rollBackDatabaseTransaction($shouldRollBackDatabaseTransaction); } - public function halt(): void + public function halt(bool $shouldRollBackDatabaseTransaction = false): void { - throw new Halt(); + throw (new Halt())->rollBackDatabaseTransaction($shouldRollBackDatabaseTransaction); } /** diff --git a/packages/forms/.stubs.php b/packages/forms/.stubs.php index 6b5032dee5..3af0d92a4b 100644 --- a/packages/forms/.stubs.php +++ b/packages/forms/.stubs.php @@ -1,10 +1,9 @@ searchable(['first_name', 'last_name']) ``` +### Saving pivot data to the relationship + +If you're using a `multiple()` relationship and your pivot table has additional columns, you can use the `pivotData()` method to specify the data that should be saved in them: + +```php +use Filament\Forms\Components\Select; + +Select::make('primaryTechnologies') + ->relationship(name: 'technologies', titleAttribute: 'name') + ->multiple() + ->pivotData([ + 'is_primary' => true, + ]) +``` + ### Creating a new option in a modal You may define a custom form that can be used to create a new record and attach it to the `BelongsTo` relationship: diff --git a/packages/forms/docs/03-fields/06-checkbox-list.md b/packages/forms/docs/03-fields/06-checkbox-list.md index 0969434b9a..c6155c4983 100644 --- a/packages/forms/docs/03-fields/06-checkbox-list.md +++ b/packages/forms/docs/03-fields/06-checkbox-list.md @@ -240,6 +240,20 @@ CheckboxList::make('authors') ->getOptionLabelFromRecordUsing(fn (Model $record) => "{$record->first_name} {$record->last_name}") ``` +### Saving pivot data to the relationship + +If your pivot table has additional columns, you can use the `pivotData()` method to specify the data that should be saved in them: + +```php +use Filament\Forms\Components\CheckboxList; + +CheckboxList::make('primaryTechnologies') + ->relationship(name: 'technologies', titleAttribute: 'name') + ->pivotData([ + 'is_primary' => true, + ]) +``` + ## Setting a custom no search results message When you're using a searchable checkbox list, you may want to display a custom message when no search results are found. You can do this using the `noSearchResultsMessage()` method: diff --git a/packages/forms/docs/03-fields/12-repeater.md b/packages/forms/docs/03-fields/12-repeater.md index 90d3a2a068..a3e3cd10f2 100644 --- a/packages/forms/docs/03-fields/12-repeater.md +++ b/packages/forms/docs/03-fields/12-repeater.md @@ -234,7 +234,7 @@ Repeater::make('qualifications') ### Reordering items in a relationship -By default, [reordering](#reordering-items) relationship repeater items is disabled. This is because your related model needs an `sort` column to store the order of related records. To enable reordering, you may use the `orderColumn()` method, passing in a name of the column on your related model to store the order in: +By default, [reordering](#reordering-items) relationship repeater items is disabled. This is because your related model needs a `sort` column to store the order of related records. To enable reordering, you may use the `orderColumn()` method, passing in a name of the column on your related model to store the order in: ```php use Filament\Forms\Components\Repeater; @@ -287,7 +287,7 @@ class OrderProduct extends Pivot { return $this->belongsTo(Order::class); } - + public function product(): BelongsTo { return $this->belongsTo(Product::class); @@ -634,7 +634,7 @@ Repeater::make('members') ->icon('heroicon-m-envelope') ->action(function (array $arguments, Repeater $component): void { $itemData = $component->getItemState($arguments['item']); - + Mail::to($itemData['email']) ->send( // ... diff --git a/packages/forms/docs/04-layout/08-custom.md b/packages/forms/docs/04-layout/08-custom.md index 5161b59029..2e5df2c491 100644 --- a/packages/forms/docs/04-layout/08-custom.md +++ b/packages/forms/docs/04-layout/08-custom.md @@ -26,7 +26,7 @@ This assumes that you have a `resources/views/filament/forms/components/wizard.b You may create your own custom component classes and views, which you can reuse across your project, and even release as a plugin to the community. -> If you're just creating a simple custom component to use once, you could instead use a [view component](#view) to render any custom Blade file. +> If you're just creating a simple custom component to use once, you could instead use a [view component](#view-components) to render any custom Blade file. To create a custom column class and view, you may use the following command: diff --git a/packages/forms/resources/lang/pt_PT/components.php b/packages/forms/resources/lang/pt_PT/components.php index 1c8daf41ad..2592984a52 100644 --- a/packages/forms/resources/lang/pt_PT/components.php +++ b/packages/forms/resources/lang/pt_PT/components.php @@ -189,7 +189,7 @@ return [ 'messages' => [ 'confirmation' => 'Não é recomendado editar ficheiros SVG, pois pode resultar em perda de qualidade ao redimensionar.\n Tem a certeza de que deseja prosseguir?', - 'disabled' => 'A edição de ficheiros SVG está desativada, pois pode resultar em perda de qualidade ao redimensionar.', + 'disabled' => 'A edição de ficheiros SVG está desactivada, pois pode resultar em perda de qualidade ao redimensionar.', ], ], diff --git a/packages/forms/src/Commands/Concerns/CanGenerateForms.php b/packages/forms/src/Commands/Concerns/CanGenerateForms.php index 486ffd6088..0e63c78848 100644 --- a/packages/forms/src/Commands/Concerns/CanGenerateForms.php +++ b/packages/forms/src/Commands/Concerns/CanGenerateForms.php @@ -155,6 +155,7 @@ trait CanGenerateForms $parameterValue = match (true) { /** @phpstan-ignore-next-line */ is_bool($parameterValue) => $parameterValue ? 'true' : 'false', + /** @phpstan-ignore-next-line */ is_null($parameterValue) => 'null', is_numeric($parameterValue) => $parameterValue, default => "'{$parameterValue}'", diff --git a/packages/forms/src/Components/CheckboxList.php b/packages/forms/src/Components/CheckboxList.php index 28db14e1dc..fc9576fb8b 100644 --- a/packages/forms/src/Components/CheckboxList.php +++ b/packages/forms/src/Components/CheckboxList.php @@ -24,6 +24,7 @@ class CheckboxList extends Field implements Contracts\CanDisableOptions, Contrac use Concerns\HasGridDirection; use Concerns\HasNestedRecursiveValidationRules; use Concerns\HasOptions; + use Concerns\HasPivotData; /** * @var view-string @@ -185,7 +186,15 @@ class CheckboxList extends Field implements Contracts\CanDisableOptions, Contrac }); $this->saveRelationshipsUsing(static function (CheckboxList $component, ?array $state) { - $component->getRelationship()->sync($state ?? []); + $pivotData = $component->getPivotData(); + + if ($pivotData === []) { + $component->getRelationship()->sync($state ?? []); + + return; + } + + $component->getRelationship()->syncWithPivotValues($state ?? [], $pivotData); }); $this->dehydrated(false); diff --git a/packages/forms/src/Components/Concerns/HasPivotData.php b/packages/forms/src/Components/Concerns/HasPivotData.php new file mode 100644 index 0000000000..ea4b817171 --- /dev/null +++ b/packages/forms/src/Components/Concerns/HasPivotData.php @@ -0,0 +1,31 @@ + | Closure + */ + protected array | Closure $pivotData = []; + + /** + * @param array | Closure $data + */ + public function pivotData(array | Closure $data): static + { + $this->pivotData = $data; + + return $this; + } + + /** + * @return array + */ + public function getPivotData(): array + { + return $this->evaluate($this->pivotData) ?? []; + } +} diff --git a/packages/forms/src/Components/Select.php b/packages/forms/src/Components/Select.php index d196f28e7a..6a8d618620 100644 --- a/packages/forms/src/Components/Select.php +++ b/packages/forms/src/Components/Select.php @@ -18,8 +18,8 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasOneOrMany; -use Illuminate\Database\Eloquent\Relations\HasOneThrough; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Arr; use Illuminate\Support\Str; @@ -46,6 +46,7 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has use Concerns\HasLoadingMessage; use Concerns\HasNestedRecursiveValidationRules; use Concerns\HasOptions; + use Concerns\HasPivotData; use Concerns\HasPlaceholder; use HasExtraAlpineAttributes; @@ -829,7 +830,7 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has } if ( - ($relationship instanceof HasOneThrough) || + ($relationship instanceof HasManyThrough) || ($relationship instanceof \Znck\Eloquent\Relations\BelongsToThrough) ) { $relatedModel = $relationship->getResults(); @@ -952,7 +953,11 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has $this->saveRelationshipsUsing(static function (Select $component, Model $record, $state) { $relationship = $component->getRelationship(); - if ($relationship instanceof \Znck\Eloquent\Relations\BelongsToThrough) { + if ( + ($relationship instanceof HasOneOrMany) || + ($relationship instanceof HasManyThrough) || + ($relationship instanceof \Znck\Eloquent\Relations\BelongsToThrough) + ) { return; } @@ -963,7 +968,15 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has return; } - $relationship->sync($state ?? []); + $pivotData = $component->getPivotData(); + + if ($pivotData === []) { + $relationship->sync($state ?? []); + + return; + } + + $relationship->syncWithPivotValues($state ?? [], $pivotData); }); $this->createOptionUsing(static function (Select $component, array $data, Form $form) { @@ -1063,7 +1076,7 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has return parent::getLabel(); } - public function getRelationship(): BelongsTo | BelongsToMany | HasOneOrMany | HasOneThrough | \Znck\Eloquent\Relations\BelongsToThrough | null + public function getRelationship(): BelongsTo | BelongsToMany | HasOneOrMany | HasManyThrough | \Znck\Eloquent\Relations\BelongsToThrough | null { if (blank($this->getRelationshipName())) { return null; @@ -1188,7 +1201,7 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has return $relationship->getQualifiedRelatedKeyName(); } - if ($relationship instanceof HasOneThrough) { + if ($relationship instanceof HasManyThrough) { return $relationship->getQualifiedForeignKeyName(); } diff --git a/packages/forms/src/Concerns/HasFormComponentActions.php b/packages/forms/src/Concerns/HasFormComponentActions.php index 32f7aa951e..1e3af14fb4 100644 --- a/packages/forms/src/Concerns/HasFormComponentActions.php +++ b/packages/forms/src/Concerns/HasFormComponentActions.php @@ -9,6 +9,7 @@ use Filament\Forms\Form; use Filament\Support\Exceptions\Cancel; use Filament\Support\Exceptions\Halt; use Illuminate\Validation\ValidationException; +use Throwable; use function Livewire\store; @@ -59,6 +60,8 @@ trait HasFormComponentActions $result = null; try { + $action->beginDatabaseTransaction(); + if ($this->mountedFormComponentActionHasForm()) { $action->callBeforeFormValidated(); @@ -74,10 +77,21 @@ trait HasFormComponentActions ]); $result = $action->callAfter() ?? $result; + + $action->commitDatabaseTransaction(); } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + $action->rollBackDatabaseTransaction() : + $action->commitDatabaseTransaction(); + return null; } catch (Cancel $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + $action->rollBackDatabaseTransaction() : + $action->commitDatabaseTransaction(); } catch (ValidationException $exception) { + $action->rollBackDatabaseTransaction(); + if (! $this->mountedFormComponentActionShouldOpenModal()) { $action->resetArguments(); $action->resetFormData(); @@ -85,6 +99,10 @@ trait HasFormComponentActions $this->unmountFormComponentAction(); } + throw $exception; + } catch (Throwable $exception) { + $action->rollBackDatabaseTransaction(); + throw $exception; } diff --git a/packages/infolists/composer.json b/packages/infolists/composer.json index e7ebc5648e..e59e82ccde 100644 --- a/packages/infolists/composer.json +++ b/packages/infolists/composer.json @@ -11,12 +11,12 @@ "php": "^8.1", "filament/actions": "self.version", "filament/support": "self.version", - "illuminate/console": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/database": "^10.0", - "illuminate/filesystem": "^10.0", - "illuminate/support": "^10.0", - "illuminate/view": "^10.0", + "illuminate/console": "^10.45", + "illuminate/contracts": "^10.45", + "illuminate/database": "^10.45", + "illuminate/filesystem": "^10.45", + "illuminate/support": "^10.45", + "illuminate/view": "^10.45", "spatie/laravel-package-tools": "^1.9" }, "autoload": { diff --git a/packages/infolists/docs/03-entries/02-text.md b/packages/infolists/docs/03-entries/02-text.md index 669aad78fe..1e2a3228fc 100644 --- a/packages/infolists/docs/03-entries/02-text.md +++ b/packages/infolists/docs/03-entries/02-text.md @@ -58,17 +58,39 @@ TextEntry::make('created_at') ## Number formatting -The `numeric()` method allows you to format an entry as a number, using PHP's `number_format()`: +The `numeric()` method allows you to format an entry as a number: ```php use Filament\Infolists\Components\TextEntry; TextEntry::make('stock') - ->numeric( - decimalPlaces: 0, - decimalSeparator: '.', - thousandsSeparator: ',', - ) + ->numeric() +``` + +If you would like to customize the number of decimal places used to format the number with, you can use the `decimalPlaces` argument: + +```php +use Filament\Infolists\Components\TextEntry; + +TextEntry::make('stock') + ->numeric(decimalPlaces: 0) +``` + +By default, your app's locale will be used to format the number suitably. If you would like to customize the locale used, you can pass it to the `locale` argument: + +```php +use Filament\Infolists\Components\TextEntry; + +TextEntry::make('stock') + ->numeric(locale: 'nl') +``` + +Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider: + +```php +use Illuminate\Support\Number; + +Number::useLocale('nl'); ``` ## Currency formatting @@ -91,6 +113,23 @@ TextEntry::make('price') ->money('EUR', divideBy: 100) ``` +By default, your app's locale will be used to format the money suitably. If you would like to customize the locale used, you can pass it to the `locale` argument: + +```php +use Filament\Infolists\Components\TextEntry; + +TextEntry::make('price') + ->money(currency: 'EUR', locale: 'nl') +``` + +Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider: + +```php +use Illuminate\Support\Number; + +Number::useLocale('nl'); +``` + ## Limiting text length You may `limit()` the length of the entry's value: diff --git a/packages/infolists/resources/views/components/image-entry.blade.php b/packages/infolists/resources/views/components/image-entry.blade.php index cb9ffd5dca..30f3ba280b 100644 --- a/packages/infolists/resources/views/components/image-entry.blade.php +++ b/packages/infolists/resources/views/components/image-entry.blade.php @@ -11,10 +11,15 @@ $height = $getHeight() ?? ($isStacked ? '2.5rem' : '8rem'); $width = $getWidth() ?? (($isCircular || $isSquare) ? $height : null); + $stateCount = count($state); + $limitedStateCount = count($limitedState); + $defaultImageUrl = $getDefaultImageUrl(); - if ((! count($limitedState)) && filled($defaultImageUrl)) { + if ((! $limitedStateCount) && filled($defaultImageUrl)) { $limitedState = [null]; + + $limitedStateCount = 1; } $ringClasses = \Illuminate\Support\Arr::toCssClasses([ @@ -29,7 +34,7 @@ }, ]); - $hasLimitedRemainingText = $hasLimitedRemainingText(); + $hasLimitedRemainingText = $hasLimitedRemainingText() && ($limitedStateCount < $stateCount); $isLimitedRemainingTextSeparate = $isLimitedRemainingTextSeparate(); $limitedRemainingTextSizeClasses = match ($getLimitedRemainingTextSize()) { @@ -50,7 +55,7 @@ ]) }} > - @if (count($limitedState)) + @if ($limitedStateCount)
@endforeach - @if ($hasLimitedRemainingText && ($loop->iteration < count($limitedState)) && (! $isLimitedRemainingTextSeparate) && $isCircular) + @if ($hasLimitedRemainingText && (! $isLimitedRemainingTextSeparate) && $isCircular)
- +{{ count($state) - count($limitedState) }} + +{{ $stateCount - $limitedStateCount }}
@endif
- @if ($hasLimitedRemainingText && ($loop->iteration < count($limitedState)) && ($isLimitedRemainingTextSeparate || (! $isCircular))) + @if ($hasLimitedRemainingText && ($isLimitedRemainingTextSeparate || (! $isCircular)))
- +{{ count($state) - count($limitedState) }} + +{{ $stateCount - $limitedStateCount }}
@endif @elseif (($placeholder = $getPlaceholder()) !== null) diff --git a/packages/infolists/src/Components/Concerns/CanFormatState.php b/packages/infolists/src/Components/Concerns/CanFormatState.php index c0e5ffbe38..8bee46d012 100644 --- a/packages/infolists/src/Components/Concerns/CanFormatState.php +++ b/packages/infolists/src/Components/Concerns/CanFormatState.php @@ -6,13 +6,13 @@ use Closure; use Filament\Infolists\Components\TextEntry; use Filament\Infolists\Infolist; use Filament\Support\Contracts\HasLabel as LabelInterface; +use Filament\Support\Enums\ArgumentValue; +use Filament\Tables\Table; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Support\Carbon; +use Illuminate\Support\Number; use Illuminate\Support\Str; -use function Filament\Support\format_money; -use function Filament\Support\format_number; - trait CanFormatState { protected ?Closure $formatStateUsing = null; @@ -99,28 +99,11 @@ trait CanFormatState return $this; } - public function money(string | Closure | null $currency = null, int $divideBy = 0): static + public function money(string | Closure | null $currency = null, int $divideBy = 0, string | Closure | null $locale = null): static { $this->isMoney = true; - $this->formatStateUsing(static function (TextEntry $component, $state) use ($currency, $divideBy): ?string { - if (blank($state)) { - return null; - } - - $currency = $component->evaluate($currency) ?? Infolist::$defaultCurrency; - - return format_money($state, $currency, $divideBy); - }); - - return $this; - } - - public function numeric(int | Closure | null $decimalPlaces = null, string | Closure | null $decimalSeparator = '.', string | Closure | null $thousandsSeparator = ','): static - { - $this->isNumeric = true; - - $this->formatStateUsing(static function (TextEntry $component, $state) use ($decimalPlaces, $decimalSeparator, $thousandsSeparator): ?string { + $this->formatStateUsing(static function (TextEntry $component, $state) use ($currency, $divideBy, $locale): ?string { if (blank($state)) { return null; } @@ -129,16 +112,47 @@ trait CanFormatState return $state; } - if ($decimalPlaces === null) { - return format_number($state); + $currency = $component->evaluate($currency) ?? Table::$defaultCurrency; + + if ($divideBy) { + $state /= $divideBy; } - return number_format( - $state, - $component->evaluate($decimalPlaces), - $component->evaluate($decimalSeparator), - $component->evaluate($thousandsSeparator), - ); + return Number::currency($state, $currency, $component->evaluate($locale)); + }); + + return $this; + } + + public function numeric(int | Closure | null $decimalPlaces = null, string | Closure | null | ArgumentValue $decimalSeparator = ArgumentValue::Default, string | Closure | null | ArgumentValue $thousandsSeparator = ArgumentValue::Default, int | Closure | null $maxDecimalPlaces = null, string | Closure | null $locale = null): static + { + $this->isNumeric = true; + + $this->formatStateUsing(static function (TextEntry $component, $state) use ($decimalPlaces, $decimalSeparator, $locale, $maxDecimalPlaces, $thousandsSeparator): ?string { + if (blank($state)) { + return null; + } + + if (! is_numeric($state)) { + return $state; + } + + $decimalSeparator = $component->evaluate($decimalSeparator); + $thousandsSeparator = $component->evaluate($thousandsSeparator); + + if ( + ($decimalSeparator !== ArgumentValue::Default) || + ($thousandsSeparator !== ArgumentValue::Default) + ) { + return number_format( + $state, + $component->evaluate($decimalPlaces), + $decimalSeparator === ArgumentValue::Default ? '.' : $decimalSeparator, + $thousandsSeparator === ArgumentValue::Default ? ',' : $thousandsSeparator, + ); + } + + return Number::format($state, $decimalPlaces, $maxDecimalPlaces, $component->evaluate($locale)); }); return $this; diff --git a/packages/infolists/src/Concerns/InteractsWithInfolists.php b/packages/infolists/src/Concerns/InteractsWithInfolists.php index 2ffe64f70f..dda99d1f70 100644 --- a/packages/infolists/src/Concerns/InteractsWithInfolists.php +++ b/packages/infolists/src/Concerns/InteractsWithInfolists.php @@ -14,6 +14,7 @@ use Filament\Support\Exceptions\Cancel; use Filament\Support\Exceptions\Halt; use Filament\Tables\Contracts\HasTable; use Illuminate\Validation\ValidationException; +use Throwable; use function Livewire\store; @@ -103,6 +104,8 @@ trait InteractsWithInfolists $result = null; try { + $action->beginDatabaseTransaction(); + if ($this->mountedInfolistActionHasForm()) { $action->callBeforeFormValidated(); @@ -118,10 +121,21 @@ trait InteractsWithInfolists ]); $result = $action->callAfter() ?? $result; + + $action->commitDatabaseTransaction(); } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + $action->rollBackDatabaseTransaction() : + $action->commitDatabaseTransaction(); + return null; } catch (Cancel $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + $action->rollBackDatabaseTransaction() : + $action->commitDatabaseTransaction(); } catch (ValidationException $exception) { + $action->rollBackDatabaseTransaction(); + if (! $this->mountedInfolistActionShouldOpenModal()) { $action->resetArguments(); $action->resetFormData(); @@ -129,6 +143,10 @@ trait InteractsWithInfolists $this->unmountInfolistAction(); } + throw $exception; + } catch (Throwable $exception) { + $action->rollBackDatabaseTransaction(); + throw $exception; } diff --git a/packages/notifications/composer.json b/packages/notifications/composer.json index a002ed55eb..23582b14c2 100644 --- a/packages/notifications/composer.json +++ b/packages/notifications/composer.json @@ -11,10 +11,10 @@ "php": "^8.1", "filament/actions": "self.version", "filament/support": "self.version", - "illuminate/contracts": "^10.0", - "illuminate/filesystem": "^10.0", - "illuminate/notifications": "^10.0", - "illuminate/support": "^10.0", + "illuminate/contracts": "^10.45", + "illuminate/filesystem": "^10.45", + "illuminate/notifications": "^10.45", + "illuminate/support": "^10.45", "spatie/laravel-package-tools": "^1.9" }, "autoload": { diff --git a/packages/panels/composer.json b/packages/panels/composer.json index 6e0c6fe7b2..761c06d1e2 100644 --- a/packages/panels/composer.json +++ b/packages/panels/composer.json @@ -17,16 +17,16 @@ "filament/support": "self.version", "filament/tables": "self.version", "filament/widgets": "self.version", - "illuminate/auth": "^10.0", - "illuminate/console": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/cookie": "^10.0", - "illuminate/database": "^10.0", - "illuminate/http": "^10.0", - "illuminate/routing": "^10.0", - "illuminate/session": "^10.0", - "illuminate/support": "^10.0", - "illuminate/view": "^10.0", + "illuminate/auth": "^10.45", + "illuminate/console": "^10.45", + "illuminate/contracts": "^10.45", + "illuminate/cookie": "^10.45", + "illuminate/database": "^10.45", + "illuminate/http": "^10.45", + "illuminate/routing": "^10.45", + "illuminate/session": "^10.45", + "illuminate/support": "^10.45", + "illuminate/view": "^10.45", "spatie/laravel-package-tools": "^1.9" }, "autoload": { diff --git a/packages/panels/dist/index.js b/packages/panels/dist/index.js index 6edaa63227..1ea8686be2 100644 --- a/packages/panels/dist/index.js +++ b/packages/panels/dist/index.js @@ -1 +1 @@ -(()=>{var Z=Object.create,L=Object.defineProperty,ee=Object.getPrototypeOf,te=Object.prototype.hasOwnProperty,re=Object.getOwnPropertyNames,ne=Object.getOwnPropertyDescriptor,ae=o=>L(o,"__esModule",{value:!0}),ie=(o,n)=>()=>(n||(n={exports:{}},o(n.exports,n)),n.exports),se=(o,n,p)=>{if(n&&typeof n=="object"||typeof n=="function")for(let d of re(n))!te.call(o,d)&&d!=="default"&&L(o,d,{get:()=>n[d],enumerable:!(p=ne(n,d))||p.enumerable});return o},oe=o=>se(ae(L(o!=null?Z(ee(o)):{},"default",o&&o.__esModule&&"default"in o?{get:()=>o.default,enumerable:!0}:{value:o,enumerable:!0})),o),fe=ie((o,n)=>{(function(p,d,P){if(!p)return;for(var h={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},y={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},g={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},q={option:"alt",command:"meta",return:"enter",escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},S,w=1;w<20;++w)h[111+w]="f"+w;for(w=0;w<=9;++w)h[w+96]=w.toString();function O(e,t,a){if(e.addEventListener){e.addEventListener(t,a,!1);return}e.attachEvent("on"+t,a)}function T(e){if(e.type=="keypress"){var t=String.fromCharCode(e.which);return e.shiftKey||(t=t.toLowerCase()),t}return h[e.which]?h[e.which]:y[e.which]?y[e.which]:String.fromCharCode(e.which).toLowerCase()}function $(e,t){return e.sort().join(",")===t.sort().join(",")}function B(e){var t=[];return e.shiftKey&&t.push("shift"),e.altKey&&t.push("alt"),e.ctrlKey&&t.push("ctrl"),e.metaKey&&t.push("meta"),t}function V(e){if(e.preventDefault){e.preventDefault();return}e.returnValue=!1}function H(e){if(e.stopPropagation){e.stopPropagation();return}e.cancelBubble=!0}function C(e){return e=="shift"||e=="ctrl"||e=="alt"||e=="meta"}function J(){if(!S){S={};for(var e in h)e>95&&e<112||h.hasOwnProperty(e)&&(S[h[e]]=e)}return S}function U(e,t,a){return a||(a=J()[e]?"keydown":"keypress"),a=="keypress"&&t.length&&(a="keydown"),a}function X(e){return e==="+"?["+"]:(e=e.replace(/\+{2}/g,"+plus"),e.split("+"))}function I(e,t){var a,c,b,M=[];for(a=X(e),b=0;b1){z(r,m,s,l);return}f=I(r,l),t._callbacks[f.key]=t._callbacks[f.key]||[],j(f.key,f.modifiers,{type:f.action},i,r,u),t._callbacks[f.key][i?"unshift":"push"]({callback:s,modifiers:f.modifiers,action:f.action,seq:i,level:u,combo:r})}t._bindMultiple=function(r,s,l){for(var i=0;i-1||D(t,a.target))return!1;if("composedPath"in e&&typeof e.composedPath=="function"){var c=e.composedPath()[0];c!==e.target&&(t=c)}return t.tagName=="INPUT"||t.tagName=="SELECT"||t.tagName=="TEXTAREA"||t.isContentEditable},v.prototype.handleKey=function(){var e=this;return e._handleKey.apply(e,arguments)},v.addKeycodes=function(e){for(var t in e)e.hasOwnProperty(t)&&(h[t]=e[t]);S=null},v.init=function(){var e=v(d);for(var t in e)t.charAt(0)!=="_"&&(v[t]=function(a){return function(){return e[a].apply(e,arguments)}}(t))},v.init(),p.Mousetrap=v,typeof n<"u"&&n.exports&&(n.exports=v),typeof define=="function"&&define.amd&&define(function(){return v})})(typeof window<"u"?window:null,typeof window<"u"?document:null)}),R=oe(fe());(function(o){if(o){var n={},p=o.prototype.stopCallback;o.prototype.stopCallback=function(d,P,h,y){var g=this;return g.paused?!0:n[h]||n[y]?!1:p.call(g,d,P,h)},o.prototype.bindGlobal=function(d,P,h){var y=this;if(y.bind(d,P,h),d instanceof Array){for(var g=0;g{o.directive("mousetrap",(n,{modifiers:p,expression:d},{evaluate:P})=>{let h=()=>d?P(d):n.click();p=p.map(y=>y.replace("-","+")),p.includes("global")&&(p=p.filter(y=>y!=="global"),R.default.bindGlobal(p,y=>{y.preventDefault(),h()})),R.default.bind(p,y=>{y.preventDefault(),h()})})},F=le;document.addEventListener("alpine:init",()=>{window.Alpine.plugin(F),window.Alpine.store("sidebar",{isOpen:window.Alpine.$persist(!0).as("isOpen"),collapsedGroups:window.Alpine.$persist(null).as("collapsedGroups"),groupIsCollapsed:function(n){return this.collapsedGroups.includes(n)},collapseGroup:function(n){this.collapsedGroups.includes(n)||(this.collapsedGroups=this.collapsedGroups.concat(n))},toggleCollapsedGroup:function(n){this.collapsedGroups=this.collapsedGroups.includes(n)?this.collapsedGroups.filter(p=>p!==n):this.collapsedGroups.concat(n)},close:function(){this.isOpen=!1},open:function(){this.isOpen=!0}});let o=localStorage.getItem("theme")??"system";window.Alpine.store("theme",o==="dark"||o==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.addEventListener("theme-changed",n=>{let p=n.detail;localStorage.setItem("theme",p),p==="system"&&(p=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.Alpine.store("theme",p)}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",n=>{localStorage.getItem("theme")==="system"&&window.Alpine.store("theme",n.matches?"dark":"light")}),window.Alpine.effect(()=>{window.Alpine.store("theme")==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")})});})(); +(()=>{var Z=Object.create,L=Object.defineProperty,ee=Object.getPrototypeOf,te=Object.prototype.hasOwnProperty,re=Object.getOwnPropertyNames,ne=Object.getOwnPropertyDescriptor,ae=s=>L(s,"__esModule",{value:!0}),ie=(s,n)=>()=>(n||(n={exports:{}},s(n.exports,n)),n.exports),oe=(s,n,p)=>{if(n&&typeof n=="object"||typeof n=="function")for(let d of re(n))!te.call(s,d)&&d!=="default"&&L(s,d,{get:()=>n[d],enumerable:!(p=ne(n,d))||p.enumerable});return s},se=s=>oe(ae(L(s!=null?Z(ee(s)):{},"default",s&&s.__esModule&&"default"in s?{get:()=>s.default,enumerable:!0}:{value:s,enumerable:!0})),s),fe=ie((s,n)=>{(function(p,d,M){if(!p)return;for(var h={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},y={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},g={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},q={option:"alt",command:"meta",return:"enter",escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},S,w=1;w<20;++w)h[111+w]="f"+w;for(w=0;w<=9;++w)h[w+96]=w.toString();function C(e,t,a){if(e.addEventListener){e.addEventListener(t,a,!1);return}e.attachEvent("on"+t,a)}function T(e){if(e.type=="keypress"){var t=String.fromCharCode(e.which);return e.shiftKey||(t=t.toLowerCase()),t}return h[e.which]?h[e.which]:y[e.which]?y[e.which]:String.fromCharCode(e.which).toLowerCase()}function V(e,t){return e.sort().join(",")===t.sort().join(",")}function $(e){var t=[];return e.shiftKey&&t.push("shift"),e.altKey&&t.push("alt"),e.ctrlKey&&t.push("ctrl"),e.metaKey&&t.push("meta"),t}function B(e){if(e.preventDefault){e.preventDefault();return}e.returnValue=!1}function H(e){if(e.stopPropagation){e.stopPropagation();return}e.cancelBubble=!0}function O(e){return e=="shift"||e=="ctrl"||e=="alt"||e=="meta"}function J(){if(!S){S={};for(var e in h)e>95&&e<112||h.hasOwnProperty(e)&&(S[h[e]]=e)}return S}function U(e,t,a){return a||(a=J()[e]?"keydown":"keypress"),a=="keypress"&&t.length&&(a="keydown"),a}function X(e){return e==="+"?["+"]:(e=e.replace(/\+{2}/g,"+plus"),e.split("+"))}function I(e,t){var a,c,b,P=[];for(a=X(e),b=0;b1){z(r,m,o,l);return}f=I(r,l),t._callbacks[f.key]=t._callbacks[f.key]||[],j(f.key,f.modifiers,{type:f.action},i,r,u),t._callbacks[f.key][i?"unshift":"push"]({callback:o,modifiers:f.modifiers,action:f.action,seq:i,level:u,combo:r})}t._bindMultiple=function(r,o,l){for(var i=0;i-1||D(t,a.target))return!1;if("composedPath"in e&&typeof e.composedPath=="function"){var c=e.composedPath()[0];c!==e.target&&(t=c)}return t.tagName=="INPUT"||t.tagName=="SELECT"||t.tagName=="TEXTAREA"||t.isContentEditable},v.prototype.handleKey=function(){var e=this;return e._handleKey.apply(e,arguments)},v.addKeycodes=function(e){for(var t in e)e.hasOwnProperty(t)&&(h[t]=e[t]);S=null},v.init=function(){var e=v(d);for(var t in e)t.charAt(0)!=="_"&&(v[t]=function(a){return function(){return e[a].apply(e,arguments)}}(t))},v.init(),p.Mousetrap=v,typeof n<"u"&&n.exports&&(n.exports=v),typeof define=="function"&&define.amd&&define(function(){return v})})(typeof window<"u"?window:null,typeof window<"u"?document:null)}),R=se(fe());(function(s){if(s){var n={},p=s.prototype.stopCallback;s.prototype.stopCallback=function(d,M,h,y){var g=this;return g.paused?!0:n[h]||n[y]?!1:p.call(g,d,M,h)},s.prototype.bindGlobal=function(d,M,h){var y=this;if(y.bind(d,M,h),d instanceof Array){for(var g=0;g{s.directive("mousetrap",(n,{modifiers:p,expression:d},{evaluate:M})=>{let h=()=>d?M(d):n.click();p=p.map(y=>y.replace("-","+")),p.includes("global")&&(p=p.filter(y=>y!=="global"),R.default.bindGlobal(p,y=>{y.preventDefault(),h()})),R.default.bind(p,y=>{y.preventDefault(),h()})})},F=le;document.addEventListener("alpine:init",()=>{window.Alpine.plugin(F),window.Alpine.store("sidebar",{isOpen:window.Alpine.$persist(!0).as("isOpen"),collapsedGroups:window.Alpine.$persist(null).as("collapsedGroups"),groupIsCollapsed:function(n){return this.collapsedGroups.includes(n)},collapseGroup:function(n){this.collapsedGroups.includes(n)||(this.collapsedGroups=this.collapsedGroups.concat(n))},toggleCollapsedGroup:function(n){this.collapsedGroups=this.collapsedGroups.includes(n)?this.collapsedGroups.filter(p=>p!==n):this.collapsedGroups.concat(n)},close:function(){this.isOpen=!1},open:function(){this.isOpen=!0}});let s=localStorage.getItem("theme")??getComputedStyle(document.documentElement).getPropertyValue("--default-theme-mode");window.Alpine.store("theme",s==="dark"||s==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.addEventListener("theme-changed",n=>{let p=n.detail;localStorage.setItem("theme",p),p==="system"&&(p=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.Alpine.store("theme",p)}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",n=>{localStorage.getItem("theme")==="system"&&window.Alpine.store("theme",n.matches?"dark":"light")}),window.Alpine.effect(()=>{window.Alpine.store("theme")==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")})});})(); diff --git a/packages/panels/docs/06-navigation.md b/packages/panels/docs/06-navigation.md index bf36b92c60..3efad6efcb 100644 --- a/packages/panels/docs/06-navigation.md +++ b/packages/panels/docs/06-navigation.md @@ -229,6 +229,16 @@ public function panel(Panel $panel): Panel } ``` +### Navigation groups in a collapsible sidebar on desktop + +> This section only applies to `sidebarCollapsibleOnDesktop()`, not `sidebarFullyCollapsibleOnDesktop()`, since the fully collapsible UI just hides the entire sidebar instead of changing its design. + +When using a collapsible sidebar on desktop, you will also often be using [navigation groups](#grouping-navigation-items). By default, the labels of each navigation group will be hidden when the sidebar is collapsed, since there is no space to display them. Even if the navigation group itself is [collapsible](#making-navigation-groups-not-collapsible), all items will still be visible in the collapsed sidebar, since there is no group label to click on to expand the group. + +These issues can be solved, to achieve a very minimal sidebar design, by [passing an `icon()`](#customizing-navigation-groups) to the navigation group objects. When an icon is defined, the icon will be displayed in the collapsed sidebar instead of the items at all times. When the icon is clicked, a dropdown will open to the side of the icon, revealing the items in the group. + +When passing an icon to a navigation group, even if the items also have icons, the expanded sidebar UI will not show the item icons. This is to keep the navigation hierarchy clear, and the design minimal. However, the icons for the items will be shown in the collapsed sidebar's dropdowns though, since the hierarchy is already clear from the fact that the dropdown is open. + ## Registering custom navigation items To register new navigation items, you can use the [configuration](configuration): diff --git a/packages/panels/resources/js/index.js b/packages/panels/resources/js/index.js index 68ba9458d7..619729e70e 100644 --- a/packages/panels/resources/js/index.js +++ b/packages/panels/resources/js/index.js @@ -37,7 +37,11 @@ document.addEventListener('alpine:init', () => { }, }) - const theme = localStorage.getItem('theme') ?? 'system' + const theme = + localStorage.getItem('theme') ?? + getComputedStyle(document.documentElement).getPropertyValue( + '--default-theme-mode', + ) window.Alpine.store( 'theme', diff --git a/packages/panels/resources/lang/it/pages/auth/edit-profile.php b/packages/panels/resources/lang/it/pages/auth/edit-profile.php index a7183fc1aa..484cf68a4b 100644 --- a/packages/panels/resources/lang/it/pages/auth/edit-profile.php +++ b/packages/panels/resources/lang/it/pages/auth/edit-profile.php @@ -43,7 +43,7 @@ return [ 'actions' => [ 'cancel' => [ - 'label' => 'indietro', + 'label' => 'Indietro', ], ], diff --git a/packages/panels/resources/lang/ro/pages/dashboard.php b/packages/panels/resources/lang/ro/pages/dashboard.php index 9f4c224464..ab7130495b 100644 --- a/packages/panels/resources/lang/ro/pages/dashboard.php +++ b/packages/panels/resources/lang/ro/pages/dashboard.php @@ -4,4 +4,30 @@ return [ 'title' => 'Panoul de control', + 'actions' => [ + + 'filter' => [ + + 'label' => 'Filtru', + + 'modal' => [ + + 'heading' => 'Filtru', + + 'actions' => [ + + 'apply' => [ + + 'label' => 'Aplică', + + ], + + ], + + ], + + ], + + ], + ]; diff --git a/packages/panels/resources/views/components/layout/base.blade.php b/packages/panels/resources/views/components/layout/base.blade.php index 7ec3f2376e..a9a8aadfca 100644 --- a/packages/panels/resources/views/components/layout/base.blade.php +++ b/packages/panels/resources/views/components/layout/base.blade.php @@ -24,7 +24,7 @@ {{ filled($title = strip_tags(($livewire ?? null)?->getTitle() ?? '')) ? "{$title} - " : null }} - {{ filament()->getBrandName() }} + {{ strip_tags(filament()->getBrandName()) }} {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::STYLES_BEFORE, scopes: $livewire->getRenderHookScopes()) }} @@ -59,6 +59,7 @@ --font-family: '{!! filament()->getFontFamily() !!}'; --sidebar-width: {{ filament()->getSidebarWidth() }}; --collapsed-sidebar-width: {{ filament()->getCollapsedSidebarWidth() }}; + --default-theme-mode: {{ filament()->getDefaultThemeMode()->value }}; } diff --git a/packages/panels/resources/views/components/layout/index.blade.php b/packages/panels/resources/views/components/layout/index.blade.php index 2ef3e459fc..cbac266630 100644 --- a/packages/panels/resources/views/components/layout/index.blade.php +++ b/packages/panels/resources/views/components/layout/index.blade.php @@ -5,20 +5,10 @@ @endphp -
- @if (filament()->hasNavigation()) -
- - - @endif - + {{-- The sidebar is after the page content in the markup to fix issues with page content overlapping dropdown content from the sidebar. --}} +
isSidebarCollapsibleOnDesktop()) x-data="{}" @@ -89,5 +79,18 @@ {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::FOOTER, scopes: $livewire->getRenderHookScopes()) }}
+ + @if (filament()->hasNavigation()) +
+ + + @endif
diff --git a/packages/panels/resources/views/components/page/sub-navigation/sidebar.blade.php b/packages/panels/resources/views/components/page/sub-navigation/sidebar.blade.php index 679c9ec7ad..c32acc28f8 100644 --- a/packages/panels/resources/views/components/page/sub-navigation/sidebar.blade.php +++ b/packages/panels/resources/views/components/page/sub-navigation/sidebar.blade.php @@ -8,6 +8,7 @@ > @foreach ($navigation as $navigationGroup) false, 'collapsible' => true, 'icon' => null, 'items' => [], @@ -7,6 +8,11 @@ 'subNavigation' => false, ]) +@php + $sidebarCollapsible = $sidebarCollapsible && filament()->isSidebarCollapsibleOnDesktop(); + $hasDropdown = filled($label) && filled($icon) && $sidebarCollapsible; +@endphp +
  • isSidebarCollapsibleOnDesktop()) + @if ($sidebarCollapsible) x-show="$store.sidebar.isOpen" x-transition:enter="delay-100 lg:transition" x-transition:enter-start="opacity-0" @@ -56,17 +62,118 @@
  • @endif + @if ($hasDropdown) + + + + + + @php + $lists = []; + + foreach ($items as $item) { + if ($childItems = $item->getChildItems()) { + $lists[] = [ + $item, + ...$childItems, + ]; + $lists[] = []; + + continue; + } + + if (empty($lists)) { + $lists[] = [$item]; + + continue; + } + + $lists[count($lists) - 1][] = $item; + } + + if (empty($lists[count($lists) - 1])) { + array_pop($lists); + } + @endphp + + @if (filled($label)) + + {{ $label }} + + @endif + + @foreach ($lists as $list) + + @foreach ($list as $item) + @php + $itemIsActive = $item->isActive(); + @endphp + + + {{ $item->getLabel() }} + + @endforeach + + @endforeach + + @endif +
      isSidebarCollapsibleOnDesktop()) + @if (filled($label)) + @if ($sidebarCollapsible) + x-show="$store.sidebar.isOpen ? ! $store.sidebar.groupIsCollapsed(label) : ! @js($hasDropdown)" + @else + x-show="! $store.sidebar.groupIsCollapsed(label)" + @endif + x-collapse.duration.200ms + @endif + @if ($sidebarCollapsible) x-transition:enter="delay-100 lg:transition" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" @endif - x-collapse.duration.200ms class="fi-sidebar-group-items flex flex-col gap-y-1" > @foreach ($items as $item) + @php + $itemIcon = $item->getIcon(); + @endphp + @foreach ($navigation as $group) $active, ]) > - @if (filled($icon) && ((! $subGrouped) || filament()->isSidebarCollapsibleOnDesktop())) + @if (filled($icon) && ((! $subGrouped) || $sidebarCollapsible)) ! $active, @@ -65,7 +65,7 @@ @if ((blank($icon) && $grouped) || $subGrouped)
      isSidebarCollapsibleOnDesktop()) + @if (filled($icon) && $subGrouped && $sidebarCollapsible) x-show="$store.sidebar.isOpen" @endif class="fi-sidebar-item-grouped-border relative flex h-6 w-6 items-center justify-center" diff --git a/packages/panels/resources/views/components/topbar/index.blade.php b/packages/panels/resources/views/components/topbar/index.blade.php index d24f1dfd5a..982daaddbd 100644 --- a/packages/panels/resources/views/components/topbar/index.blade.php +++ b/packages/panels/resources/views/components/topbar/index.blade.php @@ -79,27 +79,56 @@ - - @foreach ($group->getItems() as $item) - @php - $icon = $item->getIcon(); - $isActive = $item->isActive(); - @endphp + @php + $lists = []; - - {{ $item->getLabel() }} - - @endforeach - + foreach ($group->getItems() as $item) { + if ($childItems = $item->getChildItems()) { + $lists[] = [ + $item, + ...$childItems, + ]; + $lists[] = []; + + continue; + } + + if (empty($lists)) { + $lists[] = [$item]; + + continue; + } + + $lists[count($lists) - 1][] = $item; + } + + if (empty($lists[count($lists) - 1])) { + array_pop($lists); + } + @endphp + + @foreach ($lists as $list) + + @foreach ($list as $item) + @php + $itemIsActive = $item->isActive(); + @endphp + + + {{ $item->getLabel() }} + + @endforeach + + @endforeach @else @foreach ($group->getItems() as $item) diff --git a/packages/panels/src/Commands/MakeIssueCommand.php b/packages/panels/src/Commands/MakeIssueCommand.php deleted file mode 100644 index c882b0f532..0000000000 --- a/packages/panels/src/Commands/MakeIssueCommand.php +++ /dev/null @@ -1,62 +0,0 @@ - $issueTemplate, - 'package-version' => $filament, - 'laravel-version' => $laravel, - 'livewire-version' => $livewire, - 'php-version' => $php, - ]; - - $fullUrl = "{$baseGithubUrl}" . http_build_query($queryParams); - - $this->openGithubIssueInBrowser($fullUrl); - } - - public function openGithubIssueInBrowser(string $url): void - { - if (PHP_OS_FAMILY === 'Darwin') { - exec('open "' . $url . '"'); - } - if (PHP_OS_FAMILY === 'Linux') { - exec('xdg-open "' . $url . '"'); - } - if (PHP_OS_FAMILY === 'Windows') { - exec('start "" "' . $url . '"'); - } - } -} diff --git a/packages/panels/src/Commands/MakePageCommand.php b/packages/panels/src/Commands/MakePageCommand.php index 82bdaa7eee..19b61e0faf 100644 --- a/packages/panels/src/Commands/MakePageCommand.php +++ b/packages/panels/src/Commands/MakePageCommand.php @@ -266,7 +266,7 @@ class MakePageCommand extends Command $this->copyStubToApp('ResourceManageRelatedRecordsPage', $path, [ 'baseResourcePage' => "Filament\\Resources\\Pages\\{$resourcePage}", 'baseResourcePageClass' => $resourcePage, - 'modifyQueryUsing' => filled($modifyQueryUsing ?? null) ? PHP_EOL . $this->indentString($modifyQueryUsing ?? '', 3) : $modifyQueryUsing ?? '', + 'modifyQueryUsing' => filled($modifyQueryUsing ?? null) ? PHP_EOL . $this->indentString($modifyQueryUsing, 3) : $modifyQueryUsing ?? '', 'namespace' => "{$resourceNamespace}\\{$resource}\\Pages" . ($pageNamespace !== '' ? "\\{$pageNamespace}" : ''), 'recordTitleAttribute' => $recordTitleAttribute ?? null, 'relationship' => $relationship ?? null, diff --git a/packages/panels/src/FilamentServiceProvider.php b/packages/panels/src/FilamentServiceProvider.php index f880b3b631..bdbcfa2ed8 100644 --- a/packages/panels/src/FilamentServiceProvider.php +++ b/packages/panels/src/FilamentServiceProvider.php @@ -108,7 +108,6 @@ class FilamentServiceProvider extends PackageServiceProvider Commands\CacheComponentsCommand::class, Commands\ClearCachedComponentsCommand::class, Commands\MakeClusterCommand::class, - Commands\MakeIssueCommand::class, Commands\MakePageCommand::class, Commands\MakePanelCommand::class, Commands\MakeRelationManagerCommand::class, diff --git a/packages/panels/src/Navigation/NavigationGroup.php b/packages/panels/src/Navigation/NavigationGroup.php index 18765ac68e..0a20d4e566 100644 --- a/packages/panels/src/Navigation/NavigationGroup.php +++ b/packages/panels/src/Navigation/NavigationGroup.php @@ -3,7 +3,6 @@ namespace Filament\Navigation; use Closure; -use Exception; use Filament\Support\Components\Component; use Filament\Support\Concerns\HasExtraSidebarAttributes; use Filament\Support\Concerns\HasExtraTopbarAttributes; @@ -82,13 +81,7 @@ class NavigationGroup extends Component public function getIcon(): ?string { - $icon = $this->evaluate($this->icon); - - if ($this->hasItemIcons() && filled($icon)) { - throw new Exception("Navigation group [{$this->getLabel()}] has an icon but one or more of its items also have icons. Either the group or its items can have icons, but not both. This is to ensure a proper user experience."); - } - - return $icon; + return $this->evaluate($this->icon); } /** @@ -126,30 +119,4 @@ class NavigationGroup extends Component return false; } - - public function hasItemIcons(): bool - { - $hasIconCount = 0; - $hasNoIconCount = 0; - - foreach ($this->getItems() as $item) { - if (! $item instanceof NavigationItem) { - continue; - } - - if (blank($item->getIcon())) { - $hasNoIconCount++; - - continue; - } - - $hasIconCount++; - } - - if (($hasIconCount > 0) && ($hasNoIconCount > 0) && filled($label = $this->getLabel())) { - throw new Exception("Navigation group [{$label}] has items with and without icons. All items must have icons or none of them can have icons. This is to ensure a proper user experience."); - } - - return $hasIconCount > 0; - } } diff --git a/packages/panels/src/Pages/Auth/EditProfile.php b/packages/panels/src/Pages/Auth/EditProfile.php index 283051cb5c..928e2bb730 100644 --- a/packages/panels/src/Pages/Auth/EditProfile.php +++ b/packages/panels/src/Pages/Auth/EditProfile.php @@ -21,9 +21,11 @@ use Filament\Support\Facades\FilamentView; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Route; use Illuminate\Validation\Rules\Password; +use Throwable; use function Filament\Support\is_app_url; @@ -143,6 +145,8 @@ class EditProfile extends Page public function save(): void { try { + DB::beginTransaction(); + $this->callHook('beforeValidate'); $data = $this->form->getState(); @@ -156,8 +160,18 @@ class EditProfile extends Page $this->handleRecordUpdate($this->getUser(), $data); $this->callHook('afterSave'); + + DB::commit(); } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + DB::rollBack() : + DB::commit(); + return; + } catch (Throwable $exception) { + DB::rollBack(); + + throw $exception; } if (request()->hasSession() && array_key_exists('password', $data)) { diff --git a/packages/panels/src/Pages/Auth/Register.php b/packages/panels/src/Pages/Auth/Register.php index e7d30b14ab..70a63c1c05 100644 --- a/packages/panels/src/Pages/Auth/Register.php +++ b/packages/panels/src/Pages/Auth/Register.php @@ -22,6 +22,7 @@ use Illuminate\Auth\SessionGuard; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Illuminate\Validation\Rules\Password; @@ -74,9 +75,11 @@ class Register extends SimplePage return null; } - $data = $this->form->getState(); + $user = DB::transaction(function () { + $data = $this->form->getState(); - $user = $this->getUserModel()::create($data); + return $this->getUserModel()::create($data); + }); event(new Registered($user)); diff --git a/packages/panels/src/Pages/Tenancy/EditTenantProfile.php b/packages/panels/src/Pages/Tenancy/EditTenantProfile.php index 5e52a770dc..907e8feda1 100644 --- a/packages/panels/src/Pages/Tenancy/EditTenantProfile.php +++ b/packages/panels/src/Pages/Tenancy/EditTenantProfile.php @@ -15,7 +15,9 @@ use Filament\Support\Facades\FilamentView; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\DB; use Livewire\Attributes\Locked; +use Throwable; use function Filament\authorize; use function Filament\Support\is_app_url; @@ -108,6 +110,8 @@ abstract class EditTenantProfile extends Page public function save(): void { try { + DB::beginTransaction(); + $this->callHook('beforeValidate'); $data = $this->form->getState(); @@ -121,8 +125,18 @@ abstract class EditTenantProfile extends Page $this->handleRecordUpdate($this->tenant, $data); $this->callHook('afterSave'); + + DB::commit(); } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + DB::rollBack() : + DB::commit(); + return; + } catch (Throwable $exception) { + DB::rollBack(); + + throw $exception; } $this->getSavedNotification()?->send(); diff --git a/packages/panels/src/Pages/Tenancy/RegisterTenant.php b/packages/panels/src/Pages/Tenancy/RegisterTenant.php index 185a9d04ff..fcae0c1fec 100644 --- a/packages/panels/src/Pages/Tenancy/RegisterTenant.php +++ b/packages/panels/src/Pages/Tenancy/RegisterTenant.php @@ -15,6 +15,8 @@ use Filament\Support\Facades\FilamentView; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\DB; +use Throwable; use function Filament\authorize; use function Filament\Support\is_app_url; @@ -70,6 +72,8 @@ abstract class RegisterTenant extends SimplePage public function register(): void { try { + DB::beginTransaction(); + $this->callHook('beforeValidate'); $data = $this->form->getState(); @@ -85,8 +89,18 @@ abstract class RegisterTenant extends SimplePage $this->form->model($this->tenant)->saveRelationships(); $this->callHook('afterRegister'); + + DB::commit(); } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + DB::rollBack() : + DB::commit(); + return; + } catch (Throwable $exception) { + DB::rollBack(); + + throw $exception; } if ($redirectUrl = $this->getRedirectUrl()) { diff --git a/packages/panels/src/Panel/Concerns/HasComponents.php b/packages/panels/src/Panel/Concerns/HasComponents.php index d76ad93626..69d2c77747 100644 --- a/packages/panels/src/Panel/Concerns/HasComponents.php +++ b/packages/panels/src/Panel/Concerns/HasComponents.php @@ -671,6 +671,6 @@ trait HasComponents public function getComponentCachePath(): string { - return (config('filament.cache_path') ?? base_path('bootstrap/cache/filament')) . DIRECTORY_SEPARATOR . "panels/{$this->getId()}.php"; + return (config('filament.cache_path') ?? base_path('bootstrap/cache/filament')) . DIRECTORY_SEPARATOR . 'panels' . DIRECTORY_SEPARATOR . "{$this->getId()}.php"; } } diff --git a/packages/panels/src/Resources/Pages/CreateRecord.php b/packages/panels/src/Resources/Pages/CreateRecord.php index 7c7fa838f7..04d64d98a3 100644 --- a/packages/panels/src/Resources/Pages/CreateRecord.php +++ b/packages/panels/src/Resources/Pages/CreateRecord.php @@ -14,6 +14,8 @@ use Filament\Support\Facades\FilamentView; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasManyThrough; +use Illuminate\Support\Facades\DB; +use Throwable; use function Filament\Support\is_app_url; @@ -74,6 +76,8 @@ class CreateRecord extends Page $this->authorizeAccess(); try { + DB::beginTransaction(); + $this->callHook('beforeValidate'); $data = $this->form->getState(); @@ -89,8 +93,18 @@ class CreateRecord extends Page $this->form->model($this->getRecord())->saveRelationships(); $this->callHook('afterCreate'); + + DB::commit(); } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + DB::rollBack() : + DB::commit(); + return; + } catch (Throwable $exception) { + DB::rollBack(); + + throw $exception; } $this->rememberData(); diff --git a/packages/panels/src/Resources/Pages/EditRecord.php b/packages/panels/src/Resources/Pages/EditRecord.php index 6897d1604c..22d1c3aa58 100644 --- a/packages/panels/src/Resources/Pages/EditRecord.php +++ b/packages/panels/src/Resources/Pages/EditRecord.php @@ -20,6 +20,8 @@ use Filament\Support\Facades\FilamentView; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\DB; +use Throwable; use function Filament\Support\is_app_url; @@ -82,22 +84,23 @@ class EditRecord extends Page protected function fillForm(): void { - $data = $this->getRecord()->attributesToArray(); - /** @internal Read the DocBlock above the following method. */ - $this->fillFormWithDataAndCallHooks($data); + $this->fillFormWithDataAndCallHooks($this->getRecord()); } /** * @internal Never override or call this method. If you completely override `fillForm()`, copy the contents of this method into your override. * - * @param array $data + * @param array $extraData */ - protected function fillFormWithDataAndCallHooks(array $data): void + protected function fillFormWithDataAndCallHooks(Model $record, array $extraData = []): void { $this->callHook('beforeFill'); - $data = $this->mutateFormDataBeforeFill($data); + $data = $this->mutateFormDataBeforeFill([ + ...$record->attributesToArray(), + ...$extraData, + ]); $this->form->fill($data); @@ -129,6 +132,8 @@ class EditRecord extends Page $this->authorizeAccess(); try { + DB::beginTransaction(); + $this->callHook('beforeValidate'); $data = $this->form->getState(); @@ -142,8 +147,18 @@ class EditRecord extends Page $this->handleRecordUpdate($this->getRecord(), $data); $this->callHook('afterSave'); + + DB::commit(); } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + DB::rollBack() : + DB::commit(); + return; + } catch (Throwable $exception) { + DB::rollBack(); + + throw $exception; } $this->rememberData(); diff --git a/packages/panels/src/Resources/Pages/ViewRecord.php b/packages/panels/src/Resources/Pages/ViewRecord.php index b086ee74bb..71735d6940 100644 --- a/packages/panels/src/Resources/Pages/ViewRecord.php +++ b/packages/panels/src/Resources/Pages/ViewRecord.php @@ -13,6 +13,7 @@ use Filament\Infolists\Infolist; use Filament\Pages\Concerns\InteractsWithFormActions; use Filament\Support\Facades\FilamentIcon; use Illuminate\Contracts\Support\Htmlable; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; /** @@ -76,22 +77,23 @@ class ViewRecord extends Page protected function fillForm(): void { - $data = $this->getRecord()->attributesToArray(); - /** @internal Read the DocBlock above the following method. */ - $this->fillFormWithDataAndCallHooks($data); + $this->fillFormWithDataAndCallHooks($this->getRecord()); } /** * @internal Never override or call this method. If you completely override `fillForm()`, copy the contents of this method into your override. * - * @param array $data + * @param array $extraData */ - protected function fillFormWithDataAndCallHooks(array $data): void + protected function fillFormWithDataAndCallHooks(Model $record, array $extraData = []): void { $this->callHook('beforeFill'); - $data = $this->mutateFormDataBeforeFill($data); + $data = $this->mutateFormDataBeforeFill([ + ...$record->attributesToArray(), + ...$extraData, + ]); $this->form->fill($data); diff --git a/packages/spatie-laravel-google-fonts-plugin/composer.json b/packages/spatie-laravel-google-fonts-plugin/composer.json index 0799075e5f..eabbb16a0d 100644 --- a/packages/spatie-laravel-google-fonts-plugin/composer.json +++ b/packages/spatie-laravel-google-fonts-plugin/composer.json @@ -9,8 +9,8 @@ }, "require": { "php": "^8.1", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0", + "illuminate/contracts": "^10.45", + "illuminate/support": "^10.45", "spatie/laravel-google-fonts": "^1.0" }, "autoload": { diff --git a/packages/spatie-laravel-media-library-plugin/composer.json b/packages/spatie-laravel-media-library-plugin/composer.json index f46fa45ae3..23085949d6 100644 --- a/packages/spatie-laravel-media-library-plugin/composer.json +++ b/packages/spatie-laravel-media-library-plugin/composer.json @@ -9,7 +9,7 @@ }, "require": { "php": "^8.1", - "illuminate/support": "^10.0", + "illuminate/support": "^10.45", "spatie/laravel-medialibrary": "^10.0|^11.0" }, "autoload": { diff --git a/packages/spatie-laravel-media-library-plugin/src/Infolists/Components/SpatieMediaLibraryImageEntry.php b/packages/spatie-laravel-media-library-plugin/src/Infolists/Components/SpatieMediaLibraryImageEntry.php index b305bd1d28..b20ab2ad9a 100644 --- a/packages/spatie-laravel-media-library-plugin/src/Infolists/Components/SpatieMediaLibraryImageEntry.php +++ b/packages/spatie-laravel-media-library-plugin/src/Infolists/Components/SpatieMediaLibraryImageEntry.php @@ -3,6 +3,7 @@ namespace Filament\Infolists\Components; use Closure; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; use Spatie\MediaLibrary\MediaCollections\Models\Media; use Throwable; @@ -46,34 +47,38 @@ class SpatieMediaLibraryImageEntry extends ImageEntry } if ($this->hasRelationship($record)) { - $record = $record->getRelationValue($this->getRelationshipName()); + $record = $this->getRelationshipResults($record); } - if (! $record) { - return null; - } + $records = Arr::wrap($record); - /** @var ?Media $media */ - $media = $record->media->first(fn (Media $media): bool => $media->uuid === $state); + foreach ($records as $record) { + /** @var Model $record */ - if (! $media) { - return null; - } + /** @var ?Media $media */ + $media = $record->getRelationValue('media')->first(fn (Media $media): bool => $media->uuid === $state); - $conversion = $this->getConversion(); - - if ($this->getVisibility() === 'private') { - try { - return $media->getTemporaryUrl( - now()->addMinutes(5), - $conversion ?? '', - ); - } catch (Throwable $exception) { - // This driver does not support creating temporary URLs. + if (! $media) { + continue; } + + $conversion = $this->getConversion(); + + if ($this->getVisibility() === 'private') { + try { + return $media->getTemporaryUrl( + now()->addMinutes(5), + $conversion ?? '', + ); + } catch (Throwable $exception) { + // This driver does not support creating temporary URLs. + } + } + + return $media->getAvailableUrl(Arr::wrap($conversion)); } - return $media->getAvailableUrl(Arr::wrap($conversion)); + return null; } /** @@ -86,13 +91,25 @@ class SpatieMediaLibraryImageEntry extends ImageEntry $record = $this->getRecord(); if ($this->hasRelationship($record)) { - $record = $record->getRelationValue($this->getRelationshipName()); + $record = $this->getRelationshipResults($record); } - return $record?->getRelationValue('media') - ->filter(fn (Media $media): bool => blank($collection) || ($media->getAttributeValue('collection_name') === $collection)) - ->sortBy('order_column') - ->map(fn (Media $media): string => $media->uuid) - ->all() ?? []; + $records = Arr::wrap($record); + + $state = []; + + foreach ($records as $record) { + /** @var Model $record */ + $state = [ + ...$state, + ...$record->getRelationValue('media') + ->filter(fn (Media $media): bool => blank($collection) || ($media->getAttributeValue('collection_name') === $collection)) + ->sortBy('order_column') + ->pluck('uuid') + ->all(), + ]; + } + + return array_unique($state); } } diff --git a/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php b/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php index c109e7e6a2..59b9a9e011 100644 --- a/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php +++ b/packages/spatie-laravel-media-library-plugin/src/Tables/Columns/SpatieMediaLibraryImageColumn.php @@ -4,6 +4,7 @@ namespace Filament\Tables\Columns; use Closure; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Arr; use Spatie\MediaLibrary\MediaCollections\Models\Media; @@ -44,34 +45,38 @@ class SpatieMediaLibraryImageColumn extends ImageColumn $record = $this->getRecord(); if ($this->hasRelationship($record)) { - $record = $record->getRelationValue($this->getRelationshipName()); + $record = $this->getRelationshipResults($record); } - if (! $record) { - return null; - } + $records = Arr::wrap($record); - /** @var ?Media $media */ - $media = $record->media->first(fn (Media $media): bool => $media->uuid === $state); + foreach ($records as $record) { + /** @var Model $record */ - if (! $media) { - return null; - } + /** @var ?Media $media */ + $media = $record->getRelationValue('media')->first(fn (Media $media): bool => $media->uuid === $state); - $conversion = $this->getConversion(); - - if ($this->getVisibility() === 'private') { - try { - return $media->getTemporaryUrl( - now()->addMinutes(5), - $conversion ?? '', - ); - } catch (Throwable $exception) { - // This driver does not support creating temporary URLs. + if (! $media) { + continue; } + + $conversion = $this->getConversion(); + + if ($this->getVisibility() === 'private') { + try { + return $media->getTemporaryUrl( + now()->addMinutes(5), + $conversion ?? '', + ); + } catch (Throwable $exception) { + // This driver does not support creating temporary URLs. + } + } + + return $media->getAvailableUrl(Arr::wrap($conversion)); } - return $media->getAvailableUrl(Arr::wrap($conversion)); + return null; } /** @@ -79,19 +84,28 @@ class SpatieMediaLibraryImageColumn extends ImageColumn */ public function getState(): array { - $collection = $this->getCollection(); - $record = $this->getRecord(); if ($this->hasRelationship($record)) { - $record = $record->getRelationValue($this->getRelationshipName()); + $record = $this->getRelationshipResults($record); } - return $record?->getRelationValue('media') - ->filter(fn (Media $media): bool => blank($collection) || ($media->getAttributeValue('collection_name') === $collection)) - ->sortBy('order_column') - ->map(fn (Media $media): string => $media->uuid) - ->all() ?? []; + $records = Arr::wrap($record); + + $state = []; + + foreach ($records as $record) { + /** @var Model $record */ + $state = [ + ...$state, + ...$record->getRelationValue('media') + ->sortBy('order_column') + ->pluck('uuid') + ->all(), + ]; + } + + return array_unique($state); } public function applyEagerLoading(Builder | Relation $query): Builder | Relation @@ -100,18 +114,23 @@ class SpatieMediaLibraryImageColumn extends ImageColumn return $query; } + /** @phpstan-ignore-next-line */ + $modifyMediaQuery = fn (Builder | Relation $query) => $query + ->ordered() + ->when( + $this->getCollection(), + fn (Builder | Relation $query, string $collection) => $query->where( + 'collection_name', + $collection, + ), + ); + if ($this->hasRelationship($query->getModel())) { return $query->with([ - "{$this->getRelationshipName()}.media" => fn (Builder | Relation $query) => $query->when( - $this->getCollection(), - fn (Builder | Relation $query, string $collection) => $query->where( - 'collection_name', - $collection, - ), - ), + "{$this->getRelationshipName()}.media" => $modifyMediaQuery, ]); } - return $query->with(['media']); + return $query->with(['media' => $modifyMediaQuery]); } } diff --git a/packages/spatie-laravel-settings-plugin/composer.json b/packages/spatie-laravel-settings-plugin/composer.json index 97558fcb56..e775b0da13 100644 --- a/packages/spatie-laravel-settings-plugin/composer.json +++ b/packages/spatie-laravel-settings-plugin/composer.json @@ -10,9 +10,9 @@ "require": { "php": "^8.1", "filament/filament": "self.version", - "illuminate/console": "^10.0", - "illuminate/filesystem": "^10.0", - "illuminate/support": "^10.0", + "illuminate/console": "^10.45", + "illuminate/filesystem": "^10.45", + "illuminate/support": "^10.45", "spatie/laravel-settings": "^2.2|^3.0" }, "autoload": { diff --git a/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php b/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php index 2f0fe97b6b..ebb20b802b 100644 --- a/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php +++ b/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php @@ -9,6 +9,8 @@ use Filament\Forms\Form; use Filament\Notifications\Notification; use Filament\Support\Exceptions\Halt; use Filament\Support\Facades\FilamentView; +use Illuminate\Support\Facades\DB; +use Throwable; use function Filament\Support\is_app_url; @@ -58,6 +60,8 @@ class SettingsPage extends Page public function save(): void { try { + DB::beginTransaction(); + $this->callHook('beforeValidate'); $data = $this->form->getState(); @@ -74,8 +78,18 @@ class SettingsPage extends Page $settings->save(); $this->callHook('afterSave'); + + DB::commit(); } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + DB::rollBack() : + DB::commit(); + return; + } catch (Throwable $exception) { + DB::rollBack(); + + throw $exception; } $this->getSavedNotification()?->send(); diff --git a/packages/spatie-laravel-tags-plugin/composer.json b/packages/spatie-laravel-tags-plugin/composer.json index 045ddd7dfd..582b646932 100644 --- a/packages/spatie-laravel-tags-plugin/composer.json +++ b/packages/spatie-laravel-tags-plugin/composer.json @@ -9,7 +9,7 @@ }, "require": { "php": "^8.1", - "illuminate/database": "^10.0", + "illuminate/database": "^10.45", "spatie/laravel-tags": "^4.0" }, "autoload": { diff --git a/packages/spatie-laravel-tags-plugin/src/Infolists/Components/SpatieTagsEntry.php b/packages/spatie-laravel-tags-plugin/src/Infolists/Components/SpatieTagsEntry.php index 82b40d6e52..276bd7e1de 100644 --- a/packages/spatie-laravel-tags-plugin/src/Infolists/Components/SpatieTagsEntry.php +++ b/packages/spatie-laravel-tags-plugin/src/Infolists/Components/SpatieTagsEntry.php @@ -5,6 +5,8 @@ namespace Filament\Infolists\Components; use Closure; use Filament\SpatieLaravelTagsPlugin\Types\AllTagTypes; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Arr; class SpatieTagsEntry extends TextEntry { @@ -37,27 +39,34 @@ class SpatieTagsEntry extends TextEntry } if ($this->hasRelationship($record)) { - $record = $record->getRelationValue($this->getRelationshipName()); + $record = $this->getRelationshipResults($record); } - if (! $record) { - return []; + $records = Arr::wrap($record); + + $state = []; + + foreach ($records as $record) { + /** @var Model $record */ + if (! (method_exists($record, 'tags') && method_exists($record, 'tagsWithType'))) { + continue; + } + + $type = $this->getType(); + + if ($this->isAnyTagTypeAllowed()) { + $tags = $record->getRelationValue('tags'); + } else { + $tags = $record->tagsWithType($type); + } + + $state = [ + ...$state, + ...$tags->pluck('name')->all(), + ]; } - if (! (method_exists($record, 'tags') && method_exists($record, 'tagsWithType'))) { - return []; - } - - $type = $this->getType(); - - if ($this->isAnyTagTypeAllowed()) { - /** @phpstan-ignore-next-line */ - $tags = $record->tags; - } else { - $tags = $record->tagsWithType($type); - } - - return $tags->pluck('name')->all(); + return array_unique($state); } public function type(string | Closure | AllTagTypes | null $type): static diff --git a/packages/spatie-laravel-tags-plugin/src/Tables/Columns/SpatieTagsColumn.php b/packages/spatie-laravel-tags-plugin/src/Tables/Columns/SpatieTagsColumn.php index 74b88f915d..23d2885fc9 100644 --- a/packages/spatie-laravel-tags-plugin/src/Tables/Columns/SpatieTagsColumn.php +++ b/packages/spatie-laravel-tags-plugin/src/Tables/Columns/SpatieTagsColumn.php @@ -5,10 +5,12 @@ namespace Filament\Tables\Columns; use Closure; use Filament\SpatieLaravelTagsPlugin\Types\AllTagTypes; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; -class SpatieTagsColumn extends TagsColumn +class SpatieTagsColumn extends TextColumn { protected string | Closure | AllTagTypes | null $type; @@ -17,6 +19,8 @@ class SpatieTagsColumn extends TagsColumn parent::setUp(); $this->type(new AllTagTypes()); + + $this->badge(); } /** @@ -33,27 +37,34 @@ class SpatieTagsColumn extends TagsColumn $record = $this->getRecord(); if ($this->hasRelationship($record)) { - $record = $record->getRelationValue($this->getRelationshipName()); + $record = $this->getRelationshipResults($record); } - if (! $record) { - return []; + $records = Arr::wrap($record); + + $state = []; + + foreach ($records as $record) { + /** @var Model $record */ + if (! (method_exists($record, 'tags') && method_exists($record, 'tagsWithType'))) { + continue; + } + + $type = $this->getType(); + + if ($this->isAnyTagTypeAllowed()) { + $tags = $record->getRelationValue('tags'); + } else { + $tags = $record->tagsWithType($type); + } + + $state = [ + ...$state, + ...$tags->pluck('name')->all(), + ]; } - if (! (method_exists($record, 'tags') && method_exists($record, 'tagsWithType'))) { - return []; - } - - $type = $this->getType(); - - if ($this->isAnyTagTypeAllowed()) { - /** @phpstan-ignore-next-line */ - $tags = $record->tags; - } else { - $tags = $record->tagsWithType($type); - } - - return $tags->pluck('name')->all(); + return array_unique($state); } public function type(string | Closure | AllTagTypes | null $type): static diff --git a/packages/spatie-laravel-translatable-plugin/composer.json b/packages/spatie-laravel-translatable-plugin/composer.json index 3981892d35..6b8d6d3e44 100644 --- a/packages/spatie-laravel-translatable-plugin/composer.json +++ b/packages/spatie-laravel-translatable-plugin/composer.json @@ -10,7 +10,7 @@ "require": { "php": "^8.1", "filament/support": "self.version", - "illuminate/support": "^10.0", + "illuminate/support": "^10.45", "spatie/laravel-translatable": "^6.0" }, "autoload": { diff --git a/packages/spatie-laravel-translatable-plugin/src/Resources/Pages/Concerns/HasTranslatableFormWithExistingRecordData.php b/packages/spatie-laravel-translatable-plugin/src/Resources/Pages/Concerns/HasTranslatableFormWithExistingRecordData.php index 2e8304ca72..f727d66f11 100644 --- a/packages/spatie-laravel-translatable-plugin/src/Resources/Pages/Concerns/HasTranslatableFormWithExistingRecordData.php +++ b/packages/spatie-laravel-translatable-plugin/src/Resources/Pages/Concerns/HasTranslatableFormWithExistingRecordData.php @@ -30,10 +30,7 @@ trait HasTranslatableFormWithExistingRecordData } /** @internal Read the DocBlock above the following method. */ - $this->fillFormWithDataAndCallHooks([ - ...$record->attributesToArray(), - ...$translatedData, - ]); + $this->fillFormWithDataAndCallHooks($record, $translatedData); } } diff --git a/packages/support/composer.json b/packages/support/composer.json index 762722fbb5..ea6097e0cd 100644 --- a/packages/support/composer.json +++ b/packages/support/composer.json @@ -12,9 +12,9 @@ "blade-ui-kit/blade-heroicons": "^2.2.1", "doctrine/dbal": "^3.2", "ext-intl": "*", - "illuminate/contracts": "^10.0", - "illuminate/support": "^10.0", - "illuminate/view": "^10.0", + "illuminate/contracts": "^10.45", + "illuminate/support": "^10.45", + "illuminate/view": "^10.45", "livewire/livewire": "^3.2.3", "ryangjchandler/blade-capture-directive": "^0.2|^0.3", "spatie/color": "^1.5", diff --git a/packages/support/docs/09-blade-components/02-dropdown.md b/packages/support/docs/09-blade-components/02-dropdown.md index a2eb33a4a6..f1cb0c778a 100644 --- a/packages/support/docs/09-blade-components/02-dropdown.md +++ b/packages/support/docs/09-blade-components/02-dropdown.md @@ -79,6 +79,32 @@ You can add an [icon](https://blade-ui-kit.com/blade-icons?set=1#search) to a dr ``` +### Changing the icon color of a dropdown item + +By default, the icon color uses the [same color as the item itself](#changing-the-color-of-a-dropdown-item). You can override it to be `danger`, `info`, `primary`, `success` or `warning` by using the `icon-color` attribute: + +```blade + + Edit + + + + Edit + + + + Edit + + + + Edit + + + + Edit + +``` + ## Adding an image to a dropdown item You can add a circular image to a dropdown item by using the `image` attribute: diff --git a/packages/support/resources/lang/pt_PT/components/pagination.php b/packages/support/resources/lang/pt_PT/components/pagination.php index 0f86a7c9e2..5989b5952b 100644 --- a/packages/support/resources/lang/pt_PT/components/pagination.php +++ b/packages/support/resources/lang/pt_PT/components/pagination.php @@ -22,12 +22,20 @@ return [ 'actions' => [ + 'first' => [ + 'label' => 'Primeira', + ], + 'go_to_page' => [ 'label' => 'Ir para a página :page', ], + 'last' => [ + 'label' => 'Última', + ], + 'next' => [ - 'label' => 'Próximo', + 'label' => 'Próxima', ], 'previous' => [ diff --git a/packages/support/resources/lang/ro/components/pagination.php b/packages/support/resources/lang/ro/components/pagination.php index 2ae858e932..090b9be011 100644 --- a/packages/support/resources/lang/ro/components/pagination.php +++ b/packages/support/resources/lang/ro/components/pagination.php @@ -22,10 +22,18 @@ return [ 'actions' => [ + 'first' => [ + 'label' => 'Prima pagină', + ], + 'go_to_page' => [ 'label' => 'Mergi la pagina :page', ], + 'last' => [ + 'label' => 'Ultima pagină', + ], + 'next' => [ 'label' => 'Pagina următoare', ], diff --git a/packages/support/resources/views/components/button/group.blade.php b/packages/support/resources/views/components/button/group.blade.php index a7d90aa31b..d06423bfe5 100644 --- a/packages/support/resources/views/components/button/group.blade.php +++ b/packages/support/resources/views/components/button/group.blade.php @@ -1,7 +1,7 @@
      class([ - 'grid grid-flow-col rounded-lg shadow-sm ring-1 ring-gray-950/10 dark:ring-white/20', + 'fi-btn-group grid grid-flow-col rounded-lg shadow-sm ring-1 ring-gray-950/10 dark:ring-white/20', ]) }} > diff --git a/packages/support/resources/views/components/dropdown/list/item.blade.php b/packages/support/resources/views/components/dropdown/list/item.blade.php index e486278d59..7026ca1162 100644 --- a/packages/support/resources/views/components/dropdown/list/item.blade.php +++ b/packages/support/resources/views/components/dropdown/list/item.blade.php @@ -11,6 +11,7 @@ 'href' => null, 'icon' => null, 'iconAlias' => null, + 'iconColor' => null, 'iconSize' => IconSize::Medium, 'image' => null, 'keyBindings' => null, @@ -44,6 +45,8 @@ ) => $color !== 'gray', ]); + $iconColor ??= $color; + $iconClasses = \Illuminate\Support\Arr::toCssClasses([ 'fi-dropdown-list-item-icon', match ($iconSize) { @@ -52,7 +55,7 @@ IconSize::Large, 'lg' => 'h-6 w-6', default => $iconSize, }, - match ($color) { + match ($iconColor) { 'gray' => 'text-gray-400 dark:text-gray-500', default => 'text-custom-500 dark:text-custom-400', }, @@ -60,10 +63,10 @@ $iconStyles = \Illuminate\Support\Arr::toCssStyles([ \Filament\Support\get_color_css_variables( - $color, + $iconColor, shades: [400, 500], alias: 'dropdown.list.item.icon', - ) => $color !== 'gray', + ) => $iconColor !== 'gray', ]); $imageClasses = 'fi-dropdown-list-item-image h-5 w-5 rounded-full bg-cover bg-center'; diff --git a/packages/support/resources/views/components/modal/index.blade.php b/packages/support/resources/views/components/modal/index.blade.php index d5cb67d0cc..9576926aea 100644 --- a/packages/support/resources/views/components/modal/index.blade.php +++ b/packages/support/resources/views/components/modal/index.blade.php @@ -30,6 +30,10 @@ ]) @php + $hasDescription = filled($description); + $hasHeading = filled($heading); + $hasIcon = filled($icon); + if (! $alignment instanceof Alignment) { $alignment = filled($alignment) ? (Alignment::tryFrom($alignment) ?? $alignment) : null; } @@ -207,6 +211,7 @@ Alignment::Center => 'flex-col', default => null, }, + 'items-center' => $hasIcon && $hasHeading && (! $hasDescription) && in_array($alignment, [Alignment::Start, Alignment::Left]), ]) > @if ($closeButton) @@ -233,7 +238,7 @@ @if ($header) {{ $header }} @else - @if ($icon) + @if ($hasIcon)
      $alignment === Alignment::Center, @@ -284,7 +289,7 @@ {{ $heading }} - @if (filled($description)) + @if ($hasDescription) @@ -301,8 +306,8 @@ @class([ 'fi-modal-content flex flex-col gap-y-4 py-6', 'flex-1' => ($width === MaxWidth::Screen) || $slideOver, - 'pe-6 ps-[5.25rem]' => $icon && ($alignment === Alignment::Start), - 'px-6' => ! ($icon && ($alignment === Alignment::Start)), + 'pe-6 ps-[5.25rem]' => $hasIcon && ($alignment === Alignment::Start), + 'px-6' => ! ($hasIcon && ($alignment === Alignment::Start)), ]) > {{ $slot }} @@ -313,8 +318,8 @@
      $icon && ($alignment === Alignment::Start) && ($footerActionsAlignment !== Alignment::Center) && (! $stickyFooter), - 'px-6' => ! ($icon && ($alignment === Alignment::Start) && ($footerActionsAlignment !== Alignment::Center) && (! $stickyFooter)), + 'pe-6 ps-[5.25rem]' => $hasIcon && ($alignment === Alignment::Start) && ($footerActionsAlignment !== Alignment::Center) && (! $stickyFooter), + 'px-6' => ! ($hasIcon && ($alignment === Alignment::Start) && ($footerActionsAlignment !== Alignment::Center) && (! $stickyFooter)), 'fi-sticky sticky bottom-0 border-t border-gray-200 bg-white py-5 dark:border-white/10 dark:bg-gray-900' => $stickyFooter, 'rounded-b-xl' => $stickyFooter && ! ($slideOver || ($width === MaxWidth::Screen)), 'pb-6' => ! $stickyFooter, diff --git a/packages/support/resources/views/components/pagination/index.blade.php b/packages/support/resources/views/components/pagination/index.blade.php index 106be9704a..4768d5be62 100644 --- a/packages/support/resources/views/components/pagination/index.blade.php +++ b/packages/support/resources/views/components/pagination/index.blade.php @@ -51,9 +51,9 @@ 'filament::components/pagination.overview', $paginator->total(), [ - 'first' => \Filament\Support\format_number($paginator->firstItem() ?? 0), - 'last' => \Filament\Support\format_number($paginator->lastItem() ?? 0), - 'total' => \Filament\Support\format_number($paginator->total()), + 'first' => \Illuminate\Support\Number::format($paginator->firstItem() ?? 0), + 'last' => \Illuminate\Support\Number::format($paginator->lastItem() ?? 0), + 'total' => \Illuminate\Support\Number::format($paginator->total()), ], ) }} diff --git a/packages/panels/src/Commands/Aliases/MakeIssueCommand.php b/packages/support/src/Commands/Aliases/MakeIssueCommand.php similarity index 65% rename from packages/panels/src/Commands/Aliases/MakeIssueCommand.php rename to packages/support/src/Commands/Aliases/MakeIssueCommand.php index f47ff6b193..42da90f261 100644 --- a/packages/panels/src/Commands/Aliases/MakeIssueCommand.php +++ b/packages/support/src/Commands/Aliases/MakeIssueCommand.php @@ -1,8 +1,8 @@ openUrlInBrowser('https://github.com/filamentphp/filament/issues/new?' . http_build_query([ + 'template' => 'bug_report.yml', + 'package-version' => InstalledVersions::getPrettyVersion('filament/support'), + 'laravel-version' => InstalledVersions::getPrettyVersion('laravel/framework'), + 'livewire-version' => InstalledVersions::getPrettyVersion('livewire/livewire'), + 'php-version' => PHP_VERSION, + ])); + } + + public function openUrlInBrowser(string $url): void + { + if (PHP_OS_FAMILY === 'Darwin') { + exec('open "' . $url . '"'); + } + if (PHP_OS_FAMILY === 'Linux') { + exec('xdg-open "' . $url . '"'); + } + if (PHP_OS_FAMILY === 'Windows') { + exec('start "" "' . $url . '"'); + } + } +} diff --git a/packages/support/src/Enums/ArgumentValue.php b/packages/support/src/Enums/ArgumentValue.php new file mode 100644 index 0000000000..c7e900123a --- /dev/null +++ b/packages/support/src/Enums/ArgumentValue.php @@ -0,0 +1,8 @@ +shouldRollbackDatabaseTransaction = $condition; + + return $this; + } + + public function shouldRollbackDatabaseTransaction(): bool + { + return $this->shouldRollbackDatabaseTransaction; + } } diff --git a/packages/support/src/Exceptions/Halt.php b/packages/support/src/Exceptions/Halt.php index fbc4a8cef9..078a13351e 100644 --- a/packages/support/src/Exceptions/Halt.php +++ b/packages/support/src/Exceptions/Halt.php @@ -6,4 +6,17 @@ use Exception; class Halt extends Exception { + protected bool $shouldRollbackDatabaseTransaction = false; + + public function rollBackDatabaseTransaction(bool $condition = true): static + { + $this->shouldRollbackDatabaseTransaction = $condition; + + return $this; + } + + public function shouldRollbackDatabaseTransaction(): bool + { + return $this->shouldRollbackDatabaseTransaction; + } } diff --git a/packages/support/src/SupportServiceProvider.php b/packages/support/src/SupportServiceProvider.php index d041b1417e..59e30cf275 100644 --- a/packages/support/src/SupportServiceProvider.php +++ b/packages/support/src/SupportServiceProvider.php @@ -7,9 +7,11 @@ use Filament\Support\Assets\AssetManager; use Filament\Support\Assets\Css; use Filament\Support\Assets\Js; use Filament\Support\Colors\ColorManager; +use Filament\Support\Commands\Aliases\MakeIssueCommand as MakeIssueCommandAlias; use Filament\Support\Commands\AssetsCommand; use Filament\Support\Commands\CheckTranslationsCommand; use Filament\Support\Commands\InstallCommand; +use Filament\Support\Commands\MakeIssueCommand; use Filament\Support\Commands\UpgradeCommand; use Filament\Support\Components\ComponentManager; use Filament\Support\Components\Contracts\ScopedComponentManager; @@ -39,6 +41,9 @@ class SupportServiceProvider extends PackageServiceProvider CheckTranslationsCommand::class, InstallCommand::class, UpgradeCommand::class, + + MakeIssueCommand::class, + MakeIssueCommandAlias::class, ]) ->hasConfigFile() ->hasTranslations() diff --git a/packages/support/src/helpers.php b/packages/support/src/helpers.php index 62ecdf5f03..9512062bd5 100644 --- a/packages/support/src/helpers.php +++ b/packages/support/src/helpers.php @@ -8,30 +8,33 @@ use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Support\HtmlString; +use Illuminate\Support\Number; use Illuminate\Support\Str; use Illuminate\Translation\MessageSelector; use Illuminate\View\ComponentAttributeBag; -use NumberFormatter; +use Illuminate\View\ComponentSlot; if (! function_exists('Filament\Support\format_money')) { + /** + * @deprecated Use `Illuminate\Support\Number::currency()` instead. + */ function format_money(float | int $money, string $currency, int $divideBy = 0): string { - $formatter = new NumberFormatter(app()->getLocale(), NumberFormatter::CURRENCY); - if ($divideBy) { $money /= $divideBy; } - return $formatter->formatCurrency($money, $currency); + return Number::currency($money, $currency); } } if (! function_exists('Filament\Support\format_number')) { + /** + * @deprecated Use `Illuminate\Support\Number::format()` instead. + */ function format_number(float | int $number): string { - $formatter = new NumberFormatter(app()->getLocale(), NumberFormatter::DECIMAL); - - return $formatter->format($number); + return Number::format($number); } } @@ -118,18 +121,11 @@ if (! function_exists('Filament\Support\is_slot_empty')) { return true; } - return trim( - str_replace( - [ - '', - '', - '', - '', - ], - '', - $slot->toHtml() - ), - ) === ''; + if (! $slot instanceof ComponentSlot) { + $slot = new ComponentSlot($slot->toHtml()); + } + + return ! $slot->hasActualContent(); } } diff --git a/packages/tables/composer.json b/packages/tables/composer.json index 17d302ea5b..87ef08267e 100644 --- a/packages/tables/composer.json +++ b/packages/tables/composer.json @@ -12,12 +12,12 @@ "filament/actions": "self.version", "filament/forms": "self.version", "filament/support": "self.version", - "illuminate/console": "^10.0", - "illuminate/contracts": "^10.0", - "illuminate/database": "^10.0", - "illuminate/filesystem": "^10.0", - "illuminate/support": "^10.0", - "illuminate/view": "^10.0", + "illuminate/console": "^10.45", + "illuminate/contracts": "^10.45", + "illuminate/database": "^10.45", + "illuminate/filesystem": "^10.45", + "illuminate/support": "^10.45", + "illuminate/view": "^10.45", "kirschbaum-development/eloquent-power-joins": "^3.0", "spatie/laravel-package-tools": "^1.9" }, diff --git a/packages/tables/docs/03-columns/02-text.md b/packages/tables/docs/03-columns/02-text.md index 11f319a96b..816b7a2610 100644 --- a/packages/tables/docs/03-columns/02-text.md +++ b/packages/tables/docs/03-columns/02-text.md @@ -84,17 +84,39 @@ TextColumn::make('created_at') ## Number formatting -The `numeric()` method allows you to format a column as a number, using PHP's `number_format()`: +The `numeric()` method allows you to format an entry as a number: ```php use Filament\Tables\Columns\TextColumn; TextColumn::make('stock') - ->numeric( - decimalPlaces: 0, - decimalSeparator: '.', - thousandsSeparator: ',', - ) + ->numeric() +``` + +If you would like to customize the number of decimal places used to format the number with, you can use the `decimalPlaces` argument: + +```php +use Filament\Tables\Columns\TextColumn; + +TextColumn::make('stock') + ->numeric(decimalPlaces: 0) +``` + +By default, your app's locale will be used to format the number suitably. If you would like to customize the locale used, you can pass it to the `locale` argument: + +```php +use Filament\Tables\Columns\TextColumn; + +TextColumn::make('stock') + ->numeric(locale: 'nl') +``` + +Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider: + +```php +use Illuminate\Support\Number; + +Number::useLocale('nl'); ``` ## Currency formatting @@ -117,6 +139,23 @@ TextColumn::make('price') ->money('EUR', divideBy: 100) ``` +By default, your app's locale will be used to format the money suitably. If you would like to customize the locale used, you can pass it to the `locale` argument: + +```php +use Filament\Tables\Columns\TextColumn; + +TextColumn::make('price') + ->money('EUR', locale: 'nl') +``` + +Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider: + +```php +use Illuminate\Support\Number; + +Number::useLocale('nl'); +``` + ## Limiting text length You may `limit()` the length of the cell's value: diff --git a/packages/tables/docs/07-summaries.md b/packages/tables/docs/07-summaries.md index 061be9d05b..66041651e5 100644 --- a/packages/tables/docs/07-summaries.md +++ b/packages/tables/docs/07-summaries.md @@ -221,7 +221,17 @@ In this example, the table will calculate how many posts are published. ### Number formatting -The `numeric()` method allows you to format a summary as a number, using PHP's `number_format()`: +The `numeric()` method allows you to format an entry as a number: + +```php +use Filament\Tables\Columns\Summarizers\Average; +use Filament\Tables\Columns\TextColumn; + +TextColumn::make('rating') + ->summarize(Average::make()->numeric()) +``` + +If you would like to customize the number of decimal places used to format the number with, you can use the `decimalPlaces` argument: ```php use Filament\Tables\Columns\Summarizers\Average; @@ -230,11 +240,29 @@ use Filament\Tables\Columns\TextColumn; TextColumn::make('rating') ->summarize(Average::make()->numeric( decimalPlaces: 0, - decimalSeparator: '.', - thousandsSeparator: ',', )) ``` +By default, your app's locale will be used to format the number suitably. If you would like to customize the locale used, you can pass it to the `locale` argument: + +```php +use Filament\Tables\Columns\Summarizers\Average; +use Filament\Tables\Columns\TextColumn; + +TextColumn::make('rating') + ->summarize(Average::make()->numeric( + locale: 'nl', + )) +``` + +Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider: + +```php +use Illuminate\Support\Number; + +Number::useLocale('nl'); +``` + ### Currency formatting The `money()` method allows you to easily format monetary values, in any currency: @@ -257,6 +285,24 @@ TextColumn::make('price') ->summarize(Sum::make()->money('EUR', divideBy: 100)) ``` +By default, your app's locale will be used to format the money suitably. If you would like to customize the locale used, you can pass it to the `locale` argument: + +```php +use Filament\Tables\Columns\Summarizers\Average; +use Filament\Tables\Columns\TextColumn; + +TextColumn::make('price') + ->summarize(Sum::make()->money(currency: 'EUR', locale: 'nl')) +``` + +Alternatively, you can set the default locale used across your app using the `Number::useLocale()` method in the `boot()` method of a service provider: + +```php +use Illuminate\Support\Number; + +Number::useLocale('nl'); +``` + ### Limiting text length You may `limit()` the length of the summary's value: diff --git a/packages/tables/resources/lang/pt_PT/table.php b/packages/tables/resources/lang/pt_PT/table.php index 1f0e861195..9c03d09f79 100644 --- a/packages/tables/resources/lang/pt_PT/table.php +++ b/packages/tables/resources/lang/pt_PT/table.php @@ -134,7 +134,7 @@ return [ 'heading' => 'Filtros', - 'indicator' => 'Filtros ativos', + 'indicator' => 'Filtros activos', 'multi_select' => [ 'placeholder' => 'Todos', diff --git a/packages/tables/resources/lang/ro/table.php b/packages/tables/resources/lang/ro/table.php index 46d4935399..9f3e417ba8 100644 --- a/packages/tables/resources/lang/ro/table.php +++ b/packages/tables/resources/lang/ro/table.php @@ -26,6 +26,10 @@ return [ 'label' => 'Selectează/Deselectează elementul :key pentru operațiuni in masă.', ], + 'bulk_select_group' => [ + 'label' => 'Selectează/Deselectează grupul :title pentru operațiuni in masă.', + ], + 'search' => [ 'label' => 'Căutare', 'placeholder' => 'Căutare', @@ -102,6 +106,10 @@ return [ 'actions' => [ + 'apply' => [ + 'label' => 'Aplică filtrele', + ], + 'remove' => [ 'label' => 'Elimină filtru', ], diff --git a/packages/tables/resources/views/columns/image-column.blade.php b/packages/tables/resources/views/columns/image-column.blade.php index ad4ea2b10f..c496839afa 100644 --- a/packages/tables/resources/views/columns/image-column.blade.php +++ b/packages/tables/resources/views/columns/image-column.blade.php @@ -10,10 +10,15 @@ $height = $getHeight() ?? ($isStacked ? '2rem' : '2.5rem'); $width = $getWidth() ?? (($isCircular || $isSquare) ? $height : null); + $stateCount = count($state); + $limitedStateCount = count($limitedState); + $defaultImageUrl = $getDefaultImageUrl(); - if ((! count($limitedState)) && filled($defaultImageUrl)) { + if ((! $limitedStateCount) && filled($defaultImageUrl)) { $limitedState = [null]; + + $limitedStateCount = 1; } $ringClasses = \Illuminate\Support\Arr::toCssClasses([ @@ -28,7 +33,7 @@ }, ]); - $hasLimitedRemainingText = $hasLimitedRemainingText(); + $hasLimitedRemainingText = $hasLimitedRemainingText() && ($limitedStateCount < $stateCount); $isLimitedRemainingTextSeparate = $isLimitedRemainingTextSeparate(); $limitedRemainingTextSizeClasses = match ($getLimitedRemainingTextSize()) { @@ -50,7 +55,7 @@ ]) }} > - @if (count($limitedState)) + @if ($limitedStateCount)
      @endforeach - @if ($hasLimitedRemainingText && ($loop->iteration < count($limitedState)) && (! $isLimitedRemainingTextSeparate) && $isCircular) + @if ($hasLimitedRemainingText && (! $isLimitedRemainingTextSeparate) && $isCircular)
      - +{{ count($state) - count($limitedState) }} + +{{ $stateCount - $limitedStateCount }}
      @endif
      - @if ($hasLimitedRemainingText && ($loop->iteration < count($limitedState)) && ($isLimitedRemainingTextSeparate || (! $isCircular))) + @if ($hasLimitedRemainingText && ($isLimitedRemainingTextSeparate || (! $isCircular)))
      - +{{ count($state) - count($limitedState) }} + +{{ $stateCount - $limitedStateCount }}
      @endif
      diff --git a/packages/tables/resources/views/columns/text-input-column.blade.php b/packages/tables/resources/views/columns/text-input-column.blade.php index ff02801f8e..d1ac776979 100644 --- a/packages/tables/resources/views/columns/text-input-column.blade.php +++ b/packages/tables/resources/views/columns/text-input-column.blade.php @@ -3,13 +3,19 @@ $isDisabled = $isDisabled(); $state = $getState(); - $type = $getType(); + $mask = $getMask(); $alignment = $getAlignment() ?? Alignment::Start; if (! $alignment instanceof Alignment) { $alignment = filled($alignment) ? (Alignment::tryFrom($alignment) ?? $alignment) : null; } + + if (filled($mask)) { + $type = 'text'; + } else { + $type = $getType(); + } @endphp
      filled($mask) ? $mask : null, ]) ->class([ match ($alignment) { diff --git a/packages/tables/src/Actions/CreateAction.php b/packages/tables/src/Actions/CreateAction.php index c1d8b8adb1..5571c79b29 100644 --- a/packages/tables/src/Actions/CreateAction.php +++ b/packages/tables/src/Actions/CreateAction.php @@ -42,6 +42,8 @@ class CreateAction extends Action $this->successNotificationTitle(__('filament-actions::create.single.notifications.created.title')); + $this->databaseTransaction(); + $this->action(function (array $arguments, Form $form, HasTable $livewire): void { $model = $this->getModel(); diff --git a/packages/tables/src/Actions/EditAction.php b/packages/tables/src/Actions/EditAction.php index 625e97f642..d781ebb9a5 100644 --- a/packages/tables/src/Actions/EditAction.php +++ b/packages/tables/src/Actions/EditAction.php @@ -49,6 +49,8 @@ class EditAction extends Action return $data; }); + $this->databaseTransaction(); + $this->action(function (): void { $this->process(function (array $data, Model $record, Table $table) { $relationship = $table->getRelationship(); diff --git a/packages/tables/src/Columns/ColumnGroup.php b/packages/tables/src/Columns/ColumnGroup.php index a583cdcc5e..a9876cc896 100644 --- a/packages/tables/src/Columns/ColumnGroup.php +++ b/packages/tables/src/Columns/ColumnGroup.php @@ -78,17 +78,19 @@ class ColumnGroup extends Component } /** - * @return array + * @return array */ public function getColumns(): array { - return array_map(function (Column $column): Column { - return $column->group($this); - }, $this->evaluate($this->columns) ?? []); + return array_reduce($this->evaluate($this->columns) ?? [], function (array $result, Column $column): array { + $result[$column->getName()] = $column->group($this); + + return $result; + }, []); } /** - * @return array + * @return array */ public function getVisibleColumns(): array { diff --git a/packages/tables/src/Columns/Concerns/CanFormatState.php b/packages/tables/src/Columns/Concerns/CanFormatState.php index c1eb759e53..5a76d5bb15 100644 --- a/packages/tables/src/Columns/Concerns/CanFormatState.php +++ b/packages/tables/src/Columns/Concerns/CanFormatState.php @@ -4,15 +4,14 @@ namespace Filament\Tables\Columns\Concerns; use Closure; use Filament\Support\Contracts\HasLabel as LabelInterface; +use Filament\Support\Enums\ArgumentValue; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Support\Carbon; +use Illuminate\Support\Number; use Illuminate\Support\Str; -use function Filament\Support\format_money; -use function Filament\Support\format_number; - trait CanFormatState { protected ?Closure $formatStateUsing = null; @@ -99,28 +98,11 @@ trait CanFormatState return $this; } - public function money(string | Closure | null $currency = null, int $divideBy = 0): static + public function money(string | Closure | null $currency = null, int $divideBy = 0, string | Closure | null $locale = null): static { $this->isMoney = true; - $this->formatStateUsing(static function (TextColumn $column, $state) use ($currency, $divideBy): ?string { - if (blank($state)) { - return null; - } - - $currency = $column->evaluate($currency) ?? Table::$defaultCurrency; - - return format_money($state, $currency, $divideBy); - }); - - return $this; - } - - public function numeric(int | Closure | null $decimalPlaces = null, string | Closure | null $decimalSeparator = '.', string | Closure | null $thousandsSeparator = ','): static - { - $this->isNumeric = true; - - $this->formatStateUsing(static function (TextColumn $column, $state) use ($decimalPlaces, $decimalSeparator, $thousandsSeparator): ?string { + $this->formatStateUsing(static function (TextColumn $column, $state) use ($currency, $divideBy, $locale): ?string { if (blank($state)) { return null; } @@ -129,16 +111,47 @@ trait CanFormatState return $state; } - if ($decimalPlaces === null) { - return format_number($state); + $currency = $column->evaluate($currency) ?? Table::$defaultCurrency; + + if ($divideBy) { + $state /= $divideBy; } - return number_format( - $state, - $column->evaluate($decimalPlaces), - $column->evaluate($decimalSeparator), - $column->evaluate($thousandsSeparator), - ); + return Number::currency($state, $currency, $column->evaluate($locale)); + }); + + return $this; + } + + public function numeric(int | Closure | null $decimalPlaces = null, string | Closure | null | ArgumentValue $decimalSeparator = ArgumentValue::Default, string | Closure | null | ArgumentValue $thousandsSeparator = ArgumentValue::Default, int | Closure | null $maxDecimalPlaces = null, string | Closure | null $locale = null): static + { + $this->isNumeric = true; + + $this->formatStateUsing(static function (TextColumn $column, $state) use ($decimalPlaces, $decimalSeparator, $locale, $maxDecimalPlaces, $thousandsSeparator): ?string { + if (blank($state)) { + return null; + } + + if (! is_numeric($state)) { + return $state; + } + + $decimalSeparator = $column->evaluate($decimalSeparator); + $thousandsSeparator = $column->evaluate($thousandsSeparator); + + if ( + ($decimalSeparator !== ArgumentValue::Default) || + ($thousandsSeparator !== ArgumentValue::Default) + ) { + return number_format( + $state, + $column->evaluate($decimalPlaces), + $decimalSeparator === ArgumentValue::Default ? '.' : $decimalSeparator, + $thousandsSeparator === ArgumentValue::Default ? ',' : $thousandsSeparator, + ); + } + + return Number::format($state, $decimalPlaces, $maxDecimalPlaces, locale: $column->evaluate($locale)); }); return $this; diff --git a/packages/tables/src/Columns/Summarizers/Average.php b/packages/tables/src/Columns/Summarizers/Average.php index 9c27ad43ee..82a3002099 100644 --- a/packages/tables/src/Columns/Summarizers/Average.php +++ b/packages/tables/src/Columns/Summarizers/Average.php @@ -40,6 +40,13 @@ class Average extends Summarizer return $this->selectedState[$this->getSelectAlias()]; } + public function selectAlias(?string $alias): static + { + $this->selectAlias = $alias; + + return $this; + } + public function getSelectAlias(): string { return $this->selectAlias ??= Str::random(); diff --git a/packages/tables/src/Columns/Summarizers/Concerns/CanFormatState.php b/packages/tables/src/Columns/Summarizers/Concerns/CanFormatState.php index 3a1440362f..91934e7bb7 100644 --- a/packages/tables/src/Columns/Summarizers/Concerns/CanFormatState.php +++ b/packages/tables/src/Columns/Summarizers/Concerns/CanFormatState.php @@ -3,14 +3,12 @@ namespace Filament\Tables\Columns\Summarizers\Concerns; use Closure; +use Filament\Support\Enums\ArgumentValue; use Filament\Tables\Columns\Summarizers\Summarizer; use Filament\Tables\Table; -use Illuminate\Support\Arr; +use Illuminate\Support\Number; use Illuminate\Support\Str; -use function Filament\Support\format_money; -use function Filament\Support\format_number; - trait CanFormatState { protected ?Closure $formatStateUsing = null; @@ -26,83 +24,67 @@ trait CanFormatState public function limit(int $length = 100, ?string $end = '...'): static { - $this->formatStateUsing(static function ($state) use ($end, $length) { - $isArrayState = is_array($state); - - $state = array_map(function ($state) use ($end, $length) { - if (blank($state)) { - return null; - } - - return Str::limit(strval($state), $length, $end ?? ''); - }, Arr::wrap($state)); - - if (! $isArrayState) { - return Arr::first($state); + $this->formatStateUsing(static function ($state) use ($end, $length): ?string { + if (blank($state)) { + return null; } - return $state; + return Str::limit(strval($state), $length, $end ?? ''); }); return $this; } - public function money(string | Closure | null $currency = null, int $divideBy = 0): static + public function money(string | Closure | null $currency = null, int $divideBy = 0, string | Closure | null $locale = null): static { - $this->formatStateUsing(static function ($state, Summarizer $summarizer) use ($currency, $divideBy) { - $isArrayState = is_array($state); - - $state = array_map(function ($state) use ($currency, $divideBy, $summarizer) { - if (blank($state)) { - return null; - } - - $currency = $summarizer->evaluate($currency) ?? Table::$defaultCurrency; - - return format_money($state, $currency, $divideBy); - }, Arr::wrap($state)); - - if (! $isArrayState) { - return Arr::first($state); + $this->formatStateUsing(static function ($state, Summarizer $summarizer) use ($currency, $divideBy, $locale): ?string { + if (blank($state)) { + return null; } - return $state; + if (! is_numeric($state)) { + return $state; + } + + $currency = $summarizer->evaluate($currency) ?? Table::$defaultCurrency; + + if ($divideBy) { + $state /= $divideBy; + } + + return Number::currency($state, $currency, $summarizer->evaluate($locale)); }); return $this; } - public function numeric(int | Closure | null $decimalPlaces = null, string | Closure | null $decimalSeparator = '.', string | Closure | null $thousandsSeparator = ','): static + public function numeric(int | Closure | null $decimalPlaces = null, string | Closure | null | ArgumentValue $decimalSeparator = ArgumentValue::Default, string | Closure | null | ArgumentValue $thousandsSeparator = ArgumentValue::Default, int | Closure | null $maxDecimalPlaces = null, string | Closure | null $locale = null): static { - $this->formatStateUsing(static function ($state, Summarizer $summarizer) use ($decimalPlaces, $decimalSeparator, $thousandsSeparator) { - $isArrayState = is_array($state); + $this->formatStateUsing(static function ($state, Summarizer $summarizer) use ($decimalPlaces, $decimalSeparator, $locale, $maxDecimalPlaces, $thousandsSeparator): ?string { + if (blank($state)) { + return null; + } - $state = array_map(function ($state) use ($decimalPlaces, $decimalSeparator, $summarizer, $thousandsSeparator) { - if (blank($state)) { - return null; - } + if (! is_numeric($state)) { + return $state; + } - if (! is_numeric($state)) { - return $state; - } - - if ($decimalPlaces === null) { - return format_number($state); - } + $decimalSeparator = $summarizer->evaluate($decimalSeparator); + $thousandsSeparator = $summarizer->evaluate($thousandsSeparator); + if ( + ($decimalSeparator !== ArgumentValue::Default) || + ($thousandsSeparator !== ArgumentValue::Default) + ) { return number_format( $state, $summarizer->evaluate($decimalPlaces), - $summarizer->evaluate($decimalSeparator), - $summarizer->evaluate($thousandsSeparator), + $decimalSeparator === ArgumentValue::Default ? '.' : $decimalSeparator, + $thousandsSeparator === ArgumentValue::Default ? ',' : $thousandsSeparator, ); - }, Arr::wrap($state)); - - if (! $isArrayState) { - return Arr::first($state); } - return $state; + return Number::format($state, $decimalPlaces, $maxDecimalPlaces, locale: $summarizer->evaluate($locale)); }); return $this; diff --git a/packages/tables/src/Columns/Summarizers/Count.php b/packages/tables/src/Columns/Summarizers/Count.php index 81d6445cca..b210ee6e91 100644 --- a/packages/tables/src/Columns/Summarizers/Count.php +++ b/packages/tables/src/Columns/Summarizers/Count.php @@ -75,6 +75,13 @@ class Count extends Summarizer return $this->selectedState[$this->getSelectAlias()]; } + public function selectAlias(?string $alias): static + { + $this->selectAlias = $alias; + + return $this; + } + public function getSelectAlias(): string { return $this->selectAlias ??= Str::random(); diff --git a/packages/tables/src/Columns/Summarizers/Sum.php b/packages/tables/src/Columns/Summarizers/Sum.php index fb278c79bb..f5069952f7 100644 --- a/packages/tables/src/Columns/Summarizers/Sum.php +++ b/packages/tables/src/Columns/Summarizers/Sum.php @@ -40,6 +40,13 @@ class Sum extends Summarizer return $this->selectedState[$this->getSelectAlias()]; } + public function selectAlias(?string $alias): static + { + $this->selectAlias = $alias; + + return $this; + } + public function getSelectAlias(): string { return $this->selectAlias ??= Str::random(); diff --git a/packages/tables/src/Columns/TextInputColumn.php b/packages/tables/src/Columns/TextInputColumn.php index 2189da45a2..0b0ff9e280 100644 --- a/packages/tables/src/Columns/TextInputColumn.php +++ b/packages/tables/src/Columns/TextInputColumn.php @@ -6,6 +6,7 @@ use Closure; use Filament\Forms\Components\Concerns\HasExtraInputAttributes; use Filament\Forms\Components\Concerns\HasInputMode; use Filament\Forms\Components\Concerns\HasStep; +use Filament\Support\RawJs; use Filament\Tables\Columns\Contracts\Editable; class TextInputColumn extends Column implements Editable @@ -21,6 +22,8 @@ class TextInputColumn extends Column implements Editable */ protected string $view = 'filament-tables::columns.text-input-column'; + protected string | RawJs | Closure | null $mask = null; + protected string | Closure | null $type = null; protected function setUp(): void @@ -41,4 +44,16 @@ class TextInputColumn extends Column implements Editable { return $this->evaluate($this->type) ?? 'text'; } + + public function mask(string | RawJs | Closure | null $mask): static + { + $this->mask = $mask; + + return $this; + } + + public function getMask(): string | RawJs | null + { + return $this->evaluate($this->mask); + } } diff --git a/packages/tables/src/Concerns/CanReorderRecords.php b/packages/tables/src/Concerns/CanReorderRecords.php index f812293886..cc82a769dd 100644 --- a/packages/tables/src/Concerns/CanReorderRecords.php +++ b/packages/tables/src/Concerns/CanReorderRecords.php @@ -20,32 +20,34 @@ trait CanReorderRecords $orderColumn = (string) str($this->getTable()->getReorderColumn())->afterLast('.'); - if ( - (($relationship = $this->getTable()->getRelationship()) instanceof BelongsToMany) && - in_array($orderColumn, $relationship->getPivotColumns()) - ) { - foreach ($order as $index => $recordKey) { - $this->getTableRecord($recordKey)->{$relationship->getPivotAccessor()}->update([ - $orderColumn => $index + 1, - ]); + DB::transaction(function () use ($order, $orderColumn) { + if ( + (($relationship = $this->getTable()->getRelationship()) instanceof BelongsToMany) && + in_array($orderColumn, $relationship->getPivotColumns()) + ) { + foreach ($order as $index => $recordKey) { + $this->getTableRecord($recordKey)->{$relationship->getPivotAccessor()}->update([ + $orderColumn => $index + 1, + ]); + } + + return; } - return; - } + $model = app($this->getTable()->getModel()); + $modelKeyName = $model->getKeyName(); - $model = app($this->getTable()->getModel()); - $modelKeyName = $model->getKeyName(); - - $model - ->newModelQuery() - ->whereIn($modelKeyName, array_values($order)) - ->update([ - $orderColumn => DB::raw( - 'case ' . collect($order) - ->map(fn ($recordKey, int $recordIndex): string => 'when ' . $modelKeyName . ' = ' . DB::getPdo()->quote($recordKey) . ' then ' . ($recordIndex + 1)) - ->implode(' ') . ' end' - ), - ]); + $model + ->newModelQuery() + ->whereIn($modelKeyName, array_values($order)) + ->update([ + $orderColumn => DB::raw( + 'case ' . collect($order) + ->map(fn ($recordKey, int $recordIndex): string => 'when ' . $modelKeyName . ' = ' . DB::getPdo()->quote($recordKey) . ' then ' . ($recordIndex + 1)) + ->implode(' ') . ' end' + ), + ]); + }); } public function toggleTableReordering(): void diff --git a/packages/tables/src/Concerns/HasActions.php b/packages/tables/src/Concerns/HasActions.php index 0662ebc3fd..1d6efed9dc 100644 --- a/packages/tables/src/Concerns/HasActions.php +++ b/packages/tables/src/Concerns/HasActions.php @@ -11,6 +11,7 @@ use Filament\Tables\Actions\ActionGroup; use Illuminate\Database\Eloquent\Model; use Illuminate\Validation\ValidationException; use Livewire\Attributes\Url; +use Throwable; use function Livewire\store; @@ -93,6 +94,8 @@ trait HasActions $originallyMountedActions = $this->mountedTableActions; try { + $action->beginDatabaseTransaction(); + if ($this->mountedTableActionHasForm()) { $action->callBeforeFormValidated(); @@ -108,10 +111,21 @@ trait HasActions ]); $result = $action->callAfter() ?? $result; + + $action->commitDatabaseTransaction(); } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + $action->rollBackDatabaseTransaction() : + $action->commitDatabaseTransaction(); + return null; } catch (Cancel $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + $action->rollBackDatabaseTransaction() : + $action->commitDatabaseTransaction(); } catch (ValidationException $exception) { + $action->rollBackDatabaseTransaction(); + if (! $this->mountedTableActionShouldOpenModal()) { $action->resetArguments(); $action->resetFormData(); @@ -119,6 +133,10 @@ trait HasActions $this->unmountTableAction(); } + throw $exception; + } catch (Throwable $exception) { + $action->rollBackDatabaseTransaction(); + throw $exception; } diff --git a/packages/tables/src/Concerns/HasBulkActions.php b/packages/tables/src/Concerns/HasBulkActions.php index 5317980e9e..3094aacc40 100644 --- a/packages/tables/src/Concerns/HasBulkActions.php +++ b/packages/tables/src/Concerns/HasBulkActions.php @@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Illuminate\Validation\ValidationException; +use Throwable; use function Livewire\store; @@ -64,6 +65,8 @@ trait HasBulkActions $originallyMountedAction = $this->mountedTableBulkAction; try { + $action->beginDatabaseTransaction(); + if ($this->mountedTableBulkActionHasForm()) { $action->callBeforeFormValidated(); @@ -79,10 +82,21 @@ trait HasBulkActions ]); $result = $action->callAfter() ?? $result; + + $action->commitDatabaseTransaction(); } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + $action->rollBackDatabaseTransaction() : + $action->commitDatabaseTransaction(); + return null; } catch (Cancel $exception) { + $exception->shouldRollbackDatabaseTransaction() ? + $action->rollBackDatabaseTransaction() : + $action->commitDatabaseTransaction(); } catch (ValidationException $exception) { + $action->rollBackDatabaseTransaction(); + if (! $this->mountedTableBulkActionShouldOpenModal()) { $action->resetArguments(); $action->resetFormData(); @@ -90,6 +104,10 @@ trait HasBulkActions $this->unmountTableBulkAction(); } + throw $exception; + } catch (Throwable $exception) { + $action->rollBackDatabaseTransaction(); + throw $exception; } diff --git a/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/EqualsOperator.php b/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/EqualsOperator.php index 987d718045..471f823caa 100644 --- a/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/EqualsOperator.php +++ b/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/EqualsOperator.php @@ -6,8 +6,7 @@ use Filament\Forms\Components\Component; use Filament\Forms\Components\TextInput; use Filament\Tables\Filters\QueryBuilder\Constraints\Operators\Operator; use Illuminate\Database\Eloquent\Builder; - -use function Filament\Support\format_number; +use Illuminate\Support\Number; class EqualsOperator extends Operator { @@ -35,7 +34,7 @@ class EqualsOperator extends Operator 'filament-tables::filters/query-builder.operators.number.equals.summary.direct', [ 'attribute' => $this->getAttributeLabel(), - 'number' => format_number($this->getSettings()['number']), + 'number' => Number::format($this->getSettings()['number']), ], ); } diff --git a/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/IsMaxOperator.php b/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/IsMaxOperator.php index 46804cb691..cf8c4ad494 100644 --- a/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/IsMaxOperator.php +++ b/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/IsMaxOperator.php @@ -6,8 +6,7 @@ use Filament\Forms\Components\Component; use Filament\Forms\Components\TextInput; use Filament\Tables\Filters\QueryBuilder\Constraints\Operators\Operator; use Illuminate\Database\Eloquent\Builder; - -use function Filament\Support\format_number; +use Illuminate\Support\Number; class IsMaxOperator extends Operator { @@ -35,7 +34,7 @@ class IsMaxOperator extends Operator 'filament-tables::filters/query-builder.operators.number.is_max.summary.direct', [ 'attribute' => $this->getAttributeLabel(), - 'number' => format_number($this->getSettings()['number']), + 'number' => Number::format($this->getSettings()['number']), ], ); } diff --git a/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/IsMinOperator.php b/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/IsMinOperator.php index ed3a1584da..dffb0cca75 100644 --- a/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/IsMinOperator.php +++ b/packages/tables/src/Filters/QueryBuilder/Constraints/NumberConstraint/Operators/IsMinOperator.php @@ -6,8 +6,7 @@ use Filament\Forms\Components\Component; use Filament\Forms\Components\TextInput; use Filament\Tables\Filters\QueryBuilder\Constraints\Operators\Operator; use Illuminate\Database\Eloquent\Builder; - -use function Filament\Support\format_number; +use Illuminate\Support\Number; class IsMinOperator extends Operator { @@ -35,7 +34,7 @@ class IsMinOperator extends Operator 'filament-tables::filters/query-builder.operators.number.is_min.summary.direct', [ 'attribute' => $this->getAttributeLabel(), - 'number' => format_number($this->getSettings()['number']), + 'number' => Number::format($this->getSettings()['number']), ], ); }