@sap-ux/preview-middleware 0.25.34 → 0.25.37

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.
@@ -370,6 +370,88 @@ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-proper
370
370
  }
371
371
  }
372
372
 
373
+ /**
374
+ * Build a PendingGenericChange and track config change path if applicable.
375
+ *
376
+ * @param changeDefinition - change definition object
377
+ * @param changeType - the change type string
378
+ * @param handler - the generic change handler
379
+ * @param isActive - whether the change is currently active
380
+ * @param fileName - file name of the change
381
+ * @param textBundle - i18n text bundle
382
+ * @returns Promise resolving to PendingGenericChange
383
+ */
384
+ async buildGenericChange(changeDefinition, changeType, handler, isActive, fileName, textBundle) {
385
+ const {
386
+ properties,
387
+ changeTitle,
388
+ controlId,
389
+ changeType: type,
390
+ subtitle
391
+ } = await handler(changeDefinition, {
392
+ textBundle,
393
+ appComponent: this.options.rta.getRootControlInstance(),
394
+ configPropertyControlIdMap: this.configPropertyControlIdMap
395
+ });
396
+ const genericChange = {
397
+ kind: GENERIC_CHANGE_KIND,
398
+ type: 'pending',
399
+ changeType: type ?? changeType,
400
+ ...(subtitle && {
401
+ subtitle
402
+ }),
403
+ isActive,
404
+ title: textBundle.getText(changeTitle),
405
+ fileName,
406
+ ...(controlId && {
407
+ controlId
408
+ }),
409
+ properties
410
+ };
411
+ if (changeType === 'appdescr_fe_changePageConfiguration') {
412
+ const configChangePath = changeDefinition.content.entityPropertyChange.propertyPath;
413
+ if (genericChange.isActive) {
414
+ this.configPropertyPath.add(configChangePath);
415
+ } else {
416
+ // remove value from set if change is undone
417
+ this.configPropertyPath.delete(configChangePath);
418
+ }
419
+ this.trackPendingConfigChanges(genericChange);
420
+ }
421
+ return genericChange;
422
+ }
423
+
424
+ /**
425
+ * Build a fallback PendingChange for changes without a registered handler.
426
+ *
427
+ * @param changeType - the change type string
428
+ * @param selectorId - optional selector/control ID
429
+ * @param isActive - whether the change is currently active
430
+ * @param fileName - file name of the change
431
+ * @returns PendingChange
432
+ */
433
+ buildFallbackChange(changeType, selectorId, isActive, fileName) {
434
+ const title = TITLE_MAP[changeType] ?? '';
435
+ let result = {
436
+ type: PENDING_CHANGE_TYPE,
437
+ kind: UNKNOWN_CHANGE_KIND,
438
+ ...(title && {
439
+ title
440
+ }),
441
+ changeType,
442
+ isActive,
443
+ fileName
444
+ };
445
+ if (selectorId) {
446
+ result = {
447
+ ...result,
448
+ kind: 'control',
449
+ controlId: selectorId
450
+ };
451
+ }
452
+ return result;
453
+ }
454
+
373
455
  /**
374
456
  * Prepares the type of change based on the command and other parameters.
375
457
  *
@@ -391,65 +473,11 @@ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-proper
391
473
  fileName
392
474
  } = changeDefinition;
393
475
  const handler = GENERIC_CHANGE_HANDLER[changeType];
476
+ const isActive = index >= inactiveCommandCount;
394
477
  if (handler) {
395
- const {
396
- properties,
397
- changeTitle,
398
- controlId,
399
- changeType: type,
400
- subtitle
401
- } = await handler(changeDefinition, {
402
- textBundle,
403
- appComponent: this.options.rta.getRootControlInstance(),
404
- configPropertyControlIdMap: this.configPropertyControlIdMap
405
- });
406
- const genericChange = {
407
- kind: GENERIC_CHANGE_KIND,
408
- type: 'pending',
409
- changeType: type ?? changeType,
410
- ...(subtitle && {
411
- subtitle
412
- }),
413
- isActive: index >= inactiveCommandCount,
414
- title: textBundle.getText(changeTitle),
415
- fileName,
416
- ...(controlId && {
417
- controlId
418
- }),
419
- properties
420
- };
421
- if (changeType === 'appdescr_fe_changePageConfiguration') {
422
- const configChangePath = changeDefinition.content.entityPropertyChange.propertyPath;
423
- if (genericChange.isActive) {
424
- this.configPropertyPath.add(configChangePath);
425
- } else {
426
- // remove value from set if change is undone
427
- this.configPropertyPath.delete(configChangePath);
428
- }
429
- this.trackPendingConfigChanges(genericChange);
430
- }
431
- return genericChange;
432
- } else {
433
- const title = TITLE_MAP[changeType] ?? '';
434
- let result = {
435
- type: PENDING_CHANGE_TYPE,
436
- kind: UNKNOWN_CHANGE_KIND,
437
- ...(title && {
438
- title
439
- }),
440
- changeType,
441
- isActive: index >= inactiveCommandCount,
442
- fileName
443
- };
444
- if (selectorId) {
445
- result = {
446
- ...result,
447
- kind: 'control',
448
- controlId: selectorId
449
- };
450
- }
451
- return result;
478
+ return this.buildGenericChange(changeDefinition, changeType, handler, isActive, fileName, textBundle);
452
479
  }
480
+ return this.buildFallbackChange(changeType, selectorId, isActive, fileName);
453
481
  }
454
482
 
455
483
  /**
@@ -433,6 +433,94 @@ export class ChangeService extends EventTarget {
433
433
  }
434
434
  }
435
435
 
436
+ /**
437
+ * Build a PendingGenericChange and track config change path if applicable.
438
+ *
439
+ * @param changeDefinition - change definition object
440
+ * @param changeType - the change type string
441
+ * @param handler - the generic change handler
442
+ * @param isActive - whether the change is currently active
443
+ * @param fileName - file name of the change
444
+ * @param textBundle - i18n text bundle
445
+ * @returns Promise resolving to PendingGenericChange
446
+ */
447
+ private async buildGenericChange(
448
+ changeDefinition: ChangeDefinition,
449
+ changeType: string,
450
+ handler: ChangeHandler<GenericChange>,
451
+ isActive: boolean,
452
+ fileName: string,
453
+ textBundle: Awaited<ReturnType<typeof getTextBundle>>
454
+ ): Promise<PendingGenericChange> {
455
+ const {
456
+ properties,
457
+ changeTitle,
458
+ controlId,
459
+ changeType: type,
460
+ subtitle
461
+ } = await handler(changeDefinition as unknown as GenericChange, {
462
+ textBundle,
463
+ appComponent: this.options.rta.getRootControlInstance(),
464
+ configPropertyControlIdMap: this.configPropertyControlIdMap
465
+ });
466
+ const genericChange: PendingGenericChange = {
467
+ kind: GENERIC_CHANGE_KIND,
468
+ type: 'pending',
469
+ changeType: type ?? changeType,
470
+ ...(subtitle && { subtitle }),
471
+ isActive,
472
+ title: textBundle.getText(changeTitle),
473
+ fileName,
474
+ ...(controlId && { controlId }),
475
+ properties
476
+ };
477
+ if (changeType === 'appdescr_fe_changePageConfiguration') {
478
+ const configChangePath = (changeDefinition as ConfigChange).content.entityPropertyChange.propertyPath;
479
+ if (genericChange.isActive) {
480
+ this.configPropertyPath.add(configChangePath);
481
+ } else {
482
+ // remove value from set if change is undone
483
+ this.configPropertyPath.delete(configChangePath);
484
+ }
485
+ this.trackPendingConfigChanges(genericChange);
486
+ }
487
+ return genericChange;
488
+ }
489
+
490
+ /**
491
+ * Build a fallback PendingChange for changes without a registered handler.
492
+ *
493
+ * @param changeType - the change type string
494
+ * @param selectorId - optional selector/control ID
495
+ * @param isActive - whether the change is currently active
496
+ * @param fileName - file name of the change
497
+ * @returns PendingChange
498
+ */
499
+ private buildFallbackChange(
500
+ changeType: string,
501
+ selectorId: string | undefined,
502
+ isActive: boolean,
503
+ fileName: string
504
+ ): PendingChange {
505
+ const title = TITLE_MAP[changeType] ?? '';
506
+ let result: PendingChange = {
507
+ type: PENDING_CHANGE_TYPE,
508
+ kind: UNKNOWN_CHANGE_KIND,
509
+ ...(title && { title }),
510
+ changeType,
511
+ isActive,
512
+ fileName
513
+ };
514
+ if (selectorId) {
515
+ result = {
516
+ ...result,
517
+ kind: 'control',
518
+ controlId: selectorId
519
+ };
520
+ }
521
+ return result;
522
+ }
523
+
436
524
  /**
437
525
  * Prepares the type of change based on the command and other parameters.
438
526
  *
@@ -462,60 +550,11 @@ export class ChangeService extends EventTarget {
462
550
  const changeDefinition = change.getDefinition ? change.getDefinition() : (change.getJson() as ChangeDefinition);
463
551
  const { fileName } = changeDefinition;
464
552
  const handler = GENERIC_CHANGE_HANDLER[changeType as ChangeType] as unknown as ChangeHandler<GenericChange>;
553
+ const isActive = index >= inactiveCommandCount;
465
554
  if (handler) {
466
- const {
467
- properties,
468
- changeTitle,
469
- controlId,
470
- changeType: type,
471
- subtitle
472
- } = await handler(changeDefinition as unknown as GenericChange, {
473
- textBundle,
474
- appComponent: this.options.rta.getRootControlInstance(),
475
- configPropertyControlIdMap: this.configPropertyControlIdMap
476
- });
477
- const genericChange: PendingGenericChange = {
478
- kind: GENERIC_CHANGE_KIND,
479
- type: 'pending',
480
- changeType: type ?? changeType,
481
- ...(subtitle && { subtitle }),
482
- isActive: index >= inactiveCommandCount,
483
- title: textBundle.getText(changeTitle),
484
- fileName,
485
- ...(controlId && { controlId }),
486
- properties
487
- };
488
- if (changeType === 'appdescr_fe_changePageConfiguration') {
489
- const configChangePath = (changeDefinition as ConfigChange).content.entityPropertyChange.propertyPath;
490
- if (genericChange.isActive) {
491
- this.configPropertyPath.add(configChangePath);
492
- } else {
493
- // remove value from set if change is undone
494
- this.configPropertyPath.delete(configChangePath);
495
- }
496
- this.trackPendingConfigChanges(genericChange);
497
- }
498
- return genericChange;
499
- } else {
500
- const title = TITLE_MAP[changeType] ?? '';
501
- let result: PendingChange = {
502
- type: PENDING_CHANGE_TYPE,
503
- kind: UNKNOWN_CHANGE_KIND,
504
- ...(title && { title }),
505
- changeType,
506
- isActive: index >= inactiveCommandCount,
507
- fileName
508
- };
509
-
510
- if (selectorId) {
511
- result = {
512
- ...result,
513
- kind: 'control',
514
- controlId: selectorId
515
- };
516
- }
517
- return result;
555
+ return this.buildGenericChange(changeDefinition, changeType, handler, isActive, fileName, textBundle);
518
556
  }
557
+ return this.buildFallbackChange(changeType, selectorId, isActive, fileName);
519
558
  }
520
559
 
521
560
  /**
@@ -169,14 +169,188 @@ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-proper
169
169
  return rawValue;
170
170
  }
171
171
  }
172
+ /**
173
+ * Build the documentation object for a configuration (manifest) property.
174
+ *
175
+ * @param property - manifest property
176
+ * @returns PropertyDocumentation
177
+ */
178
+ function buildConfigPropertyDocumentation(property) {
179
+ const defValue = ['undefined', 'null'].includes(String(property.defaultValue)) ? '-' : String(property.defaultValue);
180
+ return {
181
+ description: property.description,
182
+ propertyName: property.id,
183
+ type: property.type,
184
+ defaultValue: defValue || '-'
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Process a manifest configuration property.
190
+ *
191
+ * @param property - manifest property
192
+ * @param control - ui5 control
193
+ * @returns ProcessedProperty or undefined if the property should be skipped
194
+ */
195
+ function processConfigProperty(property, control) {
196
+ const analyzedType = analyzeManifestProperty(property);
197
+ if (!analyzedType || property?.restrictedTo?.length && !property?.restrictedTo?.includes(getV4PageType(control))) {
198
+ return undefined;
199
+ }
200
+ return {
201
+ analyzedType,
202
+ isEnabled: true,
203
+ value: property.value,
204
+ propertyType: PropertyType.Configuration,
205
+ docu: buildConfigPropertyDocumentation(property)
206
+ };
207
+ }
208
+
209
+ /**
210
+ * Process a UI5 control property.
211
+ *
212
+ * @param property - control metadata property
213
+ * @param control - ui5 control
214
+ * @param hasStableId - whether the control has a stable ID
215
+ * @param controlProperties - overlay design-time property config
216
+ * @param controlOverlay - element overlay
217
+ * @returns ProcessedProperty or undefined if the property should be skipped
218
+ */
219
+ function processControlProperty(property, control, hasStableId, controlProperties, controlOverlay) {
220
+ const analyzedType = analyzePropertyType(property);
221
+ if (!analyzedType) {
222
+ return undefined;
223
+ }
224
+ const ignore = controlProperties?.[property.name]?.ignore ?? false;
225
+
226
+ //updating i18n text for the control if bindingInfo has bindingString
227
+ const controlNewData = {
228
+ id: control.getId(),
229
+ name: property.name,
230
+ newValue: control.getProperty(property.name)
231
+ };
232
+ const bindingInfo = control.getBindingInfo(controlNewData.name);
233
+ if (bindingInfo?.bindingString !== undefined) {
234
+ controlNewData.newValue = bindingInfo.bindingString;
235
+ }
236
+
237
+ // A property is enabled if:
238
+ // 1. The property supports changes
239
+ // 2. The control has stable ID
240
+ // 3. It is not configured to be ignored in design time
241
+ // 4. And control overlay is selectable
242
+ return {
243
+ analyzedType,
244
+ isEnabled: isControlEnabled(analyzedType, hasStableId, ignore, controlOverlay),
245
+ value: normalizeObjectPropertyValue(controlNewData.newValue),
246
+ propertyType: PropertyType.ControlProperty
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Build a ControlProperty entry from an analyzed property.
252
+ *
253
+ * @param property - the raw property (manifest or control metadata)
254
+ * @param processed - the processed property data
255
+ * @param selectedControlName - name of the UI5 control class
256
+ * @returns ControlProperty or undefined if the type is not supported
257
+ */
258
+ function buildPropertyEntry(property, processed, selectedControlName) {
259
+ const {
260
+ analyzedType,
261
+ isEnabled,
262
+ value,
263
+ propertyType,
264
+ docu
265
+ } = processed;
266
+ const isIcon = testIconPattern(property.name) && selectedControlName !== 'sap.m.Image' && analyzedType.ui5Type === 'sap.ui.core.URI';
267
+ const ui5Type = analyzedType.ui5Type || undefined;
268
+ const readableName = convertCamelCaseToPascalCase(property.name);
269
+ const docuSpread = docu ? {
270
+ documentation: docu
271
+ } : {};
272
+ switch (analyzedType.primitiveType) {
273
+ case 'enum':
274
+ {
275
+ const values = analyzedType.enumValues ?? {};
276
+ const options = Object.keys(values).map(key => ({
277
+ key,
278
+ text: values[key]
279
+ }));
280
+ return {
281
+ type: STRING_VALUE_TYPE,
282
+ editor: DROPDOWN_EDITOR_TYPE,
283
+ propertyType,
284
+ name: property.name,
285
+ readableName,
286
+ value: value,
287
+ isEnabled,
288
+ ui5Type,
289
+ options,
290
+ ...docuSpread
291
+ };
292
+ }
293
+ case 'string':
294
+ return {
295
+ type: STRING_VALUE_TYPE,
296
+ editor: INPUT_EDITOR_TYPE,
297
+ propertyType,
298
+ name: property.name,
299
+ readableName,
300
+ value: value,
301
+ isEnabled,
302
+ isIcon,
303
+ ui5Type,
304
+ ...docuSpread
305
+ };
306
+ case 'int':
307
+ return {
308
+ type: INTEGER_VALUE_TYPE,
309
+ editor: INPUT_EDITOR_TYPE,
310
+ propertyType,
311
+ name: property.name,
312
+ readableName,
313
+ value: value,
314
+ isEnabled,
315
+ ui5Type,
316
+ ...docuSpread
317
+ };
318
+ case 'float':
319
+ return {
320
+ type: FLOAT_VALUE_TYPE,
321
+ editor: INPUT_EDITOR_TYPE,
322
+ propertyType,
323
+ name: property.name,
324
+ readableName,
325
+ value: value,
326
+ isEnabled,
327
+ ui5Type,
328
+ ...docuSpread
329
+ };
330
+ case 'boolean':
331
+ return {
332
+ type: BOOLEAN_VALUE_TYPE,
333
+ editor: CHECKBOX_EDITOR_TYPE,
334
+ propertyType,
335
+ name: property.name,
336
+ readableName,
337
+ value: value,
338
+ isEnabled,
339
+ ui5Type,
340
+ ...docuSpread
341
+ };
342
+ default:
343
+ return undefined;
344
+ }
345
+ }
346
+
172
347
  /**
173
348
  * Build control data.
174
349
  *
175
350
  * @param control - ui5 control
176
351
  * @param changeService - Changeservice for change stack event handling.
177
352
  * @param controlOverlay - element overlay
178
- * @param includeDocumentation - include documentation flag
179
- * @returns Promise<Control>
353
+ * @returns Control
180
354
  */
181
355
  function buildControlData(control, changeService, controlOverlay) {
182
356
  const controlMetadata = control.getMetadata();
@@ -185,174 +359,25 @@ sap.ui.define(["open/ux/preview/client/thirdparty/@sap-ux-private/control-proper
185
359
  const overlayData = controlOverlay?.getDesignTimeMetadata().getData();
186
360
  const controlProperties = controlOverlay ? overlayData?.properties : undefined;
187
361
  const manifestProperties = getManifestProperties(control, changeService, controlOverlay);
188
- // Add the control's properties/manifest properties
189
362
  const allProperties = {
190
363
  ...controlMetadata.getAllProperties(),
191
364
  ...manifestProperties
192
365
  };
193
- let propertyType;
194
- const propertyNames = Object.keys(allProperties);
195
366
  const properties = [];
196
- for (const propertyName of propertyNames) {
367
+ for (const propertyName of Object.keys(allProperties)) {
197
368
  const property = allProperties[propertyName];
198
- let analyzedType;
199
- let isEnabled;
200
- let value;
201
- if (property && 'configuration' in property) {
202
- propertyType = PropertyType.Configuration;
203
- analyzedType = analyzeManifestProperty(property);
204
- if (!analyzedType || property?.restrictedTo?.length && !property?.restrictedTo?.includes(getV4PageType(control))) {
205
- continue;
206
- }
207
- isEnabled = true;
208
- value = property.value;
209
- } else {
210
- propertyType = PropertyType.ControlProperty;
211
- // the default behavior is that the property is enabled
212
- // meaning it's not ignored during design time
213
- analyzedType = analyzePropertyType(property);
214
- if (!analyzedType) {
215
- continue;
216
- }
217
- let ignore = false;
218
- if (controlProperties?.[property.name]) {
219
- // check whether the property should be ignored in design time or not
220
- // if it's 'undefined' then it's not considered when building isEnabled because it's 'true'
221
- ignore = controlProperties[property.name].ignore;
222
- }
223
-
224
- //updating i18n text for the control if bindingInfo has bindingString
225
- const controlNewData = {
226
- id: control.getId(),
227
- name: property.name,
228
- newValue: control.getProperty(property.name)
229
- };
230
- const bindingInfo = control.getBindingInfo(controlNewData.name);
231
- if (bindingInfo?.bindingString !== undefined) {
232
- controlNewData.newValue = bindingInfo.bindingString;
233
- }
234
-
235
- // A property is enabled if:
236
- // 1. The property supports changes
237
- // 2. The control has stable ID
238
- // 3. It is not configured to be ignored in design time
239
- // 4. And control overlay is selectable
240
- isEnabled = isControlEnabled(analyzedType, hasStableId, ignore, controlOverlay);
241
- value = normalizeObjectPropertyValue(controlNewData.newValue);
242
- }
243
- const isIcon = testIconPattern(property.name) && selectedControlName !== 'sap.m.Image' && analyzedType.ui5Type === 'sap.ui.core.URI';
244
- const ui5Type = analyzedType.ui5Type || undefined;
245
- const readableName = convertCamelCaseToPascalCase(property.name);
246
- let docu;
247
- if ('configuration' in property) {
248
- const defValue = ['undefined', 'null'].includes(String(property.defaultValue)) ? '-' : String(property.defaultValue);
249
- docu = {
250
- description: property.description,
251
- propertyName: property.id,
252
- type: property.type,
253
- defaultValue: defValue || '-'
254
- };
369
+ const processed = property && 'configuration' in property ? processConfigProperty(property, control) : processControlProperty(property, control, hasStableId, controlProperties, controlOverlay);
370
+ if (!processed) {
371
+ continue;
255
372
  }
256
- switch (analyzedType.primitiveType) {
257
- case 'enum':
258
- {
259
- const values = analyzedType.enumValues ?? {};
260
- const options = Object.keys(values).map(key => ({
261
- key,
262
- text: values[key]
263
- }));
264
- properties.push({
265
- type: STRING_VALUE_TYPE,
266
- editor: DROPDOWN_EDITOR_TYPE,
267
- propertyType,
268
- name: property.name,
269
- readableName,
270
- value: value,
271
- isEnabled,
272
- ui5Type,
273
- options,
274
- ...(docu && {
275
- documentation: docu
276
- })
277
- });
278
- break;
279
- }
280
- case 'string':
281
- {
282
- properties.push({
283
- type: STRING_VALUE_TYPE,
284
- editor: INPUT_EDITOR_TYPE,
285
- propertyType,
286
- name: property.name,
287
- readableName,
288
- value: value,
289
- isEnabled,
290
- isIcon,
291
- ui5Type,
292
- ...(docu && {
293
- documentation: docu
294
- })
295
- });
296
- break;
297
- }
298
- case 'int':
299
- {
300
- properties.push({
301
- type: INTEGER_VALUE_TYPE,
302
- editor: INPUT_EDITOR_TYPE,
303
- propertyType,
304
- name: property.name,
305
- readableName,
306
- value: value,
307
- isEnabled,
308
- ui5Type,
309
- ...(docu && {
310
- documentation: docu
311
- })
312
- });
313
- break;
314
- }
315
- case 'float':
316
- {
317
- properties.push({
318
- type: FLOAT_VALUE_TYPE,
319
- editor: INPUT_EDITOR_TYPE,
320
- propertyType,
321
- name: property.name,
322
- readableName,
323
- value: value,
324
- isEnabled,
325
- ui5Type,
326
- ...(docu && {
327
- documentation: docu
328
- })
329
- });
330
- break;
331
- }
332
- case 'boolean':
333
- {
334
- properties.push({
335
- type: BOOLEAN_VALUE_TYPE,
336
- editor: CHECKBOX_EDITOR_TYPE,
337
- propertyType,
338
- name: property.name,
339
- readableName,
340
- value: value,
341
- isEnabled,
342
- ui5Type,
343
- ...(docu && {
344
- documentation: docu
345
- })
346
- });
347
- break;
348
- }
373
+ const entry = buildPropertyEntry(property, processed, selectedControlName);
374
+ if (entry) {
375
+ properties.push(entry);
349
376
  }
350
377
  }
351
378
  return {
352
379
  id: control.getId(),
353
- //the id of the underlying control/aggregation
354
380
  type: selectedControlName,
355
- //the name of the ui5 class of the control/aggregation
356
381
  properties: [...properties].sort((a, b) => a.name > b.name ? 1 : -1),
357
382
  name: selectedControlName
358
383
  };
@@ -200,14 +200,217 @@ interface NewControlData {
200
200
  newValue: unknown;
201
201
  }
202
202
 
203
+ type PropertyDocumentation = {
204
+ defaultValue: string;
205
+ description: string;
206
+ propertyName: string;
207
+ type?: string;
208
+ propertyType?: string;
209
+ };
210
+
211
+ interface ProcessedProperty {
212
+ analyzedType: AnalyzedType;
213
+ isEnabled: boolean;
214
+ value: unknown;
215
+ propertyType: PropertyType;
216
+ docu?: PropertyDocumentation;
217
+ }
218
+
219
+ /**
220
+ * Build the documentation object for a configuration (manifest) property.
221
+ *
222
+ * @param property - manifest property
223
+ * @returns PropertyDocumentation
224
+ */
225
+ function buildConfigPropertyDocumentation(property: MergedSetting): PropertyDocumentation {
226
+ const defValue = ['undefined', 'null'].includes(String(property.defaultValue))
227
+ ? '-'
228
+ : String(property.defaultValue);
229
+ return {
230
+ description: property.description,
231
+ propertyName: property.id,
232
+ type: property.type,
233
+ defaultValue: defValue || '-'
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Process a manifest configuration property.
239
+ *
240
+ * @param property - manifest property
241
+ * @param control - ui5 control
242
+ * @returns ProcessedProperty or undefined if the property should be skipped
243
+ */
244
+ function processConfigProperty(property: MergedSetting, control: ManagedObject): ProcessedProperty | undefined {
245
+ const analyzedType = analyzeManifestProperty(property);
246
+ if (
247
+ !analyzedType ||
248
+ (property?.restrictedTo?.length &&
249
+ !property?.restrictedTo?.includes(getV4PageType(control) as TemplateType))
250
+ ) {
251
+ return undefined;
252
+ }
253
+ return {
254
+ analyzedType,
255
+ isEnabled: true,
256
+ value: property.value,
257
+ propertyType: PropertyType.Configuration,
258
+ docu: buildConfigPropertyDocumentation(property)
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Process a UI5 control property.
264
+ *
265
+ * @param property - control metadata property
266
+ * @param control - ui5 control
267
+ * @param hasStableId - whether the control has a stable ID
268
+ * @param controlProperties - overlay design-time property config
269
+ * @param controlOverlay - element overlay
270
+ * @returns ProcessedProperty or undefined if the property should be skipped
271
+ */
272
+ function processControlProperty(
273
+ property: ManagedObjectMetadataProperties,
274
+ control: ManagedObject,
275
+ hasStableId: boolean,
276
+ controlProperties: Record<string, { ignore: boolean }> | undefined,
277
+ controlOverlay: ElementOverlay | undefined
278
+ ): ProcessedProperty | undefined {
279
+ const analyzedType = analyzePropertyType(property);
280
+ if (!analyzedType) {
281
+ return undefined;
282
+ }
283
+ const ignore = controlProperties?.[property.name]?.ignore ?? false;
284
+
285
+ //updating i18n text for the control if bindingInfo has bindingString
286
+ const controlNewData: NewControlData = {
287
+ id: control.getId(),
288
+ name: property.name,
289
+ newValue: control.getProperty(property.name)
290
+ };
291
+ const bindingInfo: { bindingString?: string } = control.getBindingInfo(controlNewData.name) as {
292
+ bindingString?: string;
293
+ };
294
+ if (bindingInfo?.bindingString !== undefined) {
295
+ controlNewData.newValue = bindingInfo.bindingString;
296
+ }
297
+
298
+ // A property is enabled if:
299
+ // 1. The property supports changes
300
+ // 2. The control has stable ID
301
+ // 3. It is not configured to be ignored in design time
302
+ // 4. And control overlay is selectable
303
+ return {
304
+ analyzedType,
305
+ isEnabled: isControlEnabled(analyzedType, hasStableId, ignore, controlOverlay),
306
+ value: normalizeObjectPropertyValue(controlNewData.newValue),
307
+ propertyType: PropertyType.ControlProperty
308
+ };
309
+ }
310
+
311
+ /**
312
+ * Build a ControlProperty entry from an analyzed property.
313
+ *
314
+ * @param property - the raw property (manifest or control metadata)
315
+ * @param processed - the processed property data
316
+ * @param selectedControlName - name of the UI5 control class
317
+ * @returns ControlProperty or undefined if the type is not supported
318
+ */
319
+ function buildPropertyEntry(
320
+ property: ManagedObjectMetadataProperties | MergedSetting,
321
+ processed: ProcessedProperty,
322
+ selectedControlName: string
323
+ ): ControlProperty | undefined {
324
+ const { analyzedType, isEnabled, value, propertyType, docu } = processed;
325
+ const isIcon =
326
+ testIconPattern(property.name) &&
327
+ selectedControlName !== 'sap.m.Image' &&
328
+ analyzedType.ui5Type === 'sap.ui.core.URI';
329
+ const ui5Type = analyzedType.ui5Type || undefined;
330
+ const readableName = convertCamelCaseToPascalCase(property.name);
331
+ const docuSpread = docu ? { documentation: docu } : {};
332
+
333
+ switch (analyzedType.primitiveType) {
334
+ case 'enum': {
335
+ const values = analyzedType.enumValues ?? {};
336
+ const options: { key: string; text: string }[] = Object.keys(values).map((key) => ({
337
+ key,
338
+ text: values[key]
339
+ }));
340
+ return {
341
+ type: STRING_VALUE_TYPE,
342
+ editor: DROPDOWN_EDITOR_TYPE,
343
+ propertyType,
344
+ name: property.name,
345
+ readableName,
346
+ value: value as string,
347
+ isEnabled,
348
+ ui5Type,
349
+ options,
350
+ ...docuSpread
351
+ };
352
+ }
353
+ case 'string':
354
+ return {
355
+ type: STRING_VALUE_TYPE,
356
+ editor: INPUT_EDITOR_TYPE,
357
+ propertyType,
358
+ name: property.name,
359
+ readableName,
360
+ value: value as string,
361
+ isEnabled,
362
+ isIcon,
363
+ ui5Type,
364
+ ...docuSpread
365
+ };
366
+ case 'int':
367
+ return {
368
+ type: INTEGER_VALUE_TYPE,
369
+ editor: INPUT_EDITOR_TYPE,
370
+ propertyType,
371
+ name: property.name,
372
+ readableName,
373
+ value: value as number,
374
+ isEnabled,
375
+ ui5Type,
376
+ ...docuSpread
377
+ };
378
+ case 'float':
379
+ return {
380
+ type: FLOAT_VALUE_TYPE,
381
+ editor: INPUT_EDITOR_TYPE,
382
+ propertyType,
383
+ name: property.name,
384
+ readableName,
385
+ value: value as number,
386
+ isEnabled,
387
+ ui5Type,
388
+ ...docuSpread
389
+ };
390
+ case 'boolean':
391
+ return {
392
+ type: BOOLEAN_VALUE_TYPE,
393
+ editor: CHECKBOX_EDITOR_TYPE,
394
+ propertyType,
395
+ name: property.name,
396
+ readableName,
397
+ value: value as boolean,
398
+ isEnabled,
399
+ ui5Type,
400
+ ...docuSpread
401
+ };
402
+ default:
403
+ return undefined;
404
+ }
405
+ }
406
+
203
407
  /**
204
408
  * Build control data.
205
409
  *
206
410
  * @param control - ui5 control
207
411
  * @param changeService - Changeservice for change stack event handling.
208
412
  * @param controlOverlay - element overlay
209
- * @param includeDocumentation - include documentation flag
210
- * @returns Promise<Control>
413
+ * @returns Control
211
414
  */
212
415
  export function buildControlData(
213
416
  control: ManagedObject,
@@ -218,182 +421,37 @@ export function buildControlData(
218
421
  const selectedControlName = controlMetadata.getName();
219
422
  const hasStableId = Utils.checkControlId(control);
220
423
  const overlayData = controlOverlay?.getDesignTimeMetadata().getData();
221
-
222
424
  const controlProperties = controlOverlay ? overlayData?.properties : undefined;
425
+
223
426
  const manifestProperties = getManifestProperties(control, changeService, controlOverlay);
224
- // Add the control's properties/manifest properties
225
427
  const allProperties = {
226
428
  ...(controlMetadata.getAllProperties() as unknown as {
227
429
  [name: string]: ManagedObjectMetadataProperties;
228
430
  }),
229
431
  ...manifestProperties
230
432
  };
231
- let propertyType;
232
- const propertyNames = Object.keys(allProperties);
433
+
233
434
  const properties: ControlProperty[] = [];
234
- for (const propertyName of propertyNames) {
435
+ for (const propertyName of Object.keys(allProperties)) {
235
436
  const property = allProperties[propertyName];
236
- let analyzedType;
237
- let isEnabled: boolean;
238
- let value: unknown;
239
- if (property && 'configuration' in property) {
240
- propertyType = PropertyType.Configuration;
241
- analyzedType = analyzeManifestProperty(property);
242
- if (
243
- !analyzedType ||
244
- (property?.restrictedTo?.length &&
245
- !property?.restrictedTo?.includes(getV4PageType(control) as TemplateType))
246
- ) {
247
- continue;
248
- }
249
- isEnabled = true;
250
- value = property.value;
251
- } else {
252
- propertyType = PropertyType.ControlProperty;
253
- // the default behavior is that the property is enabled
254
- // meaning it's not ignored during design time
255
- analyzedType = analyzePropertyType(property);
256
- if (!analyzedType) {
257
- continue;
258
- }
259
- let ignore = false;
260
- if (controlProperties?.[property.name]) {
261
- // check whether the property should be ignored in design time or not
262
- // if it's 'undefined' then it's not considered when building isEnabled because it's 'true'
263
- ignore = controlProperties[property.name].ignore;
264
- }
265
-
266
- //updating i18n text for the control if bindingInfo has bindingString
267
- const controlNewData: NewControlData = {
268
- id: control.getId(),
269
- name: property.name,
270
- newValue: control.getProperty(property.name)
271
- };
272
- const bindingInfo: { bindingString?: string } = control.getBindingInfo(controlNewData.name) as {
273
- bindingString?: string;
274
- };
275
- if (bindingInfo?.bindingString !== undefined) {
276
- controlNewData.newValue = bindingInfo.bindingString;
277
- }
278
-
279
- // A property is enabled if:
280
- // 1. The property supports changes
281
- // 2. The control has stable ID
282
- // 3. It is not configured to be ignored in design time
283
- // 4. And control overlay is selectable
284
- isEnabled = isControlEnabled(analyzedType, hasStableId, ignore, controlOverlay);
437
+ const processed =
438
+ property && 'configuration' in property
439
+ ? processConfigProperty(property, control)
440
+ : processControlProperty(property, control, hasStableId, controlProperties, controlOverlay);
285
441
 
286
- value = normalizeObjectPropertyValue(controlNewData.newValue);
442
+ if (!processed) {
443
+ continue;
287
444
  }
288
- const isIcon =
289
- testIconPattern(property.name) &&
290
- selectedControlName !== 'sap.m.Image' &&
291
- analyzedType.ui5Type === 'sap.ui.core.URI';
292
- const ui5Type = analyzedType.ui5Type || undefined;
293
- const readableName = convertCamelCaseToPascalCase(property.name);
294
- let docu:
295
- | {
296
- defaultValue: string;
297
- description: string;
298
- propertyName: string;
299
- type?: string;
300
- propertyType?: string;
301
- }
302
- | undefined;
303
- if ('configuration' in property) {
304
- const defValue = ['undefined', 'null'].includes(String(property.defaultValue))
305
- ? '-'
306
- : String(property.defaultValue);
307
- docu = {
308
- description: property.description,
309
- propertyName: property.id,
310
- type: property.type,
311
- defaultValue: defValue || '-'
312
- };
313
- }
314
- switch (analyzedType.primitiveType) {
315
- case 'enum': {
316
- const values = analyzedType.enumValues ?? {};
317
- const options: { key: string; text: string }[] = Object.keys(values).map((key) => ({
318
- key,
319
- text: values[key]
320
- }));
321
- properties.push({
322
- type: STRING_VALUE_TYPE,
323
- editor: DROPDOWN_EDITOR_TYPE,
324
- propertyType,
325
- name: property.name,
326
- readableName,
327
- value: value as string,
328
- isEnabled,
329
- ui5Type,
330
- options,
331
- ...(docu && { documentation: docu })
332
- });
333
- break;
334
- }
335
- case 'string': {
336
- properties.push({
337
- type: STRING_VALUE_TYPE,
338
- editor: INPUT_EDITOR_TYPE,
339
- propertyType,
340
- name: property.name,
341
- readableName,
342
- value: value as string,
343
- isEnabled,
344
- isIcon,
345
- ui5Type,
346
- ...(docu && { documentation: docu })
347
- });
348
- break;
349
- }
350
- case 'int': {
351
- properties.push({
352
- type: INTEGER_VALUE_TYPE,
353
- editor: INPUT_EDITOR_TYPE,
354
- propertyType,
355
- name: property.name,
356
- readableName,
357
- value: value as number,
358
- isEnabled,
359
- ui5Type,
360
- ...(docu && { documentation: docu })
361
- });
362
- break;
363
- }
364
- case 'float': {
365
- properties.push({
366
- type: FLOAT_VALUE_TYPE,
367
- editor: INPUT_EDITOR_TYPE,
368
- propertyType,
369
- name: property.name,
370
- readableName,
371
- value: value as number,
372
- isEnabled,
373
- ui5Type,
374
- ...(docu && { documentation: docu })
375
- });
376
- break;
377
- }
378
- case 'boolean': {
379
- properties.push({
380
- type: BOOLEAN_VALUE_TYPE,
381
- editor: CHECKBOX_EDITOR_TYPE,
382
- propertyType,
383
- name: property.name,
384
- readableName,
385
- value: value as boolean,
386
- isEnabled,
387
- ui5Type,
388
- ...(docu && { documentation: docu })
389
- });
390
- break;
391
- }
445
+
446
+ const entry = buildPropertyEntry(property, processed, selectedControlName);
447
+ if (entry) {
448
+ properties.push(entry);
392
449
  }
393
450
  }
451
+
394
452
  return {
395
- id: control.getId(), //the id of the underlying control/aggregation
396
- type: selectedControlName, //the name of the ui5 class of the control/aggregation
453
+ id: control.getId(),
454
+ type: selectedControlName,
397
455
  properties: [...properties].sort((a, b) => (a.name > b.name ? 1 : -1)),
398
456
  name: selectedControlName
399
457
  };
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "bugs": {
10
10
  "url": "https://github.com/SAP/open-ux-tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Apreview-middleware"
11
11
  },
12
- "version": "0.25.34",
12
+ "version": "0.25.37",
13
13
  "license": "Apache-2.0",
14
14
  "author": "@SAP/ux-tools-team",
15
15
  "main": "dist/index.js",
@@ -27,14 +27,14 @@
27
27
  "mem-fs-editor": "9.4.0",
28
28
  "qrcode": "1.5.4",
29
29
  "@sap/bas-sdk": "3.13.6",
30
- "@sap-ux/adp-tooling": "0.18.125",
31
- "@sap-ux/btp-utils": "1.1.14",
32
- "@sap-ux/control-property-editor-sources": "npm:@sap-ux/control-property-editor@0.7.23",
30
+ "@sap-ux/adp-tooling": "0.18.128",
33
31
  "@sap-ux/feature-toggle": "0.3.8",
32
+ "@sap-ux/btp-utils": "1.1.14",
34
33
  "@sap-ux/logger": "0.8.5",
35
- "@sap-ux/project-access": "1.36.1",
36
- "@sap-ux/system-access": "0.7.9",
37
- "@sap-ux/i18n": "0.3.10"
34
+ "@sap-ux/control-property-editor-sources": "npm:@sap-ux/control-property-editor@0.7.24",
35
+ "@sap-ux/project-access": "1.36.2",
36
+ "@sap-ux/system-access": "0.7.10",
37
+ "@sap-ux/i18n": "0.3.11"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@sap-ux-private/playwright": "0.2.15",
@@ -53,10 +53,10 @@
53
53
  "nock": "14.0.11",
54
54
  "npm-run-all2": "8.0.4",
55
55
  "supertest": "7.2.2",
56
- "@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@0.25.34",
57
- "@sap-ux/axios-extension": "1.25.33",
58
- "@sap-ux/store": "1.5.13",
59
- "@sap-ux/ui5-info": "0.13.20"
56
+ "@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@0.25.37",
57
+ "@sap-ux/axios-extension": "1.25.34",
58
+ "@sap-ux/ui5-info": "0.13.20",
59
+ "@sap-ux/store": "1.5.13"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "express": "4"