@sapui5/sap.fe.base 1.125.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.
@@ -0,0 +1,714 @@
1
+ import ObjectPath from "sap/base/util/ObjectPath";
2
+ import merge from "sap/base/util/merge";
3
+ import uid from "sap/base/util/uid";
4
+ import type { Ref } from "sap/fe/base/jsx-runtime/jsx";
5
+ import type UI5Event from "sap/ui/base/Event";
6
+ import type ManagedObject from "sap/ui/base/ManagedObject";
7
+ import Metadata from "sap/ui/base/Metadata";
8
+ import BaseObject from "sap/ui/base/Object";
9
+ import type Control from "sap/ui/core/Control";
10
+ import type { $ControlSettings } from "sap/ui/core/Control";
11
+ import type UI5Element from "sap/ui/core/Element";
12
+ import type ControllerExtension from "sap/ui/core/mvc/ControllerExtension";
13
+ import ControllerMetadata from "sap/ui/core/mvc/ControllerMetadata";
14
+ import type OverrideExecution from "sap/ui/core/mvc/OverrideExecution";
15
+
16
+ type OverrideDefinition = Record<string, Function>;
17
+ type UI5ControllerMethodDefinition = {
18
+ overrideExecution?: OverrideExecution;
19
+ public?: boolean;
20
+ final?: boolean;
21
+ };
22
+ type UI5PropertyMetadata = {
23
+ type: string;
24
+ bindable?: boolean;
25
+ required?: boolean;
26
+ group?: string;
27
+ defaultValue?: unknown;
28
+ byValue?: boolean;
29
+ isBindingInfo?: boolean;
30
+ expectedAnnotations?: string[];
31
+ expectedTypes?: string[];
32
+ allowedValues?: string[];
33
+ };
34
+ export type UI5AggregationMetadata = {
35
+ type: string;
36
+ altTypes?: string[];
37
+ multiple?: boolean;
38
+ isDefault?: boolean;
39
+ defaultClass?: unknown;
40
+ singularName?: string;
41
+ visibility?: string;
42
+ };
43
+ export type UI5AssociationMetadata = {
44
+ type: string;
45
+ multiple?: boolean;
46
+ singularName?: string;
47
+ };
48
+ export type UI5ControlMetadataDefinition = {
49
+ defaultAggregation?: string;
50
+ controllerExtensions: Record<string, typeof ControllerExtension | Function>;
51
+ properties: Record<string, UI5PropertyMetadata>;
52
+ aggregations: Record<string, UI5AggregationMetadata>;
53
+ references: Record<string, boolean>;
54
+ associations: Record<string, UI5AssociationMetadata>;
55
+ methods: Record<string, UI5ControllerMethodDefinition>;
56
+ events: Record<string, {}>;
57
+ interfaces: string[];
58
+ library?: string;
59
+ designtime?: string;
60
+ config?: {
61
+ fullWidth?: boolean;
62
+ };
63
+ manifest?: string | unknown;
64
+ returnTypes?: string[];
65
+ buildingBlockMetadata?: unknown;
66
+ };
67
+ type UI5ControllerDefinition = {
68
+ override?: { extension?: Record<string, OverrideDefinition> } & {
69
+ [k: string]: Function;
70
+ };
71
+ overrides?: { extension?: Record<string, OverrideDefinition> } & {
72
+ [k: string]: Function;
73
+ };
74
+ metadata?: UI5ControlMetadataDefinition;
75
+ renderer?: Function;
76
+ };
77
+
78
+ type UI5ControlDefinition = {
79
+ metadata?: UI5ControlMetadataDefinition;
80
+ override?: unknown;
81
+ renderer?: Function;
82
+ };
83
+
84
+ type UI5APIControl = Control & {
85
+ getAPI(event: UI5Event, fqdn?: string): UI5APIControl | undefined;
86
+ instanceMap: WeakMap<object, object[]>;
87
+ __apiRegistered: boolean;
88
+ } & {
89
+ [k: string]: Function;
90
+ };
91
+
92
+ export type XMLEventHolder = {
93
+ getAPI(event: UI5Event, fqdn?: string): UI5APIControl | undefined;
94
+ instanceMap: WeakMap<object, object[]>;
95
+ };
96
+
97
+ type ControlPropertyNames<T> = {
98
+ // needed because of the TS compiler
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ [K in keyof T]: T[K] extends Function ? never : K;
101
+ }[keyof T];
102
+ export type PropertiesOf<T, K extends keyof T | undefined = undefined> = Partial<Pick<T, ControlPropertyNames<T>>> &
103
+ (K extends keyof T ? Partial<Pick<T, K>> : {}) &
104
+ $ControlSettings;
105
+ export type ConvertContextToString<T> = {
106
+ [K in keyof T]: K extends "metaPath" | "contextPath" ? string : T[K];
107
+ };
108
+ export type StrictPropertiesOf<T, K extends keyof T | undefined = undefined> = Pick<T, ControlPropertyNames<T>> &
109
+ (K extends keyof T ? Pick<T, K> : {}) &
110
+ $ControlSettings;
111
+ export type UI5Accessors<T> = {
112
+ // Add all the getXXX method, might add too much as I'm not filtering on actual properties...
113
+ [P in keyof Required<T> as `get${Capitalize<string & P>}`]: () => T[P];
114
+ };
115
+ export type UI5Setters<T> = {
116
+ // Add all the setXXX method, might add too much as I'm not filtering on actual properties...
117
+ [P in keyof Required<T> as `set${Capitalize<string & P>}`]: (value: T[P]) => T;
118
+ };
119
+ export type EnhanceWithUI5<T> = {
120
+ new (props: PropertiesOf<T>): EnhanceWithUI5<T>;
121
+ new (sId: string, props: PropertiesOf<T>): EnhanceWithUI5<T>;
122
+ } & T &
123
+ UI5Accessors<T> &
124
+ UI5Setters<T>;
125
+
126
+ const ensureMetadata = function (target: UI5ControllerDefinition | UI5ControlDefinition): UI5ControlMetadataDefinition {
127
+ target.metadata = merge(
128
+ {
129
+ controllerExtensions: {},
130
+ properties: {},
131
+ aggregations: {},
132
+ references: {},
133
+ associations: {},
134
+ methods: {},
135
+ events: {},
136
+ interfaces: []
137
+ },
138
+ target.metadata ?? {}
139
+ ) as UI5ControlMetadataDefinition;
140
+ return target.metadata;
141
+ };
142
+
143
+ /**
144
+ * Method that will find the correct API object based on the current control hierarchy.
145
+ *
146
+ * @param target
147
+ * @returns The proper getAPI function for this constructor
148
+ */
149
+ function prepareGetAPIFunction(target: UI5APIControl) {
150
+ return function <T extends UI5APIControl>(
151
+ this: UI5APIControl,
152
+ oEvent: UI5Event,
153
+ apiFQN: string = target.getMetadata().getName()
154
+ ): T | undefined {
155
+ let oSource = oEvent.getSource() as ManagedObject | null;
156
+ if (target._getAPIExtension) {
157
+ oSource = target._getAPIExtension(oSource) ?? oSource;
158
+ }
159
+ while (oSource && !oSource.isA<T>(apiFQN) && oSource.getParent) {
160
+ if (this.isDependentBound) {
161
+ const oDependents = (oSource as Control).getDependents();
162
+ const hasCorrectDependent = oDependents.find((oDependent: UI5Element) => oDependent.isA(apiFQN));
163
+ if (hasCorrectDependent) {
164
+ oSource = hasCorrectDependent as unknown as T;
165
+ } else {
166
+ oSource = oSource.getParent() as unknown as T;
167
+ }
168
+ } else {
169
+ oSource = oSource.getParent();
170
+ }
171
+ }
172
+
173
+ if (!oSource || !oSource.isA<T>(apiFQN)) {
174
+ const oSourceMap = this.instanceMap.get(this) as T[];
175
+ oSource = oSourceMap?.[oSourceMap.length - 1];
176
+ }
177
+ let targetControl;
178
+ if (oSource && oSource.isA<T>(apiFQN)) {
179
+ targetControl = oSource;
180
+ }
181
+ return targetControl;
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Ensures that when an xmlEventHandler is defined, the `getAPI` function is properly assigned.
187
+ *
188
+ * @param target The class constructor that should hold the static functions
189
+ */
190
+ const ensureGetAPI = function (target: UI5APIControl): void {
191
+ if (!target.__apiRegistered) {
192
+ target.__apiRegistered = true;
193
+ target.instanceMap = new WeakMap<object, object[]>();
194
+ target.registerInstance = function (_instance: Function): void {
195
+ if (!this.instanceMap.get(_instance.constructor)) {
196
+ this.instanceMap.set(_instance.constructor, []);
197
+ }
198
+ (this.instanceMap.get(_instance.constructor) as object[]).push(_instance);
199
+ };
200
+ target.getAPI = prepareGetAPIFunction(target);
201
+ }
202
+ };
203
+
204
+ /* #region CONTROLLER EXTENSIONS */
205
+
206
+ /**
207
+ * Defines that the following method is an override for the method name with the same name in the specific controller extension or base implementation.
208
+ *
209
+ * @param extensionName The name of the extension that will be overridden
210
+ * @returns The decorated method
211
+ */
212
+ export function methodOverride(extensionName?: string): MethodDecorator {
213
+ return function (target: UI5ControllerDefinition, propertyKey) {
214
+ if (!target.override) {
215
+ target.override = {};
216
+ }
217
+ if (!target.overrides) {
218
+ target.overrides = {};
219
+ }
220
+ let currentTarget = target.overrides;
221
+ if (extensionName) {
222
+ if (!currentTarget.extension) {
223
+ currentTarget.extension = {};
224
+ }
225
+ if (!currentTarget.extension[extensionName]) {
226
+ currentTarget.extension[extensionName] = {};
227
+ }
228
+ currentTarget = currentTarget.extension[extensionName];
229
+ }
230
+ currentTarget[propertyKey.toString()] = (target as Record<string, Function>)[propertyKey.toString()];
231
+ };
232
+ }
233
+
234
+ /**
235
+ * Defines that the method can be extended by other controller extension based on the defined overrideExecutionType.
236
+ *
237
+ * @param overrideExecutionType The OverrideExecution defining when the override should run (Before / After / Instead)
238
+ * @returns The decorated method
239
+ */
240
+ export function extensible(overrideExecutionType?: OverrideExecution | "AfterAsync" | "BeforeAsync"): MethodDecorator {
241
+ return function (target: UI5ControllerDefinition, propertyKey) {
242
+ const metadata = ensureMetadata(target);
243
+ if (!metadata.methods[propertyKey.toString()]) {
244
+ metadata.methods[propertyKey.toString()] = {};
245
+ }
246
+ metadata.methods[propertyKey.toString()].overrideExecution = overrideExecutionType as unknown as OverrideExecution;
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Defines that the method will be publicly available for controller extension usage.
252
+ *
253
+ * @returns The decorated method
254
+ */
255
+ export function publicExtension(): MethodDecorator {
256
+ return function (target: UI5ControllerDefinition, propertyKey, descriptor): void {
257
+ const metadata = ensureMetadata(target);
258
+ descriptor.enumerable = true;
259
+ if (!metadata.methods[propertyKey.toString()]) {
260
+ metadata.methods[propertyKey.toString()] = {};
261
+ }
262
+ metadata.methods[propertyKey.toString()].public = true;
263
+ };
264
+ }
265
+ /**
266
+ * Defines that the method will be only available for internal usage of the controller extension.
267
+ *
268
+ * @returns The decorated method
269
+ */
270
+ export function privateExtension(): MethodDecorator {
271
+ return function (target: UI5ControllerDefinition, propertyKey, descriptor) {
272
+ const metadata = ensureMetadata(target);
273
+ descriptor.enumerable = true;
274
+ if (!metadata.methods[propertyKey.toString()]) {
275
+ metadata.methods[propertyKey.toString()] = {};
276
+ }
277
+ metadata.methods[propertyKey.toString()].public = false;
278
+ };
279
+ }
280
+ /**
281
+ * Defines that the method cannot be further extended by other controller extension.
282
+ *
283
+ * @returns The decorated method
284
+ */
285
+ export function finalExtension(): MethodDecorator {
286
+ return function (target: UI5ControllerDefinition, propertyKey, descriptor) {
287
+ const metadata = ensureMetadata(target);
288
+ descriptor.enumerable = true;
289
+ if (!metadata.methods[propertyKey.toString()]) {
290
+ metadata.methods[propertyKey.toString()] = {};
291
+ }
292
+ metadata.methods[propertyKey.toString()].final = true;
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Defines that we are going to use instantiate a controller extension under the following variable name.
298
+ *
299
+ * @param extensionClass The controller extension that will be instantiated
300
+ * @returns The decorated property
301
+ */
302
+ export function usingExtension(extensionClass: typeof ControllerExtension | Function): PropertyDecorator {
303
+ return function (target: UI5ControllerDefinition, propertyKey: string, propertyDescriptor: TypedPropertyDescriptor<unknown>) {
304
+ const metadata = ensureMetadata(target);
305
+ delete (propertyDescriptor as { initializer?: unknown }).initializer;
306
+ metadata.controllerExtensions[propertyKey.toString()] = extensionClass;
307
+ return propertyDescriptor;
308
+ } as unknown as PropertyDecorator; // This is technically an accessor decorator, but somehow the compiler doesn't like it if I declare it as such.
309
+ }
310
+
311
+ /* #endregion */
312
+
313
+ /* #region CONTROL */
314
+ /**
315
+ * Indicates that the property shall be declared as an event on the control metadata.
316
+ *
317
+ * @returns The decorated property
318
+ */
319
+ export function event(): PropertyDecorator {
320
+ return function (target: UI5ControlDefinition, eventKey) {
321
+ const metadata = ensureMetadata(target);
322
+ if (!metadata.events[eventKey.toString()]) {
323
+ metadata.events[eventKey.toString()] = {};
324
+ }
325
+ };
326
+ }
327
+
328
+ /**
329
+ * Defines the following property in the control metatada.
330
+ *
331
+ * @param attributeDefinition The property definition
332
+ * @returns The decorated property.
333
+ */
334
+ export function property(attributeDefinition: UI5PropertyMetadata): PropertyDecorator {
335
+ return function (target: UI5ControlDefinition, propertyKey: string, propertyDescriptor: TypedPropertyDescriptor<unknown>) {
336
+ const metadata = ensureMetadata(target);
337
+ if (attributeDefinition.bindable === undefined) {
338
+ attributeDefinition.bindable = true;
339
+ }
340
+ if (attributeDefinition.isBindingInfo === true) {
341
+ attributeDefinition.bindable = false;
342
+ attributeDefinition.group = "Data";
343
+ }
344
+ if (!metadata.properties[propertyKey]) {
345
+ metadata.properties[propertyKey] = attributeDefinition;
346
+ }
347
+ delete propertyDescriptor.writable;
348
+ attributeDefinition.defaultValue ??= (propertyDescriptor as { initializer?: Function }).initializer?.();
349
+ delete (propertyDescriptor as { initializer?: unknown }).initializer;
350
+
351
+ return propertyDescriptor;
352
+ } as unknown as PropertyDecorator; // This is technically an accessor decorator, but somehow the compiler doesn't like it if i declare it as such.;
353
+ }
354
+
355
+ /**
356
+ * Defines and configure the following aggregation in the control metatada.
357
+ *
358
+ * @param aggregationDefinition The aggregation definition
359
+ * @returns The decorated property.
360
+ */
361
+ export function aggregation(aggregationDefinition: UI5AggregationMetadata): PropertyDecorator {
362
+ return function (target: UI5ControlDefinition, propertyKey: string, propertyDescriptor: TypedPropertyDescriptor<unknown>) {
363
+ const metadata = ensureMetadata(target);
364
+ if (aggregationDefinition.multiple === undefined) {
365
+ // UI5 defaults this to true but this is just weird...
366
+ aggregationDefinition.multiple = false;
367
+ }
368
+ if (!metadata.aggregations[propertyKey]) {
369
+ metadata.aggregations[propertyKey] = aggregationDefinition;
370
+ }
371
+ if (aggregationDefinition.isDefault) {
372
+ metadata.defaultAggregation = propertyKey;
373
+ }
374
+ delete propertyDescriptor.writable;
375
+ delete (propertyDescriptor as { initializer?: unknown }).initializer;
376
+
377
+ return propertyDescriptor;
378
+ } as unknown as PropertyDecorator; // This is technically an accessor decorator, but somehow the compiler doesn't like it if i declare it as such.;
379
+ }
380
+
381
+ /**
382
+ * Defines and configure the following association in the control metatada.
383
+ *
384
+ * @param ui5AssociationMetadata The definition of the association.
385
+ * @returns The decorated property
386
+ */
387
+ export function association(ui5AssociationMetadata: UI5AssociationMetadata): PropertyDecorator {
388
+ return function (target: UI5ControlDefinition, propertyKey: string, propertyDescriptor: TypedPropertyDescriptor<unknown>) {
389
+ const metadata = ensureMetadata(target);
390
+ if (!metadata.associations[propertyKey]) {
391
+ metadata.associations[propertyKey] = ui5AssociationMetadata;
392
+ }
393
+ delete propertyDescriptor.writable;
394
+ delete (propertyDescriptor as { initializer?: unknown }).initializer;
395
+
396
+ return propertyDescriptor;
397
+ } as unknown as PropertyDecorator; // This is technically an accessor decorator, but somehow the compiler doesn't like it if i declare it as such.;
398
+ }
399
+
400
+ /**
401
+ * Defines in the metadata that this control implements a specific interface.
402
+ *
403
+ * @param interfaceName The name of the implemented interface
404
+ * @returns The decorated method
405
+ */
406
+ export function implementInterface(interfaceName: string): PropertyDecorator {
407
+ return function (target: UI5ControlDefinition) {
408
+ const metadata = ensureMetadata(target);
409
+
410
+ metadata.interfaces.push(interfaceName);
411
+ };
412
+ }
413
+
414
+ /**
415
+ * Indicates that the following method should also be exposed statically so we can call it from XML.
416
+ *
417
+ * @returns The decorated method
418
+ */
419
+ export function xmlEventHandler(): MethodDecorator {
420
+ return function (target: UI5ControlDefinition, propertykey) {
421
+ const currentConstructor: UI5APIControl = target.constructor as unknown as UI5APIControl;
422
+ ensureGetAPI(currentConstructor);
423
+ currentConstructor[propertykey.toString()] = function (...args: unknown[]): void {
424
+ if (args && args.length) {
425
+ const currentTarget = currentConstructor.getAPI(args[0] as UI5Event);
426
+ currentTarget?.[propertykey.toString()](...args);
427
+ }
428
+ };
429
+ };
430
+ }
431
+
432
+ type UI5ControlAndMore = typeof Control &
433
+ Function & {
434
+ registerInstance?: Function;
435
+ override?: Function;
436
+ renderer?: unknown;
437
+ render: Function;
438
+ metadata?: UI5ControlMetadataDefinition;
439
+ };
440
+
441
+ /**
442
+ * Indicates that the following class should define a UI5 control of the specified name.
443
+ *
444
+ * @param sTarget The fully qualified name of the UI5 class
445
+ * @param metadataDefinition Inline metadata definition
446
+ * @param forceDefineCall Whether a define call should be added for this, useful when defining sub classes
447
+ * @returns A class decorator that will create a ui5 class
448
+ */
449
+ export function defineUI5Class(
450
+ sTarget: string,
451
+ metadataDefinition?: Partial<UI5ControlMetadataDefinition>,
452
+ forceDefineCall = false
453
+ ): ClassDecorator {
454
+ return function (constructor: UI5ControlAndMore) {
455
+ if (!constructor.prototype.metadata) {
456
+ constructor.prototype.metadata = {};
457
+ }
458
+ if (metadataDefinition) {
459
+ for (const key in metadataDefinition) {
460
+ if (constructor.prototype.metadata[key]) {
461
+ if (Array.isArray(constructor.prototype.metadata[key])) {
462
+ constructor.prototype.metadata[key] = constructor.prototype.metadata[key].concat(
463
+ metadataDefinition[key as keyof UI5ControlMetadataDefinition]
464
+ );
465
+ } else {
466
+ constructor.prototype.metadata[key] = {
467
+ ...constructor.prototype.metadata[key],
468
+ ...(metadataDefinition[key as keyof UI5ControlMetadataDefinition] as object)
469
+ };
470
+ }
471
+ } else {
472
+ constructor.prototype.metadata[key] = metadataDefinition[key as keyof UI5ControlMetadataDefinition];
473
+ }
474
+ }
475
+ }
476
+ return registerUI5Metadata(constructor, sTarget, constructor.prototype, forceDefineCall);
477
+ } as ClassDecorator;
478
+ }
479
+
480
+ export function createReference<T extends UI5Element>(): Ref<T> {
481
+ return {
482
+ current: undefined as unknown as T,
483
+ setCurrent: function (oControlInstance: T): void {
484
+ this.current = oControlInstance;
485
+ }
486
+ };
487
+ }
488
+ /**
489
+ * Defines that the following object will hold a reference to a control through jsx templating.
490
+ *
491
+ * @returns The decorated property.
492
+ */
493
+ export function defineReference(): PropertyDecorator {
494
+ return function (target: UI5ControlDefinition, propertyKey: string, propertyDescriptor: TypedPropertyDescriptor<unknown>) {
495
+ const metadata = ensureMetadata(target);
496
+ delete propertyDescriptor.writable;
497
+ delete (propertyDescriptor as { initializer?: unknown }).initializer;
498
+ if (BaseObject.isObjectA(target, "sap.ui.core.Control")) {
499
+ if (!metadata.references[propertyKey]) {
500
+ metadata.references[propertyKey] = true;
501
+ }
502
+ } else {
503
+ (propertyDescriptor as { initializer?: unknown }).initializer = createReference;
504
+ }
505
+
506
+ return propertyDescriptor;
507
+ } as unknown as PropertyDecorator; // This is technically an accessor decorator, but somehow the compiler doesn't like it if i declare it as such.;
508
+ }
509
+
510
+ /**
511
+ * Internal heavy lifting that will take care of creating the class property for ui5 to use.
512
+ *
513
+ * @param clazz The class prototype
514
+ * @param name The name of the class to create
515
+ * @param inObj The metadata object
516
+ * @param forceDefineCall Whether a define call should be added for this, useful when defining sub classes
517
+ * @returns The metadata class
518
+ */
519
+ function registerUI5Metadata(
520
+ clazz: UI5ControlAndMore,
521
+ name: string,
522
+ inObj: UI5ControllerDefinition | UI5ControlDefinition,
523
+ forceDefineCall: boolean
524
+ ): Function {
525
+ if (clazz.getMetadata?.().isA("sap.ui.core.mvc.ControllerExtension")) {
526
+ Object.getOwnPropertyNames(inObj).forEach((objName) => {
527
+ const descriptor = Object.getOwnPropertyDescriptor(inObj, objName);
528
+ if (descriptor && !descriptor.enumerable) {
529
+ descriptor.enumerable = true;
530
+ }
531
+ });
532
+ }
533
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
534
+ const obj: any = {};
535
+ obj.metadata = inObj.metadata || {};
536
+ obj.override = inObj.override;
537
+ obj.constructor = clazz;
538
+ obj.metadata.baseType = Object.getPrototypeOf(clazz.prototype).getMetadata().getName();
539
+
540
+ if ((clazz?.getMetadata() as unknown as { getStereotype: Function })?.getStereotype() === "control") {
541
+ const rendererDefinition = inObj.renderer || clazz.renderer || clazz.render;
542
+ obj.renderer = { apiVersion: 2 };
543
+ if (typeof rendererDefinition === "function") {
544
+ obj.renderer.render = rendererDefinition;
545
+ } else if (rendererDefinition != undefined) {
546
+ obj.renderer = rendererDefinition;
547
+ }
548
+ }
549
+ obj.metadata.interfaces = inObj.metadata?.interfaces || clazz.metadata?.interfaces;
550
+ Object.keys(clazz.prototype).forEach((key) => {
551
+ if (key !== "metadata") {
552
+ try {
553
+ obj[key] = clazz.prototype[key];
554
+ } catch (e) {
555
+ // Do nothing
556
+ }
557
+ }
558
+ });
559
+
560
+ if (obj.metadata?.controllerExtensions && Object.keys(obj.metadata.controllerExtensions).length > 0) {
561
+ for (const cExtName in obj.metadata.controllerExtensions) {
562
+ obj[cExtName] = obj.metadata.controllerExtensions[cExtName];
563
+ }
564
+ }
565
+ const output = clazz.extend(name, obj) as typeof Control & Function;
566
+ const fnInit = output.prototype.init;
567
+ output.prototype.init = function (...args: unknown[]): void {
568
+ if (fnInit) {
569
+ fnInit.apply(this, args);
570
+ }
571
+ if (clazz.registerInstance) {
572
+ clazz.registerInstance(this);
573
+ }
574
+ this.metadata = obj.metadata;
575
+
576
+ if (obj.metadata.properties) {
577
+ const aPropertyKeys = Object.keys(obj.metadata.properties);
578
+ aPropertyKeys.forEach((propertyKey) => {
579
+ const propertyDef = obj.metadata.properties[propertyKey];
580
+ if (propertyDef.isBindingInfo === true) {
581
+ this._bindingProperties ??= {};
582
+ Object.defineProperty(this, propertyKey, {
583
+ configurable: true,
584
+ set: (v: unknown) => {
585
+ this._bindingProperties[propertyKey] = v;
586
+ this.mProperties[propertyKey] = v;
587
+ return this;
588
+ },
589
+ get(): unknown {
590
+ const propValue = this._bindingProperties[propertyKey] ?? this.getProperty(propertyKey);
591
+ if (typeof propValue === "object") {
592
+ return { ...propValue }; // Create shallow copy
593
+ } else {
594
+ return propValue;
595
+ }
596
+ }
597
+ });
598
+ } else {
599
+ Object.defineProperty(this, propertyKey, {
600
+ configurable: true,
601
+ set: (v: unknown) => {
602
+ return this.setProperty(propertyKey, v);
603
+ },
604
+ get: () => {
605
+ return this.getProperty(propertyKey);
606
+ }
607
+ });
608
+ }
609
+ if (propertyDef.defaultValue !== undefined) {
610
+ this._applyingSettings = true;
611
+ this[propertyKey] = propertyDef.defaultValue;
612
+ this._applyingSettings = false;
613
+ delete this._applyingSettings;
614
+ }
615
+ });
616
+ const aAggregationKeys = Object.keys(obj.metadata.aggregations);
617
+ aAggregationKeys.forEach((aggregationKey) => {
618
+ Object.defineProperty(this, aggregationKey, {
619
+ configurable: true,
620
+ set: (v: unknown) => {
621
+ return this.setAggregation(aggregationKey, v);
622
+ },
623
+ get: () => {
624
+ const aggregationContent = this.getAggregation(aggregationKey);
625
+ if (obj.metadata.aggregations[aggregationKey].multiple) {
626
+ return aggregationContent || [];
627
+ } else {
628
+ return aggregationContent;
629
+ }
630
+ }
631
+ });
632
+ });
633
+ const aAssociationKeys = Object.keys(obj.metadata.associations);
634
+ aAssociationKeys.forEach((associationKey) => {
635
+ Object.defineProperty(this, associationKey, {
636
+ configurable: true,
637
+ set: (v: unknown) => {
638
+ return this.setAssociation(associationKey, v);
639
+ },
640
+ get: () => {
641
+ const aggregationContent = this.getAssociation(associationKey);
642
+ if (obj.metadata.associations[associationKey].multiple) {
643
+ return aggregationContent || [];
644
+ } else {
645
+ return aggregationContent;
646
+ }
647
+ }
648
+ });
649
+ });
650
+ }
651
+ if (obj.metadata.references) {
652
+ const referenceProperties = Object.keys(obj.metadata.references);
653
+ referenceProperties.forEach((propertyKey) => {
654
+ Object.defineProperty(this, propertyKey, {
655
+ configurable: true,
656
+ value: createReference()
657
+ });
658
+ });
659
+ }
660
+ if (obj.metadata.methods) {
661
+ for (const methodName in obj.metadata.methods) {
662
+ const methodDefinition = obj.metadata.methods[methodName];
663
+ if (methodDefinition.overrideExecution === "AfterAsync" || methodDefinition.overrideExecution === "BeforeAsync") {
664
+ if (!this.methodHolder) {
665
+ this.methodHolder = [];
666
+ }
667
+ this.methodHolder[methodName] = [this[methodName]];
668
+ Object.defineProperty(this, methodName, {
669
+ configurable: true,
670
+ set: (v: Function) => {
671
+ return this.methodHolder[methodName].push(v);
672
+ },
673
+ get: () => {
674
+ return async (...methodArgs: unknown[]) => {
675
+ const methodArrays = this.methodHolder[methodName];
676
+ if (methodDefinition.overrideExecution === "BeforeAsync") {
677
+ methodArrays.reverse();
678
+ }
679
+ for (const arg of methodArrays) {
680
+ await arg.apply(this, methodArgs);
681
+ }
682
+ };
683
+ }
684
+ });
685
+ }
686
+ }
687
+ }
688
+ };
689
+ clazz.override = function (oExtension: unknown): Function {
690
+ const pol = {};
691
+ (pol as Function).constructor = function (...args: unknown[]): Function {
692
+ return clazz.apply(this, args);
693
+ };
694
+ const oClass = (Metadata as unknown as { createClass: Function }).createClass(
695
+ clazz,
696
+ `anonymousExtension~${uid()}`,
697
+ pol,
698
+ ControllerMetadata
699
+ );
700
+ oClass.getMetadata()._staticOverride = oExtension;
701
+ oClass.getMetadata()._override = (clazz.getMetadata() as { _override?: unknown })._override;
702
+ return oClass;
703
+ };
704
+
705
+ ObjectPath.set(name, output);
706
+
707
+ if (forceDefineCall) {
708
+ sap.ui.define(output.getMetadata().getName().replaceAll(".", "/"), () => {
709
+ return output;
710
+ });
711
+ }
712
+
713
+ return output;
714
+ }