import { css, html } from 'lit'; import { property, query } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { BootstrapElement, defineElement } from '@bootstrap-wc/core'; let _id = 0; /** * `` — a single accordion panel. Slot `header` for the * collapsed-state label; the default slot is the panel body. * * @fires bs-accordion-item-toggle - `{detail: {open}}` on state change. */ export class BsAccordionItem extends BootstrapElement { /** * Bootstrap's accordion radius rules use `:first-of-type` / * `:last-of-type` against the `.accordion-item` div — but our * `.accordion-item` lives inside this component's shadow root, so it's * always the only `.accordion-item` in its scope and every item ends up * matching both pseudo-classes (every corner rounded, every border-top * present → doubled borders, no flush join, no shared corners). * * Override the inside-shadow rules so they're inert, then re-apply the * radius and border-collapse based on the HOST's position via * `:host(:first-child)` / `:host(:last-child)`. The host IS a sibling * of other `` elements inside ``, so * those structural pseudo-classes resolve correctly. */ static override styles = css` /* Disarm Bootstrap's intra-shadow first/last-of-type radius and * border-top rules. Each shadow root has exactly one .accordion-item * so :first-of-type and :last-of-type both match it, and every item * ends up rounded on every corner with its own top border. */ .accordion-item, .accordion-item:first-of-type, .accordion-item:last-of-type { border-radius: 0; } .accordion-item { /* No border-top by default — restored on the first host below. The * border-bottom stays so adjacent items share a single divider. */ border-top: 0; } .accordion-item:first-of-type > .accordion-header .accordion-button, .accordion-item:last-of-type > .accordion-header .accordion-button.collapsed, .accordion-item:last-of-type .accordion-collapse { border-radius: 0; } /* First item: top border + top corners rounded on the .accordion-item * box; the button echoes the inner radius on top. Specificity bumped * with the duplicated .accordion-item selector segment so these win * over Bootstrap's .accordion-item:first-of-type rules. */ :host(:first-child) .accordion-item { border-top: var(--bs-accordion-border-width) solid var(--bs-accordion-border-color); border-top-left-radius: var(--bs-accordion-border-radius); border-top-right-radius: var(--bs-accordion-border-radius); } :host(:first-child) .accordion-item .accordion-header .accordion-button { border-top-left-radius: var(--bs-accordion-inner-border-radius); border-top-right-radius: var(--bs-accordion-inner-border-radius); } /* Last item: bottom corners rounded on the .accordion-item, the * .accordion-collapse, and (when collapsed) the button. */ :host(:last-child) .accordion-item { border-bottom-left-radius: var(--bs-accordion-border-radius); border-bottom-right-radius: var(--bs-accordion-border-radius); } :host(:last-child) .accordion-item .accordion-collapse { border-bottom-left-radius: var(--bs-accordion-border-radius); border-bottom-right-radius: var(--bs-accordion-border-radius); } :host(:last-child:not([open])) .accordion-item .accordion-header .accordion-button.collapsed { border-bottom-left-radius: var(--bs-accordion-inner-border-radius); border-bottom-right-radius: var(--bs-accordion-inner-border-radius); } `; @property({ type: Boolean, reflect: true }) open = false; @property({ type: String }) heading?: string; @query('.accordion-collapse') private _panel!: HTMLElement; private _uid = `bs-ai-${++_id}`; private _toggle = () => { this.open = !this.open; this.dispatchEvent( new CustomEvent('bs-accordion-item-toggle', { bubbles: true, composed: true, detail: { open: this.open }, }), ); }; override updated(changed: Map) { if (changed.has('open') && this._panel) { if (this.open) { this._panel.style.height = `${this._panel.scrollHeight}px`; this._panel.addEventListener( 'transitionend', () => { this._panel.style.height = ''; }, { once: true }, ); } else { this._panel.style.height = `${this._panel.scrollHeight}px`; // eslint-disable-next-line @typescript-eslint/no-unused-expressions this._panel.offsetHeight; this._panel.style.height = '0px'; } } } override render() { const buttonClasses = classMap({ 'accordion-button': true, collapsed: !this.open, }); const collapseClasses = classMap({ 'accordion-collapse': true, collapse: true, show: this.open, }); const bodyId = `${this._uid}-body`; const headId = `${this._uid}-head`; return html`

`; } } defineElement('bs-accordion-item', BsAccordionItem); declare global { interface HTMLElementTagNameMap { 'bs-accordion-item': BsAccordionItem; } }