@withone/connect 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/dist/button.d.ts +18 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/types/index.d.ts +44 -0
- package/package.json +1 -1
- package/src/button.ts +414 -0
- package/src/index.ts +4 -0
- package/src/types/index.d.ts +44 -0
- package/src/window/index.ts +1 -0
package/README.md
CHANGED
|
@@ -145,6 +145,88 @@ export function ConnectWithOne() {
|
|
|
145
145
|
| `open()` | Opens the Connect modal over the current page |
|
|
146
146
|
| `close()` | Tears down the modal frame and its listeners |
|
|
147
147
|
|
|
148
|
+
|
|
149
|
+
### Optional: the pre-built button
|
|
150
|
+
|
|
151
|
+
Any element wired to `open()` works — the button is **optional**. It
|
|
152
|
+
ships as a custom element, `<one-connect-button>`, so the SAME tag
|
|
153
|
+
works in React, Next, Vue, Svelte, or plain HTML — no refs, no mount
|
|
154
|
+
calls. Importing the package registers it. It wires the whole flow
|
|
155
|
+
itself and manages Connect → Connecting → Connected.
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
// React / Next (any framework — same tag everywhere)
|
|
159
|
+
import "@withone/connect";
|
|
160
|
+
|
|
161
|
+
export function ConnectWithOne() {
|
|
162
|
+
return (
|
|
163
|
+
<one-connect-button
|
|
164
|
+
authorize-url="/api/one/authorize"
|
|
165
|
+
label="Connect your apps"
|
|
166
|
+
platforms='[{"name":"Stripe","imageUrl":"/icons/stripe.svg"},{"name":"PostHog","imageUrl":"/icons/posthog.svg"}]'
|
|
167
|
+
onSuccess={() => {/* tokens stored server-side — refresh app state */}}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```html
|
|
174
|
+
<!-- Plain HTML / any framework -->
|
|
175
|
+
<one-connect-button
|
|
176
|
+
authorize-url="/api/one/authorize"
|
|
177
|
+
label="Connect your apps"
|
|
178
|
+
></one-connect-button>
|
|
179
|
+
<script>
|
|
180
|
+
document.querySelector("one-connect-button")
|
|
181
|
+
.addEventListener("success", () => location.reload());
|
|
182
|
+
</script>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
| Attribute | What it does |
|
|
186
|
+
|---|---|
|
|
187
|
+
| `authorize-url` | Your backend authorize route (required) |
|
|
188
|
+
| `label` | Button text (default "Connect your apps") |
|
|
189
|
+
| `variant` | `default` pill · `accent` brand pill · `block` consent card |
|
|
190
|
+
| `theme` | `light` / `dark` — matches YOUR page |
|
|
191
|
+
| `app-theme` | Theme for One's card |
|
|
192
|
+
| `platforms` | JSON array of `{name, imageUrl}` — provider chips, fan on hover |
|
|
193
|
+
| `more-count` | The `+N` chip (e.g. `274`) |
|
|
194
|
+
| `description` | Sub-line on the `block` variant |
|
|
195
|
+
| `accent-color` | Fill for the `accent` variant (lime fallback) |
|
|
196
|
+
| `connected-label` | Label after success |
|
|
197
|
+
|
|
198
|
+
Events: `success`, `error` (detail = message), `close` — or set the
|
|
199
|
+
`onSuccess` / `onError` / `onClose` function props (React 19, Vue and
|
|
200
|
+
Svelte set these naturally).
|
|
201
|
+
|
|
202
|
+
TypeScript + React: add this once so JSX accepts the tag:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
// one-connect-button.d.ts
|
|
206
|
+
declare module "react" {
|
|
207
|
+
namespace JSX {
|
|
208
|
+
interface IntrinsicElements {
|
|
209
|
+
"one-connect-button": React.DetailedHTMLProps<
|
|
210
|
+
React.HTMLAttributes<HTMLElement>,
|
|
211
|
+
HTMLElement
|
|
212
|
+
> & {
|
|
213
|
+
"authorize-url"?: string; "app-theme"?: string; label?: string;
|
|
214
|
+
variant?: string; theme?: string; platforms?: string;
|
|
215
|
+
"more-count"?: string; description?: string;
|
|
216
|
+
"accent-color"?: string; "connected-label"?: string;
|
|
217
|
+
onSuccess?: () => void; onError?: (e: string) => void;
|
|
218
|
+
onClose?: () => void;
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
export {};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Programmatic alternative: `mountConnectButton(container, options)`
|
|
227
|
+
takes the same options as an object (plus `connect: {…useOneConnect
|
|
228
|
+
props}`) and returns `{ setState, destroy }`.
|
|
229
|
+
|
|
148
230
|
## 3 · Backend — the authorize route
|
|
149
231
|
|
|
150
232
|
Generates `state` (CSRF proof) and PKCE (proof that whoever redeems the code is this server), stashes both in an httpOnly cookie, and 302s the browser to One.
|
package/dist/button.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ConnectButtonHandle, ConnectButtonOptions } from "./types";
|
|
2
|
+
export declare const mountConnectButton: (container: HTMLElement, options: ConnectButtonOptions) => ConnectButtonHandle;
|
|
3
|
+
/**
|
|
4
|
+
* <one-connect-button> — the simple path. One tag, any framework
|
|
5
|
+
* (React, Vue, Svelte, plain HTML): the element wires the whole flow
|
|
6
|
+
* itself from its attributes and manages Connect → Connecting →
|
|
7
|
+
* Connected. Registered automatically in the browser on import;
|
|
8
|
+
* defined inside a function so importing this module on a server
|
|
9
|
+
* (Next.js SSR) never touches HTMLElement.
|
|
10
|
+
*
|
|
11
|
+
* Attributes: authorize-url (required), app-theme, label, variant,
|
|
12
|
+
* theme, platforms (JSON array of {name, imageUrl}), more-count,
|
|
13
|
+
* description, accent-color, connected-label.
|
|
14
|
+
* Events: "success" | "error" (detail: message) | "close" — plus
|
|
15
|
+
* matching function properties (onSuccess/onError/onClose) that
|
|
16
|
+
* React 19 / Vue / Svelte set naturally as props.
|
|
17
|
+
*/
|
|
18
|
+
export declare function registerConnectButton(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { useOneConnect } from "./useOneConnect";
|
|
2
2
|
export { completeOneConnect } from "./complete";
|
|
3
|
-
export
|
|
3
|
+
export { mountConnectButton, registerConnectButton } from "./button";
|
|
4
|
+
export type { OneConnectProps, OneConnectHandle, OneConnectResult, ConnectButtonOptions, ConnectButtonPlatform, ConnectButtonHandle, } from "./types";
|
package/dist/index.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e="@withone/connect:result",t="one-connect-frame";function n(){const e=document.getElementById(t);e&&e.remove()}const o="one-connect-success";function i(){var e;null===(e=document.getElementById(o))||void 0===e||e.remove()}function r(){return document.getElementById(t)}const s=s=>{let a=null,c=!1;const d=()=>{"undefined"!=typeof window&&a&&(window.removeEventListener("message",a),a=null),n()},l=(e,t)=>{if(!c){c=!0,d(),"success"===e&&function(e){var t;i();const n="dark"===e,r=document.createElement("div");r.id=o,Object.assign(r.style,{position:"fixed",inset:"0",zIndex:"2147483000",display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(8, 8, 8, 0.5)",opacity:"0",transition:"opacity 160ms ease"});const s=document.createElement("div");Object.assign(s.style,{width:"520px",maxWidth:"calc(100vw - 16px)",height:"520px",maxHeight:"calc(100vh - 16px)",display:"flex",flexDirection:"column",textAlign:"center",fontFamily:"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"});const a=n?"#a1a1aa":"#6b7280",c=n?"rgba(25, 25, 25, 0.97)":"rgba(255, 255, 255, 0.97)",d=n?"rgba(255,255,255,0.08)":"rgba(228,228,223,0.9)",l=n?"rgba(45, 45, 44, 0.9)":"rgba(232, 232, 228, 0.95)";s.innerHTML=`<div style="position:relative;flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:40px 48px;border-radius:28px 28px 0 0;background:${c};border:1px solid ${d};border-bottom:0;"><button data-one-close aria-label="Close" style="position:absolute;top:16px;right:16px;width:20px;height:20px;padding:0;border:0;background:none;cursor:pointer;color:${a};line-height:0;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg></button><div style="width:56px;height:56px;border-radius:50%;background:#10b981;display:flex;align-items:center;justify-content:center;"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg></div><div style="font-size:18px;font-weight:600;letter-spacing:-0.01em;color:${n?"#fafafa":"#111114"};">Access granted</div><div style="font-size:13px;line-height:1.5;max-width:280px;color:${a};">Your tools are connected. You can pick up right where you left off.</div><button data-one-close style="width:100%;margin-top:8px;padding:11px 0;border:0;border-radius:12px;cursor:pointer;font-size:14px;font-weight:500;background:${n?"#fafafa":"#111114"};color:${n?"#111114":"#fafafa"};">Close</button></div><div style="height:48px;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:0 0 28px 28px;background:${l};border:1px solid ${d};border-top:0;"><span style="font-size:11px;color:${a};">Secured by</span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${n?"#fafafa":"#111114"};">one</span></div>`,r.appendChild(s),document.body.appendChild(r),requestAnimationFrame(()=>{r.style.opacity="1"});const u=()=>{window.removeEventListener("keydown",p),r.style.opacity="0",window.setTimeout(()=>r.remove(),180)};function p(e){"Escape"===e.key&&u()}for(const e of s.querySelectorAll("[data-one-close]"))e.addEventListener("click",u);r.addEventListener("click",e=>{e.target===r&&u()}),window.addEventListener("keydown",p),null===(t=s.querySelector("[data-one-close]"))||void 0===t||t.focus()}(s.appTheme);try{var n,r;if("success"===e)null===(n=s.onSuccess)||void 0===n||n.call(s);else null===(r=s.onError)||void 0===r||r.call(s,null!=t?t:"The connection was not completed.")}catch{}}},u=t=>{const n=t.data;if(!n)return;const o=r();if(o&&t.source===o.contentWindow)if("@withone/connect:exit"!==n.type)n.type!==e||t.origin!==window.location.origin||"success"!==n.status&&"error"!==n.status||l(n.status,n.message);else{d();try{var i;null===(i=s.onClose)||void 0===i||i.call(s)}catch{}}},p=()=>{var e;const t=r();if(!t)return;let n,o;try{var i,s;n=null!==(i=null===(s=t.contentWindow)||void 0===s?void 0:s.location.href)&&void 0!==i?i:""}catch{return}try{o=new URL(n).searchParams}catch{return}const a=o.get("one_connect");"success"!==a&&"error"!==a||(t.style.visibility="hidden",l(a,null!==(e=o.get("one_connect_message"))&&void 0!==e?e:void 0))};return{open:()=>{if("undefined"==typeof window)return;c=!1,a=u,window.addEventListener("message",a);(function(e){n();const o=document.createElement("iframe");return o.id=t,o.src=e,o.setAttribute("allowtransparency","true"),Object.assign(o.style,{position:"fixed",inset:"0",width:"100%",height:"100%",border:"0",zIndex:"2147483000",background:"transparent",colorScheme:"normal"}),document.body.appendChild(o),o})((()=>{try{const e=new URL(s.authorize.url);return s.appTheme&&(e.hash=`one_theme=${s.appTheme}`),e.toString()}catch{return s.authorize.url}})()).addEventListener("load",p)},close:()=>{i(),d()}}};function a(t={status:"success"}){if("undefined"==typeof window)return!1;if(window.parent&&window.parent!==window){const n={type:e,status:t.status,message:t.message};try{return window.parent.postMessage(n,window.location.origin),!0}catch{return!1}}return!1}export{a as completeOneConnect,s as useOneConnect};
|
|
1
|
+
const e="@withone/connect:result",t="one-connect-frame";function n(){const e=document.getElementById(t);e&&e.remove()}const o="one-connect-success";function i(){var e;null===(e=document.getElementById(o))||void 0===e||e.remove()}function r(){return document.getElementById(t)}const c=c=>{let s=null,l=!1;const a=()=>{"undefined"!=typeof window&&s&&(window.removeEventListener("message",s),s=null),n()},d=(e,t)=>{if(!l){l=!0,a(),"success"===e&&function(e){var t;i();const n="dark"===e,r=document.createElement("div");r.id=o,Object.assign(r.style,{position:"fixed",inset:"0",zIndex:"2147483000",display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(8, 8, 8, 0.5)",opacity:"0",transition:"opacity 160ms ease"});const c=document.createElement("div");Object.assign(c.style,{width:"520px",maxWidth:"calc(100vw - 16px)",height:"520px",maxHeight:"calc(100vh - 16px)",display:"flex",flexDirection:"column",textAlign:"center",fontFamily:"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"});const s=n?"#a1a1aa":"#6b7280",l=n?"rgba(25, 25, 25, 0.97)":"rgba(255, 255, 255, 0.97)",a=n?"rgba(255,255,255,0.08)":"rgba(228,228,223,0.9)",d=n?"rgba(45, 45, 44, 0.9)":"rgba(232, 232, 228, 0.95)";c.innerHTML=`<div style="position:relative;flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:40px 48px;border-radius:28px 28px 0 0;background:${l};border:1px solid ${a};border-bottom:0;"><button data-one-close aria-label="Close" style="position:absolute;top:16px;right:16px;width:20px;height:20px;padding:0;border:0;background:none;cursor:pointer;color:${s};line-height:0;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg></button><div style="width:56px;height:56px;border-radius:50%;background:#10b981;display:flex;align-items:center;justify-content:center;"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg></div><div style="font-size:18px;font-weight:600;letter-spacing:-0.01em;color:${n?"#fafafa":"#111114"};">Access granted</div><div style="font-size:13px;line-height:1.5;max-width:280px;color:${s};">Your tools are connected. You can pick up right where you left off.</div><button data-one-close style="width:100%;margin-top:8px;padding:11px 0;border:0;border-radius:12px;cursor:pointer;font-size:14px;font-weight:500;background:${n?"#fafafa":"#111114"};color:${n?"#111114":"#fafafa"};">Close</button></div><div style="height:48px;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:0 0 28px 28px;background:${d};border:1px solid ${a};border-top:0;"><span style="font-size:11px;color:${s};">Secured by</span><span style="display:inline-flex;color:${n?"#fafafa":"#111114"};"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4"><circle cx="12" cy="12" r="8.5"/></svg></span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${n?"#fafafa":"#111114"};">one</span></div>`,r.appendChild(c),document.body.appendChild(r),requestAnimationFrame(()=>{r.style.opacity="1"});const p=()=>{window.removeEventListener("keydown",u),r.style.opacity="0",window.setTimeout(()=>r.remove(),180)};function u(e){"Escape"===e.key&&p()}for(const e of c.querySelectorAll("[data-one-close]"))e.addEventListener("click",p);r.addEventListener("click",e=>{e.target===r&&p()}),window.addEventListener("keydown",u),null===(t=c.querySelector("[data-one-close]"))||void 0===t||t.focus()}(c.appTheme);try{var n,r;if("success"===e)null===(n=c.onSuccess)||void 0===n||n.call(c);else null===(r=c.onError)||void 0===r||r.call(c,null!=t?t:"The connection was not completed.")}catch{}}},p=t=>{const n=t.data;if(!n)return;const o=r();if(o&&t.source===o.contentWindow)if("@withone/connect:exit"!==n.type)n.type!==e||t.origin!==window.location.origin||"success"!==n.status&&"error"!==n.status||d(n.status,n.message);else{a();try{var i;null===(i=c.onClose)||void 0===i||i.call(c)}catch{}}},u=()=>{var e;const t=r();if(!t)return;let n,o;try{var i,c;n=null!==(i=null===(c=t.contentWindow)||void 0===c?void 0:c.location.href)&&void 0!==i?i:""}catch{return}try{o=new URL(n).searchParams}catch{return}const s=o.get("one_connect");"success"!==s&&"error"!==s||(t.style.visibility="hidden",d(s,null!==(e=o.get("one_connect_message"))&&void 0!==e?e:void 0))};return{open:()=>{if("undefined"==typeof window)return;l=!1,s=p,window.addEventListener("message",s);(function(e){n();const o=document.createElement("iframe");return o.id=t,o.src=e,o.setAttribute("allowtransparency","true"),Object.assign(o.style,{position:"fixed",inset:"0",width:"100%",height:"100%",border:"0",zIndex:"2147483000",background:"transparent",colorScheme:"normal"}),document.body.appendChild(o),o})((()=>{try{const e=new URL(c.authorize.url);return c.appTheme&&(e.hash=`one_theme=${c.appTheme}`),e.toString()}catch{return c.authorize.url}})()).addEventListener("load",u)},close:()=>{i(),a()}}};function s(t={status:"success"}){if("undefined"==typeof window)return!1;if(window.parent&&window.parent!==window){const n={type:e,status:t.status,message:t.message};try{return window.parent.postMessage(n,window.location.origin),!0}catch{return!1}}return!1}function l(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var o=n.call(e,t);if("object"!=typeof o)return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:t+""}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const a="one-connect-button-styles",d="cubic-bezier(.2,.9,.25,1)",p={btnBg:"#0A0C0B",btnFg:"#ffffff",surface:"#ffffff",fg:"#0A0C0B",muted:"#6B7280",line:"#D1D5DB",shadow:"0 1px 2px rgba(10,12,11,.08), 0 4px 14px rgba(10,12,11,.06)"},u={btnBg:"#F2F5F3",btnFg:"#0A0C0B",surface:"#101312",fg:"#F2F5F3",muted:"#8A938E",line:"#2A302D",shadow:"0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35)"},f="#CCFF00",h="#3FE3A5",m="#0A0C0B";const b=(e,t)=>{var n,o;!function(){if("undefined"==typeof document||document.getElementById(a))return;const e=document.createElement("style");e.id=a,e.textContent=[`.owcb{font:500 14.5px/1.2 inherit;font-family:inherit;letter-spacing:-.01em;cursor:pointer;border:0;display:inline-flex;align-items:center;gap:12px;padding:12px 16px 12px 14px;border-radius:12px;transition:transform .2s ${d},box-shadow .2s ${d},opacity .2s ${d}}`,".owcb:hover{transform:translateY(-1px)}",".owcb:active{transform:translateY(0)}",`.owcb:focus-visible{outline:2px solid ${h};outline-offset:3px}`,".owcb[disabled]{cursor:default;opacity:.75;transform:none}",".owcb-stack{display:inline-flex;align-items:center;flex-shrink:0}",`.owcb-chip{width:22px;height:22px;border-radius:6px;display:grid;place-items:center;overflow:hidden;margin-left:-7px;transition:margin-left .28s ${d};position:relative}`,".owcb-chip:first-child{margin-left:0}",".owcb-chip img{width:14px;height:14px;display:block;object-fit:contain}",".owcb-chip.owcb-more{font-family:ui-monospace,monospace;font-size:9px;font-weight:500;letter-spacing:-.02em}",".owcb:hover .owcb-chip{margin-left:-2px}",".owcb:hover .owcb-chip:first-child{margin-left:0}",".owcb-label{white-space:nowrap}",`.owcb-arrow{opacity:.5;transition:transform .2s ${d},opacity .2s ${d}}`,".owcb:hover .owcb-arrow{transform:translateX(2px);opacity:.8}",".owcb-spinner{width:16px;height:16px;border-radius:50%;border:2px solid currentColor;border-top-color:transparent;opacity:.7;animation:owcb-spin .7s linear infinite}","@keyframes owcb-spin{to{transform:rotate(360deg)}}",".owcb.owcb-block{display:flex;flex-direction:column;align-items:stretch;width:100%;max-width:420px;text-align:left;padding:16px;border-radius:14px;gap:0}",".owcb-block-top{display:flex;align-items:center;justify-content:space-between;gap:12px}",".owcb-block-title{font-size:15px;font-weight:600;letter-spacing:-.01em}",".owcb-block-sub{font-size:13px;line-height:1.5;margin-top:6px;font-weight:400}",".owcb-block-foot{margin-top:14px;padding-top:12px;display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:400}","@media (prefers-reduced-motion:reduce){.owcb,.owcb-chip,.owcb-arrow{transition:none}.owcb-spinner{animation-duration:1.4s}}"].join("\n"),document.head.appendChild(e)}();const i="dark"===t.theme?u:p,r=null!==(n=t.variant)&&void 0!==n?n:"default",s=null!==(o=t.label)&&void 0!==o?o:"Connect your apps";let l="idle",b=!1;const g=c({...t.connect,onSuccess:()=>{var e,n;v("connected"),null===(e=(n=t.connect).onSuccess)||void 0===e||e.call(n)},onError:e=>{var n,o;v("idle"),null===(n=(o=t.connect).onError)||void 0===n||n.call(o,e)},onClose:()=>{var e,n;v("idle"),null===(e=(n=t.connect).onClose)||void 0===e||e.call(n)}}),w=document.createElement("button");w.type="button",w.className="block"===r?"owcb owcb-block":"owcb",w.addEventListener("click",()=>{"idle"!==l&&"connected"!==l||(v("connecting"),g.open())});const x=()=>{var e,n,o;const c="accent"===r,a="block"===r,d=a?i.surface:c?null!==(e=t.accentColor)&&void 0!==e?e:f:i.btnBg,p=a?i.fg:c?m:i.btnFg,u=a?i.surface:d,b=a?i.muted:p;w.style.background="connected"!==l||a?d:h,w.style.color="connected"!==l||a?p:m,w.style.boxShadow=a?`inset 0 0 0 1px ${i.line}`:"connected"===l?"none":c?`0 1px 2px rgba(10,12,11,.1), 0 6px 20px ${null!==(n=t.accentColor)&&void 0!==n?n:f}38`:i.shadow,w.disabled="connecting"===l,w.innerHTML="";const g=function(e,t,n){var o,i;const r=null!==(o=e.platforms)&&void 0!==o?o:[];if(0===r.length&&!e.moreCount)return null;const c=document.createElement("span");c.className="owcb-stack",c.setAttribute("aria-hidden","true");for(const e of r.slice(0,4)){const n=document.createElement("span");if(n.className="owcb-chip",n.style.background="#ffffff",n.style.boxShadow=`0 0 0 1.5px ${t}`,e.imageUrl){const t=document.createElement("img");t.alt="",t.src=e.imageUrl,t.addEventListener("error",()=>{t.style.display="none",n.textContent=e.name.charAt(0).toUpperCase(),n.style.font="600 10px ui-monospace,monospace",n.style.color=m}),n.appendChild(t)}else n.textContent=e.name.charAt(0).toUpperCase(),n.style.font="600 10px ui-monospace,monospace",n.style.color=m;c.appendChild(n)}const s=(null!==(i=e.moreCount)&&void 0!==i?i:0)+Math.max(0,r.length-4);if(s>0){const e=document.createElement("span");e.className="owcb-chip owcb-more",e.style.background="#ffffff",e.style.boxShadow=`0 0 0 1.5px ${t}`,e.style.color=n,e.textContent=`+${s}`,c.appendChild(e)}return c}(t,u,b);if(a){var x;const e=document.createElement("span");e.className="owcb-block-top";const n=document.createElement("span");if(n.className="owcb-block-title",n.textContent="connecting"===l?"Connecting…":"connected"===l?null!==(x=t.connectedLabel)&&void 0!==x?x:"Connected":s,e.appendChild(n),"connecting"===l){const t=document.createElement("span");t.className="owcb-spinner",e.appendChild(t)}else g&&e.appendChild(g);if(w.appendChild(e),t.description&&"idle"===l){const e=document.createElement("span");e.className="owcb-block-sub",e.style.color=i.muted,e.textContent=t.description,w.appendChild(e)}const o=document.createElement("span");return o.className="owcb-block-foot",o.style.color=i.muted,o.style.borderTop=`1px solid ${i.line}`,o.innerHTML=`<span style="font-size:11px;color:${i.muted}">Secured by</span><span style="display:inline-flex;color:${i.fg}"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4" style="flex-shrink:0"><circle cx="12" cy="12" r="8.5"/></svg></span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${i.fg}">one</span>`,void w.appendChild(o)}if("connecting"===l){const e=document.createElement("span");e.className="owcb-spinner",w.appendChild(e)}else if("connected"===l){const e=document.createElement("span");e.innerHTML='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px"><path d="M3 8.5l3.5 3.5L13 5"/></svg>',e.style.display="inline-flex",w.appendChild(e)}else g&&w.appendChild(g);const v=document.createElement("span");if(v.className="owcb-label",v.textContent="connecting"===l?"Connecting…":"connected"===l?null!==(o=t.connectedLabel)&&void 0!==o?o:"Connected":s,w.appendChild(v),"idle"===l){const e=document.createElement("span");e.innerHTML='<svg class="owcb-arrow" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3l5 5-5 5"/></svg>',e.style.display="inline-flex",w.appendChild(e)}},v=e=>{b||(l=e,x())};return x(),e.appendChild(w),{setState:v,destroy:()=>{b=!0,g.close(),w.remove()}}};function g(){if("undefined"!=typeof window&&"undefined"!=typeof customElements&&!customElements.get("one-connect-button")){class e extends HTMLElement{constructor(...e){super(...e),l(this,"onSuccess",null),l(this,"onError",null),l(this,"onClose",null),l(this,"handle",null)}connectedCallback(){this.mount()}disconnectedCallback(){var e;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null}attributeChangedCallback(){this.isConnected&&this.handle&&this.mount()}mount(){var e,t,n,o,i,r,c,s;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null;const l=this.getAttribute("authorize-url");if(!l)return;let a;const d=this.getAttribute("platforms");if(d)try{const e=JSON.parse(d);Array.isArray(e)&&(a=e)}catch{}const p=this.getAttribute("more-count"),u=p?parseInt(p,10):void 0;this.handle=b(this,{connect:{authorize:{url:l},appTheme:null!==(t=this.getAttribute("app-theme"))&&void 0!==t?t:void 0,onSuccess:()=>{var e;null===(e=this.onSuccess)||void 0===e||e.call(this),this.dispatchEvent(new CustomEvent("success"))},onError:e=>{var t;null===(t=this.onError)||void 0===t||t.call(this,e),this.dispatchEvent(new CustomEvent("error",{detail:e}))},onClose:()=>{var e;null===(e=this.onClose)||void 0===e||e.call(this),this.dispatchEvent(new CustomEvent("close"))}},label:null!==(n=this.getAttribute("label"))&&void 0!==n?n:void 0,variant:null!==(o=this.getAttribute("variant"))&&void 0!==o?o:void 0,theme:null!==(i=this.getAttribute("theme"))&&void 0!==i?i:void 0,platforms:a,moreCount:Number.isFinite(u)?u:void 0,description:null!==(r=this.getAttribute("description"))&&void 0!==r?r:void 0,accentColor:null!==(c=this.getAttribute("accent-color"))&&void 0!==c?c:void 0,connectedLabel:null!==(s=this.getAttribute("connected-label"))&&void 0!==s?s:void 0})}}l(e,"observedAttributes",["authorize-url","app-theme","label","variant","theme","platforms","more-count","description","accent-color","connected-label"]),customElements.define("one-connect-button",e)}}g();export{s as completeOneConnect,b as mountConnectButton,g as registerConnectButton,c as useOneConnect};
|
package/dist/index.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const e="@withone/connect:result",t="one-connect-frame";function n(){const e=document.getElementById(t);e&&e.remove()}const o="one-connect-success";function i(){var e;null===(e=document.getElementById(o))||void 0===e||e.remove()}function r(){return document.getElementById(t)}exports.completeOneConnect=function(t={status:"success"}){if("undefined"==typeof window)return!1;if(window.parent&&window.parent!==window){const n={type:e,status:t.status,message:t.message};try{return window.parent.postMessage(n,window.location.origin),!0}catch{return!1}}return!1},exports.useOneConnect=s=>{let a=null,c=!1;const d=()=>{"undefined"!=typeof window&&a&&(window.removeEventListener("message",a),a=null),n()},l=(e,t)=>{if(!c){c=!0,d(),"success"===e&&function(e){var t;i();const n="dark"===e,r=document.createElement("div");r.id=o,Object.assign(r.style,{position:"fixed",inset:"0",zIndex:"2147483000",display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(8, 8, 8, 0.5)",opacity:"0",transition:"opacity 160ms ease"});const s=document.createElement("div");Object.assign(s.style,{width:"520px",maxWidth:"calc(100vw - 16px)",height:"520px",maxHeight:"calc(100vh - 16px)",display:"flex",flexDirection:"column",textAlign:"center",fontFamily:"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"});const a=n?"#a1a1aa":"#6b7280",c=n?"rgba(25, 25, 25, 0.97)":"rgba(255, 255, 255, 0.97)",d=n?"rgba(255,255,255,0.08)":"rgba(228,228,223,0.9)",l=n?"rgba(45, 45, 44, 0.9)":"rgba(232, 232, 228, 0.95)";s.innerHTML=`<div style="position:relative;flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:40px 48px;border-radius:28px 28px 0 0;background:${c};border:1px solid ${d};border-bottom:0;"><button data-one-close aria-label="Close" style="position:absolute;top:16px;right:16px;width:20px;height:20px;padding:0;border:0;background:none;cursor:pointer;color:${a};line-height:0;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg></button><div style="width:56px;height:56px;border-radius:50%;background:#10b981;display:flex;align-items:center;justify-content:center;"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg></div><div style="font-size:18px;font-weight:600;letter-spacing:-0.01em;color:${n?"#fafafa":"#111114"};">Access granted</div><div style="font-size:13px;line-height:1.5;max-width:280px;color:${a};">Your tools are connected. You can pick up right where you left off.</div><button data-one-close style="width:100%;margin-top:8px;padding:11px 0;border:0;border-radius:12px;cursor:pointer;font-size:14px;font-weight:500;background:${n?"#fafafa":"#111114"};color:${n?"#111114":"#fafafa"};">Close</button></div><div style="height:48px;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:0 0 28px 28px;background:${l};border:1px solid ${d};border-top:0;"><span style="font-size:11px;color:${a};">Secured by</span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${n?"#fafafa":"#111114"};">one</span></div>`,r.appendChild(s),document.body.appendChild(r),requestAnimationFrame(()=>{r.style.opacity="1"});const u=()=>{window.removeEventListener("keydown",p),r.style.opacity="0",window.setTimeout(()=>r.remove(),180)};function p(e){"Escape"===e.key&&u()}for(const e of s.querySelectorAll("[data-one-close]"))e.addEventListener("click",u);r.addEventListener("click",e=>{e.target===r&&u()}),window.addEventListener("keydown",p),null===(t=s.querySelector("[data-one-close]"))||void 0===t||t.focus()}(s.appTheme);try{var n,r;if("success"===e)null===(n=s.onSuccess)||void 0===n||n.call(s);else null===(r=s.onError)||void 0===r||r.call(s,null!=t?t:"The connection was not completed.")}catch{}}},u=t=>{const n=t.data;if(!n)return;const o=r();if(o&&t.source===o.contentWindow)if("@withone/connect:exit"!==n.type)n.type!==e||t.origin!==window.location.origin||"success"!==n.status&&"error"!==n.status||l(n.status,n.message);else{d();try{var i;null===(i=s.onClose)||void 0===i||i.call(s)}catch{}}},p=()=>{var e;const t=r();if(!t)return;let n,o;try{var i,s;n=null!==(i=null===(s=t.contentWindow)||void 0===s?void 0:s.location.href)&&void 0!==i?i:""}catch{return}try{o=new URL(n).searchParams}catch{return}const a=o.get("one_connect");"success"!==a&&"error"!==a||(t.style.visibility="hidden",l(a,null!==(e=o.get("one_connect_message"))&&void 0!==e?e:void 0))};return{open:()=>{if("undefined"==typeof window)return;c=!1,a=u,window.addEventListener("message",a);(function(e){n();const o=document.createElement("iframe");return o.id=t,o.src=e,o.setAttribute("allowtransparency","true"),Object.assign(o.style,{position:"fixed",inset:"0",width:"100%",height:"100%",border:"0",zIndex:"2147483000",background:"transparent",colorScheme:"normal"}),document.body.appendChild(o),o})((()=>{try{const e=new URL(s.authorize.url);return s.appTheme&&(e.hash=`one_theme=${s.appTheme}`),e.toString()}catch{return s.authorize.url}})()).addEventListener("load",p)},close:()=>{i(),d()}}};
|
|
1
|
+
"use strict";const e="@withone/connect:result",t="one-connect-frame";function n(){const e=document.getElementById(t);e&&e.remove()}const o="one-connect-success";function i(){var e;null===(e=document.getElementById(o))||void 0===e||e.remove()}function r(){return document.getElementById(t)}const c=c=>{let s=null,l=!1;const a=()=>{"undefined"!=typeof window&&s&&(window.removeEventListener("message",s),s=null),n()},d=(e,t)=>{if(!l){l=!0,a(),"success"===e&&function(e){var t;i();const n="dark"===e,r=document.createElement("div");r.id=o,Object.assign(r.style,{position:"fixed",inset:"0",zIndex:"2147483000",display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(8, 8, 8, 0.5)",opacity:"0",transition:"opacity 160ms ease"});const c=document.createElement("div");Object.assign(c.style,{width:"520px",maxWidth:"calc(100vw - 16px)",height:"520px",maxHeight:"calc(100vh - 16px)",display:"flex",flexDirection:"column",textAlign:"center",fontFamily:"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"});const s=n?"#a1a1aa":"#6b7280",l=n?"rgba(25, 25, 25, 0.97)":"rgba(255, 255, 255, 0.97)",a=n?"rgba(255,255,255,0.08)":"rgba(228,228,223,0.9)",d=n?"rgba(45, 45, 44, 0.9)":"rgba(232, 232, 228, 0.95)";c.innerHTML=`<div style="position:relative;flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:40px 48px;border-radius:28px 28px 0 0;background:${l};border:1px solid ${a};border-bottom:0;"><button data-one-close aria-label="Close" style="position:absolute;top:16px;right:16px;width:20px;height:20px;padding:0;border:0;background:none;cursor:pointer;color:${s};line-height:0;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg></button><div style="width:56px;height:56px;border-radius:50%;background:#10b981;display:flex;align-items:center;justify-content:center;"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg></div><div style="font-size:18px;font-weight:600;letter-spacing:-0.01em;color:${n?"#fafafa":"#111114"};">Access granted</div><div style="font-size:13px;line-height:1.5;max-width:280px;color:${s};">Your tools are connected. You can pick up right where you left off.</div><button data-one-close style="width:100%;margin-top:8px;padding:11px 0;border:0;border-radius:12px;cursor:pointer;font-size:14px;font-weight:500;background:${n?"#fafafa":"#111114"};color:${n?"#111114":"#fafafa"};">Close</button></div><div style="height:48px;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:0 0 28px 28px;background:${d};border:1px solid ${a};border-top:0;"><span style="font-size:11px;color:${s};">Secured by</span><span style="display:inline-flex;color:${n?"#fafafa":"#111114"};"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4"><circle cx="12" cy="12" r="8.5"/></svg></span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${n?"#fafafa":"#111114"};">one</span></div>`,r.appendChild(c),document.body.appendChild(r),requestAnimationFrame(()=>{r.style.opacity="1"});const p=()=>{window.removeEventListener("keydown",u),r.style.opacity="0",window.setTimeout(()=>r.remove(),180)};function u(e){"Escape"===e.key&&p()}for(const e of c.querySelectorAll("[data-one-close]"))e.addEventListener("click",p);r.addEventListener("click",e=>{e.target===r&&p()}),window.addEventListener("keydown",u),null===(t=c.querySelector("[data-one-close]"))||void 0===t||t.focus()}(c.appTheme);try{var n,r;if("success"===e)null===(n=c.onSuccess)||void 0===n||n.call(c);else null===(r=c.onError)||void 0===r||r.call(c,null!=t?t:"The connection was not completed.")}catch{}}},p=t=>{const n=t.data;if(!n)return;const o=r();if(o&&t.source===o.contentWindow)if("@withone/connect:exit"!==n.type)n.type!==e||t.origin!==window.location.origin||"success"!==n.status&&"error"!==n.status||d(n.status,n.message);else{a();try{var i;null===(i=c.onClose)||void 0===i||i.call(c)}catch{}}},u=()=>{var e;const t=r();if(!t)return;let n,o;try{var i,c;n=null!==(i=null===(c=t.contentWindow)||void 0===c?void 0:c.location.href)&&void 0!==i?i:""}catch{return}try{o=new URL(n).searchParams}catch{return}const s=o.get("one_connect");"success"!==s&&"error"!==s||(t.style.visibility="hidden",d(s,null!==(e=o.get("one_connect_message"))&&void 0!==e?e:void 0))};return{open:()=>{if("undefined"==typeof window)return;l=!1,s=p,window.addEventListener("message",s);(function(e){n();const o=document.createElement("iframe");return o.id=t,o.src=e,o.setAttribute("allowtransparency","true"),Object.assign(o.style,{position:"fixed",inset:"0",width:"100%",height:"100%",border:"0",zIndex:"2147483000",background:"transparent",colorScheme:"normal"}),document.body.appendChild(o),o})((()=>{try{const e=new URL(c.authorize.url);return c.appTheme&&(e.hash=`one_theme=${c.appTheme}`),e.toString()}catch{return c.authorize.url}})()).addEventListener("load",u)},close:()=>{i(),a()}}};function s(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var o=n.call(e,t);if("object"!=typeof o)return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:t+""}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const l="one-connect-button-styles",a="cubic-bezier(.2,.9,.25,1)",d={btnBg:"#0A0C0B",btnFg:"#ffffff",surface:"#ffffff",fg:"#0A0C0B",muted:"#6B7280",line:"#D1D5DB",shadow:"0 1px 2px rgba(10,12,11,.08), 0 4px 14px rgba(10,12,11,.06)"},p={btnBg:"#F2F5F3",btnFg:"#0A0C0B",surface:"#101312",fg:"#F2F5F3",muted:"#8A938E",line:"#2A302D",shadow:"0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35)"},u="#CCFF00",f="#3FE3A5",h="#0A0C0B";const m=(e,t)=>{var n,o;!function(){if("undefined"==typeof document||document.getElementById(l))return;const e=document.createElement("style");e.id=l,e.textContent=[`.owcb{font:500 14.5px/1.2 inherit;font-family:inherit;letter-spacing:-.01em;cursor:pointer;border:0;display:inline-flex;align-items:center;gap:12px;padding:12px 16px 12px 14px;border-radius:12px;transition:transform .2s ${a},box-shadow .2s ${a},opacity .2s ${a}}`,".owcb:hover{transform:translateY(-1px)}",".owcb:active{transform:translateY(0)}",`.owcb:focus-visible{outline:2px solid ${f};outline-offset:3px}`,".owcb[disabled]{cursor:default;opacity:.75;transform:none}",".owcb-stack{display:inline-flex;align-items:center;flex-shrink:0}",`.owcb-chip{width:22px;height:22px;border-radius:6px;display:grid;place-items:center;overflow:hidden;margin-left:-7px;transition:margin-left .28s ${a};position:relative}`,".owcb-chip:first-child{margin-left:0}",".owcb-chip img{width:14px;height:14px;display:block;object-fit:contain}",".owcb-chip.owcb-more{font-family:ui-monospace,monospace;font-size:9px;font-weight:500;letter-spacing:-.02em}",".owcb:hover .owcb-chip{margin-left:-2px}",".owcb:hover .owcb-chip:first-child{margin-left:0}",".owcb-label{white-space:nowrap}",`.owcb-arrow{opacity:.5;transition:transform .2s ${a},opacity .2s ${a}}`,".owcb:hover .owcb-arrow{transform:translateX(2px);opacity:.8}",".owcb-spinner{width:16px;height:16px;border-radius:50%;border:2px solid currentColor;border-top-color:transparent;opacity:.7;animation:owcb-spin .7s linear infinite}","@keyframes owcb-spin{to{transform:rotate(360deg)}}",".owcb.owcb-block{display:flex;flex-direction:column;align-items:stretch;width:100%;max-width:420px;text-align:left;padding:16px;border-radius:14px;gap:0}",".owcb-block-top{display:flex;align-items:center;justify-content:space-between;gap:12px}",".owcb-block-title{font-size:15px;font-weight:600;letter-spacing:-.01em}",".owcb-block-sub{font-size:13px;line-height:1.5;margin-top:6px;font-weight:400}",".owcb-block-foot{margin-top:14px;padding-top:12px;display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:400}","@media (prefers-reduced-motion:reduce){.owcb,.owcb-chip,.owcb-arrow{transition:none}.owcb-spinner{animation-duration:1.4s}}"].join("\n"),document.head.appendChild(e)}();const i="dark"===t.theme?p:d,r=null!==(n=t.variant)&&void 0!==n?n:"default",s=null!==(o=t.label)&&void 0!==o?o:"Connect your apps";let m="idle",b=!1;const g=c({...t.connect,onSuccess:()=>{var e,n;v("connected"),null===(e=(n=t.connect).onSuccess)||void 0===e||e.call(n)},onError:e=>{var n,o;v("idle"),null===(n=(o=t.connect).onError)||void 0===n||n.call(o,e)},onClose:()=>{var e,n;v("idle"),null===(e=(n=t.connect).onClose)||void 0===e||e.call(n)}}),w=document.createElement("button");w.type="button",w.className="block"===r?"owcb owcb-block":"owcb",w.addEventListener("click",()=>{"idle"!==m&&"connected"!==m||(v("connecting"),g.open())});const x=()=>{var e,n,o;const c="accent"===r,l="block"===r,a=l?i.surface:c?null!==(e=t.accentColor)&&void 0!==e?e:u:i.btnBg,d=l?i.fg:c?h:i.btnFg,p=l?i.surface:a,b=l?i.muted:d;w.style.background="connected"!==m||l?a:f,w.style.color="connected"!==m||l?d:h,w.style.boxShadow=l?`inset 0 0 0 1px ${i.line}`:"connected"===m?"none":c?`0 1px 2px rgba(10,12,11,.1), 0 6px 20px ${null!==(n=t.accentColor)&&void 0!==n?n:u}38`:i.shadow,w.disabled="connecting"===m,w.innerHTML="";const g=function(e,t,n){var o,i;const r=null!==(o=e.platforms)&&void 0!==o?o:[];if(0===r.length&&!e.moreCount)return null;const c=document.createElement("span");c.className="owcb-stack",c.setAttribute("aria-hidden","true");for(const e of r.slice(0,4)){const n=document.createElement("span");if(n.className="owcb-chip",n.style.background="#ffffff",n.style.boxShadow=`0 0 0 1.5px ${t}`,e.imageUrl){const t=document.createElement("img");t.alt="",t.src=e.imageUrl,t.addEventListener("error",()=>{t.style.display="none",n.textContent=e.name.charAt(0).toUpperCase(),n.style.font="600 10px ui-monospace,monospace",n.style.color=h}),n.appendChild(t)}else n.textContent=e.name.charAt(0).toUpperCase(),n.style.font="600 10px ui-monospace,monospace",n.style.color=h;c.appendChild(n)}const s=(null!==(i=e.moreCount)&&void 0!==i?i:0)+Math.max(0,r.length-4);if(s>0){const e=document.createElement("span");e.className="owcb-chip owcb-more",e.style.background="#ffffff",e.style.boxShadow=`0 0 0 1.5px ${t}`,e.style.color=n,e.textContent=`+${s}`,c.appendChild(e)}return c}(t,p,b);if(l){var x;const e=document.createElement("span");e.className="owcb-block-top";const n=document.createElement("span");if(n.className="owcb-block-title",n.textContent="connecting"===m?"Connecting…":"connected"===m?null!==(x=t.connectedLabel)&&void 0!==x?x:"Connected":s,e.appendChild(n),"connecting"===m){const t=document.createElement("span");t.className="owcb-spinner",e.appendChild(t)}else g&&e.appendChild(g);if(w.appendChild(e),t.description&&"idle"===m){const e=document.createElement("span");e.className="owcb-block-sub",e.style.color=i.muted,e.textContent=t.description,w.appendChild(e)}const o=document.createElement("span");return o.className="owcb-block-foot",o.style.color=i.muted,o.style.borderTop=`1px solid ${i.line}`,o.innerHTML=`<span style="font-size:11px;color:${i.muted}">Secured by</span><span style="display:inline-flex;color:${i.fg}"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4" style="flex-shrink:0"><circle cx="12" cy="12" r="8.5"/></svg></span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${i.fg}">one</span>`,void w.appendChild(o)}if("connecting"===m){const e=document.createElement("span");e.className="owcb-spinner",w.appendChild(e)}else if("connected"===m){const e=document.createElement("span");e.innerHTML='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px"><path d="M3 8.5l3.5 3.5L13 5"/></svg>',e.style.display="inline-flex",w.appendChild(e)}else g&&w.appendChild(g);const v=document.createElement("span");if(v.className="owcb-label",v.textContent="connecting"===m?"Connecting…":"connected"===m?null!==(o=t.connectedLabel)&&void 0!==o?o:"Connected":s,w.appendChild(v),"idle"===m){const e=document.createElement("span");e.innerHTML='<svg class="owcb-arrow" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3l5 5-5 5"/></svg>',e.style.display="inline-flex",w.appendChild(e)}},v=e=>{b||(m=e,x())};return x(),e.appendChild(w),{setState:v,destroy:()=>{b=!0,g.close(),w.remove()}}};function b(){if("undefined"!=typeof window&&"undefined"!=typeof customElements&&!customElements.get("one-connect-button")){class e extends HTMLElement{constructor(...e){super(...e),s(this,"onSuccess",null),s(this,"onError",null),s(this,"onClose",null),s(this,"handle",null)}connectedCallback(){this.mount()}disconnectedCallback(){var e;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null}attributeChangedCallback(){this.isConnected&&this.handle&&this.mount()}mount(){var e,t,n,o,i,r,c,s;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null;const l=this.getAttribute("authorize-url");if(!l)return;let a;const d=this.getAttribute("platforms");if(d)try{const e=JSON.parse(d);Array.isArray(e)&&(a=e)}catch{}const p=this.getAttribute("more-count"),u=p?parseInt(p,10):void 0;this.handle=m(this,{connect:{authorize:{url:l},appTheme:null!==(t=this.getAttribute("app-theme"))&&void 0!==t?t:void 0,onSuccess:()=>{var e;null===(e=this.onSuccess)||void 0===e||e.call(this),this.dispatchEvent(new CustomEvent("success"))},onError:e=>{var t;null===(t=this.onError)||void 0===t||t.call(this,e),this.dispatchEvent(new CustomEvent("error",{detail:e}))},onClose:()=>{var e;null===(e=this.onClose)||void 0===e||e.call(this),this.dispatchEvent(new CustomEvent("close"))}},label:null!==(n=this.getAttribute("label"))&&void 0!==n?n:void 0,variant:null!==(o=this.getAttribute("variant"))&&void 0!==o?o:void 0,theme:null!==(i=this.getAttribute("theme"))&&void 0!==i?i:void 0,platforms:a,moreCount:Number.isFinite(u)?u:void 0,description:null!==(r=this.getAttribute("description"))&&void 0!==r?r:void 0,accentColor:null!==(c=this.getAttribute("accent-color"))&&void 0!==c?c:void 0,connectedLabel:null!==(s=this.getAttribute("connected-label"))&&void 0!==s?s:void 0})}}s(e,"observedAttributes",["authorize-url","app-theme","label","variant","theme","platforms","more-count","description","accent-color","connected-label"]),customElements.define("one-connect-button",e)}}b(),exports.completeOneConnect=function(t={status:"success"}){if("undefined"==typeof window)return!1;if(window.parent&&window.parent!==window){const n={type:e,status:t.status,message:t.message};try{return window.parent.postMessage(n,window.location.origin),!0}catch{return!1}}return!1},exports.mountConnectButton=m,exports.registerConnectButton=b,exports.useOneConnect=c;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -50,3 +50,47 @@ export interface OneConnectMessage {
|
|
|
50
50
|
status: "success" | "error";
|
|
51
51
|
message?: string;
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
/** A provider chip on the pre-built button. The SDK ships no One URLs,
|
|
55
|
+
* so icon sources are the consumer's to provide. */
|
|
56
|
+
export interface ConnectButtonPlatform {
|
|
57
|
+
name: string;
|
|
58
|
+
imageUrl?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ConnectButtonOptions {
|
|
62
|
+
/** Everything useOneConnect takes — the button wires open() and the
|
|
63
|
+
* Connecting/Connected states around your callbacks. */
|
|
64
|
+
connect: OneConnectProps;
|
|
65
|
+
/** "Connect your apps" unless overridden. */
|
|
66
|
+
label?: string;
|
|
67
|
+
/** default = neutral pill; accent = brand-colored pill; block =
|
|
68
|
+
* full-width consent card with description + "Secured by One" foot. */
|
|
69
|
+
variant?: "default" | "accent" | "block";
|
|
70
|
+
/** Matches the host page, not One's card (that's connect.appTheme). */
|
|
71
|
+
theme?: "light" | "dark";
|
|
72
|
+
/** Provider chips fanned on hover; first four render, the rest fold
|
|
73
|
+
* into the +N chip together with moreCount. */
|
|
74
|
+
platforms?: ConnectButtonPlatform[];
|
|
75
|
+
/** Extra count for the +N chip (e.g. 274 for "the whole catalog"). */
|
|
76
|
+
moreCount?: number;
|
|
77
|
+
/** Sub-line on the block variant, shown while idle. */
|
|
78
|
+
description?: string;
|
|
79
|
+
/** accent variant fill; lime (#CCFF00) when omitted. */
|
|
80
|
+
accentColor?: string;
|
|
81
|
+
/** Label for the connected state ("4 apps connected"). */
|
|
82
|
+
connectedLabel?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ConnectButtonHandle {
|
|
86
|
+
/** Manually override the visual state. */
|
|
87
|
+
setState: (state: "idle" | "connecting" | "connected") => void;
|
|
88
|
+
/** Remove the button and tear down the flow wiring. */
|
|
89
|
+
destroy: () => void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Registers the <one-connect-button> custom element (no-op on servers
|
|
93
|
+
* and when already registered). Importing the package does this
|
|
94
|
+
* automatically in browsers; calling it manually is only needed if a
|
|
95
|
+
* bundler stripped module side effects. */
|
|
96
|
+
export declare function registerConnectButton(): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@withone/connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Frontend bindings for One Connect, a drop-in OAuth flow that lets your users grant your application scoped, revocable access to their own connected tools. The user owns the connections; you hold a token scoped to exactly what they granted.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
package/src/button.ts
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { useOneConnect } from "./useOneConnect";
|
|
2
|
+
import type {
|
|
3
|
+
ConnectButtonHandle,
|
|
4
|
+
ConnectButtonOptions,
|
|
5
|
+
OneConnectHandle,
|
|
6
|
+
} from "./types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Optional pre-built trigger for the connect flow. Entirely opt-in —
|
|
10
|
+
* `useOneConnect` alone with any element the consumer likes remains
|
|
11
|
+
* fully supported. Framework-agnostic like the rest of the SDK: it
|
|
12
|
+
* renders real DOM into a container, so React/Vue/vanilla all mount it
|
|
13
|
+
* the same way. Zero dependencies, zero One URLs — provider icons are
|
|
14
|
+
* passed in by the consumer.
|
|
15
|
+
*
|
|
16
|
+
* The button owns the flow wiring: click opens the card, the label
|
|
17
|
+
* turns "Connecting…" while the card is up, and lands on a spring
|
|
18
|
+
* "Connected" state on success (Connect → Connecting → Connected; the
|
|
19
|
+
* verb never changes).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const STYLE_ID = "one-connect-button-styles";
|
|
23
|
+
const EASE = "cubic-bezier(.2,.9,.25,1)";
|
|
24
|
+
|
|
25
|
+
interface Palette {
|
|
26
|
+
btnBg: string;
|
|
27
|
+
btnFg: string;
|
|
28
|
+
surface: string;
|
|
29
|
+
fg: string;
|
|
30
|
+
muted: string;
|
|
31
|
+
line: string;
|
|
32
|
+
shadow: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const LIGHT: Palette = {
|
|
36
|
+
btnBg: "#0A0C0B",
|
|
37
|
+
btnFg: "#ffffff",
|
|
38
|
+
surface: "#ffffff",
|
|
39
|
+
fg: "#0A0C0B",
|
|
40
|
+
muted: "#6B7280",
|
|
41
|
+
line: "#D1D5DB",
|
|
42
|
+
shadow: "0 1px 2px rgba(10,12,11,.08), 0 4px 14px rgba(10,12,11,.06)",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const DARK: Palette = {
|
|
46
|
+
btnBg: "#F2F5F3",
|
|
47
|
+
btnFg: "#0A0C0B",
|
|
48
|
+
surface: "#101312",
|
|
49
|
+
fg: "#F2F5F3",
|
|
50
|
+
muted: "#8A938E",
|
|
51
|
+
line: "#2A302D",
|
|
52
|
+
shadow: "0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35)",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const LIME = "#CCFF00";
|
|
56
|
+
const SPRING = "#3FE3A5";
|
|
57
|
+
const CARBON = "#0A0C0B";
|
|
58
|
+
|
|
59
|
+
/** The One ring mark — same glyph the card footer pairs with the
|
|
60
|
+
* wordmark. Inline so the SDK stays free of One URLs. */
|
|
61
|
+
const ONE_MARK_SVG =
|
|
62
|
+
'<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4" style="flex-shrink:0"><circle cx="12" cy="12" r="8.5"/></svg>';
|
|
63
|
+
const ARROW_SVG =
|
|
64
|
+
'<svg class="owcb-arrow" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3l5 5-5 5"/></svg>';
|
|
65
|
+
const TICK_SVG =
|
|
66
|
+
'<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px"><path d="M3 8.5l3.5 3.5L13 5"/></svg>';
|
|
67
|
+
|
|
68
|
+
function ensureStyles(): void {
|
|
69
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID))
|
|
70
|
+
return;
|
|
71
|
+
const style = document.createElement("style");
|
|
72
|
+
style.id = STYLE_ID;
|
|
73
|
+
style.textContent = [
|
|
74
|
+
`.owcb{font:500 14.5px/1.2 inherit;font-family:inherit;letter-spacing:-.01em;cursor:pointer;border:0;display:inline-flex;align-items:center;gap:12px;padding:12px 16px 12px 14px;border-radius:12px;transition:transform .2s ${EASE},box-shadow .2s ${EASE},opacity .2s ${EASE}}`,
|
|
75
|
+
".owcb:hover{transform:translateY(-1px)}",
|
|
76
|
+
".owcb:active{transform:translateY(0)}",
|
|
77
|
+
`.owcb:focus-visible{outline:2px solid ${SPRING};outline-offset:3px}`,
|
|
78
|
+
".owcb[disabled]{cursor:default;opacity:.75;transform:none}",
|
|
79
|
+
".owcb-stack{display:inline-flex;align-items:center;flex-shrink:0}",
|
|
80
|
+
`.owcb-chip{width:22px;height:22px;border-radius:6px;display:grid;place-items:center;overflow:hidden;margin-left:-7px;transition:margin-left .28s ${EASE};position:relative}`,
|
|
81
|
+
".owcb-chip:first-child{margin-left:0}",
|
|
82
|
+
".owcb-chip img{width:14px;height:14px;display:block;object-fit:contain}",
|
|
83
|
+
".owcb-chip.owcb-more{font-family:ui-monospace,monospace;font-size:9px;font-weight:500;letter-spacing:-.02em}",
|
|
84
|
+
".owcb:hover .owcb-chip{margin-left:-2px}",
|
|
85
|
+
".owcb:hover .owcb-chip:first-child{margin-left:0}",
|
|
86
|
+
".owcb-label{white-space:nowrap}",
|
|
87
|
+
`.owcb-arrow{opacity:.5;transition:transform .2s ${EASE},opacity .2s ${EASE}}`,
|
|
88
|
+
".owcb:hover .owcb-arrow{transform:translateX(2px);opacity:.8}",
|
|
89
|
+
`.owcb-spinner{width:16px;height:16px;border-radius:50%;border:2px solid currentColor;border-top-color:transparent;opacity:.7;animation:owcb-spin .7s linear infinite}`,
|
|
90
|
+
"@keyframes owcb-spin{to{transform:rotate(360deg)}}",
|
|
91
|
+
".owcb.owcb-block{display:flex;flex-direction:column;align-items:stretch;width:100%;max-width:420px;text-align:left;padding:16px;border-radius:14px;gap:0}",
|
|
92
|
+
".owcb-block-top{display:flex;align-items:center;justify-content:space-between;gap:12px}",
|
|
93
|
+
".owcb-block-title{font-size:15px;font-weight:600;letter-spacing:-.01em}",
|
|
94
|
+
".owcb-block-sub{font-size:13px;line-height:1.5;margin-top:6px;font-weight:400}",
|
|
95
|
+
".owcb-block-foot{margin-top:14px;padding-top:12px;display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:400}",
|
|
96
|
+
"@media (prefers-reduced-motion:reduce){.owcb,.owcb-chip,.owcb-arrow{transition:none}.owcb-spinner{animation-duration:1.4s}}",
|
|
97
|
+
].join("\n");
|
|
98
|
+
document.head.appendChild(style);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildStack(
|
|
102
|
+
options: ConnectButtonOptions,
|
|
103
|
+
chipRing: string,
|
|
104
|
+
moreColor: string,
|
|
105
|
+
): HTMLElement | null {
|
|
106
|
+
const platforms = options.platforms ?? [];
|
|
107
|
+
if (platforms.length === 0 && !options.moreCount) return null;
|
|
108
|
+
const stack = document.createElement("span");
|
|
109
|
+
stack.className = "owcb-stack";
|
|
110
|
+
stack.setAttribute("aria-hidden", "true");
|
|
111
|
+
for (const platform of platforms.slice(0, 4)) {
|
|
112
|
+
const chip = document.createElement("span");
|
|
113
|
+
chip.className = "owcb-chip";
|
|
114
|
+
chip.style.background = "#ffffff";
|
|
115
|
+
chip.style.boxShadow = `0 0 0 1.5px ${chipRing}`;
|
|
116
|
+
if (platform.imageUrl) {
|
|
117
|
+
const img = document.createElement("img");
|
|
118
|
+
img.alt = "";
|
|
119
|
+
img.src = platform.imageUrl;
|
|
120
|
+
img.addEventListener("error", () => {
|
|
121
|
+
img.style.display = "none";
|
|
122
|
+
chip.textContent = platform.name.charAt(0).toUpperCase();
|
|
123
|
+
chip.style.font = "600 10px ui-monospace,monospace";
|
|
124
|
+
chip.style.color = CARBON;
|
|
125
|
+
});
|
|
126
|
+
chip.appendChild(img);
|
|
127
|
+
} else {
|
|
128
|
+
chip.textContent = platform.name.charAt(0).toUpperCase();
|
|
129
|
+
chip.style.font = "600 10px ui-monospace,monospace";
|
|
130
|
+
chip.style.color = CARBON;
|
|
131
|
+
}
|
|
132
|
+
stack.appendChild(chip);
|
|
133
|
+
}
|
|
134
|
+
const extra =
|
|
135
|
+
(options.moreCount ?? 0) + Math.max(0, platforms.length - 4);
|
|
136
|
+
if (extra > 0) {
|
|
137
|
+
const more = document.createElement("span");
|
|
138
|
+
more.className = "owcb-chip owcb-more";
|
|
139
|
+
more.style.background = "#ffffff";
|
|
140
|
+
more.style.boxShadow = `0 0 0 1.5px ${chipRing}`;
|
|
141
|
+
more.style.color = moreColor;
|
|
142
|
+
more.textContent = `+${extra}`;
|
|
143
|
+
stack.appendChild(more);
|
|
144
|
+
}
|
|
145
|
+
return stack;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const mountConnectButton = (
|
|
149
|
+
container: HTMLElement,
|
|
150
|
+
options: ConnectButtonOptions,
|
|
151
|
+
): ConnectButtonHandle => {
|
|
152
|
+
ensureStyles();
|
|
153
|
+
|
|
154
|
+
const palette = options.theme === "dark" ? DARK : LIGHT;
|
|
155
|
+
const variant = options.variant ?? "default";
|
|
156
|
+
const label = options.label ?? "Connect your apps";
|
|
157
|
+
|
|
158
|
+
let state: "idle" | "connecting" | "connected" = "idle";
|
|
159
|
+
let destroyed = false;
|
|
160
|
+
|
|
161
|
+
const handle: OneConnectHandle = useOneConnect({
|
|
162
|
+
...options.connect,
|
|
163
|
+
onSuccess: () => {
|
|
164
|
+
setState("connected");
|
|
165
|
+
options.connect.onSuccess?.();
|
|
166
|
+
},
|
|
167
|
+
onError: (error) => {
|
|
168
|
+
setState("idle");
|
|
169
|
+
options.connect.onError?.(error);
|
|
170
|
+
},
|
|
171
|
+
onClose: () => {
|
|
172
|
+
setState("idle");
|
|
173
|
+
options.connect.onClose?.();
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const button = document.createElement("button");
|
|
178
|
+
button.type = "button";
|
|
179
|
+
button.className = variant === "block" ? "owcb owcb-block" : "owcb";
|
|
180
|
+
button.addEventListener("click", () => {
|
|
181
|
+
if (state !== "idle" && state !== "connected") return;
|
|
182
|
+
setState("connecting");
|
|
183
|
+
handle.open();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const paint = () => {
|
|
187
|
+
const accent = variant === "accent";
|
|
188
|
+
const block = variant === "block";
|
|
189
|
+
const bg = block
|
|
190
|
+
? palette.surface
|
|
191
|
+
: accent
|
|
192
|
+
? (options.accentColor ?? LIME)
|
|
193
|
+
: palette.btnBg;
|
|
194
|
+
const fg = block ? palette.fg : accent ? CARBON : palette.btnFg;
|
|
195
|
+
const chipRing = block ? palette.surface : bg;
|
|
196
|
+
const moreColor = block ? palette.muted : fg;
|
|
197
|
+
|
|
198
|
+
button.style.background = state === "connected" && !block ? SPRING : bg;
|
|
199
|
+
button.style.color = state === "connected" && !block ? CARBON : fg;
|
|
200
|
+
button.style.boxShadow = block
|
|
201
|
+
? `inset 0 0 0 1px ${palette.line}`
|
|
202
|
+
: state === "connected"
|
|
203
|
+
? "none"
|
|
204
|
+
: accent
|
|
205
|
+
? `0 1px 2px rgba(10,12,11,.1), 0 6px 20px ${(options.accentColor ?? LIME)}38`
|
|
206
|
+
: palette.shadow;
|
|
207
|
+
button.disabled = state === "connecting";
|
|
208
|
+
button.innerHTML = "";
|
|
209
|
+
|
|
210
|
+
const stack = buildStack(options, chipRing, moreColor);
|
|
211
|
+
|
|
212
|
+
if (block) {
|
|
213
|
+
const top = document.createElement("span");
|
|
214
|
+
top.className = "owcb-block-top";
|
|
215
|
+
const title = document.createElement("span");
|
|
216
|
+
title.className = "owcb-block-title";
|
|
217
|
+
title.textContent =
|
|
218
|
+
state === "connecting"
|
|
219
|
+
? "Connecting…"
|
|
220
|
+
: state === "connected"
|
|
221
|
+
? (options.connectedLabel ?? "Connected")
|
|
222
|
+
: label;
|
|
223
|
+
top.appendChild(title);
|
|
224
|
+
if (state === "connecting") {
|
|
225
|
+
const spinner = document.createElement("span");
|
|
226
|
+
spinner.className = "owcb-spinner";
|
|
227
|
+
top.appendChild(spinner);
|
|
228
|
+
} else if (stack) {
|
|
229
|
+
top.appendChild(stack);
|
|
230
|
+
}
|
|
231
|
+
button.appendChild(top);
|
|
232
|
+
if (options.description && state === "idle") {
|
|
233
|
+
const sub = document.createElement("span");
|
|
234
|
+
sub.className = "owcb-block-sub";
|
|
235
|
+
sub.style.color = palette.muted;
|
|
236
|
+
sub.textContent = options.description;
|
|
237
|
+
button.appendChild(sub);
|
|
238
|
+
}
|
|
239
|
+
// Mirrors the card/overlay footer strip: "Secured by" + ring mark
|
|
240
|
+
// + "one" wordmark, so the button and the surface it opens read as
|
|
241
|
+
// the same product.
|
|
242
|
+
const foot = document.createElement("span");
|
|
243
|
+
foot.className = "owcb-block-foot";
|
|
244
|
+
foot.style.color = palette.muted;
|
|
245
|
+
foot.style.borderTop = `1px solid ${palette.line}`;
|
|
246
|
+
foot.innerHTML =
|
|
247
|
+
`<span style="font-size:11px;color:${palette.muted}">Secured by</span>` +
|
|
248
|
+
`<span style="display:inline-flex;color:${palette.fg}">${ONE_MARK_SVG}</span>` +
|
|
249
|
+
`<span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${palette.fg}">one</span>`;
|
|
250
|
+
button.appendChild(foot);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (state === "connecting") {
|
|
255
|
+
const spinner = document.createElement("span");
|
|
256
|
+
spinner.className = "owcb-spinner";
|
|
257
|
+
button.appendChild(spinner);
|
|
258
|
+
} else if (state === "connected") {
|
|
259
|
+
const tick = document.createElement("span");
|
|
260
|
+
tick.innerHTML = TICK_SVG;
|
|
261
|
+
tick.style.display = "inline-flex";
|
|
262
|
+
button.appendChild(tick);
|
|
263
|
+
} else if (stack) {
|
|
264
|
+
button.appendChild(stack);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const text = document.createElement("span");
|
|
268
|
+
text.className = "owcb-label";
|
|
269
|
+
text.textContent =
|
|
270
|
+
state === "connecting"
|
|
271
|
+
? "Connecting…"
|
|
272
|
+
: state === "connected"
|
|
273
|
+
? (options.connectedLabel ?? "Connected")
|
|
274
|
+
: label;
|
|
275
|
+
button.appendChild(text);
|
|
276
|
+
|
|
277
|
+
if (state === "idle") {
|
|
278
|
+
const arrow = document.createElement("span");
|
|
279
|
+
arrow.innerHTML = ARROW_SVG;
|
|
280
|
+
arrow.style.display = "inline-flex";
|
|
281
|
+
button.appendChild(arrow);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const setState = (next: "idle" | "connecting" | "connected") => {
|
|
286
|
+
if (destroyed) return;
|
|
287
|
+
state = next;
|
|
288
|
+
paint();
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
paint();
|
|
292
|
+
container.appendChild(button);
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
setState,
|
|
296
|
+
destroy: () => {
|
|
297
|
+
destroyed = true;
|
|
298
|
+
handle.close();
|
|
299
|
+
button.remove();
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* <one-connect-button> — the simple path. One tag, any framework
|
|
306
|
+
* (React, Vue, Svelte, plain HTML): the element wires the whole flow
|
|
307
|
+
* itself from its attributes and manages Connect → Connecting →
|
|
308
|
+
* Connected. Registered automatically in the browser on import;
|
|
309
|
+
* defined inside a function so importing this module on a server
|
|
310
|
+
* (Next.js SSR) never touches HTMLElement.
|
|
311
|
+
*
|
|
312
|
+
* Attributes: authorize-url (required), app-theme, label, variant,
|
|
313
|
+
* theme, platforms (JSON array of {name, imageUrl}), more-count,
|
|
314
|
+
* description, accent-color, connected-label.
|
|
315
|
+
* Events: "success" | "error" (detail: message) | "close" — plus
|
|
316
|
+
* matching function properties (onSuccess/onError/onClose) that
|
|
317
|
+
* React 19 / Vue / Svelte set naturally as props.
|
|
318
|
+
*/
|
|
319
|
+
export function registerConnectButton(): void {
|
|
320
|
+
if (typeof window === "undefined" || typeof customElements === "undefined")
|
|
321
|
+
return;
|
|
322
|
+
if (customElements.get("one-connect-button")) return;
|
|
323
|
+
|
|
324
|
+
class OneConnectButtonElement extends HTMLElement {
|
|
325
|
+
static observedAttributes = [
|
|
326
|
+
"authorize-url",
|
|
327
|
+
"app-theme",
|
|
328
|
+
"label",
|
|
329
|
+
"variant",
|
|
330
|
+
"theme",
|
|
331
|
+
"platforms",
|
|
332
|
+
"more-count",
|
|
333
|
+
"description",
|
|
334
|
+
"accent-color",
|
|
335
|
+
"connected-label",
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
onSuccess: (() => void) | null = null;
|
|
339
|
+
onError: ((error: string) => void) | null = null;
|
|
340
|
+
onClose: (() => void) | null = null;
|
|
341
|
+
|
|
342
|
+
private handle: ConnectButtonHandle | null = null;
|
|
343
|
+
|
|
344
|
+
connectedCallback(): void {
|
|
345
|
+
this.mount();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
disconnectedCallback(): void {
|
|
349
|
+
this.handle?.destroy();
|
|
350
|
+
this.handle = null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
attributeChangedCallback(): void {
|
|
354
|
+
if (this.isConnected && this.handle) this.mount();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private mount(): void {
|
|
358
|
+
this.handle?.destroy();
|
|
359
|
+
this.handle = null;
|
|
360
|
+
const authorizeUrl = this.getAttribute("authorize-url");
|
|
361
|
+
if (!authorizeUrl) return; // nothing to wire yet
|
|
362
|
+
|
|
363
|
+
let platforms: ConnectButtonOptions["platforms"];
|
|
364
|
+
const rawPlatforms = this.getAttribute("platforms");
|
|
365
|
+
if (rawPlatforms) {
|
|
366
|
+
try {
|
|
367
|
+
const parsed = JSON.parse(rawPlatforms) as unknown;
|
|
368
|
+
if (Array.isArray(parsed)) platforms = parsed;
|
|
369
|
+
} catch {
|
|
370
|
+
/* malformed platforms JSON — render without chips */
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const moreCountRaw = this.getAttribute("more-count");
|
|
375
|
+
const moreCount = moreCountRaw ? parseInt(moreCountRaw, 10) : undefined;
|
|
376
|
+
|
|
377
|
+
this.handle = mountConnectButton(this, {
|
|
378
|
+
connect: {
|
|
379
|
+
authorize: { url: authorizeUrl },
|
|
380
|
+
appTheme:
|
|
381
|
+
(this.getAttribute("app-theme") as "light" | "dark" | null) ??
|
|
382
|
+
undefined,
|
|
383
|
+
onSuccess: () => {
|
|
384
|
+
this.onSuccess?.();
|
|
385
|
+
this.dispatchEvent(new CustomEvent("success"));
|
|
386
|
+
},
|
|
387
|
+
onError: (error) => {
|
|
388
|
+
this.onError?.(error);
|
|
389
|
+
this.dispatchEvent(new CustomEvent("error", { detail: error }));
|
|
390
|
+
},
|
|
391
|
+
onClose: () => {
|
|
392
|
+
this.onClose?.();
|
|
393
|
+
this.dispatchEvent(new CustomEvent("close"));
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
label: this.getAttribute("label") ?? undefined,
|
|
397
|
+
variant:
|
|
398
|
+
(this.getAttribute("variant") as ConnectButtonOptions["variant"]) ??
|
|
399
|
+
undefined,
|
|
400
|
+
theme:
|
|
401
|
+
(this.getAttribute("theme") as "light" | "dark" | null) ?? undefined,
|
|
402
|
+
platforms,
|
|
403
|
+
moreCount: Number.isFinite(moreCount) ? moreCount : undefined,
|
|
404
|
+
description: this.getAttribute("description") ?? undefined,
|
|
405
|
+
accentColor: this.getAttribute("accent-color") ?? undefined,
|
|
406
|
+
connectedLabel: this.getAttribute("connected-label") ?? undefined,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
customElements.define("one-connect-button", OneConnectButtonElement);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
registerConnectButton();
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export { useOneConnect } from "./useOneConnect";
|
|
2
2
|
export { completeOneConnect } from "./complete";
|
|
3
|
+
export { mountConnectButton, registerConnectButton } from "./button";
|
|
3
4
|
export type {
|
|
4
5
|
OneConnectProps,
|
|
5
6
|
OneConnectHandle,
|
|
6
7
|
OneConnectResult,
|
|
8
|
+
ConnectButtonOptions,
|
|
9
|
+
ConnectButtonPlatform,
|
|
10
|
+
ConnectButtonHandle,
|
|
7
11
|
} from "./types";
|
package/src/types/index.d.ts
CHANGED
|
@@ -50,3 +50,47 @@ export interface OneConnectMessage {
|
|
|
50
50
|
status: "success" | "error";
|
|
51
51
|
message?: string;
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
/** A provider chip on the pre-built button. The SDK ships no One URLs,
|
|
55
|
+
* so icon sources are the consumer's to provide. */
|
|
56
|
+
export interface ConnectButtonPlatform {
|
|
57
|
+
name: string;
|
|
58
|
+
imageUrl?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface ConnectButtonOptions {
|
|
62
|
+
/** Everything useOneConnect takes — the button wires open() and the
|
|
63
|
+
* Connecting/Connected states around your callbacks. */
|
|
64
|
+
connect: OneConnectProps;
|
|
65
|
+
/** "Connect your apps" unless overridden. */
|
|
66
|
+
label?: string;
|
|
67
|
+
/** default = neutral pill; accent = brand-colored pill; block =
|
|
68
|
+
* full-width consent card with description + "Secured by One" foot. */
|
|
69
|
+
variant?: "default" | "accent" | "block";
|
|
70
|
+
/** Matches the host page, not One's card (that's connect.appTheme). */
|
|
71
|
+
theme?: "light" | "dark";
|
|
72
|
+
/** Provider chips fanned on hover; first four render, the rest fold
|
|
73
|
+
* into the +N chip together with moreCount. */
|
|
74
|
+
platforms?: ConnectButtonPlatform[];
|
|
75
|
+
/** Extra count for the +N chip (e.g. 274 for "the whole catalog"). */
|
|
76
|
+
moreCount?: number;
|
|
77
|
+
/** Sub-line on the block variant, shown while idle. */
|
|
78
|
+
description?: string;
|
|
79
|
+
/** accent variant fill; lime (#CCFF00) when omitted. */
|
|
80
|
+
accentColor?: string;
|
|
81
|
+
/** Label for the connected state ("4 apps connected"). */
|
|
82
|
+
connectedLabel?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ConnectButtonHandle {
|
|
86
|
+
/** Manually override the visual state. */
|
|
87
|
+
setState: (state: "idle" | "connecting" | "connected") => void;
|
|
88
|
+
/** Remove the button and tear down the flow wiring. */
|
|
89
|
+
destroy: () => void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Registers the <one-connect-button> custom element (no-op on servers
|
|
93
|
+
* and when already registered). Importing the package does this
|
|
94
|
+
* automatically in browsers; calling it manually is only needed if a
|
|
95
|
+
* bundler stripped module side effects. */
|
|
96
|
+
export declare function registerConnectButton(): void;
|
package/src/window/index.ts
CHANGED
|
@@ -98,6 +98,7 @@ export function showSuccessOverlay(theme?: "dark" | "light"): void {
|
|
|
98
98
|
"</div>" +
|
|
99
99
|
`<div style="height:48px;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:0 0 28px 28px;background:${footerBg};border:1px solid ${cardBorder};border-top:0;">` +
|
|
100
100
|
`<span style="font-size:11px;color:${muted};">Secured by</span>` +
|
|
101
|
+
`<span style="display:inline-flex;color:${dark ? "#fafafa" : "#111114"};"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4"><circle cx="12" cy="12" r="8.5"/></svg></span>` +
|
|
101
102
|
`<span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${dark ? "#fafafa" : "#111114"};">one</span>` +
|
|
102
103
|
"</div>";
|
|
103
104
|
|