You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

StencilJS+React封装的输入框悬停自动补全后无法使用该如何解决?

问题描述

  • 使用StencilJS开发的组件出现异常:无需点击自动填充选项,仅悬停在选项上,输入框就会触发自动补全,之后输入框完全无法使用
  • 该组件封装为StencilJS组件前运行正常,转为Web组件后才出现问题,全网未找到对应解决方案
  • 故障演示(仅悬停选项,从未点击):
    video of broken input, hovering over option and never clicking
  • 点击选项的正常效果(404为预期行为,可忽略):
    same input, but when I click an option

初始组件代码

import { Component, h, Prop } from '@stencil/core';

@Component({
  tag: 'nwh-input',
  styleUrl: 'nwh-input.scss',
  shadow: true,
})
export class NwhInput {
  @Prop() customId?: string;
  @Prop() customAriaLabel: string;
  @Prop() required?: boolean;
  @Prop() changeFunction?: (event: any) => void;
  @Prop() type?: string;
  @Prop() value?: string;
  @Prop() label: string;
  @Prop() disabled?: boolean;
  @Prop() min?: number;
  @Prop() max?: number;
  @Prop() minLength?: number;
  @Prop() maxLength?: number;
  @Prop() name?: string;
  @Prop() error?: string | boolean;
  @Prop() success?: string | boolean;
  @Prop() info?: string | boolean;
  @Prop() inputRef?: any;
  @Prop() size?: any;
  @Prop() icon?: string;
  @Prop() baseClassName: string;
  @Prop() focusFunction: (event: any) => void;
  @Prop() blurFunction: (event: any) => void;
  @Prop() baseDefaultValue: string;
  @Prop() labelClass: string;

  render() {
    return (
      <div class="wrapper">
        {this.icon && <i class={`input__icon fal ${this.icon}`} />}

        {!this.disabled && (
          <label class={this.labelClass}>
            {this.label}
            {this.required && <span class="required">*</span>}
          </label>
        )}

        <input
          id={this.customId}
          class={this.baseClassName}
          onInput={this.changeFunction}
          onFocus={this.focusFunction}
          onBlur={this.blurFunction}
          type={this.type}
          defaultValue={this.baseDefaultValue}
          disabled={this.disabled}
          aria-label={this.customAriaLabel}
          min={this.min}
          max={this.max}
          minLength={this.minLength}
          maxLength={this.maxLength}
          name={this.name}
          ref={this.inputRef}
        />

        <nwh-helper-text error={this.error} info={this.info} success={this.success} />
      </div>
    );
  }
}

自行排查代码未发现明显问题,给input标签添加autoComplete="off"确实可以解决该问题,但直接关闭自动补全功能并不是合理的解决方案。

简化后的最小复现代码

简化逻辑后无样式的输入框仍然存在相同问题:

@Component({
  tag: 'nwh-input',
  styleUrl: 'nwh-input.scss',
  shadow: true,
})
export class NwhInput {
  @Prop() changeFunction?: (event: any) => void;
  @Prop() name?: string;
  @Prop() value?: string;

  @Event() changed: EventEmitter<string>;

  handleChange = e => {
    this.value = e.target?.value || null;
    this.changed.emit(this.value);
    this.changeFunction(e);
  };

  render() {
    return (
      <div class="wrapper">
        <input name={this.name} value={this.value} onInput={e => this.handleChange(e)} />
      </div>
    );
  }
}

补充:组件使用流程

1. 生成React代理组件(components.ts)

/* eslint-disable */
/* tslint:disable */
/* auto-generated react proxies */
import { createReactComponent } from './react-component-lib';

import type { JSX } from 'nwh-components';

export const NwhButton = /*@__PURE__*/createReactComponent<JSX.NwhButton, HTMLNwhButtonElement>('nwh-button');
export const NwhCheckbox = /*@__PURE__*/createReactComponent<JSX.NwhCheckbox, HTMLNwhCheckboxElement>('nwh-checkbox');
export const NwhDropdown = /*@__PURE__*/createReactComponent<JSX.NwhDropdown, HTMLNwhDropdownElement>('nwh-dropdown');
export const NwhHelperText = /*@__PURE__*/createReactComponent<JSX.NwhHelperText, HTMLNwhHelperTextElement>('nwh-helper-text');
export const NwhInput = /*@__PURE__*/createReactComponent<JSX.NwhInput, HTMLNwhInputElement>('nwh-input');

2. React层封装Input组件(Input.tsx)

import {
  NwhDropdown,
  NwhInput,
} from '../../../nwh-components-react/src/components';
import { InputSizes } from '../../constants';
import { IconType, OptionsType } from '../../types';

interface InputProps {
  id?: string;
  ariaLabel: string;
  showRequiredAsterisk?: boolean;
  onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
  onChangeDropdown?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
  type?: string;
  value?: string;
  label: string;
  disabled?: boolean;
  isDropdown?: boolean;
  defaultValue?: string;
  optionsType?: OptionsType;
  min?: number;
  max?: number;
  minLength?: number;
  maxLength?: number;
  className?: string;
  // Unique input attribute referenced by react-hook-form to run validation.
  name?: string;
  // The error message that shows if there is a validation error on the input.
  error?: string | boolean;
  // True by default. Displays the error triangle on invalid fields.
  showErrorTriangle?: boolean;
  showErrorIcon?: boolean;
  success?: string | boolean;
  info?: string | boolean;
  // The ref to the registered component that is being used for validation.
  // TODO: Update to use non-legacy type.
  inputRef?: LegacyRef<HTMLInputElement> & LegacyRef<HTMLSelectElement>;
  // Sets the width of the input without using a wrapping div. Default is 100%.
  size?: InputSizes;
  iconType?: IconType;
  iconColor?: string;
  iconClassName?: string;
}

const Input: React.FC<InputProps> = ({
  id,
  label,
  ariaLabel,
  onChange,
  onChangeDropdown,
  showRequiredAsterisk,
  type = 'text',
  value,
  disabled = false,
  isDropdown,
  optionsType,
  defaultValue,
  min,
  max,
  minLength,
  maxLength,
  className = '',
  error = '',
  success = '',
  info = '',
  name,
  inputRef,
  size = InputSizes.DEFAULT,
  iconType,
}): JSX.Element => {
  const [inputFocused, setInputFocused] = useState(false);
  const inputErrorClass = error && 'input__error';
  const inputSuccessClass = success && 'input__success';
  const baseClassName = `input ${className} input--${size} ${inputErrorClass} ${inputSuccessClass}`;
  const baseDefaultValue = defaultValue || value;

  // Apply active class for animated input labels if your input is focused or already has a value
  const focusClass =
    (inputFocused && 'label--active') || (baseDefaultValue && 'label--active');

  return (
    <div className="input__wrapper">
      {isDropdown ? (
        <NwhDropdown
          name={name}
          customId={id}
          customAriaLabel={ariaLabel}
          baseClassName={baseClassName}
          changeFunction={onChangeDropdown}
          focusFunction={() => setInputFocused(true)}
          blurFunction={() => !baseDefaultValue && setInputFocused(false)}
          type={type}
          disabled={disabled}
          inputRef={inputRef}
          error={error}
          info={info}
          success={success}
          labelClass={`label ${focusClass}`}
          label={label}
          required={showRequiredAsterisk}
          optionsType={optionsType}
          baseDefaultValue={baseDefaultValue}
        />
      ) : (
        <NwhInput
          name={name}
          customId={id}
          customAriaLabel={ariaLabel}
          baseClassName={baseClassName}
          changeFunction={onChange}
          focusFunction={() => setInputFocused(true)}
          blurFunction={() => !baseDefaultValue && setInputFocused(false)}
          type={type}
          baseDefaultValue={baseDefaultValue}
          disabled={disabled}
          min={min}
          max={max}
          minLength={minLength}
          maxLength={maxLength}
          inputRef={inputRef}
          error={error}
          info={info}
          success={success}
          icon={iconType}
          labelClass={`label ${focusClass}`}
          label={label}
          required={showRequiredAsterisk}
        />
      )}
    </div>
  );
};

export default Input;

3. 页面最终调用(create-account-email.tsx)

import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useForm } from 'react-hook-form';
import { useHistory } from 'react-router-dom';

import '../../assets/styles/styles.scss';

import * as createAccountActions from './actions/index';

import { hasValidEmail } from '../../util/formValidation';

import { AppRoutes, FormFieldErrorMessages, InputSizes } from '../../constants';
import { AnalyticsData } from '../../types';

import PageHeader from '../../components/page-header/page-header';
import Button from '../../components/button/Button';
import Input from '../../components/inputV2/Input';
import RequiredBadge from '../../components/requiredBadge/RequiredBadge';

import Envelope from '../../assets/images/envelope.svg';

const CreateAccountEmail: React.FC = (): JSX.Element => {
  // Redux hooks
  const initiator = useSelector(
    (state: InitialState) => state.account?.accountInfo?.initiator,
  );

  // Redux hooks
  const dispatch = useDispatch();
  const history = useHistory();

  // State hooks
  const [email, setEmail] = useState('');
  const [processingRequest, setProcessingRequest] = useState(false);

  // React-hook-form hooks.
  const { register, handleSubmit, errors } = useForm({
    reValidateMode: null,
  });

  const determineNavigation = () => {
    history.push(AppRoutes.CREATE_ACCOUNT_ENTER_CODE);
  };

  // Run form submission validations and route accordingly.
  const handleFormSubmission = e => {
    e.preventDefault();
    // Do not submit the form again if request is already processing.
    if (processingRequest) {
      return;
    }

    // Disables the submit button and subsequent button clicks from triggering
    // another request to the server.
    setProcessingRequest(true);

    dispatch(createAccountActions.setEzEmail(email));

    // Run handleSubmit hook to validate form fields using react-hook-form.
    // If there are no form errors, proceed with next action.
    handleSubmit(determineNavigation)();
  };

  // Reset processing state if form errors exist.
  useEffect(() => {
    const hasFormErrors = !!Object.keys(errors).length;

    if (hasFormErrors && processingRequest) {
      setProcessingRequest(false);
    }
  });

  // Analytics
  const createAccountEmailButton: AnalyticsData = {
    testId: 'CreateAccountEmail-Button-1',
    initiator,
  };

  return (
    <>
      <PageHeader headerText="Create an account" />
      <div className="create-account-email container">
        <img
          src={Envelope}
          alt="Envelope"
          className="create-account-email__icon"
        />
        <RequiredBadge />
        <h2 className="header-2 create-account-email__header">
          What's your email?
        </h2>
        <form onSubmit={handleFormSubmission}>
          <div className="create-account-email__input">
            <Input
              label="Email address"
              ariaLabel={email || 'Email address'}
              value={email}
              name="email"
              onChange={e => {
                setEmail(e.target.value);
              }}
              inputRef={register({ validate: () => hasValidEmail(email) })}
              error={errors?.email && FormFieldErrorMessages.REQUIRED}
              size={InputSizes.LARGE}
              showRequiredAsterisk
              info={`We’ll send a confirmation code to this email`}
            />
          </div>

          <Button
            onClick={handleFormSubmission}
            analyticsData={createAccountEmailButton}
            text="Continue"
            disabled={processingRequest}
          />
        </form>
      </div>
    </>
  );
};

export default CreateAccountEmail;

内容的提问来源于stack exchange,提问作者user14063037

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.15 18:15:00