diff --git a/frontend/app-development/components/WebSocketSyncWrapper/WebSocketSyncWrapper.test.tsx b/frontend/app-development/components/WebSocketSyncWrapper/WebSocketSyncWrapper.test.tsx
index 735ff043fdf..ef733897a07 100644
--- a/frontend/app-development/components/WebSocketSyncWrapper/WebSocketSyncWrapper.test.tsx
+++ b/frontend/app-development/components/WebSocketSyncWrapper/WebSocketSyncWrapper.test.tsx
@@ -68,7 +68,7 @@ describe('WebSocketSyncWrapper', () => {
const queryClientMock = createQueryClientMock();
const invalidator = SyncSuccessQueriesInvalidator.getInstance(queryClientMock, org, app);
- invalidator.invalidateQueryByFileLocation = jest.fn();
+ invalidator.invalidateQueriesByFileLocation = jest.fn();
const mockOnWSMessageReceived = jest
.fn()
.mockImplementation((callback: Function) => callback(syncSuccessMock));
@@ -80,7 +80,7 @@ describe('WebSocketSyncWrapper', () => {
renderWebSocketSyncWrapper();
await waitFor(() => {
- expect(invalidator.invalidateQueryByFileLocation).toHaveBeenCalledWith(
+ expect(invalidator.invalidateQueriesByFileLocation).toHaveBeenCalledWith(
syncSuccessMock.source.name,
);
});
diff --git a/frontend/app-development/components/WebSocketSyncWrapper/WebSocketSyncWrapper.tsx b/frontend/app-development/components/WebSocketSyncWrapper/WebSocketSyncWrapper.tsx
index 733e8e5219a..53284a5a7db 100644
--- a/frontend/app-development/components/WebSocketSyncWrapper/WebSocketSyncWrapper.tsx
+++ b/frontend/app-development/components/WebSocketSyncWrapper/WebSocketSyncWrapper.tsx
@@ -48,8 +48,8 @@ export const WebSocketSyncWrapper = ({
const isSuccessMessage = 'source' in message;
if (isSuccessMessage) {
- // Please extend the "fileNameCacheKeyMap" inside the "SyncSuccessQueriesInvalidator" class. Do not add query-client invalidation directly here.
- invalidator.invalidateQueryByFileLocation(message.source.name);
+ // Please extend the "fileNameCacheKeysMap" inside the "SyncSuccessQueriesInvalidator" class. Do not add query-client invalidation directly here.
+ invalidator.invalidateQueriesByFileLocation(message.source.name);
}
});
diff --git a/frontend/app-development/hooks/mutations/useAddLayoutSetMutation.ts b/frontend/app-development/hooks/mutations/useAddLayoutSetMutation.ts
index 68a2d3ff174..cedff5138b8 100644
--- a/frontend/app-development/hooks/mutations/useAddLayoutSetMutation.ts
+++ b/frontend/app-development/hooks/mutations/useAddLayoutSetMutation.ts
@@ -46,6 +46,7 @@ export const useAddLayoutSetMutation = (org: string, app: string) => {
// when process-editor renders the tasks and 'adds' them on first mount, when they already exists.
if (isLayoutSets(layoutSets)) {
queryClient.setQueryData([QueryKey.LayoutSets, org, app], layoutSets);
+ queryClient.invalidateQueries({ queryKey: [QueryKey.LayoutSetsExtended, org, app] });
}
},
});
diff --git a/frontend/app-development/hooks/mutations/useDeleteLayoutSetMutation.ts b/frontend/app-development/hooks/mutations/useDeleteLayoutSetMutation.ts
index 57cfa3b9a10..1b439b227e0 100644
--- a/frontend/app-development/hooks/mutations/useDeleteLayoutSetMutation.ts
+++ b/frontend/app-development/hooks/mutations/useDeleteLayoutSetMutation.ts
@@ -11,6 +11,7 @@ export const useDeleteLayoutSetMutation = (org: string, app: string) => {
deleteLayoutSet(org, app, layoutSetIdToUpdate),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QueryKey.LayoutSets, org, app] });
+ queryClient.invalidateQueries({ queryKey: [QueryKey.LayoutSetsExtended, org, app] });
queryClient.invalidateQueries({ queryKey: [QueryKey.AppMetadataModelIds, org, app] });
},
});
diff --git a/frontend/app-development/hooks/mutations/useUpdateLayoutSetIdMutation.ts b/frontend/app-development/hooks/mutations/useUpdateLayoutSetIdMutation.ts
index 87baa945ea7..40a218cac0c 100644
--- a/frontend/app-development/hooks/mutations/useUpdateLayoutSetIdMutation.ts
+++ b/frontend/app-development/hooks/mutations/useUpdateLayoutSetIdMutation.ts
@@ -16,6 +16,7 @@ export const useUpdateLayoutSetIdMutation = (org: string, app: string) => {
}) => updateLayoutSetId(org, app, layoutSetIdToUpdate, newLayoutSetId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QueryKey.LayoutSets, org, app] });
+ queryClient.invalidateQueries({ queryKey: [QueryKey.LayoutSetsExtended, org, app] });
},
});
};
diff --git a/frontend/app-development/hooks/mutations/useUpdateProcessDataTypesMutation.test.ts b/frontend/app-development/hooks/mutations/useUpdateProcessDataTypesMutation.test.ts
index fb3380ea275..7de62a67c34 100644
--- a/frontend/app-development/hooks/mutations/useUpdateProcessDataTypesMutation.test.ts
+++ b/frontend/app-development/hooks/mutations/useUpdateProcessDataTypesMutation.test.ts
@@ -43,7 +43,7 @@ describe('useUpdateProcessDataTypeMutation', () => {
await renderHook({ queryClient });
- expect(invalidateQueriesSpy).toHaveBeenCalledTimes(2);
+ expect(invalidateQueriesSpy).toHaveBeenCalledTimes(3);
expect(invalidateQueriesSpy).toHaveBeenCalledWith({
queryKey: [QueryKey.AppMetadataModelIds, org, app],
});
diff --git a/frontend/app-development/hooks/mutations/useUpdateProcessDataTypesMutation.ts b/frontend/app-development/hooks/mutations/useUpdateProcessDataTypesMutation.ts
index 8e2dea1b706..5aae6913df6 100644
--- a/frontend/app-development/hooks/mutations/useUpdateProcessDataTypesMutation.ts
+++ b/frontend/app-development/hooks/mutations/useUpdateProcessDataTypesMutation.ts
@@ -11,6 +11,7 @@ export const useUpdateProcessDataTypesMutation = (org: string, app: string) => {
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: [QueryKey.AppMetadataModelIds, org, app] });
await queryClient.invalidateQueries({ queryKey: [QueryKey.LayoutSets, org, app] });
+ await queryClient.invalidateQueries({ queryKey: [QueryKey.LayoutSetsExtended, org, app] });
},
});
};
diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json
index 62ccde8e5d8..0e38334e340 100644
--- a/frontend/language/src/nb.json
+++ b/frontend/language/src/nb.json
@@ -1200,6 +1200,8 @@
"ux_editor.component_help_text.TextArea": "Du bruker Stort tekstfelt når du vil at brukerne skal skrive litt lengre tekst.",
"ux_editor.component_help_text.default": "Ingen informasjon å vise om denne komponenten.",
"ux_editor.component_help_text_general_title": "Åpne hjelpetekst for komponenten",
+ "ux_editor.component_other_properties_hide_many_settings": "Skjul flere innstillinger",
+ "ux_editor.component_other_properties_show_many_settings": "Vis flere innstillinger",
"ux_editor.component_other_properties_title": "Andre innstillinger",
"ux_editor.component_properties.action": "Handling",
"ux_editor.component_properties.actions": "Handlinger",
@@ -1425,11 +1427,14 @@
"ux_editor.component_properties.tableHeadersMobile": "Felter som skal vises i tabellens overskrift (mobil)",
"ux_editor.component_properties.tabs": "Faner",
"ux_editor.component_properties.tagName": "Tag-navn",
- "ux_editor.component_properties.target": "Mål",
- "ux_editor.component_properties.target_description": "Mål for oppsummeringskomponenten",
+ "ux_editor.component_properties.target": "Hva vil du vise i oppsummeringen?",
+ "ux_editor.component_properties.target_description": "Her kan du velge hva som skal vises på oppsummeringssiden. Du kan for eksempel vise hele sidegrupper, utvalgte sider eller utvalgte komponenter",
"ux_editor.component_properties.target_invalid": "Ugyldig mål",
- "ux_editor.component_properties.target_taskId": "Oppgave-ID",
- "ux_editor.component_properties.target_type": "Type",
+ "ux_editor.component_properties.target_taskId": "1. Oppsummer fra denne sidegruppen",
+ "ux_editor.component_properties.target_type": "2. Vis sidegruppe, side eller komponent",
+ "ux_editor.component_properties.target_unit_component": "3. Komponent",
+ "ux_editor.component_properties.target_unit_layout_set": "3. Sidegruppe",
+ "ux_editor.component_properties.target_unit_page": "3. Side",
"ux_editor.component_properties.taskId": "Oppgave-ID",
"ux_editor.component_properties.timeStamp": "Inkluder tidsstempel i dato (på som standard)",
"ux_editor.component_properties.triggers": "Feltet skal utløse:",
diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx
index b78acd0001b..ea8ff99beaa 100644
--- a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx
+++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx
@@ -46,11 +46,13 @@ const codeList: CodeList = [
helpText: 'Test 3 help text',
},
];
+const onBlurAny = jest.fn();
const onChange = jest.fn();
const onInvalid = jest.fn();
const defaultProps: StudioCodeListEditorProps = {
codeList,
texts,
+ onBlurAny,
onChange,
onInvalid,
};
@@ -119,8 +121,7 @@ describe('StudioCodeListEditor', () => {
const labelInput = screen.getByRole('textbox', { name: texts.itemLabel(1) });
const newValue = 'new text';
await user.type(labelInput, newValue);
- await user.tab();
- expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeList[0], label: newValue },
codeList[1],
@@ -134,8 +135,7 @@ describe('StudioCodeListEditor', () => {
const valueInput = screen.getByRole('textbox', { name: texts.itemValue(1) });
const newValue = 'new text';
await user.type(valueInput, newValue);
- await user.tab();
- expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeList[0], value: newValue },
codeList[1],
@@ -149,8 +149,7 @@ describe('StudioCodeListEditor', () => {
const descriptionInput = screen.getByRole('textbox', { name: texts.itemDescription(1) });
const newValue = 'new text';
await user.type(descriptionInput, newValue);
- await user.tab();
- expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeList[0], description: newValue },
codeList[1],
@@ -164,8 +163,7 @@ describe('StudioCodeListEditor', () => {
const helpTextInput = screen.getByRole('textbox', { name: texts.itemHelpText(1) });
const newValue = 'new text';
await user.type(helpTextInput, newValue);
- await user.tab();
- expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeList[0], helpText: newValue },
codeList[1],
@@ -196,6 +194,21 @@ describe('StudioCodeListEditor', () => {
]);
});
+ it('Calls the onBlurAny callback with the current code list when an item in the table is blurred', async () => {
+ const user = userEvent.setup();
+ renderCodeListEditor();
+ const valueInput = screen.getByRole('textbox', { name: texts.itemValue(1) });
+ const newValue = 'new text';
+ await user.type(valueInput, newValue);
+ await user.tab();
+ expect(onBlurAny).toHaveBeenCalledTimes(1);
+ expect(onBlurAny).toHaveBeenLastCalledWith([
+ { ...codeList[0], value: newValue },
+ codeList[1],
+ codeList[2],
+ ]);
+ });
+
it('Updates itself when the user changes something', async () => {
const user = userEvent.setup();
renderCodeListEditor();
@@ -267,8 +280,7 @@ describe('StudioCodeListEditor', () => {
const validValueInput = screen.getByRole('textbox', { name: texts.itemValue(3) });
const newValue = 'new value';
await user.type(validValueInput, newValue);
- await user.tab();
- expect(onInvalid).toHaveBeenCalledTimes(1);
+ expect(onInvalid).toHaveBeenCalledTimes(newValue.length);
});
it('Does not trigger onInvalid if an invalid code list is changed to a valid state', async () => {
diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx
index a4cff5e76da..d1da350578a 100644
--- a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx
+++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx
@@ -23,23 +23,32 @@ import { areThereCodeListErrors, findCodeListErrors, isCodeListValid } from './v
import type { ValueErrorMap } from './types/ValueErrorMap';
import { StudioFieldset } from '../StudioFieldset';
import { StudioErrorMessage } from '../StudioErrorMessage';
+import type { Override } from '../../types/Override';
+import type { StudioInputTableProps } from '../StudioInputTable/StudioInputTable';
export type StudioCodeListEditorProps = {
codeList: CodeList;
- onChange: (codeList: CodeList) => void;
+ onBlurAny?: (codeList: CodeList) => void;
+ onChange?: (codeList: CodeList) => void;
onInvalid?: () => void;
texts: CodeListEditorTexts;
};
export function StudioCodeListEditor({
codeList,
+ onBlurAny,
onChange,
onInvalid,
texts,
}: StudioCodeListEditorProps): ReactElement {
return (
-
+
);
}
@@ -48,6 +57,7 @@ type StatefulCodeListEditorProps = Omit;
function StatefulCodeListEditor({
codeList: defaultCodeList,
+ onBlurAny,
onChange,
onInvalid,
}: StatefulCodeListEditorProps): ReactElement {
@@ -60,18 +70,32 @@ function StatefulCodeListEditor({
const handleChange = useCallback(
(newCodeList: CodeList) => {
setCodeList(newCodeList);
- isCodeListValid(newCodeList) ? onChange(newCodeList) : onInvalid?.();
+ isCodeListValid(newCodeList) ? onChange?.(newCodeList) : onInvalid?.();
},
[onChange, onInvalid],
);
- return ;
+ const handleBlurAny = useCallback(() => {
+ onBlurAny?.(codeList);
+ }, [onBlurAny, codeList]);
+
+ return (
+
+ );
}
-type InternalCodeListEditorProps = Omit;
+type InternalCodeListEditorProps = Override<
+ Pick,
+ Omit
+>;
function ControlledCodeListEditor({
codeList,
+ onBlurAny,
onChange,
}: InternalCodeListEditorProps): ReactElement {
const { texts } = useStudioCodeListEditorContext();
@@ -86,12 +110,18 @@ function ControlledCodeListEditor({
return (
-
+
);
}
+
type InternalCodeListEditorWithErrorsProps = InternalCodeListEditorProps & ErrorsProps;
function CodeListTable(props: InternalCodeListEditorWithErrorsProps): ReactElement {
@@ -107,11 +137,14 @@ function EmptyCodeListTable(): ReactElement {
return {texts.emptyCodeList};
}
-function CodeListTableWithContent(props: InternalCodeListEditorWithErrorsProps): ReactElement {
+function CodeListTableWithContent({
+ onBlurAny,
+ ...rest
+}: InternalCodeListEditorWithErrorsProps): ReactElement {
return (
-
+
-
+
);
}
@@ -145,7 +178,7 @@ function TableBody({
[codeList, onChange],
);
- const handleBlur = useCallback(
+ const handleChange = useCallback(
(index: number, newItem: CodeListItem) => {
const updatedCodeList = changeCodeListItem(codeList, index, newItem);
onChange(updatedCodeList);
@@ -161,7 +194,7 @@ function TableBody({
item={item}
key={index}
number={index + 1}
- onBlur={(newItem) => handleBlur(index, newItem)}
+ onChange={(newItem) => handleChange(index, newItem)}
onDeleteButtonClick={() => handleDeleteButtonClick(index)}
/>
))}
diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx
index 161b6d04264..501f9f4fcac 100644
--- a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx
+++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx
@@ -13,7 +13,7 @@ type StudioCodeListEditorRowProps = {
error: ValueError | null;
item: CodeListItem;
number: number;
- onBlur: (newItem: CodeListItem) => void;
+ onChange: (newItem: CodeListItem) => void;
onDeleteButtonClick: () => void;
};
@@ -21,7 +21,7 @@ export function StudioCodeListEditorRow({
error,
item,
number,
- onBlur,
+ onChange,
onDeleteButtonClick,
}: StudioCodeListEditorRowProps) {
const { texts } = useStudioCodeListEditorContext();
@@ -29,33 +29,33 @@ export function StudioCodeListEditorRow({
const handleLabelChange = useCallback(
(label: string) => {
const updatedItem = changeLabel(item, label);
- onBlur(updatedItem);
+ onChange(updatedItem);
},
- [item, onBlur],
+ [item, onChange],
);
const handleDescriptionChange = useCallback(
(description: string) => {
const updatedItem = changeDescription(item, description);
- onBlur(updatedItem);
+ onChange(updatedItem);
},
- [item, onBlur],
+ [item, onChange],
);
const handleValueChange = useCallback(
(value: string) => {
const updatedItem = changeValue(item, value);
- onBlur(updatedItem);
+ onChange(updatedItem);
},
- [item, onBlur],
+ [item, onChange],
);
const handleHelpTextChange = useCallback(
(helpText: string) => {
const updatedItem = changeHelpText(item, helpText);
- onBlur(updatedItem);
+ onChange(updatedItem);
},
- [item, onBlur],
+ [item, onChange],
);
return (
@@ -64,22 +64,22 @@ export function StudioCodeListEditorRow({
autoComplete='off'
error={error && texts.valueErrors[error]}
label={texts.itemValue(number)}
- onBlur={handleValueChange}
+ onChange={handleValueChange}
value={item.value}
/>
@@ -90,23 +90,23 @@ export function StudioCodeListEditorRow({
type TextfieldCellProps = {
error?: string;
label: string;
- onBlur: (newString: string) => void;
+ onChange: (newString: string) => void;
value: CodeListItemValue;
autoComplete?: HTMLInputAutoCompleteAttribute;
};
-function TextfieldCell({ error, label, value, onBlur, autoComplete }: TextfieldCellProps) {
+function TextfieldCell({ error, label, value, onChange, autoComplete }: TextfieldCellProps) {
const ref = useRef(null);
useEffect((): void => {
ref.current?.setCustomValidity(error || '');
}, [error]);
- const handleBlur = useCallback(
+ const handleChange = useCallback(
(event: React.ChangeEvent): void => {
- onBlur(event.target.value);
+ onChange(event.target.value);
},
- [onBlur],
+ [onChange],
);
const handleFocus = useCallback((event: FocusEvent): void => {
@@ -118,7 +118,7 @@ function TextfieldCell({ error, label, value, onBlur, autoComplete }: TextfieldC
aria-label={label}
autoComplete={autoComplete}
className={classes.textfieldCell}
- onBlur={handleBlur}
+ onChange={handleChange}
onFocus={handleFocus}
ref={ref}
value={(value as string) ?? ''}
diff --git a/frontend/libs/studio-components/src/components/StudioTreeView/StudioTreeViewItem/StudioTreeViewItem.module.css b/frontend/libs/studio-components/src/components/StudioTreeView/StudioTreeViewItem/StudioTreeViewItem.module.css
index 53072361185..ef9e3495f41 100644
--- a/frontend/libs/studio-components/src/components/StudioTreeView/StudioTreeViewItem/StudioTreeViewItem.module.css
+++ b/frontend/libs/studio-components/src/components/StudioTreeView/StudioTreeViewItem/StudioTreeViewItem.module.css
@@ -13,6 +13,12 @@
width: 100%;
}
+.button span {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
.button[aria-selected='true'] {
background-color: var(--studio-treeitem-selected-background-colour);
border-color: var(--studio-treeitem-vertical-line-colour-root);
diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeLists/CodeLists.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeLists/CodeLists.tsx
index b2a4c57cfb4..c2553e24db5 100644
--- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeLists/CodeLists.tsx
+++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeLists/CodeLists.tsx
@@ -24,7 +24,7 @@ type CodeListProps = {
function CodeList({ codeList, onUpdateCodeList }: CodeListProps) {
const editorTexts: CodeListEditorTexts = useOptionListEditorTexts();
- const handleUpdateCodeList = (updatedCodeList: StudioComponentsCodeList): void => {
+ const handleBlurAny = (updatedCodeList: StudioComponentsCodeList): void => {
const updatedCodeListWithMetadata = updateCodeListWithMetadata(codeList, updatedCodeList);
onUpdateCodeList(updatedCodeListWithMetadata);
};
@@ -36,7 +36,7 @@ function CodeList({ codeList, onUpdateCodeList }: CodeListProps) {
diff --git a/frontend/packages/shared/src/queryInvalidator/SyncSuccessQueriesInvalidator.test.ts b/frontend/packages/shared/src/queryInvalidator/SyncSuccessQueriesInvalidator.test.ts
index 87de0c60acd..1703079e69f 100644
--- a/frontend/packages/shared/src/queryInvalidator/SyncSuccessQueriesInvalidator.test.ts
+++ b/frontend/packages/shared/src/queryInvalidator/SyncSuccessQueriesInvalidator.test.ts
@@ -19,12 +19,12 @@ describe('SyncSuccessQueriesInvalidator', () => {
jest.clearAllMocks();
});
- it('should invalidate query cache only once when invalidateQueryByFileLocation is called', async () => {
+ it('should invalidate query cache only once when invalidateQueriesByFileLocation is called', async () => {
const queriesInvalidator = SyncSuccessQueriesInvalidator.getInstance(queryClientMock, org, app);
const fileName = 'applicationmetadata.json';
- queriesInvalidator.invalidateQueryByFileLocation(fileName);
- queriesInvalidator.invalidateQueryByFileLocation(fileName);
+ queriesInvalidator.invalidateQueriesByFileLocation(fileName);
+ queriesInvalidator.invalidateQueriesByFileLocation(fileName);
await waitFor(() =>
expect(queryClientMock.invalidateQueries).toHaveBeenCalledWith({
queryKey: [QueryKey.AppMetadata, org, app],
@@ -33,22 +33,22 @@ describe('SyncSuccessQueriesInvalidator', () => {
expect(queryClientMock.invalidateQueries).toHaveBeenCalledTimes(1);
});
- it('should not invalidate query cache when invalidateQueryByFileLocation is called with an unknown file name', async () => {
+ it('should not invalidate query cache when invalidateQueriesByFileLocation is called with an unknown file name', async () => {
const queriesInvalidator = SyncSuccessQueriesInvalidator.getInstance(queryClientMock, org, app);
const fileName = 'unknown.json';
- queriesInvalidator.invalidateQueryByFileLocation(fileName);
+ queriesInvalidator.invalidateQueriesByFileLocation(fileName);
await new Promise((resolve) => setTimeout(resolve, 501));
expect(queryClientMock.invalidateQueries).not.toHaveBeenCalled();
});
- it('should invalidate query cache with layoutSetName identifier when invalidateQueryByFileLocation is called and layoutSetName has been set', async () => {
+ it('should invalidate query cache with layoutSetName identifier when invalidateQueriesByFileLocation is called and layoutSetName has been set', async () => {
const queriesInvalidator = SyncSuccessQueriesInvalidator.getInstance(queryClientMock, org, app);
queriesInvalidator.layoutSetName = selectedLayoutSet;
const fileName = 'Settings.json';
- queriesInvalidator.invalidateQueryByFileLocation(fileName);
+ queriesInvalidator.invalidateQueriesByFileLocation(fileName);
await waitFor(() => {
expect(queryClientMock.invalidateQueries).toHaveBeenCalledWith({
@@ -58,12 +58,12 @@ describe('SyncSuccessQueriesInvalidator', () => {
expect(queryClientMock.invalidateQueries).toHaveBeenCalledTimes(1);
});
- it('should invalidate layouts query cache with layoutSetName identifier when invalidateQueryByFileLocation is called and layoutSetName has been set', async () => {
+ it('should invalidate layouts query cache with layoutSetName identifier when invalidateQueriesByFileLocation is called and layoutSetName has been set', async () => {
const queriesInvalidator = SyncSuccessQueriesInvalidator.getInstance(queryClientMock, org, app);
queriesInvalidator.layoutSetName = selectedLayoutSet;
const folderName = 'layouts';
- queriesInvalidator.invalidateQueryByFileLocation(folderName);
+ queriesInvalidator.invalidateQueriesByFileLocation(folderName);
await waitFor(() =>
expect(queryClientMock.invalidateQueries).toHaveBeenCalledWith({
diff --git a/frontend/packages/shared/src/queryInvalidator/SyncSuccessQueriesInvalidator.ts b/frontend/packages/shared/src/queryInvalidator/SyncSuccessQueriesInvalidator.ts
index 0e240ae8590..3b88d565b4a 100644
--- a/frontend/packages/shared/src/queryInvalidator/SyncSuccessQueriesInvalidator.ts
+++ b/frontend/packages/shared/src/queryInvalidator/SyncSuccessQueriesInvalidator.ts
@@ -21,16 +21,19 @@ export class SyncSuccessQueriesInvalidator extends Queue {
private _queryClient: QueryClient;
// Maps file names to their cache keys for invalidation upon sync success - can be extended to include more files
- private readonly fileNameCacheKeyMap: Record> = {
- 'applicationmetadata.json': [QueryKey.AppMetadata, '[org]', '[app]'],
- 'layout-sets.json': [QueryKey.LayoutSets, '[org]', '[app]'],
- 'policy.xml': [QueryKey.AppPolicy, '[org]', '[app]'],
- 'Settings.json': [QueryKey.FormLayoutSettings, '[org]', '[app]', '[layoutSetName]'],
+ private readonly fileNameCacheKeysMap: Record>> = {
+ 'applicationmetadata.json': [[QueryKey.AppMetadata, '[org]', '[app]']],
+ 'layout-sets.json': [
+ [QueryKey.LayoutSets, '[org]', '[app]'],
+ [QueryKey.LayoutSetsExtended, '[org]', '[app]'],
+ ],
+ 'policy.xml': [[QueryKey.AppPolicy, '[org]', '[app]']],
+ 'Settings.json': [[QueryKey.FormLayoutSettings, '[org]', '[app]', '[layoutSetName]']],
};
// Maps folder names to their cache keys for invalidation upon sync success - can be extended to include more folders
- private readonly folderNameCacheKeyMap: Record> = {
- layouts: [QueryKey.FormLayouts, '[org]', '[app]'],
+ private readonly folderNameCacheKeysMap: Record>> = {
+ layouts: [[QueryKey.FormLayouts, '[org]', '[app]']],
};
public set layoutSetName(layoutSetName: string) {
@@ -67,32 +70,36 @@ export class SyncSuccessQueriesInvalidator extends Queue {
SyncSuccessQueriesInvalidator.instance = null;
}
- public invalidateQueryByFileLocation(fileOrFolderName: string): void {
- const cacheKey = this.getCacheKeyByFileLocation(fileOrFolderName);
- if (!cacheKey) return;
+ public invalidateQueriesByFileLocation(fileOrFolderName: string): void {
+ const cacheKeys = this.getCacheKeysByFileLocation(fileOrFolderName);
+ if (!cacheKeys) return;
this.addTaskToQueue({
id: fileOrFolderName,
callback: () => {
- this._queryClient.invalidateQueries({ queryKey: cacheKey });
+ cacheKeys.forEach((cacheKey) => {
+ this._queryClient.invalidateQueries({ queryKey: cacheKey });
+ });
},
});
}
- private getCacheKeyByFileLocation(fileOrFolderName: string): string[] {
- const cacheKey =
- this.fileNameCacheKeyMap[fileOrFolderName] || this.folderNameCacheKeyMap[fileOrFolderName];
- if (!cacheKey) return undefined;
+ private getCacheKeysByFileLocation(fileOrFolderName: string): Array {
+ const cacheKeys =
+ this.fileNameCacheKeysMap[fileOrFolderName] || this.folderNameCacheKeysMap[fileOrFolderName];
+ if (!cacheKeys) return undefined;
- return this.replaceCacheKeyPlaceholders(cacheKey);
+ return this.replaceCacheKeysPlaceholders(cacheKeys);
}
- private replaceCacheKeyPlaceholders(cacheKey: string[]): string[] {
- return cacheKey.map((key) =>
- key
- .replace('[org]', this._org)
- .replace('[app]', this._app)
- .replace('[layoutSetName]', this._layoutSetName),
+ private replaceCacheKeysPlaceholders(cacheKeys: Array): Array {
+ return cacheKeys.map((cacheKey) =>
+ cacheKey.map((key) =>
+ key
+ .replace('[org]', this._org)
+ .replace('[app]', this._app)
+ .replace('[layoutSetName]', this._layoutSetName),
+ ),
);
}
}
diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/CreateNewSubformSection.test.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/CreateNewSubformSection.test.tsx
index 98e31569c4c..1f0c5df9a78 100644
--- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/CreateNewSubformSection.test.tsx
+++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/CreateNewSubformSection.test.tsx
@@ -125,6 +125,31 @@ describe('CreateNewSubformLayoutSet ', () => {
expect(saveButton).toBeDisabled();
});
+ it('Toggles the save button disabling based on data model input validation', async () => {
+ const user = userEvent.setup();
+ renderCreateNewSubformLayoutSet({});
+
+ const input = screen.getByRole('textbox');
+ await user.type(input, 'NewSubform');
+
+ const saveButton = screen.getByRole('button', { name: textMock('general.save') });
+
+ const displayDataModelInput = screen.getByRole('button', {
+ name: textMock('ux_editor.component_properties.subform.create_new_data_model'),
+ });
+ await user.click(displayDataModelInput);
+
+ const dataModelInput = screen.getByRole('textbox', {
+ name: textMock('ux_editor.component_properties.subform.create_new_data_model_label'),
+ });
+ await user.type(dataModelInput, 'æøå');
+ expect(saveButton).toBeDisabled();
+
+ await user.clear(dataModelInput);
+ await user.type(dataModelInput, 'datamodel');
+ expect(saveButton).not.toBeDisabled();
+ });
+
it('enables save button when both input and data model is valid', async () => {
const user = userEvent.setup();
renderCreateNewSubformLayoutSet({});
@@ -159,6 +184,31 @@ describe('CreateNewSubformLayoutSet ', () => {
await user.type(dataModelInput, 'datamodel');
expect(saveButton).not.toBeDisabled();
});
+
+ it('Should toggle ErrorMessage visibility based on input validity', async () => {
+ const user = userEvent.setup();
+ renderCreateNewSubformLayoutSet({});
+
+ const input = screen.getByRole('textbox');
+ await user.type(input, 'NewSubform');
+
+ const displayDataModelInput = screen.getByRole('button', {
+ name: textMock('ux_editor.component_properties.subform.create_new_data_model'),
+ });
+ await user.click(displayDataModelInput);
+
+ const dataModelInput = screen.getByRole('textbox', {
+ name: textMock('ux_editor.component_properties.subform.create_new_data_model_label'),
+ });
+
+ await user.type(dataModelInput, 'new');
+ const errorMessage = screen.getByText(textMock('schema_editor.error_reserved_keyword'));
+ expect(errorMessage).toBeInTheDocument();
+
+ await user.clear(dataModelInput);
+ await user.type(dataModelInput, 'datamodel');
+ expect(errorMessage).not.toBeInTheDocument();
+ });
});
type RenderCreateNewSubformLayoutSetProps = {
diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/CreateNewSubformSection.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/CreateNewSubformSection.tsx
index c6efe918455..6a3699740e5 100644
--- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/CreateNewSubformSection.tsx
+++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/CreateNewSubformSection.tsx
@@ -12,6 +12,11 @@ import { SubformDataModel } from './SubformDataModel';
import { CreateNewSubformButtons } from './CreateNewSubformButtons';
import { SubformInstructions } from './SubformInstructions';
import { useCreateSubform } from '@altinn/ux-editor/hooks/useCreateSubform';
+import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
+import { useAppMetadataModelIdsQuery } from 'app-shared/hooks/queries/useAppMetadataModelIdsQuery';
+import { useAppMetadataQuery } from 'app-shared/hooks/queries';
+import { extractDataTypeNamesFromAppMetadata } from 'app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/utils/validationUtils';
+import { useValidateSchemaName } from 'app-shared/hooks/useValidateSchemaName';
type CreateNewSubformSectionProps = {
layoutSets: LayoutSets;
@@ -33,26 +38,36 @@ export const CreateNewSubformSection = ({
}: CreateNewSubformSectionProps): React.ReactElement => {
const { t } = useTranslation();
const { validateLayoutSetName } = useValidateLayoutSetName();
- const [nameError, setNameError] = useState();
- const [newDataModel, setNewDataModel] = useState('');
+ const [newSubformNameError, setNewSubformNameError] = useState();
const [selectedDataModel, setSelectedDataModel] = useState('');
const [displayDataModelInput, setDisplayDataModelInput] = useState(false);
const { createSubform, isPendingNewSubformMutation } = useCreateSubform();
+ const [isNewDataModelFieldEmpty, setIsNewDataModelFieldEmpty] = useState(true);
+
+ const { org, app } = useStudioEnvironmentParams();
+ const { data: dataModelIds } = useAppMetadataModelIdsQuery(org, app, false);
+ const { data: appMetadata } = useAppMetadataQuery(org, app);
+ const dataTypeNames = extractDataTypeNamesFromAppMetadata(appMetadata);
+ const {
+ validateName,
+ nameError: dataModelNameError,
+ setNameError: setDataModelNameError,
+ } = useValidateSchemaName(dataModelIds, dataTypeNames);
const handleSubformName = (subformName: string) => {
const subformNameValidation = validateLayoutSetName(subformName, layoutSets);
- setNameError(subformNameValidation);
+ setNewSubformNameError(subformNameValidation);
};
const handleCloseButton = () => {
if (displayDataModelInput) {
- setNewDataModel('');
+ setDataModelNameError('');
+ setIsNewDataModelFieldEmpty(true);
setDisplayDataModelInput(false);
} else {
setShowCreateSubformCard(false);
}
};
-
const handleCreateSubformSubmit = (e: React.FormEvent): void => {
e.preventDefault();
const formData: FormData = new FormData(e.currentTarget);
@@ -68,8 +83,11 @@ export const CreateNewSubformSection = ({
});
};
- const hasInvalidSubformName = nameError === undefined || Boolean(nameError);
- const hasInvalidDataModel = displayDataModelInput ? !newDataModel : !selectedDataModel;
+ const hasInvalidSubformName = newSubformNameError === undefined || Boolean(newSubformNameError);
+ const hasInvalidDataModel = displayDataModelInput
+ ? Boolean(dataModelNameError) || isNewDataModelFieldEmpty
+ : !selectedDataModel;
+ const disableSaveButton = hasInvalidSubformName || hasInvalidDataModel;
return (
handleSubformName(e.target.value)}
- error={nameError}
+ error={newSubformNameError}
/>
diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/SubformDataModel.test.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/SubformDataModel.test.tsx
index 6c9807f9bc8..5e9cb0ef9fc 100644
--- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/SubformDataModel.test.tsx
+++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/SubformDataModel.test.tsx
@@ -8,8 +8,6 @@ import { useAppMetadataModelIdsQuery } from 'app-shared/hooks/queries/useAppMeta
jest.mock('app-shared/hooks/queries/useAppMetadataModelIdsQuery');
-const user = userEvent.setup();
-
const mockDataModelIds = ['dataModelId1', 'dataModelId2'];
(useAppMetadataModelIdsQuery as jest.Mock).mockReturnValue({ data: mockDataModelIds });
@@ -41,6 +39,7 @@ describe('SubformDataModel', () => {
});
it('Calls setDataModel when selecting an option', async () => {
+ const user = userEvent.setup();
const setSelectedDataModel = jest.fn();
renderSubformDataModelSelect({ setSelectedDataModel });
@@ -53,6 +52,7 @@ describe('SubformDataModel', () => {
});
it('Should call setDisplayDataModelInput true when clicking create new data model button', async () => {
+ const user = userEvent.setup();
const setDisplayDataModelInput = jest.fn();
renderSubformDataModelSelect({ setDisplayDataModelInput });
const displayDataModelInput = screen.getByRole('button', {
@@ -75,9 +75,12 @@ describe('SubformDataModel', () => {
const defaultProps: SubformDataModelProps = {
setDisplayDataModelInput: jest.fn(),
- setNewDataModel: jest.fn(),
displayDataModelInput: false,
setSelectedDataModel: jest.fn(),
+ dataModelIds: mockDataModelIds,
+ validateName: jest.fn(),
+ dataModelNameError: '',
+ setIsTextfieldEmpty: jest.fn(),
};
const renderSubformDataModelSelect = (props: Partial = {}) => {
diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/SubformDataModel.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/SubformDataModel.tsx
index 32508217bb1..47d7212440b 100644
--- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/SubformDataModel.tsx
+++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformSection/SubformDataModel.tsx
@@ -2,30 +2,32 @@ import React from 'react';
import { StudioTextfield, StudioNativeSelect, StudioProperty } from '@studio/components';
import { LinkIcon } from '@studio/icons';
import { useTranslation } from 'react-i18next';
-import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
-import { useAppMetadataModelIdsQuery } from 'app-shared/hooks/queries/useAppMetadataModelIdsQuery';
import classes from './SubformDataModel.module.css';
export type SubformDataModelProps = {
setDisplayDataModelInput: (setDisplayDataModelInput: boolean) => void;
- setNewDataModel: (dataModelId: string) => void;
displayDataModelInput: boolean;
setSelectedDataModel: (dataModelId: string) => void;
+ dataModelIds?: string[];
+ validateName: (name: string) => void;
+ dataModelNameError: string;
+ setIsTextfieldEmpty: (isEmpty: boolean) => void;
};
export const SubformDataModel = ({
setDisplayDataModelInput,
setSelectedDataModel,
- setNewDataModel,
displayDataModelInput,
+ dataModelIds,
+ validateName,
+ dataModelNameError,
+ setIsTextfieldEmpty,
}: SubformDataModelProps): React.ReactElement => {
const { t } = useTranslation();
- const { org, app } = useStudioEnvironmentParams();
- const { data: dataModelIds } = useAppMetadataModelIdsQuery(org, app, false);
- const handleDataModel = (dataModelId: string) => {
- // TODO: https://github.com/Altinn/altinn-studio/issues/14184
- setNewDataModel(dataModelId);
+ const handleNewDataModel = (dataModelId: string) => {
+ validateName(dataModelId);
+ setIsTextfieldEmpty(dataModelId === '');
};
const handleDisplayInput = () => {
@@ -59,7 +61,8 @@ export const SubformDataModel = ({
name='newSubformDataModel'
label={t('ux_editor.component_properties.subform.create_new_data_model_label')}
size='sm'
- onChange={(e) => handleDataModel(e.target.value)}
+ onChange={(e) => handleNewDataModel(e.target.value)}
+ error={dataModelNameError}
/>
) : (
({
}));
describe('FormComponentConfig', () => {
- it('should render expected components', async () => {
+ it('should render expected default components', async () => {
render({});
+ const properties = ['readOnly', 'required', 'hidden'];
+ for (const property of properties) {
+ expect(
+ await screen.findByText(textMock(`ux_editor.component_properties.${property}`)),
+ ).toBeInTheDocument();
+ }
+ });
+ it('should render the show-button', async () => {
+ render({});
+ const button = screen.getByRole('button', {
+ name: textMock('ux_editor.component_other_properties_show_many_settings'),
+ });
+ expect(button).toBeInTheDocument();
+ });
+
+ it('should render the hide-button after clikcing on show-button', async () => {
+ const user = userEvent.setup();
+ render({});
+ const button = screen.getByRole('button', {
+ name: textMock('ux_editor.component_other_properties_show_many_settings'),
+ });
+ expect(button).toBeInTheDocument();
+ await user.click(button);
+ expect(
+ screen.getByRole('button', {
+ name: textMock('ux_editor.component_other_properties_hide_many_settings'),
+ }),
+ ).toBeInTheDocument();
+ });
+
+ it('Should render the rest of the components when show-button is clicked and show hide-button', async () => {
+ const use = userEvent.setup();
+ render({});
+ const button = screen.getByRole('button', {
+ name: textMock('ux_editor.component_other_properties_show_many_settings'),
+ });
+ expect(button).toBeInTheDocument();
+ await use.click(button);
const properties = [
- 'grid',
- 'readOnly',
- 'required',
- 'hidden',
'renderAsSummary',
'variant',
'autocomplete',
@@ -47,12 +81,15 @@ describe('FormComponentConfig', () => {
'pageBreak',
'formatting',
];
-
for (const property of properties) {
expect(
await screen.findByText(textMock(`ux_editor.component_properties.${property}`)),
).toBeInTheDocument();
}
+ const hideButton = screen.getByRole('button', {
+ name: textMock('ux_editor.component_other_properties_hide_many_settings'),
+ });
+ expect(hideButton).toBeInTheDocument();
});
it('should render "RedirectToLayoutSet"', () => {
@@ -174,12 +211,18 @@ describe('FormComponentConfig', () => {
).toBeInTheDocument();
});
- it('should render default boolean values if defined', () => {
+ it('should render default boolean values if defined', async () => {
+ const user = userEvent.setup();
render({
props: {
schema: DatepickerSchema,
},
});
+ const button = screen.getByRole('button', {
+ name: textMock('ux_editor.component_other_properties_show_many_settings'),
+ });
+ expect(button).toBeInTheDocument();
+ await user.click(button);
const timeStampSwitch = screen.getByRole('checkbox', {
name: textMock('ux_editor.component_properties.timeStamp'),
});
@@ -196,6 +239,11 @@ describe('FormComponentConfig', () => {
handleComponentUpdate: handleComponentUpdateMock,
},
});
+ const button = screen.getByRole('button', {
+ name: textMock('ux_editor.component_other_properties_show_many_settings'),
+ });
+ expect(button).toBeInTheDocument();
+ await user.click(button);
const timeStampSwitch = screen.getByRole('checkbox', {
name: textMock('ux_editor.component_properties.timeStamp'),
});
diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx
index 5ab3cf2935c..3d7f2c9a3d7 100644
--- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx
+++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
import { Alert, Card, Heading, Paragraph } from '@digdir/designsystemet-react';
import type { FormComponent } from '../../types/FormComponent';
import { EditBooleanValue } from './editModal/EditBooleanValue';
@@ -16,6 +16,8 @@ import type { UpdateFormMutateOptions } from '../../containers/FormItemContext';
import { useComponentPropertyDescription } from '../../hooks/useComponentPropertyDescription';
import classes from './FormComponentConfig.module.css';
import { RedirectToLayoutSet } from './editModal/RedirectToLayoutSet';
+import { ChevronDownIcon, ChevronUpIcon } from '@studio/icons';
+import { StudioProperty } from '@studio/components';
export interface IEditFormComponentProps {
editFormId: string;
@@ -38,6 +40,7 @@ export const FormComponentConfig = ({
const t = useText();
const componentPropertyLabel = useComponentPropertyLabel();
const componentPropertyDescription = useComponentPropertyDescription();
+ const [showOtherComponents, setShowOtherComponents] = useState(false);
if (!schema?.properties) return null;
@@ -95,6 +98,18 @@ export const FormComponentConfig = ({
);
});
+ const defaultDisplayedBooleanKeys = booleanPropertyKeys.slice(0, 3);
+ const restOfBooleanKeys = booleanPropertyKeys.slice(3);
+
+ const renderIcon = showOtherComponents ? (
+
+ ) : (
+
+ );
+ const rendertext = showOtherComponents
+ ? t('ux_editor.component_other_properties_hide_many_settings')
+ : t('ux_editor.component_other_properties_show_many_settings');
+
return (
<>
{layoutSet && component['layoutSet'] && (
@@ -119,8 +134,17 @@ export const FormComponentConfig = ({
)}
{/** Boolean fields, incl. expression type */}
- {booleanPropertyKeys.map((propertyKey) => {
- return (
+ {defaultDisplayedBooleanKeys.map((propertyKey) => (
+
+ ))}
+ {showOtherComponents &&
+ restOfBooleanKeys.map((propertyKey) => (
- );
- })}
+ ))}
+ {restOfBooleanKeys.length > 0 && (
+ setShowOtherComponents((prev) => !prev)}
+ property={rendertext}
+ />
+ )}
{/** Custom logic for custom file endings */}
{hasCustomFileEndings && (
diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Summary2Component.test.tsx b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Summary2Component.test.tsx
index 4161539e532..edb9f118615 100644
--- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Summary2Component.test.tsx
+++ b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Summary2Component.test.tsx
@@ -31,7 +31,7 @@ describe('Summary2ComponentTargetSelector', () => {
expect(addNewOverrideButton()).toBeInTheDocument();
- expect(componentTargetSelect()).toBeInTheDocument();
+ expect(disabledLayoutSetTargetSelect()).toBeInTheDocument();
});
it('should select the task id from the current layout when the task id of the target is not defined', async () => {
@@ -52,7 +52,9 @@ describe('Summary2ComponentTargetSelector', () => {
it('should allow selecting a task id', async () => {
const user = userEvent.setup();
- render();
+ render({
+ component: { ...defaultProps.component, target: { type: 'component', id: component1IdMock } },
+ });
await user.selectOptions(targetTaskIdSelect(), 'Task_2');
expect(defaultProps.handleComponentChange).toHaveBeenCalledWith(
@@ -62,7 +64,9 @@ describe('Summary2ComponentTargetSelector', () => {
it('should remove the task id from the target if the task id is the same as the current layout set', async () => {
const user = userEvent.setup();
- render();
+ render({
+ component: { ...defaultProps.component, target: { type: 'component', id: component1IdMock } },
+ });
await user.selectOptions(targetTaskIdSelect(), 'Task_1');
expect(defaultProps.handleComponentChange).toHaveBeenCalledWith(
@@ -70,14 +74,11 @@ describe('Summary2ComponentTargetSelector', () => {
);
});
- it('should allow selecting page target and defaults to same page', async () => {
- const user = userEvent.setup();
+ it('should defaults to page target and disabled target select', async () => {
render();
-
- await user.selectOptions(targetTypeSelect(), 'page');
- expect(defaultProps.handleComponentChange).toHaveBeenCalledWith(
- expect.objectContaining({ target: { type: 'page', id: layout1NameMock } }),
- );
+ expect(targetTypeSelect()).toHaveValue('layoutSet');
+ expect(disabledLayoutSetTargetSelect()).toBeDisabled();
+ expect(disabledLayoutSetTargetSelect()).toHaveValue(layoutSet1NameMock);
});
it('should allow selecting layoutSet target', async () => {
@@ -92,8 +93,11 @@ describe('Summary2ComponentTargetSelector', () => {
it('should allow selecting component target', async () => {
const user = userEvent.setup();
- render();
+ render({
+ component: { ...defaultProps.component, target: { type: 'component', id: component1IdMock } },
+ });
+ await user.selectOptions(targetTypeSelect(), 'component');
const componentId = component1IdMock;
await user.click(componentTargetSelect());
@@ -170,12 +174,17 @@ const targetTypeSelect = () =>
const componentTargetSelect = () =>
screen.getByRole('combobox', {
- name: textMock('general.component'),
+ name: textMock('ux_editor.component_properties.target_unit_component'),
});
const pageTargetSelect = () =>
screen.getByRole('combobox', {
- name: textMock('general.page'),
+ name: textMock('ux_editor.component_properties.target_unit_page'),
+ });
+
+const disabledLayoutSetTargetSelect = () =>
+ screen.getByRole('textbox', {
+ name: textMock('ux_editor.component_properties.target_unit_layout_set'),
});
const addNewOverrideButton = () =>
diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Target/Summary2Target.tsx b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Target/Summary2Target.tsx
index 170360003b0..6a880a0e226 100644
--- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Target/Summary2Target.tsx
+++ b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Summary2/Target/Summary2Target.tsx
@@ -51,10 +51,28 @@ export const Summary2Target = ({ target, onChange }: Summary2TargetProps) => {
const getComponentTitle = useComponentTitle();
const excludedComponents = [
- ComponentType.Summary2,
- ComponentType.NavigationButtons,
+ ComponentType.ActionButton,
+ ComponentType.Alert,
+ ComponentType.AttachmentList,
+ ComponentType.Button,
+ ComponentType.ButtonGroup,
+ ComponentType.CustomButton,
+ ComponentType.Grid,
+ ComponentType.Header,
+ ComponentType.IFrame,
+ ComponentType.Image,
+ ComponentType.InstantiationButton,
+ ComponentType.InstanceInformation,
+ ComponentType.Link,
ComponentType.NavigationBar,
+ ComponentType.NavigationButtons,
+ ComponentType.Panel,
+ ComponentType.Paragraph,
+ ComponentType.PrintButton,
+ ComponentType.Summary,
+ ComponentType.Summary2,
];
+
const components = formLayoutsData
? Object.values(formLayoutsData).flatMap((layout) =>
getAllLayoutComponents(layout, excludedComponents),
@@ -134,7 +152,7 @@ export const Summary2Target = ({ target, onChange }: Summary2TargetProps) => {
{target.type === 'page' && (
{
{target.type === 'component' && (
{
diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx
index 2f581ec6407..767d9861a24 100644
--- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx
+++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditTab/OptionListEditor/OptionListEditor.tsx
@@ -90,7 +90,7 @@ function EditLibraryOptionListEditorModal({
const optionListHasChanged = (options: Option[]): boolean =>
JSON.stringify(options) !== JSON.stringify(localOptionList);
- const handleOptionsChange = (options: Option[]) => {
+ const handleBlurAny = (options: Option[]) => {
if (optionListHasChanged(options)) {
updateOptionList({ optionListId: optionsId, optionsList: options });
setLocalOptionList(options);
@@ -126,7 +126,7 @@ function EditLibraryOptionListEditorModal({
>
@@ -147,7 +147,7 @@ function EditManualOptionListEditorModal({
const modalRef = useRef(null);
const editorTexts = useOptionListEditorTexts();
- const handleOptionsChange = (options: Option[]) => {
+ const handleBlurAny = (options: Option[]) => {
if (component.optionsId) {
delete component.optionsId;
}
@@ -175,7 +175,7 @@ function EditManualOptionListEditorModal({
>
diff --git a/frontend/packages/ux-editor/src/containers/DesignView/FormTree/FormItem/FormItemTitle/FormItemTitle.module.css b/frontend/packages/ux-editor/src/containers/DesignView/FormTree/FormItem/FormItemTitle/FormItemTitle.module.css
index 2018f7f709c..9ee89c296fc 100644
--- a/frontend/packages/ux-editor/src/containers/DesignView/FormTree/FormItem/FormItemTitle/FormItemTitle.module.css
+++ b/frontend/packages/ux-editor/src/containers/DesignView/FormTree/FormItem/FormItemTitle/FormItemTitle.module.css
@@ -5,6 +5,8 @@
.label {
flex: 1;
min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.root:hover .label {
diff --git a/frontend/packages/ux-editor/src/data/formItemConfig.ts b/frontend/packages/ux-editor/src/data/formItemConfig.ts
index c22711e41e1..698cb06b6d4 100644
--- a/frontend/packages/ux-editor/src/data/formItemConfig.ts
+++ b/frontend/packages/ux-editor/src/data/formItemConfig.ts
@@ -472,7 +472,7 @@ export const formItemConfigs: FormItemConfigs = {
itemType: LayoutItemType.Component,
defaultProperties: {
target: {
- type: 'component',
+ type: 'layoutSet',
id: '',
taskId: '',
},
diff --git a/frontend/packages/ux-editor/src/testing/componentMocks.ts b/frontend/packages/ux-editor/src/testing/componentMocks.ts
index 886371032d6..aee0cba48de 100644
--- a/frontend/packages/ux-editor/src/testing/componentMocks.ts
+++ b/frontend/packages/ux-editor/src/testing/componentMocks.ts
@@ -180,7 +180,7 @@ const repeatingGroupContainer: FormContainer = {
const summary2Component: FormComponent = {
...commonProps(ComponentType.Summary2),
target: {
- type: 'component',
+ type: 'layoutSet',
},
};