import { html, nothing } from 'lit'; import { property } from 'lit/decorators.js'; import { BootstrapElement, defineElement, type Variant } from '@bootstrap-wc/core'; /** * `` — item inside ``. The host carries * `.list-group-item` (+ variant / active / disabled modifiers) so Bootstrap's * sibling selectors like `.list-group-item + .list-group-item` match across * the slot boundary. * * Semantics adapt to the parent ``: * - `as="ul"` (default): host announces as a list item / link if `href`. * - `as="div"` (rich link list): host announces as a link by default * (matches ``) and is focusable. * * When `href` is set the host navigates on click. */ export class BsListGroupItem extends BootstrapElement { @property({ type: Boolean, reflect: true }) active = false; @property({ type: Boolean, reflect: true }) disabled = false; @property({ type: String }) variant?: Variant; @property({ type: String }) href?: string; @property({ type: Boolean }) action = false; /** Resolved from the closest `` parent (cached for render). */ private _isDivMode(): boolean { const parent = this.closest('bs-list-group') as (HTMLElement & { as?: string }) | null; return parent?.as === 'div'; } override connectedCallback(): void { super.connectedCallback(); const isLink = !!this.href || this._isDivMode() || this.action; if (!this.hasAttribute('role')) { this.setAttribute('role', isLink ? 'link' : 'listitem'); } if (isLink && !this.hasAttribute('tabindex')) { this.tabIndex = this.disabled ? -1 : 0; } this.addEventListener('click', this._onClick); this.addEventListener('keydown', this._onKeydown); } override disconnectedCallback(): void { super.disconnectedCallback(); this.removeEventListener('click', this._onClick); this.removeEventListener('keydown', this._onKeydown); } override updated(changed: Map): void { super.updated(changed); if (changed.has('active')) { // Only manage aria-current when `active` toggled — don't stomp an // author-set aria-current value when active was never true. if (this.active) this.setAttribute('aria-current', 'true'); else if (changed.get('active') === true) this.removeAttribute('aria-current'); } if (changed.has('disabled')) { this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false'); if (this.hasAttribute('tabindex')) this.tabIndex = this.disabled ? -1 : 0; } } protected override hostClasses(): string { const parts = ['list-group-item']; if (this.action || this.href || this._isDivMode()) parts.push('list-group-item-action'); if (this.active) parts.push('active'); if (this.disabled) parts.push('disabled'); if (this.variant) parts.push(`list-group-item-${this.variant}`); return parts.join(' '); } private _onClick = (ev: MouseEvent) => { if (this.disabled) { ev.preventDefault(); return; } if (this.href && ev.target === this) window.location.href = this.href; }; private _onKeydown = (ev: KeyboardEvent) => { if (this.disabled) return; if ((ev.key === 'Enter' || ev.key === ' ') && this.href) { ev.preventDefault(); this.click(); } }; override render() { return html`${nothing}`; } } defineElement('bs-list-group-item', BsListGroupItem); declare global { interface HTMLElementTagNameMap { 'bs-list-group-item': BsListGroupItem; } }