Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use lit observers #3074

Merged
merged 7 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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;

Check warning on line 27 in src/elements/core/controllers/language-controller.ts

View check run for this annotation

Codecov / codecov/patch

src/elements/core/controllers/language-controller.ts#L27

Added line #L27 was not covered by tests
private static readonly _observerConfig = {
attributeFilter: ['lang'],
attributeOldValue: true,
Expand Down Expand Up @@ -66,8 +67,11 @@
}

public hostConnected(): void {
if (isServer) {
return;
}

Check warning on line 72 in src/elements/core/controllers/language-controller.ts

View check run for this annotation

Codecov / codecov/patch

src/elements/core/controllers/language-controller.ts#L71-L72

Added lines #L71 - L72 were not covered by tests
if (!SbbLanguageController._listeners.size) {
SbbLanguageController._observer.observe(
SbbLanguageController._observer!.observe(
document.documentElement,
SbbLanguageController._observerConfig,
);
Expand All @@ -80,10 +84,13 @@
}

public hostDisconnected(): void {
if (isServer) {
return;
}

Check warning on line 89 in src/elements/core/controllers/language-controller.ts

View check run for this annotation

Codecov / codecov/patch

src/elements/core/controllers/language-controller.ts#L88-L89

Added lines #L88 - L89 were not covered by tests
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 { 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 @@

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;
}

Check warning on line 291 in src/elements/datepicker/datepicker/datepicker.ts

View check run for this annotation

Codecov / codecov/patch

src/elements/datepicker/datepicker/datepicker.ts#L289-L291

Added lines #L289 - L291 were not covered by tests
})
: null;

Check warning on line 293 in src/elements/datepicker/datepicker/datepicker.ts

View check run for this annotation

Codecov / codecov/patch

src/elements/datepicker/datepicker/datepicker.ts#L293

Added line #L293 was not covered by tests

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

Expand Down Expand Up @@ -375,7 +382,7 @@
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
Loading