@authon/svelte 0.2.0 → 0.2.1

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/dist/index.cjs CHANGED
@@ -22,7 +22,8 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  createAuthonStore: () => createAuthonStore,
24
24
  getAuthon: () => getAuthon,
25
- initAuthon: () => initAuthon
25
+ initAuthon: () => initAuthon,
26
+ renderSocialButtons: () => renderSocialButtons
26
27
  });
27
28
  module.exports = __toCommonJS(index_exports);
28
29
 
@@ -80,10 +81,120 @@ function getAuthon() {
80
81
  }
81
82
  return store;
82
83
  }
84
+
85
+ // src/SocialButtons.ts
86
+ var import_shared = require("@authon/shared");
87
+ var import_js2 = require("@authon/js");
88
+ function renderSocialButtons(options) {
89
+ const {
90
+ client,
91
+ container,
92
+ onSuccess,
93
+ onError,
94
+ compact = false,
95
+ gap,
96
+ labels,
97
+ iconSize,
98
+ borderRadius = 10,
99
+ height = 48,
100
+ size = 48
101
+ } = options;
102
+ const resolvedGap = gap ?? (compact ? 12 : 10);
103
+ const resolvedIconSize = iconSize ?? (compact ? 24 : 20);
104
+ let loadingProvider = null;
105
+ let buttons = [];
106
+ const handleClick = async (provider, btn) => {
107
+ if (loadingProvider) return;
108
+ loadingProvider = provider;
109
+ btn.innerHTML = '<span style="display:inline-block;width:16px;height:16px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:authon-spin 0.6s linear infinite"></span>';
110
+ buttons.forEach((b) => b.disabled = true);
111
+ try {
112
+ await client.signInWithOAuth(provider);
113
+ onSuccess?.();
114
+ } catch (e) {
115
+ const error = e instanceof Error ? e : new Error(String(e));
116
+ onError?.(error);
117
+ } finally {
118
+ loadingProvider = null;
119
+ renderButtons(providers);
120
+ }
121
+ };
122
+ let providers = [];
123
+ function renderButtons(providerList) {
124
+ container.innerHTML = "";
125
+ buttons = [];
126
+ if (!document.getElementById("authon-spin-style")) {
127
+ const style = document.createElement("style");
128
+ style.id = "authon-spin-style";
129
+ style.textContent = "@keyframes authon-spin{to{transform:rotate(360deg)}}";
130
+ document.head.appendChild(style);
131
+ }
132
+ const wrapper = document.createElement("div");
133
+ wrapper.style.display = "flex";
134
+ wrapper.style.gap = `${resolvedGap}px`;
135
+ if (compact) {
136
+ wrapper.style.flexDirection = "row";
137
+ wrapper.style.flexWrap = "wrap";
138
+ wrapper.style.justifyContent = "center";
139
+ } else {
140
+ wrapper.style.flexDirection = "column";
141
+ }
142
+ for (const provider of providerList) {
143
+ const colors = import_shared.PROVIDER_COLORS[provider] || { bg: "#333", text: "#fff" };
144
+ const displayName = import_shared.PROVIDER_DISPLAY_NAMES[provider] || provider;
145
+ const config = (0, import_js2.getProviderButtonConfig)(provider);
146
+ const iconSvg = config.iconSvg.replace(/width="\d+"/, `width="${resolvedIconSize}"`).replace(/height="\d+"/, `height="${resolvedIconSize}"`);
147
+ const needsBorder = colors.bg.toLowerCase() === "#ffffff";
148
+ const btn = document.createElement("button");
149
+ btn.setAttribute("aria-label", `Sign in with ${displayName}`);
150
+ btn.style.display = "flex";
151
+ btn.style.alignItems = "center";
152
+ btn.style.justifyContent = "center";
153
+ btn.style.border = needsBorder ? "1px solid #dadce0" : "none";
154
+ btn.style.cursor = "pointer";
155
+ btn.style.backgroundColor = colors.bg;
156
+ btn.style.color = colors.text;
157
+ btn.style.borderRadius = `${borderRadius}px`;
158
+ btn.style.transition = "opacity 0.15s";
159
+ btn.style.fontFamily = "inherit";
160
+ if (compact) {
161
+ btn.style.width = `${size}px`;
162
+ btn.style.height = `${size}px`;
163
+ btn.style.padding = "0";
164
+ btn.innerHTML = `<span style="display:flex;align-items:center">${iconSvg}</span>`;
165
+ } else {
166
+ btn.style.width = "100%";
167
+ btn.style.height = `${height}px`;
168
+ btn.style.gap = "10px";
169
+ btn.style.paddingLeft = "16px";
170
+ btn.style.paddingRight = "16px";
171
+ const buttonLabel = labels?.[provider] ?? `Continue with ${displayName}`;
172
+ btn.innerHTML = `<span style="display:flex;align-items:center;flex-shrink:0">${iconSvg}</span><span style="font-size:15px;font-weight:600;white-space:nowrap">${buttonLabel}</span>`;
173
+ }
174
+ btn.addEventListener("click", () => handleClick(provider, btn));
175
+ btn.addEventListener("mouseenter", () => btn.style.opacity = "0.85");
176
+ btn.addEventListener("mouseleave", () => btn.style.opacity = "1");
177
+ buttons.push(btn);
178
+ wrapper.appendChild(btn);
179
+ }
180
+ container.appendChild(wrapper);
181
+ }
182
+ client.getProviders().then((p) => {
183
+ providers = p;
184
+ if (providers.length > 0) {
185
+ renderButtons(providers);
186
+ }
187
+ });
188
+ return () => {
189
+ container.innerHTML = "";
190
+ buttons = [];
191
+ };
192
+ }
83
193
  // Annotate the CommonJS export names for ESM import in node:
84
194
  0 && (module.exports = {
85
195
  createAuthonStore,
86
196
  getAuthon,
87
- initAuthon
197
+ initAuthon,
198
+ renderSocialButtons
88
199
  });
89
200
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/store.ts","../src/context.ts"],"sourcesContent":["export { createAuthonStore } from './store';\nexport type { AuthonStore } from './store';\nexport { initAuthon, getAuthon } from './context';\n","import { writable, derived, type Readable } from 'svelte/store';\nimport { Authon } from '@authon/js';\nimport type { AuthonConfig } from '@authon/js';\nimport type { AuthonUser } from '@authon/shared';\n\nexport interface AuthonStore {\n user: Readable<AuthonUser | null>;\n isSignedIn: Readable<boolean>;\n isLoading: Readable<boolean>;\n signOut: () => Promise<void>;\n openSignIn: () => Promise<void>;\n openSignUp: () => Promise<void>;\n getToken: () => string | null;\n destroy: () => void;\n client: Authon;\n}\n\n/**\n * Creates an Authon store with reactive Svelte stores.\n *\n * Usage:\n * ```ts\n * import { createAuthonStore } from '@authon/svelte'\n *\n * const authon = createAuthonStore('pk_live_...')\n *\n * // In your component:\n * $: user = $authon.user\n * $: isSignedIn = $authon.isSignedIn\n * ```\n */\nexport function createAuthonStore(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const client = new Authon(publishableKey, config);\n const userStore = writable<AuthonUser | null>(null);\n const isLoadingStore = writable(true);\n\n const isSignedIn = derived(userStore, ($user) => $user !== null);\n\n client.on('signedIn', (user) => {\n userStore.set(user as AuthonUser);\n isLoadingStore.set(false);\n });\n\n client.on('signedOut', () => {\n userStore.set(null);\n });\n\n client.on('error', () => {\n isLoadingStore.set(false);\n });\n\n const existingUser = client.getUser();\n if (existingUser) {\n userStore.set(existingUser);\n }\n isLoadingStore.set(false);\n\n return {\n user: { subscribe: userStore.subscribe },\n isSignedIn,\n isLoading: { subscribe: isLoadingStore.subscribe },\n signOut: async () => {\n await client.signOut();\n userStore.set(null);\n },\n openSignIn: () => client.openSignIn(),\n openSignUp: () => client.openSignUp(),\n getToken: () => client.getToken(),\n destroy: () => client.destroy(),\n client,\n };\n}\n","import { setContext, getContext } from 'svelte';\nimport type { AuthonConfig } from '@authon/js';\nimport { createAuthonStore, type AuthonStore } from './store';\n\nconst AUTHON_CONTEXT_KEY = Symbol('authon');\n\n/**\n * Initialize Authon in a Svelte component tree.\n * Call this in your root layout or top-level component.\n *\n * Usage in +layout.svelte:\n * ```svelte\n * <script>\n * import { initAuthon } from '@authon/svelte'\n * const authon = initAuthon('pk_live_...')\n * </script>\n * ```\n */\nexport function initAuthon(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const store = createAuthonStore(publishableKey, config);\n setContext(AUTHON_CONTEXT_KEY, store);\n return store;\n}\n\n/**\n * Get the Authon store from Svelte context.\n * Must be called within a component tree where `initAuthon` was called.\n *\n * Usage:\n * ```svelte\n * <script>\n * import { getAuthon } from '@authon/svelte'\n * const { user, isSignedIn, signOut } = getAuthon()\n * </script>\n * ```\n */\nexport function getAuthon(): AuthonStore {\n const store = getContext<AuthonStore | undefined>(AUTHON_CONTEXT_KEY);\n if (!store) {\n throw new Error('getAuthon() must be called within a component tree where initAuthon() was called.');\n }\n return store;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAiD;AACjD,gBAAuB;AA8BhB,SAAS,kBACd,gBACA,QACa;AACb,QAAM,SAAS,IAAI,iBAAO,gBAAgB,MAAM;AAChD,QAAM,gBAAY,uBAA4B,IAAI;AAClD,QAAM,qBAAiB,uBAAS,IAAI;AAEpC,QAAM,iBAAa,sBAAQ,WAAW,CAAC,UAAU,UAAU,IAAI;AAE/D,SAAO,GAAG,YAAY,CAAC,SAAS;AAC9B,cAAU,IAAI,IAAkB;AAChC,mBAAe,IAAI,KAAK;AAAA,EAC1B,CAAC;AAED,SAAO,GAAG,aAAa,MAAM;AAC3B,cAAU,IAAI,IAAI;AAAA,EACpB,CAAC;AAED,SAAO,GAAG,SAAS,MAAM;AACvB,mBAAe,IAAI,KAAK;AAAA,EAC1B,CAAC;AAED,QAAM,eAAe,OAAO,QAAQ;AACpC,MAAI,cAAc;AAChB,cAAU,IAAI,YAAY;AAAA,EAC5B;AACA,iBAAe,IAAI,KAAK;AAExB,SAAO;AAAA,IACL,MAAM,EAAE,WAAW,UAAU,UAAU;AAAA,IACvC;AAAA,IACA,WAAW,EAAE,WAAW,eAAe,UAAU;AAAA,IACjD,SAAS,YAAY;AACnB,YAAM,OAAO,QAAQ;AACrB,gBAAU,IAAI,IAAI;AAAA,IACpB;AAAA,IACA,YAAY,MAAM,OAAO,WAAW;AAAA,IACpC,YAAY,MAAM,OAAO,WAAW;AAAA,IACpC,UAAU,MAAM,OAAO,SAAS;AAAA,IAChC,SAAS,MAAM,OAAO,QAAQ;AAAA,IAC9B;AAAA,EACF;AACF;;;AC1EA,oBAAuC;AAIvC,IAAM,qBAAqB,uBAAO,QAAQ;AAcnC,SAAS,WACd,gBACA,QACa;AACb,QAAM,QAAQ,kBAAkB,gBAAgB,MAAM;AACtD,gCAAW,oBAAoB,KAAK;AACpC,SAAO;AACT;AAcO,SAAS,YAAyB;AACvC,QAAM,YAAQ,0BAAoC,kBAAkB;AACpE,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,mFAAmF;AAAA,EACrG;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/store.ts","../src/context.ts","../src/SocialButtons.ts"],"sourcesContent":["export { createAuthonStore } from './store';\nexport type { AuthonStore } from './store';\nexport { initAuthon, getAuthon } from './context';\nexport { renderSocialButtons } from './SocialButtons';\nexport type { SocialButtonsOptions } from './SocialButtons';\n","import { writable, derived, type Readable } from 'svelte/store';\nimport { Authon } from '@authon/js';\nimport type { AuthonConfig } from '@authon/js';\nimport type { AuthonUser } from '@authon/shared';\n\nexport interface AuthonStore {\n user: Readable<AuthonUser | null>;\n isSignedIn: Readable<boolean>;\n isLoading: Readable<boolean>;\n signOut: () => Promise<void>;\n openSignIn: () => Promise<void>;\n openSignUp: () => Promise<void>;\n getToken: () => string | null;\n destroy: () => void;\n client: Authon;\n}\n\n/**\n * Creates an Authon store with reactive Svelte stores.\n *\n * Usage:\n * ```ts\n * import { createAuthonStore } from '@authon/svelte'\n *\n * const authon = createAuthonStore('pk_live_...')\n *\n * // In your component:\n * $: user = $authon.user\n * $: isSignedIn = $authon.isSignedIn\n * ```\n */\nexport function createAuthonStore(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const client = new Authon(publishableKey, config);\n const userStore = writable<AuthonUser | null>(null);\n const isLoadingStore = writable(true);\n\n const isSignedIn = derived(userStore, ($user) => $user !== null);\n\n client.on('signedIn', (user) => {\n userStore.set(user as AuthonUser);\n isLoadingStore.set(false);\n });\n\n client.on('signedOut', () => {\n userStore.set(null);\n });\n\n client.on('error', () => {\n isLoadingStore.set(false);\n });\n\n const existingUser = client.getUser();\n if (existingUser) {\n userStore.set(existingUser);\n }\n isLoadingStore.set(false);\n\n return {\n user: { subscribe: userStore.subscribe },\n isSignedIn,\n isLoading: { subscribe: isLoadingStore.subscribe },\n signOut: async () => {\n await client.signOut();\n userStore.set(null);\n },\n openSignIn: () => client.openSignIn(),\n openSignUp: () => client.openSignUp(),\n getToken: () => client.getToken(),\n destroy: () => client.destroy(),\n client,\n };\n}\n","import { setContext, getContext } from 'svelte';\nimport type { AuthonConfig } from '@authon/js';\nimport { createAuthonStore, type AuthonStore } from './store';\n\nconst AUTHON_CONTEXT_KEY = Symbol('authon');\n\n/**\n * Initialize Authon in a Svelte component tree.\n * Call this in your root layout or top-level component.\n *\n * Usage in +layout.svelte:\n * ```svelte\n * <script>\n * import { initAuthon } from '@authon/svelte'\n * const authon = initAuthon('pk_live_...')\n * </script>\n * ```\n */\nexport function initAuthon(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const store = createAuthonStore(publishableKey, config);\n setContext(AUTHON_CONTEXT_KEY, store);\n return store;\n}\n\n/**\n * Get the Authon store from Svelte context.\n * Must be called within a component tree where `initAuthon` was called.\n *\n * Usage:\n * ```svelte\n * <script>\n * import { getAuthon } from '@authon/svelte'\n * const { user, isSignedIn, signOut } = getAuthon()\n * </script>\n * ```\n */\nexport function getAuthon(): AuthonStore {\n const store = getContext<AuthonStore | undefined>(AUTHON_CONTEXT_KEY);\n if (!store) {\n throw new Error('getAuthon() must be called within a component tree where initAuthon() was called.');\n }\n return store;\n}\n","import { PROVIDER_COLORS, PROVIDER_DISPLAY_NAMES, type OAuthProviderType } from '@authon/shared';\nimport { getProviderButtonConfig, type Authon } from '@authon/js';\n\nexport interface SocialButtonsOptions {\n /** Authon client instance */\n client: Authon;\n /** Target container element */\n container: HTMLElement;\n /** Called after successful OAuth sign-in */\n onSuccess?: () => void;\n /** Called on OAuth error */\n onError?: (error: Error) => void;\n /** Compact mode — icon-only square buttons in a row (default: false) */\n compact?: boolean;\n /** Gap between buttons in px (default: 10, compact default: 12) */\n gap?: number;\n /** Custom labels per provider */\n labels?: Partial<Record<OAuthProviderType, string>>;\n /** Icon size (default: 20, compact default: 24) */\n iconSize?: number;\n /** Border radius in px (default: 10) */\n borderRadius?: number;\n /** Button height in px (default: 48) */\n height?: number;\n /** Button size for compact mode in px (default: 48) */\n size?: number;\n}\n\n/**\n * Render social login buttons into a container element.\n *\n * Usage in +page.svelte:\n * ```svelte\n * <script>\n * import { getAuthon } from '@authon/svelte'\n * import { renderSocialButtons } from '@authon/svelte'\n * import { onMount } from 'svelte'\n *\n * const { client } = getAuthon()\n * let container: HTMLElement\n *\n * onMount(() => {\n * const cleanup = renderSocialButtons({\n * client,\n * container,\n * compact: true,\n * onError: (err) => console.error(err),\n * })\n * return cleanup\n * })\n * </script>\n *\n * <div bind:this={container}></div>\n * ```\n */\nexport function renderSocialButtons(options: SocialButtonsOptions): () => void {\n const {\n client,\n container,\n onSuccess,\n onError,\n compact = false,\n gap,\n labels,\n iconSize,\n borderRadius = 10,\n height = 48,\n size = 48,\n } = options;\n\n const resolvedGap = gap ?? (compact ? 12 : 10);\n const resolvedIconSize = iconSize ?? (compact ? 24 : 20);\n let loadingProvider: string | null = null;\n let buttons: HTMLButtonElement[] = [];\n\n const handleClick = async (provider: OAuthProviderType, btn: HTMLButtonElement) => {\n if (loadingProvider) return;\n loadingProvider = provider;\n btn.innerHTML = '<span style=\"display:inline-block;width:16px;height:16px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:authon-spin 0.6s linear infinite\"></span>';\n buttons.forEach((b) => (b.disabled = true));\n\n try {\n await client.signInWithOAuth(provider);\n onSuccess?.();\n } catch (e: any) {\n const error = e instanceof Error ? e : new Error(String(e));\n onError?.(error);\n } finally {\n loadingProvider = null;\n renderButtons(providers);\n }\n };\n\n let providers: OAuthProviderType[] = [];\n\n function renderButtons(providerList: OAuthProviderType[]) {\n container.innerHTML = '';\n buttons = [];\n\n // Inject keyframe if not present\n if (!document.getElementById('authon-spin-style')) {\n const style = document.createElement('style');\n style.id = 'authon-spin-style';\n style.textContent = '@keyframes authon-spin{to{transform:rotate(360deg)}}';\n document.head.appendChild(style);\n }\n\n const wrapper = document.createElement('div');\n wrapper.style.display = 'flex';\n wrapper.style.gap = `${resolvedGap}px`;\n\n if (compact) {\n wrapper.style.flexDirection = 'row';\n wrapper.style.flexWrap = 'wrap';\n wrapper.style.justifyContent = 'center';\n } else {\n wrapper.style.flexDirection = 'column';\n }\n\n for (const provider of providerList) {\n const colors = PROVIDER_COLORS[provider] || { bg: '#333', text: '#fff' };\n const displayName = PROVIDER_DISPLAY_NAMES[provider] || provider;\n const config = getProviderButtonConfig(provider);\n const iconSvg = config.iconSvg\n .replace(/width=\"\\d+\"/, `width=\"${resolvedIconSize}\"`)\n .replace(/height=\"\\d+\"/, `height=\"${resolvedIconSize}\"`);\n const needsBorder = colors.bg.toLowerCase() === '#ffffff';\n\n const btn = document.createElement('button');\n btn.setAttribute('aria-label', `Sign in with ${displayName}`);\n btn.style.display = 'flex';\n btn.style.alignItems = 'center';\n btn.style.justifyContent = 'center';\n btn.style.border = needsBorder ? '1px solid #dadce0' : 'none';\n btn.style.cursor = 'pointer';\n btn.style.backgroundColor = colors.bg;\n btn.style.color = colors.text;\n btn.style.borderRadius = `${borderRadius}px`;\n btn.style.transition = 'opacity 0.15s';\n btn.style.fontFamily = 'inherit';\n\n if (compact) {\n btn.style.width = `${size}px`;\n btn.style.height = `${size}px`;\n btn.style.padding = '0';\n btn.innerHTML = `<span style=\"display:flex;align-items:center\">${iconSvg}</span>`;\n } else {\n btn.style.width = '100%';\n btn.style.height = `${height}px`;\n btn.style.gap = '10px';\n btn.style.paddingLeft = '16px';\n btn.style.paddingRight = '16px';\n const buttonLabel = labels?.[provider] ?? `Continue with ${displayName}`;\n btn.innerHTML = `<span style=\"display:flex;align-items:center;flex-shrink:0\">${iconSvg}</span><span style=\"font-size:15px;font-weight:600;white-space:nowrap\">${buttonLabel}</span>`;\n }\n\n btn.addEventListener('click', () => handleClick(provider, btn));\n btn.addEventListener('mouseenter', () => (btn.style.opacity = '0.85'));\n btn.addEventListener('mouseleave', () => (btn.style.opacity = '1'));\n\n buttons.push(btn);\n wrapper.appendChild(btn);\n }\n\n container.appendChild(wrapper);\n }\n\n // Init\n client.getProviders().then((p: OAuthProviderType[]) => {\n providers = p;\n if (providers.length > 0) {\n renderButtons(providers);\n }\n });\n\n // Cleanup\n return () => {\n container.innerHTML = '';\n buttons = [];\n };\n}\n"],"mappings":"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","names":["import_js"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Readable } from 'svelte/store';
2
2
  import { Authon, AuthonConfig } from '@authon/js';
3
- import { AuthonUser } from '@authon/shared';
3
+ import { AuthonUser, OAuthProviderType } from '@authon/shared';
4
4
 
5
5
  interface AuthonStore {
6
6
  user: Readable<AuthonUser | null>;
@@ -56,4 +56,57 @@ declare function initAuthon(publishableKey: string, config?: Omit<AuthonConfig,
56
56
  */
57
57
  declare function getAuthon(): AuthonStore;
58
58
 
59
- export { type AuthonStore, createAuthonStore, getAuthon, initAuthon };
59
+ interface SocialButtonsOptions {
60
+ /** Authon client instance */
61
+ client: Authon;
62
+ /** Target container element */
63
+ container: HTMLElement;
64
+ /** Called after successful OAuth sign-in */
65
+ onSuccess?: () => void;
66
+ /** Called on OAuth error */
67
+ onError?: (error: Error) => void;
68
+ /** Compact mode — icon-only square buttons in a row (default: false) */
69
+ compact?: boolean;
70
+ /** Gap between buttons in px (default: 10, compact default: 12) */
71
+ gap?: number;
72
+ /** Custom labels per provider */
73
+ labels?: Partial<Record<OAuthProviderType, string>>;
74
+ /** Icon size (default: 20, compact default: 24) */
75
+ iconSize?: number;
76
+ /** Border radius in px (default: 10) */
77
+ borderRadius?: number;
78
+ /** Button height in px (default: 48) */
79
+ height?: number;
80
+ /** Button size for compact mode in px (default: 48) */
81
+ size?: number;
82
+ }
83
+ /**
84
+ * Render social login buttons into a container element.
85
+ *
86
+ * Usage in +page.svelte:
87
+ * ```svelte
88
+ * <script>
89
+ * import { getAuthon } from '@authon/svelte'
90
+ * import { renderSocialButtons } from '@authon/svelte'
91
+ * import { onMount } from 'svelte'
92
+ *
93
+ * const { client } = getAuthon()
94
+ * let container: HTMLElement
95
+ *
96
+ * onMount(() => {
97
+ * const cleanup = renderSocialButtons({
98
+ * client,
99
+ * container,
100
+ * compact: true,
101
+ * onError: (err) => console.error(err),
102
+ * })
103
+ * return cleanup
104
+ * })
105
+ * </script>
106
+ *
107
+ * <div bind:this={container}></div>
108
+ * ```
109
+ */
110
+ declare function renderSocialButtons(options: SocialButtonsOptions): () => void;
111
+
112
+ export { type AuthonStore, type SocialButtonsOptions, createAuthonStore, getAuthon, initAuthon, renderSocialButtons };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Readable } from 'svelte/store';
2
2
  import { Authon, AuthonConfig } from '@authon/js';
3
- import { AuthonUser } from '@authon/shared';
3
+ import { AuthonUser, OAuthProviderType } from '@authon/shared';
4
4
 
5
5
  interface AuthonStore {
6
6
  user: Readable<AuthonUser | null>;
@@ -56,4 +56,57 @@ declare function initAuthon(publishableKey: string, config?: Omit<AuthonConfig,
56
56
  */
57
57
  declare function getAuthon(): AuthonStore;
58
58
 
59
- export { type AuthonStore, createAuthonStore, getAuthon, initAuthon };
59
+ interface SocialButtonsOptions {
60
+ /** Authon client instance */
61
+ client: Authon;
62
+ /** Target container element */
63
+ container: HTMLElement;
64
+ /** Called after successful OAuth sign-in */
65
+ onSuccess?: () => void;
66
+ /** Called on OAuth error */
67
+ onError?: (error: Error) => void;
68
+ /** Compact mode — icon-only square buttons in a row (default: false) */
69
+ compact?: boolean;
70
+ /** Gap between buttons in px (default: 10, compact default: 12) */
71
+ gap?: number;
72
+ /** Custom labels per provider */
73
+ labels?: Partial<Record<OAuthProviderType, string>>;
74
+ /** Icon size (default: 20, compact default: 24) */
75
+ iconSize?: number;
76
+ /** Border radius in px (default: 10) */
77
+ borderRadius?: number;
78
+ /** Button height in px (default: 48) */
79
+ height?: number;
80
+ /** Button size for compact mode in px (default: 48) */
81
+ size?: number;
82
+ }
83
+ /**
84
+ * Render social login buttons into a container element.
85
+ *
86
+ * Usage in +page.svelte:
87
+ * ```svelte
88
+ * <script>
89
+ * import { getAuthon } from '@authon/svelte'
90
+ * import { renderSocialButtons } from '@authon/svelte'
91
+ * import { onMount } from 'svelte'
92
+ *
93
+ * const { client } = getAuthon()
94
+ * let container: HTMLElement
95
+ *
96
+ * onMount(() => {
97
+ * const cleanup = renderSocialButtons({
98
+ * client,
99
+ * container,
100
+ * compact: true,
101
+ * onError: (err) => console.error(err),
102
+ * })
103
+ * return cleanup
104
+ * })
105
+ * </script>
106
+ *
107
+ * <div bind:this={container}></div>
108
+ * ```
109
+ */
110
+ declare function renderSocialButtons(options: SocialButtonsOptions): () => void;
111
+
112
+ export { type AuthonStore, type SocialButtonsOptions, createAuthonStore, getAuthon, initAuthon, renderSocialButtons };
package/dist/index.js CHANGED
@@ -52,9 +52,119 @@ function getAuthon() {
52
52
  }
53
53
  return store;
54
54
  }
55
+
56
+ // src/SocialButtons.ts
57
+ import { PROVIDER_COLORS, PROVIDER_DISPLAY_NAMES } from "@authon/shared";
58
+ import { getProviderButtonConfig } from "@authon/js";
59
+ function renderSocialButtons(options) {
60
+ const {
61
+ client,
62
+ container,
63
+ onSuccess,
64
+ onError,
65
+ compact = false,
66
+ gap,
67
+ labels,
68
+ iconSize,
69
+ borderRadius = 10,
70
+ height = 48,
71
+ size = 48
72
+ } = options;
73
+ const resolvedGap = gap ?? (compact ? 12 : 10);
74
+ const resolvedIconSize = iconSize ?? (compact ? 24 : 20);
75
+ let loadingProvider = null;
76
+ let buttons = [];
77
+ const handleClick = async (provider, btn) => {
78
+ if (loadingProvider) return;
79
+ loadingProvider = provider;
80
+ btn.innerHTML = '<span style="display:inline-block;width:16px;height:16px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:authon-spin 0.6s linear infinite"></span>';
81
+ buttons.forEach((b) => b.disabled = true);
82
+ try {
83
+ await client.signInWithOAuth(provider);
84
+ onSuccess?.();
85
+ } catch (e) {
86
+ const error = e instanceof Error ? e : new Error(String(e));
87
+ onError?.(error);
88
+ } finally {
89
+ loadingProvider = null;
90
+ renderButtons(providers);
91
+ }
92
+ };
93
+ let providers = [];
94
+ function renderButtons(providerList) {
95
+ container.innerHTML = "";
96
+ buttons = [];
97
+ if (!document.getElementById("authon-spin-style")) {
98
+ const style = document.createElement("style");
99
+ style.id = "authon-spin-style";
100
+ style.textContent = "@keyframes authon-spin{to{transform:rotate(360deg)}}";
101
+ document.head.appendChild(style);
102
+ }
103
+ const wrapper = document.createElement("div");
104
+ wrapper.style.display = "flex";
105
+ wrapper.style.gap = `${resolvedGap}px`;
106
+ if (compact) {
107
+ wrapper.style.flexDirection = "row";
108
+ wrapper.style.flexWrap = "wrap";
109
+ wrapper.style.justifyContent = "center";
110
+ } else {
111
+ wrapper.style.flexDirection = "column";
112
+ }
113
+ for (const provider of providerList) {
114
+ const colors = PROVIDER_COLORS[provider] || { bg: "#333", text: "#fff" };
115
+ const displayName = PROVIDER_DISPLAY_NAMES[provider] || provider;
116
+ const config = getProviderButtonConfig(provider);
117
+ const iconSvg = config.iconSvg.replace(/width="\d+"/, `width="${resolvedIconSize}"`).replace(/height="\d+"/, `height="${resolvedIconSize}"`);
118
+ const needsBorder = colors.bg.toLowerCase() === "#ffffff";
119
+ const btn = document.createElement("button");
120
+ btn.setAttribute("aria-label", `Sign in with ${displayName}`);
121
+ btn.style.display = "flex";
122
+ btn.style.alignItems = "center";
123
+ btn.style.justifyContent = "center";
124
+ btn.style.border = needsBorder ? "1px solid #dadce0" : "none";
125
+ btn.style.cursor = "pointer";
126
+ btn.style.backgroundColor = colors.bg;
127
+ btn.style.color = colors.text;
128
+ btn.style.borderRadius = `${borderRadius}px`;
129
+ btn.style.transition = "opacity 0.15s";
130
+ btn.style.fontFamily = "inherit";
131
+ if (compact) {
132
+ btn.style.width = `${size}px`;
133
+ btn.style.height = `${size}px`;
134
+ btn.style.padding = "0";
135
+ btn.innerHTML = `<span style="display:flex;align-items:center">${iconSvg}</span>`;
136
+ } else {
137
+ btn.style.width = "100%";
138
+ btn.style.height = `${height}px`;
139
+ btn.style.gap = "10px";
140
+ btn.style.paddingLeft = "16px";
141
+ btn.style.paddingRight = "16px";
142
+ const buttonLabel = labels?.[provider] ?? `Continue with ${displayName}`;
143
+ btn.innerHTML = `<span style="display:flex;align-items:center;flex-shrink:0">${iconSvg}</span><span style="font-size:15px;font-weight:600;white-space:nowrap">${buttonLabel}</span>`;
144
+ }
145
+ btn.addEventListener("click", () => handleClick(provider, btn));
146
+ btn.addEventListener("mouseenter", () => btn.style.opacity = "0.85");
147
+ btn.addEventListener("mouseleave", () => btn.style.opacity = "1");
148
+ buttons.push(btn);
149
+ wrapper.appendChild(btn);
150
+ }
151
+ container.appendChild(wrapper);
152
+ }
153
+ client.getProviders().then((p) => {
154
+ providers = p;
155
+ if (providers.length > 0) {
156
+ renderButtons(providers);
157
+ }
158
+ });
159
+ return () => {
160
+ container.innerHTML = "";
161
+ buttons = [];
162
+ };
163
+ }
55
164
  export {
56
165
  createAuthonStore,
57
166
  getAuthon,
58
- initAuthon
167
+ initAuthon,
168
+ renderSocialButtons
59
169
  };
60
170
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/store.ts","../src/context.ts"],"sourcesContent":["import { writable, derived, type Readable } from 'svelte/store';\nimport { Authon } from '@authon/js';\nimport type { AuthonConfig } from '@authon/js';\nimport type { AuthonUser } from '@authon/shared';\n\nexport interface AuthonStore {\n user: Readable<AuthonUser | null>;\n isSignedIn: Readable<boolean>;\n isLoading: Readable<boolean>;\n signOut: () => Promise<void>;\n openSignIn: () => Promise<void>;\n openSignUp: () => Promise<void>;\n getToken: () => string | null;\n destroy: () => void;\n client: Authon;\n}\n\n/**\n * Creates an Authon store with reactive Svelte stores.\n *\n * Usage:\n * ```ts\n * import { createAuthonStore } from '@authon/svelte'\n *\n * const authon = createAuthonStore('pk_live_...')\n *\n * // In your component:\n * $: user = $authon.user\n * $: isSignedIn = $authon.isSignedIn\n * ```\n */\nexport function createAuthonStore(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const client = new Authon(publishableKey, config);\n const userStore = writable<AuthonUser | null>(null);\n const isLoadingStore = writable(true);\n\n const isSignedIn = derived(userStore, ($user) => $user !== null);\n\n client.on('signedIn', (user) => {\n userStore.set(user as AuthonUser);\n isLoadingStore.set(false);\n });\n\n client.on('signedOut', () => {\n userStore.set(null);\n });\n\n client.on('error', () => {\n isLoadingStore.set(false);\n });\n\n const existingUser = client.getUser();\n if (existingUser) {\n userStore.set(existingUser);\n }\n isLoadingStore.set(false);\n\n return {\n user: { subscribe: userStore.subscribe },\n isSignedIn,\n isLoading: { subscribe: isLoadingStore.subscribe },\n signOut: async () => {\n await client.signOut();\n userStore.set(null);\n },\n openSignIn: () => client.openSignIn(),\n openSignUp: () => client.openSignUp(),\n getToken: () => client.getToken(),\n destroy: () => client.destroy(),\n client,\n };\n}\n","import { setContext, getContext } from 'svelte';\nimport type { AuthonConfig } from '@authon/js';\nimport { createAuthonStore, type AuthonStore } from './store';\n\nconst AUTHON_CONTEXT_KEY = Symbol('authon');\n\n/**\n * Initialize Authon in a Svelte component tree.\n * Call this in your root layout or top-level component.\n *\n * Usage in +layout.svelte:\n * ```svelte\n * <script>\n * import { initAuthon } from '@authon/svelte'\n * const authon = initAuthon('pk_live_...')\n * </script>\n * ```\n */\nexport function initAuthon(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const store = createAuthonStore(publishableKey, config);\n setContext(AUTHON_CONTEXT_KEY, store);\n return store;\n}\n\n/**\n * Get the Authon store from Svelte context.\n * Must be called within a component tree where `initAuthon` was called.\n *\n * Usage:\n * ```svelte\n * <script>\n * import { getAuthon } from '@authon/svelte'\n * const { user, isSignedIn, signOut } = getAuthon()\n * </script>\n * ```\n */\nexport function getAuthon(): AuthonStore {\n const store = getContext<AuthonStore | undefined>(AUTHON_CONTEXT_KEY);\n if (!store) {\n throw new Error('getAuthon() must be called within a component tree where initAuthon() was called.');\n }\n return store;\n}\n"],"mappings":";AAAA,SAAS,UAAU,eAA8B;AACjD,SAAS,cAAc;AA8BhB,SAAS,kBACd,gBACA,QACa;AACb,QAAM,SAAS,IAAI,OAAO,gBAAgB,MAAM;AAChD,QAAM,YAAY,SAA4B,IAAI;AAClD,QAAM,iBAAiB,SAAS,IAAI;AAEpC,QAAM,aAAa,QAAQ,WAAW,CAAC,UAAU,UAAU,IAAI;AAE/D,SAAO,GAAG,YAAY,CAAC,SAAS;AAC9B,cAAU,IAAI,IAAkB;AAChC,mBAAe,IAAI,KAAK;AAAA,EAC1B,CAAC;AAED,SAAO,GAAG,aAAa,MAAM;AAC3B,cAAU,IAAI,IAAI;AAAA,EACpB,CAAC;AAED,SAAO,GAAG,SAAS,MAAM;AACvB,mBAAe,IAAI,KAAK;AAAA,EAC1B,CAAC;AAED,QAAM,eAAe,OAAO,QAAQ;AACpC,MAAI,cAAc;AAChB,cAAU,IAAI,YAAY;AAAA,EAC5B;AACA,iBAAe,IAAI,KAAK;AAExB,SAAO;AAAA,IACL,MAAM,EAAE,WAAW,UAAU,UAAU;AAAA,IACvC;AAAA,IACA,WAAW,EAAE,WAAW,eAAe,UAAU;AAAA,IACjD,SAAS,YAAY;AACnB,YAAM,OAAO,QAAQ;AACrB,gBAAU,IAAI,IAAI;AAAA,IACpB;AAAA,IACA,YAAY,MAAM,OAAO,WAAW;AAAA,IACpC,YAAY,MAAM,OAAO,WAAW;AAAA,IACpC,UAAU,MAAM,OAAO,SAAS;AAAA,IAChC,SAAS,MAAM,OAAO,QAAQ;AAAA,IAC9B;AAAA,EACF;AACF;;;AC1EA,SAAS,YAAY,kBAAkB;AAIvC,IAAM,qBAAqB,uBAAO,QAAQ;AAcnC,SAAS,WACd,gBACA,QACa;AACb,QAAM,QAAQ,kBAAkB,gBAAgB,MAAM;AACtD,aAAW,oBAAoB,KAAK;AACpC,SAAO;AACT;AAcO,SAAS,YAAyB;AACvC,QAAM,QAAQ,WAAoC,kBAAkB;AACpE,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,mFAAmF;AAAA,EACrG;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/store.ts","../src/context.ts","../src/SocialButtons.ts"],"sourcesContent":["import { writable, derived, type Readable } from 'svelte/store';\nimport { Authon } from '@authon/js';\nimport type { AuthonConfig } from '@authon/js';\nimport type { AuthonUser } from '@authon/shared';\n\nexport interface AuthonStore {\n user: Readable<AuthonUser | null>;\n isSignedIn: Readable<boolean>;\n isLoading: Readable<boolean>;\n signOut: () => Promise<void>;\n openSignIn: () => Promise<void>;\n openSignUp: () => Promise<void>;\n getToken: () => string | null;\n destroy: () => void;\n client: Authon;\n}\n\n/**\n * Creates an Authon store with reactive Svelte stores.\n *\n * Usage:\n * ```ts\n * import { createAuthonStore } from '@authon/svelte'\n *\n * const authon = createAuthonStore('pk_live_...')\n *\n * // In your component:\n * $: user = $authon.user\n * $: isSignedIn = $authon.isSignedIn\n * ```\n */\nexport function createAuthonStore(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const client = new Authon(publishableKey, config);\n const userStore = writable<AuthonUser | null>(null);\n const isLoadingStore = writable(true);\n\n const isSignedIn = derived(userStore, ($user) => $user !== null);\n\n client.on('signedIn', (user) => {\n userStore.set(user as AuthonUser);\n isLoadingStore.set(false);\n });\n\n client.on('signedOut', () => {\n userStore.set(null);\n });\n\n client.on('error', () => {\n isLoadingStore.set(false);\n });\n\n const existingUser = client.getUser();\n if (existingUser) {\n userStore.set(existingUser);\n }\n isLoadingStore.set(false);\n\n return {\n user: { subscribe: userStore.subscribe },\n isSignedIn,\n isLoading: { subscribe: isLoadingStore.subscribe },\n signOut: async () => {\n await client.signOut();\n userStore.set(null);\n },\n openSignIn: () => client.openSignIn(),\n openSignUp: () => client.openSignUp(),\n getToken: () => client.getToken(),\n destroy: () => client.destroy(),\n client,\n };\n}\n","import { setContext, getContext } from 'svelte';\nimport type { AuthonConfig } from '@authon/js';\nimport { createAuthonStore, type AuthonStore } from './store';\n\nconst AUTHON_CONTEXT_KEY = Symbol('authon');\n\n/**\n * Initialize Authon in a Svelte component tree.\n * Call this in your root layout or top-level component.\n *\n * Usage in +layout.svelte:\n * ```svelte\n * <script>\n * import { initAuthon } from '@authon/svelte'\n * const authon = initAuthon('pk_live_...')\n * </script>\n * ```\n */\nexport function initAuthon(\n publishableKey: string,\n config?: Omit<AuthonConfig, 'mode'>,\n): AuthonStore {\n const store = createAuthonStore(publishableKey, config);\n setContext(AUTHON_CONTEXT_KEY, store);\n return store;\n}\n\n/**\n * Get the Authon store from Svelte context.\n * Must be called within a component tree where `initAuthon` was called.\n *\n * Usage:\n * ```svelte\n * <script>\n * import { getAuthon } from '@authon/svelte'\n * const { user, isSignedIn, signOut } = getAuthon()\n * </script>\n * ```\n */\nexport function getAuthon(): AuthonStore {\n const store = getContext<AuthonStore | undefined>(AUTHON_CONTEXT_KEY);\n if (!store) {\n throw new Error('getAuthon() must be called within a component tree where initAuthon() was called.');\n }\n return store;\n}\n","import { PROVIDER_COLORS, PROVIDER_DISPLAY_NAMES, type OAuthProviderType } from '@authon/shared';\nimport { getProviderButtonConfig, type Authon } from '@authon/js';\n\nexport interface SocialButtonsOptions {\n /** Authon client instance */\n client: Authon;\n /** Target container element */\n container: HTMLElement;\n /** Called after successful OAuth sign-in */\n onSuccess?: () => void;\n /** Called on OAuth error */\n onError?: (error: Error) => void;\n /** Compact mode — icon-only square buttons in a row (default: false) */\n compact?: boolean;\n /** Gap between buttons in px (default: 10, compact default: 12) */\n gap?: number;\n /** Custom labels per provider */\n labels?: Partial<Record<OAuthProviderType, string>>;\n /** Icon size (default: 20, compact default: 24) */\n iconSize?: number;\n /** Border radius in px (default: 10) */\n borderRadius?: number;\n /** Button height in px (default: 48) */\n height?: number;\n /** Button size for compact mode in px (default: 48) */\n size?: number;\n}\n\n/**\n * Render social login buttons into a container element.\n *\n * Usage in +page.svelte:\n * ```svelte\n * <script>\n * import { getAuthon } from '@authon/svelte'\n * import { renderSocialButtons } from '@authon/svelte'\n * import { onMount } from 'svelte'\n *\n * const { client } = getAuthon()\n * let container: HTMLElement\n *\n * onMount(() => {\n * const cleanup = renderSocialButtons({\n * client,\n * container,\n * compact: true,\n * onError: (err) => console.error(err),\n * })\n * return cleanup\n * })\n * </script>\n *\n * <div bind:this={container}></div>\n * ```\n */\nexport function renderSocialButtons(options: SocialButtonsOptions): () => void {\n const {\n client,\n container,\n onSuccess,\n onError,\n compact = false,\n gap,\n labels,\n iconSize,\n borderRadius = 10,\n height = 48,\n size = 48,\n } = options;\n\n const resolvedGap = gap ?? (compact ? 12 : 10);\n const resolvedIconSize = iconSize ?? (compact ? 24 : 20);\n let loadingProvider: string | null = null;\n let buttons: HTMLButtonElement[] = [];\n\n const handleClick = async (provider: OAuthProviderType, btn: HTMLButtonElement) => {\n if (loadingProvider) return;\n loadingProvider = provider;\n btn.innerHTML = '<span style=\"display:inline-block;width:16px;height:16px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:authon-spin 0.6s linear infinite\"></span>';\n buttons.forEach((b) => (b.disabled = true));\n\n try {\n await client.signInWithOAuth(provider);\n onSuccess?.();\n } catch (e: any) {\n const error = e instanceof Error ? e : new Error(String(e));\n onError?.(error);\n } finally {\n loadingProvider = null;\n renderButtons(providers);\n }\n };\n\n let providers: OAuthProviderType[] = [];\n\n function renderButtons(providerList: OAuthProviderType[]) {\n container.innerHTML = '';\n buttons = [];\n\n // Inject keyframe if not present\n if (!document.getElementById('authon-spin-style')) {\n const style = document.createElement('style');\n style.id = 'authon-spin-style';\n style.textContent = '@keyframes authon-spin{to{transform:rotate(360deg)}}';\n document.head.appendChild(style);\n }\n\n const wrapper = document.createElement('div');\n wrapper.style.display = 'flex';\n wrapper.style.gap = `${resolvedGap}px`;\n\n if (compact) {\n wrapper.style.flexDirection = 'row';\n wrapper.style.flexWrap = 'wrap';\n wrapper.style.justifyContent = 'center';\n } else {\n wrapper.style.flexDirection = 'column';\n }\n\n for (const provider of providerList) {\n const colors = PROVIDER_COLORS[provider] || { bg: '#333', text: '#fff' };\n const displayName = PROVIDER_DISPLAY_NAMES[provider] || provider;\n const config = getProviderButtonConfig(provider);\n const iconSvg = config.iconSvg\n .replace(/width=\"\\d+\"/, `width=\"${resolvedIconSize}\"`)\n .replace(/height=\"\\d+\"/, `height=\"${resolvedIconSize}\"`);\n const needsBorder = colors.bg.toLowerCase() === '#ffffff';\n\n const btn = document.createElement('button');\n btn.setAttribute('aria-label', `Sign in with ${displayName}`);\n btn.style.display = 'flex';\n btn.style.alignItems = 'center';\n btn.style.justifyContent = 'center';\n btn.style.border = needsBorder ? '1px solid #dadce0' : 'none';\n btn.style.cursor = 'pointer';\n btn.style.backgroundColor = colors.bg;\n btn.style.color = colors.text;\n btn.style.borderRadius = `${borderRadius}px`;\n btn.style.transition = 'opacity 0.15s';\n btn.style.fontFamily = 'inherit';\n\n if (compact) {\n btn.style.width = `${size}px`;\n btn.style.height = `${size}px`;\n btn.style.padding = '0';\n btn.innerHTML = `<span style=\"display:flex;align-items:center\">${iconSvg}</span>`;\n } else {\n btn.style.width = '100%';\n btn.style.height = `${height}px`;\n btn.style.gap = '10px';\n btn.style.paddingLeft = '16px';\n btn.style.paddingRight = '16px';\n const buttonLabel = labels?.[provider] ?? `Continue with ${displayName}`;\n btn.innerHTML = `<span style=\"display:flex;align-items:center;flex-shrink:0\">${iconSvg}</span><span style=\"font-size:15px;font-weight:600;white-space:nowrap\">${buttonLabel}</span>`;\n }\n\n btn.addEventListener('click', () => handleClick(provider, btn));\n btn.addEventListener('mouseenter', () => (btn.style.opacity = '0.85'));\n btn.addEventListener('mouseleave', () => (btn.style.opacity = '1'));\n\n buttons.push(btn);\n wrapper.appendChild(btn);\n }\n\n container.appendChild(wrapper);\n }\n\n // Init\n client.getProviders().then((p: OAuthProviderType[]) => {\n providers = p;\n if (providers.length > 0) {\n renderButtons(providers);\n }\n });\n\n // Cleanup\n return () => {\n container.innerHTML = '';\n buttons = [];\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authon/svelte",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Authon Svelte SDK — stores and components",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -37,8 +37,8 @@
37
37
  "store"
38
38
  ],
39
39
  "dependencies": {
40
- "@authon/js": "workspace:^",
41
- "@authon/shared": "workspace:^"
40
+ "@authon/js": "^0.2.1",
41
+ "@authon/shared": "^0.2.0"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "svelte": "^4.0.0 || ^5.0.0"