diff --git a/README.md b/README.md index 564fdefd..734916f5 100644 --- a/README.md +++ b/README.md @@ -164,3 +164,20 @@ The release flow is handled by `@rc-component/np` through the `rc-np` command af ## License @rc-component/cascader is released under the [MIT](./LICENSE) license. + +### Clear configuration + +`allowClear` accepts a boolean or an `AllowClearConfig` object from `@rc-component/util`: + +```ts +interface AllowClearConfig { + clearIcon?: React.ReactNode; + disabled?: boolean; + tabIndex?: number; + label?: string; +} +``` + +All fields are optional. `disabled: true` behaves like `allowClear={false}`: the clear button and space reserved solely for it are removed; the input remains editable. Existing suffixes and other content are preserved. This changes the previous hidden-button spacing behavior where applicable. + +`tabIndex` affects only the clear button; `-1` removes it from Tab navigation while retaining mouse activation. `label` supplies its accessible name through `aria-label`. Defaults, clear values, callbacks, and component state restrictions are unchanged. diff --git a/package.json b/package.json index 5eeec21d..603b4ae3 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,9 @@ "prepare": "husky" }, "dependencies": { - "@rc-component/select": "~1.11.0", + "@rc-component/select": "~1.12.0", "@rc-component/tree": "~1.5.3", - "@rc-component/util": "^1.13.0", + "@rc-component/util": "^1.14.0", "clsx": "^2.1.1" }, "devDependencies": { diff --git a/tests/allowClear.spec.tsx b/tests/allowClear.spec.tsx new file mode 100644 index 00000000..639ec23c --- /dev/null +++ b/tests/allowClear.spec.tsx @@ -0,0 +1,37 @@ +import { fireEvent, render } from '@testing-library/react'; +import * as React from 'react'; +import type { AllowClearConfig } from '@rc-component/util'; +import Cascader from '../src'; + +describe.each([false, true])('allowClear checkable=%s', checkable => { + it('passes the complete configuration to Select and preserves clear callbacks', () => { + const onChange = jest.fn(); + const onClear = jest.fn(); + const props = { options: [{ value: 'one', label: 'One' }], onChange, onClear }; + const renderCascader = (allowClear: boolean | AllowClearConfig) => + checkable ? ( + + ) : ( + + ); + const config = { clearIcon: 'custom', label: 'Erase', tabIndex: -1 }; + const { container, getByRole, queryByRole, rerender } = render(renderCascader(false)); + const markup = container.innerHTML; + rerender(renderCascader({ ...config, disabled: true })); + expect(container.innerHTML).toBe(markup); + expect(queryByRole('button', { name: 'Erase' })).toBeNull(); + expect(onClear).not.toHaveBeenCalled(); + expect(onChange).not.toHaveBeenCalled(); + rerender(renderCascader(config)); + const button = getByRole('button', { name: 'Erase' }); + expect(button.tabIndex).toBe(-1); + expect(button).toHaveTextContent('custom'); + rerender(renderCascader({ ...config, tabIndex: 0 })); + expect(getByRole('button', { name: 'Erase' }).tabIndex).toBe(0); + fireEvent.click(getByRole('button', { name: 'Erase' })); + expect(onClear).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange.mock.calls[0][0]).toEqual(checkable ? [] : undefined); + expect(queryByRole('button', { name: 'Erase' })).toBeNull(); + }); +});