Skip to content

Commit

Permalink
fix: remove deprecated didChange events where possible (#3253)
Browse files Browse the repository at this point in the history
Closes #3255

BREAKING CHANGE: remove deprecated `didChange` events from
`sbb-checkbox`, `sbb-checkbox-panel`, `sbb-toggle-check`, `sbb-select`,
`sbb-toggle` and `sbb-datepicker`. Use `change` event as alternative.
  • Loading branch information
jeripeierSBB authored Nov 28, 2024
1 parent ce8c318 commit da64d5d
Show file tree
Hide file tree
Showing 18 changed files with 96 additions and 92 deletions.
5 changes: 4 additions & 1 deletion src/elements/autocomplete/autocomplete-base-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ export abstract class SbbAutocompleteBaseElement extends SbbNegativeMixin(

if (this.triggerElement) {
// Set the option value
this.triggerElement.value = target.value as string;
// In order to support React onChange event, we have to get the setter and call it.
// https://github.com/facebook/react/issues/11600#issuecomment-345813130
const setValue = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')!.set!;
setValue.call(this.triggerElement, target.value);

// Manually trigger the change events
this.triggerElement.dispatchEvent(new Event('change', { bubbles: true }));
Expand Down
2 changes: 0 additions & 2 deletions src/elements/checkbox/checkbox-panel/checkbox-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export type SbbCheckboxPanelStateChange = Extract<
* @slot subtext - Slot used to render a subtext under the label (only visible within a selection panel).
* @slot suffix - Slot used to render additional content after the label (only visible within a selection panel).
* @slot badge - Use this slot to provide a `sbb-card-badge` (optional).
* @event {CustomEvent<void>} didChange - Deprecated. used for React. Will probably be removed once React 19 is available.
* @event {Event} change - Event fired on change.
* @event {InputEvent} input - Event fired on input.
*/
Expand All @@ -52,7 +51,6 @@ class SbbCheckboxPanelElement extends SbbPanelMixin(

// FIXME using ...super.events requires: https://github.com/sbb-design-systems/lyne-components/issues/2600
public static readonly events = {
didChange: 'didChange',
stateChange: 'stateChange',
panelConnected: 'panelConnected',
} as const;
Expand Down
9 changes: 4 additions & 5 deletions src/elements/checkbox/checkbox-panel/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,10 @@ If you don't want the label to appear next to the checkbox, you can use `aria-la

## Events

| Name | Type | Description | Inherited From |
| ----------- | ------------------- | -------------------------------------------------------------------------------- | -------------- |
| `change` | `Event` | Event fired on change. | |
| `didChange` | `CustomEvent<void>` | Deprecated. used for React. Will probably be removed once React 19 is available. | |
| `input` | `InputEvent` | Event fired on input. | |
| Name | Type | Description | Inherited From |
| -------- | ------------ | ---------------------- | -------------- |
| `change` | `Event` | Event fired on change. | |
| `input` | `InputEvent` | Event fired on input. | |

## Slots

Expand Down
5 changes: 0 additions & 5 deletions src/elements/checkbox/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import '../../visual-checkbox.js';
*
* @slot - Use the unnamed slot to add content to the `sbb-checkbox`.
* @slot icon - Slot used to render the checkbox icon (disabled inside a selection panel).
* @event {CustomEvent<void>} didChange - Deprecated. used for React. Will probably be removed once React 19 is available.
* @event {Event} change - Event fired on change.
* @event {InputEvent} input - Event fired on input.
*/
Expand All @@ -29,10 +28,6 @@ export
class SbbCheckboxElement extends SbbCheckboxCommonElementMixin(SbbIconNameMixin(LitElement)) {
public static override styles: CSSResultGroup = [checkboxCommonStyle, checkboxStyle];

public static readonly events = {
didChange: 'didChange',
} as const;

/** Size variant. */
@property({ reflect: true })
@getOverride((i, v) => i.group?.size ?? v)
Expand Down
9 changes: 4 additions & 5 deletions src/elements/checkbox/checkbox/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,10 @@ If you don't want the label to appear next to the checkbox, you can use `aria-la

## Events

| Name | Type | Description | Inherited From |
| ----------- | ------------------- | -------------------------------------------------------------------------------- | -------------- |
| `change` | `Event` | Event fired on change. | |
| `didChange` | `CustomEvent<void>` | Deprecated. used for React. Will probably be removed once React 19 is available. | |
| `input` | `InputEvent` | Event fired on input. | |
| Name | Type | Description | Inherited From |
| -------- | ------------ | ---------------------- | -------------- |
| `change` | `Event` | Event fired on change. | |
| `input` | `InputEvent` | Event fired on input. | |

## Slots

Expand Down
1 change: 0 additions & 1 deletion src/elements/core/mixins/form-associated-checkbox-mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ export const SbbFormAssociatedCheckboxMixin = <T extends Constructor<LitElement>

this.dispatchEvent(new InputEvent('input', { composed: true, bubbles: true }));
this.dispatchEvent(new Event('change', { bubbles: true }));
this.dispatchEvent(new CustomEvent('didChange', { bubbles: true }));
};
}

Expand Down
4 changes: 4 additions & 0 deletions src/elements/datepicker/datepicker/datepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,15 @@ describe(`sbb-datepicker`, () => {

it('renders and emit event on value change', async () => {
const changeSpy = new EventSpy('change', element);
const inputSpy = new EventSpy('input', element);
typeInElement(input, '20/01/2023');
expect(inputSpy.count).to.be.equal(10);

button.focus();
await changeSpy.calledOnce();
expect(input.value).to.be.equal('Fr, 20.01.2023');
expect(changeSpy.count).to.be.equal(1);
expect(inputSpy.count).to.be.equal(11);
});

it('renders and interpret two digit year correctly in 2000s', async () => {
Expand Down
29 changes: 15 additions & 14 deletions src/elements/datepicker/datepicker/datepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { SbbConnectedAbortController, SbbLanguageController } from '../../core/c
import { type DateAdapter, defaultDateAdapter } from '../../core/datetime.js';
import { forceType } from '../../core/decorators.js';
import { findInput, findReferencedElement } from '../../core/dom.js';
import { EventEmitter } from '../../core/eventing.js';
import { EventEmitter, forwardEventToHost } from '../../core/eventing.js';
import { i18nDateChangedTo, i18nDatePickerPlaceholder } from '../../core/i18n.js';
import type { SbbDateLike, SbbValidationChangeEvent } from '../../core/interfaces.js';
import type { SbbDatepickerButton } from '../common.js';
Expand Down Expand Up @@ -56,8 +56,8 @@ export const datepickerControlRegisteredEventFactory = (): CustomEvent =>
/**
* Combined with a native input, it displays the input's value as a formatted date.
*
* @event {CustomEvent<void>} didChange - Deprecated. used for React. Will probably be removed once React 19 is available.
* @event {CustomEvent<void>} change - Notifies that the connected input has changes.
* @event {CustomEvent<void>} input - Notifies that the connected input fired the input event.
* @event {CustomEvent<SbbInputUpdateEvent>} inputUpdated - Notifies that the attributes of the input connected to the datepicker have changes.
* @event {CustomEvent<void>} datePickerUpdated - Notifies that the attributes of the datepicker have changes.
* @event {CustomEvent<SbbValidationChangeEvent>} validationChange - Emits whenever the internal validation state changes.
Expand All @@ -67,7 +67,6 @@ export
class SbbDatepickerElement<T = Date> extends LitElement {
public static override styles: CSSResultGroup = style;
public static readonly events = {
didChange: 'didChange',
change: 'change',
inputUpdated: 'inputUpdated',
datePickerUpdated: 'datePickerUpdated',
Expand Down Expand Up @@ -111,14 +110,6 @@ class SbbDatepickerElement<T = Date> extends LitElement {
}
private _valueAsDate?: T | null;

/**
* @deprecated only used for React. Will probably be removed once React 19 is available.
*/
private _didChange: EventEmitter = new EventEmitter(this, SbbDatepickerElement.events.didChange, {
bubbles: true,
cancelable: true,
});

/** Notifies that the connected input has changes. */
private _change: EventEmitter = new EventEmitter(this, SbbDatepickerElement.events.change, {
bubbles: true,
Expand Down Expand Up @@ -249,7 +240,14 @@ class SbbDatepickerElement<T = Date> extends LitElement {
}

const options: AddEventListenerOptions = { signal: this._datePickerController.signal };
input.addEventListener('input', () => this._parseInput(), options);
input.addEventListener(
'input',
(e) => {
forwardEventToHost(e, this);
this._parseInput();
},
options,
);
input.addEventListener('change', () => this._handleInputChange(), options);
this._parseInput(true);
this._tryApplyFormatToInput();
Expand All @@ -269,7 +267,6 @@ class SbbDatepickerElement<T = Date> extends LitElement {
this._validateDate();
this._setAriaLiveMessage();
this._change.emit();
this._didChange.emit();
}

private _tryApplyFormatToInput(): boolean {
Expand All @@ -279,7 +276,11 @@ class SbbDatepickerElement<T = Date> extends LitElement {

const formattedDate = this.valueAsDate ? this._dateAdapter.format(this.valueAsDate!) : '';
if (formattedDate && this._inputElement.value !== formattedDate) {
this._inputElement.value = formattedDate;
// In order to support React onChange event, we have to get the setter and call it.
// https://github.com/facebook/react/issues/11600#issuecomment-345813130
const setValue = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')!.set!;
setValue.call(this._inputElement, formattedDate);

this._inputElement.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this._inputElement.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/elements/datepicker/datepicker/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,6 @@ Whenever the validation state changes (e.g., a valid value becomes invalid or vi
| ------------------- | --------------------------------------- | ----------------------------------------------------------------------------------- | -------------- |
| `change` | `CustomEvent<void>` | Notifies that the connected input has changes. | |
| `datePickerUpdated` | `CustomEvent<void>` | Notifies that the attributes of the datepicker have changes. | |
| `didChange` | `CustomEvent<void>` | Deprecated. used for React. Will probably be removed once React 19 is available. | |
| `input` | `CustomEvent<void>` | Notifies that the connected input fired the input event. | |
| `inputUpdated` | `CustomEvent<SbbInputUpdateEvent>` | Notifies that the attributes of the input connected to the datepicker have changes. | |
| `validationChange` | `CustomEvent<SbbValidationChangeEvent>` | Emits whenever the internal validation state changes. | |
19 changes: 16 additions & 3 deletions src/elements/form-field/form-field/form-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,10 +287,23 @@ class SbbFormFieldElement extends SbbNegativeMixin(SbbHydrationMixin(LitElement)
signal: this._inputAbortController.signal,
});

inputFocusElement = (this._input as SbbSelectElement).inputElement;
const selectInput = this._input as SbbSelectElement;
inputFocusElement = selectInput.inputElement;

// If inputElement is not yet ready, try a second time after updating.
if (!inputFocusElement) {
const controller = {
hostUpdated: () => {
selectInput.removeController(controller);
this._registerInputListener();
},
};

selectInput.addController(controller);
}
}

inputFocusElement.addEventListener(
inputFocusElement?.addEventListener(
'focusin',
() => {
this.toggleAttribute('data-input-focused', true);
Expand All @@ -304,7 +317,7 @@ class SbbFormFieldElement extends SbbNegativeMixin(SbbHydrationMixin(LitElement)
},
);

inputFocusElement.addEventListener(
inputFocusElement?.addEventListener(
'focusout',
() =>
['data-focus-origin', 'data-input-focused'].forEach((name) => this.removeAttribute(name)),
Expand Down
17 changes: 8 additions & 9 deletions src/elements/select/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,14 @@ Opened panel:

## Events

| Name | Type | Description | Inherited From |
| ----------- | ------------------- | -------------------------------------------------------------------------------- | ----------------------- |
| `change` | `CustomEvent<void>` | Notifies that the component's value has changed. | |
| `didChange` | `CustomEvent<void>` | Deprecated. used for React. Will probably be removed once React 19 is available. | |
| `didClose` | `CustomEvent<void>` | Emits whenever the `sbb-select` is closed. | SbbOpenCloseBaseElement |
| `didOpen` | `CustomEvent<void>` | Emits whenever the `sbb-select` is opened. | SbbOpenCloseBaseElement |
| `input` | `CustomEvent<void>` | Notifies that an option value has been selected. | |
| `willClose` | `CustomEvent<void>` | Emits whenever the `sbb-select` begins the closing transition. Can be canceled. | SbbOpenCloseBaseElement |
| `willOpen` | `CustomEvent<void>` | Emits whenever the `sbb-select` starts the opening transition. Can be canceled. | SbbOpenCloseBaseElement |
| Name | Type | Description | Inherited From |
| ----------- | ------------------- | ------------------------------------------------------------------------------- | ----------------------- |
| `change` | `CustomEvent<void>` | Notifies that the component's value has changed. | |
| `didClose` | `CustomEvent<void>` | Emits whenever the `sbb-select` is closed. | SbbOpenCloseBaseElement |
| `didOpen` | `CustomEvent<void>` | Emits whenever the `sbb-select` is opened. | SbbOpenCloseBaseElement |
| `input` | `CustomEvent<void>` | Notifies that an option value has been selected. | |
| `willClose` | `CustomEvent<void>` | Emits whenever the `sbb-select` begins the closing transition. Can be canceled. | SbbOpenCloseBaseElement |
| `willOpen` | `CustomEvent<void>` | Emits whenever the `sbb-select` starts the opening transition. Can be canceled. | SbbOpenCloseBaseElement |

## CSS Properties

Expand Down
9 changes: 0 additions & 9 deletions src/elements/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export interface SelectChange {
* It displays a panel with selectable options.
*
* @slot - Use the unnamed slot to add options.
* @event {CustomEvent<void>} didChange - Deprecated. used for React. Will probably be removed once React 19 is available.
* @event {CustomEvent<void>} change - Notifies that the component's value has changed.
* @event {CustomEvent<void>} input - Notifies that an option value has been selected.
* @event {CustomEvent<void>} willOpen - Emits whenever the `sbb-select` starts the opening transition. Can be canceled.
Expand Down Expand Up @@ -77,7 +76,6 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin(

// FIXME using ...super.events requires: https://github.com/sbb-design-systems/lyne-components/issues/2600
public static override readonly events = {
didChange: 'didChange',
change: 'change',
input: 'input',
stateChange: 'stateChange',
Expand Down Expand Up @@ -113,11 +111,6 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin(
/** The value displayed by the component. */
@state() private accessor _displayValue: string | null = null;

/**
* @deprecated only used for React. Will probably be removed once React 19 is available.
*/
private _didChange: EventEmitter = new EventEmitter(this, SbbSelectElement.events.didChange);

/** Notifies that the component's value has changed. */
private _change: EventEmitter = new EventEmitter(this, SbbSelectElement.events.change);

Expand Down Expand Up @@ -519,7 +512,6 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin(

this._input.emit();
this._change.emit();
this._didChange.emit();
}

/** When an option is unselected in `multiple`, removes it from value and updates displayValue. */
Expand All @@ -531,7 +523,6 @@ class SbbSelectElement extends SbbUpdateSchedulerMixin(

this._input.emit();
this._change.emit();
this._didChange.emit();
}
}

Expand Down
18 changes: 15 additions & 3 deletions src/elements/time-input/time-input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,25 @@ describe(`sbb-time-input`, () => {
it('should emit form events', async () => {
const changeSpy = new EventSpy('change', element);
const inputSpy = new EventSpy('input', element);
const nativeInputSpy = new EventSpy('input', input);
const nativeChangeSpy = new EventSpy('change', input);

typeInElement(input, '1');
input.focus();
await sendKeys({ press: '1' });
input.blur();
await waitForLitRender(element);

expect(changeSpy.count).to.be.greaterThan(0);
expect(inputSpy.count).to.be.greaterThan(0);
await nativeChangeSpy.calledOnce().then(() => {
expect(input.value).to.be.equal('01:00');
});
await changeSpy.calledOnce().then(() => {
expect(input.value).to.be.equal('01:00');
});

expect(inputSpy.count, 'sbb-time-input input event').to.be.equal(2);
expect(changeSpy.count, 'sbb-time-input change event').to.be.equal(1);
expect(nativeInputSpy.count, 'input input event').to.be.equal(2);
expect(nativeChangeSpy.count, 'input change event').to.be.equal(1);
});

it('should emit validation change event', async () => {
Expand Down
27 changes: 18 additions & 9 deletions src/elements/time-input/time-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,18 +144,22 @@ class SbbTimeInputElement extends LitElement {
);
this._inputElement.addEventListener(
'change',
(event: Event) => this._updateValueAndEmitChange(event),
(event: Event) => this._updateValue((event.target as HTMLInputElement).value),
{
signal: this._abortController.signal,
capture: true,
},
);
this._inputElement.addEventListener(
'change',
(event: Event) => {
this._emitChange(event);
this._updateAccessibilityMessage();
},
{
signal: this._abortController.signal,
},
);
}

/** Applies the correct format to values and triggers event dispatch. */
private _updateValueAndEmitChange(event: Event): void {
this._updateValue((event.target as HTMLInputElement).value);
this._emitChange(event);
this._updateAccessibilityMessage();
}

/**
Expand All @@ -175,7 +179,12 @@ class SbbTimeInputElement extends LitElement {
const isTimeValid = !!time && this._isTimeValid(time);
const isEmptyOrValid = !value || value.trim() === '' || isTimeValid;
if (isEmptyOrValid && time) {
this._inputElement.value = this._formatValue(time);
// In order to support React onChange event, we have to get the setter and call it.
// https://github.com/facebook/react/issues/11600#issuecomment-345813130
const setValue = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')!.set!;
setValue.call(this._inputElement, this._formatValue(time));

this._inputElement.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
}

const wasValid = !this._inputElement.hasAttribute('data-sbb-invalid');
Expand Down
Loading

0 comments on commit da64d5d

Please sign in to comment.