Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions src/components/accordion/accordion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
shiftKey,
} from '../common/controllers/key-bindings.js';
import { registerComponent } from '../common/definitions/register.js';
import { first, last } from '../common/util.js';
import { addSafeEventListener, first, last } from '../common/util.js';
import IgcExpansionPanelComponent from '../expansion-panel/expansion-panel.js';
import { styles } from './themes/accordion.base.css.js';

Expand Down Expand Up @@ -52,7 +52,7 @@ export default class IgcAccordionComponent extends LitElement {
constructor() {
super();

this.addEventListener('igcOpening', this.handlePanelOpening);
addSafeEventListener(this, 'igcOpening' as any, this.handlePanelOpening);

addKeybindings(this, {
skip: this.skipKeybinding,
Expand Down
10 changes: 8 additions & 2 deletions src/components/calendar/days-view/days-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ import { createDateTimeFormatters } from '../../common/localization/intl-formatt
import type { Constructor } from '../../common/mixins/constructor.js';
import { EventEmitterMixin } from '../../common/mixins/event-emitter.js';
import { partMap } from '../../common/part-map.js';
import { chunk, first, last, take } from '../../common/util.js';
import {
addSafeEventListener,
chunk,
first,
last,
take,
} from '../../common/util.js';
import { IgcCalendarBaseComponent } from '../base.js';
import {
areSameMonth,
Expand Down Expand Up @@ -138,7 +144,7 @@ export default class IgcDaysViewComponent extends EventEmitterMixin<
bindingDefaults: { preventDefault: true },
}).setActivateHandler(this.handleInteraction);

this.addEventListener('click', this.handleInteraction);
addSafeEventListener(this, 'click', this.handleInteraction);
}

public override connectedCallback() {
Expand Down
4 changes: 2 additions & 2 deletions src/components/calendar/months-view/months-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { createDateTimeFormatters } from '../../common/localization/intl-formatt
import type { Constructor } from '../../common/mixins/constructor.js';
import { EventEmitterMixin } from '../../common/mixins/event-emitter.js';
import { partMap } from '../../common/part-map.js';
import { chunk } from '../../common/util.js';
import { addSafeEventListener, chunk } from '../../common/util.js';
import { areSameMonth, getViewElement, MONTHS_PER_ROW } from '../helpers.js';
import { CalendarDay } from '../model.js';
import { all } from '../themes/year-month.js';
Expand Down Expand Up @@ -90,7 +90,7 @@ export default class IgcMonthsViewComponent extends EventEmitterMixin<
bindingDefaults: { preventDefault: true },
}).setActivateHandler(this.handleInteraction);

this.addEventListener('click', this.handleInteraction);
addSafeEventListener(this, 'click', this.handleInteraction);
}

public override connectedCallback() {
Expand Down
4 changes: 2 additions & 2 deletions src/components/calendar/years-view/years-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { registerComponent } from '../../common/definitions/register.js';
import type { Constructor } from '../../common/mixins/constructor.js';
import { EventEmitterMixin } from '../../common/mixins/event-emitter.js';
import { partMap } from '../../common/part-map.js';
import { chunk } from '../../common/util.js';
import { addSafeEventListener, chunk } from '../../common/util.js';
import { getViewElement, getYearRange, YEARS_PER_ROW } from '../helpers.js';
import { CalendarDay } from '../model.js';
import { all } from '../themes/year-month.js';
Expand Down Expand Up @@ -71,7 +71,7 @@ export default class IgcYearsViewComponent extends EventEmitterMixin<
bindingDefaults: { preventDefault: true },
}).setActivateHandler(this.handleInteraction);

this.addEventListener('click', this.handleInteraction);
addSafeEventListener(this, 'click', this.handleInteraction);
}

public override connectedCallback() {
Expand Down
10 changes: 5 additions & 5 deletions src/components/carousel/carousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type { Constructor } from '../common/mixins/constructor.js';
import { EventEmitterMixin } from '../common/mixins/event-emitter.js';
import { partMap } from '../common/part-map.js';
import {
addSafeEventListener,
asNumber,
createCounter,
findElementFromEventPath,
Expand Down Expand Up @@ -328,13 +329,12 @@ export default class IgcCarouselComponent extends EventEmitterMixin<
},
});

this.addEventListener('pointerenter', this.handlePointerEnter);
this.addEventListener('pointerleave', this.handlePointerLeave);
this.addEventListener('pointerdown', () => {
addSafeEventListener(this, 'pointerenter', this.handlePointerEnter);
addSafeEventListener(this, 'pointerleave', this.handlePointerLeave);
addSafeEventListener(this, 'pointerdown', () => {
this._hasInnerFocus = false;
});

this.addEventListener('keyup', () => {
addSafeEventListener(this, 'keyup', () => {
this._hasInnerFocus = true;
});

Expand Down
4 changes: 3 additions & 1 deletion src/components/combo/combo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from '../common/mixins/forms/form-value.js';
import { partMap } from '../common/part-map.js';
import {
addSafeEventListener,
asArray,
equal,
findElementFromEventPath,
Expand Down Expand Up @@ -475,8 +476,9 @@ export default class IgcComboComponent<
constructor() {
super();

this.addEventListener('blur', this._handleBlur);
addSafeEventListener(this, 'blur', this._handleBlur);

// TODO
this.addEventListener(
'keydown',
this._navigation.navigateHost.bind(this._navigation)
Expand Down
5 changes: 3 additions & 2 deletions src/components/common/mixins/forms/associated.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { isFunction, isString } from '../../util.js';
import { addSafeEventListener, isFunction, isString } from '../../util.js';
import type { Validator } from '../../validators.js';
import type { Constructor } from '../constructor.js';
import type { FormValue } from './form-value.js';
Expand Down Expand Up @@ -93,7 +93,8 @@ function BaseFormAssociated<T extends Constructor<LitElement>>(base: T) {
constructor(...args: any[]) {
super(args);
this.__internals = this.attachInternals();
this.addEventListener('invalid', this._handleInvalid);

addSafeEventListener(this, 'invalid', this._handleInvalid);
}

public override connectedCallback(): void {
Expand Down
29 changes: 29 additions & 0 deletions src/components/common/util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isServer } from 'lit';

export const asPercent = (part: number, whole: number) => (part / whole) * 100;

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

type EventTypeOf<T extends keyof HTMLElementEventMap | keyof WindowEventMap> =
(HTMLElementEventMap & WindowEventMap)[T];

/**
* Safely adds an event listener to an HTMLElement, automatically handling
* server-side rendering environments by doing nothing if `isServer` is true.
* This function also correctly binds the `handler`'s `this` context to the `target` element
* and ensures proper event type inference.
*/
export function addSafeEventListener<
E extends keyof HTMLElementEventMap | keyof WindowEventMap,
>(
target: HTMLElement,
eventName: E,
handler: (event: EventTypeOf<E>) => unknown,
options?: boolean | AddEventListenerOptions
): void {
if (isServer) {
return;
}

const boundHandler = (event: Event) =>
handler.call(target, event as EventTypeOf<E>);

target.addEventListener(eventName, boundHandler, options);
}

/**
* Returns whether a given collection is empty.
*/
Expand Down
5 changes: 3 additions & 2 deletions src/components/date-picker/date-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
type FormValueOf,
} from '../common/mixins/forms/form-value.js';
import {
addSafeEventListener,
createCounter,
findElementFromEventPath,
isEmpty,
Expand Down Expand Up @@ -461,8 +462,8 @@ export default class IgcDatePickerComponent extends FormAssociatedRequiredMixin(
constructor() {
super();

this.addEventListener('focusin', this._handleFocusIn);
this.addEventListener('focusout', this._handleFocusOut);
addSafeEventListener(this, 'focusin', this._handleFocusIn);
addSafeEventListener(this, 'focusout', this._handleFocusOut);

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

Expand Down
6 changes: 4 additions & 2 deletions src/components/date-range-picker/date-range-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
type FormValueOf,
} from '../common/mixins/forms/form-value.js';
import {
addSafeEventListener,
asNumber,
clamp,
createCounter,
Expand Down Expand Up @@ -581,9 +582,10 @@ export default class IgcDateRangePickerComponent extends FormAssociatedRequiredM
constructor() {
super();

addSafeEventListener(this, 'focusin', this._handleFocusIn);
addSafeEventListener(this, 'focusout', this._handleFocusOut);

this._rootClickController.update({ hideCallback: this._handleClosing });
this.addEventListener('focusin', this._handleFocusIn);
this.addEventListener('focusout', this._handleFocusOut);

addKeybindings(this, {
skip: () => this.disabled || this.readOnly,
Expand Down
6 changes: 3 additions & 3 deletions src/components/focus-trap/focus-trap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { css, html, LitElement, nothing } from 'lit';
import { property, state } from 'lit/decorators.js';

import { registerComponent } from '../common/definitions/register.js';
import { isDefined } from '../common/util.js';
import { addSafeEventListener, isDefined } from '../common/util.js';

/* blazorSuppress */
/**
Expand Down Expand Up @@ -49,8 +49,8 @@ export default class IgcFocusTrapComponent extends LitElement {
constructor() {
super();

this.addEventListener('focusin', this.onFocusIn);
this.addEventListener('focusout', this.onFocusOut);
addSafeEventListener(this, 'focusin', this.onFocusIn);
addSafeEventListener(this, 'focusout', this.onFocusOut);
}

private onFocusIn() {
Expand Down
5 changes: 3 additions & 2 deletions src/components/ripple/ripple.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { html, LitElement } from 'lit';

import { registerComponent } from '../common/definitions/register.js';
import { addSafeEventListener } from '../common/util.js';
import { styles } from './ripple.material.css.js';

const rippleFrames: Keyframe[] = [
Expand Down Expand Up @@ -34,13 +35,13 @@ export default class IgcRippleComponent extends LitElement {
public static override styles = styles;

/* blazorSuppress */
public static register() {
public static register(): void {
registerComponent(IgcRippleComponent);
}

constructor() {
super();
this.addEventListener('pointerdown', this.handler);
addSafeEventListener(this, 'pointerdown', this.handler);
}

private handler = ({ clientX, clientY }: PointerEvent) => {
Expand Down
13 changes: 9 additions & 4 deletions src/components/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ import {
type FormValueOf,
} from '../common/mixins/forms/form-value.js';
import { partMap } from '../common/part-map.js';
import { findElementFromEventPath, isEmpty, isString } from '../common/util.js';
import {
addSafeEventListener,
findElementFromEventPath,
isEmpty,
isString,
} from '../common/util.js';
import IgcIconComponent from '../icon/icon.js';
import IgcInputComponent from '../input/input.js';
import IgcPopoverComponent, {
Expand Down Expand Up @@ -291,9 +296,9 @@ export default class IgcSelectComponent extends FormAssociatedRequiredMixin(
.set(spaceBar, this.onSpaceBarKey)
.set(enterKey, this.onEnterKey);

this.addEventListener('keydown', this.handleSearch);
this.addEventListener('focusin', this.handleFocusIn);
this.addEventListener('focusout', this.handleFocusOut);
addSafeEventListener(this, 'keydown', this.handleSearch);
addSafeEventListener(this, 'focusin', this.handleFocusIn);
addSafeEventListener(this, 'focusout', this.handleFocusOut);
}

protected override createRenderRoot() {
Expand Down
10 changes: 6 additions & 4 deletions src/components/slider/slider-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { blazorDeepImport } from '../common/decorators/blazorDeepImport.js';
import { watch } from '../common/decorators/watch.js';
import {
addSafeEventListener,
asNumber,
asPercent,
clamp,
Expand Down Expand Up @@ -280,10 +281,11 @@ export class IgcSliderBaseComponent extends LitElement {

constructor() {
super();
this.addEventListener('pointerdown', this.pointerDown);
this.addEventListener('pointermove', this.pointerMove);
this.addEventListener('lostpointercapture', this.lostPointerCapture);
this.addEventListener('keyup', this.handleKeyUp);

addSafeEventListener(this, 'pointerdown', this.pointerDown);
addSafeEventListener(this, 'pointermove', this.pointerMove);
addSafeEventListener(this, 'lostpointercapture', this.lostPointerCapture);
addSafeEventListener(this, 'keyup', this.handleKeyUp);

addKeybindings(this, {
skip: () => this.disabled,
Expand Down
11 changes: 8 additions & 3 deletions src/components/textarea/textarea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import {
type FormValueOf,
} from '../common/mixins/forms/form-value.js';
import { partMap } from '../common/part-map.js';
import { asNumber, createCounter, isEmpty } from '../common/util.js';
import {
addSafeEventListener,
asNumber,
createCounter,
isEmpty,
} from '../common/util.js';
import type {
RangeTextSelectMode,
SelectionRangeDirection,
Expand Down Expand Up @@ -312,8 +317,8 @@ export default class IgcTextareaComponent extends FormAssociatedRequiredMixin(
callback: this._setAreaHeight,
});

this.addEventListener('focus', this._handleFocus);
this.addEventListener('blur', this._handleBlur);
addSafeEventListener(this, 'focus', this._handleFocus);
addSafeEventListener(this, 'blur', this._handleBlur);
}

protected override createRenderRoot(): HTMLElement | DocumentFragment {
Expand Down
17 changes: 13 additions & 4 deletions src/components/tree/tree-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import IgcCheckboxComponent from '../checkbox/checkbox.js';
import { watch } from '../common/decorators/watch.js';
import { registerComponent } from '../common/definitions/register.js';
import { partMap } from '../common/part-map.js';
import { findElementFromEventPath, isLTR } from '../common/util.js';
import {
addSafeEventListener,
findElementFromEventPath,
isLTR,
} from '../common/util.js';
import IgcIconComponent from '../icon/icon.js';
import IgcCircularProgressComponent from '../progress/circular-progress.js';
import { styles } from './themes/item.base.css.js';
Expand Down Expand Up @@ -222,6 +226,14 @@ export default class IgcTreeItemComponent extends LitElement {
}
}

constructor() {
super();

addSafeEventListener(this, 'click', this.itemClick);
addSafeEventListener(this, 'focus', this.onFocus);
addSafeEventListener(this, 'blur', this.onBlur);
}

public override connectedCallback(): void {
super.connectedCallback();
this.tree = this.closest('igc-tree') as IgcTreeComponent;
Expand All @@ -230,9 +242,6 @@ export default class IgcTreeItemComponent extends LitElement {
) as IgcTreeItemComponent | null;
this.level = this.parent ? this.parent.level + 1 : 0;
this.setAttribute('role', 'treeitem');
this.addEventListener('blur', this.onBlur);
this.addEventListener('focus', this.onFocus);
this.addEventListener('click', this.itemClick);
this.activeChange();
// if the item is not added/moved runtime
if (this.init) {
Expand Down
Loading