Skip to content

Commit d59dd9d

Browse files
feat(labs): add container slot support for expressive button
PiperOrigin-RevId: 930091976
1 parent a3379a3 commit d59dd9d

1 file changed

Lines changed: 112 additions & 2 deletions

File tree

labs/gb/components/button/md-button.ts

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import {css, CSSResultOrNative, html, LitElement, nothing} from 'lit';
8-
import {customElement, property} from 'lit/decorators.js';
7+
import {
8+
css,
9+
CSSResultOrNative,
10+
html,
11+
LitElement,
12+
nothing,
13+
PropertyValues,
14+
} from 'lit';
15+
import {customElement, property, state} from 'lit/decorators.js';
916
import {ARIAMixinStrict} from '../../../../internal/aria/aria.js';
1017
import {mixinDelegatesAria} from '../../../../internal/aria/delegate.js';
1118
import {redispatchEvent} from '../../../../internal/events/redispatch-event.js';
@@ -17,6 +24,9 @@ import focusRingStyles from '../focus/focus-ring.css' with {type: 'css'}; // git
1724
// import focusRingStyles from '../focus/focus-ring.cssresult.js'; // google3-only
1825
import rippleStyles from '../ripple/ripple.css' with {type: 'css'}; // github-only
1926
// import rippleStyles from '../ripple/ripple.cssresult.js'; // google3-only
27+
28+
import {hasSlotted} from '../shared/has-slotted.js';
29+
2030
import buttonStyles from './button.css' with {type: 'css'}; // github-only
2131
// import buttonStyles from './button.cssresult.js'; // google3-only
2232

@@ -52,13 +62,37 @@ export class Button extends baseClass {
5262
css`
5363
:host {
5464
display: inline-flex;
65+
isolation: isolate;
5566
}
5667
.btn {
5768
flex: 1;
69+
position: relative;
70+
}
71+
.btn:has([name='container'].has-slotted) {
72+
background-color: transparent;
73+
}
74+
.btn.btn-hide-outline {
75+
--outline-color: transparent;
76+
}
77+
slot[name='container'] {
78+
display: block;
79+
position: absolute;
80+
inset: 0;
81+
border-radius: inherit;
82+
pointer-events: none;
83+
--color: var(--container-color);
84+
z-index: -1;
85+
transition: inherit;
86+
}
87+
slot[name='container']::slotted(*) {
88+
width: 100%;
89+
height: 100%;
5890
}
5991
`,
6092
];
6193

94+
@state() private hideOutline = false;
95+
6296
/**
6397
* The color of the button.
6498
*/
@@ -129,13 +163,66 @@ export class Button extends baseClass {
129163
*/
130164
@property() target: '_blank' | '_parent' | '_self' | '_top' | '' = '';
131165

166+
private lastFiredEnabledState?: boolean;
167+
168+
override connectedCallback() {
169+
super.connectedCallback();
170+
this.addEventListener('md-gb:set-show-outline', this.handleSetShowOutline);
171+
}
172+
173+
override disconnectedCallback() {
174+
super.disconnectedCallback();
175+
this.removeEventListener(
176+
'md-gb:set-show-outline',
177+
this.handleSetShowOutline,
178+
);
179+
}
180+
181+
private readonly handleSetShowOutline = (event: Event) => {
182+
const customEvent = event as CustomEvent<{shown: boolean}>;
183+
this.hideOutline = !customEvent.detail.shown;
184+
};
185+
186+
protected override updated(changedProperties: PropertyValues) {
187+
super.updated(changedProperties);
188+
if (
189+
changedProperties.has('disabled') ||
190+
changedProperties.has('softDisabled')
191+
) {
192+
this.dispatchSetEnabledEvent();
193+
}
194+
}
195+
196+
private dispatchSetEnabledEvent() {
197+
const enabled = !(this.disabled || this.softDisabled);
198+
199+
if (this.lastFiredEnabledState === enabled) return;
200+
201+
const slot = this.shadowRoot?.querySelector(
202+
'slot[name="container"]',
203+
) as HTMLSlotElement;
204+
if (slot) {
205+
for (const element of slot.assignedElements({flatten: true})) {
206+
element.dispatchEvent(
207+
new CustomEvent('md-gb:set-enabled', {
208+
detail: {enabled},
209+
}),
210+
);
211+
}
212+
}
213+
this.lastFiredEnabledState = enabled;
214+
}
215+
132216
protected override render() {
133217
const classes = button({
134218
color: this.color,
135219
size: this.size,
136220
square: this.square,
137221
// Emulate `:disabled` when soft-disabled
138222
disabled: this.softDisabled,
223+
classes: {
224+
'btn-hide-outline': this.hideOutline,
225+
},
139226
});
140227

141228
// Needed for closure conformance
@@ -153,6 +240,10 @@ export class Button extends baseClass {
153240
aria-disabled=${this.disabled || this.softDisabled || nothing}
154241
tabindex=${this.disabled && !this.softDisabled ? -1 : nothing}>
155242
<slot></slot>
243+
<slot
244+
name="container"
245+
${hasSlotted()}
246+
@slotchange=${this.handleContainerSlotChange}></slot>
156247
</a>`;
157248
}
158249

@@ -167,9 +258,28 @@ export class Button extends baseClass {
167258
aria-expanded=${ariaExpanded || nothing}
168259
@change=${this.handleChange}>
169260
<slot></slot>
261+
<slot
262+
name="container"
263+
${hasSlotted()}
264+
@slotchange=${this.handleContainerSlotChange}></slot>
170265
</button>`;
171266
}
172267

268+
private handleContainerSlotChange(event: Event) {
269+
const slot = event.target as HTMLSlotElement;
270+
271+
const enabled = !(this.disabled || this.softDisabled);
272+
273+
for (const element of slot.assignedElements({flatten: true})) {
274+
element.dispatchEvent(new CustomEvent('md-gb:change-container-slot'));
275+
element.dispatchEvent(
276+
new CustomEvent('md-gb:set-enabled', {
277+
detail: {enabled},
278+
}),
279+
);
280+
}
281+
}
282+
173283
private handleChange(event: Event) {
174284
this.selected = (event.target as HTMLElement).ariaPressed === 'true';
175285
redispatchEvent(this, event);

0 commit comments

Comments
 (0)