From 12a396d3e3ab0d33f651c798c185a422229cd6ec Mon Sep 17 00:00:00 2001 From: Andrey Yamanov Date: Tue, 11 Nov 2025 14:51:26 +0100 Subject: [PATCH 1/4] fix: allow form submission with single Enter press in ComboBox with allowsCustomValue --- .changeset/fix-combobox-enter-form-submit.md | 5 ++++ .cursor/rules/changeset.mdc | 9 ++++++ .cursor/rules/commit-changes.mdc | 6 ++++ .../fields/ComboBox/ComboBox.test.tsx | 30 +++++++++++++++++++ src/components/fields/ComboBox/ComboBox.tsx | 12 ++++++-- 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 .changeset/fix-combobox-enter-form-submit.md create mode 100644 .cursor/rules/changeset.mdc create mode 100644 .cursor/rules/commit-changes.mdc diff --git a/.changeset/fix-combobox-enter-form-submit.md b/.changeset/fix-combobox-enter-form-submit.md new file mode 100644 index 000000000..e403a5421 --- /dev/null +++ b/.changeset/fix-combobox-enter-form-submit.md @@ -0,0 +1,5 @@ +--- +"@cube-dev/ui-kit": patch +--- + +Fix ComboBox with `allowsCustomValue` to allow form submission with single Enter press when typing custom values that don't match any items. diff --git a/.cursor/rules/changeset.mdc b/.cursor/rules/changeset.mdc new file mode 100644 index 000000000..61a154d4b --- /dev/null +++ b/.cursor/rules/changeset.mdc @@ -0,0 +1,9 @@ +--- +description: When user asked to add a changeset +alwaysApply: false +--- + +Place the changeset to `/.changeset` folder. +Use `patch` version for small fixed (even with breaking changes if they are minor). Use `minor` version for bigger features and noticeable breaking changes. +Use diff of the working tree to analyze changes. If it's empty then use the diff with the `main` branch. +Try to make the changeset concise. diff --git a/.cursor/rules/commit-changes.mdc b/.cursor/rules/commit-changes.mdc new file mode 100644 index 000000000..22e6c2ca9 --- /dev/null +++ b/.cursor/rules/commit-changes.mdc @@ -0,0 +1,6 @@ +--- +description: When commiting the changes +alwaysApply: false +--- + +Use Conventional Commits convention. diff --git a/src/components/fields/ComboBox/ComboBox.test.tsx b/src/components/fields/ComboBox/ComboBox.test.tsx index 29b79a7a9..735f88eb7 100644 --- a/src/components/fields/ComboBox/ComboBox.test.tsx +++ b/src/components/fields/ComboBox/ComboBox.test.tsx @@ -463,6 +463,36 @@ describe('', () => { }); }); + it('should allow form submission with single Enter press when allowsCustomValue and no matches', async () => { + const onSubmit = jest.fn(); + const { getByRole, formInstance } = renderWithForm( + + {items.map((item) => ( + {item.children} + ))} + , + { formProps: { onSubmit } }, + ); + + const combobox = getByRole('combobox'); + + // Type custom value that doesn't match any items (no popover) + await userEvent.type(combobox, 'CustomValue'); + + // Wait for popover to close (if it opened during typing) + await waitFor(() => { + expect(combobox).toHaveAttribute('aria-expanded', 'false'); + }); + + // Press Enter once - should commit value AND submit form + await userEvent.keyboard('{Enter}'); + + await waitFor(() => { + expect(formInstance.getFieldValue('tag')).toBe('CustomValue'); + expect(onSubmit).toHaveBeenCalledWith({ tag: 'CustomValue' }); + }); + }); + it('should clear invalid input on blur when clearOnBlur is true', async () => { const onSelectionChange = jest.fn(); diff --git a/src/components/fields/ComboBox/ComboBox.tsx b/src/components/fields/ComboBox/ComboBox.tsx index eed6cd9ef..57265d244 100644 --- a/src/components/fields/ComboBox/ComboBox.tsx +++ b/src/components/fields/ComboBox/ComboBox.tsx @@ -605,15 +605,21 @@ function useComboBoxKeyboard({ // If no results, handle empty input or custom values if (!hasResults) { - e.preventDefault(); - if (allowsCustomValue) { - const value = effectiveInputValue; + const value = effectiveInputValue.trim(); + // Commit the custom value onSelectionChange( (value as unknown as Key) ?? (null as unknown as Key), ); + + // If popover is closed and we have a value, allow Enter to propagate for form submission + // If popover is open OR value is empty, prevent default to avoid premature submission + if (isPopoverOpen || !value) { + e.preventDefault(); + } } else { + e.preventDefault(); onSelectionChange(null); } From 8477763e764382aa575f874528f89f89c874284c Mon Sep 17 00:00:00 2001 From: Andrey Yamanov Date: Tue, 11 Nov 2025 14:53:14 +0100 Subject: [PATCH 2/4] chore: update commit-changes rule --- .cursor/rules/commit-changes.mdc | 1 + 1 file changed, 1 insertion(+) diff --git a/.cursor/rules/commit-changes.mdc b/.cursor/rules/commit-changes.mdc index 22e6c2ca9..bed47e094 100644 --- a/.cursor/rules/commit-changes.mdc +++ b/.cursor/rules/commit-changes.mdc @@ -4,3 +4,4 @@ alwaysApply: false --- Use Conventional Commits convention. +If changes described in changesets are isolated (For example in a single component) then use syntax with `()` like `fix(ComboBox): what exactly is fixed` and so on. From c62407ba3d5ec16efa4c1d32c8b2c5e7d4193318 Mon Sep 17 00:00:00 2001 From: Andrey Yamanov Date: Tue, 11 Nov 2025 14:54:08 +0100 Subject: [PATCH 3/4] chore: update commit-changes rule * 2 --- .cursor/rules/commit-changes.mdc | 1 + 1 file changed, 1 insertion(+) diff --git a/.cursor/rules/commit-changes.mdc b/.cursor/rules/commit-changes.mdc index bed47e094..e970a8f85 100644 --- a/.cursor/rules/commit-changes.mdc +++ b/.cursor/rules/commit-changes.mdc @@ -5,3 +5,4 @@ alwaysApply: false Use Conventional Commits convention. If changes described in changesets are isolated (For example in a single component) then use syntax with `()` like `fix(ComboBox): what exactly is fixed` and so on. +The name of the commit should be as short as possible. From c9e3b28c63586254b9e243c6d83cda51251a7f61 Mon Sep 17 00:00:00 2001 From: Andrey Yamanov Date: Tue, 11 Nov 2025 16:07:25 +0100 Subject: [PATCH 4/4] chore: expose version --- src/components/Root.tsx | 2 ++ src/version.ts | 14 +++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/Root.tsx b/src/components/Root.tsx index 856693f4a..df63f17cb 100644 --- a/src/components/Root.tsx +++ b/src/components/Root.tsx @@ -16,6 +16,7 @@ import { import { TOKENS } from '../tokens'; import { useViewportSize } from '../utils/react'; import { EventBusProvider } from '../utils/react/useEventBus'; +import { VERSION } from '../version'; import { GlobalStyles } from './GlobalStyles'; import { AlertDialogApiProvider } from './overlays/AlertDialog'; @@ -132,6 +133,7 @@ export function Root(allProps: CubeRootProps) {