Skip to content

Commit

Permalink
refactor: use lit observers (#3074)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeripeierSBB authored Sep 17, 2024
1 parent 26d4d5a commit 3e554eb
Show file tree
Hide file tree
Showing 30 changed files with 305 additions and 277 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"@custom-elements-manifest/to-markdown": "0.1.0",
"@eslint/eslintrc": "3.1.0",
"@eslint/js": "9.10.0",
"@lit-labs/observers": "2.0.3",
"@lit-labs/router": "0.1.3",
"@lit-labs/testing": "0.2.4",
"@lit/react": "1.0.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MutationController } from '@lit-labs/observers/mutation-controller.js';
import { type CSSResultGroup, isServer, type PropertyValues, type TemplateResult } from 'lit';
import { customElement } from 'lit/decorators.js';

Expand All @@ -6,7 +7,6 @@ import { hostAttributes, slotState } from '../../core/decorators.js';
import { setOrRemoveAttribute } from '../../core/dom.js';
import { isEventPrevented } from '../../core/eventing.js';
import { SbbDisabledMixin, SbbNegativeMixin } from '../../core/mixins.js';
import { AgnosticMutationObserver } from '../../core/observers.js';
import { SbbIconNameMixin } from '../../icon.js';
import type { SbbAutocompleteGridOptionElement } from '../autocomplete-grid-option.js';

Expand Down Expand Up @@ -47,21 +47,27 @@ export class SbbAutocompleteGridButtonElement extends SbbDisabledMixin(
/** Whether the component must be set disabled due disabled attribute on sbb-optgroup. */
private _disabledFromGroup = false;

/** MutationObserver on data attributes. */
private _optionAttributeObserver = new AgnosticMutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.attributeName === 'data-group-disabled') {
this._disabledFromGroup = this.hasAttribute('data-group-disabled');
setOrRemoveAttribute(this, 'aria-disabled', `${this.disabled || this._disabledFromGroup}`);
}
}
});

public constructor() {
super();
if (!isServer) {
this.setupBaseEventHandlers();
this.addEventListener('click', this._handleButtonClick);

new MutationController(this, {
config: buttonObserverConfig,
callback: (mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.attributeName === 'data-group-disabled') {
this._disabledFromGroup = this.hasAttribute('data-group-disabled');
setOrRemoveAttribute(
this,
'aria-disabled',
`${this.disabled || this._disabledFromGroup}`,
);
}
}
},
});
}
}

Expand All @@ -81,7 +87,6 @@ export class SbbAutocompleteGridButtonElement extends SbbDisabledMixin(
this._disabledFromGroup = parentGroup.disabled;
setOrRemoveAttribute(this, 'aria-disabled', `${this.disabled || this._disabledFromGroup}`);
}
this._optionAttributeObserver.observe(this, buttonObserverConfig);
}

public override willUpdate(changedProperties: PropertyValues<this>): void {
Expand All @@ -91,11 +96,6 @@ export class SbbAutocompleteGridButtonElement extends SbbDisabledMixin(
}
}

public override disconnectedCallback(): void {
super.disconnectedCallback();
this._optionAttributeObserver.disconnect();
}

private _handleButtonClick = async (event: MouseEvent): Promise<void> => {
if ((await isEventPrevented(event)) || !this.closest('form')) {
return;
Expand Down
18 changes: 9 additions & 9 deletions src/elements/breadcrumb/breadcrumb-group/breadcrumb-group.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ResizeController } from '@lit-labs/observers/resize-controller.js';
import {
type CSSResultGroup,
html,
nothing,
LitElement,
nothing,
type PropertyValues,
type TemplateResult,
} from 'lit';
Expand All @@ -18,7 +19,6 @@ import { hostAttributes } from '../../core/decorators.js';
import { setOrRemoveAttribute } from '../../core/dom.js';
import { i18nBreadcrumbEllipsisButtonLabel } from '../../core/i18n.js';
import { SbbNamedSlotListMixin, type WithListChildren } from '../../core/mixins.js';
import { AgnosticResizeObserver } from '../../core/observers.js';
import type { SbbBreadcrumbElement } from '../breadcrumb.js';

import style from './breadcrumb-group.scss?lit&inline';
Expand Down Expand Up @@ -52,7 +52,11 @@ export class SbbBreadcrumbGroupElement extends SbbNamedSlotListMixin<
return this.getAttribute('data-state') as 'collapsed' | 'manually-expanded' | null;
}

private _resizeObserver = new AgnosticResizeObserver(() => this._evaluateCollapsedState());
private _resizeObserver = new ResizeController(this, {
target: null,
skipInitial: true,
callback: () => this._evaluateCollapsedState(),
});
private _abort = new SbbConnectedAbortController(this);
private _language = new SbbLanguageController(this);
private _markForFocus = false;
Expand Down Expand Up @@ -87,11 +91,6 @@ export class SbbBreadcrumbGroupElement extends SbbNamedSlotListMixin<
this.toggleAttribute('data-loaded', true);
}

public override disconnectedCallback(): void {
super.disconnectedCallback();
this._resizeObserver.disconnect();
}

protected override willUpdate(changedProperties: PropertyValues<WithListChildren<this>>): void {
super.willUpdate(changedProperties);

Expand Down Expand Up @@ -168,7 +167,8 @@ export class SbbBreadcrumbGroupElement extends SbbNamedSlotListMixin<
this.listChildren.length >= MIN_BREADCRUMBS_TO_COLLAPSE
) {
this._state = 'collapsed';
this._resizeObserver.disconnect();
this._resizeObserver.hostDisconnected();
this.removeController(this._resizeObserver);
}
}

Expand Down
16 changes: 7 additions & 9 deletions src/elements/card/common/card-action-common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MutationController } from '@lit-labs/observers/mutation-controller.js';
import type { CSSResultGroup, TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
import { html } from 'lit/static-html.js';
Expand All @@ -6,7 +7,6 @@ import { IS_FOCUSABLE_QUERY } from '../../core/a11y.js';
import type { SbbActionBaseElement } from '../../core/base-elements.js';
import { hostAttributes } from '../../core/decorators.js';
import type { AbstractConstructor } from '../../core/mixins.js';
import { AgnosticMutationObserver } from '../../core/observers.js';
import type { SbbCardElement } from '../card.js';

import style from './card-action.scss?lit&inline';
Expand Down Expand Up @@ -47,9 +47,11 @@ export const SbbCardActionCommonElementMixin = <
protected abstract actionRole: 'link' | 'button';

private _card: SbbCardElement | null = null;
private _cardMutationObserver = new AgnosticMutationObserver(() =>
this._checkForSlottedActions(),
);
private _cardMutationObserver = new MutationController(this, {
target: null,
config: { childList: true, subtree: true },
callback: () => this._checkForSlottedActions(),
});

private _onActiveChange(): void {
if (this._card) {
Expand Down Expand Up @@ -79,10 +81,7 @@ export const SbbCardActionCommonElementMixin = <
this._card.setAttribute('data-action-role', this.actionRole);

this._checkForSlottedActions();
this._cardMutationObserver.observe(this._card, {
childList: true,
subtree: true,
});
this._cardMutationObserver.observe(this._card);
}
}

Expand All @@ -97,7 +96,6 @@ export const SbbCardActionCommonElementMixin = <
.forEach((el) => el.removeAttribute('data-card-focusable'));
this._card = null;
}
this._cardMutationObserver.disconnect();
}

protected override renderTemplate(): TemplateResult {
Expand Down
16 changes: 7 additions & 9 deletions src/elements/container/sticky-bar/sticky-bar.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IntersectionController } from '@lit-labs/observers/intersection-controller.js';
import {
type CSSResultGroup,
html,
Expand All @@ -8,7 +9,6 @@ import {
import { customElement, property } from 'lit/decorators.js';

import { hostAttributes } from '../../core/decorators.js';
import { AgnosticIntersectionObserver } from '../../core/observers.js';

import style from './sticky-bar.scss?lit&inline';

Expand All @@ -34,9 +34,12 @@ export class SbbStickyBarElement extends LitElement {
@property({ reflect: true }) public color?: 'white' | 'milk';

private _intersector?: HTMLSpanElement;
private _observer = new AgnosticIntersectionObserver((entries) =>
this._toggleShadowVisibility(entries[0]),
);
private _observer = new IntersectionController(this, {
// Although `this` is observed, we have to postpone observing
// into firstUpdated() to achieve a correct initial state.
target: null,
callback: (entries) => this._toggleShadowVisibility(entries[0]),
});

public override connectedCallback(): void {
super.connectedCallback();
Expand Down Expand Up @@ -67,11 +70,6 @@ export class SbbStickyBarElement extends LitElement {
);
}

public override disconnectedCallback(): void {
super.disconnectedCallback();
this._observer.disconnect();
}

protected override render(): TemplateResult {
return html`
<div class="sbb-sticky-bar__wrapper">
Expand Down
23 changes: 15 additions & 8 deletions src/elements/core/controllers/language-controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { isServer, type ReactiveController, type ReactiveControllerHost } from 'lit';

import { readConfig } from '../config.js';
import { AgnosticMutationObserver } from '../observers.js';

/**
* The LanguageController is a reactive controller that observes the "lang" attribute
Expand All @@ -19,11 +18,13 @@ export class SbbLanguageController implements ReactiveController {
private static readonly _listeners = new Set<SbbLanguageController>();

/** MutationObserver that observes the "lang" attribute of the <html> element. */
private static readonly _observer = new AgnosticMutationObserver((mutations) => {
if (mutations[0].oldValue !== document.documentElement.getAttribute('lang')) {
SbbLanguageController._listeners.forEach((l) => l._callHandlers());
}
});
private static readonly _observer = !isServer
? new MutationObserver((mutations) => {
if (mutations[0].oldValue !== document.documentElement.getAttribute('lang')) {
SbbLanguageController._listeners.forEach((l) => l._callHandlers());
}
})
: null;
private static readonly _observerConfig = {
attributeFilter: ['lang'],
attributeOldValue: true,
Expand Down Expand Up @@ -66,8 +67,11 @@ export class SbbLanguageController implements ReactiveController {
}

public hostConnected(): void {
if (isServer) {
return;
}
if (!SbbLanguageController._listeners.size) {
SbbLanguageController._observer.observe(
SbbLanguageController._observer!.observe(
document.documentElement,
SbbLanguageController._observerConfig,
);
Expand All @@ -80,10 +84,13 @@ export class SbbLanguageController implements ReactiveController {
}

public hostDisconnected(): void {
if (isServer) {
return;
}
this._previousLanguage = this.current;
SbbLanguageController._listeners.delete(this);
if (!SbbLanguageController._listeners.size) {
SbbLanguageController._observer.disconnect();
SbbLanguageController._observer!.disconnect();
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/elements/core/observers/intersection-observer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* @deprecated use lit observers, will be removed with next major version
*/
export class NodeIntersectionObserver implements IntersectionObserver {
public root!: Element | Document | null;
public rootMargin!: string;
Expand All @@ -20,6 +23,9 @@ export class NodeIntersectionObserver implements IntersectionObserver {
}
}

/**
* @deprecated use lit observers, will be removed with next major version
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const AgnosticIntersectionObserver =
typeof IntersectionObserver === 'undefined' ? NodeIntersectionObserver : IntersectionObserver;
6 changes: 6 additions & 0 deletions src/elements/core/observers/mutation-observer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* @deprecated use lit observers, will be removed with next major version
*/
export class NodeMutationObserver implements MutationObserver {
public disconnect(): void {
// Noop
Expand All @@ -12,6 +15,9 @@ export class NodeMutationObserver implements MutationObserver {
}
}

/**
* @deprecated use lit observers, will be removed with next major version
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const AgnosticMutationObserver: typeof MutationObserver =
typeof MutationObserver === 'undefined' ? NodeMutationObserver : MutationObserver;
6 changes: 6 additions & 0 deletions src/elements/core/observers/resize-observer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* @deprecated use lit observers, will be removed with next major version
*/
export class NodeResizeObserver implements ResizeObserver {
public disconnect(): any {
// noop
Expand All @@ -12,6 +15,9 @@ export class NodeResizeObserver implements ResizeObserver {
}
}

/**
* @deprecated use lit observers, will be removed with next major version
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const AgnosticResizeObserver =
typeof ResizeObserver === 'undefined' ? NodeResizeObserver : ResizeObserver;
31 changes: 19 additions & 12 deletions src/elements/datepicker/datepicker/datepicker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
import { html, LitElement } from 'lit';
import {
type CSSResultGroup,
html,
isServer,
LitElement,
type PropertyValues,
type TemplateResult,
} from 'lit';
import { customElement, property, state } from 'lit/decorators.js';

import { readConfig } from '../../core/config.js';
Expand All @@ -9,7 +15,6 @@ import { findInput, findReferencedElement } from '../../core/dom.js';
import { EventEmitter } from '../../core/eventing.js';
import { i18nDateChangedTo, i18nDatePickerPlaceholder } from '../../core/i18n.js';
import type { SbbDateLike, SbbValidationChangeEvent } from '../../core/interfaces.js';
import { AgnosticMutationObserver } from '../../core/observers.js';
import type { SbbDatepickerButton } from '../common.js';
import type { SbbDatepickerToggleElement } from '../datepicker-toggle.js';

Expand Down Expand Up @@ -276,14 +281,16 @@ export class SbbDatepickerElement<T = Date> extends LitElement {

private _datePickerController!: AbortController;

private _inputObserver = new AgnosticMutationObserver((mutationsList) => {
this._emitInputUpdated();
// TODO: Decide whether to remove this logic by adding a value property to the datepicker.
if (this._inputElement && mutationsList?.some((e) => e.attributeName === 'value')) {
const value = this._inputElement.getAttribute('value');
this.valueAsDate = this._dateAdapter.parse(value, this.now) ?? value;
}
});
private _inputObserver = !isServer
? new MutationObserver((mutationsList) => {
this._emitInputUpdated();
// TODO: Decide whether to remove this logic by adding a value property to the datepicker.
if (this._inputElement && mutationsList?.some((e) => e.attributeName === 'value')) {
const value = this._inputElement.getAttribute('value');
this.valueAsDate = this._dateAdapter.parse(value, this.now) ?? value;
}
})
: null;

private _dateAdapter: DateAdapter<T> = readConfig().datetime?.dateAdapter ?? defaultDateAdapter;

Expand Down Expand Up @@ -375,7 +382,7 @@ export class SbbDatepickerElement<T = Date> extends LitElement {
this._inputElement = input;
if (input) {
this._datePickerController = new AbortController();
this._inputObserver.observe(input, {
this._inputObserver?.observe(input, {
attributeFilter: ['disabled', 'readonly', 'min', 'max', 'value'],
});

Expand Down
Loading

0 comments on commit 3e554eb

Please sign in to comment.