@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.
- package/package.json +1 -1
- package/templates/angular-sdk-reference/angular.json +2 -1
- package/templates/angular-sdk-reference/package.json +1 -0
- package/templates/angular-sdk-reference/pega-constellationjs-0.26.3-local.tgz +0 -0
- package/templates/angular-sdk-reference/src/app/app.component.ts +232 -15
- package/templates/angular-sdk-reference/src/components/component-map.ts +90 -0
- package/templates/angular-sdk-reference/src/components/fields/auto-complete/auto-complete.component.ts +267 -0
- package/templates/angular-sdk-reference/src/components/fields/checkbox/checkbox.component.ts +84 -0
- package/templates/angular-sdk-reference/src/components/fields/currency/currency.component.ts +97 -0
- package/templates/angular-sdk-reference/src/components/fields/date-input/date-input.component.ts +88 -0
- package/templates/angular-sdk-reference/src/components/fields/dropdown/dropdown.component.ts +101 -0
- package/templates/angular-sdk-reference/src/components/fields/email/email.component.ts +91 -0
- package/templates/angular-sdk-reference/src/components/fields/integer/integer.component.ts +93 -0
- package/templates/angular-sdk-reference/src/components/fields/phone/phone.component.ts +91 -0
- package/templates/angular-sdk-reference/src/components/fields/radio-buttons/radio-buttons.component.ts +102 -0
- package/templates/angular-sdk-reference/src/components/fields/text-area/text-area.component.ts +98 -0
- package/templates/angular-sdk-reference/src/components/fields/text-input/text-input.component.ts +97 -0
- package/templates/angular-sdk-reference/src/components/fields/url-input/url-input.component.ts +94 -0
- package/templates/angular-sdk-reference/src/components/infra/assignment/assignment.component.ts +206 -0
- package/templates/angular-sdk-reference/src/components/infra/case-summary/case-summary.component.ts +76 -0
- package/templates/angular-sdk-reference/src/components/infra/case-view/case-view.component.ts +114 -0
- package/templates/angular-sdk-reference/src/components/infra/flow-container/flow-container.component.ts +411 -0
- package/templates/angular-sdk-reference/src/components/infra/reference/reference.component.ts +158 -0
- package/templates/angular-sdk-reference/src/components/infra/region/region.component.ts +39 -0
- package/templates/angular-sdk-reference/src/components/infra/root-container/root-container.component.ts +193 -0
- package/templates/angular-sdk-reference/src/components/infra/stages/stages.component.ts +78 -0
- package/templates/angular-sdk-reference/src/components/infra/view/view.component.ts +139 -0
- package/templates/angular-sdk-reference/src/components/infra/view-container/view-container.component.ts +191 -0
- package/templates/angular-sdk-reference/src/components/templates/default-form/default-form.component.ts +101 -0
- package/templates/angular-sdk-reference/src/components/templates/narrow-wide/narrow-wide.component.ts +37 -0
- package/templates/angular-sdk-reference/src/components/templates/one-column/one-column.component.ts +37 -0
- package/templates/angular-sdk-reference/src/components/templates/two-column/two-column.component.ts +37 -0
- package/templates/angular-sdk-reference/src/components/templates/wide-narrow/wide-narrow.component.ts +37 -0
- package/templates/angular-sdk-reference/src/helpers/event-utils.ts +18 -0
- package/templates/angular-sdk-reference/src/helpers/utils.ts +38 -0
- package/templates/angular-sdk-reference/src/helpers/versionHelpers.ts +14 -0
- package/templates/angular-sdk-reference/tsconfig.json +5 -2
- package/templates/react-sdk-reference/.ai-common/agents/embedded-flow-expert.md +11 -4
- package/templates/react-sdk-reference/.ai-common/prompts/component-manifest.json +7 -0
- package/templates/react-sdk-reference/.ai-common/prompts/generate-all-components.md +3 -1
- package/templates/react-sdk-reference/.ai-common/skills/field-components.md +75 -29
- package/templates/react-sdk-reference/.ai-common/skills/field-skills/AutoComplete.md +133 -101
- package/templates/react-sdk-reference/.ai-common/skills/field-skills/EmbeddedData.md +385 -0
- package/templates/react-sdk-reference/.ai-common/skills/field-skills/EmbeddedDataMulti.md +506 -0
- package/templates/react-sdk-reference/.ai-common/skills/field-skills/ObjectReference.md +549 -86
- package/templates/react-sdk-reference/.ai-common/skills/field-skills/UserReference.md +181 -83
- package/templates/react-sdk-reference/.ai-common/skills/template-skills/ListView.md +125 -153
- package/templates/react-sdk-reference/.ai-common/skills/template-skills/SimpleTable.md +90 -81
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
+
}
|