@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 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.0...v0.1.2
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
@@ -1,4 +1,4 @@
1
- (function(v){"use strict";class S{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=n=>{if(n.origin!==this.opts.liveOrigin||n.source!==this.popup)return;const r=n.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 T(s,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"),`${s}/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"),`${s}/en/embed/sync?${i.toString()}`}const b="moment-sdk-trigger-styles",x=`
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||{});
@@ -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) {
@@ -11,6 +11,8 @@ export declare class MomentSdk {
11
11
  private liveOrigin;
12
12
  private overlay;
13
13
  private iframe;
14
+ private wrapper;
15
+ private container;
14
16
  private popupBridge;
15
17
  private messageHandler;
16
18
  private boundClickHandlers;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momentco-ai/moment-sdk",
3
- "version": "0.4.0-dev.25",
3
+ "version": "0.4.0-dev.26",
4
4
  "type": "module",
5
5
  "description": "Embeddable calendar sync widget for external team websites",
6
6
  "author": "Moment Co. <support@momentco.ai> (https://momentco.ai)",