@lightning-out/lwc-shell 2.2.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,801 @@
1
+ /*! @lightning-out/lwc-shell v2.2.0-rc.1 (2026-02-27) */
2
+ var LwcShell = (function (exports) {
3
+ 'use strict';
4
+
5
+ /**
6
+ * EmbeddingResizer - Handles dynamic iframe/container resizing
7
+ * Uses ResizeObserver to monitor element size changes and notify the host
8
+ */
9
+ /**
10
+ * Generates a pseudo-random alphanumeric identifier string for unique
11
+ * element IDs or temporary identifiers (not cryptographically secure).
12
+ *
13
+ * @returns Random alphanumeric string
14
+ *
15
+ * @example
16
+ * getUUID(); // 'k2j8f5l9m'
17
+ */
18
+ function getUUID() {
19
+ return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36);
20
+ }
21
+
22
+ /**
23
+ * Symbols used as property / method keys for the dirty-state contract.
24
+ *
25
+ * These symbols are attached to the host LWC component by `<lwc-shell>`
26
+ * (via the `hostComponent` setter) so that Salesforce's tab-close flow can
27
+ * invoke `DoCloseConfirmation` and identify the component by `Guid`.
28
+ *
29
+ * See `InternalHostLwcShell.hostComponent` setter for wiring details,
30
+ * and the `SelfManagedDirtyComponent` object in `dirtyStateModal.ts` for
31
+ * the full contract description.
32
+ */
33
+ const Guid = Symbol("Guid");
34
+ const DoCloseConfirmation = Symbol("DoCloseConfirmation");
35
+
36
+ /**
37
+ * Symbol-keyed contract for components that self-manage their dirty state.
38
+ *
39
+ * - `DoCloseConfirmation` — The component **must** define this as a function
40
+ * that returns a `Promise` resolving to one of:
41
+ * - `'SUCCESS'` – dirty state resolved, tab closure should continue.
42
+ * - `'CANCEL'` – the user cancelled the operation.
43
+ * - `'ERROR'` – a problem occurred resolving the dirty state.
44
+ *
45
+ * If the promise resolves to `'SUCCESS'`, the component **must** have
46
+ * already dispatched a `'privatelightningselfmanageddirtystatechanged'`
47
+ * event with `isUnsaved: false` to clear its dirty state.
48
+ *
49
+ * - `Guid` — Optionally, the component can define this as a property
50
+ * containing a globally unique string. If undefined, a GUID will be
51
+ * generated automatically.
52
+ */
53
+ const SelfManagedDirtyComponent = {
54
+ DoCloseConfirmation,
55
+ Guid,
56
+ };
57
+
58
+ /**
59
+ * InternalHostLwcShell
60
+ *
61
+ * A standard Web Component (custom element) that embeds an iframe-based widget
62
+ * with bridge communication, sandbox management, fullscreen support, and
63
+ * dirty-state tracking.
64
+ *
65
+ * Registered as `<lwc-shell>`.
66
+ *
67
+ * Ported from the LWC `widgetContainerLo` component (formerly known as
68
+ * WidgetContainer) — all Lightning Web Component framework dependencies have
69
+ * been removed so this can run as a plain custom element inside any LWC host.
70
+ *
71
+ * **How customers use this:**
72
+ * 1. The build produces `dist/index.esm.js` which bundles this class.
73
+ * 2. Customers copy that file into their SFDX project as an LWC entity
74
+ * (e.g. `c/lwcShell`) and deploy it to their Salesforce org.
75
+ * 3. A wrapper LWC imports `'c/lwcShell'` and creates `<lwc-shell>`
76
+ * imperatively via `document.createElement('lwc-shell')`.
77
+ *
78
+ * See the README and the `productRegistrationWidgetLo` demo for a full recipe.
79
+ */
80
+ const BASE_TOKENS = ["allow-scripts", "allow-pointer-lock"];
81
+ const OPTIONAL_TOKENS = ["allow-downloads", "allow-forms", "allow-modals"];
82
+ const BLOCKED_TOKENS = ["allow-same-origin", "allow-top-navigation", "allow-popups"];
83
+ const STATES = {
84
+ LOADING: "state-loading",
85
+ LOADED: "state-loaded",
86
+ };
87
+ const STYLES = /* css */ `
88
+ :host {
89
+ display: block;
90
+ position: relative;
91
+ min-height: 100px;
92
+ border: 1px solid #ddd;
93
+ border-radius: 4px;
94
+ overflow: hidden;
95
+ }
96
+
97
+ .container {
98
+ width: 100%;
99
+ height: 100%;
100
+ position: relative;
101
+ }
102
+
103
+ .frame {
104
+ visibility: hidden;
105
+ display: block;
106
+ width: 100%;
107
+ height: 100%;
108
+ border: none;
109
+ }
110
+
111
+ .container[data-state='state-loaded'] .frame {
112
+ visibility: visible;
113
+ }
114
+
115
+ /* Fullscreen overlay */
116
+ .overlayBackdrop {
117
+ position: fixed;
118
+ inset: 0;
119
+ width: 100vw;
120
+ height: 100vh;
121
+ z-index: 9998;
122
+ background: rgba(0, 0, 0, 0.8);
123
+ backdrop-filter: blur(4px);
124
+ }
125
+
126
+ .overlayClose {
127
+ position: fixed;
128
+ top: 24px;
129
+ right: 24px;
130
+ z-index: 9999;
131
+ width: 32px;
132
+ height: 32px;
133
+ background: rgba(0, 0, 0, 0.7);
134
+ color: #fff;
135
+ border-radius: 50%;
136
+ border: none;
137
+ display: flex;
138
+ align-items: center;
139
+ justify-content: center;
140
+ font-size: 16px;
141
+ font-weight: bold;
142
+ cursor: pointer;
143
+ }
144
+
145
+ .frameFull {
146
+ position: fixed;
147
+ inset: 0;
148
+ width: 90vw;
149
+ height: 90vh !important;
150
+ margin: 5vh 5vw;
151
+ z-index: 10000;
152
+ background: #fff;
153
+ border-radius: 8px;
154
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
155
+ }
156
+ `;
157
+ class InternalHostLwcShell extends HTMLElement {
158
+ _shadow;
159
+ _iframe = null;
160
+ _container = null;
161
+ _currentState = STATES.LOADING;
162
+ _hasExplicitResize = false;
163
+ _readinessTimeout = null;
164
+ _isFullscreen = false;
165
+ _lastThemeData = {};
166
+ _lastPayloadData = {};
167
+ _bridgeReady = false;
168
+ _hasSentTheme = false;
169
+ _hasSentData = false;
170
+ _src = null;
171
+ _srcdoc = null;
172
+ _sandbox = null;
173
+ _title = "Embedded widget";
174
+ _view = "compact";
175
+ _dirtyComponentGuid = getUUID();
176
+ _beforeUnloadHandler = null;
177
+ _dirtyStateModal = null;
178
+ _pendingDirtyActions = new Map();
179
+ _debugEnabled = true;
180
+ static get observedAttributes() {
181
+ return ["src", "srcdoc", "sandbox", "title", "view", "debug"];
182
+ }
183
+ constructor() {
184
+ super();
185
+ this._shadow = this.attachShadow({ mode: "closed" });
186
+ }
187
+ connectedCallback() {
188
+ this._renderInitial();
189
+ this._setupMessageListener();
190
+ this._log("connectedCallback");
191
+ }
192
+ disconnectedCallback() {
193
+ window.removeEventListener("message", this._handleMessage);
194
+ if (this._readinessTimeout) {
195
+ clearTimeout(this._readinessTimeout);
196
+ this._readinessTimeout = null;
197
+ }
198
+ this._log("disconnectedCallback");
199
+ }
200
+ attributeChangedCallback(name, oldVal, newVal) {
201
+ if (oldVal === newVal)
202
+ return;
203
+ switch (name) {
204
+ case "src":
205
+ this.src = newVal;
206
+ break;
207
+ case "srcdoc":
208
+ this.srcdoc = newVal;
209
+ break;
210
+ case "sandbox":
211
+ this.sandbox = newVal;
212
+ break;
213
+ case "title":
214
+ this.title = newVal;
215
+ break;
216
+ case "view":
217
+ this.view = newVal;
218
+ break;
219
+ case "debug":
220
+ this.debug = newVal !== null;
221
+ break;
222
+ }
223
+ }
224
+ get src() {
225
+ return this._src;
226
+ }
227
+ set src(v) {
228
+ const val = v || null;
229
+ if (this._src === val)
230
+ return;
231
+ this._src = val;
232
+ if (val !== null) {
233
+ this.setAttribute("src", val);
234
+ }
235
+ else {
236
+ this.removeAttribute("src");
237
+ }
238
+ this._updateIframeSrc();
239
+ }
240
+ get srcdoc() {
241
+ return this._srcdoc;
242
+ }
243
+ set srcdoc(v) {
244
+ const val = v || null;
245
+ if (this._srcdoc === val)
246
+ return;
247
+ this._srcdoc = val;
248
+ if (val !== null) {
249
+ this.setAttribute("srcdoc", val);
250
+ }
251
+ else {
252
+ this.removeAttribute("srcdoc");
253
+ }
254
+ this._updateIframeSrc();
255
+ }
256
+ get sandbox() {
257
+ return this._sandbox;
258
+ }
259
+ set sandbox(v) {
260
+ const val = v || null;
261
+ if (this._sandbox === val)
262
+ return;
263
+ this._sandbox = val;
264
+ if (val !== null) {
265
+ this.setAttribute("sandbox", val);
266
+ }
267
+ else {
268
+ this.removeAttribute("sandbox");
269
+ }
270
+ this._applySandbox();
271
+ }
272
+ get title() {
273
+ return this._title;
274
+ }
275
+ set title(v) {
276
+ const val = v || "Embedded widget";
277
+ if (this._title === val)
278
+ return;
279
+ this._title = val;
280
+ this.setAttribute("title", val);
281
+ this._updateTitle();
282
+ }
283
+ get view() {
284
+ return this._view;
285
+ }
286
+ set view(v) {
287
+ const val = v === "full" ? "full" : "compact";
288
+ if (this._view === val)
289
+ return;
290
+ this._view = val;
291
+ this.setAttribute("view", val);
292
+ this._updateViewDOM();
293
+ }
294
+ /**
295
+ * Controls debug logging. Toggle via:
296
+ * - HTML attribute: `<lwc-shell debug>`
297
+ * - Programmatically: `shell.debug = true`
298
+ */
299
+ get debug() {
300
+ return this._debugEnabled;
301
+ }
302
+ set debug(v) {
303
+ const val = !!v;
304
+ if (this._debugEnabled === val)
305
+ return;
306
+ this._debugEnabled = val;
307
+ if (val) {
308
+ this.setAttribute("debug", "");
309
+ }
310
+ else {
311
+ this.removeAttribute("debug");
312
+ }
313
+ }
314
+ get _SelfManagedDirtyComponent() {
315
+ return SelfManagedDirtyComponent;
316
+ }
317
+ /**
318
+ * The LightningModal class used to show the unsaved-changes confirmation.
319
+ * Wrapper LWC sets this via: `shell.dirtyStateModal = DirtyStateModal;`
320
+ * where `DirtyStateModal` is the class imported from `'c/dirtyStateModal'`.
321
+ */
322
+ get dirtyStateModal() {
323
+ return this._dirtyStateModal;
324
+ }
325
+ set dirtyStateModal(v) {
326
+ this._dirtyStateModal = v || null;
327
+ this._log("dirtyStateModal set", !!v);
328
+ }
329
+ updateData(newData) {
330
+ if (!newData || typeof newData !== "object")
331
+ return;
332
+ Object.entries(newData).forEach(([key, value]) => {
333
+ const dataAttr = `data-${String(key).replace(/[A-Z]/g, "-$&").toLowerCase()}`;
334
+ this.setAttribute(dataAttr, String(value));
335
+ });
336
+ const payload = this._collectDataAttributes();
337
+ this._lastPayloadData = payload;
338
+ if (this._bridgeReady) {
339
+ this._postToIframe("data", payload);
340
+ this._hasSentData = true;
341
+ this._log("send data", payload);
342
+ }
343
+ else {
344
+ this._log("queue data until bridge ready", payload);
345
+ }
346
+ }
347
+ refreshTheme() {
348
+ this._sendInitialTheme();
349
+ }
350
+ get _isFullView() {
351
+ return this._view === "full";
352
+ }
353
+ get _frameClass() {
354
+ return this._isFullView ? "frame frameFull" : "frame";
355
+ }
356
+ _log(...args) {
357
+ if (this._debugEnabled) {
358
+ // eslint-disable-next-line no-console
359
+ console.log("[InternalHostLwcShell]", JSON.stringify(args, null, 2));
360
+ }
361
+ }
362
+ _renderInitial() {
363
+ const shadow = this._shadow;
364
+ shadow.innerHTML = `
365
+ <style>${STYLES}</style>
366
+ <div class="container" data-state="${this._currentState}"
367
+ tabindex="0" role="region" aria-label="${this._title}">
368
+ <iframe
369
+ class="${this._frameClass}"
370
+ title="${this._title}"
371
+ aria-label="Interactive widget content"
372
+ ></iframe>
373
+ </div>
374
+ `;
375
+ this._container = shadow.querySelector(".container");
376
+ this._iframe = shadow.querySelector("iframe");
377
+ this._container.addEventListener("click", () => this._handleContainerClick());
378
+ this._applySandbox();
379
+ this._updateIframeSrc();
380
+ this._sendInitialTheme();
381
+ this._log("renderInitial: iframe ready");
382
+ }
383
+ _updateContainerState() {
384
+ if (this._container) {
385
+ this._container.setAttribute("data-state", this._currentState);
386
+ }
387
+ }
388
+ _updateTitle() {
389
+ if (this._iframe) {
390
+ this._iframe.setAttribute("title", this._title);
391
+ }
392
+ if (this._container) {
393
+ this._container.setAttribute("aria-label", this._title);
394
+ }
395
+ this._log("title", this._title);
396
+ }
397
+ _updateViewDOM() {
398
+ if (this._iframe) {
399
+ this._iframe.className = this._frameClass;
400
+ }
401
+ if (!this._container)
402
+ return;
403
+ const existingBackdrop = this._container.querySelector(".overlayBackdrop");
404
+ const existingClose = this._container.querySelector(".overlayClose");
405
+ if (existingBackdrop)
406
+ existingBackdrop.remove();
407
+ if (existingClose)
408
+ existingClose.remove();
409
+ if (this._isFullView) {
410
+ const backdrop = document.createElement("div");
411
+ backdrop.className = "overlayBackdrop";
412
+ backdrop.setAttribute("aria-hidden", "true");
413
+ const closeBtn = document.createElement("button");
414
+ closeBtn.className = "overlayClose";
415
+ closeBtn.type = "button";
416
+ closeBtn.setAttribute("aria-label", "Close fullscreen");
417
+ closeBtn.textContent = "\u2715";
418
+ closeBtn.addEventListener("click", this._exitFullscreen);
419
+ this._container.appendChild(backdrop);
420
+ this._container.appendChild(closeBtn);
421
+ }
422
+ }
423
+ _setState(newState) {
424
+ this._currentState = newState;
425
+ this._updateContainerState();
426
+ if (newState === STATES.LOADED) {
427
+ this._sendInitialData();
428
+ }
429
+ }
430
+ _setupMessageListener() {
431
+ window.addEventListener("message", this._handleMessage);
432
+ }
433
+ _handleMessage = (event) => {
434
+ const sourceWin = this._iframe?.contentWindow;
435
+ if (event.source !== sourceWin) {
436
+ return;
437
+ }
438
+ const payload = event.data || {};
439
+ const { type, data } = payload;
440
+ this._log("receive", type, data);
441
+ if (type === "bridge-event") {
442
+ const { eventType, detail } = data || {};
443
+ this._log("bridge-event", eventType, detail);
444
+ if (eventType === "resize") {
445
+ this._hasExplicitResize = true;
446
+ this._handleResize(detail);
447
+ }
448
+ else if (eventType === "widget-ready") {
449
+ this._handleWidgetReady();
450
+ }
451
+ else {
452
+ throw new RangeError(`Invalid bridge event ${eventType}`);
453
+ }
454
+ }
455
+ else if (type === "custom-event") {
456
+ const { eventType, detail } = data || {};
457
+ this._log("custom-event", eventType, detail);
458
+ if (eventType === "fullscreen-request") {
459
+ const shouldRunDefault = this.dispatchEvent(new CustomEvent(eventType, {
460
+ detail,
461
+ bubbles: true,
462
+ cancelable: true,
463
+ }));
464
+ if (shouldRunDefault) {
465
+ this._handleFullscreenRequest();
466
+ }
467
+ }
468
+ else if (eventType === "mfe:dirty-state-detected") {
469
+ this._handleDirtyStateDetected();
470
+ }
471
+ else if (eventType === "mfe:dirty-state-reset") {
472
+ this._handleDirtyStateReset();
473
+ }
474
+ else if (eventType === "mfe:dirty-state-modal-handle-status") {
475
+ this._handleDirtyStateModalStatus(detail);
476
+ }
477
+ }
478
+ else if (type === "bridge-ready") {
479
+ this._handleBridgeReady();
480
+ }
481
+ else if (type === "bridge-error") {
482
+ this._handleBridgeError(data);
483
+ }
484
+ };
485
+ _dispatchSelfManagedDirtyEvent(isUnsaved) {
486
+ const evt = new CustomEvent("privatelightningselfmanageddirtystatechanged", {
487
+ bubbles: true,
488
+ composed: true,
489
+ cancelable: true,
490
+ detail: {
491
+ isUnsaved,
492
+ unsavedComponent: this,
493
+ },
494
+ });
495
+ this.dispatchEvent(evt);
496
+ }
497
+ _toggleBeforeUnload(enable) {
498
+ if (enable) {
499
+ if (!this._beforeUnloadHandler) {
500
+ this._beforeUnloadHandler = (e) => {
501
+ e.preventDefault();
502
+ // Chrome requires returnValue to be set
503
+ e.returnValue = "";
504
+ return "";
505
+ };
506
+ window.addEventListener("beforeunload", this._beforeUnloadHandler);
507
+ }
508
+ }
509
+ else if (this._beforeUnloadHandler) {
510
+ window.removeEventListener("beforeunload", this._beforeUnloadHandler);
511
+ this._beforeUnloadHandler = null;
512
+ }
513
+ }
514
+ _handleDirtyStateDetected() {
515
+ this._toggleBeforeUnload(true);
516
+ this._dispatchSelfManagedDirtyEvent(true);
517
+ }
518
+ _handleDirtyStateReset() {
519
+ this._toggleBeforeUnload(false);
520
+ this._dispatchSelfManagedDirtyEvent(false);
521
+ }
522
+ _handleDirtyStateModalStatus(detail) {
523
+ const { correlationId, isSuccess } = detail || {};
524
+ const handlers = this._pendingDirtyActions.get(correlationId);
525
+ if (!handlers) {
526
+ this._log("No pending handler for correlationId", correlationId);
527
+ return;
528
+ }
529
+ this._pendingDirtyActions.delete(correlationId);
530
+ handlers.resolve(isSuccess);
531
+ }
532
+ _handleResize({ height }) {
533
+ const evt = new CustomEvent("resize", {
534
+ detail: { height },
535
+ cancelable: true,
536
+ });
537
+ this.dispatchEvent(evt);
538
+ if (!evt.defaultPrevented && !this._isFullscreen && typeof height === "number" && Number.isFinite(height)) {
539
+ if (this._iframe)
540
+ this._iframe.style.height = height + "px";
541
+ this._log("applied resize", height);
542
+ }
543
+ }
544
+ _handleWidgetReady() {
545
+ if (this._readinessTimeout) {
546
+ clearTimeout(this._readinessTimeout);
547
+ this._readinessTimeout = null;
548
+ }
549
+ this.dispatchEvent(new CustomEvent("widget-ready", { bubbles: true }));
550
+ this._log("widget-ready");
551
+ }
552
+ _handleFullscreenRequest() {
553
+ if (!this._isFullscreen) {
554
+ this._enterFullscreen();
555
+ }
556
+ }
557
+ _enterFullscreen() {
558
+ this._isFullscreen = true;
559
+ this._view = "full";
560
+ this._updateViewDOM();
561
+ if (this._iframe)
562
+ this._iframe.style.height = "100%";
563
+ this.updateData({ view: "full" });
564
+ this.dispatchEvent(new CustomEvent("fullscreen-entered", {
565
+ detail: { element: this },
566
+ bubbles: true,
567
+ }));
568
+ this._log("enter fullscreen");
569
+ }
570
+ _exitFullscreen = () => {
571
+ this._isFullscreen = false;
572
+ this._view = "compact";
573
+ this._updateViewDOM();
574
+ if (this._iframe)
575
+ this._iframe.style.height = "";
576
+ this.updateData({ view: "compact" });
577
+ this.dispatchEvent(new CustomEvent("fullscreen-exited", {
578
+ detail: { element: this },
579
+ bubbles: true,
580
+ }));
581
+ this._log("exit fullscreen");
582
+ };
583
+ _handleBridgeReady() {
584
+ this._bridgeReady = true;
585
+ this._setState(STATES.LOADED);
586
+ this._log("bridge-ready");
587
+ }
588
+ _handleBridgeError(errorData) {
589
+ this.dispatchEvent(new CustomEvent("widget-bridge-error", { detail: errorData }));
590
+ this._log("bridge-error", errorData);
591
+ }
592
+ _handleContainerClick() {
593
+ // placeholder
594
+ }
595
+ _applySandbox() {
596
+ const frame = this._iframe;
597
+ if (!frame)
598
+ return;
599
+ const tokens = this._computeSandboxTokens();
600
+ frame.setAttribute("sandbox", tokens);
601
+ this._log("sandbox", tokens);
602
+ }
603
+ _computeSandboxTokens() {
604
+ const tokens = [...BASE_TOKENS];
605
+ if (this._sandbox) {
606
+ const requested = String(this._sandbox).split(/\s+/).filter(Boolean);
607
+ requested.forEach((t) => {
608
+ if (OPTIONAL_TOKENS.includes(t) && !tokens.includes(t))
609
+ tokens.push(t);
610
+ if (BLOCKED_TOKENS.includes(t)) {
611
+ this.dispatchEvent(new CustomEvent("security-violation", {
612
+ detail: {
613
+ type: "blocked-sandbox-token",
614
+ token: t,
615
+ element: this,
616
+ },
617
+ }));
618
+ }
619
+ });
620
+ }
621
+ return tokens.join(" ");
622
+ }
623
+ _updateIframeSrc() {
624
+ const frame = this._iframe;
625
+ if (!frame)
626
+ return;
627
+ // reset tracking
628
+ this._hasExplicitResize = false;
629
+ this._currentState = STATES.LOADING;
630
+ this._updateContainerState();
631
+ // clear existing
632
+ frame.removeAttribute("src");
633
+ frame.removeAttribute("srcdoc");
634
+ if (this._src) {
635
+ frame.setAttribute("src", this._src);
636
+ }
637
+ else if (this._srcdoc) {
638
+ try {
639
+ frame.setAttribute("srcdoc", this._srcdoc);
640
+ }
641
+ catch {
642
+ frame.setAttribute("src", "about:blank");
643
+ }
644
+ }
645
+ // load events
646
+ frame.onload = this._handleIframeLoad;
647
+ frame.onerror = this._handleIframeError;
648
+ this._log("updateIframeSrc", this._src ? "src" : this._srcdoc ? "srcdoc" : "blank");
649
+ }
650
+ _handleIframeLoad = () => {
651
+ this.dispatchEvent(new CustomEvent("iframe-loaded", {
652
+ detail: { element: this },
653
+ bubbles: true,
654
+ }));
655
+ this._log("iframe-loaded");
656
+ this._readinessTimeout = setTimeout(() => {
657
+ if (this._currentState !== STATES.LOADED) {
658
+ this.dispatchEvent(new CustomEvent("widget-readiness-warning", {
659
+ detail: {
660
+ element: this,
661
+ message: "Widget may not be using ChatBridge for readiness signaling",
662
+ },
663
+ bubbles: true,
664
+ }));
665
+ this._log("widget-readiness-warning");
666
+ }
667
+ }, 3000);
668
+ };
669
+ _handleIframeError = (error) => {
670
+ this.dispatchEvent(new CustomEvent("widget-error", {
671
+ detail: { error, element: this },
672
+ }));
673
+ };
674
+ _postToIframe(type, data) {
675
+ const frame = this._iframe;
676
+ const win = frame && frame.contentWindow;
677
+ if (!win)
678
+ return;
679
+ try {
680
+ const msgType = `salesforce-${type}`;
681
+ win.postMessage("BRIDGE-JSON:" + JSON.stringify({ type: msgType, data }), "*");
682
+ this._log("postMessage", msgType, data);
683
+ }
684
+ catch (e) {
685
+ this._log("postMessage error", e instanceof Error ? e.message : String(e));
686
+ }
687
+ }
688
+ _collectDataAttributes() {
689
+ const out = {};
690
+ for (let i = 0; i < this.attributes.length; i++) {
691
+ const a = this.attributes[i];
692
+ if (a.name.startsWith("data-")) {
693
+ const raw = a.name.replace(/^data-/, "");
694
+ // convert kebab-case to camelCase
695
+ const camel = raw.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
696
+ out[camel] = a.value;
697
+ }
698
+ }
699
+ return out;
700
+ }
701
+ _sendInitialTheme() {
702
+ const computed = getComputedStyle(this);
703
+ const theme = {};
704
+ for (let i = 0; i < computed.length; i++) {
705
+ const name = computed[i];
706
+ if (name.startsWith("--")) {
707
+ const val = computed.getPropertyValue(name).trim();
708
+ if (val)
709
+ theme[name] = val;
710
+ }
711
+ }
712
+ this._lastThemeData = theme;
713
+ if (this._bridgeReady) {
714
+ this._postToIframe("theme", theme);
715
+ this._hasSentTheme = true;
716
+ this._log("send theme", theme);
717
+ }
718
+ else {
719
+ this._log("queue theme until bridge ready", theme);
720
+ }
721
+ }
722
+ _sendInitialData() {
723
+ if (this._lastThemeData && Object.keys(this._lastThemeData).length) {
724
+ this._postToIframe("theme", this._lastThemeData);
725
+ this._hasSentTheme = true;
726
+ this._log("send theme (initial)", this._lastThemeData);
727
+ }
728
+ const payload = this._collectDataAttributes();
729
+ this._lastPayloadData = { ...payload };
730
+ this._postToIframe("data", this._lastPayloadData);
731
+ this._hasSentData = true;
732
+ this._log("send data (initial)", this._lastPayloadData);
733
+ this._postToIframe("bridge-ready");
734
+ }
735
+ [Guid] = this._dirtyComponentGuid;
736
+ [DoCloseConfirmation]() {
737
+ if (!this._dirtyStateModal) {
738
+ this._log("DoCloseConfirmation: no modal configured, auto-resolving SUCCESS");
739
+ return Promise.resolve("SUCCESS");
740
+ }
741
+ if (typeof this._dirtyStateModal.open !== "function") {
742
+ this._log("DoCloseConfirmation: modal.open is not a function, auto-resolving SUCCESS");
743
+ return Promise.resolve("SUCCESS");
744
+ }
745
+ const dirtStateModalConfig = {
746
+ modalFields: {
747
+ label: `Unsaved Changes in ${this._title}`,
748
+ description: "You have unsaved changes. If you leave, your changes will be lost.",
749
+ buttons: [
750
+ {
751
+ buttonKey: "cancel",
752
+ buttonLabel: "Cancel",
753
+ },
754
+ {
755
+ buttonKey: "discard",
756
+ buttonLabel: "Discard Changes",
757
+ buttonVariant: "brand",
758
+ },
759
+ ],
760
+ },
761
+ size: "small",
762
+ };
763
+ return this._dirtyStateModal
764
+ .open(dirtStateModalConfig)
765
+ .then((buttonKey) => {
766
+ if (!buttonKey) {
767
+ return "CANCEL";
768
+ }
769
+ if (buttonKey === "discard") {
770
+ this._dispatchSelfManagedDirtyEvent(false);
771
+ return "SUCCESS";
772
+ }
773
+ else if (buttonKey === "cancel") {
774
+ return "CANCEL";
775
+ }
776
+ else {
777
+ return "ERROR";
778
+ }
779
+ })
780
+ .catch((err) => {
781
+ this._log("DoCloseConfirmation: error", err);
782
+ return "ERROR";
783
+ });
784
+ }
785
+ }
786
+ // ---------------------------------------------------------------------------
787
+ // Register the custom element
788
+ // ---------------------------------------------------------------------------
789
+ if (!window.customElements.get("lwc-shell")) {
790
+ window.customElements.define("lwc-shell", InternalHostLwcShell);
791
+ }
792
+
793
+ exports.InternalHostLwcShell = InternalHostLwcShell;
794
+ exports.default = InternalHostLwcShell;
795
+
796
+ Object.defineProperty(exports, '__esModule', { value: true });
797
+
798
+ return exports;
799
+
800
+ })({});
801
+ //# sourceMappingURL=index.iife.js.map