Skip to content

Commit ddc307e

Browse files
authoredAug 7, 2024··
feat(material/button-toggle): allow disabled buttons to be interactive (#29550)
Adds the `disabledInteractive` input to the button toggle that allows users to opt into supporting focus on disabled button toggles.
1 parent 43c8713 commit ddc307e

File tree

9 files changed

+134
-21
lines changed

9 files changed

+134
-21
lines changed
 

‎src/dev-app/button-toggle/button-toggle-demo.html

+39-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
<mat-checkbox (change)="isDisabled = $event.checked">Disable Button Toggle Items</mat-checkbox>
77
</p>
88

9+
<p>
10+
<mat-checkbox (change)="disabledInteractive = $event.checked">Allow Interaction with Disabled Button Toggles</mat-checkbox>
11+
</p>
12+
913
<p>
1014
<mat-checkbox (change)="hideSingleSelectionIndicator = $event.checked">Hide Single Selection Indicator</mat-checkbox>
1115
</p>
@@ -17,7 +21,11 @@
1721
<h1>Exclusive Selection</h1>
1822

1923
<section>
20-
<mat-button-toggle-group name="alignment" [vertical]="isVertical" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
24+
<mat-button-toggle-group
25+
name="alignment"
26+
[vertical]="isVertical"
27+
[hideSingleSelectionIndicator]="hideSingleSelectionIndicator"
28+
[disabledInteractive]="disabledInteractive">
2129
<mat-button-toggle value="left" [disabled]="isDisabled">
2230
<mat-icon>format_align_left</mat-icon>
2331
</mat-button-toggle>
@@ -34,7 +42,12 @@ <h1>Exclusive Selection</h1>
3442
</section>
3543

3644
<section>
37-
<mat-button-toggle-group appearance="legacy" name="alignment" [vertical]="isVertical" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
45+
<mat-button-toggle-group
46+
appearance="legacy"
47+
name="alignment"
48+
[vertical]="isVertical"
49+
[hideSingleSelectionIndicator]="hideSingleSelectionIndicator"
50+
[disabledInteractive]="disabledInteractive">
3851
<mat-button-toggle value="left" [disabled]="isDisabled">
3952
<mat-icon>format_align_left</mat-icon>
4053
</mat-button-toggle>
@@ -53,30 +66,44 @@ <h1>Exclusive Selection</h1>
5366
<h1>Disabled Group</h1>
5467

5568
<section>
56-
<mat-button-toggle-group name="checkbox" [vertical]="isVertical" [disabled]="isDisabled" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
69+
<mat-button-toggle-group
70+
name="checkbox"
71+
[vertical]="isVertical"
72+
[disabled]="isDisabled"
73+
[hideSingleSelectionIndicator]="hideSingleSelectionIndicator"
74+
[disabledInteractive]="disabledInteractive">
5775
<mat-button-toggle value="bold">
5876
<mat-icon>format_bold</mat-icon>
5977
</mat-button-toggle>
6078
<mat-button-toggle value="italic">
6179
<mat-icon>format_italic</mat-icon>
6280
</mat-button-toggle>
6381
<mat-button-toggle value="underline">
64-
<mat-icon>format_underline</mat-icon>
82+
<mat-icon>format_underlined</mat-icon>
6583
</mat-button-toggle>
6684
</mat-button-toggle-group>
6785
</section>
6886

6987
<h1>Multiple Selection</h1>
7088
<section>
71-
<mat-button-toggle-group multiple [vertical]="isVertical" [hideMultipleSelectionIndicator]="hideMultipleSelectionIndicator">
89+
<mat-button-toggle-group
90+
multiple
91+
[vertical]="isVertical"
92+
[hideMultipleSelectionIndicator]="hideMultipleSelectionIndicator"
93+
[disabledInteractive]="disabledInteractive">
7294
<mat-button-toggle>Flour</mat-button-toggle>
7395
<mat-button-toggle>Eggs</mat-button-toggle>
7496
<mat-button-toggle>Sugar</mat-button-toggle>
7597
<mat-button-toggle [disabled]="isDisabled">Milk</mat-button-toggle>
7698
</mat-button-toggle-group>
7799
</section>
78100
<section>
79-
<mat-button-toggle-group appearance="legacy" multiple [vertical]="isVertical" [hideMultipleSelectionIndicator]="hideMultipleSelectionIndicator">
101+
<mat-button-toggle-group
102+
appearance="legacy"
103+
multiple
104+
[vertical]="isVertical"
105+
[hideMultipleSelectionIndicator]="hideMultipleSelectionIndicator"
106+
[disabledInteractive]="disabledInteractive">
80107
<mat-button-toggle>Flour</mat-button-toggle>
81108
<mat-button-toggle>Eggs</mat-button-toggle>
82109
<mat-button-toggle>Sugar</mat-button-toggle>
@@ -90,7 +117,12 @@ <h1>Single Toggle</h1>
90117

91118
<h1>Dynamic Exclusive Selection</h1>
92119
<section>
93-
<mat-button-toggle-group name="pies" [(ngModel)]="favoritePie" [vertical]="isVertical" [hideSingleSelectionIndicator]="hideSingleSelectionIndicator">
120+
<mat-button-toggle-group
121+
name="pies"
122+
[(ngModel)]="favoritePie"
123+
[vertical]="isVertical"
124+
[hideSingleSelectionIndicator]="hideSingleSelectionIndicator"
125+
[disabledInteractive]="disabledInteractive">
94126
@for (pie of pieOptions; track pie) {
95127
<mat-button-toggle [value]="pie">{{pie}}</mat-button-toggle>
96128
}

‎src/dev-app/button-toggle/button-toggle-demo.ts

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {MatIconModule} from '@angular/material/icon';
2424
export class ButtonToggleDemo {
2525
isVertical = false;
2626
isDisabled = false;
27+
disabledInteractive = false;
2728
hideSingleSelectionIndicator = false;
2829
hideMultipleSelectionIndicator = false;
2930
favoritePie = 'Apple';

‎src/material/button-toggle/button-toggle.html

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
type="button"
33
[id]="buttonId"
44
[attr.role]="isSingleSelector() ? 'radio' : 'button'"
5-
[attr.tabindex]="disabled ? -1 : tabIndex"
5+
[attr.tabindex]="disabled && !disabledInteractive ? -1 : tabIndex"
66
[attr.aria-pressed]="!isSingleSelector() ? checked : null"
77
[attr.aria-checked]="isSingleSelector() ? checked : null"
8-
[disabled]="disabled || null"
8+
[disabled]="(disabled && !disabledInteractive) || null"
99
[attr.name]="_getButtonName()"
1010
[attr.aria-label]="ariaLabel"
1111
[attr.aria-labelledby]="ariaLabelledby"
12+
[attr.aria-disabled]="disabled && disabledInteractive ? 'true' : null"
1213
(click)="_onButtonClick()">
1314
<span class="mat-button-toggle-label-content">
1415
<!-- Render checkmark at the beginning for single-selection. -->

‎src/material/button-toggle/button-toggle.scss

+10-5
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ $_standard-tokens: (
121121
}
122122

123123
.mat-button-toggle-disabled {
124+
pointer-events: none;
125+
124126
@include token-utils.use-tokens($_legacy-tokens...) {
125127
@include token-utils.create-token-slot(color, disabled-state-text-color);
126128
@include token-utils.create-token-slot(background-color, disabled-state-background-color);
@@ -134,6 +136,10 @@ $_standard-tokens: (
134136
}
135137
}
136138

139+
.mat-button-toggle-disabled-interactive {
140+
pointer-events: auto;
141+
}
142+
137143
.mat-button-toggle-appearance-standard {
138144
@include token-utils.use-tokens($_standard-tokens...) {
139145
$divider-color: token-utils.get-token-variable(divider-color);
@@ -185,16 +191,15 @@ $_standard-tokens: (
185191
@include token-utils.create-token-slot(background-color, state-layer-color);
186192
}
187193

188-
&:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay {
194+
&:hover .mat-button-toggle-focus-overlay {
189195
@include token-utils.create-token-slot(opacity, hover-state-layer-opacity);
190196
}
191197

192198
// Similar to components like the checkbox, slide-toggle and radio, we cannot show the focus
193199
// overlay for `.cdk-program-focused` because mouse clicks on the <label> element would be
194-
// always treated as programmatic focus. Note that it needs the extra `:not` in order to have
195-
// more specificity than the `:hover` above.
200+
// always treated as programmatic focus.
196201
// TODO(paul): support `program` as well. See https://github.com/angular/components/issues/9889
197-
&.cdk-keyboard-focused:not(.mat-button-toggle-disabled) .mat-button-toggle-focus-overlay {
202+
&.cdk-keyboard-focused .mat-button-toggle-focus-overlay {
198203
@include token-utils.create-token-slot(opacity, focus-state-layer-opacity);
199204
}
200205
}
@@ -204,7 +209,7 @@ $_standard-tokens: (
204209
// because we still want to preserve the keyboard focus state for hybrid devices that have
205210
// a keyboard and a touchscreen.
206211
@media (hover: none) {
207-
&:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay {
212+
&:hover .mat-button-toggle-focus-overlay {
208213
display: none;
209214
}
210215
}

‎src/material/button-toggle/button-toggle.spec.ts

+25-3
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,25 @@ describe('MatButtonToggle without forms', () => {
434434
expect(buttons.every(input => input.disabled)).toBe(true);
435435
});
436436

437+
it('should be able to keep the button interactive while disabled', () => {
438+
const button = buttonToggleNativeElements[0].querySelector('button')!;
439+
testComponent.isGroupDisabled = true;
440+
fixture.changeDetectorRef.markForCheck();
441+
fixture.detectChanges();
442+
443+
expect(button.hasAttribute('disabled')).toBe(true);
444+
expect(button.hasAttribute('aria-disabled')).toBe(false);
445+
expect(button.getAttribute('tabindex')).toBe('-1');
446+
447+
testComponent.disabledIntearctive = true;
448+
fixture.changeDetectorRef.markForCheck();
449+
fixture.detectChanges();
450+
451+
expect(button.hasAttribute('disabled')).toBe(false);
452+
expect(button.getAttribute('aria-disabled')).toBe('true');
453+
expect(button.getAttribute('tabindex')).toBe('0');
454+
});
455+
437456
it('should update the group value when one of the toggles changes', () => {
438457
expect(groupInstance.value).toBeFalsy();
439458
buttonToggleLabelElements[0].click();
@@ -1052,9 +1071,11 @@ describe('MatButtonToggle without forms', () => {
10521071

10531072
@Component({
10541073
template: `
1055-
<mat-button-toggle-group [disabled]="isGroupDisabled"
1056-
[vertical]="isVertical"
1057-
[(value)]="groupValue">
1074+
<mat-button-toggle-group
1075+
[disabled]="isGroupDisabled"
1076+
[disabledInteractive]="disabledIntearctive"
1077+
[vertical]="isVertical"
1078+
[(value)]="groupValue">
10581079
@if (renderFirstToggle) {
10591080
<mat-button-toggle value="test1">Test1</mat-button-toggle>
10601081
}
@@ -1067,6 +1088,7 @@ describe('MatButtonToggle without forms', () => {
10671088
})
10681089
class ButtonTogglesInsideButtonToggleGroup {
10691090
isGroupDisabled: boolean = false;
1091+
disabledIntearctive = false;
10701092
isVertical: boolean = false;
10711093
groupValue: string;
10721094
renderFirstToggle = true;

‎src/material/button-toggle/button-toggle.ts

+33
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export interface MatButtonToggleDefaultOptions {
6060
hideSingleSelectionIndicator?: boolean;
6161
/** Whether icon indicators should be hidden for multiple-selection button toggle groups. */
6262
hideMultipleSelectionIndicator?: boolean;
63+
/** Whether disabled toggle buttons should be interactive. */
64+
disabledInteractive?: boolean;
6365
}
6466

6567
/**
@@ -78,6 +80,7 @@ export function MAT_BUTTON_TOGGLE_GROUP_DEFAULT_OPTIONS_FACTORY(): MatButtonTogg
7880
return {
7981
hideSingleSelectionIndicator: false,
8082
hideMultipleSelectionIndicator: false,
83+
disabledInteractive: false,
8184
};
8285
}
8386

@@ -136,6 +139,7 @@ export class MatButtonToggleChange {
136139
export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, AfterContentInit {
137140
private _multiple = false;
138141
private _disabled = false;
142+
private _disabledInteractive = false;
139143
private _selectionModel: SelectionModel<MatButtonToggle>;
140144

141145
/**
@@ -229,6 +233,16 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
229233
this._markButtonsForCheck();
230234
}
231235

236+
/** Whether buttons in the group should be interactive while they're disabled. */
237+
@Input({transform: booleanAttribute})
238+
get disabledInteractive(): boolean {
239+
return this._disabledInteractive;
240+
}
241+
set disabledInteractive(value: boolean) {
242+
this._disabledInteractive = value;
243+
this._markButtonsForCheck();
244+
}
245+
232246
/** The layout direction of the toggle button group. */
233247
get dir(): Direction {
234248
return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
@@ -529,6 +543,7 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
529543
'[class.mat-button-toggle-standalone]': '!buttonToggleGroup',
530544
'[class.mat-button-toggle-checked]': 'checked',
531545
'[class.mat-button-toggle-disabled]': 'disabled',
546+
'[class.mat-button-toggle-disabled-interactive]': 'disabledInteractive',
532547
'[class.mat-button-toggle-appearance-standard]': 'appearance === "standard"',
533548
'class': 'mat-button-toggle',
534549
'[attr.aria-label]': 'null',
@@ -626,6 +641,19 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
626641
}
627642
private _disabled: boolean = false;
628643

644+
/** Whether the button should remain interactive when it is disabled. */
645+
@Input({transform: booleanAttribute})
646+
get disabledInteractive(): boolean {
647+
return (
648+
this._disabledInteractive ||
649+
(this.buttonToggleGroup !== null && this.buttonToggleGroup.disabledInteractive)
650+
);
651+
}
652+
set disabledInteractive(value: boolean) {
653+
this._disabledInteractive = value;
654+
}
655+
private _disabledInteractive: boolean;
656+
629657
/** Event emitted when the group value changes. */
630658
@Output() readonly change: EventEmitter<MatButtonToggleChange> =
631659
new EventEmitter<MatButtonToggleChange>();
@@ -645,6 +673,7 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
645673
this.buttonToggleGroup = toggleGroup;
646674
this.appearance =
647675
defaultOptions && defaultOptions.appearance ? defaultOptions.appearance : 'standard';
676+
this.disabledInteractive = defaultOptions?.disabledInteractive ?? false;
648677
}
649678

650679
ngOnInit() {
@@ -687,6 +716,10 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
687716

688717
/** Checks the button toggle due to an interaction with the underlying native button. */
689718
_onButtonClick() {
719+
if (this.disabled) {
720+
return;
721+
}
722+
690723
const newChecked = this.isSingleSelector() ? true : !this._checked;
691724

692725
if (newChecked !== this._checked) {

‎src/material/button-toggle/testing/button-toggle-harness.spec.ts

+10
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ describe('MatButtonToggleHarness', () => {
5959
expect(await disabledToggle.isDisabled()).toBe(true);
6060
});
6161

62+
it('should get the disabled state for an interactive disabled button', async () => {
63+
fixture.componentInstance.disabledInteractive = true;
64+
fixture.changeDetectorRef.markForCheck();
65+
66+
const disabledToggle = (await loader.getAllHarnesses(MatButtonToggleHarness))[1];
67+
expect(await disabledToggle.isDisabled()).toBe(true);
68+
});
69+
6270
it('should get the toggle name', async () => {
6371
const toggle = await loader.getHarness(MatButtonToggleHarness.with({text: 'First'}));
6472
expect(await toggle.getName()).toBe('first-name');
@@ -141,6 +149,7 @@ describe('MatButtonToggleHarness', () => {
141149
checked>First</mat-button-toggle>
142150
<mat-button-toggle
143151
[disabled]="disabled"
152+
[disabledInteractive]="disabledInteractive"
144153
aria-labelledby="second-label"
145154
appearance="legacy">Second</mat-button-toggle>
146155
<span id="second-label">Second toggle</span>
@@ -150,4 +159,5 @@ describe('MatButtonToggleHarness', () => {
150159
})
151160
class ButtonToggleHarnessTest {
152161
disabled = true;
162+
disabledInteractive = false;
153163
}

‎src/material/button-toggle/testing/button-toggle-harness.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ export class MatButtonToggleHarness extends ComponentHarness {
5555

5656
/** Gets a boolean promise indicating if the button toggle is disabled. */
5757
async isDisabled(): Promise<boolean> {
58-
const disabled = (await this._button()).getAttribute('disabled');
59-
return coerceBooleanProperty(await disabled);
58+
const host = await this.host();
59+
return host.hasClass('mat-button-toggle-disabled');
6060
}
6161

6262
/** Gets a promise for the button toggle's name. */

‎tools/public_api_guard/material/button-toggle.md

+11-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
4747
set checked(value: boolean);
4848
get disabled(): boolean;
4949
set disabled(value: boolean);
50+
get disabledInteractive(): boolean;
51+
set disabledInteractive(value: boolean);
5052
disableRipple: boolean;
5153
focus(options?: FocusOptions): void;
5254
_getButtonName(): string | null;
@@ -59,6 +61,8 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
5961
// (undocumented)
6062
static ngAcceptInputType_disabled: unknown;
6163
// (undocumented)
64+
static ngAcceptInputType_disabledInteractive: unknown;
65+
// (undocumented)
6266
static ngAcceptInputType_disableRipple: unknown;
6367
// (undocumented)
6468
ngAfterViewInit(): void;
@@ -71,7 +75,7 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy {
7175
set tabIndex(value: number | null);
7276
value: any;
7377
// (undocumented)
74-
static ɵcmp: i0.ɵɵComponentDeclaration<MatButtonToggle, "mat-button-toggle", ["matButtonToggle"], { "ariaLabel": { "alias": "aria-label"; "required": false; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; }; "id": { "alias": "id"; "required": false; }; "name": { "alias": "name"; "required": false; }; "value": { "alias": "value"; "required": false; }; "tabIndex": { "alias": "tabIndex"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "appearance": { "alias": "appearance"; "required": false; }; "checked": { "alias": "checked"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; }, { "change": "change"; }, never, ["*"], true, never>;
78+
static ɵcmp: i0.ɵɵComponentDeclaration<MatButtonToggle, "mat-button-toggle", ["matButtonToggle"], { "ariaLabel": { "alias": "aria-label"; "required": false; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; }; "id": { "alias": "id"; "required": false; }; "name": { "alias": "name"; "required": false; }; "value": { "alias": "value"; "required": false; }; "tabIndex": { "alias": "tabIndex"; "required": false; }; "disableRipple": { "alias": "disableRipple"; "required": false; }; "appearance": { "alias": "appearance"; "required": false; }; "checked": { "alias": "checked"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "disabledInteractive": { "alias": "disabledInteractive"; "required": false; }; }, { "change": "change"; }, never, ["*"], true, never>;
7579
// (undocumented)
7680
static ɵfac: i0.ɵɵFactoryDeclaration<MatButtonToggle, [{ optional: true; }, null, null, null, { attribute: "tabindex"; }, { optional: true; }]>;
7781
}
@@ -91,6 +95,7 @@ export class MatButtonToggleChange {
9195
// @public
9296
export interface MatButtonToggleDefaultOptions {
9397
appearance?: MatButtonToggleAppearance;
98+
disabledInteractive?: boolean;
9499
hideMultipleSelectionIndicator?: boolean;
95100
hideSingleSelectionIndicator?: boolean;
96101
}
@@ -105,6 +110,8 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
105110
get dir(): Direction;
106111
get disabled(): boolean;
107112
set disabled(value: boolean);
113+
get disabledInteractive(): boolean;
114+
set disabledInteractive(value: boolean);
108115
_emitChangeEvent(toggle: MatButtonToggle): void;
109116
get hideMultipleSelectionIndicator(): boolean;
110117
set hideMultipleSelectionIndicator(value: boolean);
@@ -120,6 +127,8 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
120127
// (undocumented)
121128
static ngAcceptInputType_disabled: unknown;
122129
// (undocumented)
130+
static ngAcceptInputType_disabledInteractive: unknown;
131+
// (undocumented)
123132
static ngAcceptInputType_hideMultipleSelectionIndicator: unknown;
124133
// (undocumented)
125134
static ngAcceptInputType_hideSingleSelectionIndicator: unknown;
@@ -146,7 +155,7 @@ export class MatButtonToggleGroup implements ControlValueAccessor, OnInit, After
146155
vertical: boolean;
147156
writeValue(value: any): void;
148157
// (undocumented)
149-
static ɵdir: i0.ɵɵDirectiveDeclaration<MatButtonToggleGroup, "mat-button-toggle-group", ["matButtonToggleGroup"], { "appearance": { "alias": "appearance"; "required": false; }; "name": { "alias": "name"; "required": false; }; "vertical": { "alias": "vertical"; "required": false; }; "value": { "alias": "value"; "required": false; }; "multiple": { "alias": "multiple"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "hideSingleSelectionIndicator": { "alias": "hideSingleSelectionIndicator"; "required": false; }; "hideMultipleSelectionIndicator": { "alias": "hideMultipleSelectionIndicator"; "required": false; }; }, { "valueChange": "valueChange"; "change": "change"; }, ["_buttonToggles"], never, true, never>;
158+
static ɵdir: i0.ɵɵDirectiveDeclaration<MatButtonToggleGroup, "mat-button-toggle-group", ["matButtonToggleGroup"], { "appearance": { "alias": "appearance"; "required": false; }; "name": { "alias": "name"; "required": false; }; "vertical": { "alias": "vertical"; "required": false; }; "value": { "alias": "value"; "required": false; }; "multiple": { "alias": "multiple"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "disabledInteractive": { "alias": "disabledInteractive"; "required": false; }; "hideSingleSelectionIndicator": { "alias": "hideSingleSelectionIndicator"; "required": false; }; "hideMultipleSelectionIndicator": { "alias": "hideMultipleSelectionIndicator"; "required": false; }; }, { "valueChange": "valueChange"; "change": "change"; }, ["_buttonToggles"], never, true, never>;
150159
// (undocumented)
151160
static ɵfac: i0.ɵɵFactoryDeclaration<MatButtonToggleGroup, [null, { optional: true; }, { optional: true; }]>;
152161
}

0 commit comments

Comments
 (0)
Please sign in to comment.