Repository: ionic-team/ionic-framework
File: core/src/components/input/input.tsx (TypeScript)

Code before:
  private emitValueChange() {
    const { value } = this;
    const newValue = value == null ? value : value.toString();
    this.ionChange.emit({ value: newValue });
    this.previousValue = newValue;
  }

  private shouldClearOnEdit() {
    const { type, clearOnEdit } = this;
    return clearOnEdit === undefined ? type === 'password' : clearOnEdit;
  }

  private getValue(): string {
    return typeof this.value === 'number' ? this.value.toString() : (this.value || '').toString();
  }

  private emitStyle() {
    this.ionStyle.emit({
      interactive: true,
      input: true,
      'has-placeholder': this.placeholder !== undefined,
      'has-value': this.hasValue(),
      'has-focus': this.hasFocus,
      'interactive-disabled': this.disabled,
    });
  }

  private onInput = (ev: Event) => {
    const input = ev.target as HTMLInputElement | null;
    if (input) {
      this.value = input.value || '';
    }
    this.ionInput.emit(ev as InputEvent);
  };

  private onChange = () => {
    this.emitValueChange();
  };

  private onBlur = (ev: FocusEvent) => {
    this.hasFocus = false;
    this.focusChanged();
    this.emitStyle();

    if (this.value != null && this.previousValue !== this.value) {
      /**
       * Emits the `ionChange` event when the input loses focus,
       * only if the input has a value and it does not match the
       * last emitted value. This occurs when the user performs
       * a clear action, and then blurs the input.
       */

Review comment (suggestion):
I know this would be a breaking change, but would it be simpler to pass along the native `Event` payload here instead of adding this logic? Would align the API closer to `ionInput` too. Not sure why this behavior was added in the first place though, so worth checking that out too.

Code after:
   */
  @Method()
  async setFocus() {
    if (this.nativeInput) {
      this.nativeInput.focus();
    }
  }

  /**
   * Returns the native `<input>` element used under the hood.
   */
  @Method()
  getInputElement(): Promise<HTMLInputElement> {
    return Promise.resolve(this.nativeInput!);
  }

  /**
   * Emits an `ionChange` event.
   *
   * This API should be called for user committed changes.
   * This API should not be used for external value changes.
   */
  private emitValueChange() {
    const { value } = this;
    const newValue = value == null ? value : value.toString();
    this.ionChange.emit({ value: newValue });
  }

  private shouldClearOnEdit() {
    const { type, clearOnEdit } = this;
    return clearOnEdit === undefined ? type === 'password' : clearOnEdit;
  }

  private getValue(): string {
    return typeof this.value === 'number' ? this.value.toString() : (this.value || '').toString();
  }

  private emitStyle() {
    this.ionStyle.emit({
      interactive: true,
      input: true,
      'has-placeholder': this.placeholder !== undefined,
      'has-value': this.hasValue(),
      'has-focus': this.hasFocus,
      'interactive-disabled': this.disabled,
    });
  }

  private onInput = (ev: Event) => {
    const input = ev.target as HTMLInputElement | null;
    if (input) {