@pega/sdk 0.0.13 → 0.0.14

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.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/templates/angular-sdk-reference/angular.json +2 -1
  3. package/templates/angular-sdk-reference/package.json +1 -0
  4. package/templates/angular-sdk-reference/pega-constellationjs-0.26.3-local.tgz +0 -0
  5. package/templates/angular-sdk-reference/src/app/app.component.ts +232 -15
  6. package/templates/angular-sdk-reference/src/components/component-map.ts +90 -0
  7. package/templates/angular-sdk-reference/src/components/fields/auto-complete/auto-complete.component.ts +267 -0
  8. package/templates/angular-sdk-reference/src/components/fields/checkbox/checkbox.component.ts +84 -0
  9. package/templates/angular-sdk-reference/src/components/fields/currency/currency.component.ts +97 -0
  10. package/templates/angular-sdk-reference/src/components/fields/date-input/date-input.component.ts +88 -0
  11. package/templates/angular-sdk-reference/src/components/fields/dropdown/dropdown.component.ts +101 -0
  12. package/templates/angular-sdk-reference/src/components/fields/email/email.component.ts +91 -0
  13. package/templates/angular-sdk-reference/src/components/fields/integer/integer.component.ts +93 -0
  14. package/templates/angular-sdk-reference/src/components/fields/phone/phone.component.ts +91 -0
  15. package/templates/angular-sdk-reference/src/components/fields/radio-buttons/radio-buttons.component.ts +102 -0
  16. package/templates/angular-sdk-reference/src/components/fields/text-area/text-area.component.ts +98 -0
  17. package/templates/angular-sdk-reference/src/components/fields/text-input/text-input.component.ts +97 -0
  18. package/templates/angular-sdk-reference/src/components/fields/url-input/url-input.component.ts +94 -0
  19. package/templates/angular-sdk-reference/src/components/infra/assignment/assignment.component.ts +206 -0
  20. package/templates/angular-sdk-reference/src/components/infra/case-summary/case-summary.component.ts +76 -0
  21. package/templates/angular-sdk-reference/src/components/infra/case-view/case-view.component.ts +114 -0
  22. package/templates/angular-sdk-reference/src/components/infra/flow-container/flow-container.component.ts +411 -0
  23. package/templates/angular-sdk-reference/src/components/infra/reference/reference.component.ts +158 -0
  24. package/templates/angular-sdk-reference/src/components/infra/region/region.component.ts +39 -0
  25. package/templates/angular-sdk-reference/src/components/infra/root-container/root-container.component.ts +193 -0
  26. package/templates/angular-sdk-reference/src/components/infra/stages/stages.component.ts +78 -0
  27. package/templates/angular-sdk-reference/src/components/infra/view/view.component.ts +139 -0
  28. package/templates/angular-sdk-reference/src/components/infra/view-container/view-container.component.ts +191 -0
  29. package/templates/angular-sdk-reference/src/components/templates/default-form/default-form.component.ts +101 -0
  30. package/templates/angular-sdk-reference/src/components/templates/narrow-wide/narrow-wide.component.ts +37 -0
  31. package/templates/angular-sdk-reference/src/components/templates/one-column/one-column.component.ts +37 -0
  32. package/templates/angular-sdk-reference/src/components/templates/two-column/two-column.component.ts +37 -0
  33. package/templates/angular-sdk-reference/src/components/templates/wide-narrow/wide-narrow.component.ts +37 -0
  34. package/templates/angular-sdk-reference/src/helpers/event-utils.ts +18 -0
  35. package/templates/angular-sdk-reference/src/helpers/utils.ts +38 -0
  36. package/templates/angular-sdk-reference/src/helpers/versionHelpers.ts +14 -0
  37. package/templates/angular-sdk-reference/tsconfig.json +5 -2
  38. package/templates/react-sdk-reference/.ai-common/agents/embedded-flow-expert.md +11 -4
  39. package/templates/react-sdk-reference/.ai-common/prompts/component-manifest.json +7 -0
  40. package/templates/react-sdk-reference/.ai-common/prompts/generate-all-components.md +3 -1
  41. package/templates/react-sdk-reference/.ai-common/skills/field-components.md +75 -29
  42. package/templates/react-sdk-reference/.ai-common/skills/field-skills/AutoComplete.md +133 -101
  43. package/templates/react-sdk-reference/.ai-common/skills/field-skills/EmbeddedData.md +385 -0
  44. package/templates/react-sdk-reference/.ai-common/skills/field-skills/EmbeddedDataMulti.md +506 -0
  45. package/templates/react-sdk-reference/.ai-common/skills/field-skills/ObjectReference.md +549 -86
  46. package/templates/react-sdk-reference/.ai-common/skills/field-skills/UserReference.md +181 -83
  47. package/templates/react-sdk-reference/.ai-common/skills/template-skills/ListView.md +125 -153
  48. package/templates/react-sdk-reference/.ai-common/skills/template-skills/SimpleTable.md +90 -81
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pega/sdk",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "Scaffold a Pega SDK application project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,12 +19,13 @@
19
19
  "tsConfig": "tsconfig.json",
20
20
  "assets": ["src/assets", "src/sdk-config.json"],
21
21
  "styles": ["src/styles.css"],
22
- "scripts": []
22
+ "scripts": ["../../node_modules/@pega/constellationjs/dist/constellation-core-web.js"]
23
23
  }
24
24
  },
25
25
  "serve": {
26
26
  "builder": "@angular-devkit/build-angular:dev-server",
27
27
  "options": {
28
+ "buildTarget": "angular-sdk-reference:build",
28
29
  "port": 3501
29
30
  }
30
31
  }
@@ -17,6 +17,7 @@
17
17
  "@angular/platform-browser": "^21.2.0",
18
18
  "@angular/platform-browser-dynamic": "^21.2.0",
19
19
  "@angular/router": "^21.2.0",
20
+ "@pega/constellationjs": "file:pega-constellationjs-0.26.3-local.tgz",
20
21
  "@pega/sdk-angular": "^0.1.0",
21
22
  "rxjs": "~7.8.2",
22
23
  "tslib": "^2.8.1",
@@ -1,25 +1,72 @@
1
- import { Component, OnInit } from '@angular/core';
1
+ /**
2
+ * Angular Mashup sample — demonstrates @pega/sdk-core + @pega/sdk-angular
3
+ * in embedded/mashup mode using PegaRuntimeService.
4
+ *
5
+ * Flow:
6
+ * 1. PegaRuntimeService.initialize() handles auth + bootstrap
7
+ * 2. Subscribe to state$ for reactive isReady/isLoading/error
8
+ * 3. Show "Create Case" button
9
+ * 4. On click → PCore.getMashupApi().createCase()
10
+ * 5. PConnectRendererComponent renders the case form tree
11
+ */
12
+
13
+ import { ChangeDetectorRef, Component, NgZone, OnInit, OnDestroy } from '@angular/core';
2
14
  import { CommonModule } from '@angular/common';
15
+ import { Subscription } from 'rxjs';
16
+ import { PegaRuntimeService, PConnectBridgeComponent, type RuntimeState } from '@pega/sdk-angular';
17
+ import { registerSampleAngularComponents } from '../components/component-map';
18
+ import { getSdkConfig } from '@pega/auth/lib/sdk-auth-manager';
19
+ import { compareSdkPCoreVersions } from '../helpers/versionHelpers';
3
20
 
4
21
  @Component({
5
22
  selector: 'app-root',
6
23
  standalone: true,
7
- imports: [CommonModule],
24
+ imports: [CommonModule, PConnectBridgeComponent],
8
25
  template: `
9
- <div *ngIf="!isReady" class="loading-container">
10
- <span class="loading-text">Authenticating and loading Pega...</span>
26
+ <!-- Loading state -->
27
+ <div *ngIf="!isPegaReady" class="loading-container">
28
+ <span class="loading-text">
29
+ {{ errorMessage ? errorMessage : 'Authenticating and loading Pega...' }}
30
+ </span>
11
31
  </div>
12
- <div *ngIf="isReady" id="pega-root">
32
+
33
+ <!-- Main UI -->
34
+ <div *ngIf="isPegaReady" id="pega-root">
35
+ <!-- Header -->
13
36
  <div class="header">
14
- <span class="header-title">Pega SDK — Angular Reference</span>
37
+ <span class="header-title">Pega SDK — Angular Mashup Sample</span>
38
+ <button type="button" class="btn-logout" (click)="logout()">Logout</button>
15
39
  </div>
16
- <div class="content">
17
- <p>Pega SDK Angular app is ready. Implement your components here.</p>
40
+
41
+ <div class="main-content">
42
+ <!-- Create Case button -->
43
+ <div *ngIf="!showForm" class="create-case-section">
44
+ <h2>Ready to go</h2>
45
+ <p class="subtitle">Click below to create a new case and see the Pega form components in action.</p>
46
+ <button type="button" class="btn-create" (click)="handleCreateCase()">Create Case</button>
47
+ </div>
48
+
49
+ <!-- Pega form area -->
50
+ <div class="form-container">
51
+ <div *ngIf="loading" class="loading-form">Loading case form...</div>
52
+ <pconnect-bridge *ngIf="!loading && rootPConnect" [getPConnect]="rootPConnectGetter!"></pconnect-bridge>
53
+ </div>
18
54
  </div>
19
55
  </div>
20
56
  `,
21
57
  styles: [
22
58
  `
59
+ .loading-container {
60
+ display: flex;
61
+ justify-content: center;
62
+ align-items: center;
63
+ height: 100vh;
64
+ font-family: system-ui, sans-serif;
65
+ }
66
+ .loading-text {
67
+ color: #64748b;
68
+ font-size: 1rem;
69
+ }
23
70
  .header {
24
71
  padding: 1rem 1.5rem;
25
72
  background: #1e293b;
@@ -31,24 +78,194 @@ import { CommonModule } from '@angular/common';
31
78
  .header-title {
32
79
  font-weight: 600;
33
80
  }
34
- .content {
81
+ .btn-logout {
82
+ background: transparent;
83
+ border: 1px solid #64748b;
84
+ color: #94a3b8;
85
+ padding: 0.375rem 0.75rem;
86
+ border-radius: 0.375rem;
87
+ cursor: pointer;
88
+ font-size: 0.8125rem;
89
+ }
90
+ .main-content {
35
91
  max-width: 720px;
36
92
  margin: 2rem auto;
37
93
  padding: 0 1.5rem;
94
+ font-family: system-ui, sans-serif;
95
+ }
96
+ .create-case-section {
97
+ text-align: center;
98
+ margin-bottom: 2rem;
99
+ }
100
+ .create-case-section h2 {
101
+ font-size: 1.5rem;
102
+ font-weight: 600;
103
+ margin-bottom: 0.75rem;
104
+ }
105
+ .subtitle {
106
+ color: #64748b;
107
+ margin-bottom: 1.5rem;
108
+ }
109
+ .btn-create {
110
+ background: #2563eb;
111
+ color: #fff;
112
+ border: none;
113
+ padding: 0.625rem 1.5rem;
114
+ border-radius: 0.375rem;
115
+ font-size: 0.9375rem;
116
+ font-weight: 500;
117
+ cursor: pointer;
118
+ }
119
+ .form-container {
120
+ background: #fff;
121
+ border: 1px solid #e2e8f0;
122
+ border-radius: 0.75rem;
123
+ padding: 1.5rem;
124
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
125
+ }
126
+ .loading-form {
127
+ text-align: center;
128
+ padding: 2rem;
129
+ color: #64748b;
38
130
  }
39
131
  `,
40
132
  ],
41
133
  })
42
- export class AppComponent implements OnInit {
43
- isReady = false;
134
+ export class AppComponent implements OnInit, OnDestroy {
135
+ isPegaReady = false;
136
+ rootPConnect: typeof PConnect | null = null;
137
+ rootPConnectGetter: (() => typeof PConnect) | null = null;
138
+ showForm = false;
139
+ loading = false;
140
+ errorMessage: string | undefined;
141
+
142
+ private runtime = new PegaRuntimeService();
143
+ private stateSub: Subscription | undefined;
144
+ private cancelSubId = 'angularMashup-cancel';
145
+ private doneSubId = 'angularMashup-assignmentDone';
146
+
147
+ constructor(
148
+ private ngZone: NgZone,
149
+ private cdr: ChangeDetectorRef,
150
+ ) {}
44
151
 
45
- constructor() {}
152
+ ngOnInit(): void {
153
+ this.stateSub = this.runtime.state$.subscribe((state: RuntimeState) => {
154
+ console.log('Runtime state update:', state);
155
+ this.ngZone.run(() => {
156
+ this.isPegaReady = state.isReady;
157
+ this.errorMessage = state.error;
158
+
159
+ if (state.isReady && state.bootstrapResult) {
160
+ const rootProps = state.bootstrapResult.rootProps as Record<string, unknown> | undefined;
161
+ const getPConnect = rootProps?.['getPConnect'] as (() => typeof PConnect) | undefined;
162
+ this.rootPConnect = getPConnect?.() ?? null;
163
+ this.rootPConnectGetter = getPConnect ?? null;
164
+ this.subscribeToPubSub();
165
+ }
166
+
167
+ this.cdr.detectChanges();
168
+ });
169
+ });
170
+
171
+ this.bootstrap();
172
+ }
173
+
174
+ ngOnDestroy(): void {
175
+ this.stateSub?.unsubscribe();
176
+
177
+ if (this.isPegaReady) {
178
+ PCore.getPubSubUtils().unsubscribe(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL, this.cancelSubId);
179
+ PCore.getPubSubUtils().unsubscribe(
180
+ PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.END_OF_ASSIGNMENT_PROCESSING,
181
+ this.doneSubId,
182
+ );
183
+ }
184
+ }
185
+
186
+ async bootstrap(): Promise<void> {
187
+ const sdkConfig = await getSdkConfig();
188
+ const config = sdkConfig as Record<string, Record<string, unknown>>;
189
+
190
+ await this.runtime.initialize(
191
+ {
192
+ serverConfig: {
193
+ infinityRestServerUrl: config['serverConfig']['infinityRestServerUrl'] as string,
194
+ appAlias: config['serverConfig']['appAlias'] as string,
195
+ },
196
+ authConfig: {
197
+ clientId: (config['authConfig']['mashupClientId'] ?? '') as string,
198
+ authorizeUri: (config['authConfig']['authorizeUri'] ?? '') as string,
199
+ tokenUri: (config['authConfig']['tokenUri'] ?? '') as string,
200
+ revokeUri: (config['authConfig']['revokeUri'] ?? '') as string,
201
+ redirectUri: (config['authConfig']['redirectUri'] ?? '') as string,
202
+ },
203
+ },
204
+ 'pega-root',
205
+ {
206
+ onPCoreReady: () => {
207
+ console.log('PCore is ready, running onPCoreReady callback');
208
+ // Register component creator so PCore uses our bridge
209
+ PCore.registerComponentCreator((c11nEnv: unknown) => c11nEnv);
210
+ registerSampleAngularComponents();
211
+ compareSdkPCoreVersions();
212
+
213
+ // Initialize container session gates (required by ViewContainer/FlowContainer)
214
+ sessionStorage.setItem('hasViewContainer', 'false');
215
+ sessionStorage.setItem('okToInitFlowContainer', 'true');
216
+ },
217
+ },
218
+ );
219
+ }
220
+
221
+ private subscribeToPubSub(): void {
222
+ PCore.getPubSubUtils().subscribe(
223
+ PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL,
224
+ () => {
225
+ this.ngZone.run(() => {
226
+ this.showForm = false;
227
+ });
228
+ },
229
+ this.cancelSubId,
230
+ );
231
+
232
+ PCore.getPubSubUtils().subscribe(
233
+ PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.END_OF_ASSIGNMENT_PROCESSING,
234
+ () => {
235
+ this.ngZone.run(() => {
236
+ this.showForm = false;
237
+ });
238
+ },
239
+ this.doneSubId,
240
+ );
241
+ }
242
+
243
+ async handleCreateCase(): Promise<void> {
244
+ this.loading = true;
245
+ this.showForm = true;
46
246
 
47
- async ngOnInit() {
48
247
  try {
49
- this.isReady = true;
248
+ const sdkConfig = await getSdkConfig();
249
+ const mashupCaseType = (sdkConfig as Record<string, Record<string, unknown>>).serverConfig[
250
+ 'appMashupCaseType'
251
+ ] as string;
252
+
253
+ await PCore.getMashupApi().createCase(mashupCaseType, PCore.getConstants().APP.APP, {
254
+ pageName: 'pyEmbedAssignment',
255
+ startingFields: {},
256
+ });
50
257
  } catch (err) {
51
- console.error('Failed to initialize Pega SDK:', err);
258
+ console.error('Error creating case:', err);
259
+ this.showForm = false;
260
+ } finally {
261
+ this.ngZone.run(() => {
262
+ this.loading = false;
263
+ this.cdr.detectChanges();
264
+ });
52
265
  }
53
266
  }
267
+
268
+ logout(): void {
269
+ this.runtime.authClient.logout();
270
+ }
54
271
  }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Angular sample component map — maps Pega component names to Angular component classes.
3
+ *
4
+ * Mirrors the getSampleComponentMap() pattern from @pega/sdk-react.
5
+ */
6
+
7
+ import type { Type } from '@angular/core';
8
+ import { registerAngularComponentMap } from '@pega/sdk-angular';
9
+
10
+ // Infrastructure
11
+ import { RootContainerComponent } from './infra/root-container/root-container.component';
12
+ import { ViewContainerComponent } from './infra/view-container/view-container.component';
13
+ import { FlowContainerComponent } from './infra/flow-container/flow-container.component';
14
+ import { AssignmentComponent } from './infra/assignment/assignment.component';
15
+ import { ViewComponent } from './infra/view/view.component';
16
+ import { ReferenceComponent } from './infra/reference/reference.component';
17
+ import { RegionComponent } from './infra/region/region.component';
18
+ import { CaseViewComponent } from './infra/case-view/case-view.component';
19
+ import { CaseSummaryComponent } from './infra/case-summary/case-summary.component';
20
+ import { StagesComponent } from './infra/stages/stages.component';
21
+
22
+ // Templates
23
+ import { DefaultFormComponent } from './templates/default-form/default-form.component';
24
+ import { OneColumnComponent } from './templates/one-column/one-column.component';
25
+ import { TwoColumnComponent } from './templates/two-column/two-column.component';
26
+ import { NarrowWideComponent } from './templates/narrow-wide/narrow-wide.component';
27
+ import { WideNarrowComponent } from './templates/wide-narrow/wide-narrow.component';
28
+
29
+ // Fields
30
+ import { TextInputComponent } from './fields/text-input/text-input.component';
31
+ import { TextAreaComponent } from './fields/text-area/text-area.component';
32
+ import { DropdownComponent } from './fields/dropdown/dropdown.component';
33
+ import { CheckboxComponent } from './fields/checkbox/checkbox.component';
34
+ import { RadioButtonsComponent } from './fields/radio-buttons/radio-buttons.component';
35
+ import { CurrencyComponent } from './fields/currency/currency.component';
36
+ import { DateInputComponent } from './fields/date-input/date-input.component';
37
+ import { EmailComponent } from './fields/email/email.component';
38
+ import { PhoneComponent } from './fields/phone/phone.component';
39
+ import { IntegerComponent } from './fields/integer/integer.component';
40
+ import { URLInputComponent } from './fields/url-input/url-input.component';
41
+ import { AutoCompleteComponent } from './fields/auto-complete/auto-complete.component';
42
+
43
+ function getSampleAngularComponentMap(): Record<string, Type<unknown>> {
44
+ return {
45
+ // Fields
46
+ TextInput: TextInputComponent,
47
+ TextArea: TextAreaComponent,
48
+ Dropdown: DropdownComponent,
49
+ Checkbox: CheckboxComponent,
50
+ RadioButtons: RadioButtonsComponent,
51
+ Currency: CurrencyComponent,
52
+ Date: DateInputComponent,
53
+ Email: EmailComponent,
54
+ Phone: PhoneComponent,
55
+ Integer: IntegerComponent,
56
+ URL: URLInputComponent,
57
+ AutoComplete: AutoCompleteComponent,
58
+
59
+ // Templates
60
+ DefaultForm: DefaultFormComponent,
61
+ OneColumn: OneColumnComponent,
62
+ TwoColumn: TwoColumnComponent,
63
+ NarrowWide: NarrowWideComponent,
64
+ WideNarrow: WideNarrowComponent,
65
+
66
+ // Infrastructure
67
+ Assignment: AssignmentComponent,
68
+ RootContainer: RootContainerComponent,
69
+ ViewContainer: ViewContainerComponent,
70
+ FlowContainer: FlowContainerComponent,
71
+ View: ViewComponent,
72
+ Region: RegionComponent,
73
+ CaseView: CaseViewComponent,
74
+ CaseSummary: CaseSummaryComponent,
75
+ Stages: StagesComponent,
76
+
77
+ // Aliases — PCore may use different name variants
78
+ AppShell: RootContainerComponent,
79
+ CaseViewContainer: ViewContainerComponent,
80
+ SubTabs: ViewContainerComponent,
81
+ DeferLoad: ViewContainerComponent,
82
+ reference: ReferenceComponent,
83
+ };
84
+ }
85
+
86
+ function registerSampleAngularComponents(): void {
87
+ registerAngularComponentMap(getSampleAngularComponentMap());
88
+ }
89
+
90
+ export { getSampleAngularComponentMap, registerSampleAngularComponents };
@@ -0,0 +1,267 @@
1
+ /**
2
+ * AutoComplete — typeahead/autocomplete field.
3
+ *
4
+ * Supports associated lists (options from datasource prop) and data page lists
5
+ * (options fetched via PCore.getDataPageUtils). Also handles deferDatasource mode
6
+ * where datasourceMetadata transforms into a data page call.
7
+ *
8
+ * Pattern reference: angular-sdk-components AutoCompleteComponent + sdk-react AutoComplete
9
+ */
10
+
11
+ /* eslint-disable @typescript-eslint/no-explicit-any */
12
+ import { Component, Input } from '@angular/core';
13
+ import type { OnInit, OnChanges, SimpleChanges } from '@angular/core';
14
+ import { CommonModule } from '@angular/common';
15
+ import { FormsModule } from '@angular/forms';
16
+ import { handleEvent } from '../../../helpers/event-utils';
17
+ import { getOptionList } from '../../../helpers/utils';
18
+
19
+ interface IOption {
20
+ key: string;
21
+ value: string;
22
+ }
23
+
24
+ @Component({
25
+ selector: 'pega-auto-complete',
26
+ standalone: true,
27
+ imports: [CommonModule, FormsModule],
28
+ template: `
29
+ <!-- Read-only / display mode -->
30
+ <div *ngIf="readOnly || isDisplayMode" style="margin-bottom: 1rem;">
31
+ <span style="font-size: 0.75rem; color: #6b7280;">{{ label }}</span>
32
+ <div style="font-size: 0.875rem;">{{ displayValue }}</div>
33
+ </div>
34
+
35
+ <!-- Editable mode -->
36
+ <div *ngIf="!readOnly && !isDisplayMode" style="margin-bottom: 1rem; position: relative;">
37
+ <label *ngIf="label" style="display: block; margin-bottom: 0.25rem; font-weight: 500; font-size: 0.875rem;">
38
+ {{ label }}<span *ngIf="required" style="color: #dc2626; margin-left: 0.25rem;">*</span>
39
+ </label>
40
+ <input
41
+ type="text"
42
+ [attr.data-testid]="testId"
43
+ [value]="inputValue"
44
+ [placeholder]="placeholder"
45
+ [disabled]="disabled"
46
+ [required]="required"
47
+ (input)="onInput($event)"
48
+ (focus)="showDropdown = true"
49
+ (blur)="onBlur()"
50
+ autocomplete="off"
51
+ style="width: 100%; padding: 0.5rem 0.75rem; border-radius: 0.375rem; font-size: 0.875rem; box-sizing: border-box;"
52
+ [style.border]="validatemessage ? '1px solid #dc2626' : '1px solid #d1d5db'"
53
+ />
54
+ <!-- Dropdown list -->
55
+ <ul
56
+ *ngIf="showDropdown && filteredOptions.length > 0"
57
+ style="position: absolute; z-index: 10; width: 100%; max-height: 12rem; overflow-y: auto; background: #fff; border: 1px solid #d1d5db; border-radius: 0.375rem; margin-top: 0.125rem; padding: 0; list-style: none; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);"
58
+ >
59
+ <li
60
+ *ngFor="let opt of filteredOptions"
61
+ (mousedown)="selectOption(opt)"
62
+ style="padding: 0.5rem 0.75rem; cursor: pointer; font-size: 0.875rem;"
63
+ >
64
+ {{ opt.value }}
65
+ </li>
66
+ </ul>
67
+ <div
68
+ *ngIf="helperTextToDisplay"
69
+ style="font-size: 0.75rem; margin-top: 0.25rem;"
70
+ [style.color]="validatemessage ? '#dc2626' : '#6b7280'"
71
+ >
72
+ {{ helperTextToDisplay }}
73
+ </div>
74
+ </div>
75
+ `,
76
+ })
77
+ export class AutoCompleteComponent implements OnInit, OnChanges {
78
+ @Input() props: any;
79
+
80
+ label = '';
81
+ value = '';
82
+ placeholder = '';
83
+ testId = '';
84
+ required = false;
85
+ disabled = false;
86
+ readOnly = false;
87
+ validatemessage = '';
88
+ helperText = '';
89
+ isDisplayMode = false;
90
+ displayValue = '';
91
+ inputValue = '';
92
+ showDropdown = false;
93
+ options: IOption[] = [];
94
+ filteredOptions: IOption[] = [];
95
+ helperTextToDisplay = '';
96
+
97
+ private actions: any;
98
+ private propName = '';
99
+ private pConn: any;
100
+ private listType = '';
101
+
102
+ ngOnInit(): void {
103
+ this.updateFromProps();
104
+ this.loadOptions();
105
+ }
106
+
107
+ ngOnChanges(changes: SimpleChanges): void {
108
+ if (changes['props']) {
109
+ this.updateFromProps();
110
+ this.loadOptions();
111
+ }
112
+ }
113
+
114
+ private updateFromProps(): void {
115
+ const p = this.props;
116
+ this.label = p.label ?? '';
117
+ this.value = p.value ?? '';
118
+ this.placeholder = p.placeholder ?? '';
119
+ this.testId = p.testId ?? '';
120
+ this.required = p.required ?? false;
121
+ this.disabled = p.disabled ?? false;
122
+ this.readOnly = p.readOnly ?? false;
123
+ this.validatemessage = p.validatemessage ?? '';
124
+ this.helperText = p.helperText ?? '';
125
+ this.helperTextToDisplay = this.validatemessage || this.helperText;
126
+ this.isDisplayMode = p.displayMode === 'DISPLAY_ONLY' || p.displayMode === 'STACKED_LARGE_VAL';
127
+
128
+ this.pConn = p.getPConnect?.();
129
+ this.actions = this.pConn?.getActionsApi?.();
130
+ this.propName = ((this.pConn?.getStateProps?.() as Record<string, unknown>)?.value as string) ?? '';
131
+
132
+ // Resolve display value from current options
133
+ this.updateDisplayValue();
134
+ }
135
+
136
+ private updateDisplayValue(): void {
137
+ const match = this.options.find((o) => o.key === this.value);
138
+ this.displayValue = match?.value ?? this.value ?? '';
139
+ if (!this.inputValue || this.inputValue !== this.displayValue) {
140
+ this.inputValue = this.displayValue;
141
+ }
142
+ this.filterOptions();
143
+ }
144
+
145
+ private filterOptions(): void {
146
+ const text = (this.inputValue ?? '').toLowerCase();
147
+ this.filteredOptions = text ? this.options.filter((o) => o.value?.toLowerCase().includes(text)) : [...this.options];
148
+ }
149
+
150
+ private loadOptions(): void {
151
+ const p = this.props;
152
+ let { listType, parameters, datasource, columns = [] } = p;
153
+
154
+ // Handle deferDatasource
155
+ if (p.deferDatasource && p.datasourceMetadata?.datasource?.name) {
156
+ listType = 'datapage';
157
+ datasource = p.datasourceMetadata.datasource.name;
158
+ const meta = p.datasourceMetadata.datasource;
159
+ parameters = this.flattenParameters(meta.parameters);
160
+ const displayProp = meta.propertyForDisplayText?.startsWith('@P')
161
+ ? meta.propertyForDisplayText.substring(3)
162
+ : meta.propertyForDisplayText;
163
+ const valueProp = meta.propertyForValue?.startsWith('@P')
164
+ ? meta.propertyForValue.substring(3)
165
+ : meta.propertyForValue;
166
+ columns = [
167
+ { key: 'true', setProperty: 'Associated property', value: valueProp },
168
+ {
169
+ display: 'true',
170
+ primary: 'true',
171
+ useForSearch: 'true',
172
+ value: displayProp,
173
+ },
174
+ ];
175
+ }
176
+
177
+ this.listType = listType ?? '';
178
+ const processedColumns = this.preProcessColumns(columns ?? []);
179
+
180
+ if (this.listType === 'associated') {
181
+ const dataObject = this.pConn?.getDataObject?.('') ?? {};
182
+ const optList = getOptionList(p as Record<string, unknown>, dataObject as Record<string, unknown>);
183
+ this.setOptions(optList.map((o) => ({ key: o.value, value: o.text ?? o.value })));
184
+ } else if (this.listType === 'datapage' && datasource) {
185
+ const context = this.pConn?.getContextName?.() ?? '';
186
+ const flatParams =
187
+ typeof parameters === 'object' && !Array.isArray(parameters) ? this.flattenParameters(parameters) : {};
188
+ (PCore.getDataPageUtils() as any)
189
+ .getDataAsync(datasource, context, flatParams)
190
+ .then((response: any) => {
191
+ const results = Array.isArray(response) ? response : (response?.data ?? []);
192
+ const displayColumn = this.getDisplayFieldsMetaData(processedColumns);
193
+ const optionsData: IOption[] = results.map((element: Record<string, unknown>) => ({
194
+ key: String(element[displayColumn.key] ?? element['pyGUID'] ?? ''),
195
+ value: String(element[displayColumn.primary] ?? ''),
196
+ }));
197
+ this.setOptions(optionsData);
198
+ })
199
+ .catch(() => {
200
+ // Data page fetch failed
201
+ });
202
+ }
203
+ }
204
+
205
+ private setOptions(opts: IOption[]): void {
206
+ this.options = opts;
207
+ this.updateDisplayValue();
208
+ }
209
+
210
+ private flattenParameters(params: Record<string, any> = {}): Record<string, unknown> {
211
+ const flat: Record<string, unknown> = {};
212
+ for (const key of Object.keys(params)) {
213
+ const { name, value: val } = params[key];
214
+ flat[name] = val;
215
+ }
216
+ return flat;
217
+ }
218
+
219
+ private preProcessColumns(columnList: Array<Record<string, string>>): Array<Record<string, string>> {
220
+ return columnList.map((col) => {
221
+ const temp = { ...col };
222
+ temp.value = col.value && col.value.startsWith('.') ? col.value.substring(1) : col.value;
223
+ return temp;
224
+ });
225
+ }
226
+
227
+ private getDisplayFieldsMetaData(columnList: Array<Record<string, string>>): {
228
+ key: string;
229
+ primary: string;
230
+ secondary: string[];
231
+ } {
232
+ const displayColumns = columnList.filter((col) => col['display'] === 'true');
233
+ const metaData = { key: '', primary: '', secondary: [] as string[] };
234
+ const keyCol = columnList.filter((col) => col['key'] === 'true');
235
+ metaData.key = keyCol.length > 0 ? keyCol[0].value : 'auto';
236
+ for (const col of displayColumns) {
237
+ if (col['primary'] === 'true') {
238
+ metaData.primary = col.value;
239
+ } else {
240
+ metaData.secondary.push(col.value);
241
+ }
242
+ }
243
+ return metaData;
244
+ }
245
+
246
+ onInput(event: Event): void {
247
+ const target = event.target as HTMLInputElement;
248
+ this.inputValue = target.value;
249
+ this.showDropdown = true;
250
+ this.filterOptions();
251
+ }
252
+
253
+ onBlur(): void {
254
+ // Delay to allow click on an option
255
+ setTimeout(() => {
256
+ this.showDropdown = false;
257
+ }, 200);
258
+ }
259
+
260
+ selectOption(opt: IOption): void {
261
+ this.inputValue = opt.value;
262
+ this.showDropdown = false;
263
+ if (this.actions && this.propName) {
264
+ handleEvent(this.actions, 'changeNblur', this.propName, opt.key);
265
+ }
266
+ }
267
+ }