@salesforce/experimental-mfe-lwc-shell 2.2.0 → 2.2.1-rc.1
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/dist/InternalHostLwcShell.d.ts +1 -3
- package/dist/graphqlBridgeHandler.d.ts +29 -0
- package/dist/index.esm.js +118 -56
- package/dist/index.iife.js +118 -56
- package/dist/index.iife.prod.js +3 -3
- package/package.json +1 -1
- package/dist/graphqlHandler.d.ts +0 -5
- package/dist/lwc/dirtyStateModal/dirtyStateModal.html +0 -33
- package/dist/lwc/dirtyStateModal/dirtyStateModal.js +0 -82
- package/dist/lwc/dirtyStateModal/dirtyStateModal.js-meta.xml +0 -5
- package/dist/utils/dirtyStateModal.d.ts +0 -23
- package/dist/utils/dirtyStateModal.d.ts.map +0 -1
- package/dist/utils/index.d.ts +0 -13
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/symbols.d.ts +0 -18
- package/dist/utils/symbols.d.ts.map +0 -1
|
@@ -40,8 +40,7 @@ export declare class InternalHostLwcShell extends HTMLElement {
|
|
|
40
40
|
private _title;
|
|
41
41
|
private _view;
|
|
42
42
|
private _debugEnabled;
|
|
43
|
-
private
|
|
44
|
-
private static readonly MAX_CONCURRENT_GRAPHQL;
|
|
43
|
+
private _graphqlHandler;
|
|
45
44
|
static get observedAttributes(): string[];
|
|
46
45
|
constructor();
|
|
47
46
|
connectedCallback(): void;
|
|
@@ -83,7 +82,6 @@ export declare class InternalHostLwcShell extends HTMLElement {
|
|
|
83
82
|
private _exitFullscreen;
|
|
84
83
|
private _handleBridgeReady;
|
|
85
84
|
private _handleBridgeError;
|
|
86
|
-
private _handleGraphQLRequest;
|
|
87
85
|
private _handleContainerClick;
|
|
88
86
|
private _applySandbox;
|
|
89
87
|
private _computeSandboxTokens;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface GraphQLBridgeRequest {
|
|
2
|
+
requestId: string;
|
|
3
|
+
query: string;
|
|
4
|
+
variables?: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
export interface GraphQLBridgeResponse {
|
|
7
|
+
requestId: string;
|
|
8
|
+
ok: boolean;
|
|
9
|
+
result?: {
|
|
10
|
+
data: unknown;
|
|
11
|
+
errors?: unknown[];
|
|
12
|
+
};
|
|
13
|
+
error?: {
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare function executeGraphQL(query: string, variables?: Record<string, unknown>): Promise<{
|
|
18
|
+
data: unknown;
|
|
19
|
+
errors?: unknown[];
|
|
20
|
+
}>;
|
|
21
|
+
export declare class GraphQLBridgeHandler {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(sendResponse: (data: GraphQLBridgeResponse) => void, maxConcurrent?: number, maxQueueSize?: number);
|
|
24
|
+
static isValidRequest(data: unknown): data is GraphQLBridgeRequest;
|
|
25
|
+
rejectInvalidRequest(data: unknown): void;
|
|
26
|
+
handleRequest({ requestId, query, variables }: GraphQLBridgeRequest): Promise<void>;
|
|
27
|
+
reset(): void;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=graphqlBridgeHandler.d.ts.map
|
package/dist/index.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @salesforce/experimental-mfe-lwc-shell v2.2.
|
|
1
|
+
/*! @salesforce/experimental-mfe-lwc-shell v2.2.1-rc.1 (2026-04-09) */
|
|
2
2
|
import { gql, unstable_graphql_imperative } from 'lightning/graphql';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -28,6 +28,108 @@ async function executeGraphQL(query, variables) {
|
|
|
28
28
|
const result = await unstable_graphql_imperative({ query: documentNode, variables });
|
|
29
29
|
return { data: result?.data, errors: result?.errors };
|
|
30
30
|
}
|
|
31
|
+
class GraphQLBridgeHandler {
|
|
32
|
+
#sendResponse;
|
|
33
|
+
#pendingCount = 0;
|
|
34
|
+
#queue = [];
|
|
35
|
+
#maxConcurrent;
|
|
36
|
+
#maxQueueSize;
|
|
37
|
+
#generation = 0;
|
|
38
|
+
constructor(sendResponse, maxConcurrent = 10, maxQueueSize = 50) {
|
|
39
|
+
this.#sendResponse = sendResponse;
|
|
40
|
+
this.#maxConcurrent = maxConcurrent;
|
|
41
|
+
this.#maxQueueSize = maxQueueSize;
|
|
42
|
+
}
|
|
43
|
+
static isValidRequest(data) {
|
|
44
|
+
const req = data;
|
|
45
|
+
return (typeof req?.requestId === "string" &&
|
|
46
|
+
typeof req?.query === "string" &&
|
|
47
|
+
(req.variables == null || (typeof req.variables === "object" && !Array.isArray(req.variables))));
|
|
48
|
+
}
|
|
49
|
+
rejectInvalidRequest(data) {
|
|
50
|
+
const req = data;
|
|
51
|
+
if (typeof req?.requestId !== "string") {
|
|
52
|
+
this.#sendResponse({
|
|
53
|
+
requestId: req?.requestId,
|
|
54
|
+
ok: false,
|
|
55
|
+
error: { message: "Invalid GraphQL request data" },
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const { requestId, query, variables } = req;
|
|
60
|
+
if (typeof query !== "string") {
|
|
61
|
+
this.#sendResponse({
|
|
62
|
+
requestId: requestId,
|
|
63
|
+
ok: false,
|
|
64
|
+
error: { message: "Invalid GraphQL query: query must be a string" },
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (variables != null && (typeof variables !== "object" || Array.isArray(variables))) {
|
|
69
|
+
this.#sendResponse({
|
|
70
|
+
requestId: requestId,
|
|
71
|
+
ok: false,
|
|
72
|
+
error: { message: "Invalid GraphQL variables: must be a plain object" },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
handleRequest({ requestId, query, variables }) {
|
|
77
|
+
if (this.#pendingCount >= this.#maxConcurrent) {
|
|
78
|
+
return this.#enqueueRequest(requestId, query, variables);
|
|
79
|
+
}
|
|
80
|
+
return this.#execute(requestId, query, variables);
|
|
81
|
+
}
|
|
82
|
+
reset() {
|
|
83
|
+
this.#generation++;
|
|
84
|
+
this.#queue = [];
|
|
85
|
+
this.#pendingCount = 0;
|
|
86
|
+
}
|
|
87
|
+
#execute = async (requestId, query, variables) => {
|
|
88
|
+
const gen = this.#generation;
|
|
89
|
+
this.#pendingCount++;
|
|
90
|
+
try {
|
|
91
|
+
const result = await executeGraphQL(query, variables);
|
|
92
|
+
if (gen === this.#generation) {
|
|
93
|
+
this.#sendResponse({ requestId, ok: true, result });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
if (gen === this.#generation) {
|
|
98
|
+
this.#sendResponse({
|
|
99
|
+
requestId,
|
|
100
|
+
ok: false,
|
|
101
|
+
error: { message: err?.message || "GraphQL request failed" },
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
if (gen === this.#generation) {
|
|
107
|
+
this.#pendingCount--;
|
|
108
|
+
this.#processRequestQueue();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
#enqueueRequest = (requestId, query, variables) => {
|
|
113
|
+
if (this.#queue.length >= this.#maxQueueSize) {
|
|
114
|
+
this.#sendResponse({
|
|
115
|
+
requestId,
|
|
116
|
+
ok: false,
|
|
117
|
+
error: { message: "Too many queued GraphQL requests" },
|
|
118
|
+
});
|
|
119
|
+
return Promise.resolve();
|
|
120
|
+
}
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
this.#queue.push({ requestId, query, variables, done: resolve });
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
#processRequestQueue = () => {
|
|
126
|
+
if (this.#queue.length === 0 || this.#pendingCount >= this.#maxConcurrent) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const next = this.#queue.shift();
|
|
130
|
+
this.#execute(next.requestId, next.query, next.variables).then(next.done);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
31
133
|
|
|
32
134
|
/**
|
|
33
135
|
* InternalHostLwcShell
|
|
@@ -51,6 +153,8 @@ async function executeGraphQL(query, variables) {
|
|
|
51
153
|
*
|
|
52
154
|
* See the README and the `productRegistration` demo for a full recipe.
|
|
53
155
|
*/
|
|
156
|
+
const MAX_CONCURRENT_GRAPHQL_REQUESTS = 10;
|
|
157
|
+
const MAX_GRAPHQL_QUEUE_SIZE = 50;
|
|
54
158
|
const BASE_TOKENS = ["allow-scripts", "allow-pointer-lock"];
|
|
55
159
|
const OPTIONAL_TOKENS = ["allow-downloads", "allow-forms", "allow-modals"];
|
|
56
160
|
const BLOCKED_TOKENS = ["allow-same-origin", "allow-top-navigation", "allow-popups"];
|
|
@@ -147,14 +251,16 @@ class InternalHostLwcShell extends HTMLElement {
|
|
|
147
251
|
_title = "Embedded widget";
|
|
148
252
|
_view = "compact";
|
|
149
253
|
_debugEnabled = true;
|
|
150
|
-
|
|
151
|
-
static MAX_CONCURRENT_GRAPHQL = 10;
|
|
254
|
+
_graphqlHandler;
|
|
152
255
|
static get observedAttributes() {
|
|
153
256
|
return ["src", "srcdoc", "sandbox", "title", "view", "debug"];
|
|
154
257
|
}
|
|
155
258
|
constructor() {
|
|
156
259
|
super();
|
|
157
260
|
this._shadow = this.attachShadow({ mode: "closed" });
|
|
261
|
+
this._graphqlHandler = new GraphQLBridgeHandler((data) => {
|
|
262
|
+
this._postToIframe("bridge-graphql-response", data);
|
|
263
|
+
}, MAX_CONCURRENT_GRAPHQL_REQUESTS, MAX_GRAPHQL_QUEUE_SIZE);
|
|
158
264
|
}
|
|
159
265
|
connectedCallback() {
|
|
160
266
|
this._renderInitial();
|
|
@@ -163,6 +269,7 @@ class InternalHostLwcShell extends HTMLElement {
|
|
|
163
269
|
}
|
|
164
270
|
disconnectedCallback() {
|
|
165
271
|
window.removeEventListener("message", this._handleMessage);
|
|
272
|
+
this._graphqlHandler.reset();
|
|
166
273
|
if (this._readinessTimeout) {
|
|
167
274
|
clearTimeout(this._readinessTimeout);
|
|
168
275
|
this._readinessTimeout = null;
|
|
@@ -423,12 +530,17 @@ class InternalHostLwcShell extends HTMLElement {
|
|
|
423
530
|
this._handleFullscreenRequest();
|
|
424
531
|
}
|
|
425
532
|
}
|
|
426
|
-
else
|
|
533
|
+
else {
|
|
427
534
|
this.dispatchEvent(new CustomEvent(eventType, { detail, bubbles: true, cancelable: true, composed: true }));
|
|
428
535
|
}
|
|
429
536
|
}
|
|
430
537
|
else if (type === "bridge-graphql-request") {
|
|
431
|
-
|
|
538
|
+
if (GraphQLBridgeHandler.isValidRequest(data)) {
|
|
539
|
+
this._graphqlHandler.handleRequest(data);
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
this._graphqlHandler.rejectInvalidRequest(data);
|
|
543
|
+
}
|
|
432
544
|
}
|
|
433
545
|
else if (type === "bridge-ready") {
|
|
434
546
|
this._handleBridgeReady();
|
|
@@ -498,57 +610,6 @@ class InternalHostLwcShell extends HTMLElement {
|
|
|
498
610
|
this.dispatchEvent(new CustomEvent("widget-bridge-error", { detail: errorData }));
|
|
499
611
|
this._log("bridge-error", errorData);
|
|
500
612
|
}
|
|
501
|
-
async _handleGraphQLRequest(data) {
|
|
502
|
-
if (typeof data?.requestId !== "string") {
|
|
503
|
-
this._log("_handleGraphQLRequest: invalid request data", data);
|
|
504
|
-
this._postToIframe("bridge-graphql-response", {
|
|
505
|
-
requestId: data?.requestId,
|
|
506
|
-
ok: false,
|
|
507
|
-
error: { message: "Invalid GraphQL request data" },
|
|
508
|
-
});
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
const { requestId, query, variables } = data;
|
|
512
|
-
if (typeof query !== "string") {
|
|
513
|
-
this._postToIframe("bridge-graphql-response", {
|
|
514
|
-
requestId,
|
|
515
|
-
ok: false,
|
|
516
|
-
error: { message: "Invalid GraphQL query: query must be a string" },
|
|
517
|
-
});
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
if (variables != null && (typeof variables !== "object" || Array.isArray(variables))) {
|
|
521
|
-
this._postToIframe("bridge-graphql-response", {
|
|
522
|
-
requestId,
|
|
523
|
-
ok: false,
|
|
524
|
-
error: { message: "Invalid GraphQL variables: must be a plain object" },
|
|
525
|
-
});
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
if (this._pendingGraphQLCount >= InternalHostLwcShell.MAX_CONCURRENT_GRAPHQL) {
|
|
529
|
-
this._postToIframe("bridge-graphql-response", {
|
|
530
|
-
requestId,
|
|
531
|
-
ok: false,
|
|
532
|
-
error: { message: "Too many concurrent GraphQL requests" },
|
|
533
|
-
});
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
this._pendingGraphQLCount++;
|
|
537
|
-
try {
|
|
538
|
-
const result = await executeGraphQL(query, variables);
|
|
539
|
-
this._postToIframe("bridge-graphql-response", { requestId, ok: true, result });
|
|
540
|
-
}
|
|
541
|
-
catch (err) {
|
|
542
|
-
this._postToIframe("bridge-graphql-response", {
|
|
543
|
-
requestId,
|
|
544
|
-
ok: false,
|
|
545
|
-
error: { message: err?.message || "GraphQL request failed" },
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
finally {
|
|
549
|
-
this._pendingGraphQLCount--;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
613
|
_handleContainerClick() {
|
|
553
614
|
// placeholder
|
|
554
615
|
}
|
|
@@ -586,6 +647,7 @@ class InternalHostLwcShell extends HTMLElement {
|
|
|
586
647
|
return;
|
|
587
648
|
// reset tracking
|
|
588
649
|
this._bridgeReady = false;
|
|
650
|
+
this._graphqlHandler.reset();
|
|
589
651
|
this._currentState = STATES.LOADING;
|
|
590
652
|
this._updateContainerState();
|
|
591
653
|
// clear existing
|
package/dist/index.iife.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @salesforce/experimental-mfe-lwc-shell v2.2.
|
|
1
|
+
/*! @salesforce/experimental-mfe-lwc-shell v2.2.1-rc.1 (2026-04-09) */
|
|
2
2
|
var LwcShell = (function (exports, graphql) {
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
@@ -29,6 +29,108 @@ var LwcShell = (function (exports, graphql) {
|
|
|
29
29
|
const result = await graphql.unstable_graphql_imperative({ query: documentNode, variables });
|
|
30
30
|
return { data: result?.data, errors: result?.errors };
|
|
31
31
|
}
|
|
32
|
+
class GraphQLBridgeHandler {
|
|
33
|
+
#sendResponse;
|
|
34
|
+
#pendingCount = 0;
|
|
35
|
+
#queue = [];
|
|
36
|
+
#maxConcurrent;
|
|
37
|
+
#maxQueueSize;
|
|
38
|
+
#generation = 0;
|
|
39
|
+
constructor(sendResponse, maxConcurrent = 10, maxQueueSize = 50) {
|
|
40
|
+
this.#sendResponse = sendResponse;
|
|
41
|
+
this.#maxConcurrent = maxConcurrent;
|
|
42
|
+
this.#maxQueueSize = maxQueueSize;
|
|
43
|
+
}
|
|
44
|
+
static isValidRequest(data) {
|
|
45
|
+
const req = data;
|
|
46
|
+
return (typeof req?.requestId === "string" &&
|
|
47
|
+
typeof req?.query === "string" &&
|
|
48
|
+
(req.variables == null || (typeof req.variables === "object" && !Array.isArray(req.variables))));
|
|
49
|
+
}
|
|
50
|
+
rejectInvalidRequest(data) {
|
|
51
|
+
const req = data;
|
|
52
|
+
if (typeof req?.requestId !== "string") {
|
|
53
|
+
this.#sendResponse({
|
|
54
|
+
requestId: req?.requestId,
|
|
55
|
+
ok: false,
|
|
56
|
+
error: { message: "Invalid GraphQL request data" },
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const { requestId, query, variables } = req;
|
|
61
|
+
if (typeof query !== "string") {
|
|
62
|
+
this.#sendResponse({
|
|
63
|
+
requestId: requestId,
|
|
64
|
+
ok: false,
|
|
65
|
+
error: { message: "Invalid GraphQL query: query must be a string" },
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (variables != null && (typeof variables !== "object" || Array.isArray(variables))) {
|
|
70
|
+
this.#sendResponse({
|
|
71
|
+
requestId: requestId,
|
|
72
|
+
ok: false,
|
|
73
|
+
error: { message: "Invalid GraphQL variables: must be a plain object" },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
handleRequest({ requestId, query, variables }) {
|
|
78
|
+
if (this.#pendingCount >= this.#maxConcurrent) {
|
|
79
|
+
return this.#enqueueRequest(requestId, query, variables);
|
|
80
|
+
}
|
|
81
|
+
return this.#execute(requestId, query, variables);
|
|
82
|
+
}
|
|
83
|
+
reset() {
|
|
84
|
+
this.#generation++;
|
|
85
|
+
this.#queue = [];
|
|
86
|
+
this.#pendingCount = 0;
|
|
87
|
+
}
|
|
88
|
+
#execute = async (requestId, query, variables) => {
|
|
89
|
+
const gen = this.#generation;
|
|
90
|
+
this.#pendingCount++;
|
|
91
|
+
try {
|
|
92
|
+
const result = await executeGraphQL(query, variables);
|
|
93
|
+
if (gen === this.#generation) {
|
|
94
|
+
this.#sendResponse({ requestId, ok: true, result });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
if (gen === this.#generation) {
|
|
99
|
+
this.#sendResponse({
|
|
100
|
+
requestId,
|
|
101
|
+
ok: false,
|
|
102
|
+
error: { message: err?.message || "GraphQL request failed" },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
if (gen === this.#generation) {
|
|
108
|
+
this.#pendingCount--;
|
|
109
|
+
this.#processRequestQueue();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
#enqueueRequest = (requestId, query, variables) => {
|
|
114
|
+
if (this.#queue.length >= this.#maxQueueSize) {
|
|
115
|
+
this.#sendResponse({
|
|
116
|
+
requestId,
|
|
117
|
+
ok: false,
|
|
118
|
+
error: { message: "Too many queued GraphQL requests" },
|
|
119
|
+
});
|
|
120
|
+
return Promise.resolve();
|
|
121
|
+
}
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
this.#queue.push({ requestId, query, variables, done: resolve });
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
#processRequestQueue = () => {
|
|
127
|
+
if (this.#queue.length === 0 || this.#pendingCount >= this.#maxConcurrent) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const next = this.#queue.shift();
|
|
131
|
+
this.#execute(next.requestId, next.query, next.variables).then(next.done);
|
|
132
|
+
};
|
|
133
|
+
}
|
|
32
134
|
|
|
33
135
|
/**
|
|
34
136
|
* InternalHostLwcShell
|
|
@@ -52,6 +154,8 @@ var LwcShell = (function (exports, graphql) {
|
|
|
52
154
|
*
|
|
53
155
|
* See the README and the `productRegistration` demo for a full recipe.
|
|
54
156
|
*/
|
|
157
|
+
const MAX_CONCURRENT_GRAPHQL_REQUESTS = 10;
|
|
158
|
+
const MAX_GRAPHQL_QUEUE_SIZE = 50;
|
|
55
159
|
const BASE_TOKENS = ["allow-scripts", "allow-pointer-lock"];
|
|
56
160
|
const OPTIONAL_TOKENS = ["allow-downloads", "allow-forms", "allow-modals"];
|
|
57
161
|
const BLOCKED_TOKENS = ["allow-same-origin", "allow-top-navigation", "allow-popups"];
|
|
@@ -148,14 +252,16 @@ var LwcShell = (function (exports, graphql) {
|
|
|
148
252
|
_title = "Embedded widget";
|
|
149
253
|
_view = "compact";
|
|
150
254
|
_debugEnabled = true;
|
|
151
|
-
|
|
152
|
-
static MAX_CONCURRENT_GRAPHQL = 10;
|
|
255
|
+
_graphqlHandler;
|
|
153
256
|
static get observedAttributes() {
|
|
154
257
|
return ["src", "srcdoc", "sandbox", "title", "view", "debug"];
|
|
155
258
|
}
|
|
156
259
|
constructor() {
|
|
157
260
|
super();
|
|
158
261
|
this._shadow = this.attachShadow({ mode: "closed" });
|
|
262
|
+
this._graphqlHandler = new GraphQLBridgeHandler((data) => {
|
|
263
|
+
this._postToIframe("bridge-graphql-response", data);
|
|
264
|
+
}, MAX_CONCURRENT_GRAPHQL_REQUESTS, MAX_GRAPHQL_QUEUE_SIZE);
|
|
159
265
|
}
|
|
160
266
|
connectedCallback() {
|
|
161
267
|
this._renderInitial();
|
|
@@ -164,6 +270,7 @@ var LwcShell = (function (exports, graphql) {
|
|
|
164
270
|
}
|
|
165
271
|
disconnectedCallback() {
|
|
166
272
|
window.removeEventListener("message", this._handleMessage);
|
|
273
|
+
this._graphqlHandler.reset();
|
|
167
274
|
if (this._readinessTimeout) {
|
|
168
275
|
clearTimeout(this._readinessTimeout);
|
|
169
276
|
this._readinessTimeout = null;
|
|
@@ -424,12 +531,17 @@ var LwcShell = (function (exports, graphql) {
|
|
|
424
531
|
this._handleFullscreenRequest();
|
|
425
532
|
}
|
|
426
533
|
}
|
|
427
|
-
else
|
|
534
|
+
else {
|
|
428
535
|
this.dispatchEvent(new CustomEvent(eventType, { detail, bubbles: true, cancelable: true, composed: true }));
|
|
429
536
|
}
|
|
430
537
|
}
|
|
431
538
|
else if (type === "bridge-graphql-request") {
|
|
432
|
-
|
|
539
|
+
if (GraphQLBridgeHandler.isValidRequest(data)) {
|
|
540
|
+
this._graphqlHandler.handleRequest(data);
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
this._graphqlHandler.rejectInvalidRequest(data);
|
|
544
|
+
}
|
|
433
545
|
}
|
|
434
546
|
else if (type === "bridge-ready") {
|
|
435
547
|
this._handleBridgeReady();
|
|
@@ -499,57 +611,6 @@ var LwcShell = (function (exports, graphql) {
|
|
|
499
611
|
this.dispatchEvent(new CustomEvent("widget-bridge-error", { detail: errorData }));
|
|
500
612
|
this._log("bridge-error", errorData);
|
|
501
613
|
}
|
|
502
|
-
async _handleGraphQLRequest(data) {
|
|
503
|
-
if (typeof data?.requestId !== "string") {
|
|
504
|
-
this._log("_handleGraphQLRequest: invalid request data", data);
|
|
505
|
-
this._postToIframe("bridge-graphql-response", {
|
|
506
|
-
requestId: data?.requestId,
|
|
507
|
-
ok: false,
|
|
508
|
-
error: { message: "Invalid GraphQL request data" },
|
|
509
|
-
});
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
const { requestId, query, variables } = data;
|
|
513
|
-
if (typeof query !== "string") {
|
|
514
|
-
this._postToIframe("bridge-graphql-response", {
|
|
515
|
-
requestId,
|
|
516
|
-
ok: false,
|
|
517
|
-
error: { message: "Invalid GraphQL query: query must be a string" },
|
|
518
|
-
});
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
if (variables != null && (typeof variables !== "object" || Array.isArray(variables))) {
|
|
522
|
-
this._postToIframe("bridge-graphql-response", {
|
|
523
|
-
requestId,
|
|
524
|
-
ok: false,
|
|
525
|
-
error: { message: "Invalid GraphQL variables: must be a plain object" },
|
|
526
|
-
});
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
if (this._pendingGraphQLCount >= InternalHostLwcShell.MAX_CONCURRENT_GRAPHQL) {
|
|
530
|
-
this._postToIframe("bridge-graphql-response", {
|
|
531
|
-
requestId,
|
|
532
|
-
ok: false,
|
|
533
|
-
error: { message: "Too many concurrent GraphQL requests" },
|
|
534
|
-
});
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
this._pendingGraphQLCount++;
|
|
538
|
-
try {
|
|
539
|
-
const result = await executeGraphQL(query, variables);
|
|
540
|
-
this._postToIframe("bridge-graphql-response", { requestId, ok: true, result });
|
|
541
|
-
}
|
|
542
|
-
catch (err) {
|
|
543
|
-
this._postToIframe("bridge-graphql-response", {
|
|
544
|
-
requestId,
|
|
545
|
-
ok: false,
|
|
546
|
-
error: { message: err?.message || "GraphQL request failed" },
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
finally {
|
|
550
|
-
this._pendingGraphQLCount--;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
614
|
_handleContainerClick() {
|
|
554
615
|
// placeholder
|
|
555
616
|
}
|
|
@@ -587,6 +648,7 @@ var LwcShell = (function (exports, graphql) {
|
|
|
587
648
|
return;
|
|
588
649
|
// reset tracking
|
|
589
650
|
this._bridgeReady = false;
|
|
651
|
+
this._graphqlHandler.reset();
|
|
590
652
|
this._currentState = STATES.LOADING;
|
|
591
653
|
this._updateContainerState();
|
|
592
654
|
// clear existing
|
package/dist/index.iife.prod.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @salesforce/experimental-mfe-lwc-shell v2.2.
|
|
2
|
-
var LwcShell=function(e,t){"use strict";
|
|
1
|
+
/*! @salesforce/experimental-mfe-lwc-shell v2.2.1-rc.1 (2026-04-09) */
|
|
2
|
+
var LwcShell=function(e,t){"use strict";class s{#e;#t=0;#s=[];#i;#n;#a=0;constructor(e,t=10,s=50){this.#e=e,this.#i=t,this.#n=s}static isValidRequest(e){const t=e;return"string"==typeof t?.requestId&&"string"==typeof t?.query&&(null==t.variables||"object"==typeof t.variables&&!Array.isArray(t.variables))}rejectInvalidRequest(e){const t=e;if("string"!=typeof t?.requestId)return void this.#e({requestId:t?.requestId,ok:!1,error:{message:"Invalid GraphQL request data"}});const{requestId:s,query:i,variables:n}=t;"string"==typeof i?null==n||"object"==typeof n&&!Array.isArray(n)||this.#e({requestId:s,ok:!1,error:{message:"Invalid GraphQL variables: must be a plain object"}}):this.#e({requestId:s,ok:!1,error:{message:"Invalid GraphQL query: query must be a string"}})}handleRequest({requestId:e,query:t,variables:s}){return this.#t>=this.#i?this.#r(e,t,s):this.#l(e,t,s)}reset(){this.#a++,this.#s=[],this.#t=0}#l=async(e,s,i)=>{const n=this.#a;this.#t++;try{const a=await async function(e,s){if("string"!=typeof e||""===e.trim())throw new Error("Invalid GraphQL query: query must be a non-empty string.");const i=t.gql`
|
|
3
3
|
${e}
|
|
4
|
-
`,a=await t.unstable_graphql_imperative({query:i,variables:s});return{data:a?.data,errors:a?.errors}}(i,a);this._postToIframe("bridge-graphql-response",{requestId:s,ok:!0,result:e})}catch(e){this._postToIframe("bridge-graphql-response",{requestId:s,ok:!1,error:{message:e?.message||"GraphQL request failed"}})}finally{this._pendingGraphQLCount--}}else this._postToIframe("bridge-graphql-response",{requestId:s,ok:!1,error:{message:"Invalid GraphQL variables: must be a plain object"}});else this._postToIframe("bridge-graphql-response",{requestId:s,ok:!1,error:{message:"Invalid GraphQL query: query must be a string"}})}_handleContainerClick(){}_applySandbox(){const e=this._iframe;if(!e)return;const t=this._computeSandboxTokens();e.setAttribute("sandbox",t),this._log("sandbox",t)}_computeSandboxTokens(){const e=[...s];if(this._sandbox){String(this._sandbox).split(/\s+/).filter(Boolean).forEach(t=>{i.includes(t)&&!e.includes(t)&&e.push(t),a.includes(t)&&this.dispatchEvent(new CustomEvent("security-violation",{detail:{type:"blocked-sandbox-token",token:t,element:this}}))})}return e.join(" ")}_updateIframeSrc(){const e=this._iframe;if(e){if(this._bridgeReady=!1,this._currentState=n,this._updateContainerState(),e.removeAttribute("src"),e.removeAttribute("srcdoc"),this._src)e.setAttribute("src",this._src);else if(this._srcdoc)try{e.setAttribute("srcdoc",this._srcdoc)}catch{e.setAttribute("src","about:blank")}e.onload=this._handleIframeLoad,e.onerror=this._handleIframeError,this._log("updateIframeSrc",this._src?"src":this._srcdoc?"srcdoc":"blank")}}_handleIframeLoad=()=>{this.dispatchEvent(new CustomEvent("iframe-loaded",{detail:{element:this},bubbles:!0})),this._log("iframe-loaded"),this._readinessTimeout=setTimeout(()=>{this._currentState!==r&&(this.dispatchEvent(new CustomEvent("widget-readiness-warning",{detail:{element:this,message:"Widget may not be using Bridge for readiness signaling"},bubbles:!0})),this._log("widget-readiness-warning"))},3e3)};_handleIframeError=e=>{this.dispatchEvent(new CustomEvent("widget-error",{detail:{error:e,element:this}}))};_postToIframe(e,t){const s=this._iframe?.contentWindow;if(s)try{const i=`salesforce-${e}`;s.postMessage({type:i,data:t,id:this._shellInstanceId},"*"),this._log("postMessage",i,t)}catch(e){this._log("postMessage error",e instanceof Error?e.message:String(e))}}_collectDataAttributes(){const e={};for(let t=0;t<this.attributes.length;t++){const s=this.attributes[t];if(s.name.startsWith("data-")){e[s.name.replace(/^data-/,"").replace(/-([a-z])/g,(e,t)=>t.toUpperCase())]=s.value}}return e}_sendInitialTheme(){const e=getComputedStyle(this),t={};for(let s=0;s<e.length;s++){const i=e[s];if(i.startsWith("--")){const s=e.getPropertyValue(i).trim();s&&(t[i]=s)}}this._lastThemeData=t,this._bridgeReady?(this._postToIframe("theme",t),this._hasSentTheme=!0,this._log("send theme",t)):this._log("queue theme until bridge ready",t)}_sendInitialData(){this._lastThemeData&&Object.keys(this._lastThemeData).length&&(this._postToIframe("theme",this._lastThemeData),this._hasSentTheme=!0,this._log("send theme (initial)",this._lastThemeData));const e=this._collectDataAttributes();this._lastPayloadData={...e},this._postToIframe("data",this._lastPayloadData),this._hasSentData=!0,this._log("send data (initial)",this._lastPayloadData)}}return window.customElements.get("lwc-shell")||window.customElements.define("lwc-shell",l),e.InternalHostLwcShell=l,e.default=l,Object.defineProperty(e,"__esModule",{value:!0}),e}({},"undefined"!=typeof lightningGraphql?lightningGraphql:{});
|
|
4
|
+
`,n=await t.unstable_graphql_imperative({query:i,variables:s});return{data:n?.data,errors:n?.errors}}(s,i);n===this.#a&&this.#e({requestId:e,ok:!0,result:a})}catch(t){n===this.#a&&this.#e({requestId:e,ok:!1,error:{message:t?.message||"GraphQL request failed"}})}finally{n===this.#a&&(this.#t--,this.#o())}};#r=(e,t,s)=>this.#s.length>=this.#n?(this.#e({requestId:e,ok:!1,error:{message:"Too many queued GraphQL requests"}}),Promise.resolve()):new Promise(i=>{this.#s.push({requestId:e,query:t,variables:s,done:i})});#o=()=>{if(0===this.#s.length||this.#t>=this.#i)return;const e=this.#s.shift();this.#l(e.requestId,e.query,e.variables).then(e.done)}}const i=["allow-scripts","allow-pointer-lock"],n=["allow-downloads","allow-forms","allow-modals"],a=["allow-same-origin","allow-top-navigation","allow-popups"],r="state-loading",l="state-loaded";class o extends HTMLElement{_shadow;_iframe=null;_container=null;_currentState=r;_readinessTimeout=null;_isFullscreen=!1;_preFullscreenHeight="";_lastThemeData={};_lastPayloadData={};_bridgeReady=!1;_shellInstanceId=function(){return Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(36)}();_hasSentTheme=!1;_hasSentData=!1;_src=null;_srcdoc=null;_sandbox=null;_title="Embedded widget";_view="compact";_debugEnabled=!0;_graphqlHandler;static get observedAttributes(){return["src","srcdoc","sandbox","title","view","debug"]}constructor(){super(),this._shadow=this.attachShadow({mode:"closed"}),this._graphqlHandler=new s(e=>{this._postToIframe("bridge-graphql-response",e)},10,50)}connectedCallback(){this._renderInitial(),this._setupMessageListener(),this._log("connectedCallback")}disconnectedCallback(){window.removeEventListener("message",this._handleMessage),this._graphqlHandler.reset(),this._readinessTimeout&&(clearTimeout(this._readinessTimeout),this._readinessTimeout=null),this._log("disconnectedCallback")}attributeChangedCallback(e,t,s){if(t!==s)switch(e){case"src":this.src=s;break;case"srcdoc":this.srcdoc=s;break;case"sandbox":this.sandbox=s;break;case"title":this.title=s;break;case"view":this.view=s;break;case"debug":this.debug=null!==s}}get src(){return this._src}set src(e){const t=e||null;this._src!==t&&(this._src=t,null!==t?this.setAttribute("src",t):this.removeAttribute("src"),this._updateIframeSrc())}get srcdoc(){return this._srcdoc}set srcdoc(e){const t=e||null;this._srcdoc!==t&&(this._srcdoc=t,null!==t?this.setAttribute("srcdoc",t):this.removeAttribute("srcdoc"),this._updateIframeSrc())}get sandbox(){return this._sandbox}set sandbox(e){const t=e||null;this._sandbox!==t&&(this._sandbox=t,null!==t?this.setAttribute("sandbox",t):this.removeAttribute("sandbox"),this._applySandbox())}get title(){return this._title}set title(e){const t=e||"Embedded widget";this._title!==t&&(this._title=t,this.setAttribute("title",t),this._updateTitle())}get view(){return this._view}set view(e){const t="full"===e?"full":"compact";this._view!==t&&(this._view=t,this.setAttribute("view",t),this._updateViewDOM())}get debug(){return this._debugEnabled}set debug(e){const t=!!e;this._debugEnabled!==t&&(this._debugEnabled=t,t?this.setAttribute("debug",""):this.removeAttribute("debug"))}updateData(e){if(!e||"object"!=typeof e)return;Object.entries(e).forEach(([e,t])=>{const s=`data-${String(e).replace(/[A-Z]/g,"-$&").toLowerCase()}`;this.setAttribute(s,String(t))});const t=this._collectDataAttributes();this._lastPayloadData=t,this._bridgeReady?(this._postToIframe("data",t),this._hasSentData=!0,this._log("send data",t)):this._log("queue data until bridge ready",t)}refreshTheme(){this._sendInitialTheme()}get _isFullView(){return"full"===this._view}get _frameClass(){return this._isFullView?"frame frameFull":"frame"}_log(...e){this._debugEnabled&&console.log("[InternalHostLwcShell]",JSON.stringify(e,null,2))}_renderInitial(){const e=this._shadow;e.innerHTML=`\n <style>\n:host {\n display: block;\n position: relative;\n height: 100%;\n overflow: auto;\n box-sizing: border-box;\n}\n\n.container {\n width: 100%;\n height: 100%;\n position: relative;\n}\n\n.frame {\n visibility: hidden;\n display: block;\n width: 100%;\n height: 100%;\n border: none;\n}\n\n.container[data-state='state-loaded'] .frame {\n visibility: visible;\n}\n\n/* Fullscreen overlay */\n.overlayBackdrop {\n position: fixed;\n inset: 0;\n width: 100vw;\n height: 100vh;\n z-index: 9998;\n background: rgba(0, 0, 0, 0.8);\n backdrop-filter: blur(4px);\n}\n\n.overlayClose {\n position: fixed;\n top: 24px;\n right: 24px;\n z-index: 9999;\n width: 32px;\n height: 32px;\n background: rgba(0, 0, 0, 0.7);\n color: #fff;\n border-radius: 50%;\n border: none;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: bold;\n cursor: pointer;\n}\n\n.frameFull {\n position: fixed;\n inset: 0;\n width: 90vw;\n height: 90vh !important;\n margin: 5vh 5vw;\n z-index: 10000;\n background: #fff;\n border-radius: 8px;\n box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);\n}\n</style>\n <div class="container" data-state="${this._currentState}"\n tabindex="0" role="region" aria-label="${this._title}">\n <iframe\n class="${this._frameClass}"\n title="${this._title}"\n aria-label="Interactive widget content"\n ></iframe>\n </div>\n `,this._container=e.querySelector(".container"),this._iframe=e.querySelector("iframe"),this._container.addEventListener("click",()=>this._handleContainerClick()),this._applySandbox(),this._updateIframeSrc(),this._sendInitialTheme(),this._log("renderInitial: iframe ready")}_updateContainerState(){this._container&&this._container.setAttribute("data-state",this._currentState)}_updateTitle(){this._iframe&&this._iframe.setAttribute("title",this._title),this._container&&this._container.setAttribute("aria-label",this._title),this._log("title",this._title)}_updateViewDOM(){if(this._iframe&&(this._iframe.className=this._frameClass),!this._container)return;const e=this._container.querySelector(".overlayBackdrop"),t=this._container.querySelector(".overlayClose");if(e&&e.remove(),t&&t.remove(),this._isFullView){const e=document.createElement("div");e.className="overlayBackdrop",e.setAttribute("aria-hidden","true");const t=document.createElement("button");t.className="overlayClose",t.type="button",t.setAttribute("aria-label","Close fullscreen"),t.textContent="✕",t.addEventListener("click",this._exitFullscreen),this._container.appendChild(e),this._container.appendChild(t)}}_setState(e){this._currentState=e,this._updateContainerState(),e===l&&this._sendInitialData()}_setupMessageListener(){window.addEventListener("message",this._handleMessage)}_handleMessage=e=>{const t=this._iframe?.contentWindow;if(e.source!==t)return;const i=e.data||{},{type:n,data:a,id:r}=i;if(r?r===this._shellInstanceId:"bridge-ready"===n)if(this._log("receive",n,a),"bridge-event"===n){const{eventType:e,detail:t}=a||{};if(this._log("bridge-event",e,t),"resize"===e)this._handleResize(t);else{if("widget-ready"!==e)throw new RangeError(`Invalid bridge event ${e}`);this._handleWidgetReady()}}else if("custom-event"===n){const{eventType:e,detail:t}=a||{};if(this._log("custom-event",e,t),"fullscreen-request"===e){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,cancelable:!0}))&&this._handleFullscreenRequest()}else this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,cancelable:!0,composed:!0}))}else"bridge-graphql-request"===n?s.isValidRequest(a)?this._graphqlHandler.handleRequest(a):this._graphqlHandler.rejectInvalidRequest(a):"bridge-ready"===n?this._handleBridgeReady():"bridge-error"===n&&this._handleBridgeError(a)};_handleResize({height:e}){const t=new CustomEvent("resize",{detail:{height:e},cancelable:!0});this.dispatchEvent(t),t.defaultPrevented||this._isFullscreen||"number"!=typeof e||!Number.isFinite(e)||(this._iframe&&(this._iframe.style.height=e+"px"),this._log("applied resize",e))}_handleWidgetReady(){this._readinessTimeout&&(clearTimeout(this._readinessTimeout),this._readinessTimeout=null),this.dispatchEvent(new CustomEvent("widget-ready",{bubbles:!0})),this._log("widget-ready")}_handleFullscreenRequest(){this._isFullscreen||this._enterFullscreen()}_enterFullscreen(){this._isFullscreen=!0,this._preFullscreenHeight=this._iframe?.style.height??"",this._view="full",this._updateViewDOM(),this._iframe&&(this._iframe.style.height="100%"),this.updateData({view:"full"}),this.dispatchEvent(new CustomEvent("fullscreen-entered",{detail:{element:this},bubbles:!0})),this._log("enter fullscreen")}_exitFullscreen=()=>{this._isFullscreen=!1,this._view="compact",this._updateViewDOM(),this._iframe&&(this._iframe.style.height=this._preFullscreenHeight),this.updateData({view:"compact"}),this.dispatchEvent(new CustomEvent("fullscreen-exited",{detail:{element:this},bubbles:!0})),this._log("exit fullscreen")};_handleBridgeReady(){this._bridgeReady=!0,this._postToIframe("shell-ready"),this._setState(l)}_handleBridgeError(e){this.dispatchEvent(new CustomEvent("widget-bridge-error",{detail:e})),this._log("bridge-error",e)}_handleContainerClick(){}_applySandbox(){const e=this._iframe;if(!e)return;const t=this._computeSandboxTokens();e.setAttribute("sandbox",t),this._log("sandbox",t)}_computeSandboxTokens(){const e=[...i];if(this._sandbox){String(this._sandbox).split(/\s+/).filter(Boolean).forEach(t=>{n.includes(t)&&!e.includes(t)&&e.push(t),a.includes(t)&&this.dispatchEvent(new CustomEvent("security-violation",{detail:{type:"blocked-sandbox-token",token:t,element:this}}))})}return e.join(" ")}_updateIframeSrc(){const e=this._iframe;if(e){if(this._bridgeReady=!1,this._graphqlHandler.reset(),this._currentState=r,this._updateContainerState(),e.removeAttribute("src"),e.removeAttribute("srcdoc"),this._src)e.setAttribute("src",this._src);else if(this._srcdoc)try{e.setAttribute("srcdoc",this._srcdoc)}catch{e.setAttribute("src","about:blank")}e.onload=this._handleIframeLoad,e.onerror=this._handleIframeError,this._log("updateIframeSrc",this._src?"src":this._srcdoc?"srcdoc":"blank")}}_handleIframeLoad=()=>{this.dispatchEvent(new CustomEvent("iframe-loaded",{detail:{element:this},bubbles:!0})),this._log("iframe-loaded"),this._readinessTimeout=setTimeout(()=>{this._currentState!==l&&(this.dispatchEvent(new CustomEvent("widget-readiness-warning",{detail:{element:this,message:"Widget may not be using Bridge for readiness signaling"},bubbles:!0})),this._log("widget-readiness-warning"))},3e3)};_handleIframeError=e=>{this.dispatchEvent(new CustomEvent("widget-error",{detail:{error:e,element:this}}))};_postToIframe(e,t){const s=this._iframe?.contentWindow;if(s)try{const i=`salesforce-${e}`;s.postMessage({type:i,data:t,id:this._shellInstanceId},"*"),this._log("postMessage",i,t)}catch(e){this._log("postMessage error",e instanceof Error?e.message:String(e))}}_collectDataAttributes(){const e={};for(let t=0;t<this.attributes.length;t++){const s=this.attributes[t];if(s.name.startsWith("data-")){e[s.name.replace(/^data-/,"").replace(/-([a-z])/g,(e,t)=>t.toUpperCase())]=s.value}}return e}_sendInitialTheme(){const e=getComputedStyle(this),t={};for(let s=0;s<e.length;s++){const i=e[s];if(i.startsWith("--")){const s=e.getPropertyValue(i).trim();s&&(t[i]=s)}}this._lastThemeData=t,this._bridgeReady?(this._postToIframe("theme",t),this._hasSentTheme=!0,this._log("send theme",t)):this._log("queue theme until bridge ready",t)}_sendInitialData(){this._lastThemeData&&Object.keys(this._lastThemeData).length&&(this._postToIframe("theme",this._lastThemeData),this._hasSentTheme=!0,this._log("send theme (initial)",this._lastThemeData));const e=this._collectDataAttributes();this._lastPayloadData={...e},this._postToIframe("data",this._lastPayloadData),this._hasSentData=!0,this._log("send data (initial)",this._lastPayloadData)}}return window.customElements.get("lwc-shell")||window.customElements.define("lwc-shell",o),e.InternalHostLwcShell=o,e.default=o,Object.defineProperty(e,"__esModule",{value:!0}),e}({},"undefined"!=typeof lightningGraphql?lightningGraphql:{});
|
|
5
5
|
//# sourceMappingURL=index.iife.prod.js.map
|
package/package.json
CHANGED
package/dist/graphqlHandler.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
dirtyStateModal template — shipped with @lightning-out/lwc-shell.
|
|
3
|
-
|
|
4
|
-
This template is entirely generic / config-driven:
|
|
5
|
-
- Header label, body description, and footer buttons are all bound to
|
|
6
|
-
the `modalFields` @api property — nothing is hard-coded here.
|
|
7
|
-
- The concrete values (e.g. "Unsaved Changes", "Discard Changes" button)
|
|
8
|
-
are defined in InternalHostLwcShell.ts inside the
|
|
9
|
-
`[DoCloseConfirmation]()` method and passed at open-time.
|
|
10
|
-
- To change what the modal displays, update the config object in
|
|
11
|
-
InternalHostLwcShell.ts, NOT this template.
|
|
12
|
-
|
|
13
|
-
Customers: copy this file (along with .js and .js-meta.xml) into your
|
|
14
|
-
SFDX project's lwc/dirtyStateModal/ folder and deploy to your org.
|
|
15
|
-
-->
|
|
16
|
-
<template>
|
|
17
|
-
<lightning-modal-header label={modalFields.label}></lightning-modal-header>
|
|
18
|
-
<lightning-modal-body>
|
|
19
|
-
<p>{modalFields.description}</p>
|
|
20
|
-
</lightning-modal-body>
|
|
21
|
-
<lightning-modal-footer>
|
|
22
|
-
<template for:each={modalFields.buttons} for:item="button">
|
|
23
|
-
<lightning-button
|
|
24
|
-
key={button.buttonKey}
|
|
25
|
-
label={button.buttonLabel}
|
|
26
|
-
variant={button.buttonVariant}
|
|
27
|
-
data-key={button.buttonKey}
|
|
28
|
-
onclick={handleButtonClick}
|
|
29
|
-
class="slds-m-right_x-small"
|
|
30
|
-
></lightning-button>
|
|
31
|
-
</template>
|
|
32
|
-
</lightning-modal-footer>
|
|
33
|
-
</template>
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dirtyStateModal — Generic, config-driven LWC modal shipped with
|
|
3
|
-
* @lightning-out/lwc-shell.
|
|
4
|
-
*
|
|
5
|
-
* IMPORTANT: This modal is intentionally **generic**. It does NOT hard-code
|
|
6
|
-
* any specific labels, descriptions, or button definitions. Instead, it
|
|
7
|
-
* receives all of its display configuration through the `modalFields` @api
|
|
8
|
-
* property at open-time. This makes it reusable for any confirmation
|
|
9
|
-
* scenario — the caller decides what the modal says and which buttons it
|
|
10
|
-
* shows.
|
|
11
|
-
*
|
|
12
|
-
* The **concrete configuration** for the current lwc-shell dirty-state
|
|
13
|
-
* flow lives in `InternalHostLwcShell.ts` inside the
|
|
14
|
-
* `[DoCloseConfirmation]()` method, where the `dirtStateModalConfig` object
|
|
15
|
-
* is built and passed to `DirtyStateModal.open(dirtStateModalConfig)`.
|
|
16
|
-
* That config currently specifies:
|
|
17
|
-
* - label: "Unsaved Changes"
|
|
18
|
-
* - description: "You have unsaved changes. If you leave, your changes
|
|
19
|
-
* will be lost."
|
|
20
|
-
* - buttons: "Cancel" and "Discard Changes" (brand variant)
|
|
21
|
-
* - size: "small"
|
|
22
|
-
*
|
|
23
|
-
* If you need to change the wording, add/remove buttons, or adjust the
|
|
24
|
-
* modal size, update the config object in `InternalHostLwcShell.ts` —
|
|
25
|
-
* **not** in this component.
|
|
26
|
-
*
|
|
27
|
-
* ---
|
|
28
|
-
* Deployment: customers must copy this component from the lwc-shell
|
|
29
|
-
* package's `dist/lwc/dirtyStateModal/` folder into their own SFDX
|
|
30
|
-
* project and deploy it to their Salesforce org.
|
|
31
|
-
*
|
|
32
|
-
* Usage in wrapper LWC:
|
|
33
|
-
* import DirtyStateModal from 'c/dirtyStateModal';
|
|
34
|
-
* shell.dirtyStateModal = DirtyStateModal;
|
|
35
|
-
*/
|
|
36
|
-
import LightningModal from "lightning/modal";
|
|
37
|
-
import { api } from "lwc";
|
|
38
|
-
|
|
39
|
-
export default class DirtyStateModal extends LightningModal {
|
|
40
|
-
/**
|
|
41
|
-
* Generic configuration object — NOT owned by this component.
|
|
42
|
-
*
|
|
43
|
-
* The actual values are supplied by the caller (InternalHostLwcShell)
|
|
44
|
-
* at open-time via `DirtyStateModal.open({ modalFields: { ... } })`.
|
|
45
|
-
* The defaults below are minimal fallbacks; the real config (label,
|
|
46
|
-
* description, full button set) is defined in InternalHostLwcShell's
|
|
47
|
-
* `[DoCloseConfirmation]()` method.
|
|
48
|
-
*
|
|
49
|
-
* Shape:
|
|
50
|
-
* {
|
|
51
|
-
* label: string, // Modal header text
|
|
52
|
-
* description: string, // Modal body text
|
|
53
|
-
* buttons: [ // Footer buttons (rendered dynamically)
|
|
54
|
-
* {
|
|
55
|
-
* buttonKey: string, // Unique key returned on click
|
|
56
|
-
* buttonLabel: string, // Visible label
|
|
57
|
-
* buttonVariant: string // Optional SLDS variant (e.g. 'brand')
|
|
58
|
-
* }
|
|
59
|
-
* ]
|
|
60
|
-
* }
|
|
61
|
-
*/
|
|
62
|
-
@api modalFields = {
|
|
63
|
-
label: "Unsaved Changes",
|
|
64
|
-
description: "You have unsaved changes.",
|
|
65
|
-
buttons: [
|
|
66
|
-
{
|
|
67
|
-
buttonKey: "cancel",
|
|
68
|
-
buttonLabel: "Cancel",
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Generic button handler — closes the modal and returns the clicked
|
|
75
|
-
* button's `buttonKey` to the caller's `.open()` promise so the caller
|
|
76
|
-
* (InternalHostLwcShell) can decide what action to take.
|
|
77
|
-
*/
|
|
78
|
-
handleButtonClick(event) {
|
|
79
|
-
const key = event.target.dataset.key;
|
|
80
|
-
this.close(key);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { DoCloseConfirmation, Guid } from "./symbols";
|
|
2
|
-
/**
|
|
3
|
-
* Symbol-keyed contract for components that self-manage their dirty state.
|
|
4
|
-
*
|
|
5
|
-
* - `DoCloseConfirmation` — The component **must** define this as a function
|
|
6
|
-
* that returns a `Promise` resolving to one of:
|
|
7
|
-
* - `'SUCCESS'` – dirty state resolved, tab closure should continue.
|
|
8
|
-
* - `'CANCEL'` – the user cancelled the operation.
|
|
9
|
-
* - `'ERROR'` – a problem occurred resolving the dirty state.
|
|
10
|
-
*
|
|
11
|
-
* If the promise resolves to `'SUCCESS'`, the component **must** have
|
|
12
|
-
* already dispatched a `'privatelightningselfmanageddirtystatechanged'`
|
|
13
|
-
* event with `isUnsaved: false` to clear its dirty state.
|
|
14
|
-
*
|
|
15
|
-
* - `Guid` — Optionally, the component can define this as a property
|
|
16
|
-
* containing a globally unique string. If undefined, a GUID will be
|
|
17
|
-
* generated automatically.
|
|
18
|
-
*/
|
|
19
|
-
export declare const SelfManagedDirtyComponent: Readonly<{
|
|
20
|
-
DoCloseConfirmation: typeof DoCloseConfirmation;
|
|
21
|
-
Guid: typeof Guid;
|
|
22
|
-
}>;
|
|
23
|
-
//# sourceMappingURL=dirtyStateModal.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dirtyStateModal.d.ts","sourceRoot":"","sources":["../../src/utils/dirtyStateModal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAAC;IAC7C,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,IAAI,EAAE,OAAO,IAAI,CAAC;CACrB,CAGS,CAAC"}
|
package/dist/utils/index.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utils barrel — re-exports all utility modules used by lwc-shell.
|
|
3
|
-
*
|
|
4
|
-
* This folder is designed to grow as new features are added (e.g. additional
|
|
5
|
-
* state managers, analytics hooks). Each feature should get its own module
|
|
6
|
-
* and be re-exported from this index.
|
|
7
|
-
*
|
|
8
|
-
* Currently provides:
|
|
9
|
-
* - Dirty-state management symbols and helpers (`dirtyStateModal`, `symbols`).
|
|
10
|
-
*/
|
|
11
|
-
export { SelfManagedDirtyComponent } from "./dirtyStateModal";
|
|
12
|
-
export { DoCloseConfirmation, Guid } from "./symbols";
|
|
13
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/utils/symbols.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Symbols used as property / method keys for the dirty-state contract.
|
|
3
|
-
*
|
|
4
|
-
* These symbols are attached to the host LWC component by `<lwc-shell>`
|
|
5
|
-
* (via the `hostComponent` setter) so that Salesforce's tab-close flow can
|
|
6
|
-
* invoke `DoCloseConfirmation` and identify the component by `Guid`.
|
|
7
|
-
*
|
|
8
|
-
* See `InternalHostLwcShell.hostComponent` setter for wiring details,
|
|
9
|
-
* and the `SelfManagedDirtyComponent` object in `dirtyStateModal.ts` for
|
|
10
|
-
* the full contract description.
|
|
11
|
-
*/
|
|
12
|
-
export declare const Guid: unique symbol;
|
|
13
|
-
export declare const DoCloseConfirmation: unique symbol;
|
|
14
|
-
/**
|
|
15
|
-
* Generic constructor type used by the mixin.
|
|
16
|
-
*/
|
|
17
|
-
export type Constructor<T = HTMLElement> = new (...args: any[]) => T;
|
|
18
|
-
//# sourceMappingURL=symbols.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"symbols.d.ts","sourceRoot":"","sources":["../../src/utils/symbols.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,IAAI,EAAE,OAAO,MAAuB,CAAC;AAClD,eAAO,MAAM,mBAAmB,EAAE,OAAO,MAAsC,CAAC;AAEhF;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,WAAW,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC"}
|