feat(scaffolder): add new ui:options autoSelect tag (#32912)
* feat(scaffolder): add new ui:autoSelect tag Signed-off-by: Jonathan Sundquist <jonathan.sundquist@factset.com> * Update .changeset/long-hairs-throw.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Jonathan Sundquist <jsundquist@gmail.com> Signed-off-by: Jonathan Sundquist <jonathan.sundquist@factset.com> * Adding missing annotations in the schema file Signed-off-by: Jonathan Sundquist <jonathan.sundquist@factset.com> * Adding missing tests and documentation Signed-off-by: Jonathan Sundquist <jonathan.sundquist@factset.com> * adding in api report files Signed-off-by: Jonathan Sundquist <jonathan.sundquist@factset.com> * Fixing tests and ensure all required files have the necessary updates Signed-off-by: Jonathan Sundquist <jonathan.sundquist@factset.com> --------- Signed-off-by: Jonathan Sundquist <jonathan.sundquist@factset.com> Signed-off-by: Jonathan Sundquist <jsundquist@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
3cf3120eaa
commit
bd5b8426af
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Added a new `ui:autoSelect` option to the EntityPicker field that controls whether an entity is automatically selected when the field loses focus. When set to `false`, the field will remain empty if the user closes it without explicitly selecting an entity, preventing unintentional selections. Defaults to `true` for backward compatibility.
|
||||
@@ -119,6 +119,36 @@ entity:
|
||||
defaultNamespace: payment
|
||||
```
|
||||
|
||||
### `autoSelect`
|
||||
|
||||
Whether to automatically select the highlighted option when the input loses focus. Defaults to `true`.
|
||||
|
||||
When set to `false`, users must explicitly select an option from the dropdown by clicking or pressing Enter. This prevents accidental selections when typing to filter options.
|
||||
|
||||
- Default behavior with `autoSelect` as `true` (auto-selects on blur)
|
||||
|
||||
```yaml
|
||||
entity:
|
||||
title: Entity
|
||||
type: string
|
||||
description: Entity of the component
|
||||
ui:field: EntityPicker
|
||||
ui:options:
|
||||
autoSelect: true
|
||||
```
|
||||
|
||||
- Require explicit selection with `autoSelect` as `false`
|
||||
|
||||
```yaml
|
||||
entity:
|
||||
title: Entity
|
||||
type: string
|
||||
description: Entity of the component
|
||||
ui:field: EntityPicker
|
||||
ui:options:
|
||||
autoSelect: false
|
||||
```
|
||||
|
||||
## MultiEntityPicker
|
||||
|
||||
The input props that can be specified under `ui:options` for the `MultiEntityPicker` field extension.
|
||||
|
||||
@@ -74,6 +74,7 @@ export const EntityNamePickerFieldExtension: FieldExtensionComponent_2<
|
||||
export const EntityPickerFieldExtension: FieldExtensionComponent_2<
|
||||
string,
|
||||
{
|
||||
autoSelect?: boolean | undefined;
|
||||
defaultKind?: string | undefined;
|
||||
defaultNamespace?: string | false | undefined;
|
||||
catalogFilter?:
|
||||
@@ -103,6 +104,7 @@ export const EntityPickerFieldExtension: FieldExtensionComponent_2<
|
||||
export const EntityPickerFieldSchema: FieldSchema_2<
|
||||
string,
|
||||
{
|
||||
autoSelect?: boolean | undefined;
|
||||
defaultKind?: string | undefined;
|
||||
defaultNamespace?: string | false | undefined;
|
||||
catalogFilter?:
|
||||
@@ -249,6 +251,7 @@ export type MyGroupsPickerUiOptions = NonNullable<
|
||||
export const OwnedEntityPickerFieldExtension: FieldExtensionComponent_2<
|
||||
string,
|
||||
{
|
||||
autoSelect?: boolean | undefined;
|
||||
defaultKind?: string | undefined;
|
||||
defaultNamespace?: string | false | undefined;
|
||||
catalogFilter?:
|
||||
@@ -278,6 +281,7 @@ export const OwnedEntityPickerFieldExtension: FieldExtensionComponent_2<
|
||||
export const OwnedEntityPickerFieldSchema: FieldSchema_2<
|
||||
string,
|
||||
{
|
||||
autoSelect?: boolean | undefined;
|
||||
defaultKind?: string | undefined;
|
||||
defaultNamespace?: string | false | undefined;
|
||||
catalogFilter?:
|
||||
|
||||
@@ -378,6 +378,100 @@ describe('<EntityPicker />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ui:autoSelect behavior', () => {
|
||||
beforeEach(() => {
|
||||
uiSchema = {
|
||||
'ui:options': {
|
||||
defaultKind: 'Group',
|
||||
},
|
||||
};
|
||||
props = {
|
||||
onChange,
|
||||
schema,
|
||||
required,
|
||||
uiSchema,
|
||||
rawErrors,
|
||||
formData,
|
||||
} as unknown as FieldProps<any>;
|
||||
|
||||
catalogApi.getEntities.mockResolvedValue({ items: entities });
|
||||
});
|
||||
|
||||
it('default behavior', async () => {
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<EntityPicker {...props} />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const input = getByRole('textbox');
|
||||
|
||||
// Type partial match and blur
|
||||
fireEvent.change(input, { target: { value: 'team' } });
|
||||
fireEvent.blur(input);
|
||||
|
||||
// Default behavior with freeSolo enabled processes the typed value
|
||||
expect(onChange).toHaveBeenCalledWith('group:default/team');
|
||||
});
|
||||
|
||||
it('does not autoSelect value onBlur', async () => {
|
||||
uiSchema = {
|
||||
'ui:options': {
|
||||
defaultKind: 'Group',
|
||||
autoSelect: false,
|
||||
},
|
||||
};
|
||||
props = {
|
||||
...props,
|
||||
uiSchema,
|
||||
} as unknown as FieldProps<any>;
|
||||
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<EntityPicker {...props} />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const input = getByRole('textbox');
|
||||
|
||||
// Type and blur - with autoSelect=false, the autocomplete won't auto-select on blur
|
||||
fireEvent.change(input, { target: { value: 'team' } });
|
||||
fireEvent.blur(input);
|
||||
|
||||
// With autoSelect=false, onChange should not be called on blur
|
||||
// This is the key difference - users must explicitly select an option
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('autoSelects entity onBlur', async () => {
|
||||
uiSchema = {
|
||||
'ui:options': {
|
||||
defaultKind: 'Group',
|
||||
autoSelect: true,
|
||||
},
|
||||
};
|
||||
props = {
|
||||
...props,
|
||||
uiSchema,
|
||||
} as unknown as FieldProps<any>;
|
||||
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<EntityPicker {...props} />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const input = getByRole('textbox');
|
||||
|
||||
// Type and blur
|
||||
fireEvent.change(input, { target: { value: 'squad' } });
|
||||
fireEvent.blur(input);
|
||||
|
||||
// With autoSelect=true and freeSolo, processes the typed value
|
||||
expect(onChange).toHaveBeenCalledWith('group:default/squad');
|
||||
});
|
||||
});
|
||||
|
||||
describe('uses full entity ref', () => {
|
||||
beforeEach(() => {
|
||||
uiSchema = {
|
||||
|
||||
@@ -74,6 +74,7 @@ export const EntityPicker = (props: EntityPickerProps) => {
|
||||
const defaultKind = uiSchema['ui:options']?.defaultKind;
|
||||
const defaultNamespace =
|
||||
uiSchema['ui:options']?.defaultNamespace || undefined;
|
||||
const autoSelect = uiSchema?.['ui:options']?.autoSelect ?? true;
|
||||
const isDisabled = uiSchema?.['ui:disabled'] ?? false;
|
||||
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
@@ -209,7 +210,7 @@ export const EntityPicker = (props: EntityPickerProps) => {
|
||||
: entities?.entityRefToPresentation.get(stringifyEntityRef(option))
|
||||
?.entityRef!
|
||||
}
|
||||
autoSelect
|
||||
autoSelect={autoSelect}
|
||||
freeSolo={allowArbitraryValues}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
|
||||
@@ -61,6 +61,12 @@ export const EntityPickerFieldSchema = makeFieldSchema({
|
||||
)
|
||||
.optional()
|
||||
.describe('List of key-value filter expression for entities'),
|
||||
autoSelect: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Whether to automatically select an option on blur. Defaults to true.',
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user