11import Component from ' @glimmer/component' ;
2+ import { cached } from ' @glimmer/tracking' ;
23import { hash } from ' @ember/helper' ;
34
45import { Types } from ' tabster' ;
6+ import { TrackedSet } from ' tracked-built-ins' ;
57// The consumer will need to provide types for tracked-toolbox.
68// Or.. better yet, we PR to trakcked-toolbox to provide them
79// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -19,7 +21,6 @@ const TABSTER_CONFIG = JSON.stringify({
1921 },
2022});
2123
22- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2324export interface ItemSignature <Value = any > {
2425 /**
2526 * The button element will have aria-pressed="true" on it when the button is in the pressed state.
@@ -45,11 +46,9 @@ export interface ItemSignature<Value = any> {
4546 };
4647}
4748
48- // eslint-disable-next-line @typescript-eslint/no-explicit-any
4949export type Item <Value = any > = ComponentLike <ItemSignature <Value >>;
5050
51- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52- export class ToggleGroup <Value = any > extends Component <{
51+ export interface SingleSignature <Value > {
5352 Element: HTMLDivElement ;
5453 Args: {
5554 /**
@@ -64,16 +63,136 @@ export class ToggleGroup<Value = any> extends Component<{
6463 *
6564 * When none of the toggles are selected, undefined will be passed.
6665 */
67- onChange: (value : Value | undefined ) => void ;
66+ onChange? : (value : Value | undefined ) => void ;
6867 };
6968 Blocks: {
7069 default: [
7170 {
71+ /**
72+ * The Toggle Switch
73+ */
7274 Item: Item ;
7375 },
7476 ];
7577 };
76- }> {
78+ }
79+
80+ export interface MultiSignature <Value = any > {
81+ Element: HTMLDivElement ;
82+ Args: {
83+ /**
84+ * Optionally set the initial toggle state
85+ */
86+ value? : Value [] | Set <Value > | Value ;
87+ /**
88+ * Callback for when the toggle-group's state is changed.
89+ *
90+ * Can be used to control the state of the component.
91+ *
92+ *
93+ * When none of the toggles are selected, undefined will be passed.
94+ */
95+ onChange? : (value : Set <Value >) => void ;
96+ };
97+ Blocks: {
98+ default: [
99+ {
100+ /**
101+ * The Toggle Switch
102+ */
103+ Item: Item ;
104+ },
105+ ];
106+ };
107+ }
108+
109+ interface PrivateSingleSignature <Value = any > {
110+ Element: HTMLDivElement ;
111+ Args: {
112+ type? : ' single' ;
113+
114+ /**
115+ * Optionally set the initial toggle state
116+ */
117+ value? : Value ;
118+ /**
119+ * Callback for when the toggle-group's state is changed.
120+ *
121+ * Can be used to control the state of the component.
122+ *
123+ *
124+ * When none of the toggles are selected, undefined will be passed.
125+ */
126+ onChange? : (value : Value | undefined ) => void ;
127+ };
128+ Blocks: {
129+ default: [
130+ {
131+ Item: Item ;
132+ },
133+ ];
134+ };
135+ }
136+
137+ interface PrivateMultiSignature <Value = any > {
138+ Element: HTMLDivElement ;
139+ Args: {
140+ type: ' multi' ;
141+ /**
142+ * Optionally set the initial toggle state
143+ */
144+ value? : Value [] | Set <Value > | Value ;
145+ /**
146+ * Callback for when the toggle-group's state is changed.
147+ *
148+ * Can be used to control the state of the component.
149+ *
150+ *
151+ * When none of the toggles are selected, undefined will be passed.
152+ */
153+ onChange? : (value : Set <Value >) => void ;
154+ };
155+ Blocks: {
156+ default: [
157+ {
158+ Item: Item ;
159+ },
160+ ];
161+ };
162+ }
163+
164+ function isMulti(x : ' single' | ' multi' | undefined ): x is ' multi' {
165+ return x === ' multi' ;
166+ }
167+
168+ export class ToggleGroup <Value = any > extends Component <
169+ PrivateSingleSignature <Value > | PrivateMultiSignature <Value >
170+ > {
171+ // See: https://github.com/typed-ember/glint/issues/715
172+ <template >
173+ {{#if ( isMulti this . args.type) }}
174+ <MultiToggleGroup
175+ @ value ={{this .args.value }}
176+ @ onChange ={{this .args.onChange }}
177+ ...attributes
178+ as | x |
179+ >
180+ {{yield x }}
181+ </MultiToggleGroup >
182+ {{else }}
183+ <SingleToggleGroup
184+ @ value ={{this .args.value }}
185+ @ onChange ={{this .args.onChange }}
186+ ...attributes
187+ as | x |
188+ >
189+ {{yield x }}
190+ </SingleToggleGroup >
191+ {{/if }}
192+ </template >
193+ }
194+
195+ class SingleToggleGroup <Value = any > extends Component <SingleSignature <Value >> {
77196 @localCopy (' args.value' ) activePressed? : Value ;
78197
79198 handleToggle = (value : Value ) => {
@@ -96,3 +215,47 @@ export class ToggleGroup<Value = any> extends Component<{
96215 </div >
97216 </template >
98217}
218+
219+ class MultiToggleGroup <Value = any > extends Component <MultiSignature <Value >> {
220+ /**
221+ * Normalizes @value to a Set
222+ * and makes sure that even if the input Set is reactive,
223+ * we don't mistakenly dirty it.
224+ */
225+ @cached
226+ get activePressed(): TrackedSet <Value > {
227+ let value = this .args .value ;
228+
229+ if (! value ) {
230+ return new TrackedSet ();
231+ }
232+
233+ if (Array .isArray (value )) {
234+ return new TrackedSet (value );
235+ }
236+
237+ if (value instanceof Set ) {
238+ return new TrackedSet (value );
239+ }
240+
241+ return new TrackedSet ([value ]);
242+ }
243+
244+ handleToggle = (value : Value ) => {
245+ if (this .activePressed .has (value )) {
246+ this .activePressed .delete (value );
247+ } else {
248+ this .activePressed .add (value );
249+ }
250+
251+ this .args .onChange ?.(new Set <Value >(this .activePressed .values ()));
252+ };
253+
254+ isPressed = (value : Value ) => this .activePressed .has (value );
255+
256+ <template >
257+ <div data-tabster ={{TABSTER_CONFIG }} ...attributes >
258+ {{yield ( hash Item =( component Toggle onChange =this . handleToggle isPressed =this . isPressed) ) }}
259+ </div >
260+ </template >
261+ }
0 commit comments