Skip to content

Commit 71ed9bd

Browse files
committed
fix form field
1 parent 325fc5f commit 71ed9bd

File tree

5 files changed

+262
-183
lines changed

5 files changed

+262
-183
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
@props([
2+
'groupKey' => null,
3+
'depth' => 0,
4+
'node' => [],
5+
])
6+
7+
@php
8+
$key = $node['id'] ?? null;
9+
$label = $node['label'] ?? $key;
10+
$children = $node['children'] ?? [];
11+
12+
$hasChildren = count($children) > 0;
13+
@endphp
14+
15+
<div role="treeitem">
16+
<x-filament-tree::forms.tree.item
17+
:key="$key"
18+
:depth="$depth"
19+
:label="$label"
20+
:group-key="$groupKey"
21+
:has-children="$hasChildren"
22+
/>
23+
24+
@if (count($children) > 0)
25+
<div class="w-full overflow-hidden"
26+
role="group"
27+
x-show="isNodeExpanded('{{ $key }}')"
28+
>
29+
<div
30+
class="ml-4"
31+
role="group"
32+
>
33+
@foreach ($children as $index => $item)
34+
<x-filament-tree::forms.tree.group
35+
:node="$item"
36+
:depth="$depth + 1"
37+
:group-key="$key"
38+
/>
39+
@endforeach
40+
</div>
41+
</div>
42+
@endif
43+
</div>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
@props([
2+
'key',
3+
'depth',
4+
'label' => null,
5+
'groupKey' => null,
6+
'hasChildren' => false,
7+
])
8+
9+
<div class="filament-forms-tree-component-option-node flex items-center gap-1"
10+
data-treenode="{{ $key }}"
11+
data-treenode-group="{{ $groupKey }}"
12+
data-treenode-depth="{{ $depth }}"
13+
>
14+
@if ($hasChildren)
15+
<x-filament::icon-button
16+
type="button"
17+
color="secondary"
18+
size="xs"
19+
x-on:click="toggleNodeExpand('{{ $key }}')"
20+
>
21+
<x-slot name="icon">
22+
<x-heroicon-o-minus class="w-4 h-4" x-show="isNodeExpanded('{{ $key }}')" />
23+
<x-heroicon-o-plus class="w-4 h-4" x-show="!isNodeExpanded('{{ $key }}')" />
24+
</x-slot>
25+
</x-filament::icon-button>
26+
@endif
27+
28+
<label @class([
29+
'filament-forms-tree-component-option-label',
30+
'ml-4' => !$hasChildren,
31+
])>
32+
<x-filament::input.checkbox
33+
type="checkbox"
34+
x-model="state"
35+
value="{{ $key }}"
36+
class="tree-checkbox"
37+
x-on:change="treeNodeCheckboxToggleEvent"
38+
/>
39+
<span @class([
40+
'filament-forms-tree-component-option-label-text text-sm font-medium ',
41+
])>
42+
{{ $label ?? $key }}
43+
</span>
44+
</label>
45+
</div>
Lines changed: 147 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,203 @@
1+
@php
2+
$statePath = $getStatePath();
3+
@endphp
14
<x-dynamic-component
25
: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"
137
>
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+
}}
1816
wire:ignore
1917
x-data="{
2018
21-
areAllCheckboxesChecked: false,
19+
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
2220
23-
treeOptions: Array.from($root.querySelectorAll('.filament-forms-tree-component-option-label')),
21+
checkboxOptions: [],
2422
25-
collapsedAll: false,
23+
expanded: [],
2624
27-
init: function () {
25+
areAllCheckboxesChecked: false,
2826
29-
this.checkIfAllCheckboxesAreChecked()
27+
areAllExpanded: false,
3028
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;
3456
},
3557
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+
}
3865
},
3966
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+
},
42119
43-
this.treeOptions.forEach((checkboxLabel) => {
44-
checkbox = checkboxLabel.querySelector('input[type=checkbox]')
120+
toggleSelectAllCheckboxes: function () {
121+
let state = ! this.areAllCheckboxesChecked
45122
123+
this.checkboxOptions.forEach((checkbox) => {
46124
checkbox.checked = state
125+
checkbox.indeterminate = false;
126+
47127
checkbox.dispatchEvent(new Event('change'))
48128
})
49129
50130
this.areAllCheckboxesChecked = state
51131
},
52132
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+
});
55158
}
56159
}">
57160

58161
<div
59162
x-cloak
60163
class="flex gap-2 mb-2"
61-
wire:key="{{ $this->id }}.{{ $getStatePath() }}.{{ $field::class }}.buttons"
62164
>
63165
<x-filament::link
64166
tag="button"
65167
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()"
81169
>
170+
<span x-show="areAllCheckboxesChecked">
82171
{{ __('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>
83176
</x-filament::link>
84177

85178
<x-filament::icon-button
86179
size="sm"
87180
icon="heroicon-o-plus"
88181
color="secondary"
89182
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+
93185
/>
94186

95187
<x-filament::icon-button
96188
size="sm"
97189
icon="heroicon-o-minus"
98190
color="secondary"
99191
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)"
103193
/>
104194
</div>
105195

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"/>
117200
@endforeach
118-
</x-filament::grid>
201+
</div>
119202
</div>
120203
</x-dynamic-component>

0 commit comments

Comments
 (0)