diff --git a/src/elements/button/common/button-common.ts b/src/elements/button/common/button-common.ts index bd5e98ef5c..31e8317447 100644 --- a/src/elements/button/common/button-common.ts +++ b/src/elements/button/common/button-common.ts @@ -3,8 +3,7 @@ import { property } from 'lit/decorators.js'; import { html } from 'lit/static-html.js'; import type { SbbActionBaseElement } from '../../core/base-elements.js'; -import { SbbSlotStateController } from '../../core/controllers.js'; -import { hostAttributes } from '../../core/decorators.js'; +import { hostAttributes, slotState } from '../../core/decorators.js'; import type { AbstractConstructor, SbbDisabledMixinType, @@ -33,6 +32,7 @@ export const SbbButtonCommonElementMixin = @@ -40,11 +40,6 @@ export const SbbButtonCommonElementMixin = ): Promise { super.willUpdate(changedProperties); diff --git a/src/elements/checkbox/checkbox/checkbox.ts b/src/elements/checkbox/checkbox/checkbox.ts index 9e0b72cc8c..890c24b3d0 100644 --- a/src/elements/checkbox/checkbox/checkbox.ts +++ b/src/elements/checkbox/checkbox/checkbox.ts @@ -1,15 +1,15 @@ import { LitElement, html, type CSSResultGroup, type TemplateResult } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { SbbSlotStateController } from '../../core/controllers.js'; +import { slotState } from '../../core/decorators.js'; import type { SbbIconPlacement } from '../../core/interfaces.js'; import { SbbIconNameMixin } from '../../icon.js'; import { SbbCheckboxCommonElementMixin, checkboxCommonStyle } from '../common.js'; -import '../../visual-checkbox.js'; - import checkboxStyle from './checkbox.scss?lit&inline'; +import '../../visual-checkbox.js'; + /** * It displays a checkbox enhanced with the SBB Design. * @@ -20,6 +20,7 @@ import checkboxStyle from './checkbox.scss?lit&inline'; * @event {InputEvent} input - Event fired on input. */ @customElement('sbb-checkbox') +@slotState() export class SbbCheckboxElement extends SbbCheckboxCommonElementMixin( SbbIconNameMixin(LitElement), ) { @@ -33,11 +34,6 @@ export class SbbCheckboxElement extends SbbCheckboxCommonElementMixin( @property({ attribute: 'icon-placement', reflect: true }) public iconPlacement: SbbIconPlacement = 'end'; - public constructor() { - super(); - new SbbSlotStateController(this); - } - protected override render(): TemplateResult { return html` diff --git a/src/elements/core/decorators.ts b/src/elements/core/decorators.ts index 7684efe83e..1d6b1f57ad 100644 --- a/src/elements/core/decorators.ts +++ b/src/elements/core/decorators.ts @@ -1 +1,2 @@ export * from './decorators/host-attributes.js'; +export * from './decorators/slot-state.js'; diff --git a/src/elements/core/decorators/host-attributes.ts b/src/elements/core/decorators/host-attributes.ts index 168581e485..e8956e82f3 100644 --- a/src/elements/core/decorators/host-attributes.ts +++ b/src/elements/core/decorators/host-attributes.ts @@ -22,10 +22,10 @@ function applyAttributes( * * @example * + * @customElement('my-element) * @hostAttributes({ * role: 'region' * }) - * @customElement('my-element) * export class MyElement extends LitElement { * ... * } diff --git a/src/elements/core/decorators/slot-state.ts b/src/elements/core/decorators/slot-state.ts new file mode 100644 index 0000000000..371a62559d --- /dev/null +++ b/src/elements/core/decorators/slot-state.ts @@ -0,0 +1,25 @@ +import { isServer, type ReactiveElement } from 'lit'; + +import { SbbSlotStateController } from '../controllers.js'; +import type { AbstractConstructor } from '../mixins.js'; + +/** + * Adds the {@link SbbSlotStateController} to the related element. + * + * @example + * + * @customElement('my-element) + * @slotState() + * export class MyElement extends LitElement { + * ... + * } + * + * @param attributes A record of attributes to apply to the element. + */ +export const slotState = () => (target: AbstractConstructor) => { + if (!isServer) { + (target as typeof ReactiveElement).addInitializer( + (instance: ReactiveElement) => new SbbSlotStateController(instance), + ); + } +}; diff --git a/src/elements/file-selector/file-selector.ts b/src/elements/file-selector/file-selector.ts index e8b506456f..ca17b5055a 100644 --- a/src/elements/file-selector/file-selector.ts +++ b/src/elements/file-selector/file-selector.ts @@ -6,7 +6,8 @@ import { html, unsafeStatic } from 'lit/static-html.js'; import type { SbbSecondaryButtonStaticElement } from '../button.js'; import { sbbInputModalityDetector } from '../core/a11y.js'; -import { SbbLanguageController, SbbSlotStateController } from '../core/controllers.js'; +import { SbbLanguageController } from '../core/controllers.js'; +import { slotState } from '../core/decorators.js'; import { EventEmitter } from '../core/eventing.js'; import { i18nFileSelectorButtonLabel, @@ -31,6 +32,7 @@ export type DOMEvent = globalThis.Event; * @event {CustomEvent} fileChanged - An event which is emitted each time the file list changes. */ @customElement('sbb-file-selector') +@slotState() export class SbbFileSelectorElement extends SbbDisabledMixin(LitElement) { public static override styles: CSSResultGroup = style; public static readonly events = { @@ -94,11 +96,6 @@ export class SbbFileSelectorElement extends SbbDisabledMixin(LitElement) { private _language = new SbbLanguageController(this); - public constructor() { - super(); - new SbbSlotStateController(this); - } - private _blockEvent(event: DragEvent): void { event.stopPropagation(); event.preventDefault(); diff --git a/src/elements/form-field/form-field/form-field.ts b/src/elements/form-field/form-field/form-field.ts index 5bf100eb4e..4fb655471c 100644 --- a/src/elements/form-field/form-field/form-field.ts +++ b/src/elements/form-field/form-field/form-field.ts @@ -4,20 +4,18 @@ import { customElement, property, state } from 'lit/decorators.js'; import type { SbbInputModality } from '../../core/a11y.js'; import { sbbInputModalityDetector } from '../../core/a11y.js'; -import { - SbbConnectedAbortController, - SbbLanguageController, - SbbSlotStateController, -} from '../../core/controllers.js'; +import { SbbConnectedAbortController, SbbLanguageController } from '../../core/controllers.js'; +import { slotState } from '../../core/decorators.js'; import { isFirefox, setOrRemoveAttribute } from '../../core/dom.js'; import { i18nOptional } from '../../core/i18n.js'; import { SbbNegativeMixin } from '../../core/mixins.js'; import { AgnosticMutationObserver } from '../../core/observers.js'; import type { SbbSelectElement } from '../../select.js'; -import '../../icon.js'; import style from './form-field.scss?lit&inline'; +import '../../icon.js'; + let nextId = 0; let nextFormFieldErrorId = 0; @@ -33,6 +31,7 @@ const supportedPopupTagNames = ['sbb-autocomplete', 'sbb-select']; * @slot error - Use this slot to render an error. */ @customElement('sbb-form-field') +@slotState() export class SbbFormFieldElement extends SbbNegativeMixin(LitElement) { public static override styles: CSSResultGroup = style; @@ -125,11 +124,6 @@ export class SbbFormFieldElement extends SbbNegativeMixin(LitElement) { private _inputAbortController = new AbortController(); - public constructor() { - super(); - new SbbSlotStateController(this); - } - public override connectedCallback(): void { super.connectedCallback(); const signal = this._abort.signal; diff --git a/src/elements/link-list/link-list.ts b/src/elements/link-list/link-list.ts index 75b65d5e03..d0c8d77c9a 100644 --- a/src/elements/link-list/link-list.ts +++ b/src/elements/link-list/link-list.ts @@ -2,7 +2,7 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit'; import { html, LitElement, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { SbbSlotStateController } from '../core/controllers.js'; +import { slotState } from '../core/decorators.js'; import type { SbbHorizontalFrom, SbbOrientation } from '../core/interfaces.js'; import { SbbNamedSlotListMixin, SbbNegativeMixin, type WithListChildren } from '../core/mixins.js'; import type { @@ -24,6 +24,7 @@ import '../title.js'; * @slot title - Use this slot to provide a title. */ @customElement('sbb-link-list') +@slotState() export class SbbLinkListElement extends SbbNegativeMixin( SbbNamedSlotListMixin< SbbBlockLinkElement | SbbBlockLinkButtonElement | SbbBlockLinkStaticElement, @@ -56,11 +57,6 @@ export class SbbLinkListElement extends SbbNegativeMixin( /** The orientation in which the list will be shown vertical or horizontal. */ @property({ reflect: true }) public orientation: SbbOrientation = 'vertical'; - public constructor() { - super(); - new SbbSlotStateController(this); - } - protected override willUpdate(changedProperties: PropertyValues>): void { super.willUpdate(changedProperties); diff --git a/src/elements/link/common/link-common.ts b/src/elements/link/common/link-common.ts index 29028e0c1c..e3314d4444 100644 --- a/src/elements/link/common/link-common.ts +++ b/src/elements/link/common/link-common.ts @@ -3,8 +3,7 @@ import { property } from 'lit/decorators.js'; import { html } from 'lit/static-html.js'; import type { SbbActionBaseElement } from '../../core/base-elements.js'; -import { SbbSlotStateController } from '../../core/controllers.js'; -import { hostAttributes } from '../../core/decorators.js'; +import { hostAttributes, slotState } from '../../core/decorators.js'; import { SbbNegativeMixin, type SbbNegativeMixinType, @@ -24,6 +23,7 @@ export const SbbLinkCommonElementMixin = & T => { @hostAttributes({ 'data-sbb-link': '' }) + @slotState() abstract class SbbLinkCommonElement extends SbbNegativeMixin(superClass) implements Partial @@ -36,11 +36,6 @@ export const SbbLinkCommonElementMixin = `; } diff --git a/src/elements/navigation/navigation-list/navigation-list.ts b/src/elements/navigation/navigation-list/navigation-list.ts index e9915993ea..c22af287ed 100644 --- a/src/elements/navigation/navigation-list/navigation-list.ts +++ b/src/elements/navigation/navigation-list/navigation-list.ts @@ -7,7 +7,7 @@ import { } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { SbbSlotStateController } from '../../core/controllers.js'; +import { slotState } from '../../core/decorators.js'; import { SbbNamedSlotListMixin, type WithListChildren } from '../../core/mixins.js'; import type { SbbNavigationButtonElement } from '../navigation-button.js'; import type { SbbNavigationLinkElement } from '../navigation-link.js'; @@ -21,6 +21,7 @@ import style from './navigation-list.scss?lit&inline'; * @slot label - Use this to provide a label element. */ @customElement('sbb-navigation-list') +@slotState() export class SbbNavigationListElement extends SbbNamedSlotListMixin< SbbNavigationButtonElement | SbbNavigationLinkElement, typeof LitElement @@ -36,11 +37,6 @@ export class SbbNavigationListElement extends SbbNamedSlotListMixin< */ @property({ reflect: true }) public label?: string; - public constructor() { - super(); - new SbbSlotStateController(this); - } - protected override willUpdate(changedProperties: PropertyValues>): void { super.willUpdate(changedProperties); diff --git a/src/elements/navigation/navigation-section/navigation-section.ts b/src/elements/navigation/navigation-section/navigation-section.ts index a3bb031f45..fafd3483f1 100644 --- a/src/elements/navigation/navigation-section/navigation-section.ts +++ b/src/elements/navigation/navigation-section/navigation-section.ts @@ -7,8 +7,8 @@ import { getFocusableElements, setModalityOnNextFocus, } from '../../core/a11y.js'; -import { SbbLanguageController, SbbSlotStateController } from '../../core/controllers.js'; -import { hostAttributes } from '../../core/decorators.js'; +import { SbbLanguageController } from '../../core/controllers.js'; +import { hostAttributes, slotState } from '../../core/decorators.js'; import { findReferencedElement, isBreakpoint, setOrRemoveAttribute } from '../../core/dom.js'; import { i18nGoBack } from '../../core/i18n.js'; import type { SbbOpenedClosedState } from '../../core/interfaces.js'; @@ -37,6 +37,7 @@ let nextId = 0; @hostAttributes({ slot: 'navigation-section', }) +@slotState() export class SbbNavigationSectionElement extends SbbUpdateSchedulerMixin(LitElement) { public static override styles: CSSResultGroup = style; @@ -91,11 +92,6 @@ export class SbbNavigationSectionElement extends SbbUpdateSchedulerMixin(LitElem private _windowEventsController!: AbortController; private _language = new SbbLanguageController(this); - public constructor() { - super(); - new SbbSlotStateController(this); - } - /** * Opens the navigation section on trigger click. */ diff --git a/src/elements/notification/notification.ts b/src/elements/notification/notification.ts index 90156f2091..4c70978cbc 100644 --- a/src/elements/notification/notification.ts +++ b/src/elements/notification/notification.ts @@ -2,19 +2,21 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit'; import { html, LitElement, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { SbbLanguageController, SbbSlotStateController } from '../core/controllers.js'; +import { SbbLanguageController } from '../core/controllers.js'; +import { slotState } from '../core/decorators.js'; import { EventEmitter } from '../core/eventing.js'; import { i18nCloseNotification } from '../core/i18n.js'; import type { SbbOpenedClosedState } from '../core/interfaces.js'; import { AgnosticResizeObserver } from '../core/observers.js'; import type { SbbTitleLevel } from '../title.js'; + +import style from './notification.scss?lit&inline'; + import '../button/secondary-button.js'; import '../divider.js'; import '../icon.js'; import '../title.js'; -import style from './notification.scss?lit&inline'; - const notificationTypes = new Map([ ['info', 'circle-information-small'], ['success', 'circle-tick-small'], @@ -37,6 +39,7 @@ const DEBOUNCE_TIME = 150; * See style section for more information. */ @customElement('sbb-notification') +@slotState() export class SbbNotificationElement extends LitElement { // FIXME inheriting from SbbOpenCloseBaseElement requires: https://github.com/open-wc/custom-elements-manifest/issues/253 public static override styles: CSSResultGroup = style; @@ -117,11 +120,6 @@ export class SbbNotificationElement extends LitElement { SbbNotificationElement.events.didClose, ); - public constructor() { - super(); - new SbbSlotStateController(this); - } - private _open(): void { if (this._state === 'closed') { this._state = 'opening'; diff --git a/src/elements/option/option/option.ts b/src/elements/option/option/option.ts index df5a64b6ba..5cf7d14222 100644 --- a/src/elements/option/option/option.ts +++ b/src/elements/option/option/option.ts @@ -8,8 +8,8 @@ import { } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { SbbConnectedAbortController, SbbSlotStateController } from '../../core/controllers.js'; -import { hostAttributes } from '../../core/decorators.js'; +import { SbbConnectedAbortController } from '../../core/controllers.js'; +import { hostAttributes, slotState } from '../../core/decorators.js'; import { isAndroid, isSafari, setOrRemoveAttribute } from '../../core/dom.js'; import { EventEmitter } from '../../core/eventing.js'; import { SbbDisabledMixin, SbbHydrationMixin } from '../../core/mixins.js'; @@ -51,6 +51,7 @@ export type SbbOptionVariant = 'autocomplete' | 'select' | null; @hostAttributes({ role: 'option', }) +@slotState() export class SbbOptionElement extends SbbDisabledMixin( SbbIconNameMixin(SbbHydrationMixin(LitElement)), ) { @@ -140,7 +141,6 @@ export class SbbOptionElement extends SbbDisabledMixin( public constructor() { super(); - new SbbSlotStateController(this); if (inertAriaGroups) { if (this.hydrationRequired) { diff --git a/src/elements/radio-button/radio-button-group/radio-button-group.ts b/src/elements/radio-button/radio-button-group/radio-button-group.ts index 79c32e6be2..5e8218b287 100644 --- a/src/elements/radio-button/radio-button-group/radio-button-group.ts +++ b/src/elements/radio-button/radio-button-group/radio-button-group.ts @@ -3,8 +3,8 @@ import { LitElement, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { getNextElementIndex, isArrowKeyPressed } from '../../core/a11y.js'; -import { SbbConnectedAbortController, SbbSlotStateController } from '../../core/controllers.js'; -import { hostAttributes } from '../../core/decorators.js'; +import { SbbConnectedAbortController } from '../../core/controllers.js'; +import { hostAttributes, slotState } from '../../core/decorators.js'; import { EventEmitter } from '../../core/eventing.js'; import type { SbbHorizontalFrom, SbbOrientation, SbbStateChange } from '../../core/interfaces.js'; import { SbbDisabledMixin } from '../../core/mixins.js'; @@ -32,6 +32,7 @@ export type SbbRadioButtonGroupEventDetail = { @hostAttributes({ role: 'radiogroup', }) +@slotState() export class SbbRadioButtonGroupElement extends SbbDisabledMixin(LitElement) { public static override styles: CSSResultGroup = style; public static readonly events = { @@ -128,11 +129,6 @@ export class SbbRadioButtonGroupElement extends SbbDisabledMixin(LitElement) { SbbRadioButtonGroupElement.events.input, ); - public constructor() { - super(); - new SbbSlotStateController(this); - } - public override connectedCallback(): void { super.connectedCallback(); const signal = this._abort.signal; diff --git a/src/elements/radio-button/radio-button-panel/radio-button-panel.ts b/src/elements/radio-button/radio-button-panel/radio-button-panel.ts index ab7d784ac6..b027cf7d1f 100644 --- a/src/elements/radio-button/radio-button-panel/radio-button-panel.ts +++ b/src/elements/radio-button/radio-button-panel/radio-button-panel.ts @@ -8,7 +8,7 @@ import { } from 'lit'; import { customElement } from 'lit/decorators.js'; -import { SbbSlotStateController } from '../../core/controllers.js'; +import { slotState } from '../../core/decorators.js'; import { panelCommonStyle, SbbPanelMixin, SbbUpdateSchedulerMixin } from '../../core/mixins.js'; import { radioButtonCommonStyle, SbbRadioButtonCommonElementMixin } from '../common.js'; @@ -24,6 +24,7 @@ import '../../screen-reader-only.js'; * @slot badge - Use this slot to provide a `sbb-card-badge` (optional). */ @customElement('sbb-radio-button-panel') +@slotState() export class SbbRadioButtonPanelElement extends SbbPanelMixin( SbbRadioButtonCommonElementMixin(SbbUpdateSchedulerMixin(LitElement)), ) { @@ -35,11 +36,6 @@ export class SbbRadioButtonPanelElement extends SbbPanelMixin( panelConnected: 'panelConnected', } as const; - public constructor() { - super(); - new SbbSlotStateController(this); - } - protected override async willUpdate(changedProperties: PropertyValues): Promise { super.willUpdate(changedProperties); diff --git a/src/elements/radio-button/radio-button/radio-button.ts b/src/elements/radio-button/radio-button/radio-button.ts index 82e5a2194e..e7583306d8 100644 --- a/src/elements/radio-button/radio-button/radio-button.ts +++ b/src/elements/radio-button/radio-button/radio-button.ts @@ -2,7 +2,7 @@ import type { CSSResultGroup, TemplateResult } from 'lit'; import { LitElement, html, nothing } from 'lit'; import { customElement } from 'lit/decorators.js'; -import { SbbSlotStateController } from '../../core/controllers.js'; +import { slotState } from '../../core/decorators.js'; import { SbbRadioButtonCommonElementMixin, radioButtonCommonStyle } from '../common.js'; import radioButtonStyle from './radio-button.scss?lit&inline'; @@ -13,17 +13,13 @@ import radioButtonStyle from './radio-button.scss?lit&inline'; * @slot - Use the unnamed slot to add content to the radio label. */ @customElement('sbb-radio-button') +@slotState() export class SbbRadioButtonElement extends SbbRadioButtonCommonElementMixin(LitElement) { public static override styles: CSSResultGroup = [radioButtonCommonStyle, radioButtonStyle]; public static readonly events = { stateChange: 'stateChange', } as const; - public constructor() { - super(); - new SbbSlotStateController(this); - } - protected override render(): TemplateResult { return html`