diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 38648c531..b58c3d3b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -189,11 +189,11 @@ To expose custom properties for a component, define them in the `:host` block an ### Conditional Slots -When a component relies on the presence of a slot to do something, don't assume the initial state of the component is its permanent state. Users can add and remove slotted content anytime, and components should be aware of this. +When a component relies on the presence of slotted content to do something, don't assume its initial state is permanent. Slotted content can be added or removed any time and components must be aware of this. A good practice to manage this is: -- Create an `updateSlots` method that uses `hasSlot` (imported from `src/utilities/slot.ts`) to check for the required slot(s) -- Listen on the host element for the `slotchange` event and run `updateSlots` -- Don't conditionally render any slots — always use `hidden` or `display: none` so the slot exists in the DOM +- Create a `handleSlotChange` method that uses `hasSlot` (imported from `src/utilities/slot.ts`) to update state variables for the associated slot(s) +- Add `onSlotchange={this.handleSlotChange}` to the slots you want to watch +- Never conditionally render `` elements in a component — always use `hidden` so the slot remains in the DOM and the `slotchange` event can be captured See the source of card, dialog, or drawer for examples. diff --git a/docs/getting-started/changelog.md b/docs/getting-started/changelog.md index ddc86ce02..f2e89de03 100644 --- a/docs/getting-started/changelog.md +++ b/docs/getting-started/changelog.md @@ -14,6 +14,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis - Fixed a bug where concurrent active modals (i.e. dialog, drawer) would try to steal focus from each other - Improved `sl-color-picker` grid and slider handles [#246](https://github.com/shoelace-style/shoelace/issues/246) - Reworked show/hide logic in `sl-alert`, `sl-dialog`, and `sl-drawer` to not use reflow hacks and the `hidden` attribute +- Reworked slot logic in `sl-card`, `sl-dialog`, and `sl-drawer` - Updated to Popper 2.5.3 to address a fixed position bug in Firefox ## 2.0.0-beta.20 diff --git a/src/components/card/card.tsx b/src/components/card/card.tsx index 59d2b7b74..53dd18616 100644 --- a/src/components/card/card.tsx +++ b/src/components/card/card.tsx @@ -30,19 +30,10 @@ export class Card { @State() hasHeader = false; connectedCallback() { - this.updateSlots = this.updateSlots.bind(this); + this.handleSlotChange = this.handleSlotChange.bind(this); } - componentWillLoad() { - this.updateSlots(); - this.host.shadowRoot.addEventListener('slotchange', this.updateSlots); - } - - disconnectedCallback() { - this.host.shadowRoot.removeEventListener('slotchange', this.updateSlots); - } - - updateSlots() { + handleSlotChange() { this.hasFooter = hasSlot(this.host, 'footer'); this.hasImage = hasSlot(this.host, 'image'); this.hasHeader = hasSlot(this.host, 'header'); @@ -60,11 +51,11 @@ export class Card { }} >
- +
- +
@@ -72,7 +63,7 @@ export class Card {
); diff --git a/src/components/dialog/dialog.tsx b/src/components/dialog/dialog.tsx index 5eb18cd0d..2f35bf6f1 100644 --- a/src/components/dialog/dialog.tsx +++ b/src/components/dialog/dialog.tsx @@ -78,18 +78,13 @@ export class Dialog { this.handleTransitionEnd = this.handleTransitionEnd.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); this.handleOverlayClick = this.handleOverlayClick.bind(this); - this.updateSlots = this.updateSlots.bind(this); + this.handleSlotChange = this.handleSlotChange.bind(this); this.modal = new Modal(this.host, { onFocusOut: () => this.panel.focus() }); } - componentWillLoad() { - this.updateSlots(); - this.host.shadowRoot.addEventListener('slotchange', this.updateSlots); - } - componentDidLoad() { // Show on init if open if (this.open) { @@ -99,8 +94,6 @@ export class Dialog { disconnectedCallback() { unlockBodyScrolling(this.host); - - this.host.shadowRoot.removeEventListener('slotchange', this.updateSlots); } /** Shows the dialog */ @@ -162,6 +155,10 @@ export class Dialog { } } + handleSlotChange() { + this.hasFooter = hasSlot(this.host, 'footer'); + } + handleTransitionEnd(event: TransitionEvent) { const target = event.target as HTMLElement; @@ -176,10 +173,6 @@ export class Dialog { } } - updateSlots() { - this.hasFooter = hasSlot(this.host, 'footer'); - } - render() { return (
- +
diff --git a/src/components/drawer/drawer.tsx b/src/components/drawer/drawer.tsx index 496ef917c..3707291f8 100644 --- a/src/components/drawer/drawer.tsx +++ b/src/components/drawer/drawer.tsx @@ -86,18 +86,13 @@ export class Drawer { this.handleTransitionEnd = this.handleTransitionEnd.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); this.handleOverlayClick = this.handleOverlayClick.bind(this); - this.updateSlots = this.updateSlots.bind(this); + this.handleSlotChange = this.handleSlotChange.bind(this); this.modal = new Modal(this.host, { onFocusOut: () => (this.contained ? null : this.panel.focus()) }); } - componentWillLoad() { - this.updateSlots(); - this.host.shadowRoot.addEventListener('slotchange', this.updateSlots); - } - componentDidLoad() { // Show on init if open if (this.open) { @@ -107,8 +102,6 @@ export class Drawer { disconnectedCallback() { unlockBodyScrolling(this.host); - - this.host.shadowRoot.removeEventListener('slotchange', this.updateSlots); } /** Shows the drawer */ @@ -173,6 +166,10 @@ export class Drawer { } } + handleSlotChange() { + this.hasFooter = hasSlot(this.host, 'footer'); + } + handleTransitionEnd(event: TransitionEvent) { const target = event.target as HTMLElement; @@ -187,10 +184,6 @@ export class Drawer { } } - updateSlots() { - this.hasFooter = hasSlot(this.host, 'footer'); - } - render() { return (