diff --git a/docs/docs/components/card.md b/docs/docs/components/card.md index 047ce5b89..df1c67234 100644 --- a/docs/docs/components/card.md +++ b/docs/docs/components/card.md @@ -5,7 +5,7 @@ layout: component.njk --- ```html {.example} - + ( <> - + ( Headers can be used to display titles and more. ```html {.example} - +
Header Title @@ -190,7 +190,7 @@ const css = ` const App = () => ( <> - +
Header Title @@ -209,7 +209,7 @@ const App = () => ( Footers can be used to display actions, summaries, or other relevant content. ```html {.example} - + This card has a footer. You can put all sorts of things in it!
@@ -251,7 +251,7 @@ const css = ` const App = () => ( <> - + This card has a footer. You can put all sorts of things in it!
@@ -272,7 +272,7 @@ const App = () => ( Cards accept an `image` slot. The image is displayed atop the card and stretches to fit. ```html {.example} - + ( <> - + ```html {.example} - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Close @@ -35,7 +35,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> Lorem ipsum dolor sit amet, consectetur adipiscing elit. setOpen(false)}> Close @@ -56,7 +56,7 @@ const App = () => { Use the `--width` custom property to set the dialog's width. ```html {.example} - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Close @@ -84,7 +84,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> Lorem ipsum dolor sit amet, consectetur adipiscing elit. setOpen(false)}> Close @@ -103,7 +103,7 @@ const App = () => { By design, a dialog's height will never exceed that of the viewport. As such, dialogs will not scroll with the page ensuring the header and footer are always accessible to the user. ```html {.example} - +

Scroll down and give it a try! 👇

@@ -133,7 +133,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}>
{ The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed. ```html {.example} - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Close @@ -193,7 +193,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> + This dialog will not close when you click on the overlay. Close @@ -264,7 +264,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> This dialog will not close when you click on the overlay. setOpen(false)}> Close @@ -283,7 +283,7 @@ const App = () => { By default, the dialog's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the dialog. If you want a different element to have focus, add the `autofocus` attribute to it as shown below. ```html {.example} - + Close @@ -313,7 +313,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> setOpen(false)}> Close diff --git a/docs/docs/components/drawer.md b/docs/docs/components/drawer.md index 23d847616..1c1f913a1 100644 --- a/docs/docs/components/drawer.md +++ b/docs/docs/components/drawer.md @@ -7,7 +7,7 @@ layout: component.njk ```html {.example} - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Close @@ -35,7 +35,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> Lorem ipsum dolor sit amet, consectetur adipiscing elit. setOpen(false)}> Close @@ -56,7 +56,7 @@ const App = () => { By default, drawers slide in from the end. To make the drawer slide in from the start, set the `placement` attribute to `start`. ```html {.example} - + This drawer slides in from the start. Close @@ -84,7 +84,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> This drawer slides in from the start. setOpen(false)}> Close @@ -103,7 +103,7 @@ const App = () => { To make the drawer slide in from the top, set the `placement` attribute to `top`. ```html {.example} - + This drawer slides in from the top. Close @@ -131,7 +131,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> This drawer slides in from the top. setOpen(false)}> Close @@ -150,7 +150,7 @@ const App = () => { To make the drawer slide in from the bottom, set the `placement` attribute to `bottom`. ```html {.example} - + This drawer slides in from the bottom. Close @@ -178,7 +178,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> This drawer slides in from the bottom. setOpen(false)}> Close @@ -192,86 +192,12 @@ const App = () => { ``` {% endraw %} -### Contained to an Element - -By default, drawers slide out of their [containing block](https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#Identifying_the_containing_block), which is usually the viewport. To make a drawer slide out of a parent element, add the `contained` attribute to the drawer and apply `position: relative` to its parent. - -Unlike normal drawers, contained drawers are not modal. This means they do not show an overlay, they do not trap focus, and they are not dismissible with [[Escape]]. This is intentional to allow users to interact with elements outside of the drawer. - -```html {.example} -
- The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer opens. - - - Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Close - -
- -Toggle Drawer - - -``` - -{% raw %} -```jsx {.react} -import { useState } from 'react'; -import WaButton from '@shoelace-style/shoelace/dist/react/button'; -import WaDrawer from '@shoelace-style/shoelace/dist/react/drawer'; - -const App = () => { - const [open, setOpen] = useState(false); - - return ( - <> -
- The drawer will be contained to this box. This content won't shift or be affected in any way when the drawer - opens. - setOpen(false)} - style={{ '--size': '50%' }} - > - Lorem ipsum dolor sit amet, consectetur adipiscing elit. - setOpen(false)}> - Close - - -
- - setOpen(true)}>Open Drawer - - ); -}; -``` -{% endraw %} - ### Custom Size Use the `--size` custom property to set the drawer's size. This will be applied to the drawer's width or height depending on its `placement`. ```html {.example} - + This drawer is always 50% of the viewport. Close @@ -299,7 +225,7 @@ const App = () => { return ( <> - setOpen(false)} style={{ '--size': '50vw' }}> + setOpen(false)} style={{ '--size': '50vw' }}> This drawer is always 50% of the viewport. setOpen(false)}> Close @@ -318,7 +244,7 @@ const App = () => { By design, a drawer's height will never exceed 100% of its container. As such, drawers will not scroll with the page to ensure the header and footer are always accessible to the user. ```html {.example} - +

Scroll down and give it a try! 👇

@@ -348,7 +274,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}>
{ The header shows a functional close button by default. You can use the `header-actions` slot to add additional [icon buttons](/components/icon-button) if needed. ```html {.example} - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Close @@ -407,7 +333,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> window.open(location.href)} /> Lorem ipsum dolor sit amet, consectetur adipiscing elit. setOpen(false)}> @@ -431,7 +357,7 @@ To keep the drawer open in such cases, you can cancel the `wa-request-close` eve You can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it. ```html {.example} - + This drawer will not close when you click on the overlay. Close @@ -473,7 +399,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> This drawer will not close when you click on the overlay. setOpen(false)}> Save & Close @@ -492,7 +418,7 @@ const App = () => { By default, the drawer's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the drawer. If you want a different element to have focus, add the `autofocus` attribute to it as shown below. ```html {.example} - + Close @@ -522,7 +448,7 @@ const App = () => { return ( <> - setOpen(false)}> + setOpen(false)}> setOpen(false)}> Close diff --git a/docs/docs/experimental/sandbox.md b/docs/docs/experimental/sandbox.md index a01102caa..ca4e501de 100644 --- a/docs/docs/experimental/sandbox.md +++ b/docs/docs/experimental/sandbox.md @@ -7,7 +7,7 @@ layout: page.njk ## Card ```html {.example} - + - +
diff --git a/docs/docs/resources/contributing.md b/docs/docs/resources/contributing.md index 9d7e71172..72d825b7b 100644 --- a/docs/docs/resources/contributing.md +++ b/docs/docs/resources/contributing.md @@ -282,7 +282,7 @@ Internally, each component uses the [BEM methodology](http://getbem.com/) for cl ### Boolean Props -Boolean props should _always_ default to `false`, otherwise there's no way for the user to unset them using only attributes. To keep the API as friendly and consistent as possible, use a property such as `noHeader` and a corresponding kebab-case attribute such as `no-header`. +Boolean props should _always_ default to `false`, otherwise there's no way for the user to unset them using only attributes. To keep the API as friendly and consistent as possible, use a property such as `noValue` and a corresponding kebab-case attribute such as `no-value`. When naming boolean props that hide or disable things, prefix them with `no-`, e.g. `no-spin-buttons` and avoid using other verbs such as `hide-` and `disable-` for consistency. diff --git a/src/components/alert/alert.styles.ts b/src/components/alert/alert.styles.ts index f986be9fb..5972c5fff 100644 --- a/src/components/alert/alert.styles.ts +++ b/src/components/alert/alert.styles.ts @@ -56,10 +56,10 @@ export default css` border-width: var(--border-width); color: var(--content-color); font: inherit; + padding: var(--padding); margin: inherit; } - .alert:not(.alert--has-icon) .alert__icon, .alert:not(.alert--closable) .alert__close-button { display: none; } @@ -70,13 +70,15 @@ export default css` align-items: center; color: var(--icon-color); font-size: var(--icon-size); - padding-inline-start: var(--padding); + } + + .alert__icon ::slotted(*) { + margin-inline-end: var(--padding) !important; } .alert__message { flex: 1 1 auto; display: block; - padding: var(--padding); overflow: hidden; } @@ -86,7 +88,7 @@ export default css` align-items: center; color: currentColor; font-size: var(--wa-font-size-m); - padding-inline-end: var(--padding); + padding-inline-start: var(--padding); } .alert__close-button:hover::part(base) { diff --git a/src/components/alert/alert.ts b/src/components/alert/alert.ts index a3bdfd8b4..9c12c703a 100644 --- a/src/components/alert/alert.ts +++ b/src/components/alert/alert.ts @@ -3,7 +3,6 @@ import { animateTo, stopAnimations } from '../../internal/animate.js'; import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query } from 'lit/decorators.js'; import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js'; -import { HasSlotController } from '../../internal/slot.js'; import { html } from 'lit'; import { LocalizeController } from '../../utilities/localize.js'; import { waitForEvent } from '../../internal/event.js'; @@ -55,7 +54,6 @@ export default class WaAlert extends WebAwesomeElement { static styles: CSSResultGroup = [componentStyles, styles]; private autoHideTimeout: number; - private readonly hasSlotController = new HasSlotController(this, 'icon', 'suffix'); private readonly localize = new LocalizeController(this); @query('[part~="base"]') base: HTMLElement; @@ -198,7 +196,6 @@ export default class WaAlert extends WebAwesomeElement { alert: true, 'alert--open': this.open, 'alert--closable': this.closable, - 'alert--has-icon': this.hasSlotController.test('icon'), 'alert--brand': this.variant === 'brand', 'alert--success': this.variant === 'success', 'alert--neutral': this.variant === 'neutral', diff --git a/src/components/breadcrumb-item/breadcrumb-item.styles.ts b/src/components/breadcrumb-item/breadcrumb-item.styles.ts index 1896e08f8..263445f73 100644 --- a/src/components/breadcrumb-item/breadcrumb-item.styles.ts +++ b/src/components/breadcrumb-item/breadcrumb-item.styles.ts @@ -58,14 +58,13 @@ export default css` align-items: center; } - .breadcrumb-item--has-prefix .breadcrumb-item__prefix { + .breadcrumb-item__prefix, + .breadcrumb-item__suffix { display: inline-flex; - margin-inline-end: var(--wa-space-s); } - .breadcrumb-item--has-suffix .breadcrumb-item__suffix { - display: inline-flex; - margin-inline-start: var(--wa-space-s); + ::slotted(*) { + margin-inline-end: var(--wa-space-s) !important; } :host(:last-of-type) .breadcrumb-item__separator { diff --git a/src/components/breadcrumb-item/breadcrumb-item.test.ts b/src/components/breadcrumb-item/breadcrumb-item.test.ts index 473da27b6..5a380e5a7 100644 --- a/src/components/breadcrumb-item/breadcrumb-item.test.ts +++ b/src/components/breadcrumb-item/breadcrumb-item.test.ts @@ -122,11 +122,6 @@ describe('', () => { expect(childNodes.length).to.eq(1); }); - - it('should append class "breadcrumb-item--has-prefix" to "base" part', () => { - const part = el.shadowRoot!.querySelector('[part~="base"]')!; - expect(part.classList.value.trim()).to.equal('breadcrumb-item breadcrumb-item--has-prefix'); - }); }); describe('when provided an element in the slot "suffix" to support suffix icons', () => { @@ -149,10 +144,5 @@ describe('', () => { expect(childNodes.length).to.eq(1); }); - - it('should append class "breadcrumb-item--has-suffix" to "base" part', () => { - const part = el.shadowRoot!.querySelector('[part~="base"]')!; - expect(part.classList.value.trim()).to.equal('breadcrumb-item breadcrumb-item--has-suffix'); - }); }); }); diff --git a/src/components/breadcrumb-item/breadcrumb-item.ts b/src/components/breadcrumb-item/breadcrumb-item.ts index 8e1a9cbd1..b4d74445f 100644 --- a/src/components/breadcrumb-item/breadcrumb-item.ts +++ b/src/components/breadcrumb-item/breadcrumb-item.ts @@ -1,6 +1,4 @@ -import { classMap } from 'lit/directives/class-map.js'; import { customElement, property } from 'lit/decorators.js'; -import { HasSlotController } from '../../internal/slot.js'; import { html } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; import componentStyles from '../../styles/component.styles.js'; @@ -30,8 +28,6 @@ import type { CSSResultGroup } from 'lit'; export default class WaBreadcrumbItem extends WebAwesomeElement { static styles: CSSResultGroup = [componentStyles, styles]; - private readonly hasSlotController = new HasSlotController(this, 'prefix', 'suffix'); - /** * Optional URL to direct the user to when the breadcrumb item is activated. When set, a link will be rendered * internally. When unset, a button will be rendered instead. @@ -48,14 +44,7 @@ export default class WaBreadcrumbItem extends WebAwesomeElement { const isLink = this.href ? true : false; return html` -
+
`; diff --git a/src/components/drawer/drawer.styles.ts b/src/components/drawer/drawer.styles.ts index 0f7c55918..60de52b9e 100644 --- a/src/components/drawer/drawer.styles.ts +++ b/src/components/drawer/drawer.styles.ts @@ -11,6 +11,8 @@ export default css` } .drawer { + position: fixed; + z-index: var(--wa-z-index-drawer); top: 0; inset-inline-start: 0; width: 100%; @@ -19,16 +21,6 @@ export default css` overflow: hidden; } - .drawer--contained { - position: absolute; - z-index: initial; - } - - .drawer--fixed { - position: fixed; - z-index: var(--wa-z-index-drawer); - } - .drawer__panel { position: absolute; display: flex; @@ -129,10 +121,6 @@ export default css` margin-inline-end: var(--wa-spacing-xs); } - .drawer:not(.drawer--has-footer) .drawer__footer { - display: none; - } - .drawer__overlay { display: block; position: fixed; @@ -144,10 +132,6 @@ export default css` pointer-events: all; } - .drawer--contained .drawer__overlay { - display: none; - } - @media (forced-colors: active) { .drawer__panel { border: solid 1px white; diff --git a/src/components/drawer/drawer.ts b/src/components/drawer/drawer.ts index 4128cf19b..3d3113b31 100644 --- a/src/components/drawer/drawer.ts +++ b/src/components/drawer/drawer.ts @@ -3,7 +3,6 @@ import { animateTo, stopAnimations } from '../../internal/animate.js'; import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query } from 'lit/decorators.js'; import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js'; -import { HasSlotController } from '../../internal/slot.js'; import { html } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; import { LocalizeController } from '../../utilities/localize.js'; @@ -78,7 +77,6 @@ import type { CSSResultGroup } from 'lit'; export default class WaDrawer extends WebAwesomeElement { static styles: CSSResultGroup = [componentStyles, styles]; - private readonly hasSlotController = new HasSlotController(this, 'footer'); private readonly localize = new LocalizeController(this); private originalTrigger: HTMLElement | null; public modal = new Modal(this); @@ -95,25 +93,19 @@ export default class WaDrawer extends WebAwesomeElement { @property({ type: Boolean, reflect: true }) open = false; /** - * The drawer's label as displayed in the header. You should always include a relevant label even when using - * `no-header`, as it is required for proper accessibility. If you need to display HTML, use the `label` slot instead. + * The drawer's label as displayed in the header. You should always include a relevant label, as it is required for + * proper accessibility. If you need to display HTML, use the `label` slot instead. */ @property({ reflect: true }) label = ''; /** The direction from which the drawer will open. */ @property({ reflect: true }) placement: 'top' | 'end' | 'bottom' | 'start' = 'end'; - /** - * By default, the drawer slides out of its containing block (usually the viewport). To make the drawer slide out of - * its parent element, set this attribute and add `position: relative` to the parent. - */ - @property({ type: Boolean, reflect: true }) contained = false; + /** Renders the drawer with a header. */ + @property({ attribute: 'with-header', type: Boolean, reflect: true }) withHeader = false; - /** - * Removes the header. This will also remove the default close button, so please ensure you provide an easy, - * accessible way for users to dismiss the drawer. - */ - @property({ attribute: 'no-header', type: Boolean, reflect: true }) noHeader = false; + /** Renders the drawer with a footer. */ + @property({ attribute: 'with-footer', type: Boolean, reflect: true }) withFooter = false; firstUpdated() { this.drawer.hidden = !this.open; @@ -121,10 +113,8 @@ export default class WaDrawer extends WebAwesomeElement { if (this.open) { this.addOpenListeners(); - if (!this.contained) { - this.modal.activate(); - lockBodyScrolling(this); - } + this.modal.activate(); + lockBodyScrolling(this); } } @@ -152,10 +142,8 @@ export default class WaDrawer extends WebAwesomeElement { private addOpenListeners() { if ('CloseWatcher' in window) { this.closeWatcher?.destroy(); - if (!this.contained) { - this.closeWatcher = new CloseWatcher(); - this.closeWatcher.onclose = () => this.requestClose('keyboard'); - } + this.closeWatcher = new CloseWatcher(); + this.closeWatcher.onclose = () => this.requestClose('keyboard'); } else { document.addEventListener('keydown', this.handleDocumentKeyDown); this.closeWatcher?.destroy(); @@ -167,11 +155,6 @@ export default class WaDrawer extends WebAwesomeElement { } private handleDocumentKeyDown = (event: KeyboardEvent) => { - // Contained drawers aren't modal and don't response to the escape key - if (this.contained) { - return; - } - if (event.key === 'Escape' && this.modal.isActive() && this.open) { event.stopImmediatePropagation(); this.requestClose('keyboard'); @@ -187,10 +170,8 @@ export default class WaDrawer extends WebAwesomeElement { this.originalTrigger = document.activeElement as HTMLElement; // Lock body scrolling only if the drawer isn't contained - if (!this.contained) { - this.modal.activate(); - lockBodyScrolling(this); - } + this.modal.activate(); + lockBodyScrolling(this); // When the drawer is shown, Safari will attempt to set focus on whatever element has autofocus. This causes the // drawer's animation to jitter, so we'll temporarily remove the attribute, call `focus({ preventScroll: true })` @@ -239,11 +220,8 @@ export default class WaDrawer extends WebAwesomeElement { // Hide this.emit('wa-hide'); this.removeOpenListeners(); - - if (!this.contained) { - this.modal.deactivate(); - unlockBodyScrolling(this); - } + this.modal.deactivate(); + unlockBodyScrolling(this); await Promise.all([stopAnimations(this.drawer), stopAnimations(this.overlay)]); const panelAnimation = getAnimation(this, `drawer.hide${uppercaseFirstLetter(this.placement)}`, { @@ -279,19 +257,6 @@ export default class WaDrawer extends WebAwesomeElement { } } - @watch('contained', { waitUntilFirstUpdate: true }) - handleNoModalChange() { - if (this.open && !this.contained) { - this.modal.activate(); - lockBodyScrolling(this); - } - - if (this.open && this.contained) { - this.modal.deactivate(); - unlockBodyScrolling(this); - } - } - /** Shows the drawer. */ async show() { if (this.open) { @@ -323,10 +288,9 @@ export default class WaDrawer extends WebAwesomeElement { 'drawer--end': this.placement === 'end', 'drawer--bottom': this.placement === 'bottom', 'drawer--start': this.placement === 'start', - 'drawer--contained': this.contained, - 'drawer--fixed': !this.contained, 'drawer--rtl': this.localize.dir() === 'rtl', - 'drawer--has-footer': this.hasSlotController.test('footer') + 'drawer--with-header': this.withHeader, + 'drawer--with-footer': this.withFooter })} >
this.requestClose('overlay')} tabindex="-1">
@@ -337,11 +301,11 @@ export default class WaDrawer extends WebAwesomeElement { role="dialog" aria-modal="true" aria-hidden=${this.open ? 'false' : 'true'} - aria-label=${ifDefined(this.noHeader ? this.label : undefined)} - aria-labelledby=${ifDefined(!this.noHeader ? 'title' : undefined)} + aria-label=${ifDefined(this.withHeader ? undefined : this.label)} + aria-labelledby=${ifDefined(this.withHeader ? 'title' : undefined)} tabindex="0" > - ${!this.noHeader + ${this.withHeader ? html`

@@ -367,9 +331,13 @@ export default class WaDrawer extends WebAwesomeElement { -
- -
+ ${this.withFooter + ? html` +
+ +
+ ` + : ''}

`; diff --git a/src/components/menu-item/menu-item.ts b/src/components/menu-item/menu-item.ts index f363e4b96..8679cb4b9 100644 --- a/src/components/menu-item/menu-item.ts +++ b/src/components/menu-item/menu-item.ts @@ -3,7 +3,7 @@ import '../popup/popup.js'; import '../spinner/spinner.js'; import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query } from 'lit/decorators.js'; -import { getTextContent, HasSlotController } from '../../internal/slot.js'; +import { getTextContent } from '../../internal/slot.js'; import { html } from 'lit'; import { LocalizeController } from '../../utilities/localize.js'; import { SubmenuController } from './submenu-controller.js'; @@ -63,8 +63,7 @@ export default class WaMenuItem extends WebAwesomeElement { @property({ type: Boolean, reflect: true }) disabled = false; private readonly localize = new LocalizeController(this); - private readonly hasSlotController = new HasSlotController(this, 'submenu'); - private submenuController: SubmenuController = new SubmenuController(this, this.hasSlotController, this.localize); + private submenuController: SubmenuController = new SubmenuController(this, this.localize); connectedCallback() { super.connectedCallback(); @@ -145,7 +144,7 @@ export default class WaMenuItem extends WebAwesomeElement { } isSubmenu() { - return this.hasSlotController.test('submenu'); + return this.querySelector(`:scope > [slot="submenu"]`) !== null; } render() { diff --git a/src/components/menu-item/submenu-controller.ts b/src/components/menu-item/submenu-controller.ts index 79262a1d2..1e95e6ba0 100644 --- a/src/components/menu-item/submenu-controller.ts +++ b/src/components/menu-item/submenu-controller.ts @@ -1,5 +1,4 @@ import { createRef, ref, type Ref } from 'lit/directives/ref.js'; -import { type HasSlotController } from '../../internal/slot.js'; import { html } from 'lit'; import { type LocalizeController } from '../../utilities/localize.js'; import type { ReactiveController, ReactiveControllerHost } from 'lit'; @@ -14,22 +13,20 @@ export class SubmenuController implements ReactiveController { private isConnected = false; private isPopupConnected = false; private skidding = 0; - private readonly hasSlotController: HasSlotController; private readonly localize: LocalizeController; private readonly submenuOpenDelay = 100; - constructor( - host: ReactiveControllerHost & WaMenuItem, - hasSlotController: HasSlotController, - localize: LocalizeController - ) { + constructor(host: ReactiveControllerHost & WaMenuItem, localize: LocalizeController) { (this.host = host).addController(this); - this.hasSlotController = hasSlotController; this.localize = localize; } + private hasSubmenu() { + return this.host.querySelector(`:scope > [slot="submenu"]`) !== null; + } + hostConnected() { - if (this.hasSlotController.test('submenu') && !this.host.disabled) { + if (this.hasSubmenu() && !this.host.disabled) { this.addListeners(); } } @@ -39,7 +36,7 @@ export class SubmenuController implements ReactiveController { } hostUpdated() { - if (this.hasSlotController.test('submenu') && !this.host.disabled) { + if (this.hasSubmenu() && !this.host.disabled) { this.addListeners(); this.updateSkidding(); } else { @@ -93,7 +90,7 @@ export class SubmenuController implements ReactiveController { }; private handleMouseOver = () => { - if (this.hasSlotController.test('submenu')) { + if (this.hasSubmenu()) { this.enableSubmenu(); } }; diff --git a/src/internal/tabbable.test.ts b/src/internal/tabbable.test.ts index 4076c29db..29352556f 100644 --- a/src/internal/tabbable.test.ts +++ b/src/internal/tabbable.test.ts @@ -29,7 +29,7 @@ window.customElements.define( } connectedCallback() { this.shadowRoot!.innerHTML = ` - + @@ -145,7 +145,7 @@ it('Should allow tabbing to slotted elements', async () => { it('Should account for when focus is changed from outside sources (like clicking)', async () => { const dialog = await fixture(html` - + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Close @@ -186,12 +186,12 @@ it('Should respect nested modal instances', async () => { await fixture(html`
dialogOne().show()}> - + dialogTwo().show()} id="open-dialog-2">Open Dialog 2 Close - + Close