@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.
- package/README.md +27 -0
- package/dist/InternalHostLwcShell.d.ts +137 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.esm.js +791 -0
- package/dist/index.iife.js +801 -0
- package/dist/index.iife.prod.js +3 -0
- package/dist/lwc/dirtyStateModal/dirtyStateModal.html +33 -0
- package/dist/lwc/dirtyStateModal/dirtyStateModal.js +82 -0
- package/dist/lwc/dirtyStateModal/dirtyStateModal.js-meta.xml +5 -0
- package/dist/utils/dirtyStateModal.d.ts +23 -0
- package/dist/utils/dirtyStateModal.d.ts.map +1 -0
- package/dist/utils/index.d.ts +13 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/symbols.d.ts +18 -0
- package/dist/utils/symbols.d.ts.map +1 -0
- package/index.d.ts +151 -0
- package/package.json +45 -0
- package/scripts/templates/vendor-banner.js +2 -0
- package/scripts/templates/vendor-meta.xml +5 -0
- package/scripts/vendor-build.mjs +76 -0
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! @lightning-out/lwc-shell v2.2.0-rc.1 (2026-02-27) */
|
|
2
|
+
var LwcShell=function(e){"use strict";const t=Symbol("Guid"),i=Symbol("DoCloseConfirmation"),s={DoCloseConfirmation:i,Guid:t},n=["allow-scripts","allow-pointer-lock"],a=["allow-downloads","allow-forms","allow-modals"],r=["allow-same-origin","allow-top-navigation","allow-popups"],l="state-loading",o="state-loaded";class d extends HTMLElement{_shadow;_iframe=null;_container=null;_currentState=l;_hasExplicitResize=!1;_readinessTimeout=null;_isFullscreen=!1;_lastThemeData={};_lastPayloadData={};_bridgeReady=!1;_hasSentTheme=!1;_hasSentData=!1;_src=null;_srcdoc=null;_sandbox=null;_title="Embedded widget";_view="compact";_dirtyComponentGuid=function(){return Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(36)}();_beforeUnloadHandler=null;_dirtyStateModal=null;_pendingDirtyActions=new Map;_debugEnabled=!0;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,i){if(t!==i)switch(e){case"src":this.src=i;break;case"srcdoc":this.srcdoc=i;break;case"sandbox":this.sandbox=i;break;case"title":this.title=i;break;case"view":this.view=i;break;case"debug":this.debug=null!==i}}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"))}get _SelfManagedDirtyComponent(){return s}get dirtyStateModal(){return this._dirtyStateModal}set dirtyStateModal(e){this._dirtyStateModal=e||null,this._log("dirtyStateModal set",!!e)}updateData(e){if(!e||"object"!=typeof e)return;Object.entries(e).forEach(([e,t])=>{const i=`data-${String(e).replace(/[A-Z]/g,"-$&").toLowerCase()}`;this.setAttribute(i,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 min-height: 100px;\n border: 1px solid #ddd;\n border-radius: 4px;\n overflow: hidden;\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===o&&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:s,data:n}=i;if(this._log("receive",s,n),"bridge-event"===s){const{eventType:e,detail:t}=n||{};if(this._log("bridge-event",e,t),"resize"===e)this._hasExplicitResize=!0,this._handleResize(t);else{if("widget-ready"!==e)throw new RangeError(`Invalid bridge event ${e}`);this._handleWidgetReady()}}else if("custom-event"===s){const{eventType:e,detail:t}=n||{};if(this._log("custom-event",e,t),"fullscreen-request"===e){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,cancelable:!0}))&&this._handleFullscreenRequest()}else"mfe:dirty-state-detected"===e?this._handleDirtyStateDetected():"mfe:dirty-state-reset"===e?this._handleDirtyStateReset():"mfe:dirty-state-modal-handle-status"===e&&this._handleDirtyStateModalStatus(t)}else"bridge-ready"===s?this._handleBridgeReady():"bridge-error"===s&&this._handleBridgeError(n)};_dispatchSelfManagedDirtyEvent(e){const t=new CustomEvent("privatelightningselfmanageddirtystatechanged",{bubbles:!0,composed:!0,cancelable:!0,detail:{isUnsaved:e,unsavedComponent:this}});this.dispatchEvent(t)}_toggleBeforeUnload(e){e?this._beforeUnloadHandler||(this._beforeUnloadHandler=e=>(e.preventDefault(),e.returnValue="",""),window.addEventListener("beforeunload",this._beforeUnloadHandler)):this._beforeUnloadHandler&&(window.removeEventListener("beforeunload",this._beforeUnloadHandler),this._beforeUnloadHandler=null)}_handleDirtyStateDetected(){this._toggleBeforeUnload(!0),this._dispatchSelfManagedDirtyEvent(!0)}_handleDirtyStateReset(){this._toggleBeforeUnload(!1),this._dispatchSelfManagedDirtyEvent(!1)}_handleDirtyStateModalStatus(e){const{correlationId:t,isSuccess:i}=e||{},s=this._pendingDirtyActions.get(t);s?(this._pendingDirtyActions.delete(t),s.resolve(i)):this._log("No pending handler for correlationId",t)}_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._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.updateData({view:"compact"}),this.dispatchEvent(new CustomEvent("fullscreen-exited",{detail:{element:this},bubbles:!0})),this._log("exit fullscreen")};_handleBridgeReady(){this._bridgeReady=!0,this._setState(o),this._log("bridge-ready")}_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=[...n];if(this._sandbox){String(this._sandbox).split(/\s+/).filter(Boolean).forEach(t=>{a.includes(t)&&!e.includes(t)&&e.push(t),r.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._hasExplicitResize=!1,this._currentState=l,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!==o&&(this.dispatchEvent(new CustomEvent("widget-readiness-warning",{detail:{element:this,message:"Widget may not be using ChatBridge 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 i=this._iframe,s=i&&i.contentWindow;if(s)try{const i=`salesforce-${e}`;s.postMessage("BRIDGE-JSON:"+JSON.stringify({type:i,data:t}),"*"),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 i=this.attributes[t];if(i.name.startsWith("data-")){e[i.name.replace(/^data-/,"").replace(/-([a-z])/g,(e,t)=>t.toUpperCase())]=i.value}}return e}_sendInitialTheme(){const e=getComputedStyle(this),t={};for(let i=0;i<e.length;i++){const s=e[i];if(s.startsWith("--")){const i=e.getPropertyValue(s).trim();i&&(t[s]=i)}}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),this._postToIframe("bridge-ready")}[t]=this._dirtyComponentGuid;[i](){if(!this._dirtyStateModal)return this._log("DoCloseConfirmation: no modal configured, auto-resolving SUCCESS"),Promise.resolve("SUCCESS");if("function"!=typeof this._dirtyStateModal.open)return this._log("DoCloseConfirmation: modal.open is not a function, auto-resolving SUCCESS"),Promise.resolve("SUCCESS");const e={modalFields:{label:`Unsaved Changes in ${this._title}`,description:"You have unsaved changes. If you leave, your changes will be lost.",buttons:[{buttonKey:"cancel",buttonLabel:"Cancel"},{buttonKey:"discard",buttonLabel:"Discard Changes",buttonVariant:"brand"}]},size:"small"};return this._dirtyStateModal.open(e).then(e=>e?"discard"===e?(this._dispatchSelfManagedDirtyEvent(!1),"SUCCESS"):"cancel"===e?"CANCEL":"ERROR":"CANCEL").catch(e=>(this._log("DoCloseConfirmation: error",e),"ERROR"))}}return window.customElements.get("lwc-shell")||window.customElements.define("lwc-shell",d),e.InternalHostLwcShell=d,e.default=d,Object.defineProperty(e,"__esModule",{value:!0}),e}({});
|
|
3
|
+
//# 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,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,151 @@
|
|
|
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
|
+
declare const Guid: unique symbol;
|
|
13
|
+
declare const DoCloseConfirmation: unique symbol;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* InternalHostLwcShell
|
|
17
|
+
*
|
|
18
|
+
* A standard Web Component (custom element) that embeds an iframe-based widget
|
|
19
|
+
* with bridge communication, sandbox management, fullscreen support, and
|
|
20
|
+
* dirty-state tracking.
|
|
21
|
+
*
|
|
22
|
+
* Registered as `<lwc-shell>`.
|
|
23
|
+
*
|
|
24
|
+
* Ported from the LWC `widgetContainerLo` component (formerly known as
|
|
25
|
+
* WidgetContainer) — all Lightning Web Component framework dependencies have
|
|
26
|
+
* been removed so this can run as a plain custom element inside any LWC host.
|
|
27
|
+
*
|
|
28
|
+
* **How customers use this:**
|
|
29
|
+
* 1. The build produces `dist/index.esm.js` which bundles this class.
|
|
30
|
+
* 2. Customers copy that file into their SFDX project as an LWC entity
|
|
31
|
+
* (e.g. `c/lwcShell`) and deploy it to their Salesforce org.
|
|
32
|
+
* 3. A wrapper LWC imports `'c/lwcShell'` and creates `<lwc-shell>`
|
|
33
|
+
* imperatively via `document.createElement('lwc-shell')`.
|
|
34
|
+
*
|
|
35
|
+
* See the README and the `productRegistrationWidgetLo` demo for a full recipe.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
interface DirtyStateModalButton {
|
|
39
|
+
buttonKey: string;
|
|
40
|
+
buttonLabel: string;
|
|
41
|
+
buttonVariant?: string;
|
|
42
|
+
}
|
|
43
|
+
interface DirtyStateModalFields {
|
|
44
|
+
label?: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
buttons?: DirtyStateModalButton[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Represents a LightningModal-style class with a static `.open()` method.
|
|
50
|
+
*/
|
|
51
|
+
interface DirtyStateModalClass {
|
|
52
|
+
open(config: {
|
|
53
|
+
modalFields?: DirtyStateModalFields;
|
|
54
|
+
size?: string;
|
|
55
|
+
}): Promise<string | undefined>;
|
|
56
|
+
}
|
|
57
|
+
declare class InternalHostLwcShell extends HTMLElement {
|
|
58
|
+
private _shadow;
|
|
59
|
+
private _iframe;
|
|
60
|
+
private _container;
|
|
61
|
+
private _currentState;
|
|
62
|
+
private _hasExplicitResize;
|
|
63
|
+
private _readinessTimeout;
|
|
64
|
+
private _isFullscreen;
|
|
65
|
+
private _lastThemeData;
|
|
66
|
+
private _lastPayloadData;
|
|
67
|
+
private _bridgeReady;
|
|
68
|
+
private _hasSentTheme;
|
|
69
|
+
private _hasSentData;
|
|
70
|
+
private _src;
|
|
71
|
+
private _srcdoc;
|
|
72
|
+
private _sandbox;
|
|
73
|
+
private _title;
|
|
74
|
+
private _view;
|
|
75
|
+
private _dirtyComponentGuid;
|
|
76
|
+
private _beforeUnloadHandler;
|
|
77
|
+
private _dirtyStateModal;
|
|
78
|
+
private _pendingDirtyActions;
|
|
79
|
+
private _debugEnabled;
|
|
80
|
+
static get observedAttributes(): string[];
|
|
81
|
+
constructor();
|
|
82
|
+
connectedCallback(): void;
|
|
83
|
+
disconnectedCallback(): void;
|
|
84
|
+
attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
|
|
85
|
+
get src(): string | null;
|
|
86
|
+
set src(v: string | null);
|
|
87
|
+
get srcdoc(): string | null;
|
|
88
|
+
set srcdoc(v: string | null);
|
|
89
|
+
get sandbox(): string | null;
|
|
90
|
+
set sandbox(v: string | null);
|
|
91
|
+
get title(): string;
|
|
92
|
+
set title(v: string | null);
|
|
93
|
+
get view(): string | null;
|
|
94
|
+
set view(v: string | null);
|
|
95
|
+
/**
|
|
96
|
+
* Controls debug logging. Toggle via:
|
|
97
|
+
* - HTML attribute: `<lwc-shell debug>`
|
|
98
|
+
* - Programmatically: `shell.debug = true`
|
|
99
|
+
*/
|
|
100
|
+
get debug(): boolean;
|
|
101
|
+
set debug(v: boolean);
|
|
102
|
+
get _SelfManagedDirtyComponent(): Readonly<{
|
|
103
|
+
DoCloseConfirmation: typeof DoCloseConfirmation;
|
|
104
|
+
Guid: typeof Guid;
|
|
105
|
+
}>;
|
|
106
|
+
/**
|
|
107
|
+
* The LightningModal class used to show the unsaved-changes confirmation.
|
|
108
|
+
* Wrapper LWC sets this via: `shell.dirtyStateModal = DirtyStateModal;`
|
|
109
|
+
* where `DirtyStateModal` is the class imported from `'c/dirtyStateModal'`.
|
|
110
|
+
*/
|
|
111
|
+
get dirtyStateModal(): DirtyStateModalClass | null;
|
|
112
|
+
set dirtyStateModal(v: DirtyStateModalClass | null);
|
|
113
|
+
updateData(newData: Record<string, unknown>): void;
|
|
114
|
+
refreshTheme(): void;
|
|
115
|
+
private get _isFullView();
|
|
116
|
+
private get _frameClass();
|
|
117
|
+
private _log;
|
|
118
|
+
private _renderInitial;
|
|
119
|
+
private _updateContainerState;
|
|
120
|
+
private _updateTitle;
|
|
121
|
+
private _updateViewDOM;
|
|
122
|
+
private _setState;
|
|
123
|
+
private _setupMessageListener;
|
|
124
|
+
private _handleMessage;
|
|
125
|
+
private _dispatchSelfManagedDirtyEvent;
|
|
126
|
+
private _toggleBeforeUnload;
|
|
127
|
+
private _handleDirtyStateDetected;
|
|
128
|
+
private _handleDirtyStateReset;
|
|
129
|
+
private _handleDirtyStateModalStatus;
|
|
130
|
+
private _handleResize;
|
|
131
|
+
private _handleWidgetReady;
|
|
132
|
+
private _handleFullscreenRequest;
|
|
133
|
+
private _enterFullscreen;
|
|
134
|
+
private _exitFullscreen;
|
|
135
|
+
private _handleBridgeReady;
|
|
136
|
+
private _handleBridgeError;
|
|
137
|
+
private _handleContainerClick;
|
|
138
|
+
private _applySandbox;
|
|
139
|
+
private _computeSandboxTokens;
|
|
140
|
+
private _updateIframeSrc;
|
|
141
|
+
private _handleIframeLoad;
|
|
142
|
+
private _handleIframeError;
|
|
143
|
+
private _postToIframe;
|
|
144
|
+
private _collectDataAttributes;
|
|
145
|
+
private _sendInitialTheme;
|
|
146
|
+
private _sendInitialData;
|
|
147
|
+
[Guid]: string;
|
|
148
|
+
[DoCloseConfirmation](): Promise<"SUCCESS" | "CANCEL" | "ERROR">;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { InternalHostLwcShell, InternalHostLwcShell as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lightning-out/lwc-shell",
|
|
3
|
+
"version": "2.2.0-rc.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
|
+
"@lightning-out/utils": "2.2.0-rc.0",
|
|
30
|
+
"rollup-plugin-copy": "3.5.0"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist/",
|
|
34
|
+
"!dist/*.test.js",
|
|
35
|
+
"!dist/*.map",
|
|
36
|
+
"scripts/",
|
|
37
|
+
"index.d.ts",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE.txt"
|
|
40
|
+
],
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"sideEffects": true
|
|
45
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* vendor-build.mjs
|
|
4
|
+
*
|
|
5
|
+
* Copies the pre-built @lightning-out/lwc-shell artifacts into force-app
|
|
6
|
+
* as vendor-prefixed LWC components ready for deployment:
|
|
7
|
+
*
|
|
8
|
+
* 1. dist/index.esm.js → vendorLwcShell/vendorLwcShell.js
|
|
9
|
+
* 2. dist/lwc/* → vendor<Name>/vendor<Name>.* (e.g. vendorDirtyStateModal)
|
|
10
|
+
*/
|
|
11
|
+
import { dirname, join } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { mkdir, readFile, writeFile, readdir } from 'fs/promises';
|
|
14
|
+
import { createRequire } from 'module';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
18
|
+
|
|
19
|
+
// Resolve the package path using Node's module resolution (works regardless of node_modules location)
|
|
20
|
+
const lwcShellDistDir = dirname(require.resolve('@lightning-out/lwc-shell'));
|
|
21
|
+
const lwcShellLwcDir = join(lwcShellDistDir, 'lwc');
|
|
22
|
+
const lwcDest = 'force-app/main/default/lwc';
|
|
23
|
+
|
|
24
|
+
const VENDOR_PREFIX = 'vendor';
|
|
25
|
+
|
|
26
|
+
// Load templates from external files so they can be edited independently with syntax highlighting
|
|
27
|
+
const VENDOR_BANNER = await readFile(join(__dirname, 'templates', 'vendor-banner.js'), 'utf-8');
|
|
28
|
+
const META_XML = await readFile(join(__dirname, 'templates', 'vendor-meta.xml'), 'utf-8');
|
|
29
|
+
|
|
30
|
+
/** Prefix a component name: dirtyStateModal → vendorDirtyStateModal */
|
|
31
|
+
function toVendorName(name) {
|
|
32
|
+
return VENDOR_PREFIX + name.charAt(0).toUpperCase() + name.slice(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 1. Generate vendorLwcShell from dist/index.esm.js
|
|
36
|
+
const vendorShellName = 'vendorLwcShell';
|
|
37
|
+
const vendorShellDir = join(lwcDest, vendorShellName);
|
|
38
|
+
await mkdir(vendorShellDir, { recursive: true });
|
|
39
|
+
|
|
40
|
+
const esmSource = await readFile(join(lwcShellDistDir, 'index.esm.js'), 'utf-8');
|
|
41
|
+
await writeFile(join(vendorShellDir, `${vendorShellName}.js`), VENDOR_BANNER + esmSource);
|
|
42
|
+
await writeFile(join(vendorShellDir, `${vendorShellName}.js-meta.xml`), META_XML);
|
|
43
|
+
|
|
44
|
+
// 2. Copy LWC components with vendor prefix
|
|
45
|
+
const entries = await readdir(lwcShellLwcDir, { withFileTypes: true });
|
|
46
|
+
const components = entries.filter(entry => entry.isDirectory()).map(entry => entry.name);
|
|
47
|
+
|
|
48
|
+
const vendorComponentNames = [];
|
|
49
|
+
for (const originalName of components) {
|
|
50
|
+
const vendorName = toVendorName(originalName);
|
|
51
|
+
const destDir = join(lwcDest, vendorName);
|
|
52
|
+
await mkdir(destDir, { recursive: true });
|
|
53
|
+
|
|
54
|
+
// Copy each file, renaming from originalName.* to vendorName.*
|
|
55
|
+
const srcDir = join(lwcShellLwcDir, originalName);
|
|
56
|
+
const files = await readdir(srcDir);
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const renamedFile = file.replace(originalName, vendorName);
|
|
59
|
+
const contents = await readFile(join(srcDir, file), 'utf-8');
|
|
60
|
+
await writeFile(join(destDir, renamedFile), contents);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
vendorComponentNames.push(vendorName);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 3. Print deployment instructions
|
|
67
|
+
const allComponents = [vendorShellName, ...vendorComponentNames];
|
|
68
|
+
const list = allComponents
|
|
69
|
+
.map((name, i) => ` ${i + 1}. ${lwcDest}/${name}`)
|
|
70
|
+
.join('\n');
|
|
71
|
+
console.log(
|
|
72
|
+
`\n` +
|
|
73
|
+
`Below LWC components are generated from @lightning-out/lwc-shell.\n` +
|
|
74
|
+
`Please ensure to deploy them to your Salesforce org:\n` +
|
|
75
|
+
`${list}\n`
|
|
76
|
+
);
|