+
+
+
+
value: {{ value === '' ? '(empty)' : value }}
+
`,
+ }),
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ const input = canvas.getByRole('combobox');
+ await expect(input).toHaveValue('e_invoices');
+
+ // Clearing empties the model, restores the placeholder, and hands
+ // focus to the field the ✕ it was on has just unmounted beside.
+ await userEvent.click(canvas.getByRole('button', { name: 'Clear file cabinet' }));
+ await expect(canvas.getByTestId('value')).toHaveTextContent('value: (empty)');
+ await expect(input).toHaveValue('');
+ await expect(canvas.queryByRole('button', { name: 'Clear file cabinet' })).not.toBeInTheDocument();
+ await waitFor(() => expect(input).toHaveFocus());
+
+ // An empty field filters nothing, so clearing leaves the whole list
+ // open to pick from — clearing is a step towards another value.
+ const listbox = await canvas.findByRole('listbox');
+ await expect(within(listbox).getAllByRole('option')).toHaveLength(5);
+
+ // Free text brings the ✕ back, not only a picked suggestion.
+ await userEvent.type(input, 'e_brand_new');
+ await expect(canvas.getByRole('button', { name: 'Clear file cabinet' })).toBeInTheDocument();
+ },
+};
+
/**
* Options that arrive AFTER the field is focused — the remote-search shape,
* where a consumer replaces `options` with each debounced response.
diff --git a/src/components/molecules/Combobox.vue b/src/components/molecules/Combobox.vue
index 967a632..78f31bb 100644
--- a/src/components/molecules/Combobox.vue
+++ b/src/components/molecules/Combobox.vue
@@ -8,6 +8,7 @@ import { formControlClasses } from '../../helpers/formControlClasses';
import { useClickOutside } from '../../composables/useClickOutside';
import { useFieldA11y } from '../../composables/useFieldA11y';
import { useListNavigation } from '../../composables/useListNavigation';
+import Icon from '../atoms/Icon.vue';
import type { SelectOption } from '../atoms/Select.vue';
/**
@@ -23,6 +24,14 @@ export interface ComboboxProps