@momentco-ai/moment-sdk 0.3.3-dev.22 → 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
package/README.md CHANGED
@@ -91,14 +91,16 @@ Clicking the button opens a modal where the fan selects Google Calendar or Outlo
91
91
 
92
92
  Set `data-moment-trigger-type` (or `triggerType` in `open()` / `createMomentButton()`):
93
93
 
94
- | Type | Required attributes | Subscribes to |
95
- | ------------------ | ------------------------------------------------- | ------------------------------------------------------ |
96
- | `moment` (default) | `data-moment-team-slug` + `data-moment-slug` | One or more specific games/events |
97
- | `list` | `data-moment-team-slug` + `data-moment-list-slug` | A curated list of moments |
98
- | `team` | `data-moment-team-slug` | All published moments for the brand/team |
99
- | `brand-playlist` | `data-moment-team-slug` | The brand's default playlist (dynamic schedule thread) |
94
+ | Type | Required attributes | Subscribes to |
95
+ | ------------------ | ------------------------------------------------------ | ----------------------------------------------------------------- |
96
+ | `moment` (default) | `data-moment-team-slug` + `data-moment-slug` | One or more specific games/events |
97
+ | `list` | `data-moment-team-slug` + `data-moment-list-slug` | A curated list of moments |
98
+ | `team` | `data-moment-team-slug` | All published moments for the brand/team |
99
+ | `brand-playlist` | `data-moment-team-slug` | The brand's default playlist (dynamic schedule thread) |
100
+ | `playlist` | `data-moment-team-slug` + `data-moment-playlist-slugs` | One of several playlists (picker when >1 slug) |
101
+ | `schedule` | `data-moment-team-slug` | Full schedule experience — user picks a mix of playlists + events |
100
102
 
101
- For `team` and `brand-playlist`, omit moment/list/ID and source-resolution attributes — only `data-moment-team-slug` is required.
103
+ For `team`, `brand-playlist`, and `schedule`, omit moment/list/ID and source-resolution attributes — only `data-moment-team-slug` is required.
102
104
 
103
105
  ---
104
106
 
@@ -106,18 +108,19 @@ For `team` and `brand-playlist`, omit moment/list/ID and source-resolution attri
106
108
 
107
109
  Place these on any element matching the trigger selector (default: `.moment-sync-trigger`):
108
110
 
109
- | Attribute | Required | Description |
110
- | ------------------------------- | ------------ | ----------------------------------------------------------------- |
111
- | `data-moment-team-slug` | Yes | Team/brand slug provided by Moment |
112
- | `data-moment-slug` | For `moment` | Brand-level moment slug (resolved server-side) |
113
- | `data-moment-list-slug` | For `list` | List slug |
114
- | `data-moment-trigger-type` | No | `moment` (default), `list`, `team`, or `brand-playlist` |
115
- | `data-moment-ids` | No | Comma-separated moment IDs (fallback if slugs don't resolve) |
116
- | `data-moment-game-id` | No | Alias for `data-moment-ids` |
117
- | `data-moment-calendar` | No | Pre-select provider: `google` or `outlook` |
118
- | `data-api-source-id` | No | Sync-subscription source ID (pairs with external id attributes) |
119
- | `data-external-event-id` | No | Exact external id (`event_id:showing_id`) resolved against source |
120
- | `data-external-event-id-prefix` | No | Upstream event id only user picks a showing in the embed modal |
111
+ | Attribute | Required | Description |
112
+ | ------------------------------- | -------------- | ------------------------------------------------------------------------------- |
113
+ | `data-moment-team-slug` | Yes | Team/brand slug provided by Moment |
114
+ | `data-moment-slug` | For `moment` | Brand-level moment slug (resolved server-side) |
115
+ | `data-moment-list-slug` | For `list` | List slug |
116
+ | `data-moment-trigger-type` | No | `moment` (default), `list`, `team`, `brand-playlist`, `playlist`, or `schedule` |
117
+ | `data-moment-playlist-slugs` | For `playlist` | Comma-separated playlist slugs (picker shown when more than one) |
118
+ | `data-moment-ids` | No | Comma-separated moment IDs (fallback if slugs don't resolve) |
119
+ | `data-moment-game-id` | No | Alias for `data-moment-ids` |
120
+ | `data-moment-calendar` | No | Pre-select provider: `google` or `outlook` |
121
+ | `data-api-source-id` | No | Sync-subscription source ID (pairs with external id attributes) |
122
+ | `data-external-event-id` | No | Exact external id (`event_id:showing_id`) resolved against source |
123
+ | `data-external-event-id-prefix` | No | Upstream event id only — user picks a showing in the embed modal |
121
124
 
122
125
  When `data-api-source-id` is set with either `data-external-event-id` (one showing) or `data-external-event-id-prefix` (all showings for an event), Live resolves moments server-side — useful when your CMS emits native event IDs instead of Moment slugs.
123
126
 
@@ -273,7 +276,7 @@ The `onAnalytics` callback receives:
273
276
  }
274
277
  ```
275
278
 
276
- `result` is one of `success`, `error`, or `cancelled`. `provider` is `google` or `outlook` when applicable. `triggerType` is `moment`, `list`, `team`, or `brand-playlist`.
279
+ `result` is one of `success`, `error`, `cancelled`, or `partial` (schedule trigger, when only some resources synced). `provider` is `google`, `outlook`, or `apple` when applicable. `triggerType` echoes the trigger (`moment`, `list`, `team`, `brand-playlist`, `playlist`, or `schedule`). For `schedule`, an optional `counts: { requested, succeeded, failed }` reports the aggregate outcome.
277
280
 
278
281
  ---
279
282
 
@@ -1,4 +1,4 @@
1
- (function(f){"use strict";class w{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),l=Math.round(window.screenY+(window.outerHeight-700)/2);if(this.popup=window.open(t,"moment-oauth-popup",`width=500,height=700,left=${i},top=${l},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.selection&&i.set("selection",e.selection),`${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 x(n,e,t=typeof window<"u"?window.location.origin:""){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 v="moment-sdk-trigger-styles",T=`
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 S(){if(typeof document>"u"||document.getElementById(v))return;const n=document.createElement("style");n.id=v,n.textContent=T,document.head.appendChild(n)}const E=".moment-sync-trigger",I="https://live.dev.momentco.ai";class b{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:E,...e},this.liveBaseUrl=I;try{this.liveOrigin=new URL(this.liveBaseUrl).origin}catch{throw new Error(`[MomentSdk] Invalid VITE_LIVE_BASE_URL: "${this.liveBaseUrl}"`)}S(),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)},l=this.boundClickHandlers.get(t);l&&t.removeEventListener("click",l),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,l=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 a=e.getAttribute("data-moment-ids")??e.getAttribute("data-moment-game-id")??"",m=a?a.split(",").filter(Boolean):[],d=e.getAttribute("data-moment-playlist-slugs")??"",g=d?d.split(",").map(A=>A.trim()).filter(Boolean):[],o=e.getAttribute("data-moment-calendar");let c;o!=null&&o!==""&&(o==="google"||o==="outlook"?c=o:console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:",o));const h=e.getAttribute("data-api-source-id")??void 0,u=e.getAttribute("data-external-event-id")??void 0,p=e.getAttribute("data-external-event-id-prefix")??void 0;if(u&&p&&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 P=r==="team"||r==="brand-playlist"||r==="playlist"||r==="schedule";this.emitAnalytics("embed.trigger.click",{teamSlug:t,triggerType:r,momentIds:P?[]:m}),this.open({teamSlug:t,momentSlug:i,listSlug:l,triggerType:r,ids:m,playlistSlugs:g,calendar:c??void 0,apiSourceId:h,externalEventId:p&&!u?void 0:u,externalEventIdPrefix:u?void 0:p})}scopeOpenPayload(e){return e.triggerType==="playlist"||e.triggerType==="schedule"?{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",o=>{o.target===this.overlay&&this.handleCancel()});const i=o=>{o.key==="Escape"&&this.handleCancel()};document.addEventListener("keydown",i);const l=document.createElement("div");Object.assign(l.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 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",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()),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"}),s.appendChild(r),s.appendChild(this.iframe);const a=document.createElement("div");Object.assign(a.style,{display:"flex",alignItems:"center",justifyContent:"center",gap:"6px",padding:"12px 0"});const m=document.createElement("span");m.textContent="Powered by",Object.assign(m.style,{fontFamily:"Inter, system-ui, -apple-system, sans-serif",fontSize:"12px",lineHeight:"16px",color:"#ffffff"});const d=document.createElement("img");d.alt="Moment",d.src=`${this.liveBaseUrl}/brand/moment-text-white.png`,Object.assign(d.style,{height:"auto",width:"67px"}),a.appendChild(m),a.appendChild(d),l.appendChild(s),l.appendChild(a),this.overlay.appendChild(l),document.body.appendChild(this.overlay);const g=o=>{if(o.key!=="Tab")return;const c=s.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(c.length===0){o.preventDefault(),s.focus();return}const h=c[0],u=c[c.length-1],p=document.activeElement;o.shiftKey&&p===h?(o.preventDefault(),u.focus()):!o.shiftKey&&p===u&&(o.preventDefault(),h.focus())};s.addEventListener("keydown",g),r.focus(),this.previousBodyOverflow=document.body.style.overflow,document.body.style.overflow="hidden",this.didManageBodyOverflow=!0,this.setupMessageListener(),this.overlay._escHandler=i,this.overlay._focusTrapHandler=g}buildIframeUrl(e){return x(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 l=Math.round(window.innerHeight*.8);this.iframe.style.height=`${Math.min(i,l)}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.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 w({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")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"),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 y(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=b,n.createMomentButton=y}f.MomentSdk=b,f.createMomentButton=y,Object.defineProperty(f,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||{});
@@ -12,18 +12,18 @@ class y {
12
12
  */
13
13
  open(e) {
14
14
  this.cleanup(), this.resultReceived = !1;
15
- const t = this.buildPopupUrl(e), i = Math.round(window.screenX + (window.outerWidth - 500) / 2), l = Math.round(window.screenY + (window.outerHeight - 700) / 2);
15
+ const t = this.buildPopupUrl(e), i = Math.round(window.screenX + (window.outerWidth - 500) / 2), o = Math.round(window.screenY + (window.outerHeight - 700) / 2);
16
16
  if (this.popup = window.open(
17
17
  t,
18
18
  "moment-oauth-popup",
19
- `width=500,height=700,left=${i},top=${l},menubar=no,toolbar=no,location=yes,status=no`
19
+ `width=500,height=700,left=${i},top=${o},menubar=no,toolbar=no,location=yes,status=no`
20
20
  ), !this.popup || this.popup.closed) {
21
21
  this.sendStatusToIframe("popup-blocked"), this.opts.onPopupBlocked();
22
22
  return;
23
23
  }
24
- this.sendStatusToIframe("popup-opened"), this.startPolling(), this.messageHandler = (s) => {
25
- if (s.origin !== this.opts.liveOrigin || s.source !== this.popup) return;
26
- const r = s.data;
24
+ this.sendStatusToIframe("popup-opened"), this.startPolling(), this.messageHandler = (n) => {
25
+ if (n.origin !== this.opts.liveOrigin || n.source !== this.popup) return;
26
+ const r = n.data;
27
27
  !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());
28
28
  }, window.addEventListener("message", this.messageHandler);
29
29
  }
@@ -46,7 +46,7 @@ class y {
46
46
  triggerType: e.triggerType,
47
47
  returnOrigin: t
48
48
  });
49
- return e.subscriptionType && i.set("subscriptionType", e.subscriptionType), e.selection && i.set("selection", e.selection), `${this.opts.liveBaseUrl}/en/embed/oauth-popup?${i.toString()}`;
49
+ 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()}`;
50
50
  }
51
51
  startPolling() {
52
52
  this.pollTimer = setInterval(() => {
@@ -63,11 +63,15 @@ class y {
63
63
  this.opts.iframeWindow.postMessage(t, this.opts.liveOrigin);
64
64
  }
65
65
  }
66
- function w(n, e, t = typeof window < "u" ? window.location.origin : "") {
66
+ function w(s, e, t = typeof window < "u" ? window.location.origin : "") {
67
+ if (e.triggerType === "schedule") {
68
+ const o = new URLSearchParams();
69
+ return o.set("teamSlug", e.teamSlug), o.set("returnOrigin", t), o.set("embed", "1"), `${s}/en/embed/schedule?${o.toString()}`;
70
+ }
67
71
  const i = new URLSearchParams();
68
- 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()}`;
72
+ 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()}`;
69
73
  }
70
- const f = "moment-sdk-trigger-styles", x = `
74
+ const v = "moment-sdk-trigger-styles", x = `
71
75
  :where(.moment-sync-trigger) {
72
76
  background-color: #1c1917;
73
77
  color: #ffffff;
@@ -93,17 +97,21 @@ const f = "moment-sdk-trigger-styles", x = `
93
97
  }
94
98
  `;
95
99
  function T() {
96
- if (typeof document > "u" || document.getElementById(f)) return;
97
- const n = document.createElement("style");
98
- n.id = f, n.textContent = x, document.head.appendChild(n);
100
+ if (typeof document > "u" || document.getElementById(v)) return;
101
+ const s = document.createElement("style");
102
+ s.id = v, s.textContent = x, document.head.appendChild(s);
99
103
  }
100
- const E = ".moment-sync-trigger", S = "https://live.dev.momentco.ai";
104
+ const S = ".moment-sync-trigger", E = "https://live.dev.momentco.ai";
101
105
  class I {
102
106
  opts;
103
107
  liveBaseUrl;
104
108
  liveOrigin;
105
109
  overlay = null;
106
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;
107
115
  popupBridge = null;
108
116
  messageHandler = null;
109
117
  boundClickHandlers = /* @__PURE__ */ new Map();
@@ -114,9 +122,9 @@ class I {
114
122
  closeTimer = null;
115
123
  constructor(e) {
116
124
  this.opts = {
117
- triggerSelector: E,
125
+ triggerSelector: S,
118
126
  ...e
119
- }, this.liveBaseUrl = S;
127
+ }, this.liveBaseUrl = E;
120
128
  try {
121
129
  this.liveOrigin = new URL(this.liveBaseUrl).origin;
122
130
  } catch {
@@ -158,10 +166,10 @@ class I {
158
166
  // ── Private: trigger binding ──────────────────────────────────────
159
167
  bindTriggers() {
160
168
  document.querySelectorAll(this.opts.triggerSelector).forEach((t) => {
161
- const i = (s) => {
162
- s.preventDefault(), this.handleTriggerClick(t);
163
- }, l = this.boundClickHandlers.get(t);
164
- l && t.removeEventListener("click", l), t.addEventListener("click", i), this.boundClickHandlers.set(t, i);
169
+ const i = (n) => {
170
+ n.preventDefault(), this.handleTriggerClick(t);
171
+ }, o = this.boundClickHandlers.get(t);
172
+ o && t.removeEventListener("click", o), t.addEventListener("click", i), this.boundClickHandlers.set(t, i);
165
173
  });
166
174
  }
167
175
  unbindTriggers() {
@@ -170,43 +178,46 @@ class I {
170
178
  }), this.boundClickHandlers.clear();
171
179
  }
172
180
  handleTriggerClick(e) {
173
- const t = e.getAttribute("data-moment-team-slug") ?? "", i = e.getAttribute("data-moment-slug") ?? void 0, l = e.getAttribute("data-moment-list-slug") ?? void 0, s = e.getAttribute("data-moment-trigger-type");
181
+ 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");
174
182
  let r = "moment";
175
- s != null && s !== "" && (s === "moment" || s === "list" || s === "team" || s === "brand-playlist" || s === "playlist" || s === "schedule" ? r = s : console.warn(
183
+ n != null && n !== "" && (n === "moment" || n === "list" || n === "team" || n === "brand-playlist" || n === "playlist" || n === "schedule" ? r = n : console.warn(
176
184
  "[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",
177
- s
185
+ n
178
186
  ));
179
- const a = e.getAttribute("data-moment-ids") ?? e.getAttribute("data-moment-game-id") ?? "", m = a ? a.split(",").filter(Boolean) : [], d = e.getAttribute("data-moment-playlist-slugs") ?? "", g = d ? d.split(",").map((b) => b.trim()).filter(Boolean) : [], o = e.getAttribute("data-moment-calendar");
180
- let c;
181
- o != null && o !== "" && (o === "google" || o === "outlook" ? c = o : console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:", o));
182
- const h = e.getAttribute("data-api-source-id") ?? void 0, u = e.getAttribute("data-external-event-id") ?? void 0, p = e.getAttribute("data-external-event-id-prefix") ?? void 0;
183
- if (u && p && console.warn(
187
+ 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((b) => b.trim()).filter(Boolean) : [], a = e.getAttribute("data-moment-calendar");
188
+ let l;
189
+ a != null && a !== "" && (a === "google" || a === "outlook" ? l = a : console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:", a));
190
+ 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;
191
+ if (u && g && console.warn(
184
192
  "[MomentSdk] Both data-external-event-id and data-external-event-id-prefix are set; using data-external-event-id only."
185
193
  ), !t) {
186
194
  console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");
187
195
  return;
188
196
  }
189
- const v = r === "team" || r === "brand-playlist" || r === "playlist" || r === "schedule";
197
+ const f = r === "team" || r === "brand-playlist" || r === "playlist" || r === "schedule";
190
198
  this.emitAnalytics("embed.trigger.click", {
191
199
  teamSlug: t,
192
200
  triggerType: r,
193
- momentIds: v ? [] : m
201
+ momentIds: f ? [] : d
194
202
  }), this.open({
195
203
  teamSlug: t,
196
204
  momentSlug: i,
197
- listSlug: l,
205
+ listSlug: o,
198
206
  triggerType: r,
199
- ids: m,
200
- playlistSlugs: g,
201
- calendar: c ?? void 0,
202
- apiSourceId: h,
203
- externalEventId: p && !u ? void 0 : u,
204
- externalEventIdPrefix: u ? void 0 : p
207
+ ids: d,
208
+ playlistSlugs: m,
209
+ calendar: l ?? void 0,
210
+ apiSourceId: p,
211
+ externalEventId: g && !u ? void 0 : u,
212
+ externalEventIdPrefix: u ? void 0 : g
205
213
  });
206
214
  }
207
215
  /** Drop moment/list/source scoping for brand-level subscription triggers. */
208
216
  scopeOpenPayload(e) {
209
- return e.triggerType === "playlist" || e.triggerType === "schedule" ? {
217
+ return e.triggerType === "schedule" ? {
218
+ teamSlug: e.teamSlug,
219
+ triggerType: e.triggerType
220
+ } : e.triggerType === "playlist" ? {
210
221
  teamSlug: e.teamSlug,
211
222
  triggerType: e.triggerType,
212
223
  playlistSlugs: e.playlistSlugs,
@@ -231,31 +242,36 @@ class I {
231
242
  backgroundColor: "rgba(0, 0, 0, 0.5)",
232
243
  backdropFilter: "blur(2px)",
233
244
  padding: "16px"
234
- }), this.overlay.addEventListener("click", (o) => {
235
- o.target === this.overlay && this.handleCancel();
245
+ }), this.overlay.addEventListener("click", (l) => {
246
+ l.target === this.overlay && this.handleCancel();
236
247
  });
237
- const i = (o) => {
238
- o.key === "Escape" && this.handleCancel();
248
+ const i = (l) => {
249
+ l.key === "Escape" && this.handleCancel();
239
250
  };
240
251
  document.addEventListener("keydown", i);
241
- const l = document.createElement("div");
242
- Object.assign(l.style, {
252
+ const o = document.createElement("div");
253
+ Object.assign(o.style, {
243
254
  position: "relative",
244
255
  width: "100%",
245
256
  maxWidth: "380px",
246
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",
247
262
  overflow: "hidden",
248
263
  backgroundColor: "#000000",
249
264
  boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
250
265
  display: "flex",
251
266
  flexDirection: "column"
252
267
  });
253
- const s = document.createElement("div");
254
- s.setAttribute("role", "dialog"), s.setAttribute("aria-modal", "true"), s.setAttribute("aria-label", "Calendar sync modal"), s.tabIndex = -1, Object.assign(s.style, {
268
+ const n = document.createElement("div");
269
+ n.setAttribute("role", "dialog"), n.setAttribute("aria-modal", "true"), n.setAttribute("aria-label", "Calendar sync modal"), n.tabIndex = -1, Object.assign(n.style, {
255
270
  position: "relative",
256
271
  width: "100%",
257
272
  maxHeight: "80vh",
258
273
  borderRadius: "12px",
274
+ transition: "border-radius 0.3s ease",
259
275
  overflow: "auto",
260
276
  backgroundColor: "#ffffff"
261
277
  });
@@ -279,47 +295,49 @@ class I {
279
295
  r.style.color = "#666";
280
296
  }), r.addEventListener("mouseleave", () => {
281
297
  r.style.color = "#999";
282
- }), r.addEventListener("click", () => this.handleCancel()), 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, {
298
+ }), r.addEventListener("click", () => this.handleCancel());
299
+ const h = e.triggerType !== "schedule";
300
+ 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, {
283
301
  width: "100%",
284
302
  border: "none",
285
303
  display: "block",
286
304
  height: "420px",
287
305
  overflow: "hidden",
288
306
  transition: "height 0.15s ease"
289
- }), s.appendChild(r), s.appendChild(this.iframe);
290
- const a = document.createElement("div");
291
- Object.assign(a.style, {
307
+ }), h && n.appendChild(r), n.appendChild(this.iframe);
308
+ const d = document.createElement("div");
309
+ Object.assign(d.style, {
292
310
  display: "flex",
293
311
  alignItems: "center",
294
312
  justifyContent: "center",
295
313
  gap: "6px",
296
314
  padding: "12px 0"
297
315
  });
298
- const m = document.createElement("span");
299
- m.textContent = "Powered by", Object.assign(m.style, {
316
+ const c = document.createElement("span");
317
+ c.textContent = "Powered by", Object.assign(c.style, {
300
318
  fontFamily: "Inter, system-ui, -apple-system, sans-serif",
301
319
  fontSize: "12px",
302
320
  lineHeight: "16px",
303
321
  color: "#ffffff"
304
322
  });
305
- const d = document.createElement("img");
306
- d.alt = "Moment", d.src = `${this.liveBaseUrl}/brand/moment-text-white.png`, Object.assign(d.style, {
323
+ const m = document.createElement("img");
324
+ m.alt = "Moment", m.src = `${this.liveBaseUrl}/brand/moment-text-white.png`, Object.assign(m.style, {
307
325
  height: "auto",
308
326
  width: "67px"
309
- }), a.appendChild(m), a.appendChild(d), l.appendChild(s), l.appendChild(a), this.overlay.appendChild(l), document.body.appendChild(this.overlay);
310
- const g = (o) => {
311
- if (o.key !== "Tab") return;
312
- const c = s.querySelectorAll(
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);
328
+ const a = (l) => {
329
+ if (l.key !== "Tab") return;
330
+ const p = n.querySelectorAll(
313
331
  'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
314
332
  );
315
- if (c.length === 0) {
316
- o.preventDefault(), s.focus();
333
+ if (p.length === 0) {
334
+ l.preventDefault(), n.focus();
317
335
  return;
318
336
  }
319
- const h = c[0], u = c[c.length - 1], p = document.activeElement;
320
- o.shiftKey && p === h ? (o.preventDefault(), u.focus()) : !o.shiftKey && p === u && (o.preventDefault(), h.focus());
337
+ const u = p[0], g = p[p.length - 1], f = document.activeElement;
338
+ l.shiftKey && f === u ? (l.preventDefault(), g.focus()) : !l.shiftKey && f === g && (l.preventDefault(), u.focus());
321
339
  };
322
- s.addEventListener("keydown", g), r.focus(), this.previousBodyOverflow = document.body.style.overflow, document.body.style.overflow = "hidden", this.didManageBodyOverflow = !0, this.setupMessageListener(), this.overlay._escHandler = i, this.overlay._focusTrapHandler = g;
340
+ 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;
323
341
  }
324
342
  buildIframeUrl(e) {
325
343
  return w(this.liveBaseUrl, e);
@@ -341,8 +359,16 @@ class I {
341
359
  if (t.source === "moment-live-embed" && t.type === "moment.embed.resize") {
342
360
  const i = typeof t.height == "number" ? t.height : 0;
343
361
  if (i > 0 && this.iframe) {
344
- const l = Math.round(window.innerHeight * 0.8);
345
- this.iframe.style.height = `${Math.min(i, l)}px`;
362
+ const o = Math.round(window.innerHeight * 0.8);
363
+ this.iframe.style.height = `${Math.min(i, o)}px`;
364
+ }
365
+ return;
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`);
346
372
  }
347
373
  return;
348
374
  }
@@ -351,6 +377,10 @@ class I {
351
377
  i && typeof i == "string" && i.startsWith("webcal://") && window.open(i, "_blank");
352
378
  return;
353
379
  }
380
+ if (t.source === "moment-live-embed" && t.type === "moment.embed.close") {
381
+ this.handleCancel();
382
+ return;
383
+ }
354
384
  if (t.source === "moment-live-embed" && t.type === "moment.embed.result") {
355
385
  const i = t;
356
386
  this.handleResult(i);
@@ -379,7 +409,7 @@ class I {
379
409
  }), this.popupBridge.open(e);
380
410
  }
381
411
  handleResult(e) {
382
- if (e.result === "success")
412
+ if (e.result === "success" || e.result === "partial")
383
413
  this.emitAnalytics("subscribe.success", {
384
414
  provider: e.provider,
385
415
  triggerType: e.triggerType,
@@ -393,7 +423,7 @@ class I {
393
423
  }), this.opts.onError?.(e);
394
424
  else if (e.result === "cancelled")
395
425
  return;
396
- this.opts.onClose?.(e), this.emitAnalytics("embed.close"), this.closeTimer = setTimeout(() => this.cleanup(), 1500);
426
+ this.opts.onClose?.(e), this.emitAnalytics("embed.close"), e.triggerType !== "schedule" && (this.closeTimer = setTimeout(() => this.cleanup(), 1500));
397
427
  }
398
428
  handleCancel() {
399
429
  this.emitAnalytics("embed.close");
@@ -414,7 +444,7 @@ class I {
414
444
  const i = this.overlay.firstElementChild;
415
445
  i && t && i.removeEventListener("keydown", t), this.overlay.remove(), this.overlay = null;
416
446
  }
417
- 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;
418
448
  }
419
449
  // ── Private: analytics ────────────────────────────────────────────
420
450
  emitAnalytics(e, t) {
@@ -425,15 +455,15 @@ class I {
425
455
  });
426
456
  }
427
457
  }
428
- function P(n) {
458
+ function P(s) {
429
459
  const e = document.createElement("button");
430
- return e.type = "button", e.classList.add("moment-sync-trigger"), n.className && n.className.split(" ").forEach((t) => {
460
+ return e.type = "button", e.classList.add("moment-sync-trigger"), s.className && s.className.split(" ").forEach((t) => {
431
461
  t && e.classList.add(t);
432
- }), 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;
462
+ }), 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;
433
463
  }
434
464
  if (typeof window < "u") {
435
- const n = window;
436
- n.MomentSdk = I, n.createMomentButton = P;
465
+ const s = window;
466
+ s.MomentSdk = I, s.createMomentButton = P;
437
467
  }
438
468
  export {
439
469
  I as MomentSdk,
@@ -10,10 +10,7 @@ export declare function createMomentButton(options: {
10
10
  listSlug?: string;
11
11
  triggerType?: MomentTriggerType;
12
12
  ids?: string[];
13
- /**
14
- * Playlist slugs offered under a `playlist` trigger (picker when >1) or the
15
- * collections shown in the `schedule` trigger's multi-select modal.
16
- */
13
+ /** Playlist slugs offered under a `playlist` trigger (picker when >1). */
17
14
  playlistSlugs?: string[];
18
15
  calendar?: CalendarProvider;
19
16
  /** Sync subscription source id; pairs with `externalEventId` for server-side resolution. */
@@ -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;
@@ -9,12 +9,18 @@
9
9
  * - `playlist` → subscribe to one of several specific playlists. When more than
10
10
  * one slug is supplied, Live fetches each playlist and prompts the user to
11
11
  * pick which one to sync.
12
- * - `schedule` → open the brand's schedule modal. Live fetches the supplied
13
- * collection playlists and lets the fan multi-select which ones to sync in a
14
- * single pass (the generic "Add to Calendar" experience).
12
+ * - `schedule` → open the brand's full schedule experience: a multi-select
13
+ * surface where the fan picks any mix of the brand's collections (playlists)
14
+ * and individual events (moments) and syncs them all at once.
15
15
  */
16
16
  export type MomentTriggerType = 'moment' | 'list' | 'team' | 'brand-playlist' | 'playlist' | 'schedule';
17
- export type CalendarProvider = 'google' | 'outlook';
17
+ /**
18
+ * Calendar providers. `google` / `outlook` are the OAuth providers a partner may
19
+ * pre-select via `data-moment-calendar`. `apple` is never a hint — it only ever
20
+ * appears on a result payload's `provider` (guest webcal path), so consumers
21
+ * switching on `provider` must tolerate it.
22
+ */
23
+ export type CalendarProvider = 'google' | 'outlook' | 'apple';
18
24
  export interface MomentSdkInitOptions {
19
25
  /** CSS selector for trigger elements. Default: ".moment-sync-trigger" */
20
26
  triggerSelector?: string;
@@ -45,10 +51,8 @@ export interface MomentSdkInitOptions {
45
51
  * With a single slug Live syncs that playlist directly; with several it shows
46
52
  * a picker. Omit `momentSlug`, `listSlug`, `ids`, `apiSourceId`, and
47
53
  * `externalEventId` for this trigger type.
48
- * - `schedule` → `teamSlug` plus `playlistSlugs` (the collections to surface).
49
- * Live renders the schedule modal where the fan multi-selects collections and
50
- * syncs them together. Omit `momentSlug`, `listSlug`, `ids`, `apiSourceId`,
51
- * and `externalEventId` for this trigger type.
54
+ * - `schedule` → `teamSlug` only. Opens the brand's full schedule experience.
55
+ * Omit every other field (including `calendar`, which is ignored here).
52
56
  */
53
57
  export interface MomentSdkOpenPayload {
54
58
  teamSlug: string;
@@ -58,11 +62,10 @@ export interface MomentSdkOpenPayload {
58
62
  triggerType: MomentTriggerType;
59
63
  ids?: string[];
60
64
  /**
61
- * Playlist slugs to offer under a `playlist` or `schedule` trigger. Live
62
- * fetches each playlist's details. Under `playlist`, more than one slug
63
- * renders a single-choice picker; under `schedule`, the slugs become the
64
- * multi-select collections in the schedule modal. Forwarded to the Live embed
65
- * as the `playlistSlugs` query param (comma-separated).
65
+ * Playlist slugs to offer under a `playlist` trigger. Live fetches each
66
+ * playlist's details; with more than one slug it renders a picker so the user
67
+ * can choose which playlist to sync. Forwarded to the Live embed as the
68
+ * `playlistSlugs` query param (comma-separated).
66
69
  */
67
70
  playlistSlugs?: string[];
68
71
  calendar?: CalendarProvider;
@@ -107,11 +110,13 @@ export interface MomentOAuthStartPayload {
107
110
  */
108
111
  subscriptionType?: MomentSubscriptionType;
109
112
  /**
110
- * Mixed-type selection encoded as `type:id,id;type:id`. Set by the `schedule`
111
- * trigger when the cart mixes collections and individual events. Forwarded
112
- * verbatim to the popup, which subscribes each group; opaque to the SDK.
113
+ * Present for the `schedule` trigger only: the mixed selection's moment ids.
114
+ * Carried in the popup URL (not localStorage) so the handoff survives browser
115
+ * third-party storage partitioning, matching the single-resource `ids` flow.
113
116
  */
114
- selection?: string;
117
+ scheduleMomentIds?: string[];
118
+ /** Present for the `schedule` trigger only: the mixed selection's playlist ids. */
119
+ schedulePlaylistIds?: string[];
115
120
  returnOrigin: string;
116
121
  }
117
122
  /** Parent → Iframe: relay OAuth popup status */
@@ -124,10 +129,25 @@ export interface MomentOAuthStatusPayload {
124
129
  export interface MomentResultPayload {
125
130
  source: 'moment-live-embed';
126
131
  type: 'moment.embed.result';
127
- result: 'success' | 'error' | 'cancelled';
132
+ /**
133
+ * `partial` is emitted only by the `schedule` trigger when some — but not all —
134
+ * of a mixed selection subscribed successfully. Inspect `counts` for detail.
135
+ */
136
+ result: 'success' | 'error' | 'cancelled' | 'partial';
128
137
  provider?: CalendarProvider;
129
138
  triggerType: MomentTriggerType;
130
139
  momentIds: string[];
140
+ /**
141
+ * Aggregate outcome for multi-resource syncs (schedule trigger). `moments` and
142
+ * `playlists` break down the successful count by resource type.
143
+ */
144
+ counts?: {
145
+ requested: number;
146
+ succeeded: number;
147
+ failed: number;
148
+ moments?: number;
149
+ playlists?: number;
150
+ };
131
151
  message?: string;
132
152
  }
133
153
  /**
@@ -139,6 +159,30 @@ export interface MomentOpenUrlPayload {
139
159
  type: 'moment.embed.open.url';
140
160
  url: string;
141
161
  }
162
+ /**
163
+ * Iframe → Parent: request to close the modal.
164
+ * Used by interactive embeds (e.g. the `schedule` trigger) whose in-iframe
165
+ * controls — a header close, a "Done" on the success screen — must dismiss the
166
+ * modal. A `cancelled` result is deliberately NOT enough (the SDK keeps the
167
+ * modal open on cancel so the iframe can show retry UI), so this is explicit.
168
+ */
169
+ export interface MomentClosePayload {
170
+ source: 'moment-live-embed';
171
+ type: 'moment.embed.close';
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
+ }
142
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';
143
187
  export interface MomentAnalyticsEvent {
144
188
  event: MomentAnalyticsEventName;
@@ -148,4 +192,4 @@ export interface MomentAnalyticsEvent {
148
192
  momentIds?: string[];
149
193
  timestamp: number;
150
194
  }
151
- export type MomentPostMessage = MomentOAuthStartPayload | MomentOAuthStatusPayload | MomentResultPayload | MomentOpenUrlPayload;
195
+ export type MomentPostMessage = MomentOAuthStartPayload | MomentOAuthStatusPayload | MomentResultPayload | MomentOpenUrlPayload | MomentClosePayload | MomentThemePayload;
@@ -23,6 +23,7 @@ Opens **http://localhost:5051** with:
23
23
  | `/moment/` | `moment` |
24
24
  | `/collection/` | `list` |
25
25
  | `/brand-schedule/` | `brand-playlist` |
26
+ | `/schedule/` | `schedule` |
26
27
 
27
28
  Demo slugs are in `shared/fixtures.js`. Edit that file if your account uses different values.
28
29
 
@@ -29,6 +29,10 @@
29
29
  <h2>Brand schedule</h2>
30
30
  <p>Follow the full schedule with <code>triggerType: brand-playlist</code>.</p>
31
31
  </a>
32
+ <a class="hub-card" href="/schedule/">
33
+ <h2>Schedule</h2>
34
+ <p>Multi-select collections + events with <code>triggerType: schedule</code>.</p>
35
+ </a>
32
36
  </div>
33
37
 
34
38
  <div id="demo-log-host"></div>
@@ -0,0 +1,43 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Moment SDK — Schedule trigger</title>
7
+ <link rel="stylesheet" href="/shared/demo.css" />
8
+ </head>
9
+ <body>
10
+ <main class="demo-shell">
11
+ <div id="demo-nav" class="demo-top"></div>
12
+
13
+ <h1>Schedule trigger</h1>
14
+ <p class="lead">
15
+ The full brand schedule experience. The fan picks any mix of the brand's collections
16
+ (playlists) and individual events, then syncs them all at once. Only
17
+ <code>data-moment-team-slug</code> and <code>data-moment-trigger-type="schedule"</code>
18
+ are required.
19
+ </p>
20
+
21
+ <article class="hero-card">
22
+ <div class="card-info">
23
+ <div class="card-title">Customize your sdk-demo schedule</div>
24
+ <div class="card-meta">Team slug <code id="team-slug-label"></code></div>
25
+ </div>
26
+ <button type="button" id="schedule-trigger" class="moment-sync-trigger demo-btn">
27
+ View Schedule
28
+ </button>
29
+ </article>
30
+
31
+ <section class="section">
32
+ <h2>Embed snippet</h2>
33
+ <div class="snippet">
34
+ <button class="copy-btn" type="button" data-copy-target="snippet-code">Copy</button>
35
+ <pre id="snippet-code">Loading snippet…</pre>
36
+ </div>
37
+ </section>
38
+
39
+ <div id="demo-log-host"></div>
40
+ </main>
41
+ <script type="module" src="/schedule/main.js"></script>
42
+ </body>
43
+ </html>
@@ -0,0 +1,23 @@
1
+ import { DEMO_FIXTURES } from '../shared/fixtures.js';
2
+ import { embedSnippetHtml, initDemoSdk, mountLogPanel, mountNav } from '../shared/boot.js';
3
+
4
+ mountNav({ title: 'Schedule' });
5
+ mountLogPanel();
6
+
7
+ // Allow ?teamSlug=<brand> to point the demo at any brand on the connected
8
+ // backend (handy for local testing); falls back to the sdk-demo fixture.
9
+ const teamSlug =
10
+ new URLSearchParams(window.location.search).get('teamSlug') || DEMO_FIXTURES.teamSlug;
11
+
12
+ document.getElementById('team-slug-label').textContent = teamSlug;
13
+
14
+ const trigger = document.getElementById('schedule-trigger');
15
+ trigger.setAttribute('data-moment-team-slug', teamSlug);
16
+ trigger.setAttribute('data-moment-trigger-type', 'schedule');
17
+
18
+ document.getElementById('snippet-code').textContent = await embedSnippetHtml({
19
+ triggerType: 'schedule',
20
+ label: 'View Schedule',
21
+ });
22
+
23
+ await initDemoSdk();
@@ -26,6 +26,8 @@ const ROUTES = {
26
26
  '/collection/': 'collection/index.html',
27
27
  '/brand-schedule': 'brand-schedule/index.html',
28
28
  '/brand-schedule/': 'brand-schedule/index.html',
29
+ '/schedule': 'schedule/index.html',
30
+ '/schedule/': 'schedule/index.html',
29
31
  };
30
32
 
31
33
  function send(res, status, body, type = 'text/plain; charset=utf-8') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momentco-ai/moment-sdk",
3
- "version": "0.3.3-dev.22",
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)",
@@ -55,7 +55,6 @@
55
55
  "build": "vite build && tsc --emitDeclarationOnly --outDir dist/types",
56
56
  "build:dev": "vite build --mode development && tsc --emitDeclarationOnly --outDir dist/types",
57
57
  "build:prod": "vite build --mode production && tsc --emitDeclarationOnly --outDir dist/types",
58
- "build:local": "VITE_LIVE_BASE_URL=http://localhost:8081 vite build && tsc --emitDeclarationOnly --outDir dist/types",
59
58
  "type-check": "tsc --noEmit",
60
59
  "typecheck": "pnpm run type-check",
61
60
  "lint": "eslint .",
@@ -65,8 +64,6 @@
65
64
  "quality": "pnpm run type-check && pnpm run lint && pnpm run format",
66
65
  "test": "vitest run",
67
66
  "examples": "pnpm build && node examples/serve.mjs",
68
- "integration-test": "node integration-test/serve.mjs",
69
- "integration-test:local": "INTEGRATION_USE_LOCAL_SDK=1 node integration-test/serve.mjs",
70
67
  "prepare": "husky"
71
68
  },
72
69
  "devDependencies": {