diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/multi_text_input.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/multi_text_input.test.tsx
new file mode 100644
index 0000000000000..18a7357b235b9
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/multi_text_input.test.tsx
@@ -0,0 +1,72 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { fireEvent, act } from '@testing-library/react';
+
+import { createTestRendererMock } from '../../../../mock';
+
+import { MultiTextInput } from './multi_text_input';
+
+function renderInput(value = ['value1']) {
+ const renderer = createTestRendererMock();
+ const mockOnChange = jest.fn();
+
+ const utils = renderer.render();
+
+ return { utils, mockOnChange };
+}
+
+test('it should allow to add a new value', async () => {
+ const { utils, mockOnChange } = renderInput();
+
+ const addRowEl = await utils.findByText('Add row');
+ fireEvent.click(addRowEl);
+
+ expect(mockOnChange).toHaveBeenCalledWith(['value1']);
+
+ const inputEl = await utils.findByDisplayValue('');
+ expect(inputEl).toBeDefined();
+
+ fireEvent.change(inputEl, { target: { value: 'value2' } });
+
+ expect(mockOnChange).toHaveBeenCalledWith(['value1', 'value2']);
+});
+
+test('it should not show the delete button if there only one row', async () => {
+ const { utils } = renderInput(['value1']);
+
+ await act(async () => {
+ const deleteRowEl = await utils.container.querySelector('[aria-label="Delete row"]');
+ expect(deleteRowEl).toBeNull();
+ });
+});
+
+test('it should allow to update existing value', async () => {
+ const { utils, mockOnChange } = renderInput(['value1', 'value2']);
+
+ const inputEl = await utils.findByDisplayValue('value1');
+ expect(inputEl).toBeDefined();
+
+ fireEvent.change(inputEl, { target: { value: 'value1updated' } });
+
+ expect(mockOnChange).toHaveBeenCalledWith(['value1updated', 'value2']);
+});
+
+test('it should allow to remove a row', async () => {
+ const { utils, mockOnChange } = renderInput(['value1', 'value2']);
+
+ await act(async () => {
+ const deleteRowEl = await utils.container.querySelector('[aria-label="Delete row"]');
+ if (!deleteRowEl) {
+ throw new Error('Delete row button not found');
+ }
+ fireEvent.click(deleteRowEl);
+ });
+
+ expect(mockOnChange).toHaveBeenCalledWith(['value2']);
+});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/multi_text_input.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/multi_text_input.tsx
new file mode 100644
index 0000000000000..e4e724199b493
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/multi_text_input.tsx
@@ -0,0 +1,170 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React, { useCallback, useState, useEffect } from 'react';
+import type { FunctionComponent, ChangeEvent } from 'react';
+
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty,
+ EuiFieldText,
+ EuiButtonIcon,
+ EuiSpacer,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+interface Props {
+ value: string[];
+ onChange: (newValue: string[]) => void;
+ onBlur?: () => void;
+ errors?: Array<{ message: string; index?: number }>;
+ isInvalid?: boolean;
+ isDisabled?: boolean;
+}
+
+interface RowProps {
+ index: number;
+ value: string;
+ onChange: (index: number, value: string) => void;
+ onDelete: (index: number) => void;
+ onBlur?: () => void;
+ autoFocus?: boolean;
+ isDisabled?: boolean;
+ showDeleteButton?: boolean;
+}
+
+const Row: FunctionComponent = ({
+ index,
+ value,
+ onChange,
+ onDelete,
+ onBlur,
+ autoFocus,
+ isDisabled,
+ showDeleteButton,
+}) => {
+ const onDeleteHandler = useCallback(() => {
+ onDelete(index);
+ }, [onDelete, index]);
+
+ const onChangeHandler = useCallback(
+ (e: ChangeEvent) => {
+ onChange(index, e.target.value);
+ },
+ [onChange, index]
+ );
+
+ return (
+
+
+
+
+ {showDeleteButton && (
+
+
+
+ )}
+
+ );
+};
+
+function defaultValue(value: string[]) {
+ return value.length > 0 ? value : [''];
+}
+
+export const MultiTextInput: FunctionComponent = ({
+ value,
+ onChange,
+ onBlur,
+ isInvalid,
+ isDisabled,
+ errors,
+}) => {
+ const [autoFocus, setAutoFocus] = useState(false);
+ const [rows, setRows] = useState(() => defaultValue(value));
+ const [previousRows, setPreviousRows] = useState(rows);
+
+ useEffect(() => {
+ if (previousRows === rows) {
+ return;
+ }
+ setPreviousRows(rows);
+ if (rows[rows.length - 1] === '') {
+ onChange(rows.slice(0, rows.length - 1));
+ } else {
+ onChange(rows);
+ }
+ }, [onChange, previousRows, rows]);
+
+ const onDeleteHandler = useCallback(
+ (idx: number) => {
+ setRows([...rows.slice(0, idx), ...rows.slice(idx + 1)]);
+ },
+ [rows]
+ );
+
+ const onChangeHandler = useCallback(
+ (idx: number, newValue: string) => {
+ const newRows = [...rows];
+ newRows[idx] = newValue;
+ setRows(newRows);
+ },
+ [rows]
+ );
+
+ const addRowHandler = useCallback(() => {
+ setAutoFocus(true);
+ setRows([...rows, '']);
+ }, [rows]);
+
+ return (
+ <>
+
+ {rows.map((row, idx) => (
+
+ 1}
+ />
+
+ ))}
+
+
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx
index 7841e8bb62452..eed94de97113d 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_var_field.tsx
@@ -12,7 +12,6 @@ import {
EuiFormRow,
EuiSwitch,
EuiFieldText,
- EuiComboBox,
EuiText,
EuiCodeEditor,
EuiTextArea,
@@ -23,6 +22,7 @@ import type { RegistryVarsEntry } from '../../../../types';
import 'brace/mode/yaml';
import 'brace/theme/textmate';
+import { MultiTextInput } from './multi_text_input';
export const PackagePolicyInputVarField: React.FunctionComponent<{
varDef: RegistryVarsEntry;
@@ -41,16 +41,9 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{
const field = useMemo(() => {
if (multi) {
return (
- ({ label: val }))}
- onCreateOption={(newVal: any) => {
- onChange([...value, newVal]);
- }}
- onChange={(newVals: any[]) => {
- onChange(newVals.map((val) => val.label));
- }}
+ setIsDirty(true)}
isDisabled={frozen}
/>