Skip to content

Commit b8a0db8

Browse files
authored
refactor: Moved constructor event bindings to SSR safe API (#1757)
1 parent a1d32c9 commit b8a0db8

File tree

17 files changed

+106
-41
lines changed

17 files changed

+106
-41
lines changed

src/components/accordion/accordion.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
shiftKey,
1212
} from '../common/controllers/key-bindings.js';
1313
import { registerComponent } from '../common/definitions/register.js';
14-
import { first, last } from '../common/util.js';
14+
import { addSafeEventListener, first, last } from '../common/util.js';
1515
import IgcExpansionPanelComponent from '../expansion-panel/expansion-panel.js';
1616
import { styles } from './themes/accordion.base.css.js';
1717

@@ -52,7 +52,7 @@ export default class IgcAccordionComponent extends LitElement {
5252
constructor() {
5353
super();
5454

55-
this.addEventListener('igcOpening', this.handlePanelOpening);
55+
addSafeEventListener(this, 'igcOpening' as any, this.handlePanelOpening);
5656

5757
addKeybindings(this, {
5858
skip: this.skipKeybinding,

src/components/calendar/days-view/days-view.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ import { createDateTimeFormatters } from '../../common/localization/intl-formatt
1212
import type { Constructor } from '../../common/mixins/constructor.js';
1313
import { EventEmitterMixin } from '../../common/mixins/event-emitter.js';
1414
import { partMap } from '../../common/part-map.js';
15-
import { chunk, first, last, take } from '../../common/util.js';
15+
import {
16+
addSafeEventListener,
17+
chunk,
18+
first,
19+
last,
20+
take,
21+
} from '../../common/util.js';
1622
import { IgcCalendarBaseComponent } from '../base.js';
1723
import {
1824
areSameMonth,
@@ -138,7 +144,7 @@ export default class IgcDaysViewComponent extends EventEmitterMixin<
138144
bindingDefaults: { preventDefault: true },
139145
}).setActivateHandler(this.handleInteraction);
140146

141-
this.addEventListener('click', this.handleInteraction);
147+
addSafeEventListener(this, 'click', this.handleInteraction);
142148
}
143149

144150
public override connectedCallback() {

src/components/calendar/months-view/months-view.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { createDateTimeFormatters } from '../../common/localization/intl-formatt
1212
import type { Constructor } from '../../common/mixins/constructor.js';
1313
import { EventEmitterMixin } from '../../common/mixins/event-emitter.js';
1414
import { partMap } from '../../common/part-map.js';
15-
import { chunk } from '../../common/util.js';
15+
import { addSafeEventListener, chunk } from '../../common/util.js';
1616
import { areSameMonth, getViewElement, MONTHS_PER_ROW } from '../helpers.js';
1717
import { CalendarDay } from '../model.js';
1818
import { all } from '../themes/year-month.js';
@@ -90,7 +90,7 @@ export default class IgcMonthsViewComponent extends EventEmitterMixin<
9090
bindingDefaults: { preventDefault: true },
9191
}).setActivateHandler(this.handleInteraction);
9292

93-
this.addEventListener('click', this.handleInteraction);
93+
addSafeEventListener(this, 'click', this.handleInteraction);
9494
}
9595

9696
public override connectedCallback() {

src/components/calendar/years-view/years-view.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { registerComponent } from '../../common/definitions/register.js';
1010
import type { Constructor } from '../../common/mixins/constructor.js';
1111
import { EventEmitterMixin } from '../../common/mixins/event-emitter.js';
1212
import { partMap } from '../../common/part-map.js';
13-
import { chunk } from '../../common/util.js';
13+
import { addSafeEventListener, chunk } from '../../common/util.js';
1414
import { getViewElement, getYearRange, YEARS_PER_ROW } from '../helpers.js';
1515
import { CalendarDay } from '../model.js';
1616
import { all } from '../themes/year-month.js';
@@ -71,7 +71,7 @@ export default class IgcYearsViewComponent extends EventEmitterMixin<
7171
bindingDefaults: { preventDefault: true },
7272
}).setActivateHandler(this.handleInteraction);
7373

74-
this.addEventListener('click', this.handleInteraction);
74+
addSafeEventListener(this, 'click', this.handleInteraction);
7575
}
7676

7777
public override connectedCallback() {

src/components/carousel/carousel.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import type { Constructor } from '../common/mixins/constructor.js';
3434
import { EventEmitterMixin } from '../common/mixins/event-emitter.js';
3535
import { partMap } from '../common/part-map.js';
3636
import {
37+
addSafeEventListener,
3738
asNumber,
3839
createCounter,
3940
findElementFromEventPath,
@@ -328,13 +329,12 @@ export default class IgcCarouselComponent extends EventEmitterMixin<
328329
},
329330
});
330331

331-
this.addEventListener('pointerenter', this.handlePointerEnter);
332-
this.addEventListener('pointerleave', this.handlePointerLeave);
333-
this.addEventListener('pointerdown', () => {
332+
addSafeEventListener(this, 'pointerenter', this.handlePointerEnter);
333+
addSafeEventListener(this, 'pointerleave', this.handlePointerLeave);
334+
addSafeEventListener(this, 'pointerdown', () => {
334335
this._hasInnerFocus = false;
335336
});
336-
337-
this.addEventListener('keyup', () => {
337+
addSafeEventListener(this, 'keyup', () => {
338338
this._hasInnerFocus = true;
339339
});
340340

src/components/combo/combo.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
} from '../common/mixins/forms/form-value.js';
2424
import { partMap } from '../common/part-map.js';
2525
import {
26+
addSafeEventListener,
2627
asArray,
2728
equal,
2829
findElementFromEventPath,
@@ -475,8 +476,9 @@ export default class IgcComboComponent<
475476
constructor() {
476477
super();
477478

478-
this.addEventListener('blur', this._handleBlur);
479+
addSafeEventListener(this, 'blur', this._handleBlur);
479480

481+
// TODO
480482
this.addEventListener(
481483
'keydown',
482484
this._navigation.navigateHost.bind(this._navigation)

src/components/common/mixins/forms/associated.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { LitElement } from 'lit';
22
import { property } from 'lit/decorators.js';
3-
import { isFunction, isString } from '../../util.js';
3+
import { addSafeEventListener, isFunction, isString } from '../../util.js';
44
import type { Validator } from '../../validators.js';
55
import type { Constructor } from '../constructor.js';
66
import type { FormValue } from './form-value.js';
@@ -93,7 +93,8 @@ function BaseFormAssociated<T extends Constructor<LitElement>>(base: T) {
9393
constructor(...args: any[]) {
9494
super(args);
9595
this.__internals = this.attachInternals();
96-
this.addEventListener('invalid', this._handleInvalid);
96+
97+
addSafeEventListener(this, 'invalid', this._handleInvalid);
9798
}
9899

99100
public override connectedCallback(): void {

src/components/common/util.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { isServer } from 'lit';
2+
13
export const asPercent = (part: number, whole: number) => (part / whole) * 100;
24

35
export const clamp = (number: number, min: number, max: number) =>
@@ -282,6 +284,33 @@ export function addWeakEventListener(
282284
element.addEventListener(event, wrapped, options);
283285
}
284286

287+
type EventTypeOf<T extends keyof HTMLElementEventMap | keyof WindowEventMap> =
288+
(HTMLElementEventMap & WindowEventMap)[T];
289+
290+
/**
291+
* Safely adds an event listener to an HTMLElement, automatically handling
292+
* server-side rendering environments by doing nothing if `isServer` is true.
293+
* This function also correctly binds the `handler`'s `this` context to the `target` element
294+
* and ensures proper event type inference.
295+
*/
296+
export function addSafeEventListener<
297+
E extends keyof HTMLElementEventMap | keyof WindowEventMap,
298+
>(
299+
target: HTMLElement,
300+
eventName: E,
301+
handler: (event: EventTypeOf<E>) => unknown,
302+
options?: boolean | AddEventListenerOptions
303+
): void {
304+
if (isServer) {
305+
return;
306+
}
307+
308+
const boundHandler = (event: Event) =>
309+
handler.call(target, event as EventTypeOf<E>);
310+
311+
target.addEventListener(eventName, boundHandler, options);
312+
}
313+
285314
/**
286315
* Returns whether a given collection is empty.
287316
*/

src/components/date-picker/date-picker.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
type FormValueOf,
3737
} from '../common/mixins/forms/form-value.js';
3838
import {
39+
addSafeEventListener,
3940
createCounter,
4041
findElementFromEventPath,
4142
isEmpty,
@@ -461,8 +462,8 @@ export default class IgcDatePickerComponent extends FormAssociatedRequiredMixin(
461462
constructor() {
462463
super();
463464

464-
this.addEventListener('focusin', this._handleFocusIn);
465-
this.addEventListener('focusout', this._handleFocusOut);
465+
addSafeEventListener(this, 'focusin', this._handleFocusIn);
466+
addSafeEventListener(this, 'focusout', this._handleFocusOut);
466467

467468
this._rootClickController.update({ hideCallback: this._handleClosing });
468469

src/components/date-range-picker/date-range-picker.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
type FormValueOf,
3939
} from '../common/mixins/forms/form-value.js';
4040
import {
41+
addSafeEventListener,
4142
asNumber,
4243
clamp,
4344
createCounter,
@@ -581,9 +582,10 @@ export default class IgcDateRangePickerComponent extends FormAssociatedRequiredM
581582
constructor() {
582583
super();
583584

585+
addSafeEventListener(this, 'focusin', this._handleFocusIn);
586+
addSafeEventListener(this, 'focusout', this._handleFocusOut);
587+
584588
this._rootClickController.update({ hideCallback: this._handleClosing });
585-
this.addEventListener('focusin', this._handleFocusIn);
586-
this.addEventListener('focusout', this._handleFocusOut);
587589

588590
addKeybindings(this, {
589591
skip: () => this.disabled || this.readOnly,

0 commit comments

Comments
 (0)