@salesforce/experimental-mfe-lwc-shell 2.2.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,5 @@
1
+ /*! @salesforce/experimental-mfe-lwc-shell v2.2.0-rc.2 (2026-03-20) */
2
+ var LwcShell=function(e,t){"use strict";const s=["allow-scripts","allow-pointer-lock"],i=["allow-downloads","allow-forms","allow-modals"],a=["allow-same-origin","allow-top-navigation","allow-popups"],n="state-loading",r="state-loaded";class l extends HTMLElement{_shadow;_iframe=null;_container=null;_currentState=n;_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;_pendingGraphQLCount=0;static MAX_CONCURRENT_GRAPHQL=10;static get observedAttributes(){return["src","srcdoc","sandbox","title","view","debug"]}constructor(){super(),this._shadow=this.attachShadow({mode:"closed"})}connectedCallback(){this._renderInitial(),this._setupMessageListener(),this._log("connectedCallback")}disconnectedCallback(){window.removeEventListener("message",this._handleMessage),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===r&&this._sendInitialData()}_setupMessageListener(){window.addEventListener("message",this._handleMessage)}_handleMessage=e=>{const t=this._iframe?.contentWindow;if(e.source!==t)return;const s=e.data||{},{type:i,data:a,id:n}=s;if(n?n===this._shellInstanceId:"bridge-ready"===i)if(this._log("receive",i,a),"bridge-event"===i){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"===i){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"trackdirtystate"===e&&this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,cancelable:!0,composed:!0}))}else"bridge-graphql-request"===i?this._handleGraphQLRequest(a):"bridge-ready"===i?this._handleBridgeReady():"bridge-error"===i&&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(r)}_handleBridgeError(e){this.dispatchEvent(new CustomEvent("widget-bridge-error",{detail:e})),this._log("bridge-error",e)}async _handleGraphQLRequest(e){if("string"!=typeof e?.requestId)return this._log("_handleGraphQLRequest: invalid request data",e),void this._postToIframe("bridge-graphql-response",{requestId:e?.requestId,ok:!1,error:{message:"Invalid GraphQL request data"}});const{requestId:s,query:i,variables:a}=e;if("string"==typeof i)if(null==a||"object"==typeof a&&!Array.isArray(a))if(this._pendingGraphQLCount>=l.MAX_CONCURRENT_GRAPHQL)this._postToIframe("bridge-graphql-response",{requestId:s,ok:!1,error:{message:"Too many concurrent GraphQL requests"}});else{this._pendingGraphQLCount++;try{const e=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
+ ${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:{});
5
+ //# sourceMappingURL=index.iife.prod.js.map
@@ -0,0 +1,33 @@
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>
@@ -0,0 +1,82 @@
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
+ }
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
3
+ <apiVersion>64.0</apiVersion>
4
+ <isExposed>false</isExposed>
5
+ </LightningComponentBundle>
@@ -0,0 +1,23 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,13 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,18 @@
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
@@ -0,0 +1 @@
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"}
package/index.d.ts ADDED
@@ -0,0 +1,97 @@
1
+ /**
2
+ * InternalHostLwcShell
3
+ *
4
+ * A standard Web Component (custom element) that embeds an iframe-based widget
5
+ * with bridge communication, sandbox management, and fullscreen support.
6
+ *
7
+ * Registered as `<lwc-shell>`.
8
+ *
9
+ * Dirty-state tracking is handled via a simple `trackdirtystate`
10
+ * custom-event flow: the embedded widget dispatches `trackdirtystate`
11
+ * with `{ isDirty, instanceId, label }`, and this shell re-dispatches
12
+ * the event for the host LWC to observe.
13
+ *
14
+ * **How customers use this:**
15
+ * 1. The build produces `dist/index.esm.js` which bundles this class.
16
+ * 2. Customers copy that file into their SFDX project as an LWC entity
17
+ * (e.g. `c/lwcShell`) and deploy it to their Salesforce org.
18
+ * 3. A wrapper LWC imports `'c/lwcShell'` and creates `<lwc-shell>`
19
+ * imperatively via `document.createElement('lwc-shell')`.
20
+ *
21
+ * See the README and the `productRegistration` demo for a full recipe.
22
+ */
23
+ declare class InternalHostLwcShell extends HTMLElement {
24
+ private _shadow;
25
+ private _iframe;
26
+ private _container;
27
+ private _currentState;
28
+ private _readinessTimeout;
29
+ private _isFullscreen;
30
+ private _preFullscreenHeight;
31
+ private _lastThemeData;
32
+ private _lastPayloadData;
33
+ private _bridgeReady;
34
+ private _shellInstanceId;
35
+ private _hasSentTheme;
36
+ private _hasSentData;
37
+ private _src;
38
+ private _srcdoc;
39
+ private _sandbox;
40
+ private _title;
41
+ private _view;
42
+ private _debugEnabled;
43
+ private _graphqlHandler;
44
+ static get observedAttributes(): string[];
45
+ constructor();
46
+ connectedCallback(): void;
47
+ disconnectedCallback(): void;
48
+ attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
49
+ get src(): string | null;
50
+ set src(v: string | null);
51
+ get srcdoc(): string | null;
52
+ set srcdoc(v: string | null);
53
+ get sandbox(): string | null;
54
+ set sandbox(v: string | null);
55
+ get title(): string;
56
+ set title(v: string | null);
57
+ get view(): string | null;
58
+ set view(v: string | null);
59
+ /**
60
+ * Controls debug logging. Toggle via:
61
+ * - HTML attribute: `<lwc-shell debug>`
62
+ * - Programmatically: `shell.debug = true`
63
+ */
64
+ get debug(): boolean;
65
+ set debug(v: boolean);
66
+ updateData(newData: Record<string, unknown>): void;
67
+ refreshTheme(): void;
68
+ private get _isFullView();
69
+ private get _frameClass();
70
+ private _log;
71
+ private _renderInitial;
72
+ private _updateContainerState;
73
+ private _updateTitle;
74
+ private _updateViewDOM;
75
+ private _setState;
76
+ private _setupMessageListener;
77
+ private _handleMessage;
78
+ private _handleResize;
79
+ private _handleWidgetReady;
80
+ private _handleFullscreenRequest;
81
+ private _enterFullscreen;
82
+ private _exitFullscreen;
83
+ private _handleBridgeReady;
84
+ private _handleBridgeError;
85
+ private _handleContainerClick;
86
+ private _applySandbox;
87
+ private _computeSandboxTokens;
88
+ private _updateIframeSrc;
89
+ private _handleIframeLoad;
90
+ private _handleIframeError;
91
+ private _postToIframe;
92
+ private _collectDataAttributes;
93
+ private _sendInitialTheme;
94
+ private _sendInitialData;
95
+ }
96
+
97
+ export { InternalHostLwcShell, InternalHostLwcShell as default };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@salesforce/experimental-mfe-lwc-shell",
3
+ "version": "2.2.0",
4
+ "private": false,
5
+ "description": "A standard Web Component shell for UI Embedding",
6
+ "license": "SEE LICENSE IN LICENSE.txt",
7
+ "author": "Salesforce MFE Team",
8
+ "main": "dist/index.esm.js",
9
+ "module": "dist/index.esm.js",
10
+ "types": "index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./index.d.ts",
14
+ "import": "./dist/index.esm.js",
15
+ "require": "./dist/index.esm.js",
16
+ "default": "./dist/index.esm.js"
17
+ }
18
+ },
19
+ "bin": {
20
+ "lwc-shell-vendor-build": "./scripts/vendor-build.mjs"
21
+ },
22
+ "scripts": {
23
+ "build": "rollup -c",
24
+ "build:types": "tsc --emitDeclarationOnly",
25
+ "test": "jest",
26
+ "clean": "rimraf dist"
27
+ },
28
+ "devDependencies": {},
29
+ "files": [
30
+ "dist/",
31
+ "!dist/*.test.js",
32
+ "!dist/*.map",
33
+ "scripts/",
34
+ "index.d.ts",
35
+ "README.md",
36
+ "LICENSE.txt"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "sideEffects": true
42
+ }
@@ -0,0 +1,2 @@
1
+ /* eslint-disable */
2
+ // Vendored from @salesforce/experimental-mfe-lwc-shell npm package
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
3
+ <apiVersion>64.0</apiVersion>
4
+ <isExposed>false</isExposed>
5
+ </LightningComponentBundle>
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * vendor-build.mjs
4
+ *
5
+ * Copies the pre-built @salesforce/experimental-mfe-lwc-shell ESM bundle into force-app
6
+ * as a vendor-prefixed LWC component ready for deployment:
7
+ *
8
+ * dist/index.esm.js → vendorLwcShell/vendorLwcShell.js
9
+ */
10
+ import { dirname, join } from 'path';
11
+ import { fileURLToPath } from 'url';
12
+ import { mkdir, readFile, writeFile } from 'fs/promises';
13
+ import { createRequire } from 'module';
14
+
15
+ const require = createRequire(import.meta.url);
16
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
17
+
18
+ const lwcShellDistDir = dirname(require.resolve('@salesforce/experimental-mfe-lwc-shell'));
19
+ const lwcDest = 'force-app/main/default/lwc';
20
+
21
+ const VENDOR_BANNER = await readFile(join(__dirname, 'templates', 'vendor-banner.js'), 'utf-8');
22
+ const META_XML = await readFile(join(__dirname, 'templates', 'vendor-meta.xml'), 'utf-8');
23
+
24
+ const vendorShellName = 'vendorLwcShell';
25
+ const vendorShellDir = join(lwcDest, vendorShellName);
26
+ await mkdir(vendorShellDir, { recursive: true });
27
+
28
+ const esmSource = await readFile(join(lwcShellDistDir, 'index.esm.js'), 'utf-8');
29
+ await writeFile(join(vendorShellDir, `${vendorShellName}.js`), VENDOR_BANNER + esmSource);
30
+ await writeFile(join(vendorShellDir, `${vendorShellName}.js-meta.xml`), META_XML);
31
+
32
+ console.log(
33
+ `\n` +
34
+ `Below LWC component is generated from @salesforce/experimental-mfe-lwc-shell.\n` +
35
+ `Please ensure to deploy it to your Salesforce org:\n` +
36
+ ` 1. ${lwcDest}/${vendorShellName}\n`
37
+ );