@momentco-ai/moment-sdk 0.4.0-dev.25 → 0.4.0-dev.26
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/CHANGELOG.md +33 -2
- package/dist/moment-sdk.js +2 -2
- package/dist/moment-sdk.mjs +19 -2
- package/dist/types/sdk/moment-sdk.d.ts +2 -0
- package/dist/types/sdk/types.d.ts +14 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,27 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [Unreleased
|
|
8
|
+
## [0.4.0] - Unreleased
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`schedule` trigger type** — `data-moment-trigger-type="schedule"` with `teamSlug` only opens the full brand schedule experience, where a fan multi-selects any mix of collections (playlists) and individual events (moments) and syncs them all at once. Opens a dedicated Live `/embed/schedule` route.
|
|
13
|
+
- `scheduleMomentIds` / `schedulePlaylistIds` on `MomentOAuthStartPayload` — the mixed selection is carried through the OAuth popup URL so it survives the redirect and browser third-party storage partitioning.
|
|
14
|
+
- `'partial'` result on `MomentResultPayload`, plus a `counts` breakdown (`requested`, `succeeded`, `failed`, `moments`, `playlists`) for multi-resource (schedule) syncs.
|
|
15
|
+
- `moment.embed.close` message (`MomentClosePayload`) — lets the schedule experience's interactive terminal screens (header close, "Done") dismiss the modal explicitly; the SDK keeps the modal open on a `cancelled` result so the iframe can show retry UI.
|
|
16
|
+
- `moment.embed.theme` message (`MomentThemePayload`) — the `schedule` experience forwards the brand's configured surface border radius so the SDK rounds the modal frame (wrapper + container) to match the in-iframe surfaces. The SDK makes no config request of its own; the iframe resolves the brand SDK config and only passes the radius over the already-trusted (origin + source validated) channel.
|
|
17
|
+
|
|
18
|
+
## [0.3.3] - 2026-06-24
|
|
19
|
+
|
|
20
|
+
> **Reverted** the same day (#25) and rolled back to 0.3.2. This was an initial cut of `schedule` trigger support; the feature was reworked and re-introduced in 0.4.0.
|
|
21
|
+
|
|
22
|
+
## [0.3.2] - 2026-06-24
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Multi-slug `playlist` trigger — `playlistSlugs` on `MomentSdkOpenPayload` / `createMomentButton()` and the `data-moment-playlist-slugs` attribute. With a single slug Live syncs that playlist directly; with several it prompts the user to pick which one to sync.
|
|
27
|
+
|
|
28
|
+
## [0.3.1] - 2026-06-22
|
|
9
29
|
|
|
10
30
|
### Added
|
|
11
31
|
|
|
@@ -64,6 +84,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
64
84
|
- Removed transitional `subscriptionType` fallback handling to align with the intentional `triggerType` protocol.
|
|
65
85
|
- Ensured modal iframe and OAuth popup URLs both pass `triggerType` consistently to Moment Live embed routes.
|
|
66
86
|
|
|
87
|
+
## [0.1.1] - 2026-04-07
|
|
88
|
+
|
|
89
|
+
### Added
|
|
90
|
+
|
|
91
|
+
- Trigger styling examples and expanded embed documentation in the README.
|
|
92
|
+
|
|
67
93
|
## [0.1.0] - 2026-04-01
|
|
68
94
|
|
|
69
95
|
### Added
|
|
@@ -79,9 +105,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
79
105
|
- UMD + ESM builds with TypeScript declarations
|
|
80
106
|
- Build-time environment configuration via `VITE_LIVE_BASE_URL`
|
|
81
107
|
|
|
108
|
+
[0.4.0]: https://github.com/Moment-Co/moment-sdk/compare/v0.3.2...HEAD
|
|
109
|
+
[0.3.3]: https://github.com/Moment-Co/moment-sdk/compare/v0.3.2...v0.3.3
|
|
110
|
+
[0.3.2]: https://github.com/Moment-Co/moment-sdk/compare/v0.3.1...v0.3.2
|
|
111
|
+
[0.3.1]: https://github.com/Moment-Co/moment-sdk/compare/v0.3.0...v0.3.1
|
|
82
112
|
[0.3.0]: https://github.com/Moment-Co/moment-sdk/compare/v0.2.2...v0.3.0
|
|
83
113
|
[0.2.2]: https://github.com/Moment-Co/moment-sdk/compare/v0.2.1...v0.2.2
|
|
84
114
|
[0.2.1]: https://github.com/Moment-Co/moment-sdk/compare/v0.2.0...v0.2.1
|
|
85
115
|
[0.2.0]: https://github.com/Moment-Co/moment-sdk/compare/v0.1.2...v0.2.0
|
|
86
|
-
[0.1.2]: https://github.com/Moment-Co/moment-sdk/compare/v0.1.
|
|
116
|
+
[0.1.2]: https://github.com/Moment-Co/moment-sdk/compare/v0.1.1...v0.1.2
|
|
117
|
+
[0.1.1]: https://github.com/Moment-Co/moment-sdk/compare/v0.1.0...v0.1.1
|
|
87
118
|
[0.1.0]: https://github.com/Moment-Co/moment-sdk/releases/tag/v0.1.0
|
package/dist/moment-sdk.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(v){"use strict";class
|
|
1
|
+
(function(v){"use strict";class x{popup=null;pollTimer=null;messageHandler=null;resultReceived=!1;opts;constructor(e){this.opts=e}open(e){this.cleanup(),this.resultReceived=!1;const t=this.buildPopupUrl(e),i=Math.round(window.screenX+(window.outerWidth-500)/2),o=Math.round(window.screenY+(window.outerHeight-700)/2);if(this.popup=window.open(t,"moment-oauth-popup",`width=500,height=700,left=${i},top=${o},menubar=no,toolbar=no,location=yes,status=no`),!this.popup||this.popup.closed){this.sendStatusToIframe("popup-blocked"),this.opts.onPopupBlocked();return}this.sendStatusToIframe("popup-opened"),this.startPolling(),this.messageHandler=s=>{if(s.origin!==this.opts.liveOrigin||s.source!==this.popup)return;const r=s.data;!r||typeof r!="object"||r.source!=="moment-live-embed"||r.type!=="moment.embed.result"||(this.resultReceived=!0,this.sendStatusToIframe("completed"),this.opts.iframeWindow&&this.opts.iframeWindow.postMessage(r,this.opts.liveOrigin),this.opts.onResult(r),this.cleanup())},window.addEventListener("message",this.messageHandler)}cleanup(){if(this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.popup&&!this.popup.closed)try{this.popup.close()}catch{}this.popup=null}buildPopupUrl(e){const t=window.location.origin,i=new URLSearchParams({provider:e.provider,teamSlug:e.teamSlug,ids:e.ids.join(","),triggerType:e.triggerType,returnOrigin:t});return e.subscriptionType&&i.set("subscriptionType",e.subscriptionType),e.scheduleMomentIds?.length&&i.set("scheduleMomentIds",e.scheduleMomentIds.join(",")),e.schedulePlaylistIds?.length&&i.set("schedulePlaylistIds",e.schedulePlaylistIds.join(",")),`${this.opts.liveBaseUrl}/en/embed/oauth-popup?${i.toString()}`}startPolling(){this.pollTimer=setInterval(()=>{(!this.popup||this.popup.closed)&&(this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.resultReceived||(this.sendStatusToIframe("popup-closed"),this.opts.onPopupClosed()),this.cleanup())},500)}sendStatusToIframe(e){if(!this.opts.iframeWindow)return;const t={source:"moment-sdk",type:"moment.embed.oauth.status",status:e};this.opts.iframeWindow.postMessage(t,this.opts.liveOrigin)}}function S(n,e,t=typeof window<"u"?window.location.origin:""){if(e.triggerType==="schedule"){const o=new URLSearchParams;return o.set("teamSlug",e.teamSlug),o.set("returnOrigin",t),o.set("embed","1"),`${n}/en/embed/schedule?${o.toString()}`}const i=new URLSearchParams;return i.set("teamSlug",e.teamSlug),e.momentSlug&&i.set("momentSlug",e.momentSlug),e.listSlug&&i.set("listSlug",e.listSlug),i.set("triggerType",e.triggerType),e.ids?.length&&i.set("ids",e.ids.join(",")),e.playlistSlugs?.length&&i.set("playlistSlugs",e.playlistSlugs.join(",")),e.calendar&&i.set("calendar",e.calendar),e.apiSourceId&&i.set("subscriptionId",e.apiSourceId),e.externalEventId&&i.set("externalId",e.externalEventId),e.externalEventIdPrefix&&!e.externalEventId&&i.set("externalIdPrefix",e.externalEventIdPrefix),i.set("returnOrigin",t),i.set("embed","1"),`${n}/en/embed/sync?${i.toString()}`}const b="moment-sdk-trigger-styles",T=`
|
|
2
2
|
:where(.moment-sync-trigger) {
|
|
3
3
|
background-color: #1c1917;
|
|
4
4
|
color: #ffffff;
|
|
@@ -22,4 +22,4 @@
|
|
|
22
22
|
outline: 2px solid #1c1917;
|
|
23
23
|
outline-offset: 2px;
|
|
24
24
|
}
|
|
25
|
-
`;function E(){if(typeof document>"u"||document.getElementById(b))return;const s=document.createElement("style");s.id=b,s.textContent=x,document.head.appendChild(s)}const I=".moment-sync-trigger",P="https://live.dev.momentco.ai";class y{opts;liveBaseUrl;liveOrigin;overlay=null;iframe=null;popupBridge=null;messageHandler=null;boundClickHandlers=new Map;activePayload=null;previousFocusedElement=null;previousBodyOverflow="";didManageBodyOverflow=!1;closeTimer=null;constructor(e){this.opts={triggerSelector:I,...e},this.liveBaseUrl=P;try{this.liveOrigin=new URL(this.liveBaseUrl).origin}catch{throw new Error(`[MomentSdk] Invalid VITE_LIVE_BASE_URL: "${this.liveBaseUrl}"`)}E(),this.bindTriggers(),this.emitAnalytics("embed.init")}open(e){e.externalEventId&&e.externalEventIdPrefix&&console.warn("[MomentSdk] Both externalEventId and externalEventIdPrefix provided; using externalEventId only.");const t=this.scopeOpenPayload(e);this.emitAnalytics("embed.open",{teamSlug:t.teamSlug,triggerType:t.triggerType}),this.createModal(t)}close(){this.handleCancel()}rebind(){this.unbindTriggers(),this.bindTriggers()}destroy(){this.cleanup(),this.unbindTriggers()}bindTriggers(){document.querySelectorAll(this.opts.triggerSelector).forEach(t=>{const i=n=>{n.preventDefault(),this.handleTriggerClick(t)},o=this.boundClickHandlers.get(t);o&&t.removeEventListener("click",o),t.addEventListener("click",i),this.boundClickHandlers.set(t,i)})}unbindTriggers(){this.boundClickHandlers.forEach((e,t)=>{t.removeEventListener("click",e)}),this.boundClickHandlers.clear()}handleTriggerClick(e){const t=e.getAttribute("data-moment-team-slug")??"",i=e.getAttribute("data-moment-slug")??void 0,o=e.getAttribute("data-moment-list-slug")??void 0,n=e.getAttribute("data-moment-trigger-type");let r="moment";n!=null&&n!==""&&(n==="moment"||n==="list"||n==="team"||n==="brand-playlist"||n==="playlist"||n==="schedule"?r=n:console.warn("[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",n));const h=e.getAttribute("data-moment-ids")??e.getAttribute("data-moment-game-id")??"",d=h?h.split(",").filter(Boolean):[],c=e.getAttribute("data-moment-playlist-slugs")??"",m=c?c.split(",").map(k=>k.trim()).filter(Boolean):[],a=e.getAttribute("data-moment-calendar");let l;a!=null&&a!==""&&(a==="google"||a==="outlook"?l=a:console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:",a));const p=e.getAttribute("data-api-source-id")??void 0,u=e.getAttribute("data-external-event-id")??void 0,g=e.getAttribute("data-external-event-id-prefix")??void 0;if(u&&g&&console.warn("[MomentSdk] Both data-external-event-id and data-external-event-id-prefix are set; using data-external-event-id only."),!t){console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");return}const f=r==="team"||r==="brand-playlist"||r==="playlist"||r==="schedule";this.emitAnalytics("embed.trigger.click",{teamSlug:t,triggerType:r,momentIds:f?[]:d}),this.open({teamSlug:t,momentSlug:i,listSlug:o,triggerType:r,ids:d,playlistSlugs:m,calendar:l??void 0,apiSourceId:p,externalEventId:g&&!u?void 0:u,externalEventIdPrefix:u?void 0:g})}scopeOpenPayload(e){return e.triggerType==="schedule"?{teamSlug:e.teamSlug,triggerType:e.triggerType}:e.triggerType==="playlist"?{teamSlug:e.teamSlug,triggerType:e.triggerType,playlistSlugs:e.playlistSlugs,calendar:e.calendar}:e.triggerType==="team"||e.triggerType==="brand-playlist"?{teamSlug:e.teamSlug,triggerType:e.triggerType,calendar:e.calendar}:e}createModal(e){this.cleanup(),this.previousFocusedElement=document.activeElement||null,this.activePayload=e;const t=this.buildIframeUrl(e);this.overlay=document.createElement("div"),this.overlay.setAttribute("role","presentation"),Object.assign(this.overlay.style,{position:"fixed",inset:"0",zIndex:"999999",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"rgba(0, 0, 0, 0.5)",backdropFilter:"blur(2px)",padding:"16px"}),this.overlay.addEventListener("click",l=>{l.target===this.overlay&&this.handleCancel()});const i=l=>{l.key==="Escape"&&this.handleCancel()};document.addEventListener("keydown",i);const o=document.createElement("div");Object.assign(o.style,{position:"relative",width:"100%",maxWidth:"380px",borderRadius:"16px",overflow:"hidden",backgroundColor:"#000000",boxShadow:"0 25px 50px -12px rgba(0, 0, 0, 0.25)",display:"flex",flexDirection:"column"});const n=document.createElement("div");n.setAttribute("role","dialog"),n.setAttribute("aria-modal","true"),n.setAttribute("aria-label","Calendar sync modal"),n.tabIndex=-1,Object.assign(n.style,{position:"relative",width:"100%",maxHeight:"80vh",borderRadius:"12px",overflow:"auto",backgroundColor:"#ffffff"});const r=document.createElement("button");r.type="button",r.innerHTML='<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>',r.setAttribute("aria-label","Close"),Object.assign(r.style,{position:"absolute",top:"10px",right:"10px",zIndex:"10",border:"none",backgroundColor:"transparent",color:"#999",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",padding:"0"}),r.addEventListener("mouseenter",()=>{r.style.color="#666"}),r.addEventListener("mouseleave",()=>{r.style.color="#999"}),r.addEventListener("click",()=>this.handleCancel());const h=e.triggerType!=="schedule";this.iframe=document.createElement("iframe"),this.iframe.src=t,this.iframe.setAttribute("allow",""),this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-popups allow-forms"),this.iframe.setAttribute("scrolling","no"),Object.assign(this.iframe.style,{width:"100%",border:"none",display:"block",height:"420px",overflow:"hidden",transition:"height 0.15s ease"}),h&&n.appendChild(r),n.appendChild(this.iframe);const d=document.createElement("div");Object.assign(d.style,{display:"flex",alignItems:"center",justifyContent:"center",gap:"6px",padding:"12px 0"});const c=document.createElement("span");c.textContent="Powered by",Object.assign(c.style,{fontFamily:"Inter, system-ui, -apple-system, sans-serif",fontSize:"12px",lineHeight:"16px",color:"#ffffff"});const m=document.createElement("img");m.alt="Moment",m.src=`${this.liveBaseUrl}/brand/moment-text-white.png`,Object.assign(m.style,{height:"auto",width:"67px"}),d.appendChild(c),d.appendChild(m),o.appendChild(n),o.appendChild(d),this.overlay.appendChild(o),document.body.appendChild(this.overlay);const a=l=>{if(l.key!=="Tab")return;const p=n.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(p.length===0){l.preventDefault(),n.focus();return}const u=p[0],g=p[p.length-1],f=document.activeElement;l.shiftKey&&f===u?(l.preventDefault(),g.focus()):!l.shiftKey&&f===g&&(l.preventDefault(),u.focus())};n.addEventListener("keydown",a),h?r.focus():n.focus(),this.previousBodyOverflow=document.body.style.overflow,document.body.style.overflow="hidden",this.didManageBodyOverflow=!0,this.setupMessageListener(),this.overlay._escHandler=i,this.overlay._focusTrapHandler=a}buildIframeUrl(e){return T(this.liveBaseUrl,e)}setupMessageListener(){this.messageHandler=e=>{if(e.origin!==this.liveOrigin||e.source!==this.iframe?.contentWindow)return;const t=e.data;if(!(!t||typeof t!="object")){if(t.source==="moment-live-embed"&&t.type==="moment.embed.oauth.start"){const i=t;this.emitAnalytics("oauth.start",{provider:i.provider,teamSlug:i.teamSlug}),this.handleOAuthStart(i);return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.resize"){const i=typeof t.height=="number"?t.height:0;if(i>0&&this.iframe){const o=Math.round(window.innerHeight*.8);this.iframe.style.height=`${Math.min(i,o)}px`}return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.open.url"){const{url:i}=t;i&&typeof i=="string"&&i.startsWith("webcal://")&&window.open(i,"_blank");return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.close"){this.handleCancel();return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.result"){const i=t;this.handleResult(i)}}},window.addEventListener("message",this.messageHandler)}handleOAuthStart(e){this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.popupBridge=new S({liveBaseUrl:this.liveBaseUrl,liveOrigin:this.liveOrigin,iframeWindow:this.iframe?.contentWindow??null,onResult:t=>{this.handleResult(t)},onPopupBlocked:()=>{this.emitAnalytics("oauth.popup.blocked",{provider:e.provider})},onPopupClosed:()=>{this.emitAnalytics("oauth.popup.closed",{provider:e.provider})}}),this.popupBridge.open(e)}handleResult(e){if(e.result==="success"||e.result==="partial")this.emitAnalytics("subscribe.success",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onSuccess?.(e);else if(e.result==="error")this.emitAnalytics("subscribe.error",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onError?.(e);else if(e.result==="cancelled")return;this.opts.onClose?.(e),this.emitAnalytics("embed.close"),e.triggerType!=="schedule"&&(this.closeTimer=setTimeout(()=>this.cleanup(),1500))}handleCancel(){this.emitAnalytics("embed.close");const t={source:"moment-live-embed",type:"moment.embed.result",result:"cancelled",triggerType:this.activePayload?.triggerType??"moment",momentIds:this.activePayload?.ids??[]};this.opts.onClose?.(t),this.cleanup()}cleanup(){if(this.closeTimer&&(clearTimeout(this.closeTimer),this.closeTimer=null),this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.overlay){const{_escHandler:e,_focusTrapHandler:t}=this.overlay;e&&document.removeEventListener("keydown",e);const i=this.overlay.firstElementChild;i&&t&&i.removeEventListener("keydown",t),this.overlay.remove(),this.overlay=null}this.iframe=null,this.activePayload=null,this.didManageBodyOverflow&&(document.body.style.overflow=this.previousBodyOverflow,this.didManageBodyOverflow=!1),this.previousFocusedElement&&typeof this.previousFocusedElement.focus=="function"&&this.previousFocusedElement.focus(),this.previousFocusedElement=null}emitAnalytics(e,t){this.opts.onAnalytics?.({event:e,timestamp:Date.now(),...t})}}function w(s){const e=document.createElement("button");return e.type="button",e.classList.add("moment-sync-trigger"),s.className&&s.className.split(" ").forEach(t=>{t&&e.classList.add(t)}),e.setAttribute("data-moment-team-slug",s.teamSlug),s.momentSlug&&e.setAttribute("data-moment-slug",s.momentSlug),s.listSlug&&e.setAttribute("data-moment-list-slug",s.listSlug),s.triggerType&&e.setAttribute("data-moment-trigger-type",s.triggerType),s.ids?.length&&e.setAttribute("data-moment-ids",s.ids.join(",")),s.playlistSlugs?.length&&e.setAttribute("data-moment-playlist-slugs",s.playlistSlugs.join(",")),s.calendar&&e.setAttribute("data-moment-calendar",s.calendar),s.apiSourceId&&e.setAttribute("data-api-source-id",s.apiSourceId),s.externalEventId&&e.setAttribute("data-external-event-id",s.externalEventId),s.externalEventIdPrefix&&e.setAttribute("data-external-event-id-prefix",s.externalEventIdPrefix),e.textContent=s.label??"Add to Calendar",e}if(typeof window<"u"){const s=window;s.MomentSdk=y,s.createMomentButton=w}v.MomentSdk=y,v.createMomentButton=w,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})})(this.MomentSdk=this.MomentSdk||{});
|
|
25
|
+
`;function E(){if(typeof document>"u"||document.getElementById(b))return;const n=document.createElement("style");n.id=b,n.textContent=T,document.head.appendChild(n)}const I=".moment-sync-trigger",P="https://live.dev.momentco.ai";class y{opts;liveBaseUrl;liveOrigin;overlay=null;iframe=null;wrapper=null;container=null;popupBridge=null;messageHandler=null;boundClickHandlers=new Map;activePayload=null;previousFocusedElement=null;previousBodyOverflow="";didManageBodyOverflow=!1;closeTimer=null;constructor(e){this.opts={triggerSelector:I,...e},this.liveBaseUrl=P;try{this.liveOrigin=new URL(this.liveBaseUrl).origin}catch{throw new Error(`[MomentSdk] Invalid VITE_LIVE_BASE_URL: "${this.liveBaseUrl}"`)}E(),this.bindTriggers(),this.emitAnalytics("embed.init")}open(e){e.externalEventId&&e.externalEventIdPrefix&&console.warn("[MomentSdk] Both externalEventId and externalEventIdPrefix provided; using externalEventId only.");const t=this.scopeOpenPayload(e);this.emitAnalytics("embed.open",{teamSlug:t.teamSlug,triggerType:t.triggerType}),this.createModal(t)}close(){this.handleCancel()}rebind(){this.unbindTriggers(),this.bindTriggers()}destroy(){this.cleanup(),this.unbindTriggers()}bindTriggers(){document.querySelectorAll(this.opts.triggerSelector).forEach(t=>{const i=s=>{s.preventDefault(),this.handleTriggerClick(t)},o=this.boundClickHandlers.get(t);o&&t.removeEventListener("click",o),t.addEventListener("click",i),this.boundClickHandlers.set(t,i)})}unbindTriggers(){this.boundClickHandlers.forEach((e,t)=>{t.removeEventListener("click",e)}),this.boundClickHandlers.clear()}handleTriggerClick(e){const t=e.getAttribute("data-moment-team-slug")??"",i=e.getAttribute("data-moment-slug")??void 0,o=e.getAttribute("data-moment-list-slug")??void 0,s=e.getAttribute("data-moment-trigger-type");let r="moment";s!=null&&s!==""&&(s==="moment"||s==="list"||s==="team"||s==="brand-playlist"||s==="playlist"||s==="schedule"?r=s:console.warn("[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",s));const h=e.getAttribute("data-moment-ids")??e.getAttribute("data-moment-game-id")??"",d=h?h.split(",").filter(Boolean):[],c=e.getAttribute("data-moment-playlist-slugs")??"",m=c?c.split(",").map(k=>k.trim()).filter(Boolean):[],a=e.getAttribute("data-moment-calendar");let l;a!=null&&a!==""&&(a==="google"||a==="outlook"?l=a:console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:",a));const p=e.getAttribute("data-api-source-id")??void 0,u=e.getAttribute("data-external-event-id")??void 0,g=e.getAttribute("data-external-event-id-prefix")??void 0;if(u&&g&&console.warn("[MomentSdk] Both data-external-event-id and data-external-event-id-prefix are set; using data-external-event-id only."),!t){console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");return}const f=r==="team"||r==="brand-playlist"||r==="playlist"||r==="schedule";this.emitAnalytics("embed.trigger.click",{teamSlug:t,triggerType:r,momentIds:f?[]:d}),this.open({teamSlug:t,momentSlug:i,listSlug:o,triggerType:r,ids:d,playlistSlugs:m,calendar:l??void 0,apiSourceId:p,externalEventId:g&&!u?void 0:u,externalEventIdPrefix:u?void 0:g})}scopeOpenPayload(e){return e.triggerType==="schedule"?{teamSlug:e.teamSlug,triggerType:e.triggerType}:e.triggerType==="playlist"?{teamSlug:e.teamSlug,triggerType:e.triggerType,playlistSlugs:e.playlistSlugs,calendar:e.calendar}:e.triggerType==="team"||e.triggerType==="brand-playlist"?{teamSlug:e.teamSlug,triggerType:e.triggerType,calendar:e.calendar}:e}createModal(e){this.cleanup(),this.previousFocusedElement=document.activeElement||null,this.activePayload=e;const t=this.buildIframeUrl(e);this.overlay=document.createElement("div"),this.overlay.setAttribute("role","presentation"),Object.assign(this.overlay.style,{position:"fixed",inset:"0",zIndex:"999999",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"rgba(0, 0, 0, 0.5)",backdropFilter:"blur(2px)",padding:"16px"}),this.overlay.addEventListener("click",l=>{l.target===this.overlay&&this.handleCancel()});const i=l=>{l.key==="Escape"&&this.handleCancel()};document.addEventListener("keydown",i);const o=document.createElement("div");Object.assign(o.style,{position:"relative",width:"100%",maxWidth:"380px",borderRadius:"16px",transition:"border-radius 0.3s ease",overflow:"hidden",backgroundColor:"#000000",boxShadow:"0 25px 50px -12px rgba(0, 0, 0, 0.25)",display:"flex",flexDirection:"column"});const s=document.createElement("div");s.setAttribute("role","dialog"),s.setAttribute("aria-modal","true"),s.setAttribute("aria-label","Calendar sync modal"),s.tabIndex=-1,Object.assign(s.style,{position:"relative",width:"100%",maxHeight:"80vh",borderRadius:"12px",transition:"border-radius 0.3s ease",overflow:"auto",backgroundColor:"#ffffff"});const r=document.createElement("button");r.type="button",r.innerHTML='<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>',r.setAttribute("aria-label","Close"),Object.assign(r.style,{position:"absolute",top:"10px",right:"10px",zIndex:"10",border:"none",backgroundColor:"transparent",color:"#999",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",padding:"0"}),r.addEventListener("mouseenter",()=>{r.style.color="#666"}),r.addEventListener("mouseleave",()=>{r.style.color="#999"}),r.addEventListener("click",()=>this.handleCancel());const h=e.triggerType!=="schedule";this.iframe=document.createElement("iframe"),this.iframe.src=t,this.iframe.setAttribute("allow",""),this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-popups allow-forms"),this.iframe.setAttribute("scrolling","no"),Object.assign(this.iframe.style,{width:"100%",border:"none",display:"block",height:"420px",overflow:"hidden",transition:"height 0.15s ease"}),h&&s.appendChild(r),s.appendChild(this.iframe);const d=document.createElement("div");Object.assign(d.style,{display:"flex",alignItems:"center",justifyContent:"center",gap:"6px",padding:"12px 0"});const c=document.createElement("span");c.textContent="Powered by",Object.assign(c.style,{fontFamily:"Inter, system-ui, -apple-system, sans-serif",fontSize:"12px",lineHeight:"16px",color:"#ffffff"});const m=document.createElement("img");m.alt="Moment",m.src=`${this.liveBaseUrl}/brand/moment-text-white.png`,Object.assign(m.style,{height:"auto",width:"67px"}),d.appendChild(c),d.appendChild(m),this.wrapper=o,this.container=s,o.appendChild(s),o.appendChild(d),this.overlay.appendChild(o),document.body.appendChild(this.overlay);const a=l=>{if(l.key!=="Tab")return;const p=s.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(p.length===0){l.preventDefault(),s.focus();return}const u=p[0],g=p[p.length-1],f=document.activeElement;l.shiftKey&&f===u?(l.preventDefault(),g.focus()):!l.shiftKey&&f===g&&(l.preventDefault(),u.focus())};s.addEventListener("keydown",a),h?r.focus():s.focus(),this.previousBodyOverflow=document.body.style.overflow,document.body.style.overflow="hidden",this.didManageBodyOverflow=!0,this.setupMessageListener(),this.overlay._escHandler=i,this.overlay._focusTrapHandler=a}buildIframeUrl(e){return S(this.liveBaseUrl,e)}setupMessageListener(){this.messageHandler=e=>{if(e.origin!==this.liveOrigin||e.source!==this.iframe?.contentWindow)return;const t=e.data;if(!(!t||typeof t!="object")){if(t.source==="moment-live-embed"&&t.type==="moment.embed.oauth.start"){const i=t;this.emitAnalytics("oauth.start",{provider:i.provider,teamSlug:i.teamSlug}),this.handleOAuthStart(i);return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.resize"){const i=typeof t.height=="number"?t.height:0;if(i>0&&this.iframe){const o=Math.round(window.innerHeight*.8);this.iframe.style.height=`${Math.min(i,o)}px`}return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.theme"){const{borderRadius:i}=t;if(typeof i=="number"&&Number.isFinite(i)){const o=Math.max(0,Math.min(i,32));this.container&&(this.container.style.borderRadius=`${o}px`),this.wrapper&&(this.wrapper.style.borderRadius=o===0?"0px":`${o+4}px`)}return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.open.url"){const{url:i}=t;i&&typeof i=="string"&&i.startsWith("webcal://")&&window.open(i,"_blank");return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.close"){this.handleCancel();return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.result"){const i=t;this.handleResult(i)}}},window.addEventListener("message",this.messageHandler)}handleOAuthStart(e){this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.popupBridge=new x({liveBaseUrl:this.liveBaseUrl,liveOrigin:this.liveOrigin,iframeWindow:this.iframe?.contentWindow??null,onResult:t=>{this.handleResult(t)},onPopupBlocked:()=>{this.emitAnalytics("oauth.popup.blocked",{provider:e.provider})},onPopupClosed:()=>{this.emitAnalytics("oauth.popup.closed",{provider:e.provider})}}),this.popupBridge.open(e)}handleResult(e){if(e.result==="success"||e.result==="partial")this.emitAnalytics("subscribe.success",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onSuccess?.(e);else if(e.result==="error")this.emitAnalytics("subscribe.error",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onError?.(e);else if(e.result==="cancelled")return;this.opts.onClose?.(e),this.emitAnalytics("embed.close"),e.triggerType!=="schedule"&&(this.closeTimer=setTimeout(()=>this.cleanup(),1500))}handleCancel(){this.emitAnalytics("embed.close");const t={source:"moment-live-embed",type:"moment.embed.result",result:"cancelled",triggerType:this.activePayload?.triggerType??"moment",momentIds:this.activePayload?.ids??[]};this.opts.onClose?.(t),this.cleanup()}cleanup(){if(this.closeTimer&&(clearTimeout(this.closeTimer),this.closeTimer=null),this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.overlay){const{_escHandler:e,_focusTrapHandler:t}=this.overlay;e&&document.removeEventListener("keydown",e);const i=this.overlay.firstElementChild;i&&t&&i.removeEventListener("keydown",t),this.overlay.remove(),this.overlay=null}this.iframe=null,this.wrapper=null,this.container=null,this.activePayload=null,this.didManageBodyOverflow&&(document.body.style.overflow=this.previousBodyOverflow,this.didManageBodyOverflow=!1),this.previousFocusedElement&&typeof this.previousFocusedElement.focus=="function"&&this.previousFocusedElement.focus(),this.previousFocusedElement=null}emitAnalytics(e,t){this.opts.onAnalytics?.({event:e,timestamp:Date.now(),...t})}}function w(n){const e=document.createElement("button");return e.type="button",e.classList.add("moment-sync-trigger"),n.className&&n.className.split(" ").forEach(t=>{t&&e.classList.add(t)}),e.setAttribute("data-moment-team-slug",n.teamSlug),n.momentSlug&&e.setAttribute("data-moment-slug",n.momentSlug),n.listSlug&&e.setAttribute("data-moment-list-slug",n.listSlug),n.triggerType&&e.setAttribute("data-moment-trigger-type",n.triggerType),n.ids?.length&&e.setAttribute("data-moment-ids",n.ids.join(",")),n.playlistSlugs?.length&&e.setAttribute("data-moment-playlist-slugs",n.playlistSlugs.join(",")),n.calendar&&e.setAttribute("data-moment-calendar",n.calendar),n.apiSourceId&&e.setAttribute("data-api-source-id",n.apiSourceId),n.externalEventId&&e.setAttribute("data-external-event-id",n.externalEventId),n.externalEventIdPrefix&&e.setAttribute("data-external-event-id-prefix",n.externalEventIdPrefix),e.textContent=n.label??"Add to Calendar",e}if(typeof window<"u"){const n=window;n.MomentSdk=y,n.createMomentButton=w}v.MomentSdk=y,v.createMomentButton=w,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})})(this.MomentSdk=this.MomentSdk||{});
|
package/dist/moment-sdk.mjs
CHANGED
|
@@ -108,6 +108,10 @@ class I {
|
|
|
108
108
|
liveOrigin;
|
|
109
109
|
overlay = null;
|
|
110
110
|
iframe = null;
|
|
111
|
+
// Outer black frame + inner white card — kept so a `moment.embed.theme` hint
|
|
112
|
+
// from the iframe can round them to match the in-iframe surfaces.
|
|
113
|
+
wrapper = null;
|
|
114
|
+
container = null;
|
|
111
115
|
popupBridge = null;
|
|
112
116
|
messageHandler = null;
|
|
113
117
|
boundClickHandlers = /* @__PURE__ */ new Map();
|
|
@@ -251,6 +255,10 @@ class I {
|
|
|
251
255
|
width: "100%",
|
|
252
256
|
maxWidth: "380px",
|
|
253
257
|
borderRadius: "16px",
|
|
258
|
+
// Ease radius changes so a brand's configured radius (arriving via
|
|
259
|
+
// `moment.embed.theme` after the iframe resolves its config) settles
|
|
260
|
+
// smoothly instead of snapping.
|
|
261
|
+
transition: "border-radius 0.3s ease",
|
|
254
262
|
overflow: "hidden",
|
|
255
263
|
backgroundColor: "#000000",
|
|
256
264
|
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
|
|
@@ -263,6 +271,7 @@ class I {
|
|
|
263
271
|
width: "100%",
|
|
264
272
|
maxHeight: "80vh",
|
|
265
273
|
borderRadius: "12px",
|
|
274
|
+
transition: "border-radius 0.3s ease",
|
|
266
275
|
overflow: "auto",
|
|
267
276
|
backgroundColor: "#ffffff"
|
|
268
277
|
});
|
|
@@ -315,7 +324,7 @@ class I {
|
|
|
315
324
|
m.alt = "Moment", m.src = `${this.liveBaseUrl}/brand/moment-text-white.png`, Object.assign(m.style, {
|
|
316
325
|
height: "auto",
|
|
317
326
|
width: "67px"
|
|
318
|
-
}), d.appendChild(c), d.appendChild(m), o.appendChild(n), o.appendChild(d), this.overlay.appendChild(o), document.body.appendChild(this.overlay);
|
|
327
|
+
}), d.appendChild(c), d.appendChild(m), this.wrapper = o, this.container = n, o.appendChild(n), o.appendChild(d), this.overlay.appendChild(o), document.body.appendChild(this.overlay);
|
|
319
328
|
const a = (l) => {
|
|
320
329
|
if (l.key !== "Tab") return;
|
|
321
330
|
const p = n.querySelectorAll(
|
|
@@ -355,6 +364,14 @@ class I {
|
|
|
355
364
|
}
|
|
356
365
|
return;
|
|
357
366
|
}
|
|
367
|
+
if (t.source === "moment-live-embed" && t.type === "moment.embed.theme") {
|
|
368
|
+
const { borderRadius: i } = t;
|
|
369
|
+
if (typeof i == "number" && Number.isFinite(i)) {
|
|
370
|
+
const o = Math.max(0, Math.min(i, 32));
|
|
371
|
+
this.container && (this.container.style.borderRadius = `${o}px`), this.wrapper && (this.wrapper.style.borderRadius = o === 0 ? "0px" : `${o + 4}px`);
|
|
372
|
+
}
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
358
375
|
if (t.source === "moment-live-embed" && t.type === "moment.embed.open.url") {
|
|
359
376
|
const { url: i } = t;
|
|
360
377
|
i && typeof i == "string" && i.startsWith("webcal://") && window.open(i, "_blank");
|
|
@@ -427,7 +444,7 @@ class I {
|
|
|
427
444
|
const i = this.overlay.firstElementChild;
|
|
428
445
|
i && t && i.removeEventListener("keydown", t), this.overlay.remove(), this.overlay = null;
|
|
429
446
|
}
|
|
430
|
-
this.iframe = null, this.activePayload = null, this.didManageBodyOverflow && (document.body.style.overflow = this.previousBodyOverflow, this.didManageBodyOverflow = !1), this.previousFocusedElement && typeof this.previousFocusedElement.focus == "function" && this.previousFocusedElement.focus(), this.previousFocusedElement = null;
|
|
447
|
+
this.iframe = null, this.wrapper = null, this.container = null, this.activePayload = null, this.didManageBodyOverflow && (document.body.style.overflow = this.previousBodyOverflow, this.didManageBodyOverflow = !1), this.previousFocusedElement && typeof this.previousFocusedElement.focus == "function" && this.previousFocusedElement.focus(), this.previousFocusedElement = null;
|
|
431
448
|
}
|
|
432
449
|
// ── Private: analytics ────────────────────────────────────────────
|
|
433
450
|
emitAnalytics(e, t) {
|
|
@@ -170,6 +170,19 @@ export interface MomentClosePayload {
|
|
|
170
170
|
source: 'moment-live-embed';
|
|
171
171
|
type: 'moment.embed.close';
|
|
172
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Iframe → Parent: forward the brand's resolved surface radius so the SDK can
|
|
175
|
+
* round the modal frame (wrapper + container) to match the in-iframe surfaces.
|
|
176
|
+
* Emitted only by the `schedule` experience, and only when the brand configures
|
|
177
|
+
* a radius. The SDK does NOT fetch config itself — the iframe already resolved
|
|
178
|
+
* it from the SDK config; this is purely a style hint over the trusted channel.
|
|
179
|
+
*/
|
|
180
|
+
export interface MomentThemePayload {
|
|
181
|
+
source: 'moment-live-embed';
|
|
182
|
+
type: 'moment.embed.theme';
|
|
183
|
+
/** Border radius in px for the modal frame. */
|
|
184
|
+
borderRadius: number;
|
|
185
|
+
}
|
|
173
186
|
export type MomentAnalyticsEventName = 'embed.init' | 'embed.trigger.click' | 'embed.open' | 'oauth.start' | 'oauth.popup.blocked' | 'oauth.popup.closed' | 'subscribe.success' | 'subscribe.error' | 'embed.close';
|
|
174
187
|
export interface MomentAnalyticsEvent {
|
|
175
188
|
event: MomentAnalyticsEventName;
|
|
@@ -179,4 +192,4 @@ export interface MomentAnalyticsEvent {
|
|
|
179
192
|
momentIds?: string[];
|
|
180
193
|
timestamp: number;
|
|
181
194
|
}
|
|
182
|
-
export type MomentPostMessage = MomentOAuthStartPayload | MomentOAuthStatusPayload | MomentResultPayload | MomentOpenUrlPayload | MomentClosePayload;
|
|
195
|
+
export type MomentPostMessage = MomentOAuthStartPayload | MomentOAuthStatusPayload | MomentResultPayload | MomentOpenUrlPayload | MomentClosePayload | MomentThemePayload;
|
package/package.json
CHANGED