@momentco-ai/moment-sdk 0.3.0 → 0.3.2-dev.21
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 +6 -0
- package/README.md +24 -15
- package/dist/moment-sdk.js +2 -2
- package/dist/moment-sdk.mjs +85 -70
- package/dist/types/sdk/__tests__/sync-iframe-url.test.d.ts +1 -0
- package/dist/types/sdk/button.d.ts +5 -1
- package/dist/types/sdk/sync-iframe-url.d.ts +3 -0
- package/dist/types/sdk/types.d.ts +23 -1
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ 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]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `externalEventIdPrefix` on `MomentSdkOpenPayload`, `createMomentButton()`, and `data-external-event-id-prefix` for event-level sync buttons. Live fetches all showings for the upstream event and prompts the user to pick one. Mutually exclusive with `externalEventId`.
|
|
13
|
+
|
|
8
14
|
## [0.3.0] - 2026-06-08
|
|
9
15
|
|
|
10
16
|
### Added
|
package/README.md
CHANGED
|
@@ -106,19 +106,20 @@ For `team` and `brand-playlist`, omit moment/list/ID and source-resolution attri
|
|
|
106
106
|
|
|
107
107
|
Place these on any element matching the trigger selector (default: `.moment-sync-trigger`):
|
|
108
108
|
|
|
109
|
-
| Attribute
|
|
110
|
-
|
|
|
111
|
-
| `data-moment-team-slug`
|
|
112
|
-
| `data-moment-slug`
|
|
113
|
-
| `data-moment-list-slug`
|
|
114
|
-
| `data-moment-trigger-type`
|
|
115
|
-
| `data-moment-ids`
|
|
116
|
-
| `data-moment-game-id`
|
|
117
|
-
| `data-moment-calendar`
|
|
118
|
-
| `data-api-source-id`
|
|
119
|
-
| `data-external-event-id`
|
|
120
|
-
|
|
121
|
-
|
|
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 |
|
|
121
|
+
|
|
122
|
+
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.
|
|
122
123
|
|
|
123
124
|
---
|
|
124
125
|
|
|
@@ -150,12 +151,20 @@ sdk.open({
|
|
|
150
151
|
triggerType: 'moment',
|
|
151
152
|
});
|
|
152
153
|
|
|
153
|
-
// Source-based resolution (CMS event ID)
|
|
154
|
+
// Source-based resolution (CMS event ID — single showing)
|
|
154
155
|
sdk.open({
|
|
155
156
|
teamSlug: 'your-team-slug',
|
|
156
157
|
triggerType: 'moment',
|
|
157
158
|
apiSourceId: 'your-sync-source-id',
|
|
158
|
-
externalEventId: '
|
|
159
|
+
externalEventId: '1420:2706',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Source-based resolution (event with multiple showings — user picks date)
|
|
163
|
+
sdk.open({
|
|
164
|
+
teamSlug: 'your-team-slug',
|
|
165
|
+
triggerType: 'moment',
|
|
166
|
+
apiSourceId: 'your-sync-source-id',
|
|
167
|
+
externalEventIdPrefix: '1420',
|
|
159
168
|
});
|
|
160
169
|
|
|
161
170
|
// Follow full team schedule
|
package/dist/moment-sdk.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
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),`${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",S=`
|
|
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(g))return;const s=document.createElement("style");s.id=g,s.textContent=T,document.head.appendChild(s)}const E=".moment-sync-trigger",x="https://live.momentco.ai";class f{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=x;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){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"?r=n:console.warn("[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",n));const d=e.getAttribute("data-moment-ids")??e.getAttribute("data-moment-game-id")??"",c=d?d.split(",").filter(Boolean):[],a=e.getAttribute("data-moment-calendar");let m;a!=null&&a!==""&&(a==="google"||a==="outlook"?m=a:console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:",a));const l=e.getAttribute("data-api-source-id")??void 0,u=e.getAttribute("data-external-event-id")??void 0;if(!t){console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");return}const p=r==="team"||r==="brand-playlist";this.emitAnalytics("embed.trigger.click",{teamSlug:t,triggerType:r,momentIds:p?[]:c}),this.open({teamSlug:t,momentSlug:i,listSlug:o,triggerType:r,ids:c,calendar:m??void 0,apiSourceId:l,externalEventId:u})}scopeOpenPayload(e){return 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()),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"}),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 a=document.createElement("img");a.alt="Moment",a.src=`${this.liveBaseUrl}/brand/moment-text-white.png`,Object.assign(a.style,{height:"auto",width:"67px"}),d.appendChild(c),d.appendChild(a),o.appendChild(n),o.appendChild(d),this.overlay.appendChild(o),document.body.appendChild(this.overlay);const m=l=>{if(l.key!=="Tab")return;const u=n.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(u.length===0){l.preventDefault(),n.focus();return}const p=u[0],b=u[u.length-1],y=document.activeElement;l.shiftKey&&y===p?(l.preventDefault(),b.focus()):!l.shiftKey&&y===b&&(l.preventDefault(),p.focus())};n.addEventListener("keydown",m),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=m}buildIframeUrl(e){const t=new URLSearchParams;return t.set("teamSlug",e.teamSlug),e.momentSlug&&t.set("momentSlug",e.momentSlug),e.listSlug&&t.set("listSlug",e.listSlug),t.set("triggerType",e.triggerType),e.ids?.length&&t.set("ids",e.ids.join(",")),e.calendar&&t.set("calendar",e.calendar),e.apiSourceId&&t.set("subscriptionId",e.apiSourceId),e.externalEventId&&t.set("externalId",e.externalEventId),t.set("returnOrigin",window.location.origin),t.set("embed","1"),`${this.liveBaseUrl}/en/embed/sync?${t.toString()}`}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.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 v(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.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),e.textContent=s.label??"Add to Calendar",e}if(typeof window<"u"){const s=window;s.MomentSdk=f,s.createMomentButton=v}h.MomentSdk=f,h.createMomentButton=v,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})})(this.MomentSdk=this.MomentSdk||{});
|
|
25
|
+
`;function T(){if(typeof document>"u"||document.getElementById(v))return;const n=document.createElement("style");n.id=v,n.textContent=S,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}"`)}T(),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"?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 u;o!=null&&o!==""&&(o==="google"||o==="outlook"?u=o:console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:",o));const h=e.getAttribute("data-api-source-id")??void 0,c=e.getAttribute("data-external-event-id")??void 0,p=e.getAttribute("data-external-event-id-prefix")??void 0;if(c&&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";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:u??void 0,apiSourceId:h,externalEventId:p&&!c?void 0:c,externalEventIdPrefix:c?void 0:p})}scopeOpenPayload(e){return 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",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 u=s.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(u.length===0){o.preventDefault(),s.focus();return}const h=u[0],c=u[u.length-1],p=document.activeElement;o.shiftKey&&p===h?(o.preventDefault(),c.focus()):!o.shiftKey&&p===c&&(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||{});
|
package/dist/moment-sdk.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class y {
|
|
2
2
|
popup = null;
|
|
3
3
|
pollTimer = null;
|
|
4
4
|
messageHandler = null;
|
|
@@ -12,18 +12,18 @@ class v {
|
|
|
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),
|
|
15
|
+
const t = this.buildPopupUrl(e), i = Math.round(window.screenX + (window.outerWidth - 500) / 2), l = 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=${
|
|
19
|
+
`width=500,height=700,left=${i},top=${l},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 = (
|
|
25
|
-
if (
|
|
26
|
-
const r =
|
|
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;
|
|
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
|
}
|
|
@@ -63,7 +63,11 @@ class v {
|
|
|
63
63
|
this.opts.iframeWindow.postMessage(t, this.opts.liveOrigin);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
function w(n, e, t = typeof window < "u" ? window.location.origin : "") {
|
|
67
|
+
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()}`;
|
|
69
|
+
}
|
|
70
|
+
const f = "moment-sdk-trigger-styles", x = `
|
|
67
71
|
:where(.moment-sync-trigger) {
|
|
68
72
|
background-color: #1c1917;
|
|
69
73
|
color: #ffffff;
|
|
@@ -88,13 +92,13 @@ const f = "moment-sdk-trigger-styles", b = `
|
|
|
88
92
|
outline-offset: 2px;
|
|
89
93
|
}
|
|
90
94
|
`;
|
|
91
|
-
function
|
|
95
|
+
function T() {
|
|
92
96
|
if (typeof document > "u" || document.getElementById(f)) return;
|
|
93
|
-
const
|
|
94
|
-
|
|
97
|
+
const n = document.createElement("style");
|
|
98
|
+
n.id = f, n.textContent = x, document.head.appendChild(n);
|
|
95
99
|
}
|
|
96
|
-
const
|
|
97
|
-
class
|
|
100
|
+
const E = ".moment-sync-trigger", S = "https://live.dev.momentco.ai";
|
|
101
|
+
class I {
|
|
98
102
|
opts;
|
|
99
103
|
liveBaseUrl;
|
|
100
104
|
liveOrigin;
|
|
@@ -110,20 +114,23 @@ class E {
|
|
|
110
114
|
closeTimer = null;
|
|
111
115
|
constructor(e) {
|
|
112
116
|
this.opts = {
|
|
113
|
-
triggerSelector:
|
|
117
|
+
triggerSelector: E,
|
|
114
118
|
...e
|
|
115
|
-
}, this.liveBaseUrl =
|
|
119
|
+
}, this.liveBaseUrl = S;
|
|
116
120
|
try {
|
|
117
121
|
this.liveOrigin = new URL(this.liveBaseUrl).origin;
|
|
118
122
|
} catch {
|
|
119
123
|
throw new Error(`[MomentSdk] Invalid VITE_LIVE_BASE_URL: "${this.liveBaseUrl}"`);
|
|
120
124
|
}
|
|
121
|
-
|
|
125
|
+
T(), this.bindTriggers(), this.emitAnalytics("embed.init");
|
|
122
126
|
}
|
|
123
127
|
/**
|
|
124
128
|
* Programmatically open the sync modal.
|
|
125
129
|
*/
|
|
126
130
|
open(e) {
|
|
131
|
+
e.externalEventId && e.externalEventIdPrefix && console.warn(
|
|
132
|
+
"[MomentSdk] Both externalEventId and externalEventIdPrefix provided; using externalEventId only."
|
|
133
|
+
);
|
|
127
134
|
const t = this.scopeOpenPayload(e);
|
|
128
135
|
this.emitAnalytics("embed.open", {
|
|
129
136
|
teamSlug: t.teamSlug,
|
|
@@ -151,10 +158,10 @@ class E {
|
|
|
151
158
|
// ── Private: trigger binding ──────────────────────────────────────
|
|
152
159
|
bindTriggers() {
|
|
153
160
|
document.querySelectorAll(this.opts.triggerSelector).forEach((t) => {
|
|
154
|
-
const i = (
|
|
155
|
-
|
|
156
|
-
},
|
|
157
|
-
|
|
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);
|
|
158
165
|
});
|
|
159
166
|
}
|
|
160
167
|
unbindTriggers() {
|
|
@@ -163,39 +170,48 @@ class E {
|
|
|
163
170
|
}), this.boundClickHandlers.clear();
|
|
164
171
|
}
|
|
165
172
|
handleTriggerClick(e) {
|
|
166
|
-
const t = e.getAttribute("data-moment-team-slug") ?? "", i = e.getAttribute("data-moment-slug") ?? void 0,
|
|
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");
|
|
167
174
|
let r = "moment";
|
|
168
|
-
|
|
175
|
+
s != null && s !== "" && (s === "moment" || s === "list" || s === "team" || s === "brand-playlist" || s === "playlist" ? r = s : console.warn(
|
|
169
176
|
"[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",
|
|
170
|
-
|
|
177
|
+
s
|
|
171
178
|
));
|
|
172
|
-
const
|
|
173
|
-
let
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
if (
|
|
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 u;
|
|
181
|
+
o != null && o !== "" && (o === "google" || o === "outlook" ? u = o : console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:", o));
|
|
182
|
+
const h = e.getAttribute("data-api-source-id") ?? void 0, c = e.getAttribute("data-external-event-id") ?? void 0, p = e.getAttribute("data-external-event-id-prefix") ?? void 0;
|
|
183
|
+
if (c && p && console.warn(
|
|
184
|
+
"[MomentSdk] Both data-external-event-id and data-external-event-id-prefix are set; using data-external-event-id only."
|
|
185
|
+
), !t) {
|
|
177
186
|
console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");
|
|
178
187
|
return;
|
|
179
188
|
}
|
|
180
|
-
const
|
|
189
|
+
const v = r === "team" || r === "brand-playlist" || r === "playlist";
|
|
181
190
|
this.emitAnalytics("embed.trigger.click", {
|
|
182
191
|
teamSlug: t,
|
|
183
192
|
triggerType: r,
|
|
184
|
-
momentIds:
|
|
193
|
+
momentIds: v ? [] : m
|
|
185
194
|
}), this.open({
|
|
186
195
|
teamSlug: t,
|
|
187
196
|
momentSlug: i,
|
|
188
|
-
listSlug:
|
|
197
|
+
listSlug: l,
|
|
189
198
|
triggerType: r,
|
|
190
|
-
ids:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
199
|
+
ids: m,
|
|
200
|
+
playlistSlugs: g,
|
|
201
|
+
calendar: u ?? void 0,
|
|
202
|
+
apiSourceId: h,
|
|
203
|
+
externalEventId: p && !c ? void 0 : c,
|
|
204
|
+
externalEventIdPrefix: c ? void 0 : p
|
|
194
205
|
});
|
|
195
206
|
}
|
|
196
207
|
/** Drop moment/list/source scoping for brand-level subscription triggers. */
|
|
197
208
|
scopeOpenPayload(e) {
|
|
198
|
-
return e.triggerType === "
|
|
209
|
+
return e.triggerType === "playlist" ? {
|
|
210
|
+
teamSlug: e.teamSlug,
|
|
211
|
+
triggerType: e.triggerType,
|
|
212
|
+
playlistSlugs: e.playlistSlugs,
|
|
213
|
+
calendar: e.calendar
|
|
214
|
+
} : e.triggerType === "team" || e.triggerType === "brand-playlist" ? {
|
|
199
215
|
teamSlug: e.teamSlug,
|
|
200
216
|
triggerType: e.triggerType,
|
|
201
217
|
calendar: e.calendar
|
|
@@ -215,15 +231,15 @@ class E {
|
|
|
215
231
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
216
232
|
backdropFilter: "blur(2px)",
|
|
217
233
|
padding: "16px"
|
|
218
|
-
}), this.overlay.addEventListener("click", (
|
|
219
|
-
|
|
234
|
+
}), this.overlay.addEventListener("click", (o) => {
|
|
235
|
+
o.target === this.overlay && this.handleCancel();
|
|
220
236
|
});
|
|
221
|
-
const i = (
|
|
222
|
-
|
|
237
|
+
const i = (o) => {
|
|
238
|
+
o.key === "Escape" && this.handleCancel();
|
|
223
239
|
};
|
|
224
240
|
document.addEventListener("keydown", i);
|
|
225
|
-
const
|
|
226
|
-
Object.assign(
|
|
241
|
+
const l = document.createElement("div");
|
|
242
|
+
Object.assign(l.style, {
|
|
227
243
|
position: "relative",
|
|
228
244
|
width: "100%",
|
|
229
245
|
maxWidth: "380px",
|
|
@@ -234,8 +250,8 @@ class E {
|
|
|
234
250
|
display: "flex",
|
|
235
251
|
flexDirection: "column"
|
|
236
252
|
});
|
|
237
|
-
const
|
|
238
|
-
|
|
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, {
|
|
239
255
|
position: "relative",
|
|
240
256
|
width: "100%",
|
|
241
257
|
maxHeight: "80vh",
|
|
@@ -270,44 +286,43 @@ class E {
|
|
|
270
286
|
height: "420px",
|
|
271
287
|
overflow: "hidden",
|
|
272
288
|
transition: "height 0.15s ease"
|
|
273
|
-
}),
|
|
274
|
-
const
|
|
275
|
-
Object.assign(
|
|
289
|
+
}), s.appendChild(r), s.appendChild(this.iframe);
|
|
290
|
+
const a = document.createElement("div");
|
|
291
|
+
Object.assign(a.style, {
|
|
276
292
|
display: "flex",
|
|
277
293
|
alignItems: "center",
|
|
278
294
|
justifyContent: "center",
|
|
279
295
|
gap: "6px",
|
|
280
296
|
padding: "12px 0"
|
|
281
297
|
});
|
|
282
|
-
const
|
|
283
|
-
|
|
298
|
+
const m = document.createElement("span");
|
|
299
|
+
m.textContent = "Powered by", Object.assign(m.style, {
|
|
284
300
|
fontFamily: "Inter, system-ui, -apple-system, sans-serif",
|
|
285
301
|
fontSize: "12px",
|
|
286
302
|
lineHeight: "16px",
|
|
287
303
|
color: "#ffffff"
|
|
288
304
|
});
|
|
289
|
-
const
|
|
290
|
-
|
|
305
|
+
const d = document.createElement("img");
|
|
306
|
+
d.alt = "Moment", d.src = `${this.liveBaseUrl}/brand/moment-text-white.png`, Object.assign(d.style, {
|
|
291
307
|
height: "auto",
|
|
292
308
|
width: "67px"
|
|
293
|
-
}),
|
|
294
|
-
const
|
|
295
|
-
if (
|
|
296
|
-
const u =
|
|
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 u = s.querySelectorAll(
|
|
297
313
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
298
314
|
);
|
|
299
315
|
if (u.length === 0) {
|
|
300
|
-
|
|
316
|
+
o.preventDefault(), s.focus();
|
|
301
317
|
return;
|
|
302
318
|
}
|
|
303
|
-
const
|
|
304
|
-
|
|
319
|
+
const h = u[0], c = u[u.length - 1], p = document.activeElement;
|
|
320
|
+
o.shiftKey && p === h ? (o.preventDefault(), c.focus()) : !o.shiftKey && p === c && (o.preventDefault(), h.focus());
|
|
305
321
|
};
|
|
306
|
-
|
|
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;
|
|
307
323
|
}
|
|
308
324
|
buildIframeUrl(e) {
|
|
309
|
-
|
|
310
|
-
return t.set("teamSlug", e.teamSlug), e.momentSlug && t.set("momentSlug", e.momentSlug), e.listSlug && t.set("listSlug", e.listSlug), t.set("triggerType", e.triggerType), e.ids?.length && t.set("ids", e.ids.join(",")), e.calendar && t.set("calendar", e.calendar), e.apiSourceId && t.set("subscriptionId", e.apiSourceId), e.externalEventId && t.set("externalId", e.externalEventId), t.set("returnOrigin", window.location.origin), t.set("embed", "1"), `${this.liveBaseUrl}/en/embed/sync?${t.toString()}`;
|
|
325
|
+
return w(this.liveBaseUrl, e);
|
|
311
326
|
}
|
|
312
327
|
// ── Private: message handling ─────────────────────────────────────
|
|
313
328
|
setupMessageListener() {
|
|
@@ -326,8 +341,8 @@ class E {
|
|
|
326
341
|
if (t.source === "moment-live-embed" && t.type === "moment.embed.resize") {
|
|
327
342
|
const i = typeof t.height == "number" ? t.height : 0;
|
|
328
343
|
if (i > 0 && this.iframe) {
|
|
329
|
-
const
|
|
330
|
-
this.iframe.style.height = `${Math.min(i,
|
|
344
|
+
const l = Math.round(window.innerHeight * 0.8);
|
|
345
|
+
this.iframe.style.height = `${Math.min(i, l)}px`;
|
|
331
346
|
}
|
|
332
347
|
return;
|
|
333
348
|
}
|
|
@@ -344,7 +359,7 @@ class E {
|
|
|
344
359
|
}, window.addEventListener("message", this.messageHandler);
|
|
345
360
|
}
|
|
346
361
|
handleOAuthStart(e) {
|
|
347
|
-
this.popupBridge && (this.popupBridge.cleanup(), this.popupBridge = null), this.popupBridge = new
|
|
362
|
+
this.popupBridge && (this.popupBridge.cleanup(), this.popupBridge = null), this.popupBridge = new y({
|
|
348
363
|
liveBaseUrl: this.liveBaseUrl,
|
|
349
364
|
liveOrigin: this.liveOrigin,
|
|
350
365
|
iframeWindow: this.iframe?.contentWindow ?? null,
|
|
@@ -410,17 +425,17 @@ class E {
|
|
|
410
425
|
});
|
|
411
426
|
}
|
|
412
427
|
}
|
|
413
|
-
function
|
|
428
|
+
function P(n) {
|
|
414
429
|
const e = document.createElement("button");
|
|
415
|
-
return e.type = "button", e.classList.add("moment-sync-trigger"),
|
|
430
|
+
return e.type = "button", e.classList.add("moment-sync-trigger"), n.className && n.className.split(" ").forEach((t) => {
|
|
416
431
|
t && e.classList.add(t);
|
|
417
|
-
}), e.setAttribute("data-moment-team-slug",
|
|
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;
|
|
418
433
|
}
|
|
419
434
|
if (typeof window < "u") {
|
|
420
|
-
const
|
|
421
|
-
|
|
435
|
+
const n = window;
|
|
436
|
+
n.MomentSdk = I, n.createMomentButton = P;
|
|
422
437
|
}
|
|
423
438
|
export {
|
|
424
|
-
|
|
425
|
-
|
|
439
|
+
I as MomentSdk,
|
|
440
|
+
P as createMomentButton
|
|
426
441
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -10,11 +10,15 @@ export declare function createMomentButton(options: {
|
|
|
10
10
|
listSlug?: string;
|
|
11
11
|
triggerType?: MomentTriggerType;
|
|
12
12
|
ids?: string[];
|
|
13
|
+
/** Playlist slugs offered under a `playlist` trigger (picker when >1). */
|
|
14
|
+
playlistSlugs?: string[];
|
|
13
15
|
calendar?: CalendarProvider;
|
|
14
16
|
/** Sync subscription source id; pairs with `externalEventId` for server-side resolution. */
|
|
15
17
|
apiSourceId?: string;
|
|
16
|
-
/** External event identifier resolved against `apiSourceId
|
|
18
|
+
/** External event identifier resolved against `apiSourceId` (exact showing). */
|
|
17
19
|
externalEventId?: string;
|
|
20
|
+
/** Upstream event id — Live resolves all showings and prompts user to pick. */
|
|
21
|
+
externalEventIdPrefix?: string;
|
|
18
22
|
label?: string;
|
|
19
23
|
className?: string;
|
|
20
24
|
}): HTMLButtonElement;
|
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
* - `list` → subscribe to a curated list of moments
|
|
7
7
|
* - `team` → subscribe to a team / brand schedule (all published moments)
|
|
8
8
|
* - `brand-playlist` → subscribe to the brand's default playlist
|
|
9
|
+
* - `playlist` → subscribe to one of several specific playlists. When more than
|
|
10
|
+
* one slug is supplied, Live fetches each playlist and prompts the user to
|
|
11
|
+
* pick which one to sync.
|
|
9
12
|
*/
|
|
10
|
-
export type MomentTriggerType = 'moment' | 'list' | 'team' | 'brand-playlist';
|
|
13
|
+
export type MomentTriggerType = 'moment' | 'list' | 'team' | 'brand-playlist' | 'playlist';
|
|
11
14
|
export type CalendarProvider = 'google' | 'outlook';
|
|
12
15
|
export interface MomentSdkInitOptions {
|
|
13
16
|
/** CSS selector for trigger elements. Default: ".moment-sync-trigger" */
|
|
@@ -35,6 +38,10 @@ export interface MomentSdkInitOptions {
|
|
|
35
38
|
* `externalEventId` for this trigger type.
|
|
36
39
|
* - `brand-playlist` → `teamSlug` only (Live resolves the brand's
|
|
37
40
|
* `defaultPlaylistId`). Omit the same fields as `team`.
|
|
41
|
+
* - `playlist` → `teamSlug` plus `playlistSlugs` (one or more playlist slugs).
|
|
42
|
+
* With a single slug Live syncs that playlist directly; with several it shows
|
|
43
|
+
* a picker. Omit `momentSlug`, `listSlug`, `ids`, `apiSourceId`, and
|
|
44
|
+
* `externalEventId` for this trigger type.
|
|
38
45
|
*/
|
|
39
46
|
export interface MomentSdkOpenPayload {
|
|
40
47
|
teamSlug: string;
|
|
@@ -43,6 +50,13 @@ export interface MomentSdkOpenPayload {
|
|
|
43
50
|
listSlug?: string;
|
|
44
51
|
triggerType: MomentTriggerType;
|
|
45
52
|
ids?: string[];
|
|
53
|
+
/**
|
|
54
|
+
* Playlist slugs to offer under a `playlist` trigger. Live fetches each
|
|
55
|
+
* playlist's details; with more than one slug it renders a picker so the user
|
|
56
|
+
* can choose which playlist to sync. Forwarded to the Live embed as the
|
|
57
|
+
* `playlistSlugs` query param (comma-separated).
|
|
58
|
+
*/
|
|
59
|
+
playlistSlugs?: string[];
|
|
46
60
|
calendar?: CalendarProvider;
|
|
47
61
|
/**
|
|
48
62
|
* Sync subscription source identifier (the publisher's sync-subscription id).
|
|
@@ -54,9 +68,17 @@ export interface MomentSdkOpenPayload {
|
|
|
54
68
|
/**
|
|
55
69
|
* External event identifier emitted by the publisher's source feed (e.g. the
|
|
56
70
|
* provider's native event id). Resolved against `apiSourceId` to a moment.
|
|
71
|
+
* Use the composite `event_id:showing_id` format for a single showing.
|
|
57
72
|
* Forwarded to the Live embed as the `externalId` query param.
|
|
73
|
+
* Mutually exclusive with `externalEventIdPrefix`.
|
|
58
74
|
*/
|
|
59
75
|
externalEventId?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Upstream event id without showing suffix — Live fetches all showings for
|
|
78
|
+
* that event and prompts the user to pick one. Forwarded as `externalIdPrefix`.
|
|
79
|
+
* Mutually exclusive with `externalEventId`.
|
|
80
|
+
*/
|
|
81
|
+
externalEventIdPrefix?: string;
|
|
60
82
|
}
|
|
61
83
|
/** Backend subscription resource type that the embed will create */
|
|
62
84
|
export type MomentSubscriptionType = 'moment' | 'list' | 'team' | 'playlist';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momentco-ai/moment-sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2-dev.21",
|
|
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)",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"format": "prettier --write .",
|
|
63
63
|
"format:check": "prettier --check .",
|
|
64
64
|
"quality": "pnpm run type-check && pnpm run lint && pnpm run format",
|
|
65
|
+
"test": "vitest run",
|
|
65
66
|
"examples": "pnpm build && node examples/serve.mjs",
|
|
66
67
|
"prepare": "husky"
|
|
67
68
|
},
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"prettier": "^3.2.5",
|
|
73
74
|
"typescript": "^5.9.2",
|
|
74
75
|
"typescript-eslint": "^8.32.1",
|
|
75
|
-
"vite": "^7.1.5"
|
|
76
|
+
"vite": "^7.1.5",
|
|
77
|
+
"vitest": "^3.2.4"
|
|
76
78
|
}
|
|
77
79
|
}
|