From 47d7e172dd8f25696922b09fcb7a594c3ffb134a Mon Sep 17 00:00:00 2001 From: cklei-carly Date: Fri, 12 Sep 2025 10:59:40 +0800 Subject: [PATCH 1/7] Deprecate custom action views for Filament v4 Replaced the contents of several custom action Blade views with deprecation comments, indicating they are no longer needed with Filament v4. This prepares the codebase for compatibility with the new version and signals to developers that these files are obsolete. --- .../views/actions/button-action.blade.php | 13 +- resources/views/actions/group.blade.php | 10 +- .../views/actions/grouped-action.blade.php | 9 +- .../actions/icon-button-action.blade.php | 7 +- resources/views/actions/link-action.blade.php | 10 +- .../modal/actions/button-action.blade.php | 281 +----------------- 6 files changed, 6 insertions(+), 324 deletions(-) diff --git a/resources/views/actions/button-action.blade.php b/resources/views/actions/button-action.blade.php index 890c97d..d345f22 100644 --- a/resources/views/actions/button-action.blade.php +++ b/resources/views/actions/button-action.blade.php @@ -1,12 +1 @@ - - {{ $getLabel() }} - - +{{-- Deprecated with filament v4 --}} \ No newline at end of file diff --git a/resources/views/actions/group.blade.php b/resources/views/actions/group.blade.php index 0f0b2ef..d345f22 100644 --- a/resources/views/actions/group.blade.php +++ b/resources/views/actions/group.blade.php @@ -1,9 +1 @@ - +{{-- Deprecated with filament v4 --}} \ No newline at end of file diff --git a/resources/views/actions/grouped-action.blade.php b/resources/views/actions/grouped-action.blade.php index 6ddf219..d345f22 100644 --- a/resources/views/actions/grouped-action.blade.php +++ b/resources/views/actions/grouped-action.blade.php @@ -1,8 +1 @@ - - {{ $getLabel() }} - +{{-- Deprecated with filament v4 --}} \ No newline at end of file diff --git a/resources/views/actions/icon-button-action.blade.php b/resources/views/actions/icon-button-action.blade.php index ed6802f..d345f22 100644 --- a/resources/views/actions/icon-button-action.blade.php +++ b/resources/views/actions/icon-button-action.blade.php @@ -1,6 +1 @@ - +{{-- Deprecated with filament v4 --}} \ No newline at end of file diff --git a/resources/views/actions/link-action.blade.php b/resources/views/actions/link-action.blade.php index 6c4d4ac..d345f22 100644 --- a/resources/views/actions/link-action.blade.php +++ b/resources/views/actions/link-action.blade.php @@ -1,9 +1 @@ - - {{ $getLabel() }} - +{{-- Deprecated with filament v4 --}} \ No newline at end of file diff --git a/resources/views/actions/modal/actions/button-action.blade.php b/resources/views/actions/modal/actions/button-action.blade.php index 092a3d0..d345f22 100644 --- a/resources/views/actions/modal/actions/button-action.blade.php +++ b/resources/views/actions/modal/actions/button-action.blade.php @@ -1,280 +1 @@ -@if ($this instanceof \Filament\Actions\Contracts\HasActions && (! $this->hasActionsModalRendered)) -
- @php - $action = $this->getMountedAction(); - @endphp - - - @if ($action) - {{ $action->getModalContent() }} - - @if (count(($infolist = $action->getInfolist())?->getComponents() ?? [])) - {{ $infolist }} - @elseif ($this->mountedActionHasForm()) - {{ $this->getMountedActionForm() }} - @endif - - {{ $action->getModalContentFooter() }} - @endif - -
- - @php - $this->hasActionsModalRendered = true; - @endphp -@endif - -@if ($this instanceof \Filament\Infolists\Contracts\HasInfolists && (! $this->hasInfolistsModalRendered)) -
- @php - $action = $this->getMountedInfolistAction(); - @endphp - - - @if ($action) - {{ $action->getModalContent() }} - - @if (count(($infolist = $action->getInfolist())?->getComponents() ?? [])) - {{ $infolist }} - @elseif ($this->mountedInfolistActionHasForm()) - {{ $this->getMountedInfolistActionForm() }} - @endif - - {{ $action->getModalContentFooter() }} - @endif - -
- - @php - $this->hasInfolistsModalRendered = true; - @endphp -@endif - -@if ($this instanceof \Filament\Tables\Contracts\HasTable && (! $this->hasTableModalRendered)) -
- @php - $action = $this->getMountedTableAction(); - @endphp - - - @if ($action) - {{ $action->getModalContent() }} - - @if (count(($infolist = $action->getInfolist())?->getComponents() ?? [])) - {{ $infolist }} - @elseif ($this->mountedTableActionHasForm()) - {{ $this->getMountedTableActionForm() }} - @endif - - {{ $action->getModalContentFooter() }} - @endif - -
- -
- @php - $action = $this->getMountedTableBulkAction(); - @endphp - - - @if ($action) - {{ $action->getModalContent() }} - - @if (count(($infolist = $action->getInfolist())?->getComponents() ?? [])) - {{ $infolist }} - @elseif ($this->mountedTableBulkActionHasForm()) - {{ $this->getMountedTableBulkActionForm() }} - @endif - - {{ $action->getModalContentFooter() }} - @endif - -
- - @php - $this->hasTableModalRendered = true; - @endphp -@endif - -@if (! $this->hasFormsModalRendered) - @php - $action = $this->getMountedFormComponentAction(); - @endphp - -
- - @if ($action) - {{ $action->getModalContent() }} - - @if (count(($infolist = $action->getInfolist())?->getComponents() ?? [])) - {{ $infolist }} - @elseif ($this->mountedFormComponentActionHasForm()) - {{ $this->getMountedFormComponentActionForm() }} - @endif - - {{ $action->getModalContentFooter() }} - @endif - -
- - @php - $this->hasFormsModalRendered = true; - @endphp -@endif \ No newline at end of file +{{-- Deprecated with filament v4 --}} \ No newline at end of file From f38aae5a9cb9419de6c7d99940917a3eee58ff0d Mon Sep 17 00:00:00 2001 From: cklei-carly Date: Fri, 12 Sep 2025 11:35:46 +0800 Subject: [PATCH 2/7] Refactor modal components to use x-dynamic-component Replaces static Filament component tags with in modal-related Blade views. This change improves flexibility and consistency in how modal components are rendered and allows for easier customization and extension. --- resources/css/custom-nestable-item.css | 12 ++++- .../views/components/actions/index.blade.php | 2 +- .../views/components/modal/actions.blade.php | 10 ++-- .../views/components/modal/heading.blade.php | 6 +-- .../views/components/modal/index.blade.php | 47 ++++++++++++------- .../components/modal/subheading.blade.php | 6 +-- 6 files changed, 54 insertions(+), 29 deletions(-) diff --git a/resources/css/custom-nestable-item.css b/resources/css/custom-nestable-item.css index 6b68177..32dd8da 100644 --- a/resources/css/custom-nestable-item.css +++ b/resources/css/custom-nestable-item.css @@ -106,5 +106,15 @@ } .filament-tree-component .nestable-menu { - @apply flex gap-2 mb-4; + @apply mb-4; +} +.filament-tree-component .nestable-menu, +.filament-tree-component .nestable-menu .toolbar-btns { + @apply flex items-center gap-2; +} +.filament-tree-component .nestable-menu { + @apply justify-between; +} +.filament-tree-component .nestable-menu .toolbar-btns.main { + @apply flex-1; } \ No newline at end of file diff --git a/resources/views/components/actions/index.blade.php b/resources/views/components/actions/index.blade.php index d99f54f..8854887 100644 --- a/resources/views/components/actions/index.blade.php +++ b/resources/views/components/actions/index.blade.php @@ -21,4 +21,4 @@ function ($action) use ($record): bool { ); @endphp - \ No newline at end of file + \ No newline at end of file diff --git a/resources/views/components/modal/actions.blade.php b/resources/views/components/modal/actions.blade.php index 9b25347..4c6ab9a 100644 --- a/resources/views/components/modal/actions.blade.php +++ b/resources/views/components/modal/actions.blade.php @@ -1,7 +1,9 @@ - {{ $slot }} - + \ No newline at end of file diff --git a/resources/views/components/modal/heading.blade.php b/resources/views/components/modal/heading.blade.php index 6e2bc87..b184a14 100644 --- a/resources/views/components/modal/heading.blade.php +++ b/resources/views/components/modal/heading.blade.php @@ -1,6 +1,6 @@ - {{ $slot }} - + \ No newline at end of file diff --git a/resources/views/components/modal/index.blade.php b/resources/views/components/modal/index.blade.php index 8bec0df..9030b4b 100644 --- a/resources/views/components/modal/index.blade.php +++ b/resources/views/components/modal/index.blade.php @@ -1,19 +1,32 @@ -@captureSlots([ - 'actions', - 'content', - 'footer', - 'header', - 'heading', - 'subheading', - 'trigger', -]) - - {{ $slot }} - + \ No newline at end of file diff --git a/resources/views/components/modal/subheading.blade.php b/resources/views/components/modal/subheading.blade.php index 50c60ae..bb45a4a 100644 --- a/resources/views/components/modal/subheading.blade.php +++ b/resources/views/components/modal/subheading.blade.php @@ -1,6 +1,6 @@ - {{ $slot }} - + \ No newline at end of file From 540d54b6d8c8a16b1ddc1e94c345dff6ef8cad24 Mon Sep 17 00:00:00 2001 From: cklei-carly Date: Fri, 12 Sep 2025 11:40:37 +0800 Subject: [PATCH 3/7] Add support for tree toolbar actions Introduces a new mechanism for defining and rendering toolbar actions in tree components. Adds a getTreeToolbarActions method to code generators, a toolbarActions property and related methods to the Tree component, and updates the Blade view to display toolbar actions. Refactors tree action classes to use a shared TreeActionTrait for consistency. --- resources/dist/filament-tree.css | 4 +- .../views/components/tree/index.blade.php | 45 +++++--- src/Actions/Action.php | 73 +----------- src/Actions/CreateAction.php | 11 ++ src/Actions/DeleteAction.php | 64 +---------- src/Actions/EditAction.php | 79 +------------ src/Actions/ViewAction.php | 55 +-------- .../Concerns/CanGenerateTreeMethods.php | 20 ++++ .../ResourceTreePageClassGenerator.php | 1 + .../FileGenerators/TreePageClassGenerator.php | 1 + .../TreeWidgetClassGenerator.php | 1 + src/Components/Tree.php | 52 +++++++-- src/Concern/Actions/TreeActionTrait.php | 58 ++++++++++ src/Concern/HasActions.php | 105 ++++++++++++++---- src/Concern/InteractWithTree.php | 35 ++++-- src/Concern/TreePageTrait.php | 26 ++--- src/Widgets/Tree.php | 46 ++++++-- 17 files changed, 338 insertions(+), 338 deletions(-) create mode 100644 src/Actions/CreateAction.php create mode 100644 src/Concern/Actions/TreeActionTrait.php diff --git a/resources/dist/filament-tree.css b/resources/dist/filament-tree.css index 4163f8d..c092440 100644 --- a/resources/dist/filament-tree.css +++ b/resources/dist/filament-tree.css @@ -1,2 +1,2 @@ -/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-gray-50:oklch(98.5% .002 247.839);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-950:oklch(13% .028 261.692);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--font-weight-medium:500;--radius-lg:.5rem;--radius-xl:.75rem;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.collapse{visibility:collapse}.mb-2{margin-bottom:calc(var(--spacing)*2)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.hidden{display:none}.h-4{height:calc(var(--spacing)*4)}.w-4{width:calc(var(--spacing)*4)}.w-full{width:100%}.cursor-wait{cursor:wait}.items-center{align-items:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.overflow-hidden{overflow:hidden}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-gray-300{border-color:var(--color-gray-300)}.bg-white{background-color:var(--color-white)}.px-5{padding-inline:calc(var(--spacing)*5)}.py-2{padding-block:calc(var(--spacing)*2)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.opacity-70{opacity:.7}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (prefers-color-scheme:dark){.dark\:border-gray-600{border-color:var(--color-gray-600)}.dark\:bg-gray-500\/10{background-color:#6a72821a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-gray-500\/10{background-color:color-mix(in oklab,var(--color-gray-500)10%,transparent)}}}}.dd{max-width:600px;margin:0;padding:0;font-size:13px;line-height:20px;list-style:none;display:block;position:relative}.dd-list{margin:0;padding:0;list-style:none;display:block;position:relative}.dd-list .dd-list{padding-left:30px}[dir=rtl] .dd-list .dd-list{padding-left:unset!important;padding-right:30px!important}.dd-empty,.dd-item,.dd-placeholder{min-height:20px;margin:0;padding:0;font-size:13px;line-height:20px;display:block;position:relative}.dd-item>button{cursor:pointer;float:left;white-space:nowrap;text-align:center;background:0 0;border:0;width:25px;height:20px;margin:5px 0;padding:0;font-size:12px;font-weight:700;line-height:1;position:relative;overflow:hidden}[dir=rtl] .dd-item>button{float:right}.dd-item>button:before{text-align:center;text-indent:0;width:100%;display:block;position:absolute}.dd-item>button.dd-expand:before{content:"+"}.dd-item>button.dd-collapse:before{content:"-"}.dd-expand,.dd-collapsed .dd-collapse,.dd-collapsed .dd-list{display:none}.dd-collapsed .dd-expand{display:block}.dd-empty,.dd-placeholder{box-sizing:border-box;background:#f2fbff;border:1px dashed #b6bcbf;min-height:30px;margin:5px 0;padding:0}:is(.dd-empty,.dd-placeholder):is(.dark *){background:#1f2937;border-color:#4b5563}.dd-empty{background-color:#e5e5e5;background-position:0 0,30px 30px;background-size:60px 60px;border:1px dashed #bbb;min-height:100px}.dd-dragel{pointer-events:none;z-index:9999;position:absolute}.dd-dragel>.dd-item .dd-handle{margin-top:0}.dd-dragel .dd-handle{background-color:#fffc;height:40px;padding:4px;box-shadow:2px 4px 6px #0000001a}.dd-dragel .dd-handle:is(.dark *){background-color:#1f2937cc}.dd-nochildren .dd-placeholder{display:none}.dd{max-width:initial}.dd-handle{height:55px}.dd-item>button{height:45px}.btn-group{gap:1px;display:flex}.btn-group button{border-radius:0}[dir=ltr] .btn-group button:first-of-type{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}[dir=ltr] .btn-group button:last-of-type,[dir=rtl] .btn-group button:first-of-type{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}[dir=rtl] .btn-group button:last-of-type{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}.btn-group button:first-of-type{border-radius:.5rem 0 0 .5rem}[dir=rtl] .btn-group button:first-of-type,.btn-group button:last-of-type{border-radius:0 .5rem .5rem 0}[dir=rtl] .btn-group button:last-of-type{border-radius:.5rem 0 0 .5rem}.btn-group button:only-of-type{border-radius:.5rem!important}.filament-tree .dd-item .dd-handle,.filament-tree .dd-item>.loading-indicator,.dd-dragel .dd-handle{margin-bottom:calc(var(--spacing)*2);height:calc(var(--spacing)*10);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300)}:is(.filament-tree .dd-item .dd-handle,.filament-tree .dd-item>.loading-indicator,.dd-dragel .dd-handle):is(.dark *){border-color:var(--color-gray-700)}.filament-tree .dd-placeholder{align-items:center;width:100%;display:flex}.filament-tree .dd-item .dd-handle{background-color:#fff;align-items:center;width:100%;display:flex}.filament-tree .dd-item .dd-handle:is(.dark *){background-color:var(--color-gray-950)}.filament-tree .dd-item .dd-handle>button{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg);border-right-style:var(--tw-border-style);border-right-width:1px;align-items:center;height:100%;padding-inline:1px;display:flex}.filament-tree .dd-item .dd-handle>button:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);border-right-width:0;border-left-style:var(--tw-border-style);border-left-width:1px;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.filament-tree .dd-item .dd-handle>button{background-color:var(--color-gray-50);border-color:var(--color-gray-300)}.filament-tree .dd-item .dd-handle>button:is(.dark *){background-color:var(--color-gray-700);border-color:var(--color-gray-700)}.filament-tree .dd-item .dd-handle>button svg{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4)}.filament-tree .dd-item .dd-handle>button svg:first-child{margin-right:calc(var(--spacing)*-2)}.filament-tree .dd-item .dd-handle>button svg:first-child:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--spacing)*0);margin-left:calc(var(--spacing)*-2)}.filament-tree .dd-item .dd-handle>button svg{color:var(--color-gray-400)}.filament-tree .dd-item .dd-handle>button svg:is(.dark *){color:#fff}.dd-dragel .dd-content{height:100%}.dd-dragel .dd-handle>button,.dd-dragel .fi-tree-actions-ctn{display:none}.filament-tree .dd-item .dd-content,.dd-dragel .dd-content{gap:calc(var(--spacing)*1);display:flex}.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display{margin-left:calc(var(--spacing)*1);align-items:center;gap:calc(var(--spacing)*1);flex:1;display:flex}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--spacing)*1)}.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display{width:100%;max-width:150px}@media (min-width:48rem){.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display{max-width:155px}}@media (min-width:64rem){.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display{max-width:200px}}@media (min-width:80rem){.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display{max-width:350px}}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display) .icon-ctn svg{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4)}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display) .item-content-ctn{min-width:calc(var(--spacing)*0);max-width:var(--container-xs);flex-direction:column;flex:1;display:flex}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display) .item-content-ctn .item-title{text-overflow:ellipsis;white-space:nowrap;--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);overflow:hidden}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display) .item-content-ctn .item-description{text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500);overflow:hidden}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display) .item-content-ctn .item-description:is(.dark *){color:var(--color-gray-400)}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display):not(:has(.icon-ctn)) .item-content-ctn{margin-left:calc(var(--spacing)*4)}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display):not(:has(.icon-ctn)) .item-content-ctn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--spacing)*4)}.filament-tree .dd-item .dd-content .dd-item-btns{flex:1;justify-content:center;align-items:center;display:flex}.filament-tree .dd-item .dd-content .dd-item-btns button svg{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);color:var(--color-gray-400)}.filament-tree .dd-item:not(:has(.dd-list)) .dd-item-btns{display:none}.filament-tree .dd-item .fi-tree-actions-ctn{margin-right:calc(var(--spacing)*4);margin-left:auto}.filament-tree .dd-item .fi-tree-actions-ctn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:auto;margin-left:calc(var(--spacing)*4)}.filament-tree .dd-item .fi-tree-actions-ctn .fi-tree-actions{align-items:center;gap:calc(var(--spacing)*3);flex-shrink:0;display:flex}.filament-tree .dd-item>.loading-indicator{animation:var(--animate-pulse);background-color:var(--color-gray-300);display:none}.filament-tree .dd-item>.loading-indicator:is(.dark *){background-color:var(--color-gray-600)}.filament-tree-component .nestable-menu{margin-bottom:calc(var(--spacing)*4);gap:calc(var(--spacing)*2);display:flex}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@keyframes pulse{50%{opacity:.5}} \ No newline at end of file +/*! tailwindcss v4.1.13 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-gray-50:oklch(98.5% .002 247.839);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-950:oklch(13% .028 261.692);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--font-weight-medium:500;--radius-lg:.5rem;--radius-xl:.75rem;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.collapse{visibility:collapse}.mb-2{margin-bottom:calc(var(--spacing)*2)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-auto{margin-left:auto}.flex{display:flex}.hidden{display:none}.h-4{height:calc(var(--spacing)*4)}.w-4{width:calc(var(--spacing)*4)}.w-full{width:100%}.cursor-wait{cursor:wait}.items-center{align-items:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.overflow-hidden{overflow:hidden}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-gray-300{border-color:var(--color-gray-300)}.bg-white{background-color:var(--color-white)}.px-5{padding-inline:calc(var(--spacing)*5)}.py-2{padding-block:calc(var(--spacing)*2)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.opacity-70{opacity:.7}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (prefers-color-scheme:dark){.dark\:border-gray-600{border-color:var(--color-gray-600)}.dark\:bg-gray-500\/10{background-color:#6a72821a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-gray-500\/10{background-color:color-mix(in oklab,var(--color-gray-500)10%,transparent)}}}}.dd{max-width:600px;margin:0;padding:0;font-size:13px;line-height:20px;list-style:none;display:block;position:relative}.dd-list{margin:0;padding:0;list-style:none;display:block;position:relative}.dd-list .dd-list{padding-left:30px}[dir=rtl] .dd-list .dd-list{padding-left:unset!important;padding-right:30px!important}.dd-empty,.dd-item,.dd-placeholder{min-height:20px;margin:0;padding:0;font-size:13px;line-height:20px;display:block;position:relative}.dd-item>button{cursor:pointer;float:left;white-space:nowrap;text-align:center;background:0 0;border:0;width:25px;height:20px;margin:5px 0;padding:0;font-size:12px;font-weight:700;line-height:1;position:relative;overflow:hidden}[dir=rtl] .dd-item>button{float:right}.dd-item>button:before{text-align:center;text-indent:0;width:100%;display:block;position:absolute}.dd-item>button.dd-expand:before{content:"+"}.dd-item>button.dd-collapse:before{content:"-"}.dd-expand,.dd-collapsed .dd-collapse,.dd-collapsed .dd-list{display:none}.dd-collapsed .dd-expand{display:block}.dd-empty,.dd-placeholder{box-sizing:border-box;background:#f2fbff;border:1px dashed #b6bcbf;min-height:30px;margin:5px 0;padding:0}:is(.dd-empty,.dd-placeholder):is(.dark *){background:#1f2937;border-color:#4b5563}.dd-empty{background-color:#e5e5e5;background-position:0 0,30px 30px;background-size:60px 60px;border:1px dashed #bbb;min-height:100px}.dd-dragel{pointer-events:none;z-index:9999;position:absolute}.dd-dragel>.dd-item .dd-handle{margin-top:0}.dd-dragel .dd-handle{background-color:#fffc;height:40px;padding:4px;box-shadow:2px 4px 6px #0000001a}.dd-dragel .dd-handle:is(.dark *){background-color:#1f2937cc}.dd-nochildren .dd-placeholder{display:none}.dd{max-width:initial}.dd-handle{height:55px}.dd-item>button{height:45px}.btn-group{gap:1px;display:flex}.btn-group button{border-radius:0}[dir=ltr] .btn-group button:first-of-type{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}[dir=ltr] .btn-group button:last-of-type,[dir=rtl] .btn-group button:first-of-type{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}[dir=rtl] .btn-group button:last-of-type{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}.btn-group button:first-of-type{border-radius:.5rem 0 0 .5rem}[dir=rtl] .btn-group button:first-of-type,.btn-group button:last-of-type{border-radius:0 .5rem .5rem 0}[dir=rtl] .btn-group button:last-of-type{border-radius:.5rem 0 0 .5rem}.btn-group button:only-of-type{border-radius:.5rem!important}.filament-tree .dd-item .dd-handle,.filament-tree .dd-item>.loading-indicator,.dd-dragel .dd-handle{margin-bottom:calc(var(--spacing)*2);height:calc(var(--spacing)*10);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300)}:is(.filament-tree .dd-item .dd-handle,.filament-tree .dd-item>.loading-indicator,.dd-dragel .dd-handle):is(.dark *){border-color:var(--color-gray-700)}.filament-tree .dd-placeholder{align-items:center;width:100%;display:flex}.filament-tree .dd-item .dd-handle{background-color:#fff;align-items:center;width:100%;display:flex}.filament-tree .dd-item .dd-handle:is(.dark *){background-color:var(--color-gray-950)}.filament-tree .dd-item .dd-handle>button{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg);border-right-style:var(--tw-border-style);border-right-width:1px;align-items:center;height:100%;padding-inline:1px;display:flex}.filament-tree .dd-item .dd-handle>button:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);border-right-width:0;border-left-style:var(--tw-border-style);border-left-width:1px;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.filament-tree .dd-item .dd-handle>button{background-color:var(--color-gray-50);border-color:var(--color-gray-300)}.filament-tree .dd-item .dd-handle>button:is(.dark *){background-color:var(--color-gray-700);border-color:var(--color-gray-700)}.filament-tree .dd-item .dd-handle>button svg{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4)}.filament-tree .dd-item .dd-handle>button svg:first-child{margin-right:calc(var(--spacing)*-2)}.filament-tree .dd-item .dd-handle>button svg:first-child:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--spacing)*0);margin-left:calc(var(--spacing)*-2)}.filament-tree .dd-item .dd-handle>button svg{color:var(--color-gray-400)}.filament-tree .dd-item .dd-handle>button svg:is(.dark *){color:#fff}.dd-dragel .dd-content{height:100%}.dd-dragel .dd-handle>button,.dd-dragel .fi-tree-actions-ctn{display:none}.filament-tree .dd-item .dd-content,.dd-dragel .dd-content{gap:calc(var(--spacing)*1);display:flex}.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display{margin-left:calc(var(--spacing)*1);align-items:center;gap:calc(var(--spacing)*1);flex:1;display:flex}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--spacing)*1)}.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display{width:100%;max-width:150px}@media (min-width:48rem){.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display{max-width:155px}}@media (min-width:64rem){.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display{max-width:200px}}@media (min-width:80rem){.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display{max-width:350px}}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display) .icon-ctn svg{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4)}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display) .item-content-ctn{min-width:calc(var(--spacing)*0);max-width:var(--container-xs);flex-direction:column;flex:1;display:flex}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display) .item-content-ctn .item-title{text-overflow:ellipsis;white-space:nowrap;--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);overflow:hidden}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display) .item-content-ctn .item-description{text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500);overflow:hidden}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display) .item-content-ctn .item-description:is(.dark *){color:var(--color-gray-400)}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display):not(:has(.icon-ctn)) .item-content-ctn{margin-left:calc(var(--spacing)*4)}:is(.filament-tree .dd-item .dd-content>.tree-item-display,.dd-dragel .tree-item-display):not(:has(.icon-ctn)) .item-content-ctn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--spacing)*4)}.filament-tree .dd-item .dd-content .dd-item-btns{flex:1;justify-content:center;align-items:center;display:flex}.filament-tree .dd-item .dd-content .dd-item-btns button svg{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);color:var(--color-gray-400)}.filament-tree .dd-item:not(:has(.dd-list)) .dd-item-btns{display:none}.filament-tree .dd-item .fi-tree-actions-ctn{margin-right:calc(var(--spacing)*4);margin-left:auto}.filament-tree .dd-item .fi-tree-actions-ctn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:auto;margin-left:calc(var(--spacing)*4)}.filament-tree .dd-item .fi-tree-actions-ctn .fi-tree-actions{align-items:center;gap:calc(var(--spacing)*3);flex-shrink:0;display:flex}.filament-tree .dd-item>.loading-indicator{animation:var(--animate-pulse);background-color:var(--color-gray-300);display:none}.filament-tree .dd-item>.loading-indicator:is(.dark *){background-color:var(--color-gray-600)}.filament-tree-component .nestable-menu{margin-bottom:calc(var(--spacing)*4)}.filament-tree-component .nestable-menu,.filament-tree-component .nestable-menu .toolbar-btns{align-items:center;gap:calc(var(--spacing)*2);display:flex}.filament-tree-component .nestable-menu{justify-content:space-between}.filament-tree-component .nestable-menu .toolbar-btns.main{flex:1}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@keyframes pulse{50%{opacity:.5}} \ No newline at end of file diff --git a/resources/views/components/tree/index.blade.php b/resources/views/components/tree/index.blade.php index f7ef417..638b716 100644 --- a/resources/views/components/tree/index.blade.php +++ b/resources/views/components/tree/index.blade.php @@ -2,7 +2,7 @@ $containerKey = 'filament_tree_container_' . $this->getId(); $maxDepth = $getMaxDepth() ?? 1; $records = collect($this->getRootLayerRecords() ?? []); - + $toolbarActions = $tree->getToolbarActions() ?? []; @endphp
- + + + {{ ($this->displayTreeTitle() ?? false) ? $this->getTreeTitle() : null }} + -
- - {{ __('filament-tree::filament-tree.button.expand_all') }} - - - {{ __('filament-tree::filament-tree.button.collapse_all') }} - +
+
+ + {{ __('filament-tree::filament-tree.button.expand_all') }} + + + {{ __('filament-tree::filament-tree.button.collapse_all') }} + +
+
+ + + + {{ __('filament-tree::filament-tree.button.save') }} + + +
-
- - - - {{ __('filament-tree::filament-tree.button.save') }} - - -
+ @if (is_array($toolbarActions) && count($toolbarActions)) + + @endif
diff --git a/src/Actions/Action.php b/src/Actions/Action.php index 8059d64..733f6bf 100644 --- a/src/Actions/Action.php +++ b/src/Actions/Action.php @@ -3,81 +3,12 @@ namespace SolutionForest\FilamentTree\Actions; use Filament\Actions\Action as BaseAction; -use Illuminate\Database\Eloquent\Model; use SolutionForest\FilamentTree\Concern\Actions\HasTree; +use SolutionForest\FilamentTree\Concern\Actions\TreeActionTrait; use SolutionForest\FilamentTree\Concern\BelongsToTree; class Action extends BaseAction implements HasTree { use BelongsToTree; - - public function getLivewireClickHandler(): ?string - { - if (! $this->isLivewireClickHandlerEnabled()) { - return null; - } - - if (is_string($this->action)) { - return $this->action; - } - - if ($record = $this->getRecord()) { - $recordKey = $this->getLivewire()->getRecordKey($record); - - return "mountTreeAction('{$this->getName()}', '{$recordKey}')"; - } - - return "mountTreeAction('{$this->getName()}')"; - } - - /** - * @return array - */ - protected function resolveDefaultClosureDependencyForEvaluationByName(string $parameterName): array - { - return match ($parameterName) { - 'tree' => [$this->getTree()], - default => parent::resolveDefaultClosureDependencyForEvaluationByName($parameterName), - }; - } - - public function getRecordTitle(?Model $record = null): string - { - $record ??= $this->getRecord(); - - return $this->getCustomRecordTitle($record) ?? $this->getLivewire()->getTreeRecordTitle($record); - } - - public function getRecordTitleAttribute(): ?string - { - return $this->getCustomRecordTitleAttribute() ?? $this->getTree()->getRecordTitleAttribute(); - } - - public function getModelLabel(): string - { - return $this->getCustomModelLabel() ?? $this->getTree()->getModelLabel(); - } - - public function getPluralModelLabel(): string - { - return $this->getCustomPluralModelLabel() ?? $this->getTree()->getPluralModelLabel(); - } - - public function getModel(bool $withDefault = true): ?string - { - return $this->getCustomModel() ?? $this->getLivewire()->getModel(); - } - - public function prepareModalAction(BaseAction $action): BaseAction - { - $action = parent::prepareModalAction($action); - - if (! $action instanceof Action) { - return $action; - } - - return $action - ->tree($this->getTree()) - ->record($this->getRecord()); - } + use TreeActionTrait; } diff --git a/src/Actions/CreateAction.php b/src/Actions/CreateAction.php new file mode 100644 index 0000000..5827b9d --- /dev/null +++ b/src/Actions/CreateAction.php @@ -0,0 +1,11 @@ +label(__('filament-actions::delete.single.label')); - - $this->modalHeading(fn (): string => __('filament-actions::delete.single.modal.heading', ['label' => $this->getRecordTitle()])); - - $this->modalSubmitActionLabel(__('filament-actions::delete.single.modal.actions.delete.label')); - - $this->successNotificationTitle(__('filament-actions::delete.single.notifications.deleted.title')); - - $this->color('danger'); - - $this->icon('heroicon-m-trash'); - - $this->requiresConfirmation(); - - $this->modalSubheading(function (Model $record) { - if (collect($record->children)->isNotEmpty()) { - return __('filament-tree::filament-tree.actions.delete.confirmation.with_children'); - - } else { - return __('filament-actions::modal.confirmation'); - - } - }); - - $this->modalIcon('heroicon-o-trash'); - - $this->hidden(static function (Model $record): bool { - if (! method_exists($record, 'trashed')) { - return false; - } - - return $record->trashed(); - }); - - $this->action(function (): void { - $result = $this->process(static fn (Model $record) => $record->delete()); - - if (! $result) { - $this->failure(); - - return; - } - - $this->success(); - }); - } + use TreeActionTrait; } diff --git a/src/Actions/EditAction.php b/src/Actions/EditAction.php index 9b2c873..c78c3ef 100644 --- a/src/Actions/EditAction.php +++ b/src/Actions/EditAction.php @@ -2,81 +2,10 @@ namespace SolutionForest\FilamentTree\Actions; -use Closure; -use Filament\Actions\Concerns\CanCustomizeProcess; -use Illuminate\Database\Eloquent\Model; -use SolutionForest\FilamentTree\Components\Tree; +use Filament\Actions\EditAction as BaseEditAction; +use SolutionForest\FilamentTree\Concern\Actions\TreeActionTrait; -class EditAction extends Action +class EditAction extends BaseEditAction { - use CanCustomizeProcess; - - protected ?Closure $mutateRecordDataUsing = null; - - protected ?Closure $mutateFormDataBeforeSaveUsing = null; - - public static function getDefaultName(): ?string - { - return 'edit'; - } - - protected function setUp(): void - { - parent::setUp(); - - $this->label(__('filament-actions::edit.single.label')); - - $this->modalHeading(fn (): string => __('filament-actions::edit.single.modal.heading', ['label' => $this->getRecordTitle()])); - - $this->modalSubmitActionLabel(__('filament-actions::edit.single.modal.actions.save.label')); - - $this->successNotificationTitle(__('filament-actions::edit.single.notifications.saved.title')); - - $this->icon('heroicon-m-pencil-square'); - - $this->fillForm(function (Model $record, Tree $tree): array { - if ($translatableContentDriver = $tree->makeFilamentTranslatableContentDriver()) { - $data = $translatableContentDriver->getRecordAttributesToArray($record); - } else { - $data = $record->attributesToArray(); - } - - if ($this->mutateRecordDataUsing) { - $data = $this->evaluate($this->mutateRecordDataUsing, ['data' => $data, 'record' => $record]); - } - - return $data; - }); - - $this->action(function (): void { - $this->process(function (array $data, Model $record, Tree $tree) { - if ($translatableContentDriver = $tree->makeFilamentTranslatableContentDriver()) { - $translatableContentDriver->updateRecord($record, $data); - } else { - $record->update($data); - } - }); - - $this->success(); - }); - } - - public function mutateRecordDataUsing(?Closure $callback): static - { - $this->mutateRecordDataUsing = $callback; - - return $this; - } - - public function mutateFormDataBeforeSaveUsing(?Closure $callback): static - { - $this->mutateFormDataBeforeSaveUsing = $callback; - - return $this; - } - - public function getMutateFormDataBeforeSave(): ?Closure - { - return $this->mutateFormDataBeforeSaveUsing; - } + use TreeActionTrait; } diff --git a/src/Actions/ViewAction.php b/src/Actions/ViewAction.php index 4421c0c..7bda96c 100644 --- a/src/Actions/ViewAction.php +++ b/src/Actions/ViewAction.php @@ -2,57 +2,10 @@ namespace SolutionForest\FilamentTree\Actions; -use Closure; -use Illuminate\Database\Eloquent\Model; -use SolutionForest\FilamentTree\Components\Tree; +use Filament\Actions\ViewAction as BaseViewAction; +use SolutionForest\FilamentTree\Concern\Actions\TreeActionTrait; -class ViewAction extends Action +class ViewAction extends BaseViewAction { - protected ?Closure $mutateRecordDataUsing = null; - - public static function getDefaultName(): ?string - { - return 'view'; - } - - protected function setUp(): void - { - parent::setUp(); - - $this->label(__('filament-actions::view.single.label')); - - $this->modalHeading(fn (): string => __('filament-actions::view.single.modal.heading', ['label' => $this->getRecordTitle()])); - - $this->modalSubmitAction(false); - $this->modalCancelAction(fn (\Filament\Actions\Action $action) => $action->label(__('filament-actions::view.single.modal.actions.close.label'))); - - $this->color('gray'); - - $this->icon('heroicon-m-eye'); - - $this->disabledForm(); - - $this->fillForm(function (Model $record, Tree $tree): array { - if ($translatableContentDriver = $tree->makeFilamentTranslatableContentDriver()) { - $data = $translatableContentDriver->getRecordAttributesToArray($record); - } else { - $data = $record->attributesToArray(); - } - - if ($this->mutateRecordDataUsing) { - $data = $this->evaluate($this->mutateRecordDataUsing, ['data' => $data, 'record' => $record]); - } - - return $data; - }); - - $this->action(static function (): void {}); - } - - public function mutateRecordDataUsing(?Closure $callback): static - { - $this->mutateRecordDataUsing = $callback; - - return $this; - } + use TreeActionTrait; } diff --git a/src/Commands/FileGenerators/Concerns/CanGenerateTreeMethods.php b/src/Commands/FileGenerators/Concerns/CanGenerateTreeMethods.php index 0ba4b49..59025e6 100644 --- a/src/Commands/FileGenerators/Concerns/CanGenerateTreeMethods.php +++ b/src/Commands/FileGenerators/Concerns/CanGenerateTreeMethods.php @@ -25,6 +25,26 @@ protected function addGetTreeActionsMethodToClass(ClassType $class): void protected function configureGetTreeActionsMethod(Method $method): void {} + protected function addGetTreeToolbarActionsMethodToClass(ClassType $class, bool $preset = false): void + { + $body = $preset ? <<addMethod('getTreeToolbarActions') + ->setProtected() + ->setReturnType('array') + ->setBody($body); + + $this->configureGetTreeToolbarActionsMethod($method); + } + + protected function configureGetTreeToolbarActionsMethod(Method $method): void {} + protected function addGetFormSchemaMethodToClass(ClassType $class): void { $method = $class->addMethod('getFormSchema') diff --git a/src/Commands/FileGenerators/ResourceTreePageClassGenerator.php b/src/Commands/FileGenerators/ResourceTreePageClassGenerator.php index f89f319..9dd01d2 100644 --- a/src/Commands/FileGenerators/ResourceTreePageClassGenerator.php +++ b/src/Commands/FileGenerators/ResourceTreePageClassGenerator.php @@ -62,6 +62,7 @@ protected function addPropertiesToClass(ClassType $class): void protected function addMethodsToClass(ClassType $class): void { + $this->addGetTreeToolbarActionsMethodToClass($class); $this->addGetTreeActionsMethodToClass($class); $this->addHasDeleteActionMethodToClass($class); $this->addHasEditActionMethodToClass($class); diff --git a/src/Commands/FileGenerators/TreePageClassGenerator.php b/src/Commands/FileGenerators/TreePageClassGenerator.php index c9492e6..fee4534 100644 --- a/src/Commands/FileGenerators/TreePageClassGenerator.php +++ b/src/Commands/FileGenerators/TreePageClassGenerator.php @@ -69,6 +69,7 @@ protected function addPropertiesToClass(ClassType $class): void protected function addMethodsToClass(ClassType $class): void { + $this->addGetTreeToolbarActionsMethodToClass($class); $this->addGetTreeActionsMethodToClass($class); $this->addGetFormSchemaMethodToClass($class); $this->addHasDeleteActionMethodToClass($class); diff --git a/src/Commands/FileGenerators/TreeWidgetClassGenerator.php b/src/Commands/FileGenerators/TreeWidgetClassGenerator.php index c9491ef..809452a 100644 --- a/src/Commands/FileGenerators/TreeWidgetClassGenerator.php +++ b/src/Commands/FileGenerators/TreeWidgetClassGenerator.php @@ -69,6 +69,7 @@ protected function addMethodsToClass(ClassType $class): void { $this->addGetFormSchemaMethodToClass($class); $this->addGetViewFormSchemaMethodToClass($class); + $this->addGetTreeToolbarActionsMethodToClass($class,true); } public function generate(): string diff --git a/src/Components/Tree.php b/src/Components/Tree.php index 2508931..3b06457 100644 --- a/src/Components/Tree.php +++ b/src/Components/Tree.php @@ -2,9 +2,10 @@ namespace SolutionForest\FilamentTree\Components; -use Filament\Schemas\Schema; +use Filament\Actions\ActionGroup as FilamentActionsActionGroup; use Filament\Support\Components\ViewComponent; use Illuminate\Database\Eloquent\Model; +use SolutionForest\FilamentTree\Actions\ActionGroup; use SolutionForest\FilamentTree\Concern\BelongsToLivewire; use SolutionForest\FilamentTree\Contract\HasTree; use SolutionForest\FilamentTree\Support\Utils; @@ -21,6 +22,8 @@ class Tree extends ViewComponent protected array $actions = []; + protected array $toolbarActions = []; + public const LOADING_TARGETS = ['activeLocale']; public function __construct(HasTree $livewire) @@ -51,6 +54,13 @@ public function actions(array $actions): static return $this; } + public function toolbarActions(array $actions): static + { + $this->toolbarActions = $actions; + + return $this; + } + public function getMaxDepth(): int { return $this->maxDepth; @@ -64,9 +74,40 @@ public function getActions(): array public function getAction($name) { foreach ($this->actions as $action) { - if ($action instanceof \Filament\Actions\ActionGroup) { - return collect($action->getFlatActions())->get($name); + + if ($action instanceof FilamentActionsActionGroup || $action instanceof ActionGroup) { + if ($groupedAction = collect($action->getFlatActions())->get($name)) { + return $groupedAction; + } else { + continue; + } } + + if ($action->getName() === $name) { + return $action; + } + } + + return null; + } + + public function getToolbarActions(): array + { + return $this->toolbarActions; + } + + public function getToolbarAction($name) + { + foreach ($this->toolbarActions as $action) { + + if ($action instanceof FilamentActionsActionGroup || $action instanceof ActionGroup) { + if ($groupedAction = collect($action->getFlatActions())->get($name)) { + return $groupedAction; + } else { + continue; + } + } + if ($action->getName() === $name) { return $action; } @@ -97,9 +138,4 @@ public function getParentKey(?Model $record): ?string return $record->getAttributeValue((method_exists($record, 'determineParentKey') ? $record->determineParentColumnName() : Utils::parentColumnName())); } - - public function getMountedActionForm(): ?Schema - { - return $this->getLivewire()->getMountedTreeActionForm(); - } } diff --git a/src/Concern/Actions/TreeActionTrait.php b/src/Concern/Actions/TreeActionTrait.php new file mode 100644 index 0000000..9b99500 --- /dev/null +++ b/src/Concern/Actions/TreeActionTrait.php @@ -0,0 +1,58 @@ +isLivewireClickHandlerEnabled()) { + return null; + } + + if (is_string($this->action)) { + return $this->action; + } + + $arguments = Js::from($this->getArguments()); + + if ($record = $this->getRecord()) { + $recordKey = $this->getLivewire()->getRecordKey($record); + + return "mountTreeAction('{$this->getName()}', '{$recordKey}', {$arguments})"; + } + + return "mountTreeAction('{$this->getName()}', null, {$arguments})"; + } + + /** + * @return array + */ + protected function resolveDefaultClosureDependencyForEvaluationByName(string $parameterName): array + { + return match ($parameterName) { + 'tree' => [$this->getTree()], + default => parent::resolveDefaultClosureDependencyForEvaluationByName($parameterName), + }; + } + + public function prepareModalAction(BaseAction $action): BaseAction + { + $action = parent::prepareModalAction($action); + + if (! $action instanceof Action) { + return $action; + } + + return $action + ->tree($this->getTree()) + ->record($this->getRecord()); + } +} diff --git a/src/Concern/HasActions.php b/src/Concern/HasActions.php index 58e153c..fb5a5e8 100644 --- a/src/Concern/HasActions.php +++ b/src/Concern/HasActions.php @@ -4,6 +4,7 @@ use Closure; use Filament\Actions\Action as FilamentActionsAction; +use Filament\Actions\ActionGroup as FilamentActionsActionGroup; use Filament\Actions\Exceptions\ActionNotResolvableException; use Filament\Schemas\Schema; use Illuminate\Database\Eloquent\Model; @@ -13,23 +14,41 @@ trait HasActions { + protected array $cachedTreeToolbarActions = []; protected array $cachedTreeActions = []; protected function resolveAction(array $action, array $parentActions): ?FilamentActionsAction { - if ($this instanceof HasTree && filled($action['context']['tree'] ?? null)) { + if ($this instanceof HasTree) { $resolvedAction = null; - $resolvedAction = $this->getCachedTree()?->getAction($action['name']) ?? throw new ActionNotResolvableException("Action [{$action['name']}] not found on tree."); + if ( + filled($action['context']['tree'] ?? null) || + filled($action['arguments']['treeToolbar'] ?? null) + ) { - if (filled($action['context']['recordKey'] ?? null)) { - $record = $this->getTreeRecord($action['context']['recordKey']); + if (! isset($action['name']) || empty($action['name'])) { + throw new ActionNotResolvableException('Action name is not specified.'); + } - $resolvedAction->getRootGroup()?->record($record) ?? $resolvedAction->record($record); - } + if (($action['arguments']['treeToolbar'] ?? false) === true) { + $resolvedAction = $this->cachedTreeToolbarActions[$action['name']] ?? null; + } else { + $resolvedAction = $this->cachedTreeActions[$action['name']] ?? null; + } - return $resolvedAction; + if ($resolvedAction) { + + if (filled($action['context']['recordKey'] ?? null)) { + $record = $this->getTreeRecord($action['context']['recordKey']); + + $resolvedAction->getRootGroup()?->record($record) ?? $resolvedAction->record($record); + } + + return $resolvedAction; + } + } } @@ -39,30 +58,60 @@ protected function resolveAction(array $action, array $parentActions): ?Filament public function cacheTreeActions(): void { $this->cachedTreeActions = []; + $this->cachedTreeToolbarActions = []; - $actions = Action::configureUsing( - Closure::fromCallable([$this, 'configureTreeAction']), - fn (): array => $this->getTreeActions(), - ); + $configuringTreeActions = function ($actions, $extraArgs = []) { - foreach ($actions as $index => $action) { - if ($action instanceof ActionGroup) { - foreach ($action->getActions() as $groupedAction) { - $groupedAction->tree($this->getCachedTree()); - } + $configureResolvedAction = function ($action) use ($extraArgs) { - $this->cachedTreeActions[$index] = $action; + if (! empty($extraArgs) && method_exists($action, 'arguments')) { + $action = $action->arguments($extraArgs); + } - continue; - } + // Set tree on action + if (method_exists($action, 'tree')) { + $action = $action->tree($this->getCachedTree()); + } + // Set livewire on action + else { + $action = $action->livewire($this); + } - $action->tree($this->getCachedTree()); + return $action; + }; + + return collect($actions) + ->whereInstanceOf([ + ActionGroup::class, + Action::class, + FilamentActionsAction::class, + ]) + ->flatMap(function (ActionGroup|Action|FilamentActionsAction $action) { + if ($action instanceof ActionGroup) { + return $action->getFlatActions(); + } + return [$action]; + }) + // Configure action + ->map(fn (Action|FilamentActionsAction $action) => + $action->configureUsing( + Closure::fromCallable([$this, 'configureTreeAction']), + fn () => $action->configure(), + ) + ) + // Key by action name (resolve used) + ->mapWithKeys(fn (Action | FilamentActionsAction $action) => [ + $action->getName() => $configureResolvedAction($action), + ]) + ->all(); + }; + + $this->cachedTreeActions = $configuringTreeActions($this->getCachedTree()->getActions()); + $this->cachedTreeToolbarActions = $configuringTreeActions($this->getCachedTree()->getToolbarActions(), ['treeToolbar' => true]); - $this->cachedTreeActions[$action->getName()] = $action; - } } - protected function configureTreeAction(Action $action): void {} + protected function configureTreeAction(Action|FilamentActionsAction $action): void {} /** * @deprecated Use `callMountedAction()` instead. @@ -98,6 +147,11 @@ public function getCachedTreeActions(): array return $this->cachedTreeActions; } + public function getCachedTreeToolbarActions(): array + { + return $this->cachedTreeToolbarActions; + } + /** * @deprecated Use `getMountedAction()` instead. */ @@ -232,6 +286,11 @@ protected function getTreeActions(): array return []; } + protected function getTreeToolbarActions(): array + { + return []; + } + protected function getTreeActionsPosition(): ?string { return null; diff --git a/src/Concern/InteractWithTree.php b/src/Concern/InteractWithTree.php index 6edf38d..e31163f 100644 --- a/src/Concern/InteractWithTree.php +++ b/src/Concern/InteractWithTree.php @@ -16,28 +16,33 @@ trait InteractWithTree use HasHeading; use HasRecords; - protected bool $hasMounted = false; + // protected bool $hasMounted = false; protected Tree $tree; public function bootInteractWithTree() { - $tree = $this->getTree(); - $this->tree = $tree->configureUsing( + $this->tree = Tree::configureUsing( Closure::fromCallable([static::class, 'tree']), - fn (): Tree => static::tree($tree)->maxDepth(static::getMaxDepth()), + fn (): Tree => $this->makeTree() + ->maxDepth(static::getMaxDepth()) ); - $this->cacheTreeActions(); - $this->cacheTreeEmptyStateActions(); + // Fill actions and toolbar actions if not set in the tree configurator + if (empty($this->tree->getActions())) { + $this->tree->actions($this->getTreeActions()); + } + if (empty($this->tree->getToolbarActions())) { + $this->tree->toolbarActions($this->getTreeToolbarActions()); + } - $this->tree->actions(array_values($this->getCachedTreeActions())); + $this->cacheTreeActions(); - if ($this->hasMounted) { - return; - } + // if ($this->hasMounted) { + // return; + // } - $this->hasMounted = true; + // $this->hasMounted = true; } public function mountInteractsWithTree(): void {} @@ -47,7 +52,15 @@ protected function getCachedTree(): Tree return $this->tree; } + /** + * @deprecated Use makeTree() instead. + */ protected function getTree(): Tree + { + return $this->makeTree(); + } + + protected function makeTree(): Tree { return Tree::make($this); } diff --git a/src/Concern/TreePageTrait.php b/src/Concern/TreePageTrait.php index ae8e364..564b36f 100644 --- a/src/Concern/TreePageTrait.php +++ b/src/Concern/TreePageTrait.php @@ -5,7 +5,7 @@ use Filament\Actions\Action as FilamentActionsAction; use Filament\Actions\CreateAction; use Filament\Schemas\Components\Component; -use SolutionForest\FilamentTree\Actions; +use SolutionForest\FilamentTree\Actions\Action; use SolutionForest\FilamentTree\Actions\DeleteAction; use SolutionForest\FilamentTree\Actions\EditAction; use SolutionForest\FilamentTree\Actions\ViewAction; @@ -96,7 +96,7 @@ protected function configureAction(FilamentActionsAction $action): void }; } - protected function configureTreeAction(Actions\Action $action): void + protected function configureTreeAction(Action|FilamentActionsAction $action): void { match (true) { $action instanceof DeleteAction => $this->configureDeleteAction($action), @@ -127,9 +127,9 @@ protected function configureCreateAction(CreateAction $action): CreateAction protected function configureDeleteAction(DeleteAction $action): DeleteAction { - $action->tree($this->getCachedTree()); + // $action->tree($this->getCachedTree()); - $action->iconButton(); + $action->iconButton()->icon(fn () => $action->getGroupedIcon()); $this->afterConfiguredDeleteAction($action); @@ -138,9 +138,9 @@ protected function configureDeleteAction(DeleteAction $action): DeleteAction protected function configureEditAction(EditAction $action): EditAction { - $action->tree($this->getCachedTree()); + // $action->tree($this->getCachedTree()); - $action->iconButton(); + $action->iconButton()->icon(fn () => $action->getGroupedIcon()); $schema = $this->getEditFormSchema(); @@ -152,7 +152,7 @@ protected function configureEditAction(EditAction $action): EditAction $action->model($this->getModel()); - $action->mutateFormDataBeforeSaveUsing(fn (array $data) => $this->mutateFormDataBeforeSave($data)); + // $action->mutateFormDataBeforeSaveUsing(fn (array $data) => $this->mutateFormDataBeforeSave($data)); $this->afterConfiguredEditAction($action); @@ -161,9 +161,9 @@ protected function configureEditAction(EditAction $action): EditAction protected function configureViewAction(ViewAction $action): ViewAction { - $action->tree($this->getCachedTree()); + // $action->tree($this->getCachedTree()); - $action->iconButton(); + $action->iconButton()->icon(fn () => $action->getGroupedIcon()); $schema = $this->getViewFormSchema(); @@ -242,10 +242,10 @@ protected function getActions(): array ); } - protected function mutateFormDataBeforeSave(array $data): array - { - return $data; - } + // protected function mutateFormDataBeforeSave(array $data): array + // { + // return $data; + // } // protected function callHook(string $hook): void // { diff --git a/src/Widgets/Tree.php b/src/Widgets/Tree.php index a905a53..73ab3a3 100644 --- a/src/Widgets/Tree.php +++ b/src/Widgets/Tree.php @@ -2,9 +2,12 @@ namespace SolutionForest\FilamentTree\Widgets; +use Filament\Actions\Action as FilamentActionsAction; +use Filament\Actions\CreateAction as FilamentActionsCreateAction; use Filament\Support\Contracts\TranslatableContentDriver; use Illuminate\Database\Eloquent\Model; use SolutionForest\FilamentTree\Actions\Action; +use SolutionForest\FilamentTree\Actions\CreateAction; use SolutionForest\FilamentTree\Actions\DeleteAction; use SolutionForest\FilamentTree\Actions\EditAction; use SolutionForest\FilamentTree\Actions\ViewAction; @@ -49,6 +52,11 @@ protected function getFormSchema(): array return []; } + protected function getCreateFormSchema(): array + { + return []; + } + protected function getViewFormSchema(): array { return []; @@ -68,9 +76,11 @@ protected function getTreeActions(): array ); } - protected function configureTreeAction(Action $action): void + protected function configureTreeAction(Action|FilamentActionsAction $action): void { match (true) { + $action instanceof CreateAction, => $this->configureCreateAction($action), + $action instanceof FilamentActionsCreateAction => $this->configureCreateAction($action), $action instanceof DeleteAction => $this->configureDeleteAction($action), $action instanceof EditAction => $this->configureEditAction($action), $action instanceof ViewAction => $this->configureViewAction($action), @@ -110,20 +120,37 @@ protected function getViewAction(): ViewAction protected function configureDeleteAction(DeleteAction $action): DeleteAction { - $action->tree($this->getCachedTree()); + // $action->tree($this->getCachedTree()); - $action->iconButton(); + $action->iconButton()->icon(fn () => $action->getGroupedIcon()); $this->afterConfiguredDeleteAction($action); return $action; } + protected function configureCreateAction(FilamentActionsCreateAction|CreateAction $action): FilamentActionsCreateAction|CreateAction + { + $schema = $this->getCreateFormSchema(); + + if (empty($schema)) { + $schema = $this->getFormSchema(); + } + + $action->schema($schema); + + $action->model($this->getModel()); + + $this->afterConfiguredCreateAction($action); + + return $action; + } + protected function configureEditAction(EditAction $action): EditAction { - $action->tree($this->getCachedTree()); + // $action->tree($this->getCachedTree()); - $action->iconButton(); + $action->iconButton()->icon(fn () => $action->getGroupedIcon()); $schema = $this->getEditFormSchema(); @@ -142,9 +169,9 @@ protected function configureEditAction(EditAction $action): EditAction protected function configureViewAction(ViewAction $action): ViewAction { - $action->tree($this->getCachedTree()); + // $action->tree($this->getCachedTree()); - $action->iconButton(); + $action->iconButton()->icon(fn () => $action->getGroupedIcon()); $schema = $this->getViewFormSchema(); @@ -172,6 +199,11 @@ protected function afterConfiguredDeleteAction(DeleteAction $action): DeleteActi return $action; } + protected function afterConfiguredCreateAction(FilamentActionsCreateAction|CreateAction $action): FilamentActionsCreateAction|CreateAction + { + return $action; + } + protected function afterConfiguredEditAction(EditAction $action): EditAction { return $action; From 53cdc384d759f097cf897c50aa5bcbb31fd5f7f8 Mon Sep 17 00:00:00 2001 From: cklei-carly <68525320+cklei-carly@users.noreply.github.com> Date: Fri, 12 Sep 2025 03:41:00 +0000 Subject: [PATCH 4/7] Fix styling --- .../Concerns/CanGenerateTreeMethods.php | 2 +- .../TreeWidgetClassGenerator.php | 2 +- src/Components/Tree.php | 2 +- src/Concern/HasActions.php | 24 +++++++++---------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Commands/FileGenerators/Concerns/CanGenerateTreeMethods.php b/src/Commands/FileGenerators/Concerns/CanGenerateTreeMethods.php index 59025e6..f8bc1f6 100644 --- a/src/Commands/FileGenerators/Concerns/CanGenerateTreeMethods.php +++ b/src/Commands/FileGenerators/Concerns/CanGenerateTreeMethods.php @@ -31,7 +31,7 @@ protected function addGetTreeToolbarActionsMethodToClass(ClassType $class, bool return [ \SolutionForest\FilamentTree\Actions\CreateAction::make(), ]; - PHP : <<addGetFormSchemaMethodToClass($class); $this->addGetViewFormSchemaMethodToClass($class); - $this->addGetTreeToolbarActionsMethodToClass($class,true); + $this->addGetTreeToolbarActionsMethodToClass($class, true); } public function generate(): string diff --git a/src/Components/Tree.php b/src/Components/Tree.php index 3b06457..1bb9344 100644 --- a/src/Components/Tree.php +++ b/src/Components/Tree.php @@ -99,7 +99,7 @@ public function getToolbarActions(): array public function getToolbarAction($name) { foreach ($this->toolbarActions as $action) { - + if ($action instanceof FilamentActionsActionGroup || $action instanceof ActionGroup) { if ($groupedAction = collect($action->getFlatActions())->get($name)) { return $groupedAction; diff --git a/src/Concern/HasActions.php b/src/Concern/HasActions.php index fb5a5e8..57eacd3 100644 --- a/src/Concern/HasActions.php +++ b/src/Concern/HasActions.php @@ -4,7 +4,6 @@ use Closure; use Filament\Actions\Action as FilamentActionsAction; -use Filament\Actions\ActionGroup as FilamentActionsActionGroup; use Filament\Actions\Exceptions\ActionNotResolvableException; use Filament\Schemas\Schema; use Illuminate\Database\Eloquent\Model; @@ -15,6 +14,7 @@ trait HasActions { protected array $cachedTreeToolbarActions = []; + protected array $cachedTreeActions = []; protected function resolveAction(array $action, array $parentActions): ?FilamentActionsAction @@ -42,10 +42,10 @@ protected function resolveAction(array $action, array $parentActions): ?Filament if (filled($action['context']['recordKey'] ?? null)) { $record = $this->getTreeRecord($action['context']['recordKey']); - + $resolvedAction->getRootGroup()?->record($record) ?? $resolvedAction->record($record); } - + return $resolvedAction; } } @@ -71,14 +71,14 @@ public function cacheTreeActions(): void // Set tree on action if (method_exists($action, 'tree')) { $action = $action->tree($this->getCachedTree()); - } + } // Set livewire on action else { $action = $action->livewire($this); } return $action; - }; + }; return collect($actions) ->whereInstanceOf([ @@ -90,17 +90,17 @@ public function cacheTreeActions(): void if ($action instanceof ActionGroup) { return $action->getFlatActions(); } + return [$action]; - }) + }) // Configure action - ->map(fn (Action|FilamentActionsAction $action) => - $action->configureUsing( - Closure::fromCallable([$this, 'configureTreeAction']), - fn () => $action->configure(), - ) + ->map(fn (Action|FilamentActionsAction $action) => $action->configureUsing( + Closure::fromCallable([$this, 'configureTreeAction']), + fn () => $action->configure(), + ) ) // Key by action name (resolve used) - ->mapWithKeys(fn (Action | FilamentActionsAction $action) => [ + ->mapWithKeys(fn (Action|FilamentActionsAction $action) => [ $action->getName() => $configureResolvedAction($action), ]) ->all(); From 4c14a53724a2dce57e12801742b21c7b696b9410 Mon Sep 17 00:00:00 2001 From: cklei-carly Date: Fri, 12 Sep 2025 11:54:31 +0800 Subject: [PATCH 5/7] Add documentation for tree toolbar actions Updated .github/copilot-instructions.md and README.md to document the new getTreeToolbarActions() method for adding global toolbar actions above the tree. Includes usage examples and notes version support (3.1.0+). --- .github/copilot-instructions.md | 15 +++++++++++++++ README.md | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5f45153..8c1f079 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -119,6 +119,21 @@ public function getTreeRecordIcon(?Model $record = null): ?string } ``` +**Toolbar Actions**: Add global actions displayed above the tree: + +```php +protected function getTreeToolbarActions(): array +{ + return [ + CreateAction::make(), + ExportAction::make(), + ImportAction::make(), + ]; +} +``` + +> **Note**: Toolbar actions are only supported in version 3.1.0 and later. + ## Key File Locations - Models: Add `ModelTree` trait to any hierarchical model diff --git a/README.md b/README.md index f14ea25..53b0e67 100644 --- a/README.md +++ b/README.md @@ -544,6 +544,32 @@ class ProductCategoryWidget extends BaseWidget } ``` +#### Toolbar Actions + +You can add global actions that appear in the toolbar above the tree by implementing the `getTreeToolbarActions()` method. These actions are displayed alongside the default expand/collapse and save buttons: + +```php +protected function getTreeToolbarActions(): array +{ + return [ + \SolutionForest\FilamentTree\Actions\CreateAction::make() + ->label('Add Category') + ->icon('heroicon-o-plus') + ->color('primary'), + \Filament\Actions\ExportAction::make() + ->label('Export Tree') + ->icon('heroicon-o-document-arrow-down'), + \Filament\Actions\ImportAction::make() + ->label('Import Tree') + ->icon('heroicon-o-document-arrow-up'), + ]; +} +``` + +The toolbar actions are automatically generated by the Artisan command and include a `CreateAction` by default for tree widgets, but return an empty array for tree pages. You can customize this method to add any actions you need. + +> **Note**: Toolbar actions are only supported in version 3.1.0 and later. + ### Pages This plugin enables you to create tree pages in the admin panel. To create a tree page for a model, use the `make:filament-tree-page` command. For example, to create a tree page for the ProductCategory model, you can run: From b60b6580335187e5a144c8a2f90c3358ea39fa84 Mon Sep 17 00:00:00 2001 From: cklei-carly <68525320+cklei-carly@users.noreply.github.com> Date: Fri, 12 Sep 2025 11:57:05 +0800 Subject: [PATCH 6/7] Add image for tree toolbar actions in README Added an image to the README for toolbar actions. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 53b0e67..6069720 100644 --- a/README.md +++ b/README.md @@ -566,6 +566,8 @@ protected function getTreeToolbarActions(): array } ``` +![Tree toolbar actions](https://github.com/user-attachments/assets/6d2e29c0-caa3-432c-8a7f-00ae7d7265ba) + The toolbar actions are automatically generated by the Artisan command and include a `CreateAction` by default for tree widgets, but return an empty array for tree pages. You can customize this method to add any actions you need. > **Note**: Toolbar actions are only supported in version 3.1.0 and later. From 707db9b6211978c62695998b8fff066055a6dde2 Mon Sep 17 00:00:00 2001 From: cklei-carly <68525320+cklei-carly@users.noreply.github.com> Date: Fri, 12 Sep 2025 11:57:15 +0800 Subject: [PATCH 7/7] Update version update policy to 3.x only --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6069720..58c83bf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ > [!IMPORTANT] -> Please note that we will only be updating to version 2.x or 3.x, excluding any bug fixes. +> Please note that we will only be updating to version 3.x, excluding any bug fixes. # Filament Tree