StencilJS+React封装的输入框悬停自动补全后无法使用该如何解决?
问题描述
- 使用StencilJS开发的组件出现异常:无需点击自动填充选项,仅悬停在选项上,输入框就会触发自动补全,之后输入框完全无法使用
- 该组件封装为StencilJS组件前运行正常,转为Web组件后才出现问题,全网未找到对应解决方案
- 故障演示(仅悬停选项,从未点击):

- 点击选项的正常效果(404为预期行为,可忽略):

初始组件代码
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
相关产品推荐
相关产品推荐

