diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md
index 3e271d61..e2743e37 100644
--- a/docs/resources/changelog.md
+++ b/docs/resources/changelog.md
@@ -16,6 +16,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
- Improved a11y for disabled buttons that are rendered as links
- Improved a11y for `sl-button-group`
- Removed `sl-show`, `sl-hide`, `sl-after-show`, `sl-after-hide` events from `sl-color-picker` (the color picker's visibility cannot be controlled programmatically so these shouldn't have been exposed; the dropdown events now bubble up so you can listen for those instead)
+- Reworked `sl-button-group` so it doesn't require light DOM styles
## 2.0.0-beta.36
diff --git a/src/components/button-group/button-group.light-dom.scss b/src/components/button-group/button-group.light-dom.scss
deleted file mode 100644
index 205bb386..00000000
--- a/src/components/button-group/button-group.light-dom.scss
+++ /dev/null
@@ -1,62 +0,0 @@
-//
-// In general, we should avoid placing styles in the light DOM. However, we don't have a way to target slotted element
-// parts, e.g. `::slotted(sl-button)::part(base)`, so we need these styles to make buttons groups work.
-//
-// The alternative approach is to set the styles with JavaScript, but this is more expensive because it requires
-// multiple listeners + DOM traversals.
-//
-sl-button-group {
- // First
- > sl-button:first-child::part(base),
- > sl-dropdown:first-child > sl-button[slot='trigger']::part(base),
- > sl-tooltip:first-child > sl-button::part(base) {
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- }
-
- // Last
- > sl-button:last-child::part(base),
- > sl-dropdown:last-child > sl-button[slot='trigger']::part(base),
- > sl-tooltip:last-child > sl-button::part(base) {
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- }
-
- // Interior
- > sl-button:not(:first-child):not(:last-child)::part(base),
- > sl-dropdown:not(:first-child):not(:last-child) > sl-button[slot='trigger']::part(base),
- > sl-tooltip:not(:first-child):not(:last-child) > sl-button::part(base) {
- border-radius: 0;
- }
-
- // All except the first
- > sl-button:not(:first-child),
- > sl-dropdown:not(:first-child) > sl-button[slot='trigger'],
- > sl-tooltip:not(:first-child) > sl-button {
- margin-left: calc(-1 * var(--sl-input-border-width));
-
- // Add a visual separator between solid buttons
- &:not([type='default'])::part(base):not(:hover):not(:active):not(:focus):after {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- border-left: solid 1px #ffffff40;
- }
- }
-
- // Hover
- > sl-button:hover,
- > sl-dropdown:hover > sl-button[slot='trigger'],
- > sl-tooltip:hover > sl-button {
- z-index: 1;
- }
-
- // Focus
- > sl-button.sl-focus,
- > sl-dropdown > sl-button[slot='trigger'].sl-focus,
- > sl-tooltip > sl-button.sl-focus {
- z-index: 2;
- }
-}
diff --git a/src/components/button-group/button-group.scss b/src/components/button-group/button-group.scss
index b454a220..0b495403 100644
--- a/src/components/button-group/button-group.scss
+++ b/src/components/button-group/button-group.scss
@@ -7,9 +7,4 @@
.button-group {
display: flex;
flex-wrap: nowrap;
- position: relative;
-}
-
-::slotted(.sl-focus) {
- z-index: 1;
}
diff --git a/src/components/button-group/button-group.ts b/src/components/button-group/button-group.ts
index 33385f9b..b169bcd9 100644
--- a/src/components/button-group/button-group.ts
+++ b/src/components/button-group/button-group.ts
@@ -1,5 +1,5 @@
import { LitElement, html, unsafeCSS } from 'lit';
-import { customElement, property } from 'lit/decorators';
+import { customElement, property, query } from 'lit/decorators';
import styles from 'sass:./button-group.scss';
/**
@@ -14,17 +14,45 @@ import styles from 'sass:./button-group.scss';
export default class SlButtonGroup extends LitElement {
static styles = unsafeCSS(styles);
+ @query('slot') defaultSlot: HTMLSlotElement;
+
/** A label to use for the button group's `aria-label` attribute. */
@property() label = '';
handleFocus(event: CustomEvent) {
- const button = event.target as HTMLElement;
- button.classList.add('sl-focus');
+ const button = findButton(event.target as HTMLElement);
+ button?.classList.add('sl-button-group__button--focus');
}
handleBlur(event: CustomEvent) {
- const button = event.target as HTMLElement;
- button.classList.remove('sl-focus');
+ const button = findButton(event.target as HTMLElement);
+ button?.classList.remove('sl-button-group__button--focus');
+ }
+
+ handleMouseOver(event: CustomEvent) {
+ const button = findButton(event.target as HTMLElement);
+ button?.classList.add('sl-button-group__button--hover');
+ }
+
+ handleMouseOut(event: CustomEvent) {
+ const button = findButton(event.target as HTMLElement);
+ button?.classList.remove('sl-button-group__button--hover');
+ }
+
+ handleSlotChange() {
+ const slottedElements = [...this.defaultSlot.assignedElements({ flatten: true })] as HTMLElement[];
+
+ slottedElements.map(el => {
+ const index = slottedElements.indexOf(el);
+ const button = findButton(el);
+
+ if (button) {
+ button.classList.add('sl-button-group__button');
+ button.classList.toggle('sl-button-group__button--first', index === 0);
+ button.classList.toggle('sl-button-group__button--inner', index > 0 && index < slottedElements.length - 1);
+ button.classList.toggle('sl-button-group__button--last', index === slottedElements.length - 1);
+ }
+ });
}
render() {
@@ -36,13 +64,19 @@ export default class SlButtonGroup extends LitElement {
aria-label=${this.label}
@focusout=${this.handleBlur}
@focusin=${this.handleFocus}
+ @mouseover=${this.handleMouseOver}
+ @mouseout=${this.handleMouseOut}
>
-
+
`;
}
}
+function findButton(el: HTMLElement) {
+ return el.tagName.toLowerCase() === 'sl-button' ? el : el.querySelector('sl-button');
+}
+
declare global {
interface HTMLElementTagNameMap {
'sl-button-group': SlButtonGroup;
diff --git a/src/components/button/button.scss b/src/components/button/button.scss
index 14c13367..df536b79 100644
--- a/src/components/button/button.scss
+++ b/src/components/button/button.scss
@@ -446,3 +446,49 @@
}
}
}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Button groups support a variety of button types (e.g. buttons with tooltips, buttons as dropdown triggers, etc.).
+// This means buttons aren't always direct descendants of the button group, thus we can't target them with the ::slotted
+// selector. To work around this, the button group component does some magic to add these special classes to buttons and
+// we style them here instead.
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+:host(.sl-button-group__button--first) .button {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+:host(.sl-button-group__button--inner) .button {
+ border-radius: 0;
+}
+
+:host(.sl-button-group__button--last) .button {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+// All except the first
+:host(.sl-button-group__button:not(.sl-button-group__button--first)) {
+ margin-left: calc(-1 * var(--sl-input-border-width));
+}
+
+// Add a visual separator between solid buttons
+:host(.sl-button-group__button:not(.sl-button-group__button--focus, .sl-button-group__button--first, [type='default']):not(:hover, :active, :focus))
+ .button:after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ border-left: solid 1px #ffffff40;
+}
+
+// Bump focused buttons up so their focus ring isn't clipped
+:host(.sl-button-group__button--hover) {
+ z-index: 1;
+}
+
+:host(.sl-button-group__button--focus) {
+ z-index: 2;
+}
diff --git a/src/styles/base.scss b/src/styles/base.scss
index 5e752238..cc2bac0e 100644
--- a/src/styles/base.scss
+++ b/src/styles/base.scss
@@ -5,7 +5,6 @@
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@use '../components/alert/alert.light-dom';
-@use '../components/button-group/button-group.light-dom';
:root {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////