From 52b0401a97647fe6c68a7010a5366ececf0a0cf6 Mon Sep 17 00:00:00 2001 From: Lukas Spirig Date: Thu, 19 Dec 2024 11:39:28 +0100 Subject: [PATCH 1/3] refactor: move event handler to constructors This is the recommended approach for event handlers for web components. --- src/elements/accordion/accordion.ts | 12 ++--- src/elements/alert/alert-group/alert-group.ts | 12 ++--- .../autocomplete-grid/autocomplete-grid.ts | 11 ++--- .../autocomplete/autocomplete-base-element.ts | 1 + src/elements/autocomplete/autocomplete.ts | 11 ++--- .../breadcrumb-group/breadcrumb-group.ts | 14 +++--- .../checkbox/checkbox-group/checkbox-group.ts | 8 ++-- .../controllers/connected-abort-controller.ts | 4 ++ .../mixins/form-associated-checkbox-mixin.ts | 18 ++----- .../form-associated-radio-button-mixin.ts | 6 +-- .../datepicker/common/datepicker-button.ts | 10 ++-- .../datepicker-toggle/datepicker-toggle.ts | 18 +++---- .../datepicker/datepicker/datepicker.ts | 11 +++-- src/elements/dialog/dialog/dialog.ts | 19 +++----- .../expansion-panel-header.ts | 13 ++--- .../expansion-panel/expansion-panel.ts | 9 ++-- src/elements/flip-card/flip-card/flip-card.ts | 24 ++++------ .../form-field-clear/form-field-clear.ts | 10 ++-- src/elements/menu/menu/menu.ts | 21 +++++---- .../common/navigation-action-common.ts | 33 ++++++------- .../navigation-section/navigation-section.ts | 8 ++-- .../navigation/navigation/navigation.ts | 15 ++---- .../option/option/option-base-element.ts | 11 ++--- src/elements/overlay/overlay-base-element.ts | 1 + .../common/radio-button-common.ts | 10 ++-- .../radio-button-group/radio-button-group.ts | 12 ++--- src/elements/select/select.ts | 47 +++++++------------ .../selection-expansion-panel.ts | 12 ++--- src/elements/slider/slider.ts | 6 +-- src/elements/stepper/step-label/step-label.ts | 21 ++++----- src/elements/stepper/step/step.ts | 9 ++-- src/elements/stepper/stepper/stepper.ts | 21 +++++---- src/elements/tabs/tab-group/tab-group.ts | 9 ++-- src/elements/tag/tag/tag.ts | 7 +-- src/elements/toast/toast.ts | 11 +++-- .../toggle/toggle-option/toggle-option.ts | 17 ++++--- src/elements/toggle/toggle/toggle.ts | 10 ++-- .../train/train-formation/train-formation.ts | 12 ++--- .../boilerplate/component.ts | 9 ---- 39 files changed, 221 insertions(+), 292 deletions(-) diff --git a/src/elements/accordion/accordion.ts b/src/elements/accordion/accordion.ts index 92c2289e43..75eead73c0 100644 --- a/src/elements/accordion/accordion.ts +++ b/src/elements/accordion/accordion.ts @@ -2,7 +2,6 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit'; import { html, LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { SbbConnectedAbortController } from '../core/controllers.js'; import { forceType, handleDistinctChange } from '../core/decorators.js'; import { isLean } from '../core/dom.js'; import { isEventPrevented } from '../core/eventing.js'; @@ -43,12 +42,9 @@ class SbbAccordionElement extends SbbHydrationMixin(LitElement) { @property({ type: Boolean }) public accessor multi: boolean = false; - private _abort = new SbbConnectedAbortController(this); - - public override connectedCallback(): void { - super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener( + public constructor() { + super(); + this.addEventListener?.( 'willOpen', async (e: CustomEvent) => { if (!(await isEventPrevented(e))) { @@ -56,7 +52,7 @@ class SbbAccordionElement extends SbbHydrationMixin(LitElement) { } }, { - signal, + // We use capture here, because willOpen does not bubble. capture: true, }, ); diff --git a/src/elements/alert/alert-group/alert-group.ts b/src/elements/alert/alert-group/alert-group.ts index 2eb6d497af..7673bc23df 100644 --- a/src/elements/alert/alert-group/alert-group.ts +++ b/src/elements/alert/alert-group/alert-group.ts @@ -3,7 +3,6 @@ import { LitElement, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { html, unsafeStatic } from 'lit/static-html.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { forceType } from '../../core/decorators.js'; import { EventEmitter, isEventPrevented } from '../../core/eventing.js'; import { SbbHydrationMixin } from '../../core/mixins.js'; @@ -51,12 +50,9 @@ class SbbAlertGroupElement extends SbbHydrationMixin(LitElement) { /** Emits when `sbb-alert-group` becomes empty. */ private _empty: EventEmitter = new EventEmitter(this, SbbAlertGroupElement.events.empty); - private _abort = new SbbConnectedAbortController(this); - - public override connectedCallback(): void { - super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener( + public constructor() { + super(); + this.addEventListener?.( 'didClose', async (e: CustomEvent) => { if (!(await isEventPrevented(e))) { @@ -64,7 +60,7 @@ class SbbAlertGroupElement extends SbbHydrationMixin(LitElement) { } }, { - signal, + // We use capture here, because didClose does not bubble. capture: true, }, ); diff --git a/src/elements/autocomplete-grid/autocomplete-grid/autocomplete-grid.ts b/src/elements/autocomplete-grid/autocomplete-grid/autocomplete-grid.ts index 2fdfff6298..5bda2d8719 100644 --- a/src/elements/autocomplete-grid/autocomplete-grid/autocomplete-grid.ts +++ b/src/elements/autocomplete-grid/autocomplete-grid/autocomplete-grid.ts @@ -54,13 +54,10 @@ class SbbAutocompleteGridElement extends SbbAutocompleteBaseElement { ); } - public override connectedCallback(): void { - super.connectedCallback(); - const signal = this.abort.signal; - this.addEventListener( - 'autocompleteOptionSelectionChange', - (e: CustomEvent) => this.onOptionSelected(e), - { signal }, + public constructor() { + super(); + this.addEventListener?.('autocompleteOptionSelectionChange', (e: CustomEvent) => + this.onOptionSelected(e), ); } diff --git a/src/elements/autocomplete/autocomplete-base-element.ts b/src/elements/autocomplete/autocomplete-base-element.ts index 07f456bb37..31850bfb01 100644 --- a/src/elements/autocomplete/autocomplete-base-element.ts +++ b/src/elements/autocomplete/autocomplete-base-element.ts @@ -70,6 +70,7 @@ export abstract class SbbAutocompleteBaseElement extends SbbNegativeMixin( protected abstract overlayId: string; protected abstract panelRole: string; + /** @deprecated No longer used internally. */ protected abort = new SbbConnectedAbortController(this); private _overlay!: HTMLElement; private _optionContainer!: HTMLElement; diff --git a/src/elements/autocomplete/autocomplete.ts b/src/elements/autocomplete/autocomplete.ts index 27a09dd50a..73bd80444e 100644 --- a/src/elements/autocomplete/autocomplete.ts +++ b/src/elements/autocomplete/autocomplete.ts @@ -42,13 +42,10 @@ class SbbAutocompleteElement extends SbbAutocompleteBaseElement { return Array.from(this.querySelectorAll?.('sbb-option') ?? []); } - public override connectedCallback(): void { - super.connectedCallback(); - const signal = this.abort.signal; - this.addEventListener( - 'optionSelectionChange', - (e: CustomEvent) => this.onOptionSelected(e), - { signal }, + public constructor() { + super(); + this.addEventListener?.('optionSelectionChange', (e: CustomEvent) => + this.onOptionSelected(e), ); } diff --git a/src/elements/breadcrumb/breadcrumb-group/breadcrumb-group.ts b/src/elements/breadcrumb/breadcrumb-group/breadcrumb-group.ts index 3e36321920..d77a91ef35 100644 --- a/src/elements/breadcrumb/breadcrumb-group/breadcrumb-group.ts +++ b/src/elements/breadcrumb/breadcrumb-group/breadcrumb-group.ts @@ -14,7 +14,7 @@ import { isArrowKeyPressed, sbbInputModalityDetector, } from '../../core/a11y.js'; -import { SbbConnectedAbortController, SbbLanguageController } from '../../core/controllers.js'; +import { SbbLanguageController } from '../../core/controllers.js'; import { hostAttributes } from '../../core/decorators.js'; import { setOrRemoveAttribute } from '../../core/dom.js'; import { i18nBreadcrumbEllipsisButtonLabel } from '../../core/i18n.js'; @@ -58,10 +58,14 @@ class SbbBreadcrumbGroupElement extends SbbNamedSlotListMixin< skipInitial: true, callback: () => this._evaluateCollapsedState(), }); - private _abort = new SbbConnectedAbortController(this); private _language = new SbbLanguageController(this); private _markForFocus = false; + public constructor() { + super(); + this.addEventListener?.('keydown', (e) => this._handleKeyDown(e)); + } + private _handleKeyDown(evt: KeyboardEvent): void { if ( !this.listChildren.length || @@ -79,12 +83,6 @@ class SbbBreadcrumbGroupElement extends SbbNamedSlotListMixin< } } - public override connectedCallback(): void { - super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('keydown', (e) => this._handleKeyDown(e), { signal }); - } - protected override firstUpdated(changedProperties: PropertyValues): void { super.firstUpdated(changedProperties); diff --git a/src/elements/checkbox/checkbox-group/checkbox-group.ts b/src/elements/checkbox/checkbox-group/checkbox-group.ts index c68d3bd13b..b3994d213f 100644 --- a/src/elements/checkbox/checkbox-group/checkbox-group.ts +++ b/src/elements/checkbox/checkbox-group/checkbox-group.ts @@ -3,7 +3,6 @@ import { LitElement, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { getNextElementIndex, interactivityChecker, isArrowKeyPressed } from '../../core/a11y.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { forceType, slotState } from '../../core/decorators.js'; import { isLean } from '../../core/dom.js'; import type { SbbHorizontalFrom, SbbOrientation } from '../../core/interfaces.js'; @@ -54,12 +53,13 @@ class SbbCheckboxGroupElement extends SbbDisabledMixin(LitElement) { ); } - private _abort: SbbConnectedAbortController = new SbbConnectedAbortController(this); + public constructor() { + super(); + this.addEventListener?.('keydown', (e) => this._handleKeyDown(e)); + } public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('keydown', (e) => this._handleKeyDown(e), { signal }); this.toggleAttribute( 'data-has-panel', !!this.querySelector?.('sbb-selection-expansion-panel, sbb-checkbox-panel'), diff --git a/src/elements/core/controllers/connected-abort-controller.ts b/src/elements/core/controllers/connected-abort-controller.ts index 441d03c41f..e5ce1bd5b4 100644 --- a/src/elements/core/controllers/connected-abort-controller.ts +++ b/src/elements/core/controllers/connected-abort-controller.ts @@ -1,5 +1,9 @@ import type { ReactiveController, ReactiveControllerHost } from 'lit'; +/** + * @deprecated No replacement should be necessary, as this was intended + * for event listeners, which should be applied in the constructor. + */ export class SbbConnectedAbortController implements ReactiveController { private _abortController?: AbortController = new AbortController(); diff --git a/src/elements/core/mixins/form-associated-checkbox-mixin.ts b/src/elements/core/mixins/form-associated-checkbox-mixin.ts index 05f99dc8f8..c4c0284b03 100644 --- a/src/elements/core/mixins/form-associated-checkbox-mixin.ts +++ b/src/elements/core/mixins/form-associated-checkbox-mixin.ts @@ -100,22 +100,10 @@ export const SbbFormAssociatedCheckboxMixin = super(); /** @internal */ this.internals.role = 'checkbox'; - } - - public override connectedCallback(): void { - super.connectedCallback(); - - this.addEventListener('click', this._handleUserInteraction); - this.addEventListener('keydown', preventScrollOnSpacebarPress); - this.addEventListener('keyup', this._handleKeyboardInteraction); - } - - public override disconnectedCallback(): void { - super.disconnectedCallback(); - this.removeEventListener('click', this._handleUserInteraction); - this.removeEventListener('keydown', preventScrollOnSpacebarPress); - this.removeEventListener('keyup', this._handleKeyboardInteraction); + this.addEventListener?.('click', this._handleUserInteraction); + this.addEventListener?.('keydown', preventScrollOnSpacebarPress); + this.addEventListener?.('keyup', this._handleKeyboardInteraction); } public override attributeChangedCallback( diff --git a/src/elements/core/mixins/form-associated-radio-button-mixin.ts b/src/elements/core/mixins/form-associated-radio-button-mixin.ts index 0a0eb4e112..e230832767 100644 --- a/src/elements/core/mixins/form-associated-radio-button-mixin.ts +++ b/src/elements/core/mixins/form-associated-radio-button-mixin.ts @@ -36,6 +36,7 @@ export declare class SbbFormAssociatedRadioButtonMixinType public required: boolean; protected associatedRadioButtons?: Set; + /** @deprecated No longer used internally. */ protected abort: SbbConnectedAbortController; public formResetCallback(): void; @@ -76,6 +77,7 @@ export const SbbFormAssociatedRadioButtonMixin = this._handleArrowKeyDown(e)); } public override connectedCallback(): void { super.connectedCallback(); this._connectToRegistry(); - this.addEventListener('keydown', (e) => this._handleArrowKeyDown(e), { - signal: this.abort.signal, - }); } public override disconnectedCallback(): void { diff --git a/src/elements/datepicker/common/datepicker-button.ts b/src/elements/datepicker/common/datepicker-button.ts index d691ef95c7..e7519120e8 100644 --- a/src/elements/datepicker/common/datepicker-button.ts +++ b/src/elements/datepicker/common/datepicker-button.ts @@ -3,7 +3,7 @@ import { property, state } from 'lit/decorators.js'; import { SbbButtonBaseElement } from '../../core/base-elements.js'; import { readConfig } from '../../core/config.js'; -import { SbbConnectedAbortController, SbbLanguageController } from '../../core/controllers.js'; +import { SbbLanguageController } from '../../core/controllers.js'; import { type DateAdapter, defaultDateAdapter } from '../../core/datetime.js'; import { i18nToday } from '../../core/i18n.js'; import { SbbNegativeMixin } from '../../core/mixins.js'; @@ -35,17 +35,21 @@ export abstract class SbbDatepickerButton extends SbbNegativeMixin(Sbb protected datePickerElement?: SbbDatepickerElement | null = null; private _dateAdapter: DateAdapter = readConfig().datetime?.dateAdapter ?? defaultDateAdapter; private _datePickerController!: AbortController; - private _abort = new SbbConnectedAbortController(this); private _language = new SbbLanguageController(this).withHandler(() => this._setAriaLabel()); protected abstract iconName: string; protected abstract i18nOffBoundaryDay: Record; protected abstract i18nSelectOffBoundaryDay: (_currentDate: string) => Record; + + public constructor() { + super(); + this.addEventListener?.('click', () => this._handleClick()); + } + protected abstract findAvailableDate(_date: T): T; public override connectedCallback(): void { super.connectedCallback(); - this.addEventListener('click', () => this._handleClick(), { signal: this._abort.signal }); this._syncUpstreamProperties(); if (!this.datePicker) { this._init(); diff --git a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts index 92d6706f49..079d245c45 100644 --- a/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts +++ b/src/elements/datepicker/datepicker-toggle/datepicker-toggle.ts @@ -6,7 +6,7 @@ import { ref } from 'lit/directives/ref.js'; import type { SbbMiniButtonElement } from '../../button/mini-button.js'; import type { CalendarView, SbbCalendarElement } from '../../calendar.js'; import { sbbInputModalityDetector } from '../../core/a11y.js'; -import { SbbConnectedAbortController, SbbLanguageController } from '../../core/controllers.js'; +import { SbbLanguageController } from '../../core/controllers.js'; import { hostAttributes } from '../../core/decorators.js'; import { i18nShowCalendar } from '../../core/i18n.js'; import { SbbHydrationMixin, SbbNegativeMixin } from '../../core/mixins.js'; @@ -54,10 +54,14 @@ class SbbDatepickerToggleElement extends SbbNegativeMixin(SbbHydration private _popoverElement!: SbbPopoverElement; private _datePickerController!: AbortController; private _language = new SbbLanguageController(this); - private _abort = new SbbConnectedAbortController(this); public constructor() { super(); + this.addEventListener?.('click', (event) => { + if (event.composedPath()[0] === this) { + this.open(); + } + }); if (!isServer) { this.hydrationComplete.then(() => (this._renderCalendar = true)); } @@ -83,16 +87,6 @@ class SbbDatepickerToggleElement extends SbbNegativeMixin(SbbHydration if (formField) { this.negative = formField.hasAttribute('negative'); } - - this.addEventListener( - 'click', - (event) => { - if (event.composedPath()[0] === this) { - this.open(); - } - }, - { signal: this._abort.signal }, - ); } public override willUpdate(changedProperties: PropertyValues): void { diff --git a/src/elements/datepicker/datepicker/datepicker.ts b/src/elements/datepicker/datepicker/datepicker.ts index 1e23d0c3dc..248f8a2f64 100644 --- a/src/elements/datepicker/datepicker/datepicker.ts +++ b/src/elements/datepicker/datepicker/datepicker.ts @@ -9,7 +9,7 @@ import { import { customElement, property, state } from 'lit/decorators.js'; import { readConfig } from '../../core/config.js'; -import { SbbConnectedAbortController, SbbLanguageController } from '../../core/controllers.js'; +import { SbbLanguageController } from '../../core/controllers.js'; import { type DateAdapter, defaultDateAdapter } from '../../core/datetime.js'; import { forceType } from '../../core/decorators.js'; import { findInput, findReferencedElement } from '../../core/dom.js'; @@ -157,7 +157,6 @@ class SbbDatepickerElement extends LitElement { private _dateAdapter: DateAdapter = readConfig().datetime?.dateAdapter ?? defaultDateAdapter; - private _abort = new SbbConnectedAbortController(this); private _language = new SbbLanguageController(this).withHandler(() => { if (this._inputElement) { if (this._inputElementPlaceholderMutable) { @@ -169,11 +168,13 @@ class SbbDatepickerElement extends LitElement { } }); + public constructor() { + super(); + this.addEventListener?.('datepickerControlRegistered', () => this._emitInputUpdated()); + } + public override connectedCallback(): void { super.connectedCallback(); - this.addEventListener('datepickerControlRegistered', () => this._emitInputUpdated(), { - signal: this._abort.signal, - }); this._attachInput(); if (this._inputElement) { this._emitInputUpdated(); diff --git a/src/elements/dialog/dialog/dialog.ts b/src/elements/dialog/dialog/dialog.ts index a84dd0a41e..2605d620fb 100644 --- a/src/elements/dialog/dialog/dialog.ts +++ b/src/elements/dialog/dialog/dialog.ts @@ -56,6 +56,13 @@ class SbbDialogElement extends SbbOverlayBaseElement { private _dialogId = `sbb-dialog-${nextId++}`; protected closeAttribute: string = 'sbb-dialog-close'; + public constructor() { + super(); + // Close dialog on backdrop click + this.addEventListener?.('pointerdown', this._pointerDownListener); + this.addEventListener?.('pointerup', this._closeOnBackdropClick); + } + /** Opens the component. */ public open(): void { if (this.state !== 'closed') { @@ -133,18 +140,6 @@ class SbbDialogElement extends SbbOverlayBaseElement { this.focusHandler.trap(this); } - public override connectedCallback(): void { - super.connectedCallback(); - - // Close dialog on backdrop click - this.addEventListener('pointerdown', this._pointerDownListener, { - signal: this.overlayController.signal, - }); - this.addEventListener('pointerup', this._closeOnBackdropClick, { - signal: this.overlayController.signal, - }); - } - protected override firstUpdated(changedProperties: PropertyValues): void { // Synchronize the negative state before the first opening to avoid a possible color flash if it is negative. this._dialogTitleElement = this.querySelector('sbb-dialog-title')!; diff --git a/src/elements/expansion-panel/expansion-panel-header/expansion-panel-header.ts b/src/elements/expansion-panel/expansion-panel-header/expansion-panel-header.ts index 4a7e710ec8..64affe8ee7 100644 --- a/src/elements/expansion-panel/expansion-panel-header/expansion-panel-header.ts +++ b/src/elements/expansion-panel/expansion-panel-header/expansion-panel-header.ts @@ -4,7 +4,6 @@ import { customElement } from 'lit/decorators.js'; import { SbbButtonBaseElement } from '../../core/base-elements.js'; import { SbbMediaQueryHover, - SbbConnectedAbortController, SbbMediaMatcherController, SbbSlotStateController, } from '../../core/controllers.js'; @@ -44,7 +43,6 @@ class SbbExpansionPanelHeaderElement extends SbbDisabledTabIndexActionMixin( bubbles: true, }, ); - private _abort = new SbbConnectedAbortController(this); private _namedSlots = new SbbSlotStateController(this, () => this._setDataIconAttribute()); private _mediaMatcher = new SbbMediaMatcherController(this, { [SbbMediaQueryHover]: (m) => (this._isHover = m), @@ -52,12 +50,11 @@ class SbbExpansionPanelHeaderElement extends SbbDisabledTabIndexActionMixin( private _isHover: boolean = this._mediaMatcher.matches(SbbMediaQueryHover) ?? false; - public override connectedCallback(): void { - super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('click', () => this._emitExpandedEvent(), { signal }); - this.addEventListener('mouseenter', () => this._onMouseMovement(true), { signal }); - this.addEventListener('mouseleave', () => this._onMouseMovement(false), { signal }); + public constructor() { + super(); + this.addEventListener?.('click', () => this._emitExpandedEvent()); + this.addEventListener?.('mouseenter', () => this._onMouseMovement(true)); + this.addEventListener?.('mouseleave', () => this._onMouseMovement(false)); } private _emitExpandedEvent(): void { diff --git a/src/elements/expansion-panel/expansion-panel/expansion-panel.ts b/src/elements/expansion-panel/expansion-panel/expansion-panel.ts index ee0d18f70e..c474133f9f 100644 --- a/src/elements/expansion-panel/expansion-panel/expansion-panel.ts +++ b/src/elements/expansion-panel/expansion-panel/expansion-panel.ts @@ -3,7 +3,6 @@ import { LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { html, unsafeStatic } from 'lit/static-html.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { forceType } from '../../core/decorators.js'; import { isLean, isZeroAnimationDuration } from '../../core/dom.js'; import { EventEmitter } from '../../core/eventing.js'; @@ -117,13 +116,15 @@ class SbbExpansionPanelElement extends SbbHydrationMixin(LitElement) { private _progressiveId = `-${++nextId}`; private _headerRef?: SbbExpansionPanelHeaderElement; private _contentRef?: SbbExpansionPanelContentElement; - private _abort = new SbbConnectedAbortController(this); private _initialized: boolean = false; + public constructor() { + super(); + this.addEventListener?.('toggleExpanded', () => this._toggleExpanded()); + } + public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('toggleExpanded', () => this._toggleExpanded(), { signal }); this.toggleAttribute('data-accordion', !!this.closest?.('sbb-accordion')); } diff --git a/src/elements/flip-card/flip-card/flip-card.ts b/src/elements/flip-card/flip-card/flip-card.ts index c225fa1f46..6ca142e5cb 100644 --- a/src/elements/flip-card/flip-card/flip-card.ts +++ b/src/elements/flip-card/flip-card/flip-card.ts @@ -4,7 +4,7 @@ import { customElement, property, state } from 'lit/decorators.js'; import { until } from 'lit/directives/until.js'; import { IS_FOCUSABLE_QUERY } from '../../core/a11y.js'; -import { SbbConnectedAbortController, SbbLanguageController } from '../../core/controllers.js'; +import { SbbLanguageController } from '../../core/controllers.js'; import { forceType } from '../../core/decorators.js'; import { EventEmitter } from '../../core/eventing.js'; import { i18nFlipCard, i18nReverseCard } from '../../core/i18n.js'; @@ -66,7 +66,6 @@ class SbbFlipCardElement extends SbbHydrationMixin(LitElement) { /** Whether the card is flipped or not. */ @state() private accessor _flipped = false; - private _abort = new SbbConnectedAbortController(this); private _language = new SbbLanguageController(this); private _cardDetailsResizeObserver = new ResizeController(this, { target: null, @@ -74,20 +73,13 @@ class SbbFlipCardElement extends SbbHydrationMixin(LitElement) { callback: () => this._setCardDetailsHeight(), }); - public override connectedCallback(): void { - super.connectedCallback(); - this.addEventListener( - 'click', - (event: Event) => { - if ( - event.target === this || - !(event.target as HTMLElement)?.matches?.(IS_FOCUSABLE_QUERY) - ) { - this.toggle(); - } - }, - { signal: this._abort.signal }, - ); + public constructor() { + super(); + this.addEventListener?.('click', (event: Event) => { + if (event.target === this || !(event.target as HTMLElement)?.matches?.(IS_FOCUSABLE_QUERY)) { + this.toggle(); + } + }); } /** Toggles the state of the sbb-flip-card. */ diff --git a/src/elements/form-field/form-field-clear/form-field-clear.ts b/src/elements/form-field/form-field-clear/form-field-clear.ts index 5dda4be5d6..ec1e56d225 100644 --- a/src/elements/form-field/form-field-clear/form-field-clear.ts +++ b/src/elements/form-field/form-field-clear/form-field-clear.ts @@ -3,7 +3,7 @@ import { html } from 'lit'; import { customElement } from 'lit/decorators.js'; import { SbbButtonBaseElement } from '../../core/base-elements.js'; -import { SbbConnectedAbortController, SbbLanguageController } from '../../core/controllers.js'; +import { SbbLanguageController } from '../../core/controllers.js'; import { hostAttributes } from '../../core/decorators.js'; import { i18nClearInput } from '../../core/i18n.js'; import { SbbNegativeMixin } from '../../core/mixins.js'; @@ -25,13 +25,15 @@ class SbbFormFieldClearElement extends SbbNegativeMixin(SbbButtonBaseElement) { public static override styles: CSSResultGroup = style; private _formField?: SbbFormFieldElement | null; - private _abort = new SbbConnectedAbortController(this); private _language = new SbbLanguageController(this); + public constructor() { + super(); + this.addEventListener?.('click', () => this._handleClick()); + } + public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('click', () => this._handleClick(), { signal }); this._formField = this.closest('sbb-form-field, [data-form-field]'); if (this._formField) { diff --git a/src/elements/menu/menu/menu.ts b/src/elements/menu/menu/menu.ts index 724ac8e640..06df1faded 100644 --- a/src/elements/menu/menu/menu.ts +++ b/src/elements/menu/menu/menu.ts @@ -12,7 +12,6 @@ import { } from '../../core/a11y.js'; import { SbbOpenCloseBaseElement } from '../../core/base-elements.js'; import { - SbbConnectedAbortController, SbbInertController, SbbMediaMatcherController, SbbMediaQueryBreakpointSmallAndBelow, @@ -98,7 +97,6 @@ class SbbMenuElement extends SbbNamedSlotListMixin< private _isPointerDownEventOnMenu: boolean = false; private _menuController!: AbortController; private _windowEventsController!: AbortController; - private _abort = new SbbConnectedAbortController(this); private _focusHandler = new SbbFocusHandler(); private _scrollHandler = new SbbScrollHandler(); private _inertController = new SbbInertController(this); @@ -112,6 +110,12 @@ class SbbMenuElement extends SbbNamedSlotListMixin< }, }); + public constructor() { + super(); + this.addEventListener?.('click', (e) => this._onClick(e)); + this.addEventListener?.('keydown', (e) => this._handleKeyDown(e)); + } + /** * Opens the menu on trigger click. */ @@ -249,18 +253,19 @@ class SbbMenuElement extends SbbNamedSlotListMixin< } } - public override connectedCallback(): void { - super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('click', (e) => this._onClick(e), { signal }); - this.addEventListener('keydown', (e) => this._handleKeyDown(e), { signal }); + protected override createRenderRoot(): HTMLElement | DocumentFragment { + const renderRoot = super.createRenderRoot(); // Due to the fact that menu can both be a list and just a container, we need to check its // state before the SbbNamedSlotListMixin handles the slotchange event, in order to avoid // it interpreting the non list case as a list. this.shadowRoot?.addEventListener('slotchange', (e) => this._checkListCase(e), { - signal, capture: true, }); + return renderRoot; + } + + public override connectedCallback(): void { + super.connectedCallback(); // Validate trigger element and attach event listeners this._configure(this.trigger); } diff --git a/src/elements/navigation/common/navigation-action-common.ts b/src/elements/navigation/common/navigation-action-common.ts index 7f2347df56..3abbc815c2 100644 --- a/src/elements/navigation/common/navigation-action-common.ts +++ b/src/elements/navigation/common/navigation-action-common.ts @@ -3,7 +3,6 @@ import { property } from 'lit/decorators.js'; import { html } from 'lit/static-html.js'; import type { SbbActionBaseElement } from '../../core/base-elements.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { isLean } from '../../core/dom.js'; import type { AbstractConstructor } from '../../core/mixins.js'; import type { SbbNavigationButtonElement } from '../navigation-button.js'; @@ -57,28 +56,26 @@ export const SbbNavigationActionCommonElementMixin = < return this._navigationSection; } - private _abort = new SbbConnectedAbortController(this); private _navigationMarker: SbbNavigationMarkerElement | null = null; private _navigationSection: SbbNavigationSectionElement | null = null; + protected constructor(...args: any[]) { + super(...args); + this.addEventListener?.('click', () => { + if ( + !this.hasAttribute('data-action-active') && + this._navigationMarker && + !this.connectedSection + ) { + this.marker?.select( + this as unknown as SbbNavigationButtonElement | SbbNavigationLinkElement, + ); + } + }); + } + public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener( - 'click', - () => { - if ( - !this.hasAttribute('data-action-active') && - this._navigationMarker && - !this.connectedSection - ) { - this.marker?.select( - this as unknown as SbbNavigationButtonElement | SbbNavigationLinkElement, - ); - } - }, - { signal }, - ); // Check if the current element is nested inside a navigation marker. this._navigationMarker = this.closest('sbb-navigation-marker'); diff --git a/src/elements/navigation/navigation-section/navigation-section.ts b/src/elements/navigation/navigation-section/navigation-section.ts index 8de12dba6e..1493235530 100644 --- a/src/elements/navigation/navigation-section/navigation-section.ts +++ b/src/elements/navigation/navigation-section/navigation-section.ts @@ -102,6 +102,11 @@ class SbbNavigationSectionElement extends SbbUpdateSchedulerMixin(LitElement) { private _windowEventsController!: AbortController; private _language = new SbbLanguageController(this); + public constructor() { + super(); + this.addEventListener?.('keydown', (event) => this._handleNavigationSectionFocus(event)); + } + /** * Opens the navigation section on trigger click. */ @@ -212,9 +217,6 @@ class SbbNavigationSectionElement extends SbbUpdateSchedulerMixin(LitElement) { this._triggerElement.addEventListener('click', () => this.open(), { signal: this._navigationSectionController.signal, }); - this.addEventListener('keydown', (event) => this._handleNavigationSectionFocus(event), { - signal: this._navigationSectionController.signal, - }); } private _setNavigationInert(): void { diff --git a/src/elements/navigation/navigation/navigation.ts b/src/elements/navigation/navigation/navigation.ts index 4c273f2002..28dccef7b2 100644 --- a/src/elements/navigation/navigation/navigation.ts +++ b/src/elements/navigation/navigation/navigation.ts @@ -7,11 +7,7 @@ import { ref } from 'lit/directives/ref.js'; import { SbbFocusHandler, setModalityOnNextFocus } from '../../core/a11y.js'; import { SbbOpenCloseBaseElement } from '../../core/base-elements.js'; -import { - SbbConnectedAbortController, - SbbInertController, - SbbLanguageController, -} from '../../core/controllers.js'; +import { SbbInertController, SbbLanguageController } from '../../core/controllers.js'; import { forceType, hostAttributes } from '../../core/decorators.js'; import { findReferencedElement, @@ -97,7 +93,6 @@ class SbbNavigationElement extends SbbUpdateSchedulerMixin(SbbOpenCloseBaseEleme private _triggerElement: HTMLElement | null = null; private _navigationController!: AbortController; private _windowEventsController!: AbortController; - private _abort = new SbbConnectedAbortController(this); private _language = new SbbLanguageController(this); private _inertController = new SbbInertController(this); private _focusHandler = new SbbFocusHandler(); @@ -111,6 +106,9 @@ class SbbNavigationElement extends SbbUpdateSchedulerMixin(SbbOpenCloseBaseEleme public constructor() { super(); + this.addEventListener?.('click', (event) => this._handleNavigationClose(event)); + this.addEventListener?.('pointerup', (event) => this._closeOnBackdropClick(event)); + this.addEventListener?.('pointerdown', (event) => this._pointerDownListener(event)); new MutationController(this, { skipInitial: true, @@ -363,12 +361,7 @@ class SbbNavigationElement extends SbbUpdateSchedulerMixin(SbbOpenCloseBaseEleme public override connectedCallback(): void { super.connectedCallback(); this.id ||= `sbb-navigation-${nextId++}`; - const signal = this._abort.signal; - this.addEventListener('click', (e) => this._handleNavigationClose(e), { signal }); - // Validate trigger element and attach event listeners this._configure(this.trigger); - this.addEventListener('pointerup', (event) => this._closeOnBackdropClick(event), { signal }); - this.addEventListener('pointerdown', (event) => this._pointerDownListener(event), { signal }); } public override disconnectedCallback(): void { diff --git a/src/elements/option/option/option-base-element.ts b/src/elements/option/option/option-base-element.ts index 7ae42efd5b..59b5ea729b 100644 --- a/src/elements/option/option/option-base-element.ts +++ b/src/elements/option/option/option-base-element.ts @@ -2,7 +2,6 @@ import { MutationController } from '@lit-labs/observers/mutation-controller.js'; import { html, LitElement, nothing, type PropertyValues, type TemplateResult } from 'lit'; import { property, state } from 'lit/decorators.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { slotState } from '../../core/decorators.js'; import { isAndroid, isSafari, setOrRemoveAttribute } from '../../core/dom.js'; import type { EventEmitter } from '../../core/eventing.js'; @@ -78,10 +77,11 @@ abstract class SbbOptionBaseElement extends SbbDisabledMixin( @state() private accessor _inertAriaGroups = false; - private _abort = new SbbConnectedAbortController(this); - public constructor() { super(); + this.addEventListener?.('click', (e: MouseEvent) => this.selectByClick(e), { + passive: true, + }); new MutationController(this, { config: optionObserverConfig, @@ -171,11 +171,6 @@ abstract class SbbOptionBaseElement extends SbbDisabledMixin( protected init(): void { this.setAttributeFromParent(); - const signal = this._abort.signal; - this.addEventListener('click', (e: MouseEvent) => this.selectByClick(e), { - signal, - passive: true, - }); } protected updateAriaDisabled(): void { diff --git a/src/elements/overlay/overlay-base-element.ts b/src/elements/overlay/overlay-base-element.ts index a6488cc01e..68a85fb4e8 100644 --- a/src/elements/overlay/overlay-base-element.ts +++ b/src/elements/overlay/overlay-base-element.ts @@ -30,6 +30,7 @@ export abstract class SbbOverlayBaseElement extends SbbNegativeMixin(SbbOpenClos // The last element which had focus before the component was opened. protected lastFocusedElement?: HTMLElement; protected overlayCloseElement?: HTMLElement; + /** @deprecated */ protected overlayController!: AbortController; protected openOverlayController!: AbortController; protected focusHandler = new SbbFocusHandler(); diff --git a/src/elements/radio-button/common/radio-button-common.ts b/src/elements/radio-button/common/radio-button-common.ts index 7e487a354c..c5b351bde7 100644 --- a/src/elements/radio-button/common/radio-button-common.ts +++ b/src/elements/radio-button/common/radio-button-common.ts @@ -75,14 +75,16 @@ export const SbbRadioButtonCommonElementMixin = this._handleClick(e)); + this.addEventListener?.('keydown', (e) => this._handleKeyDown(e)); + } + public override connectedCallback(): void { super.connectedCallback(); this._group = this.closest('sbb-radio-button-group') as SbbRadioButtonGroupElement; - const signal = this.abort.signal; - this.addEventListener('click', (e) => this._handleClick(e), { signal }); - this.addEventListener('keydown', (e) => this._handleKeyDown(e), { signal }); - // We need to call requestUpdate to update the reflected attributes ['disabled', 'required', 'size'].forEach((p) => this.requestUpdate(p)); } 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 1ba317ea14..633fc02c94 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 @@ -2,7 +2,6 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit'; import { LitElement, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { forceType, hostAttributes, slotState } from '../../core/decorators.js'; import { isLean } from '../../core/dom.js'; import { EventEmitter } from '../../core/eventing.js'; @@ -112,7 +111,6 @@ class SbbRadioButtonGroupElement extends SbbDisabledMixin(LitElement) { } private _didLoad = false; - private _abort = new SbbConnectedAbortController(this); /** * Emits whenever the `sbb-radio-group` value changes. @@ -123,17 +121,17 @@ class SbbRadioButtonGroupElement extends SbbDisabledMixin(LitElement) { SbbRadioButtonGroupElement.events.didChange, ); + public constructor() { + super(); + this.addEventListener?.('change', (e: Event) => this._onRadioChange(e)); + } + public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; this.toggleAttribute( 'data-has-panel', !!this.querySelector?.('sbb-selection-expansion-panel, sbb-radio-button-panel'), ); - - this.addEventListener('change', (e: Event) => this._onRadioChange(e), { - signal, - }); } public override willUpdate(changedProperties: PropertyValues): void { diff --git a/src/elements/select/select.ts b/src/elements/select/select.ts index df39866159..2594625317 100644 --- a/src/elements/select/select.ts +++ b/src/elements/select/select.ts @@ -7,7 +7,7 @@ import { until } from 'lit/directives/until.js'; import { getNextElementIndex } from '../core/a11y.js'; import { SbbOpenCloseBaseElement } from '../core/base-elements.js'; -import { SbbConnectedAbortController, SbbLanguageController } from '../core/controllers.js'; +import { SbbLanguageController } from '../core/controllers.js'; import { forceType, hostAttributes } from '../core/decorators.js'; import { isNextjs, isSafari, isZeroAnimationDuration, setOrRemoveAttribute } from '../core/dom.js'; import { EventEmitter } from '../core/eventing.js'; @@ -137,7 +137,6 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin( private _searchString = ''; private _didLoad = false; private _isPointerDownEventOnMenu: boolean = false; - private _abort = new SbbConnectedAbortController(this); /** * The 'combobox' input element @@ -160,6 +159,22 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin( public constructor() { super(); + this.addEventListener?.('optionSelectionChange', (e: CustomEvent) => + this._onOptionChanged(e), + ); + this.addEventListener?.('optionLabelChanged', (e: Event) => this._onOptionLabelChanged(e)); + this.addEventListener?.('click', (e: MouseEvent) => { + const target = e.target as SbbSelectElement | SbbOptionElement; + if (target.localName === 'sbb-option') { + // Option click + if (!this.multiple && !target.disabled) { + this.close(); + this.focus(); + } + } else { + this._toggleOpening(); + } + }); new MutationController(this, { config: { attributeFilter: ['aria-labelledby', 'aria-label', 'aria-describedby'] }, @@ -356,7 +371,6 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin( this.id ||= this._overlayId; } - const signal = this._abort.signal; const formField = this.closest?.('sbb-form-field') ?? this.closest?.('[data-form-field]'); if (formField) { @@ -372,33 +386,6 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin( if (this.value) { this._onValueChanged(this.value); } - - this.addEventListener( - 'optionSelectionChange', - (e: CustomEvent) => this._onOptionChanged(e), - { signal }, - ); - - this.addEventListener('optionLabelChanged', (e: Event) => this._onOptionLabelChanged(e), { - signal, - }); - - this.addEventListener( - 'click', - (e: MouseEvent) => { - const target = e.target as SbbSelectElement | SbbOptionElement; - if (target.localName === 'sbb-option') { - // Option click - if (!this.multiple && !target.disabled) { - this.close(); - this.focus(); - } - } else { - this._toggleOpening(); - } - }, - { signal }, - ); } protected override willUpdate(changedProperties: PropertyValues): void { diff --git a/src/elements/selection-expansion-panel/selection-expansion-panel.ts b/src/elements/selection-expansion-panel/selection-expansion-panel.ts index c166b9e651..91aa8d7405 100644 --- a/src/elements/selection-expansion-panel/selection-expansion-panel.ts +++ b/src/elements/selection-expansion-panel/selection-expansion-panel.ts @@ -9,7 +9,7 @@ import { import { customElement, property, state } from 'lit/decorators.js'; import type { SbbCheckboxGroupElement, SbbCheckboxPanelElement } from '../checkbox.js'; -import { SbbConnectedAbortController, SbbLanguageController } from '../core/controllers.js'; +import { SbbLanguageController } from '../core/controllers.js'; import { forceType, slotState } from '../core/decorators.js'; import { isZeroAnimationDuration } from '../core/dom.js'; import { EventEmitter } from '../core/eventing.js'; @@ -109,7 +109,6 @@ class SbbSelectionExpansionPanelElement extends SbbHydrationMixin(LitElement) { ); private _language = new SbbLanguageController(this); - private _abort = new SbbConnectedAbortController(this); private _initialized: boolean = false; private _sizeAttributeObserver = !isServer ? new MutationObserver((mutationsList: MutationRecord[]) => @@ -129,13 +128,14 @@ class SbbSelectionExpansionPanelElement extends SbbHydrationMixin(LitElement) { | SbbCheckboxGroupElement; } + public constructor() { + super(); + this.addEventListener?.('panelConnected', (e) => this._initFromInput(e)); + } + public override connectedCallback(): void { super.connectedCallback(); - this.addEventListener('panelConnected', this._initFromInput.bind(this), { - signal: this._abort.signal, - }); - this._state ||= 'closed'; } diff --git a/src/elements/slider/slider.ts b/src/elements/slider/slider.ts index cd13d25753..e5d3e81101 100644 --- a/src/elements/slider/slider.ts +++ b/src/elements/slider/slider.ts @@ -4,7 +4,6 @@ import { customElement, property, state } from 'lit/decorators.js'; import { ref } from 'lit/directives/ref.js'; import { styleMap } from 'lit/directives/style-map.js'; -import { SbbConnectedAbortController } from '../core/controllers.js'; import { forceType, hostAttributes } from '../core/decorators.js'; import { EventEmitter, forwardEventToHost } from '../core/eventing.js'; import { @@ -136,18 +135,15 @@ class SbbSliderElement extends SbbDisabledMixin(SbbFormAssociatedMixin(LitElemen /** Reference to the inner HTMLInputElement with type='range'. */ private _rangeInput!: HTMLInputElement; - private _abort = new SbbConnectedAbortController(this); - public constructor() { super(); /** @internal */ this.internals.role = 'slider'; + this.addEventListener?.('keydown', (e) => this._handleKeydown(e)); } public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('keydown', (e) => this._handleKeydown(e), { signal }); if (!this.value) { this.value = this._getDefaultValue(); diff --git a/src/elements/stepper/step-label/step-label.ts b/src/elements/stepper/step-label/step-label.ts index a48f1ea0aa..1e98c78070 100644 --- a/src/elements/stepper/step-label/step-label.ts +++ b/src/elements/stepper/step-label/step-label.ts @@ -2,7 +2,6 @@ import { type CSSResultGroup, html, type TemplateResult, type PropertyValues } f import { customElement } from 'lit/decorators.js'; import { SbbButtonBaseElement } from '../../core/base-elements.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { hostAttributes } from '../../core/decorators.js'; import { SbbDisabledMixin } from '../../core/mixins.js'; import { SbbIconNameMixin } from '../../icon.js'; @@ -34,10 +33,18 @@ class SbbStepLabelElement extends SbbIconNameMixin(SbbDisabledMixin(SbbButtonBas return this._step; } - private _abort = new SbbConnectedAbortController(this); private _stepper: SbbStepperElement | null = null; private _step: SbbStepElement | null = null; + public constructor() { + super(); + this.addEventListener?.('click', () => { + if (this._stepper && this._step) { + this._stepper.selected = this._step; + } + }); + } + private _getStep(): SbbStepElement | null { let nextSibling = this.nextElementSibling; while (nextSibling && nextSibling.localName !== 'sbb-step') { @@ -48,7 +55,6 @@ class SbbStepLabelElement extends SbbIconNameMixin(SbbDisabledMixin(SbbButtonBas public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; this.id = this.id || `sbb-step-label-${nextId++}`; this.internals.ariaSelected = 'false'; this._stepper = this.closest('sbb-stepper'); @@ -56,15 +62,6 @@ class SbbStepLabelElement extends SbbIconNameMixin(SbbDisabledMixin(SbbButtonBas // The `data-disabled` attribute is used to preserve the initial disabled state of // step labels in case of switching from linear to non-linear mode. this.toggleAttribute('data-disabled', this.hasAttribute('disabled')); - this.addEventListener( - 'click', - () => { - if (this._stepper && this._step) { - this._stepper.selected = this._step; - } - }, - { signal }, - ); } protected override firstUpdated(changedProperties: PropertyValues): void { diff --git a/src/elements/stepper/step/step.ts b/src/elements/stepper/step/step.ts index 022793d554..691457c8b9 100644 --- a/src/elements/stepper/step/step.ts +++ b/src/elements/stepper/step/step.ts @@ -8,7 +8,6 @@ import { } from 'lit'; import { customElement } from 'lit/decorators.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { hostAttributes } from '../../core/decorators.js'; import { EventEmitter } from '../../core/eventing.js'; import type { SbbStepLabelElement } from '../step-label.js'; @@ -50,7 +49,6 @@ class SbbStepElement extends LitElement { ); private _loaded: boolean = false; - private _abort = new SbbConnectedAbortController(this); private _stepper: SbbStepperElement | null = null; private _label: SbbStepLabelElement | null = null; private _stepResizeObserver = new ResizeController(this, { @@ -64,6 +62,11 @@ class SbbStepElement extends LitElement { return this._label; } + public constructor() { + super(); + this.addEventListener?.('click', (e) => this._handleClick(e)); + } + /** * Selects and configures the step. * @internal @@ -147,9 +150,7 @@ class SbbStepElement extends LitElement { public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; this.id = this.id || `sbb-step-${nextId++}`; - this.addEventListener('click', (e) => this._handleClick(e), { signal }); this._stepper = this.closest('sbb-stepper'); this._label = this._getStepLabel(); } diff --git a/src/elements/stepper/stepper/stepper.ts b/src/elements/stepper/stepper/stepper.ts index 011474ad07..1db5c62203 100644 --- a/src/elements/stepper/stepper/stepper.ts +++ b/src/elements/stepper/stepper/stepper.ts @@ -8,7 +8,6 @@ import { import { customElement, property } from 'lit/decorators.js'; import { getNextElementIndex, isArrowKeyPressed } from '../../core/a11y.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { forceType } from '../../core/decorators.js'; import { breakpoints, isBreakpoint, isLean } from '../../core/dom.js'; import type { SbbHorizontalFrom, SbbOrientation } from '../../core/interfaces.js'; @@ -90,9 +89,13 @@ class SbbStepperElement extends SbbHydrationMixin(LitElement) { } private _loaded: boolean = false; - private _abort = new SbbConnectedAbortController(this); private _resizeObserverTimeout: ReturnType | null = null; + public constructor() { + super(); + this.addEventListener?.('keydown', (e) => this._handleKeyDown(e)); + } + /** Selects the next step. */ public next(): void { if (this.selectedIndex !== null) { @@ -230,7 +233,7 @@ class SbbStepperElement extends SbbHydrationMixin(LitElement) { setTimeout(() => this._setMarkerSize(), 0); } - private _onStepperResize(): void { + private _onStepperResize = (): void => { this._checkOrientation(); clearTimeout(this._resizeObserverTimeout!); this.toggleAttribute('data-disable-animation', true); @@ -240,7 +243,7 @@ class SbbStepperElement extends SbbHydrationMixin(LitElement) { () => this.toggleAttribute('data-disable-animation', false), DEBOUNCE_TIME, ); - } + }; private _configureLinearMode(): void { this.steps.forEach((step, index) => { @@ -254,15 +257,17 @@ class SbbStepperElement extends SbbHydrationMixin(LitElement) { public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('keydown', (e) => this._handleKeyDown(e), { signal }); - window.addEventListener('resize', () => this._onStepperResize(), { - signal, + window.addEventListener('resize', this._onStepperResize, { passive: true, }); this.toggleAttribute('data-disable-animation', !this._loaded); } + public override disconnectedCallback(): void { + super.disconnectedCallback(); + window.removeEventListener('resize', this._onStepperResize); + } + protected override async firstUpdated(changedProperties: PropertyValues): Promise { super.firstUpdated(changedProperties); await this.updateComplete; diff --git a/src/elements/tabs/tab-group/tab-group.ts b/src/elements/tabs/tab-group/tab-group.ts index d376232b90..7a4232bba6 100644 --- a/src/elements/tabs/tab-group/tab-group.ts +++ b/src/elements/tabs/tab-group/tab-group.ts @@ -6,7 +6,6 @@ import { customElement, property } from 'lit/decorators.js'; import { ref } from 'lit/directives/ref.js'; import { getNextElementIndex, isArrowKeyPressed } from '../../core/a11y.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { forceType } from '../../core/decorators.js'; import { isLean } from '../../core/dom.js'; import { EventEmitter, throttle } from '../../core/eventing.js'; @@ -67,7 +66,6 @@ class SbbTabGroupElement extends SbbHydrationMixin(LitElement) { private _selectedTab?: InterfaceSbbTabGroupTab; private _tabGroupElement!: HTMLElement; private _tabContentElement!: HTMLElement; - private _abort = new SbbConnectedAbortController(this); private _tabAttributeObserver = new MutationController(this, { target: null, config: tabObserverConfig, @@ -112,10 +110,9 @@ class SbbTabGroupElement extends SbbHydrationMixin(LitElement) { SbbTabGroupElement.events.didChange, ); - public override connectedCallback(): void { - super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('keydown', (e) => this._handleKeyDown(e), { signal }); + public constructor() { + super(); + this.addEventListener?.('keydown', (e) => this._handleKeyDown(e)); } protected override firstUpdated(changedProperties: PropertyValues): void { diff --git a/src/elements/tag/tag/tag.ts b/src/elements/tag/tag/tag.ts index 5ecb27de53..55562a7121 100644 --- a/src/elements/tag/tag/tag.ts +++ b/src/elements/tag/tag/tag.ts @@ -3,7 +3,6 @@ import { html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { SbbButtonBaseElement } from '../../core/base-elements.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { forceType, getOverride, omitEmptyConverter, slotState } from '../../core/decorators.js'; import { isLean } from '../../core/dom.js'; import { EventEmitter } from '../../core/eventing.js'; @@ -73,12 +72,14 @@ class SbbTagElement extends SbbIconNameMixin(SbbDisabledTabIndexActionMixin(SbbB bubbles: true, }); - private _abort = new SbbConnectedAbortController(this); + public constructor() { + super(); + this.addEventListener?.('click', () => this._handleClick()); + } public override connectedCallback(): void { super.connectedCallback(); this._group = this.closest('sbb-tag-group') as SbbTagGroupElement; - this.addEventListener('click', () => this._handleClick(), { signal: this._abort.signal }); } /** Method triggered on button click. Inverts the checked value and emits events. */ diff --git a/src/elements/toast/toast.ts b/src/elements/toast/toast.ts index 29c69b26de..312c32e8e1 100644 --- a/src/elements/toast/toast.ts +++ b/src/elements/toast/toast.ts @@ -4,7 +4,7 @@ import { customElement, property } from 'lit/decorators.js'; import type { SbbTransparentButtonElement, SbbTransparentButtonLinkElement } from '../button.js'; import { SbbOpenCloseBaseElement } from '../core/base-elements.js'; -import { SbbConnectedAbortController, SbbLanguageController } from '../core/controllers.js'; +import { SbbLanguageController } from '../core/controllers.js'; import { forceType, slotState } from '../core/decorators.js'; import { isFirefox, isLean, isZeroAnimationDuration } from '../core/dom.js'; import { composedPathHasAttribute } from '../core/eventing.js'; @@ -67,7 +67,6 @@ class SbbToastElement extends SbbIconNameMixin(SbbHydrationMixin(SbbOpenCloseBas @property() public accessor politeness: 'polite' | 'assertive' | 'off' = 'polite'; private _closeTimeout?: ReturnType; - private _abort = new SbbConnectedAbortController(this); private _language = new SbbLanguageController(this); /** @@ -86,6 +85,11 @@ class SbbToastElement extends SbbIconNameMixin(SbbHydrationMixin(SbbOpenCloseBas } } + public constructor() { + super(); + this.addEventListener?.('click', (e) => this._onClick(e)); + } + /** * Open the toast. * If there are other opened toasts in the page, close them first. @@ -160,9 +164,6 @@ class SbbToastElement extends SbbIconNameMixin(SbbHydrationMixin(SbbOpenCloseBas public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('click', (e) => this._onClick(e), { signal }); - // Add this toast to the global collection toastRefs.add(this); } diff --git a/src/elements/toggle/toggle-option/toggle-option.ts b/src/elements/toggle/toggle-option/toggle-option.ts index 3962cf7f74..e3e09802ad 100644 --- a/src/elements/toggle/toggle-option/toggle-option.ts +++ b/src/elements/toggle/toggle-option/toggle-option.ts @@ -2,7 +2,6 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit'; import { html, LitElement, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { forceType, hostAttributes, slotState } from '../../core/decorators.js'; import { setOrRemoveAttribute } from '../../core/dom.js'; import { SbbIconNameMixin } from '../../icon.js'; @@ -46,18 +45,18 @@ class SbbToggleOptionElement extends SbbIconNameMixin(LitElement) { private _value: string = ''; private _toggle?: SbbToggleElement; - private _abort = new SbbConnectedAbortController(this); + + public constructor() { + super(); + // We need to listen input event on host as with keyboard navigation + // the Input Event is triggered from sbb-toggle. + this.addEventListener?.('input', () => this._handleInput()); + this.addEventListener?.('click', () => this.shadowRoot!.querySelector('label')?.click()); + } public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; - // We need to listen input event on host as with keyboard navigation - // the Input Event is triggered from sbb-toggle. - this.addEventListener('input', () => this._handleInput(), { signal }); - this.addEventListener('click', () => this.shadowRoot!.querySelector('label')?.click(), { - signal, - }); // We can use closest here, as we expect the parent sbb-toggle to be in light DOM. this._toggle = this.closest?.('sbb-toggle') ?? undefined; this._verifyTabindex(); diff --git a/src/elements/toggle/toggle/toggle.ts b/src/elements/toggle/toggle/toggle.ts index 3976b66bc0..511b2dccb5 100644 --- a/src/elements/toggle/toggle/toggle.ts +++ b/src/elements/toggle/toggle/toggle.ts @@ -10,7 +10,6 @@ import { import { customElement, property } from 'lit/decorators.js'; import { getNextElementIndex, interactivityChecker, isArrowKeyPressed } from '../../core/a11y.js'; -import { SbbConnectedAbortController } from '../../core/controllers.js'; import { forceType, handleDistinctChange, hostAttributes } from '../../core/decorators.js'; import { isLean } from '../../core/dom.js'; import { EventEmitter } from '../../core/eventing.js'; @@ -92,7 +91,11 @@ class SbbToggleElement extends LitElement { composed: true, }); - private _abort = new SbbConnectedAbortController(this); + public constructor() { + super(); + this.addEventListener?.('input', () => this._handleInput(), { passive: true }); + this.addEventListener?.('keydown', (e) => this._handleKeyDown(e)); + } /** @internal */ public updatePillPosition(resizing: boolean): void { @@ -126,9 +129,6 @@ class SbbToggleElement extends LitElement { public override connectedCallback(): void { super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('input', () => this._handleInput(), { signal, passive: true }); - this.addEventListener('keydown', (e) => this._handleKeyDown(e), { signal }); this.options.forEach((option) => this._toggleResizeObserver.observe(option)); this._updateToggle(); } diff --git a/src/elements/train/train-formation/train-formation.ts b/src/elements/train/train-formation/train-formation.ts index 5d0a80e690..65e62bbb10 100644 --- a/src/elements/train/train-formation/train-formation.ts +++ b/src/elements/train/train-formation/train-formation.ts @@ -8,7 +8,7 @@ import { } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { SbbConnectedAbortController, SbbLanguageController } from '../../core/controllers.js'; +import { SbbLanguageController } from '../../core/controllers.js'; import { i18nSector, i18nSectorShort, i18nTrains } from '../../core/i18n.js'; import { SbbNamedSlotListMixin, type WithListChildren } from '../../core/mixins.js'; import type { SbbTrainBlockedPassageElement } from '../train-blocked-passage.js'; @@ -42,14 +42,12 @@ class SbbTrainFormationElement extends SbbNamedSlotListMixin this._readSectors(e), { signal }); - this.addEventListener('sectorChange', (e) => this._readSectors(e), { signal }); + public constructor() { + super(); + this.addEventListener?.('trainSlotChange', (e) => this._readSectors(e)); + this.addEventListener?.('sectorChange', (e) => this._readSectors(e)); } private _readSectors(event?: Event): void { diff --git a/tools/generate-component/boilerplate/component.ts b/tools/generate-component/boilerplate/component.ts index 3636f45834..f629149278 100644 --- a/tools/generate-component/boilerplate/component.ts +++ b/tools/generate-component/boilerplate/component.ts @@ -2,7 +2,6 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit'; import { html, LitElement, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { SbbConnectedAbortController } from '../core/controllers.js'; import { EventEmitter } from '../core/eventing.js'; import style from './__noPrefixName__.scss?lit&inline'; @@ -26,7 +25,6 @@ export class __nameUpperCase__ extends LitElement { /** _myState documentation */ @state() private _myState = false; - private _abort = new SbbConnectedAbortController(this); private _myEvent: EventEmitter = new EventEmitter( this, __nameUpperCase__.events.myEventName, @@ -36,13 +34,6 @@ export class __nameUpperCase__ extends LitElement { this._myEvent.emit(); } - public override connectedCallback(): void { - super.connectedCallback(); - const signal = this._abort.signal; - this.addEventListener('click', () => this._onClickFn(), { signal }); - // do stuff - } - protected override willUpdate(changedProperties: PropertyValues): void { super.willUpdate(changedProperties); From c30ee237400650eea93fc8292b1ecda27d1d2005 Mon Sep 17 00:00:00 2001 From: Lukas Spirig Date: Thu, 19 Dec 2024 13:24:41 +0100 Subject: [PATCH 2/3] fix: review --- .../boilerplate/component.ts | 40 +++---------------- 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/tools/generate-component/boilerplate/component.ts b/tools/generate-component/boilerplate/component.ts index f629149278..3a2f2a0907 100644 --- a/tools/generate-component/boilerplate/component.ts +++ b/tools/generate-component/boilerplate/component.ts @@ -1,8 +1,6 @@ -import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit'; -import { html, LitElement, nothing } from 'lit'; -import { customElement, property, state } from 'lit/decorators.js'; - -import { EventEmitter } from '../core/eventing.js'; +import type { CSSResultGroup, TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; import style from './__noPrefixName__.scss?lit&inline'; @@ -16,40 +14,12 @@ import style from './__noPrefixName__.scss?lit&inline'; export class __nameUpperCase__ extends LitElement { public static override styles: CSSResultGroup = style; public static readonly events: Record = { - myEventName: 'myEventName', + // Add event names or remove } as const; - /** myProp documentation */ - @property({ attribute: 'my-prop', reflect: true }) public myProp: string = ''; - - /** _myState documentation */ - @state() private _myState = false; - - private _myEvent: EventEmitter = new EventEmitter( - this, - __nameUpperCase__.events.myEventName, - ); - - private _onClickFn(): void { - this._myEvent.emit(); - } - - protected override willUpdate(changedProperties: PropertyValues): void { - super.willUpdate(changedProperties); - - if (changedProperties.has('myProp')) { - // do stuff - } - } - - public override disconnectedCallback(): void { - super.disconnectedCallback(); - // do stuff - } - protected override render(): TemplateResult { return html` -
${this._myState ? html`` : nothing} ${this.myProp}
+
`; } } From 5d775da08de0feab67b4592a881a42133cf25c9a Mon Sep 17 00:00:00 2001 From: Lukas Spirig Date: Thu, 19 Dec 2024 14:11:01 +0100 Subject: [PATCH 3/3] fix: review --- tools/generate-component/boilerplate/component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/generate-component/boilerplate/component.ts b/tools/generate-component/boilerplate/component.ts index 3a2f2a0907..2938d09541 100644 --- a/tools/generate-component/boilerplate/component.ts +++ b/tools/generate-component/boilerplate/component.ts @@ -8,7 +8,6 @@ import style from './__noPrefixName__.scss?lit&inline'; * Describe the purpose of the component with a single short sentence. * * @slot - Use the unnamed slot to add `sbb-TODO` elements. - * @event {CustomEvent} myEventName - TODO: Document this event */ @customElement('__name__') export class __nameUpperCase__ extends LitElement {