Skip to content

fix: resolve TypeScript interface conflicts between component methods and HTMLElement#6282

Merged
christian-bromann merged 2 commits intomainfrom
cb/native-method-conflict-resolution
Jun 11, 2025
Merged

fix: resolve TypeScript interface conflicts between component methods and HTMLElement#6282
christian-bromann merged 2 commits intomainfrom
cb/native-method-conflict-resolution

Conversation

@christian-bromann
Copy link
Copy Markdown
Member

What is the current behavior?

GitHub Issue Number: #4467

When Stencil components have @Method() decorators on methods with names that match HTMLElement methods (such as focus, blur, click, etc.), TypeScript compilation fails with interface conflict errors.

Example of failing code:

@Component({ tag: 'my-button' })
export class MyButton {
  @Method()
  async focus(): Promise<void> {
    // Custom focus implementation
  }
}

Current error:

Interface 'HTMLMyButtonElement' cannot simultaneously extend types 'MyButton' and 'HTMLStencilElement'. 
Named property 'focus' of types 'MyButton' and 'HTMLStencilElement' are not identical.

This occurs because:

  • Component method: focus(): Promise<void>
  • HTMLElement method: focus(options?: FocusOptions): void

What is the new behavior?

The TypeScript type generation now automatically detects and resolves method name conflicts between Stencil component methods and native HTMLElement methods.

Solution approach:

  1. Conflict Detection: Identifies when component methods conflict with a comprehensive list of HTMLElement/Element/Node/EventTarget methods
  2. Smart Interface Generation: Uses TypeScript's Omit utility type to exclude conflicting methods and re-declare them with correct signatures
  3. Backward Compatibility: Components without conflicts continue using the standard interface generation

Generated interface examples:

Without conflicts (unchanged):

interface HTMLMyButtonElement extends Components.MyButton, HTMLStencilElement {
}

With conflicts (new):

interface HTMLMyButtonElement extends Omit<Components.MyButton, "focus">, HTMLStencilElement {
    "focus": () => Promise<void>;
}

Multiple conflicts:

interface HTMLMyButtonElement extends Omit<Components.MyButton, "focus" | "blur" | "click">, HTMLStencilElement {
    "focus": () => Promise<void>;
    "blur": () => Promise<void>;
    "click": (force?: boolean) => Promise<void>;
}

Comprehensive method coverage:

  • HTMLElement methods: focus, blur, click, attachInternals, etc.
  • Element methods: getAttribute, setAttribute, querySelector, animate, etc.
  • Node methods: appendChild, removeChild, cloneNode, contains, etc.
  • EventTarget methods: addEventListener, removeEventListener, dispatchEvent

Documentation

N/A - This is an internal type generation improvement that doesn't require user-facing documentation changes.

Does this introduce a breaking change?

  • Yes
  • No

This change is fully backward compatible. Existing components without method conflicts will generate identical interfaces as before. Components with conflicts will now compile successfully instead of failing.

Testing

Comprehensive test suite added (src/compiler/types/tests/generate-component-types.spec.ts):

  1. Standard interface generation - Verifies unchanged behavior for non-conflicting components
  2. Single method conflict - Tests focus method conflict resolution
  3. Multiple method conflicts - Tests focus, blur, and click conflicts simultaneously
  4. Mixed scenarios - Components with both conflicting and non-conflicting methods
  5. JSDoc preservation - Ensures documentation is maintained for conflicting methods
  6. Comprehensive method coverage - Tests various HTMLElement methods from our extensive list

Manual testing:

  • Created test components with various conflicting method names
  • Verified TypeScript compilation succeeds
  • Confirmed generated .d.ts files contain correct interface definitions
  • Tested that component methods work correctly at runtime

Other information

Key technical details:

  • Added HTML_ELEMENT_METHODS constant with 80+ method names covering the complete DOM API surface
  • Implemented conflict detection during type generation phase
  • Used Omit<Components.ComponentName, "methodName"> pattern for clean type exclusion
  • Preserved JSDoc comments for conflicting methods in generated interfaces
  • Maintained proper optional/required method signatures

Files modified:

  • src/compiler/types/generate-component-types.ts - Main implementation
  • src/compiler/types/constants.ts - HTML method names constant
  • src/compiler/types/tests/generate-component-types.spec.ts - Comprehensive tests

This fix enables developers to use any method names in their Stencil components without worrying about conflicts with native DOM APIs, while maintaining full type safety.

… and HTMLElement

Fixes #4467

When Stencil components have @method() decorators on methods with names that
match HTMLElement methods (like 'focus', 'blur', 'click'), TypeScript fails
to compile due to incompatible method signatures between the component
interface and HTMLStencilElement.

This commit implements conflict detection and resolution by:

- Adding comprehensive HTML_ELEMENT_METHODS constant covering HTMLElement,
  Element, Node, and EventTarget method names
- Detecting method name conflicts during type generation
- Using TypeScript's Omit utility type to exclude conflicting methods from
  component interface and re-declare them with correct signatures
- Preserving JSDoc documentation for conflicting methods
- Maintaining backward compatibility for components without conflicts

The solution generates interfaces like:
`interface HTMLMyButtonElement extends Omit<Components.MyButton, "focus">, HTMLStencilElement`
with explicit method overrides when conflicts are detected.

Includes comprehensive tests covering single conflicts, multiple conflicts,
mixed scenarios, and JSDoc preservation.
@christian-bromann christian-bromann merged commit 614d305 into main Jun 11, 2025
72 checks passed
@christian-bromann christian-bromann deleted the cb/native-method-conflict-resolution branch June 11, 2025 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant