@sap-ux/control-property-editor 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/app.css.map +1 -1
- package/dist/app.js +112 -66
- package/dist/app.js.map +3 -3
- package/package.json +2 -2
- package/src/i18n/i18n.json +2 -1
- package/src/panels/changes/ChangeStack.tsx +68 -30
- package/src/panels/changes/ChangesPanel.module.scss +0 -1
- package/src/panels/changes/ChangesPanel.tsx +16 -0
- package/src/panels/changes/ControlGroup.tsx +13 -5
- package/src/panels/changes/OtherChange.module.scss +13 -0
- package/src/panels/changes/OtherChange.tsx +41 -0
- package/src/panels/changes/PropertyChange.module.scss +0 -4
- package/src/panels/changes/PropertyChange.tsx +2 -16
- package/src/panels/changes/UnknownChange.module.scss +36 -1
- package/src/panels/changes/UnknownChange.tsx +23 -8
- package/src/panels/changes/index.tsx +1 -1
- package/src/panels/properties/PropertyDocumentation.tsx +2 -6
- package/test/unit/panels/changes/ChangesPanel.test.tsx +126 -15
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "Control Property Editor",
|
|
4
4
|
"description": "Control Property Editor",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.4.0",
|
|
7
7
|
"main": "dist/app.js",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"postcss": "8.4.31",
|
|
51
51
|
"yargs-parser": "21.1.1",
|
|
52
52
|
"@sap-ux/ui-components": "1.11.21",
|
|
53
|
-
"@sap-ux-private/control-property-editor-common": "0.
|
|
53
|
+
"@sap-ux-private/control-property-editor-common": "0.3.0"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"clean": "rimraf ./dist ./coverage *.tsbuildinfo",
|
package/src/i18n/i18n.json
CHANGED
|
@@ -64,5 +64,6 @@
|
|
|
64
64
|
"CONFIRM_CHANGE_SUMMARY_DELETE_SUBTEXT": "Are you sure you want to delete the change for this property? This action cannot be undone.",
|
|
65
65
|
"NO_CONTROL_CHANGES_FOUND": "No Control Changes Found",
|
|
66
66
|
"CHANGE": "Change",
|
|
67
|
-
"FILE": "File: "
|
|
67
|
+
"FILE": "File: ",
|
|
68
|
+
"CONTROL": "Selector Id: "
|
|
68
69
|
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { ReactElement } from 'react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
|
|
4
3
|
import { Stack } from '@fluentui/react';
|
|
5
|
-
|
|
6
4
|
import type { Change, ValidChange } from '@sap-ux-private/control-property-editor-common';
|
|
7
5
|
|
|
8
6
|
import { Separator } from '../../components';
|
|
9
|
-
import type { ControlGroupProps,
|
|
7
|
+
import type { ControlGroupProps, ControlChange } from './ControlGroup';
|
|
10
8
|
import { ControlGroup } from './ControlGroup';
|
|
11
9
|
import type { UnknownChangeProps } from './UnknownChange';
|
|
12
10
|
import { UnknownChange } from './UnknownChange';
|
|
@@ -87,19 +85,23 @@ function convertChanges(changes: Change[]): Item[] {
|
|
|
87
85
|
const items: Item[] = [];
|
|
88
86
|
let i = 0;
|
|
89
87
|
while (i < changes.length) {
|
|
90
|
-
const change = changes[i];
|
|
88
|
+
const change: Change = changes[i];
|
|
89
|
+
let group: ControlGroupProps;
|
|
91
90
|
if (change.type === 'saved' && change.kind === 'unknown') {
|
|
92
91
|
items.push({
|
|
93
92
|
fileName: change.fileName,
|
|
94
|
-
timestamp: change.timestamp
|
|
93
|
+
timestamp: change.timestamp,
|
|
94
|
+
header: true,
|
|
95
|
+
controlId: change.controlId ?? ''
|
|
95
96
|
});
|
|
96
97
|
i++;
|
|
97
98
|
} else {
|
|
98
|
-
|
|
99
|
+
group = {
|
|
99
100
|
controlId: change.controlId,
|
|
101
|
+
controlName: change.controlName,
|
|
100
102
|
text: convertCamelCaseToPascalCase(change.controlName),
|
|
101
103
|
changeIndex: i,
|
|
102
|
-
changes: [
|
|
104
|
+
changes: [classifyChange(change, i)]
|
|
103
105
|
};
|
|
104
106
|
items.push(group);
|
|
105
107
|
i++;
|
|
@@ -112,7 +114,7 @@ function convertChanges(changes: Change[]): Item[] {
|
|
|
112
114
|
) {
|
|
113
115
|
break;
|
|
114
116
|
}
|
|
115
|
-
group.changes.push(
|
|
117
|
+
group.changes.push(classifyChange(nextChange, i));
|
|
116
118
|
i++;
|
|
117
119
|
}
|
|
118
120
|
}
|
|
@@ -121,21 +123,33 @@ function convertChanges(changes: Change[]): Item[] {
|
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
/**
|
|
124
|
-
*
|
|
126
|
+
* Classify Change for grouping.
|
|
125
127
|
*
|
|
126
128
|
* @param change ValidChange
|
|
127
129
|
* @param changeIndex number
|
|
128
|
-
* @returns
|
|
130
|
+
* @returns ControlChange
|
|
129
131
|
*/
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
controlId,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
function classifyChange(change: ValidChange, changeIndex: number): ControlChange {
|
|
133
|
+
let base;
|
|
134
|
+
if (change.changeType === 'propertyChange' || change.changeType === 'propertyBindingChange') {
|
|
135
|
+
const { controlId, propertyName, value, controlName, changeType } = change;
|
|
136
|
+
base = {
|
|
137
|
+
controlId,
|
|
138
|
+
controlName,
|
|
139
|
+
propertyName,
|
|
140
|
+
value,
|
|
141
|
+
changeIndex,
|
|
142
|
+
changeType
|
|
143
|
+
};
|
|
144
|
+
} else {
|
|
145
|
+
const { controlId, controlName, changeType } = change;
|
|
146
|
+
base = {
|
|
147
|
+
controlId,
|
|
148
|
+
controlName,
|
|
149
|
+
changeIndex,
|
|
150
|
+
changeType
|
|
151
|
+
};
|
|
152
|
+
}
|
|
139
153
|
if (change.type === 'pending') {
|
|
140
154
|
const { isActive } = change;
|
|
141
155
|
return {
|
|
@@ -154,28 +168,49 @@ function toPropertyChangeProps(change: ValidChange, changeIndex: number): Contro
|
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
/**
|
|
157
|
-
* Returns true, if
|
|
171
|
+
* Returns true, if controlName is defined.
|
|
158
172
|
*
|
|
159
173
|
* @param change ControlGroupProps | UnknownChangeProps
|
|
160
174
|
* @returns boolean
|
|
161
175
|
*/
|
|
162
176
|
export function isKnownChange(change: ControlGroupProps | UnknownChangeProps): change is ControlGroupProps {
|
|
163
|
-
return (change as ControlGroupProps).
|
|
177
|
+
return (change as ControlGroupProps).controlName !== undefined;
|
|
164
178
|
}
|
|
165
179
|
|
|
166
|
-
const filterPropertyChanges = (changes:
|
|
180
|
+
const filterPropertyChanges = (changes: ControlChange[], query: string): ControlChange[] => {
|
|
167
181
|
return changes.filter((item) => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
182
|
+
if (item.propertyName) {
|
|
183
|
+
return (
|
|
184
|
+
!query ||
|
|
185
|
+
item.propertyName.trim().toLowerCase().includes(query) ||
|
|
186
|
+
convertCamelCaseToPascalCase(item.propertyName.toString()).trim().toLowerCase().includes(query) ||
|
|
187
|
+
item.value.toString().trim().toLowerCase().includes(query) ||
|
|
188
|
+
convertCamelCaseToPascalCase(item.value.toString()).trim().toLowerCase().includes(query) ||
|
|
189
|
+
(item.timestamp && getFormattedDateAndTime(item.timestamp).trim().toLowerCase().includes(query))
|
|
190
|
+
);
|
|
191
|
+
} else if (item.changeType) {
|
|
192
|
+
const changeType = convertCamelCaseToPascalCase(item.changeType);
|
|
193
|
+
return !query || changeType.trim().toLowerCase().includes(query);
|
|
194
|
+
}
|
|
176
195
|
});
|
|
177
196
|
};
|
|
178
197
|
|
|
198
|
+
const isQueryMatchesChange = (item: UnknownChangeProps, query: string): boolean => {
|
|
199
|
+
const parts = item.fileName.split('_');
|
|
200
|
+
const changeName = parts[parts.length - 1];
|
|
201
|
+
const name = convertCamelCaseToPascalCase(changeName + 'Change');
|
|
202
|
+
let dateTime = '';
|
|
203
|
+
if (item.timestamp) {
|
|
204
|
+
dateTime = getFormattedDateAndTime(item.timestamp).trim();
|
|
205
|
+
}
|
|
206
|
+
return (
|
|
207
|
+
!query ||
|
|
208
|
+
item.fileName.trim().toLowerCase().includes(query) ||
|
|
209
|
+
name.trim().toLowerCase().includes(query) ||
|
|
210
|
+
dateTime.toLowerCase().includes(query)
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
|
|
179
214
|
/**
|
|
180
215
|
* Filter group in change stack.
|
|
181
216
|
*
|
|
@@ -191,6 +226,9 @@ function filterGroup(model: Item[], query: string): Item[] {
|
|
|
191
226
|
for (const item of model) {
|
|
192
227
|
let parentMatch = false;
|
|
193
228
|
if (!isKnownChange(item)) {
|
|
229
|
+
if (isQueryMatchesChange(item, query)) {
|
|
230
|
+
filteredModel.push({ ...item, changes: [] });
|
|
231
|
+
}
|
|
194
232
|
continue;
|
|
195
233
|
}
|
|
196
234
|
const name = item.text.trim().toLowerCase();
|
|
@@ -17,6 +17,22 @@ import { ChangeStackHeader } from './ChangeStackHeader';
|
|
|
17
17
|
|
|
18
18
|
import styles from './ChangesPanel.module.scss';
|
|
19
19
|
|
|
20
|
+
export interface ChangeProps {
|
|
21
|
+
controlId: string;
|
|
22
|
+
controlName: string;
|
|
23
|
+
changeIndex: number;
|
|
24
|
+
changeType: string;
|
|
25
|
+
propertyName: string;
|
|
26
|
+
value: string | number | boolean;
|
|
27
|
+
isActive: boolean;
|
|
28
|
+
timestamp?: number;
|
|
29
|
+
fileName?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Class used for showing and hiding actions
|
|
32
|
+
*/
|
|
33
|
+
actionClassName: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
/**
|
|
21
37
|
* React element for ChangePanel.
|
|
22
38
|
*
|
|
@@ -5,18 +5,20 @@ import { Link, Stack } from '@fluentui/react';
|
|
|
5
5
|
import { useAppDispatch } from '../../store';
|
|
6
6
|
import { selectControl } from '@sap-ux-private/control-property-editor-common';
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type { ChangeProps } from './ChangesPanel';
|
|
9
9
|
import { PropertyChange } from './PropertyChange';
|
|
10
|
+
import { OtherChange } from './OtherChange';
|
|
10
11
|
|
|
11
12
|
import styles from './ControlGroup.module.scss';
|
|
12
13
|
|
|
13
14
|
export interface ControlGroupProps {
|
|
14
15
|
text: string;
|
|
15
16
|
controlId: string;
|
|
17
|
+
controlName: string;
|
|
16
18
|
changeIndex: number;
|
|
17
|
-
changes:
|
|
19
|
+
changes: ControlChange[];
|
|
18
20
|
}
|
|
19
|
-
export type
|
|
21
|
+
export type ControlChange = Omit<ChangeProps, 'actionClassName'>;
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* React Element for control groups.
|
|
@@ -50,10 +52,16 @@ export function ControlGroup(controlGroupProps: ControlGroupProps): ReactElement
|
|
|
50
52
|
</Stack.Item>
|
|
51
53
|
{changes.map((change) => (
|
|
52
54
|
<Stack.Item
|
|
53
|
-
data-testid={`${stackName}-${controlId}-${change.propertyName}-${
|
|
55
|
+
data-testid={`${stackName}-${controlId}-${change.propertyName ?? change.changeType}-${
|
|
56
|
+
change.changeIndex
|
|
57
|
+
}`}
|
|
54
58
|
key={`${change.changeIndex}`}
|
|
55
59
|
className={styles.item}>
|
|
56
|
-
|
|
60
|
+
{['propertyChange', 'propertyBindingChange'].includes(change.changeType) ? (
|
|
61
|
+
<PropertyChange {...change} actionClassName={styles.actions} />
|
|
62
|
+
) : (
|
|
63
|
+
<OtherChange {...change} actionClassName={styles.actions} />
|
|
64
|
+
)}
|
|
57
65
|
</Stack.Item>
|
|
58
66
|
))}
|
|
59
67
|
</Stack>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Stack, Text } from '@fluentui/react';
|
|
5
|
+
import { convertCamelCaseToPascalCase } from '@sap-ux-private/control-property-editor-common';
|
|
6
|
+
|
|
7
|
+
import styles from './OtherChange.module.scss';
|
|
8
|
+
import type { ChangeProps } from './ChangesPanel';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* React element for Other change.
|
|
12
|
+
*
|
|
13
|
+
* @param OtherChangeProps OtherChangeProps
|
|
14
|
+
* @returns ReactElement
|
|
15
|
+
*/
|
|
16
|
+
export function OtherChange(OtherChangeProps: Readonly<ChangeProps>): ReactElement {
|
|
17
|
+
const { changeType, isActive } = OtherChangeProps;
|
|
18
|
+
return (
|
|
19
|
+
<Stack
|
|
20
|
+
tokens={{
|
|
21
|
+
childrenGap: 5
|
|
22
|
+
}}
|
|
23
|
+
className={styles.container}
|
|
24
|
+
style={{
|
|
25
|
+
opacity: isActive ? 1 : 0.4
|
|
26
|
+
}}>
|
|
27
|
+
<Stack.Item className={styles.changeType}>
|
|
28
|
+
<Stack
|
|
29
|
+
horizontal
|
|
30
|
+
horizontalAlign={'space-between'}
|
|
31
|
+
tokens={{
|
|
32
|
+
childrenGap: 5
|
|
33
|
+
}}>
|
|
34
|
+
<Stack.Item>
|
|
35
|
+
<Text className={styles.text}>{convertCamelCaseToPascalCase(changeType)}</Text>
|
|
36
|
+
</Stack.Item>
|
|
37
|
+
</Stack>
|
|
38
|
+
</Stack.Item>
|
|
39
|
+
</Stack>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -12,21 +12,7 @@ import { IconName } from '../../icons';
|
|
|
12
12
|
|
|
13
13
|
import styles from './PropertyChange.module.scss';
|
|
14
14
|
import { getFormattedDateAndTime } from './utils';
|
|
15
|
-
|
|
16
|
-
export interface PropertyChangeProps {
|
|
17
|
-
controlId: string;
|
|
18
|
-
controlName: string;
|
|
19
|
-
changeIndex: number;
|
|
20
|
-
propertyName: string;
|
|
21
|
-
value: string | number | boolean;
|
|
22
|
-
isActive: boolean;
|
|
23
|
-
timestamp?: number;
|
|
24
|
-
fileName?: string;
|
|
25
|
-
/**
|
|
26
|
-
* Class used for showing and hiding actions
|
|
27
|
-
*/
|
|
28
|
-
actionClassName: string;
|
|
29
|
-
}
|
|
15
|
+
import type { ChangeProps } from './ChangesPanel';
|
|
30
16
|
|
|
31
17
|
/**
|
|
32
18
|
* React element for property change.
|
|
@@ -34,7 +20,7 @@ export interface PropertyChangeProps {
|
|
|
34
20
|
* @param propertyChangeProps PropertyChangeProps
|
|
35
21
|
* @returns ReactElement
|
|
36
22
|
*/
|
|
37
|
-
export function PropertyChange(propertyChangeProps:
|
|
23
|
+
export function PropertyChange(propertyChangeProps: Readonly<ChangeProps>): ReactElement {
|
|
38
24
|
const { controlId, propertyName, value, isActive, timestamp, fileName, actionClassName } = propertyChangeProps;
|
|
39
25
|
const { t } = useTranslation();
|
|
40
26
|
const dispatch = useDispatch();
|
|
@@ -9,8 +9,43 @@
|
|
|
9
9
|
white-space: nowrap;
|
|
10
10
|
color: var(--vscode-editor-foreground);
|
|
11
11
|
width: 240px;
|
|
12
|
+
direction: rtl;
|
|
13
|
+
text-align: left;
|
|
14
|
+
}
|
|
15
|
+
.fileLabel {
|
|
16
|
+
min-width: 30px;
|
|
17
|
+
margin-top: 5px;
|
|
18
|
+
}
|
|
19
|
+
.fileText {
|
|
20
|
+
margin-top: 5px;
|
|
21
|
+
line-height: 18px;
|
|
22
|
+
display: inline-block;
|
|
23
|
+
text-overflow: ellipsis;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
color: var(--vscode-editor-foreground);
|
|
27
|
+
width: 210px;
|
|
28
|
+
direction: rtl;
|
|
29
|
+
text-align: left;
|
|
30
|
+
}
|
|
31
|
+
.controlLabel {
|
|
32
|
+
min-width: 75px;
|
|
33
|
+
margin-top: 5px;
|
|
34
|
+
}
|
|
35
|
+
.controlText {
|
|
36
|
+
margin-top: 5px;
|
|
37
|
+
line-height: 18px;
|
|
38
|
+
display: inline-block;
|
|
39
|
+
text-overflow: ellipsis;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
color: var(--vscode-editor-foreground);
|
|
43
|
+
width: 165px;
|
|
44
|
+
direction: rtl;
|
|
45
|
+
text-align: left;
|
|
12
46
|
}
|
|
13
47
|
.timestamp {
|
|
48
|
+
margin-top: 5px;
|
|
14
49
|
color: var(--vscode-editor-foreground);
|
|
15
50
|
font-size: 11px;
|
|
16
51
|
line-height: 15px;
|
|
@@ -19,7 +54,7 @@
|
|
|
19
54
|
}
|
|
20
55
|
.textHeader {
|
|
21
56
|
display: inline-block;
|
|
22
|
-
color: var(--vscode-
|
|
57
|
+
color: var(--vscode-foreground);
|
|
23
58
|
font-size: 13px;
|
|
24
59
|
font-weight: bold;
|
|
25
60
|
text-overflow: ellipsis;
|
|
@@ -12,6 +12,8 @@ import { getFormattedDateAndTime } from './utils';
|
|
|
12
12
|
export interface UnknownChangeProps {
|
|
13
13
|
fileName: string;
|
|
14
14
|
timestamp?: number;
|
|
15
|
+
controlId?: string;
|
|
16
|
+
header?: boolean;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -21,7 +23,7 @@ export interface UnknownChangeProps {
|
|
|
21
23
|
* @returns ReactElement
|
|
22
24
|
*/
|
|
23
25
|
export function UnknownChange(unknownChangeProps: UnknownChangeProps): ReactElement {
|
|
24
|
-
const { fileName, timestamp } = unknownChangeProps;
|
|
26
|
+
const { fileName, timestamp, header, controlId } = unknownChangeProps;
|
|
25
27
|
const { t } = useTranslation();
|
|
26
28
|
const dispatch = useDispatch();
|
|
27
29
|
const [dialogState, setDialogState] = useState<PropertyChangeDeletionDetails | undefined>(undefined);
|
|
@@ -46,14 +48,27 @@ export function UnknownChange(unknownChangeProps: UnknownChangeProps): ReactElem
|
|
|
46
48
|
<Stack.Item className={styles.property}>
|
|
47
49
|
<Stack horizontal>
|
|
48
50
|
<Stack.Item>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
{header && (
|
|
52
|
+
<Text className={styles.textHeader}>
|
|
53
|
+
{name} {t('CHANGE')}
|
|
54
|
+
</Text>
|
|
55
|
+
)}
|
|
56
|
+
<Stack horizontal>
|
|
57
|
+
<Stack.Item className={styles.fileLabel}>{t('FILE')}</Stack.Item>
|
|
58
|
+
<Stack.Item className={styles.fileText} title={fileName}>
|
|
59
|
+
{fileName}
|
|
60
|
+
</Stack.Item>
|
|
61
|
+
</Stack>
|
|
62
|
+
{controlId && (
|
|
63
|
+
<Stack horizontal>
|
|
64
|
+
<Stack.Item className={styles.controlLabel}>{t('CONTROL')}</Stack.Item>
|
|
65
|
+
<Stack.Item className={styles.controlText} title={controlId}>
|
|
66
|
+
{controlId}
|
|
67
|
+
</Stack.Item>
|
|
68
|
+
</Stack>
|
|
69
|
+
)}
|
|
56
70
|
</Stack.Item>
|
|
71
|
+
|
|
57
72
|
{fileName && (
|
|
58
73
|
<Stack.Item className={styles.actions}>
|
|
59
74
|
<UIIconButton
|
|
@@ -6,11 +6,7 @@ import { Text, Stack } from '@fluentui/react';
|
|
|
6
6
|
|
|
7
7
|
import { UIIcon, UIIconButton, UiIcons } from '@sap-ux/ui-components';
|
|
8
8
|
|
|
9
|
-
import type {
|
|
10
|
-
Control,
|
|
11
|
-
SavedPropertyChange,
|
|
12
|
-
PendingPropertyChange
|
|
13
|
-
} from '@sap-ux-private/control-property-editor-common';
|
|
9
|
+
import type { Control, SavedPropertyChange, PendingChange } from '@sap-ux-private/control-property-editor-common';
|
|
14
10
|
import { Separator } from '../../components';
|
|
15
11
|
import type { RootState } from '../../store';
|
|
16
12
|
|
|
@@ -42,7 +38,7 @@ export function PropertyDocumentation(propDocProps: PropertyDocumentationProps):
|
|
|
42
38
|
pending: number;
|
|
43
39
|
saved: number;
|
|
44
40
|
lastSavedChange?: SavedPropertyChange;
|
|
45
|
-
lastChange?:
|
|
41
|
+
lastChange?: PendingChange;
|
|
46
42
|
}
|
|
47
43
|
| undefined
|
|
48
44
|
>((state) => state.changes.controls[control?.id ?? '']?.properties[propertyName]);
|