|
| 1 | +@php |
| 2 | + $statePath = $getStatePath(); |
| 3 | +@endphp |
1 | 4 | <x-dynamic-component |
2 | 5 | :component="$getFieldWrapperView()" |
3 | | - :id="$getId()" |
4 | | - :label="$getLabel()" |
5 | | - :label-sr-only="$isLabelHidden()" |
6 | | - :helper-text="$getHelperText()" |
7 | | - :hint="$getHint()" |
8 | | - :hint-action="$getHintAction()" |
9 | | - :hint-color="$getHintColor()" |
10 | | - :hint-icon="$getHintIcon()" |
11 | | - :required="$isRequired()" |
12 | | - :state-path="$getStatePath()" |
| 6 | + :field="$field" |
13 | 7 | > |
14 | | - <div {{ $attributes->merge($getExtraAttributes())->class([ |
15 | | - 'filament-forms-tree-component py-2 px-5 bg-white border border-gray-300 rounded-xl shadow-sm', |
16 | | - 'dark:bg-gray-500/10' => config('forms.dark_mode'), |
17 | | - ]) }} |
| 8 | + <div {{ |
| 9 | + $attributes |
| 10 | + ->merge($getExtraAttributes()) |
| 11 | + ->class([ |
| 12 | + 'filament-forms-tree-component py-2 px-5 border rounded-xl shadow-sm', |
| 13 | + 'bg-white dark:bg-gray-500/10 border-gray-300 dark:border-gray-600', |
| 14 | + ]) |
| 15 | + }} |
18 | 16 | wire:ignore |
19 | 17 | x-data="{ |
20 | 18 |
|
21 | | - areAllCheckboxesChecked: false, |
| 19 | + state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }}, |
22 | 20 |
|
23 | | - treeOptions: Array.from($root.querySelectorAll('.filament-forms-tree-component-option-label')), |
| 21 | + checkboxOptions: [], |
24 | 22 |
|
25 | | - collapsedAll: false, |
| 23 | + expanded: [], |
26 | 24 |
|
27 | | - init: function () { |
| 25 | + areAllCheckboxesChecked: false, |
28 | 26 |
|
29 | | - this.checkIfAllCheckboxesAreChecked() |
| 27 | + areAllExpanded: false, |
30 | 28 |
|
31 | | - Livewire.hook('message.processed', () => { |
32 | | - this.checkIfAllCheckboxesAreChecked() |
33 | | - }) |
| 29 | + isNodeExpanded: function (key) { |
| 30 | + if (this.areAllExpanded) { |
| 31 | + return true; |
| 32 | + } |
| 33 | +
|
| 34 | + return Array.from(this.expanded).includes(key); |
| 35 | + }, |
| 36 | +
|
| 37 | + toggleNodeExpand: function (key) { |
| 38 | +
|
| 39 | + const expandedKeys = Array.from(this.expanded); |
| 40 | +
|
| 41 | + // If previous state of 'expand_all' button is true, then add other keys into 'collapsed' |
| 42 | + if (this.areAllExpanded === true) { |
| 43 | + this.expanded = Array.from(this.checkboxOptions.map(checkbox => checkbox?.getAttribute('value'))).filter(value => value != key); |
| 44 | + } |
| 45 | + // Collapse requesting key if already expanded it before |
| 46 | + else if (expandedKeys.includes(key)) { |
| 47 | + this.expanded = expandedKeys.filter(c => c !== key) |
| 48 | + } |
| 49 | + // Expand requesting key |
| 50 | + else { |
| 51 | + this.expanded.push(key); |
| 52 | + } |
| 53 | +
|
| 54 | + // Init state for 'expand_all' button |
| 55 | + this.areAllExpanded = false; |
34 | 56 | }, |
35 | 57 |
|
36 | | - checkIfAllCheckboxesAreChecked: function () { |
37 | | - this.areAllCheckboxesChecked = this.treeOptions.length === this.treeOptions.filter((checkboxLabel) => checkboxLabel.querySelector('input[type=checkbox]:checked')).length |
| 58 | + toggleExpandAll: function (isExpand) { |
| 59 | + this.areAllExpanded = Boolean(isExpand); |
| 60 | + if (this.areAllExpanded) { |
| 61 | + this.expanded = Array.from(this.checkboxOptions.map(checkbox => checkbox?.getAttribute('value'))); |
| 62 | + } else { |
| 63 | + this.initExpandedState(); |
| 64 | + } |
38 | 65 | }, |
39 | 66 |
|
40 | | - toggleAllCheckboxes: function () { |
41 | | - state = ! this.areAllCheckboxesChecked |
| 67 | + treeNodeCheckboxToggleEvent: function (event) { |
| 68 | +
|
| 69 | + const currentCheckbox = event.target; |
| 70 | + if (!currentCheckbox) return; |
| 71 | +
|
| 72 | + const ctn = currentCheckbox.closest('.filament-forms-tree-component-option-node[data-treenode-group]'); |
| 73 | + if (!ctn) return; |
| 74 | +
|
| 75 | + if (currentCheckbox.checked === false && currentCheckbox.indeterminate === false) { |
| 76 | +
|
| 77 | + const currentKey = ctn.getAttribute('data-treenode'); |
| 78 | + if (currentKey) { |
| 79 | + // All its children |
| 80 | + const children = $root.querySelectorAll(`.filament-forms-tree-component-option-node[data-treenode-group='${currentKey}'] input[type=checkbox]`); |
| 81 | + const checkedChildren = Array.from(children).filter(item => item.checked); |
| 82 | + const indeterminateChildren = Array.from(children).filter(item => item.indeterminate); |
| 83 | +
|
| 84 | + // If any children checked/ indeterminate, set currentCheckbox as 'indeterminate' |
| 85 | + if (checkedChildren.length > 0 || indeterminateChildren.length > 0) { |
| 86 | + currentCheckbox.indeterminate = true; |
| 87 | + } |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + const parentKey = ctn.getAttribute('data-treenode-group'); |
| 92 | + if (parentKey) { |
| 93 | + const parentCheckbox = $root.querySelector(`.filament-forms-tree-component-option-node[data-treenode='${parentKey}'] input[type=checkbox]`); |
| 94 | +
|
| 95 | + // Skip set 'Indeterminate' status if parentCheckbox is 'checked' |
| 96 | + if (parentCheckbox && parentCheckbox.checked == false) { |
| 97 | +
|
| 98 | + // All the parent's children. |
| 99 | + const siblingsAndSelf = $root.querySelectorAll(`.filament-forms-tree-component-option-node[data-treenode-group='${parentKey}'] input[type=checkbox]`); |
| 100 | + const checkedSiblingsAndSelf = Array.from(siblingsAndSelf).filter(item => item.checked); |
| 101 | + const indeterminateSiblingsAndSelf = Array.from(siblingsAndSelf).filter(item => item.indeterminate); |
| 102 | +
|
| 103 | + let isDispatchParentUpdateEvent = false; |
| 104 | + // If any siblings checked/indeterminate, set parentCheckbox as 'indeterminate' |
| 105 | + if (checkedSiblingsAndSelf.length > 0 || indeterminateSiblingsAndSelf.length > 0) { |
| 106 | + parentCheckbox.indeterminate = true; |
| 107 | + isDispatchParentUpdateEvent = true; |
| 108 | + } else { |
| 109 | + parentCheckbox.indeterminate = false; |
| 110 | + isDispatchParentUpdateEvent = true; |
| 111 | + } |
| 112 | +
|
| 113 | + if (isDispatchParentUpdateEvent) { |
| 114 | + parentCheckbox.dispatchEvent(new Event('change')) |
| 115 | + } |
| 116 | + } |
| 117 | + } |
| 118 | + }, |
42 | 119 |
|
43 | | - this.treeOptions.forEach((checkboxLabel) => { |
44 | | - checkbox = checkboxLabel.querySelector('input[type=checkbox]') |
| 120 | + toggleSelectAllCheckboxes: function () { |
| 121 | + let state = ! this.areAllCheckboxesChecked |
45 | 122 |
|
| 123 | + this.checkboxOptions.forEach((checkbox) => { |
46 | 124 | checkbox.checked = state |
| 125 | + checkbox.indeterminate = false; |
| 126 | +
|
47 | 127 | checkbox.dispatchEvent(new Event('change')) |
48 | 128 | }) |
49 | 129 |
|
50 | 130 | this.areAllCheckboxesChecked = state |
51 | 131 | }, |
52 | 132 |
|
53 | | - toggleCollapseAll: function () { |
54 | | - this.collapsedAll = ! this.collapsedAll |
| 133 | + initAllCheckboxesAreChecked: function () { |
| 134 | + const allCheckboxes = this.checkboxOptions; |
| 135 | +
|
| 136 | + this.areAllCheckboxesChecked = allCheckboxes.length === allCheckboxes.filter((checkbox) => checkbox.checked == true).length |
| 137 | + }, |
| 138 | +
|
| 139 | + initExpandedState: function () { |
| 140 | + this.expanded = []; |
| 141 | + }, |
| 142 | +
|
| 143 | + initCheckboxOptions: function () { |
| 144 | + this.checkboxOptions = Array.from($root.querySelectorAll('.filament-forms-tree-component-option-label input[type=checkbox]')); |
| 145 | + }, |
| 146 | +
|
| 147 | + init: function () { |
| 148 | +
|
| 149 | + this.$nextTick(() => { |
| 150 | + this.initCheckboxOptions(); |
| 151 | + this.initAllCheckboxesAreChecked(); |
| 152 | + this.initExpandedState(); |
| 153 | + }); |
| 154 | +
|
| 155 | + this.$watch('state', (value) => { |
| 156 | + this.initAllCheckboxesAreChecked(); |
| 157 | + }); |
55 | 158 | } |
56 | 159 | }"> |
57 | 160 |
|
58 | 161 | <div |
59 | 162 | x-cloak |
60 | 163 | class="flex gap-2 mb-2" |
61 | | - wire:key="{{ $this->id }}.{{ $getStatePath() }}.{{ $field::class }}.buttons" |
62 | 164 | > |
63 | 165 | <x-filament::link |
64 | 166 | tag="button" |
65 | 167 | size="sm" |
66 | | - x-show="! areAllCheckboxesChecked" |
67 | | - x-on:click="toggleAllCheckboxes()" |
68 | | - wire:loading.attr="disabled" |
69 | | - wire:key="{{ $this->id }}.{{ $getStatePath() }}.{{ $field::class }}.buttons.select_all" |
70 | | - > |
71 | | - {{ __('filament-tree::filament-tree.components.tree.buttons.select_all.label') }} |
72 | | - </x-filament::link> |
73 | | - |
74 | | - <x-filament::link |
75 | | - tag="button" |
76 | | - size="sm" |
77 | | - x-show="areAllCheckboxesChecked" |
78 | | - x-on:click="toggleAllCheckboxes()" |
79 | | - wire:loading.attr="disabled" |
80 | | - wire:key="{{ $this->id }}.{{ $getStatePath() }}.{{ $field::class }}.buttons.deselect_all" |
| 168 | + x-on:click="toggleSelectAllCheckboxes()" |
81 | 169 | > |
| 170 | + <span x-show="areAllCheckboxesChecked"> |
82 | 171 | {{ __('filament-tree::filament-tree.components.tree.buttons.deselect_all.label') }} |
| 172 | + </span> |
| 173 | + <span x-show="!areAllCheckboxesChecked"> |
| 174 | + {{ __('filament-tree::filament-tree.components.tree.buttons.select_all.label') }} |
| 175 | + </span> |
83 | 176 | </x-filament::link> |
84 | 177 |
|
85 | 178 | <x-filament::icon-button |
86 | 179 | size="sm" |
87 | 180 | icon="heroicon-o-plus" |
88 | 181 | color="secondary" |
89 | 182 | label="{{ __('filament-tree::filament-tree.components.tree.buttons.expand_all.label') }}" |
90 | | - x-on:click="toggleCollapseAll()" |
91 | | - wire:loading.attr="disabled" |
92 | | - wire:key="{{ $this->id }}.{{ $getStatePath() }}.{{ $field::class }}.buttons.expand_all" |
| 183 | + x-on:click="toggleExpandAll(true)" |
| 184 | + |
93 | 185 | /> |
94 | 186 |
|
95 | 187 | <x-filament::icon-button |
96 | 188 | size="sm" |
97 | 189 | icon="heroicon-o-minus" |
98 | 190 | color="secondary" |
99 | 191 | label="{{ __('filament-tree::filament-tree.components.tree.buttons.collapse_all.label') }}" |
100 | | - x-on:click="toggleCollapseAll()" |
101 | | - wire:loading.attr="disabled" |
102 | | - wire:key="{{ $this->id }}.{{ $getStatePath() }}.{{ $field::class }}.buttons.collapse_all" |
| 192 | + x-on:click="toggleExpandAll(false)" |
103 | 193 | /> |
104 | 194 | </div> |
105 | 195 |
|
106 | | - <x-filament::grid |
107 | | - :default="$getColumns('default')" |
108 | | - :sm="$getColumns('sm')" |
109 | | - :md="$getColumns('md')" |
110 | | - :lg="$getColumns('lg')" |
111 | | - :xl="$getColumns('xl')" |
112 | | - :two-xl="$getColumns('2xl')" |
113 | | - class="gap-1" |
114 | | - > |
115 | | - @foreach ($getOptions() as $optionValue => $item) |
116 | | - @include('filament-tree::forms.tree.option-item', ['optionValue' => $optionValue, 'item' => $item]) |
| 196 | + |
| 197 | + <div> |
| 198 | + @foreach($getOptions() as $node) |
| 199 | + <x-filament-tree::forms.tree.group :node="$node"/> |
117 | 200 | @endforeach |
118 | | - </x-filament::grid> |
| 201 | + </div> |
119 | 202 | </div> |
120 | 203 | </x-dynamic-component> |
0 commit comments