1+ @namespace Masa.Stack.Components
2+ @using System .Linq .Expressions
3+ @using Masa .Blazor .Presets
4+ @using Microsoft .AspNetCore .Components .Web .Virtualization
5+ @typeparam TItem
6+ @typeparam TItemValue
7+ @inject I18n I18n
8+
9+ <MAutocomplete Value =" @Value"
10+ ValueChanged =" @ValueChanged"
11+ ValueExpression =" @ValueExpression"
12+ Items =" @Items"
13+ ItemText =" @ItemText"
14+ ItemValue =" @ItemValue"
15+ ItemDisabled =" @ItemDisabled"
16+ Dense =" @Dense"
17+ Outlined =" @Outlined"
18+ Filled =" @Filled"
19+ Solo =" @Solo"
20+ SoloInverted =" @SoloInverted"
21+ HideDetails =" @HideDetails"
22+ Chips =" @Chips"
23+ SmallChips =" @SmallChips"
24+ Label =" @Label"
25+ Class =" @((" s-expandable-autocomplete " + Class).Trim())"
26+ Style =" @Style"
27+ Multiple >
28+ <AppendOuterContent >
29+ <MButton IconName =" mdi-arrow-expand"
30+ Class =" my-auto"
31+ Small =" @Dense"
32+ OnClickStopPropagation
33+ OnClick =" @OnExpand" />
34+ </AppendOuterContent >
35+ </MAutocomplete >
36+
37+ <PDrawer @bind-Value =" expanded" Title =" @I18n.T(" HasSelected " , Value.Count, _items.Count)" >
38+ <MTextField @bind-Value =" Search" Label =" @I18n.T(" Search " )" Dense
39+ HideDetails =" true" Class =" mb-2" Outlined Clearable ></MTextField >
40+ <MSimpleTable FixedHeader Height =" @(" calc(100vh - 159px) " )" >
41+ <thead >
42+ <tr >
43+ <th class =" text-center" style =" width : 40px " >
44+ <MSimpleCheckbox Indeterminate =" @Indeterminate"
45+ Value =" @IsAllSelected"
46+ ValueChanged =" SelectAll"
47+ Color =" primary" />
48+ </th >
49+ <th class =" text-left" >
50+ @I18n.T( "Label")
51+ </th >
52+ </tr >
53+ </thead >
54+ <tbody >
55+ <Virtualize Items =" @_filteredItems" ItemSize =" 48" >
56+ <ItemContent >
57+ <tr >
58+ <td class =" text-center" >
59+ <MSimpleCheckbox Value =" @Value.Contains(context.Value)"
60+ ValueChanged =" @((value) => Select(context.Value, value))"
61+ Color =" primary" />
62+ </td >
63+ <td >@context.Label </td >
64+ </tr >
65+ </ItemContent >
66+ </Virtualize >
67+ </tbody >
68+ </MSimpleTable >
69+ </PDrawer >
70+
71+ @code {
72+
73+ #region Parameters
74+
75+ [Parameter ] public List <TItemValue ? > Value { get ; set ; } = [];
76+
77+ [Parameter ] public EventCallback <List <TItemValue? >> ValueChanged { get ; set ; }
78+
79+ [Parameter ] public Expression <Func <List <TItemValue? >>> ? ValueExpression { get ; set ; }
80+
81+ [EditorRequired ]
82+ [Parameter ] public IList <TItem > Items { get ; set ; } = [];
83+
84+ [Parameter ]
85+ public Func <TItem , bool >? ItemDisabled { get ; set ; }
86+
87+ [Parameter ]
88+ [EditorRequired ]
89+ public Func <TItem , string >? ItemText { get ; set ; }
90+
91+ [Parameter ]
92+ [EditorRequired ]
93+ public Func <TItem , TItemValue? >? ItemValue { get ; set ; }
94+
95+ [Parameter ] public bool Dense { get ; set ; }
96+
97+ [Parameter ] public bool Outlined { get ; set ; }
98+
99+ [Parameter ] public bool Chips { get ; set ; }
100+
101+ [Parameter ] public bool SmallChips { get ; set ; }
102+
103+ [Parameter ] public string ? Label { get ; set ; }
104+
105+ [Parameter ] public string ? Class { get ; set ; }
106+
107+ [Parameter ] public string ? Style { get ; set ; }
108+
109+ [Parameter ] public bool Filled { get ; set ; }
110+
111+ [Parameter ] public bool Solo { get ; set ; }
112+
113+ [Parameter ] public bool SoloInverted { get ; set ; }
114+
115+ [Parameter ] public StringBoolean ? HideDetails { get ; set ; }
116+
117+ #endregion
118+
119+ private string ? _search ;
120+
121+ private string ? Search
122+ {
123+ get => _search ;
124+ set
125+ {
126+ _search = value ;
127+ UpdateFilterItems ();
128+ }
129+ }
130+
131+ private IList <TItem > _previousItems = [];
132+ private HashSet <InternalItem > _items = [];
133+
134+ protected override void OnParametersSet ()
135+ {
136+ base .OnParametersSet ();
137+
138+ if (! Equals (_previousItems , Items ) || _items .Count == 0 )
139+ {
140+ _previousItems = Items ;
141+ if (ItemValue is null )
142+ {
143+ return ;
144+ }
145+
146+ _items = Items .Select (item => new InternalItem (ItemText ? .Invoke (item ), ItemValue .Invoke (item )))
147+ .ToHashSet ();
148+ UpdateFilterItems ();
149+ }
150+ }
151+
152+ private bool expanded ;
153+
154+ private bool IsAllSelected => _filteredValues .Count > 0 && _filteredValues .All (v => Value .Contains (v ));
155+ private bool Indeterminate => _filteredValues .Any (v => Value .Contains (v )) && ! IsAllSelected ;
156+
157+ private HashSet <InternalItem > _filteredItems = [];
158+ private HashSet <TItemValue ? > _filteredValues = [];
159+
160+ private record InternalItem (string ? Label , TItemValue ? Value );
161+
162+ private void UpdateFilterItems ()
163+ {
164+ _filteredItems = string .IsNullOrWhiteSpace (Search )
165+ ? _items .ToHashSet ()
166+ : _items .Where (item => item .Label ? .Contains (Search , StringComparison .OrdinalIgnoreCase ) == true ).ToHashSet ();
167+
168+ _filteredValues = _filteredItems .Select (item => item .Value ).ToHashSet ();
169+ }
170+
171+ private void OnExpand ()
172+ {
173+ expanded = ! expanded ;
174+ }
175+
176+ private async Task Select (TItemValue ? item , bool value )
177+ {
178+ if (value )
179+ {
180+ if (! Value .Contains (item ))
181+ {
182+ await ValueChanged .InvokeAsync ([.. Value , item ]);
183+ }
184+ }
185+ else
186+ {
187+ List <TItemValue ? > selected = [.. Value ];
188+ selected .Remove (item );
189+ await ValueChanged .InvokeAsync (selected );
190+ }
191+ }
192+
193+ private async Task SelectAll (bool value )
194+ {
195+ if (_filteredValues .Count == 0 )
196+ {
197+ return ;
198+ }
199+
200+ if (value )
201+ {
202+ HashSet < TItemValue ? > selected = [.. Value , .. _filteredValues ];
203+
204+ await ValueChanged .InvokeAsync (selected .ToList ());
205+ }
206+ else
207+ {
208+ List <TItemValue ? > selected = [.. Value ];
209+ foreach (var filteredValue in _filteredValues )
210+ {
211+ selected .Remove (filteredValue );
212+ }
213+
214+ await ValueChanged .InvokeAsync (selected );
215+ }
216+ }
217+
218+ }
0 commit comments