From 2de65aaab21d7e539c2d03e6245248ed6eadc57b Mon Sep 17 00:00:00 2001 From: sudden3 Date: Tue, 13 Oct 2026 04:30:56 +0800 Subject: [PATCH 01/13] fix(tabs): match selected text to the primary indicator --- packages/react/src/tabs/tabs.scss | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/react/src/tabs/tabs.scss b/packages/react/src/tabs/tabs.scss index 15e90f2..0657515 100644 --- a/packages/react/src/tabs/tabs.scss +++ b/packages/react/src/tabs/tabs.scss @@ -70,10 +70,10 @@ color var(--leaf-motion-fast) var(--leaf-motion-easing), transform var(--leaf-motion-fast) var(--leaf-motion-easing); &[aria-selected='true'] { - color: var(--leaf-color-text); + color: var(--leaf-color-primary); font-weight: var(--leaf-font-weight); } - &:hover:not(:disabled) { + &:hover:not(:disabled):not([aria-selected='true']) { color: var(--leaf-color-text); } &:active:not(:disabled) { @@ -286,4 +286,7 @@ .leaf-tabs--card .leaf-tabs__tab[aria-selected='true'] { color: HighlightText; } + .leaf-tabs--line .leaf-tabs__tab[aria-selected='true'] { + color: Highlight; + } } From 0ce7817ca6c8b43200c595635590bbc59273fdc4 Mon Sep 17 00:00:00 2001 From: sudden3 Date: Tue, 13 Oct 2026 04:50:27 +0800 Subject: [PATCH 02/13] fix(form): preserve the first input after validation --- packages/react/src/form/form.test.tsx | 77 ++++++++++++++++++++++++++- packages/react/src/form/form.tsx | 5 +- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/packages/react/src/form/form.test.tsx b/packages/react/src/form/form.test.tsx index ef4a285..96dc153 100644 --- a/packages/react/src/form/form.test.tsx +++ b/packages/react/src/form/form.test.tsx @@ -1,7 +1,9 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { type ChangeEvent, useState } from 'react'; +import { flushSync } from 'react-dom'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { Button, ConfigProvider, Input, RadioGroup, Select } from '../index'; +import { Button, ConfigProvider, Input, RadioGroup, Select, Textarea } from '../index'; import { Form, FormField } from './form'; afterEach(() => vi.restoreAllMocks()); @@ -24,8 +26,81 @@ describe('Form', () => { expect(screen.getByRole('alert')).toHaveTextContent('Please fill out this field'); expect(input).toHaveAttribute('aria-invalid', 'true'); await userEvent.type(input, 'hello@example.com'); + expect(input).toHaveValue('hello@example.com'); expect(screen.queryByRole('alert')).toBeNull(); }); + it.each([ + { control: 'Input', controlled: false, formatError: false }, + { control: 'Input', controlled: true, formatError: false }, + { control: 'Textarea', controlled: false, formatError: false }, + { control: 'Textarea', controlled: true, formatError: false }, + { control: 'Input', controlled: false, formatError: true }, + { control: 'Input', controlled: true, formatError: true }, + ])( + 'preserves the first edit after validation in $control (controlled=$controlled, formatError=$formatError)', + async ({ control, controlled, formatError }) => { + const user = userEvent.setup(); + const onChange = vi.fn(); + const onInputCapture = vi.fn(); + const onInput = vi.fn(); + const initialValue = formatError ? 'a' : ''; + let submitted: FormDataEntryValue | null | undefined; + function Example() { + const [value, setValue] = useState(initialValue); + const controlProps = { + name: 'value', + value: controlled ? value : undefined, + defaultValue: controlled ? undefined : initialValue, + // Browsers can flush capture updates before bubbling; user-event batches both in act. + onInputCapture: () => flushSync(() => {}), + onChange: (event: ChangeEvent) => { + onChange(event.currentTarget.value); + setValue(event.currentTarget.value); + }, + }; + return ( + +
{ + event.preventDefault(); + submitted = new FormData(event.currentTarget).get('value'); + }} + > + + {control === 'Textarea' ? ( +