优化(07-28-all-account-login-readiness): 调整密码框Tab顺序
这个提交包含在:
@@ -2712,3 +2712,70 @@ const MANAGEMENT_ACTIVE_PATHS = ["/people", "/system/users"];
|
||||
isManagementPath(pathname, permissions);
|
||||
<RequirePermission anyOf={getManagementRoutePermissions("people")}>...</RequirePermission>
|
||||
```
|
||||
|
||||
## Scenario: Authentication Password Input Sequential Focus
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: changing the shared password input or any login, password-change or password-recovery form.
|
||||
- Purpose: preserve the domestic continuous-form Tab flow without letting the inline visibility control create an extra sequential stop.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Shared component: `client/src/components/ui/password-input.tsx`.
|
||||
- Visibility control: native `button type="button"` with `tabIndex={-1}`, dynamic `aria-label`, boolean `aria-pressed` and `aria-controls=<input-id>`.
|
||||
- Sequential focus follows DOM order; do not use positive `tabIndex` values.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Pressing Tab from a password input skips its inline visibility button and reaches the next sequential form control in the DOM.
|
||||
- Existing links such as `忘记密码?` remain keyboard reachable; do not remove unrelated controls merely to make a password-to-submit shortcut.
|
||||
- Mouse and touch can still toggle visibility. The label changes between `显示密码` and `隐藏密码`, pressed state follows visibility, and the input value is preserved.
|
||||
- Login, password-change and password-recovery pages reuse the shared component and therefore keep one focus contract.
|
||||
- The focus change does not alter password validation, autofill, form submission, authentication APIs, email delivery or server configuration.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Tab lands on the inline eye button -> sequential-focus regression.
|
||||
- Tab skips the next real input, link or submit control -> DOM-order regression; do not repair with a positive `tabIndex`.
|
||||
- Visibility click changes type but clears the value -> input-state regression.
|
||||
- Toggle lacks its dynamic label, pressed state or input association -> accessibility regression.
|
||||
- Enter from the confirmation password input does not submit the valid form -> form-keyboard regression.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: reset form moves `新密码 -> 确认新密码 -> 重置密码`, while either eye control remains clickable and preserves the password.
|
||||
- Base: login password moves to the existing `忘记密码?` link because that link is the next DOM control, then continues to login submit.
|
||||
- Bad: keep the eye button in sequential Tab despite the approved form contract, remove all button semantics, or assign positive indices to force an artificial order.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Playwright asserts the login password reaches the forgot-password link, password-change fields traverse old/new/confirm/submit, and recovery fields traverse new/confirm/submit.
|
||||
- Assert `tabindex=-1`, `aria-controls`, dynamic `aria-label`, `aria-pressed`, click-to-toggle and value preservation.
|
||||
- Assert Enter from the valid confirmation-password field submits the form.
|
||||
- Run `cd client && npm run typecheck && npm run build`, focused authentication E2E, Trellis/process/sensitive gates and `git diff --check`.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```tsx
|
||||
<button type="button" aria-label="显示密码" onClick={toggleVisible}>
|
||||
<Eye />
|
||||
</button>
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```tsx
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
aria-controls={inputId}
|
||||
aria-label={visible ? "隐藏密码" : "显示密码"}
|
||||
aria-pressed={visible}
|
||||
onClick={toggleVisible}
|
||||
>
|
||||
<Eye aria-hidden="true" />
|
||||
</button>
|
||||
```
|
||||
|
||||
在新工单中引用
屏蔽一个用户