2020use Filament \Support \Enums \IconSize ;
2121use Filament \Support \Exceptions \Cancel ;
2222use Filament \Support \Exceptions \Halt ;
23+ use Filament \Support \Facades \FilamentColor ;
24+ use Filament \Support \View \ComponentAttributeBag as FilamentComponentAttributeBag ;
25+ use Filament \Support \View \Components \DropdownComponent \ItemComponent ;
26+ use Filament \Support \View \Components \DropdownComponent \ItemComponent \IconComponent ;
27+ use Filament \Support \View \Components \LinkComponent ;
2328use Filament \Support \View \Concerns \CanGenerateBadgeHtml ;
2429use Filament \Support \View \Concerns \CanGenerateButtonHtml ;
2530use Filament \Support \View \Concerns \CanGenerateDropdownItemHtml ;
3540use Illuminate \Support \HtmlString ;
3641use Illuminate \Support \Js ;
3742use Illuminate \Support \Str ;
38- use Illuminate \View \ComponentAttributeBag ;
3943use Livewire \Drawer \Utils ;
4044
45+ use function Filament \Support \generate_icon_html ;
46+ use function Filament \Support \generate_loading_indicator_html ;
47+
4148class Action extends ViewComponent implements Arrayable
4249{
4350 use CanGenerateBadgeHtml;
@@ -653,6 +660,8 @@ public function call(array $parameters = []): mixed
653660 if ($ this ->shouldDeselectRecordsAfterCompletion ()) {
654661 $ this ->getLivewire ()->deselectAllTableRecords ();
655662 }
663+
664+ $ this ->clearVisibilityCache ();
656665 }
657666 }
658667
@@ -763,6 +772,14 @@ public function toHtml(): string
763772 return $ this ->toEmbeddedHtml ();
764773 }
765774
775+ if ($ this ->canRenderOptimizedLink ()) {
776+ return $ this ->toOptimizedLinkHtml ();
777+ }
778+
779+ if ($ this ->canRenderOptimizedGrouped ()) {
780+ return $ this ->toOptimizedGroupedHtml ();
781+ }
782+
766783 return match ($ this ->getView ()) {
767784 static ::BADGE_VIEW => $ this ->toBadgeHtml (),
768785 static ::BUTTON_VIEW => $ this ->toButtonHtml (),
@@ -773,14 +790,200 @@ public function toHtml(): string
773790 };
774791 }
775792
793+ protected function canRenderOptimizedLink (): bool
794+ {
795+ return ($ this ->getView () === static ::LINK_VIEW )
796+ && $ this ->hasSmallSizeDefault ()
797+ && ! $ this ->hasBadge ()
798+ && ! $ this ->hasTooltip ()
799+ && ! $ this ->hasIconPosition ()
800+ && ! $ this ->hasIconSize ()
801+ && ! $ this ->hasKeyBindings ()
802+ && ! $ this ->hasLabeledFromBreakpoint ()
803+ && ! $ this ->hasOutlined ()
804+ && ! $ this ->hasLabelHidden ()
805+ && ! $ this ->hasDisabled ()
806+ && ! $ this ->hasShouldOpenUrlInNewTab ()
807+ && ! $ this ->hasShouldPostToUrl ()
808+ && ! $ this ->hasClose ()
809+ && ! $ this ->hasCanAccessSelectedRecords ()
810+ && ! $ this ->hasCustomModalPresence ()
811+ && ! $ this ->hasAuthorization ()
812+ && ! $ this ->hasExtraAttributes ()
813+ && ! $ this ->hasMarkAsRead ()
814+ && ! $ this ->hasMarkAsUnread ()
815+ && ! $ this ->hasAlpineClickHandler ()
816+ && ! $ this ->hasCustomLivewireClickHandler ()
817+ && ! $ this ->hasLivewireTarget ()
818+ && ! $ this ->hasCanSubmitForm ()
819+ && ! $ this ->hasFormId ()
820+ && ! $ this ->hasTableIcon ()
821+ && ! $ this ->hasGroupedIcon ();
822+ }
823+
824+ public function hasMarkAsRead (): bool
825+ {
826+ return $ this ->shouldMarkAsRead !== false ;
827+ }
828+
829+ public function hasMarkAsUnread (): bool
830+ {
831+ return $ this ->shouldMarkAsUnread !== false ;
832+ }
833+
834+ public function hasAlpineClickHandler (): bool
835+ {
836+ return $ this ->alpineClickHandler !== null ;
837+ }
838+
839+ public function hasCustomLivewireClickHandler (): bool
840+ {
841+ return $ this ->isLivewireClickHandlerEnabled !== null ;
842+ }
843+
844+ public function hasLivewireTarget (): bool
845+ {
846+ return $ this ->livewireTarget !== null ;
847+ }
848+
849+ protected function toOptimizedLinkHtml (): string
850+ {
851+ $ color = $ this ->getColor () ?? 'primary ' ;
852+
853+ if (is_array ($ color )) {
854+ $ classString = 'fi-ac-link-action fi-link fi-size-sm fi-color ' ;
855+ $ styleString = ' style=" ' . implode ('; ' , FilamentColor::getComponentCustomStyles (LinkComponent::class, $ color )) . '" ' ;
856+ } else {
857+ $ colorClasses = implode (' ' , FilamentColor::getComponentClasses (LinkComponent::class, $ color ));
858+ $ classString = "fi-ac-link-action fi-link fi-size-sm {$ colorClasses }" ;
859+ $ styleString = '' ;
860+ }
861+
862+ $ url = $ this ->getUrl ();
863+ $ icon = $ this ->getIcon ();
864+ $ label = e ($ this ->getLabel ());
865+
866+ if (filled ($ url )) {
867+ $ iconHtml = $ icon ? generate_icon_html ($ icon , size: IconSize::Small)?->toHtml() : '' ;
868+ $ href = e ($ url );
869+
870+ return "<a href= \"{$ href }\" class= \"{$ classString }\"{$ styleString }> {$ iconHtml }{$ label }</a> " ;
871+ }
872+
873+ $ handler = $ this ->getLivewireClickHandler ();
874+
875+ if (blank ($ handler )) {
876+ return "<span class= \"{$ classString }\"{$ styleString }> {$ label }</span> " ;
877+ }
878+
879+ $ iconHtml = $ icon ? generate_icon_html (
880+ $ icon ,
881+ attributes: (new FilamentComponentAttributeBag ([
882+ 'wire:loading.remove.delay. ' . config ('filament.livewire_loading_delay ' , 'default ' ) => true ,
883+ 'wire:target ' => $ handler ,
884+ ])),
885+ size: IconSize::Small,
886+ )?->toHtml() : '' ;
887+
888+ $ loadingHtml = generate_loading_indicator_html (
889+ (new FilamentComponentAttributeBag ([
890+ 'wire:loading.delay. ' . config ('filament.livewire_loading_delay ' , 'default ' ) => '' ,
891+ 'wire:target ' => $ handler ,
892+ ])),
893+ size: IconSize::Small,
894+ )->toHtml ();
895+
896+ // Match `ComponentAttributeBag::__toString()` attribute escaping (only `"` → `\"`).
897+ $ handler = str_replace ('" ' , '\\" ' , $ handler );
898+
899+ return "<button type= \"button \" wire:loading.attr= \"disabled \" wire:click= \"{$ handler }\" class= \"{$ classString }\"{$ styleString }> {$ iconHtml }{$ loadingHtml }{$ label }</button> " ;
900+ }
901+
902+ protected function canRenderOptimizedGrouped (): bool
903+ {
904+ return ($ this ->getView () === static ::GROUPED_VIEW )
905+ && ! $ this ->hasBadge ()
906+ && ! $ this ->hasTooltip ()
907+ && ! $ this ->hasIconSize ()
908+ && ! $ this ->hasKeyBindings ()
909+ && ! $ this ->hasDisabled ()
910+ && ! $ this ->hasShouldOpenUrlInNewTab ()
911+ && ! $ this ->hasShouldPostToUrl ()
912+ && ! $ this ->hasClose ()
913+ && ! $ this ->hasCanAccessSelectedRecords ()
914+ && ! $ this ->hasCustomModalPresence ()
915+ && ! $ this ->hasAuthorization ()
916+ && ! $ this ->hasExtraAttributes ()
917+ && ! $ this ->hasMarkAsRead ()
918+ && ! $ this ->hasMarkAsUnread ()
919+ && ! $ this ->hasAlpineClickHandler ()
920+ && ! $ this ->hasCustomLivewireClickHandler ()
921+ && ! $ this ->hasLivewireTarget ()
922+ && ! $ this ->hasCanSubmitForm ();
923+ }
924+
925+ protected function toOptimizedGroupedHtml (): string
926+ {
927+ $ color = $ this ->getColor () ?? 'gray ' ;
928+
929+ if (is_array ($ color )) {
930+ $ classString = 'fi-dropdown-list-item fi-ac-grouped-action fi-color ' ;
931+ $ styleString = ' style=" ' . implode ('; ' , FilamentColor::getComponentCustomStyles (ItemComponent::class, $ color )) . '" ' ;
932+ } else {
933+ $ colorClasses = implode (' ' , FilamentColor::getComponentClasses (ItemComponent::class, $ color ));
934+ $ classString = "fi-dropdown-list-item fi-ac-grouped-action {$ colorClasses }" ;
935+ $ styleString = '' ;
936+ }
937+
938+ $ url = $ this ->getUrl ();
939+ $ icon = $ this ->getIcon (default: $ this ->getGroupedIcon ());
940+ $ label = e ($ this ->getLabel ());
941+
942+ if (filled ($ url )) {
943+ $ iconHtml = $ icon ? generate_icon_html (
944+ $ icon ,
945+ attributes: (new FilamentComponentAttributeBag )->color (IconComponent::class, $ color ),
946+ )?->toHtml() : '' ;
947+ $ href = e ($ url );
948+
949+ return "<a href= \"{$ href }\" class= \"{$ classString }\"{$ styleString }> {$ iconHtml }<span class= \"fi-dropdown-list-item-label \"> {$ label }</span></a> " ;
950+ }
951+
952+ $ handler = $ this ->getLivewireClickHandler ();
953+
954+ if (blank ($ handler )) {
955+ return "<button type= \"button \" class= \"{$ classString }\"{$ styleString }><span class= \"fi-dropdown-list-item-label \"> {$ label }</span></button> " ;
956+ }
957+
958+ $ iconHtml = $ icon ? generate_icon_html (
959+ $ icon ,
960+ attributes: (new FilamentComponentAttributeBag ([
961+ 'wire:loading.remove.delay. ' . config ('filament.livewire_loading_delay ' , 'default ' ) => true ,
962+ 'wire:target ' => $ handler ,
963+ ]))->color (IconComponent::class, $ color ),
964+ )?->toHtml() : '' ;
965+
966+ $ loadingHtml = generate_loading_indicator_html (
967+ (new FilamentComponentAttributeBag ([
968+ 'wire:loading.delay. ' . config ('filament.livewire_loading_delay ' , 'default ' ) => '' ,
969+ 'wire:target ' => $ handler ,
970+ ])),
971+ )->toHtml ();
972+
973+ // Match `ComponentAttributeBag::__toString()` attribute escaping (only `"` → `\"`).
974+ $ handlerEscaped = str_replace ('" ' , '\\" ' , $ handler );
975+
976+ return "<button type= \"button \" wire:loading.attr= \"disabled \" wire:click= \"{$ handlerEscaped }\" class= \"{$ classString }\"{$ styleString }> {$ iconHtml }{$ loadingHtml }<span class= \"fi-dropdown-list-item-label \"> {$ label }</span></button> " ;
977+ }
978+
776979 protected function toBadgeHtml (): string
777980 {
778981 $ isDisabled = $ this ->isDisabled ();
779982 $ url = $ this ->getUrl ();
780983 $ shouldPostToUrl = $ this ->shouldPostToUrl ();
781984
782985 return $ this ->generateBadgeHtml (
783- attributes: (new ComponentAttributeBag ([
986+ attributes: (new FilamentComponentAttributeBag ([
784987 'action ' => $ shouldPostToUrl ? $ url : null ,
785988 'method ' => $ shouldPostToUrl ? 'post ' : null ,
786989 'wire:click ' => $ this ->getLivewireClickHandler (),
@@ -814,7 +1017,7 @@ protected function toButtonHtml(): string
8141017 $ shouldPostToUrl = $ this ->shouldPostToUrl ();
8151018
8161019 return $ this ->generateButtonHtml (
817- attributes: (new ComponentAttributeBag ([
1020+ attributes: (new FilamentComponentAttributeBag ([
8181021 'action ' => $ shouldPostToUrl ? $ url : null ,
8191022 'method ' => $ shouldPostToUrl ? 'post ' : null ,
8201023 'wire:click ' => $ this ->getLivewireClickHandler (),
@@ -853,7 +1056,7 @@ protected function toGroupedHtml(): string
8531056 $ shouldPostToUrl = $ this ->shouldPostToUrl ();
8541057
8551058 return $ this ->generateDropdownItemHtml (
856- attributes: (new ComponentAttributeBag ([
1059+ attributes: (new FilamentComponentAttributeBag ([
8571060 'action ' => $ shouldPostToUrl ? $ url : null ,
8581061 'method ' => $ shouldPostToUrl ? 'post ' : null ,
8591062 'wire:click ' => $ this ->getLivewireClickHandler (),
@@ -886,7 +1089,7 @@ protected function toIconButtonHtml(): string
8861089 $ shouldPostToUrl = $ this ->shouldPostToUrl ();
8871090
8881091 return $ this ->generateIconButtonHtml (
889- attributes: (new ComponentAttributeBag ([
1092+ attributes: (new FilamentComponentAttributeBag ([
8901093 'action ' => $ shouldPostToUrl ? $ url : null ,
8911094 'method ' => $ shouldPostToUrl ? 'post ' : null ,
8921095 'wire:click ' => $ this ->getLivewireClickHandler (),
@@ -921,7 +1124,7 @@ protected function toLinkHtml(): string
9211124 $ shouldPostToUrl = $ this ->shouldPostToUrl ();
9221125
9231126 return $ this ->generateLinkHtml (
924- attributes: (new ComponentAttributeBag ([
1127+ attributes: (new FilamentComponentAttributeBag ([
9251128 'action ' => $ shouldPostToUrl ? $ url : null ,
9261129 'method ' => $ shouldPostToUrl ? 'post ' : null ,
9271130 'wire:click ' => $ this ->getLivewireClickHandler (),
0 commit comments